Skip to content

Commit 8f368a7

Browse files
authored
Merge pull request #739 from zeux/simplify-viz
demo: Improve simplification demo visualization
2 parents abc4c36 + 181192d commit 8f368a7

File tree

3 files changed

+122
-25
lines changed

3 files changed

+122
-25
lines changed

demo/simplify.html

+120-25
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import * as THREE from 'three';
5151
import { GLTFLoader } from 'three-examples/loaders/GLTFLoader.js';
5252
import { OrbitControls } from 'three-examples/controls/OrbitControls.js';
53+
import { mergeVertices } from 'three-examples/utils/BufferGeometryUtils.js';
5354
import { MeshoptDecoder } from '../js/meshopt_decoder.module.js';
5455
import { MeshoptSimplifier } from '../js/meshopt_simplifier.module.js';
5556
import { GUI } from 'https://unpkg.com/[email protected]/dist/lil-gui.esm.js';
@@ -62,7 +63,9 @@
6263
wireframe: false,
6364
pointSize: 1.0,
6465
ratio: 1.0,
66+
debugOverlay: false,
6567
lockBorder: false,
68+
weldVertices: false,
6669
useAttributes: false,
6770
errorThresholdLog10: 1,
6871
normalWeight: 0.5,
@@ -90,16 +93,20 @@
9093
reload();
9194
simplify();
9295
},
96+
autoUpdate: false,
97+
autoUpdateStatus: '',
9398
};
9499

95100
var gui = new GUI({ width: 300 });
96101
var guiDisplay = gui.addFolder('Display');
97102
guiDisplay.add(settings, 'wireframe').onChange(update);
98103
guiDisplay.add(settings, 'pointSize', 1, 16).onChange(update);
104+
guiDisplay.add(settings, 'debugOverlay').onChange(simplify); // requires debug data rebuild
99105

100106
var guiSimplify = gui.addFolder('Simplify');
101107
guiSimplify.add(settings, 'ratio', 0, 1, 0.01).onChange(simplify);
102108
guiSimplify.add(settings, 'lockBorder').onChange(simplify);
109+
guiSimplify.add(settings, 'weldVertices').onChange(simplify);
103110
guiSimplify.add(settings, 'errorThresholdLog10', 0, 3, 0.1).onChange(simplify);
104111
guiSimplify.add(settings, 'useAttributes').onChange(simplify);
105112
guiSimplify.add(settings, 'normalWeight', 0, 2, 0.01).onChange(simplify);
@@ -108,6 +115,8 @@
108115
var guiLoad = gui.addFolder('Load');
109116
guiLoad.add(settings, 'loadFile');
110117
guiLoad.add(settings, 'updateModule');
118+
guiLoad.add(settings, 'autoUpdate').onChange(autoReload);
119+
guiLoad.add(settings, 'autoUpdateStatus').listen();
111120

112121
var guiStats = gui.addFolder('Stats');
113122
guiStats.add(settings.stats, 'triangles').listen();
@@ -119,14 +128,16 @@
119128
animate();
120129

