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