@volter/blender-engine 0.1.55 → 0.1.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -79,6 +79,8 @@ export const ATTRIBUTE_LAYOUT: Record<AttributeType, { size: number }> = {
|
|
|
79
79
|
export interface MeshColumns {
|
|
80
80
|
/** 3 * nv */
|
|
81
81
|
co: Float32Array | Float64Array;
|
|
82
|
+
/** Blender's tessellation, triangle corner indices in face order. */
|
|
83
|
+
cornerTri?: Uint32Array | undefined;
|
|
82
84
|
/** Blender-evaluated normals, 3 per corner, independent of UV splits. */
|
|
83
85
|
cornerNormal?: Float32Array | undefined;
|
|
84
86
|
/** 3 * nv, a deformed mesh's Generated coordinates in Blender's stored
|
|
@@ -313,8 +313,113 @@ export function geometryFromDrawArrays(
|
|
|
313
313
|
return geometry;
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
+
/** Blender already supplied corner normals and triangulation. Read those
|
|
317
|
+
* columns directly: no polygon triangulation, adjacency graph, vector objects
|
|
318
|
+
* or stringified floating-point keys. Two passes size final buffers exactly. */
|
|
319
|
+
function drawNativeColumns(c: MeshColumns, hash: string): DrawArrays {
|
|
320
|
+
const triangles = c.cornerTri!, normal = c.cornerNormal!;
|
|
321
|
+
const nv = c.co.length / 3, nc = c.corner.length, nf = c.faceStart.length - 1;
|
|
322
|
+
const maps = c.attributes.filter(a => a.type === 'FLOAT2' && a.domain === 'CORNER');
|
|
323
|
+
const mapName = c.renderUv ?? c.activeUv ?? maps[0]?.name;
|
|
324
|
+
const activeMap = maps.findIndex(a => a.name === mapName);
|
|
325
|
+
if (mapName && activeMap < 0) throw new Error(`Runtime UV map ${mapName} is missing`);
|
|
326
|
+
const attributes = graphAttributeLayers(c.attributes, data => data as ArrayLike<number>);
|
|
327
|
+
const width = 3 + maps.length * 2 + attributes.length * 4;
|
|
328
|
+
const scratch = new Float32Array(width), comparison = new Float32Array(width);
|
|
329
|
+
const bits = new Uint32Array(scratch.buffer), comparisonBits = new Uint32Array(comparison.buffer);
|
|
330
|
+
const faces = new Uint32Array(nc), cornerIndex = new Uint32Array(nc);
|
|
331
|
+
const representative = new Uint32Array(nc), links = new Uint32Array(nc);
|
|
332
|
+
const used = new Uint8Array(nv);
|
|
333
|
+
let tableSize = 1;
|
|
334
|
+
while (tableSize < Math.max(1, nc * 2)) tableSize *= 2;
|
|
335
|
+
const buckets = new Uint32Array(tableSize);
|
|
336
|
+
const read = (corner: number, face: number, vertex: number, target: Float32Array): void => {
|
|
337
|
+
let offset = 0;
|
|
338
|
+
for (let k = 0; k < 3; k++) target[offset++] = normal[corner * 3 + k]!;
|
|
339
|
+
for (const map of maps) for (let k = 0; k < 2; k++) target[offset++] = Number(map.data[corner * 2 + k]);
|
|
340
|
+
for (const layer of attributes) {
|
|
341
|
+
const value = layer.read(vertex, corner, face);
|
|
342
|
+
for (let k = 0; k < 4; k++) target[offset++] = Number(value[k]);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
let count = 0;
|
|
346
|
+
for (let face = 0; face < nf; face++) {
|
|
347
|
+
for (let corner = c.faceStart[face]!; corner < c.faceStart[face + 1]!; corner++) {
|
|
348
|
+
const vertex = c.corner[corner]!;
|
|
349
|
+
if (vertex >= nv) throw new Error('Native mesh corner references a missing vertex');
|
|
350
|
+
faces[corner] = face; used[vertex] = 1;
|
|
351
|
+
read(corner, face, vertex, scratch);
|
|
352
|
+
let digest = Math.imul(vertex ^ 2166136261, 16777619);
|
|
353
|
+
for (const word of bits) digest = Math.imul(digest ^ word, 16777619);
|
|
354
|
+
const bucket = (digest >>> 0) & (tableSize - 1);
|
|
355
|
+
let found = -1;
|
|
356
|
+
for (let entry = buckets[bucket]!; entry; entry = links[entry - 1]!) {
|
|
357
|
+
const index = entry - 1, previous = representative[index]!;
|
|
358
|
+
if (c.corner[previous] !== vertex) continue;
|
|
359
|
+
read(previous, faces[previous]!, vertex, comparison);
|
|
360
|
+
let equal = true;
|
|
361
|
+
for (let k = 0; k < width; k++) if (bits[k] !== comparisonBits[k]) { equal = false; break; }
|
|
362
|
+
if (equal) { found = index; break; }
|
|
363
|
+
}
|
|
364
|
+
if (found < 0) {
|
|
365
|
+
found = count++; representative[found] = corner;
|
|
366
|
+
links[found] = buckets[bucket]!; buckets[bucket] = found + 1;
|
|
367
|
+
}
|
|
368
|
+
cornerIndex[corner] = found;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
let loose = 0; for (const flag of used) if (!flag) loose++;
|
|
372
|
+
const total = count + loose;
|
|
373
|
+
const positions = new Float32Array(total * 3), normals = new Float32Array(total * 3);
|
|
374
|
+
const sourceVertex = new Uint32Array(total);
|
|
375
|
+
const uvLayers = maps.map(map => ({ name: map.name, data: new Float32Array(total * 2) }));
|
|
376
|
+
const attributeLayers = attributes.map(layer => ({ name: layer.name, data: new Float32Array(total * 4) }));
|
|
377
|
+
const orco = c.orco ? new Float32Array(total * 3) : null;
|
|
378
|
+
const position = (index: number, vertex: number): void => {
|
|
379
|
+
sourceVertex[index] = vertex;
|
|
380
|
+
for (let k = 0; k < 3; k++) {
|
|
381
|
+
positions[index * 3 + k] = c.co[vertex * 3 + k]!;
|
|
382
|
+
if (orco) orco[index * 3 + k] = c.orco![vertex * 3 + k]! * 0.5 + 0.5;
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
for (let index = 0; index < count; index++) {
|
|
386
|
+
const corner = representative[index]!, vertex = c.corner[corner]!;
|
|
387
|
+
position(index, vertex); read(corner, faces[corner]!, vertex, scratch);
|
|
388
|
+
normals.set(scratch.subarray(0, 3), index * 3);
|
|
389
|
+
let offset = 3;
|
|
390
|
+
for (const layer of uvLayers) { layer.data.set(scratch.subarray(offset, offset + 2), index * 2); offset += 2; }
|
|
391
|
+
for (const layer of attributeLayers) { layer.data.set(scratch.subarray(offset, offset + 4), index * 4); offset += 4; }
|
|
392
|
+
}
|
|
393
|
+
let index = count;
|
|
394
|
+
for (let vertex = 0; vertex < nv; vertex++) if (!used[vertex]) {
|
|
395
|
+
position(index, vertex);
|
|
396
|
+
for (let layer = 0; layer < attributes.length; layer++) attributeLayers[layer]!.data.set(attributes[layer]!.read(vertex, undefined, undefined), index * 4);
|
|
397
|
+
index++;
|
|
398
|
+
}
|
|
399
|
+
const indices = new Uint32Array(triangles.length), groups: DrawArrays['groups'] = [];
|
|
400
|
+
let offset = 0;
|
|
401
|
+
for (let face = 0; face < nf; face++) {
|
|
402
|
+
const start = c.faceStart[face]!, end = c.faceStart[face + 1]!;
|
|
403
|
+
const length = Math.max(0, end - start - 2) * 3;
|
|
404
|
+
if (offset + length > triangles.length) throw new Error('Native mesh triangulation is incomplete');
|
|
405
|
+
for (let j = 0; j < length; j++) {
|
|
406
|
+
const corner = triangles[offset + j]!;
|
|
407
|
+
if (corner < start || corner >= end) throw new Error('Native triangle references a different face');
|
|
408
|
+
indices[offset + j] = cornerIndex[corner]!;
|
|
409
|
+
}
|
|
410
|
+
const materialIndex = c.material[face]!, previous = groups.at(-1);
|
|
411
|
+
if (previous?.materialIndex === materialIndex) previous.count += length;
|
|
412
|
+
else groups.push({ start: offset, count: length, materialIndex });
|
|
413
|
+
offset += length;
|
|
414
|
+
}
|
|
415
|
+
if (offset !== triangles.length) throw new Error('Native mesh triangulation has extra triangles');
|
|
416
|
+
return { positions, normals, sourceVertex, uv: activeMap < 0 ? null : uvLayers[activeMap]!.data,
|
|
417
|
+
uvLayers, attributeLayers, orco, indices, groups, hash };
|
|
418
|
+
}
|
|
419
|
+
|
|
316
420
|
/** The worker's door: the mesh store's columns, drawn into buffers. */
|
|
317
421
|
export function drawArraysFromColumns(c: MeshColumns, hash: string): DrawArrays {
|
|
422
|
+
if (c.cornerTri && c.cornerNormal) return drawNativeColumns(c, hash);
|
|
318
423
|
const nv = c.co.length / 3;
|
|
319
424
|
const maps = c.attributes.filter((a) => a.type === 'FLOAT2' && a.domain === 'CORNER');
|
|
320
425
|
const mapName = c.renderUv ?? c.activeUv ?? maps[0]?.name;
|
|
@@ -186,6 +186,7 @@ const columnsSchema = z
|
|
|
186
186
|
.object({
|
|
187
187
|
co: z.union([z.instanceof(Float32Array), z.instanceof(Float64Array)]),
|
|
188
188
|
cornerNormal: z.instanceof(Float32Array).optional(),
|
|
189
|
+
cornerTri: z.instanceof(Uint32Array).optional(),
|
|
189
190
|
orco: z.instanceof(Float32Array).optional(),
|
|
190
191
|
faceStart: z.instanceof(Uint32Array),
|
|
191
192
|
corner: z.instanceof(Uint32Array),
|