/*
* 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 .
--- 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
#include "3d.h"
#include "texture.h"
#include "gametexture.h"
#include "erooms.h"
#include "editor_lighting.h"
#include "descent.h"
#include "room.h"
#include "lightmap.h"
#include "polymodel.h"
#include
#include
#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 "program.h"
#include "loadlevel.h"
#include "special_face.h"
#include "boa.h"
#include "mem.h"
typedef struct
{
float x,y;
} spec_vertex;
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=0 && dest_y>=0);
// First copy the main body
for (i=0;ifaces[facenum].lmi_handle];
ushort *src_data=(ushort *)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;jnum_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;knum_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,ushort *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];
ushort *src_data=(ushort *)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;tlm_object.num_models;t++)
{
if (IsNonRenderableSubmodel (&Poly_models[this_obj->rtype.pobj_info.model_num],t))
continue;
for (k=0;klm_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;jnum_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;tnum_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;tnum_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;knum_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; xnum_verts && !match; x++ )
{
for (int y=0; ynum_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=(uint8_t *)mem_malloc (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;tnum_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);
ushort *fill_data=(ushort *)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;knum_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);
ushort *fill_data=(ushort *)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;tlm_object.num_models;t++)
{
if (IsNonRenderableSubmodel (&Poly_models[obj->rtype.pobj_info.model_num],t))
continue;
for (int j=0;jlm_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);
ushort *fill_data=(ushort *)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;blm_object.num_models;b++)
{
if (IsNonRenderableSubmodel (&Poly_models[obj->rtype.pobj_info.model_num],b))
continue;
for (int c=0;clm_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);
ushort *fill_data=(ushort *)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;tlm_object.num_models;t++)
{
if (IsNonRenderableSubmodel (&Poly_models[obj->rtype.pobj_info.model_num],t))
continue;
for (k=0;klm_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);
ushort *fill_data=(ushort *)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;blm_object.num_models;b++)
{
if (IsNonRenderableSubmodel (&Poly_models[obj->rtype.pobj_info.model_num],b))
continue;
for (int c=0;clm_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);
ushort *fill_data=(ushort *)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;inum_verts;i++)
{
if (fp->face_uvls[i].u2face_uvls[i].u2;
if (fp->face_uvls[i].u2>right)
right=fp->face_uvls[i].u2;
if (fp->face_uvls[i].v2face_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;ir<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;tnum_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[PSPATHNAME_LEN];
ddio_MakePath(filename,Base_directory,"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;roomnum200)
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;k0)
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[PSPATHNAME_LEN+1];
ddio_MakePath(filename,Base_directory,"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;tnum_faces;t++)
{
vector verts[MAX_VERTS_PER_FACE*5];
int room_list[2],face_list[2];
for (int k=0;kfaces[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=(rad_surface *)mem_malloc (facecount*sizeof(rad_surface));
ASSERT (Light_surfaces!=NULL);
// Set initial surface properties
max_index=surface_index=0;
for (t=0;tnum_faces;t++,surface_index++,max_index++)
{
ComputeSurfaceRes (&Light_surfaces[surface_index],rp,t);
if (rp->faces[t].num_verts)
{
Light_surfaces[surface_index].verts=(vector *)mem_malloc (rp->faces[t].num_verts*sizeof(vector));
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=(rad_element *)mem_malloc (Light_surfaces[surface_index].xresolution*Light_surfaces[surface_index].yresolution*sizeof(rad_element));
ASSERT (Light_surfaces[surface_index].elements!=NULL);
}
else
{
Light_surfaces[surface_index].elements=NULL;
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;tnum_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;i0)
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);
ushort *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;ielements[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=min(1.0,sp->elements[i*xres+t].exitance.r+Ambient_red+Room_ambience_r[roomnum]);
fg=min(1.0,sp->elements[i*xres+t].exitance.g+Ambient_green+Room_ambience_g[roomnum]);
fb=min(1.0,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=min(red,255);
green=min(green,255);
blue=min(blue,255);
ushort 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;ielements[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;knum_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;knum_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.xx)
code |=CC_OFF_LEFT;
if (rp->pos.x>rad_ClipRight->x)
code |=CC_OFF_RIGHT;
if (rp->pos.yy)
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;iflags |=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=(vector *)mem_malloc (sizeof(vector)*nnv);
ASSERT (ep->verts);
for (i=0;iverts[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;ipos.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;ipos.y)
continue;
int answer;
answer=ShootRayForTerrainLight (&pos,&Terrain_sky.satellite_vectors[j],tseg);
if (!answer)
continue;
Terrain_dynamic_table[tseg]|=(1<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.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;i1.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;i0)
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;i0 || 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;itopmost_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;irightmost_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;i0)
res++;
res++;
if (res>126)
xspacing+=1;
else
done_spacing=1;
}
// Set a mininum, at least
if (res<2)
res=2;
lightmap_x_res=res;
done_spacing=0;
while (!done_spacing)
{
res=(ydiff/yspacing);
if (((ydiff/yspacing)-res)>0)
res++;
res++;
if (res>126)
yspacing+=1;
else
done_spacing=1;
}
// Set a mininum, at least
if (res<2)
res=2;
lightmap_y_res=res;
/*// Find power of 2 number
for (i=0;i<=7;i++)
{
int low_num=1low_num)
{
lightmap_res=hi_num;
break;
}
}*/
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;ifaces[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 2 );
return dnv;
}
}
}
Now check for tjoint faces
for (starta=0; starta 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; ifaces[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 && roomnumtmap].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;inum_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_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;knum_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;k1)
{
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 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 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];
ushort *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;inum_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;inum_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;lnum_verts;l++)
{
int vert_to_check=fp->face_verts[l];
for (j=0;jnum_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;knum_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;inum_verts);
ASSERT (vertnorms);
for (t=0;tnum_verts;t++)
{
int total=0;
vector normal;
vm_MakeZero (&normal);
for (int k=0;knum_faces;k++)
{
face *fp=&rp->faces[k];
for (int j=0;jnum_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]=(float *)mem_malloc (sizeof(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;tnum_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 (k=0;knum_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