DEV Community

Cover image for a flying and rotating quadcopter in three.js
Roman Guivan
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) {
Enter fullscreen mode Exit fullscreen mode

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
body.applyImpulse(body.getPosition(), upforce);
Enter fullscreen mode Exit fullscreen mode

and rotation via

body.angularVelocity += MAX_ANGLE_PER_FRAME * controller.yaw
Enter fullscreen mode Exit fullscreen mode

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


        if (!flags.isPlayerOnTheGround) {

        if (controller.armed) {

Enter fullscreen mode Exit fullscreen mode

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 }
Enter fullscreen mode Exit fullscreen mode

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

        this.mesh.rotateOnAxis(forward, z); //sideways 
        this.mesh.rotateOnAxis(right, x); //forward 
        this.mesh.rotateOnAxis(this.mesh.up, y); //yaw 
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (0)