Tiago Peres
Tiago Peres

Reputation: 15622

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?

Upvotes: 0

Views: 143

Answers (1)

Piotr Adam Milewski
Piotr Adam Milewski

Reputation: 14655

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

Upvotes: 2

Related Questions