DEV Community

Cover image for a flying and rotating quadcopter in three.js
Roman Guivan
Roman Guivan

Posted on

3 2

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.

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay