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

feat: multi slerp

parent 22a9cb57
No related branches found
No related tags found
1 merge request!1feat: base project
import * as THREE from 'three';
import { PipelineRenderable } from '../core/Pipeline';
import { Line1d } from "../core/Shapes";
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.position().z = offset;
this.y.position().z = offset;
this.z.position().z = offset;
}
object(): THREE.Object3D {
return this._objects;
}
position(): THREE.Vector3 {
return this._objects.position;
}
}
export { Axes };
\ No newline at end of file
import * as THREE from 'three';
import { PipelineData } from "../core/Pipeline"; import { PipelineData } from "../core/Pipeline";
import { Mesh } from "../core/Shapes";
import { Axes } from './Axes';
import { Rotator } from './Rotator';
class QuaternionDemo extends PipelineData { class QuaternionDemo extends PipelineData {
public constructor() { super(); } public constructor() { super(); }
data(): void { data(): void {
/* INIT DATA HERE */ 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 pointLight = new THREE.PointLight(0x6e6e8e, 1);
pointLight.position.set(-1, -1, -1);
this.scene.add(pointLight);
const axes = new Axes(5);
this.addObject(axes);
const material = new THREE.MeshLambertMaterial({ color: 0xffffff });
const rotationObject: Mesh = new Mesh("../../assets/suzanne.obj", new THREE.Color(0xffffff));
rotationObject.material = material;
this.addObject(rotationObject);
const rotator: Rotator = new Rotator(rotationObject.object());
this.addObserver(rotator);
this.addGUI(rotator);
} }
} }
......
import * as THREE from 'three';
export class Quaternion { export class Quaternion {
public x: number; private _x: number;
public y: number; private _y: number;
public z: number; private _z: number;
public w: number; private _w: number;
constructor(x: number = 0, y: number = 0, z: number = 0, w: number = 1) { constructor(x: number = 0, y: number = 0, z: number = 0, w: number = 1) {
this.x = x; this._x = x;
this.y = y; this._y = y;
this.z = z; this._z = z;
this.w = w; this._w = w;
} }
public static fromEuler(x: number, y: number, z: number): Quaternion { public get x(): number { return this._x; }
const c1 = Math.cos(x / 2); public get y(): number { return this._y; }
const s1 = Math.sin(x / 2); public get z(): number { return this._z; }
const c2 = Math.cos(y / 2); public get w(): number { return this._w; }
const s2 = Math.sin(y / 2);
const c3 = Math.cos(z / 2);
const s3 = Math.sin(z / 2);
const qw = c1 * c2 * c3 + s1 * s2 * s3; public set x(value: number) {
const qx = s1 * c2 * c3 - c1 * s2 * s3; this._x = value;
const qy = c1 * s2 * c3 + s1 * c2 * s3;
const qz = c1 * c2 * s3 - s1 * s2 * c3;
return new Quaternion(qx, qy, qz, qw);
} }
public static toEulerMatrix(q: Quaternion): Array<Array<number>> { public set y(value: number) {
this._y = value;
let container = new Array<Array<number>>();
let row1 = new Array<number>();
row1.push(1 - 2 * (q.y * q.y + q.z * q.z));
row1.push(2 * (q.x * q.y - q.z * q.w));
row1.push(2 * (q.x * q.z + q.y * q.w));
container.push(row1);
let row2 = new Array<number>();
row2.push(2 * (q.x * q.y + q.z * q.w));
row2.push(1 - 2 * (q.x * q.x + q.z * q.z));
row2.push(2 * (q.y * q.z - q.x * q.w));
container.push(row2);
let row3 = new Array<number>();
row3.push(2 * (q.x * q.z - q.y * q.w));
row3.push(2 * (q.y * q.z + q.x * q.w));
row3.push(1 - 2 * (q.x * q.x + q.y * q.y));
container.push(row3);
return container;
} }
public static identity(): Quaternion { public set z(value: number) {
return new Quaternion(0, 0, 0, 1); this._z = value;
} }
public static lerp(qa: Quaternion, qb: Quaternion, t: number): Quaternion { public set w(value: number) {
return qb.clone().sub(qa).multiplyScalar(t).add(qa); this._w = value;
} }
public static slerp(qa: Quaternion, qb: Quaternion, t: number): Quaternion { public get eulerMatrix(): THREE.Matrix4 {
let dot = qa.dot(qb); const copy = Quaternion.normalize(this.clone());
const m = new THREE.Matrix4();
if (dot < 0.0) 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,
qb.multiplyScalar(-1); 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,
dot = -dot; 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;
} }
if (dot > 0.999999) public get array(): number[] {
{ return [this._x, this._y, this._z, this._w];
return Quaternion.slerp(qa, qb, t).normalise();
} }
const theta0 = Math.acos(dot); public fromEuler(euler: THREE.Vector3): this {
const theta = theta0 * t; const c1 = Math.cos(euler.x / 2);
const sinTheta = Math.sin(theta); const s1 = Math.sin(euler.x / 2);
const sinTheta0 = Math.sin(theta0); const c2 = Math.cos(euler.y / 2);
const s0 = Math.cos(theta) - dot * sinTheta / sinTheta0; const s2 = Math.sin(euler.y / 2);
const s1 = sinTheta / sinTheta0; const c3 = Math.cos(euler.z / 2);
const s3 = Math.sin(euler.z / 2);
return qa.clone().multiplyScalar(s0).add(qb.clone().multiplyScalar(s1)); this._x = s1 * c2 * c3 + c1 * s2 * s3;
this._y = c1 * s2 * c3 - s1 * c2 * s3;
this._z = c1 * c2 * s3 + s1 * s2 * c3;
this._w = c1 * c2 * c3 - s1 * s2 * s3;
this.copy(Quaternion.normalize(this));
return this;
} }
public toString(): string { public toString(): string {
return `Quaternion(${this.x}, ${this.y}, ${this.z}, ${this.w})`; return `Quaternion(${this._x}, ${this._y}, ${this._z}, ${this._w})`;
} }
public set(x: number, y: number, z: number, w: number): this { public set(x: number, y: number, z: number, w: number): this {
this.x = x; this._x = x;
this.y = y; this._y = y;
this.z = z; this._z = z;
this.w = w; this._w = w;
return this; return this;
} }
public copy(q: Quaternion): this { public copy(q: Quaternion): this {
this.x = q.x; this._x = q._x;
this.y = q.y; this._y = q._y;
this.z = q.z; this._z = q._z;
this.w = q.w; this._w = q._w;
return this; return this;
} }
public clone(): Quaternion { public clone(): Quaternion {
return new Quaternion(this.x, this.y, this.z, this.w); return new Quaternion(this._x, this._y, this._z, this._w);
} }
public add(q: Quaternion): this {
this.x += q.x; public static identity(): Quaternion {
this.y += q.y; return new Quaternion(0, 0, 0, 1);
this.z += q.z;
this.w += q.w;
return this;
} }
public sub(q: Quaternion): this { public static slerp(qa: Quaternion, qb: Quaternion, t: number): Quaternion {
this.x -= q.x;
this.y -= q.y; let cq1 = qa.clone();
this.z -= q.z; let cq2 = qb.clone();
this.w -= q.w;
return this; cq1 = Quaternion.normalize(cq1);
cq2 = Quaternion.normalize(cq2);
let costheta = Quaternion.dot(cq1, cq2);
if (costheta < 0.0) {
cq2 = Quaternion.inverse(cq2);
costheta = -costheta;
} }
public multiply(q: Quaternion): this { if (costheta > 0.9995) {
const x = this.x; return Quaternion.add(cq1, Quaternion.multiplyScalar(
const y = this.y; Quaternion.subtract(cq2, cq1), t));
const z = this.z; }
const w = this.w;
this.x = w * q.x + x * q.w + y * q.z - z * q.y; const theta = Math.acos(costheta);
this.y = w * q.y + y * q.w + z * q.x - x * q.z; const thetat = theta * t;
this.z = w * q.z + z * q.w + x * q.y - y * q.x;
this.w = w * q.w - x * q.x - y * q.y - z * q.z; const sinthetat = Math.sin(thetat);
return this; const sintheta = Math.sin(theta);
const s0 = Math.cos(theta) - costheta * sinthetat / sintheta;
const s1 = sinthetat / sintheta;
return Quaternion.add(
Quaternion.multiplyScalar(cq1, s0),
Quaternion.multiplyScalar(cq2, s1)
)
} }
public multiplyScalar(s: number): this { public static add(qa: Quaternion, qb: Quaternion): Quaternion {
this.x *= s; return new Quaternion(qa._x + qb._x, qa._y + qb._y, qa._z + qb._z, qa._w + qb._w);
this.y *= s;
this.z *= s;
this.w *= s;
return this;
} }
public dot(q: Quaternion): number { public static subtract(qa: Quaternion, qb: Quaternion): Quaternion {
return this.x * q.x + this.y * q.y + this.z * q.z + this.w * q.w; return new Quaternion(qa._x - qb._x, qa._y - qb._y, qa._z - qb._z, qa._w - qb._w);
} }
public length(): number { public static multiply(qa: Quaternion, qb: Quaternion): Quaternion {
return Math.sqrt(this.dot(this)); 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 normalise(): this { public static multiplyScalar(a: Quaternion | number, b: Quaternion | number): Quaternion {
const denom = this.length(); if (typeof b === 'number' && a instanceof Quaternion) {
if (denom < 1.0e-6) { return new Quaternion(a._x * b, a._y * b, a._z * b, a._w * b);
this.copy(Quaternion.identity()); } if (typeof a === 'number' && b instanceof Quaternion) {
return this; return new Quaternion(b._x * a, b._y * a, b._z * a, b._w * a);
} }
return this.multiplyScalar(1 / this.length()); throw new Error("Invalid arguments");
} }
public conjugate(): this { public static dot(qa: Quaternion, qb: Quaternion): number {
this.x *= -1; return qa._x * qb._x + qa._y * qb._y + qa._z * qb._z + qa._w * qb._w;
this.y *= -1; }
this.z *= -1;
return this; public static len(qa: Quaternion): number {
return Math.sqrt(Quaternion.dot(qa, qa));
}
public static normalize(qa: Quaternion): Quaternion {
const root = Quaternion.len(qa);
if (root < 0.00001) {
return Quaternion.identity();
}
return Quaternion.multiplyScalar(qa, 1 / root);
}
public static inverse(qa: Quaternion): Quaternion {
return Quaternion.multiplyScalar(qa, -1);
} }
} }
\ 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;
public constructor(objectReference: THREE.Object3D) {
this._objectReference = objectReference;
this._orientations = [];
this._index = 0;
// TODO: Extract controls to seperate class
document.addEventListener('keydown', (event: KeyboardEvent) => {
let step = 0.1;
if (event.shiftKey) {
step = -step;
}
if (event.key.toLowerCase() === 'a') {
this._orientations[this._index].x += step;
this._orientations[this._index].x = this._orientations[this._index].x % (2 * Math.PI);
}
if (event.key.toLowerCase() === 's') {
this._orientations[this._index].y += step;
this._orientations[this._index].y = this._orientations[this._index].y % (2 * Math.PI);
}
if (event.key.toLowerCase() === 'd') {
this._orientations[this._index].z += step;
this._orientations[this._index].z = this._orientations[this._index].z % (2 * Math.PI);
}
});
}
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', -1, 1, .01).listen();
folder.add(q, 'y', -1, 1, .01).listen();
folder.add(q, 'z', -1, 1, .01).listen();
folder.add(q, 'w', -1, 1, .01).listen();
folder.closed = false;
}
gui(gui: dat.GUI): void {
const folder = gui.addFolder('Rotator');
folder.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();
}
});
folder.add(this, "_t", 0, 1, 0.01).listen().name("t");
folder.add(this, 'addQuaternion').name("Add Quaternion");
folder.add(this, 'popQuaternion').name("Pop Quaternion");
folder.add(this, 'normaliseQuaternions').name("Normalise Quaternions");
this._rangeController = folder.add(this, '_index', 0, this._orientations.length - 1, 1).name("Index").listen();
folder.closed = false;
this._guiRoot = folder;
}
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.slerp(this._orientations[0], this._orientations[1], this._t);
for (let i = 2; i < this._orientations.length ; i++) {
result = Quaternion.slerp(result, this._orientations[i], this._t);
}
this._objectReference.setRotationFromMatrix(result.eulerMatrix);
}
}
}
export {Rotator}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment