Fix issue in Rocket.lua script.
[Cafu-Engine.git] / CaSHL / CaSHL.cpp
blob3016056ba474f2e078fff24f8203bd63ca781618
1 /*
2 Cafu Engine, http://www.cafu.de/
3 Copyright (c) Carsten Fuchs and other contributors.
4 This project is licensed under the terms of the MIT license.
5 */
7 /************************************************/
8 /*** ***/
9 /*** Cafu Spherical Harmonic Lighting Utility ***/
10 /*** ***/
11 /*** Der Herr sprach ***/
12 /*** Es werde Licht! ***/
13 /*** Und es wurde Licht. ***/
14 /*** Genesis ***/
15 /*** ***/
16 /************************************************/
18 // ALLGEMEINE BEMERKUNGEN ZU FACES, SHLMAPS UND PATCHES:
19 // Wir definieren eine SHLMap als ein Rechteck aus s*t quadratischen Patches, die jeweils eine Face "abdecken".
20 // Man stelle sich die unendlich große Ebene, in der die Face liegt, als von quadratischen Patches überzogen vor,
21 // wie ein kariertes Mathe-Schulheft. Wichtig ist nun, daß der Ursprung des Patch-Rasters *ganzzahlig* mit dem
22 // World-Origin zusammenfällt. D.h., wenn man die Ebene entlang ihres Normalenvektors solange verschiebt, bis der
23 // World-Origin in ihr liegt, muß ein Schnittpunkt des Patch-Rasters damit zusammenfallen.
24 // Im Gegensatz zu einem früheren Ansatz, bei dem die Verschiebung des Patch-Rasters sich an der kleinsten s- und t-Koordinate
25 // der Face orientiert hat, stellen wir mit diesem Vorgehen sicher, daß sich Patches von benachbarte Faces stets *vollständig*
26 // überlappen (oder gar nicht). Beliebige teilweise Überlappungen kommen nicht mehr vor.
27 // Das Rechteck sollte bei gegebener Seitenlänge der Patches und gegebener Orientierung (entlang des UV-Koordinatensystems,
28 // welches man mit PlaneT::GetSpanVectors() erhält) möglichst kleine s- und t-Abmessungen haben.
29 // Außerdem ziehen wir noch einen 1 Patch breiten Rahmen drumherum. Damit soll dem OpenGL-Renderer Rechnung getragen werden,
30 // der zu jeder (s,t)-Koordinate den Mittelwert des umliegenden 2x2-Quadrats bestimmt (bilinear Filtering).
31 // Betrachte dazu auch die Darstellung im Cafu Tech-Archive vom 28. Oktober 2003.
33 #ifdef _WIN32
34 #define WIN32_LEAN_AND_MEAN
35 #include <windows.h>
36 #include <conio.h>
37 #include <direct.h>
38 #else
39 #include <stdarg.h>
40 #include <unistd.h>
41 #include <string.h>
42 #include <dirent.h>
43 #define _stricmp strcasecmp
44 #define _getch getchar
45 #endif
47 #include <time.h>
48 #include <stdio.h>
50 #include "Templates/Array.hpp"
51 #include "ConsoleCommands/Console.hpp"
52 #include "ConsoleCommands/ConsoleInterpreter.hpp"
53 #include "ConsoleCommands/ConsoleStdout.hpp"
54 #include "FileSys/FileManImpl.hpp"
55 #include "GuiSys/GuiResources.hpp"
56 #include "Math3D/BoundingBox.hpp"
57 #include "Math3D/Vector3.hpp"
58 #include "Bitmap/Bitmap.hpp"
59 #include "MaterialSystem/Material.hpp"
60 #include "MaterialSystem/MaterialManager.hpp"
61 #include "MaterialSystem/MaterialManagerImpl.hpp"
62 #include "Models/ModelManager.hpp"
63 #include "SceneGraph/Node.hpp"
64 #include "SceneGraph/BspTreeNode.hpp"
65 #include "SceneGraph/FaceNode.hpp"
66 #include "ClipSys/CollisionModelMan_impl.hpp"
68 #include "CaSHLWorld.hpp"
70 #if defined(_WIN32)
71 #if defined(_MSC_VER)
72 #define vsnprintf _vsnprintf
73 #endif
74 #endif
77 static cf::ConsoleStdoutT ConsoleStdout;
78 cf::ConsoleI* Console=&ConsoleStdout;
80 static cf::FileSys::FileManImplT FileManImpl;
81 cf::FileSys::FileManI* cf::FileSys::FileMan=&FileManImpl;
83 static cf::ClipSys::CollModelManImplT CCM;
84 cf::ClipSys::CollModelManI* cf::ClipSys::CollModelMan=&CCM;
86 ConsoleInterpreterI* ConsoleInterpreter=NULL;
87 MaterialManagerI* MaterialManager =NULL;
90 const time_t ProgramStartTime=time(NULL);
92 // Returns a string with the elapsed time since program start.
93 // The string is in the format "hh:mm:ss".
94 static const char* GetTimeSinceProgramStart()
96 const unsigned long TotalSec=(unsigned long)difftime(time(NULL), ProgramStartTime);
97 const unsigned long Sec =TotalSec % 60;
98 const unsigned long Min =(TotalSec/60) % 60;
99 const unsigned long Hour =TotalSec/3600;
101 static char TimeString[24];
102 sprintf(TimeString, "%2lu:%2lu:%2lu", Hour, Min, Sec);
104 return TimeString;
108 static void Error(const char* ErrorText, ...)
110 va_list ArgList;
111 char ErrorString[256];
113 if (ErrorText!=NULL)
115 va_start(ArgList, ErrorText);
116 vsnprintf(ErrorString, 256, ErrorText, ArgList);
117 va_end(ArgList);
119 printf("\nFATAL ERROR: %s\n", ErrorString);
122 printf("Program aborted.\n\n");
123 exit(1);
127 const double REFLECTIVITY=0.3; // Gleiche Reflektivität für alle Faces und für alle Wellenlängen
128 const double Pi =3.14159265358979323846;
130 // Defined in CaSHLWorld.hpp now!
132 // struct PatchT
133 // {
134 // VectorT UnradiatedEnergy; // Noch nicht in die Umgebung abgestrahlte Energie (per unit time per unit area)
135 // VectorT TotalEnergy; // Energie, die dieser Patch in die Umgebung abstrahlt (per unit time per unit area)
137 // VectorT Coord; // Position (+safety) in world space of the center of the patch. Valid only if InsideFace==true.
138 // bool InsideFace; // InsideFace==true <==> this patch is not completely outside its face
139 // };
141 enum FaceVis { NO_VISIBILITY, PARTIAL_VISIBILITY, FULL_VISIBILITY };
142 ArrayT< ArrayT<FaceVis> > FacePVS; // The set of faces each face can see (see InitializeFacePVSMatrix() for a description!)
143 ArrayT< ArrayT<PatchT > > Patches; // Each face gets a set of patches, PatchT is declared in CaSHLWorld.hpp
146 #include "Init1.cpp" // void InitializeFacePVSMatrix(const cf::SceneGraph::BspTreeNodeT& Map ) { ... }
147 #include "Init2.cpp" // void InitializePatches (const cf::SceneGraph::BspTreeNodeT& Map, const SkyDomeT& SkyDome) { ... }
150 // Strahlt den Transfer der Patches im n*n Quadrat ab, dessen linke obere Ecke bei (s_i, t_i) liegt.
151 void RadiateTransfer(const CaSHLWorldT& CaSHLWorld, unsigned long Face_i, unsigned long s_i, unsigned long t_i, char n)
153 const cf::SceneGraph::BspTreeNodeT& Map =CaSHLWorld.GetBspTree();
154 const unsigned long NR_OF_SH_COEFFS=cf::SceneGraph::SHLMapManT::NrOfBands * cf::SceneGraph::SHLMapManT::NrOfBands;
155 const double PATCH_SIZE =Map.GetSHLMapPatchSize();
156 const cf::SceneGraph::FaceNodeT::SHLMapInfoT& SMI=Map.FaceChildren[Face_i]->SHLMapInfo;
158 unsigned long Big_P_i_Count=0;
159 VectorT Big_P_i_Coord;
160 #if USE_NORMALMAPS
161 VectorT Big_P_i_Normal;
162 #endif
163 ArrayT<double> Big_P_i_SHCoeffs_UnradiatedTransfer;
165 while (Big_P_i_SHCoeffs_UnradiatedTransfer.Size()<NR_OF_SH_COEFFS) Big_P_i_SHCoeffs_UnradiatedTransfer.PushBack(0.0);
167 // Bilde den Positions-Durchschnitt bzw. die UnradiatedTransfer-Summe aller Patches im n*n Quadrat,
168 // wobei (s_i, t_i) die linke obere Ecke ist und nur Patches innerhalb der Face berücksichtigt werden.
169 for (char y=0; y<n; y++)
170 for (char x=0; x<n; x++)
172 if (s_i+x+1>SMI.SizeS) continue;
173 if (t_i+y+1>SMI.SizeT) continue;
175 PatchT& P_i=Patches[Face_i][(t_i+y)*SMI.SizeS+(s_i+x)];
176 if (!P_i.InsideFace) continue;
178 Big_P_i_Count++;
179 Big_P_i_Coord =Big_P_i_Coord +P_i.Coord;
180 #if USE_NORMALMAPS
181 Big_P_i_Normal=Big_P_i_Normal+P_i.Normal;
182 #endif
184 for (unsigned long CoeffNr=0; CoeffNr<P_i.SHCoeffs_UnradiatedTransfer.Size(); CoeffNr++)
186 Big_P_i_SHCoeffs_UnradiatedTransfer[CoeffNr]+=P_i.SHCoeffs_UnradiatedTransfer[CoeffNr];
188 // By being added to the Big_P_i above, this patch has radiated its transfer. Job done. Mission accomplished.
189 P_i.SHCoeffs_UnradiatedTransfer[CoeffNr]=0.0;
193 if (!Big_P_i_Count) return;
194 Big_P_i_Coord =scale(Big_P_i_Coord, 1.0/double(Big_P_i_Count));
195 #if USE_NORMALMAPS
196 Big_P_i_Normal=normalize(Big_P_i_Normal, 0.0);
197 #endif
200 // Betrachte alle Patches aller Faces im PVS der Face Face_i.
201 for (unsigned long Face_j=0; Face_j<Map.FaceChildren.Size(); Face_j++)
203 // Vermeide alle unnötigen und evtl. rundungsfehlergefährdeten Berechnungen.
204 // Die folgende Zeile fängt auch alle Fälle ab, in denen Face_j in der Ebene von Face_i liegt
205 // und insb. für die Face_i==Face_j gilt. Vgl. die Erstellung und Optimierung der FacePVS-Matrix!
206 if (FacePVS[Face_i][Face_j]==NO_VISIBILITY) continue;
207 if (Map.FaceChildren[Face_j]->Polygon.Plane.GetDistance(Big_P_i_Coord)<0.1) continue;
209 bool ExplicitTestRequired=(FacePVS[Face_i][Face_j]!=FULL_VISIBILITY);
211 for (unsigned long Patch_j=0; Patch_j<Patches[Face_j].Size(); Patch_j++)
213 PatchT& P_j=Patches[Face_j][Patch_j];
214 if (!P_j.InsideFace) continue;
216 // Einsparen des Wurzelziehens: Rechne einfach mit dem Quadrat weiter!
217 const VectorT Ray =P_j.Coord-Big_P_i_Coord;
218 // double RayLength =length(Ray);
219 double RayLength2=dot(Ray, Ray);
221 // if (RayLength <0.5 ) continue; // Kommt das jemals vor?
222 if (RayLength2<0.5*0.5) continue;
224 // VectorT Dir_ij =scale(Ray, 1.0/RayLength );
225 VectorT Dir_ij2=scale(Ray, 1.0/RayLength2); // Dir_ij2==Dir_ij/RayLength
227 if (ExplicitTestRequired)
228 if (CaSHLWorld.TraceRay(Big_P_i_Coord, Ray)<1.0) continue;
230 if (RayLength2<PATCH_SIZE*PATCH_SIZE)
232 RayLength2=PATCH_SIZE*PATCH_SIZE;
233 Dir_ij2 =scale(Ray, 1.0/RayLength2);
236 #if USE_NORMALMAPS
237 const double cos1__= dot(Map.FaceChildren[Face_i]->Polygon.Plane.Normal, Dir_ij2); if (cos1__<=0) continue;
238 const double cos2__=-dot(Map.FaceChildren[Face_j]->Polygon.Plane.Normal, Dir_ij2); if (cos2__<=0) { printf("cos2__<=0\n"); continue; } // Sollte niemals vorkommen (wg. PlaneDist-Check oben)!
239 const double cos1_ = dot(Big_P_i_Normal, Dir_ij2); if (cos1_ <=0) continue;
240 const double cos2_ =-dot(P_j.Normal , Dir_ij2); if (cos2_ <=0) continue;
241 #else
242 // const double cos1 = dot(Map.FaceChildren[Face_i]->Polygon.Plane.Normal, Dir_ij ); if (cos1 <=0) continue;
243 // const double cos2 =-dot(Map.FaceChildren[Face_j]->Polygon.Plane.Normal, Dir_ij ); if (cos2 <=0) { printf("cos2 <=0\n"); continue; } // Sollte niemals vorkommen (wg. PlaneDist-Check oben)!
244 const double cos1_= dot(Map.FaceChildren[Face_i]->Polygon.Plane.Normal, Dir_ij2); if (cos1_<=0) continue;
245 const double cos2_=-dot(Map.FaceChildren[Face_j]->Polygon.Plane.Normal, Dir_ij2); if (cos2_<=0) { printf("cos2_<=0\n"); continue; } // Sollte niemals vorkommen (wg. PlaneDist-Check oben)!
246 #endif
248 // 'Alternative', einfache Herleitung des Form-Faktors:
249 // Betrachte die Halbkugel über dem Patch i mit Radius RayLength. RayLength soll groß genug sein,
250 // d.h. Patch j soll problemlos als ein Teil der Halbkugeloberfläche betrachtet werden können.
251 // Die prozentuale Sichtbarkeit erhalten wir also sofort aus A_j/O, wobei A_j der Flächeninhalt des Patches j ist
252 // und O der Oberflächeninhalt der Halbkugel, O=0.5*4*pi*RayLength^2.
253 // cos1 und cos2 berücksichtigen dann noch die gegenseitige Verdrehung der Patches und wir sind fertig.
254 // Einziges Problem: Obige Herleitung enthält noch einen Faktor 1/2, für den ich leider keine Erklärung habe.
255 // Noch eine Alternative: Man muß RayLength ausdrücken in Patch-Längen, nicht in Millimetern!
256 // double FormFactor_ij=PATCH_SIZE*PATCH_SIZE/3.14159265359*cos1 *cos2 /(RayLength*RayLength);
257 double FormFactor_ij=PATCH_SIZE*PATCH_SIZE/3.14159265359*cos1_*cos2_;
259 // Die Flächeninhalte scheinen sich herauszukürzen!?
260 // (Im FormFactor ist P_j.Area/P_i.Area enthalten, und dieser wird hier multipliziert mit P_i.Area/P_j.Area.)
261 // Wir müssen nichtmal Big_P_i_Count hineinmultiplizieren, da Big_P_i_UnradiatedEnergy schon die Summe der Einzelpatches ist!
262 for (unsigned long CoeffNr=0; CoeffNr<Big_P_i_SHCoeffs_UnradiatedTransfer.Size(); CoeffNr++)
264 const double DeltaTransfer=Big_P_i_SHCoeffs_UnradiatedTransfer[CoeffNr]*REFLECTIVITY*FormFactor_ij;
266 P_j.SHCoeffs_UnradiatedTransfer[CoeffNr]+=DeltaTransfer;
267 P_j.SHCoeffs_TotalTransfer [CoeffNr]+=DeltaTransfer;
274 // Computes the Associated Legendre Polynomial at 'x'.
275 double ALP(int l, int m, double x)
277 // Start with rule #2.
278 double Pmm =1.0;
279 double S1mx2=sqrt(1.0-x*x);
280 double fact =1.0;
282 for (int i=0; i<m; i++)
284 Pmm *= -1.0*fact*S1mx2;
285 fact += 2.0;
287 if (l==m) return Pmm;
289 double Pmmp1=x*(2.0*m+1.0)*Pmm;
290 if (l==m+1) return Pmmp1;
292 double Pll=0.0;
294 for (int ll=m+2; ll<=l; ll++)
296 Pll =(x*(2.0*ll-1.0)*Pmmp1 - (ll+m-1.0)*Pmm) / (ll-m);
297 Pmm =Pmmp1;
298 Pmmp1=Pll;
300 return Pll;
304 double factorial(int n)
306 static bool IsInitialized=false;
307 static double Factorials[33];
309 if (!IsInitialized)
311 Factorials[0]=1.0;
313 for (char i=1; i<33; i++)
314 Factorials[i]=double(i)*Factorials[i-1];
316 IsInitialized=true;
319 return Factorials[n<33 ? n : 32];
323 // Computes a spherical harmonic function.
324 // 'l' is the band, range [0...N].
325 // 'm' is in range [-l...l].
326 // 'theta' is the first polar angle, in range [0...Pi].
327 // 'phi' is the second polar angle, in range [0...2*Pi].
328 // This function is described in detail in the paper by Robin Green.
329 double SphericalHarmonic(int l, int m, double theta, double phi)
331 const double K=sqrt((2.0*l+1.0)/(4.0*Pi) * factorial(l-abs(m))/factorial(l+abs(m)));
333 if (m>0) return sqrt(2.0)*cos( m*phi)*K*ALP(l, m, cos(theta));
334 else if (m<0) return sqrt(2.0)*sin(-m*phi)*K*ALP(l, -m, cos(theta));
335 else /*m==0*/ return K*ALP(l, 0, cos(theta));
339 struct SphericalSampleT
341 // VectorT Angles; // The position on the sphere in polar coordinates.
342 VectorT UnitVector; // The same position as a unit vector.
343 ArrayT<double> Coeffs; // For an n-band approximation, these are the n^2 coefficients (values of the y(l, m, theta, phi) function).
347 void DirectLighting(const CaSHLWorldT& CaSHLWorld, const unsigned long SqrtNrOfSamples)
349 const cf::SceneGraph::BspTreeNodeT& Map=CaSHLWorld.GetBspTree();
351 printf("\n%-50s %s\n", "*** PHASE I - performing direct lighting ***", GetTimeSinceProgramStart());
354 // This is a first test with SH lighting.
355 // In order to keep things initially simple, the following implements "Shadowed Diffuse Transfer",
356 // as detailed in Robin Greens paper on pages 29 and subsequent.
357 ArrayT<SphericalSampleT> SphericalSamples;
358 ArrayT<unsigned long> SkyFaces;
359 unsigned long FaceNr;
360 const unsigned long NR_OF_SH_COEFFS=cf::SceneGraph::SHLMapManT::NrOfBands * cf::SceneGraph::SHLMapManT::NrOfBands;
363 // Bilde zuerst ein LookUp-Array, das die Nummern aller Faces mit Sky-Texture enthält.
364 for (FaceNr=0; FaceNr<Map.FaceChildren.Size(); FaceNr++)
365 if (length(Vector3T<double>(Map.FaceChildren[FaceNr]->Material->meta_SunLight_Irr))>0.1 &&
366 length(Vector3T<double>(Map.FaceChildren[FaceNr]->Material->meta_SunLight_Dir))>0.1) SkyFaces.PushBack(FaceNr);
369 for (unsigned long SampleX=0; SampleX<SqrtNrOfSamples; SampleX++)
370 for (unsigned long SampleY=0; SampleY<SqrtNrOfSamples; SampleY++)
372 const double x =double(SampleX)/double(SqrtNrOfSamples) + 0.5/double(SqrtNrOfSamples);
373 const double y =double(SampleY)/double(SqrtNrOfSamples) + 0.5/double(SqrtNrOfSamples);
374 const double theta=2.0*acos(sqrt(1.0-x));
375 const double phi =2.0*Pi*y;
377 SphericalSamples.PushBackEmpty(1);
378 // SphericalSamples[SphericalSamples.Size()-1].Angles =VectorT(theta, phi);
379 SphericalSamples[SphericalSamples.Size()-1].UnitVector=VectorT(sin(theta)*cos(phi), sin(theta)*sin(phi), cos(theta));
381 for (int l=0; l<cf::SceneGraph::SHLMapManT::NrOfBands; l++)
382 for (int m=-l; m<=l; m++) // const int Index=l*(l+1)+m;
383 SphericalSamples[SphericalSamples.Size()-1].Coeffs.PushBack(SphericalHarmonic(l, m, theta, phi));
385 if (SphericalSamples[SphericalSamples.Size()-1].Coeffs.Size()!=NR_OF_SH_COEFFS) Error("Bad number of coeffs in %s, line %u.", __FILE__, __LINE__);
389 // I assume (think/guess/hope) that the range of the SphericalHarmonic function does not exceed [-1...1].
390 // It is important to know this range because we later want to efficiently store our computed results,
391 // e.g. as fixed-point values with limited precision (like compressed into a char).
392 // However, to be safe, a rigorous mathematically founded answer would be required, which im still lacking.
394 double Min=(SphericalSamples.Size()>0 && NR_OF_SH_COEFFS>0) ? SphericalSamples[0].Coeffs[0] : 0.0;
395 double Max=(SphericalSamples.Size()>0 && NR_OF_SH_COEFFS>0) ? SphericalSamples[0].Coeffs[0] : 0.0;
397 for (unsigned long SampleNr=0; SampleNr<SphericalSamples.Size(); SampleNr++)
398 for (unsigned long CoeffNr=0; CoeffNr<NR_OF_SH_COEFFS; CoeffNr++)
400 const double Value=SphericalSamples[SampleNr].Coeffs[CoeffNr];
402 if (Value<Min) Min=Value;
403 if (Value>Max) Max=Value;
406 printf("SHL INFO: Min %15.10f Max %15.10f\n", Min, Max);
407 if (Min<-1.0 || Max>1.0) Error("Assumed range of SphericalHarmonic() is out of bounds."); // Should never happen...
411 for (FaceNr=0; FaceNr<Map.FaceChildren.Size(); FaceNr++)
413 const cf::SceneGraph::FaceNodeT::SHLMapInfoT& SMI=Map.FaceChildren[FaceNr]->SHLMapInfo;
415 printf("%5.1f%%\r", (double)FaceNr/Map.FaceChildren.Size()*100.0);
416 fflush(stdout);
418 for (unsigned long t=0; t<SMI.SizeT; t++)
419 for (unsigned long s=0; s<SMI.SizeS; s++)
421 PatchT& Patch=Patches[FaceNr][t*SMI.SizeS+s];
423 // A patch that is entirely outside of its face is not considered.
424 if (!Patch.InsideFace) continue;
426 for (unsigned long SampleNr=0; SampleNr<SphericalSamples.Size(); SampleNr++)
428 #if USE_NORMALMAPS
429 const double CosTerm =dot(SphericalSamples[SampleNr].UnitVector, Patch.Normal);
430 const double CosTerm2=dot(SphericalSamples[SampleNr].UnitVector, Map.FaceChildren[FaceNr]->Polygon.Plane.Normal);
432 if (CosTerm>0.0 && CosTerm2>0.0)
433 #else
434 const double CosTerm=dot(SphericalSamples[SampleNr].UnitVector, Map.FaceChildren[FaceNr]->Polygon.Plane.Normal);
436 if (CosTerm>0.0)
437 #endif
439 // The ray is in the upper hemisphere.
440 // Now figure out if it hits a "sky" face.
441 const VectorT Ray=SphericalSamples[SampleNr].UnitVector*9999999.9;
442 const VectorT Hit=Patch.Coord+Ray*CaSHLWorld.TraceRay(Patch.Coord, Ray);
444 // Teste, ob 'Hit' in einer Face mit Sky-Texture liegt.
445 unsigned long FNr;
447 for (FNr=0; FNr<SkyFaces.Size(); FNr++)
449 const Polygon3T<double>& SkyFace=Map.FaceChildren[SkyFaces[FNr]]->Polygon;
451 if (fabs(SkyFace.Plane.GetDistance(Hit))>0.2) continue; // Ist 'Hit' zu weit von der SkyFace weg?
453 unsigned long VNr;
454 for (VNr=0; VNr<SkyFace.Vertices.Size(); VNr++)
455 if (SkyFace.GetEdgePlane(VNr, MapT::RoundEpsilon).GetDistance(Hit)<-0.1) break;
457 if (VNr==SkyFace.Vertices.Size()) break;
460 if (FNr<SkyFaces.Size())
462 // The ray actually hit the sky!
463 for (unsigned long CoeffNr=0; CoeffNr<NR_OF_SH_COEFFS; CoeffNr++)
464 Patch.SHCoeffs_TotalTransfer[CoeffNr]+=CosTerm*SphericalSamples[SampleNr].Coeffs[CoeffNr];
469 // When done, all coefficients should be in range [-4*Pi...4*Pi].
470 for (unsigned long CoeffNr=0; CoeffNr<NR_OF_SH_COEFFS; CoeffNr++)
472 Patch.SHCoeffs_TotalTransfer[CoeffNr]*=4.0*Pi/SphericalSamples.Size();
474 // Initially, the Unradiated Transfer is the same as the Total Transfer,
475 // exactly analogous to the direct lighting phase in traditional radiosity.
476 Patch.SHCoeffs_UnradiatedTransfer[CoeffNr]=Patch.SHCoeffs_TotalTransfer[CoeffNr];
483 unsigned long BounceLighting(const CaSHLWorldT& CaSHLWorld, const char BLOCK_SIZE, double& StopUT, const bool AskForMore)
485 const cf::SceneGraph::BspTreeNodeT& Map=CaSHLWorld.GetBspTree();
487 printf("\n%-50s %s\n", "*** PHASE II - performing bounce lighting ***", GetTimeSinceProgramStart());
489 unsigned long IterationCount =0;
490 unsigned long FullSearchCount=0;
492 while (true)
494 unsigned long Face_i=0;
495 unsigned long s_i =0;
496 unsigned long t_i =0;
497 double BestUT=0; // Best unradiated transfer amount found.
499 // Finde eine Face mit einem Patch mit einem großen "Unradiated Transfer" (nicht notwendigerweise die größte, damit es schnell geht).
500 unsigned long FaceNr;
502 for (FaceNr=0; FaceNr<Map.FaceChildren.Size(); FaceNr++)
504 // Achtung: Patches[FaceNr].Size kann auch 0 sein!
505 unsigned long NrOfSamples=Patches[FaceNr].Size()<10 ? Patches[FaceNr].Size()/2 : 10;
506 const cf::SceneGraph::FaceNodeT::SHLMapInfoT& SMI =Map.FaceChildren[FaceNr]->SHLMapInfo;
508 for (unsigned long SampleNr=0; SampleNr<NrOfSamples; SampleNr++)
510 unsigned long s=rand() % SMI.SizeS; // Funktioniert nicht mehr gut wenn SMI.SizeS>RAND_MAX
511 unsigned long t=rand() % SMI.SizeT; // Funktioniert nicht mehr gut wenn SMI.SizeT>RAND_MAX
513 s=(s/BLOCK_SIZE)*BLOCK_SIZE;
514 t=(t/BLOCK_SIZE)*BLOCK_SIZE;
516 unsigned long Count =0;
517 double ThisUT=0.0;
519 for (char y=0; y<BLOCK_SIZE; y++)
520 for (char x=0; x<BLOCK_SIZE; x++)
522 if (s+x+1>SMI.SizeS) continue;
523 if (t+y+1>SMI.SizeT) continue;
525 const PatchT& P_i=Patches[FaceNr][(t+y)*SMI.SizeS+(s+x)];
526 if (!P_i.InsideFace) continue;
528 for (unsigned long CoeffNr=0; CoeffNr<P_i.SHCoeffs_UnradiatedTransfer.Size(); CoeffNr++)
529 ThisUT+=fabs(P_i.SHCoeffs_UnradiatedTransfer[CoeffNr]);
531 Count++;
534 if (!Count) continue;
535 ThisUT/=double(Count);
537 if (ThisUT>BestUT)
539 Face_i=FaceNr;
540 s_i =s;
541 t_i =t;
542 BestUT=ThisUT;
547 // Sollte der BestUT unter StopUT sein, wird hier nach einem besseren Wert gesucht.
548 // Beim erstbesseren Wert wird dieser genommen und abgebrochen, ansonsten gesucht bis zum Schluß.
549 // Wenn alle Faces nach dem besten Wert durchsucht werden sollen, muß "&& BestUT<StopUT" auskommentiert werden.
550 if (BestUT<StopUT)
552 FullSearchCount++; // Zähle, wie oft wir alles abgesucht haben!
554 for (FaceNr=0; FaceNr<Map.FaceChildren.Size() && BestUT<StopUT; FaceNr++)
556 const cf::SceneGraph::FaceNodeT::SHLMapInfoT& SMI=Map.FaceChildren[FaceNr]->SHLMapInfo;
558 for (unsigned long s=0; s<SMI.SizeS; s++)
559 for (unsigned long t=0; t<SMI.SizeT; t++)
561 const PatchT& P=Patches[FaceNr][t*SMI.SizeS+s];
563 double ThisUT=0.0;
564 for (unsigned long CoeffNr=0; CoeffNr<P.SHCoeffs_UnradiatedTransfer.Size(); CoeffNr++)
565 ThisUT+=fabs(P.SHCoeffs_UnradiatedTransfer[CoeffNr]);
567 if (ThisUT>BestUT)
569 Face_i=FaceNr;
570 s_i =s;
571 t_i =t;
572 BestUT=ThisUT;
578 printf("Iteration%6lu, BestUT %6.2f, F_i%5lu, FullSearch%4lu (%5.1f%%)\r", IterationCount, BestUT, Face_i, FullSearchCount, 100.0*float(FullSearchCount)/float(IterationCount+1));
579 fflush(stdout);
581 if (BestUT<StopUT) // Es gab keinen besseren Wert mehr -- wir können also abbrechen!
583 printf("\n");
584 if (!AskForMore) break;
586 time_t StartTime=time(NULL);
588 printf("\nStopUT value %10.7f has been reached.\n", StopUT);
589 printf("Press 'y' to confirm exit and save current result,\n");
590 printf("or any other key to divide StopUT by 10 and continue lighting!\n");
592 char Key=_getch(); if (Key==0) (void)_getch();
594 unsigned long TotalSec=(unsigned long)difftime(time(NULL), StartTime);
595 unsigned long Sec =TotalSec % 60;
596 unsigned long Min =(TotalSec/60) % 60;
597 unsigned long Hour =TotalSec/3600;
598 printf("Length of break (waiting for your decision) was %2lu:%2lu:%2lu.\n", Hour, Min, Sec);
600 if (Key=='y') break;
601 StopUT/=10.0;
604 RadiateTransfer(CaSHLWorld, Face_i, s_i, t_i, BLOCK_SIZE);
605 IterationCount++;
607 #ifdef _WIN32
608 // TODO: Ein (sinnvolles!) 'kbhit()' Äquivalent für Linux muß erst noch gefunden werden...
609 if (_kbhit())
611 char Key=_getch(); if (Key==0) (void)_getch();
613 if (Key==' ')
615 printf("\nINTERRUPTED BY USER! Press 'y' to confirm exit and save current result,\n");
616 printf("or any other key to continue lighting!\n");
618 Key=_getch(); if (Key==0) (void)_getch();
619 if (Key=='y') break;
622 #endif
625 return IterationCount;
629 #include "Ward97.cpp" // void ToneReproduction(const cf::SceneGraph::BspTreeNodeT& Map) { ... }
632 void PostProcessBorders(const CaSHLWorldT& CaSHLWorld)
634 const cf::SceneGraph::BspTreeNodeT& Map=CaSHLWorld.GetBspTree();
636 // An dieser Stelle haben wir nun quasi drei Sorten von Patches für eine Face:
637 // a) Patches, die 'InsideFace' liegen, und das vollständig.
638 // b) Patches, die 'InsideFace' liegen, aber nur teilweise (ihre Patch.Coord ist entsprechend verschoben!).
639 // c) Patches, die nicht 'InsideFace' liegen.
640 // Für Patch-Sorten a) und b) hat unser Algorithmus Patch.TotalEnergy-Werte berechnet.
641 // Unsere Aufgabe hier ist im wesentlichen das sinnvolle Ausfüllen der TotalEnergy-Werte von Patches der Sorte c).
642 // Dies ist notwendig, da die Patches von OpenGL beim Rendern bilinear interpoliert werden (2x2-Array Durchschnitt),
643 // und deswegen ohne weitere Maßnahmen schwarze Ränder bekämen.
644 printf("\n%-50s %s\n", "*** Post-Process Borders ***", GetTimeSinceProgramStart());
647 // ERSTER TEIL
648 // ***********
650 // Für alle Patches einer Face, die keinen SamplePoint innerhalb ihrer Face haben, ermittele ihren Wert aus dem
651 // Durchschnitt ihrer acht umliegenden Patches, sofern diese mit SamplePoints innerhalb der Face liegen.
652 // Diese Methode ist sehr einfach und schnell, da sie immer nur eine Face gleichzeitig betrachtet,
653 // die Nachbarumgebung hat keinen Einfluß.
654 // Dennoch ist diese Schleife ein guter Anfang, und war vorher sogar der *einzige* Nachbearbeitungsschritt!
655 // TODO: Ein "Weighted Average" wäre vielleicht besser, zumindest die "Ecken" des 3x3-Feldes könnten u.U.
656 // schwächer eingebracht werden (z.B. als ob es ein Kreis mit Radius 1,5 wäre). Nochmal überdenken!
657 // Damit könnte dann wohl auch die Spezialbehandlung der Ränder entfallen!?
658 for (unsigned long FaceNr=0; FaceNr<Map.FaceChildren.Size(); FaceNr++)
660 const cf::SceneGraph::FaceNodeT::SHLMapInfoT& SMI=Map.FaceChildren[FaceNr]->SHLMapInfo;
662 for (unsigned long t=0; t<SMI.SizeT; t++)
663 for (unsigned long s=0; s<SMI.SizeS; s++)
665 if (Patches[FaceNr][t*SMI.SizeS+s].InsideFace) continue;
667 const unsigned long SMI_SizeS=SMI.SizeS;
668 const unsigned long SMI_SizeT=SMI.SizeT;
670 ArrayT<double>& Cs=Patches[FaceNr][t*SMI.SizeS+s].SHCoeffs_TotalTransfer;
672 if (s==0 && t>0 && t<SMI_SizeT-1) Cs=Patches[FaceNr][ t *SMI.SizeS+ 1].SHCoeffs_TotalTransfer; // linker Rand (ohne Ecken)
673 else if (s==SMI_SizeS-1 && t>0 && t<SMI_SizeT-1) Cs=Patches[FaceNr][ t *SMI.SizeS+SMI.SizeS-2].SHCoeffs_TotalTransfer; // rechter Rand (ohne Ecken)
674 else if (t==0 && s>0 && s<SMI_SizeS-1) Cs=Patches[FaceNr][ 1 *SMI.SizeS+ s].SHCoeffs_TotalTransfer; // oberer Rand (ohne Ecken)
675 else if (t==SMI_SizeT-1 && s>0 && s<SMI_SizeS-1) Cs=Patches[FaceNr][(SMI.SizeT-2)*SMI.SizeS+ s].SHCoeffs_TotalTransfer; // unterer Rand (ohne Ecken)
676 else // Alle anderen Patches (inkl. Ecken)
678 const unsigned long NR_OF_SH_COEFFS=cf::SceneGraph::SHLMapManT::NrOfBands * cf::SceneGraph::SHLMapManT::NrOfBands;
679 ArrayT<double> Average;
680 char AverageCount=0;
682 while (Average.Size()<NR_OF_SH_COEFFS) Average.PushBack(0.0);
684 // Der Patch liegt in der Mitte eines 3x3-Feldes bei Koordinate (1,1).
685 for (char y=0; y<=2; y++)
686 for (char x=0; x<=2; x++)
688 if (x==1 && y==1) continue; // Nur die acht umliegenden Patches betrachten
690 if (s==0 && x==0) continue; // Linken Rand beachten
691 if (s==SMI_SizeS-1 && x==2) continue; // Rechten Rand beachten
692 if (t==0 && y==0) continue; // Oberen Rand beachten
693 if (t==SMI_SizeT-1 && y==2) continue; // Unteren Rand beachten
695 if (!Patches[FaceNr][(t+y-1)*SMI.SizeS+(s+x-1)].InsideFace) continue;
697 for (unsigned long CoeffNr=0; CoeffNr<NR_OF_SH_COEFFS; CoeffNr++)
698 Average[CoeffNr]+=Patches[FaceNr][(t+y-1)*SMI.SizeS+(s+x-1)].SHCoeffs_TotalTransfer[CoeffNr];
699 AverageCount++;
702 if (AverageCount)
703 for (unsigned long CoeffNr=0; CoeffNr<NR_OF_SH_COEFFS; CoeffNr++)
704 Cs[CoeffNr]=Average[CoeffNr]/double(AverageCount);
710 // ZWEITER TEIL
711 // ************
713 // Betrachte im nächsten Schritt Faces, die in einer gemeinsamen Ebene nahe beieinander liegen, und versuche,
714 // den "Übergang" zu verbessern. Der vorangegangene erste Schritt eliminiert zwar Fehlfarben an den Rändern,
715 // die OpenGL's bilinear Filtering ansonsten ins Spiel gebracht hätte, an Stellen mit hohen Kontrasten
716 // ("scharfe" Schatten usw.) sieht man aber unbeabsichtigte, harte Übergänge an den Kanten solcher Faces.
717 // Der folgende Code eliminiert solche Sprünge nun größtenteils, indem er auch die Patches der anderen Faces betrachtet.
718 // Damit werden für die Ränder die "realen" Berechnungsergebnisse eingebracht, nicht einfach nur eigene Mittelwerte.
719 // Der folgende Code könnte algorithmisch effizienter geschrieben sein (z.B. Vorausberechnen von wiederkehrenden
720 // Werten, statt diese jedesmal in einer Schleife neu zu berechnen), aber die praktische Laufzeit ist akzeptabel.
722 // Vorgehensweise:
723 // Zu jedem Patch P1 jeder Face suchen wir alle Patches Pi heraus, die irgendetwas zu P1 beitragen könnten.
724 // Dazu müssen sich P1 und die Pi insbesondere in der selben Ebene überlappen.
725 // Wegen der global einheitlichen Ausrichtung aller Patches in einer Ebene sind Überlappungen immer "ganz oder gar nicht".
726 // Jeder Patch wird klassifiziert, ob er vollständig in seiner Face liegt (INNER), auf dem Rand (PARTIAL),
727 // oder vollständig außerhalb (OUTER). Somit ergeben sich folgende Möglichkeiten:
729 // P1 \ Pi | INNER | PARTIAL | OUTER
730 // --------+---------+---------+---------
731 // INNER | 11 | 12 | 13
732 // PARTIAL | 21 | 22 | 23
733 // OUTER | 31 | 32 | 33
735 // Die Fälle P1==INNER (11, 12, 13) interessieren uns nicht, da wir für solche P1 einwandfreie Berechnungsergebnisse haben,
736 // und es keinen Grund gibt, mit anderen Werten (von Pi's) daran etwas herumzufummeln.
737 // Die Fälle Pi==OUTER (13, 23, 33) können wir auch direkt überspringen, weil es keinen Sinn macht, P1 irgendwie mit einem
738 // anderen OUTER-Patch zu modifizieren - dieser hat schließlich selbst keinen verwendbaren Wert.
739 // Der Fall 21 kann niemals vorkommen, denn ein Patch kann nicht vollständig in einer Face liegen, und teilweise in einer anderen
740 // (Faces überlappen sich nicht!). Es müssen also nur die Fälle 22, 31 und 32 betrachtet werden.
742 // In allen drei verbleibenden Fällen gilt, daß ein Pi nur dann beitragen darf, wenn er damit kein "Light Bleeding" verursacht.
743 // "Light Bleeding" ist die Beeinflussung von P1 durch andere Patches Pi, deren Faces sich zwar nahe, aber in Wirklichkeit z.B.
744 // durch eine dünne "Wand" getrennt sind, oder die Patches liegen "um die Ecke".
745 // Um das "Light Bleeding" Problem zu minimieren, erlauben wir die Beeinflussung von P1 durch ein Pi nur dann, wenn ein Punkt,
746 // der garantiert in der Face von P1 und möglichst nahe bei P1 liegt, die Pi.Coord "sehen" kann.
747 // Analytisch mag das nicht 100%ig korrekt sein (???), ergibt in der Praxis aber einwandfreie Ergebnisse!
749 // Fall 31 läßt sich nun leicht abhaken: Max. ein Pi ist möglich, und damit erhalten wir P1=Pi.
750 // Für Fall 32 erhalten wir P1="area-weighted average of the involved Pi".
752 // Fall 22 ist etwas schwieriger: Im Idealfall würden wir schreiben: P1="area-weighted average of the involved Pi and P1 itself".
753 // Dummerweise verändern wir damit P1, welches aber wiederum später eines der jetzigen Pi beeinflussen wird, wenn dieses Pi an der
754 // Reihe ist. Dann müßte P1 aber seinen Original-Wert haben, nicht den, den wir gerade dabei sind hineinzuschreiben.
755 // (Das sieht man auch daran, daß Fall 22 in obiger Matrix auf der Hauptdiagonalen liegt. Die beiden Fälle 31 und 32 (und 21) liegen
756 // im unteren linken Dreieck, während das rechte obere Dreieck (12, 13, 23) "inaktiv" ist. Deshalb sind 31 und 32 unproblematisch.)
757 // Lösung: Schreibe den gefundenen Wert nicht nur nach P1, sondern auch in ALLE beteiligten Pi. Das würde das Problem vollständig(!)
758 // lösen, denn spätere Mittelwertbildungen wären zwar redundant, kämen aber zu dem selben (korrekten) Ergebnis.
759 // Leider macht das "Light Bleeding"-Problem einen Strich durch die Rechnung! Wenn z.B. Pi={ P2, P3 }, dann könnte P1 von P2 beeinflußt
760 // werden, P3 aber wegen "Light Bleeding" vom Einfluß auf P1 ausgeschlossen werden. P2 könnte später aber sehr wohl von P1 UND P3 beeinflußt
761 // werden! P2 drücken wir aber nach obiger "Lösung" aber schon mal einen Mittelwert auf, und insgesamt kommt es zum falschen Ergebnis.
762 // INSGESAMT wäre die einzige WIRKLICHE Lösung, ein Backup aller Patches anzulegen, und alle Mittelwerte stets daraus zu bilden.
763 // Das verdoppelt aber sofort den Speicherbedarf, der ohnehin schon bei mehreren hundert MB liegt. Außerdem ist der bei
764 // der ersten "Lösung" entstehende Fehler wohl sehr selten und sehr klein. Konsequenz: Wir ignorieren ihn, und wenden die erste Lösung an!
766 // Zuletzt ist noch zu beachten, daß das Ausschließen von Patches wg. Light Bleeding dazu führen kann, daß die Summe der Gewichte beim
767 // weighted average < 1.0 wird. In diesen Fällen müssen die Gewichte "renormalisiert" werden.
768 // Zu diesen Ausführungen siehe auch die Skizze im Cafu Tech-Archive vom 10.12.2003!
769 const double PATCH_SIZE =Map.GetSHLMapPatchSize();
770 unsigned long PatchesWorkedOnCount=0;
772 for (unsigned long Face1Nr=0; Face1Nr<Map.FaceChildren.Size(); Face1Nr++)
774 const Polygon3T<double>& Face1=Map.FaceChildren[Face1Nr]->Polygon;
775 const cf::SceneGraph::FaceNodeT::LightMapInfoT& Face1_SMI=Map.FaceChildren[Face1Nr]->LightMapInfo;
776 ArrayT<unsigned long> NearFaces;
778 printf("%5.1f%%\r", (double)Face1Nr/Map.FaceChildren.Size()*100.0);
779 fflush(stdout);
781 // Bilde zuerst eine Liste (von Indizes) von Faces, die Face1 "nahe" sind.
782 for (unsigned long Face2Nr=0; Face2Nr<Map.FaceChildren.Size(); Face2Nr++)
784 const Polygon3T<double>& Face2=Map.FaceChildren[Face2Nr]->Polygon;
786 // Wir wollen nicht gegen uns selbst testen!
787 if (Face1Nr==Face2Nr) continue;
789 // Nur Faces in der gleichen Ebene mit gleicher Orientierung durchgehen lassen!
790 if (Face1.WhatSide(Face2.Plane, MapT::RoundEpsilon)!=Polygon3T<double>::InIdentical) continue;
792 // Faces, die zu weit voneinander entfernt liegen, brauchen nicht weiter betrachtet zu werden!
793 const VectorT BorderPadding(PATCH_SIZE, PATCH_SIZE, PATCH_SIZE);
795 BoundingBox3T<double> BB1(Face1.Vertices); BB1.Min-=BorderPadding; BB1.Max+=BorderPadding;
796 BoundingBox3T<double> BB2(Face2.Vertices); BB2.Min-=BorderPadding; BB2.Max+=BorderPadding;
798 if (!BB1.Intersects(BB2)) continue;
800 // Ein Sanity-Check, wg. Rundungsfehlern.
801 // Eigentlich müssen beide Planes nämlich exakt gleich sein!
802 if (Face1.Plane.Normal.x!=Face2.Plane.Normal.x) printf("WARNING: Face1.Plane.Normal.x!=Face2.Plane.Normal.x (%.15f != %.15f)\n", Face1.Plane.Normal.x, Face2.Plane.Normal.x);
803 if (Face1.Plane.Normal.y!=Face2.Plane.Normal.y) printf("WARNING: Face1.Plane.Normal.y!=Face2.Plane.Normal.y (%.15f != %.15f)\n", Face1.Plane.Normal.y, Face2.Plane.Normal.y);
804 if (Face1.Plane.Normal.z!=Face2.Plane.Normal.z) printf("WARNING: Face1.Plane.Normal.z!=Face2.Plane.Normal.z (%.15f != %.15f)\n", Face1.Plane.Normal.z, Face2.Plane.Normal.z);
805 if (Face1.Plane.Dist !=Face2.Plane.Dist ) printf("WARNING: Face1.Plane.Dist !=Face2.Plane.Dist (%.15f != %.15f)\n", Face1.Plane.Dist , Face2.Plane.Dist );
807 // Diese Face kommt in Betracht, speichere ihre Nummer.
808 NearFaces.PushBack(Face2Nr);
811 // Bereite nun das PatchPoly vor. Unten werden dann nur noch die 4 Vertices ausgefüllt.
812 // WICHTIG: Dieses PatchPoly ist für zwei sich überlappende Patches EXAKT IDENTISCH, da alle Patches gleich ausgerichtet sind!
813 // Der folgende Code ist SEHR ähnlich zu dem Code in InitializePatches() (Init2.cpp)!
815 // Bestimme die Spannvektoren der gemeinsamen Ebene.
816 // (Face1 und die Faces2 unten liegen in einer *gemeinsamen* Ebene!)
817 VectorT U;
818 VectorT V;
820 Face1.Plane.GetSpanVectors(U, V);
822 // Finde SmallestU und SmallestV.
823 double Face1_SmallestU=dot(Face1.Vertices[0], U);
824 double Face1_SmallestV=dot(Face1.Vertices[0], V);
826 for (unsigned long VertexNr=1; VertexNr<Face1.Vertices.Size(); VertexNr++)
828 double u=dot(Face1.Vertices[VertexNr], U);
829 double v=dot(Face1.Vertices[VertexNr], V);
831 if (u<Face1_SmallestU) Face1_SmallestU=u;
832 if (v<Face1_SmallestV) Face1_SmallestV=v;
835 Face1_SmallestU=floor(Face1_SmallestU/PATCH_SIZE);
836 Face1_SmallestV=floor(Face1_SmallestV/PATCH_SIZE);
838 const VectorT UV_Origin=scale(Face1.Plane.Normal, Face1.Plane.Dist);
839 const VectorT Safety =scale(Face1.Plane.Normal, 0.1);
841 Polygon3T<double> PatchPoly;
843 PatchPoly.Plane=dot(Face1.Plane.Normal, cross(U, V))<0 ? Face1.Plane : Face1.Plane.GetMirror();
844 PatchPoly.Vertices.PushBackEmpty(4);
846 // Betrachte nun alle Patches von Face1.
847 for (unsigned long t1=0; t1<Face1_SMI.SizeT; t1++)
848 for (unsigned long s1=0; s1<Face1_SMI.SizeS; s1++)
850 PatchT& Patch1=Patches[Face1Nr][t1*Face1_SMI.SizeS+s1];
852 // Rekonstruiere das Polygon zu Patch1.
853 PatchPoly.Vertices[0]=UV_Origin+scale(U, (Face1_SmallestU+s1-1.0)*PATCH_SIZE)+scale(V, (Face1_SmallestV+t1-1.0)*PATCH_SIZE);
854 PatchPoly.Vertices[1]=UV_Origin+scale(U, (Face1_SmallestU+s1 )*PATCH_SIZE)+scale(V, (Face1_SmallestV+t1-1.0)*PATCH_SIZE);
855 PatchPoly.Vertices[2]=UV_Origin+scale(U, (Face1_SmallestU+s1 )*PATCH_SIZE)+scale(V, (Face1_SmallestV+t1 )*PATCH_SIZE);
856 PatchPoly.Vertices[3]=UV_Origin+scale(U, (Face1_SmallestU+s1-1.0)*PATCH_SIZE)+scale(V, (Face1_SmallestV+t1 )*PATCH_SIZE);
858 // Klassifizierung: Falls Patch1 *vollständig in* seiner Face liegt, haben wir dafür einen
859 // einwandfreien Berechnungswert, und wir wollen daran nicht rumfummeln (Fälle 11, 12, 13)!
860 // Ob Patch1 PARTIAL oder OUTER ist, wird weiter unten genauer klassifiziert.
861 if (Face1.Encloses(PatchPoly, true, MapT::RoundEpsilon)) continue;
864 // Den Mittelpunkt des PatchPoly bestimmen, inkl. "Safety".
865 const VectorT PatchPoly_Center=scale(PatchPoly.Vertices[0]+PatchPoly.Vertices[1]+PatchPoly.Vertices[2]+PatchPoly.Vertices[3], 0.25)+Safety;
867 // Suche einen Punkt *IN* Face1 heraus, der "nahe" bei Patch1 liegt. Wird unten benötigt.
868 double MinDistance=3.0*PATCH_SIZE;
869 VectorT InnerPointCloseToPatch1;
871 for (unsigned long PatchNr=0; PatchNr<Patches[Face1Nr].Size(); PatchNr++)
873 const PatchT& TempPatch=Patches[Face1Nr][PatchNr];
875 // 'TempPatch' darf auch ruhig 'Patch1' sein, da 'Patch1.Coord' durchaus ein Punkt in Face1 ist, der "nahe" bei Patch1 liegt!
876 if (!TempPatch.InsideFace) continue;
878 double Distance=length(TempPatch.Coord-PatchPoly_Center);
880 if (Distance<MinDistance)
882 MinDistance =Distance;
883 InnerPointCloseToPatch1=TempPatch.Coord;
887 // Wurde auch etwas in der Nähe gefunden?
888 if (MinDistance==3.0*PATCH_SIZE) continue;
891 // Betrachte nun die umliegenden Faces, um herauszufinden, ob sie Patches enthalten,
892 // die zu "unserem" Patch etwas beitragen könnten.
893 ArrayT<PatchT*> ContributingPatches;
894 ArrayT<double > ContributingPatchesInFace; // Mit wieviel % seiner Fläche liegt der Patch in seiner Face?
896 for (unsigned long NearNr=0; NearNr<NearFaces.Size(); NearNr++)
898 const Polygon3T<double>& Face2=Map.FaceChildren[NearFaces[NearNr]]->Polygon;
899 const cf::SceneGraph::FaceNodeT::LightMapInfoT& Face2_SMI=Map.FaceChildren[NearFaces[NearNr]]->LightMapInfo;
901 double Face2_SmallestU=dot(Face2.Vertices[0], U);
902 double Face2_SmallestV=dot(Face2.Vertices[0], V);
904 for (unsigned long VertexNr=1; VertexNr<Face2.Vertices.Size(); VertexNr++)
906 double u=dot(Face2.Vertices[VertexNr], U);
907 double v=dot(Face2.Vertices[VertexNr], V);
909 if (u<Face2_SmallestU) Face2_SmallestU=u;
910 if (v<Face2_SmallestV) Face2_SmallestV=v;
913 Face2_SmallestU=floor(Face2_SmallestU/PATCH_SIZE);
914 Face2_SmallestV=floor(Face2_SmallestV/PATCH_SIZE);
916 // Gehe die Patches von Face2 durch
917 for (unsigned long t2=0; t2<Face2_SMI.SizeT; t2++)
918 for (unsigned long s2=0; s2<Face2_SMI.SizeS; s2++)
919 // Überlappen sich die Patches? Beachte: Wenn, dann ist es eine *exakte* Überlappung, wegen gleichem Patch-Alignment!
920 if ((unsigned long)(Face1_SmallestU)+s1==(unsigned long)(Face2_SmallestU)+s2 &&
921 (unsigned long)(Face1_SmallestV)+t1==(unsigned long)(Face2_SmallestV)+t2)
923 PatchT& Patch2=Patches[NearFaces[NearNr]][t2*Face2_SMI.SizeS+s2];
925 // Nur "äußere" Patches von Face1 mit "inneren" Patches von Face1 korrigieren!
926 if (!Patch2.InsideFace) continue;
928 // Avoid "Light Bleeding" by this simple visibility test.
929 if (CaSHLWorld.TraceRay(InnerPointCloseToPatch1, Patch2.Coord-InnerPointCloseToPatch1)<1.0) continue;
932 ArrayT< Polygon3T<double> > NewPolygons;
934 PatchPoly.GetChoppedUpAlong(Face2, MapT::RoundEpsilon, NewPolygons);
935 if (NewPolygons.Size()==0) Error("PolygonChopUp failed in PostProcessBorders().");
937 // Mit wieviel % seiner Fläche liegt PatchPoly in Face2?
938 const double AreaRatio=NewPolygons[NewPolygons.Size()-1].GetArea()/PatchPoly.GetArea();
940 ContributingPatches .PushBack(&Patch2);
941 ContributingPatchesInFace.PushBack(AreaRatio);
944 // Directly continue with the next face.
945 t2=Face2_SMI.SizeT;
946 break;
951 if (ContributingPatches.Size()==0) continue;
953 const unsigned long NR_OF_SH_COEFFS=cf::SceneGraph::SHLMapManT::NrOfBands * cf::SceneGraph::SHLMapManT::NrOfBands;
956 // Klassifiziere Patch1 weiter:
957 // Daß er INNER ist, haben wir oben schon ausgeschlossen.
958 // Er ist PARTIAL, wenn Patch1.InsideFace==true, sonst OUTER.
959 if (Patch1.InsideFace)
961 // Patch1 is "PARTIAL" (partially inside of Face1) - dies ist also Fall 22.
962 // Note that in case 22, Patch1 is contributing to itself, thus add it to the ContributingPatches and ContributingPatchesInFace arrays.
963 ArrayT< Polygon3T<double> > NewPolygons;
965 PatchPoly.GetChoppedUpAlong(Face1, MapT::RoundEpsilon, NewPolygons);
966 if (NewPolygons.Size()==0) Error("PolygonChopUp failed in PostProcessBorders().");
968 // Mit wieviel % seiner Fläche liegt PatchPoly in Face1?
969 const double AreaRatio=NewPolygons[NewPolygons.Size()-1].GetArea()/PatchPoly.GetArea();
971 ContributingPatches .PushBack(&Patch1);
972 ContributingPatchesInFace.PushBack(AreaRatio);
976 // Re-normalize the contribution percentages of the contributing patches.
977 // This is necessary because light-bleeding might omit patches that otherwise had contributed.
978 unsigned long CPNr;
979 double Sum=0.0;
981 for (CPNr=0; CPNr<ContributingPatchesInFace.Size(); CPNr++) Sum+=ContributingPatchesInFace[CPNr];
982 for (CPNr=0; CPNr<ContributingPatchesInFace.Size(); CPNr++) ContributingPatchesInFace[CPNr]/=Sum;
985 // Compute the weighted average.
986 ArrayT<double> ResultCoeffs;
988 unsigned long CoeffNr;
989 for (CoeffNr=0; CoeffNr<NR_OF_SH_COEFFS; CoeffNr++) ResultCoeffs.PushBack(0.0);
991 for (CPNr=0; CPNr<ContributingPatches.Size(); CPNr++)
992 for (CoeffNr=0; CoeffNr<NR_OF_SH_COEFFS; CoeffNr++)
993 ResultCoeffs[CoeffNr]+=ContributingPatches[CPNr]->SHCoeffs_TotalTransfer[CoeffNr]*ContributingPatchesInFace[CPNr];
995 if (Patch1.InsideFace)
997 for (CPNr=0; CPNr<ContributingPatches.Size(); CPNr++)
998 ContributingPatches[CPNr]->SHCoeffs_TotalTransfer=ResultCoeffs; // Case 22.
1000 else Patch1.SHCoeffs_TotalTransfer=ResultCoeffs; // Case 31 or 32.
1002 PatchesWorkedOnCount++;
1006 printf("Borders completed. %lu patches modified in 2nd part.\n", PatchesWorkedOnCount);
1010 void Usage()
1012 printf("\n");
1013 printf("USAGE: CaSHL WorldName [OPTIONS]\n");
1014 printf("\n");
1015 printf("-Bands n Number of SH bands (yielding n*n SH coeffs).\n");
1016 printf(" n must be in range 0..10, default is %u.\n", cf::SceneGraph::SHLMapManT::NrOfBands);
1017 printf("-NrOfSamples n Approx. number of SH samples (I'll determine the exact number).\n");
1018 printf(" n must be in range 20..100000, default is 10000.\n");
1019 printf("-SkipBL Skip bounce lighting. Without BL, what I compute is analogous to\n");
1020 printf(" Robin Greens \"2 Shadowed Diffuse Transfer\". With BL, it is\n");
1021 printf(" analogous to \"3 Diffuse Interreflected Transfer\" (p. 31).\n");
1022 printf("-BlockSize n Radiative block size for faster bounce lighting.\n");
1023 printf(" n must be in range 1..8, default is 3.\n");
1024 printf("-StopUT f Stop value for unradiated transfer. 0 < f <= 10, default is 0.1.\n");
1025 printf("-AskForMore Asks for a new StopUT value when the old one has been reached.\n");
1026 printf("-NoFullVis Do not precompute 'full vis' acceleration information.\n");
1027 printf(" Makes initialization much faster, but lighting a bit slower.\n");
1028 printf("-fast Same as \"-BlockSize 5 -NoFullVis\".\n");
1029 printf("-Reps n Number of representative SH vectors used for compression.\n");
1030 printf(" Default is %u. 0 means no compression.\n", cf::SceneGraph::SHLMapManT::NrOfRepres);
1031 printf("\n");
1032 printf("\n");
1033 printf("EXAMPLES:\n");
1034 printf("\n");
1035 printf("CaSHL WorldName -AskForMore\n");
1036 printf(" I'll start with the default parameters, light the world WorldName and\n");
1037 printf(" finally ask you what to do when the StopUT value has been reached.\n");
1038 printf("\n");
1039 printf("CaSHL WorldName -StopUT 0.1\n");
1040 printf(" Most worlds of the Cafu demo release are lit with these switches.\n");
1041 printf("\n");
1042 printf("CaSHL WorldName -BlockSize 1 -StopUT 0.1\n");
1043 printf(" This is ideal for batch file processing: WorldName is lit without further\n");
1044 printf(" user questioning and I'll terminate as soon as StopUT has been reached.\n");
1045 printf(" Note that BlockSize and StopUT are set for high-quality lighting here.\n");
1046 printf("\n");
1047 printf("CaSHL WorldName -fast\n");
1048 printf("CaSHL WorldName -BlockSize 5 -NoFullVis\n");
1049 printf(" Fast and ugly lighting, intended for quick tests during world development.\n");
1050 exit(1);
1054 static void WriteLogFileEntry(const char* WorldPathName, double StopUT, char BlockSize, unsigned long IterationCount)
1056 char DateTime [256]="unknown";
1057 char HostName [256]="unknown";
1058 char WorldName[256]="unknown";
1059 time_t Time =time(NULL);
1060 unsigned long RunningSec =(unsigned long)difftime(Time, ProgramStartTime);
1061 FILE* LogFile =fopen("CaSHL.log", "a");
1063 if (!LogFile) return;
1065 strftime(DateTime, 256, "%d.%m.%Y %H:%M", localtime(&Time));
1066 DateTime[255]=0;
1068 #ifdef _WIN32
1069 unsigned long Dummy=256;
1070 if (!GetComputerName(HostName, &Dummy)) sprintf(HostName, "unknown (look-up failed).");
1071 #else
1072 // This function also works on Windows, but sadly requires calls to 'WSAStartup()' and 'WSACleanup()'.
1073 if (gethostname(HostName, 256)) sprintf(HostName, "unknown (look-up failed).");
1074 #endif
1075 HostName[255]=0;
1077 if (WorldPathName)
1079 // Dateinamen abtrennen (mit Extension).
1080 size_t i=strlen(WorldPathName);
1082 while (i>0 && WorldPathName[i-1]!='/' && WorldPathName[i-1]!='\\') i--;
1083 strncpy(WorldName, WorldPathName+i, 256);
1084 WorldName[255]=0;
1086 // Extension abtrennen.
1087 i=strlen(WorldName);
1089 while (i>0 && WorldName[i-1]!='.') i--;
1090 if (i>0) WorldName[i-1]=0;
1093 // Date, Time, WorldName, TimeForCompletion on [HostName]
1094 fprintf(LogFile, "%-16s %-16s%3lu:%02lu:%02lu [%-16s]%8.5f %ux%u%8lu\n", DateTime, WorldName, RunningSec/3600, (RunningSec/60) % 60, RunningSec % 60, HostName, StopUT, BlockSize, BlockSize, IterationCount);
1095 fclose(LogFile);
1099 int main(int ArgC, const char* ArgV[])
1101 // cf::GameSys::GetComponentTIM().Init(); // The one-time init of the GameSys components type info manager.
1102 // cf::GameSys::GetGameSysEntityTIM().Init(); // The one-time init of the GameSys entity type info manager.
1103 // cf::GameSys::GetWorldTIM().Init(); // The one-time init of the GameSys world type info manager.
1105 struct CaSHLOptionsT
1107 char BlockSize;
1108 double StopUT;
1109 bool AskForMore;
1110 bool UseFullVis;
1111 unsigned long SqrtNrOfSamples;
1112 bool SkipBL;
1114 CaSHLOptionsT() : BlockSize(3), StopUT(0.1), AskForMore(false), UseFullVis(true), SqrtNrOfSamples(100), SkipBL(false) {}
1115 } CaSHLOptions;
1117 cf::SceneGraph::SHLMapManT::NrOfBands=4;
1120 // Init screen
1121 printf("\n*** Cafu SHL Utility, Version 01 (%s) ***\n\n\n", __DATE__);
1122 #if USE_NORMALMAPS
1123 #if !defined(_MSC_VER)
1124 printf("This version of CaSHL takes the normal-maps of surfaces into account!\n");
1126 // As we need access to the normal-maps, do a quick check if "./Textures" is a proper directory.
1127 DIR* TempDir=opendir("./Textures");
1129 if (TempDir==NULL)
1131 printf("I'm sorry, but for taking normal-maps into account, I need to be able to find\n");
1132 printf("them. I thus looked for the \"./Textures\" directory, but wasn't able to find it.\n");
1133 printf("Please cd into the \"Games/DeathMatch\" directory and run me again from there!\n");
1135 Error("Texture directory not found.\n");
1137 closedir(TempDir);
1138 #endif
1139 #endif
1141 // Initialize the FileMan by mounting the default file system.
1142 // Note that specifying "./" (instead of "") as the file system description effectively prevents the use of
1143 // absolute paths like "D:\abc\someDir\someFile.xy" or "/usr/bin/xy". This however should be fine for this application.
1144 cf::FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_LOCAL_PATH, "./", "");
1145 // cf::FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_ZIP_ARCHIVE, "Games/DeathMatch/Textures/TechDemo.zip", "Games/DeathMatch/Textures/TechDemo/", "Ca3DE");
1146 // cf::FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_ZIP_ARCHIVE, "Games/DeathMatch/Textures/SkyDomes.zip", "Games/DeathMatch/Textures/SkyDomes/", "Ca3DE");
1148 if (ArgC<2) Usage();
1150 // Process command line
1151 for (int CurrentArg=2; CurrentArg<ArgC; CurrentArg++)
1153 if (!_stricmp(ArgV[CurrentArg], "-Bands"))
1155 if (CurrentArg+1==ArgC) Error("I can't find a number after \"-Bands\"!");
1156 CurrentArg++;
1158 cf::SceneGraph::SHLMapManT::NrOfBands=atoi(ArgV[CurrentArg]);
1159 if (cf::SceneGraph::SHLMapManT::NrOfBands>10) Error("Bands must be in range 0..10.");
1161 else if (!_stricmp(ArgV[CurrentArg], "-NrOfSamples"))
1163 if (CurrentArg+1==ArgC) Error("I can't find a number after \"-NrOfSamples\"!");
1164 CurrentArg++;
1166 const int NrOfSamples=atoi(ArgV[CurrentArg]);
1167 if (NrOfSamples<20 || NrOfSamples>100000) Error("NrOfSamples must be in range 20..100000.");
1169 CaSHLOptions.SqrtNrOfSamples=(unsigned long)(sqrt(double(NrOfSamples))+0.5);
1171 else if (!_stricmp(ArgV[CurrentArg], "-SkipBL"))
1173 CaSHLOptions.SkipBL=true;
1175 else if (!_stricmp(ArgV[CurrentArg], "-BlockSize"))
1177 if (CurrentArg+1==ArgC) Error("I can't find a number after \"-BlockSize\"!");
1178 CurrentArg++;
1179 CaSHLOptions.BlockSize=atoi(ArgV[CurrentArg]);
1180 if (CaSHLOptions.BlockSize<1 || CaSHLOptions.BlockSize>8) Error("BlockSize must be in range 1..8.");
1182 else if (!_stricmp(ArgV[CurrentArg], "-StopUT"))
1184 if (CurrentArg+1==ArgC) Error("I can't find a number after \"-StopUT\"!");
1185 CurrentArg++;
1186 CaSHLOptions.StopUT=atof(ArgV[CurrentArg]);
1187 if (CaSHLOptions.StopUT<=0.0 || CaSHLOptions.StopUT>10.0) Error("StopUT must be in ]0, 10].");
1189 else if (!_stricmp(ArgV[CurrentArg], "-AskForMore"))
1191 CaSHLOptions.AskForMore=true;
1193 else if (!_stricmp(ArgV[CurrentArg], "-NoFullVis"))
1195 CaSHLOptions.UseFullVis=false;
1197 else if (!_stricmp(ArgV[CurrentArg], "-fast"))
1199 CaSHLOptions.BlockSize=5;
1200 CaSHLOptions.UseFullVis=false;
1202 else if (!_stricmp(ArgV[CurrentArg], "-Reps"))
1204 if (CurrentArg+1==ArgC) Error("I can't find a number after \"-Reps\"!");
1205 CurrentArg++;
1207 cf::SceneGraph::SHLMapManT::NrOfRepres=atoi(ArgV[CurrentArg]);
1208 if (cf::SceneGraph::SHLMapManT::NrOfRepres>65536) Error("Reps must be in range 0..65536.");
1210 else if (ArgV[CurrentArg][0]==0)
1212 // The argument is "", the empty string.
1213 // This can happen under Linux, when CaSHL is called via wxExecute() with white-space trailing the command string.
1215 else
1217 printf("\nSorry, I don't know what \"%s\" means.\n", ArgV[CurrentArg]);
1218 Usage();
1223 std::string GameDirectory=ArgV[1];
1225 // Determine the game directory, cleverly assuming that the destination file is in "Worlds".
1227 // Strip the file name and extention off.
1228 size_t i=GameDirectory.find_last_of("/\\");
1230 GameDirectory=GameDirectory.substr(0, i==std::string::npos ? 0 : i)+"/..";
1234 // Setup the global MaterialManager pointer.
1235 static MaterialManagerImplT MatManImpl;
1237 MaterialManager=&MatManImpl;
1239 if (MaterialManager->RegisterMaterialScriptsInDir(GameDirectory+"/Materials", GameDirectory+"/").Size()==0)
1241 printf("\nNo materials found in scripts in \"%s/Materials\".\n", GameDirectory.c_str());
1242 printf("No materials found.\n\n");
1243 Usage();
1249 printf("Loading World '%s'.\n", ArgV[1]);
1251 const char Save_NrOfBands=cf::SceneGraph::SHLMapManT::NrOfBands;
1252 const unsigned long Save_NrOfReps =cf::SceneGraph::SHLMapManT::NrOfRepres;
1254 ModelManagerT ModelMan;
1255 cf::GuiSys::GuiResourcesT GuiRes(ModelMan);
1256 CaSHLWorldT CaSHLWorld(ArgV[1], ModelMan, GuiRes);
1258 cf::SceneGraph::SHLMapManT::NrOfBands =Save_NrOfBands;
1259 cf::SceneGraph::SHLMapManT::NrOfRepres=Save_NrOfReps;
1261 // Print out options summary
1262 printf("\n");
1263 printf("- cf::SceneGraph::SHLMapManT::NrOfBands is %u (yielding %u^2==%lu SH coeffs).\n", cf::SceneGraph::SHLMapManT::NrOfBands, cf::SceneGraph::SHLMapManT::NrOfBands, (unsigned long)(cf::SceneGraph::SHLMapManT::NrOfBands)*(unsigned long)(cf::SceneGraph::SHLMapManT::NrOfBands));
1264 printf("- Number of SH samples is %lu.\n", CaSHLOptions.SqrtNrOfSamples*CaSHLOptions.SqrtNrOfSamples);
1265 printf("- I will %s the bounce lighting phase.\n", CaSHLOptions.SkipBL ? "SKIP" : "NOT skip");
1266 printf("- BlockSize is %ux%u.\n", CaSHLOptions.BlockSize, CaSHLOptions.BlockSize);
1267 printf("- StopUT is %.3f.\n", CaSHLOptions.StopUT);
1268 printf("- I will %s you for more.\n", CaSHLOptions.AskForMore ? "ASK" : "NOT ask");
1269 printf("- I will %s the 'full vis' acceleration.\n", CaSHLOptions.UseFullVis ? "USE" : "NOT use");
1270 printf("- cf::SceneGraph::SHLMapManT::NrOfRepres is %u (compression is %s).\n", cf::SceneGraph::SHLMapManT::NrOfRepres, cf::SceneGraph::SHLMapManT::NrOfRepres>0 ? "ON" : "OFF");
1271 if (cf::SceneGraph::SHLMapManT::NrOfRepres>0)
1273 const unsigned long NR_OF_SH_COEFFS =cf::SceneGraph::SHLMapManT::NrOfBands * cf::SceneGraph::SHLMapManT::NrOfBands;
1274 const unsigned long NrOfColumns =(cf::SceneGraph::SHLMapManT::NrOfRepres+255)/256; // =ceil(double(cf::SceneGraph::SHLMapManT::NrOfRepres)/256);
1275 const unsigned long NrOfPixelsPerVector=(NR_OF_SH_COEFFS+3)/4;
1277 unsigned long Width=1; while (Width<NrOfColumns*NrOfPixelsPerVector) Width*=2;
1279 printf(" The look-up texture of representatives in the engine will have size %lu x 256 (%.1f%% unused).\n", Width, 100.0-100.0*double(cf::SceneGraph::SHLMapManT::NrOfRepres*NrOfPixelsPerVector)/double(256*Width));
1282 // Initialize
1283 InitializeFacePVSMatrix(CaSHLWorld, CaSHLOptions.UseFullVis); // Init1.cpp
1284 InitializePatches(CaSHLWorld.GetBspTree()); // Init2.cpp
1286 // Perform lighting.
1287 DirectLighting(CaSHLWorld, CaSHLOptions.SqrtNrOfSamples);
1288 unsigned long IterationCount=CaSHLOptions.SkipBL ? 0 : BounceLighting(CaSHLWorld, CaSHLOptions.BlockSize, CaSHLOptions.StopUT, CaSHLOptions.AskForMore);
1290 if (!CaSHLOptions.SkipBL) ToneReproduction(CaSHLWorld.GetBspTree()); // Ward97.cpp
1291 PostProcessBorders(CaSHLWorld);
1293 printf("\n%-50s %s\n", "*** Write Patch coeffs back into SHLMaps ***", GetTimeSinceProgramStart());
1294 CaSHLWorld.PatchesToSHLMaps(Patches);
1296 printf("\n%-50s %s\n", "*** Saving World ***", GetTimeSinceProgramStart());
1297 printf("%s\n", ArgV[1]);
1298 CaSHLWorld.SaveToDisk(ArgV[1]);
1300 WriteLogFileEntry(ArgV[1], CaSHLOptions.StopUT, CaSHLOptions.BlockSize, IterationCount);
1301 printf("\n%-50s %s\n", "COMPLETED.", GetTimeSinceProgramStart());
1303 catch (const WorldT::LoadErrorT& E)
1305 printf("\nType \"CaSHL\" (without any parameters) for help.\n");
1306 Error(E.Msg);
1308 catch (const WorldT::SaveErrorT& E)
1310 printf("\nType \"CaSHL\" (without any parameters) for help.\n");
1311 Error(E.Msg);
1314 return 0;