| - [Table of Contents](#table-of-contents) | - [Table of Contents](#table-of-contents) | ||||
| - [Getting Started](#getting-started) | - [Getting Started](#getting-started) | ||||
| - [HTML/JS Quickstart (CDN)](#htmljs-quickstart-cdn) | - [HTML/JS Quickstart (CDN)](#htmljs-quickstart-cdn) | ||||
| - [React](#react) | |||||
| - [Vue.js](#vuejs) | |||||
| - [Svelte](#svelte) | |||||
| - [NPM/YARN Package](#npmyarn) | - [NPM/YARN Package](#npmyarn) | ||||
| - [Installation](#installation) | - [Installation](#installation) | ||||
| - [Loading a 3D Model](#loading-a-3d-model) | - [Loading a 3D Model](#loading-a-3d-model) | ||||
| }) | }) | ||||
| </script> | </script> | ||||
| ``` | ``` | ||||
| Check it in action: https://threepipe.org/examples/#html-sample/ | |||||
| Check it in action: https://threepipe.org/examples/#html-js-sample/ | |||||
| Check out the details about the [ThreeViewer API](#viewer-api) and more [plugins](#threepipe-plugins) below. | Check out the details about the [ThreeViewer API](#viewer-api) and more [plugins](#threepipe-plugins) below. | ||||
| ### React | |||||
| A sample [react](https://react.dev) component in tsx to render a model with an environment map. | |||||
| ```tsx | |||||
| import React from 'react' | |||||
| function ThreeViewerComponent({src, env}: {src: string, env: string}) { | |||||
| const canvasRef = React.useRef(null) | |||||
| React.useEffect(() => { | |||||
| const viewer = new ThreeViewer({canvas: canvasRef.current}) | |||||
| const envPromise = viewer.setEnvironmentMap(env) | |||||
| const modelPromise = viewer.load(src) | |||||
| Promise.all([envPromise, modelPromise]) | |||||
| return () => { | |||||
| viewer.dispose() | |||||
| } | |||||
| }, []) | |||||
| return ( | |||||
| <canvas id="three-canvas" style={{width: 800, height: 600}} ref={canvasRef} /> | |||||
| ) | |||||
| } | |||||
| ``` | |||||
| Check it in action: https://threepipe.org/examples/#react-tsx-sample/ | |||||
| Other examples in js: https://threepipe.org/examples/#react-js-sample/ and jsx: https://threepipe.org/examples/#react-jsx-sample/ | |||||
| ### Vue.js | |||||
| A sample [vue.js](https://vuejs.org/) component in js to render a model with an environment map. | |||||
| ```js | |||||
| const ThreeViewerComponent = { | |||||
| setup() { | |||||
| const canvasRef = ref(null); | |||||
| onMounted(() => { | |||||
| const viewer = new ThreeViewer({ canvas: canvasRef.value }); | |||||
| const envPromise = viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr'); | |||||
| const modelPromise = viewer.load('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf'); | |||||
| Promise.all([envPromise, modelPromise]) | |||||
| onBeforeUnmount(() => { | |||||
| viewer.dispose(); | |||||
| }); | |||||
| }); | |||||
| return { canvasRef }; | |||||
| }, | |||||
| }; | |||||
| ``` | |||||
| Check it in action: https://threepipe.org/examples/#vue-html-sample/ | |||||
| Another example with Vue SFC(Single file component): https://threepipe.org/examples/#vue-sfc-sample/ | |||||
| ### Svelte | |||||
| A sample [svelte](https://svelte.dev/) component in js to render a model with an environment map. | |||||
| ```html | |||||
| <script> | |||||
| import {onDestroy, onMount} from 'svelte'; | |||||
| import {ThreeViewer} from 'threepipe'; | |||||
| let canvasRef; | |||||
| let viewer; | |||||
| onMount(() => { | |||||
| viewer = new ThreeViewer({canvas: canvasRef}); | |||||
| const envPromise = viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr'); | |||||
| const modelPromise = viewer.load('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf'); | |||||
| Promise.all([envPromise, modelPromise]) | |||||
| }); | |||||
| onDestroy(() => viewer.dispose()) | |||||
| </script> | |||||
| <canvas bind:this={canvasRef} id="three-canvas" style="width: 800px; height: 600px"></canvas> | |||||
| ``` | |||||
| Check it in action: https://threepipe.org/examples/#svelte-sample/ | |||||
| ### NPM/YARN | ### NPM/YARN | ||||
| ### Installation | ### Installation |
| <body> | <body> | ||||
| <canvas id="three-canvas" style="width: 800px; height: 600px;"></canvas> | <canvas id="three-canvas" style="width: 800px; height: 600px;"></canvas> | ||||
| <script id="example-script" type="module" data-scripts="./index.html"> | <script id="example-script" type="module" data-scripts="./index.html"> | ||||
| import {ThreeViewer} from 'https://threepipe.org/dist/index.mjs' | |||||
| // import {ThreeViewer} from 'https://threepipe.org/dist/index.mjs' | |||||
| import {ThreeViewer} from './../../dist/index.mjs' | |||||
| const viewer = new ThreeViewer({canvas: document.getElementById('three-canvas')}) | const viewer = new ThreeViewer({canvas: document.getElementById('three-canvas')}) | ||||
| // Load an environment map | // Load an environment map | ||||
| }) | }) | ||||
| </script> | </script> | ||||
| </body> | </body> | ||||
| </html> |
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>Threepipe React/JS Sample</title> | |||||
| <style> | |||||
| html, body{ | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| margin: 0; | |||||
| overflow: hidden; | |||||
| } | |||||
| </style> | |||||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||||
| </head> | |||||
| <body> | |||||
| <div id="root"></div> | |||||
| <script id="example-script" type="module" data-scripts="./index.html"> | |||||
| // import {ThreeViewer} from 'https://threepipe.org/dist/index.mjs' | |||||
| import {ThreeViewer} from './../../dist/index.mjs' | |||||
| import React from 'https://esm.sh/react' | |||||
| import ReactDOM from 'https://esm.sh/react-dom' | |||||
| function ThreeViewerComponent({ src }) { | |||||
| const canvasRef = React.useRef(null); | |||||
| React.useEffect(() => { | |||||
| const viewer = new ThreeViewer({canvas: canvasRef.current}) | |||||
| // Load an environment map | |||||
| const envPromise = viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||||
| const modelPromise = viewer.load('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | |||||
| autoCenter: true, | |||||
| autoScale: true, | |||||
| }) | |||||
| Promise.all([envPromise, modelPromise]).then(([env, model])=>{ | |||||
| console.log('Loaded', model, env, viewer) | |||||
| }) | |||||
| return () => { | |||||
| viewer.dispose() | |||||
| } | |||||
| }, []); | |||||
| return React.createElement( | |||||
| 'canvas', | |||||
| {id: 'three-canvas', style: {width: 800, height: 600}, ref: canvasRef}, | |||||
| ) | |||||
| } | |||||
| ReactDOM.render( | |||||
| React.createElement(ThreeViewerComponent), | |||||
| document.getElementById('root') | |||||
| ) | |||||
| </script> | |||||
| </body> | |||||
| </html> |
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>Threepipe React/JSX Sample</title> | |||||
| <style> | |||||
| html, body{ | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| margin: 0; | |||||
| overflow: hidden; | |||||
| } | |||||
| </style> | |||||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||||
| <!-- Include Babel for JSX transformation --> | |||||
| <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> | |||||
| </head> | |||||
| <body> | |||||
| <div id="root"></div> | |||||
| <script id="example-script" type="text/babel" data-scripts="./index.html" data-type="module"> | |||||
| // import {ThreeViewer} from 'https://threepipe.org/dist/index.mjs' | |||||
| import {ThreeViewer} from './../../dist/index.mjs' | |||||
| import React from 'https://esm.sh/react' | |||||
| import ReactDOM from 'https://esm.sh/react-dom' | |||||
| function ThreeViewerComponent({ src, env }) { | |||||
| const canvasRef = React.useRef(null); | |||||
| React.useEffect(() => { | |||||
| const viewer = new ThreeViewer({canvas: canvasRef.current}) | |||||
| // Load an environment map | |||||
| const envPromise = viewer.setEnvironmentMap(env) | |||||
| const modelPromise = viewer.load(src, { | |||||
| autoCenter: true, | |||||
| autoScale: true, | |||||
| }) | |||||
| Promise.all([envPromise, modelPromise]).then(([env, model])=>{ | |||||
| console.log('Loaded', model, env, viewer) | |||||
| }) | |||||
| return () => { | |||||
| viewer.dispose() | |||||
| } | |||||
| }, []); | |||||
| return ( | |||||
| <canvas id="three-canvas" style={{ width: 800, height: 600 }} ref={canvasRef} /> | |||||
| ) | |||||
| } | |||||
| ReactDOM.render( | |||||
| <ThreeViewerComponent | |||||
| src={'https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf'} | |||||
| env={'https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr'} | |||||
| />, | |||||
| document.getElementById('root') | |||||
| ) | |||||
| </script> | |||||
| </body> | |||||
| </html> |
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>Threepipe React/TSX Sample</title> | |||||
| <!-- 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", | |||||
| "react": "https://esm.sh/react", | |||||
| "react-dom": "https://esm.sh/react-dom" | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style> | |||||
| html, body{ | |||||
| 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.tsx;./script.js"></script> | |||||
| </head> | |||||
| <body> | |||||
| <div id="root"></div> | |||||
| </body> | |||||
| </html> |
| import {_testFinish, ThreeViewer} from 'threepipe' | |||||
| // @ts-expect-error no need react here | |||||
| import React from 'react' | |||||
| // @ts-expect-error no need react-dom here | |||||
| import ReactDOM from 'react-dom' | |||||
| function ThreeViewerComponent({src, env}: {src: string, env: string}) { | |||||
| const canvasRef = React.useRef(null) | |||||
| React.useEffect(() => { | |||||
| const viewer = new ThreeViewer({canvas: canvasRef.current}) | |||||
| // Load an environment map | |||||
| const envPromise = viewer.setEnvironmentMap(env) | |||||
| const modelPromise = viewer.load(src, { | |||||
| autoCenter: true, | |||||
| autoScale: true, | |||||
| }) | |||||
| Promise.all([envPromise, modelPromise]).then(([env, model])=>{ | |||||
| console.log('Loaded', model, env, viewer) | |||||
| }) | |||||
| return () => { | |||||
| viewer.dispose() | |||||
| } | |||||
| }, []) | |||||
| return ( | |||||
| <canvas id="three-canvas" style={{width: 800, height: 600}} ref={canvasRef} /> | |||||
| ) | |||||
| } | |||||
| async function init() { | |||||
| ReactDOM.render( | |||||
| <ThreeViewerComponent | |||||
| src={'https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf'} | |||||
| env={'https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr'} | |||||
| />, | |||||
| document.getElementById('root') | |||||
| ) | |||||
| } | |||||
| init().then(_testFinish) |
| <script> | |||||
| import {onDestroy, onMount} from 'svelte'; | |||||
| const {ThreeViewer} = window.threepipe; // umd imported from unpkg in index.html | |||||
| // or | |||||
| // import {ThreeViewer} from 'threepipe'; // esm imported from npm | |||||
| let canvasRef; | |||||
| let viewer; | |||||
| onMount(() => { | |||||
| viewer = new ThreeViewer({canvas: canvasRef}); | |||||
| // Load an environment map | |||||
| const envPromise = viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr'); | |||||
| const modelPromise = viewer.load('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | |||||
| autoCenter: true, | |||||
| autoScale: true, | |||||
| }); | |||||
| Promise.all([envPromise, modelPromise]).then(([env, model]) => { | |||||
| console.log('Loaded', model, env, viewer); | |||||
| }); | |||||
| }); | |||||
| onDestroy(() => viewer.dispose()) | |||||
| </script> | |||||
| <canvas bind:this={canvasRef} id="three-canvas" style="width: 800px; height: 600px"></canvas> |
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>Threepipe Svelte Sample</title> | |||||
| <!-- 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" | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style> | |||||
| html, body{ | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| margin: 0; | |||||
| overflow: hidden; | |||||
| } | |||||
| </style> | |||||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||||
| <script src="./../../dist/index.js"></script> | |||||
| <!-- <script src="https://unpkg.com/threepipe"></script>--> | |||||
| <script src="https://unpkg.com/svelte-browser-import"></script> | |||||
| </head> | |||||
| <body> | |||||
| <div id="app"> | |||||
| </div> | |||||
| <script type="module" data-scripts="./App.svelte" id="example-script"> | |||||
| window["svelte-browser-import"].importSvelte('./App.svelte').then(App=> { | |||||
| const app = new App({ | |||||
| target: document.getElementById('app'), | |||||
| }) | |||||
| // to destroy the app | |||||
| // app.$destroy() | |||||
| }) | |||||
| </script> | |||||
| </body> | |||||
| </html> |
| "es2020", | "es2020", | ||||
| "esnext", | "esnext", | ||||
| "dom" | "dom" | ||||
| ] | |||||
| ], | |||||
| "jsx": "react" | |||||
| }, | }, | ||||
| "include": [ | "include": [ | ||||
| "./**/*.ts" | |||||
| "./**/*.ts", | |||||
| "./**/*.tsx" | |||||
| ], | ], | ||||
| "exclude": [ | "exclude": [ | ||||
| "node_modules", | "node_modules", |
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>Threepipe Vue/HTML Sample</title> | |||||
| <style> | |||||
| html, body{ | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| margin: 0; | |||||
| overflow: hidden; | |||||
| } | |||||
| </style> | |||||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||||
| </head> | |||||
| <body> | |||||
| <div id="app"> | |||||
| <canvas id="three-canvas" style="width: 800px; height: 600px" ref="canvasRef"></canvas> | |||||
| </div> | |||||
| <script id="example-script" type="module" data-scripts="./index.html"> | |||||
| // import { ThreeViewer } from 'https://threepipe.org/dist/index.mjs' | |||||
| import { ThreeViewer } from './../../dist/index.mjs' | |||||
| import { createApp, ref, onMounted, onBeforeUnmount } from "https://unpkg.com/vue@3/dist/vue.esm-browser.prod.js"; | |||||
| const ThreeViewerComponent = { | |||||
| setup() { | |||||
| const canvasRef = ref(null); | |||||
| onMounted(() => { | |||||
| const viewer = new ThreeViewer({ canvas: canvasRef.value }); | |||||
| // Load an environment map | |||||
| const envPromise = viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr'); | |||||
| const modelPromise = viewer.load('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | |||||
| autoCenter: true, | |||||
| autoScale: true, | |||||
| }); | |||||
| Promise.all([envPromise, modelPromise]).then(([env, model]) => { | |||||
| console.log('Loaded', model, env, viewer); | |||||
| }); | |||||
| onBeforeUnmount(() => { | |||||
| viewer.dispose(); | |||||
| }); | |||||
| }); | |||||
| return { canvasRef }; | |||||
| }, | |||||
| }; | |||||
| const app = createApp(ThreeViewerComponent); | |||||
| app.mount('#app'); | |||||
| </script> | |||||
| </body> | |||||
| </html> |
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>Threepipe React/TSX Sample</title> | |||||
| <!-- 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", | |||||
| "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.prod.js", | |||||
| "vue-import": "https://unpkg.com/vue-import/dist/vue-import.esm-browser.js" | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <!-- "vue-import": "./vue-import/dist/vue-import.esm-browser.prod.js"--> | |||||
| <style> | |||||
| html, body{ | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| margin: 0; | |||||
| overflow: hidden; | |||||
| } | |||||
| </style> | |||||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||||
| </head> | |||||
| <body> | |||||
| <div id="app"> | |||||
| <three-viewer></three-viewer> | |||||
| </div> | |||||
| <script type="module" data-scripts="./script.vue" id="example-script"> | |||||
| import { createApp } from 'vue'; | |||||
| import vueImport from 'vue-import'; | |||||
| (async ()=>{ | |||||
| const app = createApp(); | |||||
| app.component('three-viewer', await vueImport('script.vue', {})) | |||||
| app.mount('#app'); | |||||
| })() | |||||
| </script> | |||||
| </body> | |||||
| </html> |
| <template> | |||||
| <canvas id="three-canvas" style="width: 800px; height: 600px" ref="canvasRef"></canvas> | |||||
| </template> | |||||
| <script> | |||||
| import {ThreeViewer} from "threepipe"; | |||||
| import {onBeforeUnmount, onMounted, ref} from "vue" | |||||
| export default { | |||||
| setup() { | |||||
| const canvasRef = ref(null); | |||||
| onMounted(() => { | |||||
| const viewer = new ThreeViewer({canvas: canvasRef.value}); | |||||
| // Load an environment map | |||||
| const envPromise = viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr'); | |||||
| const modelPromise = viewer.load('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | |||||
| autoCenter: true, | |||||
| autoScale: true, | |||||
| }); | |||||
| Promise.all([envPromise, modelPromise]).then(([env, model]) => { | |||||
| console.log('Loaded', model, env, viewer); | |||||
| }); | |||||
| onBeforeUnmount(() => { | |||||
| viewer.dispose(); | |||||
| }); | |||||
| }); | |||||
| return {canvasRef}; | |||||
| }, | |||||
| }; | |||||
| </script> |
| "typedoc": "^0.24.7", | "typedoc": "^0.24.7", | ||||
| "typescript": "^5.0.4", | "typescript": "^5.0.4", | ||||
| "typescript-plugin-css-modules": "^5.0.1", | "typescript-plugin-css-modules": "^5.0.1", | ||||
| "uiconfig.js": "^0.0.7" | |||||
| "uiconfig.js": "^0.0.8" | |||||
| }, | }, | ||||
| "optionalDependencies": { | "optionalDependencies": { | ||||
| "win-node-env": "^0.6.1" | "win-node-env": "^0.6.1" | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/uiconfig.js": { | "node_modules/uiconfig.js": { | ||||
| "version": "0.0.7", | |||||
| "resolved": "https://registry.npmjs.org/uiconfig.js/-/uiconfig.js-0.0.7.tgz", | |||||
| "integrity": "sha512-PNZkeNd52ETa5UQRu5XLXqJZhAUoUSzCiBfYVbl+7GdRIt65XTmPgNIjqeZXL59g6zhaGrkWTLa0AVyeZzVJZQ==", | |||||
| "version": "0.0.8", | |||||
| "resolved": "https://registry.npmjs.org/uiconfig.js/-/uiconfig.js-0.0.8.tgz", | |||||
| "integrity": "sha512-0H1OO4CNHP5O0LBy82YWWFCzDK+Yf/GtXnR3i968FkMkf0+3/JsW7MC8ea2CcPtsi8ni4TA1FrMOC+KrYmMnCQ==", | |||||
| "dev": true | "dev": true | ||||
| }, | }, | ||||
| "node_modules/unbox-primitive": { | "node_modules/unbox-primitive": { | ||||
| "dev": true | "dev": true | ||||
| }, | }, | ||||
| "uiconfig.js": { | "uiconfig.js": { | ||||
| "version": "0.0.7", | |||||
| "resolved": "https://registry.npmjs.org/uiconfig.js/-/uiconfig.js-0.0.7.tgz", | |||||
| "integrity": "sha512-PNZkeNd52ETa5UQRu5XLXqJZhAUoUSzCiBfYVbl+7GdRIt65XTmPgNIjqeZXL59g6zhaGrkWTLa0AVyeZzVJZQ==", | |||||
| "version": "0.0.8", | |||||
| "resolved": "https://registry.npmjs.org/uiconfig.js/-/uiconfig.js-0.0.8.tgz", | |||||
| "integrity": "sha512-0H1OO4CNHP5O0LBy82YWWFCzDK+Yf/GtXnR3i968FkMkf0+3/JsW7MC8ea2CcPtsi8ni4TA1FrMOC+KrYmMnCQ==", | |||||
| "dev": true | "dev": true | ||||
| }, | }, | ||||
| "unbox-primitive": { | "unbox-primitive": { |
| "module": "dist/index.mjs", | "module": "dist/index.mjs", | ||||
| "types": "src/index.ts", | "types": "src/index.ts", | ||||
| "sources": "src/index.ts", | "sources": "src/index.ts", | ||||
| "browser": "dist/index.js", | |||||
| "type": "module", | "type": "module", | ||||
| "scripts": { | "scripts": { | ||||
| "new:pack": "npm run prepare && clean-package && npm pack && clean-package restore", | "new:pack": "npm run prepare && clean-package && npm pack && clean-package restore", | ||||
| "typedoc": "^0.24.7", | "typedoc": "^0.24.7", | ||||
| "typescript": "^5.0.4", | "typescript": "^5.0.4", | ||||
| "typescript-plugin-css-modules": "^5.0.1", | "typescript-plugin-css-modules": "^5.0.1", | ||||
| "uiconfig.js": "^0.0.7", | |||||
| "uiconfig.js": "^0.0.8", | |||||
| "@rollup/plugin-replace": "^5.0.2", | "@rollup/plugin-replace": "^5.0.2", | ||||
| "popmotion": "^11.0.5" | "popmotion": "^11.0.5" | ||||
| }, | }, | ||||
| }, | }, | ||||
| "//": { | "//": { | ||||
| "dependencies": { | "dependencies": { | ||||
| "uiconfig.js": "^0.0.7", | |||||
| "uiconfig.js": "^0.0.8", | |||||
| "ts-browser-helpers": "^0.8.0", | "ts-browser-helpers": "^0.8.0", | ||||
| "three": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2018/package.tgz", | "three": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2018/package.tgz", | ||||
| "three-f": "https://github.com/repalash/three.js-modded/archive/refs/tags/v0.152.2018.tar.gz", | "three-f": "https://github.com/repalash/three.js-modded/archive/refs/tags/v0.152.2018.tar.gz", |
| import postcss from 'rollup-plugin-postcss' | import postcss from 'rollup-plugin-postcss' | ||||
| import glsl from "rollup-plugin-glsl" | import glsl from "rollup-plugin-glsl" | ||||
| import replace from "@rollup/plugin-replace"; | import replace from "@rollup/plugin-replace"; | ||||
| import terser from "@rollup/plugin-terser"; | |||||
| const __filename = fileURLToPath(import.meta.url); | const __filename = fileURLToPath(import.meta.url); | ||||
| const __dirname = path.dirname(__filename); | const __dirname = path.dirname(__filename); | ||||
| // preserveModulesRoot: 'src', // optional but useful to create a more plain folder structure | // preserveModulesRoot: 'src', // optional but useful to create a more plain folder structure | ||||
| format: 'es' | format: 'es' | ||||
| }, | }, | ||||
| // { | |||||
| // file: browser, | |||||
| // ...settings, | |||||
| // name: name, | |||||
| // format: 'umd', | |||||
| // plugins: [ | |||||
| // isProduction && terser() | |||||
| // ] | |||||
| // } | |||||
| { | |||||
| file: browser, | |||||
| ...settings, | |||||
| name: name, | |||||
| format: 'umd', | |||||
| plugins: [ | |||||
| isProduction && terser() | |||||
| ] | |||||
| } | |||||
| ], | ], | ||||
| external: [], | external: [], | ||||
| plugins: [ | plugins: [ |
| // region import functions | // region import functions | ||||
| async import<T extends ImportResult|undefined = ImportResult>(assetOrPath?: string | IAsset | IAsset[], options?: ImportAssetOptions): Promise<(T|undefined)[]> { | |||||
| async import<T extends ImportResult|undefined = ImportResult>(assetOrPath?: string | IAsset | IAsset[] | File | File[], options?: ImportAssetOptions): Promise<(T|undefined)[]> { | |||||
| if (!assetOrPath) return [] | if (!assetOrPath) return [] | ||||
| if (Array.isArray(assetOrPath)) return (await Promise.all(assetOrPath.map(async a => this.import<T>(a, options)))).flat(1) | if (Array.isArray(assetOrPath)) return (await Promise.all(assetOrPath.map(async a => this.import<T>(a, options)))).flat(1) | ||||
| if (assetOrPath instanceof File) return await this.importFile<T>(assetOrPath, options) | |||||
| if (typeof assetOrPath === 'object') return await this.importAsset<T>(assetOrPath, options) | if (typeof assetOrPath === 'object') return await this.importAsset<T>(assetOrPath, options) | ||||
| if (typeof assetOrPath === 'string') return await this.importPath<T>(assetOrPath, options) | if (typeof assetOrPath === 'string') return await this.importPath<T>(assetOrPath, options) | ||||
| console.error('AssetImporter: Invalid asset or path', assetOrPath) | console.error('AssetImporter: Invalid asset or path', assetOrPath) | ||||
| return result | return result | ||||
| } | } | ||||
| async importFile<T extends ImportResult|undefined = ImportResult|undefined>(file?: File, options: ImportAssetOptions = {}, onDownloadProgress?: (e:ProgressEvent)=>void): Promise<T[]> { | |||||
| if (!file) return [] | |||||
| if (!(file instanceof File)) { | |||||
| console.error('AssetImporter: Invalid file', file) | |||||
| return [] | |||||
| } | |||||
| return this.importAsset(this._cachedAssets.find(a=>a.file === file) ?? { | |||||
| path: file.name || file.webkitRelativePath, file, | |||||
| }, options, onDownloadProgress) | |||||
| } | |||||
| /** | /** | ||||
| * Import multiple local files/blobs from a map of files, like when a local folder is loaded, or when multiple files are dropped. | * Import multiple local files/blobs from a map of files, like when a local folder is loaded, or when multiple files are dropped. | ||||
| * @param files | * @param files |
| } | } | ||||
| async addAsset<T extends ImportResult = ImportResult>(assetOrPath?: string | IAsset | IAsset[], options?: ImportAddOptions): Promise<(T|undefined)[]> { | |||||
| async addAsset<T extends ImportResult = ImportResult>(assetOrPath?: string | IAsset | IAsset[] | File | File[], options?: ImportAddOptions): Promise<(T|undefined)[]> { | |||||
| if (!this.importer || !this.viewer) return [] | if (!this.importer || !this.viewer) return [] | ||||
| const imported = await this.importer.import<T>(assetOrPath, options) | const imported = await this.importer.import<T>(assetOrPath, options) | ||||
| if (!imported) { | if (!imported) { | ||||
| return this.loadImported(imported, options) | return this.loadImported(imported, options) | ||||
| } | } | ||||
| async addAssetSingle<T extends ImportResult = ImportResult>(asset?: IAsset | string, options?: ImportAssetOptions): Promise<T|undefined> { | |||||
| async addAssetSingle<T extends ImportResult = ImportResult>(asset?: string | IAsset | File, options?: ImportAssetOptions): Promise<T|undefined> { | |||||
| return !asset ? undefined : (await this.addAsset<T>(asset, options))?.[0] | return !asset ? undefined : (await this.addAsset<T>(asset, options))?.[0] | ||||
| } | } | ||||
| type: 'slider', | type: 'slider', | ||||
| bounds: [0, 1], | bounds: [0, 1], | ||||
| property: [material, 'transmission'], | property: [material, 'transmission'], | ||||
| limitedUi: true, | |||||
| }, | }, | ||||
| { | { | ||||
| type: 'slider', | type: 'slider', |
| type: 'folder', | type: 'folder', | ||||
| label: ()=>this.name || 'unnamed', | label: ()=>this.name || 'unnamed', | ||||
| expanded: true, | expanded: true, | ||||
| limitedUi: true, | |||||
| onChange: (ev)=>{ | |||||
| if (!ev.config || ev.config.onChange) return | |||||
| this.setDirty({uiChangeEvent: ev, needsUpdate: false, refreshUi: true}) | |||||
| }, | |||||
| children: [ | children: [ | ||||
| { | { | ||||
| type: 'checkbox', | type: 'checkbox', | ||||
| label: 'Visible', | label: 'Visible', | ||||
| property: [this, 'visible'], | property: [this, 'visible'], | ||||
| limitedUi: true, | |||||
| }, | }, | ||||
| { | { | ||||
| type: 'button', | type: 'button', | ||||
| label: 'Pick/Focus', | |||||
| label: 'Pick/Focus', // todo: move to the plugin that does the picking | |||||
| value: ()=>{ | value: ()=>{ | ||||
| // todo instead of dispatching, make a IObject3D.select function | |||||
| this.dispatchEvent({type: 'select', ui: true, object: this, bubbleToParent: true, focusCamera: true}) | this.dispatchEvent({type: 'select', ui: true, object: this, bubbleToParent: true, focusCamera: true}) | ||||
| }, | }, | ||||
| }, | }, | ||||
| { | { | ||||
| type: 'button', | type: 'button', | ||||
| label: 'Pick Parent', | |||||
| label: 'Pick Parent', // todo: move to the plugin that does the picking | |||||
| hidden: ()=>!this.parent, | hidden: ()=>!this.parent, | ||||
| value: ()=>{ | value: ()=>{ | ||||
| const parent = this.parent | const parent = this.parent | ||||
| label: 'Casts Shadow', | label: 'Casts Shadow', | ||||
| hidden: () => !(this as any).isMesh, | hidden: () => !(this as any).isMesh, | ||||
| property: [this, 'castShadow'], | property: [this, 'castShadow'], | ||||
| // onChange: this.setDirty, | |||||
| }, | }, | ||||
| { | { | ||||
| type: 'checkbox', | type: 'checkbox', | ||||
| label: 'Receive Shadow', | label: 'Receive Shadow', | ||||
| hidden: () => !(this as any).isMesh, | hidden: () => !(this as any).isMesh, | ||||
| property: [this, 'receiveShadow'], | property: [this, 'receiveShadow'], | ||||
| // onChange: this.setDirty, | |||||
| }, | }, | ||||
| { | { | ||||
| type: 'checkbox', | type: 'checkbox', | ||||
| label: 'Frustum culled', | label: 'Frustum culled', | ||||
| property: [this, 'frustumCulled'], | property: [this, 'frustumCulled'], | ||||
| // onChange: this.setDirty, | |||||
| }, | }, | ||||
| { | { | ||||
| type: 'vec3', | type: 'vec3', | ||||
| label: 'Position', | label: 'Position', | ||||
| property: [this, 'position'], | property: [this, 'position'], | ||||
| limitedUi: true, | |||||
| }, | }, | ||||
| { | { | ||||
| type: 'vec3', | type: 'vec3', | ||||
| label: 'Rotation', | label: 'Rotation', | ||||
| property: [this, 'rotation'], | property: [this, 'rotation'], | ||||
| limitedUi: true, | |||||
| }, | }, | ||||
| { | { | ||||
| type: 'vec3', | type: 'vec3', | ||||
| type: 'input', | type: 'input', | ||||
| label: 'License/Credits', | label: 'License/Credits', | ||||
| property: [this.userData, 'license'], | property: [this.userData, 'license'], | ||||
| limitedUi: true, | |||||
| } : {}, | } : {}, | ||||
| ], | ], | ||||
| } | } | ||||
| // if (object.children.length === 0) return { | // if (object.children.length === 0) return { | ||||
| // type: 'button', | // type: 'button', | ||||
| // label: 'Select ' + (object.name || 'unnamed'), | // label: 'Select ' + (object.name || 'unnamed'), | ||||
| // // limitedUi: true, | |||||
| // value: dispatch, | // value: dispatch, | ||||
| // } | // } | ||||
| // return { | // return { | ||||
| // type: 'folder', | // type: 'folder', | ||||
| // label: 'Select ' + (object.name || 'unnamed'), | // label: 'Select ' + (object.name || 'unnamed'), | ||||
| // // limitedUi: true, | |||||
| // children: object.children.map((child)=>makeHierarchyUi(child, root || object)), | // children: object.children.map((child)=>makeHierarchyUi(child, root || object)), | ||||
| // value: dispatch, | // value: dispatch, | ||||
| // onExpand: dispatch, | // onExpand: dispatch, |
| @uiButton('Play/Pause', (that: GLTFAnimationPlugin)=>({ | @uiButton('Play/Pause', (that: GLTFAnimationPlugin)=>({ | ||||
| label:()=> that.animationState === 'playing' ? 'Pause' : 'Play', | label:()=> that.animationState === 'playing' ? 'Pause' : 'Play', | ||||
| limitedUi: true, | |||||
| })) | })) | ||||
| playPauseAnimation() { | playPauseAnimation() { | ||||
| this._animationState === 'playing' ? this.pauseAnimation() : this.playAnimation() | this._animationState === 'playing' ? this.pauseAnimation() : this.playAnimation() |
| export {uniform, matDefine} from './decorators' | export {uniform, matDefine} from './decorators' | ||||
| export {getEncodingComponents, getTexelEncoding, getTexelDecoding, getTexelDecoding2, getTexelDecodingFunction, getTexelEncodingFunction, getTextureColorSpaceFromMap} from './encoding' | export {getEncodingComponents, getTexelEncoding, getTexelDecoding, getTexelDecoding2, getTexelDecodingFunction, getTexelEncodingFunction, getTextureColorSpaceFromMap} from './encoding' | ||||
| export {generateUUID, toIndexedGeometry, isInScene, localToWorldQuaternion, worldToLocalQuaternion} from './misc' | export {generateUUID, toIndexedGeometry, isInScene, localToWorldQuaternion, worldToLocalQuaternion} from './misc' | ||||
| export {getTextureDataType, textureToCanvas, textureDataToImageData, textureToDataUrl, texImageToCanvas} from './texture' | |||||
| export {getTextureDataType, textureToCanvas, textureDataToImageData, textureToDataUrl, textureToBlob, texImageToCanvas} from './texture' | |||||
| export {threeConstMappings} from './const-mappings' | export {threeConstMappings} from './const-mappings' | ||||
| export {ObjectPicker} from './ObjectPicker' | export {ObjectPicker} from './ObjectPicker' | ||||
| export {SelectionWidget, BoxSelectionWidget} from './SelectionWidget' | export {SelectionWidget, BoxSelectionWidget} from './SelectionWidget' |
| export function textureToDataUrl(texture: Texture|DataTexture, maxWidth: number, flipY: boolean, mimeType?: string, quality?: number) { | export function textureToDataUrl(texture: Texture|DataTexture, maxWidth: number, flipY: boolean, mimeType?: string, quality?: number) { | ||||
| return textureToCanvas(texture, maxWidth, flipY).toDataURL(mimeType, quality) | return textureToCanvas(texture, maxWidth, flipY).toDataURL(mimeType, quality) | ||||
| } | } | ||||
| export async function textureToBlob(texture: Texture|DataTexture, maxWidth: number, flipY: boolean, mimeType?: string, quality?: number) { | |||||
| const canvas = textureToCanvas(texture, maxWidth, flipY) | |||||
| return new Promise<Blob>((resolve, reject) => { | |||||
| canvas.toBlob(blob => { | |||||
| if (blob) resolve(blob) | |||||
| else reject(new Error('Failed to create blob')) | |||||
| }, mimeType, quality) | |||||
| }) | |||||
| } |
| let container = options.container | let container = options.container | ||||
| if (container && !options.canvas) container.appendChild(this._canvas) | if (container && !options.canvas) container.appendChild(this._canvas) | ||||
| if (!container) container = this._canvas.parentElement ?? undefined | if (!container) container = this._canvas.parentElement ?? undefined | ||||
| if (!container) throw new Error('No container.') | |||||
| if (!container) throw new Error('No container(or canvas).') | |||||
| this._container = container | this._container = container | ||||
| this.setDirty = this.setDirty.bind(this) | this.setDirty = this.setDirty.bind(this) | ||||
| this._animationLoop = this._animationLoop.bind(this) | this._animationLoop = this._animationLoop.bind(this) | ||||
| * @param obj | * @param obj | ||||
| * @param options | * @param options | ||||
| */ | */ | ||||
| async load<T extends ImportResult = ImportResult>(obj: string | IAsset | null, options?: ImportAddOptions) { | |||||
| async load<T extends ImportResult = ImportResult>(obj: string | IAsset | File | null, options?: ImportAddOptions) { | |||||
| if (!obj) return | if (!obj) return | ||||
| return await this.assetManager.addAssetSingle<T>(obj, options) | return await this.assetManager.addAssetSingle<T>(obj, options) | ||||
| } | } |