diff --git a/src/00-welcome-and-example/Example.ts b/src/00-welcome-and-example/Example.ts index 2ea25116667309614bbea0c8458fd35b0a5af381..13edf70735ac9abacaa2b7d7be51d5274df2b5a7 100644 --- a/src/00-welcome-and-example/Example.ts +++ b/src/00-welcome-and-example/Example.ts @@ -25,11 +25,11 @@ class Example extends PipelineData { const line2d = new Line2d(pointsB, .01, new THREE.Color(0xffffff)); const mesh = new TriangleMesh("../assets/suzanne.obj", new THREE.Color(0xffffff)); - this.addObject(point2d); - this.addObject(point3d); - this.addObject(line1d); // gives a waring - this.addObject(line2d); - this.addObject(mesh); + this.addRenderable(point2d); + this.addRenderable(point3d); + this.addRenderable(line1d); // gives a waring + this.addRenderable(line2d); + this.addRenderable(mesh); this.addDraggable(point2d); this.addDraggable(point3d); diff --git a/src/00-welcome-and-example/demo.ts b/src/00-welcome-and-example/demo.ts index 3715ef913db4bee18bafa5ad4efa56e5ff4bd8c1..5b9f6a117567b22b9aa04e3416fa83c425ea1462 100644 --- a/src/00-welcome-and-example/demo.ts +++ b/src/00-welcome-and-example/demo.ts @@ -6,7 +6,7 @@ class Demo extends PipelineData { data(): void { const spinningCube = new SpinningCube(); - this.addObject(spinningCube); + this.addRenderable(spinningCube); this.addObserver(spinningCube); } } diff --git a/src/01-bezierCurves/CubicBezierCurve.ts b/src/01-bezierCurves/CubicBezierCurve.ts index 3e475c4fe41cce4f6c1eda070a9b8984c36a0401..4181776d39f46d7174d582ea363e96233be05548 100644 --- a/src/01-bezierCurves/CubicBezierCurve.ts +++ b/src/01-bezierCurves/CubicBezierCurve.ts @@ -41,6 +41,10 @@ class DeCasteljauAlgorithm extends CubicBezierAlgorithm { return positions[0]; } + /** + * Lerping the control points to the given time t and writing the result to the new array. + * It is a generalised form for n control points. + */ public static iteration(points: Array<THREE.Vector3>, t: number): Array<THREE.Vector3> { var result = new Array<THREE.Vector3>(); @@ -50,6 +54,10 @@ class DeCasteljauAlgorithm extends CubicBezierAlgorithm { } } +/** + * CubicBezierCurve holds points and an algorithm to evaluate points. This is the only purpose + * of this class. + */ class CubicBezierCurve { private _algorithm: CubicBezierAlgorithm; diff --git a/src/01-bezierCurves/Curve.ts b/src/01-bezierCurves/Curve.ts index 611c33144ac140fda36514a92ed49be353133722..028a93f73199ee0de427b3bdce7f381f586b5f49 100644 --- a/src/01-bezierCurves/Curve.ts +++ b/src/01-bezierCurves/Curve.ts @@ -45,11 +45,11 @@ class Curve extends Line2d implements PipelineObserver { } public update(_deltaTime: number): void { - this._points = this._generator.evaluatePositions(this._resolution); - this._points.forEach((point) => { + let newPoints = this._generator.evaluatePositions(this._resolution); + newPoints.forEach((point) => { point.z = this._offset; }); - this._createLine(); + this.points = newPoints; } } diff --git a/src/01-bezierCurves/CurveHelper.ts b/src/01-bezierCurves/CurveHelper.ts index 8bd59f6d1bec577a1a6eef814720dc64e522dc6c..d88a83a1c3216c3a6c217520a8d67de99d910c6f 100644 --- a/src/01-bezierCurves/CurveHelper.ts +++ b/src/01-bezierCurves/CurveHelper.ts @@ -6,6 +6,9 @@ import { Point2d } from "../core/Points"; import { DeCasteljauAlgorithm, BernsteinAlgorithm } from "./CubicBezierCurve"; import dat from "dat.gui"; +/** + * Draws the construction lines of the cubic bezier curve. + */ class CurveHelper implements PipelineObserver, PipelineRenderable, PipelineGUI { public curve: Curve; @@ -90,6 +93,9 @@ class CurveHelper implements PipelineObserver, PipelineRenderable, PipelineGUI { } } +/** + * Draws the Bernstein functions with points. + */ class CurveHelperBernstein implements PipelineObserver, PipelineRenderable, PipelineDraggable { private _helperReference: CurveHelper; diff --git a/src/01-bezierCurves/DemoBezier.ts b/src/01-bezierCurves/DemoBezier.ts index a22571cb7f43bd5ec264eec643d88ea1f99a894e..284c8297dd0e02da2e87f02a54db34ae1b479935 100644 --- a/src/01-bezierCurves/DemoBezier.ts +++ b/src/01-bezierCurves/DemoBezier.ts @@ -36,9 +36,9 @@ class BezierDemo extends PipelineData { const curveHelper = new CurveHelper(curve, -.5); const curveHelperBernstein = new CurveHelperBernstein(curveHelper, .25); - this.addObject(curve); - this.addObject(curveHelper); - this.addObject(curveHelperBernstein); + this.addRenderable(curve); + this.addRenderable(curveHelper); + this.addRenderable(curveHelperBernstein); this.addDraggable(curveHelperBernstein); @@ -49,10 +49,10 @@ class BezierDemo extends PipelineData { this.addGUI(curve); this.addGUI(curveHelper); - this.addObject(pointA); - this.addObject(tangentA); - this.addObject(pointB); - this.addObject(tangentB); + this.addRenderable(pointA); + this.addRenderable(tangentA); + this.addRenderable(pointB); + this.addRenderable(tangentB); this.addDraggable(pointA); this.addDraggable(tangentA); diff --git a/src/02-quaternion/DemoQuaternion.ts b/src/02-quaternion/DemoQuaternion.ts index eae46e03a3813b18158d6a847c09f851f642ee2d..4119b83854ff9ba97f079fe25561b996aaadc701 100644 --- a/src/02-quaternion/DemoQuaternion.ts +++ b/src/02-quaternion/DemoQuaternion.ts @@ -21,18 +21,18 @@ class QuaternionDemo extends PipelineData { this.scene.add(pointLight); const axes = new Axes(5); - this.addObject(axes); + this.addRenderable(axes); const rotationObject = new TriangleMesh("../../assets/suzanne.obj", new THREE.Color(0xffffff)); rotationObject.material = new THREE.MeshLambertMaterial({ color: 0xffffff }); - this.addObject(rotationObject); + this.addRenderable(rotationObject); const rotator: Rotator = new Rotator(rotationObject.object()); this.addObserver(rotator); this.addGUI(rotator); const rotationHelper = new RotationHelper(rotator, new THREE.Color(0xffffff), new THREE.Color(0xff0000)); - this.addObject(rotationHelper); + this.addRenderable(rotationHelper); this.addObserver(rotationHelper); this.addDraggable(rotationHelper); } diff --git a/src/02-quaternion/Quaternion.ts b/src/02-quaternion/Quaternion.ts index fc1aeacfb40fe7c92b82fbc61b8702aa27c8fcf8..62b877e2a152916fbf5fcf00d14af61676b9b9e8 100644 --- a/src/02-quaternion/Quaternion.ts +++ b/src/02-quaternion/Quaternion.ts @@ -49,24 +49,6 @@ export class Quaternion { return [this._x, this._y, this._z, this._w]; } - public fromEuler(euler: THREE.Vector3): this { - const c1 = Math.cos(euler.x / 2); - const s1 = Math.sin(euler.x / 2); - const c2 = Math.cos(euler.y / 2); - const s2 = Math.sin(euler.y / 2); - const c3 = Math.cos(euler.z / 2); - const s3 = Math.sin(euler.z / 2); - - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - - this.copy(Quaternion.normalize(this)); - - return this; - } - public toString(): string { return `Quaternion(${this._x}, ${this._y}, ${this._z}, ${this._w})`; } diff --git a/src/02-quaternion/Rotator.ts b/src/02-quaternion/Rotator.ts index d90515d2474b98c359bd2b0388d8083c116cede8..16990162ab0b330a1f5b9d7968dc376668c0cae6 100644 --- a/src/02-quaternion/Rotator.ts +++ b/src/02-quaternion/Rotator.ts @@ -71,11 +71,11 @@ class Rotator implements PipelineObserver, PipelineGUI { switch (event.key) { case 'a': case 'A': - this._t += .001; + this._t += .01; break; case 'd': case 'D': - this._t -= .001; + this._t -= .01; break; } this._t = Math.max(0, Math.min(1, this._t)); @@ -139,8 +139,8 @@ class Rotator implements PipelineObserver, PipelineGUI { } gui(gui: dat.GUI): void { - const folder = gui.addFolder('Rotator'); - folder.add(this, "_mode", Mode).listen().name("Mode").onFinishChange(() => { + this._guiRoot = gui.addFolder('Rotator'); + this._guiRoot.add(this, "_mode", Mode).listen().name("Mode").onFinishChange(() => { for (let i = 0; i < this._orientations.length; i++) { if (this._mode !== Mode[0]) this._guiRoot.__folders["Quaternion " + (i + 1)].close(); @@ -148,18 +148,19 @@ class Rotator implements PipelineObserver, PipelineGUI { this._guiRoot.__folders["Quaternion " + (i + 1)].open(); } }); - folder.add(this, 'addQuaternion').name("Add Quaternion"); - folder.add(this, 'popQuaternion').name("Pop Quaternion"); - folder.add(this, 'normaliseQuaternions').name("Normalise Quaternions"); - const slerpGUI = folder.addFolder("Slerp"); - slerpGUI.add(this, "_t", 0, 1, 0.001).listen().name("t"); + this._guiRoot.add(this, 'addQuaternion').name("Add Quaternion"); + this._guiRoot.add(this, 'popQuaternion').name("Pop Quaternion"); + this._guiRoot.add(this, 'normaliseQuaternions').name("Normalise Quaternions"); + this._rangeController = this._guiRoot.add(this, '_index', 0, this._orientations.length - 1, 1).name("Index").listen(); + this._guiRoot.closed = false; + + const slerpGUI = this._guiRoot.addFolder("Slerp Quaternion"); + slerpGUI.add(this, "_t", 0, 1, 0.01).listen().name("t"); slerpGUI.add(this._slerp, "x").step(.001).listen().name("x"); slerpGUI.add(this._slerp, "y").step(.001).listen().name("y"); slerpGUI.add(this._slerp, "z").step(.001).listen().name("z"); slerpGUI.add(this._slerp, "w").step(.001).listen().name("w"); - this._rangeController = folder.add(this, '_index', 0, this._orientations.length - 1, 1).name("Index").listen(); - folder.closed = false; - this._guiRoot = folder; + slerpGUI.closed = false; } update(_deltaTime: number): void { diff --git a/src/02-quaternion/RotatorHelper.ts b/src/02-quaternion/RotatorHelper.ts index 787d27f2c1d64522c50cae873dd7da42ffa033c3..74e1a4f48930b8a35540d2952c3f057d119f79aa 100644 --- a/src/02-quaternion/RotatorHelper.ts +++ b/src/02-quaternion/RotatorHelper.ts @@ -23,10 +23,14 @@ class RotationHelper extends Shape implements PipelineObserver, PipelineDraggabl this._quaternionsReference = rotator.orientations; this._slerpReference = rotator.slerp; + // UNIT SPHERE + // The projection of ijk onto the sphere. The fourth dimension is the color. const geometrySphere = new THREE.SphereBufferGeometry(1, 32, 32); const matSphere = new THREE.MeshBasicMaterial({ color: primeColor, opacity: 0.9, transparent: true }); this._unitSphere = new THREE.Mesh(geometrySphere, matSphere); + // POINTS AND LINES + // The points and lines are the projections of the quaternions onto the sphere. this._qPoints = []; this._qLines = []; for (let i = 0; i < 5; i++) @@ -35,9 +39,12 @@ class RotationHelper extends Shape implements PipelineObserver, PipelineDraggabl this._qPoints[i].material = new THREE.MeshBasicMaterial({ color: secColor }); this._qLines.push(new Line1d([new THREE.Vector3(), new THREE.Vector3()], secColor)); } + + // This is the slerped quaternion this._qPoints.push(new Point3d(.05, 16)); this._qPoints[5].material = new THREE.MeshBasicMaterial({ color: terColor }); + // ADD TO SCENE this._object3d = new THREE.Group(); this._object3d.add(this._unitSphere); for (let i = 0; i < 6; i++) @@ -68,12 +75,14 @@ class RotationHelper extends Shape implements PipelineObserver, PipelineDraggabl } for (let i = this._quaternionsReference.length; i < this._qPoints.length - 1; i++) { + // RESET NOT USED POINTS let p = this._unitSphere.position; this._qPoints[i]._object3d.position.set(p.x, p.y, p.z); this._qLines[i].points = [p, p]; this._qLines[i].points = [p, p]; } + // CHANGE THE SLERP POINT if (this._rotator.mode === Mode[1]) { let q = this._slerpReference; let p = new THREE.Vector3(q.x, q.y, q.z); diff --git a/src/Main.ts b/src/Main.ts index 39ae54112b2f0fb94da3123acc4a912c885a76c1..8c18ac5623d36adaa992ac6bcb1150da1ed87f6c 100644 --- a/src/Main.ts +++ b/src/Main.ts @@ -6,6 +6,10 @@ import { BezierDemo } from "./01-bezierCurves/demoBezier"; import { QuaternionDemo } from "./02-quaternion/demoQuaternion"; import { SimulationDemo } from "./03-simulation/demoSimulation"; +/** + * The Main.ts will be called first. It set a window.onload function that creates + * a pipeline to render different scenarios. + */ window.onload = () => { const ref = window.location.href diff --git a/src/core/Lines.ts b/src/core/Lines.ts index d79373428531a5bb5fb5b0a1ce2877084313e011..f638ec4582bd7a87ac2bb9332bd88faf34fb4327 100644 --- a/src/core/Lines.ts +++ b/src/core/Lines.ts @@ -5,6 +5,9 @@ import { LineGeometry } from "three/examples/jsm/lines/LineGeometry"; import { PipelineGUI } from "./Pipeline"; import { GUI } from "dat.gui"; +/** + * Class to uniformly create lines. + */ abstract class Line extends Shape { protected _points: Array<THREE.Vector3>; protected _color: THREE.Color; @@ -17,24 +20,29 @@ abstract class Line extends Shape { this._color = color; if (this instanceof Line2d) { + // Line2d must be a mesh to render properly. this._object3d = new THREE.Mesh(geometry, material); } else { this._object3d = new THREE.Line(geometry, material); } } - protected abstract _createLine(): void; + protected abstract createLine(): void; + protected abstract updateLine(): void; + + // updates or creates a new buffer if points have changed. public set points(points: Array<THREE.Vector3>) { if (this._points.length !== points.length) { this._points = points; - this._createLine(); + this.createLine(); } else { this._points = points; - (this._object3d as THREE.Line).geometry.setFromPoints(points); + this.updateLine(); } } + // converting points protected static pointsToBuffer(points: Array<THREE.Vector3>): Array<number> { if (points.length < 1) { return []; @@ -52,10 +60,14 @@ class Line1d extends Line { new THREE.LineBasicMaterial({ color: color.getHex(), linewidth: .1 })); } - protected _createLine(): void { + protected createLine(): void { (this._object3d as THREE.Line).geometry.dispose(); (this._object3d as THREE.Line).geometry = new THREE.BufferGeometry().setFromPoints(this._points); } + + protected updateLine(): void { + (this._object3d as THREE.Line).geometry.setFromPoints(this._points); + } } class Line2d extends Line implements PipelineGUI { @@ -77,11 +89,15 @@ class Line2d extends Line implements PipelineGUI { ((this._object3d as THREE.Mesh).material as LineMaterial).linewidth = linewidth; } - protected _createLine(): void { + protected createLine(): void { (this._object3d as THREE.Line).geometry.dispose(); (this._object3d as THREE.Line).geometry = new LineGeometry().setPositions(Line.pointsToBuffer(this._points)); } + protected updateLine(): void { + ((this._object3d as THREE.Line).geometry as LineGeometry).setPositions(Line.pointsToBuffer(this._points)); + } + gui(gui: GUI): void { const folder = gui.addFolder("Line2d"); folder.add(this, "lineWidth", 0, .2, .001).name("Line Width"); diff --git a/src/core/Loader.ts b/src/core/Loader.ts deleted file mode 100644 index ab2daf647be164c90d50fe5d0a9a7112b4e3785a..0000000000000000000000000000000000000000 --- a/src/core/Loader.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'; -import { Mesh } from './Shapes'; - -export class Loader { - - private static _obj = new OBJLoader(); - - private static _counter = 0; - - private static objectName(): string { - return `mesh-${Loader._counter++}`; - } - - public static load(path: string, scene: THREE.Scene, color: THREE.Color): Mesh { - - const name = Loader.objectName(); - Loader._obj.load(path, (object: THREE.Group) => { - scene.add(object); - object.name = name; - }); - - const object = scene.getObjectByName(name); - - if (object === undefined) - throw new Error(`Could not find object with name ${name}`); - - return new Mesh(object, color); - - - - } - -} \ No newline at end of file diff --git a/src/core/Pipeline.ts b/src/core/Pipeline.ts index f02edc8778ba6f0ac8e27255bc4c66df7a0423d8..3546ad639de5d133cc9dbd4541b96ff6d3790f4b 100644 --- a/src/core/Pipeline.ts +++ b/src/core/Pipeline.ts @@ -4,29 +4,41 @@ import * as THREE from "three"; import { DragControls } from "three/examples/jsm/controls/DragControls"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; +/** + * PipelineObserver is an abstract class that is used to observe the renderer. + */ interface PipelineObserver { /** - * PipelineObserver is an abstract class that is used to observe the renderer. - * The renderer calls in the rendering loop update of this observer. - * @param {number} _deltaTime gives the time in seconds between two frames + * The renderer calls in the rendering loop the update of this observer. + * @param {number} _deltaTime gives the time in seconds between two frames. This + * is important to have stable simulations. */ update(_deltaTime: number): void; } +/** + * PipelineRenderable is an abstract class that is used to have a unified interface + * accessing different types of objects. It offers a convenient way to access the + * THREE.js object's. + */ interface PipelineRenderable { /** - * PipelineRenderable is an abstract class that is used to have a unified interface - * to access 3d objectes of an object. + * Access the THREE.js object. */ object(): THREE.Object3D; /** - * Access the position of the 3d object directly. + * Access the position of the THREE.js object directly. */ position(): THREE.Vector3; } +/** + * This lets one customise which parts of the Object should be used to identify + * the hit box. The drag controls uses raycasting to determine, hence controling + * which THREE.js objects count as hit box is important. + */ interface PipelineDraggable { /** @@ -35,13 +47,23 @@ interface PipelineDraggable { hitbox(): THREE.Object3D; } +/** + * PipelineGUI is an abstract class that is used to add a GUI for an object. + * Object can define themselves as GUI elements that are appended to the root + * GUI element. + */ interface PipelineGUI { + /** - * THREE.Object3d that represents a hit box for the draggable. + * Add a GUI element to the root GUI element. */ gui(gui: dat.GUI): void; } +/** + * PipelineData defines all relevant data for a pipeline. In other words, it is + * a scenario describing a situation with objects and their behaviour. + */ abstract class PipelineData { observers: Array<PipelineObserver>; guiElements: Array<PipelineGUI>; @@ -56,30 +78,46 @@ abstract class PipelineData { this.data(); } - protected addObject(renderable: PipelineRenderable) { + /** + * Add objects that should be rendered in the scene + */ + protected addRenderable(renderable: PipelineRenderable) { this.scene.add(renderable.object()); } + /** + * Define objects that should be draggable. + */ protected addDraggable(draggable: PipelineDraggable) { this.draggables.push(draggable.hitbox()); } + /** + * define the objects that should be updated. + */ protected addObserver(observer: PipelineObserver) { this.observers.push(observer); } + /** + * Define objects that should be added to the GUI. + */ protected addGUI(guiElement: PipelineGUI) { this.guiElements.push(guiElement); } /** - * @brief Should define a scenario with observers, draggables and scene. + * Should define a scenario with observers, draggables and scene. * It initializes the scene and adds the draggables to the scene. * Based on that the pipeline can be initialized. */ abstract data(): void; } +/** + * Pipeline governs all data in this program. It takes care that in the rendering loop + * is updated or displayed correctly. + */ class Pipeline { public readonly renderer: THREE.WebGLRenderer; diff --git a/src/core/Points.ts b/src/core/Points.ts index c31dbc062f750bb8b8bd518cf463f2ea970ed76d..6ea94dc4bce85b688d248330f7604ab24882c8ad 100644 --- a/src/core/Points.ts +++ b/src/core/Points.ts @@ -2,6 +2,9 @@ import * as THREE from "three"; import { PipelineDraggable } from "./Pipeline"; import { Shape } from "./Shapes"; +/** + * General interface to create points. + */ abstract class Point extends Shape implements PipelineDraggable { protected _radius: number; protected _resolution: number; diff --git a/src/core/Shapes.ts b/src/core/Shapes.ts index a3e8a69cc524d68ed9f14589132a3e6697b519ef..230e017cbc0f6a67a3bbdb870d471261f241e955 100644 --- a/src/core/Shapes.ts +++ b/src/core/Shapes.ts @@ -2,6 +2,10 @@ import * as THREE from "three"; import { PipelineObserver, PipelineRenderable } from "./Pipeline"; import { LineMaterial } from "three/examples/jsm/lines/LineMaterial"; +/** + * Convenience class for creating a shapes. It provides a simple interface for + * creating THREE.js object without handling buffers or initialising them. + */ abstract class Shape implements PipelineRenderable { /** @@ -11,6 +15,9 @@ abstract class Shape implements PipelineRenderable { */ public _object3d!: THREE.Object3D; + /** + * Change the color of an object + */ public set color(color: THREE.Color) { if (this._object3d instanceof THREE.Mesh) { const mr = (this._object3d as THREE.Mesh).material; @@ -29,6 +36,9 @@ abstract class Shape implements PipelineRenderable { } } + /** + * Change the material of an object + */ public set material(material: THREE.Material) { if (this._object3d instanceof THREE.Mesh) { (this._object3d as THREE.Mesh).material = material; diff --git a/src/core/TriangleMesh.ts b/src/core/TriangleMesh.ts index b975e64394c8de6b361f48cb630a739d482dc37b..2eebac4ff7b6b951c1b9f38738e41128989ff8de 100644 --- a/src/core/TriangleMesh.ts +++ b/src/core/TriangleMesh.ts @@ -3,6 +3,9 @@ import { PipelineDraggable } from "./Pipeline"; import { Shape } from "./Shapes"; import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader"; +/** + * Capsulate loading an OBJ file and setting a mesh. + */ class TriangleMesh extends Shape implements PipelineDraggable { constructor(obj: string, color: THREE.Color) { super();