From 22e867890367c8d9d65446d4f4dbe9807bd10c42 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 16 Feb 2016 12:51:10 +0100 Subject: [PATCH] - refactored P_CollectConnectedGroups to avoid frequent heap allocations for the common cases * the temporary checking arrays are now static * the array that gets the returned values only starts allocating memory when the third touched sector group is found. The most common cases (no touched portal and one touched portal) can be handled without accessing the heap. - did some streamlining of AActor::LinkToSector: * there's only now version of this function that can handle everything * moved the FIXMAPTHINGPOS stuff into a separate function. * removed LinkToWorldForMapThing and put all special handling this function did into P_PointInSectorBuggy. --- src/actor.h | 6 +- src/p_blockmap.h | 3 +- src/p_checkposition.h | 57 ++++--- src/p_maputl.cpp | 371 +++++++++++++++++++++--------------------- src/p_mobj.cpp | 2 +- src/portal.cpp | 81 +++++++-- src/portal.h | 4 +- 7 files changed, 294 insertions(+), 230 deletions(-) diff --git a/src/actor.h b/src/actor.h index 1dcd35b90..42865543c 100644 --- a/src/actor.h +++ b/src/actor.h @@ -44,6 +44,7 @@ struct subsector_t; class PClassAmmo; struct FBlockNode; +struct FPortalGroupArray; // // NOTES: AActor @@ -1131,11 +1132,10 @@ private: friend class FActorIterator; friend bool P_IsTIDUsed(int tid); - sector_t *LinkToWorldForMapThing (); + bool FixMapthingPos(); public: - void LinkToWorld (bool buggy=false); - void LinkToWorld (sector_t *sector); + void LinkToWorld (bool spawningmapthing=false, FPortalGroupArray *groups = NULL, sector_t *sector = NULL); void UnlinkFromWorld (); void AdjustFloorClip (); void SetOrigin (fixed_t x, fixed_t y, fixed_t z, bool moving = false); diff --git a/src/p_blockmap.h b/src/p_blockmap.h index 0380b9da7..9cbe2e84b 100644 --- a/src/p_blockmap.h +++ b/src/p_blockmap.h @@ -10,12 +10,13 @@ struct FBlockNode { AActor *Me; // actor this node references int BlockIndex; // index into blocklinks for the block this node is in + int Group; // portal group this link belongs to (can be different than the actor's own group FBlockNode **PrevActor; // previous actor in this block FBlockNode *NextActor; // next actor in this block FBlockNode **PrevBlock; // previous block this actor is in FBlockNode *NextBlock; // next block this actor is in - static FBlockNode *Create (AActor *who, int x, int y); + static FBlockNode *Create (AActor *who, int x, int y, int group = -1); void Release (); static FBlockNode *FreeBlocks; diff --git a/src/p_checkposition.h b/src/p_checkposition.h index 7deccc562..6eee5374a 100644 --- a/src/p_checkposition.h +++ b/src/p_checkposition.h @@ -4,41 +4,58 @@ //============================================================================ // -// Data structure which collects all portals being touched by an actor -// when checking its position. +// This is a dynamic array which holds its first MAX_STATIC entries in normal +// variables to avoid constant allocations which this would otherwise +// require. +// +// When collecting touched portal groups the normal cases are either +// no portals == one group or +// two portals = two groups +// +// Anything with more can happen but far less infrequently, so this +// organization helps avoiding the overhead from heap allocations +// in the vast majority of situations. // //============================================================================ -struct FPortalGroupTable +struct FPortalGroupArray { - TArray data; - TArray touchingGroups; - - void setSize(int num) + enum { - data.Resize((num + 31) / 32); - memset(&data[0], 0, data.Size()*sizeof(DWORD)); + MAX_STATIC = 2 + }; + + FPortalGroupArray() + { + varused = 0; } - void clear() + void Clear() { data.Clear(); - touchingGroups.Clear(); + varused = 0; } - void setBit(int group) + void Add(DWORD num) { - if (!getBit(group)) - { - data[group >> 5] |= (1 << (group & 31)); - touchingGroups.Push(group); - } + if (varused < MAX_STATIC) entry[varused++] = num; + else data.Push(num); } - int getBit(int group) + unsigned Size() { - return data[group >> 5] & (1 << (group & 31)); + return varused + data.Size(); } + + DWORD operator[](unsigned index) + { + return index < MAX_STATIC ? entry[index] : data[index - MAX_STATIC]; + } + +private: + DWORD entry[MAX_STATIC]; + unsigned varused; + TArray data; }; @@ -78,7 +95,7 @@ struct FCheckPosition bool DoRipping; TMap LastRipped; - FPortalGroupTable Groups; + FPortalGroupArray Groups; int PushTime; FCheckPosition(bool rip=false) diff --git a/src/p_maputl.cpp b/src/p_maputl.cpp index 7df43f1c8..e09c86eb4 100644 --- a/src/p_maputl.cpp +++ b/src/p_maputl.cpp @@ -46,6 +46,8 @@ #include "po_man.h" static AActor *RoughBlockCheck (AActor *mo, int index, void *); +static int R_PointOnSideSlow(fixed_t x, fixed_t y, node_t *node); +sector_t *P_PointInSectorBuggy(fixed_t x, fixed_t y); //========================================================================== @@ -315,39 +317,118 @@ void AActor::UnlinkFromWorld () } +//========================================================================== +// +// If the thing is exactly on a line, move it into the sector +// slightly in order to resolve clipping issues in the renderer. +// +//========================================================================== + +bool AActor::FixMapthingPos() +{ + sector_t *secstart = P_PointInSectorBuggy(X(), Y()); + + int blockx = GetSafeBlockX(X() - bmaporgx); + int blocky = GetSafeBlockY(Y() - bmaporgy); + bool success = false; + + if ((unsigned int)blockx < (unsigned int)bmapwidth && + (unsigned int)blocky < (unsigned int)bmapheight) + { + int *list; + + for (list = blockmaplump + blockmap[blocky*bmapwidth + blockx] + 1; *list != -1; ++list) + { + line_t *ldef = &lines[*list]; + + if (ldef->frontsector == ldef->backsector) + { // Skip two-sided lines inside a single sector + continue; + } + if (ldef->backsector != NULL) + { + if (ldef->frontsector->floorplane == ldef->backsector->floorplane && + ldef->frontsector->ceilingplane == ldef->backsector->ceilingplane) + { // Skip two-sided lines without any height difference on either side + continue; + } + } + + // Not inside the line's bounding box + if (X() + radius <= ldef->bbox[BOXLEFT] + || X() - radius >= ldef->bbox[BOXRIGHT] + || Y() + radius <= ldef->bbox[BOXBOTTOM] + || Y() - radius >= ldef->bbox[BOXTOP]) + continue; + + // Get the exact distance to the line + divline_t dll, dlv; + fixed_t linelen = (fixed_t)sqrt((double)ldef->dx*ldef->dx + (double)ldef->dy*ldef->dy); + + P_MakeDivline(ldef, &dll); + + dlv.x = X(); + dlv.y = Y(); + dlv.dx = FixedDiv(dll.dy, linelen); + dlv.dy = -FixedDiv(dll.dx, linelen); + + fixed_t distance = abs(P_InterceptVector(&dlv, &dll)); + + if (distance < radius) + { + DPrintf("%s at (%d,%d) lies on %s line %td, distance = %f\n", + this->GetClass()->TypeName.GetChars(), X() >> FRACBITS, Y() >> FRACBITS, + ldef->dx == 0 ? "vertical" : ldef->dy == 0 ? "horizontal" : "diagonal", + ldef - lines, FIXED2DBL(distance)); + angle_t finean = R_PointToAngle2(0, 0, ldef->dx, ldef->dy); + if (ldef->backsector != NULL && ldef->backsector == secstart) + { + finean += ANGLE_90; + } + else + { + finean -= ANGLE_90; + } + finean >>= ANGLETOFINESHIFT; + + // Get the distance we have to move the object away from the wall + distance = radius - distance; + SetXY(X() + FixedMul(distance, finecosine[finean]), Y() + FixedMul(distance, finesine[finean])); + success = true; + } + } + } + return success; +} + //========================================================================== // // P_SetThingPosition -// Links a thing into both a block and a subsector based on it's x y. +// Links a thing into both a block and a subsector based on its x y. // Sets thing->sector properly // //========================================================================== -void AActor::LinkToWorld (bool buggy) +void AActor::LinkToWorld (bool spawningmapthing, FPortalGroupArray *groups, sector_t *sector) { - // link into subsector - sector_t *sec; - - if (!buggy || numgamenodes == 0) + if (spawningmapthing && (flags4 & MF4_FIXMAPTHINGPOS) && sector == NULL) { - sec = P_PointInSector (X(), Y()); - } - else - { - sec = LinkToWorldForMapThing (); + if (FixMapthingPos()) spawningmapthing = false; } - LinkToWorld (sec); -} - -void AActor::LinkToWorld (sector_t *sec) -{ - if (sec == NULL) + if (sector == NULL) { - LinkToWorld (); - return; + if (!spawningmapthing || numgamenodes == 0) + { + sector = P_PointInSector(X(), Y()); + } + else + { + sector = P_PointInSectorBuggy(X(), Y()); + } } - Sector = sec; + + Sector = sector; subsector = R_PointInSubsector(X(), Y()); // this is from the rendering nodes, not the gameplay nodes! if ( !(flags & MF_NOSECTOR) ) @@ -356,7 +437,7 @@ void AActor::LinkToWorld (sector_t *sec) // killough 8/11/98: simpler scheme using pointer-to-pointer prev // pointers, allows head nodes to be treated like everything else - AActor **link = &sec->thinglist; + AActor **link = §or->thinglist; AActor *next = *link; if ((snext = next)) next->sprev = &snext; @@ -405,7 +486,7 @@ void AActor::LinkToWorld (sector_t *sec) for (int x = x1; x <= x2; ++x) { FBlockNode **link = &blocklinks[y*bmapwidth + x]; - FBlockNode *node = FBlockNode::Create (this, x, y); + FBlockNode *node = FBlockNode::Create (this, x, y, this->Sector->PortalGroup); // Link in to block if ((node->NextActor = *link) != NULL) @@ -426,168 +507,6 @@ void AActor::LinkToWorld (sector_t *sec) } } -//========================================================================== -// -// [RH] LinkToWorldForMapThing -// -// Emulate buggy PointOnLineSide and fix actors that lie on -// lines to compensate for some IWAD maps. -// -//========================================================================== - -static int R_PointOnSideSlow (fixed_t x, fixed_t y, node_t *node) -{ - // [RH] This might have been faster than two multiplies and an - // add on a 386/486, but it certainly isn't on anything newer than that. - fixed_t dx; - fixed_t dy; - double left; - double right; - - if (!node->dx) - { - if (x <= node->x) - return node->dy > 0; - - return node->dy < 0; - } - if (!node->dy) - { - if (y <= node->y) - return node->dx < 0; - - return node->dx > 0; - } - - dx = (x - node->x); - dy = (y - node->y); - - // Try to quickly decide by looking at sign bits. - if ( (node->dy ^ node->dx ^ dx ^ dy)&0x80000000 ) - { - if ( (node->dy ^ dx) & 0x80000000 ) - { - // (left is negative) - return 1; - } - return 0; - } - - // we must use doubles here because the fixed point code will produce errors due to loss of precision for extremely short linedefs. - left = (double)node->dy * (double)dx; - right = (double)dy * (double)node->dx; - - if (right < left) - { - // front side - return 0; - } - // back side - return 1; -} - -sector_t *AActor::LinkToWorldForMapThing () -{ - node_t *node = gamenodes + numgamenodes - 1; - - do - { - // Use original buggy point-on-side test when spawning - // things at level load so that the map spots in the - // emerald key room of Hexen MAP01 are spawned on the - // window ledge instead of the blocking floor in front - // of it. Why do I consider it buggy? Because a point - // that lies directly on a line should always be - // considered as "in front" of the line. The orientation - // of the line should be irrelevant. - node = (node_t *)node->children[R_PointOnSideSlow (X(), Y(), node)]; - } - while (!((size_t)node & 1)); - - subsector_t *ssec = (subsector_t *)((BYTE *)node - 1); - - if (flags4 & MF4_FIXMAPTHINGPOS) - { - // If the thing is exactly on a line, move it into the subsector - // slightly in order to resolve clipping issues in the renderer. - // This check needs to use the blockmap, because an actor on a - // one-sided line might go into a subsector behind the line, so - // the line would not be included as one of its subsector's segs. - - int blockx = GetSafeBlockX(X() - bmaporgx); - int blocky = GetSafeBlockY(Y() - bmaporgy); - - if ((unsigned int)blockx < (unsigned int)bmapwidth && - (unsigned int)blocky < (unsigned int)bmapheight) - { - int *list; - - for (list = blockmaplump + blockmap[blocky*bmapwidth + blockx] + 1; *list != -1; ++list) - { - line_t *ldef = &lines[*list]; - - if (ldef->frontsector == ldef->backsector) - { // Skip two-sided lines inside a single sector - continue; - } - if (ldef->backsector != NULL) - { - if (ldef->frontsector->floorplane == ldef->backsector->floorplane && - ldef->frontsector->ceilingplane == ldef->backsector->ceilingplane) - { // Skip two-sided lines without any height difference on either side - continue; - } - } - - // Not inside the line's bounding box - if (X() + radius <= ldef->bbox[BOXLEFT] - || X() - radius >= ldef->bbox[BOXRIGHT] - || Y() + radius <= ldef->bbox[BOXBOTTOM] - || Y() - radius >= ldef->bbox[BOXTOP] ) - continue; - - // Get the exact distance to the line - divline_t dll, dlv; - fixed_t linelen = (fixed_t)sqrt((double)ldef->dx*ldef->dx + (double)ldef->dy*ldef->dy); - - P_MakeDivline (ldef, &dll); - - dlv.x = X(); - dlv.y = Y(); - dlv.dx = FixedDiv(dll.dy, linelen); - dlv.dy = -FixedDiv(dll.dx, linelen); - - fixed_t distance = abs(P_InterceptVector(&dlv, &dll)); - - if (distance < radius) - { - DPrintf ("%s at (%d,%d) lies on %s line %td, distance = %f\n", - this->GetClass()->TypeName.GetChars(), X()>>FRACBITS, Y()>>FRACBITS, - ldef->dx == 0? "vertical" : ldef->dy == 0? "horizontal" : "diagonal", - ldef-lines, FIXED2DBL(distance)); - angle_t finean = R_PointToAngle2 (0, 0, ldef->dx, ldef->dy); - if (ldef->backsector != NULL && ldef->backsector == ssec->sector) - { - finean += ANGLE_90; - } - else - { - finean -= ANGLE_90; - } - finean >>= ANGLETOFINESHIFT; - - // Get the distance we have to move the object away from the wall - distance = radius - distance; - SetXY(X() + FixedMul(distance, finecosine[finean]), Y() + FixedMul(distance, finesine[finean])); - return P_PointInSector (X(), Y()); - } - } - } - } - - return ssec->sector; -} - void AActor::SetOrigin (fixed_t ix, fixed_t iy, fixed_t iz, bool moving) { UnlinkFromWorld (); @@ -601,7 +520,7 @@ void AActor::SetOrigin (fixed_t ix, fixed_t iy, fixed_t iz, bool moving) FBlockNode *FBlockNode::FreeBlocks = NULL; -FBlockNode *FBlockNode::Create (AActor *who, int x, int y) +FBlockNode *FBlockNode::Create (AActor *who, int x, int y, int group) { FBlockNode *block; @@ -1528,6 +1447,67 @@ static AActor *RoughBlockCheck (AActor *mo, int index, void *param) return NULL; } +//========================================================================== +// +// [RH] LinkToWorldForMapThing +// +// Emulate buggy PointOnLineSide and fix actors that lie on +// lines to compensate for some IWAD maps. +// +//========================================================================== + +static int R_PointOnSideSlow(fixed_t x, fixed_t y, node_t *node) +{ + // [RH] This might have been faster than two multiplies and an + // add on a 386/486, but it certainly isn't on anything newer than that. + fixed_t dx; + fixed_t dy; + double left; + double right; + + if (!node->dx) + { + if (x <= node->x) + return node->dy > 0; + + return node->dy < 0; + } + if (!node->dy) + { + if (y <= node->y) + return node->dx < 0; + + return node->dx > 0; + } + + dx = (x - node->x); + dy = (y - node->y); + + // Try to quickly decide by looking at sign bits. + if ((node->dy ^ node->dx ^ dx ^ dy) & 0x80000000) + { + if ((node->dy ^ dx) & 0x80000000) + { + // (left is negative) + return 1; + } + return 0; + } + + // we must use doubles here because the fixed point code will produce errors due to loss of precision for extremely short linedefs. + left = (double)node->dy * (double)dx; + right = (double)dy * (double)node->dx; + + if (right < left) + { + // front side + return 0; + } + // back side + return 1; +} + + //=========================================================================== // // P_VanillaPointOnLineSide @@ -1616,3 +1596,24 @@ int P_VanillaPointOnDivlineSide(fixed_t x, fixed_t y, const divline_t* line) return 1; // back side } +sector_t *P_PointInSectorBuggy(fixed_t x, fixed_t y) +{ + node_t *node = gamenodes + numgamenodes - 1; + + do + { + // Use original buggy point-on-side test when spawning + // things at level load so that the map spots in the + // emerald key room of Hexen MAP01 are spawned on the + // window ledge instead of the blocking floor in front + // of it. Why do I consider it buggy? Because a point + // that lies directly on a line should always be + // considered as "in front" of the line. The orientation + // of the line should be irrelevant. + node = (node_t *)node->children[R_PointOnSideSlow(x, y, node)]; + } while (!((size_t)node & 1)); + + subsector_t *ssec = (subsector_t *)((BYTE *)node - 1); + return ssec->sector; +} + diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index edf8598c9..82326c39b 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -456,7 +456,7 @@ void AActor::Serialize (FArchive &arc) if (arc.IsLoading ()) { touching_sectorlist = NULL; - LinkToWorld (Sector); + LinkToWorld (false, NULL, Sector); AddToHash (); SetShade (fillcolor); if (player) diff --git a/src/portal.cpp b/src/portal.cpp index 8f7fda6e6..a525b22f0 100644 --- a/src/portal.cpp +++ b/src/portal.cpp @@ -62,6 +62,39 @@ FDisplacementTable Displacements; TArray linePortals; TArray linkedPortals; // only the linked portals, this is used to speed up looking for them in P_CollectConnectedGroups. +//============================================================================ +// +// This is used to mark processed portals for some collection functions. +// +//============================================================================ + +struct FPortalBits +{ + TArray data; + + void setSize(int num) + { + data.Resize((num + 31) / 32); + clear(); + } + + void clear() + { + memset(&data[0], 0, data.Size()*sizeof(DWORD)); + } + + void setBit(int group) + { + data[group >> 5] |= (1 << (group & 31)); + } + + int getBit(int group) + { + return data[group >> 5] & (1 << (group & 31)); + } +}; + + //============================================================================ // @@ -1021,22 +1054,31 @@ void P_CreateLinkedPortals() // //============================================================================ -bool P_CollectConnectedGroups(AActor *actor, fixed_t newx, fixed_t newy, FPortalGroupTable &out) +bool P_CollectConnectedGroups(AActor *actor, fixed_t newx, fixed_t newy, FPortalGroupArray &out) { - TArray foundPortals; + // Keep this temporary work stuff static. This function can never be called recursively + // and this would have to be reallocated for each call otherwise. + static FPortalBits processMask; + static TArray foundPortals; + bool retval = false; - if (linePortals.Size() == 0) + if (linkedPortals.Size() == 0) { + // If there are no portals, all sectors are in group 0. + out.Add(0); return false; } - out.setSize(Displacements.size); - out.setBit(actor->Sector->PortalGroup); - //FBoundingBox box(newx, newy, actor->radius); + processMask.setSize(linkedPortals.Size()); + processMask.clear(); + foundPortals.Clear(); + int thisgroup = actor->Sector->PortalGroup; - for (unsigned i = 0; i < linePortals.Size(); i++) + processMask.setBit(thisgroup); + out.Add(thisgroup); + + for (unsigned i = 0; i < linkedPortals.Size(); i++) { - if (linePortals[i].mType != PORTT_LINKED) continue; // not a linked portal - line_t *ld = linePortals[i].mOrigin; + line_t *ld = linkedPortals[i]->mOrigin; int othergroup = ld->frontsector->PortalGroup; FDisplacement &disp = Displacements(thisgroup, othergroup); if (!disp.isSet) continue; // no connection. @@ -1049,8 +1091,8 @@ bool P_CollectConnectedGroups(AActor *actor, fixed_t newx, fixed_t newy, FPortal || box.Bottom() >= ld->bbox[BOXTOP]) continue; // not touched - if (box.BoxOnLineSide(linePortals[i].mOrigin) != -1) continue; // not touched - foundPortals.Push(&linePortals[i]); + if (box.BoxOnLineSide(linkedPortals[i]->mOrigin) != -1) continue; // not touched + foundPortals.Push(linkedPortals[i]); } bool foundone = true; while (foundone) @@ -1058,10 +1100,11 @@ bool P_CollectConnectedGroups(AActor *actor, fixed_t newx, fixed_t newy, FPortal foundone = false; for (int i = foundPortals.Size() - 1; i >= 0; i--) { - if (out.getBit(foundPortals[i]->mOrigin->frontsector->PortalGroup) && - !out.getBit(foundPortals[i]->mDestination->frontsector->PortalGroup)) + if (processMask.getBit(foundPortals[i]->mOrigin->frontsector->PortalGroup) && + !processMask.getBit(foundPortals[i]->mDestination->frontsector->PortalGroup)) { - out.setBit(foundPortals[i]->mDestination->frontsector->PortalGroup); + processMask.setBit(foundPortals[i]->mDestination->frontsector->PortalGroup); + out.Add(foundPortals[i]->mDestination->frontsector->PortalGroup); foundone = true; retval = true; foundPortals.Delete(i); @@ -1076,8 +1119,9 @@ bool P_CollectConnectedGroups(AActor *actor, fixed_t newx, fixed_t newy, FPortal FDisplacement &disp = Displacements(actor->Sector->PortalGroup, othersec->PortalGroup); fixed_t dx = newx + disp.x; fixed_t dy = newx + disp.y; - out.setBit(othersec->PortalGroup); - wsec = P_PointInSector(dx, dy); // get upper sector at the exact spot we want to check and repeat, + processMask.setBit(othersec->PortalGroup); + out.Add(othersec->PortalGroup); + wsec = P_PointInSector(dx, dy); // get upper sector at the exact spot we want to check and repeat retval = true; } wsec = sec; @@ -1087,8 +1131,9 @@ bool P_CollectConnectedGroups(AActor *actor, fixed_t newx, fixed_t newy, FPortal FDisplacement &disp = Displacements(actor->Sector->PortalGroup, othersec->PortalGroup); fixed_t dx = newx + disp.x; fixed_t dy = newx + disp.y; - out.setBit(othersec->PortalGroup); - wsec = P_PointInSector(dx, dy); // get lower sector at the exact spot we want to check and repeat, + processMask.setBit(othersec->PortalGroup); + out.Add(othersec->PortalGroup); + wsec = P_PointInSector(dx, dy); // get lower sector at the exact spot we want to check and repeat retval = true; } return retval; diff --git a/src/portal.h b/src/portal.h index 6dd271696..40c4a30fc 100644 --- a/src/portal.h +++ b/src/portal.h @@ -9,7 +9,7 @@ #include "m_bbox.h" #include "a_sharedglobal.h" -struct FPortalGroupTable; +struct FPortalGroupArray; //============================================================================ // // This table holds the offsets for the different parts of a map @@ -120,7 +120,7 @@ void P_SpawnLinePortal(line_t* line); void P_FinalizePortals(); bool P_ChangePortal(line_t *ln, int thisid, int destid); void P_CreateLinkedPortals(); -bool P_CollectConnectedGroups(AActor *actor, fixed_t newx, fixed_t newy, FPortalGroupTable &out); +bool P_CollectConnectedGroups(AActor *actor, fixed_t newx, fixed_t newy, FPortalGroupArray &out); void P_CollectLinkedPortals(); inline int P_NumPortalGroups() {