I am trying to draw a plane terrain in using ThreeJS but it doesn't work.
Here is my plane creation code:
var plane = new THREE.Mesh(new THREE.PlaneGeometry(300, 300), new THREE.MeshBasicMaterial({
color: 0x0000ff
}));
plane.overdraw = true;
this.scene.add(plane);
Pretty straight-forward. Actually I just copied it from some site.
This is how I initialize scene and camera:
this.camera =
new THREE.PerspectiveCamera(
45, // view angle
width / height, // aspect
0.1, // near
10000); // far
this.scene = new THREE.Scene();
// add the camera to the scene
this.scene.add(this.camera);
// the camera starts at 0,0,0
// so pull it back
this.camera.position.z = 800;
this.camera.position.y = -100;
I also have a sphere of radius 20 in the center. It shows up just fine.
What am I missing?
The camera is looking at the "back face" of the plane.
A plane have two sides, but only one is visible.
Two solutions:
1) Move the camera to look at the "front face" of the plane:
this.camera.position.y = 100;
2) Or, activate the doubleSided flag:
plane.doubleSided = true;
Related
I'm using Three.js to apply a displacement map to a simple plane. The displacement applies successfully, but the lighting is wrong, as if all the normals remain unchanged. The result is a surface with the right shape that's lit as if it were flat.
displacement map:
result:
How can I fix this to properly change the lighting?
Here is the relevant bit of code:
// shortened from actual code - please excuse any small typos
var renderer = new THREE.WebGLRenderer();
renderer.setSize(500, 250);
var scene = new THREE.Scene();
var light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(100, 100, 100);
var ambient = new THREE.AmbientLight(0xffffff, 0.2);
scene.add(light);
scene.add(ambient);
var camera = new THREE.PerspectiveCamera(60, 2, 1, 20000);
var geometry = new THREE.PlaneBufferGeometry(100, 100, 1000, 1000);
geometry.rotateX(-Math.PI / 2);
var material = new THREE.MeshPhongMaterial();
var textureLoader = new THREE.TextureLoader();
var texture = textureLoader.load('circlemap.png');
material.displacementScale = 20;
material.displacementMap = texture;
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// ... later
renderer.render(scene, camera)
edit: when I set flatShading: true in the material, the normals are updated correctly, but look bad (because of the flat shading):
You're going to have to pass a normalMap along with your displacementMap.
See this demo, when normalScale = 0 it's the equivalent of having no normalMap, and you can see that the reflections don't follow the displacement, only the default topography. However, when normalMap is 1, then the reflections do respect the displacement.
You need to generate a normalMap.
I'm using Three.js to develop a player for 360° pictures.
My scene contains a sphere (BasicMeshMaterial) that has a 360° picture as texture. The camera is in the middle of that sphere, so that the user sees the picture, that is applied to the sphere's "walls".
I recently added the possibility to click on some spots of the sphere, to rotate the camera towards that spot. The camera smoothly moves to this spot, untill it has this spot in the center of the screen.
My problem is that sometimes the camera easing (tween animation) that is supposed to focus the spot clicked uses a very strange path and it's all very ugly.
This seems to happen when the initial position of the camera and the target position cross a certain point of the sphere. I have almost no clue what happens. Do you ? Could it have a link with the sphere, or the camera is only playing with quaternions and so it's not affected by others objects ? Then what could make it chose wrong path, and how come it happens only with a certain angle of the sphere ? anything would be help !
The player is testable at http://isiko.io/, after scrolling a bit.
I can show you the code but don't really know what to pick. Tell me if you need the camera's rotation,or the sphere's initiation..
Thank you for your feedbacks.
There is another approach with tweening an angle around an axis:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 0);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var sphere = new THREE.Mesh(new THREE.SphereGeometry(10, 32, 24), new THREE.MeshBasicMaterial({
color: "yellow",
wireframe: true
}));
scene.add(sphere);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var center = new THREE.Vector3();
var vectorStart = new THREE.Vector3(0, 0, -10); // in front of the camera
var vectorEnd = new THREE.Vector3();
var angle = {
value: 0
};
var angleEnd = {
value: 0
};
var normal = new THREE.Vector3();
var lookAt = new THREE.Vector3();
var isMoving = false;
window.addEventListener("mousedown", onMouseDown, false);
function onMouseDown(event) {
if (isMoving) return;
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
let newPosition = raycaster.ray.at(10);
setPoint(newPosition);
// prepare the values we need for tweening
vectorEnd.copy(newPosition);
angle.value = 0; // we will tween this value
angleEnd.value = vectorStart.angleTo(vectorEnd); // to this one
normal.copy(vectorStart).cross(vectorEnd).normalize(); // around this axis
// tween the angle
var tween = new TWEEN.Tween(angle).to(angleEnd, 1000).delay(250).easing(TWEEN.Easing.Cubic.InOut).onStart(function(){
isMoving = true;
}).onUpdate(
function() {
camera.lookAt(lookAt.copy(vectorStart).applyAxisAngle(normal, angle.value));
}
).onComplete(function() {
isMoving = false;
vectorStart.copy(vectorEnd);
}).start();
}
function setPoint(position) {
let point = new THREE.Mesh(new THREE.SphereGeometry(0.125, 4, 2), new THREE.MeshBasicMaterial({
color: "red",
wireframe: true
}));
point.position.copy(position);
scene.add(point);
}
render()
function render() {
requestAnimationFrame(render);
TWEEN.update(); // don't forget to put this line into the animation loop, when you use Tween.js
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/17.2.0/Tween.min.js"></script>
Using Three.js I have a sphere (globe) and several sprites attached to volcano points. I can rotate (spin) the globe and the sprites stay in their positions because they're added as a group to the sphere.
Now I want to be able to spin the globe to an arbitrary position using a button. How can I do this? For example if the point I want to spin to is at the back of the globe, how can I rotate the globe so it's in the front?
This code is essentially what I have right now. A main mesh which I add sprite to.
<html>
<head></head>
<body>
<script src="three.min.js"></script>
<script>
var scene, camera, renderer;
var geometry, material, mesh;
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 1000;
material = new THREE.MeshBasicMaterial( { color: 0xff0000, wireframe: false } );
geometry = new THREE.SphereGeometry( 159, 32, 32 );
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
var map = THREE.ImageUtils.loadTexture( "sprite1.png" );
var material2 = new THREE.SpriteMaterial( { map:map, color:0x00ff00 } );
var sprite1 = new THREE.Sprite( material2 );
sprite1.position.set(100,100,100);
sprite1.scale.set(40,40,40);
mesh.add(sprite1);
var sprite2 = new THREE.Sprite( material2);
sprite2.position.set(-100,-100,-100);
sprite2.scale.set(30,30,30);
mesh.add(sprite2);
var sprite3 = new THREE.Sprite(material2);
sprite3.position.set(100,-100,100);
sprite3.scale.set(20,20,20);
mesh.add(sprite3);
renderer = new THREE.WebGLRenderer({alpha:true});
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
}
function animate() {
requestAnimationFrame( animate );
mesh.rotation.y += 0.01;
renderer.render( scene, camera );
}
</script>
</body>
</html>
Diagram example
This would be my approach:
// as sprite is a child of mesh get world position
var spritePos = new THREE.Vector3().setFromMatrixPosition(sprite.matrixWorld);
// get the vectors for calculating angle
var cv3 = new THREE.Vector3().subVectors(camera.position, mesh.position);
var sv3 = new THREE.Vector3().subVectors(spritePos, mesh.position);
// we only want to rotate around y-axis, so only the angle in x-z-plane is relevant
var cv2 = new THREE.Vector2(cv3.x, cv3.z);
var sv2 = new THREE.Vector2(sv3.x, sv3.z);
// normalize Vectors
cv2.normalize();
sv2.normalize();
// dot product
var dot = cv2.dot(sv2);
// angle to between sprite and camera in radians
// cosinus is from 1 to -1, so we need to normalize and invert it and multiply it with PI to get proper angle
var angle = (1 - (dot + 1) / 2) * Math.PI;
// is sprite left or right from camera?
if(spritePos.x < 0)
mesh.rotation += angle;
else
mesh.rotation -= angle;
Now, I made a Plunker.
It seems a bit inaccurate as it always rotates a bit left or right to the very front position. Maybe it's due to the cosinus near to some specific angles.
Also keep in mind that the determination whether the sprite is left or right from the camera is a bit more difficult if camera or mesh is somewhere else in the scene.
Explanation after dot product:
The dot product gives the angle of two vectors as cosinus. So we get a value between -1 and 1. e.g. cos(0) = 1 cos(PI/2) = 0 cos(PI) = -1 So at the moment is 0° = 1 and 180° = -1.
We want to get the angle in radians to rotate the mesh in position. So first we normalize it (dot + 1) / 2, so 0° = 1 and 180° = 0.
Then invert it (0° = 0, 180° = 1) and multiply with PI (0° = 0, 180° = PI).
Now, we have the angle to rotate, but we don't know if need to rotate to the left or to the right, that's why I check if the sprite is left or right from camera.
I don't know if it's explanation enough or if it's comprehensable at all?
I'm adding sprites in a 3d scene using three.js and I want to know distance between the camera and sprite when I click on screen. So I use a Raycater.
But if I click on the sprite, the distance property of intersection object is always "wrong" (someting like 0.3), or maybe I don't know how to read and understand the result. I thought "distance" value of intersection is the distance from camera to sprite (so, in my case something like 5).
Here is a shortened version of my code :
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 sprite = new THREE.Sprite(new THREE.SpriteMaterial({color: 0x00ff00}));
scene.add(sprite);
camera.position.z = 5;
var render = function () {
requestAnimationFrame(render);
renderer.render(scene, camera);
};
render();
window.addEventListener('mousedown', function (e) {
if (e.target == renderer.domElement) {
var vector = new THREE.Vector3((e.clientX / window.innerWidth) * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1, 0.5);
var projector = new THREE.Projector();
projector.unprojectVector(vector, camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects([sprite]);
console.log(intersects[0]);
}
}, false);
You can see it in action here : http://jsfiddle.net/pWr57/
So how can I have the distance form camera to a sprite ?
three.js r66
Do this, instead
console.log( raycaster.ray.origin.distanceTo( intersects[0].point ) );
Tip: Read the source code, Raycaster.js, so you know what it is doing. It is currently returning the perpendicular distance from the sprite center to the ray.
In this case, I agree that it would be better to return the distance from the camera.
three.js r.66
I am trying to render a set of photos, positioned in 3-dimensional space, which cast shadows on one another. I am starting with two rectangles, and here is my code
function loadScene() {
var WIDTH = 1200,
HEIGHT = 500,
VIEW_ANGLE = 45,
ASPECT = WIDTH / HEIGHT,
NEAR = 0.1,
FAR = 10000,
world = document.getElementById('world'),
renderer = new THREE.WebGLRenderer(),
camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR),
scene = new THREE.Scene(),
texture = THREE.ImageUtils.loadTexture('image.jpg', {}, function() {
renderer.render(scene, camera);
}),
material = new THREE.MeshLambertMaterial({map: texture}),
solidMaterial = new THREE.MeshPhongMaterial({color: 0xCC0000}),
rectangle = new THREE.PlaneGeometry(200, 120),
mesh = new THREE.Mesh(rectangle, material),
mesh1 = new THREE.Mesh(rectangle, material),
spotLight = new THREE.SpotLight(0xFFFFFF, 1.3);
camera.position.set(0, 0, 200);
mesh.translateX(140);
mesh.translateZ(-70);
mesh.receiveShadow = true;
mesh1.castShadow = true;
spotLight.position.x = -100;
spotLight.position.y = 230;
spotLight.position.z = 230;
spotLight.castShadow = true;
scene.add(spotLight);
scene.add(mesh);
scene.add(mesh1);
renderer.setSize(WIDTH, HEIGHT);
renderer.shadowMapEnabled = true;
renderer.render(scene, camera);
world.appendChild(renderer.domElement);
}
Here solidMaterial is a solid red, and material is a textured material. What I get is the following:
If I use material for both the meshes, the rectangles appear as expected, but without shadows. Same if I use solidMaterial for both.
If I use material for mesh (the one farther away) and solidMaterial for mesh1, I see a red rectangle which casts shadow on a textured one. This is the only case that works as I would expect.
If I use solidMaterial for mesh (the one farther away) and material for mesh1, I see a red rectangle with the shadow on it, but the textured rectangle in front is not drawn at all.
What is the correct use of shadows?
It turns out the shadow does not appear when the two rectangles have the same material. I wonder whether this a bug in THREE.js.
On the other hand, if I use two different material, the shadow appears as expected, even if they have the same texture.