diff --git a/doomsday/client/include/world/blockmap.h b/doomsday/client/include/world/blockmap.h index 3222c5fc13..c8a08b6725 100644 --- a/doomsday/client/include/world/blockmap.h +++ b/doomsday/client/include/world/blockmap.h @@ -35,8 +35,8 @@ namespace de { class Blockmap { public: - typedef Gridmap::Cell Cell; - typedef Gridmap::CellBlock CellBlock; + typedef GridmapCell Cell; + typedef GridmapCellBlock CellBlock; public: /** diff --git a/doomsday/client/include/world/contactblockmap.h b/doomsday/client/include/world/contactblockmap.h index 57146be117..fbb3762c88 100644 --- a/doomsday/client/include/world/contactblockmap.h +++ b/doomsday/client/include/world/contactblockmap.h @@ -22,74 +22,103 @@ #define DENG_CLIENT_WORLD_CONTACTBLOCKMAP_H #include + +#include + #include "world/map.h" class BspLeaf; struct Contact; class Lumobj; -class ContactBlockmap +enum ContactType +{ + ContactMobj = 0, + ContactLumobj, + + ContactTypeCount +}; + +/// @todo Obviously, polymorphism is a better solution. +struct Contact { -public: + Contact *nextUsed; ///< Next in the used list (if any, not owned). + Contact *next; ///< Next in global list of contacts (if any, not owned). + + ContactType _type; ///< Logical identifier. + void *_object; ///< The contacted object. + + ContactType type() const; + + void *objectPtr() const; + + template + ObjectType &objectAs() const { + DENG2_ASSERT(_object != 0); + return *static_cast(_object); + } + /** - * Construct a new contact blockmap. - * - * @param bounds Bounding box of the blockmap. - * @param blockSize Size of each block. + * Returns a copy of the linked object's origin in map space. */ - ContactBlockmap(AABoxd const &bounds, uint blockSize = 128); + de::Vector3d objectOrigin() const; /** - * Returns the origin of the blockmap in the map coordinate space. + * Returns the linked object's radius in map space. */ - de::Vector2d origin() const; + double objectRadius() const; /** - * Returns the bounds of the blockmap in the map coordinate space. + * Returns an axis-aligned bounding box for the linked object in map space. */ - AABoxd const &bounds() const; + AABoxd objectAABox() const; /** - * @param contact Contact to be linked. Note that if the object's origin - * lies outside the blockmap it will not be linked! + * Returns the BSP leaf at the linked object's origin in map space. */ + BspLeaf &objectBspLeafAtOrigin() const; +}; + +struct ListNode; + +struct ContactList +{ + // Start reusing list nodes. + static void reset(); + void link(Contact *contact); - /** - * Clear all linked contacts. - */ - void unlinkAll(); + ListNode *begin() const; +private: /** - * Spread contacts in the blockmap to any touched neighbors. - * - * @param box Map space region in which to perform spreading. + * 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. */ - void spreadAllContacts(AABoxd const &box); + static ListNode *newNode(void *object); -private: - DENG2_PRIVATE(d) + ListNode *_head; }; /** - * 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 + * To be called during game change/on shutdown to destroy all contact lists. + * This is necessary because the lists are allocated from the Zone using a * >= PU_MAP purge level and access to them is handled with global pointers. * - * @todo Encapsulate allocation of and access to the blockmaps in de::Map + * @todo Encapsulate allocation of and access to the lists in de::Map */ -void R_DestroyContactBlockmaps(); +void R_DestroyContactLists(); /** - * Initialize contact blockmaps for the current map. + * Initialize contact lists for the current map. */ -void R_InitContactBlockmaps(de::Map &map); +void R_InitContactLists(de::Map &map); /** - * To be called at the beginning of a render frame to clear all contacts ready - * for the new frame. + * To be called at the beginning of a render frame to clear all contact lists + * ready for the new frame. */ -void R_ClearContacts(de::Map &map); +void R_ClearContactLists(de::Map &map); /** * Add a new contact for the specified mobj, for spreading purposes. @@ -102,30 +131,27 @@ void R_AddContact(struct mobj_s &mobj); void R_AddContact(Lumobj &lumobj); /** - * To be called to link all contacts into the contact blockmaps. - * - * @todo Why don't we link contacts immediately? -ds + * Traverse the list of contacts for the current render frame. */ -void R_LinkContacts(); +int R_ContactIterator(int (*callback) (Contact &, void *), void *context = 0); /** - * Perform all contact spreading for the specified @a BspLeaf. Note this should - * only be called once per BSP leaf per render frame! + * Returns the contact list for the specified BSP @a leaf and contact @a type. */ -void R_SpreadContacts(BspLeaf &bspLeaf); +ContactList &R_ContactList(BspLeaf &leaf, ContactType type); /** - * Traverse the list of mobj contacts linked directly to @a bspLeaf, for the - * current render frame. + * Traverse the list of mobj contacts linked directly to the specified BSP @a leaf, + * for the current render frame. */ -int R_IterateBspLeafMobjContacts(BspLeaf &bspLeaf, +int R_IterateBspLeafMobjContacts(BspLeaf &leaf, int (*callback) (struct mobj_s &, void *), void *context = 0); /** - * Traverse the list of lumobj contacts linked directly to @a bspLeaf, for the - * current render frame. + * Traverse the list of lumobj contacts linked directly to the specified BSP @a leaf, + * for the current render frame. */ -int R_IterateBspLeafLumobjContacts(BspLeaf &bspLeaf, +int R_IterateBspLeafLumobjContacts(BspLeaf &leaf, int (*callback) (Lumobj &, void *), void *context = 0); #endif // DENG_CLIENT_WORLD_CONTACTBLOCKMAP_H diff --git a/doomsday/client/include/world/map.h b/doomsday/client/include/world/map.h index df5d693ed7..776c414dbc 100644 --- a/doomsday/client/include/world/map.h +++ b/doomsday/client/include/world/map.h @@ -805,6 +805,11 @@ class Map */ void initLightGrid(); + /** + * Perform spreading of all contacts in the specified map space @a region. + */ + void spreadAllContacts(AABoxd const ®ion); + protected: /// Observes World FrameBegin void worldFrameBegins(World &world, bool resetNextViewer); @@ -845,6 +850,11 @@ class Map * Must be called before rendering a frame with bias lighting enabled. */ void initBias(); + + /** + * Initialize the map object => BSP leaf "contact" blockmaps. + */ + void initContactBlockmaps(); #endif // __CLIENT__ public: diff --git a/doomsday/client/src/dd_main.cpp b/doomsday/client/src/dd_main.cpp index ca13e0a432..97e488ffc3 100644 --- a/doomsday/client/src/dd_main.cpp +++ b/doomsday/client/src/dd_main.cpp @@ -1500,7 +1500,7 @@ bool App_ChangeGame(Game &game, bool allowReload) #ifdef __CLIENT__ R_ClearViewData(); - R_DestroyContactBlockmaps(); + R_DestroyContactLists(); P_ControlShutdown(); Con_Execute(CMDS_DDAY, "clearbindings", true, false); diff --git a/doomsday/client/src/render/rend_main.cpp b/doomsday/client/src/render/rend_main.cpp index 192935aa29..18be593f25 100644 --- a/doomsday/client/src/render/rend_main.cpp +++ b/doomsday/client/src/render/rend_main.cpp @@ -2499,7 +2499,10 @@ static void drawCurrentLeaf() leaf->markVisible(); markLeafFrontFacingWalls(); - R_SpreadContacts(*leaf); + + // Perform contact spreading for this map region. + leaf->map().spreadAllContacts(leaf->poly().aaBox()); + Rend_RadioBspLeafEdges(*leaf); /* diff --git a/doomsday/client/src/world/blockmap.cpp b/doomsday/client/src/world/blockmap.cpp index 5089430aa5..ce50ab65a4 100644 --- a/doomsday/client/src/world/blockmap.cpp +++ b/doomsday/client/src/world/blockmap.cpp @@ -273,10 +273,10 @@ struct BlockLinkWorkerParams bool didLink; }; -static int blockLinkWorker(void *cdPtr, void *parameters) +static int blockLinkWorker(void *cdPtr, void *context) { CellData *cellData = (CellData *) cdPtr; - BlockLinkWorkerParams *p = (BlockLinkWorkerParams *) parameters; + BlockLinkWorkerParams *p = (BlockLinkWorkerParams *) context; if(cellData->link(p->elem)) { p->didLink = true; @@ -310,10 +310,10 @@ struct BlockUnlinkWorkerParams bool didUnlink; }; -static int blockUnlinkWorker(void *cdPtr, void *contaxt) +static int blockUnlinkWorker(void *cdPtr, void *context) { CellData *cellData = (CellData *) cdPtr; - BlockUnlinkWorkerParams *p = (BlockUnlinkWorkerParams *) contaxt; + BlockUnlinkWorkerParams *p = (BlockUnlinkWorkerParams *) context; if(cellData->unlink(p->elem)) { p->didUnlink = true; diff --git a/doomsday/client/src/world/contactblockmap.cpp b/doomsday/client/src/world/contactblockmap.cpp index 7ffc401f3b..854b262320 100644 --- a/doomsday/client/src/world/contactblockmap.cpp +++ b/doomsday/client/src/world/contactblockmap.cpp @@ -1,4 +1,4 @@ -/** @file p_objlink.cpp World object => BSP leaf "contact" blockmap. +/** @file contactblockmap.cpp World object => BSP leaf "contact" blockmap. * * @authors Copyright © 2003-2013 Jaakko Keränen * @authors Copyright © 2006-2013 Daniel Swanson @@ -17,9 +17,7 @@ * http://www.gnu.org/licenses */ -//#include - -#include +//#include #include #include @@ -27,114 +25,76 @@ #include #include "Face" -//#include "gridmap.h" #include "world/map.h" -#include "world/blockmap.h" +//#include "world/blockmap.h" #include "world/p_object.h" #include "BspLeaf" -#include "Surface" +//#include "Surface" -#include "render/r_main.h" // validCount -#include "render/rend_main.h" // Rend_MapSurfaceMaterialSpec -#include "WallEdge" +//#include "render/r_main.h" // validCount +//#include "render/rend_main.h" // Rend_MapSurfaceMaterialSpec +//#include "WallEdge" #include "world/contactblockmap.h" using namespace de; -enum ContactType +ContactType Contact::type() const { - ContactMobj = 0, - ContactLumobj, - - ContactTypeCount -}; + return _type; +} -/// @todo Obviously, polymorphism is a better solution. -struct Contact +void *Contact::objectPtr() const { - //Contact *nextInBlock; ///< Next in the same blockmap cell (if any, not owned). - Contact *nextUsed; ///< Next in the used list (if any, not owned). - Contact *next; ///< Next in global list of contacts (if any, not owned). - - ContactType _type; ///< Logical identifier. - void *_object; ///< The contacted object. - - ContactType type() const - { - return _type; - } + return _object; +} - void *objectPtr() const +Vector3d Contact::objectOrigin() const +{ + switch(_type) { - return _object; - } + case ContactLumobj: return objectAs().origin(); + case ContactMobj: return Mobj_Origin(objectAs()); - template - ObjectType &objectAs() const { - DENG2_ASSERT(_object != 0); - return *static_cast(_object); + default: + DENG2_ASSERT(false); + return Vector3d(); } +} - /** - * Returns a copy of the linked object's origin in map space. - */ - Vector3d objectOrigin() const +double Contact::objectRadius() const +{ + switch(_type) { - switch(_type) - { - case ContactLumobj: return objectAs().origin(); - case ContactMobj: return Mobj_Origin(objectAs()); - - default: - DENG2_ASSERT(false); - return Vector3d(); - } - } + case ContactLumobj: return objectAs().radius(); + case ContactMobj: return Mobj_VisualRadius(&objectAs()); - /** - * Returns the linked object's radius in map space. - */ - ddouble objectRadius() const - { - switch(_type) - { - case ContactLumobj: return objectAs().radius(); - case ContactMobj: return Mobj_VisualRadius(&objectAs()); - - default: - DENG2_ASSERT(false); - return 0; - } + default: + DENG2_ASSERT(false); + return 0; } +} - /** - * Returns an axis-aligned bounding box for the linked object in map space. - */ - AABoxd objectAABox() const - { - Vector2d const origin = objectOrigin(); - ddouble const radius = objectRadius(); - return AABoxd(origin.x - radius, origin.y - radius, - origin.x + radius, origin.y + radius); - } +AABoxd Contact::objectAABox() const +{ + Vector2d const origin = objectOrigin(); + ddouble const radius = objectRadius(); + return AABoxd(origin.x - radius, origin.y - radius, + origin.x + radius, origin.y + radius); +} - /** - * Returns the BSP leaf at the linked object's origin in map space. - */ - BspLeaf &objectBspLeafAtOrigin() const +BspLeaf &Contact::objectBspLeafAtOrigin() const +{ + switch(_type) { - switch(_type) - { - case ContactLumobj: return objectAs().bspLeafAtOrigin(); - case ContactMobj: return Mobj_BspLeafAtOrigin(objectAs()); - - default: - throw Error("Contact::objectBspLeafAtOrigin", "Invalid type"); - } + case ContactLumobj: return objectAs().bspLeafAtOrigin(); + case ContactMobj: return Mobj_BspLeafAtOrigin(objectAs()); + + default: + throw Error("Contact::objectBspLeafAtOrigin", "Invalid type"); } -}; +} struct ListNode { @@ -145,450 +105,59 @@ struct ListNode static ListNode *firstNode; ///< First unused list node. static ListNode *cursor; ///< Current list node. -struct ContactList +void ContactList::reset() // static { - // 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; - } + cursor = firstNode; +} - ListNode *_head; -}; +void ContactList::link(Contact *contact) +{ + if(!contact) return; -// Separate contact lists for each BSP leaf and contact type. -static ContactList *bspLeafContactLists; + ListNode *node = newNode(contact->objectPtr()); -static inline ContactList &contactList(BspLeaf &bspLeaf, ContactType type) -{ - return bspLeafContactLists[bspLeaf.indexInMap() * ContactTypeCount + int( type )]; + node->next = _head; + _head = node; } -/** - * 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) +ListNode *ContactList::begin() const { - 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); + return _head; } -DENG2_PIMPL(ContactBlockmap) +ListNode *ContactList::newNode(void *object) // static { - /*struct CellData - { - Contact *head; - - void unlinkAll() - { - head = 0; - } - };*/ - - //Gridmap gridmap; - Blockmap blockmap; - //AABoxd bounds; - //uint blockSize; ///< In map space units. - - // For perf, spread state data is "global". - struct SpreadState - { - Contact *contact; - AABoxd contactAABox; - - SpreadState() : contact(0) {} - }; - SpreadState spread; - QBitArray spreadBlocks; ///< Used to prevent repeat processing. - - Instance(Public *i, AABoxd const &bounds, uint blockSize) - : Base(i), - blockmap(bounds, Vector2ui(blockSize, blockSize)), - //bounds(bounds), - //blockSize(blockSize), - spreadBlocks(blockmap.width() * blockmap.height()) - {} - - inline int toCellIndex(uint cellX, uint cellY) - { - return int(cellY * blockmap.width() + cellX); - } - -#if 0 - /** - * Given map space X coordinate @a x, return the corresponding cell coordinate. - * If @a x is outside the blockmap it will be clamped to the nearest edge on - * the X axis. - * - * @param x Map space X coordinate to be translated. - * @param didClip Set to @c true iff clamping was necessary. - * - * @return Translated blockmap cell X coordinate. - */ - uint toCellX(ddouble x, bool &didClip) const - { - AABoxd const &bounds = blockmap.bounds(); - didClip = false; - if(x < bounds.minX) - { - x = bounds.minX; - didClip = true; - } - else if(x >= bounds.maxX) - { - x = bounds.maxX - 1; - didClip = true; - } - return uint((x - bounds.minX) / blockmap.cellWidth()); - } - - /** - * Given map space Y coordinate @a y, return the corresponding cell coordinate. - * If @a y is outside the blockmap it will be clamped to the nearest edge on - * the Y axis. - * - * @param y Map space Y coordinate to be translated. - * @param didClip Set to @c true iff clamping was necessary. - * - * @return Translated blockmap cell Y coordinate. - */ - uint toCellY(ddouble y, bool &didClip) const - { - AABoxd const &bounds = blockmap.bounds(); - didClip = false; - if(y < bounds.minY) - { - y = bounds.minY; - didClip = true; - } - else if(y >= bounds.maxY) - { - y = bounds.maxY - 1; - didClip = true; - } - return uint((y - bounds.minY) / blockmap.cellHeight()); - } - - /** - * Determines in which blockmap cell the specified map point lies. If the - * these coordinates are outside the blockmap they will be clamped within - * the valid range. - * - * @param retAdjusted If specified, whether or not the @a point coordinates - * had to be adjusted is written back here. - * - * @return The determined blockmap cell. - */ - GridmapCell toCell(Vector2d const &point, bool *retAdjusted = 0) const - { - bool didClipX, didClipY; - GridmapCell cell(toCellX(point.x, didClipX), toCellY(point.y, didClipY)); - if(retAdjusted) *retAdjusted = didClipX | didClipY; - return cell; - } - - inline GridmapCellBlock toCellBlock(AABoxd const &box) - { - return GridmapCellBlock(toCell(box.min), toCell(box.max)); - } - - /** - * Returns the data for the specified cell. - */ - inline CellData *cellData(GridmapCell const &cell, bool canAlloc = false) - { - return static_cast(gridmap.cellData(cell, canAlloc)); - } -#endif - - 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. - } - - // Is the leaf on the back side outside the origin's AABB? - AABoxd const &aaBox = backLeaf.poly().aaBox(); - if(aaBox.maxX <= spread.contactAABox.minX || aaBox.minX >= spread.contactAABox.maxX || - aaBox.maxY <= spread.contactAABox.minY || aaBox.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.) - */ - void spreadInBspLeaf(BspLeaf &bspLeaf) - { - if(bspLeaf.hasCluster()) - { - HEdge *base = bspLeaf.poly().hedge(); - HEdge *hedge = base; - do - { - maybeSpreadOverEdge(hedge); - - } while((hedge = &hedge->next()) != base); - } - } + DENG2_ASSERT(object != 0); - /** - * 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) + ListNode *node; + if(!cursor) { - BspLeaf &bspLeaf = contact.objectBspLeafAtOrigin(); - DENG2_ASSERT(bspLeaf.hasCluster()); // Sanity check. - - contactList(bspLeaf, contact.type()).link(&contact); - - // Spread to neighboring BSP leafs. - bspLeaf.setValidCount(++validCount); + node = (ListNode *) Z_Malloc(sizeof(*node), PU_APPSTATIC, 0); - spread.contact = &contact; - spread.contactAABox = contact.objectAABox(); - - spreadInBspLeaf(bspLeaf); + // Link in the global list of used nodes. + node->nextUsed = firstNode; + firstNode = node; } - - static int spreadContactWorker(void *contact, void *context) + else { - Instance *inst = static_cast(context); - inst->spreadContact(*static_cast(contact)); - return false; // Continue iteration. + node = cursor; + cursor = cursor->nextUsed; } - /*static int unlinkAllWorker(void *obj, void *context) - { - DENG2_UNUSED(context); - static_cast(obj)->unlinkAll(); - return false; // Continue iteration. - }*/ -}; - -ContactBlockmap::ContactBlockmap(AABoxd const &bounds, uint blockSize) - : d(new Instance(this, bounds, blockSize)) -{} - -Vector2d ContactBlockmap::origin() const -{ - return d->blockmap.origin(); -} - -AABoxd const &ContactBlockmap::bounds() const -{ - return d->blockmap.bounds(); -} - -void ContactBlockmap::link(Contact *contact) -{ - if(!contact) return; - - bool outside; - BlockmapCell cell = d->blockmap.toCell(contact->objectOrigin(), &outside); - if(outside) return; + node->obj = object; + node->next = 0; - d->blockmap.link(cell, contact); - - //Instance::CellData *data = d->cellData(cell, true/*can allocate a block*/); - //contact->nextInBlock = data->head; - //data->head = contact; + return node; } -void ContactBlockmap::unlinkAll() -{ - d->spreadBlocks.fill(false); - d->blockmap.unlinkAll(); - //d->gridmap.iterate(Instance::unlinkAllWorker); -} +// Separate contact lists for each BSP leaf and contact type. +static ContactList *bspLeafContactLists; -void ContactBlockmap::spreadAllContacts(AABoxd const &box) +ContactList &R_ContactList(BspLeaf &bspLeaf, ContactType type) { - BlockmapCellBlock const cellBlock = d->blockmap.toCellBlock(box); - - BlockmapCell 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) - { - int cellIndex = d->toCellIndex(cell.x, cell.y); - if(!d->spreadBlocks.testBit(cellIndex)) - { - d->spreadBlocks.setBit(cellIndex); - d->blockmap.iterate(cell, Instance::spreadContactWorker, d); - /*if(Instance::CellData *data = d->cellData(cell)) - { - for(Contact *iter = data->head; iter; iter = iter->nextInBlock) - { - d->spreadContact(*iter); - } - }*/ - } - } + return bspLeafContactLists[bspLeaf.indexInMap() * ContactTypeCount + int( type )]; } -// Each contactable object type uses a separate blockmap. -static ContactBlockmap *blockmaps[ContactTypeCount]; - static Contact *contacts; static Contact *contactFirst, *contactCursor; @@ -611,8 +180,6 @@ static Contact *newContact(void *object, ContactType type) contactCursor = contactCursor->nextUsed; } - //contact->nextInBlock = 0; - // Link in the list of in-use contacts. contact->next = contacts; contacts = contact; @@ -623,27 +190,16 @@ static Contact *newContact(void *object, ContactType type) return contact; } -void R_InitContactBlockmaps(Map &map) +void R_InitContactLists(Map &map) { - for(int i = 0; i < ContactTypeCount; ++i) - { - DENG2_ASSERT(blockmaps[i] == 0); - blockmaps[i] = new ContactBlockmap(map.bounds()); - } - // Initialize object => BspLeaf contact lists. bspLeafContactLists = (ContactList *) Z_Calloc(map.bspLeafCount() * ContactTypeCount * sizeof(*bspLeafContactLists), PU_MAPSTATIC, 0); } -void R_DestroyContactBlockmaps() +void R_DestroyContactLists() { - for(int i = 0; i < ContactTypeCount; ++i) - { - delete blockmaps[i]; blockmaps[i] = 0; - } - if(bspLeafContactLists) { Z_Free(bspLeafContactLists); @@ -651,18 +207,8 @@ void R_DestroyContactBlockmaps() } } -static inline ContactBlockmap &blockmap(ContactType type) -{ - return *blockmaps[int( type )]; -} - -void R_ClearContacts(Map &map) +void R_ClearContactLists(Map &map) { - for(int i = 0; i < ContactTypeCount; ++i) - { - blockmap(ContactType(i)).unlinkAll(); - } - // Start reusing contacts. contactCursor = contactFirst; contacts = 0; @@ -677,39 +223,6 @@ void R_ClearContacts(Map &map) } } -static inline float radiusMax(ContactType type) -{ - switch(type) - { - case ContactLumobj: return Lumobj::radiusMax(); - case ContactMobj: return DDMOBJ_RADIUS_MAX; - - default: - DENG2_ASSERT(false); - return 8; - } -} - -void R_SpreadContacts(BspLeaf &bspLeaf) -{ - if(!bspLeaf.hasCluster()) - return; - - for(int i = 0; i < ContactTypeCount; ++i) - { - float const maxRadius = radiusMax(ContactType(i)); - ContactBlockmap &bmap = blockmap(ContactType(i)); - - AABoxd bounds = bspLeaf.poly().aaBox(); - bounds.minX -= maxRadius; - bounds.minY -= maxRadius; - bounds.maxX += maxRadius; - bounds.maxY += maxRadius; - - bmap.spreadAllContacts(bounds); - } -} - void R_AddContact(mobj_t &mobj) { // BspLeafs with no geometry cannot be contacted (zero world volume). @@ -728,19 +241,21 @@ void R_AddContact(Lumobj &lum) } } -void R_LinkContacts() +int R_ContactIterator(int (*callback) (Contact &, void *), void *context) { // Link contacts into the relevant blockmap. for(Contact *contact = contacts; contact; contact = contact->next) { - blockmap(contact->type()).link(contact); + if(int result = callback(*contact, context)) + return result; } + return false; // Continue iteration. } int R_IterateBspLeafMobjContacts(BspLeaf &bspLeaf, int (*callback)(mobj_s &, void *), void *context) { - ContactList &list = contactList(bspLeaf, ContactMobj); + ContactList &list = R_ContactList(bspLeaf, ContactMobj); for(ListNode *node = list.begin(); node; node = node->next) { if(int result = callback(*static_cast(node->obj), context)) @@ -752,7 +267,7 @@ int R_IterateBspLeafMobjContacts(BspLeaf &bspLeaf, int R_IterateBspLeafLumobjContacts(BspLeaf &bspLeaf, int (*callback)(Lumobj &, void *), void *context) { - ContactList &list = contactList(bspLeaf, ContactLumobj); + ContactList &list = R_ContactList(bspLeaf, ContactLumobj); for(ListNode *node = list.begin(); node; node = node->next) { if(int result = callback(*static_cast(node->obj), context)) diff --git a/doomsday/client/src/world/map.cpp b/doomsday/client/src/world/map.cpp index 0a4f15a19c..aacddd6e53 100644 --- a/doomsday/client/src/world/map.cpp +++ b/doomsday/client/src/world/map.cpp @@ -21,6 +21,8 @@ * 02110-1301 USA */ +#include + #include #include @@ -89,6 +91,284 @@ static int bspSplitFactor = 7; // cvar +#ifdef __CLIENT__ + +/** + * 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(de::HEdge const &hedge, de::Vector2d const &point) +{ + de::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); +} + +struct ContactBlockmap +{ + /** + * 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) + : _blockmap(bounds, de::Vector2ui(blockSize, blockSize)), + _spreadBlocks(_blockmap.width() * _blockmap.height()) + {} + + /** + * Returns the origin of the blockmap in the map coordinate space. + */ + de::Vector2d origin() const + { + return _blockmap.origin(); + } + + /** + * Returns the bounds of the blockmap in the map coordinate space. + */ + AABoxd const &bounds() const + { + return _blockmap.bounds(); + } + + /** + * @param contact Contact to be linked. Note that if the object's origin + * lies outside the blockmap it will not be linked! + */ + void link(Contact *contact) + { + if(!contact) return; + + bool outside; + de::BlockmapCell cell = _blockmap.toCell(contact->objectOrigin(), &outside); + if(outside) return; + + _blockmap.link(cell, contact); + } + + /** + * Clear all linked contacts. + */ + void unlinkAll() + { + _spreadBlocks.fill(false); + _blockmap.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) + { + de::BlockmapCellBlock const cellBlock = _blockmap.toCellBlock(box); + + de::BlockmapCell 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) + { + int cellIndex = toCellIndex(cell.x, cell.y); + if(!_spreadBlocks.testBit(cellIndex)) + { + _spreadBlocks.setBit(cellIndex); + _blockmap.iterate(cell, spreadContactWorker, this); + } + } + } + + /** + * 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) + { + BspLeaf &bspLeaf = contact.objectBspLeafAtOrigin(); + DENG2_ASSERT(bspLeaf.hasCluster()); // Sanity check. + + R_ContactList(bspLeaf, contact.type()).link(&contact); + + // Spread to neighboring BSP leafs. + bspLeaf.setValidCount(++validCount); + + _spread.contact = &contact; + _spread.contactAABox = contact.objectAABox(); + + spreadInBspLeaf(bspLeaf); + } + +private: + de::Blockmap _blockmap; + struct SpreadState + { + Contact *contact; + AABoxd contactAABox; + + SpreadState() : contact(0) {} + }; + SpreadState _spread; + QBitArray _spreadBlocks; ///< Used to prevent repeat processing. + + inline int toCellIndex(uint cellX, uint cellY) + { + return int(cellY * _blockmap.width() + cellX); + } + + void maybeSpreadOverEdge(de::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. + } + + // Is the leaf on the back side outside the origin's AABB? + AABoxd const &aaBox = backLeaf.poly().aaBox(); + if(aaBox.maxX <= _spread.contactAABox.minX || aaBox.minX >= _spread.contactAABox.maxX || + aaBox.maxY <= _spread.contactAABox.minY || aaBox.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. + de::MaterialSnapshot const &ms = facingLineSide.middle().material().prepare(Rend_MapSurfaceMaterialSpec()); + if(ms.height() >= openTop - openBottom) + { + // Possibly; check the placement. + de::WallEdge edge(de::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); + + R_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.) + */ + void spreadInBspLeaf(BspLeaf &bspLeaf) + { + if(bspLeaf.hasCluster()) + { + de::HEdge *base = bspLeaf.poly().hedge(); + de::HEdge *hedge = base; + do + { + maybeSpreadOverEdge(hedge); + + } while((hedge = &hedge->next()) != base); + } + } + + static int spreadContactWorker(void *contact, void *context) + { + ContactBlockmap *inst = static_cast(context); + inst->spreadContact(*static_cast(contact)); + return false; // Continue iteration. + } +}; + +#endif // __CLIENT__ + namespace de { struct EditableElements @@ -140,6 +420,10 @@ DENG2_OBSERVES(bsp::Partitioner, UnclosedSectorFound) QScopedPointer polyobjBlockmap; QScopedPointer lineBlockmap; QScopedPointer bspLeafBlockmap; +#ifdef __CLIENT__ + QScopedPointer mobjContactBlockmap; /// @todo Redundant? + QScopedPointer lumobjContactBlockmap; +#endif nodepile_t mobjNodes; nodepile_t lineNodes; @@ -936,6 +1220,34 @@ DENG2_OBSERVES(bsp::Partitioner, UnclosedSectorFound) } } +#ifdef __CLIENT__ + static int linkContactWorker(Contact &contact, void *context) + { + Instance *inst = static_cast(context); + switch(contact.type()) + { + case ContactMobj: inst->mobjContactBlockmap->link(&contact); break; + case ContactLumobj: inst->lumobjContactBlockmap->link(&contact); break; + + default: + DENG2_ASSERT(false); + break; + } + + return false; // Continue iteration. + } + + /** + * To be called to link all contacts into the contact blockmaps. + * + * @todo Why don't we link contacts immediately? -ds + */ + void linkContacts() + { + R_ContactIterator(linkContactWorker, this); + } +#endif + /** * Locate a polyobj by sound emitter. * @@ -1325,6 +1637,24 @@ void Map::buildMaterialLists() } } +void Map::initContactBlockmaps() +{ + d->mobjContactBlockmap.reset(new ContactBlockmap(bounds())); + d->lumobjContactBlockmap.reset(new ContactBlockmap(bounds())); +} + +void Map::spreadAllContacts(AABoxd const ®ion) +{ + // Expand the region according by the maxium radius of each contact type. + d->mobjContactBlockmap-> + spreadAllContacts(AABoxd(region.minX - DDMOBJ_RADIUS_MAX, region.minY - DDMOBJ_RADIUS_MAX, + region.maxX + DDMOBJ_RADIUS_MAX, region.maxY + DDMOBJ_RADIUS_MAX)); + + d->lumobjContactBlockmap-> + spreadAllContacts(AABoxd(region.minX - Lumobj::radiusMax(), region.minY - Lumobj::radiusMax(), + region.maxX + Lumobj::radiusMax(), region.maxY + Lumobj::radiusMax())); +} + #endif // __CLIENT__ Uri const &Map::uri() const @@ -2903,7 +3233,10 @@ void Map::worldFrameBegins(World &world, bool resetNextViewer) removeAllLumobjs(); // Clear the "contact" blockmaps (BSP leaf => object). - R_ClearContacts(*this); + d->mobjContactBlockmap->unlinkAll(); + d->lumobjContactBlockmap->unlinkAll(); + + R_ClearContactLists(*this); // Generate surface decorations for the frame. if(useLightDecorations) @@ -2944,8 +3277,7 @@ void Map::worldFrameBegins(World &world, bool resetNextViewer) // Link all active particle generators into the world. P_CreatePtcGenLinks(); - // Link objs to all contacted surfaces. - R_LinkContacts(); + d->linkContacts(); } } diff --git a/doomsday/client/src/world/world.cpp b/doomsday/client/src/world/world.cpp index f26b2af8d9..02e26adafa 100644 --- a/doomsday/client/src/world/world.cpp +++ b/doomsday/client/src/world/world.cpp @@ -641,7 +641,8 @@ DENG2_PIMPL(World) Rend_RadioInitForMap(*map); - R_InitContactBlockmaps(*map); + map->initContactBlockmaps(); + R_InitContactLists(*map); Rend_ProjectorInitForMap(*map); VL_InitForMap(*map); // Converted vlights (from lumobjs). map->initBias(); // Shadow bias sources and surfaces. @@ -750,7 +751,7 @@ bool World::changeMap(de::Uri const &uri) /// for allocating memory used elsewhere so it should be repurposed for /// this usage specifically. #ifdef __CLIENT__ - R_DestroyContactBlockmaps(); + R_DestroyContactLists(); #endif delete d->map; d->map = 0; Z_FreeTags(PU_MAP, PU_PURGELEVEL - 1);