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.
7 /************************************************/
9 /*** Cafu Spherical Harmonic Lighting Utility ***/
11 /*** Der Herr sprach ***/
12 /*** Es werde Licht! ***/
13 /*** Und es wurde Licht. ***/
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.
34 #define WIN32_LEAN_AND_MEAN
43 #define _stricmp strcasecmp
44 #define _getch getchar
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"
72 #define vsnprintf _vsnprintf
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
);
108 static void Error(const char* ErrorText
, ...)
111 char ErrorString
[256];
115 va_start(ArgList
, ErrorText
);
116 vsnprintf(ErrorString
, 256, ErrorText
, ArgList
);
119 printf("\nFATAL ERROR: %s\n", ErrorString
);
122 printf("Program aborted.\n\n");
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!
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
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
;
161 VectorT Big_P_i_Normal
;
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;
179 Big_P_i_Coord
=Big_P_i_Coord
+P_i
.Coord
;
181 Big_P_i_Normal
=Big_P_i_Normal
+P_i
.Normal
;
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
));
196 Big_P_i_Normal
=normalize(Big_P_i_Normal
, 0.0);
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
);
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;
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)!
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.
279 double S1mx2
=sqrt(1.0-x
*x
);
282 for (int i
=0; i
<m
; i
++)
284 Pmm
*= -1.0*fact
*S1mx2
;
287 if (l
==m
) return Pmm
;
289 double Pmmp1
=x
*(2.0*m
+1.0)*Pmm
;
290 if (l
==m
+1) return Pmmp1
;
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
);
304 double factorial(int n
)
306 static bool IsInitialized
=false;
307 static double Factorials
[33];
313 for (char i
=1; i
<33; i
++)
314 Factorials
[i
]=double(i
)*Factorials
[i
-1];
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);
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
++)
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)
434 const double CosTerm
=dot(SphericalSamples
[SampleNr
].UnitVector
, Map
.FaceChildren
[FaceNr
]->Polygon
.Plane
.Normal
);
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.
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?
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;
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;
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
]);
534 if (!Count
) continue;
535 ThisUT
/=double(Count
);
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.
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
];
564 for (unsigned long CoeffNr
=0; CoeffNr
<P
.SHCoeffs_UnradiatedTransfer
.Size(); CoeffNr
++)
565 ThisUT
+=fabs(P
.SHCoeffs_UnradiatedTransfer
[CoeffNr
]);
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));
581 if (BestUT
<StopUT
) // Es gab keinen besseren Wert mehr -- wir können also abbrechen!
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
);
604 RadiateTransfer(CaSHLWorld
, Face_i
, s_i
, t_i
, BLOCK_SIZE
);
608 // TODO: Ein (sinnvolles!) 'kbhit()' Äquivalent für Linux muß erst noch gefunden werden...
611 char Key
=_getch(); if (Key
==0) (void)_getch();
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();
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());
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
;
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
];
703 for (unsigned long CoeffNr
=0; CoeffNr
<NR_OF_SH_COEFFS
; CoeffNr
++)
704 Cs
[CoeffNr
]=Average
[CoeffNr
]/double(AverageCount
);
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.
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);
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!)
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.
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.
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
);
1013 printf("USAGE: CaSHL WorldName [OPTIONS]\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
);
1033 printf("EXAMPLES:\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");
1039 printf("CaSHL WorldName -StopUT 0.1\n");
1040 printf(" Most worlds of the Cafu demo release are lit with these switches.\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");
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");
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
));
1069 unsigned long Dummy
=256;
1070 if (!GetComputerName(HostName
, &Dummy
)) sprintf(HostName
, "unknown (look-up failed).");
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).");
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);
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
);
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
1111 unsigned long SqrtNrOfSamples
;
1114 CaSHLOptionsT() : BlockSize(3), StopUT(0.1), AskForMore(false), UseFullVis(true), SqrtNrOfSamples(100), SkipBL(false) {}
1117 cf::SceneGraph::SHLMapManT::NrOfBands
=4;
1121 printf("\n*** Cafu SHL Utility, Version 01 (%s) ***\n\n\n", __DATE__
);
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");
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");
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\"!");
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\"!");
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\"!");
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\"!");
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\"!");
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.
1217 printf("\nSorry, I don't know what \"%s\" means.\n", ArgV
[CurrentArg
]);
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");
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
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
));
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");
1308 catch (const WorldT::SaveErrorT
& E
)
1310 printf("\nType \"CaSHL\" (without any parameters) for help.\n");