Three.js - Camera collision with scene - three.js

This is my first question in StackOverflow, but I've been browsing it for some years now, so I kindly ask you to bear with me. :)
I've been experimenting with Three.js to create a 3D world, and everything looked fine until I needed to control the camera. Since I'm using this lib to avoid having to do matricial calculations myself I found and added TrackballControls to my code aswell. It worked fine but then my camera could pass through the 3D shapes, and also below terrain. Unfortunately, although the movement is exactly what I needed, it didn't serve the purpose of allowing camera to respect collision.
My scene is simply the ground (thin BoxGeometry) and a cube (normal-sized BoxGeometry), and a rotating sphere that shares directionalLight position for a "sun light" effect. Some people here suggested adding Physijs to the code and simulate() physics within the scene, and adding a BoxMesh to the camera to make the physics apply to it aswell, but it simply didn't work (scene turned blank).
My working code so far (without Physijs) is:
window.onload = function() {
var renderer, scene, camera, ground, box, sphere, ambient_light, sun_light, controls;
var angle = 0;
var clock = new THREE.Clock();
init();
render();
function init(){
// Create renderer and add it to the page
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0xffffff );
renderer.shadowMapEnabled = true;
document.body.appendChild( renderer.domElement );
// Create a scene to hold our awesome 3D world
scene = new THREE.Scene();
/*** 3D WORLD ***/
// Objects
ground = new THREE.Mesh(
new THREE.BoxGeometry(50, 1, 50),
new THREE.MeshLambertMaterial({ color: 0x33CC33 }),
0 // mass
);
ground.receiveShadow = true;
scene.add( ground );
box = new THREE.Mesh(
new THREE.BoxGeometry( 10, 10, 10 ),
new THREE.MeshLambertMaterial({ color: 0xDD3344 })
);
box.position.y = 5;
box.castShadow = true;
scene.add( box );
sphere = new THREE.Mesh(
new THREE.SphereGeometry( 3, 32, 32 ),
new THREE.MeshBasicMaterial({ color: 0xFFBB00 })
);
sphere.position.set( 1, 15.5, 5 );
scene.add( sphere );
// Light
ambient_light = new THREE.AmbientLight( 0x333333 );
ambient_light.mass = 0;
scene.add( ambient_light );
sun_light = new THREE.DirectionalLight( 0xBBBBBB );
sun_light.position.set( 1, 15.5, 5 );
sun_light.castShadow = true;
sun_light.shadowCameraNear = 1;
sun_light.shadowCameraFar = 100;
sun_light.shadowCameraLeft = -50;
sun_light.shadowCameraRight = 50;
sun_light.shadowCameraTop = -50;
sun_light.shadowCameraBottom = 50;
sun_light.shadowBias = -.01;
scene.add( sun_light );
// Create a camera
camera = new THREE.PerspectiveCamera(
45, // FOV
window.innerWidth / window.innerHeight, // Aspect Ratio
1, // Near plane
1000 // Far plane
);
camera.position.set( 30, 30, 30 ); // Position camera
camera.lookAt( box.position ); // Look at the scene origin
scene.add(camera);
// After swapping THREE.Mesh to Physijs.BoxMesh, this is where I'd attach a BoxMesh to the camera
window.addEventListener( 'resize', onWindowResize, false );
controls = new THREE.TrackballControls( camera );
controls.rotateSpeed = 4.0;
controls.panSpeed = 0.3;
controls.staticMoving = true; // No sliding after-effects
}
function render() {
// use requestAnimationFrame to create a render loop
angle += .007;
var oscillateZ = Math.sin(angle * (Math.PI*4));
var oscillateX = -Math.cos(angle * (Math.PI*4));
//console.log(oscillateZ);
sphere.position.setZ( sphere.position.z + oscillateZ );
sphere.position.setX( sphere.position.x + oscillateX );
sun_light.position.setZ( sun_light.position.z + oscillateZ );
sun_light.position.setX( sun_light.position.x + oscillateX );
requestAnimationFrame( render );
controls.update();
renderer.render( scene, camera );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
};
Can you guys enlighten me? Thank you for your time!
#Edit
Physijs attempt

Related

Clickable element with ThreeJS PerspectiveCamera

