/* * Descent 3 * Copyright (C) 2024 Parallax Software * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include "3d.h" #include "gametexture.h" #include "editor_lighting.h" #include "room.h" #include "lightmap.h" #include "polymodel.h" #include #include #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(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(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(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(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=1low_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(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]); } }