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 }