API Reference · Scene and camera

Node

Namespace: Ava3D

public class Node

A node in the scene graph: a transform, and children that inherit it.

Nodes are owned by the UI thread. Every mutation bumps the owning Scene's version, which is how the renderer — running on the render thread — learns that its snapshot is stale. That is the whole threading contract: mutate freely on the UI thread, never touch a node from anywhere else.

Properties

MemberDescription
BoundingBox Bounds { get; }

This node's extent plus every descendant's, in its parent's coordinate space.

NodeCollection Children { get; }

Child nodes. Adding one attaches it to this node's scene.

IEnumerable<Node> Descendants { get; }

Every node beneath this one, depth first, children in the order they were added. Not this node.

Lazy, so a search that stops early stops walking.

Nullable<Text.Json.JsonElement> Extras { get; set; }

What a glTF file's extras said about this node, when it came from one — the slot authoring tools and pipelines use for what the format has no field for, such as a game's own tags. Null for a node built by hand or a file that wrote none. The scene graph never looks at it.

bool IsVisible { get; set; }

Whether this node and its children are drawn.

BoundingBox LocalBounds { get; }

Local-space extent of this node alone, ignoring children. Empty unless overridden.

Matrix4x4 LocalTransform { get; set; }

The node's transform. Assigning it overrides position/rotation/scale wholesale, which is what a glTF node wants — those arrive as a matrix, and decomposing and recomposing only loses precision. Setting any of the three components afterwards takes control back.

string Name { get; set; }

Optional name, for debugging and for identifying a picked node.

Node Parent { get; }

The node this one hangs from, or null for a root.

Vector3 Position { get; set; }

Translation relative to the parent.

int RenderOrder { get; set; }

Which pass this node is drawn in. Lower draws first; zero is the default and negatives are allowed.

It overrides everything else about ordering, which is the point: a sky sphere at −10 is behind the scene however far away it actually is, and an additive shell at +1 is in front of the body it wraps however their origins compare. Within one value the renderers draw all opaque geometry first, then blended geometry back to front by the distance from the camera to each node's origin.

Nodes that set nothing all share order 0 and keep the order they were added in.

Quaternion Rotation { get; set; }

Rotation relative to the parent. See Node.RotationDegrees for an Euler-angle alternative.

Vector3 RotationDegrees { get; set; }

The rotation as yaw (Y), pitch (X) and roll (Z) in degrees. Converts both ways, so reading it after setting Node.Rotation gives angles that reproduce that rotation — though not necessarily the same triple, since several map to one orientation.

Vector3 Scale { get; set; }

Scale relative to the parent. Uniform scale is cheapest; the renderers do not build an inverse transpose for normals.

Scene Scene { get; }

The scene this node belongs to, or null while it is detached.

BoundingBox WorldBounds { get; }

This node's extent plus every descendant's, in world spaceNode.Bounds is the same thing in the parent's space. This is the one to hand Camera.Fit for a node partway down a graph; Node.Bounds is right only for a root.

Vector3 WorldPosition { get; set; }

Where this node's origin sits in world space.

Assigning it sets Node.Position to whatever puts the origin there, so a node can be placed in world terms without unpicking its parents — which is what a camera following it, or a shot aimed at it, actually wants. A node holding an explicit Node.LocalTransform keeps it and has only its translation replaced, rather than losing the rotation and scale that came with the matrix.

When an ancestor scales to zero the parent transform cannot be inverted and the assignment is ignored: every local position maps to the same world point, so there is no answer to give.

Matrix4x4 WorldTransform { get; }

Node.LocalTransform with every ancestor's applied: the matrix that takes a point from this node's own space to world space.

Walks up to the root on each read rather than caching, because a cache would have to be invalidated by every ancestor's every move and the graphs a viewer control draws are shallow. The one case where that is the wrong trade is reading it for every node in the graph every frame, and the renderer does not — it accumulates downward as it walks, which is what Scene.EnumerateMeshes exposes if you need the same thing.

Methods

MemberDescription
Node Find(string name)

The first descendant whose Node.Name is name, or null. Depth first.

This node's own name is not considered. A search that can return the thing you searched from is a search every caller has to guard, and the loaders that make names worth searching give the root one too.

T Find<T>(string name)

The first descendant of type T named name, or null.

A node whose name matches but whose type does not is skipped rather than ending the search, because a glTF file that gives a group and the mesh inside it the same name is common and should not defeat Find<MeshNode>.

void LookAt(Vector3 target, Nullable<Vector3> up = default)

Turns this node so its nose — −Z, as Rotations.LookAlong defines it — points at target, keeping its top as near up as that allows.

target is in world space, the same space as Node.WorldPosition and therefore the space the thing being aimed at is already known in. Any rotation the parents apply is solved out, so a turret bolted to a hull that is itself rolling still ends up looking at what it was told to look at. Node.Rotation, which this writes, stays what it has always been: relative to the parent.

Aiming at the node's own position does nothing rather than snapping to identity — there is no direction in it, and leaving the last good attitude alone is what a tracker that briefly overflies its target wants.

A node holding an explicit Node.LocalTransform keeps its scale and translation and has only its rotation replaced, the same bargain Node.WorldPosition makes — so a glTF node, which arrives as a matrix, can be re-aimed without losing where the file put it. A sheared matrix cannot be taken apart that way, and there the components take over as they would after any other assignment to Node.Rotation.

int Query(in BoundingBox region, List<ValueTuple<MeshNode, Matrix4x4>> into)

Every mesh beneath this node whose world bounds meet region, with the world transform each was found under.

The broadphase, and it is here because the renderer needed it anyway. Frustum culling asks exactly this question against six planes rather than a box, and picking asks it against a ray — so the walk existed three times over before it existed once. It is on this list because both consumers wrote a fourth: the demo keeps every solid surface as "the box the renderer already computed for culling" and EliteQuest walks its wall list the same way, both to stop somebody walking through a wall.

What this is not. Collision. There is no sweep here, no normal, no penetration depth and no response — those are a physics engine's job and this library is not one. This answers "what is near enough to be worth looking at properly", which is the half that needs the scene graph, and leaves the half that needs a simulation to whoever is running one.

Results are appended to into, which is cleared first. A list rather than an enumerable because the calling shape is a game asking every frame, and a lazy walk allocating an enumerator per node is what SceneSnapshot.Build already measured and stopped doing.

region

A box in world space.

into

Where to put the answers. Cleared, then filled.

returns

How many were found, which is also into.Count.

int Query(Vector3 origin, Vector3 direction, List<ValueTuple<MeshNode, Matrix4x4, float>> into)

Every mesh beneath this node whose world bounds the ray enters, nearest first, with how far along the ray each was met.

The rejection half of picking, exposed so that a caller can do the other half itself — a game that wants the nearest wall, or the three things under the cursor rather than the one. Ava3DView.Pick is this followed by a triangle test.

The distance is to the bounding box, not to the surface. A caller wanting the surface has to intersect the geometry, which is the expensive half and is why this one is separate.

origin

Where the ray starts, in world space.

direction

Which way it goes. Need not be unit length.

into

Where to put the answers. Cleared, then filled, then sorted by distance.

returns

How many were found.

See also