├── README.md └── Trajectory.cs /README.md: -------------------------------------------------------------------------------- 1 | # Trajectory-Extension-Methods 2 | A collection of extension methods for predicting and drawing the trajectories of Rigidbody and Rigidbody2D objects in Unity 3 | 4 | USAGE: 5 | 6 | Since they are extension methods they can be called directly from Rigidbody or Rigidbody2D instances eg: 7 | 8 | Rigidbody MyRigidbodyInstance = GetComponent(); 9 | MyRigidbodyInstance.DebugTrajectory(Force, ForceMode, Color.Green, 3.0f, 3.0f); 10 | MyRigidbodyInstance.AddForce(Force, ForceMode); 11 | 12 | Alternatively, you can call the methods from the Trajectory class itself. 13 | 14 | Trajectory.DebugTrajectory(MyRigidbodyInstance, Force, ForceMode, Color.green, 3.0f, 3.0f); 15 | 16 | 17 | METHODS: 18 | 19 | GetTrajectory - returns an array of position vectors for each point along the trajectory. There is also an Out parameter for getting an array of the expected velocities that the rigidbody should have at each point along the trajectory. 20 | 21 | DrawTrajectory - Draws a trajectory using Gizmos.DrawLine. More efficient than DebugTrajectory, but has less options. 22 | 23 | DebugTrajectory - Draws a trajectory using Debug.DrawLine. Allows for drawing lines that persist for more than 1 frame and has depthTest parameter. 24 | 25 | 26 | PARAMETERS: 27 | 28 | rb - Rigidbody or Rigidbody2D : The body whose trajectory is being drawn. 29 | 30 | force - Vector3 or Vector2 : Vector of the force being predicted. If not trying to predict a force, use Vector3.zero or Vector2.zero. 31 | 32 | mode - ForceMode or ForceMode2D : Force Mode of the force being predicted. Irrelevant if using a zero vector force. 33 | 34 | color - Color : Color of the line being drawn. 35 | 36 | out velocities - Vector3[] or Vector2[] : Outputs an array of vectors containing the expected velocities at each position in the trajectory. 37 | 38 | trajectoryDuration - float : Amount of time in seconds to predict trajectory. 39 | 40 | lineDuration - float : Amount of time in seconds the drawn line will persist. 41 | 42 | constantForce - bool : For predicting forces that are applied every FixedUpdate? Eg, true for predicting the trajectory of a rocket, false for predicting the trajectory of a cannon ball. 43 | 44 | depthTest - bool : Whether or not the line should be faded when behind other objects. 45 | 46 | 47 | NOTE: 48 | 49 | Since I didn't know precisely how and in what order unity's 3D "PhysX" and 2D "Box2D" engines apply their forces, It was mostly guess work. Both 2D and 3D predictions begin to fall apart when the drag is higher than the Physics Framerate. Apart from that i haven't been able to find any errors with the 3D trajectory, even when using absurd values for mass, timestep, force etc... The 2D predictions however reveal slight inaccuracies when traveling at very fast speeds over huge distances, the margin of error is probably only ~0.1 - 0.5% and shouldn't be noticeable in normal use cases. 50 | -------------------------------------------------------------------------------- /Trajectory.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | /// 4 | /// A collection of extension methods for and that 5 | /// calculate and get/draw expected trajectories. 6 | /// 7 | /// 8 | /// The methods take either a force and a for 9 | /// , or a force and a for 10 | /// . They can also be passed a Vector2.zero or Vector3.zero, for calculating the 11 | /// trajectory using only the objects current velocity and regular physics forces without any additonal forces. 12 | /// When using these zero vectors the forcemode is irrelevant. 13 | /// 14 | /// For RigidBody, the calculations act upon 's Gravity, 15 | /// and the Rigidbodys's Mass, Linear Drag, UseGravity and Freeze Position Constraints. 16 | /// For Rigidbody2D, the calculations act upon 's Gravity 17 | /// and MaxTranslationSpeed, and the Rigidbody2D's Mass, Linear Drag, Gravity Scale and 18 | /// Freeze Position Constraints. 19 | /// 20 | /// 2D trajectory prediction may become innaccurate when using extremely high linear drag values or if the 21 | /// velocity exceeds Physics2D.MaxTranslationSpeed. 22 | /// 23 | public static class Trajectory 24 | { 25 | #region GetTrajectory2D Methods 26 | 27 | /// 28 | /// Calculates a 's trajectory and returns it as an array of position vectors. 29 | /// 30 | /// The body whose trajectory is being drawn. 31 | /// For predicting the effects of a Rigidbody2D.AddForce() method. Use Vector2.zero if not needed. 32 | /// Determines how the force vector changes the velocity. Irrelevant when using Vector2.zero. 33 | /// An array of the body's expected velocities at each point along the trajectory. 34 | /// Amount of time in seconds to predict. 35 | /// Will the force be applied every FixedUpdate. 36 | /// 37 | /// An array of the body's expected positions after each FixedUpdate in the trajectory. The last index being the 38 | /// final position. 39 | /// 40 | public static Vector2[] GetTrajectory(this Rigidbody2D rb, Vector2 force, ForceMode2D mode, out Vector2[] velocities, 41 | float trajectoryDuration = 1.0f, bool constantForce = false) 42 | { 43 | var maxSpeedErrorThrown = false; 44 | var physicsFramerate = 1 / Time.fixedDeltaTime; 45 | 46 | if (rb.drag >= physicsFramerate) 47 | Debug.LogWarning(rb + " Linear Drag(" + rb.drag + ") too high, trajectory Prediction will be inaccurate." 48 | + " Maximum safe drag = (1 / Time.fixedDeltaTime), currently: " + (physicsFramerate) + 49 | ")."); 50 | 51 | trajectoryDuration = Mathf.Max(Time.fixedDeltaTime * 2, trajectoryDuration); 52 | var points = Mathf.Max(2, Mathf.RoundToInt(trajectoryDuration / Time.fixedDeltaTime)); 53 | 54 | var positions = new Vector2[points]; 55 | velocities = new Vector2[points]; 56 | 57 | positions[0] = rb.transform.position; 58 | velocities[0] = rb.velocity; 59 | 60 | // As far as i can tell... (please correct me if i'm wrong) 61 | // Rigidbody2D.AddForce() applies forces after gravity and drag have already been applied. 62 | // Rigidbody.AddForce() applies forces before gravity and drag. 63 | // This only has a noticable difference when comparing large forces against objects with high drag as 64 | // Rigidbody2D.AddForce is not immediately effected by drag, Rigidbody.AddForce is. 65 | 66 | //Rigidbody2D order: 67 | //Gravity 68 | //Drag 69 | //AddForce() 70 | //Constraints 71 | 72 | for (var i = 0; i < positions.Length - 1; i++) 73 | { 74 | var updatedVelocity = velocities[i]; 75 | 76 | updatedVelocity = updatedVelocity.ApplyGravity(rb); 77 | updatedVelocity = updatedVelocity.ApplyDrag(rb); 78 | 79 | if (i == 0 || constantForce) 80 | updatedVelocity = updatedVelocity.ApplyForce(rb, force, mode); 81 | 82 | if (!maxSpeedErrorThrown && updatedVelocity.magnitude > Physics2D.maxTranslationSpeed * physicsFramerate) 83 | { 84 | Debug.Log(rb + "is expected to reach a speed (" + updatedVelocity.magnitude * Time.fixedDeltaTime + 85 | ") greater than Physics2D.maxTranslationSpeed(" + Physics2D.maxTranslationSpeed + 86 | ") trajectory prediction may be innaccurate, Consider increasing maxTranslationSpeed," + 87 | " mass, linear drag or decreasing manually applied forces"); 88 | maxSpeedErrorThrown = true; 89 | } 90 | 91 | updatedVelocity = updatedVelocity.ApplyConstraints(rb); 92 | 93 | velocities[i] = updatedVelocity; 94 | 95 | positions[i + 1] = positions[i] + (velocities[i] * Time.fixedDeltaTime); 96 | velocities[i + 1] = velocities[i]; 97 | } 98 | 99 | return positions; 100 | } 101 | 102 | /// 103 | /// Calculates a 's trajectory and returns it as an array of position vectors. 104 | /// 105 | /// The body whose trajectory is being drawn. 106 | /// For predicting the effects of a Rigidbody2D.AddForce() method. Use Vector2.zero if not needed. 107 | /// Determines how the force vector changes the velocity. Irrelevant when using Vector2.zero. 108 | /// Amount of time in seconds to predict. 109 | /// Will the force be applied every FixedUpdate. 110 | /// 111 | /// An array of the body's expected positions after each FixedUpdate in the trajectory. The last index being the 112 | /// final position. 113 | /// 114 | public static Vector2[] GetTrajectory(this Rigidbody2D rb, Vector2 force, ForceMode2D mode, 115 | float trajectoryDuration = 1.0f, bool constantForce = false) 116 | { 117 | Vector2[] velocities; 118 | return rb.GetTrajectory(force, mode, out velocities, trajectoryDuration, constantForce); 119 | } 120 | 121 | #endregion 122 | 123 | #region GetTrajectory3D Methods 124 | 125 | /// 126 | /// Calculates a 's trajectory and returns it as an array of position vectors. 127 | /// 128 | /// The body whose trajectory is being drawn. 129 | /// For predicting the effects of a Rigidbody.AddForce() method. Use Vector3.zero if not needed. 130 | /// Determines how the force vector changes the velocity. Irrelevant when using Vector3.zero. 131 | /// An array of the body's expected velocities at each point along the trajectory. 132 | /// Amount of time in seconds to predict. 133 | /// Will the force be applied every FixedUpdate. 134 | /// 135 | /// An array of the body's expected positions after each FixedUpdate in the trajectory. The last index being the 136 | /// final position. 137 | /// 138 | public static Vector3[] GetTrajectory(this Rigidbody rb, Vector3 force, ForceMode mode, out Vector3[] velocities, 139 | float trajectoryDuration = 1.0f, bool constantForce = false) 140 | { 141 | var physicsFramerate = 1 / Time.fixedDeltaTime; 142 | if (rb.drag >= physicsFramerate) 143 | Debug.LogWarning(rb + " Linear Drag(" + rb.drag + ") too high, trajectory Prediction may be inaccurate." 144 | + " Maximum safe drag = (1 / Time.fixedDeltaTime), currently: " + (physicsFramerate) + 145 | ")."); 146 | 147 | trajectoryDuration = Mathf.Max(Time.fixedDeltaTime * 2, trajectoryDuration); 148 | 149 | var points = Mathf.Max(2, Mathf.RoundToInt(trajectoryDuration / Time.fixedDeltaTime)); 150 | 151 | var positions = new Vector3[points]; 152 | velocities = new Vector3[points]; 153 | 154 | positions[0] = rb.transform.position; 155 | velocities[0] = rb.velocity; 156 | 157 | // As far as i can tell... (please correct me if i'm wrong) 158 | // Rigidbody2D.AddForce() applies forces after gravity and drag have already been applied. 159 | // Rigidbody.AddForce() applies forces before gravity and drag. 160 | // This only has a noticable difference when comparing large forces against objects with high drag as 161 | // Rigidbody2D.AddForce is not immediately effected by drag, Rigidbody.AddForce is. 162 | 163 | //Rigidbody order: 164 | //AddForce() 165 | //Gravity 166 | //Drag 167 | //Constraints 168 | 169 | for (var i = 0; i < positions.Length - 1; i++) 170 | { 171 | var updatedVelocity = velocities[i]; 172 | 173 | 174 | if (i == 0 || constantForce) 175 | updatedVelocity = updatedVelocity.ApplyForce(rb, force, mode); 176 | 177 | updatedVelocity = updatedVelocity.ApplyGravity(rb); 178 | updatedVelocity = updatedVelocity.ApplyDrag(rb); 179 | updatedVelocity = updatedVelocity.ApplyConstraints(rb); 180 | 181 | velocities[i] = updatedVelocity; 182 | 183 | positions[i + 1] = positions[i] + (velocities[i] * Time.fixedDeltaTime); 184 | velocities[i + 1] = velocities[i]; 185 | } 186 | return positions; 187 | } 188 | 189 | /// 190 | /// Calculates a 's trajectory and returns it as an array of position vectors. 191 | /// 192 | /// The body whose trajectory is being drawn. 193 | /// For predicting the effects of a Rigidbody.AddForce() method. Use Vector3.zero if not needed. 194 | /// Determines how the force vector changes the velocity. Irrelevant when using Vector3.zero. 195 | /// Amount of time in seconds to predict. 196 | /// Will the force be applied every FixedUpdate. 197 | /// 198 | /// An array of the body's expected positions after each FixedUpdate in the trajectory. The last index being the 199 | /// final position. 200 | /// 201 | public static Vector3[] GetTrajectory(this Rigidbody rb, Vector3 force, ForceMode mode, 202 | float trajectoryDuration = 1.0f, bool constantForce = false) 203 | { 204 | Vector3[] velocities; 205 | return rb.GetTrajectory(force, mode, out velocities, trajectoryDuration, constantForce); 206 | } 207 | 208 | #endregion 209 | 210 | #region DebugTrajectory Methods 211 | 212 | /// 213 | /// Draws a 's trajectory using .DrawLine. 214 | /// 215 | /// The body whose trajectory is being drawn. 216 | /// For predicting the effects of a Rigidbody2D.AddForce() method. Use Vector2.zero if not needed. 217 | /// Determines how the force vector changes the velocity. Irrelevant when using Vector2.zero. 218 | /// The color of the line being drawn. 219 | /// Amount of time in seconds to predict. 220 | /// Amount of time in seconds the drawn line will persist. 221 | /// Will the force be applied every FixedUpdate. 222 | /// Whether or not the line should be faded when behind other objects. 223 | public static void DebugTrajectory(this Rigidbody2D rb, Vector2 force, ForceMode2D mode, Color color, 224 | float trajectoryDuration = 1.0f, float lineDuration = 0.0f, bool constantForce = false, bool depthTest = false) 225 | { 226 | var positions = rb.GetTrajectory(force, mode, trajectoryDuration, constantForce); 227 | for (var i = 0; i < positions.Length - 1; i++) 228 | Debug.DrawLine(positions[i], positions[i + 1], color, lineDuration, depthTest); 229 | } 230 | 231 | /// 232 | /// Draws a 's trajectory using .DrawLine. 233 | /// 234 | /// The body whose trajectory is being drawn. 235 | /// For predicting the effects of a Rigidbody.AddForce() method. Use Vector3.zero if not needed. 236 | /// Determines how the force vector changes the velocity. Irrelevant when using Vector3.zero. 237 | /// The color of the line being drawn. 238 | /// Amount of time in seconds to predict. 239 | /// Amount of time in seconds the drawn line will persist. 240 | /// Will the force be applied every FixedUpdate. 241 | /// Whether or not the line should be faded when behind other objects. 242 | public static void DebugTrajectory(this Rigidbody rb, Vector3 force, ForceMode mode, Color color, 243 | float trajectoryDuration = 1.0f, float lineDuration = 0.0f, bool constantForce = false, bool depthTest = false) 244 | { 245 | var positions = rb.GetTrajectory(force, mode, trajectoryDuration, constantForce); 246 | for (var i = 0; i < positions.Length - 1; i++) 247 | Debug.DrawLine(positions[i], positions[i + 1], color, lineDuration, depthTest); 248 | } 249 | 250 | #endregion 251 | 252 | #region DrawTrajectory Methods 253 | 254 | /// 255 | /// Draws a 's trajectory using .DrawLine. 256 | /// 257 | /// The body whose trajectory is being drawn. 258 | /// For predicting the effects of a Rigidbody2D.AddForce() method. Use Vector2.zero if not needed. 259 | /// Determines how the force vector changes the velocity. Irrelevant when using Vector2.zero. 260 | /// The color of the line being drawn. 261 | /// Amount of time in seconds to predict. 262 | /// Will the force be applied every FixedUpdate. 263 | public static void DrawTrajectory(this Rigidbody2D rb, Vector2 force, ForceMode2D mode, Color color, 264 | float trajectoryDuration = 1.0f, bool constantForce = false) 265 | { 266 | var oldColor = Gizmos.color; 267 | Gizmos.color = color; 268 | 269 | var positions = rb.GetTrajectory(force, mode, trajectoryDuration, constantForce); 270 | for (var i = 0; i < positions.Length - 1; i++) 271 | Gizmos.DrawLine(positions[i], positions[i + 1]); 272 | 273 | Gizmos.color = oldColor; 274 | } 275 | 276 | /// 277 | /// Draws a 's trajectory using .DrawLine. 278 | /// 279 | /// The body whose trajectory is being drawn. 280 | /// For predicting the effects of a Rigidbody.AddForce() method. Use Vector3.zero if not needed. 281 | /// Determines how the force vector changes the velocity. Irrelevant when using Vector3.zero. 282 | /// The color of the line being drawn. 283 | /// Amount of time in seconds to predict. 284 | /// Will the force be applied every FixedUpdate. 285 | public static void DrawTrajectory(this Rigidbody rb, Vector3 force, ForceMode mode, Color color, 286 | float trajectoryDuration = 1.0f, bool constantForce = false) 287 | { 288 | var oldColor = Gizmos.color; 289 | Gizmos.color = color; 290 | 291 | var positions = rb.GetTrajectory(force, mode, trajectoryDuration, constantForce); 292 | for (var i = 0; i < positions.Length - 1; i++) 293 | Gizmos.DrawLine(positions[i], positions[i + 1]); 294 | 295 | Gizmos.color = oldColor; 296 | } 297 | 298 | #endregion 299 | 300 | #region Helper Methods for GetTrajectory 301 | 302 | /// 303 | /// Takes a velocity vector, calculates and adds the gravity forces 304 | /// which the given Rigidbody2D would recieve, then returns the result. 305 | /// 306 | /// The velocity vector to modify 307 | /// The Rigidbody2D used to calculate the gravity forces. 308 | /// Velocity vector with gravity applied. 309 | private static Vector2 ApplyGravity(this Vector2 velocity, Rigidbody2D rb) 310 | { 311 | var gravity = Physics2D.gravity; 312 | var gravityScale = rb.gravityScale; 313 | 314 | if (rb.constraints == RigidbodyConstraints2D.FreezePositionX) 315 | gravity.x = 0; 316 | if (rb.constraints == RigidbodyConstraints2D.FreezePositionY) 317 | gravity.y = 0; 318 | 319 | var newVelocity = velocity + (gravity * gravityScale * Time.fixedDeltaTime); 320 | return newVelocity; 321 | } 322 | 323 | /// 324 | /// Takes a velocity vector, calculates and adds the gravity forces 325 | /// which the given Rigidbody would recieve, then returns the result. 326 | /// 327 | /// The velocity vector to modify 328 | /// The Rigidbody used to calculate the gravity forces. 329 | /// Velocity vector with gravity applied. 330 | private static Vector3 ApplyGravity(this Vector3 velocity, Rigidbody rb) 331 | { 332 | var gravity = Physics.gravity; 333 | 334 | if (rb.useGravity == false) 335 | gravity = Vector3.zero; 336 | 337 | if (rb.constraints == RigidbodyConstraints.FreezePositionX) 338 | gravity.x = 0; 339 | if (rb.constraints == RigidbodyConstraints.FreezePositionY) 340 | gravity.y = 0; 341 | if (rb.constraints == RigidbodyConstraints.FreezePositionZ) 342 | gravity.z = 0; 343 | 344 | var newVelocity = velocity + (gravity * Time.fixedDeltaTime); 345 | return newVelocity; 346 | } 347 | 348 | /// 349 | /// Takes a velocity vector, calculates and adds the effect of 350 | /// the Rigidbody's drag, then returns the result. 351 | /// 352 | /// The velocity vector to modify 353 | /// The Rigidbody used to calculate the drag forces. 354 | /// Velocity vector with drag applied. 355 | private static Vector2 ApplyDrag(this Vector2 velocity, Rigidbody2D rb) 356 | { 357 | var newVelocity = velocity; 358 | var drag = (1 - (rb.drag * Time.fixedDeltaTime)); 359 | 360 | newVelocity *= drag; 361 | return newVelocity; 362 | } 363 | 364 | /// 365 | /// Takes a velocity vector, calculates and adds the effect of 366 | /// the Rigidbody's drag, then returns the result. 367 | /// 368 | /// The velocity vector to modify 369 | /// The Rigidbody used to calculate the drag forces. 370 | /// Velocity vector with drag applied. 371 | private static Vector3 ApplyDrag(this Vector3 velocity, Rigidbody rb) 372 | { 373 | var newVelocity = velocity; 374 | var drag = (1 - (rb.drag * Time.fixedDeltaTime)); 375 | 376 | newVelocity *= drag; 377 | return newVelocity; 378 | } 379 | 380 | /// 381 | /// Takes a velocity vector, calculates and adds the velocity change amount 382 | /// that would be caused by a force with given forcemode against given Rigidbody2D. 383 | /// Then Returns the result. 384 | /// 385 | /// The velocity vector to modify. 386 | /// The Rigidbody2D used to calculate how the force is applied. 387 | /// The force vector to apply. 388 | /// Determines how the force translates into a change in velocity. 389 | /// Velocity vector with force applied. 390 | private static Vector2 ApplyForce(this Vector2 velocity, Rigidbody2D rb, Vector2 force, ForceMode2D mode) 391 | { 392 | var newVelocity = velocity; 393 | 394 | switch (mode) 395 | { 396 | case ForceMode2D.Force: 397 | newVelocity += (force * Time.fixedDeltaTime) / rb.mass; 398 | break; 399 | case ForceMode2D.Impulse: 400 | newVelocity += (force) / rb.mass; 401 | break; 402 | } 403 | 404 | return newVelocity; 405 | } 406 | 407 | /// 408 | /// Takes a velocity vector, calculates and adds the velocity change amount 409 | /// that would be caused by a force with given forcemode against given Rigidbody. 410 | /// Then Returns the result. 411 | /// 412 | /// The velocity vector to modify. 413 | /// The Rigidbody used to calculate how the force is applied. 414 | /// The force vector to apply. 415 | /// Determines how the force translates into a change in velocity. 416 | /// Velocity vector with force applied. 417 | private static Vector3 ApplyForce(this Vector3 velocity, Rigidbody rb, Vector3 force, ForceMode mode) 418 | { 419 | var newVelocity = velocity; 420 | 421 | switch (mode) 422 | { 423 | case ForceMode.Force: 424 | newVelocity += (force * Time.fixedDeltaTime) / rb.mass; 425 | break; 426 | case ForceMode.Acceleration: 427 | newVelocity += force * Time.fixedDeltaTime; 428 | break; 429 | case ForceMode.Impulse: 430 | newVelocity += force / rb.mass; 431 | break; 432 | case ForceMode.VelocityChange: 433 | newVelocity += force; 434 | break; 435 | } 436 | 437 | return newVelocity; 438 | } 439 | 440 | /// 441 | /// Takes a velocity vector and limits it according to a 442 | /// RigidBody2D's constraints then returns the result. 443 | /// 444 | /// The velocity vector to modify. 445 | /// The Rigidbody2D whose constraints will be used against the vector 446 | /// Velocity vector with constraints applied. 447 | private static Vector2 ApplyConstraints(this Vector2 velocity, Rigidbody2D rb) 448 | { 449 | var newVelocity = velocity; 450 | 451 | if ((rb.constraints & RigidbodyConstraints2D.FreezePositionX) == RigidbodyConstraints2D.FreezePositionX) 452 | newVelocity.x = 0; 453 | if ((rb.constraints & RigidbodyConstraints2D.FreezePositionY) == RigidbodyConstraints2D.FreezePositionY) 454 | newVelocity.y = 0; 455 | 456 | newVelocity = Vector2.ClampMagnitude(newVelocity, Physics2D.maxTranslationSpeed * (1 / Time.fixedDeltaTime)); 457 | 458 | return newVelocity; 459 | } 460 | 461 | /// 462 | /// Takes a velocity vector and limits it according to a 463 | /// RigidBody's constraints then returns the result. 464 | /// 465 | /// The velocity vector to modify. 466 | /// The Rigidbody whose constraints will be used against the vector 467 | /// Velocity vector with constraints applied. 468 | private static Vector3 ApplyConstraints(this Vector3 velocity, Rigidbody rb) 469 | { 470 | var newVelocity = velocity; 471 | 472 | if ((rb.constraints & RigidbodyConstraints.FreezePositionX) == RigidbodyConstraints.FreezePositionX) 473 | newVelocity.x = 0; 474 | if ((rb.constraints & RigidbodyConstraints.FreezePositionY) == RigidbodyConstraints.FreezePositionY) 475 | newVelocity.y = 0; 476 | if ((rb.constraints & RigidbodyConstraints.FreezePositionZ) == RigidbodyConstraints.FreezePositionZ) 477 | newVelocity.z = 0; 478 | 479 | return newVelocity; 480 | } 481 | 482 | #endregion 483 | } --------------------------------------------------------------------------------