using System.IO;
using eyecm.PhysX;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Index = System.Int32;

namespace XELF.PhysX.ClothSample {

	partial class PhysicsWorld : System.IDisposable {
		readonly Physics core = Physics.Create();
		readonly Scene scene;

		public Physics Core { get { return core; } }
		public Scene Scene { get { return scene; } }

		public PhysicsWorld() {
#if DEBUG
			//Core.Foundation.RemoteDebugger.Connect("localhost", 5425, RemoteDebuggerEventMask.Everything);
#endif
			SceneDesc sceneDesc = new SceneDesc();
			sceneDesc.SimulationType = SimulationTypes.Software;
			sceneDesc.Gravity = new Vector3(0.0f, -9.81f, 0.0f);
			sceneDesc.GroundPlane = true;

			// デバッグ可視化用の設定
			core.Parameters.VisualizationScale = 16;
			core.Parameters.VisualizeActorAxes = 1;
			core.Parameters.VisualizeBodyAxes = 1;
			core.Parameters.VisualizeClothMesh = 1;
			core.Parameters.VisualizeCollisionShapes = 1;
			core.Parameters.VisualizeWorldAxes = 1;

			scene = core.CreateScene(sceneDesc);
		}

		public void Update(float elaspedTime) {
			scene.Simulate(elaspedTime);
			scene.FlushStream();
			scene.FetchResults(SimulationStatuses.AllFinished, true);
		}

		public void Dispose() {
			core.Dispose();
		}
	}

	#region ConvexMesh関連
	partial class PhysicsWorld {
		ConvexMesh CookConvexMesh(ConvexMeshDesc description) {
			using (var stream = new MemoryStream()) {
				CookingInterface.InitCooking();
				CookingInterface.CookConvexMesh(description, stream);
				CookingInterface.CloseCooking();
				// ストリームの先頭位置に戻す。
				stream.Position = 0;
				return core.CreateConvexMesh(stream);
			}
		}
		Actor CreateConvexActor(ConvexMeshDesc meshDescription) {
			var mesh = CookConvexMesh(meshDescription);
			var shapeDescription = new ConvexShapeDesc(mesh);
			{
				shapeDescription.ShapeFlags = ShapeFlags.PointContactForce | ShapeFlags.ClothTwoway
					| ShapeFlags.SoftbodyTwoway | ShapeFlags.Visualization;
				shapeDescription.SkinWidth = 0.01f;
				ActorDesc actorDescription = new ActorDesc(shapeDescription);
				actorDescription.Body = new BodyDesc { Mass = 1000000, };
				actorDescription.GlobalPose = Matrix.CreateTranslation(0, 20, 0);
				return scene.CreateActor(actorDescription);
			}
		}
		public Actor CreateConvexActorFromMesh(Mesh mesh) {
			var description = new ConvexMeshDesc();
			{
				description.PinPoints(mesh.Vertices, 0, 0);
				description.PinTriangles<Index>(mesh.Indices, 0, 0);
				description.VertexCount = (uint)mesh.Vertices.Length;
				description.TriangleCount = (uint)mesh.Indices.Length / 3;
				description.Flags = ConvexFlags.ComputeConvex;
				return CreateConvexActor(description);
			}
		}
	}
	#endregion

