-5

I'm trying to make StarCraft like game in THREE.JS..

I have problem with rotating object on its' Y-axis to face the new direction, and then change position.

and by rotating I mean ANIMATING / so please stop with a crappy lookAt() function

I would appreciate if someone told me how to TWEEN some magic obj.MatrixRotationY(angle), and I want that angle to be calculated from Vector3

Here is what I have so far:

http://f.cl.ly/items/3J3R0X2q2R1h1J203C1V/drone.jpg

I take that new position from Vector3 as You can see below.

// scene
var container;
var camera, scene, renderer;

// intersect with objects in this array
var intersectObjects    = [];

// materials
var material = new THREE.MeshNormalMaterial();
var cristal_texture = new THREE.MeshLambertMaterial({ map: THREE.ImageUtils.loadTexture("models/JSON/textures/cristal.jpg") });
var destination;

init();
animate();


function init() {

    container = document.createElement( 'div' );
    document.body.appendChild( container );

    camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 15000 );
    camera.rotation.x = - 90 * ( Math.PI / 180 );
    camera.position.set( 800 , 1000 , 0 );

    scene = new THREE.Scene();

    plane = new THREE.Mesh( new THREE.PlaneGeometry( 3000, 3000 ), new THREE.MeshBasicMaterial( { color: 0xe0e0e0 } ) );
    plane.rotation.x = - 90 * ( Math.PI / 180 );
    plane.position.set(0,0,0);
    plane.overdraw = true;
    scene.add( plane );
    intersectObjects.push(plane);

    loader = new THREE.JSONLoader();
    loader.load( "models/JSON/driller.js", function( geometry ) {
        driller = new THREE.Mesh( geometry, material );
        driller.position.set(0,50,0);
        matr = new THREE.Matrix4();     
        driller.matrixAutoUpdate = false;
        driller.geometry.applyMatrix( matr.makeRotationY( 0 ) );
        driller.scale.set(0.5,0.5,0.5);
        scene.add( driller );
    });

    loade = new THREE.JSONLoader();
    loade.load( "models/JSON/cristal.js", function( geometry ) {
        cristal = new THREE.Mesh( geometry, cristal_texture );
        cristal.position.set(-1450,0,1450);
        matre = new THREE.Matrix4();        
        cristal.matrixAutoUpdate = false;
        cristal.geometry.applyMatrix(matre.makeRotationY( 0 ));
        cristal.scale.set(0.5,0.5,0.5);         
        scene.add( cristal );
        intersectObjects.push(plane);
    });

    // lightning properties
    var ambientLight = new THREE.AmbientLight(0xFFFFFF);
    scene.add(ambientLight);
    scene.matrixAutoUpdate = false;

    // render engine
    renderer = new THREE.WebGLRenderer( { antialias: true } );
    renderer.setSize( window.innerWidth, window.innerHeight );
    renderer.sortObjects = false;
    container.appendChild( renderer.domElement );

    // event listeners
    document.addEventListener('mouseup', onMouseUp, false);


}

function onMouseUp(event) {

    event.preventDefault();        
    x_pos = (event.clientX / window.innerWidth) * 2 - 1;
    y_pos = -(event.clientY / window.innerHeight) * 2 + 1;
    z_pos = 0.5;

    var vector = new THREE.Vector3( x_pos , y_pos , z_pos );

    var projector = new THREE.Projector();
    projector.unprojectVector(vector, camera);
    var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
    var intersects = raycaster.intersectObjects(intersectObjects);

    if (intersects.length > 0) {

        xp = intersects[0].point.x.toFixed(2);
        yp = intersects[0].point.y.toFixed(2);
        zp = intersects[0].point.z.toFixed(2);  
        destination = new THREE.Vector3( xp , 50 , zp );

        var se23k = Math.random() * 4 * 4 ;
        new TWEEN.Tween( driller.rotation ).to( { y: se23k }, 1000 ).easing( TWEEN.Easing.Linear.None).start();

    }

    else {
        console.log('outside boundaries');
    }

};

function update(){  

   camera.lookAt( plane.position );
   renderer.render( scene, camera );
}

function animate() {

    requestAnimationFrame( animate );       
    update();
    render();
}

function render() {
    driller.updateMatrix();
    cristal.updateMatrix();

    TWEEN.update();
    renderer.render( scene, camera );       
}
  • If by rotating you mean animating, why not just say that in the first place? And what do you mean by "so please stop with a crappy lookAt() function"? It seems like you will need to use tween in conjunction with lookAt() to get your desired effect. Are you trying to write your code without using the lookAt() function? – Stemkoski Jun 20 '13 at 01:42
  • Will try and provide a larger answer later, but it will involve creating two QUaterions from Euler angles, and slerping between them. http://threejs.org/docs/58/#Reference/Math/Quaternion #slerp – David Souther Jun 24 '13 at 16:33
  • 1
    Also, you'll get fewer downvotes if you remove the line about lookAt - lookAt is a very important and useful function for a multitude of situations. While it does not work for your issue, it certainly is not "crappy". – David Souther Jun 24 '13 at 16:34

2 Answers2

1

