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 }