diff --git a/src/01-bezierCurves/Curve.ts b/src/01-bezierCurves/Curve.ts index 502dbb2a066db02f54dd71746d91cf256368bdcd..d5c7a8ded2f4c52a44f93e264a13775b168991fa 100644 --- a/src/01-bezierCurves/Curve.ts +++ b/src/01-bezierCurves/Curve.ts @@ -38,6 +38,10 @@ class Curve extends Line2d { this._generator = new CubicBezierCurve(this.positions, new DeCasteljauAlgorithm()); } + public get resolution(): number { + return this._resolution; + } + public update(_deltaTime: number): void { this._points = this._generator.evaluatePositions(this._resolution); this._points.forEach((point) => { diff --git a/src/01-bezierCurves/CurveHelper.ts b/src/01-bezierCurves/CurveHelper.ts index 0b08618caea9104672413a538bca4b78b2c7ac32..1e4496ea2d7cd2944c5572fd1bee0b3064d967a6 100644 --- a/src/01-bezierCurves/CurveHelper.ts +++ b/src/01-bezierCurves/CurveHelper.ts @@ -1,14 +1,16 @@ import * as THREE from "three"; import { Curve } from "./Curve"; -import { PipelineObserver, PipelineRenderable } from "../core/Pipeline"; -import { Line1d } from "../core/Shapes"; -import { DeCasteljauAlgorithm } from "./CubicBezierCurve"; +import { PipelineDraggable, PipelineGUI, PipelineObserver, PipelineRenderable } from "../core/Pipeline"; +import { Line1d, Point2d } from "../core/Shapes"; +import { DeCasteljauAlgorithm, BernsteinAlgorithm } from "./CubicBezierCurve"; import dat from "dat.gui"; -class CurveHelper implements PipelineObserver, PipelineRenderable { +class CurveHelper implements PipelineObserver, PipelineRenderable, PipelineGUI { + + public curve: Curve; public group: THREE.Group; - private _curve: Curve; + private _point: Point2d; private _lines: Array<Line1d>; private _offset: number; private _speed: number = 1; @@ -16,11 +18,16 @@ class CurveHelper implements PipelineObserver, PipelineRenderable { t: number = 0; constructor(curve: Curve, offset: number = 0) { - this._curve = curve; + this.curve = curve; this._offset = offset; this._lines = new Array<Line1d>(); this.group = new THREE.Group(); + this._point = new Point2d(.03, 32, new THREE.Color(0xff00ff)); + this._point.material = new THREE.MeshBasicMaterial({ + color: new THREE.Color(0xff00ff)}); + this._point._object3d.position.z = this._offset; + this._lines.push(new Line1d([new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()], new THREE.Color(0xffff00))); this._lines.push(new Line1d([new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()], new THREE.Color(0x00ffff))); this._lines.push(new Line1d([new THREE.Vector3(), new THREE.Vector3()], new THREE.Color(0xff00ff))); @@ -43,11 +50,17 @@ class CurveHelper implements PipelineObserver, PipelineRenderable { this._lines.forEach((line) => { this.group.add(line.object()); }); + this.group.add(this._point.object()); + } - this.gui() + gui(gui: dat.GUI): void { + const folder = gui.addFolder('CurveHelper'); + folder.add(this, 't', 0, 1, 0.01).listen(); + folder.add(this, '_speed', 1, 2, 0.1).name("speed"); + folder.closed = false; } - object(): THREE.Object3D<THREE.Event> { + object(): THREE.Object3D { return this.group; } @@ -55,14 +68,9 @@ class CurveHelper implements PipelineObserver, PipelineRenderable { return this.group.position; } - gui() : void { - const test = new dat.GUI(); - test.add(this, 't', 0, 1); - } - public update(_deltaTime: number): void { let points = new Array<THREE.Vector3>(); - this._curve.positions.forEach((position) => { + this.curve.positions.forEach((position) => { const p = position.clone(); p.z = this._offset; points.push(p); @@ -73,15 +81,107 @@ class CurveHelper implements PipelineObserver, PipelineRenderable { this._lines[1].points = points; points = DeCasteljauAlgorithm.iteration(points, this.t); this._lines[2].points = points; + points = DeCasteljauAlgorithm.iteration(points, this.t); + this._point._object3d.position.x = points[0].x; + this._point._object3d.position.y = points[0].y; } } -class CurveHelperExtension { +class CurveHelperBernstein implements PipelineObserver, PipelineRenderable, PipelineDraggable { + + private _helperReference: CurveHelper; + + private _xAxis: Line1d; + private _yAxis: Line1d; + private _graphs: Array<Line1d>; + private _point: Array<Point2d>; + private _background: THREE.Mesh; + private _group: THREE.Group; + + constructor(helper: CurveHelper, offset: number = 0) { + this._helperReference = helper; + this._xAxis = new Line1d( + [new THREE.Vector3(-.5, -.5, 0), new THREE.Vector3(.5, -.5, 0)], + new THREE.Color(0xff0000)); + this._yAxis = new Line1d( + [new THREE.Vector3(-.5, -.5, 0), new THREE.Vector3(-.5, .5, 0)], + new THREE.Color(0x00ff00)); + this._graphs = new Array<Line1d>(); + this._point = new Array<Point2d>(); + + this._background = new THREE.Mesh( + new THREE.PlaneGeometry(1, 1), + new THREE.MeshBasicMaterial({ color: 0xffffff, opacity: 0.5, transparent: true })); + this._background.position.z = offset; + + const points: Array< Array<THREE.Vector3>> = new Array< Array<THREE.Vector3>>(); + for (let i = 0; i < 4; i++) { + points.push(new Array<THREE.Vector3>()); + } + + const resolution = this._helperReference.curve.resolution; + for (let i = 0; i < resolution + 1; i++) { + let t = i / resolution; + let coefficients = BernsteinAlgorithm.calculateCoefficients(t); + coefficients.forEach((c, index) => { + points[index].push(new THREE.Vector3(t, c, 0.001).sub(new THREE.Vector3(0.5, 0.5, 0))); + }); + } + + for (let i = 0; i < 4; i++) { + this._graphs.push(new Line1d(points[i], new THREE.Color(0x0000ff))); + } + + const material = new THREE.MeshBasicMaterial({ color: 0x0000ff }); + const coefficient = BernsteinAlgorithm.calculateCoefficients(this._helperReference.t); + for (let i = 0; i < 4; i++) { + this._point.push(new Point2d(.02, 32, new THREE.Color(0x0000ff))); + this._point[i].material = material; + this._point[i].position().copy(new THREE.Vector3(this._helperReference.t, coefficient[i], 0.001)); + } + + this._group = new THREE.Group(); + this._group.add(this._xAxis.object()); + this._group.add(this._yAxis.object()); + this._graphs.forEach((graph) => { + this._group.add(graph.object()); + }); + this._point.forEach((point) => { + this._group.add(point.object()); + }); + this._group.add(this._background); + + this._xAxis._object3d.parent = this._background; + this._yAxis._object3d.parent = this._background; + this._graphs.forEach((graph) => { + graph._object3d.parent = this._background; + }); + this._point.forEach((point) => { + point._object3d.parent = this._background; + }); + + } + + hitbox(): THREE.Object3D { + return this._background; + } + + object(): THREE.Object3D { + return this._group; + } + + position(): THREE.Vector3 { + return this._group.position; + } - constructor(helper: CurveHelper) { - helper.group.position.set(0, 0, 0); + update(_deltaTime: number): void { + const coefficient = BernsteinAlgorithm.calculateCoefficients(this._helperReference.t); + for (let i = 0; i < 4; i++) { + this._point.push(new Point2d(.02, 32, new THREE.Color(0x0000ff))); + this._point[i].position().copy(new THREE.Vector3(this._helperReference.t, coefficient[i], 0.001).sub(new THREE.Vector3(0.5, 0.5, 0))); + } } } -export { CurveHelper } +export { CurveHelper, CurveHelperBernstein } diff --git a/src/01-bezierCurves/DemoBezier.ts b/src/01-bezierCurves/DemoBezier.ts index cc67a5063997334b960596171b6083fb2597a728..1bb096260d4ef668301352ca14ea7297a81e2710 100644 --- a/src/01-bezierCurves/DemoBezier.ts +++ b/src/01-bezierCurves/DemoBezier.ts @@ -2,7 +2,7 @@ import * as THREE from 'three'; import { PipelineData } from "../core/Pipeline"; import { Point2d } from "../core/Shapes"; import { Curve } from './Curve'; -import { CurveHelper } from './CurveHelper'; +import { CurveHelper, CurveHelperBernstein } from './CurveHelper'; class BezierDemo extends PipelineData { public constructor() { super(); } @@ -31,16 +31,30 @@ class BezierDemo extends PipelineData { }); const curveHelper = new CurveHelper(curve, -.5); + const curveHelperBernstein = new CurveHelperBernstein(curveHelper, .25); this.addObject(curve); this.addObject(curveHelper); - this.addObject(pointA, true); - this.addObject(tangentA, true); - this.addObject(pointB, true); - this.addObject(tangentB, true); + this.addObject(curveHelperBernstein); + + this.addDraggable(curveHelperBernstein); this.addObserver(curve); this.addObserver(curveHelper); + this.addObserver(curveHelperBernstein); + + this.addGUI(curveHelper); + + this.addObject(pointA); + this.addObject(tangentA); + this.addObject(pointB); + this.addObject(tangentB); + + this.addDraggable(pointA); + this.addDraggable(tangentA); + this.addDraggable(pointB); + this.addDraggable(tangentB); + this.addObserver(pointA); this.addObserver(tangentA); this.addObserver(pointB); diff --git a/src/core/Pipeline.ts b/src/core/Pipeline.ts index 6383b92e7258a7b9f3815f68f52f733091268689..f02edc8778ba6f0ac8e27255bc4c66df7a0423d8 100644 --- a/src/core/Pipeline.ts +++ b/src/core/Pipeline.ts @@ -1,3 +1,4 @@ +import dat from "dat.gui"; import * as THREE from "three"; import { DragControls } from "three/examples/jsm/controls/DragControls"; @@ -26,29 +27,51 @@ interface PipelineRenderable { position(): THREE.Vector3; } +interface PipelineDraggable { + + /** + * THREE.Object3d that represents a hit box for the draggable. + */ + hitbox(): THREE.Object3D; +} + +interface PipelineGUI { + /** + * THREE.Object3d that represents a hit box for the draggable. + */ + gui(gui: dat.GUI): void; +} + abstract class PipelineData { observers: Array<PipelineObserver>; + guiElements: Array<PipelineGUI>; draggables: Array<THREE.Object3D>; scene: THREE.Scene; constructor() { this.observers = new Array<PipelineObserver>(); + this.guiElements = new Array<PipelineGUI>(); this.draggables = new Array<THREE.Object3D>(); this.scene = new THREE.Scene(); this.data(); } - protected addObject(renderable: PipelineRenderable, draggable: boolean = false) { + protected addObject(renderable: PipelineRenderable) { this.scene.add(renderable.object()); - if (draggable) { - this.draggables.push(renderable.object()); - } } + protected addDraggable(draggable: PipelineDraggable) { + this.draggables.push(draggable.hitbox()); + } + protected addObserver(observer: PipelineObserver) { this.observers.push(observer); } + protected addGUI(guiElement: PipelineGUI) { + this.guiElements.push(guiElement); + } + /** * @brief Should define a scenario with observers, draggables and scene. * It initializes the scene and adds the draggables to the scene. @@ -64,6 +87,7 @@ class Pipeline { public orbitControls: OrbitControls; public dragControls: DragControls; + public gui: dat.GUI; private scene?: THREE.Scene; private observer: Array<PipelineObserver>; @@ -71,7 +95,6 @@ class Pipeline { constructor(perspective: boolean = true) { // CREATE CAMERA - if (perspective) { this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); @@ -119,6 +142,7 @@ class Pipeline { }); // INITIALISE OTHER FIELDS + this.gui = new dat.GUI(); this.observer = new Array<PipelineObserver>(); this.startTime = performance.now(); } @@ -127,19 +151,13 @@ class Pipeline { this.setScene(data.scene); data.draggables.forEach(draggable => { this.addDraggable(draggable); }); data.observers.forEach(observer => { this.addPipelineObserver(observer); }); + data.guiElements.forEach(guiElement => { this.addGUI(guiElement); }); } public addPipelineObserver(object: PipelineObserver): void { this.observer.push(object); } - public removePipelineObserver(object: PipelineObserver): void { - let index = this.observer.indexOf(object); - if (index > -1) { - this.observer.splice(index, 1); - } - } - public addDraggable(object: THREE.Object3D): void { if (object instanceof THREE.Line) { console.warn('Cannot add draggable to line'); @@ -148,6 +166,10 @@ class Pipeline { this.dragControls.getObjects().push(object); } + public addGUI(object: PipelineGUI): void { + object.gui(this.gui); + } + public setScene(scene: THREE.Scene): void { this.scene = scene; } @@ -176,4 +198,4 @@ class Pipeline { } export { Pipeline, PipelineData }; -export type { PipelineObserver, PipelineRenderable }; +export type { PipelineObserver, PipelineRenderable, PipelineDraggable, PipelineGUI }; diff --git a/src/core/Shapes.ts b/src/core/Shapes.ts index 3c3306ddc72ce217f9c2a08b488be253cf563e7c..9530680a9e337ed3310c51c7b0367840b721a274 100644 --- a/src/core/Shapes.ts +++ b/src/core/Shapes.ts @@ -1,5 +1,5 @@ import * as THREE from "three"; -import { PipelineObserver, PipelineRenderable } from "./Pipeline"; +import { PipelineDraggable, PipelineObserver, PipelineRenderable } from "./Pipeline"; import { LineMaterial } from "three/examples/jsm/lines/LineMaterial"; import { LineGeometry } from "three/examples/jsm/lines/LineGeometry"; @@ -12,7 +12,7 @@ abstract class Shape implements PipelineObserver, PipelineRenderable { * It is not enforced through the construct as initalization of other fields * become uncomfortable. */ - protected _object3d!: THREE.Object3D; + public _object3d!: THREE.Object3D; public object(): THREE.Object3D { return this._object3d!; @@ -25,7 +25,7 @@ abstract class Shape implements PipelineObserver, PipelineRenderable { update(_deltaTime: number): void {/* INTENTIONAL */ } } -abstract class Point extends Shape { +abstract class Point extends Shape implements PipelineDraggable { private _geometry: THREE.BufferGeometry; private _material: THREE.MeshBasicMaterial | THREE.MeshLambertMaterial; @@ -69,6 +69,10 @@ abstract class Point extends Shape { this._material.color = this._color; (this._object3d as THREE.Mesh).material = material; } + + hitbox(): THREE.Object3D<THREE.Event> { + return this._object3d; + } } @@ -224,7 +228,7 @@ class Line2d extends Line { } } -class Mesh extends Shape { +class Mesh extends Shape implements PipelineDraggable { protected _material: THREE.MeshBasicMaterial | THREE.MeshLambertMaterial; protected _color: THREE.Color; @@ -271,6 +275,10 @@ class Mesh extends Shape { } }); } + + hitbox(): THREE.Object3D<THREE.Event> { + return this._object3d; + } }