CinemachineTransposer.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  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. // Respect minimum target distance on XZ plane
  177. var targetPosition = FollowTargetPosition;
  178. pos += GetOffsetForMinimumTargetDistance(
  179. pos, offset, curState.RawOrientation * Vector3.forward,
  180. curState.ReferenceUp, targetPosition);
  181. curState.RawPosition = pos + offset;
  182. curState.ReferenceUp = orient * Vector3.up;
  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
  220. = (m_previousTarget == null) ? Quaternion.identity : FollowTargetRotation;
  221. }
  222. if (!prevStateValid)
  223. {
  224. m_PreviousTargetPosition = FollowTargetPosition;
  225. m_PreviousReferenceOrientation = GetReferenceOrientation(curState.ReferenceUp);
  226. }
  227. }
  228. /// <summary>Positions the virtual camera according to the transposer rules.</summary>
  229. /// <param name="deltaTime">Used for damping. If less than 0, no damping is done.</param>
  230. /// <param name="up">Current camera up</param>
  231. /// <param name="desiredCameraOffset">Where we want to put the camera relative to the follow target</param>
  232. /// <param name="outTargetPosition">Resulting camera position</param>
  233. /// <param name="outTargetOrient">Damped target orientation</param>
  234. protected void TrackTarget(
  235. float deltaTime, Vector3 up, Vector3 desiredCameraOffset,
  236. out Vector3 outTargetPosition, out Quaternion outTargetOrient)
  237. {
  238. var targetOrientation = GetReferenceOrientation(up);
  239. var dampedOrientation = targetOrientation;
  240. bool prevStateValid = deltaTime >= 0 && VirtualCamera.PreviousStateIsValid;
  241. if (prevStateValid)
  242. {
  243. if (m_AngularDampingMode == AngularDampingMode.Quaternion
  244. && m_BindingMode == BindingMode.LockToTarget)
  245. {
  246. float t = VirtualCamera.DetachedFollowTargetDamp(1, m_AngularDamping, deltaTime);
  247. dampedOrientation = Quaternion.Slerp(
  248. m_PreviousReferenceOrientation, targetOrientation, t);
  249. }
  250. else
  251. {
  252. var relative = (Quaternion.Inverse(m_PreviousReferenceOrientation)
  253. * targetOrientation).eulerAngles;
  254. for (int i = 0; i < 3; ++i)
  255. if (relative[i] > 180)
  256. relative[i] -= 360;
  257. relative = VirtualCamera.DetachedFollowTargetDamp(relative, AngularDamping, deltaTime);
  258. dampedOrientation = m_PreviousReferenceOrientation * Quaternion.Euler(relative);
  259. }
  260. }
  261. m_PreviousReferenceOrientation = dampedOrientation;
  262. var targetPosition = FollowTargetPosition;
  263. var currentPosition = m_PreviousTargetPosition;
  264. var previousOffset = prevStateValid ? m_PreviousOffset : desiredCameraOffset;
  265. var offsetDelta = desiredCameraOffset - previousOffset;
  266. if (offsetDelta.sqrMagnitude > 0.01f)
  267. {
  268. var q = UnityVectorExtensions.SafeFromToRotation(
  269. m_PreviousOffset.ProjectOntoPlane(up),
  270. desiredCameraOffset.ProjectOntoPlane(up), up);
  271. currentPosition = targetPosition + q * (m_PreviousTargetPosition - targetPosition);
  272. }
  273. m_PreviousOffset = desiredCameraOffset;
  274. // Adjust for damping, which is done in camera-offset-local coords
  275. var positionDelta = targetPosition - currentPosition;
  276. if (prevStateValid)
  277. {
  278. Quaternion dampingSpace;
  279. if (desiredCameraOffset.AlmostZero())
  280. dampingSpace = VcamState.RawOrientation;
  281. else
  282. dampingSpace = Quaternion.LookRotation(dampedOrientation * desiredCameraOffset, up);
  283. var localDelta = Quaternion.Inverse(dampingSpace) * positionDelta;
  284. localDelta = VirtualCamera.DetachedFollowTargetDamp(localDelta, Damping, deltaTime);
  285. positionDelta = dampingSpace * localDelta;
  286. }
  287. currentPosition += positionDelta;
  288. outTargetPosition = m_PreviousTargetPosition = currentPosition;
  289. outTargetOrient = dampedOrientation;
  290. }
  291. /// <summary>Return a new damped target position that respects the minimum
  292. /// distance from the real target</summary>
  293. /// <param name="dampedTargetPos">The effective position of the target, after damping</param>
  294. /// <param name="cameraOffset">Desired camera offset from target</param>
  295. /// <param name="cameraFwd">Current camera local +Z direction</param>
  296. /// <param name="up">Effective world up</param>
  297. /// <param name="actualTargetPos">The real undamped target position</param>
  298. /// <returns>New camera offset, potentially adjusted to respect minimum distance from target</returns>
  299. protected Vector3 GetOffsetForMinimumTargetDistance(
  300. Vector3 dampedTargetPos, Vector3 cameraOffset,
  301. Vector3 cameraFwd, Vector3 up, Vector3 actualTargetPos)
  302. {
  303. var posOffset = Vector3.zero;
  304. if (VirtualCamera.FollowTargetAttachment > 1 - Epsilon)
  305. {
  306. cameraOffset = cameraOffset.ProjectOntoPlane(up);
  307. var minDistance = cameraOffset.magnitude * 0.2f;
  308. if (minDistance > 0)
  309. {
  310. actualTargetPos = actualTargetPos.ProjectOntoPlane(up);
  311. dampedTargetPos = dampedTargetPos.ProjectOntoPlane(up);
  312. var cameraPos = dampedTargetPos + cameraOffset;
  313. var d = Vector3.Dot(
  314. actualTargetPos - cameraPos,
  315. (dampedTargetPos - cameraPos).normalized);
  316. if (d < minDistance)
  317. {
  318. var dir = actualTargetPos - dampedTargetPos;
  319. var len = dir.magnitude;
  320. if (len < 0.01f)
  321. dir = -cameraFwd.ProjectOntoPlane(up);
  322. else
  323. dir /= len;
  324. posOffset = dir * (minDistance - d);
  325. }
  326. m_PreviousTargetPosition += posOffset;
  327. }
  328. }
  329. return posOffset;
  330. }
  331. /// <summary>
  332. /// Damping speeds for each of the 3 axes of the offset from target
  333. /// </summary>
  334. protected Vector3 Damping
  335. {
  336. get
  337. {
  338. switch (m_BindingMode)
  339. {
  340. case BindingMode.SimpleFollowWithWorldUp:
  341. return new Vector3(0, m_YDamping, m_ZDamping);
  342. default:
  343. return new Vector3(m_XDamping, m_YDamping, m_ZDamping);
  344. }
  345. }
  346. }
  347. /// <summary>
  348. /// Damping speeds for each of the 3 axes of the target's rotation
  349. /// </summary>
  350. protected Vector3 AngularDamping
  351. {
  352. get
  353. {
  354. switch (m_BindingMode)
  355. {
  356. case BindingMode.LockToTargetNoRoll:
  357. return new Vector3(m_PitchDamping, m_YawDamping, 0);
  358. case BindingMode.LockToTargetWithWorldUp:
  359. return new Vector3(0, m_YawDamping, 0);
  360. case BindingMode.LockToTargetOnAssign:
  361. case BindingMode.WorldSpace:
  362. case BindingMode.SimpleFollowWithWorldUp:
  363. return Vector3.zero;
  364. default:
  365. return new Vector3(m_PitchDamping, m_YawDamping, m_RollDamping);
  366. }
  367. }
  368. }
  369. /// <summary>Internal API for the Inspector Editor, so it can draw a marker at the target</summary>
  370. /// <param name="worldUp">Current effective world up</param>
  371. /// <returns>The position of the Follow target</returns>
  372. public virtual Vector3 GetTargetCameraPosition(Vector3 worldUp)
  373. {
  374. if (!IsValid)
  375. return Vector3.zero;
  376. return FollowTargetPosition + GetReferenceOrientation(worldUp) * EffectiveOffset;
  377. }
  378. /// <summary>State information for damping</summary>
  379. Vector3 m_PreviousTargetPosition = Vector3.zero;
  380. Quaternion m_PreviousReferenceOrientation = Quaternion.identity;
  381. Quaternion m_targetOrientationOnAssign = Quaternion.identity;
  382. Vector3 m_PreviousOffset;
  383. Transform m_previousTarget = null;
  384. /// <summary>Internal API for the Inspector Editor, so it can draw a marker at the target</summary>
  385. /// <param name="worldUp">Current effective world up</param>
  386. /// <returns>The rotation of the Follow target, as understood by the Transposer.
  387. /// This is not necessarily the same thing as the actual target rotation</returns>
  388. public Quaternion GetReferenceOrientation(Vector3 worldUp)
  389. {
  390. if (m_BindingMode == BindingMode.WorldSpace)
  391. return Quaternion.identity;
  392. if (FollowTarget != null)
  393. {
  394. Quaternion targetOrientation = FollowTarget.rotation;
  395. switch (m_BindingMode)
  396. {
  397. case BindingMode.LockToTargetOnAssign:
  398. return m_targetOrientationOnAssign;
  399. case BindingMode.LockToTargetWithWorldUp:
  400. {
  401. Vector3 fwd = (targetOrientation * Vector3.forward).ProjectOntoPlane(worldUp);
  402. if (fwd.AlmostZero())
  403. break;
  404. return Quaternion.LookRotation(fwd, worldUp);
  405. }
  406. case BindingMode.LockToTargetNoRoll:
  407. return Quaternion.LookRotation(targetOrientation * Vector3.forward, worldUp);
  408. case BindingMode.LockToTarget:
  409. return targetOrientation;
  410. case BindingMode.SimpleFollowWithWorldUp:
  411. {
  412. Vector3 fwd = (FollowTargetPosition - VcamState.RawPosition).ProjectOntoPlane(worldUp);
  413. if (fwd.AlmostZero())
  414. break;
  415. return Quaternion.LookRotation(fwd, worldUp);
  416. }
  417. }
  418. }
  419. // Gimbal lock situation - use previous orientation if it exists
  420. #if UNITY_2019_1_OR_NEWER
  421. return m_PreviousReferenceOrientation.normalized;
  422. #else
  423. return m_PreviousReferenceOrientation.Normalized();
  424. #endif
  425. }
  426. }
  427. }