three.js - select objects, get object material - javascript

I am trying to visualize 3d model of a car with three.js, and I have couple of issues(my code is below):
1)
The 3d model is made in Maya 2016, and then exported as .obj and a .mtl files. I want the parts of the car to be selectable( i mean when u select it, it highlights in green color, when you click again on selected part, it goes back to normal color.
I've already done that, but the problem is that the raycast selects all the objects in the path of the ray, i mean when I select door, everything behind it gets selected and I want to select the door only.
2)
The second problem is that the renderer doesn't visualize transparent materials for ex. the windows and headlights. And I've seen some 3d car visualizers that can display transparent materials.
3)
The third issue goes back to number 1. When I select a part, i want to store it's previous material color(before I apply the highlight color), so when I de-select it, it could bring back the normal color, I've put the color of the car paint, which is just wrong because when i de-select windows they go red.
So, here is my index.html code:
<code><html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=0.1">
<script src="JavaScript/three.js"></script>
<script src="JavaScript/Detector.js"></script>
<script src="JavaScript/OrbitControls.js"></script>
<script src="JavaScript/OBJLoader.js"></script>
<script src="JavaScript/MTLLoader.js"></script>
<script src="JavaScript/Projector.js"></script>
<link rel="stylesheet" type="text/css" href="css/site.css">
</head>
<body>
<div class="left">
<p>Low-Poly Croupière<p>
<p>manu.ninja</p>
</div>
<div id="test" class="left" hidden="true">
<p>xaxaxaxaxaaxaaxx<p>
</div>
<a class="right" href="https://github.com/Lorti/webgl-3d-model-viewer-using-three.js" target="_top">
<img src="https://camo.githubusercontent.com/652c5b9acfaddf3a9c326fa6bde407b87f7be0f4/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6f72616e67655f6666373630302e706e67">
</a>
<script>
if (!Detector.webgl) {
Detector.addGetWebGLMessage();
}
var container;
var camera, controls, scene, renderer;
var lighting, ambient, keyLight, fillLight, backLight;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
var objects = [];
init();
animate();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
/* Camera */
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 140;
/* Scene */
scene = new THREE.Scene();
lighting = true;
ambient = new THREE.AmbientLight(0xefefff, 1.0);
scene.add(ambient);
keyLight = new THREE.DirectionalLight(new THREE.Color('hsl(60, 100%, 75%)'), 3.0);
keyLight.position.set(-100, 0, 100);
fillLight = new THREE.DirectionalLight(new THREE.Color('hsl(540, 100%, 75%)'), 1.75);
fillLight.position.set(100, 0, 100);
backLight = new THREE.DirectionalLight(0xe3ffef, 1.0);
backLight.position.set(100, 0, -100).normalize();
/* Model */
var mtlLoader = new THREE.MTLLoader();
mtlLoader.setBaseUrl('assets/');
mtlLoader.setPath('assets/');
mtlLoader.load('e46_red_5.mtl', function (materials) {
materials.preload();
//materials.materials.default.map.magFilter = THREE.NearestFilter;
// materials.materials.default.map.minFilter = THREE.LinearFilter;
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials(materials);
objLoader.setPath('assets/');
objLoader.load('e46_red_5.obj', function (object) {
scene.add(object);
objects.push(object);
});
});
/* Renderer */
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(new THREE.Color("hsl(0, 0%, 10%)"));
container.appendChild(renderer.domElement);
/* Controls */
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.25;
controls.enableZoom = false;
/* Events */
window.addEventListener('resize', onWindowResize, false);
window.addEventListener('keydown', onKeyboardEvent, false);
//window.addEventListener( 'mousemove', onMouseMove, 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 onKeyboardEvent(e) {
if (e.code === 'KeyL') {
lighting = !lighting;
if (lighting) {
ambient.intensity = 0.25;
scene.add(keyLight);
scene.add(fillLight);
scene.add(backLight);
} else {
ambient.intensity = 1.0;
scene.remove(keyLight);
scene.remove(fillLight);
scene.remove(backLight);
}
}
}
function animate() {
requestAnimationFrame(animate);
controls.update();
render();
}
function render() {
renderer.render(scene, camera);
}
var already_selected = false;
function onDocumentMouseDown( event ) {
event.preventDefault();
var projector = new THREE.Projector();
mouseVector = new THREE.Vector3();
mouseVector.x = 2 * (event.clientX / window.innerWidth) - 1;
mouseVector.y = 1 - 2 * ( event.clientY / window.innerHeight );
//var mouse3D = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1,
// -( event.clientY / window.innerHeight ) * 2 + 1,
// 0.5 );
//var raycaster = projector.pickingRay( mouseVector.clone(), camera );
var raycaster = new THREE.Raycaster();
raycaster.setFromCamera( mouseVector, camera );
var intersects = raycaster.intersectObjects( objects[0].children );
console.log(intersects)
if ( intersects.length > 0 ) {
var door = document.getElementById("test");
door.hidden =false;
for (var i = 0; i < intersects.length; i++) {
//intersects[i].object.material.color.setHex( Math.random() * 0xffffff );
//var currentColor = new THREE.Color(intersects[i].object.material.color);
var currentColor = +'0x' + intersects[i].object.material.color.getHex().toString( 16 );
if (already_selected == true) {
intersects[i].object.material.color.setHex( 0x380000 );
//intersects[i].object.material.color.setHex( currentColor );
already_selected = false;
}
else {
intersects[i].object.material.color.setHex( 0xccffcc );
already_selected = true;
}
}
}
}
</script>
</body>
</html></code>

