@needle-tools/gltf-build-pipeline 2.8.0-alpha.1 → 2.8.1-alpha.0

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,15 @@ 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
+ ## [2.8.1-alpha.0] - 2024-03-26
8
+ - Fix: Issue where cache key gets longer depending on the primitive count
9
+ - Fix: Cache key not taking index buffer into account
10
+ - Fix: Caching of mesh LODs did clone shared mesh attributes for every primitive instead of re-using previous clones
11
+
12
+ ## [2.8.0-alpha.2] - 2024-03-04
13
+ - change: use webp compression for textures smaller than 32x32
14
+ - fix: webp compression for small textures (64x64)
15
+
7
16
  ## [2.8.0-alpha.0] - 2024-02-27
8
17
  - change: `product` usecase now uses webp compression for 'normalTexture' and 'metallicRoughnessTexture' textures
9
18
 
@@ -202,8 +202,9 @@ export function getHash(property, level = 0) {
202
202
  const weights = property.getWeights();
203
203
  hash += hashObject(weights);
204
204
  const primitives = property.listPrimitives();
205
- for (const prim of primitives)
206
- hashStr += getHash(prim, level + 1);
205
+ for (const prim of primitives) {
206
+ hash += hashString(getHash(prim, level + 1));
207
+ }
207
208
  }
208
209
  else if (property instanceof Primitive) {
209
210
  const indices = property.getIndices();
@@ -222,11 +223,19 @@ export function getHash(property, level = 0) {
222
223
  targets,
223
224
  semantics: semanticsNames
224
225
  });
226
+ if (indices) {
227
+ const indexBuffer = indices.getArray();
228
+ if (indexBuffer) {
229
+ hash += hashBuffer(indexBuffer.buffer, indexBuffer.byteOffset, indexBuffer.byteLength);
230
+ }
231
+ }
225
232
  // We can not pass the whole buffer into hashObject since very large meshes will cause errors when trying to stringify
226
233
  // See https://linear.app/needle/issue/NE-6414
227
234
  for (const sem of semantics) {
228
- const arr = property.getAttribute(sem).getArray();
229
- hash += hashBuffer(arr.buffer, arr.byteOffset, arr.byteLength);
235
+ const arr = property.getAttribute(sem)?.getArray();
236
+ if (arr) {
237
+ hash += hashBuffer(arr.buffer, arr.byteOffset, arr.byteLength);
238
+ }
230
239
  }
231
240
  }
232
241
  }
