Home / Docs / Placement and camera
One convention covers 2D and 3D, single objects and instances: position is the world position of the anchor, and width, height and depth are the target size — not scale factors. The anchor is the geometric centre of the object's own bounding box.
| Member | Meaning |
|---|---|
PosX / PosY / PosZ | World position of the anchor. Left-handed axes; +X is screen right with the default camera. |
Width / Height / Depth | Desired world size. The engine derives the scale from the asset's own bounds. They are float? — null means "not set yet". |
Rotation | Radians around Y on Model. Mesh3D and instance types take a quaternion. |
Alpha | Opacity, and the usual way to hide something without removing it. |
LocalSize | Read-only. The asset's own size before scaling, if you need to reason in its units. |
ComputedScale | Read-only. The per-axis scale the engine derived: target size divided by local size. |
AnchorLocal | Read-only. The anchor in model space — the centre of the raw bounding box. |
AnchorWorldOffset | Read-only. Use it when you need to place the model's origin instead of its centre; see below. |
The payoff is that you can size an imported asset without knowing how the artist exported it.
Width = 1f means one metre wide, whatever the GLB claims, and
two models from two artists placed side by side are actually the same height.
Every 3D control builds its matrix the same way, and the order is the whole point:
Matrix4x4.CreateTranslation(-AnchorLocal)
* Matrix4x4.CreateScale(ComputedScale)
* GetRotationMatrix()
* Matrix4x4.CreateTranslation(PosX, PosY, PosZ)
Under the row-vector convention that is
((p - A) * S) * R + Pos. The anchor
A lands exactly on
Pos, the rotation pivot is the anchor, and therefore rotating
an object changes neither its position nor its size. That property is what makes the object
editor in the reference application possible without any compensation maths.
If scale came first, evaluation would degrade to
(p * S - A) * R + Pos: the anchor would no longer land on
Pos and the world centre would drift by
A * (1 - S), so rendering, picking and highlight bounds
would quietly disagree with each other. The anchor was also a corner
(Min.X, Max.Y, Min.Z) until it was changed to the
bounding-box centre, because corner anchoring gave biased rotation pivots and made
placement hard to think about.
Sometimes you want the model's own origin pinned to a world point — a character
exported standing on the ground plane, for instance. The identity
p*S*R + t == ((p-A)*S)*R + Pos gives you the conversion
directly:
// Pin the model's local origin to a world-space target.
var target = new Vector3(4f, 0f, 12f);
var offset = robot.AnchorWorldOffset; // recompute each frame
robot.PosX = target.X + offset.X;
robot.PosY = target.Y + offset.Y;
robot.PosZ = target.Z + offset.Z;
Recompute the offset each frame rather than caching it: it already has the current scale and rotation folded in, so the identity keeps holding as the object changes. Set size and rotation first, then position.
Width,
Height or Depth that is null
or 0 is filled in from LocalSize, so a model with no size
set appears at its exported scale rather than collapsing to nothing.
GetWorldBounds() returns the conservative box used for
culling and shadows, inflated for skinned motion.
GetWorldBoundsRaw() matches the rendered body. Picking and
selection boxes use the raw one; using the conservative box for a selection box leaves
visible empty space above and below the model.
BaseApp.Camera is a single
Camera3D shared by every backend and every pass, and it is the
only source of truth for view, projection and frustum.
CameraPos and
CameraTarget forward to
Camera.Position and
Camera.Target.
CameraTarget = new Vector3(0f, 3.5f, -2.5f);
CameraPos = CameraTarget + new Vector3(0f, 0.5f, -3.5f);
Camera.FovY = MathF.PI / 4f; // radians
Camera.Near = 0.1f;
Camera.Far = 1300f;
| Member | Notes |
|---|---|
Position, Target, Up | Left-handed look-at. Writing an identical value does not dirty the matrices. |
FovY, Near, Far | Projection, changeable at runtime. Far also clamps how far the sun cascades reach. |
Aspect | Read-only; the engine supplies it from the surface each frame. |
View, Projection, ViewProjection | Rebuilt only when something changed, which is why touching the camera every frame is free. |
Frustum | What culling tests against. Available to you for your own coarse visibility checks. |
ProjectionJittered, PrevViewProjection, JitterPixels | Owned by temporal anti-aliasing. Read them if you are debugging TAA; do not write them. |
With CameraPos.Z negative the camera looks toward the origin
and world +X is screen right — the arrangement every sample uses, and the one the 2D
overlay coordinate system agrees with.