CinemachineTargetGroup.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. using UnityEngine;
  2. using System;
  3. using Cinemachine.Utility;
  4. using System.Collections.Generic;
  5. namespace Cinemachine
  6. {
  7. /// <summary>
  8. /// Interface representing something that can be used as a vcam target.
  9. /// It has a transform, a bounding box, and a bounding sphere.
  10. /// </summary>
  11. public interface ICinemachineTargetGroup
  12. {
  13. /// <summary>
  14. /// Get the MonoBehaviour's Transform
  15. /// </summary>
  16. Transform Transform { get; }
  17. /// <summary>
  18. /// The axis-aligned bounding box of the group, computed using the targets positions and radii
  19. /// </summary>
  20. Bounds BoundingBox { get; }
  21. /// <summary>
  22. /// The bounding sphere of the group, computed using the targets positions and radii
  23. /// </summary>
  24. BoundingSphere Sphere { get; }
  25. /// <summary>
  26. /// Returns true if the group has no non-zero-weight members
  27. /// </summary>
  28. bool IsEmpty { get; }
  29. /// <summary>The axis-aligned bounding box of the group, in a specific reference frame</summary>
  30. /// <param name="observer">The frame of reference in which to compute the bounding box</param>
  31. /// <returns>The axis-aligned bounding box of the group, in the desired frame of reference</returns>
  32. Bounds GetViewSpaceBoundingBox(Matrix4x4 observer);
  33. /// <summary>
  34. /// Get the local-space angular bounds of the group, from a spoecific point of view.
  35. /// Also returns the z depth range of the members.
  36. /// </summary>
  37. /// <param name="observer">Point of view from which to calculate, and in whose
  38. /// space the return values are</param>
  39. /// <param name="minAngles">The lower bound of the screen angles of the members (degrees)</param>
  40. /// <param name="maxAngles">The upper bound of the screen angles of the members (degrees)</param>
  41. /// <param name="zRange">The min and max depth values of the members, relative to the observer</param>
  42. void GetViewSpaceAngularBounds(
  43. Matrix4x4 observer, out Vector2 minAngles, out Vector2 maxAngles, out Vector2 zRange);
  44. }
  45. /// <summary>Defines a group of target objects, each with a radius and a weight.
  46. /// The weight is used when calculating the average position of the target group.
  47. /// Higher-weighted members of the group will count more.
  48. /// The bounding box is calculated by taking the member positions, weight,
  49. /// and radii into account.
  50. /// </summary>
  51. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  52. [AddComponentMenu("Cinemachine/CinemachineTargetGroup")]
  53. [SaveDuringPlay]
  54. [ExecuteAlways]
  55. [DisallowMultipleComponent]
  56. [HelpURL(Documentation.BaseURL + "manual/CinemachineTargetGroup.html")]
  57. public class CinemachineTargetGroup : MonoBehaviour, ICinemachineTargetGroup
  58. {
  59. /// <summary>Holds the information that represents a member of the group</summary>
  60. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  61. [Serializable] public struct Target
  62. {
  63. /// <summary>The target objects. This object's position and orientation will contribute to the
  64. /// group's average position and orientation, in accordance with its weight</summary>
  65. [Tooltip("The target objects. This object's position and orientation will contribute to the "
  66. + "group's average position and orientation, in accordance with its weight")]
  67. public Transform target;
  68. /// <summary>How much weight to give the target when averaging. Cannot be negative</summary>
  69. [Tooltip("How much weight to give the target when averaging. Cannot be negative")]
  70. public float weight;
  71. /// <summary>The radius of the target, used for calculating the bounding box. Cannot be negative</summary>
  72. [Tooltip("The radius of the target, used for calculating the bounding box. Cannot be negative")]
  73. public float radius;
  74. }
  75. /// <summary>How the group's position is calculated</summary>
  76. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  77. public enum PositionMode
  78. {
  79. ///<summary>Group position will be the center of the group's axis-aligned bounding box</summary>
  80. GroupCenter,
  81. /// <summary>Group position will be the weighted average of the positions of the members</summary>
  82. GroupAverage
  83. }
  84. /// <summary>How the group's position is calculated</summary>
  85. [Tooltip("How the group's position is calculated. Select GroupCenter for the center of the bounding box, "
  86. + "and GroupAverage for a weighted average of the positions of the members.")]
  87. public PositionMode m_PositionMode = PositionMode.GroupCenter;
  88. /// <summary>How the group's orientation is calculated</summary>
  89. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  90. public enum RotationMode
  91. {
  92. /// <summary>Manually set in the group's transform</summary>
  93. Manual,
  94. /// <summary>Weighted average of the orientation of its members.</summary>
  95. GroupAverage
  96. }
  97. /// <summary>How the group's orientation is calculated</summary>
  98. [Tooltip("How the group's rotation is calculated. Select Manual to use the value in the group's transform, "
  99. + "and GroupAverage for a weighted average of the orientations of the members.")]
  100. public RotationMode m_RotationMode = RotationMode.Manual;
  101. /// <summary>This enum defines the options available for the update method.</summary>
  102. public enum UpdateMethod
  103. {
  104. /// <summary>Updated in normal MonoBehaviour Update.</summary>
  105. Update,
  106. /// <summary>Updated in sync with the Physics module, in FixedUpdate</summary>
  107. FixedUpdate,
  108. /// <summary>Updated in MonoBehaviour LateUpdate.</summary>
  109. LateUpdate
  110. };
  111. /// <summary>When to update the group's transform based on the position of the group members</summary>
  112. [Tooltip("When to update the group's transform based on the position of the group members")]
  113. public UpdateMethod m_UpdateMethod = UpdateMethod.LateUpdate;
  114. /// <summary>The target objects, together with their weights and radii, that will
  115. /// contribute to the group's average position, orientation, and size</summary>
  116. [NoSaveDuringPlay]
  117. [Tooltip("The target objects, together with their weights and radii, that will contribute to the "
  118. + "group's average position, orientation, and size.")]
  119. public Target[] m_Targets = Array.Empty<Target>();
  120. float m_MaxWeight;
  121. float m_WeightSum;
  122. Vector3 m_AveragePos;
  123. Bounds m_BoundingBox;
  124. BoundingSphere m_BoundingSphere;
  125. int m_LastUpdateFrame = -1;
  126. // Caches of valid members so we don't keep checking activeInHierarchy
  127. List<int> m_ValidMembers = new List<int>();
  128. List<bool> m_MemberValidity = new List<bool>();
  129. void OnValidate()
  130. {
  131. var count = m_Targets == null ? 0 : m_Targets.Length;
  132. for (int i = 0; i < count; ++i)
  133. {
  134. m_Targets[i].weight = Mathf.Max(0, m_Targets[i].weight);
  135. m_Targets[i].radius = Mathf.Max(0, m_Targets[i].radius);
  136. }
  137. }
  138. void Reset()
  139. {
  140. m_PositionMode = PositionMode.GroupCenter;
  141. m_RotationMode = RotationMode.Manual;
  142. m_UpdateMethod = UpdateMethod.LateUpdate;
  143. m_Targets = Array.Empty<Target>();
  144. }
  145. /// <summary>
  146. /// Get the MonoBehaviour's Transform
  147. /// </summary>
  148. public Transform Transform => transform;
  149. /// <summary>The axis-aligned bounding box of the group, computed using the
  150. /// targets positions and radii</summary>
  151. public Bounds BoundingBox
  152. {
  153. get
  154. {
  155. if (m_LastUpdateFrame != Time.frameCount)
  156. DoUpdate();
  157. return m_BoundingBox;
  158. }
  159. private set => m_BoundingBox = value;
  160. }
  161. /// <summary>The bounding sphere of the group, computed using the
  162. /// targets positions and radii</summary>
  163. public BoundingSphere Sphere
  164. {
  165. get
  166. {
  167. if (m_LastUpdateFrame != Time.frameCount)
  168. DoUpdate();
  169. return m_BoundingSphere;
  170. }
  171. private set => m_BoundingSphere = value;
  172. }
  173. /// <summary>Return true if there are no members with weight > 0. This returns the
  174. /// cached member state and is only valid after a call to DoUpdate(). If members
  175. /// are added or removed after that call, this will not necessarily return
  176. /// correct information before the next update.</summary>
  177. public bool IsEmpty
  178. {
  179. get
  180. {
  181. if (m_LastUpdateFrame != Time.frameCount)
  182. DoUpdate();
  183. return m_ValidMembers.Count == 0;
  184. }
  185. }
  186. /// <summary>Add a member to the group</summary>
  187. /// <param name="t">The member to add</param>
  188. /// <param name="weight">The new member's weight</param>
  189. /// <param name="radius">The new member's radius</param>
  190. public void AddMember(Transform t, float weight, float radius)
  191. {
  192. int index = 0;
  193. if (m_Targets == null)
  194. m_Targets = new Target[1];
  195. else
  196. {
  197. index = m_Targets.Length;
  198. var oldTargets = m_Targets;
  199. m_Targets = new Target[index + 1];
  200. Array.Copy(oldTargets, m_Targets, index);
  201. }
  202. m_Targets[index].target = t;
  203. m_Targets[index].weight = weight;
  204. m_Targets[index].radius = radius;
  205. }
  206. /// <summary>Remove a member from the group</summary>
  207. /// <param name="t">The member to remove</param>
  208. public void RemoveMember(Transform t)
  209. {
  210. int index = FindMember(t);
  211. if (index >= 0)
  212. {
  213. var oldTargets = m_Targets;
  214. m_Targets = new Target[m_Targets.Length - 1];
  215. if (index > 0)
  216. Array.Copy(oldTargets, m_Targets, index);
  217. if (index < oldTargets.Length - 1)
  218. Array.Copy(oldTargets, index + 1, m_Targets, index, oldTargets.Length - index - 1);
  219. }
  220. }
  221. /// <summary>Locate a member's index in the group.</summary>
  222. /// <param name="t">The member to find</param>
  223. /// <returns>Member index, or -1 if not a member</returns>
  224. public int FindMember(Transform t)
  225. {
  226. if (m_Targets != null)
  227. {
  228. for (int i = m_Targets.Length-1; i >= 0; --i)
  229. if (m_Targets[i].target == t)
  230. return i;
  231. }
  232. return -1;
  233. }
  234. /// <summary>
  235. /// Get the bounding sphere of a group memebr, with the weight taken into account.
  236. /// As the member's weight goes to 0, the position lerps to the group average position.
  237. /// Note that this result is only valid after DoUpdate has been called. If members
  238. /// are added or removed after that call or change their weights or active state,
  239. /// this will not necessarily return correct information before the next update.
  240. /// </summary>
  241. /// <param name="index">Member index</param>
  242. /// <returns>The weighted bounding sphere</returns>
  243. public BoundingSphere GetWeightedBoundsForMember(int index)
  244. {
  245. if (m_LastUpdateFrame != Time.frameCount)
  246. DoUpdate();
  247. if (!IndexIsValid(index) || !m_MemberValidity[index])
  248. return Sphere;
  249. return WeightedMemberBoundsForValidMember(ref m_Targets[index], m_AveragePos, m_MaxWeight);
  250. }
  251. /// <summary>The axis-aligned bounding box of the group, in a specific reference frame.
  252. /// Note that this result is only valid after DoUpdate has been called. If members
  253. /// are added or removed after that call or change their weights or active state,
  254. /// this will not necessarily return correct information before the next update.</summary>
  255. /// <param name="observer">The frame of reference in which to compute the bounding box</param>
  256. /// <returns>The axis-aligned bounding box of the group, in the desired frame of reference</returns>
  257. public Bounds GetViewSpaceBoundingBox(Matrix4x4 observer)
  258. {
  259. if (m_LastUpdateFrame != Time.frameCount)
  260. DoUpdate();
  261. var inverseView = observer;
  262. if (!Matrix4x4.Inverse3DAffine(observer, ref inverseView))
  263. inverseView = observer.inverse;
  264. var b = new Bounds(inverseView.MultiplyPoint3x4(m_AveragePos), Vector3.zero);
  265. if (CachedCountIsValid)
  266. {
  267. bool gotOne = false;
  268. var unit = 2 * Vector3.one;
  269. var count = m_ValidMembers.Count;
  270. for (int i = 0; i < count; ++i)
  271. {
  272. var s = WeightedMemberBoundsForValidMember(ref m_Targets[m_ValidMembers[i]], m_AveragePos, m_MaxWeight);
  273. s.position = inverseView.MultiplyPoint3x4(s.position);
  274. if (gotOne)
  275. b.Encapsulate(new Bounds(s.position, s.radius * unit));
  276. else
  277. b = new Bounds(s.position, s.radius * unit);
  278. gotOne = true;
  279. }
  280. }
  281. return b;
  282. }
  283. bool CachedCountIsValid => m_MemberValidity.Count == (m_Targets == null ? 0 : m_Targets.Length);
  284. bool IndexIsValid(int index) => index >= 0 && m_Targets != null && index < m_Targets.Length && CachedCountIsValid;
  285. static BoundingSphere WeightedMemberBoundsForValidMember(ref Target t, Vector3 avgPos, float maxWeight)
  286. {
  287. var pos = t.target == null ? avgPos : TargetPositionCache.GetTargetPosition(t.target);
  288. var w = Mathf.Max(0, t.weight);
  289. if (maxWeight > UnityVectorExtensions.Epsilon && w < maxWeight)
  290. w /= maxWeight;
  291. else
  292. w = 1;
  293. return new BoundingSphere(Vector3.Lerp(avgPos, pos, w), t.radius * w);
  294. }
  295. /// <summary>
  296. /// Update the group's transform right now, depending on the transforms of the members.
  297. /// Normally this is called automatically by Update() or LateUpdate().
  298. /// </summary>
  299. public void DoUpdate()
  300. {
  301. m_LastUpdateFrame = Time.frameCount;
  302. UpdateMemberValidity();
  303. m_AveragePos = CalculateAveragePosition();
  304. BoundingBox = CalculateBoundingBox();
  305. m_BoundingSphere = CalculateBoundingSphere();
  306. switch (m_PositionMode)
  307. {
  308. case PositionMode.GroupCenter:
  309. transform.position = Sphere.position;
  310. break;
  311. case PositionMode.GroupAverage:
  312. transform.position = m_AveragePos;
  313. break;
  314. }
  315. switch (m_RotationMode)
  316. {
  317. case RotationMode.Manual:
  318. break;
  319. case RotationMode.GroupAverage:
  320. transform.rotation = CalculateAverageOrientation();
  321. break;
  322. }
  323. }
  324. void UpdateMemberValidity()
  325. {
  326. int count = m_Targets == null ? 0 : m_Targets.Length;
  327. m_ValidMembers.Clear();
  328. m_ValidMembers.Capacity = Mathf.Max(m_ValidMembers.Capacity, count);
  329. m_MemberValidity.Clear();
  330. m_MemberValidity.Capacity = Mathf.Max(m_MemberValidity.Capacity, count);
  331. m_WeightSum = m_MaxWeight = 0;
  332. for (int i = 0; i < count; ++i)
  333. {
  334. m_MemberValidity.Add(m_Targets[i].target != null
  335. && m_Targets[i].weight > UnityVectorExtensions.Epsilon
  336. && m_Targets[i].target.gameObject.activeInHierarchy);
  337. if (m_MemberValidity[i])
  338. {
  339. m_ValidMembers.Add(i);
  340. m_MaxWeight = Mathf.Max(m_MaxWeight, m_Targets[i].weight);
  341. m_WeightSum += m_Targets[i].weight;
  342. }
  343. }
  344. }
  345. // Assumes that UpdateMemberValidity() has been called
  346. Vector3 CalculateAveragePosition()
  347. {
  348. if (m_WeightSum < UnityVectorExtensions.Epsilon)
  349. return transform.position;
  350. var pos = Vector3.zero;
  351. var count = m_ValidMembers.Count;
  352. for (int i = 0; i < count; ++i)
  353. {
  354. var targetIndex = m_ValidMembers[i];
  355. var weight = m_Targets[targetIndex].weight;
  356. pos += TargetPositionCache.GetTargetPosition(m_Targets[targetIndex].target) * weight;
  357. }
  358. return pos / m_WeightSum;
  359. }
  360. // Assumes that UpdateMemberValidity() has been called
  361. Bounds CalculateBoundingBox()
  362. {
  363. if (m_MaxWeight < UnityVectorExtensions.Epsilon)
  364. return BoundingBox;
  365. var b = new Bounds(m_AveragePos, Vector3.zero);
  366. var count = m_ValidMembers.Count;
  367. for (int i = 0; i < count; ++i)
  368. {
  369. var s = WeightedMemberBoundsForValidMember(ref m_Targets[m_ValidMembers[i]], m_AveragePos, m_MaxWeight);
  370. b.Encapsulate(new Bounds(s.position, s.radius * 2 * Vector3.one));
  371. }
  372. return b;
  373. }
  374. /// <summary>
  375. /// Use Ritter's algorithm for calculating an approximate bounding sphere.
  376. /// Assumes that UpdateMemberValidity() has been called.
  377. /// </summary>
  378. /// <param name="maxWeight">The maximum weight of members in the group</param>
  379. /// <returns>An approximate bounding sphere. Will be slightly large.</returns>
  380. BoundingSphere CalculateBoundingSphere()
  381. {
  382. var count = m_ValidMembers.Count;
  383. if (count == 0 || m_MaxWeight < UnityVectorExtensions.Epsilon)
  384. return m_BoundingSphere;
  385. var sphere = WeightedMemberBoundsForValidMember(ref m_Targets[m_ValidMembers[0]], m_AveragePos, m_MaxWeight);
  386. for (int i = 1; i < count; ++i)
  387. {
  388. var s = WeightedMemberBoundsForValidMember(ref m_Targets[m_ValidMembers[i]], m_AveragePos, m_MaxWeight);
  389. var distance = (s.position - sphere.position).magnitude + s.radius;
  390. if (distance > sphere.radius)
  391. {
  392. // Point is outside current sphere: update
  393. sphere.radius = (sphere.radius + distance) * 0.5f;
  394. sphere.position = (sphere.radius * sphere.position + (distance - sphere.radius) * s.position) / distance;
  395. }
  396. }
  397. return sphere;
  398. }
  399. /// Assumes that UpdateMemberValidity() has been called.
  400. Quaternion CalculateAverageOrientation()
  401. {
  402. if (m_WeightSum > 0.001f)
  403. {
  404. var averageForward = Vector3.zero;
  405. var averageUp = Vector3.zero;
  406. var count = m_ValidMembers.Count;
  407. for (int i = 0; i < count; ++i)
  408. {
  409. var targetIndex = m_ValidMembers[i];
  410. var scaledWeight = m_Targets[targetIndex].weight / m_WeightSum;
  411. var rot = TargetPositionCache.GetTargetRotation(m_Targets[targetIndex].target);
  412. averageForward += rot * Vector3.forward * scaledWeight;
  413. averageUp += rot * Vector3.up * scaledWeight;
  414. }
  415. if (averageForward.sqrMagnitude > 0.0001f && averageUp.sqrMagnitude > 0.0001f)
  416. return Quaternion.LookRotation(averageForward, averageUp);
  417. }
  418. return transform.rotation;
  419. }
  420. void FixedUpdate()
  421. {
  422. if (m_UpdateMethod == UpdateMethod.FixedUpdate)
  423. DoUpdate();
  424. }
  425. void Update()
  426. {
  427. if (!Application.isPlaying || m_UpdateMethod == UpdateMethod.Update)
  428. DoUpdate();
  429. }
  430. void LateUpdate()
  431. {
  432. if (m_UpdateMethod == UpdateMethod.LateUpdate)
  433. DoUpdate();
  434. }
  435. /// <summary>
  436. /// Get the local-space angular bounds of the group, from a specific point of view.
  437. /// Also returns the z depth range of the members.
  438. /// Note that this result is only valid after DoUpdate has been called. If members
  439. /// are added or removed after that call or change their weights or active state,
  440. /// this will not necessarily return correct information before the next update.
  441. /// </summary>
  442. /// <param name="observer">Point of view from which to calculate, and in whose
  443. /// space the return values are</param>
  444. /// <param name="minAngles">The lower bound of the screen angles of the members (degrees)</param>
  445. /// <param name="maxAngles">The upper bound of the screen angles of the members (degrees)</param>
  446. /// <param name="zRange">The min and max depth values of the members, relative to the observer</param>
  447. public void GetViewSpaceAngularBounds(
  448. Matrix4x4 observer, out Vector2 minAngles, out Vector2 maxAngles, out Vector2 zRange)
  449. {
  450. if (m_LastUpdateFrame != Time.frameCount)
  451. DoUpdate();
  452. var world2local = observer;
  453. if (!Matrix4x4.Inverse3DAffine(observer, ref world2local))
  454. world2local = observer.inverse;
  455. var r = m_BoundingSphere.radius;
  456. var b = new Bounds() { center = world2local.MultiplyPoint3x4(m_AveragePos), extents = new Vector3(r, r, r) };
  457. zRange = new Vector2(b.center.z - r, b.center.z + r);
  458. if (CachedCountIsValid)
  459. {
  460. bool haveOne = false;
  461. var count = m_ValidMembers.Count;
  462. for (int i = 0; i < count; ++i)
  463. {
  464. var s = WeightedMemberBoundsForValidMember(ref m_Targets[m_ValidMembers[i]], m_AveragePos, m_MaxWeight);
  465. var p = world2local.MultiplyPoint3x4(s.position);
  466. if (p.z < UnityVectorExtensions.Epsilon)
  467. continue; // behind us
  468. var rN = s.radius / p.z;
  469. var rN2 = new Vector3(rN, rN, 0);
  470. var pN = p / p.z;
  471. if (!haveOne)
  472. {
  473. b.center = pN;
  474. b.extents = rN2;
  475. zRange = new Vector2(p.z, p.z);
  476. haveOne = true;
  477. }
  478. else
  479. {
  480. b.Encapsulate(pN + rN2);
  481. b.Encapsulate(pN - rN2);
  482. zRange.x = Mathf.Min(zRange.x, p.z);
  483. zRange.y = Mathf.Max(zRange.y, p.z);
  484. }
  485. }
  486. }
  487. // Don't need the high-precision versions of UnityVectorExtensions.SignedAngle
  488. var pMin = b.min;
  489. var pMax = b.max;
  490. minAngles = new Vector2(
  491. Vector3.SignedAngle(Vector3.forward, new Vector3(0, pMin.y, 1), Vector3.left),
  492. Vector3.SignedAngle(Vector3.forward, new Vector3(pMin.x, 0, 1), Vector3.up));
  493. maxAngles = new Vector2(
  494. Vector3.SignedAngle(Vector3.forward, new Vector3(0, pMax.y, 1), Vector3.left),
  495. Vector3.SignedAngle(Vector3.forward, new Vector3(pMax.x, 0, 1), Vector3.up));
  496. }
  497. }
  498. }