/* * vim: ts=4 sw=4 et tw=0 wm=0 * * libavoid - Fast, Incremental, Object-avoiding Line Router * Copyright (C) 2004-2006 Michael Wybrow * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include #include "libavoid/shape.h" #include "libavoid/region.h" namespace Avoid { Region *centerRegion = NULL; static const BBox screenBBox = {Point(-INFINITY, -INFINITY), Point(INFINITY, INFINITY)}; Region::Region() : _bbox(screenBBox) , _left(NULL), _right(NULL), _up(NULL), _down(NULL) { _blocks.clear(); } Region::Region(double x1, double y1, double x2, double y2) : _left(NULL), _right(NULL), _up(NULL), _down(NULL) { _bbox.a.x = x1; _bbox.a.y = y1; _bbox.b.x = x2; _bbox.b.y = y2; _blocks.clear(); } static const unsigned int R_INSIDE = 0; static const unsigned int R_LEFT = 1; static const unsigned int R_LEDGE = 2; static const unsigned int R_RIGHT = 4; static const unsigned int R_REDGE = 8; static const unsigned int R_ABOVE = 16; static const unsigned int R_TEDGE = 32; static const unsigned int R_BELOW = 64; static const unsigned int R_BEDGE = 128; static const unsigned int R_NONE = R_INSIDE; static const unsigned int R_UP = R_ABOVE; static const unsigned int R_DOWN = R_BELOW; static const unsigned int R_HORI = R_LEFT | R_RIGHT; static const unsigned int R_VERT = R_UP | R_DOWN; static void printBBox(const char *label, const BBox &bbox) { if (label) { printf("%s: ", label); } printf("(%.2f, %.2f)-(%.2f, %.2f)\n", bbox.a.x, bbox.a.y, bbox.b.x, bbox.b.y); } bool Region::overlapCheck(BBox& bbox, unsigned int& p) { p = R_INSIDE; Point& a = bbox.a; Point& b = bbox.b; Point& r = _bbox.a; Point& s = _bbox.b; if (s.x <= a.x) { // Wholly right. p = R_RIGHT; return false; } else if (r.x >= b.x) { // Wholly left. p = R_LEFT; return false; } if (s.y <= a.y) { // Wholly below. p = R_BELOW; return false; } else if (r.y >= b.y) { // Wholly above. p = R_ABOVE; return false; } if (a.y == r.y) { // Shared top edge. p |= R_TEDGE; } else if (a.y < r.y) { // Need to split above. p |= R_ABOVE; } if (b.y == s.y) { // Shared bottom edge. p |= R_BEDGE; } else if (b.y > s.y) { // Need to split below. p |= R_BELOW; } if (a.x == r.x) { // Shared left edge. p |= R_LEDGE; } else if (a.x < r.x) { // Need to split left. p |= R_LEFT; } if (b.x == s.x) { // Shared right edge. p |= R_REDGE; } else if (b.x > s.x) { // Need to split right. p |= R_RIGHT; } return true; } void Region::getBBox(BBox& bb) { bb.a = _bbox.a; bb.b = _bbox.b; } Region *Region::up(void) { return _up; } Region *Region::down(void) { return _down; } Region *Region::left(void) { return _left; } Region *Region::right(void) { return _right; } bool Region::isBlock(void) { return !(_blocks.empty()); } void Region::initialSplit(BBox& bbox, unsigned int pos, unsigned int& shapeId) { Point& n1 = bbox.a; Point& n2 = bbox.b; Region *newR = this; Region *left, *right, *top, *bottom; if (pos == R_INSIDE) { split(n2.y, R_HORI); split(n2.x, R_VERT); newR = split(n1.y, R_HORI); newR = newR->split(n1.x, R_VERT); printf("%p - list %d add %d\n", newR, (int) newR->_blocks.size(), shapeId); newR->_blocks.push_back((int) shapeId); newR->_blocks.sort(); newR->_blocks.unique(); } else { Region *tar = NULL; tar = newR->findRegion(n1.x, R_VERT); if (pos & R_LEFT) { if (n1.x == tar->_bbox.a.x) { newR = tar->_right; } else if (n1.x == tar->_bbox.b.x) { newR = tar; } else { newR = tar->split(n1.x, R_VERT); } } else if (!(pos & R_LEDGE)) { newR = tar->split(n1.x, R_VERT); } left = newR; tar = left->findRegion(n1.y, R_HORI); if (pos & R_ABOVE) { if (n1.y == tar->_bbox.a.y) { newR = tar->_down; } else if (n1.y == tar->_bbox.b.y) { newR = tar; } else { newR = tar->split(n1.y, R_HORI); } } else if (!(pos & R_TEDGE)) { newR = tar->split(n1.y, R_HORI); } top = newR; right = newR; tar = newR->findRegion(n2.x, R_VERT); if (pos & R_RIGHT) { if (n2.x == tar->_bbox.a.x) { right = tar->_left; } else if (n2.x == tar->_bbox.b.x) { right = tar; } else { tar->split(n2.x, R_VERT); right = tar; } } else if (!(pos & R_REDGE)) { tar->split(n2.x, R_VERT); right = tar; } bottom = right; tar = right->findRegion(n2.y, R_HORI); if (pos & R_BELOW) { if (n2.y == tar->_bbox.a.y) { bottom = tar->_up; } else if (n2.y == tar->_bbox.b.y) { bottom = tar; } else { tar->split(n2.y, R_HORI); bottom = tar; } } else if (!(pos & R_BEDGE)) { tar->split(n2.y, R_HORI); bottom = tar; } // top is actually top-left, and bottom is bottom-right. Region *curr = top, *cptr = NULL; while (curr->_bbox.b.y <= bottom->_bbox.b.y) { cptr = curr; while (cptr->_bbox.b.x <= bottom->_bbox.b.x) { printf("%p - list %d add %d\n", cptr, (int) cptr->_blocks.size(), shapeId); cptr->_blocks.push_back((int) shapeId); cptr->_blocks.sort(); cptr->_blocks.unique(); cptr = cptr->_right; } curr = curr->_down; } } } // Returns the region containing the value 'pos' in the direction 'dir'. // Thus, if looking for the x value 55, you would pass R_VERT as 'dir'. // 'forMerge' specifies that the left or top block of a pair of regions // with the split value of 'pos' should be returned. Region *Region::findRegion(double pos, unsigned int dir, const bool forMerge) { Region *curr = this; if (dir & R_VERT) { while (pos > curr->_bbox.b.x) { curr = curr->_right; } while (pos < curr->_bbox.a.x) { curr = curr->_left; } if (forMerge) { if (pos == curr->_bbox.a.x) { curr = curr->_left; } if (pos != curr->_bbox.b.x) { // 'pos' is not on the boundary. return NULL; } } } else if (dir & R_HORI) { while (pos > curr->_bbox.b.y) { curr = curr->_down; } while (pos < curr->_bbox.a.y) { curr = curr->_up; } if (forMerge) { if (pos == curr->_bbox.a.y) { curr = curr->_up; } if (pos != curr->_bbox.b.y) { // 'pos' is not on the boundary. return NULL; } } } return curr; } Region *Region::split(double pos, unsigned int dir) { Region *newR = NULL; bool first = true; if (dir & R_VERT) { newR = splitDir(pos, R_UP, first); if (_down) _down->splitDir(pos, R_DOWN); } else if (dir & R_HORI) { newR = splitDir(pos, R_RIGHT, first); if (_left) _left->splitDir(pos, R_LEFT); } return newR; } void Region::merge(unsigned int dir) { bool first = true; if (dir & R_VERT) { mergeDir(R_UP, first); if (_down) _down->mergeDir(R_DOWN); } else if (dir & R_HORI) { mergeDir(R_RIGHT, first); if (_left) _left->mergeDir(R_LEFT); } } void Region::mergeRegion(Region *src) { assert(src != NULL); if (src == _left) { pairHor(src->_left, this); _bbox.a.x = src->_bbox.a.x; } else if (src == _right) { pairHor(this, src->_right); _bbox.b.x = src->_bbox.b.x; } else if (src == _up) { pairVer(src->_up, this); _bbox.a.y = src->_bbox.a.y; } else if (src == _down) { pairVer(this, src->_down); _bbox.b.y = src->_bbox.b.y; } else { fprintf(stderr, "Error: Avoid::Region::merge(): " "Argument not adjoining region.\n"); abort(); } mergeAttributes(src); printf("DEL %p\n", src); delete src; } Region *Region::splitDir(double pos, unsigned int dir, bool first) { Point& o1 = _bbox.a; Point& o2 = _bbox.b; Region *newR = NULL; if (dir & R_VERT) { assert(pos > _bbox.a.x); assert(pos < _bbox.b.x); // Vertical recursion: // Create new block. Region *r = new Region(pos, o1.y, o2.x, o2.y); printf("NEW %p\n", r); r->copyAttributes(this); Region *o_up = _up; Region *o_down = _down; // Resize old block. o2.x = pos; pairHor(r, _right); pairHor(this, r); if (dir & R_UP) { if (!first) pairVer(r, _down->_right); if (o_up) o_up->splitDir(pos, R_UP); } else if (dir & R_DOWN) { if (!first) pairVer(_up->_right, r); if (o_down) o_down->splitDir(pos, R_DOWN); } newR = r; } else if (dir & R_HORI) { // Vertical recursion: // Create new block. Region *b = new Region(o1.x, pos, o2.x, o2.y); printf("NEW %p\n", b); b->copyAttributes(this); Region *o_left = _left; Region *o_right = _right; // Resize old block. o2.y = pos; pairVer(b, _down); pairVer(this, b); if (dir & R_LEFT) { if (!first) pairHor(b, _right->_down); if (o_left) o_left->splitDir(pos, R_LEFT); } else if (dir & R_RIGHT) { if (!first) pairHor(_left->_down, b); if (o_right) o_right->splitDir(pos, R_RIGHT); } newR = b; } return newR; } void Region::mergeDir(unsigned int dir, bool first) { if (dir & R_VERT) { assert(_right != NULL); mergeRegion(_right); if (dir & R_UP) { if (_up) _up->mergeDir(dir, R_UP); } else if (dir & R_DOWN) { if (_down) _down->mergeDir(dir, R_DOWN); } } else if (dir & R_HORI) { assert(_down != NULL); mergeRegion(_down); if (dir & R_LEFT) { if (_left) _left->mergeDir(dir, R_LEFT); } else if (dir & R_RIGHT) { if (_right) _right->mergeDir(dir, R_RIGHT); } } } void Region::copyAttributes(Region *src) { _blocks = src->_blocks; } void Region::mergeAttributes(Region *src) { _blocks.merge(src->_blocks); _blocks.sort(); _blocks.unique(); } //--------------------------- // Static member functions: void Region::pairHor(Region *l, Region *r) { if (l) l->_right = r; if (r) r->_left = l; } void Region::pairVer(Region *a, Region *b) { if (a) a->_down = b; if (b) b->_up = a; } void Region::addShape(ShapeRef *shape) { if (!centerRegion) { // Add new default region. centerRegion = new Region(); printf("NEW %p\n", centerRegion); } BBox bbox; // Get bounding box for added shape. shape->boundingBox(bbox); printBBox("Add", bbox); unsigned int shapeId = shape->id(); // Find starting point for overlap Region *curr = centerRegion; unsigned int pos = R_INSIDE; while (!(curr->overlapCheck(bbox, pos))) { if (pos & R_LEFT) { curr = curr->_left; } else if (pos & R_RIGHT) { curr = curr->_right; } else if (pos & R_ABOVE) { curr = curr->_up; } else if (pos & R_BELOW) { curr = curr->_down; } } curr->initialSplit(bbox, pos, shapeId); centerRegion = curr; } void Region::removeShape(ShapeRef *shape) { const bool forMerge = true; BBox bbox; // Get bounding box for added shape. shape->boundingBox(bbox); printBBox("Remove", bbox); unsigned int shapeId = shape->id(); Region *aboveTop = centerRegion->findRegion(bbox.a.y, R_HORI, forMerge); Region *aboveBottom = aboveTop->findRegion(bbox.b.y, R_HORI, forMerge); Region *leftOfLeft = aboveBottom->findRegion(bbox.a.x, R_VERT, forMerge); Region *leftOfRight = leftOfLeft->findRegion(bbox.b.x, R_VERT, forMerge); assert(aboveTop != NULL); assert(aboveBottom != NULL); assert(leftOfLeft != NULL); assert(leftOfRight != NULL); // Find Top Left. Region *topLeft = leftOfLeft->_right; while (topLeft->_bbox.a.y != aboveTop->_bbox.b.y) { topLeft = topLeft->_up; } // Find Bottom Right. Region *botRight = leftOfRight; while (botRight->_bbox.b.y != aboveBottom->_bbox.b.y) { botRight = botRight->_down; } // Clear blocking flag. Region *curr = topLeft, *cptr = NULL; while (curr->_bbox.b.y <= botRight->_bbox.b.y) { cptr = curr; while (cptr->_bbox.b.x <= botRight->_bbox.b.x) { ShapeList& blocks = cptr->_blocks; assert(std::find(blocks.begin(), blocks.end(), (int) shapeId) != blocks.end()); printf("%p - list %d remove %d\n", cptr, (int) blocks.size(), shapeId); cptr->_blocks.remove((int) shapeId); cptr = cptr->_right; } curr = curr->_down; } // These two are safe since they don't invalidate the pointers // to the regions that are inside the shape. if (aboveBottom->safeToMerge(R_HORI)) { aboveBottom->merge(R_HORI); } if (leftOfRight->safeToMerge(R_VERT)) { leftOfRight->merge(R_VERT); } // Be a bit more careful with these. double leftX = leftOfLeft->_bbox.b.x; if (aboveTop->safeToMerge(R_HORI)) { aboveTop->merge(R_HORI); } // leftOfLeft may have been freed, so look for the new block at // that position. leftOfLeft = aboveTop->findRegion(leftX, R_VERT, forMerge); assert(leftOfLeft); if (leftOfLeft->safeToMerge(R_VERT)) { leftOfLeft->merge(R_VERT); } } bool Region::safeToMerge(unsigned int dir) { bool unsafe = false; if (dir == R_HORI) { printf("safeToMerge? y = %.3f... ", _bbox.b.y); unsafe |= unsafeToMerge(R_RIGHT); if (_left) unsafe |= _left->unsafeToMerge(R_LEFT); } else if (dir == R_VERT) { printf("safeToMerge? x = %.3f... ", _bbox.b.x); unsafe |= unsafeToMerge(R_DOWN); if (_up) unsafe |= _up->unsafeToMerge(R_UP); } printf("%s.\n", (unsafe) ? "no" : "yes"); return !unsafe; } bool Region::unsafeToMerge(unsigned int dir) { bool unsafe = false; if (dir & R_HORI) { // If they are not the same on both sides then we can merge. if (_blocks != _down->_blocks) { printf("\n _blocks:\n "); for (ShapeList::iterator i = _blocks.begin(); i != _blocks.end(); ++i) { printf("%d ", *i); } printf("\n _down->_blocks:\n "); for (ShapeList::iterator i = _down->_blocks.begin(); i != _down->_blocks.end(); ++i) { printf("%d ", *i); } unsafe |= true; printf("\n"); } if ((dir == R_LEFT) && _left) { unsafe |= _left->unsafeToMerge(dir); } else if ((dir == R_RIGHT) && _right) { unsafe |= _right->unsafeToMerge(dir); } } else if (dir & R_VERT) { if (_blocks != _right->_blocks) { printf("\n _blocks:\n "); for (ShapeList::iterator i = _blocks.begin(); i != _blocks.end(); ++i) { printf("%d ", *i); } printf("\n _right->_blocks:\n "); for (ShapeList::iterator i = _right->_blocks.begin(); i != _right->_blocks.end(); ++i) { printf("%d ", *i); } unsafe |= true; printf("\n"); } if ((dir == R_UP) && _up) { unsafe |= _up->unsafeToMerge(dir); } else if ((dir == R_DOWN) && _down) { unsafe |= _down->unsafeToMerge(dir); } } return unsafe; } Region *Region::getTopLeftRegion(void) { Region *curr = centerRegion; while (curr && (curr->_up || curr->_left)) { if (curr->_up) { curr = curr->_up; } else { curr = curr->_left; } } return curr; } }