Avoid propagating keydown events from dat.GUI to Three.js scene - javascript

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;
}
// [...]
}

Related

Can't get material from a gltf loaded scene in threejs

I'd like to modify the material in a gltf loaded model to set its opacity/transparency.
I can't seem to get any meshes. I have created this fiddle that says 'no mesh found'. I have found quite a few examples of getting material and they all seem to do it this way (making sure it's mesh then getting the material).
<style>
canvas {
display: block;
width: 98%;
height: 98%;
}
</style>
<canvas id="canvasid"></canvas>
<script src="https://cdn.jsdelivr.net/npm/three#0.143/examples/js/controls/OrbitControls.js"></script>-->
<script async src="https://unpkg.com/es-module-shims#1.3.6/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three#0.143/build/three.module.js"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { GLTFLoader } from 'https://cdn.jsdelivr.net/npm/three#0.143/examples/jsm/loaders/GLTFLoader.js';
import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/three#0.143/examples/jsm/controls/OrbitControls.js';
var scene = new THREE.Scene();
scene.background = new THREE.Color(0x6699cc);
const lights = [];
lights[0] = new THREE.PointLight(0xffffff, 1, 0);
lights[1] = new THREE.PointLight(0xffffff, 1, 0);
lights[2] = new THREE.PointLight(0xffffff, 1, 0);
lights[0].position.set(0, 100, 0);
lights[1].position.set(100, 100, 100);
lights[2].position.set(- 100, - 100, - 100);
scene.add(lights[0]);
scene.add(lights[1]);
scene.add(lights[2]);
const loader = new GLTFLoader();
loader.load("https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf", function (gltf) {
const mesh = gltf.scene.children[0];
scene.add(mesh);
//my main question is about this code - looks like everyone can test if isMesh, then can get the material. I have tried multiple models and can never find any mesh. If I just assumme a material that doesn't work either
gltf.scene.traverse((o) => {
if (o.isMesh) {
console.log("found mesh");
console.log(o && o.material);
} else {
console.log("no mesh found");
}
});
}, undefined, function (error) {
console.error(error);
});
var canvas = document.getElementById("canvasid");
var renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true
});
renderer.setSize(canvas.parentElement.clientWidth, canvas.parentElement.clientHeight);
var camera = new THREE.PerspectiveCamera(1, canvas.parentElement.clientWidth / canvas.parentElement.clientHeight, 1, 1000);
camera.position.z = 100;
const controls = new OrbitControls(camera, renderer.domElement);
controls.update();
controls.autoRotate = true;
controls.enableDamping = true;
var animate = function () {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
};
window.addEventListener('resize', function () {
renderer.setSize(canvas.parentElement.clientWidth, canvas.parentElement.clientHeight);
camera.aspect = canvas.parentElement.clientWidth / canvas.parentElement.clientHeight;
camera.updateProjectionMatrix();
}, false);
animate();
How can I access the mesh/material?
The traverse does not work since you add the first child of gltf.scene to scene. That means you change the object hierarchy and thus break the traversal.
To fix this, simply move the below two lines of code after the call of Object3D.traverse().
const mesh = gltf.scene.children[0];
scene.add(mesh);
Also, consider to just do this since a glTF asset does not necessarily only have a single child mesh:
scene.add(gltf.scene);
Updated fiddle: https://jsfiddle.net/ftcnby9e/

[Violation]Added non-passive event listener to a scroll-blocking 'touchstart' event

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();

How to switch between a WebGL renderer and a canvas renderer in three.js