I’ve created a panoramic image with PerspectiveCamera and all went well. I’ve also managed to add controls.
Now I want to place a clickable element (link/button/whatever) in the scene but I have no clue how to add and position the clickable element…every tip/suggestion is highly appreciated! Here is my code sofar:
function init() {
const container = document.getElementById( 'three-image' );
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 1100 );
scene = new THREE.Scene();
const geometry = new THREE.SphereGeometry( 500, 60, 40 );
// invert the geometry on the x-axis so that all of the faces point inward
geometry.scale( - 1, 1, 1 );
//let imageLocation = <?php echo $block->getPanoramaImages();?>;
let imageLocation = '/three/luifel.jpg';
//alert(imageLocation)
const texture = new THREE.TextureLoader().load(imageLocation);
const material = new THREE.MeshBasicMaterial( { map: texture } );
const mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
container.style.touchAction = 'none';
container.addEventListener( 'pointerdown', onPointerDown );
document.addEventListener( 'wheel', onDocumentMouseWheel );
window.addEventListener( 'resize', onWindowResize );
}
If you're looking to attach an html element to a 3d element, you can project a 3d position to 2d and use that.
var vector = new THREE.Vector3(100,0,0); // some point
vector.project(camera);
var widthHalf = window.innerWidth / 2;
var heightHalf = window.innerHeight / 2;
vector.x = vector.x * widthHalf + widthHalf;
vector.y = -(vector.y * heightHalf) + heightHalf;
vector.z = 0;
If you want to attach to an object, use the objects position in world space:
vector.setFromMatrixPosition(obj.matrixWorld);
vector.project(camera);
...etc
EDIT:
I've created an example here:
https://codepen.io/flatworldstudio/pen/LYbgvgY

Centering pivot point in three.js with OrbitControls autorotate

I'm loading a .glb model into three.js, and while I have it rotating automatically using OrbitControls, I'm not able to see how to change the pivot point so the rotating model is centered.
I've seen a lot of questions on setting boxes or pivot points with rotation, but not with OrbitControls and autorotate. Is there a way for me to center the imported model using autorotate as per my code below?
var scene = new THREE.Scene();
// Load Camera Perspective
var camera = new THREE.PerspectiveCamera( 25, window.innerWidth / window.innerHeight, 1, 8000 );
camera.position.set( 200, 100, 0 );
// Load a Renderer
var renderer = new THREE.WebGLRenderer({ alpha: false });
renderer.setClearColor( 0xC5C5C3 );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Load the Orbitcontroller
var controls = new THREE.OrbitControls( camera, renderer.domElement );
camera.position.set( 60, 20, 100 );
controls.update();
controls.autoRotate = true;
controls.minDistance = 700;
controls.maxDistance = 2000;
//controls.update();
// Load Light
var ambientLight = new THREE.AmbientLight( 0xcccccc );
scene.add( ambientLight );
var directionalLight = new THREE.DirectionalLight( 0xffffff );
directionalLight.position.set( 0, 1, 1 ).normalize();
scene.add( directionalLight );
// glTf 2.0 Loader
var loader = new THREE.GLTFLoader();
loader.load( 'BTR.glb', function ( gltf ) {
var object = gltf.scene;
gltf.scene.scale.set( 1, 1, 1 );
gltf.scene.position.x = 0; //Position (x = right+ left-)
gltf.scene.position.y = 0; //Position (y = up+, down-)
gltf.scene.position.z = 0; //Position (z = front +, back-)
scene.add( gltf.scene );
});
function animate() {
// required if controls.enableDamping or controls.autoRotate are set to true
controls.update();
render();
requestAnimationFrame( animate );
}
function render() {
renderer.render( scene, camera );
}
render();
animate();
I think this issue can be solved by setting Controls.target (the focus point) to the center point of your glTF asset. You should be able to do this like so:
var aabb = new THREE.Box3().setFromObject( gltf.scene );
aabb.getCenter( controls.target );
controls.update();
three.js R107
Correct way to set target.
var aabb = new THREE.Box3().setFromObject( gltf.scene );
controls.target.set(aabb.getCenter());
controls.update();
it should take (aab.getCenter()), as it returns a vector3 with 3 axis values. But I found this didn't work for me, so I used the following
let aabb = new THREE.Box3().setFromObject( gltf.scene );
let aabbc = aabb.getCenter()
controls.target.set(aabbc.x, aabbc.y, aabbc.z);
controls.update();
just separating into 3 values, if you ever get stuck just console.log(whateveryourstuckwith) and read through the methods and variables and stuff, really helped me understand Three.js more

