Renderer · class
Camera
Perspective camera. Yaw/pitch convention: yaw 0 looks toward -Z, positive yaw turns right (+X); positive pitch looks up.
Explained in Hello world.
class Cameraimport { Camera } from '@driftengine/core';Properties
| Name | Type | Description |
|---|---|---|
positionreadonly | vec3 | |
yaw | number | |
pitch | number | |
fovYDeg | number | |
near | number | Near plane, metres — and the single biggest control this engine has over
z-fighting.MoreA conventional depth buffer spends its precision hyperbolically: the smallest
separation it can resolve at distance Precision scales linearly with this number, so four times the near plane is
four times the resolution at every distance, and it costs nothing. Safe at 0.4
because a third-person boom never brings the eye closer than It is a mitigation and not the cure. A 0.2 mm seam, which the daily generator
does produce where two decks meet, still fights past about forty metres. The
cure is a reversed-Z float depth buffer; see |
far | number | |
forwardreadonly | vec3 | |
viewreadonly | mat4 | |
projectionreadonly | mat4 | |
viewProjectionreadonly | mat4 | |
invViewProjectionreadonly | mat4 | |
roll | number | Roll about the view direction, radians. Positive drops the right side. Render-only: nothing about the camera reaches the simulation. |
Methods
lookAt
lookAt(x: number, y: number, z: number): voidPoint the camera at a world position from wherever it currently stands.
| Parameter | Type | Description |
|---|---|---|
x | number | |
y | number | |
z | number |
More
Added because three callers had each derived this by hand and one of them got it
wrong: a preview rig placed itself on a circle around its subject and computed a yaw
that was exactly π out, so it rendered the inside of an empty box and the figure it was
built to show was behind it. The trig is four lines and the sign conventions are this
class's own — yaw 0 looks toward −Z, positive yaw turns toward +X — so leaving every
caller to rediscover them is leaving them a trap.
Sets yaw and pitch only. Roll is a separate decision (it is a lean, not an aim),
and updateMatrices still has to be called afterwards.
updateMatrices
updateMatrices(aspect: number): void| Parameter | Type | Description |
|---|---|---|
aspect | number |
adoptView
adoptView(view: ReadonlyMat4, projection: ReadonlyMat4): voidTake a view and a projection that were computed somewhere else.
| Parameter | Type | Description |
|---|---|---|
view | ReadonlyMat4 | |
projection | ReadonlyMat4 |
More
Because an XR eye cannot be described by this class, and this file already says why.
updateMatrices derives the view from yaw and pitch and the projection from
mat4.perspective, and the note above it records the corollary: only a centred crop of a
symmetric frustum is itself a symmetric frustum; an off-centre one is sheared, which
perspective cannot produce. A headset's eye projection is exactly that shear, off-axis by
the distance from the pupil to the display's centre, and its view comes from a pose no pair of
Euler angles was ever asked to describe.
So a supplied view is adopted rather than configured. Everything downstream reads view,
projection, viewProjection and invViewProjection, so frustum culling, picking, the sky
and every pass that samples the inverse keep working with nothing changed.
position and forward are decomposed back out, and that is not a convenience. A view
matrix is the inverse of the camera's world transform, so the eye is -Rᵀt and the direction
is the third row negated. A consumer reading camera.position to place a listener or to score
a level of detail would otherwise get whatever the last non-XR frame left there, which is a
defect that looks like an audio bug rather than a camera one.
yaw, pitch and roll are deliberately left alone, and reading them after this is
reading the last angles somebody set. A supplied rotation may be one no Euler triple
describes without a convention this class does not own, and inventing one here would put a
second answer to "where is the camera looking" beside forward, which is derived from the
matrix and is always right.
near and far are not read and not written: the supplied projection carries its own depth
range, which the runtime chose.
project
project(out: Float32Array | number[], x: number, y: number, z: number, cssWidth: number, cssHeight: number): booleanWhere a world point lands on the canvas, in CSS pixels, or false if it is behind.
| Parameter | Type | Description |
|---|---|---|
out | Float32Array | number[] | |
x | number | |
y | number | |
z | number | |
cssWidth | number | |
cssHeight | number |
More
For attaching DOM to a place in the world: a focus ring on a card, a label on a marker. The caller passes the CSS box rather than the drawing buffer, because the answer is for the document and the document does not know the device pixel ratio.
False rather than a coordinate when w <= 1e-6. The perspective divide by a negative w mirrors the point through the origin, so a thing behind the viewer reports a perfectly plausible position on the opposite side of the screen. A caller that placed an affordance there would be putting it on the wrong object, which is worse than putting it nowhere.
rayThrough
rayThrough(origin: Float32Array, direction: Float32Array, cssX: number, cssY: number, cssWidth: number, cssHeight: number): voidThe ray a pixel looks along, in world space.
| Parameter | Type | Description |
|---|---|---|
origin | Float32Array | |
direction | Float32Array | |
cssX | number | |
cssY | number | |
cssWidth | number | |
cssHeight | number |
More
Two points are unprojected rather than one, because a direction cannot be recovered from a single unprojected point without knowing where the eye is in the same space — and for an orthographic camera there is no single eye at all. Unprojecting both ends of the depth range and subtracting is correct for either projection, which is why it is written this way for a camera that is currently only perspective.
cssX/cssY are in the canvas's CSS box, so a caller hands over
event.clientX - rect.left and nothing has to know the device pixel ratio.