My overall aim is to be able to load an .obj file which is a human body. Allow the user to select two vertices and highlight them with flags. Then find the index of the two vertices from the original .obj file and run a php script to measure the distance between the two vertices.
I have tried a number of approches but had no luck, normally around selecting the two vertices. My current approach used the obj loader which works fine however I can't find out what vertex I am clicking on using Projector and Ray. It always returns an empty array.
Here is my code so far, once the intersects array isn't empty I try to find the nearest vertex from the file, change the color of the object and change the face which was clicked on.
<!doctype html>
<html lang="en">
<head>
<title>three.js webgl - loaders - OBJ loader</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: white;
color: #fff;
margin: 0px;
overflow: hidden;
}
#info {
color: #fff;
position: absolute;
top: 10px;
width: 100%;
text-align: center;
z-index: 100;
display:block;
}
#info a, .button { color: #f00; font-weight: bold; text-decoration: underline; cursor: pointer }
</style>
</head>
<body>
<script src="javascripts/Three.js"></script>
<script src="javascripts/OBJLoader.js"></script>
<script>
var container, stats;
var camera, scene, renderer, model;
var mouseX = 0, mouseY = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 2000 );
camera.position.z = 1;
scene.add( camera );
camera.position.y = -4;
var ambient = new THREE.AmbientLight( 0x101030 );
scene.add( ambient );
var directionalLight = new THREE.DirectionalLight( 0xffeedd );
directionalLight.position.set( 0, 0, 1 ).normalize();
scene.add( directionalLight );
var loader = new THREE.OBJLoader();
loader.load( "img/originalMeanModel.obj", function ( object ) {
model = object;
scene.add( model );
} );
// RENDERER
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
}
function onDocumentMouseDown( event ){
console.log('Morphable-body-obj: Width '+ window.innerWidth ) ;
console.log('Morphable-body-obj: Height '+ window.innerHeight) ;
var vector = new THREE.Vector3 ((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight)*2+1, 0.5);
var projector = new THREE.Projector();
projector.unprojectVector(vector, camera);
var ray = new THREE.Ray(camera.position, vector.subSelf(camera.position).normalize());
var intersects = ray.intersectObject(scene);
console.log(intersects);
if (intersects.length > 0)
{
var xhr = new XMLHttpRequest();
xhr.open('GET', '/img/originalMeanModel.obj', false);
xhr.send(null);
var text = xhr.responseText;
var origText = text;
var lines = text.split("\n");
for (i=0; i<6449; i++){
lines[i] = lines[i].split(" ");
}
var low = Math.sqrt(
(Math.pow((intersects[0].point.x - parseFloat(lines[0][1])), 2))+
(Math.pow((intersects[0].point.y - parseFloat(lines[0][2])), 2))+
(Math.pow((intersects[0].point.z - parseFloat(lines[0][3])), 2))
);
var c = 0;
for(i=1; i<6449; i++){
var temp = Math.sqrt(
(Math.pow((intersects[0].point.x - parseFloat(lines[i][1])), 2))+
(Math.pow((intersects[0].point.y - parseFloat(lines[i][2])), 2))+
(Math.pow((intersects[0].point.z - parseFloat(lines[i][3])), 2))
);
if(temp < low){
low = temp;
c=i;
}
}
console.log(
'Mouse coordinates:' + '\nx = ' + intersects[0].point.x + '\ny = ' + intersects[0].point.y + '\nz = ' + intersects[0].point.z +'\n'+
'Nearest Vertex' + '\nx= ' + lines[c][1] + '\ny= ' + lines[c][2] + '\nz =' + lines[c][3] + "\n" +
'Difference' + '\nx= ' + (intersects[0].point.x - lines[c][1]) + '\ny= ' + (intersects[0].point.y - lines[c][2]) + '\nz= ' + (intersects[0].point.z - lines[c][3])
);
intersects[0].object.materials[0].color = new THREE.Color( Math.random() * 0xffffff );
intersects[0].face.color = new THREE.Color(0xffffff);
intersects[0].object.geometry.colorsNeedUpdate = true;
intersects[0].object.geometry.dynamic = true;
}
else{
alert('error');
}
}
function onDocumentMouseMove( event ) {
mouseX = ( event.clientX - windowHalfX ) / 2;
mouseY = ( event.clientY - windowHalfY ) / 2;
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
camera.lookAt( scene.position );
renderer.render( scene, camera );
}
</script>
</body>
The .obj file in question can be found here https://dl.dropbox.com/u/23384412/originalMeanModel.obj
If anyone can point me in the right direction it would be really appreciated.
Thanks in advance! :)
To fix this I created a mesh from the object returned by the obj loader using this code
var loader = new THREE.OBJLoader();
loader.load( "img/originalMeanModel.obj", function ( object ) {
object.children[0].geometry.computeFaceNormals();
var geometry = object.children[0].geometry;
console.log(geometry);
THREE.GeometryUtils.center(geometry);
var material = new THREE.MeshLambertMaterial({color: 0xffffff, shading: THREE.FlatShading, vertexColors: THREE.VertexColors });
mesh = new THREE.Mesh(geometry, material);
model = mesh;
// model = object;
scene.add( model );
} );
Then when performing the intersectObject I did it on the model not the scene
var intersects = ray.intersectObject(model);
Related
For a little project, I would like to add different maps to an OBJ object in a Three.js 3D scene to get a photorealistic metallic effect. Unfortunately, I have some problems with it.
Directly embedding the code here in a working way doesn't work. So I created this as template:
https://codepen.io/Anna_B/pen/NWroEMP
The material should look like here, if you add under THREE.MeshStandardMaterial the envMaps, map, and roughnessMap.
I have tried to write it like this:
import * as THREE from "https://threejs.org/build/three.module.js";
import {
OBJLoader
} from "https://threejs.org/examples/jsm/loaders/OBJLoader.js";
var container;
var camera, scene, renderer;
var mouseX = 0,
mouseY = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
const textureLoader = new TextureLoader();
const envMaps = (function() {
const path = '../../examples/textures/cube/SwedishRoyalCastle/';
const format = '.jpg';
const urls = [
path + 'px' + format, path + 'nx' + format,
path + 'py' + format, path + 'ny' + format,
path + 'pz' + format, path + 'nz' + format
];
const reflectionCube = cubeTextureLoader.load(urls);
reflectionCube.format = RGBFormat;
const refractionCube = cubeTextureLoader.load(urls);
refractionCube.mapping = CubeRefractionMapping;
refractionCube.format = RGBFormat;
return {
none: null,
reflection: reflectionCube,
refraction: refractionCube
};
})();
const roughnessMaps = (function() {
const bricks = textureLoader.load('../../examples/textures/brick_roughness.jpg');
bricks.wrapT = RepeatWrapping;
bricks.wrapS = RepeatWrapping;
bricks.repeat.set(9, 1);
return {
none: null,
bricks: bricks
};
})();
var object;
init();
animate();
function init() {
container = document.createElement("div");
container.className = "object";
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
1,
2000
);
camera.position.z = 250;
// scene
scene = new THREE.Scene();
var ambientLight = new THREE.AmbientLight(0xcccccc, 0.4);
scene.add(ambientLight);
var pointLight = new THREE.PointLight(0xffffff, 2);
pointLight.position.set(100, 100, 50);
camera.add(pointLight);
scene.add(camera);
// manager
function loadModel() {
object.traverse(function(child) {
//This allow us to check if the children is an instance of the Mesh constructor
if (child instanceof THREE.Mesh) {
child.material = new THREE.MeshStandardMaterial({
color: "#555",
roughness: 0.1,
metalness: 0.4,
texture: textureLoader,
envMap: envMaps,
roughnessMaps: roughnessMap
});
child.material.flatShading = false;
//Sometimes there are some vertex normals missing in the .obj files, ThreeJs will compute them
}
});
object.position.y = -90;
scene.add(object);
}
var manager = new THREE.LoadingManager(loadModel);
manager.onProgress = function(item, loaded, total) {
console.log(item, loaded, total);
};
// model
function onProgress(xhr) {
if (xhr.lengthComputable) {
var percentComplete = (xhr.loaded / xhr.total) * 100;
console.log("model " + Math.round(percentComplete, 2) + "% downloaded");
}
}
function onError() {}
var loader = new OBJLoader(manager);
loader.load(
"https://threejs.org/examples/models/obj/female02/female02.obj",
function(obj) {
object = obj;
},
onProgress,
onError
);
//
renderer = new THREE.WebGLRenderer({
alpha: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
document.addEventListener("mousemove", onDocumentMouseMove, false);
//
window.addEventListener("resize", onWindowResize, false);
}
function onWindowResize() {
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onDocumentMouseMove(event) {
mouseX = (event.clientX - windowHalfX) / 2;
mouseY = (event.clientY - windowHalfY) / 2;
}
//
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
camera.position.x += (mouseX - camera.position.x) * 0.05;
camera.position.y += (-mouseY - camera.position.y) * 0.05;
camera.lookAt(scene.position);
renderer.render(scene, camera);
}
I think something is completely wrong. It would be sooooo nice if somebody could help me! I would be so thankful!!
If you're looking for a ThreeJS tool to experiment with metallic effects, try...
https://threejs.org/examples/webgl_materials_displacementmap.html
...which includes a set of controls to easily adjust critical mesh material parameters, and immediately see the affects. After using this tool, in your specific example, I came up with the following parameters for your mesh material, giving the object a very photo realistic metallic effect, and changing the color of the object to "#88f" for a bluish tint...
THREE.MeshStandardMaterial({
color: "#88f",
metalness: 1,
roughness: 0.37,
aoMapIntensity: 1.0,
ambientIntensity: 0.42,
envMapIntensity: 2.2,
displacementScale: 2.1,
normalScale: 1
});
Obviously you'll have to experiment with the aforementioned tool to obtain the specific metallic effect you desire. Hopefully this assists your efforts...
EDIT The environment map feature depends on environment images that are eventually reflected or refracted onto the object. The codepen example appears to be missing this critical element (ie, the THREE.CubeTextureLoader()). Based on the code in the question, it appears that it was originally adapted from https://threejs.org/examples/#webgl_materials_cubemap, which I believe has the necessary elements of the desired environment map concept. Using that ThreeJS example as a baseline, and injecting the female02.obj model into the scene, we arrive at...
<!DOCTYPE html>
<html lang="en">
<head>
<title>Adapted from "three.js webgl - materials - cube reflection / refraction [Walt]"</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>
<div id="container"></div>
<div id="info">
three.js - Adapted from "cube mapping demo."<br />
Texture by Humus
</div>
<script type="module">
import * as THREE from 'https://threejs.org/build/three.module.js';
import { OrbitControls } from 'https://threejs.org/examples/jsm/controls/OrbitControls.js';
import { OBJLoader } from 'https://threejs.org/examples/jsm/loaders/OBJLoader.js';
let container;
let camera, scene, renderer;
let pointLight;
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 5000 );
camera.position.z = 2000;
//cubemap
const path = 'https://threejs.org/examples/textures/cube/SwedishRoyalCastle/';
const format = '.jpg';
const urls = [
path + 'px' + format, path + 'nx' + format,
path + 'py' + format, path + 'ny' + format,
path + 'pz' + format, path + 'nz' + format
];
const reflectionCube = new THREE.CubeTextureLoader().load( urls );
const refractionCube = new THREE.CubeTextureLoader().load( urls );
refractionCube.mapping = THREE.CubeRefractionMapping;
scene = new THREE.Scene();
scene.background = reflectionCube;
//lights
const ambient = new THREE.AmbientLight( 0xffffff );
scene.add( ambient );
pointLight = new THREE.PointLight( 0xffffff, 2 );
scene.add( pointLight );
//materials
const cubeMaterial3 = new THREE.MeshLambertMaterial( { color: 0xff6600, envMap: reflectionCube, combine: THREE.MixOperation, reflectivity: 0.3 } );
const cubeMaterial2 = new THREE.MeshLambertMaterial( { color: 0xffee00, envMap: refractionCube, refractionRatio: 0.95 } );
const cubeMaterial1 = new THREE.MeshLambertMaterial( { color: 0xffffff, envMap: reflectionCube } );
//models
const objLoader = new OBJLoader();
objLoader.setPath( 'https://threejs.org/examples/models/obj/female02/' );
objLoader.load( 'female02.obj', function ( object ) {
object.scale.multiplyScalar( 10 );
object.position.y = - 1200;
object.children.forEach( mesh => { mesh.material = cubeMaterial1 } );
scene.add( object );
let object2 = object.clone();
object2.position.x = -900;
object2.children.forEach( mesh => { mesh.material = cubeMaterial2 } );
scene.add( object2 );
let object3 = object.clone();
object3.position.x = +900;
object3.children.forEach( mesh => { mesh.material = cubeMaterial3 } );
scene.add( object3 );
} );
//renderer
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
//controls
const controls = new OrbitControls( camera, renderer.domElement );
controls.enableZoom = false;
controls.enablePan = false;
controls.minPolarAngle = Math.PI / 4;
controls.maxPolarAngle = Math.PI / 1.5;
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
renderer.render( scene, camera );
}
</script>
</body>
</html>
Note that the female02.obj is composed of over a dozen meshes, so the object has to be cloned() and then the mesh material set for each of the individual meshes.
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.
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
I'm using the OBJLoader to load a large 3D model (described in a .obj file) but it loads the whole file as a single Object3D object. Using scene.add(object) it adds the whole object to the scene.
I need to pick the selected mesh and change some of its properties, but when I add mouse function and use Ray.intersectObjects try to get the selected mesh it never works. I can not find where I made mistakes.
Would love some help with trying to get this working. Thanks!
It confused me for couples of days. The following is all my code:
<!doctype html>
<html lang="en">
<head>
<title>three.js webgl - loaders - OBJ loader</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: #000;
color: #fff;
margin: 0px;
overflow: hidden;
}
#info {
color: #fff;
position: absolute;
top: 10px;
width: 100%;
text-align: center;
z-index: 100;
display:block;
}
#info a, .button { color: #f00; font-weight: bold; text-decoration: underline; cursor: pointer }
</style>
</head>
<body>
<div id="info">
three.js - OBJLoader test
</div>
<script src="../build/Three.js"></script>
<script src="js/loaders/OBJLoader.js"></script>
<script src="js/Detector.js"></script>
<script src="js/Stats.js"></script>
<script>
var container, stats;
var camera, scene, renderer;
var mouseX = 0, mouseY = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
var _mouse = { x: 0, y: 0 },
objects = [],
_projector = new THREE.Projector();
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 );
camera.position.z = 100;
scene.add( camera );
var ambient = new THREE.AmbientLight( 0x101030 );
scene.add( ambient );
var directionalLight = new THREE.DirectionalLight( 0xffeedd );
directionalLight.position.set( 0, 0, 1 ).normalize();
scene.add( directionalLight );
var texture = THREE.ImageUtils.loadTexture( 'textures/ash_uvgrid01.jpg' );
var loader = new THREE.OBJLoader();
loader.load( "obj/male02/male02.obj", function ( object ) {
for ( var i = 0, l = object.children.length; i < l; i ++ ) {
object.children[ i ].material.map = texture;
}
object.position.y = - 80;
object.position.z = - 160;
scene.add( object );
objects.push( object );
} );
// RENDERER
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
}
function onDocumentMouseDown( event ) {
event.preventDefault();
// find intersections
_mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
_mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
var vector = new THREE.Vector3( _mouse.x, _mouse.y, 1 );
var ray = _projector.pickingRay( vector, camera );
var intersects = ray.intersectObjects( scene.children );
if ( intersects.length > 0 ) {
alert("selected!");
_SELECTED_DOWN = true;
}
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
renderer.render( scene, camera );
}
</script>
</body>
</html>
ray.intersectObjects() is not recursive. You need to pass a list of the objects you want to test.
I have a cube in ThreeJS and I would like to rotate it 90 degrees clockwise every time I press a button. I think I have the basic gist of it: create a Three.Animation instance, bind it to the cube, and then have the animation begin every time I press the correct button. However, I'm having a difficult time understanding ThreeJS's API, because it doesn't seem to contain any examples for its methods.
This is THREE.js's Animation constructor: ( root, data, interpolationType, JITCompile ) I don't understand what goes into the fields. I'm guessing root would be where I put my cube, but what about the rest?
Also can I just call animation.play() to cause the animation whenever I want? And how does the animationHandler work?
I think for for rotating an object 90 degrees clockwise, using the TWEEN class will do. I think the Animation class is handy for heavier stuff (like bones/skin morphs/etc.)
To use the tween class there are 3 basic steps:
include the class in your file (<script src="js/Tween.js"></script>)
add your tween for the event you need (new TWEEN.Tween( cube.rotation ).to( { y:Math.random()}, 1000 ).easing( TWEEN.Easing.Quadratic.EaseOut).start();)
update the tween in your render loop (TWEEN.update();)
You can have a have a look at the cubes tween example for a start.
I've modified the default cube example to have the tween in:
<!doctype html>
<html lang="en">
<head>
<title>three.js canvas - geometry - cube</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="../build/Three.js"></script>
<script src="js/Tween.js"></script>
<script src="js/RequestAnimationFrame.js"></script>
<script src="js/Stats.js"></script>
<script>
var container, stats;
var camera, scene, renderer;
var cube, plane;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
var rad90 = Math.PI * .5;
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 = 'click to tween';
container.appendChild( info );
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.y = 150;
camera.position.z = 500;
scene = new THREE.Scene();
// Cube
var materials = [];
for ( var i = 0; i < 6; i ++ ) {
materials.push( [ new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff } ) ] );
}
cube = new THREE.Mesh( new THREE.CubeGeometry( 200, 200, 200, 1, 1, 1, materials ), new THREE.MeshFaceMaterial() );
cube.position.y = 150;
cube.overdraw = true;
scene.add( cube );
// Plane
plane = new THREE.Mesh( new THREE.PlaneGeometry( 200, 200 ), new THREE.MeshBasicMaterial( { color: 0xe0e0e0 } ) );
plane.rotation.x = - 90 * ( Math.PI / 180 );
plane.overdraw = true;
scene.add( plane );
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 );
}
//
function onDocumentMouseDown( event ) {
event.preventDefault();
new TWEEN.Tween( cube.rotation ).to( { y: cube.rotation.y + rad90}, 1000 ).easing( TWEEN.Easing.Quadratic.EaseOut).start();
new TWEEN.Tween( plane.rotation ).to( { z: plane.rotation.z + rad90}, 1000 ).easing( TWEEN.Easing.Quadratic.EaseOut).start();
console.log("click");
}
//
function animate() {
requestAnimationFrame( animate );
render();
stats.update();
}
function render() {
TWEEN.update();
renderer.render( scene, camera );
}
</script>
</body>
</html>