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