CinemachineTransposer.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. using System;
  2. using Cinemachine.Utility;
  3. using UnityEngine;
  4. namespace Cinemachine
  5. {
  6. /// <summary>
  7. /// This is a CinemachineComponent in the Body section of the component pipeline.
  8. /// Its job is to position the camera in a fixed relationship to the vcam's Follow
  9. /// target object, with offsets and damping.
  10. ///
  11. /// The Tansposer will only change the camera's position in space. It will not
  12. /// re-orient or otherwise aim the camera. To to that, you need to instruct
  13. /// the vcam in the Aim section of its pipeline.
  14. /// </summary>
  15. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  16. [AddComponentMenu("")] // Don't display in add component menu
  17. [SaveDuringPlay]
  18. public class CinemachineTransposer : CinemachineComponentBase
  19. {
  20. /// <summary>
  21. /// The coordinate space to use when interpreting the offset from the target
  22. /// </summary>
  23. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  24. public enum BindingMode
  25. {
  26. /// <summary>
  27. /// Camera will be bound to the Follow target using a frame of reference consisting
  28. /// of the target's local frame at the moment when the virtual camera was enabled,
  29. /// or when the target was assigned.
  30. /// </summary>
  31. LockToTargetOnAssign = 0,
  32. /// <summary>
  33. /// Camera will be bound to the Follow target using a frame of reference consisting
  34. /// of the target's local frame, with the tilt and roll zeroed out.
  35. /// </summary>
  36. LockToTargetWithWorldUp = 1,
  37. /// <summary>
  38. /// Camera will be bound to the Follow target using a frame of reference consisting
  39. /// of the target's local frame, with the roll zeroed out.
  40. /// </summary>
  41. LockToTargetNoRoll = 2,
  42. /// <summary>
  43. /// Camera will be bound to the Follow target using the target's local frame.
  44. /// </summary>
  45. LockToTarget = 3,
  46. /// <summary>Camera will be bound to the Follow target using a world space offset.</summary>
  47. WorldSpace = 4,
  48. /// <summary>Offsets will be calculated relative to the target, using Camera-local axes</summary>
  49. SimpleFollowWithWorldUp = 5
  50. }
  51. /// <summary>The coordinate space to use when interpreting the offset from the target</summary>
  52. [Tooltip("The coordinate space to use when interpreting the offset from the target. This is also "
  53. + "used to set the camera's Up vector, which will be maintained when aiming the camera.")]
  54. public BindingMode m_BindingMode = BindingMode.LockToTargetWithWorldUp;
  55. /// <summary>The distance which the transposer will attempt to maintain from the transposer subject</summary>
  56. [Tooltip("The distance vector that the transposer will attempt to maintain from the Follow target")]
  57. public Vector3 m_FollowOffset = Vector3.back * 10f;
  58. /// <summary>How aggressively the camera tries to maintain the offset in the X-axis.
  59. /// Small numbers are more responsive, rapidly translating the camera to keep the target's
  60. /// x-axis offset. Larger numbers give a more heavy slowly responding camera.
  61. /// Using different settings per axis can yield a wide range of camera behaviors</summary>
  62. [Range(0f, 20f)]
  63. [Tooltip("How aggressively the camera tries to maintain the offset in the X-axis. Small numbers "
  64. + "are more responsive, rapidly translating the camera to keep the target's x-axis offset. "
  65. + "Larger numbers give a more heavy slowly responding camera. Using different settings per "
  66. + "axis can yield a wide range of camera behaviors.")]
  67. public float m_XDamping = 1f;
  68. /// <summary>How aggressively the camera tries to maintain the offset in the Y-axis.
  69. /// Small numbers are more responsive, rapidly translating the camera to keep the target's
  70. /// y-axis offset. Larger numbers give a more heavy slowly responding camera.
  71. /// Using different settings per axis can yield a wide range of camera behaviors</summary>
  72. [Range(0f, 20f)]
  73. [Tooltip("How aggressively the camera tries to maintain the offset in the Y-axis. Small numbers "
  74. + "are more responsive, rapidly translating the camera to keep the target's y-axis offset. "
  75. + "Larger numbers give a more heavy slowly responding camera. Using different settings per "
  76. + "axis can yield a wide range of camera behaviors.")]
  77. public float m_YDamping = 1f;
  78. /// <summary>How aggressively the camera tries to maintain the offset in the Z-axis.
  79. /// Small numbers are more responsive, rapidly translating the camera to keep the
  80. /// target's z-axis offset. Larger numbers give a more heavy slowly responding camera.
  81. /// Using different settings per axis can yield a wide range of camera behaviors</summary>
  82. [Range(0f, 20f)]
  83. [Tooltip("How aggressively the camera tries to maintain the offset in the Z-axis. "
  84. + "Small numbers are more responsive, rapidly translating the camera to keep the "
  85. + "target's z-axis offset. Larger numbers give a more heavy slowly responding camera. "
  86. + "Using different settings per axis can yield a wide range of camera behaviors.")]
  87. public float m_ZDamping = 1f;
  88. /// <summary>How to calculate the angular damping for the target orientation</summary>
  89. public enum AngularDampingMode
  90. {
  91. /// <summary>Use Euler angles to specify damping values.
  92. /// Subject to gimbal-lock fwhen pitch is steep.</summary>
  93. Euler,
  94. /// <summary>
  95. /// Use quaternions to calculate angular damping.
  96. /// No per-channel control, but not susceptible to gimbal-lock</summary>
  97. Quaternion
  98. }
  99. /// <summary>How to calculate the angular damping for the target orientation.
  100. /// Use Quaternion if you expect the target to take on very steep pitches, which would
  101. /// be subject to gimbal lock if Eulers are used.</summary>
  102. public AngularDampingMode m_AngularDampingMode = AngularDampingMode.Euler;
  103. /// <summary>How aggressively the camera tries to track the target rotation's X angle.
  104. /// Small numbers are more responsive. Larger numbers give a more heavy slowly responding camera.</summary>
  105. [Range(0f, 20f)]
  106. [Tooltip("How aggressively the camera tries to track the target rotation's X angle. "
  107. + "Small numbers are more responsive. Larger numbers give a more heavy slowly responding camera.")]
  108. public float m_PitchDamping = 0;
  109. /// <summary>How aggressively the camera tries to track the target rotation's Y angle.
  110. /// Small numbers are more responsive. Larger numbers give a more heavy slowly responding camera.</summary>
  111. [Range(0f, 20f)]
  112. [Tooltip("How aggressively the camera tries to track the target rotation's Y angle. "
  113. + "Small numbers are more responsive. Larger numbers give a more heavy slowly responding camera.")]
  114. public float m_YawDamping = 0;
  115. /// <summary>How aggressively the camera tries to track the target rotation's Z angle.
  116. /// Small numbers are more responsive. Larger numbers give a more heavy slowly responding camera.</summary>
  117. [Range(0f, 20f)]
  118. [Tooltip("How aggressively the camera tries to track the target rotation's Z angle. "
  119. + "Small numbers are more responsive. Larger numbers give a more heavy slowly responding camera.")]
  120. public float m_RollDamping = 0f;
  121. /// <summary>How aggressively the camera tries to track the target's orientation.
  122. /// Small numbers are more responsive. Larger numbers give a more heavy slowly responding camera.</summary>
  123. [Range(0f, 20f)]
  124. [Tooltip("How aggressively the camera tries to track the target's orientation. "
  125. + "Small numbers are more responsive. Larger numbers give a more heavy slowly responding camera.")]
  126. public float m_AngularDamping = 0f;
  127. /// <summary>Derived classes should call this from their OnValidate() implementation</summary>
  128. protected virtual void OnValidate()
  129. {
  130. m_FollowOffset = EffectiveOffset;
  131. }
  132. /// <summary>Hide the offset in int inspector. Used by FreeLook.</summary>
  133. public bool HideOffsetInInspector { get; set; }
  134. /// <summary>Get the target offset, with sanitization</summary>
  135. public Vector3 EffectiveOffset
  136. {
  137. get
  138. {
  139. Vector3 offset = m_FollowOffset;
  140. if (m_BindingMode == BindingMode.SimpleFollowWithWorldUp)
  141. {
  142. offset.x = 0;
  143. offset.z = -Mathf.Abs(offset.z);
  144. }
  145. return offset;
  146. }
  147. }
  148. /// <summary>True if component is enabled and has a valid Follow target</summary>
  149. public override bool IsValid { get { return enabled && FollowTarget != null; } }
  150. /// <summary>Get the Cinemachine Pipeline stage that this component implements.
  151. /// Always returns the Body stage</summary>
  152. public override CinemachineCore.Stage Stage { get { return CinemachineCore.Stage.Body; } }
  153. /// <summary>
  154. /// Report maximum damping time needed for this component.
  155. /// </summary>
  156. /// <returns>Highest damping setting in this component</returns>
  157. public override float GetMaxDampTime()
  158. {
  159. var d = Damping;
  160. var d2 = AngularDamping;
  161. var a = Mathf.Max(d.x, Mathf.Max(d.y, d.z));
  162. var b = Mathf.Max(d2.x, Mathf.Max(d2.y, d2.z));
  163. return Mathf.Max(a, b);
  164. }
  165. /// <summary>Positions the virtual camera according to the transposer rules.</summary>
  166. /// <param name="curState">The current camera state</param>
  167. /// <param name="deltaTime">Used for damping. If less than 0, no damping is done.</param>
  168. public override void MutateCameraState(ref CameraState curState, float deltaTime)
  169. {
  170. InitPrevFrameStateInfo(ref curState, deltaTime);
  171. if (IsValid)
  172. {
  173. Vector3 offset = EffectiveOffset;
  174. TrackTarget(deltaTime, curState.ReferenceUp, offset, out Vector3 pos, out Quaternion orient);
  175. offset = orient * offset;
  176. curState.ReferenceUp = orient * Vector3.up;
  177. // Respect minimum target distance on XZ plane
  178. var targetPosition = FollowTargetPosition;
  179. pos += GetOffsetForMinimumTargetDistance(
  180. pos, offset, curState.RawOrientation * Vector3.forward,
  181. curState.ReferenceUp, targetPosition);
  182. curState.RawPosition = pos + offset;
  183. }
  184. }
  185. /// <summary>This is called to notify the us that a target got warped,
  186. /// so that we can update its internal state to make the camera
  187. /// also warp seamlessy.</summary>
  188. /// <param name="target">The object that was warped</param>
  189. /// <param name="positionDelta">The amount the target's position changed</param>
  190. public override void OnTargetObjectWarped(Transform target, Vector3 positionDelta)
  191. {
  192. base.OnTargetObjectWarped(target, positionDelta);
  193. if (target == FollowTarget)
  194. m_PreviousTargetPosition += positionDelta;
  195. }
  196. /// <summary>
  197. /// Force the virtual camera to assume a given position and orientation
  198. /// </summary>
  199. /// <param name="pos">Worldspace pposition to take</param>
  200. /// <param name="rot">Worldspace orientation to take</param>
  201. public override void ForceCameraPosition(Vector3 pos, Quaternion rot)
  202. {
  203. base.ForceCameraPosition(pos, rot);
  204. // Infer target pos from camera
  205. var targetRot = m_BindingMode == BindingMode.SimpleFollowWithWorldUp
  206. ? rot : GetReferenceOrientation(VirtualCamera.State.ReferenceUp);
  207. m_PreviousTargetPosition = pos - targetRot * EffectiveOffset;
  208. }
  209. /// <summary>Initializes the state for previous frame if appropriate.</summary>
  210. /// <param name="curState">The current camera state</param>
  211. /// <param name="deltaTime">Current effective deltaTime.</param>
  212. protected void InitPrevFrameStateInfo(
  213. ref CameraState curState, float deltaTime)
  214. {
  215. bool prevStateValid = deltaTime >= 0 && VirtualCamera.PreviousStateIsValid;
  216. if (m_previousTarget != FollowTarget || !prevStateValid)
  217. {
  218. m_previousTarget = FollowTarget;
  219. m_targetOrientationOnAssign = FollowTargetRotation;
  220. }
  221. if (!prevStateValid)
  222. {
  223. m_PreviousTargetPosition = FollowTargetPosition;
  224. m_PreviousReferenceOrientation = GetReferenceOrientation(curState.ReferenceUp);
  225. }
  226. }
  227. /// <summary>Positions the virtual camera according to the transposer rules.</summary>
  228. /// <param name="deltaTime">Used for damping. If less than 0, no damping is done.</param>
  229. /// <param name="up">Current camera up</param>
  230. /// <param name="desiredCameraOffset">Where we want to put the camera relative to the follow target</param>
  231. /// <param name="outTargetPosition">Resulting camera position</param>
  232. /// <param name="outTargetOrient">Damped target orientation</param>
  233. protected void TrackTarget(
  234. float deltaTime, Vector3 up, Vector3 desiredCameraOffset,
  235. out Vector3 outTargetPosition, out Quaternion outTargetOrient)
  236. {
  237. var targetOrientation = GetReferenceOrientation(up);
  238. var dampedOrientation = targetOrientation;
  239. bool prevStateValid = deltaTime >= 0 && VirtualCamera.PreviousStateIsValid;
  240. if (prevStateValid)
  241. {
  242. if (m_AngularDampingMode == AngularDampingMode.Quaternion
  243. && m_BindingMode == BindingMode.LockToTarget)
  244. {
  245. float t = VirtualCamera.DetachedFollowTargetDamp(1, m_AngularDamping, deltaTime);
  246. dampedOrientation = Quaternion.Slerp(
  247. m_PreviousReferenceOrientation, targetOrientation, t);
  248. }
  249. else if (m_BindingMode != BindingMode.SimpleFollowWithWorldUp)
  250. {
  251. var relative = (Quaternion.Inverse(m_PreviousReferenceOrientation)
  252. * targetOrientation).eulerAngles;
  253. for (int i = 0; i < 3; ++i)
  254. {
  255. if (relative[i] > 180)
  256. relative[i] -= 360;
  257. if (Mathf.Abs(relative[i]) < 0.01f) // correct for precision drift
  258. relative[i] = 0;
  259. }
  260. relative = VirtualCamera.DetachedFollowTargetDamp(relative, AngularDamping, deltaTime);
  261. dampedOrientation = m_PreviousReferenceOrientation * Quaternion.Euler(relative);
  262. }
  263. }
  264. m_PreviousReferenceOrientation = dampedOrientation;
  265. var targetPosition = FollowTargetPosition;
  266. var currentPosition = m_PreviousTargetPosition;
  267. var previousOffset = prevStateValid ? m_PreviousOffset : desiredCameraOffset;
  268. var offsetDelta = desiredCameraOffset - previousOffset;
  269. if (offsetDelta.sqrMagnitude > 0.01f)
  270. {
  271. var q = UnityVectorExtensions.SafeFromToRotation(
  272. m_PreviousOffset.ProjectOntoPlane(up),
  273. desiredCameraOffset.ProjectOntoPlane(up), up);
  274. currentPosition = targetPosition + q * (m_PreviousTargetPosition - targetPosition);
  275. }
  276. m_PreviousOffset = desiredCameraOffset;
  277. // Adjust for damping, which is done in camera-offset-local coords
  278. var positionDelta = targetPosition - currentPosition;
  279. if (prevStateValid)
  280. {
  281. Quaternion dampingSpace;
  282. if (desiredCameraOffset.AlmostZero())
  283. dampingSpace = VcamState.RawOrientation;
  284. else
  285. dampingSpace = Quaternion.LookRotation(dampedOrientation * desiredCameraOffset, up);
  286. var localDelta = Quaternion.Inverse(dampingSpace) * positionDelta;
  287. localDelta = VirtualCamera.DetachedFollowTargetDamp(localDelta, Damping, deltaTime);
  288. positionDelta = dampingSpace * localDelta;
  289. }
  290. currentPosition += positionDelta;
  291. outTargetPosition = m_PreviousTargetPosition = currentPosition;
  292. outTargetOrient = dampedOrientation;
  293. }
  294. /// <summary>Return a new damped target position that respects the minimum
  295. /// distance from the real target</summary>
  296. /// <param name="dampedTargetPos">The effective position of the target, after damping</param>
  297. /// <param name="cameraOffset">Desired camera offset from target</param>
  298. /// <param name="cameraFwd">Current camera local +Z direction</param>
  299. /// <param name="up">Effective world up</param>
  300. /// <param name="actualTargetPos">The real undamped target position</param>
  301. /// <returns>New camera offset, potentially adjusted to respect minimum distance from target</returns>
  302. protected Vector3 GetOffsetForMinimumTargetDistance(
  303. Vector3 dampedTargetPos, Vector3 cameraOffset,
  304. Vector3 cameraFwd, Vector3 up, Vector3 actualTargetPos)
  305. {
  306. var posOffset = Vector3.zero;
  307. if (VirtualCamera.FollowTargetAttachment > 1 - Epsilon)
  308. {
  309. cameraOffset = cameraOffset.ProjectOntoPlane(up);
  310. var minDistance = cameraOffset.magnitude * 0.2f;
  311. if (minDistance > 0)
  312. {
  313. actualTargetPos = actualTargetPos.ProjectOntoPlane(up);
  314. dampedTargetPos = dampedTargetPos.ProjectOntoPlane(up);
  315. var cameraPos = dampedTargetPos + cameraOffset;
  316. var d = Vector3.Dot(
  317. actualTargetPos - cameraPos,
  318. (dampedTargetPos - cameraPos).normalized);
  319. if (d < minDistance)
  320. {
  321. var dir = actualTargetPos - dampedTargetPos;
  322. var len = dir.magnitude;
  323. if (len < 0.01f)
  324. dir = -cameraFwd.ProjectOntoPlane(up);
  325. else
  326. dir /= len;
  327. posOffset = dir * (minDistance - d);
  328. }
  329. m_PreviousTargetPosition += posOffset;
  330. }
  331. }
  332. return posOffset;
  333. }
  334. /// <summary>
  335. /// Damping speeds for each of the 3 axes of the offset from target
  336. /// </summary>
  337. protected Vector3 Damping
  338. {
  339. get
  340. {
  341. switch (m_BindingMode)
  342. {
  343. case BindingMode.SimpleFollowWithWorldUp:
  344. return new Vector3(0, m_YDamping, m_ZDamping);
  345. default:
  346. return new Vector3(m_XDamping, m_YDamping, m_ZDamping);
  347. }
  348. }
  349. }
  350. /// <summary>
  351. /// Damping speeds for each of the 3 axes of the target's rotation
  352. /// </summary>
  353. protected Vector3 AngularDamping
  354. {
  355. get
  356. {
  357. switch (m_BindingMode)
  358. {
  359. case BindingMode.LockToTargetNoRoll:
  360. return new Vector3(m_PitchDamping, m_YawDamping, 0);
  361. case BindingMode.LockToTargetWithWorldUp:
  362. return new Vector3(0, m_YawDamping, 0);
  363. case BindingMode.LockToTargetOnAssign:
  364. case BindingMode.WorldSpace:
  365. case BindingMode.SimpleFollowWithWorldUp:
  366. return Vector3.zero;
  367. default:
  368. return new Vector3(m_PitchDamping, m_YawDamping, m_RollDamping);
  369. }
  370. }
  371. }
  372. /// <summary>Internal API for the Inspector Editor, so it can draw a marker at the target</summary>
  373. /// <param name="worldUp">Current effective world up</param>
  374. /// <returns>The position of the Follow target</returns>
  375. public virtual Vector3 GetTargetCameraPosition(Vector3 worldUp)
  376. {
  377. if (!IsValid)
  378. return Vector3.zero;
  379. return FollowTargetPosition + GetReferenceOrientation(worldUp) * EffectiveOffset;
  380. }
  381. /// <summary>State information for damping</summary>
  382. Vector3 m_PreviousTargetPosition = Vector3.zero;
  383. Quaternion m_PreviousReferenceOrientation = Quaternion.identity;
  384. Quaternion m_targetOrientationOnAssign = Quaternion.identity;
  385. Vector3 m_PreviousOffset;
  386. Transform m_previousTarget = null;
  387. /// <summary>Internal API for the Inspector Editor, so it can draw a marker at the target</summary>
  388. /// <param name="worldUp">Current effective world up</param>
  389. /// <returns>The rotation of the Follow target, as understood by the Transposer.
  390. /// This is not necessarily the same thing as the actual target rotation</returns>
  391. public Quaternion GetReferenceOrientation(Vector3 worldUp)
  392. {
  393. if (m_BindingMode == BindingMode.WorldSpace)
  394. return Quaternion.identity;
  395. if (FollowTarget != null)
  396. {
  397. Quaternion targetOrientation = FollowTarget.rotation;
  398. switch (m_BindingMode)
  399. {
  400. case BindingMode.LockToTargetOnAssign:
  401. return m_targetOrientationOnAssign;
  402. case BindingMode.LockToTargetWithWorldUp:
  403. {
  404. Vector3 fwd = (targetOrientation * Vector3.forward).ProjectOntoPlane(worldUp);
  405. if (fwd.AlmostZero())
  406. break;
  407. return Quaternion.LookRotation(fwd, worldUp);
  408. }
  409. case BindingMode.LockToTargetNoRoll:
  410. return Quaternion.LookRotation(targetOrientation * Vector3.forward, worldUp);
  411. case BindingMode.LockToTarget:
  412. return targetOrientation;
  413. case BindingMode.SimpleFollowWithWorldUp:
  414. {
  415. Vector3 fwd = (FollowTargetPosition - VcamState.RawPosition).ProjectOntoPlane(worldUp);
  416. if (fwd.AlmostZero())
  417. break;
  418. return Quaternion.LookRotation(fwd, worldUp);
  419. }
  420. }
  421. }
  422. // Gimbal lock situation - use previous orientation if it exists
  423. #if UNITY_2019_1_OR_NEWER
  424. return m_PreviousReferenceOrientation.normalized;
  425. #else
  426. return m_PreviousReferenceOrientation.Normalized();
  427. #endif
  428. }
  429. }
  430. }