How do I manipulate shadows in Three.js without editing the underlying mesh?

I'm working on an app that should allow users to manipulate 3D objects in the scene and observe how their changes affect the ground shadow:
In this scene, the yellow cylinder casts a shadow on a white plane with the middle of the cylinder contained in the green cube. What I would like to happen is for the cube to remove the middle of the shadow, like so:
Obviosly, my first thought was to subtract the green cube volume from the yellow cylinder volume and after a bit of googling I found CSG.js. Unfortunately, CSG.js is too slow for the actual model that I'm going to use, which will going to have at least 15k vertices.
I started digging into the Three.js source and reading about shadow maps to understand how shadows are produced, but my shader-fu is not strong enough yet to fully grasp how I can tweak shadow rendering.
How can I achieve this "shadow subtraction" effect?
var camera, scene, renderer;
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.z = 500;
camera.position.y = 100;
camera.lookAt(scene.position);
var ambient = new THREE.AmbientLight(0x909090);
scene.add(ambient);
var directionalLight = new THREE.DirectionalLight( 0xffffff, 1.0 );
directionalLight.position.set( -300, 300, 0 );
directionalLight.castShadow = true;
directionalLight.shadow.camera.near = 10;
directionalLight.shadow.camera.far = 2000;
directionalLight.shadow.camera.right = 350;
directionalLight.shadow.camera.left = -350;
directionalLight.shadow.camera.top = 350;
directionalLight.shadow.camera.bottom = -350;
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
scene.add( directionalLight );
//var lightHelper = new THREE.CameraHelper(directionalLight.shadow.camera);
//scene.add(lightHelper);
var geometry = new THREE.CylinderGeometry( 50, 50, 400, 32 );
var material = new THREE.MeshPhongMaterial( {color: 0xffff00} );
var cylinder = new THREE.Mesh( geometry, material );
cylinder.castShadow = true;
scene.add( cylinder );
var geometry = new THREE.BoxGeometry( 110, 110, 110 );
var material = new THREE.MeshPhongMaterial( {color: 0x00ff00} );
var cube = new THREE.Mesh( geometry, material );
cube.castShadow = true;
scene.add( cube );
var geometry = new THREE.PlaneGeometry( 3000, 3000, 32 );
var material = new THREE.MeshPhongMaterial( {color: 0xffffff, side: THREE.DoubleSide} );
var plane = new THREE.Mesh( geometry, material );
plane.lookAt(new THREE.Vector3(0, 1, 0));
plane.position.y = -200;
plane.receiveShadow = true;
scene.add( plane );
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.BasicShadowMap;
document.body.appendChild( renderer.domElement );
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera );
}
jsFiddle
Update:
What about a more complicated scene? Is it possible for the shadow from the red cylinder to be unaffected (you can see it being cut in half with cube.customDepthMaterial = new THREE.MeshBasicMaterial({ depthTest: false}))?
Updated jsFiddle
You can subtract an object's shadow from the rest of scene by setting the object's .customDepthMaterial property like so:
var cube = new THREE.Mesh( geometry, material );
cube.castShadow = true;
cube.receiveShadow = false;
// The secret sauce
cube.customDepthMaterial =
new THREE.MeshBasicMaterial({ depthTest: false});
scene.add( cube );
jsFiddle
No shader-fu required.
Why This Works
When the shadow map is rendered, each object's depth material ( .customDepthMaterial or the default ) is used to render the scene from the light's perspective. The depth material's resulting render represents the object's depth from the camera packed as RGBA. Since THREE.MeshBasicMaterial defaults to { color: 0xffffff, opacity: 1 }, it will return the maximum depth which makes the object further than the shadow camera's far.
I disabled depthTest because in your desired result screenshot you clipped the area where the cube's given the cylinder wasn't there. Disabling depthTest means that parts of the cube which are blocked by the cylinder will still cut out the shadow, giving you your desired result.
Documentation
There unfortunately is no documentation on .customDepthMaterial yet but I did find an official example where it is used.
Updated Answer:
To allow an object's shadow to always show:
You can use the same trick as above just setting the material's color and opacity to 0
Make sure it's added to the scene after the 'subtractive shadow' object. This way the additive shadow will win out even though they both have depthTest disabled.
updated jsFiddle
If you have anything more complicated, it will be up to you to figure out a way to manage the order of the shadow rendering.
Tested in r77

