Al desarrollar mi proyecto sobre el tema del espacio, me encontré con el hecho de que, por alguna razón, three.js no tiene una herramienta de control de cámara conveniente y preparada adecuada para tales tareas. Por supuesto, admito que solo me veía mal ... Pero, una búsqueda bastante larga de los resultados no produjo.
OrbitControls es un favorito tradicional de los ejemplos de three.js, no sabe cómo poner la cámara al revés y no sabe cuántas otras cosas necesita.
TrackballControls es notable porque la cámara gira alrededor del objeto a su gusto y al revés también, pero no sabe cómo girar en el eje de visión, no sabe cómo moverse hacia adelante y hacia atrás sin cambiar la escala, no hay un ajuste conveniente de la velocidad de los movimientos y giros.
FlyControls : por el contrario, le permite hacer un "barril" y cambiar la velocidad, pero ... ¿a dónde fue la rotación de la cámara alrededor del objeto en cuestión?
Era posible, por supuesto, salir con la ayuda de todo tipo de muletas, pero de alguna manera esto no es tan común. Después de asegurarme de que no haya una solución preparada para mis propósitos, decidí crearla yo mismo. ¿Quién está interesado, por favor, debajo del gato.
Decidí tomar TrackballControls.js como base, después de abrirlo, vemos a los autores:
Este es su trabajo y servirá como nuestro punto de partida. Es necesario agregar un poco, y si es posible no romper el existente. (Sin embargo, algo tuvo que ser cortado).
Para empezar, lo que tenemos: la cámara gira alrededor del objeto sin restricciones, no se atasca en el poste, en todos los giros mantiene una velocidad angular constante. Hay un zoom, hay una panorámica. Esto es bueno, pero no suficiente.
La sorpresa fue la extraña posición con los botones. De hecho, esto es inesperado:
this.keys = [ 65 , 83 , 68 ];
¿Control de cámara con solo tres botones? tal vez sea una buena idea, pero de hecho no funciona. En el controlador de eventos keydown en la fuente, observamos las siguientes líneas:
window.removeEventListener( 'keydown', keydown ); _prevState = _state; if ( _state !== STATE.NONE ) { return; } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && ! _this.noRotate ) { _state = STATE.ROTATE; } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && ! _this.noZoom ) { _state = STATE.ZOOM; } else if ( event.keyCode === _this.keys[ STATE.PAN ] && ! _this.noPan ) { _state = STATE.PAN; }
Parece un poco misterioso, pero ... no funciona. Es decir en general Los botones anteriores no hacen nada, de hecho, la cámara se controla solo con el mouse o el tacto.
Comenté este código como innecesario y lo reemplacé con el mío, sobre el cual la historia irá un poco más allá. Eliminación de la devolución de llamada para el procesamiento del botón: obviamente, era necesario que los botones funcionaran solo una vez cada vez que se presionan. Pero este no es el comportamiento que se necesita en el espacio exterior, por lo que también está bajo comentario.
Entonces, ¿qué nos estamos perdiendo aquí? Lo que decidí agregar:
- Movimiento de ida y vuelta a lo largo de la línea de visión.
- La rotación de la cámara cuyo eje es la línea de visión.
- Autorotación.
- La capacidad de cambiar la velocidad de todos los movimientos y giros con un solo botón.
- Control total del teclado.
Espero que el lector me perdone por el hecho de que hablaré sobre el proceso de edición no en el orden de completar estas subtareas, sino en el orden de seguir el código fuente de arriba a abajo. Explicaré qué y por qué estoy haciendo.
Constructor
Agregue algunas variables:
this.rotationZFactor = 0.0; this.RVMovingFactor = 0.0; this.autoRotate = false; this.autoRotateSpeed = 0.001; this.allSpeedsFactor = 1;
rotación Se necesita ZFactor para controlar la rotación de la cámara en el eje de visión,
RVMovingFactor para controlar hacia adelante y hacia atrás a lo largo del mismo eje,
autoRotate, supongo que no necesita comentarios,
allSpeedsFactor para controlar la velocidad de todos los combinados, movimientos y giros.
Método RotateCamera
Organizado de esta manera:
this.rotateCamera = ( function () { var axis = new THREE.Vector3(), quaternion = new THREE.Quaternion(), eyeDirection = new THREE.Vector3(), objectUpDirection = new THREE.Vector3(), objectSidewaysDirection = new THREE.Vector3(), moveDirection = new THREE.Vector3(), tmpQuaternion = new THREE.Quaternion(), angle; return function rotateCamera() { ...
Aquí, casi todo fue así, lo único que agregué fue la variable tmpQuaternion para manejar la rotación de la cámara a lo largo del eje Z.
Además, verificar si era necesario algún procesamiento fue bastante sencillo:
moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 ); angle = moveDirection.length(); if ( angle ) {
Y se volvió así:
if (_this.autoRotate) _moveCurr.x += _this.autoRotateSpeed; moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 ); moveDirection.setLength(moveDirection.length() * _this.allSpeedsFactor); angle = moveDirection.length(); if ( angle || _this.rotationZFactor) {
¿Qué hay aquí y por qué? En primer lugar, si la rotación automática está activa, agregamos su velocidad a la rotación a lo largo del eje X. En segundo lugar, agregamos la escala de todas las rotaciones (y también las rotaciones automáticas) a allSpeedsFactor. Para controlar la velocidad de todas las conversiones. Y en tercer lugar, realizaremos acciones adicionales no solo en el caso de que el ángulo angular (movimiento de la cámara en relación con los controles.target) no sea cero, sino también en el caso de que la rotación ZFactor, que es responsable de la rotación de la cámara a lo largo del eje Z, no sea cero.
Además en el código del método, después de calcular el cuaternión de rotación, una pequeña adición:
quaternion.setFromAxisAngle( axis, angle );
El cuaternión de rotación de la cámara alrededor del objeto, multiplicamos por el cuaternión de rotación calculado por separado a lo largo del eje Z. Ahora la cámara puede girar a lo largo de los tres ejes.
Método ZoomCamera
Te daré el código completo, es pequeño:
this.zoomCamera = function () { var factor; if ( _state === STATE.TOUCH_ZOOM_PAN ) {
Todos los cambios se reducen a agregar allSpeedsFactor en varios lugares para controlar la velocidad (movimientos, rotaciones) y el zoom.
Método PanCamera
También daré su código completo, como es pequeño
this.panCamera = ( function () { var mouseChange = new THREE.Vector2(), objectUp = new THREE.Vector3(), rv = new THREE.Vector3() pan = new THREE.Vector3(); return function panCamera() { mouseChange.copy( _panEnd ).sub( _panStart ); mouseChange.setLength(mouseChange.length() * _this.allSpeedsFactor); if ( mouseChange.lengthSq() || _this.RVMovingFactor ) { mouseChange.multiplyScalar( _eye.length() * _this.panSpeed ); pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x ); pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) ); pan.add( rv.copy( _eye ).setLength(_this.RVMovingFactor * _eye.length()) ); _this.object.position.add( pan ); _this.target.add( pan ); if ( _this.staticMoving ) { _panStart.copy( _panEnd ); } else { _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor * _this.allSpeedsFactor ) ); } } }; }() );
Pero hay muchos más cambios. Te contaré todo en orden.
Variable rv - vector agregado para procesar la velocidad radial de la cámara, es decir su velocidad a lo largo del eje z.
Se agregó la escala del vector mouseChange al mismo allSpeedsFactor, pero nuevamente está aquí, quiero ajustar todas las transformaciones a una sola usándola, por lo que se agregará para manejar todos los movimientos.
La comprobación de la necesidad de procesamiento ahora pasa no solo si se mueve el mouse, sino también en el caso en que RVMovingFactor no es cero, una variable responsable del movimiento de la cámara a lo largo de la línea de visión.
Y finalmente, en el código del método, agregue los vectores pan y rv para obtener el movimiento completo de la cámara a lo largo de los tres ejes y aplicarlo a la cámara y a control.target.
Método de actualización
Para eliminar algunos efectos no deseados, se agregan las siguientes líneas al final del método:
_this.RVMovingFactor = 0.0; _this.rotationZFactor = 0.0;
Los efectos no deseados se asociaron con la rotación y el movimiento a lo largo del eje Z, como puede suponer.
Método keydown
El código fuente que mostré al comienzo de la historia está completamente comentado. Y además de él, prácticamente no había nada en el cuerpo del método, por lo que podemos decir que fue reescrito desde cero.
function keydown( event ) { if ( _this.enabled === false ) return; switch (event.keyCode) {
Entonces, ¿qué se ha hecho aquí?
Muchos botones simulan cambios realizados por el movimiento del mouse.
“A”, “D”: duplica la rotación de la cámara hacia la izquierda y hacia la derecha.
“Z”, “X”: duplica la cámara girando hacia arriba y hacia abajo.
"+", "-" - duplica el zoom con la rueda del mouse.
Los botones de flecha duplican el movimiento de la cámara hacia arriba y hacia abajo, hacia la izquierda y hacia la derecha, sin cambiar el zoom y el ángulo de rotación.
Y también se agregaron características en las que los botones del mouse estándar no serían suficientes:
"W", "S" - movimiento a lo largo de la línea de visión sin cambiar el zoom.
"Q", "E" - rotación de la cámara alrededor del eje Z, es decir alrededor de la línea de visión.
“R”, “Shift”: disminución de la velocidad de todos los movimientos, giros, transformaciones, 3 veces y 20 veces, respectivamente.
Al mismo tiempo, puede notar que hubo una negativa a utilizar la matriz de claves. Para mis propósitos, esto no era necesario, y para ser honesto, para joder lo que personalmente no necesito, era demasiado vago.
Método keyup
Lo citaré en su totalidad:
function keyup( event ) { if ( _this.enabled === false ) return; switch (event.keyCode) {
Como puede suponer, todo lo que se está haciendo es devolver los valores de las variables RVMovingFactor, rotacionZFactor, allSpeedsFactor, a sus valores predeterminados.
Además, comentó sobre la instalación del controlador de pulsación de teclas, ya que su eliminación se eliminó en el método keydown.
Finalmente, para no confundir la biblioteca modificada con la estándar, cambiamos el nombre.
Cambie el nombre del constructor:
THREE.AstroControls = function ( object, domElement ) {
En lugar de TrackballControls, ahora AstroControls.
Y al final del archivo escribimos:
THREE.AstroControls.prototype = Object.create( THREE.EventDispatcher.prototype ); THREE.AstroControls.prototype.constructor = THREE.AstroControls;
Todo, los nuevos controles están listos para usar.
Todo su código está bajo el spoiler THREE.AstroControls = function ( object, domElement ) { var _this = this; var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 }; this.object = object; this.domElement = ( domElement !== undefined ) ? domElement : document; this.rotationZFactor = 0.0;
Solicitud
Guarde el código completo en un archivo, asígnele el nombre AstroControls.js y agréguelo a su proyecto.
Crea controles como este:
var controls; ...
En el futuro, todo es estándar, establezca los controles.target y controls.autoRotate como de costumbre.
(Si tiene alguna pregunta, consulte los ejemplos de three.js , hay ejemplos en casi todas partes del uso de OrbitControls. Los AstroControls se pueden manejar de la misma manera) Y use la capacidad de rotar y mover la cámara como desee en los tres ejes, usando el mouse o el teclado, o eventos táctiles.Espero que esto sea útil para alguien.