Why my camera move and stop suddenly?

Hi,

I use a joystick on my example. When i go backward my camera backs up and forward my camera is moving. But the camera is stop after a position…

I made a jsfiddle about my problem :

https://jsfiddle.net/espace3d/sw6hL4yq/

And i post here my code to better understand :
Thanks for your help :wink:

let m = {} // materials
let g = {} // geometry
let o = {} // objects

let scene, camera, controls; // pour orbit

let theta = 0; //"time", increases every frame

let tb = 0;
let tf = 0;
var newPosition = new THREE.Vector3();

//essai avec wasd
var moveForward = false;
var moveBackward = false;
var moveLeft = false;
var moveRight = false;
let prevTime = performance.now();

const color = new THREE.Color();

let onMouseMove;
let onMouseDown;
let onMouseUp;

var Colors = {
    enemy: 0xff2680,
    red: 0xf25346,
    white: 0xffffff,
    green: 0xc5ff9a,
    brown: 0x59332e,
    brownDark: 0x23190f,
    pink: 0xff26ff,
    yellow: 0xf4ce93,
    blue: 0xCC3E4E,
    arbre: 0x2a4e11,
    sol: 0xD92B5A,
    sky: 0x181626,
    tronc: 0xF2A099,
    branches: 0xD96666,
};
//utilitaires avec Phaser
var random = (min, max) => {
    let num = Phaser.Math.Between(min, max)
    return num
}
class JoyStick {
    constructor(options) {
        const circle = document.createElement("div");
        circle.style.cssText = "position:absolute; bottom:35px; width:80px; height:80px; background:rgba(126, 126, 126, 0.2); border:rgba(126, 126, 126, 0.2) ; border-radius:50%; left:10%; transform:translateX(-50%);";
        const thumb = document.createElement("div");
        thumb.style.cssText = "position: absolute; left: 20px; top: 20px; width: 40px; height: 40px; border-radius: 50%; background: #ff0065;";
        circle.appendChild(thumb);
        document.body.appendChild(circle);
        this.domElement = thumb;
        this.maxRadius = options.maxRadius || 40;
        this.maxRadiusSquared = this.maxRadius * this.maxRadius;
        this.onMove = options.onMove;
        this.game = options.game;
        this.origin = {
            left: this.domElement.offsetLeft,
            top: this.domElement.offsetTop
        };

        if (this.domElement != undefined) {
            const joystick = this;
            if ('ontouchstart' in window) {
                this.domElement.addEventListener('touchstart', function (evt) {
                    joystick.tap(evt);
                });
            } else {
                this.domElement.addEventListener('mousedown', function (evt) {
                    joystick.tap(evt);
                });
            }
        }
    }

    getMousePosition(evt) {
        let clientX = evt.targetTouches ? evt.targetTouches[0].pageX : evt.clientX;
        let clientY = evt.targetTouches ? evt.targetTouches[0].pageY : evt.clientY;
        return {
            x: clientX,
            y: clientY
        };
    }

    tap(evt) {
        evt = evt || window.event;
        // get the mouse cursor position at startup:
        this.offset = this.getMousePosition(evt);
        const joystick = this;
        if ('ontouchstart' in window) {
            document.ontouchmove = function (evt) {
                joystick.move(evt);
            };
            document.ontouchend = function (evt) {
                joystick.up(evt);
            };
        } else {
            document.onmousemove = function (evt) {
                joystick.move(evt);
            };
            document.onmouseup = function (evt) {
                joystick.up(evt);
            };
        }
    }

