CARLA
Loading...
Searching...
No Matches
CarlaLight.h
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#pragma once
8
12
13#include <util/ue-header-guard-begin.h>
14#include "CoreMinimal.h"
15#include "Components/ActorComponent.h"
16#include "UObject/ObjectKey.h"
17#include <util/ue-header-guard-end.h>
18
19#include <type_traits>
20#include <utility>
21
22#include "CarlaLight.generated.h"
23
24
25
26#define CARLA_ENUM_FROM_RPC(e) static_cast<uint8>(carla::rpc::LightState::LightGroup:: e)
27
28UENUM(BlueprintType)
29enum class ELightType : uint8
30{
31 Null = 0, // Workarround for UE4.24 issue with enums
32 Vehicle = CARLA_ENUM_FROM_RPC(Vehicle) UMETA(DisplayName = "Vehicle"),
33 Street = CARLA_ENUM_FROM_RPC(Street) UMETA(DisplayName = "Street"),
34 Building = CARLA_ENUM_FROM_RPC(Building) UMETA(DisplayName = "Building"),
35 Other = CARLA_ENUM_FROM_RPC(Other) UMETA(DisplayName = "Other"),
36};
37
38#undef CARLA_ENUM_FROM_RPC
39
40
41
42UENUM(BlueprintType, Meta = (Bitflags))
43enum class ECarlaLightFlags : uint8
44{
45 Default = 0 UMETA(Hidden),
46 Registered = 1 UMETA(Hidden),
47 TurnedOn = 2
48};
49
51{
52 using U = std::underlying_type_t<ECarlaLightFlags>;
53 return static_cast<ECarlaLightFlags>(static_cast<U>(lhs) & static_cast<U>(rhs));
54}
55
57{
58 using U = std::underlying_type_t<ECarlaLightFlags>;
59 return static_cast<ECarlaLightFlags>(static_cast<U>(lhs) | static_cast<U>(rhs));
60}
61
63{
64 using U = std::underlying_type_t<ECarlaLightFlags>;
65 return static_cast<ECarlaLightFlags>(static_cast<U>(lhs) ^ static_cast<U>(rhs));
66}
67
68constexpr auto operator~(ECarlaLightFlags lhs)
69{
70 using U = std::underlying_type_t<ECarlaLightFlags>;
71 return static_cast<ECarlaLightFlags>(~static_cast<U>(lhs));
72}
73
75{
76 lhs = lhs & rhs;
77 return lhs;
78}
79
81{
82 lhs = lhs | rhs;
83 return lhs;
84}
85
87{
88 lhs = lhs ^ rhs;
89 return lhs;
90}
91
92
93
94
104
105
106
107
108// Class representing a light in the scene
109UCLASS(Blueprintable, BlueprintType, ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
110class CARLA_API UCarlaLight :
111 public UActorComponent
112{
113 GENERATED_BODY()
114public:
115
116 UCarlaLight();
117
118 // Registers with UCarlaLightSubsystem so it can preview day/night toggling
119 // in the editor too, not just once actually playing -- BeginPlay (the only
120 // other place that calls RegisterLight) never fires outside Play/PIE, so
121 // in a pure editor world nothing was ever registered and the day/night
122 // broadcast had zero listeners. RegisterLight() is idempotent (checks its
123 // own Registered flag), so this is harmless alongside BeginPlay's call.
124 void OnRegister() override;
125
126 // Paired with OnRegister: the editor re-registers components routinely
127 // (property edits, undo/redo, hot reload...) without ever going through
128 // OnComponentDestroyed/EndPlay, calling OnUnregister/OnRegister again
129 // instead. Without this override, that cycle left a stale entry under the
130 // old Id in the subsystem's map (RegisterLight's collision-avoidance loop
131 // just hands out a fresh one) every time it happened -- confirmed via a
132 // scanned bus stop count of 44 for 11 actually-placed actors, exactly 4x.
133 void OnUnregister() override;
134
135 void BeginPlay() override;
136
137 void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
138
139 void OnComponentDestroyed(bool bDestroyingHierarchy) override;
140
141 UFUNCTION(BlueprintCallable, Category = "Carla Light")
142 void RegisterLight();
143
144 UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category = "Carla Light")
145 void UpdateLights();
146
147 UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category = "Carla Light")
148 void DayTimeChanged(bool bIsDay);
149
150 /// C++ handler bound to UCarlaLightSubsystem::DayTimeChangeEvent. Forwards
151 /// to the blueprint DayTimeChanged event, then syncs the CarlaLight on/off
152 /// state for street lights: ported UE4 lamp blueprints render the change
153 /// but never update the flags, so the client API reported every lamp as
154 /// off while it was visibly lit.
155 UFUNCTION()
156 void HandleDayTimeChanged(bool bIsDay);
157
158 /// Scale the owner's light components from UE4-era authored intensities to
159 /// UE5 photometric units (see carla.Light.LegacyIntensityScale). Must run
160 /// after every path that lets blueprints push authored values into the
161 /// components -- including the subsystem's day/night broadcast, whose
162 /// blueprint-bound handlers can fire after any per-light delegate; guarded
163 /// so already-converted intensities are left alone.
164 void ApplyLegacyComponentConversion();
165
166 UFUNCTION(BlueprintCallable, Category = "Carla Light")
167 void SetLightIntensity(float Intensity);
168
169 UFUNCTION(BlueprintPure, Category = "Carla Light")
170 float GetLightIntensity() const;
171
172 UFUNCTION(BlueprintCallable, Category = "Carla Light")
173 void SetLightColor(FLinearColor Color);
174
175 UFUNCTION(BlueprintPure, Category = "Carla Light")
176 FLinearColor GetLightColor() const;
177
178 UFUNCTION(BlueprintCallable, Category = "Carla Light")
179 void SetLightOn(bool bOn);
180
181 UFUNCTION(BlueprintPure, Category = "Carla Light")
182 bool GetLightOn() const;
183
184 /// Independent of LightIntensity/LightColor -- some assets want an
185 /// emissive glow with no CarlaLight-driven light at all, or vice versa.
186 /// Applied natively via ApplyEmissiveToComponents (see there for why the
187 /// old BlueprintImplementableEvent-based SetEmissive path never worked).
188 UFUNCTION(BlueprintCallable, Category = "Carla Light")
189 void SetEmissiveIntensity(float Value);
190
191 UFUNCTION(BlueprintPure, Category = "Carla Light")
192 float GetEmissiveIntensity() const;
193
194 UFUNCTION(BlueprintCallable, Category = "Carla Light")
195 void SetLightType(ELightType Type);
196
197 UFUNCTION(BlueprintPure, Category = "Carla Light")
198 ELightType GetLightType() const;
199
200 carla::rpc::LightState GetLightState();
201
202 void SetLightState(carla::rpc::LightState LightState);
203
204 /// SetLightState with the exact values (the RPC color has 8 bits per
205 /// channel): how Multi-GPU secondaries replay the primary's lights.
206 void SetLightState(float Intensity, FLinearColor Color, ELightType Type, bool bOn);
207
208 FVector GetLocation() const;
209
210 UFUNCTION(BlueprintPure, Category = "Carla Light")
211 int GetId() const;
212
213 UFUNCTION(BlueprintCallable, Category = "Carla Light")
214 void SetId(int InId);
215
216 /// Activates, disables shadow-casting on, strips the IES profile from, and
217 /// widens the attenuation radius of every Point/Spot/Rect light component found
218 /// on Owner (see the fix comments in RegisterLight). Exposed statically so
219 /// actors that own real light components without a UCarlaLight wrapper --
220 /// vehicle headlights, notably, which are authored directly on the vehicle
221 /// Blueprint -- can share the same UE5 activation fix instead of silently
222 /// rendering nothing under bAutoActivate=false.
223 static void ActivateAndConfigureLightComponents(AActor* Owner);
224
225 /// Multiplies the Intensity of every Point/Spot/Rect light component on Owner
226 /// that still looks like an authored UE4 value (see
227 /// CarlaLightMaxAuthoredIntensity in the .cpp) by the scale registered for
228 /// LightType. Shared with vehicle headlights for the same reason as above.
229 /// With Converted, a component still holding the value last written for it
230 /// (recorded there) is skipped, so the conversion never applies twice.
231 static void ScaleLightComponentIntensities(AActor* Owner, ELightType LightType,
232 TMap<FObjectKey, float>* Converted = nullptr);
233
234 /// The UE4->UE5 intensity conversion factor for a light type (the value of
235 /// carla.Light.StreetIntensityScale or carla.Light.LegacyIntensityScale),
236 /// for callers that scale blueprint-side values instead of components.
237 static float GetLegacyIntensityScale(ELightType LightType);
238
239 /// Street light intensity -> lumens: authored UE4-era kilolumens (< 1000)
240 /// are scaled by carla.Light.StreetIntensityScale, legacy 6-10 M values are
241 /// divided back to lumens, anything else already is lumens.
242 static float StreetIntensityToLumens(float Intensity);
243
244private:
245
246 /// Show or hide every Point/Spot/Rect light component on the owner. The
247 /// blueprint UpdateLights event is supposed to react to state changes, but
248 /// several ported lamp blueprints have dead UpdateLights graphs, which left
249 /// the client light API (turn_on/turn_off/set_light_state) without any
250 /// visual effect. Enforce the on/off state from C++ so the API works
251 /// regardless of the content blueprint.
252 void ApplyLightOnToComponents(bool bOn);
253
254 /// Sets the Intensity of every Point/Spot/Rect light component on the owner
255 /// directly, scaled by GetLegacyIntensityScale(LightType). Same rationale as
256 /// ApplyLightOnToComponents: the blueprint UpdateLights event never reaches
257 /// the native component (confirmed by instrumented testing -- logged
258 /// component intensity was identical before and after UpdateLights() in
259 /// every case, at registration and on a live Reapply), so intensity needs
260 /// the same native enforcement on/off already has.
261 void ApplyIntensityToComponents(float Intensity);
262
263 /// Sets the LightColor of every Point/Spot/Rect light component on the owner
264 /// directly. Same rationale as ApplyIntensityToComponents.
265 void ApplyColorToComponents(FLinearColor Color);
266
267 /// Pushes the "EmissiveIntensity" scalar (and the "On/Off" gate scalar) of
268 /// every StaticMeshComponent's materials on the owner, via a per-instance
269 /// MID. The lamp materials (M_Artificial_Lamp / M_MaterialMaster and their
270 /// instances, e.g. M_StreetLight06) author "On/Off" defaulting to 0, which
271 /// forces their emissive Lerp to black regardless of EmissiveIntensity --
272 /// confirmed the reason editing emissiveIntensity from the Light Defaults
273 /// tool never visibly did anything: nothing ever flipped that gate. Setting
274 /// On/Off alongside EmissiveIntensity here (on whenever Value > 0) fixes
275 /// that without touching the ported material graph. No-ops safely on
276 /// materials without these parameters (e.g. the glass lens materials).
277 void ApplyEmissiveToComponents(float Value);
278
279 /// Applies Value to the material immediately and once more a tick later
280 /// (see the comment in SetEmissiveIntensity's .cpp body) WITHOUT touching
281 /// the EmissiveIntensity member -- the day/night handler uses this to
282 /// visually turn the glow off by day and back on by night while keeping
283 /// the authored/saved intensity intact for the next real edit.
284 void ApplyEmissiveVisualState(float Value);
285
286 /// The pending "next tick" retry's handle -- a NEW call reuses/cancels it
287 /// instead of always scheduling an independent one. Without this, a burst
288 /// of calls landing in the same or adjacent tick (e.g. dragging
289 /// SunAltitudeAngle: each edit re-broadcasts day/night to every light)
290 /// left multiple stale timers in flight, each capturing its OWN Value from
291 /// when it was scheduled -- an OLDER one could fire after a NEWER call's
292 /// immediate apply and silently overwrite it, which read as the on/off
293 /// state lagging one edit behind.
294 FTimerHandle DeferredEmissiveTimerHandle;
295
296 /// Looks this instance's owning class up in the light-defaults catalog
297 /// (ULightDefaultsJsonUtils, class entry falling back to LightType's group
298 /// entry) and pushes color/intensity/emissive if a match is found. A no-op
299 /// if the catalog has neither -- content keeps whatever it was authored
300 /// with. Called from RegisterLight, after the legacy intensity conversion.
301 void ApplyLightAssetDefault();
302
303protected:
304
305 UPROPERTY(EditAnywhere, Category = "Carla Light")
306 FLinearColor LightColor;
307
308 UPROPERTY(EditAnywhere, Category = "Carla Light")
309 float LightIntensity;
310
311 UPROPERTY(EditAnywhere, Category = "Carla Light")
312 float EmissiveIntensity = 0.0f;
313
314 UPROPERTY(EditAnywhere, Category = "Carla Light")
315 ELightType LightType = ELightType::Street;
316
317 UPROPERTY(EditAnywhere, Category = "Carla Light")
318 int Id = -1;
319
320 UPROPERTY(EditAnywhere, Category = "Carla Light")
321 ECarlaLightFlags flags;
322
323private:
324
325 void RecordLightChange() const;
326
327 // Intensity this light last wrote to each of its owner's light components
328 // (already in final units). A component still at that value is not a fresh
329 // blueprint push and must not be converted again: 0.5 became 500 lm and
330 // then 500000 lm on the next day/night change.
331 TMap<FObjectKey, float> ConvertedIntensities;
332
333};
constexpr auto operator&(ECarlaLightFlags lhs, ECarlaLightFlags rhs)
Definition CarlaLight.h:50
constexpr auto operator~(ECarlaLightFlags lhs)
Definition CarlaLight.h:68
ELightType
Definition CarlaLight.h:30
constexpr auto operator^=(ECarlaLightFlags &lhs, ECarlaLightFlags rhs)
Definition CarlaLight.h:86
constexpr auto operator^(ECarlaLightFlags lhs, ECarlaLightFlags rhs)
Definition CarlaLight.h:62
ECarlaLightFlags
Definition CarlaLight.h:44
constexpr auto operator|=(ECarlaLightFlags &lhs, ECarlaLightFlags rhs)
Definition CarlaLight.h:80
constexpr auto operator|(ECarlaLightFlags lhs, ECarlaLightFlags rhs)
Definition CarlaLight.h:56
constexpr auto operator&=(ECarlaLightFlags &lhs, ECarlaLightFlags rhs)
Definition CarlaLight.h:74
#define CARLA_ENUM_FROM_RPC(e)
Definition CarlaLight.h:26
ELightType LightType
Definition CarlaLight.h:101
FLinearColor LightColor
Definition CarlaLight.h:97