internal void splitIfNecessary(SSBVHNodeAdaptor <GO> nAda) { if (gobjects.Count > nAda.BVH.LEAF_OBJ_MAX) { splitNode(nAda); } }
private static void addObject_Pushdown(SSBVHNodeAdaptor <GO> nAda, ssBVHNode <GO> curNode, GO newOb) { var left = curNode.Left; var right = curNode.Right; // merge and pushdown left and right as a new node.. var mergedSubnode = new ssBVHNode <GO>(nAda.BVH) { Left = left, Right = right, Parent = curNode, ContainedObjects = null }; // we need to be an interior node... so null out our object list.. left.Parent = mergedSubnode; right.Parent = mergedSubnode; mergedSubnode.ChildRefit(nAda, false); // make new subnode for obj var newSubnode = new ssBVHNode <GO>(nAda.BVH); newSubnode.Parent = curNode; newSubnode.ContainedObjects = new List <GO> { newOb }; nAda.MapObjectToBvhLeaf(newOb, newSubnode); newSubnode.ComputeVolume(nAda); // make assignments.. curNode.Left = mergedSubnode; curNode.Right = newSubnode; curNode.SetDepth(nAda, curNode.Depth); // propagate new depths to our children. curNode.ChildRefit(nAda); }
private void SplitIfNecessary(SSBVHNodeAdaptor <GO> nAda) { if (ContainedObjects.Count > nAda.BVH.LEAF_OBJ_MAX) { SplitNode(nAda); } }
internal void splitNode(SSBVHNodeAdaptor <GO> nAda) { // second, decide which axis to split on, and sort.. List <GO> splitlist = gobjects; splitlist.ForEach(o => nAda.unmapObject(o)); Axis splitAxis = pickSplitAxis(); switch (splitAxis) // sort along the appropriate axis { case Axis.X: splitlist.Sort(delegate(GO go1, GO go2) { return(nAda.objectpos(go1).X.CompareTo(nAda.objectpos(go2).X)); }); break; case Axis.Y: splitlist.Sort(delegate(GO go1, GO go2) { return(nAda.objectpos(go1).Y.CompareTo(nAda.objectpos(go2).Y)); }); break; case Axis.Z: splitlist.Sort(delegate(GO go1, GO go2) { return(nAda.objectpos(go1).Z.CompareTo(nAda.objectpos(go2).Z)); }); break; default: throw new NotImplementedException(); } int center = (int)(splitlist.Count / 2); // Find the center object in our current sub-list gobjects = null; // create the new left and right nodes... left = new ssBVHNode <GO>(nAda.BVH, this, splitlist.GetRange(0, center), splitAxis, this.depth + 1); // Split the Hierarchy to the left right = new ssBVHNode <GO>(nAda.BVH, this, splitlist.GetRange(center, splitlist.Count - center), splitAxis, this.depth + 1); // Split the Hierarchy to the right }
internal void childExpanded(SSBVHNodeAdaptor <GO> nAda, ssBVHNode <GO> child) { bool expanded = false; if (child.box.Min.X < box.Min.X) { box.Min.X = child.box.Min.X; expanded = true; } if (child.box.Max.X > box.Max.X) { box.Max.X = child.box.Max.X; expanded = true; } if (child.box.Min.Y < box.Min.Y) { box.Min.Y = child.box.Min.Y; expanded = true; } if (child.box.Max.Y > box.Max.Y) { box.Max.Y = child.box.Max.Y; expanded = true; } if (child.box.Min.Z < box.Min.Z) { box.Min.Z = child.box.Min.Z; expanded = true; } if (child.box.Max.Z > box.Max.Z) { box.Max.Z = child.box.Max.Z; expanded = true; } if (expanded && parent != null) { parent.childExpanded(nAda, this); } }
internal static void addObject_Pushdown(SSBVHNodeAdaptor <GO> nAda, ssBVHNode <GO> curNode, GO newOb) { var left = curNode.left; var right = curNode.right; // merge and pushdown left and right as a new node.. var mergedSubnode = new ssBVHNode <GO>(nAda.BVH); mergedSubnode.left = left; mergedSubnode.right = right; mergedSubnode.parent = curNode; mergedSubnode.gobjects = null; // we need to be an interior node... so null out our object list.. left.parent = mergedSubnode; right.parent = mergedSubnode; mergedSubnode.childRefit(nAda, propagate: false); // make new subnode for obj var newSubnode = new ssBVHNode <GO>(nAda.BVH); newSubnode.parent = curNode; newSubnode.gobjects = new List <GO> { newOb }; nAda.mapObjectToBVHLeaf(newOb, newSubnode); newSubnode.computeVolume(nAda); // make assignments.. curNode.left = mergedSubnode; curNode.right = newSubnode; curNode.setDepth(nAda, curNode.depth); // propagate new depths to our children. curNode.childRefit(nAda); }
internal void computeVolume(SSBVHNodeAdaptor <GO> nAda) { assignVolume(nAda.objectpos(gobjects[0]), nAda.radius(gobjects[0])); for (int i = 1; i < gobjects.Count; i++) { expandVolume(nAda, nAda.objectpos(gobjects[i]), nAda.radius(gobjects[i])); } }
internal void computeVolume(SSBVHNodeAdaptor <GO> nAda) { box = nAda.boundingBox(gobjects[0]); for (int i = 1; i < gobjects.Count; i++) { expandVolume(nAda, nAda.boundingBox(gobjects[i])); } }
internal static float SA(SSBVHNodeAdaptor <GO> nAda, GO obj) { float radius = nAda.radius(obj); float size = radius * 2; return(6.0f * (size * size)); }
private void ComputeVolume(SSBVHNodeAdaptor <GO> nAda) { Bounds = nAda.GetObjectBounds(ContainedObjects[0]); for (int i = 1; i < ContainedObjects.Count; i++) { ExpandVolume(nAda.GetObjectBounds(ContainedObjects[i])); } }
internal float SAofList(SSBVHNodeAdaptor <GO> nAda, List <GO> list) { var box = AABBofOBJ(nAda, list[0]); list.ToList <GO>().GetRange(1, list.Count - 1).ForEach(obj => { var newbox = AABBofOBJ(nAda, obj); box.ExpandBy(newbox); }); return(SA(box)); }
internal static SSAABB AABBofOBJ(SSBVHNodeAdaptor <GO> nAda, GO obj) { float radius = nAda.radius(obj); SSAABB box; box.Min.X = -radius; box.Max.X = radius; box.Min.Y = -radius; box.Max.Y = radius; box.Min.Z = -radius; box.Max.Z = radius; return(box); }
private double SAofList(SSBVHNodeAdaptor <GO> nAda, List <GO> list) { Bounds_d box = nAda.GetObjectBounds(list[0]); for (int i = 1; i < list.Count - 1; i++) { box.ExpandToFit(nAda.GetObjectBounds(list[i])); } return(SA(ref box)); }
void setDepth(SSBVHNodeAdaptor <GO> nAda, int newdepth) { this.depth = newdepth; if (newdepth > nAda.BVH.maxDepth) { nAda.BVH.maxDepth = newdepth; } if (gobjects == null) { left.setDepth(nAda, newdepth + 1); right.setDepth(nAda, newdepth + 1); } }
private void expandVolume(SSBVHNodeAdaptor <GO> nAda, SSAABB targetBox) { var boxCopy = box; box.ExpandToFit(targetBox); var expanded = !boxCopy.Equals(box); if (expanded && parent != null) { parent.childExpanded(nAda, this); } }
private void RemoveLeaf(SSBVHNodeAdaptor <GO> nAda, ssBVHNode <GO> removeLeaf) { if (Left == null || Right == null) { throw new Exception("bad intermediate node"); } ssBVHNode <GO> keepLeaf; if (removeLeaf == Left) { keepLeaf = Right; } else if (removeLeaf == Right) { keepLeaf = Left; } else { throw new Exception("removeLeaf doesn't match any leaf!"); } // "become" the leaf we are keeping. Bounds = keepLeaf.Bounds; Left = keepLeaf.Left; Right = keepLeaf.Right; ContainedObjects = keepLeaf.ContainedObjects; // clear the leaf.. // keepLeaf.left = null; keepLeaf.right = null; keepLeaf.gobjects = null; keepLeaf.parent = null; if (ContainedObjects == null) { Left.Parent = this; Right.Parent = this; // reassign child parents.. SetDepth(nAda, Depth); // this reassigns depth for our children } else { // map the objects we adopted to us... ContainedObjects.ForEach( o => { nAda.MapObjectToBvhLeaf(o, this); }); } // propagate our new volume.. if (Parent != null) { Parent.ChildRefit(nAda); } }
void SetDepth(SSBVHNodeAdaptor <GO> nAda, int newdepth) { Depth = newdepth; if (newdepth > nAda.BVH.MaxDepth) { nAda.BVH.MaxDepth = newdepth; } if (ContainedObjects == null) { Left.SetDepth(nAda, newdepth + 1); Right.SetDepth(nAda, newdepth + 1); } }
internal void findOverlappingLeaves(SSBVHNodeAdaptor <GO> nAda, Vector3 origin, float radius, List <ssBVHNode <GO> > overlapList) { if (toAABB().IntersectsSphere(origin, radius)) { if (gobjects != null) { overlapList.Add(this); } else { left.findOverlappingLeaves(nAda, origin, radius, overlapList); right.findOverlappingLeaves(nAda, origin, radius, overlapList); } } }
internal void findOverlappingLeaves(SSBVHNodeAdaptor <GO> nAda, SSAABB aabb, List <ssBVHNode <GO> > overlapList) { if (toAABB().IntersectsAABB(aabb)) { if (gobjects != null) { overlapList.Add(this); } else { left.findOverlappingLeaves(nAda, aabb, overlapList); right.findOverlappingLeaves(nAda, aabb, overlapList); } } }
/// <summary> /// initializes a BVH with a given nodeAdaptor, and object list. /// </summary> /// <param name="nodeAdaptor"></param> /// <param name="objects"></param> /// <param name="LEAF_OBJ_MAX">WARNING! currently this must be 1 to use dynamic BVH updates</param> public ssBVH(SSBVHNodeAdaptor <GO> nodeAdaptor, List <GO> objects, int LEAF_OBJ_MAX = 1) { this.LEAF_OBJ_MAX = LEAF_OBJ_MAX; nodeAdaptor.setBVH(this); this.nAda = nodeAdaptor; if (objects.Count > 0) { rootBVH = new ssBVHNode <GO>(this, objects); } else { rootBVH = new ssBVHNode <GO>(this); rootBVH.gobjects = new List <GO>(); // it's a leaf, so give it an empty object list } }
internal static void addObject(SSBVHNodeAdaptor <GO> nAda, ssBVHNode <GO> curNode, GO newOb, ref SSAABB newObBox, float newObSAH) { // 1. first we traverse the node looking for the best leaf while (curNode.gobjects == null) { // find the best way to add this object.. 3 options.. // 1. send to left node (L+N,R) // 2. send to right node (L,R+N) // 3. merge and pushdown left-and-right node (L+R,N) var left = curNode.left; var right = curNode.right; float leftSAH = SA(left); float rightSAH = SA(right); float sendLeftSAH = rightSAH + SA(left.box.ExpandedBy(newObBox)); // (L+N,R) float sendRightSAH = leftSAH + SA(right.box.ExpandedBy(newObBox)); // (L,R+N) float mergedLeftAndRightSAH = SA(AABBofPair(left, right)) + newObSAH; // (L+R,N) // Doing a merge-and-pushdown can be expensive, so we only do it if it's notably better const float MERGE_DISCOUNT = 0.3f; if (mergedLeftAndRightSAH < (Math.Min(sendLeftSAH, sendRightSAH)) * MERGE_DISCOUNT) { addObject_Pushdown(nAda, curNode, newOb); return; } else { if (sendLeftSAH < sendRightSAH) { curNode = left; } else { curNode = right; } } } // 2. then we add the object and map it to our leaf curNode.gobjects.Add(newOb); nAda.mapObjectToBVHLeaf(newOb, curNode); curNode.refitVolume(nAda); // split if necessary... curNode.splitIfNecessary(nAda); }
private static void ChildRefit(SSBVHNodeAdaptor <GO> nAda, ssBVHNode <GO> curNode, bool propagate = true) { do { ssBVHNode <GO> left = curNode.Left; ssBVHNode <GO> right = curNode.Right; // start with the left box Bounds_d newBox = left.Bounds.ExpandedToFit(right.Bounds); // now set our box to the newly created box curNode.Bounds = newBox; // and walk up the tree curNode = curNode.Parent; } while (propagate && curNode != null); }
internal static void childRefit(SSBVHNodeAdaptor <GO> nAda, ssBVHNode <GO> curNode, bool propagate = true) { do { SSAABB oldbox = curNode.box; ssBVHNode <GO> left = curNode.left; ssBVHNode <GO> right = curNode.right; // start with the left box SSAABB newBox = left.box; // expand any dimension bigger in the right node if (right.box.Min.X < newBox.Min.X) { newBox.Min.X = right.box.Min.X; } if (right.box.Min.Y < newBox.Min.Y) { newBox.Min.Y = right.box.Min.Y; } if (right.box.Min.Z < newBox.Min.Z) { newBox.Min.Z = right.box.Min.Z; } if (right.box.Max.X > newBox.Max.X) { newBox.Max.X = right.box.Max.X; } if (right.box.Max.Y > newBox.Max.Y) { newBox.Max.Y = right.box.Max.Y; } if (right.box.Max.Z > newBox.Max.Z) { newBox.Max.Z = right.box.Max.Z; } // now set our box to the newly created box curNode.box = newBox; // and walk up the tree curNode = curNode.parent; } while (propagate && curNode != null); }
private static void AddObject(SSBVHNodeAdaptor <GO> nAda, ssBVHNode <GO> curNode, GO newOb, ref Bounds_d newObBox, double newObSAH) { // 1. first we traverse the node looking for the best leaf while (curNode.ContainedObjects == null) { // find the best way to add this object.. 3 options.. // 1. send to left node (L+N,R) // 2. send to right node (L,R+N) // 3. merge and pushdown left-and-right node (L+R,N) var left = curNode.Left; var right = curNode.Right; double leftSAH = SA(left); double rightSAH = SA(right); double sendLeftSAH = rightSAH + SA(left.Bounds.ExpandedToFit(newObBox)); // (L+N,R) double sendRightSAH = leftSAH + SA(right.Bounds.ExpandedToFit(newObBox)); // (L,R+N) double mergedLeftAndRightSAH = SA(BoundsOfPair(left, right)) + newObSAH; // (L+R,N) // Doing a merge-and-pushdown can be expensive, so we only do it if it's notably better const double MERGE_DISCOUNT = 0.3f; if (mergedLeftAndRightSAH < (Math.Min(sendLeftSAH, sendRightSAH)) * MERGE_DISCOUNT) { addObject_Pushdown(nAda, curNode, newOb); return; } if (sendLeftSAH < sendRightSAH) { curNode = left; } else { curNode = right; } } // 2. then we add the object and map it to our leaf curNode.ContainedObjects.Add(newOb); nAda.MapObjectToBvhLeaf(newOb, curNode); curNode.RefitVolume(nAda); // split if necessary... curNode.SplitIfNecessary(nAda); }
/// <summary> /// initializes a BVH with a given nodeAdaptor, and object list. /// </summary> /// <param name="nodeAdaptor"></param> /// <param name="objects"></param> /// <param name="LEAF_OBJ_MAX">WARNING! currently this must be 1 to use dynamic BVH updates</param> public ssBVH(SSBVHNodeAdaptor <T> nodeAdaptor, List <T> objects, int LEAF_OBJ_MAX = 1) { this.LEAF_OBJ_MAX = LEAF_OBJ_MAX; nodeAdaptor.BVH = this; nAda = nodeAdaptor; if (objects.Count > 0) { RootBVH = new ssBVHNode <T>(this, objects); } else { RootBVH = new ssBVHNode <T>(this) { ContainedObjects = new List <T>() }; // it's a leaf, so give it an empty object list } }
internal void removeLeaf(SSBVHNodeAdaptor <GO> nAda, ssBVHNode <GO> removeLeaf) { if (left == null || right == null) { throw new Exception("bad intermediate node"); } ssBVHNode <GO> keepLeaf; if (removeLeaf == left) { keepLeaf = right; } else if (removeLeaf == right) { keepLeaf = left; } else { throw new Exception("removeLeaf doesn't match any leaf!"); } // "become" the leaf we are keeping. box = keepLeaf.box; left = keepLeaf.left; right = keepLeaf.right; gobjects = keepLeaf.gobjects; // clear the leaf.. // keepLeaf.left = null; keepLeaf.right = null; keepLeaf.gobjects = null; keepLeaf.parent = null; if (gobjects == null) { left.parent = this; right.parent = this; // reassign child parents.. this.setDepth(this.depth); // this reassigns depth for our children } else { // map the objects we adopted to us... gobjects.ForEach(o => { nAda.mapObjectToBVHLeaf(o, this); }); } // propagate our new volume.. if (parent != null) { parent.childRefit(nAda); } }
private ssBVHNode(ssBVH <GO> bvh, ssBVHNode <GO> lparent, List <GO> gobjectlist, Axis lastSplitAxis, int curdepth) { SSBVHNodeAdaptor <GO> nAda = bvh.nAda; this.nodeNumber = bvh.nodeCount++; this.parent = lparent; // save off the parent BVHGObj Node this.depth = curdepth; if (bvh.maxDepth < curdepth) { bvh.maxDepth = curdepth; } // Early out check due to bad data // If the list is empty then we have no BVHGObj, or invalid parameters are passed in if (gobjectlist == null || gobjectlist.Count < 1) { throw new Exception("ssBVHNode constructed with invalid paramaters"); } // Check if we’re at our LEAF node, and if so, save the objects and stop recursing. Also store the min/max for the leaf node and update the parent appropriately if (gobjectlist.Count <= bvh.LEAF_OBJ_MAX) { // once we reach the leaf node, we must set prev/next to null to signify the end left = null; right = null; // at the leaf node we store the remaining objects, so initialize a list gobjects = gobjectlist; gobjects.ForEach(o => nAda.mapObjectToBVHLeaf(o, this)); computeVolume(nAda); splitIfNecessary(nAda); } else { // -------------------------------------------------------------------------------------------- // if we have more than (bvh.LEAF_OBJECT_COUNT) objects, then compute the volume and split gobjects = gobjectlist; computeVolume(nAda); splitNode(nAda); childRefit(nAda, propagate: false); } }
public void refit_ObjectChanged(SSBVHNodeAdaptor <GO> nAda, GO obj) { if (ContainedObjects == null) { throw new Exception("dangling leaf!"); } if (RefitVolume(nAda)) { // add our parent to the optimize list... if (Parent != null) { nAda.BVH.RefitNodes.Add(Parent); // you can force an optimize every time something moves, but it's not very efficient // instead we do this per-frame after a bunch of updates. // nAda.BVH.optimize(); } } }
private void SplitNode(SSBVHNodeAdaptor <GO> nAda) { // second, decide which axis to split on, and sort.. List <GO> splitlist = ContainedObjects; splitlist.ForEach(nAda.UnmapObject); int center = splitlist.Count / 2; // find the center object SplitAxisOpt <GO> bestSplit = eachAxis.Min( axis => { var orderedlist = new List <GO>(splitlist); switch (axis) { case Axis.X: orderedlist.Sort((go1, go2) => nAda.GetObjectPosition(go1).x.CompareTo(nAda.GetObjectPosition(go2).x)); break; case Axis.Y: orderedlist.Sort((go1, go2) => nAda.GetObjectPosition(go1).y.CompareTo(nAda.GetObjectPosition(go2).y)); break; case Axis.Z: orderedlist.Sort((go1, go2) => nAda.GetObjectPosition(go1).z.CompareTo(nAda.GetObjectPosition(go2).z)); break; default: throw new NotImplementedException("unknown split axis: " + axis.ToString()); } var left_s = orderedlist.GetRange(0, center); var right_s = orderedlist.GetRange(center, splitlist.Count - center); double SAH = SAofList(nAda, left_s) * left_s.Count + SAofList(nAda, right_s) * right_s.Count; return(new SplitAxisOpt <GO>(SAH, axis, left_s, right_s)); }); // perform the split ContainedObjects = null; Left = new ssBVHNode <GO>(nAda.BVH, this, bestSplit.left, Depth + 1); // Split the Hierarchy to the left Right = new ssBVHNode <GO>(nAda.BVH, this, bestSplit.right, Depth + 1); // Split the Hierarchy to the right }
private bool RefitVolume(SSBVHNodeAdaptor <GO> nAda) { if (ContainedObjects.Count == 0) { throw new NotImplementedException(); } // TODO: fix this... we should never get called in this case... Bounds_d oldbox = Bounds; ComputeVolume(nAda); if (!Bounds.Equals(oldbox)) { if (Parent != null) { Parent.ChildRefit(nAda); } return(true); } return(false); }