void CreateBezier(Vector3 centre, bool defaultIs2D = false) { if (_bezierPath != null) { _bezierPath.OnModified -= BezierPathEdited; } PathSpace space = defaultIs2D ? PathSpace.xy : PathSpace.xyz; _bezierPath = new BezierPath(centre, false, space); _bezierPath.OnModified += BezierPathEdited; vertexPathUpToDate = false; bezierOrVertexPathModified?.Invoke(); bezierCreated?.Invoke(); }
void DrawVertexPathSceneEditor() { Color bezierCol = GlobalDisplaySettings.BezierPath; bezierCol.a *= .5f; if (Data.showBezierPathInVertexMode) { for (int i = 0; i < BezierPath.NumSegments; i++) { Vector3[] points = BezierPath.GetPointsInSegment(i); for (int j = 0; j < points.Length; j++) { points[j] = MathUtility.TransformPoint(points[j], creator.transform, BezierPath.Space); } Handles.DrawBezier(points[0], points[3], points[1], points[2], bezierCol, null, 2); } } Handles.color = GlobalDisplaySettings.vertexPath; for (int i = 0; i < creator.VertexPath.NumPoints; i++) { int nextIndex = (i + 1) % creator.VertexPath.NumPoints; if (nextIndex != 0 || BezierPath.IsClosed) { Handles.DrawLine(creator.VertexPath.GetPoint(i), creator.VertexPath.GetPoint(nextIndex)); } } if (Data.showNormalsInVertexMode) { Handles.color = GlobalDisplaySettings.normals; Vector3[] normalLines = new Vector3[creator.VertexPath.NumPoints * 2]; for (int i = 0; i < creator.VertexPath.NumPoints; i++) { normalLines[i * 2] = creator.VertexPath.GetPoint(i); normalLines[i * 2 + 1] = creator.VertexPath.GetPoint(i) + creator.VertexPath.localNormals[i] * GlobalDisplaySettings.normalsLength; } Handles.DrawLines(normalLines); } }
void DrawHandle(int i) { Vector3 handlePosition = MathUtility.TransformPoint(BezierPath[i], creator.transform, BezierPath.Space); float anchorHandleSize = GetHandleDiameter(GlobalDisplaySettings.anchorSize * Data.bezierHandleScale, BezierPath[i]); float controlHandleSize = GetHandleDiameter(GlobalDisplaySettings.controlSize * Data.bezierHandleScale, BezierPath[i]); bool isAnchorPoint = i % 3 == 0; bool isInteractive = isAnchorPoint || BezierPath.ControlPointMode != BezierPath.ControlMode.Automatic; float handleSize = (isAnchorPoint) ? anchorHandleSize : controlHandleSize; bool doTransformHandle = i == handleIndexToDisplayAsTransform; PathHandle.HandleColours handleColours = (isAnchorPoint) ? splineAnchorColours : splineControlColours; if (i == handleIndexToDisplayAsTransform) { handleColours.defaultColour = (isAnchorPoint) ? GlobalDisplaySettings.anchorSelected : GlobalDisplaySettings.controlSelected; } var cap = capFunctions[(isAnchorPoint) ? GlobalDisplaySettings.anchorShape : GlobalDisplaySettings.controlShape]; handlePosition = PathHandle.DrawHandle(handlePosition, BezierPath.Space, isInteractive, handleSize, cap, handleColours, out PathHandle.HandleInputType handleInputType, i); if (doTransformHandle) { // Show normals rotate tool if (Data.showNormals && Tools.current == Tool.Rotate && isAnchorPoint && BezierPath.Space == PathSpace.xyz) { Handles.color = handlesStartCol; int attachedControlIndex = (i == BezierPath.NumPoints - 1) ? i - 1 : i + 1; Vector3 dir = (BezierPath[attachedControlIndex] - handlePosition).normalized; float handleRotOffset = (360 + BezierPath.GlobalNormalsAngle) % 360; anchorAngleHandle.radius = handleSize * 3; anchorAngleHandle.angle = handleRotOffset + BezierPath.GetAnchorNormalAngle(i / 3); Vector3 handleDirection = Vector3.Cross(dir, Vector3.up); Matrix4x4 handleMatrix = Matrix4x4.TRS( handlePosition, Quaternion.LookRotation(handleDirection, dir), Vector3.one ); using (new Handles.DrawingScope(handleMatrix)) { // draw the handle EditorGUI.BeginChangeCheck(); anchorAngleHandle.DrawHandle(); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(creator, "Set angle"); BezierPath.SetAnchorNormalAngle(i / 3, anchorAngleHandle.angle - handleRotOffset); } } } else { handlePosition = Handles.DoPositionHandle(handlePosition, Quaternion.identity); } } switch (handleInputType) { case PathHandle.HandleInputType.LMBDrag: draggingHandleIndex = i; handleIndexToDisplayAsTransform = -1; Repaint(); break; case PathHandle.HandleInputType.LMBRelease: draggingHandleIndex = -1; handleIndexToDisplayAsTransform = -1; Repaint(); break; case PathHandle.HandleInputType.LMBClick: draggingHandleIndex = -1; if (Event.current.shift) { handleIndexToDisplayAsTransform = -1; // disable move tool if new point added } else { if (handleIndexToDisplayAsTransform == i) { handleIndexToDisplayAsTransform = -1; // disable move tool if clicking on point under move tool } else { handleIndexToDisplayAsTransform = i; } } Repaint(); break; case PathHandle.HandleInputType.LMBPress: if (handleIndexToDisplayAsTransform != i) { handleIndexToDisplayAsTransform = -1; Repaint(); } break; } Vector3 localHandlePosition = MathUtility.InverseTransformPoint(handlePosition, creator.transform, BezierPath.Space); if (BezierPath[i] != localHandlePosition) { Undo.RecordObject(creator, "Move point"); BezierPath.MovePoint(i, localHandlePosition); } }
void DrawBezierPathSceneEditor() { bool displayControlPoints = Data.displayControlPoints && (BezierPath.ControlPointMode != BezierPath.ControlMode.Automatic || !GlobalDisplaySettings.hideAutoControls); Bounds bounds = BezierPath.CalculateBoundsWithTransform(creator.transform); if (Event.current.type == EventType.Repaint) { for (int i = 0; i < BezierPath.NumSegments; i++) { Vector3[] points = BezierPath.GetPointsInSegment(i); for (int j = 0; j < points.Length; j++) { points[j] = MathUtility.TransformPoint(points[j], creator.transform, BezierPath.Space); } if (Data.showPerSegmentBounds) { Bounds segmentBounds = PathUtility.CalculateSegmentBounds(points[0], points[1], points[2], points[3]); Handles.color = GlobalDisplaySettings.segmentBounds; Handles.DrawWireCube(segmentBounds.center, segmentBounds.size); } // Draw lines between control points if (displayControlPoints) { Handles.color = (BezierPath.ControlPointMode == BezierPath.ControlMode.Automatic) ? GlobalDisplaySettings.handleDisabled : GlobalDisplaySettings.controlLine; Handles.DrawLine(points[1], points[0]); Handles.DrawLine(points[2], points[3]); } // Draw VertexPath bool highlightSegment = (i == selectedSegmentIndex && Event.current.shift && draggingHandleIndex == -1 && mouseOverHandleIndex == -1); Color segmentCol = (highlightSegment) ? GlobalDisplaySettings.highlightedPath : GlobalDisplaySettings.BezierPath; Handles.DrawBezier(points[0], points[3], points[1], points[2], segmentCol, null, 2); } if (Data.showPathBounds) { Handles.color = GlobalDisplaySettings.bounds; Handles.DrawWireCube(bounds.center, bounds.size); } // Draw normals if (Data.showNormals) { if (!hasUpdatedNormalsVertexPath) { normalsVertexPath = new VertexPath(BezierPath, creator.transform, normalsSpacing); hasUpdatedNormalsVertexPath = true; } if (editingNormalsOld != Data.showNormals) { editingNormalsOld = Data.showNormals; Repaint(); } Vector3[] normalLines = new Vector3[normalsVertexPath.NumPoints * 2]; Handles.color = GlobalDisplaySettings.normals; for (int i = 0; i < normalsVertexPath.NumPoints; i++) { normalLines[i * 2] = normalsVertexPath.GetPoint(i); normalLines[i * 2 + 1] = normalsVertexPath.GetPoint(i) + normalsVertexPath.GetNormal(i) * GlobalDisplaySettings.normalsLength; } Handles.DrawLines(normalLines); } } if (Data.displayAnchorPoints) { for (int i = 0; i < BezierPath.NumPoints; i += 3) { DrawHandle(i); } } if (displayControlPoints) { for (int i = 1; i < BezierPath.NumPoints - 1; i += 3) { DrawHandle(i); DrawHandle(i + 1); } } }
void ProcessBezierPathInput(Event e) { // Find which handle mouse is over. Start by looking at previous handle index first, as most likely to still be closest to mouse int previousMouseOverHandleIndex = (mouseOverHandleIndex == -1) ? 0 : mouseOverHandleIndex; mouseOverHandleIndex = -1; for (int i = 0; i < BezierPath.NumPoints; i += 3) { int handleIndex = (previousMouseOverHandleIndex + i) % BezierPath.NumPoints; float handleRadius = GetHandleDiameter(GlobalDisplaySettings.anchorSize * Data.bezierHandleScale, BezierPath[handleIndex]) / 2f; Vector3 pos = MathUtility.TransformPoint(BezierPath[handleIndex], creator.transform, BezierPath.Space); float dst = HandleUtility.DistanceToCircle(pos, handleRadius); if (dst == 0) { mouseOverHandleIndex = handleIndex; break; } } // Shift-left click (when mouse not over a handle) to split or add segment if (mouseOverHandleIndex == -1) { if (e.type == EventType.MouseDown && e.button == 0 && e.shift) { UpdatePathMouseInfo(); // Insert point along selected segment if (selectedSegmentIndex != -1 && selectedSegmentIndex < BezierPath.NumSegments) { Vector3 newPathPoint = pathMouseInfo.closestWorldPointToMouse; newPathPoint = MathUtility.InverseTransformPoint(newPathPoint, creator.transform, BezierPath.Space); Undo.RecordObject(creator, "Split segment"); BezierPath.SplitSegment(newPathPoint, selectedSegmentIndex, pathMouseInfo.timeOnBezierSegment); } // If VertexPath is not a closed loop, add new point on to the end of the VertexPath else if (!BezierPath.IsClosed) { // If control/command are held down, the point gets pre-pended, so we want to check distance // to the endpoint we are adding to var pointIdx = e.control || e.command ? 0 : BezierPath.NumPoints - 1; // insert new point at same dst from scene camera as the point that comes before it (for a 3d VertexPath) var endPointLocal = BezierPath[pointIdx]; var endPointGlobal = MathUtility.TransformPoint(endPointLocal, creator.transform, BezierPath.Space); var distanceCameraToEndpoint = (Camera.current.transform.position - endPointGlobal).magnitude; var newPointGlobal = MouseUtility.GetMouseWorldPosition(BezierPath.Space, distanceCameraToEndpoint); var newPointLocal = MathUtility.InverseTransformPoint(newPointGlobal, creator.transform, BezierPath.Space); Undo.RecordObject(creator, "Add segment"); if (e.control || e.command) { BezierPath.AddSegmentToStart(newPointLocal); } else { BezierPath.AddSegmentToEnd(newPointLocal); } } } } // Control click or backspace/delete to remove point if (e.keyCode == KeyCode.Backspace || e.keyCode == KeyCode.Delete || ((e.control || e.command) && e.type == EventType.MouseDown && e.button == 0)) { if (mouseOverHandleIndex != -1) { Undo.RecordObject(creator, "Delete segment"); BezierPath.DeleteSegment(mouseOverHandleIndex); if (mouseOverHandleIndex == handleIndexToDisplayAsTransform) { handleIndexToDisplayAsTransform = -1; } mouseOverHandleIndex = -1; Repaint(); } } // Holding shift and moving mouse (but mouse not over a handle/dragging a handle) if (draggingHandleIndex == -1 && mouseOverHandleIndex == -1) { bool shiftDown = e.shift && !shiftLastFrame; if (shiftDown || ((e.type == EventType.MouseMove || e.type == EventType.MouseDrag) && e.shift)) { UpdatePathMouseInfo(); if (pathMouseInfo.mouseDstToLine < segmentSelectDistanceThreshold) { if (pathMouseInfo.closestSegmentIndex != selectedSegmentIndex) { selectedSegmentIndex = pathMouseInfo.closestSegmentIndex; HandleUtility.Repaint(); } } else { selectedSegmentIndex = -1; HandleUtility.Repaint(); } } } shiftLastFrame = e.shift; }
void DrawBezierPathInspector() { using (var check = new EditorGUI.ChangeCheckScope()) { // Path options: Data.showPathOptions = EditorGUILayout.Foldout(Data.showPathOptions, new GUIContent("Bézier Path Options"), true, boldFoldoutStyle); if (Data.showPathOptions) { BezierPath.Space = (PathSpace)EditorGUILayout.Popup("Space", (int)BezierPath.Space, spaceNames); BezierPath.ControlPointMode = (BezierPath.ControlMode)EditorGUILayout.EnumPopup(new GUIContent("Control Mode"), BezierPath.ControlPointMode); if (BezierPath.ControlPointMode == BezierPath.ControlMode.Automatic) { BezierPath.AutoControlLength = EditorGUILayout.Slider(new GUIContent("Control Spacing"), BezierPath.AutoControlLength, 0, 1); } BezierPath.IsClosed = EditorGUILayout.Toggle("Closed Path", BezierPath.IsClosed); Data.showTransformTool = EditorGUILayout.Toggle(new GUIContent("Enable Transforms"), Data.showTransformTool); Tools.hidden = !Data.showTransformTool; // Check if out of bounds (can occur after undo operations) if (handleIndexToDisplayAsTransform >= BezierPath.NumPoints) { handleIndexToDisplayAsTransform = -1; } // If a point has been selected if (handleIndexToDisplayAsTransform != -1) { EditorGUILayout.LabelField("Selected Point:"); using (new EditorGUI.IndentLevelScope()) { var currentPosition = creator.BezierPath[handleIndexToDisplayAsTransform]; var newPosition = EditorGUILayout.Vector3Field("Position", currentPosition); if (newPosition != currentPosition) { Undo.RecordObject(creator, "Move point"); creator.BezierPath.MovePoint(handleIndexToDisplayAsTransform, newPosition); } // Don't draw the angle field if we aren't selecting an anchor point/not in 3d space if (handleIndexToDisplayAsTransform % 3 == 0 && creator.BezierPath.Space == PathSpace.xyz) { var anchorIndex = handleIndexToDisplayAsTransform / 3; var currentAngle = creator.BezierPath.GetAnchorNormalAngle(anchorIndex); var newAngle = EditorGUILayout.FloatField("Angle", currentAngle); if (newAngle != currentAngle) { Undo.RecordObject(creator, "Set Angle"); creator.BezierPath.SetAnchorNormalAngle(anchorIndex, newAngle); } } } } if (Data.showTransformTool & (handleIndexToDisplayAsTransform == -1)) { if (GUILayout.Button("Centre Transform")) { Vector3 worldCentre = BezierPath.CalculateBoundsWithTransform(creator.transform).center; Vector3 transformPos = creator.transform.position; if (BezierPath.Space == PathSpace.xy) { transformPos = new Vector3(transformPos.x, transformPos.y, 0); } else if (BezierPath.Space == PathSpace.xz) { transformPos = new Vector3(transformPos.x, 0, transformPos.z); } Vector3 worldCentreToTransform = transformPos - worldCentre; if (worldCentre != creator.transform.position) { //Undo.RecordObject (creator, "Centralize Transform"); if (worldCentreToTransform != Vector3.zero) { Vector3 localCentreToTransform = MathUtility.InverseTransformVector(worldCentreToTransform, creator.transform, BezierPath.Space); for (int i = 0; i < BezierPath.NumPoints; i++) { BezierPath.SetPoint(i, BezierPath.GetPoint(i) + localCentreToTransform, true); } } creator.transform.position = worldCentre; BezierPath.NotifyPathModified(); } } } if (GUILayout.Button("Reset Path")) { Undo.RecordObject(creator, "Reset Path"); bool in2DEditorMode = EditorSettings.defaultBehaviorMode == EditorBehaviorMode.Mode2D; Data.ResetBezierPath(creator.transform.position, in2DEditorMode); EditorApplication.QueuePlayerLoopUpdate(); } GUILayout.Space(inspectorSectionSpacing); } Data.showNormals = EditorGUILayout.Foldout(Data.showNormals, new GUIContent("Normals Options"), true, boldFoldoutStyle); if (Data.showNormals) { BezierPath.FlipNormals = EditorGUILayout.Toggle(new GUIContent("Flip Normals"), BezierPath.FlipNormals); if (BezierPath.Space == PathSpace.xyz) { BezierPath.GlobalNormalsAngle = EditorGUILayout.Slider(new GUIContent("Global Angle"), BezierPath.GlobalNormalsAngle, 0, 360); if (GUILayout.Button("Reset Normals")) { Undo.RecordObject(creator, "Reset Normals"); BezierPath.FlipNormals = false; BezierPath.ResetNormalAngles(); } } GUILayout.Space(inspectorSectionSpacing); } // Editor display options Data.showDisplayOptions = EditorGUILayout.Foldout(Data.showDisplayOptions, new GUIContent("Display Options"), true, boldFoldoutStyle); if (Data.showDisplayOptions) { Data.showPathBounds = GUILayout.Toggle(Data.showPathBounds, new GUIContent("Show Path Bounds")); Data.showPerSegmentBounds = GUILayout.Toggle(Data.showPerSegmentBounds, new GUIContent("Show Segment Bounds")); Data.displayAnchorPoints = GUILayout.Toggle(Data.displayAnchorPoints, new GUIContent("Show Anchor Points")); if (!(BezierPath.ControlPointMode == BezierPath.ControlMode.Automatic && GlobalDisplaySettings.hideAutoControls)) { Data.displayControlPoints = GUILayout.Toggle(Data.displayControlPoints, new GUIContent("Show Control Points")); } Data.keepConstantHandleSize = GUILayout.Toggle(Data.keepConstantHandleSize, new GUIContent("Constant Point Size", constantSizeTooltip)); Data.bezierHandleScale = Mathf.Max(0, EditorGUILayout.FloatField(new GUIContent("Handle Scale"), Data.bezierHandleScale)); DrawGlobalDisplaySettingsInspector(); } if (check.changed) { SceneView.RepaintAll(); EditorApplication.QueuePlayerLoopUpdate(); } } }
/// Internal contructor VertexPath(BezierPath path, PathUtility.PathSplitData pathSplitData, Transform transform) { this.transform = transform; space = path.Space; isClosedLoop = path.IsClosed; int numVerts = pathSplitData.vertices.Count; length = pathSplitData.cumulativeLength[numVerts - 1]; localPoints = new Vector3[numVerts]; localNormals = new Vector3[numVerts]; localTangents = new Vector3[numVerts]; cumulativeLengthAtEachVertex = new float[numVerts]; times = new float[numVerts]; bounds = new Bounds((pathSplitData.minMax.Min + pathSplitData.minMax.Max) * .5f, pathSplitData.minMax.Max - pathSplitData.minMax.Min); // Figure out up direction for path up = bounds.size.z > bounds.size.y ? Vector3.up : -Vector3.forward; Vector3 lastRotationAxis = up; // Loop through the data and assign to arrays. for (int i = 0; i < localPoints.Length; ++i) { localPoints[i] = pathSplitData.vertices[i]; localTangents[i] = pathSplitData.tangents[i]; cumulativeLengthAtEachVertex[i] = pathSplitData.cumulativeLength[i]; times[i] = cumulativeLengthAtEachVertex[i] / length; // Calculate normals if (space != PathSpace.xyz) { localNormals[i] = Vector3.Cross(localTangents[i], up) * (path.FlipNormals ? 1 : -1); continue; } if (i == 0) { localNormals[0] = Vector3.Cross(lastRotationAxis, pathSplitData.tangents[0]).normalized; continue; } // First reflection Vector3 offset = (localPoints[i] - localPoints[i - 1]); float sqrDst = offset.sqrMagnitude; Vector3 r = lastRotationAxis - offset * 2 / sqrDst * Vector3.Dot(offset, lastRotationAxis); Vector3 t = localTangents[i - 1] - offset * 2 / sqrDst * Vector3.Dot(offset, localTangents[i - 1]); // Second reflection Vector3 v2 = localTangents[i] - t; float c2 = Vector3.Dot(v2, v2); Vector3 finalRot = r - v2 * 2 / c2 * Vector3.Dot(v2, r); Vector3 n = Vector3.Cross(finalRot, localTangents[i]).normalized; localNormals[i] = n; lastRotationAxis = finalRot; } // Apply correction for 3d normals along a closed path if (space != PathSpace.xyz) { return; } if (isClosedLoop) { // Get angle between first and last normal (if zero, they're already lined up, otherwise we need to correct) float normalsAngleErrorAcrossJoin = Vector3.SignedAngle(localNormals[localNormals.Length - 1], localNormals[0], localTangents[0]); // Gradually rotate the normals along the path to ensure start and end normals line up correctly if (Mathf.Abs(normalsAngleErrorAcrossJoin) > 0.1f) // don't bother correcting if very nearly correct { for (int i = 1; i < localNormals.Length; i++) { float t = i / (localNormals.Length - 1f); float angle = normalsAngleErrorAcrossJoin * t; Quaternion rot = Quaternion.AngleAxis(angle, localTangents[i]); localNormals[i] = rot * localNormals[i] * (path.FlipNormals ? -1 : 1); } } return; } // Rotate normals to match up with user-defined anchor angles for (int anchorIndex = 0; anchorIndex < pathSplitData.anchorVertexMap.Count - 1; ++anchorIndex) { int nextAnchorIndex = isClosedLoop ? (anchorIndex + 1) % path.NumSegments : anchorIndex + 1; float startAngle = path.GetAnchorNormalAngle(anchorIndex) + path.GlobalNormalsAngle; float endAngle = path.GetAnchorNormalAngle(nextAnchorIndex) + path.GlobalNormalsAngle; float deltaAngle = Mathf.DeltaAngle(startAngle, endAngle); int startVertIndex = pathSplitData.anchorVertexMap[anchorIndex]; int endVertIndex = pathSplitData.anchorVertexMap[anchorIndex + 1]; int num = endVertIndex - startVertIndex; if (anchorIndex == pathSplitData.anchorVertexMap.Count - 2) { num += 1; } for (int i = 0; i < num; i++) { int vertIndex = startVertIndex + i; float t = i / (num - 1f); float angle = startAngle + deltaAngle * t; Quaternion rot = Quaternion.AngleAxis(angle, localTangents[vertIndex]); localNormals[vertIndex] = (rot * localNormals[vertIndex]) * (path.FlipNormals ? -1 : 1); } } }
/// <summary> Splits bezier path into array of vertices along the path.</summary> ///<param name="maxAngleError">How much can the angle of the path change before a vertex is added. This allows fewer vertices to be generated in straighter sections.</param> ///<param name="minVertexDst">Vertices won't be added closer together than this distance, regardless of angle error.</param> ///<param name="accuracy">Higher value means the change in angle is checked more frequently.</param> public VertexPath(BezierPath path, Transform transform, float vertexSpacing) : this(path, PathUtility.SplitBezierPathEvenly(path, Mathf.Max(vertexSpacing, minVertexSpacing), VertexPath.accuracy), transform) { }
/// <summary> Splits bezier path into array of vertices along the path.</summary> ///<param name="maxAngleError">How much can the angle of the path change before a vertex is added. This allows fewer vertices to be generated in straighter sections.</param> ///<param name="minVertexDst">Vertices won't be added closer together than this distance, regardless of angle error.</param> public VertexPath(BezierPath path, Transform transform, float maxAngleError = 0.3f, float minVertexDst = 0) : this(path, PathUtility.SplitBezierPathByAngleError(path, maxAngleError, minVertexDst, VertexPath.accuracy), transform) { }
public ScreenSpacePolyLine(BezierPath BezierPath, Transform transform, float maxAngleError, float minVertexDst, float accuracy = 1) { this.transform = transform; transformPosition = transform.position; transformRotation = transform.rotation; transformScale = transform.localScale; // Split path in vertices based on angle error verticesWorld = new List <Vector3>(); vertexToPathSegmentMap = new List <int>(); segmentStartIndices = new int[BezierPath.NumSegments + 1]; verticesWorld.Add(BezierPath[0]); vertexToPathSegmentMap.Add(0); Vector3 prevPointOnPath = BezierPath[0]; float dstSinceLastVertex = 0; Vector3 lastAddedPoint = prevPointOnPath; float dstSinceLastIntermediary = 0; for (int segmentIndex = 0; segmentIndex < BezierPath.NumSegments; segmentIndex++) { Vector3[] segmentPoints = BezierPath.GetPointsInSegment(segmentIndex); verticesWorld.Add(segmentPoints[0]); vertexToPathSegmentMap.Add(segmentIndex); segmentStartIndices[segmentIndex] = verticesWorld.Count - 1; prevPointOnPath = segmentPoints[0]; lastAddedPoint = prevPointOnPath; dstSinceLastVertex = 0; dstSinceLastIntermediary = 0; float estimatedSegmentLength = MathUtility.EstimateCurveLength(segmentPoints[0], segmentPoints[1], segmentPoints[2], segmentPoints[3]); int divisions = Mathf.CeilToInt(estimatedSegmentLength * accuracy * accuracyMultiplier); float increment = 1f / divisions; for (float t = increment; t <= 1; t += increment) { Vector3 pointOnPath = MathUtility.CubicBezier(segmentPoints[0], segmentPoints[1], segmentPoints[2], segmentPoints[3], t); Vector3 nextPointOnPath = MathUtility.CubicBezier(segmentPoints[0], segmentPoints[1], segmentPoints[2], segmentPoints[3], t + increment); // angle at current point on path float localAngle = 180 - MathUtility.MinAngle(prevPointOnPath, pointOnPath, nextPointOnPath); // angle between the last added vertex, the current point on the path, and the next point on the path float angleFromPrevVertex = 180 - MathUtility.MinAngle(lastAddedPoint, pointOnPath, nextPointOnPath); float angleError = Mathf.Max(localAngle, angleFromPrevVertex); if (angleError > maxAngleError && dstSinceLastVertex >= minVertexDst) { dstSinceLastVertex = 0; dstSinceLastIntermediary = 0; verticesWorld.Add(pointOnPath); vertexToPathSegmentMap.Add(segmentIndex); lastAddedPoint = pointOnPath; } else { if (dstSinceLastIntermediary > intermediaryThreshold) { verticesWorld.Add(pointOnPath); vertexToPathSegmentMap.Add(segmentIndex); dstSinceLastIntermediary = 0; } else { dstSinceLastIntermediary += (pointOnPath - prevPointOnPath).magnitude; } dstSinceLastVertex += (pointOnPath - prevPointOnPath).magnitude; } prevPointOnPath = pointOnPath; } } segmentStartIndices[BezierPath.NumSegments] = verticesWorld.Count; // ensure final point gets added (unless path is closed loop) verticesWorld.Add(!BezierPath.IsClosed ? BezierPath[BezierPath.NumPoints - 1] : BezierPath[0]); // Calculate length cumululativeLengthWorld = new float[verticesWorld.Count]; for (int i = 0; i < verticesWorld.Count; i++) { verticesWorld[i] = MathUtility.TransformPoint(verticesWorld[i], transform, BezierPath.Space); if (i > 0) { pathLengthWorld += (verticesWorld[i - 1] - verticesWorld[i]).magnitude; cumululativeLengthWorld[i] = pathLengthWorld; } } }