Search code examples
html3daframevirtual-realitywebvr

Go towards or away from the model in mobile


From the code on my previous answer, I get a WebVR page that works fine in Desktop but in the mobile phone I'm not able to go closer to the model (looks more like a skybox).

WebVR not very nice

Would like to be able to go towards / away from the model (using two fingers going, respectively, away from each other or closer), apart from looking around.

After some searching, found an example of where this functionality I look for is used.

Space WebVR cool

How can I get that same mobile functionality working for my case?


Solution

  • 1) Orbit controls

    If you want your experience to revolve around a model, moving the camera around it, and zooming in and out - I'd say the orbit controls are exacly what You're looking for:

    <a-entity camera look-controls 
              orbit-controls="target: 0 1.6 -0.5; initialPosition: 0 5 15"></a-entity>
    

    The camera will move around the defined target. Works with mobile touch events. Check it out with gltf models here.

    2) Moving the camera with pinching

    If you want to move the camera towars or away the direction it's looking at, as far as i know, You'll have to implement such behavior in a custom component. A poor mans version could look a bit like this:

    AFRAME.registerComponent('pinch-controls', {
      init: function() {
        // moveCamera uses variables from 'this' scope
        this.moveCamera.bind(this);
    
        // we'll use this to get the 'pinch direction'
        this.distance = 0;
        // we'll keep here the camera's current direction
        this.direction = new THREE.Vector3();
        // camera entity reference
        this.camera = document.querySelector("[camera]");
    
        // listeners
        document.body.addEventListener('touchstart', event => {
          // we're interested only in two - finger pinches
          if (event.touches.length != 2) return 0;
          // calculate the distance
          this.distance = this.calculatePinchDistance(event);
          // we don't want the touch to rotate the camera around
          this.el.setAttribute('look-controls', 'touchEnabled', false);
        }, false);
    
        document.body.addEventListener('touchend', event => {
          // when the pinch ends - restore the look-controls
          if (event.touches.length != 1) this.el.setAttribute('look-controls', 'touchEnabled', true);
        }, false);
    
        document.body.addEventListener('touchmove', event => {
          // we're interested only in two - finger pinches
          if (event.touches.length != 2) return 0;
    
          // compare the distances to determine which direction should we move
          var distance = this.calculatePinchDistance(event);
          let speed = (distance < this.distance) ? -0.2 : 0.2;
          this.moveCamera(speed);
    
          // keep the distance for the next callback
          this.distance = distance;
        }, false);
      }, 
      calculatePinchDistance(event) {
          var dx = event.touches[0].pageX - event.touches[1].pageX;
          var dy = event.touches[0].pageY - event.touches[1].pageY;
          return Math.sqrt(dx * dx + dy * dy);
      },
      moveCamera: function(speed) {
            // get the camera direction, and multiply it by the desired 'speed'
            this.el.sceneEl.camera.getWorldDirection(this.direction);
            this.direction.multiplyScalar(speed);
            // apply the change to the actual position
            var pos = this.el.getAttribute("position");
            pos.add(this.direction);
            this.el.setAttribute("position", pos);
        }
    }
    
    // HTML
    // <a-entity camera look-controls pinch-controls></a-entity>
    

    Check it out here


    If you want to move the camera towards the model (instead of the direction it's facing), you can just replace extracting the camera direction (in moveCamera) with calculating the camera <-> model direction:

    // instead of
    // this.el.sceneEl.camera.getWorldDirection(this.direction);
    // grab the direction towards the model
    this.direction.copy(this.el.object3D.position)
    this.direction.add(modelReference.object3D.position)
    this.direction.normalize();
    // (...)
    

    Tiago edit:

    The final code used was

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>Octant Cube - by Dodds, H. & Peres, T.</title>
        <meta name="description" content="Present in the Taxonomy article">
            <script src="https://kit.fontawesome.com/c9500776a0.js" crossorigin="anonymous"></script>
        <script src="misc/codeBtn.js"></script>
        <script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>
        <script src="https://unpkg.com/[email protected]/dist/aframe-orbit-controls.min.js"></script>
        <script src="https://unpkg.com/[email protected]/dist/aframe-supercraft-loader.js"></script>
        <style>
        #toggleBtn {
            position: fixed;
            z-index: 9999;
            margin: 25px;
            font-size: 3em;
            color: rgb(128, 0, 128);
            cursor: pointer;
        }
    
        #toggleBtn:hover {
            color: rgba(128, 0, 128, 0.6);
        }
        </style>
        <script>
        // Models swap component
        AFRAME.registerComponent('content-manager', {
            init: function() {
                const btn = document.querySelector("#toggleBtn")
                const castle = document.querySelector("#castle")
                const fish = document.querySelector("#fish")
    
                btn.addEventListener("click", e => {
                    if (castle.getAttribute("visible")) {
                        castle.emit("fadeOut")
                        btn.classList.remove("fa-fish")
                        btn.classList.add("fa-landmark")
                    } else {
                        fish.emit("fadeOut")
                        btn.classList.remove("fa-landmark")
                        btn.classList.add("fa-fish")
                    }
                })
                fish.addEventListener('animationcomplete__fadeout', e => {
                    fish.setAttribute("visible", "false")
                    castle.setAttribute("visible", "true")
                    castle.emit("fadeIn")
                })
                castle.addEventListener('animationcomplete__fadeout', e => {
                    castle.setAttribute("visible", "false")
                    fish.setAttribute("visible", "true")
                    fish.emit("fadeIn")
                })
            }
        })
    
        // move the camera on pinching
        AFRAME.registerComponent('pinch-controls', {
            init: function() {
                // bind the methods that use the scope variables
                this.moveCamera.bind(this);
    
                // use this to keep track whether the user is moving forward or backwards
                this.distance = 0;
                // store the camera direction here
                this.direction = new THREE.Vector3();
                // camera entity reference
                this.camera = document.querySelector("[camera]");
    
                document.body.addEventListener('touchstart', event => {
                    // react only on two finger pinches
                    if (event.touches.length != 2) return 0;
    
                    this.distance = this.calculatePinchDistance(event);
                    // prevent the look controls to rotate the camera while pinching
                    this.el.setAttribute('look-controls', 'touchEnabled', false);
                }, false);
    
                document.body.addEventListener('touchend', event => {
                    // restore the look-controls
                    if (event.touches.length != 1) this.el.setAttribute('look-controls', 'touchEnabled', true)
                }, false);
                document.body.addEventListener('wheel', e => {
                    this.moveCamera(e.deltaY < 0 ? -0.2 : 0.2);
                })
                document.body.addEventListener('touchmove', event => {
                    // we're interested only in pinching
                    if (event.touches.length != 2) return 0;
    
                    // calculate the pinch difference and move the camera
                    var distance = this.calculatePinchDistance(event);
                    let multiplier = (distance < this.distance) ? -0.2 : 0.2;;
                    if (!isNaN(multiplier)) this.moveCamera(multiplier);
    
                    // for later use
                    this.distance = distance;
                }, false);
            },
            calculatePinchDistance: function(mouseEvent) {
                var dx = event.touches[0].pageX - event.touches[1].pageX;
                var dy = event.touches[0].pageY - event.touches[1].pageY;
                return Math.sqrt(dx * dx + dy * dy);
            },
            moveCamera: function(speed) {
                this.el.sceneEl.camera.getWorldDirection(this.direction);
                this.direction.multiplyScalar(speed);
                var pos = this.el.getAttribute("position");
                pos.add(this.direction);
                this.el.setAttribute("position", pos)
            }
        })
        </script>
      </head>
      <body>
        <a-scene background="color: #FAFAFA">
          <a-assets>
            <a-asset-item id="octant" src="octant.glb"></a-asset-item>
          </a-assets>
          <a-entity position="0 0.347 -4" rotation="0 60 -1"  gltf-model="#octant"  scale="5 5 5" animation__fadeIn="property: scale; dur: 150; from: 0.001 0.001 0.001; to: 0.5 0.5 0.5; easing: easeInQuad; startEvents: fadeIn" animation__fadeOut="property: scale; dur: 150; from: 0.5 0.5 0.5; to: 0.001 0.001 0.001; easing: easeInQuad; startEvents: fadeOut"></a-entity>
          <a-entity camera look-controls 
              orbit-controls="target: 0 1.6 -0.5; initialPosition: 0 5 15"></a-entity>
        </a-scene>
      </body>
    </html>
    

    And this was the final result

    WebVR 3D Octant with orbit controls