lightsolve.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. /*
  2. ** Command & Conquer Renegade(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/wwphys/lightsolve.cpp $*
  25. * *
  26. * Original Author:: Greg Hjelstrom *
  27. * *
  28. * $Author:: Patrick $*
  29. * *
  30. * $Modtime:: 3/28/02 11:53a $*
  31. * *
  32. * $Revision:: 3 $*
  33. * *
  34. *---------------------------------------------------------------------------------------------*
  35. * Functions: *
  36. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  37. #include "lightsolve.h"
  38. #include "phys.h"
  39. #include "staticphys.h"
  40. #include "rendobj.h"
  41. #include "mesh.h"
  42. #include "dx8renderer.h"
  43. #include "simplevec.h"
  44. #include "vp.h"
  45. #include "lightphys.h"
  46. #include "light.h"
  47. #include "lightsolvecontext.h"
  48. #include "lightsolveprogress.h"
  49. #include "renegadeterrainpatch.h"
  50. /**
  51. ** VertexSolveClass
  52. ** This class does the job of generated a vertex solve for a mesh.
  53. */
  54. class VertexSolveClass
  55. {
  56. public:
  57. VertexSolveClass(void) {}
  58. void Light_Mesh(LightSolveContextClass & context,MeshClass * mesh, NonRefPhysListClass & lights);
  59. void Light_Terrain(LightSolveContextClass & context,RenegadeTerrainPatchClass * patch, NonRefPhysListClass & lights);
  60. protected:
  61. void Grow_Arrays(int new_vcount);
  62. void Add_Light_To_Vertex(LightSolveContextClass & context,int idx,LightClass * light_obj);
  63. SimpleVecClass<Vector3> Position;
  64. SimpleVecClass<Vector3> Normal;
  65. SimpleVecClass<Vector4> MeshAmbient;
  66. SimpleVecClass<Vector4> MeshDiffuse;
  67. SimpleVecClass<Vector4> AmbientSolve;
  68. SimpleVecClass<Vector4> DiffuseSolve;
  69. SimpleVecClass<Vector4> Solve;
  70. };
  71. static VertexSolveClass TheVertexSolver;
  72. void VertexSolveClass::Light_Mesh(LightSolveContextClass & context,MeshClass * mesh, NonRefPhysListClass & lights)
  73. {
  74. int vi;
  75. context.Get_Progress().Set_Current_Mesh_Name(mesh->Get_Name());
  76. context.Get_Progress().Set_Current_Mesh_Vertex_Count(mesh->Peek_Model()->Get_Vertex_Count());
  77. /*
  78. ** Get the model and initialize our arrays
  79. */
  80. MeshModelClass * model = mesh->Peek_Model();
  81. int vcount = model->Get_Vertex_Count();
  82. Grow_Arrays(vcount);
  83. /*
  84. ** Transform the positions and normals into world space
  85. */
  86. Matrix3D tm = mesh->Get_Transform();
  87. const Vector3 * src_verts = model->Get_Vertex_Array();
  88. const Vector3 * src_norms = model->Get_Vertex_Normal_Array();
  89. VectorProcessorClass::Transform(&(Position[0]), src_verts, tm, vcount);
  90. tm.Set_Translation(Vector3(0,0,0));
  91. VectorProcessorClass::Transform(&(Normal[0]), src_norms, tm, vcount);
  92. /*
  93. ** Fill the mesh ambient and diffuse arrays with the per vertex material
  94. ** color (either from the material itself or the vertex color array)
  95. **
  96. ** LIGHT SOLVE GENERATION RULES:
  97. ** - for each vertex
  98. ** - for ambient and diffuse
  99. ** - if this vertex is supposed to use a color array in any pass copy the color from the array
  100. ** - else copy the material setting from pass0
  101. **
  102. ** LIGHT SOLVE INSTALL RULES:
  103. ** - for each pass
  104. ** - for each vertex material
  105. ** - if this material has any emissive component, leave the vmtl alone
  106. ** - else, point the emissive source to the new light solve color array,
  107. ** make the diffuse and ambient use the material settings.
  108. */
  109. unsigned * dcg = NULL;
  110. for (int pi=0; pi<model->Get_Pass_Count(); pi++) {
  111. if (model->Get_DCG_Array(pi) != NULL) {
  112. dcg = model->Get_DCG_Array(pi);
  113. }
  114. }
  115. for (vi=0; vi<model->Get_Vertex_Count(); vi++) {
  116. bool use_array = false;
  117. for (int pi=0; pi<model->Get_Pass_Count(); pi++) {
  118. VertexMaterialClass * pass_mtl = model->Peek_Material(vi,pi);
  119. if ( (pass_mtl != NULL) &&
  120. (dcg != NULL) &&
  121. (pass_mtl->Get_Diffuse_Color_Source() == VertexMaterialClass::COLOR1) )
  122. {
  123. use_array = true;
  124. }
  125. }
  126. /*
  127. ** Set up the ambient color for this vertex
  128. */
  129. VertexMaterialClass * vmtl = model->Peek_Material(vi,0);
  130. if (use_array) {
  131. MeshAmbient[vi] = DX8Wrapper::Convert_Color(dcg[vi]);
  132. } else if (vmtl != NULL) {
  133. Vector3 ambient;
  134. vmtl->Get_Ambient(&ambient);
  135. MeshAmbient[vi] = Vector4(ambient.X,ambient.Y,ambient.Z,1.0f);
  136. } else {
  137. MeshAmbient[vi].Set(1,1,1,1);
  138. }
  139. /*
  140. ** Set up the diffuse color for this vertex
  141. */
  142. if (use_array) {
  143. MeshDiffuse[vi] = DX8Wrapper::Convert_Color(dcg[vi]);
  144. } else if (vmtl != NULL) {
  145. Vector3 diffuse;
  146. vmtl->Get_Diffuse(&diffuse);
  147. MeshDiffuse[vi] = Vector4(diffuse.X,diffuse.Y,diffuse.Z,1.0f);
  148. } else {
  149. MeshDiffuse[vi].Set(1,1,1,1);
  150. }
  151. }
  152. /*
  153. ** Compute the light solve
  154. */
  155. LightClass * sun = PhysicsSceneClass::Get_Instance()->Get_Sun_Light();
  156. Vector3 scene_ambient = PhysicsSceneClass::Get_Instance()->Get_Ambient_Light();
  157. int callback_counter = 0;
  158. for (vi=0; vi<model->Get_Vertex_Count(); vi++) {
  159. AmbientSolve[vi].X = scene_ambient.X;
  160. AmbientSolve[vi].Y = scene_ambient.Y;
  161. AmbientSolve[vi].Z = scene_ambient.Z;
  162. AmbientSolve[vi].W = 1.0f;
  163. DiffuseSolve[vi].X = 0.0f;
  164. DiffuseSolve[vi].Y = 0.0f;
  165. DiffuseSolve[vi].Z = 0.0f;
  166. DiffuseSolve[vi].W = MeshDiffuse[vi].W;
  167. /*
  168. ** Sun
  169. */
  170. Add_Light_To_Vertex(context,vi,sun);
  171. /*
  172. ** Other lights
  173. */
  174. NonRefPhysListIterator it(&lights);
  175. while (!it.Is_Done()) {
  176. LightPhysClass * light = it.Peek_Obj()->As_LightPhysClass();
  177. if ((light) && (light->Peek_Model()) && (light->Peek_Model()->Class_ID() == RenderObjClass::CLASSID_LIGHT)) {
  178. LightClass * light_obj = (LightClass*)light->Peek_Model();
  179. Add_Light_To_Vertex(context,vi,light_obj);
  180. }
  181. it.Next();
  182. }
  183. context.Get_Progress().Set_Current_Vertex(vi);
  184. callback_counter++;
  185. if (callback_counter > 100) {
  186. callback_counter = 0;
  187. context.Update_Observer();
  188. }
  189. }
  190. VectorProcessorClass::Clamp(&(AmbientSolve[0]),&(AmbientSolve[0]), 0.0f, 1.0f,vcount);
  191. VectorProcessorClass::Clamp(&(DiffuseSolve[0]),&(DiffuseSolve[0]), 0.0f, 1.0f,vcount);
  192. /*
  193. ** Modulate the accumulated light by the material properties
  194. */
  195. for (vi=0; vi<vcount; vi++) {
  196. Solve[vi].X = AmbientSolve[vi].X * MeshAmbient[vi].X + DiffuseSolve[vi].X * MeshDiffuse[vi].X;
  197. Solve[vi].Y = AmbientSolve[vi].Y * MeshAmbient[vi].Y + DiffuseSolve[vi].Y * MeshDiffuse[vi].Y;
  198. Solve[vi].Z = AmbientSolve[vi].Z * MeshAmbient[vi].Z + DiffuseSolve[vi].Z * MeshDiffuse[vi].Z;
  199. Solve[vi].W = DiffuseSolve[vi].W;
  200. }
  201. VectorProcessorClass::Clamp(&(Solve[0]),&(Solve[0]), 0.0f, 1.0f,vcount);
  202. mesh->Install_User_Lighting_Array(&(Solve[0]));
  203. context.Update_Observer();
  204. REF_PTR_RELEASE(sun);
  205. }
  206. static Vector3 _offset (0, 0, 0);
  207. void VertexSolveClass::Light_Terrain(LightSolveContextClass & context,RenegadeTerrainPatchClass * patch, NonRefPhysListClass & lights)
  208. {
  209. int vi;
  210. context.Get_Progress().Set_Current_Mesh_Name(patch->Get_Name());
  211. context.Get_Progress().Set_Current_Mesh_Vertex_Count(patch->Get_Vertex_Count());
  212. /*
  213. ** Get the model and initialize our arrays
  214. */
  215. int vcount = patch->Get_Vertex_Count();
  216. Grow_Arrays(vcount);
  217. /*
  218. ** Transform the positions and normals into world space
  219. */
  220. Matrix3D tm = patch->Get_Transform();
  221. const Vector3 * src_verts = patch->Get_Vertex_Array();
  222. const Vector3 * src_norms = patch->Get_Vertex_Normal_Array();
  223. VectorProcessorClass::Transform(&(Position[0]), src_verts, tm, vcount);
  224. tm.Set_Translation(Vector3(0,0,0));
  225. VectorProcessorClass::Transform(&(Normal[0]), src_norms, tm, vcount);
  226. /*
  227. ** For heightfield terrains, the mesh ambient and diffuse colors are white
  228. */
  229. for (int index = 0; index < vcount; index ++) {
  230. MeshAmbient[index].Set (1.0F, 1.0F, 1.0F, 1.0F);
  231. MeshDiffuse[index].Set (1.0F, 1.0F, 1.0F, 1.0F);
  232. }
  233. /*
  234. ** Compute the light solve
  235. */
  236. LightClass * sun = PhysicsSceneClass::Get_Instance()->Get_Sun_Light();
  237. Vector3 scene_ambient = PhysicsSceneClass::Get_Instance()->Get_Ambient_Light();
  238. int callback_counter = 0;
  239. for (vi = 0; vi < vcount; vi ++) {
  240. AmbientSolve[vi].X = scene_ambient.X;
  241. AmbientSolve[vi].Y = scene_ambient.Y;
  242. AmbientSolve[vi].Z = scene_ambient.Z;
  243. AmbientSolve[vi].W = 1.0f;
  244. DiffuseSolve[vi].X = 0.0f;
  245. DiffuseSolve[vi].Y = 0.0f;
  246. DiffuseSolve[vi].Z = 0.0f;
  247. DiffuseSolve[vi].W = MeshDiffuse[vi].W;
  248. /*
  249. ** Sun
  250. */
  251. Add_Light_To_Vertex(context,vi,sun);
  252. /*
  253. ** Other lights
  254. */
  255. NonRefPhysListIterator it(&lights);
  256. while (!it.Is_Done()) {
  257. LightPhysClass * light = it.Peek_Obj()->As_LightPhysClass();
  258. if ((light) && (light->Peek_Model()) && (light->Peek_Model()->Class_ID() == RenderObjClass::CLASSID_LIGHT)) {
  259. LightClass * light_obj = (LightClass*)light->Peek_Model();
  260. Add_Light_To_Vertex(context,vi,light_obj);
  261. }
  262. it.Next();
  263. }
  264. context.Get_Progress().Set_Current_Vertex(vi);
  265. callback_counter++;
  266. if (callback_counter > 100) {
  267. callback_counter = 0;
  268. context.Update_Observer();
  269. }
  270. }
  271. VectorProcessorClass::Clamp(&(AmbientSolve[0]),&(AmbientSolve[0]), 0.0f, 1.0f,vcount);
  272. VectorProcessorClass::Clamp(&(DiffuseSolve[0]),&(DiffuseSolve[0]), 0.0f, 1.0f,vcount);
  273. /*
  274. ** Modulate the accumulated light by the material properties
  275. */
  276. for (vi=0; vi<vcount; vi++) {
  277. Solve[vi].X = AmbientSolve[vi].X * MeshAmbient[vi].X + DiffuseSolve[vi].X * MeshDiffuse[vi].X;
  278. Solve[vi].Y = AmbientSolve[vi].Y * MeshAmbient[vi].Y + DiffuseSolve[vi].Y * MeshDiffuse[vi].Y;
  279. Solve[vi].Z = AmbientSolve[vi].Z * MeshAmbient[vi].Z + DiffuseSolve[vi].Z * MeshDiffuse[vi].Z;
  280. Solve[vi].W = DiffuseSolve[vi].W;
  281. }
  282. VectorProcessorClass::Clamp(&(Solve[0]),&(Solve[0]), 0.0f, 1.0f,vcount);
  283. /*
  284. ** Pass the resultant vertex colors onto the patch
  285. */
  286. for (vi=0; vi<vcount; vi++) {
  287. patch->Set_Vertex_Color (vi, Vector3 (Solve[vi].X, Solve[vi].Y, Solve[vi].Z));
  288. }
  289. //
  290. // Let the patch know that it is now prelit
  291. //
  292. patch->Set_Is_Prelit (true);
  293. return ;
  294. }
  295. void VertexSolveClass::Add_Light_To_Vertex(LightSolveContextClass & context,int vi,LightClass * light_obj)
  296. {
  297. if (light_obj->Is_Within_Attenuation_Radius(Position[vi])) {
  298. // cast ray...
  299. bool is_occluded = false;
  300. #pragma message("need a collision group for light occlusion here...")
  301. if (context.Is_Occlusion_Enabled()) {
  302. CastResultStruct res;
  303. Vector3 p0 = Position[vi];
  304. Vector3 p1 = light_obj->Get_Position();
  305. if (light_obj->Get_Type() == LightClass::DIRECTIONAL) {
  306. Vector3 dir = -(light_obj->Get_Transform().Get_Z_Vector());
  307. p1 = p0 + dir * light_obj->Get_Attenuation_Range();
  308. }
  309. const float MOVE_AMOUNT = 0.25f; // can't be occluded closer than this
  310. Vector3 delta = p1-p0;
  311. delta.Normalize();
  312. p0 += MOVE_AMOUNT * delta;
  313. p0 += _offset;
  314. p1 += _offset;
  315. LineSegClass ray(p0,p1);
  316. PhysRayCollisionTestClass raytest(ray,&res,0,COLLISION_TYPE_PROJECTILE);
  317. raytest.CheckDynamicObjs = false;
  318. PhysicsSceneClass::Get_Instance()->Cast_Ray(raytest);
  319. if (res.Fraction < 1.0f) {
  320. is_occluded = true;
  321. }
  322. }
  323. if (!is_occluded) {
  324. Vector3 ambient;
  325. Vector3 diffuse;
  326. light_obj->Compute_Lighting(Position[vi],Normal[vi],&ambient,&diffuse);
  327. AmbientSolve[vi] += Vector4(ambient.X,ambient.Y,ambient.Z,0.0f);
  328. DiffuseSolve[vi] += Vector4(diffuse.X,diffuse.Y,diffuse.Z,0.0f);
  329. }
  330. }
  331. }
  332. void VertexSolveClass::Grow_Arrays(int vcount)
  333. {
  334. Position.Uninitialised_Grow(vcount);
  335. Normal.Uninitialised_Grow(vcount);
  336. AmbientSolve.Uninitialised_Grow(vcount);
  337. DiffuseSolve.Uninitialised_Grow(vcount);
  338. Solve.Uninitialised_Grow(vcount);
  339. MeshAmbient.Uninitialised_Grow(vcount);
  340. MeshDiffuse.Uninitialised_Grow(vcount);
  341. }
  342. /*****************************************************************************
  343. **
  344. ** LightSolveClass Implementation
  345. **
  346. *****************************************************************************/
  347. /*
  348. Global Solve:
  349. - start with StaticObjList
  350. - pass list to cull function
  351. - cull function gens new list with objects that will not be lit removed
  352. - pass to solve function which accepts a list
  353. - iterate list, pass each to solve function for an object
  354. List Solve:
  355. - pass list to cull function
  356. - cull function gens new list with objects that will not be lit removed
  357. - pass to solve function which accepts a list
  358. - iterate list, pass each to solve function for an object
  359. */
  360. void LightSolveClass::Generate_Static_Light_Solve(LightSolveContextClass & context)
  361. {
  362. RefPhysListIterator it = PhysicsSceneClass::Get_Instance()->Get_Static_Object_Iterator();
  363. /*
  364. ** Build a list of the objects to be solved
  365. */
  366. RefPhysListClass solve_list;
  367. while (!it.Is_Done()) {
  368. StaticPhysClass * obj = it.Peek_Obj()->As_StaticPhysClass();
  369. if ((obj != NULL) && (Does_Obj_Get_Static_Light_Solve(obj))) {
  370. solve_list.Add(obj);
  371. }
  372. it.Next();
  373. }
  374. /*
  375. ** Pass the list to the solve function
  376. */
  377. Compute_Solve(context,solve_list);
  378. }
  379. void LightSolveClass::Generate_Static_Light_Solve(LightSolveContextClass & context,RefPhysListClass & input_list)
  380. {
  381. RefPhysListClass solve_list;
  382. /*
  383. ** Cull out objects which should not get a lighting solve
  384. */
  385. RefPhysListIterator it(&input_list);
  386. while (!it.Is_Done()) {
  387. StaticPhysClass * obj = it.Peek_Obj()->As_StaticPhysClass();
  388. if ((obj != NULL) && (Does_Obj_Get_Static_Light_Solve(obj))) {
  389. solve_list.Add(obj);
  390. }
  391. it.Next();
  392. }
  393. /*
  394. ** Pass the list to the solve function
  395. */
  396. Compute_Solve(context,solve_list);
  397. }
  398. void LightSolveClass::Compute_Solve(LightSolveContextClass & context,RefPhysListClass & obj_list)
  399. {
  400. RefPhysListIterator it(&obj_list);
  401. /*
  402. ** Count the objects for the progress display
  403. */
  404. int count = 0;
  405. while (!it.Is_Done()) {
  406. count++;
  407. it.Next();
  408. }
  409. context.Get_Progress().Set_Object_Count(count);
  410. /*
  411. ** Generate a light solve for each static object
  412. */
  413. it.First();
  414. while (!it.Is_Done() && !context.Get_Progress().Is_Cancel_Requested()) {
  415. StaticPhysClass * obj = it.Peek_Obj()->As_StaticPhysClass();
  416. if (obj != NULL) {
  417. Compute_Solve(context,obj);
  418. context.Get_Progress().Increment_Processed_Object_Count();
  419. }
  420. it.Next();
  421. }
  422. /*
  423. ** Re-gen all VB's, IB's, etc
  424. */
  425. TheDX8MeshRenderer.Invalidate();
  426. }
  427. void LightSolveClass::Compute_Solve(LightSolveContextClass & context,StaticPhysClass * obj)
  428. {
  429. WWASSERT(obj != NULL);
  430. if (context.Get_Progress().Get_Object_Count() == 0) {
  431. context.Get_Progress().Set_Object_Count(1);
  432. }
  433. /*
  434. ** Generate a light solve for each mesh in this object
  435. */
  436. if (obj->Is_Model_Pre_Lit() == false) {
  437. NonRefPhysListClass light_list;
  438. PhysicsSceneClass::Get_Instance()->Collect_Lights(obj->Get_Cull_Box(),true,false,&light_list);
  439. Compute_Solve(context,obj->Peek_Model(),light_list);
  440. }
  441. }
  442. void LightSolveClass::Compute_Solve(LightSolveContextClass & context,RenderObjClass * obj,NonRefPhysListClass & light_list)
  443. {
  444. /*
  445. ** Mark this render object as containing a static lighting solve
  446. */
  447. obj->Set_Has_User_Lighting(true);
  448. /*
  449. ** Recurse through all sub-objects
  450. */
  451. WWASSERT(obj != NULL);
  452. for (int i=0; i<obj->Get_Num_Sub_Objects(); i++) {
  453. RenderObjClass * sub_obj = obj->Get_Sub_Object(i);
  454. if (sub_obj != NULL) {
  455. Compute_Solve(context,sub_obj,light_list);
  456. REF_PTR_RELEASE(sub_obj);
  457. }
  458. }
  459. /*
  460. ** For all mesh objects, generate a vertex lighting solution.
  461. */
  462. if (obj->Class_ID() == RenderObjClass::CLASSID_MESH) {
  463. MeshClass * mesh = (MeshClass*)obj;
  464. WWDEBUG_SAY(("Generating Solve for Mesh: %s\r\n",mesh->Get_Name()));
  465. TheVertexSolver.Light_Mesh(context,mesh,light_list);
  466. } else if (obj->Class_ID() == RenderObjClass::CLASSID_RENEGADE_TERRAIN) {
  467. TheVertexSolver.Light_Terrain(context,(RenegadeTerrainPatchClass *)obj,light_list);
  468. }
  469. }
  470. bool LightSolveClass::Does_Obj_Get_Static_Light_Solve(StaticPhysClass * obj)
  471. {
  472. if (obj->Is_Model_Pre_Lit()) {
  473. return false;
  474. }
  475. return Does_Model_Get_Static_Light_Solve(obj->Peek_Model());
  476. }
  477. bool LightSolveClass::Does_Model_Get_Static_Light_Solve(RenderObjClass * model)
  478. {
  479. if (model == NULL) {
  480. return false;
  481. }
  482. if (!model->Is_Not_Hidden_At_All()) {
  483. return false;
  484. }
  485. if (model->Get_Num_Sub_Objects() > 0) {
  486. bool any_sub_objs_visible = false;
  487. for (int i=0; i<model->Get_Num_Sub_Objects(); i++) {
  488. RenderObjClass * sub_obj = model->Get_Sub_Object(i);
  489. if (sub_obj != NULL) {
  490. if (sub_obj->Is_Not_Hidden_At_All()) {
  491. any_sub_objs_visible = true;
  492. }
  493. REF_PTR_RELEASE(sub_obj);
  494. }
  495. }
  496. if (any_sub_objs_visible == false) {
  497. return false;
  498. }
  499. }
  500. // TODO: check if the entire model is emissive???
  501. return true;
  502. }