Descent3/editor/editor_object_lighting.cpp
Jan Engelhardt 3c9234c4c0 mem_malloc type triviality checks (8/8)
(Manual modification.) Change mem_malloc callsites with
non-fundamental types to mem_rmalloc.
2024-09-16 22:12:14 +02:00

1014 lines
30 KiB
C++

/*
* 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/>.
*/
#include <algorithm>
#include "3d.h"
#include "gametexture.h"
#include "editor_lighting.h"
#include "room.h"
#include "lightmap.h"
#include "polymodel.h"
#include <string.h>
#include <stdlib.h>
#include "radiosity.h"
#include "lightmap_info.h"
#include "object_lighting.h"
#include "mem.h"
#include "mono.h"
void ComputeObjectSurfaceRes(rad_surface *surf, object *obj, int subnum, int facenum) {
int i;
float left = 1.1f, right = -1, top = 1.1f, bottom = -1;
lightmap_object_face *lfp = &obj->lm_object.lightmap_faces[subnum][facenum];
int lw = lmi_w(lfp->lmi_handle);
int lh = lmi_h(lfp->lmi_handle);
for (i = 0; i < lfp->num_verts; i++) {
if (lfp->u2[i] < left)
left = lfp->u2[i];
if (lfp->u2[i] > right)
right = lfp->u2[i];
if (lfp->v2[i] < top)
top = lfp->v2[i];
if (lfp->v2[i] > bottom)
bottom = lfp->v2[i];
}
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++;
}
void ApplyLightmapToObjectSurface(object *obj, int subnum, int facenum, rad_surface *sp) {
lightmap_object_face *fp = &obj->lm_object.lightmap_faces[subnum][facenum];
int i, t, lmi_handle;
int xres, yres;
int lw, lh;
int x1 = sp->x1;
int y1 = sp->y1;
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);
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);
fg = std::min(1.0f, sp->elements[i * xres + t].exitance.g + Ambient_green);
fb = std::min(1.0f, sp->elements[i * xres + t].exitance.b + Ambient_blue);
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);
dest_data[(i + y1) * lw + (t + x1)] = OPAQUE_FLAG | GR_RGB16(red, green, blue);
}
}
}
}
void GetPointInObjectSpace(vector *dest, vector *pos, object *obj, int subnum, int world) {
poly_model *pm = &Poly_models[obj->rtype.pobj_info.model_num];
bsp_info *sm = &pm->submodel[subnum];
float normalized_time[MAX_SUBOBJECTS];
int i;
int rotate_list[MAX_SUBOBJECTS];
int num_to_rotate = 0;
if (!pm->new_style)
return;
for (i = 0; i < MAX_SUBOBJECTS; i++)
normalized_time[i] = 0.0;
SetModelAnglesAndPos(pm, normalized_time);
vector pnt = *pos;
int mn = subnum;
vector tpnt;
matrix m;
while (mn != -1) {
rotate_list[num_to_rotate] = mn;
num_to_rotate++;
mn = pm->submodel[mn].parent;
}
// Subtract and rotate position
if (world)
tpnt = pnt - obj->pos;
else
tpnt = pnt;
pnt = tpnt * obj->orient;
for (i = num_to_rotate - 1; i >= 0; i--) {
// Subtract and rotate position for this submodel
mn = rotate_list[i];
if (world)
tpnt = pnt - pm->submodel[mn].offset;
else
tpnt = pnt;
vm_AnglesToMatrix(&m, pm->submodel[mn].angs.p, pm->submodel[mn].angs.h, pm->submodel[mn].angs.b);
pnt = tpnt * m;
}
*dest = pnt;
}
// Goes through all objects and fills in the lightmap data for them
void AssignLightmapsToObjectSurfaces(int surface_index, int terrain) {
int i, t, j;
uint8_t rotated[MAX_LIGHTMAP_INFOS];
memset(rotated, 0, MAX_LIGHTMAP_INFOS);
for (i = 0; i <= Highest_object_index; i++) {
if ((terrain != 0) != (OBJECT_OUTSIDE(&Objects[i]) != 0))
continue;
if (Objects[i].type != OBJ_NONE && Objects[i].lighting_render_type == LRT_LIGHTMAPS) {
poly_model *po = &Poly_models[Objects[i].rtype.pobj_info.model_num];
if (!po->new_style)
continue;
for (t = 0; t < po->n_models; t++) {
bsp_info *sm = &po->submodel[t];
if (IsNonRenderableSubmodel(po, t))
continue;
for (j = 0; j < sm->num_faces; j++, surface_index++) {
ApplyLightmapToObjectSurface(&Objects[i], t, j, &Light_surfaces[surface_index]);
// Rotate the lightmap upper left
object *obj = &Objects[i];
lightmap_object_face *fp = &obj->lm_object.lightmap_faces[t][j];
lightmap_info *lmi_ptr = &LightmapInfo[fp->lmi_handle];
if (!rotated[fp->lmi_handle]) {
vector uleft, rvec, uvec, norm;
GetPointInObjectSpace(&uleft, &lmi_ptr->upper_left, obj, t, 1);
GetPointInObjectSpace(&norm, &lmi_ptr->normal, obj, t, 0);
lmi_ptr->normal = norm;
lmi_ptr->upper_left = uleft;
GetPointInObjectSpace(&rvec, &ScratchRVecs[fp->lmi_handle], obj, t, 0);
GetPointInObjectSpace(&uvec, &ScratchUVecs[fp->lmi_handle], obj, t, 0);
rotated[fp->lmi_handle] = 1;
// Find all the faces in this submodel that have this lightmap info handle
for (int k = 0; k < sm->num_faces; k++) {
lightmap_object_face *this_fp = &obj->lm_object.lightmap_faces[t][k];
if (fp->lmi_handle == this_fp->lmi_handle) {
this_fp->rvec = rvec;
this_fp->uvec = uvec;
}
}
}
}
}
}
}
}
// Goes through all objects int a room and fills in the lightmap data for them
void AssignLightmapsToObjectSurfacesForSingleRoom(int surface_index, int roomnum) {
int i, t, j;
for (i = 0; i <= Highest_object_index; i++) {
if (Objects[i].type != OBJ_NONE && Objects[i].lighting_render_type == LRT_LIGHTMAPS &&
Objects[i].roomnum == roomnum) {
poly_model *po = &Poly_models[Objects[i].rtype.pobj_info.model_num];
if (!po->new_style)
continue;
for (t = 0; t < po->n_models; t++) {
bsp_info *sm = &po->submodel[t];
if (IsNonRenderableSubmodel(po, t))
continue;
for (j = 0; j < sm->num_faces; j++, surface_index++)
ApplyLightmapToObjectSurface(&Objects[i], t, j, &Light_surfaces[surface_index]);
}
}
}
}
// Sets up radiosity surfaces for objects in the mine
// Returns the number of new surfaces
int ComputeSurfacesForObjects(int surface_index, int terrain) {
int i, t, j;
for (i = 0; i <= Highest_object_index; i++) {
if ((terrain != 0) != (OBJECT_OUTSIDE(&Objects[i]) != 0))
continue;
if (Objects[i].type != OBJ_NONE && Objects[i].lighting_render_type == LRT_LIGHTMAPS) {
poly_model *po = &Poly_models[Objects[i].rtype.pobj_info.model_num];
if (!po->new_style)
continue;
SetupObjectLightmapMemory(&Objects[i]);
if (terrain)
CombineObjectLightmapUVs(&Objects[i], LMI_TERRAIN_OBJECT);
else
CombineObjectLightmapUVs(&Objects[i], LMI_ROOM_OBJECT);
for (t = 0; t < po->n_models; t++) {
bsp_info *sm = &po->submodel[t];
if (IsNonRenderableSubmodel(po, t))
continue;
for (j = 0; j < sm->num_faces; j++, surface_index++) {
ComputeObjectSurfaceRes(&Light_surfaces[surface_index], &Objects[i], t, j);
if (sm->faces[j].nverts > 0) {
Light_surfaces[surface_index].verts = mem_rmalloc<vector>(sm->faces[j].nverts);
ASSERT(Light_surfaces[surface_index].verts != NULL);
} else
Light_surfaces[surface_index].verts = NULL;
if (Light_surfaces[surface_index].xresolution * Light_surfaces[surface_index].yresolution > 0) {
Light_surfaces[surface_index].elements =
mem_rmalloc<rad_element>(Light_surfaces[surface_index].xresolution *
Light_surfaces[surface_index].yresolution);
ASSERT(Light_surfaces[surface_index].elements != NULL);
} else
Light_surfaces[surface_index].elements = NULL;
Light_surfaces[surface_index].flags = 0;
if (sm->faces[j].texnum == -1) {
Light_surfaces[surface_index].emittance.r = 0;
Light_surfaces[surface_index].emittance.g = 0;
Light_surfaces[surface_index].emittance.b = 0;
Light_surfaces[surface_index].reflectivity = .5;
} else {
Light_surfaces[surface_index].emittance.r = (float)GameTextures[po->textures[sm->faces[j].texnum]].r;
Light_surfaces[surface_index].emittance.g = (float)GameTextures[po->textures[sm->faces[j].texnum]].g;
Light_surfaces[surface_index].emittance.b = (float)GameTextures[po->textures[sm->faces[j].texnum]].b;
Light_surfaces[surface_index].reflectivity = GameTextures[po->textures[sm->faces[j].texnum]].reflectivity;
if ((GetMaxColor(&Light_surfaces[surface_index].emittance)) > .005)
Light_surfaces[surface_index].flags |= SF_LIGHTSOURCE;
}
if (terrain)
Light_surfaces[surface_index].surface_type = ST_TERRAIN_OBJECT;
else
Light_surfaces[surface_index].surface_type = ST_ROOM_OBJECT;
Light_surfaces[surface_index].normal =
LightmapInfo[Objects[i].lm_object.lightmap_faces[t][j].lmi_handle].normal;
Light_surfaces[surface_index].roomnum = Objects[i].roomnum;
if (Light_surfaces[surface_index].surface_type == ST_ROOM_OBJECT) {
if (Rooms[Objects[i].roomnum].flags & RF_TOUCHES_TERRAIN)
Light_surfaces[surface_index].flags |= SF_TOUCHES_TERRAIN;
for (int k = 0; k < Rooms[Objects[i].roomnum].num_portals; k++) {
if (Rooms[Objects[i].roomnum].portals[k].croom == -1 ||
(Rooms[Rooms[Objects[i].roomnum].portals[k].croom].flags & RF_EXTERNAL))
Light_surfaces[surface_index].flags |= SF_TOUCHES_TERRAIN;
}
}
// Set the vertices for each element
BuildElementListForObjectFace(i, t, j, &Light_surfaces[surface_index]);
}
}
}
}
return 0;
}
// Sets up radiosity surfaces for objects in a room
// Returns the number of new surfaces
int ComputeSurfacesForObjectsForSingleRoom(int surface_index, int roomnum) {
int i, t, j;
for (i = 0; i <= Highest_object_index; i++) {
if (Objects[i].type != OBJ_NONE && Objects[i].lighting_render_type == LRT_LIGHTMAPS &&
Objects[i].roomnum == roomnum) {
poly_model *po = &Poly_models[Objects[i].rtype.pobj_info.model_num];
if (!po->new_style)
continue;
SetupObjectLightmapMemory(&Objects[i]);
CombineObjectLightmapUVs(&Objects[i], LMI_ROOM_OBJECT);
for (t = 0; t < po->n_models; t++) {
bsp_info *sm = &po->submodel[t];
if (IsNonRenderableSubmodel(po, t))
continue;
for (j = 0; j < sm->num_faces; j++, surface_index++) {
ComputeObjectSurfaceRes(&Light_surfaces[surface_index], &Objects[i], t, j);
if (sm->faces[j].nverts > 0) {
Light_surfaces[surface_index].verts = mem_rmalloc<vector>(sm->faces[j].nverts);
ASSERT(Light_surfaces[surface_index].verts != NULL);
} else
Light_surfaces[surface_index].verts = NULL;
if (Light_surfaces[surface_index].xresolution * Light_surfaces[surface_index].yresolution > 0) {
Light_surfaces[surface_index].elements =
mem_rmalloc<rad_element>(Light_surfaces[surface_index].xresolution *
Light_surfaces[surface_index].yresolution);
ASSERT(Light_surfaces[surface_index].elements != NULL);
} else
Light_surfaces[surface_index].elements = NULL;
if (sm->faces[j].texnum == -1) {
Light_surfaces[surface_index].emittance.r = 0;
Light_surfaces[surface_index].emittance.g = 0;
Light_surfaces[surface_index].emittance.b = 0;
Light_surfaces[surface_index].reflectivity = .5;
} else {
Light_surfaces[surface_index].emittance.r = (float)GameTextures[po->textures[sm->faces[j].texnum]].r;
Light_surfaces[surface_index].emittance.g = (float)GameTextures[po->textures[sm->faces[j].texnum]].g;
Light_surfaces[surface_index].emittance.b = (float)GameTextures[po->textures[sm->faces[j].texnum]].b;
Light_surfaces[surface_index].reflectivity = GameTextures[po->textures[sm->faces[j].texnum]].reflectivity;
}
Light_surfaces[surface_index].surface_type = ST_ROOM_OBJECT;
Light_surfaces[surface_index].normal =
LightmapInfo[Objects[i].lm_object.lightmap_faces[t][j].lmi_handle].normal;
Light_surfaces[surface_index].roomnum = Objects[i].roomnum;
// Set the vertices for each element
BuildElementListForObjectFace(i, t, j, &Light_surfaces[surface_index]);
}
}
}
}
return 0;
}
// Gets the total number of object faces that exist in a mine
int GetTotalObjectFaces(int terrain) {
int i;
int facecount = 0;
for (i = 0; i <= Highest_object_index; i++) {
if (Objects[i].type != OBJ_NONE) {
if ((terrain != 0) != (OBJECT_OUTSIDE(&Objects[i]) != 0))
continue;
if (Objects[i].lighting_render_type == LRT_LIGHTMAPS) {
poly_model *po = &Poly_models[Objects[i].rtype.pobj_info.model_num];
if (!po->new_style)
continue;
facecount += CountFacesInPolymodel(po);
}
}
}
return facecount;
}
// Gets the total number of object faces that exist in a room
int GetTotalObjectFacesForSingleRoom(int roomnum) {
int i;
int facecount = 0;
for (i = 0; i <= Highest_object_index; i++) {
if (Objects[i].type != OBJ_NONE) {
if (Objects[i].roomnum != roomnum)
continue;
if (Objects[i].lighting_render_type == LRT_LIGHTMAPS) {
poly_model *po = &Poly_models[Objects[i].rtype.pobj_info.model_num];
if (!po->new_style)
continue;
facecount += CountFacesInPolymodel(po);
}
}
}
return facecount;
}
void BuildObjectLightmapUVs(object *obj, int *sublist, int *facelist, int count, vector *lightmap_poly, int nv,
int lm_type) {
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;
vector world_verts[32];
poly_model *pm = &Poly_models[obj->rtype.pobj_info.model_num];
for (i = 0; i < pm->submodel[sublist[0]].faces[facelist[0]].nverts; i++)
GetObjectPointInWorld(&world_verts[i], obj, sublist[0], pm->submodel[sublist[0]].faces[facelist[0]].vertnums[i]);
// 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
vm_GetNormal(&fvec, &world_verts[0], &world_verts[1], &world_verts[2]);
fvec = -fvec;
if ((vm_NormalizeVector(&fvec)) != 0)
vm_VectorToMatrix(&face_matrix, &fvec, NULL, NULL);
else
vm_MakeIdentity(&face_matrix);
// 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;
}
}*/
lmi_handle = AllocLightmapInfo(lightmap_x_res, lightmap_y_res, lm_type);
ASSERT(lmi_handle != BAD_LMI_INDEX);
// 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++) {
obj->lm_object.lightmap_faces[sublist[i]][facelist[i]].lmi_handle = lmi_handle;
bsp_info *sm = &pm->submodel[sublist[i]];
polyface *fp = &sm->faces[facelist[i]];
lightmap_object_face *lfp = &obj->lm_object.lightmap_faces[sublist[i]][facelist[i]];
for (t = 0; t < fp->nverts; t++)
GetObjectPointInWorld(&world_verts[t], obj, sublist[i], fp->vertnums[t]);
for (t = 0; t < fp->nverts; t++) {
vector vert = world_verts[t];
vert -= avg_vert;
vm_MatrixMulVector(&rot_vert, &vert, &trans_matrix);
facevert = rot_vert;
// Find uv2s for this vertex
lfp->u2[t] = (facevert.x - verts[leftmost_point].x) / (float)(lightmap_x_res * xspace_int);
lfp->v2[t] = fabs((verts[topmost_point].y - facevert.y)) / (float)(lightmap_y_res * yspace_int);
ASSERT(lfp->u2[t] >= 0 && lfp->u2[t] <= 1.0);
ASSERT(lfp->v2[t] >= 0 && lfp->v2[t] <= 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 = -fvec;
ScratchCenters[lmi_handle] = avg_vert;
}
// Important - vertnum is the index into the face_verts[] array in the face structure,
// not an index into the verts[] array of the room structure
void BuildElementListForObjectFace(int objnum, int subnum, 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;
vector world_verts[32];
int i, t;
int xres, yres;
int lmi_handle;
int x1 = surf->x1, y1 = surf->y1;
poly_model *pm = &Poly_models[Objects[objnum].rtype.pobj_info.model_num];
bsp_info *sm = &pm->submodel[subnum];
polyface *fp = &sm->faces[facenum];
xres = surf->xresolution;
yres = surf->yresolution;
ASSERT(pm->used);
ASSERT(fp->nverts >= 3);
ASSERT(Objects[objnum].lm_object.lightmap_faces[subnum][facenum].lmi_handle != BAD_LMI_INDEX);
ASSERT(fp->nverts < 32);
for (i = 0; i < fp->nverts; i++)
GetObjectPointInWorld(&world_verts[i], &Objects[objnum], subnum, fp->vertnums[i]);
lmi_handle = Objects[objnum].lm_object.lightmap_faces[subnum][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;
if ((vm_NormalizeVector(&fvec)) != 0)
vm_VectorToMatrix(&face_matrix, &fvec, NULL, NULL);
else
vm_MakeIdentity(&face_matrix);
ScratchRVecs[lmi_handle] = face_matrix.rvec;
ScratchUVecs[lmi_handle] = face_matrix.uvec;
// 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 < fp->nverts; i++) {
vert = world_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);
vm_TransposeMatrix(&trans_matrix);
xdiff.x = LightmapInfo[lmi_handle].xspacing;
ydiff.y = LightmapInfo[lmi_handle].yspacing;
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];
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, fp->nverts);
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 = fp->nverts;
for (int k = 0; k < surf->num_verts; k++) {
surf->verts[k] = world_verts[k];
}
}
}
#define MAX_COMBINES 50
#define LM_ADJACENT_FACE_THRESHOLD .95
uint8_t *ObjectsAlreadyCombined[MAX_OBJECTS];
// Given a submodel and a face, goes through the entire object and checks to see
// if this face can share a lightmap with any other face
int TestObjectLightAdjacency(object *obj, int subnum, int facenum, int lmi_type) {
int i, t, k;
poly_model *pm = &Poly_models[obj->rtype.pobj_info.model_num];
bsp_info *a_sm = &pm->submodel[subnum];
polyface *afp = &a_sm->faces[facenum];
vector anormal;
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 submodel_combine_list[MAX_COMBINES];
if (afp->texnum == -1)
return 0;
int tex = pm->textures[afp->texnum];
if (GameTextures[tex].r > 0 || GameTextures[tex].g > 0 || GameTextures[tex].b > 0)
return 0;
// Setup our 'base' face
int anv = afp->nverts;
int total_faces = 1;
submodel_combine_list[0] = subnum;
face_combine_list[0] = facenum;
for (i = 0; i < afp->nverts; i++)
GetObjectPointInWorld(&averts[i], obj, subnum, afp->vertnums[i]);
vm_GetNormal(&anormal, &averts[0], &averts[1], &averts[2]);
StartOver:
// Go through each room and find an adjacent face
for (i = 0; i < pm->n_models; i++) {
bsp_info *bsm = &pm->submodel[i];
if (IsNonRenderableSubmodel(pm, i))
continue;
if (bsm != a_sm) // only combine faces in the same submodel
continue;
for (t = 0; t < bsm->num_faces; t++) {
if (total_faces >= MAX_COMBINES - 1)
continue;
if (bsm == a_sm && t == facenum)
continue; // don't do self
// Don't do if already spoken fore
if (ObjectsAlreadyCombined[i][t])
continue;
polyface *bfp = &bsm->faces[t];
vector bnormal;
// Don't do combine light sources
tex = pm->textures[bfp->texnum];
if (GameTextures[tex].r > 0 || GameTextures[tex].g > 0 || GameTextures[tex].b > 0)
continue;
for (k = 0; k < bfp->nverts; k++)
GetObjectPointInWorld(&bverts[k], obj, i, bfp->vertnums[k]);
vm_GetNormal(&bnormal, &bverts[0], &bverts[1], &bverts[2]);
int nv = CombineLightFaces(dest_verts, averts, anv, &anormal, bverts, bfp->nverts, &bnormal);
// We have a combine! Mark this face in the appropriate list
// And update our new polygon
if (nv > 0) {
submodel_combine_list[total_faces] = i;
face_combine_list[total_faces] = t;
total_faces++;
ObjectsAlreadyCombined[subnum][facenum] = 1;
ObjectsAlreadyCombined[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) {
BuildObjectLightmapUVs(obj, submodel_combine_list, face_combine_list, total_faces, averts, anv, lmi_type);
}
return 1;
}
// Computes the the mines UVs
// Faces can now share one lightmap, so this routine goes through and tries to
// combine as many faces as it can into one lightmap
void CombineObjectLightmapUVs(object *obj, int lmi_type) {
int i, t, k;
int not_combined = 0;
poly_model *pm = &Poly_models[obj->rtype.pobj_info.model_num];
ASSERT(obj->lm_object.used);
for (i = 0; i < pm->n_models; i++) {
bsp_info *sm = &pm->submodel[i];
if (IsNonRenderableSubmodel(pm, i))
continue;
ObjectsAlreadyCombined[i] = mem_rmalloc<uint8_t>(sm->num_faces);
ASSERT(ObjectsAlreadyCombined[i]);
for (k = 0; k < sm->num_faces; k++)
ObjectsAlreadyCombined[i][k] = 0;
}
for (i = 0; i < pm->n_models; i++) {
bsp_info *sm = &pm->submodel[i];
if (IsNonRenderableSubmodel(pm, i))
continue;
for (t = 0; t < sm->num_faces; t++) {
if (*(ObjectsAlreadyCombined[i] + t) == 0)
TestObjectLightAdjacency(obj, i, t, lmi_type);
}
}
// Now build lightmaps for any faces that couldn't be combined
for (i = 0; i < pm->n_models; i++) {
bsp_info *sm = &pm->submodel[i];
if (IsNonRenderableSubmodel(pm, i))
continue;
for (t = 0; t < sm->num_faces; t++) {
if (!ObjectsAlreadyCombined[i][t]) {
vector verts[MAX_VERTS_PER_FACE * 5];
int submodel_list[2], face_list[2];
for (k = 0; k < sm->faces[t].nverts; k++) {
GetObjectPointInWorld(&verts[k], obj, i, sm->faces[t].vertnums[k]);
}
submodel_list[0] = i;
face_list[0] = t;
BuildObjectLightmapUVs(obj, submodel_list, face_list, 1, verts, sm->faces[t].nverts, lmi_type);
not_combined++;
}
}
}
mprintf(0, "%d %s faces couldn't be combined!\n", not_combined, pm->name);
// Free memory
for (i = 0; i < pm->n_models; i++) {
bsp_info *sm = &pm->submodel[i];
if (IsNonRenderableSubmodel(pm, i))
continue;
mem_free(ObjectsAlreadyCombined[i]);
}
}