Closed general

Ground rebuild discards consumer styling (needs a hook, or no dispose)

Colby Knox · 11d ago ·closed by Colby Knox

Split out of adom/wiki#60 (@aravk item 3). Filed here rather than on adom/wiki because it is viewer engine work, not wiki work.

Problem

setGroundVisible(true) does not toggle the ground, it rebuilds it. The ground is created inside frameModel(), sized and positioned from the current content bounds, and each call disposes the existing mesh and constructs a fresh PBRMetallicRoughnessMaterial. Any consumer customization of the ground (material, colour, texture, opacity) is silently discarded, not just on an explicit visibility toggle but on every reframe, which happens on load and on layer changes too.

Ask, in AravK's words

Preserve the ground's material and state across visibility toggles, or expose a ground-style hook.

Why it is two different changes

These are worth separating, because one is easy and one is not:

  1. A style hook is straightforward: a groundMaterial option, or an onGroundCreated(mesh) callback fired after each rebuild so a consumer can restyle it. This does not stop the rebuild, it just gives the consumer a reliable moment to reapply. Roughly 15 lines.

  2. Genuinely preserving arbitrary consumer state means not disposing at all: keeping the mesh across reframes and updating only its size and position. That is a rework of the ground block in frameModel, and it needs care around the ground being tied to zUp and to content extent, both of which can change between frames.

Option 1 solves the stated need for most consumers and is cheap. Option 2 is the honest fix and is not.

My inclination is to ship the hook first and see whether anyone actually needs the second, since a callback that fires on every rebuild is a complete answer for restyling, and the rebuild itself exists for a real reason: the ground has to track content bounds.

2 Replies

Colby Knox · 11d ago

Same direction call from @kcknox applies here: the viewer stays a blank canvas, and wiki-specific behaviour is built on top through its API.

That settles the choice this issue was holding open. The two options were a style hook, or genuinely never disposing the ground so arbitrary consumer state survives. Under blank-canvas the hook is not the cheap compromise, it is the correct answer:

  • The engine owns the ground's geometry, and it must, because the ground has to track content bounds and the zUp convention. Rebuilding it on reframe is not a bug, it is the mechanism.
  • The engine should not own the ground's appearance. A material choice is exactly the kind of look-and-feel decision that belongs to whoever is embedding the viewer.

So: onGroundCreated(mesh) fired after each rebuild, or a groundMaterial option, or both. The consumer restyles on every rebuild, which is reliable precisely because the rebuild is predictable. Roughly 15 lines, and it drops the "never dispose" rework entirely rather than deferring it.

Worth noting the same tension as adom/adom-3d-viewer#2: the current ground styling (20 percent alpha, radial rim fade, unpickable) is a look the wiki wants, sitting in the engine as a default. The hook does not remove that, it just stops it being the only possibility. Whether the wiki should pass its own ground style explicitly instead of relying on an engine default is the same question as whether the layers panel belongs in the engine, and is better answered once there is a second embedder to compare against.

Colby Knox · 11d ago

Shipped in 0.6.0, live on prod. onGroundCreated(ground) is an init() option, called with the ground mesh after every rebuild.

Adom3DViewerBabylon9.init(el, {
  showGround: true,
  onGroundCreated: (g) => { g.material = myGroundMaterial; },
});

The blank-canvas direction settled the choice this issue was holding open, and it landed on the hook rather than the rework:

  • The ground's geometry belongs to the viewer, and has to. It is sized from content bounds and positioned against the zUp convention, so it is rebuilt on every reframe. That is the mechanism, not a bug.
  • The ground's appearance does not. A material is a look, and looks belong to whoever is embedding the viewer.

So the rebuild stays, and the hook fires after it. That is what makes restyling durable: setting the material once from outside is what was getting lost, because the next reframe replaced the mesh underneath it. Restyling in the callback runs again on every rebuild, so it survives by construction.

That also drops the "never dispose" option entirely rather than deferring it. Keeping the mesh alive across reframes would have meant updating size, position and orientation in place while the model, the bounds and possibly zUp all changed, which is a lot of state to keep correct in exchange for a callback that already solves the stated problem.

A throwing callback is caught and logged rather than taking the frame down with it.

Verified in a browser: the hook fires once per model load with the ground mesh, across two consecutive loads.

Worth noting the same tension you raised is still there in a smaller form. The default ground look (20 percent alpha, radial rim fade, unpickable) is a choice the wiki wants, sitting in the engine as a default. The hook means it is no longer the only possibility, which was the actual complaint. Whether the wiki should pass its own ground style explicitly instead of relying on that default is the same question as whether the layers panel belongs in the engine (adom/adom-3d-viewer#2), and is better answered once there is a second embedder to compare against.

Log in to reply.