1 module armos.graphics.easycam;
2 import armos.graphics.camera;
3 import armos.math;
4 
5 private alias V2 = Vector2f;
6 private alias V3 = Vector3f;
7 private alias M3 = Matrix3f;
8 private alias Q  = Quaternionf;
9 
10 private enum ManupilationType {
11     RotationXY,
12     RotationZ,
13     Translation
14 }//enum ManupilationType
15 
16 ///
17 class EasyCam :Camera{
18     import armos.events;
19     import armos.app;
20     import rx;
21     private  alias This = typeof(this);
22     mixin CameraImpl;
23 
24     public{
25         ///
26         this(){
27             _subjectTranslate = new SubjectObject!V3;
28             _subjectRotate    = new SubjectObject!V3;
29             _subjectZoom      = new SubjectObject!float;
30             // _moussePositionPre = 
31             _manupilationType = ManupilationType.RotationXY;
32             _arcBall = new ArcBall;
33 
34             reset();
35         }
36 
37         ///
38         ~this(){
39             if(_hasSetEvents){
40                 removeEvents();
41             }
42         }
43 
44         ///
45         This update(){
46             if(!_hasSetEvents){
47                 addEvents();
48             }
49 
50             if(_isButtonPressed){
51                 _mouseVelocity = _mousePositionCurrent - _mousePositionPre;
52             }else{
53                 _mouseVelocity = V2.zero;
54             }
55 
56             import armos.graphics:viewportSize;
57             import std.math;
58             _unitDistance = viewportSize.y / (2.0f * tan(PI * _fov / 360.0f));
59             _arcBall.radius = viewportSize.y*0.5;
60 
61             if(_isButtonPressed){
62             }
63             updateTranslation();
64             updateRotation();
65 
66             import armos.app:targetFps;
67             _arcBall.update(1.0/targetFps);
68 
69             updateCameraAriment();
70 
71             _mousePositionPre = _mousePositionCurrent;
72             _mouseScrollAmount = 0;
73             return this;
74         }
75 
76         ///
77         This reset(){
78             _hasSetDistance = false;
79             _arcBall.reset;
80             _distanceRate = 1.0;
81             _mouseScrollAmount = 0.0;
82             return this;
83         }
84 
85         
86     }//public
87 
88     private{
89         bool _hasSetDistance;
90         bool _hasSetEvents = false;
91         ArcBall _arcBall;
92         V3 _angularSensitivity = V3(40, 40, 0.05);
93         V3 _linearSensitivity = V3(5, 5, 0.02);
94 
95         V2 _mousePositionPre;
96         V2 _mousePositionCurrent;
97         V2 _mouseVelocity;
98         float _mouseScrollAmount;
99         float _distanceRate = 1.0;
100 
101         float _unitDistance;
102 
103         ManupilationType _manupilationType;
104         bool _isButtonPressed = false;
105         bool _isShiftPressed = false;
106 
107         Disposable _disposableKeyPressed;
108         Disposable _disposableKeyReleased;
109         Disposable _disposableMousePressed;
110         Disposable _disposableMouseDragged;
111         Disposable _disposableMouseReleased;
112         Disposable[string] _disposables;
113 
114         SubjectObject!V3    _subjectTranslate;
115         SubjectObject!V3    _subjectRotate;
116         SubjectObject!float _subjectZoom;
117 
118 
119         This addEvents(){
120             import std.typecons;
121             import armos.utils.keytype;
122             _disposables["keyPressed"]    = currentObservables.keyPressed
123                                                               .filter!(event => event.key == KeyType.LeftShift || event.key == KeyType.RightShift)
124                                                               .doSubscribe!((event){_isShiftPressed = true;
125                                                                       });
126             _disposables["keyReleased"]   = currentObservables.keyReleased
127                                                               .filter!(event => event.key == KeyType.LeftShift || event.key == KeyType.RightShift)
128                                                               .doSubscribe!((event){_isShiftPressed = false;
129                                                                       });
130 
131             _disposables["mousePressed"]  = currentObservables.mousePressed
132                                                               .doSubscribe!((event){
133                                                                                        if(_isShiftPressed){
134                                                                                            _manupilationType = ManupilationType.Translation;
135                                                                                            _isButtonPressed = true;
136                                                                                            _mousePositionCurrent = V2(event.x, event.y);
137                                                                                            _mousePositionPre = _mousePositionCurrent;
138                                                                                            _mouseVelocity = V2.zero;
139                                                                                        }else{
140                                                                                            if(V2(event.x, event.y).isInside(_arcBall.radius)){
141                                                                                                _isButtonPressed = true;
142                                                                                                _mousePositionCurrent = V2(event.x, event.y);
143                                                                                                _mousePositionPre = _mousePositionCurrent;
144                                                                                                _mouseVelocity = V2.zero;
145                                                                                                _manupilationType = ManupilationType.RotationXY;
146                                                                                                if(V2(event.x, event.y).isInside(_arcBall.radius, _arcBall.radius*0.7)){
147                                                                                                    _manupilationType = ManupilationType.RotationZ;
148                                                                                                }
149                                                                                            }
150                                                                                        }
151                                                                                    });
152             _disposables["mouseDragged"]  = currentObservables.mouseDragged
153                                                               .doSubscribe!(event => mouseDragged(event));
154             _disposables["mouseReleased"] = currentObservables.mouseReleased
155                                                               .doSubscribe!((event){_isButtonPressed = false;});
156 
157             _disposables["mouseScrolled"] = currentObservables.mouseScrolled
158                                                               .doSubscribe!((event){_mouseScrollAmount += event.yOffset;});
159             _hasSetEvents = true;
160             return this;
161         }
162 
163         This removeEvents(){
164             import std.algorithm;
165             _disposables.keys.map!(key => _disposables[key])
166                              .each!(d => d.dispose);
167             _hasSetEvents = false;
168             return this;
169         }
170 
171         This updateTranslation(){
172             if(_isButtonPressed){
173                 if(_manupilationType == ManupilationType.Translation){
174                     import armos.app:targetFps;
175                     V2 mouseVelocity = (_mousePositionCurrent - _mousePositionPre)*(1.0/targetFps);
176                     _arcBall.linearVelocity = _arcBall.orientation.rotatedVector(V3(mouseVelocity.x, mouseVelocity.y, 0.0)*_linearSensitivity*_distanceRate*_unitDistance);
177                 }
178             }
179 
180             if(_mouseScrollAmount != 0f){
181                 import std.math;
182                 _distanceRate = fmax(_distanceRate + _mouseScrollAmount*_linearSensitivity.z, 0);
183             }
184             return this;
185         }
186 
187         This updateRotation(){
188             if(_isButtonPressed){
189                 if(_manupilationType == ManupilationType.RotationXY){
190                     import armos.app:targetFps;
191                     V2 mouseVelocity = (_mousePositionCurrent - _mousePositionPre)*(1.0/targetFps);
192                     _arcBall.angularVelocity = _arcBall.orientation.rotatedVector(V3(mouseVelocity.y, -mouseVelocity.x, 0.0)*_angularSensitivity);
193                 }
194 
195                 if(_manupilationType == ManupilationType.RotationZ){
196                     import armos.app:targetFps;
197                     import std.math;
198                     V2 mouseVelocity = (_mousePositionCurrent - _mousePositionPre)*(1.0/targetFps);
199                     auto mousePositionFromCenter = (_mousePositionCurrent*currentWindow.pixelScreenCoordScale - viewportCenter);
200                     auto thetaVelocity = V3(mouseVelocity.x, mouseVelocity.y, 0).vectorProduct(V3(mousePositionFromCenter.x, mousePositionFromCenter.y, 0)).z;
201                     _arcBall.angularVelocity = _arcBall.orientation.rotatedVector(V3(0f, 0f, thetaVelocity)*_angularSensitivity);
202                 }
203             }
204             return this;
205         }
206 
207         This updateCameraAriment(){
208             _target = _arcBall.position;
209             _position = _target - _arcBall.orientation.rotatedVector(V3(0, 0, _distanceRate*_unitDistance));
210             _up = _arcBall.orientation.rotatedVector(V3(0, 1, 0));
211             return this;
212         }
213 
214         void mouseDragged(MouseDraggedEvent event){
215             _mousePositionCurrent = V2(event.x, event.y);
216         }
217     }//private
218 }//class EasyCam
219 
220 private bool isInside(in V2 position, in float radius1, in float radius2 = 0.0){
221     import std.math;
222     auto rMax = fmax(radius1, radius2);
223     auto rMin = fmin(radius1, radius2);
224     import armos.app.window;
225     auto rMouse = (position*currentWindow.pixelScreenCoordScale - viewportCenter).norm;
226     return rMin < rMouse && rMouse < rMax;
227 }
228 
229 private V2 viewportCenter(){
230     import armos.graphics:viewportSize, viewportPosition;
231     import std.conv;
232     return viewportPosition.to!V2 + viewportSize.to!V2 * 0.5;
233 }
234 
235 /++
236 +/
237 private class ArcBall {
238     public{
239         V3 angularVelocity;
240         V3 linearVelocity;
241         Q orientation;
242         V3 position;
243         float linearDamping = 0.9;
244         float angularDamping = 0.9;
245         float radius = 1.0;
246 
247         ///
248         this(){
249             reset();
250         }
251 
252         ///
253         ArcBall reset(){
254             angularVelocity = V3.zero;
255             linearVelocity  = V3.zero;
256             orientation     = Q.unit;
257             position        = V3.zero;
258             return this;
259         }
260 
261         ///
262         ArcBall update(in float unitTime){
263             position = position + linearVelocity * unitTime;
264             if(angularVelocity.norm > 0.0){
265                 immutable qAngularVelocityPerUnitTime = Q.angleAxis(angularVelocity.norm*unitTime, angularVelocity.normalized);
266                 orientation = qAngularVelocityPerUnitTime * orientation;
267             }
268             linearVelocity  = linearVelocity*linearDamping;
269             angularVelocity = angularVelocity*angularDamping;
270             return this;
271         }
272     }//public
273 }//class ArcBall