diff --git a/lib/src/functions/index.ts b/lib/src/functions/index.ts index f038715..4adecea 100644 --- a/lib/src/functions/index.ts +++ b/lib/src/functions/index.ts @@ -1 +1,15 @@ +import { Rectangle, Transform } from "../models"; + export const degToRad = (deg: number) => (deg * Math.PI) / 180; +export const radToDeg = (radians: number) => radians * (180 / Math.PI); + +export const getBounds = (rectangle: Rectangle, transform: Transform) => { + const { top, left, bottom, right } = rectangle; + + const matrix2d = transform.toMatrix2D(); + + const topLeft = matrix2d.transformPoint({ x: left, y: top }); + const bottomRight = matrix2d.transformPoint({ x: right, y: bottom }); + + return Rectangle.fromPoints(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y); +}; diff --git a/lib/src/interfaces/index.ts b/lib/src/interfaces/index.ts index 2be2da6..33bd6da 100644 --- a/lib/src/interfaces/index.ts +++ b/lib/src/interfaces/index.ts @@ -7,3 +7,16 @@ export interface IPoint { x: number; y: number; } + +export interface ITransform { + scaleX: number; + scaleY: number; + + translateY: number; + translateX: number; + + skewY: number; + skewX: number; + + rotate: number; +} diff --git a/lib/src/models/ellipse.ts b/lib/src/models/ellipse.ts index 5324ec3..952ddac 100644 --- a/lib/src/models/ellipse.ts +++ b/lib/src/models/ellipse.ts @@ -1,5 +1,6 @@ import { IPoint } from "../interfaces"; import { Point } from "./point"; +import { Rectangle } from "./rectangle"; export class Ellipse { center = new Point(); @@ -19,4 +20,17 @@ export class Ellipse { this.radiusX = radiusX; this.radiusY = radiusY; } + + static fromRectangle(rectangle: Rectangle) { + return new Ellipse(rectangle.center, rectangle.width / 2, rectangle.height / 2); + } + + toRectangle() { + return Rectangle.fromPoints( + this.center.x - this.radiusX, + this.center.y - this.radiusY, + this.center.x + this.radiusX, + this.center.y + this.radiusY, + ); + } } diff --git a/lib/src/models/index.ts b/lib/src/models/index.ts index a9c6a7c..e88355e 100644 --- a/lib/src/models/index.ts +++ b/lib/src/models/index.ts @@ -4,3 +4,4 @@ export * from "./size"; export * from "./ellipse"; export * from "./rectangle"; export * from "./transform"; +export * from "./matrix-2d"; diff --git a/lib/src/models/matrix-2d.ts b/lib/src/models/matrix-2d.ts new file mode 100644 index 0000000..ef8f701 --- /dev/null +++ b/lib/src/models/matrix-2d.ts @@ -0,0 +1,78 @@ +import { IPoint } from "../interfaces"; +import { Point } from "./point"; + +export type Matrix = [[number, number, number], [number, number, number], [number, number, number]]; + +const DEF_MATRIX: Matrix = [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1], +]; + +export class Matrix2D { + private _matrix: Matrix; + + constructor(matrix?: Matrix) { + this._matrix = matrix ?? DEF_MATRIX; + } + + clone(): Matrix2D { + return new Matrix2D(this.getData()); + } + + multiply(matrix: Matrix2D): Matrix2D { + const m = structuredClone(DEF_MATRIX); + for (let i = 0; i < 3; i++) { + for (let j = 0; j < 3; j++) { + m[i][j] = 0; + for (let k = 0; k < 3; k++) { + m[i][j] += this._matrix[i][k] * matrix._matrix[k][j]; + } + } + } + return new Matrix2D(m); + } + + translate(x: number, y: number): Matrix2D { + const translationMatrix = new Matrix2D([ + [1, 0, x], + [0, 1, y], + [0, 0, 1], + ]); + return this.multiply(translationMatrix); + } + + rotate(angle: number): Matrix2D { + const cos = Math.cos(angle); + const sin = Math.sin(angle); + const rotationMatrix = new Matrix2D([ + [cos, -sin, 0], + [sin, cos, 0], + [0, 0, 1], + ]); + return this.multiply(rotationMatrix); + } + + scale(sx: number, sy: number): Matrix2D { + const scaleMatrix = new Matrix2D([ + [sx, 0, 0], + [0, sy, 0], + [0, 0, 1], + ]); + return this.multiply(scaleMatrix); + } + + transformPoint(point: IPoint): Point { + const x = point.x; + const y = point.y; + const w = this._matrix[2][0] * x + this._matrix[2][1] * y + this._matrix[2][2]; + return new Point( + (this._matrix[0][0] * x + this._matrix[0][1] * y + this._matrix[0][2]) / w, + (this._matrix[1][0] * x + this._matrix[1][1] * y + this._matrix[1][2]) / w, + ); + } + + getData(): Matrix { + return structuredClone(this._matrix); + } +} diff --git a/lib/src/models/transform.ts b/lib/src/models/transform.ts index 38a909e..01755b8 100644 --- a/lib/src/models/transform.ts +++ b/lib/src/models/transform.ts @@ -1,3 +1,7 @@ +import { degToRad, radToDeg } from "../functions"; +import { ITransform } from "../interfaces"; +import { Matrix2D } from "./matrix-2d"; + export class Transform { scaleX = 1; scaleY = 1; @@ -9,4 +13,38 @@ export class Transform { skewX = 0; rotate = 0; + + constructor(transform?: Partial) { + this.scaleX = transform?.scaleX ?? 1; + this.scaleY = transform?.scaleY ?? 1; + this.translateX = transform?.translateX ?? 0; + this.translateY = transform?.translateY ?? 0; + this.skewY = transform?.scaleY ?? 0; + this.skewX = transform?.scaleX ?? 0; + this.rotate = transform?.rotate ?? 0; + } + + toMatrix2D(): Matrix2D { + const matrix = new Matrix2D(); + matrix.translate(this.translateX, this.translateY).rotate(degToRad(this.rotate)).scale(this.scaleX, this.scaleY); + return matrix; + } + + static fromMatrix2D(matrix: Matrix2D): Transform { + const m = matrix.getData(); + const transform = new Transform(); + + // Извлечение масштаба + transform.scaleX = Math.sqrt(m[0][0] * m[0][0] + m[0][1] * m[0][1]); + transform.scaleY = Math.sqrt(m[1][0] * m[1][0] + m[1][1] * m[1][1]); + + // Извлечение поворота + transform.rotate = radToDeg(Math.atan2(m[0][1], m[0][0])); + + // Извлечение сдвига + transform.translateX = m[0][2]; + transform.translateY = m[1][2]; + + return transform; + } } diff --git a/lib/src/package.json b/lib/src/package.json index d8960e4..04402cc 100644 --- a/lib/src/package.json +++ b/lib/src/package.json @@ -1,6 +1,6 @@ { "name": "@gxc-solutions/math", - "version": "0.0.2", + "version": "0.0.3", "main": "index.js", "author": "GXC Solutions", "publishConfig": {