manipulator-view

The visible half of the manipulator: a universal widget, in the sense Cheetah 3D means it. Every enabled affordance is on screen at once and the part you grab says what the drag means — drag a shaft to move along an axis, a pad to move in a plane, a ring to turn, a cube to scale. The maths is in manipulator; the element you actually place in a scene is b3d-manipulator.

Babylon has a GizmoManager, and it is mouse-shaped. This is built so a hand can grab it, not only a ray.

Why one widget instead of a mode

A mode switch makes you say what you want twice: once to the toolbar and again to the handle. It also costs a round trip for the commonest edit there is — nudge it over, then turn it a bit. The cost is crowding, and the answer to that is the transform set: turn off what you are not using and its grips are simply not built.

Sized for hands, not only for pixels

Handles carry a nearRadius and are picked two ways: a hand inside that radius grabs directly, anything further grabs by pointing. A gizmo designed for a mouse gets this wrong by being visually thin — fine for a pixel-accurate cursor, impossible to grab with a controller you are holding at arm's length, and reported from a phone as "I couldn't move a selection". So every drawn handle carries a second, invisible, fatter mesh that exists only to be picked.

Two-pass picking, and why one pass was the bug

gripAt picks the DRAWN handles first, then falls back to the fat targets. One pass over both meant the fat targets — which overlap by design — decided everything by ray depth, so the nearest surface was regularly a ring's tube passing in front of the arrowhead squarely under the cursor. "I rotated when I tried to translate."

renderingGroupId draws it on top and does NOT make it pickable

Handles draw in group 1, because a handle buried inside the mesh it manipulates cannot be seen — and the object an author most wants to move is usually the one embedded in something else. That is a LOOK fix only: rendering group has no bearing on picking, and this library has already shipped that mistake once (see the camera-relative panel note in b3d-svg-plane, where a panel painted in front and could not be touched).

It is safe here for a reason that does not generalise: gripAt picks with a PREDICATE that admits nothing but handle meshes, so an occluder is never a candidate and the depth question never arises. Pick handles any other way and the terrain in front of them wins.

Where this came from

Ported from tosijs-3d-ensemble, where every proportion below was measured against the owner's reference model rather than eyeballed, and every sizing decision was won against a real complaint. The geometry is unchanged; the API is smaller — gripAt folds in the two-pass pick the consumer used to write itself, and composeRotation folds in the euler composition it used to inject.