Visuals and collisions

A geometry is a simple shape, such as a box, sphere, or capsule, that represents an object’s physical extent. Viam uses one set of geometry types for two jobs: drawing a custom visual in the 3D scene, and telling the motion planner about an obstacle.

To create a custom visual, you attach a geometry to a transform’s physical_object field and provide that transform to a world state store service. This service holds the transforms you publish and streams them to the 3D scene. This page covers what a transform contains, the difference between planner geometry and visualization geometry, and how to build each geometry type.

Anatomy of a transform

A Transform carries four things that together place and style a visual:

  • Reference frame and pose: the visual’s origin.
  • Geometry (the physical_object field): the shape to draw (a box, sphere, capsule, mesh, or point cloud).
  • Metadata: styling such as color and opacity.
  • UUID: a stable identifier for this specific visual.

The reference frame and pose decide where, the geometry decides what shape, and the metadata decides how it looks.

The UUID gives a visual a stable identity

Each transform has a UUID. That identifier is what lets a module change one visual without disturbing the rest of the scene: to update a visual, the module re-sends a transform with the same UUID; to remove it, it references that UUID; to add a new one, it uses a fresh UUID. Without stable identifiers the client would have to re-render everything on every change. With them, the scene applies incremental add, update, and remove operations to individual visuals.

Metadata styles the visual

The metadata is a set of rendering attributes the scene reads when it draws the geometry:

  • colors: the fill color, or one color per point for point cloud geometry
  • opacities: how transparent the shape is
  • show_axes_helper: draws a coordinate triad at the visual’s origin
  • invisible: hides the visual by default

The scene reads only the keys it recognizes and ignores the rest. These are all visualization attributes: they control how the visual looks. The planner reads its solid geometry from the frame system and the WorldState you pass to Move, so metadata changes what you see without changing what the planner plans around.

The scene draws, the planner collision-checks

The geometry on a world state store transform renders in the 3D scene: publishing a box draws a box. The motion planner collision-checks a separate geometry, which it reads from two places:

  • The frame system: each component’s frame.geometry.
  • The WorldState you pass to a Move call: obstacles and transforms supplied for that single planning request.

Despite the similar names, these are different things: the world state store service holds transforms for the scene to draw, and the WorldState you pass to Move carries obstacles for the planner to avoid. A world state store transform and a WorldState obstacle travel two paths, each with its own job: one is drawn in the scene, the other is planned around. The same shape can take both paths.

Making a geometry both visible and collision-checked

If you want a geometry to appear in the scene and be avoided by the planner, you do both, separately:

  • For the scene: publish it as a transform through the world state store service (see Publish visuals from a module).
  • For planning: add it to the frame system, or include it in the WorldState you pass to Move.

Today these are two separate outputs you produce from the same source data: one transform for the scene, one geometry for the planner.

Geometry types

The supported types are:

  • box: dimensions in millimeters
  • sphere: a radius
  • capsule: a radius and length
  • mesh: an arbitrary triangle mesh
  • point cloud: a set of points

Choose the type that matches what you are representing: a box or capsule to approximate a physical object, a mesh for a precise model, a point cloud for sensor data. For guidance on matching a geometry type to a physical object, see the geometry types table.

You build a geometry as a Geometry proto, the same type a world state store transform and a WorldState obstacle both carry. The Python SDK and the Go SDK construct that proto directly, with no helper library. The box, sphere, and capsule primitives also have a machine config (JSON) form. The transform you attach the geometry to supplies its reference frame and pose.

For the constructor for each type, in JSON, Python, and Go, see Geometry constructors.

Assemble a transform

A geometry is only the shape. To render it as a visual, wrap it in a transform that carries the other anatomy fields: a reference frame and pose, metadata, and a UUID. The draw library assembles them for you, so you do not build the Transform proto by hand:

import (
    "github.com/golang/geo/r3"
    "github.com/viam-labs/motion-tools/draw"
    "go.viam.com/rdk/spatialmath"
)

// A red box at (400, 0, 200) in the world frame.
box, err := spatialmath.NewBox(
    spatialmath.NewZeroPose(), r3.Vector{X: 100, Y: 100, Z: 100}, "target-box",
)
if err != nil {
    return nil, err
}
drawn, err := draw.NewDrawnGeometry(
    box,
    draw.WithGeometryColor(draw.NewColor(draw.WithName("red"))), // metadata: color
)
if err != nil {
    return nil, err
}
transform, err := drawn.Draw(
    "target-box",             // the name of this visual's frame
    draw.WithID("target-box"), // UUID: a stable identity for later updates
    draw.WithParent("world"),  // reference frame the pose is expressed in
    draw.WithPose(spatialmath.NewPoseFromPoint(r3.Vector{X: 400, Y: 0, Z: 200})), // pose
)

Publish transform through a world state store service to draw it. For the full module that serves transforms this way, see Publish visuals from a module.

What’s next