@threepipe/plugin-gltf-transform 0.2.2 → 0.3.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 +22 -3
- package/README.md +2 -2
- package/dist/GLTFDracoExportPlugin.d.ts +2 -44
- package/dist/GLTFDracoExportPlugin.d.ts.map +1 -1
- package/dist/GLTFDracoExporter.d.ts +5 -40
- package/dist/GLTFDracoExporter.d.ts.map +1 -1
- package/dist/GLTFDracoExporterBase.d.ts +49 -0
- package/dist/GLTFDracoExporterBase.d.ts.map +1 -0
- package/dist/GLTFSpecGlossinessConverterPlugin.d.ts +10 -31
- package/dist/GLTFSpecGlossinessConverterPlugin.d.ts.map +1 -1
- package/dist/GLTFSpecGlossinessConverterPluginBase.d.ts +45 -0
- package/dist/GLTFSpecGlossinessConverterPluginBase.d.ts.map +1 -0
- package/dist/dependencies.txt +12 -12
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +119 -136
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +117 -134
- package/dist/index.mjs.map +1 -1
- package/docs/assets/hierarchy.js +1 -1
- package/docs/assets/icons.js +1 -1
- package/docs/assets/icons.svg +1 -1
- package/docs/assets/main.js +2 -2
- package/docs/assets/navigation.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/assets/style.css +3 -3
- package/docs/classes/GLTFDracoExportPlugin.html +7 -6
- package/docs/classes/GLTFDracoExporter.html +15 -14
- package/docs/classes/GLTFDracoExporterBase.html +37 -0
- package/docs/classes/GLTFSpecGlossinessConverterPlugin.html +16 -14
- package/docs/classes/GLTFSpecGlossinessConverterPluginBase.html +37 -0
- package/docs/classes/GLTFViewerConfigExtensionGP.html +5 -0
- package/docs/enums/EncoderMethod.html +2 -2
- package/docs/functions/createGenericExtensionClass.html +1 -1
- package/docs/functions/gltfKhrPbrSpecularGlossinessConverter.html +2 -1
- package/docs/functions/gltfKhrPbrSpecularGlossinessConverterBase.html +1 -0
- package/docs/hierarchy.html +1 -1
- package/docs/index.html +1 -1
- package/package.json +24 -2
- package/src/GLTFDracoExportPlugin.ts +2 -2
- package/src/GLTFDracoExporter.ts +10 -417
- package/src/GLTFDracoExporterBase.ts +427 -0
- package/src/GLTFSpecGlossinessConverterPlugin.ts +14 -115
- package/src/GLTFSpecGlossinessConverterPluginBase.ts +129 -0
- package/src/index.ts +3 -1
- package/tsconfig.json +1 -0
- package/docs/.nojekyll +0 -1
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Extension,
|
|
3
|
+
ExtensionProperty,
|
|
4
|
+
GLTF,
|
|
5
|
+
Graph,
|
|
6
|
+
PlatformIO,
|
|
7
|
+
Property,
|
|
8
|
+
PropertyType,
|
|
9
|
+
ReaderContext,
|
|
10
|
+
Texture,
|
|
11
|
+
TextureChannel,
|
|
12
|
+
TextureInfo,
|
|
13
|
+
WriterContext,
|
|
14
|
+
} from '@gltf-transform/core'
|
|
15
|
+
import {ALL_EXTENSIONS, KHRDracoMeshCompression} from '@gltf-transform/extensions'
|
|
16
|
+
|
|
17
|
+
// From the public API: @gltf-transform/extensions does not export its encoder module path.
|
|
18
|
+
export type EncoderOptions = Parameters<KHRDracoMeshCompression['setEncoderOptions']>[0]
|
|
19
|
+
import {DRACOLoader2, GLTFExporter2, GLTFExporter2Options, GLTFViewerConfigExtension, IExportWriter} from 'threepipe'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* GLTF Draco Exporter Base
|
|
23
|
+
*
|
|
24
|
+
* Extension of GLTFExporter2 that runs the output through gltf-transform for draco compression.
|
|
25
|
+
* Takes a PlatformIO instance as a constructor parameter, allowing use with both WebIO (browser) and NodeIO (Node.js).
|
|
26
|
+
*/
|
|
27
|
+
export class GLTFDracoExporterBase extends GLTFExporter2 implements IExportWriter {
|
|
28
|
+
public loader?: DRACOLoader2 // required for loading draco libs.
|
|
29
|
+
protected _io: PlatformIO
|
|
30
|
+
private _loadedLibs = false
|
|
31
|
+
private _encoderOptions: EncoderOptions
|
|
32
|
+
|
|
33
|
+
constructor(io: PlatformIO, encoderOptions?: EncoderOptions, loader?: DRACOLoader2) {
|
|
34
|
+
super()
|
|
35
|
+
encoderOptions = encoderOptions || {
|
|
36
|
+
method: KHRDracoMeshCompression.EncoderMethod.EDGEBREAKER,
|
|
37
|
+
encodeSpeed: 5,
|
|
38
|
+
}
|
|
39
|
+
this._io = io.registerExtensions(ALL_EXTENSIONS)
|
|
40
|
+
this._encoderOptions = encoderOptions
|
|
41
|
+
|
|
42
|
+
if (loader) {
|
|
43
|
+
this.loader = loader
|
|
44
|
+
this.loader.preload(true, true)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
preload(): this {
|
|
50
|
+
this._loadLibs()
|
|
51
|
+
return this
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private async _loadLibs() {
|
|
55
|
+
if (this._loadedLibs || !this.loader) return
|
|
56
|
+
|
|
57
|
+
const libs = await Promise.all([
|
|
58
|
+
this.loader.initEncoder(),
|
|
59
|
+
this.loader.initDecoder(),
|
|
60
|
+
])
|
|
61
|
+
this._io.registerDependencies({
|
|
62
|
+
['draco3d.encoder']: libs[0],
|
|
63
|
+
['draco3d.decoder']: libs[1], // only required if we are loading a draco compressed gltf
|
|
64
|
+
})
|
|
65
|
+
this._loadedLibs = true
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async parseAsync(obj: any, {compress = false, dracoOptions, ...options}: {compress: boolean, dracoOptions?: EncoderOptions} & GLTFExporter2Options, throwOnError = false): Promise<Blob> {
|
|
70
|
+
if (!this.loader) {
|
|
71
|
+
console.error('GLTFDracoExporter: No DRACOLoader2 instance provided')
|
|
72
|
+
return super.parseAsync(obj, options)
|
|
73
|
+
}
|
|
74
|
+
await this._loadLibs()
|
|
75
|
+
|
|
76
|
+
const ops = {...options}
|
|
77
|
+
if (compress) {
|
|
78
|
+
// externalImagesInExtras: this is required because gltf-transform doesn't support external images in glb
|
|
79
|
+
// see https://github.com/donmccurdy/glTF-Transform/discussions/644
|
|
80
|
+
ops.externalImagesInExtras = true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const uncompressed = await new Promise((resolve, reject) => this.parse(obj, resolve, reject, ops)) as any
|
|
84
|
+
|
|
85
|
+
const uncompressedBlob = await super.parseAsync(uncompressed, ops)
|
|
86
|
+
if (!compress) return uncompressedBlob
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
if (!uncompressed) throw new Error('GLTFDracoExporter: gltf is null')
|
|
90
|
+
|
|
91
|
+
let gltf = uncompressed
|
|
92
|
+
|
|
93
|
+
const bytes = (gltf as ArrayBuffer).byteLength || Infinity
|
|
94
|
+
|
|
95
|
+
const iDocument = await (typeof gltf === 'object' && !(gltf as any).byteLength ? this._io.readJSON({
|
|
96
|
+
json: gltf as GLTF.IGLTF,
|
|
97
|
+
resources: {},
|
|
98
|
+
}) : this._io.readBinary(new Uint8Array(gltf as ArrayBuffer)))
|
|
99
|
+
|
|
100
|
+
// iDocument.createExtension(GLTFViewerConfigExtensionGP)
|
|
101
|
+
iDocument.createExtension(KHRDracoMeshCompression)
|
|
102
|
+
.setRequired(true)
|
|
103
|
+
.setEncoderOptions({...this._encoderOptions, ...dracoOptions ?? {}})
|
|
104
|
+
|
|
105
|
+
if (ops.exportExt === 'glb') {
|
|
106
|
+
gltf = await this._io.writeBinary(iDocument)
|
|
107
|
+
if (isFinite(bytes)) {
|
|
108
|
+
console.log('DRACO Compression ratio: ' + ((gltf as ArrayBuffer).byteLength / bytes).toFixed(5))
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
const jDoc = await this._io.writeJSON(iDocument)
|
|
112
|
+
gltf = jDoc.json
|
|
113
|
+
if (Object.values(jDoc.resources).filter(v => v).length > 0) {
|
|
114
|
+
console.warn('DRACOExporter: extra resources in resources not supported properly')
|
|
115
|
+
;(gltf as any).resources = jDoc.resources
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
gltf.__isGLTFOutput = true
|
|
120
|
+
const blob = await super.parseAsync(gltf, ops) as any // this will just convert it to blob because __isGLTFOutput is set (checked in GLTFExporter2)
|
|
121
|
+
if (!blob) throw new Error('GLTFDracoExporter: blob is null')
|
|
122
|
+
blob.ext = 'glb'
|
|
123
|
+
;(blob as any).__uncompressed = uncompressedBlob
|
|
124
|
+
return blob
|
|
125
|
+
} catch (e) {
|
|
126
|
+
if (throwOnError) throw e
|
|
127
|
+
console.error('Unable to compress glb with DRACO extension, fallback to uncompressed')
|
|
128
|
+
console.error(e)
|
|
129
|
+
return uncompressedBlob
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
addExtension(...extension: (typeof Extension)[]): this {
|
|
134
|
+
this._io.registerExtensions(extension)
|
|
135
|
+
return this
|
|
136
|
+
}
|
|
137
|
+
createAndAddExtension(name: string, textures?: Record<string, string|number>): this {
|
|
138
|
+
return this.addExtension(createGenericExtensionClass(name, textures))
|
|
139
|
+
}
|
|
140
|
+
createAndAddExtensions(extensions: [string, Record<string, string|number>|undefined][]): this {
|
|
141
|
+
return this.addExtension(...extensions.map(e=> createGenericExtensionClass(e[0], e[1])))
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
declare module 'threepipe'{
|
|
146
|
+
interface GLTFExporter2Options {
|
|
147
|
+
compress?: boolean
|
|
148
|
+
dracoOptions?: EncoderOptions
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// for @gltf-transform/core
|
|
153
|
+
class ViewerJSONExtensionProperty extends ExtensionProperty {
|
|
154
|
+
readonly extensionName: string = GLTFViewerConfigExtension.ViewerConfigGLTFExtension
|
|
155
|
+
readonly parentTypes: string[] = [PropertyType.SCENE]
|
|
156
|
+
readonly propertyType: string = 'ViewerJSON'
|
|
157
|
+
|
|
158
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
159
|
+
protected init(): void {return}
|
|
160
|
+
|
|
161
|
+
}
|
|
162
|
+
export class GLTFViewerConfigExtensionGP extends Extension {
|
|
163
|
+
public readonly extensionName = GLTFViewerConfigExtension.ViewerConfigGLTFExtension
|
|
164
|
+
public static readonly EXTENSION_NAME = GLTFViewerConfigExtension.ViewerConfigGLTFExtension
|
|
165
|
+
private _viewerConfig: any = {}
|
|
166
|
+
// private _texturesRef: [any, Texture][] = []
|
|
167
|
+
|
|
168
|
+
read(context: ReaderContext): this {
|
|
169
|
+
this._viewerConfig = {}
|
|
170
|
+
context.jsonDoc.json.scenes?.forEach((sceneDef, sceneIndex)=>{
|
|
171
|
+
if (sceneDef.extensions && sceneDef.extensions[GLTFViewerConfigExtension.ViewerConfigGLTFExtension]) {
|
|
172
|
+
const prop = new ViewerJSONExtensionProperty(this.document.getGraph())
|
|
173
|
+
context.scenes[sceneIndex].setExtension(GLTFViewerConfigExtension.ViewerConfigGLTFExtension, prop)
|
|
174
|
+
this._viewerConfig = sceneDef.extensions[GLTFViewerConfigExtension.ViewerConfigGLTFExtension] as any
|
|
175
|
+
// prop.setExtras()
|
|
176
|
+
|
|
177
|
+
/*
|
|
178
|
+
const buffers = [] as any[]
|
|
179
|
+
|
|
180
|
+
Object.values(viewerConfig.resources).forEach((res: any) => {
|
|
181
|
+
Object.values(res).forEach((item: any) => {
|
|
182
|
+
if (!item.url) return
|
|
183
|
+
if (item.url.data?.image !== null) {
|
|
184
|
+
buffers.push(item.url)
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
const jsonDoc = context.jsonDoc
|
|
189
|
+
console.log(buffers)
|
|
190
|
+
for (const buffer of buffers) {
|
|
191
|
+
const img = buffer.data.image as number
|
|
192
|
+
const imageDef = jsonDoc.json.images![img]
|
|
193
|
+
const bufferViewDef = jsonDoc.json.bufferViews![imageDef.bufferView!]
|
|
194
|
+
const bufferDef = jsonDoc.json.buffers![bufferViewDef.buffer]
|
|
195
|
+
const bufferData = bufferDef.uri ? jsonDoc.resources[bufferDef.uri] : jsonDoc.resources[GLB_BUFFER]
|
|
196
|
+
const byteOffset = bufferViewDef.byteOffset || 0
|
|
197
|
+
const byteLength = bufferViewDef.byteLength
|
|
198
|
+
const imageData = bufferData.slice(byteOffset, byteOffset + byteLength)
|
|
199
|
+
const texture = this.document.createTexture(imageDef.name)
|
|
200
|
+
texture.setImage(imageData)
|
|
201
|
+
this._texturesRef.push([buffer, texture])
|
|
202
|
+
}
|
|
203
|
+
*/
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
return this
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
write(context: WriterContext): this {
|
|
212
|
+
this.document.getRoot().listScenes().forEach((scene)=>{
|
|
213
|
+
const prop = scene.getExtension(GLTFViewerConfigExtension.ViewerConfigGLTFExtension)
|
|
214
|
+
if (prop) {
|
|
215
|
+
const sceneDef = context.jsonDoc.json.scenes?.[context.jsonDoc.json.scene || 0] // todo: get proper scene index, if working with multiple scenes
|
|
216
|
+
if (sceneDef && Object.keys(this._viewerConfig).length > 0) {
|
|
217
|
+
sceneDef.extensions = sceneDef.extensions || {}
|
|
218
|
+
|
|
219
|
+
/*
|
|
220
|
+
console.log(context.jsonDoc.json.images)
|
|
221
|
+
for (const [buffer, texture] of this._texturesRef) {
|
|
222
|
+
const imageDef = context.createPropertyDef(texture) as GLTF.IImage
|
|
223
|
+
context.createImageData(imageDef, texture.getImage()!, texture)
|
|
224
|
+
buffer.data.image = context.jsonDoc.json.images!.push(imageDef) - 1
|
|
225
|
+
context.imageIndexMap.set(texture, buffer.data.image)
|
|
226
|
+
}
|
|
227
|
+
console.log(context.jsonDoc.json)
|
|
228
|
+
*/
|
|
229
|
+
|
|
230
|
+
sceneDef.extensions[GLTFViewerConfigExtension.ViewerConfigGLTFExtension] = this._viewerConfig
|
|
231
|
+
|
|
232
|
+
// this._texturesRef = []
|
|
233
|
+
this._viewerConfig = {}
|
|
234
|
+
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
return this
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
required = true
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
class GenericExtensionProperty extends ExtensionProperty<any> {
|
|
246
|
+
readonly extensionName: string
|
|
247
|
+
readonly parentTypes: string[] = [PropertyType.MATERIAL, PropertyType.MESH, PropertyType.NODE, PropertyType.SCENE]
|
|
248
|
+
readonly propertyType: string = 'GenericExtension'
|
|
249
|
+
textures: Record<string, [TextureInfo, Texture|null]> = {}
|
|
250
|
+
|
|
251
|
+
addTexture(key: string, texInfo: TextureInfo, texture: Texture | null, channels = 0x1111) {
|
|
252
|
+
this.setRef(key, texture, {channels})
|
|
253
|
+
this.textures[key] = [texInfo, texture]
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
constructor(graph: Graph<Property>, name: string, extensionName: string) {
|
|
257
|
+
super(graph, name)
|
|
258
|
+
this.extensionName = extensionName
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
262
|
+
protected init(): void {return}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// see transmission extension for reference
|
|
266
|
+
abstract class GenericExtension extends Extension {
|
|
267
|
+
abstract readonly extensionName: string
|
|
268
|
+
|
|
269
|
+
textureChannels: Record<string, number> = {}
|
|
270
|
+
|
|
271
|
+
read(context: ReaderContext): this {
|
|
272
|
+
const jsonDoc = context.jsonDoc
|
|
273
|
+
// console.log(jsonDoc)
|
|
274
|
+
const materialDefs = jsonDoc.json.materials || []
|
|
275
|
+
const textureDefs = jsonDoc.json.textures || []
|
|
276
|
+
materialDefs.forEach((materialDef, materialIndex) => {
|
|
277
|
+
if (materialDef.extensions && materialDef.extensions[this.extensionName]) {
|
|
278
|
+
const paramsExt = new GenericExtensionProperty(this.document.getGraph(), '', this.extensionName)
|
|
279
|
+
context.materials[materialIndex].setExtension(this.extensionName, paramsExt)
|
|
280
|
+
const paramsExtDef = materialDef.extensions[this.extensionName] as Record<string, any>
|
|
281
|
+
const paramsExtDef2 = {...paramsExtDef}
|
|
282
|
+
|
|
283
|
+
for (const [key, value] of Object.entries(paramsExtDef2)) {
|
|
284
|
+
if (typeof value?.index === 'number') { // this is a texture...
|
|
285
|
+
const textureInfoDef = value
|
|
286
|
+
const source = textureDefs[textureInfoDef.index]?.source
|
|
287
|
+
if (typeof source !== 'number') {
|
|
288
|
+
console.warn('GLTF Pipeline: source texture not found for texture info', textureInfoDef)
|
|
289
|
+
continue
|
|
290
|
+
}
|
|
291
|
+
const texture = context.textures[source]
|
|
292
|
+
const texInfo = new TextureInfo(this.document.getGraph())
|
|
293
|
+
const channels = this.textureChannels[key] ?? 0x1111
|
|
294
|
+
paramsExt.addTexture(key, texInfo, texture, channels)
|
|
295
|
+
context.setTextureInfo(texInfo, textureInfoDef)
|
|
296
|
+
delete paramsExtDef2[key]
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
paramsExt.setExtras(paramsExtDef2)
|
|
301
|
+
// console.log({...paramsExtDef})
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
const meshDefs = jsonDoc.json.meshes || []
|
|
305
|
+
meshDefs.forEach((meshDef, meshIndex) => {
|
|
306
|
+
if (meshDef.extensions && meshDef.extensions[this.extensionName]) {
|
|
307
|
+
const paramsExt = new GenericExtensionProperty(this.document.getGraph(), '', this.extensionName)
|
|
308
|
+
context.meshes[meshIndex].setExtension(this.extensionName, paramsExt)
|
|
309
|
+
const paramsExtDef = meshDef.extensions[this.extensionName] as Record<string, any>
|
|
310
|
+
paramsExt.setExtras(paramsExtDef)
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
const nodeDefs = jsonDoc.json.nodes || []
|
|
314
|
+
nodeDefs.forEach((nodeDef, nodeIndex) => {
|
|
315
|
+
if (nodeDef.extensions && nodeDef.extensions[this.extensionName]) {
|
|
316
|
+
const paramsExt = new GenericExtensionProperty(this.document.getGraph(), '', this.extensionName)
|
|
317
|
+
context.nodes[nodeIndex].setExtension(this.extensionName, paramsExt)
|
|
318
|
+
const paramsExtDef = nodeDef.extensions[this.extensionName] as Record<string, any>
|
|
319
|
+
paramsExt.setExtras(paramsExtDef)
|
|
320
|
+
// console.log(paramsExtDef)
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
const sceneDefs = jsonDoc.json.scenes || []
|
|
324
|
+
sceneDefs.forEach((sceneDef, sceneIndex) => {
|
|
325
|
+
if (sceneDef.extensions && sceneDef.extensions[this.extensionName]) {
|
|
326
|
+
const paramsExt = new GenericExtensionProperty(this.document.getGraph(), '', this.extensionName)
|
|
327
|
+
context.scenes[sceneIndex].setExtension(this.extensionName, paramsExt)
|
|
328
|
+
const paramsExtDef = sceneDef.extensions[this.extensionName] as Record<string, any>
|
|
329
|
+
paramsExt.setExtras(paramsExtDef)
|
|
330
|
+
// console.log(paramsExtDef)
|
|
331
|
+
}
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
return this
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
write(context: WriterContext): this {
|
|
338
|
+
const jsonDoc = context.jsonDoc
|
|
339
|
+
this.document.getRoot()
|
|
340
|
+
.listMaterials()
|
|
341
|
+
.forEach((material) => {
|
|
342
|
+
const paramsExt = material.getExtension<GenericExtensionProperty>(this.extensionName)
|
|
343
|
+
// console.log(paramsExt)
|
|
344
|
+
if (paramsExt) {
|
|
345
|
+
const materialIndex = context.materialIndexMap.get(material)!
|
|
346
|
+
const materialDef = jsonDoc.json.materials![materialIndex]
|
|
347
|
+
materialDef.extensions = materialDef.extensions || {}
|
|
348
|
+
const extensionDef = paramsExt.getExtras()
|
|
349
|
+
const extensionDef2 = {...extensionDef}
|
|
350
|
+
|
|
351
|
+
// console.log(paramsExt.textures)
|
|
352
|
+
for (const [key, value] of Object.entries(paramsExt.textures)) {
|
|
353
|
+
const textureInfo = value[0]
|
|
354
|
+
const textureLink = value[1]
|
|
355
|
+
const texture = textureLink
|
|
356
|
+
|
|
357
|
+
if (texture)
|
|
358
|
+
extensionDef2[key] = context.createTextureInfoDef(texture, textureInfo)
|
|
359
|
+
|
|
360
|
+
// console.log(texture)
|
|
361
|
+
|
|
362
|
+
}
|
|
363
|
+
// console.log(extensionDef2)
|
|
364
|
+
|
|
365
|
+
materialDef.extensions[this.extensionName] = extensionDef2
|
|
366
|
+
}
|
|
367
|
+
})
|
|
368
|
+
this.document.getRoot()
|
|
369
|
+
.listMeshes()
|
|
370
|
+
.forEach((mesh) => {
|
|
371
|
+
const paramsExt = mesh.getExtension<GenericExtensionProperty>(this.extensionName)
|
|
372
|
+
if (paramsExt) {
|
|
373
|
+
const meshIndex = context.meshIndexMap.get(mesh)!
|
|
374
|
+
const meshDef = jsonDoc.json.meshes![meshIndex]
|
|
375
|
+
meshDef.extensions = meshDef.extensions || {}
|
|
376
|
+
meshDef.extensions[this.extensionName] = paramsExt.getExtras()
|
|
377
|
+
}
|
|
378
|
+
})
|
|
379
|
+
this.document.getRoot()
|
|
380
|
+
.listNodes()
|
|
381
|
+
.forEach((node) => {
|
|
382
|
+
const paramsExt = node.getExtension<GenericExtensionProperty>(this.extensionName)
|
|
383
|
+
if (paramsExt) {
|
|
384
|
+
const nodeIndex = context.nodeIndexMap.get(node)!
|
|
385
|
+
const nodeDef = jsonDoc.json.nodes![nodeIndex]
|
|
386
|
+
nodeDef.extensions = nodeDef.extensions || {}
|
|
387
|
+
nodeDef.extensions[this.extensionName] = paramsExt.getExtras()
|
|
388
|
+
}
|
|
389
|
+
})
|
|
390
|
+
this.document.getRoot()
|
|
391
|
+
.listScenes()
|
|
392
|
+
.forEach((scene) => {
|
|
393
|
+
const paramsExt = scene.getExtension<GenericExtensionProperty>(this.extensionName)
|
|
394
|
+
if (paramsExt) {
|
|
395
|
+
const sceneIndex = context.jsonDoc.json.scene || 0 // todo: get proper scene index, if working with multiple scenes, this will do the default one.
|
|
396
|
+
const sceneDef = jsonDoc.json.scenes![sceneIndex]
|
|
397
|
+
if (!sceneDef) return
|
|
398
|
+
sceneDef.extensions = sceneDef.extensions || {}
|
|
399
|
+
sceneDef.extensions[this.extensionName] = paramsExt.getExtras()
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
return this
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function stringToChannel(s: string) {
|
|
409
|
+
let r = 0
|
|
410
|
+
if (s.includes('R')) r |= TextureChannel.R
|
|
411
|
+
if (s.includes('G')) r |= TextureChannel.G
|
|
412
|
+
if (s.includes('B')) r |= TextureChannel.B
|
|
413
|
+
if (s.includes('A')) r |= TextureChannel.A
|
|
414
|
+
return r
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function createGenericExtensionClass(name: string, textures?: Record<string, string|number>): typeof GenericExtension {
|
|
418
|
+
return class extends GenericExtension {
|
|
419
|
+
public static readonly EXTENSION_NAME = name
|
|
420
|
+
readonly extensionName = name
|
|
421
|
+
textureChannels: Record<string, number> = !textures ? {} : Object.fromEntries(
|
|
422
|
+
Object.entries(textures)
|
|
423
|
+
.map(([k, v])=>
|
|
424
|
+
[k, typeof v === 'number' ? v : stringToChannel(v)])
|
|
425
|
+
)
|
|
426
|
+
}
|
|
427
|
+
}
|
|
@@ -1,126 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import {PlatformIO, WebIO} from '@gltf-transform/core'
|
|
2
|
+
import {GLTFSpecGlossinessConverterPluginBase, gltfKhrPbrSpecularGlossinessConverterBase} from './GLTFSpecGlossinessConverterPluginBase'
|
|
3
|
+
|
|
4
|
+
// Re-export base for CLI consumers
|
|
5
|
+
export {GLTFSpecGlossinessConverterPluginBase, gltfKhrPbrSpecularGlossinessConverterBase} from './GLTFSpecGlossinessConverterPluginBase'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* GLTFSpecGlossinessConverterPlugin
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* Browser-specific plugin that uses WebIO for spec-gloss to metal-rough conversion.
|
|
11
11
|
*
|
|
12
12
|
* To use this plugin, simply add it to the viewer and import a file with specular glossiness materials.
|
|
13
13
|
* If `confirm` is set to true, a confirmation dialog will be shown before the conversion.
|
|
14
14
|
*/
|
|
15
|
-
export class GLTFSpecGlossinessConverterPlugin extends
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
static readonly PluginType = 'GLTFSpecGlossinessConverterPlugin'
|
|
19
|
-
toJSON: any = undefined
|
|
20
|
-
|
|
21
|
-
// dependencies = [GLTFDracoExportPlugin]
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Whether to show the confirmation dialog when loading a GLTF file with specular glossiness materials.
|
|
25
|
-
* If set to false, the conversion will be done automatically without confirmation.
|
|
26
|
-
* To disable the conversion entirely, disable the plugin.
|
|
27
|
-
*/
|
|
28
|
-
confirm = true
|
|
29
|
-
/**
|
|
30
|
-
* The message to show in the confirmation dialog when loading a GLTF file with specular glossiness materials.
|
|
31
|
-
*/
|
|
32
|
-
confirmMessage = 'GLTF Load: This file includes specular glossiness materials, do you want to convert it to metallic roughness before load?'
|
|
33
|
-
|
|
34
|
-
private _loaderCreate({loader}: {loader: GLTFLoader2}) {
|
|
35
|
-
if (!loader.isGLTFLoader2) return
|
|
36
|
-
|
|
37
|
-
const ep = this._viewer?.getPlugin(GLTFDracoExportPlugin)
|
|
38
|
-
if (!ep) {
|
|
39
|
-
console.error('GLTFSpecGlossinessConverterPlugin requires GLTFDracoExportPlugin in the plugin for extra extensions')
|
|
40
|
-
}
|
|
41
|
-
loader.register(gltfKhrPbrSpecularGlossinessConverter(async(m)=>!this.enabled ? false : !this.confirm ? true : this._viewer?.dialog.confirm(this.confirmMessage || m) ?? true, ep?.gltfTransformExtensions))
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
constructor() {
|
|
45
|
-
super()
|
|
46
|
-
this._loaderCreate = this._loaderCreate.bind(this)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
onAdded(v: ThreeViewer) {
|
|
50
|
-
super.onAdded(v)
|
|
51
|
-
v.assetManager.importer.addEventListener('loaderCreate', this._loaderCreate as any)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
onRemove(v: ThreeViewer) {
|
|
55
|
-
v.assetManager.importer.removeEventListener('loaderCreate', this._loaderCreate as any)
|
|
56
|
-
return super.onRemove(v)
|
|
15
|
+
export class GLTFSpecGlossinessConverterPlugin extends GLTFSpecGlossinessConverterPluginBase {
|
|
16
|
+
protected _createIO(): PlatformIO {
|
|
17
|
+
return new WebIO()
|
|
57
18
|
}
|
|
58
19
|
}
|
|
59
20
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (!parser.json.extensionsUsed || !parser.json.extensionsUsed.includes('KHR_materials_pbrSpecularGlossiness')) return
|
|
66
|
-
const doConfirm = parser.importOptions?.confirmSpecGlossConversion ?? true
|
|
67
|
-
if (doConfirm && confirm && !await confirm('Convert KHR_materials_pbrSpecularGlossiness to KHR_materials_pbrMetallicRoughness?')) return
|
|
68
|
-
|
|
69
|
-
const json = parser.json
|
|
70
|
-
const io = new WebIO().registerExtensions(ALL_EXTENSIONS).registerExtensions(extensions ?? [])
|
|
71
|
-
|
|
72
|
-
if (parser.extensions.KHR_draco_mesh_compression) {
|
|
73
|
-
const dracoLoader = parser.extensions.KHR_draco_mesh_compression.dracoLoader as DRACOLoader2
|
|
74
|
-
if (!dracoLoader.isDRACOLoader2) {
|
|
75
|
-
console.error('gltfKhrPbrSpecularGlossinessConverter: DRACOLoader2 required')
|
|
76
|
-
return
|
|
77
|
-
}
|
|
78
|
-
const decoder = await dracoLoader.initDecoder()
|
|
79
|
-
io.registerDependencies({
|
|
80
|
-
// 'draco3d.encoder': libs[0],
|
|
81
|
-
['draco3d.decoder']: decoder,
|
|
82
|
-
})
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const document = await io.readJSON({
|
|
86
|
-
json, resources: {
|
|
87
|
-
['@glb.bin']: new Uint8Array(parser.extensions.KHR_binary_glTF?.body),
|
|
88
|
-
},
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// Convert materials.
|
|
93
|
-
await document.transform(metalRough())
|
|
94
|
-
|
|
95
|
-
// no need to compress
|
|
96
|
-
if (parser.extensions.KHR_draco_mesh_compression) {
|
|
97
|
-
document.getRoot().listExtensionsUsed().find(e => e.extensionName === 'KHR_draco_mesh_compression')?.dispose()
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Write back to GLB.
|
|
101
|
-
const res = await io.writeBinary(document)
|
|
102
|
-
const binaryExtension = new GLTFBinaryExtension(res.buffer as any)
|
|
103
|
-
parser.extensions.KHR_binary_glTF = binaryExtension
|
|
104
|
-
parser.json = JSON.parse(binaryExtension.content)
|
|
105
|
-
|
|
106
|
-
// this doesn't work for some reason
|
|
107
|
-
// const res = await io.writeJSON(document, {format: Format.GLB})
|
|
108
|
-
// parser.extensions.KHR_binary_glTF.data = res.resources['@glb.bin'].buffer as any
|
|
109
|
-
// parser.extensions.KHR_binary_glTF.content = res.json
|
|
110
|
-
// parser.json = res.json
|
|
111
|
-
} catch (e) {
|
|
112
|
-
console.error(e)
|
|
113
|
-
return
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
declare module 'threepipe'{
|
|
119
|
-
interface ImportAddOptions{
|
|
120
|
-
/**
|
|
121
|
-
* Whether to confirm the conversion of specular glossiness materials to metallic roughness.
|
|
122
|
-
* @default true
|
|
123
|
-
*/
|
|
124
|
-
confirmSpecGlossConversion?: boolean
|
|
125
|
-
}
|
|
126
|
-
}
|
|
21
|
+
/**
|
|
22
|
+
* @deprecated Use {@link gltfKhrPbrSpecularGlossinessConverterBase} with an IO factory instead.
|
|
23
|
+
*/
|
|
24
|
+
export const gltfKhrPbrSpecularGlossinessConverter = (confirm?: (s: string)=>Promise<boolean>, extensions?: (typeof import('@gltf-transform/core').Extension)[]) =>
|
|
25
|
+
gltfKhrPbrSpecularGlossinessConverterBase(() => new WebIO(), confirm, extensions)
|