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"