Cache bounding boxes.

This results in massive performance improvements for hit testing.
Files with very large amounts of entities (e.g. [1]) inflict
a delay of several seconds between moving the pointer and
highlighting an entity in commit HEAD^^^, whereas in this commit
the delay is barely perceptible.

[1]: http://solvespace.com/forum.pl?action=viewthread&parent=872
pull/4/head
whitequark 2016-03-05 15:09:11 +00:00
parent e99eedd7a3
commit bda2835e9f
4 changed files with 41 additions and 13 deletions

View File

@ -318,6 +318,20 @@ void GraphicsWindow::HitTestMakeSelection(Point2d mp) {
double d, dmin = 1e12; double d, dmin = 1e12;
Selection s = {}; Selection s = {};
// Did the view projection change? If so, invalidate bounding boxes.
if(!offset.EqualsExactly(cached.offset) ||
!projRight.EqualsExactly(cached.projRight) ||
!projUp.EqualsExactly(cached.projUp) ||
EXACT(scale != cached.scale)) {
cached.offset = offset;
cached.projRight = projRight;
cached.projUp = projUp;
cached.scale = scale;
for(Entity *e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
e->screenBBoxValid = false;
}
}
// Always do the entities; we might be dragging something that should // Always do the entities; we might be dragging something that should
// be auto-constrained, and we need the hover for that. // be auto-constrained, and we need the hover for that.
for(i = 0; i < SK.entity.n; i++) { for(i = 0; i < SK.entity.n; i++) {

View File

@ -152,35 +152,38 @@ SEdgeList *Entity::GetOrGenerateEdges() {
return &edges; return &edges;
} }
BBox Entity::GetScreenBBox(bool *hasBBox) { BBox Entity::GetOrGenerateScreenBBox(bool *hasBBox) {
SBezierList *sbl = GetOrGenerateBezierCurves(); SBezierList *sbl = GetOrGenerateBezierCurves();
// We don't bother with bounding boxes for normals, workplanes, etc. // We don't bother with bounding boxes for normals, workplanes, etc.
*hasBBox = (IsPoint() || sbl->l.n > 0); *hasBBox = (IsPoint() || sbl->l.n > 0);
if(!*hasBBox) return {}; if(!*hasBBox) return {};
BBox result = {}; if(screenBBoxValid)
return screenBBox;
if(IsPoint()) { if(IsPoint()) {
Vector proj = SS.GW.ProjectPoint3(PointGetNum()); Vector proj = SS.GW.ProjectPoint3(PointGetNum());
result = BBox::From(proj, proj); screenBBox = BBox::From(proj, proj);
} else if(sbl->l.n > 0) { } else if(sbl->l.n > 0) {
Vector first = SS.GW.ProjectPoint3(sbl->l.elem[0].ctrl[0]); Vector first = SS.GW.ProjectPoint3(sbl->l.elem[0].ctrl[0]);
result = BBox::From(first, first); screenBBox = BBox::From(first, first);
for(int i = 0; i < sbl->l.n; i++) { for(int i = 0; i < sbl->l.n; i++) {
SBezier *sb = &sbl->l.elem[i]; SBezier *sb = &sbl->l.elem[i];
for(int i = 0; i <= sb->deg; i++) { for(int i = 0; i <= sb->deg; i++) {
result.Include(SS.GW.ProjectPoint3(sb->ctrl[i])); screenBBox.Include(SS.GW.ProjectPoint3(sb->ctrl[i]));
} }
} }
} else oops(); } else oops();
// Enlarge the bounding box to consider selection radius. // Enlarge the bounding box to consider selection radius.
result.minp.x -= SELECTION_RADIUS; screenBBox.minp.x -= SELECTION_RADIUS;
result.minp.y -= SELECTION_RADIUS; screenBBox.minp.y -= SELECTION_RADIUS;
result.maxp.x += SELECTION_RADIUS; screenBBox.maxp.x += SELECTION_RADIUS;
result.maxp.y += SELECTION_RADIUS; screenBBox.maxp.y += SELECTION_RADIUS;
return result; screenBBoxValid = true;
return screenBBox;
} }
double Entity::GetDistance(Point2d mp) { double Entity::GetDistance(Point2d mp) {
@ -508,7 +511,7 @@ void Entity::DrawOrGetDistance(void) {
// whether the pointer is inside its bounding box first. // whether the pointer is inside its bounding box first.
if(!dogd.drawing) { if(!dogd.drawing) {
bool hasBBox; bool hasBBox;
BBox box = GetScreenBBox(&hasBBox); BBox box = GetOrGenerateScreenBBox(&hasBBox);
if(hasBBox && !box.Contains(dogd.mp)) if(hasBBox && !box.Contains(dogd.mp))
return; return;
} }

View File

@ -455,7 +455,8 @@ public:
// POD members with indeterminate value. // POD members with indeterminate value.
Entity() : EntityBase({}), forceHidden(), actPoint(), actNormal(), Entity() : EntityBase({}), forceHidden(), actPoint(), actNormal(),
actDistance(), actVisible(), style(), construction(), actDistance(), actVisible(), style(), construction(),
dogd(), beziers(), edges(), edgesChordTol() {}; dogd(), beziers(), edges(), edgesChordTol(), screenBBox(),
screenBBoxValid() {};
// An imported entity that was hidden in the source file ends up hidden // An imported entity that was hidden in the source file ends up hidden
// here too. // here too.
@ -476,6 +477,8 @@ public:
SBezierList beziers; SBezierList beziers;
SEdgeList edges; SEdgeList edges;
double edgesChordTol; double edgesChordTol;
BBox screenBBox;
bool screenBBoxValid;
// Routines to draw and hit-test the representation of the entity // Routines to draw and hit-test the representation of the entity
// on-screen. // on-screen.
@ -509,7 +512,7 @@ public:
SBezierList *GetOrGenerateBezierCurves(); SBezierList *GetOrGenerateBezierCurves();
SEdgeList *GetOrGenerateEdges(); SEdgeList *GetOrGenerateEdges();
BBox GetScreenBBox(bool *hasBBox); BBox GetOrGenerateScreenBBox(bool *hasBBox);
void Clear() { void Clear() {
beziers.l.Clear(); beziers.l.Clear();

View File

@ -487,6 +487,14 @@ public:
Vector marqueePoint; Vector marqueePoint;
bool startedMoving; bool startedMoving;
} orig; } orig;
// We need to detect when the projection is changed to invalidate
// caches for drawn items.
struct {
Vector offset;
Vector projRight;
Vector projUp;
double scale;
} cached;
// Most recent mouse position, updated every time the mouse moves. // Most recent mouse position, updated every time the mouse moves.
Point2d currentMousePosition; Point2d currentMousePosition;