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
6  * Documentation:
7  * Coverage:
8  */
9 module liberty.core.scenegraph.camera;
10 import liberty.core.engine: CoreEngine;
11 import liberty.core.scenegraph.actor: Actor;
12 import liberty.core.scenegraph.node: Node;
13 import liberty.core.scenegraph.services: NodeServices, Constructor;
14 import liberty.core.input: Input, KeyCode, KeyModFlag, MouseButton;
15 import liberty.math.functions: radians, sin, cos;
16 import liberty.math.vector: Vector2I, Vector3F, cross;
17 import liberty.math.matrix: Matrix4F;
18 ///
19 enum CameraProjection: byte {
20     /// For 3D and 2D views
21     Perspective = 0x00,
22     /// Only for 2D views
23     Orthographic = 0x01
24 }
25 ///
26 enum CameraMovement: byte {
27 	///
28 	Forward = 0x00,
29 	///
30 	Backward = 0x01,
31 	///
32 	Left = 0x02,
33 	///
34 	Right = 0x03
35 }
36 ///
37 final class Camera : Actor {
38 	mixin(NodeServices);
39 	private {
40 		Matrix4F _projection;
41         Matrix4F _view;
42 		// Attributes.
43 		Vector3F _position;
44 		Vector3F _front;
45 		Vector3F _up;
46 		Vector3F _right;
47 		Vector3F _worldUp;
48 		// Euler Angles.
49 		float _yaw;
50 		float _pitch;
51 		// Options.
52 		float _movementSpeedMultiplier = 100.0f;
53 		float _movementSpeed = 8.0f;
54 		float _mouseSensitivity = 0.1f;
55 		float _fov = 45.0f;
56 		bool _constrainPitch = true;
57 		bool _firstMouse = true;
58 		bool _scrollReversedOrder = true; // TODO: For Speed Multiplier.
59 		float _lastX;
60 		float _lastY;
61 		float _nearPlane = 0.1f;
62 		float _farPlane = 1000.0f;
63 		CameraProjection _cameraProjection = CameraProjection.Perspective;
64 	}
65 	///
66 	enum Yaw = -90.0f;
67 	///
68 	enum Pitch = 0.0f;
69 	///
70 	float fov() pure nothrow const {
71 		return _fov;
72 	}
73 	///
74 	float nearPlane() pure nothrow const {
75 		return _nearPlane;
76 	}
77 	///
78 	float farPlane() pure nothrow const {
79 		return _farPlane;
80 	}
81 	///
82 	@Constructor private void _(){
83 		_front = Vector3F.backward;
84         _position = Vector3F.zero;
85         _worldUp = Vector3F.up;
86         _yaw = Yaw;
87         _pitch = Pitch;
88         updateCameraVectors();
89         _lastX = CoreEngine.get.mainWindow.width / 2;
90         _lastY = CoreEngine.get.mainWindow.height / 2;
91 	}
92 	///
93 	this(string id, Node parent, Vector3F position = Vector3F.zero, Vector3F up = Vector3F.up, float yaw = Yaw, float pitch = Pitch) {
94 		super(id, parent);
95 	}
96 	///
97 	@property void position(Vector3F position) {
98 		_position = position;
99 	}
100 	///
101 	Matrix4F projectionMatrix() {
102 		Vector2I size = CoreEngine.get.mainWindow.size;
103 		final switch (_cameraProjection) with (CameraProjection) {
104 			case Perspective:
105 				return Matrix4F.perspective(radians(_fov), cast(float)size.x / size.y, _nearPlane, _farPlane);
106 			case Orthographic:
107 				return Matrix4F.orthographic(0, size.x, size.y, 0, _nearPlane, _farPlane);
108 		}
109 	}
110 	///
111 	Matrix4F viewMatrix() {
112 		return Matrix4F.lookAt(_position, _position + _front, _up);
113 	}
114 	///
115 	void fov(float value) {
116 		_fov = value;
117 	}
118 	///
119 	void resetFov() {
120 		_fov = 45.0f;
121 	}
122 	///
123 	override void update(in float deltaTime) {
124 		if (scene.activeCamera.id == id) {
125 			float velocity = _movementSpeed * deltaTime;
126 			// Process Mouse Scroll.
127 			if (Input.get.isMouseScrolling()) {
128 				if (Input.get.isKeyHold(KeyModFlag.LeftCtrl)) {
129 					if(_fov >= 1.0f && _fov <= 45.0f) {
130 						if (_scrollReversedOrder) {
131 							_fov += Input.get.mouseDeltaWheelY();
132 						} else {
133 							_fov -= Input.get.mouseDeltaWheelY();
134 						}
135 					}
136 					if(_fov <= 1.0f) {
137 						_fov = 1.0f;
138 					}
139 					if(_fov >= 45.0f) {
140 						_fov = 45.0f;
141 					}
142 				} else {
143 					velocity *= _movementSpeedMultiplier;
144 				}
145 			}
146 			// Process Keyboard.
147 			if (Input.get.isKeyHold(KeyCode.W)) {
148 				_position += _front * velocity;
149 			}
150 			if (Input.get.isKeyHold(KeyCode.S)) {
151 				_position -= _front * velocity;
152 			}
153 			if (Input.get.isKeyHold(KeyCode.A)) {
154 				_position -= _right * velocity;
155 			}
156 			if (Input.get.isKeyHold(KeyCode.D)) {
157 				_position += _right * velocity;
158 			}
159 			// Process Mouse Movement.
160 			if (Input.get.isMouseButtonPressed(MouseButton.Right)) {
161 				Input.get.windowGrab = true;
162 				Input.get.cursorVisible = false;
163 				if (Input.get.isMouseMoving()) {
164 					if (_firstMouse) {
165 						_lastX = Input.get.mousePosition.x;
166 						_lastY = Input.get.mousePosition.y;
167 						_firstMouse = false;
168 					}
169 					float xoffset = Input.get.mousePosition.x - _lastX;
170 					float yoffset = _lastY - Input.get.mousePosition.y;
171 					_lastX = Input.get.mousePosition.x;
172 					_lastY = Input.get.mousePosition.y;
173 					xoffset *= _mouseSensitivity;
174 					yoffset *= _mouseSensitivity;
175 					_yaw   += xoffset;
176 					_pitch += yoffset;
177 					if (_constrainPitch) {
178 						if (_pitch > 89.0f) {
179 							_pitch =  89.0f;
180 						}
181 						if (_pitch < -89.0f) {
182 							_pitch = -89.0f;
183 						}
184 					}
185 					updateCameraVectors();
186 				}
187 			} else {
188 				Input.get.windowGrab = false;
189 				Input.get.cursorVisible = true;
190 				_firstMouse = true;
191 			}
192 			_projection = projectionMatrix;
193 			_view = viewMatrix;
194 		}
195 	}
196 	///
197 	ref const(Matrix4F) projection() {
198 		return _projection;
199 	}
200 	///
201 	ref const(Matrix4F) view() {
202 		return _view;
203 	}
204 	///
205 	void rotateYaw(float value) {
206 		_yaw += value;
207 	}
208 	///
209     void rotatePitch(float value) {
210         _pitch += value;
211     }
212     ///
213     void yaw(float value) {
214         _yaw = value;
215     }
216     ///
217     void pitch(float value) {
218         _pitch = value;
219     }
220 	///
221 	void switchProjectionType() {
222 		final switch (_cameraProjection) with (CameraProjection) {
223 			case Perspective:
224 				_cameraProjection = Orthographic;
225 				break;
226 			case Orthographic:
227 				_cameraProjection = Perspective;
228 				break;
229 		}
230 	}
231 	private void updateCameraVectors() {
232 		Vector3F front;
233 		front.x = cos(radians(_yaw)) * cos(radians(_pitch));
234         front.y = sin(radians(_pitch));
235         front.z = sin(radians(_yaw)) * cos(radians(_pitch));
236         _front = front.normalized();
237         _right = _front.cross(_worldUp);
238         _right.normalize();
239         _up = _right.cross(_front);
240         _up.normalize();
241 	}
242 }