Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ npm run pack -- my-module # -> my-module.zip, ready to install
| [health](modules/health/) | Hit points for objects and players. An object's hp is DERIVED from core Counters fed by `damage`/`heal` pulses (one trigger-log entry per pulse, the count riding the DEVX #18 handshake, so two peers and a late joiner read ONE number with no authority); a player's own hp rides their own `peerVars` row with regen derived from the clock. Sources: a wired event, click, touch, a zone, the knock feed (speed-scaled); death hides or respawns; a manager toolbox; HUD actions. Needs the R3a game seams. |
| [waves](modules/waves/) | 2.0 (30b): a VR wave shooter — a gun in hand (Blaster / Scatter / Beam), an ability on the grip (Shield / Slow-mo / Pulse), five levels of grunts, runners and tanks, the crystal breached, a menu reachable in VR. Wave survival composed on `health`: pre-placed enemies with health chains, the wave derived from their hit counters (each wave heals them back with local pulses), enemies walking spawn point → goal on the synced clock, `over` → the game shell, kills on each player's own row, the run log in `gameState.vars`. A late joiner lands on the same wave. Needs `health`. |
| [untangle](modules/untangle/) | The 190 test-flight GAME: drag the dots until no edges cross. Procedural guaranteed-solvable puzzles (seed-deterministic — determinism IS the netcode), desktop+VR drag via `api.pointerRay()`, replicated moves, lockstep win/level advance, generative WebAudio pad + SFX. Needs app ≥ the `api.pointerRay` SDK (core PR #37). |
| [smart-unwrap](modules/smart-unwrap/) | **Smart (xatlas)** in the UV editor's Unwrap menu: automatic seams, flattened charts and an overlap-free packed atlas from [xatlas](https://github.com/jpcy/xatlas) compiled to WebAssembly. The 177 kB `.wasm` ships inside the zip and loads through `api.assetUrl` on the first unwrap — no network, no CSP change. Charts come out unmirrored; peers without the module still receive the unwrapped mesh. Needs core ≥ 1.3 (`api.registerUnwrapBackend`). |

## Module format

Expand Down
16 changes: 16 additions & 0 deletions index.json
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,22 @@
"rhythm"
]
},
{
"id": "smart-unwrap",
"name": "Smart unwrap (xatlas)",
"version": "1.0.0",
"description": "Adds \"Smart (xatlas)\" to the UV editor's Unwrap menu: automatic seams, flattened charts and an overlap-free packed atlas, from xatlas compiled to WebAssembly (the .wasm ships in the zip).",
"author": "theprototype-app",
"source": "modules/smart-unwrap",
"zip": "smart-unwrap.zip",
"category": "tool",
"tags": [
"uv",
"unwrap",
"texturing",
"wasm"
]
},
{
"id": "tutorial-room",
"name": "Tutorial Room",
Expand Down
127 changes: 127 additions & 0 deletions modules/smart-unwrap/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# smart-unwrap

**Smart unwrap (xatlas)**: an automatic UV unwrapper for the UV editor. Core's
Unwrap menu ships projections (box, planar, cylindrical, spherical). This module
adds **Smart (xatlas)**, which cuts the mesh into charts along creases and seams,
flattens each chart (planar, ortho or LSCM), and packs them into one atlas with
no overlap. Other tools call this "smart UV project" or "lightmap pack". Under
the hood it is [xatlas](https://github.com/jpcy/xatlas) compiled to WebAssembly.
The `.wasm` travels inside the zip, so it needs no network, no CDN and no CSP
change.

## Use it

1. Burger menu ▸ **Modules ▸ Install** (from zip: `npm run pack -- smart-unwrap`
writes `smart-unwrap.zip`).
2. Select a mesh and open the **UV editor**.
3. **Unwrap ▾** ▸ **Smart (xatlas)**. With faces picked in Edit Mesh, only those
faces are unwrapped and the rest of the mesh keeps its UVs. That part is core's
scoped-unwrap path.

The first unwrap instantiates the wasm (about 177 kB) and later ones reuse it.
The result is committed by core (`uvEditor.unwrapObject`) as one undo entry and
replicated as a `meshgeo` snapshot. Peers **without** the module still get the
unwrapped mesh, because only the peer who presses the button runs xatlas.

If the menu was already open in a UV editor when you installed the module, close
and reopen the editor. The editor reads the backend list when it opens.

## Options

The UV editor calls the backend with `{margin: 0.02}`. A script can pass more
through `uvEditor.unwrapObject(uuid, 'mod-smart-unwrap-xatlas', options)`:

| option | default | meaning |
|---|---|---|
| `margin` | `0.01` | gap between charts as a fraction of the texture, the same meaning as for the built-in packer. It is converted to xatlas padding of `margin × resolution / 2` texels per chart. |
| `resolution` | `512` | the texture size the padding is computed for. Going higher is not reliably tighter: at 1024 the 216-tri sphere covered 46% of the square, against 49% at 512, and took about 10× longer. The 12k-tri sphere went from 50% to 54% coverage. |
| `padding` | (from `margin`) | xatlas padding in texels, which overrides `margin` |
| `chartOptions` | `{fixWinding: true}` | raw xatlas `ChartOptions` (`maxChartArea`, `maxCost`, `normalSeamWeight`, …), merged last |
| `packOptions` | see below | raw xatlas `PackOptions`, merged last |

The pack defaults are `bilinear: true`, `rotateCharts: false`, and `bruteForce`
for meshes of 4000 triangles or fewer.

**Why `rotateCharts` is off:** xatlas's packer turns a chart 90° by
*transposing* it (it swaps u and v), and a transpose is a mirror. With it on, a
216-triangle UV sphere came back with 2 of its 6 charts mirrored, so painted text
would read backwards on those faces. With it off and `fixWinding` on, every
triangle in the tests winds counter-clockwise (unmirrored for three's v-up UVs).
The cost is a slightly looser pack. `rotateChartsToAxis` is a true rotation and
stays on.

## Limits

- **Main thread.** A few hundred triangles take tens of ms. A 12k-triangle sphere
takes about 1.5 s and a 9.6k-triangle torus knot about 1.5 s, and the UI waits
for it. (These times are from node on a Steam Deck. A worker would keep the UI
responsive and is a possible next step.)
- **One atlas, 0..1.** If xatlas spills into several atlases at the chosen
resolution, the backend re-packs into one so that nothing overlaps once
normalised.
- **Degenerate faces** (zero area or NaN) are ignored by xatlas. They get
`[0,0]` UVs and ride one extra island, so every face is still accounted for.
- **Welding is exact.** Corners are joined only when their positions are
bit-identical, which is true of every mesh the editor builds. A mesh whose
"shared" vertices differ by float noise unwraps with more seams.
- **Failure** (missing asset or a wasm abort) shows a toast and returns
nothing, so core commits nothing. The next unwrap starts a fresh runtime.

## Lifecycle

`api.registerUnwrapBackend` is journaled by the SDK (`sdk/backends.js`
`onDispose`). Unloading or live-updating the module removes **Smart (xatlas)**
from the menu with no code here. The module's own `api.onUnload` drops the
cached wasm runtime so its memory can be collected.

## Which wasm build, and why

**xatlas-wasm 0.1.3** (mode777, MIT declared in package.json; xatlas itself is
MIT, © Jonathan Young), not **watlas 1.0.1** (toji, MIT). The reasons:

- **Size.** The xatlas-wasm `.wasm` is 177 kB, a plain C API with 6 imports and
no embind or C++ exceptions. watlas is 225 kB with 56 embind and exception
imports.
- **Glue.** xatlas-wasm has a 39 kB release glue, and its emscripten
`Module.instantiateWasm` hook takes our blob-URL bytes with no patching.
watlas has an 87 kB ASSERTIONS build that *aborts* if `instantiateWasm` or
`wasmBinary` is supplied, and it resolves `watlas.wasm` against
`import.meta.url`, which is meaningless for a blob-URL module. It would need
its minified glue patched.
- **Threads.** Both run single-threaded on the main thread (no pthreads or
SharedArrayBuffer), and both expose the same xatlas API (positions + indices
in, then per-vertex uv / xref / chartIndex and per-chart face lists out).
- **The cost.** xatlas-wasm is young (one maintainer, a few stars), and its repo
has no LICENSE file of its own, only the MIT declaration. The text and
attributions are in [THIRD_PARTY_LICENSES.txt](THIRD_PARTY_LICENSES.txt),
which ships in the zip.

### Refreshing the vendored build

`module.js` carries the xatlas-wasm glue between the `VENDORED` markers, and
`assets/xatlas.wasm` is the binary. Both come from the npm tarball through
`vendor.mjs`, which reads the package as **text** and never runs it:

```bash
mkdir /tmp/xw && cd /tmp/xw && npm pack xatlas-wasm@0.1.3 --ignore-scripts && tar xzf *.tgz
node modules/smart-unwrap/vendor.mjs /tmp/xw/package/dist/index.mjs
npm run test:smart-unwrap
```

## Tests

`npm run test:smart-unwrap` runs in node with no app. It loads the real
`module.js` and the real `assets/xatlas.wasm` through a fake `api` whose
`assetUrl` returns blob URLs, as the app does. It then unwraps a cube (12 tris),
a UV sphere (216), a cylinder (240), a scoped half-sphere and a 12k-tri sphere.
It checks properties rather than exact numbers:

- 3 corners per face, every uv in [0,1], no zero-area uv triangle and a total
area of at most 1
- the islands partition the faces, and no two islands overlap on a 512² raster
- every triangle is counter-clockwise, and the cube has 6 or fewer islands

Counterfactuals show that each check catches a broken result: stacked islands,
a duplicated island, collapsed UVs, shifted UVs, a dropped face, unwelded input
and xatlas's mirroring `rotateCharts`. The suite also covers the lazy and
cached instantiation, the unload hook and the failure path.
82 changes: 82 additions & 0 deletions modules/smart-unwrap/THIRD_PARTY_LICENSES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
Smart unwrap (xatlas) — third-party software
=============================================

This module ships, unmodified except as noted, a WebAssembly build of xatlas and the
JavaScript glue that drives it:

assets/xatlas.wasm the xatlas-wasm 0.1.3 binary, decoded byte-for-byte from the
package's inline (SINGLE_FILE) copy
module.js the xatlas-wasm 0.1.3 emscripten glue + wrapper, between the
"VENDORED xatlas-wasm glue" markers; two edits: the inline binary
is dropped (the .wasm above is loaded through
Module.instantiateWasm instead) and the ES `export {...}` block is
removed

Source package: https://www.npmjs.com/package/xatlas-wasm (version 0.1.3,
sha512-LGWvSyHryDda9+a3+le2bd+QWPTDMFjfu3CPlVauTm8Liqd+S9wIjQScBzlOChFTyL8OpzZ4AI0tE0zoF3kc6g==)
Repository: https://github.com/mode777/xatlas-wasm


-------------------------------------------------------------------------------
xatlas-wasm
-------------------------------------------------------------------------------
Copyright (c) Alexander Klingenbeck (alexander.klingenbeck@gmx.de)

xatlas-wasm declares the MIT license (package.json "license": "MIT"); the package and
repository carry no separate license text, so the standard MIT terms are reproduced
here:

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


-------------------------------------------------------------------------------
xatlas — https://github.com/jpcy/xatlas
-------------------------------------------------------------------------------
MIT License

Copyright (c) 2018-2020 Jonathan Young

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


-------------------------------------------------------------------------------
Code inside xatlas (notices from source/xatlas/xatlas.cpp, all MIT, terms as above)
-------------------------------------------------------------------------------
thekla_atlas — https://github.com/Thekla/thekla_atlas
Copyright (c) 2013 Thekla, Inc
Copyright NVIDIA Corporation 2006 -- Ignacio Castano <icastano@nvidia.com>

Fast-BVH — https://github.com/brandonpelfrey/Fast-BVH
Copyright (c) 2012 Brandon Pelfrey
Binary file added modules/smart-unwrap/assets/xatlas.wasm
Binary file not shown.
9 changes: 9 additions & 0 deletions modules/smart-unwrap/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"id": "smart-unwrap",
"name": "Smart unwrap (xatlas)",
"version": "1.0.0",
"format": 1,
"description": "Adds \"Smart (xatlas)\" to the UV editor's Unwrap menu: automatic seams, flattened charts and an overlap-free packed atlas, from xatlas compiled to WebAssembly.",
"entry": "module.js",
"files": ["module.js", "assets/xatlas.wasm", "THIRD_PARTY_LICENSES.txt"]
}
Loading