/* * Descent 3 * Copyright (C) 2024 Parallax Software * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "stdafx.h" #include "editor.h" #include "LightingStatus.h" #include "radiosity.h" #include "pserror.h" #include "findintersection.h" #include "hemicube.h" #include "descent.h" #include "rad_cast.h" #include "ddio.h" #include "vecmat.h" #include #include "mem.h" // Some radiosity globals int Shoot_method=SM_HEMICUBE; int Hemicube_resolution=1024; int Ignore_terrain=0; int Ignore_satellites=0; float rad_TotalFlux=0.0f; float rad_Convergence=1.0f; int rad_NumSurfaces; int rad_NumElements; float *rad_FormFactors; int rad_StepCount=0; int rad_MaxStep=1; int rad_DoneCalculating=0; float rad_TotalUnsent=0.0f; rad_surface *rad_MaxSurface=NULL; rad_surface *rad_Surfaces; int UseVolumeLights=0; // User selectable to do volumelights int Calculate_specular_lighting=0; // Specular variable float *Room_strongest_value[MAX_ROOMS][4]; // Tells radiosity renderer to do volume lighting int Do_volume_lighting=0; volume_element *Volume_elements[MAX_VOLUME_ELEMENTS]; // Shoot_from_patch tells us whether or not we're shooting from the center of // a surface or if we must shoot from the center of each of its individual elements // Shoot_from_patch=1 is much faster int Shoot_from_patch=1; int DoRadiosityRun (int method,rad_surface *light_surfaces,int count) { float start_time; mprintf ((0,"Calculating radiosity on %d faces.\n",count)); rad_Surfaces=light_surfaces; rad_NumSurfaces=count; Shoot_method=method; start_time = timer_GetTime(); InitRadiosityRun(); // Setup our window CLightingStatus dlg; dlg.Create(IDD_LIGHTINGSTATUS); CalculateRadiosity (); dlg.DestroyWindow(); CloseRadiosityRun(); // Print time taken mprintf((0,"\nLighting took %.4f seconds.\n",timer_GetTime()-start_time)); return 1; } // Sets up our radiosity run void InitRadiosityRun () { rad_TotalFlux=0.0f; rad_StepCount=0; rad_DoneCalculating=0; // Clear key buffer // ddio_KeyFrame(); ddio_KeyFlush(); CountElements(); CalculateArea (); InitExitance (); if (Shoot_method==SM_HEMICUBE) { SetupFormFactors (); InitHemicube(Hemicube_resolution); } } // Initalizes memory for form factors void SetupFormFactors () { ASSERT (rad_NumElements>0); rad_FormFactors=(float *)mem_malloc (rad_NumElements * sizeof (float)); ASSERT (rad_FormFactors!=NULL); } void CalculateAreaForSurface (rad_surface *sp) { int i; vector normal; vm_GetPerp (&normal,&sp->verts[0],&sp->verts[1],&sp->verts[2]); sp->area=(vm_GetMagnitude (&normal)/2); for (i=2;inum_verts-1;i++) { vm_GetPerp (&normal,&sp->verts[0],&sp->verts[i],&sp->verts[i+1]); sp->area+=(vm_GetMagnitude (&normal)/2); } sp->surface_area=sp->area; sp->element_area=sp->area/(sp->xresolution*sp->yresolution); } void CalculateAreaForElement (rad_element *ep) { int i; vector normal; if (ep->flags & EF_IGNORE) { ep->area=.0000001f; return; } vm_GetPerp (&normal,&ep->verts[0],&ep->verts[1],&ep->verts[2]); ep->area=(vm_GetMagnitude (&normal)/2); for (i=2;inum_verts-1;i++) { vm_GetPerp (&normal,&ep->verts[0],&ep->verts[i],&ep->verts[i+1]); ep->area+=(vm_GetMagnitude (&normal)/2); } if (ep->area<.05) ep->flags|=EF_SMALL; if (ep->area==0) { ep->flags|=EF_IGNORE; ep->area=.00000001f; } } // Calculates the area of the surfaces and elements in our environment void CalculateArea () { rad_surface *surf; int i,t; for (i=0;ixresolution*surf->yresolution;t++) { rad_element *ep=&surf->elements[t]; CalculateAreaForElement (ep); } } } // Counts the total number of elements we have to work with void CountElements () { rad_surface *surf; int i; rad_NumElements=0; for (i=0;ixresolution*surf->yresolution); } mprintf ((0,"Number of elements=%d\n",rad_NumElements)); } // Initializes the exitances for all surfaces void InitExitance () { int i; for (i=0;iemittance; } // Sets all the elements of a surface to their initial unshot exitance values void SetExitanceForSurface (rad_surface *surf) { int i; surf->exitance=surf->emittance; for (i=0;ixresolution*surf->yresolution;i++) { if (Shoot_from_patch) { surf->elements[i].exitance.r=0; surf->elements[i].exitance.g=0; surf->elements[i].exitance.b=0; } else surf->elements[i].exitance=surf->emittance; } rad_TotalFlux+=GetUnsentFlux (surf); } // Find the surface we want to shoot from void UpdateUnsentValues () { float cur_unsent; float max_unsent=0.0f; float sat_max_unsent=0.0f; int use_sat=0; int i; rad_surface *sat_surface; static last_report_time=-10; rad_TotalUnsent=0.0f; rad_MaxSurface=NULL; // Go through all the surfaces searching for the surface with the greatest // exitance yet to be shot for (i=0;i max_unsent) { max_unsent = cur_unsent; rad_MaxSurface=surf; } // Always give satellites priority if (surf->surface_type==ST_SATELLITE && cur_unsent>0) { if (cur_unsent>sat_max_unsent) { use_sat=1; sat_max_unsent=cur_unsent; sat_surface=surf; } } } // Update convergence if (rad_TotalFlux > .0001) rad_Convergence = fabs(rad_TotalUnsent) / rad_TotalFlux; else rad_Convergence = 0.0; mprintf_at ((2,3,0,"Left=%f ",rad_Convergence)); if (timer_GetTime()-last_report_time>10.0) { mprintf ((0,"Percentage left=%f\n",rad_Convergence)); last_report_time=timer_GetTime(); } if (use_sat) rad_MaxSurface=sat_surface; if (!use_sat && Shoot_method==SM_SWITCH_AFTER_SATELLITES) { SetupFormFactors (); InitHemicube(Hemicube_resolution); Shoot_method=SM_HEMICUBE; } // No energy left to shoot? if (rad_MaxSurface==NULL || rad_TotalUnsent==0) rad_DoneCalculating=1; } // Finds the world coordinate center of a surface void GetCenterOfSurface (rad_surface *sp,vector *dest) { vm_GetCentroid (dest,sp->verts,sp->num_verts); } // Finds the world coordinate center of a surface void GetCenterOfElement (rad_element *ep,vector *dest) { vm_GetCentroid (dest,ep->verts,ep->num_verts); } void CalculateRadiosity () { int key; while (!rad_DoneCalculating) { if (rad_StepCount >= rad_MaxStep) { rad_DoneCalculating=1; break; } mprintf_at ((2,2,0,"Lightcount=%d ",rad_StepCount)); DoRadiosityIteration(); rad_StepCount++; Descent->defer(); // ddio_KeyFrame(); while ((key = ddio_KeyInKey())!=0) { if (key==KEY_LAPOSTRO) { rad_DoneCalculating=1; break; } } } // Clear key buffer ddio_KeyFlush(); } // returns the amount of unsent flux from a surface float GetUnsentFlux (rad_surface *surface) { float flux; flux=surface->exitance.r+surface->exitance.g+surface->exitance.b; if (surface->surface_type!=ST_SATELLITE) flux*=surface->area; return flux; } float GetMaxColor (spectra *sp) { float m; m=(float)max (sp->r,sp->g); m=(float)max (sp->b,m); return m; } int FixEdges=0; extern void AddSpectra (spectra *dest,spectra *a,spectra *b); void NormalizeExitance() { int i,t; float rmax=0.0f; for (i=0;iemittance; for (t=0;txresolution*surf->yresolution;t++) { rad_element *ep=&surf->elements[t]; if (ep->flags & EF_IGNORE) continue; if (Shoot_from_patch) { ep->exitance.r+=emit->r; ep->exitance.g+=emit->g; ep->exitance.b+=emit->b; } /* if (ep->exitance.r>1) ep->exitance.r=1; if (ep->exitance.g>1) ep->exitance.g=1; if (ep->exitance.b>1) ep->exitance.b=1;*/ rmax=GetMaxColor(&ep->exitance); if (rmax>1.0 && rmax>0.0) { ep->exitance.r/=rmax; ep->exitance.g/=rmax; ep->exitance.b/=rmax; } } } } // Shuts down the radiosity stuff, freeing memory, etc void CloseRadiosityRun () { NormalizeExitance(); if (Shoot_method==SM_HEMICUBE) { mem_free (rad_FormFactors); CloseHemicube(); } } void Calculate () { if (Shoot_method==SM_HEMICUBE) CalculateFormFactorsHemiCube (); else CalculateFormFactorsRaycast(); // Set unshot exitance for MaxSurface to zero rad_MaxSurface->exitance.r=0; rad_MaxSurface->exitance.g=0; rad_MaxSurface->exitance.b=0; rad_MaxSurface->flags &=~SF_LIGHTSOURCE; } // Does one iteration of ray-casting radiosity int DoRadiosityIteration () { UpdateUnsentValues(); if (!rad_DoneCalculating) Calculate (); return 1; }