mirror of
https://github.com/kevinbentley/Descent3.git
synced 2025-01-22 19:55:23 +00:00
1013 lines
30 KiB
C++
1013 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"
|
|
|
|
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 = (vector *)mem_malloc(sm->faces[j].nverts * sizeof(vector));
|
|
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 =
|
|
(rad_element *)mem_malloc(Light_surfaces[surface_index].xresolution *
|
|
Light_surfaces[surface_index].yresolution * sizeof(rad_element));
|
|
ASSERT(Light_surfaces[surface_index].elements != NULL);
|
|
} else
|
|
Light_surfaces[surface_index].elements = NULL;
|
|
|
|
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 = (vector *)mem_malloc(sm->faces[j].nverts * sizeof(vector));
|
|
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 =
|
|
(rad_element *)mem_malloc(Light_surfaces[surface_index].xresolution *
|
|
Light_surfaces[surface_index].yresolution * sizeof(rad_element));
|
|
ASSERT(Light_surfaces[surface_index].elements != NULL);
|
|
} else
|
|
Light_surfaces[surface_index].elements = NULL;
|
|
|
|
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] = (uint8_t *)mem_malloc(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]);
|
|
}
|
|
}
|