Three.js: Camera flying around sphere? - javascript

In Three.js (which uses JavaScript/ WebGL), how would one create a camera which flies around a sphere at fixed height, fixed forward speed, and fixed orientation in relation to the sphere, with the user only being able to steer left and right?
Imagine an airplane on an invisible string to the center of a globe, flying near ground and always seeing part of the sphere:
(I currently have code which rotates the sphere so to the camera it looks like it's flying -- left and right steering not implemented yet -- but I figure before I go further it might be cleaner to move the camera/ airplane, not the sphere group.)
Thanks!

You mean like in my Ludum Dare 23 game? I found this to be a bit more complicated than I expected. It's not difficult, though.
Here I'm assuming that you know the latitude and longitude of the camera and its distance from the center of the sphere (called radius), and want to create a transformation matrix for the camera.
Create the following objects only once to avoid creating new objects in the game loop:
var rotationY = new Matrix4();
var rotationX = new Matrix4();
var translation = new Matrix4();
var matrix = new Matrix4();
Then every time the camera moves, create the matrix as follows:
rotationY.setRotationY(longitude);
rotationX.setRotationX(-latitude);
translation.setTranslation(0, 0, radius);
matrix.multiply(rotationY, rotationX).multiplySelf(translation);
After this just set the camera matrix (assuming camera is your camera object):
// Clear the camera matrix.
// Strangely, Object3D doesn't have a way to just SET the matrix(?)
camera.matrix.identity();
camera.applyMatrix(matrix);

Thanks for Martin's answer! I've now got it running fine in another approach as follows (Martin's approach may be perfect too; also many thanks to Lmg!):
Set the camera to be a straight line atop the sphere in the beginning (i.e. a high y value, a bit beyond the radius, which was 200 in my case); make it look a bit lower:
camera.position.set(0, 210, 0);
camera.lookAt( new THREE.Vector3(0, 190, -50) );
Create an empty group (an Object3D) and put the camera in:
camGroup = new THREE.Object3D();
camGroup.add(camera);
scene.add(camGroup);
Track the mouse position in percent in relation to the screen half:
var halfWidth = window.innerWidth / 2, halfHeight = window.innerHeight / 2;
app.mouseX = event.pageX - halfWidth;
app.mouseY = event.pageY - halfHeight;
app.mouseXPercent = Math.ceil( (app.mouseX / halfWidth) * 100 );
app.mouseYPercent = Math.ceil( (app.mouseY / halfHeight) * 100 );
In the animation loop, apply this percent to a rotation, while automoving forward:
camGroup.matrix.rotateY(-app.mouseXPercent * .00025);
camGroup.matrix.rotateX(-.0025);
camGroup.rotation.getRotationFromMatrix(camGroup.matrix);
requestAnimationFrame(animate);
renderer.render(scene, camera);

Related

Update PerspectiveCamera position with the device motion: acceleration

I would like to build an AR experience (without web xr). The idea is to populate your surrounding with 3D cubes and be able to walk around them. I've created a github/codesandbox with a minimal demo. If you want to test the code run it from your favorite mobile phone. (it's just an experiment with some code sorry if it's not working on all phones tested with ios13+ and android OxygenOS 10.0 only for now)
What I did so far:
Populate your surrounding with 3D objects ✅
Create a gyro camera to see around you all the objects ✅
Update the camera position using the accelerometer from you phone to walk around the 3D scene and objects
Details:
In my scene I have X number of cubes placed around me given a distance and an angle as follow:
public async randomPosition(object: THREE.Object3D) {
const normalizedDistance = new THREE.Vector3()
const randomPosition = new THREE.Vector3()
const distance = getRandomNumber(1, 10)
const angleDeg = getRandomNumber(0, 360)
normalizedDistance.copy(new THREE.Vector3(0, 0, this.distance))
const angleRad = THREE.MathUtils.degToRad(this.angleDeg)
randomPosition.copy(normalizedDistance.applyAxisAngle(new THREE.Vector3(0, 1, 0), angleRad))
object.position.copy(this.randomPosition)
}
}
Then I used a gyro camera and now when I move around with my phone I'm able to see all the cubes around me. For now this code is based on the DeviceOrientationControls.
Finally I would like to use the devicemotion event to get the acceleration x/y/z of my device and update the camera's position with those values every frame to be able to walk around my cubes. At the moment I'm doing this:
const normalizedDistance = new THREE.Vector3()
const currentPosition = new THREE.Vector3()
const accX = this.findDistance(this.accelerationX, 0.9) // m/s^2 convert to distance <=> 0.5 * acceleration * speed ** 2
const accY = this.findDistance(this.accelerationY, 0.9)
const accZ = this.findDistance(this.accelerationZ, 0.9)
normalizedDistance.copy(new THREE.Vector3(accX, accY, accZ))
const angleRad = THREE.MathUtils.degToRad(this.rotationGamma)
currentPosition.copy(
normalizedDistance.applyAxisAngle(new THREE.Vector3(0, 1, 0), angleRad)
)
this.camera.position.copy(currentPosition)
The result I'm getting is the camera jiggling a lot when using a value of ~1s for the findDistance (see code above: findDistance(this.accelerationX, speed) // m/s^2 convert to distance <=> 0.5 * acceleration * speed ** 2 ) and the camera is not moving when using the clock delta time for the speed.
Also when I'm moving my phone the whole scene is moving with it, at the same time, so it doesn't give the effect of a camera moving throughout the 3D environment around you, if it makes sense.
I'm looking for advices, maybe someone has already been through this and would know if I'm on the good way or not. Thank you.

Three.js - How to get camera position with respect to origin

The title of this question might a bit ambiguous, but I don't know how to phrase it in a line.
Basically I've got this situation: there is a perspective camera in the scene and a mesh. The mesh is NOT centered at the origin of the axis.
The camera points directly to the center of this object, and it's position (I mean literally the "position" property of the Three.js camera object) is the position with respect to the center of the object, not the origin; so it works in another coordinate system.
My question is: how can I get the position of the camera not with respect of the object center but with respect of the origin of the "global" coordinate system?
To clarify, the situation is this. In this image you can see a hand mesh that has a position far away from the origin of the coordinate system. The camera points directly to the center of the hand (so the origin from the point of view of the camera is the center of the hand), and if I print it's position it gives me these values:
x: -53.46980763626004; y: -2.7201492246619283; z: -9.814480359970839
while actually I want the position with respect to the origin of the coordinate stystem (so in this case the values would be different; for example, the y value would be positive).
UPDATE:
I tried #leota's suggestion, so I used the localToWorld method in this way:
var camera = scene.getCamera();
var viewPos = camera.position;
var newView = new THREE.Vector3();
newView.copy(viewPos);
camera.localToWorld(newView);
I did an experiment with this mesh. As you can see this mesh is also not centered on the origin (you can see the axes on the bottom-left corner).
If I print the normal value of the camera's position (so, with respect to the center of the mesh) it gives me these results:
x: 0; y: 0; z: 15
If now I print the resulting values after the code above, the result is:
x: 0; y: 0; z: 30
which is wrong, because as you can see the camera position in the image has x and y values clearly different than 0 (while z = 30 could be true, as far as I can see).
If for example I rotate the camera so that it's very close to the origin, like this (in the image the camera is just behind the origin, so its position in world coordinates should have negative values for x, y, z), the coordinates with respect of the center of the object are:
x: -4.674180744175711; y: -4.8370441591630255; z: -4.877951155147168
while after the code above they become:
x: 3.6176076166961373; y: -4.98753160894295; z: -4.365141278155379
The y and z values might even be accurate at a glance, but the positive value of x tells me that it's totally wrong, and I don't know why.
I'm going to continue looking for a solution, but this might be a step in the right direction. Still, any more suggestions are appreciated!
UPDATE 2:
Found the solution. What #leota said is correct, that is indeed how you would get absolute coordinates for the camera. In my case though, I finally found a single line of code hidden in the project's code that was scaling everything according to some rule (project related). So the solution for me was to take the camera position as it is and then scale it back according to that rule.
Since #leota's answer was indeed the solution to the original question, I'm accepting it as the correct anwser
Not sure I got your question :) if I did then you need to switch between World and Local coordinate systems. The THREE.PerspectiveCamera inherits from THREE.Object3D so you can use the following methods to set your coordinate system:
.localToWorld ( vector )
vector - A local vector.
Updates the vector from local space to world space.
.worldToLocal ( vector )
vector - A world vector.
Updates the vector from world space to local space.
From Three.js Documentation
Update:
First update your camera Matrix:
camera.updateMatrixWorld();
Then:
var vector = camera.position.clone();
vector.applyMatrix( camera.matrixWorld );
The vector should hold the position in world coordinate
I had same question trying to answer I was confused for a while my guess but not sure is
var plot = camera.position.x - mesh.position.x;
var plotb = camera.position.y - mesh.position.y;
var plotc = camera.position.z - mesh.position.z;
mesh.position.x = (camera.position.x + plot) - mesh.position.x;
mesh.position.y = (camera.position.y + plotb) - mesh.position.y;
mesh.position.z = (camera.position.z + plotc) - mesh.position.z;
or
var plot = (camera.position.x * mesh.position.x) / 1000;
var plotb = (camera.position.y * mesh.position.y) / 1000;
var plotc = (camera.position.z * mesh.position.z) / 1000;
mesh.position.x = mesh.position.x + plot;
mesh.position.y = mesh.position.y + plotb;
mesh.position.z = mesh.position.z + plotc;