	#region TriangleMesh関連
	partial class PhysicsWorld {
		TriangleMesh CookTriangleMesh(TriangleMeshDesc description) {
			using (var stream = new MemoryStream()) {
				CookingInterface.InitCooking();
				CookingInterface.CookTriangleMesh(description, stream);
				CookingInterface.CloseCooking();
				// ストリームの先頭位置に戻す。
				stream.Position = 0;
				return core.CreateTriangleMesh(stream);
			}
		}
		Actor CreateTriangleActor(TriangleMeshDesc meshDescription) {
			var mesh = CookTriangleMesh(meshDescription);
			var shapeDescription = new TriangleMeshShapeDesc() { TriangleMesh = mesh, };
			shapeDescription.ShapeFlags = ShapeFlags.PointContactForce | ShapeFlags.ClothTwoway
				| ShapeFlags.SoftbodyTwoway | ShapeFlags.Visualization;
			shapeDescription.MeshFlags = MeshShapeFlags.DoubleSided | MeshShapeFlags.SmoothSphereCollisions;
			shapeDescription.SkinWidth = 0.01f;
			var actorDescription = new ActorDesc(shapeDescription);
			actorDescription.Body = new BodyDesc { Mass = 1000000, };
			actorDescription.GlobalPose = Matrix.CreateTranslation(0, 20, 0);
			return scene.CreateActor(actorDescription);
		}
		public Actor CreateTriangleActorFromMesh(Mesh mesh) {
			TriangleMeshDesc description = new TriangleMeshDesc();
			description.PinPoints<VertexPositionNormalTexture>(mesh.Vertices, 0, 0);
			description.PinTriangles<Index>(mesh.Indices, 0, 0);
			description.VertexCount = (uint)mesh.Vertices.Length;
			description.TriangleCount = (uint)mesh.Indices.Length / 3;
			return CreateTriangleActor(description);
		}
	}
	#endregion

	#region ClothMesh関連
	partial class PhysicsWorld {
		/// <summary>
		/// 布のメッシュを作る。
		/// </summary>
		/// <param name="clothMeshDesc"></param>
		/// <returns></returns>
		ClothMesh CookClothMesh(ClothMeshDesc clothMeshDesc) {
			using (var stream = new MemoryStream()) {

				CookingInterface.InitCooking();
				CookingInterface.CookClothMesh(clothMeshDesc, stream);
				CookingInterface.CloseCooking();

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

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

		/// <summary>
		/// 布を作る。
		/// </summary>
		/// <param name="clothMeshDesc"></param>
		/// <returns></returns>
		Cloth CreateCloth(ClothMeshDesc clothMeshDesc, Mesh mesh) {
			var clothMesh = CookClothMesh(clothMeshDesc);
			var clothDescription = new ClothDesc { ClothMesh = clothMesh, };
			{
				clothDescription.Thickness = 0.01f;
				clothDescription.SolverIterationCount = 5;
				clothDescription.Friction = 1.0f;
				clothDescription.BendingStiffness = 0.00001f;
				clothDescription.DampingCoefficient = 0.01f;
				clothDescription.StretchingStiffness = 1;
				clothDescription.Flags = ClothFlags.TriangleCollision | ClothFlags.SelfCollision
					| ClothFlags.Gravity | ClothFlags.Bending | ClothFlags.Damping
					| ClothFlags.CollisionTwoway | ClothFlags.Visualization;
				clothDescription.GlobalPose =
					Matrix.CreateScale(1.0f) *
					Matrix.CreateTranslation(0, 20, 0);

				// 物理演算されたメッシュのデータを受け取るバッファの設定
				clothDescription.MeshData.PinVertices<VertexPositionNormalTexture>(mesh.Vertices, 0, 12, 32);
				clothDescription.MeshData.PinIndices<Index>(mesh.Indices, 0, 4);
				clothDescription.MeshData.MaxVertexCount = (uint)mesh.Vertices.Length;
				clothDescription.MeshData.MaxIndexCount = (uint)mesh.Indices.Length;

				return scene.CreateCloth(clothDescription);
			}
		}

		public Cloth CreateClothFromMesh(Mesh mesh) {
			var description = new ClothMeshDesc();
			{
				description.PinPoints<VertexPositionNormalTexture>(mesh.Vertices, 0, 0);
				description.PinTriangles<Index>(mesh.Indices, 0, 12);

				description.VertexCount = (uint)(mesh.Vertices.Length);
				description.TriangleCount = (uint)(mesh.Indices.Length / 3);

				description.WeldingDistance = 0.0f;
				description.Flags = (MeshFlags)(1 << 9) | MeshFlags.FlipNormals;

				return CreateCloth(description, mesh);
			}
		}

	}
	#endregion

}
