@threepipe/plugin-gltf-transform 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/dist/GLTFDracoExportPlugin.d.ts +63 -0
- package/dist/GLTFDracoExportPlugin.d.ts.map +1 -0
- package/dist/GLTFDracoExporter.d.ts +38 -0
- package/dist/GLTFDracoExporter.d.ts.map +1 -0
- package/dist/dependencies.txt +138 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5369 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +5366 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +34 -0
- package/src/GLTFDracoExportPlugin.ts +161 -0
- package/src/GLTFDracoExporter.ts +418 -0
- package/src/global.d.ts +40 -0
- package/src/index.ts +2 -0
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@threepipe/plugin-gltf-transform",
|
|
3
|
+
"description": "Utility plugins for threepipe using gltf-transform to optimize/compress glTF files.",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"dependencies": {},
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"module": "dist/index.mjs",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"author": "repalash <palash@shaders.app>",
|
|
15
|
+
"license": "Apache-2.0",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"three",
|
|
18
|
+
"three.js",
|
|
19
|
+
"threepipe",
|
|
20
|
+
"vite",
|
|
21
|
+
"plugin"
|
|
22
|
+
],
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/repalash/threepipe/issues"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/repalash/threepipe#readme",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git://github.com/repalash/threepipe.git"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"threepipe": "^0.0.32"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AssetExporterPlugin,
|
|
3
|
+
AViewerPluginSync,
|
|
4
|
+
ClearcoatTintPlugin,
|
|
5
|
+
CustomBumpMapPlugin,
|
|
6
|
+
DRACOLoader2,
|
|
7
|
+
generateUUID,
|
|
8
|
+
GLTFLightExtrasExtension,
|
|
9
|
+
GLTFMaterialExtrasExtension,
|
|
10
|
+
GLTFMaterialsAlphaMapExtension,
|
|
11
|
+
GLTFMaterialsBumpMapExtension,
|
|
12
|
+
GLTFMaterialsDisplacementMapExtension,
|
|
13
|
+
GLTFMaterialsLightMapExtension,
|
|
14
|
+
GLTFObject3DExtrasExtension,
|
|
15
|
+
NoiseBumpMaterialPlugin,
|
|
16
|
+
ThreeViewer,
|
|
17
|
+
} from 'threepipe'
|
|
18
|
+
import {GLTFDracoExporter} from './GLTFDracoExporter'
|
|
19
|
+
import {UiObjectConfig} from 'uiconfig.js'
|
|
20
|
+
import {EncoderOptions} from '@gltf-transform/extensions/dist/khr-draco-mesh-compression/encoder'
|
|
21
|
+
|
|
22
|
+
export enum EncoderMethod {
|
|
23
|
+
EDGEBREAKER = 1,
|
|
24
|
+
SEQUENTIAL = 0
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* GLTF Draco Export Plugin
|
|
29
|
+
*
|
|
30
|
+
* Overloads the default gltf exporter in the asset manager with GLTFDracoExporter. When exporting with compress = true, the output will be compressed.
|
|
31
|
+
* Note - Only `glb` supported right now.
|
|
32
|
+
*
|
|
33
|
+
* @category Plugins
|
|
34
|
+
*/
|
|
35
|
+
export class GLTFDracoExportPlugin extends AViewerPluginSync<''> {
|
|
36
|
+
public static readonly PluginType = 'GLTFDracoExportPlugin'
|
|
37
|
+
enabled = true
|
|
38
|
+
|
|
39
|
+
onAdded(viewer: ThreeViewer): void {
|
|
40
|
+
super.onAdded(viewer)
|
|
41
|
+
const importer = viewer.assetManager.importer
|
|
42
|
+
const exporter = viewer.assetManager.exporter
|
|
43
|
+
|
|
44
|
+
const glbExporter = exporter.getExporter('glb')
|
|
45
|
+
if (glbExporter) exporter.removeExporter(glbExporter)
|
|
46
|
+
|
|
47
|
+
// todo remove exporter and add back the old one on plugin remove.
|
|
48
|
+
exporter.addExporter({
|
|
49
|
+
...glbExporter || {
|
|
50
|
+
ext: ['glb', 'gltf'],
|
|
51
|
+
extensions: [],
|
|
52
|
+
}, // for extensions
|
|
53
|
+
ctor: (_, _exporter) => {
|
|
54
|
+
const tempFile = generateUUID() + '.drc' // dummy
|
|
55
|
+
const ex = new GLTFDracoExporter({},
|
|
56
|
+
// todo unregister on dispose
|
|
57
|
+
importer.registerFile(tempFile) as DRACOLoader2)
|
|
58
|
+
ex.setup(viewer, _exporter.extensions)
|
|
59
|
+
ex.createAndAddExtension(GLTFMaterialsBumpMapExtension.WebGiMaterialsBumpMapExtension, {
|
|
60
|
+
bumpTexture: 'R',
|
|
61
|
+
})
|
|
62
|
+
ex.createAndAddExtension(GLTFMaterialsLightMapExtension.WebGiMaterialsLightMapExtension, {
|
|
63
|
+
lightMapTexture: 'RGB',
|
|
64
|
+
})
|
|
65
|
+
ex.createAndAddExtension(GLTFMaterialsAlphaMapExtension.WebGiMaterialsAlphaMapExtension, {
|
|
66
|
+
alphaTexture: 'G',
|
|
67
|
+
})
|
|
68
|
+
ex.createAndAddExtension(GLTFMaterialsDisplacementMapExtension.WebGiMaterialsDisplacementMapExtension, {
|
|
69
|
+
displacementTexture: 'R',
|
|
70
|
+
})
|
|
71
|
+
ex.createAndAddExtension(CustomBumpMapPlugin.CUSTOM_BUMP_MAP_GLTF_EXTENSION, {
|
|
72
|
+
customBumpMap: 'RGB',
|
|
73
|
+
})
|
|
74
|
+
ex.createAndAddExtension(GLTFLightExtrasExtension.WebGiLightExtrasExtension)
|
|
75
|
+
ex.createAndAddExtension(GLTFObject3DExtrasExtension.WebGiObject3DExtrasExtension)
|
|
76
|
+
ex.createAndAddExtension(GLTFMaterialExtrasExtension.WebGiMaterialExtrasExtension)
|
|
77
|
+
ex.createAndAddExtension(ClearcoatTintPlugin.CLEARCOAT_TINT_GLTF_EXTENSION)
|
|
78
|
+
ex.createAndAddExtension(NoiseBumpMaterialPlugin.NOISE_BUMP_MATERIAL_GLTF_EXTENSION)
|
|
79
|
+
// todo port
|
|
80
|
+
// DiamondMaterialExtension
|
|
81
|
+
// AnimationMarkersExtension
|
|
82
|
+
// AnisotropyMaterialExtension
|
|
83
|
+
// ThinFilmLayerMaterialExtension
|
|
84
|
+
// TriplanarMappingMaterialExtension
|
|
85
|
+
// SSBevelMaterialExtension
|
|
86
|
+
return ex
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// for ui
|
|
91
|
+
const exportPlugin = viewer.getPlugin(AssetExporterPlugin)
|
|
92
|
+
if (exportPlugin) {
|
|
93
|
+
Object.assign(exportPlugin.exportOptions, {
|
|
94
|
+
compress: false,
|
|
95
|
+
dracoOptions: {
|
|
96
|
+
encodeSpeed: 5,
|
|
97
|
+
method: EncoderMethod.EDGEBREAKER,
|
|
98
|
+
quantizationVolume: 'mesh',
|
|
99
|
+
quantizationBits: {
|
|
100
|
+
['POSITION']: 14,
|
|
101
|
+
['NORMAL']: 10,
|
|
102
|
+
['COLOR']: 8,
|
|
103
|
+
['TEX_COORD']: 12,
|
|
104
|
+
['GENERIC']: 12,
|
|
105
|
+
},
|
|
106
|
+
} as EncoderOptions,
|
|
107
|
+
})
|
|
108
|
+
const exportOptions = exportPlugin.uiConfig.children?.find(c => (c as UiObjectConfig).label === 'GLB Export') as UiObjectConfig
|
|
109
|
+
if (exportOptions) {
|
|
110
|
+
exportOptions.children = [this._makeUi(exportPlugin), ...exportOptions.children || []]
|
|
111
|
+
} else {
|
|
112
|
+
console.warn('GLTFDracoExportPlugin: Unable to setup UI')
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
protected _makeUi = (exporter: AssetExporterPlugin)=>[
|
|
118
|
+
{
|
|
119
|
+
type: 'checkbox',
|
|
120
|
+
label: 'DRACO Compress',
|
|
121
|
+
property: [exporter.exportOptions, 'compress'],
|
|
122
|
+
onChange: ()=>exporter.uiConfig.uiRefresh?.(true),
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
type: 'folder',
|
|
126
|
+
hidden: ()=>!exporter.exportOptions.compress,
|
|
127
|
+
label: 'DRACO Options',
|
|
128
|
+
children: [
|
|
129
|
+
{
|
|
130
|
+
type: 'slider',
|
|
131
|
+
label: 'Encode Speed',
|
|
132
|
+
bounds: [1, 10],
|
|
133
|
+
property: [exporter.exportOptions.dracoOptions, 'encodeSpeed'],
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
type: 'dropdown',
|
|
137
|
+
label: 'Encoder Method',
|
|
138
|
+
property: [exporter.exportOptions.dracoOptions, 'method'],
|
|
139
|
+
children: Object.entries(EncoderMethod).map(([k, v]) => ({label: k, value: v})),
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
type: 'dropdown',
|
|
143
|
+
label: 'Quantization Volume',
|
|
144
|
+
property: [exporter.exportOptions.dracoOptions, 'quantizationVolume'],
|
|
145
|
+
children: ['mesh', 'scene', 'bbox'].map(v => ({label: v})),
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
type: 'folder',
|
|
149
|
+
label: 'Quantization Bits',
|
|
150
|
+
children: Object.keys(exporter.exportOptions.dracoOptions?.quantizationBits || {}).map(k => ({
|
|
151
|
+
type: 'slider',
|
|
152
|
+
label: k,
|
|
153
|
+
bounds: [1, 16],
|
|
154
|
+
stepSize: 1,
|
|
155
|
+
property: [exporter.exportOptions.dracoOptions?.quantizationBits, k],
|
|
156
|
+
})),
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
},
|
|
160
|
+
]
|
|
161
|
+
}
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Extension,
|
|
3
|
+
ExtensionProperty,
|
|
4
|
+
GLTF,
|
|
5
|
+
Graph,
|
|
6
|
+
Property,
|
|
7
|
+
PropertyType,
|
|
8
|
+
ReaderContext,
|
|
9
|
+
Texture,
|
|
10
|
+
TextureChannel,
|
|
11
|
+
TextureInfo,
|
|
12
|
+
WebIO,
|
|
13
|
+
WriterContext,
|
|
14
|
+
} from '@gltf-transform/core'
|
|
15
|
+
import {EncoderOptions} from '@gltf-transform/extensions/dist/khr-draco-mesh-compression/encoder'
|
|
16
|
+
import {ALL_EXTENSIONS, KHRDracoMeshCompression} from '@gltf-transform/extensions'
|
|
17
|
+
import {DRACOLoader2, GLTFExporter2, GLTFExporter2Options, GLTFViewerConfigExtension, IExportParser} from 'threepipe'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* GLTF Draco Exporter
|
|
21
|
+
*
|
|
22
|
+
* Extension of GLTFExporter2 that runs the output through gltf-transform for draco compression.
|
|
23
|
+
*/
|
|
24
|
+
export class GLTFDracoExporter extends GLTFExporter2 implements IExportParser {
|
|
25
|
+
public loader?: DRACOLoader2 // required for loading draco libs.
|
|
26
|
+
private _io: WebIO
|
|
27
|
+
private _loadedLibs = false
|
|
28
|
+
private _encoderOptions: EncoderOptions
|
|
29
|
+
|
|
30
|
+
constructor(encoderOptions?: EncoderOptions, loader?: DRACOLoader2) {
|
|
31
|
+
super()
|
|
32
|
+
encoderOptions = encoderOptions || {
|
|
33
|
+
method: KHRDracoMeshCompression.EncoderMethod.EDGEBREAKER,
|
|
34
|
+
encodeSpeed: 5,
|
|
35
|
+
}
|
|
36
|
+
this._io = new WebIO().registerExtensions(ALL_EXTENSIONS)
|
|
37
|
+
.registerExtensions([
|
|
38
|
+
GLTFViewerConfigExtensionGP,
|
|
39
|
+
])
|
|
40
|
+
this._encoderOptions = encoderOptions
|
|
41
|
+
|
|
42
|
+
if (loader) {
|
|
43
|
+
this.loader = loader
|
|
44
|
+
this.loader.setDecoderConfig({type: 'js'}) // todo: hack for now.
|
|
45
|
+
this.loader.preload(true, true)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
preload(): this {
|
|
51
|
+
this._loadLibs()
|
|
52
|
+
return this
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private async _loadLibs() {
|
|
56
|
+
if (this._loadedLibs || !this.loader) return
|
|
57
|
+
|
|
58
|
+
const libs = await Promise.all([
|
|
59
|
+
this.loader.initEncoder(),
|
|
60
|
+
this.loader.initDecoder(),
|
|
61
|
+
])
|
|
62
|
+
this._io.registerDependencies({
|
|
63
|
+
['draco3d.encoder']: libs[0],
|
|
64
|
+
['draco3d.decoder']: libs[1], // only required if we are loading a draco compressed gltf
|
|
65
|
+
})
|
|
66
|
+
this._loadedLibs = true
|
|
67
|
+
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async parseAsync(obj: any, {compress = false, dracoOptions, ...options}: {compress: boolean, dracoOptions?: EncoderOptions} & GLTFExporter2Options): Promise<Blob> {
|
|
71
|
+
if (!this.loader) {
|
|
72
|
+
console.error('GLTFDracoExporter: No DRACOLoader2 instance provided')
|
|
73
|
+
return super.parseAsync(obj, options)
|
|
74
|
+
}
|
|
75
|
+
await this._loadLibs()
|
|
76
|
+
|
|
77
|
+
const ops = {...options}
|
|
78
|
+
if (compress) {
|
|
79
|
+
// externalImagesInExtras: this is required because gltf-transform doesn't support external images in glb
|
|
80
|
+
// see https://github.com/donmccurdy/glTF-Transform/discussions/644
|
|
81
|
+
ops.externalImagesInExtras = true
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const uncompressed = await new Promise((resolve, reject) => this.parse(obj, resolve, reject, ops)) as any
|
|
85
|
+
|
|
86
|
+
const uncompressedBlob = await super.parseAsync(uncompressed, ops)
|
|
87
|
+
if (!compress) return uncompressedBlob
|
|
88
|
+
|
|
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
|
+
}
|
|
126
|
+
|
|
127
|
+
addExtension(extension: typeof Extension): this {
|
|
128
|
+
this._io.registerExtensions([extension])
|
|
129
|
+
return this
|
|
130
|
+
}
|
|
131
|
+
createAndAddExtension(name: string, textures?: Record<string, string|number>): this {
|
|
132
|
+
return this.addExtension(createGenericExtensionClass(name, textures))
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
declare module 'threepipe'{
|
|
137
|
+
interface GLTFExporter2Options {
|
|
138
|
+
compress?: boolean
|
|
139
|
+
dracoOptions?: EncoderOptions
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// for @gltf-transform/core
|
|
144
|
+
class ViewerJSONExtensionProperty extends ExtensionProperty {
|
|
145
|
+
readonly extensionName: string = GLTFViewerConfigExtension.ViewerConfigGLTFExtension
|
|
146
|
+
readonly parentTypes: string[] = [PropertyType.SCENE]
|
|
147
|
+
readonly propertyType: string = 'ViewerJSON'
|
|
148
|
+
|
|
149
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
150
|
+
protected init(): void {return}
|
|
151
|
+
|
|
152
|
+
}
|
|
153
|
+
class GLTFViewerConfigExtensionGP extends Extension {
|
|
154
|
+
public readonly extensionName = GLTFViewerConfigExtension.ViewerConfigGLTFExtension
|
|
155
|
+
public static readonly EXTENSION_NAME = GLTFViewerConfigExtension.ViewerConfigGLTFExtension
|
|
156
|
+
private _viewerConfig: any = {}
|
|
157
|
+
// private _texturesRef: [any, Texture][] = []
|
|
158
|
+
|
|
159
|
+
read(context: ReaderContext): this {
|
|
160
|
+
this._viewerConfig = {}
|
|
161
|
+
context.jsonDoc.json.scenes?.forEach((sceneDef, sceneIndex)=>{
|
|
162
|
+
if (sceneDef.extensions && sceneDef.extensions[GLTFViewerConfigExtension.ViewerConfigGLTFExtension]) {
|
|
163
|
+
const prop = new ViewerJSONExtensionProperty(this.document.getGraph())
|
|
164
|
+
context.scenes[sceneIndex].setExtension(GLTFViewerConfigExtension.ViewerConfigGLTFExtension, prop)
|
|
165
|
+
this._viewerConfig = sceneDef.extensions[GLTFViewerConfigExtension.ViewerConfigGLTFExtension] as any
|
|
166
|
+
// prop.setExtras()
|
|
167
|
+
|
|
168
|
+
/*
|
|
169
|
+
const buffers = [] as any[]
|
|
170
|
+
|
|
171
|
+
Object.values(viewerConfig.resources).forEach((res: any) => {
|
|
172
|
+
Object.values(res).forEach((item: any) => {
|
|
173
|
+
if (!item.url) return
|
|
174
|
+
if (item.url.data?.image !== null) {
|
|
175
|
+
buffers.push(item.url)
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
const jsonDoc = context.jsonDoc
|
|
180
|
+
console.log(buffers)
|
|
181
|
+
for (const buffer of buffers) {
|
|
182
|
+
const img = buffer.data.image as number
|
|
183
|
+
const imageDef = jsonDoc.json.images![img]
|
|
184
|
+
const bufferViewDef = jsonDoc.json.bufferViews![imageDef.bufferView!]
|
|
185
|
+
const bufferDef = jsonDoc.json.buffers![bufferViewDef.buffer]
|
|
186
|
+
const bufferData = bufferDef.uri ? jsonDoc.resources[bufferDef.uri] : jsonDoc.resources[GLB_BUFFER]
|
|
187
|
+
const byteOffset = bufferViewDef.byteOffset || 0
|
|
188
|
+
const byteLength = bufferViewDef.byteLength
|
|
189
|
+
const imageData = bufferData.slice(byteOffset, byteOffset + byteLength)
|
|
190
|
+
const texture = this.document.createTexture(imageDef.name)
|
|
191
|
+
texture.setImage(imageData)
|
|
192
|
+
this._texturesRef.push([buffer, texture])
|
|
193
|
+
}
|
|
194
|
+
*/
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
return this
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
write(context: WriterContext): this {
|
|
203
|
+
this.document.getRoot().listScenes().forEach((scene)=>{
|
|
204
|
+
const prop = scene.getExtension(GLTFViewerConfigExtension.ViewerConfigGLTFExtension)
|
|
205
|
+
if (prop) {
|
|
206
|
+
const sceneDef = context.jsonDoc.json.scenes?.[context.jsonDoc.json.scene || 0] // todo: get proper scene index, if working with multiple scenes
|
|
207
|
+
if (sceneDef && Object.keys(this._viewerConfig).length > 0) {
|
|
208
|
+
sceneDef.extensions = sceneDef.extensions || {}
|
|
209
|
+
|
|
210
|
+
/*
|
|
211
|
+
console.log(context.jsonDoc.json.images)
|
|
212
|
+
for (const [buffer, texture] of this._texturesRef) {
|
|
213
|
+
const imageDef = context.createPropertyDef(texture) as GLTF.IImage
|
|
214
|
+
context.createImageData(imageDef, texture.getImage()!, texture)
|
|
215
|
+
buffer.data.image = context.jsonDoc.json.images!.push(imageDef) - 1
|
|
216
|
+
context.imageIndexMap.set(texture, buffer.data.image)
|
|
217
|
+
}
|
|
218
|
+
console.log(context.jsonDoc.json)
|
|
219
|
+
*/
|
|
220
|
+
|
|
221
|
+
sceneDef.extensions[GLTFViewerConfigExtension.ViewerConfigGLTFExtension] = this._viewerConfig
|
|
222
|
+
|
|
223
|
+
// this._texturesRef = []
|
|
224
|
+
this._viewerConfig = {}
|
|
225
|
+
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
return this
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
required = true
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
class GenericExtensionProperty extends ExtensionProperty<any> {
|
|
237
|
+
readonly extensionName: string
|
|
238
|
+
readonly parentTypes: string[] = [PropertyType.MATERIAL, PropertyType.MESH, PropertyType.NODE, PropertyType.SCENE]
|
|
239
|
+
readonly propertyType: string = 'GenericExtension'
|
|
240
|
+
textures: Record<string, [TextureInfo, Texture|null]> = {}
|
|
241
|
+
|
|
242
|
+
addTexture(key: string, texInfo: TextureInfo, texture: Texture | null, channels = 0x1111) {
|
|
243
|
+
this.setRef(key, texture, {channels})
|
|
244
|
+
this.textures[key] = [texInfo, texture]
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
constructor(graph: Graph<Property>, name: string, extensionName: string) {
|
|
248
|
+
super(graph, name)
|
|
249
|
+
this.extensionName = extensionName
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
253
|
+
protected init(): void {return}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// see transmission extension for reference
|
|
257
|
+
abstract class GenericExtension extends Extension {
|
|
258
|
+
abstract readonly extensionName: string
|
|
259
|
+
|
|
260
|
+
textureChannels: Record<string, number> = {}
|
|
261
|
+
|
|
262
|
+
read(context: ReaderContext): this {
|
|
263
|
+
const jsonDoc = context.jsonDoc
|
|
264
|
+
// console.log(jsonDoc)
|
|
265
|
+
const materialDefs = jsonDoc.json.materials || []
|
|
266
|
+
const textureDefs = jsonDoc.json.textures || []
|
|
267
|
+
materialDefs.forEach((materialDef, materialIndex) => {
|
|
268
|
+
if (materialDef.extensions && materialDef.extensions[this.extensionName]) {
|
|
269
|
+
const paramsExt = new GenericExtensionProperty(this.document.getGraph(), '', this.extensionName)
|
|
270
|
+
context.materials[materialIndex].setExtension(this.extensionName, paramsExt)
|
|
271
|
+
const paramsExtDef = materialDef.extensions[this.extensionName] as Record<string, any>
|
|
272
|
+
const paramsExtDef2 = {...paramsExtDef}
|
|
273
|
+
|
|
274
|
+
for (const [key, value] of Object.entries(paramsExtDef2)) {
|
|
275
|
+
if (typeof value?.index === 'number') { // this is a texture...
|
|
276
|
+
const textureInfoDef = value
|
|
277
|
+
const source = textureDefs[textureInfoDef.index]?.source
|
|
278
|
+
if (typeof source !== 'number') {
|
|
279
|
+
console.warn('GLTF Pipeline: source texture not found for texture info', textureInfoDef)
|
|
280
|
+
continue
|
|
281
|
+
}
|
|
282
|
+
const texture = context.textures[source]
|
|
283
|
+
const texInfo = new TextureInfo(this.document.getGraph())
|
|
284
|
+
const channels = this.textureChannels[key] ?? 0x1111
|
|
285
|
+
paramsExt.addTexture(key, texInfo, texture, channels)
|
|
286
|
+
context.setTextureInfo(texInfo, textureInfoDef)
|
|
287
|
+
delete paramsExtDef2[key]
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
paramsExt.setExtras(paramsExtDef2)
|
|
292
|
+
// console.log({...paramsExtDef})
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
const meshDefs = jsonDoc.json.meshes || []
|
|
296
|
+
meshDefs.forEach((meshDef, meshIndex) => {
|
|
297
|
+
if (meshDef.extensions && meshDef.extensions[this.extensionName]) {
|
|
298
|
+
const paramsExt = new GenericExtensionProperty(this.document.getGraph(), '', this.extensionName)
|
|
299
|
+
context.meshes[meshIndex].setExtension(this.extensionName, paramsExt)
|
|
300
|
+
const paramsExtDef = meshDef.extensions[this.extensionName] as Record<string, any>
|
|
301
|
+
paramsExt.setExtras(paramsExtDef)
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
const nodeDefs = jsonDoc.json.nodes || []
|
|
305
|
+
nodeDefs.forEach((nodeDef, nodeIndex) => {
|
|
306
|
+
if (nodeDef.extensions && nodeDef.extensions[this.extensionName]) {
|
|
307
|
+
const paramsExt = new GenericExtensionProperty(this.document.getGraph(), '', this.extensionName)
|
|
308
|
+
context.nodes[nodeIndex].setExtension(this.extensionName, paramsExt)
|
|
309
|
+
const paramsExtDef = nodeDef.extensions[this.extensionName] as Record<string, any>
|
|
310
|
+
paramsExt.setExtras(paramsExtDef)
|
|
311
|
+
// console.log(paramsExtDef)
|
|
312
|
+
}
|
|
313
|
+
})
|
|
314
|
+
const sceneDefs = jsonDoc.json.scenes || []
|
|
315
|
+
sceneDefs.forEach((sceneDef, sceneIndex) => {
|
|
316
|
+
if (sceneDef.extensions && sceneDef.extensions[this.extensionName]) {
|
|
317
|
+
const paramsExt = new GenericExtensionProperty(this.document.getGraph(), '', this.extensionName)
|
|
318
|
+
context.scenes[sceneIndex].setExtension(this.extensionName, paramsExt)
|
|
319
|
+
const paramsExtDef = sceneDef.extensions[this.extensionName] as Record<string, any>
|
|
320
|
+
paramsExt.setExtras(paramsExtDef)
|
|
321
|
+
// console.log(paramsExtDef)
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
return this
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
write(context: WriterContext): this {
|
|
329
|
+
const jsonDoc = context.jsonDoc
|
|
330
|
+
this.document.getRoot()
|
|
331
|
+
.listMaterials()
|
|
332
|
+
.forEach((material) => {
|
|
333
|
+
const paramsExt = material.getExtension<GenericExtensionProperty>(this.extensionName)
|
|
334
|
+
// console.log(paramsExt)
|
|
335
|
+
if (paramsExt) {
|
|
336
|
+
const materialIndex = context.materialIndexMap.get(material)!
|
|
337
|
+
const materialDef = jsonDoc.json.materials![materialIndex]
|
|
338
|
+
materialDef.extensions = materialDef.extensions || {}
|
|
339
|
+
const extensionDef = paramsExt.getExtras()
|
|
340
|
+
const extensionDef2 = {...extensionDef}
|
|
341
|
+
|
|
342
|
+
// console.log(paramsExt.textures)
|
|
343
|
+
for (const [key, value] of Object.entries(paramsExt.textures)) {
|
|
344
|
+
const textureInfo = value[0]
|
|
345
|
+
const textureLink = value[1]
|
|
346
|
+
const texture = textureLink
|
|
347
|
+
|
|
348
|
+
if (texture)
|
|
349
|
+
extensionDef2[key] = context.createTextureInfoDef(texture, textureInfo)
|
|
350
|
+
|
|
351
|
+
// console.log(texture)
|
|
352
|
+
|
|
353
|
+
}
|
|
354
|
+
// console.log(extensionDef2)
|
|
355
|
+
|
|
356
|
+
materialDef.extensions[this.extensionName] = extensionDef2
|
|
357
|
+
}
|
|
358
|
+
})
|
|
359
|
+
this.document.getRoot()
|
|
360
|
+
.listMeshes()
|
|
361
|
+
.forEach((mesh) => {
|
|
362
|
+
const paramsExt = mesh.getExtension<GenericExtensionProperty>(this.extensionName)
|
|
363
|
+
if (paramsExt) {
|
|
364
|
+
const meshIndex = context.meshIndexMap.get(mesh)!
|
|
365
|
+
const meshDef = jsonDoc.json.meshes![meshIndex]
|
|
366
|
+
meshDef.extensions = meshDef.extensions || {}
|
|
367
|
+
meshDef.extensions[this.extensionName] = paramsExt.getExtras()
|
|
368
|
+
}
|
|
369
|
+
})
|
|
370
|
+
this.document.getRoot()
|
|
371
|
+
.listNodes()
|
|
372
|
+
.forEach((node) => {
|
|
373
|
+
const paramsExt = node.getExtension<GenericExtensionProperty>(this.extensionName)
|
|
374
|
+
if (paramsExt) {
|
|
375
|
+
const nodeIndex = context.nodeIndexMap.get(node)!
|
|
376
|
+
const nodeDef = jsonDoc.json.nodes![nodeIndex]
|
|
377
|
+
nodeDef.extensions = nodeDef.extensions || {}
|
|
378
|
+
nodeDef.extensions[this.extensionName] = paramsExt.getExtras()
|
|
379
|
+
}
|
|
380
|
+
})
|
|
381
|
+
this.document.getRoot()
|
|
382
|
+
.listScenes()
|
|
383
|
+
.forEach((scene) => {
|
|
384
|
+
const paramsExt = scene.getExtension<GenericExtensionProperty>(this.extensionName)
|
|
385
|
+
if (paramsExt) {
|
|
386
|
+
const sceneIndex = context.jsonDoc.json.scene || 0 // todo: get proper scene index, if working with multiple scenes, this will do the default one.
|
|
387
|
+
const sceneDef = jsonDoc.json.scenes![sceneIndex]
|
|
388
|
+
if (!sceneDef) return
|
|
389
|
+
sceneDef.extensions = sceneDef.extensions || {}
|
|
390
|
+
sceneDef.extensions[this.extensionName] = paramsExt.getExtras()
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
return this
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function stringToChannel(s: string) {
|
|
400
|
+
let r = 0
|
|
401
|
+
if (s.includes('R')) r |= TextureChannel.R
|
|
402
|
+
if (s.includes('G')) r |= TextureChannel.G
|
|
403
|
+
if (s.includes('B')) r |= TextureChannel.B
|
|
404
|
+
if (s.includes('A')) r |= TextureChannel.A
|
|
405
|
+
return r
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export function createGenericExtensionClass(name: string, textures?: Record<string, string|number>): typeof GenericExtension {
|
|
409
|
+
return class extends GenericExtension {
|
|
410
|
+
public static readonly EXTENSION_NAME = name
|
|
411
|
+
readonly extensionName = name
|
|
412
|
+
textureChannels: Record<string, number> = !textures ? {} : Object.fromEntries(
|
|
413
|
+
Object.entries(textures)
|
|
414
|
+
.map(([k, v])=>
|
|
415
|
+
[k, typeof v === 'number' ? v : stringToChannel(v)])
|
|
416
|
+
)
|
|
417
|
+
}
|
|
418
|
+
}
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
declare module '*.txt' {
|
|
2
|
+
const content: string
|
|
3
|
+
export default content
|
|
4
|
+
}
|
|
5
|
+
declare module '*.glsl' {
|
|
6
|
+
const content: string
|
|
7
|
+
export default content
|
|
8
|
+
}
|
|
9
|
+
declare module '*.vert' {
|
|
10
|
+
const content: string
|
|
11
|
+
export default content
|
|
12
|
+
}
|
|
13
|
+
declare module '*.frag' {
|
|
14
|
+
const content: string
|
|
15
|
+
export default content
|
|
16
|
+
}
|
|
17
|
+
declare module '*.module.scss' {
|
|
18
|
+
const content: any
|
|
19
|
+
export default content
|
|
20
|
+
export const stylesheet: string
|
|
21
|
+
}
|
|
22
|
+
declare module '*.module.css' {
|
|
23
|
+
const content: any
|
|
24
|
+
export default content
|
|
25
|
+
export const stylesheet: string
|
|
26
|
+
}
|
|
27
|
+
declare module '*.css' {
|
|
28
|
+
const content: string
|
|
29
|
+
export default content
|
|
30
|
+
}
|
|
31
|
+
declare module '*.css?inline' { // for vite
|
|
32
|
+
const content: string
|
|
33
|
+
export default content
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// export {}
|
|
37
|
+
|
|
38
|
+
// hack for typedoc
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
40
|
+
// declare type OffscreenCanvas = HTMLCanvasElement
|
package/src/index.ts
ADDED