Source code for simvx.core.nodes_2d.remote_transform

"""RemoteTransform2D -- Pushes transform to a remote node."""

from __future__ import annotations

import logging

from ..descriptors import Property
from ..math.types import Vec2
from .node2d import Node2D

log = logging.getLogger(__name__)


[docs] class RemoteTransform2D(Node2D): """Pushes this node's transform to a remote Node2D each frame. Useful for making a node follow another without parenting, or for synchronising transforms across different branches of the scene tree. Set ``remote_path`` to a node path (e.g. ``'../Camera2D'``). Each frame the resolved node's position/rotation/scale are overwritten with this node's values, according to the ``update_*`` flags. """ remote_path = Property("", hint="Path to the target node (e.g. '../Camera2D')") update_position = Property(True, hint="Push position to remote node") update_rotation = Property(True, hint="Push rotation to remote node") update_scale = Property(False, hint="Push scale to remote node") use_global_coordinates = Property(True, hint="Use global transforms instead of local") def __init__(self, **kwargs): super().__init__(**kwargs) self._cached_remote: Node2D | None = None self._cached_path: str = "" def _resolve_remote(self) -> Node2D | None: """Resolve remote_path to a Node2D, caching the result.""" path = self.remote_path if not path: self._cached_remote = None self._cached_path = "" return None if path == self._cached_path and self._cached_remote is not None: # Verify the cached node is still in the tree if self._cached_remote._tree is not None: return self._cached_remote try: node = self.get_node(path) except (ValueError, KeyError, AttributeError): if path != self._cached_path: log.warning("RemoteTransform2D '%s': invalid remote_path '%s'", self.name, path) self._cached_remote = None self._cached_path = path return None if not isinstance(node, Node2D): if path != self._cached_path: log.warning("RemoteTransform2D '%s': target '%s' is not a Node2D", self.name, path) self._cached_remote = None self._cached_path = path return None self._cached_remote = node self._cached_path = path return node
[docs] def process(self, dt: float): remote = self._resolve_remote() if remote is None: return if self.use_global_coordinates: if self.update_position: remote.world_position = self.world_position if self.update_rotation: # Node2D has no world_rotation setter -- compute local rotation parent_rot = ( remote.parent.world_rotation if remote.parent and isinstance(remote.parent, Node2D) else 0.0 ) remote.rotation = self.world_rotation - parent_rot if self.update_scale: # Node2D has no world_scale setter -- compute local scale if remote.parent and isinstance(remote.parent, Node2D): ps = remote.parent.world_scale remote.scale = Vec2( self.world_scale.x / ps.x if ps.x != 0 else 1.0, self.world_scale.y / ps.y if ps.y != 0 else 1.0, ) else: remote.scale = Vec2(self.world_scale) else: if self.update_position: remote.position = Vec2(self.position) if self.update_rotation: remote.rotation = self.rotation if self.update_scale: remote.scale = Vec2(self.scale)