using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using StillDesign.PhysX;

namespace XELF.PhysX.ClothSample {

	public class ClothGame1 : Microsoft.Xna.Framework.Game {
		readonly GraphicsDeviceManager graphics;
		SpriteBatch spriteBatch;

		readonly Core core = new Core();
		Scene scene;
		Cloth cloth;

		BasicEffect effect;
		VertexDeclaration declaration;

		Mesh mesh = Mesh.CreateGrid(20, 20);

		/// <summary>
		/// 布のメッシュを作る。
		/// </summary>
		/// <param name="description"></param>
		/// <returns></returns>
		ClothMesh CookClothMesh(ClothMeshDescription clothMeshDesc) {
			var memoryStream = new MemoryStream();

			Cooking.InitializeCooking();
			Cooking.CookClothMesh(clothMeshDesc, memoryStream);
			Cooking.CloseCooking();

			// ストリームの先頭位置に戻す。
			memoryStream.Position = 0;

			ClothMesh clothMesh = core.CreateClothMesh(memoryStream);
			return clothMesh;
		}

		/// <summary>
		/// 布を作る。
		/// </summary>
		/// <param name="description"></param>
		/// <returns></returns>
		Cloth CreateCloth(ClothMeshDescription clothMeshDesc) {
			var clothMesh = CookClothMesh(clothMeshDesc);

			var clothDescription = new ClothDescription();
			clothDescription.ClothMesh = clothMesh;
			clothDescription.Flags =
				ClothFlag.Gravity | ClothFlag.Bending | ClothFlag.CollisionTwoway | ClothFlag.Visualization;
			clothDescription.GlobalPose =
				Matrix.CreateTranslation(0, 20, 0);

			// 物理演算されたメッシュのデータを受け取るバッファの設定
			clothDescription.MeshData.AllocatePositions<Vector3>(mesh.Points.Length);
			clothDescription.MeshData.AllocateIndices<int>(mesh.Indices.Length);
			clothDescription.MeshData.MaximumVertices = mesh.Points.Length;
			clothDescription.MeshData.MaximumIndices = mesh.Indices.Length;
			// （StillDesign.PhysX.Netの新バージョンに対応するための追加の処理）
			clothDescription.MeshData.NumberOfVertices = mesh.Points.Length;
			clothDescription.MeshData.NumberOfIndices = mesh.Indices.Length;

			return scene.CreateCloth(clothDescription);
		}

		Cloth CreateClothFromMesh(Mesh mesh) {
			ClothMeshDescription description = new ClothMeshDescription();
			description.AllocateVertices<Vector3>(mesh.Points.Length);
			description.AllocateTriangles<int>(mesh.Indices.Length / 3);

			description.VertexCount = mesh.Points.Length;
			description.TriangleCount = mesh.Indices.Length / 3;

			description.VerticesStream.SetData<Vector3>(mesh.Points);
			description.TriangleStream.SetData<int>(mesh.Indices);

			return CreateCloth(description);
		}

		public ClothGame1() {
			// PhysXのVisual Remote Debuggerに接続する。
			core.Foundation.RemoteDebugger.Connect("localhost", 5425, RemoteDebuggerEventMask.Everything);

			graphics = new GraphicsDeviceManager(this);
			graphics.PreferMultiSampling = true;
			Content.RootDirectory = "Content";

			SceneDescription sceneDesc = new SceneDescription();
			sceneDesc.SimulationType = SimulationType.Software;
			sceneDesc.Gravity = new Vector3(0.0f, -9.81f, 0.0f);
			sceneDesc.GroundPlaneEnabled = true;
			scene = core.CreateScene(sceneDesc);
		}

		protected override void LoadContent() {
			spriteBatch = new SpriteBatch(GraphicsDevice);
			effect = new BasicEffect(GraphicsDevice, null);
			declaration = new VertexDeclaration(GraphicsDevice, new VertexElement[]{
				new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default,
					VertexElementUsage.Position, 0),
			});
			cloth = CreateClothFromMesh(mesh);
			cloth.Name = "Cloth1";
			cloth.AttachVertexToGlobalPosition(0, new Vector3(0, 20, 0));
		}

		protected override void Update(GameTime gameTime) {
			if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
				this.Exit();

			cloth.WindAcceleration = new Vector3(10, (float)gameTime.TotalGameTime.TotalSeconds % 10, 10);

			scene.Simulate(1.0f / 60);
			scene.FlushStream();
			scene.FetchResults(SimulationStatus.RigidBodyFinished, true);

			// 計算された頂点の位置を得る。
			mesh.Points = cloth.ParticlePositions;

			effect.World = Matrix.Identity;
			effect.View = Matrix.CreateLookAt(new Vector3(0, 0, 40), new Vector3(10, 10, 0), Vector3.Up);
			effect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
				GraphicsDevice.Viewport.AspectRatio, 1, 1000);
			base.Update(gameTime);
		}

		protected override void Draw(GameTime gameTime) {
			GraphicsDevice.Clear(Color.CornflowerBlue);
			GraphicsDevice.RenderState.FillMode = FillMode.WireFrame;
			GraphicsDevice.RenderState.CullMode = CullMode.None;

			effect.Begin();
			foreach (var pass in effect.CurrentTechnique.Passes) {
				pass.Begin();
				GraphicsDevice.VertexDeclaration = declaration;
				GraphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList,
					mesh.Points, 0, mesh.Points.Length, mesh.Indices, 0, mesh.Indices.Length / 3);
				pass.End();
			}
			effect.End();

			base.Draw(gameTime);
		}
	}
}
