@bitbybit-dev/base
Version:
Bit By Bit Developers Base CAD Library to Program Geometry
320 lines (319 loc) • 11.8 kB
JavaScript
/**
* Transformations help to move, scale, rotate objects. You can combine multiple transformations
* for object to be placed exactly into position and orientation that you want.
* Contains various methods for transformations that represent 4x4 matrixes in flat 16 number arrays.
*/
export class Transforms {
constructor(vector, math) {
this.vector = vector;
this.math = math;
}
/**
* Creates a rotation transformations around the center and an axis
* @param inputs Rotation around center with an axis information
* @returns array of transformations
* @group rotation
* @shortname center axis
* @drawable false
*/
rotationCenterAxis(inputs) {
return [
this.translation(-inputs.center[0], -inputs.center[1], -inputs.center[2]),
this.rotationAxis(inputs.axis, this.math.degToRad({ number: inputs.angle })),
this.translation(inputs.center[0], inputs.center[1], inputs.center[2]),
];
}
/**
* Creates a rotation transformations around the center and an X axis
* @param inputs Rotation around center with an X axis information
* @returns array of transformations
* @group rotation
* @shortname center x
* @drawable false
*/
rotationCenterX(inputs) {
return [
this.translation(-inputs.center[0], -inputs.center[1], -inputs.center[2]),
this.rotationX(this.math.degToRad({ number: inputs.angle })),
this.translation(inputs.center[0], inputs.center[1], inputs.center[2]),
];
}
/**
* Creates a rotation transformations around the center and an Y axis
* @param inputs Rotation around center with an Y axis information
* @returns array of transformations
* @group rotation
* @shortname center y
* @drawable false
*/
rotationCenterY(inputs) {
return [
this.translation(-inputs.center[0], -inputs.center[1], -inputs.center[2]),
this.rotationY(this.math.degToRad({ number: inputs.angle })),
this.translation(inputs.center[0], inputs.center[1], inputs.center[2]),
];
}
/**
* Creates a rotation transformations around the center and an Z axis
* @param inputs Rotation around center with an Z axis information
* @returns array of transformations
* @group rotation
* @shortname center z
* @drawable false
*/
rotationCenterZ(inputs) {
return [
this.translation(-inputs.center[0], -inputs.center[1], -inputs.center[2]),
this.rotationZ(this.math.degToRad({ number: inputs.angle })),
this.translation(inputs.center[0], inputs.center[1], inputs.center[2]),
];
}
/**
* Creates a rotation transformations with yaw pitch and roll
* @param inputs Yaw pitch roll rotation information
* @returns array of transformations
* @group rotation
* @shortname yaw pitch roll
* @drawable false
*/
rotationCenterYawPitchRoll(inputs) {
return [
this.translation(-inputs.center[0], -inputs.center[1], -inputs.center[2]),
this.rotationYawPitchRoll(this.math.degToRad({ number: inputs.yaw }), this.math.degToRad({ number: inputs.pitch }), this.math.degToRad({ number: inputs.roll })),
this.translation(inputs.center[0], inputs.center[1], inputs.center[2]),
];
}
/**
* Scale transformation around center and xyz directions
* @param inputs Scale center xyz trnansformation
* @returns array of transformations
* @group rotation
* @shortname center xyz
* @drawable false
*/
scaleCenterXYZ(inputs) {
return [
this.translation(-inputs.center[0], -inputs.center[1], -inputs.center[2]),
this.scaling(inputs.scaleXyz[0], inputs.scaleXyz[1], inputs.scaleXyz[2]),
this.translation(inputs.center[0], inputs.center[1], inputs.center[2]),
];
}
/**
* Creates the scale transformation in x, y and z directions
* @param inputs Scale XYZ number array information
* @returns transformation
* @group scale
* @shortname xyz
* @drawable false
*/
scaleXYZ(inputs) {
return [this.scaling(inputs.scaleXyz[0], inputs.scaleXyz[1], inputs.scaleXyz[2])];
}
/**
* Creates a stretch transformation along a specific direction, relative to a center point.
* This scales points along the given direction vector while leaving points in the
* plane perpendicular to the direction (passing through the center) unchanged.
* @param inputs Defines the center, direction, and scale factor for the stretch.
* @returns Array of transformations: [Translate To Origin, Stretch, Translate Back].
* @group scale
* @shortname stretch dir center
* @drawable false
*/
stretchDirFromCenter(inputs) {
return [
this.translation(-inputs.center[0], -inputs.center[1], -inputs.center[2]),
this.stretchDirection(inputs.direction, inputs.scale),
this.translation(inputs.center[0], inputs.center[1], inputs.center[2]),
];
}
/**
* Creates uniform scale transformation
* @param inputs Scale Dto
* @returns transformation
* @group scale
* @shortname uniform
* @drawable false
*/
uniformScale(inputs) {
return [this.scaling(inputs.scale, inputs.scale, inputs.scale)];
}
/**
* Creates uniform scale transformation from the center
* @param inputs Scale Dto with center point information
* @returns array of transformations
* @group scale
* @shortname uniform from center
* @drawable false
*/
uniformScaleFromCenter(inputs) {
return [
this.translation(-inputs.center[0], -inputs.center[1], -inputs.center[2]),
this.scaling(inputs.scale, inputs.scale, inputs.scale),
this.translation(inputs.center[0], inputs.center[1], inputs.center[2]),
];
}
/**
* Creates the translation transformation
* @param inputs Translation information
* @returns transformation
* @group translation
* @shortname xyz
* @drawable false
*/
translationXYZ(inputs) {
return [this.translation(inputs.translation[0], inputs.translation[1], inputs.translation[2])];
}
/**
* Creates the translation transformation
* @param inputs Translation information
* @returns transformation
* @group translations
* @shortname xyz
* @drawable false
*/
translationsXYZ(inputs) {
return inputs.translations.map(translation => [this.translation(translation[0], translation[1], translation[2])]);
}
/**
* Creates the identity transformation
* @returns transformation
* @group identity
* @shortname identity
* @drawable false
*/
identity() {
return [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0];
}
translation(x, y, z) {
return [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, x, y, z, 1.0];
}
scaling(x, y, z) {
return [x, 0.0, 0.0, 0.0, 0.0, y, 0.0, 0.0, 0.0, 0.0, z, 0.0, 0.0, 0.0, 0.0, 1.0];
}
rotationAxis(axis, angle) {
const s = Math.sin(-angle);
const c = Math.cos(-angle);
const c1 = 1 - c;
const a = this.vector.normalized({ vector: axis });
const x = a[0];
const y = a[1];
const z = a[2];
const m = this.identity();
m[0] = x * x * c1 + c;
m[1] = x * y * c1 - z * s;
m[2] = x * z * c1 + y * s;
m[3] = 0.0;
m[4] = y * x * c1 + z * s;
m[5] = y * y * c1 + c;
m[6] = y * z * c1 - x * s;
m[7] = 0.0;
m[8] = z * x * c1 - y * s;
m[9] = z * y * c1 + x * s;
m[10] = z * z * c1 + c;
m[11] = 0.0;
m[12] = 0.0;
m[13] = 0.0;
m[14] = 0.0;
m[15] = 1.0;
return m;
}
rotationX(angle) {
const s = Math.sin(angle);
const c = Math.cos(angle);
return [1.0, 0.0, 0.0, 0.0, 0.0, c, s, 0.0, 0.0, -s, c, 0.0, 0.0, 0.0, 0.0, 1.0];
}
rotationY(angle) {
const s = Math.sin(angle);
const c = Math.cos(angle);
return [c, 0.0, -s, 0.0, 0.0, 1.0, 0.0, 0.0, s, 0.0, c, 0.0, 0.0, 0.0, 0.0, 1.0];
}
rotationZ(angle) {
const s = Math.sin(angle);
const c = Math.cos(angle);
return [c, s, 0.0, 0.0, -s, c, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0];
}
rotationYawPitchRoll(yaw, pitch, roll) {
const halfRoll = roll * 0.5;
const halfPitch = pitch * 0.5;
const halfYaw = yaw * 0.5;
const sinRoll = Math.sin(halfRoll);
const cosRoll = Math.cos(halfRoll);
const sinPitch = Math.sin(halfPitch);
const cosPitch = Math.cos(halfPitch);
const sinYaw = Math.sin(halfYaw);
const cosYaw = Math.cos(halfYaw);
const x = cosYaw * sinPitch * cosRoll + sinYaw * cosPitch * sinRoll;
const y = sinYaw * cosPitch * cosRoll - cosYaw * sinPitch * sinRoll;
const z = cosYaw * cosPitch * sinRoll - sinYaw * sinPitch * cosRoll;
const w = cosYaw * cosPitch * cosRoll + sinYaw * sinPitch * sinRoll;
return this.rotationMatrixFromQuat(x, y, z, w);
}
rotationMatrixFromQuat(x, y, z, w) {
const xx = x * x;
const yy = y * y;
const zz = z * z;
const xy = x * y;
const zw = z * w;
const zx = z * x;
const yw = y * w;
const yz = y * z;
const xw = x * w;
const m = this.identity();
m[0] = 1.0 - 2.0 * (yy + zz);
m[1] = 2.0 * (xy + zw);
m[2] = 2.0 * (zx - yw);
m[3] = 0.0;
m[4] = 2.0 * (xy - zw);
m[5] = 1.0 - 2.0 * (zz + xx);
m[6] = 2.0 * (yz + xw);
m[7] = 0.0;
m[8] = 2.0 * (zx + yw);
m[9] = 2.0 * (yz - xw);
m[10] = 1.0 - 2.0 * (yy + xx);
m[11] = 0.0;
m[12] = 0.0;
m[13] = 0.0;
m[14] = 0.0;
m[15] = 1.0;
return m;
}
/**
* Creates a 4x4 matrix that scales along a given direction vector.
* @param direction The direction vector (will be normalized).
* @param scale The scale factor along the direction.
* @returns A 4x4 column-major transformation matrix.
*/
stretchDirection(direction, scale) {
const d = this.vector.normalized({ vector: direction });
const [dx, dy, dz] = d;
// Handle potential zero vector after normalization (if input was zero)
if (isNaN(dx) || (dx === 0 && dy === 0 && dz === 0)) {
console.warn("Stretch direction vector is zero or invalid. Returning identity matrix.");
return this.identity();
}
const s = scale;
const sMinus1 = s - 1.0;
// Calculate elements of the 3x3 directional scaling part
const m11 = 1.0 + sMinus1 * dx * dx;
const m12 = sMinus1 * dx * dy;
const m13 = sMinus1 * dx * dz;
// m14 = 0
const m21 = sMinus1 * dy * dx;
const m22 = 1.0 + sMinus1 * dy * dy;
const m23 = sMinus1 * dy * dz;
// m24 = 0
const m31 = sMinus1 * dz * dx;
const m32 = sMinus1 * dz * dy;
const m33 = 1.0 + sMinus1 * dz * dz;
// m34 = 0
// m41, m42, m43 = 0, m44 = 1
// Assemble the 4x4 matrix in COLUMN-MAJOR order
const m = [
m11, m21, m31, 0.0,
m12, m22, m32, 0.0,
m13, m23, m33, 0.0,
0.0, 0.0, 0.0, 1.0 // Column 4
];
return m;
}
}