-
Notifications
You must be signed in to change notification settings - Fork 28
/
SimplifyModifierPlugin.ts
206 lines (192 loc) · 7.26 KB
/
SimplifyModifierPlugin.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
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', {sendArgs: false})
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)
}
}
}