/* * 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 . */ /* * $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]; ubyte *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 (); ubyte *Lightmap_mask=NULL; static ubyte *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=(ubyte *)mem_malloc (MAX_LIGHTMAP_INFOS); Lightmap_mask=(ubyte *)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); ubyte 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; } ubyte CodeRadPoint (rad_point *rp) { ubyte 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; ubyte and=0xff; ubyte 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 ubyte *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=(ubyte *)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]=(ubyte *)mem_malloc (Rooms[roomnum].num_faces); ASSERT (RoomsAlreadyCombined[roomnum]); for (k=0;k