void OnSceneGUI() { GUI.changed = false; // Get the keyframes of the AnimationCurve to be manipulated Keyframe[] keys = script.spline.keys; // Set defaultLocalRotation so that the initial local rotation will be the zero point for the rotation limit if (!Application.isPlaying) { script.defaultLocalRotation = script.transform.localRotation; } if (script.axis == Vector3.zero) { return; } // Make the curve loop script.spline.postWrapMode = WrapMode.Loop; script.spline.preWrapMode = WrapMode.Loop; DrawRotationSphere(script.transform.position); // Display the main axis DrawArrow(script.transform.position, Direction(script.axis), colorDefault, "Axis", 0.02f); Vector3 swing = script.axis.normalized; // Editing tools GUI Handles.BeginGUI(); GUILayout.BeginArea(new Rect(10, Screen.height - 140, 400, 90), "Rotation Limit Spline", "Window"); // Scale Mode and Tangent Mode GUILayout.BeginHorizontal(); scaleMode = (ScaleMode)EditorGUILayout.EnumPopup("Drag Handle", scaleMode); tangentMode = (TangentMode)EditorGUILayout.EnumPopup("Drag Tangents", tangentMode); GUILayout.EndHorizontal(); EditorGUILayout.Space(); if (Inspector.Button("Rotate 90 degrees", "Rotate rotation limit around axis.", script, GUILayout.Width(220))) { if (!Application.isPlaying) { Undo.RecordObject(script, "Handle Value"); } for (int i = 0; i < keys.Length; i++) { keys[i].time += 90; } } // Cloning values from another RotationLimitSpline EditorGUILayout.BeginHorizontal(); if (Inspector.Button("Clone From", "Make this rotation limit identical to another", script, GUILayout.Width(220))) { CloneLimit(); keys = script.spline.keys; } clone = (RotationLimitSpline)EditorGUILayout.ObjectField("", clone, typeof(RotationLimitSpline), true); EditorGUILayout.EndHorizontal(); GUILayout.EndArea(); Handles.EndGUI(); // Draw keyframes for (int i = 0; i < keys.Length - 1; i++) { float angle = keys[i].time; // Start drawing handles Quaternion offset = Quaternion.AngleAxis(angle, swing); Quaternion rotation = Quaternion.AngleAxis(keys[i].value, offset * script.crossAxis); Vector3 position = script.transform.position + Direction(rotation * swing); Handles.Label(position, " " + i.ToString()); // Dragging Values if (selectedHandle == i) { Handles.color = colorHandles; switch (scaleMode) { case ScaleMode.Limit: float inputValue = keys[i].value; inputValue = Mathf.Clamp(Handles.ScaleValueHandle(inputValue, position, Quaternion.identity, 0.5f, Handles.SphereCap, 0), 0.01f, 180); if (keys[i].value != inputValue) { if (!Application.isPlaying) { Undo.RecordObject(script, "Handle Value"); } keys[i].value = inputValue; } break; case ScaleMode.Angle: float inputTime = keys[i].time; inputTime = Handles.ScaleValueHandle(inputTime, position, Quaternion.identity, 0.5f, Handles.SphereCap, 0); if (keys[i].time != inputTime) { if (!Application.isPlaying) { Undo.RecordObject(script, "Handle Angle"); } keys[i].time = inputTime; } break; } } // Handle select button if (selectedHandle != i) { Handles.color = Color.blue; if (Handles.Button(position, script.transform.rotation, 0.05f, 0.05f, Handles.SphereCap)) { selectedHandle = i; } } // Tangents if (selectedHandle == i) { // Evaluate positions before and after the key to get the tangent positions Vector3 prevPosition = GetAnglePosition(keys[i].time - 1); Vector3 nextPosition = GetAnglePosition(keys[i].time + 1); // Draw handles for the tangents Handles.color = Color.white; Vector3 toNext = (nextPosition - position).normalized * 0.3f; float outTangent = keys[i].outTangent; outTangent = Handles.ScaleValueHandle(outTangent, position + toNext, Quaternion.identity, 0.2f, Handles.SphereCap, 0); Vector3 toPrev = (prevPosition - position).normalized * 0.3f; float inTangent = keys[i].inTangent; inTangent = Handles.ScaleValueHandle(inTangent, position + toPrev, Quaternion.identity, 0.2f, Handles.SphereCap, 0); if (outTangent != keys[i].outTangent || inTangent != keys[i].inTangent) { selectedHandle = i; } // Make the other tangent match the dragged tangent (if in "Smooth" TangentMode) switch (tangentMode) { case TangentMode.Smooth: if (outTangent != keys[i].outTangent) { if (!Application.isPlaying) { Undo.RecordObject(script, "Tangents"); } keys[i].outTangent = outTangent; keys[i].inTangent = outTangent; } else if (inTangent != keys[i].inTangent) { if (!Application.isPlaying) { Undo.RecordObject(script, "Tangents"); } keys[i].outTangent = inTangent; keys[i].inTangent = inTangent; } break; case TangentMode.Independent: if (outTangent != keys[i].outTangent) { if (!Application.isPlaying) { Undo.RecordObject(script, "Tangents"); } keys[i].outTangent = outTangent; } else if (inTangent != keys[i].inTangent) { if (!Application.isPlaying) { Undo.RecordObject(script, "Tangents"); } keys[i].inTangent = inTangent; } break; } // Draw lines and labels to tangent handles Handles.color = Color.white; GUI.color = Color.white; Handles.DrawLine(position, position + toNext); Handles.Label(position + toNext, " Out"); Handles.DrawLine(position, position + toPrev); Handles.Label(position + toPrev, " In"); } } // Selected Point GUI if (selectedHandle != -1) { Handles.BeginGUI(); GUILayout.BeginArea(new Rect(Screen.width - 240, Screen.height - 200, 230, 150), "Handle " + selectedHandle.ToString(), "Window"); if (Inspector.Button("Delete", "Delete this handle", script)) { if (keys.Length > 4) { deleteHandle = selectedHandle; } else if (!Warning.logged) { script.LogWarning("Spline Rotation Limit should have at least 3 handles"); } } if (Inspector.Button("Add Handle", "Add a new handle next to this one", script)) { addHandle = selectedHandle; } // Clamp the key angles to previous and next handle angles float prevTime = 0, nextTime = 0; if (selectedHandle < keys.Length - 2) { nextTime = keys[selectedHandle + 1].time; } else { nextTime = keys[0].time + 360; } if (selectedHandle == 0) { prevTime = keys[keys.Length - 2].time - 360; } else { prevTime = keys[selectedHandle - 1].time; } // Angles float inputTime = keys[selectedHandle].time; inputTime = Mathf.Clamp(EditorGUILayout.FloatField(new GUIContent("Angle", "Angle of the point (0-360)."), inputTime), prevTime, nextTime); if (keys[selectedHandle].time != inputTime) { if (!Application.isPlaying) { Undo.RecordObject(script, "Handle Angle"); } keys[selectedHandle].time = inputTime; } // Limits float inputValue = keys[selectedHandle].value; inputValue = Mathf.Clamp(EditorGUILayout.FloatField(new GUIContent("Limit", "Max angular limit from Axis at this angle"), inputValue), 0, 180); if (keys[selectedHandle].value != inputValue) { if (!Application.isPlaying) { Undo.RecordObject(script, "Handle Limit"); } keys[selectedHandle].value = inputValue; } // In Tangents float inputInTangent = keys[selectedHandle].inTangent; inputInTangent = EditorGUILayout.FloatField(new GUIContent("In Tangent", "In tangent of the handle point on the spline"), inputInTangent); if (keys[selectedHandle].inTangent != inputInTangent) { if (!Application.isPlaying) { Undo.RecordObject(script, "Handle In Tangent"); } keys[selectedHandle].inTangent = inputInTangent; } // Out tangents float inputOutTangent = keys[selectedHandle].outTangent; inputOutTangent = EditorGUILayout.FloatField(new GUIContent("Out Tangent", "Out tangent of the handle point on the spline"), inputOutTangent); if (keys[selectedHandle].outTangent != inputOutTangent) { if (!Application.isPlaying) { Undo.RecordObject(script, "Handle Out Tangent"); } keys[selectedHandle].outTangent = inputOutTangent; } GUILayout.EndArea(); Handles.EndGUI(); } // Make sure the keyframes are valid; ValidateKeyframes(keys); // Replace the AnimationCurve keyframes with the manipulated keyframes script.spline.keys = keys; // Display limits for (int i = 0; i < 360; i += 2) { float evaluatedLimit = script.spline.Evaluate((float)i); Quaternion offset = Quaternion.AngleAxis(i, swing); Quaternion evaluatedRotation = Quaternion.AngleAxis(evaluatedLimit, offset * script.crossAxis); Quaternion testRotation = Quaternion.AngleAxis(179.9f, offset * script.crossAxis); Quaternion limitedRotation = script.LimitSwing(testRotation); Vector3 evaluatedDirection = evaluatedRotation * swing; Vector3 limitedDirection = limitedRotation * swing; // Display the limit points in red if they are out of range bool isValid = Vector3.Distance(evaluatedDirection, limitedDirection) < 0.01f && evaluatedLimit >= 0; Color color = isValid? colorDefaultTransparent: colorInvalid; Vector3 limitPoint = script.transform.position + Direction(evaluatedDirection); Handles.color = color; if (i == 0) { zeroPoint = limitPoint; } Handles.DrawLine(script.transform.position, limitPoint); if (i > 0) { Handles.color = isValid? colorDefault: colorInvalid; Handles.DrawLine(limitPoint, lastPoint); if (i == 358) { Handles.DrawLine(limitPoint, zeroPoint); } } lastPoint = limitPoint; } // Deleting points if (deleteHandle != -1) { DeleteHandle(deleteHandle); selectedHandle = -1; deleteHandle = -1; } // Adding points if (addHandle != -1) { AddHandle(addHandle); addHandle = -1; } Handles.color = Color.white; if (GUI.changed) { EditorUtility.SetDirty(script); } }
void OnSceneGUI() { GUI.changed = false; // Get the keyframes of the AnimationCurve to be manipulated Keyframe[] keys = script.spline.keys; // Set defaultLocalRotation so that the initial local rotation will be the zero point for the rotation limit if (!Application.isPlaying) script.defaultLocalRotation = script.transform.localRotation; if (script.axis == Vector3.zero) return; // Make the curve loop script.spline.postWrapMode = WrapMode.Loop; script.spline.preWrapMode = WrapMode.Loop; DrawRotationSphere(script.transform.position); // Display the main axis DrawArrow(script.transform.position, Direction(script.axis), colorDefault, "Axis", 0.02f); Vector3 swing = script.axis.normalized; // Editing tools GUI Handles.BeginGUI(); GUILayout.BeginArea(new Rect(10, Screen.height - 140, 440, 90), "Rotation Limit Spline", "Window"); // Scale Mode and Tangent Mode GUILayout.BeginHorizontal(); scaleMode = (ScaleMode)EditorGUILayout.EnumPopup("Drag Handle", scaleMode); tangentMode = (TangentMode)EditorGUILayout.EnumPopup("Drag Tangents", tangentMode); GUILayout.EndHorizontal(); EditorGUILayout.Space(); if (Inspector.Button("Rotate 90 degrees", "Rotate rotation limit around axis.", script, GUILayout.Width(220))) { if (!Application.isPlaying) Undo.RecordObject(script, "Handle Value"); for (int i = 0; i < keys.Length; i++) keys[i].time += 90; } // Cloning values from another RotationLimitSpline EditorGUILayout.BeginHorizontal(); if (Inspector.Button("Clone From", "Make this rotation limit identical to another", script, GUILayout.Width(220))) { CloneLimit(); keys = script.spline.keys; } clone = (RotationLimitSpline)EditorGUILayout.ObjectField("", clone, typeof(RotationLimitSpline), true); EditorGUILayout.EndHorizontal(); GUILayout.EndArea(); Handles.EndGUI(); // Draw keyframes for (int i = 0; i < keys.Length - 1; i++) { float angle = keys[i].time; // Start drawing handles Quaternion offset = Quaternion.AngleAxis(angle, swing); Quaternion rotation = Quaternion.AngleAxis(keys[i].value, offset * script.crossAxis); Vector3 position = script.transform.position + Direction(rotation * swing); Handles.Label(position, " " + i.ToString()); // Dragging Values if (selectedHandle == i) { Handles.color = colorHandles; switch(scaleMode) { case ScaleMode.Limit: float inputValue = keys[i].value; inputValue = Mathf.Clamp(Handles.ScaleValueHandle(inputValue, position, Quaternion.identity, 0.5f, Handles.SphereCap, 0), 0.01f, 180); if (keys[i].value != inputValue) { if (!Application.isPlaying) Undo.RecordObject(script, "Handle Value"); keys[i].value = inputValue; } break; case ScaleMode.Angle: float inputTime = keys[i].time; inputTime = Handles.ScaleValueHandle(inputTime, position, Quaternion.identity, 0.5f, Handles.SphereCap, 0); if (keys[i].time != inputTime) { if (!Application.isPlaying) Undo.RecordObject(script, "Handle Angle"); keys[i].time = inputTime; } break; } } // Handle select button if (selectedHandle != i) { Handles.color = Color.blue; if (Handles.Button(position, script.transform.rotation, 0.05f, 0.05f, Handles.SphereCap)) { selectedHandle = i; } } // Tangents if (selectedHandle == i) { // Evaluate positions before and after the key to get the tangent positions Vector3 prevPosition = GetAnglePosition(keys[i].time - 1); Vector3 nextPosition = GetAnglePosition(keys[i].time + 1); // Draw handles for the tangents Handles.color = Color.white; Vector3 toNext = (nextPosition - position).normalized * 0.3f; float outTangent = keys[i].outTangent; outTangent = Handles.ScaleValueHandle(outTangent, position + toNext, Quaternion.identity, 0.2f, Handles.SphereCap, 0); Vector3 toPrev = (prevPosition - position).normalized * 0.3f; float inTangent = keys[i].inTangent; inTangent = Handles.ScaleValueHandle(inTangent, position + toPrev, Quaternion.identity, 0.2f, Handles.SphereCap, 0); if (outTangent != keys[i].outTangent || inTangent != keys[i].inTangent) selectedHandle = i; // Make the other tangent match the dragged tangent (if in "Smooth" TangentMode) switch(tangentMode) { case TangentMode.Smooth: if (outTangent != keys[i].outTangent) { if (!Application.isPlaying) Undo.RecordObject(script, "Tangents"); keys[i].outTangent = outTangent; keys[i].inTangent = outTangent; } else if (inTangent != keys[i].inTangent) { if (!Application.isPlaying) Undo.RecordObject(script, "Tangents"); keys[i].outTangent = inTangent; keys[i].inTangent = inTangent; } break; case TangentMode.Independent: if (outTangent != keys[i].outTangent) { if (!Application.isPlaying) Undo.RecordObject(script, "Tangents"); keys[i].outTangent = outTangent; } else if (inTangent != keys[i].inTangent) { if (!Application.isPlaying) Undo.RecordObject(script, "Tangents"); keys[i].inTangent = inTangent; } break; } // Draw lines and labels to tangent handles Handles.color = Color.white; GUI.color = Color.white; Handles.DrawLine(position, position + toNext); Handles.Label(position + toNext, " Out"); Handles.DrawLine(position, position + toPrev); Handles.Label(position + toPrev, " In"); } } // Selected Point GUI if (selectedHandle != -1) { Handles.BeginGUI(); GUILayout.BeginArea(new Rect(Screen.width - 240, Screen.height - 200, 230, 150), "Handle " + selectedHandle.ToString(), "Window"); if (Inspector.Button("Delete", "Delete this handle", script)) { if (keys.Length > 4) { deleteHandle = selectedHandle; } else if (!Warning.logged) script.LogWarning("Spline Rotation Limit should have at least 3 handles"); } if (Inspector.Button("Add Handle", "Add a new handle next to this one", script)) { addHandle = selectedHandle; } // Clamp the key angles to previous and next handle angles float prevTime = 0, nextTime = 0; if (selectedHandle < keys.Length - 2) nextTime = keys[selectedHandle + 1].time; else nextTime = keys[0].time + 360; if (selectedHandle == 0) prevTime = keys[keys.Length - 2].time - 360; else prevTime = keys[selectedHandle - 1].time; // Angles float inputTime = keys[selectedHandle].time; inputTime = Mathf.Clamp(EditorGUILayout.FloatField(new GUIContent("Angle", "Angle of the point (0-360)."), inputTime), prevTime, nextTime); if (keys[selectedHandle].time != inputTime) { if (!Application.isPlaying) Undo.RecordObject(script, "Handle Angle"); keys[selectedHandle].time = inputTime; } // Limits float inputValue = keys[selectedHandle].value; inputValue = Mathf.Clamp(EditorGUILayout.FloatField(new GUIContent("Limit", "Max angular limit from Axis at this angle"), inputValue), 0, 180); if (keys[selectedHandle].value != inputValue) { if (!Application.isPlaying) Undo.RecordObject(script, "Handle Limit"); keys[selectedHandle].value = inputValue; } // In Tangents float inputInTangent = keys[selectedHandle].inTangent; inputInTangent = EditorGUILayout.FloatField(new GUIContent("In Tangent", "In tangent of the handle point on the spline"), inputInTangent); if (keys[selectedHandle].inTangent != inputInTangent) { if (!Application.isPlaying) Undo.RecordObject(script, "Handle In Tangent"); keys[selectedHandle].inTangent = inputInTangent; } // Out tangents float inputOutTangent = keys[selectedHandle].outTangent; inputOutTangent = EditorGUILayout.FloatField(new GUIContent("Out Tangent", "Out tangent of the handle point on the spline"), inputOutTangent); if (keys[selectedHandle].outTangent != inputOutTangent) { if (!Application.isPlaying) Undo.RecordObject(script, "Handle Out Tangent"); keys[selectedHandle].outTangent = inputOutTangent; } GUILayout.EndArea(); Handles.EndGUI(); } // Make sure the keyframes are valid; ValidateKeyframes(keys); // Replace the AnimationCurve keyframes with the manipulated keyframes script.spline.keys = keys; // Display limits for (int i = 0; i < 360; i+= 2) { float evaluatedLimit = script.spline.Evaluate((float)i); Quaternion offset = Quaternion.AngleAxis(i, swing); Quaternion evaluatedRotation = Quaternion.AngleAxis(evaluatedLimit, offset * script.crossAxis); Quaternion testRotation = Quaternion.AngleAxis(179.9f, offset * script.crossAxis); Quaternion limitedRotation = script.LimitSwing(testRotation); Vector3 evaluatedDirection = evaluatedRotation * swing; Vector3 limitedDirection = limitedRotation * swing; // Display the limit points in red if they are out of range bool isValid = Vector3.Distance(evaluatedDirection, limitedDirection) < 0.01f && evaluatedLimit >= 0; Color color = isValid? colorDefaultTransparent: colorInvalid; Vector3 limitPoint = script.transform.position + Direction(evaluatedDirection); Handles.color = color; if (i == 0) zeroPoint = limitPoint; Handles.DrawLine(script.transform.position, limitPoint); if (i > 0) { Handles.color = isValid? colorDefault: colorInvalid; Handles.DrawLine(limitPoint, lastPoint); if (i == 358) Handles.DrawLine(limitPoint, zeroPoint); } lastPoint = limitPoint; } // Deleting points if (deleteHandle != -1) { DeleteHandle(deleteHandle); selectedHandle = -1; deleteHandle = -1; } // Adding points if (addHandle != -1) { AddHandle(addHandle); addHandle = -1; } Handles.color = Color.white; if (GUI.changed) EditorUtility.SetDirty(script); }