CinemachineTargetGroup.cs 20 KB

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