void HPlane::distance(LineSegmentSide const &lineSeg, coord_t *fromDist, coord_t *toDist) const { // Any work to do? if(!fromDist && !toDist) return; /// @attention Ensure line segments produced from the partition's source /// line are always treated as collinear. This special case is only /// necessary due to precision inaccuracies when a line is split into /// multiple segments. if(d->lineSegment && &d->lineSegment->mapSide().line() == lineSeg.partitionMapLine()) { if(fromDist) *fromDist = 0; if(toDist) *toDist = 0; return; } coord_t toSegDirectionV1[2] = { d->partition.direction.x, d->partition.direction.y } ; if(fromDist) { coord_t fromV1[2] = { lineSeg.from().origin().x, lineSeg.from().origin().y }; *fromDist = V2d_PointLinePerpDistance(fromV1, toSegDirectionV1, d->perp, d->length); } if(toDist) { coord_t toV1[2] = { lineSeg.to().origin().x, lineSeg.to().origin().y }; *toDist = V2d_PointLinePerpDistance(toV1, toSegDirectionV1, d->perp, d->length); } }
void HPlane::configure(LineSegmentSide const &newBaseSeg) { // Only map line segments are suitable. DENG2_ASSERT(newBaseSeg.hasMapSide()); LOG_AS("HPlane::configure"); // Clear the list of intersection points. clearIntercepts(); // Reconfigure the partition line. LineSide &mapSide = newBaseSeg.mapSide(); d->partition.direction = mapSide.to().origin() - mapSide.from().origin(); d->partition.origin = mapSide.from().origin(); d->lineSegment = const_cast<LineSegmentSide *>(&newBaseSeg); d->length = d->partition.direction.length(); d->angle = M_DirectionToAngleXY(d->partition.direction.x, d->partition.direction.y); d->slopeType = M_SlopeTypeXY(d->partition.direction.x, d->partition.direction.y); d->perp = d->partition.origin.y * d->partition.direction.x - d->partition.origin.x * d->partition.direction.y; d->para = -d->partition.origin.x * d->partition.direction.x - d->partition.origin.y * d->partition.direction.y; //LOG_DEBUG("line segment %p %s") // << &newBaseSeg << d->partition.asText(); }
/** * Find the intersection point between a line segment and the current * partition plane. Takes advantage of some common situations like * horizontal and vertical lines to choose a 'nicer' intersection point. */ Vector2d intersectPartition(LineSegmentSide const &seg, coord_t fromDist, coord_t toDist) const { // Horizontal partition vs vertical line segment. if(hplane.slopeType() == ST_HORIZONTAL && seg.slopeType() == ST_VERTICAL) { return Vector2d(seg.from().origin().x, hplane.partition().origin.y); } // Vertical partition vs horizontal line segment. if(hplane.slopeType() == ST_VERTICAL && seg.slopeType() == ST_HORIZONTAL) { return Vector2d(hplane.partition().origin.x, seg.from().origin().y); } // 0 = start, 1 = end. coord_t ds = fromDist / (fromDist - toDist); Vector2d point = seg.from().origin(); if(seg.slopeType() != ST_VERTICAL) point.x += seg.direction().x * ds; if(seg.slopeType() != ST_HORIZONTAL) point.y += seg.direction().y * ds; return point; }
double HPlane::intersect(LineSegmentSide const &lineSeg, int edge) { Vertex &vertex = lineSeg.vertex(edge); coord_t pointV1[2] = { vertex.origin().x, vertex.origin().y }; coord_t directionV1[2] = { d->partition.direction.x, d->partition.direction.y }; return V2d_PointLineParaDistance(pointV1, directionV1, d->para, d->length); }
void buildSubspaceGeometries() { for(ConvexSubspaceProxy const &subspace : subspaces) { /// @todo Move BSP leaf construction here? BspLeaf &bspLeaf = *subspace.bspLeaf(); subspace.buildGeometry(bspLeaf, *mesh); // Account the new segments. /// @todo Refactor away. for(OrderedSegment const &oseg : subspace.segments()) { if(oseg.segment->hasHEdge()) { // There is now one more line segment. segmentCount += 1; } } } /* * Finalize the built geometry by adding a twin half-edge for any * which don't yet have one. */ for(ConvexSubspaceProxy const &convexSet : subspaces) for(OrderedSegment const &oseg : convexSet.segments()) { LineSegmentSide *seg = oseg.segment; if(seg->hasHEdge() && !seg->back().hasHEdge()) { HEdge *hedge = &seg->hedge(); DENG2_ASSERT(!hedge->hasTwin()); // Allocate the twin from the same mesh. hedge->setTwin(hedge->mesh().newHEdge(seg->back().from())); hedge->twin().setTwin(hedge); } } }
/** * Analyze the half-plane intercepts, building new line segments to cap * any gaps (new segments are added onto the end of the appropriate list * (rights to @a rightList and lefts to @a leftList)). * * @param rights Set of line segments on the right of the partition. * @param lefts Set of line segments on the left of the partition. */ void addPartitionLineSegments(LineSegmentBlockTreeNode &rights, LineSegmentBlockTreeNode &lefts) { LOG_TRACE("Building line segments along partition %s") << hplane.partition().asText(); // First, fix any near-distance issues with the intercepts. hplane.sortAndMergeIntercepts(); //hplane.printIntercepts(); // We must not create new line segments on top of the source partition // line segment (as this will result in duplicate edges finding their // way into the BSP leaf geometries). LineSegmentSide *partSeg = hplane.lineSegment(); double nearDist = 0, farDist = 0; if(partSeg) { nearDist = hplane.intersect(*partSeg, LineSegment::From); farDist = hplane.intersect(*partSeg, LineSegment::To); } // Create new line segments. for(int i = 0; i < hplane.interceptCount() - 1; ++i) { HPlaneIntercept const &cur = hplane.intercepts()[i]; HPlaneIntercept const &next = hplane.intercepts()[i+1]; // Does this range overlap the partition line segment? if(partSeg && cur.distance() >= nearDist && next.distance() <= farDist) continue; // Void space or an existing segment between the two intercepts? if(!cur.after() && !next.before()) continue; // Check for some nasty open/closed or close/open cases. if(cur.after() && !next.before()) { if(!cur.lineSegmentIsSelfReferencing()) { Vector2d nearPoint = (cur.vertex().origin() + next.vertex().origin()) / 2; notifyUnclosedSectorFound(*cur.after(), nearPoint); } continue; } if(!cur.after() && next.before()) { if(!next.lineSegmentIsSelfReferencing()) { Vector2d nearPoint = (cur.vertex().origin() + next.vertex().origin()) / 2; notifyUnclosedSectorFound(*next.before(), nearPoint); } continue; } /* * This is definitely open space. */ Vertex &fromVertex = cur.vertex(); Vertex &toVertex = next.vertex(); Sector *sector = cur.after(); if(!cur.before() && next.before() == next.after()) { sector = next.before(); } // Choose the non-self-referencing sector when we can. else if(cur.after() != next.before()) { if(!cur.lineSegmentIsSelfReferencing() && !next.lineSegmentIsSelfReferencing()) { LOG_DEBUG("Sector mismatch #%d %s != #%d %s") << cur.after()->indexInMap() << cur.vertex().origin().asText() << next.before()->indexInMap() << next.vertex().origin().asText(); } LineSegmentSide *afterSeg = cur.afterLineSegment(); if(afterSeg->hasMapLine() && afterSeg->mapLine().isSelfReferencing()) { LineSegmentSide *beforeSeg = next.beforeLineSegment(); if(beforeSeg->hasMapLine() && !beforeSeg->mapLine().isSelfReferencing()) { sector = next.before(); } } } DENG2_ASSERT(sector); LineSegment &newSeg = *makeLineSegment(fromVertex, toVertex, sector, sector, nullptr /*no map line*/, partSeg? &partSeg->mapLine() : nullptr); edgeTipSet(newSeg.from()) << EdgeTip(newSeg.front()); edgeTipSet(newSeg.to()) << EdgeTip(newSeg.back()); // Add each new line segment to the appropriate set. linkLineSegmentInBlockTree(rights, newSeg.front()); linkLineSegmentInBlockTree(lefts, newSeg.back()); /* LOG_DEBUG("Built line segment from %s to %s (sector #%i)") << fromVertex.origin().asText() << toVertex.origin().asText() << sector->indexInArchive(); */ } }
/** * Remove all the line segments from the list, partitioning them into the * left or right sets according to their position relative to partition line. * Adds any intersections onto the intersection list as it goes. * * @param node Block tree node containing the line segments to be partitioned. * @param rights Set of line segments on the right side of the partition. * @param lefts Set of line segments on the left side of the partition. */ void divideSegments(LineSegmentBlockTreeNode &node, LineSegmentBlockTreeNode &rights, LineSegmentBlockTreeNode &lefts) { /** * @todo Revise this algorithm so that @var segments is not modified * during the partitioning process. */ int const totalSegs = node.userData()->totalCount(); DENG2_ASSERT(totalSegs != 0); DENG2_UNUSED(totalSegs); // Iterative pre-order traversal of SuperBlock. LineSegmentBlockTreeNode *cur = &node; LineSegmentBlockTreeNode *prev = nullptr; while(cur) { while(cur) { LineSegmentBlock &segs = *cur->userData(); LineSegmentSide *seg; while((seg = segs.pop())) { // Disassociate the line segment from the block tree. seg->setBlockTreeNode(nullptr); divideOneSegment(*seg, rights, lefts); } if(prev == cur->parentPtr()) { // Descending - right first, then left. prev = cur; if(cur->hasRight()) cur = cur->rightPtr(); else cur = cur->leftPtr(); } else if(prev == cur->rightPtr()) { // Last moved up the right branch - descend the left. prev = cur; cur = cur->leftPtr(); } else if(prev == cur->leftPtr()) { // Last moved up the left branch - continue upward. prev = cur; cur = cur->parentPtr(); } } if(prev) { // No left child - back up. cur = prev->parentPtr(); } } // Sanity checks... DENG2_ASSERT(rights.userData()->totalCount()); DENG2_ASSERT(lefts.userData ()->totalCount()); DENG2_ASSERT(( rights.userData()->totalCount() + lefts.userData ()->totalCount()) >= totalSegs); }
/** * Take the given line segment @a lineSeg, compare it with the partition * plane and determine into which of the two sets it should be. If the * line segment is found to intersect the partition, the intercept point * is determined and the line segment then split in two at this point. * Each piece of the line segment is then added to the relevant set. * * If the line segment is collinear with, or intersects the partition then * a new intercept is added to the partitioning half-plane. * * @note Any existing @em twin of @a lineSeg is so too handled uniformly. * * @param seg Line segment to be "partitioned". * @param rights Set of line segments on the right side of the partition. * @param lefts Set of line segments on the left side of the partition. */ void divideOneSegment(LineSegmentSide &seg, LineSegmentBlockTreeNode &rights, LineSegmentBlockTreeNode &lefts) { coord_t fromDist, toDist; LineRelationship rel = hplane.relationship(seg, &fromDist, &toDist); switch(rel) { case Collinear: { interceptPartition(seg, LineSegment::From); interceptPartition(seg, LineSegment::To); // Direction (vs that of the partition plane) determines in which // subset this line segment belongs. if(seg.direction().dot(hplane.partition().direction) < 0) { linkLineSegmentInBlockTree(lefts, seg); } else { linkLineSegmentInBlockTree(rights, seg); } break; } case Right: case RightIntercept: if(rel == RightIntercept) { // Direction determines which edge of the line segment interfaces // with the new half-plane intercept. interceptPartition(seg, (fromDist < DIST_EPSILON? LineSegment::From : LineSegment::To)); } linkLineSegmentInBlockTree(rights, seg); break; case Left: case LeftIntercept: if(rel == LeftIntercept) { interceptPartition(seg, (fromDist > -DIST_EPSILON? LineSegment::From : LineSegment::To)); } linkLineSegmentInBlockTree(lefts, seg); break; case Intersects: { // Calculate the intersection point and split this line segment. Vector2d point = intersectPartition(seg, fromDist, toDist); LineSegmentSide &newFrontRight = splitLineSegment(seg, point); // Ensure the new back left segment is inserted into the same block as // the old back right segment. if(LineSegmentBlockTreeNode *backLeftBlock = (LineSegmentBlockTreeNode *)seg.back().blockTreeNodePtr()) { linkLineSegmentInBlockTree(*backLeftBlock, newFrontRight.back()); } interceptPartition(seg, LineSegment::To); // Direction determines which subset the line segments are added to. if(fromDist < 0) { linkLineSegmentInBlockTree(rights, newFrontRight); linkLineSegmentInBlockTree(lefts, seg); } else { linkLineSegmentInBlockTree(rights, seg); linkLineSegmentInBlockTree(lefts, newFrontRight); } break; } } }
/// @todo refactor away inline void interceptPartition(LineSegmentSide &seg, int edge) { hplane.intercept(seg, edge, edgeTipSet(seg.vertex(edge))); }
/** * Link @a seg into the line segment block tree. * * Performs k-d tree subdivision of the 2D coordinate space, splitting the node * tree as necessary, however new nodes are created only when they need to be * populated (i.e., a split does not generate two nodes at the same time). */ void linkLineSegmentInBlockTree(LineSegmentBlockTreeNode &node_, LineSegmentSide &seg) { // Traverse the node tree beginning at "this" node. for(LineSegmentBlockTreeNode *node = &node_; ;) { LineSegmentBlock &block = *node->userData(); AABox const &bounds = block.bounds(); // The segment "touches" this node; increment the ref counters. block.addRef(seg); // Determine whether further subdivision is necessary/possible. Vector2i dimensions(Vector2i(bounds.max) - Vector2i(bounds.min)); if(dimensions.x <= 256 && dimensions.y <= 256) { // Thats as small as we go; link it in and return. block.link(seg); seg.setBlockTreeNode(node); return; } // Determine whether the node should be split and on which axis. int const splitAxis = (dimensions.x < dimensions.y); // x=0, y=1 int const midOnAxis = (bounds.min[splitAxis] + bounds.max[splitAxis]) / 2; LineSegmentBlockTreeNode::ChildId fromSide = LineSegmentBlockTreeNode::ChildId(seg.from().origin()[splitAxis] >= midOnAxis); LineSegmentBlockTreeNode::ChildId toSide = LineSegmentBlockTreeNode::ChildId(seg.to ().origin()[splitAxis] >= midOnAxis); // Does the segment lie entirely within one half of this node? if(fromSide != toSide) { // No, the segment crosses @var midOnAxis; link it in and return. block.link(seg); seg.setBlockTreeNode(node); return; } // Do we need to create the child node? if(!node->hasChild(fromSide)) { bool const toLeft = (fromSide == LineSegmentBlockTreeNode::Left); AABox childBounds; if(splitAxis) { // Vertical split. int division = bounds.minY + 0.5 + (bounds.maxY - bounds.minY) / 2; childBounds.minX = bounds.minX; childBounds.minY = (toLeft? division : bounds.minY); childBounds.maxX = bounds.maxX; childBounds.maxY = (toLeft? bounds.maxY : division); } else { // Horizontal split. int division = bounds.minX + 0.5 + (bounds.maxX - bounds.minX) / 2; childBounds.minX = (toLeft? division : bounds.minX); childBounds.minY = bounds.minY; childBounds.maxX = (toLeft? bounds.maxX : division); childBounds.maxY = bounds.maxY; } // Add a new child node and link it to its parent. LineSegmentBlock *childBlock = new LineSegmentBlock(childBounds); node->setChild(fromSide, new LineSegmentBlockTreeNode(childBlock, node)); } // Descend to the child node. node = node->childPtr(fromSide); } }