root/cafu/trunk/CaPVS/CaPVS.cpp

Revision 455, 43.7 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/*** To iterate is human.            ***/
27/*** To recurse, divine.             ***/
28/***   -- L. Peter Deutsch           ***/
29/***                                 ***/
30/*** Denn die einen sind im Dunkeln  ***/
31/*** und die andern sind im Licht    ***/
32/*** und man siehet die im Lichte    ***/
33/*** die im Dunkeln sieht man nicht. ***/
34/***   -- Bertolt Brecht             ***/
35/***                                 ***/
36/***************************************/
37
38#ifdef _WIN32
39#define WIN32_LEAN_AND_MEAN
40#include <windows.h>
41#else
42#include <stdarg.h>
43#include <unistd.h>
44#include <string.h>
45#define _stricmp strcasecmp
46#endif
47
48#include <time.h>
49#include <stdio.h>
50
51#include "CaPVSWorld.hpp"
52#include "ConsoleCommands/Console.hpp"
53#include "ConsoleCommands/ConsoleInterpreter.hpp"
54#include "ConsoleCommands/ConsoleStdout.hpp"
55#include "FileSys/FileManImpl.hpp"
56#include "MaterialSystem/MaterialManager.hpp"
57#include "MaterialSystem/MaterialManagerImpl.hpp"
58#include "Models/ModelManager.hpp"
59#include "ClipSys/CollisionModelMan_impl.hpp"
60
61
62static cf::ConsoleStdoutT ConsoleStdout;
63cf::ConsoleI* Console=&ConsoleStdout;
64
65static cf::FileSys::FileManImplT FileManImpl;
66cf::FileSys::FileManI* cf::FileSys::FileMan=&FileManImpl;
67
68static cf::ClipSys::CollModelManImplT CCM;
69cf::ClipSys::CollModelManI* cf::ClipSys::CollModelMan=&CCM;
70
71ConsoleInterpreterI* ConsoleInterpreter=NULL;
72MaterialManagerI*    MaterialManager   =NULL;
73
74
75const time_t ProgramStartTime=time(NULL);
76
77// Returns a string with the elapsed time since program start.
78// The string is in the format "hh:mm:ss".
79static const char* GetTimeSinceProgramStart()
80{
81    const unsigned long TotalSec=(unsigned long)difftime(time(NULL), ProgramStartTime);
82    const unsigned long Sec     =TotalSec % 60;
83    const unsigned long Min     =(TotalSec/60) % 60;
84    const unsigned long Hour    =TotalSec/3600;
85
86    static char TimeString[16];
87    sprintf(TimeString, "%2lu:%2lu:%2lu", Hour, Min, Sec);
88
89    return TimeString;
90}
91
92
93ArrayT<SuperLeafT>              SuperLeaves;
94ArrayT< BoundingBox3T<double> > SuperLeavesBBs;
95ArrayT<unsigned long>           SuperLeavesPVS;
96
97
98// Records in 'SuperLeavesPVS' that we can see from 'FromSL' to 'ToSL'.
99inline void FlagVisible(unsigned long FromSL, unsigned long ToSL)
100{
101    const unsigned long PVSTotalBitNr=FromSL*SuperLeaves.Size()+ToSL;
102    const unsigned long PVS_W32_Nr   =PVSTotalBitNr >> 5;
103    const unsigned long PVSBitMask   =1 << (PVSTotalBitNr & 31);
104
105    SuperLeavesPVS[PVS_W32_Nr]|=PVSBitMask;
106}
107
108
109// Returns 'true' if the 'SuperLeavesPVS' has a record that we can see from 'FromSL' to 'ToSL', 'false' otherwise.
110inline bool IsVisible(unsigned long FromSL, unsigned long ToSL)
111{
112    const unsigned long PVSTotalBitNr=FromSL*SuperLeaves.Size()+ToSL;
113
114    return bool((SuperLeavesPVS[PVSTotalBitNr >> 5] >> (PVSTotalBitNr & 31)) & 1);
115}
116
117
118// Returns how many other SuperLeaves each SuperLeaf can see in average.
119// The lower bound is 1.0 (each SuperLeaf can only see itself).
120// The upper bound is the number of SuperLeaves (each SuperLeaf can see all other SuperLeaves, including itself).
121double GetAverageVisibility()
122{
123    unsigned long VisCount=0;
124
125    for (unsigned long SL1=0; SL1<SuperLeaves.Size(); SL1++)
126        for (unsigned long SL2=0; SL2<SuperLeaves.Size(); SL2++)
127            if (IsVisible(SL1, SL2))
128                VisCount++;
129
130    return double(VisCount)/SuperLeaves.Size();
131}
132
133
134// This function determines the adjacency cell graph of the 'SuperLeaves'.
135// It is filled-in and stored in the 'Neighbours' members/components of the 'SuperLeaves'.
136// After this function was called, all components of all 'SuperLeaves' are completely filled-in.
137void DetermineAdjacencyGraph()
138{
139    for (unsigned long SL1Nr=0; SL1Nr<SuperLeaves.Size(); SL1Nr++)
140        for (unsigned long SL2Nr=0; SL2Nr<SuperLeaves.Size(); SL2Nr++)
141        {
142            if (SL1Nr==SL2Nr || !SuperLeaves[SL1Nr].BB.GetEpsilonBox(MapT::RoundEpsilon).Intersects(SuperLeaves[SL2Nr].BB)) continue;
143
144            // Jedes Portal des ersten Leafs gegen jedes Portal des zweiten Leafs
145            // prüfen und testen, ob sie sich schneiden (dann sind sie durchlässig).
146            // BEACHTE: SuperLeaves haben *alle* Portale von *allen* ihrer Leaves!
147            // D.h. es können sich nicht nur Portale auf ihrer konvexen Hülle befinden, sondern auch "innen drin".
148            // Wegen der Konvexität der SuperLeaves dürfte das aber keine Rolle spielen und folgendes müßte trotzdem korrekt funktionieren.
149            for (unsigned long Portal1Nr=0; Portal1Nr<SuperLeaves[SL1Nr].Portals.Size(); Portal1Nr++)
150                for (unsigned long Portal2Nr=0; Portal2Nr<SuperLeaves[SL2Nr].Portals.Size(); Portal2Nr++)
151                {
152                    if (!SuperLeaves[SL1Nr].Portals[Portal1Nr].Overlaps(SuperLeaves[SL2Nr].Portals[Portal2Nr], false, MapT::RoundEpsilon)) continue;
153
154                    // Folgender Check ist zwar redundant, aber zumindest sinnvoll.
155                    // Da diese Funktion sowieso schnell genug ist, lasse ihn nicht weg!
156                    if (SuperLeaves[SL1Nr].Portals[Portal1Nr].WhatSide(SuperLeaves[SL2Nr].Portals[Portal2Nr].Plane, MapT::RoundEpsilon)!=Polygon3T<double>::InMirrored) continue;
157
158                    ArrayT< Polygon3T<double> > NewPortals;
159                    SuperLeaves[SL1Nr].Portals[Portal1Nr].GetChoppedUpAlong(SuperLeaves[SL2Nr].Portals[Portal2Nr], MapT::RoundEpsilon, NewPortals);
160
161                    SuperLeaves[SL1Nr].Neighbours.PushBackEmpty();
162                    SuperLeaves[SL1Nr].Neighbours[SuperLeaves[SL1Nr].Neighbours.Size()-1].SuperLeafNr=SL2Nr;
163                    SuperLeaves[SL1Nr].Neighbours[SuperLeaves[SL1Nr].Neighbours.Size()-1].SubPortal  =NewPortals[NewPortals.Size()-1];
164                }
165        }
166
167    printf("SLs Adjacency Graph :       done\n");
168}
169
170
171// This function computes for each SuperLeaf the bounding box over its 'SubPortals'.
172// Such bounding boxes are useful when a SuperLeaf is considered in its role as a "target" SuperLeaf.
173// The results are stored in the 'SuperLeavesBBs', which is assumed to be empty before this function is called.
174// SuperLeaves that have no (sub-)portals (and thus, no neighbours), get the default bounding box assigned.
175void ComputeSuperLeavesBBs()
176{
177    for (unsigned long SL=0; SL<SuperLeaves.Size(); SL++)
178    {
179        SuperLeavesBBs.PushBackEmpty();
180
181        if (SuperLeaves[SL].Neighbours.Size()==0) continue;
182
183        SuperLeavesBBs[SL]=BoundingBox3T<double>(SuperLeaves[SL].Neighbours[0].SubPortal.Vertices);
184
185        for (unsigned long NeighbourNr=1; NeighbourNr<SuperLeaves[SL].Neighbours.Size(); NeighbourNr++)
186            SuperLeavesBBs[SL].Insert(SuperLeaves[SL].Neighbours[NeighbourNr].SubPortal.Vertices);
187    }
188
189    printf("SLs Bounding Boxes  :       done\n");
190}
191
192
193// Determines the trivial visibility (recorded in the 'SuperLeavesPVS').
194// That is, each SuperLeaf can see itself as well as its immediate neighbours.
195void DetermineTrivialVisibility()
196{
197    for (unsigned long SL=0; SL<SuperLeaves.Size(); SL++)
198    {
199        // Sich selbst als sichtbar markieren.
200        FlagVisible(SL, SL);
201
202        // Und die unmittelbaren Nachbarn als sichtbar markieren.
203        for (unsigned long NeighbourNr=0; NeighbourNr<SuperLeaves[SL].Neighbours.Size(); NeighbourNr++)
204        {
205            // This is the only place where we ever omit flagging the mutual "vice-versa" visibility:
206            // FlagVisible(SuperLeaves[SL].Neighbours[NeighbourNr].SuperLeafNr, SL);
207            // It's just not needed: The 'SuperLeavesPVS' matrix *must* be symmetric anyway.
208            FlagVisible(SL, SuperLeaves[SL].Neighbours[NeighbourNr].SuperLeafNr);
209        }
210    }
211
212    printf("Trivial Visibility  : %10.5f\n", GetAverageVisibility());
213}
214
215
216// Determines a simple pre-PVS by sampling "random" rays (and stores it in 'SuperLeavesPVS').
217// The result is a subset of the analytically determined, conservative, "exact" PVS.
218// This function does not handle the fact that SuperLeaves can see themselves.
219// Thus, calling 'DetermineTrivialVisibility()' before (or after) calling this function is required.
220void DetermineRayPresampledVisibility(const CaPVSWorldT* CaPVSWorld)
221{
222    ArrayT< ArrayT<VectorT> > SuperLeavesCenters;
223    ArrayT< ArrayT<bool   > > SuperLeavesCenterIsValid;
224
225    for (unsigned long SL=0; SL<SuperLeaves.Size(); SL++)
226    {
227        SuperLeavesCenters      .PushBackEmpty();
228        SuperLeavesCenterIsValid.PushBackEmpty();
229
230        for (unsigned long NbNr=0; NbNr<SuperLeaves[SL].Neighbours.Size(); NbNr++)
231        {
232            const Polygon3T<double>& SubPortal=SuperLeaves[SL].Neighbours[NbNr].SubPortal;
233            VectorT                  Center   =SubPortal.Vertices[0];
234
235            for (unsigned long VertexNr=1; VertexNr<SubPortal.Vertices.Size(); VertexNr++) Center=Center+SubPortal.Vertices[VertexNr];
236            Center=scale(Center, 1.0/double(SubPortal.Vertices.Size()))+scale(SubPortal.Plane.Normal, 0.2);
237
238            // Not really necessary, but add a little check to see if everything is in order.
239            // (Or, more precisely, assert that the 'Center' is really "inside" the SuperLeaf.)
240            const unsigned long LeafNr=CaPVSWorld->WhatLeaf(Center);
241            unsigned long LeafSetNr;
242
243            for (LeafSetNr=0; LeafSetNr<SuperLeaves[SL].LeafSet.Size(); LeafSetNr++)
244                if (LeafNr==SuperLeaves[SL].LeafSet[LeafSetNr]) break;
245
246            SuperLeavesCenters      [SL].PushBack(Center);
247            SuperLeavesCenterIsValid[SL].PushBack(LeafSetNr<SuperLeaves[SL].LeafSet.Size());
248
249            // if (LeafSetNr>=SuperLeaves[SL1].LeafSet.Size()) printf("WARNING: Center not in its SuperLeaf. TODO: Print more extensive diagnostics.\n");
250        }
251    }
252
253    for (unsigned long SL1=0; SL1+1<SuperLeaves.Size(); SL1++)
254    {
255        printf("%5.1f%%\r", (double)SL1/SuperLeaves.Size()*100.0);
256        fflush(stdout);
257
258        for (unsigned long SL2=SL1+1; SL2<SuperLeaves.Size(); SL2++)
259        {
260            if (IsVisible(SL1, SL2)) continue;
261
262            for (unsigned long Nb1Nr=0; Nb1Nr<SuperLeaves[SL1].Neighbours.Size(); Nb1Nr++)
263            {
264                if (!SuperLeavesCenterIsValid[SL1][Nb1Nr]) continue;
265
266                for (unsigned long Nb2Nr=0; Nb2Nr<SuperLeaves[SL2].Neighbours.Size(); Nb2Nr++)
267                {
268                    if (!SuperLeavesCenterIsValid[SL2][Nb2Nr]) continue;
269
270                    const VectorT& Center1=SuperLeavesCenters[SL1][Nb1Nr];
271                    const VectorT& Center2=SuperLeavesCenters[SL2][Nb2Nr];
272
273                    // This ClipLine() only considers faces, but not BPs, terrains, etc.
274                    if (CaPVSWorld->ClipLine(Center1, Center2-Center1)==1.0)
275                    {
276                        FlagVisible(SL1, SL2);
277                        FlagVisible(SL2, SL1);
278                        goto DoneWithSL2;
279                    }
280                }
281            }
282
283            DoneWithSL2:;
284        }
285    }
286
287    printf("Estimated PVS       : %10.5f\n", GetAverageVisibility());
288}
289
290
291// Diese Funktion bestimmt die Sichtpyramide ('Frustum'), die eine Lichtquelle 'LightSource' durch ein Loch 'Hole' wirft.
292//
293// Dabei leuchtet die 'LightSource' in die *entgegengesetzte* Richtung ihres Normalenvektors,
294// und das 'Hole' läßt auch nur Licht in der Gegenrichtung seines Normalenvektors durch.
295// Beides ist sinnvoll, denn sowohl 'LightSource' als auch 'Hole' sind idR Portale von Leaves.
296// Beachte, daß die Normalenvektoren von Portalen stets zur *Mitte* ihrer Leaves hin zeigen (nicht nach außen wie bei Brushes).
297// Die 'LightSource' ist dann ein Portal des vorangegangenen Leafs, durch das das aktuelle Leaf betreten wird.
298// Das 'Hole' ist ein Portal des aktuellen Leafs zum nächsten Leaf.
299// Bemerkung: Würde man das Loch zur Lichtquelle und umgekehrt machen (PolygonMirror),
300// wäre das Frustum das gleiche, dessen Ebenen wären aber gespiegelt!
301//
302// Es wird vorausgesetzt, daß 'LightSource' und 'Hole' gültige Polygone sind.
303// Wenn daraus erfolgreich ein Frustum konstruiert werden kann, wird dieses zurückgegeben und es gilt 'Frustum.Size()>0'.
304// Andernfalls scheitert die Funktion und es wird ein Frustum der Größe 0 zurückgegeben ('Frustum.Size()==0').
305// Die Voraussetzung für den Erfolg dieser Funktion ist eine - in unserem Sinne - "vernünftige" Anordnung der beiden Polygone:
306// a) Das 'Hole' muß komplett auf der (Licht emittierenden) Rückseite der 'LightSource'-Ebene liegen.
307// b) Die 'LightSource' muß komplett auf der (lichtdurchlässigen) Vorderseite der 'Hole'-Ebene liegen.
308// Beachte, daß im allgemeinen bzw. erweiterten Sinne andere Frustren durchaus auch sinnvoll sein können,
309// z.B. wenn die 'LightSource' und das 'Hole' sich schneiden.
310// Solche Fälle betrachten wir jedoch als ungültig und sie führen zum Scheitern der Funktion.
311inline void FindFrustum(const Polygon3T<double>& LightSource, const Polygon3T<double>& Hole, ArrayT< Plane3T<double> >& Frustum)
312{
313    Frustum.Overwrite();
314
315    if (Hole.WhatSideSimple(LightSource.Plane, MapT::RoundEpsilon)!=Polygon3T<double>::Back) return;
316    if (LightSource.WhatSideSimple(Hole.Plane, MapT::RoundEpsilon)!=Polygon3T<double>::Front) return;
317
318    unsigned long V2=Hole.Vertices.Size()-1;
319    unsigned long V3;
320
321    for (V3=0; V3<Hole.Vertices.Size(); V3++)
322    {
323        for (unsigned long V1=0; V1<LightSource.Vertices.Size(); V1++)
324        {
325            // Eigentlich würde ich hier gerne folgenden Wunsch-Code schreiben:
326            //     try
327            //     {
328            //         Plane3T<double> FrustumPlane(Hole.Vertices[V2], LightSource.Vertices[V1], Hole.Vertices[V3]);
329            //
330            //         // ...
331            //     }
332            //     catch (DivisionByZero) { }  // Nicht mögliche FrustumPlanes einfach ignorieren.
333            // Aus irgendeinem Grund ist die Verwendung oder das Fangen der DivisionByZero-Exception aber sehr langsam.
334            // Deshalb rolle ich lieber den Plane3T<double>-Konstruktor aus, um ohne dieses Exception-Handling auszukommen.
335            // Das Programm wird *deutlich* schneller, ca. Faktor 1,5. Ob das eine Schwäche des Watcom-Compilers ist??
336            VectorT Normal(cross(Hole.Vertices[V3]-Hole.Vertices[V2], LightSource.Vertices[V1]-Hole.Vertices[V2]));
337            double  NLength=length(Normal);
338
339            if (NLength<MapT::RoundEpsilon) continue;
340            Normal=scale(Normal, 1.0/NLength);
341
342            Plane3T<double> FrustumPlane(Normal, dot(Hole.Vertices[V2], Normal));
343
344            // Diese neue FrustumPlane nur dann akzeptieren, wenn das Hole auf ihrer Vorderseite liegt
345            // (konstruktionsbedingt sowieso der Fall!) und die LightSource auf ihrer Rückseite liegt.
346            // Wenn eine Edge des Hole in der Ebene der LightSource liegt, darf die LightSource
347            // auch in der FrustumPlane liegen.
348            Polygon3T<double>::SideT Side=LightSource.WhatSideSimple(FrustumPlane, MapT::RoundEpsilon);
349
350            if (Side==Polygon3T<double>::Back || Side==Polygon3T<double>::InMirrored)
351            {
352                Frustum.PushBack(FrustumPlane);
353                break;
354            }
355        }
356
357        V2=V3;
358    }
359
360    // Rollen vertauschen: Das Loch sei nun die Lichtquelle, und die Lichtquelle das Loch! Siehe Skizze!
361    V2=LightSource.Vertices.Size()-1;
362
363    for (V3=0; V3<LightSource.Vertices.Size(); V3++)
364    {
365        for (unsigned long V1=0; V1<Hole.Vertices.Size(); V1++) // Optimize: Check if edges are in already existing frustum planes!
366        {
367            // Es bringt übrigens nichts, doppelt auftretende Planes hier vermeiden zu wollen!
368            // Messungen waren z.B. 1:09:05 ohne Prüfung, 1:08:42 mit Prüfung auf Doppelvorkommen.
369            // Könnte man aber später nochmal überprüfen...
370         /* // Prüfe, ob wir diese Plane schon im Frustum haben.
371            // Teste dazu, ob die drei Punkte in der Plane liegen.
372            // Die Orientierung braucht dabei nicht beachtet zu werden.
373            for (unsigned long FrustumNr=0; FrustumNr<FrustumSize1stPart; FrustumNr++)
374            {
375                const double Dist1=PlaneDistance(Frustum[FrustumNr],        Hole.Vertices[V1]);
376                const double Dist2=PlaneDistance(Frustum[FrustumNr], LightSource.Vertices[V2]);
377                const double Dist3=PlaneDistance(Frustum[FrustumNr], LightSource.Vertices[V3]);
378
379                if (fabs(Dist1)<0.1 && fabs(Dist2)<0.1 && fabs(Dist3)<0.1) break;
380            }
381            if (FrustumNr<FrustumSize1stPart) continue; */
382
383            // Eigentlich würde ich hier gerne folgenden Wunsch-Code schreiben:
384            //     try
385            //     {
386            //         Plane3T<double> FrustumPlane(LightSource.Vertices[V2], Hole.Vertices[V1], LightSource.Vertices[V3]);
387            //
388            //         // ...
389            //     }
390            //     catch (DivisionByZero) { }  // Nicht mögliche Ebenen einfach ignorieren.
391            // Aus irgendeinem Grund ist die Verwendung oder das Fangen der DivisionByZero-Exception aber sehr langsam.
392            // Deshalb rolle ich lieber den Plane3T<double>-Konstruktor aus, um ohne dieses Exception-Handling auszukommen.
393            // Das Programm wird *deutlich* schneller, ca. Faktor 1,5. Ob das eine Schwäche des Watcom-Compilers ist??
394            VectorT Normal(cross(LightSource.Vertices[V3]-LightSource.Vertices[V2], Hole.Vertices[V1]-LightSource.Vertices[V2]));
395            double  NLength=length(Normal);
396
397            if (NLength<MapT::RoundEpsilon) continue;
398            Normal=scale(Normal, 1.0/NLength);
399
400            Plane3T<double> FrustumPlane(Normal, dot(LightSource.Vertices[V2], Normal));
401
402            // Diese neue FrustumPlane nur dann akzeptieren, wenn die LightSource auf ihrer Rückseite
403            // liegt (konstruktionsbedingt sowieso der Fall!) und das Hole auf ihrer Vorderseite liegt.
404            // Wenn eine Edge der LightSource in der Ebene des Holes liegt, darf das Hole
405            // auch in der FrustumPlane liegen.
406            Polygon3T<double>::SideT Side=Hole.WhatSideSimple(FrustumPlane, MapT::RoundEpsilon);
407
408            // Wegen dem Rollentausch ist die Orientierung für den WhatSideSimple() Test falsch, ...
409            if (Side==Polygon3T<double>::Front || Side==Polygon3T<double>::InMirrored)
410            {
411                Frustum.PushBack(FrustumPlane);   // ...im Gesamten aber wieder richtig!
412                break;
413            }
414        }
415
416        V2=V3;
417    }
418}
419
420
421// Enter the SuperLeaf 'CurrentSL' through the 'EnteringPortal' of the *preceding* SuperLeaf.
422// The ancestors of 'CurrentSL' are recorded in 'AncestorSLs'. For the remaining parameters, see the implementation.
423// Returns 'true' if visibility from the 'MasterSL' to the 'TargetSL' could be established, false otherwise.
424bool DetermineVisibility(unsigned long CurrentSL, const Polygon3T<double>& EnteringPortal, ArrayT<unsigned long> AncestorSLs, const Polygon3T<double>& MasterPortal, const unsigned long TargetSL, BoundingBox3T<double> TargetBB)
425{
426    // Record that we can see from all ancestors into 'CurrentSL', and vice-versa.
427    for (unsigned long AncestorNr=0; AncestorNr<AncestorSLs.Size(); AncestorNr++)
428    {
429        FlagVisible(AncestorSLs[AncestorNr], CurrentSL);
430        FlagVisible(CurrentSL, AncestorSLs[AncestorNr]);
431    }
432
433    // If we reached the desired target, return success.
434    if (CurrentSL==TargetSL) return true;
435
436    // Clip 'TargetBB' against the plane of the 'EnteringPortal'. This is done to prevent the following kind of problem:
437    // Consider a 'TargetSL' that is "thin" and diagonal. As a consequence, it will have a much too large 'TargetBB'.
438    // If we then clip the 'TargetBB' against the frustum "MasterPortal --> EnteringPortal" below,
439    // we might find that it is still (partially) inside this frustum.
440    // However, these parts of the 'TargetBB' inside the frustum might be the much-too-large parts that arose from the special form of the 'TargetSL'.
441    // In other words, had we done a better test instead (e.g. the "exact" one, by clipping the actual sub-portals of 'TargetSL' against the frustum),
442    // we had found that the frustum actually missed the 'TargetSL' (that is, 'TargetSL' is entirely somewhere outside of the frustum).
443    // In the former case, we'll find ourselves trying to find a visibility for something that's long "behind" us, and doing so at *exponential* costs!
444    // In the latter case, we can skip all this from here.
445    // Besides the "exact" test (which is also quite slow), we can also clip the 'TargetBB' against the plane of the 'EnteringPortal'.
446    // The latter approach is conceptually a little different, but achieves the same positive net effect at lesser costs.
447    switch (TargetBB.GetEpsilonBox(-MapT::RoundEpsilon).WhatSide(EnteringPortal.Plane))
448    {
449        case BoundingBox3T<double>::Front:
450            return false;
451
452        case BoundingBox3T<double>::Back:
453            // Do nothing.
454            break;
455
456        case BoundingBox3T<double>::Both:
457            // Note the /2 in MapT::RoundEpsilon/2, this should make sure that GetSplits() doesn't reach another conclusion than WhatSide(),
458            // i.e. it prevents that GetSplits() cannot find a proper sub-box on *both* sides of the plane.
459            TargetBB=TargetBB.GetSplits(EnteringPortal.Plane, MapT::RoundEpsilon/2)[1];
460            break;
461    }
462
463    // Determine the frustum "MasterPortal --> EnteringPortal".
464    ArrayT< Plane3T<double> > Frustum;    // Note that 'Frustum' cannot be declared as 'static', as this function recurses!
465    FindFrustum(MasterPortal, EnteringPortal, Frustum);
466
467    if (Frustum.Size()==0)
468    {
469        // printf("\nWARNING: Invalid frustum 1.\n");   // Should never happen.
470        return false;
471    }
472
473    // Clip 'TargetBB' against the frustum "MasterPortal --> EnteringPortal".
474    unsigned long FrustumNr;
475
476    for (FrustumNr=0; FrustumNr<Frustum.Size(); FrustumNr++)
477    {
478        switch (TargetBB.GetEpsilonBox(-MapT::RoundEpsilon).WhatSide(Frustum[FrustumNr]))
479        {
480            case BoundingBox3T<double>::Front:
481                // Do nothing.
482                break;
483
484            case BoundingBox3T<double>::Back:
485                return false;
486
487            case BoundingBox3T<double>::Both:
488                // Note the /2 in MapT::RoundEpsilon/2, this should make sure that GetSplits() doesn't reach another conclusion than WhatSide(),
489                // i.e. it prevents that GetSplits() cannot find a proper sub-box on *both* sides of the plane.
490                TargetBB=TargetBB.GetSplits(Frustum[FrustumNr], MapT::RoundEpsilon/2)[0];
491                break;
492        }
493    }
494
495    // Bevor wir in das nächste SuperLeaf gehen, nimm das gegenwärtige 'CurrentSL' in die Liste der Vorgänger auf.
496    AncestorSLs.PushBack(CurrentSL);
497
498    for (unsigned long NeighbourNr=0; NeighbourNr<SuperLeaves[CurrentSL].Neighbours.Size(); NeighbourNr++)
499    {
500        const unsigned long NextSL    =SuperLeaves[CurrentSL].Neighbours[NeighbourNr].SuperLeafNr;
501        Polygon3T<double>   NextPortal=SuperLeaves[CurrentSL].Neighbours[NeighbourNr].SubPortal;
502
503        // Wenn für das 'NextSL' schon festgestellt wurde, daß das 'TargetSL' von dort aus nicht zu sehen ist, können wir direkt weitermachen.
504        if (NextSL<AncestorSLs[0]/*MasterSL*/ && !IsVisible(NextSL, TargetSL)) continue;
505
506        // Abkürzung: Gleiche Ebene? Dann weiter mit dem nächsten Portal!
507        Polygon3T<double>::SideT Side=NextPortal.WhatSide(EnteringPortal.Plane, MapT::RoundEpsilon);
508
509        if (Side==Polygon3T<double>::InIdentical || Side==Polygon3T<double>::InMirrored) continue;
510
511        // Clippe 'NextPortal' gegen das Frustum "MasterPortal --> EnteringPortal".
512        for (FrustumNr=0; FrustumNr<Frustum.Size(); FrustumNr++)
513        {
514            Polygon3T<double>::SideT Side=NextPortal.WhatSideSimple(Frustum[FrustumNr], MapT::RoundEpsilon);
515
516                 if (Side==Polygon3T<double>::Both ) NextPortal=NextPortal.GetSplits(Frustum[FrustumNr], MapT::RoundEpsilon)[0];
517            else if (Side!=Polygon3T<double>::Front) break;
518        }
519        if (FrustumNr<Frustum.Size()) continue;
520
521        // Bestimme das Frustum "EnteringPortal --> NextPortal".
522        static ArrayT< Plane3T<double> > Frustum2;
523        FindFrustum(EnteringPortal, NextPortal, Frustum2);
524
525        if (Frustum2.Size()==0)
526        {
527            // printf("\nWARNING: Invalid frustum 2.\n");   // Should never happen.
528            continue;
529        }
530
531        // Clippe 'MasterPortal' gegen das Frustum "NextPortal --> EnteringPortal".
532        // Um die Portale nicht spiegeln zu müssen, das gespiegelte Frustum benutzen!
533        Polygon3T<double> MP=MasterPortal;
534
535        for (FrustumNr=0; FrustumNr<Frustum2.Size(); FrustumNr++)
536        {
537            Polygon3T<double>::SideT Side=MP.WhatSideSimple(Frustum2[FrustumNr], MapT::RoundEpsilon);
538
539                 if (Side==Polygon3T<double>::Both) MP=MP.GetSplits(Frustum2[FrustumNr], MapT::RoundEpsilon)[1];
540            else if (Side!=Polygon3T<double>::Back) break;
541        }
542        if (FrustumNr<Frustum2.Size()) continue;
543
544        if (DetermineVisibility(NextSL, NextPortal, AncestorSLs, MP, TargetSL, TargetBB)) return true;
545    }
546
547    return false;
548}
549
550
551// This functions determines if we can see from 'MasterSL' to 'TargetSL', that is, if 'TargetSL' is visible from 'MasterSL'.
552// ATTENTION 1: IT IS ASSUMED THAT THE PVS FOR ALL SUPERLEAVES IN RANGE '0..MasterSL-1' HAS ALREADY BEEN ESTABLISHED!
553// ATTENTION 2: THIS FUNCTION DOES NOT WORK IF 'MasterSL==TargetSL', OR 'TargetSL' IS AN IMMEDIATE NEIGHBOUR OF 'MasterSL'!
554// If mutual visibility could be established, the result is recorded in 'SuperLeavesPVS', for both "from A to B" and "from B to A".
555bool CanSeeFromAToB(unsigned long MasterSL, unsigned long TargetSL)
556{
557    ArrayT<unsigned long> AncestorSLs;
558    AncestorSLs.PushBackEmpty(2);
559
560    // Für den alten Algorithmus war hier vermerkt, daß "outer leaves" keine Neighbours/Portals haben,
561    // wegen der Eigenschaften von Portalize() und FillInside() des CaBSP Programms.
562    // Der neue Algorithmus verwendet SuperLeaves, bei denen überhaupt nicht zwischen "inner" und "outer" unterschieden wird.
563    // Alles was zählt, sind die Portale. Deshalb setzen sich SuperLeaves korrekt aus beliebigen Leaves eines Sub-Trees zusammen.
564    for (unsigned long NeighbourNr=0; NeighbourNr<SuperLeaves[MasterSL].Neighbours.Size(); NeighbourNr++)
565    {
566        const unsigned long      NeighbourSL =SuperLeaves[MasterSL].Neighbours[NeighbourNr].SuperLeafNr;
567        const Polygon3T<double>& MasterPortal=SuperLeaves[MasterSL].Neighbours[NeighbourNr].SubPortal;
568
569        // Wenn für das 'NeighbourSL' schon festgestellt wurde, daß das 'TargetSL' von dort aus nicht zu sehen ist,
570        // sparen wir uns die Mühe, es trotzdem zu versuchen, und machen direkt weiter.
571        if (NeighbourSL<MasterSL && !IsVisible(NeighbourSL, TargetSL)) continue;
572
573        AncestorSLs[0]=MasterSL;
574        AncestorSLs[1]=NeighbourSL;
575
576        for (unsigned long NNNr=0; NNNr<SuperLeaves[NeighbourSL].Neighbours.Size(); NNNr++)
577        {
578            const unsigned long      NeighboursNeighbourSL=SuperLeaves[NeighbourSL].Neighbours[NNNr].SuperLeafNr;
579            const Polygon3T<double>& EnteringPortal       =SuperLeaves[NeighbourSL].Neighbours[NNNr].SubPortal;
580
581            if (NeighboursNeighbourSL==MasterSL) continue;
582
583            const Polygon3T<double>::SideT Side=MasterPortal.WhatSide(EnteringPortal.Plane, MapT::RoundEpsilon);
584
585            if (Side==Polygon3T<double>::InIdentical || Side==Polygon3T<double>::InMirrored) continue;
586
587            // Wenn für das 'NeighboursNeighbourSL' schon festgestellt wurde, daß das 'TargetSL' von dort aus nicht zu sehen ist,
588            // sparen wir uns die Mühe, es trotzdem zu versuchen, und machen direkt weiter.
589            if (NeighboursNeighbourSL<MasterSL && !IsVisible(NeighboursNeighbourSL, TargetSL)) continue;
590
591            // Das TOLLE: Sobald wir festgestellt haben, daß "irgendeine" Sichtbarkeit von 'MasterSL' nach 'TargetSL' existiert,
592            // können wir sofort AUFHÖREN(!!!) und mit dem nächsten SuperLeaves-Paar weitermachen!
593            if (DetermineVisibility(NeighboursNeighbourSL, EnteringPortal, AncestorSLs, MasterPortal, TargetSL, SuperLeavesBBs[TargetSL])) return true;
594        }
595    }
596
597    return false;
598}
599
600
601// Analytically computes the 'SuperLeavesPVS'.
602// A prior call to 'DetermineTrivialVisibility()' is assumed.
603void BuildPVS()
604{
605    /* // Der komplette alte, aber einfache Code. Evtl. nützlich für Debugging-Zwecke.
606    for (unsigned long MasterSL=0; MasterSL+1<SuperLeaves.Size(); MasterSL++)
607    {
608        printf("%5.1f%%\r", (double)MasterSL/SuperLeaves.Size()*100.0);
609        fflush(stdout);
610
611        // Bestimme für jedes SuperLeaves-Paar, ob eine gegenseitige Sichtbarkeit besteht.
612        // Beachte, daß die folgende Schleife bei 'MasterSL+1' starten kann (statt bei '0'), da Sichtbarkeit immer wechselseitig ist.
613        for (unsigned long TargetSL=MasterSL+1; TargetSL<SuperLeaves.Size(); TargetSL++)
614        {
615            // Wenn die Sichtbarkeit zwischen 'MasterSL' und 'TargetSL' bereits festgestellt wurde,
616            // können wir direkt mit dem nächsten 'TargetSL' weitermachen. Sinnvoll für unmittelbare Nachbarn.
617            // Auch vorangegangene Iterationen für andere, aber weiter weg liegende 'TargetSL's haben u.U.
618            // "auf dem Weg" liegende SuperLeaves als sichtbar markiert, sodaß diese hier nicht nochmal getestet werden müssen.
619            if (IsVisible(MasterSL, TargetSL)) continue;
620
621            // Wenn das 'TargetSL' keine Nachbarn hat, können wir direkt mit dem nächsten 'TargetSL' weitermachen.
622            // Für ein solches SL ohne Nachbarn haben wir sowieso keine sinnvolle "Target Bounding Box" in den 'SuperLeavesBBs' konstruiert!
623            if (SuperLeaves[TargetSL].Neighbours.Size()==0) continue;
624
625            // Intentionally ignore the returned result.
626            CanSeeFromAToB(MasterSL, TargetSL);
627        }
628    } */
629
630
631    // Für jedes SuperLeaf das PVS bestimmen.
632    for (unsigned long MasterSL=0; MasterSL<SuperLeaves.Size(); MasterSL++)
633    {
634        printf("%5.1f%%\r", (double)MasterSL/SuperLeaves.Size()*100.0);
635        fflush(stdout);
636
637        // Initialisiere die Hilfsarrays für das 'MasterSL'.
638        ArrayT<bool> AV;    // List of "Already     Visible" SuperLeaves from 'MasterSL'.
639        ArrayT<bool> PV;    // List of "Potentially Visible" SuperLeaves from 'MasterSL'.
640        ArrayT<bool> NV;    // List of "Not         Visible" SuperLeaves from 'MasterSL'.
641
642        unsigned long SL;
643
644        for (SL=0; SL<SuperLeaves.Size(); SL++)
645        {
646            AV.PushBack(false);
647            PV.PushBack(false);
648            NV.PushBack(false);
649        }
650
651        // Bestimme, welche SuperLeaves wir von 'MasterSL' aus schon sehen können.
652        for (SL=0; SL<SuperLeaves.Size(); SL++)
653            if (IsVisible(MasterSL, SL)) AV[SL]=true;
654
655        // Bilde eine Liste 'PV' aller SuperLeaves, die von 'MasterSL' aus "potentially visible" sind.
656        // Dies sind zunächst *alle* Nachbarn aller SuperLeaves in der 'AV' Liste, außer denjenigen,
657        // die bereits in der 'AV' oder 'NV' Liste sind, oder deren Index-Nummern 'NeighbourSL' kleiner/gleich 'MasterSL' sind.
658        // Der Grund für letzteres: Wenn früher schon festgestellt wurde, daß wir nicht von 'NeighbourSL' nach 'MasterSL' sehen können,
659        // können wir uns jetzt den Test, ob wir von 'MasterSL' nach 'NeighbourSL' sehen können, sparen.
660        // Wäre der frühere Test dagegen positiv gewesen, wäre 'AV[NeighbourSL]==true', was auch keinen 'PV'-Eintrag verursacht.
661        for (SL=0; SL<SuperLeaves.Size(); SL++)
662            if (AV[SL])
663                for (unsigned long NeighbourNr=0; NeighbourNr<SuperLeaves[SL].Neighbours.Size(); NeighbourNr++)
664                {
665                    const unsigned long NeighbourSL=SuperLeaves[SL].Neighbours[NeighbourNr].SuperLeafNr;
666
667                    if (!AV[NeighbourSL] && !NV[NeighbourSL] && NeighbourSL>MasterSL) PV[NeighbourSL]=true;
668                }
669
670        // For each 'TargetSL' in 'PV': Determine if 'TargetSL' is visible from 'MasterSL'.
671        while (true)
672        {
673            unsigned long TargetSL;
674
675            for (TargetSL=0; TargetSL<PV.Size(); TargetSL++) if (PV[TargetSL]) break;
676            if (TargetSL>=PV.Size()) break;
677
678            if (CanSeeFromAToB(MasterSL, TargetSL))
679            {
680                // Redo the init (but 'NV' is possibly not empty anymore).
681                for (SL=0; SL<SuperLeaves.Size(); SL++)
682                {
683                    AV[SL]=false;
684                    PV[SL]=false;
685                }
686
687                // Bestimme, welche SuperLeaves wir von 'MasterSL' aus schon sehen können.
688                for (SL=0; SL<SuperLeaves.Size(); SL++)
689                    if (IsVisible(MasterSL, SL)) AV[SL]=true;
690
691                // Bilde eine Liste 'PV' aller SuperLeaves, die von 'MasterSL' aus "potentially visible" sind.
692                // Dies sind zunächst *alle* Nachbarn aller SuperLeaves in der 'AV' Liste, außer denjenigen,
693                // die bereits in der 'AV' oder 'NV' Liste sind, oder deren Index-Nummern 'NeighbourSL' kleiner/gleich 'MasterSL' sind.
694                // Der Grund für letzteres: Wenn früher schon festgestellt wurde, daß wir nicht von 'NeighbourSL' nach 'MasterSL' sehen können,
695                // können wir uns jetzt den Test, ob wir von 'MasterSL' nach 'NeighbourSL' sehen können, sparen.
696                // Wäre der frühere Test dagegen positiv gewesen, wäre 'AV[NeighbourSL]==true', was auch keinen 'PV'-Eintrag verursacht.
697                for (SL=0; SL<SuperLeaves.Size(); SL++)
698                    if (AV[SL])
699                        for (unsigned long NeighbourNr=0; NeighbourNr<SuperLeaves[SL].Neighbours.Size(); NeighbourNr++)
700                        {
701                            const unsigned long NeighbourSL=SuperLeaves[SL].Neighbours[NeighbourNr].SuperLeafNr;
702
703                            if (!AV[NeighbourSL] && !NV[NeighbourSL] && NeighbourSL>MasterSL) PV[NeighbourSL]=true;
704                        }
705            }
706            else
707            {
708                PV[TargetSL]=false;     // Delete 'TargetSL' from PV list.
709                NV[TargetSL]=true;      // Add    'TargetSL' to   NV list.
710            }
711        }
712    }
713
714    printf("Final Avg Visibility: %10.5f\n", GetAverageVisibility());
715}
716
717
718static void WriteLogFileEntry(const char* WorldPathName, unsigned long CheckSum)
719{
720    char          DateTime [256]="unknown";
721    char          HostName [256]="unknown";
722    char          WorldName[256]="unknown";
723    time_t        Time          =time(NULL);
724    unsigned long RunningSec    =(unsigned long)difftime(Time, ProgramStartTime);
725    FILE*         LogFile       =fopen("CaPVS.log", "a");
726
727    if (!LogFile) return;
728
729    strftime(DateTime, 256, "%d.%m.%Y %H:%M", localtime(&Time));
730    DateTime[255]=0;
731
732#ifdef _WIN32
733    unsigned long Dummy=256;
734    if (!GetComputerName(HostName, &Dummy)) sprintf(HostName, "unknown (look-up failed).");
735#else
736    // This function also works on Windows, but sadly requires calls to 'WSAStartup()' and 'WSACleanup()'.
737    if (gethostname(HostName, 256)) sprintf(HostName, "unknown (look-up failed).");
738#endif
739    HostName[255]=0;
740
741    if (WorldPathName)
742    {
743        // Dateinamen abtrennen (mit Extension).
744        size_t i=strlen(WorldPathName);
745
746        while (i>0 && WorldPathName[i-1]!='/' && WorldPathName[i-1]!='\\') i--;
747        strncpy(WorldName, WorldPathName+i, 256);
748        WorldName[255]=0;
749
750        // Extension abtrennen.
751        i=strlen(WorldName);
752
753        while (i>0 && WorldName[i-1]!='.') i--;
754        if (i>0) WorldName[i-1]=0;
755    }
756
757    // Date, Time, WorldName, TimeForCompletion on [HostName]
758    fprintf(LogFile, "%-16s %-16s%3lu:%02lu:%02lu [%-16s] %10lu\n", DateTime, WorldName, RunningSec/3600, (RunningSec/60) % 60, RunningSec % 60, HostName, CheckSum);
759    fclose(LogFile);
760}
761
762
763void Usage()
764{
765    printf("\n");
766    printf("USAGE: CaPVS Path/WorldName.cw [OPTIONS]\n");
767    printf("\n");
768    printf("OPTIONS:\n");
769    printf("-maxRecDepthSL n : Leaves that are stored in the BSP tree and have a greater\n");
770    printf("                   depth than this value get combined in a common SuperLeaf\n");
771    printf("                   that is created at this depth value.\n");
772    printf("-minAreaSL a     : BSP sub-trees whose non-detail faces have a common area of\n");
773    printf("                   less than this value get combined in a SuperLeaf.\n");
774    printf("-onlySLs         : Do not compute the PVS, just print out how many SuperLeaves\n");
775    printf("                   would be created. Used for estimating the above parameter\n");
776    printf("                   values (speed vs. quality).\n");
777    printf("\n");
778
779    exit(1);
780}
781
782
783int main(int ArgC, const char* ArgV[])
784{
785    unsigned long MaxRecDepthSL  =0xFFFFFFFF;
786    double        MinAreaSL      =0.0;
787    bool          OnlySuperLeaves=false;
788
789    printf("\n*** Cafu Potentially Visibility Set Utility, Version 05 (%s) ***\n\n\n", __DATE__);
790
791    if (ArgC<2) Usage();
792
793    // Initialize the FileMan by mounting the default file system.
794    // Note that specifying "./" (instead of "") as the file system description effectively prevents the use of
795    // absolute paths like "D:\abc\someDir\someFile.xy" or "/usr/bin/xy". This however should be fine for this application.
796    cf::FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_LOCAL_PATH, "./", "");
797    cf::FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_ZIP_ARCHIVE, "Games/DeathMatch/Textures/TechDemo.zip", "Games/DeathMatch/Textures/TechDemo/", "Ca3DE");
798    cf::FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_ZIP_ARCHIVE, "Games/DeathMatch/Textures/SkyDomes.zip", "Games/DeathMatch/Textures/SkyDomes/", "Ca3DE");
799
800    // Parse the command line.
801    for (int ArgNr=2; ArgNr<ArgC; ArgNr++)
802    {
803        if (!_stricmp(ArgV[ArgNr], "-maxRecDepthSL"))
804        {
805            ArgNr++;
806            if (ArgNr>=ArgC) Usage();
807
808            MaxRecDepthSL=atoi(ArgV[ArgNr]);
809            printf("maxRecDepthSL == %lu\n", MaxRecDepthSL);
810        }
811        else if (!_stricmp(ArgV[ArgNr], "-minAreaSL"))
812        {
813            ArgNr++;
814            if (ArgNr>=ArgC) Usage();
815
816            MinAreaSL=atof(ArgV[ArgNr]);
817            if (MinAreaSL<0.0) MinAreaSL=0.0;
818            printf("minAreaSL     == %.3f\n", MinAreaSL);
819        }
820        else if (!_stricmp(ArgV[ArgNr], "-onlySLs"))
821        {
822            OnlySuperLeaves=true;
823        }
824        else if (ArgV[ArgNr][0]==0)
825        {
826            // The argument is "", the empty string.
827            // This can happen under Linux, when CaPVS is called via wxExecute() with white-space trailing the command string.
828        }
829        else
830        {
831            printf("Unknown option '%s'.\n", ArgV[ArgNr]);
832            Usage();
833        }
834    }
835
836
837    std::string GameDirectory=ArgV[1];
838
839    // Determine the game directory, cleverly assuming that the destination file is in "Worlds".
840    {
841        // Strip the file name and extention off.
842        size_t i=GameDirectory.find_last_of("/\\");
843
844        GameDirectory=GameDirectory.substr(0, i==std::string::npos ? 0 : i)+"/..";
845    }
846
847
848    // Setup the global MaterialManager pointer.
849    static MaterialManagerImplT MatManImpl;
850
851    MaterialManager=&MatManImpl;
852
853    if (MaterialManager->RegisterMaterialScriptsInDir(GameDirectory+"/Materials", GameDirectory+"/").Size()==0)
854    {
855        printf("\nNo materials found in scripts in \"%s/Materials\".\n", GameDirectory.c_str());
856        printf("No materials found.\n\n");
857        Usage();
858    }
859
860
861    try
862    {
863        // General note: Robustness against the 'sharp wedge' problem is granted:
864        // DetermineAdjacencyGraph() is the only place where PolygonOverlap() is called, but only once on *input* portals.
865        // Everywhere else (especially in BuildPVS() and its sub-functions), only PolygonSplit() and PolygonWhatSide() are called.
866        printf("*** Load World %s ***\n", ArgV[1]);
867
868        // 1. Load the 'CaPVSWorld'.
869        ModelManagerT ModelMan;
870        CaPVSWorldT*  CaPVSWorld=new CaPVSWorldT(ArgV[1], ModelMan, MaxRecDepthSL, MinAreaSL);
871
872        // 2. Create the 'SuperLeaves'.
873        CaPVSWorld->CreateSuperLeaves(SuperLeaves);
874        if (OnlySuperLeaves) return 0;
875
876        // 3. Determine the adjacency graph.
877        //    This completely fills-in the remaining components of the 'SuperLeaves'.
878        printf("\n%-50s %s\n", "*** Initialize ***", GetTimeSinceProgramStart());
879        DetermineAdjacencyGraph();
880
881        // 4. Compute for each SuperLeaf the bounding box over its (sub-)portals.
882        ComputeSuperLeavesBBs();
883
884        // 5. Create and initialize the 'SuperLeavesPVS' (reset to complete blindness (all 0s)).
885        SuperLeavesPVS.PushBackEmpty((SuperLeaves.Size()*SuperLeaves.Size()+31)/32);
886        for (unsigned long Vis=0; Vis<SuperLeavesPVS.Size(); Vis++) SuperLeavesPVS[Vis]=0;
887
888        // 6. For each SuperLeaf, flag itself and the immediate neighbours as visible.
889        DetermineTrivialVisibility();
890
891        // 7. Do some simple ray tests in order to quickly obtain a good estimation of the actual PVS.
892        //    Note that calling this function is ENTIRELY OPTIONAL, and the call can be omitted without danger!
893        //    (The latter might e.g. be useful for debugging.)
894        DetermineRayPresampledVisibility(CaPVSWorld);
895
896        // 8. Finally calculate the 'SuperLeavesPVS' using analytical methods.
897        printf("\n%-50s %s\n", "*** Potentially Visibility Set ***", GetTimeSinceProgramStart());
898        BuildPVS();
899
900        // 9. Carry the information in 'SuperLeavesPVS' into the PVS of the 'CaPVSWorld'.
901        CaPVSWorld->StorePVS(SuperLeaves, SuperLeavesPVS);
902
903        // 10. Print some statistics and obtain a checksum.
904        unsigned long CheckSum=CaPVSWorld->GetChecksumAndPrintStats();
905
906        // 11. Save the 'CaPVSWorld' back to disk.
907        printf("\n%-50s %s\n", "*** Save World ***", GetTimeSinceProgramStart());
908        printf("%s\n", ArgV[1]);
909        CaPVSWorld->SaveToDisk(ArgV[1]);
910
911        // 12. Clean-up and write log file entry.
912        delete CaPVSWorld;
913        WriteLogFileEntry(ArgV[1], CheckSum);
914        printf("\n%-50s %s\n", "COMPLETED.", GetTimeSinceProgramStart());
915    }
916    catch (const WorldT::LoadErrorT& E)
917    {
918        printf("\nFATAL ERROR: %s\n", E.Msg);
919        printf("Program aborted.\n\n");
920        exit(1);
921    }
922    catch (const WorldT::SaveErrorT& E)
923    {
924        printf("\nFATAL ERROR: %s\n", E.Msg);
925        printf("Program aborted.\n\n");
926        exit(1);
927    }
928
929    return 0;
930}
Note: See TracBrowser for help on using the browser.