/* * 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" #include "mono.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 = mem_rmalloc(rad_NumElements); 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; i < sp->num_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; i < ep->num_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; i < rad_NumSurfaces; i++) { surf = &rad_Surfaces[i]; CalculateAreaForSurface(surf); for (t = 0; t < surf->xresolution * 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; i < rad_NumSurfaces; i++) { surf = &rad_Surfaces[i]; rad_NumElements += (surf->xresolution * surf->yresolution); } mprintf(0, "Number of elements=%d\n", rad_NumElements); } // Initializes the exitances for all surfaces void InitExitance() { int i; for (i = 0; i < rad_NumSurfaces; i++) { SetExitanceForSurface(&rad_Surfaces[i]); } } // Gets the spectral emittance for a surface void GetEmittance(rad_surface *surf, spectra *dest) { *dest = surf->emittance; } // 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; i < surf->xresolution * 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 int 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 < rad_NumSurfaces; i++) { rad_surface *surf = &rad_Surfaces[i]; cur_unsent = GetUnsentFlux(surf); rad_TotalUnsent += cur_unsent; if (cur_unsent > 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; 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; } 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 = std::max(sp->r, sp->g); m = std::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; i < rad_NumSurfaces; i++) { rad_surface *surf = &rad_Surfaces[i]; spectra *emit = &surf->emittance; for (t = 0; t < surf->xresolution * 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; }