@needle-tools/gltf-build-pipeline 1.5.0-alpha.8 → 1.6.0-alpha
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 +13 -0
- package/dist/cache/cache.d.ts +2 -1
- package/dist/cache/cache.js +8 -2
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +102 -0
- package/dist/config/index.d.ts +43 -0
- package/dist/config/index.js +125 -0
- package/dist/extensions/NEEDLE_opaque/NEEDLE_opaque.d.ts +2 -1
- package/dist/extensions/NEEDLE_opaque/NEEDLE_opaque.js +32 -14
- package/dist/extensions/NEEDLE_opaque/index.d.ts +2 -2
- package/dist/extensions/NEEDLE_opaque/index.js +7 -0
- package/dist/extensions/NEEDLE_progressive_mesh_settings/index.d.ts +4 -1
- package/dist/extensions/NEEDLE_progressive_texture_settings/index.d.ts +4 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/scripts/index.d.ts +1 -0
- package/dist/scripts/index.js +1 -0
- package/dist/scripts/pack-gltf.d.ts +12 -0
- package/dist/scripts/pack-gltf.js +119 -0
- package/dist/transforms/needle_common.d.ts +3 -1
- package/dist/transforms/needle_mesh_transform.d.ts +3 -1
- package/dist/transforms/needle_progressive.d.ts +3 -0
- package/dist/transforms/needle_progressive.js +9 -2
- package/dist/transforms/needle_texture_transform.d.ts +9 -1
- package/dist/transforms/needle_texture_transform.js +17 -11
- package/dist/transforms/needle_toktx.d.ts +2 -2
- package/dist/transforms/needle_toktx.js +15 -3
- package/dist/transforms/needle_webp.d.ts +2 -2
- package/dist/transforms/needle_webp.js +1 -1
- package/dist/utils/compression_utils.d.ts +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/stats.d.ts +8 -0
- package/dist/utils/stats.js +6 -0
- package/package.json +13 -9
- package/scripts/clear-caches.mjs +4 -0
- package/scripts/defaults.mjs +12 -0
- package/{fileutils.mjs → scripts/fileutils.mjs} +1 -1
- package/{index.mjs → scripts/index.mjs} +3 -3
- package/scripts/limit-caches.mjs +4 -0
- package/{make-progressive.mjs → scripts/make-progressive.mjs} +4 -2
- package/scripts/pack-gltf.mjs +101 -0
- package/clear-caches.mjs +0 -4
- package/defaults.mjs +0 -12
- package/limit-caches.mjs +0 -4
- package/pack-gltf.mjs +0 -189
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ 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.6.0-alpha] - 2023-05-27
|
|
8
|
+
- add: `gltf-build-pipeline` commandline support with `transform` and `compress` (pack-gltf) options.
|
|
9
|
+
|
|
10
|
+
## [1.5.0-alpha.12] - 2023-05-20
|
|
11
|
+
- add: pack-gltf now also runs `resample` for animations
|
|
12
|
+
- fix: nullreference error in accessing opaque debug options
|
|
13
|
+
|
|
14
|
+
## [1.5.0-alpha.10] - 2023-05-17
|
|
15
|
+
- add: `generateLods` option to progressive texture settings
|
|
16
|
+
|
|
17
|
+
## [1.5.0-alpha.9] - 2023-05-16
|
|
18
|
+
- add: start of VRM 1.0 support. Not implemented is the `VRMC_materials_mtoon` extension
|
|
19
|
+
|
|
7
20
|
## [1.5.0-alpha.8] - 2023-05-16
|
|
8
21
|
- fix: issue where updating ktx compressed texture extension also flattened the path
|
|
9
22
|
|
package/dist/cache/cache.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { ExtensibleProperty } from "@gltf-transform/core";
|
|
2
|
+
import { Logger } from "@donmccurdy/caporal";
|
|
2
3
|
/**
|
|
3
4
|
* Clear the cache.
|
|
4
5
|
* This will remove all files in the cache directory.
|
|
5
6
|
*/
|
|
6
|
-
export declare function clearCache(): void;
|
|
7
|
+
export declare function clearCache(logger?: Logger): void;
|
|
7
8
|
/**
|
|
8
9
|
* Ensure that the cache size does not exceed the given maximum size in MB.
|
|
9
10
|
* @param maxSizeMB The maximum size in MB.
|
package/dist/cache/cache.js
CHANGED
|
@@ -23,10 +23,13 @@ const buildPipelineVersion = getVersion(false);
|
|
|
23
23
|
* Clear the cache.
|
|
24
24
|
* This will remove all files in the cache directory.
|
|
25
25
|
*/
|
|
26
|
-
export function clearCache() {
|
|
26
|
+
export function clearCache(logger) {
|
|
27
27
|
const dir = getCacheDirectory(true);
|
|
28
28
|
fs.rmSync(dir, { recursive: true });
|
|
29
|
-
|
|
29
|
+
if (!logger)
|
|
30
|
+
console.log(`Cleared cache at ${dir}`);
|
|
31
|
+
else
|
|
32
|
+
logger.info(`Cleared cache at ${dir}`);
|
|
30
33
|
}
|
|
31
34
|
/**
|
|
32
35
|
* Ensure that the cache size does not exceed the given maximum size in MB.
|
|
@@ -223,6 +226,9 @@ function hashObject(obj) {
|
|
|
223
226
|
const buffer = obj.getArray();
|
|
224
227
|
return hashBuffer(buffer);
|
|
225
228
|
}
|
|
229
|
+
else if (obj == null) {
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
226
232
|
const str = JSON.stringify(obj);
|
|
227
233
|
return hashString(str);
|
|
228
234
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#! /usr/bin/env node
|
|
2
|
+
import { program } from '@donmccurdy/caporal';
|
|
3
|
+
import { packGLTF } from "../scripts/index.js";
|
|
4
|
+
import { printStats } from '../utils/stats.js';
|
|
5
|
+
import { getVersion } from '../utils/version.js';
|
|
6
|
+
import { UsecaseOptions, createConfig, getConfig } from '../config/index.js';
|
|
7
|
+
import { make_progressive } from '../transforms/needle_progressive.js';
|
|
8
|
+
import { clearCache } from '../cache/cache.js';
|
|
9
|
+
// For testing / dev you can run `npm link` in the package directory
|
|
10
|
+
// For removing the link run `npm rm --global @needle-tools/gltf-build-pipeline`
|
|
11
|
+
program
|
|
12
|
+
.command("version", "Print current verison")
|
|
13
|
+
.action(async ({ logger }) => {
|
|
14
|
+
logger.info(getVersion());
|
|
15
|
+
})
|
|
16
|
+
.command("clear-caches", "Clear all caches")
|
|
17
|
+
.help(`
|
|
18
|
+
Clear all caches.
|
|
19
|
+
`)
|
|
20
|
+
.action(async ({ logger }) => {
|
|
21
|
+
logger.info("Clearing caches");
|
|
22
|
+
clearCache(logger);
|
|
23
|
+
})
|
|
24
|
+
.command("compress", "Compress a glTF file")
|
|
25
|
+
.help(`
|
|
26
|
+
Compress a single glTF file. The input file is compressed and written to the output file.
|
|
27
|
+
`)
|
|
28
|
+
.argument('<input>', "The input glTF, GLB or VRM file that should be compressed")
|
|
29
|
+
.option("--usecase <usecase>", `The usecase for the compression. This will set the compression settings. Options are ${UsecaseOptions.join(", ")}`, { validator: program.STRING })
|
|
30
|
+
.option("--config <config>", "The path to a JSON file containing the compression settings. This can also be a needle.config.json file with a \"compression\" object or usecase string.", { validator: program.STRING })
|
|
31
|
+
.option("--output <output>", "The output file. If not provided, the input file will be overwritten.", { validator: program.STRING })
|
|
32
|
+
.action(async ({ logger, args, options }) => {
|
|
33
|
+
logger.info("Transforming glTF file: " + args.input);
|
|
34
|
+
const stats = {
|
|
35
|
+
startTime: Date.now(),
|
|
36
|
+
totalGPUMemoryInMB: 0,
|
|
37
|
+
totalFilesProcessed: 0,
|
|
38
|
+
totalFileSizeInMB: 0,
|
|
39
|
+
totalFileSizeInMBBefore: 0,
|
|
40
|
+
};
|
|
41
|
+
let config = getConfig(options.config?.toString());
|
|
42
|
+
if (options.usecase) {
|
|
43
|
+
logger.info("Using usecase: " + options.usecase);
|
|
44
|
+
config = { ...config, ...createConfig(options.usecase.toString()) };
|
|
45
|
+
}
|
|
46
|
+
const file = args.input.toString();
|
|
47
|
+
const set = new Set();
|
|
48
|
+
await packGLTF(file, options.output?.toString() || file, {
|
|
49
|
+
config,
|
|
50
|
+
packedFiles: set,
|
|
51
|
+
stats,
|
|
52
|
+
debug: false,
|
|
53
|
+
useCache: true,
|
|
54
|
+
});
|
|
55
|
+
printStats(stats);
|
|
56
|
+
})
|
|
57
|
+
.command("transform", "Transform a glTF file")
|
|
58
|
+
.help(`
|
|
59
|
+
This will produce multiple versions of the input file, each with a different level of optimization.
|
|
60
|
+
Each version will be compressed and written to the output directory.
|
|
61
|
+
`)
|
|
62
|
+
.argument('<input>', "The input glTF, GLB or VRM file that should be compressed")
|
|
63
|
+
.option("--usecase <usecase>", `The usecase for the compression. This will set the compression settings. Options are ${UsecaseOptions.join(", ")}`, { validator: program.STRING })
|
|
64
|
+
.option("--config <config>", "The path to a JSON file containing the compression settings. This can also be a needle.config.json file with a \"compression\" object or usecase string.", { validator: program.STRING })
|
|
65
|
+
// .option("--output <output>", "The output file. If not provided, the input file will be overwritten.", { validator: program.STRING })
|
|
66
|
+
.action(async ({ logger, args, options }) => {
|
|
67
|
+
logger.info("Transforming glTF file: " + args.input);
|
|
68
|
+
const stats = {
|
|
69
|
+
startTime: Date.now(),
|
|
70
|
+
totalGPUMemoryInMB: 0,
|
|
71
|
+
totalFilesProcessed: 0,
|
|
72
|
+
totalFileSizeInMB: 0,
|
|
73
|
+
totalFileSizeInMBBefore: 0,
|
|
74
|
+
};
|
|
75
|
+
let config = getConfig(options.config?.toString());
|
|
76
|
+
if (options.usecase) {
|
|
77
|
+
logger.info("Using usecase: " + options.usecase);
|
|
78
|
+
config = { ...config, ...createConfig(options.usecase.toString()) };
|
|
79
|
+
}
|
|
80
|
+
const file = args.input.toString();
|
|
81
|
+
const progressive_results = new Array();
|
|
82
|
+
await make_progressive({
|
|
83
|
+
path: file,
|
|
84
|
+
results: progressive_results,
|
|
85
|
+
config,
|
|
86
|
+
debug: false,
|
|
87
|
+
useCache: true,
|
|
88
|
+
});
|
|
89
|
+
console.log("Progressive results: ", progressive_results);
|
|
90
|
+
const opts = {
|
|
91
|
+
config,
|
|
92
|
+
stats,
|
|
93
|
+
debug: false,
|
|
94
|
+
useCache: true,
|
|
95
|
+
};
|
|
96
|
+
for (const prog of progressive_results) {
|
|
97
|
+
await packGLTF(prog, prog, opts);
|
|
98
|
+
}
|
|
99
|
+
await packGLTF(file, file, opts);
|
|
100
|
+
printStats(stats);
|
|
101
|
+
});
|
|
102
|
+
program.run();
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
declare type Usecase = "unknown" | "product" | "game" | "metaverse";
|
|
2
|
+
export declare const UsecaseOptions: string[];
|
|
3
|
+
/**
|
|
4
|
+
* The config object contains information about how a file should be processed.
|
|
5
|
+
*/
|
|
6
|
+
export type Config = {
|
|
7
|
+
/** High-level configuration */
|
|
8
|
+
usecase: Usecase;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Create a config object with the given usecase.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createConfig(usecase: string): {
|
|
14
|
+
usecase: Usecase;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Get the config object either provided by a CLI path or a needle.config.json file in the parent directory.
|
|
18
|
+
*
|
|
19
|
+
* @example CLI argument where the path points to a json file containing the config as `Config`
|
|
20
|
+
* ```sh
|
|
21
|
+
* node script.js --config path/to/my-optimization-config.json
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example needle.config.json - the usecase can be provided as a string
|
|
25
|
+
* ```json
|
|
26
|
+
* {
|
|
27
|
+
* "optimization": "product"
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @example needle.config.json - the whole config object can be provided
|
|
32
|
+
* ```json
|
|
33
|
+
* {
|
|
34
|
+
* "optimization": {
|
|
35
|
+
* "usecase": "product"
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
*
|
|
41
|
+
*/
|
|
42
|
+
export declare function getConfig(path?: string): Config;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
export const UsecaseOptions = ["product", "game", "metaverse"];
|
|
3
|
+
const defaultConfig = {
|
|
4
|
+
usecase: "unknown",
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Create a config object with the given usecase.
|
|
8
|
+
*/
|
|
9
|
+
export function createConfig(usecase) {
|
|
10
|
+
if (!UsecaseOptions.includes(usecase)) {
|
|
11
|
+
throw new Error(`Invalid usecase: \"${usecase}\". Valid usecases: ${UsecaseOptions.join(", ")}`);
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
usecase: usecase
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get the config object either provided by a CLI path or a needle.config.json file in the parent directory.
|
|
19
|
+
*
|
|
20
|
+
* @example CLI argument where the path points to a json file containing the config as `Config`
|
|
21
|
+
* ```sh
|
|
22
|
+
* node script.js --config path/to/my-optimization-config.json
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @example needle.config.json - the usecase can be provided as a string
|
|
26
|
+
* ```json
|
|
27
|
+
* {
|
|
28
|
+
* "optimization": "product"
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example needle.config.json - the whole config object can be provided
|
|
33
|
+
* ```json
|
|
34
|
+
* {
|
|
35
|
+
* "optimization": {
|
|
36
|
+
* "usecase": "product"
|
|
37
|
+
* }
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
*
|
|
42
|
+
*/
|
|
43
|
+
export function getConfig(path) {
|
|
44
|
+
if (path != null) {
|
|
45
|
+
if (!existsSync(path)) {
|
|
46
|
+
throw new Error(`No config found at \"${path}\"`);
|
|
47
|
+
}
|
|
48
|
+
return resolveConfig(path, readFileSync(path, "utf8"));
|
|
49
|
+
}
|
|
50
|
+
if (_cachedConfig !== undefined) {
|
|
51
|
+
return _cachedConfig;
|
|
52
|
+
}
|
|
53
|
+
// Resolve the config once and cache it
|
|
54
|
+
const result = internalResolveConfig();
|
|
55
|
+
if (result) {
|
|
56
|
+
console.log("INFO: Using optimization config with usecase: " + result.usecase);
|
|
57
|
+
_cachedConfig = result;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
console.log("INFO: No optimization config found. Using default config.");
|
|
61
|
+
_cachedConfig = defaultConfig;
|
|
62
|
+
}
|
|
63
|
+
return _cachedConfig;
|
|
64
|
+
}
|
|
65
|
+
let _cachedConfig = undefined;
|
|
66
|
+
function internalResolveConfig() {
|
|
67
|
+
// Check if a config path is passed as an argument
|
|
68
|
+
const args = process.argv.slice(2);
|
|
69
|
+
for (let i = 0; i < args.length; i++) {
|
|
70
|
+
if (args[i] === "--config") {
|
|
71
|
+
if (i + 1 < args.length) {
|
|
72
|
+
const configPath = args[i + 1];
|
|
73
|
+
if (existsSync(configPath)) {
|
|
74
|
+
const content = readFileSync(configPath, "utf8");
|
|
75
|
+
return resolveConfig(configPath, content);
|
|
76
|
+
}
|
|
77
|
+
throw new Error(`Config file not found at path configured by CLI --config argument: \"${configPath}\"`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Check if a needle.config.json exists in the directory from which the script is run (or any parent directory)
|
|
82
|
+
const workingDir = process.cwd();
|
|
83
|
+
let directory = workingDir.replaceAll("\\", "/");
|
|
84
|
+
while (directory.length > 0) {
|
|
85
|
+
const configPath = `${directory}/needle.config.json`;
|
|
86
|
+
if (existsSync(configPath)) {
|
|
87
|
+
const content = readFileSync(configPath, "utf8");
|
|
88
|
+
const config = resolveConfig(configPath, content);
|
|
89
|
+
if (config) {
|
|
90
|
+
return config;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const lastSlash = directory.lastIndexOf("/");
|
|
94
|
+
if (lastSlash < 0) {
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
directory = directory.slice(0, lastSlash);
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
function resolveConfig(filename, content) {
|
|
102
|
+
// Handle config being part of `needle.config.json`
|
|
103
|
+
if (filename.endsWith("needle.config.json")) {
|
|
104
|
+
const config = JSON.parse(content);
|
|
105
|
+
if (config.optimization) {
|
|
106
|
+
console.log("INFO: Found optimization config at \"" + filename + "\".");
|
|
107
|
+
// If the optimization config is a string, it's the usecase
|
|
108
|
+
if (typeof config.optimization === "string") {
|
|
109
|
+
return createConfig(config.optimization);
|
|
110
|
+
}
|
|
111
|
+
return config.optimization;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
console.log("INFO: No optimization config found in needle.config.json. Using default config.");
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Handle config being the content of the file
|
|
119
|
+
else if (content.trimStart().startsWith("{")) {
|
|
120
|
+
return JSON.parse(content);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
throw new Error("Invalid config file format. Expected JSON object.");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -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 {
|
|
@@ -19,13 +19,14 @@ export function createOpaqueExtension(name, types, opts) {
|
|
|
19
19
|
// private roots: Array<OpaqueProperty> = [];
|
|
20
20
|
jsonPointerHandler;
|
|
21
21
|
read(context) {
|
|
22
|
-
if (opts
|
|
22
|
+
if (opts?.breakpoint === true)
|
|
23
23
|
debugger;
|
|
24
24
|
currentGenerator = context.jsonDoc.json.asset?.generator;
|
|
25
25
|
if (debugLog)
|
|
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:
|
|
@@ -224,7 +225,7 @@ export function createOpaqueExtension(name, types, opts) {
|
|
|
224
225
|
}
|
|
225
226
|
}
|
|
226
227
|
write(context) {
|
|
227
|
-
if (opts
|
|
228
|
+
if (opts?.breakpoint === true)
|
|
228
229
|
debugger;
|
|
229
230
|
if (opts?.write === false) {
|
|
230
231
|
return this;
|
|
@@ -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
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Extension } from "@gltf-transform/core";
|
|
2
2
|
export * from "./NEEDLE_opaque.js";
|
|
3
|
-
export declare const ALL_EXTENSIONS:
|
|
3
|
+
export declare const ALL_EXTENSIONS: (typeof Extension)[];
|
|
@@ -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
|
];
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { Extension, ExtensionProperty, PropertyType, ReaderContext, WriterContext } from "@gltf-transform/core";
|
|
2
2
|
export declare interface IProgressiveMeshSettings {
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* When false no LODs will be generated for this mesh
|
|
5
|
+
*/
|
|
4
6
|
generateLods: boolean;
|
|
7
|
+
guid: string;
|
|
5
8
|
}
|
|
6
9
|
export declare class NEEDLE_progressive_mesh_settings extends Extension {
|
|
7
10
|
static EXTENSION_NAME: string;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { Extension, ExtensionProperty, PropertyType, ReaderContext, WriterContext } from "@gltf-transform/core";
|
|
2
2
|
export declare interface IProgressiveTextureSettings {
|
|
3
|
+
/**
|
|
4
|
+
* When false the texture no progressive texture will be generated
|
|
5
|
+
*/
|
|
6
|
+
generateLods?: boolean;
|
|
3
7
|
maxSize: number;
|
|
4
8
|
guid: string;
|
|
5
9
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./pack-gltf.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./pack-gltf.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Stats } from "../utils/index.js";
|
|
2
|
+
import { Config } from '../config/index.js';
|
|
3
|
+
export type PackGLTFOptions = {
|
|
4
|
+
debug: boolean;
|
|
5
|
+
useCache: boolean;
|
|
6
|
+
packedFiles?: Set<string>;
|
|
7
|
+
stats?: Stats;
|
|
8
|
+
config?: Config;
|
|
9
|
+
};
|
|
10
|
+
/** Compress a glTF file
|
|
11
|
+
*/
|
|
12
|
+
export declare function packGLTF(inputFile: string, outputFile: string, options: PackGLTFOptions): Promise<boolean>;
|