Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions lib/gl-vec3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { mat4, vec3 } from "gl-matrix"
import type { Point3 } from "./types"

export type Vec3 = vec3

export function createVec3(): vec3 {
return vec3.create()
}

export function cloneVec3(v: vec3): vec3 {
return vec3.clone(v)
}

export function fromPoint(point: Point3, out: vec3 = vec3.create()): vec3 {
out[0] = point.x
out[1] = point.y
out[2] = point.z
return out
}

export function toPoint(source: vec3): Point3 {
return { x: source[0]!, y: source[1]!, z: source[2]! }
}

export function add(out: vec3, a: vec3, b: vec3): vec3 {
return vec3.add(out, a, b)
}

export function subtract(out: vec3, a: vec3, b: vec3): vec3 {
return vec3.subtract(out, a, b)
}

export function scale(out: vec3, a: vec3, scalar: number): vec3 {
return vec3.scale(out, a, scalar)
}

export function dot(a: vec3, b: vec3): number {
return vec3.dot(a, b)
}

export function cross(out: vec3, a: vec3, b: vec3): vec3 {
return vec3.cross(out, a, b)
}

export function length(a: vec3): number {
return vec3.length(a)
}

export function normalize(out: vec3, a: vec3): vec3 {
return vec3.normalize(out, a)
}

export function createRotationMatrix(rotation?: Point3 | null): mat4 | null {
if (!rotation) return null
const matrix = mat4.create()
mat4.identity(matrix)
// Apply in Z→Y→X order so the combined transform matches the legacy
// rotLocal implementation (which rotated vectors X, then Y, then Z).
if (rotation.z) mat4.rotateZ(matrix, matrix, rotation.z)
if (rotation.y) mat4.rotateY(matrix, matrix, rotation.y)
if (rotation.x) mat4.rotateX(matrix, matrix, rotation.x)
return matrix
}

export function transformMat4(
out: vec3,
v: vec3,
matrix: mat4 | null | undefined,
): vec3 {
if (!matrix) return vec3.copy(out, v)
return vec3.transformMat4(out, v, matrix)
}
128 changes: 83 additions & 45 deletions lib/mesh.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,108 @@
import type { Point3, STLMesh, Box } from "./types"
import { add, sub, scale, rotLocal } from "./vec3"
import { vec3 } from "gl-matrix"
import type { STLMesh, Box } from "./types"
import {
add,
createRotationMatrix,
fromPoint,
scale,
subtract,
transformMat4,
} from "./gl-vec3"

function accumulateOptionalPosition(out: vec3, box: Box) {
if (box.stlPosition) add(out, out, fromPoint(box.stlPosition))
if (box.objPosition) add(out, out, fromPoint(box.objPosition))
if (box.threeMfPosition) add(out, out, fromPoint(box.threeMfPosition))
}