121130
function simplifyMesh(geo) {
122-
if (!geo.index.original) {
123-
geo.index.original = geo.index.array;
131+
if (settings.weldVertices) {
132+
// pre-welding is important for some meshes that have very close normals in adjacent face corners
133+
// for now we use the welder from three.js; this is not as performant as it could be
134+
geo = mergeVertices(geo, 1e-2);
124135
}
125136

126137
var attributes = 6; // 3 color, 3 normal
127138

128139
var positions = new Float32Array(geo.attributes.position.array);
129-
var indices = geo.index.original;
140+
var indices = new Uint32Array(geo.index.array); // needed for _InternalDebug to work
130141
var target = Math.floor((indices.length * settings.ratio) / 3) * 3;
131142

132143
if (settings.useAttributes) {
@@ -166,30 +177,68 @@
166177

167178
console.time('simplify');
168179

169-
var res = settings.useAttributes
170-
? MeshoptSimplifier.simplifyWithAttributes(
171-
indices,
172-
positions,
173-
stride,
174-
attrib,
175-
attributes,
176-
attrib_weights,
177-
null,
178-
target,
179-
threshold,
180-
flags,
181-
)
182-
: MeshoptSimplifier.simplify(indices, positions, stride, target, threshold, flags);
180+
function run() {
181+
var S = MeshoptSimplifier; // to avoid line breaks below...
182+
return settings.useAttributes
183+
? S.simplifyWithAttributes(indices, positions, stride, attrib, attributes, attrib_weights, null, target, threshold, flags)
184+
: MeshoptSimplifier.simplify(indices, positions, stride, target, threshold, flags);
185+
}
186+
187+
var res = run();
188+
189+
if (settings.debugOverlay) {
190+
flags.push('_InternalDebug');
191+
var dres = run();
192+
}
183193

184194
console.timeEnd('simplify');
185195

186-
console.log('simplified to', res[0].length / 3, 'with error', res[1]);
196+
var rgeo = geo.clone();
197+
198+
var dind = res[0];
199+
200+
if (settings.debugOverlay) {
201+
// we need extra indices for debug overlay
202+
dind = Array.from(res[0]);
203+
204+
for (var kind = 1; kind <= 4; ++kind) {
205+
var offset = dind.length;
206+
207+
for (var i = 0; i < dres[0].length; i += 3) {
208+
var mask = (1 << 28) - 1;
209+
210+
for (var e = 0; e < 3; ++e) {
211+
var a = dres[0][i + e],
212+
b = dres[0][i + ((e + 1) % 3)];
213+
214+
if (a >> 31 != 0 && ((a >> 28) & 7) == kind) {
215+
// loop of current kind
216+
dind.push(a & mask);
217+
dind.push(a & mask);
218+
dind.push(b & mask);
219+
} else if (kind == 4 && ((a >> 28) & 7) == kind && ((b >> 28) & 7) == kind) {
220+
// locked edge (may not be marked as a loop)
221+
dind.push(a & mask);
222+
dind.push(a & mask);
223+
dind.push(b & mask);
224+
}
225+
}
226+
}
227+
228+
if (offset != dind.length) {
229+
rgeo.addGroup(offset, dind.length - offset, kind);
230+
offset = dind.length;
231+
}
232+
}
233+
}
234+
235+
rgeo.index.array = new Uint32Array(dind);
236+
rgeo.index.count = dind.length;
237+
rgeo.index.needsUpdate = true;
187238

188-
geo.index.array = res[0];
189-
geo.index.count = res[0].length;
190-
geo.index.needsUpdate = true;
239+
rgeo.addGroup(0, res[0].length, 0);
191240

192-
return res[1];
241+
return [rgeo, res[0].length / 3, res[1]];
193242
}
194243

195244
function simplifyPoints(geo) {
@@ -232,10 +281,29 @@
232281

233282
scene.traverse(function (object) {
234283
if (object.isMesh) {
235-
var err = simplifyMesh(object.geometry);
284+
if (!object.original) {
285+
object.original = object.geometry.clone();
286+
287+
// use small depth offset to avoid overlay z-fighting with the original mesh
288+
// has to be done on the main material as overlays use lines that don't support depth offset
289+
object.material.polygonOffset = true;
290+
object.material.polygonOffsetUnits = 16;
291+
292+
object.material = [
293+
object.material,
294+
new THREE.MeshBasicMaterial({ color: 0x0000ff, wireframe: true }), // border
295+
new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true }), // seam
296+
new THREE.MeshBasicMaterial({ color: 0xff00ff, wireframe: true }), // complex
297+
new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true }), // locked
298+
];
299+
}
300+
301+
var [geo, tri, err] = simplifyMesh(object.original);
302+
303+
object.geometry = geo;
236304

237305
error = Math.max(error, err); // note: we are ignoring the possibility of different mesh scales atm
238-
triangles += object.geometry.index.count / 3;
306+
triangles += tri;
239307
vertices += object.geometry.attributes.position.count;
240308
}
241309
if (object.isPoints) {
@@ -262,10 +330,37 @@
262330
});
263331
}
264332

333+
var moduleLastModified = 0;
334+
335+
function autoReload() {
336+
if (!settings.autoUpdate) return;
337+
338+
fetch('/js/meshopt_simplifier.module.js?x=' + Date.now(), { method: 'HEAD' })
339+
.then(function (r) {
340+
var last = r.headers.get('Last-Modified');
341+
if (last != moduleLastModified) {
342+
moduleLastModified = last;
343+
reload();
344+
simplify();
345+
}
346+
347+
settings.autoUpdateStatus = new Date(last).toLocaleTimeString();
348+
setTimeout(autoReload, 1000);
349+
})
350+
.catch(function (e) {
351+
settings.autoUpdateStatus = 'error';
352+
setTimeout(autoReload, 5000);
353+
});
354+
}
355+
265356
function update() {
266357
scene.traverse(function (child) {
267358
if (child.isMesh) {
268-
child.material.wireframe = settings.wireframe;
359+
if (Array.isArray(child.material)) {
360+
child.material[0].wireframe = settings.wireframe;
361+
} else {
362+
child.material.wireframe = settings.wireframe;
363+
}
269364
}
270365
if (child.isPoints) {
271366
child.material.size = settings.pointSize;

js/meshopt_simplifier.js

+1
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ var MeshoptSimplifier = (function () {
197197
LockBorder: 1,
198198
Sparse: 2,
199199
ErrorAbsolute: 4,
200+
_InternalDebug: 1 << 30,
200201
};
201202

202203
return {

js/meshopt_simplifier.module.js

+1
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ var MeshoptSimplifier = (function () {
196196
LockBorder: 1,
197197
Sparse: 2,
198198
ErrorAbsolute: 4,
199+
_InternalDebug: 1 << 30,
199200
};
200201

201202
return {

0 commit comments

Comments
 (0)