1 using System;
2 using LibRender2.Camera;
3 using LibRender2.Viewports;
4 using OpenBveApi.Graphics;
5 using OpenBveApi.Math;
6 using OpenBveApi.Runtime;
7 
8 namespace LibRender2.Cameras
9 {
10 	public class CameraProperties
11 	{
12 		private readonly BaseRenderer Renderer;
13 		/// <summary>The current viewing distance in the forward direction.</summary>
14 		public double ForwardViewingDistance;
15 		/// <summary>The current viewing distance in the backward direction.</summary>
16 		public double BackwardViewingDistance;
17 		/// <summary>The current viewing distance</summary>
18 		public double ViewingDistance
19 		{
20 			get
21 			{
22 				return Math.Max(ForwardViewingDistance, BackwardViewingDistance);
23 			}
24 		}
25 
26 		/// <summary>The extra viewing distance used for determining visibility of animated objects.</summary>
27 		public double ExtraViewingDistance;
28 		/// <summary>Whether the camera has reached the end of the world</summary>
29 		public bool AtWorldEnd;
30 		/// <summary>The current horizontal viewing angle in radians</summary>
31 		public double HorizontalViewingAngle;
32 		/// <summary>The current vertical viewing angle in radians</summary>
33 		public double VerticalViewingAngle;
34 		/// <summary>The original vertical viewing angle in radians</summary>
35 		public double OriginalVerticalViewingAngle;
36 		/// <summary>A Matrix4D describing the current camera translation</summary>
37 		public Matrix4D TranslationMatrix;
38 		/// <summary>The absolute in-world camera position</summary>
39 		public Vector3 AbsolutePosition
40 		{
41 			get
42 			{
43 				return absolutePosition;
44 			}
45 			set
46 			{
47 				absolutePosition = value;
48 				TranslationMatrix = Matrix4D.CreateTranslation(-value.X, -value.Y, value.Z);
49 			}
50 		}
51 		/// <summary>The absolute in-world camera Direction vector</summary>
52 		public Vector3 AbsoluteDirection;
53 		/// <summary>The absolute in-world camera Up vector</summary>
54 		public Vector3 AbsoluteUp;
55 		/// <summary>The absolute in-world camera Side vector</summary>
56 		public Vector3 AbsoluteSide;
57 		/// <summary>The current relative camera alignment</summary>
58 		public CameraAlignment Alignment;
59 		/// <summary>The current relative camera Direction</summary>
60 		public CameraAlignment AlignmentDirection;
61 		/// <summary>The current relative camera Speed</summary>
62 		public CameraAlignment AlignmentSpeed;
63 		/// <summary>The current camera movement speed</summary>
64 		public double CurrentSpeed;
65 		/// <summary>The top speed when moving in a straight line in an interior view</summary>
66 		public const double InteriorTopSpeed = 5.0;
67 		/// <summary>The top speed when moving at an angle in an interior view</summary>
68 		public const double InteriorTopAngularSpeed = 5.0;
69 		/// <summary>The top speed when moving in a straight line in an exterior view</summary>
70 		public const double ExteriorTopSpeed = 50.0;
71 		/// <summary>The top speed when moving in an angle in an exterior view</summary>
72 		public const double ExteriorTopAngularSpeed = 10.0;
73 		/// <summary>The top speed when zooming in or out</summary>
74 		public const double ZoomTopSpeed = 2.0;
75 		/// <summary>The current camera mode</summary>
76 		public CameraViewMode CurrentMode;
77 		/// <summary>The current camera restriction mode</summary>
78 		public CameraRestrictionMode CurrentRestriction = CameraRestrictionMode.NotAvailable;
79 		/// <summary>The saved exterior camera alignment</summary>
80 		public CameraAlignment SavedExterior;
81 		/// <summary>The saved track camera alignment</summary>
82 		public CameraAlignment SavedTrack;
83 
84 		private Vector3 absolutePosition;
85 
CameraProperties(BaseRenderer renderer)86 		internal CameraProperties(BaseRenderer renderer)
87 		{
88 			Renderer = renderer;
89 		}
90 
91 		/// <summary>Tests whether the camera may move further in the current direction</summary>
PerformRestrictionTest(CameraRestriction Restriction)92 		public bool PerformRestrictionTest(CameraRestriction Restriction)
93 		{
94 			if (CurrentRestriction == CameraRestrictionMode.On)
95 			{
96 				Vector3[] p = { Restriction.BottomLeft, Restriction.TopRight };
97 				Vector2[] r = new Vector2[2];
98 
99 				for (int j = 0; j < 2; j++)
100 				{
101 					// determine relative world coordinates
102 					p[j].Rotate(AbsoluteDirection, AbsoluteUp, AbsoluteSide);
103 					double rx = -Math.Tan(Alignment.Yaw) - Alignment.Position.X;
104 					double ry = -Math.Tan(Alignment.Pitch) - Alignment.Position.Y;
105 					double rz = -Alignment.Position.Z;
106 					p[j] += rx * AbsoluteSide + ry * AbsoluteUp + rz * AbsoluteDirection;
107 
108 					// determine screen coordinates
109 					double ez = AbsoluteDirection.X * p[j].X + AbsoluteDirection.Y * p[j].Y + AbsoluteDirection.Z * p[j].Z;
110 
111 					if (ez == 0.0)
112 					{
113 						return false;
114 					}
115 
116 					double ex = AbsoluteSide.X * p[j].X + AbsoluteSide.Y * p[j].Y + AbsoluteSide.Z * p[j].Z;
117 					double ey = AbsoluteUp.X * p[j].X + AbsoluteUp.Y * p[j].Y + AbsoluteUp.Z * p[j].Z;
118 					r[j].X = ex / (ez * Math.Tan(0.5 * HorizontalViewingAngle));
119 					r[j].Y = ey / (ez * Math.Tan(0.5 * VerticalViewingAngle));
120 				}
121 
122 				return r[0].X <= -1.0025 & r[1].X >= 1.0025 & r[0].Y <= -1.0025 & r[1].Y >= 1.0025;
123 			}
124 			if (CurrentRestriction == CameraRestrictionMode.Restricted3D)
125 			{
126 				Vector3[] p = { Restriction.BottomLeft, Restriction.TopRight };
127 
128 				for (int j = 0; j < 2; j++)
129 				{
130 					// determine relative world coordinates
131 					p[j].Rotate(AbsoluteDirection, AbsoluteUp, AbsoluteSide);
132 					double rx = -Math.Tan(Alignment.Yaw) - Alignment.Position.X;
133 					double ry = -Math.Tan(Alignment.Pitch) - Alignment.Position.Y;
134 					double rz = -Alignment.Position.Z;
135 					p[j] += rx * AbsoluteSide + ry * AbsoluteUp + rz * AbsoluteDirection;
136 				}
137 
138 				if (AlignmentDirection.Position.X > 0)
139 				{
140 					//moving right
141 					if (AbsolutePosition.X >= Restriction.AbsoluteTopRight.X)
142 					{
143 						return false;
144 					}
145 				}
146 				if (AlignmentDirection.Position.X < 0)
147 				{
148 					//moving left
149 					if (AbsolutePosition.X <= Restriction.AbsoluteBottomLeft.X)
150 					{
151 						return false;
152 					}
153 				}
154 
155 				if (AlignmentDirection.Position.Y > 0)
156 				{
157 					//moving up
158 					if (AbsolutePosition.Y >= Restriction.AbsoluteTopRight.Y)
159 					{
160 						return false;
161 					}
162 				}
163 				if (AlignmentDirection.Position.Y < 0)
164 				{
165 					//moving down
166 					if (AbsolutePosition.Y <= Restriction.AbsoluteBottomLeft.Y)
167 					{
168 						return false;
169 					}
170 				}
171 
172 				if (AlignmentDirection.Position.Z > 0)
173 				{
174 					//moving forwards
175 					if (AbsolutePosition.Z >= Restriction.AbsoluteTopRight.Z)
176 					{
177 						return false;
178 					}
179 				}
180 				if (AlignmentDirection.Position.Z < 0)
181 				{
182 					//moving back
183 					if (AbsolutePosition.Z <= Restriction.AbsoluteBottomLeft.Z)
184 					{
185 						return false;
186 					}
187 				}
188 			}
189 			return true;
190 		}
191 
192 		/// <summary>Performs progressive adjustments taking into account the specified camera restriction</summary>
PerformProgressiveAdjustmentForCameraRestriction(ref double Source, double Target, bool Zoom, CameraRestriction Restriction)193 		public bool PerformProgressiveAdjustmentForCameraRestriction(ref double Source, double Target, bool Zoom, CameraRestriction Restriction)
194 		{
195 			if ((CurrentMode != CameraViewMode.Interior & CurrentMode != CameraViewMode.InteriorLookAhead) | (CurrentRestriction != CameraRestrictionMode.On && CurrentRestriction != CameraRestrictionMode.Restricted3D))
196 			{
197 				Source = Target;
198 				return true;
199 			}
200 
201 			double best = Source;
202 			const int Precision = 8;
203 			double a = Source;
204 			double b = Target;
205 			Source = Target;
206 			if (Zoom) ApplyZoom();
207 			if (PerformRestrictionTest(Restriction))
208 			{
209 				return true;
210 			}
211 
212 			double x = 0.5 * (a + b);
213 			bool q = true;
214 			for (int i = 0; i < Precision; i++)
215 			{
216 
217 #pragma warning disable IDE0059 //IDE is wrong, best may never be updated if q is never true
218 				Source = x;
219 #pragma warning restore IDE0059
220 				if (Zoom) ApplyZoom();
221 				q = PerformRestrictionTest(Restriction);
222 				if (q)
223 				{
224 					a = x;
225 					best = x;
226 				}
227 				else
228 				{
229 					b = x;
230 				}
231 
232 				x = 0.5 * (a + b);
233 			}
234 
235 			Source = best;
236 			if (Zoom) ApplyZoom();
237 			return q;
238 		}
239 
240 		/// <summary>Adjusts the camera alignment based upon the specified parameters</summary>
AdjustAlignment(ref double Source, double Direction, ref double Speed, double TimeElapsed, bool Zoom = false, CameraRestriction? Restriction = null)241 		public void AdjustAlignment(ref double Source, double Direction, ref double Speed, double TimeElapsed, bool Zoom = false, CameraRestriction? Restriction = null) {
242 			if (Direction != 0.0 | Speed != 0.0) {
243 				if (TimeElapsed > 0.0) {
244 					if (Direction == 0.0) {
245 						double d = (0.025 + 5.0 * Math.Abs(Speed)) * TimeElapsed;
246 						if (Speed >= -d & Speed <= d) {
247 							Speed = 0.0;
248 						} else {
249 							Speed -= Math.Sign(Speed) * d;
250 						}
251 					} else {
252 						double t = Math.Abs(Direction);
253 						double d = ((1.15 - 1.0 / (1.0 + 0.025 * Math.Abs(Speed)))) * TimeElapsed;
254 						Speed += Direction * d;
255 						if (Speed < -t) {
256 							Speed = -t;
257 						} else if (Speed > t) {
258 							Speed = t;
259 						}
260 					}
261 
262 					if (Restriction != null)
263 					{
264 						double x = Source + Speed * TimeElapsed;
265 						if (!PerformProgressiveAdjustmentForCameraRestriction(ref Source, x, Zoom, (CameraRestriction)Restriction))
266 						{
267 							Speed = 0.0;
268 						}
269 					}
270 					else
271 					{
272 						Source += Speed * TimeElapsed;
273 					}
274 				}
275 			}
276 		}
277 
278 		/// <summary>Applies the current zoom settings after a change</summary>
ApplyZoom()279 		public void ApplyZoom()
280 		{
281 			VerticalViewingAngle = OriginalVerticalViewingAngle * Math.Exp(Alignment.Zoom);
282 			if (VerticalViewingAngle < 0.001) VerticalViewingAngle = 0.001;
283 			if (VerticalViewingAngle > 1.5) VerticalViewingAngle = 1.5;
284 			Renderer.UpdateViewport(ViewportChangeMode.NoChange);
285 		}
286 
287 		/// <summary>Resets the camera to an absolute position</summary>
Reset(Vector3 Position)288 		public void Reset(Vector3 Position)
289 		{
290 			AbsolutePosition = Position;
291 			AbsoluteDirection = new Vector3(-AbsolutePosition.X, -AbsolutePosition.Y, -AbsolutePosition.Z);
292 			AbsoluteSide = new Vector3(-AbsolutePosition.Z, 0.0, AbsolutePosition.X);
293 			AbsoluteDirection.Normalize();
294 			AbsoluteSide.Normalize();
295 			AbsoluteUp = Vector3.Cross(AbsoluteDirection, AbsoluteSide);
296 			VerticalViewingAngle = 45.0.ToRadians();
297 			HorizontalViewingAngle = 2.0 * Math.Atan(Math.Tan(0.5 * VerticalViewingAngle) * Renderer.Screen.AspectRatio);
298 			OriginalVerticalViewingAngle = VerticalViewingAngle;
299 		}
300 
301 		/// <summary>Unconditionally resets the camera</summary>
Reset()302 		public void Reset()
303 		{
304 			Alignment.Yaw = 0.0;
305 			Alignment.Pitch = 0.0;
306 			Alignment.Roll = 0.0;
307 			Alignment.Position = new Vector3(0.0, 2.5, 0.0);
308 			Alignment.Zoom = 0.0;
309 			AlignmentDirection = new CameraAlignment();
310 			AlignmentSpeed = new CameraAlignment();
311 			VerticalViewingAngle = OriginalVerticalViewingAngle;
312 		}
313 	}
314 }
315