UNPKG

@bitbybit-dev/base

Version:

Bit By Bit Developers Base CAD Library to Program Geometry

334 lines (333 loc) 13.3 kB
/** * 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 rotation transformations around a center point and custom axis. * Combines translation to origin, axis rotation, then translation back. * Example: center=[5,0,0], axis=[0,1,0], angle=90° → rotates around vertical axis through point [5,0,0] * @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 rotation transformations around a center point along the X axis. * Example: center=[5,5,5], angle=90° → rotates 90° around X axis through point [5,5,5] * @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 rotation transformations around a center point along the Y axis. * Example: center=[0,0,0], angle=45° → rotates 45° around Y axis through origin * @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 rotation transformations around a center point along the Z axis. * Example: center=[10,10,0], angle=180° → rotates 180° around Z axis through point [10,10,0] * @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 rotation transformations using yaw-pitch-roll (Euler angles) around a center point. * Yaw → Y axis rotation, Pitch → X axis rotation, Roll → Z axis rotation. * Example: center=[0,0,0], yaw=90°, pitch=0°, roll=0° → rotates 90° around Y axis * @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]), ]; } /** * Creates non-uniform scale transformation around a center point (different scale per axis). * Example: center=[5,5,5], scaleXyz=[2,1,0.5] → doubles X, keeps Y, halves Z around point [5,5,5] * @param inputs Scale center xyz trnansformation * @returns array of transformations * @group scale * @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 non-uniform scale transformation from origin (different scale per axis). * Example: scaleXyz=[2,3,1] → doubles X, triples Y, keeps Z unchanged * @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 directional stretch transformation that scales along a specific direction from a center point. * Points move only along the direction vector; perpendicular plane remains unchanged. * Example: center=[0,0,0], direction=[1,0,0], scale=2 → stretches 2× along X axis only * @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 from origin (same scale on all axes). * Example: scale=2 → doubles size in all directions (X, Y, Z) * @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 around a center point (same scale on all axes). * Example: center=[5,5,5], scale=0.5 → halves size in all directions around point [5,5,5] * @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 translation transformation (moves objects in space). * Example: translation=[10,5,0] → moves object 10 units in X, 5 in Y, 0 in Z * @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 multiple translation transformations (batch move operations). * Example: translations=[[1,0,0], [0,2,0]] → generates two transforms: move +X, move +Y * @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 identity transformation matrix (no transformation - leaves objects unchanged). * Returns 4×4 matrix: [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1] * @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; } }