1
\$\begingroup\$

First off let me apologize for the vague title but I really couldn't think of a more descriptive one (maybe after reading this a more advanced user might be able to edit it).

Alright, so I have been putting together a three.js example that shows a different animated texture on a billboard when the camera rotates around it (just like oldschool doom and build engine games). After having the initial concept explained to me (Doom-style 2.5D Movement Animations) I managed to get something up and working.

Everything works fine. You can move the camera around the object and the correct animations play for the angle you are currently seeing the object. However...as soon as you change the rotation of the object (the mesh object associated with the billboard not the camera), even by the slightest amount, the theta value that is calculated to determine the angle becomes skewed.

I'm not sure if I need to use another method to calculate which animation should be displayed or if there is something simple I'm just over looking. The source is located below as well as on github: https://github.com/commanderZiltoid/threejs-2.5d-fps.

A working example can be found here: https://stashcube.com/stackexamples/rotate/

When you first browse to that page the billboard object will not be rotated. You can pan around it and see everything work. If you press the enter button the billboard object will be rotated and you can see what I have explained.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <link rel="stylesheet" href="/css/custom.css" media="all"/>
    </head>
    <body>
        <div id="blocker">
            <div id="instructions">
                <span style="font-size:40px">Basic WASD/Mouse Movement</span>
                <br/>
                <span style="font-size:30px">Click to start</span>
                <br />
                (W, A, S, D = Move, SPACE = Jump, MOUSE = Look around)
            </div>
        </div>

        <script src="/libs/three.js"></script>
        <script src="/libs/PointerLockControls.js"></script>


        <script src="/src/verify_pointerlock.js"></script>
        <script src="/src/TextureAnimator.js"></script>
        <script>
                var camera, 
                    scene, 
                    renderer,
                    geometry,
                    material,
                    mesh,
                    controls,
                    blocker = document.getElementById( 'blocker' ),
                    instructions = document.getElementById( 'instructions' ),
                    controlsEnabled = false,
                    moveForward = false,
                    moveBackward = false,
                    moveLeft = false,
                    moveRight = false,
                    canJump = false,
                    prevTime = performance.now(),
                    velocity = new THREE.Vector3(),
                    clock = new THREE.Clock();



                camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);

                scene = new THREE.Scene();
                scene.fog = new THREE.Fog(0xffffff, 0, 750);

                var light = new THREE.HemisphereLight(0xeeeeff, 0x777788, 0.75);
                light.position.set(0.5, 1, 0.75);
                scene.add(light);

                controls = new THREE.PointerLockControls(camera);
                scene.add(controls.getObject());


                //FLOOR
                geometry = new THREE.PlaneGeometry( 2000, 2000, 100, 100 );
                geometry.rotateX( - Math.PI / 2 );
                for (var i = 0, l = geometry.vertices.length; i < l; i ++) {
                        var vertex = geometry.vertices[ i ];
                        vertex.x += Math.random() * 20 - 10;
                        vertex.y += Math.random() * 2;
                        vertex.z += Math.random() * 20 - 10;
                }
                for (var i = 0, l = geometry.faces.length; i < l; i ++) {
                        var face = geometry.faces[ i ];
                        face.vertexColors[ 0 ] = new THREE.Color().setHSL( Math.random() * 0.3 + 0.5, 0.75, Math.random() * 0.25 + 0.75 );
                        face.vertexColors[ 1 ] = new THREE.Color().setHSL( Math.random() * 0.3 + 0.5, 0.75, Math.random() * 0.25 + 0.75 );
                        face.vertexColors[ 2 ] = new THREE.Color().setHSL( Math.random() * 0.3 + 0.5, 0.75, Math.random() * 0.25 + 0.75 );
                }
                material = new THREE.MeshBasicMaterial({vertexColors: THREE.VertexColors});
                mesh = new THREE.Mesh(geometry, material);
                scene.add(mesh);
                //END FLOOR



                var players = [];                
                function add_player(x,y,z){

                    var i = players.length;

                    players.push({id:'player_' + i});

                    players[i].textures = {
                        s:new THREE.ImageUtils.loadTexture('/assets/textures/' + players[i].id + '/s.png'),
                        se:new THREE.ImageUtils.loadTexture('/assets/textures/' + players[i].id + '/se.png'),
                        e:new THREE.ImageUtils.loadTexture('/assets/textures/' + players[i].id + '/e.png'),
                        ne:new THREE.ImageUtils.loadTexture('/assets/textures/' + players[i].id + '/ne.png'),
                        n:new THREE.ImageUtils.loadTexture('/assets/textures/' + players[i].id + '/n.png'),
                        nw:new THREE.ImageUtils.loadTexture('/assets/textures/' + players[i].id + '/nw.png'),
                        w:new THREE.ImageUtils.loadTexture('/assets/textures/' + players[i].id + '/w.png'),
                        sw:new THREE.ImageUtils.loadTexture('/assets/textures/' + players[i].id + '/sw.png')
                    };

                    players[i].animations = {
                        s:new TextureAnimator(players[i].textures.s, 4, 1, 4, 160),
                        se:new TextureAnimator(players[i].textures.se, 4, 1, 4, 160),
                        e:new TextureAnimator(players[i].textures.e, 4, 1, 4, 160),
                        ne:new TextureAnimator(players[i].textures.ne, 4, 1, 4, 160),
                        n:new TextureAnimator(players[i].textures.n, 4, 1, 4, 160),
                        nw:new TextureAnimator(players[i].textures.nw, 4, 1, 4, 160),
                        w:new TextureAnimator(players[i].textures.w, 4, 1, 4, 160),
                        sw:new TextureAnimator(players[i].textures.sw, 4, 1, 4, 160)
                    };

                    players[i].billboard_mat = new THREE.MeshBasicMaterial({map: players[i].textures.n,  transparent:true});
                    players[i].billboard_geom = new THREE.PlaneGeometry(14, 14, 1, 1);
                    players[i].billboard = new THREE.Mesh(players[i].billboard_geom, players[i].billboard_mat);

                    players[i].material = new THREE.MeshBasicMaterial({transparent:true, opacity:0.0});
                    players[i].geometry = new THREE.PlaneGeometry(14, 14, 1, 1);
                    players[i].mesh = new THREE.Mesh(players[i].geometry, players[i].material);

                    players[i].billboard.position.set(x,y,z);
                    scene.add(players[i].billboard);

                    players[i].mesh.position.set(x,y,z);
                    scene.add(players[i].mesh);

                }


                add_player(-100,10,0);
                //add_player(-200,10,100);


                renderer = new THREE.WebGLRenderer();
                renderer.setClearColor(0xffffff);
                renderer.setPixelRatio(window.devicePixelRatio);
                renderer.setSize(window.innerWidth, window.innerHeight);
                document.body.appendChild(renderer.domElement);                


                animate();


                function animate() {

                    var delta = 1000 * clock.getDelta();

                    //find the position each player is facing relative to
                    //the current player camera position and display the 
                    //correct texture on the player billboard, animate texture
                    for(var i = 0; i < players.length; i++){

                        var a = new THREE.Vector3();
                        a.subVectors(controls.getObject().position, players[i].mesh.position);
                        a.normalize();

                        var b = new THREE.Vector3(0,0,1);
                        b.applyQuaternion(players[i].mesh.quaternion);
                        b.normalize();

                        var theta = Math.acos(a.dot(b)) * (180/Math.PI);
                        if(a.x * a.z < 0){
                            theta = 360.0 - theta;
                        }

                        if((theta > 0 && theta <= 22.5) || (theta > 337.5 && theta <= 359.99)){//south
                            players[i].billboard.material.map = players[i].textures.s;
                            players[i].billboard.needsUpdate = true;
                            players[i].animations.s.update(delta);
                        }
                        else if(theta > 292.5 && theta <= 337.5){//south east
                            players[i].billboard.material.map = players[i].textures.se;
                            players[i].billboard.needsUpdate = true;
                            players[i].animations.se.update(delta);
                        }
                        else if((theta < 292.5 && theta >= 270) || (theta > 90 && theta <= 112.5)){//east
                            players[i].billboard.material.map = players[i].textures.e;
                            players[i].billboard.needsUpdate = true;
                            players[i].animations.e.update(delta);
                        }
                        else if(theta > 112.5 && theta <= 157.5){//northeast
                            players[i].billboard.material.map = players[i].textures.ne;
                            players[i].billboard.needsUpdate = true;
                            players[i].animations.ne.update(delta);
                        }
                        else if(theta > 157.5 && theta <= 202.5){//north
                            players[i].billboard.material.map = players[i].textures.n;
                            players[i].billboard.needsUpdate = true;
                            players[i].animations.n.update(delta);
                        }
                        else if(theta > 202.5 && theta <= 247.5){//northwest
                            players[i].billboard.material.map = players[i].textures.nw;
                            players[i].billboard.needsUpdate = true;
                            players[i].animations.nw.update(delta);
                        }
                        else if((theta > 67.5 && theta <= 89.99) || (theta > 247.5 && theta <= 269.99)){//west
                            players[i].billboard.material.map = players[i].textures.w;
                            players[i].billboard.needsUpdate = true;
                            players[i].animations.w.update(delta);
                        }
                        else if(theta > 22.5 && theta <= 67.5){//southwest
                            players[i].billboard.material.map = players[i].textures.sw;
                            players[i].billboard.needsUpdate = true;
                            players[i].animations.sw.update(delta);
                        }

                        players[i].billboard.lookAt(controls.getObject().position);

                    }


                    requestAnimationFrame(animate);
                    if (controlsEnabled) {

                        var time = performance.now();
                        var delta = ( time - prevTime ) / 1000;

                        velocity.x -= velocity.x * 10.0 * delta;
                        velocity.z -= velocity.z * 10.0 * delta;
                        velocity.y -= 9.8 * 100.0 * delta; // 100.0 = mass

                        if ( moveForward ) velocity.z -= 400.0 * delta;
                        if ( moveBackward ) velocity.z += 400.0 * delta;
                        if ( moveLeft ) velocity.x -= 400.0 * delta;
                        if ( moveRight ) velocity.x += 400.0 * delta;

                        controls.getObject().translateX( velocity.x * delta );
                        controls.getObject().translateY( velocity.y * delta );
                        controls.getObject().translateZ( velocity.z * delta );

                        if ( controls.getObject().position.y < 10 ) {
                                velocity.y = 0;
                                controls.getObject().position.y = 10;
                                canJump = true;
                        }

                        prevTime = time;

                    }

                    renderer.render(scene, camera);

                }


                //keydown event
                document.addEventListener('keydown', function(event){
                    switch (event.keyCode){
                        case 38: // up
                        case 87: // w
                                moveForward = true;
                                break;
                        case 37: // left
                        case 65: // a
                                moveLeft = true; break;
                        case 40: // down
                        case 83: // s
                                moveBackward = true;
                                break;
                        case 39: // right
                        case 68: // d
                                moveRight = true;
                                break;
                        case 32: // space
                                if ( canJump === true ) velocity.y += 350;
                                canJump = false;
                                break;                                    
                        }
                }, false);

                //keyup event
                document.addEventListener('keyup', function(event){
                    switch(event.keyCode) {
                        case 38: // up
                        case 87: // w
                                moveForward = false;
                                break;
                        case 37: // left
                        case 65: // a
                                moveLeft = false;
                                break;
                        case 40: // down
                        case 83: // s
                                moveBackward = false;
                                break;
                        case 39: // right
                        case 68: // d
                                moveRight = false;
                                break;
                        }
                }, false);

                //Window resize event
                window.addEventListener('resize', function(){
                    camera.aspect = window.innerWidth / window.innerHeight;
                    camera.updateProjectionMatrix();
                    renderer.setSize(window.innerWidth, window.innerHeight);
                }, false );

        </script>
    </body>
</html>
\$\endgroup\$

1 Answer 1

1
+50
\$\begingroup\$

I'm not well-versed in three.js, but it looks like you should be able to do it like this...

var view = new THREE.Vector3();
view.subVectors(controls.getObject().position, players[i].mesh.position);

var inverse = players[i].mesh.quaternion.clone();
inverse.inverse();

view.applyQuaternion(inverse);

// view is now a direction in the object's local space.

var angle = Math.Round(Math.atan2(view.x, view.z) * 4/Math.PI);

// Now do your animation selection based on this angle:
// (If I've gotten the coordinate handedness right)

// ±4: toward viewer
//  3: toward-right
//  2: right
//  1: away-right
//  0: away
// -1: away-left
// -2: left
// -3: toward-left
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .