a flying and rotating quadcopter in three.js
Roman Guivan
Posted on January 19, 2022
IT IS ALIVE and rolls and spins
In the previous write-up
I've started an ambitious project with drones flying anywhere i want, as long as "anywhere i want" is written in THREE.js.
I got SOMEWHERE using oimo.js rigid body physics simulation, applying lift force in update() loop, using angular velocity on rigid body to perform yaw spins.
The thing i couldn't get right was rotation around X and Z axes.
See, rigid body physics engines all provide multiple body types, two that you need to know of for getting this article are:
- DYNAMIC (moved with impulses and FORCE VECTORS, collide other bodies as if in "REAL LIFE")
- KINEMATIC (things just go through each other, you set their position and rotation yourself, but collision events are still fired somewhere, just saying HEY you're flying through CUBE-A, hehe)
What kind of body was my drone in part1? - If you said dynamic, you were right. And then before each render i ran:
export function updateMeshPositions(bodies) {
let i = bodies.length;
while (i--) {
const body = bodies[i];
const mesh = meshes[i];
if (!body.sleeping && mesh) {
mesh.position.copy(body.getPosition());
mesh.quaternion.copy(body.getQuaternion());
}
}
}
keeping physics and 3d meshes in sync.
Angular force for rotation + lift as linear velocity were not all that precious of "REAL SIMULATIONS" anyways, so i have given up and switched to kinematic body type.
Forces to codes
Two things that WORKED were gravity, lift, that i got calling
const {x,y,z} = {x: 0, y: MAX_LIFT * controller.throttle, z: 0};
const upforce = new Vector3(x,y,z);
// to make Y force a RELATIVE y based on body position, not WORLD Y
upforce.applyQuaternion(body.getQuaternion());
body.applyImpulse(body.getPosition(), upforce);
and rotation via
body.angularVelocity += MAX_ANGLE_PER_FRAME * controller.yaw
I've left our LERPs for the simplicity here. So what would that be for a kinematic object?
Knowing that F = ma
, and gravity is mg
is enough.
// called every frame
update(timeSinceStart = 0) {
const timeMs = timeSinceStart - this.timeSinceStart;
const timeSeconds = timeMs / 1000;
const position = this.mesh.position.clone();
const sumOfForces = new Vector3(0, 0, 0);
const throttleAcceleration = (controller.throttle + 1) / 2 * MAX_THROTTLE;
const gravityVector = new Vector3(0, KINEMATIC_GRAVITY * timeSeconds, 0);
const upforceVector = new Vector3(0, throttleAcceleration * timeSeconds, 0);
upforceVector.applyQuaternion(this.mesh.quaternion);
sumOfForces.add(gravityVector);
if (!flags.isPlayerOnTheGround) {
this.rotate(timeSeconds)
this.moveForward(timeSeconds);
this.moveSideways(timeSeconds);
}
if (controller.armed) {
sumOfForces.add(upforceVector);
}
position.add(sumOfForces);
Thats your "moving up and down", covered.
then pitch, yaw and roll become just transforms on mesh
rotate() {
this.nextFrameRotation = { ...this.nextFrameRotation, y: MAX_YAW_ANGLE * controller.yaw };
}
moveForward() {
// rotate obj
this.nextFrameRotation = ({ ... this.nextFrameRotation, x: - MAX_ROTATION_ANGLE * controller.roll });
}
moveSideways() {
this.nextFrameRotation = { ...this.nextFrameRotation, z: MAX_ROTATION_ANGLE * -controller.pitch }
}
and once the next angles are there - i'm spinning the body
const { x, y, z } = this.nextFrameRotation;
let forward = new Vector3(0, 0, 1);
let right = new Vector3(1, 0, 0);
forward.normalize();
right.normalize();
this.mesh.rotateOnAxis(forward, z); //sideways
this.mesh.rotateOnAxis(right, x); //forward
this.mesh.rotateOnAxis(this.mesh.up, y); //yaw
aaand that's it mostly. For collisions i'd use a raycast in each direction. Now with no "physics" my chances of wrapping it all into a "camera controls" plugin for THREE are way higher. Sweet stuff.
Guess all that i have for you today, babies. Wait for part three, perhaps it's gonna be an exciting one.
Posted on January 19, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 30, 2024
November 30, 2024