Skip to content

Commit 9578bdc

Browse files
glabuteMark Visser
authored andcommitted
CMCL-710: 3rd person gimbal lock fix (#370)
* eliminate 3rdpersonFollow console spam when looking up or down * revert a too-aggressive change * too aggressive again
1 parent ace2c9b commit 9578bdc

File tree

3 files changed

+122
-6
lines changed

3 files changed

+122
-6
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
66

77
## [2.8.4] - 2021-12-10
88
- Regression fix: virtual cameras no longer forget that they are targeting groups on domain reload.
9+
- Moved Cinemachine tools into the main Tools overlay (2022.1+), moved Freelook rig selection to a separate overlay, updated icons to support light and dark themes.
10+
- Bugfix: 3rdPersonFollow logged console messages when looking straight up or down.
911
- BugFix: InputProvider no longer causes a tiny gc alloc every frame.
1012

1113

Editor/Editors/Cinemachine3rdPersonFollowEditor.cs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,119 @@ static void Draw3rdPersonGizmos(Cinemachine3rdPersonFollow target, GizmoType sel
3535
Gizmos.color = originalGizmoColour;
3636
}
3737
}
38+
39+
#if UNITY_2021_2_OR_NEWER
40+
protected virtual void OnEnable()
41+
{
42+
CinemachineSceneToolUtility.RegisterTool(typeof(FollowOffsetTool));
43+
}
44+
45+
protected virtual void OnDisable()
46+
{
47+
CinemachineSceneToolUtility.UnregisterTool(typeof(FollowOffsetTool));
48+
}
49+
50+
void OnSceneGUI()
51+
{
52+
DrawSceneTools();
53+
}
54+
55+
void DrawSceneTools()
56+
{
57+
var thirdPerson = Target;
58+
if (thirdPerson == null || !thirdPerson.IsValid)
59+
{
60+
return;
61+
}
62+
63+
if (CinemachineSceneToolUtility.IsToolActive(typeof(FollowOffsetTool)))
64+
{
65+
var originalColor = Handles.color;
66+
67+
thirdPerson.GetRigPositions(out var followTargetPosition, out var shoulderPosition,
68+
out var armPosition);
69+
var followTargetRotation = thirdPerson.FollowTargetRotation;
70+
var targetForward = followTargetRotation * Vector3.forward;
71+
var heading = Cinemachine3rdPersonFollow.GetHeading(
72+
followTargetRotation, thirdPerson.VirtualCamera.State.ReferenceUp);
73+
74+
EditorGUI.BeginChangeCheck();
75+
// shoulder handle
76+
var sHandleMinId = GUIUtility.GetControlID(FocusType.Passive); // TODO: KGB workaround until id is exposed
77+
var newShoulderPosition = Handles.PositionHandle(shoulderPosition, heading);
78+
var sHandleMaxId = GUIUtility.GetControlID(FocusType.Passive); // TODO: KGB workaround until id is exposed
79+
80+
Handles.color = Handles.preselectionColor;
81+
// arm handle
82+
var followUp = followTargetRotation * Vector3.up;
83+
var aHandleId = GUIUtility.GetControlID(FocusType.Passive);
84+
var newArmPosition = Handles.Slider(aHandleId, armPosition, followUp,
85+
CinemachineSceneToolHelpers.CubeHandleCapSize(armPosition), Handles.CubeHandleCap, 0.5f);
86+
87+
// cam distance handle
88+
var camDistance = thirdPerson.CameraDistance;
89+
var camPos = armPosition - targetForward * camDistance;
90+
var cdHandleId = GUIUtility.GetControlID(FocusType.Passive);
91+
var newCamPos = Handles.Slider(cdHandleId, camPos, targetForward,
92+
CinemachineSceneToolHelpers.CubeHandleCapSize(camPos), Handles.CubeHandleCap, 0.5f);
93+
if (EditorGUI.EndChangeCheck())
94+
{
95+
// Modify via SerializedProperty for OnValidate to get called automatically, and scene repainting too
96+
var so = new SerializedObject(thirdPerson);
97+
98+
var shoulderOffset = so.FindProperty(() => thirdPerson.ShoulderOffset);
99+
shoulderOffset.vector3Value +=
100+
CinemachineSceneToolHelpers.PositionHandleDelta(heading, newShoulderPosition, shoulderPosition);
101+
var verticalArmLength = so.FindProperty(() => thirdPerson.VerticalArmLength);
102+
verticalArmLength.floatValue +=
103+
CinemachineSceneToolHelpers.SliderHandleDelta(newArmPosition, armPosition, followUp);
104+
var cameraDistance = so.FindProperty(() => thirdPerson.CameraDistance);
105+
cameraDistance.floatValue -=
106+
CinemachineSceneToolHelpers.SliderHandleDelta(newCamPos, camPos, targetForward);
107+
108+
so.ApplyModifiedProperties();
109+
}
110+
111+
var isDragged = IsHandleDragged(sHandleMinId, sHandleMaxId, shoulderPosition, "Shoulder Offset "
112+
+ thirdPerson.ShoulderOffset.ToString("F1"), followTargetPosition, shoulderPosition);
113+
isDragged |= IsHandleDragged(aHandleId, aHandleId, armPosition, "Vertical Arm Length ("
114+
+ thirdPerson.VerticalArmLength.ToString("F1") + ")", shoulderPosition, armPosition);
115+
isDragged |= IsHandleDragged(cdHandleId, cdHandleId, camPos, "Camera Distance ("
116+
+ camDistance.ToString("F1") + ")", armPosition, camPos);
117+
118+
CinemachineSceneToolHelpers.SoloOnDrag(isDragged, thirdPerson.VirtualCamera, sHandleMaxId);
119+
120+
Handles.color = originalColor;
121+
}
122+
123+
// local function that draws label and guide lines, and returns true if a handle has been dragged
124+
static bool IsHandleDragged
125+
(int handleMinId, int handleMaxId, Vector3 labelPos, string text, Vector3 lineStart, Vector3 lineEnd)
126+
{
127+
bool handleIsDragged;
128+
bool handleIsDraggedOrHovered;
129+
if (handleMinId == handleMaxId) {
130+
handleIsDragged = GUIUtility.hotControl == handleMinId;
131+
handleIsDraggedOrHovered = handleIsDragged || HandleUtility.nearestControl == handleMinId;
132+
}
133+
else
134+
{
135+
handleIsDragged = handleMinId < GUIUtility.hotControl && GUIUtility.hotControl < handleMaxId;
136+
handleIsDraggedOrHovered = handleIsDragged ||
137+
(handleMinId < HandleUtility.nearestControl && HandleUtility.nearestControl < handleMaxId);
138+
}
139+
140+
if (handleIsDraggedOrHovered)
141+
CinemachineSceneToolHelpers.DrawLabel(labelPos, text);
142+
143+
Handles.color = handleIsDraggedOrHovered ?
144+
Handles.selectedColor : CinemachineSceneToolHelpers.HelperLineDefaultColor;
145+
Handles.DrawLine(lineStart, lineEnd, CinemachineSceneToolHelpers.LineThickness);
146+
147+
return handleIsDragged;
148+
}
149+
}
150+
#endif
38151
}
39152
}
40153

