I'm making a model of the Solar System. This is my current metric:
scale = 0.001;
// 1 unit - 1 kilometer
var AU = 149597871 * scale;
This is how i define the camera, renderer and controls:
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1 * scale, 0.1 * AU);
renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
controls = new THREE.OrbitControls(camera, renderer.domElement);
Then i give the user the option to jump between the objects so this is how i set the camera after user selects a planet/moon:
function cameraGoTo() {
for (var i = scene.children.length - 1; i >= 0 ; i--) {
var obj = scene.children[i];
if (obj.name == parameters.selected) {
controls.target = obj.position;
camera.position.copy(obj.position);
camera.position.y += obj.radius * 2;
}
}
}
The problem is that for small planets/moons ( <= 1000 km in radius) camera is shaking while rotating around the object. I have only basic knowledge of computer graphics so i don't know either this is the problem of Orbit Controls or it has something to with renderer itself...so I've tried to set logarithmicDepthBuffer = true but it didn't help. Also trying different scale didn't change anything.
Thank in advance for any help/clues.
EDIT:
Here's the fiddle:
http://jsfiddle.net/twxyz/8kxcdkjj/
You can see that shaking increases with any of the following:
the smaller the object,
the further the object from the point of origin,
What is the cause of this? It clearly seems it has nothing to do with the camera near/far spectrum values but is related to the distance the objects are from the center of the scene.
I've come up with the solution.
My problem was with the floating point precision errors when dealing with objects far from the point of origin. This turns out to be a very known problem and there are various solutions. I've used this one:
http://answers.unity3d.com/questions/54739/any-solution-for-extreamly-large-gameworlds-single.html
What happens is basically instead of moving the camera/player, we transform whole scene relative to the camera/player that is always at the point of origin. In this case, Orbit Controls' target is always point of origin.
Related
I'd like to be able to set the rotation of a Three.js sphere to an absolute value, but whenever I set rotateY the value I apply is added or subtracted from the last rotation, rather than becoming a new absolute rotation setting.
In a related answer about a cube (Three.js Set absolute local rotation), the cube has a rotation attribute, and cube.rotation.x = someValue results in the kind of absolute rotation that I'm looking for.
But the SphereGeometry object that I'm using (with a world map as its texture) has no rotation attribute.
I suppose I could keep track of previous rotations, and apply only the difference, but I'd think that would suffer eventually from cumulative round-off errors.
Is there another way to do this? A reset method of some sort?
async orient(lon: number, lat: number): Promise<void> {
if (Globe.mapFailed)
throw new Error('Map not available');
else if (!Globe.mapImage)
await new Promise<void>((resolve, reject) => Globe.waitList.push({ resolve, reject }));
if (!this.initialized) {
this.camera = new PerspectiveCamera(FIELD_OF_VIEW, 1);
this.scene = new Scene();
this.globe = new SphereGeometry(GLOBE_RADIUS, 50, 50);
const mesh = new Mesh(
this.globe,
new MeshBasicMaterial({
map: new CanvasTexture(Globe.mapCanvas)
})
);
this.renderer = new WebGLRenderer({ alpha: true });
this.renderer.setSize(GLOBE_PIXEL_SIZE, GLOBE_PIXEL_SIZE);
this.rendererHost.appendChild(this.renderer.domElement);
this.scene.add(mesh);
this.camera.position.z = VIEW_DISTANCE;
this.camera.rotation.order = 'YXZ';
this.initialized = true;
}
this.globe.rotateY(PI / 20); // Just a sample value I experimented with
this.camera.rotation.z = (lat >= 0 ? PI : 0);
requestAnimationFrame(() => this.renderer.render(this.scene, this.camera));
}
Update:
My workaround for now is this:
this.globe.rotateX(-this.lat);
this.globe.rotateY(this.lon);
this.lon = to_radian(lon);
this.lat = to_radian(lat);
this.globe.rotateY(-this.lon);
this.globe.rotateX(this.lat);
I'm saving the previous rotations which have been done so that I can undo them, then apply new rotations. (Degree/radian conversions, and the sign of the longitude rotation needing to be reversed, obscures the process a bit.)
I think you're confusing geometry.rotateY(rot) with mesh.rotation.y = rot. As explained in the docs:
.rotateY(): Rotate the geometry about the Y axis. This is typically done as a one time operation, and not during a loop. Use Object3D.rotation for typical real-time mesh rotation.
geometry.rotateY(rot) should only be used once because it updates the values of all the vertex positions, so it has to iterate through every vertex and update it. This is useful if you need to modify the "original state" of your geometry, for example a character model that needs to start facing down the z-axis.
mesh.rotation.y = rot; is what you're probably looking for. This is what you use during realtime rotations, so the intrinsic vertex positions are left untouched, you're just rotating the mesh as a whole. For example, when your character is running all over the map.
this.mesh = new Mesh(geometry, material);
// Set rotation to an absolute rotation value
this.mesh.rotation.y = Math.PI / 20;
// Increment rotation a relative amount (like once per frame):
this.mesh.rotation.y += Math.PI / 20;
I am adding a plane to the scene like this:
// Camera
this.three.camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 60);
// Plane
const planeGeometry = new THREE.PlaneBufferGeometry(1,1,this.options.planeSegments,this.options.planeSegments);
const planeMat = new THREE.ShaderMaterial( ... )
this.three.plane = new THREE.Mesh(planeGeometry,planeMat);
this.three.scene.add(this.three.plane);
Pretty basic. I am than trying to find out how I have to move the plane in the Z axis for it to fill the browser-viewport. For that,
// See attachment "solving for this" is closeZ
const closeZ = 0.5 / Math.tan((this.three.camera.fov/2.0) * Math.PI / 180.0);
this.uniforms.uZMax = new THREE.Uniform(this.three.camera.position.z - closeZ);
So now I know in my shader how much I can add to Z to make the plane fill the viewport. Vertex Shader looks like this:
uniform float uZMax;
void main() {
vec3 pos = (position.xy, uZMax);
gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1 );
}
This actually zoom the plane to fill the viewport, but in Y-Axis, not in X-Axis.
I would like to discover why my math is referring to the Y-Axis and how I need to transform it, so the plane will fill the viewport width instead of it's height?
Edit:
I'm trying to achieve something like this https://tympanus.net/Tutorials/GridToFullscreenAnimations/index4.html - But in the given example they're just scaling the x- and y-pixels to fill the screen and therefore no actual 3d - and therefore again no lighting is going on.
I want to actually move the plane towards the camera using different z-values so I can calculate surface normals to then again calculate lighting in the fragment shader by how aligned the normal is with the light direction - like it's done in raymarching.
You can easily achieve such a fullscreen effect by using the following setup:
const camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
const geometry = new THREE.PlaneBufferGeometry( 2, 2 );
When creating a mesh with this geometry and a custom shader material, the orthographic camera will ensure the intended fullscreen effect. This approach is used in all post-processing example where the entire viewport has to be filled with a single quad.
I figured it out, and as suspected it has to do with the aspect ratio passed to the camera. For anyone looking for a solution after me, here is how it works:
I wrongly assumed that the field-of-value for the camera is the same in all directions. But the FOV is referring to the Y-Axis FOV, so we have to convert the camera-fov to the x-axis also:
function getXFOV() {
// Convert angle to radiant
const FOV = this.three.camera.fov;
let yFovRadiant = FOV * Math.PI/180;
// Calculate X-FOV Radiant
let xFovRadiant = 2 * Math.atan( Math.tan(yFovRadiant/2) * (window.innerWidth / window.innerHeight));
// Convert back to angle
let xFovAngle = xFovRadiant * 180/Math.PI;
return xFovAngle;
}
And then we simply use that angle in in the closeZ-calculation instead of the camera's fov. Now it snaps to the window-width.
const closeZ = 0.5 / Math.tan((this.getXFOV()) * Math.PI / 180.0);
this.uniforms.uZMax = new THREE.Uniform(this.three.camera.position.z - closeZ);
I'm creating a game level page using three js. Here I used mapcontrols for user control. In this when I click and drag any part of the screen, the object gets translated (as per my wish). But when I move along the Z axis, the objects move along z-axis which I need to block.
I just want to make the scene like a horizontal carousel in normal html (literally)
I tried some properties of OrbitControls which is given in threejs docs.
https://threejs.org/docs/index.html#examples/en/controls/OrbitControls
Here is the code I tried
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();
renderer.setSize( window.innerWidth, window.innerHeight );
var mapControls = new THREE.MapControls( camera, renderer.domElement );
mapControls.enableDamping = true;
mapControls.enableRotate = false;
mapControls.enableZoom = false;
To create a carousel-like experience, you don't need orbit controls, you only need to move the camera while looking at your object is advancing through the carousel, like the typical platform games.
I have created this fiddle with an example with an sphere moving along the x axis and the camera following it.
and the relevant code is basically on the render method:
function render() {
requestAnimationFrame(render);
mesh.position.x -= 0.1;
camera.lookAt(mesh.position);
camera.position.x -= 0.1;
renderer.render(scene, camera);
}
If you want to still playing with OrbitControls and fix the axes, you need to play with minPolarAngle and maxPolarAngle, that will block vertical axis if you set the same value for both.
controls.maxPolarAngle = Math.PI / 2;
controls.minPolarAngle = Math.PI / 2;
but that gives no perspective at all, so I would use:
controls.maxPolarAngle = Math.PI / 2.2;
controls.minPolarAngle = Math.PI / 2.2;
Then you have to play with the horizontal perspective, for that you need to set minAzimuthAngle and maxAzimuthAngle between -2 PI and +2 PI...:
controls.minAzimuthAngle = -Math.PI * 1.1;
controls.maxAzimuthAngle = Math.PI * 1.1;
This will slightly turn your camera angle:
Then, using only OrbitControls you will need to move the rest of the objects in the scene, so instead of moving the sphere, you will need to move conceptually the "floor".
If this solution solves your question, please mark the answer as answer accepted, in that way it will also help other users to know it was the right solution.
I'm working with this gltf model: http://virtualvizcaya.org/pages/1916barge1.html using Three.js and Potree. But the model is flickering. I've read these related posts Flickering planes and Texture/model flickering in distance (3D).
If I understood it correctly, it requires the PerspectiveCamera's near and far plane arguments to be distant from each other, but my attempt on this hasn't fix this problem.
Please help! (I'm pretty new on three.js and potree btw)
Like #Brakebein mentioned, this is called z-fighting.
If you use WebGLRenderer, you could try:
var renderer = new THREE.WebGLRenderer({
logarithmicDepthBuffer: true
});
In my viewer (http://gltf-viewer.donmccurdy.com/) I've used the following code to configure the camera's near/far planes:
const object; // my model
const box = new THREE.Box3().setFromObject(object);
const size = box.getSize(new THREE.Vector3()).length();
const center = box.getCenter(new THREE.Vector3());
this.controls.reset();
object.position.x += (object.position.x - center.x);
object.position.y += (object.position.y - center.y);
object.position.z += (object.position.z - center.z);
controls.maxDistance = size * 10;
camera.near = size / 100;
camera.far = size * 100;
camera.updateProjectionMatrix();
There is some dependency here on scene scale and hardware precision — in your code you're scaling the model up significantly, and may want to try bringing the camera closer instead. But I'm not familiar with Potree, so I'm not sure what it might be doing that would give different results than using three.js alone.
I am making this program where you can click on an object, zoom to it, then look at it from all angles by holding the right mouse button and dragging. I need the camera to be going around the object, not rotate the object with the camera looking at it. I honestly just have no idea how to math it out!
For testing there is already a game object with an xyz we have selected and are looking at
var g = new GameObject(500, 0, 0);//The game object with xyz
this.selected = g;//set selected to g
//Create and set the camera
this.camera = new THREE.PerspectiveCamera(45, w/h, 1, 10000);
this.camera.position.x = 0;
this.camera.position.y = 0;
this.camera.position.z = 0;
//set camera to look at the object which is 500 away in the x direction
this.camera.lookAt(new THREE.Vector3(this.selected.x, this.selected.y, this.selected.z));
So the radius between the camera and the object is 500 and while selected and rotating, the camera should always be 500 away.
I update the scene here:
Main.prototype.update = function(){
this.renderer.render(this.scene, this.camera);//scene is just some ambient lighting
//what to do when mouse right is held down
if(this.rightMouseDown){
//placeholder functionality, needs to rotate around object based on mouse movements
this.camera.position.x -= 5;
}
}
How do I rotate this camera around g with a radius of 500?!?!
As gaitat mentioned, trackball controls are the best place to start with many configurable parameters to make camera rotation/revolution easy. One enormous potential benefit of this method ( especially for your project ) is avoiding "gimbal lock" which is the source of much frustration when working with rotations. Here's a link that might help you with Trackball controls and Orbitcontrols:
Rotate camera in Three.js with mouse
Another option would be setting camera coordinates yourself in the animation loop which is actually quite simple:
var angle = 0;
var radius = 500;
function animate() {
...
// Use Math.cos and Math.sin to set camera X and Z values based on angle.
camera.position.x = radius * Math.cos( angle );
camera.position.z = radius * Math.sin( angle );
angle += 0.01;
...
}
Another option would be to connect the camera to a pivot object and just rotate the pivot:
var camera_pivot = new THREE.Object3D()
var Y_AXIS = new THREE.Vector3( 0, 1, 0 );
scene.add( camera_pivot );
camera_pivot.add( camera );
camera.position.set( 500, 0, 0 );
camera.lookAt( camera_pivot.position );
...
camera_pivot.rotateOnAxis( Y_AXIS, 0.01 ); // radians
If you pursue this option, be aware that the camera object is in "camera pivot space", and might be more challenging to manipulate further.