Skip to content

adjacency

Adjacency

Bases: TensorizedRelativeState

Pairwise adjacency state.

S = number of scenes N = number of objects with Adjacency state VALUES has shape (S, N, N, 18) bool: +Z, -Z, then the positive and negative directions of the eight unique legacy horizontal axes. VALUES[s, a, b, k] is True iff object b is adjacent to object a in direction k (from a's AABB center).

Diagonal and cross-scene cells are always False. Cloth is excluded via is_compatible (no collision mesh to ray-cast against).

Source code in OmniGibson/omnigibson/object_states/adjacency.py
class Adjacency(TensorizedRelativeState):
    """
    Pairwise adjacency state.

    S = number of scenes
    N = number of objects with Adjacency state
    VALUES has shape (S, N, N, 18) bool: +Z, -Z, then the positive and negative
    directions of the eight unique legacy horizontal axes. VALUES[s, a, b, k] is True iff
    object b is adjacent to object a in direction k (from a's AABB center).

    Diagonal and cross-scene cells are always False.
    Cloth is excluded via is_compatible (no collision mesh to ray-cast against).
    """

    # Wp kernel inputs (single source of truth).
    _aabb_obj_idxs = None  # wp.array (N_adj,) int32
    _link_to_obj_idx = None  # wp.array (L_total,) int32
    _link_to_scene_idx = None  # wp.array (L_total,) int32
    _directions = None  # wp.array2d (18, 3) float32
    _max_distances = None  # wp.array (18,) float32
    _output = None  # wp.array4d (S, N_adj, N_adj, 18) int32 — atomic_max target

    @classproperty
    def value_shape(cls):
        return (_ADJ_AXIS_COUNT,)

    @classproperty
    def value_type(cls):
        return th.bool

    @classproperty
    def value_name(cls):
        return "adjacency"

    @classmethod
    def get_dependencies(cls):
        deps = super().get_dependencies()
        deps.add(AABB)
        return deps

    @classmethod
    def is_compatible(cls, obj, **kwargs):
        compatible, reason = super().is_compatible(obj, **kwargs)
        if not compatible:
            return compatible, reason
        # Cloth has no collision_mesh_cpu_data — exclude as both origin and target.
        # TODO: revisit when cloth gains a queryable mesh proxy.
        if obj.prim_type == PrimType.CLOTH:
            return False, "Adjacency does not support cloth objects"
        return True, None

    @classmethod
    def global_initialize(cls):
        super().global_initialize()
        # Build the constant axis tables (directions + per-axis max distance).
        directions_cpu, max_distances_cpu = _build_adjacency_axis_tables()
        cls._directions = lazy.isaacsim.core.utils.warp.tensor.create_tensor_from_list(
            directions_cpu, "float32", device="cuda"
        )
        cls._max_distances = lazy.isaacsim.core.utils.warp.tensor.create_tensor_from_list(
            max_distances_cpu, "float32", device="cuda"
        )

    @classmethod
    def initialize_view(cls):
        super().initialize_view()

        S = len(cls.IDX_OBJS)
        N = len(cls.OBJ_IDXS)

        # Build aabb_obj_idxs: maps Adjacency-N → AABB-N (or -1 if unknown to AABB).
        if N > 0 and AABB.OBJ_IDXS is not None:
            aabb_obj_idxs_cpu = th.full((N,), -1, dtype=th.int32)
            for rel_path, adj_idx in cls.OBJ_IDXS.items():
                aabb_obj_idxs_cpu[adj_idx] = AABB.OBJ_IDXS.get(rel_path, -1)
            cls._aabb_obj_idxs = lazy.isaacsim.core.utils.warp.tensor.create_tensor_from_list(
                aabb_obj_idxs_cpu, "int32", device="cuda"
            )
        else:
            cls._aabb_obj_idxs = None

        # Build link_to_obj_idx / link_to_scene_idx tables, length = N_links_total in
        # RigidBodyViewAPI (whether or not those links belong to Adjacency-tracked objects).
        # Untracked link slots stay at -1; the kernel skips them.
        if RigidBodyViewAPI._PATH_TO_IDX:
            L_total = len(RigidBodyViewAPI._PATH_TO_IDX)
            link_to_obj_cpu = th.full((L_total,), -1, dtype=th.int32)
            link_to_scene_cpu = th.full((L_total,), -1, dtype=th.int32)
            for s_idx, scene_row in enumerate(cls.IDX_OBJS):
                for adj_idx, obj in enumerate(scene_row):
                    if obj is None:
                        continue
                    for link in obj.links.values():
                        flat_idx = RigidBodyViewAPI.get_flat_idx(link.prim_path)
                        if flat_idx is None:
                            continue
                        link_to_obj_cpu[flat_idx] = adj_idx
                        link_to_scene_cpu[flat_idx] = s_idx
            cls._link_to_obj_idx = lazy.isaacsim.core.utils.warp.tensor.create_tensor_from_list(
                link_to_obj_cpu, "int32", device="cuda"
            )
            cls._link_to_scene_idx = lazy.isaacsim.core.utils.warp.tensor.create_tensor_from_list(
                link_to_scene_cpu, "int32", device="cuda"
            )
        else:
            cls._link_to_obj_idx = None
            cls._link_to_scene_idx = None

        # Allocate the int32 scratch (S, N, N, K) used as the atomic_max target.
        if S > 0 and N > 0:
            cls._output = wp.zeros((S, N, N, _ADJ_AXIS_COUNT), dtype=wp.int32, device="cuda")
        else:
            cls._output = None

    @classmethod
    def _update_values(cls, values):
        # All required handles must be live; otherwise nothing to do this step.
        if (
            cls._output is None
            or cls.VALUES_WP is None
            or AABB.VALUES_WP is None
            or cls._aabb_obj_idxs is None
            or RigidBodyViewAPI.LINK_MESH_IDS is None
            or RigidBodyViewAPI.POSE_MATRICES is None
            or cls._link_to_obj_idx is None
            or cls._link_to_scene_idx is None
        ):
            return

        S = values.shape[0]
        N = values.shape[1]
        K = _ADJ_AXIS_COUNT
        L = RigidBodyViewAPI.LINK_MESH_IDS.shape[0]
        if S == 0 or N == 0 or L == 0:
            return

        # 1. Zero the int32 scratch via CUDA memset (graph-capturable for contiguous wp.array).
        cls._output.zero_()

        # 2. Ray-cast against each link's mesh; atomic_max into scratch on hit.
        wp.launch(
            kernel=_adjacency_ray_cast_kernel,
            dim=(S, N, L, K),
            inputs=[
                AABB.VALUES_WP,
                cls._aabb_obj_idxs,
                cls._directions,
                cls._max_distances,
                RigidBodyViewAPI.LINK_MESH_IDS,
                RigidBodyViewAPI.POSE_MATRICES,
                cls._link_to_obj_idx,
                cls._link_to_scene_idx,
                cls._output,
            ],
            device="cuda",
        )

        # 3. Convert int32 scratch → uint8 bool VALUES.
        wp.launch(
            kernel=_adjacency_finalize_kernel,
            dim=(S, N, N, K),
            inputs=[cls._output, cls.VALUES_WP],
            device="cuda",
        )