uint32_t* SingleOpenGLWindowImplT::GetFrameBuffer(unsigned int& Width_, unsigned int& Height_) { static ArrayT<uint32_t> FrameBuffer; FrameBuffer.Overwrite(); FrameBuffer.PushBackEmpty(Width*Height); // Pixel vom BackBuffer in den FrameBuffer lesen. // Beachte: Die ersten beiden Parameter (0, 0) spezifizieren die linke UNTERE Ecke des gewünschten Bereichs! glReadPixels(0, 0, Width, Height, GL_RGBA, GL_UNSIGNED_BYTE, &FrameBuffer[0]); Width_ =Width; Height_=Height; // Wie oben schon erwähnt, steht der 'FrameBuffer' leider auf dem Kopf. // Vertausche daher alle Zeilen (vertikale Spiegelung). for (unsigned int y=0; y<Height_/2; y++) { uint32_t* UpperRow=&FrameBuffer[0]+ y *Width_; uint32_t* LowerRow=&FrameBuffer[0]+(Height_-y-1)*Width_; for (unsigned int x=0; x<Width_; x++) { const uint32_t Swap=*UpperRow; *UpperRow=*LowerRow; *LowerRow=Swap; UpperRow++; LowerRow++; } } return &FrameBuffer[0]; }
template<class T> ArrayT< Polygon3T<T> > Polygon3T<T>::GetSplits(const Plane3T<T>& SplitPlane, const T HalfPlaneThickness) const { const unsigned long FRONT=0; const unsigned long BACK =1; ArrayT< Polygon3T<T> > Result; Result.PushBackEmpty(2); Result[FRONT].Plane=this->Plane; Result[BACK ].Plane=this->Plane; if (!Vertices.Size()) return Result; Vector3T<T> LastVertex=Vertices[Vertices.Size()-1]; double LastDist =SplitPlane.GetDistance(LastVertex); for (unsigned long VertexNr=0; VertexNr<Vertices.Size(); VertexNr++) { const Vector3T<T>& ThisVertex=Vertices[VertexNr]; const double ThisDist =SplitPlane.GetDistance(ThisVertex); if (ThisDist>HalfPlaneThickness) { if (LastDist<-HalfPlaneThickness) { Vector3T<T> Intersection=SplitPlane.GetIntersection(LastVertex, ThisVertex, 0); Result[BACK ].Vertices.PushBack(Intersection); Result[FRONT].Vertices.PushBack(Intersection); } else if (LastDist<=HalfPlaneThickness) Result[FRONT].Vertices.PushBack(LastVertex); Result[FRONT].Vertices.PushBack(ThisVertex); } else if (ThisDist<-HalfPlaneThickness) { if (LastDist>HalfPlaneThickness) { Vector3T<T> Intersection=SplitPlane.GetIntersection(LastVertex, ThisVertex, 0); Result[FRONT].Vertices.PushBack(Intersection); Result[BACK ].Vertices.PushBack(Intersection); } else if (LastDist>=-HalfPlaneThickness) Result[BACK].Vertices.PushBack(LastVertex); Result[BACK].Vertices.PushBack(ThisVertex); } else { if (LastDist> HalfPlaneThickness) Result[FRONT].Vertices.PushBack(ThisVertex); else if (LastDist<-HalfPlaneThickness) Result[BACK ].Vertices.PushBack(ThisVertex); } LastVertex=ThisVertex; LastDist =ThisDist; } return Result; }
void BspTreeBuilderT::CreateLeafPortals(unsigned long LeafNr, const ArrayT< Plane3T<double> >& NodeList) { ArrayT<cf::SceneGraph::BspTreeNodeT::LeafT>& Leaves=BspTree->Leaves; Console->Print(cf::va("%5.1f%%\r", (double)LeafNr/Leaves.Size()*100.0)); // fflush(stdout); // The stdout console auto-flushes the output. const BoundingBox3T<double> LeafBB=Leaves[LeafNr].BB.GetEpsilonBox(MapT::RoundEpsilon); ArrayT< Polygon3T<double> > NewPortals; for (unsigned long Nr=0; Nr<NodeList.Size(); Nr++) if (LeafBB.WhatSide(NodeList[Nr])==BoundingBox3T<double>::Both) { NewPortals.PushBackEmpty(); NewPortals[NewPortals.Size()-1].Plane=NodeList[Nr]; } // Hier ist auch denkbar, daß Portals mit 0 Vertices zurückkommen (outer leaves)! // (Auch ganz normale gültige Portale in outer leaves sind denkbar!) Polygon3T<double>::Complete(NewPortals, MapT::RoundEpsilon); for (unsigned long PortalNr=0; PortalNr<NewPortals.Size(); PortalNr++) { const Polygon3T<double>& Portal=NewPortals[PortalNr]; // In degenerierten Grenzfällen (in Gegenwart von Splittern) können auch andere ungültige Polygone entstehen // (z.B. mehrere Vertices quasi auf einer Edge), sodaß wir explizit die Gültigkeit prüfen. // if (Portal.Vertices.Size()<3) continue; // Ist in .IsValid() enthalten! if (!Portal.IsValid(MapT::RoundEpsilon, MapT::MinVertexDist)) continue; // Another very serious problem is the fact that we sometimes self-create leaks, // because nearly all operations in this program suffer from rounding errors. // I have *NO* idea how to best combat them (except the introduction of exact arithmetic, which I'm seriously considering). // But for now, lets try something simpler - enforce a "minimum area" for portals. // Portals that are smaller than this minimum are considered degenerate, despite they were classified as valid above. // Note that the same is enforced below, where portals are split along the leafs faces. // UPDATE: As the new Polygon3T<double>::IsValid() method now enforces the MapT::MinVertexDist, // I believe the problem is solved the the polygon area check not longer required. if (Portal.GetArea()<=100.0 /* 1 cm^2 */) continue; // Note that rejecting portals here (i.e. adding additional test criteria) is a dangerous idea, // because any omitted portal might stop the subsequent flood-fill early. // This in turn might easily tear big holes into the world. // Consider my Tech-Archive notes from 2005-11-15 for a sketch that shows a problematic (but valid!) leaf // whose entry portal must not be omitted so that it can be entered during the flood-fill, // or else the left wall will be erroneously removed by the fill. // Okay, the portal seems to be good, so add it to the leaf. Leaves[LeafNr].Portals.PushBack(Portal.GetMirror()); } }
void CaptureStreamT::ReduceSamplesTo(unsigned int NumLeft) { const unsigned int NumNow=GetNumCaptureSamples(); if (NumNow>NumLeft) { ArrayT<unsigned char> Discard; Discard.PushBackEmpty((NumNow-NumLeft)*BytesPerSample(m_FORMAT)); alcCaptureSamples(m_CaptureDevice, &Discard[0], NumNow-NumLeft); } }
// This function computes for each SuperLeaf the bounding box over its 'SubPortals'. // Such bounding boxes are useful when a SuperLeaf is considered in its role as a "target" SuperLeaf. // The results are stored in the 'SuperLeavesBBs', which is assumed to be empty before this function is called. // SuperLeaves that have no (sub-)portals (and thus, no neighbours), get the default bounding box assigned. void ComputeSuperLeavesBBs() { for (unsigned long SL=0; SL<SuperLeaves.Size(); SL++) { SuperLeavesBBs.PushBackEmpty(); if (SuperLeaves[SL].Neighbours.Size()==0) continue; SuperLeavesBBs[SL]=BoundingBox3T<double>(SuperLeaves[SL].Neighbours[0].SubPortal.Vertices); for (unsigned long NeighbourNr=1; NeighbourNr<SuperLeaves[SL].Neighbours.Size(); NeighbourNr++) SuperLeavesBBs[SL].Insert(SuperLeaves[SL].Neighbours[NeighbourNr].SubPortal.Vertices); } printf("SLs Bounding Boxes : done\n"); }
// This functions determines if we can see from 'MasterSL' to 'TargetSL', that is, if 'TargetSL' is visible from 'MasterSL'. // ATTENTION 1: IT IS ASSUMED THAT THE PVS FOR ALL SUPERLEAVES IN RANGE '0..MasterSL-1' HAS ALREADY BEEN ESTABLISHED! // ATTENTION 2: THIS FUNCTION DOES NOT WORK IF 'MasterSL==TargetSL', OR 'TargetSL' IS AN IMMEDIATE NEIGHBOUR OF 'MasterSL'! // If mutual visibility could be established, the result is recorded in 'SuperLeavesPVS', for both "from A to B" and "from B to A". bool CanSeeFromAToB(unsigned long MasterSL, unsigned long TargetSL) { ArrayT<unsigned long> AncestorSLs; AncestorSLs.PushBackEmpty(2); // Für den alten Algorithmus war hier vermerkt, daß "outer leaves" keine Neighbours/Portals haben, // wegen der Eigenschaften von Portalize() und FillInside() des CaBSP Programms. // Der neue Algorithmus verwendet SuperLeaves, bei denen überhaupt nicht zwischen "inner" und "outer" unterschieden wird. // Alles was zählt, sind die Portale. Deshalb setzen sich SuperLeaves korrekt aus beliebigen Leaves eines Sub-Trees zusammen. for (unsigned long NeighbourNr=0; NeighbourNr<SuperLeaves[MasterSL].Neighbours.Size(); NeighbourNr++) { const unsigned long NeighbourSL =SuperLeaves[MasterSL].Neighbours[NeighbourNr].SuperLeafNr; const Polygon3T<double>& MasterPortal=SuperLeaves[MasterSL].Neighbours[NeighbourNr].SubPortal; // Wenn für das 'NeighbourSL' schon festgestellt wurde, daß das 'TargetSL' von dort aus nicht zu sehen ist, // sparen wir uns die Mühe, es trotzdem zu versuchen, und machen direkt weiter. if (NeighbourSL<MasterSL && !IsVisible(NeighbourSL, TargetSL)) continue; AncestorSLs[0]=MasterSL; AncestorSLs[1]=NeighbourSL; for (unsigned long NNNr=0; NNNr<SuperLeaves[NeighbourSL].Neighbours.Size(); NNNr++) { const unsigned long NeighboursNeighbourSL=SuperLeaves[NeighbourSL].Neighbours[NNNr].SuperLeafNr; const Polygon3T<double>& EnteringPortal =SuperLeaves[NeighbourSL].Neighbours[NNNr].SubPortal; if (NeighboursNeighbourSL==MasterSL) continue; const Polygon3T<double>::SideT Side=MasterPortal.WhatSide(EnteringPortal.Plane, MapT::RoundEpsilon); if (Side==Polygon3T<double>::InIdentical || Side==Polygon3T<double>::InMirrored) continue; // Wenn für das 'NeighboursNeighbourSL' schon festgestellt wurde, daß das 'TargetSL' von dort aus nicht zu sehen ist, // sparen wir uns die Mühe, es trotzdem zu versuchen, und machen direkt weiter. if (NeighboursNeighbourSL<MasterSL && !IsVisible(NeighboursNeighbourSL, TargetSL)) continue; // Das TOLLE: Sobald wir festgestellt haben, daß "irgendeine" Sichtbarkeit von 'MasterSL' nach 'TargetSL' existiert, // können wir sofort AUFHÖREN(!!!) und mit dem nächsten SuperLeaves-Paar weitermachen! if (DetermineVisibility(NeighboursNeighbourSL, EnteringPortal, AncestorSLs, MasterPortal, TargetSL, SuperLeavesBBs[TargetSL])) return true; } } return false; }
// Diese Funktion sortiert die Faces anhand ihres Texture-Namens in aufsteigender Reihenfolge. // Dank Z-Buffering kann die Engine damit die Faces in dieser Reihenfolge mit einem Minimum von State-Changes rendern. // Da außerdem die LightMaps der Faces in dieser Reihenfolge in die größeren LightMaps einsortiert werden // (CreateFullBrightLightMaps()), erhalten wir den selben positiven Effekt auch für die LightMaps! void BspTreeBuilderT::SortFacesIntoTexNameOrder() { if (FaceChildren.Size()==0) return; Console->Print(cf::va("\n%-50s %s\n", "*** Sort Faces ***", GetTimeSinceProgramStart())); FaceNrs.Clear(); for (unsigned long FaceNr=0; FaceNr<FaceChildren.Size(); FaceNr++) FaceNrs.PushBack(FaceNr); // QuickSort Faces according to their texture name. ToDoRanges.Clear(); ToDoRanges.PushBack(0); ToDoRanges.PushBack(FaceChildren.Size()-1); QuickSortFacesIntoTexNameOrder(); // Verify sorting. for (unsigned long FaceNr=0; FaceNr+1<FaceChildren.Size(); FaceNr++) if (_stricmp(FaceChildren[FaceNr]->Material->Name.c_str(), FaceChildren[FaceNr+1]->Material->Name.c_str())>0) Error("Bad sorting!"); // Wir wissen nun, daß an Stelle der Face i nun die Face FaceNrs[i] steht, wollen aber wissen, an welcher // Stelle nun die i-te Face steht. Führe dazu das RevFaceNrs-Array ein und fülle es entsprechend aus. ArrayT<unsigned long> RevFaceNrs; RevFaceNrs.PushBackEmpty(FaceChildren.Size()); for (unsigned long FaceNr=0; FaceNr<FaceChildren.Size(); FaceNr++) RevFaceNrs[FaceNrs[FaceNr]]=FaceNr; // Korrigiere damit die FaceSets der Leaves. ArrayT<cf::SceneGraph::BspTreeNodeT::LeafT>& Leaves=BspTree->Leaves; for (unsigned long LeafNr=0; LeafNr<Leaves.Size(); LeafNr++) for (unsigned long FaceNr=0; FaceNr<Leaves[LeafNr].FaceChildrenSet.Size(); FaceNr++) Leaves[LeafNr].FaceChildrenSet[FaceNr]=RevFaceNrs[Leaves[LeafNr].FaceChildrenSet[FaceNr]]; Console->Print("done\n"); }
int main(int ArgC, const char* ArgV[]) { unsigned long MaxRecDepthSL =0xFFFFFFFF; double MinAreaSL =0.0; bool OnlySuperLeaves=false; printf("\n*** Cafu Potentially Visibility Set Utility, Version 05 (%s) ***\n\n\n", __DATE__); if (ArgC<2) Usage(); // Initialize the FileMan by mounting the default file system. // Note that specifying "./" (instead of "") as the file system description effectively prevents the use of // absolute paths like "D:\abc\someDir\someFile.xy" or "/usr/bin/xy". This however should be fine for this application. cf::FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_LOCAL_PATH, "./", ""); cf::FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_ZIP_ARCHIVE, "Games/DeathMatch/Textures/TechDemo.zip", "Games/DeathMatch/Textures/TechDemo/", "Ca3DE"); cf::FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_ZIP_ARCHIVE, "Games/DeathMatch/Textures/SkyDomes.zip", "Games/DeathMatch/Textures/SkyDomes/", "Ca3DE"); // Parse the command line. for (int ArgNr=2; ArgNr<ArgC; ArgNr++) { if (!_stricmp(ArgV[ArgNr], "-maxRecDepthSL")) { ArgNr++; if (ArgNr>=ArgC) Usage(); MaxRecDepthSL=atoi(ArgV[ArgNr]); printf("maxRecDepthSL == %lu\n", MaxRecDepthSL); } else if (!_stricmp(ArgV[ArgNr], "-minAreaSL")) { ArgNr++; if (ArgNr>=ArgC) Usage(); MinAreaSL=atof(ArgV[ArgNr]); if (MinAreaSL<0.0) MinAreaSL=0.0; printf("minAreaSL == %.3f\n", MinAreaSL); } else if (!_stricmp(ArgV[ArgNr], "-onlySLs")) { OnlySuperLeaves=true; } else if (ArgV[ArgNr][0]==0) { // The argument is "", the empty string. // This can happen under Linux, when CaPVS is called via wxExecute() with white-space trailing the command string. } else { printf("Unknown option '%s'.\n", ArgV[ArgNr]); Usage(); } } std::string GameDirectory=ArgV[1]; // Determine the game directory, cleverly assuming that the destination file is in "Worlds". { // Strip the file name and extention off. size_t i=GameDirectory.find_last_of("/\\"); GameDirectory=GameDirectory.substr(0, i==std::string::npos ? 0 : i)+"/.."; } // Setup the global MaterialManager pointer. static MaterialManagerImplT MatManImpl; MaterialManager=&MatManImpl; if (MaterialManager->RegisterMaterialScriptsInDir(GameDirectory+"/Materials", GameDirectory+"/").Size()==0) { printf("\nNo materials found in scripts in \"%s/Materials\".\n", GameDirectory.c_str()); printf("No materials found.\n\n"); Usage(); } try { // General note: Robustness against the 'sharp wedge' problem is granted: // DetermineAdjacencyGraph() is the only place where PolygonOverlap() is called, but only once on *input* portals. // Everywhere else (especially in BuildPVS() and its sub-functions), only PolygonSplit() and PolygonWhatSide() are called. printf("*** Load World %s ***\n", ArgV[1]); // 1. Load the 'CaPVSWorld'. ModelManagerT ModelMan; CaPVSWorldT* CaPVSWorld=new CaPVSWorldT(ArgV[1], ModelMan, MaxRecDepthSL, MinAreaSL); // 2. Create the 'SuperLeaves'. CaPVSWorld->CreateSuperLeaves(SuperLeaves); if (OnlySuperLeaves) return 0; // 3. Determine the adjacency graph. // This completely fills-in the remaining components of the 'SuperLeaves'. printf("\n%-50s %s\n", "*** Initialize ***", GetTimeSinceProgramStart()); DetermineAdjacencyGraph(); // 4. Compute for each SuperLeaf the bounding box over its (sub-)portals. ComputeSuperLeavesBBs(); // 5. Create and initialize the 'SuperLeavesPVS' (reset to complete blindness (all 0s)). SuperLeavesPVS.PushBackEmpty((SuperLeaves.Size()*SuperLeaves.Size()+31)/32); for (unsigned long Vis=0; Vis<SuperLeavesPVS.Size(); Vis++) SuperLeavesPVS[Vis]=0; // 6. For each SuperLeaf, flag itself and the immediate neighbours as visible. DetermineTrivialVisibility(); // 7. Do some simple ray tests in order to quickly obtain a good estimation of the actual PVS. // Note that calling this function is ENTIRELY OPTIONAL, and the call can be omitted without danger! // (The latter might e.g. be useful for debugging.) DetermineRayPresampledVisibility(CaPVSWorld); // 8. Finally calculate the 'SuperLeavesPVS' using analytical methods. printf("\n%-50s %s\n", "*** Potentially Visibility Set ***", GetTimeSinceProgramStart()); BuildPVS(); // 9. Carry the information in 'SuperLeavesPVS' into the PVS of the 'CaPVSWorld'. CaPVSWorld->StorePVS(SuperLeaves, SuperLeavesPVS); // 10. Print some statistics and obtain a checksum. unsigned long CheckSum=CaPVSWorld->GetChecksumAndPrintStats(); // 11. Save the 'CaPVSWorld' back to disk. printf("\n%-50s %s\n", "*** Save World ***", GetTimeSinceProgramStart()); printf("%s\n", ArgV[1]); CaPVSWorld->SaveToDisk(ArgV[1]); // 12. Clean-up and write log file entry. delete CaPVSWorld; WriteLogFileEntry(ArgV[1], CheckSum); printf("\n%-50s %s\n", "COMPLETED.", GetTimeSinceProgramStart()); } catch (const WorldT::LoadErrorT& E) { printf("\nFATAL ERROR: %s\n", E.Msg); printf("Program aborted.\n\n"); exit(1); } catch (const WorldT::SaveErrorT& E) { printf("\nFATAL ERROR: %s\n", E.Msg); printf("Program aborted.\n\n"); exit(1); } return 0; }
// Determines a simple pre-PVS by sampling "random" rays (and stores it in 'SuperLeavesPVS'). // The result is a subset of the analytically determined, conservative, "exact" PVS. // This function does not handle the fact that SuperLeaves can see themselves. // Thus, calling 'DetermineTrivialVisibility()' before (or after) calling this function is required. void DetermineRayPresampledVisibility(const CaPVSWorldT* CaPVSWorld) { ArrayT< ArrayT<VectorT> > SuperLeavesCenters; ArrayT< ArrayT<bool > > SuperLeavesCenterIsValid; for (unsigned long SL=0; SL<SuperLeaves.Size(); SL++) { SuperLeavesCenters .PushBackEmpty(); SuperLeavesCenterIsValid.PushBackEmpty(); for (unsigned long NbNr=0; NbNr<SuperLeaves[SL].Neighbours.Size(); NbNr++) { const Polygon3T<double>& SubPortal=SuperLeaves[SL].Neighbours[NbNr].SubPortal; VectorT Center =SubPortal.Vertices[0]; for (unsigned long VertexNr=1; VertexNr<SubPortal.Vertices.Size(); VertexNr++) Center=Center+SubPortal.Vertices[VertexNr]; Center=scale(Center, 1.0/double(SubPortal.Vertices.Size()))+scale(SubPortal.Plane.Normal, 0.2); // Not really necessary, but add a little check to see if everything is in order. // (Or, more precisely, assert that the 'Center' is really "inside" the SuperLeaf.) const unsigned long LeafNr=CaPVSWorld->WhatLeaf(Center); unsigned long LeafSetNr; for (LeafSetNr=0; LeafSetNr<SuperLeaves[SL].LeafSet.Size(); LeafSetNr++) if (LeafNr==SuperLeaves[SL].LeafSet[LeafSetNr]) break; SuperLeavesCenters [SL].PushBack(Center); SuperLeavesCenterIsValid[SL].PushBack(LeafSetNr<SuperLeaves[SL].LeafSet.Size()); // if (LeafSetNr>=SuperLeaves[SL1].LeafSet.Size()) printf("WARNING: Center not in its SuperLeaf. TODO: Print more extensive diagnostics.\n"); } } for (unsigned long SL1=0; SL1+1<SuperLeaves.Size(); SL1++) { printf("%5.1f%%\r", (double)SL1/SuperLeaves.Size()*100.0); fflush(stdout); for (unsigned long SL2=SL1+1; SL2<SuperLeaves.Size(); SL2++) { if (IsVisible(SL1, SL2)) continue; for (unsigned long Nb1Nr=0; Nb1Nr<SuperLeaves[SL1].Neighbours.Size(); Nb1Nr++) { if (!SuperLeavesCenterIsValid[SL1][Nb1Nr]) continue; for (unsigned long Nb2Nr=0; Nb2Nr<SuperLeaves[SL2].Neighbours.Size(); Nb2Nr++) { if (!SuperLeavesCenterIsValid[SL2][Nb2Nr]) continue; const VectorT& Center1=SuperLeavesCenters[SL1][Nb1Nr]; const VectorT& Center2=SuperLeavesCenters[SL2][Nb2Nr]; // This ClipLine() only considers faces, but not BPs, terrains, etc. if (CaPVSWorld->ClipLine(Center1, Center2-Center1)==1.0) { FlagVisible(SL1, SL2); FlagVisible(SL2, SL1); goto DoneWithSL2; } } } DoneWithSL2:; } } printf("Estimated PVS : %10.5f\n", GetAverageVisibility()); }
void FaceNodeT::CreatePatchMeshes(ArrayT<cf::PatchMeshT>& PatchMeshes, ArrayT< ArrayT< ArrayT<Vector3dT> > >& SampleCoords) const { if (LightMapInfo.SizeS==0) return; if (LightMapInfo.SizeT==0) return; if (Polygon.Vertices.Size()<3) return; // A FaceNodeT creates excatly one patch mesh - namely the one that covers it. PatchMeshes.PushBackEmpty(); SampleCoords.PushBackEmpty(); PatchMeshT& PatchMesh=PatchMeshes[PatchMeshes.Size()-1]; // Fill-in basic data. PatchMesh.Width =LightMapInfo.SizeS; PatchMesh.Height=LightMapInfo.SizeT; PatchMesh.Patches.PushBackEmpty(PatchMesh.Width*PatchMesh.Height); PatchMesh.WrapsHorz=false; PatchMesh.WrapsVert=false; PatchMesh.Node =this; PatchMesh.Material=Material; SampleCoords[0].PushBackEmpty(PatchMesh.Width*PatchMesh.Height); // Compute the details for each patch. // Dieser Code ist sehr ähnlich zu dem Code in CaLights PostProcessBorders() Funktion! // Änderungen hier könnten Änderungen in dieser Funktion erforderlich machen! // Bestimme die Spannvektoren. VectorT U; VectorT V; Polygon.Plane.GetSpanVectors(U, V); // Finde SmallestU und SmallestV. double SmallestU=dot(Polygon.Vertices[0], U); double SmallestV=dot(Polygon.Vertices[0], V); for (unsigned long VertexNr=1; VertexNr<Polygon.Vertices.Size(); VertexNr++) { double u=dot(Polygon.Vertices[VertexNr], U); double v=dot(Polygon.Vertices[VertexNr], V); if (u<SmallestU) SmallestU=u; if (v<SmallestV) SmallestV=v; } // Bereite folgende Schleife vor. const VectorT UV_Origin=scale(Polygon.Plane.Normal, Polygon.Plane.Dist); const VectorT Safety =scale(Polygon.Plane.Normal, 0.1); Polygon3T<double> PatchPoly; PatchPoly.Plane=dot(Polygon.Plane.Normal, cross(U, V))<0 ? Polygon.Plane : Polygon.Plane.GetMirror(); // Nun betrachte alle Patches. for (unsigned long t=0; t<PatchMesh.Height; t++) for (unsigned long s=0; s<PatchMesh.Width; s++) { const double PATCH_SIZE=cf::SceneGraph::FaceNodeT::LightMapInfoT::PatchSize; cf::PatchT& Patch =PatchMesh.Patches[t*PatchMesh.Width+s]; Patch.Coord =VectorT(0, 0, 0); Patch.Normal =Polygon.Plane.Normal; Patch.Area =PATCH_SIZE*PATCH_SIZE; Patch.InsideFace=false; // Also assign an initial, non-zero "Where comes the energy from?"-direction. // The value (==length) has been chosen entirely arbitrary with the accumulative nature of the computations in CaLight // in mind, and in the hope to pick a reasonable value. // (However, tests seem to indicate the smaller values are better. I tried 0.3 first, then 0.05.) Patch.EnergyFromDir=Patch.Normal*0.02; const double s_=s; const double t_=t; PatchPoly.Vertices.Clear(); PatchPoly.Vertices.PushBack(UV_Origin+scale(U, SmallestU+(s_-1.0)*PATCH_SIZE)+scale(V, SmallestV+(t_-1.0)*PATCH_SIZE)); PatchPoly.Vertices.PushBack(UV_Origin+scale(U, SmallestU+ s_ *PATCH_SIZE)+scale(V, SmallestV+(t_-1.0)*PATCH_SIZE)); PatchPoly.Vertices.PushBack(UV_Origin+scale(U, SmallestU+ s_ *PATCH_SIZE)+scale(V, SmallestV+ t_ *PATCH_SIZE)); PatchPoly.Vertices.PushBack(UV_Origin+scale(U, SmallestU+(s_-1.0)*PATCH_SIZE)+scale(V, SmallestV+ t_ *PATCH_SIZE)); assert(fabs(PatchPoly.GetArea()-Patch.Area)<1.0); // This is just to make sure there is no logic error in area computation. if (!Polygon.Overlaps(PatchPoly, false, ROUND_EPSILON)) continue; if (!Polygon.Encloses(PatchPoly, true, ROUND_EPSILON)) { ArrayT< Polygon3T<double> > NewPolygons; PatchPoly.GetChoppedUpAlong(Polygon, ROUND_EPSILON, NewPolygons); if (NewPolygons.Size()>0 && NewPolygons[NewPolygons.Size()-1].Vertices.Size()>0) { PatchPoly=NewPolygons[NewPolygons.Size()-1]; } else { // It's not utterly right, but to keep things simple in case of problems with chopping, // we just treat this patch as if it was fully outside of our polygon. printf("WARNING: PolygonChopUp failed or PatchPoly.Vertices.Size()==0.\n"); continue; } } // If a patch was at least fractionally inside its face, we (re-)determine its center point now. for (unsigned long VertexNr=0; VertexNr<PatchPoly.Vertices.Size(); VertexNr++) Patch.Coord=Patch.Coord+PatchPoly.Vertices[VertexNr]; // The "Safety" is to avoid (or at least reduce) accidental self-intersections due to rounding errors // during clipping computations in CaLight. Patch.Coord =scale(Patch.Coord, 1.0/double(PatchPoly.Vertices.Size()))+Safety; // Patch.Normal =Polygon.Plane.Normal; // Already done above. Patch.Area =PatchPoly.GetArea(); Patch.InsideFace=true; // Compute several sample coordinates for each patch. // The caller may use them for example for computing/sampling the initial incident of sunlight. // Note that we add two kinds of safety margin: We move the samples a bit "above" their plane along the // normal vector ("Safety") as well as a bit towards the center of the patch, away from their border. ArrayT<Vector3dT>& ppsc=SampleCoords[0][t*PatchMesh.Width+s]; for (unsigned long VertexNr=0; VertexNr<PatchPoly.Vertices.Size(); VertexNr++) { const Vector3dT& V1=PatchPoly.Vertices[VertexNr]; const Vector3dT& V2=PatchPoly.Vertices[(VertexNr+1) % PatchPoly.Vertices.Size()]; const Vector3dT SafetyTowardsCenter1=normalize(Patch.Coord-(V1+Safety), 0.0)*0.1; const Vector3dT SafetyTowardsCenter2=normalize(Patch.Coord-(V2+Safety), 0.0)*0.1; ppsc.PushBack(V1+SafetyTowardsCenter1+Safety); ppsc.PushBack(scale(V1+SafetyTowardsCenter1 + V2+SafetyTowardsCenter2, 0.5)+Safety); ppsc.PushBack(scale(V1+Safety + Patch.Coord, 0.5)); } ppsc.PushBack(Patch.Coord); } }