frame_bounds
Bounds union and exact frustum fitting — the math behind "put this subject in frame".
frame_bounds
Bounds union and exact frustum fitting — the math behind "put this subject in frame".
fit solves for the camera distance that keeps every corner of a box inside
the frustum at a given orbit angle. That is exact for any shape at any angle,
which a circumscribed sphere is not: a sphere frames the DIAGONAL of the box
from every direction, so a 14 x 0.5 x 14 plate backs the camera off nearly
1.4x further than its silhouette needs.
Two consumers share this one implementation: the capture toolbox's framed shots and editor frame-selection. Toolboxes are self-contained and never cross-require, so the math lives here as a module rather than inside either.
local FrameBounds = require("@builtin::modules.frame_bounds")
local box = FrameBounds.union({
FrameBounds.ofEntity(entity.find("player")),
FrameBounds.ofEntity(entity.find("prop")),
})
local framed = FrameBounds.fit(box, { fov = 60, aspect = 16 / 9, angle = { 0, 20 } })
-- framed.px/py/pz is where the camera goes, framed.cx/cy/cz is what it looks at.
Exports
FrameBounds.union(boxes) -> Aabb?— the AABB enclosing every box in the list.nilwhen the list is empty (or holds nothing withmin/max), which is the signal that there is nothing to frame.FrameBounds.ofEntity(target) -> Aabb?— world-space bounds of an entity and its descendants (hierarchyBounds, covering a character root plus its skinned mesh and bones), falling back to the entity's own meshbounds.nilwhen the target has no renderable geometry.FrameBounds.fit(box, opts?) -> Framed— a camera pose that framesbox.optsis{ fov = 60, aspect = 16/9, margin = 1.15, angle = { yawDeg, pitchDeg } };anglealso accepts{ yaw = , pitch = }. Returns{ px, py, pz, cx, cy, cz, distance, radius, fov, near, far, center, size }— camera position, look-at point (the box centre), the fitted distance, the bounding-sphere radius, geometry-derived near/far clip planes, and the box's centre and full size. A degenerate (point) box gets a short fixed distance so the camera is not sitting inside the subject.FrameBounds.fitDistance(hx, hy, hz, dx, dy, dz, tanH, tanV) -> number— the corner projection itself: the smallest distance along the orbit direction(dx, dy, dz)(subject toward camera, unit length) that keeps all eight corners of the half-extent box inside a frustum with half-angle tangentstanH/tanV.fitappliesmarginto this; call it directly when you already have a direction and want the raw fit.
Interface
What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.
conforms to
zero/source-extract/v2Bounds union and exact frustum fitting. `fit` solves for the camera distance that keeps every corner of a box inside the frustum at a given orbit angle. It is exact for any shape at any angle, which a circumscribed sphere is not: a sphere frames the DIAGONAL from every direction, so a 14 x 0.5 x 14 plate backs the camera off nearly 1.4x further than its silhouette needs. Two consumers: the capture toolbox's framed shots, and editor frame-selection. Toolboxes are self-contained and never cross-require, so the shared implementation lives here as a module rather than in either. Consumers: local FrameBounds = require("@builtin::modules.frame_bounds") local box = FrameBounds.union({ FrameBounds.ofEntity(entity.find("player")) }) local framed = FrameBounds.fit(box, { fov = 60, angle = { 0, 20 } })
union(boxes: { Aabb }) → Aabb
Union a list of AABBs into one.
| arg | type | description |
|---|---|---|
| boxes | { Aabb } | Array of `{ min = vec3, max = vec3 }`. |
examples
local u = FrameBounds.union({ a:hierarchyBounds(), b:hierarchyBounds() })ofEntity(target: any) → Aabb
World-space bounds of an entity and its descendants, falling back to the entity's own mesh bounds.
| arg | type | description |
|---|---|---|
| target | any | An entity proxy. |
examples
local b = FrameBounds.ofEntity(entity.find("player"))fitDistance(hx: number, hy: number, hz: number, dx: number, dy: number, dz: number, tanH: number, tanV: number) → number
Smallest distance along the orbit direction that keeps all eight corners of a half-extent box inside the frustum.
| arg | type | description |
|---|---|---|
| hx | number | Half-extent on X. |
| hy | number | Half-extent on Y. |
| hz | number | Half-extent on Z. |
| dx | number | Orbit direction X (subject toward camera, unit length). |
| dy | number | Orbit direction Y. |
| dz | number | Orbit direction Z. |
| tanH | number | Tangent of the half horizontal FOV. |
| tanV | number | Tangent of the half vertical FOV. |
examples
local d = FrameBounds.fitDistance(1, 1, 1, 0, 0.34, 0.94, 1.03, 0.58)
cameraAxes(dx: number, dy: number, dz: number) → CameraAxes
The camera's own forward / right / up for a view along an orbit direction, with right and up taken from the world up.
| arg | type | description |
|---|---|---|
| dx | number | Orbit direction X (subject toward camera, unit length). |
| dy | number | Orbit direction Y. |
| dz | number | Orbit direction Z. |
examples
local axes = FrameBounds.cameraAxes(0, 0.34, 0.94)
fitDistanceAxes(hx: number, hy: number, hz: number, axes: CameraAxes, tanH: number, tanV: number) → number
Smallest distance that keeps all eight corners of a half-extent box inside the frustum, projected onto explicitly-given camera axes. Callers that aim by a named station rather than an orbit angle pass their own axes; `fitDistance` derives them from a direction and calls this.
| arg | type | description |
|---|---|---|
| hx | number | Half-extent on the first extent axis. |
| hy | number | Half-extent on the second. |
| hz | number | Half-extent on the third. |
| axes | CameraAxes | `{ forward, right, up }`, in the SAME frame the half-extents are measured in. |
| tanH | number | Tangent of the half horizontal FOV. |
| tanV | number | Tangent of the half vertical FOV. |
examples
local d = FrameBounds.fitDistanceAxes(7, 0.25, 7, axes, 1.03, 0.58)
fit(box: Aabb, opts: FitOpts?) → Framed
Solve for a camera pose that frames `box` at the given orbit angle.
| arg | type | description |
|---|---|---|
| box | Aabb | The AABB to frame. |
| opts | FitOpts? | `{ fov, aspect, margin, angle = { yawDeg, pitchDeg } }`. |
examples
local f = FrameBounds.fit(b, { fov = 60, angle = { 0, 20 } })frameEditorCamera(refs: { any }) → boolean
Frame the EDITOR camera on a set of entity refs (`{ kind, id }`): fit the union of their renderable bounds at the camera's CURRENT view direction, so repeated framings keep the angle. Refs without geometry (lights, empty containers) frame their transforms. The one implementation behind the F key, the Hierarchy's Focus action, and a row double-click — a camera move over this module's own fit math, living here for the same reason `fit` does. Returns true when the camera moved.
| arg | type | description |
|---|---|---|
| refs | { any } |
Vec3 = { x: number, y: number, z: number }Aabb = { min: Vec3, max: Vec3 }CameraAxes = {FitOpts = {Framed = {Sub-parts
Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.
Problems
Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.
agent_score is exposed.+ quality × 0.35
+ performance × 0.25
± compat factor
Usability ratings
Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".
Scoped to this part · feeds back into the world's score.