Three.js scene gets hanged and becomes slow - javascript

The threejs scene consists of sphere and plane geometry, The sphere is textured with image and plane geometry
is textured with 2d text, and plane geometry is attached with click event, When I click on plane geometry
with the mouse I need to remove the previous sphere and plane geometry and load new sphere with new textured
image and new plane geometry which is happening, but the previous sphere and plane geometry are still remaining in memory and i need
to remove those objects, i tried using "dispose" method but that didn't help me may be i am making some mistake
to implement the dispose method,because of this the scene gets hanged, can someone please help me how to solve
this problem. I have added part of my code which might give an idea regarding the problem.https://jsfiddle.net/v1ayw803/
var spheregeometry = new THREE.SphereGeometry(radius, 20, 20, 0, -6.283, 1, 1);
var texture = THREE.ImageUtils.loadTexture(response.ImagePath);
texture.minFilter = THREE.NearestFilter;
var spherematerial = new THREE.MeshBasicMaterial({map: texture});
var sphere = new THREE.Mesh(spheregeometry, spherematerial);
//texture.needsUpdate = true;
scene.add(sphere);
var objects = [];
var objects_sphere = [];
objects_sphere.push(sphere);
for(var i=0; i<spriteResponse.length; i++)
{
var cardinal = {ID: parseInt(spriteResponse[i].ID), lat: parseFloat(spriteResponse[i].lat), lon: parseFloat(spriteResponse[i].lng), name: spriteResponse[i].name};
//var sprite = new labelBox(cardinal, radius, root);
//sprite.update(); was previously commented
//spritearray.push(sprite);
var phi = Math.log( Math.tan( cardinal.lat*(Math.PI/180) / 2 + Math.PI / 4 ) / Math.tan( click_marker.getPosition().lat()* (Math.PI/180) / 2 + Math.PI / 4) );
var delta_lon = Math.abs( click_marker.getPosition().lng() - cardinal.lon )*Math.PI/180;
var bearing = Math.atan2( delta_lon , phi ) ;
var Z_value = Math.cos(bearing)*(radius*0.75);
var X_value = Math.sin(bearing)*(radius*0.75);
var canvas = document.createElement('canvas');
context = canvas.getContext('2d');
metrics = null,
textHeight = 32,
textWidth = 0,
// actualFontSize = 2;
context.font = "normal " + textHeight + "px Arial";
metrics = context.measureText(cardinal.name);
var textWidth = metrics.width;
//var textHeight = metrics.height;
canvas.width = textWidth;
canvas.height = textHeight;
context.font = "normal " + textHeight + "px Arial";
context.textAlign = "center";
context.textBaseline = "middle";
context.beginPath();
context.rect(0, 0, textWidth, textHeight);
context.fillStyle = "white";
context.fill();
context.fillStyle = "black";
context.fillText(cardinal.name, textWidth / 2, textHeight / 2);
texture_plane = new THREE.Texture(canvas);
var GPU_Value = renderer.getMaxAnisotropy();
texture_plane.anisotropy = GPU_Value;
texture_plane.needsUpdate = true;
//var spriteAlignment = new THREE.Vector2(0,0) ;
material = new THREE.MeshBasicMaterial( {color: 0xffffff,side: THREE.DoubleSide ,map : texture_plane} );
material.needsUpdate = true;
//material.transparent=true;
geometry = new THREE.PlaneGeometry(0.3, 0.2);
plane = new THREE.Mesh( geometry, material );
plane.database_id = cardinal.ID;
plane.LabelText = cardinal.name;
//plane.scale.set( 0.3, 0.3,1 );
plane.scale.set( textWidth/165, textHeight/70, 1 );
plane.position.set(X_value,0,Z_value);
plane.coordinates = { X: X_value, Z: Z_value};
plane.lat_lon = { LAT: cardinal.lat, LON: cardinal.lon};
plane.textWidth = textWidth;
plane.textHeight = textHeight;
objects.push( plane );
scene.add(plane);
plane.userData = { keepMe: true };
//objects.push( plane );
//plane.id = cardinal.ID;
//var direction = camera.getWorldDirection();
camera.updateMatrixWorld();
var vector = camera.position.clone();
vector.applyMatrix3( camera.matrixWorld );
plane.lookAt(vector);
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( objects );
var matched_marker = null;
if(intersects.length != 0)
{
for ( var i = 0; intersects.length > 0 && i < intersects.length; i++)
{
var x_id = intersects[0].object.database_id;
for( var j = 0; markers.length > 0 && j < markers.length; j++)
{
if(x_id == markers[j].ID)
{
matched_marker = markers[j];
break;
}
}
if(matched_marker != null)
{
break;
}
}
// loadScene();
clean_data();
google.maps.event.trigger( matched_marker, 'click' );
}
}
function clean_data()
{
for(var k=0;k<objects_sphere.length;k++)
{
scene.remove( objects_sphere[k] );
objects_sphere[k].geometry.dispose();
objects_sphere[k].material.map.dispose();
objects_sphere[k].material.dispose();
}
for (var j=0; j<objects.length; j++)
{
scene.remove( objects[j] );
objects[j].geometry.dispose();
objects[j].material.map.dispose();
objects[j].material.dispose();
// objects[j].material.needsUpdate = true;
}
/*spheregeometry.dispose();
spherematerial.dispose();
texture.dispose();
scene.remove( sphere );*/
} `

It looks like you're never rerendering the scene in either the example code or the jsFiddle. If you remove your object from the scene and the objects remain, it's likely that you've not rendered the scene again. Try adding a render loop.
animationLoop () {
myrenderer.render(myScene, myCamera)
window.requestAnimationFrame(animationLoop)
}

Related

Mapping image (Frontside-Backside) onto a sphere in three js

I have a question regarding the Three JS.
Fiddle https://jsfiddle.net/syildiz/fk8thLsq/17/
I want to creat an eye that follows the mouse movements. The images i upload are visible in the background at the same time and there is a white line at the joining of the image.
In the example, I have added pictures I want to use in the front and back. I want to use the image at the front, in the back and the image (or color) at the back, in the front. I also want to get rid of the white line, how can i do this?
Frontside Image https://image.ibb.co/mmsJ7J/logo_front.png
Backside Image https://image.ibb.co/bE8i7J/logo_back.png (or color)
I am still fairly new with Three JS, i would appreciate if you could help me on this matter.
You have to assign two materials to the object, one for the front and one for the back. Then you need to set the materialIndex of each face, so the faces know which material to use.
// use material array to apply multiple materials
var material = [
new THREE.MeshBasicMaterial({
map: new THREE.TextureLoader().load( "https://image.ibb.co/mmsJ7J/logo_front.png")
}),
new THREE.MeshBasicMaterial({
map: new THREE.TextureLoader().load( "https://image.ibb.co/bE8i7J/logo_back.png")
})
];
// in your faces loop, set materialIndex according to front or back side of geometry
if (v1.z < 0)
faces[i].materialIndex = 1;
// after loop, set groupsNeedUpdate -> "Set to true if a face3 materialIndex has been updated."
geometry.groupsNeedUpdate = true;
Regarding the white line: this is because you are at the very edge of the texture (respectively the opaque edge of the circle). I added some offset, so the texture will slightly overlap.
var max = geometry.boundingBox.max.clone().add(new THREE.Vector3(1,1,1)),
min = geometry.boundingBox.min.clone().add(new THREE.Vector3(-1,-1,-1));
// If the radius of the sphere is smaller, you should also set a smaller offset.
// in this example it's 1:60, which is ok.
Here's a snippet (or have a look at your updated jsFiddle: https://jsfiddle.net/fk8thLsq/36/)
//Setup:
var container = document.querySelector('#container');
var renderer = new THREE.WebGLRenderer({alpha: true});
var WIDTH = window.innerWidth;
var HEIGHT = window.innerHeight;
renderer.setSize(WIDTH, HEIGHT);
var VIEW_ANGLE = 40;
var ASPECT = WIDTH / HEIGHT;
var NEAR = 0.1;
var FAR = 1000;
var camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
camera.position.set(0, 0, 50);
var scene = new THREE.Scene();
scene.background = null;
scene.add(camera);
container.appendChild(renderer.domElement);
var RADIUS = 200;
var SEGMENTS = 50;
var RINGS = 50;
var group = new THREE.Group();
scene.add(group);
var material = [
new THREE.MeshBasicMaterial({
map: new THREE.TextureLoader().load( "https://image.ibb.co/mmsJ7J/logo_front.png" ),
overdraw: 0
}),
new THREE.MeshBasicMaterial({
map: new THREE.TextureLoader().load( "https://image.ibb.co/bE8i7J/logo_back.png" ),
overdraw: 0
})
];
scene.add(new THREE.AmbientLight(0xffffff, 0.2));
var light = new THREE.PointLight(0xffffff, 0.2);
camera.add(light);
var geometry = new THREE.SphereGeometry(60, 64, 32);
geometry.computeBoundingBox();
var max = geometry.boundingBox.max.clone().add(new THREE.Vector3(1,1,1)),
min = geometry.boundingBox.min.clone().add(new THREE.Vector3(-1,-1,-1));
var offset = new THREE.Vector2(0 - min.x, 0 - min.y);
var range = new THREE.Vector2(max.x - min.x, max.y - min.y);
var faces = geometry.faces;
geometry.faceVertexUvs[0] = [];
for (var i = 0; i < faces.length; i++) {
var v1 = geometry.vertices[faces[i].a],
v2 = geometry.vertices[faces[i].b],
v3 = geometry.vertices[faces[i].c];
if (v1.z < 0)
faces[i].materialIndex = 1;
geometry.faceVertexUvs[0].push([
new THREE.Vector2((v1.x + offset.x) / range.x, (v1.y + offset.y) / range.y),
new THREE.Vector2((v2.x + offset.x) / range.x, (v2.y + offset.y) / range.y),
new THREE.Vector2((v3.x + offset.x) / range.x, (v3.y + offset.y) / range.y)
]);
}
geometry.groupsNeedUpdate = true;
geometry.uvsNeedUpdate = true;
var mesh = new THREE.Mesh(geometry, material);
group.add(mesh);
group.position.z = -270;
var pointLight = new THREE.PointLight(0xFFFFFF);
pointLight.position.x = -100;
pointLight.position.y = 0;
pointLight.position.z = 200;
scene.add(pointLight);
function update() {
renderer.render(scene, camera);
requestAnimationFrame(update);
}
requestAnimationFrame(update);
function animationBuilder(direction) {
return function animateRotate() {
switch (direction) {
case 'up':
group.rotation.x -= 0.2;
break;
case 'down':
group.rotation.x += 0.2;
break;
case 'left':
group.rotation.y -= 0.2;
break;
case 'right':
group.rotation.y += 0.2;
break;
default:
break;
}
};
}
var animateDirection = {
up: animationBuilder('up'),
down: animationBuilder('down'),
left: animationBuilder('left'),
right: animationBuilder('right')
};
function checkKey(e) {
e = e || window.event;
e.preventDefault();
if (e.keyCode == '38') {
animateDirection.up();
} else if (e.keyCode == '40') {
animateDirection.down();
} else if (e.keyCode == '37') {
animateDirection.left();
} else if (e.keyCode == '39') {
animateDirection.right();
}
}
document.onkeydown = checkKey;
var lastMove = [window.innerWidth / 2, window.innerHeight / 2];
function rotateOnMouseMove(e) {
e = e || window.event;
var moveX = e.clientX - lastMove[0];
var moveY = e.clientY - lastMove[1];
group.rotation.y += moveX * .004;
group.rotation.x += moveY * .004;
lastMove[0] = e.clientX;
lastMove[1] = e.clientY;
}
document.addEventListener('mousemove', rotateOnMouseMove);
body{margin:0;background:#ddd;}
.logo{position:absolute;top:10px;width:100px;height:100px;background-size:100% auto;background-repeat:no-repeat;}
.front{left:10px;background-image:url("https://image.ibb.co/mmsJ7J/logo_front.png")}
.back{left:120px;background-image:url("https://image.ibb.co/bE8i7J/logo_back.png")}
<div class="logo front"></div>
<div class="logo back"></div>
<div id="container" width="100vw" height="100vh"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/94/three.min.js"></script>
You will notice that there is still a seam between both materials. Maybe you should use a plain color for your textures and achieve the slightly specular behaviour by setting the material properties accordingly.

threejs, adding points to scene and not seeing them

Hello I have one doubt:
I have studied:
https://threejs.org/docs/#api/materials/PointsMaterial
And I have adapted the example to work with my existing code.
The aim is to render points on top of the model which we have loaded on click position.
Here we have the code, the important part is onDocumentMouseDown(), the file, logic.js:
if (!Detector.webgl) Detector.addGetWebGLMessage();
// global variables for this scripts
let OriginalImg,
SegmentImg;
var mouse = new THREE.Vector2();
var raycaster = new THREE.Raycaster();
var mousePressed = false;
var clickCount = 0;
init();
animate();
// initilize the page
function init() {
let filename = "models/nrrd/columna01.nrrd"; // change your nrrd file
let idDiv = 'original';
OriginalImg = new InitCanvas(idDiv, filename);
OriginalImg.init();
console.log(OriginalImg);
filename = "models/nrrd/columnasegmentado01.nrrd"; // change your nrrd file
idDiv = 'segment';
SegmentImg = new InitCanvas(idDiv, filename);
SegmentImg.init();
}
let originalCanvas = document.getElementById('original');
originalCanvas.addEventListener('mousedown', onDocumentMouseDown, false);
originalCanvas.addEventListener('mouseup', onDocumentMouseUp, false);
function onDocumentMouseDown(event) {
mousePressed = true;
clickCount++;
mouse.x = ( ( event.clientX - OriginalImg.renderer.domElement.offsetLeft ) / OriginalImg.renderer.domElement.clientWidth ) * 2 - 1;
mouse.y = -( ( event.clientY - OriginalImg.renderer.domElement.offsetTop ) / OriginalImg.renderer.domElement.clientHeight ) * 2 + 1
console.log('Mouse x position is: ', mouse.x, 'the click number was: ', clickCount);
console.log('Mouse Y position is: ', mouse.y);
raycaster.setFromCamera(mouse.clone(), OriginalImg.camera);
var objects = raycaster.intersectObjects(OriginalImg.scene.children);
var pointGeometry = new THREE.Geometry();
var position = new THREE.Vector3();
position.x = objects[0].point.x;
position.y = objects[0].point.y;
position.z = objects[0].point.z;
pointGeometry.vertices.push(position);
var pointMaterial = new THREE.PointsMaterial({color: 0x888888});
var point = new THREE.Points(pointGeometry, pointMaterial);
OriginalImg.scene.add(point);
console.log(objects);
}
function onDocumentMouseUp(event) {
mousePressed = false
}
function animate() {
requestAnimationFrame(animate);
OriginalImg.animate();
SegmentImg.animate();
}
And we do add the points to the scene, but in fact they do not render, they do not show, and I wonder why?:
As you could see in the image we see that the raycaster intercepts those new created points, however the do not get drawn.
I wonder if they are too small, or just the color hides them with the background.
Could you help me please?.
Additional code:
// this class handles the load and the canva for a nrrd
// Using programming based on prototype: https://javascript.info/class
// This class should be improved:
// - Canvas Width and height
InitCanvas = function (IdDiv, Filename) {
this.IdDiv = IdDiv;
this.Filename = Filename
}
InitCanvas.prototype = {
constructor: InitCanvas,
init: function () {
this.container = document.getElementById(this.IdDiv);
// this should be changed.
debugger;
this.container.innerHeight = 600;
this.container.innerWidth = 800;
//These statenments should be changed to improve the image position
this.camera = new THREE.PerspectiveCamera(60, this.container.innerWidth / this.container.innerHeight, 0.01, 1e10);
this.camera.position.z = 300;
let scene = new THREE.Scene();
scene.add(this.camera);
// light
let dirLight = new THREE.DirectionalLight(0xffffff);
dirLight.position.set(200, 200, 1000).normalize();
this.camera.add(dirLight);
this.camera.add(dirLight.target);
// read file
let loader = new THREE.NRRDLoader();
loader.load(this.Filename, function (volume) {
//z plane
let sliceZ = volume.extractSlice('z', Math.floor(volume.RASDimensions[2] / 4));
debugger;
this.container.innerWidth = sliceZ.iLength;
this.container.innerHeight = sliceZ.jLength;
sliceZ.mesh.material.color.setRGB(0,1,1);
console.log('Our slice is: ', sliceZ);
scene.add(sliceZ.mesh);
}.bind(this));
this.scene = scene;
// renderer
this.renderer = new THREE.WebGLRenderer({alpha: true});
this.renderer.setPixelRatio(this.container.devicePixelRatio);
debugger;
this.renderer.setSize(this.container.innerWidth, this.container.innerHeight);
// add canvas in container
this.container.appendChild(this.renderer.domElement);
},
animate: function () {
this.renderer.render(this.scene, this.camera);
}
}
I wonder about the point size because if we see this example they are made with 0.05 of size:
https://github.com/mrdoob/three.js/blob/master/examples/webgl_interactive_raycasting_points.html
And in the example we see the camera being quite far away from the points being generated and they are visible:
https://threejs.org/examples/webgl_interactive_raycasting_points.html
What do you think?
You can use THREE.BufferGeometry() with .setDrawRange():
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(1, 5, 5);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var mesh = new THREE.Mesh(new THREE.SphereBufferGeometry(3, 32, 24), new THREE.MeshBasicMaterial({
wireframe: true,
color: "red"
}));
scene.add(mesh);
var idx = 0;
var maxIdx = 10;
var points = [];
for (let i = 0; i < maxIdx; i++) {
points.push(new THREE.Vector3());
}
var geometry = new THREE.BufferGeometry().setFromPoints(points);
geometry.setDrawRange(0, idx);
var points = new THREE.Points(geometry, new THREE.PointsMaterial({
size: 0.125,
color: "yellow"
}));
scene.add(points);
window.addEventListener("mousemove", onMouseMove, false);
window.addEventListener("mousedown", onMouseDown, false);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var intersects = [];
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
function onMouseDown(event) {
raycaster.setFromCamera(mouse, camera);
intersects = raycaster.intersectObject(mesh);
if (intersects.length === 0) return;
if (idx == maxIdx) return;
let p = intersects[0].point;
geometry.attributes.position.setXYZ(idx, p.x, p.y, p.z);
geometry.attributes.position.needsUpdate = true;
idx++;
geometry.setDrawRange(0, idx);
}
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

How do I plot random meshes on top of a terrain using a heightmap in three.js?

So as the title states I'd like to know how to plot randomly generated meshes at the y-position that matches the terrain's corresponding y-position in three.js. I've looked through the docs and feel like using a raycaster might work, but I can only see examples that uses the detection as a mouse event, and not before render, so I'm not sure how to implement it.
Here is my code for the terrain, heightmap, and mesh plotting so far. It all works technically, but as you can see the plotAssets meshes y-positions are just sitting at zero right now. Any insights would be very much appreciated, I'm pretty new to three.js.
Terrain:
var heightmaploader = new THREE.ImageLoader();
heightmaploader.load(
"assets/noisemaps/cloud.png",
function(img) {
data = getHeightData(img);
var terrainG = new THREE.PlaneBufferGeometry(700, 700, worldWidth - 1, worldDepth - 1);
terrainG.rotateX(-Math.PI / 2);
var vertices = terrainG.attributes.position.array;
for (var i = 0, j = 0, l = vertices.length; i < l; i++, j += 3) {
vertices[j + 1] = data[i] * 5;
}
terrainG.computeFaceNormals();
terrainG.computeVertexNormals();
var material = new THREE.MeshLambertMaterial({
map: terrainT,
//side: THREE.DoubleSide,
color: 0xffffff,
transparent: false,
});
terrain = new THREE.Mesh(terrainG, material);
terrain.receiveShadow = true;
terrain.castShadow = true;
terrain.position.y = 0;
scene.add(terrain);
plotAsset('boulder-photo-01.png', 30, 18, data);
plotAsset('boulder-outline-01.png', 20, 20, data);
plotAsset('birch-outline-01.png', 10, 50, data);
plotAsset('tree-photo-01.png', 20, 50, data);
plotAsset('grass-outline-01.png', 10, 20, data);
plotAsset('grass-outline-02.png', 10, 20, data);
}
);
Plot Assets:
function plotAsset(texturefile, amount, size, array) {
console.log(array);
var loader = new THREE.TextureLoader();
loader.load(
"assets/textures/objects/" + texturefile,
function(texturefile) {
var geometry = new THREE.PlaneGeometry(size, size, 10, 1);
var material = new THREE.MeshBasicMaterial({
color: 0xFFFFFF,
map: texturefile,
side: THREE.DoubleSide,
transparent: true,
depthWrite: false,
depthTest: false,
alphaTest: 0.5,
});
var uniforms = { texture: { value: texturefile } };
var vertexShader = document.getElementById( 'vertexShaderDepth' ).textContent;
var fragmentShader = document.getElementById( 'fragmentShaderDepth' ).textContent;
// add bunch o' stuff
for (var i = 0; i < amount; i++) {
var scale = Math.random() * (1 - 0.8 + 1) + 0.8;
var object = new THREE.Mesh(geometry, material);
var x = Math.random() * 400 - 400 / 2;
var z = Math.random() * 400 - 400 / 2;
object.rotation.y = 180 * Math.PI / 180;
//object.position.y = size * scale / 2;
object.position.x = x;
object.position.z = z;
object.position.y = 0;
object.castShadow = true;
object.scale.x = scale; // random scale
object.scale.y = scale;
object.scale.z = scale;
scene.add(object);
object.customDepthMaterial = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: vertexShader,
fragmentShader: fragmentShader,
side: THREE.DoubleSide
} );
}
}
);
}
Height Data:
function getHeightData(img) {
var canvas = document.createElement('canvas');
canvas.width = 2048 / 8;
canvas.height = 2048 / 8;
var context = canvas.getContext('2d');
var size = 2048 / 8 * 2048 / 8,
data = new Float32Array(size);
context.drawImage(img, 0, 0);
for (var i = 0; i < size; i++) {
data[i] = 0
}
var imgd = context.getImageData(0, 0, 2048 / 8, 2048 / 8);
var pix = imgd.data;
var j = 0;
for (var i = 0, n = pix.length; i < n; i += (4)) {
var all = pix[i] + pix[i + 1] + pix[i + 2];
data[j++] = all / 40;
}
return data;
}
Yes, using of THREE.Raycaster() works well.
A raycaster has the .set(origin, direction) method. The only thing you have to do here is to set the point of origin higher than the highest point of the height map.
var n = new THREE.Mesh(...); // the object we want to aling along y-axis
var collider = new THREE.Raycaster();
var shiftY = new THREE.Vector3();
var colliderDir = new THREE.Vector3(0, -1, 0); // down along y-axis to the mesh of height map
shiftY.set(n.position.x, 100, n.position.z); // set the point of the origin
collider.set(shiftY, colliderDir); //set the ray of the raycaster
colliderIntersects = collider.intersectObject(plane); // plane is the mesh of height map
if (colliderIntersects.length > 0){
n.position.y = colliderIntersects[0].point.y; // set the position of the object
}
jsfiddle example

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.

JavaScript Double Click Function Three.js

I've got a double click function to allow the user to double click on a car model and it displays which objects have been intersected; e.g. wipers, grille, tyres and so on, and this function displays them in a list with the number of items the double click intersected with.
However, I am now trying to get it so that when a certain part of the car is clicked, for example, the tyres, it will display a paragraph with information on them. I can see how this is just a case of checking the name of the intersecting object and then displaying the relevant text if it intersects it, but every time I go to do what I think is right, it just breaks the already existing function to the point where the whole thing won't run.
I'm not exactly a JavaScript or Three.js pro at all, but trying to progress my function further is proving to be rather difficult.
Any suggestions? I've included the entire double click function, however it's when it's checking if there has been intersections near the bottom that is where the alterations need to be.
// On Mouse double click event function
function onDoubleClick(event) {
// Set the mouse down flag to false
mouseDown = false;
// Canvas x (left) and y (top) position
var canvasLeft = 0;
var canvasTop = 0;
// "event.clientX" is the mouse x position. "event.clientY" is the mouse y position
var tempX = event.clientX - canvasLeft;
var tempY = event.clientY - canvasTop;
// Create a normalised vector in 2d space
var vector = new THREE.Vector3((tempX / window.innerWidth) * 2 - 1, - (tempY / innerHeight) * 2 + 1, 0.5);
// Unproject a 2D point into the 3D word
// Use the camera projection matrix to transform the vector to the 3D world space
vector.unproject(camera);
// Send a ray in the direction the user has clicked from the cameras position
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
// Check if the ray has intersected with any objects and get intersections
var intersects = raycaster.intersectObjects(objects, true);
// Check if intersected with objects
if (intersects.length > 0) {
var tempStr = "Number of items: " + intersects.length + " ";
// List the items that were hit
for(var i=0; i < intersects.length; i++){
if(intersects[i].object.name != ""){
// The mesh name set above
tempStr += " | Name: " + intersects[i].object.name;
} else {
// The names inside the model
tempStr += " | Name: " + intersects[i].object.parent.name;
}
}
//Debug information
document.getElementById("debugInfo").innerHTML = tempStr + ".<br>";
//END
}
}
EDIT:
This is the entire code for the javascript file, as altering elements of the double click function seems to stop the page from loading.
window.onload = init;
// declare variables
var scene,camera,renderer, container;
var controls, guiControls, datGUI;
var grid, color;
var cube, cubeGeometry, cubeMaterial;
var plane, planeGeometry, planeMaterial;
var skyBoxMesh, texture_placeholder;
var spotLight;
var stats;
// Handles the mouse events.
var mouseOverCanvas;
var mouseDown;
// An array of objects that can be clicked on
var objects = [];
//DAE models
var showroom ,carOld, carNew;
var daeObject;
var animations;
var kfAnimations = [];
var kfAnimationsLength = 0;
var lastFrameCurrentTime = [];
var clock = new THREE.Clock();
var mouseOverCanvas, mouseDown;
var objectsClick=[];
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
//creates empty scene
scene = new THREE.Scene();
//camera
camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, .1, 500);
camera.position.x = 40;
camera.position.y = 40;
camera.position.z = 40;
camera.lookAt(scene.position);
//renderer
renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setClearColor(0xe6f2ff);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMapSoft = true;
container.appendChild( renderer.domElement );
// Add an event to set if the mouse is over our canvas
renderer.domElement.onmouseover=function(e){ mouseOverCanvas = true; }
renderer.domElement.onmousemove=function(e){ mouseOverCanvas = true; }
renderer.domElement.onmouseout=function(e){ mouseOverCanvas = false; }
renderer.domElement.onmousedown=function(e){ mouseDown = true; }
renderer.domElement.onmouseup=function(e){ mouseDown = false; }
// Double Click Event. Set a function called "onDoubleClick"
renderer.domElement.ondblclick=onDoubleClick;
// stats
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
container.appendChild( stats.domElement );
//adds controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.addEventListener('change', render);
var ambient = new THREE.AmbientLight( 0xadad85 );
scene.add( ambient );
//---------- creates grid ---------------
grid = new THREE.GridHelper(50,5);
color= new THREE.Color("rgb(255,0,0)");
grid.setColors( 0x000000);
scene.add(grid);
//----------- creates cube --------------
cubeGeometry = new THREE.BoxGeometry(5,5,5);
cubeMaterial = new THREE.MeshPhongMaterial({color: 0xff3300});
cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.x = 0;
cube.position.y = 6;
cube.position.z = 2.5;
cube.castShadow = true;
scene.add(cube);
//----------- creates plane ---------------
planeGeomenty= new THREE.PlaneGeometry(100,100,100);
planeMaterial = new THREE.MeshLambertMaterial({color: 0x00cc00});
plane = new THREE.Mesh(planeGeomenty, planeMaterial);
//position the add objects to the scene
plane.rotation.x = -.5*Math.PI;
plane.receiveShadow = true;
scene.add(plane);
//------------- skyBox --------------
texture_placeholder = document.createElement('canvas');
texture_placeholder.width = 128;
texture_placeholder.height = 128;
var context = texture_placeholder.getContext('2d');
context.fillStyle = 'rgb(200,200, 200)';
context.fillRect(0, 0,texture_placeholder.width, texture_placeholder.height);
var materials = [
loadTexture('images/skybox/posX.jpg'),
loadTexture('images/skybox/negX.jpg'),
loadTexture('images/skybox/posY.jpg'),
loadTexture('images/skybox/negY.jpg'),
loadTexture('images/skybox/posZ.jpg'),
loadTexture('images/skybox/negZ.jpg')
];
skyBoxMesh = new THREE.Mesh(new THREE.BoxGeometry(500,500,500,7,7,7),
new THREE.MeshFaceMaterial(materials));
skyBoxMesh.scale.x = -1;
scene.add(skyBoxMesh);
//---------- loads collada files -----------
loadCollada();
daeObject = cube;
// initialise datGUI controls values
guiControls = new function() {
this.rotationY = 0.0;
this.positionX = 0.0;
this.positionY = 0.0;
this.positionZ = -10;
this.lightX = 20;
this.lightY = 35;
this.lightZ = 40;
this.intensity = 1;
this.distance = 0;
this.angle = 1.570;
this.target = cube;
}
//add spotLight with starting parameters
spotLight = new THREE.SpotLight(0xffffff);
spotLight.castShadow = true;
spotLight.position.set(20,35,40);
spotLight.intensity = guiControls.intensity;
spotLight.distance = guiControls.distance;
spotLight.angle = guiControls.angle;
scene.add(spotLight);
//adds controls on the scene
datGUI = new dat.GUI();
// datGUI.add(guiControls, 'positionZ', 0, 1);
datGUI.add(guiControls, 'positionZ', -10, 25, 0.5). name("Move the car");
datGUI.add(guiControls, 'rotationY', 0, 1).name('Rotate the car');
datGUI.add(guiControls, 'lightX', -60, 180);
datGUI.add(guiControls, 'lightY', 0, 180);
datGUI.add(guiControls, 'lightZ', -60, 180);
datGUI.add(guiControls, 'target',[ 'cube','Modern Mini', 'Classic Mini']).onChange(function() {
if(guiControls.target == 'cube'){
spotLight.target = cube;
daeObject = cube;
}
else if(guiControls.target == 'Classic Mini'){
spotLight.target = carOld;
daeObject = carOld;
}
else if(guiControls.target = 'Modern Mini'){
spotLight.target = carNew;
daeObject = carNew;
}
});
datGUI.add(guiControls, 'intensity', 0.01, 5).onChange(function (value){
spotLight.intensity = value;
});
datGUI.add(guiControls, 'distance', 0, 1000).onChange(function (value){
spotLight.distance = value;
});
datGUI.add(guiControls, 'angle', 0.001, 1.570).onChange(function (value){
spotLight.angle = value;
});
datGUI.close();
container.appendChild(renderer.domElement);
window.addEventListener( 'resize', onWindowResize, false );
}
//------------------------- END INIT() ----------------------------
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function loadCollada() {
//--- Loads the Classic Mini ---
colladaLoader = new THREE.ColladaLoader();
colladaLoader.options.convertUpAxis = true;
colladaLoader.load( 'dae_files/ClassicMini.dae', function ( collada ) {
carOld = collada.scene; // stores dae file to a global variable
carOld.position.set( 14.5, 1.8, -10 );
carOld.scale.set( 0.04, 0.04, 0.04 );
carOld.traverse(function (child) {
child.castShadow = true;
child.receiveShadow = true;
});
carOld.updateMatrix();
carOld.name = "Classic";
scene.add( carOld );
objects.push( carOld );
} );
//--- loads Modern Mini ---
colladaLoader = new THREE.ColladaLoader();
colladaLoader.options.convertUpAxis = true;
colladaLoader.load( 'dae_files/ModernMini.dae', function ( collada ) {
carNew = collada.scene;
carNew.position.set( -14.5, 6.3, -10 );
carNew.scale.set( 0.06, 0.06, 0.06 );
// creates shadow
carNew.traverse(function (child) {
child.castShadow = true;
child.receiveShadow = true;
});
carNew.updateMatrix();
carNew.name = "Modern";
scene.add( carNew );
objects.push( carNew );
} );
//--- loads the Showroom ---
colladaLoader = new THREE.ColladaLoader();
colladaLoader.options.convertUpAxis = true;
colladaLoader.load( 'dae_files/roomAnim2.dae', function collada( collada ) {
showroom = collada.scene;
animations = collada.animations;
kfAnimationsLength = animations.length;
// Initialise last frame current time.
for ( var i = 0; i < kfAnimationsLength; i++ ) {
lastFrameCurrentTime[i] = 0;
}
// Get all the key frame animations.
for ( var i = 0; i < kfAnimationsLength; i++ ) {
var anim = animations[ i ];
var keyFrameAnim = new THREE.KeyFrameAnimation( anim );
keyFrameAnim.timeScale = 1;
keyFrameAnim.loop = false;
kfAnimations.push( keyFrameAnim );
anim = kfAnimations[i];
anim.play();
}
showroom.position.set(0, 0, -20);
showroom.scale.set(0.06, 0.06, 0.06);
showroom.traverse(function (child) {
child.castShadow = true;
child.receiveShadow = true;
});
showroom.updateMatrix();
scene.add( showroom );
animate();
} );
}
// On Mouse double click event function
function onDoubleClick(event) {
// Set the mouse down flag to false
mouseDown = false;
// Canvas x (left) and y (top) position
var canvasLeft = 0;
var canvasTop = 0;
// "event.clientX" is the mouse x position. "event.clientY" is the mouse y position
var tempX = event.clientX - canvasLeft;
var tempY = event.clientY - canvasTop;
// Create a normalised vector in 2d space
var vector = new THREE.Vector3((tempX / window.innerWidth) * 2 - 1, - (tempY / innerHeight) * 2 + 1, 0.5);
// Unproject a 2D point into the 3D word
// Use the camera projection matrix to transform the vector to the 3D world space
vector.unproject(camera);
// Send a ray in the direction the user has clicked from the cameras position
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
// Check if the ray has intersected with any objects and get intersections
var intersects = raycaster.intersectObjects(objects, true);
// Check if intersected with objects
if (intersects.length > 0) {
var tempStr = "Number of items: " + intersects.length + " ";
// List the items that were hit
for(var i=0; i < intersects.length; i++){
if(intersects[i].object.name != ""){
// The mesh name set above
tempStr += " | Name: " + intersects[i].object.name;
} else {
// The names inside the model
tempStr += " | Name: " + intersects[i].object.parent.name;
}
}
//Debug information
document.getElementById("debugInfo").innerHTML = tempStr + ".<br>";
//END
}
}
function loopAnimations(){
// Loop through all animations
for ( var i = 0; i < kfAnimationsLength; i++ ) {
// Check if the animation is player and not paused.
if(kfAnimations[i].isPlaying && !kfAnimations[i].isPaused){
if(kfAnimations[i].currentTime == lastFrameCurrentTime[i]) {
kfAnimations[i].stop();
//kfAnimations[i].play();
lastFrameCurrentTime[i] = 0;
}
}
}
}
function play_pauseAnim() {
//checks is there animation and is it paused
if(kfAnimationsLength > 0) {
if(kfAnimations[0].isPlaying) {
for(i = 0; i < kfAnimationsLength; i++){
kfAnimations[i].stop();
}
}else {
for(i = 0; i < kfAnimationsLength; i++) {
lastFrameCurrentTime[i] = 0;
//kfAnimations[i].play(kfAnimations[i].currentTime);
kfAnimations[i].play(0);
}
}
}
}
function checkTime(){
if(kfAnimationsLength > 0) {
if(kfAnimations[0].isPlaying) {
if(kfAnimations[0].currentTime > 3){
play_pauseAnim();
}
}
}
}
// create a render loop to draw the scene 60 times per second
function render() {
//checkTime();
daeObject.rotation.y += guiControls.rotationY;
//if (daeObject.position.z < 25) {
daeObject.position.z = guiControls.positionZ;
//}
spotLight.rotation.x += guiControls.rotationX;
spotLight.rotation.y += guiControls.rotationY;
spotLight.rotation.z += guiControls.rotationZ;
stats.update();
}
function animate () {
var deltaTime = clock.getDelta();
for ( var i = 0; i < kfAnimationsLength; i++ ) {
// Get a key frame animation.
var anim = kfAnimations[i];
anim.update( deltaTime );
}
loopAnimations();
requestAnimationFrame(animate);
// Update last frame current time.
for ( var i = 0; i < kfAnimationsLength; i++ ) {
lastFrameCurrentTime[i] = kfAnimations[i].currentTime;
}
render();
renderer.render(scene, camera);
}
// Loads skybox texture
function loadTexture(path) {
var texture = new THREE.Texture(texture_placeholder);
var material = new THREE.MeshBasicMaterial({
map: texture,
overdraw: 0.5
});
var image = new Image();
image.onload = function() {
texture.image = this;
texture.needsUpdate = true;
};
image.src = path;
return material;
}
Macast,
Please, check if you haven't forgotten in your code:
var objets = [];
var raycaster = new THREE.Raycaster();
And for each part of the car this line :
objects.push( mesh );
Ex:
var geometry = new THREE.RingGeometry( 1, 5, 32 );
var material = new THREE.MeshBasicMaterial( { color: 0xff0000, side: THREE.DoubleSide } );
var simpleTire = new THREE.Mesh( geometry, material );
simpleTire.name = 'tire';
objects.push( simpleTire );
scene.add( simpleTire );
Then, it's simple :
if ( intersects.length > 0 ) {
switch(intersects[0].object.name){
case 'tire':
console.log('A pretty red tire');
break;
case 'motor':
console.log('An electric motor');
break;
}
}

Categories