@needle-tools/gltf-build-pipeline 0.1.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 +140 -0
- package/clear-caches.mjs +4 -0
- package/defaults.mjs +12 -0
- package/dist/cache/cache.d.ts +5 -0
- package/dist/cache/cache.js +82 -0
- package/dist/cache/index.d.ts +1 -0
- package/dist/cache/index.js +1 -0
- package/dist/extensions/NEEDLE_compression_texture/NEEDLE_compression_texture.d.ts +24 -0
- package/dist/extensions/NEEDLE_compression_texture/NEEDLE_compression_texture.js +54 -0
- package/dist/extensions/NEEDLE_compression_texture/index.d.ts +1 -0
- package/dist/extensions/NEEDLE_compression_texture/index.js +1 -0
- package/dist/extensions/NEEDLE_mesh_compression/NEEDLE_mesh_compression.d.ts +23 -0
- package/dist/extensions/NEEDLE_mesh_compression/NEEDLE_mesh_compression.js +43 -0
- package/dist/extensions/NEEDLE_mesh_compression/index.d.ts +1 -0
- package/dist/extensions/NEEDLE_mesh_compression/index.js +1 -0
- package/dist/extensions/NEEDLE_opaque/NEEDLE_opaque.d.ts +20 -0
- package/dist/extensions/NEEDLE_opaque/NEEDLE_opaque.js +699 -0
- package/dist/extensions/NEEDLE_opaque/index.d.ts +3 -0
- package/dist/extensions/NEEDLE_opaque/index.js +18 -0
- package/dist/extensions/NEEDLE_progressive/NEEDLE_progressive.d.ts +23 -0
- package/dist/extensions/NEEDLE_progressive/NEEDLE_progressive.js +53 -0
- package/dist/extensions/NEEDLE_progressive/index.d.ts +1 -0
- package/dist/extensions/NEEDLE_progressive/index.js +1 -0
- package/dist/extensions/NEEDLE_progressive_texture_settings/index.d.ts +21 -0
- package/dist/extensions/NEEDLE_progressive_texture_settings/index.js +39 -0
- package/dist/extensions/index.d.ts +6 -0
- package/dist/extensions/index.js +16 -0
- package/dist/extensions/utils.d.ts +6 -0
- package/dist/extensions/utils.js +72 -0
- package/dist/functions/list-texture-slots.d.ts +1 -0
- package/dist/functions/list-texture-slots.js +35 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/transforms/index.d.ts +9 -0
- package/dist/transforms/index.js +9 -0
- package/dist/transforms/needle_animation_transform.d.ts +2 -0
- package/dist/transforms/needle_animation_transform.js +5 -0
- package/dist/transforms/needle_asset.d.ts +2 -0
- package/dist/transforms/needle_asset.js +28 -0
- package/dist/transforms/needle_common.d.ts +11 -0
- package/dist/transforms/needle_common.js +1 -0
- package/dist/transforms/needle_mesh_transform.d.ts +3 -0
- package/dist/transforms/needle_mesh_transform.js +83 -0
- package/dist/transforms/needle_progressive.d.ts +34 -0
- package/dist/transforms/needle_progressive.js +264 -0
- package/dist/transforms/needle_simplify.d.ts +3 -0
- package/dist/transforms/needle_simplify.js +76 -0
- package/dist/transforms/needle_texture_transform.d.ts +5 -0
- package/dist/transforms/needle_texture_transform.js +123 -0
- package/dist/transforms/needle_toktx.d.ts +24 -0
- package/dist/transforms/needle_toktx.js +144 -0
- package/dist/transforms/needle_webp.d.ts +13 -0
- package/dist/transforms/needle_webp.js +53 -0
- package/dist/transforms/toktx.d.ts +77 -0
- package/dist/transforms/toktx.js +230 -0
- package/dist/transforms/types/squoosh.d.ts +31 -0
- package/dist/transforms/types/squoosh.js +33 -0
- package/dist/transforms/util.d.ts +21 -0
- package/dist/transforms/util.js +90 -0
- package/dist/utils/fileutils.d.ts +2 -0
- package/dist/utils/fileutils.js +95 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/fileutils.mjs +6 -0
- package/index.mjs +33 -0
- package/make-progressive.mjs +11 -0
- package/pack-gltf.mjs +119 -0
- package/package.json +60 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
import { Extension, ExtensionProperty, PropertyType } from '@gltf-transform/core';
|
|
2
|
+
import { getTexture, tryGetInArray, writeExtension } from '../utils.js';
|
|
3
|
+
const ALL_PROPERTY_TYPES = [];
|
|
4
|
+
for (const key in PropertyType) {
|
|
5
|
+
ALL_PROPERTY_TYPES.push(PropertyType[key]);
|
|
6
|
+
}
|
|
7
|
+
// an opaque extension can be used to pass data through the gltf transform process
|
|
8
|
+
// it should also be able to re-write/fix json pointers after the transformation which might have shuffled them around
|
|
9
|
+
export function createOpaqueExtension(name, types, opts) {
|
|
10
|
+
if (!Array.isArray(types))
|
|
11
|
+
types = [types];
|
|
12
|
+
const EXTENSION_NAME = name;
|
|
13
|
+
const ExtensionPropertyTypes = ALL_PROPERTY_TYPES;
|
|
14
|
+
const debugLog = opts?.debug ?? false;
|
|
15
|
+
class NEEDLE_opaque extends Extension {
|
|
16
|
+
extensionName = EXTENSION_NAME;
|
|
17
|
+
static EXTENSION_NAME = EXTENSION_NAME;
|
|
18
|
+
// private roots: Array<OpaqueProperty> = [];
|
|
19
|
+
jsonPointerHandler;
|
|
20
|
+
read(context) {
|
|
21
|
+
if (debugLog)
|
|
22
|
+
console.log("READ " + EXTENSION_NAME);
|
|
23
|
+
this.jsonPointerHandler = new JsonPointerHandler(this.document, EXTENSION_NAME);
|
|
24
|
+
this.jsonPointerHandler.debug = debugLog;
|
|
25
|
+
for (const type of types) {
|
|
26
|
+
switch (type) {
|
|
27
|
+
case PropertyType.ACCESSOR:
|
|
28
|
+
this.onRead(context, "accessors", "accessorIndexMap");
|
|
29
|
+
break;
|
|
30
|
+
case PropertyType.ANIMATION:
|
|
31
|
+
this.onRead(context, "animations", "animationIndexMap");
|
|
32
|
+
break;
|
|
33
|
+
case PropertyType.ANIMATION_CHANNEL:
|
|
34
|
+
if (EXTENSION_NAME === "KHR_animation_pointer") {
|
|
35
|
+
context.jsonDoc.json.animations?.forEach((animDef, animIndex) => {
|
|
36
|
+
animDef.channels?.forEach((channel, channelIndex) => {
|
|
37
|
+
const jsonTarget = channel.target;
|
|
38
|
+
const khr_animation = jsonTarget.extensions && jsonTarget.extensions[EXTENSION_NAME];
|
|
39
|
+
if (!khr_animation)
|
|
40
|
+
return;
|
|
41
|
+
const anim = context.animations[animIndex];
|
|
42
|
+
const existing = anim.getExtension(EXTENSION_NAME);
|
|
43
|
+
if (debugLog)
|
|
44
|
+
console.log(">>>", EXTENSION_NAME, "animation", animIndex, "channel", channelIndex, khr_animation);
|
|
45
|
+
if (existing) {
|
|
46
|
+
assignChannels(existing);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const prop = this.createExtension();
|
|
50
|
+
// there seems no way (yet?) to add extensions to channel.targets (?)
|
|
51
|
+
// https://github.com/donmccurdy/glTF-Transform/discussions/613
|
|
52
|
+
// so we add it to an object by keys to the animation itself and read it back in WRITE
|
|
53
|
+
const khr_animation_data = {};
|
|
54
|
+
khr_animation_data[channelIndex] = khr_animation;
|
|
55
|
+
anim.setExtension(EXTENSION_NAME, prop);
|
|
56
|
+
assignChannels(prop);
|
|
57
|
+
function assignChannels(prop) {
|
|
58
|
+
prop.add(ctx => {
|
|
59
|
+
const animations = ctx.jsonDoc.json.animations;
|
|
60
|
+
if (!animations)
|
|
61
|
+
return;
|
|
62
|
+
const animDef = animations[animIndex];
|
|
63
|
+
const channel = animDef?.channels[channelIndex];
|
|
64
|
+
if (channel) {
|
|
65
|
+
if (debugLog)
|
|
66
|
+
console.log("<<<", EXTENSION_NAME, "animation", animIndex, "channel", channelIndex, khr_animation);
|
|
67
|
+
channel.target = jsonTarget;
|
|
68
|
+
}
|
|
69
|
+
else if (debugLog)
|
|
70
|
+
console.log("WARN: ", EXTENSION_NAME, "animation", animIndex, "channel", channelIndex, "not found");
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
;
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
case PropertyType.ANIMATION_SAMPLER:
|
|
79
|
+
break;
|
|
80
|
+
case PropertyType.BUFFER:
|
|
81
|
+
this.onRead(context, "buffers", "bufferIndexMap");
|
|
82
|
+
break;
|
|
83
|
+
case PropertyType.CAMERA:
|
|
84
|
+
this.onRead(context, "cameras", "cameraIndexMap");
|
|
85
|
+
break;
|
|
86
|
+
case PropertyType.MATERIAL:
|
|
87
|
+
this.onRead(context, "materials", "materialIndexMap");
|
|
88
|
+
break;
|
|
89
|
+
case PropertyType.TEXTURE:
|
|
90
|
+
context.jsonDoc.json.textures?.forEach((textureDefinition, index) => {
|
|
91
|
+
if (!textureDefinition.extensions)
|
|
92
|
+
return;
|
|
93
|
+
const ext = textureDefinition.extensions[EXTENSION_NAME];
|
|
94
|
+
if (!ext)
|
|
95
|
+
return;
|
|
96
|
+
// TODO: textures and textureDefinition are not in the same order?
|
|
97
|
+
// see https://github.com/donmccurdy/glTF-Transform/blob/0e99c426b8ef41536228fe860ab8329be5d1d50f/packages/core/src/io/reader.ts#L132
|
|
98
|
+
// textures are in image order
|
|
99
|
+
// TODO: we need to use the image index to find the texture
|
|
100
|
+
let texture = getTexture(context, textureDefinition);
|
|
101
|
+
if (!texture) {
|
|
102
|
+
console.log("WARN: could potentially not find gltf transform texture for " + index);
|
|
103
|
+
texture = context.textures[index];
|
|
104
|
+
}
|
|
105
|
+
this.jsonPointerHandler.read(context, texture, ext);
|
|
106
|
+
const prop = this.createExtension();
|
|
107
|
+
texture.setExtension(EXTENSION_NAME, prop);
|
|
108
|
+
const handler = new TextureHandler(this.document, textureDefinition, EXTENSION_NAME, ext, debugLog);
|
|
109
|
+
prop.add(handler);
|
|
110
|
+
});
|
|
111
|
+
break;
|
|
112
|
+
case PropertyType.NODE:
|
|
113
|
+
this.onRead(context, "nodes", "nodeIndexMap");
|
|
114
|
+
break;
|
|
115
|
+
case PropertyType.MESH:
|
|
116
|
+
this.onRead(context, "meshes", "meshIndexMap");
|
|
117
|
+
break;
|
|
118
|
+
case PropertyType.PRIMITIVE:
|
|
119
|
+
context.jsonDoc.json.meshes?.forEach((meshDefinition, meshIndex) => {
|
|
120
|
+
const mesh = context.meshes[meshIndex];
|
|
121
|
+
const primitives = mesh.listPrimitives();
|
|
122
|
+
meshDefinition.primitives?.forEach((primitiveDefinition, primitiveIndex) => {
|
|
123
|
+
if (!primitiveDefinition.extensions)
|
|
124
|
+
return;
|
|
125
|
+
const ext = primitiveDefinition.extensions[EXTENSION_NAME];
|
|
126
|
+
if (!ext)
|
|
127
|
+
return;
|
|
128
|
+
const primitive = primitives[primitiveIndex];
|
|
129
|
+
this.jsonPointerHandler.read(context, primitive, ext);
|
|
130
|
+
const prop = this.createExtension();
|
|
131
|
+
primitive.setExtension(EXTENSION_NAME, prop);
|
|
132
|
+
// this is invoked on write
|
|
133
|
+
prop.add(ctx => {
|
|
134
|
+
const newMeshIndex = ctx.meshIndexMap.get(mesh);
|
|
135
|
+
const newMesh = ctx.jsonDoc.json.meshes[newMeshIndex];
|
|
136
|
+
const newPrimitive = newMesh.primitives[primitiveIndex];
|
|
137
|
+
newPrimitive.extensions = newPrimitive.extensions || {};
|
|
138
|
+
newPrimitive.extensions[EXTENSION_NAME] = ext;
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
break;
|
|
143
|
+
case PropertyType.ROOT:
|
|
144
|
+
const sourceArray = context.jsonDoc.json.extensions;
|
|
145
|
+
if (!sourceArray)
|
|
146
|
+
continue;
|
|
147
|
+
const ext = sourceArray[EXTENSION_NAME];
|
|
148
|
+
if (ext) {
|
|
149
|
+
this.lightmapWorkaround(context);
|
|
150
|
+
const root = this.document.getRoot();
|
|
151
|
+
this.jsonPointerHandler.read(context, root, ext);
|
|
152
|
+
const prop = this.createExtension();
|
|
153
|
+
root.setExtension(EXTENSION_NAME, prop);
|
|
154
|
+
prop.add(ctx => {
|
|
155
|
+
ctx.jsonDoc.json.extensions = ctx.jsonDoc.json.extensions || {};
|
|
156
|
+
ctx.jsonDoc.json.extensions[EXTENSION_NAME] = ext;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return this;
|
|
163
|
+
}
|
|
164
|
+
// this is temporarely here to prevent compression on textures named Lightmap
|
|
165
|
+
// until we have better control over the compression process
|
|
166
|
+
lightmapWorkaround(context) {
|
|
167
|
+
const textureDefinitions = context.jsonDoc.json.textures;
|
|
168
|
+
if (!textureDefinitions)
|
|
169
|
+
return;
|
|
170
|
+
const textures = this.document.getRoot().listTextures();
|
|
171
|
+
for (let i = 0; i < textures.length; i++) {
|
|
172
|
+
const tex = textures[i];
|
|
173
|
+
const texDef = textureDefinitions[i];
|
|
174
|
+
const name = texDef?.name;
|
|
175
|
+
if (name?.startsWith("Lightmap") && tex.getMimeType() !== "image/exr") {
|
|
176
|
+
if (debugLog)
|
|
177
|
+
console.log("Found lightmap texture", name, tex.getMimeType(), EXTENSION_NAME);
|
|
178
|
+
tex.setMimeType("image/lightmap");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
createExtension(name = '') {
|
|
183
|
+
return new OpaqueProperty(this.document.getGraph(), name);
|
|
184
|
+
;
|
|
185
|
+
}
|
|
186
|
+
onRead(context, gltfField, map) {
|
|
187
|
+
if (gltfField === undefined)
|
|
188
|
+
return;
|
|
189
|
+
try {
|
|
190
|
+
const sourceArray = context.jsonDoc.json[gltfField];
|
|
191
|
+
const target = context[gltfField];
|
|
192
|
+
sourceArray?.forEach((def, index) => {
|
|
193
|
+
if (!def.extensions || !def.extensions[EXTENSION_NAME])
|
|
194
|
+
return;
|
|
195
|
+
const ext = def.extensions[EXTENSION_NAME];
|
|
196
|
+
if (debugLog)
|
|
197
|
+
console.log("READ", index, ext);
|
|
198
|
+
const prop = this.createExtension();
|
|
199
|
+
// attach the property
|
|
200
|
+
target[index].setExtension(EXTENSION_NAME, prop);
|
|
201
|
+
// capture all the json pointers in the extension
|
|
202
|
+
this.jsonPointerHandler.read(context, target[index], ext);
|
|
203
|
+
// make sure we write our extension back to the gltf
|
|
204
|
+
prop.add(new OpaqueWriter(map, gltfField, ext));
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
console.error("ERR: " + error);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
write(context) {
|
|
212
|
+
if (opts?.write === false) {
|
|
213
|
+
return this;
|
|
214
|
+
}
|
|
215
|
+
if (debugLog)
|
|
216
|
+
console.log("WRITE " + EXTENSION_NAME);
|
|
217
|
+
this.jsonPointerHandler.resolveAndWrite(context);
|
|
218
|
+
const root = this.document.getRoot();
|
|
219
|
+
// for (const rootExt of this.roots) {
|
|
220
|
+
// rootExt.onWrite(context, root);
|
|
221
|
+
// }
|
|
222
|
+
for (const type of types) {
|
|
223
|
+
switch (type) {
|
|
224
|
+
case PropertyType.ACCESSOR:
|
|
225
|
+
this.onWrite(context, () => root.listAccessors());
|
|
226
|
+
break;
|
|
227
|
+
case PropertyType.ANIMATION:
|
|
228
|
+
this.onWrite(context, () => root.listAnimations());
|
|
229
|
+
break;
|
|
230
|
+
case PropertyType.ANIMATION_CHANNEL:
|
|
231
|
+
break;
|
|
232
|
+
case PropertyType.ANIMATION_SAMPLER:
|
|
233
|
+
break;
|
|
234
|
+
case PropertyType.BUFFER:
|
|
235
|
+
this.onWrite(context, () => root.listBuffers());
|
|
236
|
+
break;
|
|
237
|
+
case PropertyType.CAMERA:
|
|
238
|
+
this.onWrite(context, () => root.listCameras());
|
|
239
|
+
break;
|
|
240
|
+
case PropertyType.MATERIAL:
|
|
241
|
+
this.onWrite(context, () => root.listMaterials());
|
|
242
|
+
break;
|
|
243
|
+
case PropertyType.TEXTURE:
|
|
244
|
+
this.onWrite(context, () => root.listTextures());
|
|
245
|
+
break;
|
|
246
|
+
case PropertyType.NODE:
|
|
247
|
+
this.onWrite(context, () => root.listNodes());
|
|
248
|
+
break;
|
|
249
|
+
case PropertyType.MESH:
|
|
250
|
+
this.onWrite(context, () => root.listMeshes());
|
|
251
|
+
break;
|
|
252
|
+
case PropertyType.PRIMITIVE:
|
|
253
|
+
const allPrimitives = root.listMeshes().flatMap(mesh => mesh.listPrimitives());
|
|
254
|
+
for (const primitive of allPrimitives) {
|
|
255
|
+
const ext = primitive.getExtension(EXTENSION_NAME);
|
|
256
|
+
if (ext instanceof OpaqueProperty) {
|
|
257
|
+
ext.onWrite(context, primitive);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
case PropertyType.ROOT:
|
|
262
|
+
const prop = root.getExtension(EXTENSION_NAME);
|
|
263
|
+
if (prop instanceof OpaqueProperty) {
|
|
264
|
+
prop.onWrite(context, root);
|
|
265
|
+
}
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return this;
|
|
270
|
+
}
|
|
271
|
+
onWrite(context, get) {
|
|
272
|
+
const arr = get();
|
|
273
|
+
if (!arr)
|
|
274
|
+
return;
|
|
275
|
+
for (const entry of arr) {
|
|
276
|
+
const ext = entry.getExtension(EXTENSION_NAME);
|
|
277
|
+
if (ext instanceof OpaqueProperty) {
|
|
278
|
+
ext.onWrite(context, entry);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// function setExtension(ext: ExtensibleProperty, name, extensionProperty) {
|
|
285
|
+
// // for opaque extension we need to bypass the type system
|
|
286
|
+
// // because we generate the OpaqueProperty class at runtime and thus the method name for _validateParent is different when compiled
|
|
287
|
+
// // there's likely a better way to do this... as usual
|
|
288
|
+
// const pext = ext as any as { setRefMap: (mapName: string, key: string, value: any) => any }
|
|
289
|
+
// return pext.setRefMap('extensions', name, extensionProperty);
|
|
290
|
+
// }
|
|
291
|
+
class OpaqueProperty extends ExtensionProperty {
|
|
292
|
+
static EXTENSION_NAME = EXTENSION_NAME;
|
|
293
|
+
extensionName = typeof EXTENSION_NAME;
|
|
294
|
+
parentTypes = ExtensionPropertyTypes;
|
|
295
|
+
propertyType = "NEEDLE_opaque_property";
|
|
296
|
+
init() { }
|
|
297
|
+
writers = [];
|
|
298
|
+
add(writer) {
|
|
299
|
+
if (typeof writer === "function") {
|
|
300
|
+
writer = new GenericWriter(writer);
|
|
301
|
+
}
|
|
302
|
+
this.writers.push(writer);
|
|
303
|
+
}
|
|
304
|
+
onWrite(context, prop) {
|
|
305
|
+
for (const writer of this.writers) {
|
|
306
|
+
writer.write?.call(writer, context, prop);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
getExtensionDefinition() {
|
|
310
|
+
const ext = {};
|
|
311
|
+
for (const writer of this.writers) {
|
|
312
|
+
if (writer.assignTo)
|
|
313
|
+
writer.assignTo(ext);
|
|
314
|
+
}
|
|
315
|
+
return ext;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// called after the transformation
|
|
319
|
+
// writes the extension json back to the correct object
|
|
320
|
+
class OpaqueWriter {
|
|
321
|
+
map;
|
|
322
|
+
gltfField;
|
|
323
|
+
ext;
|
|
324
|
+
assignTo(obj) {
|
|
325
|
+
Object.assign(obj, this.ext);
|
|
326
|
+
}
|
|
327
|
+
constructor(map, gltfField, ext) {
|
|
328
|
+
this.map = map;
|
|
329
|
+
this.gltfField = gltfField;
|
|
330
|
+
this.ext = ext;
|
|
331
|
+
}
|
|
332
|
+
write(context, prop) {
|
|
333
|
+
if (prop && this.gltfField !== undefined) {
|
|
334
|
+
const map = context[this.map];
|
|
335
|
+
const index = map.get(prop);
|
|
336
|
+
const result = tryGetInArray(context.jsonDoc.json[this.gltfField], index);
|
|
337
|
+
writeExtension(result, EXTENSION_NAME, this.ext);
|
|
338
|
+
if (debugLog) {
|
|
339
|
+
// console.log("WRITE", EXTENSION_NAME, "to", this.gltfField, "at index", index);
|
|
340
|
+
console.log(result);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return NEEDLE_opaque;
|
|
346
|
+
}
|
|
347
|
+
/** use to register a generic callback */
|
|
348
|
+
class GenericWriter {
|
|
349
|
+
fn;
|
|
350
|
+
constructor(fn) {
|
|
351
|
+
this.fn = fn;
|
|
352
|
+
}
|
|
353
|
+
write(ctx) {
|
|
354
|
+
this.fn(ctx);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
class TextureHandler {
|
|
358
|
+
document;
|
|
359
|
+
textureDefinition;
|
|
360
|
+
index;
|
|
361
|
+
key;
|
|
362
|
+
ext;
|
|
363
|
+
debug = false;
|
|
364
|
+
assignTo(obj) {
|
|
365
|
+
Object.assign(obj, this.ext);
|
|
366
|
+
}
|
|
367
|
+
constructor(doc, def, key, ext, debug) {
|
|
368
|
+
this.document = doc;
|
|
369
|
+
this.textureDefinition = def;
|
|
370
|
+
this.key = key;
|
|
371
|
+
this.ext = ext;
|
|
372
|
+
this.debug = debug;
|
|
373
|
+
this.index = this.tryGetSourceIndex(this.textureDefinition);
|
|
374
|
+
if (debug)
|
|
375
|
+
console.log(def);
|
|
376
|
+
}
|
|
377
|
+
write(ctx) {
|
|
378
|
+
let foundTexture = false;
|
|
379
|
+
const textureDefs = ctx.jsonDoc.json.textures || [];
|
|
380
|
+
const imageIndex = this.index !== undefined ? this.index : this.tryGetSourceIndex(this.textureDefinition);
|
|
381
|
+
if (imageIndex === undefined) {
|
|
382
|
+
console.warn("WARN: no source index found for texture", this.key);
|
|
383
|
+
}
|
|
384
|
+
textureDefs?.forEach((textureDef, i) => {
|
|
385
|
+
if (foundTexture)
|
|
386
|
+
return;
|
|
387
|
+
const textureImageIndex = this.tryGetSourceIndex(textureDef);
|
|
388
|
+
if (imageIndex === textureImageIndex) {
|
|
389
|
+
if (!textureDef.extensions || !textureDef.extensions[this.key]) {
|
|
390
|
+
// assign texture extension again
|
|
391
|
+
textureDef.extensions = textureDef.extensions || {};
|
|
392
|
+
foundTexture = true;
|
|
393
|
+
if (this.debug)
|
|
394
|
+
console.log("Re-Assign extension:", this.key, "at", i);
|
|
395
|
+
const newExt = { ...this.ext };
|
|
396
|
+
if (newExt.source !== undefined)
|
|
397
|
+
newExt.source = imageIndex;
|
|
398
|
+
textureDef.extensions[this.key] = newExt;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
if (foundTexture === false) {
|
|
403
|
+
console.warn("WARN: failed to re-assign extension:", this.key, this.index, this.tryGetSourceIndex(this.textureDefinition), textureDefs.length);
|
|
404
|
+
}
|
|
405
|
+
// this.document
|
|
406
|
+
// .getRoot()
|
|
407
|
+
// .listTextures()
|
|
408
|
+
// .forEach((texture, index) => {
|
|
409
|
+
// if (foundTexture) return;
|
|
410
|
+
// if (!texture.getExtension(this.key)) return;
|
|
411
|
+
// const imageIndex = ctx.imageIndexMap.get(texture);
|
|
412
|
+
// });
|
|
413
|
+
}
|
|
414
|
+
tryGetSourceIndex(def) {
|
|
415
|
+
let index = def.source;
|
|
416
|
+
if (index !== undefined)
|
|
417
|
+
return index;
|
|
418
|
+
// if a glb was already compressed the source index might be missing so we also need to search in extensions
|
|
419
|
+
if (def.extensions) {
|
|
420
|
+
for (const ext of Object.keys(def.extensions)) {
|
|
421
|
+
const extDef = def.extensions[ext];
|
|
422
|
+
if (extDef.source !== undefined) {
|
|
423
|
+
index = extDef.source;
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return index;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
// declare type MaterialTextureCallback = (ctx: WriterContext, map: TemporaryMaterial, index: number) => void;
|
|
432
|
+
// class TemporaryMaterial implements IExtensionWriter {
|
|
433
|
+
// private readonly document: Document;
|
|
434
|
+
// private readonly material: Material;
|
|
435
|
+
// private readonly tex: Texture;
|
|
436
|
+
// private readonly callbacks: MaterialTextureCallback[] = [];
|
|
437
|
+
// constructor(doc: Document, tex: Texture, name: string) {
|
|
438
|
+
// console.log("Create material", name);
|
|
439
|
+
// this.document = doc;
|
|
440
|
+
// this.tex = tex;
|
|
441
|
+
// this.material = this.document
|
|
442
|
+
// .createMaterial(name)
|
|
443
|
+
// .setBaseColorTexture(tex);
|
|
444
|
+
// }
|
|
445
|
+
// add(cb: MaterialTextureCallback): TemporaryMaterial {
|
|
446
|
+
// this.callbacks.push(cb);
|
|
447
|
+
// return this;
|
|
448
|
+
// }
|
|
449
|
+
// write(context: WriterContext): void {
|
|
450
|
+
// const matIndex = context.materialIndexMap.get(this.material);
|
|
451
|
+
// if (matIndex !== undefined && matIndex !== -1) {
|
|
452
|
+
// console.log("Remove material", this.material.getName(), matIndex);
|
|
453
|
+
// context.jsonDoc.json.materials?.splice(matIndex, 1);
|
|
454
|
+
// if (this.callbacks.length > 0) {
|
|
455
|
+
// const texInfo = this.material.getBaseColorTextureInfo();
|
|
456
|
+
// if (texInfo) {
|
|
457
|
+
// const def = context.textureInfoDefMap.get(texInfo);
|
|
458
|
+
// if (def) {
|
|
459
|
+
// const index = def.index;
|
|
460
|
+
// for (const cb of this.callbacks) {
|
|
461
|
+
// cb(context, this, index);
|
|
462
|
+
// }
|
|
463
|
+
// }
|
|
464
|
+
// }
|
|
465
|
+
// }
|
|
466
|
+
// }
|
|
467
|
+
// }
|
|
468
|
+
// }
|
|
469
|
+
// json pointer -------------------------------------------------------------
|
|
470
|
+
// abstract class PointerProperty extends ExtensionProperty {
|
|
471
|
+
// abstract setPointer(texture: Texture, name: string);
|
|
472
|
+
// }
|
|
473
|
+
// const { R, G, B, A } = TextureChannel;
|
|
474
|
+
// function createPointerProperty(EXTENSION_NAME: string, doc: Document): PointerProperty {
|
|
475
|
+
// class TexturePointerPointerProperty extends ExtensionProperty<any> {
|
|
476
|
+
// static EXTENSION_NAME = EXTENSION_NAME;
|
|
477
|
+
// readonly extensionName: string = EXTENSION_NAME;
|
|
478
|
+
// parentTypes: PropertyType[] = ALL_PROPERTY_TYPES;
|
|
479
|
+
// propertyType = "NEEDLE_pointer_property";
|
|
480
|
+
// init() { }
|
|
481
|
+
// public setPointer(texture: Texture | null, name: string): this {
|
|
482
|
+
// console.log("SET REF", name);
|
|
483
|
+
// return this.setRef(name, texture, { channels: R | G | B | A });
|
|
484
|
+
// }
|
|
485
|
+
// }
|
|
486
|
+
// return new TexturePointerPointerProperty(doc.getGraph());
|
|
487
|
+
// }
|
|
488
|
+
class JsonPointerHandler {
|
|
489
|
+
id;
|
|
490
|
+
document;
|
|
491
|
+
map = new Map();
|
|
492
|
+
debug = false;
|
|
493
|
+
constructor(document, id) {
|
|
494
|
+
this.document = document;
|
|
495
|
+
this.id = id;
|
|
496
|
+
}
|
|
497
|
+
read(context, prop, obj, key, fullPath) {
|
|
498
|
+
const value = key !== undefined ? obj[key] : obj;
|
|
499
|
+
// the pointer path contains the full path from the root object to the json pointer
|
|
500
|
+
if (key !== undefined) {
|
|
501
|
+
fullPath ??= "";
|
|
502
|
+
fullPath += "/" + key;
|
|
503
|
+
}
|
|
504
|
+
switch (typeof value) {
|
|
505
|
+
case 'object':
|
|
506
|
+
if (value !== null && value !== undefined) {
|
|
507
|
+
for (const key of Object.keys(value)) {
|
|
508
|
+
this.read(context, prop, value, key, fullPath);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
break;
|
|
512
|
+
case "string":
|
|
513
|
+
if (value && key) {
|
|
514
|
+
let prefix = "/textures/";
|
|
515
|
+
let isTexturePointer = value.startsWith(prefix);
|
|
516
|
+
if (!isTexturePointer) {
|
|
517
|
+
// backwards compatibility for old format where it didnt start with /textures/ but with textures/
|
|
518
|
+
prefix = "textures/";
|
|
519
|
+
isTexturePointer = value.startsWith(prefix);
|
|
520
|
+
}
|
|
521
|
+
if (isTexturePointer) {
|
|
522
|
+
if (this.debug)
|
|
523
|
+
console.log("Read pointer", value);
|
|
524
|
+
let pointer = this.map.get(value);
|
|
525
|
+
if (!pointer) {
|
|
526
|
+
const length = prefix.length;
|
|
527
|
+
const index = parseInt(value.substring(length));
|
|
528
|
+
const path = value.substring(0, length);
|
|
529
|
+
const definition = context.jsonDoc.json.textures[index];
|
|
530
|
+
const imageIndex = findSource(definition);
|
|
531
|
+
if (this.debug)
|
|
532
|
+
console.log("Texture index", index, imageIndex, context.jsonDoc.json.textures.length, "Textures:" + context.textures.length);
|
|
533
|
+
const object = tryGetInArray(context.textures, imageIndex);
|
|
534
|
+
if (object) {
|
|
535
|
+
const res = new TexturePointerResolver(this.document, object, definition);
|
|
536
|
+
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)
|
|
537
|
+
pointer = new JsonPointer(path, res);
|
|
538
|
+
pointer.debug = this.debug;
|
|
539
|
+
this.map.set(value, pointer);
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
console.warn("Texture not found", fullPath, value);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (pointer) {
|
|
546
|
+
if (this.debug)
|
|
547
|
+
console.log("> Register pointer", this.id + "/" + fullPath + " : " + value);
|
|
548
|
+
pointer?.register(obj, key);
|
|
549
|
+
}
|
|
550
|
+
else
|
|
551
|
+
console.warn("WARN: failed registering pointer", fullPath, value);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
resolveAndWrite(context) {
|
|
558
|
+
this.map?.forEach(ptr => {
|
|
559
|
+
ptr.resolve(context);
|
|
560
|
+
ptr.write();
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
class JsonPointer {
|
|
565
|
+
debug = false;
|
|
566
|
+
resolver;
|
|
567
|
+
pointerBase;
|
|
568
|
+
constructor(pointerBase, resolver) {
|
|
569
|
+
this.pointerBase = pointerBase;
|
|
570
|
+
this.resolver = resolver;
|
|
571
|
+
}
|
|
572
|
+
_objects = [];
|
|
573
|
+
_props = [];
|
|
574
|
+
register(obj, prop) {
|
|
575
|
+
this._objects.push(obj);
|
|
576
|
+
this._props.push(prop);
|
|
577
|
+
}
|
|
578
|
+
newPointer;
|
|
579
|
+
resolve(context) {
|
|
580
|
+
//@ts-ignore
|
|
581
|
+
this.resolver.debug = this.debug;
|
|
582
|
+
const index = this.resolver.resolve(context);
|
|
583
|
+
this.newPointer = this.pointerBase + index;
|
|
584
|
+
if (this.debug)
|
|
585
|
+
console.log("RESOLVED", this.pointerBase, index, " to → ", this.newPointer);
|
|
586
|
+
}
|
|
587
|
+
write() {
|
|
588
|
+
for (let i = 0; i < this._objects.length; i++) {
|
|
589
|
+
const obj = this._objects[i];
|
|
590
|
+
const prop = this._props[i];
|
|
591
|
+
if (this.debug)
|
|
592
|
+
console.log("< Resolved pointer", this.newPointer, "in", prop);
|
|
593
|
+
obj[prop] = this.newPointer;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
class PointerResolver {
|
|
598
|
+
document;
|
|
599
|
+
/* the object originally being referenced by the pointer */
|
|
600
|
+
inputObject;
|
|
601
|
+
/* the original json definition */
|
|
602
|
+
inputDefinition;
|
|
603
|
+
constructor(doc, obj, def) {
|
|
604
|
+
this.document = doc;
|
|
605
|
+
this.inputObject = obj;
|
|
606
|
+
this.inputDefinition = def;
|
|
607
|
+
}
|
|
608
|
+
debug = false;
|
|
609
|
+
}
|
|
610
|
+
class TexturePointerResolver extends PointerResolver {
|
|
611
|
+
_debugName;
|
|
612
|
+
_samplerDef;
|
|
613
|
+
init(context) {
|
|
614
|
+
this._debugName = this.inputDefinition.name ?? this.inputDefinition.source?.toString() ?? "unknown";
|
|
615
|
+
const samplerIndex = this.inputDefinition.sampler;
|
|
616
|
+
if (samplerIndex !== undefined) {
|
|
617
|
+
this._samplerDef = context.jsonDoc.json.samplers[samplerIndex];
|
|
618
|
+
}
|
|
619
|
+
if (this.debug)
|
|
620
|
+
console.log(this._debugName, this._samplerDef);
|
|
621
|
+
}
|
|
622
|
+
resolve(context) {
|
|
623
|
+
const imgIndex = context.imageIndexMap.get(this.inputObject);
|
|
624
|
+
if (imgIndex === undefined) {
|
|
625
|
+
console.error("ERR: image is missing", imgIndex);
|
|
626
|
+
return -1;
|
|
627
|
+
}
|
|
628
|
+
// ensure we have a texture array
|
|
629
|
+
context.jsonDoc.json.textures = context.jsonDoc.json.textures || [];
|
|
630
|
+
// try find the texture that points to the same image source
|
|
631
|
+
for (let i = 0; i < context.jsonDoc.json.textures.length; i++) {
|
|
632
|
+
const textureDef = context.jsonDoc.json.textures[i];
|
|
633
|
+
if (findSource(textureDef) === imgIndex) {
|
|
634
|
+
if (textureDef.sampler === undefined) {
|
|
635
|
+
textureDef.sampler = this.findOrCreateSampler(context.jsonDoc.json);
|
|
636
|
+
}
|
|
637
|
+
if (this.debug)
|
|
638
|
+
console.log("Found texture def at " + i, "using image", imgIndex, textureDef);
|
|
639
|
+
return i;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
// the texture was not found - add it
|
|
643
|
+
// create a new texture def entry
|
|
644
|
+
if (this.debug)
|
|
645
|
+
console.log("Creating new texture", this._debugName);
|
|
646
|
+
const hasExtensionWithSource = this.inputDefinition.extensions && Object.keys(this.inputDefinition.extensions).some(key => this.inputDefinition.extensions[key]["source"] !== undefined);
|
|
647
|
+
const textureDef = {
|
|
648
|
+
source: hasExtensionWithSource ? undefined : imgIndex,
|
|
649
|
+
sampler: this.findOrCreateSampler(context.jsonDoc.json),
|
|
650
|
+
extensions: this.inputDefinition.extensions,
|
|
651
|
+
extras: this.inputDefinition.extras,
|
|
652
|
+
};
|
|
653
|
+
context.jsonDoc.json.textures.push(textureDef);
|
|
654
|
+
return context.jsonDoc.json.textures.length - 1;
|
|
655
|
+
}
|
|
656
|
+
findOrCreateSampler(json) {
|
|
657
|
+
json.samplers = json.samplers || [];
|
|
658
|
+
if (this._samplerDef === undefined) {
|
|
659
|
+
console.log("WARN: Creating default sampler", this._debugName);
|
|
660
|
+
// create a default sampler
|
|
661
|
+
this._samplerDef = {
|
|
662
|
+
magFilter: 9729,
|
|
663
|
+
minFilter: 9729,
|
|
664
|
+
wrapS: 33648,
|
|
665
|
+
wrapT: 33648
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
for (let i = 0; i < json.samplers.length; i++) {
|
|
670
|
+
const sampler = json.samplers[i];
|
|
671
|
+
if (sampler.minFilter === this._samplerDef.minFilter &&
|
|
672
|
+
sampler.magFilter === this._samplerDef.magFilter &&
|
|
673
|
+
sampler.wrapS === this._samplerDef.wrapS &&
|
|
674
|
+
sampler.wrapT === this._samplerDef.wrapT) {
|
|
675
|
+
return i;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
// append previous sampler
|
|
680
|
+
json.samplers.push(this._samplerDef);
|
|
681
|
+
return json.samplers.length - 1;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
function findSource(texDef) {
|
|
685
|
+
if (texDef.source !== undefined) {
|
|
686
|
+
return texDef.source;
|
|
687
|
+
}
|
|
688
|
+
if (texDef.extensions) {
|
|
689
|
+
const keys = Object.keys(texDef.extensions);
|
|
690
|
+
for (let i = 0; i < keys.length; i++) {
|
|
691
|
+
const key = keys[i];
|
|
692
|
+
const ext = texDef.extensions[key];
|
|
693
|
+
if (ext["source"] !== undefined) {
|
|
694
|
+
return ext["source"];
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return undefined;
|
|
699
|
+
}
|