lightenvironment.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. /*
  2. ** Command & Conquer Generals Zero Hour(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /***********************************************************************************************
  19. *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
  20. ***********************************************************************************************
  21. * *
  22. * Project Name : WWPhys *
  23. * *
  24. * $Archive:: /Commando/Code/ww3d2/lightenvironment.cpp $*
  25. * *
  26. * Original Author:: Greg Hjelstrom *
  27. * *
  28. * $Author:: Greg_h $*
  29. * *
  30. * $Modtime:: 2/01/01 5:40p $*
  31. * *
  32. * $Revision:: 3 $*
  33. * *
  34. *---------------------------------------------------------------------------------------------*
  35. * Functions: *
  36. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  37. #include "lightenvironment.h"
  38. #include "matrix3d.h"
  39. #include "camera.h"
  40. #include "light.h"
  41. #include "colorspace.h"
  42. /*
  43. ** Constants
  44. */
  45. const float DIFFUSE_TO_AMBIENT_FRACTION = 1.0f;
  46. /*
  47. ** Static variables
  48. */
  49. static _LightingLODCutoff = 0.5f;
  50. static _LightingLODCutoff2 = 0.5f * 0.5f;
  51. /************************************************************************************************
  52. **
  53. ** LightEnvironmentClass::InputLightStruct Implementation
  54. **
  55. ************************************************************************************************/
  56. void LightEnvironmentClass::InputLightStruct::Init
  57. (
  58. const LightClass & light,
  59. const Vector3 & object_center
  60. )
  61. {
  62. m_point = false;
  63. switch(light.Get_Type())
  64. {
  65. case LightClass::POINT:
  66. case LightClass::SPOT:
  67. Init_From_Point_Or_Spot_Light(light,object_center);
  68. break;
  69. case LightClass::DIRECTIONAL:
  70. Init_From_Directional_Light(light,object_center);
  71. break;
  72. };
  73. }
  74. void LightEnvironmentClass::InputLightStruct::Init_From_Point_Or_Spot_Light
  75. (
  76. const LightClass & light,
  77. const Vector3 & object_center
  78. )
  79. {
  80. /*
  81. ** Compute the direction vector and the distance to the light
  82. */
  83. Direction = light.Get_Position() - object_center;
  84. float dist = Direction.Length();
  85. if (dist > 0.0f) {
  86. Direction /= dist;
  87. }
  88. /*
  89. ** Compute the attenuation factor
  90. */
  91. float atten = 1.0f;
  92. double atten_start,atten_end;
  93. light.Get_Far_Attenuation_Range(atten_start,atten_end);
  94. if (light.Get_Flag(LightClass::FAR_ATTENUATION)) {
  95. if (WWMath::Fabs(atten_end - atten_start) < WWMATH_EPSILON) {
  96. /*
  97. ** Start and end are equal, attenuation is a "step" function
  98. */
  99. if (dist > atten_start) {
  100. atten = 0.0f;
  101. }
  102. } else {
  103. /*
  104. ** Compute the attenuation
  105. */
  106. atten = 1.0f - (dist - atten_start) / (atten_end - atten_start);
  107. atten = WWMath::Clamp(atten,0.0f,1.0f);
  108. }
  109. }
  110. if (light.Get_Type() == LightClass::SPOT) {
  111. Vector3 spot_dir;
  112. light.Get_Spot_Direction(spot_dir);
  113. Matrix3D::Rotate_Vector(light.Get_Transform(),spot_dir,&spot_dir);
  114. float spot_angle_cos = light.Get_Spot_Angle_Cos();
  115. atten *= (Vector3::Dot_Product(-spot_dir,Direction) - spot_angle_cos) / (1.0f - spot_angle_cos);
  116. atten = WWMath::Clamp(atten,0.0f,1.0f);
  117. }
  118. /*
  119. ** Compute the ambient and diffuse values. Rejecting the diffuse
  120. ** component and folding it into the ambient component if it is below
  121. ** the LOD cutoff
  122. */
  123. light.Get_Ambient(&Ambient);
  124. light.Get_Diffuse(&Diffuse);
  125. Ambient *= light.Get_Intensity(); //(gth) CNC3 obey the intensity parameter
  126. Diffuse *= light.Get_Intensity();
  127. m_point = (light.Get_Type() == LightClass::POINT);
  128. m_center = light.Get_Position();
  129. m_innerRadius = atten_start;
  130. m_outerRadius = atten_end;
  131. m_ambient = Ambient;
  132. m_diffuse = Diffuse;
  133. if (Diffuse.Length2() > _LightingLODCutoff2) {
  134. DiffuseRejected = false;
  135. Ambient *= atten;
  136. Diffuse *= atten;
  137. } else {
  138. DiffuseRejected = true;
  139. Ambient *= atten;
  140. Ambient += atten * DIFFUSE_TO_AMBIENT_FRACTION * Diffuse;
  141. Diffuse.Set(0,0,0);
  142. }
  143. }
  144. void LightEnvironmentClass::InputLightStruct::Init_From_Directional_Light
  145. (
  146. const LightClass & light,
  147. const Vector3 & object_center
  148. )
  149. {
  150. Direction = -light.Get_Transform().Get_Z_Vector();
  151. DiffuseRejected = false;
  152. light.Get_Ambient(&Ambient);
  153. light.Get_Diffuse(&Diffuse);
  154. }
  155. float LightEnvironmentClass::InputLightStruct::Contribution(void)
  156. {
  157. return Diffuse.Length2();
  158. }
  159. /************************************************************************************************
  160. **
  161. ** LightEnvironmentClass::OutputLightStruct Implementation
  162. **
  163. ************************************************************************************************/
  164. void LightEnvironmentClass::OutputLightStruct::Init
  165. (
  166. const InputLightStruct & input,
  167. const Matrix3D & camera_tm
  168. )
  169. {
  170. Diffuse = input.Diffuse;
  171. Matrix3D::Inverse_Rotate_Vector(camera_tm,input.Direction,&Direction);
  172. // Guard against a direction that is invalid
  173. if(Direction.Length2() == 0.0f) {
  174. Direction.X = 1.0f;
  175. }
  176. }
  177. /************************************************************************************************
  178. **
  179. ** LightEnvironmentClass Implementation
  180. **
  181. ************************************************************************************************/
  182. LightEnvironmentClass::LightEnvironmentClass(void) :
  183. LightCount(0),
  184. ObjectCenter(0,0,0),
  185. OutputAmbient(0,0,0),
  186. FillLight(),
  187. FillIntensity(0.0f)
  188. {
  189. }
  190. LightEnvironmentClass::~LightEnvironmentClass(void)
  191. {
  192. }
  193. void LightEnvironmentClass::Reset(const Vector3 & object_center,const Vector3 & ambient)
  194. {
  195. LightCount = 0;
  196. ObjectCenter = object_center;
  197. OutputAmbient = ambient;
  198. }
  199. void LightEnvironmentClass::Add_Light(const LightClass & light)
  200. {
  201. // Jani: Don't accept lights that are almost black
  202. Vector3 diff;
  203. light.Get_Diffuse(&diff);
  204. if (diff[0]<0.05f && diff[1]<0.05f && diff[2]<0.05f) {
  205. return;
  206. }
  207. /*
  208. ** Compute the equivalent directional + ambient light
  209. */
  210. InputLightStruct new_light;
  211. new_light.Init(light, ObjectCenter);
  212. // If we have the fill light set, we also want to the diffuse light to be modified by the intensity of the light source
  213. if(FillIntensity) new_light.Diffuse *= light.Get_Intensity();
  214. /*
  215. ** Add in the ambient component
  216. */
  217. OutputAmbient += new_light.Ambient;
  218. /*
  219. ** If not rejected, add the directional component to the active lights
  220. */
  221. if (new_light.DiffuseRejected == false || new_light.m_point) {
  222. // Insert the light into the sorted list of InputLights if it's contribution is greater than the any of the current number of lights
  223. for (int light_index=0; light_index < LightCount; light_index++) {
  224. if (new_light.Contribution() > InputLights[light_index].Contribution()) {
  225. // Move back the lights in the InputLights Array to make space for the new light.
  226. // The last light might be discarded if it moves off the array as it is the weakest light in the list.
  227. for (int i = LightCount; i > light_index; --i) {
  228. if (i < MAX_LIGHTS) {
  229. InputLights[i] = InputLights[i - 1];
  230. }
  231. }
  232. // Add the new light into the InputLights List where it belongs
  233. InputLights[light_index] = new_light;
  234. // Increment the light count if we have not reach the maximum lights limit yet
  235. LightCount = min(LightCount + 1, (int)MAX_LIGHTS);
  236. // Since we have inserted a new light, we are done for this function
  237. return;
  238. }
  239. }
  240. // If the light was not inserted but there are still spots empty in the InputLights list, insert the lights at the end of the list
  241. if (LightCount < MAX_LIGHTS) {
  242. InputLights[LightCount] = new_light;
  243. ++LightCount;
  244. }
  245. }
  246. }
  247. void LightEnvironmentClass::Pre_Render_Update(const Matrix3D & camera_tm)
  248. {
  249. /*
  250. ** Calculate and set up the fill light for the object
  251. */
  252. Calculate_Fill_Light();
  253. /*
  254. ** Transform each light into camera space
  255. ** and add up the ambient effect of each light
  256. */
  257. for (int light_index=0; light_index<LightCount; light_index++) {
  258. OutputLights[light_index].Init(InputLights[light_index],camera_tm);
  259. }
  260. // Clamp ambient.
  261. OutputAmbient.X = WWMath::Clamp(OutputAmbient.X,0.0f,1.0f);
  262. OutputAmbient.Y = WWMath::Clamp(OutputAmbient.Y,0.0f,1.0f);
  263. OutputAmbient.Z = WWMath::Clamp(OutputAmbient.Z,0.0f,1.0f);
  264. }
  265. void LightEnvironmentClass::Set_Lighting_LOD_Cutoff(float inten)
  266. {
  267. _LightingLODCutoff = inten;
  268. _LightingLODCutoff2 = _LightingLODCutoff * _LightingLODCutoff;
  269. }
  270. float LightEnvironmentClass::Get_Lighting_LOD_Cutoff(void)
  271. {
  272. return _LightingLODCutoff;
  273. }
  274. /************************************************************************************************
  275. **
  276. ** LightEnvironmentClass::Add_Fill_Light Implementation
  277. ** The fill light is inserted in the InputLights list as ont of the lights, and if the
  278. ** list is already full, it preempts the last and weakest light in that list.
  279. **
  280. ************************************************************************************************/
  281. void LightEnvironmentClass::Add_Fill_Light(void)
  282. {
  283. // Don't add black (or almost black) lights!
  284. if (FillLight.Diffuse[0]<0.05f && FillLight.Diffuse[1]<0.05f && FillLight.Diffuse[2]<0.05f) {
  285. OutputAmbient += FillLight.Ambient;
  286. return;
  287. }
  288. // Get the 1st empty light slot or the very last slot regardless of whether the last slot is empty or not
  289. int slot = 0;
  290. if (LightCount == MAX_LIGHTS) {
  291. slot = MAX_LIGHTS - 1;
  292. } else {
  293. slot = LightCount;
  294. ++LightCount;
  295. }
  296. /*
  297. ** Add in the ambient component
  298. */
  299. OutputAmbient += FillLight.Ambient;
  300. /*
  301. ** Insert the fill light into the calculated slot of the InputLights
  302. */
  303. InputLights[slot] = FillLight;
  304. }
  305. /************************************************************************************************
  306. **
  307. ** LightEnvironmentClass::Calculate_Fill_Light Implementation
  308. ** The fill light takes up to the top 3 lights in the InputList and averages them into 1 light source.
  309. ** The averaged light source is then flipped in direction and location as well as in HUE of the color.
  310. ** This final light is used to support the top 3 lights by providing a calulated fill to augment the lights.
  311. **
  312. ************************************************************************************************/
  313. void LightEnvironmentClass::Calculate_Fill_Light(void)
  314. {
  315. // Early exit if we have no lights at all or if the fill light intensity is zero
  316. if (LightCount == 0 || FillIntensity == 0.0f) return;
  317. // Initialize the averaged light to the primary light source (light with the most contribution)
  318. float primary_contribution = InputLights[0].Contribution();
  319. InputLightStruct average_light = InputLights[0];
  320. // Loop through the remaining lights on the list (up to 2) and add their contributions to the averaged light
  321. int num_lights = min(LightCount, MAX_LIGHTS - 1);
  322. for (int i = 1; i < num_lights; ++i) {
  323. // The ratio is the percentage of the remaining light's contribution compared to the primary light source
  324. float ratio = InputLights[i].Contribution() / primary_contribution;
  325. average_light.Direction += (InputLights[i].Direction * ratio);
  326. average_light.Ambient += (InputLights[i].Ambient * ratio);
  327. average_light.Diffuse += (InputLights[i].Diffuse * ratio);
  328. }
  329. // Normalize the averaged light direction
  330. average_light.Direction.Normalize();
  331. // Now we have the averaged light, we should derive the fill light
  332. // Convert from RGB to HSV to get the reserve hue and with the value modified by the fill intensity
  333. Vector3 temp;
  334. RGB_To_HSV(temp, average_light.Diffuse);
  335. temp.X += 180.0f; // Get the opposite hue from the averaged light
  336. if(temp.X > 360.0f) {
  337. temp.X -= 360.0f;
  338. }
  339. temp.Z *= FillIntensity; // fraction of the intensity
  340. HSV_To_RGB(FillLight.Diffuse, temp);
  341. // Zero out the fill ambient
  342. FillLight.Ambient.Set(0.0f, 0.0f, 0.0f);
  343. // now we set the fill light direction to be opposite the average light
  344. FillLight.Direction = average_light.Direction * (-1.0f);
  345. FillLight.DiffuseRejected = false;
  346. // Add the fill light into the InputLights list
  347. Add_Fill_Light();
  348. }