internal void SplitIfNecessary(IBVHNodeAdapter <T> nAda) { if (GObjects.Count > nAda.BVH.LEAF_OBJ_MAX) { SplitNode(nAda); } }
internal static void AddObjectPushdown(IBVHNodeAdapter <T> nAda, BVHNode <T> curNode, T newOb) { var left = curNode.Left; var right = curNode.Right; // merge and pushdown left and right as a new node.. var mergedSubnode = new BVHNode <T>(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 BVHNode <T>(nAda.BVH); newSubnode.Parent = curNode; newSubnode.GObjects = new List <T> { 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 static float SA(IBVHNodeAdapter <T> nAda, T obj) { float radius = nAda.GetRadius(obj); float size = radius * 2; return(6.0f * (size * size)); }
internal void ComputeVolume(IBVHNodeAdapter <T> nAda) { AssignVolume(nAda.GetObjectPos(GObjects[0]), nAda.GetRadius(GObjects[0])); for (int i = 1; i < GObjects.Count; i++) { ExpandVolume(nAda, nAda.GetObjectPos(GObjects[i]), nAda.GetRadius(GObjects[i])); } }
internal float SAofList(IBVHNodeAdapter <T> nAda, List <T> list) { var box = AABBofOBJ(nAda, list[0]); list.ToList <T>().GetRange(1, list.Count - 1).ForEach(obj => { var newbox = AABBofOBJ(nAda, obj); box.Encapsulate(newbox); }); return(SA(box)); }
internal static Bounds AABBofOBJ(IBVHNodeAdapter <T> nAda, T obj) { float radius = nAda.GetRadius(obj); Bounds box = new Bounds { min = new Vector3(-radius, -radius, -radius), max = new Vector3(radius, radius, radius) }; return(box); }
void SetDepth(IBVHNodeAdapter <T> 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); } }
internal void FindOverlappingLeaves(IBVHNodeAdapter <T> nAda, Vector3 origin, float radius, List <BVHNode <T> > overlapList) { if (BoundsIntersectsSphere(ToBounds(), origin, radius)) { if (GObjects != null) { overlapList.Add(this); } else { Left.FindOverlappingLeaves(nAda, origin, radius, overlapList); Right.FindOverlappingLeaves(nAda, origin, radius, overlapList); } } }
internal void FindOverlappingLeaves(IBVHNodeAdapter <T> nAda, Bounds aabb, List <BVHNode <T> > overlapList) { if (ToBounds().Intersects(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 BVH(IBVHNodeAdapter <T> nodeAdaptor, List <T> objects, int LEAF_OBJ_MAX = 1) { this.LEAF_OBJ_MAX = LEAF_OBJ_MAX; nodeAdaptor.BVH = this; this.nAda = nodeAdaptor; if (objects.Count > 0) { rootBVH = new BVHNode <T>(this, objects); } else { rootBVH = new BVHNode <T>(this); rootBVH.GObjects = new List <T>(); // it's a leaf, so give it an empty object list } }
internal static void ChildRefit(IBVHNodeAdapter <T> nAda, BVHNode <T> curNode, bool propagate = true) { do { Bounds oldbox = curNode.Box; BVHNode <T> left = curNode.Left; BVHNode <T> right = curNode.Right; // start with the left box Bounds newBox = left.Box; // expand any dimension bigger in the right node if (right.Box.min.x < newBox.min.x) { newBox.min = new Vector3(right.Box.min.x, newBox.min.y, newBox.min.z); } if (right.Box.min.y < newBox.min.y) { newBox.min = new Vector3(newBox.min.x, right.Box.min.y, newBox.min.z); } if (right.Box.min.z < newBox.min.z) { newBox.min = new Vector3(newBox.min.x, newBox.min.y, right.Box.min.z); } if (right.Box.max.x > newBox.max.x) { newBox.max = new Vector3(right.Box.max.x, newBox.max.y, newBox.max.z); } if (right.Box.max.y > newBox.max.y) { newBox.max = new Vector3(newBox.max.x, right.Box.max.y, newBox.max.z); } if (right.Box.max.z > newBox.max.z) { newBox.max = new Vector3(newBox.max.x, newBox.max.y, 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); }
internal void RemoveLeaf(IBVHNodeAdapter <T> nAda, BVHNode <T> removeLeaf) { if (Left == null || Right == null) { throw new Exception("bad intermediate node"); } BVHNode <T> 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(nAda, 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); } }
public void RefitObjectChanged(IBVHNodeAdapter <T> nAda, T obj) { if (GObjects == 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 ExpandVolume(IBVHNodeAdapter <T> nAda, Vector3 objectpos, float radius) { bool expanded = false; // test min X and max X against the current bounding volume if ((objectpos.x - radius) < Box.min.x) { Box.min = new Vector3(objectpos.x - radius, Box.min.y, Box.min.z); expanded = true; } if ((objectpos.x + radius) > Box.max.x) { Box.max = new Vector3(objectpos.x + radius, Box.max.y, Box.max.z); expanded = true; } // test min Y and max Y against the current bounding volume if ((objectpos.y - radius) < Box.min.y) { Box.min = new Vector3(Box.min.x, (objectpos.y - radius), Box.min.z); expanded = true; } if ((objectpos.y + radius) > Box.max.y) { Box.max = new Vector3(Box.max.x, (objectpos.y + radius), Box.max.z); expanded = true; } // test min Z and max Z against the current bounding volume if ((objectpos.z - radius) < Box.min.z) { Box.min = new Vector3(Box.min.x, Box.min.y, (objectpos.z - radius)); expanded = true; } if ((objectpos.z + radius) > Box.max.z) { Box.max = new Vector3(Box.max.x, Box.max.y, (objectpos.z + radius)); expanded = true; } if (expanded && Parent != null) { Parent.ChildExpanded(nAda, this); } }
private BVHNode(BVH <T> bvh, BVHNode <T> lparent, List <T> gobjectlist, Axis lastSplitAxis, int curdepth) { IBVHNodeAdapter <T> 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); } }
internal void SplitNode(IBVHNodeAdapter <T> nAda) { // second, decide which axis to split on, and sort.. List <T> splitlist = GObjects; splitlist.ForEach(o => nAda.UnmapObject(o)); int center = (int)(splitlist.Count / 2); // find the center object SplitAxisOpt <T> bestSplit = EachAxis.Min((axis) => { var orderedlist = new List <T>(splitlist); switch (axis) { case Axis.X: orderedlist.Sort(delegate(T go1, T go2) { return(nAda.GetObjectPos(go1).x.CompareTo(nAda.GetObjectPos(go2).x)); }); break; case Axis.Y: orderedlist.Sort(delegate(T go1, T go2) { return(nAda.GetObjectPos(go1).y.CompareTo(nAda.GetObjectPos(go2).y)); }); break; case Axis.Z: orderedlist.Sort(delegate(T go1, T go2) { return(nAda.GetObjectPos(go1).z.CompareTo(nAda.GetObjectPos(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); float SAH = SAofList(nAda, left_s) * left_s.Count + SAofList(nAda, right_s) * right_s.Count; return(new SplitAxisOpt <T>(SAH, axis, left_s, right_s)); }); // perform the split GObjects = null; this.Left = new BVHNode <T>(nAda.BVH, this, bestSplit.left, bestSplit.axis, this.Depth + 1); // Split the Hierarchy to the left this.Right = new BVHNode <T>(nAda.BVH, this, bestSplit.right, bestSplit.axis, this.Depth + 1); // Split the Hierarchy to the right }
internal void ChildExpanded(IBVHNodeAdapter <T> nAda, BVHNode <T> child) { bool expanded = false; if (child.Box.min.x < Box.min.x) { Box.min = new Vector3(child.Box.min.x, Box.min.y, Box.min.z); expanded = true; } if (child.Box.max.x > Box.max.x) { Box.max = new Vector3(child.Box.max.x, Box.max.y, Box.max.z); expanded = true; } if (child.Box.min.y < Box.min.y) { Box.min = new Vector3(Box.min.x, child.Box.min.y, Box.min.z); expanded = true; } if (child.Box.max.y > Box.max.y) { Box.max = new Vector3(Box.max.x, child.Box.max.y, Box.max.z); expanded = true; } if (child.Box.min.z < Box.min.z) { Box.min = new Vector3(Box.min.x, Box.min.y, child.Box.min.z); expanded = true; } if (child.Box.max.z > Box.max.z) { Box.max = new Vector3(Box.max.x, Box.max.y, child.Box.max.z); expanded = true; } if (expanded && Parent != null) { Parent.ChildExpanded(nAda, this); } }
internal bool RefitVolume(IBVHNodeAdapter <T> nAda) { if (GObjects.Count == 0) { // TODO: fix this... we should never get called in this case... throw new NotImplementedException(); } Bounds oldbox = Box; ComputeVolume(nAda); if (!Box.Equals(oldbox)) { if (Parent != null) { Parent.ChildRefit(nAda); } return(true); } else { return(false); } }
internal void Remove(IBVHNodeAdapter <T> nAda, T newOb) { if (GObjects == null) { throw new Exception("removeObject() called on nonLeaf!"); } nAda.UnmapObject(newOb); GObjects.Remove(newOb); if (GObjects.Count > 0) { RefitVolume(nAda); } else { // our leaf is empty, so collapse it if we are not the root... if (Parent != null) { GObjects = null; Parent.RemoveLeaf(nAda, this); Parent = null; } } }
internal void Add(IBVHNodeAdapter <T> nAda, T newOb, ref Bounds newObBox, float newObSAH) { Add(nAda, this, newOb, ref newObBox, newObSAH); }
internal void ChildRefit(IBVHNodeAdapter <T> nAda, bool propagate = true) { ChildRefit(nAda, this, propagate: propagate); }
/// <summary> /// tryRotate looks at all candidate rotations, and executes the rotation with the best resulting SAH (if any) /// </summary> /// <param name="bvh"></param> internal void TryRotate(BVH <T> bvh) { IBVHNodeAdapter <T> nAda = bvh.nAda; // if we are not a grandparent, then we can't rotate, so queue our parent and bail out if (Left.IsLeaf && Right.IsLeaf) { if (Parent != null) { bvh.refitNodes.Add(Parent); return; } } // for each rotation, check that there are grandchildren as necessary (aka not a leaf) // then compute total SAH cost of our branches after the rotation. float mySA = SA(Left) + SA(Right); RotOpt bestRot = EachRot.Min((rot) => { switch (rot) { case Rot.NONE: return(new RotOpt(mySA, Rot.NONE)); // child to grandchild rotations case Rot.L_RL: if (Right.IsLeaf) { return(new RotOpt(float.MaxValue, Rot.NONE)); } else { return(new RotOpt(SA(Right.Left) + SA(AABBofPair(Left, Right.Right)), rot)); } case Rot.L_RR: if (Right.IsLeaf) { return(new RotOpt(float.MaxValue, Rot.NONE)); } else { return(new RotOpt(SA(Right.Right) + SA(AABBofPair(Left, Right.Left)), rot)); } case Rot.R_LL: if (Left.IsLeaf) { return(new RotOpt(float.MaxValue, Rot.NONE)); } else { return(new RotOpt(SA(AABBofPair(Right, Left.Right)) + SA(Left.Left), rot)); } case Rot.R_LR: if (Left.IsLeaf) { return(new RotOpt(float.MaxValue, Rot.NONE)); } else { return(new RotOpt(SA(AABBofPair(Right, Left.Left)) + SA(Left.Right), rot)); } // grandchild to grandchild rotations case Rot.LL_RR: if (Left.IsLeaf || Right.IsLeaf) { return(new RotOpt(float.MaxValue, Rot.NONE)); } else { return(new RotOpt(SA(AABBofPair(Right.Right, Left.Right)) + SA(AABBofPair(Right.Left, Left.Left)), rot)); } case Rot.LL_RL: if (Left.IsLeaf || Right.IsLeaf) { return(new RotOpt(float.MaxValue, Rot.NONE)); } else { return(new RotOpt(SA(AABBofPair(Right.Left, Left.Right)) + SA(AABBofPair(Left.Left, Right.Right)), rot)); } // unknown... default: throw new NotImplementedException("missing implementation for BVH Rotation SAH Computation .. " + rot.ToString()); } }); // perform the best rotation... if (bestRot.rot != Rot.NONE) { // if the best rotation is no-rotation... we check our parents anyhow.. if (Parent != null) { // but only do it some random percentage of the time. if ((DateTime.Now.Ticks % 100) < 2) { bvh.refitNodes.Add(Parent); } } } else { if (Parent != null) { bvh.refitNodes.Add(Parent); } if (((mySA - bestRot.SAH) / mySA) < 0.3f) { return; // the benefit is not worth the cost } Console.WriteLine("BVH swap {0} from {1} to {2}", bestRot.rot.ToString(), mySA, bestRot.SAH); // in order to swap we need to: // 1. swap the node locations // 2. update the depth (if child-to-grandchild) // 3. update the parent pointers // 4. refit the boundary box BVHNode <T> swap = null; switch (bestRot.rot) { case Rot.NONE: break; // child to grandchild rotations case Rot.L_RL: swap = Left; Left = Right.Left; Left.Parent = this; Right.Left = swap; swap.Parent = Right; Right.ChildRefit(nAda, propagate: false); break; case Rot.L_RR: swap = Left; Left = Right.Right; Left.Parent = this; Right.Right = swap; swap.Parent = Right; Right.ChildRefit(nAda, propagate: false); break; case Rot.R_LL: swap = Right; Right = Left.Left; Right.Parent = this; Left.Left = swap; swap.Parent = Left; Left.ChildRefit(nAda, propagate: false); break; case Rot.R_LR: swap = Right; Right = Left.Right; Right.Parent = this; Left.Right = swap; swap.Parent = Left; Left.ChildRefit(nAda, propagate: false); break; // grandchild to grandchild rotations case Rot.LL_RR: swap = Left.Left; Left.Left = Right.Right; Right.Right = swap; Left.Left.Parent = Left; swap.Parent = Right; Left.ChildRefit(nAda, propagate: false); Right.ChildRefit(nAda, propagate: false); break; case Rot.LL_RL: swap = Left.Left; Left.Left = Right.Left; Right.Left = swap; Left.Left.Parent = Left; swap.Parent = Right; Left.ChildRefit(nAda, propagate: false); Right.ChildRefit(nAda, propagate: false); break; // unknown... default: throw new NotImplementedException("missing implementation for BVH Rotation .. " + bestRot.rot.ToString()); } // fix the depths if necessary.... switch (bestRot.rot) { case Rot.L_RL: case Rot.L_RR: case Rot.R_LL: case Rot.R_LR: this.SetDepth(nAda, this.Depth); break; } } }
internal static void Add(IBVHNodeAdapter <T> nAda, BVHNode <T> curNode, T newOb, ref Bounds 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); //Create new bounds to avoid modifying originals when using encapsulate Bounds leftExpanded = new Bounds { min = left.Box.min, max = left.Box.max }; Bounds rightExpanded = new Bounds { min = right.Box.min, max = right.Box.max }; leftExpanded.Encapsulate(newObBox); rightExpanded.Encapsulate(newObBox); float sendLeftSAH = rightSAH + SA(leftExpanded); // (L+N,R) float sendRightSAH = leftSAH + SA(rightExpanded); // (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) { AddObjectPushdown(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); }