    move(evt) {
        evt = evt || window.event;
        //  console.log(evt)
        const mouse = this.getMousePosition(evt);
        // calculate the new cursor position:
        let left = mouse.x - this.offset.x;
        let top = mouse.y - this.offset.y;
        //this.offset = mouse;

        const sqMag = left * left + top * top;
        if (sqMag > this.maxRadiusSquared) {
            //Only use sqrt if essential
            const magnitude = Math.sqrt(sqMag);
            left /= magnitude;
            top /= magnitude;
            left *= this.maxRadius;
            top *= this.maxRadius;
        }

        // set the element's new position:
        this.domElement.style.top = `${top + this.domElement.clientHeight/2}px`;
        this.domElement.style.left = `${left + this.domElement.clientWidth/2}px`;

        const forward = -(top - this.origin.top + this.domElement.clientHeight / 2) / this.maxRadius;
        const turn = (left - this.origin.left + this.domElement.clientWidth / 2) / this.maxRadius;
        if (forward > 0) {
            console.log(forward, turn, 'forward', 'turn')
            // toggleForward = true
            // toggleBackward = false
            moveForward = true
            moveBackward = false

            //  console.log('up', toggleForward)
        }
        if (forward < 0) {
            //  console.log('down')
            // toggleForward = false
            // toggleBackward = true
            moveForward = false
            moveBackward = true
        }
        if (turn > 0) {
            //  console.log('right')
            // toggleLeft = false
            // toggleRight = true
            moveLeft = false
            moveRight = true
        }
        if (turn < 0) {
            //  console.log('left')
            // toggleLeft = true
            // toggleRight = false
            moveLeft = true
            moveRight = false
        }

        if (this.onMove != undefined) this.onMove.call(this.game, forward, turn);

    }

    up(evt) {
        console.log('up')
        game.move = 1
        if ('ontouchstart' in window) {
            document.ontouchmove = null;
            document.touchend = null;
        } else {
            document.onmousemove = null;
            document.onmouseup = null;
        }
        this.domElement.style.top = `${this.origin.top}px`;
        this.domElement.style.left = `${this.origin.left}px`;
        // toggleForward = false
        // toggleBackward = false
        // toggleLeft = false
        // toggleRight = false
        moveForward = false
        moveBackward = false
        moveLeft = false
        moveRight = false
        //  this.onMove.call(this.game, 0, 0);
    }
}

let create_lights = function () {

    //---------------------------------------
    o.hemisphereLight = new THREE.HemisphereLight(0xf6ffb9, 0xf6ffb9, .5)
    o.ambientLight = new THREE.AmbientLight(0xf6ffb9, .2);
    o.shadowLight = new THREE.DirectionalLight(0xf6ffb9, .5);
    o.shadowLight.position.set(150, 1500, 350);
    o.shadowLight.castShadow = true;
    const d = 1000
    o.shadowLight.shadow.camera.left = -d;
    o.shadowLight.shadow.camera.right = d;
    o.shadowLight.shadow.camera.top = d;
    o.shadowLight.shadow.camera.bottom = -d;
    o.shadowLight.shadow.camera.near = 1;
    o.shadowLight.shadow.camera.far = 10000;
    o.shadowLight.shadow.mapSize.width = 8192;
    o.shadowLight.shadow.mapSize.height = 8192;
    o.shadowLight.castShadow = true;
    o.shadowLight.bias = -0.001;
    scene.add(o.hemisphereLight);
    scene.add(o.shadowLight);
    scene.add(o.ambientLight);
    //---------------------------------------
}

let create_ground = function () {
    g.ground = new THREE.PlaneBufferGeometry(20000, 20000)
    m.ground = new THREE.MeshPhongMaterial({
        color: Colors.green,
        flatShading: THREE.FlatShading
    });
    o.ground = new THREE.Mesh(g.ground, m.ground)

    o.ground.rotation.x = -Math.PI / 2;
    o.ground.position.y = -10;
    o.ground.receiveShadow = true;
    o.ground.castShadow = true;
    scene.add(o.ground);
}

class Tree {
    constructor(n) {
        //  this.scene = new THREE.Scene();

        //modèle 3d
        this.mesh = new THREE.Object3D();
        this.mesh.name = "tree";
        this.n = n
        this.abri = "something"
        // tronc
        g.tronc = new THREE.CubeGeometry(20, 80, 20);
        m.tronc = new THREE.MeshPhongMaterial({
            color: Colors.branches,
            flatShading: THREE.SmoothShading,
        });
        o.tronc = new THREE.Mesh(g.tronc, m.tronc);
        o.tronc.position.set(0, 0, 0);
        o.tronc.castShadow = true;
        o.tronc.receiveShadow = true;
        this.mesh.add(o.tronc);

        // arbre
        g.arbre = new THREE.CubeGeometry(50, 150, 50);
        m.arbre = new THREE.MeshPhongMaterial({
            color: Colors.arbre,
            flatShading: THREE.SmoothShading,
        });
        o.arbre = new THREE.Mesh(g.arbre, m.arbre);
        o.arbre.position.set(0, 90, 0);
        o.arbre.castShadow = true;
        o.arbre.receiveShadow = true;
        o.tronc.add(o.arbre)
        this.mesh.add(o.arbre);
        this.mesh.castShadow = true
        this.mesh.receiveShadow = true;
    }
}

