How to zoom a three.js scene with the mouse wheel? - javascript

I have a simple three.js graphics, and I tried to use the answers in this and this question to make the created plot zoomable by the mouse wheel. By using the mouse wheel I would like to zoom in to the graphics or to zoom out.
Here is the complete code: pastebin link
However, when turning the mouse wheel nothing happens, and I do not get an error message. Maybe I am missing something?

DEMO
var container, camera, scene, renderer, colors;
var selected = 0;
var selectedObject;
var objects = [];
// DOM element...
container = document.createElement('div');
document.body.appendChild(container);
// Camera...
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(0, 0, 150);
// Scene...
scene = new THREE.Scene();
scene.add(camera);
// Renderer...
renderer = new THREE.WebGLRenderer({
clearAlpha: 1
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xffffff, 1);
document.body.appendChild(renderer.domElement);
// Scatter plot...
scatterPlot = new THREE.Object3D();
scene.add(scatterPlot);
// Plot some random points...
circle = new THREE.CircleGeometry(1, 20);
colors = [];
var max = 50;
var min = -50;
for (var i = 0; i < 10; i++) {
var object = new THREE.Mesh( circle.clone(), new THREE.MeshBasicMaterial( { color: new THREE.Color('black'), opacity: 0.5 } ) );
object.position.x = Math.random() * (max - min) + min;
object.position.y = Math.random() * (max - min) + min;
object.position.z = 0;
scene.add( object );
objects.push( object );
}
animate();
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
document.addEventListener( 'mousewheel', onDocumentMouseWheel, false );
function onDocumentMouseDown( event ) {
event.preventDefault();
mouse.x = ( event.clientX / renderer.domElement.clientWidth ) * 2 - 1;
mouse.y = - ( event.clientY / renderer.domElement.clientHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( objects );
if ( intersects.length > 0 ) {
//intersects[ 0 ].object.material.color.set('red');
//intersects[ 0 ].object.geometry.scale(1.1,1.1,1.1);
if (selected === 0) {
selected = 1;
selectedObject = intersects[ 0 ].object;
selectedObject.material.color.set('red');
console.log(selectedObject.position.x);
} else {
selected = 0;
var geometry = new THREE.Geometry();
geometry.vertices.push(intersects[ 0 ].object.position);
geometry.vertices.push(selectedObject.position);
var line = new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: 0x0000ff }));
scene.add(line);
selectedObject.material.color.set('black');
}
}
}
function onWindowResize() {
camera.left = window.innerWidth / - 2;
camera.right = window.innerWidth / 2;
camera.top = window.innerHeight / 2;
camera.bottom = window.innerHeight / - 2;
camera.aspect = window.innerWidth / window.innerHeight;
renderer.setSize( window.innerWidth, window.innerHeight );
}
function onDocumentMouseWheel( event ) {
var fovMAX = 160;
var fovMIN = 1;
camera.fov -= event.wheelDeltaY * 0.05;
camera.fov = Math.max( Math.min( camera.fov, fovMAX ), fovMIN );
camera.projectionMatrix = new THREE.Matrix4().makePerspective(camera.fov, window.innerWidth / window.innerHeight, camera.near, camera.far);
}
body { margin: 0; }
canvas { width: 100%; height: 100% }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/54/three.js"></script>
In your code add event listener
document.addEventListener( 'mousewheel', onDocumentMouseWheel, false );

document.addEventListener( 'mousewheel', (event) => {
camera.position.z +=event.deltaY/500;
});

The mousewheel event is supported only in Webkit browsers.
For cross-browser compaitibility use the wheel event.

Related

How to drag a scene with three.js?

