public bool ExtensionVectorIntersects(ObjectPlacementBoxStackSegment querySegment) { if (NumberOfStacks == 0 || querySegment.NumberOfStacks == 0) { return(false); } if (_extensionDirection.IsPerpendicularTo(querySegment.ExtensionDirection)) { Plane segmentBasePlane = GetBasePlane(); Vector3 thisSegmentStart = segmentBasePlane.ProjectPoint(FirstStackBasePosition); Vector3 thisSegmentEnd = segmentBasePlane.ProjectPoint(LastStackBasePosition); if (NumberOfStacks == 1) { thisSegmentEnd = thisSegmentStart + _extensionDirection * 0.5f * GetBoxSizeAlongNormalizedDirection(_extensionDirection); } Vector3 querySegmentStart = segmentBasePlane.ProjectPoint(querySegment.FirstStackBasePosition); Vector3 querySegmentEnd = segmentBasePlane.ProjectPoint(querySegment.LastStackBasePosition); if (querySegment.NumberOfStacks == 1) { querySegmentEnd = querySegmentStart + querySegment.ExtensionDirection * 0.5f * querySegment.GetBoxSizeAlongNormalizedDirection(querySegment.ExtensionDirection); } Segment3D thisSegment3D = new Segment3D(thisSegmentStart, thisSegmentEnd); Segment3D querySegment3D = new Segment3D(querySegmentStart, querySegmentEnd); return(thisSegment3D.IntersectsWith(querySegment3D)); } return(false); }
public static Plane GetPolygonEdgePlane(Segment3D polyEdge, Vector3 polyNormal) { Vector3 planeNormal = Vector3.Cross(polyEdge.Direction, polyNormal); planeNormal.Normalize(); return(new Plane(planeNormal, polyEdge.StartPoint)); }
private Plane GetSegmentPlane(Segment3D segment) { Vector3 segmentPlaneNormal = Vector3.Cross(segment.Direction, _polygonNormal); segmentPlaneNormal.Normalize(); return(new Plane(segmentPlaneNormal, segment.StartPoint)); }
public Plane GetSegmentPlane(int segmentIndex) { Segment3D segment = GetSegment(segmentIndex); Vector3 segmentPlaneNormal = Vector3.Cross(segment.Direction, _plane.normal); segmentPlaneNormal.Normalize(); return(new Plane(segmentPlaneNormal, segment.StartPoint)); }
public static Plane CalculateSegmentPlaneNormal(ObjectNode firstNode, ObjectNode secondNode) { Segment3D segment = CalculateSegmentBetweenNodes(firstNode, secondNode); Vector3 segmentPlaneNormal = Vector3.Cross(segment.Direction, firstNode.ObjectSurfaceData.SurfaceNormal); segmentPlaneNormal.Normalize(); return(new Plane(segmentPlaneNormal, segment.StartPoint)); }
public bool IntersectsWith(Segment3D segment, out float t1, out float t2) { t1 = t2 = 0.0f; if (_length == 0.0f || segment.Length == 0.0f) { return(false); } // If the segments are parallel, they don't intersect if (segment.NormalizedDirection.IsAlignedWith(_normalizedDirection)) { return(false); } // Check if the 2 segments are coplanar Vector3 segmentPlaneNormal = Vector3.Cross(segment.NormalizedDirection, _normalizedDirection); Vector3 fromThisSegmentStartToOtherSegmentStart = segment.StartPoint - _startPoint; if (!fromThisSegmentStartToOtherSegmentStart.IsPerpendicularTo(segmentPlaneNormal)) { return(false); } // Build a plane which slices thorough the segment Vector3 slicingPlaneNormal = Vector3.Cross(segmentPlaneNormal, _normalizedDirection); slicingPlaneNormal.Normalize(); Plane slicingPlane = new Plane(slicingPlaneNormal, _startPoint); // Check if the query segment intersects the plane float t; Ray3D querySegmentRay = new Ray3D(segment.StartPoint, segment.Direction); if (querySegmentRay.IntersectsPlane(slicingPlane, out t)) { // The segment intersect the plane, but we have to ensure that the intersection point lies somewehre along 'this' segment. Vector3 intersectionPoint = querySegmentRay.Origin + t * querySegmentRay.Direction; Vector3 toIntersectionPoint = intersectionPoint - _startPoint; if (Vector3.Dot(toIntersectionPoint, _normalizedDirection) < 0.0f) { return(false); } if (toIntersectionPoint.sqrMagnitude > _sqrLength) { return(false); } t1 = toIntersectionPoint.magnitude / _length; t2 = t; return(true); } return(false); }
public Plane GetBoundarySegmentPlane(int segmentIndex) { Segment3D boundarySegment = GetBoundarySegment(segmentIndex); Vector3 centerPivotPoint = GetPointByIndex(IndexOfPointInCenter); Vector3 centerPivotPointProjectedOnSegment = centerPivotPoint.CalculateProjectionPointOnSegment(boundarySegment.StartPoint, boundarySegment.EndPoint); Vector3 planeNormal = centerPivotPointProjectedOnSegment - centerPivotPoint; planeNormal.Normalize(); return(new Plane(planeNormal, boundarySegment.StartPoint)); }
public List <Segment3D> Calculate() { Vector3Extensions.EliminateDuplicatePoints(_polygonPointsOnSamePlane); if (_polygonPointsOnSamePlane.Count < 3) { return(new List <Segment3D>()); } List <Vector3> workingPointList = new List <Vector3>(_polygonPointsOnSamePlane); Vector3 minPoint, maxPoint; CalculateInitialMinMaxPoints(out minPoint, out maxPoint); workingPointList.RemoveAll(item => item == minPoint || item == maxPoint); List <Segment3D> allHullSegments = new List <Segment3D>(); Segment3D minMaxSegment = new Segment3D(minPoint, maxPoint); Plane minMaxSegmentPlane = GetSegmentPlane(minMaxSegment); bool furthestPointIsBehind = false; int indexOfPointFurthestFromPlane = minMaxSegmentPlane.GetIndexOfFurthestPointInFront(workingPointList); if (indexOfPointFurthestFromPlane < 0) { indexOfPointFurthestFromPlane = minMaxSegmentPlane.GetIndexOfFurthestPointBehind(workingPointList); furthestPointIsBehind = true; } if (indexOfPointFurthestFromPlane >= 0) { Vector3 furthestPointFromPlane = workingPointList[indexOfPointFurthestFromPlane]; workingPointList.RemoveAt(indexOfPointFurthestFromPlane); List <Segment3D> hullSegments = new List <Segment3D>(); if (furthestPointIsBehind) { hullSegments.Add(new Segment3D(minMaxSegment.EndPoint, furthestPointFromPlane)); hullSegments.Add(new Segment3D(furthestPointFromPlane, minMaxSegment.StartPoint)); hullSegments.Add(minMaxSegment); QuickHull(workingPointList, hullSegments); } else { hullSegments.Add(new Segment3D(furthestPointFromPlane, minMaxSegment.EndPoint)); hullSegments.Add(new Segment3D(minMaxSegment.StartPoint, furthestPointFromPlane)); hullSegments.Add(new Segment3D(minMaxSegment.EndPoint, minMaxSegment.StartPoint)); // Reversed min-max segment QuickHull(workingPointList, hullSegments); } allHullSegments.AddRange(hullSegments); } return(allHullSegments); }
private bool IsPointInsideHull(List <Segment3D> currentHullSegments, Vector3 point) { for (int segmentIndex = 0; segmentIndex < currentHullSegments.Count; ++segmentIndex) { Segment3D currentSegment = currentHullSegments[segmentIndex]; if (!IsPointBehindSegmentPlane(currentSegment, point)) { return(false); } } return(true); }
private void QuickHull(List <Vector3> workingPoints, List <Segment3D> hullSegments) { // First, remove all points which lie inside the hull for (int pointIndex = 0; pointIndex < workingPoints.Count;) { Vector3 point = workingPoints[pointIndex]; if (IsPointInsideHull(hullSegments, point)) { workingPoints.RemoveAt(pointIndex); } else { ++pointIndex; } } // Loop thorugh each existing hull segment and create new segments as necessary var newSegments = new List <Segment3D>(); for (int segmentIndex = 0; segmentIndex < hullSegments.Count;) { Segment3D currentSegment = hullSegments[segmentIndex]; Plane currentSegmentPlane = GetSegmentPlane(currentSegment); // Expand the hull by creating new segments if necessary int indexOfFurthestPointInFront = currentSegmentPlane.GetIndexOfFurthestPointInFront(workingPoints); if (indexOfFurthestPointInFront >= 0) { Vector3 furthestPointInFront = workingPoints[indexOfFurthestPointInFront]; hullSegments.RemoveAt(segmentIndex); Segment3D firstSegment = new Segment3D(currentSegment.StartPoint, furthestPointInFront); Segment3D secondSegment = new Segment3D(furthestPointInFront, currentSegment.EndPoint); newSegments.Add(firstSegment); newSegments.Add(secondSegment); workingPoints.RemoveAt(indexOfFurthestPointInFront); } else { ++segmentIndex; } } if (newSegments.Count != 0) { hullSegments.AddRange(newSegments); QuickHull(workingPoints, hullSegments); } }
private void CalculateIndexOfStrokeAlignmentSegmentPlane() { int indexOfPointClosestToStrokeSurfacePickPoint = _pivotPointsOfLastPlacedHierarchy.GetIndexOfPointClosestToPoint(_strokeSurface.MouseCursorPickPoint); if (indexOfPointClosestToStrokeSurfacePickPoint != ProjectedBoxFacePivotPoints.IndexOfPointInCenter) { Vector3 pivotPointClosestToStrokeSurfacePickPoint = _pivotPointsOfLastPlacedHierarchy.GetPointByIndex(indexOfPointClosestToStrokeSurfacePickPoint); // Now identify the segment whose center point is closest to the calculated pivot point List <Segment3D> segments = _pivotPointsOfLastPlacedHierarchy.GetAllBoundarySegments(); int indexOfBestSegment = -1; float minDistance = float.MaxValue; for (int segmentIndex = 0; segmentIndex < segments.Count; ++segmentIndex) { Segment3D segment = segments[segmentIndex]; Vector3 segmentMidPoint = segment.GetPoint(0.5f); float distanceFromPivotPoint = (segmentMidPoint - pivotPointClosestToStrokeSurfacePickPoint).magnitude; if (distanceFromPivotPoint < minDistance) { minDistance = distanceFromPivotPoint; indexOfBestSegment = segmentIndex; } } _indexOfStrokeAlignmentSegmentPlane = indexOfBestSegment; } else { Vector3 fromCenterToStrokeSurfacePickPoint = _strokeSurface.MouseCursorPickPoint - _pivotPointsOfLastPlacedHierarchy.CenterPoint; List <Plane> pivotPointBoundarySegmentPlanes = _pivotPointsOfLastPlacedHierarchy.GetAllBoundarySegmentPlanes(); _indexOfStrokeAlignmentSegmentPlane = PlaneExtensions.GetIndexOfPlaneWhoseNormalIsMostAlignedWithDir(pivotPointBoundarySegmentPlanes, fromCenterToStrokeSurfacePickPoint); } /* Left here for reference. This is the way in which it was initially done. * if(indexOfPointClosestToStrokeSurfacePickPoint != ProjectedBoxFacePivotPoints.IndexOfPointInCenter) * { * Vector3 pivotPointClosestToStrokeSurfacePickPoint = _pivotPointsOfLastPlacedHierarchy.GetPointByIndex(indexOfPointClosestToStrokeSurfacePickPoint); * Vector3 fromCenterPivotToCalculatedClosestPoint = pivotPointClosestToStrokeSurfacePickPoint - _pivotPointsOfLastPlacedHierarchy.CenterPoint; * * pivotPointBoundarySegmentPlanes = _pivotPointsOfLastPlacedHierarchy.GetAllBoundarySegmentPlanes(); * _indexOfStrokeAlignmentSegmentPlane = PlaneExtensions.GetIndexOfPlaneWhoseNormalIsMostAlignedWithDir(pivotPointBoundarySegmentPlanes, fromCenterPivotToCalculatedClosestPoint); * } * else * { * fromClosestPointToStrokeSurfacePickPoint = _strokeSurface.MouseCursorPickPoint - _pivotPointsOfLastPlacedHierarchy.CenterPoint; * pivotPointBoundarySegmentPlanes = _pivotPointsOfLastPlacedHierarchy.GetAllBoundarySegmentPlanes(); * _indexOfStrokeAlignmentSegmentPlane = PlaneExtensions.GetIndexOfPlaneWhoseNormalIsMostAlignedWithDir(pivotPointBoundarySegmentPlanes, fromCenterPivotToStrokeSurfacePickPoint); * } */ }
public bool IntersectsRay(Ray3D ray, out float t) { Plane circlePlane = Plane; Vector3 circleCenter = Center; float scaledRadius = ScaledRadius; // Note: 'IntersectsPlane' not enough. Also check for containment???? if (ray.IntersectsPlane(circlePlane, out t)) { return(true); } else if (ray.Direction.IsPerpendicularTo(circlePlane.normal)) { // Project the circle center onto the ray direction segment. If the distance between the circle center // and the projected center is <= the circle's radius, the ray might intersect the circle. Otherwise, // the ray can not possibly intersect the circle. Segment3D segment = new Segment3D(ray); Vector3 centerProjectedOnRayDir = circleCenter.CalculateProjectionPointOnSegment(segment.StartPoint, segment.EndPoint); Vector3 fromCenterToProjectedCenter = centerProjectedOnRayDir - circleCenter; float triAdjSideLength1 = fromCenterToProjectedCenter.magnitude; if (triAdjSideLength1 > scaledRadius) { return(false); } // At this point it is possible that the ray might intersect the circle. Calcluate how much // we have to move from the center projection to a point on the circle along the reverse of // the ray direction vector. We will store this amount in 'triAdjSideLength2'. float triAdjSideLength2 = Mathf.Sqrt(scaledRadius * scaledRadius - triAdjSideLength1 * triAdjSideLength1); // Now check if moving from the projected center along the reverse ray direction by an amount equal to // 'triAdjSideLength2', we end up on a point which resides on the ray direction segment. Vector3 normalizedRayDirection = ray.Direction; normalizedRayDirection.Normalize(); Vector3 targetPoint = centerProjectedOnRayDir - triAdjSideLength2 * normalizedRayDirection; if (targetPoint.IsOnSegment(segment.StartPoint, segment.EndPoint)) { // The point sits on the segment, which means that the ray intersects the circle. // Now we need to calculate the intersection offset. t = (targetPoint - ray.Origin).magnitude / ray.Direction.magnitude; return(true); } } return(false); }
public List <Plane> GetBoundarySegmentPlanesFacingOutward() { List <Segment3D> boundarySegments = GetBoundarySegments(); List <Plane> segmentPlanes = new List <Plane>(boundarySegments.Count); Plane quadPlane = Plane; for (int segmentIndex = 0; segmentIndex < boundarySegments.Count; ++segmentIndex) { Segment3D segment = boundarySegments[segmentIndex]; Vector3 planeNormal = Vector3.Cross(segment.Direction, quadPlane.normal); planeNormal.Normalize(); segmentPlanes.Add(new Plane(planeNormal, segment.StartPoint)); } return(segmentPlanes); }
private void CalculateArea() { List <Segment3D> boundarySegments = GetAllBoundarySegments(); for (int segmentIndex = 0; segmentIndex < boundarySegments.Count; ++segmentIndex) { Segment3D currentSegment = boundarySegments[segmentIndex]; for (int perpSegmentIndex = segmentIndex + 1; perpSegmentIndex < boundarySegments.Count; ++perpSegmentIndex) { Segment3D perpSegment = boundarySegments[perpSegmentIndex]; if (perpSegment.IsPerpendicualrTo(currentSegment)) { Vector3 cross = Vector3.Cross(perpSegment.Direction, currentSegment.Direction); _area = cross.magnitude; return; } } } }
private bool IsPointBehindSegmentPlane(Segment3D segment, Vector3 point) { Plane segmentPlane = GetSegmentPlane(segment); return(segmentPlane.IsPointBehind(point)); }
public static void RenderOrientedBoxCornerEdges(OrientedBox orientedBox, float cornerEdgeLengthPercentage, Color color) { cornerEdgeLengthPercentage = Mathf.Clamp(cornerEdgeLengthPercentage, 0.0f, 1.0f); List <Vector3> boxCornerPoints = orientedBox.GetCenterAndCornerPoints(); GizmosColor.Push(color); // Render the corner edges along the top edge of the box's front face Segment3D segment = new Segment3D(boxCornerPoints[(int)BoxPoint.FrontTopLeft], boxCornerPoints[(int)BoxPoint.FrontTopRight]); float edgeLength = segment.HalfLength * cornerEdgeLengthPercentage; if (edgeLength > 0.0f) { Gizmos.DrawLine(segment.StartPoint, segment.StartPoint + segment.NormalizedDirection * edgeLength); Gizmos.DrawLine(segment.EndPoint, segment.EndPoint - segment.NormalizedDirection * edgeLength); } // Render the corner edges along the bottom edge of the box's front face segment = new Segment3D(boxCornerPoints[(int)BoxPoint.FrontBottomLeft], boxCornerPoints[(int)BoxPoint.FrontBottomRight]); edgeLength = segment.HalfLength * cornerEdgeLengthPercentage; if (edgeLength > 0.0f) { Gizmos.DrawLine(segment.StartPoint, segment.StartPoint + segment.NormalizedDirection * edgeLength); Gizmos.DrawLine(segment.EndPoint, segment.EndPoint - segment.NormalizedDirection * edgeLength); } // Render the corner edges along the left edge of the box's front face segment = new Segment3D(boxCornerPoints[(int)BoxPoint.FrontTopLeft], boxCornerPoints[(int)BoxPoint.FrontBottomLeft]); edgeLength = segment.HalfLength * cornerEdgeLengthPercentage; if (edgeLength > 0.0f) { Gizmos.DrawLine(segment.StartPoint, segment.StartPoint + segment.NormalizedDirection * edgeLength); Gizmos.DrawLine(segment.EndPoint, segment.EndPoint - segment.NormalizedDirection * edgeLength); } // Render the corner edges along the right edge of the box's front face segment = new Segment3D(boxCornerPoints[(int)BoxPoint.FrontTopRight], boxCornerPoints[(int)BoxPoint.FrontBottomRight]); edgeLength = segment.HalfLength * cornerEdgeLengthPercentage; if (edgeLength > 0.0f) { Gizmos.DrawLine(segment.StartPoint, segment.StartPoint + segment.NormalizedDirection * edgeLength); Gizmos.DrawLine(segment.EndPoint, segment.EndPoint - segment.NormalizedDirection * edgeLength); } // Render the corner edges along the top edge of the box's back face segment = new Segment3D(boxCornerPoints[(int)BoxPoint.BackTopLeft], boxCornerPoints[(int)BoxPoint.BackTopRight]); edgeLength = segment.HalfLength * cornerEdgeLengthPercentage; if (edgeLength > 0.0f) { Gizmos.DrawLine(segment.StartPoint, segment.StartPoint + segment.NormalizedDirection * edgeLength); Gizmos.DrawLine(segment.EndPoint, segment.EndPoint - segment.NormalizedDirection * edgeLength); } // Render the corner edges along the bottom edge of the box's back face segment = new Segment3D(boxCornerPoints[(int)BoxPoint.BackBottomLeft], boxCornerPoints[(int)BoxPoint.BackBottomRight]); edgeLength = segment.HalfLength * cornerEdgeLengthPercentage; if (edgeLength > 0.0f) { Gizmos.DrawLine(segment.StartPoint, segment.StartPoint + segment.NormalizedDirection * edgeLength); Gizmos.DrawLine(segment.EndPoint, segment.EndPoint - segment.NormalizedDirection * edgeLength); } // Render the corner edges along the left edge of the box's back face segment = new Segment3D(boxCornerPoints[(int)BoxPoint.BackTopLeft], boxCornerPoints[(int)BoxPoint.BackBottomLeft]); edgeLength = segment.HalfLength * cornerEdgeLengthPercentage; if (edgeLength > 0.0f) { Gizmos.DrawLine(segment.StartPoint, segment.StartPoint + segment.NormalizedDirection * edgeLength); Gizmos.DrawLine(segment.EndPoint, segment.EndPoint - segment.NormalizedDirection * edgeLength); } // Render the corner edges along the right edge of the box's back face segment = new Segment3D(boxCornerPoints[(int)BoxPoint.BackTopRight], boxCornerPoints[(int)BoxPoint.BackBottomRight]); edgeLength = segment.HalfLength * cornerEdgeLengthPercentage; if (edgeLength > 0.0f) { Gizmos.DrawLine(segment.StartPoint, segment.StartPoint + segment.NormalizedDirection * edgeLength); Gizmos.DrawLine(segment.EndPoint, segment.EndPoint - segment.NormalizedDirection * edgeLength); } // Render the corner edges along the left edge of the box's top face segment = new Segment3D(boxCornerPoints[(int)BoxPoint.FrontTopLeft], boxCornerPoints[(int)BoxPoint.BackTopRight]); edgeLength = segment.HalfLength * cornerEdgeLengthPercentage; if (edgeLength > 0.0f) { Gizmos.DrawLine(segment.StartPoint, segment.StartPoint + segment.NormalizedDirection * edgeLength); Gizmos.DrawLine(segment.EndPoint, segment.EndPoint - segment.NormalizedDirection * edgeLength); } // Render the corner edges along the right edge of the box's top face segment = new Segment3D(boxCornerPoints[(int)BoxPoint.FrontTopRight], boxCornerPoints[(int)BoxPoint.BackTopLeft]); edgeLength = segment.HalfLength * cornerEdgeLengthPercentage; if (edgeLength > 0.0f) { Gizmos.DrawLine(segment.StartPoint, segment.StartPoint + segment.NormalizedDirection * edgeLength); Gizmos.DrawLine(segment.EndPoint, segment.EndPoint - segment.NormalizedDirection * edgeLength); } // Render the corner edges along the left edge of the box's bottom face segment = new Segment3D(boxCornerPoints[(int)BoxPoint.FrontBottomLeft], boxCornerPoints[(int)BoxPoint.BackBottomRight]); edgeLength = segment.HalfLength * cornerEdgeLengthPercentage; if (edgeLength > 0.0f) { Gizmos.DrawLine(segment.StartPoint, segment.StartPoint + segment.NormalizedDirection * edgeLength); Gizmos.DrawLine(segment.EndPoint, segment.EndPoint - segment.NormalizedDirection * edgeLength); } // Render the corner edges along the right edge of the box's bottom face segment = new Segment3D(boxCornerPoints[(int)BoxPoint.FrontBottomRight], boxCornerPoints[(int)BoxPoint.BackBottomLeft]); edgeLength = segment.HalfLength * cornerEdgeLengthPercentage; if (edgeLength > 0.0f) { Gizmos.DrawLine(segment.StartPoint, segment.StartPoint + segment.NormalizedDirection * edgeLength); Gizmos.DrawLine(segment.EndPoint, segment.EndPoint - segment.NormalizedDirection * edgeLength); } GizmosColor.Pop(); }
public bool IsPerpendicualrTo(Segment3D segment) { return(Direction.IsPerpendicularTo(segment.Direction)); }
public List <ObjectPlacementData> Calculate(Quaternion rotationToApplyForStrokeAlignment) { if (!ValidateCalculationRequirements()) { return(new List <ObjectPlacementData>()); } _objectNodeNetwork.Clear(); _rotationToApplyForStrokeAlignment = rotationToApplyForStrokeAlignment; CreateSurfaceColliderProjector(); _elementToNewPrefabRotation.Clear(); _allowObjectIntersection = ObjectPlacementSettings.Get().ObjectIntersectionSettings.AllowIntersectionForDecorPaintBrushModeDrag; int currentObjectIndex = 0; var objectPlacementDataInstances = new List <ObjectPlacementData>(_workingBrush.MaxNumberOfObjects); while (currentObjectIndex < _workingBrush.MaxNumberOfObjects) { DecorPaintObjectPlacementBrushElement brushElement = _brushElementSpawnChanceTable.PickEntity(UnityEngine.Random.Range(0.0f, 1.0f)); int brushElementIndex = _allValidBrushElements.FindIndex(item => item == brushElement); ++currentObjectIndex; // No object nodes were created yet? if (_objectNodeNetwork.NumberOfNodes == 0) { // Create the first node at a random position inside the brush circle Vector3 randomPositionInsideCircle = _workingBrushCircle.GetRandomPointInside(); ObjectSurfaceData objectSurfaceData = CalculateObjectSurfaceData(randomPositionInsideCircle); MatrixObjectBoxPair matrixObjectBoxPair = CalculateMatrixObjectBoxPair(brushElementIndex, objectSurfaceData); // We need to know if the normal of the surface on which the object resides lies within the desired slope range bool passesSlopeTest = DoesObjectSurfacePassSlopeTest(objectSurfaceData, brushElement); // Note: Even if the slope test is not passed, we will still create an object node. The reason for this is that // we want to have some kind of continuity in the algorithm. Imagine that the brush circle is large and is // divided by a large terrain mountain which sits in the middle. If the object generation starts on one side // of the mountain, the algorithm might never get a chance to go over the other side if the slope condition // is not satisifed. We want to spread objects as much as possible so even though this object will not be // placed in the scene, we will still add it to the node network. _objectNodeNetwork.AddNodeToEnd(matrixObjectBoxPair.ObjectBox, objectSurfaceData); if (passesSlopeTest && DoesBoxPassObjectIntersectionTest(matrixObjectBoxPair.ObjectBox, brushElement.Prefab.UnityPrefab, matrixObjectBoxPair.ObjectMatrix)) { objectPlacementDataInstances.Add(new ObjectPlacementData(matrixObjectBoxPair.ObjectMatrix, brushElement.Prefab, brushElement.MustEmbedInSurface)); } } else { // Are there any node segments available? if (_objectNodeNetwork.NumberOfSegments != 0) { // The first step is to generate a random node index and store references to that node and its immediate neighbour _objectNodeNetwork.RemoveAllNodesWhichGenerateConcavities(_workingBrushCircle.Plane); int randomNodeIndex = _objectNodeNetwork.GetRandomNodeIndex(); ObjectNode firstNode = _objectNodeNetwork.GetNodeByIndex(randomNodeIndex); ObjectNode secondNode = _objectNodeNetwork.GetNodeByIndex(randomNodeIndex + 1); // Calculate the plane of the segment which unites the 2 nodes. We will also store the // actual segment and the middle point on the segment. We will use this middle point to // generate the initial object position. Segment3D nodeSegment = ObjectNodeNetwork.CalculateSegmentBetweenNodes(firstNode, secondNode); Vector3 segmentMidPoint = nodeSegment.GetPoint(0.5f); Plane segmentPlane = ObjectNodeNetwork.CalculateSegmentPlaneNormal(firstNode, secondNode); OrientedBox firstNodeBox = firstNode.ObjectBox; OrientedBox secondNodeBox = secondNode.ObjectBox; // Calculate the furthest point in front of the plane using the corner points of the // 2 nodes. The idea is to move the new object as much as possible from the bulk of // objects that have already been generated. Vector3 furthestPointFromPlane; List <Vector3> nodeCornerPoints = firstNodeBox.GetCornerPoints(); nodeCornerPoints.AddRange(secondNodeBox.GetCornerPoints()); if (!segmentPlane.GetFurthestPointInFront(nodeCornerPoints, out furthestPointFromPlane)) { continue; } // Use the calculated furthest point from plane and the the existing plane normal to calculate the // pivot plane. The new object will reside at some distance away from this plane. Plane pivotPlane = new Plane(segmentPlane.normal, furthestPointFromPlane); // Calculate the new object transform data. We will use the segment's mid point to generate the // initial object position. ObjectSurfaceData objectSurfaceData = CalculateObjectSurfaceData(segmentMidPoint); MatrixObjectBoxPair matrixObjectBoxPair = CalculateMatrixObjectBoxPair(brushElementIndex, objectSurfaceData); OrientedBox objectBox = matrixObjectBoxPair.ObjectBox; // Identify the objects's furthest point behind the plane Vector3 objectBoxPivotPoint; List <Vector3> objectBoxCornerPoints = objectBox.GetCornerPoints(); if (!pivotPlane.GetFurthestPointBehind(objectBoxCornerPoints, out objectBoxPivotPoint)) { continue; } // Use the furthest point to move the object in front of the plane and take the distance between objects into account Vector3 fromPivotPointToCenter = objectBox.Center - objectBoxPivotPoint; Vector3 projectedPivotPoint = pivotPlane.ProjectPoint(objectBoxPivotPoint); objectBox.Center = projectedPivotPoint + fromPivotPointToCenter + pivotPlane.normal * _workingBrush.DistanceBetweenObjects; // Generate the object surface data objectSurfaceData = CalculateObjectSurfaceData(objectBox.Center); bool passesSlopeTest = DoesObjectSurfacePassSlopeTest(objectSurfaceData, brushElement); // Now we need to adjust the orientation and center of the box. If the calculated center // lies outside the brush circle, we will ignore this node. AdjustObjectBoxRotationOnSurface(objectBox, objectSurfaceData, brushElement); AdjustObjectBoxCenterToSitOnSurface(objectBox, objectSurfaceData, brushElement); if (!_workingBrushCircle.ContainsPoint(_workingBrushCircle.Plane.ProjectPoint(objectBox.Center))) { continue; } // Recalculate the object matrix using the new box data TransformMatrix objectMatrix = matrixObjectBoxPair.ObjectMatrix; objectMatrix.Rotation = objectBox.Rotation; objectMatrix.Translation = ObjectPositionCalculator.CalculateObjectHierarchyPosition(brushElement.Prefab, objectBox.Center, objectMatrix.Scale, objectBox.Rotation); // We have been modifying the matrix and box data independently so we will ensure that the box uses the latest data OrientedBox finalBox = new OrientedBox(objectBox); finalBox.SetTransformMatrix(objectMatrix); // If the slope test passed, we will calculate an object placement data instance. Otherwise, we will just insert a new node. if (passesSlopeTest && DoesBoxPassObjectIntersectionTest(finalBox, brushElement.Prefab.UnityPrefab, objectMatrix)) { objectPlacementDataInstances.Add(new ObjectPlacementData(objectMatrix, brushElement.Prefab, brushElement.MustEmbedInSurface)); } _objectNodeNetwork.InsertAfterNode(objectBox, objectSurfaceData, randomNodeIndex); } else { // When there are no segments available it means we have only one node. We will use this node to generate // a new one at some distance away from it. First we will store some data that we will need during the entire // procedure. ObjectNode pivotNode = _objectNodeNetwork.GetFirstNode(); Vector3 pivotNodeSurfaceTangent = pivotNode.ObjectSurfaceData.GetSurfaceTangentVector(); OrientedBox pivotNodeObjectBox = pivotNode.ObjectBox; // We will place the new node at some distance away from the first node's face which points // along the calculated tangent vector. We will call this the pivot face. BoxFace pivotBoxFace = pivotNodeObjectBox.GetBoxFaceMostAlignedWithNormal(pivotNodeSurfaceTangent); Plane pivotFacePlane = pivotNodeObjectBox.GetBoxFacePlane(pivotBoxFace); // Generate the data for the new node in the same position as the first node. // Note: Although the same position is used, the rotation and scale will differ and they will // be established by 'CalculateMatrixObjectBoxPair'. MatrixObjectBoxPair matrixObjectBoxPair = CalculateMatrixObjectBoxPair(brushElementIndex, pivotNode.ObjectSurfaceData); OrientedBox objectBox = matrixObjectBoxPair.ObjectBox; // At this point we have to start moving the generated object box to its new positino along the // tangent vector. We will do this by calculating the furthest box point which lies behind the // pivot plane and then move the box so that this point resides on that plane. We will call this // the pivot point. // Note: We will perform a safety check to see if this point could not be calculated and use the // closest point in front if necessary. However, this check should not be necessary. Because // we are placing te object box in the center of the previous box, we can be usre that there // will always be a point which lies behind the pivot plane. Vector3 objectBoxPivotPoint; List <Vector3> objectBoxCornerPoints = objectBox.GetCornerPoints(); if (!pivotFacePlane.GetFurthestPointBehind(objectBoxCornerPoints, out objectBoxPivotPoint) && !pivotFacePlane.GetClosestPointInFront(objectBoxCornerPoints, out objectBoxPivotPoint)) { continue; } // Project the pivot point onto the pivot plane. We will also store a vector which goes from the // original pivot point to the box center. This will allow us to maintain the relationship between // the projected pivot point and the box center so that the center can be adjusted accordingly. Vector3 fromPivotPointToCenter = objectBox.Center - objectBoxPivotPoint; Vector3 projectedPivotPoint = pivotFacePlane.ProjectPoint(objectBoxPivotPoint); // Adjust the center using the projected pivot point and also take the distance between objects into account objectBox.Center = projectedPivotPoint + fromPivotPointToCenter + pivotNodeSurfaceTangent * _workingBrush.DistanceBetweenObjects; // Generate the object surface data at the current box position. // Note: This is the step which can actually cause objects to intersect a little bit. The surface data is // calculated by projecting along the brush circle plane normal. If we are placing objects on a terrain // and the center of the circle lies somewhere at the base of the terrain where the normal points straight // up, but the center of the box resides somewhere on a clif, the new center might move the box closer // or even further away from the pivot node. This however, should not be a problem especially if the distance // between objects is not 0. ObjectSurfaceData objectSurfaceData = CalculateObjectSurfaceData(objectBox.Center); bool passesSlopeTest = DoesObjectSurfacePassSlopeTest(objectSurfaceData, brushElement); // Now we need to adjust the orientation and center of the box. If the calculated center // lies outside the brush circle, we will ignore this node. AdjustObjectBoxRotationOnSurface(objectBox, objectSurfaceData, brushElement); AdjustObjectBoxCenterToSitOnSurface(objectBox, objectSurfaceData, brushElement); if (!_workingBrushCircle.ContainsPoint(_workingBrushCircle.Plane.ProjectPoint(objectBox.Center))) { continue; } // Recalculate the object matrix using the new box data TransformMatrix objectMatrix = matrixObjectBoxPair.ObjectMatrix; objectMatrix.Rotation = objectBox.Rotation; objectMatrix.Translation = ObjectPositionCalculator.CalculateObjectHierarchyPosition(brushElement.Prefab, objectBox.Center, objectMatrix.Scale, objectBox.Rotation); // We have been modifying the matrix and box data independently so we will ensure that the box uses the latest data OrientedBox finalBox = new OrientedBox(objectBox); finalBox.SetTransformMatrix(objectMatrix); // If the slope test passed, we will calculate an object placement data instance. Otherwise, we will just insert a new node. if (passesSlopeTest && DoesBoxPassObjectIntersectionTest(finalBox, brushElement.Prefab.UnityPrefab, objectMatrix)) { objectPlacementDataInstances.Add(new ObjectPlacementData(objectMatrix, brushElement.Prefab, brushElement.MustEmbedInSurface)); } _objectNodeNetwork.InsertAfterNode(objectBox, objectSurfaceData, 0); } } } // Adjust the prefab rotations for the next time the function is called if (_elementToNewPrefabRotation.Count != 0) { foreach (var prefabRotationPair in _elementToNewPrefabRotation) { DecorPaintObjectPlacementBrushElement brushElement = prefabRotationPair.Key; if (_elementToCurrentPrefabRotation.ContainsKey(brushElement)) { _elementToCurrentPrefabRotation[brushElement] = prefabRotationPair.Value; } } } return(objectPlacementDataInstances); }
public bool IntersectsWith(Segment3D segment) { float t1, t2; return(IntersectsWith(segment, out t1, out t2)); }