I want to render a scene using a WebGL renderer or a canvas renderer in three.js (version 69).
This is my code:
<!DOCTYPE html>
<html>
<head>
<script src="./libs/three.js"></script>
<script src="./libs/CanvasRenderer.js"></script>
<script src="./libs/Projector.js"></script>
<script src="./libs/dat.gui.js"></script>
</head>
<body>
<div id="renderer-output"></div>
<script type="text/javascript">
// global variables
var scene, camera, renderer;
var gui;
var config = {
rendererType: 'WebGL',
perspectiveCameraFOV: 45,
perspectiveCameraNear: 0.1,
perspectiveCameraFar: 1000,
cameraPosition_x: -30,
cameraPosition_y: 40,
cameraPosition_z: 30,
};
function init() {
// create the scene
scene = new THREE.Scene();
// create the camera
camera = new THREE.PerspectiveCamera(
config.perspectiveCameraFOV,
window.innerWidth / window.innerHeight,
config.perspectiveCameraNear,
config.perspectiveCameraFar
);
// set the position
camera.position.set(
config.cameraPosition_x,
config.cameraPosition_y,
config.cameraPosition_z
);
// set the look at
camera.lookAt(scene.position);
// create the renderer
if (config.rendererType == 'WebGL') {
renderer = new THREE.WebGLRenderer();
} else if (config.rendererType == 'canvas') {
renderer = new THREE.CanvasRenderer();
}
// set the size
renderer.setSize(window.innerWidth, window.innerHeight);
// add the output to the html element
document.getElementById("renderer-output").appendChild(renderer.domElement);
var boxGeometry = new THREE.BoxGeometry(4, 4, 4);
var boxMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
var box = new THREE.Mesh(boxGeometry, boxMaterial);
scene.add(box);
// create the dat gui
gui = new dat.GUI({
preset: config // preset default config
});
// remember config
gui.remember(config);
// add a renderer folder
let rendererFolder = gui.addFolder("Renderer");
rendererFolder.open();
// set control rendererType
let rendererTypeController = rendererFolder.add(
config,
"rendererType",
['WebGL', 'canvas']
);
rendererTypeController.onFinishChange(function (rendererType) {
// remove the renderer output from the html element
document.getElementById('renderer-output').removeChild(renderer.domElement);
// create the renderer
if (rendererType == 'WebGL') {
renderer = new THREE.WebGLRenderer();
} else {
renderer = new THREE.CanvasRenderer();
}
// set the size
renderer.setSize(window.innerWidth, innerHeight);
// add the output to the html element
document.getElementById('renderer-output').appendChild(renderer.domElement);
});
render();
}
function render() {
// update the scene
requestAnimationFrame(render);
renderer.render(scene, camera);
}
window.onload = init;
</script>
</body>
</html>
When I want to change the renderer type, the following lines are executed:
// remove the renderer output from the html element
document.getElementById('renderer-output').removeChild(renderer.domElement);
// create the renderer
if (rendererType == 'WebGL') {
renderer = new THREE.WebGLRenderer();
} else {
renderer = new THREE.CanvasRenderer();
}
// set the size
renderer.setSize(window.innerWidth, innerHeight);
// add the output to the html element
document.getElementById('renderer-output').appendChild(renderer.domElement);
But when I change the renderer from canvas to WebGL I can't see the objects in the scene. When I go from WebGL to canvas I can see it.
I don't get any error in console.
Do you have any advice ?
I can find a solution creating two variables to store the two different renderer types and calling them when I change the value of config.rendererTypes.
<!DOCTYPE html>
<html>
<head>
<script src="./libs/three.js"></script>
<script src="./libs/CanvasRenderer.js"></script>
<script src="./libs/Projector.js"></script>
<script src="./libs/dat.gui.js"></script>
</head>
<body>
<div id="renderer-output"></div>
<script type="text/javascript">
// global variables
var camera, scene;
var rendererWebGL, rendererCanvas;
var gui;
var config = {
rendererType: 'WebGL'
};
function init() {
// create the scene
scene = new THREE.Scene();
// create the camera
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
// set the position
camera.position.set(-30, 40, 30);
// set the look at
camera.lookAt(new THREE.Vector3(0, 0, 0));
// create the renderer
rendererWebGL = new THREE.WebGLRenderer();
rendererCanvas = new THREE.WebGLRenderer();
// set the sizeWebGLRenderer
rendererWebGL.setSize(window.innerWidth, window.innerHeight);
rendererCanvas.setSize(window.innerWidth, window.innerHeight);
// set the color
// rendererWebGL.setClearColor(0xEEEEEE);
// rendererCanvas.setClearColor(0xEEEEEE);
// add the output to the html element
if (config.rendererType == 'WebGL') {
document.getElementById("renderer-output").appendChild(rendererWebGL.domElement);
} else if (config.rendererType == 'canvas') {
document.getElementById("renderer-output").appendChild(rendererCanvas.domElement);
}
// add a box to the scene
var boxGeometry = new THREE.BoxGeometry(4, 4, 4);
var boxMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
scene.add(new THREE.Mesh(boxGeometry, boxMaterial));
// create the dat gui
gui = new dat.GUI({
preset: config
});
// remember config
gui.remember(config);
// add a renderer folder
let rendererFolder = gui.addFolder("Renderer");
// set folder open
rendererFolder.open();
// add a control rendererType
let rendererTypeController = rendererFolder.add(config, "rendererType", ['WebGL', 'canvas']);
// set the onFinishCange
rendererTypeController.onFinishChange(function (rendererType) {
// remove the renderer's domElement
var renderer_output = document.getElementById("renderer-output");
renderer_output.removeChild(renderer_output.children[0]);
// add the renderer-output
if (rendererType == 'WebGL') {
renderer_output.appendChild(rendererWebGL.domElement);
} else {
renderer_output.appendChild(rendererWebGL.domElement);
}
});
render();
}
function render() {
// update the scene
if (config.rendererType == 'WebGL') {
rendererWebGL.render(scene, camera);
} else if (config.rendererType == 'canvas') {
rendererCanvas.render(scene, camera);
}
requestAnimationFrame(render);
}
window.onload = init;
</script>
</body>
</html>

