Scenes and targets

How a WiTwin Core Scene supplies reflecting geometry, how PointTargets and StructureTargets declare scatterers, and how motion enters the world.

Radar separates two questions about the world. The Scene says which surfaces reflect the wave. The targets say where the scatterers are and how strongly they scatter. This page explains both and how they combine into round trips.

The Scene reflects#

You pass a witwin.core Scene (static) or DynamicScene (moving) to trace, simulate or stream. Its structures are meshes with materials. Channel uses them for line-of-sight occlusion and for reflections on each one-way leg.

import torch
from witwin.core import Mesh, PhysicalMaterial, Scene, Structure
 
wall = Mesh(
    vertices=torch.tensor([(-2.0, -2.0, -5.0), (2.0, -2.0, -5.0), (2.0, 2.0, -5.0), (-2.0, 2.0, -5.0)]),
    faces=torch.tensor([(0, 1, 2), (0, 2, 3)]),
    recenter=False, fill_mode="surface", topology_diagnostics=False,
)
scene = Scene(
    structures=(Structure(geometry=wall, material=PhysicalMaterial(name="concrete", eps_r=5.24, sigma_e=0.0462),
                          structure_id=1, material_id=1, assignment_id=1, surface_id=1),),
    endpoints=[],
)

Keep world coordinates

Pass recenter=False when you build a Mesh from vertices that are already in world coordinates. Otherwise Core moves the mesh to its centroid and your wall is no longer where you put it.

A Scene with no structures is valid: Scene(structures=(), endpoints=[]) gives free space, where only the direct round trips to each target exist.

The radar adds its own antennas to the world as Channel endpoints, so endpoints can be empty. You need Core endpoints only when antennas move independently of the radar pose; see Simulate moving scenes.

Targets scatter#

The targets argument is required. There is no default target set, and Radar does not place scatterers on meshes: it computes no surface samples, centroids or bounding-box centres. A mesh only reflects.

PointTargets#

PointTargets puts scatterers at world positions you name:

from witwin.radar import PointTargets
 
targets = PointTargets(positions=[(0.0, 0.0, -3.0), (0.5, 0.0, -3.0)], rcs=1.0)
  • positions is an (S, 3) tensor or a sequence of triples, in metres.
  • Give exactly one strength: rcs, the radar cross section in m², or amplitude, the dimensionless strength sqrt(4π · rcs) / λ. Both describe the same quantity, so giving both or neither raises ValueError.
  • One rcs applies to every target. A per-target strength vector needs an Aspect and must be given as amplitude; rcs must stay a scalar.
  • phase is the scattering phase in radians, default 0.0.
  • trajectory is an optional callable: trajectory(t) returns the same ordered (S, 3) positions at time t seconds. Use it for moving targets.
  • ids fixes stable world IDs for the targets. None allocates them.

Aspect-dependent scattering#

Without an Aspect, a target scatters equally in every direction. Aspect(axis, exponent, coherent_interval, phase_rate=0.0) makes the response depend on the incoming and outgoing directions:

response = amplitude · max(-dot(dir_in, axis), 0)^n · max(dot(dir_out, axis), 0)^n

axis is one unit vector per target (the direction of strongest return), exponent (n, at least 1) sets how narrow the lobe is, and coherent_interval (seconds) is how long one aspect realization stays coherent.

StructureTargets#

StructureTargets puts one scatterer at the world anchor that Core publishes for each structure with rigid motion. Use it to attach a scatterer to a moving object without computing its position yourself.

from witwin.radar import StructureTargets
 
targets = StructureTargets(rcs=0.5, structure_ids=(7,))

A structure without rigid motion has no anchor, so StructureTargets on a static Scene raises NotImplementedError. structure_ids=None selects every structure in the snapshot, ordered by ascending structure ID.

How round trips are formed#

For each transmitter, target and receiver, Radar asks Channel for the one-way legs transmitter to target and target to receiver, through the components you request (los, reflections). It then joins every pair of legs into a round trip and multiplies by the target's scatter response and the antenna pattern gain.

With reflections=1 and one wall, a single target produces several round trips: direct out and back, out by the wall and back direct, out direct and back by the wall, and by the wall both ways. The Point target in front of a wall tutorial shows the resulting multipath peak.

Received amplitude follows the radar equation. For an isotropic target at range R, the round-trip amplitude in sqrt(W) is sqrt(P_tx) · λ / (4π R) · amplitude · λ / (4π R), which equals sqrt(P_tx · λ² · rcs / ((4π)³ R⁴)).

Motion in the world#

Four things can move during a simulation, and each one makes the default motion sampling evaluate the world inside every frame:

What movesHow you declare it
A targetPointTargets(trajectory=...)
A structure, rigidlyDynamicScene(scene, structure_trajectories={id: trajectory}) with a Core trajectory such as LinearTrajectory
A structure, deformingDynamicScene(scene, structure_deformations={id: deformation}), for example SmplPoseDeformation
An antenna phase centreDynamicScene(scene, endpoint_trajectories={antenna_id: trajectory}) plus endpoints=SensorEndpointIds(...)

See Motion sampling for how often the world is evaluated, and Simulate moving scenes for code.