| @@ -44,6 +44,9 @@ To make changes and run the example, click on the CodePen button on the top righ | |||
| - [Table of Contents](#table-of-contents) | |||
| - [Getting Started](#getting-started) | |||
| - [HTML/JS Quickstart (CDN)](#htmljs-quickstart-cdn) | |||
| - [React](#react) | |||
| - [Vue.js](#vuejs) | |||
| - [Svelte](#svelte) | |||
| - [NPM/YARN Package](#npmyarn) | |||
| - [Installation](#installation) | |||
| - [Loading a 3D Model](#loading-a-3d-model) | |||
| @@ -145,10 +148,96 @@ To make changes and run the example, click on the CodePen button on the top righ | |||
| }) | |||
| </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. | |||
| ### 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 | |||
| ### Installation | |||
| @@ -17,7 +17,8 @@ | |||
| <body> | |||
| <canvas id="three-canvas" style="width: 800px; height: 600px;"></canvas> | |||
| <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')}) | |||
| // Load an environment map | |||
| @@ -32,3 +33,4 @@ | |||
| }) | |||
| </script> | |||
| </body> | |||
| </html> | |||
| @@ -0,0 +1,56 @@ | |||
| <!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> | |||
| @@ -0,0 +1,60 @@ | |||
| <!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> | |||
| @@ -0,0 +1,36 @@ | |||
| <!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> | |||
| @@ -0,0 +1,41 @@ | |||
| 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) | |||
| @@ -0,0 +1,29 @@ | |||
| <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> | |||
| @@ -0,0 +1,47 @@ | |||
| <!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> | |||
| @@ -28,10 +28,12 @@ | |||
| "es2020", | |||
| "esnext", | |||
| "dom" | |||
| ] | |||
| ], | |||
| "jsx": "react" | |||
| }, | |||
| "include": [ | |||
| "./**/*.ts" | |||
| "./**/*.ts", | |||
| "./**/*.tsx" | |||
| ], | |||
| "exclude": [ | |||
| "node_modules", | |||
| @@ -0,0 +1,57 @@ | |||
| <!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> | |||
| @@ -0,0 +1,48 @@ | |||
| <!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> | |||
| @@ -0,0 +1,36 @@ | |||
| <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> | |||
| @@ -44,7 +44,7 @@ | |||
| "typedoc": "^0.24.7", | |||
| "typescript": "^5.0.4", | |||
| "typescript-plugin-css-modules": "^5.0.1", | |||
| "uiconfig.js": "^0.0.7" | |||
| "uiconfig.js": "^0.0.8" | |||
| }, | |||
| "optionalDependencies": { | |||
| "win-node-env": "^0.6.1" | |||
| @@ -9826,9 +9826,9 @@ | |||
| } | |||
| }, | |||
| "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 | |||
| }, | |||
| "node_modules/unbox-primitive": { | |||
| @@ -17501,9 +17501,9 @@ | |||
| "dev": true | |||
| }, | |||
| "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 | |||
| }, | |||
| "unbox-primitive": { | |||
| @@ -6,6 +6,7 @@ | |||
| "module": "dist/index.mjs", | |||
| "types": "src/index.ts", | |||
| "sources": "src/index.ts", | |||
| "browser": "dist/index.js", | |||
| "type": "module", | |||
| "scripts": { | |||
| "new:pack": "npm run prepare && clean-package && npm pack && clean-package restore", | |||
| @@ -97,7 +98,7 @@ | |||
| "typedoc": "^0.24.7", | |||
| "typescript": "^5.0.4", | |||
| "typescript-plugin-css-modules": "^5.0.1", | |||
| "uiconfig.js": "^0.0.7", | |||
| "uiconfig.js": "^0.0.8", | |||
| "@rollup/plugin-replace": "^5.0.2", | |||
| "popmotion": "^11.0.5" | |||
| }, | |||
| @@ -109,7 +110,7 @@ | |||
| }, | |||
| "//": { | |||
| "dependencies": { | |||
| "uiconfig.js": "^0.0.7", | |||
| "uiconfig.js": "^0.0.8", | |||
| "ts-browser-helpers": "^0.8.0", | |||
| "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", | |||
| @@ -10,6 +10,7 @@ import {fileURLToPath} from 'url'; | |||
| import postcss from 'rollup-plugin-postcss' | |||
| import glsl from "rollup-plugin-glsl" | |||
| import replace from "@rollup/plugin-replace"; | |||
| import terser from "@rollup/plugin-terser"; | |||
| const __filename = fileURLToPath(import.meta.url); | |||
| const __dirname = path.dirname(__filename); | |||
| @@ -44,15 +45,15 @@ export default { | |||
| // preserveModulesRoot: 'src', // optional but useful to create a more plain folder structure | |||
| format: 'es' | |||
| }, | |||
| // { | |||
| // file: browser, | |||
| // ...settings, | |||
| // name: name, | |||
| // format: 'umd', | |||
| // plugins: [ | |||
| // isProduction && terser() | |||
| // ] | |||
| // } | |||
| { | |||
| file: browser, | |||
| ...settings, | |||
| name: name, | |||
| format: 'umd', | |||
| plugins: [ | |||
| isProduction && terser() | |||
| ] | |||
| } | |||
| ], | |||
| external: [], | |||
| plugins: [ | |||
| @@ -89,9 +89,10 @@ export class AssetImporter extends EventDispatcher<IAssetImporterEvent, IAssetIm | |||
| // 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 (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 === 'string') return await this.importPath<T>(assetOrPath, options) | |||
| console.error('AssetImporter: Invalid asset or path', assetOrPath) | |||
| @@ -185,6 +186,17 @@ export class AssetImporter extends EventDispatcher<IAssetImporterEvent, IAssetIm | |||
| 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. | |||
| * @param files | |||
| @@ -192,7 +192,7 @@ export class AssetManager extends EventDispatcher<BaseEvent&{data: ImportResult} | |||
| } | |||
| 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 [] | |||
| const imported = await this.importer.import<T>(assetOrPath, options) | |||
| if (!imported) { | |||
| @@ -261,7 +261,7 @@ export class AssetManager extends EventDispatcher<BaseEvent&{data: ImportResult} | |||
| 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] | |||
| } | |||
| @@ -385,7 +385,6 @@ export const iMaterialUI = { | |||
| type: 'slider', | |||
| bounds: [0, 1], | |||
| property: [material, 'transmission'], | |||
| limitedUi: true, | |||
| }, | |||
| { | |||
| type: 'slider', | |||
| @@ -51,25 +51,26 @@ export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjec | |||
| type: 'folder', | |||
| label: ()=>this.name || 'unnamed', | |||
| expanded: true, | |||
| limitedUi: true, | |||
| onChange: (ev)=>{ | |||
| if (!ev.config || ev.config.onChange) return | |||
| this.setDirty({uiChangeEvent: ev, needsUpdate: false, refreshUi: true}) | |||
| }, | |||
| children: [ | |||
| { | |||
| type: 'checkbox', | |||
| label: 'Visible', | |||
| property: [this, 'visible'], | |||
| limitedUi: true, | |||
| }, | |||
| { | |||
| type: 'button', | |||
| label: 'Pick/Focus', | |||
| label: 'Pick/Focus', // todo: move to the plugin that does the picking | |||
| value: ()=>{ | |||
| // todo instead of dispatching, make a IObject3D.select function | |||
| this.dispatchEvent({type: 'select', ui: true, object: this, bubbleToParent: true, focusCamera: true}) | |||
| }, | |||
| }, | |||
| { | |||
| type: 'button', | |||
| label: 'Pick Parent', | |||
| label: 'Pick Parent', // todo: move to the plugin that does the picking | |||
| hidden: ()=>!this.parent, | |||
| value: ()=>{ | |||
| const parent = this.parent | |||
| @@ -91,32 +92,27 @@ export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjec | |||
| label: 'Casts Shadow', | |||
| hidden: () => !(this as any).isMesh, | |||
| property: [this, 'castShadow'], | |||
| // onChange: this.setDirty, | |||
| }, | |||
| { | |||
| type: 'checkbox', | |||
| label: 'Receive Shadow', | |||
| hidden: () => !(this as any).isMesh, | |||
| property: [this, 'receiveShadow'], | |||
| // onChange: this.setDirty, | |||
| }, | |||
| { | |||
| type: 'checkbox', | |||
| label: 'Frustum culled', | |||
| property: [this, 'frustumCulled'], | |||
| // onChange: this.setDirty, | |||
| }, | |||
| { | |||
| type: 'vec3', | |||
| label: 'Position', | |||
| property: [this, 'position'], | |||
| limitedUi: true, | |||
| }, | |||
| { | |||
| type: 'vec3', | |||
| label: 'Rotation', | |||
| property: [this, 'rotation'], | |||
| limitedUi: true, | |||
| }, | |||
| { | |||
| type: 'vec3', | |||
| @@ -170,7 +166,6 @@ export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjec | |||
| type: 'input', | |||
| label: 'License/Credits', | |||
| property: [this.userData, 'license'], | |||
| limitedUi: true, | |||
| } : {}, | |||
| ], | |||
| } | |||
| @@ -226,13 +221,11 @@ export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjec | |||
| // if (object.children.length === 0) return { | |||
| // type: 'button', | |||
| // label: 'Select ' + (object.name || 'unnamed'), | |||
| // // limitedUi: true, | |||
| // value: dispatch, | |||
| // } | |||
| // return { | |||
| // type: 'folder', | |||
| // label: 'Select ' + (object.name || 'unnamed'), | |||
| // // limitedUi: true, | |||
| // children: object.children.map((child)=>makeHierarchyUi(child, root || object)), | |||
| // value: dispatch, | |||
| // onExpand: dispatch, | |||
| @@ -145,7 +145,6 @@ export class GLTFAnimationPlugin extends AViewerPluginSync<'checkpointEnd'|'chec | |||
| @uiButton('Play/Pause', (that: GLTFAnimationPlugin)=>({ | |||
| label:()=> that.animationState === 'playing' ? 'Pause' : 'Play', | |||
| limitedUi: true, | |||
| })) | |||
| playPauseAnimation() { | |||
| this._animationState === 'playing' ? this.pauseAnimation() : this.playAnimation() | |||
| @@ -4,7 +4,7 @@ export {dataTextureFromColor, dataTextureFromVec4, halfFloatToRgbe} from './conv | |||
| export {uniform, matDefine} from './decorators' | |||
| export {getEncodingComponents, getTexelEncoding, getTexelDecoding, getTexelDecoding2, getTexelDecodingFunction, getTexelEncodingFunction, getTextureColorSpaceFromMap} from './encoding' | |||
| 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 {ObjectPicker} from './ObjectPicker' | |||
| export {SelectionWidget, BoxSelectionWidget} from './SelectionWidget' | |||
| @@ -107,3 +107,12 @@ export function texImageToCanvas(image: TexImageSource, maxWidth: number, flipY | |||
| export function textureToDataUrl(texture: Texture|DataTexture, maxWidth: number, flipY: boolean, mimeType?: string, quality?: number) { | |||
| 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) | |||
| }) | |||
| } | |||
| @@ -308,7 +308,7 @@ export class ThreeViewer extends EventDispatcher<IViewerEvent, IViewerEventTypes | |||
| let container = options.container | |||
| if (container && !options.canvas) container.appendChild(this._canvas) | |||
| 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.setDirty = this.setDirty.bind(this) | |||
| this._animationLoop = this._animationLoop.bind(this) | |||
| @@ -431,7 +431,7 @@ export class ThreeViewer extends EventDispatcher<IViewerEvent, IViewerEventTypes | |||
| * @param obj | |||
| * @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 | |||
| return await this.assetManager.addAssetSingle<T>(obj, options) | |||
| } | |||