@needle-tools/gltf-build-pipeline 1.4.1-beta.10 → 1.4.1-beta.11

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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,11 @@ All notable changes to this package will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [1.4.1-beta.11] - 2023-03-20
8
+ - add: NEEDLE_progressive LOD meshes morph targets data
9
+ - fix: NEEDLE_progressive should check if simplifcation did result in improved meshes (> 32 verts removed && > 5 % improvement) and otherwise discard LODs (and the whole extension if no improvement was made)
10
+ - change: Unity NEEDLE_progressive logs
11
+
7
12
  ## [1.4.1-beta.10] - 2023-03-19
8
13
  - change: NEEDLE_progressive calculates density per mesh level
9
14
  - change: NEEDLE_progressive `uri` field is removed and added `lod` array of objects
@@ -1,4 +1,4 @@
1
1
  import { Transform, Document, Primitive, Mesh } from "@gltf-transform/core";
2
2
  import { SimplifyOptions, WeldOptions } from "@gltf-transform/functions";
3
3
  export declare const needle_mesh_transform: (_options: SimplifyOptions) => Transform;
4
- export declare function compressPrimitive(document: Document, mesh: Mesh, prim: Primitive, index: number, weldOptions: Required<WeldOptions>, meshOptions: Required<SimplifyOptions>): Promise<void>;
4
+ export declare function compressPrimitive(document: Document, mesh: Mesh, prim: Primitive, index: number, weldOptions: Required<WeldOptions>, meshOptions: Required<SimplifyOptions>): Promise<Primitive>;
@@ -93,7 +93,7 @@ async function simplifierTransform(document, _context, _options) {
93
93
  }