@@ -954,7 +954,7 @@ class MeshIndexResolver extends PointerResolver {
954
954
  if (parent instanceof Node) {
955
955
  obj = parent.getMesh();
956
956
  if (obj) {
957
- console.log(`INFO: resolved mesh \"${this.inputObject.getName()}\" from parent node \"${parent.getName()}\"`);
957
+ this.document.getLogger().info(`INFO: resolved mesh \"${this.inputObject.getName()}\" from parent node \"${parent.getName()}\"`);
958
958
  break;
959
959
  }
960
960
  }
@@ -850,27 +850,14 @@ async function createMeshLod(inputFileName, doc, basePath, level, mesh, meshInfo
850
850
  }
851
851
  const tasks = new Array();
852
852
  // let didRunSimplification = false;
853
+ const clonedAttributes = new Map();
854
+ debugger;
853
855
  for (const prim of primitives) {
854
856
  const newPrim = newDoc.createPrimitive();
855
857
  newMesh.addPrimitive(newPrim);
856
858
  newPrim.setName(prim.getName());
857
859
  newPrim.setMode(prim.getMode());
858
860
  newPrim.setMaterial(newMat);
859
- const semantics = prim.listSemantics();
860
- for (let i = 0; i < semantics.length; i++) {
861
- const attr = prim.getAttribute(semantics[i]);
862
- if (attr) {
863
- const newAttr = newDoc.createAccessor();
864
- newAttr.setArray(attr.getArray());
865
- newAttr.setType(attr.getType());
866
- newAttr.setSparse(attr.getSparse());
867
- newAttr.setNormalized(attr.getNormalized());
868
- newPrim.setAttribute(semantics[i], newAttr);
869
- }
870
- else {
871
- logger.warn(`[${NAME}] Missing attribute ${semantics[i]} in mesh LOD ${level}`);
872
- }
873
- }
874
861
  const indices = prim.getIndices();
875
862
  if (indices) {
876
863
  const newIndices = newDoc.createAccessor();
@@ -880,6 +867,28 @@ async function createMeshLod(inputFileName, doc, basePath, level, mesh, meshInfo
880
867
  newIndices.setNormalized(indices.getNormalized());
881
868
  newPrim.setIndices(newIndices);
882
869
  }
870
+ const semantics = prim.listSemantics();
871
+ for (let i = 0; i < semantics.length; i++) {
872
+ const attr = prim.getAttribute(semantics[i]);
873
+ if (attr) {
874
+ if (clonedAttributes.has(attr)) {
875
+ const cloned = clonedAttributes.get(attr);
876
+ newPrim.setAttribute(semantics[i], cloned);
877
+ }
878
+ else {
879
+ const newAttr = newDoc.createAccessor();
880
+ newAttr.setArray(attr.getArray());
881
+ newAttr.setType(attr.getType());
882
+ newAttr.setSparse(attr.getSparse());
883
+ newAttr.setNormalized(attr.getNormalized());
884
+ newPrim.setAttribute(semantics[i], newAttr);
885
+ clonedAttributes.set(attr, newAttr);
886
+ }
887
+ }
888
+ else {
889
+ logger.warn(`[${NAME}] Missing attribute ${semantics[i]} in mesh LOD ${level}`);
890
+ }
891
+ }
883
892
  const targets = prim.listTargets();
884
893
  for (const target of targets) {
885
894
  const newTarget = newDoc.createPrimitiveTarget(target.getName());
@@ -964,7 +973,8 @@ async function createMeshLod(inputFileName, doc, basePath, level, mesh, meshInfo
964
973
  // add to cache
965
974
  if (cacheKey != undefined) {
966
975
  const bytes = readFileSync(outPath);
967
- logger.info(`[${NAME}] Add LOD ${level} to cache for \"${mesh.getName()}\" as ${cacheKey}`);
976
+ const sizeInMB = (bytes.byteLength / 1024 / 1024).toFixed(2);
977
+ logger.info(`[${NAME}] Add LOD ${level} to cache (${sizeInMB} MB) for \"${mesh.getName()}\" as ${cacheKey}`);
968
978
  addToCache(cacheKey, bytes);
969
979
  addMetaToCache(cacheKey, { meshInfo: newMeshInformation, density: density });
970
980
  }
@@ -147,6 +147,7 @@ async function processTexture(texture, index, document, logger, context) {
147
147
  else {
148
148
  let mimeType = "image/ktx2";
149
149
  let ktx2Format = undefined;
150
+ let webpOptions = {};
150
151
  const slots = listTextureSlots(texture);
151
152
  const size = texture.getSize();
152
153
  const isVRM = context.file?.toLowerCase().endsWith(".vrm");
@@ -166,13 +167,8 @@ async function processTexture(texture, index, document, logger, context) {
166
167
  }
167
168
  // for https://linear.app/needle/issue/NE-6464
168
169
  else if (size[0] <= 32 || size[1] <= 32) {
169
- if (canRunToktx) {
170
- mimeType = "image/ktx2";
171
- ktx2Format = "UASTC";
172
- }
173
- else {
174
- mimeType = "image/webp";
175
- }
170
+ // can not use UASTC because it's ALSO shitty, see issue above
171
+ mimeType = "image/webp";
176
172
  }
177
173
  // For product compression we automatically select webP if the texture is used in a PBR material
178
174
  // https://linear.app/needle/issue/NE-6458
@@ -191,6 +187,7 @@ async function processTexture(texture, index, document, logger, context) {
191
187
  await resizeTextureIfNecessary(texture);
192
188
  if (mimeType === "image/webp") {
193
189
  successFullyProcessed = await context.webPContext.process(index, texture, {
190
+ ...webpOptions,
194
191
  quality: .9,
195
192
  }, context);
196
193
  }
@@ -1,6 +1,7 @@
1
1
  import { Document, ILogger, Texture } from '@gltf-transform/core';
2
2
  import { NEEDLE_compression_texture_schema } from '../extensions/index.js';
3
3
  import { INeedleTextureTransformContext, NeedleTransformContext } from './needle_common.js';
4
+ import sharp from 'sharp';
4
5
  export interface NeedleWebPOptions {
5
6
  }
6
7
  export declare class NeedleWebPContext implements INeedleTextureTransformContext {
@@ -9,6 +10,6 @@ export declare class NeedleWebPContext implements INeedleTextureTransformContext
9
10
  private compressed;
10
11
  constructor(options: NeedleWebPOptions);
11
12
  prepare(document: Document): Promise<void>;
12
- process(index: number, texture: Texture, settings: Pick<NEEDLE_compression_texture_schema, "quality">, context: NeedleTransformContext): Promise<boolean>;
13
+ process(index: number, texture: Texture, settings: Pick<NEEDLE_compression_texture_schema, "quality"> & Partial<sharp.WebpOptions>, context: NeedleTransformContext): Promise<boolean>;
13
14
  finalize(document: Document): Promise<void>;
14
15
  }
@@ -13,7 +13,15 @@ export class NeedleWebPContext {
13
13
  this.logger = document.getLogger();
14
14
  }
15
15
  async process(index, texture, settings, context) {
16
+ const size = texture.getSize();
17
+ // if the texture is too small we will use lossless compression
18
+ let lossless = false;
19
+ if (size[0] < 64 || size[1] < 64) {
20
+ lossless = true;
21
+ }
16
22
  const options = {
23
+ lossless,
24
+ ...settings,
17
25
  quality: settings.quality,
18
26
  // effort: remap(options.effort, 100, 6),
19
27
  // lossless: options.lossless,
@@ -1 +1 @@
1
- export declare const version = "2.8.0-alpha.1";
1
+ export declare const version = "2.8.1-alpha.0";
@@ -1 +1 @@
1
- export const version = "2.8.0-alpha.1";
1
+ export const version = "2.8.1-alpha.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/gltf-build-pipeline",
3
- "version": "2.8.0-alpha.1",
3
+ "version": "2.8.1-alpha.0",
4
4
  "description": "Pipeline and tools for optimizing gltf files using gltf-transform and compression settings within glTF extensions",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",