void CommandDeleteT::Undo() { wxASSERT(m_Done); if (!m_Done) return; ArrayT<MapElementT*> InsertedElems; for (unsigned long PrimNr=0; PrimNr<m_DeletePrims.Size(); PrimNr++) { m_MapDoc.Insert(m_DeletePrims[PrimNr], m_DeletePrimsParents[PrimNr]); InsertedElems.PushBack(m_DeletePrims[PrimNr]); } for (unsigned long EntNr=0; EntNr<m_DeleteEnts.Size(); EntNr++) { m_MapDoc.Insert(m_DeleteEnts[EntNr]); InsertedElems.PushBack(m_DeleteEnts[EntNr]); } // Update all observers. m_MapDoc.UpdateAllObservers_Created(InsertedElems); // Select the previously selected elements again. m_CommandSelect->Undo(); m_Done=false; }
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 CommandDeleteT::Do() { wxASSERT(!m_Done); if (m_Done) return false; // Deselect any affected elements that are selected. m_CommandSelect->Do(); ArrayT<MapElementT*> DeletedElems; for (unsigned long EntNr=0; EntNr<m_DeleteEnts.Size(); EntNr++) { m_MapDoc.Remove(m_DeleteEnts[EntNr]); DeletedElems.PushBack(m_DeleteEnts[EntNr]); } for (unsigned long PrimNr=0; PrimNr<m_DeletePrims.Size(); PrimNr++) { m_MapDoc.Remove(m_DeletePrims[PrimNr]); DeletedElems.PushBack(m_DeletePrims[PrimNr]); } // Update all observers. m_MapDoc.UpdateAllObservers_Deleted(DeletedElems); m_Done=true; return true; }
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 InspDlgEntityPropsT::OnContextMenuItemDelete(wxCommandEvent& event) { // Just in case the user pressed e.g. CTRL+Q (clear selection) in the meanwhile... if (LastRightClickedProperty==NULL) return; // Only handle deletes on properties that are not a category. if (LastRightClickedProperty->IsCategory()) return; // Key is not undefined (cannot be deleted). if (LastRightClickedProperty->GetParent()!=UnDefKeys) { wxMessageBox("Only undefined keys can be deleted."); return; } ArrayT<CommandT*> Commands; for (unsigned long i=0; i<SelectedEntities.Size(); i++) Commands.PushBack(new CommandDeletePropertyT(*MapDoc, SelectedEntities[i], LastRightClickedProperty->GetName())); CommandT* MacroCommand=new CommandMacroT(Commands, "Delete Property '"+LastRightClickedProperty->GetName()+"'"); // Note that we also receive the update notification recursively (triggered by command execution in SubmitCommand()). // This is intentional, or else we had to manually fix the PropMan, CombinedPropInfos, etc. here, which is error-prone. // The complete recursive update is much less work and 100% safe, because it guarantees that no new bugs are introduced here. MapDoc->GetHistory().SubmitCommand(MacroCommand); }
bool EditorChoiceWindowT::HandlePGChange(wxPropertyGridEvent& Event, GuiEditor::ChildFrameT* ChildFrame) { if (EditorWindowT::HandlePGChange(Event, ChildFrame)) return true; const wxPGProperty* Prop =Event.GetProperty(); const wxString PropName=Prop->GetName(); if (PropName=="Choices") { ArrayT<std::string> NewStrings; wxStringTokenizer Tokenizer(Prop->GetValueAsString(), "\r\n"); while (Tokenizer.HasMoreTokens()) NewStrings.PushBack(std::string(Tokenizer.GetNextToken())); ChildFrame->SubmitCommand(new CommandSetWinPropT< ArrayT<std::string> >(m_GuiDoc, this, PropName, m_Choice->m_Choices, NewStrings)); return true; } if (PropName=="DefaultChoice") { wxASSERT(m_Choice->GetMemberVar("selectedChoice").Member!=NULL); ChildFrame->SubmitCommand(new CommandModifyWindowT(m_GuiDoc, m_Choice, PropName, m_Choice->GetMemberVar("selectedChoice"), Prop->GetValue().GetLong())); return true; } return false; }
void SubjectT::UpdateAllObservers_Modified(IntrusivePtrT<cf::GuiSys::WindowT> Window, WindowModDetailE Detail, const wxString& PropertyName) { ArrayT< IntrusivePtrT<cf::GuiSys::WindowT> > Windows; Windows.PushBack(Window); UpdateAllObservers_Modified(Windows, Detail, PropertyName); }
CommandSelectT* CommandSelectT::Add(GuiDocumentT* GuiDocument, IntrusivePtrT<cf::GuiSys::WindowT> Window) { ArrayT< IntrusivePtrT<cf::GuiSys::WindowT> > AddSelection; AddSelection.PushBack(Window); return CommandSelectT::Add(GuiDocument, AddSelection); }
CommandSelectT* CommandSelectT::Remove(GuiDocumentT* GuiDocument, IntrusivePtrT<cf::GuiSys::WindowT> Window) { ArrayT< IntrusivePtrT<cf::GuiSys::WindowT> > RemoveSelection; RemoveSelection.PushBack(Window); return CommandSelectT::Remove(GuiDocument, RemoveSelection); }
void SubjectT::UpdateAllObservers_Modified(IntrusivePtrT<cf::GuiSys::WindowT> Window, WindowModDetailE Detail) { ArrayT< IntrusivePtrT<cf::GuiSys::WindowT> > Windows; Windows.PushBack(Window); UpdateAllObservers_Modified(Windows, Detail); }
void SubjectT::UpdateAllObservers_Deleted(IntrusivePtrT<cf::GuiSys::WindowT> Window) { ArrayT< IntrusivePtrT<cf::GuiSys::WindowT> > Windows; Windows.PushBack(Window); UpdateAllObservers_Deleted(Windows); }
void CommandTransformT::Undo() { wxASSERT(m_Done); if (!m_Done) return; if (m_DoClone) { m_CommandSelect->Undo(); // Remove cloned objects from world again. for (unsigned long CloneNr=0; CloneNr<m_ClonedElems.Size(); CloneNr++) m_MapDoc.Remove(m_ClonedElems[CloneNr]); m_MapDoc.UpdateAllObservers_Deleted(m_ClonedElems); } else { // Record the previous bounding-boxes for the observer message. ArrayT<BoundingBox3fT> OldBounds; for (unsigned long ElemNr=0; ElemNr<m_TransElems.Size(); ElemNr++) { OldBounds.PushBack(m_TransElems[ElemNr]->GetBB()); m_TransElems[ElemNr]->Assign(m_OldStates[ElemNr]); } m_MapDoc.UpdateAllObservers_Modified(m_TransElems, MEMD_TRANSFORM, OldBounds); } m_Done=false; }
bool CommandTransformT::Do() { wxASSERT(!m_Done); if (m_Done) return false; if (m_DoClone) { // Insert cloned objects into the document, attaching them to the same parents as the respective source element. for (unsigned long CloneNr=0; CloneNr<m_ClonedElems.Size(); CloneNr++) { MapEntityT* Ent=dynamic_cast<MapEntityT*>(m_ClonedElems[CloneNr]); if (Ent) { m_MapDoc.Insert(Ent); continue; } MapPrimitiveT* ClonedPrim=dynamic_cast<MapPrimitiveT*>(m_ClonedElems[CloneNr]); MapPrimitiveT* OrigPrim =dynamic_cast<MapPrimitiveT*>(m_TransElems[CloneNr]); wxASSERT((ClonedPrim==NULL)==(OrigPrim==NULL)); if (ClonedPrim && OrigPrim) { m_MapDoc.Insert(ClonedPrim, OrigPrim->GetParent()); continue; } // TODO(?): Insert m_ClonedElems[CloneNr] into the same group as m_TransElems[CloneNr]? } m_MapDoc.UpdateAllObservers_Created(m_ClonedElems); m_CommandSelect->Do(); } else { // Record the previous bounding-boxes for the observer message. ArrayT<BoundingBox3fT> OldBounds; for (unsigned long ElemNr=0; ElemNr<m_TransElems.Size(); ElemNr++) { OldBounds.PushBack(m_TransElems[ElemNr]->GetBB()); switch (m_Mode) { case MODE_TRANSLATE: m_TransElems[ElemNr]->TrafoMove(m_Amount); break; case MODE_ROTATE: m_TransElems[ElemNr]->TrafoRotate(m_RefPoint, m_Amount); break; case MODE_SCALE: m_TransElems[ElemNr]->TrafoScale(m_RefPoint, m_Amount); break; case MODE_MATRIX: m_TransElems[ElemNr]->Transform(m_Matrix); break; } } m_MapDoc.UpdateAllObservers_Modified(m_TransElems, MEMD_TRANSFORM, OldBounds); } m_Done=true; return true; }
template<class T> void Polygon3T<T>::GetChoppedUpAlong(const Polygon3T<T>& SplittingPoly, const T EdgeThickness, ArrayT< Polygon3T<T> >& NewPolys) const { Polygon3T<T> FragmentPoly=*this; NewPolys.Clear(); for (unsigned long VertexNr=0; VertexNr<SplittingPoly.Vertices.Size(); VertexNr++) { const Plane3T<T> SplitPlane=SplittingPoly.GetEdgePlane(VertexNr, EdgeThickness); if (FragmentPoly.WhatSideSimple(SplitPlane, EdgeThickness)!=Both) continue; ArrayT< Polygon3T<T> > SplitResult=FragmentPoly.GetSplits(SplitPlane, EdgeThickness); FragmentPoly=SplitResult[0]; NewPolys.PushBack(SplitResult[1]); } NewPolys.PushBack(FragmentPoly); }
CommandDeleteT::CommandDeleteT(MapDocumentT& MapDoc, MapElementT* DeleteElem) : m_MapDoc(MapDoc), m_DeleteEnts(), m_DeletePrims(), m_DeletePrimsParents(), m_CommandSelect(NULL) { ArrayT<MapElementT*> DeleteElems; DeleteElems.PushBack(DeleteElem); Init(DeleteElems); }
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 CommandDeletePropertyT::Undo() { wxASSERT(m_Done); if (!m_Done) return; m_Entity->GetProperties().InsertAt(m_Index, m_PropBackup); ArrayT<MapElementT*> MapElements; MapElements.PushBack(m_Entity); m_MapDoc.UpdateAllObservers_Modified(MapElements, MEMD_ENTITY_PROPERTY_CREATED, m_PropBackup.Key); m_Done=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"); }
void EngineEntityT::WriteNewBaseLine(unsigned long SentClientBaseLineFrameNr, ArrayT< ArrayT<char> >& OutDatas) const { // Nur dann etwas tun, wenn unsere 'BaseLineFrameNr' größer (d.h. jünger) als 'SentClientBaseLineFrameNr' ist, // d.h. unsere 'BaseLineFrameNr' noch nie / noch nicht an den Client gesendet wurde. if (SentClientBaseLineFrameNr>=BaseLineFrameNr) return; NetDataT NewBaseLineMsg; NewBaseLineMsg.WriteByte(SC1_EntityBaseLine); NewBaseLineMsg.WriteLong(Entity->GetID()); NewBaseLineMsg.WriteLong(Entity->GetType()->TypeNr); NewBaseLineMsg.WriteLong(Entity->GetWorldFileIndex()); NewBaseLineMsg.WriteDMsg(m_BaseLine.GetDeltaMessage(cf::Network::StateT() /*::ALL_ZEROS*/)); OutDatas.PushBack(NewBaseLineMsg.Data); }
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(); }
ArrayT<const SoundShaderT*> SoundShaderManagerImplT::RegisterSoundShaderScript(const std::string& ScriptFile, const std::string& ModDir) { ArrayT<const SoundShaderT*> NewSoundShaders; // Check if script has already been loaded and return empty sound shader array if this is the case. for (unsigned long Nr=0; Nr<m_SoundShaderScriptFiles.Size(); Nr++) if (m_SoundShaderScriptFiles[Nr]==ScriptFile) return NewSoundShaders; m_SoundShaderScriptFiles.PushBack(ScriptFile); // Get sound shaders from the script. TextParserT TextParser(ScriptFile.c_str(), "({[]}),"); try { while (!TextParser.IsAtEOF()) { const std::string Token=TextParser.GetNextToken(); // If the sound shader cannot be parsed (e.g. due to a syntax error or unknown token), // the parsing of the entire file is aborted - the file might be something else than a sound shader script. // Even if it was, we cannot easily continue anyway. SoundShaderT* NewSoundShader=new SoundShaderT(Token, TextParser, ModDir); SoundShaderT*& TestShader =m_SoundShaders[NewSoundShader->Name]; // Check if sound shader with this name already exists. if (TestShader==NULL) { TestShader=NewSoundShader; NewSoundShaders.PushBack(NewSoundShader); } else { std::cout << "File '"<< ScriptFile << "' sound shader '" << NewSoundShader->Name << "' duplicate definition (ignored).\n"; delete NewSoundShader; } } } catch (const TextParserT::ParseError&) { std::cout << "Error parsing '" << ScriptFile << "' at input byte " << TextParser.GetReadPosByte() << "\n"; } return NewSoundShaders; }
bool CommandDeletePropertyT::Do() { wxASSERT(!m_Done); if (m_Done) return false; if (m_Index<0) return false; // Note that only non-class keys can be deleted here, so we don't need to call anything MapEntityT specific here - children don't need to be notified. m_Entity->GetProperties().RemoveAtAndKeepOrder(m_Index); // FIXME Note that when a property of multiple entities is deleted, this observer notification is created // for EACH of these entities. We should change the command to accept an array of entities... ArrayT<MapElementT*> MapElements; MapElements.PushBack(m_Entity); m_MapDoc.UpdateAllObservers_Modified(MapElements, MEMD_ENTITY_PROPERTY_DELETED, m_PropBackup.Key); m_Done=true; return true; }
void InspDlgEntityPropsT::OnContextMenuItemAdd(wxCommandEvent& event) { const wxString NewKey=wxGetTextFromUser("Please enter the name of the new key.", "New Key", ""); if (NewKey=="") return; // Check if a property with this key already exists. if (PropMan->GetPropertyByName(NewKey)!=NULL) { wxMessageBox("A key with the same name already exists.", "Couldn't create property", wxOK | wxICON_ERROR); return; } ArrayT<CommandT*> Commands; for (unsigned long i=0; i<SelectedEntities.Size(); i++) Commands.PushBack(new CommandSetPropertyT(*MapDoc, SelectedEntities[i], NewKey, "")); CommandT* MacroCommand=new CommandMacroT(Commands, "Add Property '"+NewKey+"'"); // Note that we also receive the update notification recursively (triggered by command execution in SubmitCommand()). // This is intentional, or else we had to manually fix the PropMan, CombinedPropInfos, etc. here, which is error-prone. // The complete recursive update is much less work and 100% safe, because it guarantees that no new bugs are introduced here. MapDoc->GetHistory().SubmitCommand(MacroCommand); }
// 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 InspDlgEntityPropsT::OnPropertyGridChanged(wxPropertyGridEvent& event) { // Load property info from property to get all entities that belong to this property. PropInfoT* PropInfo=(PropInfoT*)event.GetProperty()->GetClientData(); wxASSERT(PropInfo!=NULL); // Because the property grid items were created from a list of PropInfoTs. wxASSERT(PropInfo->KeyState!=MIXED); // Because keys with such state have been disabled from editing. // Only write value change to entities if the property got its property info and the key isn't a MIXED key // (as they are not editable and thus have been disabled for editing above). if (PropInfo==NULL || PropInfo->KeyState==MIXED) return; wxString NewValue=event.GetProperty()->GetValueAsString(); // We need to translate the value here according to property type. if (wxString(event.GetProperty()->GetClassInfo()->GetClassName())=="wxFlagsProperty" || wxString(event.GetProperty()->GetClassInfo()->GetClassName())=="wxBoolProperty") { NewValue=wxString::Format("%li", event.GetProperty()->GetValue().GetLong()); } else if (event.GetProperty()->GetValueType()=="wxColour") { wxColour NewColor; NewColor << event.GetProperty()->GetValue(); NewValue=wxString::Format("%i %i %i", NewColor.Red(), NewColor.Green(), NewColor.Blue()); } // Have the selected entities available as a set of MapElementTs as well. ArrayT<MapElementT*> MapElements; for (unsigned long i=0; i<SelectedEntities.Size(); i++) MapElements.PushBack(SelectedEntities[i]); CommandT* MacroCommand=NULL; // Changing the class of an entity. if (event.GetProperty()->GetName()=="classname") { // Only allow a class change if all entities share the same class. for (unsigned long i=1; i<SelectedEntities.Size(); i++) { if (SelectedEntities[i]->GetClass()!=SelectedEntities[0]->GetClass()) { wxMessageBox("Entities have different entity classes.", "Error: Couldn't change entity class.", wxOK | wxICON_ERROR); // Intentionally update also this dialog to restore previous property value. TODO: Can the event be vetoed instead? NotifySubjectChanged_Modified(MapDoc, MapElements, MEMD_ENTITY_PROPERTY_MODIFIED, ""); return; } } const EntityClassT* NewEntityClass=MapDoc->GetGameConfig()->FindClass(NewValue); if (NewEntityClass==NULL) { wxMessageBox("Could not find entity class \""+NewValue+"\" in the game config.", "Error: Couldn't change entity class.", wxOK | wxICON_ERROR); // Intentionally update also this dialog to restore previous property value. TODO: Can the event be vetoed instead? NotifySubjectChanged_Modified(MapDoc, MapElements, MEMD_ENTITY_PROPERTY_MODIFIED, ""); return; } ArrayT<CommandT*> Commands; for (unsigned long i=0; i<SelectedEntities.Size(); i++) { MapEntityT* Ent=dynamic_cast<MapEntityT*>(SelectedEntities[i]); if (Ent) Commands.PushBack(new CommandChangeClassT(*MapDoc, Ent, NewEntityClass)); } MacroCommand=new CommandMacroT(Commands, "Change entity class"); } else { wxASSERT(SelectedEntities[0]!=NULL); const wxString Key=event.GetProperty()->GetName(); // Check if key is found in all entities, in the same category (defined or undefined) and has the same type for all objects. // First entity as reference. bool Undefined=(SelectedEntities[0]->GetClass() && SelectedEntities[0]->GetClass()->FindVar(Key)==NULL); for (unsigned long i=1; i<SelectedEntities.Size(); i++) { if (Undefined) // Just check if key is also undefined (type is always string). { if ( (SelectedEntities[i]->GetClass() && SelectedEntities[i]->GetClass()->FindVar(Key)!=NULL) || SelectedEntities[i]->FindProperty(Key)==NULL) { wxMessageBox("Value is not defined by all entities.", "Error: Couldn't change property value.", wxOK | wxICON_ERROR); NotifySubjectChanged_Modified(MapDoc, MapElements, MEMD_ENTITY_PROPERTY_MODIFIED, ""); // Intentionally update also this dialog to restore previous property value. return; } } else // Check if key is defined and has the same type. { if ( SelectedEntities[i]->GetClass()==NULL || SelectedEntities[i]->GetClass()->FindVar(Key)==NULL || SelectedEntities[i]->GetClass()->FindVar(Key)->GetType()!=SelectedEntities[0]->GetClass()->FindVar(Key)->GetType()) { wxMessageBox("Value is not defined by all entities or hasn't the same type.", "Error: Couldn't change property value.", wxOK | wxICON_ERROR); NotifySubjectChanged_Modified(MapDoc, MapElements, MEMD_ENTITY_PROPERTY_MODIFIED, ""); // Intentionally update also this dialog to restore previous property value. return; } } } // Check Lua compatibility for entity names. if (Key=="name" && !IsLuaIdentifier(NewValue)) { wxMessageBox("An entity name must be a string of letters, digits, and underscores that is\n" "not beginning with a digit and is not a reserved Lua keyword or global variable.", "Entity name is not a valid script identifier.", wxOK | wxICON_ERROR); NotifySubjectChanged_Modified(MapDoc, MapElements, MEMD_ENTITY_PROPERTY_MODIFIED, ""); // Intentionally update also this dialog to restore previous property value. return; } // Handle unique keys here. if (SelectedEntities[0]->GetClass()->FindVar(Key) && SelectedEntities[0]->GetClass()->FindVar(Key)->IsUnique()) { // Only change unique when only one entity is selected. wxASSERT(SelectedEntities.Size()==1); // Since unique properties are disabled, when more than one entity is selected, we should never get here. // This is just a security precaution for release code. We forgo writing back the original value here, since this // case will probably never happen. if (SelectedEntities.Size()>1) { wxMessageBox("Unique keys can only be changed if one entity is selected.", "Error: Couldn't change unique key.", wxOK | wxICON_ERROR); NotifySubjectChanged_Modified(MapDoc, MapElements, MEMD_ENTITY_PROPERTY_MODIFIED, ""); // Intentionally update also this dialog to restore previous property value. return; } // Load all map entities. // We cannot call PropObjects[0]->CheckUniqueValues() here, since this method checks all values of the entity // for uniqueness so it would fail if another unique marked value is not unique. for (unsigned long EntNr=1/*skip world*/; EntNr<MapDoc->GetEntities().Size(); EntNr++) { MapEntityBaseT* Entity=MapDoc->GetEntities()[EntNr]; EntPropertyT* Prop=Entity->FindProperty(Key); const EntClassVarT* ClassVar=Entity->GetClass() ? Entity->GetClass()->FindVar(Key) : NULL; // Check if new value is already used by an entity. if ((Prop!=NULL && Prop->Value==NewValue) || (ClassVar!=NULL && ClassVar->GetDefault()==NewValue)) { // Set value in property grid to old value and return. wxMessageBox("This unique value is already used by another entity.", "Error: Couldn't change unique key.", wxOK | wxICON_ERROR); NotifySubjectChanged_Modified(MapDoc, MapElements, MEMD_ENTITY_PROPERTY_MODIFIED, ""); // Intentionally update also this dialog to restore previous property value. return; } } } // Set the property to its new value. ArrayT<CommandT*> Commands; for (unsigned long i=0; i<SelectedEntities.Size(); i++) Commands.PushBack(new CommandSetPropertyT(*MapDoc, SelectedEntities[i], Key, NewValue)); MacroCommand=new CommandMacroT(Commands, "Change property '"+Key+"'"); } // Execution of command triggers recursive update of this dialog. To prevent this we set IsRecursiveSelfNotify. IsRecursiveSelfNotify=true; // Run command (in SubmitCommand()) and react if its execution fails by updating the whole dialog recursively. // Also update whole dialog in case classname has changed. if (!MapDoc->GetHistory().SubmitCommand(MacroCommand) || event.GetProperty()->GetName()=="classname") { IsRecursiveSelfNotify=false; NotifySubjectChanged_Modified(MapDoc, MapElements, MEMD_ENTITY_PROPERTY_MODIFIED, ""); return; } IsRecursiveSelfNotify=false; PropInfo->ValueIsConsistent=true; PropMan ->SetPropertyBackgroundColour(event.GetProperty(), COLOR_CUSTOM); // Special case: Model file of the entity has changed. if (PropInfo->ClassVar && PropInfo->ClassVar->GetType()==EntClassVarT::TYPE_FILE_MODEL && event.GetProperty()->GetName()=="model") { wxPGProperty* Insert=PropMan->GetPropertyByName("collisionModel"); // If a collisionModel key is found color it to notify the user that it should be changed too. if (Insert!=NULL) PropMan->SetPropertyBackgroundColour(Insert, COLOR_WARNING); } // Check if newly set value is a DEFAULT value and update background color. if ((PropInfo->KeyState & NORMAL) && NewValue==PropInfo->ClassVar->GetDefault()) { PropMan->SetPropertyBackgroundColour(event.GetProperty(), COLOR_DEFAULT); PropInfo->ValueIsDefault=true; } }
// Diese Funktion bestimmt die Sichtpyramide ('Frustum'), die eine Lichtquelle 'LightSource' durch ein Loch 'Hole' wirft. // // Dabei leuchtet die 'LightSource' in die *entgegengesetzte* Richtung ihres Normalenvektors, // und das 'Hole' läßt auch nur Licht in der Gegenrichtung seines Normalenvektors durch. // Beides ist sinnvoll, denn sowohl 'LightSource' als auch 'Hole' sind idR Portale von Leaves. // Beachte, daß die Normalenvektoren von Portalen stets zur *Mitte* ihrer Leaves hin zeigen (nicht nach außen wie bei Brushes). // Die 'LightSource' ist dann ein Portal des vorangegangenen Leafs, durch das das aktuelle Leaf betreten wird. // Das 'Hole' ist ein Portal des aktuellen Leafs zum nächsten Leaf. // Bemerkung: Würde man das Loch zur Lichtquelle und umgekehrt machen (PolygonMirror), // wäre das Frustum das gleiche, dessen Ebenen wären aber gespiegelt! // // Es wird vorausgesetzt, daß 'LightSource' und 'Hole' gültige Polygone sind. // Wenn daraus erfolgreich ein Frustum konstruiert werden kann, wird dieses zurückgegeben und es gilt 'Frustum.Size()>0'. // Andernfalls scheitert die Funktion und es wird ein Frustum der Größe 0 zurückgegeben ('Frustum.Size()==0'). // Die Voraussetzung für den Erfolg dieser Funktion ist eine - in unserem Sinne - "vernünftige" Anordnung der beiden Polygone: // a) Das 'Hole' muß komplett auf der (Licht emittierenden) Rückseite der 'LightSource'-Ebene liegen. // b) Die 'LightSource' muß komplett auf der (lichtdurchlässigen) Vorderseite der 'Hole'-Ebene liegen. // Beachte, daß im allgemeinen bzw. erweiterten Sinne andere Frustren durchaus auch sinnvoll sein können, // z.B. wenn die 'LightSource' und das 'Hole' sich schneiden. // Solche Fälle betrachten wir jedoch als ungültig und sie führen zum Scheitern der Funktion. inline void FindFrustum(const Polygon3T<double>& LightSource, const Polygon3T<double>& Hole, ArrayT< Plane3T<double> >& Frustum) { Frustum.Overwrite(); if (Hole.WhatSideSimple(LightSource.Plane, MapT::RoundEpsilon)!=Polygon3T<double>::Back) return; if (LightSource.WhatSideSimple(Hole.Plane, MapT::RoundEpsilon)!=Polygon3T<double>::Front) return; unsigned long V2=Hole.Vertices.Size()-1; unsigned long V3; for (V3=0; V3<Hole.Vertices.Size(); V3++) { for (unsigned long V1=0; V1<LightSource.Vertices.Size(); V1++) { // Eigentlich würde ich hier gerne folgenden Wunsch-Code schreiben: // try // { // Plane3T<double> FrustumPlane(Hole.Vertices[V2], LightSource.Vertices[V1], Hole.Vertices[V3]); // // // ... // } // catch (DivisionByZero) { } // Nicht mögliche FrustumPlanes einfach ignorieren. // Aus irgendeinem Grund ist die Verwendung oder das Fangen der DivisionByZero-Exception aber sehr langsam. // Deshalb rolle ich lieber den Plane3T<double>-Konstruktor aus, um ohne dieses Exception-Handling auszukommen. // Das Programm wird *deutlich* schneller, ca. Faktor 1,5. Ob das eine Schwäche des Watcom-Compilers ist?? VectorT Normal(cross(Hole.Vertices[V3]-Hole.Vertices[V2], LightSource.Vertices[V1]-Hole.Vertices[V2])); double NLength=length(Normal); if (NLength<MapT::RoundEpsilon) continue; Normal=scale(Normal, 1.0/NLength); Plane3T<double> FrustumPlane(Normal, dot(Hole.Vertices[V2], Normal)); // Diese neue FrustumPlane nur dann akzeptieren, wenn das Hole auf ihrer Vorderseite liegt // (konstruktionsbedingt sowieso der Fall!) und die LightSource auf ihrer Rückseite liegt. // Wenn eine Edge des Hole in der Ebene der LightSource liegt, darf die LightSource // auch in der FrustumPlane liegen. Polygon3T<double>::SideT Side=LightSource.WhatSideSimple(FrustumPlane, MapT::RoundEpsilon); if (Side==Polygon3T<double>::Back || Side==Polygon3T<double>::InMirrored) { Frustum.PushBack(FrustumPlane); break; } } V2=V3; } // Rollen vertauschen: Das Loch sei nun die Lichtquelle, und die Lichtquelle das Loch! Siehe Skizze! V2=LightSource.Vertices.Size()-1; for (V3=0; V3<LightSource.Vertices.Size(); V3++) { for (unsigned long V1=0; V1<Hole.Vertices.Size(); V1++) // Optimize: Check if edges are in already existing frustum planes! { // Es bringt übrigens nichts, doppelt auftretende Planes hier vermeiden zu wollen! // Messungen waren z.B. 1:09:05 ohne Prüfung, 1:08:42 mit Prüfung auf Doppelvorkommen. // Könnte man aber später nochmal überprüfen... /* // Prüfe, ob wir diese Plane schon im Frustum haben. // Teste dazu, ob die drei Punkte in der Plane liegen. // Die Orientierung braucht dabei nicht beachtet zu werden. for (unsigned long FrustumNr=0; FrustumNr<FrustumSize1stPart; FrustumNr++) { const double Dist1=PlaneDistance(Frustum[FrustumNr], Hole.Vertices[V1]); const double Dist2=PlaneDistance(Frustum[FrustumNr], LightSource.Vertices[V2]); const double Dist3=PlaneDistance(Frustum[FrustumNr], LightSource.Vertices[V3]); if (fabs(Dist1)<0.1 && fabs(Dist2)<0.1 && fabs(Dist3)<0.1) break; } if (FrustumNr<FrustumSize1stPart) continue; */ // Eigentlich würde ich hier gerne folgenden Wunsch-Code schreiben: // try // { // Plane3T<double> FrustumPlane(LightSource.Vertices[V2], Hole.Vertices[V1], LightSource.Vertices[V3]); // // // ... // } // catch (DivisionByZero) { } // Nicht mögliche Ebenen einfach ignorieren. // Aus irgendeinem Grund ist die Verwendung oder das Fangen der DivisionByZero-Exception aber sehr langsam. // Deshalb rolle ich lieber den Plane3T<double>-Konstruktor aus, um ohne dieses Exception-Handling auszukommen. // Das Programm wird *deutlich* schneller, ca. Faktor 1,5. Ob das eine Schwäche des Watcom-Compilers ist?? VectorT Normal(cross(LightSource.Vertices[V3]-LightSource.Vertices[V2], Hole.Vertices[V1]-LightSource.Vertices[V2])); double NLength=length(Normal); if (NLength<MapT::RoundEpsilon) continue; Normal=scale(Normal, 1.0/NLength); Plane3T<double> FrustumPlane(Normal, dot(LightSource.Vertices[V2], Normal)); // Diese neue FrustumPlane nur dann akzeptieren, wenn die LightSource auf ihrer Rückseite // liegt (konstruktionsbedingt sowieso der Fall!) und das Hole auf ihrer Vorderseite liegt. // Wenn eine Edge der LightSource in der Ebene des Holes liegt, darf das Hole // auch in der FrustumPlane liegen. Polygon3T<double>::SideT Side=Hole.WhatSideSimple(FrustumPlane, MapT::RoundEpsilon); // Wegen dem Rollentausch ist die Orientierung für den WhatSideSimple() Test falsch, ... if (Side==Polygon3T<double>::Front || Side==Polygon3T<double>::InMirrored) { Frustum.PushBack(FrustumPlane); // ...im Gesamten aber wieder richtig! break; } } V2=V3; } }
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; }
// Enter the SuperLeaf 'CurrentSL' through the 'EnteringPortal' of the *preceding* SuperLeaf. // The ancestors of 'CurrentSL' are recorded in 'AncestorSLs'. For the remaining parameters, see the implementation. // Returns 'true' if visibility from the 'MasterSL' to the 'TargetSL' could be established, false otherwise. bool DetermineVisibility(unsigned long CurrentSL, const Polygon3T<double>& EnteringPortal, ArrayT<unsigned long> AncestorSLs, const Polygon3T<double>& MasterPortal, const unsigned long TargetSL, BoundingBox3T<double> TargetBB) { // Record that we can see from all ancestors into 'CurrentSL', and vice-versa. for (unsigned long AncestorNr=0; AncestorNr<AncestorSLs.Size(); AncestorNr++) { FlagVisible(AncestorSLs[AncestorNr], CurrentSL); FlagVisible(CurrentSL, AncestorSLs[AncestorNr]); } // If we reached the desired target, return success. if (CurrentSL==TargetSL) return true; // Clip 'TargetBB' against the plane of the 'EnteringPortal'. This is done to prevent the following kind of problem: // Consider a 'TargetSL' that is "thin" and diagonal. As a consequence, it will have a much too large 'TargetBB'. // If we then clip the 'TargetBB' against the frustum "MasterPortal --> EnteringPortal" below, // we might find that it is still (partially) inside this frustum. // However, these parts of the 'TargetBB' inside the frustum might be the much-too-large parts that arose from the special form of the 'TargetSL'. // In other words, had we done a better test instead (e.g. the "exact" one, by clipping the actual sub-portals of 'TargetSL' against the frustum), // we had found that the frustum actually missed the 'TargetSL' (that is, 'TargetSL' is entirely somewhere outside of the frustum). // In the former case, we'll find ourselves trying to find a visibility for something that's long "behind" us, and doing so at *exponential* costs! // In the latter case, we can skip all this from here. // Besides the "exact" test (which is also quite slow), we can also clip the 'TargetBB' against the plane of the 'EnteringPortal'. // The latter approach is conceptually a little different, but achieves the same positive net effect at lesser costs. switch (TargetBB.GetEpsilonBox(-MapT::RoundEpsilon).WhatSide(EnteringPortal.Plane)) { case BoundingBox3T<double>::Front: return false; case BoundingBox3T<double>::Back: // Do nothing. break; case BoundingBox3T<double>::Both: // Note the /2 in MapT::RoundEpsilon/2, this should make sure that GetSplits() doesn't reach another conclusion than WhatSide(), // i.e. it prevents that GetSplits() cannot find a proper sub-box on *both* sides of the plane. TargetBB=TargetBB.GetSplits(EnteringPortal.Plane, MapT::RoundEpsilon/2)[1]; break; } // Determine the frustum "MasterPortal --> EnteringPortal". ArrayT< Plane3T<double> > Frustum; // Note that 'Frustum' cannot be declared as 'static', as this function recurses! FindFrustum(MasterPortal, EnteringPortal, Frustum); if (Frustum.Size()==0) { // printf("\nWARNING: Invalid frustum 1.\n"); // Should never happen. return false; } // Clip 'TargetBB' against the frustum "MasterPortal --> EnteringPortal". unsigned long FrustumNr; for (FrustumNr=0; FrustumNr<Frustum.Size(); FrustumNr++) { switch (TargetBB.GetEpsilonBox(-MapT::RoundEpsilon).WhatSide(Frustum[FrustumNr])) { case BoundingBox3T<double>::Front: // Do nothing. break; case BoundingBox3T<double>::Back: return false; case BoundingBox3T<double>::Both: // Note the /2 in MapT::RoundEpsilon/2, this should make sure that GetSplits() doesn't reach another conclusion than WhatSide(), // i.e. it prevents that GetSplits() cannot find a proper sub-box on *both* sides of the plane. TargetBB=TargetBB.GetSplits(Frustum[FrustumNr], MapT::RoundEpsilon/2)[0]; break; } } // Bevor wir in das nächste SuperLeaf gehen, nimm das gegenwärtige 'CurrentSL' in die Liste der Vorgänger auf. AncestorSLs.PushBack(CurrentSL); for (unsigned long NeighbourNr=0; NeighbourNr<SuperLeaves[CurrentSL].Neighbours.Size(); NeighbourNr++) { const unsigned long NextSL =SuperLeaves[CurrentSL].Neighbours[NeighbourNr].SuperLeafNr; Polygon3T<double> NextPortal=SuperLeaves[CurrentSL].Neighbours[NeighbourNr].SubPortal; // Wenn für das 'NextSL' schon festgestellt wurde, daß das 'TargetSL' von dort aus nicht zu sehen ist, können wir direkt weitermachen. if (NextSL<AncestorSLs[0]/*MasterSL*/ && !IsVisible(NextSL, TargetSL)) continue; // Abkürzung: Gleiche Ebene? Dann weiter mit dem nächsten Portal! Polygon3T<double>::SideT Side=NextPortal.WhatSide(EnteringPortal.Plane, MapT::RoundEpsilon); if (Side==Polygon3T<double>::InIdentical || Side==Polygon3T<double>::InMirrored) continue; // Clippe 'NextPortal' gegen das Frustum "MasterPortal --> EnteringPortal". for (FrustumNr=0; FrustumNr<Frustum.Size(); FrustumNr++) { Polygon3T<double>::SideT Side=NextPortal.WhatSideSimple(Frustum[FrustumNr], MapT::RoundEpsilon); if (Side==Polygon3T<double>::Both ) NextPortal=NextPortal.GetSplits(Frustum[FrustumNr], MapT::RoundEpsilon)[0]; else if (Side!=Polygon3T<double>::Front) break; } if (FrustumNr<Frustum.Size()) continue; // Bestimme das Frustum "EnteringPortal --> NextPortal". static ArrayT< Plane3T<double> > Frustum2; FindFrustum(EnteringPortal, NextPortal, Frustum2); if (Frustum2.Size()==0) { // printf("\nWARNING: Invalid frustum 2.\n"); // Should never happen. continue; } // Clippe 'MasterPortal' gegen das Frustum "NextPortal --> EnteringPortal". // Um die Portale nicht spiegeln zu müssen, das gespiegelte Frustum benutzen! Polygon3T<double> MP=MasterPortal; for (FrustumNr=0; FrustumNr<Frustum2.Size(); FrustumNr++) { Polygon3T<double>::SideT Side=MP.WhatSideSimple(Frustum2[FrustumNr], MapT::RoundEpsilon); if (Side==Polygon3T<double>::Both) MP=MP.GetSplits(Frustum2[FrustumNr], MapT::RoundEpsilon)[1]; else if (Side!=Polygon3T<double>::Back) break; } if (FrustumNr<Frustum2.Size()) continue; if (DetermineVisibility(NextSL, NextPortal, AncestorSLs, MP, TargetSL, TargetBB)) return true; } return false; }
// Analytically computes the 'SuperLeavesPVS'. // A prior call to 'DetermineTrivialVisibility()' is assumed. void BuildPVS() { /* // Der komplette alte, aber einfache Code. Evtl. nützlich für Debugging-Zwecke. for (unsigned long MasterSL=0; MasterSL+1<SuperLeaves.Size(); MasterSL++) { printf("%5.1f%%\r", (double)MasterSL/SuperLeaves.Size()*100.0); fflush(stdout); // Bestimme für jedes SuperLeaves-Paar, ob eine gegenseitige Sichtbarkeit besteht. // Beachte, daß die folgende Schleife bei 'MasterSL+1' starten kann (statt bei '0'), da Sichtbarkeit immer wechselseitig ist. for (unsigned long TargetSL=MasterSL+1; TargetSL<SuperLeaves.Size(); TargetSL++) { // Wenn die Sichtbarkeit zwischen 'MasterSL' und 'TargetSL' bereits festgestellt wurde, // können wir direkt mit dem nächsten 'TargetSL' weitermachen. Sinnvoll für unmittelbare Nachbarn. // Auch vorangegangene Iterationen für andere, aber weiter weg liegende 'TargetSL's haben u.U. // "auf dem Weg" liegende SuperLeaves als sichtbar markiert, sodaß diese hier nicht nochmal getestet werden müssen. if (IsVisible(MasterSL, TargetSL)) continue; // Wenn das 'TargetSL' keine Nachbarn hat, können wir direkt mit dem nächsten 'TargetSL' weitermachen. // Für ein solches SL ohne Nachbarn haben wir sowieso keine sinnvolle "Target Bounding Box" in den 'SuperLeavesBBs' konstruiert! if (SuperLeaves[TargetSL].Neighbours.Size()==0) continue; // Intentionally ignore the returned result. CanSeeFromAToB(MasterSL, TargetSL); } } */ // Für jedes SuperLeaf das PVS bestimmen. for (unsigned long MasterSL=0; MasterSL<SuperLeaves.Size(); MasterSL++) { printf("%5.1f%%\r", (double)MasterSL/SuperLeaves.Size()*100.0); fflush(stdout); // Initialisiere die Hilfsarrays für das 'MasterSL'. ArrayT<bool> AV; // List of "Already Visible" SuperLeaves from 'MasterSL'. ArrayT<bool> PV; // List of "Potentially Visible" SuperLeaves from 'MasterSL'. ArrayT<bool> NV; // List of "Not Visible" SuperLeaves from 'MasterSL'. unsigned long SL; for (SL=0; SL<SuperLeaves.Size(); SL++) { AV.PushBack(false); PV.PushBack(false); NV.PushBack(false); } // Bestimme, welche SuperLeaves wir von 'MasterSL' aus schon sehen können. for (SL=0; SL<SuperLeaves.Size(); SL++) if (IsVisible(MasterSL, SL)) AV[SL]=true; // Bilde eine Liste 'PV' aller SuperLeaves, die von 'MasterSL' aus "potentially visible" sind. // Dies sind zunächst *alle* Nachbarn aller SuperLeaves in der 'AV' Liste, außer denjenigen, // die bereits in der 'AV' oder 'NV' Liste sind, oder deren Index-Nummern 'NeighbourSL' kleiner/gleich 'MasterSL' sind. // Der Grund für letzteres: Wenn früher schon festgestellt wurde, daß wir nicht von 'NeighbourSL' nach 'MasterSL' sehen können, // können wir uns jetzt den Test, ob wir von 'MasterSL' nach 'NeighbourSL' sehen können, sparen. // Wäre der frühere Test dagegen positiv gewesen, wäre 'AV[NeighbourSL]==true', was auch keinen 'PV'-Eintrag verursacht. for (SL=0; SL<SuperLeaves.Size(); SL++) if (AV[SL]) for (unsigned long NeighbourNr=0; NeighbourNr<SuperLeaves[SL].Neighbours.Size(); NeighbourNr++) { const unsigned long NeighbourSL=SuperLeaves[SL].Neighbours[NeighbourNr].SuperLeafNr; if (!AV[NeighbourSL] && !NV[NeighbourSL] && NeighbourSL>MasterSL) PV[NeighbourSL]=true; } // For each 'TargetSL' in 'PV': Determine if 'TargetSL' is visible from 'MasterSL'. while (true) { unsigned long TargetSL; for (TargetSL=0; TargetSL<PV.Size(); TargetSL++) if (PV[TargetSL]) break; if (TargetSL>=PV.Size()) break; if (CanSeeFromAToB(MasterSL, TargetSL)) { // Redo the init (but 'NV' is possibly not empty anymore). for (SL=0; SL<SuperLeaves.Size(); SL++) { AV[SL]=false; PV[SL]=false; } // Bestimme, welche SuperLeaves wir von 'MasterSL' aus schon sehen können. for (SL=0; SL<SuperLeaves.Size(); SL++) if (IsVisible(MasterSL, SL)) AV[SL]=true; // Bilde eine Liste 'PV' aller SuperLeaves, die von 'MasterSL' aus "potentially visible" sind. // Dies sind zunächst *alle* Nachbarn aller SuperLeaves in der 'AV' Liste, außer denjenigen, // die bereits in der 'AV' oder 'NV' Liste sind, oder deren Index-Nummern 'NeighbourSL' kleiner/gleich 'MasterSL' sind. // Der Grund für letzteres: Wenn früher schon festgestellt wurde, daß wir nicht von 'NeighbourSL' nach 'MasterSL' sehen können, // können wir uns jetzt den Test, ob wir von 'MasterSL' nach 'NeighbourSL' sehen können, sparen. // Wäre der frühere Test dagegen positiv gewesen, wäre 'AV[NeighbourSL]==true', was auch keinen 'PV'-Eintrag verursacht. for (SL=0; SL<SuperLeaves.Size(); SL++) if (AV[SL]) for (unsigned long NeighbourNr=0; NeighbourNr<SuperLeaves[SL].Neighbours.Size(); NeighbourNr++) { const unsigned long NeighbourSL=SuperLeaves[SL].Neighbours[NeighbourNr].SuperLeafNr; if (!AV[NeighbourSL] && !NV[NeighbourSL] && NeighbourSL>MasterSL) PV[NeighbourSL]=true; } } else { PV[TargetSL]=false; // Delete 'TargetSL' from PV list. NV[TargetSL]=true; // Add 'TargetSL' to NV list. } } } printf("Final Avg Visibility: %10.5f\n", GetAverageVisibility()); }
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; } }