diff --git a/src/main.ts b/src/main.ts index 502c7c2bcd8d26ab77384ab47577007b509a3c2b..50f7b9a00733cba06f8a43a0de339079c967fe73 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,32 +1,13 @@ -import * as CG from "./uitls/rendering"; -import * as THREE from "three"; -import { UI } from "./uitls/ui"; -import * as Interpolation from "./uitls/bezierCurve"; +import * as CG from "./uitls/Rendering"; +import { UI } from "./uitls/UI"; +import { Curve2d } from "./uitls/Curve2d"; const render = new CG.RenderManager('#canvas', { near: 0.1, far: 1000, fov: 45, height: 1 }); const ui = new UI(); -const startPoint = new THREE.Vector2(-1, -1); -const endPoint = new THREE.Vector2(1, 1); -const startControlPoint = new THREE.Vector2(-1, 1); -const endControlPoint = new THREE.Vector2(1, -1); +const curve = new Curve2d(); +curve.objects().forEach(obj => { render.add(obj); }); -// const curve = new Interpolation.BezierCurveTest( -// startPoint, -// startControlPoint, -// endControlPoint, -// endPoint -// ) -const curve = new Interpolation.BezierCurve( - startPoint, - endPoint, - startControlPoint, - endControlPoint -) - -const line = curve.createLine(); - -render.add(line); render.render(); ui.addModifiable(render); diff --git a/src/uitls/CubicBezierCurve2d.ts b/src/uitls/CubicBezierCurve2d.ts new file mode 100644 index 0000000000000000000000000000000000000000..498ef7cf8915497bca95577747656453fc22b769 --- /dev/null +++ b/src/uitls/CubicBezierCurve2d.ts @@ -0,0 +1,163 @@ +import { Vector2 } from "three"; + +function lerp(a: Vector2, b: Vector2, t: number): Vector2 { + return new Vector2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); +} + +export class CubicBezierCurve2d { + private _startPoint: Vector2; + private _endPoint: Vector2; + private _startControlPoint: Vector2; + private _endControlPoint: Vector2; + + private _dirty: boolean = true; + private _resolution: number = 5; + private _points: Vector2[] = []; + private _coefficients: number[][] = []; + + constructor( + startPoint: Vector2, + endPoint: Vector2, + startControlPoint: Vector2, + endControlPoint: Vector2 + ) { + this._startPoint = startPoint; + this._endPoint = endPoint; + this._startControlPoint = startControlPoint; + this._endControlPoint = endControlPoint; + } + + get startPoint(): Vector2 { + return this._startPoint; + } + + get endPoint(): Vector2 { + return this._endPoint; + } + + get startControlPoint(): Vector2 { + return this._startControlPoint; + } + + get endControlPoint(): Vector2 { + return this._endControlPoint; + } + + get points(): Vector2[] { + return this._points; + } + + get coefficients(): number[][] { + return this._coefficients; + } + + set startPoint(value: Vector2) { + this._startPoint = value; + this._dirty = true; + this.generatePoints(this._resolution); + } + + set endPoint(value: Vector2) { + this._endPoint = value; + this._dirty = true; + this.generatePoints(this._resolution); + } + + set startControlPoint(value: Vector2) { + this._startControlPoint = value; + this._dirty = true; + this.generatePoints(this._resolution); + } + + set endControlPoint(value: Vector2) { + this._endControlPoint = value; + this._dirty = true; + this.generatePoints(this._resolution); + } + + bernstain(s: number): [Vector2, number[]] { + + // p(t) = (1-t)³ * p0 + 3 * (1-t)² * t * p1 + + // 3 * (1-t) * t² * p2 + t³ * p3 + // => subtitute: 1 - t = s + // p(t) = k³ * p0 + 3 * k² * t * p1 + + // 3 * k * t² * p2 + t³ * p3 + + const p0 = this.startPoint; + const p1 = this.startControlPoint; + const p2 = this.endControlPoint; + const p3 = this.endPoint; + + // set k = 1 - s => substitution + const t = s; + const k = 1 - s; + + // calculate the coefficients + const u0 = 1 * Math.pow(k, 3) * Math.pow(t, 0); + const u1 = 3 * Math.pow(k, 2) * Math.pow(t, 1); + const u2 = 3 * Math.pow(k, 1) * Math.pow(t, 2); + const u3 = 1 * Math.pow(k, 0) * Math.pow(t, 3); + + // calculate the point + const p = new Vector2( + u0 * p0.x + u1 * p1.x + u2 * p2.x + u3 * p3.x, + u0 * p0.y + u1 * p1.y + u2 * p2.y + u3 * p3.y + ); + + return [p, [u0, u1, u2, u3]]; + } + + deCasteljau(t: number): Vector2[][] { + + const p0 = this.startPoint; + const p1 = this.startControlPoint; + const p2 = this.endControlPoint; + const p3 = this.endPoint; + + // iterative implementation of deCasteljau + let points: Vector2[] = [p0, p1, p2, p3]; + let temporary: Vector2[] = []; + + const results: Vector2[][] = []; + results.push(points); + while (points.length > 1) { + temporary = []; + for (let i = 0; i < points.length - 1; i++) + temporary.push(lerp(points[i], points[i + 1], t)); + points = temporary; + results.push(points); // save every iteration + } + + return results; + } + + generatePoints(resolution: number): Vector2[] { + + if (!this._dirty && this._points.length === resolution) + return this._points; + + this._coefficients = []; + this._points = []; + + this._resolution = resolution; + const sampleSize = 1 / resolution; + + for (let t = 0; t <= 1; t += sampleSize) { + t = Math.round(t * 10000) / 10000; + const [point, coefficients] = this.bernstain(t); + this._points.push(point); + this._coefficients.push(coefficients); + } + + this._dirty = false; + + return this._points; + } + + generatePointAt(t: number): [Vector2[][], number[], Vector2] { + const points = this.deCasteljau(t); + const coefficients = this._coefficients[Math.floor(t * this._resolution)]; + const point = points[points.length - 1]; + return [points, coefficients, point[0]]; + } +} diff --git a/src/uitls/Curve2d.ts b/src/uitls/Curve2d.ts new file mode 100644 index 0000000000000000000000000000000000000000..2badea50376f42c667290f35c94edb677cce5f78 --- /dev/null +++ b/src/uitls/Curve2d.ts @@ -0,0 +1,216 @@ +import { GUI } from "dat.gui"; +import { BufferGeometry, CircleGeometry, Color, Group, Line, LineBasicMaterial, Mesh, MeshBasicMaterial, Object3D, Vector2 } from "three"; +import { CubicBezierCurve2d } from "./CubicBezierCurve2d"; +import { Animatable, Updatable, Modifiable } from "./Interfaces"; +import { UI } from "./UI"; + + +export class Curve2d implements Animatable, Updatable, Modifiable { + private _curve: CubicBezierCurve2d = new CubicBezierCurve2d( + new Vector2(0, 0), + new Vector2(1, 1), + new Vector2(0.5, 0), + new Vector2(0.5, 1) + ); + + private _reference: Group = new Group(); + private _bernstain: Group = new Group(); + private _points: Group = new Group(); + private _line: Line = new Line(); + + public t: number = .5; + public resolution: number = 100; + public positionBernstain: Vector2 = new Vector2(1.5,0); + + constructor() { + this._curve.generatePoints(this.resolution); + this._line.material = new LineBasicMaterial({ color: 0xffffff }); + + this._bernstain.position.set(this.positionBernstain.x, this.positionBernstain.y, 0); + this._points.position.set(this.positionBernstain.x, this.positionBernstain.y, 0); + + this.createReference(); + this.createLine(); + this.createBernstain(); + } + + /** + * @returns {Object3D[]} Contains all the object which are used to draw the curve + */ + objects(): Object3D[] { + return [this._bernstain, this._line, this._reference, this._points]; + } + + /** + * Updates static objects + */ + update(): void { + this._curve.generatePoints(this.resolution); + this._line.geometry.dispose(); + this._line.geometry = new BufferGeometry().setFromPoints(this._curve.points); + this._line.geometry.attributes.position.needsUpdate = true; + + this._bernstain.position.set(this.positionBernstain.x, this.positionBernstain.y, 0); + this._points.position.set(this.positionBernstain.x, this.positionBernstain.y, 0); + + this.animate(this.t); + } + + /** + * Takes care of updating dynamic objects + * @param delta t value between 0 and 1 to find the position of the bernstein + */ + animate(delta: number): void { + const [cpoints, coefficients] = this._curve.generatePointAt(delta); + + // update the reference + for (let i = 0; i < cpoints.length; i++) { + + // update lines + for (let j = 0; j < cpoints[i].length - 1; j++) { + // update lines + const line = this._reference.getObjectByName(`line_${i}_${j}_${j + 1}`) as Line; + line.geometry.setFromPoints([cpoints[i][j], cpoints[i][j + 1]]); + line.geometry.attributes.position.needsUpdate = true; + } + + // update circle + for (let j = 0; j < cpoints[i].length; j++) { + const circle = this._reference.getObjectByName(`point_${i}_${j}_${j + 1}`) as Mesh; + circle.position.set(cpoints[i][j].x, cpoints[i][j].y, 0); + } + } + + // update the bernstain + for (let i = 0; i < coefficients.length; i++) { + const bernstain = this._points.getObjectByName(`bernstain_point_${i}`) as Mesh; + bernstain.position.set(delta, coefficients[i], 0); + } + + + } + + /** + * Build the UI elements + */ + createElement(gui: GUI): void { + const curve = gui.addFolder('Curve'); + curve.add(this, 'resolution', 100, 500).step(1).onChange(() => this.update()); + curve.add(this, 't', 0, 1).step(0.01).onChange(() => this.animate(this.t)); + UI.addVector<Vector2>(curve, this, this.positionBernstain, 'Bernstain position') + UI.addVector<Vector2>(curve, this, this._curve.startPoint, 'Start Point'); + UI.addVector<Vector2>(curve, this, this._curve.endPoint, 'End Point'); + UI.addVector<Vector2>(curve, this, this._curve.startControlPoint, 'Start Control Point'); + UI.addVector<Vector2>(curve, this, this._curve.endControlPoint, 'End Control Point'); + } + + + private createLine(): void { + this._line.geometry = new BufferGeometry().setFromPoints(this._curve.points); + this._line.position.set(0, 0, 0); + } + + + private createReference(): void { + const cpoints = this._curve.generatePointAt(0.74)[0]; + + for (let i = 0; i < cpoints.length; i++) { + + const color = new Color(0xffffff); + color.setHSL(i / cpoints.length, 1, 0.5); + + const mat_point = new MeshBasicMaterial({ color: color.getHex() }); + const mat_line = new LineBasicMaterial({ color: color.getHex() }); + + // create lines + for (let j = 0; j < cpoints[i].length - 1; j++) { + const p1 = cpoints[i][j]; + const p2 = cpoints[i][j + 1]; + + const lineGeometry = new BufferGeometry().setFromPoints([p1, p2]); + const line = new Line(lineGeometry, mat_line); + line.name = `line_${i}_${j}_${j + 1}`; + line.geometry.setFromPoints([p1, p2]); + this._reference.add(line); + } + + // create circle on every point with the color of the line + for (let j = 0; j < cpoints[i].length; j++) { + const circle = new Mesh(new CircleGeometry(0.01, 32), mat_point); + circle.position.set(cpoints[i][j].x, cpoints[i][j].y, 0); + circle.name = `point_${i}_${j}_${j + 1}`; + this._reference.add(circle); + } + + } + + } + + private createBernstain(): void { + + // prepare vector for lines + const points: Vector2[][] = []; + for (let i = 0; i < this._curve.coefficients[0].length; i++) + points.push([]); + + // create points + let max = this._curve.coefficients.length; + for (let i = 0; i < max; i++) { + let bernstainY = this._curve.coefficients[i]; + let X = i / max; + + for (let i = 0; i < bernstainY.length; i++) { + const Y = bernstainY[i]; + const point = new Vector2(X, Y); + points[i].push(point); + } + } + + // create the lines + for (let i = 0; i < points.length; i++) { + const lineGeometry = new BufferGeometry().setFromPoints(points[i]); + + const color = new Color(0xffffff); + let s = i / points.length; + color.setHSL(s, 1 - s, 0.5); + const line = new Line(lineGeometry, new LineBasicMaterial({ color: color.getHex() })); + line.name = `bernstain_${i}`; + this._bernstain.add(line); + } + + + const lineGeometry = new BufferGeometry().setFromPoints([ + new Vector2(0, 0), + new Vector2(0, 1) + ]); + this._bernstain.add( + new Line(lineGeometry, new LineBasicMaterial({ color: 0xffffff })) + ); + + const lineGeometry2 = new BufferGeometry().setFromPoints([ + new Vector2(0, 0), + new Vector2(1, 0) + ]); + this._bernstain.add( + new Line(lineGeometry2, new LineBasicMaterial({ color: 0xffffff })) + ); + + // get coefficients at 0.5 + const coefficients = this._curve.generatePointAt(0.5)[1]; + + // create the points + for (let i = 0; i < coefficients.length; i++) { + const point = new Vector2(0.5, coefficients[i]); + const pointGeometry = new CircleGeometry(0.01, 32); + const pointMaterial = new MeshBasicMaterial({ color: 0xffffff }); + const pointMesh = new Mesh(pointGeometry, pointMaterial); + pointMesh.name = `bernstain_point_${i}`; + pointMesh.position.set(point.x, point.y, 0); + this._points.add(pointMesh); + + } + + } + + +} \ No newline at end of file diff --git a/src/uitls/Interfaces.ts b/src/uitls/Interfaces.ts new file mode 100644 index 0000000000000000000000000000000000000000..c41b1dc2c6a99bd3a3fae4da252244bd16bb586a --- /dev/null +++ b/src/uitls/Interfaces.ts @@ -0,0 +1,25 @@ +import { GUI } from "dat.gui"; + + + +export interface Animatable { + animate(delta: number): void; +} + +/** + * Class which implement this interface can be updated. It is important for + * the GUI element to be updated when the object is changed. + * @interface Updatable + */ +export interface Updatable { + update(): void; +} + +/** + * Class which implement this interface do create a GUI element for the object. + * The element will be added to the GUI which is passed to the constructor. + * @interface Modifiable + */ +export interface Modifiable { + createElement(gui: GUI): void; +} \ No newline at end of file diff --git a/src/uitls/bezierCurve.ts b/src/uitls/bezierCurve.ts deleted file mode 100644 index 40465d9a8f4529e9deb26a865fce99253cc326c2..0000000000000000000000000000000000000000 --- a/src/uitls/bezierCurve.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { Modifiable, UI, Updatable } from "./ui"; -import { - Line, - Vector2, - Vector3, - BufferGeometry, - LineBasicMaterial, - CubicBezierCurve3 -} from "three"; -import { GUI } from "dat.gui"; - -function lerp(a: Vector2, b: Vector2, t: number): Vector2 { - return new Vector2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); -} - - -export abstract class AbstractBezierCurve implements Modifiable, Updatable { - constructor(startPoint: Vector2, endPoint: Vector2, startControlPoint: Vector2, endControlPoint: Vector2) { - this.startPoint = startPoint; - this.endPoint = endPoint; - this.startControlPoint = startControlPoint; - this.endControlPoint = endControlPoint; - - this._geomBuffer = new BufferGeometry(); - } - - abstract generateCurvePoints(): Vector2[]; - - updateGeomBuffer() { - this._geomBuffer.dispose(); - this._geomBuffer = new BufferGeometry(); - this._geomBuffer.setFromPoints(this._curvePoints); - } - - createLine() { - this.update(); - this._line = new Line(this._geomBuffer, new LineBasicMaterial({ color: 0xffffff })); - this._line.name = 'bezierCurve'; - return this._line; - } - - update() { - this._curvePoints = this.generateCurvePoints(); - this.updateGeomBuffer(); - if (this._line) - this._line.geometry = this._geomBuffer; - } - - createElement(gui: GUI): void { - const folder = gui.addFolder('Bezier Curve'); - UI.addVector(folder, this, this.startPoint, 'Start Point'); - UI.addVector(folder, this, this.endPoint, 'End Point'); - UI.addVector(folder, this, this.startControlPoint, 'Start Control Point'); - UI.addVector(folder, this, this.endControlPoint, 'End Control Point'); - folder.add(this, "_numPoints", 10, 100).step(1).name("Points").onChange(() => { this.update(); }); - - } - - public startPoint: Vector2; - public endPoint: Vector2; - public startControlPoint: Vector2; - public endControlPoint: Vector2; - - protected _numPoints: number = 100; - - private _line?: Line; - private _curvePoints: Vector2[] = []; - private _geomBuffer: BufferGeometry; -} - -// TODO: Handles for the control points -export class Debug implements Updatable, Modifiable { - constructor(private _curve: AbstractBezierCurve) { } - - update() { - this._curve.update(); - } - - createElement(gui: GUI): void { - const folder = gui.addFolder('Debug'); - folder.add(this, "update").name("Update"); - } -} - - -export class BezierCurveTest extends AbstractBezierCurve { - - generateCurvePoints(): Vector2[] { - const curve = new CubicBezierCurve3( - new Vector3(this.startPoint.x, this.startPoint.y, 0), - new Vector3(this.startControlPoint.x, this.startControlPoint.y, 0), - new Vector3(this.endPoint.x, this.endPoint.y, 0), - new Vector3(this.endControlPoint.x, this.endControlPoint.y, 0) - ); - - let curvePoints: Vector2[] = []; - - curve.getPoints(this._numPoints).forEach(point => { - curvePoints.push(new Vector2(point.x, point.y)); - }); - - return curvePoints; - } -} - - -export class BezierCurve extends AbstractBezierCurve { - - - bernstain(s: number): [Vector2, number[]] { - - // p(t) = (1-t)³ * p0 + 3 * (1-t)² * t * p1 + - // 3 * (1-t) * t² * p2 + t³ * p3 - // => subtitute: 1 - t = s - // p(t) = k³ * p0 + 3 * k² * t * p1 + - // 3 * k * t² * p2 + t³ * p3 - - const p0 = this.startPoint; - const p1 = this.startControlPoint; - const p2 = this.endControlPoint; - const p3 = this.endPoint; - - // set k = 1 - s => substitution - const t = s; - const k = 1 - s; - - // calculate the coefficients - const u0 = 1 * Math.pow(k, 3) * Math.pow(t, 0); - const u1 = 3 * Math.pow(k, 2) * Math.pow(t, 1); - const u2 = 3 * Math.pow(k, 1) * Math.pow(t, 2); - const u3 = 1 * Math.pow(k, 0) * Math.pow(t, 3); - - // calculate the point - const p = new Vector2( - u0 * p0.x + u1 * p1.x + u2 * p2.x + u3 * p3.x, - u0 * p0.y + u1 * p1.y + u2 * p2.y + u3 * p3.y - ); - - return [p, [u0, u1, u2, u3]]; - } - - - deCasteljau(t: number): Vector2 { - - const p0 = this.startPoint; - const p1 = this.startControlPoint; - const p2 = this.endControlPoint; - const p3 = this.endPoint; - - // iterative implementation of deCasteljau - let points: Vector2[] = [p0, p1, p2, p3]; - let temporary: Vector2[] = []; - while (points.length > 1) { - temporary = []; - for (let i = 0; i < points.length - 1; i++) - temporary.push(lerp(points[i], points[i + 1], t)); - points = temporary; - } - - return points[0]; - } - - - generateCurvePoints(): Vector2[] { - const curvePoints: Vector2[] = []; - const sampleSize = 1 / this._numPoints; - - if (true) - for (let t = 0; t < 1; t += sampleSize) { - t = Math.round(t * 10000) / 10000; - curvePoints.push(this.deCasteljau(t)); - } - else - for (let s = 0; s < 1; s += sampleSize) { - s = Math.round(s * 10000) / 10000; - const [point, coefficient] = this.bernstain(s); - curvePoints.push(point); - } - - - return curvePoints; - } -} \ No newline at end of file diff --git a/src/uitls/rendering.ts b/src/uitls/rendering.ts index 347d57203890f943029078e39f1ddf465fd97587..f9b784762c1bdf58d60f521def05b6a73f2756cf 100644 --- a/src/uitls/rendering.ts +++ b/src/uitls/rendering.ts @@ -7,7 +7,7 @@ import { Scene } from 'three'; -import { Modifiable } from './ui'; +import { Modifiable } from './Interfaces'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; import { GUI } from 'dat.gui'; @@ -32,7 +32,7 @@ class CameraManager { new OrthographicCamera(-aspect, aspect, 1, -1, settings.near, settings.far) ]; - this.active = this.cameras[0]; + this.active = this.cameras[1]; this.active.position.set(0, 0, 5); } diff --git a/src/uitls/ui.ts b/src/uitls/ui.ts index 3bf15e3b07dfca272be7cd00ba7cdd67526a90e7..fba0ab99203a07b04321b657955c509b9e6ba3e0 100644 --- a/src/uitls/ui.ts +++ b/src/uitls/ui.ts @@ -1,22 +1,5 @@ import { GUI } from 'dat.gui'; - -/** - * Class which implement this interface do create a GUI element for the object. - * The element will be added to the GUI which is passed to the constructor. - * @interface Modifiable - */ -export interface Modifiable { - createElement(gui: GUI): void; -} - -/** - * Class which implement this interface can be updated. It is important for - * the GUI element to be updated when the object is changed. - * @interface Updatable - */ -export interface Updatable { - update(): void; -} +import { Modifiable, Updatable } from './Interfaces'; export class UI { private _gui: GUI;