FAQ |
Here is a collection of frequently asked questions, common problems and solutions.
This topic contains the following sections:
We are working on an editor to simplify and automate this process. Without an editor you can use one of following approaches.
The DigitalRune samples demonstrate several different ways to create shapes for models.
Usually, the collision detection reports only a single contact between a RayShape and a TriangleMeshShape. This is by design. If the ray hits several features (triangles) of a single object, only the first hit is returned. (Note: RayShapeStopsAtFirstHit is only relevant if the ray hits several different objects.)
If you want to find all hit triangles, check out How can I efficiently perform a lot of ray casts against a single mesh?.
Use a TriangleMeshShape with an AabbTreeT or a CompressedAabbTree for the static mesh. (You have to experiment which tree type is faster. The CompressedAabbTree can be slower than the AabbTreeT because it has to do a bit more computations, but it could also be faster because it uses less memory and generates less cache misses.)
Create a Ray in the local space of the mesh. Call TriangleMeshShape.Partition.GetOverlaps(Ray) to find all triangles which potentially overlap the ray. Use GeometryHelperGetContact to test the returned triangles vs. ray. The returned contacts are in the local space of the triangle mesh object. Don't forget to convert them back to world space if necessary.
The winding order is relevant for the DCEL mesh. The DcelMeshFromTriangleMesh method currently assumes that all triangles in the mesh are connected. Unconnected submeshes are not allowed. Two triangles are connected if they share an edge and if the winding order matches. - In other words, the triangle (A, B, C) is connected to the triangle (C, B, D) but not to the triangle (B, C, D).
You can use the method TriangleMeshFromModel. This method works very similar to this code snippet:
public static TriangleMesh FromModel(Model model) { var triangleMesh = new TriangleMesh(); foreach (var modelMesh in model.Meshes) { // Get bone transformation. Matrix transform = GetAbsoluteTransform(modelMesh.ParentBone); foreach (var modelMeshPart in modelMesh.MeshParts) { // Get vertex element info. var vertexDeclaration = modelMeshPart.VertexBuffer.VertexDeclaration; var vertexElements = vertexDeclaration.GetVertexElements(); // Get the vertex positions. var positionElement = vertexElements.First(e => e.VertexElementUsage == VertexElementUsage.Position); if (positionElement.VertexElementFormat != VertexElementFormat.Vector3) throw new NotSupportedException("For vertex positions only VertexElementFormat.Vector3 is supported."); var positions = new Vector3[modelMeshPart.NumVertices]; modelMeshPart.VertexBuffer.GetData( modelMeshPart.VertexOffset * vertexDeclaration.VertexStride + positionElement.Offset, positions, 0, modelMeshPart.NumVertices, vertexDeclaration.VertexStride); // Apply bone transformation. for (int i = 0; i < positions.Length; i++) positions[i] = Vector3.Transform(positions[i], transform); // Get indices. var indexElementSize = (modelMeshPart.IndexBuffer.IndexElementSize == IndexElementSize.SixteenBits) ? 2 : 4; if (indexElementSize != 2) throw new NotSupportedException("Only 16 bit indices are supported."); var indices = new short[modelMeshPart.PrimitiveCount * 3]; modelMeshPart.IndexBuffer.GetData( modelMeshPart.StartIndex * 2, indices, 0, modelMeshPart.PrimitiveCount * 3); // Remember the number of vertices already in the mesh. int vertexCount = triangleMesh.Vertices.Count; // Add the vertices of the current modelMeshPart. foreach (var p in positions) triangleMesh.Vertices.Add((Vector3F)p); // Add indices to triangle mesh. for (int i = 0; i < modelMeshPart.PrimitiveCount; i++) { // The three indices of the next triangle. // We add 'vertexCount' because the triangleMesh already contains other mesh parts. var i0 = indices[i * 3 + 0] + vertexCount; var i1 = indices[i * 3 + 1] + vertexCount; var i2 = indices[i * 3 + 2] + vertexCount; triangleMesh.Indices.Add(i0); triangleMesh.Indices.Add(i2); // DigitalRune Geometry uses other winding order! triangleMesh.Indices.Add(i1); } } } return triangleMesh; } private static Matrix GetAbsoluteTransform(ModelBone bone) { if (bone == null) return Matrix.Identity; return bone.Transform * GetAbsoluteTransform(bone.Parent); }
Our spatial partitions are used in several places:
In the broad phase the collision detection finds potential candidate pairs. This is done using a spatial partitioning method with EnableSelfOverlaps = true.
Recommended types are:
Compound Collision Shapes:
The CompositeShape and the TriangleMeshShape are shapes that consist of several other objects. When the number of shapes exceeds a certain threshold (needs to be found experimentally), a spatial partition needs to be set to improve collision detection. Suitable types are:
Recommendations:
For the collision detection broad phase use the SweepAndPruneSpaceT. Only if you make a lot of ray queries and/or use frustum culling use a DualPartitionT.
Start using these partitions. Once you have a representative level running in your game, you can test other partition types and see whether they perform better.
Note: Our library is very flexible. You can, for example, use an AdaptiveAabbTreeT as the collision detection broad phase, or the DualPartitionT for a triangle mesh. Though in a typical game these combinations will perform worse. For example:
// This is the default: _simulation.CollisionDomain.BroadPhase = new SweepAndPruneSpace<CollisionObject>(); // Here are other examples: _simulation.CollisionDomain.BroadPhase = new DualPartition<CollisionObject>(); _simulation.CollisionDomain.BroadPhase = new DynamicAabbTree<CollisionObject> { EnableMotionPrediction = true, OptimizationPerFrame = 0.01f }; _simulation.CollisionDomain.BroadPhase = new AdaptiveAabbTree<CollisionObject>();
Use a DynamicAabbTreeT. If the AABB of an item in tree is changed at runtime, you must invalidate the tree:
tree.Invalidate(indexOfChangedAabb);
In each frame call:
tree.Update(false);
This rebuilds or incrementally optimizes the tree when needed. (It does nothing if no AABBs have changed and no objects were added or removed.) If you do not make this call, then this will be done automatically in GetOverlaps. But it is better to control manually when this happens.