/Rendering/Cameras/LookAtCamera.cs
C# | 503 lines | 298 code | 54 blank | 151 comment | 6 complexity | ca26169872b6728d425c2648e779351c MD5 | raw file
1//#define USE_COMMANDS 2 3using System.IO; 4using Delta.Engine; 5using Delta.InputSystem; 6using Delta.InputSystem.Devices; 7using Delta.Utilities; 8using Delta.Utilities.Datatypes; 9using Delta.Utilities.Helpers; 10using NUnit.Framework; 11 12namespace Delta.Rendering.Cameras 13{ 14 /// <summary> 15 /// 3D camera that support setting of position and target. 16 /// </summary> 17 /// <remarks> 18 /// With this camera you can apply behaviour of looking at given target or 19 /// move around given target. 20 /// </remarks> 21 public class LookAtCamera : BaseCamera 22 { 23 #region Constants 24 25 #region ImplementationVersion 26 /// <summary> 27 /// The current version of the implementation of this Camera class. 28 /// </summary> 29 private const int ImplementationVersion = 1; 30 #endregion 31 32 #region MPS 33 /// <summary> 34 /// The Move speed Per Second. 35 /// </summary> 36 private const float MPS = 100.0f; 37 #endregion 38 39 #region MouseSpeedFactor 40 /// <summary> 41 /// Speed multiplier for mouse control 42 /// </summary> 43 private const float MouseSpeedFactor = 2.75f; 44 #endregion 45 46 #region KeyboardSpeedFactor 47 /// <summary> 48 /// Speed multiplier for keyboard control 49 /// </summary> 50 private const float KeyboardSpeedFactor = 1.0f; 51 #endregion 52 53 #region MinPitchRotation 54 /// <summary> 55 /// Limit for looking down 56 /// </summary> 57 private const float MinPitchRotation = -90 + 1; //2; 58 #endregion 59 60 #region MaxPitchRotation 61 /// <summary> 62 /// Limit for looking up 63 /// </summary> 64 private const float MaxPitchRotation = +90 - 1; //2; 65 #endregion 66 67 #region DefaultLookAtCamPosition 68 /// <summary> 69 /// A default position which can be used if the current camera position 70 /// value isn't known (in the constructor). 71 /// </summary> 72 public static readonly Vector DefaultLookAtCamPosition = 73 new Vector(0.0f, -5.0f, 5.0f); 74 #endregion 75 76 #region DefaultTargetPosition 77 /// <summary> 78 /// A position to a default target twhich can be used if the current camera 79 /// target position value isn't known (in the constructor). 80 /// </summary> 81 public static readonly Vector DefaultTargetPosition = Vector.Zero; 82 #endregion 83 84 #endregion 85 86 #region Public 87 88 #region Position (override) 89 /// <summary> 90 /// The camera position. 91 /// </summary> 92 public override Vector Position 93 { 94 get 95 { 96 return position; 97 } 98 set 99 { 100 // At first directly set the new position 101 position = value; 102 SetPositionValues(); 103 } 104 } 105 #endregion 106 107 #region Target (override) 108 /// <summary> 109 /// Target 110 /// </summary> 111 public override Vector Target 112 { 113 get 114 { 115 return target; 116 } 117 set 118 { 119 // Set the new target (position) 120 target = value; 121 SetPositionValues(); 122 } 123 } 124 #endregion 125 126 #region Distance (override) 127 /// <summary> 128 /// The distance between target position and camera position 129 /// </summary> 130 public override float Distance 131 { 132 get 133 { 134 return base.Distance; 135 } 136 set 137 { 138 base.Distance = value; 139 UpdatePosition(); 140 } // set 141 } 142 143 // Distance 144 #endregion 145 146 #endregion 147 148 #region Private 149 /// <summary> 150 /// Used to be able to reset the position to constructor time 151 /// </summary> 152 private Vector defaultPosition; 153 154 /// <summary> 155 /// Used to be able to reset the target to constructor time 156 /// </summary> 157 private Vector defaultTarget; 158 159 /// <summary> 160 /// Flag determining if shift is pressed. 161 /// Used to make the speed multiplier slower. 162 /// </summary> 163 private bool shiftPressed; 164 165 /// <summary> 166 /// Flag determining if control is pressed. 167 /// Used to make the speed multiplier faster. 168 /// </summary> 169 private bool ctrlPressed; 170 171 #region SpeedMultiplier 172 /// <summary> 173 /// Speed multiplier for every movement 174 /// </summary> 175 private float SpeedMultiplier 176 { 177 get 178 { 179 return shiftPressed 180 ? SlowMultiplier 181 : (ctrlPressed 182 ? FastMultiplier 183 : 1.0f); 184 } 185 } 186 #endregion 187 188 #region MoveSpeed 189 /// <summary> 190 /// Movespeed per second over time. 191 /// </summary> 192 private static float MoveSpeed 193 { 194 get 195 { 196 return MPS * Time.Delta; 197 } // get 198 } 199 #endregion 200 201 #endregion 202 203 #region Constructor 204 /// <summary> 205 /// Create look at camera 206 /// </summary> 207 /// <param name="setPosition">Camera initial position.</param> 208 public LookAtCamera(Vector setPosition) 209 : this(setPosition, DefaultTargetPosition) 210 { 211 } 212 213 /// <summary> 214 /// Create look at camera 215 /// </summary> 216 /// <param name="setPosition">Camera initial position.</param> 217 /// <param name="setTarget">Camera initial target.</param> 218 public LookAtCamera(Vector setPosition, Vector setTarget) 219 : base(setPosition) 220 { 221 target = setTarget; 222 SetPositionValues(); 223 224 // Store defaults 225 defaultPosition = setPosition; 226 defaultTarget = setTarget; 227 shiftPressed = false; 228 ctrlPressed = false; 229 } 230 #endregion 231 232 #region UpdateViewMatrix (override) 233 /// <summary> 234 /// Update view matrix 235 /// </summary> 236 protected override void UpdateViewMatrix() 237 { 238 // Create a standard look at matrix 239 viewMatrix = Matrix.CreateLookAt(Position, Target, UpVector); 240 } 241 #endregion 242 243 #region SetupInputCommands (override) 244 /// <summary> 245 /// Setup the connection between input commands and camera movement. 246 /// </summary> 247 protected override void SetupInputCommands() 248 { 249 // Make sure the commands are used (to allow quitting with Escape) 250 if (Input.Commands == null) 251 { 252 return; 253 } 254 255 } 256 #endregion 257 258 #region Zoom 259 /// <summary> 260 /// Zoom in or out. if respectFPS is set to true, Time.Delta will be taken 261 /// into account. E.g.: set this to true for hold-able buttons. 262 /// </summary> 263 /// <param name="zoomIn">if set to <c>true</c> [zoom in].</param> 264 public void Zoom(bool zoomIn, bool respectFps) 265 { 266 if (IsLocked) 267 { 268 return; 269 } 270 float zoomSpeed = MathHelper.Max(Distance, 0.1f) * 0.1f * 271 SpeedMultiplier * (respectFps 272 ? (Time.Delta * 10f) 273 : 1f); 274 // Limit zoomSpeed, else we get into millions too quickly 275 zoomSpeed = MathHelper.Min(zoomSpeed, 100000 * Time.Delta); 276 Distance += zoomIn 277 ? -zoomSpeed 278 : zoomSpeed; 279 } 280 #endregion 281 282 283 #region RotateHorizontal 284 /// <summary> 285 /// Rotate horizontal 286 /// </summary> 287 /// <param name="left">if set to <c>true</c> [left].</param> 288 public void RotateHorizontal(bool left) 289 { 290 if (IsLocked) 291 { 292 return; 293 } 294 rotation.X += 295 MPS * 0.05f * KeyboardSpeedFactor * 296 (left 297 ? -SpeedMultiplier 298 : SpeedMultiplier) * (Time.Delta * 10f); 299 UpdatePosition(); 300 } 301 #endregion 302 303 #region RotateVertical 304 /// <summary> 305 /// Rotate vertical 306 /// </summary> 307 /// <param name="up">if set to <c>true</c> [up].</param> 308 public void RotateVertical(bool up) 309 { 310 if (IsLocked) 311 { 312 return; 313 } 314 rotation.Y += 315 MPS * 0.05f * KeyboardSpeedFactor * 316 (up 317 ? SpeedMultiplier 318 : -SpeedMultiplier) * (Time.Delta * 10f); 319 UpdatePosition(); 320 } 321 #endregion 322 323 #region ResetCamera 324 /// <summary> 325 /// Resets camera position/rotation to values at constructor time 326 /// </summary> 327 public void ResetCamera() 328 { 329 position = defaultPosition; 330 target = defaultTarget; 331 SetPositionValues(); 332 333 UpdatePosition(); 334 } 335 #endregion 336 337 #region UpdatePosition 338 /// <summary> 339 /// Update position 340 /// </summary> 341 private void UpdatePosition() 342 { 343 // Always clamp the up-down rotation so we don't rotate "over" the 344 // up-vector to avoid the "flipping problem" 345 rotation.Y = MathHelper.Clamp(rotation.Y, MinPitchRotation, 346 MaxPitchRotation); 347 348 Matrix rotationMatrix = Matrix.Identity; 349 // Matrix.CreateRotationY(Z) - We don't need here the "Roll" rotation 350 Matrix xRotation = Matrix.CreateRotationX(rotation.Y); 351 Matrix zRotation = Matrix.CreateRotationZ(rotation.X); 352 Matrix.Multiply(ref xRotation, ref zRotation, ref rotationMatrix); 353 354 // We have to invert here the look vector, because to compute the 355 // position we have to "go" from the target to the position instead of 356 // "looking" case which is the other case (-> look from position to the 357 // target) 358 Vector lookVector = new Vector(0f, Distance, 0f); 359 Vector.TransformNormal(ref lookVector, ref rotationMatrix, ref position); 360 Vector.Add(ref position, ref target, out position); 361 } 362 #endregion 363 364 #region SetPositionValues 365 /// <summary> 366 /// Set position values 367 /// Used by the Position and Target properties. 368 /// </summary> 369 private void SetPositionValues() 370 { 371 // and we have to clear the current rotation now, because the new 372 // position is already "final" 373 374 // Now we still need to know the new direction we have to look to the 375 // (old) target 376 Vector.Subtract(ref target, ref position, out lookDirection); 377 // and store the new distance to (in base because we want to avoid the 378 // unnecessary UpdatePosition() call from Distance here) 379 distance = lookDirection.Length; 380 lookDirection.Normalize(); 381 382 // Position.Z = sin(Rotation.X) -> see trigonometrical functions 383 // Note: we consider at the moment only the x rotation, because we don't 384 // have any rotation on y or z (with default values) 385 rotation.Y = MathHelper.Asin(-LookDirection.Z); 386 rotation.X = -MathHelper.Atan(-LookDirection.X, -lookDirection.Y); 387 } 388 #endregion 389 390 #region ISaveLoadBinary Members 391 392 #region Save (override) 393 /// <summary> 394 /// Override saving data from BinaryStream. 395 /// </summary> 396 /// <param name="dataWriter">Binary writer used for writing data.</param> 397 public override void Save(BinaryWriter dataWriter) 398 { 399 base.Save(dataWriter); 400 401 // Save the implementation version first 402 dataWriter.Write(ImplementationVersion); 403 404 // and then all required values 405 defaultPosition.Save(dataWriter); 406 defaultTarget.Save(dataWriter); 407 } 408 #endregion 409 410 #region Load (override) 411 /// <summary> 412 /// Override loading data from BinaryStream. 413 /// </summary> 414 /// <param name="reader">Binary reader used for reading data.</param> 415 public override void Load(BinaryReader reader) 416 { 417 base.Load(reader); 418 419 // First read the implementation version 420 int version = reader.ReadInt32(); 421 switch (version) 422 { 423 // Version 1 424 case 1: 425 // Now just read the saved values 426 defaultPosition = new Vector(reader); 427 defaultTarget = new Vector(reader); 428 break; 429 430 default: 431 Log.InvalidVersionWarning(GetType().Name + ": " + Name, 432 version, ImplementationVersion); 433 break; 434 } // switch 435 } 436 #endregion 437 438 #endregion 439 440 #region Tests 441 /// <summary> 442 /// Tests 443 /// </summary> 444 internal class LookAtCameraTests 445 { 446 #region TestSaveAndLoad (LongRunning) 447 /// <summary> 448 /// Test save and load functionality of the Graph class 449 /// </summary> 450 [Test, Category("LongRunning")] 451 public void TestSaveAndLoad() 452 { 453 // Creation of the graph 454 LookAtCamera lookAtCamera = new LookAtCamera(new Vector(3, -6, 8)) 455 { 456 // BaseCamera 457 LookDirection = new Vector(-1, 2, -1), 458 Target = new Vector(3, 4, 1), 459 Rotation = new Vector(3, 1, 0), 460 FieldOfView = 65, 461 FarPlane = 205, 462 NearPlane = 0.15f, 463 AlwaysNeedsUpdate = true, 464 IsLocked = true, 465 // LookAtCamera 466 defaultPosition = new Vector(0, -5, 9), 467 defaultTarget = new Vector(3, 3, 1), 468 }; 469 470 // Saving 471 MemoryStream savedStream = new MemoryStream(); 472 BinaryWriter writer = new BinaryWriter(savedStream); 473 lookAtCamera.Save(writer); 474 writer.Flush(); 475 writer = null; 476 477 // Loading 478 savedStream.Position = 0; 479 BinaryReader reader = new BinaryReader(savedStream); 480 LookAtCamera loadedCamera = new LookAtCamera(Vector.Zero); 481 loadedCamera.Load(reader); 482 483 // Checking 484 // BaseCamera values 485 Assert.Equal(loadedCamera.LookDirection, lookAtCamera.LookDirection); 486 Assert.Equal(loadedCamera.Target, lookAtCamera.Target); 487 Assert.Equal(loadedCamera.Rotation, lookAtCamera.Rotation); 488 Assert.Equal(loadedCamera.FieldOfView, lookAtCamera.FieldOfView); 489 Assert.Equal(loadedCamera.FarPlane, lookAtCamera.FarPlane); 490 Assert.Equal(loadedCamera.NearPlane, lookAtCamera.NearPlane); 491 Assert.Equal(loadedCamera.AlwaysNeedsUpdate, 492 lookAtCamera.AlwaysNeedsUpdate); 493 Assert.Equal(loadedCamera.IsLocked, lookAtCamera.IsLocked); 494 // LookAtCamera values 495 Assert.Equal(loadedCamera.defaultPosition, 496 lookAtCamera.defaultPosition); 497 Assert.Equal(loadedCamera.defaultTarget, lookAtCamera.defaultTarget); 498 } 499 #endregion 500 } 501 #endregion 502 } 503}