Runtime/Components/Cinemachine3rdPersonFollow.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ void PositionCamera(ref CameraState curState, float deltaTime)
186186
var targetPos = FollowTargetPosition;
187187
var targetRot = FollowTargetRotation;
188188
var targetForward = targetRot * Vector3.forward;
189-
var heading = GetHeading(targetForward, up);
189+
var heading = GetHeading(targetRot, up);
190190

191191
if (deltaTime < 0)
192192
{
@@ -234,8 +234,7 @@ public void GetRigPositions(out Vector3 root, out Vector3 shoulder, out Vector3
234234
{
235235
var up = VirtualCamera.State.ReferenceUp;
236236
var targetRot = FollowTargetRotation;
237-
var targetForward = targetRot * Vector3.forward;
238-
var heading = GetHeading(targetForward, up);
237+
var heading = GetHeading(targetRot, up);
239238
root = m_PreviousFollowTargetPosition;
240239
GetRawRigPositions(root, targetRot, heading, out shoulder, out hand);
241240
#if CINEMACHINE_PHYSICS
@@ -244,10 +243,12 @@ public void GetRigPositions(out Vector3 root, out Vector3 shoulder, out Vector3
244243
#endif
245244
}
246245

247-
static Quaternion GetHeading(Vector3 targetForward, Vector3 up)
246+
internal static Quaternion GetHeading(Quaternion targetRot, Vector3 up)
248247
{
249-
var planeForward = targetForward.ProjectOntoPlane(up);
250-
planeForward = Vector3.Cross(up, Vector3.Cross(planeForward, up));
248+
var targetForward = targetRot * Vector3.forward;
249+
var planeForward = Vector3.Cross(up, Vector3.Cross(targetForward.ProjectOntoPlane(up), up));
250+
if (planeForward.AlmostZero())
251+
planeForward = Vector3.Cross(targetRot * Vector3.right, up);
251252
return Quaternion.LookRotation(planeForward, up);
252253
}
253254

0 commit comments

Comments
 (0)