Descent3/editor/editor_lighting.cpp

3414 lines
105 KiB
C++
Raw Permalink Normal View History

/*
2024-06-15 18:12:48 +00:00
* 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 <http://www.gnu.org/licenses/>.
--- 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 <algorithm>
#include <windows.h>
#include "3d.h"
#include "gametexture.h"
#include "erooms.h"
#include "editor_lighting.h"
#include "descent.h"
#include "room.h"
#include "lightmap.h"
#include "polymodel.h"
#include <string.h>
#include <stdlib.h>
#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 "loadlevel.h"
#include "special_face.h"
#include "boa.h"
#include "mem.h"
#include "mono.h"
2024-06-15 18:12:48 +00:00
struct spec_vertex {
float x, y;
};
2024-06-15 18:12:48 +00:00
int AllowCombining = 1;
float GlobalMultiplier = 1.0;
2024-06-15 18:12:48 +00:00
rad_surface *Light_surfaces = NULL;
vector ScratchCenters[MAX_LIGHTMAP_INFOS];
vector ScratchRVecs[MAX_LIGHTMAP_INFOS];
vector ScratchUVecs[MAX_LIGHTMAP_INFOS];
2024-06-15 18:12:48 +00:00
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];
2024-05-24 03:07:26 +00:00
uint8_t *TerrainLightSpeedup[MAX_SATELLITES];
2024-06-15 18:12:48 +00:00
int LightSpacing = LIGHTMAP_SPACING;
int BestFit = 0;
int Square_surfaces = 0;
int Lightmaps_for_rad = 0;
// Ambient values for terrain
2024-06-15 18:12:48 +00:00
float Ambient_red = 0.0f, Ambient_green = 0.0f, Ambient_blue = 0.0f;
2024-06-15 18:12:48 +00:00
void DoTerrainDynamicTable();
2024-06-15 18:12:48 +00:00
uint8_t *Lightmap_mask = NULL;
2024-05-24 03:07:26 +00:00
static uint8_t *Lmi_spoken_for;
2024-06-15 18:12:48 +00:00
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;
}
2024-06-15 18:12:48 +00:00
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;
}
2024-06-15 18:12:48 +00:00
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++;
}
2024-06-15 18:12:48 +00:00
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
2024-06-15 18:12:48 +00:00
void ClearCombinePortals(int terrain) {
for (int i = 0; i <= Highest_room_index; i++) {
room *rp = &Rooms[i];
2024-06-15 18:12:48 +00:00
if (!rp->used)
continue;
2024-06-15 18:12:48 +00:00
if (terrain && !(rp->flags & RF_EXTERNAL))
continue;
2024-06-15 18:12:48 +00:00
if (!terrain && (rp->flags & RF_EXTERNAL))
continue;
2024-06-15 18:12:48 +00:00
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
2024-06-15 18:12:48 +00:00
void CheckCombinePortals(int terrain) {
ClearCombinePortals(terrain);
2024-06-15 18:12:48 +00:00
int combine_count = 0;
mprintf(0, "Combining portals...");
2024-06-15 18:12:48 +00:00
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;
2024-06-15 18:12:48 +00:00
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; x<face_a->num_verts && !match; x++ )
{
for (int y=0; y<face_b->num_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);
}
2024-06-15 18:12:48 +00:00
// 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 = mem_rmalloc<uint8_t>(MAX_LIGHTMAP_INFOS);
2024-06-15 18:12:48 +00:00
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");
}
2024-06-15 18:12:48 +00:00
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
2024-06-15 18:12:48 +00:00
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
2024-06-15 18:12:48 +00:00
int CheckForBadFaces(int roomnum) {
int i, t;
2024-06-15 18:12:48 +00:00
for (i = 0; i <= Highest_room_index; i++) {
if (roomnum != -1 && i != roomnum)
continue;
2024-06-15 18:12:48 +00:00
room *rp = &Rooms[i];
if (!rp->used)
continue;
2024-06-15 18:12:48 +00:00
for (t = 0; t < rp->num_faces; t++) {
if (rp->faces[t].num_verts < 3)
return 0; // Bad face detected!
}
}
2024-06-15 18:12:48 +00:00
return 1;
}
// Calculates radiosity and sets lightmaps for indoor faces only
2024-06-15 18:12:48 +00:00
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];
Add -aditionaldir option Before this change, Descent 3 would look for all of its game data files in a single directory. This change allows users to spread out Descent 3’s game data over multiple directories. Building Descent 3 produces multiple files that can be freely redistributed (Descent3, d3-linux.hog, online/Direct TCP~IP.d3c, etc.). Running Descent 3 requires those files and several additional files that cannot be freely redistributed. Before this change, the files that were redistributable had to be in the same directory as the files that were not redistributable. This change makes it so that they can be in separate directories. The main motivation behind this change is to allow people to package Descent 3 for Linux in a reasonable manner. For the most part, binary packages for Descent 3 will contain all of the freely redistributable components. Package managers will copy those components into system directories that are owned by root and that users probably shouldn’t edit manually. Users will then create a new directory and copy the game data from their copy of Descent 3 into that new directory. Users will then be able to run: Descent3 -setdir <path-to-proprietary-files> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to support more complicated scenarios. For example, if the user is using Debian’s game-data-packager [1], then they would do something like this: Descent3 -setdir <path-to-writable-directory> -additionaldir <path-to-gdp-directory> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to load a mod that replaces .hog files: Descent3 -setdir <path-to-base-game-data> -additionaldir <path-to-mod-files> [1]: <https://github.com/DescentDevelopers/Descent3/issues/373#issuecomment-2120330650>
2024-05-24 19:26:43 +00:00
ddio_MakePath(filename, cf_GetWritableBaseDirectory().u8string().c_str(), "BSPSave.D3L", NULL);
2024-06-15 18:12:48 +00:00
// 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] = mem_rmalloc<volume_element>(vw * vh * vd);
2024-06-15 18:12:48 +00:00
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 = mem_rmalloc<rad_surface>(facecount);
2024-06-15 18:12:48 +00:00
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 = mem_rmalloc<vector>(Rooms[i].faces[t].num_verts);
2024-06-15 18:12:48 +00:00
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 =
mem_rmalloc<rad_element>(Light_surfaces[surface_index].xresolution *
Light_surfaces[surface_index].yresolution);
2024-06-15 18:12:48 +00:00
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 = mem_rmalloc<vector>(3);
2024-06-15 18:12:48 +00:00
ASSERT(Light_surfaces[surface_index].verts != NULL);
Light_surfaces[surface_index].elements = mem_rmalloc<rad_element>();
2024-06-15 18:12:48 +00:00
ASSERT(Light_surfaces[surface_index].elements != NULL);
Light_surfaces[surface_index].elements[0].verts = mem_rmalloc<vector>(3);
2024-06-15 18:12:48 +00:00
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];
Add -aditionaldir option Before this change, Descent 3 would look for all of its game data files in a single directory. This change allows users to spread out Descent 3’s game data over multiple directories. Building Descent 3 produces multiple files that can be freely redistributed (Descent3, d3-linux.hog, online/Direct TCP~IP.d3c, etc.). Running Descent 3 requires those files and several additional files that cannot be freely redistributed. Before this change, the files that were redistributable had to be in the same directory as the files that were not redistributable. This change makes it so that they can be in separate directories. The main motivation behind this change is to allow people to package Descent 3 for Linux in a reasonable manner. For the most part, binary packages for Descent 3 will contain all of the freely redistributable components. Package managers will copy those components into system directories that are owned by root and that users probably shouldn’t edit manually. Users will then create a new directory and copy the game data from their copy of Descent 3 into that new directory. Users will then be able to run: Descent3 -setdir <path-to-proprietary-files> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to support more complicated scenarios. For example, if the user is using Debian’s game-data-packager [1], then they would do something like this: Descent3 -setdir <path-to-writable-directory> -additionaldir <path-to-gdp-directory> -additionaldir <path-to-open-source-files> The -additionaldir option can also be used to load a mod that replaces .hog files: Descent3 -setdir <path-to-base-game-data> -additionaldir <path-to-mod-files> [1]: <https://github.com/DescentDevelopers/Descent3/issues/373#issuecomment-2120330650>
2024-05-24 19:26:43 +00:00
ddio_MakePath(filename, cf_GetWritableBaseDirectory().u8string().c_str(), "LightSave.D3L", NULL);
2024-06-15 18:12:48 +00:00
// Save the level to disk
SaveLevel(filename);
OutrageMessageBox("Mine radiosity complete!");
}
// Calculates radiosity and sets lightmaps for indoor faces only
2024-06-15 18:12:48 +00:00
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 = mem_rmalloc<rad_surface>(facecount);
2024-06-15 18:12:48 +00:00
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 = mem_rmalloc<vector>(rp->faces[t].num_verts);
2024-06-15 18:12:48 +00:00
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 = mem_rmalloc<rad_element>(
Light_surfaces[surface_index].xresolution * Light_surfaces[surface_index].yresolution);
2024-06-15 18:12:48 +00:00
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!");
}
2024-06-15 18:12:48 +00:00
// 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
2024-06-15 18:12:48 +00:00
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]];
}
}
}
2024-06-15 18:12:48 +00:00
vector *rad_ClipTop, *rad_ClipBottom, *rad_ClipLeft, *rad_ClipRight;
rad_point GlobalTempRadPoint;
2024-06-15 18:12:48 +00:00
void SetRadClipLines(vector *tp, vector *rp, vector *bp, vector *lp) {
rad_ClipTop = tp;
rad_ClipRight = rp;
rad_ClipBottom = bp;
rad_ClipLeft = lp;
}
2024-06-15 18:12:48 +00:00
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;
}
2024-06-15 18:12:48 +00:00
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]);
2024-06-15 18:12:48 +00:00
for (i = 0; i < nv; i++) {
src_verts[i].pos = surf_verts[i];
src_verts[i].code = CodeRadPoint(&src_verts[i]);
}
2024-06-15 18:12:48 +00:00
for (i = 0; i < nv; i++) {
and &= src_verts[i].code;
or |= src_verts[i].code;
}
2024-06-15 18:12:48 +00:00
if (and) {
// This element is not even in the face, ignore it
ep->flags |= EF_IGNORE;
ep->num_verts = 0;
return;
}
2024-06-15 18:12:48 +00:00
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 = mem_rmalloc<vector>(nnv);
2024-06-15 18:12:48 +00:00
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
2024-06-15 18:12:48 +00:00
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
2024-06-15 18:12:48 +00:00
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);
}
2024-06-15 18:12:48 +00:00
// 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
2024-06-15 18:12:48 +00:00
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
2024-06-15 18:12:48 +00:00
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);
2024-06-15 18:12:48 +00:00
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;
2024-06-15 18:12:48 +00:00
int fate = fvi_FindIntersection(&fq, &hit_info);
2024-06-15 18:12:48 +00:00
if (fate == HIT_OUT_OF_TERRAIN_BOUNDS || fate == HIT_NONE)
return 1;
2024-06-15 18:12:48 +00:00
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
2024-06-15 18:12:48 +00:00
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++;
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;
}
}
}
}
}
2024-06-15 18:12:48 +00:00
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++;
for (j = 0; j < Terrain_sky.num_satellites; j++)
TerrainLightSpeedup[j][tseg] = ShootRayForTerrainLight(&pos, &Terrain_sky.satellite_vectors[j], tseg);
}
}
}
2024-06-15 18:12:48 +00:00
float GetMaxColor(spectra *sp);
// Calculates radiosity for the outdoor surfaces
2024-06-15 18:12:48 +00:00
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] = mem_rmalloc<spectra>(TERRAIN_WIDTH * TERRAIN_DEPTH);
terrain_sums[1] = mem_rmalloc<spectra>(TERRAIN_WIDTH * TERRAIN_DEPTH);
2024-06-15 18:12:48 +00:00
ASSERT(terrain_sums[0] && terrain_sums[1]);
Light_surfaces = mem_rmalloc<rad_surface>(total_surfaces);
2024-06-15 18:12:48 +00:00
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 = mem_rmalloc<rad_element>();
2024-06-15 18:12:48 +00:00
ASSERT(Light_surfaces[i * 2].elements != NULL);
Light_surfaces[i * 2].elements[0].verts = mem_rmalloc<vector>(3);
2024-06-15 18:12:48 +00:00
ASSERT(Light_surfaces[i * 2].elements[0].verts);
Light_surfaces[i * 2].verts = mem_rmalloc<vector>(3);
2024-06-15 18:12:48 +00:00
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 = mem_rmalloc<rad_element>();
2024-06-15 18:12:48 +00:00
ASSERT(Light_surfaces[i * 2 + 1].elements != NULL);
Light_surfaces[i * 2 + 1].elements[0].verts = mem_rmalloc<vector>(3);
2024-06-15 18:12:48 +00:00
ASSERT(Light_surfaces[i * 2 + 1].elements[0].verts);
Light_surfaces[i * 2 + 1].verts = mem_rmalloc<vector>(3);
2024-06-15 18:12:48 +00:00
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 = mem_rmalloc<vector>(3);
2024-06-15 18:12:48 +00:00
ASSERT(Light_surfaces[surf_index].verts != NULL);
Light_surfaces[surf_index].elements = mem_rmalloc<rad_element>();
2024-06-15 18:12:48 +00:00
ASSERT(Light_surfaces[surf_index].elements != NULL);
Light_surfaces[surf_index].elements[0].verts = mem_rmalloc<vector>(3);
2024-06-15 18:12:48 +00:00
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 = mem_rmalloc<vector>(Rooms[i].faces[t].num_verts);
2024-06-15 18:12:48 +00:00
ASSERT(Light_surfaces[surf_index].verts != NULL);
Light_surfaces[surf_index].elements = mem_rmalloc<rad_element>(
Light_surfaces[surf_index].xresolution * Light_surfaces[surf_index].yresolution);
2024-06-15 18:12:48 +00:00
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 ()
{
2024-06-15 18:12:48 +00:00
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;i<Terrain_sky.num_satellites;i++)
{
int tmap=Terrain_sky.satellite_texture[i];
if (GameTextures[tmap].r>0 || 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<TERRAIN_DEPTH*TERRAIN_WIDTH;i++)
{
exitance[i].r=0;
exitance[i].g=0;
exitance[i].b=0;
}
// Now shoot a ray from every triangle on the terrain to every moon/sun in the
// sky, tallying the results
for (i=0;i<TERRAIN_DEPTH-1;i++)
{
for (t=0;t<TERRAIN_WIDTH-1;t++)
{
int tseg=i*TERRAIN_WIDTH+t;
vector pos1,pos2;
pos1.x=(t*TERRAIN_SIZE)+(TERRAIN_SIZE/3);
pos1.z=(i*TERRAIN_SIZE)+(TERRAIN_SIZE * .66);
pos1.y=GetTerrainGroundPoint (&pos1);
pos1+=((TerrainNormals[MAX_TERRAIN_LOD-1][tseg].normal1)/16);
pos2.x=(t*TERRAIN_SIZE)+(TERRAIN_SIZE * .66);
pos2.z=(i*TERRAIN_SIZE)+(TERRAIN_SIZE/3);
pos2.y=GetTerrainGroundPoint (&pos2);
pos2+=((TerrainNormals[MAX_TERRAIN_LOD-1][tseg].normal2)/16);
for (j=0;j<Terrain_sky.num_satellites;j++)
{
raynum++;
int answer1,answer2;
answer1=ShootRayForTerrainLight (&pos1,&Terrain_sky.satellite_vectors[j],tseg);
answer2=ShootRayForTerrainLight (&pos2,&Terrain_sky.satellite_vectors[j],tseg);
if (answer1==0 && answer2==0)
continue;
float r=exitance[tseg].r;
float g=exitance[tseg].g;
float b=exitance[tseg].b;
int tmap=Terrain_sky.satellite_texture[j];
float light1,light2,light_avg;
vector tnorm1=TerrainNormals[MAX_TERRAIN_LOD-1][tseg].normal1;
vector tnorm2=TerrainNormals[MAX_TERRAIN_LOD-1][tseg].normal2;
vector normal;
vm_GetNormalizedDir (&normal,&Terrain_sky.satellite_vectors[j],&pos1);
light1=answer1*(vm_DotProduct (&normal,&tnorm1));
if (light1<0)
light1=0;
vm_GetNormalizedDir (&normal,&Terrain_sky.satellite_vectors[j],&pos2);
light2=answer2*(vm_DotProduct (&normal,&tnorm2));
if (light2<0)
light2=0;
light_avg=(light1+light2)/2;
r+=(light_avg*GameTextures[tmap].r);
g+=(light_avg*GameTextures[tmap].g);
b+=(light_avg*GameTextures[tmap].b);
exitance[tseg].r=r;
exitance[tseg].g=g;
exitance[tseg].b=b;
}
}
}
// Clip to 1.0
for (i=0;i<TERRAIN_DEPTH*TERRAIN_WIDTH;i++)
{
exitance[i].r=std::min(exitance[i].r,1.0f);
exitance[i].g=std::min(exitance[i].g,1.0f);
exitance[i].b=std::min(exitance[i].b,1.0f);
}
// Now average the surrounding cells to get a 'smooth' shadow effect
for (i=0;i<TERRAIN_DEPTH;i++)
{
for (t=0;t<TERRAIN_WIDTH;t++)
{
int tseg=i*TERRAIN_WIDTH+t;
spectra total={0,0,0};
int num=0;
AddSpectra (&total,&total,&exitance[tseg]);
num++;
if (i!=0)
{
AddSpectra (&total,&total,&exitance[(i-1)*TERRAIN_WIDTH+t]);
num++;
if (t!=0)
{
AddSpectra (&total,&total,&exitance[(i-1)*TERRAIN_WIDTH+(t-1)]);
num++;
}
}
if (t!=0)
{
AddSpectra (&total,&total,&exitance[i*TERRAIN_WIDTH+(t-1)]);
num++;
}
total.r/=num;
total.g/=num;
total.b/=num;
Terrain_seg[tseg].r=total.r*255;
Terrain_seg[tseg].g=total.g*255;
Terrain_seg[tseg].b=total.b*255;
Terrain_seg[tseg].l=((total.r+total.g+total.b)*255)/3;
}
}
free (exitance);
if (do_dynamic)
DoTerrainDynamicTable();
UpdateTerrainLightmaps();
OutrageMessageBox ("Terrain radiosity complete!\nIt is recommended you save the level so you won't have to rerun
this operation.");
}*/
2024-06-15 18:12:48 +00:00
void BuildLightmapUVs(int *room_list, int *face_list, int count, vector *lightmap_poly, int nv, int external) {
matrix face_matrix, trans_matrix;
vector fvec;
vector avg_vert;
vector verts[MAX_VERTS_PER_FACE * 5];
vector facevert;
vector rot_vert;
int i, t;
int lmi_handle;
2024-06-15 18:12:48 +00:00
// find the center point of this face
vm_MakeZero(&avg_vert);
for (i = 0; i < nv; i++)
avg_vert += lightmap_poly[i];
avg_vert /= nv;
// Make the orientation matrix
// Reverse the normal because we're looking "at" the face, not from it
fvec = -Rooms[room_list[0]].faces[face_list[0]].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 < nv; i++) {
vector vert = lightmap_poly[i];
vert -= avg_vert;
vm_MatrixMulVector(&rot_vert, &vert, &trans_matrix);
verts[i] = rot_vert;
}
// Find left most point
int leftmost_point = -1;
float leftmost_x = 900000.00f; // a big number
for (i = 0; i < nv; i++) {
if (verts[i].x < leftmost_x) {
leftmost_point = i;
leftmost_x = verts[i].x;
}
}
ASSERT(leftmost_point != -1);
// Find top most point
int topmost_point = -1;
float topmost_y = -900000.0f; // a big number
for (i = 0; i < nv; i++) {
if (verts[i].y > 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=1<i;
int hi_num=2<<i;
if (res<=hi_num && res>low_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;
}
2024-06-15 18:12:48 +00:00
#define MAX_COMBINES 50
#define LM_ADJACENT_FACE_THRESHOLD .999
2024-05-24 03:07:26 +00:00
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
2024-06-15 18:12:48 +00:00
int CombineLightFaces( vector *dest_verts,vector *averts, int nva, vector *norma,vector *bverts, int nvb,vector
*normb,int aroom,int broom)
{
2024-06-15 18:12:48 +00:00
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<nva; starta++ )
{
int nexta=(starta+1)%nva;
for (startb=0; startb<nvb; startb++ )
{
int nextb=(startb+1)%nvb;
if ((PointsAreSame (&averts[starta],&bverts[nextb])) &&
(PointsAreSame(&averts[nexta],&bverts[startb])))
{
//MATCH!!!!!!!!
int dnv = 0;
for (i=1; i<nva; i++ )
{
ASSERT(dnv < MAX_VERTS_PER_FACE*5);
dest_verts[dnv] = averts[(starta+i)%nva];
va = dest_verts[dnv];
dnv++;
}
if ( (PointsAreSame(&va,&bverts[(startb+2)%nvb])))
mprintf(0, "WARNING!!! Faces were combined that caused the loss of a
vertex!\n");
for (i=1; i<nvb; i++ )
{
ASSERT(dnv < MAX_VERTS_PER_FACE*5 );
if ( (i==1) && (PointsAreSame(&va,&bverts[(startb+i+1)%nvb])))
continue;
else if ( (i==2) && (PointsAreSame(&va,&bverts[(startb+i)%nvb])))
continue;
else
{
dest_verts[dnv] = bverts[(startb+i)%nvb];
dnv++;
}
}
ASSERT( dnv > 2 );
return dnv;
}
}
}
Now check for tjoint faces
for (starta=0; starta<nva; starta++)
{
int nexta=(starta+1)%nva;
for (startb=0; startb<nvb; startb++)
{
vector line_a=averts[nexta]-averts[starta];
}
}
// If these two faces are in the same room and are on the same plane
// then we should combine them by default
if (aroom!=-1 && aroom==broom)
{
int dnv = 0;
int match=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;
}*/
// Returns number of verts in dest if face a can be safely combined with face b
// Returns 0 if not
2024-06-15 18:12:48 +00:00
int CombineLightFaces(vector *dest_verts, vector *averts, int nva, vector *norma, vector *bverts, int nvb,
vector *normb, int aroom, int broom) {
int i;
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
// 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
2024-06-15 18:12:48 +00:00
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;
2024-06-15 18:12:48 +00:00
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
2024-06-15 18:12:48 +00:00
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] = mem_rmalloc<uint8_t>(Rooms[i].num_faces);
2024-06-15 18:12:48 +00:00
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
2024-06-15 18:12:48 +00:00
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
2024-06-15 18:12:48 +00:00
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
2024-06-15 18:12:48 +00:00
// 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)
{
2024-06-15 18:12:48 +00:00
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;i<fp->num_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)
2024-06-15 18:12:48 +00:00
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
2024-06-15 18:12:48 +00:00
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
2024-06-15 18:12:48 +00:00
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 = mem_rmalloc<vector>(rp->num_verts);
2024-06-15 18:12:48 +00:00
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] = mem_rmalloc<float>(rp->num_faces);
2024-06-15 18:12:48 +00:00
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 (int k = 0; k < fp->num_verts; k++) {
2024-06-15 18:12:48 +00:00
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)
{
2024-06-15 18:12:48 +00:00
int t,k;
int not_combined=0;
RoomsAlreadyCombined[roomnum]=(uint8_t *)mem_malloc (Rooms[roomnum].num_faces);
ASSERT (RoomsAlreadyCombined[roomnum]);
for (k=0;k<Rooms[roomnum].num_faces;k++)
RoomsAlreadyCombined[roomnum][k]=0;
for (t=0;t<Rooms[roomnum].num_faces;t++)
{
if (*(RoomsAlreadyCombined[roomnum]+t)==0)
TestLightAdjacency (roomnum,t,0);
}
for (t=0;t<Rooms[roomnum].num_faces;t++)
{
if (!RoomsAlreadyCombined[roomnum][t])
{
vector verts[MAX_VERTS_PER_FACE];
int room_list[2],face_list[2];
for (k=0;k<Rooms[roomnum].faces[t].num_verts;k++)
verts[k]=Rooms[roomnum].verts[Rooms[roomnum].faces[t].face_verts[k]];
room_list[0]=i;
face_list[0]=t;
BuildLightmapUVs (room_list,face_list,1,verts,Rooms[roomnum].faces[t].num_verts,external);
not_combined++;
}
}
mprintf(0,"%d faces couldn't be combined!\n",not_combined);
// Free memory
free (RoomsAlreadyCombined[roomnum]);
}*/