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() {