mirror of
https://github.com/Heretek-AI/GDevelop.git
synced 2026-08-25 12:23:58 -04:00
474 lines
12 KiB
TypeScript
474 lines
12 KiB
TypeScript
namespace gdjs {
|
|
/**
|
|
* An affine transformation that can transform points.
|
|
* @category Utils > Geometry
|
|
*/
|
|
export class AffineTransformation {
|
|
private matrix: Float32Array;
|
|
|
|
/**
|
|
* Initialize to the identity.
|
|
*/
|
|
constructor() {
|
|
// | 1 0 0 |
|
|
// | 0 1 0 |
|
|
// | 0 0 1 |
|
|
this.matrix = new Float32Array([1, 0, 0, 1, 0, 0]);
|
|
}
|
|
|
|
/**
|
|
* Reset to the identity.
|
|
*/
|
|
setToIdentity() {
|
|
const matrix = this.matrix;
|
|
// | 1 0 0 |
|
|
// | 0 1 0 |
|
|
// | 0 0 1 |
|
|
matrix[0] = 1;
|
|
matrix[1] = 0;
|
|
matrix[2] = 0;
|
|
matrix[3] = 1;
|
|
matrix[4] = 0;
|
|
matrix[5] = 0;
|
|
}
|
|
|
|
/**
|
|
* Check if this transformation is the identity.
|
|
*/
|
|
isIdentity(): boolean {
|
|
const matrix = this.matrix;
|
|
return (
|
|
matrix[0] === 1 &&
|
|
matrix[1] === 0 &&
|
|
matrix[2] === 0 &&
|
|
matrix[3] === 1 &&
|
|
matrix[4] === 0 &&
|
|
matrix[5] === 0
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check if this is equals to another transformation.
|
|
* @param other The transformation to check.
|
|
*/
|
|
equals(other: AffineTransformation): boolean {
|
|
const matrix = this.matrix;
|
|
const otherMatrix = other.matrix;
|
|
return (
|
|
this === other ||
|
|
(matrix[0] === otherMatrix[0] &&
|
|
matrix[1] === otherMatrix[1] &&
|
|
matrix[2] === otherMatrix[2] &&
|
|
matrix[3] === otherMatrix[3] &&
|
|
matrix[4] === otherMatrix[4] &&
|
|
matrix[5] === otherMatrix[5])
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check if this is almost equals to another transformation.
|
|
* @param other The transformation to check.
|
|
* @param epsilon The relative margin error.
|
|
*/
|
|
nearlyEquals(other: AffineTransformation, epsilon: float): boolean {
|
|
const matrix = this.matrix;
|
|
const otherMatrix = other.matrix;
|
|
return (
|
|
this === other ||
|
|
(gdjs.nearlyEqual(matrix[0], otherMatrix[0], epsilon) &&
|
|
gdjs.nearlyEqual(matrix[1], otherMatrix[1], epsilon) &&
|
|
gdjs.nearlyEqual(matrix[2], otherMatrix[2], epsilon) &&
|
|
gdjs.nearlyEqual(matrix[3], otherMatrix[3], epsilon) &&
|
|
gdjs.nearlyEqual(matrix[4], otherMatrix[4], epsilon) &&
|
|
gdjs.nearlyEqual(matrix[5], otherMatrix[5], epsilon))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Copy a transformation.
|
|
* @param other The transformation to copy.
|
|
*/
|
|
copyFrom(other: AffineTransformation) {
|
|
const matrix = this.matrix;
|
|
const otherMatrix = other.matrix;
|
|
|
|
matrix[0] = otherMatrix[0];
|
|
matrix[1] = otherMatrix[1];
|
|
matrix[2] = otherMatrix[2];
|
|
matrix[3] = otherMatrix[3];
|
|
matrix[4] = otherMatrix[4];
|
|
matrix[5] = otherMatrix[5];
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Reset to a translation.
|
|
*
|
|
* @param tx The horizontal translation value.
|
|
* @param ty The vertical translation value.
|
|
*/
|
|
setToTranslation(tx: float, ty: float) {
|
|
const matrix = this.matrix;
|
|
// | m0 m2 m4 | | 1 0 tx |
|
|
// | m1 m3 m5 | = | 0 1 ty |
|
|
// | 0 0 1 | | 0 0 1 |
|
|
matrix[0] = 1;
|
|
matrix[1] = 0;
|
|
matrix[2] = 0;
|
|
matrix[3] = 1;
|
|
matrix[4] = tx;
|
|
matrix[5] = ty;
|
|
}
|
|
|
|
/**
|
|
* Concatenate a translation.
|
|
*
|
|
* @param tx The horizontal translation value.
|
|
* @param ty The vertical translation value.
|
|
*/
|
|
translate(tx: float, ty: float) {
|
|
var matrix = this.matrix;
|
|
// 1 0 tx
|
|
// 0 1 ty
|
|
// 0 0 1
|
|
// m0 m2 m4
|
|
// m1 m3 m5
|
|
// 0 0 1
|
|
matrix[4] = matrix[0] * tx + matrix[2] * ty + matrix[4];
|
|
matrix[5] = matrix[1] * tx + matrix[3] * ty + matrix[5];
|
|
}
|
|
|
|
/**
|
|
* Reset to a scale.
|
|
*
|
|
* @param sx The horizontal scale value.
|
|
* @param sy The vertical scale value.
|
|
*/
|
|
setToScale(sx: float, sy: float) {
|
|
const matrix = this.matrix;
|
|
// | m0 m2 m4 | | sx 0 0 |
|
|
// | m1 m3 m5 | = | 0 sy 0 |
|
|
// | 0 0 1 | | 0 0 1 |
|
|
matrix[0] = sx;
|
|
matrix[1] = 0;
|
|
matrix[2] = 0;
|
|
matrix[3] = sy;
|
|
matrix[4] = 0;
|
|
matrix[5] = 0;
|
|
}
|
|
|
|
/**
|
|
* Concatenate a scale.
|
|
*
|
|
* @param sx The horizontal scale value.
|
|
* @param sy The vertical scale value.
|
|
*/
|
|
scale(sx: float, sy: float) {
|
|
const matrix = this.matrix;
|
|
// sx 0 0
|
|
// 0 sy 0
|
|
// 0 0 1
|
|
// m0 m2 m4
|
|
// m1 m3 m5
|
|
// 0 0 1
|
|
matrix[0] *= sx;
|
|
matrix[1] *= sx;
|
|
matrix[2] *= sy;
|
|
matrix[3] *= sy;
|
|
}
|
|
|
|
/**
|
|
* Reset to a rotation.
|
|
*
|
|
* @param angle The angle of rotation in radians.
|
|
*/
|
|
setToRotation(angle: float) {
|
|
const matrix = this.matrix;
|
|
let cost = Math.cos(angle);
|
|
let sint = Math.sin(angle);
|
|
|
|
// Avoid rounding errors around 0.
|
|
if (cost === -1 || cost === 1) {
|
|
sint = 0;
|
|
}
|
|
if (sint === -1 || sint === 1) {
|
|
cost = 0;
|
|
}
|
|
|
|
// | m0 m2 m4 | | cost -sint 0 |
|
|
// | m1 m3 m5 | = | sint cost 0 |
|
|
// | 0 0 1 | | 0 0 1 |
|
|
matrix[0] = cost;
|
|
matrix[1] = sint;
|
|
matrix[2] = -sint;
|
|
matrix[3] = cost;
|
|
matrix[4] = 0;
|
|
matrix[5] = 0;
|
|
}
|
|
|
|
/**
|
|
* Concatenate a rotation.
|
|
*
|
|
* @param angle The angle of rotation in radians.
|
|
*/
|
|
rotate(angle: float) {
|
|
const matrix = this.matrix;
|
|
let cost = Math.cos(angle);
|
|
let sint = Math.sin(angle);
|
|
|
|
// Avoid rounding errors around 0.
|
|
if (cost === -1 || cost === 1) {
|
|
sint = 0;
|
|
}
|
|
if (sint === -1 || sint === 1) {
|
|
cost = 0;
|
|
}
|
|
|
|
// cost -sint 0
|
|
// sint cost 0
|
|
// 0 0 1
|
|
// m0 m2 m4
|
|
// m1 m3 m5
|
|
// 0 0 1
|
|
|
|
const m0 = matrix[0];
|
|
const m1 = matrix[1];
|
|
const m2 = matrix[2];
|
|
const m3 = matrix[3];
|
|
|
|
matrix[0] = m0 * cost + m2 * sint;
|
|
matrix[1] = m1 * cost + m3 * sint;
|
|
matrix[2] = m0 * -sint + m2 * cost;
|
|
matrix[3] = m1 * -sint + m3 * cost;
|
|
}
|
|
|
|
/**
|
|
* Reset to a rotation.
|
|
*
|
|
* @param angle The angle of rotation in radians.
|
|
* @param anchorX The rotation anchor point X.
|
|
* @param anchorY The rotation anchor point Y.
|
|
*/
|
|
setToRotationAround(angle: float, anchorX: float, anchorY: float) {
|
|
const matrix = this.matrix;
|
|
let cost = Math.cos(angle);
|
|
let sint = Math.sin(angle);
|
|
|
|
// Avoid rounding errors around 0.
|
|
if (cost === -1 || cost === 1) {
|
|
sint = 0;
|
|
}
|
|
if (sint === -1 || sint === 1) {
|
|
cost = 0;
|
|
}
|
|
|
|
// | m0 m2 m4 | | cost -sint x-x*cost+y*sint |
|
|
// | m1 m3 m5 | = | sint cost y-x*sint-y*cost |
|
|
// | 0 0 1 | | 0 0 1 |
|
|
matrix[0] = cost;
|
|
matrix[1] = sint;
|
|
matrix[2] = -sint;
|
|
matrix[3] = cost;
|
|
matrix[4] = anchorX - anchorX * cost + anchorY * sint;
|
|
matrix[5] = anchorY - anchorX * sint + anchorY * cost;
|
|
}
|
|
|
|
/**
|
|
* Concatenate a rotation.
|
|
*
|
|
* @param angle The angle of rotation in radians.
|
|
* @param anchorX The rotation anchor point X.
|
|
* @param anchorY The rotation anchor point Y.
|
|
*/
|
|
rotateAround(angle: float, anchorX: float, anchorY: float) {
|
|
this.translate(anchorX, anchorY);
|
|
this.rotate(angle);
|
|
// First: translate anchor to origin
|
|
this.translate(-anchorX, -anchorY);
|
|
}
|
|
|
|
/**
|
|
* Reset to an horizontal flip.
|
|
*
|
|
* @param anchorX The flip anchor point X.
|
|
*/
|
|
setToFlipX(anchorX: float) {
|
|
const matrix = this.matrix;
|
|
// | m0 m2 m4 | | -1 0 2x |
|
|
// | m1 m3 m5 | = | 0 1 0 |
|
|
// | 0 0 1 | | 0 0 1 |
|
|
matrix[0] = -1;
|
|
matrix[1] = 0;
|
|
matrix[2] = 0;
|
|
matrix[3] = 1;
|
|
matrix[4] = 2 * anchorX;
|
|
matrix[5] = 0;
|
|
}
|
|
|
|
/**
|
|
* Concatenate an horizontal flip.
|
|
*
|
|
* @param anchorX The flip anchor point X.
|
|
*/
|
|
flipX(anchorX: float) {
|
|
this.translate(anchorX, 0);
|
|
this.scale(-1, 1);
|
|
// First: translate anchor to origin
|
|
this.translate(-anchorX, 0);
|
|
}
|
|
|
|
/**
|
|
* Reset to an vertical flip.
|
|
*
|
|
* @param anchorY The flip anchor point Y.
|
|
*/
|
|
setToFlipY(anchorY: float) {
|
|
const matrix = this.matrix;
|
|
// | m0 m2 m4 | | 1 0 0 |
|
|
// | m1 m3 m5 | = | 0 -1 2x |
|
|
// | 0 0 1 | | 0 0 1 |
|
|
matrix[0] = -1;
|
|
matrix[1] = 0;
|
|
matrix[2] = 0;
|
|
matrix[3] = 1;
|
|
matrix[4] = 0;
|
|
matrix[5] = 2 * anchorY;
|
|
}
|
|
|
|
/**
|
|
* Concatenate an vertical flip.
|
|
*
|
|
* @param anchorY The flip anchor point Y.
|
|
*/
|
|
flipY(anchorY: float) {
|
|
this.translate(0, anchorY);
|
|
this.scale(1, -1);
|
|
// First: translate anchor to origin
|
|
this.translate(0, -anchorY);
|
|
}
|
|
|
|
/**
|
|
* Concatenate a transformation after this one.
|
|
* @param other The transformation to concatenate.
|
|
*/
|
|
concatenate(other: AffineTransformation) {
|
|
const matrix = this.matrix;
|
|
const otherMatrix = other.matrix;
|
|
|
|
const m0 = matrix[0];
|
|
const m1 = matrix[1];
|
|
const m2 = matrix[2];
|
|
const m3 = matrix[3];
|
|
const m4 = matrix[4];
|
|
const m5 = matrix[5];
|
|
|
|
const o0 = otherMatrix[0];
|
|
const o1 = otherMatrix[1];
|
|
const o2 = otherMatrix[2];
|
|
const o3 = otherMatrix[3];
|
|
const o4 = otherMatrix[4];
|
|
const o5 = otherMatrix[5];
|
|
|
|
// o0 o2 o4
|
|
// o1 o3 o5
|
|
// 0 0 1
|
|
// m0 m2 m4
|
|
// m1 m3 m5
|
|
// 0 0 1
|
|
matrix[0] = o0 * m0 + o1 * m2;
|
|
matrix[1] = o0 * m1 + o1 * m3;
|
|
matrix[2] = o2 * m0 + o3 * m2;
|
|
matrix[3] = o2 * m1 + o3 * m3;
|
|
matrix[4] = o4 * m0 + o5 * m2 + m4;
|
|
matrix[5] = o4 * m1 + o5 * m3 + m5;
|
|
}
|
|
|
|
/**
|
|
* Concatenate a transformation before this one.
|
|
* @param other The transformation to concatenate.
|
|
*/
|
|
preConcatenate(other: AffineTransformation) {
|
|
const matrix = this.matrix;
|
|
const otherMatrix = other.matrix;
|
|
|
|
const m0 = matrix[0];
|
|
const m1 = matrix[1];
|
|
const m2 = matrix[2];
|
|
const m3 = matrix[3];
|
|
const m4 = matrix[4];
|
|
const m5 = matrix[5];
|
|
|
|
const o0 = otherMatrix[0];
|
|
const o1 = otherMatrix[1];
|
|
const o2 = otherMatrix[2];
|
|
const o3 = otherMatrix[3];
|
|
const o4 = otherMatrix[4];
|
|
const o5 = otherMatrix[5];
|
|
|
|
// m0 m2 m4
|
|
// m1 m3 m5
|
|
// 0 0 1
|
|
// o0 o2 o4
|
|
// o1 o3 o5
|
|
// 0 0 1
|
|
matrix[0] = m0 * o0 + m1 * o2;
|
|
matrix[1] = m0 * o1 + m1 * o3;
|
|
matrix[2] = m2 * o0 + m3 * o2;
|
|
matrix[3] = m2 * o1 + m3 * o3;
|
|
matrix[4] = m4 * o0 + m5 * o2 + o4;
|
|
matrix[5] = m4 * o1 + m5 * o3 + o5;
|
|
}
|
|
|
|
/**
|
|
* Transform a point.
|
|
*
|
|
* @param source The point to transform.
|
|
* @param destination The Point to store the transformed coordinates.
|
|
*/
|
|
transform(source: FloatPoint, destination: FloatPoint) {
|
|
const matrix = this.matrix;
|
|
// x
|
|
// y
|
|
// 1
|
|
// m0 m2 m4
|
|
// m1 m3 m5
|
|
// 0 0 1
|
|
const x = matrix[0] * source[0] + matrix[2] * source[1] + matrix[4];
|
|
const y = matrix[1] * source[0] + matrix[3] * source[1] + matrix[5];
|
|
destination[0] = x;
|
|
destination[1] = y;
|
|
}
|
|
|
|
/**
|
|
* Invert the matrix.
|
|
*/
|
|
invert() {
|
|
const matrix = this.matrix;
|
|
|
|
const m0 = matrix[0];
|
|
const m1 = matrix[1];
|
|
const m2 = matrix[2];
|
|
const m3 = matrix[3];
|
|
const m4 = matrix[4];
|
|
const m5 = matrix[5];
|
|
|
|
const n = m0 * m3 - m1 * m2;
|
|
|
|
matrix[0] = m3 / n;
|
|
matrix[1] = -m1 / n;
|
|
matrix[2] = -m2 / n;
|
|
matrix[3] = m0 / n;
|
|
matrix[4] = (m2 * m5 - m3 * m4) / n;
|
|
matrix[5] = -(m0 * m5 - m1 * m4) / n;
|
|
|
|
return this;
|
|
}
|
|
|
|
toString() {
|
|
const matrix = this.matrix;
|
|
return `[[${matrix[0]} ${matrix[1]}] [${matrix[2]} ${matrix[3]}] [${matrix[4]} ${matrix[5]}]]`;
|
|
}
|
|
}
|
|
}
|