let create_tree = function () {
    o.tree = []
    for (var i = 0; i < 90; i++) {
        o.tree[i] = new Tree(i);
        o.tree[i].mesh.position.x = random(-4000, 4000);
        o.tree[i].mesh.position.y = 0
        o.tree[i].mesh.position.z = random(-4000, 4000);
        //random scale
        let rs = random(2, 2.5);
        o.tree[i].mesh.scale.set(rs, rs, rs)
        scene.add(o.tree[i].mesh);
    }
}

class Game {
    constructor() {
        scene = new THREE.Scene();
        scene.background = new THREE.Color(0xf6ffb9);
        scene.fog = new THREE.Fog(0xc3ffb9, 0, 2250);
        // camera
        camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);
        camera.position.set(0, 0, 0);
        camera.rotation.order = "YXZ";
        camera.rotation.y = -Math.PI / 2;
        camera.position.z = 3;
        camera.updateProjectionMatrix();

        this.renderer = new THREE.WebGLRenderer({
            antialias: true
        });
        this.renderer.setPixelRatio(window.devicePixelRatio);
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.renderer.shadowMap.enabled = true;
        this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
        document.body.appendChild(this.renderer.domElement);

        controls = new THREE.OrbitControls(camera, this.renderer.domElement);
        controls.autoRotate = false
        controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
        controls.minDistance = 10;
        controls.maxDistance = 50;
        controls.maxPolarAngle = Math.PI;
        controls.screenSpacePanning = true;

        // PREFABS
        create_lights()
        create_ground()
        create_tree()

        this.joystick = new JoyStick({
            onMove: this.moveActions,
            //  onMove: this.playerControl,
            game: this
        });
        this.animate();
    }

    animate() {
        const game = this;
        const time = performance.now();
        controls.update()

        requestAnimationFrame(function () {
            game.animate();
        });

        tb += 0.1;
        tf -= 0.1
        console.log('tf: ', tf);

        if (moveForward) {
            camera.translateZ(tf)
        }
        if (moveBackward) {
            camera.translateZ(tb)
        }

        prevTime = time;

        this.renderer.render(scene, camera);
    }
}

You custom controls do conflict with OrbitControls. Whenever you translate the camera, you have to update Controls.target, too. https://jsfiddle.net/anf74qz8/

BTW: Do you really have to use OrbitControls when the user already controls the camera with a virtual joystick? Probably better to head for a solution that does not require two control implementations.

hi Mugen87,

Thanks a lot for your jsfiddle. I have find that scene.translateZ(value) do also the solution.
I agree with you for the problem of two controls implementations…but i don’t find by myself a solution.

I found this :
https://threejs.org/examples/?q=ao#webgl_geometry_minecraft_ao

But i don’t success, i have two problems with this :

  • i don’t success to avoid the player to go beneath the ground;
  • this code works for desktop but on mobile not.

Have you a good example for me ?

You have to implement a collision detection to solve this issue.

The used FirstPersonControls class does not work with touch events.

Sorry, but I can’t provide an example that does exactly what you are looking for. I suggest you start with controls development first and later handle the collision detection.

Mugen87,

FirstPersonControls, TrackballControls, Pointerlock,

Nothing works with mobile… it’s a shame that three.js don’t provide this support.

TrackballControls (as well as OrbitControls) does work on mobile. Do you mind elaborating what does not work with TrackballControls?

PointerLockControls only works with a mouse since it’s based on the same named Pointer Lock API. Mouse capturing will never work on touch devices.

Touch support to FirstPersonControls could be considered as a feature request.