FERS 1.0.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
api.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2// Copyright (c) 2025-present FERS Contributors (see AUTHORS.md).
3
4/**
5 * @file api.cpp
6 * @brief Implementation of the C-style FFI for the libfers core library.
7 *
8 * This file provides the C implementations for the functions declared in `api.h`.
9 * It acts as the bridge between the C ABI and the C++ core, handling object
10 * creation/destruction, exception catching, error reporting, and type casting.
11 */
12
13#include <algorithm>
14#include <core/logging.h>
15#include <core/parameters.h>
16#include <core/sim_id.h>
17#include <cstring>
18#include <filesystem>
19#include <format>
20#include <functional>
21#include <libfers/api.h>
22#include <math/path.h>
23#include <math/rotation_path.h>
24#include <mutex>
25#include <nlohmann/json.hpp>
26#include <string>
27#include <vector>
28
30#include "core/fers_context.h"
31#include "core/sim_threading.h"
32#include "core/thread_pool.h"
37#include "serial/xml_parser.h"
39#include "signal/radar_signal.h"
41
42// The fers_context struct is defined here as an alias for our C++ class.
43// This allows the C-API to return an opaque pointer, hiding the C++ implementation.
45{
46};
47
48// A thread-local error message string ensures that error details from one
49// thread's API call do not interfere with another's. This is crucial for a
50// thread-safe FFI layer.
51thread_local std::string last_error_message;
52thread_local std::vector<std::string> last_warning_messages;
53
54/**
55 * @brief Centralized exception handler for the C-API boundary.
56 *
57 * This function catches standard C++ exceptions, records their `what()` message
58 * into the thread-local error storage, and logs the error. This prevents C++
59 * exceptions from propagating across the FFI boundary, which would be undefined behavior.
60 * @param e The exception that was caught.
61 * @param function_name The name of the API function where the error occurred.
62 */
63static void handle_api_exception(const std::exception& e, const std::string& function_name)
64{
65 last_error_message = e.what();
66 LOG(logging::Level::ERROR, "API Error in {}: {}", function_name, last_error_message);
67}
68
74
79
85
86extern "C" {
87
89{
90 last_error_message.clear();
92 try
93 {
94 return new fers_context_t();
95 }
96 catch (const std::bad_alloc& e)
97 {
98 handle_api_exception(e, "fers_context_create");
99 return nullptr;
100 }
101 catch (const std::exception& e)
102 {
103 handle_api_exception(e, "fers_context_create");
104 return nullptr;
105 }
106}
107
109{
110 if (context == nullptr)
111 {
112 return;
113 }
114 delete context;
115}
116
117// Helper to map C enum to internal C++ enum
119{
120 switch (level)
121 {
122 case FERS_LOG_TRACE:
124 case FERS_LOG_DEBUG:
126 case FERS_LOG_INFO:
128 case FERS_LOG_WARNING:
130 case FERS_LOG_ERROR:
132 case FERS_LOG_FATAL:
134 case FERS_LOG_OFF:
135 return logging::Level::OFF;
136 default:
138 }
139}
140
142{
143 switch (level)
144 {
146 return FERS_LOG_TRACE;
148 return FERS_LOG_DEBUG;
150 return FERS_LOG_INFO;
152 return FERS_LOG_WARNING;
154 return FERS_LOG_ERROR;
156 return FERS_LOG_FATAL;
158 return FERS_LOG_OFF;
159 default:
160 return FERS_LOG_INFO;
161 }
162}
163
164namespace
165{
166 std::mutex log_callback_mutex;
167 fers_log_callback_t log_callback = nullptr;
168 void* log_callback_user_data = nullptr;
169
170 void forward_log_callback(const logging::Level level, const std::string& line, void* /*user_data*/)
171 {
172 fers_log_callback_t callback = nullptr;
173 void* user_data = nullptr;
174
175 {
176 std::scoped_lock lock(log_callback_mutex);
177 callback = log_callback;
178 user_data = log_callback_user_data;
179 }
180
181 if (callback != nullptr)
182 {
183 callback(map_internal_log_level(level), line.c_str(), user_data);
184 }
185 }
186}
187
188int fers_configure_logging(fers_log_level_t level, const char* log_file_path)
189{
190 last_error_message.clear();
191 try
192 {
194 if ((log_file_path != nullptr) && ((*log_file_path) != 0))
195 {
196 auto result = logging::logger.logToFile(log_file_path);
197 if (!result)
198 {
199 last_error_message = result.error();
200 return 1;
201 }
202 }
203 return 0;
204 }
205 catch (const std::exception& e)
206 {
207 handle_api_exception(e, "fers_configure_logging");
208 return 1;
209 }
210}
211
213
214void fers_set_log_callback(fers_log_callback_t callback, void* user_data)
215{
216 {
217 std::scoped_lock lock(log_callback_mutex);
218 log_callback = callback;
219 log_callback_user_data = user_data;
220 }
221
222 logging::logger.setCallback(callback == nullptr ? nullptr : forward_log_callback, nullptr);
223}
224
225void fers_log(fers_log_level_t level, const char* message)
226{
227 if (message == nullptr)
228 return;
229 // We pass a default source_location because C-API calls don't provide C++ source info
230 logging::logger.log(map_api_log_level(level), message, std::source_location::current());
231}
232
233int fers_set_thread_count(unsigned num_threads)
234{
235 last_error_message.clear();
236 try
237 {
238 if (auto res = params::setThreads(num_threads); !res)
239 {
240 last_error_message = res.error();
241 return 1;
242 }
243 return 0;
244 }
245 catch (const std::exception& e)
246 {
247 handle_api_exception(e, "fers_set_thread_count");
248 return 1;
249 }
250}
251
252int fers_set_output_directory(fers_context_t* context, const char* out_dir)
253{
254 last_error_message.clear();
255 if ((context == nullptr) || (out_dir == nullptr))
256 {
257 last_error_message = "Invalid arguments: context or out_dir is NULL.";
259 return -1;
260 }
261 auto* ctx = reinterpret_cast<FersContext*>(context);
262 try
263 {
264 ctx->setOutputDir(out_dir);
265 return 0;
266 }
267 catch (const std::exception& e)
268 {
269 handle_api_exception(e, "fers_set_output_directory");
270 return 1;
271 }
272}
273
274int fers_load_scenario_from_xml_file(fers_context_t* context, const char* xml_filepath, const int validate)
275{
276 last_error_message.clear();
278 if ((context == nullptr) || (xml_filepath == nullptr))
279 {
280 last_error_message = "Invalid arguments: context or xml_filepath is NULL.";
283 return -1;
284 }
285
286 auto* ctx = reinterpret_cast<FersContext*>(context);
287 try
288 {
289 // Set default output directory to the scenario file's directory
290 std::filesystem::path p(xml_filepath);
291 auto parent = p.parent_path();
292 if (parent.empty())
293 parent = ".";
294 ctx->setOutputDir(parent.string());
295
296 serial::parseSimulation(xml_filepath, ctx->getWorld(), static_cast<bool>(validate), ctx->getMasterSeeder());
297
298 // After parsing, seed the master random number generator. This is done
299 // to ensure simulation reproducibility. If the scenario specifies a seed,
300 // it is used; otherwise, a non-deterministic seed is generated so that
301 // subsequent runs are unique by default.
302 if (params::params.random_seed)
303 {
304 LOG(logging::Level::INFO, "Using master seed from scenario file: {}", *params::params.random_seed);
305 ctx->getMasterSeeder().seed(*params::params.random_seed);
306 }
307 else
308 {
309 const auto seed = std::random_device{}();
310 LOG(logging::Level::INFO, "No master seed provided in scenario. Using random_device seed: {}", seed);
312 ctx->getMasterSeeder().seed(seed);
313 }
315 return 0; // Success
316 }
317 catch (const std::exception& e)
318 {
320 handle_api_exception(e, "fers_load_scenario_from_xml_file");
321 return 1; // Error
322 }
323}
324
325int fers_load_scenario_from_xml_string(fers_context_t* context, const char* xml_content, const int validate)
326{
327 last_error_message.clear();
329 if ((context == nullptr) || (xml_content == nullptr))
330 {
331 last_error_message = "Invalid arguments: context or xml_content is NULL.";
334 return -1;
335 }
336
337 auto* ctx = reinterpret_cast<FersContext*>(context);
338 try
339 {
340 serial::parseSimulationFromString(xml_content, ctx->getWorld(), static_cast<bool>(validate),
341 ctx->getMasterSeeder());
342
343 // After parsing, seed the master random number generator. This ensures
344 // that if the scenario provides a seed, the simulation will be
345 // reproducible. If not, a random seed is used to ensure unique runs.
346 if (params::params.random_seed)
347 {
348 LOG(logging::Level::INFO, "Using master seed from scenario string: {}", *params::params.random_seed);
349 ctx->getMasterSeeder().seed(*params::params.random_seed);
350 }
351 else
352 {
353 const auto seed = std::random_device{}();
354 LOG(logging::Level::INFO, "No master seed provided in scenario. Using random_device seed: {}", seed);
356 ctx->getMasterSeeder().seed(seed);
357 }
358
360 return 0; // Success
361 }
362 catch (const std::exception& e)
363 {
365 handle_api_exception(e, "fers_load_scenario_from_xml_string");
366 return 1; // Parsing or logic error
367 }
368}
369
371{
372 last_error_message.clear();
373 if (context == nullptr)
374 {
375 last_error_message = "Invalid context provided to fers_get_scenario_as_json.";
377 return nullptr;
378 }
379
380 const auto* ctx = reinterpret_cast<FersContext*>(context);
381 try
382 {
383 const nlohmann::json j = serial::world_to_json(*ctx->getWorld());
384 const std::string json_str = j.dump(2);
385 // A heap-allocated copy of the string is returned. This is necessary
386 // to transfer ownership of the memory across the FFI boundary to a
387 // client that will free it using `fers_free_string`.
388 return strdup(json_str.c_str());
389 }
390 catch (const std::exception& e)
391 {
392 handle_api_exception(e, "fers_get_scenario_as_json");
393 return nullptr;
394 }
395}
396
398{
399 last_error_message.clear();
400 if (context == nullptr)
401 {
402 last_error_message = "Invalid context provided to fers_get_scenario_as_xml.";
404 return nullptr;
405 }
406
407 const auto* ctx = reinterpret_cast<FersContext*>(context);
408 try
409 {
410 const std::string xml_str = serial::world_to_xml_string(*ctx->getWorld());
411 if (xml_str.empty())
412 {
413 throw std::runtime_error("XML serialization resulted in an empty string.");
414 }
415 // `strdup` is used to create a heap-allocated string that can be safely
416 // passed across the FFI boundary. The client is responsible for freeing
417 // this memory with `fers_free_string`.
418 return strdup(xml_str.c_str());
419 }
420 catch (const std::exception& e)
421 {
422 handle_api_exception(e, "fers_get_scenario_as_xml");
423 return nullptr;
424 }
425}
426
428{
429 last_error_message.clear();
430 if (context == nullptr)
431 {
432 last_error_message = "Invalid context provided to fers_get_last_output_metadata_json.";
434 return nullptr;
435 }
436
437 const auto* ctx = reinterpret_cast<FersContext*>(context);
438 try
439 {
440 const std::string json_str = ctx->getLastOutputMetadataJson();
441 return strdup(json_str.c_str());
442 }
443 catch (const std::exception& e)
444 {
445 handle_api_exception(e, "fers_get_last_output_metadata_json");
446 return nullptr;
447 }
448}
449
450int fers_update_platform_from_json(fers_context_t* context, uint64_t id, const char* json)
451{
452 last_error_message.clear();
454 if ((context == nullptr) || (json == nullptr))
455 {
457 return -1;
458 }
459 auto* ctx = reinterpret_cast<FersContext*>(context);
460 try
461 {
462 auto* p = ctx->getWorld()->findPlatform(id);
463 if (p == nullptr)
464 {
465 last_error_message = "Platform not found";
467 return 1;
468 }
469 auto j = nlohmann::json::parse(json);
471 if (j.contains("name"))
472 {
473 p->setName(j.at("name").get<std::string>());
474 }
476 return 0;
477 }
478 catch (const std::exception& e)
479 {
481 handle_api_exception(e, "fers_update_platform_from_json");
482 return 1;
483 }
484}
485
487{
488 last_error_message.clear();
490 if ((context == nullptr) || (json == nullptr))
491 {
493 return -1;
494 }
495 auto* ctx = reinterpret_cast<FersContext*>(context);
496 try
497 {
498 auto j = nlohmann::json::parse(json);
499 serial::update_parameters_from_json(j, ctx->getMasterSeeder());
501 return 0;
502 }
503 catch (const std::exception& e)
504 {
506 handle_api_exception(e, "fers_update_parameters_from_json");
507 return 1;
508 }
509}
510
511int fers_update_antenna_from_json(fers_context_t* context, const char* json)
512{
513 last_error_message.clear();
514 if ((context == nullptr) || (json == nullptr))
515 return -1;
516 auto* ctx = reinterpret_cast<FersContext*>(context);
517 try
518 {
519 auto j = nlohmann::json::parse(json);
520 auto id = j.at("id").is_string() ? std::stoull(j.at("id").get<std::string>()) : j.at("id").get<uint64_t>();
521 auto* ant = ctx->getWorld()->findAntenna(id);
522 if (ant == nullptr)
523 {
524 last_error_message = "Antenna not found";
525 return 1;
526 }
527 serial::update_antenna_from_json(j, ant, *ctx->getWorld());
528 return 0;
529 }
530 catch (const std::exception& e)
531 {
532 handle_api_exception(e, "fers_update_antenna_from_json");
533 return 1;
534 }
535}
536
537int fers_update_waveform_from_json(fers_context_t* context, const char* json)
538{
539 last_error_message.clear();
540 if ((context == nullptr) || (json == nullptr))
541 return -1;
542 auto* ctx = reinterpret_cast<FersContext*>(context);
543 try
544 {
545 auto j = nlohmann::json::parse(json);
547 if (wf)
548 {
549 ctx->getWorld()->replace(std::move(wf));
550 }
551 return 0;
552 }
553 catch (const std::exception& e)
554 {
555 handle_api_exception(e, "fers_update_waveform_from_json");
556 return 1;
557 }
558}
559
560int fers_update_transmitter_from_json(fers_context_t* context, uint64_t id, const char* json)
561{
562 last_error_message.clear();
563 if ((context == nullptr) || (json == nullptr))
564 return -1;
565 auto* ctx = reinterpret_cast<FersContext*>(context);
566 try
567 {
568 auto* tx = ctx->getWorld()->findTransmitter(id);
569 if (tx == nullptr)
570 {
571 last_error_message = "Transmitter not found";
572 return 1;
573 }
574 auto j = nlohmann::json::parse(json);
575 serial::update_transmitter_from_json(j, tx, *ctx->getWorld(), ctx->getMasterSeeder());
576 return 0;
577 }
578 catch (const std::exception& e)
579 {
580 handle_api_exception(e, "fers_update_transmitter_from_json");
581 return 1;
582 }
583}
584
585int fers_update_receiver_from_json(fers_context_t* context, uint64_t id, const char* json)
586{
587 last_error_message.clear();
588 if ((context == nullptr) || (json == nullptr))
589 return -1;
590 auto* ctx = reinterpret_cast<FersContext*>(context);
591 try
592 {
593 auto* rx = ctx->getWorld()->findReceiver(id);
594 if (rx == nullptr)
595 {
596 last_error_message = "Receiver not found";
597 return 1;
598 }
599 auto j = nlohmann::json::parse(json);
600 serial::update_receiver_from_json(j, rx, *ctx->getWorld(), ctx->getMasterSeeder());
601 return 0;
602 }
603 catch (const std::exception& e)
604 {
605 handle_api_exception(e, "fers_update_receiver_from_json");
606 return 1;
607 }
608}
609
610int fers_update_target_from_json(fers_context_t* context, uint64_t id, const char* json)
611{
612 last_error_message.clear();
613 if ((context == nullptr) || (json == nullptr))
614 return -1;
615 auto* ctx = reinterpret_cast<FersContext*>(context);
616 try
617 {
618 auto* tgt = ctx->getWorld()->findTarget(id);
619 if (tgt == nullptr)
620 {
621 last_error_message = "Target not found";
622 return 1;
623 }
624 auto j = nlohmann::json::parse(json);
625 serial::update_target_from_json(j, tgt, *ctx->getWorld(), ctx->getMasterSeeder());
626 return 0;
627 }
628 catch (const std::exception& e)
629 {
630 handle_api_exception(e, "fers_update_target_from_json");
631 return 1;
632 }
633}
634
636{
637 last_error_message.clear();
638 if ((context == nullptr) || (json == nullptr))
639 return -1;
640 auto* ctx = reinterpret_cast<FersContext*>(context);
641 try
642 {
643 auto j = nlohmann::json::parse(json);
644 uint64_t tx_id =
645 j.at("tx_id").is_string() ? std::stoull(j.at("tx_id").get<std::string>()) : j.at("tx_id").get<uint64_t>();
646 uint64_t rx_id =
647 j.at("rx_id").is_string() ? std::stoull(j.at("rx_id").get<std::string>()) : j.at("rx_id").get<uint64_t>();
648 auto* tx = ctx->getWorld()->findTransmitter(tx_id);
649 auto* rx = ctx->getWorld()->findReceiver(rx_id);
650 if ((tx == nullptr) || (rx == nullptr))
651 {
652 last_error_message = "Monostatic components not found";
653 return 1;
654 }
655 serial::update_monostatic_from_json(j, tx, rx, *ctx->getWorld(), ctx->getMasterSeeder());
656 return 0;
657 }
658 catch (const std::exception& e)
659 {
660 handle_api_exception(e, "fers_update_monostatic_from_json");
661 return 1;
662 }
663}
664
665int fers_update_timing_from_json(fers_context_t* context, uint64_t id, const char* json)
666{
667 last_error_message.clear();
668 if ((context == nullptr) || (json == nullptr))
669 return -1;
670 auto* ctx = reinterpret_cast<FersContext*>(context);
671 try
672 {
673 if (ctx->getWorld()->findTiming(id) == nullptr)
674 {
675 last_error_message = "Timing not found";
676 return 1;
677 }
678 auto j = nlohmann::json::parse(json);
679 serial::update_timing_from_json(j, *ctx->getWorld(), id);
680 return 0;
681 }
682 catch (const std::exception& e)
683 {
684 handle_api_exception(e, "fers_update_timing_from_json");
685 return 1;
686 }
687}
688
689int fers_update_scenario_from_json(fers_context_t* context, const char* scenario_json)
690{
691 last_error_message.clear();
693 if ((context == nullptr) || (scenario_json == nullptr))
694 {
695 last_error_message = "Invalid arguments: context or scenario_json is NULL.";
698 return -1;
699 }
700
701 auto* ctx = reinterpret_cast<FersContext*>(context);
702 try
703 {
704 const nlohmann::json j = nlohmann::json::parse(scenario_json);
705 serial::json_to_world(j, *ctx->getWorld(), ctx->getMasterSeeder());
707
708 return 0; // Success
709 }
710 catch (const nlohmann::json::exception& e)
711 {
712 // A specific catch block for JSON errors is used to provide more
713 // detailed feedback to the client (e.g., the UI), which can help
714 // developers diagnose schema or data format issues more easily.
715 last_error_message = "JSON parsing/deserialization error: " + std::string(e.what());
716 LOG(logging::Level::ERROR, "API Error in {}: {}", "fers_update_scenario_from_json", last_error_message);
718 return 2; // JSON error
719 }
720 catch (const std::exception& e)
721 {
723 handle_api_exception(e, "fers_update_scenario_from_json");
724 return 1; // Generic error
725 }
726}
727
729{
730 if (last_error_message.empty())
731 {
732 return nullptr; // No error to report
733 }
734 // `strdup` allocates with `malloc`, which is part of the C standard ABI,
735 // making it safe to transfer ownership across the FFI boundary. The caller
736 // must then free this memory using `fers_free_string`.
737 return strdup(last_error_message.c_str());
738}
739
741{
742 if (last_warning_messages.empty())
743 {
744 return nullptr;
745 }
746
747 const std::string warning_json = nlohmann::json(last_warning_messages).dump();
748 last_warning_messages.clear();
749 return strdup(warning_json.c_str());
750}
751
752void fers_free_string(char* str)
753{
754 if (str != nullptr)
755 {
756 free(str);
757 }
758}
759
760int fers_run_simulation(fers_context_t* context, fers_progress_callback_t callback, void* user_data)
761{
762 last_error_message.clear();
763 if (context == nullptr)
764 {
765 last_error_message = "Invalid context provided to fers_run_simulation.";
767 return -1;
768 }
769
770 auto* ctx = reinterpret_cast<FersContext*>(context);
771
772 // Wrap the C-style callback in a std::function for easier use in C++.
773 // This also handles the case where the callback is null.
774 std::function<void(const std::string&, int, int)> progress_fn;
775 if (callback != nullptr)
776 {
777 progress_fn = [callback, user_data](const std::string& msg, const int current, const int total)
778 { callback(msg.c_str(), current, total, user_data); };
779 }
780
781 try
782 {
784
785 ctx->clearLastOutputMetadata();
786 const auto output_metadata = core::runEventDrivenSim(ctx->getWorld(), pool, progress_fn, ctx->getOutputDir());
787 ctx->setLastOutputMetadata(output_metadata);
788
789 return 0;
790 }
791 catch (const std::exception& e)
792 {
793 handle_api_exception(e, "fers_run_simulation");
794 return 1;
795 }
796}
797
798int fers_generate_kml(const fers_context_t* context, const char* output_kml_filepath)
799{
800 last_error_message.clear();
801 if ((context == nullptr) || (output_kml_filepath == nullptr))
802 {
803 last_error_message = "Invalid arguments: context or output_kml_filepath is NULL.";
805 return -1;
806 }
807
808 const auto* ctx = reinterpret_cast<const FersContext*>(context);
809
810 try
811 {
812 if (serial::KmlGenerator::generateKml(*ctx->getWorld(), output_kml_filepath))
813 {
814 return 0; // Success
815 }
816
817 last_error_message = "KML generation failed for an unknown reason.";
819 return 2; // Generation failed
820 }
821 catch (const std::exception& e)
822 {
823 handle_api_exception(e, "fers_generate_kml");
824 return 1; // Exception thrown
825 }
826}
827
828// --- Helper to convert C-API enum to C++ enum ---
842
856
857
859 const size_t waypoint_count,
860 const fers_interp_type_t interp_type,
861 const size_t num_points)
862{
863 last_error_message.clear();
864 if ((waypoints == nullptr) || waypoint_count == 0 || num_points == 0)
865 {
866 last_error_message = "Invalid arguments: waypoints cannot be null and counts must be > 0.";
868 return nullptr;
869 }
870 if (interp_type == FERS_INTERP_CUBIC && waypoint_count < 2)
871 {
872 last_error_message = "Cubic interpolation requires at least 2 waypoints.";
874 return nullptr;
875 }
876
877 try
878 {
879 math::Path path;
880 path.setInterp(to_cpp_interp_type(interp_type));
881
882 for (size_t i = 0; i < waypoint_count; ++i)
883 {
884 math::Coord c;
885 c.t = waypoints[i].time;
886 c.pos.x = waypoints[i].x;
887 c.pos.y = waypoints[i].y;
888 c.pos.z = waypoints[i].z;
889 path.addCoord(c);
890 }
891
892 path.finalize();
893
894 auto* result_path = new fers_interpolated_path_t();
895 result_path->points = new fers_interpolated_point_t[num_points];
896 result_path->count = num_points;
897
898 const double start_time = waypoints[0].time;
899 const double end_time = waypoints[waypoint_count - 1].time;
900 const double duration = end_time - start_time;
901
902 // Handle static case separately
903 if (waypoint_count < 2 || duration <= 0)
904 {
905 const math::Vec3 pos = path.getPosition(start_time);
906 for (size_t i = 0; i < num_points; ++i)
907 {
908 result_path->points[i] = {pos.x, pos.y, pos.z, 0.0, 0.0, 0.0};
909 }
910 return result_path;
911 }
912
913 const double time_step =
914 duration / static_cast<double>(num_points > 1 ? num_points - 1 : static_cast<size_t>(1));
915
916 for (size_t i = 0; i < num_points; ++i)
917 {
918 const double t = start_time + static_cast<double>(i) * time_step;
919 const math::Vec3 pos = path.getPosition(t);
920 const math::Vec3 vel = path.getVelocity(t);
921 result_path->points[i] = {pos.x, pos.y, pos.z, vel.x, vel.y, vel.z};
922 }
923
924 return result_path;
925 }
926 catch (const std::exception& e)
927 {
928 handle_api_exception(e, "fers_get_interpolated_motion_path");
929 return nullptr;
930 }
931}
932
934{
935 if (path != nullptr)
936 {
937 delete[] path->points;
938 delete path;
939 }
940}
941
943 const size_t waypoint_count,
944 const fers_interp_type_t interp_type,
945 const fers_angle_unit_t angle_unit,
946 const size_t num_points)
947{
948 last_error_message.clear();
949 last_warning_messages.clear();
951 if ((waypoints == nullptr) || waypoint_count == 0 || num_points == 0)
952 {
953 last_error_message = "Invalid arguments: waypoints cannot be null and counts must be > 0.";
955 return nullptr;
956 }
957 if (interp_type == FERS_INTERP_CUBIC && waypoint_count < 2)
958 {
959 last_error_message = "Cubic interpolation requires at least 2 waypoints.";
961 return nullptr;
962 }
963
964 try
965 {
966 const auto unit =
969 path.setInterp(to_cpp_rot_interp_type(interp_type));
970
971 for (size_t i = 0; i < waypoint_count; ++i)
972 {
974 waypoints[i].azimuth, unit, serial::rotation_warning_utils::ValueKind::Angle, "C-API",
975 std::format("rotation waypoint {}", i), "azimuth");
977 waypoints[i].elevation, unit, serial::rotation_warning_utils::ValueKind::Angle, "C-API",
978 std::format("rotation waypoint {}", i), "elevation");
980 waypoints[i].azimuth, waypoints[i].elevation, waypoints[i].time, unit));
981 }
982
983 path.finalize();
984
985 auto* result_path = new fers_interpolated_rotation_path_t();
986 result_path->points = new fers_interpolated_rotation_point_t[num_points];
987 result_path->count = num_points;
988
989 const double start_time = waypoints[0].time;
990 const double end_time = waypoints[waypoint_count - 1].time;
991 const double duration = end_time - start_time;
992
993 // Handle static case separately
994 if (waypoint_count < 2 || duration <= 0)
995 {
996 const math::SVec3 rot = path.getPosition(start_time);
997 for (size_t i = 0; i < num_points; ++i)
998 {
999 result_path->points[i] = fers_interpolated_rotation_point_t{
1002 }
1003 return result_path;
1004 }
1005
1006 const double time_step =
1007 duration / static_cast<double>(num_points > 1 ? num_points - 1 : static_cast<size_t>(1));
1008
1009 for (size_t i = 0; i < num_points; ++i)
1010 {
1011 const double t = start_time + static_cast<double>(i) * time_step;
1012 const math::SVec3 rot = path.getPosition(t);
1013
1014 result_path->points[i] = fers_interpolated_rotation_point_t{
1017 }
1018
1019 return result_path;
1020 }
1021 catch (const std::exception& e)
1022 {
1024 handle_api_exception(e, "fers_get_interpolated_rotation_path");
1025 return nullptr;
1026 }
1027}
1028
1030{
1031 if (path != nullptr)
1032 {
1033 delete[] path->points;
1034 delete path;
1035 }
1036}
1037
1038// --- Antenna Pattern Implementation ---
1039
1041 const size_t az_samples, const size_t el_samples,
1042 const double frequency_hz)
1043{
1044 last_error_message.clear();
1045 if ((context == nullptr) || az_samples < 2 || el_samples < 2)
1046 {
1047 last_error_message = "Invalid arguments: context must be non-null and sample counts must be >= 2.";
1049 return nullptr;
1050 }
1051
1052 try
1053 {
1054 const auto* ctx = reinterpret_cast<const FersContext*>(context);
1055 antenna::Antenna* ant = ctx->getWorld()->findAntenna(static_cast<SimId>(antenna_id));
1056
1057 if (ant == nullptr)
1058 {
1059 last_error_message = "Antenna ID '" + std::to_string(antenna_id) + "' not found in the world.";
1061 return nullptr;
1062 }
1063
1064 // TODO: Currently only using the first-found waveform. This is incorrect but also difficult to represent
1065 // correctly in scenarios with multiple waveforms as the gain for squarehorn and parabolic antennas
1066 // depends on the wavelength. Hence a decision needs to be made about whether to return multiple patterns
1067 // per waveform or have the user specify a representative wavelength in the UI per antenna.
1068 // Calculate wavelength from the provided frequency.
1069 // Default to 1GHz (0.3m) if frequency is invalid/zero, though the UI should prevent this
1070 // for antennas that strictly require it (Horn/Parabolic).
1071 RealType wavelength = 0.3;
1072 if (frequency_hz > 0.0)
1073 {
1074 wavelength = params::c() / frequency_hz;
1075 }
1076
1077 auto* data = new fers_antenna_pattern_data_t();
1078 data->az_count = az_samples;
1079 data->el_count = el_samples;
1080 const size_t total_samples = az_samples * el_samples;
1081 data->gains = new double[total_samples];
1082
1083 // The reference angle (boresight) is implicitly the local X-axis in the FERS engine.
1084 // We pass a zero rotation to get the gain relative to this boresight.
1085 const math::SVec3 ref_angle(1.0, 0.0, 0.0);
1086 double max_gain = 0.0;
1087
1088 const RealType az_denominator = static_cast<RealType>(az_samples - 1);
1089 const RealType el_denominator = static_cast<RealType>(el_samples - 1);
1090
1091 for (size_t i = 0; i < el_samples; ++i)
1092 {
1093 // Elevation from -PI/2 to PI/2
1094 const RealType elevation = (static_cast<RealType>(i) / el_denominator) * PI - (PI / 2.0);
1095 for (size_t j = 0; j < az_samples; ++j)
1096 {
1097 // Azimuth from -PI to PI
1098 const RealType azimuth = (static_cast<RealType>(j) / az_denominator) * 2.0 * PI - PI;
1099 const math::SVec3 sample_angle(1.0, azimuth, elevation);
1100 const RealType gain = ant->getGain(sample_angle, ref_angle, wavelength);
1101 data->gains[i * az_samples + j] = gain;
1102 max_gain = std::max(gain, max_gain);
1103 }
1104 }
1105
1106 data->max_gain = max_gain;
1107
1108 // Normalize the gains
1109 if (max_gain > 0)
1110 {
1111 for (size_t i = 0; i < total_samples; ++i)
1112 {
1113 data->gains[i] /= max_gain;
1114 }
1115 }
1116
1117 return data;
1118 }
1119 catch (const std::exception& e)
1120 {
1121 handle_api_exception(e, "fers_get_antenna_pattern");
1122 return nullptr;
1123 }
1124}
1125
1127{
1128 if (data != nullptr)
1129 {
1130 delete[] data->gains;
1131 delete data;
1132 }
1133}
1134
1135// --- Preview Link Calculation Implementation ---
1136
1138{
1139 last_error_message.clear();
1140 if (context == nullptr)
1141 {
1142 last_error_message = "Invalid context passed to fers_calculate_preview_links";
1144 return nullptr;
1145 }
1146
1147 try
1148 {
1149 const auto* ctx = reinterpret_cast<const FersContext*>(context);
1150 // Call the core physics logic in channel_model.cpp
1151 const auto cpp_links = simulation::calculatePreviewLinks(*ctx->getWorld(), time);
1152
1153 // Convert C++ vector to C-API struct
1154 auto* result = new fers_visual_link_list_t();
1155 result->count = cpp_links.size();
1156
1157 if (!cpp_links.empty())
1158 {
1159 result->links = new fers_visual_link_t[result->count];
1160 for (size_t i = 0; i < result->count; ++i)
1161 {
1162 const auto& src = cpp_links[i];
1163 auto& dst = result->links[i];
1164
1165 // Map enums
1166 switch (src.type)
1167 {
1170 break;
1172 dst.type = FERS_LINK_BISTATIC_TX_TGT;
1173 break;
1175 dst.type = FERS_LINK_BISTATIC_TGT_RX;
1176 break;
1178 dst.type = FERS_LINK_DIRECT_TX_RX;
1179 break;
1180 }
1181
1182 dst.quality = (src.quality == simulation::LinkQuality::Strong) ? FERS_LINK_STRONG : FERS_LINK_WEAK;
1183
1184 // Safe string copy
1185 std::strncpy(dst.label, src.label.c_str(), sizeof(dst.label) - 1);
1186 dst.label[sizeof(dst.label) - 1] = '\0';
1187
1188 dst.source_id = static_cast<uint64_t>(src.source_id);
1189 dst.dest_id = static_cast<uint64_t>(src.dest_id);
1190 dst.origin_id = static_cast<uint64_t>(src.origin_id);
1191 }
1192 }
1193 else
1194 {
1195 result->links = nullptr;
1196 }
1197 return result;
1198 }
1199 catch (const std::exception& e)
1200 {
1201 handle_api_exception(e, "fers_calculate_preview_links");
1202 return nullptr;
1203 }
1204}
1205
1207{
1208 if (list != nullptr)
1209 {
1210 delete[] list->links;
1211 delete list;
1212 }
1213}
1214}
Header file defining various types of antennas and their gain patterns.
static void discard_warning_capture() noexcept
Definition api.cpp:80
char * fers_get_scenario_as_xml(fers_context_t *context)
Serializes the current simulation scenario into a FERS XML string.
Definition api.cpp:397
int fers_update_antenna_from_json(fers_context_t *context, const char *json)
Updates a single antenna from JSON without full context recreation.
Definition api.cpp:511
void fers_free_antenna_pattern_data(fers_antenna_pattern_data_t *data)
Frees the memory allocated for an antenna pattern data structure.
Definition api.cpp:1126
int fers_load_scenario_from_xml_string(fers_context_t *context, const char *xml_content, const int validate)
Loads a scenario into the context from a FERS XML string.
Definition api.cpp:325
void fers_log(fers_log_level_t level, const char *message)
Submits a log message to the library's unified logging system.
Definition api.cpp:225
char * fers_get_last_warning_messages_json()
Returns the last deduplicated rotation-unit warning list for the calling thread as JSON.
Definition api.cpp:740
int fers_update_waveform_from_json(fers_context_t *context, const char *json)
Updates a single waveform from JSON without full context recreation.
Definition api.cpp:537
int fers_update_transmitter_from_json(fers_context_t *context, uint64_t id, const char *json)
Updates a single transmitter from JSON without full context recreation.
Definition api.cpp:560
char * fers_get_last_error_message()
Retrieves the last error message that occurred on the current thread.
Definition api.cpp:728
void fers_free_interpolated_motion_path(fers_interpolated_path_t *path)
Frees the memory allocated for an interpolated motion path.
Definition api.cpp:933
int fers_update_parameters_from_json(fers_context_t *context, const char *json)
Updates the global simulation parameters from JSON without full context recreation.
Definition api.cpp:486
int fers_generate_kml(const fers_context_t *context, const char *output_kml_filepath)
Generates a KML file for visualizing the scenario in the context.
Definition api.cpp:798
int fers_set_output_directory(fers_context_t *context, const char *out_dir)
Sets the output directory for simulation results.
Definition api.cpp:252
void fers_context_destroy(fers_context_t *context)
Destroys a FERS simulation context and releases all associated memory.
Definition api.cpp:108
static void handle_api_exception(const std::exception &e, const std::string &function_name)
Centralized exception handler for the C-API boundary.
Definition api.cpp:63
fers_context_t * fers_context_create()
Creates a new FERS simulation context.
Definition api.cpp:88
int fers_update_target_from_json(fers_context_t *context, uint64_t id, const char *json)
Updates a single target from JSON without full context recreation.
Definition api.cpp:610
void fers_free_preview_links(fers_visual_link_list_t *list)
Frees the memory allocated for a preview link list.
Definition api.cpp:1206
int fers_update_receiver_from_json(fers_context_t *context, uint64_t id, const char *json)
Updates a single receiver from JSON without full context recreation.
Definition api.cpp:585
fers_interpolated_path_t * fers_get_interpolated_motion_path(const fers_motion_waypoint_t *waypoints, const size_t waypoint_count, const fers_interp_type_t interp_type, const size_t num_points)
Calculates an interpolated motion path from a set of waypoints.
Definition api.cpp:858
int fers_run_simulation(fers_context_t *context, fers_progress_callback_t callback, void *user_data)
Runs the simulation defined in the provided context.
Definition api.cpp:760
void fers_free_string(char *str)
Frees a string that was allocated and returned by the libfers API.
Definition api.cpp:752
fers_log_level_t fers_get_log_level()
Returns the current internal logger level.
Definition api.cpp:212
static void begin_warning_capture() noexcept
Definition api.cpp:69
math::RotationPath::InterpType to_cpp_rot_interp_type(const fers_interp_type_t type)
Definition api.cpp:843
fers_antenna_pattern_data_t * fers_get_antenna_pattern(const fers_context_t *context, const uint64_t antenna_id, const size_t az_samples, const size_t el_samples, const double frequency_hz)
Samples the gain pattern of a specified antenna and provides the data.
Definition api.cpp:1040
thread_local std::vector< std::string > last_warning_messages
Definition api.cpp:52
int fers_configure_logging(fers_log_level_t level, const char *log_file_path)
Configures the internal logger.
Definition api.cpp:188
void fers_free_interpolated_rotation_path(fers_interpolated_rotation_path_t *path)
Frees the memory allocated for an interpolated rotation path.
Definition api.cpp:1029
math::Path::InterpType to_cpp_interp_type(const fers_interp_type_t type)
Definition api.cpp:829
void fers_set_log_callback(fers_log_callback_t callback, void *user_data)
Registers a callback for formatted log lines.
Definition api.cpp:214
int fers_load_scenario_from_xml_file(fers_context_t *context, const char *xml_filepath, const int validate)
Loads a scenario into the context from a FERS XML file.
Definition api.cpp:274
fers_visual_link_list_t * fers_calculate_preview_links(const fers_context_t *context, const double time)
Calculates visual links for a specific simulation time.
Definition api.cpp:1137
char * fers_get_scenario_as_json(fers_context_t *context)
Serializes the current simulation scenario into a JSON string.
Definition api.cpp:370
static void complete_warning_capture()
Definition api.cpp:75
int fers_update_monostatic_from_json(fers_context_t *context, const char *json)
Updates a monostatic radar from JSON without full context recreation.
Definition api.cpp:635
int fers_update_platform_from_json(fers_context_t *context, uint64_t id, const char *json)
Updates a single platform's paths and name from JSON without full context recreation.
Definition api.cpp:450
int fers_update_timing_from_json(fers_context_t *context, uint64_t id, const char *json)
Updates a single timing source from JSON without full context recreation.
Definition api.cpp:665
static logging::Level map_api_log_level(fers_log_level_t level)
Definition api.cpp:118
fers_interpolated_rotation_path_t * fers_get_interpolated_rotation_path(const fers_rotation_waypoint_t *waypoints, const size_t waypoint_count, const fers_interp_type_t interp_type, const fers_angle_unit_t angle_unit, const size_t num_points)
Calculates an interpolated rotation path from a set of waypoints.
Definition api.cpp:942
static fers_log_level_t map_internal_log_level(logging::Level level)
Definition api.cpp:141
int fers_update_scenario_from_json(fers_context_t *context, const char *scenario_json)
Updates the simulation scenario from a JSON string.
Definition api.cpp:689
char * fers_get_last_output_metadata_json(fers_context_t *context)
Returns JSON metadata for the most recent simulation output files.
Definition api.cpp:427
int fers_set_thread_count(unsigned num_threads)
Sets the number of worker threads for the simulation.
Definition api.cpp:233
thread_local std::string last_error_message
Definition api.cpp:51
fers_angle_unit_t
Units used for external rotation angles and rates.
Definition api.h:457
@ FERS_ANGLE_UNIT_RAD
Definition api.h:459
void(* fers_progress_callback_t)(const char *message, int current, int total, void *user_data)
A function pointer type for progress reporting callbacks.
Definition api.h:40
void(* fers_log_callback_t)(fers_log_level_t level, const char *line, void *user_data)
A function pointer type for receiving formatted log lines.
Definition api.h:103
fers_log_level_t
Log levels for the FERS library.
Definition api.h:86
@ FERS_LOG_FATAL
Definition api.h:92
@ FERS_LOG_DEBUG
Definition api.h:88
@ FERS_LOG_ERROR
Definition api.h:91
@ FERS_LOG_OFF
Definition api.h:93
@ FERS_LOG_INFO
Definition api.h:89
@ FERS_LOG_TRACE
Definition api.h:87
@ FERS_LOG_WARNING
Definition api.h:90
fers_interp_type_t
Defines the interpolation methods available for path generation.
Definition api.h:447
@ FERS_INTERP_CUBIC
Definition api.h:450
@ FERS_INTERP_STATIC
Definition api.h:448
@ FERS_INTERP_LINEAR
Definition api.h:449
struct fers_context fers_context_t
Definition api.h:26
@ FERS_LINK_BISTATIC_TX_TGT
Definition api.h:595
@ FERS_LINK_MONOSTATIC
Definition api.h:594
@ FERS_LINK_BISTATIC_TGT_RX
Definition api.h:596
@ FERS_LINK_DIRECT_TX_RX
Definition api.h:597
@ FERS_LINK_WEAK
Definition api.h:586
@ FERS_LINK_STRONG
Definition api.h:585
Header for radar channel propagation and interaction models.
Manages the lifetime and state of a single FERS simulation scenario.
void setOutputDir(std::string dir)
Sets the output directory for simulation results.
core::World * getWorld() const noexcept
Retrieves a pointer to the simulation world.
std::string getLastOutputMetadataJson() const
Abstract base class representing an antenna.
virtual RealType getGain(const math::SVec3 &angle, const math::SVec3 &refangle, RealType wavelength) const =0
Computes the gain of the antenna based on the input angle and reference angle.
radar::Target * findTarget(const SimId id)
Finds a target by ID.
Definition world.cpp:120
radar::Receiver * findReceiver(const SimId id)
Finds a receiver by ID.
Definition world.cpp:112
radar::Transmitter * findTransmitter(const SimId id)
Finds a transmitter by ID.
Definition world.cpp:104
radar::Platform * findPlatform(const SimId id)
Finds a platform by ID.
Definition world.cpp:94
void log(Level level, const std::string &message, const std::source_location &location=std::source_location::current()) noexcept
Logs a message with a specific log level and source location.
Definition logging.cpp:45
std::expected< void, std::string > logToFile(const std::string &filePath) noexcept
Sets the log file path to log messages to a file.
Definition logging.cpp:90
void setCallback(Callback callback, void *user_data) noexcept
Sets an optional callback that receives each formatted log line.
Definition logging.cpp:83
void setLevel(Level level) noexcept
Sets the logging level.
Definition logging.cpp:25
Represents a path with coordinates and allows for various interpolation methods.
Definition path.h:30
Vec3 getPosition(RealType t) const
Retrieves the position at a given time along the path.
Definition path.cpp:36
void setInterp(InterpType settype) noexcept
Changes the interpolation type.
Definition path.cpp:164
InterpType
Types of interpolation supported by the Path class.
Definition path.h:36
Vec3 getVelocity(RealType t) const
Retrieves the velocity at a given time along the path.
Definition path.cpp:60
void addCoord(const Coord &coord) noexcept
Adds a coordinate to the path.
Definition path.cpp:27
void finalize()
Finalizes the path, preparing it for interpolation.
Definition path.cpp:147
Manages rotational paths with different interpolation techniques.
void finalize()
Finalizes the rotation path for interpolation.
void setInterp(InterpType setinterp) noexcept
Sets the interpolation type for the path.
SVec3 getPosition(RealType t) const
Gets the rotational position at a given time.
InterpType
Enumeration for types of interpolation.
void addCoord(const RotationCoord &coord) noexcept
Adds a rotation coordinate to the path.
A class representing a vector in spherical coordinates.
RealType elevation
The elevation angle of the vector.
RealType azimuth
The azimuth angle of the vector.
A class representing a vector in rectangular coordinates.
RealType x
The x component of the vector.
RealType z
The z component of the vector.
RealType y
The y component of the vector.
A simple thread pool implementation.
Definition thread_pool.h:29
static bool generateKml(const core::World &world, const std::string &outputKmlPath)
Generates a KML file from a pre-built simulation world.
double RealType
Type for real numbers.
Definition config.h:27
constexpr RealType PI
Mathematical constant π (pi).
Definition config.h:43
Internal C++ class that encapsulates the state of a simulation instance.
Provides functions to serialize and deserialize the simulation world to/from JSON.
KML file generator for geographical visualization of FERS scenarios.
Header file for the logging system.
#define LOG(level,...)
Definition logging.h:19
OutputMetadata runEventDrivenSim(World *world, pool::ThreadPool &pool, const std::function< void(const std::string &, int, int)> &progress_callback, const std::string &output_dir)
Runs the unified, event-driven radar simulation.
@ WARNING
Warning level for potentially harmful situations.
@ FATAL
Fatal level for severe error events.
@ TRACE
Trace level for detailed debugging information.
@ INFO
Info level for informational messages.
@ OFF
Special level to disable all logging.
@ ERROR
Error level for error events.
@ DEBUG
Debug level for general debugging information.
Logger logger
Externally available logger object.
Definition logging.cpp:23
unsigned renderThreads() noexcept
Get the number of worker threads.
Definition parameters.h:145
std::expected< void, std::string > setThreads(const unsigned threads) noexcept
Set the number of worker threads.
Definition parameters.h:260
@ Radians
Compass azimuth and elevation expressed in radians.
@ Degrees
Compass azimuth and elevation expressed in degrees.
Parameters params
Definition parameters.h:85
RealType c() noexcept
Get the speed of light.
Definition parameters.h:91
RealType internal_elevation_to_external(const RealType elevation, const params::RotationAngleUnit unit) noexcept
math::RotationCoord external_rotation_to_internal(const RealType azimuth, const RealType elevation, const RealType time, const params::RotationAngleUnit unit) noexcept
RealType internal_azimuth_to_external(const RealType azimuth, const params::RotationAngleUnit unit) noexcept
std::vector< std::string > take_captured_warnings()
void maybe_warn_about_rotation_value(const RealType value, const params::RotationAngleUnit declared_unit, const ValueKind kind, const std::string_view source, const std::string_view owner, const std::string_view field)
void update_platform_paths_from_json(const nlohmann::json &j, radar::Platform *plat)
Updates a platform's motion and rotation paths from JSON.
void update_parameters_from_json(const nlohmann::json &j, std::mt19937 &masterSeeder)
Updates global simulation parameters from JSON.
void json_to_world(const nlohmann::json &j, core::World &world, std::mt19937 &masterSeeder)
Deserializes a nlohmann::json object and reconstructs the simulation world.
void update_receiver_from_json(const nlohmann::json &j, radar::Receiver *rx, core::World &world, std::mt19937 &)
Updates a receiver from JSON without full context recreation.
void update_timing_from_json(const nlohmann::json &j, core::World &world, const SimId id)
Updates a timing source from JSON without full context recreation.
void update_monostatic_from_json(const nlohmann::json &j, radar::Transmitter *tx, radar::Receiver *rx, core::World &world, std::mt19937 &masterSeeder)
Updates a monostatic radar from JSON without full context recreation.
void update_transmitter_from_json(const nlohmann::json &j, radar::Transmitter *tx, core::World &world, std::mt19937 &)
Updates a transmitter from JSON without full context recreation.
void parseSimulation(const std::string &filename, core::World *world, const bool validate, std::mt19937 &masterSeeder)
Parses a simulation configuration from an XML file.
void update_antenna_from_json(const nlohmann::json &j, antenna::Antenna *ant, core::World &world)
Updates an antenna from JSON without full context recreation.
void update_target_from_json(const nlohmann::json &j, radar::Target *existing_tgt, core::World &world, std::mt19937 &)
Updates a target from JSON without full context recreation.
nlohmann::json world_to_json(const core::World &world)
Serializes the entire simulation world into a nlohmann::json object.
void parseSimulationFromString(const std::string &xmlContent, core::World *world, const bool validate, std::mt19937 &masterSeeder)
Parses a simulation configuration directly from an XML string in memory.
std::string world_to_xml_string(const core::World &world)
Serializes the entire simulation world into an XML formatted string.
std::unique_ptr< fers_signal::RadarSignal > parse_waveform_from_json(const nlohmann::json &j)
Parses a Waveform from JSON.
std::vector< PreviewLink > calculatePreviewLinks(const core::World &world, const RealType time)
Calculates all visual links for the current world state at a specific time.
@ DirectTxRx
Interference path.
@ Monostatic
Combined Tx/Rx path.
@ BistaticTgtRx
Scattered path.
@ BistaticTxTgt
Illuminator path.
Defines the Parameters struct and provides methods for managing simulation parameters.
Provides the definition and functionality of the Path class for handling coordinate-based paths with ...
Classes for handling radar waveforms and signals.
Defines the RotationPath class for handling rotational paths with different interpolation types.
uint64_t SimId
64-bit Unique Simulation ID.
Definition sim_id.h:18
Header file for the main simulation runner.
Represents a sampled 2D antenna gain pattern.
Definition api.h:402
A container for an array of interpolated motion path points.
Definition api.h:515
fers_interpolated_point_t * points
Definition api.h:516
Represents a single interpolated point on a motion path.
Definition api.h:490
A container for an array of interpolated rotation path points.
Definition api.h:525
fers_interpolated_rotation_point_t * points
Definition api.h:526
Represents a single interpolated point on a rotation path.
Definition api.h:504
Represents a single waypoint for a motion path.
Definition api.h:467
double x
X coordinate in meters (East in ENU).
Definition api.h:469
double time
Time in seconds.
Definition api.h:468
double y
Y coordinate in meters (North in ENU).
Definition api.h:470
double z
Z coordinate in meters (Up/Altitude in ENU).
Definition api.h:471
Represents a single waypoint for a rotation path.
Definition api.h:479
double time
Time in seconds.
Definition api.h:480
Represents a position in 3D space with an associated time.
Definition coord.h:24
std::optional< unsigned > random_seed
Random seed for simulation.
Definition parameters.h:70
A simple thread pool implementation.
High-level facade for parsing XML configuration files into the FERS simulation environment.
Provides functions to serialize the simulation world back into the FERS XML format.