Sample a Finite-Width Beam#

Sample several directions around each transmitter’s forward axis, then optionally connect those paths to neighboring receivers. Ray sampling and receiver connectivity can be changed independently.

Before you start. Author the base and depth patterns from Sample a Depth Grid. The Gaussian USD Python tab modifies those patterns; the bloom tab modifies the Gaussian result.

Directivity and ray sampling#

A directivity diagram shows how emission varies with direction. Use its angular shape to choose where to concentrate ray directions. With the same ray count, a narrow distribution concentrates samples near the forward axis; a broad distribution spreads them across a wider angular region. In the model, author that distribution as a ray bundle, then reuse it at each transmitter pose in the selected trace definition.

Broad and narrow angular shapes beside equal-count ray bundles. The narrow bundle concentrates its equal-length rays closer to the forward axis.

These azimuth sections relate smooth angular shapes to discrete ray directions. Both bundles contain the same number of rays; narrowing the shape moves samples toward the forward axis. The shapes and ray lengths are schematic, not calibrated gain or propagation distance.#

Choose a Gaussian bundle for a concentration around the forward axis with independent horizontal and vertical spreads. Use Free rays to specify another set of directions explicitly, or a spherical Grid pattern for structured angular samples. The figure relates a directional shape to sampling density. It does not assign a gain value to each direction: the model’s ray inputs describe geometry and firing times, without antenna-gain tables or individual ray power weights.

After launch, each sampled ray can produce paths and contributions at scene hits and connected receivers. The bundle determines the emission directions; receiver mapping determines the candidate receiving elements.

Gaussian ray sampling#

Use a Gaussian bundle when each transmitter should emit many rays around its forward axis. One stored bundle is reused in the frame of every transmitter element in the selected pattern.

Choose the sample count and angular spreads separately. More rays add samples and output slots. Wider angular spreads change the directions sampled; they do not change the number of rays. Keep omni:sensor:GenericWpm:sourceDivergence separate from these choices; its meaning is described under propagation controls.

Replace the depth example’s single ray with nine Gaussian rays per transmitter to sample a finite angular spread. Seed 7 makes the sampling repeatable; 1 degree horizontal and 0.5 degree vertical standard deviations make the spread wider horizontally. With zero bloom, 15 * 9 = 135 slots are allocated.

The override clears the inherited Free-ray data because that family is no longer present in ray:patternTypes:

#usda 1.0
(
    defaultPrim = "World"
    metersPerUnit = 1
    upAxis = "Z"
    timeCodesPerSecond = 10
    subLayers = [@generic_wpm_depth.usda@]
)

over "World"
{
    over "Sensor"
    {
        token[] omni:sensor:GenericWpm:ray:patternTypes = ["gaussian"]
        # Clear the inherited Free family when replacing its ray bundle.
        uint[] omni:sensor:GenericWpm:freePatterns:ray:numElementsPerPattern = []
        float3[] omni:sensor:GenericWpm:freePatterns:ray:positions = []
        float4[] omni:sensor:GenericWpm:freePatterns:ray:orientations = []
        uint[] omni:sensor:GenericWpm:gaussianPatterns:ray:numElements = [9]
        uint[] omni:sensor:GenericWpm:gaussianPatterns:ray:seeds = [7]
        float[] omni:sensor:GenericWpm:gaussianPatterns:ray:stdDevsHorizontal = [1]
        float[] omni:sensor:GenericWpm:gaussianPatterns:ray:stdDevsVertical = [0.5]
        float3[] omni:sensor:GenericWpm:gaussianPatterns:ray:positions = [(0, 0, 0)]
        float4[] omni:sensor:GenericWpm:gaussianPatterns:ray:orientations = [(0, 0, 0, 1)]
        # Uniform firing offset from each transmitter, not propagation time.
        uint[] omni:sensor:GenericWpm:gaussianPatterns:ray:globalTimeOffsetsNs = [0]
    }
}
sensor = stage.GetPrimAtPath("/World/Sensor")
ns = "omni:sensor:GenericWpm:"
sensor.GetAttribute(ns + "ray:patternTypes").Set(["gaussian"])
# Clear the inherited Free family when replacing its ray bundle.
for name in ("numElementsPerPattern", "positions", "orientations"):
    sensor.GetAttribute(ns + "freePatterns:ray:" + name).Set([])
ray = ns + "gaussianPatterns:ray:"
sensor.CreateAttribute(ray + "numElements", Sdf.ValueTypeNames.UIntArray, custom=False).Set([9])
sensor.CreateAttribute(ray + "seeds", Sdf.ValueTypeNames.UIntArray, custom=False).Set([7])
sensor.CreateAttribute(ray + "stdDevsHorizontal", Sdf.ValueTypeNames.FloatArray, custom=False).Set([1])
sensor.CreateAttribute(ray + "stdDevsVertical", Sdf.ValueTypeNames.FloatArray, custom=False).Set([0.5])
sensor.CreateAttribute(ray + "positions", Sdf.ValueTypeNames.Float3Array, custom=False).Set([(0, 0, 0)])
sensor.CreateAttribute(ray + "orientations", Sdf.ValueTypeNames.Float4Array, custom=False).Set([(0, 0, 0, 1)])
# Uniform firing offset from each transmitter, not propagation time.
sensor.CreateAttribute(ray + "globalTimeOffsetsNs", Sdf.ValueTypeNames.UIntArray, custom=False).Set([0])

Setting both spreads to zero still generates nine coincident rays. The number of samples and their angular spread are independent of sourceDivergence.

Receiver bloom#

Add bloom when a path should contribute to neighboring grid receivers as well as its paired receiver. Radius 1 reserves a 3 by 3 receiver neighborhood for each ray and increases capacity to 15 * 9 * 9 = 1,215 slots. It does not increase the emitted ray count or move the receivers. Choose bloom to expand the set of receiver connections while keeping the sampled emission directions. This layer changes only that radius:

#usda 1.0
(
    defaultPrim = "World"
    metersPerUnit = 1
    upAxis = "Z"
    timeCodesPerSecond = 10
    subLayers = [@generic_wpm_gaussian.usda@]
)

over "World"
{
    over "Sensor"
    {
        # Radius one allocates nine receiver slots per ray; edge slots can be invalid.
        uint[] omni:sensor:GenericWpm:tx:mappingBloomFactors = [1]
    }
}
sensor = stage.GetPrimAtPath("/World/Sensor")
# Radius one allocates nine receiver slots per ray; edge slots can be invalid.
sensor.GetAttribute("omni:sensor:GenericWpm:tx:mappingBloomFactors").Set([1])

One recorded run of the enclosure produced 819 valid slots. Clipped neighbors at the grid edges retain their positions in the dense layout. The exact valid count is an observation of that scene, not a general guarantee.

Attribute meanings are on USD Attributes; complete types and defaults are in Sensor API Schemas.

Verification#

  1. Compare accepted capacity before and after each edit: the depth pattern has 15 slots, nine rays raise it to 135, and radius-one bloom raises it to 1,215.

  2. Bloom must leave raysPerTx unchanged.

  3. Set both Gaussian spreads to zero to confirm that nine coincident rays remain allocated.

  4. Read only valid slots when comparing sampled directions.

Potential issues#

Use the Generic wpm checks for confirming checks and recovery.

  • A family replacement is rejected. Inherited arrays for the removed family must be cleared. Refer to array requirements.

  • Bloom is rejected. The two grids may have different shapes or a wrapping receiver grid may be too narrow. Refer to receiver mapping.