|
50 | 50 | import * as THREE from 'three';
|
51 | 51 | import { GLTFLoader } from 'three-examples/loaders/GLTFLoader.js';
|
52 | 52 | import { OrbitControls } from 'three-examples/controls/OrbitControls.js';
|
| 53 | + import { mergeVertices } from 'three-examples/utils/BufferGeometryUtils.js'; |
53 | 54 | import { MeshoptDecoder } from '../js/meshopt_decoder.module.js';
|
54 | 55 | import { MeshoptSimplifier } from '../js/meshopt_simplifier.module.js';
|
55 | 56 | import { GUI } from 'https://unpkg.com/[email protected]/dist/lil-gui.esm.js';
|
|
62 | 63 | wireframe: false,
|
63 | 64 | pointSize: 1.0,
|
64 | 65 | ratio: 1.0,
|
| 66 | + debugOverlay: false, |
65 | 67 | lockBorder: false,
|
| 68 | + weldVertices: false, |
66 | 69 | useAttributes: false,
|
67 | 70 | errorThresholdLog10: 1,
|
68 | 71 | normalWeight: 0.5,
|
|
90 | 93 | reload();
|
91 | 94 | simplify();
|
92 | 95 | },
|
| 96 | + autoUpdate: false, |
| 97 | + autoUpdateStatus: '', |
93 | 98 | };
|
94 | 99 |
|
95 | 100 | var gui = new GUI({ width: 300 });
|
96 | 101 | var guiDisplay = gui.addFolder('Display');
|
97 | 102 | guiDisplay.add(settings, 'wireframe').onChange(update);
|
98 | 103 | guiDisplay.add(settings, 'pointSize', 1, 16).onChange(update);
|
| 104 | + guiDisplay.add(settings, 'debugOverlay').onChange(simplify); // requires debug data rebuild |
99 | 105 |
|
100 | 106 | var guiSimplify = gui.addFolder('Simplify');
|
101 | 107 | guiSimplify.add(settings, 'ratio', 0, 1, 0.01).onChange(simplify);
|
102 | 108 | guiSimplify.add(settings, 'lockBorder').onChange(simplify);
|
| 109 | + guiSimplify.add(settings, 'weldVertices').onChange(simplify); |
103 | 110 | guiSimplify.add(settings, 'errorThresholdLog10', 0, 3, 0.1).onChange(simplify);
|
104 | 111 | guiSimplify.add(settings, 'useAttributes').onChange(simplify);
|
105 | 112 | guiSimplify.add(settings, 'normalWeight', 0, 2, 0.01).onChange(simplify);
|
|
108 | 115 | var guiLoad = gui.addFolder('Load');
|
109 | 116 | guiLoad.add(settings, 'loadFile');
|
110 | 117 | guiLoad.add(settings, 'updateModule');
|
| 118 | + guiLoad.add(settings, 'autoUpdate').onChange(autoReload); |
| 119 | + guiLoad.add(settings, 'autoUpdateStatus').listen(); |
111 | 120 |
|
112 | 121 | var guiStats = gui.addFolder('Stats');
|
113 | 122 | guiStats.add(settings.stats, 'triangles').listen();
|
|
119 | 128 | animate();
|
120 | 129 |
|
121 | 130 | 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); |
124 | 135 | }
|
125 | 136 |
|
126 | 137 | var attributes = 6; // 3 color, 3 normal
|
127 | 138 |
|
128 | 139 | 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 |
130 | 141 | var target = Math.floor((indices.length * settings.ratio) / 3) * 3;
|
131 | 142 |
|
132 | 143 | if (settings.useAttributes) {
|
|
166 | 177 |
|
167 | 178 | console.time('simplify');
|
168 | 179 |
|
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 | + } |
183 | 193 |
|
184 | 194 | console.timeEnd('simplify');
|
185 | 195 |
|
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; |
187 | 238 |
|
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); |
191 | 240 |
|
192 |
| - return res[1]; |
| 241 | + return [rgeo, res[0].length / 3, res[1]]; |
193 | 242 | }
|
194 | 243 |
|
195 | 244 | function simplifyPoints(geo) {
|
|
232 | 281 |
|
233 | 282 | scene.traverse(function (object) {
|
234 | 283 | 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; |
236 | 304 |
|
237 | 305 | 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; |
239 | 307 | vertices += object.geometry.attributes.position.count;
|
240 | 308 | }
|
241 | 309 | if (object.isPoints) {
|
|
262 | 330 | });
|
263 | 331 | }
|
264 | 332 |
|
| 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 | + |
265 | 356 | function update() {
|
266 | 357 | scene.traverse(function (child) {
|
267 | 358 | 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 | + } |
269 | 364 | }
|
270 | 365 | if (child.isPoints) {
|
271 | 366 | child.material.size = settings.pointSize;
|
|
0 commit comments