Hello recently I started learning three.js. At the moment I have a project in which I'd like the user to move only in 2d (x axis and y axis without rotating the camera) although it is a 3d environment.
Is there any extension / plugin for three.js like OrbitControls but with the desired functionality?
You can do that with OrbitControls. The camera movement will be restricted to a plane perpendicular to the view direction. Use a pattern like this one:
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set( 0, 0, 50 );
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.target.set( 0, 0, 0 ); // view direction perpendicular to XY-plane
controls.enableRotate = false;
controls.enableZoom = true; // optional
If you want the left mouse button to control the camera movement, then add this:
controls.mouseButtons = { PAN: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, ORBIT: THREE.MOUSE.RIGHT }; // swapping left and right buttons
You can modify the touch controls to match, but you will have to edit the OrbitControls source code to do so.
three.js r.89
add this line if you need to enable pan:
controls.mouseButtons = {
LEFT: THREE.MOUSE.PAN,
MIDDLE: THREE.MOUSE.DOLLY,
RIGHT: THREE.MOUSE.ORBIT
}
Related
Some objects are being frustrum culled after I amend their buffer geometry vertex positions. I don't want to set frustrumCulled = false, nor can I (feasibly) change the mesh position to anything other than (0,0,0). What options do I have here? My expectation is that the meshes should behave consistently with each other. I'm obviously missing something here.
Demo:
2 Meshes, one green, one red
Green mesh is initialised with position 0,0,0 with vertices around 0,10,0
Red mesh is initialised with position 0,0,0 with vertices around 0,0,0, but after 2 seconds, vertices are updated to around 0,10,0
Orbit controls allow zooming (Just zoom in see see actual behaviour)
Expected behaviour: Both meshes are visible when I zoom / move the camera. Or at least, they both behave in the same way
Actual behaviour, green mesh behaves as expected, red mesh is sometimes culled
const OrbitControls = THREE.OrbitControls // CDN shim
// Helper to create mesh with buffer geom
const createGeo = (initialY, color) => {
const geometry = new THREE.BufferGeometry()
const vertices = new Float32Array([
0, initialY, 0,
0, initialY + 1, 0,
0, initialY, 1,
0, initialY, 0,
0, initialY + 1, 0,
1, initialY, 0
])
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
const material = new THREE.MeshBasicMaterial({
color: color,
side: THREE.DoubleSide
})
mesh = new THREE.Mesh(geometry, material)
// Note: Setting frustumCulled to false always allows this to be seen, but I don't this object to always be seen
// mesh.frustumCulled = false
scene.add(mesh)
return mesh
}
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 0.1, 100)
let controls
const greenMesh = createGeo(10, 0x00FF00) // Set Green at 0,10,0
const redMesh = createGeo(0, 0xFF0000) // Set red at 0,0,0, then update to 0,10,0
const renderer = new THREE.WebGLRenderer({antialias: true})
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setAnimationLoop(() => {
controls.update()
greenMesh.rotation.y += 0.1
redMesh.rotation.y -= 0.1
renderer.render(scene, camera)
})
document.body.appendChild(renderer.domElement)
controls = new OrbitControls(camera, renderer.domElement)
camera.position.set(15, -20, 15)
controls.target.set(0, 10, 0)
window.onresize = function() {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
}
scene.add(new THREE.AxesHelper(100))
// Update red mesh to same position as green mesh
setTimeout(() => {
redMesh.geometry.attributes.position.setXYZ(0, 0, 10, 0)
redMesh.geometry.attributes.position.setXYZ(1, 0, 11, 0)
redMesh.geometry.attributes.position.setXYZ(2, 0, 10, 1)
redMesh.geometry.attributes.position.setXYZ(3, 0, 10, 0)
redMesh.geometry.attributes.position.setXYZ(4, 0, 11, 0)
redMesh.geometry.attributes.position.setXYZ(5, 1, 10, 0)
// As per - https://threejs.org/docs/#manual/en/introduction/How-to-update-things
redMesh.geometry.attributes.position.needsUpdate = true
redMesh.geometry.computeBoundingBox()
// When zooming / panning, red mesh is not always visible
}, 2000)
<script src="https://cdn.jsdelivr.net/npm/three#0.147.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.147.0/examples/js/controls/OrbitControls.min.js"></script>
As mentioned by #kikon, both
mesh.geometry.computeBoundingBox()
mesh.geometry.computeBoundingSphere()
are required. Whilst it doesn't explicitly state these in this docs, they have to both be used when updating vertex positions (I had made the incorrect assumption that you would use either the box or sphere).
The code for frustrum culling uses the bounding sphere, and a bounding sphere is automatically created once first invoked (hence the difference in red / green mesh behaviour), and has to be recomputed and both methods need to be called in this case.
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 have an instance of THREE.PlaneBufferGeometry that I apply an image texture to like this:
var camera, scene, renderer;
var geometry, material, mesh, light, floor;
scene = new THREE.Scene();
THREE.ImageUtils.loadTexture( "someImage.png", undefined, handleLoaded, handleError );
function handleLoaded(texture) {
var geometry = new THREE.PlaneBufferGeometry(
texture.image.naturalWidth,
texture.image.naturalHeight,
1,
1
);
var material = new THREE.MeshBasicMaterial({
map: texture,
overdraw: true
});
floor = new THREE.Mesh( geometry, material );
floor.material.side = THREE.DoubleSide;
scene.add( floor );
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, texture.image.naturalHeight * A_BUNCH );
camera.position.z = texture.image.naturalWidth * 0.5;
camera.position.y = SOME_INT;
camera.lookAt(floor.position);
renderer = new THREE.CanvasRenderer();
renderer.setSize(window.innerWidth,window.innerHeight);
appendToDom();
animate();
}
function handleError() {
console.log(arguments);
}
function appendToDom() {
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene,camera);
}
Here's the code pen: http://codepen.io/anon/pen/qELxvj?editors=001
( Note: ThreeJs "pollutes" the global scope, to use a harsh term, and then decorates THREE using a decorator pattern--relying on scripts loading in the correct order without using a module loader system. So, for brevity's sake, I simply copy-pasted the source code of a few required decorators into the code pen to ensure they load in the right order. You'll have to scroll down several thousand lines to the bottom of the code pen to play with the code that instantiates the plane, paints it and moves the camera. )
In the code pen, I simply lay the plane flat against the x-y axis, looking straight up the z-axis, as it were. Then, I slowly pan the camera down along the y-axis, continuously pointing it at the plane.
As you can see in the code pen, as the camera moves along the y-axis in the negative direction, the texture on the plane appears to develop a kink in it around West Texas.
Why? How can I prevent this from happening?
I've seen similar behaviour, not in three.js, not in a browser with webGL but with directX and vvvv; still, i think you'll just have to set widthSegments/heightSegments of your PlaneBufferGeometry to a higher level (>4) and you're set!
I want to spin a sphere, so I wonder if orbit controls could work for that.
However, code below won't work:
var geometry = new THREE.SphereGeometry(16, 16, 16);
var material = new THREE.MeshNormalMaterial();
var mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
var controls = new THREE.OrbitControls(mesh);
//then inside the animation loop
controls.update();
It seems like orbit controls only works when argument is camera. why?
You must pass the camera to the Orbit.
Like this:
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set( 0, 1000, 1000 );
var controls = new THREE.OrbitControls( camera );
This helps?
I don't think orbit controls work like that, the camera revolves around a point, it doesn't 'spin' that point. You can however change the target of the orbit controls to the sphere's position vector, allowing the camera to revolve around the sphere's position:
controls.target.copy(mesh.position);
I'm new to threejs just doing a basic cube with a texture to the backside. I have words on colour sides to the texture. However the words come out mirrored like. How can I get them to come out correctly.
You can negatively scale your cube to undo the mirror effect, like this:
cube.scale.x = -1;
There are two things you can do:
Reverse or rotate your UV coordinates on each face on the cube until you get the desired result. This is easy since the UV coordinates of a cube are usually 0.0 and 1.0.
Use an image package to rotate the textures as you want them.
I think I had the same problem as you with regards to texturing a cube.
As I understand it all surfaces come out correct orientation except the backside. The way i got around this was to place the textures on the cube per face and then alter the UV mapping of the back face.
This solved the problem of the back face being oriented incorrectly and also as a result of UV mapping I am now able to put textures on irregular faces to like pyramids etc.
Here is the solution by changing the UV of the backface. Just replace the loaded texture with a local texture cut and paste into notepad and save as html file and your good to go.
<html>
<head>
</head> <body> <script src="js/three.min.js"></script> <script> var
scene, camera, renderer; var geometry, material; var modarray=[];
var material=[]; var rotation=0; init(); animate(); function init()
{
renderer = new THREE.WebGLRenderer();
//renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize( window.innerWidth, 100 );
document.body.appendChild( renderer.domElement );
/////////// // Camera// ///////////
camera = new THREE.OrthographicCamera( window.innerWidth / - 2,
window.innerWidth / 2, 100 / 2, 100 / - 2, - 500, 1000 );
camera.position.z = 2000; camera.position.y = 0; camera.position.x = 0; scene= new THREE.Scene();
geometry = new THREE.BoxGeometry( 50, 50, 50 ); geometry2 = new THREE.BoxGeometry( 50, 50, 50 );
/////////////////////////////// // Store Materials for blocks//
/////////////////////////////// var bricks; material[0] = new
THREE.MeshPhongMaterial( { map:
THREE.ImageUtils.loadTexture('10.png') } );
var basex=-455; //////////////////////////////////////////////////
// Vector array to hold where UV will be placed //
////////////////////////////////////////////////// bricks = [new
THREE.Vector2(1, 0), new THREE.Vector2(1, 1), new
THREE.Vector2(0, 1), new THREE.Vector3(0, 0)];
///////////////////////////////////////////////////// // choose what
face this eccects from vertex array // // in this case backside
// // choose the orientation of the triangles //
/////////////////////////////////////////////////////
geometry.faceVertexUvs[0][10] = [ bricks[0], bricks[1], bricks[3]];
geometry.faceVertexUvs[0][11] = [ bricks[1], bricks[2], bricks[3]];
modarray[0] = new THREE.Mesh( geometry, material[0]); modarray[1] = new THREE.Mesh( geometry2, material[0]);
modarray[0].position.x=basex; modarray[0].position.z=1000;
modarray[0].position.y=0;
scene.add(modarray[0]);
modarray[1].position.x=basex+65; modarray[1].position.z=1000;
modarray[1].position.y=0;
scene.add(modarray[0]); scene.add(modarray[1]);
////////// // LIGHT// ////////// var light2 = new
THREE.AmbientLight(0xffffff); light2.position.set(0,100,2000);
scene.add(light2);
}
//////////////////// // Animation Loop // ///////////////////
function animate() {
requestAnimationFrame( animate ); var flag=0;
for(n=0; n<2; n++) {
modarray[n].rotation.x=rotation;
} rotation+=0.03;
renderer.render( scene, camera );
}
</script> <p>The cube on the left is with UV mapping to correct the
back surface.
The cube on the right is without the UV mapping.</p> </body>
</html>