21 private enum FrustumPlanes
31 [Tooltip(
"The object the direction indicator will point to.")]
34 [Tooltip(
"The camera depth at which the indicator rests.")]
37 [Tooltip(
"The point around which the indicator pivots. Should be placed at the model's 'tip'.")]
40 [Tooltip(
"The object used to 'point' at the target.")]
43 [Tooltip(
"Determines what percentage of the visible field should be margin.")]
47 [Tooltip(
"Debug draw the planes used to calculate the pointer lock location.")]
50 private GameObject pointer;
52 private static int frustumLastUpdated = -1;
54 private static Plane[] frustumPlanes;
55 private static Vector3 cameraForward;
56 private static Vector3 cameraPosition;
57 private static Vector3 cameraRight;
58 private static Vector3 cameraUp;
60 private Plane[] indicatorVolume;
66 if (PointerPrefab == null)
68 this.gameObject.SetActive(
false);
72 pointer = GameObject.Instantiate(PointerPrefab);
76 pointer.transform.parent = transform;
77 pointer.transform.position = -Pivot;
81 indicatorVolume =
new Plane[]
95 if (!HasObjectsToTrack()) {
return; }
97 int currentFrameCount = Time.frameCount;
98 if (currentFrameCount != frustumLastUpdated)
103 frustumLastUpdated = currentFrameCount;
106 UpdatePointerTransform(
CameraCache.
Main, indicatorVolume, TargetObject.transform.position);
109 private bool HasObjectsToTrack()
111 return TargetObject != null && pointer != null;
115 private void CacheCameraTransform(Camera camera)
117 cameraForward = camera.transform.forward;
118 cameraPosition = camera.transform.position;
119 cameraRight = camera.transform.right;
120 cameraUp = camera.transform.up;
121 frustumPlanes = GeometryUtility.CalculateFrustumPlanes(camera);
126 private FrustumPlanes GetExitPlane(Vector3 targetPosition, Camera camera)
134 float aspect = camera.aspect;
135 float fovy = 0.5f * camera.fieldOfView;
136 float near = camera.nearClipPlane;
137 float far = camera.farClipPlane;
139 float tanFovy = Mathf.Tan(Mathf.Deg2Rad * fovy);
140 float tanFovx = aspect * tanFovy;
144 Vector3 nearTop = near * tanFovy * cameraUp;
145 Vector3 nearRight = near * tanFovx * cameraRight;
146 Vector3 nearBottom = -nearTop;
147 Vector3 nearLeft = -nearRight;
148 Vector3 farTop = far * tanFovy * cameraUp;
149 Vector3 farRight = far * tanFovx * cameraRight;
150 Vector3 farLeft = -farRight;
154 Vector3 nearBase = near * cameraForward;
155 Vector3 farBase = far * cameraForward;
158 Vector3 nearUpperLeft = nearBase + nearTop + nearLeft;
159 Vector3 nearLowerRight = nearBase + nearBottom + nearRight;
160 Vector3 farUpperLeft = farBase + farTop + farLeft;
162 Plane d =
new Plane(nearUpperLeft, nearLowerRight, farUpperLeft);
165 Vector3 nearUpperRight = nearBase + nearTop + nearRight;
166 Vector3 nearLowerLeft = nearBase + nearBottom + nearLeft;
167 Vector3 farUpperRight = farBase + farTop + farRight;
169 Plane e =
new Plane(nearUpperRight, nearLowerLeft, farUpperRight);
172 if (DebugDrawPointerOrientationPlanes)
175 Debug.DrawLine(nearUpperLeft, nearLowerRight);
176 Debug.DrawLine(nearLowerRight, farUpperLeft);
177 Debug.DrawLine(farUpperLeft, nearUpperLeft);
180 Debug.DrawLine(nearUpperRight, nearLowerLeft);
181 Debug.DrawLine(nearLowerLeft, farUpperRight);
182 Debug.DrawLine(farUpperRight, nearUpperRight);
188 float dDistance = d.GetDistanceToPoint(targetPosition);
189 float eDistance = e.GetDistanceToPoint(targetPosition);
207 if (dDistance > 0.0f)
209 if (eDistance > 0.0f)
211 return FrustumPlanes.Left;
214 return FrustumPlanes.Bottom;
218 if (eDistance > 0.0f)
220 return FrustumPlanes.Top;
223 return FrustumPlanes.Right;
231 private bool TryGetIndicatorPosition(Vector3 targetPosition, Plane frustumWall, out Ray r)
239 Vector3 cameraToTarget = targetPosition - cameraPosition;
240 Vector3 normal = Vector3.Cross(cameraToTarget.normalized, cameraForward);
244 if (normal == Vector3.zero)
246 normal = -Vector3.right;
249 Plane q =
new Plane(normal, targetPosition);
250 return TryIntersectPlanes(frustumWall, q, out r);
255 private bool TryIntersectPlanes(Plane p, Plane q, out Ray intersection)
257 Vector3 rNormal = Vector3.Cross(p.normal, q.normal);
258 float det = rNormal.sqrMagnitude;
262 Vector3 rPoint = ((Vector3.Cross(rNormal, q.normal) * p.distance) +
263 (Vector3.Cross(p.normal, rNormal) * q.distance)) / det;
264 intersection =
new Ray(rPoint, rNormal);
268 intersection =
new Ray();
276 private void UpdatePointerTransform(Camera camera, Plane[] planes, Vector3 targetPosition)
279 UpdateIndicatorVolume(camera);
282 Vector3 indicatorPosition = cameraPosition + Depth * (targetPosition - cameraPosition).normalized;
286 bool pointNotInsideIndicatorField =
false;
287 for (
int i = 0; i < 5; ++i)
289 float dot = Vector3.Dot(planes[i].normal, (targetPosition - cameraPosition).normalized);
292 pointNotInsideIndicatorField =
true;
298 if (pointNotInsideIndicatorField)
304 FrustumPlanes exitPlane = GetExitPlane(targetPosition, camera);
307 if (TryGetIndicatorPosition(targetPosition, planes[(
int)exitPlane], out r))
309 indicatorPosition = cameraPosition + Depth * r.direction.normalized;
313 this.transform.position = indicatorPosition;
322 Vector3 indicatorFieldOffset = indicatorPosition - cameraPosition;
323 indicatorFieldOffset = Vector3.Dot(indicatorFieldOffset, cameraForward) * cameraForward;
325 Vector3 indicatorFieldCenter = cameraPosition + indicatorFieldOffset;
326 Vector3 pointerDirection = (indicatorPosition - indicatorFieldCenter).normalized;
329 this.transform.rotation = Quaternion.LookRotation(cameraForward, pointerDirection);
334 private void UpdateIndicatorVolume(Camera camera)
338 for (
int i = 0; i < 4; ++i)
344 float angle = Mathf.Acos(Vector3.Dot(frustumPlanes[i].normal.normalized, cameraForward));
349 float angleStep = IndicatorMarginPercent * (0.5f * Mathf.PI - angle);
353 Vector3 normal = Vector3.RotateTowards(frustumPlanes[i].normal, cameraForward, -angleStep, 0.0f);
355 indicatorVolume[i].normal = normal.normalized;
356 indicatorVolume[i].distance = frustumPlanes[i].distance;
359 indicatorVolume[4] = frustumPlanes[4];
360 indicatorVolume[5] = frustumPlanes[5];