94
94
  export async function compressPrimitive(document, mesh, prim, index, weldOptions, meshOptions) {
95
95
  const logger = document.getLogger();
96
- logger.debug(`[${NAME}]: Welding primitive[${index}] of mesh "${mesh.getName()}" → tolerance: ${weldOptions.tolerance}`);
96
+ logger.debug(`weld: primitive[${index}] of mesh "${mesh.getName()}" → tolerance: ${weldOptions.tolerance}`);
97
97
  weldPrimitive(document, prim, weldOptions);
98
98
  // make sure we wait for the mesh simplifier to be ready
99
99
  if (meshOptions.simplifier && meshOptions.simplifier?.ready instanceof Promise) {
@@ -109,8 +109,8 @@ export async function compressPrimitive(document, mesh, prim, index, weldOptions
109
109
  if (!res)
110
110
  return;
111
111
  }
112
- logger.debug(`[${NAME}]: Simplifying primitive[${index}] of mesh "${mesh.getName()}" → ratio: ${meshOptions.ratio}, error: ${meshOptions.error}, lockBorder: ${meshOptions.lockBorder}`);
113
- simplifyPrimitive(document, prim, meshOptions);
112
+ logger.debug(`simplify: primitive[${index}] of mesh "${mesh.getName()}" → ratio: ${meshOptions.ratio}, error: ${meshOptions.error}, lockBorder: ${meshOptions.lockBorder}`);
113
+ return simplifyPrimitive(document, prim, meshOptions);
114
114
  }
115
115
  // function updatePrimitive(prim: Primitive, simplifier: any, positionArray, dstIndicesArray) {
116
116
  // const position = prim.getAttribute('POSITION')!;
@@ -306,7 +306,7 @@ export function make_progressive_meshes(filePath, _options, opts) {
306
306
  await MeshoptSimplifier.ready;
307
307
  const logger = doc.getLogger();
308
308
  const filename = path.basename(filePath);
309
- console.log(`> [${NAME}] start generating LOD meshes for \"${filename}\"`);
309
+ console.log(`[${NAME}]: Begin generating progressive meshes for \"${filename}\"`);
310
310
  let index = 0;
311
311
  // the lowest LOD (last entry) will be saved in the original file
312
312
  const MAX_LODS = 6;
@@ -333,12 +333,13 @@ export function make_progressive_meshes(filePath, _options, opts) {
333
333
  const visited = new Map();
334
334
  const meshes = doc.getRoot().listMeshes();
335
335
  for (const mesh of meshes) {
336
- const meshInformation = getMeshInformation(mesh);
337
- const { totalVertexCount, maxVertexCount } = meshInformation;
336
+ /** The size and vertex count of the original mesh */
337
+ const originalMeshInformation = getMeshInformation(mesh);
338
+ const { totalVertexCount: originalTotalVertexCount, maxVertexCount: originalMaxVertexCount } = originalMeshInformation;
338
339
  let settings = mesh.getExtension(NEEDLE_progressive_mesh_settings.EXTENSION_NAME);
339
340
  if (!settings) {
340
- if (maxVertexCount < 32) {
341
- logger.debug(`[${NAME}] Skipping, no settings found and no primitive with more than 32 vertices. mesh[${index}] has ${totalVertexCount} vertices (max: ${maxVertexCount})`);
341
+ if (originalMaxVertexCount < 32) {
342
+ logger.debug(`[${NAME}] Skipping, no settings found and no primitive with more than 32 vertices. mesh[${index}] has ${originalTotalVertexCount} vertices (max: ${originalMaxVertexCount})`);
342
343
  continue;
343
344
  }
344
345
  else {
@@ -346,7 +347,7 @@ export function make_progressive_meshes(filePath, _options, opts) {
346
347
  guid: generateGuid(path + "_mesh_" + index.toString()),
347
348
  generateLods: true,
348
349
  };
349
- logger.debug(`[${NAME}] No settings found for mesh[${index}] with ${totalVertexCount} vertices (max: ${maxVertexCount}) - will use default settings (guid: ${settings.guid})`);
350
+ logger.debug(`[${NAME}] No settings found for mesh[${index}] with ${originalTotalVertexCount} vertices (max: ${originalMaxVertexCount}) - will use default settings (guid: ${settings.guid})`);
350
351
  }
351
352
  }
352
353
  else if (settings.generateLods === false) {
@@ -371,12 +372,13 @@ export function make_progressive_meshes(filePath, _options, opts) {
371
372
  /** async simplification tasks... */
372
373
  const tasks = new Array();
373
374
  if (needsToGenerateLODs) {
375
+ console.log(`[${NAME}] Begin generating mesh LODs for \"${mesh.getName()}\"`);
374
376
  // produce LODS, skip the last level because it will be saved in the original file
375
377
  for (let level = 0; level < LODS.length - 1; level++) {
376
378
  const lod = LODS[level];
377
379
  const guid = settings.guid;
378
380
  // TODO: check if the LOD level did actually change the mesh - otherwise we can just skip generating lower resolutions
379
- const lodTask = createMeshLod(doc, outputDirectory, level, mesh, meshInformation, guid, lod, opts);
381
+ const lodTask = createMeshLod(doc, outputDirectory, level, mesh, originalMeshInformation, guid, lod, opts);
380
382
  lodTask.then(res => {
381
383
  if (res?.success) {
382
384
  generatedLODInfos[level] = res;
@@ -406,11 +408,11 @@ export function make_progressive_meshes(filePath, _options, opts) {
406
408
  let shouldOmitLevel = false;
407
409
  // if the mesh size on disc is identical to the last level, we can omit this level
408
410
  if (sizeOnDisc <= 0) {
409
- console.log(`[${NAME}] mesh LOD ${level} size on disc is 0 - level will be omitted (if this mesh is is taken from cache it can mean that the LOD level was omitted before) \"${lodInfo.path}\"`);
411
+ console.log(`[${NAME}] Flag mesh LOD ${level}: size on disc is 0 → level will be omitted (if this mesh is is taken from cache it can mean that the LOD level was omitted before). \"${mesh.getName()}\" at \"${lodInfo.path}\"`);
410
412
  shouldOmitLevel = true;
411
413
  }
412
414
  else if (diff == 0) {
413
- console.log(`[${NAME}] mesh LOD ${level} size on disc did not change compared to previous level - level will be omitted \"${lodInfo.path}\"`);
415
+ console.log(`[${NAME}] Flag mesh LOD ${level}: size on disc did not change compared to previous level → level will be omitted. \"${mesh.getName()}\" at \"${lodInfo.path}\"`);
414
416
  shouldOmitLevel = true;
415
417
  }
416
418
  else if (!shouldOmitLevel) {
@@ -419,13 +421,13 @@ export function make_progressive_meshes(filePath, _options, opts) {
419
421
  const lastMeshInfo = generatedLODInfos[i - 1]?.lodMeshInfo;
420
422
  if (meshInfo && lastMeshInfo) {
421
423
  const vertexDifference = lastMeshInfo.totalVertexCount - meshInfo.totalVertexCount;
422
- const diffPercent = vertexDifference / lastMeshInfo.totalVertexCount;
424
+ const diff01 = vertexDifference / lastMeshInfo.totalVertexCount;
423
425
  if (vertexDifference < 0) {
424
- console.log(`WARN: [${NAME}] mesh LOD ${level} did increase vertex count (${-vertexDifference.toFixed(0)} vertices, ${(-diffPercent * 100).toFixed(4)}%) - level will be omitted \"${lodInfo.path}\" ${JSON.stringify(lastMeshInfo)} → ${JSON.stringify(meshInfo)}`);
426
+ console.log(`WARN: ❌ [${NAME}] Flag mesh LOD ${level}: vertex count did increase (${-vertexDifference.toFixed(0)} vertices, ${(-diff01 * 100).toFixed(4)}%) → LOD ${level} will be omitted. \"${mesh.getName()}\" at \"${lodInfo.path}\" ${JSON.stringify(lastMeshInfo)} → ${JSON.stringify(meshInfo)}`);
425
427
  shouldOmitLevel = true;
426
428
  }
427
- else if (vertexDifference < 32 || diffPercent < 0.05) {
428
- console.log(`[${NAME}] mesh LOD ${level} change was too low (${-vertexDifference.toFixed(0)} vertices, ${(-diffPercent * 100).toFixed(4)}%) - level will be omitted \"${lodInfo.path}\" ${JSON.stringify(lastMeshInfo)} → ${JSON.stringify(meshInfo)}`);
429
+ else if (vertexDifference < 32 || diff01 < 0.05) {
430
+ console.log(`[${NAME}] Flag mesh LOD ${level}: vertex count difference below threshold (removed ${vertexDifference.toFixed(0)} vertices, ${(-diff01 * 100).toFixed(4)}%) → LOD ${level} will be omitted. \"${mesh.getName()}\" at \"${lodInfo.path}\"`);
429
431
  shouldOmitLevel = true;
430
432
  }
431
433
  }
@@ -446,10 +448,9 @@ export function make_progressive_meshes(filePath, _options, opts) {
446
448
  }
447
449
  let maxDensity = 0;
448
450
  let primitiveIndex = 0;
451
+ const lodLevelInFile = generatedLODInfos.length;
449
452
  for (const prim of mesh.listPrimitives()) {
450
- const lodsCount = generatedLODInfos.length;
451
- const lastLod = LODS[lodsCount];
452
- console.log(`[${NAME}] ${mesh.getName()} LOD ${lodsCount}`);
453
+ const lastLod = LODS[lodLevelInFile];
453
454
  // TODO: this should be run after simplification but then we need to change the runtime to use the LOD 0 density
454
455
  const density = calculateMeshDensity(prim);
455
456
  maxDensity = Math.max(maxDensity, density);
@@ -459,8 +460,23 @@ export function make_progressive_meshes(filePath, _options, opts) {
459
460
  await Promise.all(tasks);
460
461
  tasks.length = 0;
461
462
  const { totalVertexCount: totalVertexCountAfter } = getMeshInformation(mesh);
463
+ const difference = originalTotalVertexCount - totalVertexCountAfter;
464
+ const diff01 = difference / originalTotalVertexCount;
462
465
  if (totalVertexCountAfter <= 0) {
463
- logger.error(`ERR: [${NAME}] Mesh[${index}] has no vertices after simplification: ${mesh.getName()}`);
466
+ logger.error(`ERR: [${NAME}]: Mesh[${index}] has no vertices after simplification: ${mesh.getName()}`);
467
+ }
468
+ else if (difference < 32 || diff01 <= 0.05) {
469
+ // the lowest LOD has the same vertex count as the original mesh → we don't need to write the extension and can also delete previously generated LODs
470
+ console.log(`[${NAME}]: Flag mesh LOD ${generatedLODInfos.length}: vertex count difference below threshold (removed ${difference} vertices, ${(-diff01 * 100).toFixed(2)} %) → will remove LODs. \"${mesh.getName()}\"`);
471
+ try {
472
+ for (const lodInfo of generatedLODInfos) {
473
+ rmSync(outputDirectory + "/" + lodInfo.path);
474
+ }
475
+ }
476
+ catch (err) {
477
+ logger.error(`ERR: [${NAME}] Failed to remove LOD files for \"${mesh.getName()}\"`);
478
+ }
479
+ continue;
464
480
  }
465
481
  if (generatedLODInfos.length > 0) {
466
482
  const model = progressiveExt.create({
@@ -483,7 +499,7 @@ export function make_progressive_meshes(filePath, _options, opts) {
483
499
  }
484
500
  }
485
501
  }
486
- console.log(`< [${NAME}] finished generating LOD meshes for \"${filename}\"`);
502
+ console.log(`[${NAME}]: Finished generating progressive meshes for \"${filename}\"`);
487
503
  });
488
504
  }
489
505
  /**
@@ -527,6 +543,10 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
527
543
  newMesh.setName(mesh.getName() + " LOD " + level);
528
544
  newNode.setMesh(newMesh);
529
545
  const newMat = newDoc.createMaterial();
546
+ const weights = mesh.getWeights();
547
+ if (weights) {
548
+ newMesh.setWeights(weights);
549
+ }
530
550
  const tasks = new Array();
531
551
  // let didRunSimplification = false;
532
552
  for (const prim of primitives) {
@@ -544,6 +564,9 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
544
564
  newAttr.setType(attr.getType());
545
565
  newPrim.setAttribute(semantics[i], newAttr);
546
566
  }
567
+ else {
568
+ console.warn(`[${NAME}] Missing attribute ${semantics[i]} in mesh LOD ${level}`);
569
+ }
547
570
  }
548
571
  const indices = prim.getIndices();
549
572
  if (indices) {
@@ -552,6 +575,21 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
552
575
  newIndices.setType(indices.getType());
553
576
  newPrim.setIndices(newIndices);
554
577
  }
578
+ const targets = prim.listTargets();
579
+ for (const target of targets) {
580
+ const newTarget = newDoc.createPrimitiveTarget(target.getName());
581
+ newPrim.addTarget(newTarget);
582
+ for (const attr of target.listSemantics()) {
583
+ const accessor = target.getAttribute(attr);
584
+ if (accessor) {
585
+ const newAccessor = newDoc.createAccessor();
586
+ newAccessor.setArray(accessor.getArray());
587
+ newAccessor.setType(accessor.getType());
588
+ newTarget.setAttribute(attr, newAccessor);
589
+ }
590
+ }
591
+ }
592
+ ;
555
593
  const extensions = prim.listExtensions();
556
594
  for (const ext of extensions) {
557
595
  const PropertyClass = ext.constructor;
@@ -603,7 +641,7 @@ async function createMeshLod(doc, basePath, level, mesh, meshInfo, guid, lodLeve
603
641
  }
604
642
  const newIo = new NodeIO();
605
643
  registerExtensions(newIo);
606
- console.log(`[${NAME}] Write mesh LOD ${level} with: ${JSON.stringify(lodLevel)} to \"${fileName}\"`);
644
+ console.log(`[${NAME}] Write mesh LOD ${level} with to \"${fileName}\"`);
607
645
  await newIo.write(outPath, newDoc);
608
646
  // const hasChanged = meshInfo.maxVertexCount == newMeshInformation.maxVertexCount && meshInfo.totalVertexCount == newMeshInformation.totalVertexCount;
609
647
  // add to cache
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/gltf-build-pipeline",
3
- "version": "1.4.1-beta.10",
3
+ "version": "1.4.1-beta.11",
4
4
  "description": "Pipeline and tools for optimizing gltf files using gltf-transform and compression settings within glTF extensions",
5
5
  "main": "index.mjs",
6
6
  "type": "module",