4
\$\begingroup\$

I'm working on a test project porting a simple game from iOS to Javascript. I've got the orthographic camera working and it can view the objects in the scene and move up and down the tower of meshes in the scene. Now I am trying to select the meshes with raycasting and I'm having trouble getting any of the objects to register. I have done a lot of research, spent some time in the debugger, and tried configuring things a number of different ways without success. I have checked my code numerous times and I think I am doing everything correctly as per the Three.js examples I can find, but maybe I am missing something obvious.

First I set up the global variables:

var container;
var camera;
var scene;
var renderer;
var projector;

var mouseX = 0;
var mouseY = 0;

//implemented a fixed game size here
var windowWidth = 800;
var windowHeight = 600;
var windowHalfX = windowWidth / 2;
var windowHalfY = windowHeight / 2;

var intersects = [];
var entities = [];

//this is the game model object
var worldSize = new WorldSize(windowHalfX, windowHalfY);
var tower = new Tower(worldSize);

Then I initialize the camera, renderer and projector:

var zoom = 1.0;
camera =  new THREE.OrthographicCamera(windowWidth * zoom / -2, windowWidth * zoom / 2, windowHeight * zoom / 2, windowHeight * zoom / -2, 0, 1000);
//z value of position can be zero or positive but negative does not work
camera.position = new THREE.Vector3(0,0,0);
camera.lookAt(new THREE.Vector3(0,0,0));
camera.updateProjectionMatrix();

projector = new THREE.Projector();

renderer = new THREE.WebGLRenderer({clearColor: 0xff0000, clearAlpha: 1});
renderer.setClearColor(0xC0C0C0, 1);     //this is light grey
renderer.setSize(windowWidth, windowHeight);
container.appendChild(renderer.domElement);

Then I iterate through my game model and add the objects to the scene:

var floorShape = THREE.SceneUtils.createMultiMaterialObject( 
    new THREE.BoxGeometry(500, tower.towerFloors[obj].floorSize * 10, 1), 
    multiMaterial);

//negative values work for the Z for this, 0 looks strange, positive not in view
floorShape.position.set(0, tower.towerFloors[obj].yposition * 10, -20);

scene.add(floorShape);
entities.push(floorShape);

And finally on a mouse click I call this function:

function onDocumentMouseDown(event) {
    event.preventDefault();

    var vectorx = (event.clientX / windowWidth) * 2 - 1;
    var vectory = -(event.clintY / windowHeight) * 2 + 1;
    var vectorz = camera.position.z;
    var vector = new THREE.Vector3(vectorx, vectory, vectorz);

    //quick hack to make sure the x is not the problem
    vector.x = camera.position.x;

    projector.unprojectVector(vector, camera);

    var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position), 0, -100);
    // use picking ray since it's an orthographic camera
    raycaster = projector.pickingRay(vector, camera);

    intersects = raycaster.intersectObjects(entities);

    alert(vector.x + ' , ' + vector.y + ',' + vector.z);

    if ( intersects.length > 0 ) {
        console.log( intersects[ 0 ] );
        alert('did it');
    }
}

When I click the screen, the value that shows up in the alert is (0, theCorrectValueForY, 0). When I check the objects in the scene.children or in the entities array, they are at the proper xyz positions.

When debugging I noticed that when I first call var raycaster = ..., the near is set to 0 and the far is set to -100 like I told it to be. But after the call to raycaster = projector.pickingRay, the far value is set to null value. I've tried all different values for the near and far for the raycaster, and none of them have worked. The origin of the ray is correctly at the clicked position, and the direction it is moving is always (0, 0, -1) after raycaster = projector.pickingRay.

I can think of a couple possible problems, such as possibly I am somehow not moving the actual mesh of the objects to match their positions? Or maybe I am sending the ray in the wrong direction? But I cannot figure it out. I don't know if I am doing something wrong, or if some combination of correct values for the camera position, object positions, and raycasting values would work as expected. I would very much appreciate any help!

Edit: Here is the complete code in case anyone wants to try it. index.html

<!DOCTYPE html>
<html>
<head lang="en">
    <style>

        body {
        background-color: #ffffff;
        margin: 50px;
        overflow: hidden;
        font-family:Monospace;
        font-size:13px;
        text-align:center;
        font-weight: bold;
        text-align:center;
    }

    div{
        background: #ffffff;
    }
    </style>

    <meta charset="UTF-8">
    <title></title>
</head>

<body>
<script src="js/jquery.min.js"></script>
<script src="js/three.min.js"></script>
<script src = "js/three2dtest.js"></script>
</body>
</html>

three2dtest.js

var container;
var camera;
var scene;
var renderer;
var projector;

var mouseX = 0;
var mouseY = 0;

//implemented a fixed game size here
var windowWidth = 800;
var windowHeight = 600;
var windowHalfX = windowWidth / 2;
var windowHalfY = windowHeight / 2;

var particleMaterial;
var intersects = [];
var entities = [];

//this is the game model object
var worldSize = new WorldSize(windowHalfX, windowHalfY);
var tower = new Tower(worldSize);