In the following html page I create a scene with some points, in which you can zoom comfortably by using the mouse wheel.
But what I want to do is to drag that scene after I have zoomed in. I want to press the left mouse button, keep it pressed and then move the mouse. I want the scene to move accordingly (e.g. by changing the x/y coordinates of the camera).
I tried to create a listener to listen to clicks, but when I click somewhere I do not see any console output.
I also searched and found the suggestion to use DragControls, but this does not seem to be defined in THREE. At least I get an error when I uncomment these lines.
So how to implement something so I can drag the whole scene (or the camera)?
Code:
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r79/three.min.js"></script>
<title>Test</title>
</head>
<body>
<script>
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(0, 0, 150);
scene = new THREE.Scene();
scene.add(camera);
renderer = new THREE.WebGLRenderer({
clearAlpha: 1
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x228B22, 1);
document.body.appendChild(renderer.domElement);
// Define a standard Circle
circle = new THREE.CircleGeometry(1, 20);
var max = 50;
var min = -50;
for (var i = 0; i < 100; i++) {
var object = new THREE.Mesh( circle.clone(), new THREE.MeshBasicMaterial( { color: new THREE.Color('yellow'), opacity: 0.5 } ) );
object.position.x = Math.random() * (max - min) + min;
object.position.y = Math.random() * (max - min) + min;
object.position.z = 0;
scene.add( object );
}
document.addEventListener( 'mousewheel', onDocumentMouseWheel, false );
function onDocumentMouseWheel( event ) {
var fovMAX = 100;
var fovMIN = 1;
camera.fov -= event.wheelDeltaY * 0.05;
camera.fov = Math.max( Math.min( camera.fov, fovMAX ), fovMIN );
camera.projectionMatrix = new THREE.Matrix4().makePerspective(camera.fov, window.innerWidth / window.innerHeight, camera.near, camera.far);
}
document.addEventListener( 'mouseclick', onDocumentMouseClick, false );
function onDocumentMouseClick( event ) {
console.log("mouseclick! " + event.offsetX + "-" + event.offsetY, );
}
animate();
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
//// undefined:
//var controls = new THREE.DragControls( objects, camera, renderer.domElement );
//controls.addEventListener( 'dragstart', function ( event ) {
// event.object.material.emissive.set( 0xaaaaaa );
//} );
//
//controls.addEventListener( 'dragend', function ( event ) {
// event.object.material.emissive.set( 0x000000 );
//} );
</script>
</body>
</html>
Maybe there is a better way to do this, but I found a way (see below). The trick is to have a flag that tells if the mouse button is pressed, and then you have an algorithm to determine the difference on the previous position, and move the camera accordingly.
var mouseDown = false;
var mousePos = [0,0];
var cameraPos = 0;
document.addEventListener('mousedown', onMouseDown, false);
function onMouseDown( event ) {
mouseDown = true;
mousePos = [event.offsetX, event.offsetY];
cameraPos = camera.position;
}
document.addEventListener('mouseup', onMouseUp, false);
function onMouseUp( event ) {
mouseDown = false;
}
document.addEventListener('mousemove', onMouseMove, false);
function onMouseMove( event ) {
if (mouseDown) {
// scale factor takes into account the current FOV
scale = Math.tan(camera.fov/2 * Math.PI / 180)/1.5;
dx = mousePos[0] - event.offsetX;
dy = mousePos[1] - event.offsetY;
x = cameraPos.x + scale*dx;
y = cameraPos.y - scale*dy;
camera.position.x = x;
camera.position.y = y;
mousePos = [event.offsetX, event.offsetY];
cameraPos = camera.position;
}
}

three.js make object dynamically on double click

I made a site that has graphical 3d made by three.js.
It has so many square box and user can add more boxes.
When user double clicks the scene anywhere the box will be added dynamically at that point. (mouse cursor's point)
My code is here:
// Define Variables
var myElement = document.getElementById("threejs");
let camera, scene, renderer;
const mouse = new THREE.Vector2();
const clickMouse = new THREE.Vector2();
const target = new THREE.Vector2();
const windowHalf = new THREE.Vector2( window.innerWidth / 2, window.innerHeight / 2 );
const moveState = {forward: 0, back: 0};
const moveVector = new THREE.Vector3( 0, 0, 0 );
onMouseMove = (event) => {
mouse.x = ( (event.clientX/2) - (windowHalf.x/2) );
mouse.y = ( (event.clientY/2) - (windowHalf.y/2) );
}
onResize = (event) => {
const width = window.innerWidth;
const height = window.innerHeight;
windowHalf.set( width / 2, height / 2 );
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize( width, height );
}
init = () => {
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1500 );
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = 200;
scene = new THREE.Scene();
const geometry = new THREE.BoxBufferGeometry();
const material = new THREE.MeshNormalMaterial({ transparent: true });
const object = new THREE.Mesh( geometry, material );
object.position.x = Math.random() * 80 - 40;
object.position.y = Math.random() * 80 - 40;
object.position.z = Math.random() * 80 - 40;
scene.add(object);
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
// Event handler
document.addEventListener('mousemove', onMouseMove, false);
document.addEventListener('dblclick', onClickScene, false);
window.addEventListener('resize', onResize, false);
function onClickScene(event) {
clickMouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
clickMouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
console.log(clickMouse.x);
const test = new THREE.Mesh( geometry, material );
test.position.x = clickMouse.x;
test.position.y = clickMouse.y;
scene.add(test);
}
animate = () => {
// For camera follow mouse cursor
target.x = ( 1 - mouse.x ) * 0.002;
target.y = ( 1 - mouse.y ) * 0.002;
camera.rotation.x += 0.05 * ( target.y - camera.rotation.x );
camera.rotation.y += 0.05 * ( target.x - camera.rotation.y );
if(isMobile) {
controls.update();
}
switch(hold) {
case 1:
camera.position.z -= 2;
break;
case 3:
camera.position.z += 2;
break;
}
requestAnimationFrame( animate );
renderer.render( scene, camera );
}
// Run
animate();
}
// Run
init();
Look at the function onClickScene(event).
I defined:
clickMouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
clickMouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
console.log(clickMouse.x);
const test = new THREE.Mesh( geometry, material );
test.position.x = clickMouse.x;
test.position.y = clickMouse.y;
scene.add(test);
So when I double click the scene, mouse point's x value and y value was printed to console.
And it's value is depend on scene's location.
But when I double click the scene, object created at same location.
I want to make object at the double clicked scene's location.
Why object created at the same position?
Is there any solution to this?
Thanks.
Object is created at the same position because you assign window plane coordinates. You probably want to raycast from mouse position to a plane https://threejs.org/docs/#api/en/core/Raycaster.
( event.clientX / window.innerWidth ) * 2 - 1
These are the coordinates on a 2D window, not in your 3D scene.
Take a look at this example https://threejs.org/examples/?q=voxel#webgl_interactive_voxelpainter.

Multiple moving objects with text attached to each object

I've been playing with the interactive cubes and decided to add a click function, which leads to a specific link. Now I would like to add unique text on each block which is rendered as a material. For right now, I am using a single material, so I am thinking I need to create an array for all my possible materials, however whenever I reference that array in the parameters for building the mesh of my objects, I get a setHex() is undefined returned to me in the console. How can I attach a unique material to each cube?
<html lang="en">
<head>
<title>three.js webgl - interactive cubes</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
font-family: Monospace;
background-color: #f0f0f0;
margin: 0px;
overflow: hidden;
}
div#stats{
display:none;
}
p{
position:absolute;
top:500px;
left:500px;
}
</style>
</head>
<body>
<div><p>Hello!</p></div>
<script src="../build/three.min.js"></script>
<script src="js/renderers/Projector.js"></script>
<script src='../build/threex.dynamictexture.js'></script>
<script src="js/libs/stats.min.js"></script>
<script>
var container, stats;
var camera, scene, raycaster, renderer;
var mouse = new THREE.Vector2(), INTERSECTED;
var radius = 100, theta = 0;
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
var info = document.createElement( 'div' );
info.style.position = 'absolute';
info.style.top = '10px';
info.style.width = '100%';
info.style.textAlign = 'center';
// info.innerHTML = 'three.js webgl - interactive cubes';
container.appendChild( info );
camera = new THREE.PerspectiveCamera( 100, window.innerWidth / window.innerHeight, 1, 10000 );
scene = new THREE.Scene();
var light = new THREE.DirectionalLight( 0x6699CC, 0.5 );
light.position.set( 1, 1, 1 ).normalize();
scene.add( light );
raycaster = new THREE.Raycaster();
projector = new THREE.Projector();
renderer = new THREE.WebGLRenderer();
renderer.setClearColor( 0xf0f0f0 );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.sortObjects = false;
container.appendChild(renderer.domElement);
var dynamicTexture = new THREEx.DynamicTexture(512,512);
var dynamicTexture2 = new THREEx.DynamicTexture(512,512);
dynamicTexture.context.font = "bold"+(0.2*512)+ "px Courier";
dynamicTexture.texture.anisotropy = renderer.getMaxAnisotropy();
dynamicTexture2.context.font = "bold"+(0.2*512)+ "px Courier";
dynamicTexture2.texture.anisotropy = renderer.getMaxAnisotropy();
dynamicTexture2.clear('blue');
dynamicTexture.drawTextCooked({
text: 'alltheletters',
lineHeight: 0.2,
})
dynamicTexture.clear('gray');
dynamicTexture.drawTextCooked({
text: 'wow text text text',
lineHeight: 0.2,
})
// var materialArray = [];
// materialArray.push(new THREE.MeshBasicMaterial({map: dynamicTexture2 }))
scene.add(object);
for ( var i = 0; i < 5; i ++ ) {
var object = new THREE.Mesh( geometry, material );
object.position.x = Math.random() * 800 - 400;
object.position.y = Math.random() * 800 - 400;
object.position.z = Math.random() * 800 - 400;
object.rotation.x = Math.random() * 2 * Math.PI;
object.rotation.y = Math.random() * 2 * Math.PI;
object.rotation.z = Math.random() * 2 * Math.PI;
object.scale.x = Math.random() + 0.5;
object.scale.y = Math.random() + 0.5;
object.scale.z = Math.random() + 0.5;
scene.add( object );
var geometry = new THREE.BoxGeometry( 62, 62, 62 );
var material = new THREE.MeshBasicMaterial({
map: dynamicTexture.texture,
color:Math.random() * 0xffffff,
name: "box1",
})
switch (i) {
case 0:
object.userData = {
URL: "http://google.com"
};
break;
case 1:
object.userData = {
URL: "http://yahoo.com"
};
break;
case 2:
object.userData = {
URL: "http://msn.com"
};
break;
case 3:
object.userData = {
URL: "http://engadget.com"
};
break;
case 4:
object.userData = {
URL: "http://stackoverflow.com"
};
break;
}
}
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
container.appendChild( stats.domElement );
document.addEventListener('mousedown', onDocumentMouseDown,false);
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
//
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function onDocumentMouseMove( event ) {
event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
function onDocumentMouseDown(event) {
event.preventDefault();
var vector = new THREE.Vector3((event.clientX / window.innerWidth) * 2 -
1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5);
projector.unprojectVector(vector, camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position)
.normalize());
var intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
window.open(intersects[0].object.userData.URL);
}
}
//
function animate() {
requestAnimationFrame( animate );
render();
stats.update();
}
function render() {
theta += 0.1;
camera.position.x = radius * Math.sin( THREE.Math.degToRad( theta ) );
camera.position.y = radius * Math.sin( THREE.Math.degToRad( theta ) );
camera.position.z = radius * Math.cos( THREE.Math.degToRad( theta ) );
camera.lookAt( scene.position );
camera.updateMatrixWorld();
// find intersections
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( scene.children ), material;
// for(var p =0; p < intersects.length; p++){
// }
if (intersects.length > 0) {
if (INTERSECTED != intersects[0].object) {
if (INTERSECTED){
material = INTERSECTED.material;
if(material.emissive){
material.emissive.setHex(INTERSECTED.currentHex);
}
else{
material.color.setHex(INTERSECTED.currentHex);
}
}
INTERSECTED = intersects[0].object;
material = INTERSECTED.material;
if(material.emissive){
INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
material.emissive.setHex(0xff0000);
}
else{
INTERSECTED.currentHex = material.color.getHex();
material.color.setHex(0xff0000);
}
console.log(INTERSECTED.position);
}
}
else {
if (INTERSECTED){
material = INTERSECTED.material;
if(material.emissive){
material.emissive.setHex(INTERSECTED.currentHex);
}
else
{
material.color.setHex(INTERSECTED.currentHex);
}
}
INTERSECTED = null;
}
renderer.render( scene, camera );
}
</script>
</body>
</html>
I don't think the problem is the array, its the object in the array. Try this:
var materialArray = [];
materialArray.push(new THREE.MeshBasicMaterial({
map: dynamicTexture2.texture,
color:Math.random() * 0xffffff,
name: "box1",
}));
//...
var object = new THREE.Mesh( geometry, materialArray[0] );
If you want to create multiple cubes with different materials, you'll need to create multiple objects, instead of reusing object. You can wrap all the object creation/positioning code in a function, and have the unique material be a parameter.

