// This function determines the adjacency cell graph of the 'SuperLeaves'. // It is filled-in and stored in the 'Neighbours' members/components of the 'SuperLeaves'. // After this function was called, all components of all 'SuperLeaves' are completely filled-in. void DetermineAdjacencyGraph() { for (unsigned long SL1Nr=0; SL1Nr<SuperLeaves.Size(); SL1Nr++) for (unsigned long SL2Nr=0; SL2Nr<SuperLeaves.Size(); SL2Nr++) { if (SL1Nr==SL2Nr || !SuperLeaves[SL1Nr].BB.GetEpsilonBox(MapT::RoundEpsilon).Intersects(SuperLeaves[SL2Nr].BB)) continue; // Jedes Portal des ersten Leafs gegen jedes Portal des zweiten Leafs // prüfen und testen, ob sie sich schneiden (dann sind sie durchlässig). // BEACHTE: SuperLeaves haben *alle* Portale von *allen* ihrer Leaves! // D.h. es können sich nicht nur Portale auf ihrer konvexen Hülle befinden, sondern auch "innen drin". // Wegen der Konvexität der SuperLeaves dürfte das aber keine Rolle spielen und folgendes müßte trotzdem korrekt funktionieren. for (unsigned long Portal1Nr=0; Portal1Nr<SuperLeaves[SL1Nr].Portals.Size(); Portal1Nr++) for (unsigned long Portal2Nr=0; Portal2Nr<SuperLeaves[SL2Nr].Portals.Size(); Portal2Nr++) { if (!SuperLeaves[SL1Nr].Portals[Portal1Nr].Overlaps(SuperLeaves[SL2Nr].Portals[Portal2Nr], false, MapT::RoundEpsilon)) continue; // Folgender Check ist zwar redundant, aber zumindest sinnvoll. // Da diese Funktion sowieso schnell genug ist, lasse ihn nicht weg! if (SuperLeaves[SL1Nr].Portals[Portal1Nr].WhatSide(SuperLeaves[SL2Nr].Portals[Portal2Nr].Plane, MapT::RoundEpsilon)!=Polygon3T<double>::InMirrored) continue; ArrayT< Polygon3T<double> > NewPortals; SuperLeaves[SL1Nr].Portals[Portal1Nr].GetChoppedUpAlong(SuperLeaves[SL2Nr].Portals[Portal2Nr], MapT::RoundEpsilon, NewPortals); SuperLeaves[SL1Nr].Neighbours.PushBackEmpty(); SuperLeaves[SL1Nr].Neighbours[SuperLeaves[SL1Nr].Neighbours.Size()-1].SuperLeafNr=SL2Nr; SuperLeaves[SL1Nr].Neighbours[SuperLeaves[SL1Nr].Neighbours.Size()-1].SubPortal =NewPortals[NewPortals.Size()-1]; } } printf("SLs Adjacency Graph : done\n"); }
CommandSelectT::CommandSelectT(GuiDocumentT* GuiDocument, const ArrayT< IntrusivePtrT<cf::GuiSys::WindowT> >& OldSelection, const ArrayT< IntrusivePtrT<cf::GuiSys::WindowT> >& NewSelection) : CommandT(abs(int(OldSelection.Size())-int(NewSelection.Size()))>3, false), // Only show selection command in the undo/redo history if selection difference is greater 3. m_GuiDocument(GuiDocument), m_OldSelection(OldSelection), m_NewSelection(NewSelection) { }
void BspTreeBuilderT::QuickSortFacesIntoTexNameOrder() { while (ToDoRanges.Size()>=2) { const unsigned long LastIndex =ToDoRanges[ToDoRanges.Size()-1]; ToDoRanges.DeleteBack(); const unsigned long FirstIndex=ToDoRanges[ToDoRanges.Size()-1]; ToDoRanges.DeleteBack(); if (FirstIndex<LastIndex) { const char* x=FaceChildren[LastIndex]->Material->Name.c_str(); unsigned long i=FirstIndex-1; for (unsigned long j=FirstIndex; j<=LastIndex-1; j++) if (_stricmp(FaceChildren[j]->Material->Name.c_str(), x)<0) { i++; std::swap(FaceChildren[i], FaceChildren[j]); std::swap(FaceNrs[i], FaceNrs[j]); } std::swap(FaceChildren[i+1], FaceChildren[LastIndex]); std::swap(FaceNrs[i+1], FaceNrs[LastIndex]); i++; ToDoRanges.PushBack(i+1); ToDoRanges.PushBack(LastIndex); if (i>0) { ToDoRanges.PushBack(FirstIndex); ToDoRanges.PushBack(i-1); } } } }
bool EngineEntityT::Repredict(const ArrayT<PlayerCommandT>& PlayerCommands, unsigned long RemoteLastIncomingSequenceNr, unsigned long LastOutgoingSequenceNr) { if (!UsePrediction.GetValueBool()) return false; if (LastOutgoingSequenceNr-RemoteLastIncomingSequenceNr>PlayerCommands.Size()) { EnqueueString("WARNING - Prediction impossible: Last ack'ed PlayerCommand is too old (%u, %u)!\n", RemoteLastIncomingSequenceNr, LastOutgoingSequenceNr); return false; } /* * This assumes that this method is immediately called after ParseServerDeltaUpdateMessage(), * where the state of this entity has been set to the state of the latest server frame, * and that every in-game packet from the server contains a delta update message for our local client! */ // Unseren Entity über alle relevanten (d.h. noch nicht bestätigten) PlayerCommands unterrichten. // Wenn wir auf dem selben Host laufen wie der Server (z.B. Single-Player Spiel oder lokaler Client bei non-dedicated-Server Spiel), // werden die Netzwerk-Nachrichten in Nullzeit (im Idealfall über Memory-Buffer) versandt. // Falls dann auch noch der Server mit full-speed läuft, sollte daher immer RemoteLastIncomingSequenceNr==LastOutgoingSequenceNr sein, // was impliziert, daß dann keine Prediction stattfindet (da nicht notwendig!). for (unsigned long SequenceNr=RemoteLastIncomingSequenceNr+1; SequenceNr<=LastOutgoingSequenceNr; SequenceNr++) Entity->ProcessConfigString(&PlayerCommands[SequenceNr & (PlayerCommands.Size()-1)], "PlayerCommand"); Entity->Think(-2.0, 0); return true; }
int InspectorDialogT::GetBestPage(const ArrayT<MapElementT*>& Selection) const { if (Selection.Size()==0) { // Nothing is selected, so just show the scene graph. return 0; } else if (Selection.Size()==1) { MapElementT* MapElement=Selection[0]; return (MapElement->GetType()==&MapEntityT::TypeInfo) ? 1 : 2; } else { // Multiple map elements are selected. // If we have only map primitives, open the Primitive Properties tab // (even though it doesn't make much sense - primitive properties can be edited for a single item only). // One or more entities in the selection cause us to open the Entitiy Properties tab. bool HaveEntities=false; for (unsigned long SelNr=0; SelNr<Selection.Size(); SelNr++) { MapElementT* MapElement=Selection[SelNr]; if (MapElement->GetType()==&MapEntityT::TypeInfo) { HaveEntities=true; break; } } return HaveEntities ? 1 : 2; } }
/*static*/ bool StateT::IsDeltaMessageEmpty(const ArrayT<uint8_t>& DeltaMessage) { if (DeltaMessage[0] == 1) { // Check the RLE-compressed delta message. unsigned int i = 1; while (i < DeltaMessage.Size()) { const unsigned int N = DeltaMessage[i]; i++; if (N < MAX_VERBATIM) { for (unsigned int Stop = i+N+1; i < Stop; i++) if (DeltaMessage[i]) return false; } else { if (DeltaMessage[i]) return false; i++; } } } else { // Check the uncompressed delta message. for (unsigned int i = 1; i < DeltaMessage.Size(); i++) if (DeltaMessage[i]) return false; } return true; }
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()); } }
// Returns how many other SuperLeaves each SuperLeaf can see in average. // The lower bound is 1.0 (each SuperLeaf can only see itself). // The upper bound is the number of SuperLeaves (each SuperLeaf can see all other SuperLeaves, including itself). double GetAverageVisibility() { unsigned long VisCount=0; for (unsigned long SL1=0; SL1<SuperLeaves.Size(); SL1++) for (unsigned long SL2=0; SL2<SuperLeaves.Size(); SL2++) if (IsVisible(SL1, SL2)) VisCount++; return double(VisCount)/SuperLeaves.Size(); }
bool ToolEditSurfaceT::OnLMouseDown3D(ViewWindow3DT& ViewWindow, wxMouseEvent& ME) { const ArrayT<ViewWindow3DT::HitInfoT> Hits=ViewWindow.GetElementsAt(ME.GetPosition()); // Having this statement here means that the user absolutely cannot entirely clear the // surfaces selection (in 3D views, it works in 2D views), but that is just fine - why would he? if (Hits.Size()==0) return true; MapElementT* Object =Hits[0].Object; unsigned long FaceIndex=Hits[0].FaceNr; if (m_EyeDropperActive) { ViewWindow.GetChildFrame()->GetSurfacePropsDialog()->EyeDropperClick(Object, FaceIndex); return true; } if (!ME.ControlDown()) ViewWindow.GetChildFrame()->GetSurfacePropsDialog()->ClearSelection(); ViewWindow.GetChildFrame()->GetSurfacePropsDialog()->ToggleClick(Object, ME.ShiftDown() ? EditSurfacePropsDialogT::ALL_FACES : FaceIndex); // When something is newly selected (Control is not down), its surface properties are also picked up. if (!ME.ControlDown()) ViewWindow.GetChildFrame()->GetSurfacePropsDialog()->EyeDropperClick(Object, FaceIndex); return true; }
bool CommandGroupSetPropT::Do() { wxASSERT(!m_Done); if (m_Done) return false; // Set the new property // and notify all observers that our groups inventory changed. switch (m_Prop) { case PROP_NAME: m_Group->Name=m_NewName; break; case PROP_COLOR: m_Group->Color=m_NewColor; break; case PROP_CANSELECT: m_Group->CanSelect=m_NewFlag; break; case PROP_SELECTASGROUP: m_Group->SelectAsGroup=m_NewFlag; break; } m_MapDoc.UpdateAllObservers_GroupsChanged(); // If the elements in the group have changed color, update all observers accordingly. if (m_Prop==PROP_COLOR) { const ArrayT<MapElementT*> GroupElems=m_Group->GetMembers(m_MapDoc); if (GroupElems.Size()>0) m_MapDoc.UpdateAllObservers_Modified(GroupElems, MEMD_GENERIC /*MEMD_VISIBILITY*/); } m_Done=true; return true; }
void ClipWorldT::TraceRay(const Vector3dT& Start, const Vector3dT& Ray, unsigned long ClipMask, const ClipModelT* Ignore, TraceResultT& Result, ClipModelT** HitClipModel) const { // Violation of this requirement is usually, but not necessarily, an error. // If you ever get false-positives here, just remove the test entirely. assert(Result.Fraction==1.0 && !Result.StartSolid); if (HitClipModel) *HitClipModel=NULL; // Try the trace against the WorldCollMdl first. WorldCollMdl->TraceRay(Start, Ray, ClipMask, Result); // if (Result.Fraction<OldFrac && HitClipModel) *HitClipModel=WorldCollMdl; // FIXME: WorldCollMdl is of type CollisionModelT... if (Result.Fraction==0.0) return; // Now try all the entity models. ArrayT<ClipModelT*> ClipModels; const BoundingBox3dT OverallBB(Start, Start+Ray*Result.Fraction); GetClipModelsFromBB(ClipModels, ClipMask, OverallBB); for (unsigned long ModelNr=0; ModelNr<ClipModels.Size(); ModelNr++) { ClipModelT* ClipModel =ClipModels[ModelNr]; const double OldFraction=Result.Fraction; if (ClipModel==Ignore) continue; ClipModel->TraceRay(Start, Ray, ClipMask, Result); if (Result.Fraction<OldFraction && HitClipModel) *HitClipModel=ClipModel; if (Result.Fraction==0.0) break; } }
void InspDlgEntityPropsT::NotifySubjectChanged_Selection(SubjectT* Subject, const ArrayT<MapElementT*>& OldSelection, const ArrayT<MapElementT*>& NewSelection) { // Part 1: Determine the list of selected entities. SelectedEntities.Clear(); // Loop over the map selection in order to determine the list of selected entities. for (unsigned long i=0; i<NewSelection.Size(); i++) { MapEntityBaseT* Ent=dynamic_cast<MapEntityBaseT*>(NewSelection[i]); if (Ent) { if (SelectedEntities.Find(Ent)==-1) SelectedEntities.PushBack(Ent); continue; } MapPrimitiveT* Prim=dynamic_cast<MapPrimitiveT*>(NewSelection[i]); if (Prim) { MapEntityBaseT* Ent=Prim->GetParent(); if (SelectedEntities.Find(Ent)==-1) SelectedEntities.PushBack(Ent); continue; } } UpdatePropertyGrid(); }
static int LuaCI_LineCompletion(lua_State* LuaState) { ArrayT<std::string> Completions; std::string CommonPrefix=ConsoleInterpreter->LineCompletion(luaL_checkstring(LuaState, 1), Completions); lua_pushstring(LuaState, CommonPrefix.c_str()); lua_createtable(LuaState, Completions.Size(), 0); for (unsigned long CompletionNr=0; CompletionNr<Completions.Size(); CompletionNr++) { lua_pushstring(LuaState, Completions[CompletionNr].c_str()); lua_rawseti(LuaState, -2, CompletionNr+1); } return 2; }
// Records in 'SuperLeavesPVS' that we can see from 'FromSL' to 'ToSL'. inline void FlagVisible(unsigned long FromSL, unsigned long ToSL) { const unsigned long PVSTotalBitNr=FromSL*SuperLeaves.Size()+ToSL; const unsigned long PVS_W32_Nr =PVSTotalBitNr >> 5; const unsigned long PVSBitMask =1 << (PVSTotalBitNr & 31); SuperLeavesPVS[PVS_W32_Nr]|=PVSBitMask; }
StateT::StateT(const StateT& Other, const ArrayT<uint8_t>& DeltaMessage) { if (DeltaMessage[0] == 1) { // Run the RLE-decompression. UnpackBits(m_Data, &DeltaMessage[1], DeltaMessage.Size()-1); } else { m_Data.PushBackEmptyExact(DeltaMessage.Size()-1); for (unsigned int i = 0; i < m_Data.Size(); i++) m_Data[i] = DeltaMessage[i+1]; } // Run the delta-decompression. for (unsigned int i = 0; i < m_Data.Size(); i++) m_Data[i] ^= i < Other.m_Data.Size() ? Other.m_Data[i] : 0; }
void CommandDeleteT::Init(const ArrayT<MapElementT*>& DeleteElems) { // Split the list of elements into a list of primitives and a list of entities. // The lists are checked for duplicates and kept free of them as well. for (unsigned long ElemNr=0; ElemNr<DeleteElems.Size(); ElemNr++) { MapElementT* Elem=DeleteElems[ElemNr]; MapPrimitiveT* Prim=dynamic_cast<MapPrimitiveT*>(Elem); MapEntityT* Ent =dynamic_cast<MapEntityT*>(Elem); if (Prim) { MapEntityT* Parent=dynamic_cast<MapEntityT*>(Prim->GetParent()); if (Parent && IsEntirelyDeleted(Parent, DeleteElems)) { // If the parent is a regular entity (not the world!) that is entirely deleted anyway, // add the parent to the records instead of the individual primitive. if (m_DeleteEnts.Find(Parent)==-1) m_DeleteEnts.PushBack(Parent); } else { if (m_DeletePrims.Find(Prim)==-1) { m_DeletePrims.PushBack(Prim); m_DeletePrimsParents.PushBack(Prim->GetParent()); } } continue; } if (Ent) { if (m_DeleteEnts.Find(Ent)==-1) m_DeleteEnts.PushBack(Ent); continue; } } // Build the combined list of all deleted elements in order to unselect them. ArrayT<MapElementT*> Unselect; for (unsigned long PrimNr=0; PrimNr<m_DeletePrims.Size(); PrimNr++) Unselect.PushBack(m_DeletePrims[PrimNr]); for (unsigned long EntNr=0; EntNr<m_DeleteEnts.Size(); EntNr++) { Unselect.PushBack(m_DeleteEnts[EntNr]); for (unsigned long PrimNr=0; PrimNr<m_DeleteEnts[EntNr]->GetPrimitives().Size(); PrimNr++) Unselect.PushBack(m_DeleteEnts[EntNr]->GetPrimitives()[PrimNr]); } m_CommandSelect=CommandSelectT::Remove(&m_MapDoc, Unselect); }
void SubmodelsListT::UnloadSelectedSubmodels() { // We have to make the "detour" via the DelSM array, because unloading any submodel potentially modifies the indices of the rest. ArrayT<ModelDocumentT::SubmodelT*> DelSM; for (long SelNr=GetFirstSelected(); SelNr!=-1; SelNr=GetNextSelected(SelNr)) DelSM.PushBack(m_ModelDoc->GetSubmodels()[SelNr]); for (unsigned long SMNr=0; SMNr<DelSM.Size(); SMNr++) m_ModelDoc->UnloadSubmodel(m_ModelDoc->GetSubmodels().Find(DelSM[SMNr])); m_ModelDoc->UpdateAllObservers_SubmodelsChanged(); }
void ClipWorldT::GetContacts(const Vector3dT& Start, const Vector3dT& Ray, unsigned long ClipMask, const ClipModelT* Ignore, ContactsResultT& Contacts) const { Contacts.NrOfRepContacts=0; Contacts.NrOfAllContacts=0; // Try all the entity models - the WorldCollMdl is never included among the contacts. ArrayT<ClipModelT*> ClipModels; const BoundingBox3dT OverallBB(Start, Start+Ray); GetClipModelsFromBB(ClipModels, ClipMask, OverallBB); for (unsigned long ModelNr=0; ModelNr<ClipModels.Size(); ModelNr++) { ClipModelT* ClipModel=ClipModels[ModelNr]; if (ClipModel==Ignore) continue; TraceResultT Result(1.0); ClipModel->TraceRay(Start, Ray, ClipMask, Result); if (Result.Fraction<1.0) { Contacts.NrOfAllContacts++; if (Contacts.NrOfRepContacts==ContactsResultT::MAX_CONTACTS) { // No more room for storing another contact. if (Result.Fraction>=Contacts.TraceResults[ContactsResultT::MAX_CONTACTS-1].Fraction) continue; // Outside of Contacts array. Contacts.NrOfRepContacts--; // Will throw out the topmost element. } unsigned long ConNr=Contacts.NrOfRepContacts; // ClipModel was hit by the trace - that is, there was a contact! // Now insert the result at the right place into Contacts. while (ConNr>0 && Contacts.TraceResults[ConNr-1].Fraction>Result.Fraction) { Contacts.TraceResults[ConNr]=Contacts.TraceResults[ConNr-1]; Contacts.ClipModels [ConNr]=Contacts.ClipModels [ConNr-1]; ConNr--; } Contacts.TraceResults[ConNr]=Result; Contacts.ClipModels [ConNr]=ClipModel; Contacts.NrOfRepContacts++; } } }
void InspDlgEntityPropsT::NotifySubjectChanged_Modified(SubjectT* Subject, const ArrayT<MapElementT*>& MapElements, MapElemModDetailE Detail, const wxString& Key) { if (IsRecursiveSelfNotify) return; // If one of the changed entities is part of the current selection, update the grid. for (unsigned long i=0; i<MapElements.Size(); i++) { if (MapElements[i]->IsSelected()) { UpdatePropertyGrid(); break; } } }
bool ToolEditSurfaceT::OnRMouseClick3D(ViewWindow3DT& ViewWindow, wxMouseEvent& ME) { const ArrayT<ViewWindow3DT::HitInfoT> Hits=ViewWindow.GetElementsAt(ME.GetPosition()); // This is in the RMB *up* instead of the RMB *down* handler in order to not have the context menu shown // when the user hit something for an apply-click. if (Hits.Size()>0) { ViewWindow.GetChildFrame()->GetSurfacePropsDialog()->ApplyClick(ViewWindow, Hits[0].Object, ME.ShiftDown() ? EditSurfacePropsDialogT::ALL_FACES : Hits[0].FaceNr); return true; } return false; }
// Note that the world clip model is *not* in the list of returned clip models! void ClipWorldT::GetClipModelsFromBB(ArrayT<ClipModelT*>& ClipModels, unsigned long ContentMask, const BoundingBox3dT& BB) const { BoundingBox3dT ExpandedBB=BB.GetEpsilonBox(1.0); unsigned long GridRect[4]; GetGridRectFromBB(GridRect, BB); #ifdef DEBUG for (unsigned long ModelNr=0; ModelNr<ClipModels.Size(); ModelNr++) assert(!ClipModels[ModelNr]->AlreadyChecked); #endif for (unsigned long x=GridRect[0]; x<GridRect[2]; x++) for (unsigned long y=GridRect[1]; y<GridRect[3]; y++) { ClipSectorT& Sector=Sectors[y*SectorSubdivs + x]; // The sector at (x, y). if ((Sector.ModelContents & ContentMask)==0) continue; for (ClipLinkT* Link=Sector.ListOfModels; Link!=NULL; Link=Link->NextModelInSector) { ClipModelT* ClipModel=Link->ClipModel; if (ClipModel->AlreadyChecked) continue; if (!ClipModel->IsEnabled) continue; if ((ClipModel->GetContents() & ContentMask)==0) continue; if (!ClipModel->GetAbsoluteBB().Intersects(ExpandedBB)) continue; ClipModel->AlreadyChecked=true; ClipModels.PushBack(ClipModel); } } for (unsigned long ModelNr=0; ModelNr<ClipModels.Size(); ModelNr++) ClipModels[ModelNr]->AlreadyChecked=false; }
void BspTreeBuilderT::BuildBSPPortals(unsigned long NodeNr, ArrayT< Plane3T<double> >& NodeList) { const ArrayT<cf::SceneGraph::BspTreeNodeT::NodeT>& Nodes=BspTree->Nodes; NodeList.PushBack(Nodes[NodeNr].Plane.GetMirror()); if (Nodes[NodeNr].FrontIsLeaf) CreateLeafPortals(Nodes[NodeNr].FrontChild, NodeList); else BuildBSPPortals(Nodes[NodeNr].FrontChild, NodeList); NodeList[NodeList.Size()-1]=Nodes[NodeNr].Plane; if (Nodes[NodeNr].BackIsLeaf) CreateLeafPortals(Nodes[NodeNr].BackChild, NodeList); else BuildBSPPortals(Nodes[NodeNr].BackChild, NodeList); NodeList.DeleteBack(); }
// 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"); }
void ClipWorldT::TraceConvexSolid(const TraceSolidT& TraceSolid, const Vector3dT& Start, const Vector3dT& Ray, unsigned long ClipMask, const ClipModelT* Ignore, TraceResultT& Result, ClipModelT** HitClipModel) const { // Violation of this requirement is usually, but not necessarily, an error. // If you ever get false-positives here, just remove the test entirely. assert(Result.Fraction==1.0 && !Result.StartSolid); if (HitClipModel) *HitClipModel=NULL; if (TraceSolid.Vertices.Size()==0) return; // Use the optimized point trace whenever possible. if (TraceSolid.Vertices.Size()==1) { // TraceSolid.Vertices[0] is normally supposed to be (0, 0, 0), but let's support the generic case. TraceRay(Start+TraceSolid.Vertices[0], Ray, ClipMask, Ignore, Result, HitClipModel); return; } // Try the trace against the WorldCollMdl first. WorldCollMdl->TraceConvexSolid(TraceSolid, Start, Ray, ClipMask, Result); // if (Result.Fraction<OldFrac && HitClipModel) *HitClipModel=WorldCollMdl; // FIXME: WorldCollMdl is of type CollisionModelT... if (Result.Fraction==0.0) return; // Now try all the entity models. ArrayT<ClipModelT*> ClipModels; const BoundingBox3dT HullBB(TraceSolid.Vertices); const BoundingBox3dT OverallHullBB=HullBB.GetOverallTranslationBox(Start, Start+Ray*Result.Fraction); GetClipModelsFromBB(ClipModels, ClipMask, OverallHullBB); for (unsigned long ModelNr=0; ModelNr<ClipModels.Size(); ModelNr++) { ClipModelT* ClipModel =ClipModels[ModelNr]; const double OldFraction=Result.Fraction; if (ClipModel==Ignore) continue; ClipModel->TraceConvexSolid(TraceSolid, Start, Ray, ClipMask, Result); if (Result.Fraction<OldFraction && HitClipModel) *HitClipModel=ClipModel; if (Result.Fraction==0.0) break; } }
bool ToolEditSurfaceT::OnLMouseDown2D(ViewWindow2DT& ViewWindow, wxMouseEvent& ME) { const ArrayT<MapElementT*> Hits=ViewWindow.GetElementsAt(ME.GetPosition()); if (!ME.ControlDown()) m_MapDoc.GetChildFrame()->GetSurfacePropsDialog()->ClearSelection(); for (unsigned long HitNr=0; HitNr<Hits.Size(); HitNr++) { m_MapDoc.GetChildFrame()->GetSurfacePropsDialog()->ToggleClick(Hits[HitNr], EditSurfacePropsDialogT::ALL_FACES); // When something is newly selected (Control is not down), its surface properties are also picked up. if (HitNr==0 && !ME.ControlDown()) ViewWindow.GetChildFrame()->GetSurfacePropsDialog()->EyeDropperClick(Hits[HitNr], 0); } return true; }
// Determines the trivial visibility (recorded in the 'SuperLeavesPVS'). // That is, each SuperLeaf can see itself as well as its immediate neighbours. void DetermineTrivialVisibility() { for (unsigned long SL=0; SL<SuperLeaves.Size(); SL++) { // Sich selbst als sichtbar markieren. FlagVisible(SL, SL); // Und die unmittelbaren Nachbarn als sichtbar markieren. for (unsigned long NeighbourNr=0; NeighbourNr<SuperLeaves[SL].Neighbours.Size(); NeighbourNr++) { // This is the only place where we ever omit flagging the mutual "vice-versa" visibility: // FlagVisible(SuperLeaves[SL].Neighbours[NeighbourNr].SuperLeafNr, SL); // It's just not needed: The 'SuperLeavesPVS' matrix *must* be symmetric anyway. FlagVisible(SL, SuperLeaves[SL].Neighbours[NeighbourNr].SuperLeafNr); } } printf("Trivial Visibility : %10.5f\n", GetAverageVisibility()); }
template<class T> void Polygon3T<T>::Complete(ArrayT< Polygon3T<T> >& Polys, const T HalfPlaneThickness, const Vector3T<T>& BoundingSphereCenter, const T BoundingSphereRadius) { #if 1 for (unsigned long PolyNr=0; PolyNr<Polys.Size(); PolyNr++) { Polygon3T<T>& Poly=Polys[PolyNr]; Vector3T<T> Span1; Vector3T<T> Span2; // Note that Span2 points "down", not "up" as we'd normally expect for an y-axis. Poly.Plane.GetSpanVectorsByRotation(Span1, Span2); const Vector3T<T> Origin =BoundingSphereCenter-Poly.Plane.Normal*Poly.Plane.GetDistance(BoundingSphereCenter); const Vector3T<T> HugeSpan1=Span1*BoundingSphereRadius; const Vector3T<T> HugeSpan2=Span2*BoundingSphereRadius; Poly.Vertices.Clear(); Poly.Vertices.PushBack(Origin-HugeSpan1+HugeSpan2); Poly.Vertices.PushBack(Origin-HugeSpan1-HugeSpan2); Poly.Vertices.PushBack(Origin+HugeSpan1-HugeSpan2); Poly.Vertices.PushBack(Origin+HugeSpan1+HugeSpan2); // The initial winding should never be invalid, but I've never tested this code with T=float. // (Even if the points were slightly off due to rounding error, would it matter here??) if (!Poly.IsValid(HalfPlaneThickness, 2.0*HalfPlaneThickness)) { throw InvalidOperationE(); // for (unsigned long VertexNr=0; VertexNr<Poly.Vertices.Size(); VertexNr++) // Poly.Vertices[VertexNr]-=Poly.Plane.Normal*Poly.Plane.GetDistance(Poly.Vertices[VertexNr]); } for (unsigned long PlaneNr=0; PlaneNr<Polys.Size(); PlaneNr++) { if (PlaneNr==PolyNr) continue; const Plane3T<T>& Plane=Polys[PlaneNr].Plane; const SideT Side =Poly.WhatSideSimple(Plane, HalfPlaneThickness); if (Side==Front) { Poly.Vertices.Clear(); break; } // Make Poly invalid. if (Side==Both ) { Poly=Poly.GetSplits(Plane, HalfPlaneThickness)[1]; } // Split Poly and keep the backside. // There is nothing to do if Side is Back, InMirrored or InIdentical. // I'm not doing an IsValid() check here because in a few rare cases, calling Poly.GetSplits() yields a temporarily invalid // polygon, i.e. a polygon that has vertices that will be clipped in a later iteration but are closer than 2.0*HalfPlaneThickness. // For example, I've seen this with an initial huge winding in the XY plane whose four edges were not axis aligned, but rotated by // 45 degrees (because the spans happened to be this way, probably because the plane normal was not 100% exactly (0, 0, 1)). // Then, this winding is typically clipped against an axis-aligned plane that goes through two of its vertices. // However, with a minimal roundoff error present, the far away vertices are just not "in" the splitting plane, which yields // additional vertices that however violate the minimum vertex distance requirement. // if (!Poly.IsValid(HalfPlaneThickness, 2.0*HalfPlaneThickness)) break; } if (!Poly.IsValid(HalfPlaneThickness, 2.0*HalfPlaneThickness)) Poly.Vertices.Clear(); } #else for (unsigned long P1Nr=0; P1Nr<Polys.Size(); P1Nr++) { for (unsigned long P2Nr=0; P2Nr+1<Polys.Size(); P2Nr++) for (unsigned long P3Nr=P2Nr+1; P3Nr<Polys.Size(); P3Nr++) { try { // Bilde den Schnittpunkt der Planes. Vector3T<T> A=Plane3T<T>::GetIntersection(Polys[P1Nr].Plane, Polys[P2Nr].Plane, Polys[P3Nr].Plane); // Ist A gültig bzw. liegt A im Brush? unsigned long P4Nr; for (P4Nr=0; P4Nr<Polys.Size(); P4Nr++) if (Polys[P4Nr].Plane.GetDistance(A)>PlaneThickness) break; if (P4Nr<Polys.Size()) continue; // Liegt A auch wirklich in Polys[P1Nr].Plane? // Dies ist bei extrem degenerierten, beinahe-parallelen Eingabe-Planes nach Rundungsfehlern denkbar! if (Polys[P1Nr].Plane.GetDistance(A)<-PlaneThickness) continue; // Wenn A nicht ohnehin schon im Poly vorkommt (Haus-des-Nikolaus-Effekt!), A ins Poly einfügen. if (Polys[P1Nr].HasVertex(A, PlaneThickness)) continue; // Aufgrund der Problemstellung (Ebenenschnitte) können kolineare Vertices normalerweise nicht // vorkommen (mehr als 2 Vertices auf einer Geraden). // A ist ein Punkt des Polygons. Polys[P1Nr].Vertices.PushBack(A); } catch (const DivisionByZeroE&) { } } // Die Vertices im Uhrzeigersinn sortieren. for (unsigned long Vertex1Nr=0; Vertex1Nr+1<Polys[P1Nr].Vertices.Size(); Vertex1Nr++) { unsigned long Vertex2Nr; // Ausgehend vom Vertex1 denjenigen Vertex2 heraussuchen, mit dem sich eine Edge (im Uhrzeigersinn) bilden läßt. for (Vertex2Nr=Vertex1Nr+1; Vertex2Nr<Polys[P1Nr].Vertices.Size(); Vertex2Nr++) { const Vector3T<T>& N=Polys[P1Nr].Plane.Normal; const Vector3T<T>& A=Polys[P1Nr].Vertices[Vertex1Nr]; const Vector3T<T>& B=Polys[P1Nr].Vertices[Vertex2Nr]; const Vector3T<T> C=A-N; // Do *NOT* insert a '&' (reference) here - big bug! // According to their construction, A, B and C are guaranteed to be non-colinear, and so the plane constructor below should never fail. Plane3T<T> P(A, B, C, 0); unsigned long Vertex3Nr; for (Vertex3Nr=Vertex2Nr+1; Vertex3Nr<Polys[P1Nr].Vertices.Size(); Vertex3Nr++) if (P.GetDistance(Polys[P1Nr].Vertices[Vertex3Nr])<-PlaneThickness) break; if (Vertex3Nr==Polys[P1Nr].Vertices.Size()) break; } // Wenn Vertex1 und Vertex2 schon in Folge vorliegen oder keine Edge gefunden werden // kann (sollte niemals vorkommen, schwerer Fehler!), einfach weitermachen. if (Vertex1Nr+1==Vertex2Nr || Vertex2Nr==Polys[P1Nr].Vertices.Size()) continue; // Andernfalls Vertex2 als Nachfolger für Vertex1 zutauschen, // damit es im nächsten Schleifendurchlauf damit weitergeht. Vector3T<T> Temp=Polys[P1Nr].Vertices[Vertex1Nr+1]; Polys[P1Nr].Vertices[Vertex1Nr+1]=Polys[P1Nr].Vertices[Vertex2Nr]; Polys[P1Nr].Vertices[Vertex2Nr]=Temp; } } #endif }
void BspTreeBuilderT::Portalize() { ArrayT<cf::SceneGraph::BspTreeNodeT::LeafT>& Leaves=BspTree->Leaves; if (FaceChildren.Size()==0) { // BSP trees with zero faces (and thus no nodes and no leaves) can occur with many entity classes ("point entities"). assert(BspTree->Nodes.Size()==0); assert(BspTree->Leaves.Size()==0); return; } Console->Print(cf::va("\n%-50s %s\n", "*** Portalization ***", GetTimeSinceProgramStart())); unsigned long TotalNrOfPortals=0; // Kommentare des ehem. Portalize berücksichtigen!!! ArrayT< Plane3T<double> > NodeList; BuildBSPPortals(0, NodeList); Console->Print("Portalization : done\n"); for (unsigned long LeafNr=0; LeafNr<Leaves.Size(); LeafNr++) { Console->Print(cf::va("%5.1f%%\r", (double)LeafNr/Leaves.Size()*100.0)); // fflush(stdout); // The stdout console auto-flushes the output. for (unsigned long PortalNr=0; PortalNr<Leaves[LeafNr].Portals.Size(); PortalNr++) for (unsigned long FaceNr=0; FaceNr<Leaves[LeafNr].FaceChildrenSet.Size(); FaceNr++) { const cf::SceneGraph::FaceNodeT* CurrentFace =FaceChildren[Leaves[LeafNr].FaceChildrenSet[FaceNr]]; const Polygon3T<double>& CurrentPortal=Leaves[LeafNr].Portals[PortalNr]; // Wenn das Material der CurrentFace "durchsichtig" ist, d.h. BSP Portale nicht clippt // bzw. nicht solid für sie ist, mache weiter (und lasse das CurrentPortal unberührt). if ((CurrentFace->Material->ClipFlags & MaterialT::Clip_BspPortals)==0) continue; // Wenn die CurrentFace das CurrentPortal nicht überlappt, mache weiter. if (!CurrentFace->Polygon.Overlaps(CurrentPortal, false, MapT::RoundEpsilon)) continue; // Zerschneide das CurrentPortal entlang der Edges der CurrentFace. ArrayT< Polygon3T<double> > NewPortals; CurrentPortal.GetChoppedUpAlong(CurrentFace->Polygon, MapT::RoundEpsilon, NewPortals); // Das letzte der NewPortals überdeckt sich mit der Face, daher löschen wir es. NewPortals.DeleteBack(); // Das alte Portal wird nicht mehr gebraucht. Leaves[LeafNr].Portals[PortalNr]=Leaves[LeafNr].Portals[Leaves[LeafNr].Portals.Size()-1]; Leaves[LeafNr].Portals.DeleteBack(); // Dafür die Splitter anhängen. for (unsigned long PNr=0; PNr<NewPortals.Size(); PNr++) if (NewPortals[PNr].IsValid(MapT::RoundEpsilon, MapT::MinVertexDist)) // 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 above, where portals are first created. // 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 (NewPortals[PNr].GetArea()>100.0 /* 1 cm^2 */) Leaves[LeafNr].Portals.PushBack(NewPortals[PNr]); PortalNr--; break; } TotalNrOfPortals+=Leaves[LeafNr].Portals.Size(); } Console->Print(cf::va("Portals : %10lu\n", TotalNrOfPortals)); }
ArrayT<uint8_t> StateT::GetDeltaMessage(const StateT& Other, bool Compress) const { // Delta-compress the data. static ArrayT<uint8_t> DeltaData; DeltaData.Overwrite(); for (unsigned int i = 0; i < m_Data.Size(); i++) DeltaData.PushBack(m_Data[i] ^ (i < Other.m_Data.Size() ? Other.m_Data[i] : 0)); // Optionally RLE-compress the data, then write the delta message. ArrayT<uint8_t> DeltaMessage; if (Compress) { DeltaMessage.PushBack(1); PackBits(DeltaMessage, &DeltaData[0], DeltaData.Size()); #ifdef DEBUG // Make sure that unpacking yields the original data. ArrayT<uint8_t> Check; UnpackBits(Check, &DeltaMessage[1], DeltaMessage.Size()-1); assert(Check == DeltaData); #endif } else { DeltaMessage.PushBack(0); DeltaMessage.PushBack(DeltaData); } #if 0 static std::ofstream Log("compress_log.txt"); Log << "\n" << DeltaData.Size() << " bytes in original delta message\n"; { uLongf DestLen=compressBound(DeltaData.Size()); ArrayT<uint8_t> Dest; Dest.PushBackEmptyExact(DestLen); const int Result=compress2(&Dest[0], &DestLen, &DeltaData[0], DeltaData.Size(), 9); Log << DestLen << " bytes in deflate-compressed message, "; Log << "compression result is " << Result << " (" << (Result == Z_OK ? "Z_OK" : "error") << ")\n"; } { ArrayT<uint8_t> DestRLE; PackBits(DestRLE, &DeltaData[0], DeltaData.Size()); Log << DestRLE.Size() << " bytes in RLE-compressed message.\n"; ArrayT<uint8_t> DestRLE_CHECK; UnpackBits(DestRLE_CHECK, &DestRLE[0], DestRLE.Size()); assert(DestRLE_CHECK == DeltaData); } #endif return DeltaMessage; }
template<class T> void Brush3T<T>::TraceBoundingBox(const BoundingBox3T<T>& BB, const Vector3T<T>& Origin, const Vector3T<T>& Dir, VB_Trace3T<T>& Trace) const { static ArrayT<T> BloatDistanceOffsets; // Bloat-Distance-Offsets für alle Planes dieses Brushs bestimmen. while (BloatDistanceOffsets.Size()<Planes.Size()) BloatDistanceOffsets.PushBack(0.0); for (unsigned long PlaneNr=0; PlaneNr<Planes.Size(); PlaneNr++) { const Plane3T<T>& P=Planes[PlaneNr]; BloatDistanceOffsets[PlaneNr]=dot(P.Normal, Vector3T<T>(P.Normal.x<0 ? BB.Max.x : BB.Min.x, P.Normal.y<0 ? BB.Max.y : BB.Min.y, P.Normal.z<0 ? BB.Max.z : BB.Min.z)); } // Wenn 'Origin' im Inneren des (soliden) Brushs liegt, sitzen wir fest. unsigned long PlaneNr; for (PlaneNr=0; PlaneNr<Planes.Size(); PlaneNr++) if (Planes[PlaneNr].GetDistance(Origin)+BloatDistanceOffsets[PlaneNr]>=0) break; if (PlaneNr==Planes.Size()) { Trace.StartSolid=true; Trace.Fraction =0; return; } for (PlaneNr=0; PlaneNr<Planes.Size(); PlaneNr++) { // Bestimmen, bei welchem Bruchteil (Fraction F) von Dir wir die Plane schneiden. T Nenner=dot(Planes[PlaneNr].Normal, Dir); // Dir muß dem Normalenvektor der Ebene wirklich entgegenzeigen! Ist der Nenner==0, so ist Dir parallel zur Plane, // und mit dieser Plane ex. kein Schnittpunkt. Ist der Nenner>0, nutzen wir die Konvexität des Brushs aus: // Es gibt damit nur genau einen Schnittpunkt mit dem Brush (Eintrittsstelle) und die Plane behindert nicht // eine Bewegung von ihr weg (Feststecken wenn Dist==0 (s.u.)). if (Nenner>=0) continue; T Dist= Planes[PlaneNr].GetDistance(Origin)+BloatDistanceOffsets[PlaneNr]; T F =-Dist/Nenner; // Der Schnitt macht nur Sinn, wenn F im gewünschten Intervall liegt if (F<0 || F>Trace.Fraction) continue; // Prüfen, ob Schnittpunkt wirklich auf dem Brush liegt Vector3T<T> HitPos=Origin + Dir*F; unsigned long PNr; for (PNr=0; PNr<Planes.Size(); PNr++) if (PNr!=PlaneNr /*Rundungsfehlern zuvorkommen!*/ && Planes[PNr].GetDistance(HitPos)+BloatDistanceOffsets[PNr]>0.01) break; if (PNr<Planes.Size()) continue; // Wir haben die einzige Eintrittsstelle gefunden! // Eigentlich ist das errechete F einwandfrei. Wir wollen es jedoch nochmal etwas verringern, sodaß der sich // ergebende Schnittpunkt (HitPos) in einem Abstand von 0.03125 ÜBER der Ebene liegt! Bildhaft wird dazu die // Schnittebene um 0.03125 in Richtung ihres Normalenvektors geschoben und F wie oben neu errechnet. // Dies erspart uns ansonsten ernste Probleme: // - Wird diese Funktion nochmals mit dem Trace-Ergebnis (HitPos) als Origin-Vektor aufgerufen, // kann dieser neue Origin-Vektor nicht wegen Rundungsfehlern in den Brush geraten (Solid!). // - Wird unser Trace-Ergebnis (HitPos) als Origin-Vektor dieser Funktion, aber mit einem anderen // Brush benutzt, der gegenüber diesem Brush nur in der Schnittebene verschoben ist (z.B. eine lange // Wand, die aus mehreren "Backsteinen" besteht), kommt es auch hier nicht zum (falschen) // "Hängenbleiben" an einer gemeinsamen Brush-Kante. // Aber: Die HitPos kann natürlich trotzdem näher als 0.03125 an der Ebene liegen, nämlich genau dann, wenn // es nicht zu einem Schnitt kam und Dir zufällig dort endet. Wir ignorieren diese Möglichkeit: Kommt es doch // noch zu einem Schnitt, wird eben F==0. Deshalb können wir uns auch in diesem Fall nicht durch Rundungsfehler // ins Innere des Brushs schaukeln. F=-(Dist-(T)0.03125)/Nenner; // Vgl. Berechnung von F oben! if (F<0 ) F=0; if (F>Trace.Fraction) F=Trace.Fraction; // pro forma Trace.Fraction =F; Trace.ImpactNormal=Planes[PlaneNr].Normal; break; } }