| 1 | /* |
|---|
| 2 | ================================================================================= |
|---|
| 3 | This file is part of Cafu, the open-source game engine and graphics engine |
|---|
| 4 | for multiplayer, cross-platform, real-time 3D action. |
|---|
| 5 | Copyright (C) 2002-2012 Carsten Fuchs Software. |
|---|
| 6 | |
|---|
| 7 | Cafu is free software: you can redistribute it and/or modify it under the terms |
|---|
| 8 | of the GNU General Public License as published by the Free Software Foundation, |
|---|
| 9 | either version 3 of the License, or (at your option) any later version. |
|---|
| 10 | |
|---|
| 11 | Cafu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; |
|---|
| 12 | without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
|---|
| 13 | PURPOSE. See the GNU General Public License for more details. |
|---|
| 14 | |
|---|
| 15 | You should have received a copy of the GNU General Public License |
|---|
| 16 | along with Cafu. If not, see <http://www.gnu.org/licenses/>. |
|---|
| 17 | |
|---|
| 18 | For 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 | |
|---|
| 62 | static cf::ConsoleStdoutT ConsoleStdout; |
|---|
| 63 | cf::ConsoleI* Console=&ConsoleStdout; |
|---|
| 64 | |
|---|
| 65 | static cf::FileSys::FileManImplT FileManImpl; |
|---|
| 66 | cf::FileSys::FileManI* cf::FileSys::FileMan=&FileManImpl; |
|---|
| 67 | |
|---|
| 68 | static cf::ClipSys::CollModelManImplT CCM; |
|---|
| 69 | cf::ClipSys::CollModelManI* cf::ClipSys::CollModelMan=&CCM; |
|---|
| 70 | |
|---|
| 71 | ConsoleInterpreterI* ConsoleInterpreter=NULL; |
|---|
| 72 | MaterialManagerI* MaterialManager =NULL; |
|---|
| 73 | |
|---|
| 74 | |
|---|
| 75 | const 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". |
|---|
| 79 | static 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 | |
|---|
| 93 | ArrayT<SuperLeafT> SuperLeaves; |
|---|
| 94 | ArrayT< BoundingBox3T<double> > SuperLeavesBBs; |
|---|
| 95 | ArrayT<unsigned long> SuperLeavesPVS; |
|---|
| 96 | |
|---|
| 97 | |
|---|
| 98 | // Records in 'SuperLeavesPVS' that we can see from 'FromSL' to 'ToSL'. |
|---|
| 99 | inline 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. |
|---|
| 110 | inline 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). |
|---|
| 121 | double 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. |
|---|
| 137 | void 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. |
|---|
| 175 | void 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. |
|---|
| 195 | void 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. |
|---|
| 220 | void 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. |
|---|
| 311 | inline 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. |
|---|
| 424 | bool 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". |
|---|
| 555 | bool 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. |
|---|
| 603 | void 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 | |
|---|
| 718 | static 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 | |
|---|
| 763 | void 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 | |
|---|
| 783 | int 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 | } |
|---|