InstancedMesh with unique texture per instance - three.js

I am looking for a way to draw several objects with unique textures. I came across this old question about instancedMesh where someone got the multiple instances with different textures but on desktop, textures have weird artifacts. Initially I thought something must be wrong with that demo but everything seems fine to me, I also tried to use mix functions in place of conditionals but textures still have artifacts.
I have been looking for different ways to draw multiple unique geometries so merging geometries isn't an option, but most results I get are for multiple objects with merged geometry. Would be great if someone can offer some guidance.
var camera, scene, renderer, stats;
var mesh;
var amount = parseInt( window.location.search.substr( 1 ) ) || 10;
var count = Math.pow( amount, 3 );
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2( 1, 1 );
var rotationTheta = 0.1;
var rotationMatrix = new THREE.Matrix4().makeRotationY( rotationTheta );
var instanceMatrix = new THREE.Matrix4();
var matrix = new THREE.Matrix4();
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set( amount, amount, amount );
camera.lookAt( 0, 0, 0 );
scene = new THREE.Scene();
var light = new THREE.HemisphereLight( 0xffffff, 0x000088 );
light.position.set( - 1, 1.5, 1 );
scene.add( light );
var light = new THREE.HemisphereLight( 0xffffff, 0x880000, 0.5 );
light.position.set( - 1, - 1.5, - 1 );
scene.add( light );
var geometry = new THREE.BoxBufferGeometry( .5, .5, .5, 1, 1, 1 );
var material = [
new THREE.MeshStandardMaterial( { map: new THREE.TextureLoader().load( 'https://threejs.org/examples/textures/square-outline-textured.png' ) } ),
new THREE.MeshStandardMaterial( { map: new THREE.TextureLoader().load( 'https://threejs.org/examples/textures/golfball.jpg' ) } ),
new THREE.MeshStandardMaterial( { map: new THREE.TextureLoader().load( 'https://threejs.org/examples/textures/metal.jpg' ) } ),
new THREE.MeshStandardMaterial( { map: new THREE.TextureLoader().load( 'https://threejs.org/examples/textures/roughness_map.jpg' ) } ),
new THREE.MeshStandardMaterial( { map: new THREE.TextureLoader().load( 'https://threejs.org/examples/textures/tri_pattern.jpg' ) } ),
new THREE.MeshStandardMaterial( { map: new THREE.TextureLoader().load( 'https://threejs.org/examples/textures/water.jpg' ) } ),
];
material.forEach((m,side)=>{
if ( side!=2 ) return;
m.onBeforeCompile = ( shader ) => {
shader.uniforms.textures = { 'type': 'tv', value: [
new THREE.TextureLoader().load( 'https://threejs.org/examples/textures/crate.gif' ),
new THREE.TextureLoader().load( 'https://threejs.org/examples/textures/equirectangular.png' ),
new THREE.TextureLoader().load( 'https://threejs.org/examples/textures/colors.png' )
] };
shader.vertexShader = shader.vertexShader.replace(
'#define STANDARD',
`#define STANDARD
varying vec3 vTint;
varying float vTextureIndex;`
).replace(
'#include <common>',
`#include <common>
attribute vec3 tint;
attribute float textureIndex;`
).replace(
'#include <project_vertex>',
`#include <project_vertex>
vTint = tint;
vTextureIndex=textureIndex;`
);
shader.fragmentShader = shader.fragmentShader.replace(
'#define STANDARD',
`#define STANDARD
uniform sampler2D textures[3];
varying vec3 vTint;
varying float vTextureIndex;`
)
.replace(
'#include <fog_fragment>',
`#include <fog_fragment>
int texIdx = int(vTextureIndex);
vec4 col;
if (texIdx == 0) {
col = texture2D(textures[0], vUv );
} else if ( texIdx==1) {
col = texture2D(textures[1], vUv );
} else if ( texIdx==2) {
col = texture2D(textures[2], vUv );
}
gl_FragColor = col;
// gl_FragColor.rgb *= vTint;`
)
;
}
});
mesh = new THREE.InstancedMesh( geometry, material, count );
var i = 0;
var offset = ( amount - 1 ) / 2;
var transform = new THREE.Object3D();
var textures = [];
for ( var x = 0; x < amount; x ++ ) {
for ( var y = 0; y < amount; y ++ ) {
for ( var z = 0; z < amount; z ++ ) {
transform.position.set( offset - x, offset - y, offset - z );
transform.updateMatrix();
mesh.setMatrixAt( i ++, transform.matrix );
textures.push(Math.random()<0.3 ? 0 : (Math.random()<0.5 ? 1 : 2));
}
}
}
geometry.setAttribute( 'textureIndex',
new THREE.InstancedBufferAttribute( new Float32Array(textures), 1 ) );
scene.add( mesh );
renderer = new THREE.WebGLRenderer( { antialias: false } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
new THREE.OrbitControls( camera, renderer.domElement );
stats = new Stats();
document.body.appendChild( stats.dom );
window.addEventListener( 'resize', onWindowResize, false );
document.addEventListener( 'mousemove', onMouseMove, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function onMouseMove( event ) {
event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
raycaster.setFromCamera( mouse, camera );
var intersection = raycaster.intersectObject( mesh );
// console.log('intersection', intersection.length);
if ( intersection.length > 0 ) {
mesh.getMatrixAt( intersection[ 0 ].instanceId, instanceMatrix );
matrix.multiplyMatrices( instanceMatrix, rotationMatrix );
mesh.setMatrixAt( intersection[ 0 ].instanceId, matrix );
mesh.instanceMatrix.needsUpdate = true;
}
renderer.render( scene, camera );
stats.update();
}
<script src="https://threejs.org/build/three.js"></script>
<script src="https://threejs.org/examples/js/libs/stats.min.js"></script>
<script src="https://threejs.org/examples/js/libs/dat.gui.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

I believe your issue comes from converting a float to an int, and then using that to create branches. This bug shows up only in a few GPUs, not all of them. I got it to work by keeping vTextureIndex as float, sampling all 3 textures and multiplying each by 1 if the textureIndex matches, or multiplying by 0 if the textureIndex does not match.
I basically replaced these lines:
int texIdx = int(vTextureIndex);
vec4 col;
if (texIdx == 0) {
col = texture2D(textures[0], vUv );
} else if ( texIdx==1) {
col = texture2D(textures[1], vUv );
} else if ( texIdx==2) {
col = texture2D(textures[2], vUv );
}
with this approach:
float x = vTextureIndex;
vec4 col;
col = texture2D(textures[0], vUv ) * step(-0.1, x) * step(x, 0.1);
col += texture2D(textures[1], vUv ) * step(0.9, x) * step(x, 1.1);
col += texture2D(textures[2], vUv ) * step(1.9, x) * step(x, 2.1);
If textureIndex is 0, the first texture is multiplied by 1, the others by 0
If textureIndex is 1, the second texture is multiplied by 1, the others by 0
If textureIndex is 2, the third texture is multiplied by 1, the others by 0
var camera, scene, renderer, stats;
var mesh;
var amount = parseInt( window.location.search.substr( 1 ) ) || 10;
var count = Math.pow( amount, 3 );
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2( 1, 1 );
var rotationTheta = 0.1;
var rotationMatrix = new THREE.Matrix4().makeRotationY( rotationTheta );
var instanceMatrix = new THREE.Matrix4();
var matrix = new THREE.Matrix4();
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set( amount, amount, amount );
camera.lookAt( 0, 0, 0 );
scene = new THREE.Scene();
var light = new THREE.HemisphereLight( 0xffffff, 0x666666 );
light.position.set( - 1, 1.5, 1 );
scene.add( light );
var light = new THREE.HemisphereLight( 0xffffff, 0x666666, 0.5 );
light.position.set( - 1, - 1.5, - 1 );
scene.add( light );
var geometry = new THREE.BoxBufferGeometry( .5, .5, .5, 1, 1, 1 );
var material = [
new THREE.MeshStandardMaterial({color: 0xff9900}),
new THREE.MeshStandardMaterial({color: 0xff0099}),
new THREE.MeshStandardMaterial( { map: new THREE.TextureLoader().load( 'https://threejs.org/examples/textures/metal.jpg' ) } ),
new THREE.MeshStandardMaterial({color: 0x9900ff}),
new THREE.MeshStandardMaterial({color: 0x0099ff}),
new THREE.MeshStandardMaterial({color: 0x99ff00}),
];
material.forEach((m,side)=>{
if ( side!=2 ) return;
m.onBeforeCompile = ( shader ) => {
shader.uniforms.textures = { 'type': 'tv', value: [
new THREE.TextureLoader().load( 'https://threejs.org/examples/textures/crate.gif' ),
new THREE.TextureLoader().load( 'https://threejs.org/examples/textures/sprite0.png' ),
new THREE.TextureLoader().load( 'https://threejs.org/examples/textures/sprite.png' )
] };
shader.vertexShader = shader.vertexShader.replace(
'#define STANDARD',
`#define STANDARD
varying vec3 vTint;
varying float vTextureIndex;`
).replace(
'#include <common>',
`#include <common>
attribute vec3 tint;
attribute float textureIndex;`
).replace(
'#include <project_vertex>',
`#include <project_vertex>
vTint = tint;
vTextureIndex=textureIndex;`
);
shader.fragmentShader = shader.fragmentShader.replace(
'#define STANDARD',
`#define STANDARD
uniform sampler2D textures[3];
varying vec3 vTint;
varying float vTextureIndex;`
)
.replace(
'#include <fog_fragment>',
`#include <fog_fragment>
float x = vTextureIndex;
vec4 col;
col = texture2D(textures[0], vUv ) * step(-0.1, x) * step(x, 0.1);
col += texture2D(textures[1], vUv ) * step(0.9, x) * step(x, 1.1);
col += texture2D(textures[2], vUv ) * step(1.9, x) * step(x, 2.1);
gl_FragColor = col;
`
)
;
}
});
mesh = new THREE.InstancedMesh( geometry, material, count );
var i = 0;
var offset = ( amount - 1 ) / 2;
var transform = new THREE.Object3D();
var textures = [];
for ( var x = 0; x < amount; x ++ ) {
for ( var y = 0; y < amount; y ++ ) {
for ( var z = 0; z < amount; z ++ ) {
transform.position.set( offset - x, offset - y, offset - z );
transform.updateMatrix();
mesh.setMatrixAt( i ++, transform.matrix );
textures.push(Math.random()<0.3 ? 0 : (Math.random()<0.5 ? 1 : 2));
}
}
}
geometry.setAttribute( 'textureIndex',
new THREE.InstancedBufferAttribute( new Float32Array(textures), 1 ) );
scene.add( mesh );
renderer = new THREE.WebGLRenderer( { antialias: false } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
new THREE.OrbitControls( camera, renderer.domElement );
stats = new Stats();
document.body.appendChild( stats.dom );
window.addEventListener( 'resize', onWindowResize, false );
document.addEventListener( 'mousemove', onMouseMove, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function onMouseMove( event ) {
event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
raycaster.setFromCamera( mouse, camera );
var intersection = raycaster.intersectObject( mesh );
// console.log('intersection', intersection.length);
if ( intersection.length > 0 ) {
mesh.getMatrixAt( intersection[ 0 ].instanceId, instanceMatrix );
matrix.multiplyMatrices( instanceMatrix, rotationMatrix );
mesh.setMatrixAt( intersection[ 0 ].instanceId, matrix );
mesh.instanceMatrix.needsUpdate = true;
}
renderer.render( scene, camera );
stats.update();
}
<script src="https://cdn.jsdelivr.net/npm/three#0.140.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.140.0/examples/js/libs/stats.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.140.0/examples/js/controls/OrbitControls.js"></script>

Related

Three.js fragment shader color transition

I'm trying to animate the color transition for ShaderMaterial.
I want to have 5 colors, and switch them every few seconds for a whole object (wave of particles).
Here is my JS code
var material = new THREE.ShaderMaterial( {
uniforms: {
color: { value: new THREE.Color('violet') },
},
vertexShader: document.getElementById( 'vertexshader' ).textContent,
fragmentShader: document.getElementById( 'fragmentshader' ).textContent
});
And fragment shader part:
uniform vec3 color;
void main() {
if ( length( gl_PointCoord - vec2( 0.5, 0.5 ) ) > 0.475 ) discard;
gl_FragColor = vec4(color, 1.0 );
}
I have just a little idea of how three.js works. Any suggestions highly appreciated! :)
All code is here: codepen.io/agrhff/pen/MWKEqQy
As an option, you can use setInterval():
body {
background-color: black;
overflow: hidden;
margin: 0;
}
<script type="x-shader/x-vertex" id="vertexshader">
attribute float scale;
void main() {
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = scale * ( 300.0 / - mvPosition.z );
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform vec3 color;
void main() {
if ( length( gl_PointCoord - 0.5 ) > 0.475 ) discard;
gl_FragColor = vec4(color, 1.0 );
}
</script>
<script type="module">
import * as THREE from 'https://threejs.org/build/three.module.js';
import Stats from 'https://threejs.org/examples/jsm/libs/stats.module.js';
var SEPARATION = 100, AMOUNTX = 150, AMOUNTY = 100;
var container, stats;
var camera, scene, renderer;
var particles, count = 0;
var mouseX = 0, mouseY = -500;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
var colors = [
new THREE.Color(0xffff00),
new THREE.Color(0xff00ff),
new THREE.Color(0x00ffff),
new THREE.Color(0xffffff),
new THREE.Color(0x888888)
];
var colIdx = 0;
init();
animate();
function init() {
container = document.createElement( 'div' );
container.classList.add("mystyle");
document.body.appendChild( container );
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 1000;
scene = new THREE.Scene();
//
var numParticles = AMOUNTX * AMOUNTY;
var positions = new Float32Array( numParticles * 3 );
var scales = new Float32Array( numParticles );
var i = 0, j = 0;
for ( var ix = 0; ix < AMOUNTX; ix ++ ) {
for ( var iy = 0; iy < AMOUNTY; iy ++ ) {
positions[ i ] = ix * SEPARATION - ( ( AMOUNTX * SEPARATION ) / 2 ); // x
positions[ i + 1 ] = 0; // y
positions[ i + 2 ] = iy * SEPARATION - ( ( AMOUNTY * SEPARATION ) / 2 ); // z
scales[ j ] = 1;
i += 3;
j ++;
}
}
var geometry = new THREE.BufferGeometry();
geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
geometry.setAttribute( 'scale', new THREE.BufferAttribute( scales, 1 ) );
var material = new THREE.ShaderMaterial( {
uniforms: {
color: { value: new THREE.Color('violet') }
},
vertexShader: document.getElementById( 'vertexshader' ).textContent,
fragmentShader: document.getElementById( 'fragmentshader' ).textContent
} );
//
particles = new THREE.Points( geometry, material);
scene.add( particles );
//
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
stats = new Stats();
container.appendChild( stats.dom );
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
document.addEventListener( 'touchstart', onDocumentTouchStart, false );
document.addEventListener( 'touchmove', onDocumentTouchMove, false );
//
window.addEventListener( 'resize', onWindowResize, false );
// change the colours, one a second
setInterval(function(){
colIdx = (colIdx + 1) % 5;;
material.uniforms.color.value.copy(colors[colIdx]);
}, 1000);
}
function onWindowResize() {
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
//
function onDocumentMouseMove( event ) {
mouseX = event.clientX - windowHalfX;
// mouseY = event.clientY - windowHalfY;
}
function onDocumentTouchStart( event ) {
if ( event.touches.length === 1 ) {
event.preventDefault();
mouseX = event.touches[ 0 ].pageX - windowHalfX;
// mouseY = event.touches[ 0 ].pageY - windowHalfY;
}
}
function onDocumentTouchMove( event ) {
if ( event.touches.length === 1 ) {
event.preventDefault();
mouseX = event.touches[ 0 ].pageX - windowHalfX;
// mouseY = event.touches[ 0 ].pageY - windowHalfY;
}
}
//
function animate() {
requestAnimationFrame( animate );
render();
stats.update();
}
function render() {
camera.position.x += ( mouseX - camera.position.x ) * .05;
camera.position.y += ( - mouseY - camera.position.y );
camera.lookAt( scene.position );
var positions = particles.geometry.attributes.position.array;
var scales = particles.geometry.attributes.scale.array;
var i = 0, j = 0;
for ( var ix = 0; ix < AMOUNTX; ix ++ ) {
for ( var iy = 0; iy < AMOUNTY; iy ++ ) {
positions[ i + 1 ] = ( Math.sin( ( ix + count ) * 0.3 ) * 50 ) +
( Math.sin( ( iy + count ) * 0.5 ) * 50 );
scales[ j ] = ( Math.sin( ( ix + count ) * 0.3 ) + 1 ) * 8 +
( Math.sin( ( iy + count ) * 0.5 ) + 1 ) * 8;
i += 3;
j ++;
}
}
particles.geometry.attributes.position.needsUpdate = true;
particles.geometry.attributes.scale.needsUpdate = true;
renderer.render( scene, camera );
count += 0.1;
}
</script>

How to rotate the plane using three.js?

Hey guys I am using three.js and have tried to rotate this plane below but to avail. Any idea how to do it please? Below is the code which renders the plane using three.js. How can I rotate it, please?. Thanks!
var container;
var camera, scene, renderer;
var plane;
var mouse, raycaster, isShiftDown = false;
var cubeGeometry = new THREE.BoxBufferGeometry( 50, 50, 50 );
var cubeMaterial = new THREE.MeshLambertMaterial( { color: 0x00ff80, overdraw: 0.5 } );
var objects = [];
init();
render();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
var info = document.createElement( 'div' );
info.style.position = 'absolute';
info.style.top = '10px';
info.style.width = '100%';
info.style.textAlign = 'center';
info.innerHTML = 'three.js - voxel painter<br><strong>click</strong>: add voxel, <strong>shift + click</strong>: remove voxel, save .png';
container.appendChild( info );
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set( 500, 800, 1300 );
camera.lookAt( 0, 0, 0 );
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xf0f0f0 );
// Grid
var gridHelper = new THREE.GridHelper( 1000, 20 );
scene.add( gridHelper );
//
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
//PlaneBufferGeometry: platform to lay cubes
var geometry = new THREE.PlaneBufferGeometry( 10000, 10000 );
geometry.rotateX( - Math.PI / 2 );
plane = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { visible: false } ) );
scene.add( plane );
objects.push( plane );
// Lights
var ambientLight = new THREE.AmbientLight( 0x606060 );
scene.add( ambientLight );
var directionalLight = new THREE.DirectionalLight( 0xffffff );
directionalLight.position.x = Math.random() - 0.5;
directionalLight.position.y = Math.random() - 0.5;
directionalLight.position.z = Math.random() - 0.5;
directionalLight.position.normalize();
scene.add( directionalLight );
var directionalLight = new THREE.DirectionalLight( 0x808080 );
directionalLight.position.x = Math.random() - 0.5;
directionalLight.position.y = Math.random() - 0.5;
directionalLight.position.z = Math.random() - 0.5;
directionalLight.position.normalize();
scene.add( directionalLight );
renderer = new THREE.CanvasRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild(renderer.domElement);
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
//document.addEventListener( 'keydown', onDocumentKeyDown, false );
//document.addEventListener( 'keyup', onDocumentKeyUp, false );
//
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
render();
}
function onDocumentMouseDown( event ) {
event.preventDefault();
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 );
if ( intersects.length > 0 ) {
var intersect = intersects[ 0 ];
if ( isShiftDown ) {
if ( intersect.object != plane ) {
scene.remove( intersect.object );
objects.splice( objects.indexOf( intersect.object ), 1 );
}
} else {
var voxel = new THREE.Mesh( cubeGeometry, cubeMaterial );
voxel.position.copy( intersect.point ).add( intersect.face.normal );
voxel.position.divideScalar( 50 ).floor().multiplyScalar( 50 ).addScalar( 25 );
scene.add( voxel );
objects.push( voxel );
}
render();
}
}
function render() {
renderer.render( scene, camera );
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/renderers/CanvasRenderer.js"></script>
<script src="https://threejs.org/examples/js/renderers/Projector.js"></script>
Look at the Plane here
I recommend to use THREE.OrbitControls.
Add orbit control:
orbitControls = new THREE.OrbitControls(camera);
And animate your scene continuously:
function render() {
renderer.render( scene, camera );
requestAnimationFrame(render);
}
Further you have to manage how THREE.OrbitControls and the mouse event (onDocumentMouseDown) interact together.
A possibility would be to handle only the left mouse button in onDocumentMouseDown:
function onDocumentMouseDown( event ) {
if ( event.button != 0 ) // 0 means left mouse button
return;
And to rotate the scene by the right mouse button. This can be done by setting the .mouseButtons property of OrbitControls:
orbitControls.mouseButtons = {
LEFT: THREE.MOUSE.RIGHT,
MIDDLE: THREE.MOUSE.MIDDLE,
RIGHT: THREE.MOUSE.LEFT
}
Further note, that you can enable or disable pane, zoom and rotate by setting .enablePan, .enableZoom respectively .enableRotate.
See the following example, where the boxes are positioned by the left mouse button and the scene can be rotated by holding the right mouse button:
var container;
var camera, scene, renderer;
var plane;
var mouse, raycaster, isShiftDown = false;
var cubeGeometry = new THREE.BoxBufferGeometry( 50, 50, 50 );
var cubeMaterial = new THREE.MeshLambertMaterial( { color: 0x00ff80, overdraw: 0.5 } );
var objects = [];
init();
render();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set( 500, 800, 1300 );
camera.lookAt( 0, 0, 0 );
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xf0f0f0 );
var gridHelper = new THREE.GridHelper( 1000, 20 );
scene.add( gridHelper );
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
//PlaneBufferGeometry: platform to lay cubes
var geometry = new THREE.PlaneBufferGeometry( 10000, 10000 );
geometry.rotateX( - Math.PI / 2 );
plane = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { visible: false } ) );
scene.add( plane );
objects.push( plane );
// Lights
var ambientLight = new THREE.AmbientLight( 0x606060 );
scene.add( ambientLight );
var directionalLight = new THREE.DirectionalLight( 0xffffff );
directionalLight.position.x = Math.random() - 0.5;
directionalLight.position.y = Math.random() - 0.5;
directionalLight.position.z = Math.random() - 0.5;
directionalLight.position.normalize();
scene.add( directionalLight );
var directionalLight = new THREE.DirectionalLight( 0x808080 );
directionalLight.position.x = Math.random() - 0.5;
directionalLight.position.y = Math.random() - 0.5;
directionalLight.position.z = Math.random() - 0.5;
directionalLight.position.normalize();
scene.add( directionalLight );
renderer = new THREE.CanvasRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild(renderer.domElement);
orbitControls = new THREE.OrbitControls(camera);
orbitControls.mouseButtons = {
LEFT: THREE.MOUSE.RIGHT,
MIDDLE: THREE.MOUSE.MIDDLE,
RIGHT: THREE.MOUSE.LEFT
}
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function onDocumentMouseDown( event ) {
if ( event.button != 0 )
return;
event.preventDefault();
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 );
if ( intersects.length > 0 ) {
var intersect = intersects[ 0 ];
if ( isShiftDown ) {
if ( intersect.object != plane ) {
scene.remove( intersect.object );
objects.splice( objects.indexOf( intersect.object ), 1 );
}
} else {
var voxel = new THREE.Mesh( cubeGeometry, cubeMaterial );
voxel.position.copy( intersect.point ).add( intersect.face.normal );
voxel.position.divideScalar( 50 ).floor().multiplyScalar( 50 ).addScalar( 25 );
scene.add( voxel );
objects.push( voxel );
}
}
}
function render() {
renderer.render( scene, camera );
requestAnimationFrame(render);
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/renderers/CanvasRenderer.js"></script>
<script src="https://threejs.org/examples/js/renderers/Projector.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

THREE.JS import a blender OBJ - add material

I have a simple square plate made in Blender.
I'm trying to add a simple texture to this object. I tried so many tutorials and code I found on the net, I just cant make it happen
my code is
<html>
<style>
body{
margin: 0;
overflow: hidden;
}
</style>
<canvas id="myCanvas"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/93/three.min.js"></script>
<script src="http://threejs.org/examples/js/loaders/MTLLoader.js"></script>
<script src="http://threejs.org/examples/js/loaders/OBJLoader.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="UnpackDepthRGBAShader.js"></script>
<script src="ShadowMapViewer.js"></script>
<script>
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45 ,window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 5, 14);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setClearColor(0xededed);
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var loader = new THREE.TextureLoader();
var texture = loader.load( "./2.jpg" );
// it's necessary to apply these settings in order to correctly display the texture on a shape geometry
//texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 0.05, 0.05 );
var controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.25;
controls.enableZoom = true;
var keyLight = new THREE.DirectionalLight(new THREE.Color('hsl(30, 100%, 75%)'), 1.0);
keyLight.position.set(-100, 0, 100);
var fillLight = new THREE.DirectionalLight(new THREE.Color('hsl(240, 100%, 75%)'), 0.75);
fillLight.position.set(100, 0, 100);
var backLight = new THREE.DirectionalLight(0xffffff, 1.0);
backLight.position.set(100, 0, -100).normalize();
var manager = new THREE.LoadingManager();
manager.onProgress = function ( item, loaded, total ) {
console.log( item, loaded, total );
};
var textureLoader = new THREE.TextureLoader( manager );
var textureOBJ = textureLoader.load( './1.jpg' );
var onProgress = function ( xhr ) {
if ( xhr.lengthComputable ) {
var percentComplete = xhr.loaded / xhr.total * 100;
console.log( Math.round(percentComplete, 2) + '% downloaded' );
}
};
var onError = function ( xhr ) {
console.log(xhr);
};
var roundedRectShape = new THREE.Shape();
( function roundedRect( ctx, x, y, width, height, radius ) {
ctx.moveTo( x, y + radius );
ctx.lineTo( x, y + height - radius );
ctx.quadraticCurveTo( x, y + height, x + radius, y + height );
ctx.lineTo( x + width - radius, y + height );
ctx.quadraticCurveTo( x + width, y + height, x + width, y + height - radius );
ctx.lineTo( x + width, y + radius );
ctx.quadraticCurveTo( x + width, y, x + width - radius, y );
ctx.lineTo( x + radius, y );
ctx.quadraticCurveTo( x, y, x, y + radius );
} )( roundedRectShape, 0, 0, 18.5, 18.5, 2 );
addShape( roundedRectShape, 0x008000, -1.41, 0.5, 1, Math.PI / -2, 0, 0, 0.1 );
scene.add(keyLight);
scene.add(fillLight);
scene.add(backLight);
var loader = new THREE.OBJLoader( manager );
loader.load( './3.obj', function ( object ) {
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.material.map = textureOBJ;
}
} );
object.position.y = -0.01;
object.position.x = 0;
object.position.z = 0;
scene.add( object );
}, onProgress, onError );
controls.update();
var animate = function () {
requestAnimationFrame( animate );
renderer.render(scene, camera);
};
animate();
function addShape( shape, color, x, y, z, rx, ry, rz, s ) {
var geometry = new THREE.ShapeBufferGeometry( shape );
var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial( { side: THREE.DoubleSide, map: texture } ) );
mesh.position.set( x, y, z );
mesh.rotation.set( rx, ry, rz );
mesh.scale.set( s, s, s );
scene.add( mesh );
}
</script>
I'm trying to add this simple png as the texture. anyone can help?
plate - example -> http://37.59.53.90:8080/test1.html
I've managed to create a cup with texture -> http://37.59.53.90:8080/test.html
It looks like you're using THREE.TextureLoader incorrectly.
The texture loader's load() method takes two arguments;
- the URL of the image to be loaded, and
- a callback
The callback is invoked when the texture has loaded and is ready for use.
You should restructure your code so that the texture returned in the load() methods callback can be applied to your object.
To illustrate this, a quick solution to get textures loading and displaying on your object might look like this:
var loader = new THREE.OBJLoader( manager );
// Use a callback, in a similar way to your THREE.OBJLoader
new THREE.TextureLoader( manager ).load( './1.jpg' , function(texture) {
loader.load( './3.obj', function ( object ) {
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
// Assign the loaded texture from the enclosing THREE.TextureLoader to the child material
child.material.map = texture;
}
});
object.position.y = -0.01;
object.position.x = 0;
object.position.z = 0;
scene.add( object );
}, onProgress, onError );
})

