I want to add and remove a geometry with its wireframe together. I can use raycaster to pick object from the scene but it is hard to pick a wireframe.
One way I can think of is creating a group of object and its wireframe, when the raycaster intersect with the object (e.g. obj.geometry.type == "BoxGeometry"), find its parent and delete the parent. However, the wireframe has to be some geometry that can be added as a child. I am using a Boxhelper to create a wireframe for a cube, which should be added to the scene directly, not to be added as a child to any object. What is a good way to solve this?
Thanks.
I am not sure if i understand what you are after exactly, but maybe ou can create a clone of your object, and render that one as a wireframe. The clone wireframe object can then be added as a child to the original object. So when the original object is picked, you can remove it from the scene, and then the wireframe object will be removed aswell.
Clone your object and change its material to wireframe:
var wireframe = cube.clone();
wireframe.material = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true});
cube.add( wireframe ); // adding wireframe as child to the cube
When object is picked: check whether it is a cubegeometry (if you only want it to work with cubes) and check whether its material is wireframe (if you dont want to be able to remove the wireframe without removing the cube aswell)
if (pickedObject.geometry.type == "BoxGeometry" &&
!pickedObject.material["wireframe"]){
pickedObject.parent.remove(pickedObject); //this will remove object from
// scene if it has no parents
}
Working example:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var geometry = new THREE.BoxGeometry( 1, 1, 1 );
var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
var cube = new THREE.Mesh( geometry, material );
scene.add( cube );
var wireframe = cube.clone();
wireframe.material = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true});
cube.add( wireframe );
//picking stuff
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onMouseClick( event ) {
// calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
// update the picking ray with the camera and mouse position
raycaster.setFromCamera( mouse, camera );
// calculate objects intersecting the picking ray
var intersects = raycaster.intersectObjects( scene.children );
for ( var i = 0; i < intersects.length; i++ ) {
if (intersects[ i ].object.geometry.type == "BoxGeometry" &&
!intersects[ i ].object.material["wireframe"]){
intersects[ i ].object.parent.remove(intersects[ i ].object);
}
}
}
camera.position.z = 5;
var render = function () {
requestAnimationFrame( render );
cube.rotation.x += 0.1;
cube.rotation.y += 0.1;
renderer.render(scene, camera);
};
window.addEventListener( 'mouseup', onMouseClick, false );
render();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r71/three.min.js"></script>
There is also a way of doing what you require with the Three.EdgesHelper instead of cloning, as exemplified in this fiddle. This shows the wireframes without the diagonals.
Even if you don't want to use the Three.EdgesHelper, I noticed that while implementing the clone solution above, it didn't completely show the wireframe, because it was slightly hidden.
To avoid this hiding I added the following code to the constructor of material, which offsets the original shape slightly so that the wireframe can be completely seen:
var material = new THREE.MeshLambertMaterial({ polygonOffset: true, polygonOffsetFactor: 1, polygonOffsetUnits: 1 })
I hope this is somehow useful.
Related
I'm attempting to use the Magnify-3d package after using an EffectComposer to apply a bokeh effect to a scene. Magnify-3D takes in a renderer, so I cannot simply pass in the EffectComposer after applying passes.
The intended result would work like a microscope, with the ability to independently adjust the plane of focus by adjusting the BokehPass effect, and the magnification using Magnify-3d.
The only way I've gotten this to work so far has been like so:
Create a temporary scene containing actual scene I want to render.
Create an EffectComposer that applies BokehPass and renders the temporary scene to renderTarget.
Create a PlaneBufferGeometry and a mesh that maps renderTarget.texture (the output of the temporary scene) to the plane, and add this to a new scene.
Render the new scene that effectively is just a camera staring at a mesh of the original scene.
Pass the renderer into magnify3d, which creates a lens effect around the scene I want to render.
This seems to work OK (though there appears to be some fuzziness and distortion of the original scene I'm still working out), but I began to wonder -- is this really the best way to use something rendered to a RenderTarget? Is there a more direct way than effectively taking a picture of a picture that I can use to take the output of an EffectComposer, feed it back into a renderer so my final scene is rendered in a WebGLRenderer, and pass it into a library? Any other way I can think to do this, the EffectComposer would not be able to run (since Magnify3d takes in a renderer, not an EffectComposer and I need to run the EffectComposer before passing it into a renderer).
Here's a screenshot of the final result
Here's some code below:
// create initial scene
const renderTarget = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight );
const rtScene = new THREE.Scene();
const rtCamera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000000000 );
// add a sample cube to the new scene and move the camera back
const geometry = new THREE.BoxGeometry( 10, 10, 1 );
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
const cube = new THREE.Mesh( geometry, material );
rtScene.add(cube);
rtCamera.position.z = 500;
// initialize renderer
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0xffffff, 1 );
document.body.appendChild( renderer.domElement );
// initialize EffectComposer and apply BokehEffect.
// Note that composer renders its output to RenderTarget.
const composer = new EffectComposer( renderer, renderTarget );
composer.renderToScreen = false;
const renderPass = new RenderPass( rtScene, rtCamera );
composer.addPass( renderPass );
const bokehValues = {
focus: 500,
aperture: .00025,
maxblur: 1,
width: window.innerWidth,
height: window.innerHeight
}
export const bokehPass = new BokehPass( rtScene, rtCamera, bokehValues);
composer.addPass( bokehPass );
// create final scene to render before passing to Magnify3D
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000000000 );
const scene = new THREE.Scene();
camera.position.z = 100;
// make a plane geometry, a mesh that maps renderTarget.texture, and add it to the scene
const planeGeometry = new THREE.PlaneBufferGeometry( window.innerWidth, window.innerHeight );
const planeMaterial = new THREE.MeshBasicMaterial( { map: renderTarget.texture } );
const plane = new THREE.Mesh( planeGeometry, planeMaterial );
scene.add( plane );
function animate() {
// first render temporary scene
renderer.setClearColor( 0xffffff, 1 );
composer.render(); // now composer has rendered to renderTarget
renderer.setClearColor( 0x141414, 1 ); // create a black background for outside of microscope
renderer.render( scene, camera ); // render final scene with meshed plane
// Finally use Magnify3D to render final scene with microscope
magnify3d.render({
renderer: renderer,
pos: {
x: window.innerWidth/2,
y: window.innerHeight/2
},
renderSceneCB: (target) => {
if (target) {
renderer.setRenderTarget(target);
} else {
renderer.setRenderTarget(null);
}
renderer.render(scene, camera);
},
radius: window.innerWidth/5,
exp: 150,
zoom: state.magnification,
});
requestAnimationFrame( animate );
}
I am using the following code to create this 3D transparent cube.
// Create the cube itself
const cubeGeom = new THREE.BoxGeometry( 1, 1, 1 );
const material = new THREE.MeshBasicMaterial( {color: 0x00ff00, opacity:0.4, transparent:true});
const cube = new THREE.Mesh( cubeGeom, material );
// Also add a wireframe to the cube to better see the depth
const _wireframe = new THREE.EdgesGeometry( cubeGeom ); // or WireframeGeometry( geometry )
const wireframe = new THREE.LineSegments( _wireframe);
// Rotate it a little for a better vantage point
cube.rotation.set(0.2, -0.2, -0.1)
wireframe.rotation.set(0.2, -0.2, -0.1)
// add to scene
scene.add( cube )
scene.add( wireframe );
As can been seen, the cube appears as a single volume that is transparent. Instead, I would want to create a hollow cube with 6 transparent faces. Think of a cube made out of 6 transparent and colored window-panes. See this example: my desired result would be example 1 for each of the 6 faces, but now it is like example 2.
Update
I tried to create individual 'window panes'. However the behavior is not as I would expect.
I create individual panes like so:
geometry = new THREE.PlaneGeometry( 1, 1 );
material = new THREE.MeshBasicMaterial( {color: 0x00ff00, side: THREE.DoubleSide, transparent:true, opacity:0.2});
planeX = new THREE.Mesh( geometry, material);
planeY = new THREE.Mesh( geometry, material);
planeZ = new THREE.Mesh( geometry, material);
And then I add all three planes to wireframe.
Then I rotate them a little, so they intersect at different orientations.
const RAD_TO_DEG = Math.PI * 2 / 360;
planeX.rotation.y = RAD_TO_DEG * 90
planeY.rotation.x = RAD_TO_DEG * 90
Now I can see the effect of 'stacking' the panes on top of each other, however it is not as it should be.
I would instead expect something like this based on real physics (made with terrible paint-skills). That is, the color depends on the number of overlapping panes.
EDIT
When transparent panes overlap from the viewing direciton, transparancy appears to work perfectly. However, when the panes intersect it breaks.
Here I have copied the snipped provided by #Anye and added one.rotation.y = Math.PI * 0.5 and commented out two.position.set(0.5, 0.5, 0.5); so that the panes intersect.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var cube = new THREE.Group();
one = new Pane();
two = new Pane();
one.rotation.y = Math.PI * 0.5
one.position.z = 0.2;
// two.position.set(0.5, 0.5, 0.5);
cube.add(one);
cube.add(two);
cube.rotation.set(Math.PI / 4, Math.PI / 4, Math.PI / 4);
scene.add(cube);
function Pane() {
let geometry = new THREE.PlaneGeometry(1, 1);
let material = new THREE.MeshBasicMaterial({color:0x00ff00, transparent: true, opacity: 0.4});
let mesh = new THREE.Mesh(geometry, material);
return mesh;
}
camera.position.z = 2;
var animate = function () {
requestAnimationFrame( animate );
renderer.render(scene, camera);
};
animate();
body {
margin: 0;
overflow: hidden;
}
canvas {
width: 640px;
height: 360px;
}
<html>
<head>
<title>Demo</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"></script>
</body>
</html>
EDIT
The snipped looks pretty good; it clearly shows a different color where the panes overlap. However, it does not show this everywhere. See this image. The left is what the snippet generates, the right is what it should look like. Only the portion of overlap that is in front of the intersection shows the discoloration, while the section behind the intersection should, but does not show discoloration.
You might want to take a look at CSG, Constructive Solid Geometry. With CSG, you can create a hole in your original cube using a boolean. To start, you could take a look at this quick tutorial. Below are some examples of what you can do with CSG.
var cube = new CSG.cube();
var sphere = CSG.sphere({radius: 1.3, stacks: 16});
var geometry = cube.subtract(sphere);
=>
CSG, though, has some limitations, since it isn't made specifically for three.js. A cheap alternative would be to create six individual translucent panes, and format them to create a cube. Then you could group them:
var group = new THREE.Group();
group.add(pane1);
group.add(pane2);
group.add(pane3);
group.add(pane4);
group.add(pane5);
group.add(pane6);
Update
Something may be wrong with your code, which is why it isn't shading accordingly for you. See this minimal example, which shows how the panes shade appropriately based on overlaps.
Update 2
I updated the snippet so the 2 panes aren't touching at all... I am still able to see the shading. Maybe if you were to try to reproduce this example?
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var cube = new THREE.Group();
one = new Pane();
two = new Pane();
one.rotation.y = Math.PI * 0.5;
one.position.z = 0.2;
cube.add(one);
cube.add(two);
cube.rotation.set(Math.PI / 4, Math.PI / 4, Math.PI / 4);
scene.add(cube);
function Pane() {
let geometry = new THREE.PlaneGeometry(1, 1);
let material = new THREE.MeshBasicMaterial({color:0x00ff00, transparent: true, opacity: 0.4});
material.depthWrite = false
let mesh = new THREE.Mesh(geometry, material);
return mesh;
}
camera.position.z = 2;
var animate = function () {
requestAnimationFrame( animate );
renderer.render(scene, camera);
};
animate();
body {
margin: 0;
overflow: hidden;
}
canvas {
width: 640px;
height: 360px;
}
<html>
<head>
<title>Demo</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"></script>
</body>
</html>
Update 3
Below is a screenshot of what I see in your snippet... Seems to be working fine...
You're experiencing one of my first head-scratchers:
ShaderMaterial transparency
As the answer to that question states, the three.js transparency system performs order-dependent transparency. Normally, it will take whichever object is closest to the camera (by mesh position), but because all of your planes are centered at the same point, there is no winner, so you get some strange transparency effects.
If you move the plane meshes out to form the actual sides of the box, then you should see the effect you're looking for. But that won't be the end of strange transparency effects, And you would need to implement your own Order-Independent Transparency (or find an extension library that does it for you) to achieve more physically-accurate transparency effects.
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
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
I'm trying add subdivisions to a sphere like this:
http://stemkoski.github.com/Three.js/Subdivision-Cube.html
Here's my code: http://jsfiddle.net/mdrorum/HvFLw/
<script src="http://mrdoob.github.com/three.js/build/three.js"></script>
<script type="text/javascript">
var camera, scene, renderer;
var geometry, material, mesh;
var smooth, subdiv, modifier;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 1000;
scene = new THREE.Scene();
renderer = new THREE.CanvasRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
geometry = new THREE.SphereGeometry( 200 );
material = new THREE.MeshBasicMaterial( { color: 0x00ff00, wireframe: true } );
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
var smooth = mesh.clone();
var subdiv = 3;
var modifier = new THREE.SubdivisionModifier( subdiv );
//modifier.modify( smooth );
}
function animate() {
requestAnimationFrame( animate );
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.02;
renderer.render( scene, camera );
}
</script>
This works fine, but uncomment: //modifier.modify( smooth );
Nothing happens. :(
How I can add subdivisions?
Here you can find a good tutorial with a working demo. Citing the author:
// First we want to clone our original geometry.
// Just in case we want to get the low poly version back.
var smooth = THREE.GeometryUtils.clone( geometry );
// Next, we need to merge vertices to clean up any unwanted vertex.
smooth.mergeVertices();
// Create a new instance of the modifier and pass the number of divisions.
var divisions = 3;
var modifier = new THREE.SubdivisionModifier(divisions);
// Apply the modifier to our cloned geometry.
modifier.modify( smooth );
// Finally, add our new detailed geometry to a mesh object and add it to our scene.
var mesh = new THREE.Mesh( smooth, new THREE.MeshPhongMaterial( { color: 0x222222 } ) );
scene.add( mesh );
You are trying to modify a mesh. You need to modify a geometry.
modifier.modify( geometry );