| - [@threepipe/plugin-blueprintjs](#threepipeplugin-blueprintjs) BlueprintJs UI Plugin | - [@threepipe/plugin-blueprintjs](#threepipeplugin-blueprintjs) BlueprintJs UI Plugin | ||||
| - [@threepipe/plugin-tweakpane-editor](#threepipeplugin-tweakpane-editor) - Tweakpane Editor Plugin | - [@threepipe/plugin-tweakpane-editor](#threepipeplugin-tweakpane-editor) - Tweakpane Editor Plugin | ||||
| - [@threepipe/plugin-configurator](#threepipeplugin-configurator) - Provides Material Configurator and Switch Node Plugin to allow users to select variations | - [@threepipe/plugin-configurator](#threepipeplugin-configurator) - Provides Material Configurator and Switch Node Plugin to allow users to select variations | ||||
| - [@threepipe/plugin-gltf-transform](#threepipeplugin-gltf-transform) - Plugin to transform gltf models (draco compression) | |||||
| - [@threepipe/plugins-extra-importers](#threepipeplugins-extra-importers) - Plugin for loading more file types supported by loaders in three.js | - [@threepipe/plugins-extra-importers](#threepipeplugins-extra-importers) - Plugin for loading more file types supported by loaders in three.js | ||||
| - [@threepipe/plugin-blend-importer](#threepipeplugin-blend-importer) - Blender to add support for loading .blend file | - [@threepipe/plugin-blend-importer](#threepipeplugin-blend-importer) - Blender to add support for loading .blend file | ||||
| - [@threepipe/plugin-geometry-generator](#threepipeplugin-geometry-generator) - Generate parametric geometry types that can be re-generated from UI/API. | - [@threepipe/plugin-geometry-generator](#threepipeplugin-geometry-generator) - Generate parametric geometry types that can be re-generated from UI/API. | ||||
| [//]: # (TODO Add Example for custom UI) | [//]: # (TODO Add Example for custom UI) | ||||
| ## @threepipe/plugin-gltf-transform | |||||
| Exports [GLTFDracoExportPlugin](https://threepipe.org/plugins/gltf-transform/docs/classes/GLTFDracoExportPlugin.html) that extends the default gltf exporter to compress the file after export. | |||||
| [Example](https://threepipe.org/examples/#glb-draco-export/) — | |||||
| [Source Code](plugins/gltf-transform/src/index.ts) — | |||||
| [API Reference](https://threepipe.org/plugins/gltf-transform/docs) | |||||
| NPM: `npm install @threepipe/plugin-gltf-transform` | |||||
| To use, simply add the plugin to the viewer and export using the `viewer.export` or `viewer.exportScene` functions. This also adds UI options to `AssetExporterPlugin` which are used when exporting using the plugin or using `viewer.exportScene` | |||||
| The plugin overloads the default gltf exporter in the asset manager with `GLTFDracoExporter`. Using the [gltf-transform](https://gltf-transform.donmccurdy.com/) library, it compresses the exported gltf file using the [khr_draco_mesh_compression](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_draco_mesh_compression/README.md) extension. | |||||
| Note - Only `glb` export supported right now. | |||||
| Sample Usage: | |||||
| ```typescript | |||||
| import {ThreeViewer, downloadBlob} from 'threepipe' | |||||
| import {GLTFDracoExportPlugin} from '@threepipe/plugin-gltf-transform' | |||||
| const viewer = new ThreeViewer({...}) | |||||
| viewer.addPluginSync(GLTFDracoExportPlugin) | |||||
| await viewer.load('file.glb') | |||||
| const blob = await viewer.exportScene({ | |||||
| compress: true, // this must be specified, by default it's false. | |||||
| viewerConfig: true, // to export with viewer, scene and plugin settings | |||||
| }) | |||||
| // download the file | |||||
| downloadBlob(blob, 'scene.glb') | |||||
| ``` | |||||
| ## @threepipe/plugin-network | ## @threepipe/plugin-network | ||||
| Network/Cloud related plugin implementations for Threepipe. | Network/Cloud related plugin implementations for Threepipe. | ||||
| [//]: # ( TODO: The plugin should parse and references to other assets and find them relative to the .blend file or the current location.) | [//]: # ( TODO: The plugin should parse and references to other assets and find them relative to the .blend file or the current location.) | ||||
| ## @threepipe/plugin-geometry-generator | ## @threepipe/plugin-geometry-generator | ||||
| Exports [GeometryGeneratorPlugin](https://threepipe.org/plugins/geometry-generator/docs/classes/BlendLoadPlugin.html) with several Geometry generators to create parametric and updatable geometries like plane, circle, sphere, box, torus, cylinder, cone etc. | Exports [GeometryGeneratorPlugin](https://threepipe.org/plugins/geometry-generator/docs/classes/BlendLoadPlugin.html) with several Geometry generators to create parametric and updatable geometries like plane, circle, sphere, box, torus, cylinder, cone etc. |
| { | { | ||||
| "imports": { | "imports": { | ||||
| "threepipe": "./../../dist/index.mjs", | "threepipe": "./../../dist/index.mjs", | ||||
| "@threepipe/plugin-gltf-transform": "./../../plugins/gltf-transform/dist/index.mjs", | |||||
| "@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/dist/index.mjs" | "@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/dist/index.mjs" | ||||
| } | } | ||||
| } | } |
| ThreeViewer, | ThreeViewer, | ||||
| } from 'threepipe' | } from 'threepipe' | ||||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | ||||
| import {GLTFDracoExportPlugin} from '@threepipe/plugin-gltf-transform' | |||||
| async function init() { | async function init() { | ||||
| const viewer = new ThreeViewer({ | const viewer = new ThreeViewer({ | ||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | ||||
| msaa: true, | msaa: true, | ||||
| plugins: [LoadingScreenPlugin, AssetExporterPlugin, SceneUiConfigPlugin], | |||||
| plugins: [LoadingScreenPlugin, AssetExporterPlugin, SceneUiConfigPlugin, GLTFDracoExportPlugin], | |||||
| }) | }) | ||||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) |
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>GLB Draco Export</title> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||||
| <!-- Import maps polyfill --> | |||||
| <!-- Remove this when import maps will be widely supported --> | |||||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||||
| <script type="importmap"> | |||||
| { | |||||
| "imports": { | |||||
| "threepipe": "./../../dist/index.mjs", | |||||
| "@threepipe/plugin-gltf-transform": "./../../plugins/gltf-transform/dist/index.mjs" | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style id="example-style"> | |||||
| html, body, #canvas-container, #mcanvas { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| margin: 0; | |||||
| overflow: hidden; | |||||
| } | |||||
| </style> | |||||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||||
| <script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script> | |||||
| </head> | |||||
| <body> | |||||
| <div id="canvas-container"> | |||||
| <canvas id="mcanvas"></canvas> | |||||
| </div> | |||||
| </body> |
| import {_testFinish, downloadBlob, IObject3D, LoadingScreenPlugin, ThreeViewer} from 'threepipe' | |||||
| import {createSimpleButtons} from '../examples-utils/simple-bottom-buttons.js' | |||||
| import {GLTFDracoExportPlugin} from '@threepipe/plugin-gltf-transform' | |||||
| const viewer = new ThreeViewer({canvas: document.getElementById('mcanvas') as HTMLCanvasElement, msaa: true}) | |||||
| async function init() { | |||||
| viewer.addPluginSync(LoadingScreenPlugin) | |||||
| viewer.addPluginSync(GLTFDracoExportPlugin) | |||||
| // Note: see asset-exporter-plugin example as well | |||||
| // load obj + mtl | |||||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||||
| const helmet = await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | |||||
| autoCenter: true, | |||||
| autoScale: true, | |||||
| }) | |||||
| if (!helmet) { | |||||
| console.error('Unable to load model') | |||||
| return | |||||
| } | |||||
| const mesh = helmet.getObjectByName('node_damagedHelmet_-6514')! | |||||
| // const blob = await viewer.export(helmetObject, {exportExt: 'glb'}) | |||||
| // const blob = await viewer.exportScene({viewerConfig: false}) // export scene without viewer config | |||||
| // const blob = await viewer.exportScene() // export scene with viewer config and default settings. | |||||
| createSimpleButtons({ | |||||
| ['Download Helmet Object GLB + DRACO']: async() => { | |||||
| const blob = await viewer.export(mesh, { | |||||
| exportExt: 'glb', | |||||
| embedUrlImages: true, // embed images in glb even when url is available. | |||||
| compress: true, | |||||
| }) | |||||
| if (!blob) { | |||||
| alert('Unable to export helmet object') | |||||
| return | |||||
| } | |||||
| downloadBlob(blob, 'helmet.' + blob.ext) | |||||
| }, | |||||
| ['Download Scene GLB (Without Viewer Config) + DRACO']: async() => { | |||||
| const blob = await viewer.exportScene({viewerConfig: false, compress: true}) | |||||
| if (!blob || blob.ext !== 'glb') { | |||||
| alert('Unable to export scene') | |||||
| return | |||||
| } | |||||
| downloadBlob(blob, 'scene.glb') | |||||
| }, | |||||
| ['Download Scene GLB (With Viewer Config) + DRACO']: async() => { | |||||
| const blob = await viewer.exportScene({viewerConfig: true, compress: true}) | |||||
| if (!blob || blob.ext !== 'glb') { | |||||
| alert('Unable to export scene') | |||||
| return | |||||
| } | |||||
| downloadBlob(blob, 'scene_with_config.glb') | |||||
| }, | |||||
| }) | |||||
| } | |||||
| init().finally(_testFinish) |
| <li><a href="./image-snapshot-export/">PNG, JPEG, WEBP Export<br/>(Image Snapshot) </a></li> | <li><a href="./image-snapshot-export/">PNG, JPEG, WEBP Export<br/>(Image Snapshot) </a></li> | ||||
| <li><a href="./render-target-export/">EXR, PNG, JPEG, WEBP Export<br/>(Render Target Export) </a></li> | <li><a href="./render-target-export/">EXR, PNG, JPEG, WEBP Export<br/>(Render Target Export) </a></li> | ||||
| <li><a href="./glb-export/">GLB Export </a></li> | <li><a href="./glb-export/">GLB Export </a></li> | ||||
| <li><a href="./glb-draco-export/">GLB (+DRACO) Export </a></li> | |||||
| <li><a href="./pmat-material-export/">PMAT Material Export </a></li> | <li><a href="./pmat-material-export/">PMAT Material Export </a></li> | ||||
| <li><a href="./transfr-share-plugin/">Transfr.one Share Plugin<br/>(Upload, share link) </a></li> | <li><a href="./transfr-share-plugin/">Transfr.one Share Plugin<br/>(Upload, share link) </a></li> | ||||
| <li><a href="./svg-geometry-playground/">SVG Geometry Playground </a></li> | <li><a href="./svg-geometry-playground/">SVG Geometry Playground </a></li> | ||||
| </ul> | </ul> | ||||
| </div> | </div> | ||||
| <div class="iframe-container"> | <div class="iframe-container"> | ||||
| <!-- TODO: allow only threepipe and localhost domains --> | |||||
| <iframe id="example-iframe" src="./tweakpane-editor/" frameborder="0" allowfullscreen="allowfullscreen" | <iframe id="example-iframe" src="./tweakpane-editor/" frameborder="0" allowfullscreen="allowfullscreen" | ||||
| allow="accelerometer *; ambient-light-sensor *; autoplay *; camera *; clipboard-read *; clipboard-write *; encrypted-media *; fullscreen *; geolocation *; gyroscope *; magnetometer *; microphone *; midi *; payment *; picture-in-picture *; screen-wake-lock *; speaker *; sync-xhr *; usb *; web-share *; vibrate *; vr *"> | allow="accelerometer *; ambient-light-sensor *; autoplay *; camera *; clipboard-read *; clipboard-write *; encrypted-media *; fullscreen *; geolocation *; gyroscope *; magnetometer *; microphone *; midi *; payment *; picture-in-picture *; screen-wake-lock *; speaker *; sync-xhr *; usb *; web-share *; vibrate *; vr *"> | ||||
| </iframe> | </iframe> |
| "@threepipe/plugin-geometry-generator": "./../../plugins/geometry-generator/dist/index.mjs", | "@threepipe/plugin-geometry-generator": "./../../plugins/geometry-generator/dist/index.mjs", | ||||
| "@threepipe/plugin-configurator": "./../../plugins/configurator/dist/index.mjs", | "@threepipe/plugin-configurator": "./../../plugins/configurator/dist/index.mjs", | ||||
| "@threepipe/plugin-network": "./../../plugins/network/dist/index.mjs", | "@threepipe/plugin-network": "./../../plugins/network/dist/index.mjs", | ||||
| "@threepipe/plugin-gltf-transform": "./../../plugins/gltf-transform/dist/index.mjs" | |||||
| "@threepipe/plugin-gaussian-splatting": "./../../plugins/gaussian-splatting/dist/index.mjs" | "@threepipe/plugin-gaussian-splatting": "./../../plugins/gaussian-splatting/dist/index.mjs" | ||||
| } | } | ||||
| } | } |
| import {GaussianSplattingPlugin} from '@threepipe/plugin-gaussian-splatting' | import {GaussianSplattingPlugin} from '@threepipe/plugin-gaussian-splatting' | ||||
| import {MaterialConfiguratorPlugin, SwitchNodePlugin} from '@threepipe/plugin-configurator' | import {MaterialConfiguratorPlugin, SwitchNodePlugin} from '@threepipe/plugin-configurator' | ||||
| import {AWSClientPlugin, TransfrSharePlugin} from '@threepipe/plugin-network' | import {AWSClientPlugin, TransfrSharePlugin} from '@threepipe/plugin-network' | ||||
| import {GLTFDracoExportPlugin} from '@threepipe/plugin-gltf-transform' | |||||
| async function init() { | async function init() { | ||||
| await viewer.addPlugins([ | await viewer.addPlugins([ | ||||
| LoadingScreenPlugin, | LoadingScreenPlugin, | ||||
| AssetExporterPlugin, | AssetExporterPlugin, | ||||
| GLTFDracoExportPlugin, | |||||
| new ProgressivePlugin(), | new ProgressivePlugin(), | ||||
| new SSAAPlugin(), | new SSAAPlugin(), | ||||
| GLTFAnimationPlugin, | GLTFAnimationPlugin, |
| } | } | ||||
| }, | }, | ||||
| "node_modules/caniuse-lite": { | "node_modules/caniuse-lite": { | ||||
| "version": "1.0.30001583", | |||||
| "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001583.tgz", | |||||
| "integrity": "sha512-acWTYaha8xfhA/Du/z4sNZjHUWjkiuoAi2LM+T/aL+kemKQgPT1xBb/YKjlQ0Qo8gvbHsGNplrEJ+9G3gL7i4Q==", | |||||
| "version": "1.0.30001642", | |||||
| "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz", | |||||
| "integrity": "sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==", | |||||
| "dev": true, | "dev": true, | ||||
| "funding": [ | "funding": [ | ||||
| { | { |
| { | { | ||||
| "name": "threepipe", | "name": "threepipe", | ||||
| "version": "0.0.31", | |||||
| "version": "0.0.32", | |||||
| "description": "A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.", | "description": "A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.", | ||||
| "main": "dist/index.js", | "main": "dist/index.js", | ||||
| "module": "dist/index.mjs", | "module": "dist/index.mjs", | ||||
| "docs-all": "npm run docs && npm run docs-plugins", | "docs-all": "npm run docs && npm run docs-plugins", | ||||
| "build-plugins": "node scripts/each-plugin.mjs install", | "build-plugins": "node scripts/each-plugin.mjs install", | ||||
| "prepare": "npm run build && npm run build-plugins && npm run build-examples", | "prepare": "npm run build && npm run build-plugins && npm run build-examples", | ||||
| "update-version": "node scripts/update-version.mjs" | |||||
| "update-version": "node scripts/update-version.mjs" | |||||
| }, | }, | ||||
| "clean-package": { | "clean-package": { | ||||
| "remove": [ | "remove": [ |
| { | |||||
| "name": "@threepipe/plugin-gltf-transform", | |||||
| "version": "0.1.0", | |||||
| "lockfileVersion": 3, | |||||
| "requires": true, | |||||
| "packages": { | |||||
| "": { | |||||
| "name": "@threepipe/plugin-gltf-transform", | |||||
| "version": "0.1.0", | |||||
| "license": "Apache-2.0", | |||||
| "dependencies": { | |||||
| "threepipe": "file:./../../src/" | |||||
| }, | |||||
| "devDependencies": { | |||||
| "@gltf-transform/core": "3.2.1", | |||||
| "@gltf-transform/extensions": "3.2.1" | |||||
| } | |||||
| }, | |||||
| "../../src": {}, | |||||
| "../tweakpane/src": { | |||||
| "extraneous": true | |||||
| }, | |||||
| "node_modules/@gltf-transform/core": { | |||||
| "version": "3.2.1", | |||||
| "resolved": "https://registry.npmjs.org/@gltf-transform/core/-/core-3.2.1.tgz", | |||||
| "integrity": "sha512-EE4AXJsu1jsSvcTnzk+mCu/VgLldPsb0gGhOV7onRlHM4DYh8m9aCCjGdLJf1uNJi+KUP5hPmOrUteXuopFq2A==", | |||||
| "dev": true, | |||||
| "dependencies": { | |||||
| "property-graph": "^1.2.0" | |||||
| } | |||||
| }, | |||||
| "node_modules/@gltf-transform/extensions": { | |||||
| "version": "3.2.1", | |||||
| "resolved": "https://registry.npmjs.org/@gltf-transform/extensions/-/extensions-3.2.1.tgz", | |||||
| "integrity": "sha512-7uiCXDV7/o2pnuXM9z/URdP9RR3baOaVo3f3eo65WtoUBIRKO6I/WHJzwGrRdWIWKQQWfxwZLTzYYaAgprnxDg==", | |||||
| "dev": true, | |||||
| "dependencies": { | |||||
| "@gltf-transform/core": "^3.2.1", | |||||
| "ktx-parse": "^0.5.0" | |||||
| } | |||||
| }, | |||||
| "node_modules/ktx-parse": { | |||||
| "version": "0.5.0", | |||||
| "resolved": "https://registry.npmjs.org/ktx-parse/-/ktx-parse-0.5.0.tgz", | |||||
| "integrity": "sha512-5IZrv5s1byUeDTIee1jjJQBiD5LPDB0w9pJJ0oT9BCKKJf16Tuj123vm1Ps0GOHSHmeWPgKM0zuViCVuTRpqaA==", | |||||
| "dev": true | |||||
| }, | |||||
| "node_modules/property-graph": { | |||||
| "version": "1.3.1", | |||||
| "resolved": "https://registry.npmjs.org/property-graph/-/property-graph-1.3.1.tgz", | |||||
| "integrity": "sha512-gei3N/bHWJdCItJ4blnlGWd9iauEZI+JZYj/A0D177XSI01+QhiJGAVscYBhe3Yywow3A2QJzVtsO2P+UgrRRQ==", | |||||
| "dev": true | |||||
| }, | |||||
| "node_modules/threepipe": { | |||||
| "resolved": "../../src", | |||||
| "link": true | |||||
| } | |||||
| } | |||||
| } |
| { | |||||
| "name": "@threepipe/plugin-gltf-transform", | |||||
| "description": "Utility plugins for threepipe using gltf-transform to optimize/compress glTF files.", | |||||
| "version": "0.1.0", | |||||
| "devDependencies": { | |||||
| "@gltf-transform/core": "3.2.1", | |||||
| "@gltf-transform/extensions": "3.2.1" | |||||
| }, | |||||
| "dependencies": { | |||||
| "threepipe": "file:./../../src/" | |||||
| }, | |||||
| "clean-package": { | |||||
| "remove": [ | |||||
| "clean-package", | |||||
| "scripts", | |||||
| "devDependencies", | |||||
| "//", | |||||
| "markdown-to-html" | |||||
| ], | |||||
| "replace": { | |||||
| "dependencies": {}, | |||||
| "peerDependencies": { | |||||
| "threepipe": "^0.0.32" | |||||
| } | |||||
| } | |||||
| }, | |||||
| "type": "module", | |||||
| "main": "dist/index.js", | |||||
| "module": "dist/index.mjs", | |||||
| "types": "dist/index.d.ts", | |||||
| "files": [ | |||||
| "dist", | |||||
| "src" | |||||
| ], | |||||
| "scripts": { | |||||
| "new:pack": "npm run prepare && clean-package && npm pack && clean-package restore", | |||||
| "new:publish": "npm run prepare && clean-package && npm publish --access public && clean-package restore", | |||||
| "prepare": "npm run build && npm run docs", | |||||
| "build": "rimraf dist && vite build", | |||||
| "dev": "NODE_ENV=development vite build --watch", | |||||
| "docs": "rimraf docs && npx typedoc" | |||||
| }, | |||||
| "author": "repalash <palash@shaders.app>", | |||||
| "license": "Apache-2.0", | |||||
| "keywords": [ | |||||
| "three", | |||||
| "three.js", | |||||
| "threepipe", | |||||
| "vite", | |||||
| "plugin" | |||||
| ], | |||||
| "bugs": { | |||||
| "url": "https://github.com/repalash/threepipe/issues" | |||||
| }, | |||||
| "homepage": "https://github.com/repalash/threepipe#readme", | |||||
| "repository": { | |||||
| "type": "git", | |||||
| "url": "git://github.com/repalash/threepipe.git" | |||||
| } | |||||
| } |
| import { | |||||
| AssetExporterPlugin, | |||||
| AViewerPluginSync, | |||||
| ClearcoatTintPlugin, | |||||
| CustomBumpMapPlugin, | |||||
| DRACOLoader2, | |||||
| generateUUID, | |||||
| GLTFLightExtrasExtension, | |||||
| GLTFMaterialExtrasExtension, | |||||
| GLTFMaterialsAlphaMapExtension, | |||||
| GLTFMaterialsBumpMapExtension, | |||||
| GLTFMaterialsDisplacementMapExtension, | |||||
| GLTFMaterialsLightMapExtension, | |||||
| GLTFObject3DExtrasExtension, | |||||
| NoiseBumpMaterialPlugin, | |||||
| ThreeViewer, | |||||
| } from 'threepipe' | |||||
| import {GLTFDracoExporter} from './GLTFDracoExporter' | |||||
| import {UiObjectConfig} from 'uiconfig.js' | |||||
| import {EncoderOptions} from '@gltf-transform/extensions/dist/khr-draco-mesh-compression/encoder' | |||||
| export enum EncoderMethod { | |||||
| EDGEBREAKER = 1, | |||||
| SEQUENTIAL = 0 | |||||
| } | |||||
| /** | |||||
| * GLTF Draco Export Plugin | |||||
| * | |||||
| * Overloads the default gltf exporter in the asset manager with GLTFDracoExporter. When exporting with compress = true, the output will be compressed. | |||||
| * Note - Only `glb` supported right now. | |||||
| * | |||||
| * @category Plugins | |||||
| */ | |||||
| export class GLTFDracoExportPlugin extends AViewerPluginSync<''> { | |||||
| public static readonly PluginType = 'GLTFDracoExportPlugin' | |||||
| enabled = true | |||||
| onAdded(viewer: ThreeViewer): void { | |||||
| super.onAdded(viewer) | |||||
| const importer = viewer.assetManager.importer | |||||
| const exporter = viewer.assetManager.exporter | |||||
| const glbExporter = exporter.getExporter('glb') | |||||
| if (glbExporter) exporter.removeExporter(glbExporter) | |||||
| // todo remove exporter and add back the old one on plugin remove. | |||||
| exporter.addExporter({ | |||||
| ...glbExporter || { | |||||
| ext: ['glb', 'gltf'], | |||||
| extensions: [], | |||||
| }, // for extensions | |||||
| ctor: (_, _exporter) => { | |||||
| const tempFile = generateUUID() + '.drc' // dummy | |||||
| const ex = new GLTFDracoExporter({}, | |||||
| // todo unregister on dispose | |||||
| importer.registerFile(tempFile) as DRACOLoader2) | |||||
| ex.setup(viewer, _exporter.extensions) | |||||
| ex.createAndAddExtension(GLTFMaterialsBumpMapExtension.WebGiMaterialsBumpMapExtension, { | |||||
| bumpTexture: 'R', | |||||
| }) | |||||
| ex.createAndAddExtension(GLTFMaterialsLightMapExtension.WebGiMaterialsLightMapExtension, { | |||||
| lightMapTexture: 'RGB', | |||||
| }) | |||||
| ex.createAndAddExtension(GLTFMaterialsAlphaMapExtension.WebGiMaterialsAlphaMapExtension, { | |||||
| alphaTexture: 'G', | |||||
| }) | |||||
| ex.createAndAddExtension(GLTFMaterialsDisplacementMapExtension.WebGiMaterialsDisplacementMapExtension, { | |||||
| displacementTexture: 'R', | |||||
| }) | |||||
| ex.createAndAddExtension(CustomBumpMapPlugin.CUSTOM_BUMP_MAP_GLTF_EXTENSION, { | |||||
| customBumpMap: 'RGB', | |||||
| }) | |||||
| ex.createAndAddExtension(GLTFLightExtrasExtension.WebGiLightExtrasExtension) | |||||
| ex.createAndAddExtension(GLTFObject3DExtrasExtension.WebGiObject3DExtrasExtension) | |||||
| ex.createAndAddExtension(GLTFMaterialExtrasExtension.WebGiMaterialExtrasExtension) | |||||
| ex.createAndAddExtension(ClearcoatTintPlugin.CLEARCOAT_TINT_GLTF_EXTENSION) | |||||
| ex.createAndAddExtension(NoiseBumpMaterialPlugin.NOISE_BUMP_MATERIAL_GLTF_EXTENSION) | |||||
| // todo port | |||||
| // DiamondMaterialExtension | |||||
| // AnimationMarkersExtension | |||||
| // AnisotropyMaterialExtension | |||||
| // ThinFilmLayerMaterialExtension | |||||
| // TriplanarMappingMaterialExtension | |||||
| // SSBevelMaterialExtension | |||||
| return ex | |||||
| }, | |||||
| }) | |||||
| // for ui | |||||
| const exportPlugin = viewer.getPlugin(AssetExporterPlugin) | |||||
| if (exportPlugin) { | |||||
| Object.assign(exportPlugin.exportOptions, { | |||||
| compress: false, | |||||
| dracoOptions: { | |||||
| encodeSpeed: 5, | |||||
| method: EncoderMethod.EDGEBREAKER, | |||||
| quantizationVolume: 'mesh', | |||||
| quantizationBits: { | |||||
| ['POSITION']: 14, | |||||
| ['NORMAL']: 10, | |||||
| ['COLOR']: 8, | |||||
| ['TEX_COORD']: 12, | |||||
| ['GENERIC']: 12, | |||||
| }, | |||||
| } as EncoderOptions, | |||||
| }) | |||||
| const exportOptions = exportPlugin.uiConfig.children?.find(c => (c as UiObjectConfig).label === 'GLB Export') as UiObjectConfig | |||||
| if (exportOptions) { | |||||
| exportOptions.children = [this._makeUi(exportPlugin), ...exportOptions.children || []] | |||||
| } else { | |||||
| console.warn('GLTFDracoExportPlugin: Unable to setup UI') | |||||
| } | |||||
| } | |||||
| } | |||||
| protected _makeUi = (exporter: AssetExporterPlugin)=>[ | |||||
| { | |||||
| type: 'checkbox', | |||||
| label: 'DRACO Compress', | |||||
| property: [exporter.exportOptions, 'compress'], | |||||
| onChange: ()=>exporter.uiConfig.uiRefresh?.(true), | |||||
| }, | |||||
| { | |||||
| type: 'folder', | |||||
| hidden: ()=>!exporter.exportOptions.compress, | |||||
| label: 'DRACO Options', | |||||
| children: [ | |||||
| { | |||||
| type: 'slider', | |||||
| label: 'Encode Speed', | |||||
| bounds: [1, 10], | |||||
| property: [exporter.exportOptions.dracoOptions, 'encodeSpeed'], | |||||
| }, | |||||
| { | |||||
| type: 'dropdown', | |||||
| label: 'Encoder Method', | |||||
| property: [exporter.exportOptions.dracoOptions, 'method'], | |||||
| children: Object.entries(EncoderMethod).map(([k, v]) => ({label: k, value: v})), | |||||
| }, | |||||
| { | |||||
| type: 'dropdown', | |||||
| label: 'Quantization Volume', | |||||
| property: [exporter.exportOptions.dracoOptions, 'quantizationVolume'], | |||||
| children: ['mesh', 'scene', 'bbox'].map(v => ({label: v})), | |||||
| }, | |||||
| { | |||||
| type: 'folder', | |||||
| label: 'Quantization Bits', | |||||
| children: Object.keys(exporter.exportOptions.dracoOptions?.quantizationBits || {}).map(k => ({ | |||||
| type: 'slider', | |||||
| label: k, | |||||
| bounds: [1, 16], | |||||
| stepSize: 1, | |||||
| property: [exporter.exportOptions.dracoOptions?.quantizationBits, k], | |||||
| })), | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| ] | |||||
| } |
| import { | |||||
| Extension, | |||||
| ExtensionProperty, | |||||
| GLTF, | |||||
| Graph, | |||||
| Property, | |||||
| PropertyType, | |||||
| ReaderContext, | |||||
| Texture, | |||||
| TextureChannel, | |||||
| TextureInfo, | |||||
| WebIO, | |||||
| WriterContext, | |||||
| } from '@gltf-transform/core' | |||||
| import {EncoderOptions} from '@gltf-transform/extensions/dist/khr-draco-mesh-compression/encoder' | |||||
| import {ALL_EXTENSIONS, KHRDracoMeshCompression} from '@gltf-transform/extensions' | |||||
| import {DRACOLoader2, GLTFExporter2, GLTFExporter2Options, GLTFViewerConfigExtension, IExportParser} from 'threepipe' | |||||
| /** | |||||
| * GLTF Draco Exporter | |||||
| * | |||||
| * Extension of GLTFExporter2 that runs the output through gltf-transform for draco compression. | |||||
| */ | |||||
| export class GLTFDracoExporter extends GLTFExporter2 implements IExportParser { | |||||
| public loader?: DRACOLoader2 // required for loading draco libs. | |||||
| private _io: WebIO | |||||
| private _loadedLibs = false | |||||
| private _encoderOptions: EncoderOptions | |||||
| constructor(encoderOptions?: EncoderOptions, loader?: DRACOLoader2) { | |||||
| super() | |||||
| encoderOptions = encoderOptions || { | |||||
| method: KHRDracoMeshCompression.EncoderMethod.EDGEBREAKER, | |||||
| encodeSpeed: 5, | |||||
| } | |||||
| this._io = new WebIO().registerExtensions(ALL_EXTENSIONS) | |||||
| .registerExtensions([ | |||||
| GLTFViewerConfigExtensionGP, | |||||
| ]) | |||||
| this._encoderOptions = encoderOptions | |||||
| if (loader) { | |||||
| this.loader = loader | |||||
| this.loader.setDecoderConfig({type: 'js'}) // todo: hack for now. | |||||
| this.loader.preload(true, true) | |||||
| } | |||||
| } | |||||
| preload(): this { | |||||
| this._loadLibs() | |||||
| return this | |||||
| } | |||||
| private async _loadLibs() { | |||||
| if (this._loadedLibs || !this.loader) return | |||||
| const libs = await Promise.all([ | |||||
| this.loader.initEncoder(), | |||||
| this.loader.initDecoder(), | |||||
| ]) | |||||
| this._io.registerDependencies({ | |||||
| ['draco3d.encoder']: libs[0], | |||||
| ['draco3d.decoder']: libs[1], // only required if we are loading a draco compressed gltf | |||||
| }) | |||||
| this._loadedLibs = true | |||||
| } | |||||
| async parseAsync(obj: any, {compress = false, dracoOptions, ...options}: {compress: boolean, dracoOptions?: EncoderOptions} & GLTFExporter2Options): Promise<Blob> { | |||||
| if (!this.loader) { | |||||
| console.error('GLTFDracoExporter: No DRACOLoader2 instance provided') | |||||
| return super.parseAsync(obj, options) | |||||
| } | |||||
| await this._loadLibs() | |||||
| const ops = {...options} | |||||
| if (compress) { | |||||
| // externalImagesInExtras: this is required because gltf-transform doesn't support external images in glb | |||||
| // see https://github.com/donmccurdy/glTF-Transform/discussions/644 | |||||
| ops.externalImagesInExtras = true | |||||
| } | |||||
| const uncompressed = await new Promise((resolve, reject) => this.parse(obj, resolve, reject, ops)) as any | |||||
| const uncompressedBlob = await super.parseAsync(uncompressed, ops) | |||||
| if (!compress) return uncompressedBlob | |||||
| if (!uncompressed) throw new Error('GLTFDracoExporter: gltf is null') | |||||
| let gltf = uncompressed | |||||
| const bytes = (gltf as ArrayBuffer).byteLength || Infinity | |||||
| const iDocument = await (typeof gltf === 'object' && !(gltf as any).byteLength ? this._io.readJSON({ | |||||
| json: gltf as GLTF.IGLTF, | |||||
| resources: {}, | |||||
| }) : this._io.readBinary(new Uint8Array(gltf as ArrayBuffer))) | |||||
| // iDocument.createExtension(GLTFViewerConfigExtensionGP) | |||||
| iDocument.createExtension(KHRDracoMeshCompression) | |||||
| .setRequired(true) | |||||
| .setEncoderOptions({...this._encoderOptions, ...dracoOptions ?? {}}) | |||||
| if (ops.exportExt === 'glb') { | |||||
| gltf = await this._io.writeBinary(iDocument) | |||||
| if (isFinite(bytes)) { | |||||
| console.log('DRACO Compression ratio: ' + ((gltf as ArrayBuffer).byteLength / bytes).toFixed(5)) | |||||
| } | |||||
| } else { | |||||
| const jDoc = await this._io.writeJSON(iDocument) | |||||
| gltf = jDoc.json | |||||
| if (Object.values(jDoc.resources).filter(v => v).length > 0) { | |||||
| console.warn('DRACOExporter: extra resources in resources not supported properly') | |||||
| ;(gltf as any).resources = jDoc.resources | |||||
| } | |||||
| } | |||||
| gltf.__isGLTFOutput = true | |||||
| const blob = await super.parseAsync(gltf, ops) as any // this will just convert it to blob because __isGLTFOutput is set (checked in GLTFExporter2) | |||||
| if (!blob) throw new Error('GLTFDracoExporter: blob is null') | |||||
| blob.ext = 'glb' | |||||
| ;(blob as any).__uncompressed = uncompressedBlob | |||||
| return blob | |||||
| } | |||||
| addExtension(extension: typeof Extension): this { | |||||
| this._io.registerExtensions([extension]) | |||||
| return this | |||||
| } | |||||
| createAndAddExtension(name: string, textures?: Record<string, string|number>): this { | |||||
| return this.addExtension(createGenericExtensionClass(name, textures)) | |||||
| } | |||||
| } | |||||
| declare module 'threepipe'{ | |||||
| interface GLTFExporter2Options { | |||||
| compress?: boolean | |||||
| dracoOptions?: EncoderOptions | |||||
| } | |||||
| } | |||||
| // for @gltf-transform/core | |||||
| class ViewerJSONExtensionProperty extends ExtensionProperty { | |||||
| readonly extensionName: string = GLTFViewerConfigExtension.ViewerConfigGLTFExtension | |||||
| readonly parentTypes: string[] = [PropertyType.SCENE] | |||||
| readonly propertyType: string = 'ViewerJSON' | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| protected init(): void {return} | |||||
| } | |||||
| class GLTFViewerConfigExtensionGP extends Extension { | |||||
| public readonly extensionName = GLTFViewerConfigExtension.ViewerConfigGLTFExtension | |||||
| public static readonly EXTENSION_NAME = GLTFViewerConfigExtension.ViewerConfigGLTFExtension | |||||
| private _viewerConfig: any = {} | |||||
| // private _texturesRef: [any, Texture][] = [] | |||||
| read(context: ReaderContext): this { | |||||
| this._viewerConfig = {} | |||||
| context.jsonDoc.json.scenes?.forEach((sceneDef, sceneIndex)=>{ | |||||
| if (sceneDef.extensions && sceneDef.extensions[GLTFViewerConfigExtension.ViewerConfigGLTFExtension]) { | |||||
| const prop = new ViewerJSONExtensionProperty(this.document.getGraph()) | |||||
| context.scenes[sceneIndex].setExtension(GLTFViewerConfigExtension.ViewerConfigGLTFExtension, prop) | |||||
| this._viewerConfig = sceneDef.extensions[GLTFViewerConfigExtension.ViewerConfigGLTFExtension] as any | |||||
| // prop.setExtras() | |||||
| /* | |||||
| const buffers = [] as any[] | |||||
| Object.values(viewerConfig.resources).forEach((res: any) => { | |||||
| Object.values(res).forEach((item: any) => { | |||||
| if (!item.url) return | |||||
| if (item.url.data?.image !== null) { | |||||
| buffers.push(item.url) | |||||
| } | |||||
| }) | |||||
| }) | |||||
| const jsonDoc = context.jsonDoc | |||||
| console.log(buffers) | |||||
| for (const buffer of buffers) { | |||||
| const img = buffer.data.image as number | |||||
| const imageDef = jsonDoc.json.images![img] | |||||
| const bufferViewDef = jsonDoc.json.bufferViews![imageDef.bufferView!] | |||||
| const bufferDef = jsonDoc.json.buffers![bufferViewDef.buffer] | |||||
| const bufferData = bufferDef.uri ? jsonDoc.resources[bufferDef.uri] : jsonDoc.resources[GLB_BUFFER] | |||||
| const byteOffset = bufferViewDef.byteOffset || 0 | |||||
| const byteLength = bufferViewDef.byteLength | |||||
| const imageData = bufferData.slice(byteOffset, byteOffset + byteLength) | |||||
| const texture = this.document.createTexture(imageDef.name) | |||||
| texture.setImage(imageData) | |||||
| this._texturesRef.push([buffer, texture]) | |||||
| } | |||||
| */ | |||||
| } | |||||
| }) | |||||
| return this | |||||
| } | |||||
| write(context: WriterContext): this { | |||||
| this.document.getRoot().listScenes().forEach((scene)=>{ | |||||
| const prop = scene.getExtension(GLTFViewerConfigExtension.ViewerConfigGLTFExtension) | |||||
| if (prop) { | |||||
| const sceneDef = context.jsonDoc.json.scenes?.[context.jsonDoc.json.scene || 0] // todo: get proper scene index, if working with multiple scenes | |||||
| if (sceneDef && Object.keys(this._viewerConfig).length > 0) { | |||||
| sceneDef.extensions = sceneDef.extensions || {} | |||||
| /* | |||||
| console.log(context.jsonDoc.json.images) | |||||
| for (const [buffer, texture] of this._texturesRef) { | |||||
| const imageDef = context.createPropertyDef(texture) as GLTF.IImage | |||||
| context.createImageData(imageDef, texture.getImage()!, texture) | |||||
| buffer.data.image = context.jsonDoc.json.images!.push(imageDef) - 1 | |||||
| context.imageIndexMap.set(texture, buffer.data.image) | |||||
| } | |||||
| console.log(context.jsonDoc.json) | |||||
| */ | |||||
| sceneDef.extensions[GLTFViewerConfigExtension.ViewerConfigGLTFExtension] = this._viewerConfig | |||||
| // this._texturesRef = [] | |||||
| this._viewerConfig = {} | |||||
| } | |||||
| } | |||||
| }) | |||||
| return this | |||||
| } | |||||
| required = true | |||||
| } | |||||
| class GenericExtensionProperty extends ExtensionProperty<any> { | |||||
| readonly extensionName: string | |||||
| readonly parentTypes: string[] = [PropertyType.MATERIAL, PropertyType.MESH, PropertyType.NODE, PropertyType.SCENE] | |||||
| readonly propertyType: string = 'GenericExtension' | |||||
| textures: Record<string, [TextureInfo, Texture|null]> = {} | |||||
| addTexture(key: string, texInfo: TextureInfo, texture: Texture | null, channels = 0x1111) { | |||||
| this.setRef(key, texture, {channels}) | |||||
| this.textures[key] = [texInfo, texture] | |||||
| } | |||||
| constructor(graph: Graph<Property>, name: string, extensionName: string) { | |||||
| super(graph, name) | |||||
| this.extensionName = extensionName | |||||
| } | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| protected init(): void {return} | |||||
| } | |||||
| // see transmission extension for reference | |||||
| abstract class GenericExtension extends Extension { | |||||
| abstract readonly extensionName: string | |||||
| textureChannels: Record<string, number> = {} | |||||
| read(context: ReaderContext): this { | |||||
| const jsonDoc = context.jsonDoc | |||||
| // console.log(jsonDoc) | |||||
| const materialDefs = jsonDoc.json.materials || [] | |||||
| const textureDefs = jsonDoc.json.textures || [] | |||||
| materialDefs.forEach((materialDef, materialIndex) => { | |||||
| if (materialDef.extensions && materialDef.extensions[this.extensionName]) { | |||||
| const paramsExt = new GenericExtensionProperty(this.document.getGraph(), '', this.extensionName) | |||||
| context.materials[materialIndex].setExtension(this.extensionName, paramsExt) | |||||
| const paramsExtDef = materialDef.extensions[this.extensionName] as Record<string, any> | |||||
| const paramsExtDef2 = {...paramsExtDef} | |||||
| for (const [key, value] of Object.entries(paramsExtDef2)) { | |||||
| if (typeof value?.index === 'number') { // this is a texture... | |||||
| const textureInfoDef = value | |||||
| const source = textureDefs[textureInfoDef.index]?.source | |||||
| if (typeof source !== 'number') { | |||||
| console.warn('GLTF Pipeline: source texture not found for texture info', textureInfoDef) | |||||
| continue | |||||
| } | |||||
| const texture = context.textures[source] | |||||
| const texInfo = new TextureInfo(this.document.getGraph()) | |||||
| const channels = this.textureChannels[key] ?? 0x1111 | |||||
| paramsExt.addTexture(key, texInfo, texture, channels) | |||||
| context.setTextureInfo(texInfo, textureInfoDef) | |||||
| delete paramsExtDef2[key] | |||||
| } | |||||
| } | |||||
| paramsExt.setExtras(paramsExtDef2) | |||||
| // console.log({...paramsExtDef}) | |||||
| } | |||||
| }) | |||||
| const meshDefs = jsonDoc.json.meshes || [] | |||||
| meshDefs.forEach((meshDef, meshIndex) => { | |||||
| if (meshDef.extensions && meshDef.extensions[this.extensionName]) { | |||||
| const paramsExt = new GenericExtensionProperty(this.document.getGraph(), '', this.extensionName) | |||||
| context.meshes[meshIndex].setExtension(this.extensionName, paramsExt) | |||||
| const paramsExtDef = meshDef.extensions[this.extensionName] as Record<string, any> | |||||
| paramsExt.setExtras(paramsExtDef) | |||||
| } | |||||
| }) | |||||
| const nodeDefs = jsonDoc.json.nodes || [] | |||||
| nodeDefs.forEach((nodeDef, nodeIndex) => { | |||||
| if (nodeDef.extensions && nodeDef.extensions[this.extensionName]) { | |||||
| const paramsExt = new GenericExtensionProperty(this.document.getGraph(), '', this.extensionName) | |||||
| context.nodes[nodeIndex].setExtension(this.extensionName, paramsExt) | |||||
| const paramsExtDef = nodeDef.extensions[this.extensionName] as Record<string, any> | |||||
| paramsExt.setExtras(paramsExtDef) | |||||
| // console.log(paramsExtDef) | |||||
| } | |||||
| }) | |||||
| const sceneDefs = jsonDoc.json.scenes || [] | |||||
| sceneDefs.forEach((sceneDef, sceneIndex) => { | |||||
| if (sceneDef.extensions && sceneDef.extensions[this.extensionName]) { | |||||
| const paramsExt = new GenericExtensionProperty(this.document.getGraph(), '', this.extensionName) | |||||
| context.scenes[sceneIndex].setExtension(this.extensionName, paramsExt) | |||||
| const paramsExtDef = sceneDef.extensions[this.extensionName] as Record<string, any> | |||||
| paramsExt.setExtras(paramsExtDef) | |||||
| // console.log(paramsExtDef) | |||||
| } | |||||
| }) | |||||
| return this | |||||
| } | |||||
| write(context: WriterContext): this { | |||||
| const jsonDoc = context.jsonDoc | |||||
| this.document.getRoot() | |||||
| .listMaterials() | |||||
| .forEach((material) => { | |||||
| const paramsExt = material.getExtension<GenericExtensionProperty>(this.extensionName) | |||||
| // console.log(paramsExt) | |||||
| if (paramsExt) { | |||||
| const materialIndex = context.materialIndexMap.get(material)! | |||||
| const materialDef = jsonDoc.json.materials![materialIndex] | |||||
| materialDef.extensions = materialDef.extensions || {} | |||||
| const extensionDef = paramsExt.getExtras() | |||||
| const extensionDef2 = {...extensionDef} | |||||
| // console.log(paramsExt.textures) | |||||
| for (const [key, value] of Object.entries(paramsExt.textures)) { | |||||
| const textureInfo = value[0] | |||||
| const textureLink = value[1] | |||||
| const texture = textureLink | |||||
| if (texture) | |||||
| extensionDef2[key] = context.createTextureInfoDef(texture, textureInfo) | |||||
| // console.log(texture) | |||||
| } | |||||
| // console.log(extensionDef2) | |||||
| materialDef.extensions[this.extensionName] = extensionDef2 | |||||
| } | |||||
| }) | |||||
| this.document.getRoot() | |||||
| .listMeshes() | |||||
| .forEach((mesh) => { | |||||
| const paramsExt = mesh.getExtension<GenericExtensionProperty>(this.extensionName) | |||||
| if (paramsExt) { | |||||
| const meshIndex = context.meshIndexMap.get(mesh)! | |||||
| const meshDef = jsonDoc.json.meshes![meshIndex] | |||||
| meshDef.extensions = meshDef.extensions || {} | |||||
| meshDef.extensions[this.extensionName] = paramsExt.getExtras() | |||||
| } | |||||
| }) | |||||
| this.document.getRoot() | |||||
| .listNodes() | |||||
| .forEach((node) => { | |||||
| const paramsExt = node.getExtension<GenericExtensionProperty>(this.extensionName) | |||||
| if (paramsExt) { | |||||
| const nodeIndex = context.nodeIndexMap.get(node)! | |||||
| const nodeDef = jsonDoc.json.nodes![nodeIndex] | |||||
| nodeDef.extensions = nodeDef.extensions || {} | |||||
| nodeDef.extensions[this.extensionName] = paramsExt.getExtras() | |||||
| } | |||||
| }) | |||||
| this.document.getRoot() | |||||
| .listScenes() | |||||
| .forEach((scene) => { | |||||
| const paramsExt = scene.getExtension<GenericExtensionProperty>(this.extensionName) | |||||
| if (paramsExt) { | |||||
| const sceneIndex = context.jsonDoc.json.scene || 0 // todo: get proper scene index, if working with multiple scenes, this will do the default one. | |||||
| const sceneDef = jsonDoc.json.scenes![sceneIndex] | |||||
| if (!sceneDef) return | |||||
| sceneDef.extensions = sceneDef.extensions || {} | |||||
| sceneDef.extensions[this.extensionName] = paramsExt.getExtras() | |||||
| } | |||||
| }) | |||||
| return this | |||||
| } | |||||
| } | |||||
| function stringToChannel(s: string) { | |||||
| let r = 0 | |||||
| if (s.includes('R')) r |= TextureChannel.R | |||||
| if (s.includes('G')) r |= TextureChannel.G | |||||
| if (s.includes('B')) r |= TextureChannel.B | |||||
| if (s.includes('A')) r |= TextureChannel.A | |||||
| return r | |||||
| } | |||||
| export function createGenericExtensionClass(name: string, textures?: Record<string, string|number>): typeof GenericExtension { | |||||
| return class extends GenericExtension { | |||||
| public static readonly EXTENSION_NAME = name | |||||
| readonly extensionName = name | |||||
| textureChannels: Record<string, number> = !textures ? {} : Object.fromEntries( | |||||
| Object.entries(textures) | |||||
| .map(([k, v])=> | |||||
| [k, typeof v === 'number' ? v : stringToChannel(v)]) | |||||
| ) | |||||
| } | |||||
| } |
| declare module '*.txt' { | |||||
| const content: string | |||||
| export default content | |||||
| } | |||||
| declare module '*.glsl' { | |||||
| const content: string | |||||
| export default content | |||||
| } | |||||
| declare module '*.vert' { | |||||
| const content: string | |||||
| export default content | |||||
| } | |||||
| declare module '*.frag' { | |||||
| const content: string | |||||
| export default content | |||||
| } | |||||
| declare module '*.module.scss' { | |||||
| const content: any | |||||
| export default content | |||||
| export const stylesheet: string | |||||
| } | |||||
| declare module '*.module.css' { | |||||
| const content: any | |||||
| export default content | |||||
| export const stylesheet: string | |||||
| } | |||||
| declare module '*.css' { | |||||
| const content: string | |||||
| export default content | |||||
| } | |||||
| declare module '*.css?inline' { // for vite | |||||
| const content: string | |||||
| export default content | |||||
| } | |||||
| // export {} | |||||
| // hack for typedoc | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| // declare type OffscreenCanvas = HTMLCanvasElement |
| export {GLTFDracoExporter, createGenericExtensionClass} from './GLTFDracoExporter' | |||||
| export {GLTFDracoExportPlugin} from './GLTFDracoExportPlugin' |
| { | |||||
| "compilerOptions": { | |||||
| "baseUrl": "./src", | |||||
| "rootDir": "./src", | |||||
| "allowJs": true, | |||||
| "checkJs": false, | |||||
| "skipLibCheck": true, | |||||
| "allowSyntheticDefaultImports": true, | |||||
| "experimentalDecorators": true, | |||||
| "isolatedModules": true, | |||||
| "module": "es2020", | |||||
| "noImplicitAny": true, | |||||
| "declaration": true, | |||||
| "declarationMap": true, | |||||
| "declarationDir": "dist", | |||||
| "outDir": "dist", | |||||
| "noImplicitThis": true, | |||||
| "noUnusedLocals": true, | |||||
| "noUnusedParameters": true, | |||||
| "removeComments": false, | |||||
| "preserveConstEnums": true, | |||||
| "moduleResolution": "node", | |||||
| "emitDecoratorMetadata": false, | |||||
| "sourceMap": true, | |||||
| "target": "ES2021", | |||||
| "strictNullChecks": true, | |||||
| "lib": [ | |||||
| "es2020", | |||||
| "esnext", | |||||
| "dom" | |||||
| ] | |||||
| }, | |||||
| "include": [ | |||||
| "src/**/*" | |||||
| ], | |||||
| "exclude": [ | |||||
| "node_modules", | |||||
| "**/*.spec.ts", | |||||
| "dist" | |||||
| ] | |||||
| } |
| { | |||||
| "extends": [ | |||||
| "../../typedoc.json" | |||||
| ], | |||||
| "entryPoints": [ | |||||
| "src/index.ts" | |||||
| ], | |||||
| "name": "Threepipe gltf-transform Plugins", | |||||
| "readme": "none" | |||||
| } |
| import {defineConfig} from 'vite' | |||||
| import json from '@rollup/plugin-json'; | |||||
| import dts from 'vite-plugin-dts' | |||||
| import packageJson from './package.json'; | |||||
| import license from 'rollup-plugin-license'; | |||||
| import replace from '@rollup/plugin-replace'; | |||||
| import glsl from 'rollup-plugin-glsl'; | |||||
| import path from 'node:path'; | |||||
| const isProd = process.env.NODE_ENV === 'production' | |||||
| const { name, version, author } = packageJson | |||||
| const {main, module, browser} = packageJson | |||||
| const globals = { | |||||
| 'three': 'threepipe', // just incase someone uses three | |||||
| 'threepipe': 'threepipe', | |||||
| } | |||||
| export default defineConfig({ | |||||
| optimizeDeps: { | |||||
| exclude: ['uiconfig.js', 'ts-browser-helpers'], | |||||
| }, | |||||
| base: '', | |||||
| // define: { | |||||
| // 'process.env': process.env | |||||
| // }, | |||||
| build: { | |||||
| sourcemap: true, | |||||
| minify: false, | |||||
| cssMinify: isProd, | |||||
| cssCodeSplit: false, | |||||
| watch: !isProd ? { | |||||
| buildDelay: 1000, | |||||
| } : null, | |||||
| lib: { | |||||
| entry: 'src/index.ts', | |||||
| formats: isProd ? ['es', 'umd'] : ['es'], | |||||
| name: name, | |||||
| fileName: (format) => (format === 'umd' ? main : module).replace('dist/', ''), | |||||
| }, | |||||
| outDir: 'dist', | |||||
| emptyOutDir: isProd, | |||||
| commonjsOptions: { | |||||
| exclude: [/uiconfig.js/, /ts-browser-helpers/], | |||||
| }, | |||||
| rollupOptions: { | |||||
| output: { | |||||
| // inlineDynamicImports: false, | |||||
| globals, | |||||
| }, | |||||
| external: Object.keys(globals), | |||||
| }, | |||||
| }, | |||||
| plugins: [ | |||||
| isProd ? dts({tsconfigPath: './tsconfig.json'}) : null, | |||||
| replace({ | |||||
| 'from \'three\'': 'from \'threepipe\'', | |||||
| delimiters: ['', ''], | |||||
| }), | |||||
| replace({ | |||||
| 'process.env.NODE_ENV': JSON.stringify(isProd ? 'production' : 'development'), | |||||
| preventAssignment: true, | |||||
| }), | |||||
| glsl({ // todo: minify glsl. | |||||
| include: 'src/**/*.glsl', | |||||
| }), | |||||
| json(), | |||||
| // postcss({ | |||||
| // modules: false, | |||||
| // autoModules: true, // todo; issues with typescript import css, because inject is false | |||||
| // inject: false, | |||||
| // minimize: isProduction, | |||||
| // // Or with custom options for `postcss-modules` | |||||
| // }), | |||||
| license({ | |||||
| banner: ` | |||||
| @license | |||||
| ${name} v${version} | |||||
| Copyright 2022<%= moment().format('YYYY') > 2022 ? '-' + moment().format('YYYY') : null %> ${author} | |||||
| ${packageJson.license} License | |||||
| See ./dependencies.txt for any bundled third-party dependencies and licenses. | |||||
| `, | |||||
| thirdParty: { | |||||
| output: path.join(__dirname, 'dist', 'dependencies.txt'), | |||||
| includePrivate: true, // Default is false. | |||||
| }, | |||||
| }), | |||||
| ], | |||||
| }) |