|
97 | 97 |
|
98 | 98 | this._domElementKeyEvents = null; // |
99 | 99 | // public methods |
| 100 | + |
| 101 | + // ZOOM-TO-CURSOR |
| 102 | + this.cursorScreen = new THREE.Vector3(); |
| 103 | + this.cursorWorld = new THREE.Vector3(); |
| 104 | + this.enableZoomToCursor = false; |
| 105 | + this.adjustmentAfterZoomNeeded = false; |
| 106 | + this.maxTargetDistanceFromOrigin = Infinity; |
| 107 | + |
100 | 108 | // |
101 | 109 |
|
| 110 | + // ZOOM-TO-CURSOR |
| 111 | + this.adjustAfterZoom = function () { |
| 112 | + |
| 113 | + const lastTarget = scope.target.clone(); |
| 114 | + const newCursorWorld = new THREE.Vector3( scope.cursorScreen.x, scope.cursorScreen.y, scope.target.clone().project( scope.object ).z ).clone().unproject( scope.object ); |
| 115 | + const delta = new THREE.Vector3().subVectors( scope.cursorWorld, newCursorWorld ); |
| 116 | + |
| 117 | + let target = null; |
| 118 | + |
| 119 | + if ( !scope.screenSpacePanning ) { |
| 120 | + |
| 121 | + const plane = new THREE.Plane( scope.object.up.clone() ); |
| 122 | + const ray = new THREE.Ray( scope.object.position.clone(), new THREE.Vector3().subVectors( scope.target.clone().add( delta ), scope.object.position ).normalize()); |
| 123 | + target = ray.intersectPlane( plane, new THREE.Vector3() ); |
| 124 | + |
| 125 | + if ( target === null || new THREE.Vector3().subVectors( scope.object.position, scope.target ).normalize().multiply( scope.object.up.clone().normalize() ).length() < 0.00001 ) { |
| 126 | + |
| 127 | + scope.target.add( delta ); |
| 128 | + if ( scope.target.length() > this.maxTargetDistanceFromOrigin ) scope.target.setLength( this.maxTargetDistanceFromOrigin ); |
| 129 | + scope.object.position.add( new THREE.Vector3().subVectors( scope.target, lastTarget ) ); |
| 130 | + |
| 131 | + const mulVector = new THREE.Vector3( 1 - scope.object.up.x, 1 - scope.object.up.y, 1 - scope.object.up.z ); |
| 132 | + scope.target.multiply( mulVector ); |
| 133 | + scope.object.position.multiply( mulVector ); |
| 134 | + |
| 135 | + } else { |
| 136 | + |
| 137 | + if ( target.length() > this.maxTargetDistanceFromOrigin ) target.setLength( this.maxTargetDistanceFromOrigin ); |
| 138 | + scope.target.copy( target ); |
| 139 | + scope.object.position.add( new THREE.Vector3().subVectors( scope.target, lastTarget ) ); |
| 140 | + |
| 141 | + } |
| 142 | + |
| 143 | + } else { |
| 144 | + |
| 145 | + scope.target.add( delta ); |
| 146 | + scope.object.position.add( delta ); |
| 147 | + |
| 148 | + } |
| 149 | + |
| 150 | + } |
| 151 | + |
| 152 | + this.setCursorWorld = function () { |
| 153 | + |
| 154 | + scope.cursorWorld.copy( new THREE.Vector3( scope.cursorScreen.x, scope.cursorScreen.y, scope.target.clone().project( scope.object ).z ).unproject( scope.object ) ); |
| 155 | + |
| 156 | + } |
| 157 | + |
102 | 158 | this.getPolarAngle = function () { |
103 | 159 |
|
104 | 160 | return spherical.phi; |
|
236 | 292 | // min(camera displacement, camera rotation in radians)^2 > EPS |
237 | 293 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 |
238 | 294 |
|
| 295 | + // ZOOM-TO-CURSOR |
| 296 | + if ( scope.target.length() > this.maxTargetDistanceFromOrigin ) { |
| 297 | + |
| 298 | + const lastTarget = scope.target.clone(); |
| 299 | + scope.target.setLength( this.maxTargetDistanceFromOrigin ); |
| 300 | + scope.object.position.add( new THREE.Vector3().subVectors( scope.target, lastTarget ) ); |
| 301 | + |
| 302 | + } |
| 303 | + |
239 | 304 | if ( zoomChanged || lastPosition.distanceToSquared( scope.object.position ) > EPS || 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { |
240 | 305 |
|
241 | 306 | scope.dispatchEvent( _changeEvent ); |
| 307 | + |
| 308 | + // ZOOM-TO-CURSOR |
| 309 | + if ( scope.enableZoomToCursor && scope.adjustmentAfterZoomNeeded ) { |
| 310 | + |
| 311 | + scope.adjustmentAfterZoomNeeded = false; |
| 312 | + this.adjustAfterZoom(); |
| 313 | + |
| 314 | + } |
| 315 | + |
242 | 316 | lastPosition.copy( scope.object.position ); |
243 | 317 | lastQuaternion.copy( scope.object.quaternion ); |
244 | 318 | zoomChanged = false; |
|
408 | 482 |
|
409 | 483 | function dollyOut( dollyScale ) { |
410 | 484 |
|
| 485 | + // ZOOM-TO-CURSOR |
| 486 | + if ( scope.enableZoomToCursor ) scope.setCursorWorld(); |
| 487 | + |
411 | 488 | if ( scope.object.isPerspectiveCamera ) { |
412 | 489 |
|
413 | 490 | scale /= dollyScale; |
|
425 | 502 |
|
426 | 503 | } |
427 | 504 |
|
| 505 | + // ZOOM-TO-CURSOR |
| 506 | + if ( scope.enableZoomToCursor ) { |
| 507 | + |
| 508 | + if ( scope.object.isOrthographicCamera ) scope.adjustAfterZoom(); |
| 509 | + else if ( scope.object.isPerspectiveCamera ) scope.adjustmentAfterZoomNeeded = true; |
| 510 | + |
| 511 | + } |
| 512 | + |
428 | 513 | } |
429 | 514 |
|
430 | 515 | function dollyIn( dollyScale ) { |
431 | 516 |
|
| 517 | + // ZOOM-TO-CURSOR |
| 518 | + if ( scope.enableZoomToCursor ) scope.setCursorWorld(); |
| 519 | + |
432 | 520 | if ( scope.object.isPerspectiveCamera ) { |
433 | 521 |
|
434 | 522 | scale *= dollyScale; |
|
446 | 534 |
|
447 | 535 | } |
448 | 536 |
|
| 537 | + // ZOOM-TO-CURSOR |
| 538 | + if ( scope.enableZoomToCursor ) { |
| 539 | + |
| 540 | + if ( scope.object.isOrthographicCamera ) scope.adjustAfterZoom(); |
| 541 | + else if ( scope.object.isPerspectiveCamera ) scope.adjustmentAfterZoomNeeded = true; |
| 542 | + |
| 543 | + } |
| 544 | + |
449 | 545 | } // |
450 | 546 | // event callbacks - update the object state |
451 | 547 | // |
|
1065 | 1161 | scope.domElement.addEventListener( 'contextmenu', onContextMenu ); |
1066 | 1162 | scope.domElement.addEventListener( 'pointerdown', onPointerDown ); |
1067 | 1163 | scope.domElement.addEventListener( 'pointercancel', onPointerCancel ); |
1068 | | - scope.domElement.addEventListener( 'wheel', onMouseWheel, { |
1069 | | - passive: false |
1070 | | - } ); // force an update at start |
| 1164 | + scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); |
| 1165 | + |
| 1166 | + // ZOOM-TO-CURSOR |
| 1167 | + scope.domElement.addEventListener( 'mousemove', event => { |
| 1168 | + |
| 1169 | + if ( !scope.enableZoomToCursor ) return; |
| 1170 | + |
| 1171 | + scope.cursorScreen.copy( |
| 1172 | + new THREE.Vector3( |
| 1173 | + ( ( event.clientX - scope.domElement.getBoundingClientRect().left ) / scope.domElement.clientWidth) * 2 - 1, |
| 1174 | + - ( (event.clientY - scope.domElement.getBoundingClientRect().top ) / scope.domElement.clientHeight) * 2 + 1, |
| 1175 | + scope.target.clone().project( scope.object ).z |
| 1176 | + ) |
| 1177 | + ); |
| 1178 | + |
| 1179 | + }); |
| 1180 | + |
| 1181 | + const handleTouch = event => { |
| 1182 | + |
| 1183 | + const touches = event.touches; |
| 1184 | + let touch; |
| 1185 | + |
| 1186 | + if ( touches.length === 1 ) { |
| 1187 | + |
| 1188 | + touch = new THREE.Vector2( touches[ 0 ].clientX, touches[ 0 ].clientY ); |
| 1189 | + |
| 1190 | + } else if ( touches.length === 2 ) { |
| 1191 | + |
| 1192 | + touch = new THREE.Vector2( ( touches[ 0 ].clientX + touches[ 1 ].clientX ) / 2, ( touches[ 0 ].clientY + touches[ 1 ].clientY ) / 2 ); |
| 1193 | + |
| 1194 | + } |
| 1195 | + |
| 1196 | + if ( touch !== undefined ) { |
| 1197 | + |
| 1198 | + scope.cursorScreen.copy( |
| 1199 | + new THREE.Vector3( |
| 1200 | + ( (touch.x - scope.domElement.getBoundingClientRect().left ) / scope.domElement.clientWidth ) * 2 - 1, |
| 1201 | + - ( ( touch.y - scope.domElement.getBoundingClientRect().top ) / scope.domElement.clientHeight ) * 2 + 1, |
| 1202 | + scope.target.clone().project( scope.object ).z |
| 1203 | + ) |
| 1204 | + ); |
| 1205 | + |
| 1206 | + } |
| 1207 | + |
| 1208 | + }; |
| 1209 | + |
| 1210 | + scope.domElement.addEventListener( 'touchstart', handleTouch); |
| 1211 | + scope.domElement.addEventListener( 'touchmove', handleTouch); |
1071 | 1212 |
|
| 1213 | + // force an update at start |
1072 | 1214 | this.update(); |
1073 | 1215 |
|
1074 | 1216 | } |
|
0 commit comments