@needle-tools/gltf-build-pipeline 1.6.0-alpha.6 → 2.0.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 +11 -1
- package/README.md +18 -0
- package/dist/package.json +31 -25
- package/dist/src/cli/index.js +37 -25
- package/dist/src/extensions/NEEDLE_opaque/NEEDLE_opaque.d.ts +1 -1
- package/dist/src/extensions/NEEDLE_opaque/NEEDLE_opaque.js +53 -24
- package/dist/src/scripts/pack-gltf.js +12 -17
- package/dist/src/transforms/needle_mesh_transform.js +3 -4
- package/dist/src/transforms/needle_progressive.js +48 -4
- package/dist/src/transforms/needle_toktx.js +52 -22
- package/dist/src/transforms/toktx.d.ts +9 -35
- package/dist/src/transforms/toktx.js +73 -92
- package/dist/src/utils/fileutils.d.ts +1 -0
- package/dist/src/utils/fileutils.js +9 -0
- package/dist/src/utils/index.d.ts +1 -0
- package/dist/src/utils/index.js +1 -0
- package/dist/src/utils/stats.d.ts +9 -4
- package/dist/src/utils/stats.js +9 -3
- package/dist/src/utils/texture.d.ts +2 -0
- package/dist/src/utils/texture.js +8 -0
- package/package.json +32 -26
- package/scripts/clear-caches.mjs +1 -1
- package/scripts/defaults.mjs +2 -2
- package/scripts/fileutils.mjs +1 -1
- package/scripts/index.mjs +1 -1
- package/scripts/limit-caches.mjs +1 -1
- package/scripts/make-progressive.mjs +2 -2
- package/scripts/pack-gltf.mjs +1 -1
- package/tsconfig.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,8 +4,18 @@ 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
|
-
## [
|
|
7
|
+
## [2.0.0-alpha] - 2023-06-01
|
|
8
|
+
- update gltf-transform dependency to 4.x
|
|
9
|
+
- change: ensure ETC1S and UASTC textures are power of 2
|
|
10
|
+
|
|
11
|
+
- CLI: commands can be run with `needle-gltf <command>`
|
|
12
|
+
- CLI: add `--cache` to allow disabling cache with `--cache False` (default true)
|
|
13
|
+
- CLI: add `--stats` to produce `.needle.stats.json` (default false)
|
|
14
|
+
- CLI: change `--output` to be the optional second argument
|
|
15
|
+
|
|
16
|
+
## [1.6.0-alpha.7] - 2023-05-30
|
|
8
17
|
- fix: npx cli command return wrong error due to `packGLTF` not returning a booelan
|
|
18
|
+
- fix: mjs script import paths
|
|
9
19
|
|
|
10
20
|
## [1.6.0-alpha.5] - 2023-05-28
|
|
11
21
|
- fix: determine build pipeline version
|
package/README.md
CHANGED
|
@@ -8,6 +8,24 @@ Includes support for generating LOD quality level for textures and meshes that c
|
|
|
8
8
|
- [Example on Github](https://github.com/needle-engine/progressive-meshes-sample)
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
In general all files that have been processed by this package are valid glTF/GLB files.
|
|
14
|
+
The glTF Build Pipeline package is meant to be used as a last step before delivery.
|
|
15
|
+
That means:
|
|
16
|
+
- files are meant to be loaded e.g. in a web runtime like three.js
|
|
17
|
+
- files are not to be opened in Blender again to be further edited
|
|
18
|
+
|
|
19
|
+
To load progressively enhanced glTF files in any three.js based engine see examples at [@needle-tools/gltf-progressive](https://www.npmjs.com/package/@needle-tools/gltf-progressive)
|
|
20
|
+
|
|
21
|
+
### Installation
|
|
22
|
+
|
|
23
|
+
`npx @needle-tools/gltf-build-pipeline@latest`
|
|
24
|
+
|
|
25
|
+
### Commands
|
|
26
|
+
- `gltf-build-pipeline help` - use help to see options for each command
|
|
27
|
+
- `gltf-build-pipeline compress <input>`
|
|
28
|
+
- `gltf-build-pipeline transform <input>`
|
|
11
29
|
|
|
12
30
|
# Contact ✒️
|
|
13
31
|
<b>[🌵 needle — tools for creators](https://needle.tools)</b> •
|
package/dist/package.json
CHANGED
|
@@ -1,30 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/gltf-build-pipeline",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-alpha",
|
|
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",
|
|
7
7
|
"readme": "README.md",
|
|
8
|
+
"author": {
|
|
9
|
+
"name": "Needle",
|
|
10
|
+
"email": "help@needle.tools",
|
|
11
|
+
"url": "https://needle.tools/"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/needle-tools/needle-gltf-extensions.git"
|
|
16
|
+
},
|
|
8
17
|
"bin": {
|
|
9
|
-
"gltf-build-pipeline": "./dist/src/cli/index.js"
|
|
18
|
+
"gltf-build-pipeline": "./dist/src/cli/index.js",
|
|
19
|
+
"needle-gltf": "./dist/src/cli/index.js"
|
|
10
20
|
},
|
|
11
21
|
"scripts": {
|
|
12
22
|
"start": "node scripts/index.mjs",
|
|
13
23
|
"dev": "npm-watch compile",
|
|
24
|
+
"compile": "tsc || exit 1",
|
|
25
|
+
"clear-caches": "node scripts/clear-caches.mjs",
|
|
26
|
+
"limit-caches": "node scripts/limit-caches.mjs",
|
|
14
27
|
"transform": "node scripts/index.mjs",
|
|
15
28
|
"transform:make-progressive": "node scripts/make-progressive.mjs",
|
|
16
29
|
"transform:pack-gltf": "node --no-experimental-fetch scripts/pack-gltf.mjs",
|
|
17
30
|
"pack-gltf": "node --no-experimental-fetch scripts/pack-gltf.mjs",
|
|
18
|
-
"dev:pack-gltf": "npm run compile && node scripts/pack-gltf.mjs"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
31
|
+
"dev:pack-gltf": "npm run compile && node scripts/pack-gltf.mjs"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@img/sharp-win32-x64": "^0.33.4",
|
|
35
|
+
"@types/node": "^18.7.23",
|
|
36
|
+
"npm-watch": "^0.13.0",
|
|
37
|
+
"typescript": "^4.9.5"
|
|
38
|
+
},
|
|
39
|
+
"optionalDependencies": {
|
|
40
|
+
"@img/sharp-linux-arm": "^0.33.4",
|
|
41
|
+
"@img/sharp-win32-x64": "^0.33.4"
|
|
22
42
|
},
|
|
23
43
|
"dependencies": {
|
|
24
44
|
"@donmccurdy/caporal": "~0.0.10",
|
|
25
|
-
"@gltf-transform/
|
|
26
|
-
"@gltf-transform/
|
|
27
|
-
"@gltf-transform/
|
|
45
|
+
"@gltf-transform/cli": "^4.0.1",
|
|
46
|
+
"@gltf-transform/core": "^4.0.1",
|
|
47
|
+
"@gltf-transform/extensions": "^4.0.1",
|
|
48
|
+
"@gltf-transform/functions": "^4.0.1",
|
|
28
49
|
"command-exists": "^1.2.9",
|
|
29
50
|
"command-line-args": "^5.2.1",
|
|
30
51
|
"draco3dgltf": "^1.5.3",
|
|
@@ -36,7 +57,7 @@
|
|
|
36
57
|
"ndarray-pixels": "^2.0.1",
|
|
37
58
|
"p-limit": "3.1.0",
|
|
38
59
|
"semver": "^7.3.7",
|
|
39
|
-
"sharp": "^0.
|
|
60
|
+
"sharp": "^0.33.4",
|
|
40
61
|
"tmp": "^0.2.1",
|
|
41
62
|
"uuid": "^9.0.1"
|
|
42
63
|
},
|
|
@@ -48,20 +69,5 @@
|
|
|
48
69
|
"extensions": "ts",
|
|
49
70
|
"quiet": false
|
|
50
71
|
}
|
|
51
|
-
},
|
|
52
|
-
"devDependencies": {
|
|
53
|
-
"@types/node": "^18.7.23",
|
|
54
|
-
"nodemon": "^3.1.2",
|
|
55
|
-
"npm-watch": "^0.13.0",
|
|
56
|
-
"typescript": "^4.9.5"
|
|
57
|
-
},
|
|
58
|
-
"author": {
|
|
59
|
-
"name": "Needle",
|
|
60
|
-
"email": "help@needle.tools",
|
|
61
|
-
"url": "https://needle.tools/"
|
|
62
|
-
},
|
|
63
|
-
"repository": {
|
|
64
|
-
"type": "git",
|
|
65
|
-
"url": "git+https://github.com/needle-tools/needle-gltf-extensions.git"
|
|
66
72
|
}
|
|
67
73
|
}
|
package/dist/src/cli/index.js
CHANGED
|
@@ -7,8 +7,8 @@ import { UsecaseOptions, createConfig, getConfig } from '../config/index.js';
|
|
|
7
7
|
import { make_progressive } from '../transforms/needle_progressive.js';
|
|
8
8
|
import { cacheSizeLimit, clearCache } from '../cache/cache.js';
|
|
9
9
|
import { existsSync, statSync } from 'fs';
|
|
10
|
-
import { foreachGLTF } from '../utils/fileutils.js';
|
|
11
|
-
import { dirname } from 'path';
|
|
10
|
+
import { foreachGLTF, getDirName } from '../utils/fileutils.js';
|
|
11
|
+
import path, { dirname } from 'path';
|
|
12
12
|
import { ERROR_CODES } from '../constants.js';
|
|
13
13
|
// For testing / dev you can run `npm link` in the package directory
|
|
14
14
|
// For removing the link run `npm rm --global @needle-tools/gltf-build-pipeline`
|
|
@@ -30,10 +30,12 @@ Caches are limited to ${cacheSizeLimit} MB disc space by default.
|
|
|
30
30
|
Compress a single glTF file. The input file is compressed and written to the output file.
|
|
31
31
|
`)
|
|
32
32
|
.argument('<input>', "The input glTF, GLB or VRM file that should be compressed OR a directory that contains files to be compressed")
|
|
33
|
-
.
|
|
33
|
+
.argument('<output>', "The output file. If not provided, the input file will be overwritten.", { default: "" })
|
|
34
|
+
.option("--usecase <usecase>", `The usecase for the compression allows you to determine how your asset is supposed to be used:\nThis will set compression and transform settings on a high level if no other settings are found.\nPossible options: [${UsecaseOptions.join(", ")}] e.g. \"--usecase ${UsecaseOptions[0]}\"`, { validator: program.STRING })
|
|
34
35
|
.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 })
|
|
35
|
-
.option("--
|
|
36
|
-
.option("--
|
|
36
|
+
.option("--stats", "Output a stats file next to input file (or inside the input directory). It will be named as the input file/directory \"<input>.needle.stats.json\"", { validator: program.BOOLEAN, default: false })
|
|
37
|
+
.option("--cache", "Use \"--cache False\" to disable caching.\nWhen caching is enabled then previously processed files will be detected and loaded from disc cache instead of being processed again if settings or the file contents have not changed.", { validator: program.BOOLEAN, default: true })
|
|
38
|
+
.option("--debug", "Enable for more verbose outout with \"--debug True\"", { validator: program.BOOLEAN, default: false })
|
|
37
39
|
.action(async ({ logger, args, options }) => {
|
|
38
40
|
logger.info("Transforming glTF file: " + args.input);
|
|
39
41
|
const stats = {
|
|
@@ -54,8 +56,10 @@ Compress a single glTF file. The input file is compressed and written to the out
|
|
|
54
56
|
config.usecase = options.usecase.toString();
|
|
55
57
|
}
|
|
56
58
|
}
|
|
59
|
+
const debug = options.debug === true;
|
|
57
60
|
const file = args.input.toString();
|
|
58
|
-
const
|
|
61
|
+
const isDirectory = statSync(file).isDirectory();
|
|
62
|
+
const outDir = isDirectory ? file : dirname(file);
|
|
59
63
|
let outputIsDirectory = false;
|
|
60
64
|
if (statSync(file).isDirectory() && options.output) {
|
|
61
65
|
if (statSync(options.output.toString()).isDirectory()) {
|
|
@@ -73,17 +77,20 @@ Compress a single glTF file. The input file is compressed and written to the out
|
|
|
73
77
|
const file_stats = [];
|
|
74
78
|
const set = new Set();
|
|
75
79
|
await foreachGLTF(file, async (file) => {
|
|
76
|
-
let outputFile =
|
|
80
|
+
let outputFile = args.output?.toString();
|
|
81
|
+
if (!outputFile?.length) {
|
|
82
|
+
outputFile = file;
|
|
83
|
+
}
|
|
77
84
|
if (outputIsDirectory) {
|
|
78
85
|
const filename = file.replaceAll("\\", "/").split("/").pop();
|
|
79
|
-
outputFile =
|
|
86
|
+
outputFile = args.output?.toString() + "/" + filename;
|
|
80
87
|
}
|
|
81
88
|
const res = await packGLTF(file, outputFile, {
|
|
82
89
|
config,
|
|
83
90
|
packedFiles: set,
|
|
84
91
|
stats,
|
|
85
|
-
debug
|
|
86
|
-
useCache:
|
|
92
|
+
debug,
|
|
93
|
+
useCache: options.cache !== false,
|
|
87
94
|
logger: logger,
|
|
88
95
|
file_stats: file_stats,
|
|
89
96
|
});
|
|
@@ -93,8 +100,9 @@ Compress a single glTF file. The input file is compressed and written to the out
|
|
|
93
100
|
});
|
|
94
101
|
printStats(stats);
|
|
95
102
|
console.log("Stats: ", options);
|
|
96
|
-
if (options.
|
|
97
|
-
|
|
103
|
+
if (options.stats) {
|
|
104
|
+
const statsFile = (isDirectory ? (path.join(file, getDirName(file))) : file) + ".needle.stats.json";
|
|
105
|
+
writeFileStatsToFile(file_stats, statsFile);
|
|
98
106
|
}
|
|
99
107
|
})
|
|
100
108
|
.command("transform", "Transform (progressive loading) and compress glTF, GLB or VRM files")
|
|
@@ -103,10 +111,12 @@ This will produce multiple versions of the input file, each with a different lev
|
|
|
103
111
|
Each version will be compressed and written to the output directory.
|
|
104
112
|
`)
|
|
105
113
|
.argument('<input>', "The input glTF, GLB or VRM file that should be compressed OR a directory that contains files to be compressed")
|
|
114
|
+
// .argument('<output>', "The output file. If not provided, the input file will be overwritten.", { default: "" })
|
|
106
115
|
.option("--usecase <usecase>", `The usecase for the compression. This will set the compression settings. Possible options: [${UsecaseOptions.join(", ")}]`, { validator: program.STRING })
|
|
107
116
|
.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 })
|
|
108
|
-
|
|
109
|
-
.option("--
|
|
117
|
+
.option("--stats", "Output a stats file next to input file (or inside the input directory). It will be named as the input file/directory \"<input>.needle.stats.json\"", { validator: program.BOOLEAN, default: false })
|
|
118
|
+
.option("--cache", "Use \"--cache False\" to disable caching.\nWhen caching is enabled then previously processed files will be detected and loaded from disc cache instead of being processed again if settings or the file contents have not changed.", { validator: program.BOOLEAN, default: true })
|
|
119
|
+
.option("--debug", "Enable for more verbose outout with \"--debug True\"", { validator: program.BOOLEAN, default: false })
|
|
110
120
|
.action(async ({ logger, args, options }) => {
|
|
111
121
|
logger.info("Transforming glTF file: " + args.input);
|
|
112
122
|
const stats = {
|
|
@@ -127,19 +137,22 @@ Each version will be compressed and written to the output directory.
|
|
|
127
137
|
config.usecase = options.usecase.toString();
|
|
128
138
|
}
|
|
129
139
|
}
|
|
130
|
-
|
|
131
|
-
const
|
|
140
|
+
let file = args.input.toString();
|
|
141
|
+
const isDirectory = statSync(file).isDirectory();
|
|
142
|
+
const outDir = isDirectory ? file : dirname(file);
|
|
132
143
|
const file_stats = [];
|
|
133
144
|
const set = new Set();
|
|
145
|
+
const useCache = options.cache !== false;
|
|
146
|
+
const debug = options.debug === true;
|
|
134
147
|
await foreachGLTF(file, async (file) => {
|
|
135
|
-
logger.info("
|
|
148
|
+
logger.info("→ Transform " + file);
|
|
136
149
|
const progressive_results = new Array();
|
|
137
150
|
await make_progressive({
|
|
138
151
|
path: file,
|
|
139
152
|
results: progressive_results,
|
|
140
153
|
config,
|
|
141
|
-
debug
|
|
142
|
-
useCache
|
|
154
|
+
debug,
|
|
155
|
+
useCache,
|
|
143
156
|
}).catch(err => {
|
|
144
157
|
logger.error(err);
|
|
145
158
|
process.exit(ERROR_CODES.PROGRESSIVE_FAILED);
|
|
@@ -148,25 +161,24 @@ Each version will be compressed and written to the output directory.
|
|
|
148
161
|
config,
|
|
149
162
|
stats,
|
|
150
163
|
packedFiles: set,
|
|
151
|
-
debug
|
|
152
|
-
useCache
|
|
164
|
+
debug,
|
|
165
|
+
useCache,
|
|
153
166
|
logger: logger,
|
|
154
167
|
file_stats,
|
|
155
168
|
};
|
|
156
169
|
for (const res of progressive_results) {
|
|
157
|
-
logger.info("> Compress " + res);
|
|
158
170
|
if (await packGLTF(res, res, opts) === false) {
|
|
159
171
|
process.exit(ERROR_CODES.PACKING_FAILED);
|
|
160
172
|
}
|
|
161
173
|
}
|
|
162
|
-
logger.info("> Compress " + file);
|
|
163
174
|
if (await packGLTF(file, file, opts) === false) {
|
|
164
175
|
process.exit(ERROR_CODES.PACKING_FAILED);
|
|
165
176
|
}
|
|
166
177
|
});
|
|
167
178
|
printStats(stats);
|
|
168
|
-
if (options.
|
|
169
|
-
|
|
179
|
+
if (options.stats) {
|
|
180
|
+
const statsFile = (isDirectory ? (path.join(file, getDirName(file))) : file) + ".needle.stats.json";
|
|
181
|
+
writeFileStatsToFile(file_stats, statsFile);
|
|
170
182
|
}
|
|
171
183
|
});
|
|
172
184
|
program.run();
|
|
@@ -15,7 +15,7 @@ export interface IOpaqueProperty {
|
|
|
15
15
|
/** register a reference so gltf-transform will not remove it in e.g. dedup
|
|
16
16
|
* https://github.com/donmccurdy/glTF-Transform/issues/980#issuecomment-1585748202
|
|
17
17
|
*/
|
|
18
|
-
setReference(name: string, value: any):
|
|
18
|
+
setReference(name: string, value: any, attributes?: Record<string, any>): void;
|
|
19
19
|
}
|
|
20
20
|
export interface IExtensionWriter {
|
|
21
21
|
/** used by progressive texture extension to pass an extension between documents */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Extension, ExtensionProperty, PropertyType } from '@gltf-transform/core';
|
|
1
|
+
import { Extension, ExtensionProperty, PropertyType, TextureChannel } from '@gltf-transform/core';
|
|
2
2
|
import { getTexture, tryGetInArray, writeExtension } from '../utils.js';
|
|
3
3
|
const ALL_PROPERTY_TYPES = [];
|
|
4
4
|
for (const key in PropertyType) {
|
|
@@ -16,6 +16,7 @@ export function createOpaqueExtension(name, types, opts) {
|
|
|
16
16
|
class NEEDLE_opaque extends Extension {
|
|
17
17
|
extensionName = EXTENSION_NAME;
|
|
18
18
|
static EXTENSION_NAME = EXTENSION_NAME;
|
|
19
|
+
prewriteTypes = [PropertyType.MATERIAL];
|
|
19
20
|
// private roots: Array<OpaqueProperty> = [];
|
|
20
21
|
jsonPointerHandler;
|
|
21
22
|
read(context) {
|
|
@@ -118,8 +119,8 @@ export function createOpaqueExtension(name, types, opts) {
|
|
|
118
119
|
console.log("WARN: could potentially not find gltf transform texture for " + index);
|
|
119
120
|
texture = context.textures[index];
|
|
120
121
|
}
|
|
121
|
-
this.jsonPointerHandler.read(context, texture, ext);
|
|
122
122
|
const prop = this.createExtension();
|
|
123
|
+
this.jsonPointerHandler.read(context, texture, ext, prop);
|
|
123
124
|
texture.setExtension(EXTENSION_NAME, prop);
|
|
124
125
|
const handler = new TextureHandler(this.document, textureDefinition, EXTENSION_NAME, ext, debugLog);
|
|
125
126
|
prop.add(handler);
|
|
@@ -142,8 +143,8 @@ export function createOpaqueExtension(name, types, opts) {
|
|
|
142
143
|
if (!ext)
|
|
143
144
|
return;
|
|
144
145
|
const primitive = primitives[primitiveIndex];
|
|
145
|
-
this.jsonPointerHandler.read(context, primitive, ext);
|
|
146
146
|
const prop = this.createExtension();
|
|
147
|
+
this.jsonPointerHandler.read(context, primitive, ext, prop);
|
|
147
148
|
primitive.setExtension(EXTENSION_NAME, prop);
|
|
148
149
|
// this is invoked on write
|
|
149
150
|
prop.add(ctx => {
|
|
@@ -164,8 +165,8 @@ export function createOpaqueExtension(name, types, opts) {
|
|
|
164
165
|
if (ext) {
|
|
165
166
|
this.lightmapWorkaround(context);
|
|
166
167
|
const root = this.document.getRoot();
|
|
167
|
-
this.jsonPointerHandler.read(context, root, ext);
|
|
168
168
|
const prop = this.createExtension();
|
|
169
|
+
this.jsonPointerHandler.read(context, root, ext, prop);
|
|
169
170
|
root.setExtension(EXTENSION_NAME, prop);
|
|
170
171
|
prop.add(ctx => {
|
|
171
172
|
ctx.jsonDoc.json.extensions = ctx.jsonDoc.json.extensions || {};
|
|
@@ -224,6 +225,13 @@ export function createOpaqueExtension(name, types, opts) {
|
|
|
224
225
|
console.error("ERR: " + error);
|
|
225
226
|
}
|
|
226
227
|
}
|
|
228
|
+
prewrite(context, _propertyType) {
|
|
229
|
+
// Attempt resolve textures here so that the json.textures is already populated for cases where a texture isn't used by any material
|
|
230
|
+
// this is e.g. the case for textures that are just referenced by a component but not used by any texture
|
|
231
|
+
// https://linear.app/needle/issue/NE-4928
|
|
232
|
+
this.jsonPointerHandler.resolveAndWrite(context, "prewrite");
|
|
233
|
+
return this;
|
|
234
|
+
}
|
|
227
235
|
write(context) {
|
|
228
236
|
if (opts?.breakpoint === true)
|
|
229
237
|
debugger;
|
|
@@ -232,7 +240,7 @@ export function createOpaqueExtension(name, types, opts) {
|
|
|
232
240
|
}
|
|
233
241
|
if (debugLog)
|
|
234
242
|
console.log("WRITE " + EXTENSION_NAME);
|
|
235
|
-
this.jsonPointerHandler.resolveAndWrite(context);
|
|
243
|
+
this.jsonPointerHandler.resolveAndWrite(context, "write");
|
|
236
244
|
const root = this.document.getRoot();
|
|
237
245
|
// for (const rootExt of this.roots) {
|
|
238
246
|
// rootExt.onWrite(context, root);
|
|
@@ -313,11 +321,11 @@ export function createOpaqueExtension(name, types, opts) {
|
|
|
313
321
|
extensionName = typeof EXTENSION_NAME;
|
|
314
322
|
parentTypes = ExtensionPropertyTypes;
|
|
315
323
|
propertyType = "NEEDLE_opaque_property";
|
|
316
|
-
setReference(name, value) {
|
|
317
|
-
this.setRef(name, value, { modifyChild: true });
|
|
318
|
-
}
|
|
319
324
|
init() {
|
|
320
325
|
}
|
|
326
|
+
setReference(name, value, attributes = { modifyChild: true }) {
|
|
327
|
+
this.setRef(name, value, attributes);
|
|
328
|
+
}
|
|
321
329
|
writers = [];
|
|
322
330
|
add(writer) {
|
|
323
331
|
if (typeof writer === "function") {
|
|
@@ -520,7 +528,7 @@ class JsonPointerHandler {
|
|
|
520
528
|
this.document = document;
|
|
521
529
|
this.id = id;
|
|
522
530
|
}
|
|
523
|
-
read(context,
|
|
531
|
+
read(context, extProp, obj, prop, key, fullPath) {
|
|
524
532
|
const value = key !== undefined ? obj[key] : obj;
|
|
525
533
|
// the pointer path contains the full path from the root object to the json pointer
|
|
526
534
|
if (key !== undefined) {
|
|
@@ -535,7 +543,7 @@ class JsonPointerHandler {
|
|
|
535
543
|
case 'object':
|
|
536
544
|
if (value !== null && value !== undefined) {
|
|
537
545
|
for (const key of Object.keys(value)) {
|
|
538
|
-
this.read(context,
|
|
546
|
+
this.read(context, extProp, value, prop, key, fullPath);
|
|
539
547
|
}
|
|
540
548
|
}
|
|
541
549
|
break;
|
|
@@ -568,6 +576,15 @@ class JsonPointerHandler {
|
|
|
568
576
|
pointer = new JsonPointer(path, res, value);
|
|
569
577
|
pointer.debug = this.debug;
|
|
570
578
|
this.map.set(value, pointer);
|
|
579
|
+
// Assume all texture slots are being used. The referenceName is the json pointer right now - usually this is e.g. `baseColorTexture` (the name ends up in the slots array)
|
|
580
|
+
const referenceName = value;
|
|
581
|
+
// const slots = listTextureSlots(object);
|
|
582
|
+
prop.setReference(referenceName, object, { channels: TextureChannel.R | TextureChannel.G | TextureChannel.B | TextureChannel.A });
|
|
583
|
+
// make sure there's a reference to the buffer to avoid dedup or the buffer being removed because it MIGHT not be used by any node
|
|
584
|
+
// TODO: how do we get the buffer that the texture/image is currently using - here we just take the first buffer but this might not be correct
|
|
585
|
+
const buffer = this.document.getRoot().listBuffers()[0];
|
|
586
|
+
if (buffer)
|
|
587
|
+
prop.setReference("buffer", buffer);
|
|
571
588
|
}
|
|
572
589
|
else {
|
|
573
590
|
console.warn("Texture not found", fullPath, value);
|
|
@@ -585,23 +602,24 @@ class JsonPointerHandler {
|
|
|
585
602
|
if (this.debug)
|
|
586
603
|
console.log("Read pointer", value);
|
|
587
604
|
const index = parseInt(value.substring("/materials/".length));
|
|
588
|
-
const
|
|
589
|
-
if (
|
|
605
|
+
const object = context.materials[index];
|
|
606
|
+
if (object) {
|
|
590
607
|
// set a reference to the material to avoid dedup or the material being removed because it MIGHT not be used by any node
|
|
591
608
|
// BUT a component is referencing the material so we don't want it ever to be removed
|
|
592
609
|
// see https://github.com/donmccurdy/glTF-Transform/issues/980#issuecomment-1585748202
|
|
593
|
-
if (
|
|
594
|
-
|
|
610
|
+
if (prop) {
|
|
611
|
+
prop.setReference(value, object);
|
|
595
612
|
}
|
|
596
613
|
let pointer = this.map.get(value);
|
|
597
614
|
if (!pointer) {
|
|
598
615
|
const definition = context.jsonDoc.json.materials[index];
|
|
599
|
-
const res = new MaterialIndexResolver(this.document,
|
|
616
|
+
const res = new MaterialIndexResolver(this.document, object, definition);
|
|
600
617
|
res.debug = this.debug;
|
|
601
618
|
res.init(context); // < init here otherwise the fields defined in the TexturePointer will be undefined (this caused e.g. finding the previous texture filter to fail)
|
|
602
619
|
pointer = new JsonPointer("/materials/", res, value);
|
|
603
620
|
pointer.debug = this.debug;
|
|
604
621
|
this.map.set(value, pointer);
|
|
622
|
+
prop.setReference(value, object);
|
|
605
623
|
}
|
|
606
624
|
pointer?.register(obj, key, fullPath);
|
|
607
625
|
}
|
|
@@ -625,6 +643,7 @@ class JsonPointerHandler {
|
|
|
625
643
|
pointer.debug = this.debug;
|
|
626
644
|
pointer.register(obj, key, fullPath);
|
|
627
645
|
this.map.set(dictKey, pointer);
|
|
646
|
+
prop.setReference(dictKey, object);
|
|
628
647
|
}
|
|
629
648
|
}
|
|
630
649
|
else if (
|
|
@@ -663,9 +682,7 @@ class JsonPointerHandler {
|
|
|
663
682
|
pointer.debug = this.debug;
|
|
664
683
|
pointer.register(obj, key, fullPath);
|
|
665
684
|
this.map.set(dictKey, pointer);
|
|
666
|
-
|
|
667
|
-
ext.setReference(dictKey, object);
|
|
668
|
-
}
|
|
685
|
+
prop.setReference(dictKey, object);
|
|
669
686
|
}
|
|
670
687
|
}
|
|
671
688
|
break;
|
|
@@ -673,10 +690,10 @@ class JsonPointerHandler {
|
|
|
673
690
|
break;
|
|
674
691
|
}
|
|
675
692
|
}
|
|
676
|
-
resolveAndWrite(context) {
|
|
693
|
+
resolveAndWrite(context, step) {
|
|
677
694
|
this.map?.forEach(ptr => {
|
|
678
|
-
ptr.resolve(context)
|
|
679
|
-
|
|
695
|
+
if (ptr.resolve(context, step))
|
|
696
|
+
ptr.write();
|
|
680
697
|
});
|
|
681
698
|
}
|
|
682
699
|
}
|
|
@@ -698,8 +715,12 @@ class JsonPointer {
|
|
|
698
715
|
this._props.push(prop);
|
|
699
716
|
this._debugInfo.push(fullpath);
|
|
700
717
|
}
|
|
701
|
-
newPointer;
|
|
702
|
-
resolve(context) {
|
|
718
|
+
newPointer = undefined;
|
|
719
|
+
resolve(context, step) {
|
|
720
|
+
// If the pointer was already resolved, we don't need to do it again
|
|
721
|
+
if (this.newPointer !== undefined) {
|
|
722
|
+
return true;
|
|
723
|
+
}
|
|
703
724
|
//@ts-ignore
|
|
704
725
|
this.resolver.debug = this.debug;
|
|
705
726
|
const index = this.resolver.resolve(context);
|
|
@@ -712,12 +733,20 @@ class JsonPointer {
|
|
|
712
733
|
}
|
|
713
734
|
if (this.debug)
|
|
714
735
|
console.log(`RESOLVED pointer ${this.oldPointer} to → ${this.newPointer}`);
|
|
736
|
+
return true;
|
|
715
737
|
}
|
|
716
738
|
else {
|
|
717
|
-
|
|
739
|
+
// If the pointer could not be resolved during write we need to log an error
|
|
740
|
+
if (step === "write")
|
|
741
|
+
console.log("ERR: 🧨 FAILED to resolve pointer", this.pointerBase, ` used by ${this._debugInfo.join(", ")}`);
|
|
718
742
|
}
|
|
743
|
+
return false;
|
|
719
744
|
}
|
|
745
|
+
_didWrite = false;
|
|
720
746
|
write() {
|
|
747
|
+
if (this._didWrite)
|
|
748
|
+
return;
|
|
749
|
+
this._didWrite = false;
|
|
721
750
|
for (let i = 0; i < this._objects.length; i++) {
|
|
722
751
|
const obj = this._objects[i];
|
|
723
752
|
const prop = this._props[i];
|
|
@@ -4,9 +4,9 @@ import { ALL_EXTENSIONS } from '@gltf-transform/extensions';
|
|
|
4
4
|
import draco3d from 'draco3dgltf';
|
|
5
5
|
import { existsSync, readFileSync, statSync, writeFileSync } from 'fs';
|
|
6
6
|
import { MeshoptDecoder, MeshoptEncoder } from 'meshoptimizer';
|
|
7
|
-
import { resample } from '@gltf-transform/functions';
|
|
7
|
+
import { dedup, prune, resample } from '@gltf-transform/functions';
|
|
8
8
|
import { ALL_EXTENSIONS as NEEDLE_EXTENSIONS, NEEDLE_compression_texture, NEEDLE_mesh_compression, } from '../extensions/index.js';
|
|
9
|
-
import { isMeshLOD, needle_animation_transform, needle_asset, needle_mesh_transform, needle_texture_transform, } from '../transforms/index.js';
|
|
9
|
+
import { isLOD, isMeshLOD, needle_animation_transform, needle_asset, needle_mesh_transform, needle_texture_transform, } from '../transforms/index.js';
|
|
10
10
|
import { createNewFilestats, writeNodeIO } from "../utils/index.js";
|
|
11
11
|
import { addToCache, getHash, tryGetFromCache, } from "../cache/index.js";
|
|
12
12
|
import { getConfig } from '../config/index.js';
|
|
@@ -19,10 +19,7 @@ export async function packGLTF(inputFile, outputFile, options) {
|
|
|
19
19
|
const { debug, stats, packedFiles, useCache, logger } = options;
|
|
20
20
|
if (!existsSync(inputFile)) {
|
|
21
21
|
logger.error("WARN: can not pack, file does not exist: " + inputFile);
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
if (debug && inputFile.includes(".debug.")) {
|
|
25
|
-
return true;
|
|
22
|
+
return false;
|
|
26
23
|
}
|
|
27
24
|
// check if it's in the cache.
|
|
28
25
|
// We only need to check mesh LOD files because image LODs are cache via our toktx transform
|
|
@@ -48,7 +45,6 @@ export async function packGLTF(inputFile, outputFile, options) {
|
|
|
48
45
|
const config = options.config || getConfig();
|
|
49
46
|
const prevSizeInMB = statSync(inputFile).size / 1024 / 1024;
|
|
50
47
|
logger.info(`→ Compressing ${prevSizeInMB.toFixed(1)} MB: ${inputFile}`);
|
|
51
|
-
// const isProgressiveAsset = isLOD(file);
|
|
52
48
|
const startTime = Date.now();
|
|
53
49
|
const io = new NodeIO()
|
|
54
50
|
.registerExtensions(ALL_EXTENSIONS)
|
|
@@ -65,7 +61,7 @@ export async function packGLTF(inputFile, outputFile, options) {
|
|
|
65
61
|
});
|
|
66
62
|
const document = await io.read(inputFile);
|
|
67
63
|
/** @ts-ignore */
|
|
68
|
-
document.getLogger().verbosity = Verbosity.
|
|
64
|
+
document.getLogger().verbosity = options.debug ? Verbosity.DEBUG : Verbosity.WARN;
|
|
69
65
|
let totalGPUTextureMemory = 0;
|
|
70
66
|
/** @type {Array<import('@gltf-transform/core').Transform>} */
|
|
71
67
|
const transforms = [
|
|
@@ -83,18 +79,17 @@ export async function packGLTF(inputFile, outputFile, options) {
|
|
|
83
79
|
needle_mesh_transform({ file: inputFile, config }, { ratio: 0.75, error: 0.0005 }),
|
|
84
80
|
needle_animation_transform({}),
|
|
85
81
|
resample(),
|
|
86
|
-
|
|
87
|
-
//
|
|
82
|
+
dedup(),
|
|
83
|
+
// prune(),
|
|
88
84
|
];
|
|
89
|
-
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
85
|
+
const isProgressiveAsset = isLOD(inputFile);
|
|
86
|
+
// TODO: progressive mesh LODs should not be pruned right now because it will remove e.g. UVs from meshes (and probably blend shapes too) (so we just not do it for any progressive asset)
|
|
87
|
+
if (!isProgressiveAsset) {
|
|
88
|
+
transforms.push(prune());
|
|
89
|
+
}
|
|
93
90
|
await document.transform(...transforms);
|
|
94
91
|
if (!outputFile)
|
|
95
92
|
outputFile = inputFile;
|
|
96
|
-
if (debug)
|
|
97
|
-
outputFile += ".debug.glb";
|
|
98
93
|
await writeNodeIO(io, outputFile, document);
|
|
99
94
|
if (useCache && cacheKey != undefined && existsSync(outputFile))
|
|
100
95
|
addToCache(cacheKey, readFileSync(outputFile));
|
|
@@ -116,7 +111,7 @@ export async function packGLTF(inputFile, outputFile, options) {
|
|
|
116
111
|
return true;
|
|
117
112
|
}
|
|
118
113
|
catch (err) {
|
|
119
|
-
logger.error(
|
|
114
|
+
logger.error(`packing ${path.basename(inputFile)} failed: ${err}`);
|
|
120
115
|
if (debug)
|
|
121
116
|
throw err;
|
|
122
117
|
return false;
|
|
@@ -52,7 +52,6 @@ async function simplifierTransform(document, _context, _options) {
|
|
|
52
52
|
options.simplifier = simplifier;
|
|
53
53
|
const weldOptions = {
|
|
54
54
|
...WELD_DEFAULTS,
|
|
55
|
-
tolerance: 0.00000000001,
|
|
56
55
|
overwrite: true,
|
|
57
56
|
};
|
|
58
57
|
for (const mesh of document.getRoot().listMeshes()) {
|
|
@@ -99,8 +98,8 @@ async function simplifierTransform(document, _context, _options) {
|
|
|
99
98
|
}
|
|
100
99
|
export async function compressPrimitive(document, mesh, prim, index, weldOptions, meshOptions) {
|
|
101
100
|
const logger = document.getLogger();
|
|
102
|
-
logger.debug(`weld: primitive[${index}] of mesh "${mesh.getName()}"
|
|
103
|
-
weldPrimitive(
|
|
101
|
+
logger.debug(`weld: primitive[${index}] of mesh "${mesh.getName()}"`);
|
|
102
|
+
weldPrimitive(prim, weldOptions);
|
|
104
103
|
// make sure we wait for the mesh simplifier to be ready
|
|
105
104
|
if (meshOptions.simplifier && meshOptions.simplifier?.ready instanceof Promise) {
|
|
106
105
|
const res = await meshOptions.simplifier?.ready
|
|
@@ -116,7 +115,7 @@ export async function compressPrimitive(document, mesh, prim, index, weldOptions
|
|
|
116
115
|
return;
|
|
117
116
|
}
|
|
118
117
|
logger.debug(`simplify: primitive[${index}] of mesh "${mesh.getName()}" → ratio: ${meshOptions.ratio}, error: ${meshOptions.error}, lockBorder: ${meshOptions.lockBorder}`);
|
|
119
|
-
return simplifyPrimitive(
|
|
118
|
+
return simplifyPrimitive(prim, meshOptions);
|
|
120
119
|
}
|
|
121
120
|
// function updatePrimitive(prim: Primitive, simplifier: any, positionArray, dstIndicesArray) {
|
|
122
121
|
// const position = prim.getAttribute('POSITION')!;
|