Ejemplo n.º 1
0
// 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");
}
Ejemplo n.º 2
0
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)
{
}
Ejemplo n.º 3
0
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); }
        }
    }
}
Ejemplo n.º 4
0
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;
}
Ejemplo n.º 5
0
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;
    }
}
Ejemplo n.º 6
0
/*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;
}
Ejemplo n.º 7
0
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());
    }
}
Ejemplo n.º 8
0
// 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();
}
Ejemplo n.º 9
0
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;
}
Ejemplo n.º 10
0
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;
}
Ejemplo n.º 11
0
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;
    }
}
Ejemplo n.º 12
0
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();
}
Ejemplo n.º 13
0
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;
}
Ejemplo n.º 14
0
// 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;
}
Ejemplo n.º 15
0
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;
}
Ejemplo n.º 16
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);
}
Ejemplo n.º 17
0
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();
}
Ejemplo n.º 18
0
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++;
        }
    }
}
Ejemplo n.º 19
0
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;
        }
    }
}
Ejemplo n.º 20
0
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;
}
Ejemplo n.º 21
0
// 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;
}
Ejemplo n.º 22
0
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();
}
Ejemplo n.º 23
0
// 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");
}
Ejemplo n.º 24
0
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;
    }
}
Ejemplo n.º 25
0
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;
}
Ejemplo n.º 26
0
// 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());
}
Ejemplo n.º 27
0
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
}
Ejemplo n.º 28
0
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));
}
Ejemplo n.º 29
0
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;
}
Ejemplo n.º 30
0
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;
    }
}