Ao desenvolver meu projeto sobre o tema do espaço, deparei-me com o fato de que, por algum motivo, o three.js não possui uma ferramenta de controle de câmera pronta e conveniente, adequada para essas tarefas. É claro que admito que estava parecendo mal ... Mas, uma pesquisa bastante demorada pelos resultados não produziu.
O OrbitControls é o favorito tradicional dos exemplos three.js. ele não sabe como virar a câmera de cabeça para baixo e não sabe quantas outras coisas precisa.
O TrackballControls é notável, pois a câmera gira em torno do objeto como você gosta e de cabeça para baixo também, mas não sabe como ativar o eixo de visão, não sabe como se mover para frente e para trás sem alterar a escala, não há ajuste conveniente da velocidade dos movimentos e das curvas.
FlyControls - pelo contrário, permite fazer um "barril" e alterar a velocidade, mas ... para onde foi a rotação da câmera em torno do objeto em questão?
Era possível, é claro, sair com a ajuda de todos os tipos de muletas, mas de alguma forma isso não é comum. Tendo me assegurado de que não há uma solução pronta para meus propósitos, decidi criar ela mesma. Quem está interessado, por favor, sob gato.
Decidi tomar o TrackballControls.js como base; depois de aberto, vemos os autores:
Este é o trabalho deles e servirá como ponto de partida. É necessário adicionar um pouco e, se possível, não quebrar o existente. (No entanto, algo teve que ser cortado).
Para começar, o que temos: a câmera gira em torno do objeto sem restrições, não fica presa no poste, em todas as curvas mantém uma velocidade angular constante. Há um zoom, há uma panela. Isso é bom, mas não o suficiente.
A surpresa foi a posição estranha com os botões. De fato, isso é inesperado:
this.keys = [ 65 , 83 , 68 ];
Controle da câmera com apenas três botões? talvez seja uma boa ideia, mas na verdade não funciona. No manipulador de eventos keydown na fonte, observamos as seguintes linhas:
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 um pouco misterioso, mas ... não funciona. I.e. em geral Os botões acima não fazem nada, a câmera é controlada apenas pelo mouse ou pelo toque.
Comentei esse pedaço de código como desnecessário e o substituí pelo meu, sobre o qual a história será um pouco mais adiante. Remoção do retorno de chamada para processamento de botões - obviamente, era necessário que os botões funcionassem apenas uma vez a cada vez que eram pressionados. Mas esse não é o comportamento necessário no espaço sideral, por isso também está sendo comentado.
Então, o que estamos perdendo aqui? O que eu decidi adicionar:
- Movimento para frente e para trás ao longo da linha de visão.
- A rotação da câmera cujo eixo é a linha de visão.
- Autorotação.
- A capacidade de alterar a velocidade de todos os movimentos e curvas com um botão.
- Controle total do teclado.
Espero que o leitor me perdoe pelo fato de falar sobre o processo de edição não na ordem de concluir essas subtarefas, mas na ordem de seguir o código fonte de cima para baixo. Vou explicar o que e por que estou fazendo.
Construtor
Adicione algumas variáveis:
this.rotationZFactor = 0.0; this.RVMovingFactor = 0.0; this.autoRotate = false; this.autoRotateSpeed = 0.001; this.allSpeedsFactor = 1;
rotationZFactor é necessário para controlar a rotação da câmera no eixo de visão,
RVMovingFactor para controlar a frente e a ré ao longo do mesmo eixo,
autoRotate Suponho que não precise de comentários,
allSpeedsFactor para controlar a velocidade de todos os combinados, movimentos e curvas.
Método RotateCamera
Organizado desta maneira:
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() { ...
Aqui, quase tudo estava correto, a única coisa que adicionei foi a variável tmpQuaternion para lidar com a rotação da câmera ao longo do eixo Z.
Além disso, verificar se era necessário algum processamento era bastante direto:
moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 ); angle = moveDirection.length(); if ( angle ) {
E ficou assim:
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) {
O que está aqui e por quê. Primeiro, se a rotação automática estiver ativa, adicionamos sua velocidade à rotação ao longo do eixo X. Em segundo lugar, adicionamos a escala de todas as rotações (e também as rotações automáticas) a allSpeedsFactor. Para controlar a velocidade de todas as conversões. E, em terceiro lugar, executaremos ações adicionais, não apenas no caso em que o ângulo do ângulo (movimento da câmera em relação ao controles.target) for diferente de zero, mas também no caso em que a rotaçãoZFactor, responsável pela rotação da câmera ao longo do eixo Z, seja diferente de zero.
Além disso, no código do método, depois de calcular o quaternion de rotação, uma pequena adição:
quaternion.setFromAxisAngle( axis, angle );
O quaternion de rotação da câmera em torno do objeto, multiplicamos pelo quaternion de rotação calculado separadamente ao longo do eixo Z. Agora a câmera pode girar ao longo dos três eixos.
Método ZoomCamera
Vou dar o código inteiro, é pequeno:
this.zoomCamera = function () { var factor; if ( _state === STATE.TOUCH_ZOOM_PAN ) {
Todas as alterações se resumem à adição de allSpeedsFactor em vários locais para controlar a velocidade (movimentos, rotações) e o zoom.
Método PanCamera
Também darei todo o código, como é pequeno:
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 ) ); } } }; }() );
Mas há muito mais mudanças. Vou falar sobre tudo em ordem.
A variável rv é adicionada - um vetor para processar a velocidade radial da câmera, ou seja, sua velocidade ao longo do eixo z.
Adicionada escala do vetor mouseChange ao mesmo allSpeedsFactor, mas, novamente, aqui, desejo ajustar todas as transformações para uma única usando-o, para que ele seja adicionado para manipular todos os movimentos.
A verificação da necessidade de processamento agora passa não apenas se o mouse é movido, mas também no caso em que RVMovingFactor é diferente de zero - uma variável responsável pelo movimento da câmera ao longo da linha de visão.
E, finalmente, no código do método, adicione os vetores pan e rv para obter o movimento completo da câmera ao longo dos três eixos e aplicá-lo à câmera e ao controls.target.
Atualizar método
Para eliminar alguns efeitos indesejados, as seguintes linhas são adicionadas ao final do método:
_this.RVMovingFactor = 0.0; _this.rotationZFactor = 0.0;
Efeitos indesejáveis foram associados à rotação e movimento ao longo do eixo Z, como você pode imaginar.
Método Keydown
O código fonte que mostrei no início da história é completamente comentado. Além disso, praticamente não havia nada no corpo do método, então podemos dizer que foi reescrito do zero.
function keydown( event ) { if ( _this.enabled === false ) return; switch (event.keyCode) {
Então, o que foi feito aqui:
Muitos botões simulam as alterações feitas pelo movimento do mouse.
“A”, “D” - duplica a rotação da câmera para a esquerda e para a direita.
“Z”, “X” - duplica a câmera girando para cima e para baixo.
"+", "-" - duplique o zoom com a roda do mouse.
Os botões de seta duplicam o movimento da câmera para cima e para baixo, para a esquerda e para a direita, sem alterar o zoom e o ângulo de rotação.
E também adicionou recursos nos quais os botões do mouse padrão não seriam suficientes:
"W", "S" - movimento ao longo da linha de visão sem alterar o zoom.
"Q", "E" - rotação da câmera em torno do eixo Z, ou seja, em torno da linha de visão.
"R", "Shift" - diminui a velocidade de todos os movimentos, curvas, transformações, 3 vezes e 20 vezes, respectivamente.
Nesse caso, você pode perceber que houve uma recusa em usar a matriz de chaves. Para meus propósitos, isso não era necessário e, para ser sincero, estragar o que eu pessoalmente não precisava, eu era muito preguiçoso.
Método de keyup
Vou citar na íntegra:
function keyup( event ) { if ( _this.enabled === false ) return; switch (event.keyCode) {
Como você pode imaginar, tudo o que está sendo feito é retornar os valores das variáveis RVMovingFactor, rotationZFactor, allSpeedsFactor, para seus valores padrão.
Além disso, comentou sobre a instalação do manipulador de pressionamento de tecla - porque sua remoção foi removida no método de pressionamento de tecla.
Finalmente, para não confundir a biblioteca modificada com o padrão, alteramos o nome.
Renomeie o construtor:
THREE.AstroControls = function ( object, domElement ) {
Em vez de TrackballControls, agora AstroControls.
E no final do arquivo, escrevemos:
THREE.AstroControls.prototype = Object.create( THREE.EventDispatcher.prototype ); THREE.AstroControls.prototype.constructor = THREE.AstroControls;
Tudo, novos controles estão prontos para uso.
Todo o seu código está sob o 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;
Aplicação
Salve o código completo em um arquivo, nomeie-o como AstroControls.js e adicione-o ao seu projeto.
Crie controles como este:
var controls; ...
No futuro, tudo será padrão, defina controls.target e controls.autoRotate como de costume.
(Se você tiver alguma dúvida, veja os exemplos three.js. , existem quase todos os exemplos de uso do OrbitControls. O AstroControls pode ser tratado da mesma maneira) E use a capacidade de girar e mover a câmera como desejar nos três eixos, usando o mouse ou o teclado, ou eventos de toque.Espero que isso seja útil para alguém.