#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2009 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
namespace Nuclex.Geometry.Volumes {
/// Three-dimensional triangle mesh
public class TriangleMesh3 : IVolume3 {
/// Initializes a triangle mesh from a sequentiel list of triangles
/// Array of vertices to construct triangles from
///
/// This variant of the constructor generates a mesh from the vertex array by
/// building triangles of every three vertices in the list. The triangles are
/// required to form a closed, convex hull.
///
TriangleMesh3(Vector3[] vertices)
: this(vertices, generateIndices(vertices.Length)) { }
/// Initializes a triangle mesh from an indexed list of triangles
/// Vertices to construct triangles from
/// Indices to the vertices to construct triangles from
///
/// This variant of the constructor generates a mesh from the vertex array by
/// taking every three indices in the index array and then generating a triangle
/// by the three vertices with the given index. The triangles are required to
/// form a closed, convex hull.
///
TriangleMesh3(Vector3[] vertices, int[] indices) {
this.Vertices = vertices;
this.Indices = indices;
computeMassProperties();
}
/// Accepts a visitor to access the concrete volume implementation
/// Visitor to be accepted
public void Accept(VolumeVisitor visitor) {
visitor.Visit(this);
}
/// Smallest box that encloses the volume in its entirety
///
/// This always produces an optimal box which means a tight-fitting box is generated
/// that will touch the volume on each of its six sides. As a side effect, it is very
/// likely that this box needs to be recalculated whenever the volume changes its
/// orientation.
///
public AxisAlignedBox3 BoundingBox {
get {
if(this.Vertices == null) {
return new AxisAlignedBox3(Vector3.Zero, Vector3.Zero);
} else {
Vector3 min, max;
min = max = this.Vertices[0];
for(int index = 0; index < this.Vertices.Length; ++index) {
Vector3 vertex = this.Vertices[index];
if(vertex.X < min.X)
min.X = vertex.X;
if(vertex.Y < min.Y)
min.Y = vertex.Y;
if(vertex.Z < min.Z)
min.Z = vertex.Z;
if(vertex.X > max.X)
max.X = vertex.X;
if(vertex.Y > max.Y)
max.Y = vertex.Y;
if(vertex.Z > max.Z)
max.Z = vertex.Z;
}
return new AxisAlignedBox3(min, max);
}
}
}
/// Smallest sphere that encloses the volume in its entirety
///
/// Bounding spheres have the advantage to not change even when the volume is
/// rotated. That makes them ideal for dynamic objects that are not keeping their
/// original orientation.
///
public Sphere3 BoundingSphere {
get { throw new Exception("The method or operation is not implemented."); }
}
/// Amount of mass that the volume contains
public float Mass {
get { return this.mass; }
}
/// The volume's total surface area
public float SurfaceArea {
get { return this.surfaceArea; }
}
/// Center of the volume's mass
public Vector3 CenterOfMass {
get { return this.centerOfMass; }
}
/// The inetria tensor matrix of the volume
public Matrix InertiaTensor {
get { return this.inertiaTensor; }
}
/// Locates the nearest point in the volume to some arbitrary location
/// Location to which the closest point is determined
/// The closest point in the volume to the specified location
public Vector3 ClosestPointTo(Vector3 location) {
throw new Exception("The method or operation is not implemented.");
}
/// Determines if the volume clips the circle
/// Circle that will be checked for intersection
/// True if the objects overlap
public bool Intersects(Sphere3 sphere) {
return Collisions.MeshSphereCollider.CheckContact(
this, sphere.Center, sphere.Radius
);
}
/// Determines if the volume clips the axis aligned box
/// Box that will be checked for intersection
/// True if the objects overlap
public bool Intersects(AxisAlignedBox3 box) {
return Collisions.AabbMeshCollider.CheckContact(
box.Min, box.Max, this
);
}
/// Determines if the volume clips the box
/// Box that will be checked for intersection
/// True if the objects overlap
public bool Intersects(Box3 box) {
return Collisions.MeshObbCollider.CheckContact(
this, box.Transform, box.Extents
);
}
/// Returns a random point on the volume's surface
/// Random number generator that will be used
/// A random point on the volume's surface
public Vector3 RandomPointOnSurface(IRandom randomNumberGenerator) {
// This number is used to select the triangle on which the random point will be
// generated. Is is relative to the total surface area of the mesh, so smaller
// triangles cover a smaller numeric range to achieve uniform distribution.
float areaIndex = (float)randomNumberGenerator.NextDouble();
areaIndex *= this.surfaceArea;
// MSDN states that BinarySearch can be used even when the searched value doesn't exist
//
// Returns:
// "If value is not found and value is less than one or more elements in array,
// a negative number which is the bitwise complement of the index of the first
// element that is larger than value"
int index = Array.BinarySearch(this.accumulatedTriangleSurface, areaIndex);
if(index < 0)
index = ~index - 1;
// Construct a triangle from the vertices and generate a random point in its area
return Areas.PointGenerators.Triangle3PointGenerator.GenerateRandomPointWithin(
randomNumberGenerator,
this.Vertices[this.Indices[index * 3 + 0]],
this.Vertices[this.Indices[index * 3 + 1]],
this.Vertices[this.Indices[index * 3 + 2]]
);
}
/// Returns a random point within the volume
/// Random number generator that will be used
/// A random point within the volume
public Vector3 RandomPointWithin(IRandom randomNumberGenerator) {
throw new Exception("The method or operation is not implemented.");
}
/// Enumerates the triangles contained in the mesh
public IEnumerable Triangles {
get {
int indicesIndex = 0;
while(indicesIndex <= (this.Indices.Length - 3)) {
yield return new Areas.Triangle3(
this.Vertices[this.Indices[indicesIndex + 0]],
this.Vertices[this.Indices[indicesIndex + 1]],
this.Vertices[this.Indices[indicesIndex + 2]]
);
indicesIndex += 3;
}
}
}
/// Location at which mesh is placed in space
///
/// This doesn't take the center of mass or any other physical property into account,
/// it's merely an offset for the original vertex coordinates. Otherwise you would have
/// a hard time aligning your physical mesh with the visual one :)
///
public Vector3 Location {
get { return this.Transform.Translation; }
set { this.Transform.Translation = value; }
}
/// Orientation as position of the mesh in space
public Matrix Transform;
/// Compute or recompute the mass properties of this triangle mesh
///
///
/// The code has been translated to C# from David Eberly's document titled
/// "Polyhedral Mass Properties (Revisited)" which is in turn based on
/// Brian Mirtich's "Fast and accurate computation of polyhedral mass properties".
/// You can find David Eberly's document at
/// http://www.geometrictools.com/Documentation/PolyhedralMassProperties.pdf
///
///
private void computeMassProperties() {
int triangleCount = (this.Indices.Length - (this.Indices.Length % 3)) / 3;
this.accumulatedTriangleSurface = new float[triangleCount];
this.surfaceArea = 0.0f;
// Order: 1, x, y, z, x^2, y^2, z^2, xy, yz, zx
float[] integral = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
int triangleIndex = 0;
foreach(Areas.Triangle3 triangle in this.Triangles) {
// Get edges and cross product of edges
Vector3 ab = triangle.B - triangle.A;
Vector3 ac = triangle.C - triangle.A;
Vector3 cross = Vector3.Cross(ab, ac);
// Compute integral terms
Vector3 t0 = triangle.A + triangle.B;
Vector3 t1 = triangle.A * triangle.A;
Vector3 t2 = t1 + triangle.B * t0;
Vector3 f1 = t0 + triangle.C;
Vector3 f2 = t2 + triangle.C * f1;
Vector3 f3 = triangle.A * t1 + triangle.B * t2 + triangle.C * f2;
Vector3 g0 = f2 + triangle.A * (f1 + triangle.A);
Vector3 g1 = f2 + triangle.B * (f1 + triangle.B);
Vector3 g2 = f2 + triangle.C * (f1 + triangle.C);
// Update integrals
integral[0] += cross.X * f1.X;
integral[1] += cross.X * f2.X;
integral[2] += cross.Y * f2.Y;
integral[3] += cross.Z * f2.Z;
integral[4] += cross.X * f3.X;
integral[5] += cross.Y * f3.Y;
integral[6] += cross.Z * f3.Z;
integral[7] += cross.X * (
triangle.A.Y * g0.X + triangle.B.Y * g1.X + triangle.C.Y * g2.X
);
integral[8] += cross.Y * (
triangle.A.Z * g0.Y + triangle.B.Z * g1.Y + triangle.C.Z * g2.Y
);
integral[9] += cross.Z * (
triangle.A.X * g0.Z + triangle.B.X * g1.Z + triangle.C.X * g2.Z
);
// Update the surface area accumulation array
this.surfaceArea += triangle.CircumferenceLength;
this.accumulatedTriangleSurface[triangleIndex] = this.surfaceArea;
}
integral[0] /= 6.0f;
integral[1] /= 24.0f;
integral[2] /= 24.0f;
integral[3] /= 24.0f;
integral[4] /= 60.0f;
integral[5] /= 60.0f;
integral[6] /= 60.0f;
integral[7] /= 120.0f;
integral[8] /= 120.0f;
integral[9] /= 120.0f;
this.mass = integral[0];
// Center of mass
this.centerOfMass.X = integral[1] / this.mass;
this.centerOfMass.Y = integral[2] / this.mass;
this.centerOfMass.Z = integral[3] / this.mass;
// Inertia tensor relative to center of mass
this.inertiaTensor.M11 = integral[5] + integral[6] - this.mass * (
this.centerOfMass.Y * this.centerOfMass.Y + this.centerOfMass.Z * this.centerOfMass.Z
);
this.inertiaTensor.M22 = integral[4] + integral[6] - this.mass * (
this.centerOfMass.Z * this.centerOfMass.Z + this.centerOfMass.X * this.centerOfMass.X
);
this.inertiaTensor.M33 = integral[4] + integral[5] - this.mass * (
this.centerOfMass.X * this.centerOfMass.X + this.centerOfMass.Y * this.centerOfMass.Y
);
this.inertiaTensor.M21 =
-(integral[7] - this.mass * this.centerOfMass.X * this.centerOfMass.Y);
this.inertiaTensor.M32 =
-(integral[8] - this.mass * this.centerOfMass.Y * this.centerOfMass.Z);
this.inertiaTensor.M31 =
-(integral[9] - this.mass * this.centerOfMass.Z * this.centerOfMass.X);
// Compute volume of arbitrary, non-convex meshes:
// http://www.gamedev.net/community/forums/topic.asp?topic_id=520749
}
/// Generates sequential indices for a triangle vertex array
/// Number of indices to generate
/// An array containing sequential indices
private static int[] generateIndices(int count) {
int[] indices = new int[count];
for(int indicesIndex = 0; indicesIndex < count; ++indicesIndex)
indices[indicesIndex] = indicesIndex;
return indices;
}
/// The vertices that make up the mesh
protected Vector3[] Vertices;
/// Indices of the vertices from which to build triangles
protected int[] Indices;
/// Total amount of surface area of this mesh
protected float surfaceArea;
/// The center of mass in mesh coordinates
protected Vector3 centerOfMass;
/// The mass of this mesh assuming a material density of 1.0
protected float mass;
/// The meshes inertia tensor matrix, useful for physics calculations
protected Matrix inertiaTensor;
/// Accumulated surface of the triangles in this mesh
///
/// This is used to perform a binary search in the RandomPointOnSurface() method
///
protected float[] accumulatedTriangleSurface;
}
} // namespace Nuclex.Geometry.Volumes