Skip to content

Waypoint

Module: terminaltexteffects.engine.motion

Bases: Generic[PointT]

A Waypoint comprises an identifier, a position, and, optionally, bezier control point(s).

Positions are in the moving node's parent's frame. On the text plane a Coord is a cell, and means Vec3(column, row, 0); a Vec3 can add a height toward the camera.

A waypoint (or a bezier control) can also be a node, such as another character or a body: a live target. A path reads a live target's world position again every tick, so the mover heads for where it is now and arrives on it exactly when the path finishes, however it moved.

A waypoint made by Path.walk is an amount instead: coord is Vec3(columns, rows, 0), and where the walk ends is worked out from the previous waypoint when the path is activated.

Attributes:

Name Type Description
waypoint_id str

Unique identifier for the waypoint.

coord Coord | Vec3 | SpatialNode

Position of the waypoint as given, or the node it homes in on.

bezier_control tuple[Coord | Vec3 | SpatialNode, ...] | None

Optional bezier control point(s). Defaults to None.

point Vec3

coord as a Vec3. For a live target, where it was last read, in the mover's frame. For a walk, where it ended on the last activation.

Source code in terminaltexteffects/engine/motion.py
@dataclass(frozen=True)
class Waypoint(typing.Generic[PointT]):
    """A Waypoint comprises an identifier, a position, and, optionally, bezier control point(s).

    Positions are in the moving node's parent's frame. On the text plane a `Coord` is a cell, and means
    `Vec3(column, row, 0)`; a `Vec3` can add a height toward the camera.

    A waypoint (or a bezier control) can also be a node, such as another character or a body: a *live target*.
    A path reads a live target's world position again every tick, so the mover heads for where it is now and
    arrives on it exactly when the path finishes, however it moved.

    A waypoint made by `Path.walk` is an amount instead: `coord` is `Vec3(columns, rows, 0)`, and where the walk ends
    is worked out from the previous waypoint when the path is activated.

    Attributes:
        waypoint_id (str): Unique identifier for the waypoint.
        coord (Coord | g3.Vec3 | SpatialNode): Position of the waypoint as given, or the node it homes in on.
        bezier_control (tuple[Coord | g3.Vec3 | SpatialNode, ...] | None): Optional bezier control point(s).
            Defaults to None.
        point (g3.Vec3): `coord` as a `Vec3`. For a live target, where it was last read, in the mover's frame. For a
            walk, where it ended on the last activation.

    """

    waypoint_id: str
    coord: PointT
    bezier_control: tuple[Point | SpatialNode, ...] | None = None
    _xyz: XYZ = field(init=False, repr=False, compare=False)
    _curve_controls: tuple[XYZ | SpatialNode, ...] | None = field(init=False, repr=False, compare=False)
    _target: SpatialNode | None = field(init=False, repr=False, compare=False)
    _live: bool = field(init=False, repr=False, compare=False)
    _walk: tuple[float, float] | None = field(init=False, repr=False, compare=False)

    def __post_init__(self) -> None:
        """Keep the position as floats too, for stepping."""
        # Written to __dict__ directly: the dataclass is frozen, and this runs for every waypoint.
        attributes = self.__dict__
        coord = self.coord
        target = None
        if type(coord) is Coord:
            attributes["_xyz"] = (coord.column, coord.row, 0.0)
        elif isinstance(coord, SpatialNode):
            target = coord
            attributes["_xyz"] = (0.0, 0.0, 0.0)  # read when the path is activated
        else:
            attributes["_xyz"] = _as_xyz(coord)
        attributes["_target"] = target
        attributes["_walk"] = None
        controls = self.bezier_control
        if controls:
            curve = tuple(c if isinstance(c, SpatialNode) else _as_xyz(c) for c in controls)
            attributes["_curve_controls"] = curve
            attributes["_live"] = target is not None or any(isinstance(c, SpatialNode) for c in curve)
        else:
            attributes["_curve_controls"] = None
            attributes["_live"] = target is not None

    @property
    def point(self) -> g3.Vec3:
        """`coord` as a `Vec3`. For a live target, where it was last read, in the mover's frame."""
        return g3.Vec3(*self._xyz)

point property

coord as a Vec3. For a live target, where it was last read, in the mover's frame.

__post_init__()

Keep the position as floats too, for stepping.

Source code in terminaltexteffects/engine/motion.py
def __post_init__(self) -> None:
    """Keep the position as floats too, for stepping."""
    # Written to __dict__ directly: the dataclass is frozen, and this runs for every waypoint.
    attributes = self.__dict__
    coord = self.coord
    target = None
    if type(coord) is Coord:
        attributes["_xyz"] = (coord.column, coord.row, 0.0)
    elif isinstance(coord, SpatialNode):
        target = coord
        attributes["_xyz"] = (0.0, 0.0, 0.0)  # read when the path is activated
    else:
        attributes["_xyz"] = _as_xyz(coord)
    attributes["_target"] = target
    attributes["_walk"] = None
    controls = self.bezier_control
    if controls:
        curve = tuple(c if isinstance(c, SpatialNode) else _as_xyz(c) for c in controls)
        attributes["_curve_controls"] = curve
        attributes["_live"] = target is not None or any(isinstance(c, SpatialNode) for c in curve)
    else:
        attributes["_curve_controls"] = None
        attributes["_live"] = target is not None