| @@ -10,7 +10,7 @@ if(exampleScript.textContent) scripts.push(exampleScript) | |||
| const exampleStyle = document.querySelector('#example-style') | |||
| const css = exampleStyle ? exampleStyle.textContent : '' | |||
| const importMap = document.querySelector('script[type="importmap"]') | |||
| const imports = JSON.parse(importMap.textContent||'{}').imports||{} | |||
| const imports = importMap ? JSON.parse(importMap.textContent||'{}').imports||{} : {} | |||
| Object.entries(imports).forEach(([k,v])=>imports[k] = v.replace(/^\.\/\.\.\/\.\.\//, rootPath)) // ./../../ -> rootPath | |||
| function replaceImports(code) { | |||
| for (const [name, link] of Object.entries(imports)) code = code.replaceAll(` from '${name}'`, ` from '${link}'`) | |||
| @@ -19,7 +19,7 @@ function replaceImports(code) { | |||
| .replaceAll(` from '../`, ` from '${rootPath+examplePath}`) | |||
| } | |||
| setupCodePreview( | |||
| document.getElementById('canvas-container') || document.querySelector('.code-preview-container'), | |||
| document.getElementById('canvas-container') || document.querySelector('.code-preview-container') || document.body, | |||
| scripts, | |||
| scripts.map(s=>s.textContent ? 'js' : s.split('.').pop()), // title | |||
| scripts.map(s=>(typeof s === 'string' && s.endsWith('.js')) ? s : (codePath+examplePath+window.location.pathname.split('/examples/').pop().replace('index.html', '')+(s.textContent ? 'index.html' : s))), // todo: github link | |||
| @@ -0,0 +1,34 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>Threepipe HTML/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> | |||
| <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' | |||
| const viewer = new ThreeViewer({canvas: document.getElementById('three-canvas')}) | |||
| // 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) | |||
| }) | |||
| </script> | |||
| </body> | |||
| @@ -0,0 +1,45 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>Image Load</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 id="example-style"> | |||
| html, body, #canvas-container, #mcanvas { | |||
| width: 100%; | |||
| height: 100%; | |||
| margin: 0; | |||
| overflow: hidden; | |||
| } | |||
| p{ | |||
| position: absolute; | |||
| top: 5%; | |||
| left: 50%; | |||
| transform: translate(-50%, -50%); | |||
| font-size: 1.25em; | |||
| color: #8cd55b; | |||
| font-family: sans-serif; | |||
| pointer-events: none; | |||
| } | |||
| </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"> | |||
| <p>Drop .png, .jpeg, .jpg, .svg, .ico, .avif, .webp files here</p> | |||
| <canvas id="mcanvas"></canvas> | |||
| </div> | |||
| </body> | |||
| @@ -0,0 +1,62 @@ | |||
| import {_testFinish, ITexture, Mesh, PlaneGeometry, SRGBColorSpace, ThreeViewer, UnlitMaterial} from 'threepipe' | |||
| async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| dropzone: { | |||
| allowedExtensions: ['png', 'jpg', 'jpeg', 'svg', 'webp', 'avif', 'ico'], | |||
| addOptions: { | |||
| disposeSceneObjects: false, | |||
| autoSetEnvironment: false, // when hdr is dropped | |||
| autoSetBackground: false, | |||
| }, | |||
| }, | |||
| }) | |||
| viewer.scene.setBackgroundColor('#555555') | |||
| const urls = [ | |||
| 'https://threejs.org/examples/textures/sprite0.png', | |||
| 'https://threejs.org/examples/textures/uv_grid_opengl.jpg', | |||
| 'https://threejs.org/examples/models/svg/style-css-inside-defs.svg', | |||
| 'https://threejs.org/examples/textures/tiltbrush/Light.webp', | |||
| // todo: avif | |||
| 'https://threejs.org/favicon.ico', | |||
| ] | |||
| const geometry = new PlaneGeometry(1, 1) | |||
| let i = 0 | |||
| for (const url of urls) { | |||
| // Load the url as a Texture | |||
| const texture = await viewer.load<ITexture>(url) | |||
| if (!texture) continue | |||
| texture.colorSpace = SRGBColorSpace | |||
| const material = new UnlitMaterial({ | |||
| map: texture, | |||
| transparent: true, | |||
| }) | |||
| const plane = new Mesh(geometry, material) | |||
| plane.position.set(i % 3 - 1, -Math.floor(i / 3) + 1, 0) | |||
| viewer.scene.addObject(plane) | |||
| i++ | |||
| } | |||
| // Listen to when a file is dropped | |||
| viewer.assetManager.addEventListener('loadAsset', (e)=>{ | |||
| if (!e.data.isTexture) return | |||
| const texture = e.data as ITexture | |||
| texture.colorSpace = SRGBColorSpace | |||
| const material = new UnlitMaterial({ | |||
| map: texture, | |||
| transparent: true, | |||
| }) | |||
| const plane = new Mesh(geometry, material) | |||
| plane.position.set(i % 3 - 1, -Math.floor(i / 3) + 1, 0) | |||
| viewer.scene.addObject(plane) | |||
| i++ | |||
| }) | |||
| } | |||
| init().then(_testFinish) | |||
| @@ -241,6 +241,11 @@ | |||
| <li><a href="./drc-load/">DRACO(DRC) Load </a></li> | |||
| <li><a href="./hdr-load/">HDR Load </a></li> | |||
| <li><a href="./exr-load/">EXR Load </a></li> | |||
| <li><a href="./image-load/">Image(png, jpeg, svg, ico, webp, avif) Load </a></li> | |||
| <li><a href="./ply-load/">PLY Load </a></li> | |||
| <li><a href="./stl-load/">STL Load </a></li> | |||
| <li><a href="./ktx2-load/">KTX2 Load </a></li> | |||
| <li><a href="./ktx-load/">KTX Load </a></li> | |||
| </ul> | |||
| <h2 class="category">Export</h2> | |||
| <ul> | |||
| @@ -265,6 +270,10 @@ | |||
| <li><a href="./3dm-to-glb/">Convert 3DM to GLB </a></li> | |||
| <li><a href="./hdr-to-exr/">Convert HDR to EXR </a></li> | |||
| </ul> | |||
| <h2 class="category">Samples</h2> | |||
| <ul> | |||
| <li><a href="./html-sample/">HTML/JS Sample </a></li> | |||
| </ul> | |||
| <h2 class="category">Tests</h2> | |||
| <ul> | |||
| <li><a href="./gltf-transmission-test/">GLTF Transmission Test </a></li> | |||
| @@ -0,0 +1,45 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>KTX Texture Load</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 id="example-style"> | |||
| html, body, #canvas-container, #mcanvas { | |||
| width: 100%; | |||
| height: 100%; | |||
| margin: 0; | |||
| overflow: hidden; | |||
| } | |||
| p{ | |||
| position: absolute; | |||
| top: 5%; | |||
| left: 50%; | |||
| transform: translate(-50%, -50%); | |||
| font-size: 1.25em; | |||
| color: #8cd55b; | |||
| font-family: sans-serif; | |||
| pointer-events: none; | |||
| } | |||
| </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"> | |||
| <p>Drop .ktx files here</p> | |||
| <canvas id="mcanvas"></canvas> | |||
| </div> | |||
| </body> | |||
| @@ -0,0 +1,85 @@ | |||
| import { | |||
| _testFinish, | |||
| ITexture, | |||
| KTXLoadPlugin, | |||
| Mesh, | |||
| PlaneGeometry, | |||
| SRGBColorSpace, | |||
| ThreeViewer, | |||
| UnlitMaterial, | |||
| } from 'threepipe' | |||
| async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| dropzone: { | |||
| allowedExtensions: ['ktx'], | |||
| }, | |||
| }) | |||
| viewer.addPluginSync(KTXLoadPlugin) | |||
| viewer.scene.setBackgroundColor('#555555') | |||
| const urls = [] | |||
| // Checking which ktx formats are supported by the browser | |||
| const formats = { | |||
| astc: viewer.renderManager.renderer.extensions.has('WEBGL_compressed_texture_astc'), | |||
| etc1: viewer.renderManager.renderer.extensions.has('WEBGL_compressed_texture_etc1'), | |||
| s3tc: viewer.renderManager.renderer.extensions.has('WEBGL_compressed_texture_s3tc'), | |||
| pvrtc: viewer.renderManager.renderer.extensions.has('WEBGL_compressed_texture_pvrtc'), | |||
| } | |||
| if (formats.pvrtc) urls.push( | |||
| 'https://threejs.org/examples/textures/compressed/disturb_PVR2bpp.ktx', | |||
| 'https://threejs.org/examples/textures/compressed/lensflare_PVR4bpp.ktx' | |||
| ) | |||
| if (formats.s3tc) urls.push( | |||
| 'https://threejs.org/examples/textures/compressed/disturb_BC1.ktx', | |||
| 'https://threejs.org/examples/textures/compressed/lensflare_BC3.ktx' | |||
| ) | |||
| if (formats.etc1) urls.push( | |||
| 'https://threejs.org/examples/textures/compressed/disturb_ETC1.ktx' | |||
| ) | |||
| if (formats.astc) urls.push( | |||
| 'https://threejs.org/examples/textures/compressed/disturb_ASTC4x4.ktx', | |||
| 'https://threejs.org/examples/textures/compressed/lensflare_ASTC8x8.ktx' | |||
| ) | |||
| const geometry = new PlaneGeometry(1, 1) | |||
| let i = 0 | |||
| for (const url of urls) { | |||
| // Load the url as a Texture | |||
| const texture = await viewer.load<ITexture>(url) | |||
| if (!texture) continue | |||
| texture.colorSpace = SRGBColorSpace | |||
| const material = new UnlitMaterial({ | |||
| map: texture, | |||
| transparent: true, | |||
| }) | |||
| const plane = new Mesh(geometry, material) | |||
| plane.position.set(i % 3 - 1, -Math.floor(i / 3) + 1, 0) | |||
| viewer.scene.addObject(plane) | |||
| i++ | |||
| } | |||
| // Listen to when a file is dropped | |||
| viewer.assetManager.addEventListener('loadAsset', (e)=>{ | |||
| if (!e.data.isTexture) return | |||
| const texture = e.data as ITexture | |||
| texture.colorSpace = SRGBColorSpace | |||
| const material = new UnlitMaterial({ | |||
| map: texture, | |||
| transparent: true, | |||
| }) | |||
| const plane = new Mesh(geometry, material) | |||
| plane.position.set(i % 3 - 1, -Math.floor(i / 3) + 1, 0) | |||
| viewer.scene.addObject(plane) | |||
| i++ | |||
| }) | |||
| } | |||
| init().then(_testFinish) | |||
| @@ -0,0 +1,45 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>KTX2 Texture Load</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 id="example-style"> | |||
| html, body, #canvas-container, #mcanvas { | |||
| width: 100%; | |||
| height: 100%; | |||
| margin: 0; | |||
| overflow: hidden; | |||
| } | |||
| p{ | |||
| position: absolute; | |||
| top: 5%; | |||
| left: 50%; | |||
| transform: translate(-50%, -50%); | |||
| font-size: 1.25em; | |||
| color: #8cd55b; | |||
| font-family: sans-serif; | |||
| pointer-events: none; | |||
| } | |||
| </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"> | |||
| <p>Drop .ktx2 files here</p> | |||
| <canvas id="mcanvas"></canvas> | |||
| </div> | |||
| </body> | |||
| @@ -0,0 +1,77 @@ | |||
| import { | |||
| _testFinish, | |||
| ITexture, | |||
| KTX2LoadPlugin, | |||
| Mesh, | |||
| PlaneGeometry, | |||
| SRGBColorSpace, | |||
| ThreeViewer, | |||
| UnlitMaterial, | |||
| } from 'threepipe' | |||
| import {BufferGeometry} from 'three' | |||
| async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| dropzone: { | |||
| allowedExtensions: ['ktx2'], | |||
| }, | |||
| }) | |||
| viewer.addPluginSync(KTX2LoadPlugin) | |||
| viewer.scene.setBackgroundColor('#555555') | |||
| const urls = [ | |||
| 'https://threejs.org/examples/textures/compressed/sample_etc1s.ktx2', | |||
| 'https://threejs.org/examples/textures/compressed/sample_uastc.ktx2', | |||
| 'https://threejs.org/examples/textures/compressed/sample_uastc_zstd.ktx2', | |||
| ] | |||
| // PlaneGeometry UVs assume flipY=true, which compressed textures don't support. | |||
| const geometry = flipY(new PlaneGeometry(1, 1)) | |||
| let i = 0 | |||
| for (const url of urls) { | |||
| // Load the url as a Texture | |||
| const texture = await viewer.load<ITexture>(url) | |||
| if (!texture) continue | |||
| texture.colorSpace = SRGBColorSpace | |||
| const material = new UnlitMaterial({ | |||
| map: texture, | |||
| transparent: true, | |||
| }) | |||
| const plane = new Mesh(geometry, material) | |||
| plane.position.set(i % 3 - 1, -Math.floor(i / 3) + 1, 0) | |||
| viewer.scene.addObject(plane) | |||
| i++ | |||
| } | |||
| // Listen to when a file is dropped | |||
| viewer.assetManager.addEventListener('loadAsset', (e)=>{ | |||
| if (!e.data.isTexture) return | |||
| const texture = e.data as ITexture | |||
| texture.colorSpace = SRGBColorSpace | |||
| const material = new UnlitMaterial({ | |||
| map: texture, | |||
| transparent: true, | |||
| }) | |||
| const plane = new Mesh(geometry, material) | |||
| plane.position.set(i % 3 - 1, -Math.floor(i / 3) + 1, 0) | |||
| viewer.scene.addObject(plane) | |||
| i++ | |||
| }) | |||
| } | |||
| init().then(_testFinish) | |||
| /** Correct UVs to be compatible with `flipY=false` textures. */ | |||
| function flipY(geometry: BufferGeometry) { | |||
| const uv = geometry.attributes.uv | |||
| for (let i = 0; i < uv.count; i++) { | |||
| uv.setY(i, 1 - uv.getY(i)) | |||
| } | |||
| return geometry | |||
| } | |||
| @@ -0,0 +1,34 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>PLY Load</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 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> | |||
| @@ -0,0 +1,32 @@ | |||
| import {_testFinish, IObject3D, PLYLoadPlugin, ThreeViewer} from 'threepipe' | |||
| async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| dropzone: { | |||
| allowedExtensions: ['ply', 'hdr'], | |||
| addOptions: { | |||
| disposeSceneObjects: true, | |||
| autoSetEnvironment: true, // when hdr is dropped | |||
| autoSetBackground: true, | |||
| }, | |||
| }, | |||
| }) | |||
| viewer.addPluginSync(PLYLoadPlugin) | |||
| const options = { | |||
| autoCenter: true, | |||
| autoScale: true, | |||
| } | |||
| await Promise.all([ | |||
| viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr'), | |||
| viewer.load<IObject3D>('https://threejs.org/examples/models/ply/ascii/dolphins_colored.ply', options), | |||
| viewer.load<IObject3D>('https://threejs.org/examples/models/ply/binary/Lucy100k.ply', options), | |||
| ]) | |||
| } | |||
| init().then(_testFinish) | |||
| @@ -0,0 +1,34 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>STL Load</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 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> | |||
| @@ -0,0 +1,33 @@ | |||
| import {_testFinish, IObject3D, STLLoadPlugin, ThreeViewer} from 'threepipe' | |||
| async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| dropzone: { | |||
| allowedExtensions: ['stl', 'hdr'], | |||
| addOptions: { | |||
| disposeSceneObjects: true, | |||
| autoSetEnvironment: true, // when hdr is dropped | |||
| autoSetBackground: true, | |||
| }, | |||
| }, | |||
| }) | |||
| viewer.addPluginSync(STLLoadPlugin) | |||
| const options = { | |||
| autoCenter: true, | |||
| autoScale: true, | |||
| } | |||
| const res = await Promise.all([ | |||
| viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr'), | |||
| viewer.load<IObject3D>('https://threejs.org/examples/models/stl/ascii/slotted_disk.stl', options), | |||
| viewer.load<IObject3D>('https://threejs.org/examples/models/stl/binary/pr2_head_pan.stl', options), | |||
| ]) | |||
| console.log(res) | |||
| } | |||
| init().then(_testFinish) | |||
| @@ -4,10 +4,14 @@ import { | |||
| DropzonePlugin, | |||
| FullScreenPlugin, | |||
| HalfFloatType, | |||
| IObject3D, | |||
| KTX2LoadPlugin, | |||
| KTXLoadPlugin, | |||
| NormalBufferPlugin, | |||
| PLYLoadPlugin, | |||
| RenderTargetPreviewPlugin, | |||
| Rhino3dmLoadPlugin, | |||
| SceneUiConfigPlugin, | |||
| STLLoadPlugin, | |||
| ThreeViewer, | |||
| TonemapPlugin, | |||
| ViewerUiConfigPlugin, | |||
| @@ -21,6 +25,7 @@ async function init() { | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| rgbm: true, | |||
| zPrepass: false, // set it to true if you only have opaque objects in the scene to get better performance. | |||
| dropzone: { | |||
| addOptions: { | |||
| clearSceneObjects: false, // clear the scene before adding new objects on drop. | |||
| @@ -37,6 +42,11 @@ async function init() { | |||
| new DepthBufferPlugin(HalfFloatType, true, true), | |||
| new NormalBufferPlugin(HalfFloatType, false), | |||
| new RenderTargetPreviewPlugin(false), | |||
| new KTX2LoadPlugin(), | |||
| new KTXLoadPlugin(), | |||
| new PLYLoadPlugin(), | |||
| new Rhino3dmLoadPlugin(), | |||
| new STLLoadPlugin(), | |||
| ]) | |||
| const rt = viewer.getOrAddPluginSync(RenderTargetPreviewPlugin) | |||
| @@ -50,13 +60,12 @@ async function init() { | |||
| ['Debug']: [RenderTargetPreviewPlugin], | |||
| }) | |||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr', { | |||
| setBackground: true, | |||
| }) | |||
| await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | |||
| autoCenter: true, | |||
| autoScale: true, | |||
| }) | |||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||
| // await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | |||
| // autoCenter: true, | |||
| // autoScale: true, | |||
| // }) | |||
| // const model = result?.getObjectByName('node_damagedHelmet_-6514') | |||
| // const config = model?.uiConfig | |||
| @@ -5,7 +5,7 @@ import {AnyOptions, IDisposable} from 'ts-browser-helpers' | |||
| export interface ILoader<T = any, T2 = any> extends Loader, Partial<IDisposable> { | |||
| loadAsync(url: string, onProgress?: (event: ProgressEvent) => void): Promise<any>; | |||
| /** | |||
| * Transform after load, like convert geometry to mesh, etc. for reference see {@link DRACOLoader2} | |||
| * Transform after load, like convert geometry to mesh, etc. for reference see {@link DRACOLoader2} or {@link PLYLoadPlugin} | |||
| * @param res - result of load | |||
| * @param options | |||
| */ | |||
| @@ -0,0 +1,85 @@ | |||
| import {IViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {GLTFWriter2, ILoader, Importer, ImportResultExtras} from '../../assetmanager' | |||
| import {KTX2Loader} from 'three/examples/jsm/loaders/KTX2Loader.js' | |||
| import {CompressedTexture} from 'three' | |||
| import {serializeTextureInExtras} from '../../utils' | |||
| import {ITexture} from '../../core' | |||
| /** | |||
| * Adds support for loading Compressed Textures of format `.ktx2`, `image/ktx2` files and data uris. | |||
| * @category Plugins | |||
| */ | |||
| export class KTX2LoadPlugin implements IViewerPluginSync { | |||
| declare ['constructor']: typeof KTX2LoadPlugin | |||
| public static readonly PluginType = 'KTX2LoadPlugin' | |||
| private _importer = new Importer(KTX2Loader2, ['ktx2'], ['image/ktx2'], false) | |||
| public static TRANSCODER_LIBRARY_PATH = 'https://cdn.jsdelivr.net/gh/BinomialLLC/basis_universal@1.16.4/webgl/transcoder/build/' | |||
| onAdded(viewer: ThreeViewer) { | |||
| this._importer.onCtor = (l: KTX2Loader2) => l | |||
| .setTranscoderPath(KTX2LoadPlugin.TRANSCODER_LIBRARY_PATH) | |||
| .detectSupport(viewer.renderManager.renderer) | |||
| viewer.assetManager.importer.addImporter(this._importer) | |||
| viewer.assetManager.exporter.getExporter('gltf', 'glb')?.extensions?.push(glTFTextureBasisUExtensionExport) | |||
| } | |||
| onRemove(viewer: ThreeViewer) { | |||
| viewer.assetManager.importer.removeImporter(this._importer) | |||
| const exporter = viewer.assetManager.exporter.getExporter('gltf', 'glb') | |||
| const index = exporter?.extensions?.indexOf(glTFTextureBasisUExtensionExport) | |||
| if (index !== undefined && index !== -1) exporter?.extensions?.splice(index, 1) | |||
| } | |||
| dispose() { | |||
| return | |||
| } | |||
| } | |||
| export class KTX2Loader2 extends KTX2Loader implements ILoader { | |||
| async createTexture(buffer: ArrayBuffer, config: any): Promise<CompressedTexture> { | |||
| const buffer2 = new Uint8Array(buffer.slice(0)) // clones the buffer | |||
| const texture = (await super.createTexture(buffer, config)) as CompressedTexture & ITexture | |||
| texture.source._sourceImgBuffer = buffer2 // keep the same buffer when cloned and all, used in serializeTextureInExtras | |||
| texture.userData.mimeType = 'image/ktx2' | |||
| texture.toJSON = (meta?: any)=>{ | |||
| return serializeTextureInExtras(texture, meta, texture.name, 'image/ktx2') | |||
| } | |||
| texture.clone = ()=>{ | |||
| throw new Error('ktx2 texture cloning not supported') | |||
| } | |||
| return texture | |||
| } | |||
| } | |||
| export const KHR_TEXTURE_BASISU = 'KHR_texture_basisu' | |||
| const glTFTextureBasisUExtensionExport = (w: GLTFWriter2)=> ({ | |||
| writeTexture: (texture: ITexture&ImportResultExtras, textureDef: any) => { | |||
| // if (!w.options.embedImages) return // option is removed. | |||
| if (texture.userData.mimeType !== 'image/ktx2') return | |||
| if (textureDef.source !== undefined && textureDef.source !== null) { | |||
| console.warn('ktx2 export: source already set') | |||
| return | |||
| } | |||
| const sourceBuffer = texture.source._sourceImgBuffer || texture.__sourceBuffer // todo do this for all images that have a __sourceBuffer (in GLTFExporter.processImage or GLTFWriter2.processTexture) | |||
| if (!sourceBuffer) { | |||
| console.warn('ktx2 export: no source buffer for ktx2') | |||
| return | |||
| } | |||
| textureDef.extensions = textureDef.extensions || {} | |||
| const extensionDef: any = {} | |||
| const blob = new Blob([sourceBuffer], {type: 'image/ktx2'}) | |||
| extensionDef.source = w.processImageBlob(blob, texture) | |||
| textureDef.extensions[ KHR_TEXTURE_BASISU ] = extensionDef | |||
| w.extensionsUsed[ KHR_TEXTURE_BASISU ] = true | |||
| }, | |||
| }) | |||
| @@ -0,0 +1,27 @@ | |||
| import {IViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {Importer} from '../../assetmanager' | |||
| import {KTXLoader} from 'three/examples/jsm/loaders/KTXLoader.js' | |||
| /** | |||
| * Adds support for loading `.ktx`, `image/ktx` files and data uris. | |||
| * @category Plugins | |||
| */ | |||
| export class KTXLoadPlugin implements IViewerPluginSync { | |||
| declare ['constructor']: typeof KTXLoadPlugin | |||
| public static readonly PluginType = 'KTXLoadPlugin' | |||
| private _importer = new Importer(KTXLoader, ['ktx'], ['image/ktx'], false) | |||
| onAdded(viewer: ThreeViewer) { | |||
| viewer.assetManager.importer.addImporter(this._importer) | |||
| } | |||
| onRemove(viewer: ThreeViewer) { | |||
| viewer.assetManager.importer.removeImporter(this._importer) | |||
| } | |||
| dispose() { | |||
| return | |||
| } | |||
| } | |||
| @@ -0,0 +1,36 @@ | |||
| import {IViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {ILoader, Importer} from '../../assetmanager' | |||
| import {PLYLoader} from 'three/examples/jsm/loaders/PLYLoader.js' | |||
| import {AnyOptions} from 'ts-browser-helpers' | |||
| import {BufferGeometry, Color, Mesh} from 'three' | |||
| import {PhysicalMaterial} from '../../core' | |||
| /** | |||
| * Adds support for loading `.ply`, `text/plain+ply` files and data uris | |||
| * @category Plugins | |||
| */ | |||
| export class PLYLoadPlugin implements IViewerPluginSync { | |||
| declare ['constructor']: typeof PLYLoadPlugin | |||
| public static readonly PluginType = 'PLYLoadPlugin' | |||
| private _importer = new Importer(class extends PLYLoader implements ILoader { | |||
| transform(res: BufferGeometry, _: AnyOptions): Mesh|undefined { | |||
| if (!res.attributes?.normal) res.computeVertexNormals() | |||
| // todo set mesh name from options/path | |||
| return res ? new Mesh(res, new PhysicalMaterial({color: new Color(1, 1, 1)})) : undefined | |||
| } | |||
| }, ['ply'], ['text/plain+ply'], false) | |||
| onAdded(viewer: ThreeViewer) { | |||
| viewer.assetManager.importer.addImporter(this._importer) | |||
| } | |||
| onRemove(viewer: ThreeViewer) { | |||
| viewer.assetManager.importer.removeImporter(this._importer) | |||
| } | |||
| dispose() { | |||
| return | |||
| } | |||
| } | |||
| @@ -0,0 +1,36 @@ | |||
| import {IViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {ILoader, Importer} from '../../assetmanager' | |||
| import {STLLoader} from 'three/examples/jsm/loaders/STLLoader.js' | |||
| import {BufferGeometry, Color, Mesh} from 'three' | |||
| import {AnyOptions} from 'ts-browser-helpers' | |||
| import {PhysicalMaterial} from '../../core' | |||
| /** | |||
| * Adds support for loading `.stl`, `model/stl` files and data uris. | |||
| * @category Plugins | |||
| */ | |||
| export class STLLoadPlugin implements IViewerPluginSync { | |||
| declare ['constructor']: typeof STLLoadPlugin | |||
| public static readonly PluginType = 'STLLoadPlugin' | |||
| private _importer = new Importer(class extends STLLoader implements ILoader { | |||
| transform(res: BufferGeometry, _: AnyOptions): Mesh|undefined { | |||
| if (!res.attributes?.normal) res.computeVertexNormals() | |||
| // todo set mesh name from options/path | |||
| return res ? new Mesh(res, new PhysicalMaterial({color: new Color(1, 1, 1)})) : undefined | |||
| } | |||
| }, ['stl'], ['model/stl', 'model/x.stl-binary', 'model/x.stl-ascii'], false) | |||
| onAdded(viewer: ThreeViewer) { | |||
| viewer.assetManager.importer.addImporter(this._importer) | |||
| } | |||
| onRemove(viewer: ThreeViewer) { | |||
| viewer.assetManager.importer.removeImporter(this._importer) | |||
| } | |||
| dispose() { | |||
| return | |||
| } | |||
| } | |||
| @@ -18,6 +18,10 @@ export {FullScreenPlugin} from './interaction/FullScreenPlugin' | |||
| // import | |||
| export {Rhino3dmLoadPlugin} from './import/Rhino3dmLoadPlugin' | |||
| export {PLYLoadPlugin} from './import/PLYLoadPlugin' | |||
| export {STLLoadPlugin} from './import/STLLoadPlugin' | |||
| export {KTXLoadPlugin} from './import/KTXLoadPlugin' | |||
| export {KTX2LoadPlugin} from './import/KTX2LoadPlugin' | |||
| // postprocessing | |||
| export {TonemapPlugin} from './postprocessing/TonemapPlugin' | |||
| @@ -3,7 +3,7 @@ export {windowDialogWrapper, type IDialogWrapper} from './DialogWrapper' | |||
| export {GLStatsJS} from './GLStatsJS' | |||
| export {CustomContextMenu} from './CustomContextMenu' | |||
| export {Dropzone, type DropFile, type ListenerCallback, type DropEventType} from './Dropzone' | |||
| export {ThreeSerialization, type SerializationMetaType, type SerializationResourcesType, MetaImporter, metaToResources, getEmptyMeta, metaFromResources, convertArrayBufferToStringsInMeta, convertStringsToArrayBuffersInMeta, copyMaterialUserData, copyObject3DUserData, copyUserData, copyTextureUserData, jsonToBlob} from './serialization' | |||
| export {ThreeSerialization, type SerializationMetaType, type SerializationResourcesType, MetaImporter, metaToResources, getEmptyMeta, metaFromResources, convertArrayBufferToStringsInMeta, convertStringsToArrayBuffersInMeta, copyMaterialUserData, copyObject3DUserData, copyUserData, copyTextureUserData, jsonToBlob, serializeTextureInExtras} from './serialization' | |||
| export {shaderReplaceString} from './shader-helpers' | |||
| export {makeGLBFile} from './gltf' | |||
| @@ -14,7 +14,7 @@ import { | |||
| Vector4, | |||
| } from 'three' | |||
| import type {AssetImporter, AssetManager, MaterialManager} from '../assetmanager' | |||
| import {BlobExt, IAssetImporter} from '../assetmanager' | |||
| import {BlobExt, IAssetImporter, ImportResultExtras} from '../assetmanager' | |||
| import {ThreeViewer} from '../viewer' | |||
| import {ITexture} from '../core' | |||
| import {IRenderTarget, RenderManager} from '../rendering' | |||
| @@ -777,3 +777,47 @@ export function jsonToBlob(json: any): BlobExt { | |||
| b.ext = 'json' | |||
| return b | |||
| } | |||
| /** | |||
| * Used in {@link LUTCubeTextureWrapper} and {@link KTX2LoadPlugin} and imported in {@link loadConfigResources} | |||
| * @param texture | |||
| * @param meta | |||
| * @param name | |||
| * @param mime | |||
| */ | |||
| export function serializeTextureInExtras(texture: ITexture & ImportResultExtras, meta: any, name?: string, mime?: string) { | |||
| if (meta?.extras[texture.uuid]) return {uuid: texture.uuid, resource: 'extras'} | |||
| let url: any = '' | |||
| if (texture.source?._sourceImgBuffer || texture.__sourceBuffer) { | |||
| // serialize blob to data in image. | |||
| // Note: do not change to Uint16Array because it's encoded to rgbe in `processViewer` | |||
| const data = new Uint8Array(texture.source?._sourceImgBuffer || texture.__sourceBuffer as ArrayBuffer) | |||
| const mimeType = mime || texture.userData.mimeType || '' | |||
| url = { | |||
| data: Array.from(data), // texture need to be a normal array, not a typed array. | |||
| type: data.constructor.name, | |||
| path: texture.userData.__sourceBlob?.name || texture.userData.rootPath || 'file.' + mimeType.split('/')[1], | |||
| } | |||
| if (mimeType) url.mimeType = mimeType | |||
| } else if (texture.userData.rootPath) { | |||
| url = texture.userData.rootPath | |||
| } else { | |||
| console.error('Unable to serialize LUT texture, not loaded through asset manager.') | |||
| } | |||
| const tex = { | |||
| uuid: texture.uuid, | |||
| url, | |||
| userData: copyTextureUserData({}, texture.userData), | |||
| type: texture.type, | |||
| name: name || texture.name, | |||
| } | |||
| if (meta?.extras) { | |||
| meta.extras[texture.uuid] = tex | |||
| return {uuid: texture.uuid, resource: 'extras'} | |||
| } | |||
| return tex | |||
| } | |||