1) For your raycaster issue, in your intersects array, you would only want to access intersects[0] since that is the first object that the raycast intersects. I do not see the point in going through the whole array if you don't want to select items behind the first one
2) In order to see transparency, You have to set
(object here).material.transparent = true;
I had to do this in order to make see through objects when you click on them, but I also had to set opacity. Assuming your object has an opacity assigned already, then it would need only the transparency flag, otherwise you can also set
(object here).material.opacity = some number;
3) What I have done when I want to 'highlight' a material is I actually change intersects[0].object.material.emissive.set('#(insert color)');
Then when the mouse moves of the object, you reset the emissive to #000000.
Emissive is better because you're not actually changing the color, just an outward appearance.

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: Raycast Has an Empty Array of Intersects

I am using this example for my WebGL panorama cube: https://threejs.org/examples/?q=pano#webgl_panorama_equirectangular
I want to know what cube user clicks on and I discovered I can use Raycaster for this. According to docs I added the following function:
function onMouseDown( event ) {
event.preventDefault();
var mouseVector = new THREE.Vector3(
( event.clientX / window.innerWidth ) * 2 - 1,
- ( event.clientY / window.innerHeight ) * 2 + 1,
1 );
//projector.unprojectVector( mouseVector, camera );
mouseVector.unproject( camera );
var raycaster = new THREE.Raycaster( camera.position, mouseVector.sub( camera.position ).normalize() );
// create an array containing all objects in the scene with which the ray intersects
var intersects = raycaster.intersectObjects( scene.children );
console.log(intersects);
if (intersects.length>0){
console.log("Intersected object:", intersects.length);
intersects[ 0 ].object.material.color.setHex( Math.random() * 0xffffff );
}
// ...
But intersects is always empty. My scene is defined as
scene = new THREE.Scene();
and has skyBox added:
var skyBox = new THREE.Mesh( new THREE.CubeGeometry( 1, 1, 1 ), materials );
skyBox.applyMatrix( new THREE.Matrix4().makeScale( 1, 1, - 1 ) );
scene.add( skyBox );
I've seen similar posts related to this issue but could not figure out how to apply to this example. Any directions are appreciated.
Try adding this to your material definition:
var materials = new THREE.SomeMaterial({
/* other settings */,
side: THREE.DoubleSide
});
Raycaster won't intersect back-faces unless the side property is set to THREE.BackSide or THREE.DoubleSide. Even though your scaling technically inverts the face direction, the vertex order stays the same, which is what's important to Raycaster.
Some further explanation
The snippet below is showing how a ray projected from a camera at the center of a skybox inverted by a -Z scale might look.
The box itself looks weird because it has been -Z scaled, and the normals no longer match the material. But that's here nor there.
The green arrow represents the original ray. The red arrow represents what will happen to that ray inside the Mesh.raycast function, which will apply the inverse of the object's world matrix to the ray, but not to the object's geometry. This is a whole different problem.
The point I'm making is that within Mesh.raycast, it does not affect the vertex/index order, so when it checks the triangles of the mesh, they are still in their original order. For a standard BoxGeometry/BoxBufferGeometry, this means the faces all face outward from the geometric origin.
This means the rays (regardless of how the transformation matrix affects them) are still trying to intersect the back-face of those triangles, which will not work unless the material is set to THREE.DoubleSide. (It can also be set to THREE.BackSide, but the -Z scale will ruin that.)
Clicking either of the raycast buttons will produce 0 intersects if the -Z scaled box is not set to THREE.DoubleSide (default). Click the "Set THREE.DoubleSide" button and try it again--it will now intersect.
var renderer, scene, camera, controls, stats;
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight,
FOV = 35,
NEAR = 1,
FAR = 1000,
ray1, ray2, mesh;
function populateScene(){
var cubeGeo = new THREE.BoxBufferGeometry(10, 10, 10),
cubeMat = new THREE.MeshPhongMaterial({ color: "red", transparent: true, opacity: 0.5 });
mesh = new THREE.Mesh(cubeGeo, cubeMat);
mesh.applyMatrix( new THREE.Matrix4().makeScale( 1, 1, -1 ) );
mesh.updateMatrixWorld(true);
scene.add(mesh);
var dir = new THREE.Vector3(0.5, 0.5, 1);
dir.normalize();
ray1 = new THREE.Ray(new THREE.Vector3(), dir);
var arrow1 = new THREE.ArrowHelper(ray1.direction, ray1.origin, 20, 0x00ff00);
scene.add(arrow1);
var inverseMatrix = new THREE.Matrix4();
inverseMatrix.getInverse(mesh.matrixWorld);
ray2 = ray1.clone();
ray2.applyMatrix4(inverseMatrix);
var arrow2 = new THREE.ArrowHelper(ray2.direction, ray2.origin, 20, 0xff0000);
scene.add(arrow2);
}
function init() {
document.body.style.backgroundColor = "slateGray";
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
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 = 50;
scene.add(camera);
controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.dynamicDampingFactor = 0.5;
controls.rotateSpeed = 3;
var light = new THREE.PointLight(0xffffff, 1, Infinity);
camera.add(light);
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0';
document.body.appendChild(stats.domElement);
resize();
window.onresize = resize;
populateScene();
animate();
var rayCaster = new THREE.Raycaster();
document.getElementById("greenCast").addEventListener("click", function(){
rayCaster.ray.copy(ray1);
alert(rayCaster.intersectObject(mesh).length + " intersections!");
});
document.getElementById("redCast").addEventListener("click", function(){
rayCaster.ray.copy(ray2);
alert(rayCaster.intersectObject(mesh).length + " intersections!");
});
document.getElementById("setSide").addEventListener("click", function(){
mesh.material.side = THREE.DoubleSide;
mesh.material.needsUpdate = true;
});
}
function resize() {
WIDTH = window.innerWidth;
HEIGHT = window.innerHeight;
if (renderer && camera && controls) {
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
controls.handleResize();
}
}
function render() {
renderer.render(scene, camera);
}
function animate() {
requestAnimationFrame(animate);
render();
controls.update();
stats.update();
}
function threeReady() {
init();
}
(function () {
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("https://threejs.org/build/three.js", function () {
addScript("https://threejs.org/examples/js/controls/TrackballControls.js", function () {
addScript("https://threejs.org/examples/js/libs/stats.min.js", function () {
threeReady();
})
})
})
})();
body{
text-align: center;
}
<input id="greenCast" type="button" value="Cast Green">
<input id="redCast" type="button" value="Cast Red">
<input id="setSide" type="button" value="Set THREE.DoubleSide">
You might actually want to use an easier process to determine your ray from the camera:
THREE.Raycaster.prototype.setFromCamera( Vector2, Camera );
Simply define your mouse coordinates as you do in a Vector2, then pass the elements to the Raycaster and let it do its thing. It hides the complexity of intersecting the frustrum with the ray from the camera, and should solve your problem.
(Also, the raycaster does indeed only intersect faces that face the ray directly, but since your SkyBox has been inverted its geometries faces are pointing to the inside of the box, so they should intersect if the camera is inside the box. Another possibility is that your box is further away than the raycasters default far value.)
function onMouseDown( event ) {
event.preventDefault();
var mouseVector = new THREE.Vector2(
event.clientX / window.innerWidth * 2 - 1,
-event.clientY / window.innerHeight * 2 + 1
);
var raycaster = new THREE.Raycaster;
raycaster.setFromCamera( mouseVector, camera );
var intersects = raycaster.intersectObjects( scene.children );
console.log(intersects);
if( intersects.length > 0 ){
console.log( "Intersected object:", intersects[ 0 ] );
intersects[ 0 ].object.material.color.setHex( Math.random() * 0xffffff );
}
}

When mouseover (hover) on object the mouse cursor should change ( three.js)

I have added a sphere and plane geometry to the scene when clicked on plane geometry it is linked to a website
now when hover on plane geometry the "mouse cursor" should change to "mouse pointer (hand)" and when not hovered
on plane geometry the mouse should retain its original style.
I tried using this statement "$('html,body').css('cursor','pointer');" but mouse cursor is not changing on
hovering, its changing when clicked on plane geometry and its cursor is not retaining to its original position.
can someone please help me how to solve the problem. I have also uploaded the code.
<html>
<head>
<body>
<script type="text/javascript" src="jquery-1.11.3.js"></script>
<script src ="./three.js-master/build/three.js"></script>
<script src ="./three.js-master/examples/js/controls/OrbitControls.js">
</script>
<script src ="./three.js-master/examples/js/renderers/Projector.js">
</script>
<script type="text/javascript" src="math.min.js"></script>
<script type="text/javascript">
window.onload = createsphere();
function createsphere()
{
var controls,scene,camera,renderer;
var planes = [];
var baseVector = new THREE.Vector3(0, 0, 1);
var camDir = new THREE.Vector3();
var planeLookAt = new THREE.Vector3();
function init()
{
var spriteResponse = [];
spriteResponse[0] = {ID:1, x: 0, y: 0};
spriteResponse[1] = {ID:2, x: 0, y: 0.1};
spriteResponse[2] = {ID:3, x: 0, y: 0.5};
spriteResponse[3] = {ID:4, x: 0.5, y: 0};
spriteResponse[4] = {ID:5, x: 0.25, y: 0.5 };
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
//camera.position.y = 1;
camera.position.z = 1 ;
var width = window.innerWidth;
var height = window.innerHeight;
renderer = new THREE.WebGLRenderer( {antialias:true} );
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);
/* ------------------------ creating the geometry of sphere------------------------------*/
var radius = 2.5;
var spheregeometry = new THREE.SphereGeometry(radius, 20, 20, 0, -6.283, 1, 1);
//var texture = THREE.ImageUtils.loadTexture ('rbi00000083.jpg');
//texture.minFilter = THREE.NearestFilter;
//var spherematerial = new THREE.MeshBasicMaterial({map: texture});
var spherematerial = new THREE.MeshBasicMaterial({color: '#A9A9A9'});
var sphere = new THREE.Mesh(spheregeometry, spherematerial);
scene.add(sphere);
scene.add(camera);
scene.autoUpdate = true;
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.minPolarAngle = Math.PI/4;
controls.maxPolarAngle = 3*Math.PI/4;
for(var i=0; i<spriteResponse.length;i++)
{
//var spriteAlignment = new THREE.Vector2(0,0) ;
material_plane = new THREE.MeshBasicMaterial( {color: 0xffffff,side: THREE.DoubleSide } );
material_plane.needsUpdate = true;
//material.transparent=true;
geometry_plane = new THREE.PlaneGeometry(0.3, 0.2);
plane = new THREE.Mesh( geometry_plane, material_plane );
plane.database_id = spriteResponse[i].ID;
plane.LabelText = spriteResponse[i].name;
plane.position.set(spriteResponse[i].x,spriteResponse[i].y,-1);
scene.add(plane);
//plane.userData = { keepMe: true };
planes.push(plane);
//plane.id = cardinal.ID;
//var direction = camera.getWorldDirection();
camera.updateMatrixWorld();
var vector = camera.position.clone();
vector.applyMatrix3( camera.matrixWorld );
plane.lookAt(vector);
plane.userData = { URL: "http://stackoverflow.com"};
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
}
function onDocumentMouseDown( event )
{
//clearScene();
event.preventDefault();
var mouse = new THREE.Vector2();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
var raycaster = new THREE.Raycaster();
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( planes );
var matched_marker = null;
if(intersects.length != 0)
{
$('html,body').css('cursor','pointer');//mouse cursor change
for ( var i = 0; intersects.length > 0 && i < intersects.length; i++)
{
window.open(intersects[0].object.userData.URL);
}
}
else
$('html,body').css('cursor','cursor');//mouse cursor change
}//onDocumentMouseDown( event )
}
function animate()
{
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
init();
animate();
}
</script>
</body>
</head>
</html>
There are a number of ways to do it, but to keep it simple and make it easier for you to understand, my example includes a method that keeps with the format of the code you provided in your question.
I added a mousemove event to your init() function. The handler looks like this:
function onDocumentMouseMove(event) {
var mouse = new THREE.Vector2();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
var raycaster = new THREE.Raycaster();
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( planes );
if(intersects.length > 0) {
$('html,body').css('cursor', 'pointer');
} else {
$('html,body').css('cursor', 'default');
}
}
All this does is check whether or not one of your planes is intersected each time you move the mouse.
The reason this wasn't working before is because you only changed the cursor on mouse-down which won't give the desired 'hover' effect.
Here's a working fiddle. Just note that I've commented out any controls related code to get the fiddle working quicker, it won't change the solution.
You can't change hover state within JS as stated here:
https://stackoverflow.com/a/11371599/5001964
I think easiest solution would be to make it with css:
body:hover {
cursor: pointer;
}
Although it would be better if instead body you choose a specific DOM node to make the hover effect.

Embed 3D model viewer

I'm trying to implement this 3D model viewer, however I want to embed it into an already set div instead of making a new one as this does. So I've edited the code like so but it hasn't worked. Any help would be appreciated.
<script>
// This is where our model viewer code goes.
var container;
var camera, scene, renderer;
var mouseX = 0, mouseY = 0;
var windowHalfX = document.getElementById('viewer').clientHeight / 2;
var windowHalfY = document.getElementById('viewer').clientHeight / 2;
init();
animate();
// Initialize
function init() {
// This <div> will host the canvas for our scene.
container = document.getElementById( 'viewer' );
//document.body.appendChild( container );
// You can adjust the cameras distance and set the FOV to something
// different than 45°. The last two values set the clippling plane.
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 );
camera.position.z = 100;
// These variables set the camera behaviour and sensitivity.
controls = new THREE.TrackballControls( camera );
controls.rotateSpeed = 5.0;
controls.zoomSpeed = 5;
controls.panSpeed = 2;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
// This is the scene we will add all objects to.
scene = new THREE.Scene();
// You can set the color of the ambient light to any value.
// I have chose a completely white light because I want to paint
// all the shading into my texture. You propably want something darker.
var ambient = new THREE.AmbientLight( 0xffffff );
scene.add( ambient );
// Uncomment these lines to create a simple directional light.
// var directionalLight = new THREE.DirectionalLight( 0xffeedd );
// directionalLight.position.set( 0, 0, 1 ).normalize();
// scene.add( directionalLight );
// Texture Loading
var manager = new THREE.LoadingManager();
manager.onProgress = function ( item, loaded, total ) {
console.log( item, loaded, total );
};
var texture = new THREE.Texture();
var loader = new THREE.ImageLoader( manager );
// You can set the texture properties in this function.
// The string has to be the path to your texture file.
loader.load( 'img/sickletexture.png', function ( image ) {
texture.image = image;
texture.needsUpdate = true;
// I wanted a nearest neighbour filtering for my low-poly character,
// so that every pixel is crips and sharp. You can delete this lines
// if have a larger texture and want a smooth linear filter.
texture.magFilter = THREE.NearestFilter;
texture.minFilter = THREE.NearestMipMapLinearFilter;
} );
// OBJ Loading
var loader = new THREE.OBJLoader( manager );
// As soon as the OBJ has been loaded this function looks for a mesh
// inside the data and applies the texture to it.
loader.load( 'obj/sickle.obj', function ( event ) {
var object = event;
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.material.map = texture;
}
} );
// My initial model was too small, so I scaled it upwards.
object.scale = new THREE.Vector3( 2, 2, 2 );
// You can change the position of the object, so that it is not
// centered in the view and leaves some space for overlay text.
object.position.y -= 2.5;
scene.add( object );
});
// We set the renderer to the size of the window and
// append a canvas to our HTML page.
renderer = new THREE.WebGLRenderer();
renderer.setSize( document.getElementById('viewer').innerWidth, document.getElementById('viewer').innerHeight );
container.appendChild( renderer.domElement );
}
// The Loop
function animate() {
// This function calls itself on every frame. You can for example change
// the objects rotation on every call to create a turntable animation.
requestAnimationFrame( animate );
// On every frame we need to calculate the new camera position
// and have it look exactly at the center of our scene.
controls.update();
camera.lookAt(scene.position);
renderer.render(scene, camera);
}
</script>
I'm trying to get things to work myself and this code works for me with the latest version (66) of three. Its a little different to you example as I am using a vrml model rather than an obj and I handle the material differently. But it does run fine.
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - loaders - vrml 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>
threewindow {
border: 1px solid black;
}
</style>
<script src="../three.js/build/three.min.js"></script>
<script src="../three.js/examples/js/controls/TrackballControls.js"></script>
<script src="../three.js/examples/js/loaders/VRMLLoader.js"></script>
<script src="../three.js/examples/js/Detector.js"></script>
<script src="../three.js/examples/js/libs/stats.min.js"></script>
<script>
if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
var container, stats;
var camera, controls, scene, renderer;
var cross;
function init() {
alert("init");
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.01, 1e10 );
camera.position.z = 6;
controls = new THREE.TrackballControls( camera );
controls.rotateSpeed = 5.0;
controls.zoomSpeed = 5;
controls.panSpeed = 2;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
scene = new THREE.Scene();
scene.add( camera );
var sphereMaterial =
new THREE.MeshLambertMaterial(
{
color: 0xCC0000
});
// light
var dirLight = new THREE.DirectionalLight( 0xffffff );
dirLight.position.set( 200, 200, 1000 ).normalize();
camera.add( dirLight );
camera.add( dirLight.target );
var loader = new THREE.VRMLLoader();
loader.addEventListener( 'load', function ( event ) {
var object = event.content;
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
//child.material.map = texture;
//child.material = sphereMaterial;
child.material.side = THREE.DoubleSide;
}
} );
scene.add(object);
} );
// loader.load( "models/vrml/house.wrl" );
loader.load( "cayley.wrl" );
// renderer
renderer = new THREE.WebGLRenderer( { antialias: false } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setSize(200, 200);
document.getElementById("threewindow").appendChild(renderer.domElement);
// container = document.createElement( 'div' );
// document.body.appendChild( container );
// container.appendChild( renderer.domElement );
// stats = new Stats();
// stats.domElement.style.position = 'absolute';
// stats.domElement.style.top = '0px';
// container.appendChild( stats.domElement );
window.addEventListener( 'resize', onWindowResize, false );
animate();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
controls.handleResize();
}
function animate() {
requestAnimationFrame( animate );
controls.update();
renderer.render( scene, camera );
//stats.update();
}
</script>
</head>
<body onload="init()">
<h1>Cubic surfaces</h1>
<p>All the surfaces defined by cubics equations.</p>
<ul><li>Singularities of cubic surfaces.</li>
<li>A pictorial introduction to singularity theory.</li>
</ul>
<div id="threewindow"></div>
</body>
</html>
I found a rather easy solution, I'm surprised I did not find it earlier.
Create the 3D in a seperate html document (using the original script, not the edited one in the OP), then in the div <embed src="3d.html"></embed>

