1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "Renderer/BsLight.h"
4#include "Private/RTTI/BsLightRTTI.h"
5#include "Renderer/BsRenderer.h"
6#include "Scene/BsSceneObject.h"
7#include "Mesh/BsMesh.h"
8#include "CoreThread/BsCoreObjectSync.h"
9
10namespace bs
11{
12 LightBase::LightBase()
13 : mType(LightType::Radial), mCastsShadows(false), mColor(Color::White), mAttRadius(10.0f), mSourceRadius(0.0f)
14 , mIntensity(100.0f), mSpotAngle(45), mSpotFalloffAngle(35.0f), mAutoAttenuation(false), mShadowBias(0.5f)
15 {
16 updateAttenuationRange();
17 }
18
19 LightBase::LightBase(LightType type, Color color, float intensity, float attRadius, float srcRadius, bool castsShadows,
20 Degree spotAngle, Degree spotFalloffAngle)
21 : mType(type), mCastsShadows(castsShadows), mColor(color), mAttRadius(attRadius), mSourceRadius(srcRadius)
22 , mIntensity(intensity), mSpotAngle(spotAngle), mSpotFalloffAngle(spotFalloffAngle), mAutoAttenuation(false)
23 , mShadowBias(0.5f)
24 {
25 updateAttenuationRange();
26 }
27
28 void LightBase::setUseAutoAttenuation(bool enabled)
29 {
30 mAutoAttenuation = enabled;
31
32 if(enabled)
33 updateAttenuationRange();
34
35 _markCoreDirty();
36 }
37
38 void LightBase::setAttenuationRadius(float radius)
39 {
40 if (mAutoAttenuation)
41 return;
42
43 mAttRadius = radius;
44 _markCoreDirty();
45 updateBounds();
46 }
47
48 void LightBase::setSourceRadius(float radius)
49 {
50 mSourceRadius = radius;
51
52 if (mAutoAttenuation)
53 updateAttenuationRange();
54
55 _markCoreDirty();
56 }
57
58 void LightBase::setIntensity(float intensity)
59 {
60 mIntensity = intensity;
61
62 if (mAutoAttenuation)
63 updateAttenuationRange();
64
65 _markCoreDirty();
66 }
67
68 float LightBase::getLuminance() const
69 {
70 float radius2 = mSourceRadius * mSourceRadius;
71
72 switch (mType)
73 {
74 case LightType::Radial:
75 if (mSourceRadius > 0.0f)
76 return mIntensity / (4 * radius2 * Math::PI); // Luminous flux -> luminance
77 else
78 return mIntensity / (4 * Math::PI); // Luminous flux -> luminous intensity
79 case LightType::Spot:
80 {
81 if (mSourceRadius > 0.0f)
82 return mIntensity / (radius2 * Math::PI); // Luminous flux -> luminance
83 else
84 {
85 // Note: Consider using the simpler conversion I / PI to match with the area-light conversion
86 float cosTotalAngle = Math::cos(mSpotAngle);
87 float cosFalloffAngle = Math::cos(mSpotFalloffAngle);
88
89 // Luminous flux -> luminous intensity
90 return mIntensity / (Math::TWO_PI * (1.0f - (cosFalloffAngle + cosTotalAngle) * 0.5f));
91 }
92 }
93 case LightType::Directional:
94 if (mSourceRadius > 0.0f)
95 {
96 // Use cone solid angle formulae to calculate disc solid angle
97 float solidAngle = Math::TWO_PI * (1 - cos(mSourceRadius * Math::DEG2RAD));
98 return mIntensity / solidAngle; // Illuminance -> luminance
99 }
100 else
101 return mIntensity; // In luminance units by default
102 default:
103 return 0.0f;
104 }
105 }
106
107 void LightBase::updateAttenuationRange()
108 {
109 // Value to which intensity needs to drop in order for the light contribution to fade out to zero
110 const float minAttenuation = 0.2f;
111
112 if(mSourceRadius > 0.0f)
113 {
114 // Inverse of the attenuation formula for area lights:
115 // a = I / (1 + (2/r) * d + (1/r^2) * d^2
116 // Where r is the source radius, and d is the distance from the light. As derived here:
117 // https://imdoingitwrong.wordpress.com/2011/01/31/light-attenuation/
118
119 float luminousFlux = getIntensity();
120
121 float a = sqrt(minAttenuation);
122 mAttRadius = (mSourceRadius * (sqrt(luminousFlux - a))) / a;
123 }
124 else // Based on the basic inverse square distance formula
125 {
126 float luminousIntensity = getIntensity();
127
128 float a = minAttenuation;
129 mAttRadius = sqrt(std::max(0.0f, luminousIntensity / a));
130 }
131
132 updateBounds();
133 }
134
135 void LightBase::updateBounds()
136 {
137 const Transform& tfrm = getTransform();
138
139 switch (mType)
140 {
141 case LightType::Directional:
142 mBounds = Sphere(tfrm.getPosition(), std::numeric_limits<float>::infinity());
143 break;
144 case LightType::Radial:
145 mBounds = Sphere(tfrm.getPosition(), mAttRadius);
146 break;
147 case LightType::Spot:
148 {
149 // Note: We could use the formula for calculating the circumcircle of a triangle (after projecting the cone),
150 // but the radius of the sphere is the same as in the formula we use here, yet it is much simpler with no need
151 // to calculate multiple determinants. Neither are good approximations when cone angle is wide.
152 Vector3 offset(0, 0, mAttRadius * 0.5f);
153
154 // Direction along the edge of the cone, on the YZ plane (doesn't matter if we used XZ instead)
155 Degree angle = Math::clamp(mSpotAngle * 0.5f, Degree(-89), Degree(89));
156 Vector3 coneDir(0, Math::tan(angle)*mAttRadius, mAttRadius);
157
158 // Distance between the "corner" of the cone and our center, must be the radius (provided the center is at
159 // the middle of the range)
160 float radius = (offset - coneDir).length();
161
162 Vector3 center = tfrm.getPosition() - tfrm.getRotation().rotate(offset);
163 mBounds = Sphere(center, radius);
164 }
165 break;
166 default:
167 break;
168 }
169 }
170
171 void LightBase::setTransform(const Transform& transform)
172 {
173 if (mMobility != ObjectMobility::Movable)
174 return;
175
176 SceneActor::setTransform(transform);
177 updateBounds();
178 }
179
180 template <class P>
181 void LightBase::rttiEnumFields(P p)
182 {
183 p(mType);
184 p(mCastsShadows);
185 p(mColor);
186 p(mAttRadius);
187 p(mSourceRadius);
188 p(mIntensity);
189 p(mSpotAngle);
190 p(mSpotFalloffAngle);
191 p(mAutoAttenuation);
192 p(mBounds);
193 p(mShadowBias);
194 }
195
196 Light::Light(LightType type, Color color, float intensity, float attRadius, float srcRadius, bool castsShadows,
197 Degree spotAngle, Degree spotFalloffAngle)
198 : LightBase(type, color, intensity, attRadius, srcRadius, castsShadows, spotAngle, spotFalloffAngle)
199 {
200 // Calling virtual method is okay here because this is the most derived type
201 updateBounds();
202 }
203
204 SPtr<ct::Light> Light::getCore() const
205 {
206 return std::static_pointer_cast<ct::Light>(mCoreSpecific);
207 }
208
209 SPtr<Light> Light::create(LightType type, Color color,
210 float intensity, float attRadius, bool castsShadows, Degree spotAngle, Degree spotFalloffAngle)
211 {
212 Light* handler = new (bs_alloc<Light>())
213 Light(type, color, intensity, attRadius, 0.0f, castsShadows, spotAngle, spotFalloffAngle);
214 SPtr<Light> handlerPtr = bs_core_ptr<Light>(handler);
215 handlerPtr->_setThisPtr(handlerPtr);
216 handlerPtr->initialize();
217
218 return handlerPtr;
219 }
220
221 SPtr<Light> Light::createEmpty()
222 {
223 Light* handler = new (bs_alloc<Light>()) Light();
224 SPtr<Light> handlerPtr = bs_core_ptr<Light>(handler);
225 handlerPtr->_setThisPtr(handlerPtr);
226
227 return handlerPtr;
228 }
229
230 SPtr<ct::CoreObject> Light::createCore() const
231 {
232 ct::Light* handler = new (bs_alloc<ct::Light>())
233 ct::Light(mType, mColor, mIntensity, mAttRadius, mSourceRadius, mCastsShadows, mSpotAngle, mSpotFalloffAngle);
234 SPtr<ct::Light> handlerPtr = bs_shared_ptr<ct::Light>(handler);
235 handlerPtr->_setThisPtr(handlerPtr);
236
237 return handlerPtr;
238 }
239
240 CoreSyncData Light::syncToCore(FrameAlloc* allocator)
241 {
242 UINT32 size = 0;
243 size += rttiGetElemSize(getCoreDirtyFlags());
244 size += coreSyncGetElemSize((SceneActor&)*this);
245 size += coreSyncGetElemSize(*this);
246
247 UINT8* buffer = allocator->alloc(size);
248
249 char* dataPtr = (char*)buffer;
250 dataPtr = rttiWriteElem(getCoreDirtyFlags(), dataPtr);
251 dataPtr = coreSyncWriteElem((SceneActor&)*this, dataPtr);
252 dataPtr = coreSyncWriteElem(*this, dataPtr);
253
254 return CoreSyncData(buffer, size);
255 }
256
257 void Light::_markCoreDirty(ActorDirtyFlag flag)
258 {
259 markCoreDirty((UINT32)flag);
260 }
261
262 RTTITypeBase* Light::getRTTIStatic()
263 {
264 return LightRTTI::instance();
265 }
266
267 RTTITypeBase* Light::getRTTI() const
268 {
269 return Light::getRTTIStatic();
270 }
271
272 namespace ct
273 {
274 const UINT32 Light::LIGHT_CONE_NUM_SIDES = 20;
275 const UINT32 Light::LIGHT_CONE_NUM_SLICES = 10;
276
277 Light::Light(LightType type, Color color,
278 float intensity, float attRadius, float srcRadius, bool castsShadows, Degree spotAngle, Degree spotFalloffAngle)
279 :LightBase(type, color, intensity, attRadius, srcRadius, castsShadows, spotAngle, spotFalloffAngle), mRendererId(0)
280 {
281
282 }
283
284 Light::~Light()
285 {
286 gRenderer()->notifyLightRemoved(this);
287 }
288
289 void Light::initialize()
290 {
291 updateBounds();
292 gRenderer()->notifyLightAdded(this);
293
294 CoreObject::initialize();
295 }
296
297 void Light::syncToCore(const CoreSyncData& data)
298 {
299 char* dataPtr = (char*)data.getBuffer();
300
301 UINT32 dirtyFlags = 0;
302 bool oldIsActive = mActive;
303 LightType oldType = mType;
304
305 dataPtr = rttiReadElem(dirtyFlags, dataPtr);
306 dataPtr = coreSyncReadElem((SceneActor&)*this, dataPtr);
307 dataPtr = coreSyncReadElem(*this, dataPtr);
308
309 updateBounds();
310
311 if((dirtyFlags & ((UINT32)ActorDirtyFlag::Everything | (UINT32)ActorDirtyFlag::Active)) != 0)
312 {
313 if (oldIsActive != mActive)
314 {
315 if (mActive)
316 gRenderer()->notifyLightAdded(this);
317 else
318 {
319 LightType newType = mType;
320 mType = oldType;
321 gRenderer()->notifyLightRemoved(this);
322 mType = newType;
323 }
324 }
325 else
326 {
327 LightType newType = mType;
328 mType = oldType;
329 gRenderer()->notifyLightRemoved(this);
330 mType = newType;
331
332 gRenderer()->notifyLightAdded(this);
333 }
334 }
335 else if((dirtyFlags & (UINT32)ActorDirtyFlag::Mobility) != 0)
336 {
337 gRenderer()->notifyLightRemoved(this);
338 gRenderer()->notifyLightAdded(this);
339 }
340 else if ((dirtyFlags & (UINT32)ActorDirtyFlag::Transform) != 0)
341 {
342 if (mActive)
343 gRenderer()->notifyLightUpdated(this);
344 }
345 }
346}}
347