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;
}