Skip to content

AcresSensors.h#

Acres/Source/Acres/AcresSensors.h Generated

Synthetic sensor episode recorder for the ACRE tractor: truth, actions, GNSS, IMU, LiDAR, camera and CAN.

The 120 Hz physics step is the clock. Truth, actions, IMU, GNSS and CAN rows are made on the physics thread (Unreal's Chaos physics runs on its own thread; AsyncPhysicsTickActor in AcresVehicle.cpp calls PhysicsSample once per fixed step). LiDAR scans and camera captures need the Unreal world and the renderer, so they run on the game thread (Unreal's main thread, where actors tick) at a pose stamped by the physics thread. Heavy work (PLY/PNG encoding, the camera sensor model) runs on Unreal's thread pool, and every text row goes through one bounded writer thread (an FRunnable, Unreal's thread-body interface) into <root>/episode-NNN/<stream>.jsonl.

With a live stream attached (SetStream, AcresSensorStream.h: -SensorStream=) the same INS epochs, organized LiDAR clouds and processed camera images also go to a local client (the ROS 2 bridge), with or without the files (SetFileOutput).

Output frames are ENU (east, north, up) for world quantities and body FLU (forward, left, up) for sensor quantities, in SI units. Unreal itself uses centimetres and a left-handed world with X east, Y south, Z up in this project; the recorder converts at the boundary. Nothing here applies forces to the vehicle.

The per-sensor models live in AcresGnssModel.h (receiver), AcresLidarModel.h (radiometry and returns), AcresLidarGpu.h (GPU ray tracing) and AcresCameraModel.h (lens and image sensor). Configuration comes from Content/Simulation/sensors.json plus -Sensor* command-line flags. All noise is seeded, so an episode can be reproduced exactly from its seed.

Name Type Unit Default Description
UsSurveyFootM inline constexpr double m 1200. / 3937. Length of one US survey foot in metres (exactly 1200/3937).

IndianaWestFeetToGeographic#

Converts EPSG:2968 grid coordinates (NAD83(HARN) / Indiana West, US survey feet) to geographic coordinates.

Inverse Transverse Mercator on the GRS80 ellipsoid using the Snyder (1987) series, lat0 37.5 deg, lon0 -87.0833 deg, k0 0.999966667, false easting 900000 m, false northing 250000 m. Sub-millimetre this close to the central meridian.

Argument Description
EastingFt Grid easting in US survey feet.
NorthingFt Grid northing in US survey feet.
LatitudeDeg Output NAD83(HARN) latitude in degrees.
LongitudeDeg Output NAD83(HARN) longitude in degrees (negative west).
void IndianaWestFeetToGeographic(double EastingFt, double NorthingFt, double& LatitudeDeg, double& LongitudeDeg);

GeographicToIndianaWestFeet#

Forward EPSG:2968 projection: NAD83(HARN) latitude/longitude to Indiana West grid feet (Snyder 1987 series).

Argument Description
LatitudeDeg NAD83(HARN) latitude in degrees.
LongitudeDeg NAD83(HARN) longitude in degrees (negative west).
EastingFt Output grid easting in US survey feet.
NorthingFt Output grid northing in US survey feet.
void GeographicToIndianaWestFeet(double LatitudeDeg, double LongitudeDeg, double& EastingFt, double& NorthingFt);

WriteBytes#

Writes a byte buffer to a file, replacing it. Used for the PLY/PNG sensor files and the camera self-test.

Argument Description
Path Destination file.
Data Bytes to write.
Bytes Number of bytes.

Returns: True if the file was opened and written without error.

Note

Thread-safe (called from thread-pool tasks).

bool WriteBytes(const FString& Path, const void* Data, int64 Bytes);

IsTreeActor#

Whether an actor holds trees (instanced tree meshes whose crowns the LiDAR and GNSS proxies model): the woodland plantings (tag "woodland"), the surveyed single trees (REF_ACRE_Planting_MeasuredTrees: tag "ACRETree") and, for levels saved before those tags, any vegetation actor (tag "ACREVegetation", not a trunk-collision "ACREObstacle", shrub or crop actor) whose instanced meshes are tree assets (/Game/ACRE/Trees/). The actor's name is not used: in a cooked game it is the class name plus a number (BP_ReferenceInstances_C_2), not the editor label.

Argument Description
Actor Any actor (null is not a tree).

Returns: True for a tree-holding actor.

bool IsTreeActor(const AActor* Actor);

