FERS 1.0.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
channel_model.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 channel_model.cpp
10 * @brief Implementation of radar channel propagation and interaction models.
11 *
12 * This file provides the implementations for the functions that model the radar
13 * channel, as declared in channel_model.h. It contains the core physics calculations
14 * that determine signal properties based on geometry, velocity, and object characteristics.
15 */
16
17#include "channel_model.h"
18
19#include <cmath>
20
21#include "core/logging.h"
22#include "core/parameters.h"
23#include "core/world.h"
25#include "math/geometry_ops.h"
26#include "radar/radar_obj.h"
27#include "radar/receiver.h"
28#include "radar/target.h"
29#include "radar/transmitter.h"
30#include "serial/response.h"
31#include "signal/radar_signal.h"
32#include "timing/timing.h"
33
35using logging::Level;
36using math::SVec3;
37using math::Vec3;
38using radar::Receiver;
39using radar::Target;
41
42namespace
43{
44 /**
45 * @struct LinkGeometry
46 * @brief Holds geometric properties of a path segment between two points.
47 */
48 struct LinkGeometry
49 {
50 Vec3 u_vec; ///< Unit vector pointing from Source to Destination.
51 RealType dist{}; ///< Distance between Source and Destination.
52 };
53
54 /**
55 * @brief Computes the geometry (distance and direction) between two points.
56 * @param p_from Starting position.
57 * @param p_to Ending position.
58 * @return LinkGeometry containing distance and unit vector.
59 * @throws RangeError if the distance is too small (<= EPSILON).
60 */
61 LinkGeometry computeLink(const Vec3& p_from, const Vec3& p_to)
62 {
63 const Vec3 vec = p_to - p_from;
64 const RealType dist = vec.length();
65
66 if (dist <= EPSILON)
67 {
68 // LOG(Level::FATAL) is handled by the caller or generic exception handler if needed,
69 // but for RangeError strictly we just throw here to keep it pure.
70 // However, existing code logged FATAL before throwing.
71 // We'll throw RangeError, and let callers decide if they want to log or return 0.
73 }
74
75 return {vec / dist, dist};
76 }
77
78 /**
79 * @brief Calculates the antenna gain for a specific direction and time.
80 * @param radar The radar object (Transmitter or Receiver).
81 * @param direction_vec The unit vector pointing AWAY from the antenna towards the target/receiver.
82 * @param time The simulation time for rotation lookup.
83 * @param lambda The signal wavelength.
84 * @return The linear gain value.
85 */
86 RealType computeAntennaGain(const radar::Radar* radar, const Vec3& direction_vec, RealType time, RealType lambda)
87 {
88 return radar->getGain(SVec3(direction_vec), radar->getRotation(time), lambda);
89 }
90
91 /**
92 * @brief Computes the power scaling factor for a direct path (Friis Transmission Equation).
93 * @param tx_gain Transmitter gain (linear).
94 * @param rx_gain Receiver gain (linear).
95 * @param lambda Wavelength (meters).
96 * @param dist Distance (meters).
97 * @param no_prop_loss If true, distance-based attenuation is ignored.
98 * @return The power scaling factor (Pr / Pt).
99 */
100 RealType computeDirectPathPower(RealType tx_gain, RealType rx_gain, RealType lambda, RealType dist,
101 bool no_prop_loss)
102 {
103 const RealType numerator = tx_gain * rx_gain * lambda * lambda;
104 RealType denominator = 16.0 * PI * PI; // (4 * PI)^2
105
106 if (!no_prop_loss)
107 {
108 denominator *= dist * dist;
109 }
110
111 return numerator / denominator;
112 }
113
114 /**
115 * @brief Computes the power scaling factor for a reflected path (Bistatic Radar Range Equation).
116 * @param tx_gain Transmitter gain (linear).
117 * @param rx_gain Receiver gain (linear).
118 * @param rcs Target Radar Cross Section (m^2).
119 * @param lambda Wavelength (meters).
120 * @param r_tx Distance from Transmitter to Target.
121 * @param r_rx Distance from Target to Receiver.
122 * @param no_prop_loss If true, distance-based attenuation is ignored.
123 * @return The power scaling factor (Pr / Pt).
124 */
125 RealType computeReflectedPathPower(RealType tx_gain, RealType rx_gain, RealType rcs, RealType lambda, RealType r_tx,
126 RealType r_rx, bool no_prop_loss)
127 {
128 const RealType numerator = tx_gain * rx_gain * rcs * lambda * lambda;
129 RealType denominator = 64.0 * PI * PI * PI; // (4 * PI)^3
130
131 if (!no_prop_loss)
132 {
133 denominator *= r_tx * r_tx * r_rx * r_rx;
134 }
135
136 return numerator / denominator;
137 }
138
139 /**
140 * @brief Computes the non-coherent phase shift due to timing offsets.
141 *
142 * Used for CW simulation where LO effects are modeled analytically.
143 *
144 * @param tx The transmitter.
145 * @param rx The receiver.
146 * @param time The current simulation time.
147 * @return The phase shift in radians.
148 */
149 RealType computeTimingPhase(const Transmitter* tx, const Receiver* rx, RealType time)
150 {
151 const auto tx_timing = tx->getTiming();
152 const auto rx_timing = rx->getTiming();
153 const RealType delta_f = tx_timing->getFreqOffset() - rx_timing->getFreqOffset();
154 const RealType delta_phi = tx_timing->getPhaseOffset() - rx_timing->getPhaseOffset();
155 return 2 * PI * delta_f * time + delta_phi;
156 }
157
158 // Helper to check noise floor threshold (Signal > kTB)
159 bool isSignalStrong(RealType power_watts, RealType temp_kelvin)
160 {
161 // Use configured rate or default to 1Hz if unconfigured to prevent divide-by-zero or silly values
162 const RealType bw = params::rate() > 0 ? params::rate() : 1.0;
163 const RealType noise_floor = params::boltzmannK() * (temp_kelvin > 0 ? temp_kelvin : 290.0) * bw;
164 return power_watts > noise_floor;
165 }
166
167 /**
168 * @brief Converts power in watts to decibels milliwatts (dBm).
169 *
170 * @param watts Power in watts.
171 * @return Power in dBm. Returns -999.0 dBm for non-positive input.
172 */
173 RealType wattsToDbm(RealType watts)
174 {
175 if (watts <= 0)
176 {
177 return -999.0;
178 }
179 return 10.0 * std::log10(watts * 1000.0);
180 }
181
182 /**
183 * @brief Converts power in watts to decibels (dB).
184 *
185 * @param watts Power in watts.
186 * @return Power in decibels (dB). Returns -999.0 dB for non-positive input.
187 */
188 RealType wattsToDb(RealType watts)
189 {
190 if (watts <= 0)
191 {
192 return -999.0;
193 }
194 return 10.0 * std::log10(watts);
195 }
196
197 /**
198 * @brief Checks if a component is active at the given time based on its schedule.
199 *
200 * @param schedule The component's operating schedule.
201 * @param time The current simulation time.
202 * @return true If the schedule is empty (implied always on) or if time is within a period.
203 * @return false If the schedule is populated but the time is outside all periods.
204 */
205 bool isComponentActive(const std::vector<radar::SchedulePeriod>& schedule, RealType time)
206 {
207 if (schedule.empty())
208 {
209 return true;
210 }
211 for (const auto& period : schedule)
212 {
213 if (time >= period.start && time <= period.end)
214 {
215 return true;
216 }
217 }
218 return false;
219 }
220}
221
222namespace simulation
223{
224 void solveRe(const Transmitter* trans, const Receiver* recv, const Target* targ,
225 const std::chrono::duration<RealType>& time, const RadarSignal* wave, ReResults& results)
226 {
227 // Note: RangeError log messages are handled by the original catch block in calculateResponse
228 // or explicitly here if strict adherence to original logging is required.
229 // Using the helper logic which throws RangeError on epsilon check.
230
231 const RealType t_val = time.count();
232 const auto p_tx = trans->getPosition(t_val);
233 const auto p_rx = recv->getPosition(t_val);
234 const auto p_tgt = targ->getPosition(t_val);
235
236 // Link 1: Tx -> Target
237 LinkGeometry link_tx_tgt;
238 // Link 2: Target -> Rx (Note: Vector for calculation is Tgt->Rx)
239 LinkGeometry link_tgt_rx;
240
241 try
242 {
243 link_tx_tgt = computeLink(p_tx, p_tgt);
244 link_tgt_rx = computeLink(p_tgt, p_rx); // Vector Tgt -> Rx
245 }
246 catch (const RangeError&)
247 {
248 LOG(Level::FATAL, "Transmitter or Receiver too close to Target for accurate simulation");
249 throw;
250 }
251
252 results.delay = (link_tx_tgt.dist + link_tgt_rx.dist) / params::c();
253
254 // Calculate RCS
255 // Note: getRcs expects (InAngle, OutAngle).
256 // InAngle: Tx -> Tgt (link_tx_tgt.u_vec)
257 // OutAngle: Rx -> Tgt (Opposite of Tgt->Rx, so -link_tgt_rx.u_vec)
258 // This matches existing logic.
259 SVec3 in_angle(link_tx_tgt.u_vec);
260 SVec3 out_angle(-link_tgt_rx.u_vec);
261 const auto rcs = targ->getRcs(in_angle, out_angle, t_val);
262
263 const auto wavelength = params::c() / wave->getCarrier();
264
265 // Tx Gain: Direction Tx -> Tgt
266 const auto tx_gain = computeAntennaGain(trans, link_tx_tgt.u_vec, t_val, wavelength);
267 // Rx Gain: Direction Rx -> Tgt (Opposite of Tgt->Rx).
268 // Time is time + delay.
269 const auto rx_gain = computeAntennaGain(recv, -link_tgt_rx.u_vec, results.delay + t_val, wavelength);
270
271 const bool no_loss = recv->checkFlag(Receiver::RecvFlag::FLAG_NOPROPLOSS);
272 results.power =
273 computeReflectedPathPower(tx_gain, rx_gain, rcs, wavelength, link_tx_tgt.dist, link_tgt_rx.dist, no_loss);
274
275 results.phase = -results.delay * 2 * PI * wave->getCarrier();
276 }
277
278 void solveReDirect(const Transmitter* trans, const Receiver* recv, const std::chrono::duration<RealType>& time,
279 const RadarSignal* wave, ReResults& results)
280 {
281 const RealType t_val = time.count();
282 const auto p_tx = trans->getPosition(t_val);
283 const auto p_rx = recv->getPosition(t_val);
284
285 LinkGeometry link;
286 try
287 {
288 link = computeLink(p_tx, p_rx); // Vector Tx -> Rx
289 }
290 catch (const RangeError&)
291 {
292 LOG(Level::FATAL, "Transmitter or Receiver too close for accurate simulation");
293 throw;
294 }
295
296 results.delay = link.dist / params::c();
297 const RealType wavelength = params::c() / wave->getCarrier();
298
299 // Discrepancy Fix: Original code used (Rx - Tx) for Receiver Gain but (Tx - Rx) logic for Transmitter gain
300 // was ambiguous/incorrect (using `tpos - rpos` which is Rx->Tx).
301 // Per `calculateDirectPathContribution` preference:
302 // Tx Gain uses Vector Tx -> Rx.
303 // Rx Gain uses Vector Rx -> Tx.
304
305 const auto tx_gain = computeAntennaGain(trans, link.u_vec, t_val, wavelength);
306 const auto rx_gain = computeAntennaGain(recv, -link.u_vec, t_val + results.delay, wavelength);
307
308 const bool no_loss = recv->checkFlag(Receiver::RecvFlag::FLAG_NOPROPLOSS);
309 results.power = computeDirectPathPower(tx_gain, rx_gain, wavelength, link.dist, no_loss);
310
311 results.phase = -results.delay * 2 * PI * wave->getCarrier();
312 }
313
315 {
316 // Check for co-location to prevent singularities.
317 // If they share the same platform, we assume they are isolated (no leakage) or explicit
318 // monostatic handling is required (which is not modeled via the far-field path).
319 if (trans->getPlatform() == recv->getPlatform())
320 {
321 return {0.0, 0.0};
322 }
323
324 const auto p_tx = trans->getPlatform()->getPosition(timeK);
325 const auto p_rx = recv->getPlatform()->getPosition(timeK);
326
327 LinkGeometry link;
328 try
329 {
330 link = computeLink(p_tx, p_rx);
331 }
332 catch (const RangeError&)
333 {
334 return {0.0, 0.0};
335 }
336
337 const RealType tau = link.dist / params::c();
338 const auto signal = trans->getSignal();
339 const RealType carrier_freq = signal->getCarrier();
340 const RealType lambda = params::c() / carrier_freq;
341
342 // Tx Gain: Direction Tx -> Rx
343 const RealType tx_gain = computeAntennaGain(trans, link.u_vec, timeK, lambda);
344 // Rx Gain: Direction Rx -> Tx (-u_vec)
345 const RealType rx_gain = computeAntennaGain(recv, -link.u_vec, timeK + tau, lambda);
346
347 const bool no_loss = recv->checkFlag(Receiver::RecvFlag::FLAG_NOPROPLOSS);
348 const RealType scaling_factor = computeDirectPathPower(tx_gain, rx_gain, lambda, link.dist, no_loss);
349
350 // Include Signal Power
351 const RealType amplitude = std::sqrt(signal->getPower() * scaling_factor);
352
353 // Carrier Phase
354 const RealType phase = -2 * PI * carrier_freq * tau;
355 ComplexType contribution = std::polar(amplitude, phase);
356
357 // Non-coherent Local Oscillator Effects
358 const RealType non_coherent_phase = computeTimingPhase(trans, recv, timeK);
359 contribution *= std::polar(1.0, non_coherent_phase);
360
361 return contribution;
362 }
363
365 const RealType timeK)
366 {
367 // Check for co-location involving the target.
368 // We do not model a platform tracking itself (R=0) or illuminating itself (R=0).
369 if (trans->getPlatform() == targ->getPlatform() || recv->getPlatform() == targ->getPlatform())
370 {
371 return {0.0, 0.0};
372 }
373
374 const auto p_tx = trans->getPlatform()->getPosition(timeK);
375 const auto p_rx = recv->getPlatform()->getPosition(timeK);
376 const auto p_tgt = targ->getPlatform()->getPosition(timeK);
377
378 LinkGeometry link_tx_tgt;
379 LinkGeometry link_tgt_rx;
380
381 try
382 {
383 link_tx_tgt = computeLink(p_tx, p_tgt);
384 link_tgt_rx = computeLink(p_tgt, p_rx);
385 }
386 catch (const RangeError&)
387 {
388 return {0.0, 0.0};
389 }
390
391 const RealType tau = (link_tx_tgt.dist + link_tgt_rx.dist) / params::c();
392 const auto signal = trans->getSignal();
393 const RealType carrier_freq = signal->getCarrier();
394 const RealType lambda = params::c() / carrier_freq;
395
396 // RCS Lookups: In (Tx->Tgt), Out (Rx->Tgt = - (Tgt->Rx))
397 SVec3 in_angle(link_tx_tgt.u_vec);
398 SVec3 out_angle(-link_tgt_rx.u_vec);
399 const RealType rcs = targ->getRcs(in_angle, out_angle, timeK);
400
401 // Tx Gain: Direction Tx -> Tgt
402 const RealType tx_gain = computeAntennaGain(trans, link_tx_tgt.u_vec, timeK, lambda);
403 // Rx Gain: Direction Rx -> Tgt (- (Tgt->Rx)). Time: timeK + tau.
404 const RealType rx_gain = computeAntennaGain(recv, -link_tgt_rx.u_vec, timeK + tau, lambda);
405
406 const bool no_loss = recv->checkFlag(Receiver::RecvFlag::FLAG_NOPROPLOSS);
407 const RealType scaling_factor =
408 computeReflectedPathPower(tx_gain, rx_gain, rcs, lambda, link_tx_tgt.dist, link_tgt_rx.dist, no_loss);
409
410 // Include Signal Power
411 const RealType amplitude = std::sqrt(signal->getPower() * scaling_factor);
412
413 const RealType phase = -2 * PI * carrier_freq * tau;
414 ComplexType contribution = std::polar(amplitude, phase);
415
416 // Non-coherent Local Oscillator Effects
417 const RealType non_coherent_phase = computeTimingPhase(trans, recv, timeK);
418 contribution *= std::polar(1.0, non_coherent_phase);
419
420 return contribution;
421 }
422
423 std::unique_ptr<serial::Response> calculateResponse(const Transmitter* trans, const Receiver* recv,
424 const RadarSignal* signal, const RealType startTime,
425 const Target* targ)
426 {
427 // If calculating direct path (no target) and components are co-located:
428 // 1. If explicitly attached (monostatic), skip (internal leakage handled elsewhere).
429 // 2. If independent but on the same platform, distance is 0. Far-field logic (1/R^2)
430 // diverges. We skip calculation to avoid RangeError crashes, assuming
431 // no direct coupling/interference for co-located far-field antennas.
432 if (targ == nullptr && (trans->getAttached() == recv || trans->getPlatform() == recv->getPlatform()))
433 {
434 LOG(Level::TRACE, "Skipping direct path calculation for co-located Transmitter {} and Receiver {}",
435 trans->getName(), recv->getName());
436 return nullptr;
437 }
438
439 // If calculating reflected path and target is co-located with either Tx or Rx:
440 // Skip to avoid singularity. Simulating a radar tracking its own platform
441 // requires near-field clutter models, not point-target RCS models.
442 if (targ != nullptr &&
443 (targ->getPlatform() == trans->getPlatform() || targ->getPlatform() == recv->getPlatform()))
444 {
445 LOG(Level::TRACE,
446 "Skipping reflected path calculation for Target {} co-located with Transmitter {} or Receiver {}",
447 targ->getName(), trans->getName(), recv->getName());
448 return nullptr;
449 }
450
451 const auto start_time_chrono = std::chrono::duration<RealType>(startTime);
452 const auto end_time_chrono = start_time_chrono + std::chrono::duration<RealType>(signal->getLength());
453 const auto sample_time_chrono = std::chrono::duration<RealType>(1.0 / params::simSamplingRate());
454 const int point_count = static_cast<int>(std::ceil(signal->getLength() / sample_time_chrono.count()));
455
456 if (targ && point_count == 0)
457 {
458 LOG(Level::FATAL, "No time points are available for execution!");
459 throw std::runtime_error("No time points are available for execution!");
460 }
461
462 auto response = std::make_unique<serial::Response>(signal, trans);
463
464 try
465 {
466 for (int i = 0; i <= point_count; ++i)
467 {
468 const auto current_time =
469 i < point_count ? start_time_chrono + i * sample_time_chrono : end_time_chrono;
470
471 ReResults results{};
472 if (targ)
473 {
474 solveRe(trans, recv, targ, current_time, signal, results);
475 }
476 else
477 {
478 solveReDirect(trans, recv, current_time, signal, results);
479 }
480
481 interp::InterpPoint point{.power = results.power,
482 .time = current_time.count() + results.delay,
483 .delay = results.delay,
484 .phase = results.phase};
485 response->addInterpPoint(point);
486 }
487 }
488 catch (const RangeError&)
489 {
490 LOG(Level::FATAL, "Receiver or Transmitter too close for accurate simulation");
491 throw; // Re-throw to be caught by the runner
492 }
493
494 return response;
495 }
496
497 std::vector<PreviewLink> calculatePreviewLinks(const core::World& world, const RealType time)
498 {
499 std::vector<PreviewLink> links;
500
501 // Default wavelength (1GHz) if no waveform is attached, to allow geometric visualization
502 const RealType lambda_default = 0.3;
503
504 for (const auto& tx : world.getTransmitters())
505 {
506 // 1. Check Transmitter Schedule
507 if (!isComponentActive(tx->getSchedule(), time))
508 {
509 continue;
510 }
511
512 const auto p_tx = tx->getPosition(time);
513 const auto* waveform = tx->getSignal();
514 const RealType pt = waveform ? waveform->getPower() : 0.0;
515 const RealType lambda = waveform ? (params::c() / waveform->getCarrier()) : lambda_default;
516
517 // --- PRE-CALCULATE ILLUMINATOR PATHS (Tx -> Tgt) ---
518 // These depend only on the Transmitter and Targets. We calculate them once per Tx
519 // to avoid duplication when multiple receivers are present.
520 // We do NOT filter out Monostatic transmitters here. Even in a monostatic setup,
521 // the "Illuminator" link provides distinct info (Power Density at Target) compared
522 // to the "Monostatic" link (Received Power at Rx). By being outside the Rx loop,
523 // it is guaranteed to be unique per Target.
524 for (const auto& tgt : world.getTargets())
525 {
526 const auto p_tgt = tgt->getPosition(time);
527 const Vec3 vec_tx_tgt = p_tgt - p_tx;
528 const RealType r1 = vec_tx_tgt.length();
529
530 if (r1 <= EPSILON)
531 continue;
532
533 const Vec3 u_tx_tgt = vec_tx_tgt / r1;
534 // Tx Gain: Tx -> Tgt
535 const RealType gt = computeAntennaGain(tx.get(), u_tx_tgt, time, lambda);
536
537 // Power Density at Target: S = (Pt * Gt) / (4 * pi * R1^2)
538 const RealType p_density = (pt * gt) / (4.0 * PI * r1 * r1);
539
540 links.push_back({.type = LinkType::BistaticTxTgt,
541 .quality = LinkQuality::Strong,
542 .label = std::format("{:.1f} dBW/m\u00B2", wattsToDb(p_density)),
543 .source_name = tx->getName(),
544 .dest_name = tgt->getName(),
545 .origin_name = tx->getName()});
546 }
547
548 for (const auto& rx : world.getReceivers())
549 {
550 // 2. Check Receiver Schedule
551 if (!isComponentActive(rx->getSchedule(), time))
552 {
553 continue;
554 }
555
556 const auto p_rx = rx->getPosition(time);
557 const bool is_monostatic = (tx->getAttached() == rx.get());
558 const bool no_loss = rx->checkFlag(Receiver::RecvFlag::FLAG_NOPROPLOSS);
559
560 if (is_monostatic)
561 {
562 // --- Monostatic Case ---
563 // Calculates the round-trip Received Power (dBm)
564 for (const auto& tgt : world.getTargets())
565 {
566 const auto p_tgt = tgt->getPosition(time);
567
568 // Use computeLink helper logic, but catch exception manually by checking dist
569 // to avoid try/catch overhead in render loop
570 const Vec3 vec_tx_tgt = p_tgt - p_tx;
571 const RealType dist = vec_tx_tgt.length();
572
573 if (dist <= EPSILON)
574 continue;
575
576 const Vec3 u_tx_tgt = vec_tx_tgt / dist; // Unit vec Tx -> Tgt
577
578 // Reusing internal helpers
579 // Tx Gain: Direction Tx -> Tgt
580 const RealType gt = computeAntennaGain(tx.get(), u_tx_tgt, time, lambda);
581 // Rx Gain: Direction Rx -> Tgt (which is -u_tx_tgt because Rx is at Tx)
582 const RealType gr = computeAntennaGain(rx.get(), u_tx_tgt, time, lambda);
583
584 // RCS: In = Tx->Tgt, Out = Tgt->Rx (which is -(Tx->Tgt))
585 SVec3 in_angle(u_tx_tgt);
586 SVec3 out_angle(-u_tx_tgt);
587 const RealType rcs = tgt->getRcs(in_angle, out_angle, time);
588
589 // Reusing Reflected Path Helper
590 // r_tx = dist, r_rx = dist
591 const RealType power_ratio =
592 computeReflectedPathPower(gt, gr, rcs, lambda, dist, dist, no_loss);
593
594 const RealType pr_watts = pt * power_ratio;
595
596 links.push_back(
597 {.type = LinkType::Monostatic,
598 .quality = isSignalStrong(pr_watts, rx->getNoiseTemperature()) ? LinkQuality::Strong
600 .label = std::format("{:.1f} dBm (RCS: {:.1f}m\u00B2)", wattsToDbm(pr_watts), rcs),
601 .source_name = tx->getName(), // Monostatic implies Tx/Rx is same platform/system
602 .dest_name = tgt->getName(),
603 .origin_name = tx->getName()});
604 }
605 }
606 else
607 {
608 // --- Bistatic Case ---
609
610 // 1. Direct Path (Interference)
611 if (!rx->checkFlag(Receiver::RecvFlag::FLAG_NODIRECT))
612 {
613 const Vec3 vec_direct = p_rx - p_tx;
614 const RealType dist = vec_direct.length();
615
616 if (dist > EPSILON)
617 {
618 const Vec3 u_tx_rx = vec_direct / dist;
619
620 // Tx Gain: Tx -> Rx
621 const RealType gt = computeAntennaGain(tx.get(), u_tx_rx, time, lambda);
622 // Rx Gain: Rx -> Tx (which is -u_tx_rx)
623 const RealType gr = computeAntennaGain(rx.get(), -u_tx_rx, time, lambda);
624
625 const RealType power_ratio = computeDirectPathPower(gt, gr, lambda, dist, no_loss);
626 const RealType pr_watts = pt * power_ratio;
627
628 links.push_back({.type = LinkType::DirectTxRx,
629 .quality = LinkQuality::Strong,
630 .label = std::format("Direct: {:.1f} dBm", wattsToDbm(pr_watts)),
631 .source_name = tx->getName(),
632 .dest_name = rx->getName(),
633 .origin_name = tx->getName()});
634 }
635 }
636
637 // 2. Reflected Path (Scattered Leg: Tgt -> Rx)
638 for (const auto& tgt : world.getTargets())
639 {
640 const auto p_tgt = tgt->getPosition(time);
641 const Vec3 vec_tx_tgt = p_tgt - p_tx;
642 const Vec3 vec_tgt_rx = p_rx - p_tgt; // Vector Tgt -> Rx
643
644 const RealType r1 = vec_tx_tgt.length();
645 const RealType r2 = vec_tgt_rx.length();
646
647 if (r1 <= EPSILON || r2 <= EPSILON)
648 continue;
649
650 const Vec3 u_tx_tgt = vec_tx_tgt / r1;
651 const Vec3 u_tgt_rx = vec_tgt_rx / r2;
652
653 // Tx Gain: Tx -> Tgt
654 const RealType gt = computeAntennaGain(tx.get(), u_tx_tgt, time, lambda);
655 // Rx Gain: Rx -> Tgt (Vector is Tgt->Rx, so look vector is -u_tgt_rx)
656 const RealType gr = computeAntennaGain(rx.get(), -u_tgt_rx, time, lambda);
657
658 // RCS
659 SVec3 in_angle(u_tx_tgt);
660 SVec3 out_angle(-u_tgt_rx); // Out angle is usually defined from Target OUTWARDS
661 const RealType rcs = tgt->getRcs(in_angle, out_angle, time);
662
663 const RealType power_ratio = computeReflectedPathPower(gt, gr, rcs, lambda, r1, r2, no_loss);
664 const RealType pr_watts = pt * power_ratio;
665
666 // Note: Illuminator leg (Tx->Tgt) was handled in the outer loop.
667
668 // Leg 2: Scattered
669 links.push_back({.type = LinkType::BistaticTgtRx,
670 .quality = isSignalStrong(pr_watts, rx->getNoiseTemperature())
673 .label = std::format("{:.1f} dBm", wattsToDbm(pr_watts)),
674 .source_name = tgt->getName(),
675 .dest_name = rx->getName(),
676 .origin_name = tx->getName()});
677 }
678 }
679 }
680 }
681 return links;
682 }
683}
Header for radar channel propagation and interaction models.
The World class manages the simulator environment.
Definition world.h:38
const std::vector< std::unique_ptr< radar::Target > > & getTargets() const noexcept
Retrieves the list of radar targets.
Definition world.h:143
const std::vector< std::unique_ptr< radar::Transmitter > > & getTransmitters() const noexcept
Retrieves the list of radar transmitters.
Definition world.h:163
const std::vector< std::unique_ptr< radar::Receiver > > & getReceivers() const noexcept
Retrieves the list of radar receivers.
Definition world.h:153
Class representing a radar signal with associated properties.
RealType getCarrier() const noexcept
Gets the carrier frequency of the radar signal.
RealType getLength() const noexcept
Gets the length of the radar signal.
RealType getPower() const noexcept
Gets the power of the radar signal.
A class representing a vector in spherical coordinates.
A class representing a vector in rectangular coordinates.
RealType length() const noexcept
Calculates the length (magnitude) of the vector.
const std::string & getName() const noexcept
Retrieves the name of the object.
Definition object.h:68
Platform * getPlatform() const noexcept
Retrieves the associated platform of the object.
Definition object.h:61
math::Vec3 getPosition(const RealType time) const
Retrieves the position of the object.
Definition object.h:46
math::Vec3 getPosition(const RealType time) const
Gets the position of the platform at a specific time.
Definition platform.h:74
Represents a radar system on a platform.
Definition radar_obj.h:46
const Radar * getAttached() const noexcept
Retrieves the attached radar object.
Definition radar_obj.h:71
std::shared_ptr< timing::Timing > getTiming() const
Retrieves the timing source for the radar.
Definition radar_obj.cpp:66
Manages radar signal reception and response processing.
Definition receiver.h:36
bool checkFlag(RecvFlag flag) const noexcept
Checks if a specific flag is set.
Definition receiver.h:86
const std::vector< SchedulePeriod > & getSchedule() const noexcept
Retrieves the list of active reception periods.
Definition receiver.h:255
RealType getNoiseTemperature() const noexcept
Retrieves the noise temperature of the receiver.
Definition receiver.h:93
Base class for radar targets.
Definition target.h:117
virtual RealType getRcs(math::SVec3 &inAngle, math::SVec3 &outAngle, RealType time) const =0
Gets the RCS value for the target.
Represents a radar transmitter system.
Definition transmitter.h:32
fers_signal::RadarSignal * getSignal() const noexcept
Retrieves the radar signal currently being transmitted.
Definition transmitter.h:68
const std::vector< SchedulePeriod > & getSchedule() const noexcept
Retrieves the list of active transmission periods.
Exception thrown when a range calculation fails, typically due to objects being too close.
double RealType
Type for real numbers.
Definition config.h:27
constexpr RealType EPSILON
Machine epsilon for real numbers.
Definition config.h:51
std::complex< RealType > ComplexType
Type for complex numbers.
Definition config.h:35
constexpr RealType PI
Mathematical constant π (pi).
Definition config.h:43
Classes and operations for 3D geometry.
Defines a structure to store interpolation point data for signal processing.
Header file for the logging system.
#define LOG(level,...)
Definition logging.h:19
RealType simSamplingRate() noexcept
Get the simulation sampling rate.
Definition parameters.h:103
RealType rate() noexcept
Get the rendering sample rate.
Definition parameters.h:109
RealType boltzmannK() noexcept
Get the Boltzmann constant.
Definition parameters.h:85
RealType c() noexcept
Get the speed of light.
Definition parameters.h:79
@ Weak
SNR < 0 dB (Geometric line of sight, but below noise floor)
ComplexType calculateDirectPathContribution(const Transmitter *trans, const Receiver *recv, const RealType timeK)
Calculates the complex envelope contribution for a direct propagation path (Tx -> Rx) at a specific t...
void solveReDirect(const Transmitter *trans, const Receiver *recv, const std::chrono::duration< RealType > &time, const RadarSignal *wave, ReResults &results)
Solves the radar equation for a direct path (Tx -> Rx).
void solveRe(const Transmitter *trans, const Receiver *recv, const Target *targ, const std::chrono::duration< RealType > &time, const RadarSignal *wave, ReResults &results)
Solves the bistatic radar equation for a reflected path (Tx -> Tgt -> Rx).
std::vector< PreviewLink > calculatePreviewLinks(const core::World &world, const RealType time)
Calculates all visual links for the current world state at a specific time.
std::unique_ptr< serial::Response > calculateResponse(const Transmitter *trans, const Receiver *recv, const RadarSignal *signal, const RealType startTime, const Target *targ)
Creates a Response object by simulating a signal's interaction over its duration.
ComplexType calculateReflectedPathContribution(const Transmitter *trans, const Receiver *recv, const Target *targ, const RealType timeK)
Calculates the complex envelope contribution for a reflected path (Tx -> Tgt -> Rx) at a specific tim...
@ 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.
Defines the Radar class and associated functionality.
Classes for handling radar waveforms and signals.
Radar Receiver class for managing signal reception and response handling.
Classes for managing radar signal responses.
Stores data for an interpolation point.
RealType power
Power level of the signal at the interpolation point.
Stores the intermediate results of a radar equation calculation for a single time point.
RealType delay
Signal propagation delay in seconds.
RealType power
Power scaling factor (dimensionless, relative to transmitted power).
RealType phase
Phase shift in radians due to propagation delay.
Defines classes for radar targets and their Radar Cross-Section (RCS) models.
Timing source for simulation objects.
Header file for the Transmitter class in the radar namespace.
Header file for the World class in the simulator.