Renderer · class

Gizmo

Explained in Hello world.

class Gizmo
import { Gizmo } from '@driftengine/core';

Constructor

new

constructor(ringSegments?: number)

ringSegments is the resolution of a rotation ring, and it also sets every buffer's capacity because a ring is the largest thing any one of them holds.

ParameterTypeDescription
ringSegments?number

Properties

NameTypeDescription
modeGizmoMode
spaceGizmoSpace
sizenumberThe gizmo's own world size. See gizmoScaleFor. Non-positive draws and picks nothing.
positionreadonlyFloat32Array<ArrayBuffer>The transform being edited. Written by a drag; the caller copies it where it belongs.
rotationreadonlyFloat32Array<ArrayBuffer>A quaternion, xyzw, identity at construction.
scalereadonlyFloat32Array<ArrayBuffer>

Accessors

NameTypeDescription
hoveredgetnumberWhat a hover last found, or GIZMO_NONE.
activegetnumberThe handle a drag is on, or GIZMO_NONE.
dragginggetboolean
dragAnglegetnumberHow far the current rotation drag has turned, in radians, signed and unbounded.
More

This is the only place the unwrap in dragRing is observable, and that is not obvious. The orientation is not: quat.setAxisAngle is 2π-periodic and a quaternion double-covers the rotations, so an accumulated angle that is wrong by a whole turn produces the same orientation, negated — which is the same rotation. What the unwrap buys is a continuous number: a readout that says 270° rather than −90°, and a caller that wants to snap to fifteen degrees or stop at a limit, both of which need the turn and not the pose. Zero when no rotation drag is running.

Methods

segments

segments(group: number): LineSegments

One group's geometry, filled by the last build. Draw it with GIZMO_GROUP_COLORS[group].

ParameterTypeDescription
groupnumber

pick

pick(origin: ArrayLike<number>, direction: ArrayLike<number>): number

The handle a ray is over, or GIZMO_NONE. No side effects; hover is the one that remembers.

ParameterTypeDescription
originArrayLike<number>
directionArrayLike<number>
More

Nearest along the ray wins, except that the centre handle wins outright, which is one documented exception rather than a priority table: a priority table would let a plane handle behind the camera beat an arm in front of it, which a user sees as a click landing on the wrong thing.

As the radii stand the exception decides nothing, and that is worth saying rather than implying otherwise: an arm's pick cylinder starts at ARM_START of the size and the centre's sphere ends at CENTRE_RADIUS, which is smaller, so the two regions do not meet. The ordering is what keeps a later change to either radius from quietly producing a centre nobody can click, which is the failure it is here to prevent.

direction must be unit length. camera.rayThrough gives one.

hover

hover(origin: ArrayLike<number>, direction: ArrayLike<number>): number

pick, remembered. Returns what it found. Ignored while a drag is running.

ParameterTypeDescription
originArrayLike<number>
directionArrayLike<number>

beginDrag

beginDrag(origin: ArrayLike<number>, direction: ArrayLike<number>): boolean

Grab whatever the ray is over. Returns whether a drag started.

ParameterTypeDescription
originArrayLike<number>
directionArrayLike<number>
More

The transform and the axes are both captured here and neither is re-read until the drag ends. Recomputing the basis from a rotation the drag is changing feeds the answer back into its own input, and what that looks like is a ring that accelerates away from the pointer.

A grab whose anchor cannot be computed — a ray parallel to the axis it is grabbing — refuses rather than starting a drag with an arbitrary origin.

updateDrag

updateDrag(origin: ArrayLike<number>, direction: ArrayLike<number>): boolean

Move the drag. Returns whether the transform changed.

ParameterTypeDescription
originArrayLike<number>
directionArrayLike<number>
More

Every answer is absolute, computed from the anchor rather than accumulated from the last call, so a drag cannot drift however many frames it runs for. Rotation is the one exception and §5 of the design says why: an angle has to be unwrapped against the previous one to survive the seam at ±π, so the total is accumulated and the total is what the transform is built from.

A degenerate ray keeps the previous value rather than producing one. The alternative is the object teleporting at the moment the pointer crosses the plane it is dragging in, which is the single most annoying thing a gizmo can do.

endDrag

endDrag(): void

Let go. Safe to call on a frame with no drag running.

build

build(): void

Fill the five buffers for this frame's mode, transform and highlight.

More

Cheap enough to call every frame — a rotation ring at the default resolution is 48 segments of arithmetic — and it has to be, because the highlight changes with the pointer.