1 /** 2 * Retrograde Engine 3 * 4 * Authors: 5 * Mike Bierlee, m.bierlee@lostmoment.com 6 * Copyright: 2014-2021 Mike Bierlee 7 * License: 8 * This software is licensed under the terms of the MIT license. 9 * The full terms of the license can be found in the LICENSE.txt file. 10 */ 11 12 module retrograde.camera; 13 14 import retrograde.entity; 15 import retrograde.math; 16 import retrograde.input; 17 import retrograde.messaging; 18 import retrograde.camera; 19 import retrograde.stringid; 20 21 import std.math; 22 23 import poodinis; 24 25 class CameraComponent : EntityComponent { 26 mixin EntityComponentIdentity!"CameraComponent"; 27 } 28 29 class PitchYawComponent : EntityComponent { 30 mixin EntityComponentIdentity!"PitchYawComponent"; 31 32 public Vector2D pitchYawVector; 33 34 this() { 35 this(Vector2D(0)); 36 } 37 38 this(Vector2D pitchYawVector) { 39 this.pitchYawVector = pitchYawVector; 40 } 41 } 42 43 class FirstPersonCameraEntityFactory : EntityFactory { 44 public const string entityName = "CameraEntity"; 45 46 this() { 47 super(entityName); 48 } 49 50 public override Entity createEntity(CreationParameters parameters = null) { 51 auto entity = createBlankEntity(); 52 53 entity.addComponent!CameraComponent; 54 entity.addComponent!Position3DComponent; 55 entity.addComponent!PitchYawComponent; 56 57 return entity; 58 } 59 } 60 61 enum CameraRotateCommand : StringId { 62 pitch = sid("cmd_camera_rotate_pitch"), 63 yaw = sid("cmd_camera_rotate_yaw") 64 } 65 66 enum CameraRotationModeCommand : StringId { 67 setMouseLook = sid("cmd_camera_mode_set_mouse_look"), 68 setStickLook = sid("cmd_camera_mode_set_stick_look") 69 } 70 71 enum CameraMoveCommand : StringId { 72 moveForwardBackward = sid("cmd_camera_move_forward_back"), 73 moveSideways = sid("cmd_camera_move_sideways") 74 } 75 76 class FirstPersonFlyCameraProcessor : EntityProcessor { 77 78 private Entity activeCamera; 79 80 private double addedPitch = 0; 81 private double addedYaw = 0; 82 private double addedMovement = 0; 83 private double addedStrafe = 0; 84 private CameraRotationModeCommand rotationMode = CameraRotationModeCommand.setMouseLook; 85 private const double pitchIncrements = (2 * PI) / 200; 86 private const double yawIncrements = pitchIncrements; 87 88 @Autowire 89 private MappedInputCommandChannel mappedInputCommandChannel; //TODO: Receive from different channel. Route from mapped command. 90 91 public override void initialize() { 92 mappedInputCommandChannel.connect(&handleInputEvent); 93 }; 94 95 public override bool acceptsEntity(Entity entity) { 96 return entity.hasComponent!CameraComponent && entity.hasComponent!PitchYawComponent; 97 } 98 99 public override void processAcceptedEntity(Entity entity) { 100 activeCamera = entity; 101 } 102 103 public override void update() { 104 auto pitchYawComponent = activeCamera.getComponent!PitchYawComponent; 105 auto currentPitchYaw = pitchYawComponent.pitchYawVector; 106 107 if ((addedPitch != 0 || addedYaw != 0)) { 108 auto newPitchYaw = currentPitchYaw - Vector2D(pitchIncrements * addedPitch, yawIncrements * addedYaw); 109 constrainPitchYaw(&newPitchYaw); 110 pitchYawComponent.pitchYawVector = newPitchYaw; 111 currentPitchYaw = newPitchYaw; 112 113 if (rotationMode == CameraRotationModeCommand.setMouseLook) { 114 addedPitch = 0; 115 addedYaw = 0; 116 } 117 } 118 119 if (addedMovement != 0 || addedStrafe) { 120 auto sideDirectionVector = radiansToUnitVector(currentPitchYaw.y); 121 auto directionVector = radiansToUnitVector(currentPitchYaw.y - PI_2); 122 123 auto positionComponent = activeCamera.getComponent!Position3DComponent; 124 auto newPosition = positionComponent.position; 125 126 if (addedMovement != 0) { 127 auto translation = Vector3D(directionVector.x, 0, -directionVector.y) * addedMovement; 128 newPosition = newPosition + translation; 129 } 130 131 if (addedStrafe != 0) { 132 auto translation = Vector3D(sideDirectionVector.x, 0, -sideDirectionVector.y) * addedStrafe; 133 newPosition = newPosition + translation; 134 } 135 136 positionComponent.position = newPosition; 137 } 138 }; 139 140 private void constrainPitchYaw(Vector2D* vector) { 141 vector.x = clampPitch(vector.x.wrapAngle); 142 vector.y = vector.y.wrapAngle; 143 } 144 145 private double clampPitch(double pitch) { 146 if (pitch > PI_2 && pitch < TwoPI - PI_2) { 147 if (pitch > PI) return TwoPI - PI_2; 148 return PI_2; 149 } 150 151 return pitch; 152 } 153 154 private void handleInputEvent(const(Event) event) { 155 switch (event.type) { 156 case CameraRotateCommand.pitch: 157 if (rotationMode == CameraRotationModeCommand.setStickLook) { 158 addedPitch = event.magnitude; 159 } else { 160 addedPitch += event.magnitude; 161 } 162 break; 163 164 case CameraRotateCommand.yaw: 165 if (rotationMode == CameraRotationModeCommand.setStickLook) { 166 addedYaw = event.magnitude; 167 } else { 168 addedYaw += event.magnitude; 169 } 170 break; 171 172 case CameraMoveCommand.moveForwardBackward: 173 addedMovement = -event.magnitude; 174 break; 175 176 case CameraMoveCommand.moveSideways: 177 addedStrafe = event.magnitude; 178 break; 179 180 case CameraRotationModeCommand.setMouseLook: 181 rotationMode = CameraRotationModeCommand.setMouseLook; 182 break; 183 184 case CameraRotationModeCommand.setStickLook: 185 rotationMode = CameraRotationModeCommand.setStickLook; 186 break; 187 188 default: 189 break; 190 } 191 } 192 193 }