1 /****************************************************************************************/
\r
4 /* Author: John Pollard */
\r
5 /* Description: Cacluates radiosity for a BSP. */
\r
7 /* The contents of this file are subject to the Genesis3D Public License */
\r
8 /* Version 1.01 (the "License"); you may not use this file except in */
\r
9 /* compliance with the License. You may obtain a copy of the License at */
\r
10 /* http://www.genesis3d.com */
\r
12 /* Software distributed under the License is distributed on an "AS IS" */
\r
13 /* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See */
\r
14 /* the License for the specific language governing rights and limitations */
\r
15 /* under the License. */
\r
17 /* The Original Code is Genesis3D, released March 25, 1999. */
\r
18 /*Genesis3D Version 1.1 released November 15, 1999 */
\r
19 /* Copyright (C) 1999 WildTangent, Inc. All Rights Reserved */
\r
21 /****************************************************************************************/
\r
22 #include <Windows.h>
\r
25 #include "DCommon.h"
\r
27 #include "GBSPFile.h"
\r
29 #include "MathLib.h"
\r
32 #include "Texture.h"
\r
36 pRAD_Patch *FacePatches;
\r
37 pRAD_Patch *PatchList;
\r
43 geBoolean BuildPatch(int32 Face);
\r
44 geBoolean FinalizePatches(void);
\r
45 RAD_Patch *AllocPatch(void);
\r
46 void FreePatch(RAD_Patch *Patch);
\r
47 void CalcPatchReflectivity(int32 Face, RAD_Patch *Patch);
\r
48 void GetFaceMinsMaxs(int32 Face, geVec3d *Mins, geVec3d *Maxs);
\r
50 geBoolean SaveReceiverFile(char *FileName);
\r
51 geBoolean LoadReceiverFile(char *FileName);
\r
53 //====================================================================================
\r
55 //====================================================================================
\r
56 geBoolean BuildPatches(void)
\r
60 GHook.Printf("--- Build Patches --- \n");
\r
62 NumPatches = NumGFXFaces;
\r
64 FacePatches = GE_RAM_ALLOCATE_ARRAY(pRAD_Patch,NumGFXFaces);
\r
68 GHook.Error("BuilkdPatches: Not enough memory for patches.\n");
\r
72 for (i=0; i< NumGFXFaces; i++)
\r
74 FacePatches[i] = NULL;
\r
76 //if (GFXTexInfo[GFXFaces[i].TexInfo].Flags & TEXINFO_NO_LIGHTMAP)
\r
84 if (!FinalizePatches())
\r
86 GHook.Error("BuildPatches: Could not finalize face patches.\n");
\r
91 GHook.Printf("Num Patches : %5i\n", NumPatches);
\r
96 //====================================================================================
\r
98 //====================================================================================
\r
99 geBoolean CalcPatchInfo(RAD_Patch *Patch)
\r
104 ClearBounds(&Patch->Mins, &Patch->Maxs);
\r
106 Poly = Patch->Poly;
\r
110 GHook.Error("CalcPatchInfo: No Poly!\n");
\r
114 for (i=0; i< Poly->NumVerts; i++)
\r
116 for (k=0; k< 3; k++)
\r
118 if (VectorToSUB(Poly->Verts[i], k) < VectorToSUB(Patch->Mins, k))
\r
119 VectorToSUB(Patch->Mins, k) = VectorToSUB(Poly->Verts[i], k);
\r
120 if (VectorToSUB(Poly->Verts[i], k) > VectorToSUB(Patch->Maxs, k))
\r
121 VectorToSUB(Patch->Maxs, k) = VectorToSUB(Poly->Verts[i], k);
\r
128 //====================================================================================
\r
129 // GBSPPolyFromGFXFace
\r
130 //====================================================================================
\r
131 GBSP_Poly *GBSPPolyFromGFXFace(GFX_Face *Face)
\r
133 GBSP_Poly *NewPoly;
\r
136 NewPoly = AllocPoly(Face->NumVerts);
\r
138 for (i=0; i< Face->NumVerts; i++)
\r
140 Index = GFXVertIndexList[i + Face->FirstVert];
\r
142 NewPoly->Verts[i].X = (geFloat)GFXVerts[Index].X;
\r
143 NewPoly->Verts[i].Y = (geFloat)GFXVerts[Index].Y;
\r
144 NewPoly->Verts[i].Z = (geFloat)GFXVerts[Index].Z;
\r
147 RemoveDegenerateEdges(NewPoly);
\r
152 //====================================================================================
\r
154 //====================================================================================
\r
155 geBoolean PatchNeedsSplit(RAD_Patch *Patch, GBSP_Plane *Plane)
\r
160 if (NumPatches >= MAX_PATCHES-2) // Do not cut any more if max patches exceeded!!!
\r
167 for (i=0; i<3; i++)
\r
169 Dist = VectorToSUB(Patch->Maxs, i) - VectorToSUB(Patch->Mins, i);
\r
171 if (Dist > PatchSize)
\r
173 // Cut it right through the center...
\r
174 geVec3d_Clear(&Plane->Normal);
\r
175 VectorToSUB(Plane->Normal, i) = (geFloat)1;
\r
176 Plane->Dist = (VectorToSUB(Patch->Maxs, i) + VectorToSUB(Patch->Mins, i))/2.0f;
\r
177 Plane->Type = PLANE_ANY;
\r
186 for (i=0 ; i<3 ; i++)
\r
188 Min = VectorToSUB(Patch->Mins,i)+1.0f;
\r
189 Max = VectorToSUB(Patch->Maxs,i)-1.0f;
\r
190 if (floor(Min/PatchSize) < floor(Max/PatchSize))
\r
192 geVec3d_Clear(&Plane->Normal);
\r
193 VectorToSUB(Plane->Normal, i) = (geFloat)1;
\r
194 Plane->Dist = PatchSize*(1.0f+(geFloat)floor(Min/PatchSize));
\r
195 Plane->Type = PLANE_ANY;
\r
203 //====================================================================================
\r
204 // SubdivideFacePatches
\r
205 //====================================================================================
\r
206 RAD_Patch *SubdivideFacePatches(RAD_Patch *Patch)
\r
208 RAD_Patch *CPatch, *NewPatch, *NextPatch;
\r
209 GBSP_Poly *Poly, *FPoly, *BPoly;
\r
212 for (CPatch = Patch; CPatch; CPatch = NextPatch)
\r
214 NextPatch = CPatch->Next;
\r
216 if (PatchNeedsSplit(CPatch, &Plane))
\r
220 Poly = CPatch->Poly;
\r
221 if (!SplitPoly(Poly, &Plane, &FPoly, &BPoly, GE_FALSE))
\r
224 if (!FPoly || !BPoly)
\r
226 GHook.Error("SubdivideFacePatches: Patch was not split.\n");
\r
230 NewPatch = AllocPatch();
\r
233 GHook.Error("SubdivideFacePatches: Out of memory for new patch.\n");
\r
237 *NewPatch = *CPatch; // Make it take on all the attributes of it's parent
\r
239 NewPatch->Next = NextPatch;
\r
240 NewPatch->Poly = FPoly;
\r
241 if (!CalcPatchInfo(NewPatch))
\r
243 GHook.Error("SubdivideFacePatches: Could not calculate patch info.\n");
\r
247 // Re-use the first patch
\r
248 CPatch->Next = NewPatch;
\r
249 CPatch->Poly = BPoly;
\r
251 if (!CalcPatchInfo(CPatch))
\r
253 GHook.Error("SubdivideFacePatches: Could not calculate patch info.\n");
\r
257 NextPatch = CPatch; // Keep working from here till satisfied...
\r
264 //====================================================================================
\r
265 // FinalizePatchInfo
\r
266 //====================================================================================
\r
267 geBoolean FinalizePatchInfo(int32 Face, RAD_Patch *Patch)
\r
272 Poly = Patch->Poly;
\r
276 GHook.Error("FinalizePatchInfo: No Poly!\n");
\r
280 geVec3d_Clear(&Patch->Origin);
\r
282 for (i=0; i< Poly->NumVerts; i++)
\r
284 geVec3d_Add(&Patch->Origin, &Poly->Verts[i], &Patch->Origin);
\r
287 for (i=0; i<3; i++)
\r
288 VectorToSUB(Patch->Origin, i) /= Poly->NumVerts;
\r
290 Patch->Plane.Normal.X = (geFloat)GFXPlanes[GFXFaces[Face].PlaneNum].Normal.X;
\r
291 Patch->Plane.Normal.Y = (geFloat)GFXPlanes[GFXFaces[Face].PlaneNum].Normal.Y;
\r
292 Patch->Plane.Normal.Z = (geFloat)GFXPlanes[GFXFaces[Face].PlaneNum].Normal.Z;
\r
294 Patch->Plane.Dist = (geFloat)GFXPlanes[GFXFaces[Face].PlaneNum].Dist;
\r
295 Patch->Plane.Type = PLANE_ANY;
\r
297 if (GFXFaces[Face].PlaneSide)
\r
299 geVec3d_Inverse(&Patch->Plane.Normal);
\r
300 Patch->Plane.Dist = -Patch->Plane.Dist;
\r
303 geVec3d_AddScaled(&Patch->Origin, &Patch->Plane.Normal, 2.0f, &Patch->Origin);
\r
305 Patch->Leaf = FindGFXLeaf(0, &Patch->Origin);
\r
307 Patch->Area = PolyArea(Patch->Poly);
\r
308 if (Patch->Area < 1.0f)
\r
309 Patch->Area = 1.0f;
\r
311 FreePoly(Patch->Poly); // Don't need this anymore
\r
312 Patch->Poly = NULL;
\r
317 //====================================================================================
\r
319 //====================================================================================
\r
320 geBoolean FinalizePatches(void)
\r
325 for (i=0; i< NumGFXFaces; i++)
\r
326 for (Patch = FacePatches[i]; Patch; Patch = Patch->Next)
\r
328 FinalizePatchInfo(i, Patch);
\r
332 PatchList = GE_RAM_ALLOCATE_ARRAY(pRAD_Patch,NumPatches);
\r
336 GHook.Error("FinalizePatches: Out of memory for patch list.\n");
\r
340 // Build the patch list, so we can use indexing, instead of pointers (for receivers)...
\r
342 for (i=0; i< NumGFXFaces; i++)
\r
343 for (Patch = FacePatches[i]; Patch; Patch = Patch->Next)
\r
345 PatchList[k] = Patch;
\r
352 //====================================================================================
\r
354 //====================================================================================
\r
355 void FreePatches(void)
\r
359 for (i=0; i< NumPatches; i++)
\r
361 geRam_Free(PatchList[i]);
\r
367 geRam_Free(PatchList);
\r
369 geRam_Free(FacePatches);
\r
372 FacePatches = NULL;
\r
376 geFloat BestPatchSize(int32 Face)
\r
381 geFloat Mins, Maxs, Size, BestSize, v;
\r
385 Poly = FacePatches[Face]->Poly;
\r
387 // Special (nonsurface cached) faces don't need subdivision
\r
388 Tex = &TexInfo[GFXFaces[Face].TexInfo];
\r
390 BestSize = -MIN_MAX_BOUNDS2;
\r
392 for (Axis = 0 ; Axis < 2 ; Axis++)
\r
394 Mins = MIN_MAX_BOUNDS;
\r
395 Maxs = -MIN_MAX_BOUNDS;
\r
397 Verts = Poly->Verts;
\r
398 for (i=0 ; i< Poly->NumVerts ; i++)
\r
400 v = geVec3d_DotProduct(&Verts[i], &Tex->Vecs[Axis]);
\r
407 Size = Maxs - Mins;
\r
408 if (Size > BestSize)
\r
412 return (BestSize/4);
\r
415 //====================================================================================
\r
417 //====================================================================================
\r
418 void ApplyLightmapToPatches(int32 Face);
\r
420 geBoolean BuildPatch(int32 Face)
\r
424 //Texture = GFXTexInfo[GFXFaces[Face].TexInfo].Texture;
\r
426 FacePatches[Face] = AllocPatch();
\r
427 if (!FacePatches[Face])
\r
429 GHook.Error("BuildPatch: Could not allocate patch.\n");
\r
433 CalcPatchReflectivity(Face, FacePatches[Face]);
\r
435 FacePatches[Face]->Poly = GBSPPolyFromGFXFace(&GFXFaces[Face]);
\r
437 if (!CalcPatchInfo(FacePatches[Face]))
\r
439 GHook.Error("BuildPatch: Could not calculate patch info.\n");
\r
443 FacePatches[Face] = SubdivideFacePatches(FacePatches[Face]);
\r
445 if (!FacePatches[Face])
\r
447 GHook.Error("BuildPatch: Could not subdivide patch.\n");
\r
454 //====================================================================================
\r
456 //====================================================================================
\r
457 RAD_Patch *AllocPatch(void)
\r
461 Patch = GE_RAM_ALLOCATE_STRUCT(RAD_Patch);
\r
465 GHook.Error("AllocPatch: Not enough memory.\n");
\r
469 memset(Patch, 0, sizeof(RAD_Patch));
\r
474 //====================================================================================
\r
476 //====================================================================================
\r
477 void FreePatch(RAD_Patch *Patch)
\r
481 GHook.Printf("*WARNING* FreePatch: Nothing to free!\n");
\r
486 FreePoly(Patch->Poly);
\r
488 Patch->Poly = NULL;
\r
493 //====================================================================================
\r
495 //====================================================================================
\r
496 RAD_Receiver *AllocReceiver(void)
\r
498 RAD_Receiver *Receiver;
\r
500 Receiver = GE_RAM_ALLOCATE_STRUCT(RAD_Receiver);
\r
504 GHook.Error("AllocReceiver: Not enough memory.\f");
\r
508 memset(Receiver, 0, sizeof(RAD_Receiver));
\r
513 //====================================================================================
\r
515 //====================================================================================
\r
516 void FreeReceiver(RAD_Receiver *Rec)
\r
520 GHook.Printf("*WARNING* FreeReceiver: Nothing to free!\n");
\r
526 //====================================================================================
\r
527 // FindPatchReceivers
\r
528 // PreCalculate who can see who, and how much they emit
\r
529 //====================================================================================
\r
530 geBoolean FindPatchReceivers(RAD_Patch *Patch)
\r
537 geFloat Total, Scale;
\r
539 geVec3d Vect, Normal;
\r
540 RAD_Receiver *Receiver;
\r
544 pLeaf = &GFXLeafs[Patch->Leaf];
\r
545 Cluster = pLeaf->Cluster;
\r
546 Area = pLeaf->Area;
\r
548 if (Cluster >= 0 && GFXClusters[Cluster].VisOfs >= 0)
\r
550 VisData = &GFXVisData[GFXClusters[Cluster].VisOfs];
\r
554 VisInfo = GE_FALSE;
\r
558 Normal = Patch->Plane.Normal;
\r
560 // For each face, go through all it's patches
\r
561 for (i=0; i< NumPatches; i++)
\r
565 GHook.Printf("Cancel requested...\n");
\r
569 Patch2 = PatchList[i];
\r
571 RecAmount[i] = 0.0f;
\r
573 if (Patch2 == Patch)
\r
576 pLeaf = &GFXLeafs[Patch2->Leaf];
\r
578 if (pLeaf->Area != Area) // Radiosity only bounces in it's original area
\r
583 Cluster = pLeaf->Cluster;
\r
584 if (Cluster >= 0 && !(VisData[Cluster>>3] &(1<<(Cluster&7))) )
\r
589 geVec3d_Subtract(&Patch2->Origin, &Patch->Origin, &Vect);
\r
591 Dist = geVec3d_Normalize(&Vect);
\r
593 //if (Dist > PatchSize)
\r
597 Scale = geVec3d_DotProduct(&Vect, &Normal);
\r
598 Scale *= -geVec3d_DotProduct(&Vect, &Patch2->Plane.Normal);
\r
603 if (RayCollision(&Patch->Origin, &Patch2->Origin, NULL))
\r
604 continue; // Blocked by somthing in the world
\r
606 Amount = Scale * Patch2->Area / (Dist*Dist);
\r
608 if (Amount <= 0.0f)
\r
611 RecAmount[i] = Amount;
\r
613 // Add the receiver
\r
616 Patch->NumReceivers++;
\r
619 Patch->Receivers = GE_RAM_ALLOCATE_ARRAY(RAD_Receiver,Patch->NumReceivers);
\r
621 if (!Patch->Receivers)
\r
623 GHook.Error("CalcReceivers: Out of memory for receiver.\n");
\r
627 Receiver = Patch->Receivers;
\r
629 for (i=0; i< NumPatches; i++)
\r
634 Receiver->Patch = (uint16)i;
\r
635 Receiver->Amount = (uint16)(RecAmount[i]*0x10000 / Total);
\r
642 //====================================================================================
\r
644 //====================================================================================
\r
645 geBoolean CalcReceivers(char *FileName)
\r
654 // Try to load the receiver file first!!!
\r
655 if (LoadReceiverFile(FileName))
\r
657 GHook.Printf("--- Found receiver file ---\n");
\r
661 GHook.Printf(" --- Calculating receivers from scratch ---\n");
\r
663 RecAmount = GE_RAM_ALLOCATE_ARRAY(geFloat,NumPatches);
\r
667 GHook.Error("CalcReceivers: Out of memory for RecAmount.\n");
\r
671 Perc = (NumPatches/20);
\r
672 for (i=0; i< NumPatches; i++)
\r
676 if (!(i%Perc) && (i/Perc)<=20)
\r
677 GHook.Printf(".%i", (i/Perc));
\r
680 Patch = PatchList[i];
\r
682 if (!FindPatchReceivers(Patch))
\r
684 GHook.Error("CalcReceivers: There was an error calculating receivers.\n");
\r
688 GHook.Printf("\n");
\r
691 geRam_Free(RecAmount);
\r
694 Megs = (geFloat)NumReceivers * sizeof(RAD_Receiver) / (1024*1024);
\r
695 GHook.Printf("Num Receivers : %5i, Megs %2.2f\n", NumReceivers, Megs);
\r
697 // Save receiver file for later retreival
\r
698 if (!SaveReceiverFile(FileName))
\r
700 GHook.Error("CalcReceivers: Failed to save receiver file...\n");
\r
707 //====================================================================================
\r
709 //====================================================================================
\r
710 void FreeReceivers(void)
\r
717 for (i=0; i< NumPatches; i++)
\r
719 Patch = PatchList[i];
\r
721 if (Patch->NumReceivers)
\r
722 geRam_Free(Patch->Receivers);
\r
724 Patch->Receivers = NULL;
\r
728 //====================================================================================
\r
730 //====================================================================================
\r
731 geBoolean CheckPatch(RAD_Patch *Patch)
\r
735 for (i=0; i<3; i++)
\r
737 if (VectorToSUB(Patch->RadFinal, i) < 0.0f)
\r
739 GHook.Error("CheckPatch: Bad final radiosity Color in patch.\n");
\r
747 //====================================================================================
\r
748 // CollectPatchLight
\r
749 //====================================================================================
\r
750 geFloat CollectPatchLight(void)
\r
758 for (i=0; i< NumPatches; i++)
\r
760 Patch = PatchList[i];
\r
762 for (j=0 ; j<3 ; j++)
\r
764 // Add receive amount to Final amount
\r
765 VectorToSUB(Patch->RadFinal, j) +=
\r
766 VectorToSUB(Patch->RadReceive, j) / Patch->Area;
\r
768 VectorToSUB(Patch->RadSend, j) =
\r
769 VectorToSUB(Patch->RadReceive, j) * VectorToSUB(Patch->Reflectivity, j);
\r
771 Total += VectorToSUB(Patch->RadSend, j);
\r
774 geVec3d_Clear(&Patch->RadReceive);
\r
781 //====================================================================================
\r
783 //====================================================================================
\r
784 void SendPatch(RAD_Patch *Patch)
\r
789 RAD_Receiver *Receiver;
\r
791 for (k=0; k<3; k++)
\r
792 VectorToSUB(Send, k) = VectorToSUB(Patch->RadSend, k) / (geFloat)0x10000;
\r
793 //VectorToSUB(Send, k) = VectorToSUB(Patch->RadSend, k);
\r
795 // Send light out to each pre-computed receiver
\r
796 Receiver = Patch->Receivers;
\r
797 for (k=0; k< Patch->NumReceivers; k++, Receiver++)
\r
799 RPatch = PatchList[Receiver->Patch];
\r
801 geVec3d_AddScaled(&RPatch->RadReceive, &Send, (geFloat)Receiver->Amount, &RPatch->RadReceive);
\r
802 //geVec3d_AddScaled(&RPatch->RadReceive, &Send, .0005f, &RPatch->RadReceive);
\r
806 //====================================================================================
\r
808 //====================================================================================
\r
809 geBoolean BouncePatches(void)
\r
815 GHook.Printf("--- Bounce Patches --- \n");
\r
817 for (i=0 ; i< NumPatches; i++)
\r
819 // Set each patches first pass send amount with what was obtained
\r
820 // from their lightmaps...
\r
821 Patch = PatchList[i];
\r
822 for (j=0 ; j<3 ; j++)
\r
824 VectorToSUB(Patch->RadSend, j) = VectorToSUB(Patch->RadStart, j) *
\r
825 VectorToSUB(Patch->Reflectivity, j) * Patch->Area;
\r
828 //geVec3d_Clear(&Patch->RadFinal);
\r
831 for (i=0 ; i<NumBounce ; i++)
\r
834 GHook.Printf("Bounce: %3i, ", i+1);
\r
838 GHook.Printf("Cancel requested...\n");
\r
842 // For each patch, send it's energy to each pre-computed receiver
\r
843 for (j=0 ; j< NumPatches; j++)
\r
845 Patch = PatchList[j];
\r
849 // For each patch, collect any light it might have received
\r
850 // and throw into patch RadFinal
\r
851 Total = CollectPatchLight();
\r
854 GHook.Printf("Energy: %2.2f\n", Total);
\r
857 for (j=0 ; j< NumPatches; j++)
\r
859 Patch = PatchList[j];
\r
860 if (!CheckPatch(Patch))
\r
867 typedef struct _PlaneFace
\r
871 } PlaneFace, *pPlaneFace;
\r
873 pPlaneFace *PlaneFaces;
\r
875 //====================================================================================
\r
877 //====================================================================================
\r
878 void LinkPlaneFaces(void)
\r
883 PlaneFaces = GE_RAM_ALLOCATE_ARRAY(pPlaneFace,NumGFXPlanes);
\r
885 for (i=0; i< NumGFXPlanes; i++)
\r
886 PlaneFaces[i] = NULL;
\r
888 for (i=0; i< NumGFXFaces; i++)
\r
890 PFace = GE_RAM_ALLOCATE_STRUCT(PlaneFace);
\r
891 memset(PFace, 0, sizeof(PlaneFace));
\r
893 PlaneNum = GFXFaces[i].PlaneNum;
\r
894 PFace->GFXFace = i;
\r
895 PFace->Next = PlaneFaces[PlaneNum];
\r
896 PlaneFaces[PlaneNum] = PFace;
\r
900 //====================================================================================
\r
902 //====================================================================================
\r
903 void FreePlaneFaces(void)
\r
906 PlaneFace *PFace, *Next;
\r
908 for (i=0; i< NumGFXPlanes; i++)
\r
910 for (PFace = PlaneFaces[i]; PFace; PFace = Next)
\r
912 Next = PFace->Next;
\r
917 geRam_Free(PlaneFaces);
\r
920 //====================================================================================
\r
922 //====================================================================================
\r
923 void GetFaceMinsMaxs(int32 Face, geVec3d *Mins, geVec3d *Maxs)
\r
928 ClearBounds(Mins, Maxs);
\r
930 for (i=0; i< GFXFaces[Face].NumVerts; i++)
\r
932 Index = GFXVertIndexList[GFXFaces[Face].FirstVert+i];
\r
933 for (k=0; k<3; k++)
\r
935 t = VectorToSUB(GFXVerts[Index], k);
\r
937 if (t < VectorToSUB(*Mins, k))
\r
938 VectorToSUB(*Mins, k) = t;
\r
939 if (t > VectorToSUB(*Maxs, k))
\r
940 VectorToSUB(*Maxs, k) = t;
\r
945 //====================================================================================
\r
946 // MakeCornerPatches
\r
947 //====================================================================================
\r
948 RAD_Patch *MakeCornerPatches(int32 Face, Tri_Patch *Tri)
\r
950 RAD_Patch *PatchList, *NewPatch;
\r
951 int32 i, Width, Height, u, v;
\r
952 geFloat StartU, StartV, CurU, CurV, EndU, EndV;
\r
958 Fi = &FaceInfo[Face];
\r
959 Li = &Lightmaps[Face];
\r
961 Width = Li->LSize[0]+1;
\r
962 Height = Li->LSize[1]+1;
\r
963 StartU = ((geFloat)Li->LMins[0]) * (geFloat)LGRID_SIZE;
\r
964 StartV = ((geFloat)Li->LMins[1]) * (geFloat)LGRID_SIZE;
\r
966 EndU = ((geFloat)Li->LMaxs[0]) * (geFloat)LGRID_SIZE;
\r
967 EndV = ((geFloat)Li->LMaxs[1]) * (geFloat)LGRID_SIZE;
\r
969 for (v=0; v < 2; v++)
\r
971 for (u=0; u < 2; u++)
\r
983 NewPatch = AllocPatch();
\r
986 GHook.Error("CreateCornerPatches: Could not create patch.\n");
\r
990 for (i=0; i< 3; i++)
\r
991 VectorToSUB(NewPatch->Origin, i) = VectorToSUB(Fi->TexOrg,i) +
\r
992 VectorToSUB(Fi->T2WVecs[0], i) * CurU +
\r
993 VectorToSUB(Fi->T2WVecs[1], i) * CurV;
\r
995 if (!FindClosestTriPoint(&NewPatch->Origin, Tri, &NewPatch->RadFinal))
\r
997 GHook.Error("MakeCornerPatches: Could not find patch for Color.\n");
\r
1001 //NewPatch->RadFinal.X = 0.0f;
\r
1002 //NewPatch->RadFinal.Y = 255.0f;
\r
1003 //NewPatch->RadFinal.Z = 0.0f;
\r
1004 // Insert it into the beginning of the list
\r
1005 NewPatch->Next = PatchList;
\r
1006 PatchList = NewPatch;
\r
1012 //====================================================================================
\r
1014 //====================================================================================
\r
1015 void FreePatchList(RAD_Patch *Patches)
\r
1017 RAD_Patch *Patch, *Next;
\r
1019 for (Patch = Patches; Patch; Patch = Next)
\r
1021 Next = Patch->Next;
\r
1026 //====================================================================================
\r
1028 //====================================================================================
\r
1029 geBoolean AbsorbPatches(void)
\r
1033 geVec3d Add, *pPoint, *pRGB;
\r
1034 int32 i, k, PNum, FNum, PSide;
\r
1035 RAD_Patch *Patch, *OPatch, *TempPatches;
\r
1037 geVec3d FMins, FMaxs;
\r
1039 LinkPlaneFaces(); // We need all the faces that belong to each Plane
\r
1041 for (i=0; i< NumGFXFaces; i++)
\r
1044 GFX_Face *pGFXFace;
\r
1046 pGFXFace = &GFXFaces[i];
\r
1048 if (CancelRequest)
\r
1050 GHook.Printf("Cancel requested...\n");
\r
1054 pPoint = FaceInfo[i].Points;
\r
1055 pRGB = Lightmaps[i].RGBLData[0];
\r
1057 Flags = GFXTexInfo[GFXFaces[i].TexInfo].Flags;
\r
1059 if ((Flags & TEXINFO_NO_LIGHTMAP) && !(Flags & TEXINFO_GOURAUD))
\r
1065 Plane.Normal.X = (geFloat)GFXPlanes[GFXFaces[i].PlaneNum].Normal.X;
\r
1066 Plane.Normal.Y = (geFloat)GFXPlanes[GFXFaces[i].PlaneNum].Normal.Y;
\r
1067 Plane.Normal.Z = (geFloat)GFXPlanes[GFXFaces[i].PlaneNum].Normal.Z;
\r
1068 Plane.Dist = (geFloat)GFXPlanes[GFXFaces[i].PlaneNum].Dist;
\r
1069 Plane.Type = PLANE_ANY;
\r
1071 if(GFXFaces[i].PlaneSide)
\r
1073 geVec3d_Inverse(&Plane.Normal);
\r
1074 Plane.Dist = -Plane.Dist;
\r
1078 Tri = Tri_PatchCreate(&Plane);
\r
1081 GHook.Error("AbsorbPatches: Tri_PatchCreate failed.\n");
\r
1085 PNum = GFXFaces[i].PlaneNum;
\r
1086 PSide = GFXFaces[i].PlaneSide;
\r
1088 OPatch = FacePatches[i];
\r
1090 GetFaceMinsMaxs(i, &FMins, &FMaxs);
\r
1092 for (PFace = PlaneFaces[PNum]; PFace; PFace = PFace->Next)
\r
1094 FNum = PFace->GFXFace;
\r
1096 if (GFXFaces[FNum].PlaneSide != PSide)
\r
1099 for (Patch = FacePatches[FNum]; Patch; Patch = Patch->Next)
\r
1101 for (k=0 ; k < 3 ; k++)
\r
1103 if (VectorToSUB(Patch->Origin, k) < VectorToSUB(FMins,k) - (PatchSize*2))
\r
1106 if (VectorToSUB(Patch->Origin, k) > VectorToSUB(FMaxs,k) + (PatchSize*2))
\r
1112 // Don't include patches that might cross through walls or floors, or
\r
1113 // it will cause false light bleeds from patch to patch
\r
1114 //if (OPatch != Patch)
\r
1115 //if (RayCollision(&OPatch->Origin, &Patch->Origin, NULL))
\r
1118 if (!AddPointToTriangulation (Patch, Tri))
\r
1120 GHook.Error("AbsorbPatches: Could not add patch to triangulation.\n");
\r
1127 TempPatches = NULL;
\r
1129 TempPatches = MakeCornerPatches(i, Tri);
\r
1132 GHook.Error("AbsorbPatches: Could not create corner temp patches.\n");
\r
1136 for (Patch = TempPatches; Patch; Patch = Patch->Next)
\r
1138 if (!AddPointToTriangulation (Patch, Tri))
\r
1140 GHook.Error("AbsorbPatches: Could not add temp patch to triangulation.\n");
\r
1145 if (!TriangulatePoints (Tri))
\r
1147 GHook.Error("AbsorbPatches: Could not triangulate patches.\n");
\r
1151 if (Flags & TEXINFO_GOURAUD)
\r
1153 for (k=0; k< pGFXFace->NumVerts; k++)
\r
1157 vn = pGFXFace->FirstVert+k;
\r
1159 pPoint = &GFXVerts[GFXVertIndexList[vn]];
\r
1161 SampleTriangulation (pPoint, Tri, &Add);
\r
1163 geVec3d_Add(&GFXRGBVerts[vn], &Add, &GFXRGBVerts[vn]);
\r
1169 geBoolean Created = (pRGB != NULL);
\r
1171 for (k=0; k< FaceInfo[i].NumPoints; k++, pRGB++, pPoint++)
\r
1173 if (!SampleTriangulation (pPoint, Tri, &Add))
\r
1175 GHook.Error("AbsorbPatches: Could not sample from patch triangles.\n");
\r
1181 if (Add.X > 0 || Add.Y > 0 || Add.Z > 0)
\r
1183 if (Lightmaps[i].NumLTypes > MAX_LTYPES)
\r
1185 GHook.Error("AbsorbPatches: Too many Light Types on Face.\n");
\r
1189 Lightmaps[i].RGBLData[0] = GE_RAM_ALLOCATE_ARRAY(geVec3d,FaceInfo[i].NumPoints);
\r
1190 if (!Lightmaps[i].RGBLData[0])
\r
1192 GHook.Error("AbsorbPAtches: Out of memory for lightmap.\n");
\r
1195 Lightmaps[i].NumLTypes++;
\r
1196 pRGB = Lightmaps[i].RGBLData[0];
\r
1197 memset(pRGB, 0, FaceInfo[i].NumPoints*sizeof(geVec3d));
\r
1199 Created = GE_TRUE;
\r
1203 geVec3d_Add(pRGB, &Add, pRGB);
\r
1208 Tri_PatchDestroy(Tri);
\r
1209 //FreePatchList(TempPatches);
\r
1210 TempPatches = NULL;
\r
1218 //====================================================================================
\r
1219 // Tri_PatchCreate
\r
1220 //====================================================================================
\r
1221 Tri_Patch *Tri_PatchCreate(GBSP_Plane *Plane)
\r
1225 Patch = GE_RAM_ALLOCATE_STRUCT(Tri_Patch);
\r
1228 GHook.Error("Tri_PatchCreate: Out of memory.\n");
\r
1232 Patch->NumPoints = 0;
\r
1233 Patch->NumEdges = 0;
\r
1234 Patch->NumTris = 0;
\r
1236 Patch->Plane = Plane;
\r
1241 //====================================================================================
\r
1242 // Tri_PatchDestroy
\r
1243 //====================================================================================
\r
1244 void Tri_PatchDestroy(Tri_Patch *tr)
\r
1250 //====================================================================================
\r
1252 //====================================================================================
\r
1253 Tri_Edge *FindEdge(Tri_Patch *TriPatch, int p0, int p1)
\r
1260 if (TriPatch->EdgeMatrix[p0][p1])
\r
1261 return TriPatch->EdgeMatrix[p0][p1];
\r
1263 if (TriPatch->NumEdges > MAX_TRI_EDGES-2)
\r
1265 GHook.Error ("TriPatch->NumEdges > MAX_TRI_EDGES-2");
\r
1269 geVec3d_Subtract (&TriPatch->Points[p1]->Origin, &TriPatch->Points[p0]->Origin, &v1);
\r
1270 geVec3d_Normalize (&v1);
\r
1271 geVec3d_CrossProduct (&v1, &TriPatch->Plane->Normal, &normal);
\r
1272 dist = geVec3d_DotProduct (&TriPatch->Points[p0]->Origin, &normal);
\r
1274 e = &TriPatch->Edges[TriPatch->NumEdges];
\r
1278 geVec3d_Copy(&normal, &e->normal);
\r
1280 TriPatch->NumEdges++;
\r
1281 TriPatch->EdgeMatrix[p0][p1] = e;
\r
1283 // Go ahead and make the reverse edge ahead of time
\r
1284 be = &TriPatch->Edges[TriPatch->NumEdges];
\r
1288 geVec3d_Copy(&normal, &be->normal);
\r
1289 geVec3d_Inverse(&be->normal);
\r
1291 TriPatch->NumEdges++;
\r
1292 TriPatch->EdgeMatrix[p1][p0] = be;
\r
1297 //====================================================================================
\r
1299 //====================================================================================
\r
1300 Tri *AllocTriangle(Tri_Patch *TriPatch)
\r
1304 if (TriPatch->NumTris >= MAX_TRI_TRIS)
\r
1306 GHook.Error ("TriPatch->NumTris >= MAX_TRI_TRIS");
\r
1310 t = &TriPatch->TriList[TriPatch->NumTris];
\r
1311 TriPatch->NumTris++;
\r
1316 //====================================================================================
\r
1318 //====================================================================================
\r
1319 geBoolean Tri_Edge_r (Tri_Patch *TriPatch, Tri_Edge *e)
\r
1323 geVec3d *p0, *p1, *p;
\r
1324 geFloat best, ang;
\r
1331 p0 = &TriPatch->Points[e->p0]->Origin;
\r
1332 p1 = &TriPatch->Points[e->p1]->Origin;
\r
1334 for (i=0 ; i< TriPatch->NumPoints ; i++)
\r
1336 p = &TriPatch->Points[i]->Origin;
\r
1338 if (geVec3d_DotProduct(p, &e->normal) - e->dist < 0)
\r
1341 geVec3d_Subtract(p0, p, &v1);
\r
1342 geVec3d_Subtract(p1, p, &v2);
\r
1344 if (!geVec3d_Normalize(&v1))
\r
1346 if (!geVec3d_Normalize(&v2))
\r
1349 ang = geVec3d_DotProduct (&v1, &v2);
\r
1359 nt = AllocTriangle (TriPatch);
\r
1362 GHook.Error("Tri_Edge_r: Could not allocate triangle.\n");
\r
1366 if (!nt->Edges[0])
\r
1368 GHook.Error("Tri_Edge_r: There was an error finding an edge.\n");
\r
1371 nt->Edges[1] = FindEdge (TriPatch, e->p1, bestp);
\r
1372 if (!nt->Edges[1])
\r
1374 GHook.Error("Tri_Edge_r: There was an error finding an edge.\n");
\r
1377 nt->Edges[2] = FindEdge (TriPatch, bestp, e->p0);
\r
1378 if (!nt->Edges[2])
\r
1380 GHook.Error("Tri_Edge_r: There was an error finding an edge.\n");
\r
1383 for (i=0 ; i<3 ; i++)
\r
1384 nt->Edges[i]->tri = nt;
\r
1386 e2 = FindEdge (TriPatch, bestp, e->p1);
\r
1389 GHook.Error("Tri_Edge_r: There was an error finding an edge.\n");
\r
1392 if (!Tri_Edge_r (TriPatch, e2))
\r
1395 e2 = FindEdge (TriPatch, e->p0, bestp);
\r
1398 GHook.Error("Tri_Edge_r: There was an error finding an edge.\n");
\r
1401 if (!Tri_Edge_r (TriPatch, e2))
\r
1407 //====================================================================================
\r
1408 // TriangulatePoints
\r
1409 //====================================================================================
\r
1410 geBoolean TriangulatePoints(Tri_Patch *TriPatch)
\r
1414 int bp1, bp2, i, j;
\r
1418 for (i=0; i<TriPatch->NumPoints; i++)
\r
1419 memset(TriPatch->EdgeMatrix[i], 0, TriPatch->NumPoints*sizeof(TriPatch->EdgeMatrix[0][0]) );
\r
1421 if (TriPatch->NumPoints < 2)
\r
1424 // Find the two closest Points
\r
1425 bestd = MIN_MAX_BOUNDS2;
\r
1426 for (i=0 ; i<TriPatch->NumPoints ; i++)
\r
1428 p1 = &TriPatch->Points[i]->Origin;
\r
1429 for (j=i+1 ; j<TriPatch->NumPoints ; j++)
\r
1431 p2 = &TriPatch->Points[j]->Origin;
\r
1432 geVec3d_Subtract(p2, p1, &v1);
\r
1433 d = geVec3d_Length(&v1);
\r
1434 if (d < bestd && d > .05f)
\r
1443 e = FindEdge (TriPatch, bp1, bp2);
\r
1446 GHook.Error("There was an error finding an edge.\n");
\r
1449 e2 = FindEdge (TriPatch, bp2, bp1);
\r
1452 GHook.Error("There was an error finding an edge.\n");
\r
1455 if (!Tri_Edge_r (TriPatch, e))
\r
1457 if (!Tri_Edge_r (TriPatch, e2))
\r
1463 //====================================================================================
\r
1464 // AddPointToTriangulation
\r
1465 //====================================================================================
\r
1466 geBoolean AddPointToTriangulation(RAD_Patch *patch, Tri_Patch *TriPatch)
\r
1470 pnum = TriPatch->NumPoints;
\r
1471 if (pnum == MAX_TRI_POINTS)
\r
1473 GHook.Error ("TriPatch->NumPoints == MAX_TRI_POINTS");
\r
1476 TriPatch->Points[pnum] = patch;
\r
1477 TriPatch->NumPoints++;
\r
1482 //====================================================================================
\r
1484 //====================================================================================
\r
1485 void LerpTriangle(Tri_Patch *TriPatch, Tri *t, geVec3d *Point, geVec3d *Color)
\r
1487 RAD_Patch *p1, *p2, *p3;
\r
1488 geVec3d base, d1, d2;
\r
1489 geFloat x, y, x1, y1, x2, y2;
\r
1491 p1 = TriPatch->Points[t->Edges[0]->p0];
\r
1492 p2 = TriPatch->Points[t->Edges[1]->p0];
\r
1493 p3 = TriPatch->Points[t->Edges[2]->p0];
\r
1495 geVec3d_Copy(&p1->RadFinal, &base);
\r
1496 geVec3d_Subtract(&p2->RadFinal, &base, &d1);
\r
1497 geVec3d_Subtract(&p3->RadFinal, &base, &d2);
\r
1499 x = geVec3d_DotProduct(Point, &t->Edges[0]->normal) - t->Edges[0]->dist;
\r
1500 y = geVec3d_DotProduct(Point, &t->Edges[2]->normal) - t->Edges[2]->dist;
\r
1503 y1 = geVec3d_DotProduct(&p2->Origin, &t->Edges[2]->normal) - t->Edges[2]->dist;
\r
1505 x2 = geVec3d_DotProduct(&p3->Origin, &t->Edges[0]->normal) - t->Edges[0]->dist;
\r
1508 if (fabs(y1)<ON_EPSILON || fabs(x2)<ON_EPSILON)
\r
1510 geVec3d_Copy(&base, Color);
\r
1514 geVec3d_AddScaled(&base, &d2, x/x2, Color);
\r
1515 geVec3d_AddScaled(Color, &d1, y/y1, Color);
\r
1518 //====================================================================================
\r
1519 // Tri_PointInside
\r
1520 //====================================================================================
\r
1521 geBoolean Tri_PointInside(Tri *Tri, geVec3d *Point)
\r
1525 for (i=0 ; i<3 ; i++)
\r
1530 pEdge = Tri->Edges[i];
\r
1532 Dist = geVec3d_DotProduct(&pEdge->normal, Point) - pEdge->dist;
\r
1541 //====================================================================================
\r
1542 // SampleTriangulation
\r
1543 //====================================================================================
\r
1544 geBoolean SampleTriangulation(geVec3d *Point, Tri_Patch *TriPatch, geVec3d *Color)
\r
1549 RAD_Patch *p0, *p1;
\r
1553 if (TriPatch->NumPoints == 0)
\r
1555 geVec3d_Clear(Color);
\r
1558 if (TriPatch->NumPoints == 1)
\r
1560 geVec3d_Copy(&TriPatch->Points[0]->RadFinal, Color);
\r
1564 // See of the Point is inside a tri in the patch
\r
1565 for (t = TriPatch->TriList, j=0 ; j < TriPatch->NumTris ; t++, j++)
\r
1567 if (!Tri_PointInside (t, Point))
\r
1570 LerpTriangle (TriPatch, t, Point, Color);
\r
1575 for (e=TriPatch->Edges, j=0 ; j< TriPatch->NumEdges ; e++, j++)
\r
1578 continue; // not an exterior edge
\r
1580 d = geVec3d_DotProduct(Point, &e->normal) - e->dist;
\r
1582 continue; // not in front of edge
\r
1584 p0 = TriPatch->Points[e->p0];
\r
1585 p1 = TriPatch->Points[e->p1];
\r
1587 geVec3d_Subtract(&p1->Origin, &p0->Origin, &v1);
\r
1588 geVec3d_Normalize (&v1);
\r
1590 geVec3d_Subtract(Point, &p0->Origin, &v2);
\r
1591 d = geVec3d_DotProduct(&v2, &v1);
\r
1598 for (i=0 ; i<3 ; i++)
\r
1599 VectorToSUB(*Color,i) = VectorToSUB(p0->RadFinal,i) + d * (VectorToSUB(p1->RadFinal,i) - VectorToSUB(p0->RadFinal,i));
\r
1603 if (!FindClosestTriPoint(Point, TriPatch, Color))
\r
1605 GHook.Error("SampleTriangulation: Could not find closest Color.\n");
\r
1612 //========================================================================================
\r
1613 // FindClosestTriPoint
\r
1614 //========================================================================================
\r
1615 geBoolean FindClosestTriPoint(geVec3d *Point, Tri_Patch *Tri, geVec3d *Color)
\r
1618 RAD_Patch *p0, *BestPatch;
\r
1619 geFloat BestDist, d;
\r
1622 // Search for nearest Point
\r
1623 BestDist = MIN_MAX_BOUNDS2;
\r
1626 for (i=0 ; i<Tri->NumPoints ; i++)
\r
1628 p0 = Tri->Points[i];
\r
1629 geVec3d_Subtract(Point, &p0->Origin, &v1);
\r
1630 d = geVec3d_Length(&v1);
\r
1640 GHook.Error ("FindClosestTriPoint: No Points.\n");
\r
1644 geVec3d_Copy(&BestPatch->RadFinal, Color);
\r
1649 //========================================================================================
\r
1650 // CalcPatchReflectivity
\r
1651 //========================================================================================
\r
1652 void CalcPatchReflectivity(int32 Face, RAD_Patch *Patch)
\r
1654 GFX_Texture *pTexture;
\r
1657 uint8 *pGFXTexData;
\r
1658 DRV_Palette *Palette;
\r
1659 GFX_TexInfo *pTexInfo;
\r
1662 pTexInfo = &GFXTexInfo[GFXFaces[Face].TexInfo];
\r
1663 pTexture = &GFXTextures[pTexInfo->Texture];
\r
1665 geVec3d_Clear(&Color);
\r
1667 pGFXTexData = &GFXTexData[pTexture->Offset];
\r
1668 Size = pTexture->Width*pTexture->Height;
\r
1670 Palette = &GFXPalettes[pTexture->PaletteIndex];
\r
1672 for (i=0; i< Size; i++, pGFXTexData++)
\r
1676 RGB = &(*Palette)[*pGFXTexData];
\r
1677 Color.X += (geFloat)RGB->r;
\r
1678 Color.Y += (geFloat)RGB->g;
\r
1679 Color.Z += (geFloat)RGB->b;
\r
1682 geVec3d_Scale(&Color, 1.0f/(geFloat)Size, &Color);
\r
1683 geVec3d_Scale(&Color, 1.0f/255.0f, &Color);
\r
1685 Scale = ColorNormalize(&Color, &Patch->Reflectivity);
\r
1690 geVec3d_Scale(&Patch->Reflectivity, Scale, &Patch->Reflectivity);
\r
1693 geVec3d_Scale(&Patch->Reflectivity, ReflectiveScale*pTexInfo->ReflectiveScale, &Patch->Reflectivity);
\r
1699 SYSTEMTIME BSPTime;
\r
1703 Rec_Header RecHeader;
\r
1705 //========================================================================================
\r
1706 // SaveReceiverFile
\r
1707 //========================================================================================
\r
1708 geBoolean SaveReceiverFile(char *FileName)
\r
1713 GHook.Printf("--- Save Receiver File --- \n");
\r
1715 f = fopen(FileName, "wb");
\r
1719 GHook.Error("SaveReceiverFile: Could not open receiver file for writing...\n");
\r
1723 RecHeader.Version = GBSPHeader.Version;
\r
1724 RecHeader.BSPTime = GBSPHeader.BSPTime;
\r
1725 RecHeader.NumPatches = NumPatches;
\r
1728 if (fwrite(&RecHeader, sizeof(Rec_Header), 1, f) != 1)
\r
1730 GHook.Printf("*WARNING* SaveReceiverFile: Could not save header info...\n");
\r
1736 for (i=0; i< NumPatches; i++)
\r
1738 int32 NumReceivers = PatchList[i]->NumReceivers;
\r
1740 // Write out the number of receivers for this patch
\r
1741 if (fwrite(&NumReceivers, sizeof(int32), 1, f) != 1)
\r
1743 GHook.Printf("*WARNING* SaveReceiverFile: Could not save num receivers...\n");
\r
1748 // Write out the actual receivers
\r
1749 if (fwrite(PatchList[i]->Receivers, sizeof(RAD_Receiver), NumReceivers, f) != (uint32)NumReceivers)
\r
1751 GHook.Printf("*WARNING* SaveReceiverFile: Could not save receivers...\n");
\r
1760 //========================================================================================
\r
1761 // LoadReceiverFile
\r
1762 //========================================================================================
\r
1763 geBoolean LoadReceiverFile(char *FileName)
\r
1767 uint8 *pTime1, *pTime2;
\r
1769 f = fopen(FileName, "rb");
\r
1775 if (fread(&RecHeader, sizeof(Rec_Header), 1, f) != 1)
\r
1777 GHook.Error("LoadReceiverFile: Could not load header info...\n");
\r
1782 pTime1 = (uint8*)&(RecHeader.BSPTime);
\r
1783 pTime2 = (uint8*)&(GBSPHeader.BSPTime);
\r
1785 if (RecHeader.Version != GBSPHeader.Version)
\r
1787 GHook.Printf("*WARNING* LoadReceiverFile: Versions do not match, skipping...\n");
\r
1792 // Make sure internal time matches...
\r
1793 for (i=0; i< sizeof(SYSTEMTIME); i++)
\r
1795 if (*pTime1 != *pTime2)
\r
1805 // Make sure the number of patches in the receiver file, matches the number loaded
\r
1807 if (RecHeader.NumPatches != NumPatches)
\r
1809 GHook.Printf("*WARNING* LoadReceiverFile: NumPatches do not match, skipping...\n");
\r
1814 // Load Patche receivers
\r
1815 for (i=0; i< NumPatches; i++)
\r
1817 int32 NumReceivers;
\r
1819 // Load the number of receivers for this patch
\r
1820 if (fread(&NumReceivers, sizeof(int32), 1, f) != 1)
\r
1822 GHook.Error("LoadReceiverFile: Could not load num receivers...\n");
\r
1827 PatchList[i]->Receivers = GE_RAM_ALLOCATE_ARRAY(RAD_Receiver,NumReceivers);
\r
1829 if (!PatchList[i]->Receivers)
\r
1831 GHook.Error("LoadReceiverFile: Out of memory for receivers.\n");
\r
1836 PatchList[i]->NumReceivers = (uint16)NumReceivers;
\r
1838 // Load the actual receivers
\r
1839 if (fread(PatchList[i]->Receivers, sizeof(RAD_Receiver), NumReceivers, f) != (uint32)NumReceivers)
\r
1841 GHook.Error("SaveReceiverFile: Could not save receivers...\n");
\r