@needle-tools/gltf-build-pipeline 1.5.0-alpha.7 → 1.5.0-alpha.9

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,12 @@ 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.5.0-alpha.9] - 2023-05-16
8
+ - add: start of VRM 1.0 support. Not implemented is the `VRMC_materials_mtoon` extension
9
+
10
+ ## [1.5.0-alpha.8] - 2023-05-16
11
+ - fix: issue where updating ktx compressed texture extension also flattened the path
12
+
7
13
  ## [1.5.0-alpha.7] - 2023-05-16
8
14
  - fix: external resources that are KTX2 compressed are now saved with a `.ktx2` extension
9
15
  - change: toktx now uses compression "auto" mode to select uastc or etc1s
@@ -1,9 +1,10 @@
1
1
  import { WriterContext, PropertyType } from '@gltf-transform/core';
2
2
  declare interface IExtensibleProperty {
3
3
  }
4
+ type DebugBreakPoint = boolean | "pointer:read";
4
5
  export type Options = {
5
6
  debug?: boolean;
6
- breakpoint?: boolean;
7
+ breakpoint?: DebugBreakPoint;
7
8
  write?: boolean;
8
9
  };
9
10
  export interface IOpaqueExtension {
@@ -26,6 +26,7 @@ export function createOpaqueExtension(name, types, opts) {
26
26
  console.log("READ " + EXTENSION_NAME);
27
27
  this.jsonPointerHandler = new JsonPointerHandler(this.document, EXTENSION_NAME);
28
28
  this.jsonPointerHandler.debug = debugLog;
29
+ this.jsonPointerHandler.breakPoint = opts.breakpoint;
29
30
  for (const type of types) {
30
31
  switch (type) {
31
32
  case PropertyType.ACCESSOR:
@@ -514,6 +515,7 @@ class JsonPointerHandler {
514
515
  document;
515
516
  map = new Map();
516
517
  debug = false;
518
+ breakPoint = false;
517
519
  constructor(document, id) {
518
520
  this.document = document;
519
521
  this.id = id;
@@ -524,7 +526,11 @@ class JsonPointerHandler {
524
526
  if (key !== undefined) {
525
527
  fullPath ??= "";
526
528
  fullPath += "/" + key;
529
+ if (this.breakPoint === "pointer:read") {
530
+ debugger;
531
+ }
527
532
  }
533
+ // if(this.debug && key === "colliders") debugger;
528
534
  switch (typeof value) {
529
535
  case 'object':
530
536
  if (value !== null && value !== undefined) {
@@ -570,7 +576,7 @@ class JsonPointerHandler {
570
576
  if (pointer) {
571
577
  if (this.debug)
572
578
  console.log("> Register pointer", this.id + "/" + fullPath + " : " + value);
573
- pointer?.register(obj, key);
579
+ pointer?.register(obj, key, fullPath);
574
580
  }
575
581
  else
576
582
  console.warn("WARN: failed registering pointer", fullPath, value);
@@ -597,13 +603,15 @@ class JsonPointerHandler {
597
603
  pointer.debug = this.debug;
598
604
  this.map.set(value, pointer);
599
605
  }
600
- pointer?.register(obj, key);
606
+ pointer?.register(obj, key, fullPath);
601
607
  }
602
608
  }
603
609
  }
604
610
  break;
605
611
  case 'number':
606
- if (key === "node" || key === "mesh") {
612
+ if (key === "node" || key === "mesh"
613
+ // VRM 1.0 spring bone center https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_springBone-1.0/schema/VRMC_springBone.spring.schema.json
614
+ || fullPath.match(/springs\/\d+\/center/g)) {
607
615
  const object = context.nodes[value];
608
616
  if (object) {
609
617
  const res = new NodeIndexResolver(this.document, object, context.jsonDoc.json.nodes[value]);
@@ -615,12 +623,17 @@ class JsonPointerHandler {
615
623
  pointer = new JsonPointer(null, res, value);
616
624
  }
617
625
  pointer.debug = this.debug;
618
- pointer.register(obj, key);
626
+ pointer.register(obj, key, fullPath);
619
627
  this.map.set(dictKey, pointer);
620
628
  }
621
629
  }
622
- // key texture is matching for example for VRM meta/texture (AvatarSample_A) and the regex is also for handling VRM material properties
623
- else if (key === "texture" || fullPath.match(/materialProperties\/\d+?\/textureProperties/g)) {
630
+ else if (
631
+ // key texture is matching for example for VRM meta/texture (AvatarSample_A)
632
+ key === "texture"
633
+ // VRM 0.0 material properties
634
+ || fullPath.match(/materialProperties\/\d+\/textureProperties/g)
635
+ // VRM 1.0 meta
636
+ || fullPath.match(/meta\/thumbnailImage/g)) {
624
637
  const index = value;
625
638
  // e.g. in meta.texture for VRM avatars it seems like we can have negative values
626
639
  if (index < 0) {
@@ -648,7 +661,7 @@ class JsonPointerHandler {
648
661
  pointer = new JsonPointer(null, res, value);
649
662
  }
650
663
  pointer.debug = this.debug;
651
- pointer.register(obj, key);
664
+ pointer.register(obj, key, fullPath);
652
665
  this.map.set(dictKey, pointer);
653
666
  if (ext) {
654
667
  ext.setReference(dictKey, object);
@@ -656,6 +669,8 @@ class JsonPointerHandler {
656
669
  }
657
670
  }
658
671
  break;
672
+ default:
673
+ break;
659
674
  }
660
675
  }
661
676
  resolveAndWrite(context) {
@@ -677,9 +692,11 @@ class JsonPointer {
677
692
  }
678
693
  _objects = [];
679
694
  _props = [];
680
- register(obj, prop) {
695
+ _debugInfo = [];
696
+ register(obj, prop, fullpath) {
681
697
  this._objects.push(obj);
682
698
  this._props.push(prop);
699
+ this._debugInfo.push(fullpath);
683
700
  }
684
701
  newPointer;
685
702
  resolve(context) {
@@ -694,18 +711,19 @@ class JsonPointer {
694
711
  this.newPointer = index;
695
712
  }
696
713
  if (this.debug)
697
- console.log(`RESOLVED ${this.oldPointer} to → ${this.newPointer}`);
714
+ console.log(`RESOLVED pointer ${this.oldPointer} to → ${this.newPointer}`);
698
715
  }
699
716
  else {
700
- console.log("ERR: failed to resolve pointer", this.pointerBase);
717
+ console.log("ERR: 🧨 FAILED to resolve pointer", this.pointerBase, ` used by ${this._debugInfo.join(", ")}`);
701
718
  }
702
719
  }
703
720
  write() {
704
721
  for (let i = 0; i < this._objects.length; i++) {
705
722
  const obj = this._objects[i];
706
723
  const prop = this._props[i];
707
- if (this.debug)
708
- console.log("< Resolved pointer", this.newPointer, "in", prop);
724
+ if (this.debug) {
725
+ console.log(`< Resolved pointer ${this._debugInfo[i]}: ${this.newPointer}`);
726
+ }
709
727
  obj[prop] = this.newPointer;
710
728
  }
711
729
  }
@@ -17,5 +17,12 @@ export const ALL_EXTENSIONS = [
17
17
  createOpaqueExtension("EXT_texture_exr", [PropertyType.TEXTURE], { ...opaqueOptions }),
18
18
  createOpaqueExtension("NEEDLE_editor", [PropertyType.NODE, PropertyType.MATERIAL], { ...opaqueOptions }),
19
19
  createOpaqueExtension("NEEDLE_gltf_dependencies", [PropertyType.ROOT], { ...opaqueOptions }),
20
+ // VRM 0.0
20
21
  createOpaqueExtension("VRM", [PropertyType.ROOT], { ...opaqueOptions }),
22
+ // VRM 1.0 https://vrm.dev/en/vrm1/changed/
23
+ createOpaqueExtension("VRMC_vrm", [PropertyType.ROOT], { ...opaqueOptions }),
24
+ createOpaqueExtension("VRMC_springBone", [PropertyType.ROOT], { ...opaqueOptions }),
25
+ createOpaqueExtension("VRMC_node_constraint", [PropertyType.NODE], { ...opaqueOptions }),
26
+ // TODO: mtoon materials don't work yet
27
+ // createOpaqueExtension("VRMC_materials_mtoon", [PropertyType.MATERIAL], { ...opaqueOptions, debug: true, breakpoint: "pointer:read" },),
21
28
  ];
@@ -76,8 +76,8 @@ export const needle_texture_transform = function (options) {
76
76
  });
77
77
  };
78
78
  async function processTexture(texture, index, document, logger, context) {
79
- const mimeType = texture.getMimeType();
80
- const isSupported = mimeType === "image/jpeg" || mimeType === "image/png" || mimeType === "image/webp";
79
+ const originalMimeType = texture.getMimeType();
80
+ const isSupported = originalMimeType === "image/jpeg" || originalMimeType === "image/png" || originalMimeType === "image/webp";
81
81
  // dont try to transform EXR or already processed textures
82
82
  if (!isSupported)
83
83
  return;
@@ -128,7 +128,10 @@ async function processTexture(texture, index, document, logger, context) {
128
128
  }
129
129
  }
130
130
  else {
131
- let mimetype = "image/ktx2";
131
+ let mimeType = "image/ktx2";
132
+ // const slots = listTextureSlots(texture);
133
+ // if (slots.includes("metallicRoughnessTexture"))
134
+ // mimeType = "image/webp";
132
135
  const isVRM = context.file?.toLowerCase().endsWith(".vrm");
133
136
  // TODO: need to make this into a function
134
137
  // const webpThreshold = 256; // this is the default threshold in progressive textures (needle_progressive.ts)
@@ -144,12 +147,14 @@ async function processTexture(texture, index, document, logger, context) {
144
147
  // If we have a texture cached we can use it
145
148
  // the default compression is toktx so we can safely use that for now
146
149
  if (cached) {
147
- texture.setImage(cached).setMimeType(mimetype);
150
+ texture.setImage(cached).setMimeType(mimeType);
148
151
  return;
149
152
  }
150
153
  await resizeTextureIfNecessary(texture);
151
154
  if (mimeType === "image/webp") {
152
- successFullyProcessed = await context.webPContext.process(index, texture, null);
155
+ successFullyProcessed = await context.webPContext.process(index, texture, {
156
+ quality: .9,
157
+ });
153
158
  }
154
159
  else if (canRunToktx) {
155
160
  // if no specific compression settings are provided we just use the toktx default settings
@@ -8,6 +8,7 @@ import fs from 'fs/promises';
8
8
  import os from 'os';
9
9
  import tmp from 'tmp';
10
10
  import pLimit from 'p-limit';
11
+ import { replaceExtension } from "../utils/fileutils.js";
11
12
  const NUM_CPUS = os.cpus().length || 1; // microsoft/vscode#112122
12
13
  let didLogEnvironmentInfo = false;
13
14
  export class NeedleToktxContext {
@@ -126,9 +127,12 @@ export class NeedleToktxContext {
126
127
  }
127
128
  }
128
129
  else if (slots) {
129
- const betterQuality = slots.includes("normalTexture") || slots.includes("metallicRoughnessTexture");
130
+ const isNormal = slots.includes("normalTexture");
131
+ const isMetallicRoughness = slots.includes("metallicRoughnessTexture");
132
+ const betterQuality = isNormal || isMetallicRoughness;
130
133
  // mode = useUASTC ? Mode.UASTC : Mode.ETC1S;
131
134
  mode = Mode.ETC1S;
135
+ // if (isMetallicRoughness) mode = Mode.UASTC;
132
136
  // https://github.khronos.org/KTX-Software/ktxtools/toktx.html
133
137
  quality = betterQuality ? 255 : 128;
134
138
  // ETC1S / BasisLZ compression level, an encoding speed vs. quality tradeoff. Range is [0,5], default is 1. Higher values are slower but give higher quality.
@@ -158,7 +162,7 @@ export class NeedleToktxContext {
158
162
  // If this is a texture that was loaded from a file, we need to update the URI
159
163
  if (texture.getURI()) {
160
164
  const oldUri = texture.getURI();
161
- const newUri = FileUtils.basename(oldUri) + '.ktx2';
165
+ const newUri = replaceExtension(oldUri, 'ktx2');
162
166
  texture.setURI(newUri);
163
167
  }
164
168
  return true;
@@ -8,6 +8,6 @@ export declare class NeedleWebPContext implements INeedleTextureTransformContext
8
8
  logger: ILogger;
9
9
  constructor(options: NeedleWebPOptions);
10
10
  prepare(document: Document): Promise<void>;
11
- process(index: number, texture: Texture, settings: NEEDLE_compression_texture_schema): Promise<boolean>;
11
+ process(index: number, texture: Texture, settings: Pick<NEEDLE_compression_texture_schema, "quality">): Promise<boolean>;
12
12
  finalize(document: Document): Promise<void>;
13
13
  }
@@ -1,2 +1,3 @@
1
1
  export declare type GLTFFileCallback = (file: string) => void | Promise<void>;
2
2
  export declare function foreachGLTF(args: string | string[], callback: GLTFFileCallback): Promise<void>;
3
+ export declare function replaceExtension(filepath: string, newExtension: string): string;
@@ -113,3 +113,8 @@ async function testEntry(entry, callback, context) {
113
113
  }
114
114
  }
115
115
  }
116
+ export function replaceExtension(filepath, newExtension) {
117
+ const parts = filepath.split(".");
118
+ parts.pop();
119
+ return parts.join(".") + "." + newExtension;
120
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/gltf-build-pipeline",
3
- "version": "1.5.0-alpha.7",
3
+ "version": "1.5.0-alpha.9",
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",