1 /**
2  * Copyright:       Copyright (C) 2018 Gabriel Gheorghe, All Rights Reserved
3  * Authors:         $(Gabriel Gheorghe)
4  * License:         $(LINK2 https://www.gnu.org/licenses/gpl-3.0.txt, GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007)
5  * Source:          $(LINK2 https://github.com/GabyForceQ/LibertyEngine/blob/master/source/liberty/camera/impl.d)
6  * Documentation:
7  * Coverage:
8 **/
9 module liberty.camera.impl;
10 
11 import liberty.core.engine;
12 import liberty.math.functions;
13 import liberty.math.transform;
14 import liberty.math.vector;
15 import liberty.math.matrix;
16 import liberty.core.platform;
17 import liberty.time;
18 import liberty.camera.constants;
19 import liberty.camera.preset;
20 import liberty.scene.meta;
21 import liberty.scene.entity;
22 import liberty.scene.impl;
23 
24 /**
25  * Represents the view of the observer.
26  * Everything that is rendered to the screen is processed within the projection matrix and view matrix of a camera.
27  * Inheriths $(D Entity) class and encapsulates $(D NodeBody) macro.
28  * It has a custom constructor that calls: $(D updateCameraVectors) and adds default $(D CameraPreset).
29 **/
30 final class Camera : Entity {
31   mixin NodeBody;
32 
33   private {
34     float _yaw = -90.0f;
35     float _pitch = -30.0f;
36     float _zNear = 0.01f;
37     float _zFar = 1000.0f;
38   }
39 
40   ///
41   Vector3F frontVector = Vector3F.forward;
42   ///
43   Vector3F upVector = Vector3F.up;
44   ///
45   Vector3F rightVector = Vector3F.zero;
46   ///
47   Vector3F worldUpVector = Vector3F.up;
48   ///
49   float mouseSensitivity = 0.1f;
50   ///
51   float fieldOfView = 45.0f;
52   ///
53   float movementSpeed = 10.0f;
54   ///
55   bool mouseMoveLocked;
56   ///
57   bool mouseScrollLocked;
58   ///
59   bool keyboardLocked;
60   ///
61   bool constrainPitch = true;
62   ///
63   CameraPreset preset;
64 
65   /// Default camera constructor.
66   this(string id) {
67     super(id);
68     register;
69 
70     updateCameraVectors;
71     preset = CameraPreset.getDefault;
72     
73     component!Transform
74       .setLocation(0.0f, 3.0f, 4.0f);
75   }
76 
77   /// Get camera yaw.
78   @property float yaw() { return _yaw; }
79 
80   /// Get camera pitch.
81   @property float pitch() { return _pitch; }
82 
83   /// Get camera zNear.
84   @property float zNear() { return _zNear; }
85 
86   /// Get camera zFar.
87   @property float zFar() { return _zFar; }
88 
89   /// Set camera yaw.
90   @property void yaw(float value) {
91     updateCameraVectors;
92     _yaw = value;
93   }
94 
95   /// Set camera pitch.
96   @property void pitch(float value) {
97     checkPitchLimits;
98     updateCameraVectors;
99     _pitch = value;
100   }
101 
102   /// Set camera zNear.
103   @property void zNear(float value) {
104     checkZNearLimits;
105     _zNear = value;
106   }
107 
108   /// Set camera zFar.
109   @property void zFar(float value) {
110     checkZFarLimits;
111     _zFar = value;
112   }
113 
114   /**
115    * Set keyboard listener using a camera movement direction.
116    * Works only if camera keyboard listener isn't locked.
117    * Returns reference to this so it can be used in a stream.
118   **/
119   typeof(this) processKeyboard(CameraMovement direction) {
120     if (!keyboardLocked) {
121       const float velocity = movementSpeed * Time.getDelta();
122       preset.runKeyboardProcess(this, direction, velocity);
123     }
124 
125     return this;
126   }
127 
128   /**
129    * Set mouse move listener using x and y offsets.
130    * Works only if camera mouse move listener isn't locked.
131    * If it works then it updates camera vectors at the end.
132    * Returns reference to this so it can be used in a stream.
133   **/
134   typeof(this) processMouseMovement(float xOffset, float yOffset) {
135     if (!mouseMoveLocked) {
136       xOffset *= mouseSensitivity;
137       yOffset *= mouseSensitivity;
138 
139       _yaw += xOffset;
140       _pitch += yOffset;
141 
142       checkPitchLimits;
143       updateCameraVectors;
144     }
145 
146     return this;
147   }
148 
149   /**
150    * Set mouse scroll listener using y offset.
151    * Works only if camera mouse scroll listener isn't locked.
152    * Returns reference to this so it can be used in a stream.
153   **/
154   typeof(this) processMouseScroll(float yOffset)   {
155     if (!mouseScrollLocked) {
156       if (fieldOfView >= 1.0f && fieldOfView <= 45.0f)
157         fieldOfView -= yOffset;
158       if (fieldOfView <= 1.0f)
159         fieldOfView = 1.0f;
160       if (fieldOfView >= 45.0f)
161         fieldOfView = 45.0f;
162     }
163 
164     return this;
165   }
166 
167   /**
168    * Returns camera view matrix.
169   **/
170   Matrix4F viewMatrix() {
171     return Matrix4F.lookAt(
172       component!Transform.getLocation,
173       component!Transform.getLocation + frontVector,
174       upVector
175     );
176   }
177 
178   /**
179    * Returns camera projection matrix.
180   **/
181   Matrix4F projectionMatrix()  {
182     return Matrix4F.perspective(
183       fieldOfView.radians,
184       cast(float)Platform.getWindow.getWidth,
185       cast(float)Platform.getWindow.getHeight,
186       _zNear,
187       _zFar
188     );
189   }
190 
191   private void updateCameraVectors() {
192     Vector3F front;
193 
194     front.x = cos(radians(_yaw)) * cos(radians(_pitch));
195     front.y = sin(radians(_pitch));
196     front.z = sin(radians(_yaw)) * cos(radians(_pitch));
197     
198     frontVector = front.normalized;
199     rightVector = cross(frontVector, worldUpVector).normalized;
200     upVector = cross(rightVector, frontVector).normalized;
201   }
202 
203   pragma (inline, true)
204   private void checkPitchLimits()   {
205     if (constrainPitch) {
206       if (_pitch > 89.0f)
207         _pitch = 89.0f;
208       if (_pitch < -89.0f)
209         _pitch = -89.0f;
210     }
211   }
212 
213   pragma (inline, true)
214   private void checkZNearLimits()   {
215     if (_zNear < 0.001f)
216       _zNear = 0.001f;
217     if (_zNear > 10_000.0f)
218       _zNear = 10_000.0f;
219   }
220 
221   pragma (inline, true)
222   private void checkZFarLimits()   {
223     if (_zFar < 0.001f)
224       _zFar = 0.0001f;
225     if (_zFar > 10_000.0f)
226       _zFar = 10_000.0f;
227   }
228 }