diff --git a/doomsday/client/include/world/contactblockmap.h b/doomsday/client/include/world/contactblockmap.h index 0bc08f54a3..9cd239c443 100644 --- a/doomsday/client/include/world/contactblockmap.h +++ b/doomsday/client/include/world/contactblockmap.h @@ -21,11 +21,51 @@ #ifndef DENG_CLIENT_WORLD_CONTACTBLOCKMAP_H #define DENG_CLIENT_WORLD_CONTACTBLOCKMAP_H +#include #include "world/map.h" class BspLeaf; +struct Contact; class Lumobj; +class ContactBlockmap +{ +public: + /** + * Construct a new contact blockmap. + * + * @param bounds Bounding box of the blockmap. + * @param blockSize Size of each block. + */ + ContactBlockmap(AABoxd const &bounds, uint blockSize = 128); + + /** + * Returns the origin of the blockmap in map space. + */ + de::Vector2d const &origin() const; + + /** + * @param contact Contact to be linked. Note that if object's origin lies + * outside the blockmap it will not be linked! + */ + void link(Contact *contact); + + /** + * Clear all the contact list heads and spread flags. + */ + void unlinkAll(); + + /** + * Spread contacts in the blockmap to any touched neighbors. + * + * @param box Map space region in which to perform spreading. + */ + void spreadAllContacts(AABoxd const &box); + +private: + DENG2_PRIVATE(d) +}; + /** * To be called during game change/on shutdown to destroy all contact blockmaps. * This is necessary because the blockmaps are allocated from the Zone using a diff --git a/doomsday/client/src/world/contactblockmap.cpp b/doomsday/client/src/world/contactblockmap.cpp index f56cc092bd..a4c88e92f6 100644 --- a/doomsday/client/src/world/contactblockmap.cpp +++ b/doomsday/client/src/world/contactblockmap.cpp @@ -133,9 +133,101 @@ struct Contact } }; -class ContactBlockmap +struct ListNode +{ + ListNode *next; ///< Next in the BSP leaf. + ListNode *nextUsed; ///< Next used contact. + void *obj; +}; +static ListNode *firstNode; ///< First unused list node. +static ListNode *cursor; ///< Current list node. + +struct ContactList +{ + // Start reusing list nodes. + static void reset() + { + cursor = firstNode; + } + + void link(Contact *contact) + { + if(!contact) return; + + ListNode *node = newNode(contact->objectPtr()); + + node->next = _head; + _head = node; + } + + ListNode *begin() const + { + return _head; + } + +private: + /** + * Create a new list node. If there are none available in the list of + * used objects a new one will be allocated and linked to the global list. + */ + static ListNode *newNode(void *object) + { + DENG2_ASSERT(object != 0); + + ListNode *node; + if(!cursor) + { + node = (ListNode *) Z_Malloc(sizeof(*node), PU_APPSTATIC, 0); + + // Link in the global list of used nodes. + node->nextUsed = firstNode; + firstNode = node; + } + else + { + node = cursor; + cursor = cursor->nextUsed; + } + + node->obj = object; + node->next = 0; + + return node; + } + + ListNode *_head; +}; + +// Separate contact lists for each BSP leaf and contact type. +static ContactList *bspLeafContactLists; + +static inline ContactList &contactList(BspLeaf &bspLeaf, ContactType type) +{ + return bspLeafContactLists[bspLeaf.indexInMap() * ContactTypeCount + int( type )]; +} + +/** + * On which side of the half-edge does the specified @a point lie? + * + * @param hedge Half-edge to test. + * @param point Point to test in the map coordinate space. + * + * @return @c <0 Point is to the left/back of the segment. + * @c =0 Point lies directly on the segment. + * @c >0 Point is to the right/front of the segment. + */ +static coord_t pointOnHEdgeSide(HEdge const &hedge, Vector2d const &point) +{ + Vector2d const direction = hedge.twin().origin() - hedge.origin(); + + ddouble pointV1[2] = { point.x, point.y }; + ddouble fromOriginV1[2] = { hedge.origin().x, hedge.origin().y }; + ddouble directionV1[2] = { direction.x, direction.y }; + return V2d_PointOnLineSide(pointV1, fromOriginV1, directionV1); +} + +DENG2_PIMPL(ContactBlockmap) { -public: struct CellData { Contact *head; @@ -148,20 +240,35 @@ class ContactBlockmap } }; -public: - ContactBlockmap(AABoxd const &bounds, uint blockSize = 128) - : _origin(bounds.min), - _gridmap(Vector2ui(de::ceil((bounds.maxX - bounds.minX) / ddouble( blockSize )), - de::ceil((bounds.maxY - bounds.minY) / ddouble( blockSize ))), - sizeof(CellData), PU_MAPSTATIC) + Vector2d origin; ///< Origin in map space. + Gridmap gridmap; + + // For perf, spread state data is "global". + struct SpreadState + { + Contact *contact; + AABoxd contactAABox; + }; + SpreadState spread; + + Instance(Public *i, AABoxd const &bounds, uint blockSize) + : Base(i), + origin(bounds.min), + gridmap(Vector2ui(de::ceil((bounds.maxX - bounds.minX) / ddouble( blockSize )), + de::ceil((bounds.maxY - bounds.minY) / ddouble( blockSize ))), + sizeof(CellData), PU_MAPSTATIC) {} - /** - * Returns the origin of the blockmap in map space. - */ - Vector2d const &origin() const + inline uint toX(ddouble x) const { - return _origin; + DENG2_ASSERT(x >= origin.x); + return (x - origin.x) / ddouble( BLOCK_SIZE ); + } + + inline uint toY(ddouble y) const + { + DENG2_ASSERT(y >= origin.y); + return (y - origin.y) / ddouble( BLOCK_SIZE ); } /** @@ -176,18 +283,18 @@ class ContactBlockmap */ GridmapCell toCell(Vector2d const &point, bool *retAdjusted = 0) const { - Vector2d const max = _origin + _gridmap.dimensions() * BLOCK_SIZE; + Vector2d const max = origin + gridmap.dimensions() * BLOCK_SIZE; GridmapCell cell; bool adjusted = false; - if(point.x < _origin.x) + if(point.x < origin.x) { cell.x = 0; adjusted = true; } else if(point.x >= max.x) { - cell.x = _gridmap.width() - 1; + cell.x = gridmap.width() - 1; adjusted = true; } else @@ -195,14 +302,14 @@ class ContactBlockmap cell.x = toX(point.x); } - if(point.y < _origin.y) + if(point.y < origin.y) { cell.y = 0; adjusted = true; } else if(point.y >= max.y) { - cell.y = _gridmap.height() - 1; + cell.y = gridmap.height() - 1; adjusted = true; } else @@ -224,44 +331,168 @@ class ContactBlockmap } /** - * @param contact Contact to be linked. Note that if object's origin lies - * outside the blockmap it will not be linked! + * Returns the data for the specified cell. */ - void link(Contact *contact) + inline CellData *cellData(GridmapCell const &cell, bool canAlloc = false) { - if(!contact) return; - - bool outside; - GridmapCell cell = toCell(contact->objectOrigin(), &outside); - if(outside) return; - - CellData *cellData = data(cell, true/*can allocate a block*/); - contact->nextInBlock = cellData->head; - cellData->head = contact; + return static_cast(gridmap.cellData(cell, canAlloc)); } - // Clear all the contact list heads and spread flags. - void unlinkAll() + void maybeSpreadOverEdge(HEdge *hedge) { - iterate(unlinkAllWorker); - } + DENG2_ASSERT(spread.contact != 0); - CellData *data(GridmapCell const &cell, bool canAlloc = false) - { - return static_cast(_gridmap.cellData(cell, canAlloc)); + if(!hedge) return; + + BspLeaf &leaf = hedge->face().mapElementAs(); + SectorCluster &cluster = leaf.cluster(); + + // There must be a back BSP leaf to spread to. + if(!hedge->hasTwin()) return; + if(!hedge->twin().hasFace()) return; + + BspLeaf &backLeaf = hedge->twin().face().mapElementAs(); + if(!backLeaf.hasCluster()) return; + + SectorCluster &backCluster = backLeaf.cluster(); + + // Which way does the spread go? + if(!(leaf.validCount() == validCount && backLeaf.validCount() != validCount)) + { + return; // Not eligible for spreading. + } + + AABoxd const &backLeafAABox = backLeaf.poly().aaBox(); + + // Is the leaf on the back side outside the origin's AABB? + if(backLeafAABox.maxX <= spread.contactAABox.minX || + backLeafAABox.minX >= spread.contactAABox.maxX || + backLeafAABox.maxY <= spread.contactAABox.minY || + backLeafAABox.minY >= spread.contactAABox.maxY) + return; + + // Too far from the edge? + coord_t const length = (hedge->twin().origin() - hedge->origin()).length(); + coord_t const distance = pointOnHEdgeSide(*hedge, spread.contact->objectOrigin()) / length; + if(de::abs(distance) >= spread.contact->objectRadius()) + return; + + // Do not spread if the sector on the back side is closed with no height. + if(!backCluster.hasWorldVolume()) + return; + + if(backCluster.visCeiling().heightSmoothed() <= cluster.visFloor().heightSmoothed() || + backCluster.visFloor().heightSmoothed() >= cluster.visCeiling().heightSmoothed()) + return; + + // Are there line side surfaces which should prevent spreading? + if(hedge->hasMapElement()) + { + LineSideSegment const &seg = hedge->mapElementAs(); + + // On which side of the line are we? (distance is from segment to origin). + LineSide const &facingLineSide = seg.line().side(seg.lineSide().sideId() ^ (distance < 0)); + + // One-way window? + if(!facingLineSide.back().hasSections()) + return; + + SectorCluster const &fromCluster = facingLineSide.isFront()? cluster : backCluster; + SectorCluster const &toCluster = facingLineSide.isFront()? backCluster : cluster; + + // Might a material cover the opening? + if(facingLineSide.hasSections() && facingLineSide.middle().hasMaterial()) + { + // Stretched middles always cover the opening. + if(facingLineSide.isFlagged(SDF_MIDDLE_STRETCH)) + return; + + // Determine the opening between the visual sector planes at this edge. + coord_t openBottom; + if(toCluster.visFloor().heightSmoothed() > fromCluster.visFloor().heightSmoothed()) + { + openBottom = toCluster.visFloor().heightSmoothed(); + } + else + { + openBottom = fromCluster.visFloor().heightSmoothed(); + } + + coord_t openTop; + if(toCluster.visCeiling().heightSmoothed() < fromCluster.visCeiling().heightSmoothed()) + { + openTop = toCluster.visCeiling().heightSmoothed(); + } + else + { + openTop = fromCluster.visCeiling().heightSmoothed(); + } + + // Ensure we have up to date info about the material. + MaterialSnapshot const &ms = facingLineSide.middle().material().prepare(Rend_MapSurfaceMaterialSpec()); + if(ms.height() >= openTop - openBottom) + { + // Possibly; check the placement. + WallEdge edge(WallSpec::fromMapSide(facingLineSide, LineSide::Middle), + *facingLineSide.leftHEdge(), Line::From); + + if(edge.isValid() && edge.top().z() > edge.bottom().z() && + edge.top().z() >= openTop && edge.bottom().z() <= openBottom) + return; + } + } + } + + // During the next step this contact will spread from the back leaf. + backLeaf.setValidCount(validCount); + + contactList(backLeaf, spread.contact->type()).link(spread.contact); + + spreadInBspLeaf(backLeaf); } -private: - inline uint toX(ddouble x) const + /** + * Attempt to spread the obj from the given contact from the source + * BspLeaf and into the (relative) back BspLeaf. + * + * @param bspLeaf BspLeaf to attempt to spread over to. + * + * @return Always @c true. (This function is also used as an iterator.) + */ + void spreadInBspLeaf(BspLeaf &bspLeaf) { - DENG2_ASSERT(x >= _origin.x); - return (x - _origin.x) / ddouble( BLOCK_SIZE ); + if(bspLeaf.hasCluster()) + { + HEdge *base = bspLeaf.poly().hedge(); + HEdge *hedge = base; + do + { + maybeSpreadOverEdge(hedge); + + } while((hedge = &hedge->next()) != base); + } } - inline uint toY(ddouble y) const + /** + * Link the contact in all BspLeafs which touch the linked object (tests are + * done with bounding boxes and the BSP leaf spread test). + * + * @param contact Contact to be spread. + */ + void spreadContact(Contact &contact) { - DENG2_ASSERT(y >= _origin.y); - return (y - _origin.y) / ddouble( BLOCK_SIZE ); + BspLeaf &bspLeaf = contact.objectBspLeafAtOrigin(); + DENG2_ASSERT(bspLeaf.hasCluster()); // Sanity check. + + contactList(bspLeaf, contact.type()).link(&contact); + + // Spread to neighboring BSP leafs. + bspLeaf.setValidCount(++validCount); + + spread.contact = &contact; + spread.contactAABox = contact.objectAABox(); + + spreadInBspLeaf(bspLeaf); } static int unlinkAllWorker(void *obj, void *context) @@ -270,90 +501,61 @@ class ContactBlockmap static_cast(obj)->unlinkAll(); return false; // Continue iteration. } - - int iterate(int (*callback) (void *obj, void *context), void *context = 0) - { - return _gridmap.iterate(callback, context); - } - - Vector2d _origin; ///< Origin in map space. - Gridmap _gridmap; }; -// Each contactable object type uses a separate blockmap. -static ContactBlockmap *blockmaps[ContactTypeCount]; +ContactBlockmap::ContactBlockmap(const AABoxd &bounds, uint blockSize) + : d(new Instance(this, bounds, blockSize)) +{} -struct ListNode +Vector2d const &ContactBlockmap::origin() const { - ListNode *next; ///< Next in the BSP leaf. - ListNode *nextUsed; ///< Next used contact. - void *obj; -}; -static ListNode *firstNode; ///< First unused list node. -static ListNode *cursor; ///< Current list node. + return d->origin; +} -struct ContactList +void ContactBlockmap::link(Contact *contact) { - // Start reusing list nodes. - static void reset() - { - cursor = firstNode; - } + if(!contact) return; - void link(Contact *contact) - { - if(!contact) return; + bool outside; + GridmapCell cell = d->toCell(contact->objectOrigin(), &outside); + if(outside) return; - ListNode *node = newNode(contact->objectPtr()); + Instance::CellData *data = d->cellData(cell, true/*can allocate a block*/); + contact->nextInBlock = data->head; + data->head = contact; +} - node->next = _head; - _head = node; - } +void ContactBlockmap::unlinkAll() +{ + d->gridmap.iterate(Instance::unlinkAllWorker); +} - ListNode *begin() const - { - return _head; - } +void ContactBlockmap::spreadAllContacts(AABoxd const &box) +{ + GridmapCellBlock const cellBlock = d->toCellBlock(box); -private: - /** - * Create a new list node. If there are none available in the list of - * used objects a new one will be allocated and linked to the global list. - */ - static ListNode *newNode(void *object) + GridmapCell cell; + for(cell.y = cellBlock.min.y; cell.y <= cellBlock.max.y; ++cell.y) + for(cell.x = cellBlock.min.x; cell.x <= cellBlock.max.x; ++cell.x) { - DENG2_ASSERT(object != 0); - - ListNode *node; - if(!cursor) - { - node = (ListNode *) Z_Malloc(sizeof(*node), PU_APPSTATIC, 0); - - // Link in the global list of used nodes. - node->nextUsed = firstNode; - firstNode = node; - } - else + Instance::CellData *data = d->cellData(cell, true/*can allocate a block*/); + if(!data->doneSpread) { - node = cursor; - cursor = cursor->nextUsed; + for(Contact *iter = data->head; iter; iter = iter->nextInBlock) + { + d->spreadContact(*iter); + } + data->doneSpread = true; } - - node->obj = object; - node->next = 0; - - return node; } +} - ListNode *_head; -}; +// Each contactable object type uses a separate blockmap. +static ContactBlockmap *blockmaps[ContactTypeCount]; static Contact *contacts; static Contact *contactFirst, *contactCursor; -// Separate contact lists for each BSP leaf and contact type. -static ContactList *bspLeafContactLists; - static Contact *newContact(void *object, ContactType type) { DENG2_ASSERT(object != 0); @@ -418,11 +620,6 @@ static inline ContactBlockmap &blockmap(ContactType type) return *blockmaps[int( type )]; } -static inline ContactList &contactList(BspLeaf &bspLeaf, ContactType type) -{ - return bspLeafContactLists[bspLeaf.indexInMap() * ContactTypeCount + int( type )]; -} - void R_ClearContacts(Map &map) { for(int i = 0; i < ContactTypeCount; ++i) @@ -444,218 +641,6 @@ void R_ClearContacts(Map &map) } } -/** - * On which side of the half-edge does the specified @a point lie? - * - * @param hedge Half-edge to test. - * @param point Point to test in the map coordinate space. - * - * @return @c <0 Point is to the left/back of the segment. - * @c =0 Point lies directly on the segment. - * @c >0 Point is to the right/front of the segment. - */ -static coord_t pointOnHEdgeSide(HEdge const &hedge, Vector2d const &point) -{ - Vector2d const direction = hedge.twin().origin() - hedge.origin(); - - ddouble pointV1[2] = { point.x, point.y }; - ddouble fromOriginV1[2] = { hedge.origin().x, hedge.origin().y }; - ddouble directionV1[2] = { direction.x, direction.y }; - return V2d_PointOnLineSide(pointV1, fromOriginV1, directionV1); -} - -// For perf, spread state data is global. -struct SpreadState -{ - Contact *contact; - AABoxd contactAABox; -}; -static SpreadState spread; - -static void spreadInBspLeaf(BspLeaf &bspLeaf); - -static void maybeSpreadOverEdge(HEdge *hedge) -{ - DENG2_ASSERT(spread.contact != 0); - - if(!hedge) return; - - BspLeaf &leaf = hedge->face().mapElementAs(); - SectorCluster &cluster = leaf.cluster(); - - // There must be a back BSP leaf to spread to. - if(!hedge->hasTwin()) return; - if(!hedge->twin().hasFace()) return; - - BspLeaf &backLeaf = hedge->twin().face().mapElementAs(); - if(!backLeaf.hasCluster()) return; - - SectorCluster &backCluster = backLeaf.cluster(); - - // Which way does the spread go? - if(!(leaf.validCount() == validCount && backLeaf.validCount() != validCount)) - { - return; // Not eligible for spreading. - } - - AABoxd const &backLeafAABox = backLeaf.poly().aaBox(); - - // Is the leaf on the back side outside the origin's AABB? - if(backLeafAABox.maxX <= spread.contactAABox.minX || - backLeafAABox.minX >= spread.contactAABox.maxX || - backLeafAABox.maxY <= spread.contactAABox.minY || - backLeafAABox.minY >= spread.contactAABox.maxY) - return; - - // Too far from the edge? - coord_t const length = (hedge->twin().origin() - hedge->origin()).length(); - coord_t const distance = pointOnHEdgeSide(*hedge, spread.contact->objectOrigin()) / length; - if(de::abs(distance) >= spread.contact->objectRadius()) - return; - - // Do not spread if the sector on the back side is closed with no height. - if(!backCluster.hasWorldVolume()) - return; - - if(backCluster.visCeiling().heightSmoothed() <= cluster.visFloor().heightSmoothed() || - backCluster.visFloor().heightSmoothed() >= cluster.visCeiling().heightSmoothed()) - return; - - // Are there line side surfaces which should prevent spreading? - if(hedge->hasMapElement()) - { - LineSideSegment const &seg = hedge->mapElementAs(); - - // On which side of the line are we? (distance is from segment to origin). - LineSide const &facingLineSide = seg.line().side(seg.lineSide().sideId() ^ (distance < 0)); - - // One-way window? - if(!facingLineSide.back().hasSections()) - return; - - SectorCluster const &fromCluster = facingLineSide.isFront()? cluster : backCluster; - SectorCluster const &toCluster = facingLineSide.isFront()? backCluster : cluster; - - // Might a material cover the opening? - if(facingLineSide.hasSections() && facingLineSide.middle().hasMaterial()) - { - // Stretched middles always cover the opening. - if(facingLineSide.isFlagged(SDF_MIDDLE_STRETCH)) - return; - - // Determine the opening between the visual sector planes at this edge. - coord_t openBottom; - if(toCluster.visFloor().heightSmoothed() > fromCluster.visFloor().heightSmoothed()) - { - openBottom = toCluster.visFloor().heightSmoothed(); - } - else - { - openBottom = fromCluster.visFloor().heightSmoothed(); - } - - coord_t openTop; - if(toCluster.visCeiling().heightSmoothed() < fromCluster.visCeiling().heightSmoothed()) - { - openTop = toCluster.visCeiling().heightSmoothed(); - } - else - { - openTop = fromCluster.visCeiling().heightSmoothed(); - } - - // Ensure we have up to date info about the material. - MaterialSnapshot const &ms = facingLineSide.middle().material().prepare(Rend_MapSurfaceMaterialSpec()); - if(ms.height() >= openTop - openBottom) - { - // Possibly; check the placement. - WallEdge edge(WallSpec::fromMapSide(facingLineSide, LineSide::Middle), - *facingLineSide.leftHEdge(), Line::From); - - if(edge.isValid() && edge.top().z() > edge.bottom().z() && - edge.top().z() >= openTop && edge.bottom().z() <= openBottom) - return; - } - } - } - - // During the next step this contact will spread from the back leaf. - backLeaf.setValidCount(validCount); - - contactList(backLeaf, spread.contact->type()).link(spread.contact); - - spreadInBspLeaf(backLeaf); -} - -/** - * Attempt to spread the obj from the given contact from the source - * BspLeaf and into the (relative) back BspLeaf. - * - * @param bspLeaf BspLeaf to attempt to spread over to. - * - * @return Always @c true. (This function is also used as an iterator.) - */ -static void spreadInBspLeaf(BspLeaf &bspLeaf) -{ - if(bspLeaf.hasCluster()) - { - HEdge *base = bspLeaf.poly().hedge(); - HEdge *hedge = base; - do - { - maybeSpreadOverEdge(hedge); - - } while((hedge = &hedge->next()) != base); - } -} - -/** - * Link the contact in all BspLeafs which touch the linked object (tests are - * done with bounding boxes and the BSP leaf spread test). - * - * @param contact Contact to be spread. - */ -static void spreadContact(Contact &contact) -{ - BspLeaf &bspLeaf = contact.objectBspLeafAtOrigin(); - DENG2_ASSERT(bspLeaf.hasCluster()); // Sanity check. - - contactList(bspLeaf, contact.type()).link(&contact); - - // Spread to neighboring BSP leafs. - bspLeaf.setValidCount(++validCount); - - spread.contact = &contact; - spread.contactAABox = contact.objectAABox(); - - spreadInBspLeaf(bspLeaf); -} - -/** - * Spread contacts in the blockmap to any touched neighbors. - * - * @param bmap Contact blockmap. - * @param box Map space region in which to perform spreading. - */ -static void spreadAllContacts(ContactBlockmap &bmap, AABoxd const &box) -{ - GridmapCellBlock const cellBlock = bmap.toCellBlock(box); - - GridmapCell cell; - for(cell.y = cellBlock.min.y; cell.y <= cellBlock.max.y; ++cell.y) - for(cell.x = cellBlock.min.x; cell.x <= cellBlock.max.x; ++cell.x) - { - ContactBlockmap::CellData *data = bmap.data(cell, true/*can allocate a block*/); - if(!data->doneSpread) - { - for(Contact *iter = data->head; iter; iter = iter->nextInBlock) - { - spreadContact(*iter); - } - data->doneSpread = true; - } - } -} static inline float radiusMax(ContactType type) { @@ -686,7 +671,7 @@ void R_SpreadContacts(BspLeaf &bspLeaf) bounds.maxX += maxRadius; bounds.maxY += maxRadius; - spreadAllContacts(bmap, bounds); + bmap.spreadAllContacts(bounds); } }