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) {
            mesh.position.copy(body.getPosition());
            mesh.quaternion.copy(body.getQuaternion());
        }
    }
}
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
upforce.applyQuaternion(body.getQuaternion());
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);
        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);
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);
        forward.normalize();
        right.normalize();

        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)