three.js shadow map shows on back side issue. Is there a work around?

I have an issue with shadow map showing on the back side of an object, I have been fighting with that for almost two days now, thinking I was surely doing something wrong.
Then I wrote this jsFiddle : http://jsfiddle.net/gui2one/ec1snfee/
code below :
// shadow map issue
var light ,mesh, renderer, scene, camera, controls;
var clock = new THREE.Clock({autoStart:true});
init();
animate();
function init() {
// renderer
renderer = new THREE.WebGLRenderer();
renderer.shadowMapEnabled = true;
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
// scene
scene = new THREE.Scene();
// camera
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set( -58, 10, 29 );
// controls
controls = new THREE.OrbitControls( camera );
// ambient
scene.add( new THREE.AmbientLight( 0x222222 ) );
// light
light = new THREE.SpotLight();
light.position.set( 20, 0, 0 );
//light.target.position.set(1000,0,0);
light.castShadow = true;
light.shadowBias = -0.001;
light.shadowCameraNear = 1;
light.shadowCameraFar = 100;
light.shadowMapWidth = 2048;
light.shadowMapHeight = 2048;
light.shadowCameraVisible = true;
scene.add( light );
// axes
scene.add( new THREE.AxisHelper( 5 ) );
var materialTest = new THREE.MeshPhongMaterial();
var planetTestGeo = new THREE.SphereGeometry(5);
var planetTest = new THREE.Mesh(planetTestGeo, materialTest);
planetTest.castShadow = true;
planetTest.receiveShadow = true;
scene.add(planetTest);
planetTest.position.set(-30,0,0);
var moonTestGeo = new THREE.SphereGeometry(1);
var moonTest = new THREE.Mesh(moonTestGeo, materialTest);
moonTest.castShadow = true;
moonTest.receiveShadow = true;
scene.add(moonTest);
moonTest.position.set(planetTest.position.x+10 ,planetTest.position.y-0.5 ,planetTest.position.z);
camera.lookAt(moonTest.position);
}
function animate() {
requestAnimationFrame( animate );
//controls.update();
renderer.render( scene, camera );
}
and then I noticed this question ( more than 2 years ago )
THREE.JS Shadow on opposite side of light.
Is there a work around that now ?
I am trying to make an animated solar system. And I can't use different objects , or different materials to solve this problem, because planets and moons evidently always rotate on themselves and around each other.
Easy fix:
light.shadowBias = 0.001;
instead of
light.shadowBias = -0.001;
I've seen examples on the web using negative bias. I'm not sure if that is correct behaviour.
Good luck!
UPDATE
You will see some shadow acne on the light terminal on the sphere. This appears to be an issue with three.js shadow mapping methods and is due for an update AFAIK.

Why does the initial rotation of this cube not take effect on first render call?

I want to render a still image of a rotated cube. The following shows the cube, but it is not rotated. If I call that last line renderer.render(scene, camera); twice in a row, it renders the rotated cube as I would expect with only a single call. Can someone explain this to me? Seems odd I would have to do this to render a static 3D scene.
/* globals THREE, window, document */
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.z = 5;
var renderer = new THREE.WebGLRenderer({ antialias: true });
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: 'red'
});
var cube = new THREE.Mesh( geometry, material );
cube.rotation.x = 40 * Math.PI/180;
cube.rotation.y = 40 * Math.PI/180;
cube.position.z = 1;
var box = new THREE.BoxHelper(cube);
scene.add( cube );
scene.add( box );
renderer.render(scene, camera);
Adding the the cube to the scene after the box solves the issue.
scene.add( box );
scene.add( cube );
jsfiddle
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 500;
scene.add(camera);
renderer = new THREE.CanvasRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.CubeGeometry(100,100,100);
var material = new THREE.MeshBasicMaterial({
color: 'red'
});
var cube = new THREE.Mesh( geometry, material );
cube.rotation.x = 40 * Math.PI/180;
cube.rotation.y = 40 * Math.PI/180;
cube.position.z = 1;
var box = new THREE.BoxHelper(cube);
scene.add( box );
scene.add( cube );
renderer.render(scene, camera);

Resources