// The following basic top-down approach is used in Bullet (btDbvt). /// <summary> /// Builds the subtree top-down. /// </summary> /// <typeparam name="T">The type of item stored in the tree.</typeparam> /// <param name="leaves">The leaves of the tree.</param> /// <param name="firstLeaf">The first leaf.</param> /// <param name="lastLeaf">The last leaf.</param> /// <param name="createNode">A function that creates a new, empty node.</param> /// <returns>The root node of the subtree.</returns> private static IAabbTreeNode <T> BuildBottomUp <T>(List <IAabbTreeNode <T> > leaves, int firstLeaf, int lastLeaf, Func <IAabbTreeNode <T> > createNode) { // Replace 'leaves' with new list we can work with. var nodes = DigitalRune.ResourcePools <IAabbTreeNode <T> > .Lists.Obtain(); for (int i = firstLeaf; i <= lastLeaf; i++) { nodes.Add(leaves[i]); } // Iteratively merge nodes (subtrees) until we have a single tree. while (nodes.Count > 1) { float minSize = float.PositiveInfinity; Pair <int> minPair = new Pair <int>(-1, -1); for (int i = 0; i < nodes.Count; i++) { // Compare node with all subsequent nodes in list. for (int j = i + 1; j < nodes.Count; j++) { Aabb mergedAabb = Aabb.Merge(nodes[i].Aabb, nodes[j].Aabb); // Compute a "size" which can be used to estimate the fit of the new node. // Here: volume + edges Vector3F edges = mergedAabb.Extent; float size = edges.X * edges.Y * edges.Z + edges.X + edges.Y + edges.Z; if (size <= minSize) // Note: We compare with ≤ because size can be ∞. { minSize = size; minPair.First = i; minPair.Second = j; } } } if (minPair.First < 0 || minPair.Second < 0) { throw new GeometryException("Could not build AABB tree because the AABB of an item is invalid (e.g. NaN)."); } // Create a new parent node that merges the two subtrees. IAabbTreeNode <T> leftChild = nodes[minPair.First]; IAabbTreeNode <T> rightChild = nodes[minPair.Second]; IAabbTreeNode <T> parent = createNode(); parent.Aabb = Aabb.Merge(leftChild.Aabb, rightChild.Aabb); parent.LeftChild = leftChild; parent.RightChild = rightChild; // Remove subtrees from list and add the new node. nodes.RemoveAt(minPair.Second); nodes.RemoveAt(minPair.First); nodes.Add(parent); } IAabbTreeNode <T> root = nodes[0]; DigitalRune.ResourcePools <IAabbTreeNode <T> > .Lists.Recycle(nodes); return(root); }
/// <summary> /// Performs a full refit of the specified subtree. /// </summary> /// <param name="node">The root node of the subtree.</param> private void FullRefit(Node node) { Debug.Assert(node != null, "Refit should not be called with node == null."); if (node.IsLeaf) { // Update leaf AABB. node.Aabb = GetAabbForItem(node.Item); } else { // Internal node. if (node.IsValid && node.IsActive && !node.LeftChild.IsActive) { Debug.Assert(!node.RightChild.IsActive, "When the left child is inactive the right child must also be inactive."); // Node was front node in the last collision detection query. // Invalidate node: // - Remove left and right subtrees // - Gather all leaf nodes locally. InvalidateSubtree(node); } // Update AABBs. if (!node.IsValid) { // Leaf nodes are stored locally. Node leaf = node.Leaves[0]; leaf.IsActive = false; leaf.Aabb = GetAabbForItem(leaf.Item); node.Aabb = leaf.Aabb; int numberOfLeaves = node.Leaves.Count; for (int index = 1; index < numberOfLeaves; index++) { leaf = node.Leaves[index]; leaf.IsActive = false; leaf.Aabb = GetAabbForItem(leaf.Item); node.Aabb.Grow(leaf.Aabb); } } else { // Valid internal node. (Node was visited in the last collision detection query, but // was not a front node.) FullRefit(node.LeftChild); FullRefit(node.RightChild); node.Aabb = Aabb.Merge(node.LeftChild.Aabb, node.RightChild.Aabb); // Check whether parent/children relationship is degenerate. node.IsDegenerate = IsDegenerate(node); } } node.IsActive = false; }
/// <summary> /// Removes the specified leaf node from the tree. /// </summary> /// <param name="leaf">The leaf node .</param> /// <returns> /// The closest ancestor of <paramref name="leaf"/> that was not resized during remove. /// </returns> private Node RemoveLeaf(Node leaf) { if (_root == leaf) { // Remove only node of tree. _root = null; return(null); } else { Node parent = leaf.Parent; Node previous = parent.Parent; Node sibling = (parent.LeftChild == leaf) ? parent.RightChild : parent.LeftChild; if (previous == null) { // The sibling becomes the new root of the tree. _root = sibling; sibling.Parent = null; Nodes.Recycle(parent); return(_root); } else { // Replace parent by sibling. if (previous.LeftChild == parent) { previous.LeftChild = sibling; } else { previous.RightChild = sibling; } sibling.Parent = previous; Nodes.Recycle(parent); // Update AABBs of ancestors. do { Aabb oldAabb = previous.Aabb; previous.Aabb = Aabb.Merge(previous.LeftChild.Aabb, previous.RightChild.Aabb); if (oldAabb != previous.Aabb) { previous = previous.Parent; } else { break; } } while (previous != null); return(previous ?? _root); } } }
/// <summary> /// Recomputes the AABB of the specified node. (All leaf nodes need to be up-to-date.) /// </summary> /// <param name="node">The node.</param> private static void RecomputeAabb(Node node) { Debug.Assert(!node.IsLeaf); if (node.IsValid) { node.Aabb = Aabb.Merge(node.LeftChild.Aabb, node.RightChild.Aabb); } else { node.Aabb = node.Leaves[0].Aabb; int numberOfLeaves = node.Leaves.Count; for (int i = 1; i < numberOfLeaves; i++) { node.Aabb.Grow(node.Leaves[i].Aabb); } } }
/// <summary> /// Updates the AABB of the spatial partition. /// </summary> private void UpdateAabb() { bool staticPartitionValid = (StaticPartition.Count > 0); bool dynamicPartitionValid = (DynamicPartition.Count > 0); if (staticPartitionValid && dynamicPartitionValid) { Aabb = Aabb.Merge(StaticPartition.Aabb, DynamicPartition.Aabb); } else if (staticPartitionValid) { Aabb = StaticPartition.Aabb; } else if (dynamicPartitionValid) { Aabb = DynamicPartition.Aabb; } else { Aabb = new Aabb(); } }
private void ComputeAabbs(Aabb[] buffer, int index, ref int count) { // Increment the counter for each node visited. count++; Node node = _nodes[index]; if (node.IsLeaf) { // Store unquantized AABB of leaf node. buffer[index] = GetAabbForItem(node.Item); } else { // Compute AABB of child nodes. int leftIndex = index + 1; ComputeAabbs(buffer, leftIndex, ref count); int rightIndex = count; ComputeAabbs(buffer, rightIndex, ref count); buffer[index] = Aabb.Merge(buffer[leftIndex], buffer[rightIndex]); } }
/// <summary> /// Adds a leaf node to the given tree. /// </summary> /// <param name="root">The root of the tree (or subtree).</param> /// <param name="leaf">The leaf node to be added.</param> private void AddLeaf(Node root, Node leaf) { if (_root == null) { // Insert leaf as new root. _root = leaf; } else { // Search for leaf node that is closest to 'leaf'. This node that will become the new // sibling of 'leaf'. Node sibling = root; while (!sibling.IsLeaf) { int selection = AabbTreeHelper.SelectClosest(leaf.Aabb, sibling.LeftChild.Aabb, sibling.RightChild.Aabb); sibling = (selection == 0) ? sibling.LeftChild : sibling.RightChild; } // Add a new node as the parent of (sibling, leaf). Node parent = sibling.Parent; Node node = Nodes.Obtain(); node.Aabb = Aabb.Merge(sibling.Aabb, leaf.Aabb); node.Parent = parent; node.LeftChild = sibling; node.RightChild = leaf; sibling.Parent = node; leaf.Parent = node; if (parent == null) { // node is the new root. _root = node; } else { // node is an internal node. if (parent.LeftChild == sibling) { parent.LeftChild = node; } else { parent.RightChild = node; } // Update AABBs of ancestor. do { if (!parent.Aabb.Contains(node.Aabb)) { parent.Aabb = Aabb.Merge(parent.LeftChild.Aabb, parent.RightChild.Aabb); } else { break; } node = parent; parent = parent.Parent; } while (parent != null); } } }
public static Mesh Merge(IEnumerable<SceneNode> sceneNodes) { if (sceneNodes == null) throw new ArgumentNullException("sceneNodes"); // Gather flat list of all mesh nodes. Each mesh node could have children. var meshNodes = new List<MeshNode>(); foreach (var meshNode in sceneNodes) meshNodes.AddRange(meshNode.GetSubtree().OfType<MeshNode>()); var mergedMesh = new Mesh(); try { var jobs = new List<MergeJob>(); // A list of all encountered vertex types. var vertexDeclarations = new List<VertexDeclaration>(); // A list of total vertex counts for each vertex declaration. var vertexCounts = new List<int>(); // For indices we only need one counter because we use only one shared index buffer. int indexCount = 0; var mergedAabb = new Aabb(new Vector3(float.MaxValue), new Vector3(float.MinValue)); // Merge materials, create job list, merge AABBs, check if there is an occluder. bool hasOccluder = false; foreach (var meshNode in meshNodes) { var mesh = meshNode.Mesh; if (mesh.Skeleton != null) throw new NotSupportedException("Cannot merge skinned meshes."); foreach (var submesh in mesh.Submeshes) { if (submesh.PrimitiveCount <= 0) continue; if (submesh.VertexBufferEx == null) continue; // Merge materials and get material index. var material = submesh.GetMaterial(); int mergedMaterialIndex = mergedMesh.Materials.IndexOf(material); if (mergedMaterialIndex < 0) { mergedMaterialIndex = mergedMesh.Materials.Count; mergedMesh.Materials.Add(material); } if (mergedMaterialIndex > byte.MaxValue) throw new NotSupportedException("Cannot merge meshes. Merged mesh must not have more than 256 materials."); // Try to find index of existing matching vertex declaration. var vertexDeclaration = submesh.VertexBuffer.VertexDeclaration; int vertexDeclarationIndex = -1; for (int i = 0; i < vertexDeclarations.Count; i++) { if (AreEqual(vertexDeclarations[i], vertexDeclaration)) { vertexDeclarationIndex = i; break; } } if (vertexDeclarationIndex < 0) { // Add new vertex declaration. vertexDeclarationIndex = vertexDeclarations.Count; vertexDeclarations.Add(vertexDeclaration); vertexCounts.Add(0); } if (vertexDeclarationIndex > byte.MaxValue) throw new NotSupportedException("Cannot merge meshes. Merged mesh must not have more than 256 different vertex declarations."); // Count total number of vertices per vertex declaration. vertexCounts[vertexDeclarationIndex] += submesh.VertexCount; // Count number of indices. if (submesh.IndexBuffer != null) indexCount += GetNumberOfIndices(submesh.PrimitiveType, submesh.PrimitiveCount); jobs.Add(new MergeJob { Pose = meshNode.PoseWorld, Scale = meshNode.ScaleWorld, Submesh = submesh, MergedMaterialIndex = (byte)mergedMaterialIndex, VertexDeclarationIndex = (byte)vertexDeclarationIndex, // We set a sort key by which we can quickly sort all jobs. // We can merge submeshes if they have the same material, vertex declaration, // primitive type. // Submeshes do not need to have an index buffer. We could merge a submesh with // and without index buffer by generating indices. However, we do not merge in // this case. // ------------------------------------------------------------------------------- // Sort key = | unused | vertex type | material | primitive type | has index buffer | // | 8 bit | 8 bit | 8 bit | 7 bit | 1 bit | // ------------------------------------------------------------------------------- SortKey = (uint)(vertexDeclarationIndex << 16 | mergedMaterialIndex << 8 | (int)submesh.PrimitiveType << 1 | ((submesh.IndexBuffer != null) ? 1 : 0)), }); } // Merge AABBs. mergedAabb = Aabb.Merge(meshNode.Aabb, mergedAabb); hasOccluder |= (mesh.Occluder != null); } if (jobs.Count == 0) { mergedMesh.BoundingShape = Shape.Empty; return mergedMesh; } // Create new bounding shape from merged AABB. var extent = mergedAabb.Extent; if (Numeric.IsFinite(extent.X + extent.Y + extent.Z)) { var boxShape = new BoxShape(extent); if (mergedAabb.Center.IsNumericallyZero) mergedMesh.BoundingShape = boxShape; else mergedMesh.BoundingShape = new TransformedShape(new GeometricObject(boxShape, new Pose(mergedAabb.Center))); } else { mergedMesh.BoundingShape = Shape.Infinite; } jobs.Sort(MergeJobComparer.Instance); MergeSubmeshes(jobs, mergedMesh, vertexDeclarations, vertexCounts, indexCount); if (hasOccluder) mergedMesh.Occluder = MergeOccluders(meshNodes); return mergedMesh; } catch { mergedMesh.Dispose(); throw; } }
/// <summary> /// Performs a partial refit of the current subtree. /// </summary> /// <param name="node">The root node of the subtree.</param> /// <param name="invalidItems">The set of invalid items.</param> /// <returns> /// <see langword="true"/> if the AABB of <paramref name="node"/> was updated; otherwise, /// <see langword="false"/> if the AABB has not changed. /// </returns> private bool PartialRefit(Node node, HashSet <T> invalidItems) { Debug.Assert(node != null); Debug.Assert(invalidItems != null); bool updated = false; if (node.IsLeaf) { // Update leaf AABB if necessary. if (invalidItems.Contains(node.Item)) { node.Aabb = GetAabbForItem(node.Item); updated = true; } } else { // Refit children. if (!node.IsValid) { // Leaf nodes are stored locally. Node leaf = node.Leaves[0]; leaf.IsActive = false; if (invalidItems.Contains(leaf.Item)) { leaf.Aabb = GetAabbForItem(leaf.Item); updated = true; } node.Aabb = leaf.Aabb; int numberOfLeaves = node.Leaves.Count; for (int index = 1; index < numberOfLeaves; index++) { leaf = node.Leaves[index]; leaf.IsActive = false; if (invalidItems.Contains(leaf.Item)) { leaf.Aabb = GetAabbForItem(leaf.Item); updated = true; } node.Aabb.Grow(leaf.Aabb); } } else { // Valid internal node. bool leftUpdated = PartialRefit(node.LeftChild, invalidItems); bool rightUpdated = PartialRefit(node.RightChild, invalidItems); updated = leftUpdated || rightUpdated; if (updated) { // Update internal AABB. node.Aabb = Aabb.Merge(node.LeftChild.Aabb, node.RightChild.Aabb); // Check whether parent/children relationship is degenerate. node.IsDegenerate = IsDegenerate(node); } } } node.IsActive = false; return(updated); }
public static Mesh Merge(Mesh mesh, IList <Vector3F> scales, IList <Pose> poses) { if (mesh == null) { throw new ArgumentNullException("mesh"); } #if ANIMATION if (mesh.Skeleton != null) { throw new NotSupportedException("Cannot merge skinned meshes."); } #endif if (poses == null) { throw new ArgumentNullException("poses"); } if (scales == null) { var array = new Vector3F[poses.Count]; for (int i = 0; i < array.Length; i++) { array[i] = Vector3F.One; } scales = array; } if (scales.Count != poses.Count) { throw new ArgumentException("The number of elements in poses and scales must be equal."); } var mergedMesh = new Mesh(); try { var jobs = new List <MergeJob>(); // A list of all encountered vertex types. var vertexDeclarations = new List <VertexDeclaration>(); // A list of total vertex counts for each vertex declaration. var vertexCounts = new List <int>(); // For indices we only need one counter because we use only one shared index buffer. int indexCount = 0; // Merge materials, create job list. foreach (var submesh in mesh.Submeshes) { if (submesh.PrimitiveCount <= 0) { continue; } if (submesh.VertexBufferEx == null) { continue; } // Merge materials and get material index. var material = submesh.GetMaterial(); var mergedMaterialIndex = mergedMesh.Materials.IndexOf(material); if (mergedMaterialIndex < 0) { mergedMaterialIndex = mergedMesh.Materials.Count; mergedMesh.Materials.Add(material); } if (mergedMaterialIndex > byte.MaxValue) { throw new NotSupportedException("Cannot merge meshes. Merged mesh must not have more than 256 materials."); } // Try to find index of existing matching vertex declaration. int vertexDeclarationIndex = -1; for (int i = 0; i < vertexDeclarations.Count; i++) { if (AreEqual(vertexDeclarations[i], submesh.VertexBuffer.VertexDeclaration)) { vertexDeclarationIndex = i; break; } } if (vertexDeclarationIndex < 0) { // Add new vertex declaration. vertexDeclarationIndex = vertexDeclarations.Count; vertexDeclarations.Add(submesh.VertexBuffer.VertexDeclaration); vertexCounts.Add(0); } if (vertexDeclarationIndex > byte.MaxValue) { throw new NotSupportedException("Cannot merge meshes. Merged mesh must not have more than 256 different vertex declarations."); } for (int instanceIndex = 0; instanceIndex < poses.Count; instanceIndex++) { // Count total number of vertices per vertex declaration. vertexCounts[vertexDeclarationIndex] += submesh.VertexCount; // Count number of indices. if (submesh.IndexBuffer != null) { indexCount += GetNumberOfIndices(submesh.PrimitiveType, submesh.PrimitiveCount); } jobs.Add(new MergeJob { Pose = poses[instanceIndex], Scale = scales[instanceIndex], Submesh = submesh, MergedMaterialIndex = (byte)mergedMaterialIndex, VertexDeclarationIndex = (byte)vertexDeclarationIndex, // We set a sort key by which we can quickly sort all jobs. // We can merge submeshes if they have the same material, vertex declaration, // primitive type. // Submeshes do not need to have an index buffer. We could merge a submesh with // and without index buffer by generating indices. However, we do not merge in // this case. // ------------------------------------------------------------------------------- // Sort key = | unused | vertex type | material | primitive type | has index buffer | // | 8 bit | 8 bit | 8 bit | 7 bit | 1 bit | // ------------------------------------------------------------------------------- SortKey = (uint)(vertexDeclarationIndex << 16 | mergedMaterialIndex << 8 | (int)submesh.PrimitiveType << 1 | ((submesh.IndexBuffer != null) ? 1 : 0)), }); } } // Merge AABBs. var mergedAabb = new Aabb(new Vector3F(float.MaxValue), new Vector3F(float.MinValue)); for (int instanceIndex = 0; instanceIndex < poses.Count; instanceIndex++) { mergedAabb = Aabb.Merge( mesh.BoundingShape.GetAabb(scales[instanceIndex], poses[instanceIndex]), mergedAabb); } if (jobs.Count == 0) { mergedMesh.BoundingShape = Shape.Empty; return(mergedMesh); } // Create new bounding shape from merged AABB. var extent = mergedAabb.Extent; if (Numeric.IsFinite(extent.X + extent.Y + extent.Z)) { var boxShape = new BoxShape(extent); if (mergedAabb.Center.IsNumericallyZero) { mergedMesh.BoundingShape = boxShape; } else { mergedMesh.BoundingShape = new TransformedShape(new GeometricObject(boxShape, new Pose(mergedAabb.Center))); } } else { mergedMesh.BoundingShape = Shape.Infinite; } jobs.Sort(MergeJobComparer.Instance); MergeSubmeshes(jobs, mergedMesh, vertexDeclarations, vertexCounts, indexCount); // Merge occluders. if (mesh.Occluder != null) { mergedMesh.Occluder = MergeOccluders(mesh, scales, poses); } return(mergedMesh); } catch { mergedMesh.Dispose(); throw; } }