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)positionsis an(S, 3)tensor or a sequence of triples, in metres.- Give exactly one strength:
rcs, the radar cross section in m², oramplitude, the dimensionless strengthsqrt(4π · rcs) / λ. Both describe the same quantity, so giving both or neither raisesValueError. - One
rcsapplies to every target. A per-target strength vector needs anAspectand must be given asamplitude;rcsmust stay a scalar. phaseis the scattering phase in radians, default0.0.trajectoryis an optional callable:trajectory(t)returns the same ordered(S, 3)positions at timetseconds. Use it for moving targets.idsfixes stable world IDs for the targets.Noneallocates 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)^naxis 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 moves | How you declare it |
|---|---|
| A target | PointTargets(trajectory=...) |
| A structure, rigidly | DynamicScene(scene, structure_trajectories={id: trajectory}) with a Core trajectory such as LinearTrajectory |
| A structure, deforming | DynamicScene(scene, structure_deformations={id: deformation}), for example SmplPoseDeformation |
| An antenna phase centre | DynamicScene(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.