CinemachineCollider.cs 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829
  1. #if !UNITY_2019_3_OR_NEWER
  2. #define CINEMACHINE_PHYSICS
  3. #endif
  4. using UnityEngine;
  5. using System.Collections.Generic;
  6. using Cinemachine.Utility;
  7. using UnityEngine.Serialization;
  8. using System;
  9. namespace Cinemachine
  10. {
  11. #if CINEMACHINE_PHYSICS
  12. /// <summary>
  13. /// An add-on module for Cinemachine Virtual Camera that post-processes
  14. /// the final position of the virtual camera. Based on the supplied settings,
  15. /// the Collider will attempt to preserve the line of sight
  16. /// with the LookAt target of the virtual camera by moving
  17. /// away from objects that will obstruct the view.
  18. ///
  19. /// Additionally, the Collider can be used to assess the shot quality and
  20. /// report this as a field in the camera State.
  21. /// </summary>
  22. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  23. [AddComponentMenu("")] // Hide in menu
  24. [SaveDuringPlay]
  25. [ExecuteAlways]
  26. [DisallowMultipleComponent]
  27. [HelpURL(Documentation.BaseURL + "manual/CinemachineCollider.html")]
  28. public class CinemachineCollider : CinemachineExtension
  29. {
  30. /// <summary>Objects on these layers will be detected.</summary>
  31. [Header("Obstacle Detection")]
  32. [Tooltip("Objects on these layers will be detected")]
  33. public LayerMask m_CollideAgainst = 1;
  34. /// <summary>Obstacles with this tag will be ignored. It is a good idea to set this field to the target's tag</summary>
  35. [TagField]
  36. [Tooltip("Obstacles with this tag will be ignored. It is a good idea to set this field to the target's tag")]
  37. public string m_IgnoreTag = string.Empty;
  38. /// <summary>Objects on these layers will never obstruct view of the target.</summary>
  39. [Tooltip("Objects on these layers will never obstruct view of the target")]
  40. public LayerMask m_TransparentLayers = 0;
  41. /// <summary>Obstacles closer to the target than this will be ignored</summary>
  42. [Tooltip("Obstacles closer to the target than this will be ignored")]
  43. public float m_MinimumDistanceFromTarget = 0.1f;
  44. /// <summary>
  45. /// When enabled, will attempt to resolve situations where the line of sight to the
  46. /// target is blocked by an obstacle
  47. /// </summary>
  48. [Space]
  49. [Tooltip("When enabled, will attempt to resolve situations where the line of sight "
  50. + "to the target is blocked by an obstacle")]
  51. [FormerlySerializedAs("m_PreserveLineOfSight")]
  52. public bool m_AvoidObstacles = true;
  53. /// <summary>
  54. /// The raycast distance to test for when checking if the line of sight to this camera's target is clear.
  55. /// </summary>
  56. [Tooltip("The maximum raycast distance when checking if the line of sight to this camera's target is clear. "
  57. + "If the setting is 0 or less, the current actual distance to target will be used.")]
  58. [FormerlySerializedAs("m_LineOfSightFeelerDistance")]
  59. public float m_DistanceLimit;
  60. /// <summary>
  61. /// Don't take action unless occlusion has lasted at least this long.
  62. /// </summary>
  63. [Tooltip("Don't take action unless occlusion has lasted at least this long.")]
  64. public float m_MinimumOcclusionTime;
  65. /// <summary>
  66. /// Camera will try to maintain this distance from any obstacle.
  67. /// Increase this value if you are seeing inside obstacles due to a large
  68. /// FOV on the camera.
  69. /// </summary>
  70. [Tooltip("Camera will try to maintain this distance from any obstacle. Try to keep this value small. "
  71. + "Increase it if you are seeing inside obstacles due to a large FOV on the camera.")]
  72. public float m_CameraRadius = 0.1f;
  73. /// <summary>The way in which the Collider will attempt to preserve sight of the target.</summary>
  74. public enum ResolutionStrategy
  75. {
  76. /// <summary>Camera will be pulled forward along its Z axis until it is in front of
  77. /// the nearest obstacle</summary>
  78. PullCameraForward,
  79. /// <summary>In addition to pulling the camera forward, an effort will be made to
  80. /// return the camera to its original height</summary>
  81. PreserveCameraHeight,
  82. /// <summary>In addition to pulling the camera forward, an effort will be made to
  83. /// return the camera to its original distance from the target</summary>
  84. PreserveCameraDistance
  85. };
  86. /// <summary>The way in which the Collider will attempt to preserve sight of the target.</summary>
  87. [Tooltip("The way in which the Collider will attempt to preserve sight of the target.")]
  88. public ResolutionStrategy m_Strategy = ResolutionStrategy.PreserveCameraHeight;
  89. /// <summary>
  90. /// Upper limit on how many obstacle hits to process. Higher numbers may impact performance.
  91. /// In most environments, 4 is enough.
  92. /// </summary>
  93. [Range(1, 10)]
  94. [Tooltip("Upper limit on how many obstacle hits to process. Higher numbers may impact performance. "
  95. + "In most environments, 4 is enough.")]
  96. public int m_MaximumEffort = 4;
  97. /// <summary>
  98. /// Smoothing to apply to obstruction resolution. Nearest camera point is held for at least this long.
  99. /// </summary>
  100. [Range(0, 2)]
  101. [Tooltip("Smoothing to apply to obstruction resolution. Nearest camera point is held for at least this long")]
  102. public float m_SmoothingTime;
  103. /// <summary>
  104. /// How gradually the camera returns to its normal position after having been corrected.
  105. /// Higher numbers will move the camera more gradually back to normal.
  106. /// </summary>
  107. [Range(0, 10)]
  108. [Tooltip("How gradually the camera returns to its normal position after having been corrected. "
  109. + "Higher numbers will move the camera more gradually back to normal.")]
  110. [FormerlySerializedAs("m_Smoothing")]
  111. public float m_Damping;
  112. /// <summary>
  113. /// How gradually the camera moves to resolve an occlusion.
  114. /// Higher numbers will move the camera more gradually.
  115. /// </summary>
  116. [Range(0, 10)]
  117. [Tooltip("How gradually the camera moves to resolve an occlusion. "
  118. + "Higher numbers will move the camera more gradually.")]
  119. public float m_DampingWhenOccluded;
  120. /// <summary>If greater than zero, a higher score will be given to shots when the target is closer to
  121. /// this distance. Set this to zero to disable this feature</summary>
  122. [Header("Shot Evaluation")]
  123. [Tooltip("If greater than zero, a higher score will be given to shots when the target is closer to this distance. "
  124. + "Set this to zero to disable this feature.")]
  125. public float m_OptimalTargetDistance;
  126. /// <summary>See whether an object is blocking the camera's view of the target</summary>
  127. /// <param name="vcam">The virtual camera in question. This might be different from the
  128. /// virtual camera that owns the collider, in the event that the camera has children</param>
  129. /// <returns>True if something is blocking the view</returns>
  130. public bool IsTargetObscured(ICinemachineCamera vcam)
  131. {
  132. return GetExtraState<VcamExtraState>(vcam).targetObscured;
  133. }
  134. /// <summary>See whether the virtual camera has been moved nby the collider</summary>
  135. /// <param name="vcam">The virtual camera in question. This might be different from the
  136. /// virtual camera that owns the collider, in the event that the camera has children</param>
  137. /// <returns>True if the virtual camera has been displaced due to collision or
  138. /// target obstruction</returns>
  139. public bool CameraWasDisplaced(ICinemachineCamera vcam)
  140. {
  141. return GetCameraDisplacementDistance(vcam) > 0;
  142. }
  143. /// <summary>See how far the virtual camera wa moved nby the collider</summary>
  144. /// <param name="vcam">The virtual camera in question. This might be different from the
  145. /// virtual camera that owns the collider, in the event that the camera has children</param>
  146. /// <returns>True if the virtual camera has been displaced due to collision or
  147. /// target obstruction</returns>
  148. public float GetCameraDisplacementDistance(ICinemachineCamera vcam)
  149. {
  150. return GetExtraState<VcamExtraState>(vcam).previousDisplacement.magnitude;
  151. }
  152. void OnValidate()
  153. {
  154. m_DistanceLimit = Mathf.Max(0, m_DistanceLimit);
  155. m_MinimumOcclusionTime = Mathf.Max(0, m_MinimumOcclusionTime);
  156. m_CameraRadius = Mathf.Max(0, m_CameraRadius);
  157. m_MinimumDistanceFromTarget = Mathf.Max(0.01f, m_MinimumDistanceFromTarget);
  158. m_OptimalTargetDistance = Mathf.Max(0, m_OptimalTargetDistance);
  159. }
  160. /// <summary>
  161. /// Cleanup
  162. /// </summary>
  163. protected override void OnDestroy()
  164. {
  165. RuntimeUtility.DestroyScratchCollider();
  166. base.OnDestroy();
  167. }
  168. /// This must be small but greater than 0 - reduces false results due to precision
  169. const float k_PrecisionSlush = 0.001f;
  170. /// <summary>
  171. /// Per-vcam extra state info
  172. /// </summary>
  173. class VcamExtraState
  174. {
  175. public Vector3 previousDisplacement;
  176. public Vector3 previousCameraOffset;
  177. public Vector3 previousCameraPosition;
  178. public float previousDampTime;
  179. public bool targetObscured;
  180. public float occlusionStartTime;
  181. public List<Vector3> debugResolutionPath;
  182. public void AddPointToDebugPath(Vector3 p)
  183. {
  184. #if UNITY_EDITOR
  185. if (debugResolutionPath == null)
  186. debugResolutionPath = new List<Vector3>();
  187. debugResolutionPath.Add(p);
  188. #endif
  189. }
  190. // Thanks to Sebastien LeTouze from Exiin Studio for the smoothing idea
  191. float m_SmoothedDistance;
  192. float m_SmoothedTime;
  193. public float ApplyDistanceSmoothing(float distance, float smoothingTime)
  194. {
  195. if (m_SmoothedTime != 0 && smoothingTime > Epsilon)
  196. {
  197. float now = CinemachineCore.CurrentTime;
  198. if (now - m_SmoothedTime < smoothingTime)
  199. return Mathf.Min(distance, m_SmoothedDistance);
  200. }
  201. return distance;
  202. }
  203. public void UpdateDistanceSmoothing(float distance)
  204. {
  205. if (m_SmoothedDistance == 0 || distance < m_SmoothedDistance)
  206. {
  207. m_SmoothedDistance = distance;
  208. m_SmoothedTime = CinemachineCore.CurrentTime;
  209. }
  210. }
  211. public void ResetDistanceSmoothing(float smoothingTime)
  212. {
  213. float now = CinemachineCore.CurrentTime;
  214. if (now - m_SmoothedTime >= smoothingTime)
  215. m_SmoothedDistance = m_SmoothedTime = 0;
  216. }
  217. };
  218. /// <summary>Inspector API for debugging collision resolution path</summary>
  219. public List<List<Vector3>> DebugPaths
  220. {
  221. get
  222. {
  223. List<List<Vector3>> list = new List<List<Vector3>>();
  224. List<VcamExtraState> extraStates = GetAllExtraStates<VcamExtraState>();
  225. foreach (var v in extraStates)
  226. if (v.debugResolutionPath != null && v.debugResolutionPath.Count > 0)
  227. list.Add(v.debugResolutionPath);
  228. return list;
  229. }
  230. }
  231. /// <summary>
  232. /// Report maximum damping time needed for this component.
  233. /// </summary>
  234. /// <returns>Highest damping setting in this component</returns>
  235. public override float GetMaxDampTime()
  236. {
  237. return Mathf.Max(m_Damping, Mathf.Max(m_DampingWhenOccluded, m_SmoothingTime));
  238. }
  239. /// <summary>This is called to notify the extension that a target got warped,
  240. /// so that the extension can update its internal state to make the camera
  241. /// also warp seamlessy. Base class implementation does nothing.</summary>
  242. /// <param name="target">The object that was warped</param>
  243. /// <param name="positionDelta">The amount the target's position changed</param>
  244. public override void OnTargetObjectWarped(Transform target, Vector3 positionDelta)
  245. {
  246. var states = GetAllExtraStates<VcamExtraState>();
  247. for (int i = 0; i < states.Count; ++i)
  248. {
  249. var extra = states[i];
  250. extra.previousCameraPosition += positionDelta;
  251. }
  252. }
  253. /// <summary>
  254. /// Callback to do the collision resolution and shot evaluation
  255. /// </summary>
  256. /// <param name="vcam">The virtual camera being processed</param>
  257. /// <param name="stage">The current pipeline stage</param>
  258. /// <param name="state">The current virtual camera state</param>
  259. /// <param name="deltaTime">The current applicable deltaTime</param>
  260. protected override void PostPipelineStageCallback(
  261. CinemachineVirtualCameraBase vcam,
  262. CinemachineCore.Stage stage, ref CameraState state, float deltaTime)
  263. {
  264. if (stage == CinemachineCore.Stage.Body)
  265. {
  266. var extra = GetExtraState<VcamExtraState>(vcam);
  267. extra.targetObscured = false;
  268. extra.debugResolutionPath?.RemoveRange(0, extra.debugResolutionPath.Count);
  269. if (m_AvoidObstacles)
  270. {
  271. var initialCamPos = state.CorrectedPosition;
  272. // Rotate the previous collision correction along with the camera
  273. var dampingBypass = Quaternion.Euler(state.PositionDampingBypass);
  274. extra.previousDisplacement = dampingBypass * extra.previousDisplacement;
  275. // Calculate the desired collision correction
  276. Vector3 displacement = PreserveLineOfSight(ref state, ref extra);
  277. if (m_MinimumOcclusionTime > Epsilon)
  278. {
  279. // If minimum occlusion time set, ignore new occlusions until they've lasted long enough
  280. float now = CinemachineCore.CurrentTime;
  281. if (displacement.AlmostZero())
  282. extra.occlusionStartTime = 0; // no occlusion
  283. else
  284. {
  285. if (extra.occlusionStartTime <= 0)
  286. extra.occlusionStartTime = now; // occlusion timer starts now
  287. if (now - extra.occlusionStartTime < m_MinimumOcclusionTime)
  288. displacement = extra.previousDisplacement;
  289. }
  290. }
  291. // Apply distance smoothing - this can artificially hold the camera closer
  292. // to the target for a while, to reduce popping in and out on bumpy objects
  293. if (m_SmoothingTime > Epsilon && state.HasLookAt)
  294. {
  295. Vector3 pos = initialCamPos + displacement;
  296. Vector3 dir = pos - state.ReferenceLookAt;
  297. float distance = dir.magnitude;
  298. if (distance > Epsilon)
  299. {
  300. dir /= distance;
  301. if (!displacement.AlmostZero())
  302. extra.UpdateDistanceSmoothing(distance);
  303. distance = extra.ApplyDistanceSmoothing(distance, m_SmoothingTime);
  304. displacement += (state.ReferenceLookAt + dir * distance) - pos;
  305. }
  306. }
  307. if (displacement.AlmostZero())
  308. extra.ResetDistanceSmoothing(m_SmoothingTime);
  309. // Apply additional correction due to camera radius
  310. var cameraPos = initialCamPos + displacement;
  311. var lookAt = state.HasLookAt ? state.ReferenceLookAt : cameraPos;
  312. displacement += RespectCameraRadius(cameraPos, lookAt);
  313. // Apply damping
  314. float dampTime = m_DampingWhenOccluded;
  315. if (deltaTime >= 0 && VirtualCamera.PreviousStateIsValid && m_DampingWhenOccluded + m_Damping > Epsilon)
  316. {
  317. // To ease the transition between damped and undamped regions, we damp the damp time
  318. var dispSqrMag = displacement.sqrMagnitude;
  319. dampTime = dispSqrMag > extra.previousDisplacement.sqrMagnitude ? m_DampingWhenOccluded : m_Damping;
  320. if (dispSqrMag < Epsilon)
  321. dampTime = extra.previousDampTime - Damper.Damp(extra.previousDampTime, dampTime, deltaTime);
  322. if (dampTime > 0)
  323. {
  324. bool bodyAfterAim = false;
  325. if (vcam is CinemachineVirtualCamera)
  326. {
  327. var body = (vcam as CinemachineVirtualCamera).GetCinemachineComponent(CinemachineCore.Stage.Body);
  328. bodyAfterAim = body != null && body.BodyAppliesAfterAim;
  329. }
  330. var prevDisplacement = bodyAfterAim ? extra.previousDisplacement
  331. : lookAt + dampingBypass * extra.previousCameraOffset - initialCamPos;
  332. displacement = prevDisplacement + Damper.Damp(displacement - prevDisplacement, dampTime, deltaTime);
  333. }
  334. }
  335. state.PositionCorrection += displacement;
  336. cameraPos = state.CorrectedPosition;
  337. // Adjust the damping bypass to account for the displacement
  338. if (state.HasLookAt && VirtualCamera.PreviousStateIsValid)
  339. {
  340. var dir0 = extra.previousCameraPosition - state.ReferenceLookAt;
  341. var dir1 = cameraPos - state.ReferenceLookAt;
  342. if (dir0.sqrMagnitude > Epsilon && dir1.sqrMagnitude > Epsilon)
  343. state.PositionDampingBypass = UnityVectorExtensions.SafeFromToRotation(
  344. dir0, dir1, state.ReferenceUp).eulerAngles;
  345. }
  346. extra.previousDisplacement = displacement;
  347. extra.previousCameraOffset = cameraPos - lookAt;
  348. extra.previousCameraPosition = cameraPos;
  349. extra.previousDampTime = dampTime;
  350. }
  351. }
  352. // Rate the shot after the aim was set
  353. if (stage == CinemachineCore.Stage.Aim)
  354. {
  355. var extra = GetExtraState<VcamExtraState>(vcam);
  356. extra.targetObscured = IsTargetOffscreen(state) || CheckForTargetObstructions(state);
  357. // GML these values are an initial arbitrary attempt at rating quality
  358. if (extra.targetObscured)
  359. state.ShotQuality *= 0.2f;
  360. if (!extra.previousDisplacement.AlmostZero())
  361. state.ShotQuality *= 0.8f;
  362. float nearnessBoost = 0;
  363. const float kMaxNearBoost = 0.2f;
  364. if (m_OptimalTargetDistance > 0 && state.HasLookAt)
  365. {
  366. float distance = Vector3.Magnitude(state.ReferenceLookAt - state.FinalPosition);
  367. if (distance <= m_OptimalTargetDistance)
  368. {
  369. float threshold = m_OptimalTargetDistance / 2;
  370. if (distance >= threshold)
  371. nearnessBoost = kMaxNearBoost * (distance - threshold)
  372. / (m_OptimalTargetDistance - threshold);
  373. }
  374. else
  375. {
  376. distance -= m_OptimalTargetDistance;
  377. float threshold = m_OptimalTargetDistance * 3;
  378. if (distance < threshold)
  379. nearnessBoost = kMaxNearBoost * (1f - (distance / threshold));
  380. }
  381. state.ShotQuality *= (1f + nearnessBoost);
  382. }
  383. }
  384. }
  385. Vector3 PreserveLineOfSight(ref CameraState state, ref VcamExtraState extra)
  386. {
  387. Vector3 displacement = Vector3.zero;
  388. if (state.HasLookAt && m_CollideAgainst != 0
  389. && m_CollideAgainst != m_TransparentLayers)
  390. {
  391. Vector3 cameraPos = state.CorrectedPosition;
  392. Vector3 lookAtPos = state.ReferenceLookAt;
  393. RaycastHit hitInfo = new RaycastHit();
  394. displacement = PullCameraInFrontOfNearestObstacle(
  395. cameraPos, lookAtPos, m_CollideAgainst & ~m_TransparentLayers, ref hitInfo);
  396. Vector3 pos = cameraPos + displacement;
  397. if (hitInfo.collider != null)
  398. {
  399. extra.AddPointToDebugPath(pos);
  400. if (m_Strategy != ResolutionStrategy.PullCameraForward)
  401. {
  402. Vector3 targetToCamera = cameraPos - lookAtPos;
  403. pos = PushCameraBack(
  404. pos, targetToCamera, hitInfo, lookAtPos,
  405. new Plane(state.ReferenceUp, cameraPos),
  406. targetToCamera.magnitude, m_MaximumEffort, ref extra);
  407. }
  408. }
  409. displacement = pos - cameraPos;
  410. }
  411. return displacement;
  412. }
  413. Vector3 PullCameraInFrontOfNearestObstacle(
  414. Vector3 cameraPos, Vector3 lookAtPos, int layerMask, ref RaycastHit hitInfo)
  415. {
  416. Vector3 displacement = Vector3.zero;
  417. Vector3 dir = cameraPos - lookAtPos;
  418. float targetDistance = dir.magnitude;
  419. if (targetDistance > Epsilon)
  420. {
  421. dir /= targetDistance;
  422. float minDistanceFromTarget = Mathf.Max(m_MinimumDistanceFromTarget, Epsilon);
  423. if (targetDistance < minDistanceFromTarget + Epsilon)
  424. displacement = dir * (minDistanceFromTarget - targetDistance);
  425. else
  426. {
  427. float rayLength = targetDistance - minDistanceFromTarget;
  428. if (m_DistanceLimit > Epsilon)
  429. rayLength = Mathf.Min(m_DistanceLimit, rayLength);
  430. // Make a ray that looks towards the camera, to get the obstacle closest to target
  431. Ray ray = new Ray(cameraPos - rayLength * dir, dir);
  432. rayLength += k_PrecisionSlush;
  433. if (rayLength > Epsilon)
  434. {
  435. if (m_Strategy == ResolutionStrategy.PullCameraForward && m_CameraRadius >= Epsilon)
  436. {
  437. if (RuntimeUtility.SphereCastIgnoreTag(lookAtPos + dir * m_CameraRadius,
  438. m_CameraRadius, dir, out hitInfo,
  439. rayLength - m_CameraRadius, layerMask, m_IgnoreTag))
  440. {
  441. var desiredResult = hitInfo.point + hitInfo.normal * m_CameraRadius;
  442. displacement = desiredResult - cameraPos;
  443. }
  444. }
  445. else
  446. {
  447. if (RuntimeUtility.RaycastIgnoreTag(ray, out hitInfo, rayLength, layerMask, m_IgnoreTag))
  448. {
  449. // Pull camera forward in front of obstacle
  450. float adjustment = Mathf.Max(0, hitInfo.distance - k_PrecisionSlush);
  451. displacement = ray.GetPoint(adjustment) - cameraPos;
  452. }
  453. }
  454. }
  455. }
  456. }
  457. return displacement;
  458. }
  459. Vector3 PushCameraBack(
  460. Vector3 currentPos, Vector3 pushDir, RaycastHit obstacle,
  461. Vector3 lookAtPos, Plane startPlane, float targetDistance, int iterations,
  462. ref VcamExtraState extra)
  463. {
  464. // Take a step along the wall.
  465. Vector3 pos = currentPos;
  466. Vector3 dir = Vector3.zero;
  467. if (!GetWalkingDirection(pos, pushDir, obstacle, ref dir))
  468. return pos;
  469. Ray ray = new Ray(pos, dir);
  470. float distance = GetPushBackDistance(ray, startPlane, targetDistance, lookAtPos);
  471. if (distance <= Epsilon)
  472. return pos;
  473. // Check only as far as the obstacle bounds
  474. float clampedDistance = ClampRayToBounds(ray, distance, obstacle.collider.bounds);
  475. distance = Mathf.Min(distance, clampedDistance + k_PrecisionSlush);
  476. if (RuntimeUtility.RaycastIgnoreTag(ray, out var hitInfo, distance,
  477. m_CollideAgainst & ~m_TransparentLayers, m_IgnoreTag))
  478. {
  479. // We hit something. Stop there and take a step along that wall.
  480. float adjustment = hitInfo.distance - k_PrecisionSlush;
  481. pos = ray.GetPoint(adjustment);
  482. extra.AddPointToDebugPath(pos);
  483. if (iterations > 1)
  484. pos = PushCameraBack(
  485. pos, dir, hitInfo,
  486. lookAtPos, startPlane,
  487. targetDistance, iterations-1, ref extra);
  488. return pos;
  489. }
  490. // Didn't hit anything. Can we push back all the way now?
  491. pos = ray.GetPoint(distance);
  492. // First check if we can still see the target. If not, abort
  493. dir = pos - lookAtPos;
  494. float d = dir.magnitude;
  495. if (d < Epsilon || RuntimeUtility.RaycastIgnoreTag(
  496. new Ray(lookAtPos, dir), out _, d - k_PrecisionSlush,
  497. m_CollideAgainst & ~m_TransparentLayers, m_IgnoreTag))
  498. return currentPos;
  499. // All clear
  500. ray = new Ray(pos, dir);
  501. extra.AddPointToDebugPath(pos);
  502. distance = GetPushBackDistance(ray, startPlane, targetDistance, lookAtPos);
  503. if (distance > Epsilon)
  504. {
  505. if (!RuntimeUtility.RaycastIgnoreTag(ray, out hitInfo, distance,
  506. m_CollideAgainst & ~m_TransparentLayers, m_IgnoreTag))
  507. {
  508. pos = ray.GetPoint(distance); // no obstacles - all good
  509. extra.AddPointToDebugPath(pos);
  510. }
  511. else
  512. {
  513. // We hit something. Stop there and maybe take a step along that wall
  514. float adjustment = hitInfo.distance - k_PrecisionSlush;
  515. pos = ray.GetPoint(adjustment);
  516. extra.AddPointToDebugPath(pos);
  517. if (iterations > 1)
  518. pos = PushCameraBack(
  519. pos, dir, hitInfo, lookAtPos, startPlane,
  520. targetDistance, iterations-1, ref extra);
  521. }
  522. }
  523. return pos;
  524. }
  525. RaycastHit[] m_CornerBuffer = new RaycastHit[4];
  526. bool GetWalkingDirection(
  527. Vector3 pos, Vector3 pushDir, RaycastHit obstacle, ref Vector3 outDir)
  528. {
  529. Vector3 normal2 = obstacle.normal;
  530. // Check for nearby obstacles. Are we in a corner?
  531. float nearbyDistance = k_PrecisionSlush * 5;
  532. int numFound = Physics.SphereCastNonAlloc(
  533. pos, nearbyDistance, pushDir.normalized, m_CornerBuffer, 0,
  534. m_CollideAgainst & ~m_TransparentLayers, QueryTriggerInteraction.Ignore);
  535. if (numFound > 1)
  536. {
  537. // Calculate the second normal
  538. for (int i = 0; i < numFound; ++i)
  539. {
  540. if (m_CornerBuffer[i].collider == null)
  541. continue;
  542. if (m_IgnoreTag.Length > 0 && m_CornerBuffer[i].collider.CompareTag(m_IgnoreTag))
  543. continue;
  544. Type type = m_CornerBuffer[i].collider.GetType();
  545. if (type == typeof(BoxCollider)
  546. || type == typeof(SphereCollider)
  547. || type == typeof(CapsuleCollider))
  548. {
  549. Vector3 p = m_CornerBuffer[i].collider.ClosestPoint(pos);
  550. Vector3 d = p - pos;
  551. if (d.magnitude > Vector3.kEpsilon)
  552. {
  553. if (m_CornerBuffer[i].collider.Raycast(
  554. new Ray(pos, d), out m_CornerBuffer[i], nearbyDistance))
  555. {
  556. if (!(m_CornerBuffer[i].normal - obstacle.normal).AlmostZero())
  557. normal2 = m_CornerBuffer[i].normal;
  558. break;
  559. }
  560. }
  561. }
  562. }
  563. }
  564. // Walk along the wall. If we're in a corner, walk their intersecting line
  565. Vector3 dir = Vector3.Cross(obstacle.normal, normal2);
  566. if (dir.AlmostZero())
  567. dir = Vector3.ProjectOnPlane(pushDir, obstacle.normal);
  568. else
  569. {
  570. float dot = Vector3.Dot(dir, pushDir);
  571. if (Mathf.Abs(dot) < Epsilon)
  572. return false;
  573. if (dot < 0)
  574. dir = -dir;
  575. }
  576. if (dir.AlmostZero())
  577. return false;
  578. outDir = dir.normalized;
  579. return true;
  580. }
  581. const float k_AngleThreshold = 0.1f;
  582. float GetPushBackDistance(Ray ray, Plane startPlane, float targetDistance, Vector3 lookAtPos)
  583. {
  584. float maxDistance = targetDistance - (ray.origin - lookAtPos).magnitude;
  585. if (maxDistance < Epsilon)
  586. return 0;
  587. if (m_Strategy == ResolutionStrategy.PreserveCameraDistance)
  588. return maxDistance;
  589. if (!startPlane.Raycast(ray, out var distance))
  590. distance = 0;
  591. distance = Mathf.Min(maxDistance, distance);
  592. if (distance < Epsilon)
  593. return 0;
  594. // If we are close to parallel to the plane, we have to take special action
  595. float angle = Mathf.Abs(UnityVectorExtensions.Angle(startPlane.normal, ray.direction) - 90);
  596. if (angle < k_AngleThreshold)
  597. distance = Mathf.Lerp(0, distance, angle / k_AngleThreshold);
  598. return distance;
  599. }
  600. static float ClampRayToBounds(Ray ray, float distance, Bounds bounds)
  601. {
  602. float d;
  603. if (Vector3.Dot(ray.direction, Vector3.up) > 0)
  604. {
  605. if (new Plane(Vector3.down, bounds.max).Raycast(ray, out d) && d > Epsilon)
  606. distance = Mathf.Min(distance, d);
  607. }
  608. else if (Vector3.Dot(ray.direction, Vector3.down) > 0)
  609. {
  610. if (new Plane(Vector3.up, bounds.min).Raycast(ray, out d) && d > Epsilon)
  611. distance = Mathf.Min(distance, d);
  612. }
  613. if (Vector3.Dot(ray.direction, Vector3.right) > 0)
  614. {
  615. if (new Plane(Vector3.left, bounds.max).Raycast(ray, out d) && d > Epsilon)
  616. distance = Mathf.Min(distance, d);
  617. }
  618. else if (Vector3.Dot(ray.direction, Vector3.left) > 0)
  619. {
  620. if (new Plane(Vector3.right, bounds.min).Raycast(ray, out d) && d > Epsilon)
  621. distance = Mathf.Min(distance, d);
  622. }
  623. if (Vector3.Dot(ray.direction, Vector3.forward) > 0)
  624. {
  625. if (new Plane(Vector3.back, bounds.max).Raycast(ray, out d) && d > Epsilon)
  626. distance = Mathf.Min(distance, d);
  627. }
  628. else if (Vector3.Dot(ray.direction, Vector3.back) > 0)
  629. {
  630. if (new Plane(Vector3.forward, bounds.min).Raycast(ray, out d) && d > Epsilon)
  631. distance = Mathf.Min(distance, d);
  632. }
  633. return distance;
  634. }
  635. static Collider[] s_ColliderBuffer = new Collider[5];
  636. Vector3 RespectCameraRadius(Vector3 cameraPos, Vector3 lookAtPos)
  637. {
  638. Vector3 result = Vector3.zero;
  639. if (m_CameraRadius < Epsilon || m_CollideAgainst == 0)
  640. return result;
  641. Vector3 dir = cameraPos - lookAtPos;
  642. float distance = dir.magnitude;
  643. if (distance > Epsilon)
  644. dir /= distance;
  645. // Pull it out of any intersecting obstacles
  646. RaycastHit hitInfo;
  647. int numObstacles = Physics.OverlapSphereNonAlloc(
  648. cameraPos, m_CameraRadius, s_ColliderBuffer,
  649. m_CollideAgainst, QueryTriggerInteraction.Ignore);
  650. if (numObstacles == 0 && m_TransparentLayers != 0
  651. && distance > m_MinimumDistanceFromTarget + Epsilon)
  652. {
  653. // Make sure the camera position isn't completely inside an obstacle.
  654. // OverlapSphereNonAlloc won't catch those.
  655. float d = distance - m_MinimumDistanceFromTarget;
  656. Vector3 targetPos = lookAtPos + dir * m_MinimumDistanceFromTarget;
  657. if (RuntimeUtility.RaycastIgnoreTag(new Ray(targetPos, dir),
  658. out hitInfo, d, m_CollideAgainst, m_IgnoreTag))
  659. {
  660. // Only count it if there's an incoming collision but not an outgoing one
  661. Collider c = hitInfo.collider;
  662. if (!c.Raycast(new Ray(cameraPos, -dir), out hitInfo, d))
  663. s_ColliderBuffer[numObstacles++] = c;
  664. }
  665. }
  666. if (numObstacles > 0 && distance == 0 || distance > m_MinimumDistanceFromTarget)
  667. {
  668. var scratchCollider = RuntimeUtility.GetScratchCollider();
  669. scratchCollider.radius = m_CameraRadius;
  670. Vector3 newCamPos = cameraPos;
  671. for (int i = 0; i < numObstacles; ++i)
  672. {
  673. Collider c = s_ColliderBuffer[i];
  674. if (m_IgnoreTag.Length > 0 && c.CompareTag(m_IgnoreTag))
  675. continue;
  676. // If we have a lookAt target, move the camera to the nearest edge of obstacle
  677. if (distance > m_MinimumDistanceFromTarget)
  678. {
  679. dir = newCamPos - lookAtPos;
  680. float d = dir.magnitude;
  681. if (d > Epsilon)
  682. {
  683. dir /= d;
  684. var ray = new Ray(lookAtPos, dir);
  685. if (c.Raycast(ray, out hitInfo, d + m_CameraRadius))
  686. newCamPos = ray.GetPoint(hitInfo.distance) - (dir * k_PrecisionSlush);
  687. }
  688. }
  689. if (Physics.ComputePenetration(
  690. scratchCollider, newCamPos, Quaternion.identity,
  691. c, c.transform.position, c.transform.rotation,
  692. out var offsetDir, out var offsetDistance))
  693. {
  694. newCamPos += offsetDir * offsetDistance;
  695. }
  696. }
  697. result = newCamPos - cameraPos;
  698. }
  699. // Respect the minimum distance from target - push camera back if we have to
  700. if (distance > Epsilon && m_MinimumDistanceFromTarget > Epsilon)
  701. {
  702. float minDistance = Mathf.Max(m_MinimumDistanceFromTarget, m_CameraRadius) + k_PrecisionSlush;
  703. Vector3 newOffset = cameraPos + result - lookAtPos;
  704. if (newOffset.magnitude < minDistance)
  705. result = lookAtPos - cameraPos + dir * minDistance;
  706. }
  707. return result;
  708. }
  709. bool CheckForTargetObstructions(CameraState state)
  710. {
  711. if (state.HasLookAt)
  712. {
  713. Vector3 lookAtPos = state.ReferenceLookAt;
  714. Vector3 pos = state.CorrectedPosition;
  715. Vector3 dir = lookAtPos - pos;
  716. float distance = dir.magnitude;
  717. if (distance < Mathf.Max(m_MinimumDistanceFromTarget, Epsilon))
  718. return true;
  719. Ray ray = new Ray(pos, dir.normalized);
  720. if (RuntimeUtility.RaycastIgnoreTag(ray, out _,
  721. distance - m_MinimumDistanceFromTarget,
  722. m_CollideAgainst & ~m_TransparentLayers, m_IgnoreTag))
  723. return true;
  724. }
  725. return false;
  726. }
  727. static bool IsTargetOffscreen(CameraState state)
  728. {
  729. if (state.HasLookAt)
  730. {
  731. Vector3 dir = state.ReferenceLookAt - state.CorrectedPosition;
  732. dir = Quaternion.Inverse(state.CorrectedOrientation) * dir;
  733. if (state.Lens.Orthographic)
  734. {
  735. if (Mathf.Abs(dir.y) > state.Lens.OrthographicSize)
  736. return true;
  737. if (Mathf.Abs(dir.x) > state.Lens.OrthographicSize * state.Lens.Aspect)
  738. return true;
  739. }
  740. else
  741. {
  742. float fov = state.Lens.FieldOfView / 2;
  743. float angle = UnityVectorExtensions.Angle(dir.ProjectOntoPlane(Vector3.right), Vector3.forward);
  744. if (angle > fov)
  745. return true;
  746. fov = Mathf.Rad2Deg * Mathf.Atan(Mathf.Tan(fov * Mathf.Deg2Rad) * state.Lens.Aspect);
  747. angle = UnityVectorExtensions.Angle(dir.ProjectOntoPlane(Vector3.up), Vector3.forward);
  748. if (angle > fov)
  749. return true;
  750. }
  751. }
  752. return false;
  753. }
  754. }
  755. #endif
  756. }