I am attempting to recreate a Flash website using Three.JS but have run into a snag attempting to create some of the functionality.
What should be happening is that I want to have some button images orbit around the center of the screen. They should be able to be stopped if the mouse hovers over them and then clicked to open up a different location on the website.
I have everything working up until the requirement that the buttons stop when the mouse is hovering over them. I am attempting to implement this using a raycast, however when I am moving the mouse around the scene it seems as though the mesh is not at the same place as the rendered texture. In fact it almost seems like the mesh and the rendered image are orbiting in the opposite direction from each other.
Here is my code and I will link you to a page on my website where I am testing this revamp at so you can get a real-world example:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
<style type="text/css">
body { margin: 0;}
canvas { display: block;}
</style>
</head>
<body>
<script>
//var THREE = new THREE();
var renderer,scene,camera,nucleus;
var width = window.innerWidth;
var height = window.innerHeight;
var fov = 60;
var near = 1;
var far = 10000;
var min = 0;
var max = 10;
var insObjSpeed = 0;
var buttons = [];
var numOfBtns = 1;
var projector, mouse = { x: 0, y: 0 }, INTERSECTED;
//var mouse;
function populateScene(){
var geom = new THREE.CircleGeometry(3,50);
var matTx = new THREE.TextureLoader().load('imgs/buttons/logo.png');
var mat = new THREE.MeshBasicMaterial({map:matTx});
nucleus = new THREE.Mesh(geom, mat);
nucleus.position.set(0,0,0);
scene.add(nucleus);
var btn = null;
var btnMat = null;
var plane = new THREE.Plane();
var point = new THREE.Vector3();
var btnMats = [new THREE.TextureLoader().load('imgs/buttons/georo.png'),
new THREE.TextureLoader().load('imgs/buttons/wiki.png'),
new THREE.TextureLoader().load('imgs/buttons/port.png'),
new THREE.TextureLoader().load('imgs/buttons/yt.png'),
new THREE.TextureLoader().load('imgs/buttons/l2r.png'),
new THREE.TextureLoader().load('imgs/buttons/ci.png'),
new THREE.TextureLoader().load('imgs/buttons/ov.png'),
new THREE.TextureLoader().load('imgs/buttons/ttv.png'),
new THREE.TextureLoader().load('imgs/buttons/tw.png'),
new THREE.TextureLoader().load('imgs/buttons/ffs.png')];
var usedMats = [];
for (var i1 = 0; i1 < numOfBtns; i1++){
var matIdx = Math.floor(Math.random() * (max - min + 1)) + min;
var matTx = btnMats[matIdx];
if (usedMats.includes(matTx)){
while (usedMats.includes(matTx)){
matIdx = Math.floor(Math.random() * (max - min + 1)) + min;
matTx = btnMats[matIdx];
}
}
usedMats.push(matTx);
//var matTx = new THREE.TextureLoader().load('imgs/buttons/georo.png')
var geom2 = new THREE.CircleGeometry(3,50);
var mat = new THREE.MeshBasicMaterial({map:matTx});
btn = new THREE.Mesh(geom2, mat);
btn.angle = new THREE.Vector3(Math.random(),
Math.random(),
Math.random()).normalize();
btn.orbitSpeed = 0.0045; //Math.random() * 0.05) * 0.5;
plane.normal.copy(btn.angle);
point.set(Math.random(),Math.random(),Math.random());
plane.projectPoint(point, btn.position);
if (Math.random() > 0.5) { btn.orbitSpeed *= -1; }
btn.position.setLength(Math.floor(Math.random() * 20) + 15);
btn.position.applyAxisAngle(btn.angle, Math.random() / 10);
btn.position.add(nucleus.position);
buttons.push(btn);
scene.add(btn);
}
}
function updateBtns() {
var obj = null;
for (var i1 = 0; i1 < numOfBtns; i1++){
obj = buttons[i1];
obj.position.sub(nucleus.position);
obj.position.applyAxisAngle(obj.angle,obj.orbitSpeed);
obj.position.add(nucleus.position);
//if (i1 == 0){ console.log(obj.position); }
}
}
function init() {
document.body.style.backgroundColor = "#333333";
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
mouse = new THREE.Vector2();
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
document.body.style.overflow = "hidden";
document.body.style.margin = "0";
document.body.style.padding = "0";
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(fov, width/height, near, far);
camera.position.z = 100;
scene.add(camera);
//controls = new THREE.TrackballControls(camera, renderer.domElement);
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
projector = new THREE.Projector();
resize();
window.onresize = resize;
populateScene();
animate();
}
function onDocumentMouseMove( event )
{
// the following line would stop any other event handler from firing
// (such as the mouse's TrackballControls)
event.preventDefault();
// update the mouse variable
mouse.x = ( event.clientX / renderer.domElement.clientWidth) * 2 - 1;
mouse.y = -( event.clientY / renderer.domElement.clientHeight) * 2 + 1;
console.log("mouse pos: " + mouse.x + ", " + mouse.y);
}
function resize(){
width = window.innerWidth;
height = window.innerHeight;
if (renderer && camera){
renderer.setSize(width, height);
camera.aspect = width / height;
camera.updateProjectionMatrix();
}
}
function render(){
renderer.render(scene, camera);
}
function animate(){
checkMouse();
requestAnimationFrame(animate);
updateBtns();
render();
//checkMouseHover();
}
function checkMouse(){
var vector = new THREE.Vector3( mouse.x, mouse.y, 1 );
console.log("ray vec: " + vector.x + ", " + vector.y);
projector.unprojectVector( vector, camera );
var ray = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
// create an array containing all objects in the scene with which the ray intersects
var intersects = ray.intersectObjects( scene.children, true );
// INTERSECTED = the object in the scene currently closest to the camera
// and intersected by the Ray projected from the mouse position
// if there is one (or more) intersections
if ( intersects.length > 0 ) {
// if the closest object intersected is not the currently stored intersection object
if ( intersects[ 0 ].object != INTERSECTED && intersects[0].object !== nucleus) {
// restore previous intersection object (if it exists) to its original color
if ( INTERSECTED ) {
console.log("Intersection detected at: " + intersects[0].point);
INTERSECTED = intersects[0].object;
INTERSECTED.material.color.setHex( INTERSECTED.currentHex );
// store reference to closest object as current intersection object
// store color of closest object (for later restoration)
INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
// set a new color for closest object
INTERSECTED.material.color.setHex( 0xffff00 );
}
}
}
else { // there are no intersections
// restore previous intersection object (if it exists) to its original color
if ( INTERSECTED ) { INTERSECTED.material.color.setHex( INTERSECTED.currentHex ); }
// remove previous intersection object reference
// by setting current intersection object to "nothing"
INTERSECTED = null;
}
}
function onMouseOver(ev){
insObjSpeed = ev.object.orbitSpeed;
//ev.object.orbitSpeed = 0.0;
}
function onMouseOut(ev) { ev.object.orbitSpeed = insObjSpeed; }
function THREEReady() { init(); }
function onMouseMove(event){
mouse.x = (event.clientX/width)*2-1;
mouse.y = (event.clientY/height)*2-1;
}
(function() {
window.addEventListener('mousemove',onMouseMove,false);
function addScript(url, callback){
callback = callback || function() {};
var script = document.createElement("script");
script.addEventListener("load", callback);
script.setAttribute("src", url);
document.head.appendChild(script);
}
addScript("./js/ThreeJS_0.119.1.js",function() {
addScript("./js/ThreeJS_0.119.1_Projector.js",function(){
THREEReady();
})});
})();
</script>
</body>
For starters, your
mouseMove()
is missing the negative sign before the y-value calculation. This means your y-value gets flipped (it's-1
when it should be+1
) It should be as follows:Secondly, I'm not sure why you're creating a new Vector3 on each frame when you can just use the existing
mouse
Vector2, as outlined in the docs.There's no need to perform
unprojectVector
then subtract camera position, or any of that if you usesetFromCamera()
.Edit: Just realize you also have two listeners that override each other (
onMouseMove
andonDocumentMouseMove
), one of which has the wrong formula to calculatemouse.y
, and that's why you're getting an inverted y value.