Orthographic camera and selecting objects with raycast

I am running into a bit of difficulty selecting objects with the orthographic camera using the raycaster. Though, I have no problem with it when I use a perspective camera. The only thing I am changing when switching between the two is the type camera.
I am able to select faces on the orthographic view, but it is only loosely related to where I am clicking on the screen. When I can click far away from the object and it will still come back as if it has hit the object near its center.
Any ideas on what I am missing here?
I am basing much of my code on this example, and am hoping to achieve a very similar result from my code. (this example I'm referencing uses the perspective camera)
Any help is much appreciated
<html>
<head>
<style>
canvas {
left: 0;
top: 0;
width: 100%;
height: 100%;
position: fixed;
background-color: #111115;
}
</style>
</head>
<body id='c'>
<script src="js/three.js"></script>
<script>
var obj = [];
var mouse ={};
var zoom = 2;
var scene = new THREE.Scene();
//switch between these two and see the difference:
//var camera = new THREE.OrthographicCamera(window.innerWidth / -zoom, window.innerWidth / zoom, window.innerHeight / zoom, window.innerHeight / -zoom, -1000, 1000);
var camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position = new THREE.Vector3(100,100,100);
camera.lookAt(new THREE.Vector3(0,0,0));
// this material causes a mesh to use colors assigned to faces
var material = new THREE.MeshBasicMaterial(
{ color: 0xffffff, vertexColors: THREE.FaceColors } );
var sphereGeometry = new THREE.SphereGeometry( 80, 32, 16 );
for ( var i = 0; i < sphereGeometry.faces.length; i++ )
{
face = sphereGeometry.faces[ i ];
face.color.setRGB( 0, 0, 0.8 * Math.random() + 0.2 );
}
obj['box'] = {};
obj['box'] = new THREE.Mesh( sphereGeometry, material );
obj['box'].castShadow = true;
obj['box'].receiveShadow = true;
scene.add(obj['box']);
var ambientLight = new THREE.AmbientLight(0xbbbbbb);
scene.add(ambientLight);
var directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(-100, 40, 100);
directionalLight.castShadow = true;
directionalLight.shadowOnly = true;
directionalLight.shadowDarkness = .5;
scene.add(directionalLight);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMapEnabled = true;
renderer.shadowMapSoft = true;
document.body.appendChild(renderer.domElement);
projector = new THREE.Projector();
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
function onDocumentMouseDown( event ) {
// the following line would stop any other event handler from firing
// (such as the mouse's TrackballControls)
// event.preventDefault();
console.log("Click.");
// update the mouse variable
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
// find intersections
// create a Ray with origin at the mouse position
// and direction into the scene (camera direction)
var vector = new THREE.Vector3( mouse.x, mouse.y, 1 );
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( [obj['box']] );
// if there is one (or more) intersections
if ( intersects.length > 0 )
{
console.log("Hit # " + toString( intersects[0].point ) );
console.log(intersects);
// change the color of the closest face.
intersects[ 0 ].face.color.setRGB( 0.8 * Math.random() + 0.2, 0, 0 );
intersects[ 0 ].object.geometry.colorsNeedUpdate = true;
}
}
function toString(v) { return "[ " + v.x + ", " + v.y + ", " + v.z + " ]"; }
var render = function() {
requestAnimationFrame(render);
renderer.render(scene, camera);
};
console.log(camera);
console.log(obj['box'])
render();
</script>
</body>
I am hoping it is something simple that I just don't know about yet.
three.js r60
Here is the pattern to use when raycasting with either an orthographic camera or perspective camera:
var raycaster = new THREE.Raycaster(); // create once
var mouse = new THREE.Vector2(); // create once
...
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, recursiveFlag );
three.js r.84
One more note that might save you some trouble. If you have a camera like this:
var camera = new THREE.OrthographicCamera(0, window.innerWidth, -window.innerHeight, 0, -100, 100);
Then during raycasting, be sure to move the ray origin.z to camera.far for it to hit anything in the entire visible range:
this.ray.origin.set(0, 0, 0);
this.camera.localToWorld(this.ray.origin);
this.ray.setFromCamera(this.mouseCoord, this.camera);
this.ray.origin.z = this.camera.far;

Categories