Kaynağa Gözat

Add SimplifyModifierPlugin, MeshOptSimplifyModifierPlugin and examples.

master
Palash Bansal 2 yıl önce
ebeveyn
işleme
181a9606e6
No account linked to committer's email address

+ 43
- 0
README.md Dosyayı Görüntüle

@@ -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.

+ 3
- 1
examples/index.html Dosyayı Görüntüle

@@ -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>

+ 36
- 0
examples/meshopt-simplify-modifier-plugin/index.html Dosyayı Görüntüle

@@ -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>

+ 40
- 0
examples/meshopt-simplify-modifier-plugin/script.ts Dosyayı Görüntüle

@@ -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)

+ 38
- 0
examples/simplify-modifier-plugin/index.html Dosyayı Görüntüle

@@ -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>

+ 63
- 0
examples/simplify-modifier-plugin/script.ts Dosyayı Görüntüle

@@ -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)

+ 8
- 2
examples/tweakpane-editor/script.ts Dosyayı Görüntüle

@@ -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],

+ 104
- 0
src/plugins/extras/MeshOptSimplifyModifierPlugin.ts Dosyayı Görüntüle

@@ -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
}
}

+ 206
- 0
src/plugins/extras/SimplifyModifierPlugin.ts Dosyayı Görüntüle

@@ -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)
}
}

}

+ 2
- 0
src/plugins/index.ts Dosyayı Görüntüle

@@ -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'

Loading…
İptal
Kaydet