👔Director

three.js ⟩ Director

// 2023.03.06 - 18:37 (/) ThreejsManager -> Director
// 2023.02.23 - 10:41 (•) first edition
// -----------------------------------------------------------------------

import * as THREE from 'three';

// ⭐ Director
// ---------------
// .canvas, .width, .height, .aspect
// .scene, .camera, .renderer
// .models
// .setup
// .update
class Director {

    // ---------------------------------------
    //     ⭐ init
    // ---------------------------------------

    constructor({
        scene, camera, renderer, 
        canvas, 
        setup,                      // further setup
        rootModels = {},            // added to scene directly
        models = {},                // a dictionary of `THREE.Mesh`
        update = function(){},      // update function for animate loop
    }={}) {

        // ⭐ canvas
        if (!canvas) {
            // by default, <canvas> is 300x150
            canvas = document.createElement('canvas');
            document.body.appendChild(canvas);
        }
        
        this.canvas = canvas;

        if (this.width === 0 || this.height === 0) {
            throw new Error(`ThreejsManager: canvas width/height = 0 `);
        }

        // ---------------------------------------
        //     setup scene / camera / renderer
        // ---------------------------------------
        
        // To display anything, we need: 
        // 1. scene 2. camera 3. renderer
        // so that we can render the scene with camera.

        // ⭐ 1. scene
        this.scene = scene || new THREE.Scene();
        
        // ⭐ 2. camera
        // camera defaults to looking down the -Z axis with +Y up.
        this.camera = new THREE.PerspectiveCamera(
            // frustum:
            // ----------------------------------------
            // • fov: (vertical) field of view (in degrees)
            // • aspect: width / height
            // • near/far planes:
            //   - height: determined by FOV.
            //   - width : determined by FOV and aspect.
            camera ?. fov ?? 75,    
            camera ?. aspect ?? this.aspect,
            camera ?. near ?? 0.1,
            camera ?. far ?? 100
        );
        
        // ⭐ 3. renderer
        if (!renderer) {
            renderer = new THREE.WebGLRenderer({canvas});
            // renderer.setSize( this.width, this.height );    // canvas size will be fixed❗
        }

        this.renderer = renderer;
        
        // ---------------------------------------
        //     ⭐ add root models to the scene
        // ---------------------------------------

        Object.keys(rootModels).forEach(name => {
            const m = rootModels[name];
            if (Array.isArray(m)) {
                // array of models
                m.forEach(model => this.scene.add(model));
            } else {
                // single model
                this.scene.add(m);
            }
        });
        
        this.models = models;
        this.rootModels = rootModels;

        // ---------------------------------------
        //     ⭐ further setup
        // ---------------------------------------
        if (setup) setup.apply(this);

        // -------------------------------
        //     ⭐ update for animate loop
        // -------------------------------
        
        this.update = update;
    }

    // ---------------------------------------
    //     ⭐ canvas info
    // ---------------------------------------

    get width() { return this.canvas.clientWidth }
    get height() { return this.canvas.clientHeight }
    get aspect() { return this.width / this.height }

    get isCanvasResized() {
        const c = this.canvas;
        // (width, height): logical CSS size
        // (clientWidth, clientHeight): physically displayed size
        return c.width !== c.clientWidth || c.height !== c.clientHeight;
    }

    // ---------------------------------------
    //     ⭐ animate loop
    // ---------------------------------------

    animate(t) {

        t *= 0.001    // ms -> s

        // ⭐ if canvas resized, update camera/renderer's settings.
        if (this.isCanvasResized) {

            // camera
            this.camera.aspect = this.aspect;
            this.camera.updateProjectionMatrix();
            
            // renderer
            // - (set canvas "resolution" size)
            // - false: WITHOUT altering CSS settings 
            //   (let the browser decide the canvas's "displayed" size)
            this.renderer.setSize(this.width, this.height, false);    
        }
        
        // ⭐ update
        this.update(t);
        
        // ⭐ render the scene with camera
        this.renderer.render( this.scene, this.camera );

        // ⭐ requestAnimationFrame
        //  • passes the time since the page loaded (in ms).
        //  • 60 FPS (frames per second)
        requestAnimationFrame( this.animate.bind(this) );    // ⭐ .bind(this) is a MUST!
    }
    
}

// -----------------------
//     factory objects
// -----------------------

// ⭐ Light
const Light = {
    
    // Directional lights have position/target, 
    // both default to (0, 0, 0).
    Directional(color, intensity) {
        return new THREE.DirectionalLight(color, intensity);
    },

    // point light
    Point(color, intensity) {
        return new THREE.PointLight(color, intensity);
    },
};

// ⭐ Geometry
const Geometry = {
    
    // box
    Box(...args) {
        return new THREE.BoxGeometry(...args);
    },
    
    // sphere
    Sphere(...args) {
        return new THREE.SphereGeometry(...args);
    },

    // buffer
    Buffer: {
        fromPoints(pts) {
            return new THREE.BufferGeometry().setFromPoints(pts);
        },
    },
    
};

// ⭐ Material
const Material = {
    
    // for Meshes
    Mesh: {
        // not affected by lights
        Basic(opts) {
            return new THREE.MeshBasicMaterial( opts );
        },

        // affected by lights
        Phong(opts) {
            return new THREE.MeshPhongMaterial(opts);
        }
    },

    // for Lines
    Line: {
        // basic
        Basic(opts) {
            return new THREE.LineBasicMaterial( opts );
        },
    },
    
};

// ⭐ Mesh
function Mesh(geometry, material) {
    return new THREE.Mesh( geometry, material );
}

// Line
function Line(geo, mat) {
    return new THREE.Line( geo, mat );
}

// Vector
function Vector(x, y, z) {
    return new THREE.Vector3(x, y, z);
}

// ⭐ export
// -----------------
export {
    Director,
    Light,
    Geometry,
    Material,
    Mesh,
    Line,
    Vector,
};

History

0    (•) first edition

Last updated