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 public bool pause = false; 133 134 this(Entity target, float speed) { 135 super(target); 136 this.speed = speed; 137 } 138 139 public void set_xz_angle(float val) { 140 _angle.x = val; 141 } 142 143 /// based on https://github.com/raysan5/raylib/blob/6fa6757a8bf90d4b2fd0ce82dace7c7223635efa/src/camera.h#L400 144 void update() { 145 if (!pause) { 146 _angle.x += speed * Time.delta_time; // camera xz orbit angle 147 } 148 149 // camera distance clamp 150 if (_target_dist < third_person_dist) 151 _target_dist = third_person_dist; 152 153 // update camera position with changes 154 auto npos_x = sin(_angle.x) * _target_dist * cos(_angle.y) + target.position.x; 155 auto npos_y = ((_angle.y <= 0.0f) ? 1 : -1) * sin( 156 _angle.y) * _target_dist * sin(_angle.y) + target.position.y; 157 auto npos_z = cos(_angle.x) * _target_dist * cos(_angle.y) + target.position.z; 158 entity.position = Vector3(npos_x, npos_y, npos_z); 159 } 160 } 161 162 /// third person look camera 163 class CameraThirdPerson : CameraFollow3D { 164 import re.input : Keys, Input; 165 166 mixin Reflect; 167 public float move_sensitivity = 20; 168 public float look_sensitivity = 0.003; 169 protected enum third_person_min_clamp = 5; 170 protected enum third_person_max_clamp = -85; 171 172 this(Entity target) { 173 super(target); 174 } 175 176 // based on https://github.com/raysan5/raylib/blob/6fa6757a8bf90d4b2fd0ce82dace7c7223635efa/src/camera.h#L458 177 void update() { 178 // bool direction[6] = { 179 // IsKeyDown(CAMERA.moveControl[MOVE_FRONT]), IsKeyDown(CAMERA.moveControl[MOVE_BACK]), 180 // IsKeyDown(CAMERA.moveControl[MOVE_RIGHT]), IsKeyDown(CAMERA.moveControl[MOVE_LEFT]), 181 // IsKeyDown(CAMERA.moveControl[MOVE_UP]), IsKeyDown(CAMERA.moveControl[MOVE_DOWN]) 182 // }; 183 // bool[6] direction = [false, false, false, false, false, false]; 184 // bool[6] direction = [ 185 // Input.is_key_down(Keys.KEY_W), Input.is_key_down(Keys.KEY_S), 186 // Input.is_key_down(Keys.KEY_D), Input.is_key_down(Keys.KEY_A), 187 // Input.is_key_down(Keys.KEY_E), Input.is_key_down(Keys.KEY_Q) 188 // ]; 189 // enum MOVE_FRONT = 0; 190 // enum MOVE_BACK = 1; 191 // enum MOVE_RIGHT = 2; 192 // enum MOVE_LEFT = 3; 193 // enum MOVE_UP = 4; 194 // enum MOVE_DOWN = 5; 195 196 // auto dpos_x = (sin(_angle.x) * direction[MOVE_BACK] - sin( 197 // _angle.x) * direction[MOVE_FRONT] - cos( 198 // _angle.x) * direction[MOVE_LEFT] + cos(_angle.x) * direction[MOVE_RIGHT]) / move_sensitivity; 199 // auto npos_x = transform.position.x + dpos_x; 200 201 // auto dpos_y = (sin(_angle.y) * direction[MOVE_FRONT] - sin( 202 // _angle.y) * direction[MOVE_BACK] + 1.0f * direction[MOVE_UP] 203 // - 1.0f * direction[MOVE_DOWN]) / move_sensitivity; 204 // auto npos_y = transform.position.y + dpos_y; 205 206 // auto dpos_z = (cos(_angle.x) * direction[MOVE_BACK] - cos( 207 // _angle.x) * direction[MOVE_FRONT] + sin( 208 // _angle.x) * direction[MOVE_LEFT] - sin(_angle.x) * direction[MOVE_RIGHT]) / move_sensitivity; 209 // auto npos_z = transform.position.z + dpos_z; 210 211 // transform.position = Vector3(npos_x, npos_y, npos_z); 212 213 auto npos_x = transform.position.x; 214 auto npos_y = transform.position.y; 215 auto npos_z = transform.position.z; 216 217 // CAMDATA orientation calculation 218 _angle.x = _angle.x + (Input.mouse_delta.x * -look_sensitivity); 219 _angle.y = _angle.y + (Input.mouse_delta.y * -look_sensitivity); 220 221 // Angle clamp 222 if (_angle.y > third_person_min_clamp * C_DEG2RAD) 223 _angle.y = third_person_min_clamp * C_DEG2RAD; 224 else if (_angle.y < third_person_max_clamp * C_DEG2RAD) 225 _angle.y = third_person_max_clamp * C_DEG2RAD; 226 227 // CAMDATA zoom 228 // _target_dist -= (wheel_delta * CAMERA_MOUSE_SCROLL_SENSITIVITY); 229 230 // CAMDATA distance clamp 231 if (_target_dist < third_person_dist) 232 _target_dist = third_person_dist; 233 234 // TODO: It seems CAMDATA.position is not correctly updated or some rounding issue makes the CAMDATA move straight to target.transform.position... 235 npos_x = sin(_angle.x) * _target_dist * cos(_angle.y) + target.transform.position.x; 236 237 if (_angle.y <= 0.0f) 238 npos_y = sin(_angle.y) * _target_dist * sin(_angle.y) + target.transform.position.y; 239 else 240 npos_y = -sin(_angle.y) * _target_dist * sin(_angle.y) + target.transform.position.y; 241 242 npos_z = cos(_angle.x) * _target_dist * cos(_angle.y) + target.transform.position.z; 243 244 transform.position = Vector3(npos_x, npos_y, npos_z); 245 } 246 } 247 248 /// free-look camera 249 class CameraFreeLook : CameraFollow3D { 250 import re.input : Keys, MouseButton, Input; 251 252 mixin Reflect; 253 // public float move_sensitivity = ; 254 public float look_sensitivity = 0.01; 255 public float zoom_sensitivity = 1.5; 256 public float smooth_zoom_sensitivity = 0.05; 257 protected enum free_min_clamp = 85; 258 protected enum free_max_clamp = -85; 259 protected enum free_dist_min_clamp = 0.3; 260 protected enum free_dist_max_clamp = 120; 261 protected enum free_pan_divider = 5.1; 262 263 this(Entity target) { 264 super(target); 265 } 266 267 // based on https://github.com/raysan5/raylib/blob/6fa6757a8bf90d4b2fd0ce82dace7c7223635efa/src/camera.h#L314 268 void update() { 269 auto npos_x = transform.position.x; 270 auto npos_y = transform.position.y; 271 auto npos_z = transform.position.z; 272 273 auto wheel_delta = Input.scroll_delta(); 274 auto mouse_delta = Input.mouse_delta; 275 276 auto key_pan = Input.is_mouse_down(MouseButton.MOUSE_RIGHT_BUTTON) 277 || Input.is_key_down(Keys.KEY_LEFT_ALT); 278 auto key_alternate = !Input.is_key_down(Keys.KEY_LEFT_SHIFT); 279 auto key_smooth = Input.is_key_down(Keys.KEY_LEFT_CONTROL); 280 281 auto tpos_x = cam.camera.target.x; 282 auto tpos_y = cam.camera.target.y; 283 auto tpos_z = cam.camera.target.z; 284 285 // Camera zoom 286 if ((_target_dist < free_dist_max_clamp) && (wheel_delta < 0)) { 287 _target_dist -= (wheel_delta * zoom_sensitivity); 288 if (_target_dist > free_dist_max_clamp) 289 _target_dist = free_dist_max_clamp; 290 } // Camera looking down 291 else if ((entity.position.y > tpos_y) 292 && (_target_dist == free_dist_max_clamp) && (wheel_delta < 0)) { 293 tpos_x += wheel_delta * (tpos_x - entity.position.x) * zoom_sensitivity / _target_dist; 294 tpos_y += wheel_delta * (tpos_y - entity.position.y) * zoom_sensitivity / _target_dist; 295 tpos_z += wheel_delta * (tpos_z - entity.position.z) * zoom_sensitivity / _target_dist; 296 } else if ((entity.position.y > tpos_y) && (tpos_y >= 0)) { 297 tpos_x += wheel_delta * (tpos_x - entity.position.x) * zoom_sensitivity / _target_dist; 298 tpos_y += wheel_delta * (tpos_y - entity.position.y) * zoom_sensitivity / _target_dist; 299 tpos_z += wheel_delta * (tpos_z - entity.position.z) * zoom_sensitivity / _target_dist; 300 301 // if (tpos_y < 0) tpos_y = -0.001; 302 } else if ((entity.position.y > tpos_y) && (tpos_y < 0) && (wheel_delta > 0)) { 303 _target_dist -= (wheel_delta * zoom_sensitivity); 304 if (_target_dist < free_dist_min_clamp) 305 _target_dist = free_dist_min_clamp; 306 } // Camera looking up 307 else if ((entity.position.y < tpos_y) 308 && (_target_dist == free_dist_max_clamp) && (wheel_delta < 0)) { 309 tpos_x += wheel_delta * (tpos_x - entity.position.x) * zoom_sensitivity / _target_dist; 310 tpos_y += wheel_delta * (tpos_y - entity.position.y) * zoom_sensitivity / _target_dist; 311 tpos_z += wheel_delta * (tpos_z - entity.position.z) * zoom_sensitivity / _target_dist; 312 } else if ((entity.position.y < tpos_y) && (tpos_y <= 0)) { 313 tpos_x += wheel_delta * (tpos_x - entity.position.x) * zoom_sensitivity / _target_dist; 314 tpos_y += wheel_delta * (tpos_y - entity.position.y) * zoom_sensitivity / _target_dist; 315 tpos_z += wheel_delta * (tpos_z - entity.position.z) * zoom_sensitivity / _target_dist; 316 317 // if (tpos_y > 0) tpos_y = 0.001; 318 } else if ((entity.position.y < tpos_y) && (tpos_y > 0) && (wheel_delta > 0)) { 319 _target_dist -= (wheel_delta * zoom_sensitivity); 320 if (_target_dist < free_dist_min_clamp) 321 _target_dist = free_dist_min_clamp; 322 } 323 324 // Input keys checks 325 if (key_pan) { 326 if (key_alternate) // Alternative key behaviour 327 { 328 if (key_smooth) { 329 // Camera smooth zoom 330 _target_dist += (mouse_delta.y * smooth_zoom_sensitivity); 331 } else { 332 // Camera rotation 333 _angle.x += mouse_delta.x * -look_sensitivity; 334 _angle.y += mouse_delta.y * -look_sensitivity; 335 336 // Angle clamp 337 if (_angle.y > free_min_clamp * C_DEG2RAD) 338 _angle.y = free_min_clamp * C_DEG2RAD; 339 else if (_angle.y < free_max_clamp * C_DEG2RAD) 340 _angle.y = free_max_clamp * C_DEG2RAD; 341 } 342 } else { 343 // Camera panning 344 tpos_x += ((mouse_delta.x * look_sensitivity) * cos(_angle.x) + ( 345 mouse_delta.y * look_sensitivity) * sin(_angle.x) * sin(_angle.y)) * ( 346 _target_dist / free_pan_divider); 347 tpos_y += ((mouse_delta.y * look_sensitivity) * cos(_angle.y)) * ( 348 _target_dist / free_pan_divider); 349 tpos_z += ((mouse_delta.x * -look_sensitivity) * sin(_angle.x) + ( 350 mouse_delta.y * look_sensitivity) * cos(_angle.x) * sin(_angle.y)) * ( 351 _target_dist / free_pan_divider); 352 } 353 } 354 355 // Update camera position with changes 356 npos_x = -sin(_angle.x) * _target_dist * cos(_angle.y) + tpos_x; 357 npos_y = -sin(_angle.y) * _target_dist + tpos_y; 358 npos_z = -cos(_angle.x) * _target_dist * cos(_angle.y) + tpos_z; 359 360 transform.position = Vector3(npos_x, npos_y, npos_z); 361 cam.camera.target = Vector3(tpos_x, tpos_y, tpos_z); 362 } 363 }