@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 +6 -0
- package/dist/extensions/NEEDLE_opaque/NEEDLE_opaque.d.ts +2 -1
- package/dist/extensions/NEEDLE_opaque/NEEDLE_opaque.js +30 -12
- package/dist/extensions/NEEDLE_opaque/index.js +7 -0
- package/dist/transforms/needle_texture_transform.js +10 -5
- package/dist/transforms/needle_toktx.js +6 -2
- package/dist/transforms/needle_webp.d.ts +1 -1
- package/dist/utils/fileutils.d.ts +1 -0
- package/dist/utils/fileutils.js +5 -0
- package/package.json +1 -1
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?:
|
|
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
|
-
|
|
623
|
-
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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
|
|
80
|
-
const isSupported =
|
|
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
|
|
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(
|
|
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,
|
|
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
|
|
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 =
|
|
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;
|
package/dist/utils/fileutils.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/gltf-build-pipeline",
|
|
3
|
-
"version": "1.5.0-alpha.
|
|
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",
|