| @@ -126,6 +126,8 @@ To make changes and run the example, click on the CodePen button on the top righ | |||
| - [STLLoadPlugin](#stlloadplugin) - Add support for loading .stl files | |||
| - [KTX2LoadPlugin](#ktx2loadplugin) - Add support for loading .ktx2 files | |||
| - [KTXLoadPlugin](#ktxloadplugin) - Add support for loading .ktx files | |||
| - [SimplifyModifierPlugin](#simplifymodifierplugin) - Boilerplate for plugin to simplify geometries | |||
| - [MeshOptSimplifyModifierPlugin](#meshoptsimplifymodifierplugin) - Simplify geometries using meshoptimizer library | |||
| - [Packages](#threepipe-packages) | |||
| - [@threepipe/plugin-tweakpane](#threepipeplugin-tweakpane) Tweakpane UI Plugin | |||
| - [@threepipe/plugin-blueprintjs](#threepipeplugin-blueprintjs) BlueprintJs UI Plugin | |||
| @@ -3277,6 +3279,47 @@ viewer.addPluginSync(new KTXLoadPlugin()) | |||
| const texture = await viewer.load('file.ktx') | |||
| ``` | |||
| ## SimplifyModifierPlugin | |||
| [Example](https://threepipe.org/examples/#simplify-modifier-plugin/) — | |||
| [Source Code](./src/plugins/extras/SimplifyModifierPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/SimplifyModifierPlugin.html) | |||
| Boilerplate for implementing a plugin for simplifying geometries. | |||
| This is a base class and cannot be used directly. | |||
| A sample to use it: | |||
| ```typescript | |||
| class SimplifyModifierPluginImpl extends SimplifyModifierPlugin { | |||
| protected _simplify(geometry: IGeometry, count: number) { | |||
| return new SimplifyModifier().modify(geometry, count) as IGeometry | |||
| } | |||
| } | |||
| const plugin = viewer.addPluginSync(new SimplifyModifierPluginImpl()) | |||
| const root = await viewer.load('file.glb') | |||
| plugin.simplifyAll(root, {factor: 0.75}) | |||
| ``` | |||
| Check the [example](https://threepipe.org/examples/#simplify-modifier-plugin/) for full implementation. | |||
| ## MeshOptSimplifyModifierPlugin | |||
| [Example](https://threepipe.org/examples/#meshopt-simplify-modifier-plugin/) — | |||
| [Source Code](./src/plugins/extras/MeshOptSimplifyModifierPlugin.ts) — | |||
| [API Reference](https://threepipe.org/docs/classes/MeshOptSimplifyModifierPlugin.html) | |||
| Simplify modifier using [meshoptimizer](https://github.com/zeux/meshoptimizer) library. It Loads the library at runtime from a customisable CDN URL. | |||
| Note: It does not guarantee that the geometry will be simplified to the exact target count. | |||
| ```typescript | |||
| const simplifyModifier = viewer.addPluginSync(new MeshOptSimplifyModifierPlugin()) | |||
| const root = await viewer.load('file.glb') | |||
| simplifyModifier.simplifyAll(root, {factor: 0.75}) | |||
| ``` | |||
| # @threepipe Packages | |||
| Additional plugins can be found in the [plugins](plugins/) directory. | |||
| @@ -343,6 +343,8 @@ | |||
| <li><a href="./device-orientation-controls-plugin/">Device Orientation Controls Plugin (Gyroscope) </a></li> | |||
| <li><a href="./pointer-lock-controls-plugin/">Pointer Lock(FPS) Controls Plugin </a></li> | |||
| <li><a href="./three-first-person-controls-plugin/">Three First Person(look around) Controls Plugin </a></li> | |||
| <li><a href="./simplify-modifier-plugin/">Simplify Modifier Plugin </a></li> | |||
| <li><a href="./meshopt-simplify-modifier-plugin/">MeshOpt Simplify Modifier Plugin </a></li> | |||
| </ul> | |||
| <h2 class="category">Import</h2> | |||
| <ul> | |||
| @@ -369,7 +371,7 @@ | |||
| <li><a href="./image-snapshot-export/">PNG, JPEG, WEBP Export<br/>(Image Snapshot) </a></li> | |||
| <li><a href="./render-target-export/">EXR, PNG, JPEG, WEBP Export<br/>(Render Target Export) </a></li> | |||
| <li><a href="./glb-export/">GLB Export </a></li> | |||
| <li><a href="./pmat-material-export/">PMAT Material export </a></li> | |||
| <li><a href="./pmat-material-export/">PMAT Material Export </a></li> | |||
| <li><a href="./svg-geometry-playground/">SVG Geometry Playground </a></li> | |||
| </ul> | |||
| <h2 class="category">UI Config</h2> | |||
| @@ -0,0 +1,36 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>MeshOpt Simplify Modifier Plugin</title> | |||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||
| <!-- Import maps polyfill --> | |||
| <!-- Remove this when import maps will be widely supported --> | |||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||
| <script type="importmap"> | |||
| { | |||
| "imports": { | |||
| "threepipe": "./../../dist/index.mjs", | |||
| "@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/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,40 @@ | |||
| import {_testFinish, IObject3D, MeshOptSimplifyModifierPlugin, PickingPlugin, ThreeViewer} from 'threepipe' | |||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | |||
| import {createSimpleButtons} from '../examples-utils/simple-bottom-buttons.js' | |||
| async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| plugins: [PickingPlugin], | |||
| }) | |||
| const simplify = viewer.addPluginSync(MeshOptSimplifyModifierPlugin) | |||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | |||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr', { | |||
| setBackground: true, | |||
| }) | |||
| const result = await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | |||
| autoCenter: true, | |||
| autoScale: true, | |||
| }) | |||
| result?.traverse((obj) => { | |||
| obj.materials?.map(m=>m.wireframe = true) | |||
| }) | |||
| createSimpleButtons({ | |||
| ['Simplify']: async(_: HTMLButtonElement) => { | |||
| await simplify.simplifyAll(result, {factor: 0.5}) | |||
| }, | |||
| }) | |||
| ui.setupPluginUi(MeshOptSimplifyModifierPlugin) | |||
| ui.setupPluginUi(PickingPlugin) | |||
| } | |||
| init().finally(_testFinish) | |||
| @@ -0,0 +1,38 @@ | |||
| <!DOCTYPE html> | |||
| <html lang="en"> | |||
| <head> | |||
| <meta charset="UTF-8"> | |||
| <title>Simplify Modifier Plugin</title> | |||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||
| <!-- Import maps polyfill --> | |||
| <!-- Remove this when import maps will be widely supported --> | |||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||
| <script type="importmap"> | |||
| { | |||
| "imports": { | |||
| "three": "./../../dist/index.mjs", | |||
| "three/examples/jsm/modifiers/SimplifyModifier.js": "https://cdn.jsdelivr.net/gh/repalash/three.js-modded/examples/jsm/modifiers/SimplifyModifier.js", | |||
| "threepipe": "./../../dist/index.mjs", | |||
| "@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/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,63 @@ | |||
| import { | |||
| _testFinish, | |||
| generateUiFolder, | |||
| IGeometry, | |||
| IMaterial, | |||
| IObject3D, | |||
| PickingPlugin, | |||
| SimplifyModifierPlugin, | |||
| ThreeViewer, | |||
| } from 'threepipe' | |||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | |||
| import {SimplifyModifier} from 'three/examples/jsm/modifiers/SimplifyModifier.js' | |||
| import {createSimpleButtons} from '../examples-utils/simple-bottom-buttons.js' | |||
| class SimplifyModifierPluginImpl extends SimplifyModifierPlugin { | |||
| protected _simplify(geometry: IGeometry, count: number) { | |||
| const res = new SimplifyModifier().modify(geometry, count) as IGeometry | |||
| res.computeVertexNormals() | |||
| return res | |||
| } | |||
| uiConfig = generateUiFolder('Simplify Modifier', this) | |||
| } | |||
| async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| plugins: [PickingPlugin], | |||
| }) | |||
| const simplify = viewer.addPluginSync(SimplifyModifierPluginImpl) | |||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | |||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr', { | |||
| setBackground: true, | |||
| }) | |||
| const result = await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | |||
| autoCenter: true, | |||
| autoScale: true, | |||
| }) | |||
| const mats: IMaterial[] = [] | |||
| result?.traverse((obj) => { | |||
| obj.materials?.map(m=>{ | |||
| if (!m) return | |||
| m.wireframe = true | |||
| mats.push(m) | |||
| }) | |||
| }) | |||
| createSimpleButtons({ | |||
| ['Simplify']: async(_: HTMLButtonElement) => { | |||
| await simplify.simplifyAll(result, {factor: 0.5}) | |||
| }, | |||
| }) | |||
| ui.setupPluginUi(SimplifyModifierPluginImpl) | |||
| mats.forEach(m=>ui.appendChild(m.uiConfig, {expanded: false})) | |||
| } | |||
| init().finally(_testFinish) | |||
| @@ -22,6 +22,7 @@ import { | |||
| HemisphereLight, | |||
| KTX2LoadPlugin, | |||
| KTXLoadPlugin, | |||
| MeshOptSimplifyModifierPlugin, | |||
| NoiseBumpMaterialPlugin, | |||
| NormalBufferPlugin, | |||
| Object3DGeneratorPlugin, | |||
| @@ -33,11 +34,13 @@ import { | |||
| RenderTargetPreviewPlugin, | |||
| Rhino3dmLoadPlugin, | |||
| SceneUiConfigPlugin, | |||
| SSAOPlugin, | |||
| STLLoadPlugin, | |||
| ThreeFirstPersonControlsPlugin, | |||
| ThreeViewer, | |||
| TonemapPlugin, | |||
| TransformControlsPlugin, | |||
| UnsignedByteType, | |||
| USDZLoadPlugin, | |||
| ViewerUiConfigPlugin, | |||
| VignettePlugin, | |||
| @@ -92,6 +95,7 @@ async function init() { | |||
| new VignettePlugin(false), | |||
| new ChromaticAberrationPlugin(false), | |||
| new FilmicGrainPlugin(false), | |||
| new SSAOPlugin(UnsignedByteType, 1), | |||
| KTX2LoadPlugin, | |||
| KTXLoadPlugin, | |||
| PLYLoadPlugin, | |||
| @@ -109,6 +113,8 @@ async function init() { | |||
| DeviceOrientationControlsPlugin, | |||
| PointerLockControlsPlugin, | |||
| ThreeFirstPersonControlsPlugin, | |||
| new MeshOptSimplifyModifierPlugin(false), // will auto-initialize on first use. | |||
| // new BasicSVGRendererPlugin(false, true), | |||
| ...extraImportPlugins, | |||
| ]) | |||
| @@ -121,9 +127,9 @@ async function init() { | |||
| editor.loadPlugins({ | |||
| ['Viewer']: [ViewerUiConfigPlugin, SceneUiConfigPlugin, DropzonePlugin, FullScreenPlugin, TweakpaneUiPlugin], | |||
| ['Scene']: [ContactShadowGroundPlugin], | |||
| ['Interaction']: [HierarchyUiPlugin, TransformControlsPlugin, PickingPlugin, Object3DGeneratorPlugin, GeometryGeneratorPlugin, EditorViewWidgetPlugin, Object3DWidgetsPlugin], | |||
| ['Interaction']: [HierarchyUiPlugin, TransformControlsPlugin, PickingPlugin, Object3DGeneratorPlugin, GeometryGeneratorPlugin, EditorViewWidgetPlugin, Object3DWidgetsPlugin, MeshOptSimplifyModifierPlugin], | |||
| ['GBuffer']: [GBufferPlugin, DepthBufferPlugin, NormalBufferPlugin], | |||
| ['Post-processing']: [TonemapPlugin, ProgressivePlugin, FrameFadePlugin, VignettePlugin, ChromaticAberrationPlugin, FilmicGrainPlugin], | |||
| ['Post-processing']: [TonemapPlugin, ProgressivePlugin, SSAOPlugin, FrameFadePlugin, VignettePlugin, ChromaticAberrationPlugin, FilmicGrainPlugin], | |||
| ['Export']: [CanvasSnapshotPlugin], | |||
| ['Animation']: [GLTFAnimationPlugin, CameraViewPlugin], | |||
| ['Extras']: [HDRiGroundPlugin, Rhino3dmLoadPlugin, ClearcoatTintPlugin, FragmentClippingExtensionPlugin, NoiseBumpMaterialPlugin, CustomBumpMapPlugin, VirtualCamerasPlugin], | |||
| @@ -0,0 +1,104 @@ | |||
| import {ThreeViewer} from '../../viewer' | |||
| import {BufferAttribute, BufferGeometry} from 'three' | |||
| import {IGeometry, iGeometryCommons} from '../../core' | |||
| import {toIndexedGeometry} from '../../three' | |||
| import {SimplifyModifierPlugin} from './SimplifyModifierPlugin' | |||
| import {uiFolderContainer, uiNumber, uiToggle} from 'uiconfig.js' | |||
| /** | |||
| * Simplify modifier using [meshoptimizer](https://github.com/zeux/meshoptimizer) library. | |||
| * Loads the library at runtime from a customisable cdn url. | |||
| */ | |||
| @uiFolderContainer('Simplify Modifier (meshopt)') | |||
| export class MeshOptSimplifyModifierPlugin extends SimplifyModifierPlugin { | |||
| public static readonly PluginType = 'MeshOptSimplifyModifierPlugin' | |||
| constructor(initialize = true) { | |||
| super() | |||
| // todo: check if compatible? | |||
| if (initialize) this.initialize() | |||
| } | |||
| get initialized() { | |||
| return !!window.MeshoptSimplifier | |||
| } | |||
| // static SIMPLIFIER_URL = 'https://cdn.jsdelivr.net/gh/zeux/meshoptimizer@master/js/meshopt_simplifier.module.js' | |||
| static SIMPLIFIER_URL = 'https://unpkg.com/meshoptimizer@0.20.0/meshopt_simplifier.module.js' | |||
| onAdded(viewer: ThreeViewer) { | |||
| super.onAdded(viewer) | |||
| } | |||
| protected _initializing?: Promise<void> = undefined | |||
| async initialize() { | |||
| if (this.initialized) return | |||
| if (this._initializing) return await this._initializing | |||
| const s = document.createElement('script') | |||
| s.type = 'module' | |||
| const ev = Math.random().toString(36).substring(7) | |||
| s.innerHTML = ` | |||
| import { MeshoptSimplifier } from '${MeshOptSimplifyModifierPlugin.SIMPLIFIER_URL}'; | |||
| MeshoptSimplifier.ready.then(() => { | |||
| window.MeshoptSimplifier = MeshoptSimplifier; | |||
| window.dispatchEvent(new CustomEvent('${ev}')) | |||
| }); | |||
| ` | |||
| this._initializing = new Promise<void>((res) => { | |||
| const l = () => { | |||
| window.removeEventListener(ev, l) | |||
| res() // todo timeout? | |||
| } | |||
| window.addEventListener(ev, l) | |||
| document.head.appendChild(s) // todo remove later? | |||
| // this._script = s | |||
| }) | |||
| } | |||
| @uiNumber() | |||
| errorThreshold = 0.5 | |||
| @uiToggle() | |||
| lockBorder = false | |||
| protected _simplify(geometry: BufferGeometry, count: number): IGeometry { | |||
| if (!this.initialized) throw new Error('MeshOptSimplifyModifierPlugin not initialized') | |||
| if (!geometry.index) { | |||
| geometry = toIndexedGeometry(geometry) | |||
| } else { | |||
| geometry = geometry.clone() | |||
| } | |||
| const srcIndexArray = geometry.index!.array | |||
| const srcPositionArray = geometry.attributes.position.array | |||
| const factor = count / geometry.attributes.position.count | |||
| // console.log(factor) | |||
| // const targetCount = count * 3 | |||
| const targetCount = 3 * Math.floor(factor * srcIndexArray.length / 3) | |||
| // console.log('srcCount', srcIndexArray.length / 3, 'targetCount', targetCount / 3) | |||
| // const errorThresh = 1e-2 | |||
| const [dstIndexArray, error] = window.MeshoptSimplifier.simplify( | |||
| srcIndexArray, | |||
| srcPositionArray, | |||
| 3, | |||
| targetCount, | |||
| this.errorThreshold, | |||
| this.lockBorder ? ['LockBorder'] : [], | |||
| ) | |||
| console.log('srcCount', srcIndexArray.length / 3, 'destCount', dstIndexArray.length / 3) | |||
| if (error) { | |||
| console.error('Simplify error', error) | |||
| // return geometry // todo | |||
| } | |||
| // (geometry.index!.array as Uint32Array).set(dstIndexArray) | |||
| geometry.setIndex(new BufferAttribute(new Uint32Array(dstIndexArray), 1)) | |||
| // geometry.index!.needsUpdate = true | |||
| // geometry.setDrawRange(0, dstIndexArray.length) | |||
| return iGeometryCommons.upgradeGeometry.call(geometry.toNonIndexed()) | |||
| } | |||
| } | |||
| declare global{ | |||
| interface Window{ | |||
| MeshoptSimplifier?: any | |||
| } | |||
| } | |||
| @@ -0,0 +1,206 @@ | |||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {PickingPlugin} from '../interaction/PickingPlugin' | |||
| import {uiButton, uiSlider} from 'uiconfig.js' | |||
| import {IGeometry, IObject3D} from '../../core' | |||
| import {ValOrArr} from 'ts-browser-helpers' | |||
| import {Vector3} from 'three' | |||
| export interface SimplifyOptions{ | |||
| /** | |||
| * Number of vertices to remove. | |||
| * Factor is not used when count is set. | |||
| */ | |||
| count?: number | |||
| /** | |||
| * Factor of vertices to remove. eg 0.5 will remove half of the vertices. | |||
| */ | |||
| factor?: number | |||
| /** | |||
| * Replace the geometry with the simplified version in all meshes that use it. | |||
| */ | |||
| replace?: boolean | |||
| /** | |||
| * Displace the simplified geometry in the scene. Only used when replace is true | |||
| * If set to true, the geometry will be disposed when replaced. | |||
| * Default is false. | |||
| * This will automatically be done when disposeOnIdle is not false in the geometry.userData. | |||
| */ | |||
| disposeOnReplace?: boolean | |||
| } | |||
| /** | |||
| * Boilerplate for implementing a plugin for simplifying geometries. | |||
| * This is a base class and cannot be used directly. | |||
| * See {@link MeshOptSimplifyModifierPlugin} the [simplify-modifier-plugin](https://threepipe.org/examples/#simplify-modifier-plugin) example for a sample implementation. | |||
| */ | |||
| export abstract class SimplifyModifierPlugin extends AViewerPluginSync<''> { | |||
| public static readonly PluginType: string = 'SimplifyModifierPlugin' | |||
| enabled = true | |||
| toJSON: any = undefined | |||
| constructor() { | |||
| super() | |||
| } | |||
| get initialized() { return true } | |||
| async initialize() {return} | |||
| private _pickingPlugin?: PickingPlugin | |||
| onAdded(viewer: ThreeViewer) { | |||
| super.onAdded(viewer) | |||
| this._pickingPlugin = viewer.getPlugin(PickingPlugin) | |||
| } | |||
| /** | |||
| * Factor of vertices to remove. eg 0.5 will remove half of the vertices. | |||
| * Default is 0.5 | |||
| * This is used when no factor or count is provided in the options to simplifyGeometry or simplifyGeometries. | |||
| */ | |||
| @uiSlider('Simplify Factor', [0, 1]) | |||
| simplifyFactor = 0.5 | |||
| simplifyGeometries(geometry?: ValOrArr<IGeometry>, options?: SimplifyOptions) { | |||
| if (!geometry) { | |||
| const selected = this._pickingPlugin?.getSelectedObject() | |||
| const geom: IGeometry[] = [] | |||
| selected?.traverse((o) => { | |||
| if (o.geometry && !geom.includes(o.geometry)) geom.push(o.geometry) | |||
| }) | |||
| geometry = geom | |||
| if (!geometry || !geometry.length) return | |||
| } | |||
| if (!Array.isArray(geometry)) geometry = [geometry] | |||
| const res: IGeometry[] = [] | |||
| for (const g of geometry) { | |||
| res.push(this.simplifyGeometry(g, options)!) | |||
| } | |||
| return res | |||
| } | |||
| simplifyGeometry(geometry?: IGeometry, { | |||
| factor, | |||
| count, | |||
| replace = true, | |||
| disposeOnReplace = false, | |||
| }: SimplifyOptions = {}): IGeometry|undefined { | |||
| if (!geometry) { | |||
| const selected = this._pickingPlugin?.getSelectedObject() | |||
| geometry = selected?.geometry | |||
| if (!geometry) return undefined | |||
| } | |||
| if (!geometry.attributes.position) { | |||
| this._viewer?.console.error('SimplifyModifierPlugin: Geometry does not have position attribute', geometry) | |||
| return geometry | |||
| } | |||
| factor = factor || this.simplifyFactor | |||
| count = count || geometry.attributes.position.count * factor | |||
| if (!geometry.boundingBox) geometry.computeBoundingBox() | |||
| const simplified = this._simplify(geometry, count) | |||
| simplified.computeBoundingBox() | |||
| simplified.computeBoundingSphere() | |||
| simplified.computeVertexNormals() | |||
| const bbox = simplified.boundingBox | |||
| const size = bbox!.getSize(new Vector3()) | |||
| if (!isFinite(size.x) || !isFinite(size.y) || !isFinite(size.z)) { | |||
| this._viewer?.console.error('SimplifyModifierPlugin: Unable to simplify', geometry, simplified, size) | |||
| return geometry | |||
| } | |||
| const oldBB = geometry.boundingBox | |||
| const oldSize = oldBB!.getSize(new Vector3()) | |||
| const diff = size.clone().sub(oldSize) | |||
| const diffPerc = diff.clone().divide(oldSize) | |||
| if (diffPerc.lengthSq() > 0.001) { | |||
| // todo: add option to skip this | |||
| console.warn('Simplify', geometry, simplified, bbox, oldBB, size, oldSize, diff, diffPerc) | |||
| } | |||
| // simplified.setDirty() | |||
| if (!replace) return simplified | |||
| // not working? | |||
| // geometry.copy(simplified) | |||
| // geometry.setDirty() | |||
| // simplified.dispose() | |||
| const meshes = geometry.appliedMeshes | |||
| if (!meshes) { | |||
| console.error('No meshes found for geometry, cannot replace', geometry) | |||
| return simplified | |||
| } | |||
| for (const mesh of meshes) { | |||
| mesh.geometry = simplified | |||
| } | |||
| if (disposeOnReplace) { | |||
| geometry.dispose(true) | |||
| } | |||
| return simplified | |||
| } | |||
| /** | |||
| * Sample for three.js addons SimplifyModifier: | |||
| * ` | |||
| * import {SimplifyModifier} from 'three/examples/jsm/modifiers/SimplifyModifier' | |||
| * protected _simplify(geometry: IGeometry, count: number): IGeometry { | |||
| * const modifier = new SimplifyModifier() | |||
| * return modifier.modify(geometry, count) as IGeometry | |||
| * } | |||
| * ` | |||
| * @param geometry | |||
| * @param count | |||
| */ | |||
| protected abstract _simplify(geometry: IGeometry, count: number): IGeometry | |||
| @uiButton('Simplify All') | |||
| async simplifyAll(root?: IObject3D, options?: SimplifyOptions) { | |||
| if (!root && this._viewer) root = this._viewer.scene.modelRoot | |||
| if (!root) { | |||
| console.error('SimplifyModifierPlugin: No root found') | |||
| return | |||
| } | |||
| if (!this.initialized) { | |||
| await this.initialize() | |||
| if (!this.initialized) { | |||
| this._viewer?.console.error('SimplifyModifierPlugin cannot be initialized') | |||
| return | |||
| } | |||
| } | |||
| const geometries: IGeometry[] = [] | |||
| root.traverse((o) => { | |||
| if (o.geometry && !geometries.includes(o.geometry)) geometries.push(o.geometry) | |||
| }) | |||
| if (!geometries.length) { | |||
| console.error('SimplifyModifierPlugin: No geometries found') | |||
| return | |||
| } | |||
| return this.simplifyGeometries(geometries, options) | |||
| } | |||
| @uiButton('Simplify Selected') | |||
| async simplifySelected() { | |||
| if (!this._viewer) return | |||
| if (!this.initialized) { | |||
| await this.initialize() | |||
| if (!this.initialized) { | |||
| await this._viewer.dialog.alert('SimplifyModifierPlugin cannot be initialized') | |||
| return | |||
| } | |||
| } | |||
| const selected = this._pickingPlugin?.getSelectedObject() | |||
| if (!selected) { | |||
| await this._viewer.dialog.alert('Nothing Selected') | |||
| return | |||
| } | |||
| let doAll = false | |||
| if (!selected.geometry) doAll = true | |||
| else if (selected.children.length === 0) doAll = true | |||
| if (!doAll) { | |||
| const resp = await this._viewer.dialog.confirm('Simplify all in hierarchy?') | |||
| if (resp) doAll = true | |||
| } | |||
| if (doAll) { | |||
| this.simplifyGeometries() | |||
| } else { | |||
| this.simplifyGeometry(selected.geometry) | |||
| } | |||
| } | |||
| } | |||
| @@ -70,3 +70,5 @@ export {HDRiGroundPlugin} from './extras/HDRiGroundPlugin' | |||
| export {Object3DWidgetsPlugin} from './extras/Object3DWidgetsPlugin' | |||
| export {Object3DGeneratorPlugin} from './extras/Object3DGeneratorPlugin' | |||
| export {ContactShadowGroundPlugin} from './extras/ContactShadowGroundPlugin' | |||
| export {SimplifyModifierPlugin} from './extras/SimplifyModifierPlugin' | |||
| export {MeshOptSimplifyModifierPlugin} from './extras/MeshOptSimplifyModifierPlugin' | |||