I have a run into a problem with FPS camera controls in a three.js scene. I'm using a Raycaster to determine the camera group position based on it's intersection with the scene along the Y axis (a poor man's gravity if you will =) and then apply user input to move around. The camera group position keeps getting reset to the intersection location on every frame, essentially gluing you to the spot.
I'm assuming this is either an updateMatrix() problem or a that a Vector3 is getting passed by reference somewhere, but for the life of me I can't seem to put my finger on it. I need some help... I hope this code is clear enough to help understand the problem :
renderer.setAnimationLoop((event) => {
if (clock.running) {
update();
renderer.render(scene,character.view);
}
});
clock.start();
//
const update = () => {
// velocity
const velocity = new THREE.Vector3();
velocity.x = input.controller.direction.x;
velocity.z = input.controller.direction.y;
velocity.clampLength(0,1);
if (velocity.z < 0) {
velocity.z *= 1.4;
}
// gravity
if (scene.gravity.length() > 0) {
const origin = new THREE.Vector3().copy(character.view.position);
const direction = new THREE.Vector3().copy(scene.gravity).normalize();
const intersection = new THREE.Raycaster(origin,direction).intersectObjects(scene.collision).shift();
if (intersection) {
character.group.position.copy(intersection.point);
character.group.updateMatrix();
}
}
// rotation
const rotation = new THREE.Euler();
rotation.x = input.controller.rotation.y;
rotation.y = input.controller.rotation.x;
character.group.rotation.set(0,rotation.y,0);
character.view.rotation.set(rotation.x,0,0);
// velocity.applyEuler(rotation);
const quaternion = new THREE.Quaternion();
character.group.getWorldQuaternion(quaternion);
velocity.applyQuaternion(quaternion);
// collision
const origin = new THREE.Vector3().setFromMatrixPosition(character.view.matrixWorld);
const direction = new THREE.Vector3().copy(velocity).normalize();
const raycaster = new THREE.Raycaster(origin,direction);
for (const intersection of raycaster.intersectObjects(scene.collision)) {
if (intersection.distance < 0.5) {
// face normals ignore object quaternions
const normal = new THREE.Vector3().copy(intersection.face.normal);
const matrix = new THREE.Matrix4().extractRotation(intersection.object.matrixWorld);
normal.applyMatrix4(matrix);
// normal
normal.multiplyScalar(velocity.clone().dot(normal));
velocity.sub(normal);
}
}
// step
const delta = 0.001 / clock.getDelta();
velocity.multiplyScalar(delta);
// apply
character.group.position.add(velocity);
}
The camera setup is a lot like the PointerLockControls helper, the camera being a child of a Group Object for yaw and pitch. The controller input is defined elsewhere as it can come from the mouse or a gamepad, but it returns normalized values.
To be more precise, the part that is causing the problem is here :
// gravity
if (scene.gravity.length() > 0) {
const origin = new THREE.Vector3().copy(character.view.position);
const direction = new THREE.Vector3().copy(scene.gravity).normalize();
const intersection = new THREE.Raycaster(origin,direction).intersectObjects(scene.collision).shift();
if (intersection) {
character.group.position.copy(intersection.point);
character.group.updateMatrix();
}
}
if I comment out character.group.position.copy(intersection.point);, for example, the camera moves like it's supposed to (except of course it's flying), but otherwise it moves a frame's worth of distance and then gets reset back to the intersection point on the next frame.
I have tried all manner of updateMatrix(), updateMatrixWorld(), updateProjectionMatrix(), and Object.matrixWorldNeedsUpdate = true, but alas no joy.
I apologise for using a copy/paste of my code rather than a testable case scenario. Thank you for your time.
Holy cow, I feel dumb... const origin = new THREE.Vector3().copy(character.view.position); returns local space coordinates, of course it gets reset to the origin!
replacing it with const origin = new THREE.Vector3().setFromMatrixPosition(character.view.matrixWorld); gives me the proper result
There's a lesson in there somewhere about staring blankly et your code for too long. I hope at least that this question helps someone out there one day.
Related
I'm trying to make a simple game. Tell me how to make object (comet) fly and rotate around orbit of another object (planet). I could only make the comet turn and fly toward the planet, but it is necessary that when a certain distance is reached, the comet starts to rotate around the planet’s orbit.
IMAGE
this.mesh.translateZ(this.speed);
if (this.target.position.distanceTo(this.mesh.position) >= 100) {
let targetQuaternion = new THREE.Quaternion();
let rotationMatrix = new THREE.Matrix4();
rotationMatrix.lookAt(this.mesh.position, this.target.position, this.mesh.up);
targetQuaternion.setFromRotationMatrix(rotationMatrix);
if (!this.mesh.quaternion.equals(targetQuaternion)) {
let step = 0.01;
this.mesh.quaternion.rotateTowards(targetQuaternion, step);
}
} else {
//how to make a comet(this.mesh) fly around the orbit of the planet(this.target)?
}
The simplest way to make one object orbit another is to utilize parent-child relationships. If you add a child to an object it shares a transformation, so when the parent is rotated the child is rotated with it - and when the child has a large translation offset, it effectively "orbits" the parent.
Very simple example of this:
init() {
this.planet = new Mesh(new SphereBufferGeometry(100))
this.satellite = new Mesh(new SphereBufferGeometry(5))
this.satelliteCenter = new Object3D()
this.satellite.position.set(1000,0,0)
this.satelliteCenter.add(this.satellite)
this.scene.add(this.planet)
this.scene.add(this.satelliteCenter)
}
update() {
this.satelliteCenter.rotateY(Math.PI / 2000) // one full revolution every 1000 frames
}
I want to rotate an object3D with hammerjs gestures.
Basically the rotation work, but with two issues I can't figure out.
The direction of the rotation changes randomly. It turns left. I stop and than move my fingers in the same diction again and suddenly instead of continuing to rotate left its going right. Happens sometimes but not always.
Once the rotation is started it only rotates to the same direction, despite me moving my fingers to different directions.
Here is how I handle the rotation:
public rotateObject3D (e: HammerInput): void {
if (rotationEnabled) {
const translation = new THREE.Vector3();
const rotation = new THREE.Quaternion();
const scale = new THREE.Vector3();
const rotateMatrix = new THREE.Matrix4();
const deltaRotationQuaternion = new THREE.Quaternion();
this._myObject.matrix.decompose(translation, rotation, scale);
this._deltaRot = (e.rotation * 0.01);
deltaRotationQuaternion.setFromEuler(new THREE.Euler(
0,
this._deltaRot * (Math.PI / 180),
0,
'XYZ'
));
deltaRotationQuaternion.multiplyQuaternions(deltaRotationQuaternion, rotation);
this._myObject.matrix = rotateMatrix.compose(translation, deltaRotationQuaternion, scale);
}
}
And this is the call of it:
this._hammerManager.on('rotate', (e) => {
this._arTools.rotateObject3D(e);
});
Is there anything I am missing?
It looks like you're using the absolute rotation value, instead of the change in rotation. For instance, consider the following scenario:
Rotation1: 12°
Rotation2: 10°
Rotation3: 5°
By multiplying your quaternions, your object is being rotated to 12°, then 22°, then finally 27°, even though you were turning back towards 0. This is because you're adding the new rotation to the last rotation on each event.
What you should do is save the previous rotation value, and subtract it from the new one, to get the rotation delta:
var previousRot = 0;
var newRot = 0;
rotateObject3D(e) {
newRot = e.rotation - previousRot;
// You could use newRot for your quaternion calculations
// but modifying .rotation.y is simpler
myObject.rotation.y += newRot
// Save value to be used on next event
previousRot = newRot;
}
With this method, the scenario above will give you the change in rotation. Your object will first rotate by +12°, then -2°, then -5°, for a more natural behavior.
Just make sure to reset previousRot = 0 on HammerJS' rotateend event so you don't use the value from previous gestures when a new one begins.
I'm using three.js on a separate canvas on top of a openlayers map. the way I synchronize the view of the map and the three.js camera (which looks straight down onto the map) works fine, and looks like this:
function updateThreeCam(
group, // three.js group — has camera in it
view, // ol3 view
map // ol3 map
) {
// get visible part of map
const extent = getViewExtent(view, map);
const h = ol.extent.getHeight(extent);
const mapCenter = ol.extent.getCenter(extent);
// calculates how for away from the ground the camera has to
// be to match the currently visible part of the map
const camDist = h / (2 * Math.tan(constants.CAM_V_FOV_RAD / 2));
camera.updateProjectionMatrix(); // needed?
group.position.set(...mapCenter, camDist);
group.updateMatrixWorld();
}
however, when I am animating the flight of an object from in front of the camera down to the ground, the movement is not smooth: the vertical movement is jumping a lot. https://jsbin.com/qohutabofe/2/edit?js,output
this is the animation code:
// construct a spline to animate along
const waypoints = [
objStartPosition,
objEndPosition,
];
const pathSpline = new THREE.CatmullRomCurve3(waypoints);
// const pathSpline = new THREE.LineCurve3(...waypoints);
startTime = Date.now();
// [...]
function animate() {
const diff = Date.now() - startTime;
const t = diff / aniDuration;
// sample curve at `t`
const pos = pathSpline.getPointAt(t);
// update position
obj.position.copy(pos);
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
what is weird is that I don't really see the jumps in the sampled positions:
as you can see the map coordinates tend to be very big numbers. that's why I thought it might be a float precision issue. — dividing everything by 1000 indeed seems to help. there is just two new problems with that:
the foreshortening effect is way less drastic / not realistic anymore
panning does not work as expected anymore: the rectangle on the ground does not stay in its position
https://jsbin.com/bujepediro/1/edit?js,output
I am writing a trace-line function for a visualization project that requires jumping between time step values. My issue is that during rendering, the line created using THREE.js's BufferGeometry and the setDrawRange method, will only be visible if the origin of the line is in the camera's view. Panning away will result in the line disappearing and panning toward the origin of the line (usually 0,0,0) will make it appear again. Is there a reason for this and a way around it? I have tried playing around with render settings.
The code I have included is being used in testing and draws the trace of the object as time progresses.
var traceHandle = {
/* setup() returns trace-line */
setup : function (MAX_POINTS) {
var lineGeo = new THREE.BufferGeometry();
//var MAX_POINTS = 500*10;
var positions = new Float32Array( MAX_POINTS * 3 ); // 3 vertices per point
lineGeo.addAttribute('position', new THREE.BufferAttribute(positions, 3));
var lineMaterial = new THREE.LineBasicMaterial({color:0x00ff00 });
var traceLine = new THREE.Line(lineGeo, lineMaterial);
scene.add(traceLine);
return traceLine;
},
/****
* updateTrace() updates and draws trace line
* Need 'index' saved globally for this
****/
updateTrace : function (traceLine, obj, timeStep, index) {
traceLine.geometry.setDrawRange( 0, timeStep );
traceLine.geometry.dynamic = true;
var positions = traceLine.geometry.attributes.position.array;
positions[index++]=obj.position.x;
positions[index++]=obj.position.y;
positions[index++]=obj.position.z;
// required after the first render
traceLine.geometry.attributes.position.needsUpdate = true;
return index;
}
};
Thanks a lot!
Likely, the bounding sphere is not defined or has radius zero. Since you are adding points dynamically, you can set:
traceLine.frustumCulled = false;
The other option is to make sure the bounding sphere is current, but given your use case, that seems too computationally expensive.
three.js r.73
I am trying to rotate the camera smoothly and without altering the y-vector of the camera direction, i can use look at, and it changes the camera direction in a flash, but this is not working for me, I would like a smooth transition as the direction of the camera changes. I have been reading up, and not understanding everything, but it seems to me that quaternions are the solution to this problem.
I have this.object (my camera) moving along a set path (this.spline.points). The location of the camera at any one time is (thisx,thisy, thisz)
I have cc[i] the direction vector for the direction I would like the camera to face (formerly I was using lookat(cc[i]) which changes the direction correctly, but too quickly/instantaneously)
Using info I have read, I have tried this below, and it just resulted in the screen going black at the point when the camera is due to move.
Could anyone please explain if I am on the right track, how to correct my code.
Thanks
var thisx = this.object.matrixWorld.getPosition().x.toPrecision(3);
var thisy = this.object.matrixWorld.getPosition().y.toPrecision(3);
var thisz = this.object.matrixWorld.getPosition().z.toPrecision(3);
var i = 0;
do {
var pathx = this.spline.points[i].x.toPrecision(3);
var pathz = this.spline.points[i].z.toPrecision(3);
if (thisx == pathx && thisz == pathz){
this.object.useQuaternion = true;
this.object.quaternion = new THREE.Quaternion(thisx, thisy, thisz, 1);
var newvect;
newvect.useQuaternion = true;
newvect.quaternion = new THREE.Quaternion(thisx+cc[i].x, thisy+cc[i].y, thisz+cc[i].z, 1);
var newQuaternion = new THREE.Quaternion();
THREE.Quaternion.slerp(this.object.quaternion, newvect.quaternion, newQuaternion, 0.5);
this.object.quaternion = newQuaternion;
//this.object.lookAt( cc[i]);
i = cc.length;
} else i++;
} while(i < cc.length);
There is no need to call this.object.useQuaternion = true. That is default behavior.
Also, this.object.quaternion contains the current rotation, so no need to generate that either.
You might want to try a different approach - construct the rotation matrix from the spline position, lookAt and up vectors, creating a path of quaternions as a preprocessing step:
var eye = this.spline.points[i].clone().normalize();
var center = cc[i].normalize();
var up = this.object.up.normalize();
var rotMatrix = new THREE.Matrix4().lookAt(eye, center, up);
You could then create the quaternions from the rotation matrix:
var quaternionAtSplineCoordinates = [];
quaternionAtSplineCoordinates.push(new THREE.Quaternion().setFromRotationMatrix(rotMatrix));
Once you have that path, you could apply the quaternion to the camera in your animation loop - provided you have a large enough number of samples. Otherwise, you could consider using slerp to generate the intermediate points.