I have two 3d objects "meshes" that I created in three.js.
I want to add click event to these objects so that when I click on one of them, it is scaled up and when I click on the other one, it rotates.
I tried this method to add a click event to an object and it's not working.
<script src='threex.domevent.js'> </script>
<script>
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(20, window.innerWidth/window.innerHeight, 1, 1000);
camera.position.z = 100;
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.CubeGeometry(1,1,1);
var material = new THREE.MeshBasicMaterial({color: 0x00ff00});
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);
cube.addEventListener( 'click',function(){cubeScale()} , false );
function cubeScale()
{
cube.scale.x *= 20;
}
var render = function () {
requestAnimationFrame(render);
cube.rotation.y += 0.1;
renderer.render(scene, camera);
};
render();
</script>
you can use this event manager three.interaction
and see this demo
import { Scene, PerspectiveCamera, WebGLRenderer, Mesh, BoxGeometry, MeshBasicMaterial } from 'three';
import { Interaction } from 'three.interaction';
const renderer = new WebGLRenderer({ canvas: canvasElement });
const scene = new Scene();
const camera = new PerspectiveCamera(60, width / height, 0.1, 100);
// new a interaction, then you can add interaction-event with your free style
const interaction = new Interaction(renderer, scene, camera);
const cube = new Mesh(
new BoxGeometry(1, 1, 1),
new MeshBasicMaterial({ color: 0xffffff }),
);
scene.add(cube);
cube.cursor = 'pointer';
cube.on('click', function(ev) {});
cube.on('touchstart', function(ev) {});
cube.on('touchcancel', function(ev) {});
cube.on('touchmove', function(ev) {});
cube.on('touchend', function(ev) {});
cube.on('mousedown', function(ev) {});
cube.on('mouseout', function(ev) {});
cube.on('mouseover', function(ev) {});
cube.on('mousemove', function(ev) {});
cube.on('mouseup', function(ev) {});
// and so on ...
/**
* you can also listen on parent-node or any display-tree node,
* source event will bubble up along with display-tree.
* you can stop the bubble-up by invoke ev.stopPropagation function.
*/
scene.on('touchstart', ev => {
console.log(ev);
})
scene.on('touchmove', ev => {
console.log(ev);
})
Absolutely you need to implement raycaster to select/click/pick certain object in three.js. I've already implemented it in one of my project using three.js and raycaster against collada model. For your reference, these links can help you:
http://soledadpenades.com/articles/three-js-tutorials/object-picking/
http://jensarps.de/2012/08/10/mouse-picking-collada-models-with-three-js/
You can use pickingRay to perform clicking with orthographic camera like that follows in this code:
var vector = new THREE.Vector3(
(event.clientX / window.innerWidth) * 2 - 1,
-(event.clientY / window.innerHeight) * 2 + 1, 0.5);
var rayCaster = projector.pickingRay(vector, camera);
var intersectedObjects = rayCaster.intersectObjects(scene.children, true);
But if you want perform clicking on perspective camera you have to perform an clicking using the unprojectVector like that follows below:
var vector = new THREE.Vector3(
(event.clientX / window.innerWidth) * 2 - 1,
- (event.clientY / window.innerHeight) * 2 + 1,
0.5 );
var rayCaster = projector.unprojectVector(vector, camera);
var intersectedObjects = rayCaster.intersectObjects(objects);
Related
I just started working with Three.js & everytime I try to use the onchange event, I keep getting this non-passive event listener violation for my onchange event. please help.
ERROR : [Violation] Added non-passive event listener to a scroll-blocking 'touchstart' event. Consider marking event handler as 'passive' to make the page more responsive. See https://www.chromestatus.com/feature/5745543795965952
CODE :
"use strict";
import * as THREE from "https://unpkg.com/three#0.141.0/build/three.module";
import * as dat from "https://cdn.skypack.dev/dat.gui";
// Adding the gui //
const gui = new dat.GUI();
const world = {
plane: {
width: 10
}
}
gui.add(world.plane, 'width', 1, 20).
onchange = function() {
console.log(planeMesh.geometry);
// planeMesh.geometry.dispose();
// planeMesh.geometry = new THREE.planeGeometry(world.plane.width, 10, 10, 10);
}
// Instantiating scene, camera & renderer //
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
camera.position.z = 5;
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth,window.innerHeight);
document.body.appendChild(renderer.domElement);
// Building the geometry & material //
const planeGeometry = new THREE.PlaneGeometry(10, 10, 10, 10);
const planeMaterial = new THREE.MeshPhongMaterial({
color: 0xff0000,
side: THREE.DoubleSide,
flatShading: THREE.FlatShading
});
// Binding the geometry and material //
const planeMesh = new THREE.Mesh(planeGeometry,planeMaterial);
// Adding the planeMesh to the scene //
scene.add( planeMesh );
const {array} = planeMesh.geometry.attributes.position;
for(let i=0; i<array.length; i += 3){
const x = array[i];
const y = array[i+1];
const z = array[i+2];
array[i+2] = z + Math.random();
}
// Adding light //
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0,0,1);
scene.add(light);
// Animating & rendering the design //
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera );
};
animate();
I have this code (see bellow) with 2 event listeners:
renderer.domElement.addEventListener("pointerdown", changeColor, false);
document.addEventListener("keydown", changeColor, false);
They both trigger a color change in the cube. However, when I edit the input parameter in the GUI, keydown events also result in a color change, and I would like to avoid that.
I am guessing this is because I am using document.addEventListener for the keydown events. However, if I use renderer.domElement.addEventListener instead it wont work.
How can I avoid keydown events to propagate when editing GUI parameters?
Code
var renderer, controls, scene, camera;
var cube;
init();
function init() {
// Scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0xb0b0b0);
// Camera
camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(300, 300, 300);
camera.up.set(0, 0, 1);
// Light
var ambientLight = new THREE.AmbientLight(0xcccccc, 0.2);
scene.add(ambientLight);
// Helpers
var helpers = new THREE.Group();
var grid = new THREE.GridHelper(200, 10);
grid.rotation.x = Math.PI / 2;
var axis = THREE.AxisHelper(100);
helpers.add(grid);
helpers.add(axis);
scene.add(helpers);
// Renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
// Event listeners
controls.addEventListener("change", render, false);
// Draw the cube
var cubeGeometry = new THREE.BoxGeometry( 50, 50, 50 );
var cubeMaterial = new THREE.MeshBasicMaterial({ color: 0x000088 } );
cube = new THREE.Mesh( cubeGeometry, cubeMaterial );
scene.add(cube);
// GUI
params = {
size: 50,
};
var gui = new dat.GUI();
gui.add(params, "size", 0.0, 100, 1);
// Listeners
renderer.domElement.addEventListener("pointerdown", changeColor, false);
document.addEventListener("keydown", changeColor, false);
// Render
render();
}
function changeColor(event) {
cube.material.color.set(Math.random() * 0xffffff);
cube.material.needsUpdate = true;
render();
}
function render() {
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<html>
<head>
<script src="https://unpkg.com/three#0.126.0/build/three.min.js"></script>
<script src="https://unpkg.com/three#0.126.0/examples/js/controls/OrbitControls.js"></script>
<script src="https://stemkoski.github.io/Three.js/js/DAT.GUI.min.js"></script>
</head>
<body>
</body>
</html>
Not sure this is the best approach, but you can ignore keydown events from the event handler by inspecting its target. If the target is an input, then do nothing:
function changeColor(event) {
// Ignore key event when editing dat.GUI input values
if (event.target.localName == "input") {
return;
}
// [...]
}
I am loading a model through GLTF loader. I want to select a mesh on mouse hover. Everything is going cool, but the main problem is when hovering its changing the color all material whose name is same (as per my researches). When i am debugging its INTERSECTED returning single material. I don't know why its happening. After many researches i am asking this question here.
Please see my code below.
<div id="ThreeJS" style="position: absolute; left:0px; top:0px"></div>
var container, scene, camera, renderer, controls, stats;
var clock = new THREE.Clock();
var xyzz;
// custom global variables
var cube;
var projector,
mouse = {
x: 0,
y: 0
},
INTERSECTED;
init();
animate();
// FUNCTIONS
function init() {
// SCENE
scene = new THREE.Scene();
// CAMERA
var SCREEN_WIDTH = window.innerWidth,
SCREEN_HEIGHT = window.innerHeight;
var VIEW_ANGLE = 45,
ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT,
NEAR = 0.1,
FAR = 20000;
camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
scene.add(camera);
camera.position.set(0, 0, 0);
camera.lookAt(scene.position);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
container = document.getElementById("ThreeJS");
container.appendChild(renderer.domElement);
// EVENTS
// CONTROLS
controls = new THREE.OrbitControls(camera, renderer.domElement);
// STATS
stats = new Stats();
stats.domElement.style.position = "absolute";
stats.domElement.style.bottom = "0px";
stats.domElement.style.zIndex = 100;
container.appendChild(stats.domElement);
// LIGHT
const skyColor = 0xb1e1ff; // light blue
const groundColor = 0xb97a20; // brownish orange
const intensity = 5;
const light = new THREE.HemisphereLight(
skyColor,
groundColor,
intensity
);
scene.add(light);
scene.background = new THREE.Color("#fff");
// GLTF Loader
function frameArea(sizeToFitOnScreen, boxSize, boxCenter, camera) {
const halfSizeToFitOnScreen = sizeToFitOnScreen * 0.5;
const halfFovY = THREE.Math.degToRad(camera.fov * 0.5);
const distance = halfSizeToFitOnScreen / Math.tan(halfFovY);
// compute a unit vector that points in the direction the camera is now
// in the xz plane from the center of the box
const direction = new THREE.Vector3()
.subVectors(camera.position, boxCenter)
.multiply(new THREE.Vector3(1, 0, 1))
.normalize();
// move the camera to a position distance units way from the center
// in whatever direction the camera was from the center already
camera.position.copy(
direction.multiplyScalar(distance).add(boxCenter)
);
// pick some near and far values for the frustum that
// will contain the box.
camera.near = boxSize / 100;
camera.far = boxSize * 100;
camera.updateProjectionMatrix();
// point the camera to look at the center of the box
// camera.position.set(0, 150, 400);
camera.lookAt(boxCenter.x, boxCenter.y, boxCenter.z);
}
var loader = new THREE.GLTFLoader();
loader.load(
// resource URL
"models/gltf/DamagedHelmet/glTF/50423_ Revit Model.gltf",
// called when the resource is loaded
function(gltf) {
const root = gltf.scene;
scene.add(root);
// console.log(dumpObject(root).join("\n"));
const box = new THREE.Box3().setFromObject(root);
const boxSize = box.getSize(new THREE.Vector3()).length();
const boxCenter = box.getCenter(new THREE.Vector3());
// set the camera to frame the box
frameArea(boxSize * 1, boxSize, boxCenter, camera);
// update the Trackball controls to handle the new size
controls.maxDistance = boxSize * 10;
controls.target.copy(boxCenter);
controls.update();
},
// called while loading is progressing
function(xhr) {
console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
},
// called when loading has errors
function(error) {
debugger;
console.log("An error happened");
}
);
projector = new THREE.Projector();
// when the mouse moves, call the given function
document.addEventListener("mousemove", onDocumentMouseMove, false);
}
function onDocumentMouseMove(event) {
// update the mouse variable
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
function animate() {
requestAnimationFrame(animate);
render();
update();
}
function update() {
// 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);
vector.unproject(camera);
var ray = new THREE.Raycaster(
camera.position,
vector.sub(camera.position).normalize()
);
// create an array containing all objects in the scene with which the ray intersects
var intersects = ray.intersectObjects(scene.children, true);
// INTERSECTED = the object in the scene currently closest to the camera
// and intersected by the Ray projected from the mouse position
// if there is one (or more) intersections
if (intersects.length > 0) {
// if the closest object intersected is not the currently stored intersection object
if (intersects[0].object != INTERSECTED) {
// 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;
console.log(INTERSECTED);
// 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);
}
}
// there are no intersections
else {
// restore previous intersection object (if it exists) to its original color
if (INTERSECTED)
INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
// remove previous intersection object reference
// by setting current intersection object to "nothing"
INTERSECTED = null;
}
controls.update();
stats.update();
}
function render() {
renderer.render(scene, camera);
}
function dumpObject(obj, lines = [], isLast = true, prefix = "") {
const localPrefix = isLast ? "└─" : "├─";
lines.push(
`${prefix}${prefix ? localPrefix : ""}${obj.name || "*no-name*"} [${
obj.type
}]`
);
const newPrefix = prefix + (isLast ? " " : "│ ");
const lastNdx = obj.children.length - 1;
obj.children.forEach((child, ndx) => {
const isLast = ndx === lastNdx;
dumpObject(child, lines, isLast, newPrefix);
});
return lines;
}
Please help me out.
I didn't read through all of the code, but I think this might already help:
In your intersection-handler, you are updating the color of the material assigned to the object (INTERSECTED.material.color.setHex(...)). This will cause the problems you describe as identical materials are very likely reused for multiple objects. To prevent that, you could use a different material:
const hightlightMaterial = new MeshStandardMaterial(...);
and instead of just updating the color, replace the material:
INTERSECTED.originalMaterial = INTERSECTED.material;
INTERSECTED.material = highlightMaterial;
Restore the original when "unhighlighting" the object:
INTERSECTED.material = INTERSECTED.originalMaterial;
delete INTERSECTED.originalMaterial;
If you need the highlightMaterial to retain other material-properties from the original, you can do this to copy over all material properties beforehand:
highlightMaterial.copy(INTERSECTED.material);
highlightMaterial.color.copy(highlightColor);
As I am new in the 3D world and three JS world, I understood most of the things, but always gets confused when it comes to matrices.
What I am trying to do is that I want to drag a small object on top of other objects and small object should face the same direction of the main object (an example is like hanging a wall clock on the wall).
To do this, I first tried placing an axis helper on top of rotating cube and applied the simple logic that, an Intersecting point will give the position for putting a small object and intersecting objects face normal will give direction for small object lookAt. I did and found success but not appropriate.
Then I did some calculation and searched some codes for calculating the same things, I got success now. But didn't understand the whole logic behind, WHY we did this.
this.normalMatrix.getNormalMatrix(intersects[i].object.matrixWorld);
this.worldNormal.copy(intersects[i].face.normal).applyMatrix3(this.normalMatrix).normalize();
this.object.position.addVectors(intersects[i].point, this.worldNormal);
this.lookAtVec.addVectors(this.object.position,this.worldNormal.multiplyScalar(15));
this.object.lookAt(this.lookAtVec);
One guy actually created a wall and placed a small object on top. He changed this line
this.object.position.addVectors(intersects[i].point, this.worldNormal);
to
this.object.position.copy(intersects[i].point);
and it is working for him, but the same thing for my axis helper is not working.
Just an option of how you can do it. Look at the end of the onMouseMove() function:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(-3, 5, 8);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x404040);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var light = new THREE.DirectionalLight(0xffffff, 0.5);
light.position.setScalar(10);
scene.add(light);
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
var walls = [];
makeWall(Math.PI * 0.5);
makeWall(0);
makeWall(Math.PI * -0.5);
var clockGeom = new THREE.BoxBufferGeometry(1, 1, 0.1);
clockGeom.translate(0, 0, 0.05);
var clockMat = new THREE.MeshBasicMaterial({
color: "orange"
});
var clock = new THREE.Mesh(clockGeom, clockMat);
scene.add(clock);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var intersects = [];
var lookAt = new THREE.Vector3();
renderer.domElement.addEventListener("mousemove", onMouseMove, false);
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
intersects = raycaster.intersectObjects(walls);
if (intersects.length === 0) return;
clock.position.copy(intersects[0].point);
clock.lookAt(lookAt.copy(intersects[0].point).add(intersects[0].face.normal));
}
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
function makeWall(rotY, color) {
let geom = new THREE.BoxBufferGeometry(8, 8, 0.1);
geom.translate(0, 0, -4);
geom.rotateY(rotY);
let mat = new THREE.MeshLambertMaterial({
color: Math.random() * 0x777777 + 0x777777
});
let wall = new THREE.Mesh(geom, mat);
scene.add(wall);
walls.push(wall);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/94/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
I'm pretty beginner in three.js and webgl programming. so I have created a box in three.js and its working fine but the problem is when I set camera position in z axis(eg: camera.position.z = 2; ) the box just disappears. could anyone explain me why is it happening and how can I properly set the position of the camera?
try uncommenting the camera.position.z = 2; in the fiddle
function init() {
var scene = new THREE.Scene();
var box = getBox(1, 1, 1);
scene.add(box);
var camera = new THREE.Camera(45, window.innerWidth/window.innerHeight, 1, 1000 );
//camera.position.z = 2;
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById("webgl").appendChild(renderer.domElement);
renderer.render(scene, camera);
}
function getBox(w, h, d) {
var geometry = new THREE.BoxGeometry(w, h, d);
var material = new THREE.MeshBasicMaterial({
color : 0x00ff00
});
var mesh = new THREE.Mesh(geometry, material);
return mesh;
}
init();
not sure if you're trying to create this scene with an orthographic camera or perspective camera, but you'll typically need to specify the camera by type (ie THREE.PerspectiveCamera(...)).
I also added a few extra lines to ensure the camera was configured correctly, namely, setting the "LookAt" point to (0,0,0) , as well as setting an actual position of the camera via the THREE.Vector3.set(...) method.
Here are my adjustments to your code:
function init() {
var scene = new THREE.Scene();
var box = getBox(1, 1, 1);
scene.add(box);
var camera = new THREE.PerspectiveCamera(70,
window.innerWidth/window.innerHeight, 0.1, 1000 ); // Specify camera type like this
camera.position.set(0,2.5,2.5); // Set position like this
camera.lookAt(new THREE.Vector3(0,0,0)); // Set look at coordinate like this
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById("webgl").appendChild(renderer.domElement);
renderer.render(scene, camera);
}
function getBox(w, h, d) {
var geometry = new THREE.BoxGeometry(w, h, d);
var material = new THREE.MeshBasicMaterial({
color : 0x00ff00
});
var mesh = new THREE.Mesh(geometry, material);
return mesh;
}
init();
Try
camera.position.set( <X> , <Y> , <Z> );
where <X> and <Z> are 2D coordinates and <Y> is height