FERS 0.1.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
world.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2//
3// Copyright (c) 2006-2008 Marc Brooker and Michael Inggs
4// Copyright (c) 2008-present FERS Contributors (see AUTHORS.md).
5//
6// See the GNU GPLv2 LICENSE file in the FERS project root for more information.
7
8/**
9 * @file world.cpp
10 * @brief Implementation of the World class for the radar simulation environment.
11 */
12
13#include "world.h"
14
15#include <algorithm>
16#include <iomanip>
17#include <limits>
18#include <optional>
19#include <sstream>
20#include <stdexcept>
21#include <unordered_map>
22#include <utility>
23
25#include "core/sim_events.h"
26#include "core/sim_id.h"
27#include "parameters.h"
28#include "radar/radar_obj.h"
29#include "signal/radar_signal.h"
31#include "timing/timing.h"
32
35using radar::Platform;
36using radar::Receiver;
37using radar::Target;
40
41namespace core
42{
43 namespace
44 {
45 std::vector<ActiveStreamingSource> transmitterDechirpSources(const Transmitter* const tx)
46 {
47 std::vector<ActiveStreamingSource> sources;
48 if (tx->getSchedule().empty())
49 {
51 if (source.segment_start < source.segment_end)
52 {
53 sources.push_back(source);
54 }
55 return sources;
56 }
57
58 for (const auto& period : tx->getSchedule())
59 {
60 auto source = makeActiveSource(tx, period.start, std::min(params::endTime(), period.end));
61 if (source.segment_start < source.segment_end && source.segment_end > params::startTime())
62 {
63 sources.push_back(source);
64 }
65 }
66 return sources;
67 }
68
69 std::vector<ActiveStreamingSource> waveformDechirpSources(const RadarSignal* const waveform,
70 const Receiver* const rx)
71 {
72 std::vector<ActiveStreamingSource> sources;
73 if (rx->getSchedule().empty())
74 {
76 if (source.segment_start < source.segment_end)
77 {
78 sources.push_back(source);
79 }
80 return sources;
81 }
82
83 for (const auto& period : rx->getSchedule())
84 {
85 auto source =
86 makeActiveSourceFromWaveform(waveform, period.start, std::min(params::endTime(), period.end));
87 if (source.segment_start < source.segment_end && source.segment_end > params::startTime())
88 {
89 sources.push_back(source);
90 }
91 }
92 return sources;
93 }
94
95 void validateDechirpTransmitter(const Transmitter* const tx, const std::string& owner)
96 {
97 if (tx == nullptr)
98 {
99 throw std::runtime_error(owner + " references a missing dechirp transmitter.");
100 }
101 if (tx->getMode() != radar::OperationMode::FMCW_MODE || tx->getSignal() == nullptr ||
102 !tx->getSignal()->isFmcwFamily())
103 {
104 throw std::runtime_error(owner + " dechirp reference transmitter '" + tx->getName() +
105 "' must be an FMCW transmitter with an FMCW waveform.");
106 }
107 }
108 }
109
110 void World::add(std::unique_ptr<Platform> plat) noexcept { _platforms.push_back(std::move(plat)); }
111
112 void World::add(std::unique_ptr<Transmitter> trans) noexcept
113 {
114 _transmitters_by_name[trans->getName()] = trans.get();
115 _transmitters.push_back(std::move(trans));
116 }
117
118 void World::add(std::unique_ptr<Receiver> recv) noexcept { _receivers.push_back(std::move(recv)); }
119
120 void World::add(std::unique_ptr<Target> target) noexcept { _targets.push_back(std::move(target)); }
121
122 void World::add(std::unique_ptr<RadarSignal> waveform)
123 {
124 const SimId id = waveform->getId();
125 if (_waveforms.contains(id))
126 {
127 throw std::runtime_error("A waveform with the ID " + std::to_string(id) + " already exists.");
128 }
129 _waveform_ids_by_name[waveform->getName()] = id;
130 _waveforms[id] = std::move(waveform);
131 }
132
133 void World::add(std::unique_ptr<Antenna> antenna)
134 {
135 const SimId id = antenna->getId();
136 if (_antennas.contains(id))
137 {
138 throw std::runtime_error("An antenna with the ID " + std::to_string(id) + " already exists.");
139 }
140 _antennas[id] = std::move(antenna);
141 }
142
143 void World::add(std::unique_ptr<PrototypeTiming> timing)
144 {
145 const SimId id = timing->getId();
146 if (_timings.contains(id))
147 {
148 throw std::runtime_error("A timing source with the ID " + std::to_string(id) + " already exists.");
149 }
150 _timings[id] = std::move(timing);
151 }
152
154 {
155 const auto it = _waveforms.find(id);
156 return it != _waveforms.end() ? it->second.get() : nullptr;
157 }
158
160 {
161 const auto it = _antennas.find(id);
162 return it != _antennas.end() ? it->second.get() : nullptr;
163 }
164
166 {
167 const auto it = _timings.find(id);
168 return it != _timings.end() ? it->second.get() : nullptr;
169 }
170
172 {
173 for (auto& p : _platforms)
174 {
175 if (p->getId() == id)
176 return p.get();
177 }
178 return nullptr;
179 }
180
182 {
183 for (auto& tx : _transmitters)
184 if (tx->getId() == id)
185 return tx.get();
186 return nullptr;
187 }
188
190 {
191 const auto it = _transmitters_by_name.find(name);
192 return it != _transmitters_by_name.end() ? it->second : nullptr;
193 }
194
196 {
197 for (auto& rx : _receivers)
198 if (rx->getId() == id)
199 return rx.get();
200 return nullptr;
201 }
202
203 RadarSignal* World::findWaveformByName(const std::string& name)
204 {
205 const auto it = _waveform_ids_by_name.find(name);
206 return it != _waveform_ids_by_name.end() ? findWaveform(it->second) : nullptr;
207 }
208
210 {
211 const auto include_streaming_interval_start = [](std::optional<RealType>& earliest,
212 const RealType segment_start, const RealType segment_end,
213 const bool allow_pre_start)
214 {
218 {
219 return;
220 }
221
223 allow_pre_start && segment_start < sim_start ? segment_start : std::max(sim_start, segment_start);
224 earliest = earliest.has_value() ? std::min(*earliest, required_start) : required_start;
225 };
226
227 std::optional<RealType> earliest;
228 for (const auto& transmitter_ptr : _transmitters)
229 {
230 if (transmitter_ptr == nullptr || !transmitter_ptr->isStreamingMode())
231 {
232 continue;
233 }
234
235 const auto& schedule = transmitter_ptr->getSchedule();
236 if (schedule.empty())
237 {
239 continue;
240 }
241
242 for (const auto& period : schedule)
243 {
245 }
246 }
247
248 for (const auto& receiver_ptr : _receivers)
249 {
250 if (receiver_ptr == nullptr ||
254 {
255 continue;
256 }
257
258 const auto& schedule = receiver_ptr->getSchedule();
259 if (schedule.empty())
260 {
262 continue;
263 }
264
265 for (const auto& period : schedule)
266 {
268 }
269 }
270
271 return earliest.value_or(params::startTime());
272 }
273
275 {
276 for (auto& tgt : _targets)
277 if (tgt->getId() == id)
278 return tgt.get();
279 return nullptr;
280 }
281
282 void World::replace(std::unique_ptr<Target> target)
283 {
284 const SimId id = target->getId();
285 for (auto& t : _targets)
286 {
287 if (t->getId() == id)
288 {
289 t = std::move(target);
290 return;
291 }
292 }
293 _targets.push_back(std::move(target));
294 }
295
296 void World::replace(std::unique_ptr<Antenna> antenna)
297 {
298 const SimId id = antenna->getId();
299 const Antenna* new_ptr = antenna.get();
300
301 std::unique_ptr<Antenna> old_owned;
302 const Antenna* old_ptr = nullptr;
303
304 if (auto it = _antennas.find(id); it != _antennas.end())
305 {
306 old_owned = std::move(it->second);
307 old_ptr = old_owned.get();
308 it->second = std::move(antenna);
309 }
310 else
311 {
312 _antennas[id] = std::move(antenna);
313 }
314
315 if ((old_ptr != nullptr) && old_ptr != new_ptr)
316 {
317 for (auto& tx : _transmitters)
318 if (tx->getAntenna() == old_ptr)
319 tx->setAntenna(new_ptr);
320
321 for (auto& rx : _receivers)
322 if (rx->getAntenna() == old_ptr)
323 rx->setAntenna(new_ptr);
324 }
325 }
326
327 void World::replace(std::unique_ptr<RadarSignal> waveform)
328 {
329 const SimId id = waveform->getId();
330 RadarSignal* new_ptr = waveform.get();
331 const std::string new_name = waveform->getName();
332
333 std::unique_ptr<RadarSignal> old_owned;
334 const RadarSignal* old_ptr = nullptr;
335
336 if (auto it = _waveforms.find(id); it != _waveforms.end())
337 {
338 old_owned = std::move(it->second);
339 old_ptr = old_owned.get();
340 _waveform_ids_by_name.erase(old_owned->getName());
341 _waveform_ids_by_name[new_name] = id;
342 it->second = std::move(waveform);
343 }
344 else
345 {
346 _waveform_ids_by_name[new_name] = id;
347 _waveforms[id] = std::move(waveform);
348 }
349
350 if ((old_ptr != nullptr) && old_ptr != new_ptr)
351 {
352 for (auto& tx : _transmitters)
353 if (tx->getSignal() == old_ptr)
354 tx->setSignal(new_ptr);
355 }
356 }
357
358 void World::replace(std::unique_ptr<PrototypeTiming> timing)
359 {
360 const SimId id = timing->getId();
361 const PrototypeTiming* new_ptr = timing.get();
362
363 std::unique_ptr<PrototypeTiming> old_owned;
364 const PrototypeTiming* old_ptr = nullptr;
365
366 if (auto it = _timings.find(id); it != _timings.end())
367 {
368 old_owned = std::move(it->second);
369 old_ptr = old_owned.get();
370 it->second = std::move(timing);
371 }
372 else
373 {
374 _timings[id] = std::move(timing);
375 }
376
377 std::unordered_map<const timing::Timing*, std::shared_ptr<timing::Timing>> refreshed_instances;
379 {
380 const auto current_timing = radar_obj->getTiming();
381 if (!current_timing || (current_timing->getId() != id))
382 {
383 return;
384 }
385
386 const timing::Timing* const timing_key = current_timing.get();
387 const auto [it, inserted] = refreshed_instances.try_emplace(timing_key);
388 if (inserted)
389 {
390 auto refreshed =
391 std::make_shared<timing::Timing>(new_ptr->getName(), current_timing->getSeed(), new_ptr->getId());
392 refreshed->initializeModel(new_ptr);
393 it->second = std::move(refreshed);
394 }
395 radar_obj->setTiming(it->second);
396 };
397
398 if ((old_ptr != nullptr) && old_ptr != new_ptr)
399 {
400 for (auto& tx : _transmitters)
402
403 for (auto& rx : _receivers)
405 }
406 }
407
409 {
410 _platforms.clear();
411 _transmitters.clear();
412 _transmitters_by_name.clear();
413 _receivers.clear();
414 _targets.clear();
415 _waveforms.clear();
416 _waveform_ids_by_name.clear();
417 _antennas.clear();
418 _timings.clear();
419 _event_queue = {};
420 _simulation_state = {};
421 }
422
423 void World::swap(World& other) noexcept
424 {
425 using std::swap;
426
427 _platforms.swap(other._platforms);
428 _transmitters.swap(other._transmitters);
429 _receivers.swap(other._receivers);
430 _targets.swap(other._targets);
431 _waveforms.swap(other._waveforms);
432 _waveform_ids_by_name.swap(other._waveform_ids_by_name);
433 _transmitters_by_name.swap(other._transmitters_by_name);
434 _antennas.swap(other._antennas);
435 _timings.swap(other._timings);
436 _event_queue.swap(other._event_queue);
437 swap(_simulation_state, other._simulation_state);
438 }
439
441 {
444
445 for (const auto& transmitter : _transmitters)
446 {
447 scheduleInitialTransmitterEvents(transmitter.get(), sim_start, sim_end);
448 }
449
450 for (const auto& receiver : _receivers)
451 {
452 scheduleInitialReceiverEvents(receiver.get(), sim_start, sim_end);
453 }
454 }
455
456 void World::scheduleInitialTransmitterEvents(Transmitter* const transmitter, const RealType sim_start,
457 const RealType sim_end)
458 {
460 {
461 scheduleInitialPulsedTransmitterEvent(transmitter, sim_start, sim_end);
462 return;
463 }
464 scheduleInitialStreamingTransmitterEvents(transmitter, sim_start, sim_end);
465 }
466
467 void World::scheduleInitialPulsedTransmitterEvent(Transmitter* const transmitter, const RealType sim_start,
468 const RealType sim_end)
469 {
470 // Find the first valid pulse time starting from the simulation start time.
471 if (auto start_time = transmitter->getNextPulseTime(sim_start); start_time && *start_time <= sim_end)
472 {
473 _event_queue.push({*start_time, EventType::TX_PULSED_START, transmitter});
474 }
475 }
476
477 void World::scheduleInitialStreamingTransmitterEvents(Transmitter* const transmitter, const RealType sim_start,
478 const RealType sim_end)
479 {
480 const auto& schedule = transmitter->getSchedule();
481 if (schedule.empty())
482 {
484 pushStreamingTransmitterEvents(transmitter, sim_start, end);
485 return;
486 }
487
488 for (const auto& period : schedule)
489 {
490 const RealType start = std::max(sim_start, period.start);
491 const RealType end = makeActiveSource(transmitter, period.start, std::min(sim_end, period.end)).segment_end;
492 pushStreamingTransmitterEvents(transmitter, start, end);
493 }
494 }
495
496 void World::pushStreamingTransmitterEvents(Transmitter* const transmitter, const RealType start, const RealType end)
497 {
498 if (start < end)
499 {
500 _event_queue.push({start, EventType::TX_STREAMING_START, transmitter});
501 _event_queue.push({end, EventType::TX_STREAMING_END, transmitter});
502 }
503 }
504
505 void World::scheduleInitialReceiverEvents(Receiver* const receiver, const RealType sim_start,
506 const RealType sim_end)
507 {
509 {
510 scheduleInitialPulsedReceiverEvent(receiver, sim_end);
511 return;
512 }
513 scheduleInitialStreamingReceiverEvents(receiver, sim_start, sim_end);
514 }
515
516 void World::scheduleInitialPulsedReceiverEvent(Receiver* const receiver, const RealType sim_end)
517 {
519 if (auto start = receiver->getNextWindowTime(nominal_start); start && *start < sim_end)
520 {
521 _event_queue.push({*start, EventType::RX_PULSED_WINDOW_START, receiver});
522 }
523 }
524
525 void World::scheduleInitialStreamingReceiverEvents(Receiver* const receiver, const RealType sim_start,
526 const RealType sim_end)
527 {
528 const auto& schedule = receiver->getSchedule();
529 if (schedule.empty())
530 {
531 _event_queue.push({sim_start, EventType::RX_STREAMING_START, receiver});
532 _event_queue.push({sim_end, EventType::RX_STREAMING_END, receiver});
533 return;
534 }
535
536 for (const auto& period : schedule)
537 {
538 const RealType start = std::max(sim_start, period.start);
539 const RealType end = std::min(sim_end, period.end);
540 pushStreamingReceiverEvents(receiver, start, end);
541 }
542 }
543
544 void World::pushStreamingReceiverEvents(Receiver* const receiver, const RealType start, const RealType end)
545 {
546 if (start < end)
547 {
548 _event_queue.push({start, EventType::RX_STREAMING_START, receiver});
549 _event_queue.push({end, EventType::RX_STREAMING_END, receiver});
550 }
551 }
552
554 {
555 for (const auto& rx_ptr : _receivers)
556 {
557 auto& rx = *rx_ptr;
558 rx.clearResolvedDechirpSources();
559 if (!rx.isDechirpEnabled())
560 {
561 continue;
562 }
563 if (rx.getMode() != radar::OperationMode::FMCW_MODE)
564 {
565 throw std::runtime_error("Receiver '" + rx.getName() + "' enables dechirping outside FMCW mode.");
566 }
567
568 auto reference = rx.getDechirpReference();
569 std::vector<ActiveStreamingSource> sources;
570 const std::string owner = "Receiver '" + rx.getName() + "'";
571 switch (reference.source)
572 {
573 case Receiver::DechirpReferenceSource::Attached:
574 {
575 const auto* const tx = dynamic_cast<const Transmitter*>(rx.getAttached());
577 reference.transmitter_id = tx->getId();
578 reference.transmitter_name = tx->getName();
580 break;
581 }
582 case Receiver::DechirpReferenceSource::Transmitter:
583 {
584 auto* const tx = findTransmitterByName(reference.name);
586 reference.transmitter_id = tx->getId();
587 reference.transmitter_name = tx->getName();
589 break;
590 }
591 case Receiver::DechirpReferenceSource::Custom:
592 {
593 auto* const waveform = findWaveformByName(reference.name);
594 if (waveform == nullptr || !waveform->isFmcwFamily())
595 {
596 throw std::runtime_error(owner + " custom dechirp reference waveform '" + reference.name +
597 "' must be a top-level FMCW waveform.");
598 }
599 reference.waveform_id = waveform->getId();
600 reference.waveform_name = waveform->getName();
601 sources = waveformDechirpSources(waveform, &rx);
602 break;
603 }
604 case Receiver::DechirpReferenceSource::None:
605 throw std::runtime_error(owner + " enables dechirping without a dechirp reference.");
606 }
607
608 if (sources.empty())
609 {
610 throw std::runtime_error(owner + " dechirp reference has no active LO segments in the simulation.");
611 }
612 std::ranges::sort(sources, [](const ActiveStreamingSource& lhs, const ActiveStreamingSource& rhs)
613 { return lhs.segment_start < rhs.segment_start; });
614 rx.setDechirpReference(std::move(reference));
615 rx.setResolvedDechirpSources(std::move(sources));
616 }
617 }
618
619 std::string World::dumpEventQueue() const
620 {
621 if (_event_queue.empty())
622 {
623 return "Event Queue is empty.\n";
624 }
625
626 std::stringstream ss;
627 ss << std::fixed << std::setprecision(6);
628
629 const std::string separator = "--------------------------------------------------------------------";
630 const std::string title = "| Event Queue Contents (" + std::to_string(_event_queue.size()) + " events)";
631 if (separator.size() > static_cast<std::size_t>(std::numeric_limits<int>::max()))
632 {
633 throw std::runtime_error("Separator width exceeds stream formatting limits.");
634 }
635 const int title_width = static_cast<int>(separator.size()) - 1;
636
637 ss << separator << "\n"
638 << std::left << std::setw(title_width) << title << "|\n"
639 << separator << "\n"
640 << "| " << std::left << std::setw(12) << "Timestamp" << " | " << std::setw(21) << "Event Type" << " | "
641 << std::setw(25) << "Source Object" << " |\n"
642 << separator << "\n";
643
644 auto queue_copy = _event_queue;
645
646 while (!queue_copy.empty())
647 {
648 const auto [timestamp, event_type, source_object] = queue_copy.top();
649 queue_copy.pop();
650
651 ss << "| " << std::right << std::setw(12) << timestamp << " | " << std::left << std::setw(21)
652 << toString(event_type) << " | " << std::left << std::setw(25) << source_object->getName() << " |\n";
653 }
654 ss << separator << "\n";
655
656 return ss.str();
657 }
658}
Header file defining various types of antennas and their gain patterns.
const Transmitter & transmitter
const Receiver & receiver
Abstract base class representing an antenna.
The World class manages the simulator environment.
Definition world.h:39
void scheduleInitialEvents()
Populates the event queue with the initial events for the simulation.
Definition world.cpp:440
void add(std::unique_ptr< radar::Platform > plat) noexcept
Adds a radar platform to the simulation world.
Definition world.cpp:110
void replace(std::unique_ptr< radar::Target > target)
Replaces an existing target, updating internal pointers.
Definition world.cpp:282
fers_signal::RadarSignal * findWaveform(const SimId id)
Finds a radar signal by ID.
Definition world.cpp:153
radar::Target * findTarget(const SimId id)
Finds a target by ID.
Definition world.cpp:274
fers_signal::RadarSignal * findWaveformByName(const std::string &name)
Finds a waveform by name.
Definition world.cpp:203
radar::Receiver * findReceiver(const SimId id)
Finds a receiver by ID.
Definition world.cpp:195
radar::Transmitter * findTransmitter(const SimId id)
Finds a transmitter by ID.
Definition world.cpp:181
void clear() noexcept
Clears all objects and assets from the simulation world.
Definition world.cpp:408
void resolveReceiverDechirpReferences()
Resolves and validates receiver FMCW dechirp references after all components are loaded.
Definition world.cpp:553
timing::PrototypeTiming * findTiming(const SimId id)
Finds a timing source by ID.
Definition world.cpp:165
antenna::Antenna * findAntenna(const SimId id)
Finds an antenna by ID.
Definition world.cpp:159
void swap(World &other) noexcept
Exchanges all owned world state with another world.
Definition world.cpp:423
std::string dumpEventQueue() const
Dumps the current state of the event queue to a string for debugging.
Definition world.cpp:619
radar::Platform * findPlatform(const SimId id)
Finds a platform by ID.
Definition world.cpp:171
radar::Transmitter * findTransmitterByName(const std::string &name)
Finds a transmitter by name.
Definition world.cpp:189
RealType earliestPhaseNoiseLookupStart() const
Finds the earliest simulation time that can require CW phase-noise samples.
Definition world.cpp:209
Class representing a radar signal with associated properties.
SimId getId() const noexcept
Gets the unique ID of the radar signal.
const std::string & getName() const noexcept
Gets the name of the radar signal.
bool isFmcwFamily() const noexcept
Returns true when this signal belongs to the FMCW waveform family.
Represents a simulation platform with motion and rotation paths.
Definition platform.h:32
Manages radar signal reception and response processing.
Definition receiver.h:47
const std::vector< SchedulePeriod > & getSchedule() const noexcept
Retrieves the list of active reception periods.
Definition receiver.h:382
RealType getWindowStart(unsigned window) const
Retrieves the start time of a specific radar window.
Definition receiver.cpp:371
std::optional< RealType > getNextWindowTime(RealType time) const
Determines the next valid window start time at or after the given time.
Definition receiver.cpp:384
OperationMode getMode() const noexcept
Gets the operational mode of the receiver.
Definition receiver.h:209
Base class for radar targets.
Definition target.h:118
Represents a radar transmitter system.
Definition transmitter.h:34
std::optional< RealType > getNextPulseTime(RealType time) const
Determines the valid simulation time for a pulse at or after the given time.
const std::vector< SchedulePeriod > & getSchedule() const noexcept
Retrieves the list of active transmission periods.
OperationMode getMode() const noexcept
Gets the operational mode of the transmitter.
Definition transmitter.h:96
Manages timing properties such as frequency, offsets, and synchronization.
Represents a timing source for simulation.
Definition timing.h:36
double RealType
Type for real numbers.
Definition config.h:27
ActiveStreamingSource makeActiveSource(const radar::Transmitter *const tx, const RealType segment_start, const RealType segment_end)
Builds an active-source cache from a streaming transmitter and segment bounds.
std::string toString(const EventType type)
Converts an EventType enum to its string representation.
Definition sim_events.h:74
@ RX_PULSED_WINDOW_START
A pulsed receiver opens its listening window.
@ TX_STREAMING_END
A streaming transmitter stops transmitting.
@ RX_STREAMING_END
A streaming receiver stops listening.
@ TX_STREAMING_START
A streaming transmitter starts transmitting.
@ TX_PULSED_START
A pulsed transmitter begins emitting a pulse.
@ RX_STREAMING_START
A streaming receiver starts listening.
ActiveStreamingSource makeActiveSourceFromWaveform(const fers_signal::RadarSignal *const signal, const RealType segment_start, const RealType segment_end)
Builds an active-source cache directly from a waveform for receiver-local LO references.
RealType endTime() noexcept
Get the end time for the simulation.
Definition parameters.h:109
RealType startTime() noexcept
Get the start time for the simulation.
Definition parameters.h:103
@ SFCW_MODE
The component operates in a stepped-frequency CW streaming mode.
@ PULSED_MODE
The component operates in a pulsed mode.
@ CW_MODE
The component operates in a continuous-wave mode.
@ FMCW_MODE
The component operates in an FMCW streaming mode.
Defines the Parameters struct and provides methods for managing simulation parameters.
Header file for the PrototypeTiming class.
Defines the Radar class and associated functionality.
Classes for handling radar waveforms and signals.
Defines the core structures for the event-driven simulation engine.
uint64_t SimId
64-bit Unique Simulation ID.
Definition sim_id.h:18
math::Vec3 max
Cached description of an active streaming transmitter segment.
RealType segment_end
Segment end time in seconds.
Timing source for simulation objects.
Header file for the World class in the simulator.