Skip to content
Snippets Groups Projects
Unverified Commit 262a0fa0 authored by Jamie Temple's avatar Jamie Temple
Browse files

feat: interpolation

parent 53f28977
No related branches found
No related tags found
No related merge requests found
import * as CG from "./uitls/rendering"; import * as CG from "./uitls/Rendering";
import * as THREE from "three"; import { UI } from "./uitls/UI";
import { UI } from "./uitls/ui"; import { Curve2d } from "./uitls/Curve2d";
import * as Interpolation from "./uitls/bezierCurve";
const render = new CG.RenderManager('#canvas', { near: 0.1, far: 1000, fov: 45, height: 1 }); const render = new CG.RenderManager('#canvas', { near: 0.1, far: 1000, fov: 45, height: 1 });
const ui = new UI(); const ui = new UI();
const startPoint = new THREE.Vector2(-1, -1); const curve = new Curve2d();
const endPoint = new THREE.Vector2(1, 1); curve.objects().forEach(obj => { render.add(obj); });
const startControlPoint = new THREE.Vector2(-1, 1);
const endControlPoint = new THREE.Vector2(1, -1);
// 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(); render.render();
ui.addModifiable(render); ui.addModifiable(render);
......
import { Modifiable, UI, Updatable } from "./ui"; import { Vector2 } from "three";
import {
Line,
Vector2,
Vector3,
BufferGeometry,
LineBasicMaterial,
CubicBezierCurve3
} from "three";
import { GUI } from "dat.gui";
function lerp(a: Vector2, b: Vector2, t: number): Vector2 { 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); 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;
export abstract class AbstractBezierCurve implements Modifiable, Updatable { private _dirty: boolean = true;
constructor(startPoint: Vector2, endPoint: Vector2, startControlPoint: Vector2, endControlPoint: Vector2) { private _resolution: number = 5;
this.startPoint = startPoint; private _points: Vector2[] = [];
this.endPoint = endPoint; private _coefficients: number[][] = [];
this.startControlPoint = startControlPoint;
this.endControlPoint = endControlPoint;
this._geomBuffer = new BufferGeometry(); constructor(
startPoint: Vector2,
endPoint: Vector2,
startControlPoint: Vector2,
endControlPoint: Vector2
) {
this._startPoint = startPoint;
this._endPoint = endPoint;
this._startControlPoint = startControlPoint;
this._endControlPoint = endControlPoint;
} }
abstract generateCurvePoints(): Vector2[]; get startPoint(): Vector2 {
return this._startPoint;
updateGeomBuffer() {
this._geomBuffer.dispose();
this._geomBuffer = new BufferGeometry();
this._geomBuffer.setFromPoints(this._curvePoints);
} }
createLine() { get endPoint(): Vector2 {
this.update(); return this._endPoint;
this._line = new Line(this._geomBuffer, new LineBasicMaterial({ color: 0xffffff }));
this._line.name = 'bezierCurve';
return this._line;
} }
update() { get startControlPoint(): Vector2 {
this._curvePoints = this.generateCurvePoints(); return this._startControlPoint;
this.updateGeomBuffer();
if (this._line)
this._line.geometry = this._geomBuffer;
} }
createElement(gui: GUI): void { get endControlPoint(): Vector2 {
const folder = gui.addFolder('Bezier Curve'); return this._endControlPoint;
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; get points(): Vector2[] {
public endPoint: Vector2; return this._points;
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 get coefficients(): number[][] {
export class Debug implements Updatable, Modifiable { return this._coefficients;
constructor(private _curve: AbstractBezierCurve) { }
update() {
this._curve.update();
} }
createElement(gui: GUI): void { set startPoint(value: Vector2) {
const folder = gui.addFolder('Debug'); this._startPoint = value;
folder.add(this, "update").name("Update"); this._dirty = true;
} this.generatePoints(this._resolution);
} }
set endPoint(value: Vector2) {
export class BezierCurveTest extends AbstractBezierCurve { this._endPoint = value;
this._dirty = true;
generateCurvePoints(): Vector2[] { this.generatePoints(this._resolution);
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 { 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[]] { bernstain(s: number): [Vector2, number[]] {
...@@ -139,8 +107,7 @@ export class BezierCurve extends AbstractBezierCurve { ...@@ -139,8 +107,7 @@ export class BezierCurve extends AbstractBezierCurve {
return [p, [u0, u1, u2, u3]]; return [p, [u0, u1, u2, u3]];
} }
deCasteljau(t: number): Vector2[][] {
deCasteljau(t: number): Vector2 {
const p0 = this.startPoint; const p0 = this.startPoint;
const p1 = this.startControlPoint; const p1 = this.startControlPoint;
...@@ -150,34 +117,47 @@ export class BezierCurve extends AbstractBezierCurve { ...@@ -150,34 +117,47 @@ export class BezierCurve extends AbstractBezierCurve {
// iterative implementation of deCasteljau // iterative implementation of deCasteljau
let points: Vector2[] = [p0, p1, p2, p3]; let points: Vector2[] = [p0, p1, p2, p3];
let temporary: Vector2[] = []; let temporary: Vector2[] = [];
const results: Vector2[][] = [];
results.push(points);
while (points.length > 1) { while (points.length > 1) {
temporary = []; temporary = [];
for (let i = 0; i < points.length - 1; i++) for (let i = 0; i < points.length - 1; i++)
temporary.push(lerp(points[i], points[i + 1], t)); temporary.push(lerp(points[i], points[i + 1], t));
points = temporary; points = temporary;
results.push(points); // save every iteration
} }
return points[0]; return results;
} }
generatePoints(resolution: number): Vector2[] {
generateCurvePoints(): Vector2[] { if (!this._dirty && this._points.length === resolution)
const curvePoints: Vector2[] = []; return this._points;
const sampleSize = 1 / this._numPoints;
if (true) this._coefficients = [];
for (let t = 0; t < 1; t += sampleSize) { this._points = [];
this._resolution = resolution;
const sampleSize = 1 / resolution;
for (let t = 0; t <= 1; t += sampleSize) {
t = Math.round(t * 10000) / 10000; t = Math.round(t * 10000) / 10000;
curvePoints.push(this.deCasteljau(t)); const [point, coefficients] = this.bernstain(t);
} this._points.push(point);
else this._coefficients.push(coefficients);
for (let s = 0; s < 1; s += sampleSize) {
s = Math.round(s * 10000) / 10000;
const [point, coefficient] = this.bernstain(s);
curvePoints.push(point);
} }
this._dirty = false;
return this._points;
}
return curvePoints; 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]];
} }
} }
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
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
...@@ -7,7 +7,7 @@ import { ...@@ -7,7 +7,7 @@ import {
Scene Scene
} from 'three'; } from 'three';
import { Modifiable } from './ui'; import { Modifiable } from './Interfaces';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GUI } from 'dat.gui'; import { GUI } from 'dat.gui';
...@@ -32,7 +32,7 @@ class CameraManager { ...@@ -32,7 +32,7 @@ class CameraManager {
new OrthographicCamera(-aspect, aspect, 1, -1, settings.near, settings.far) 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); this.active.position.set(0, 0, 5);
} }
......
import { GUI } from 'dat.gui'; import { GUI } from 'dat.gui';
import { Modifiable, Updatable } from './Interfaces';
/**
* 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;
}
export class UI { export class UI {
private _gui: GUI; private _gui: GUI;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment