Transform3D

A transform between two 3D spaces, i.e. a pose.

From the point of view of the entity's coordinate system, all components are applied in the inverse order they are listed here. E.g. if both a translation and a max3x3 transform are present, the 3x3 matrix is applied first, followed by the translation.

Whenever you log this archetype, it will write all components, even if you do not explicitly set them. This means that if you first log a transform with only a translation, and then log one with only a rotation, it will be resolved to a transform with only a rotation.

For transforms that affect only a single entity and do not propagate along the entity tree refer to archetypes.InstancePoses3D.

Components components

Optional: Translation3D, RotationAxisAngle, RotationQuat, Scale3D, TransformMat3x3, TransformRelation, AxisLength

Shown in shown-in

Examples examples

Variety of 3D transforms variety-of-3d-transforms

"""Log different transforms between three arrows."""

from math import pi

import rerun as rr
from rerun.datatypes import Angle, RotationAxisAngle

rr.init("rerun_example_transform3d", spawn=True)

arrow = rr.Arrows3D(origins=[0, 0, 0], vectors=[0, 1, 0])

rr.log("base", arrow)

rr.log("base/translated", rr.Transform3D(translation=[1, 0, 0]))
rr.log("base/translated", arrow)

rr.log(
    "base/rotated_scaled",
    rr.Transform3D(
        rotation=RotationAxisAngle(axis=[0, 0, 1], angle=Angle(rad=pi / 4)),
        scale=2,
    ),
)
rr.log("base/rotated_scaled", arrow)

Transform hierarchy transform-hierarchy

"""Logs a transforms transform hierarchy."""

import numpy as np
import rerun as rr
import rerun.blueprint as rrb

rr.init("rerun_example_transform3d_hierarchy", spawn=True)

# One space with the sun in the center, and another one with the planet.
rr.send_blueprint(
    rrb.Horizontal(rrb.Spatial3DView(origin="sun"), rrb.Spatial3DView(origin="sun/planet", contents="sun/**"))
)

rr.set_time_seconds("sim_time", 0)

# Planetary motion is typically in the XY plane.
rr.log("/", rr.ViewCoordinates.RIGHT_HAND_Z_UP, static=True)

# Setup points, all are in the center of their own space:
# TODO(#1361): Should use spheres instead of points.
rr.log("sun", rr.Points3D([0.0, 0.0, 0.0], radii=1.0, colors=[255, 200, 10]))
rr.log("sun/planet", rr.Points3D([0.0, 0.0, 0.0], radii=0.4, colors=[40, 80, 200]))
rr.log("sun/planet/moon", rr.Points3D([0.0, 0.0, 0.0], radii=0.15, colors=[180, 180, 180]))

# Draw fixed paths where the planet & moon move.
d_planet = 6.0
d_moon = 3.0
angles = np.arange(0.0, 1.01, 0.01) * np.pi * 2
circle = np.array([np.sin(angles), np.cos(angles), angles * 0.0]).transpose()
rr.log("sun/planet_path", rr.LineStrips3D(circle * d_planet))
rr.log("sun/planet/moon_path", rr.LineStrips3D(circle * d_moon))

# Movement via transforms.
for i in range(0, 6 * 120):
    time = i / 120.0
    rr.set_time_seconds("sim_time", time)
    r_moon = time * 5.0
    r_planet = time * 2.0

    rr.log(
        "sun/planet",
        rr.Transform3D(
            translation=[np.sin(r_planet) * d_planet, np.cos(r_planet) * d_planet, 0.0],
            rotation=rr.RotationAxisAngle(axis=(1, 0, 0), degrees=20),
        ),
    )
    rr.log(
        "sun/planet/moon",
        rr.Transform3D(
            translation=[np.cos(r_moon) * d_moon, np.sin(r_moon) * d_moon, 0.0],
            from_parent=True,
        ),
    )

Update a transform over time update-a-transform-over-time

"""
Update a transform over time.

See also the `transform3d_column_updates` example, which achieves the same thing in a single operation.
"""

import math

import rerun as rr


def truncated_radians(deg: float) -> float:
    return float(int(math.radians(deg) * 1000.0)) / 1000.0


rr.init("rerun_example_transform3d_row_updates", spawn=True)

rr.set_time_sequence("tick", 0)
rr.log(
    "box",
    rr.Boxes3D(half_sizes=[4.0, 2.0, 1.0], fill_mode=rr.components.FillMode.Solid),
    rr.Transform3D(clear=False, axis_length=10),
)

for t in range(100):
    rr.set_time_sequence("tick", t + 1)
    rr.log(
        "box",
        rr.Transform3D(
            clear=False,
            translation=[0, 0, t / 10.0],
            rotation_axis_angle=rr.RotationAxisAngle(axis=[0.0, 1.0, 0.0], radians=truncated_radians(t * 4)),
        ),
    )

Update a transform over time, in a single operation update-a-transform-over-time-in-a-single-operation

"""
Update a transform over time, in a single operation.

This is semantically equivalent to the `transform3d_row_updates` example, albeit much faster.
"""

import math

import rerun as rr


def truncated_radians(deg: float) -> float:
    return float(int(math.radians(deg) * 1000.0)) / 1000.0


rr.init("rerun_example_transform3d_column_updates", spawn=True)

rr.set_time_sequence("tick", 0)
rr.log(
    "box",
    rr.Boxes3D(half_sizes=[4.0, 2.0, 1.0], fill_mode=rr.components.FillMode.Solid),
    rr.Transform3D(clear=False, axis_length=10),
)

rr.send_columns(
    "box",
    indexes=[rr.TimeSequenceColumn("tick", range(1, 101))],
    columns=rr.Transform3D.columns(
        translation=[[0, 0, t / 10.0] for t in range(100)],
        rotation_axis_angle=[
            rr.RotationAxisAngle(axis=[0.0, 1.0, 0.0], radians=truncated_radians(t * 4)) for t in range(100)
        ],
    ),
)

Update specific properties of a transform over time update-specific-properties-of-a-transform-over-time

"""Update specific properties of a transform over time."""

import math

import rerun as rr


def truncated_radians(deg: float) -> float:
    return float(int(math.radians(deg) * 1000.0)) / 1000.0


rr.init("rerun_example_transform3d_partial_updates", spawn=True)

# Set up a 3D box.
rr.log(
    "box",
    rr.Boxes3D(half_sizes=[4.0, 2.0, 1.0], fill_mode=rr.components.FillMode.Solid),
    rr.Transform3D(clear=False, axis_length=10),
)

# Update only the rotation of the box.
for deg in range(46):
    rad = truncated_radians(deg * 4)
    rr.log(
        "box",
        rr.Transform3D.from_fields(
            rotation_axis_angle=rr.RotationAxisAngle(axis=[0.0, 1.0, 0.0], radians=rad),
        ),
    )

# Update only the position of the box.
for t in range(51):
    rr.log(
        "box",
        rr.Transform3D.from_fields(translation=[0, 0, t / 10.0]),
    )

# Update only the rotation of the box.
for deg in range(46):
    rad = truncated_radians((deg + 45) * 4)
    rr.log(
        "box",
        rr.Transform3D.from_fields(
            rotation_axis_angle=rr.RotationAxisAngle(axis=[0.0, 1.0, 0.0], radians=rad),
        ),
    )

# Clear all of the box's attributes, and reset its axis length.
rr.log(
    "box",
    rr.Transform3D.from_fields(clear_unset=True, axis_length=15),
)