Three.js - Object follows mouse position

I am creating a sphere in Three.js which has to follow the mouse whenever it moves, as displayed in this example. The function that handles the mouse movement is the following:
function onMouseMove(event) {
// Update the mouse variable
event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
// Make the sphere follow the mouse
mouseMesh.position.set(event.clientX, event.clientY, 0);
};
I attach a JSFiddle with the complete code inside it, where you can see that according to the DOM, mouseMesh is undefined. Do you have an idea of what am I doing wrong?
Thanks in advance for your replies!
For sphere to follow mouse, you need to convert screen coordinates to threejs world position. Reference link.
Updated fiddle
var vector = new THREE.Vector3(mouse.x, mouse.y, 0.5);
vector.unproject( camera );
var dir = vector.sub( camera.position ).normalize();
var distance = - camera.position.z / dir.z;
var pos = camera.position.clone().add( dir.multiplyScalar( distance ) );
You should use a THREE.Raycaster for this. When you set a list of intersectObjects you will be able to get an array of objects that intersected with the ray. So you can get the position from the 'clicked' object from returned list
Basically, you need to project from the 3D world space and the 2D screen space.
Renderers use projectVector for translating 3D points to the 2D screen. unprojectVector is basically for doing the inverse, unprojecting 2D points into the 3D world. For both methods you pass the camera you're viewing the scene through.
So, in this code you're creating a normalised vector in 2D space.