Change vertice position on mousehover

I have a PlaneGeometry and I want to modify the z position of the vertice hovered but I don't know how to retrieve it.
//THREE.WebGLRenderer 69
// Generating plane
var geometryPlane = new THREE.PlaneGeometry( 100, 100, 20, 10 );
for (var vertIndex = 0; vertIndex < geometryPlane.vertices.length; vertIndex++) {
geometryPlane.vertices[vertIndex].z += Math.random();
}
geometryPlane.dynamic = true;
geometryPlane.computeFaceNormals();
geometryPlane.normalsNeedUpdate = true;
var materialPlane = new THREE.MeshLambertMaterial( {
color: 0xffff00,
side: THREE.DoubleSide,
shading: THREE.FlatShading,
overdraw: 0.5,
vertexColors: THREE.FaceColors
} );
plane = new THREE.Mesh( geometryPlane, materialPlane );
plane.geometry.colorsNeedUpdate = true;
// Mouse event
container[0].addEventListener( 'mousemove', onMouseMove, false );
function onMouseMove( event ) {
var mouseX = ( event.clientX / window.innerWidth ) * 2 - 1;
var mouseY = -( event.clientY / window.innerHeight ) * 2 + 1;
var vector = new THREE.Vector3( mouseX, mouseY, camera.near );
vector.unproject( camera );
raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
if ( intersects.length > 0 ) {
// Change the z position of the selected vertice
var selectedVertice = ???
selectedVertice.position.z +=5;
}
}
If the question concerns one vertex and not the whole object maybe you can :
1- retrieve the intersection face in intersects[0].face
2- this face may contain 3 vertices (it is probably a THREE.Face3) : find the nearest vertex V with the intersection point intersects[0].point.
3- change V.z
I don't know if it works : it is an idea... ;)
You can modify the Z position of the intersected object like this
intersects[0].object.position.z+=10;
You can refer to the code snippet below for demo/code.
var container, stats;
var camera, scene, raycaster, renderer;
var mouse = new THREE.Vector2(),
INTERSECTED;
var radius = 100,
theta = 0;
init();
animate();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
var info = document.createElement('div');
info.style.position = 'absolute';
info.style.top = '10px';
info.style.width = '100%';
info.style.textAlign = 'center';
info.innerHTML = 'three.js webgl - interactive cubes';
container.appendChild(info);
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000);
scene = new THREE.Scene();
var light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 1, 1).normalize();
scene.add(light);
var geometry = new THREE.PlaneGeometry (50, 50, 50);
for (var i = 0; i < 500; i++) {
var object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({
color: Math.random() * 0xffffff
}));
object.position.x = Math.random() * 800 - 400;
object.position.y = Math.random() * 800 - 400;
object.position.z = Math.random() * 800 - 400;
object.rotation.x = Math.random() * 2 * Math.PI;
object.rotation.y = Math.random() * 2 * Math.PI;
object.rotation.z = Math.random() * 2 * Math.PI;
object.scale.x = Math.random() + 0.5;
object.scale.y = Math.random() + 0.5;
object.scale.z = Math.random() + 0.5;
scene.add(object);
}
raycaster = new THREE.Raycaster();
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xf0f0f0);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.sortObjects = false;
container.appendChild(renderer.domElement);
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
container.appendChild(stats.domElement);
document.addEventListener('mousemove', onDocumentMouseMove, false);
//
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onDocumentMouseMove(event) {
event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
//
function animate() {
requestAnimationFrame(animate);
render();
stats.update();
}
function render() {
// find intersections
var vector = new THREE.Vector3(mouse.x, mouse.y, 1).unproject(camera);
raycaster.set(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
if (INTERSECTED != intersects[0].object) {
console.log(intersects[0].point);
console.log(intersects[0].object.position);
if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
/*******************************************************************/
/// You can change the Z position like the way done below
intersects[0].object.position.z+=10;
/********************************************************************/
INTERSECTED = intersects[0].object;
INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
INTERSECTED.material.emissive.setHex(0xff0000);
}
} else {
if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
INTERSECTED = null;
}
renderer.render(scene, camera);
}
<script src="http://threejs.org/examples/js/libs/stats.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r69/three.min.js"></script>

How do I add a tag/label to appear on top of several objects so that the tag always faces the camera when the user clicks the object?

Essentially, what I am saying is I want to create a tag or label that appears on top of/on the surface of an object so that the tag always faces the camera when the user clicks the object even when the object is rotated.
How do I go about doing so?
I was told to use Orthogonal camera (but I'm not sure how?) and CSS for the label (see previous post: How can I make my text labels face the camera at all times? Perhaps using sprites?)
The label in CSS and html is below. However, I want to do this for several objects as well, so I guess I can make a list of all the tags I want for each cube in this case.
CSS:
label {
vertical-align: middle;
display: table-cell;
background-color : #99FFCC;
border: 1px solid #008000;
width: 150px;
}
HTML:
<div id="Cube1">
<label>Cube 1</label>
</div>
Previous Code:
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js canvas - interactive - cubes</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
font-family: Monospace;
background-color: #f0f0f0;
margin: 0px;
overflow: hidden;
}
</style>
</head>
<body>
<script src="js/three.min.js"></script>
<script src="js/stats.min.js"></script>
<script>
var container, stats;
var camera, scene, projector, renderer;
var projector, mouse = { x: 0, y: 0 }, INTERSECTED;
var particleMaterial;
var currentLabel = null;
var objects = [];
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
var info = document.createElement( 'div' );
info.style.position = 'absolute';
info.style.top = '10px';
info.style.width = '100%';
info.style.textAlign = 'center';
info.innerHTML = 'three.js - clickable objects';
container.appendChild( info );
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set( 0, 300, 500 );
scene = new THREE.Scene();
var geometry = new THREE.CubeGeometry( 100, 100, 100 );
for ( var i = 0; i < 10; i ++ ) {
var object = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff, opacity: 0.5 } ) );
object.position.x = Math.random() * 800 - 400;
object.position.y = Math.random() * 800 - 400;
object.position.z = Math.random() * 800 - 400;
object.scale.x = Math.random() * 2 + 1;
object.scale.y = Math.random() * 2 + 1;
object.scale.z = Math.random() * 2 + 1;
object.rotation.x = Math.random() * 2 * Math.PI;
object.rotation.y = Math.random() * 2 * Math.PI;
object.rotation.z = Math.random() * 2 * Math.PI;
object.label = "Object " + i;
scene.add( object );
objects.push( object );
}
var PI2 = Math.PI * 2;
particleMaterial = new THREE.ParticleCanvasMaterial( {
color: 0x000000,
program: function ( context ) {
context.beginPath();
context.arc( 0, 0, 1, 0, PI2, true );
context.closePath();
context.fill();
}
} );
projector = new THREE.Projector();
renderer = new THREE.CanvasRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
container.appendChild( stats.domElement );
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
//
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function onDocumentMouseDown( event ) {
event.preventDefault();
var vector = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
projector.unprojectVector( vector, camera );
var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
var intersects = raycaster.intersectObjects( objects );
if ( intersects.length > 0 ) {
if ( intersects[ 0 ].object != INTERSECTED )
{
// restore previous intersection object (if it exists) to its original color
if ( INTERSECTED ) {
INTERSECTED.material.color.setHex( INTERSECTED.currentHex ); }
// store reference to closest object as current intersection object
INTERSECTED = intersects[ 0 ].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 );
var canvas1 = document.createElement('canvas');
var context1 = canvas1.getContext('2d');
context1.font = "Bold 40px Arial";
context1.fillStyle = "rgba(255,0,0,0.95)";
context1.fillText(INTERSECTED.label, 0, 50);
// canvas contents will be used for a texture
var texture1 = new THREE.Texture(canvas1)
texture1.needsUpdate = true;
var material1 = new THREE.MeshBasicMaterial( {map: texture1, side:THREE.DoubleSide } );
material1.transparent = true;
var mesh1 = new THREE.Mesh(
new THREE.PlaneGeometry(canvas1.width, canvas1.height),
material1
);
mesh1.position = intersects[0].point;
if (currentLabel)
scene.remove(currentLabel);
scene.add( mesh1 );
currentLabel = mesh1;
}
else // there are no intersections
{
// restore previous intersection object (if it exists) to its original color
if ( INTERSECTED ) {
console.log("hello");
INTERSECTED.material.color.setHex( INTERSECTED.currentHex );
}
// remove previous intersection object reference
// by setting current intersection object to "nothing"
INTERSECTED = null;
mesh1 = null;
mesh1.position = intersects[0].point;
scene.add( mesh1 );
}
//var particle = new THREE.Particle( particleMaterial );
//particle.position = intersects[ 0 ].point;
//particle.scale.x = particle.scale.y = 8;
//scene.add( particle );
}
/*
// Parse all the faces
for ( var i in intersects ) {
intersects[ i ].face.material[ 0 ].color.setHex( Math.random() * 0xffffff | 0x80000000 );
}
*/
}
//
function animate() {
requestAnimationFrame( animate );
render();
stats.update();
}
var radius = 600;
var theta = 0;
function render() {
theta += 0.1;
camera.position.x = radius * Math.sin( THREE.Math.degToRad( theta ) );
camera.position.y = radius * Math.sin( THREE.Math.degToRad( theta ) );
camera.position.z = radius * Math.cos( THREE.Math.degToRad( theta ) );
camera.lookAt( scene.position );
renderer.render( scene, camera );
}
</script>
</body>
Please let me know if I'm being unclear.
I'm not especially familiar with Three.js, but here's the usual steps:
Choose a point on the surface of your object you want to align the label to.
Use projector.projectVector to get an on-screen point from the in-world point.
(You may need to scale the result from NDC (-1 to 1) to canvas (0 to canvas.width) coordinates here; I'm not sure.)
Use the X and Y to set CSS absolute positioning for your label.
Here's code I wrote to do the same thing in my Cubes project (not using Three.js, but the principles are the same). It is slightly more complex because what it does is position the element so that it is next to an object represented by a set of points (which are provided to the callback passed to pointGenerator). It also tries to do sensible things when the object is out of view of the camera.
Feel free to reuse this code and adapt it to your liking.
// Position an overlay HTML element adjacent to the provided set of points.
function positionByWorld(element, keepInBounds, pointGenerator) {
var canvasStyle = window.getComputedStyle(theCanvas,null);
var canvasWidth = parseInt(canvasStyle.width, 10);
var canvasHeight = parseInt(canvasStyle.height, 10);
var elemStyle = window.getComputedStyle(element, null);
var elemWidth = parseInt(elemStyle.width, 10);
var elemHeight = parseInt(elemStyle.height, 10);
var slx = Infinity;
var sly = Infinity;
var shx = -Infinity;
var shy = -Infinity;
var toScreenPoint = vec4.create();
pointGenerator(function (x, y, z, w) {
toScreenPoint[0] = x;
toScreenPoint[1] = y;
toScreenPoint[2] = z;
toScreenPoint[3] = w;
renderer.transformPoint(toScreenPoint);
toScreenPoint[0] /= toScreenPoint[3];
toScreenPoint[1] /= toScreenPoint[3];
toScreenPoint[2] /= toScreenPoint[3];
if (toScreenPoint[3] > 0) {
slx = Math.min(slx, toScreenPoint[0]);
shx = Math.max(shx, toScreenPoint[0]);
sly = Math.min(sly, toScreenPoint[1]);
shy = Math.max(shy, toScreenPoint[1]);
}
});
if (shx > -1 && shy > -1 && slx < 1 && sly < 1 /* visible */) {
// convert to screen
slx = (slx + 1) / 2 * canvasWidth;
//shx = (shx + 1) / 2 * canvasWidth;
//sly = (sly + 1) / 2 * canvasHeight;
shy = (shy + 1) / 2 * canvasHeight;
if (keepInBounds) {
slx = Math.max(0, Math.min(canvasWidth - elemWidth, slx));
shy = Math.max(0, Math.min(canvasHeight - elemHeight, shy));
}
element.style.left = slx + "px";
element.style.bottom = shy + "px";
} else {
element.style.left = canvasWidth + "px";
}
}
—Permalink on GitHub

Categories