Lors du développement de mon projet sur le thème de l'espace, je suis tombé sur le fait que three.js ne dispose pas d'un outil de contrôle de caméra prêt à l'emploi et pratique adapté à de telles tâches. Bien sûr, j'avoue que je regardais juste mal ... Mais, une recherche de résultats assez longue n'a pas donné.
OrbitControls est un favori traditionnel des exemples three.js, il ne sait pas comment renverser la caméra et il ne sait pas combien d'autres choses dont il a besoin.
TrackballControls est remarquable en ce que la caméra tourne autour de l'objet comme vous le souhaitez, et à l'envers aussi, mais elle ne sait pas comment tourner sur l'axe de vue, elle ne sait pas comment se déplacer d'avant en arrière sans changer l'échelle, il n'y a pas d'ajustement pratique de la vitesse des mouvements et des virages.
FlyControls - au contraire, permet de faire un "baril" et de changer la vitesse, mais ... où est passée la rotation de la caméra autour de l'objet en question?
Il était possible, bien sûr, de sortir avec l'aide de toutes sortes de béquilles, mais ce n'est pas comme il faut. Après m'être assuré qu'il n'y a pas de solution toute faite pour mes besoins, j'ai décidé de la créer moi-même. Qui s'intéresse, s'il vous plaît, sous cat.
J'ai décidé de prendre TrackballControls.js comme base, l'ayant ouvert, nous voyons les auteurs:
Ceci est leur travail et servira de point de départ. Il faut en rajouter un peu, et si possible ne pas casser celui existant. (Cependant, quelque chose devait être coupé).
Pour commencer, ce que nous avons: la caméra tourne autour de l'objet sans restrictions, ne se coince pas sur le poteau, à tous les tours elle maintient une vitesse angulaire constante. Il y a un zoom, il y a un panoramique. C'est bien, mais pas assez.
La surprise a été l'étrange position des boutons. En fait, c'est inattendu:
this.keys = [ 65 , 83 , 68 ];
Contrôle de la caméra avec seulement trois boutons? c'est peut-être une bonne idée, mais en fait cela ne fonctionne pas. Dans le gestionnaire d'événements keydown de la source, nous observons les lignes suivantes:
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; }
Ça a l'air un peu mystérieux, mais ... ça ne marche pas. C'est-à-dire en général. Les boutons ci-dessus ne font rien en fait, la caméra est contrôlée uniquement par la souris ou le toucher.
J'ai commenté ce morceau de code comme inutile, et l'ai remplacé par le mien, dont l'histoire sera un peu plus loin. Suppression du rappel pour le traitement des boutons - il était évident que les boutons ne devaient fonctionner qu'une seule fois à chaque pression. Mais ce n'est pas le comportement qui est nécessaire dans l'espace, il est donc également sous commentaire.
Alors qu'est-ce qui nous manque ici? Ce que j'ai décidé d'ajouter:
- Mouvement d'avant en arrière le long de la ligne de visée.
- La rotation de la caméra dont l'axe est la ligne de visée.
- Autorotation.
- La possibilité de changer la vitesse de tous les mouvements et virages avec un seul bouton.
- Contrôle complet du clavier.
J'espère que le lecteur me pardonnera le fait que je parlerai du processus d'édition non pas dans l'ordre de réalisation de ces sous-tâches, mais dans l'ordre de suivre le code source de haut en bas. Je vais expliquer quoi et pourquoi je fais.
Constructeur
Ajoutez quelques variables:
this.rotationZFactor = 0.0; this.RVMovingFactor = 0.0; this.autoRotate = false; this.autoRotateSpeed = 0.001; this.allSpeedsFactor = 1;
rotationZFactor est nécessaire pour contrôler la rotation de la caméra sur l'axe de vue,
RVMovingFactor pour contrôler en avant et en arrière le long du même axe,
autoRotate, je suppose qu'il n'a pas besoin de commentaires,
allSpeedsFactor pour contrôler la vitesse de tous les mouvements et virages combinés.
RotateCamera, méthode
Organisé de cette façon:
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() { ...
Ici, presque tout était ainsi, la seule chose que j'ai ajoutée était la variable tmpQuaternion pour gérer la rotation de la caméra le long de l'axe Z.
De plus, vérifier si un traitement était nécessaire était assez simple:
moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 ); angle = moveDirection.length(); if ( angle ) {
Et c'est devenu comme ça:
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'est-ce qui est ici et pourquoi. Premièrement, si la rotation automatique est active, nous ajoutons sa vitesse à la rotation le long de l'axe X. Deuxièmement, nous avons ajouté la mise à l'échelle de toutes les rotations (et des rotations automatiques également) à allSpeedsFactor. Pour contrôler la vitesse de toutes les conversions. Et troisièmement, nous effectuerons d'autres actions non seulement dans le cas où l'angle d'angle (mouvement de la caméra par rapport à controls.target) est non nul, mais aussi dans le cas où rotationZFactor, qui est responsable de la rotation de la caméra le long de l'axe Z, est non nul.
Plus loin dans le code de la méthode, après avoir calculé le quaternion de rotation, une petite addition:
quaternion.setFromAxisAngle( axis, angle );
Le quaternion de rotation de la caméra autour de l'objet, nous multiplions par le quaternion de rotation calculé séparément le long de l'axe Z. Maintenant, la caméra peut tourner le long des trois axes.
ZoomCamera, méthode
Je vais donner tout le code, c'est petit:
this.zoomCamera = function () { var factor; if ( _state === STATE.TOUCH_ZOOM_PAN ) {
Toutes les modifications se résument à l'ajout de allSpeedsFactor à plusieurs endroits pour contrôler la vitesse (mouvements, rotations) et le zoom.
Méthode PanCamera
Je vais également donner son code complet, comme c'est petit:
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 ) ); } } }; }() );
Mais il y a beaucoup plus de changements. Je vais vous parler de tout dans l'ordre.
La variable rv est ajoutée - un vecteur pour traiter la vitesse radiale de la caméra, c'est-à-dire sa vitesse le long de l'axe z.
Ajout de la mise à l'échelle du vecteur mouseChange au même allSpeedsFactor, mais là encore, je veux ajuster toutes les transformations en une seule en l'utilisant, il sera donc ajouté pour gérer tous les mouvements.
La vérification du besoin de traitement passe désormais non seulement si la souris est déplacée, mais aussi dans le cas où RVMovingFactor est non nul - une variable responsable du mouvement de la caméra le long de la ligne de visée.
Et enfin, dans le code de méthode, ajoutez les vecteurs pan et rv pour obtenir le mouvement complet de la caméra sur les trois axes et appliquez-le à la caméra et à controls.target.
Méthode de mise à jour
Pour éliminer certains effets indésirables, les lignes suivantes sont ajoutées à la toute fin de la méthode:
_this.RVMovingFactor = 0.0; _this.rotationZFactor = 0.0;
Comme vous pouvez le deviner, les effets indésirables étaient associés à la rotation et au mouvement le long de l'axe Z.
Méthode Keydown
Le code source que j'ai montré au tout début de l'histoire est complètement commenté. Et à part lui, il n'y avait pratiquement rien dans le corps de la méthode, donc on peut dire qu'elle a été réécrite à partir de zéro.
function keydown( event ) { if ( _this.enabled === false ) return; switch (event.keyCode) {
Alors, qu'est-ce qui a été fait ici:
De nombreux boutons simulent les modifications apportées par le mouvement de la souris.
«A», «D» - reproduit la rotation de la caméra vers la gauche et la droite.
«Z», «X» - duplique l'appareil photo en le tournant de haut en bas.
"+", "-" - dupliquez le zoom avec la molette de la souris.
Les boutons fléchés reproduisent le mouvement de la caméra de haut en bas et de gauche à droite, sans changer le zoom et l'angle de rotation.
Et également ajouté des fonctionnalités sur lesquelles les boutons de la souris standard ne suffiraient pas:
"W", "S" - mouvement le long de la ligne de visée sans changer le zoom.
"Q", "E" - rotation de la caméra autour de l'axe Z, c'est-à-dire autour de la ligne de vue.
«R», «Shift» - diminution de la vitesse de tous les mouvements, virages, transformations, 3 fois et 20 fois, respectivement.
En même temps, vous pouvez remarquer qu'il y a eu un refus d'utiliser le tableau de clés. Pour mes besoins, cela n'était pas nécessaire, et pour être honnête, pour visser ce dont je n'ai personnellement pas besoin, j'étais trop paresseux.
Méthode de saisie
Je vais le citer dans son intégralité:
function keyup( event ) { if ( _this.enabled === false ) return; switch (event.keyCode) {
Comme vous pouvez le deviner, tout ce qui est fait est de retourner les valeurs des variables RVMovingFactor, rotationZFactor, allSpeedsFactor, à leurs valeurs par défaut.
En outre, a commenté l'installation du gestionnaire de touches - parce que sa suppression a été supprimée dans la méthode keydown.
Enfin, afin de ne pas confondre la bibliothèque modifiée avec la bibliothèque standard, nous changeons le nom.
Renommez le constructeur:
THREE.AstroControls = function ( object, domElement ) {
Au lieu de TrackballControls, maintenant AstroControls.
Et à la fin du fichier, nous écrivons:
THREE.AstroControls.prototype = Object.create( THREE.EventDispatcher.prototype ); THREE.AstroControls.prototype.constructor = THREE.AstroControls;
Tout, de nouveaux contrôles sont prêts à l'emploi.
Tout son code est sous le 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;
Candidature
Enregistrez le code complet dans un fichier, nommez-le AstroControls.js, ajoutez-le à votre projet.
Créez des contrôles comme celui-ci:
var controls; ...
À l'avenir, tout est standard, définissez controls.target et controls.autoRotate comme d'habitude. ( ,
three.js , OrbitControls . AstroControls ) , , , touch-events.
, - .