How can I make three.js update a scene after adding an object or group of objects?

I am fairly new to three.js and have a problem I can't readily find an answer for.
Here is a codepen that should sum up the situation: http://codepen.io/anon/pen/PPYPzO
var container, stats;
var camera, controls, scene, renderer, raycaster, mouse;
init();
animate();
add_world();
var indie_render = true;
for(var j = 0; j < 20; j++){
add_objects(20);
indie_render = !indie_render;
console.log("adding more objects...");
if(!indie_render){render();}
}
function add_world(){
var geometry = new THREE.BoxGeometry( 1000, 1000, 1000);
var mesh = new THREE.MeshBasicMaterial( {color: 0xf5f5dc, wireframe: false, opacity: 0.2, transparent:true } );
var world = new THREE.Mesh( geometry, mesh );
scene.add( world );
render();
}
function add_objects(num, indiv){
var geometry = new THREE.SphereGeometry( 5, 32,32 );
var material = new THREE.MeshBasicMaterial( { shading: THREE.FlatShading } );
material.color.setRGB( Math.random(), Math.random(), Math.random() );
for ( var i = 0; i < num; i ++ ) {
var mesh = new THREE.Mesh( geometry, material );
mesh.position.x = ( Math.random() - 0.5 ) * 1000;
mesh.position.y = ( Math.random() - 0.5 ) * 1000;
mesh.position.z = ( Math.random() - 0.5 ) * 1000;
mesh.updateMatrix();
mesh.matrixAutoUpdate = false;
scene.add( mesh );
if(indie_render){
console.log("individual render");
render();
}
}
}
function init() {
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 2000 );
camera.position.set(500, 500, -1000);
camera.up.set( 0, 1, 0 );
camera.lookAt(500,500,500);
controls = new THREE.OrbitControls( camera );
controls.addEventListener( 'change', render );
//world
scene = new THREE.Scene();
// lights
light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 1, 1, 1 );
scene.add( light );
light = new THREE.DirectionalLight( 0x002288 );
light.position.set( -1, -1, -1 );
scene.add( light );
light = new THREE.AmbientLight( 0x222222 );
scene.add( light );
renderer = new THREE.WebGLRenderer( { antialias: false } );
renderer.setClearColor( 0x000000, 1 );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.sortObjects = false;
container = document.getElementById( 'container' );
container.appendChild( renderer.domElement );
mouse = new THREE.Vector2();
raycaster = new THREE.Raycaster();
container.addEventListener( 'mousemove', onMouseMove, false );
container.addEventListener( 'mousedown', onMouseDown, false );
window.addEventListener( 'resize', onWindowResize, false );
}
function animate() {
requestAnimationFrame( animate );
controls.update();
}
function render() {
renderer.render( scene, camera );
}
function onMouseMove( e ) {
mouse.x = ( e.clientX / renderer.domElement.width ) * 2 - 1;
mouse.y = - ( e.clientY / renderer.domElement.height ) * 2 + 1;
}
function onMouseDown( e ) {
mouse.x = ( e.clientX / renderer.domElement.width ) * 2 - 1;
mouse.y = - ( e.clientY / renderer.domElement.height ) * 2 + 1;
if(e.button == 2){ //right button
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( scene.children, true );
if ( intersects.length > 0 ) {
var geometry = new THREE.SphereGeometry( 5, 32,32 );
var material = new THREE.MeshBasicMaterial( { color:0xff0000, shading: THREE.FlatShading } );
var mesh = new THREE.Mesh( geometry, material );
mesh.position.set(intersects[0].point.x, intersects[0].point.y, intersects[0].point.z);
scene.add(mesh);
render();
}
}
}
In this demo, I init() and animate() a blank scene, and then add a translucent cube, following what seems to be convention. Then I add groups of spheres to the scene in a nested for loop, randomly placing the spheres inside the cube and making a render() call after every scene.add() call.
Currently, the scene adds all the spheres and only then is visible to the user, even though I can add individual objects after the for-loop objects are added (by right-clicking on the cube). I need for the user to be able to watch as spheres are added, rather than waiting for the whole thing to be done.
I realize this may not be the most efficient way to render the scene, but it would be quite helpful if, for example, the info on the objects to be animated is arriving asynchronously from a server. Does anyone have a suggestion?
Thanks
1) First: move call render() to animate:
function animate() {
requestAnimationFrame( animate );
render();
controls.update();
}
2) Call add_objects asynchronously: setTimeout( add_objects, 0, 20 );
http://codepen.io/anon/pen/bVbEEP

