root/cafu/trunk/CaPVS/CaPVSold.cpp

Revision 455, 17.3 KB (checked in by Carsten, 4 months ago)

Updated copyright banners (in C++ files).

Line 
1/*
2=================================================================================
3This file is part of Cafu, the open-source game engine and graphics engine
4for multiplayer, cross-platform, real-time 3D action.
5Copyright (C) 2002-2012 Carsten Fuchs Software.
6
7Cafu is free software: you can redistribute it and/or modify it under the terms
8of the GNU General Public License as published by the Free Software Foundation,
9either version 3 of the License, or (at your option) any later version.
10
11Cafu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
12without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
13PURPOSE. See the GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with Cafu. If not, see <http://www.gnu.org/licenses/>.
17
18For support and more information about Cafu, visit us at <http://www.cafu.de>.
19=================================================================================
20*/
21
22/***************************************/
23/***                                 ***/
24/*** Cafu Potentially Visibility Set ***/
25/***                                 ***/
26/***************************************/
27
28#include <time.h>
29#include <stdio.h>
30
31#include "CaPVSWorld.hpp"
32#include "Win32/Win32Console.hpp"
33
34
35const time_t          ProgramStartTime=time(NULL);
36Win32ConsoleT         Win32Console;
37ArrayT<SuperLeafT>    SuperLeaves;
38ArrayT<unsigned long> SuperLeavesPVS;
39unsigned long         MasterSuperLeafNr;
40
41
42// This function determines the adjacency cell graph of the 'SuperLeaves'.
43// It is filled-in and stored in the 'Neighbours' members of the 'SuperLeaves'.
44void DetermineAdjacencyGraph()
45{
46    Win32Console.FunctionID("SuperLeaves Adjacency Graph");
47
48    for (unsigned long SL1Nr=0; SL1Nr<SuperLeaves.Size(); SL1Nr++)
49        for (unsigned long SL2Nr=0; SL2Nr<SuperLeaves.Size(); SL2Nr++)
50        {
51            if (SL1Nr==SL2Nr || !BoundingBoxTest(SuperLeaves[SL1Nr].BB, SuperLeaves[SL2Nr].BB)) continue;
52
53            // Jedes Portal des ersten Leafs gegen jedes Portal des zweiten Leafs
54            // prüfen und testen, ob sie sich schneiden (dann sind sie durchlässig).
55            // BEACHTE: SuperLeaves haben *alle* Portale ihrer Leaves!
56            // D.h. es können sich nicht nur Portale auf ihrer konvexen Hülle befinden, sondern auch "innen drin".
57            // Wegen der Konvexität der SuperLeaves dürfte das aber keine Rolle spielen und folgendes müßte trotzdem korrekt funktionieren.
58            for (unsigned long Portal1Nr=0; Portal1Nr<SuperLeaves[SL1Nr].Portals.Size(); Portal1Nr++)
59                for (unsigned long Portal2Nr=0; Portal2Nr<SuperLeaves[SL2Nr].Portals.Size(); Portal2Nr++)
60                {
61                    if (!PolygonOverlap(SuperLeaves[SL1Nr].Portals[Portal1Nr], SuperLeaves[SL2Nr].Portals[Portal2Nr])) continue;
62
63                    // Folgender Check ist zwar redundant, aber zumindest sinnvoll.
64                    // Da diese Funktion sowieso schnell genug ist, lasse ihn nicht weg!
65                    if (PolygonWhatSide(SuperLeaves[SL1Nr].Portals[Portal1Nr], SuperLeaves[SL2Nr].Portals[Portal2Nr].Plane)!=-3) continue;
66
67                    ArrayT<PolygonT> NewPortals;
68                    PolygonChopUp(SuperLeaves[SL2Nr].Portals[Portal2Nr], SuperLeaves[SL1Nr].Portals[Portal1Nr], NewPortals);
69
70                    SuperLeaves[SL1Nr].Neighbours.PushBackEmpty();
71                    SuperLeaves[SL1Nr].Neighbours[SuperLeaves[SL1Nr].Neighbours.Size()-1].SuperLeafNr=SL2Nr;
72                    SuperLeaves[SL1Nr].Neighbours[SuperLeaves[SL1Nr].Neighbours.Size()-1].SubPortal  =NewPortals[NewPortals.Size()-1];
73                }
74        }
75
76    printf("done.\n");
77}
78
79
80// Markiere das SuperLeaf 'SLNr' als von 'MasterSuperLeafNr' aus sichtbar.
81inline void FlagVisible(unsigned long SLNr)
82{
83    const unsigned long PVSTotalBitNr=MasterSuperLeafNr*SuperLeaves.Size()+SLNr;
84    const unsigned long PVS_W32_Nr   =PVSTotalBitNr >> 5;
85    const unsigned long PVSBitMask   =1 << (PVSTotalBitNr & 31);
86
87    SuperLeavesPVS[PVS_W32_Nr]|=PVSBitMask;
88}
89
90
91// Bestimme die Sichtpyramide (Frustum), die eine Lichtquelle (LightSource) durch ein Loch (Hole) wirft.
92// Dabei leuchtet die Lichtquelle in die entgegengesetzte(!!) Richtung ihres Normalenvektors, und das Loch
93// läßt auch nur Licht in der Gegenrichtung seines Normalenvektors durch. Würde man das Loch zur Lichtquelle
94// und umgekehrt machen (PolygonMirror), wäre das Frustum das gleiche, die Ebenen wären aber gespiegelt!
95// Voraussetzung: Die Ebene der Lichtquelle schneidet nicht das Loch und umgekehrt. Dieser Fall wird nicht
96// abgedeckt bzw. ist gar nicht durchdacht. Da die Leaves konvex sind usw. wird dies aber immer eingehalten!
97inline void FindFrustum(const PolygonT& LightSource, const PolygonT& Hole, ArrayT<PlaneT>& Frustum)
98{
99    Frustum.Clear();   // Frustum.ClearForOverwrite();
100
101    unsigned long V2=Hole.Vertices.Size()-1;
102    for(unsigned long V3=0; V3<Hole.Vertices.Size(); V3++)
103    {
104        for (unsigned long V1=0; V1<LightSource.Vertices.Size(); V1++)
105        {
106            // Eigentlich würde ich hier gerne folgenden Wunsch-Code schreiben:
107            //     try
108            //     {
109            //         PlaneT FrustumPlane(Hole.Vertices[V2], LightSource.Vertices[V1], Hole.Vertices[V3]);
110            //
111            //         // ...
112            //     }
113            //     catch (DivisionByZero) { }  // Nicht mögliche FrustumPlanes einfach ignorieren.
114            // Aus irgendeinem Grund ist die Verwendung oder das Fangen der DivisionByZero-Exception aber sehr langsam.
115            // Deshalb rolle ich lieber den PlaneT-Konstruktor aus, um ohne dieses Exception-Handling auszukommen.
116            // Das Programm wird *deutlich* schneller, ca. Faktor 1,5. Ob das eine Schwäche des Watcom-Compilers ist??
117            VectorT Normal(VectorCross(Hole.Vertices[V3]-Hole.Vertices[V2], LightSource.Vertices[V1]-Hole.Vertices[V2]));
118            double  NLength=VectorLength(Normal);
119
120            if (NLength<0.1) continue;
121            Normal=VectorScale(Normal, 1.0/NLength);
122
123            PlaneT FrustumPlane(Normal, VectorDot(Hole.Vertices[V2], Normal));
124
125            // Diese neue FrustumPlane nur dann akzeptieren, wenn das Hole auf ihrer Vorderseite liegt
126            // (konstruktionsbedingt sowieso der Fall!) und die LightSource auf ihrer Rückseite liegt.
127            // Wenn eine Edge des Hole in der Ebene der LightSource liegt, darf die LightSource
128            // auch in der FrustumPlane liegen.
129            if (PolygonWhatSide(LightSource, FrustumPlane)<0)
130            {
131                Frustum.PushBack(FrustumPlane);
132                break;
133            }
134        }
135
136        V2=V3;
137    }
138
139    // Rollen vertauschen: Das Loch sei nun die Lichtquelle, und die Lichtquelle das Loch! Siehe Skizze!
140    V2=LightSource.Vertices.Size()-1;
141    for(V3=0; V3<LightSource.Vertices.Size(); V3++)
142    {
143        for (unsigned long V1=0; V1<Hole.Vertices.Size(); V1++) // Optimize: Check if edges are in already existing frustum planes!
144        {
145            // Eigentlich würde ich hier gerne folgenden Wunsch-Code schreiben:
146            //     try
147            //     {
148            //         PlaneT FrustumPlane(LightSource.Vertices[V2], Hole.Vertices[V1], LightSource.Vertices[V3]);
149            //
150            //         // ...
151            //     }
152            //     catch (DivisionByZero) { }  // Nicht mögliche Ebenen einfach ignorieren.
153            // Aus irgendeinem Grund ist die Verwendung oder das Fangen der DivisionByZero-Exception aber sehr langsam.
154            // Deshalb rolle ich lieber den PlaneT-Konstruktor aus, um ohne dieses Exception-Handling auszukommen.
155            // Das Programm wird *deutlich* schneller, ca. Faktor 1,5. Ob das eine Schwäche des Watcom-Compilers ist??
156            VectorT Normal(VectorCross(LightSource.Vertices[V3]-LightSource.Vertices[V2], Hole.Vertices[V1]-LightSource.Vertices[V2]));
157            double  NLength=VectorLength(Normal);
158
159            if (NLength<0.1) continue;
160            Normal=VectorScale(Normal, 1.0/NLength);
161
162            PlaneT FrustumPlane(Normal, VectorDot(LightSource.Vertices[V2], Normal));
163
164            // Diese neue FrustumPlane nur dann akzeptieren, wenn die LightSource auf ihrer Rückseite
165            // liegt (konstruktionsbedingt sowieso der Fall!) und das Hole auf ihrer Vorderseite liegt.
166            // Wenn eine Edge der LightSource in der Ebene des Holes liegt, darf das Hole
167            // auch in der FrustumPlane liegen.
168            const signed char Side=PolygonWhatSide(Hole, FrustumPlane);    // Wegen dem Rollentausch ist die Orientierung für diesen Test falsch, ...
169
170            if (Side==1 || Side==-3)
171            {
172                Frustum.PushBack(FrustumPlane);                           // ...im Gesamten aber wieder richtig!
173                break;
174            }
175        }
176
177        V2=V3;
178    }
179}
180
181
182// Betrete das SuperLeaf 'SLNr' durch das 'EnteringPortal' des vorhergehenden SuperLeafs.
183void FindVisibleLeaves(unsigned long SLNr, const PolygonT& MasterPortal, const PolygonT& EnteringPortal)
184{
185    FlagVisible(SLNr);
186
187    // Das Frustum MasterPortal --> EnteringPortal bestimmen.
188    ArrayT<PlaneT> Frustum;
189    FindFrustum(MasterPortal, EnteringPortal, Frustum);
190
191    for (unsigned long NeighbourNr=0; NeighbourNr<SuperLeaves[SLNr].Neighbours.Size(); NeighbourNr++)
192    {
193        PolygonT NextPortal=SuperLeaves[SLNr].Neighbours[NeighbourNr].SubPortal;
194
195        // Abkürzung: Gleiche Ebene? Dann weiter mit dem nächsten Portal!
196        if (abs(PolygonWhatSide(NextPortal, EnteringPortal.Plane))==3) continue;
197
198        // Clippe 'NextPortal' gegen das Frustum MasterPortal --> EnteringPortal.
199        static PolygonT ClipPortal;
200
201        for (unsigned long FrustumNr=0; FrustumNr<Frustum.Size(); FrustumNr++)
202        {
203            const signed char Side=PolygonWhatSide(NextPortal, Frustum[FrustumNr]);
204
205                 if (Side==2) PolygonSplit(NextPortal, Frustum[FrustumNr], NextPortal, ClipPortal);
206            else if (Side!=1) break;
207        }
208        if (FrustumNr<Frustum.Size()) continue;
209
210        // Clippe 'MasterPortal' gegen das Frustum NextPortal --> EnteringPortal.
211        // Um die Portale nicht spiegeln zu müssen, das gespiegelte Frustum benutzen!
212        ArrayT<PlaneT> Frustum2;
213        FindFrustum(EnteringPortal, NextPortal, Frustum2);
214
215        PolygonT MP=MasterPortal;
216
217        for (FrustumNr=0; FrustumNr<Frustum2.Size(); FrustumNr++)
218        {
219            const signed char Side=PolygonWhatSide(MP, Frustum2[FrustumNr]);
220
221                 if (Side== 2) PolygonSplit(MP, Frustum2[FrustumNr], ClipPortal, MP);
222            else if (Side!=-1) break;
223        }
224        if (FrustumNr<Frustum2.Size()) continue;
225
226        FindVisibleLeaves(SuperLeaves[SLNr].Neighbours[NeighbourNr].SuperLeafNr, MP, NextPortal);
227    }
228}
229
230
231void BuildPVS()
232{
233    Win32Console.FunctionID("Potentially Visibility Set");
234
235    // Für jedes SuperLeaf das PVS bestimmen.
236    for (MasterSuperLeafNr=0; MasterSuperLeafNr<SuperLeaves.Size(); MasterSuperLeafNr++)
237    {
238        Win32Console.RefreshStatusLine((double)MasterSuperLeafNr/SuperLeaves.Size());
239
240        FlagVisible(MasterSuperLeafNr);
241
242        // Für den alten Algorithmus war hier vermerkt, daß "outer leaves" keine Neighbours/Portals haben,
243        // wegen der Eigenschaften von Portalize() und FillInside() des CaBSP Programms.
244        // Der neue Algorithmus verwendet SuperLeaves, bei denen überhaupt nicht zwischen "inner" und "outer" unterschieden wird.
245        // Alles was zählt, sind die Portale. Deshalb setzen sich SuperLeaves korrekt aus beliebigen Leaves eines Sub-Trees zusammen.
246        for (unsigned long NeighbourNr=0; NeighbourNr<SuperLeaves[MasterSuperLeafNr].Neighbours.Size(); NeighbourNr++)
247        {
248            const unsigned long NeighbourSLNr=SuperLeaves[MasterSuperLeafNr].Neighbours[NeighbourNr].SuperLeafNr;
249            const PolygonT&     MasterPortal =SuperLeaves[MasterSuperLeafNr].Neighbours[NeighbourNr].SubPortal;
250
251            // Der unmittelbare Nachbar ist auf jeden Fall sichtbar.
252            FlagVisible(NeighbourSLNr);
253
254            for (unsigned long NNr=0; NNr<SuperLeaves[NeighbourSLNr].Neighbours.Size(); NNr++)
255            {
256                const unsigned long NeighboursNeighbourSLNr=SuperLeaves[NeighbourSLNr].Neighbours[NNr].SuperLeafNr;
257                const PolygonT&     EnteringPortal         =SuperLeaves[NeighbourSLNr].Neighbours[NNr].SubPortal;
258
259                if (abs(PolygonWhatSide(MasterPortal, EnteringPortal.Plane))==3) continue;
260
261                FindVisibleLeaves(NeighboursNeighbourSLNr, MasterPortal, EnteringPortal);
262            }
263        }
264    }
265
266    printf("SuperLeavesPVS      :       done\n");
267}
268
269
270void WriteLogFileEntry(const char* WorldPathName, unsigned long CheckSum)
271{
272    FILE* LogFile=fopen("CaPVS.log", "a");
273
274    if (LogFile)
275    {
276        SYSTEMTIME    SystemTime;
277        char          WorldName[_MAX_FNAME];
278        char          ComputerName[MAX_COMPUTERNAME_LENGTH+1]="unknown";
279        unsigned long LengthCN  =MAX_COMPUTERNAME_LENGTH+1;
280        unsigned long RunningSec=(unsigned long)difftime(time(NULL), ProgramStartTime);
281
282        GetLocalTime(&SystemTime);
283        GetComputerName(ComputerName, &LengthCN);
284        _splitpath(WorldPathName, NULL, NULL, WorldName, NULL);
285
286        // Date, Time, WorldName, TimeForCompletion on [ComputerName]
287        fprintf(LogFile, "%02u.%02u.%02u %2u:%02u %-16s%3u:%02u:%02u [%-16s] %10u\n",
288            SystemTime.wDay, SystemTime.wMonth, SystemTime.wYear % 100, SystemTime.wHour, SystemTime.wMinute,
289            WorldName, RunningSec/3600, (RunningSec/60) % 60, RunningSec % 60, ComputerName, CheckSum);
290        fclose(LogFile);
291    }
292}
293
294
295void Usage()
296{
297    printf("\n");
298    printf("USAGE: CaPVS Path/WorldName.cw [OPTIONS]\n");
299    printf("\n");
300    printf("OPTIONS:\n");
301    printf("-maxRecDepthSL n : Leaves that are stored in the BSP tree and have a greater\n");
302    printf("                   depth than this value get combined in a common SuperLeaf\n");
303    printf("                   that is created at this depth value.\n");
304    printf("-minAreaSL a     : BSP sub-trees whose non-detail faces have a common area of\n");
305    printf("                   less than this value get combined in a SuperLeaf.\n");
306    printf("-onlySLs         : Do not compute the PVS, just print out how many SuperLeaves\n");
307    printf("                   would be created. Used for estimating the above parameter\n");
308    printf("                   values (speed vs. quality).\n");
309    printf("\n");
310
311    exit(1);
312}
313
314
315int main(int ArgC, const char* ArgV[])
316{
317    unsigned long MaxRecDepthSL  =0xFFFFFFFF;
318    double        MinAreaSL      =0.0;
319    bool          OnlySuperLeaves=false;
320
321    Win32Console.InitScreen("Cafu Potentially Visibility Set Utility, Version 04", __DATE__);
322
323    if (ArgC<2) Usage();
324
325    for (int ArgNr=2; ArgNr<ArgC; ArgNr++)
326    {
327        if (!stricmp(ArgV[ArgNr], "-maxRecDepthSL"))
328        {
329            ArgNr++;
330            if (ArgNr>=ArgC) Usage();
331
332            MaxRecDepthSL=atoi(ArgV[ArgNr]);
333            printf("maxRecDepthSL == %u\n", MaxRecDepthSL);
334        }
335        else if (!stricmp(ArgV[ArgNr], "-minAreaSL"))
336        {
337            ArgNr++;
338            if (ArgNr>=ArgC) Usage();
339
340            MinAreaSL=atof(ArgV[ArgNr]);
341            if (MinAreaSL<0.0) MinAreaSL=0.0;
342            printf("minAreaSL     == %.3f\n", MinAreaSL);
343        }
344        else if (!stricmp(ArgV[ArgNr], "-onlySLs"))
345        {
346            OnlySuperLeaves=true;
347        }
348        else
349        {
350            printf("Unknown option '%s'.\n", ArgV[ArgNr]);
351            Usage();
352        }
353    }
354
355    try
356    {
357        printf("Load World %s\n", ArgV[1]);
358
359        CaPVSWorldT* CaPVSWorld=new CaPVSWorldT(ArgV[1], MaxRecDepthSL, MinAreaSL);
360
361        // Robustness against the 'sharp wedge' problem is granted:
362        // DetermineAdjacencyGraph() is the only place where PolygonOverlap() is called, but only once on *input* portals.
363        // Everywhere else (especially in BuildPVS() and its sub-functions), only PolygonSplit() and PolygonWhatSide() are called.
364        CaPVSWorld->CreateSuperLeaves(SuperLeaves);
365        if (OnlySuperLeaves) return 0;
366        DetermineAdjacencyGraph();
367
368        // SuperLeavesPVS erzeugen und zurücksetzen (völlige Blindheit).
369        SuperLeavesPVS.PushBackEmpty((SuperLeaves.Size()*SuperLeaves.Size()+31)/32);
370        for (unsigned long Vis=0; Vis<SuperLeavesPVS.Size(); Vis++) SuperLeavesPVS[Vis]=0;
371
372        // Berechne das PVS der SuperLeaves.
373        BuildPVS();
374
375        // 'SuperLeavesPVS' ins PVS der 'CaPVSWorld' übertragen.
376        CaPVSWorld->StorePVS(SuperLeaves, SuperLeavesPVS);
377
378        // Drucke Statistiken aus und erhalte eine Prüfsumme zurück.
379        unsigned long CheckSum=CaPVSWorld->GetChecksumAndPrintStats();
380
381        // Speichere die World zurück auf Disk.
382        Win32Console.FunctionID("Save World %s", ArgV[1]);
383        CaPVSWorld->SaveToDisk(ArgV[1]);
384
385        delete CaPVSWorld;
386        WriteLogFileEntry(ArgV[1], CheckSum);
387        printf("COMPLETED.\n");
388    }
389    catch (const WorldT::LoadErrorT& E) { Win32Console.Error(E.Msg); }
390    catch (const WorldT::SaveErrorT& E) { Win32Console.Error(E.Msg); }
391
392    return 0;
393}
Note: See TracBrowser for help on using the browser.