Here's some code that rotates a ship between pointing up the Z axis to pointing up the X axis, smoothly.

There is a demo video at http://youtu.be/jBlDVSBv_m4

I've trimmed out massive parts of the renderer setup, to focus just on the quaternion slerping.

The following code is in coffee script, and uses a couple custom classes from S3age; I've commented them where possible. If this is problematic, mention it in a comment and I'll rewrite it to VanillaJS.

# RequireJS preamble, not important.
define ["util/stage", "game/clock"],
    (Stage, Clock)->
        ###
        # Ship is a constructor.
        # It uses no ctor arguments, but does pass them to Object3D.
        ###
        Ship = (Object3DArgs)->
            # Ship directly extends Object3D
            THREE.Object3D.apply @, arguments
            @build()

            # The critical piece - use the model's quaternion, not its rotation
            @useQuaternion = yes
            @

        # Finish subclassing, by creating a new prototype chain.
        Ship:: = Object.create THREE.Object3D::
        ###
        # Create some geometry, materials, etc. Not important, could be from
        # Collada/JSON/etcLoader
        # Important part is noticing the rotation and position are set relative to
        # the Ship object itself; moving or rotating @ will move/rotate the 
        # geometry inside it implicitly.
        ###
        Ship::build = ->
            geom = new THREE.CylinderGeometry 0, 3, 1.5, 20, 1
            mat = new THREE.MeshLambertMaterial
            @hull = new THREE.Mesh geom, mat
            @hull.scale.set(0.3, 1, 0.1)
            @hull.rotation.set(Math.PI / 2, 0, 0)
            @hull.position.set(0, 0, 0.33)
            @add @hull
            @


        ###
        # An easing function, to go from ticks on a frame counter to some
        # percentage between zero and one, over the course of some time.
        ###
        ease = do ->
            seconds = 4
            d = time = (seconds * 60)
            b = 0.0
            c = 1.0
            #easeInOutQuad from http://www.robertpenner.com/easing/
            (t) ->
                # Constrain `t` between `0` and the animation's duration
                t %= d 
                if ((t /= d/2 ) < 1)
                    c/2*t*t + b
                else
                    -c/2 * ((--t)*(t-2) - 1) + b

        # Y is the axis to rotate through. In this instance, it is the Y axis.
        # In general, it should be the "Up" vertex of the model.
        # You will calculate this when the user clicks.
        y = new THREE.Vector3(0, 1, 0)

        # r0 is the initial direction when the animation starts;
        # that is, the "up" vector at theta=0
        r0 = new THREE.Quaternion()
        r0.setFromAxisAngle(y, 0)

        # r1 is the desired direction. Calculate theta from the dot product of the
        # ship's normal with the vector pointing from the ship to the target (eg
        # target.position - ship.position)
        r1 = new THREE.Quaternion()
        r1.setFromAxisAngle(y, Math.PI / 2)

        ###
        # The update function will get called every physics update.
        # Clock has time, delta, and frame.
        # Frame is number of ticks, time is total time elapsed,
        # delta is time since last update.
        ###
        Ship::update = (clock)->
            # Get the prct between zero and one of how far through
            # the animation the clock is
            prct = ease clock.frame
            # Use the static slerp, storing the result directly
            # in the ship's quaternion.
            THREE.Quaternion.slerp r0, r1, @quaternion, prct

        # A convention in my loader, not important.
        # Creates a S3age, gets the scene, etc.
        play: (selector)->
            stage = new Stage selector
            stage.scene = new THREE.Scene()

            stage.scene.add ship = new Ship()

            # Some lights, controls, an axis.
            stage.scene.add new THREE.AmbientLight 0x404040
            stage.scene.add light = new THREE.PointLight 0xF0F0F0, 1, 100
            light.position.set(20, 30, 50)
            stage.controls = new THREE.Trackball stage.camera, stage.container
            stage.camera.position.set(2, 5, -5)
            stage.scene.add new THREE.AxisHelper(2)
            # Call the update function every time the clock goes tick-tock
            (new Clock()).addEventListener 'tick', (event)->
                clock = event.clock
                ship.update clock
            stage.start()
David Souther
  • 8,125
  • 2
  • 36
  • 53
0

Actually, I've found quicker solution ;)

        xp = intersects[0].point.x.toFixed(2);
        yp = intersects[0].point.y.toFixed(2);
        zp = intersects[0].point.z.toFixed(2);  
        destination = new THREE.Vector3( xp , yp , zp );

        radians =  Math.atan2( ( driller.position.x - xp) , (driller.position.z - zp));
        radians += 90 * (Math.PI / 180);

        var tween = new TWEEN.Tween(driller.rotation).to({ y : radians },200).easing(TWEEN.Easing.Linear.None).start();
  • I don't see this working in the general case... you are getting the spin from a vector at the origin pointing towards the driller to the vector at the origin pointing towards the target, NOT the spin from the vector pointing in the direction the driller is facing to the vector at the driller towards the target. – David Souther Jun 26 '13 at 17:06
  • Well, I wanted just that. What can I say – Tomasz Drozdowski Jun 26 '13 at 19:48