init();

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

    //test text on screen
    var info = document.createElement('div');
    info.style.position = 'absolute';
    info.style.top = '2px';
    info.style.width = '100%';
    info.style.textAlign = 'center';
    info.innerHTML = '<p>clickable tower floors</p>';
    container.appendChild(info);

    var zoom = 1.0;
    camera =  new THREE.OrthographicCamera(windowWidth * zoom / -2, windowWidth * zoom / 2, windowHeight * zoom / 2, windowHeight * zoom / -2, 0, 1000);
    //z value of position can be zero or positive but negative does not work
    camera.position = new THREE.Vector3(0,0,0);
    camera.lookAt(new THREE.Vector3(0,0,0));
    camera.updateProjectionMatrix();

    scene = new THREE.Scene();
    //scene.fog = new THREE.FogExp2( 0x000000, 0.0025 );
    console.log(scene);

    projector = new THREE.Projector();

    //particle material
    var PI2 = Math.PI * 2;
    particleMaterial = new THREE.SpriteCanvasMaterial( {
        color:0x000000,
        program: function(context) {
            context.beginPath();
            context.arc(0,0,0.5,0,PI2,true);
            context.fill();
        }
    });

    for (obj in tower.towerFloors) {
        console.log(tower.towerFloors[obj].yposition);

        // Using wireframe materials to illustrate shape details.
        var darkMaterial = new THREE.MeshBasicMaterial({color: 0xffffcc});
        var lightMaterial = new THREE.MeshBasicMaterial({color: 0xffffff});
        var wireframeMaterial = new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true, transparent: true}); 

        var localFloor = tower.towerFloors[obj];
        if (localFloor.hasBeenClicked === true) {
            var multiMaterial = [lightMaterial, wireframeMaterial];
        } else {
            var multiMaterial = [darkMaterial, wireframeMaterial];
        }

        // create a cube for each of the floors on the tower
        var floorShape = THREE.SceneUtils.createMultiMaterialObject( 
        new THREE.BoxGeometry(500, tower.towerFloors[obj].floorSize * 10, 1), 
    multiMaterial);

        //negative values work for the Z for this, 0 looks strange, positive not in view
        floorShape.position.set(0, tower.towerFloors[obj].yposition * 10, -20);
        scene.add(floorShape);

        entities.push(floorShape);
    }

    renderer = new THREE.WebGLRenderer({clearColor: 0xff0000, clearAlpha: 1});
    //renderer = new THREE.CanvasRenderer();
    renderer.setClearColor(0xC0C0C0, 1);     //this is light grey
    renderer.setSize(windowWidth, windowHeight);
    container.appendChild(renderer.domElement);

    document.addEventListener( 'mousemove', onDocumentMouseMove, false );
    window.addEventListener( 'resize', onWindowResize, false );

    document.addEventListener('mousedown', onDocumentMouseDown, false);

    //should be better to start animate inside here rather than outside
    animate();
}

//new function from documentation but not the raycasting is not working
function onDocumentMouseDown(event) {
    event.preventDefault();

    var vectorx = (event.clientX / windowWidth) * 2 - 1;
    var vectory = (event.clintY / windowHeight) * 2 + 1;
    var vectorz = camera.position.z;
    var vector = new THREE.Vector3(vectorx, vectory, vectorz);

    //quick hack to make sure the x is not the problem
    vector.x = camera.position.x;

    projector.unprojectVector(vector, camera);

    var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize(), 0, -10000);
    // use picking ray since it's an orthographic camera
    raycaster = projector.pickingRay(vector, camera);

    intersects = raycaster.intersectObjects(entities);

   alert(vector.x + ' , ' + vector.y + ',' + vector.z);

    if ( intersects.length > 0 ) {

        console.log( intersects[ 0 ] );
        alert('did it');
    }
}

function onDocumentMouseMove( event ) {
    mouseX = event.clientX - windowHalfX;
    mouseY = event.clientY - windowHalfY;
}
function onWindowResize() {
    windowHalfX = windowWidth/ 2;
    windowHalfY = windowHeight / 2;

    camera.aspect = windowWidth / windowHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(windowWidth, windowHeight);
}

function animate() {
    requestAnimationFrame( animate );
    render();
}

function render() {
    //move only up and down
    //camera.position.x += ( mouseX - camera.position.x ) * 0.05;
   camera.position.y += ( - mouseY - camera.position.y / 10 ) * 0.15;

    //the camera wont move above the top floor so no bounds check needed
    if (camera.position.y < 0) {
        camera.position.y = 0;
    }

    camera.updateProjectionMatrix();

    renderer.render(scene, camera);
}

///////////////////////////////////////////////
/*           Tower Objects                   */
///////////////////////////////////////////////
function Tower (worldSize) {
    this.towerFloors = {};

    var floorNumber = 0;
    for (floorNumber = 0; floorNumber < 50; floorNumber++) {

        var floor = new Floor(floorNumber, worldSize);

        this.towerFloors[floorNumber] = floor;
    }

    for (obj in this.towerFloors) {
       //var thing = this.towerFloors[obj];
        console.log(this.towerFloors[obj].floorNumber);
    }
}
function Floor (floorNumber, worldSize) {
    this.floorNumber = floorNumber;
    this.floorSize = worldSize.ysize / 50;
    this.yposition = this.floorSize * floorNumber;
    this.hasBeenClicked = false;
}
function WorldSize (xsize, ysize) {
    this.xsize = xsize;
    this.ysize = ysize;
}
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Not sure what's going on here. The whole point of vector is to convert the mouse location into a 3d point in front of the camera. Setting Z to camera.position.z when the frame of reference is currently focused on the camera (camera is 0,0,0) and then unprojecting that value into world space in particular makes no sense.

var vectorx = (event.clientX / windowWidth) * 2 - 1;
var vectory = (event.clientY / windowHeight) * 2 + 1;
var vectorz = camera.position.z;
var vector = new THREE.Vector3(vectorx, vectory, vectorz);

Try the code below. It simply creates point in front of the camera based on the click coordinates, unprojects that point so that it is in the appropriate position in the world space, and then creates a ray from those two points.

var vector = new THREE.Vector3(event.clientX, event.clientY, 1);
projector.unprojectVector(vector, camera);
raycaster.set(camera.position, vector.sub(camera.position).normalize()); 
\$\endgroup\$

You must log in to answer this question.

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