private bool FindInteractionVolumeHit(Ray ray, out int interactionVolumeHitIndex, out IntersectInfo info) { interactionVolumeHitIndex = -1; if (interactionVolumes.Count == 0 || interactionVolumes[0].CollisionVolume == null) { info = null; return(false); } List <IRayTraceable> mesheTraceables = new List <IRayTraceable>(); foreach (InteractionVolume interactionVolume in interactionVolumes) { IRayTraceable traceData = interactionVolume.CollisionVolume; mesheTraceables.Add(new Transform(traceData, interactionVolume.TotalTransform)); } IRayTraceable allObjects = BoundingVolumeHierarchy.CreateNewHierachy(mesheTraceables); info = allObjects.GetClosestIntersection(ray); if (info != null) { for (int i = 0; i < interactionVolumes.Count; i++) { List <IRayTraceable> insideBounds = new List <IRayTraceable>(); interactionVolumes[i].CollisionVolume.GetContained(insideBounds, info.closestHitObject.GetAxisAlignedBoundingBox()); if (insideBounds.Contains(info.closestHitObject)) { interactionVolumeHitIndex = i; return(true); } } } return(false); }
public IEnumerable IntersectionIterator(Ray ray) { if (ray.Intersection(Aabb)) { IRayTraceable checkFirst = nodeA; IRayTraceable checkSecond = nodeB; if (ray.directionNormal[splitingPlane] < 0) { checkFirst = nodeB; checkSecond = nodeA; } foreach (IntersectInfo info in checkFirst.IntersectionIterator(ray)) { if (info != null && info.hitType != IntersectionType.None) { yield return(info); } } if (checkSecond != null) { foreach (IntersectInfo info in checkSecond.IntersectionIterator(ray)) { if (info != null && info.hitType != IntersectionType.None) { yield return(info); } } } } }
public BoundingVolumeHierarchy(IRayTraceable nodeA, IRayTraceable nodeB, int splitingPlane) { this.splitingPlane = splitingPlane; this.nodeA = nodeA; this.nodeB = nodeB; this.Aabb = nodeA.GetAxisAlignedBoundingBox() + nodeB.GetAxisAlignedBoundingBox(); // we can cache this because it is not allowed to change. }
private void AddTestStl() { Stopwatch loadTime = new Stopwatch(); loadTime.Start(); PolygonMesh.Mesh simpleMesh = StlProcessing.Load("Simple.stl")[0].Meshes[0]; //PolygonMesh.Mesh simpleMesh = StlProcessing.Load("Complex.stl"); //PolygonMesh.Mesh simpleMesh = StlProcessing.Load("bunny.stl"); //PolygonMesh.Mesh simpleMesh = StlProcessing.Load("bunny_binary.stl"); loadTime.Stop(); timingStrings.Add("Time to load STL {0:0.0}s".FormatWith(loadTime.Elapsed.TotalSeconds)); Stopwatch bvhTime = new Stopwatch(); bvhTime.Start(); IRayTraceable bvhCollection = MeshToBVH.Convert(simpleMesh); bvhTime.Stop(); timingStrings.Add("Time to create BVH {0:0.0}s".FormatWith(bvhTime.Elapsed.TotalSeconds)); renderCollection.Add(bvhCollection); }
public static void CreateITraceableForMeshGroup(List <PlatingMeshGroupData> perMeshGroupInfo, List <MeshGroup> meshGroups, int meshGroupIndex, ReportProgressRatio reportProgress) { if (meshGroups != null) { MeshGroup meshGroup = meshGroups[meshGroupIndex]; perMeshGroupInfo[meshGroupIndex].meshTraceableData.Clear(); int totalActionCount = 0; foreach (Mesh mesh in meshGroup.Meshes) { totalActionCount += mesh.Faces.Count; } int currentAction = 0; bool needUpdateTitle = true; for (int i = 0; i < meshGroup.Meshes.Count; i++) { Mesh mesh = meshGroup.Meshes[i]; List <IRayTraceable> allPolys = AddTraceDataForMesh(mesh, totalActionCount, ref currentAction, ref needUpdateTitle, reportProgress); needUpdateTitle = true; if (reportProgress != null) { bool continueProcessing; reportProgress(currentAction / (double)totalActionCount, "Creating Trace Group", out continueProcessing); } // only allow limited recusion to speed this up building this data IRayTraceable traceData = BoundingVolumeHierarchy.CreateNewHierachy(allPolys, 0); perMeshGroupInfo[meshGroupIndex].meshTraceableData.Add(traceData); } } }
public Transform(IRayTraceable root, Matrix4X4 transform) { this.child = root; WorldToAxis = transform; AxisToWorld = Matrix4X4.Invert(WorldToAxis); AxisToWorld = transform; WorldToAxis = Matrix4X4.Invert(AxisToWorld); }
public IntersectInfo(IntersectInfo copyInfo) { this.hitType = copyInfo.hitType; this.closestHitObject = copyInfo.closestHitObject; this.hitPosition = copyInfo.hitPosition; this.normalAtHit = copyInfo.normalAtHit; this.distanceToHit = copyInfo.distanceToHit; }
public IntersectInfo GetClosestIntersection(Ray ray) { if (ray.Intersection(Aabb)) { IRayTraceable checkFirst = nodeA; IRayTraceable checkSecond = nodeB; if (ray.directionNormal[splitingPlane] < 0) { checkFirst = nodeB; checkSecond = nodeA; } IntersectInfo infoFirst = checkFirst.GetClosestIntersection(ray); if (infoFirst != null && infoFirst.hitType != IntersectionType.None) { if (ray.isShadowRay) { return(infoFirst); } else { ray.maxDistanceToConsider = infoFirst.distanceToHit; } } if (checkSecond != null) { IntersectInfo infoSecond = checkSecond.GetClosestIntersection(ray); if (infoSecond != null && infoSecond.hitType != IntersectionType.None) { if (ray.isShadowRay) { return(infoSecond); } else { ray.maxDistanceToConsider = infoSecond.distanceToHit; } } if (infoFirst != null && infoFirst.hitType != IntersectionType.None && infoFirst.distanceToHit >= 0) { if (infoSecond != null && infoSecond.hitType != IntersectionType.None && infoSecond.distanceToHit < infoFirst.distanceToHit && infoSecond.distanceToHit >= 0) { return(infoSecond); } else { return(infoFirst); } } return(infoSecond); // we don't have to test it because it didn't hit. } return(infoFirst); } return(null); }
long CalculateIntersectCostsForItem(IRayTraceable item, int numInterations) { Stopwatch timer = new Stopwatch(); timer.Start(); for (int i = 0; i < numInterations; i++) { item.GetClosestIntersection(GetRandomIntersectingRay()); } return timer.ElapsedMilliseconds; }
long CalculateIntersectCostsForItem(IRayTraceable item, int numInterations) { Stopwatch timer = new Stopwatch(); timer.Start(); for (int i = 0; i < numInterations; i++) { item.GetClosestIntersection(GetRandomIntersectingRay()); } return(timer.ElapsedMilliseconds); }
private IntersectInfo FindNextIntersections(IRayTraceable element, Ray ray, IntersectInfo info, IntersectionType intersectionType) { // get all the intersection for the object Ray currentRayCheckBackfaces = new Ray(ray); currentRayCheckBackfaces.intersectionType = intersectionType; currentRayCheckBackfaces.minDistanceToConsider = ((info.hitPosition + ray.directionNormal * Ray.sameSurfaceOffset) - ray.origin).Length; currentRayCheckBackfaces.maxDistanceToConsider = double.PositiveInfinity; return(element.GetClosestIntersection(currentRayCheckBackfaces)); }
/// <summary> /// This will remove the shapes from 'Shapes' and add them to a Bounding Volume Hierarchy. Then add that at a single element /// to 'Shapes'. You could also create a list of 'List<IRayTraceable>' and put that dirrectly into a BVH and then add that /// to the Shapes list (there could be more than 1 BVH in the 'Shapes' list. /// </summary> public IRayTraceable MoveShapesIntoBoundingVolumeHierachy() { IRayTraceable rootObject = BoundingVolumeHierarchy.CreateNewHierachy(shapes); if (rootObject != null) { shapes.Clear(); shapes.Add(rootObject); } return(rootObject); }
private void AddBoxAndBoxBooleanTest() { BoxShape box1 = new BoxShape(new Vector3(.5, .5, .5), new Vector3(1.5, 1.5, 1.5), new SolidMaterial(RGBA_Floats.Green, .01, 0.0, 2.0)); List <IRayTraceable> subtractShapes = new List <IRayTraceable>(); SolidMaterial material = new SolidMaterial(RGBA_Floats.Red, 0, 0, 0); subtractShapes.Add(new BoxShape(new Vector3(), new Vector3(1, 1, 1), material)); IRayTraceable subtractGroup = BoundingVolumeHierarchy.CreateNewHierachy(subtractShapes); Difference merge = new Difference(box1, subtractGroup); renderCollection.Add(merge); }
private bool FindMeshGroupHitPosition(Vector2 screenPosition, out int meshHitIndex) { meshHitIndex = 0; if (MeshGroupExtraData.Count == 0 || MeshGroupExtraData[0].meshTraceableData == null) { return(false); } List <IRayTraceable> mesheTraceables = new List <IRayTraceable>(); for (int i = 0; i < MeshGroupExtraData.Count; i++) { foreach (IRayTraceable traceData in MeshGroupExtraData[i].meshTraceableData) { mesheTraceables.Add(new Transform(traceData, MeshGroupTransforms[i].TotalTransform)); } } IRayTraceable allObjects = BoundingVolumeHierarchy.CreateNewHierachy(mesheTraceables); Vector2 meshViewerWidgetScreenPosition = meshViewerWidget.TransformFromParentSpace(this, screenPosition); Ray ray = meshViewerWidget.TrackballTumbleWidget.GetRayFromScreen(meshViewerWidgetScreenPosition); IntersectInfo info = allObjects.GetClosestIntersection(ray); if (info != null) { meshSelectInfo.planeDownHitPos = info.hitPosition; meshSelectInfo.lastMoveDelta = new Vector3(); for (int i = 0; i < MeshGroupExtraData.Count; i++) { List <IRayTraceable> insideBounds = new List <IRayTraceable>(); foreach (IRayTraceable traceData in MeshGroupExtraData[i].meshTraceableData) { traceData.GetContained(insideBounds, info.closestHitObject.GetAxisAlignedBoundingBox()); } if (insideBounds.Contains(info.closestHitObject)) { meshHitIndex = i; return(true); } } } return(false); }
public override void OnMouseDown(MouseEventArgs mouseEvent) { base.OnMouseDown(mouseEvent); lastMouseMovePoint.x = mouseEvent.X; lastMouseMovePoint.y = mouseEvent.Y; if (Focused && MouseCaptured) { if (trackBallController.CurrentTrackingType == TrackBallController.MouseDownType.None) { if (Focused && MouseCaptured && mouseEvent.Button == MouseButtons.Left) { trackBallController.OnMouseDown(lastMouseMovePoint, Matrix4X4.Identity); } else if (mouseEvent.Button == MouseButtons.Middle) { trackBallController.OnMouseDown(lastMouseMovePoint, Matrix4X4.Identity, TrackBallController.MouseDownType.Translation); } } if (MouseCaptured) { lastMouseMovePoint.x = mouseEvent.X; lastMouseMovePoint.y = mouseEvent.Y; cameraDataAtStartOfMouseTracking = cameraData; cameraDataAtStartOfMouseTracking.cameraMatrix = scene.camera.axisToWorld; Ray rayAtPoint = scene.camera.GetRay(lastMouseMovePoint.x, lastMouseMovePoint.y); IntersectInfo info = raytracer.TracePrimaryRay(rayAtPoint, scene); if (info != null) { focusedObject = (BaseShape)info.closestHitObject; if (focusedObject != null && mouseEvent.Clicks == 2) { cameraData.lookAtPoint = focusedObject.GetAxisAlignedBoundingBox().Center; OrientCamera(); } } } } }
public void GetClosestIntersections(RayBundle rayBundle, int rayIndexToStartCheckingFrom, IntersectInfo[] intersectionsForBundle) { int startRayIndex = FindFirstRay(rayBundle, rayIndexToStartCheckingFrom); if (startRayIndex != -1) { IRayTraceable checkFirst = nodeA; IRayTraceable checkSecond = nodeB; if (rayBundle.rayArray[startRayIndex].directionNormal[splitingPlane] < 0) { checkFirst = nodeB; checkSecond = nodeA; } checkFirst.GetClosestIntersections(rayBundle, startRayIndex, intersectionsForBundle); if (checkSecond != null) { checkSecond.GetClosestIntersections(rayBundle, startRayIndex, intersectionsForBundle); } } }
private void AddBoxAndSheresBooleanTest() { RayTracer.BoxShape box1 = new RayTracer.BoxShape(new Vector3(.5, .5, .5), new Vector3(1.5, 1.5, 1.5), new RayTracer.SolidMaterial(RGBA_Floats.Green, 0, 0, 0));//.01, 0.0, 2.0)); List <RayTracer.IRayTraceable> subtractShapes = new List <RayTracer.IRayTraceable>(); RayTracer.SolidMaterial material = new RayTracer.SolidMaterial(RGBA_Floats.Red, 0, 0, 0); #if true // two big spheres. Looks good. subtractShapes.Add(new RayTracer.SphereShape(new Vector3(.5, .5, 1), .6, material)); subtractShapes.Add(new RayTracer.SphereShape(new Vector3(1.5, .5, 1), .6, material)); Transform cylinder = new Transform(new RayTracer.CylinderShape(.1, 3, material)); cylinder.MoveToAbsolute(1, 1, 1); cylinder.RotateRelative(.1, .6, .6); //subtractShapes.Add(cylinder); //renderCollection.Add(cylinder); #else for (int z = 0; z < 6; z++) { for (int y = 0; y < 6; y++) { for (int x = 0; x < 6; x++) { subtractShapes.Add(new SphereShape(new Vector3(x * .2 + .5, y * .2 + .5, z * .2 + .5), .1, material)); //subtractShapes.Add(new SphereShape(new Vector3(x * .2 + .5, y * .2 + .5, z * .2 + .5), .13, material)); } } } #endif IRayTraceable subtractGroup = BoundingVolumeHierarchy.CreateNewHierachy(subtractShapes); Difference merge = new Difference(box1, subtractGroup); renderCollection.Add(merge); }
private void CreateScene() { scene = new Scene(); scene.camera = new Camera((int)Width, (int)Height, MathHelper.DegreesToRadians(40)); scene.background = new Background(new RGBA_Floats(0.5, .5, .5), 0.4); //AddBoxAndSheresBooleanTest(); //AddBoxAndBoxBooleanTest(); #if false renderCollection.Add(new BoxShape(new Vector3(), new Vector3(1, 1, 1), new SolidMaterial(new RGBA_Floats(.9, .2, .1), .01, 0.0, 2.0))); renderCollection.Add(new BoxShape(new Vector3(.5, .5, .5), new Vector3(1.5, 1.5, 1.5), new SolidMaterial(new RGBA_Floats(.9, .2, .1), .01, 0.0, 2.0))); #endif //renderCollection.Add(new CylinderShape(.25, 1, new SolidMaterial(RGBA_Floats.Cyan, 0, 0, 0))); AddTestStl(); //AddPolygonTest(); //AddSphereAndBox(); //AddAxisMarker(); //AddCubeOfShperes(); //renderCollection.Add(MakerGearXCariage()); allObjects = BoundingVolumeHierarchy.CreateNewHierachy(renderCollection); trackBallTransform = new Transform(allObjects); //allObjects = root; scene.shapes.Add(trackBallTransform); //AddAFloor(); //add two lights for better lighting effects scene.lights.Add(new Light(new Vector3(50, 10, 100), new RGBA_Floats(0.8, 0.8, 0.8))); scene.lights.Add(new Light(new Vector3(-30, 150, 50), new RGBA_Floats(0.8, 0.8, 0.8))); OrientCamera(); }
public Transform(IRayTraceable root) { this.child = root; }
public static IRayTraceable CreateNewHierachy(List <IRayTraceable> traceableItems, int maxRecursion = int.MaxValue, int recursionDepth = 0, SortingAccelerator accelerator = null) { if (accelerator == null) { accelerator = new SortingAccelerator(); } int numItems = traceableItems.Count; if (numItems == 0) { return(null); } if (numItems == 1) { return(traceableItems[0]); } int bestAxis = -1; int bestIndexToSplitOn = -1; CompareCentersOnAxis axisSorter = new CompareCentersOnAxis(0); if (recursionDepth < maxRecursion) { if (numItems > 5000) { bestAxis = accelerator.NextAxis; bestIndexToSplitOn = numItems / 2; } else { double totalIntersectCost = 0; int skipInterval = 1; for (int i = 0; i < numItems; i += skipInterval) { IRayTraceable item = traceableItems[i]; totalIntersectCost += item.GetIntersectCost(); } // get the bounding box of all the items we are going to consider. AxisAlignedBoundingBox OverallBox = traceableItems[0].GetAxisAlignedBoundingBox(); for (int i = skipInterval; i < numItems; i += skipInterval) { OverallBox += traceableItems[i].GetAxisAlignedBoundingBox(); } double areaOfTotalBounds = OverallBox.GetSurfaceArea(); double bestCost = totalIntersectCost; Vector3 totalDeviationOnAxis = new Vector3(); double[] surfaceArreaOfItem = new double[numItems - 1]; double[] rightBoundsAtItem = new double[numItems - 1]; for (int axis = 0; axis < 3; axis++) { double intersectCostOnLeft = 0; axisSorter.WhichAxis = axis; traceableItems.Sort(axisSorter); // Get all left bounds AxisAlignedBoundingBox currentLeftBounds = traceableItems[0].GetAxisAlignedBoundingBox(); surfaceArreaOfItem[0] = currentLeftBounds.GetSurfaceArea(); for (int itemIndex = 1; itemIndex < numItems - 1; itemIndex += skipInterval) { currentLeftBounds += traceableItems[itemIndex].GetAxisAlignedBoundingBox(); surfaceArreaOfItem[itemIndex] = currentLeftBounds.GetSurfaceArea(); totalDeviationOnAxis[axis] += Math.Abs(traceableItems[itemIndex].GetCenter()[axis] - traceableItems[itemIndex - 1].GetCenter()[axis]); } // Get all right bounds if (numItems > 1) { AxisAlignedBoundingBox currentRightBounds = traceableItems[numItems - 1].GetAxisAlignedBoundingBox(); rightBoundsAtItem[numItems - 2] = currentRightBounds.GetSurfaceArea(); for (int itemIndex = numItems - 1; itemIndex > 1; itemIndex -= skipInterval) { currentRightBounds += traceableItems[itemIndex - 1].GetAxisAlignedBoundingBox(); rightBoundsAtItem[itemIndex - 2] = currentRightBounds.GetSurfaceArea(); } } // Sweep from left for (int itemIndex = 0; itemIndex < numItems - 1; itemIndex += skipInterval) { double thisCost = 0; { // Evaluate Surface Cost Equation double costOfTwoAABB = 2 * AxisAlignedBoundingBox.GetIntersectCost(); // the cost of the two children AABB tests // do the left cost intersectCostOnLeft += traceableItems[itemIndex].GetIntersectCost(); double leftCost = (surfaceArreaOfItem[itemIndex] / areaOfTotalBounds) * intersectCostOnLeft; // do the right cost double intersectCostOnRight = totalIntersectCost - intersectCostOnLeft; double rightCost = (rightBoundsAtItem[itemIndex] / areaOfTotalBounds) * intersectCostOnRight; thisCost = costOfTwoAABB + leftCost + rightCost; } if (thisCost < bestCost + .000000001) // if it is less within some tiny error { if (thisCost > bestCost - .000000001) { // they are the same within the error if (axis > 0 && bestAxis != axis) // we have changed axis since last best and we need to decide if this is better than the last axis best { if (totalDeviationOnAxis[axis] > totalDeviationOnAxis[axis - 1]) { // this new axis is better and we'll switch to it. Otherwise don't switch. bestCost = thisCost; bestIndexToSplitOn = itemIndex; bestAxis = axis; } } } else // this is just better { bestCost = thisCost; bestIndexToSplitOn = itemIndex; bestAxis = axis; } } } } } } if (bestAxis == -1) { // No better partition found return(new UnboundCollection(traceableItems)); } else { axisSorter.WhichAxis = bestAxis; traceableItems.Sort(axisSorter); List <IRayTraceable> leftItems = new List <IRayTraceable>(bestIndexToSplitOn + 1); List <IRayTraceable> rightItems = new List <IRayTraceable>(numItems - bestIndexToSplitOn + 1); for (int i = 0; i <= bestIndexToSplitOn; i++) { leftItems.Add(traceableItems[i]); } for (int i = bestIndexToSplitOn + 1; i < numItems; i++) { rightItems.Add(traceableItems[i]); } IRayTraceable leftGroup = CreateNewHierachy(leftItems, maxRecursion, recursionDepth + 1, accelerator); IRayTraceable rightGroup = CreateNewHierachy(rightItems, maxRecursion, recursionDepth + 1, accelerator); BoundingVolumeHierarchy newBVHNode = new BoundingVolumeHierarchy(leftGroup, rightGroup, bestAxis); return(newBVHNode); } }
private IntersectInfo FindNextIntersections(IRayTraceable element, Ray ray, IntersectInfo info, IntersectionType intersectionType) { // get all the intersection for the object Ray currentRayCheckBackfaces = new Ray(ray); currentRayCheckBackfaces.intersectionType = intersectionType; currentRayCheckBackfaces.minDistanceToConsider = ((info.hitPosition + ray.direction * Ray.sameSurfaceOffset) - ray.origin).Length; currentRayCheckBackfaces.maxDistanceToConsider = double.PositiveInfinity; return element.GetClosestIntersection(currentRayCheckBackfaces); }
public Difference(IRayTraceable primary, IRayTraceable subtract) { this.primary = primary; this.subtract = subtract; }
public InteractionVolume(IRayTraceable collisionVolume, MeshViewerWidget meshViewerToDrawWith) { this.collisionVolume = collisionVolume; this.meshViewerToDrawWith = meshViewerToDrawWith; }
public void DifferenceTestsForBox() { SolidMaterial redMaterial = new SolidMaterial(RGBA_Floats.Red, 0, 0, 0); SolidMaterial blueMaterial = new SolidMaterial(RGBA_Floats.Blue, 0, 0, 0); Ray castRay = new Ray(new Vector3(0, -1, 0), Vector3.UnitY); BoxShape box1X1 = new BoxShape(new Vector3(-.5, -.5, -.5), new Vector3(.5, .5, .5), blueMaterial); // just a box all by itself { IntersectInfo testInfo = box1X1.GetClosestIntersection(castRay); Assert.IsTrue(testInfo.hitType == IntersectionType.FrontFace, "Found Hit : Box No CSG"); Assert.IsTrue(testInfo.closestHitObject == box1X1, "Found Hit : Box No CSG"); Assert.IsTrue(testInfo.hitPosition == new Vector3(0, -.5, 0), "Hit position y = -.5 : Box No CSG"); Assert.IsTrue(testInfo.distanceToHit == .5, "Hit length = .5 : Box No CSG"); Assert.IsTrue(testInfo.normalAtHit == -Vector3.UnitY, "Normal Correct : Box No CSG"); } // one subtract from the front of a box, the front faces are aligned { BoxShape subtractBox = new BoxShape(new Vector3(-.5, -.5, -.5), new Vector3(.5, 0, .5), redMaterial); Difference merge = new Difference(box1X1, subtractBox); IntersectInfo testInfo = merge.GetClosestIntersection(castRay); Assert.IsTrue(testInfo.hitType == IntersectionType.FrontFace, "Found Hit : One Subtract"); Assert.IsTrue(testInfo.closestHitObject == subtractBox, "Found Hit : One Subtract"); Assert.IsTrue(testInfo.hitPosition == new Vector3(0, 0, 0), "Hit position y = 0 : One Subtract"); Assert.IsTrue(testInfo.distanceToHit == 1, "Hit length = 1 : One Subtract"); Assert.IsTrue(testInfo.normalAtHit == -Vector3.UnitY, "Normal Correct : One Subtract"); } #if false // An internal primary object that needs to be skipped over { List <IRayTraceable> primaryShapes = new List <IRayTraceable>(); BoxShape insideBox = new BoxShape(new Vector3(-.1, -.1, -.1), new Vector3(.1, .1, .1), blueMaterial); primaryShapes.Add(box1X1); primaryShapes.Add(insideBox); IRayTraceable primamryGroup = BoundingVolumeHierarchy.CreateNewHierachy(primaryShapes); List <IRayTraceable> subtractShapes = new List <IRayTraceable>(); subtractShapes.Add(new BoxShape(new Vector3(-.5, -.5, -.5), new Vector3(.5, .4, .5), redMaterial)); IRayTraceable subtractGroup = BoundingVolumeHierarchy.CreateNewHierachy(subtractShapes); Difference merge = new Difference(primamryGroup, subtractGroup); IntersectInfo testInfo = merge.GetClosestIntersection(castRay); Assert.IsTrue(testInfo.isHit == true, "Found Hit : 5 Subtracts"); //Assert.IsTrue(testInfo.closestHitObject == subtractBox, "Found Hit : 5 Subtracts"); Assert.IsTrue(testInfo.hitPosition == new Vector3(0, 0, 0), "Hit position y = 0 : 5 Subtracts"); Assert.IsTrue(testInfo.distanceToHit == 1, "Hit length = 1 : 5 Subtracts"); Assert.IsTrue(testInfo.normalAtHit == -Vector3.UnitY, "Normal Correct : 5 Subtracts"); } // Go through 5 subtract boxes to get to 1/2 way through the main box. { List <IRayTraceable> subtractShapes = new List <IRayTraceable>(); for (int i = 0; i < 5; i++) { subtractShapes.Add(new BoxShape(new Vector3(-.5, -.5 + i * .1, -.5), new Vector3(.5, -.4 + i * .1, .5), redMaterial)); } IRayTraceable subtractGroup = BoundingVolumeHierarchy.CreateNewHierachy(subtractShapes); Difference merge = new Difference(box1X1, subtractGroup); IntersectInfo testInfo = merge.GetClosestIntersection(castRay); Assert.IsTrue(testInfo.isHit == true, "Found Hit : 5 Subtracts"); //Assert.IsTrue(testInfo.closestHitObject == subtractBox, "Found Hit : 5 Subtracts"); Assert.IsTrue(testInfo.hitPosition == new Vector3(0, 0, 0), "Hit position y = 0 : 5 Subtracts"); Assert.IsTrue(testInfo.distanceToHit == 1, "Hit length = 1 : 5 Subtracts"); Assert.IsTrue(testInfo.normalAtHit == -Vector3.UnitY, "Normal Correct : 5 Subtracts"); } #endif }