This commit is contained in:
@@ -179,6 +179,24 @@ function makeMaterial(color: [number, number, number] | null): THREE.MeshStandar
|
||||
})
|
||||
}
|
||||
|
||||
// ---- Binary decode helpers (geometry v2) ---------------------------------
|
||||
|
||||
function b64ToFloat32(b64: string): Float32Array {
|
||||
const bin = atob(b64)
|
||||
const buf = new ArrayBuffer(bin.length)
|
||||
const u8 = new Uint8Array(buf)
|
||||
for (let i = 0; i < bin.length; i++) u8[i] = bin.charCodeAt(i)
|
||||
return new Float32Array(buf)
|
||||
}
|
||||
|
||||
function b64ToUint32(b64: string): Uint32Array {
|
||||
const bin = atob(b64)
|
||||
const buf = new ArrayBuffer(bin.length)
|
||||
const u8 = new Uint8Array(buf)
|
||||
for (let i = 0; i < bin.length; i++) u8[i] = bin.charCodeAt(i)
|
||||
return new Uint32Array(buf)
|
||||
}
|
||||
|
||||
// ---- STEP/STP loader (geometry JSON, pre-processed server-side) ----------
|
||||
|
||||
async function loadStepGeometry(): Promise<void> {
|
||||
@@ -193,28 +211,64 @@ async function loadStepGeometry(): Promise<void> {
|
||||
throw new Error(`Could not load geometry (HTTP ${res.status})`)
|
||||
}
|
||||
|
||||
setLoading('Building 3D scene…')
|
||||
const data = await res.json() as GeometryFile
|
||||
// Stream the response body so there is visible activity during large downloads
|
||||
let text: string
|
||||
if (res.body) {
|
||||
const reader = res.body.getReader()
|
||||
const chunks: Uint8Array[] = []
|
||||
let loaded = 0
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
chunks.push(value)
|
||||
loaded += value.byteLength
|
||||
setLoading(`Downloading geometry… ${Math.round(loaded / 1024)} KB`)
|
||||
}
|
||||
setLoading('Parsing geometry…')
|
||||
text = new TextDecoder().decode(
|
||||
chunks.reduce((acc, c) => { const m = new Uint8Array(acc.length + c.length); m.set(acc); m.set(c, acc.length); return m }, new Uint8Array(0))
|
||||
)
|
||||
} else {
|
||||
text = await res.text()
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const data = JSON.parse(text) as GeometryFile & { version: number; meshes: any[] }
|
||||
|
||||
if (!data.meshes || data.meshes.length === 0) {
|
||||
throw new Error('Geometry file contains no meshes.')
|
||||
}
|
||||
|
||||
setLoading('Building 3D scene…')
|
||||
const group = new THREE.Group()
|
||||
const isV2 = data.version >= 2
|
||||
|
||||
for (const mesh of data.meshes) {
|
||||
const geo = new THREE.BufferGeometry()
|
||||
geo.setAttribute('position', new THREE.Float32BufferAttribute(mesh.positions, 3))
|
||||
|
||||
if (mesh.normals && mesh.normals.length > 0) {
|
||||
geo.setAttribute('normal', new THREE.Float32BufferAttribute(mesh.normals, 3))
|
||||
let positions: Float32Array
|
||||
let normals: Float32Array | null
|
||||
let indices: Uint32Array | null
|
||||
|
||||
if (isV2) {
|
||||
positions = b64ToFloat32(mesh.positions)
|
||||
normals = mesh.normals ? b64ToFloat32(mesh.normals) : null
|
||||
indices = mesh.indices ? b64ToUint32(mesh.indices) : null
|
||||
} else {
|
||||
// Legacy v1: plain number arrays
|
||||
positions = new Float32Array(mesh.positions as number[])
|
||||
normals = mesh.normals ? new Float32Array(mesh.normals as number[]) : null
|
||||
indices = mesh.indices ? new Uint32Array(mesh.indices as number[]) : null
|
||||
}
|
||||
|
||||
if (mesh.indices && mesh.indices.length > 0) {
|
||||
geo.setIndex(new THREE.Uint32BufferAttribute(mesh.indices, 1))
|
||||
geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3))
|
||||
if (normals && normals.length > 0) {
|
||||
geo.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3))
|
||||
}
|
||||
|
||||
if (!mesh.normals || mesh.normals.length === 0) {
|
||||
if (indices && indices.length > 0) {
|
||||
geo.setIndex(new THREE.Uint32BufferAttribute(indices, 1))
|
||||
}
|
||||
if (!normals || normals.length === 0) {
|
||||
geo.computeVertexNormals()
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export interface GeometryMesh {
|
||||
positions: number[]
|
||||
normals: number[] | null
|
||||
indices: number[]
|
||||
positions: string // base64-encoded Float32Array
|
||||
normals: string | null // base64-encoded Float32Array
|
||||
indices: string // base64-encoded Uint32Array
|
||||
color: [number, number, number] | null
|
||||
}
|
||||
|
||||
export interface GeometryFile {
|
||||
version: 1
|
||||
version: 2
|
||||
sourceFile: string
|
||||
meshCount: number
|
||||
meshes: GeometryMesh[]
|
||||
@@ -45,15 +45,20 @@ export async function convertStepFile(
|
||||
}
|
||||
|
||||
const geo: GeometryFile = {
|
||||
version: 1,
|
||||
version: 2,
|
||||
sourceFile: path.basename(inputPath),
|
||||
meshCount: result.meshes.length,
|
||||
meshes: result.meshes.map(mesh => ({
|
||||
positions: Array.from(mesh.attributes.position.array),
|
||||
normals: mesh.attributes.normal ? Array.from(mesh.attributes.normal.array) : null,
|
||||
indices: Array.from(mesh.index.array),
|
||||
meshes: result.meshes.map(mesh => {
|
||||
const pos = mesh.attributes.position.array
|
||||
const nor = mesh.attributes.normal?.array
|
||||
const idx = mesh.index.array
|
||||
return {
|
||||
positions: Buffer.from(pos.buffer, pos.byteOffset, pos.byteLength).toString('base64'),
|
||||
normals: nor ? Buffer.from(nor.buffer, nor.byteOffset, nor.byteLength).toString('base64') : null,
|
||||
indices: Buffer.from(idx.buffer, idx.byteOffset, idx.byteLength).toString('base64'),
|
||||
color: mesh.color ? [mesh.color[0], mesh.color[1], mesh.color[2]] : null,
|
||||
})),
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
fs.writeFileSync(outputPath, JSON.stringify(geo))
|
||||
|
||||
@@ -60,8 +60,10 @@ export function thumbnailOutputPath(uploadsDir: string, modelId: number | bigint
|
||||
export function geometryToTriangles(geo: GeometryFile): ThumbTri[] {
|
||||
const tris: ThumbTri[] = []
|
||||
for (const mesh of geo.meshes) {
|
||||
const pos = mesh.positions
|
||||
const idx = mesh.indices
|
||||
const posBuf = Buffer.from(mesh.positions, 'base64')
|
||||
const idxBuf = Buffer.from(mesh.indices, 'base64')
|
||||
const pos = new Float32Array(posBuf.buffer, posBuf.byteOffset, posBuf.byteLength / 4)
|
||||
const idx = new Uint32Array(idxBuf.buffer, idxBuf.byteOffset, idxBuf.byteLength / 4)
|
||||
const r = mesh.color ? Math.round(mesh.color[0] * 255) : 136
|
||||
const g = mesh.color ? Math.round(mesh.color[1] * 255) : 153
|
||||
const b = mesh.color ? Math.round(mesh.color[2] * 255) : 170
|
||||
|
||||
Reference in New Issue
Block a user