/// <summary> /// Simulates rotating the agent towards the specified direction and returns the new rotation. /// /// Note that this only calculates a new rotation, it does not change the actual rotation of the agent. /// /// See: <see cref="orientation"/> /// See: <see cref="movementPlane"/> /// </summary> /// <param name="direction">Direction in the movement plane to rotate towards.</param> /// <param name="maxDegreesMainAxis">Maximum number of degrees to rotate this frame around the character's main axis. This is rotating left and right as a character normally does.</param> /// <param name="maxDegreesOffAxis">Maximum number of degrees to rotate this frame around other axes. This is used to ensure the character's up direction is correct. /// It is only used for non-planar worlds where the up direction changes depending on the position of the character. /// More precisely a faster code path which ignores this parameter is used whenever the current #movementPlane is exactly the XZ or XY plane. /// This must be at least as large as maxDegreesMainAxis.</param> protected Quaternion SimulateRotationTowards(Vector2 direction, float maxDegreesMainAxis, float maxDegreesOffAxis = float.PositiveInfinity) { Quaternion targetRotation; if (movementPlane.isXY || movementPlane.isXZ) { if (direction == Vector2.zero) { return(simulatedRotation); } // Common fast path. // A standard XY or XZ movement plane indicates that the character is moving in a normal planar world. // We will use a much faster code path for this case since we don't have to deal with changing the 'up' direction of the character all the time. // This code path mostly works for non-planar worlds too, but it will fail in some cases. // In particular it will not be able to adjust the up direction of the character while it is standing still (because then a zero maxDegreesMainAxis is usually passed). // That case may be important, especially when the character has just been spawned and does not have a destination yet. targetRotation = Quaternion.LookRotation(movementPlane.ToWorld(direction, 0), movementPlane.ToWorld(Vector2.zero, 1)); maxDegreesOffAxis = maxDegreesMainAxis; } else { // Decompose the rotation into two parts: a rotation around the main axis of the character, and a rotation around the other axes. // Then limit the rotation speed along those two components separately. var forwardInPlane = movementPlane.ToPlane(rotation * (orientation == OrientationMode.YAxisForward ? Vector3.up : Vector3.forward)); // Can happen if the character is perpendicular to the plane if (forwardInPlane == Vector2.zero) { forwardInPlane = Vector2.right; } var rotationVectorAroundMainAxis = VectorMath.ComplexMultiplyConjugate(direction, forwardInPlane); // Note: If the direction is zero, then angle will also be zero since atan2(0,0) = 0 var angle = Mathf.Atan2(rotationVectorAroundMainAxis.y, rotationVectorAroundMainAxis.x) * Mathf.Rad2Deg; var rotationAroundMainAxis = Quaternion.AngleAxis(-Mathf.Min(Mathf.Abs(angle), maxDegreesMainAxis) * Mathf.Sign(angle), Vector3.up); targetRotation = Quaternion.LookRotation(movementPlane.ToWorld(forwardInPlane, 0), movementPlane.ToWorld(Vector2.zero, 1)); targetRotation = targetRotation * rotationAroundMainAxis; } // This causes the character to only rotate around the Z axis if (orientation == OrientationMode.YAxisForward) { targetRotation *= Quaternion.Euler(90, 0, 0); } return(Quaternion.RotateTowards(simulatedRotation, targetRotation, maxDegreesOffAxis)); }