diff --git a/src/main.ts b/src/main.ts index c0bf5eb96c7bc7b727241618730da8db43262d32..bc8f73653bccd76ddaf94bceaa36240a77301785 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,29 +1,35 @@ import * as CG from "./uitls/Rendering"; import { UI } from "./uitls/UI"; import { Curve2d } from "./uitls/Curve2d"; -import { AmbientLight, DirectionalLight } from "three"; +import { AmbientLight, GridHelper, PointLight, PointLightHelper } from "three"; import * as THREE from "three"; import { RotationObject } from "./uitls/RotationObject"; import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader"; +import { colorprime } from "./uitls/globals"; -function demo1() { +function demo1(render: CG.RenderManager, ui: UI) { const curve = new Curve2d(); curve.objects().forEach(obj => { render.add(obj); }); ui.addModifiable(curve); } -function demo2() { +function demo2(render: CG.RenderManager, ui: UI) { const rot = new RotationObject(); + const grid = new GridHelper(10,10); + grid.renderOrder = -1 + + render.add(grid) + const loader = new OBJLoader(); loader.setPath("models/"); loader.load( - "Suzanne.obj", + "Arrow.obj", function (obj) { let tmp = obj.children[0] as THREE.Mesh; - tmp.material = new THREE.MeshLambertMaterial({ wireframe: true, side: 0 }); + tmp.material = new THREE.MeshLambertMaterial({ wireframe: false, color: 0xaaaaaa }); rot.setMesh(tmp); render.add(tmp); } @@ -32,35 +38,33 @@ function demo2() { const ambient = new AmbientLight(0x404040); render.add(ambient); - const directionalright = new DirectionalLight(0xFFFFFF, 1.5); - render.add(directionalright); - directionalright.translateX(3) + const plightx = new PointLight(0xffffff, 1, 100); + plightx.position.set(5, 0, 0); + render.add(plightx); - const directionalleft = new DirectionalLight(0xFFFFFF, 1.5); - render.add(directionalleft); - directionalleft.translateX(-3) + const plighty = new PointLight(0xffffff, 1, 100); + plighty.position.set(0, 5, 0); + render.add(plighty); - const directional = new DirectionalLight(0xFFFFFF, 0.5); - render.add(directional); - directional.translateZ(3); + const plightz = new PointLight(0xffffff, 1, 100); + plightz.position.set(0, 0, 5); + render.add(plightz); rot.objects().forEach(obj => { render.add(obj); }); render.render(); - ui.addModifiable(rot); - } const ui = new UI(); -const render = new CG.RenderManager('#canvas', { near: 0.1, far: 1000, fov: 45, height: 1 }); +const render = new CG.RenderManager('#canvas', { near: 0.01, far: 1000, fov: 45, height: 1 }); ui.addModifiable(render); -// demo1(); -demo2(); +// demo1(render, ui); +demo2(render, ui); render.render(); diff --git a/src/uitls/Quaternion.ts b/src/uitls/Quaternion.ts index 60fc67b80ac68f6027a807ced7ac559ebc924ca6..1f382eec2a1b56f25f642d3c692d7c89c251658a 100644 --- a/src/uitls/Quaternion.ts +++ b/src/uitls/Quaternion.ts @@ -6,81 +6,92 @@ import { Matrix4 } from "three"; * @class Quaternion * @description Class for quaternion. Inspired by prbt book. * https://pbr-book.org/3ed-2018/Geometry_and_Transformations/Animating_Transformations#Quaternions + * @static all static methods are returning a copy. Passed quaternion are handled as immutable. */ export class Quaternion { - public _x: number; - public _y: number; - public _z: number; - public _w: number; - - private _mode: string = "normal"; - private _keepUnitQuaternion: boolean; - - constructor(x: number = 0, y: number = 0, z: number = 0, w: number = 1, keepUnitQuaternion: boolean = true) { + constructor(x: number = 0, y: number = 0, z: number = 0, w: number = 1, keepUnitQuaternion: boolean = true, mode: string = "normal") { this._x = x; this._y = y; this._z = z; this._w = w; + this._mode = mode; this._keepUnitQuaternion = keepUnitQuaternion; } + 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 get mode(): string { return this._mode; } + public get keepUnitQuaternion(): boolean { return this._keepUnitQuaternion; } + public set x(x: number) { this.set(x, this.y, this.z, this.w) } public set y(y: number) { this.set(this.x, y, this.z, this.w) } public set z(z: number) { this.set(this.x, this.y, z, this.w) } public set w(w: number) { this.set(this.x, this.y, this.z, w) } public set mode(mode: string) { this._mode = mode; } + public set keepUnitQuaternion(keepUnitQuaternion: boolean) { this._keepUnitQuaternion = keepUnitQuaternion; } - public static setX(q: Quaternion, x: number): void { q.x = x; } - public static setY(q: Quaternion, y: number): void { q.y = y; } - public static setZ(q: Quaternion, z: number): void { q.z = z; } - public static setW(q: Quaternion, w: number): void { q.w = w; } + /** + * Setter which is set values and if keepUnitQuaternion is true, it will keep values for unit quaternion. + * @param x Quaternion i component + * @param y Quaternion j component + * @param z Quaternion k component + * @param w Quaternion s component + * @returns itself + */ public set(x: number, y: number, z: number, w: number): Quaternion { - if (this._keepUnitQuaternion) { - const length = Math.sqrt(x * x + y * y + z * z + w * w); - - if (length === 0) { - this._x = 0; - this._y = 0; - this._z = 0; - this._w = 1; - } else { - this._x = x / length; - this._y = y / length; - this._z = z / length; - this._w = w / length; - } - - return this - } - this._x = x; this._y = y; this._z = z; this._w = w; + + const length = Math.sqrt(x * x + y * y + z * z + w * w); + if (this._keepUnitQuaternion && length !== 0) { + this._x = x / length; + this._y = y / length; + this._z = z / length; + this._w = w / length; + } + return this; } + + /** + * In js there is call by reference, so we need to clone the quaternion. + * @returns clone of itself + */ 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, + this.keepUnitQuaternion, this.mode); } + + /** + * deep copy of quaternion + * @param q Quaternion to be copied + * @returns itself + */ public copy(q: Quaternion): Quaternion { this._x = q.w; this._y = q.x; this._z = q.y; this._w = q.z; + this._mode = q.mode; + this._keepUnitQuaternion = q.keepUnitQuaternion; return this; } + + /** + * Transforms quaternion to matrix + */ public toMatrix(): Matrix4 { let copy = this.clone(); @@ -115,16 +126,28 @@ export class Quaternion { return matrix; } + + // dot product public static dot(q1: Quaternion, q2: Quaternion): number { return q1.w * q2.w + q1.x * q2.x + q1.y * q2.y + q1.z * q2.z; } + + // normalize quaternion public static normalize(q: Quaternion): Quaternion { const length = Math.sqrt(Quaternion.dot(q, q)); if (length === 0) return new Quaternion(); return new Quaternion(q.x / length, q.y / length, q.z / length, q.w / length); } + + /** + * Multiply two quaternions or a quaternion and scalar. Function handles both because + * it is neater. + * @param q Quaternion to be multiplied + * @param other is another quaternion or number + * @returns + */ public static multiply(q: Quaternion, other: Quaternion | number): Quaternion { if (typeof other === "number") @@ -138,18 +161,26 @@ export class Quaternion { ); } + + // addition of two quaternions public static add(q1: Quaternion, q2: Quaternion): Quaternion { return new Quaternion(q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.w + q2.w); } + + // subtraction of two quaternions public static subtract(q1: Quaternion, q2: Quaternion): Quaternion { return Quaternion.add(q1, Quaternion.multiply(q2, -1)); } + + // inverse of quaternion ! real part is not changed public static inverse(q: Quaternion): Quaternion { return new Quaternion(-q.x, -q.y, -q.z, q.w); } + + // spherical linear interpolation public static slerp(q1: Quaternion, q2: Quaternion, t: number): Quaternion { let cq1 = q1.clone(); @@ -182,6 +213,14 @@ export class Quaternion { Quaternion.multiply(cq1, s0), Quaternion.multiply(cq2, s1) ) - } + + + public _x: number; + public _y: number; + public _z: number; + public _w: number; + + private _mode: string; + private _keepUnitQuaternion: boolean; } \ No newline at end of file diff --git a/src/uitls/RotationObject.ts b/src/uitls/RotationObject.ts index 3d11ee04600475b4616d7286d33ce472e541ca4a..7966ebaf0df15de2675cbd3d9c454f5305055185 100644 --- a/src/uitls/RotationObject.ts +++ b/src/uitls/RotationObject.ts @@ -1,70 +1,60 @@ -import { GUI } from "dat.gui"; -import { BufferGeometry, Color, Group, Line, LineBasicMaterial, Mesh, MeshBasicMaterial, Object3D, SphereBufferGeometry, Vector3 } from "three"; -import { Quaternion } from "./Quaternion"; +import { + SphereBufferGeometry, MeshBasicMaterial, LineBasicMaterial, BufferGeometry, + Object3D, Vector3, Group, Line, Mesh +} from "three"; import { Updatable, Modifiable } from "./Interfaces"; +import { colorprime, colorsec } from "./globals"; +import { Quaternion } from "./Quaternion"; +import { GUI } from "dat.gui"; export class RotationObject implements Updatable, Modifiable { - public SLERP: boolean = false; - public MODE: string = "Single"; - - public qa: Quaternion = new Quaternion();; - public qb: Quaternion = new Quaternion();; - public t: number = 0; - - private _qworking: Quaternion = new Quaternion();; - // private _orientation: Group = new Group(); - private _coordinate: Group = new Group(); - private _rotationAxis: Group = new Group(); - private _mesh?: Mesh; - constructor() { this.createReference(); this.createRotationAxis(); } + + /** + * Object which should be displayed by the renderer + */ objects(): Object3D[] { return [this._coordinate, this._rotationAxis]; } - update(): void { - if (this.SLERP) { - this._qworking = Quaternion.slerp(this.qa, this.qb, this.t); - } else { - if (this.MODE === "Single") - this._qworking = this.qa.clone(); - else - this._qworking = Quaternion.multiply(this.qa, this.qb); + /** + * Updates the qworking quaternion which is applied to the mesh + * qa and qb are used to calculate the quaternion and left in their original state + */ + update(): void { - } + if (this.SLERP) this.qworking = Quaternion.slerp(this.qa, this.qb, this.t); + else if (this.MODE === "Single") this.qworking = this.qa.clone(); + else this.qworking = Quaternion.multiply(this.qa, this.qb); if (this._mesh) { - this._mesh.setRotationFromMatrix(this._qworking.toMatrix()); + this._mesh.setRotationFromMatrix(this.qworking.toMatrix()); + this._coordinate.setRotationFromMatrix(this.qworking.toMatrix()); this.updateRotationAxis(); } } + + /** + * Resets the two quaternions to their default values + */ resetQuaternions(): void { this.qa.set(0, 0, 0, 1); this.qb.set(0, 0, 0, 1); this.update(); } - clampQuaternions(): void { - const E = 1e-2; - if (Math.abs(this.qa.x) < E) this.qa.x = 0; - if (Math.abs(this.qa.y) < E) this.qa.y = 0; - if (Math.abs(this.qa.z) < E) this.qa.z = 0; - if (Math.abs(this.qa.w) < E) this.qa.w = 0; - - if (Math.abs(this.qb.x) < E) this.qb.x = 0; - if (Math.abs(this.qb.y) < E) this.qb.y = 0; - if (Math.abs(this.qb.z) < E) this.qb.z = 0; - if (Math.abs(this.qb.w) < E) this.qb.w = 0; - } + /** + * Create the UI element of this object + */ createElement(gui: GUI): void { let folder = gui.__folders["Rotation Object"]; if (folder) gui.removeFolder(folder); @@ -89,10 +79,6 @@ export class RotationObject implements Updatable, Modifiable { this.createElement(gui); }); - folder.add(this, "clampQuaternions").name("CLAMP").onFinishChange(() => { - this.createElement(gui); - }); - const range: number = 1; const step: number = .01; @@ -102,10 +88,11 @@ export class RotationObject implements Updatable, Modifiable { const fqa = folder.addFolder(this.MODE === "Single" ? "Quaternion" : "Quaternion A"); fqa.open(); - fqa.add(this.qa, "x", -range, range, step).listen().onChange(func); - fqa.add(this.qa, "y", -range, range, step).listen().onChange(func); - fqa.add(this.qa, "z", -range, range, step).listen().onChange(func); - fqa.add(this.qa, "w", -range, range, step).listen().onChange(func); + fqa.add(this.qa, "keepUnitQuaternion").onChange(func); + fqa.add(this.qa, "x", -range, range, step).onChange(func); + fqa.add(this.qa, "y", -range, range, step).onChange(func); + fqa.add(this.qa, "z", -range, range, step).onChange(func); + fqa.add(this.qa, "w", -range, range, step).onChange(func); } @@ -113,25 +100,25 @@ export class RotationObject implements Updatable, Modifiable { const fqb = folder.addFolder("Quaternion B"); fqb.open(); - fqb.add(this.qb, "x", -range, range, step).listen().onChange(func); - fqb.add(this.qb, "y", -range, range, step).listen().onChange(func); - fqb.add(this.qb, "z", -range, range, step).listen().onChange(func); - fqb.add(this.qb, "w", -range, range, step).listen().onChange(func); + fqb.add(this.qb, "keepUnitQuaternion").onChange(func); + fqb.add(this.qb, "x", -range, range, step).onChange(func); + fqb.add(this.qb, "y", -range, range, step).onChange(func); + fqb.add(this.qb, "z", -range, range, step).onChange(func); + fqb.add(this.qb, "w", -range, range, step).onChange(func); } } - createReference(): void { - const colors: Color[] = [ - new Color(1, 0, 0), - new Color(0, 1, 0), - new Color(0, 0, 1) - ]; + /** + * this creates the local coordinate system of the object + */ + createReference(): void { + const scale = 3; const positions: Vector3[] = [ - new Vector3(5, 0, 0), - new Vector3(0, 5, 0), - new Vector3(0, 0, 5) + new Vector3(scale, 0, 0), + new Vector3(0, scale, 0), + new Vector3(0, 0, scale) ]; let lines: Line[] = []; @@ -140,14 +127,14 @@ export class RotationObject implements Updatable, Modifiable { for (let i = 0; i < 3; i++) { const line = new Line( new BufferGeometry().setFromPoints([positions[i].clone().multiplyScalar(-1), positions[i]]), - new LineBasicMaterial({ color: colors[i].getHex() }) + new LineBasicMaterial({ color: colorprime[i] }) ); line.name = "line" + i; lines.push(line); const sphere = new Mesh( new SphereBufferGeometry(.05, 32, 32), - new MeshBasicMaterial({ color: colors[i].getHex() }) + new MeshBasicMaterial({ color: colorprime[i] }) ); sphere.name = "sphere" + i; sphere.position.copy(positions[i]); @@ -158,12 +145,16 @@ export class RotationObject implements Updatable, Modifiable { this._coordinate.add(...spheres); } + + /** + * Visualisation of the "quaterions" more the resulting rotation axis + */ updateRotationAxis(): void { const suffix: string[] = ["A", "B", "C"]; - const ref: Quaternion[] = [this.qa, this.qb, this._qworking]; + const ref: Quaternion[] = [this.qa, this.qb, this.qworking]; - for (let i = 0; i < suffix.length; i++) { + for (let i = 0; i < ref.length; i++) { const line = this._rotationAxis.getObjectByName("rotationAxisLine" + suffix[i]) as Line; const point = this._rotationAxis.getObjectByName("rotationAxisPoint" + suffix[i]) as Mesh; @@ -174,32 +165,36 @@ export class RotationObject implements Updatable, Modifiable { ref[i].y, ref[i].z ) - const origin = new Vector3(0,0,0); + const origin = new Vector3(0, 0, 0); + + if (suffix[i] === "B" && !this.SLERP) dir.applyMatrix4(this.qa.toMatrix()); + let factor: number = 3.5; + if (suffix[i] === "C") factor = 4; - line.geometry = new BufferGeometry().setFromPoints([origin, dir.normalize().multiplyScalar(2)]); + line.geometry = new BufferGeometry().setFromPoints([origin, dir.normalize().multiplyScalar(factor)]); point.position.copy(dir); } } + + /** + * Initializes the rotation axis lines + */ createRotationAxis(): void { - const colors: Color[] = [ - new Color(0xff8844), - new Color(0x44ff88), - new Color(0x884488) - ] + const suffix: string[] = ["A", "B", "C"]; for (let i = 0; i < suffix.length; i++) { const line = new Line( undefined, - new LineBasicMaterial({ color: colors[i].getHex() }) + new LineBasicMaterial({ color: colorsec[i] }) ); line.name = "rotationAxisLine" + suffix[i]; this._rotationAxis.add(line); const point = new Mesh( new SphereBufferGeometry(.05, 32, 32), - new MeshBasicMaterial({ color: colors[i].getHex() }) + new MeshBasicMaterial({ color: colorsec[i] }) ); point.name = "rotationAxisPoint" + suffix[i]; this._rotationAxis.add(point); @@ -208,8 +203,25 @@ export class RotationObject implements Updatable, Modifiable { this.updateRotationAxis(); } + + /** + * required to load mesh from main, because the objloader is weird + */ setMesh(mesh: Mesh): void { this._mesh = mesh; this.update() } + + + public SLERP: boolean = false; + public MODE: string = "Single"; + + public qa: Quaternion = new Quaternion();; + public qb: Quaternion = new Quaternion();; + public qworking: Quaternion = new Quaternion();; + public t: number = 0; + + private _coordinate: Group = new Group(); + private _rotationAxis: Group = new Group(); + private _mesh?: Mesh; } \ No newline at end of file diff --git a/src/uitls/globals.ts b/src/uitls/globals.ts new file mode 100644 index 0000000000000000000000000000000000000000..146b3594ad79730b9be0d1778d63fa632f42b254 --- /dev/null +++ b/src/uitls/globals.ts @@ -0,0 +1,11 @@ +export const colorprime: [number, number, number] = [ + 0xF55600, // red + 0x5DFF70, // green + 0x123FB3 // blue +] + +export const colorsec: [number, number, number] = [ + 0xF500EF, // pink + 0xFFD500, // yellow + 0x77F1FF // turquoise +] \ No newline at end of file diff --git a/src/uitls/rendering.ts b/src/uitls/rendering.ts index daaef0fee74dd3f5ba5b967fe662766d5f8a4468..c8ee645a3ea83fd2b5f8604eb07fa26af829420e 100644 --- a/src/uitls/rendering.ts +++ b/src/uitls/rendering.ts @@ -30,21 +30,18 @@ class CameraManager { this.cameras = [ new PerspectiveCamera(settings.fov, aspect, settings.near, settings.far), - new OrthographicCamera(-aspect * s, aspect * s, 1* s, -1* s, settings.near, settings.far) + new OrthographicCamera(-aspect * s, aspect * s, 1 * s, -1 * s, settings.near, settings.far) ]; this.active = this.cameras[1]; - this.active.position.set(0, -3, 5); + this.active.position.set(0, 0, 10); } switchCamera(): void { - if (this.active == this.cameras[0]) { - this.active = this.cameras[1]; - this.active.position.set(0, 0, 5); - } - else { - this.active = this.cameras[0]; - } + const pos = this.active.position.clone(); + if (this.active == this.cameras[0]) this.active = this.cameras[1]; + else this.active = this.cameras[0]; + this.active.position.copy(pos); } camera(): Camera { @@ -83,20 +80,21 @@ export class RenderManager implements Modifiable { } + /** + * Create the UI element for dat.gui + * @param gui + */ createElement(gui: GUI): void { const rendering = gui.addFolder('Rendering'); + rendering.open(); rendering.add(this, '_fps', 10, 60).step(1).name('FPS'); rendering.add(this._cameraManager, 'switchCamera').name('Switch camera').onFinishChange(() => { - if (this._cameraManager.camera().type == 'PerspectiveCamera' ? true : false) - this._controls.enableRotate = true; - else - this._controls.enableRotate = false; - this._controls.object = this._cameraManager.camera(); this._controls.reset(); }); } + /** * Render routine (called by the main loop) */