/* * 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 . --- HISTORICAL COMMENTS FOLLOW --- * $Logfile: /DescentIII/Main/editor/editor_lighting.cpp $ * $Revision: 1.1.1.1 $ * $Date: 2003-08-26 03:57:38 $ * $Author: kevinb $ * $NoKeywords: $ */ #include #include #include "3d.h" #include "texture.h" #include "gametexture.h" #include "erooms.h" #include "editor_lighting.h" #include "descent.h" #include "room.h" #include "lightmap.h" #include "polymodel.h" #include #include #include "terrain.h" #include "radiosity.h" #include "lighting.h" #include "findintersection.h" #include "lightmap_info.h" #include "object_lighting.h" #include "d3edit.h" #include "ddio.h" #include "bsp.h" #include "Application.h" #include "AppDatabase.h" #include "program.h" #include "loadlevel.h" #include "special_face.h" #include "boa.h" #include "mem.h" struct spec_vertex { float x, y; }; int AllowCombining = 1; float GlobalMultiplier = 1.0; rad_surface *Light_surfaces = NULL; vector ScratchCenters[MAX_LIGHTMAP_INFOS]; vector ScratchRVecs[MAX_LIGHTMAP_INFOS]; vector ScratchUVecs[MAX_LIGHTMAP_INFOS]; float Room_multiplier[MAX_ROOMS + MAX_PALETTE_ROOMS]; float Room_ambience_r[MAX_ROOMS + MAX_PALETTE_ROOMS], Room_ambience_g[MAX_ROOMS + MAX_PALETTE_ROOMS], Room_ambience_b[MAX_ROOMS + MAX_PALETTE_ROOMS]; uint8_t *TerrainLightSpeedup[MAX_SATELLITES]; int LightSpacing = LIGHTMAP_SPACING; int BestFit = 0; int Square_surfaces = 0; int Lightmaps_for_rad = 0; // Ambient values for terrain float Ambient_red = 0.0f, Ambient_green = 0.0f, Ambient_blue = 0.0f; void DoTerrainDynamicTable(); uint8_t *Lightmap_mask = NULL; static uint8_t *Lmi_spoken_for; int Squeeze_lightmap_handle = -1; int FindEmptyMaskSpot(int w, int h, int *dest_x, int *dest_y) { int cur_x = 0, cur_y = 0; int i, t; for (cur_y = 0; cur_y < 128; cur_y++) { if (cur_y + h > 128) return 0; for (cur_x = 0; cur_x < 128; cur_x++) { if (cur_x + w > 128) continue; int hit_mask = 0; for (i = 0; i < h && !hit_mask; i++) { for (t = 0; t < w && !hit_mask; t++) { if (Lightmap_mask[((cur_y + i) * 128) + cur_x + t]) hit_mask = 1; } } if (hit_mask == 0) { // Hurray! We found an empty spot *dest_x = cur_x; *dest_y = cur_y; return 1; } } } return 0; } void CopySqueezeBodyAndEdges(uint16_t *dest_data, uint16_t *src_data, int w, int h, int dest_x, int dest_y) { int i, t; ASSERT(w + dest_x <= 126); ASSERT(h + dest_y <= 126); ASSERT(dest_x >= 0 && dest_y >= 0); // First copy the main body for (i = 0; i < h; i++) { for (t = 0; t < w; t++) { dest_data[((dest_y + 1 + i) * 128) + (dest_x + 1 + t)] = src_data[(w * i) + t]; Lightmap_mask[((dest_y + i + 1) * 128) + dest_x + 1 + t] = 1; } } // Now copy the edges // Left edge for (i = 0; i < h; i++) { dest_data[((dest_y + 1 + i) * 128) + (dest_x + 0)] = src_data[(w * i) + 0]; Lightmap_mask[((dest_y + i + 1) * 128) + dest_x + 0] = 1; } // Right edge for (i = 0; i < h; i++) { dest_data[((dest_y + 1 + i) * 128) + (dest_x + w + 1)] = src_data[(w * i) + (w - 1)]; Lightmap_mask[((dest_y + i + 1) * 128) + dest_x + w + 1] = 1; } // Top edge for (i = 0; i < w; i++) { dest_data[(dest_y * 128) + (dest_x + i + 1)] = src_data[i]; Lightmap_mask[(dest_y * 128) + (dest_x + i + 1)] = 1; } // Bottom edge for (i = 0; i < w; i++) { dest_data[((dest_y + 1 + h) * 128) + (dest_x + i + 1)] = src_data[(w * (h - 1)) + i]; Lightmap_mask[((dest_y + 1 + h) * 128) + (dest_x + i + 1)] = 1; } // Now copy the corners // Upper left dest_data[(dest_y * 128) + (dest_x)] = src_data[0]; Lightmap_mask[(dest_y * 128) + (dest_x)] = 1; // Upper right dest_data[(dest_y * 128) + (dest_x + w + 1)] = src_data[w - 1]; Lightmap_mask[(dest_y * 128) + (dest_x + w + 1)] = 1; // Lower left dest_data[((dest_y + h + 1) * 128) + (dest_x)] = src_data[w * (h - 1)]; Lightmap_mask[((dest_y + h + 1) * 128) + (dest_x)] = 1; // Lower right dest_data[((dest_y + h + 1) * 128) + (dest_x + w + 1)] = src_data[(w * (h - 1)) + (w - 1)]; Lightmap_mask[((dest_y + h + 1) * 128) + (dest_x + w + 1)] = 1; } void CopySqueezeDataForRooms(int roomnum, int facenum, uint16_t *dest_data, int dest_x, int dest_y) { room *rp = &Rooms[roomnum]; lightmap_info *lmi_ptr = &LightmapInfo[rp->faces[facenum].lmi_handle]; uint16_t *src_data = (uint16_t *)lm_data(lmi_ptr->lm_handle); int w = lmi_ptr->width; int h = lmi_ptr->height; // Copy over the actual lightmap data CopySqueezeBodyAndEdges(dest_data, src_data, w, h, dest_x, dest_y); // Now alter all face uvs that have this lightmap info float dest_u = (float)(dest_x + 1) / 128.0; float dest_v = (float)(dest_y + 1) / 128.0; float u_scalar = (float)w / 128.0; float v_scalar = (float)h / 128.0; for (int t = roomnum; t <= Highest_room_index; t++) { room *this_rp = &Rooms[t]; if (!this_rp->used) continue; for (int j = 0; j < this_rp->num_faces; j++) { if (!(this_rp->faces[j].flags & FF_LIGHTMAP)) continue; if (this_rp->faces[j].lmi_handle == BAD_LMI_INDEX) continue; if (Lmi_spoken_for[this_rp->faces[j].lmi_handle]) continue; if (this_rp->faces[j].lmi_handle == rp->faces[facenum].lmi_handle) { lmi_ptr->x1 = dest_x + 1; lmi_ptr->y1 = dest_y + 1; // We have to alter our uvs for this face to reflect our change face *fp = &this_rp->faces[j]; for (int k = 0; k < fp->num_verts; k++) { fp->face_uvls[k].u2 *= u_scalar; fp->face_uvls[k].u2 += dest_u; fp->face_uvls[k].v2 *= v_scalar; fp->face_uvls[k].v2 += dest_v; } } } } ASSERT(rp->faces[facenum].lmi_handle != BAD_LMI_INDEX); Lmi_spoken_for[rp->faces[facenum].lmi_handle] = 1; // Free our old lightmap GameLightmaps[lmi_ptr->lm_handle].used = 1; lm_FreeLightmap(lmi_ptr->lm_handle); lmi_ptr->lm_handle = Squeeze_lightmap_handle; GameLightmaps[Squeeze_lightmap_handle].used++; } void CopySqueezeDataForObject(object *obj, int subnum, int facenum, uint16_t *dest_data, int dest_x, int dest_y) { lightmap_object_face *fp = &obj->lm_object.lightmap_faces[subnum][facenum]; lightmap_info *lmi_ptr = &LightmapInfo[fp->lmi_handle]; uint16_t *src_data = (uint16_t *)lm_data(lmi_ptr->lm_handle); int t, k; int w = lmi_ptr->width; int h = lmi_ptr->height; // Copy over the actual lightmap data CopySqueezeBodyAndEdges(dest_data, src_data, w, h, dest_x, dest_y); // Now alter all face uvs that have this lightmap info float dest_u = (float)(dest_x + 1) / 128.0; float dest_v = (float)(dest_y + 1) / 128.0; float u_scalar = (float)w / 128.0; float v_scalar = (float)h / 128.0; for (int objnum = obj - Objects; objnum != -1; objnum = Objects[objnum].next) { object *this_obj = &Objects[objnum]; if (this_obj->lighting_render_type != LRT_LIGHTMAPS) continue; if (!this_obj->lm_object.used) continue; for (t = 0; t < this_obj->lm_object.num_models; t++) { if (IsNonRenderableSubmodel(&Poly_models[this_obj->rtype.pobj_info.model_num], t)) continue; for (k = 0; k < this_obj->lm_object.num_faces[t]; k++) { lightmap_object_face *this_fp = &this_obj->lm_object.lightmap_faces[t][k]; int lmi_handle = this_fp->lmi_handle; if (lmi_handle == BAD_LMI_INDEX) continue; if (Lmi_spoken_for[lmi_handle]) continue; if (lmi_handle == fp->lmi_handle) { lmi_ptr->x1 = dest_x + 1; lmi_ptr->y1 = dest_y + 1; // We have to alter our uvs for this face to reflect our change for (int j = 0; j < this_fp->num_verts; j++) { this_fp->u2[j] *= u_scalar; this_fp->u2[j] += dest_u; this_fp->v2[j] *= v_scalar; this_fp->v2[j] += dest_v; } } } } } ASSERT(fp->lmi_handle != BAD_LMI_INDEX); Lmi_spoken_for[fp->lmi_handle] = 1; // Free our old lightmap GameLightmaps[lmi_ptr->lm_handle].used = 1; lm_FreeLightmap(lmi_ptr->lm_handle); lmi_ptr->lm_handle = Squeeze_lightmap_handle; GameLightmaps[Squeeze_lightmap_handle].used++; } // Simply clears flags for combine portals void ClearCombinePortals(int terrain) { for (int i = 0; i <= Highest_room_index; i++) { room *rp = &Rooms[i]; if (!rp->used) continue; if (terrain && !(rp->flags & RF_EXTERNAL)) continue; if (!terrain && (rp->flags & RF_EXTERNAL)) continue; for (int t = 0; t < rp->num_portals; t++) { portal *portal_a = &rp->portals[t]; portal_a->flags &= ~PF_COMBINED; } } } // For rendering...combines all portals if they are on the same plane void CheckCombinePortals(int terrain) { ClearCombinePortals(terrain); int combine_count = 0; mprintf(0, "Combining portals..."); for (int i = 0; i <= Highest_room_index; i++) { room *rp = &Rooms[i]; if (!rp->used) continue; if (terrain && !(rp->flags & RF_EXTERNAL)) continue; if (!terrain && (rp->flags & RF_EXTERNAL)) continue; for (int t = 0; t < rp->num_portals; t++) { portal *portal_a = &rp->portals[t]; face *face_a = &rp->faces[portal_a->portal_face]; if (portal_a->flags & PF_COMBINED) continue; // Don't combine if face is breakable if (GameTextures[face_a->tmap].flags & TF_BREAKABLE) continue; float dist_a = vm_DotProduct(&rp->verts[face_a->face_verts[0]], &face_a->normal); for (int k = 0; k < rp->num_portals; k++) { portal *portal_b = &rp->portals[k]; face *face_b = &rp->faces[portal_b->portal_face]; if (portal_b->flags & PF_COMBINED) continue; // Don't combine if one portal is render-faces and the other is not if ((portal_a->flags & PF_RENDER_FACES) != (portal_b->flags & PF_RENDER_FACES)) continue; // Don't combine if face is breakable if (GameTextures[face_b->tmap].flags & TF_BREAKABLE) continue; if (t == k) continue; // Check to see if the portals connect to the same room if (portal_a->croom != portal_b->croom) continue; /*// Check to see if they share a normal float dp=vm_DotProduct (&face_a->normal,&face_b->normal); if (dp < .95f) continue; // Check to see if the distances are same float dist_b=vm_DotProduct (&rp->verts[face_b->face_verts[0]],&face_b->normal); if (fabs(dist_b-dist_a)>.5) continue; int match=0; // Test to see if any points at all can touch for (int x=0; xnum_verts && !match; x++ ) { for (int y=0; ynum_verts && !match; y++ ) { if (PointsAreSame(&rp->verts[face_a->face_verts[x]],&rp->verts[face_b->face_verts[y]])) match=1; } } if (match==0) continue; //Check to see if the portals connect to the same room if (portal_a->croom != portal_b->croom) continue; // Hurray! These portals can be combined*/ portal_a->flags |= PF_COMBINED; portal_b->flags |= PF_COMBINED; portal_a->combine_master = t; portal_b->combine_master = t; combine_count++; } } } mprintf(0, "%d portals combined.\n", combine_count); } // Squeezes all the lightmaps down into as few 128x128s as possible void SqueezeLightmaps(int external, int target_roomnum) { int i, t, k; mprintf(0, "Squeezing %s lightmaps, please wait...\n", external ? "external" : "internal"); Lmi_spoken_for = (uint8_t *)mem_malloc(MAX_LIGHTMAP_INFOS); Lightmap_mask = (uint8_t *)mem_malloc(128 * 128); Squeeze_lightmap_handle = -1; ASSERT(Lightmap_mask); ASSERT(Lmi_spoken_for); memset(Lmi_spoken_for, 0, MAX_LIGHTMAP_INFOS); memset(Lightmap_mask, 0, 128 * 128); // Go through all the rooms and sqeeze them one by one for (i = 0; i <= Highest_room_index; i++) { room *rp = &Rooms[i]; if (!rp->used) continue; if (rp->flags & RF_NO_LIGHT) continue; if (external) { if (!(rp->flags & RF_EXTERNAL)) continue; } else { if ((rp->flags & RF_EXTERNAL)) continue; if (target_roomnum != -1 && target_roomnum != i) continue; } // Go through each face for (t = 0; t < rp->num_faces; t++) { face *fp = &rp->faces[t]; int lmi_handle = fp->lmi_handle; if (!(fp->flags & FF_LIGHTMAP)) continue; if (lmi_handle == BAD_LMI_INDEX) continue; if (Lmi_spoken_for[lmi_handle]) continue; lightmap_info *lmi = &LightmapInfo[lmi_handle]; int dest_x, dest_y; int src_w = lmi->width; int src_h = lmi->height; if (Squeeze_lightmap_handle == -1) { memset(Lightmap_mask, 0, 128 * 128); Squeeze_lightmap_handle = lm_AllocLightmap(128, 128); uint16_t *fill_data = (uint16_t *)lm_data(Squeeze_lightmap_handle); memset(fill_data, 0, 128 * 128 * 2); } if (FindEmptyMaskSpot(src_w + 2, src_h + 2, &dest_x, &dest_y)) { // Cool, found a spot CopySqueezeDataForRooms(i, t, lm_data(Squeeze_lightmap_handle), dest_x, dest_y); } else { // Can't find an empty slot, so search through all the other remaining faces for (k = t; k < rp->num_faces; k++) { face *fp = &rp->faces[k]; int lmi_handle = fp->lmi_handle; if (!(fp->flags & FF_LIGHTMAP)) continue; if (lmi_handle == BAD_LMI_INDEX) continue; if (Lmi_spoken_for[lmi_handle]) continue; lightmap_info *lmi = &LightmapInfo[lmi_handle]; int src_w = lmi->width; int src_h = lmi->height; if (FindEmptyMaskSpot(src_w + 2, src_h + 2, &dest_x, &dest_y)) { CopySqueezeDataForRooms(i, k, lm_data(Squeeze_lightmap_handle), dest_x, dest_y); } } // Now, allocate a new lightmap and start over ASSERT(Squeeze_lightmap_handle != -1); ASSERT(GameLightmaps[Squeeze_lightmap_handle].used != 1); GameLightmaps[Squeeze_lightmap_handle].used--; memset(Lightmap_mask, 0, 128 * 128); Squeeze_lightmap_handle = lm_AllocLightmap(128, 128); uint16_t *fill_data = (uint16_t *)lm_data(Squeeze_lightmap_handle); memset(fill_data, 0, 128 * 128 * 2); ASSERT(Lmi_spoken_for[lmi_handle] == 0); if (FindEmptyMaskSpot(src_w + 2, src_h + 2, &dest_x, &dest_y)) { CopySqueezeDataForRooms(i, t, lm_data(Squeeze_lightmap_handle), dest_x, dest_y); } else { Int3(); // Get Jason, how did this happen???? } } } // Now search through all the objects in this room for (k = rp->objects; k != -1; k = Objects[k].next) { object *obj = &Objects[k]; if (obj->lighting_render_type != LRT_LIGHTMAPS) continue; if (!obj->lm_object.used) continue; for (t = 0; t < obj->lm_object.num_models; t++) { if (IsNonRenderableSubmodel(&Poly_models[obj->rtype.pobj_info.model_num], t)) continue; for (int j = 0; j < obj->lm_object.num_faces[t]; j++) { lightmap_object_face *fp = &obj->lm_object.lightmap_faces[t][j]; int lmi_handle = fp->lmi_handle; if (lmi_handle == BAD_LMI_INDEX) continue; if (Lmi_spoken_for[lmi_handle]) continue; lightmap_info *lmi = &LightmapInfo[lmi_handle]; int dest_x, dest_y; int src_w = lmi->width; int src_h = lmi->height; if (Squeeze_lightmap_handle == -1) { memset(Lightmap_mask, 0, 128 * 128); Squeeze_lightmap_handle = lm_AllocLightmap(128, 128); uint16_t *fill_data = (uint16_t *)lm_data(Squeeze_lightmap_handle); memset(fill_data, 0, 128 * 128 * 2); } if (FindEmptyMaskSpot(src_w + 2, src_h + 2, &dest_x, &dest_y)) { // Cool, found a spot CopySqueezeDataForObject(obj, t, j, lm_data(Squeeze_lightmap_handle), dest_x, dest_y); } else { // Can't find an empty slot, so search through all the other remaining faces for (int a = rp->objects; a != -1; a = Objects[a].next) { object *obj = &Objects[a]; if (obj->lighting_render_type != LRT_LIGHTMAPS) continue; if (!obj->lm_object.used) continue; for (int b = 0; b < obj->lm_object.num_models; b++) { if (IsNonRenderableSubmodel(&Poly_models[obj->rtype.pobj_info.model_num], b)) continue; for (int c = 0; c < obj->lm_object.num_faces[b]; c++) { lightmap_object_face *fp = &obj->lm_object.lightmap_faces[b][c]; int lmi_handle = fp->lmi_handle; if (lmi_handle == BAD_LMI_INDEX) continue; if (Lmi_spoken_for[lmi_handle]) continue; lightmap_info *lmi = &LightmapInfo[lmi_handle]; int src_w = lmi->width; int src_h = lmi->height; if (FindEmptyMaskSpot(src_w + 2, src_h + 2, &dest_x, &dest_y)) { CopySqueezeDataForObject(obj, b, c, lm_data(Squeeze_lightmap_handle), dest_x, dest_y); } } } } // Now, allocate a new lightmap and start over ASSERT(Squeeze_lightmap_handle != -1); ASSERT(GameLightmaps[Squeeze_lightmap_handle].used != 1); GameLightmaps[Squeeze_lightmap_handle].used--; memset(Lightmap_mask, 0, 128 * 128); Squeeze_lightmap_handle = lm_AllocLightmap(128, 128); uint16_t *fill_data = (uint16_t *)lm_data(Squeeze_lightmap_handle); memset(fill_data, 0, 128 * 128 * 2); ASSERT(Lmi_spoken_for[lmi_handle] == 0); if (FindEmptyMaskSpot(src_w + 2, src_h + 2, &dest_x, &dest_y)) { CopySqueezeDataForObject(obj, t, j, lm_data(Squeeze_lightmap_handle), dest_x, dest_y); } else { Int3(); // Get Jason, how did this happen???? } } } } } } if (Squeeze_lightmap_handle != -1) { ASSERT(GameLightmaps[Squeeze_lightmap_handle].used != 1); GameLightmaps[Squeeze_lightmap_handle].used--; } // Squeeze all terrain object lightmaps now if (external) { Squeeze_lightmap_handle = -1; for (i = 0; i <= Highest_object_index; i++) { object *obj = &Objects[i]; if (obj->type == OBJ_ROOM) continue; if (obj->lighting_render_type != LRT_LIGHTMAPS) continue; if (!obj->lm_object.used) continue; if (!OBJECT_OUTSIDE(obj)) continue; for (t = 0; t < obj->lm_object.num_models; t++) { if (IsNonRenderableSubmodel(&Poly_models[obj->rtype.pobj_info.model_num], t)) continue; for (k = 0; k < obj->lm_object.num_faces[t]; k++) { lightmap_object_face *fp = &obj->lm_object.lightmap_faces[t][k]; int lmi_handle = fp->lmi_handle; if (lmi_handle == BAD_LMI_INDEX) continue; if (Lmi_spoken_for[lmi_handle]) continue; lightmap_info *lmi = &LightmapInfo[lmi_handle]; int dest_x, dest_y; int src_w = lmi->width; int src_h = lmi->height; if (Squeeze_lightmap_handle == -1) { memset(Lightmap_mask, 0, 128 * 128); Squeeze_lightmap_handle = lm_AllocLightmap(128, 128); uint16_t *fill_data = (uint16_t *)lm_data(Squeeze_lightmap_handle); memset(fill_data, 0, 128 * 128 * 2); } if (FindEmptyMaskSpot(src_w + 2, src_h + 2, &dest_x, &dest_y)) { // Cool, found a spot CopySqueezeDataForObject(obj, t, k, lm_data(Squeeze_lightmap_handle), dest_x, dest_y); } else { // Search through the remaining objects on the terrain for (int a = i; a <= Highest_object_index; a++) { object *obj = &Objects[a]; if (!obj->lm_object.used) continue; if (!OBJECT_OUTSIDE(obj)) continue; for (int b = 0; b < obj->lm_object.num_models; b++) { if (IsNonRenderableSubmodel(&Poly_models[obj->rtype.pobj_info.model_num], b)) continue; for (int c = 0; c < obj->lm_object.num_faces[b]; c++) { lightmap_object_face *fp = &obj->lm_object.lightmap_faces[b][c]; int lmi_handle = fp->lmi_handle; if (lmi_handle == BAD_LMI_INDEX) continue; if (Lmi_spoken_for[lmi_handle]) continue; lightmap_info *lmi = &LightmapInfo[lmi_handle]; int src_w = lmi->width; int src_h = lmi->height; if (FindEmptyMaskSpot(src_w + 2, src_h + 2, &dest_x, &dest_y)) { CopySqueezeDataForObject(obj, b, c, lm_data(Squeeze_lightmap_handle), dest_x, dest_y); } } } } // Now, allocate a new lightmap and start over ASSERT(Squeeze_lightmap_handle != -1); ASSERT(GameLightmaps[Squeeze_lightmap_handle].used != 1); GameLightmaps[Squeeze_lightmap_handle].used--; memset(Lightmap_mask, 0, 128 * 128); Squeeze_lightmap_handle = lm_AllocLightmap(128, 128); uint16_t *fill_data = (uint16_t *)lm_data(Squeeze_lightmap_handle); memset(fill_data, 0, 128 * 128 * 2); ASSERT(Lmi_spoken_for[lmi_handle] == 0); if (FindEmptyMaskSpot(src_w + 2, src_h + 2, &dest_x, &dest_y)) { CopySqueezeDataForObject(obj, t, k, lm_data(Squeeze_lightmap_handle), dest_x, dest_y); } else { Int3(); // Get Jason, how did this happen???? } } } } } if (Squeeze_lightmap_handle != -1) { ASSERT(GameLightmaps[Squeeze_lightmap_handle].used != 1); GameLightmaps[Squeeze_lightmap_handle].used--; } } mem_free(Lightmap_mask); mem_free(Lmi_spoken_for); mprintf(0, "Done squeezing lightmaps.\n"); } void ComputeSurfaceRes(rad_surface *surf, room *rp, int facenum) { int i; float left = 1.1f, right = -1, top = 1.1f, bottom = -1; face *fp = &rp->faces[facenum]; int lw = lmi_w(fp->lmi_handle); int lh = lmi_h(fp->lmi_handle); for (i = 0; i < fp->num_verts; i++) { if (fp->face_uvls[i].u2 < left) left = fp->face_uvls[i].u2; if (fp->face_uvls[i].u2 > right) right = fp->face_uvls[i].u2; if (fp->face_uvls[i].v2 < top) top = fp->face_uvls[i].v2; if (fp->face_uvls[i].v2 > bottom) bottom = fp->face_uvls[i].v2; } float left_result = (left * lw) + .0001; float right_result = (right * lw) + .0001; float top_result = (top * lh) + .0001; float bottom_result = (bottom * lh) + .0001; surf->x1 = floor(left_result); surf->x2 = floor(right_result); surf->y1 = floor(top_result); surf->y2 = floor(bottom_result); surf->xresolution = (surf->x2 - surf->x1); surf->yresolution = (surf->y2 - surf->y1); // Adjust for a accuracy errors if (((right_result) - (float)surf->x2) > .005) surf->xresolution++; if (((bottom_result) - (float)surf->y2) > .005) surf->yresolution++; if (((top_result) - (float)surf->y1) > .99) surf->y1++; if (((left_result) - (float)surf->x1) > .99) surf->x1++; ASSERT((surf->x1 + surf->xresolution) <= lw); ASSERT((surf->y1 + surf->yresolution) <= lh); } // Take the computed volume spectra of a room and save it in the room struct void AssignVolumeSpectraToRoom(int roomnum) { ASSERT(Rooms[roomnum].used); ASSERT(!(Rooms[roomnum].flags & RF_EXTERNAL)); ASSERT(!(Rooms[roomnum].flags & RF_NO_LIGHT)); room *rp = &Rooms[roomnum]; int i, t, j; int w = rp->volume_width; int h = rp->volume_height; int d = rp->volume_depth; for (i = 0; i < d; i++) { for (t = 0; t < h; t++) { for (j = 0; j < w; j++) { spectra *this_spectra = &Volume_elements[roomnum][(i * w * h) + (t * w) + j].color; if (this_spectra->r < 0) { Int3(); // Shouldn't hit this rp->volume_lights[(i * w * h) + (t * w) + j] = INVISIBLE_VOLUME_ELEMENT; } else { float rmax = GetMaxColor(this_spectra); if (rmax > 1.0 && rmax > 0.0) { this_spectra->r /= rmax; this_spectra->g /= rmax; this_spectra->b /= rmax; } int r = (this_spectra->r * 7); r <<= 5; int g = (this_spectra->g * 7); g <<= 2; int b = (this_spectra->b * 3); uint8_t volume_color = r | g | b; if (!UseVolumeLights) rp->volume_lights[(i * w * h) + (t * w) + j] = 255; else rp->volume_lights[(i * w * h) + (t * w) + j] = volume_color; } } } } } // Returns 0 if there are bad faces in this level int CheckForBadFaces(int roomnum) { int i, t; for (i = 0; i <= Highest_room_index; i++) { if (roomnum != -1 && i != roomnum) continue; room *rp = &Rooms[i]; if (!rp->used) continue; for (t = 0; t < rp->num_faces; t++) { if (rp->faces[t].num_verts < 3) return 0; // Bad face detected! } } return 1; } // Calculates radiosity and sets lightmaps for indoor faces only void DoRadiosityForRooms() { int i, t, j; int facecount = 0; int surface_index = 0; int max_index; int save_after_bsp = 0; if (!CheckForBadFaces(-1)) { OutrageMessageBox("You have bad faces in your level. Please do a Verify Level."); return; } MakeBOAVisTable(1); if (UseBSP) { // if ((MessageBox(NULL,"Do you wish to save the level after BSP construction?","Question",MB_YESNO))==IDYES) // save_after_bsp=1; } BuildBSPTree(); if (save_after_bsp) { char filename[_MAX_PATH]; ddio_MakePath(filename, Base_directory, "BSPSave.D3L", NULL); // Save the level to SaveLevel(filename); } mprintf(0, "Setting up...\n"); Lightmaps_for_rad = 0; if (UseVolumeLights) Do_volume_lighting = 1; ClearAllVolumeLights(); ClearAllRoomLightmaps(0); ClearAllObjectLightmaps(0); ComputeAllRoomLightmapUVs(0); // Figure out memory for volume lights int vw, vh, vd; for (int roomnum = 0; roomnum < MAX_ROOMS; roomnum++) { Volume_elements[roomnum] = NULL; if (Rooms[roomnum].used && !(Rooms[roomnum].flags & RF_EXTERNAL) && !(Rooms[roomnum].flags & RF_NO_LIGHT)) { int num_bytes = GetVolumeSizeOfRoom(&Rooms[roomnum], &vw, &vh, &vd); Rooms[roomnum].volume_width = vw; Rooms[roomnum].volume_height = vh; Rooms[roomnum].volume_depth = vd; Rooms[roomnum].volume_lights = (uint8_t *)mem_malloc(vw * vh * vd); ASSERT(Rooms[roomnum].volume_lights); Volume_elements[roomnum] = (volume_element *)mem_malloc((vw * vh * vd) * sizeof(volume_element)); ASSERT(Volume_elements[roomnum]); // Now go through and find all the valid spectra points float cur_z = Rooms[roomnum].min_xyz.z + .1; for (i = 0; i < vd; i++, cur_z += VOLUME_SPACING) { float cur_y = Rooms[roomnum].min_xyz.y + .1; for (t = 0; t < vh; t++, cur_y += VOLUME_SPACING) { float cur_x = Rooms[roomnum].min_xyz.x + .1; for (j = 0; j < vw; j++, cur_x += VOLUME_SPACING) { vector dest_vec; dest_vec.x = cur_x; dest_vec.y = cur_y; dest_vec.z = cur_z; Volume_elements[roomnum][(i * vw * vh) + (t * vw) + j].pos = dest_vec; if (FindPointRoom(&dest_vec) != roomnum) { Volume_elements[roomnum][(i * vw * vh) + (t * vw) + j].color.r = 0; Volume_elements[roomnum][(i * vw * vh) + (t * vw) + j].flags = VEF_REVERSE_SHOOT; } else { Volume_elements[roomnum][(i * vw * vh) + (t * vw) + j].color.r = 0; Volume_elements[roomnum][(i * vw * vh) + (t * vw) + j].flags = 0; } } } } } } // First count how many faces we'll need for (i = 0; i < MAX_ROOMS; i++) { if (Rooms[i].used && !(Rooms[i].flags & RF_EXTERNAL) && !(Rooms[i].flags & RF_NO_LIGHT)) { facecount += Rooms[i].num_faces; } } // Setup specular lighting Calculate_specular_lighting = 1; SetupSpecularLighting(0); // Do objects facecount += GetTotalObjectFaces(0); // Do satellites facecount += Terrain_sky.num_satellites; // Allocate enough memory to hold all surfaces Light_surfaces = (rad_surface *)mem_malloc(facecount * sizeof(rad_surface)); ASSERT(Light_surfaces != NULL); // Set initial surface properties max_index = surface_index = 0; for (i = 0; i < MAX_ROOMS; i++) { if (Rooms[i].used) { if ((Rooms[i].flags & RF_EXTERNAL) || (Rooms[i].flags & RF_NO_LIGHT)) continue; for (t = 0; t < Rooms[i].num_faces; t++, surface_index++, max_index++) { ComputeSurfaceRes(&Light_surfaces[surface_index], &Rooms[i], t); if (Rooms[i].faces[t].num_verts) { Light_surfaces[surface_index].verts = (vector *)mem_malloc(Rooms[i].faces[t].num_verts * sizeof(vector)); ASSERT(Light_surfaces[surface_index].verts != NULL); } else { Light_surfaces[surface_index].verts = NULL; mprintf(0, "Room=%d Face %d has no verts!\n", i, t); } if (Light_surfaces[surface_index].xresolution * Light_surfaces[surface_index].yresolution) { Light_surfaces[surface_index].elements = (rad_element *)mem_malloc(Light_surfaces[surface_index].xresolution * Light_surfaces[surface_index].yresolution * sizeof(rad_element)); ASSERT(Light_surfaces[surface_index].elements != NULL); } else { Light_surfaces[surface_index].elements = NULL; mprintf(0, "Room=%d Face %d is slivered!\n", i, t); } Light_surfaces[surface_index].flags = 0; if (Rooms[i].faces[t].portal_num != -1 && (((Rooms[i].portals[Rooms[i].faces[t].portal_num].flags & PF_RENDER_FACES) == 0) || ((Rooms[i].portals[Rooms[i].faces[t].portal_num].flags & PF_RENDER_FACES) && (GameTextures[Rooms[i].faces[t].tmap].flags & TF_TMAP2)))) { Light_surfaces[surface_index].surface_type = ST_PORTAL; Light_surfaces[surface_index].emittance.r = 0; Light_surfaces[surface_index].emittance.g = 0; Light_surfaces[surface_index].emittance.b = 0; } else { if (Rooms[i].faces[t].light_multiple > 200) Rooms[i].faces[t].light_multiple = 4; float mul = ((float)Rooms[i].faces[t].light_multiple) / 4.0; mul *= GlobalMultiplier * Room_multiplier[i]; Light_surfaces[surface_index].emittance.r = (float)GameTextures[Rooms[i].faces[t].tmap].r * mul; Light_surfaces[surface_index].emittance.g = (float)GameTextures[Rooms[i].faces[t].tmap].g * mul; Light_surfaces[surface_index].emittance.b = (float)GameTextures[Rooms[i].faces[t].tmap].b * mul; Light_surfaces[surface_index].surface_type = ST_ROOM; if ((GetMaxColor(&Light_surfaces[surface_index].emittance)) > .005) Light_surfaces[surface_index].flags |= SF_LIGHTSOURCE; } Light_surfaces[surface_index].normal = LightmapInfo[Rooms[i].faces[t].lmi_handle].normal; Light_surfaces[surface_index].roomnum = i; Light_surfaces[surface_index].facenum = t; if (Rooms[i].flags & RF_TOUCHES_TERRAIN) Light_surfaces[surface_index].flags |= SF_TOUCHES_TERRAIN; for (int k = 0; k < Rooms[i].num_portals; k++) { if (Rooms[i].portals[k].croom == -1 || (Rooms[Rooms[i].portals[k].croom].flags & RF_EXTERNAL)) Light_surfaces[surface_index].flags |= SF_TOUCHES_TERRAIN; } Light_surfaces[surface_index].reflectivity = GameTextures[Rooms[i].faces[t].tmap].reflectivity; // Set the vertices for each element BuildElementListForRoomFace(i, t, &Light_surfaces[surface_index]); int xres = Light_surfaces[surface_index].xresolution; int yres = Light_surfaces[surface_index].yresolution; } } } // Setup satellites for (i = 0; i < Terrain_sky.num_satellites; i++, surface_index++) { Light_surfaces[surface_index].verts = (vector *)mem_malloc(sizeof(vector) * 3); ASSERT(Light_surfaces[surface_index].verts != NULL); Light_surfaces[surface_index].elements = (rad_element *)mem_malloc(sizeof(rad_element)); ASSERT(Light_surfaces[surface_index].elements != NULL); Light_surfaces[surface_index].elements[0].verts = (vector *)mem_malloc(sizeof(vector) * 3); ASSERT(Light_surfaces[surface_index].elements[0].verts); Light_surfaces[surface_index].surface_type = ST_SATELLITE; Light_surfaces[surface_index].emittance.r = Terrain_sky.satellite_r[i]; Light_surfaces[surface_index].emittance.g = Terrain_sky.satellite_g[i]; Light_surfaces[surface_index].emittance.b = Terrain_sky.satellite_b[i]; Light_surfaces[surface_index].roomnum = i; Light_surfaces[surface_index].facenum = 0; Light_surfaces[surface_index].reflectivity = 0; Light_surfaces[surface_index].xresolution = 1; Light_surfaces[surface_index].yresolution = 1; vm_MakeZero(&Light_surfaces[surface_index].normal); Light_surfaces[surface_index].verts[0] = Terrain_sky.satellite_vectors[i]; Light_surfaces[surface_index].verts[1] = Terrain_sky.satellite_vectors[i]; Light_surfaces[surface_index].verts[2] = Terrain_sky.satellite_vectors[i]; Light_surfaces[surface_index].num_verts = 3; Light_surfaces[surface_index].elements[0].verts[0] = Terrain_sky.satellite_vectors[i]; Light_surfaces[surface_index].elements[0].verts[1] = Terrain_sky.satellite_vectors[i]; Light_surfaces[surface_index].elements[0].verts[2] = Terrain_sky.satellite_vectors[i]; Light_surfaces[surface_index].elements[0].num_verts = 3; Light_surfaces[surface_index].elements[0].flags = 0; } // Setup Objects ComputeSurfacesForObjects(surface_index, 0); mprintf(0, "This radiosity run is using %d lightmaps.\n", Lightmaps_for_rad); mprintf(0, "Solving radiosity equation (press tilde key to stop)...\n"); if (D3EditState.hemicube_radiosity) DoRadiosityRun(SM_HEMICUBE, Light_surfaces, facecount); else DoRadiosityRun(SM_RAYCAST, Light_surfaces, facecount); mprintf(0, "Done solving radiosity - cleaning up...\n"); surface_index = 0; // Assign lightap properties for (i = 0; i < MAX_ROOMS; i++) { if (Rooms[i].used) { if ((Rooms[i].flags & RF_EXTERNAL) || (Rooms[i].flags & RF_NO_LIGHT)) continue; for (t = 0; t < Rooms[i].num_faces; t++, surface_index++) { AssignRoomSurfaceToLightmap(i, t, &Light_surfaces[surface_index]); } AssignVolumeSpectraToRoom(i); mem_free(Volume_elements[i]); } } surface_index += Terrain_sky.num_satellites; AssignLightmapsToObjectSurfaces(surface_index, 0); BlurLightmapInfos(LMI_ROOM); BlurLightmapInfos(LMI_ROOM_OBJECT); ShadeLightmapInfoEdges(LMI_ROOM); ShadeLightmapInfoEdges(LMI_ROOM_OBJECT); // Free our memory for (i = 0; i < facecount; i++) { if (Light_surfaces[i].verts) mem_free(Light_surfaces[i].verts); for (t = 0; t < Light_surfaces[i].xresolution * Light_surfaces[i].yresolution; t++) if (Light_surfaces[i].elements[t].num_verts > 0) mem_free(Light_surfaces[i].elements[t].verts); if (Light_surfaces[i].elements) mem_free(Light_surfaces[i].elements); Light_surfaces[i].elements = NULL; } mem_free(Light_surfaces); Light_surfaces = NULL; Do_volume_lighting = 0; // Free specular lighting stuff CleanupSpecularLighting(0); Calculate_specular_lighting = 0; // Figure out combined portals CheckCombinePortals(0); // Finally, squeeze the lightmaps SqueezeLightmaps(0, -1); char filename[_MAX_PATH + 1]; ddio_MakePath(filename, Base_directory, "LightSave.D3L", NULL); // Save the level to disk SaveLevel(filename); OutrageMessageBox("Mine radiosity complete!"); } // Calculates radiosity and sets lightmaps for indoor faces only void DoRadiosityForCurrentRoom(room *rp) { int t; int facecount = 0; int surface_index = 0; int max_index; if (!CheckForBadFaces(rp - Rooms)) { OutrageMessageBox("You have bad faces in your level. Please do a Verify Level."); return; } mprintf(0, "Setting up...\n"); ASSERT(rp != NULL); ASSERT(rp->used); if (rp->flags & RF_EXTERNAL) { OutrageMessageBox("You cannot run single room radiosity on external rooms!"); return; } if (rp->flags & RF_NO_LIGHT) { OutrageMessageBox("You cannot run single room radiosity on a room marked not to light!"); return; } // Build bsp tree BuildSingleBSPTree(rp - Rooms); ClearRoomLightmaps(rp - Rooms); for (t = 0; t <= Highest_object_index; t++) { if (Objects[t].type != OBJ_NONE && (Objects[t].roomnum == rp - Rooms)) ClearObjectLightmaps(&Objects[t]); } // Build lightmap uvs for (t = 0; t < rp->num_faces; t++) { vector verts[MAX_VERTS_PER_FACE * 5]; int room_list[2], face_list[2]; for (int k = 0; k < rp->faces[t].num_verts; k++) verts[k] = rp->verts[rp->faces[t].face_verts[k]]; room_list[0] = rp - Rooms; face_list[0] = t; BuildLightmapUVs(room_list, face_list, 1, verts, rp->faces[t].num_verts, 0); } // First count how many faces we'll need facecount += rp->num_faces; // Do objects facecount += GetTotalObjectFacesForSingleRoom(rp - Rooms); // Allocate enough memory to hold all surfaces Light_surfaces = (rad_surface *)mem_malloc(facecount * sizeof(rad_surface)); ASSERT(Light_surfaces != NULL); // Set initial surface properties max_index = surface_index = 0; for (t = 0; t < rp->num_faces; t++, surface_index++, max_index++) { ComputeSurfaceRes(&Light_surfaces[surface_index], rp, t); if (rp->faces[t].num_verts) { Light_surfaces[surface_index].verts = (vector *)mem_malloc(rp->faces[t].num_verts * sizeof(vector)); ASSERT(Light_surfaces[surface_index].verts != NULL); } else { Light_surfaces[surface_index].verts = NULL; mprintf(0, "Room=%d Face %d has no verts!\n", rp - Rooms, t); } if (Light_surfaces[surface_index].xresolution * Light_surfaces[surface_index].yresolution) { Light_surfaces[surface_index].elements = (rad_element *)mem_malloc( Light_surfaces[surface_index].xresolution * Light_surfaces[surface_index].yresolution * sizeof(rad_element)); ASSERT(Light_surfaces[surface_index].elements != NULL); } else { Light_surfaces[surface_index].elements = NULL; mprintf(0, "Room=%d Face %d is slivered!\n", rp - Rooms, t); } if (rp->faces[t].portal_num != -1 && (((rp->portals[rp->faces[t].portal_num].flags & PF_RENDER_FACES) == 0) || ((rp->portals[rp->faces[t].portal_num].flags & PF_RENDER_FACES) && (GameTextures[rp->faces[t].tmap].flags & TF_TMAP2)))) { Light_surfaces[surface_index].surface_type = ST_PORTAL; Light_surfaces[surface_index].emittance.r = 0; Light_surfaces[surface_index].emittance.g = 0; Light_surfaces[surface_index].emittance.b = 0; } else { float mul = ((float)rp->faces[t].light_multiple) / 4.0; mul *= GlobalMultiplier * Room_multiplier[rp - Rooms]; Light_surfaces[surface_index].emittance.r = (float)GameTextures[rp->faces[t].tmap].r * mul; Light_surfaces[surface_index].emittance.g = (float)GameTextures[rp->faces[t].tmap].g * mul; Light_surfaces[surface_index].emittance.b = (float)GameTextures[rp->faces[t].tmap].b * mul; Light_surfaces[surface_index].surface_type = ST_ROOM; } Light_surfaces[surface_index].normal = rp->faces[t].normal; Light_surfaces[surface_index].roomnum = ROOMNUM(rp); Light_surfaces[surface_index].facenum = t; Light_surfaces[surface_index].reflectivity = GameTextures[rp->faces[t].tmap].reflectivity; // Set the vertices for each element BuildElementListForRoomFace(rp - Rooms, t, &Light_surfaces[surface_index]); int xres = Light_surfaces[surface_index].xresolution; int yres = Light_surfaces[surface_index].yresolution; } // Setup Objects ComputeSurfacesForObjectsForSingleRoom(surface_index, rp - Rooms); mprintf(0, "Solving radiosity equation (press tilde key to stop)...\n"); if (D3EditState.hemicube_radiosity) DoRadiosityRun(SM_HEMICUBE, Light_surfaces, facecount); else DoRadiosityRun(SM_RAYCAST, Light_surfaces, facecount); mprintf(0, "Done solving radiosity - cleaning up...\n"); surface_index = 0; // Assign lightap properties for (t = 0; t < rp->num_faces; t++, surface_index++) { AssignRoomSurfaceToLightmap(rp - Rooms, t, &Light_surfaces[surface_index]); } AssignLightmapsToObjectSurfacesForSingleRoom(surface_index, rp - Rooms); // BlurLightmapInfos (LMI_ROOM); // BlurLightmapInfos (LMI_ROOM_OBJECT); // ShadeLightmapInfoEdges (LMI_ROOM); // ShadeLightmapInfoEdges (LMI_ROOM_OBJECT); // Free our memory for (int i = 0; i < facecount; i++) { mem_free(Light_surfaces[i].verts); for (t = 0; t < Light_surfaces[i].xresolution * Light_surfaces[i].yresolution; t++) if (Light_surfaces[i].elements[t].num_verts > 0) mem_free(Light_surfaces[i].elements[t].verts); mem_free(Light_surfaces[i].elements); Light_surfaces[i].elements = NULL; } mem_free(Light_surfaces); Light_surfaces = NULL; // Finally, squeeze the lightmaps SqueezeLightmaps(0, rp - Rooms); OutrageMessageBox("Room radiosity complete!"); } // Allocates and sets a lightmap based on the surface elements given void AssignRoomSurfaceToLightmap(int roomnum, int facenum, rad_surface *sp) { face *fp = &Rooms[roomnum].faces[facenum]; room *rp = &Rooms[roomnum]; int i, t, lmi_handle; int xres, yres; int lw, lh; int x1 = sp->x1; int y1 = sp->y1; int use_lightmap = 1; xres = sp->xresolution; yres = sp->yresolution; ASSERT(fp->lmi_handle != BAD_LMI_INDEX); lmi_handle = fp->lmi_handle; lw = lmi_w(lmi_handle); lh = lmi_h(lmi_handle); ASSERT((xres + x1) <= lw); ASSERT((yres + y1) <= lh); ASSERT(lw >= 2); ASSERT(lh >= 2); uint16_t *dest_data = lm_data(LightmapInfo[lmi_handle].lm_handle); // Set face pointer if (GameTextures[fp->tmap].flags & TF_ALPHA) use_lightmap = 0; if (GameTextures[fp->tmap].flags & (TF_FORCE_LIGHTMAP | TF_SATURATE_LIGHTMAP)) use_lightmap = 1; if (use_lightmap) fp->flags |= FF_LIGHTMAP; for (i = 0; i < yres; i++) { for (t = 0; t < xres; t++) { if (!(sp->elements[i * xres + t].flags & EF_IGNORE)) { ddgr_color color = GR_16_TO_COLOR(dest_data[(i + y1) * lw + (t + x1)]); int red = GR_COLOR_RED(color); int green = GR_COLOR_GREEN(color); int blue = GR_COLOR_BLUE(color); float fr, fg, fb; if (!(dest_data[(i + y1) * lw + (t + x1)] & OPAQUE_FLAG)) { red = green = blue = 0; } fr = std::min(1.0f, sp->elements[i * xres + t].exitance.r + Ambient_red + Room_ambience_r[roomnum]); fg = std::min(1.0f, sp->elements[i * xres + t].exitance.g + Ambient_green + Room_ambience_g[roomnum]); fb = std::min(1.0f, sp->elements[i * xres + t].exitance.b + Ambient_blue + Room_ambience_b[roomnum]); fr = (fr * 255) + .5; fg = (fg * 255) + .5; fb = (fb * 255) + .5; red += (int)fr; green += (int)fg; blue += (int)fb; if (dest_data[(i + y1) * lw + (t + x1)] & OPAQUE_FLAG) { red /= 2; green /= 2; blue /= 2; } red = std::min(red, 255); green = std::min(green, 255); blue = std::min(blue, 255); uint16_t texel = OPAQUE_FLAG | GR_RGB16(red, green, blue); dest_data[(i + y1) * lw + (t + x1)] = texel; } } } } // Important - vertnum is the index into the face_verts[] array in the face structure, // not an index into the verts[] array of the room structure void BuildElementListForRoomFace(int roomnum, int facenum, rad_surface *surf) { matrix face_matrix, trans_matrix; vector fvec; vector avg_vert; vector verts[MAX_VERTS_PER_FACE * 5]; vector rot_vert; vector vert; int i, t; int xres, yres; int lmi_handle; int x1 = surf->x1; int y1 = surf->y1; xres = surf->xresolution; yres = surf->yresolution; ASSERT(Rooms[roomnum].used); ASSERT(Rooms[roomnum].faces[facenum].num_verts >= 3); ASSERT(Rooms[roomnum].faces[facenum].lmi_handle != BAD_LMI_INDEX); lmi_handle = Rooms[roomnum].faces[facenum].lmi_handle; avg_vert = ScratchCenters[lmi_handle]; // Make the orientation matrix // Reverse the normal because we're looking "at" the face, not from it fvec = -LightmapInfo[lmi_handle].normal; vm_VectorToMatrix(&face_matrix, &fvec, NULL, NULL); // Make the transformation matrix angvec avec; vm_ExtractAnglesFromMatrix(&avec, &face_matrix); vm_AnglesToMatrix(&trans_matrix, avec.p, avec.h, avec.b); // Rotate all the points for (i = 0; i < Rooms[roomnum].faces[facenum].num_verts; i++) { vert = Rooms[roomnum].verts[Rooms[roomnum].faces[facenum].face_verts[i]]; vert -= avg_vert; vm_MatrixMulVector(&rot_vert, &vert, &trans_matrix); verts[i] = rot_vert; } // Find a base vector vector base_vector; vector xdiff, ydiff; vm_MakeZero(&xdiff); vm_MakeZero(&ydiff); // Rotate our upper left point into our 2d space vert = LightmapInfo[lmi_handle].upper_left - avg_vert; vm_MatrixMulVector(&base_vector, &vert, &trans_matrix); xdiff.x = LightmapInfo[lmi_handle].xspacing; ydiff.y = LightmapInfo[lmi_handle].yspacing; vm_TransposeMatrix(&trans_matrix); for (i = 0; i < yres; i++) { for (t = 0; t < xres; t++) { int element_index = i * xres + t; vector clip_verts[4]; rad_element *ep = &surf->elements[element_index]; // Allocate some free space clip_verts[0] = base_vector + (xdiff * (t + x1)) - (ydiff * (i + y1)); clip_verts[1] = base_vector + (xdiff * (t + x1 + 1)) - (ydiff * (i + y1)); clip_verts[2] = base_vector + (xdiff * (t + x1 + 1)) - (ydiff * (i + y1 + 1)); clip_verts[3] = base_vector + (xdiff * (t + x1)) - (ydiff * (i + y1 + 1)); ClipSurfaceElement(verts, ep, clip_verts, Rooms[roomnum].faces[facenum].num_verts); for (int k = 0; k < ep->num_verts; k++) { vm_MatrixMulVector(&rot_vert, &ep->verts[k], &trans_matrix); ep->verts[k] = rot_vert + avg_vert; } } } if (Square_surfaces) { surf->verts[0] = base_vector; surf->verts[1] = base_vector + (xdiff * xres); surf->verts[2] = base_vector + (xdiff * xres) - (ydiff * yres); surf->verts[3] = base_vector - (ydiff * yres); for (int k = 0; k < 4; k++) { vm_MatrixMulVector(&rot_vert, &surf->verts[k], &trans_matrix); surf->verts[k] = rot_vert + avg_vert; } surf->num_verts = 4; } else { surf->num_verts = Rooms[roomnum].faces[facenum].num_verts; for (int k = 0; k < surf->num_verts; k++) { surf->verts[k] = Rooms[roomnum].verts[Rooms[roomnum].faces[facenum].face_verts[k]]; } } } vector *rad_ClipTop, *rad_ClipBottom, *rad_ClipLeft, *rad_ClipRight; rad_point GlobalTempRadPoint; void SetRadClipLines(vector *tp, vector *rp, vector *bp, vector *lp) { rad_ClipTop = tp; rad_ClipRight = rp; rad_ClipBottom = bp; rad_ClipLeft = lp; } uint8_t CodeRadPoint(rad_point *rp) { uint8_t code = 0; if (rp->pos.x < rad_ClipLeft->x) code |= CC_OFF_LEFT; if (rp->pos.x > rad_ClipRight->x) code |= CC_OFF_RIGHT; if (rp->pos.y < rad_ClipBottom->y) code |= CC_OFF_BOT; if (rp->pos.y > rad_ClipTop->y) code |= CC_OFF_TOP; rp->code = code; return code; } void ClipSurfaceElement(vector *surf_verts, rad_element *ep, vector *clip_verts, int nv) { rad_point src_verts[50]; rad_point dest_verts[50]; rad_point *slist, *dlist; uint8_t and = 0xff; uint8_t or = 0; int i; ASSERT(nv < 50); ep->flags = 0; SetRadClipLines(&clip_verts[0], &clip_verts[1], &clip_verts[2], &clip_verts[3]); for (i = 0; i < nv; i++) { src_verts[i].pos = surf_verts[i]; src_verts[i].code = CodeRadPoint(&src_verts[i]); } for (i = 0; i < nv; i++) { and &= src_verts[i].code; or |= src_verts[i].code; } if (and) { // This element is not even in the face, ignore it ep->flags |= EF_IGNORE; ep->num_verts = 0; return; } int pnv = nv, nnv; slist = src_verts; dlist = dest_verts; nnv = ClipRadPointList(&slist, &dlist, &pnv, or); ep->num_verts = nnv; if (ep->num_verts == 0) ep->flags |= EF_IGNORE; else { ep->verts = (vector *)mem_malloc(sizeof(vector) * nnv); ASSERT(ep->verts); for (i = 0; i < nnv; i++) { ep->verts[i] = dlist[i].pos; } } } // Takes a points and clips all its viewpoints so they fit into the view frustum // Puts the new clipped list in dest // Returns number of points in clipped polygon int ClipRadPointList(rad_point **src, rad_point **dest, int *nv, int code) { int plane, num = 0; rad_point **save_src = src, *t; rad_point **save_dest = dest; for (plane = 1; plane < 16; plane <<= 1) { if (plane & code) // Is this point off this plane? { *nv = ClipRadToPlane(plane, *src, *dest, *nv); if (*nv == 0) return 0; ASSERT(*nv >= 3); t = *dest; *dest = *src; *src = t; num++; } } t = *dest; *dest = *src; *src = t; return (*nv); } // Clips to a specific plane, putting resulting points in dest and returning number of points in dest int ClipRadToPlane(int plane, rad_point *src, rad_point *dest, int nv) { int i, num = 0, limit = nv - 1, ppoint, npoint; ASSERT(nv >= 3); for (i = 0; i < nv; i++) { if (i == limit) npoint = 0; else npoint = i + 1; if (i == 0) ppoint = limit; else ppoint = i - 1; // mprintf(0,"checking point %d ",i); if (src[i].code & plane) // off this plane? { if (!(src[ppoint].code & plane)) // prev point on? { // mprintf(0,"pVertex %d off %d plane.\n",i,plane); ClipRadEdge(plane, &src[ppoint], &src[i]); memcpy(&dest[num], &GlobalTempRadPoint, sizeof(rad_point)); num++; } if (!(src[npoint].code & plane)) // next point on? { // mprintf(0,"nVertex %d off %d plane.\n",i,plane); ClipRadEdge(plane, &src[npoint], &src[i]); memcpy(&dest[num], &GlobalTempRadPoint, sizeof(rad_point)); num++; } } else // This point is on { // mprintf(0,"is on\n"); memcpy(&dest[num], &src[i], sizeof(rad_point)); num++; } } return (num); } // Takes two points and a plane, and clips. void ClipRadEdge(int plane_flag, rad_point *on_pnt, rad_point *off_pnt) { if (plane_flag & CC_OFF_TOP) { float percent_on = 1.0 - ((off_pnt->pos.y - rad_ClipTop->y) / (off_pnt->pos.y - on_pnt->pos.y)); GlobalTempRadPoint.pos = on_pnt->pos + ((off_pnt->pos - on_pnt->pos) * percent_on); } if (plane_flag & CC_OFF_RIGHT) { float percent_on = 1.0 - ((off_pnt->pos.x - rad_ClipRight->x) / (off_pnt->pos.x - on_pnt->pos.x)); GlobalTempRadPoint.pos = on_pnt->pos + ((off_pnt->pos - on_pnt->pos) * percent_on); } if (plane_flag & CC_OFF_LEFT) { float percent_on = 1.0 - ((off_pnt->pos.x - rad_ClipLeft->x) / (off_pnt->pos.x - on_pnt->pos.x)); GlobalTempRadPoint.pos = on_pnt->pos + ((off_pnt->pos - on_pnt->pos) * percent_on); } if (plane_flag & CC_OFF_BOT) { float percent_on = 1.0 - ((off_pnt->pos.y - rad_ClipBottom->y) / (off_pnt->pos.y - on_pnt->pos.y)); GlobalTempRadPoint.pos = on_pnt->pos + ((off_pnt->pos - on_pnt->pos) * percent_on); } CodeRadPoint(&GlobalTempRadPoint); } // Adds color spectrums together void AddSpectra(spectra *dest, spectra *a, spectra *b) { dest->r = a->r + b->r; dest->g = a->g + b->g; dest->b = a->b + b->b; } // Returns 1 if a src vector can hit dest vector unobstructed, else 0 int ShootRayForTerrainLight(vector *src, vector *dest, int cellnum) { fvi_info hit_info; fvi_query fq; vector temp_dest = *dest; if (temp_dest.y >= MAX_TERRAIN_HEIGHT * 3) { float mag; float ydiff; vector ray = temp_dest - *src; mag = vm_GetMagnitude(&ray); ray /= mag; ydiff = ((MAX_TERRAIN_HEIGHT * 3) - src->y) / ray.y; temp_dest = *src + (ray * ydiff); } // shoot a ray from the light position to the current vertex fq.p0 = src; fq.p1 = &temp_dest; fq.startroom = MAKE_ROOMNUM(cellnum); fq.rad = 0.0f; fq.flags = FQ_CHECK_OBJS | FQ_IGNORE_NON_LIGHTMAP_OBJECTS | FQ_OBJ_BACKFACE | FQ_NO_RELINK | FQ_IGNORE_RENDER_THROUGH_PORTALS; fq.thisobjnum = -1; fq.ignore_obj_list = NULL; int fate = fvi_FindIntersection(&fq, &hit_info); if (fate == HIT_OUT_OF_TERRAIN_BOUNDS || fate == HIT_NONE) return 1; return 0; } #define AREA_X (TERRAIN_WIDTH - 1) #define AREA_Z (TERRAIN_DEPTH - 1) // #define AREA_X 128 // #define AREA_Z 128 // Puts a 3d grid of points on the terrain and sets a bit indicating if a light source // can reach that point. This is used for the dynamic lighting of objects on the terrain void DoTerrainDynamicTable() { int i, t, k, j, maxrays; int raynum = 0; int key; maxrays = AREA_X * AREA_Z * 8 * Terrain_sky.num_satellites; mprintf(0, "Calculating dynamic light table for %d points...\n", maxrays); mprintf(0, "Press tilde key to abort!\n"); memset(Terrain_dynamic_table, 0, (TERRAIN_DEPTH * TERRAIN_WIDTH)); for (i = 0; i < AREA_Z; i++) { // ddio_KeyFrame(); while ((key = ddio_KeyInKey()) != 0) { if (key == KEY_LAPOSTRO) { for (; i < AREA_Z; i++) { for (t = 0; t < AREA_X; t++) { int tseg = i * TERRAIN_WIDTH + t; Terrain_dynamic_table[tseg] = 255; } } return; } } for (t = 0; t < AREA_X; t++) { int tseg = i * TERRAIN_WIDTH + t; vector gp; gp.x = (t * TERRAIN_SIZE); gp.z = (i * TERRAIN_SIZE); gp.y = Terrain_seg[tseg].y; for (k = 0; k < 8; k++) { vector pos; pos.x = gp.x; pos.z = gp.z; pos.y = k * (MAX_TERRAIN_HEIGHT / 8); for (j = 0; j < Terrain_sky.num_satellites; j++) { raynum++; mprintf_at(2, 4, 0, "Ray=%d ", raynum); if (gp.y > pos.y) continue; int answer; answer = ShootRayForTerrainLight(&pos, &Terrain_sky.satellite_vectors[j], tseg); if (!answer) continue; Terrain_dynamic_table[tseg] |= (1 << k); j = Terrain_sky.num_satellites; } } } } } void ComputeTerrainSpeedTable() { int i, t, j, raynum = 0; mprintf(0, "Precomputing terrain speed table...(%d rays)\n", AREA_X * AREA_Z); for (i = 0; i < AREA_Z; i++) { for (t = 0; t < AREA_X; t++) { int tseg = i * TERRAIN_WIDTH + t; vector pos; pos.x = (t * TERRAIN_SIZE); pos.z = (i * TERRAIN_SIZE); pos.y = (Terrain_seg[tseg].y) + .001; raynum++; if ((raynum % 1000) == 0) mprintf_at(2, 4, 0, "Ray=%d ", raynum); for (j = 0; j < Terrain_sky.num_satellites; j++) TerrainLightSpeedup[j][tseg] = ShootRayForTerrainLight(&pos, &Terrain_sky.satellite_vectors[j], tseg); } } } float GetMaxColor(spectra *sp); // Calculates radiosity for the outdoor surfaces void DoRadiosityForTerrain() { int i, t, raynum = 0; int extra_surfaces = 0, total_surfaces = 0; int surf_index = 0; spectra *terrain_sums[2]; int room_surf_start, obj_surf_start; int do_dynamic = 0; int lit = 0; // First make sure there is even a light source for (i = 0; i < Terrain_sky.num_satellites; i++) { int tmap = Terrain_sky.satellite_texture[i]; if (Terrain_sky.satellite_r[i] > 0 || Terrain_sky.satellite_g[i] > 0 || Terrain_sky.satellite_b[i] > 0) lit = 1; } if (!lit) { OutrageMessageBox("No moons/suns cast light so there is no point in running terrain radiosity!"); return; } for (i = 0; i < Terrain_sky.num_satellites; i++) { TerrainLightSpeedup[i] = (uint8_t *)mem_malloc(TERRAIN_WIDTH * TERRAIN_DEPTH); ASSERT(TerrainLightSpeedup[i]); } if ((MessageBox(NULL, "Do you wish to calculate dynamic terrain lighting when radiosity is completed? (Note: Dynamic " "lighting takes a long time)", "Question", MB_YESNO)) == IDYES) do_dynamic = 1; if (!Ignore_terrain) ComputeTerrainSpeedTable(); extra_surfaces += Terrain_sky.num_satellites; extra_surfaces += GetTotalObjectFaces(1); // Get our external rooms ready ClearAllRoomLightmaps(1); ComputeAllRoomLightmapUVs(1); // count how many faces we'll need for (i = 0; i < MAX_ROOMS; i++) { if (Rooms[i].used && (Rooms[i].flags & RF_EXTERNAL)) extra_surfaces += Rooms[i].num_faces; } if (!Ignore_terrain) total_surfaces = ((AREA_X) * (AREA_Z) * 2); total_surfaces += extra_surfaces; // Get our object lightmaps ready ClearAllObjectLightmaps(1); // Allocate memory terrain_sums[0] = (spectra *)mem_malloc(TERRAIN_WIDTH * TERRAIN_DEPTH * sizeof(spectra)); terrain_sums[1] = (spectra *)mem_malloc(TERRAIN_WIDTH * TERRAIN_DEPTH * sizeof(spectra)); ASSERT(terrain_sums[0] && terrain_sums[1]); Light_surfaces = (rad_surface *)mem_malloc(total_surfaces * sizeof(rad_surface)); ASSERT(Light_surfaces != NULL); // Setup radiosity surfaces if (!Ignore_terrain) { for (i = 0; i < (AREA_X) * (AREA_Z); i++, surf_index += 2) { vector a, b, c; int x, z; x = i % AREA_X; z = i / AREA_Z; int seg = z * TERRAIN_WIDTH + x; // Do common stuff (for both triangles) for (x = 0; x < 2; x++) { Light_surfaces[i * 2 + x].surface_type = ST_TERRAIN; Light_surfaces[i * 2 + x].emittance.r = (float)GameTextures[Terrain_tex_seg[Terrain_seg[seg].texseg_index].tex_index].r; Light_surfaces[i * 2 + x].emittance.g = (float)GameTextures[Terrain_tex_seg[Terrain_seg[seg].texseg_index].tex_index].g; Light_surfaces[i * 2 + x].emittance.b = (float)GameTextures[Terrain_tex_seg[Terrain_seg[seg].texseg_index].tex_index].b; Light_surfaces[i * 2 + x].roomnum = MAKE_ROOMNUM(seg); Light_surfaces[i * 2 + x].facenum = x; Light_surfaces[i * 2 + x].reflectivity = GameTextures[Terrain_tex_seg[Terrain_seg[seg].texseg_index].tex_index].reflectivity; Light_surfaces[i * 2 + x].xresolution = 1; Light_surfaces[i * 2 + x].yresolution = 1; } // Do upper left triangle Light_surfaces[i * 2].elements = (rad_element *)mem_malloc(sizeof(rad_element)); ASSERT(Light_surfaces[i * 2].elements != NULL); Light_surfaces[i * 2].elements[0].verts = (vector *)mem_malloc(sizeof(vector) * 3); ASSERT(Light_surfaces[i * 2].elements[0].verts); Light_surfaces[i * 2].verts = (vector *)mem_malloc(sizeof(vector) * 3); ASSERT(Light_surfaces[i * 2].verts != NULL); Light_surfaces[i * 2].normal = TerrainNormals[MAX_TERRAIN_LOD - 1][seg].normal1; z = i / (AREA_X); x = i % (AREA_X); a.x = x * TERRAIN_SIZE; a.y = Terrain_seg[(z * TERRAIN_WIDTH) + (x)].y; a.z = z * TERRAIN_SIZE; b.x = x * TERRAIN_SIZE; b.y = Terrain_seg[((z + 1) * TERRAIN_WIDTH) + (x)].y; b.z = (z + 1) * TERRAIN_SIZE; c.x = (x + 1) * TERRAIN_SIZE; c.y = Terrain_seg[((z + 1) * TERRAIN_WIDTH) + (x + 1)].y; c.z = (z + 1) * TERRAIN_SIZE; Light_surfaces[i * 2].verts[0] = a; Light_surfaces[i * 2].verts[1] = b; Light_surfaces[i * 2].verts[2] = c; Light_surfaces[i * 2].num_verts = 3; Light_surfaces[i * 2].elements[0].verts[0] = a; Light_surfaces[i * 2].elements[0].verts[1] = b; Light_surfaces[i * 2].elements[0].verts[2] = c; Light_surfaces[i * 2].elements[0].num_verts = 3; Light_surfaces[i * 2].elements[0].flags = 0; // Now do lower right Light_surfaces[i * 2 + 1].elements = (rad_element *)mem_malloc(sizeof(rad_element)); ASSERT(Light_surfaces[i * 2 + 1].elements != NULL); Light_surfaces[i * 2 + 1].elements[0].verts = (vector *)mem_malloc(sizeof(vector) * 3); ASSERT(Light_surfaces[i * 2 + 1].elements[0].verts); Light_surfaces[i * 2 + 1].verts = (vector *)mem_malloc(sizeof(vector) * 3); ASSERT(Light_surfaces[i * 2 + 1].verts != NULL); Light_surfaces[i * 2 + 1].normal = TerrainNormals[MAX_TERRAIN_LOD - 1][seg].normal2; z = i / (AREA_X); x = i % (AREA_X); a.x = x * TERRAIN_SIZE; a.y = Terrain_seg[(z * TERRAIN_WIDTH) + (x)].y; a.z = z * TERRAIN_SIZE; b.x = (x + 1) * TERRAIN_SIZE; b.y = Terrain_seg[((z + 1) * TERRAIN_WIDTH) + (x + 1)].y; b.z = (z + 1) * TERRAIN_SIZE; c.x = (x + 1) * TERRAIN_SIZE; c.y = Terrain_seg[(z * TERRAIN_WIDTH) + (x + 1)].y; c.z = z * TERRAIN_SIZE; Light_surfaces[i * 2 + 1].verts[0] = a; Light_surfaces[i * 2 + 1].verts[1] = b; Light_surfaces[i * 2 + 1].verts[2] = c; Light_surfaces[i * 2 + 1].num_verts = 3; Light_surfaces[i * 2 + 1].elements[0].verts[0] = a; Light_surfaces[i * 2 + 1].elements[0].verts[1] = b; Light_surfaces[i * 2 + 1].elements[0].verts[2] = c; Light_surfaces[i * 2 + 1].elements[0].num_verts = 3; Light_surfaces[i * 2 + 1].elements[0].flags = 0; } } // Setup satellites for (i = 0; i < Terrain_sky.num_satellites; i++, surf_index++) { Light_surfaces[surf_index].verts = (vector *)mem_malloc(sizeof(vector) * 3); ASSERT(Light_surfaces[surf_index].verts != NULL); Light_surfaces[surf_index].elements = (rad_element *)mem_malloc(sizeof(rad_element)); ASSERT(Light_surfaces[surf_index].elements != NULL); Light_surfaces[surf_index].elements[0].verts = (vector *)mem_malloc(sizeof(vector) * 3); ASSERT(Light_surfaces[surf_index].elements[0].verts); Light_surfaces[surf_index].surface_type = ST_SATELLITE; Light_surfaces[surf_index].emittance.r = Terrain_sky.satellite_r[i]; Light_surfaces[surf_index].emittance.g = Terrain_sky.satellite_g[i]; Light_surfaces[surf_index].emittance.b = Terrain_sky.satellite_b[i]; Light_surfaces[surf_index].roomnum = i; Light_surfaces[surf_index].facenum = 0; Light_surfaces[surf_index].reflectivity = 0; Light_surfaces[surf_index].xresolution = 1; Light_surfaces[surf_index].yresolution = 1; vm_MakeZero(&Light_surfaces[surf_index].normal); Light_surfaces[surf_index].verts[0] = Terrain_sky.satellite_vectors[i]; Light_surfaces[surf_index].verts[1] = Terrain_sky.satellite_vectors[i]; Light_surfaces[surf_index].verts[2] = Terrain_sky.satellite_vectors[i]; Light_surfaces[surf_index].num_verts = 3; Light_surfaces[surf_index].elements[0].verts[0] = Terrain_sky.satellite_vectors[i]; Light_surfaces[surf_index].elements[0].verts[1] = Terrain_sky.satellite_vectors[i]; Light_surfaces[surf_index].elements[0].verts[2] = Terrain_sky.satellite_vectors[i]; Light_surfaces[surf_index].elements[0].num_verts = 3; Light_surfaces[surf_index].elements[0].flags = 0; } // Setup external rooms room_surf_start = surf_index; for (i = 0; i < MAX_ROOMS; i++) { if (Rooms[i].used) { if (!(Rooms[i].flags & RF_EXTERNAL)) continue; for (t = 0; t < Rooms[i].num_faces; t++, surf_index++) { ComputeSurfaceRes(&Light_surfaces[surf_index], &Rooms[i], t); Light_surfaces[surf_index].verts = (vector *)mem_malloc(Rooms[i].faces[t].num_verts * sizeof(vector)); ASSERT(Light_surfaces[surf_index].verts != NULL); Light_surfaces[surf_index].elements = (rad_element *)mem_malloc( Light_surfaces[surf_index].xresolution * Light_surfaces[surf_index].yresolution * sizeof(rad_element)); ASSERT(Light_surfaces[surf_index].elements != NULL); if (Rooms[i].faces[t].portal_num != -1 && !(Rooms[i].portals[Rooms[i].faces[t].portal_num].flags & PF_RENDER_FACES)) { Light_surfaces[surf_index].surface_type = ST_PORTAL; Light_surfaces[surf_index].emittance.r = 0; Light_surfaces[surf_index].emittance.g = 0; Light_surfaces[surf_index].emittance.b = 0; } else { float mul = ((float)Rooms[i].faces[t].light_multiple) / 4.0; mul *= GlobalMultiplier * Room_multiplier[i]; Light_surfaces[surf_index].emittance.r = (float)GameTextures[Rooms[i].faces[t].tmap].r * mul; Light_surfaces[surf_index].emittance.g = (float)GameTextures[Rooms[i].faces[t].tmap].g * mul; Light_surfaces[surf_index].emittance.b = (float)GameTextures[Rooms[i].faces[t].tmap].b * mul; Light_surfaces[surf_index].surface_type = ST_EXTERNAL_ROOM; // if ((GetMaxColor (&Light_surfaces[surf_index].emittance))>.005) // Light_surfaces[surf_index].flags|=SF_LIGHTSOURCE; } Light_surfaces[surf_index].normal = LightmapInfo[Rooms[i].faces[t].lmi_handle].normal; Light_surfaces[surf_index].roomnum = i; Light_surfaces[surf_index].facenum = t; Light_surfaces[surf_index].reflectivity = GameTextures[Rooms[i].faces[t].tmap].reflectivity; // Set the vertices for each element BuildElementListForRoomFace(i, t, &Light_surfaces[surf_index]); int xres = Light_surfaces[surf_index].xresolution; int yres = Light_surfaces[surf_index].yresolution; } } } // Setup objects obj_surf_start = surf_index; ComputeSurfacesForObjects(surf_index, 1); mprintf(0, "Solving radiosity equation (press tilde key to stop)...\n"); if (D3EditState.hemicube_radiosity) DoRadiosityRun(SM_SWITCH_AFTER_SATELLITES, Light_surfaces, total_surfaces); else DoRadiosityRun(SM_RAYCAST, Light_surfaces, total_surfaces); // Figure out lighting by averaging the two triangles per terrain cell if (!Ignore_terrain) { for (i = 0; i < AREA_X * AREA_Z; i++) { // Add in ambient terrain light terrain_sums[0][i].r = std::min(1.0f, Light_surfaces[i * 2].elements[0].exitance.r + Ambient_red); terrain_sums[0][i].g = std::min(1.0f, Light_surfaces[i * 2].elements[0].exitance.g + Ambient_green); terrain_sums[0][i].b = std::min(1.0f, Light_surfaces[i * 2].elements[0].exitance.b + Ambient_blue); terrain_sums[1][i].r = std::min(1.0f, Light_surfaces[i * 2 + 1].elements[0].exitance.r + Ambient_red); terrain_sums[1][i].g = std::min(1.0f, Light_surfaces[i * 2 + 1].elements[0].exitance.g + Ambient_green); terrain_sums[1][i].b = std::min(1.0f, Light_surfaces[i * 2 + 1].elements[0].exitance.b + Ambient_blue); } for (i = 0; i < (AREA_X) * (AREA_Z); i++) { int x, z; spectra total; int num_items = 0; int seg; memset(&total, 0, sizeof(spectra)); x = i % (AREA_X); z = i / (AREA_Z); seg = z * TERRAIN_WIDTH + x; if (x != 0) { AddSpectra(&total, &total, &terrain_sums[1][z * (AREA_X) + (x - 1)]); num_items++; } AddSpectra(&total, &total, &terrain_sums[0][z * (AREA_X) + x]); AddSpectra(&total, &total, &terrain_sums[1][z * (AREA_X) + x]); num_items += 2; if (x != 0) { if (z != 0) { AddSpectra(&total, &total, &terrain_sums[0][(z - 1) * (AREA_X) + (x - 1)]); AddSpectra(&total, &total, &terrain_sums[1][(z - 1) * (AREA_X) + (x - 1)]); num_items += 2; } } if (z != 0) { AddSpectra(&total, &total, &terrain_sums[0][(z - 1) * (AREA_X) + x]); num_items++; } total.r /= num_items; total.g /= num_items; total.b /= num_items; if (total.r > 1.0) total.r = 1.0; if (total.g > 1.0) total.g = 1.0; if (total.b > 1.0) total.b = 1.0; Terrain_seg[seg].l = Float_to_ubyte(GetMaxColor(&total)); Terrain_seg[seg].r = Float_to_ubyte(total.r); Terrain_seg[seg].g = Float_to_ubyte(total.g); Terrain_seg[seg].b = Float_to_ubyte(total.b); } } // Assign lightmaps to external rooms for (i = 0; i < MAX_ROOMS; i++) { if (Rooms[i].used) { if (!(Rooms[i].flags & RF_EXTERNAL)) continue; for (t = 0; t < Rooms[i].num_faces; t++, room_surf_start++) { AssignRoomSurfaceToLightmap(i, t, &Light_surfaces[room_surf_start]); } } } // Now assign lightmaps to objects AssignLightmapsToObjectSurfaces(obj_surf_start, 1); // Shade room/object lightmaps BlurLightmapInfos(LMI_EXTERNAL_ROOM); BlurLightmapInfos(LMI_TERRAIN_OBJECT); ShadeLightmapInfoEdges(LMI_EXTERNAL_ROOM); ShadeLightmapInfoEdges(LMI_TERRAIN_OBJECT); // Free memory for (i = 0; i < total_surfaces; i++) { mem_free(Light_surfaces[i].verts); for (int t = 0; t < Light_surfaces[i].xresolution * Light_surfaces[i].yresolution; t++) if (Light_surfaces[i].elements[t].num_verts > 0) mem_free(Light_surfaces[i].elements[t].verts); mem_free(Light_surfaces[i].elements); } mem_free(Light_surfaces); mem_free(terrain_sums[0]); mem_free(terrain_sums[1]); for (i = 0; i < Terrain_sky.num_satellites; i++) mem_free(TerrainLightSpeedup[i]); UpdateTerrainLightmaps(); SqueezeLightmaps(1, -1); // Figure out combined portals CheckCombinePortals(1); if (do_dynamic) DoTerrainDynamicTable(); OutrageMessageBox( "Terrain radiosity complete!\nIt is recommended you save the level so you won't have to rerun this operation."); } /* void DoRadiosityForTerrain () { int i,t,j,maxrays; int raynum=0; spectra *exitance; int lit=0; int do_dynamic=0; if ((MessageBox(NULL,"Do you wish to calculate dynamic terrain lighting when radiosity is completed? (Note: Dynamic lighting takes a long time)","Question",MB_YESNO))==IDYES) do_dynamic=1; // First make sure there is even a light source for (i=0;i0 || GameTextures[tmap].g || GameTextures[tmap].b) lit=1; } if (!lit) { OutrageMessageBox ("No moons/suns cast light so there is no point in running terrain radiosity!"); return; } maxrays=(TERRAIN_DEPTH-1)*(TERRAIN_WIDTH-1)*Terrain_sky.num_satellites; mprintf(0,"Calculating terrain radiosity for %d points! (be patient!)\n",maxrays); // Malloc some memory exitance=(spectra *)mem_malloc (TERRAIN_DEPTH*TERRAIN_WIDTH*sizeof(spectra)); ASSERT (exitance!=NULL); for (i=0;i topmost_y) { topmost_point = i; topmost_y = verts[i].y; } } ASSERT(topmost_point != -1); // Find right most point int rightmost_point = -1; float rightmost_x = -900000.00f; // a big number for (i = 0; i < nv; i++) { if (verts[i].x > rightmost_x) { rightmost_point = i; rightmost_x = verts[i].x; } } ASSERT(rightmost_point != -1); // Find bottom most point int bottommost_point = -1; float bottommost_y = 900000.0f; // a big number for (i = 0; i < nv; i++) { if (verts[i].y < bottommost_y) { bottommost_point = i; bottommost_y = verts[i].y; } } ASSERT(bottommost_point != -1); // now set the base vertex, which is where we base uv 0,0 on vector base_vector; base_vector.x = verts[leftmost_point].x; base_vector.y = verts[topmost_point].y; base_vector.z = 0; // Figure out lightmap resolution float xdiff = verts[rightmost_point].x - verts[leftmost_point].x; float ydiff = verts[topmost_point].y - verts[bottommost_point].y; float max_diff = (float)std::max(xdiff, ydiff); int lightmap_x_res = -1, lightmap_y_res = -1; float xspacing = LightSpacing; float yspacing = LightSpacing; float spacing = LightSpacing; int res, done_spacing = 0; int xspace_int, yspace_int; // If the default spacing would make us go over our lightmap resolution // limit, then increase the spacing and try again while (!done_spacing) { res = (xdiff / xspacing); if (((xdiff / xspacing) - res) > 0) res++; res++; if (res > 126) xspacing += 1; else done_spacing = 1; } // Set a mininum, at least if (res < 2) res = 2; lightmap_x_res = res; done_spacing = 0; while (!done_spacing) { res = (ydiff / yspacing); if (((ydiff / yspacing) - res) > 0) res++; res++; if (res > 126) yspacing += 1; else done_spacing = 1; } // Set a mininum, at least if (res < 2) res = 2; lightmap_y_res = res; /*// Find power of 2 number for (i=0;i<=7;i++) { int low_num=1low_num) { lightmap_res=hi_num; break; } }*/ if (external) lmi_handle = AllocLightmapInfo(lightmap_x_res, lightmap_y_res, LMI_EXTERNAL_ROOM); else lmi_handle = AllocLightmapInfo(lightmap_x_res, lightmap_y_res, LMI_ROOM); ASSERT(lmi_handle != BAD_LMI_INDEX); ASSERT(lmi_handle >= 0 && lmi_handle <= MAX_LIGHTMAP_INFOS); Lightmaps_for_rad++; // Now do best fit spacing if (BestFit) { xspace_int = (xdiff / lightmap_x_res); if ((xdiff - (lightmap_x_res * xspace_int)) > 0) xspace_int++; yspace_int = (ydiff / lightmap_y_res); if ((ydiff - (lightmap_y_res * yspace_int)) > 0) yspace_int++; } else { xspace_int = xspacing; yspace_int = yspacing; } // Figure out lightmap uvs // Rotate all the face points for (i = 0; i < count; i++) { Rooms[room_list[i]].faces[face_list[i]].lmi_handle = lmi_handle; for (t = 0; t < Rooms[room_list[i]].faces[face_list[i]].num_verts; t++) { room *rp = &Rooms[room_list[i]]; face *fp = &rp->faces[face_list[i]]; vector vert = rp->verts[fp->face_verts[t]]; vert -= avg_vert; vm_MatrixMulVector(&rot_vert, &vert, &trans_matrix); facevert = rot_vert; // Find uv2s for this vertex fp->face_uvls[t].u2 = (facevert.x - verts[leftmost_point].x) / (float)(lightmap_x_res * xspace_int); fp->face_uvls[t].v2 = fabs((verts[topmost_point].y - facevert.y)) / (float)(lightmap_y_res * yspace_int); if (fp->face_uvls[t].u2 < 0) fp->face_uvls[t].u2 = 0; if (fp->face_uvls[t].v2 < 0) fp->face_uvls[t].v2 = 0; ASSERT(fp->face_uvls[t].u2 >= 0 && fp->face_uvls[t].u2 <= 1.0); ASSERT(fp->face_uvls[t].v2 >= 0 && fp->face_uvls[t].v2 <= 1.0); } } // Find upper left corner vm_TransposeMatrix(&trans_matrix); vm_MatrixMulVector(&rot_vert, &base_vector, &trans_matrix); LightmapInfo[lmi_handle].upper_left = rot_vert + avg_vert; LightmapInfo[lmi_handle].xspacing = xspace_int; LightmapInfo[lmi_handle].yspacing = yspace_int; LightmapInfo[lmi_handle].normal = Rooms[room_list[0]].faces[face_list[0]].normal; ScratchCenters[lmi_handle] = avg_vert; } #define MAX_COMBINES 50 #define LM_ADJACENT_FACE_THRESHOLD .999 uint8_t *RoomsAlreadyCombined[MAX_ROOMS]; /* // Returns number of verts in dest if face a can be safely combined with face b // Returns 0 if not int CombineLightFaces( vector *dest_verts,vector *averts, int nva, vector *norma,vector *bverts, int nvb,vector *normb,int aroom,int broom) { int starta, startb, i; vector va; float dp; dp=vm_DotProduct (normb,norma); if (dp < LM_ADJACENT_FACE_THRESHOLD) return 0; ASSERT (nva > 2 ); ASSERT (nvb > 2 ); // Go through each vertex and get a match for (starta=0; starta 2 ); return dnv; } } } Now check for tjoint faces for (starta=0; starta 2); ASSERT(nvb > 2); // Go through each vertex and get a match // If these two faces are in the same room and are on the same plane // then we should combine them by default if (1 || (aroom != -1 && aroom == broom)) { int dnv = 0; int match = 0; // Don't go over our point limit if ((nva + nvb) >= (MAX_VERTS_PER_FACE * 5)) return 0; // Test to see if any points at all can touch for (i = 0; i < nva && !match; i++) { for (int t = 0; t < nvb && !match; t++) { if (PointsAreSame(&averts[i], &bverts[t])) match = 1; } } if (!match) return 0; // At least one point is the same, so combine them! for (i = 0; i < nva; i++) { dest_verts[dnv] = averts[i]; dnv++; ASSERT(dnv < MAX_VERTS_PER_FACE * 5); } for (i = 0; i < nvb; i++) { dest_verts[dnv] = bverts[i]; dnv++; ASSERT(dnv < MAX_VERTS_PER_FACE * 5); } return dnv; } return 0; } // Given a roomnumber and a face, goes through the entire mine and checks to see // if this face can share a lightmap with any other face int TestLightAdjacency(int roomnum, int facenum, int external) { int i, t, k; room *arp = &Rooms[roomnum]; face *afp = &arp->faces[facenum]; vector averts[MAX_VERTS_PER_FACE * 5]; vector bverts[MAX_VERTS_PER_FACE * 5]; vector dest_verts[MAX_VERTS_PER_FACE * 5]; int face_combine_list[MAX_COMBINES]; int room_combine_list[MAX_COMBINES]; ASSERT(roomnum >= 0 && roomnum < MAX_ROOMS); if (!AllowCombining) return 0; // if (GameTextures[afp->tmap].r>0 || GameTextures[afp->tmap].g>0 || GameTextures[afp->tmap].b>0) // return 0; // Don't combine portals! if (afp->portal_num != -1) return 0; // Setup our 'base' face int anv = afp->num_verts; int total_faces = 1; room_combine_list[0] = roomnum; face_combine_list[0] = facenum; for (i = 0; i < afp->num_verts; i++) averts[i] = arp->verts[afp->face_verts[i]]; StartOver: // Go through each room and find an adjacent face for (i = roomnum; i < MAX_ROOMS; i++) { if (!Rooms[i].used) continue; if (Rooms[i].flags & RF_NO_LIGHT) continue; if (external && !(Rooms[i].flags & RF_EXTERNAL)) continue; if (!external && (Rooms[i].flags & RF_EXTERNAL)) continue; /*if (i!=roomnum) // Only combine faces in the same room continue;*/ for (t = 0; t < Rooms[i].num_faces; t++) { if (total_faces >= MAX_COMBINES - 1) continue; if (&Rooms[i] == arp && t == facenum) continue; // don't do self // Don't do if already spoken fore if (RoomsAlreadyCombined[i][t]) continue; room *brp = &Rooms[i]; face *bfp = &brp->faces[t]; // Don't do combine light sources // if (GameTextures[bfp->tmap].r>0 || GameTextures[bfp->tmap].g>0 || GameTextures[bfp->tmap].b>0) // continue; // Don't combine portals! if (bfp->portal_num != -1) continue; for (k = 0; k < bfp->num_verts; k++) bverts[k] = brp->verts[bfp->face_verts[k]]; int nv = CombineLightFaces(dest_verts, averts, anv, &afp->normal, bverts, bfp->num_verts, &bfp->normal, roomnum, i); // We have a combine! Mark this face in the appropriate list // And update our new polygon if (nv > 0) { room_combine_list[total_faces] = i; face_combine_list[total_faces] = t; total_faces++; RoomsAlreadyCombined[roomnum][facenum] = 1; RoomsAlreadyCombined[i][t] = 1; for (k = 0; k < nv; k++) averts[k] = dest_verts[k]; anv = nv; goto StartOver; } } } // Now build 1 lightmap to be shared across all the faces that were combined if (total_faces > 1) { BuildLightmapUVs(room_combine_list, face_combine_list, total_faces, averts, anv, external); } return 1; } // Computes the the mines UVs // Faces can now share one lightmap, so this routine goes through and tries to // combine as many faces as it can into one lightmap void ComputeAllRoomLightmapUVs(int external) { int i, t, k; int not_combined = 0; for (i = 0; i < MAX_ROOMS; i++) { if (Rooms[i].used) { if (Rooms[i].flags & RF_NO_LIGHT) continue; if (external && !(Rooms[i].flags & RF_EXTERNAL)) continue; if (!external && (Rooms[i].flags & RF_EXTERNAL)) continue; RoomsAlreadyCombined[i] = (uint8_t *)mem_malloc(Rooms[i].num_faces); ASSERT(RoomsAlreadyCombined[i]); for (k = 0; k < Rooms[i].num_faces; k++) RoomsAlreadyCombined[i][k] = 0; } } for (i = 0; i < MAX_ROOMS; i++) { if (Rooms[i].used) { if (Rooms[i].flags & RF_NO_LIGHT) continue; if (external && !(Rooms[i].flags & RF_EXTERNAL)) continue; if (!external && (Rooms[i].flags & RF_EXTERNAL)) continue; for (t = 0; t < Rooms[i].num_faces; t++) { if (*(RoomsAlreadyCombined[i] + t) == 0) TestLightAdjacency(i, t, external); } } } // Now build lightmaps for any faces that couldn't be combined for (i = 0; i < MAX_ROOMS; i++) { if (Rooms[i].used) { if (Rooms[i].flags & RF_NO_LIGHT) continue; if (external && !(Rooms[i].flags & RF_EXTERNAL)) continue; if (!external && (Rooms[i].flags & RF_EXTERNAL)) continue; for (t = 0; t < Rooms[i].num_faces; t++) { if (!RoomsAlreadyCombined[i][t]) { vector verts[MAX_VERTS_PER_FACE * 5]; int room_list[2], face_list[2]; for (k = 0; k < Rooms[i].faces[t].num_verts; k++) verts[k] = Rooms[i].verts[Rooms[i].faces[t].face_verts[k]]; room_list[0] = i; face_list[0] = t; BuildLightmapUVs(room_list, face_list, 1, verts, Rooms[i].faces[t].num_verts, external); not_combined++; } } } } mprintf(0, "%d faces couldn't be combined!\n", not_combined); // Free memory for (i = 0; i < MAX_ROOMS; i++) { if (Rooms[i].used) { if (Rooms[i].flags & RF_NO_LIGHT) continue; if (external && !(Rooms[i].flags & RF_EXTERNAL)) continue; if (!external && (Rooms[i].flags & RF_EXTERNAL)) continue; mem_free(RoomsAlreadyCombined[i]); } } } // Returns number preceding val modulo modulus. // prevmod(3,4) = 2 // prevmod(0,4) = 3 int SpecularPrevIndex(int val, int modulus) { if (val > 0) return val - 1; else return modulus - 1; } // Returns number succeeding val modulo modulus. // succmod(3,4) = 0 // succmod(0,4) = 1 int SpecularNextIndex(int val, int modulus) { if (val < modulus - 1) return val + 1; else return 0; } // Gets the left top, left bottom, right top, and right bottom indices. Also finds // lowest y index. void GetSpecularVertexOrdering(spec_vertex *verts, int nv, int *vlt, int *vlb, int *vrt, int *vrb, int *bottom_y_ind) { int i; float min_y, max_y; int min_y_ind; int original_vrt; float min_x; // Scan all vertices, set min_y_ind to vertex with smallest y coordinate. min_y = verts[0].y; max_y = min_y; min_y_ind = 0; min_x = verts[0].x; *bottom_y_ind = 0; for (i = 1; i < nv; i++) { if (verts[i].y < min_y) { min_y = verts[i].y; min_y_ind = i; min_x = verts[i].x; } else if (verts[i].y == min_y) { if (verts[i].x < min_x) { min_y_ind = i; min_x = verts[i].x; } } if (verts[i].y > max_y) { max_y = verts[i].y; *bottom_y_ind = i; } } // Set "vertex left top", etc. based on vertex with topmost y coordinate *vlt = min_y_ind; *vrt = *vlt; *vlb = SpecularPrevIndex(*vlt, nv); *vrb = SpecularNextIndex(*vrt, nv); // If right edge is horizontal, then advance along polygon bound until it no longer is or until all // vertices have been examined. // (Left edge cannot be horizontal, because *vlt is set to leftmost point with highest y coordinate.) original_vrt = *vrt; while (verts[*vrt].y == verts[*vrb].y) { if (SpecularNextIndex(*vrt, nv) == original_vrt) break; *vrt = SpecularNextIndex(*vrt, nv); *vrb = SpecularNextIndex(*vrt, nv); } } /* // Creates a map that contains all the interpolated normals for a particular face // Used in specular mapping void CreateNormalMapForFace (room *rp,face *fp) { int w=lmi_w(fp->lmi_handle); int h=lmi_h(fp->lmi_handle); special_face *sfp=&SpecialFaces[fp->special_handle]; spec_vertex spec_verts[MAX_VERTS_PER_FACE]; vector vertnorms[MAX_VERTS_PER_FACE]; int vlt,vlb,vrt,vrb,max_y_vertex,top_y,bottom_y,height; int no_height=0,no_width=0,no_right=0,no_left=0; lightmap_info *lmi_ptr=&LightmapInfo[fp->lmi_handle]; uint16_t *src_data=lm_data(lmi_ptr->lm_handle); matrix facematrix; vector fvec=-lmi_ptr->normal; vm_VectorToMatrix(&facematrix,&fvec,NULL,NULL); sfp->normal_map=(uint8_t *)mem_malloc (w*h*3); ASSERT (sfp->normal_map); for (int i=0;i<(w*h);i++) { sfp->normal_map[i*3+0]=0; sfp->normal_map[i*3+1]=0; sfp->normal_map[i*3+2]=128; } for (i=0;inum_verts;i++) { vm_MatrixMulVector (&vertnorms[i],&sfp->vertnorms[i],&facematrix); spec_verts[i].x=fp->face_uvls[i].u2; spec_verts[i].y=fp->face_uvls[i].v2; } GetSpecularVertexOrdering(spec_verts,fp->num_verts,&vlt,&vlb,&vrt,&vrb,&max_y_vertex); top_y=spec_verts[vlt].y*h; bottom_y=spec_verts[max_y_vertex].y*h; height = bottom_y-top_y; if(height == 0) no_height=1; // Setup left interpolant int left_height=((spec_verts[vlb].y-spec_verts[vlt].y)*(float)h); float left_x=spec_verts[vlt].x*w; float delta_left_x=((spec_verts[vlb].x-spec_verts[vlt].x)*w)/left_height; vector left_vnorm=vertnorms[vlt]; vector delta_left_vnorm=(vertnorms[vlb]-vertnorms[vlt])/left_height; int next_break_left = spec_verts[vlb].y*h; // Setup right interpolant int right_height=((spec_verts[vrb].y-spec_verts[vrt].y)*(float)h); float right_x=spec_verts[vrt].x*w; float delta_right_x=((spec_verts[vrb].x-spec_verts[vrt].x)*w)/right_height; vector right_vnorm=vertnorms[vrt]; vector delta_right_vnorm=(vertnorms[vrb]-vertnorms[vrt])/right_height; int next_break_right = spec_verts[vrb].y*h; // Do the loop for (int y=top_y;y<=bottom_y;y++) { if (y==next_break_left && !no_height) { while (y == (int)(spec_verts[vlb].y*h)) { vlt = vlb; vlb = SpecularPrevIndex(vlb,fp->num_verts); } // Setup left interpolant left_height=((spec_verts[vlb].y-spec_verts[vlt].y)*(float)h); left_x=spec_verts[vlt].x*w; delta_left_x=((spec_verts[vlb].x-spec_verts[vlt].x)*w)/left_height; left_vnorm=vertnorms[vlt]; delta_left_vnorm=(vertnorms[vlb]-vertnorms[vlt])/left_height; next_break_left = spec_verts[vlb].y*h; } if (y==next_break_right && !no_height) { while (y == (int)(spec_verts[vrb].y*h)) { vrt = vrb; vrb = SpecularNextIndex(vrb,fp->num_verts); } right_height=((spec_verts[vrb].y-spec_verts[vrt].y)*(float)h); right_x=spec_verts[vrt].x*w; delta_right_x=((spec_verts[vrb].x-spec_verts[vrt].x)*w)/right_height; right_vnorm=vertnorms[vrt]; delta_right_vnorm=(vertnorms[vrb]-vertnorms[vrt])/right_height; next_break_right = spec_verts[vrb].y*h; } int width=(right_x-left_x)+1; vector delta_vnorm=(right_vnorm-left_vnorm)/width; vector vnorm=left_vnorm; for (i=left_x;i<(left_x+width);i++) { int offset=y*w+i; if (!(src_data[offset] & OPAQUE_FLAG)) continue; sfp->normal_map[(offset*3)+0]=((vnorm.x+1.0)/2.0)*255.0; sfp->normal_map[(offset*3)+1]=((vnorm.y+1.0)/2.0)*255.0; sfp->normal_map[(offset*3)+2]=((vnorm.z+1.0)/2.0)*255.0; vnorm+=delta_vnorm; } // Update our interpolants left_x += delta_left_x; right_x += delta_right_x; right_vnorm+=delta_right_vnorm; left_vnorm+=delta_left_vnorm; } }*/ // Frees memory for specular lighting void CleanupSpecularLighting(int external) { int i, t; for (i = 0; i < MAX_ROOMS; i++) { if (Rooms[i].used) { if (Rooms[i].flags & RF_NO_LIGHT) continue; if ((Rooms[i].flags & RF_EXTERNAL) && !external) continue; if (!(Rooms[i].flags & RF_EXTERNAL) && external) continue; room *rp = &Rooms[i]; for (t = 0; t < 4; t++) { mem_free(Room_strongest_value[i][t]); } for (t = 0; t < rp->num_faces; t++) { face *fp = &rp->faces[t]; if (GameTextures[fp->tmap].flags & TF_SPECULAR) { ASSERT(fp->special_handle != BAD_SPECIAL_FACE_INDEX); int j, k, l; if (SpecialFaces[fp->special_handle].spec_instance[0].bright_color == 0) { // This face didn't get enough light, either free it up or // find another face that shares a lightmap and just use that // faces values int found = 0; float strongest_light = 0; face *best_face; for (l = 0; l < fp->num_verts; l++) { int vert_to_check = fp->face_verts[l]; for (j = 0; j < rp->num_faces; j++) { face *this_fp = &rp->faces[j]; if (this_fp == fp) continue; // don't do self if (this_fp->lmi_handle != fp->lmi_handle) continue; // only do faces that have same lightmaps if (this_fp->special_handle == BAD_SPECIAL_FACE_INDEX) continue; // Only do specmaps if (SpecialFaces[this_fp->special_handle].spec_instance[0].bright_color == 0) continue; // Don't do dark ones if (GameTextures[fp->tmap].flags & TF_SMOOTH_SPECULAR) continue; // Don't do smooth shades for (k = 0; k < this_fp->num_verts; k++) { if (vert_to_check == this_fp->face_verts[k]) { // We have a match, so see if this face is brighter // than all the others int color = SpecialFaces[this_fp->special_handle].spec_instance[0].bright_color; int r = (color >> 10) & 0x1f; int g = (color >> 5) & 0x1f; int b = (color & 0x1f); float sr = (float)r / 31.0; float sg = (float)g / 31.0; float sb = (float)b / 31.0; float mono_color = (sr * .3) + (sg * .59) + (sb * .11); if (mono_color > strongest_light) { strongest_light = mono_color; found = 1; best_face = this_fp; } } } } } // If after all that searching we couldn't find a good light, free it! if (!found) { FreeSpecialFace(fp->special_handle); fp->special_handle = BAD_SPECIAL_FACE_INDEX; } else { for (int z = 0; z < 4; z++) SpecialFaces[fp->special_handle].spec_instance[z] = SpecialFaces[best_face->special_handle].spec_instance[z]; } } } } } } } // Sets up memory usage for specular lighting void SetupSpecularLighting(int external) { int i, t; ClearAllRoomSpecmaps(external); for (i = 0; i < MAX_ROOMS; i++) { if (Rooms[i].used) { if (Rooms[i].flags & RF_NO_LIGHT) continue; if ((Rooms[i].flags & RF_EXTERNAL) && !external) continue; if (!(Rooms[i].flags & RF_EXTERNAL) && external) continue; room *rp = &Rooms[i]; // Calculate vertex normals for this room vector *vertnorms = (vector *)mem_malloc(sizeof(vector) * rp->num_verts); ASSERT(vertnorms); for (t = 0; t < rp->num_verts; t++) { int total = 0; vector normal; vm_MakeZero(&normal); for (int k = 0; k < rp->num_faces; k++) { face *fp = &rp->faces[k]; for (int j = 0; j < fp->num_verts; j++) { if (fp->face_verts[j] == t) { total++; normal += fp->normal; } } } if (total != 0) normal /= total; vertnorms[t] = normal; } for (t = 0; t < 4; t++) { Room_strongest_value[i][t] = (float *)mem_malloc(sizeof(float) * rp->num_faces); ASSERT(Room_strongest_value[i][t]); memset(Room_strongest_value[i][t], 0, sizeof(float) * rp->num_faces); } for (t = 0; t < rp->num_faces; t++) { face *fp = &rp->faces[t]; if (GameTextures[fp->tmap].flags & TF_SPECULAR) { ASSERT(fp->special_handle == BAD_SPECIAL_FACE_INDEX); int n; if (GameTextures[fp->tmap].flags & TF_SMOOTH_SPECULAR) n = AllocSpecialFace(SFT_SPECULAR, 4, true, fp->num_verts); else n = AllocSpecialFace(SFT_SPECULAR, 4); fp->special_handle = n; for (int k = 0; k < 4; k++) { SpecialFaces[n].spec_instance[k].bright_color = 0; vm_MakeZero(&SpecialFaces[n].spec_instance[k].bright_center); } // Get vertex normals for this punk if (GameTextures[fp->tmap].flags & TF_SMOOTH_SPECULAR) { for (k = 0; k < fp->num_verts; k++) { SpecialFaces[n].vertnorms[k] = vertnorms[fp->face_verts[k]]; } } } } mem_free(vertnorms); } } } // Computes the the mines UVs // Faces can now share one lightmap, so this routine goes through and tries to // combine as many faces as it can into one lightmap // Only works on one room /*void ComputeSingleRoomLightmapUVs (int roomnum) { int t,k; int not_combined=0; RoomsAlreadyCombined[roomnum]=(uint8_t *)mem_malloc (Rooms[roomnum].num_faces); ASSERT (RoomsAlreadyCombined[roomnum]); for (k=0;k