Three.js - Accurate ray casting for collision detection

I'm working with Three.js, version 68. I'm using the same method for collision detection as this guy is using here, which is great most of the time (A big "thank you" goes out to the author!): http://stemkoski.github.io/Three.js/Collision-Detection.html
Here is a link to the source if you want to download it from github. Just look for Collision-Detection.html: https://github.com/stemkoski/stemkoski.github.com
Here is the code that is important to the collision detection:
var MovingCube;
var collidableMeshList = [];
var wall = new THREE.Mesh(wallGeometry, wallMaterial);
wall.position.set(100, 50, -100);
scene.add(wall);
collidableMeshList.push(wall);
var wall = new THREE.Mesh(wallGeometry, wireMaterial);
wall.position.set(100, 50, -100);
scene.add(wall);
var wall2 = new THREE.Mesh(wallGeometry, wallMaterial);
wall2.position.set(-150, 50, 0);
wall2.rotation.y = 3.14159 / 2;
scene.add(wall2);
collidableMeshList.push(wall2);
var wall2 = new THREE.Mesh(wallGeometry, wireMaterial);
wall2.position.set(-150, 50, 0);
wall2.rotation.y = 3.14159 / 2;
scene.add(wall2);
var cubeGeometry = new THREE.CubeGeometry(50,50,50,1,1,1);
var wireMaterial = new THREE.MeshBasicMaterial( { color: 0xff0000, wireframe:true } );
MovingCube = new THREE.Mesh( cubeGeometry, wireMaterial );
MovingCube.position.set(0, 25.1, 0);
// collision detection:
// determines if any of the rays from the cube's origin to each vertex
// intersects any face of a mesh in the array of target meshes
// for increased collision accuracy, add more vertices to the cube;
// for example, new THREE.CubeGeometry( 64, 64, 64, 8, 8, 8, wireMaterial )
// HOWEVER: when the origin of the ray is within the target mesh, collisions do not occur
var originPoint = MovingCube.position.clone();
for (var vertexIndex = 0; vertexIndex < MovingCube.geometry.vertices.length; vertexIndex++)
{
var localVertex = MovingCube.geometry.vertices[vertexIndex].clone();
var globalVertex = localVertex.applyMatrix4( MovingCube.matrix );
var directionVector = globalVertex.sub( MovingCube.position );
var ray = new THREE.Raycaster( originPoint, directionVector.clone().normalize() );
var collisionResults = ray.intersectObjects( collidableMeshList );
if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() )
appendText(" Hit ");
}
This works great most of the time, but there are times when I can move the cube partially into the wall, and it won't register a collision. For example, look at this image:
It should say "Hit" in the top-left corner where there are just a bunch of dots, and it's not.
NOTE: I also tried his suggestion and did the following, but it didn't seem to help much:
THREE.BoxGeometry( 64, 64, 64, 8, 8, 8, wireMaterial ) // BoxGeometry is used in version 68 instead of CubeGeometry
Does anyone know how this method could be more accurate? Another question: Does anyone know what the following if statement is for, i.e. why does the object's distance have to be less than the length of the direction vector?:
if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() )
To answer your last question first: that line detects whether the collision happened inside your MovingCube. Your raycasting code casts a ray from the MovingCube's position towards each of its vertices. Anything that the ray intersects with is returned, along with the distance from the MovingCube's position at which the intersected object was found (collisionResults[0].distance). That distance is compared with the distance from the MovingCube's position to the relevant vertex. If the distance to the collision is less than the distance to the vertex, the collision happened inside the cube.
Raycasting is a poor method of collision detection because it only detects collisions in the exact directions rays are cast. It also has some additional edge cases. For example, if the ray is cast from inside another object, the other object might not be considered to be colliding. As another example, raycasting in Three.js uses bounding spheres (or, if unavailable, bounding boxes) to calculate ray intersection, so rays can "intersect" with objects even if they wouldn't hit them visually.
If you're only dealing with spheres or upright cuboids, it's straightforward math to check collision. (That's why Three.js uses bounding spheres and bounding boxes - and most applications that need to do collision checking use secondary collision-only geometries that are less complicated than the rendered ones.) Spheres are colliding if the distance between their centers is less than the sum of their radii. Boxes are colliding if the edges overlap (e.g. if the left edge of box 1 is to the left of the right edge of box 2, and the boxes are within a vertical distance the sum of their half-heights and a horizontal distance the sum of their half-lengths).
For certain applications you can also use voxels, e.g. divide the world into cubical units, do box math, and say that two objects are colliding if they overlap with the same cube-unit.
For more complex applications, you'll probably want to use a library like Ammo.js, Cannon.js, or Physi.js.
The reason raycasting is appealing is because it's workable with more complex geometries without using a library. As you've discovered, however, it's less than perfect. :-)
I wrote a book called Game Development with Three.js which goes into this topic in some depth. (I won't link to it here because I'm not here to promote it, but you can Google it if you're interested.) The book comes with sample code that shows how to do basic collision detection, including full code for a 3D capture-the-flag game.

Getting the real screen width in pixels of a div/image using Three.js and the CSS3DRenderer

Really been struggling with this one.
I have a program that uses the CSS3D Renderer of Three.js and I am trying to get projected "real" screen-width in pixels of a div (which contains a single image).
I've tried using getBoundingClientRect() on the image but the widths and heights returned from that function are wrong. I assume due to a perspective issue.
I have also tried this method which gives me an accurate center coordinate and is from this post by mrdoob: https://stackoverflow.com/a/11605007
var width = window.innerWidth, height = window.innerHeight;
var widthHalf = width / 2, heightHalf = height / 2;
var projector = new THREE.Projector();
var vector = projector.projectVector( object.matrixWorld.getPosition().clone(), this.camera );
vector.x = ( vector.x * widthHalf ) + widthHalf;
vector.y = -( vector.y * heightHalf ) + heightHalf;
Unfortunately all methods I am finding for getting the top,left corner or any corner of the object use Geometry objects which the CSS3DObject does not use (ie. getting the vector of the corner vertices and projecting those vectors ref: https://stackoverflow.com/a/14044103/2009076).
Anyone have any idea of how to accomplish this? To be concise, I would just like to get the real on-screen size of a div (which fits tightly around its img) in a Three.js CSS3D scene. In the end I would like to detect if the camera zoom has made an image bigger than its original size in pixels.
Thanks!

Categories