three-demo
Version:
A demo framework for three.js.
227 lines (221 loc) • 6.1 kB
JavaScript
/**
* three-demo v5.1.3 build Mon Dec 06 2021
* https://github.com/vanruesc/three-demo
* Copyright 2021 Raoul van Rüschen
* @license Zlib
*/
// src/core/Demo.ts
import { LoadingManager, Scene } from "three";
var Demo = class {
id;
renderer;
loadingManager;
assets;
scene = new Scene();
camera;
constructor(id = "demo") {
this.id = id;
this.renderer = null;
this.loadingManager = new LoadingManager();
this.assets = /* @__PURE__ */ new Map();
this.scene = new Scene();
this.camera = null;
}
setRenderer(renderer) {
this.renderer = renderer;
return this;
}
getCamera() {
return this.camera;
}
load() {
return Promise.resolve();
}
initialize() {
}
update(deltaTime, timestamp) {
}
render(deltaTime, timestamp) {
this.renderer.render(this.scene, this.camera);
}
registerOptions(menu) {
}
dispose() {
}
reset() {
this.scene = new Scene();
this.camera = null;
return this;
}
};
// src/core/DemoManager.ts
import { GUI } from "dat.gui";
import {
EventDispatcher,
OrthographicCamera,
PerspectiveCamera,
WebGLRenderer as WebGLRenderer2
} from "three";
// src/core/DemoManagerEvent.ts
var DemoManagerEvent = class {
type;
previousDemo;
demo;
constructor(type) {
this.type = type;
this.previousDemo = null;
this.demo = null;
}
};
// src/core/demo-manager-events.ts
var change = new DemoManagerEvent("change");
var load = new DemoManagerEvent("load");
// src/core/DemoManager.ts
var MILLISECONDS_TO_SECONDS = 1 / 1e3;
var DemoManager = class extends EventDispatcher {
renderer;
timestamp;
menu;
loadedDemos;
demos;
currentDemo;
demoId;
constructor(viewport, params) {
super();
const options = Object.assign({
aside: viewport,
renderer: null
}, params);
this.renderer = options.renderer;
if (this.renderer === null) {
const renderer = new WebGLRenderer2();
renderer.setSize(viewport.clientWidth, viewport.clientHeight);
renderer.setPixelRatio(window.devicePixelRatio);
this.renderer = renderer;
}
this.timestamp = 0;
this.menu = new GUI({ autoPlace: false });
this.loadedDemos = /* @__PURE__ */ new Set();
this.demos = /* @__PURE__ */ new Map();
this.currentDemo = null;
this.demoId = null;
viewport.appendChild(this.renderer.domElement);
options.aside.appendChild(this.menu.domElement);
}
resetMenu() {
const node = this.menu.domElement.parentNode;
const menu = new GUI({ autoPlace: false });
if (this.demos.size > 1) {
const selection = menu.add({ demo: this.demoId }, "demo", [...this.demos.keys()]);
selection.onChange((value) => this.loadDemo(value));
}
node.removeChild(this.menu.domElement);
node.appendChild(menu.domElement);
this.menu.destroy();
this.menu = menu;
return menu;
}
startDemo(demo) {
if (demo.id === this.demoId) {
demo.initialize();
demo.registerOptions(this.resetMenu());
this.loadedDemos.add(demo);
load.demo = demo;
this.dispatchEvent(load);
}
}
loadDemo(id) {
const nextDemo = this.demos.get(id);
const currentDemo = this.currentDemo;
const renderer = this.renderer;
window.location.hash = nextDemo.id;
if (currentDemo !== null) {
currentDemo.dispose();
currentDemo.reset();
}
this.menu.domElement.style.display = "none";
change.previousDemo = currentDemo;
change.demo = nextDemo;
this.currentDemo = nextDemo;
this.demoId = id;
this.dispatchEvent(change);
this.loadedDemos.delete(nextDemo);
renderer.clear();
nextDemo.load().then(() => this.startDemo(nextDemo)).catch((e) => console.error(e));
}
getCurrentDemo() {
return this.currentDemo;
}
addDemo(demo) {
const hash = window.location.hash.slice(1);
const currentDemo = this.currentDemo;
this.demos.set(demo.id, demo.setRenderer(this.renderer));
if (this.demoId === null && hash.length === 0 || demo.id === hash) {
this.loadDemo(demo.id);
}
this.resetMenu();
if (currentDemo !== null && this.loadedDemos.has(currentDemo)) {
currentDemo.registerOptions(this.menu);
}
return this;
}
removeDemo(id) {
const demos = this.demos;
if (demos.has(id)) {
demos.delete(id);
if (this.demoId === id && demos.size > 0) {
const entries = Array.from(demos.entries());
const firstEntry = entries[0];
this.currentDemo = firstEntry[1];
this.loadDemo(firstEntry[0]);
} else {
this.demoId = null;
this.currentDemo = null;
this.renderer.clear();
}
}
return this;
}
setSize(width, height, updateStyle = true) {
const demo = this.currentDemo;
const camera = demo.getCamera();
this.renderer.setSize(width, height, updateStyle);
if (demo !== null && camera !== null) {
if (camera instanceof OrthographicCamera) {
camera.left = width / -2;
camera.right = width / 2;
camera.top = height / 2;
camera.bottom = height / -2;
camera.updateProjectionMatrix();
} else if (camera instanceof PerspectiveCamera) {
camera.aspect = width / height;
camera.updateProjectionMatrix();
}
}
}
render(timestamp) {
const demo = this.currentDemo;
const elapsed = (timestamp - this.timestamp) * MILLISECONDS_TO_SECONDS;
this.timestamp = timestamp;
if (demo !== null && this.loadedDemos.has(demo)) {
demo.update(elapsed, timestamp);
demo.render(elapsed, timestamp);
}
}
};
// src/utils/CameraUtils.ts
var DEG2RAD = Math.PI / 180;
var RAD2DEG = 180 / Math.PI;
function calculateVerticalFoV(horizontalFoV, aspect = 16 / 9) {
return Math.atan(Math.tan(horizontalFoV * DEG2RAD * 0.5) / aspect) * RAD2DEG * 2;
}
function calculateHorizontalFoV(verticalFoV, aspect = 16 / 9) {
return Math.atan(Math.tan(verticalFoV * DEG2RAD * 0.5) * aspect) * RAD2DEG * 2;
}
export {
Demo,
DemoManager,
DemoManagerEvent,
calculateHorizontalFoV,
calculateVerticalFoV
};