export function scaleAndPositionMesh(
mesh: STLMesh,
box: Box,
scaleToBox: boolean,
modelType: "stl" | "obj" | "3mf",
): Point3[] {
): Float32Array {
const { boundingBox } = mesh
const meshCenter = scale(add(boundingBox.min, boundingBox.max), 0.5)
const meshCenter = vec3.create()
add(meshCenter, fromPoint(boundingBox.min), fromPoint(boundingBox.max))
scale(meshCenter, meshCenter, 0.5)
const centerModel = box.centerModel !== false

// Rotate vertices around the mesh center
const rotatedVerts: Point3[] = []
const rotationMatrix =
modelType === "stl"
? createRotationMatrix(box.stlRotation)
: modelType === "obj"
? createRotationMatrix(box.objRotation)
: createRotationMatrix(box.threeMfRotation)

const rotatedVerts = new Float32Array(mesh.triangles.length * 9)
const local = vec3.create()
const rotated = vec3.create()
let cursor = 0

for (const tri of mesh.triangles) {
for (const v of tri.vertices) {
let p = sub(v, meshCenter)
if (modelType === "stl" && box.stlRotation)
p = rotLocal(p, box.stlRotation)
if (modelType === "obj" && box.objRotation)
p = rotLocal(p, box.objRotation)
if (modelType === "3mf" && box.threeMfRotation)
p = rotLocal(p, box.threeMfRotation)
if (!centerModel) p = add(p, meshCenter)
rotatedVerts.push(p)
for (const vertex of tri.vertices) {
subtract(local, fromPoint(vertex), meshCenter)
transformMat4(rotated, local, rotationMatrix)
if (!centerModel) add(rotated, rotated, meshCenter)
rotatedVerts[cursor++] = rotated[0]!
rotatedVerts[cursor++] = rotated[1]!
rotatedVerts[cursor++] = rotated[2]!
}
}

let uniformScale = 1
let rotatedCenter = { x: 0, y: 0, z: 0 }
const rotatedCenter = vec3.create()

if (scaleToBox) {
// Compute bounding box after rotation
let min = { x: Infinity, y: Infinity, z: Infinity }
let max = { x: -Infinity, y: -Infinity, z: -Infinity }
for (const v of rotatedVerts) {
if (v.x < min.x) min.x = v.x
if (v.y < min.y) min.y = v.y
if (v.z < min.z) min.z = v.z
if (v.x > max.x) max.x = v.x
if (v.y > max.y) max.y = v.y
if (v.z > max.z) max.z = v.z
const min = vec3.fromValues(Infinity, Infinity, Infinity)
const max = vec3.fromValues(-Infinity, -Infinity, -Infinity)
for (let i = 0; i < rotatedVerts.length; i += 3) {
const x = rotatedVerts[i]!
const y = rotatedVerts[i + 1]!
const z = rotatedVerts[i + 2]!
if (x < min[0]!) min[0] = x
if (y < min[1]!) min[1] = y
if (z < min[2]!) min[2] = z
if (x > max[0]!) max[0] = x
if (y > max[1]!) max[1] = y
if (z > max[2]!) max[2] = z
}
const rotatedSize = sub(max, min)
const boxSize = box.size
const scaleX = boxSize.x / rotatedSize.x
const scaleY = boxSize.y / rotatedSize.y
const scaleZ = boxSize.z / rotatedSize.z
const rotatedSize = vec3.create()
subtract(rotatedSize, max, min)
const boxSize = fromPoint(box.size)
const scaleX = rotatedSize[0]! ? boxSize[0]! / rotatedSize[0]! : 1
const scaleY = rotatedSize[1]! ? boxSize[1]! / rotatedSize[1]! : 1
const scaleZ = rotatedSize[2]! ? boxSize[2]! / rotatedSize[2]! : 1
uniformScale = Math.min(scaleX, scaleY, scaleZ)
rotatedCenter = scale(add(min, max), 0.5)
add(rotatedCenter, min, max)
scale(rotatedCenter, rotatedCenter, 0.5)
}

const transformedVertices: Point3[] = []
for (const p of rotatedVerts) {
let t = p
const transformedVertices = new Float32Array(rotatedVerts.length)
const translation = vec3.create()
accumulateOptionalPosition(translation, box)
const boxRotation = createRotationMatrix(box.rotation)
const boxCenter = fromPoint(box.center)

const transformed = vec3.create()
for (let i = 0; i < rotatedVerts.length; i += 3) {
vec3.set(
transformed,
rotatedVerts[i]!,
rotatedVerts[i + 1]!,
rotatedVerts[i + 2]!,
)
if (scaleToBox) {
t = sub(t, rotatedCenter)
t = scale(t, uniformScale)
if (!centerModel) t = add(t, rotatedCenter)
subtract(transformed, transformed, rotatedCenter)
scale(transformed, transformed, uniformScale)
if (!centerModel) add(transformed, transformed, rotatedCenter)
}
if (box.stlPosition) t = add(t, box.stlPosition)
if (box.objPosition) t = add(t, box.objPosition)
if (box.threeMfPosition) t = add(t, box.threeMfPosition)
if (box.rotation) t = rotLocal(t, box.rotation)
t = add(t, box.center)
transformedVertices.push(t)
add(transformed, transformed, translation)
transformMat4(transformed, transformed, boxRotation)
add(transformed, transformed, boxCenter)
transformedVertices[i] = transformed[0]!
transformedVertices[i + 1] = transformed[1]!
transformedVertices[i + 2] = transformed[2]!
}

return transformedVertices
Expand Down
29 changes: 18 additions & 11 deletions lib/render-elements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ function fmtPrecise(n: number): string {
return (Math.round(n * 100) / 100).toString()
}

function readVertex(buffer: Float32Array, vertexIndex: number): Point3 {
const base = vertexIndex * 3
return {
x: buffer[base]!,
y: buffer[base + 1]!,
z: buffer[base + 2]!,
}
}

/*────────────── Camera & Projection ─────────────*/
const W_DEF = 400
const H_DEF = 400
Expand Down Expand Up @@ -147,12 +156,10 @@ export async function buildRenderElements(

// Render STL triangles
for (let i = 0; i < mesh.triangles.length; i++) {
const triangle = mesh.triangles[i]
const vertexStart = i * 3

const v0w = transformedVertices[vertexStart]!
const v1w = transformedVertices[vertexStart + 1]!
const v2w = transformedVertices[vertexStart + 2]!
const v0w = readVertex(transformedVertices, vertexStart)
const v1w = readVertex(transformedVertices, vertexStart + 1)
const v2w = readVertex(transformedVertices, vertexStart + 2)

const v0c = toCam(v0w, scene.camera)
const v1c = toCam(v1w, scene.camera)
Expand Down Expand Up @@ -188,9 +195,9 @@ export async function buildRenderElements(
const vertexStart = i * 3
const triangle = mesh.triangles[i]!

const v0w = transformedVertices[vertexStart]!
const v1w = transformedVertices[vertexStart + 1]!
const v2w = transformedVertices[vertexStart + 2]!
const v0w = readVertex(transformedVertices, vertexStart)
const v1w = readVertex(transformedVertices, vertexStart + 1)
const v2w = readVertex(transformedVertices, vertexStart + 2)

const v0c = toCam(v0w, scene.camera)
const v1c = toCam(v1w, scene.camera)
Expand Down Expand Up @@ -229,9 +236,9 @@ export async function buildRenderElements(
const vertexStart = i * 3
const triangle = mesh.triangles[i]!

const v0w = transformedVertices[vertexStart]!
const v1w = transformedVertices[vertexStart + 1]!
const v2w = transformedVertices[vertexStart + 2]!
const v0w = readVertex(transformedVertices, vertexStart)
const v1w = readVertex(transformedVertices, vertexStart + 1)
const v2w = readVertex(transformedVertices, vertexStart + 2)

const v0c = toCam(v0w, scene.camera)
const v1c = toCam(v1w, scene.camera)
Expand Down
77 changes: 42 additions & 35 deletions lib/vec3.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,60 @@
import { mat4, vec3 } from "gl-matrix"
import type { Point3 } from "./types"

function toVec(p: Point3): vec3 {
return vec3.fromValues(p.x, p.y, p.z)
}

function fromVec(v: vec3): Point3 {
return { x: v[0]!, y: v[1]!, z: v[2]! }
}

export function add(a: Point3, b: Point3): Point3 {
return { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z }
const out = vec3.create()
vec3.add(out, toVec(a), toVec(b))
return fromVec(out)
}

export function sub(a: Point3, b: Point3): Point3 {
return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }
const out = vec3.create()
vec3.subtract(out, toVec(a), toVec(b))
return fromVec(out)
}

export function dot(a: Point3, b: Point3): number {
return a.x * b.x + a.y * b.y + a.z * b.z
return vec3.dot(toVec(a), toVec(b))
}

export function cross(a: Point3, b: Point3): Point3 {
return {
x: a.y * b.z - a.z * b.y,
y: a.z * b.x - a.x * b.z,
z: a.x * b.y - a.y * b.x,
}
const out = vec3.create()
vec3.cross(out, toVec(a), toVec(b))
return fromVec(out)
}

export function scale(v: Point3, k: number): Point3 {
return { x: v.x * k, y: v.y * k, z: v.z * k }
const out = vec3.create()
vec3.scale(out, toVec(v), k)
return fromVec(out)
}

export function len(v: Point3): number {
return Math.sqrt(dot(v, v))
return vec3.length(toVec(v))
}

export function norm(v: Point3): Point3 {
const l = len(v) || 1
return scale(v, 1 / l)
const out = vec3.create()
vec3.normalize(out, toVec(v))
if (!out[0] && !out[1] && !out[2]) return { x: 0, y: 0, z: 0 }
return fromVec(out)
}

export function rotLocal(p: Point3, r: Point3 = { x: 0, y: 0, z: 0 }): Point3 {
let { x, y, z } = p
if (r.x) {
const c = Math.cos(r.x)
const s = Math.sin(r.x)
const y2 = y * c - z * s
z = y * s + z * c
y = y2
}
if (r.y) {
const c = Math.cos(r.y)
const s = Math.sin(r.y)
const x2 = x * c + z * s
z = -x * s + z * c
x = x2
}
if (r.z) {
const c = Math.cos(r.z)
const s = Math.sin(r.z)
const x2 = x * c - y * s
y = x * s + y * c
x = x2
}
return { x, y, z }
const matrix = mat4.create()
mat4.identity(matrix)
if (r.x) mat4.rotateX(matrix, matrix, r.x)
if (r.y) mat4.rotateY(matrix, matrix, r.y)
if (r.z) mat4.rotateZ(matrix, matrix, r.z)
const out = vec3.create()
vec3.transformMat4(out, toVec(p), matrix)
return fromVec(out)
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
},
"dependencies": {
"fast-xml-parser": "^5.2.5",
"fflate": "^0.8.2"
"fflate": "^0.8.2",
"gl-matrix": "^3.4.4"
}
}
Loading