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); } }
/// 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); } } }