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