FAcresPcg32#

struct FAcresPcg32

Reproducible random numbers: PCG32 (PCG-XSH-RR, O'Neill 2014) plus Box-Muller Gaussians.

Every noise source in the recorder owns one of these, seeded from sensors.json "seed" so an episode can be replayed bit for bit.

Example

FAcresPcg32 Rng;
Rng.Seed(42 + 1009, 2);
const double Noise = 0.03 * Rng.Gaussian();
Name Type Unit Default Description
State uint64 0 64-bit LCG state.
Inc uint64 0 Stream increment (always odd); different increments give independent streams.

FAcresPcg32::Seed#

Seeds the generator the way the PCG reference pcg32_srandom_r does.

Argument Description
InitState Starting state (the recorder uses seed + k * 1009 per stream).
InitSeq Stream selector; the increment becomes 2 * InitSeq + 1.
void Seed(uint64 InitState, uint64 InitSeq);

FAcresPcg32::Next#

Returns the next 32-bit output and advances the state.

uint32 Next();

FAcresPcg32::Uniform#

Uniform double in (0, 1), never exactly 0 or 1, so log(Uniform()) is safe.

double Uniform() ;

FAcresPcg32::Gaussian#

Standard normal draw (mean 0, sigma 1) from the cosine branch of Box-Muller; consumes two uniforms.

double Gaussian();

FAcresSensorConfig#

struct FAcresSensorConfig

All recorder settings, loaded from Content/Simulation/sensors.json and the -Sensor* command line.

Units follow the suffix: Hz, seconds (S), metres (M), m/s2, rad/s, degrees (Deg), pixels. Extrinsics are FLU metres from the chassis reference origin (the actor origin, not the centre of mass).

Example

const FAcresSensorConfig Config = FAcresSensorConfig::Load();
auto Recorder = MakeShared<FAcresSensorRecorder>(Pawn, Body, Farm, Ignored, Config);
Name Type Unit Default Description
Seed int 42 Master seed for every PCG32 stream (GNSS, IMU, LiDAR, camera noise, GNSS constellation).
DelayS double s .05 Modelled transport latency added to every delayed stream (GNSS, IMU, LiDAR, camera), seconds.
Gnss, Imu, Lidar, Camera, Can bool Per-stream enable switches (sensors.json "<stream>.enabled", or -SensorNo<Stream>).
GnssHz, ImuHz, LidarHz, CameraHz, CanHz double Sample rates in Hz, each 1-120. The default camera rate matches the Polaris recording profile.
GnssStdM, AccelStdMps2, GyroStdRadps, AccelBiasMps2, GyroBiasRadps, LidarStdM double GnssStdM: legacy GNSS noise field, kept in the config export but unused (the receiver model sets the noise). AccelStdMps2 / GyroStdRadps: IMU white noise per axis per sample (m/s2, rad/s). AccelBiasMps2 / GyroBiasRadps: sigma of the constant per-episode IMU bias draw (m/s2, rad/s). LidarStdM: LiDAR range-noise floor, metres (the radiometric model adds low-SNR noise on top).
LidarRangeM double m 60 Maximum range, m (1-200).
LidarElevationMinDeg double ° -20 Elevation of the lowest and highest ring.
LidarElevationMaxDeg double ° 10 See above.
LidarColumns int 180 Azimuth steps over 360 deg (4-720).
LidarRings int 8 Elevation rings, evenly spaced (1-32).
CameraWidth, CameraHeight int Delivered camera image size in pixels (64-4096 x 64-2160).
CameraHfovDeg double ° 90 Pinhole-equivalent horizontal field of view that defines fx, degrees (20-120).
CameraExposure FString TEXT("volume") Exposure mode for the scene capture. "volume" leaves exposure to the level's post-process volume (the look of the review camera). "physical" fixes EV100 = log2(N^2 / t * 100 / ISO) like a real camera, from CameraFstop, CameraShutterS and CameraIso. "copy" copies the highest-priority unbound post-process volume onto the capture component. Load applies the -SensorCameraExposure= override, so this is always the mode in use.
CameraFstop double 8 Aperture N.
CameraShutterS double s 1. / 333 Exposure time, s (also the dark-current integration time).
CameraIso double 100 ISO; gain in the sensor model = ISO / base ISO.
CameraTemporal bool true Temporal history for the camera render. Lumen GI and sky occlusion, TSR anti-aliasing and volumetric clouds all accumulate over frames. A lone on-demand capture has no history (black shadows, smeared clouds), so with this on the capture renders on several frames, like the main view. Costs extra scene renders (see CameraWarmupFrames).
CameraWarmupFrames int 2 With temporal history on: render the capture only on the N frames leading up to each sample (Lumen and TSR improve with more history). 0 = render every frame. Clamped to 0-16. The default uses two renders per sample at 10 Hz, matching Polaris; settings can trade additional GPU time for more temporal convergence.
CameraModel FAcresCameraModelConfig Lens, rolling shutter and photometric post-processing (AcresCameraModel.h).
LidarModel FAcresLidarModelConfig Intensity, multi-return and dropout model (AcresLidarModel.h).
GnssModel FAcresGnssModelConfig RTK fix states, Gauss-Markov errors, sky obstruction and datum chain (AcresGnssModel.h).
GnssFlu, ImuFlu, LidarFlu, CameraFlu FVector Sensor mounting points in body FLU metres from the chassis reference origin (each within 10 m). With MountFrame "base_footprint" the file gives them from the vehicle's base_footprint (the rear-axle midpoint on the ground, as the vehicle's URDF) and Load adds BaseFootprintFlu.
LidarRpyDeg, CameraRpyDeg FVector Mount rotations of the LiDAR and the camera (lidar.rpy_deg, camera.rpy_deg): roll, pitch, yaw, deg, the ROS convention (fixed axes x, y, z of the body FLU frame, R = Rz(yaw) Ry(pitch) Rx(roll), positive pitch looks down). Zero: the sensor frame is parallel to the body.
MountFrame FString TEXT("chassis") "chassis" (default) or "base_footprint": where the mounting points in the file are measured from.
BaseFootprintFlu FVector FVector::ZeroVector The vehicle's base_footprint in the chassis FLU frame, m (the pawn's rear-axle midpoint on the ground; set by Load from its argument). The INS reports the pose of this point.
LidarElevationsDeg TArray&lt;double> ° LiDAR beam table (lidar.elevations_deg): the elevation of every ring, deg, in the sensor's ring order (the column order of an organized cloud). Empty: LidarRings rings evenly spaced from min to max. The RoboSense Helios table of the Purdue Ranger comes from its recorded clouds (sensors_polaris.json).
LidarClockwise bool false Column direction (lidar.azimuth_order): false "ccw" (column 0 forward, counter-clockwise, the default) or true "cw" (column k at -360 k / columns deg, the row order of rslidar_sdk's organized clouds).
LidarAzimuthOffsetsDeg TArray&lt;double> ° Horizontal offset of every ring from its column's azimuth (lidar.azimuth_offsets_deg), deg counter-clockwise, in ring order; empty = none. The Helios' two laser banks sit +3.9..+5.1 deg (even rings) and -3.6..-5.0 deg (odd).
LidarLensCenterM double m 0 Lens centre distance from the spin axis (lidar.lens_center_m), m: each beam starts there, at the column's azimuth, and points are written as rslidar_sdk does (range along the beam plus the lens offset). 0 = on the axis.
LidarSelfReturns FString The vehicle seen by its own LiDAR (lidar.self_returns): a file of 4 x columns x rings float32 (per cell: the probability that the beam returns from the body, the body's range, m, and intensity, and the probability that the body blocks the beam without a return), relative to Content/Simulation. Empty = none (the vehicle's meshes are skipped by the traces).
LidarDelayS double s -1 Delivery delay of LiDAR rows (lidar.delay_s), s; negative = delay_s. The Helios reaches the bag 11-12 ms after its stamp.
LidarOutput FString TEXT("ply") Point files (lidar.output): "ply" (default), "pcd" (an organized PCD laid out as the vehicle's PointCloud2: HEIGHT = columns, WIDTH = rings, x y z intensity float32, NaN where a beam has no return) or "both".
LidarFrameId FString TEXT("lidar") Frame id of the LiDAR's clouds (lidar.frame_id; "lidar" on the Ranger).
Ins bool false INS (OxTS-like) stream, "ins" block: enabled, rate, Hz; reported point (ins.position_flu_m: the INS output point, base_footprint on the Ranger); position error as a Gauss-Markov process (sigma, m; correlation time, s) plus white noise, m; heading and roll / pitch errors, deg (1 sigma, Gauss-Markov with the same time); white velocity, acceleration and angular-rate noise (m/s, m/s2, rad/s); latitude / longitude datum ("nad83" = NAD83(2011) as an RTK fix, or "wgs84_g2139"); UTM zone of the odometry.
SourcePath FString The sensors.json actually loaded.
SourceSha1 FString SHA-1 of its text, written to episode.json.
ProfileName FString TEXT("tractor-default") ProfileName: the sensor profile the files name ("profile" of sensors.json or the vehicle overlay: tractor-default, polaris). bProfileValidated: the profile's camera and LiDAR were validated against the real sensors ("profile_validated"; the Polaris: Calibration/Polaris/camera_results.md, lidar_results.md).
ProfileChanges TArray&lt;FString> Camera and LiDAR settings that differ from the profile as shipped (Content/Simulation/sensors.json plus the overlay, no command line), as JSON paths such as "camera.width" or "camera.model.noise": what the menu's session file or command-line flags changed. Empty when the camera and LiDAR record the profile's data.

FAcresSensorConfig::Load#

Loads the configuration, applies command-line overrides and validates it.

Reads Content/Simulation/sensors.json (or the file given by -SensorConfig=), merges a vehicle overlay on top (every key of the overlay replaces the base's, objects merged key by key; the Polaris uses sensors_polaris.json), then applies -SensorSeed=, -SensorDelay=, -Sensor<Stream>Hz=, -SensorLidarColumns=/Rings=/Range=, -SensorCameraWidth=/Height=/Hfov=, -SensorCameraWarmup=, -SensorCameraNoHistory, -SensorCameraExposure=, -SensorNo<Stream>, -SensorNoNoise (no noise in any sensor, camera included) and the model-specific flags of the camera, LiDAR and GNSS configs.

Argument Description
OverlayFile Vehicle overlay JSON (empty = none).
BaseFootprintFlu The vehicle's base_footprint in the chassis FLU frame, m (for MountFrame "base_footprint").

Returns: The validated configuration.

Note

Uses checkf (Unreal's assert with a message; active in the Development builds this project packages), so a broken or out-of-range file stops the game instead of recording a bad episode. ProfileChanges is filled by comparing DataSignature with that of the same overlay on the shipped sensors.json without the command line.

static FAcresSensorConfig Load(const FString& OverlayFile = FString(), const FVector& BaseFootprintFlu = FVector::ZeroVector);

FAcresSensorConfig::DataSignature#

The camera and LiDAR settings that define their data (resolution, rate, mount, lens and photometry, capture strategy and exposure; beam table, geometry, radiometry and returns), without the on/off switches and the point-file format.

Returns: {"camera": {...}, "lidar": {...}}.

TSharedRef<FJsonObject> DataSignature() const;

FAcresSensorConfig::ToJson#

Serialises the configuration (including the three model blocks) as compact JSON for sensors-config.json and the "config" block of episode.json.

Returns: One JSON object as a string.

FString ToJson() const;

FAcresSensorPhysicsSample#

struct FAcresSensorPhysicsSample

One snapshot of the chassis, as handed to the recorder.

On the physics thread every field is filled once per 120 Hz step. On the game thread (GameTick) only PhysicsTimeS, Step, PositionCm, Rotation and VelocityMps are filled, from the latest published telemetry. Positions are Unreal world centimetres (X east, Y south, Z up); velocities are Unreal world axes in m/s.

Name Type Unit Default Description
Dt double 0 Physics step, s (1/120).
PhysicsTimeS double s 0 The run's physics clock, s.
EpisodeTimeS double s 0 The vehicle's own episode clock, s (restarts on a vehicle reset).
Step uint64 0 Physics step counter.
ResetEpoch uint64 0 Increments when the vehicle is reset (the IMU restarts its finite differences on a change).
PositionCm FVector cm FVector::ZeroVector Actor origin, Unreal world cm.
VelocityMps FVector m/s FVector::ZeroVector Chassis linear velocity, Unreal world axes, m/s.
AngularRadps FVector rad/s FVector::ZeroVector Chassis angular velocity, Unreal world axes, rad/s.
CenterOfMassCm FVector cm FVector::ZeroVector Centre of mass offset from the actor origin in the body frame, cm.
Rotation FQuat FQuat::Identity Chassis orientation, body to Unreal world.
Rpm double 0 Engine speed, rev/min.
SteerRad double rad 0 Actual road-wheel steer angle, rad.
SteerRequestRad double rad 0 Requested steer angle, rad. Throttle,
Throttle double 0 See above.
Brake double 0 Pedal commands 0-1.
Direction double 1 +1 forward, -1 reverse.
MassKg double kg 0 Chassis mass, kg.
FarmTimeS double s 0 Farm simulation clock, s.
BunkerKg double kg 0 Harvested grain on board, kg.
Gear int 0 Selected gear, 1-based.
ControlSource const TCHAR* TEXT("manual") Who is driving: "manual", "replay", "scripted_test", "route_review" or "benchmark".
Wheels std::array&lt;AcresSim::FWheelState, 4> Per-wheel state from the vehicle model (contact, normal load, slip, sinkage, surface).

FAcresSensorRecorder#

class FAcresSensorRecorder final : public FRunnable

Records one sensor episode at a time for the tractor.

AAcresVehiclePawn creates one recorder in BeginPlay with FAcresSensorConfig::Load(). Start (game thread, F6 or -SensorRecord) opens <root>/episode-NNN/; after that the pawn calls PhysicsSample from its fixed-step physics callback and GameTick from its per-frame Tick. Finish (game thread, F6 again or destruction) drains everything and writes the final episode.json.

The recorder is itself the writer thread's body (FRunnable::Run). Rows are formatted where they are produced, held in a latency buffer until their available time, and then queued to the writer. Queues are bounded; anything dropped is counted in episode.json "dropped" instead of stretching time.

Example

auto Recorder = MakeShared<FAcresSensorRecorder>(this, Body, Farm, {this}, FAcresSensorConfig::Load());
const FString Error = Recorder->Start(FPaths::ProjectSavedDir() / TEXT("sensors"));
// ... physics thread: Recorder->PhysicsSample(Sample); game thread: Recorder->GameTick(Latest);
Recorder->Finish();
Name Type Unit Default Description
Config const FAcresSensorConfig The configuration this recorder was built with (read-only).

FAcresSensorRecorder::FAcresSensorRecorder#

Builds the recorder. Does not start recording.

Also self-checks the frame conversion, reads the georeference origin from Content/Simulation/ACRE/site.json and loads the 4 m ground-cover raster (field-grid.json + cover.u8) used for LiDAR ground materials.

Argument Description
Owner The tractor actor; owns the camera capture component and supplies the world.
Body The chassis physics component the camera is attached to and reads angular velocity from.
Farm Shared farm state (crop proxies for the CPU LiDAR, water grid for wetness). May be null.
IgnoredActors Actors the LiDAR must see through (the tractor and its trailer).
Config Settings; copied into the public Config member.
FAcresSensorRecorder(AActor* Owner, UPrimitiveComponent* Body, TSharedPtr<FAcresFarmRuntime> Farm, const TArray<AActor*>& IgnoredActors, const FAcresSensorConfig& Config);

FAcresSensorRecorder::~FAcresSensorRecorder#

Calls Finish, then waits for any thread-pool task still using the recorder, so a recording in progress is closed cleanly and nothing touches the object after it is freed.

~FAcresSensorRecorder();

FAcresSensorRecorder::Start#

Starts a new episode.

Creates <Root>/episode-NNN (NNN one above the highest existing number) with camera/ and lidar/ subfolders, opens the seven .jsonl streams, writes sensors-config.json, seeds the RNG streams, draws the IMU bias, gathers LiDAR and GNSS obstacle proxies, creates the camera capture and render target, starts the writer thread and writes an initial episode.json with state "recording".

Argument Description
Root Parent folder for episodes (default <vehicle output>/sensors, or -SensorOutput=).

Returns: Empty string on success, otherwise a short error message.

Note

Game thread only.

FString Start(const FString& Root);

FAcresSensorRecorder::Finish#

Stops the episode and flushes everything.

Waits for an in-progress physics sample, finishes the pending camera readback, runs the LiDAR scans that were already stamped, waits for every PLY/PNG encode (no timeout), delivers still-delayed rows with "pending_at_stop":true, stops the writer thread, closes files and writes the final episode.json. Does nothing if not recording.

Note

Game thread only. Blocks the game thread while it drains.

void Finish();

FAcresSensorRecorder::IsRecording#

True between Start and Finish. Safe from any thread.

bool IsRecording() const ;

FAcresSensorRecorder::IsIdle#

True when nothing that fell due is still in flight: no LiDAR scan queued, on the GPU or being encoded, no camera image due, rendering, reading back or being processed. Lockstep (AcresSimControl.h) waits for this before it answers a step, so every sensor output of the steps has gone to the stream. Game thread.

bool IsIdle() const;

FAcresSensorRecorder::SensorProfile#

The episode's sensor profile: "<profile>-calibrated" (a validated profile recorded as validated, with the calibrated sensor render profile), "<profile>" (an unvalidated one such as tractor-default), "<...>-reduced-6gb" (recorded with the low tier's reduced sensor render profile, AcresRenderTier.h) or "custom" (camera or LiDAR settings changed from the profile, or the pinned render settings overridden on the console).

FString SensorProfile() const;

FAcresSensorRecorder::SetMountOffsets#

Hardware shift of the sensor mounts (AcresSimControl.h set_vehicle_shift): offsets added to the camera and LiDAR mount positions (FLU, m) and roll / pitch / yaw (deg) of sensors.json. Scans and images taken from now on use them; the stream's hello keeps the calibrated mounts (as a drifted sensor's description would). Game thread.

void SetMountOffsets(const FVector& CameraM, const FVector& CameraRpyDeg, const FVector& LidarM, const FVector& LidarRpyDeg);

FAcresSensorRecorder::PhysicsSample#

Records one physics step.

Writes a truth and an actions row every step, then, when due: a GNSS epoch, an IMU sample, a LiDAR scan request (queued for the game thread) and two CAN frames. Finally delivers delayed rows whose available time has passed.

Argument Description
S Fully filled physics sample for this step.

Note

Physics thread, once per fixed step. time_s in the rows is (steps since Start) x Dt.

void PhysicsSample(const FAcresSensorPhysicsSample& S);

FAcresSensorRecorder::GameTick#

Per-frame work on the game thread: camera capture scheduling and readback, finishing GPU LiDAR scans and starting the next queued LiDAR scan.

Argument Description
Latest The latest physics telemetry (pose, velocity, step and physics time only).

Note

Game thread, once per frame.

void GameTick(const FAcresSensorPhysicsSample& Latest);

FAcresSensorRecorder::StatusLabel#

One-line status for the HUD, e.g. "Recording episode-003 | 12.5 s | truth 1500 imu ...".

FString StatusLabel() const;

FAcresSensorRecorder::CopyLivePoints#

Copies the latest scan's points for display.

Argument Description
Out Receives the points (sensor FLU metres).
RangeM Receives the configured LiDAR range, m, for scaling the view.

Note

Thread-safe (takes a lock); the points are replaced by a thread-pool task after each scan is encoded.

void CopyLivePoints(TArray<FLivePoint>& Out, double& RangeM) const ;

FAcresSensorRecorder::CopyLiveCamera#

Copies the latest camera image if it is newer than the caller's copy (compares Frame). Thread-safe.

Argument Description
InOut The caller's copy; replaced when a newer image exists.

Returns: True when InOut was replaced.

bool CopyLiveCamera(FLiveImage& InOut) const ;

FAcresSensorRecorder::SetStream#

Also sends every INS epoch, LiDAR cloud and camera image to a local client (AcresSensorStream.h). Call before Start; null = no stream.

void SetStream(TSharedPtr<FAcresSensorStream> InStream) ;

FAcresSensorRecorder::SetFileOutput#

Episode files on (default) or off. Off, the recorder runs for the stream only: no folder, no .jsonl rows, no PLY/PCD/PNG files and no episode.json. Call before Start.

void SetFileOutput(bool bInFiles) ;

FAcresSensorRecorder::StreamHello#

The stream's Hello JSON: sensor rates, the LiDAR layout, the camera's output intrinsics and distortion, and the mount poses relative to base_footprint (FLU m, ROS roll/pitch/yaw deg). Call after Start (the camera model exists then).

Argument Description
AgentName The agent's name.
Vehicle "maxxum" or "polaris".

Returns: One JSON object as a string.

FString StreamHello(const FString& AgentName, const FString& Vehicle) const;

FLivePoint#

struct FLivePoint

One point of the latest LiDAR scan for the live HUD view.

Name Type Unit Default Description
X, Y, Z float Point position in the sensor FLU frame, metres (x forward, y left, z up).
Class uint8 Semantic class id (see episode.json lidar.classes).
Intensity uint8 Calibrated reflectivity byte 0-255.

FLiveImage#

struct FLiveImage

The latest camera image for the live HUD view: the processed sensor image (distortion, noise) at half resolution.

Name Type Unit Default Description
Width, Height int Width, Height: pixels. Frame: capture counter (changes with every new image; 0 = none yet).