Replace fixed-interval yields (every 4096 iterations) with time-based
yields (every ~100ms of wall time). In foreground tabs this means ~10
yields per second instead of ~50-200, with identical UI responsiveness.
In background tabs where setTimeout(0) is throttled to ~1s, this
reduces overhead from ~200 wasted seconds to ~10 — the export runs
nearly as fast in the background as in the foreground.
Addresses #2 (background tab resource allocation).
Round 2 of performance and correctness improvements:
- Spatial grid index for brush painting: forEachTriInSphere now queries
only nearby grid cells instead of scanning all triangles. ~5.7x faster
for brush operations on 68k+ tri meshes.
- Decimation overflow fix: hasLinkViolation used a fixed 0x200000
multiplier for vertex-pair keys, overflowing at >2M vertices.
Now uses dynamic multiplier based on actual vertex count.
- Decimation determinant threshold: solveQ used absolute 1e-10 which
fails for large coordinates. Now relative to matrix element magnitude.
- 3MF triangle index validation: bounds-check all parsed indices against
vertex count, throw clear error on corrupt files instead of silent NaN.
- File size limit: reject files >500 MB before loading into memory,
prevents browser tab crash on oversized files.
- Accessibility: preset swatches now keyboard-navigable (role=button,
tabindex=0, Enter/Space to select). Modal dialogs trap focus and
close on Escape.
- Ctrl+click straight line tool: click to set start point, Ctrl+click
to paint a straight line between points. Ctrl+hover shows preview.
- Precision masking available for radius brush mode.
- Spatial grid rebuilt when entering/leaving precision mode.
- SoAHeap: hole method for bubbleUp/sinkDown (half the typed-array
writes per heap level vs the old swap approach).
- Seed dedup: Number keys instead of BigInt when vertex count < 94M
(10-50x faster per key). BigInt fallback for extreme meshes.
- addCreaseQuadrics: encode face pairs as negative numbers instead
of allocating small arrays per edge.
- Guard 3 (checkFlipped): single branch for corner index instead of
9 ternary operations per face.
- Yield interval kept at 4096 with setTimeout(0) for reliable UI updates.