Three Js Object3D Button Group Detect Single Object Click While Mouse Movement Causes Object3D Button Group Zoomi

I am trying to detect a cube click in an Object3D group of Cubes. I have viewed, and tried to incorporate the examples and tutorials found at:
http://mrdoob.github.com/three.js/examples/webgl_interactive_cubes.html
and
http://mrdoob.github.com/three.js/examples/canvas_interactive_cubes.html
Also, I have consulted the posts on this site at:
Three.js - how to detect what shape was selected? after drag
and
how to Get CLICKED element in THREE.js
But for some reason, it's still not working. Can anyone please tell me what I'm doing wrong?
Here is my code, thanks:
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Double Stamp It No Erasies</title>
<style>
html {
background: url(Images/ComicBookExplosionBackground.jpg) no-repeat center center fixed;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}
body {
}
</style>
<script src="ThreeJs/build/three.min.js"></script>
</head>
<body onLoad="onLoad();" style="">
<div id="container" style="width:100%; height:100%; position:absolute;"></div>
<script>
var container, ButtonsCamera, ButtonsScene, ButtonsRenderer, ButtonsGeometry, ButtonsGroup;
var mouseX = 0, mouseY = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
/****************************** CLICK START **********************************/
var mouse = { x: 0, y: 0 }, projector, INTERSECTED;
var objects = [];
/****************************** CLICK END **********************************/
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
//document.addEventListener( 'mousedown', onDocumentMouseDown, false );
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
ButtonsCamera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 10000 );
ButtonsCamera.position.z = 500;
ButtonsScene = new THREE.Scene();
ButtonsScene.fog = new THREE.Fog( 0xffffff, 1, 10000 );
/*************************** STACKOVERFLOW 1ST ANSWER START **********************************/
var ButtonsGeometry = new THREE.CubeGeometry( 100, 100, 100 );
var ButtonsMaterial = new THREE.MeshFaceMaterial( [
new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'Images/Twitter.jpg' ) } ),
new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'Images/Twitter.jpg' ) } ),
new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'Images/Twitter.jpg' ) } ),
new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'Images/Twitter.jpg' ) } ),
new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'Images/Twitter.jpg' ) } ),
new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'Images/Twitter.jpg' ) } )] );
/*************************** STACKOVERFLOW 1ST ANSWER END **********************************/
ButtonsGroup = new THREE.Object3D();
for ( var i = 0; i < 100; i ++ ) {
var ButtonsMesh;
if(i == 0)
{
ButtonsMesh = new THREE.Mesh( ButtonsGeometry, ButtonsMaterial );
}
else
{
ButtonsMesh = new THREE.Mesh( ButtonsGeometry, ButtonsMaterial );
}
ButtonsMesh.position.x = Math.random() * 2000 - 1000;
ButtonsMesh.position.y = Math.random() * 2000 - 1000;
ButtonsMesh.position.z = Math.random() * 2000 - 1000;
ButtonsMesh.rotation.x = Math.random() * 360 * ( Math.PI / 180 );
ButtonsMesh.rotation.y = Math.random() * 360 * ( Math.PI / 180 );
ButtonsMesh.matrixAutoUpdate = false;
ButtonsMesh.updateMatrix();
ButtonsGroup.add( ButtonsMesh );
}
ButtonsScene.add( ButtonsGroup );
/****************************** CLICK START **********************************/
objects.push( ButtonsMesh );
projector = new THREE.Projector();
/****************************** CLICK END **********************************/
ButtonsRenderer = new THREE.WebGLRenderer();
ButtonsRenderer.setSize( window.innerWidth, window.innerHeight );
ButtonsRenderer.sortObjects = false;
container.appendChild( ButtonsRenderer.domElement );
window.addEventListener( 'resize', onWindowResize, false );
/****************************** CLICK START **********************************/
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
/****************************** CLICK END **********************************/
}
/****************************** CLICK START **********************************/
function onDocumentMouseDown( event ) {
//alert('clicky');
event.preventDefault();
var vector = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
projector.unprojectVector( vector, ButtonsCamera );
var raycaster = new THREE.Raycaster( ButtonsCamera.position, vector.subSelf( ButtonsCamera.position ).normalize() );
var intersects = raycaster.intersectObjects( objects);
if ( intersects.length > 0 ) {
intersects[ 0 ].object.material.color.setHex( Math.random() * 0xffffff );
var particle = new THREE.Particle( particleMaterial );
particle.position = intersects[ 0 ].point;
particle.scale.x = particle.scale.y = 8;
ButtonsScene.add( particle );
}
/*
// Parse all the faces
for ( var i in intersects ) {
intersects[ i ].face.material[ 0 ].color.setHex( Math.random() * 0xffffff | 0x80000000 );
}
*/
}
/****************************** CLICK END **********************************/
function onWindowResize() {
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;
ButtonsCamera.aspect = window.innerWidth / window.innerHeight;
ButtonsCamera.updateProjectionMatrix();
ButtonsRenderer.setSize( window.innerWidth, window.innerHeight );
}
function onDocumentMouseMove(event) {
mouseX = ( event.clientX - windowHalfX ) * 10;
mouseY = ( event.clientY - windowHalfY ) * 10;
}
function animate() {
requestAnimationFrame( animate );
render();
ButtonsStats.update();
}
/****************************** CLICK START **********************************/
var radius = 100;
var theta = 0;
/****************************** CLICK END **********************************/
function render() {
var ButtonsTime = Date.now() * 0.001;
var rx = Math.sin( ButtonsTime * 0.7 ) * 0.5,
ry = Math.sin( ButtonsTime * 0.3 ) * 0.5,
rz = Math.sin( ButtonsTime * 0.2 ) * 0.5;
ButtonsCamera.position.x += ( mouseX - ButtonsCamera.position.x ) * .05;
ButtonsCamera.position.y += ( - mouseY - ButtonsCamera.position.y ) * .05;
ButtonsCamera.lookAt( ButtonsScene.position );
ButtonsGroup.rotation.x = rx;
ButtonsGroup.rotation.y = ry;
ButtonsGroup.rotation.z = rz;
ButtonsRenderer.render( ButtonsScene, ButtonsCamera );
}
</script>
</body>
</html>
Hey I hope I am not too late but anyway the solution to your problem is
a misplaced statement and a deprecated method.
You only have one object in your objects array that is why when you click on a
random box the raycaster is unlikely to detect an intersection.
Move the array push call into the for loop in order to add every object into
the array instead of the last object created.
for (var i = 0; i < 100; i++) {
var ButtonsMesh;
if (i == 0)
{
ButtonsMesh = new THREE.Mesh(ButtonsGeometry, ButtonsMaterial);
}
else
{
ButtonsMesh = new THREE.Mesh(ButtonsGeometry, ButtonsMaterial);
}
ButtonsMesh.position.x = Math.random() * 2000 - 1000;
ButtonsMesh.position.y = Math.random() * 2000 - 1000;
ButtonsMesh.position.z = Math.random() * 2000 - 1000;
ButtonsMesh.rotation.x = Math.random() * 360 * (Math.PI / 180);
ButtonsMesh.rotation.y = Math.random() * 360 * (Math.PI / 180);
ButtonsMesh.matrixAutoUpdate = false;
ButtonsMesh.updateMatrix();
ButtonsGroup.add(ButtonsMesh);
objects.push(ButtonsMesh);
}
The second problem is that newer versions of THREE don't use subSelf(),
it is replaced by sub(). So make the change to the Raycaster definition.
var raycaster = new THREE.Raycaster(ButtonsCamera.position,
vector.sub(ButtonsCamera.position).normalize());
That should solve your problems but there are more errors in your code
but they are all trivial.
I hope this helps and here is a working version: http://jsbin.com/uhihoq/1/edit
Peace!

Resources