PageRenderTime 44ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/Rendering/Cameras/LookAtCamera.cs

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