CARLA
Loading...
Searching...
No Matches
PathLossModel.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma
2// de Barcelona (UAB).
3//
4// This work is licensed under the terms of the MIT license.
5// For a copy, see <https://opensource.org/licenses/MIT>.
6
7#include "PathLossModel.h"
8
9#include "Carla.h"
12
13#include "DrawDebugHelpers.h"
14#include "Math/UnrealMathUtility.h"
15
16#include <algorithm>
17#include <limits>
18#include <random>
19
21{
22 mRandomEngine = random_engine;
23 mActorOwner = Owner;
24}
25
26void PathLossModel::SetParams(const float TransmitPower,
27 const float ReceiverSensitivity,
28 const float Frequency,
29 const float combined_antenna_gain,
30 const float path_loss_exponent,
31 const float reference_distance_fspl,
32 const float filter_distance,
33 const bool use_etsi_fading,
34 const float custom_fading_stddev)
35{
36 this->TransmitPower = TransmitPower;
37 this->ReceiverSensitivity = ReceiverSensitivity;
38 this->path_loss_exponent = path_loss_exponent;
39 this->reference_distance_fspl = reference_distance_fspl;
40 this->filter_distance = filter_distance;
41 this->use_etsi_fading = use_etsi_fading;
42 this->custom_fading_stddev = custom_fading_stddev;
43 this->combined_antenna_gain = combined_antenna_gain;
44 // The parameter is named Frequency and shadows the member of the same name,
45 // so disambiguate the member writes with this->.
47 this->Frequency = this->Frequency_GHz * std::pow(10, 9);
49 // when reference distance is set, we prepare the FSPL for the reference distance to be used in LDPL
51}
52
54{
55 this->scenario = scenario;
56}
57
58std::map<AActor *, float> PathLossModel::GetReceiveActorPowerList()
59{
61}
62
63void PathLossModel::Simulate(const std::vector<ActorPowerPair> ActorList, UCarlaEpisode *CarlaEpisode, UWorld *World)
64{
65 // Set current world and episode
66 mWorld = World;
67 mCarlaEpisode = CarlaEpisode;
68
69 CurrentActorLocation = mActorOwner->GetTransform().GetLocation();
70 FVector OtherActorLocation;
72 float ReceivedPower = 0;
73
75
76 for (auto &actor_power_pair : ActorList)
77 {
78 const FCarlaActor *view = Registry.FindCarlaActor(actor_power_pair.first);
79 // ensure other actor is still alive
80 if (!view)
81 {
82 continue;
83 }
84 OtherActorLocation = actor_power_pair.first->GetTransform().GetLocation();
85
86 // calculate relative ht and hr respecting slope and elevation
87 // cm
88 // if objects are on a slope, minimum Z height of both is the reference to calculate transmitter height
89 double ref0 = std::min(CurrentActorLocation.Z, OtherActorLocation.Z);
90 // cm
91 double ht = CurrentActorLocation.Z - ref0;
92 double hr = OtherActorLocation.Z - ref0;
93 // localize to common ref0 as ground
94 FVector source_rel = CurrentActorLocation;
95 FVector dest_rel = OtherActorLocation;
96
97 double Distance3d = FVector::Distance(source_rel, dest_rel) / 100.0f; // From cm to m
98 // to meters
99 ht = ht / 100.0f;
100 hr = hr / 100.0f;
101
102 if (Distance3d < filter_distance) // maybe change this for highway
103 {
104 // Two actors sharing a location give Distance3d == 0, which would feed
105 // log10(0) = -inf (and a divide-by-zero in the NLOSb branch) into the
106 // path-loss math. Floor it at the FSPL reference distance before any
107 // downstream log10/division consumes it.
108 const double Distance3dGuarded = std::max(Distance3d, static_cast<double>(reference_distance_fspl));
109 float OtherTransmitPower = actor_power_pair.second;
110 ReceivedPower = CalculateReceivedPower(actor_power_pair.first,
111 OtherTransmitPower,
113 OtherActorLocation,
114 Distance3dGuarded,
115 ht,
116 hr,
117 ref0);
118 if (ReceivedPower > -1.0 * std::numeric_limits<float>::max())
119 {
120 mReceiveActorPowerList.insert(std::make_pair(actor_power_pair.first, ReceivedPower));
121 }
122 }
123 }
124}
125
127 const float OtherTransmitPower,
128 const FVector Source,
129 const FVector Destination,
130 const double Distance3d,
131 const double ht,
132 const double hr,
133 const double reference_z)
134{
135 // hr in m
136 // ht in m
137 // reference_z in cm
138 // distance3d in m
139 bool ret = false;
140 FCollisionObjectQueryParams ObjectParams;
141 // Channels to check for collision with different object types
142 ObjectParams.AddObjectTypesToQuery(ECollisionChannel::ECC_WorldStatic);
143 ObjectParams.AddObjectTypesToQuery(ECollisionChannel::ECC_PhysicsBody);
144 ObjectParams.AddObjectTypesToQuery(ECollisionChannel::ECC_Vehicle);
145 ObjectParams.AddObjectTypesToQuery(ECollisionChannel::ECC_WorldDynamic);
146 HitResult.Reset();
147
148 FVector tx = Source;
149 FVector rx = Destination;
150 mWorld->LineTraceMultiByObjectType(HitResult, tx, rx, ObjectParams);
151
152 // all losses
153 float loss = ComputeLoss(OtherActor, Source, Destination, Distance3d, ht, hr, reference_z);
154
155 // we incorporate the tx power of the sender (the other actor), not our own
156 // NOTE: combined antenna gain is parametrized for each sensor. Better solution would be to parametrize individual antenna gain
157 // and combine at the receiver. This would allow to better account for antenna characteristics.
158 float ReceivedPower = OtherTransmitPower + combined_antenna_gain - loss;
159
160 if (ReceivedPower >= ReceiverSensitivity)
161 {
162 ret = true;
163 }
164 float deltaPercentage = loss / (OtherTransmitPower - ReceiverSensitivity);
165
166 // Works only when run in debug mode
167 DrawDebugLine(
168 mWorld,
169 tx,
170 rx,
171 FColor::Cyan,
172 false, 0.05f, 0,
173 30);
174 if (deltaPercentage < 0.65)
175 {
176 DrawDebugLine(
177 mWorld,
178 tx,
179 rx,
180 FColor::Green,
181 false, 0.1f, 0,
182 30);
183 }
184 else if (deltaPercentage < 0.8)
185 {
186 DrawDebugLine(
187 mWorld,
188 tx,
189 rx,
190 FColor::Orange,
191 false, 0.1f, 0,
192 30);
193 }
194 else if (ReceivedPower >= ReceiverSensitivity)
195 {
196 DrawDebugLine(
197 mWorld,
198 tx,
199 rx,
200 FColor::Red,
201 false, 0.1f, 0,
202 30);
203 }
204
205 if (ret)
206 {
207 return ReceivedPower;
208 }
209 else
210 {
211 return -1.0 * std::numeric_limits<float>::max();
212 }
213}
214
216 FVector CurrentActorLocation,
217 double TxHeight,
218 double RxHeight,
219 double reference_z,
220 EPathState &state,
221 std::vector<FVector> &vehicle_obstacles)
222{
223 // CurrentActorLocation in cm original
224 // TxHeight in m
225 // RxHeight in m
226 // reference_z in cm
227
228 // init with LOS
229 state = EPathState::LOS;
230 if (HitResult.Num() == 0)
231 {
232 // no hits --> LOS
233 return;
234 }
235
236 // check all hits
237 for (const FHitResult &HitInfo : HitResult)
238 {
239
240 FVector location;
241 if (HitIsSelfOrOther(HitInfo, OtherActor))
242 {
243 // the current hit is either Tx or Rx, so we can skip it, no obstacle
244 continue;
245 }
246
247 // cal by ref
248 if (GetLocationIfVehicle(CurrentActorLocation, HitInfo, reference_z, location))
249 {
250 // we found a vehicle
251 // Note: we may set this several times if we have several vehicles in between
252 state = EPathState::NLOSv;
253 // z (height) is gonna be in m in relation to reference height
254 // x,y also in meters
255 // call by reference
256 vehicle_obstacles.emplace_back(location);
257
258 // alternative (cf Etsi): statistical model
259 // vehicle_blockage_loss += MakeVehicleBlockageLoss(TxHeight, RxHeight, obj_height, obj_dist);
260 }
261 // but if we hit a building, we stop and switch to NLOSb
262 else
263 {
264 state = EPathState::NLOSb;
265 break;
266 }
267 }
268}
269
270double PathLossModel::CalcVehicleLoss(const double d1, const double d2, const double h)
271{
272 double V = h * sqrt(2.0 * (d1 + d2) / (lambda * d1 * d2));
273 if (V >= -0.78) {
274 double T = std::pow(V - 0.1, 2);
275 return 6.9 + 20.0 * log10(sqrt(T + 1.0) + V - 0.1);
276 }
277 return 0.0;
278}
279
280double PathLossModel::CalculateNLOSvLoss(const FVector Source,
281 const FVector Destination,
282 const double TxHeight,
283 const double RxHeight,
284 const double RxDistance3d,
285 std::vector<FVector> &vehicle_obstacles)
286{
287
288 // convert all positions to meters
289 FVector pos_tx = Source;
290 pos_tx.X /= 100.0f;
291 pos_tx.Y /= 100.0f;
292 pos_tx.Z = TxHeight;
293
294 FVector pos_rx = Destination;
295 pos_rx.X /= 100.0f;
296 pos_rx.Y /= 100.0f;
297 pos_rx.Z = RxHeight;
298
299 double max_loss = 0.0;
300 for(auto veh_it = vehicle_obstacles.begin(); veh_it != vehicle_obstacles.end(); veh_it++)
301 {
302 double dist_tx_veh = sqrt(std::pow(veh_it->X - pos_tx.X, 2) + std::pow(veh_it->Y - pos_tx.Y, 2));
303 double dist_veh_rx = sqrt(std::pow(pos_rx.X - veh_it->X, 2) + std::pow(pos_rx.Y - veh_it->Y, 2));
304 double cur_loss = CalcVehicleLoss(dist_tx_veh,dist_veh_rx,veh_it->Z);
305 if(cur_loss >= max_loss)
306 {
307 max_loss = cur_loss;
308 }
309 }
310
311 return max_loss;
312}
313
315{
316 model = path_loss_model;
317}
318
319float PathLossModel::ComputeLoss(AActor *OtherActor, FVector Source, FVector Destination, double Distance3d, double TxHeight, double RxHeight, double reference_z)
320{
321 // TxHeight in m
322 // RxHeight in m
323 // reference_z in cm
324 // distance3d in m
325 EPathState state;
326
327 float PathLoss = 0.0;
328 double VehicleBlockageLoss = 0.0;
329 float ShadowFadingLoss = 0.0;
330
331 // state and vehicle obstacles are call-by-reference
332 std::vector<FVector> vehicle_obstacles;
333 EstimatePathStateAndVehicleObstacles(OtherActor, Source, TxHeight, RxHeight, reference_z, state, vehicle_obstacles);
334
336 {
337 // calculate pure path loss depending on state and scenario
338 PathLoss = CalculatePathLoss_WINNER(state, Distance3d);
339 // in nlosv case, add multi knife edge
340 if (state == EPathState::NLOSv)
341 {
342 PathLoss = PathLoss + CalculateNLOSvLoss(Source, Destination, TxHeight, RxHeight, Distance3d, vehicle_obstacles);
343 }
344 }
345 else
346 {
347 if (state == EPathState::LOS)
348 {
349 // full two ray path loss
350 PathLoss = CalculateTwoRayPathLoss(Distance3d, TxHeight, RxHeight);
351 }
352 else if (state == EPathState::NLOSb)
353 {
354 // log distance path loss
355 PathLoss = (m_fspl_d0 + 10.0 * path_loss_exponent * log10(Distance3d / reference_distance_fspl));
356 }
357 else
358 {
359 // fspl + knife edge
360 // fspl
361 double free_space_loss = 20.0 * log10(Distance3d) + 20.0 * log10(4.0 * PI / lambda);
362 // add the knife edge vehicle blockage loss
363 PathLoss = free_space_loss + CalculateNLOSvLoss(Source, Destination, TxHeight, RxHeight, Distance3d, vehicle_obstacles);
364 }
365 }
366
367 // add random shadows
368 ShadowFadingLoss = CalculateShadowFading(state);
369 return PathLoss + ShadowFadingLoss;
370}
371
372bool PathLossModel::IsVehicle(const FHitResult &HitInfo)
373{
374 bool Vehicle = false;
375 const FActorRegistry &Registry = mCarlaEpisode->GetActorRegistry();
376
377 const AActor *actor = HitInfo.GetActor();
378
379 if (actor != nullptr)
380 {
381
382 const FCarlaActor *view = Registry.FindCarlaActor(actor);
383 if (view)
384 {
386 {
387 Vehicle = true;
388 }
389 }
390 }
391 return Vehicle;
392}
393
394bool PathLossModel::HitIsSelfOrOther(const FHitResult &HitInfo, AActor *OtherActor)
395{
396 const AActor *actor = HitInfo.GetActor();
397 if (actor != nullptr)
398 {
399 if (actor == mActorOwner)
400 {
401 return true;
402 }
403 else if (actor == OtherActor)
404 {
405 return true;
406 }
407 }
408 return false;
409}
410
411bool PathLossModel::GetLocationIfVehicle(const FVector CurrentActorLocation, const FHitResult &HitInfo, const double reference_z, FVector &location)
412{
413 // reference_z in cm
414 bool Vehicle = false;
415 const FActorRegistry &Registry = mCarlaEpisode->GetActorRegistry();
416
417 const AActor *actor = HitInfo.GetActor();
418
419 if (actor != nullptr)
420 {
421
422 const FCarlaActor *view = Registry.FindCarlaActor(actor);
423 if (view)
424 {
426 {
427 Vehicle = true;
428 location = actor->GetTransform().GetLocation();
429 location.Z = location.Z - reference_z + (actor->GetSimpleCollisionHalfHeight() * 2.0) + 2.0;
430 location.Z = location.Z / 100.0f;
431 // cm to m
432 location.X /= 100.0f;
433 location.Y /= 100.0f;
434 }
435 }
436 }
437 return Vehicle;
438}
439
441{
442 m_fspl_d0 = 20.0 * log10(reference_distance_fspl) + 20.0 * log10(Frequency) + 20.0 * log10(4.0 * PI / c_speedoflight);
443}
444
445// Following ETSI TR 103 257-1 V1.1.1 (2019-05: from WINNER Project Board: "D5.3 - WINNER+ Final Channel Models", 30 06 2010.
447{
448
449 if (state == EPathState::NLOSb)
450 {
451 // Distance should be euclidean here.
452 return 36.85f + 30.0f * log10(Distance) + 18.9f * log10(Frequency_GHz);
453 }
454 else // LOS, NLOSv
455 {
457 {
458 return 32.4f + 20.0f * log10(Distance) + 20.0f * log10(Frequency_GHz);
459 }
460 else // Rural or Urban
461 {
462 return 38.77f + 16.7f * log10(Distance) + 18.2f * log10(Frequency_GHz);
463 }
464 }
465}
466
467// Following ETSI TR 103 257-1 V1.1.1 Table 6: Shadow-fading parameter σ for V2V
469{
470 const float Mean = 0.0f;
471 float std_dev_dB; // = 5.2;
472 if (use_etsi_fading)
473 {
474 switch (state)
475 {
476 case EPathState::LOS:
477 switch (scenario)
478 {
480 std_dev_dB = 3.3f;
481 break;
482 case EScenario::Urban:
483 std_dev_dB = 5.2f;
484 break;
485 case EScenario::Rural:
486 // no known values in ETSI Standard, take middle of Highway and Urban
487 std_dev_dB = 4.25f;
488 break;
489 }
490 break;
492 switch (scenario)
493 {
494 // Note: according to ETSI, NLOSb is not applicable in Highway scenario, only Urban
495 // we use the same std_dev for all three scenarios if in NLOSb
497 std_dev_dB = 6.8f;
498 break;
499 case EScenario::Urban:
500 std_dev_dB = 6.8f;
501 break;
502 case EScenario::Rural:
503 std_dev_dB = 6.8f;
504 break;
505 }
506 break;
508 switch (scenario)
509 {
511 std_dev_dB = 3.8f;
512 break;
513 case EScenario::Urban:
514 std_dev_dB = 5.3f;
515 break;
516 case EScenario::Rural:
517 // no known values in ETSI Standard, take middle of Highway and Urban
518 std_dev_dB = 4.55f;
519 break;
520 }
521 break;
522 }
523 }
524 else
525 {
526 // custom fading param
527 std_dev_dB = custom_fading_stddev;
528 }
529
530 // in dB
531 return mRandomEngine->GetNormalDistribution(Mean, std_dev_dB);
532}
533
534float PathLossModel::CalculateTwoRayPathLossSimple(double Distance3d, double TxHeight, double RxHeight)
535{
536 // simplified Two Ray Path Loss (https://en.wikipedia.org/wiki/Two-ray_ground-reflection_model)
537 // is only a valid assumption, if distance >> 4 pi TxHeight * RxHeight / lambda
538 // with f=5.9 Ghz -> lambda = c/ f = 0.05m
539 // and with average antenna height 2m: distance >> 1000m
540 return 40 * log10(Distance3d) - 10 * log10(TxHeight * TxHeight * RxHeight * RxHeight);
541}
542
543double PathLossModel::CalculateTwoRayPathLoss(double Distance3d, double TxHeight, double RxHeight)
544{
545 // tx and rx height in m
546 // distance 3d is LOS in m
547 double d_ground = sqrt(std::pow(Distance3d, 2) - std::pow(TxHeight - RxHeight, 2));
548
549 // reflected path
550 // d_reflection = sqrt(d_ground^2+(ht+hr)^2) -> with d_ground = sqrt(d_los^2 - (ht-hr)^2):
551 double d_refl = sqrt(std::pow(Distance3d, 2) + 4.0 * TxHeight * RxHeight);
552
553 // Sine and cosine of incident angle
554 double sin_theta = (TxHeight + RxHeight) / d_refl;
555 double cos_theta = d_ground / d_refl;
556
557 double gamma = (sin_theta - sqrt(epsilon_r - std::pow(cos_theta, 2))) / (sin_theta + sqrt(epsilon_r - std::pow(cos_theta, 2)));
558
559 double phi = (2.0 * PI / lambda * (Distance3d - d_refl));
560
561 return 20 * log10(4.0 * PI * d_ground / lambda * 1.0 / sqrt(std::pow(1 + gamma * cos(phi), 2) + std::pow(gamma, 2) * std::pow(sin(phi), 2)));
562}
EPathState
@ NLOSb
@ NLOSv
@ LOS
EScenario
@ Rural
@ Urban
@ Highway
EPathLossModel
@ Winner
A registry of all the Carla actors.
FCarlaActor * FindCarlaActor(IdType Id)
A view over an actor and its properties.
Definition CarlaActor.h:25
ActorType GetActorType() const
Definition CarlaActor.h:86
float ReceiverSensitivity
float ComputeLoss(AActor *OtherActor, FVector Source, FVector Destination, double Distance3d, double TxHeight, double RxHeight, double reference_z)
Method that allow to preprocess if the rays will be traced.
double CalcVehicleLoss(const double d1, const double d2, const double h)
UWorld * mWorld
FVector CurrentActorLocation
double Frequency_GHz
ActorPowerMap GetReceiveActorPowerList()
void Simulate(const std::vector< ActorPowerPair > ActorList, UCarlaEpisode *CarlaEpisode, UWorld *World)
EPathLossModel model
UCarlaEpisode * mCarlaEpisode
float path_loss_exponent
void SetScenario(EScenario scenario)
URandomEngine * mRandomEngine
float CalculatePathLoss_WINNER(EPathState state, double Distance)
void SetPathLossModel(const EPathLossModel path_loss_model)
double CalculateTwoRayPathLoss(double Distance3d, double TxHeight, double RxHeight)
static constexpr float c_speedoflight
EScenario scenario
float custom_fading_stddev
bool GetLocationIfVehicle(const FVector CurrentActorLocation, const FHitResult &HitInfo, const double reference_z, FVector &location)
float CalculateTwoRayPathLossSimple(double Distance3d, double TxHeight, double RxHeight)
float CalculateReceivedPower(AActor *OtherActor, const float OtherTransmitPower, const FVector Source, const FVector Destination, const double Distance3d, const double ht, const double hr, const double reference_z)
double CalculateNLOSvLoss(const FVector Source, const FVector Destination, const double TxHeight, const double RxHeight, const double RxDistance3d, std::vector< FVector > &vehicle_obstacles)
void EstimatePathStateAndVehicleObstacles(AActor *OtherActor, FVector Source, double TxHeight, double RxHeight, double reference_z, EPathState &state, std::vector< FVector > &vehicle_obstacles)
void SetParams(const float TransmitPower, const float ReceiverSensitivity, const float Frequency, const float combined_antenna_gain, const float path_loss_exponent, const float reference_distance_fspl, const float filter_distance, const bool use_etsi_fading, const float custom_fading_stddev)
AActor * mActorOwner
float reference_distance_fspl
ActorPowerMap mReceiveActorPowerList
bool HitIsSelfOrOther(const FHitResult &HitInfo, AActor *OtherActor)
TArray< FHitResult > HitResult
float combined_antenna_gain
const double epsilon_r
float CalculateShadowFading(EPathState state)
PathLossModel(URandomEngine *random_engine, AActor *Owner)
bool IsVehicle(const FHitResult &HitInfo)
A simulation episode.
const FActorRegistry & GetActorRegistry() const
float GetNormalDistribution(float Mean, float StandardDeviation)