👔Director
three.js ⟩ Director
replit ⟩ three.js playground
// 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
// 2023.02.23 - 10:41 (•) first edition
// -----------------------------------------------------------------------
import * as THREE from 'three';
// ThreejsManager
// ---------------
// .canvas, .width, .height, .aspect
// .scene, .camera, .renderer
// .models
// .setup
// .update
class ThreejsManager {
// ---------------------------------------
// ⭐ init
// ---------------------------------------
constructor({
scene, camera, renderer,
canvas,
setup, // further setup
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 models to the scene
// ---------------------------------------
Object.keys(models).forEach(name => {
const m = models[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;
// ---------------------------------------
// ⭐ 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);
},
};
// ⭐ Geometry
const Geometry = {
// box
Box(width, height, depth, widthSegments, heightSegments, depthSegments) {
return new THREE.BoxGeometry(
width, height, depth,
widthSegments, heightSegments, depthSegments
);
},
// 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 {
ThreejsManager,
Light,
Geometry,
Material,
Mesh,
Line,
Vector,
};
Last updated