conflict between dat.gui.js and trackballControl.js in three.js

When using three.js, I found a conflict between dat.gui.js and trackballControl.js. For example, after changing values by dat.gui, I can't rotate the camera with mousemove because the mouse can not move out from the area of GUI. Why and how to deal with it?
The trackballControls constructor allows for a second argument of a dom element.
This dom element is what the mouse event listeners for the controls will be added to. If you don't supply this argument, the mouse event listeners will be added to the document (which I believe is your problem).
It is hard to give you an example which will work for you, because you have not posted your code.
You should be able to send the renderer dom element to the trackballControls to fix your problem.
eg.
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
// send dom element to trackballControlls.
controls = new THREE.TrackballControls( camera, renderer.domElement );
Run snippet below for working example.
var camera, scene, renderer, mesh, material, controls, gui, directionalLight;
init();
render();
animate();
function init() {
// Renderer.
renderer = new THREE.WebGLRenderer();
//renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
// Add renderer to page
document.body.appendChild(renderer.domElement);
// Create camera.
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 400;
// Add TrackballControls, sending dom element to attach mouseevent listeners to.
controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.addEventListener( 'change', render );
// Create scene.
scene = new THREE.Scene();
// Create material
material = new THREE.MeshPhongMaterial();
// Create cube and add to scene.
var geometry = new THREE.BoxGeometry(200, 200, 200);
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// Create ambient light and add to scene.
var light = new THREE.AmbientLight(0x404040); // soft white light
scene.add(light);
// Create directional light and add to scene.
directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(1, 1, 1).normalize();
scene.add(directionalLight);
// Add listener for window resize.
window.addEventListener('resize', onWindowResize, false);
// Add GUI
var gui = new dat.GUI();
var lightFolder = gui.addFolder('Light');
lightFolder.add(directionalLight, 'intensity').onChange(function(){render()});
lightFolder.open();
}
function animate() {
requestAnimationFrame(animate);
controls.update();
}
// Only called by controls change or gui change.
function render() {
renderer.render(scene, camera);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
controls.handleResize();
}
body {
margin: 0px;
}
<script src="https://cdn.rawgit.com/dataarts/dat.gui/v0.6.2/build/dat.gui.min.js"></script>
<script src="https://rawgit.com/mrdoob/three.js/master/build/three.min.js"></script>
<script src="https://rawgit.com/mrdoob/three.js/dev/examples/js/controls/TrackballControls.js"></script>

three js clickable objects

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);

Categories

Resources