diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 00000000..797acea5 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/package.json b/package.json index c5958ad5..a1904162 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gtools", - "version": "0.2.60", + "version": "0.2.61", "description": "Small library including utils methods for easier life", "main": "./index.js", "module": "./_esm5/index.js", diff --git a/src/dom/canvas-drawer-advanced.ts b/src/dom/canvas-drawer-advanced.ts index 48c6d5ff..7ca1f532 100644 --- a/src/dom/canvas-drawer-advanced.ts +++ b/src/dom/canvas-drawer-advanced.ts @@ -1,6 +1,8 @@ +import { Origin } from "../enums"; import { XYWH } from "../types"; import { CanvasDrawer } from "./canvas-drawer"; import { CanvasManager } from "./canvas-manager"; +import { getXYWHFrom } from "./canvas-misc-utilts"; import { CanvasShadowConfig } from "./types/canvas-shadow-config"; export interface ObjectOptions { @@ -32,7 +34,8 @@ export class CanvasDrawerAdvanced { public constructor(private readonly context: CanvasRenderingContext2D) { } - public renderRect(location: XYWH, options: RenderOptions): void { + public renderRect(rawLocation: XYWH, options: RenderOptions, origin = Origin.TL): void { + const location = getXYWHFrom(rawLocation, {x: rawLocation.w, y: rawLocation.h}, origin); this.prepareShadow(options.shadow); this.prepareOpacity(options.opacity); if (options.fill) { @@ -40,7 +43,7 @@ export class CanvasDrawerAdvanced { this.prepareOpacity(options.fill.opacity); if (options.fill.fillImage) { - this.drawer.drawImage(options.fill.fillImage); + this.drawer.drawImage(options.fill.fillImage, location.x, location.y, location.w, location.h); } if (options.fill.fillColor) { this.drawer.fillRect(location.x, location.y, location.w, location.h, options.fill.fillColor); @@ -58,8 +61,9 @@ export class CanvasDrawerAdvanced { this.context.lineCap = options.stroke.lineCap; } - if (!isNaN(options.stroke.width ?? NaN)) { - this.context.lineWidth = options.stroke.width as number; + const width = options.stroke.width ?? NaN; + if (!isNaN(width)) { + this.context.lineWidth = width; } if (options.stroke.strokeColor) { this.drawer.fillRect(location.x, location.y, location.w, location.h, options.stroke.strokeColor); @@ -68,15 +72,17 @@ export class CanvasDrawerAdvanced { } private prepareShadow(shadow?: CanvasShadowConfig): void { - if (shadow) { - CanvasManager.setShadow( - this.context, - shadow.x ?? 0, - shadow.y ?? 0, - shadow.color ?? "black", - shadow.blur ?? 5, - ); + if (!shadow) { + return; } + + CanvasManager.setShadow( + this.context, + shadow.x ?? 0, + shadow.y ?? 0, + shadow.color ?? "black", + shadow.blur ?? 5, + ); } private prepareDashed(dashes?: number[]): void { diff --git a/src/dom/canvas-drawer.ts b/src/dom/canvas-drawer.ts index 75614533..6d2f28c7 100644 --- a/src/dom/canvas-drawer.ts +++ b/src/dom/canvas-drawer.ts @@ -1,26 +1,38 @@ -import { SimpleVector2 } from "../math"; +import { ReadonlySimpleVector2, SimpleVector2 } from "../math"; +import { Color } from "../models"; import { RoundData, TextOptionsInterface } from "../types"; import { makeRoundedRect } from "./canvas-misc-utilts"; import { Drawer } from "./drawer"; const PI2 = Math.PI * 2; +const PI05 = Math.PI * 0.5; + +export type ColorType = string | Color; + +function extractColor(color: ColorType): string { + if (typeof color === "string") { + return color; + } + + return color.hex; +} export class CanvasDrawer implements Drawer { public constructor(private readonly context: CanvasRenderingContext2D) { } - public fillRoundedRect(x: number, y: number, w: number, h: number, round: RoundData, color?: string): void { + public fillRoundedRect(x: number, y: number, w: number, h: number, round: RoundData, color?: ColorType): void { if (color) { - this.context.fillStyle = color; + this.context.fillStyle = extractColor(color); } makeRoundedRect(this.context, x, y, w, h, round); this.context.fill(); } - public strokeRoundedRect(x: number, y: number, w: number, h: number, round: RoundData, color?: string, width = NaN): void { + public strokeRoundedRect(x: number, y: number, w: number, h: number, round: RoundData, color?: ColorType, width = NaN): void { if (color) { - this.context.strokeStyle = color; + this.context.strokeStyle = extractColor(color); } if (!isNaN(width)) { @@ -35,17 +47,40 @@ export class CanvasDrawer implements Drawer { this.context.stroke(); } - public fillRect(x: number, y: number, w: number, h: number, color?: string): void { + public fillRectangles(data: [x: number, y: number, w: number, h: number][], color?: Color | string): void { + if (color) { + this.context.fillStyle = extractColor(color); + } + + data.forEach((item) => { + this.context.fillRect(...item); + }); + } + + public fillRect(x: number, y: number, w: number, h: number, color?: ColorType): void { if (color) { - this.context.fillStyle = color; + this.context.fillStyle = extractColor(color); } this.context.strokeRect(x, y, w, h); } - public strokeRect(x: number, y: number, w: number, h: number, color?: string, width = NaN): void { + public strokeRectangles(data: [x: number, y: number, w: number, h: number][], color?: Color | string, width?: number): void { if (color) { - this.context.strokeStyle = color; + this.context.strokeStyle = extractColor(color); + } + if (typeof width !== "undefined") { + this.context.lineWidth = width; + } + + data.forEach((item) => { + this.context.strokeRect(...item); + }); + } + + public strokeRect(x: number, y: number, w: number, h: number, color?: ColorType, width = NaN): void { + if (color) { + this.context.strokeStyle = extractColor(color); } if (!isNaN(width)) { @@ -59,26 +94,42 @@ export class CanvasDrawer implements Drawer { this.context.strokeRect(x, y, w, h); } - public fillArc(x: number, y: number, w: number, h: number, color?: string): void { + private createEllipse(x: number, y: number, w: number, h: number): void { + if(w === h) { + const halfSize = w / 2; + this.context.arc( + x + halfSize, + y + halfSize, + w, + 0, + PI2, + ); + } else { + const halfSize = {x: w / 2, y: h / 2}; + this.context.ellipse( + x + halfSize.x, + y + halfSize.y, + halfSize.x, + halfSize.y, + 0, + 0, + PI2, + ); + } + } + + public fillArc(x: number, y: number, w: number, h: number, color?: ColorType): void { if (color) { - this.context.fillStyle = color; - } - - const halfSize = {x: w / 2, y: h / 2}; - this.context.ellipse( - x + halfSize.x, - y + halfSize.y, - halfSize.x, - halfSize.y, - 0, - 0, - PI2); + this.context.fillStyle = extractColor(color); + } + + this.createEllipse(x, y, w, h); this.context.stroke(); } - public strokeArc(x: number, y: number, w: number, h: number, color?: string, width = NaN): void { + public strokeArc(x: number, y: number, w: number, h: number, color?: ColorType, width = NaN): void { if (color) { - this.context.strokeStyle = color; + this.context.strokeStyle = extractColor(color); } if (!isNaN(width)) { @@ -89,25 +140,17 @@ export class CanvasDrawer implements Drawer { } } - const halfSize = {x: w / 2, y: h / 2}; - this.context.ellipse( - x + halfSize.x, - y + halfSize.y, - halfSize.x, - halfSize.y, - 0, - 0, - PI2); + this.createEllipse(x, y, w, h); this.context.stroke(); } - public fillPath(points: SimpleVector2[], color?: string, close = false): void { + public fillPath(points: ReadonlySimpleVector2[], color?: ColorType, close = false): void { if (!Array.isArray(points) || points.length < 2) { return; } if (color) { - this.context.strokeStyle = color; + this.context.strokeStyle = extractColor(color); } this.context.moveTo(points[0].x, points[0].y); @@ -122,13 +165,13 @@ export class CanvasDrawer implements Drawer { this.context.fill(); } - public drawPath(points: SimpleVector2[], color?: string, width = NaN, close = false): void { + public drawPath(points: ReadonlySimpleVector2[], color?: ColorType, width = NaN, close = false): void { if (!Array.isArray(points) || points.length < 2) { return; } if (color) { - this.context.strokeStyle = color; + this.context.strokeStyle = extractColor(color); } if (!isNaN(width)) { @@ -160,9 +203,9 @@ export class CanvasDrawer implements Drawer { this.context.drawImage(image, x, y, w, h); } - public drawLine(x1: number, y1: number, x2: number, y2: number, color?: string, width = NaN): void { + public drawLine(x1: number, y1: number, x2: number, y2: number, color?: ColorType, width = NaN): void { if (color) { - this.context.strokeStyle = color; + this.context.strokeStyle = extractColor(color); } if (!isNaN(width)) { @@ -205,7 +248,7 @@ export class CanvasDrawer implements Drawer { realX += w; } - this.context.fillText(text, realX, realY, w); + this.fillRotatedText(text, realX, realY, textOptions.rotation ?? 0, w); } public clear(resetTransform = true): void { @@ -231,4 +274,67 @@ export class CanvasDrawer implements Drawer { this.context.stroke(); } } + + public fillText(text: string, x: number, y: number, maxWidth?: number): void { + this.fillRotatedText(text, x, y, 0, maxWidth); + } + + public fillVerticalText(text: string, x: number, y: number, maxWidth?: number): void { + this.fillRotatedText(text, x, y, -PI05, maxWidth); + } + + private fillRotatedText(text: string, x: number, y: number, angle: number, maxWidth?: number): void { + if (!angle) { + return this.context.fillText(text, 0, 0, maxWidth); + } + + this.context.save(); + this.context.translate(x, y); + this.context.rotate(angle); + this.context.fillText(text, 0, 0, maxWidth); + this.context.restore(); + } + + public horizontalLine(y: number, startX = 0, endX = this.context.canvas.width): void { + this.context.moveTo(startX, y); + this.context.lineTo(endX, y); + } + + public drawFullCanvasGrid(startX: number, startY: number, offset: number, rows: number, columns: number): void { + this.context.beginPath(); + this.fullCanvasGrid(startX, startY, offset, rows, columns); + this.context.stroke(); + } + + public fullCanvasGrid(startX: number, startY: number, offset: number, rows: number, columns: number): void { + this.verticalLines( + startX, + offset, + columns, + ); + this.horizontalLines( + startY, + offset, + rows, + ); + } + + public horizontalLines(startY: number, offsetY: number, steps: number, startX = 0, endX = this.context.canvas.width): void { + for (let i = 0; i < steps; i++) { + const y = startY + offsetY * i; + this.horizontalLine(y, startX, endX); + } + } + + public verticalLines(startX: number, offsetX: number, steps: number, startY = 0, endY = this.context.canvas.height): void { + for (let i = 0; i < steps; i++) { + const x = startX + offsetX * i; + this.verticalLine(x, startY, endY); + } + } + + public verticalLine(x: number, startY = 0, endY = this.context.canvas.height): void { + this.context.moveTo(x, startY); + this.context.lineTo(x, endY); + } } diff --git a/src/dom/canvas-misc-utilts.ts b/src/dom/canvas-misc-utilts.ts index 3535ef79..6d37e98f 100644 --- a/src/dom/canvas-misc-utilts.ts +++ b/src/dom/canvas-misc-utilts.ts @@ -1,4 +1,32 @@ -import { RoundData } from "../types"; +import { Origin } from "../enums"; +import { ReadonlySimpleVector2 } from "../math"; +import { RoundData, XYWH } from "../types"; + +function extractRoundData(radius: RoundData): { tr: number; tl: number; br: number; bl: number } { + if (typeof radius === "number") { + return { + bl: radius, + tl: radius, + br: radius, + tr: radius, + }; + } + if (Array.isArray(radius)) { + return { + tr: radius[0], + br: radius[1], + bl: radius[2], + tl: radius[3], + }; + } + + return { + tr: radius.tr ?? 0, + br: radius.br ?? 0, + bl: radius.bl ?? 0, + tl: radius.tl ?? 0, + }; +} export function makeRoundedRect( context: CanvasRenderingContext2D, @@ -6,11 +34,9 @@ export function makeRoundedRect( y: number, w: number, h: number, - radius: RoundData): void { - const tr = typeof radius === "number" ? radius : (typeof radius.tr === "number" ? radius.tr : 0); - const tl = typeof radius === "number" ? radius : (typeof radius.tl === "number" ? radius.tl : 0); - const br = typeof radius === "number" ? radius : (typeof radius.br === "number" ? radius.br : 0); - const bl = typeof radius === "number" ? radius : (typeof radius.bl === "number" ? radius.bl : 0); + radius: RoundData, +): void { + const {tr, br, bl, tl} = extractRoundData(radius); context.beginPath(); context.moveTo(x + tl, y); @@ -40,3 +66,31 @@ export function makeRoundedRect( y); context.closePath(); } + +export function getXYWHFrom(position: ReadonlySimpleVector2, size: ReadonlySimpleVector2, origin: Origin): XYWH { + switch (origin) { + case Origin.TL: + return { + x: position.x, + y: position.y, + w: size.x, + h: size.y, + }; + case Origin.BR: + return { + x: position.x - size.x, + y: position.y - size.y, + w: size.x, + h: size.y, + }; + case Origin.CENTER: + return { + x: position.x - size.x / 2, + y: position.y - size.y / 2, + w: size.x, + h: size.y, + }; + default: + throw new Error(`Not implemented conversion from origin ${origin}`); + } +} \ No newline at end of file diff --git a/src/enums/index.ts b/src/enums/index.ts index a37c6c65..d4d3150a 100644 --- a/src/enums/index.ts +++ b/src/enums/index.ts @@ -4,3 +4,4 @@ export * from "./encodings.enum"; export * from "./file-types.enum"; export * from "./http-status-codes.enum"; export * from "./keys.enum"; +export * from "./origin.enum"; diff --git a/src/enums/origin.enum.ts b/src/enums/origin.enum.ts new file mode 100644 index 00000000..f6e8181b --- /dev/null +++ b/src/enums/origin.enum.ts @@ -0,0 +1,11 @@ +export enum Origin { + TR = "TR", + CENTER = "CENTER", + TL = "TL", + BR = "BR", + BL = "BL", + T = "T", + L = "L", + R = "R", + B = "B" +} diff --git a/src/math/gl-mat4.ts b/src/math/gl-mat4.ts new file mode 100644 index 00000000..f5ed8ce1 --- /dev/null +++ b/src/math/gl-mat4.ts @@ -0,0 +1,90 @@ +import { Mat4 } from "./mat4"; +import { mat4type, SimpleMat4 } from "./simple-mat4"; + +export type vec3type = [number, number, number]; +export const EPSILON = 0.000001; + +export class GlMat4 { + public constructor( + private readonly data: mat4type, + ) { + } + + private equalsData(data: number[]): boolean{ + for (let i = 0; i < 16; i++) { + if (data[i] !== this.data[i]) { + return false; + } + } + + return true; + } + + public equals(data?: null | GlMat4 | SimpleMat4 | number[]): boolean { + if(!data) { + return false; + } + if (Array.isArray(data)) { + return this.equalsData(data); + } + + if(data instanceof GlMat4) { + return this.equalsData(data.data); + } + + return this.equalsData(data.data); + } + + public static fromRotation(out: mat4type, rad: number, axis: vec3type): GlMat4 | null { + let x = axis[0]; + let y = axis[1]; + let z = axis[2]; + let len = Math.sqrt(x * x + y * y + z * z); + const s = Math.sin(rad); + const c = Math.cos(rad); + const t = 1 - c; + + if (Math.abs(len) < EPSILON) { + return null; + } + + len = 1 / len; + x *= len; + y *= len; + z *= len; + + // Perform rotation-specific matrix multiplication + out[0] = x * x * t + c; + out[1] = y * x * t + z * s; + out[2] = z * x * t - y * s; + out[3] = 0; + out[4] = x * y * t - z * s; + out[5] = y * y * t + c; + out[6] = z * y * t + x * s; + out[7] = 0; + out[8] = x * z * t + y * s; + out[9] = y * z * t - x * s; + out[10] = z * z * t + c; + out[11] = 0; + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; + + return new GlMat4(out); + } + + public transformVec3(out: vec3type, a: vec3type, m: mat4type = this.data): vec3type { + const x = a[0]; + const y = a[1]; + const z = a[2]; + let w = m[3] * x + m[7] * y + m[11] * z + m[15]; + w = w || 1; + out[0] = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w; + out[1] = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w; + out[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w; + + return out; + } + +} diff --git a/src/math/mat4.spec.ts b/src/math/mat4.spec.ts index caa48781..53a18d69 100644 --- a/src/math/mat4.spec.ts +++ b/src/math/mat4.spec.ts @@ -1,6 +1,7 @@ import { expect } from "chai"; import "mocha"; import { Mat4 } from "./mat4"; +import { Vector3 } from "./vector3"; describe("Mat4", () => { describe("init", () => { @@ -31,5 +32,60 @@ describe("Mat4", () => { // expect(Mat4.fromYRotation(0)).to.deep.equal(Mat4.create()); // expect(Mat4.fromZRotation(0)).to.deep.equal(Mat4.create()); }); + it("Test matrix translation", () => { + const matrix = Mat4.fromTranslation({x: 2, y: 3, z: 4}); + const matrixB = Mat4.create().translate(2, 3, 4); + + expect(matrix).to.deep.equal(matrixB); + + const positionA = {x: 0, y: 0, z: 0}; + const positionB = {x: 2, y: 3, z: 4}; + const positionC = {x: 10, y: 10, z: 10}; + const positionATransformed = matrix.getTransformedVector(positionA); + const positionBTransformed = matrix.getTransformedVector(positionB); + const positionCTransformed = matrix.getTransformedVector(positionC); + + expect(positionATransformed).to.deep.equal({x: 2, y: 3, z: 4}); + expect(positionBTransformed).to.deep.equal({x: 4, y: 6, z: 8}); + expect(positionCTransformed).to.deep.equal({x: 12, y: 13, z: 14}); + }); + + it("Test matrix rotation", () => { + const matrix = Mat4.fromRotation(Math.PI, {x: 0, y: 1, z: 0}); + if(!matrix) { + throw new Error("Matrix is null"); + } + const matrixB = Mat4.create().rotate(Math.PI, {x: 0, y: 1, z: 0}); + expect(matrix).to.deep.equal(matrixB); + + const positionA = {x: 2, y: 0, z: 0}; + const positionB = {x: 0, y: 2, z: 0}; + const positionC = {x: 0, y: 0, z: 2}; + const positionATransformed = matrix.getTransformedVector(positionA); + const positionBTransformed = matrix.getTransformedVector(positionB); + const positionCTransformed = matrix.getTransformedVector(positionC); + + expect(Vector3.equalsApproximately(positionATransformed, {x: -2, y: 0, z: 0})).to.be.true; + expect(Vector3.equalsApproximately(positionBTransformed, {x: 0, y: 2, z: 0})).to.be.true; + expect(Vector3.equalsApproximately(positionCTransformed, {x: 0, y: 0, z: -2})).to.be.true; + }); + + it("Test matrix scale", () => { + const matrix = Mat4.fromScale({x: 1, y: 2, z: 3}); + const matrixB = Mat4.create().scale(1, 2, 3); + + expect(matrix).to.deep.equal(matrixB); + + const positionA = {x: 2, y: 0, z: 0}; + const positionB = {x: 0, y: 2, z: 0}; + const positionC = {x: 0, y: 0, z: 2}; + const positionATransformed = matrix.getTransformedVector(positionA); + const positionBTransformed = matrix.getTransformedVector(positionB); + const positionCTransformed = matrix.getTransformedVector(positionC); + + expect(Vector3.equalsApproximately(positionATransformed, {x: 2, y: 0, z: 0})).to.be.true; + expect(Vector3.equalsApproximately(positionBTransformed, {x: 0, y: 4, z: 0})).to.be.true; + expect(Vector3.equalsApproximately(positionCTransformed, {x: 0, y: 0, z: 6})).to.be.true; + }); }); }); diff --git a/src/math/mat4.ts b/src/math/mat4.ts index bdd9f97a..0a65dcd0 100644 --- a/src/math/mat4.ts +++ b/src/math/mat4.ts @@ -1,6 +1,14 @@ import { SimpleMat4 } from "./simple-mat4"; export class Mat4 extends SimpleMat4 { + // public static create(): Mat4 { + // return new Mat4([ + // 1, 0, 0, 0, + // 0, 1, 0, 0, + // 0, 0, 1, 0, + // 0, 0, 0, 1, + // ]); + // } public static createViewMatrix(): void { // TODO: implement } diff --git a/src/math/quaternion.ts b/src/math/quaternion.ts index 184c4e9b..63c0ed9e 100644 --- a/src/math/quaternion.ts +++ b/src/math/quaternion.ts @@ -1,12 +1,116 @@ -import { SimpleVector4 } from "./simple-vector4"; +import { Mat4 } from "./mat4"; +import { SimpleVector3 } from "./simple-vector3"; +import { Vector4 } from "./vector4"; -export class Quaternion implements SimpleVector4 { - public constructor( - public w: number, - public x: number, - public y: number, - public z: number, - ) { +export class Quaternion extends Vector4 { + public static fromRotationMatrix(rot: Mat4): Quaternion { + const trace = rot.get(0, 0) + rot.get(1, 1) + rot.get(2, 2); + + let x: number; + let y: number; + let z: number; + let w: number; + if (trace > 0) { + const s = 0.5 / Math.sqrt(trace + 1); + w = 0.25 / s; + x = (rot.get(1, 2) - rot.get(2, 1)) * s; + y = (rot.get(2, 0) - rot.get(0, 2)) * s; + z = (rot.get(0, 1) - rot.get(1, 0)) * s; + } else if (rot.get(0, 0) > rot.get(1, 1) && rot.get(0, 0) > rot.get(2, 2)) { + const s = 2 * Math.sqrt(1 + rot.get(0, 0) - rot.get(1, 1) - rot.get(2, 2)); + w = (rot.get(1, 2) - rot.get(2, 1)) / s; + x = 0.25 * s; + y = (rot.get(1, 0) + rot.get(0, 1)) / s; + z = (rot.get(2, 0) + rot.get(0, 2)) / s; + } else if (rot.get(1, 1) > rot.get(2, 2)) { + const s = 2 * Math.sqrt(1 + rot.get(1, 1) - rot.get(0, 0) - rot.get(2, 2)); + w = (rot.get(2, 0) - rot.get(0, 2)) / s; + x = (rot.get(1, 0) + rot.get(0, 1)) / s; + y = 0.25 * s; + z = (rot.get(2, 1) + rot.get(1, 2)) / s; + } else { + const s = 2 * Math.sqrt(1 + rot.get(2, 2) - rot.get(0, 0) - rot.get(1, 1)); + w = (rot.get(0, 1) - rot.get(1, 0)) / s; + x = (rot.get(2, 0) + rot.get(0, 2)) / s; + y = (rot.get(1, 2) + rot.get(2, 1)) / s; + z = 0.25 * s; + } + + const length = Math.sqrt(x * x + y * y + z * z + w * w); + + return new Quaternion( + x / length, + y / length, + z / length, + w / length, + ); + } + + public static fromEuler(x: number, y: number, z: number, result = new Quaternion()): Quaternion { + const halfToRad = 0.5 * Math.PI / 180; + x *= halfToRad; + y *= halfToRad; + z *= halfToRad; + const sx = Math.sin(x); + const cx = Math.cos(x); + const sy = Math.sin(y); + const cy = Math.cos(y); + const sz = Math.sin(z); + const cz = Math.cos(z); + result.x = sx * cy * cz - cx * sy * sz; + result.y = cx * sy * cz + sx * cy * sz; + result.z = cx * cy * sz - sx * sy * cz; + result.w = cx * cy * cz + sx * sy * sz; + + return result; + } + + public clone(): Quaternion { + return new Quaternion(this.x, this.y, this.z, this.w); + } + + + public static multiply(a: Quaternion, b: Quaternion, result = new Quaternion()): Quaternion { + const ax = a.x; + const ay = a.y; + const az = a.z; + const aw = a.w; + const bx = b.x; + const by = b.y; + const bz = b.z; + const bw = b.w; + + result.x = ax * bw + aw * bx + ay * bz - az * by; + result.y = ay * bw + aw * by + az * bx - ax * bz; + result.z = az * bw + aw * bz + ax * by - ay * bx; + result.w = aw * bw - ax * bx - ay * by - az * bz; + + return result; } + public toEuler(): SimpleVector3 { + const ysqr = this.y * this.y; + + const t0 = 2 * (this.w * this.x + this.y * this.z); + const t1 = 1 - 2 * (this.x * this.x + ysqr); + const resX = Math.atan2(t0, t1); + + let t2 = 2 * (this.w * this.y - this.z * this.x); + if (t2 > 1) { + t2 = 1; + } else if (t2 < -1) { + t2 = -1; + } + const resY = Math.asin(t2); + + const t3 = 2 * (this.w * this.z + this.x * this.y); + const t4 = 1 - 2 * (ysqr + this.z * this.z); + const resZ = Math.atan2(t3, t4); + + return { + x: resX, + y: resY, + z: resZ, + }; + } } diff --git a/src/math/simple-mat4.ts b/src/math/simple-mat4.ts index b0f945b6..7757f9f4 100644 --- a/src/math/simple-mat4.ts +++ b/src/math/simple-mat4.ts @@ -3,6 +3,8 @@ import { Quaternion } from "./quaternion"; import { ReadonlySimpleVector3, SimpleVector3 } from "./simple-vector3"; import { Vector3 } from "./vector3"; +export type mat4type = number[]; + /** * https://github.com/mrdoob/three.js/blob/dev/src/math/Matrix4.js * https://github.com/BennyQBD/3DEngineCpp/blob/master/src/core/math3d.h @@ -15,10 +17,16 @@ import { Vector3 } from "./vector3"; * 03 07 11 14 */ export class SimpleMat4 { - public constructor(public readonly data: number[]) { + public constructor(public readonly data: mat4type) { + } + + public set(matrix: SimpleMat4): void { + for(let i = 0; i < 16; i++) { + this.data[i] = matrix.data[i]; + } } - public set(x: number, y: number, value: number): void { + public setItem(x: number, y: number, value: number): void { this.data[x * 4 + y] = value; } @@ -110,6 +118,30 @@ export class SimpleMat4 { return result; } + public scale(x: number, y: number, z: number): this { + this.data[0] = this.data[0] * x; + this.data[1] = this.data[1] * x; + this.data[2] = this.data[2] * x; + this.data[3] = this.data[3] * x; + this.data[4] = this.data[4] * y; + this.data[5] = this.data[5] * y; + this.data[6] = this.data[6] * y; + this.data[7] = this.data[7] * y; + this.data[8] = this.data[8] * z; + this.data[9] = this.data[9] * z; + this.data[10] = this.data[10] * z; + this.data[11] = this.data[11] * z; + + return this; + } + + /** + * same as + * ```typescript + * Mat4.setIdentity(result); + * Mat4.translate(scale.x, scale.y, scale.z, result.data); + * ``` + */ public static fromTranslation(translation: SimpleVector3, result = SimpleMat4.create()): SimpleMat4 { Mat4.setIdentity(result); Mat4.setTranslation(translation.x, translation.y, translation.z, result.data); @@ -117,9 +149,102 @@ export class SimpleMat4 { return result; } + /** + * same as + * ```typescript + * Mat4.setIdentity(result); + * Mat4.setScale(scale.x, scale.y, scale.z, result.data); + * // or + * Mat4.setIdentity(result); + * result.scale(scale.x, scale.y, scale.z, result.data); + * ``` + */ public static fromScale(scale: SimpleVector3, result = SimpleMat4.create()): SimpleMat4 { - Mat4.setIdentity(result); - Mat4.setScale(scale.x, scale.y, scale.z, result.data); + const out = result.data; + out[0] = scale.x; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = scale.y; + out[6] = 0; + out[7] = 0; + out[8] = 0; + out[9] = 0; + out[10] = scale.z; + out[11] = 0; + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; + + return result; + } + + public translate(x: number, y: number, z: number): this { + this.data[12] = this.data[0] * x + this.data[4] * y + this.data[8] * z + this.data[12]; + this.data[13] = this.data[1] * x + this.data[5] * y + this.data[9] * z + this.data[13]; + this.data[14] = this.data[2] * x + this.data[6] * y + this.data[10] * z + this.data[14]; + this.data[15] = this.data[3] * x + this.data[7] * y + this.data[11] * z + this.data[15]; + + return this; + } + + /** + * Same as + * ```typescript + * mat4.identity(dest); + * mat4.translate(dest, vec); + * const quatMat = mat4.create(); + * quat4.toMat4(quat, quatMat); + * mat4.multiply(dest, quatMat); + * mat4.scale(dest, scale) + * ``` + */ + public static fromRotationTranslationScale( + rotation: Quaternion, + translation: ReadonlySimpleVector3, + scale: ReadonlySimpleVector3, + result = SimpleMat4.create(), + ): SimpleMat4 { + const out = result.data; + // Quaternion math + const x = rotation.x; + const y = rotation.y; + const z = rotation.z; + const w = rotation.w; + const x2 = x + x; + const y2 = y + y; + const z2 = z + z; + const xx = x * x2; + const xy = x * y2; + const xz = x * z2; + const yy = y * y2; + const yz = y * z2; + const zz = z * z2; + const wx = w * x2; + const wy = w * y2; + const wz = w * z2; + const sx = scale.x; + const sy = scale.y; + const sz = scale.z; + + out[0] = (1 - (yy + zz)) * sx; + out[1] = (xy + wz) * sx; + out[2] = (xz - wy) * sx; + out[3] = 0; + out[4] = (xy - wz) * sy; + out[5] = (1 - (xx + zz)) * sy; + out[6] = (yz + wx) * sy; + out[7] = 0; + out[8] = (xz + wy) * sz; + out[9] = (yz - wx) * sz; + out[10] = (1 - (xx + yy)) * sz; + out[11] = 0; + out[12] = translation.x; + out[13] = translation.y; + out[14] = translation.z; + out[15] = 1; return result; } @@ -192,6 +317,56 @@ export class SimpleMat4 { return result; } + public rotate(angle: number, axis: ReadonlySimpleVector3, res = SimpleMat4.create()): this { + const {x, y, z} = axis; + + const c = Math.cos(angle); + const s = Math.sin(angle); + const oneminusc = 1 - c; + const xy = x * y; + const yz = y * z; + const xz = x * z; + const xs = x * s; + const ys = y * s; + const zs = z * s; + + const f00 = x * x * oneminusc + c; + const f01 = xy * oneminusc + zs; + const f02 = xz * oneminusc - ys; + + const f10 = xy * oneminusc - zs; + const f11 = y * y * oneminusc + c; + const f12 = yz * oneminusc + xs; + + const f20 = xz * oneminusc + ys; + const f21 = yz * oneminusc - xs; + const f22 = z * z * oneminusc + c; + + res.setItem(0, 0, this.get(0, 0) * f00 + this.get(1, 0) * f01 + this.get(2, 0) * f02); + res.setItem(0, 1, this.get(0, 1) * f00 + this.get(1, 1) * f01 + this.get(2, 1) * f02); + res.setItem(0, 2, this.get(0, 2) * f00 + this.get(1, 2) * f01 + this.get(2, 2) * f02); + res.setItem(0, 3, this.get(0, 3) * f00 + this.get(1, 3) * f01 + this.get(2, 3) * f02); + res.setItem(1, 0, this.get(0, 0) * f10 + this.get(1, 0) * f11 + this.get(2, 0) * f12); + res.setItem(1, 1, this.get(0, 1) * f10 + this.get(1, 1) * f11 + this.get(2, 1) * f12); + res.setItem(1, 2, this.get(0, 2) * f10 + this.get(1, 2) * f11 + this.get(2, 2) * f12); + res.setItem(1, 3, this.get(0, 3) * f10 + this.get(1, 3) * f11 + this.get(2, 3) * f12); + res.setItem(2, 0, this.get(0, 0) * f20 + this.get(1, 0) * f21 + this.get(2, 0) * f22); + res.setItem(2, 1, this.get(0, 1) * f20 + this.get(1, 1) * f21 + this.get(2, 1) * f22); + res.setItem(2, 2, this.get(0, 2) * f20 + this.get(1, 2) * f21 + this.get(2, 2) * f22); + res.setItem(2, 3, this.get(0, 3) * f20 + this.get(1, 3) * f21 + this.get(2, 3) * f22); + + res.data.forEach((item, index) => this.data[index] = item); + + return this; + } + + /** + * Same as + * ```typescript + * mat4.identity(dest); + * mat4.rotate(dest, dest, rad, axis); + * ``` + */ public static fromRotation(rad: number, axis: SimpleVector3, result = SimpleMat4.create()): SimpleMat4 | null { let len = Math.hypot(axis.x, axis.y, axis.z); if (len < Number.EPSILON) { @@ -296,4 +471,67 @@ export class SimpleMat4 { z: (m[2] * x + m[6] * y + m[10] * z + m[14]) / w, }; } + + public static multiply(matA: Mat4, matB: Mat4, result = Mat4.create()): Mat4 { + const a = matA.data; + const b = matB.data; + const out = result.data; + + const a00 = a[0]; + const a01 = a[1]; + const a02 = a[2]; + const a03 = a[3]; + const a10 = a[4]; + const a11 = a[5]; + const a12 = a[6]; + const a13 = a[7]; + const a20 = a[8]; + const a21 = a[9]; + const a22 = a[10]; + const a23 = a[11]; + const a30 = a[12]; + const a31 = a[13]; + const a32 = a[14]; + const a33 = a[15]; + + // Cache only the current line of the second matrix + let b0 = b[0]; + let b1 = b[1]; + let b2 = b[2]; + let b3 = b[3]; + out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; + out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; + out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; + out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; + + b0 = b[4]; + b1 = b[5]; + b2 = b[6]; + b3 = b[7]; + out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; + out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; + out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; + out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; + + b0 = b[8]; + b1 = b[9]; + b2 = b[10]; + b3 = b[11]; + out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; + out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; + out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; + out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; + + b0 = b[12]; + b1 = b[13]; + b2 = b[14]; + b3 = b[15]; + out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; + out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; + out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; + out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; + + return result; + } + } diff --git a/src/math/vector3.ts b/src/math/vector3.ts index b9a933b8..655dd8c1 100644 --- a/src/math/vector3.ts +++ b/src/math/vector3.ts @@ -40,7 +40,15 @@ export class Vector3 implements SimpleVector3 { return vecA.x === vecB.x && vecA.y === vecB.y && vecA.z === vecB.z; } + public static equalsApproximately (vecA: ReadonlySimpleVector3, vecB: ReadonlySimpleVector3, EPSILON = 0.0000001): boolean { + if (vecA === vecB) { + return true; + } + const equal = (a:number, b: number): boolean => Math.abs(a - b) <= EPSILON * Math.max(1, Math.abs(a), Math.abs(b)); + + return equal(vecA.x, vecB.x) && equal(vecA.y, vecB.y) && equal(vecA.z, vecB.z); + } public static sub(vecA: ReadonlySimpleVector3, vecB: ReadonlySimpleVector3): Vector3 { return new Vector3(vecA.x - vecB.x, vecA.y - vecB.y, vecA.z - vecB.z); } diff --git a/src/math/vector4.ts b/src/math/vector4.ts index 67786ed7..8d1646be 100644 --- a/src/math/vector4.ts +++ b/src/math/vector4.ts @@ -41,6 +41,16 @@ export class Vector4 implements SimpleVector4 { return vecA.x === vecB.x && vecA.y === vecB.y && vecA.z === vecB.z && vecA.w === vecB.w; } + public static equalsApproximately (vecA: ReadonlySimpleVector4, vecB: ReadonlySimpleVector4, EPSILON = 0.0000001): boolean { + if (vecA === vecB) { + return true; + } + + const equal = (a:number, b: number): boolean => Math.abs(a - b) <= EPSILON * Math.max(1, Math.abs(a), Math.abs(b)); + + return equal(vecA.x, vecB.x) && equal(vecA.y, vecB.y) && equal(vecA.z, vecB.z) && equal(vecA.w, vecB.w); + } + public static min(vecA: ReadonlySimpleVector4, vecB: ReadonlySimpleVector4): Vector4 { return new Vector4( Math.min(vecA.x, vecB.x), diff --git a/src/models/data-structures/grid-accessor/holders-3d/abstract-grid3-holder.ts b/src/models/data-structures/grid-accessor/holders-3d/abstract-grid3-holder.ts new file mode 100644 index 00000000..4806611f --- /dev/null +++ b/src/models/data-structures/grid-accessor/holders-3d/abstract-grid3-holder.ts @@ -0,0 +1,36 @@ +import { Grid3Holder } from "./grid3-holder"; + +export abstract class AbstractGrid3Holder implements Grid3Holder{ + public getProperty(x: number, y: number, z: number, key: S): T[S] | undefined { + const item = this.get(x, y, z); + + if(item) { + return item[key]; + } + } + + public readonly abstract length: number; + + public abstract clear(): void; + + public abstract fill>(value: ((x: number, y: number, z: number) => R) | R): void; + + public abstract forEach(callback: (block: T, x: number, y: number, z: number) => unknown): boolean; + + public abstract get(x: number, y: number, z: number): T; + + public abstract mirror(orientation: "XY" | "XZ" | "YZ"): void; + + public abstract rotateCCW(): void; + + public abstract rotateCW(): void; + + public abstract set(x: number, y: number, z: number, value: T): void; + + public abstract setHolder(holder: Grid3Holder): void; + + public abstract swap(ax: number, ay: number, az: number, bx: number, by: number, bz: number): void; + + public abstract transform(x: number, y: number, z: number, transformer: (value: T) => T): void; + +} diff --git a/src/models/data-structures/grid-accessor/holders-3d/grid3-array-holder.ts b/src/models/data-structures/grid-accessor/holders-3d/grid3-array-holder.ts index 42720573..23002f5b 100644 --- a/src/models/data-structures/grid-accessor/holders-3d/grid3-array-holder.ts +++ b/src/models/data-structures/grid-accessor/holders-3d/grid3-array-holder.ts @@ -81,7 +81,8 @@ export class Grid3ArrayHolder implements Grid3Holder { } public set(x: number, y: number, z: number, value: T): void { - this.data[this.getIndex(x, y, z)] = value; + const index = this.getIndex(x, y, z); + this.data[index] = value; } public swap(ax: number, ay: number, az: number, bx: number, by: number, bz: number): void { diff --git a/src/models/data-structures/grid-accessor/holders-3d/grid3-holder.ts b/src/models/data-structures/grid-accessor/holders-3d/grid3-holder.ts index df9ac277..a2a79d5d 100644 --- a/src/models/data-structures/grid-accessor/holders-3d/grid3-holder.ts +++ b/src/models/data-structures/grid-accessor/holders-3d/grid3-holder.ts @@ -35,8 +35,16 @@ export interface Grid3Holder { * * @param callback - function to be executed on each element */ + forEachUntil?(callback: (block: T, x: number, y: number, z: number) => unknown): boolean; + + /** + * @deprecated use either {@link forEachUntil} or {@link forEach} + * @param callback - function to be executed on each element + */ forEach(callback: (block: T, x: number, y: number, z: number) => unknown): boolean; + // forEach(callback: (block: T, x: number, y: number, z: number) => void): void; + setHolder(holder: Grid3Holder): void; clear(): void; diff --git a/src/models/data-structures/grid-accessor/holders-3d/grid3.perf.ts b/src/models/data-structures/grid-accessor/holders-3d/grid3.perf.ts index ed0dd0ed..5e618d64 100644 --- a/src/models/data-structures/grid-accessor/holders-3d/grid3.perf.ts +++ b/src/models/data-structures/grid-accessor/holders-3d/grid3.perf.ts @@ -18,11 +18,10 @@ describe("Grid3", () => { const mapHolder = Grid3MapHolder.initEmpty(size.x, size.y, size.z, 0); const objectHolder = new Grid3ObjectHolder(); const hashHolder = new Grid3HashHolder(); - const sorts: Grid3Holder[] = [ arrayHolder, objectHolder, - // hashHolder, + hashHolder, mapHolder, ]; @@ -77,6 +76,18 @@ describe("Grid3", () => { }); }); + it("It should test filling", () => { + sorts.forEach((holder) => { + const start = now(); + + holder.fill(123 as any); + holder.fill(() => (123 as any)); + + const diff = now() - start; + console.log(holder.constructor.name, ": ", diff, "ms"); + + }); + }); it("It should test iterating", () => { sorts.forEach((holder) => { const start = now(); diff --git a/src/models/hierarchical-transform.spec.ts b/src/models/hierarchical-transform.spec.ts new file mode 100644 index 00000000..7ae0b5ed --- /dev/null +++ b/src/models/hierarchical-transform.spec.ts @@ -0,0 +1,30 @@ +import { expect } from "chai"; +import "mocha"; +import { HierarchicalTransform } from "./hierarchical-transform"; + +describe("HierarchicalTransform", () => { + it("It should test rotation", () => { + const parentTransform = new HierarchicalTransform(); + + const childTransform = new HierarchicalTransform(); + childTransform.setPosition(5, 0, 0); + childTransform.setParent(parentTransform); + + expect(parentTransform.position).to.include({x: 0, y: 0, z: 0}); + expect(childTransform.position).to.include({x: 5, y: 0, z: 0}); + expect(childTransform.transformedPos).to.include({x: 5, y: 0, z: 0}); + + parentTransform.move({x: 1, y: 2, z: 3}); + + expect(parentTransform.position).to.include({x: 1, y: 2, z: 3}); + expect(childTransform.position).to.include({x: 5, y: 0, z: 0}); + expect(childTransform.transformedPos).to.include({x: 6, y: 2, z: 3}); + + parentTransform.setScale(2, 1, 3); + + expect(parentTransform.scale).to.include({x: 2, y: 1, z: 3}); + expect(childTransform.scale).to.include({x: 1, y: 1, z: 1}); + expect(childTransform.transformedPos).to.include({x: 11, y: 2, z: 3}); + }); + +}); diff --git a/src/models/hierarchical-transform.ts b/src/models/hierarchical-transform.ts index 895c70ae..3e5e84b1 100644 --- a/src/models/hierarchical-transform.ts +++ b/src/models/hierarchical-transform.ts @@ -1,75 +1,65 @@ -// import { mat4, quat, vec3 } from "gl-matrix"; -// import { SimpleVector3, Vector3 } from "gtools/math"; -// import { Transform } from "./transform"; -// -// /** -// * @see https://github.com/G43riko/JavaUtils/blob/master/GLib2/src/main/java/org/glib2/math/transforms/TransformHierarchy.java -// * @see https://github.com/G43riko/JavaUtils/blob/master/GLib2/src/test/java/org/glib2/math/TransformHierarchyTest.java -// */ -// export class HierarchicalTransform extends Transform { -// private _parent?: HierarchicalTransform; -// private _parentTransformation = mat4.create(); -// private _oldPosition = Vector3.ZERO; -// private _oldRotation = quat.create(); -// private _oldScale = Vector3.ONE; -// -// private hasChange(): boolean { -// if (this._parent?.hasChange()) { -// return true; -// } -// -// if (!Vector3.equals(this.position, this._oldPosition)) { -// return true; -// } -// -// if (!quat.equals(this.rotation, this._oldRotation)) { -// return true; -// } -// -// return !Vector3.equals(this.scale, this._oldScale); -// } -// -// public update(): void { -// this._oldPosition.set(this.position); -// quat.copy(this._oldRotation, this.rotation); -// this._oldScale.set(this.scale); -// } -// -// public setParent(parent: HierarchicalTransform | Transform): void { -// this._parent = parent as HierarchicalTransform; -// } -// -// public get transformation(): mat4 { -// return mat4.mul(mat4.create(), this.parentMatrix(), super.transformation); -// } -// -// private parentMatrix(): mat4 { -// if (this._parent?.hasChange()) { -// this._parentTransformation = mat4.copy(this._parentTransformation, this._parent?.transformation); -// } -// -// return this._parentTransformation; -// } -// -// public get transformedPos(): SimpleVector3 { -// const result = vec3.transformMat4([0, 0, 0], [ -// this.position.x, -// this.position.y, -// this.position.z -// ], this.parentMatrix()); -// -// return { -// x: result[0], -// y: result[1], -// z: result[2] -// }; -// } -// -// public get transformedRot(): quat { -// if (!this._parent) { -// return this.rotation; -// } -// -// return quat.mul([0, 0, 0, 0], this._parent.transformedRot, this.rotation); -// } -// } +import { Mat4, SimpleVector3, Vector3 } from "../math"; +import { Quaternion } from "../math/quaternion"; +import { Transform } from "./transform"; + +/** + * @see https://github.com/G43riko/JavaUtils/blob/master/GLib2/src/main/java/org/glib2/math/transforms/TransformHierarchy.java + * @see https://github.com/G43riko/JavaUtils/blob/master/GLib2/src/test/java/org/glib2/math/TransformHierarchyTest.java + */ +export class HierarchicalTransform extends Transform { + private _parent?: HierarchicalTransform; + private readonly _parentTransformation = Mat4.create(); + private readonly _oldPosition = new Vector3(); + private readonly _oldRotation = new Quaternion(); + private readonly _oldScale = Vector3.ONE; + + private hasChange(): boolean { + if (this._parent?.hasChange()) { + return true; + } + + if (!Vector3.equals(this.position, this._oldPosition)) { + return true; + } + + if (!Quaternion.equals(this.rotation, this._oldRotation)) { + return true; + } + + return !Vector3.equals(this.scale, this._oldScale); + } + + public update(): void { + this._oldPosition.set(this.position); + this._oldRotation.set(this.rotation); + this._oldScale.set(this.scale); + } + + public setParent(parent: HierarchicalTransform): void { + this._parent = parent; + } + + public get transformation(): Mat4 { + return Mat4.multiply(this.parentMatrix(), this._transformationMatrix); + } + + private parentMatrix(): Mat4 { + if (this._parent?.hasChange()) { + this._parentTransformation.set(this._parent.transformation); + } + + return this._parentTransformation; + } + + public get transformedPos(): SimpleVector3 { + return this.parentMatrix().getTransformedVector(this.position); + } + + public get transformedRot(): Quaternion { + if (!this._parent) { + return this.rotation; + } + + return Quaternion.multiply(this._parent.transformedRot, this.rotation); + } +} diff --git a/src/models/index.ts b/src/models/index.ts index ab5f1cf6..a38b127d 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -25,6 +25,12 @@ export * from "./paginate.model"; export * from "./object-change"; export * from "./loops"; +export * from "./transform"; +export * from "./transform-2d"; +export * from "./transform-3d"; +export * from "./hierarchical-transform"; + + // TODO: Cannot import countries.data.json // export * from "./countries/country.interface"; // export * from "./countries/country.model"; diff --git a/src/models/transform-3d.ts b/src/models/transform-3d.ts index 023a885b..7f2533ef 100644 --- a/src/models/transform-3d.ts +++ b/src/models/transform-3d.ts @@ -9,7 +9,7 @@ export interface Transform3D { readonly rotation: ReadonlySimpleVector4; } -export function getDefaultTransform2D(): Transform3D { +export function getDefaultTransform3D(): Transform3D { return { offset : { x: 0, diff --git a/src/models/transform.ts b/src/models/transform.ts index 74ff0ddd..dbbe4c7e 100644 --- a/src/models/transform.ts +++ b/src/models/transform.ts @@ -1,98 +1,104 @@ -// import { mat4, quat } from "gl-matrix"; -// import { ReadonlySimpleVector3, SimpleVector3, Vector3 } from "gtools/math"; -// -// /** -// * const yaw = () => -atan2f( x,z ) -// * const pitch = () => atan2f( y, sqrtf( x*x+z*z ) ) -// */ -// -// export class Transform { -// private _position = Vector3.ZERO; -// private _rotation = quat.create(); -// private _scale = Vector3.ONE; -// private _transformationMatrix = mat4.create(); -// -// public lookAt(target: SimpleVector3): void { -// mat4.getRotation( -// this._rotation, -// mat4.targetTo( -// mat4.create(), -// this._position.toArray(), -// [target.x, target.y, target.z], -// [0, 1, 0] -// ) -// ); -// this.updateTransformationMatrix(); -// } -// -// public clone(): Transform { -// const result = new Transform(); -// -// result._position = this._position.clone(); -// result._scale = this._scale.clone(); -// result._rotation = quat.clone(this._rotation); -// result.updateTransformationMatrix(); -// -// return result; -// } -// -// public move(offset: SimpleVector3): void { -// this._position.add(offset); -// this.updateTransformationMatrix(); -// } -// -// public get transformation(): mat4 { -// return this._transformationMatrix; -// } -// -// private updateTransformationMatrix(): mat4 { -// return mat4.fromRotationTranslationScale( -// this._transformationMatrix, -// this._rotation, -// this._position.toArray(), -// this._scale.toArray() -// ); -// } -// -// public setEulerRotation(x: number, y: number, z: number): void { -// quat.fromEuler(this._rotation, x, y, z); -// this.updateTransformationMatrix(); -// } -// -// public set rotation(rotation: quat) { -// this._rotation = rotation; -// this.updateTransformationMatrix(); -// } -// -// public get rotation(): quat { -// return this._rotation; -// } -// -// public setPosition(x: number, y: number, z: number): void { -// this._position.setData(x, y, z); -// this.updateTransformationMatrix(); -// } -// -// public set position(position: ReadonlySimpleVector3) { -// this._position.setData(position.x, position.y, position.z); -// this.updateTransformationMatrix(); -// } -// -// public get position(): ReadonlySimpleVector3 { -// return this._position; -// } -// -// public setScale(x: number, y: number, z: number): void { -// this._scale.setData(x, y, z); -// this.updateTransformationMatrix(); -// } -// -// public set scale(scale: Vector3) { -// this._scale.set(scale); -// this.updateTransformationMatrix(); -// } -// -// public get scale(): Vector3 { -// return this._scale; -// } -// } +import { Mat4, ReadonlySimpleVector3, SimpleVector3, Vector3 } from "../math"; +import { Quaternion } from "../math/quaternion"; + +/** + * const yaw = () => -atan2f( x,z ) + * const pitch = () => atan2f( y, sqrtf( x*x+z*z ) ) + */ + +export class Transform { + private _position = new Vector3(); + private _rotation = new Quaternion(); + private _scale = new Vector3(1, 1, 1); + protected readonly _transformationMatrix = Mat4.create(); + + // public lookAt(target: SimpleVector3): void { + // mat4.getRotation( + // this._rotation, + // mat4.targetTo( + // mat4.create(), + // this._position.toArray(), + // [target.x, target.y, target.z], + // [0, 1, 0] + // ) + // ); + // this.updateTransformationMatrix(); + // } + + public clone(): Transform { + const result = new Transform(); + + result._position = this._position.clone(); + result._scale = this._scale.clone(); + result._rotation = this._rotation.clone(); + result.updateTransformationMatrix(); + + return result; + } + + public move(offset: ReadonlySimpleVector3): void { + this._position.add(offset); + this.updateTransformationMatrix(); + } + + public get transformation(): Mat4 { + return this._transformationMatrix; + } + + private updateTransformationMatrix(): Mat4 { + return Mat4.fromRotationTranslationScale( + this._rotation, + this._position, + this._scale, + this._transformationMatrix, + ); + } + + /** + * + * @param x - rotation about X axis in radians + * @param y - rotation about Y axis in radians + * @param z - rotation about Z axis in radians + */ + public setEulerRotation(x: number, y: number, z: number): void { + Quaternion.fromEuler(x, y, z, this._rotation); + this.updateTransformationMatrix(); + } + + public get rotation(): Quaternion { + return this._rotation; + } + + public set rotation(rotation: Quaternion) { + this._rotation.set(rotation); + this.updateTransformationMatrix(); + } + + public setPosition(x: number, y: number, z: number): void { + this._position.setData(x, y, z); + this.updateTransformationMatrix(); + } + + public set position(position: ReadonlySimpleVector3) { + this._position.setData(position.x, position.y, position.z); + this.updateTransformationMatrix(); + } + + public get position(): ReadonlySimpleVector3 { + return this._position; + } + + public setScale(x: number, y: number, z: number): void { + this._scale.setData(x, y, z); + this.updateTransformationMatrix(); + } + + public set scale(scale: Vector3) { + this._scale.set(scale); + this.updateTransformationMatrix(); + } + + public get scale(): Vector3 { + return this._scale; + } +} diff --git a/src/types/round-data.type.ts b/src/types/round-data.type.ts index d5603a01..cc1dc73d 100644 --- a/src/types/round-data.type.ts +++ b/src/types/round-data.type.ts @@ -1 +1 @@ -export type RoundData = number | { tr?: number; tl?: number; br?: number; bl?: number }; +export type RoundData = number | { tr?: number; tl?: number; br?: number; bl?: number } | [tr: number, br: number, bt: number, tl: number]; diff --git a/src/types/text-options.interface.ts b/src/types/text-options.interface.ts index 079a9826..c9adc114 100644 --- a/src/types/text-options.interface.ts +++ b/src/types/text-options.interface.ts @@ -5,6 +5,7 @@ export interface TextOptionsInterface { fontSize: number; font: string; fontColor: string; + rotation?: number; verticalAlign: VerticalAlign; horizontalAlign: HorizontalAlign; } diff --git a/yarn.lock b/yarn.lock index f5f7bee1..8797be34 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2401,6 +2401,11 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +gl-matrix@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.3.0.tgz#232eef60b1c8b30a28cbbe75b2caf6c48fd6358b" + integrity sha512-COb7LDz+SXaHtl/h4LeaFcNdJdAQSDeVqjiIihSXNrkWObZLhDI4hIkZC11Aeqp7bcE72clzB0BnDXr2SmslRA== + glob-parent@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28"