Roman Guivan

Posted on

a flying and rotating quadcopter in three.js

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);

if (!flags.isPlayerOnTheGround) {
this.rotate(timeSeconds)
this.moveForward(timeSeconds);
this.moveSideways(timeSeconds);
}

if (controller.armed) {
}

``````

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.