Skip to content
Snippets Groups Projects
Commit 96784233 authored by Temple's avatar Temple
Browse files

Merge branch 'rewrite' into 'main'

feat: new project architecture

See merge request !1
parents a77daa69 e7ec1c65
Branches
No related tags found
1 merge request!1feat: base project
Showing
with 1734 additions and 233 deletions
import { SpinningCube } from "./spinningCube";
import { PipelineData } from "../core/Pipeline";
class Demo extends PipelineData {
public constructor() { super(); }
data(): void {
const spinningCube = new SpinningCube();
this.addRenderable(spinningCube);
this.addObserver(spinningCube);
}
}
export { Demo };
\ No newline at end of file
import * as THREE from "three";
abstract class CubicBezierAlgorithm {
/**
* @param controlPointsPosition an array with vectors that give the positons of the control points. The
* first vector is the start point, the second vector is the first control point, the third vector is the
* second control point and the fourth vector is the end point.
* @param t the time at which the curve should be evaluated (0.0 - 1.0)
*/
abstract evaluatePosition(controlPointsPosition: Array<THREE.Vector3>, t: number): THREE.Vector3;
}
class BernsteinAlgorithm extends CubicBezierAlgorithm {
evaluatePosition(positions: Array<THREE.Vector3>, t: number): THREE.Vector3 {
const c = BernsteinAlgorithm.calculateCoefficients(t);
const x = c[0] * positions[0].x + c[1] * positions[1].x + c[2] * positions[2].x + c[3] * positions[3].x;
const y = c[0] * positions[0].y + c[1] * positions[1].y + c[2] * positions[2].y + c[3] * positions[3].y;
const z = c[0] * positions[0].z + c[1] * positions[1].z + c[2] * positions[2].z + c[3] * positions[3].z;
return new THREE.Vector3(x, y, z);
}
/**
* Weight the contribution of controlpoints by calculating the values of the different terms of the
* Bernstein polynome. The terms yield a coefficient of the different points.
* p(t) = (1-t)^3 * p0 + 3 * (1-t)^2 * t * p1 +
* 3 * (1-t) * t^2 * p2 + t^3 * p3
*/
public static calculateCoefficients(t: number): number[]{
const u0 = 1.0 * Math.pow(1.0 - t, 3.0) * Math.pow(t, 0.0);
const u1 = 3.0 * Math.pow(1.0 - t, 2.0) * Math.pow(t, 1.0);
const u2 = 3.0 * Math.pow(1.0 - t, 1.0) * Math.pow(t, 2.0);
const u3 = 1.0 * Math.pow(1.0 - t, 0.0) * Math.pow(t, 3.0);
return [u0, u1, u2, u3];
}
}
class DeCasteljauAlgorithm extends CubicBezierAlgorithm {
evaluatePosition(positions: Array<THREE.Vector3>, t: number): THREE.Vector3 {
while (positions.length > 1)
positions = DeCasteljauAlgorithm.iteration(positions, t);
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>();
for (let i = 0; i < points.length - 1; i++)
result.push(points[i + 1].clone().sub(points[i]).multiplyScalar(t).add(points[i]));
return result;
}
}
/**
* CubicBezierCurve holds points and an algorithm to evaluate points. This is the only purpose
* of this class.
*/
class CubicBezierCurve {
private _algorithm: CubicBezierAlgorithm;
private _controlPoints: Array<THREE.Vector3>;
constructor(controlPoints: Array<THREE.Vector3>, algorithm: CubicBezierAlgorithm) {
this._controlPoints = controlPoints;
this._algorithm = algorithm;
}
public evaluatePosition(t: number): THREE.Vector3 {
return this._algorithm.evaluatePosition(this._controlPoints, t);
}
public evaluatePositions(resolution: number): Array<THREE.Vector3> {
const positions = new Array<THREE.Vector3>();
for (let i = 0; i < resolution + 1; i++)
positions.push(this.evaluatePosition(i / resolution));
return positions;
}
public get controlPoints(): Array<THREE.Vector3> {
return this._controlPoints;
}
public set controlPoints(value: Array<THREE.Vector3>) {
this._controlPoints = value;
}
}
export {CubicBezierAlgorithm, BernsteinAlgorithm, DeCasteljauAlgorithm, CubicBezierCurve};
\ No newline at end of file
import * as THREE from 'three';
import { Line2d } from "../core/Lines";
import { PipelineObserver } from '../core/Pipeline';
import { Point2d } from '../core/Points';
import { CubicBezierCurve, DeCasteljauAlgorithm } from "./CubicBezierCurve";
interface CurveParameter {
pointA: Point2d;
tangentA: Point2d;
pointB: Point2d;
tangentB: Point2d;
offset: number;
resolution: number;
linewidth: number;
color: THREE.Color;
}
class Curve extends Line2d implements PipelineObserver {
public positions: Array<THREE.Vector3>;
private _generator: CubicBezierCurve;
private _resolution: number;
private _offset: number;
constructor(parameter: CurveParameter) {
super([], parameter.linewidth, parameter.color);
this._resolution = parameter.resolution;
this._offset = parameter.offset;
this.positions = [
parameter.pointA._object3d.position,
parameter.tangentA._object3d.position,
parameter.tangentB._object3d.position,
parameter.pointB._object3d.position,
];
this._generator = new CubicBezierCurve(this.positions, new DeCasteljauAlgorithm());
}
public get resolution(): number {
return this._resolution;
}
public update(_deltaTime: number): void {
let newPoints = this._generator.evaluatePositions(this._resolution);
newPoints.forEach((point) => {
point.z = this._offset;
});
this.points = newPoints;
}
}
export { Curve };
export type { CurveParameter };
import * as THREE from "three";
import { Curve } from "./Curve";
import { PipelineDraggable, PipelineGUI, PipelineObserver, PipelineRenderable } from "../core/Pipeline";
import { Line1d } from "../core/Lines";
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;
public group: THREE.Group;
private _point: Point2d;
private _lines: Array<Line1d>;
private _offset: number;
private _speed: number = 1;
t: number = 0;
constructor(curve: Curve, offset: number = 0) {
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)));
// TODO: Extract controls to seperate class
document.addEventListener('keydown', (event: KeyboardEvent) => {
if (event.key === 'a') {
this.t = Math.max(0, Math.min(1, this.t - 0.01 * this._speed));
}
if (event.key === 'd') {
this.t = Math.max(0, Math.min(1, this.t + 0.01 * this._speed));
}
if (event.key === 'w') {
this._speed = Math.max(1, Math.min(2, this._speed + 0.1));
}
if (event.key === 's') {
this._speed = Math.max(1, Math.min(2, this._speed - 0.1));
}
});
this._lines.forEach((line) => {
this.group.add(line.object());
});
this.group.add(this._point.object());
}
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).listen().name("speed");
folder.closed = false;
}
object(): THREE.Object3D {
return this.group;
}
position(): THREE.Vector3 {
return this.group.position;
}
public update(_deltaTime: number): void {
let points = new Array<THREE.Vector3>();
this.curve.positions.forEach((position) => {
const p = position.clone();
p.z = this._offset;
points.push(p);
});
this._lines[0].points = points;
points = DeCasteljauAlgorithm.iteration(points, this.t);
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;
}
}
/**
* Draws the Bernstein functions with points.
*/
class CurveHelperBernstein implements PipelineObserver, PipelineRenderable, PipelineDraggable {
private _helperReference: CurveHelper;
private _scale: number = 1;
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;
const halfScale = (this._scale * .5);
this._xAxis = new Line1d(
[new THREE.Vector3(-halfScale, -halfScale, 0), new THREE.Vector3(halfScale, -halfScale, 0)],
new THREE.Color(0xff0000));
this._yAxis = new Line1d(
[new THREE.Vector3(-halfScale, -halfScale, 0), new THREE.Vector3(-halfScale, halfScale, 0)],
new THREE.Color(0x00ff00));
this._graphs = new Array<Line1d>();
this._point = new Array<Point2d>();
// this will also be the collider/ hitbox for dragging
this._background = new THREE.Mesh(
new THREE.PlaneGeometry(this._scale, this._scale),
new THREE.MeshBasicMaterial({ color: 0xffffff, opacity: 0.5, transparent: true }));
this._background.position.z = offset;
// initialize here to avoid null reference => does not show in inspector
this.initGraphs();
this.initPoints();
this._group = new THREE.Group();
this._group.add(this._background);
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()); });
// to make the translation easier all object are parented to the 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; });
}
private initGraphs(): void {
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((this._scale * .5), (this._scale * .5), 0)));
});
}
for (let i = 0; i < 4; i++) {
this._graphs.push(new Line1d(points[i], new THREE.Color(0x0000ff)));
}
}
private initPoints(): void {
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]._object3d.position.copy(new THREE.Vector3(this._helperReference.t, coefficient[i], 0.001));
}
}
hitbox(): THREE.Object3D {
return this._background;
}
object(): THREE.Object3D {
return this._group;
}
position(): THREE.Vector3 {
return this._group.position;
}
update(_deltaTime: number): void {
const coefficient = BernsteinAlgorithm.calculateCoefficients(this._helperReference.t);
for (let i = 0; i < 4; i++) {
this._point[i]._object3d.position.copy(new THREE.Vector3(this._helperReference.t, coefficient[i], 0.001)
.sub(new THREE.Vector3((this._scale * .5), (this._scale * .5), 0)));
}
}
}
export { CurveHelper, CurveHelperBernstein }
import * as THREE from 'three';
import { PipelineData } from "../core/Pipeline";
import { Point2d } from "../core/Points";
import { Curve } from './Curve';
import { CurveHelper, CurveHelperBernstein } from './CurveHelper';
class BezierDemo extends PipelineData {
public constructor() { super(); }
data(): void {
const pointA = new Point2d(.05, 36);
const tangentA = new Point2d(.025, 36);
const pointB = new Point2d(.05, 36);
const tangentB = new Point2d(.025, 36);
const solidGreenMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const solidRedMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
pointA.material = solidGreenMaterial;
tangentA.material = solidGreenMaterial;
pointB.material = solidRedMaterial;
tangentB.material = solidRedMaterial;
const curve = new Curve({
pointA: pointA,
tangentA: tangentA,
pointB: pointB,
tangentB: tangentB,
resolution: 128,
linewidth: .02,
offset: -1,
color: new THREE.Color(0xffffff)
});
const curveHelper = new CurveHelper(curve, -.5);
const curveHelperBernstein = new CurveHelperBernstein(curveHelper, .25);
this.addRenderable(curve);
this.addRenderable(curveHelper);
this.addRenderable(curveHelperBernstein);
this.addDraggable(curveHelperBernstein);
this.addObserver(curve);
this.addObserver(curveHelper);
this.addObserver(curveHelperBernstein);
this.addGUIElement(curve);
this.addGUIElement(curveHelper);
this.addRenderable(pointA);
this.addRenderable(tangentA);
this.addRenderable(pointB);
this.addRenderable(tangentB);
this.addDraggable(pointA);
this.addDraggable(tangentA);
this.addDraggable(pointB);
this.addDraggable(tangentB);
}
}
export { BezierDemo };
\ No newline at end of file
import * as THREE from 'three';
import { PipelineRenderable } from '../core/Pipeline';
import { Line1d } from "../core/Lines";
class Axes implements PipelineRenderable {
public x: Line1d;
public y: Line1d;
public z: Line1d;
private _objects: THREE.Group;
constructor(max: number) {
this._objects = new THREE.Group();
this.x = new Line1d([new THREE.Vector3(0, 0, 0), new THREE.Vector3(max, 0, 0)], new THREE.Color(0xff0000));
this.y = new Line1d([new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, max, 0)], new THREE.Color(0x00ff00));
this.z = new Line1d([new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, max)], new THREE.Color(0x0000ff));
this._objects.add(this.x.object());
this._objects.add(this.y.object());
this._objects.add(this.z.object());
const offset = 0.001;
this.x._object3d.position.z = offset;
this.y._object3d.position.z = offset;
this.z._object3d.position.z = offset;
}
object(): THREE.Object3D {
return this._objects;
}
}
export { Axes };
\ No newline at end of file
import * as THREE from 'three';
import { PipelineData } from "../core/Pipeline";
import { TriangleMesh } from "../core/TriangleMesh";
import { Axes } from './Axes';
import { Rotator } from './Rotator';
import { RotationHelper } from './RotatorHelper';
class QuaternionDemo extends PipelineData {
public constructor() { super(); }
data(): void {
const grid = new THREE.GridHelper(10, 10);
this.scene.add(grid);
const light = new THREE.DirectionalLight(0x8e8e8e, 1);
light.position.set(1, 1, 1);
this.scene.add(light);
const backLight = new THREE.PointLight(0x6e6e8e, 1);
backLight.position.set(-1, -1, -1);
this.scene.add(backLight);
const lightA = new THREE.PointLight(0x8eaa8e, 1);
lightA.position.set(2, 2, 0);
this.scene.add(lightA);
const lightC = new THREE.PointLight(0x8eaa7f, 1);
lightC.position.set(-2, 0, -2);
this.scene.add(lightC);
const axes = new Axes(5);
this.addRenderable(axes);
const rotationObject = new TriangleMesh("../../assets/suzanne.obj", new THREE.Color(0xffffff));
rotationObject.material = new THREE.MeshLambertMaterial({ color: 0xffffff });
this.addRenderable(rotationObject);
const rotator: Rotator = new Rotator(rotationObject.object());
this.addObserver(rotator);
this.addGUIElement(rotator);
const rotationHelper = new RotationHelper(rotator, new THREE.Color(0xffffff), new THREE.Color(0xff0000));
this.addRenderable(rotationHelper);
this.addObserver(rotationHelper);
this.addDraggable(rotationHelper);
}
}
export { QuaternionDemo };
\ No newline at end of file
import * as THREE from 'three';
export class Quaternion {
private _x: number;
private _y: number;
private _z: number;
private _w: number;
constructor(x: number = 0, y: number = 0, z: number = 0, w: number = 1) {
this._x = x;
this._y = y;
this._z = z;
this._w = w;
}
public get x(): number { return this._x; }
public get y(): number { return this._y; }
public get z(): number { return this._z; }
public get w(): number { return this._w; }
public set x(value: number) {
this._x = value;
}
public set y(value: number) {
this._y = value;
}
public set z(value: number) {
this._z = value;
}
public set w(value: number) {
this._w = value;
}
public get eulerMatrix(): THREE.Matrix4 {
const copy = Quaternion.normalize(this.clone());
const m = new THREE.Matrix4();
m.set(
1 - 2 * (copy._y * copy._y + copy._z * copy._z), 2 * (copy._x * copy._y - copy._z * copy._w), 2 * (copy._x * copy._z + copy._y * copy._w), 0,
2 * (copy._x * copy._y + copy._z * copy._w), 1 - 2 * (copy._x * copy._x + copy._z * copy._z), 2 * (copy._y * copy._z - copy._x * copy._w), 0,
2 * (copy._x * copy._z - copy._y * copy._w), 2 * (copy._y * copy._z + copy._x * copy._w), 1 - 2 * (copy._x * copy._x + copy._y * copy._y), 0,
0, 0, 0, 1
);
return m;
}
public get array(): number[] {
return [this._x, this._y, this._z, this._w];
}
public toString(): string {
return `Quaternion(${this._x}, ${this._y}, ${this._z}, ${this._w})`;
}
public set(x: number, y: number, z: number, w: number): this {
this._x = x;
this._y = y;
this._z = z;
this._w = w;
return this;
}
public copy(q: Quaternion): this {
this._x = q._x;
this._y = q._y;
this._z = q._z;
this._w = q._w;
return this;
}
public clone(): Quaternion {
return new Quaternion(this._x, this._y, this._z, this._w);
}
public static identity(): Quaternion {
return new Quaternion(0, 0, 0, 1);
}
public static slerp(qa: Quaternion, qb: Quaternion, t: number): Quaternion {
const cosTheta = Quaternion.dot(qa, qb);
if (cosTheta > .9995) {
return Quaternion.add(Quaternion.multiplyScalar(qa, 1 - t), Quaternion.multiplyScalar(qb, t));
} else {
const theta = Math.acos(cosTheta);
const sinTheta = Math.sin(theta);
const sinThetaA = Math.sin(theta * (1 - t));
const sinThetaB = Math.sin(theta * t);
return Quaternion.add(Quaternion.multiplyScalar(qa, sinThetaA / sinTheta), Quaternion.multiplyScalar(qb, sinThetaB / sinTheta));
}
}
public static slerpCG3(qa: Quaternion, qb: Quaternion, t: number): Quaternion {
let cq1 = qa.clone();
let cq2 = qb.clone();
cq1 = Quaternion.normalize(cq1);
cq2 = Quaternion.normalize(cq2);
let dot = Quaternion.dot(cq1, cq2);
if (dot < 0.0) {
cq2 = Quaternion.inverse(cq2);
dot = -dot;
}
if (dot > 0.9995) {
return Quaternion.add(cq1, Quaternion.multiplyScalar(
Quaternion.subtract(cq2, cq1), t));
}
const theta0 = Math.acos(dot);
const theta = theta0 * t;
const sinTheta0 = Math.sin(theta0);
const sinTheta = Math.sin(theta);
const s0 = Math.cos(theta) - dot * sinTheta / sinTheta0;
const s1 = sinTheta / sinTheta0;
return Quaternion.add(
Quaternion.multiplyScalar(cq1, s0),
Quaternion.multiplyScalar(cq2, s1)
)
}
public static add(qa: Quaternion, qb: Quaternion): Quaternion {
return new Quaternion(qa._x + qb._x, qa._y + qb._y, qa._z + qb._z, qa._w + qb._w);
}
public static subtract(qa: Quaternion, qb: Quaternion): Quaternion {
return new Quaternion(qa._x - qb._x, qa._y - qb._y, qa._z - qb._z, qa._w - qb._w);
}
public static multiply(qa: Quaternion, qb: Quaternion): Quaternion {
return new Quaternion(
qa._w * qb._x + qa._x * qb._w + qa._y * qb._z - qa._z * qb._y,
qa._w * qb._y + qa._y * qb._w + qa._z * qb._x - qa._x * qb._z,
qa._w * qb._z + qa._z * qb._w + qa._x * qb._y - qa._y * qb._x,
qa._w * qb._w - qa._x * qb._x - qa._y * qb._y - qa._z * qb._z
);
}
public static multiplyScalar(a: Quaternion | number, b: Quaternion | number): Quaternion {
if (typeof b === 'number' && a instanceof Quaternion) {
return new Quaternion(a._x * b, a._y * b, a._z * b, a._w * b);
} if (typeof a === 'number' && b instanceof Quaternion) {
return new Quaternion(b._x * a, b._y * a, b._z * a, b._w * a);
}
throw new Error("Invalid arguments");
}
public static dot(qa: Quaternion, qb: Quaternion): number {
return qa._x * qb._x + qa._y * qb._y + qa._z * qb._z + qa._w * qb._w;
}
public static len(qa: Quaternion): number {
return Math.sqrt(Quaternion.dot(qa, qa));
}
public static normalize(qa: Quaternion): Quaternion {
const length = Quaternion.len(qa);
if (length < 0.00001) {
return Quaternion.identity();
}
return Quaternion.multiplyScalar(qa, 1 / length);
}
public static inverse(qa: Quaternion): Quaternion {
return new Quaternion(-qa._x, -qa._y, -qa._z, -qa._w);
}
}
\ No newline at end of file
import * as THREE from 'three';
import "dat.gui";
import { PipelineGUI, PipelineObserver } from "../core/Pipeline";
import { Quaternion } from "./Quaternion";
const Mode = ["EDIT", "SLERP"];
class Rotator implements PipelineObserver, PipelineGUI {
private _objectReference: THREE.Object3D;
private _mode: string = Mode[0];
private _t: number = 0;
private _orientations: Array<Quaternion>;
private _index: number;
private _guiRoot!: dat.GUI;
private _rangeController!: dat.GUIController;
private _slerp: Quaternion = new Quaternion();
public constructor(objectReference: THREE.Object3D) {
this._objectReference = objectReference;
this._orientations = [];
this._index = 0;
// TODO: Extract controls to seperate class
document.addEventListener('keydown', (event: KeyboardEvent) => {
if (event.key === 'e') {
this.addQuaternion();
} else if (event.key === 'E') {
this.popQuaternion();
} else if (event.key === ' ' && this._orientations.length > 1) {
this._index = (this._index + 1) % this._orientations.length;
} else if (event.key === 'q') {
this._mode = (this._mode === Mode[0]) ? Mode[1] : Mode[0];
} else if (event.key === 'n') {
this.normaliseQuaternions();
}
if (this._orientations.length < 1) {
return;
}
if (this._mode === Mode[0]) {
let step = 0.1;
if (event.shiftKey) {
step = -step;
}
switch (event.key) {
case 'a':
case 'A':
this._orientations[this._index].x += step;
break;
case 's':
case 'S':
this._orientations[this._index].y += step;
break;
case 'd':
case 'D':
this._orientations[this._index].z += step;
break;
case 'w':
case 'W':
this._orientations[this._index].w += step;
break;
}
} else {
switch (event.key) {
case 'a':
case 'A':
this._t += .01;
break;
case 'd':
case 'D':
this._t -= .01;
break;
}
this._t = Math.max(0, Math.min(1, this._t));
console.log(this._t);
}
});
}
public get orientations(): Array<Quaternion> {
return this._orientations;
}
public get index(): number {
return this._index;
}
public get slerp(): Quaternion {
return this._slerp;
}
public get mode(): string {
return this._mode;
}
private addQuaternion(): void {
if (this._orientations.length >= 5) {
return;
}
this._orientations.push(new Quaternion());
this.createQuaternionGUI(this._guiRoot, this._orientations[this._orientations.length - 1]);
if (this._rangeController) {
this._rangeController.max(this._orientations.length - 1);
}
}
private popQuaternion(): void {
if (this._orientations.length <= 0) {
return;
}
this._guiRoot.removeFolder(this._guiRoot.__folders["Quaternion " + this._orientations.length]);
this._orientations.pop();
if (this._rangeController) {
this._rangeController.max(this._orientations.length - 1);
}
}
private normaliseQuaternions(): void {
for (let i = 0; i < this._orientations.length; i++) {
this._orientations[i].copy(Quaternion.normalize(this._orientations[i]));
}
}
private createQuaternionGUI(gui: dat.GUI, q: Quaternion): void {
const folder = gui.addFolder('Quaternion ' + this._orientations.length);
folder.add(q, 'x').step(.001).listen();
folder.add(q, 'y').step(.001).listen();
folder.add(q, 'z').step(.001).listen();
folder.add(q, 'w').step(.001).listen();
folder.closed = false;
}
gui(gui: dat.GUI): void {
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();
else
this._guiRoot.__folders["Quaternion " + (i + 1)].open();
}
});
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");
slerpGUI.closed = false;
}
update(_deltaTime: number): void {
if (this._mode === Mode[0] && this._orientations.length !== 0) {
this._objectReference.setRotationFromMatrix(this._orientations[this._index].eulerMatrix);
} else if (this._mode === Mode[1] && this._orientations.length > 1) {
let result: Quaternion = Quaternion.slerpCG3(this._orientations[0], this._orientations[1], this._t);
for (let i = 2; i < this._orientations.length; i++) {
result = Quaternion.slerpCG3(result, this._orientations[i], this._t);
}
this._objectReference.setRotationFromMatrix(result.eulerMatrix);
this._slerp.copy(result);
}
}
}
export { Rotator, Mode }
export type { }
import * as THREE from "three";
import { Line1d } from "../core/Lines";
import { PipelineDraggable, PipelineObserver } from "../core/Pipeline";
import { Point3d } from "../core/Points";
import { Shape } from "../core/Shapes";
import { Quaternion } from "./Quaternion";
import { Rotator, Mode } from "./Rotator";
class RotationHelper extends Shape implements PipelineObserver, PipelineDraggable {
private _unitSphere: THREE.Mesh;
private _qPoints: Array<Point3d>;
private _qLines: Array<Line1d>;
private _quaternionsReference: Array<Quaternion>;
private _slerpReference: Quaternion;
private _rotator: Rotator;
constructor(rotator: Rotator, primeColor: THREE.Color = new THREE.Color(1,1,1),
secColor: THREE.Color = new THREE.Color(1,0,0), terColor: THREE.Color = new THREE.Color(0,1,1)) {
super();
this._rotator = rotator;
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: .8, 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++)
{
this._qPoints.push(new Point3d(.05, 16));
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++)
this._object3d.add(this._qPoints[i].object());
for (let i = 0; i < 5; i++)
this._object3d.add(this._qLines[i].object());
this._object3d.position.x = 2.5;
}
update(_deltaTime: number): void {
for (let i = 0; i < this._quaternionsReference.length; i++) {
// SET POINT POSITION
let q = this._quaternionsReference[i];
let p = new THREE.Vector3(q.x, q.y, q.z).normalize().add(this._unitSphere.position);
this._qPoints[i]._object3d.position.set(p.x, p.y, p.z);
// SET POINT COLOR
let nq = Quaternion.normalize(q);
((this._qPoints[i]._object3d as THREE.Mesh).material as THREE.MeshBasicMaterial)
.color.set(new THREE.Color(Math.cos(nq.w),Math.sin(nq.w), 1));
// SET LINE POSITION
let p1 = new THREE.Vector3(q.x, q.y, q.z).normalize().multiplyScalar(1.2);
let p2 = p1.clone().multiplyScalar(-1);
this._qLines[i].points = [p1.add(this._unitSphere.position), p2.add(this._unitSphere.position)];
((this._qLines[i]._object3d as THREE.Line).material as THREE.LineBasicMaterial)
.color.set(new THREE.Color(Math.cos(nq.w),Math.sin(nq.w), 1));
}
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);
p.normalize().add(this._unitSphere.position);
this._qPoints[5]._object3d.position.set(p.x, p.y, p.z);
q = Quaternion.normalize(q);
((this._qPoints[5]._object3d as THREE.Mesh).material as THREE.MeshBasicMaterial).color.set(new THREE.Color(Math.cos(q.w),Math.sin(q.w), 1));
} else {
let p = this._unitSphere.position;
this._qPoints[5]._object3d.position.set(p.x, p.y, p.z);
}
}
hitbox(): THREE.Object3D {
return this._unitSphere;
}
}
export {RotationHelper}
\ No newline at end of file
import { PipelineData } from "../core/Pipeline";
import { Simulation } from "./Simulation";
import { SimulationData } from './SimulationData';
class SimulationDemo extends PipelineData {
public constructor() { super(); }
data(): void {
const data = new SimulationData();
this.addGUIElement(data);
this.addRenderable(data);
const simulation = new Simulation();
this.addObserver(simulation);
}
}
export { SimulationDemo };
\ No newline at end of file
import * as THREE from 'three';
import { PipelineObserver } from "../core/Pipeline";
import { Particle } from './physics/particle';
import { SimulationData } from "./SimulationData";
export class Simulation implements PipelineObserver {
public static readonly G = new THREE.Vector3(0, -9.807, 0);
public static current: Particle;
constructor() {
SimulationData.generateTextile();
}
update(_deltaTime: number): void {
const h = _deltaTime / 100;
for (let t = 0; t < _deltaTime; t += h) {
SimulationData.pList?.forEach(pSub => {
pSub.forEach(p => {
if (p.usePhysics) {
Simulation.current = p;
let x = [p.position.x, p.position.y, p.position.z,
p.velocity.x, p.velocity.y, p.velocity.z];
x = SimulationData.INTEGRATORS[SimulationData.integratorType].step(x, h, Simulation.derivation);
p.setState(x[0], x[1], x[2], x[3], x[4], x[5]);
}
});
})
}
}
public static derivation(x: Array<number>): Array<number> {
// 1. create new array to store derivatives
let xDot = new Array<number>(x.length);
// 2. derivative of position => velocity
xDot[0] = x[3];
xDot[1] = x[4];
xDot[2] = x[5];
// 3. derivative of velocity => acceleration
// 3.1 accumulate forces
let totalForce = new THREE.Vector3(0, 0, 0);
totalForce.add(SimulationData.externalForce);
totalForce.divideScalar(Math.pow(SimulationData.resolution, 2));
totalForce.divideScalar(Math.sqrt(Math.pow(x[0], 2) + Math.pow(x[1], 2) + Math.pow(x[2], 2)));
// totalForce.multiplyScalar(Math.sin(2 * Math.PI * Math.random()) + .5);
// 3.2 accumulate spring forces
Simulation.current.references.forEach(spring => {
const force = spring.force(Simulation.current.position);
totalForce.add(force);
});
// 3.3 damping force
const dampingForce = Simulation.current.velocity.clone().multiplyScalar(-SimulationData.damping);
totalForce.add(dampingForce);
// 3.3 acceleration = force / mass
let totalAcceleration = totalForce.divideScalar(Simulation.current.mass);
totalAcceleration.add(Simulation.G);
// 4. derivative of acceleration => velocity
xDot[3] = totalAcceleration.x;
xDot[4] = totalAcceleration.y;
xDot[5] = totalAcceleration.z;
return xDot;
}
}
\ No newline at end of file
import * as THREE from 'three';
import { GUI } from 'dat.gui';
import { picked, PipelineGUI, PipelineRenderable } from '../core/Pipeline';
import { BendingSpring, ShearSpring, Spring, SpringConstants, StructuralSpring, } from './physics/Springs';
import { Particle } from './physics/particle';
import { EulerIntegrator, Integrator, MidpointIntegrator, RungeKuttaIntegrator } from './physics/Integrators';
document.addEventListener('keydown', (event: KeyboardEvent) => {
if (event.key === 'e') {
SimulationData.switchParticlePhysics();
}
});
class SimulationData implements PipelineGUI, PipelineRenderable {
public static container: THREE.Group = new THREE.Group();
// cloth settings
public static readonly MAX_TOTAL_MASS = 10;
public static readonly MIN_TOTAL_MASS = 0.1;
public static readonly MAX_WIDTH = 5;
public static readonly MIN_WIDTH = 0.01;
public static readonly MAX_HEIGHT = 2;
public static readonly MIN_HEIGHT = 0.01;
public static readonly MAX_RESOLUTION = 20;
public static readonly MIN_RESOLUTION = 2;
public static totalMass: number = 1;
public static width: number = .2;
public static height: number = .2;
public static resolution: number = 5;
public static damping: number = .1;
public static externalForce: THREE.Vector3 = new THREE.Vector3(0, 0, 0);
// particls
public static pList? = new Array<Array<Particle>>();
public static pColorDefault: number = 0xffffff;
public static pColorNoPhysix: number = 0xff00ff;
// springs
public static readonly MAX_CONSTANT = 10;
public static readonly MIN_CONSTANT = 0.1;
public static sList? = new Array<Spring>();
public static sConstants: SpringConstants = { structural: 10, shear: 2, bend: 4 };
public static sColorStructural: number = 0xff0000;
public static sColorShear: number = 0x00ff00;
public static sColorBend: number = 0x0000ff;
// integrator
public static readonly STEP_SIZE = {Fixed:0, Adaptive:1};
public static readonly INTEGRATORS: Integrator[] = [
new EulerIntegrator(),
new MidpointIntegrator(),
new RungeKuttaIntegrator
]
public static integratorType: number = 2;
public static stepSize: number = 0;
public static setEuler = () => { SimulationData.integratorType = 0; }
public static setMidpoint = () => { SimulationData.integratorType = 1; }
public static setRungeKutta = () => { SimulationData.integratorType = 2; }
public static resetExternalForce(): void {
SimulationData.externalForce.x = 0;
SimulationData.externalForce.y = 0;
SimulationData.externalForce.z = 0;
}
public static generateTextile(): void {
// 1. reset pList
delete SimulationData.pList;
SimulationData.pList = new Array<Array<Particle>>();
// 2. generate particles
for (let i = 0; i < SimulationData.resolution; i++) {
SimulationData.pList[i] = new Array<Particle>();
for (let j = 0; j < SimulationData.resolution; j++) {
const p = new Particle({
position: new THREE.Vector3(
SimulationData.width * (i / SimulationData.resolution) - SimulationData.width / 2,
SimulationData.height * (j / SimulationData.resolution) - SimulationData.height / 2,
0),
velocity: new THREE.Vector3(0, 0, 0),
mass: SimulationData.totalMass / (SimulationData.resolution * SimulationData.resolution),
usePhysics: true,
references: [],
radius: 0,
color: SimulationData.pColorDefault,
wireframe: true
});
SimulationData.pList[i][j] = p;
}
}
for (let i = 0; i < SimulationData.resolution; i++) {
SimulationData.pList[i][SimulationData.resolution - 1].usePhysics = false;
}
// 3. set particle mass and color
SimulationData.changedParticleSize();
SimulationData.changedParticleColor();
// 4. add particles to container
const pLinList = new Array<THREE.Mesh>();
SimulationData.pList.forEach(pSub => {
pSub.forEach(p => {
pLinList.push(p.mesh);
});
});
// 5. reset sList
delete SimulationData.sList;
SimulationData.sList = new Array<Spring>();
// 6. generate springs
for (let i = 0; i < SimulationData.resolution; i++) {
for (let j = 0; j < SimulationData.resolution; j++) {
// structural springs (horizontal)
if (i < SimulationData.resolution - 1) {
const pA = SimulationData.pList[i][j];
const pB = SimulationData.pList[i + 1][j];
const s = new StructuralSpring({
positionA: pA.position,
positionB: pB.position,
constants: SimulationData.sConstants,
color: SimulationData.sColorStructural,
});
SimulationData.sList.push(s);
s.line.renderOrder = 2;
pA.references.push(s);
pB.references.push(s);
}
// structural springs (vertical)
if (j < SimulationData.resolution - 1) {
const pA = SimulationData.pList[i][j];
const pB = SimulationData.pList[i][j + 1];
const s = new StructuralSpring({
positionA: pA.position,
positionB: pB.position,
constants: SimulationData.sConstants,
color: SimulationData.sColorStructural,
});
SimulationData.sList.push(s);
s.line.renderOrder = 2;
pA.references.push(s);
pB.references.push(s);
}
// shear springs (bottom-left to top-right)
if (i < SimulationData.resolution - 1 && j < SimulationData.resolution - 1) {
const pA = SimulationData.pList[i][j];
const pB = SimulationData.pList[i + 1][j + 1];
const s = new ShearSpring({
positionA: pA.position,
positionB: pB.position,
constants: SimulationData.sConstants,
color: SimulationData.sColorShear,
});
SimulationData.sList.push(s);
s.line.renderOrder = 1;
pA.references.push(s);
pB.references.push(s);
}
// shear springs (top-left to bottom-right)
if (i < SimulationData.resolution - 1 && j > 0) {
const pA = SimulationData.pList[i][j];
const pB = SimulationData.pList[i + 1][j - 1];
const s = new ShearSpring({
positionA: pA.position,
positionB: pB.position,
constants: SimulationData.sConstants,
color: SimulationData.sColorShear,
});
SimulationData.sList.push(s);
s.line.renderOrder = 1;
pA.references.push(s);
pB.references.push(s);
}
// bend springs (horizontal)
if (i < SimulationData.resolution - 2) {
const pA = SimulationData.pList[i][j];
const pB = SimulationData.pList[i + 2][j];
const s = new BendingSpring({
positionA: pA.position,
positionB: pB.position,
constants: SimulationData.sConstants,
color: SimulationData.sColorBend,
});
SimulationData.sList.push(s);
s.line.renderOrder = 0;
pA.references.push(s);
pB.references.push(s);
}
// bend springs (vertical)
if (j < SimulationData.resolution - 2) {
const pA = SimulationData.pList[i][j];
const pB = SimulationData.pList[i][j + 2];
const s = new BendingSpring({
positionA: pA.position,
positionB: pB.position,
constants: SimulationData.sConstants,
color: SimulationData.sColorBend,
});
SimulationData.sList.push(s);
s.line.renderOrder = 0;
pA.references.push(s);
pB.references.push(s);
}
}
}
// 7. add springs to container
const sLinList = new Array<THREE.Line>();
SimulationData.sList.forEach(s => {
sLinList.push(s.line);
});
// 8. add particles and springs to container
while (SimulationData.container.children.length > 0) {
let rm = SimulationData.container.children.pop();
if (rm instanceof THREE.Line) {
rm.geometry.dispose();
rm.material.dispose();
} else if (rm instanceof THREE.Mesh) {
rm.geometry.dispose();
rm.material.dispose();
}
}
SimulationData.container.add(...pLinList, ...sLinList);
}
public static changedParticleSize(): void {
// 1. calculate current mass
const pCount = SimulationData.resolution * SimulationData.resolution;
const pMass = SimulationData.totalMass / pCount;
// 1.1 update particle mass
SimulationData.pList?.forEach(pSub => {
pSub.forEach(p => {
p.mass = pMass;
});
});
// 2. calculate max pSize
const maxSize = Math.min(SimulationData.width, SimulationData.height);
// 3. normalize pMass
const pMassNorm = (pMass + SimulationData.MIN_TOTAL_MASS) /
(SimulationData.MAX_TOTAL_MASS + SimulationData.MIN_TOTAL_MASS);
// 4. calculate pSize
const pSize = maxSize * pMassNorm;
// 5. set pSize
SimulationData.pList?.forEach(pSub => { pSub.forEach(p => p.setRadius(pSize)) });
}
public static changedDimensions(): void {
if (!SimulationData.pList)
return;
// 1. reset positions of particles
for (let i = 0; i < SimulationData.resolution; i++) {
for (let j = 0; j < SimulationData.resolution; j++) {
SimulationData.pList[i][j].setPosition(
SimulationData.width * (i / SimulationData.resolution) - SimulationData.width / 2,
SimulationData.height * (j / SimulationData.resolution) - SimulationData.height / 2,
0);
}
}
// 2. set size based on particle mass
SimulationData.changedParticleSize();
}
public static changedParticleColor(): void {
// iterate over all particles and set color based on current internal state
SimulationData.pList?.forEach(pSub => {
pSub.forEach(p => {
if (p.usePhysics) {
p.setColor(SimulationData.pColorDefault);
} else {
p.setColor(SimulationData.pColorNoPhysix);
}
})
});
}
public static changedSpringColor(): void {
// iterate over all particles and set color based on current internal state
SimulationData.sList?.forEach(s => {
if (s instanceof StructuralSpring) {
s.setColor(SimulationData.sColorStructural);
} else if (s instanceof ShearSpring) {
s.setColor(SimulationData.sColorShear);
} else if (s instanceof BendingSpring) {
s.setColor(SimulationData.sColorBend);
}
});
}
public static switchParticlePhysics(): void {
// 1. is picked object not null
if (!picked) {
return;
}
// 2. compare mesh to all particles => find particle and change usePhysics
SimulationData.pList?.forEach(pSub => {
pSub.forEach(p => {
if (p.mesh === picked) {
p.usePhysics = !p.usePhysics;
p.setColor(p.usePhysics ? SimulationData.pColorDefault
: SimulationData.pColorNoPhysix);
}
})
});
}
gui(gui: GUI): void {
const general = gui.addFolder('General');
general.add(SimulationData, 'totalMass', SimulationData.MIN_TOTAL_MASS, SimulationData.MAX_TOTAL_MASS)
.step(0.1).name('Total Mass').onChange(SimulationData.changedParticleSize);
general.add(SimulationData, 'width', SimulationData.MIN_WIDTH, SimulationData.MAX_WIDTH)
.step(0.1).name('Width').onChange(SimulationData.changedDimensions);
general.add(SimulationData, 'height', SimulationData.MIN_HEIGHT, SimulationData.MAX_HEIGHT)
.step(0.1).name('Height').onChange(SimulationData.changedDimensions);
general.add(SimulationData, 'resolution', SimulationData.MIN_RESOLUTION, SimulationData.MAX_RESOLUTION)
.step(1).name('Resolution').onChange(SimulationData.generateTextile);
general.open();
const integration = gui.addFolder('Integrator');
integration.add(SimulationData, 'setEuler').name('Euler').onChange(()=>{console.log(SimulationData.integratorType)});
integration.add(SimulationData, 'setMidpoint').name('Midpoint').onChange(()=>{console.log(SimulationData.integratorType)});
integration.add(SimulationData, 'setRungeKutta').name('Runge Kutta (4th order)').onChange(()=>{console.log(SimulationData.integratorType)});
integration.open();
const extForce = general.addFolder('External Force');
extForce.add(SimulationData.externalForce, 'x',).step(0.01);
extForce.add(SimulationData.externalForce, 'y',).step(0.01);
extForce.add(SimulationData.externalForce, 'z',).step(0.01);
extForce.open();
const particles = gui.addFolder('Particles');
particles.add(SimulationData, 'switchParticlePhysics').name('Switch Particle Physics');
particles.addColor(SimulationData, 'pColorDefault').name('Default').onChange(SimulationData.changedParticleColor);
particles.addColor(SimulationData, 'pColorNoPhysix').name('No Phyisx').onChange(SimulationData.changedParticleColor);
particles.open();
const springs = gui.addFolder('Springs');
springs.add(SimulationData, 'damping', 0, 0.2, 0.0001).name('Damping Factor');
springs.add(SimulationData.sConstants, 'structural', SimulationData.MIN_CONSTANT, SimulationData.MAX_CONSTANT)
.step(0.1).name('Structural');
springs.add(SimulationData.sConstants, 'shear', SimulationData.MIN_CONSTANT, SimulationData.MAX_CONSTANT)
.step(0.1).name('Shear');
springs.add(SimulationData.sConstants, 'bend', SimulationData.MIN_CONSTANT, SimulationData.MAX_CONSTANT)
.step(0.1).name('Bend');
springs.addColor(SimulationData, 'sColorStructural').name('Structural').onChange(SimulationData.changedSpringColor);
springs.addColor(SimulationData, 'sColorShear').name('Shear').onChange(SimulationData.changedSpringColor);
springs.addColor(SimulationData, 'sColorBend').name('Bend').onChange(SimulationData.changedSpringColor);
springs.open();
}
object(): THREE.Object3D<THREE.Event> {
return SimulationData.container;
}
}
export { SimulationData };
abstract class Integrator {
abstract step(x: Array<number>, h: number, f: (x: Array<number>) => Array<number>): Array<number>;
public static adaptiveStep(integrator: Integrator, x: Array<number>, h: number,
f: (x: Array<number>) => Array<number>, errorMax: number): number {
let xFull = integrator.step(x, h, f);
let xHalf = integrator.step(x, h / 2, f);
let error: Array<number> = [];
for (let i = 0; i < xFull.length; i++) {
error.push(Math.abs(xFull[i] - xHalf[i]));
}
let errorApproxMax = error.reduce((a, b) => Math.max(a, b));
return h * Math.sqrt(errorMax / errorApproxMax);
}
}
class EulerIntegrator extends Integrator {
step(x: Array<number>, h: number, f: (x: Array<number>) => Array<number>): Array<number>
{
let y = f(x);
let x_new = x.map((_, i) => x[i] + h * y[i]);
return x_new;
}
}
class MidpointIntegrator extends Integrator {
private euler: EulerIntegrator;
constructor() {
super();
this.euler = new EulerIntegrator();
}
step(x: Array<number>, h: number, f: (x: Array<number>) => Array<number>): Array<number>
{
let euler_step = this.euler.step(x, h / 2, f);
let y = f(euler_step);
let x_new = x.map((_, i) => x[i] + h * y[i]);
return x_new;
}
}
class RungeKuttaIntegrator extends Integrator {
step(x: Array<number>, h: number, f: (x: Array<number>) => Array<number>): Array<number>
{
let k1 = f(x);
let k2 = f(x.map((_, i) => x[i] + h / 2 * k1[i]));
let k3 = f(x.map((_, i) => x[i] + h / 2 * k2[i]));
let k4 = f(x.map((_, i) => x[i] + h * k3[i]));
let x_new = x.map((_, i) => x[i] + h / 6 * (k1[i] + 2 * k2[i] + 2 * k3[i] + k4[i]));
return x_new;
}
}
export { Integrator, EulerIntegrator, MidpointIntegrator, RungeKuttaIntegrator };
import * as THREE from 'three';
import { PipelineRenderable } from '../../core/Pipeline';
import './Springs';
import { Spring } from './Springs';
export interface ParticleParameters {
// particle traits
position: THREE.Vector3;
velocity: THREE.Vector3;
// internal parameters
usePhysics: boolean;
mass: number;
references: Spring[];
// visual parameters
radius: number;
color: number;
wireframe: boolean;
}
export class Particle implements PipelineRenderable {
public position: THREE.Vector3;
public velocity: THREE.Vector3;
public mass: number;
public usePhysics: boolean;
public references: Spring[];
public mesh: THREE.Mesh;
constructor(params: ParticleParameters) {
this.position = params.position;
this.velocity = params.velocity;
this.mass = params.mass;
this.usePhysics = params.usePhysics;
this.references = params.references;
this.mesh = new THREE.Mesh(
new THREE.SphereGeometry(params.radius, 4, 2),
new THREE.MeshBasicMaterial({ color: params.color, wireframe: params.wireframe })
);
this.mesh.position.copy(this.position);
}
public setColor(color: number): void {
(this.mesh.material as THREE.MeshBasicMaterial).color.set(color);
}
public setState(px: number, py: number, pz: number, vx: number, vy: number, vz: number): void {
this.velocity.set(vx, vy, vz);
this.setPosition(px, py, pz);
}
public setPosition(x: number, y: number, z: number): void {
this.position.set(x, y, z);
this.mesh.position.copy(this.position);
this.references.forEach(spring => spring.notify());
}
public setRadius(radius: number): void {
this.mesh.geometry.dispose();
this.mesh.geometry = new THREE.SphereGeometry(radius, 4, 2);
}
public object(): THREE.Object3D<THREE.Event> {
return this.mesh;
}
}
This diff is collapsed.
import { Pipeline, PipelineData } from "./core/Pipeline";
import { Demo } from "./00-welcome-and-example/Demo";
import { Example } from "./00-welcome-and-example/example";
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
let scenario: PipelineData;
let pipeline: Pipeline;
if (ref.includes("bezier")) {
pipeline = new Pipeline(false);
pipeline.orbitControls.enableRotate = false;
scenario = new BezierDemo();
} else if (ref.includes("quaternion")) {
pipeline = new Pipeline();
scenario = new QuaternionDemo();
} else if (ref.includes("simulation")) {
pipeline = new Pipeline();
scenario = new SimulationDemo();
} else if (ref.includes("example")) {
pipeline = new Pipeline();
scenario = new Example();
} else {
pipeline = new Pipeline();
scenario = new Demo();
}
pipeline.init(scenario);
pipeline.loop();
}
import * as THREE from 'three';
let scene: THREE.Scene, camera: THREE.PerspectiveCamera, renderer: THREE.Renderer, cube: THREE.Mesh;
const speed = 0.01;
let WIDTH = window.innerWidth * 0.8;
let HEIGHT = window.innerHeight * 0.50;
function initCamera() {
camera = new THREE.PerspectiveCamera(70, WIDTH / HEIGHT, 1, 10);
camera.position.set(0, 3.5, 5);
camera.lookAt(scene.position);
}
function initRenderer() {
const target = document.querySelector("#preview") as HTMLCanvasElement;
renderer = new THREE.WebGLRenderer({ canvas: target, antialias: true, alpha: true });
renderer.setSize(WIDTH, HEIGHT);
window.addEventListener("resize", () => {
WIDTH = window.innerWidth * 0.8;
HEIGHT = window.innerHeight * 0.50;
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
});
}
function initCube() {
cube = new THREE.Mesh(new THREE.BoxGeometry(2, 2, 2), new THREE.MeshBasicMaterial(
{ wireframe: true, color: 0x000000 }));
scene.add(cube);
}
function rotateCube() {
cube.rotation.x += speed;
cube.rotation.y += speed;
cube.rotation.z += speed;
}
export function init() {
scene = new THREE.Scene();
initCamera();
initRenderer();
initCube();
}
export function render() {
requestAnimationFrame(render);
rotateCube();
renderer.render(scene, camera);
}
This diff is collapsed.
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment