1 module re.ng.camera.cam3d; 2 3 import re.ecs; 4 import re.time; 5 import re.math; 6 import re.gfx.raytypes; 7 import re.ng.camera.base; 8 import std.math; 9 static import raylib; 10 11 /// represents a camera for a 3D scene 12 class SceneCamera3D : SceneCamera { 13 mixin Reflect; 14 private raylib.Camera3D _camera; 15 private ProjectionType _projection; 16 17 /// the projection used for the camera 18 public enum ProjectionType { 19 Perspective, 20 Orthographic 21 } 22 23 this() { 24 _camera = raylib.Camera3D(); 25 // default settings 26 up = Vector3(0, 1, 0); // unit vector y+ 27 fov = C_PI_4; // 45 deg 28 projection = ProjectionType.Perspective; 29 } 30 31 /// gets the underlying camera object (used internally) 32 @property ref raylib.Camera3D camera() return { 33 return _camera; 34 } 35 36 /// gets the projection type 37 @property ProjectionType projection() { 38 return _projection; 39 } 40 41 /// sets the projection type 42 @property ProjectionType projection(ProjectionType value) { 43 _projection = value; 44 switch (_projection) { 45 case ProjectionType.Perspective: 46 _camera.type = raylib.CameraType.CAMERA_PERSPECTIVE; 47 break; 48 case ProjectionType.Orthographic: 49 _camera.type = raylib.CameraType.CAMERA_ORTHOGRAPHIC; 50 break; 51 default: 52 assert(0); 53 } 54 return value; 55 } 56 57 /// gets the Y-field-of-view in radians 58 @property float fov() { 59 return _camera.fovy * C_DEG2RAD; 60 } 61 62 /// sets the Y-field-of-view in radians 63 @property float fov(float value) { 64 return _camera.fovy = value * C_RAD2DEG; 65 } 66 67 /// gets the direction that is up relative to this camera 68 @property Vector3 up() { 69 return _camera.up; 70 } 71 72 /// sets the direction that is up relative to this camera 73 @property Vector3 up(Vector3 value) { 74 return _camera.up = value; 75 } 76 77 override void update() { 78 super.update(); 79 80 // copy entity to camera transform 81 _camera.position = entity.transform.position; 82 83 // import std.stdio : writefln; 84 // writefln("cam pos: %s", _camera.position); 85 86 // update raylib camera 87 raylib.UpdateCamera(&_camera); 88 } 89 90 /// orient the camera in the direction of a point 91 public void look_at(Vector3 target) { 92 _camera.target = target; 93 } 94 95 /// orient the camera in the direction of an entity 96 public void look_at(Entity entity) { 97 look_at(entity.position); 98 } 99 } 100 101 abstract class CameraFollow3D : Component, Updatable { 102 mixin Reflect; 103 protected SceneCamera3D cam; 104 /// the target entity 105 public Entity target; 106 protected enum third_person_dist = 1.2f; 107 protected Vector2 _angle; // xz plane camera angle 108 protected float _target_dist; 109 110 this(Entity target) { 111 this.target = target; 112 } 113 114 override void setup() { 115 cam = entity.get_component!SceneCamera3D(); 116 cam.look_at(target); // start by looking at the target 117 118 auto to_target = target.position - entity.position; 119 120 _target_dist = raymath.Vector3Length(to_target); 121 _angle = Vector2(atan2(to_target.x, to_target.z), // Camera angle in plane XZ (0 aligned with Z, move positive CCW) 122 atan2(to_target.y, 123 sqrt(to_target.x * to_target.x + to_target.z * to_target.z))); // // Camera angle in plane XY (0 aligned with X, move positive CW) 124 } 125 } 126 127 /// controls a camera by making it orbit an entity 128 class CameraOrbit : CameraFollow3D { 129 mixin Reflect; 130 /// the orbit speed, in radians per second 131 public float speed; 132 133 this(Entity target, float speed) { 134 super(target); 135 this.speed = speed; 136 } 137 138 /// based on https://github.com/raysan5/raylib/blob/6fa6757a8bf90d4b2fd0ce82dace7c7223635efa/src/camera.h#L400 139 void update() { 140 _angle.x += speed * Time.delta_time; // camera orbit angle 141 142 // camera distance clamp 143 if (_target_dist < third_person_dist) 144 _target_dist = third_person_dist; 145 146 // update camera position with changes 147 auto npos_x = sin(_angle.x) * _target_dist * cos(_angle.y) + target.position.x; 148 auto npos_y = ((_angle.y <= 0.0f) ? 1 : -1) * sin( 149 _angle.y) * _target_dist * sin(_angle.y) + target.position.y; 150 auto npos_z = cos(_angle.x) * _target_dist * cos(_angle.y) + target.position.z; 151 entity.position = Vector3(npos_x, npos_y, npos_z); 152 } 153 } 154 155 /// third person look camera 156 class CameraThirdPerson : CameraFollow3D { 157 import re.input : Keys, Input; 158 159 mixin Reflect; 160 public float move_sensitivity = 20; 161 public float look_sensitivity = 0.003; 162 protected enum third_person_min_clamp = 5; 163 protected enum third_person_max_clamp = -85; 164 165 this(Entity target) { 166 super(target); 167 } 168 169 // based on https://github.com/raysan5/raylib/blob/6fa6757a8bf90d4b2fd0ce82dace7c7223635efa/src/camera.h#L458 170 void update() { 171 // bool direction[6] = { 172 // IsKeyDown(CAMERA.moveControl[MOVE_FRONT]), IsKeyDown(CAMERA.moveControl[MOVE_BACK]), 173 // IsKeyDown(CAMERA.moveControl[MOVE_RIGHT]), IsKeyDown(CAMERA.moveControl[MOVE_LEFT]), 174 // IsKeyDown(CAMERA.moveControl[MOVE_UP]), IsKeyDown(CAMERA.moveControl[MOVE_DOWN]) 175 // }; 176 // bool[6] direction = [false, false, false, false, false, false]; 177 // bool[6] direction = [ 178 // Input.is_key_down(Keys.KEY_W), Input.is_key_down(Keys.KEY_S), 179 // Input.is_key_down(Keys.KEY_D), Input.is_key_down(Keys.KEY_A), 180 // Input.is_key_down(Keys.KEY_E), Input.is_key_down(Keys.KEY_Q) 181 // ]; 182 // enum MOVE_FRONT = 0; 183 // enum MOVE_BACK = 1; 184 // enum MOVE_RIGHT = 2; 185 // enum MOVE_LEFT = 3; 186 // enum MOVE_UP = 4; 187 // enum MOVE_DOWN = 5; 188 189 // auto dpos_x = (sin(_angle.x) * direction[MOVE_BACK] - sin( 190 // _angle.x) * direction[MOVE_FRONT] - cos( 191 // _angle.x) * direction[MOVE_LEFT] + cos(_angle.x) * direction[MOVE_RIGHT]) / move_sensitivity; 192 // auto npos_x = transform.position.x + dpos_x; 193 194 // auto dpos_y = (sin(_angle.y) * direction[MOVE_FRONT] - sin( 195 // _angle.y) * direction[MOVE_BACK] + 1.0f * direction[MOVE_UP] 196 // - 1.0f * direction[MOVE_DOWN]) / move_sensitivity; 197 // auto npos_y = transform.position.y + dpos_y; 198 199 // auto dpos_z = (cos(_angle.x) * direction[MOVE_BACK] - cos( 200 // _angle.x) * direction[MOVE_FRONT] + sin( 201 // _angle.x) * direction[MOVE_LEFT] - sin(_angle.x) * direction[MOVE_RIGHT]) / move_sensitivity; 202 // auto npos_z = transform.position.z + dpos_z; 203 204 // transform.position = Vector3(npos_x, npos_y, npos_z); 205 206 auto npos_x = transform.position.x; 207 auto npos_y = transform.position.y; 208 auto npos_z = transform.position.z; 209 210 // CAMDATA orientation calculation 211 _angle.x = _angle.x + (Input.mouse_delta.x * -look_sensitivity); 212 _angle.y = _angle.y + (Input.mouse_delta.y * -look_sensitivity); 213 214 // Angle clamp 215 if (_angle.y > third_person_min_clamp * C_DEG2RAD) 216 _angle.y = third_person_min_clamp * C_DEG2RAD; 217 else if (_angle.y < third_person_max_clamp * C_DEG2RAD) 218 _angle.y = third_person_max_clamp * C_DEG2RAD; 219 220 // CAMDATA zoom 221 // _target_dist -= (wheel_delta * CAMERA_MOUSE_SCROLL_SENSITIVITY); 222 223 // CAMDATA distance clamp 224 if (_target_dist < third_person_dist) 225 _target_dist = third_person_dist; 226 227 // TODO: It seems CAMDATA.position is not correctly updated or some rounding issue makes the CAMDATA move straight to target.transform.position... 228 npos_x = sin(_angle.x) * _target_dist * cos(_angle.y) + target.transform.position.x; 229 230 if (_angle.y <= 0.0f) 231 npos_y = sin(_angle.y) * _target_dist * sin(_angle.y) + target.transform.position.y; 232 else 233 npos_y = -sin(_angle.y) * _target_dist * sin(_angle.y) + target.transform.position.y; 234 235 npos_z = cos(_angle.x) * _target_dist * cos(_angle.y) + target.transform.position.z; 236 237 transform.position = Vector3(npos_x, npos_y, npos_z); 238 } 239 } 240 241 /// free-look camera 242 class CameraFreeLook : CameraFollow3D { 243 import re.input : Keys, MouseButton, Input; 244 245 mixin Reflect; 246 // public float move_sensitivity = ; 247 public float look_sensitivity = 0.01; 248 public float zoom_sensitivity = 1.5; 249 public float smooth_zoom_sensitivity = 0.05; 250 protected enum free_min_clamp = 85; 251 protected enum free_max_clamp = -85; 252 protected enum free_dist_min_clamp = 0.3; 253 protected enum free_dist_max_clamp = 120; 254 protected enum free_pan_divider = 5.1; 255 256 this(Entity target) { 257 super(target); 258 } 259 260 // based on https://github.com/raysan5/raylib/blob/6fa6757a8bf90d4b2fd0ce82dace7c7223635efa/src/camera.h#L314 261 void update() { 262 auto npos_x = transform.position.x; 263 auto npos_y = transform.position.y; 264 auto npos_z = transform.position.z; 265 266 auto wheel_delta = Input.scroll_delta(); 267 auto mouse_delta = Input.mouse_delta; 268 269 auto key_pan = Input.is_mouse_down(MouseButton.MOUSE_RIGHT_BUTTON) || Input.is_key_down(Keys.KEY_LEFT_ALT); 270 auto key_alternate = !Input.is_key_down(Keys.KEY_LEFT_SHIFT); 271 auto key_smooth = Input.is_key_down(Keys.KEY_LEFT_CONTROL); 272 273 auto tpos_x = cam.camera.target.x; 274 auto tpos_y = cam.camera.target.y; 275 auto tpos_z = cam.camera.target.z; 276 277 // Camera zoom 278 if ((_target_dist < free_dist_max_clamp) && (wheel_delta < 0)) { 279 _target_dist -= (wheel_delta * zoom_sensitivity); 280 if (_target_dist > free_dist_max_clamp) 281 _target_dist = free_dist_max_clamp; 282 } // Camera looking down 283 else if ((entity.position.y > tpos_y) && (_target_dist == free_dist_max_clamp) 284 && (wheel_delta < 0)) { 285 tpos_x += wheel_delta * (tpos_x - entity.position.x) * zoom_sensitivity / _target_dist; 286 tpos_y += wheel_delta * (tpos_y - entity.position.y) * zoom_sensitivity / _target_dist; 287 tpos_z += wheel_delta * (tpos_z - entity.position.z) * zoom_sensitivity / _target_dist; 288 } else if ((entity.position.y > tpos_y) && (tpos_y >= 0)) { 289 tpos_x += wheel_delta * (tpos_x - entity.position.x) * zoom_sensitivity / _target_dist; 290 tpos_y += wheel_delta * (tpos_y - entity.position.y) * zoom_sensitivity / _target_dist; 291 tpos_z += wheel_delta * (tpos_z - entity.position.z) * zoom_sensitivity / _target_dist; 292 293 // if (tpos_y < 0) tpos_y = -0.001; 294 } else if ((entity.position.y > tpos_y) && (tpos_y < 0) && (wheel_delta > 0)) { 295 _target_dist -= (wheel_delta * zoom_sensitivity); 296 if (_target_dist < free_dist_min_clamp) 297 _target_dist = free_dist_min_clamp; 298 } // Camera looking up 299 else if ((entity.position.y < tpos_y) && (_target_dist == free_dist_max_clamp) 300 && (wheel_delta < 0)) { 301 tpos_x += wheel_delta * (tpos_x - entity.position.x) * zoom_sensitivity / _target_dist; 302 tpos_y += wheel_delta * (tpos_y - entity.position.y) * zoom_sensitivity / _target_dist; 303 tpos_z += wheel_delta * (tpos_z - entity.position.z) * zoom_sensitivity / _target_dist; 304 } else if ((entity.position.y < tpos_y) && (tpos_y <= 0)) { 305 tpos_x += wheel_delta * (tpos_x - entity.position.x) * zoom_sensitivity / _target_dist; 306 tpos_y += wheel_delta * (tpos_y - entity.position.y) * zoom_sensitivity / _target_dist; 307 tpos_z += wheel_delta * (tpos_z - entity.position.z) * zoom_sensitivity / _target_dist; 308 309 // if (tpos_y > 0) tpos_y = 0.001; 310 } else if ((entity.position.y < tpos_y) && (tpos_y > 0) && (wheel_delta > 0)) { 311 _target_dist -= (wheel_delta * zoom_sensitivity); 312 if (_target_dist < free_dist_min_clamp) 313 _target_dist = free_dist_min_clamp; 314 } 315 316 // Input keys checks 317 if (key_pan) { 318 if (key_alternate) // Alternative key behaviour 319 { 320 if (key_smooth) { 321 // Camera smooth zoom 322 _target_dist += (mouse_delta.y * smooth_zoom_sensitivity); 323 } else { 324 // Camera rotation 325 _angle.x += mouse_delta.x * -look_sensitivity; 326 _angle.y += mouse_delta.y * -look_sensitivity; 327 328 // Angle clamp 329 if (_angle.y > free_min_clamp * C_DEG2RAD) 330 _angle.y = free_min_clamp * C_DEG2RAD; 331 else if (_angle.y < free_max_clamp * C_DEG2RAD) 332 _angle.y = free_max_clamp * C_DEG2RAD; 333 } 334 } else { 335 // Camera panning 336 tpos_x += ((mouse_delta.x * look_sensitivity) * cos(_angle.x) + ( 337 mouse_delta.y * look_sensitivity) * sin(_angle.x) * sin(_angle.y)) * ( 338 _target_dist / free_pan_divider); 339 tpos_y += ((mouse_delta.y * look_sensitivity) * cos(_angle.y)) * ( 340 _target_dist / free_pan_divider); 341 tpos_z += ((mouse_delta.x * -look_sensitivity) * sin(_angle.x) + ( 342 mouse_delta.y * look_sensitivity) * cos(_angle.x) * sin(_angle.y)) * ( 343 _target_dist / free_pan_divider); 344 } 345 } 346 347 // Update camera position with changes 348 npos_x = -sin(_angle.x) * _target_dist * cos(_angle.y) + tpos_x; 349 npos_y = -sin(_angle.y) * _target_dist + tpos_y; 350 npos_z = -cos(_angle.x) * _target_dist * cos(_angle.y) + tpos_z; 351 352 transform.position = Vector3(npos_x, npos_y, npos_z); 353 cam.camera.target = Vector3(tpos_x, tpos_y, tpos_z); 354 } 355 }