From ce906e83f8f6d2c83bb198fcf599e40cf87992ed Mon Sep 17 00:00:00 2001 From: twanvl Date: Wed, 27 Aug 2008 23:46:31 +0000 Subject: [PATCH] Cleaned up the calculation of bounds of symbols, this fixes bounds calculation with symmetries. git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@1178 0fc631ac-6414-0410-93d0-97cfa31319b6 --- src/data/action/symbol.cpp | 41 +++++------ src/data/action/symbol.hpp | 10 +-- src/data/symbol.cpp | 120 ++++++++++++++++++++++--------- src/data/symbol.hpp | 44 +++++++++--- src/gfx/bezier.cpp | 44 +++++------- src/gfx/bezier.hpp | 12 +--- src/gui/symbol/select_editor.cpp | 25 +++---- src/gui/symbol/select_editor.hpp | 5 +- src/gui/symbol/selection.cpp | 6 +- src/render/symbol/viewer.cpp | 10 ++- src/util/vector2d.hpp | 10 ++- 11 files changed, 192 insertions(+), 135 deletions(-) diff --git a/src/data/action/symbol.cpp b/src/data/action/symbol.cpp index 92876645..0c138b28 100644 --- a/src/data/action/symbol.cpp +++ b/src/data/action/symbol.cpp @@ -32,14 +32,12 @@ SymbolPartsAction::SymbolPartsAction(const set& parts) SymbolPartMoveAction::SymbolPartMoveAction(const set& parts, const Vector2D& delta) : SymbolPartsAction(parts) , delta(delta), moved(-delta) - , min_pos(Vector2D::infinity()), max_pos(-Vector2D::infinity()) , constrain(false) , snap(0) { // Determine min/max_pos FOR_EACH(p, parts) { - min_pos = piecewise_min(min_pos, p->min_pos); - max_pos = piecewise_max(max_pos, p->max_pos); + bounds.update(p->bounds); } } @@ -55,9 +53,9 @@ void SymbolPartMoveAction::perform(bool to_undo) { moved = -moved; } void SymbolPartMoveAction::movePart(SymbolPart& part) { + part.bounds.min -= moved; + part.bounds.max -= moved; if (SymbolShape* s = part.isSymbolShape()) { - s->min_pos -= moved; - s->max_pos -= moved; FOR_EACH(pnt, s->points) { pnt->pos -= moved; } @@ -68,14 +66,13 @@ void SymbolPartMoveAction::movePart(SymbolPart& part) { FOR_EACH(p, g->parts) { movePart(*p); } - g->calculateBoundsNonRec(); } } void SymbolPartMoveAction::move(const Vector2D& deltaDelta) { delta += deltaDelta; // Determine actual delta, possibly constrained and snapped - Vector2D d = constrain_snap_vector_offset(min_pos, max_pos, delta, constrain, snap); + Vector2D d = constrain_snap_vector_offset(bounds.min, bounds.max, delta, constrain, snap); Vector2D dd = d - moved; // move this much more // Move each point by d moved = -dd; @@ -94,6 +91,7 @@ void SymbolPartMatrixAction::transform(const Matrix2D& m) { // Transform each part FOR_EACH(p, parts) { transform(*p, m); + p->updateBounds(); } } void SymbolPartMatrixAction::transform(SymbolPart& part, const Matrix2D& m) { @@ -103,8 +101,6 @@ void SymbolPartMatrixAction::transform(SymbolPart& part, const Matrix2D& m) { pnt->delta_before = pnt->delta_before * m; pnt->delta_after = pnt->delta_after * m; } - // bounds change after transforming - s->calculateBounds(); } else if (SymbolSymmetry* s = part.isSymbolSymmetry()) { s->center = (s->center - center) * m + center; s->handle = s->handle * m; @@ -113,7 +109,6 @@ void SymbolPartMatrixAction::transform(SymbolPart& part, const Matrix2D& m) { FOR_EACH(p, g->parts) { transform(*p, m); } - g->calculateBoundsNonRec(); } } @@ -205,15 +200,13 @@ SymbolPartScaleAction::SymbolPartScaleAction(const set& parts, int , snap(0) { // Find min and max coordinates - old_min = Vector2D( 1e6, 1e6); - Vector2D old_max (-1e6,-1e6); + Bounds bounds; FOR_EACH(p, parts) { - old_min = piecewise_min(old_min, p->min_pos); - old_max = piecewise_max(old_max, p->max_pos); + bounds.update(p->bounds); } // new == old - new_min = new_real_min = old_min; - new_size = new_real_size = old_size = old_max - old_min; + new_min = new_real_min = old_min = bounds.min; + new_size = new_real_size = old_size = bounds.max - bounds.min; } String SymbolPartScaleAction::getName(bool to_undo) const { @@ -266,14 +259,15 @@ void SymbolPartScaleAction::transformAll() { } } void SymbolPartScaleAction::transformPart(SymbolPart& part) { + // update bounds + part.bounds.min = transform(part.bounds.min); + part.bounds.max = transform(part.bounds.max); + // make sure that max >= min + if (part.bounds.min.x > part.bounds.max.x) swap(part.bounds.min.x, part.bounds.max.x); + if (part.bounds.min.y > part.bounds.max.y) swap(part.bounds.min.y, part.bounds.max.y); if (SymbolShape* s = part.isSymbolShape()) { - Vector2D scale = new_size.div(old_size); - s->min_pos = transform(s->min_pos); - s->max_pos = transform(s->max_pos); - // make sure that max >= min - if (s->min_pos.x > s->max_pos.x) swap(s->min_pos.x, s->max_pos.x); - if (s->min_pos.y > s->max_pos.y) swap(s->min_pos.y, s->max_pos.y); // scale all points + Vector2D scale = new_size.div(old_size); FOR_EACH(pnt, s->points) { pnt->pos = transform(pnt->pos); // also scale handles @@ -288,7 +282,6 @@ void SymbolPartScaleAction::transformPart(SymbolPart& part) { FOR_EACH(p, g->parts) { transformPart(*p); } - g->calculateBoundsNonRec(); } } @@ -538,7 +531,7 @@ GroupSymbolPartsAction::GroupSymbolPartsAction(SymbolGroup& root, const setcalculateBounds(); + group->updateBounds(); } String GroupSymbolPartsAction::getName(bool to_undo) const { return group->isSymbolSymmetry() ? _ACTION_("add symmetry") : _ACTION_("group parts"); diff --git a/src/data/action/symbol.hpp b/src/data/action/symbol.hpp index 64057fd2..dface940 100644 --- a/src/data/action/symbol.hpp +++ b/src/data/action/symbol.hpp @@ -48,14 +48,14 @@ class SymbolPartMoveAction : public SymbolPartsAction { void move(const Vector2D& delta); private: - Vector2D delta; ///< How much to move - Vector2D moved; ///< How much has been moved - Vector2D min_pos, max_pos; ///< Bounding box of the thing we are moving + Vector2D delta; ///< How much to move + Vector2D moved; ///< How much has been moved + Bounds bounds; ///< Bounding box of the thing we are moving void movePart(SymbolPart& part); ///< Move a single part public: - bool constrain; ///< Constrain movement? - int snap; ///< Snap to grid? + bool constrain; ///< Constrain movement? + int snap; ///< Snap to grid? }; // ----------------------------------------------------------------------------- : Rotating symbol parts diff --git a/src/data/symbol.cpp b/src/data/symbol.cpp index f1ec8b60..ca2ea06f 100644 --- a/src/data/symbol.cpp +++ b/src/data/symbol.cpp @@ -94,11 +94,36 @@ Vector2D& ControlPoint::getOther(WhichHandle wh) { } } +// ----------------------------------------------------------------------------- : Bounds + +void Bounds::update(const Vector2D& p) { + min = piecewise_min(min, p); + max = piecewise_max(max, p); +} +void Bounds::update(const Bounds& b) { + min = piecewise_min(min, b.min); + max = piecewise_max(max, b.max); +} + +bool Bounds::contains(const Vector2D& p) const { + return p.x >= min.x && p.y >= min.y && + p.x <= max.x && p.y <= max.y; +} +bool Bounds::contains(const Bounds& b) const { + return b.min.x >= min.x && b.min.y >= min.y && + b.max.x <= max.x && b.max.y <= max.y; +} + +Vector2D Bounds::corner(int dx, int dy) const { + return Vector2D( + 0.5 * (min.x + max.x + dx * (max.x - min.x)), + 0.5 * (min.y + max.y + dy * (max.y - min.y))); +} + // ----------------------------------------------------------------------------- : SymbolPart -void SymbolPart::calculateBounds() { - min_pos = Vector2D::infinity(); - max_pos = -Vector2D::infinity(); +void SymbolPart::updateBounds() { + calculateBounds(Vector2D(), Matrix2D(), true); } IMPLEMENT_REFLECTION(SymbolPart) { @@ -133,6 +158,23 @@ IMPLEMENT_REFLECTION_ENUM(SymbolShapeCombine) { VALUE_N("border", SYMBOL_COMBINE_BORDER); } + +template void fix(const T&,SymbolShape&) {} +void fix(const Reader& reader, SymbolShape& shape) { + if (reader.file_app_version != Version()) return; + shape.updateBounds(); + if (shape.bounds.max.x < 100 || shape.bounds.max.y < 100) return; + // this is a <= 0.1.2 symbol, points range [0...500] instead of [0...1] + // adjust it + FOR_EACH(p, shape.points) { + p->pos /= 500.0; + p->delta_before /= 500.0; + p->delta_after /= 500.0; + } + if (shape.name.empty()) shape.name = _("Shape"); + shape.updateBounds(); +} + IMPLEMENT_REFLECTION(SymbolShape) { REFLECT_BASE(SymbolPart); REFLECT(combine); @@ -141,21 +183,11 @@ IMPLEMENT_REFLECTION(SymbolShape) { REFLECT_IF_READING { // enforce constraints enforceConstraints(); - calculateBounds(); - if (max_pos.x > 100 && max_pos.y > 100) { - // this is a <= 0.1.2 symbol, points range [0...500] instead of [0...1] - // adjust it - FOR_EACH(p, points) { - p->pos /= 500.0; - p->delta_before /= 500.0; - p->delta_after /= 500.0; - } - if (name.empty()) name = _("Shape"); - calculateBounds(); - } + fix(tag,*this); } } + SymbolShape::SymbolShape() : combine(SYMBOL_COMBINE_OVERLAP), rotation_center(.5, .5) {} @@ -182,13 +214,13 @@ void SymbolShape::enforceConstraints() { } } -void SymbolShape::calculateBounds() { - min_pos = Vector2D::infinity(); - max_pos = -Vector2D::infinity(); - Rotation rot(0); +Bounds SymbolShape::calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity) { + Bounds bounds; for (int i = 0 ; i < (int)points.size() ; ++i) { - segment_bounds(rot, *getPoint(i), *getPoint(i + 1), min_pos, max_pos); + bounds.update(segment_bounds(origin, m, *getPoint(i), *getPoint(i + 1))); } + if (is_identity) this->bounds = bounds; + return bounds; } // ----------------------------------------------------------------------------- : SymbolSymmetry @@ -220,6 +252,32 @@ String SymbolSymmetry::expectedName() const { + String::Format(_(" (%d)"), copies); } +Bounds SymbolSymmetry::calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity) { + Bounds bounds; + // See SymbolViewer::draw + double b = 2 * handle.angle(); + int copies = kind == SYMMETRY_REFLECTION ? this->copies & ~1 : this->copies; + FOR_EACH_CONST(p, parts) { + for (int i = 0 ; i < copies ; ++i) { + double a = i * 2 * M_PI / copies; + if (kind == SYMMETRY_ROTATION || i % 2 == 0) { + Matrix2D rot(cos(a),-sin(a), sin(a),cos(a)); + bounds.update( + p->calculateBounds(origin + (center - center*rot) * m, rot * m, is_identity && i == 0) + ); + } else { + Matrix2D rot(cos(a+b),sin(a+b), sin(a+b),-cos(a+b)); + bounds.update( + p->calculateBounds(origin + (center - center*rot) * m, rot * m, is_identity && i == 0) + ); + } + } + } + // done + if (is_identity) this->bounds = bounds; + return bounds; +} + IMPLEMENT_REFLECTION(SymbolSymmetry) { REFLECT_BASE(SymbolPart); REFLECT(kind); @@ -227,7 +285,6 @@ IMPLEMENT_REFLECTION(SymbolSymmetry) { REFLECT(center); REFLECT(handle); REFLECT(parts); - REFLECT_IF_READING calculateBoundsNonRec(); } // ----------------------------------------------------------------------------- : SymbolGroup @@ -257,30 +314,25 @@ bool SymbolGroup::isAncestor(const SymbolPart& that) const { return false; } -void SymbolGroup::calculateBounds() { - FOR_EACH(p, parts) p->calculateBounds(); - calculateBoundsNonRec(); -} -void SymbolGroup::calculateBoundsNonRec() { - min_pos = Vector2D::infinity(); - max_pos = -Vector2D::infinity(); +Bounds SymbolGroup::calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity) { + Bounds bounds; FOR_EACH(p, parts) { - min_pos = piecewise_min(min_pos, p->min_pos); - max_pos = piecewise_max(max_pos, p->max_pos); + bounds.update(p->calculateBounds(origin, m, is_identity)); } + if (is_identity) this->bounds = bounds; + return bounds; } IMPLEMENT_REFLECTION(SymbolGroup) { REFLECT_BASE(SymbolPart); REFLECT(parts); - REFLECT_IF_READING calculateBoundsNonRec(); } // ----------------------------------------------------------------------------- : Symbol IMPLEMENT_REFLECTION(Symbol) { REFLECT(parts); - REFLECT_IF_READING calculateBoundsNonRec(); + REFLECT_IF_READING updateBounds(); } double Symbol::aspectRatio() const { @@ -288,8 +340,8 @@ double Symbol::aspectRatio() const { // In each direction take the lowest one // This is at most 0.5 (if the symbol is just a line in the middle) // Multiply by 2 (below) to give something in the range [0...1] i.e. [touches the edge...only in the middle] - double margin_x = min(0.4999, max(0., min(min_pos.x, 1-max_pos.x))); - double margin_y = min(0.4999, max(0., min(min_pos.y, 1-max_pos.y))); + double margin_x = min(0.4999, max(0., min(bounds.min.x, 1-bounds.max.x))); + double margin_y = min(0.4999, max(0., min(bounds.min.y, 1-bounds.max.y))); // The difference between these two, // e.g. if the vertical margin is more then the horizontal one, the symbol is 'flat' double delta = 2 * (margin_y - margin_x); diff --git a/src/data/symbol.hpp b/src/data/symbol.hpp index 90f17b50..6b63af43 100644 --- a/src/data/symbol.hpp +++ b/src/data/symbol.hpp @@ -13,6 +13,7 @@ #include #include #include +#include DECLARE_POINTER_TYPE(ControlPoint); DECLARE_POINTER_TYPE(SymbolPart); @@ -105,6 +106,32 @@ class SelectedHandle { }; +// ----------------------------------------------------------------------------- : Bounds + +/// Bounding box of a symbol part +class Bounds { + public: + inline Bounds() : min(Vector2D::infinity()), max(-Vector2D::infinity()) {} + inline explicit Bounds(const Vector2D& p) : min(p), max(p) {} + inline Bounds(const Vector2D& min, const Vector2D& max) : min(min), max(max) {} + + /// Combine with another bounding box + void update(const Bounds& b); + void update(const Vector2D& p); + + /// Does this box contain the given point? + bool contains(const Vector2D& p) const; + /// Does this box contain the given rectangle? + bool contains(const Bounds& b) const; + + /// Corner or center of this bounding box, dx,dy in <-1, 0, 1> + Vector2D corner(int dx, int dy) const; + + Vector2D min, max; + + inline operator RealRect () const { return RealRect(min, RealSize(max - min)); } +}; + // ----------------------------------------------------------------------------- : SymbolPart /// A part of a symbol, not necesserly a shape @@ -114,7 +141,7 @@ class SymbolPart : public IntrusivePtrVirtualBase { String name; /// Position and size of the part. /** this is the smallest axis aligned bounding box that fits around the part */ - Vector2D min_pos, max_pos; + Bounds bounds; /// Type of this part virtual String typeName() const = 0; @@ -137,8 +164,10 @@ class SymbolPart : public IntrusivePtrVirtualBase { /** also true if this==that*/ virtual bool isAncestor(const SymbolPart& that) const { return this == &that; } - /// Calculate the position and size of the part (min_pos and max_pos) - virtual void calculateBounds(); + /// Calculate the position and size of the part (bounds) + virtual void updateBounds(); + /// Calculate the position and size of the part using the given rotation matrix + virtual Bounds calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity) = 0; DECLARE_REFLECTION_VIRTUAL(); }; @@ -189,8 +218,8 @@ class SymbolShape : public SymbolPart { /// Enforce lock constraints void enforceConstraints(); - /// Calculate the position and size of the part - virtual void calculateBounds(); + /// Calculate the position and size of the part using the given rotation matrix + virtual Bounds calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity); DECLARE_REFLECTION(); }; @@ -212,9 +241,7 @@ class SymbolGroup : public SymbolPart { virtual bool isAncestor(const SymbolPart& that) const; - virtual void calculateBounds(); - /// re-calculate the bounds, but not of the contained parts - void calculateBoundsNonRec(); + virtual Bounds calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity); DECLARE_REFLECTION(); }; @@ -245,6 +272,7 @@ class SymbolSymmetry : public SymbolGroup { virtual const SymbolSymmetry* isSymbolSymmetry() const { return this; } String expectedName() const; + virtual Bounds calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity); DECLARE_REFLECTION(); }; diff --git a/src/gfx/bezier.cpp b/src/gfx/bezier.cpp index 602c75e5..2249e349 100644 --- a/src/gfx/bezier.cpp +++ b/src/gfx/bezier.cpp @@ -94,23 +94,28 @@ void segment_subdivide(const ControlPoint& p0, const ControlPoint& p1, const Vec // ----------------------------------------------------------------------------- : Bounds -void segment_bounds(const Rotation& rot, const ControlPoint& p1, const ControlPoint& p2, Vector2D& min, Vector2D& max) { +Bounds segment_bounds(const Vector2D& origin, const Matrix2D& m, const ControlPoint& p1, const ControlPoint& p2) { assert(p1.segment_after == p2.segment_before); if (p1.segment_after == SEGMENT_LINE) { - line_bounds (rot, p1.pos, p2.pos, min, max); + return line_bounds (origin, m, p1.pos, p2.pos); } else { - bezier_bounds(rot, p1, p2, min, max); + return bezier_bounds(origin, m, p1, p2); } } -void bezier_bounds(const Rotation& rot, const ControlPoint& p1, const ControlPoint& p2, Vector2D& min, Vector2D& max) { +Bounds bezier_bounds(const Vector2D& origin, const Matrix2D& m, const ControlPoint& p1, const ControlPoint& p2) { assert(p1.segment_after == SEGMENT_CURVE); + // Transform the control points + Vector2D r1 = origin + p1.pos * m; + Vector2D r2 = origin + (p1.pos + p1.delta_after) * m; + Vector2D r3 = origin + (p2.pos + p2.delta_before) * m; + Vector2D r4 = origin + p2.pos * m; // First of all, the corners should be in the bounding box - point_bounds(rot, p1.pos, min, max); - point_bounds(rot, p2.pos, min, max); + Bounds bounds(r1); + bounds.update(r4); // Solve the derivative of the bezier curve to find its extremes // It's only a quadtratic equation :) - BezierCurve curve(p1,p2); + BezierCurve curve(r1,r2,r3,r4); double roots[4]; UInt count; count = solve_quadratic(3*curve.a.x, 2*curve.b.x, curve.c.x, roots); @@ -119,35 +124,24 @@ void bezier_bounds(const Rotation& rot, const ControlPoint& p1, const ControlPoi for (UInt i = 0 ; i < count ; ++i) { double t = roots[i]; if (t >=0 && t <= 1) { - point_bounds(rot, curve.pointAt(t), min, max); + bounds.update(curve.pointAt(t)); } } + return bounds; } -void line_bounds(const Rotation& rot, const Vector2D& p1, const Vector2D& p2, Vector2D& min, Vector2D& max) { - point_bounds(rot, p1, min, max); - point_bounds(rot, p2, min, max); +Bounds line_bounds(const Vector2D& origin, const Matrix2D& m, const Vector2D& p1, const Vector2D& p2) { + Bounds bounds(origin + p1 * m); + bounds.update(origin + p2 * m); + return bounds; } -void point_bounds(const Rotation& rot, const Vector2D& p, Vector2D& min, Vector2D& max) { - Vector2D pr = rot.tr(p); - min = piecewise_min(min, pr); - max = piecewise_max(max, pr); -} - -// Is a point inside the bounds ? -bool point_in_bounds(const Vector2D& p, const Vector2D& min, const Vector2D& max) { - return p.x >= min.x && p.y >= min.y && - p.x <= max.x && p.y <= max.y; -} - - // ----------------------------------------------------------------------------- : Point tests // Is a point inside a symbol shape? bool point_in_shape(const Vector2D& pos, const SymbolShape& shape) { // Step 1. compare bounding box of the part - if (!point_in_bounds(pos, shape.min_pos, shape.max_pos)) return false; + if (!shape.bounds.contains(pos)) return false; // Step 2. trace ray outward, count intersections int count = 0; diff --git a/src/gfx/bezier.hpp b/src/gfx/bezier.hpp index 40323163..26f7b3dc 100644 --- a/src/gfx/bezier.hpp +++ b/src/gfx/bezier.hpp @@ -78,25 +78,19 @@ void segment_subdivide(const ControlPoint& p0, const ControlPoint& p1, const Vec * min is only changed if the minimum is smaller then the current value in min, * max only if the maximum is larger. */ -void segment_bounds(const Rotation& rot, const ControlPoint& p1, const ControlPoint& p2, Vector2D& min, Vector2D& max); +Bounds segment_bounds(const Vector2D& origin, const Matrix2D& m, const ControlPoint& p1, const ControlPoint& p2); /// Find a bounding box that fits a curve between p1 and p2, stores the results in min and max. /** min is only changed if the minimum is smaller then the current value in min, * max only if the maximum is larger */ -void bezier_bounds(const Rotation& rot, const ControlPoint& p1, const ControlPoint& p2, Vector2D& min, Vector2D& max); +Bounds bezier_bounds(const Vector2D& origin, const Matrix2D& m, const ControlPoint& p1, const ControlPoint& p2); /// Find a bounding box that fits around p1 and p2, stores the result in min and max /** min is only changed if the minimum is smaller then the current value in min, * max only if the maximum is larger */ -void line_bounds(const Rotation& rot, const Vector2D& p1, const Vector2D& p2, Vector2D& min, Vector2D& max); - -/// Find a bounding 'box' that fits around a single point -/** min is only changed if the minimum is smaller then the current value in min, - * max only if the maximum is larger - */ -void point_bounds(const Rotation& rot, const Vector2D& p, Vector2D& min, Vector2D& max); +Bounds line_bounds(const Vector2D& origin, const Matrix2D& m, const Vector2D& p1, const Vector2D& p2); // ----------------------------------------------------------------------------- : Point tests diff --git a/src/gui/symbol/select_editor.cpp b/src/gui/symbol/select_editor.cpp index e68896e9..f7dd6adc 100644 --- a/src/gui/symbol/select_editor.cpp +++ b/src/gui/symbol/select_editor.cpp @@ -39,7 +39,7 @@ SymbolSelectEditor::SymbolSelectEditor(SymbolControl* control, bool rotate) handleShearY = wxBitmap(rotate_image(shear,90)); handleCenter = wxBitmap(load_resource_image(_("handle_center"))); // Make sure all parts have updated bounds - getSymbol()->calculateBounds(); + getSymbol()->updateBounds(); resetActions(); } @@ -87,7 +87,7 @@ void SymbolSelectEditor::drawHandles(DC& dc) { } void SymbolSelectEditor::drawHandle(DC& dc, int dx, int dy) { - wxPoint p = control.rotation.tr(handlePos(dx, dy)); + wxPoint p = control.rotation.tr(bounds.corner(dx, dy)); p.x += 4 * dx; p.y += 4 * dy; if (rotate) { @@ -312,7 +312,7 @@ void SymbolSelectEditor::onMouseDrag (const Vector2D& from, const Vector2D& to, if (rotate) { if (scaleX == 0 || scaleY == 0) { // shear, center/fixed point on the opposite side - shearAction = new SymbolPartShearAction(control.selected_parts.get(), handlePos(-scaleX, -scaleY)); + shearAction = new SymbolPartShearAction(control.selected_parts.get(), bounds.corner(-scaleX, -scaleY)); addAction(shearAction); } else { // rotate @@ -360,7 +360,7 @@ void SymbolSelectEditor::onMouseDrag (const Vector2D& from, const Vector2D& to, // shear the selected parts Vector2D delta = to-from; delta = delta.mul(Vector2D(scaleY, scaleX)); - delta = delta.div(maxV - minV); + delta = delta.div(bounds.max - bounds.min); // shearAction->constrain = ev.ControlDown(); shearAction->snap = snap(ev); shearAction->move(delta); @@ -426,15 +426,8 @@ bool SymbolSelectEditor::isEditing() { // ----------------------------------------------------------------------------- : Other -Vector2D SymbolSelectEditor::handlePos(int dx, int dy) { - return Vector2D( - 0.5 * (maxV.x + minV.x + dx * (maxV.x - minV.x)), - 0.5 * (maxV.y + minV.y + dy * (maxV.y - minV.y)) - ); -} - bool SymbolSelectEditor::onHandle(const Vector2D& mpos, int dx, int dy) { - wxPoint p = control.rotation.tr(handlePos(dx, dy)); + wxPoint p = control.rotation.tr(bounds.corner(dx, dy)); wxPoint mp = control.rotation.tr(mpos); p.x = p.x + 4 * dx; p.y = p.y + 4 * dy; @@ -460,11 +453,9 @@ double SymbolSelectEditor::angleTo(const Vector2D& pos) { void SymbolSelectEditor::updateBoundingBox() { // Find min and max coordinates - minV = Vector2D::infinity(); - maxV = -Vector2D::infinity(); + bounds = Bounds(); FOR_EACH(p, control.selected_parts.get()) { - minV = piecewise_min(minV, p->min_pos); - maxV = piecewise_max(maxV, p->max_pos); + bounds.update(p->bounds); } /* // Find rotation center center = Vector2D(0,0); @@ -475,7 +466,7 @@ void SymbolSelectEditor::updateBoundingBox() { } center /= control.selected_parts.size(); */ - center = (minV + maxV) / 2; + center = bounds.corner(0,0); } void SymbolSelectEditor::resetActions() { diff --git a/src/gui/symbol/select_editor.hpp b/src/gui/symbol/select_editor.hpp index 73a95fec..1feb157e 100644 --- a/src/gui/symbol/select_editor.hpp +++ b/src/gui/symbol/select_editor.hpp @@ -71,7 +71,7 @@ class SymbolSelectEditor : public SymbolEditorBase { SymbolPartRotateAction* rotateAction; SymbolPartShearAction* shearAction; // Bounding box of selection - Vector2D minV, maxV; + Bounds bounds; // Where is the rotation center? Vector2D center; // What kind of clicking/dragging are we doing @@ -112,9 +112,6 @@ class SymbolSelectEditor : public SymbolEditorBase { /// Angle between center and pos double angleTo(const Vector2D& pos); - /// Return the position of a handle, dx,dy in <-1, 0, 1> - Vector2D handlePos(int dx, int dy); - /// Update minV and maxV to be the bounding box of the selected_parts /// Updates center to be the rotation center of the parts void updateBoundingBox(); diff --git a/src/gui/symbol/selection.cpp b/src/gui/symbol/selection.cpp index 2e2ccebc..65b845a1 100644 --- a/src/gui/symbol/selection.cpp +++ b/src/gui/symbol/selection.cpp @@ -116,9 +116,11 @@ bool SymbolPartsSelection::selectRect(const Vector2D& a, const Vector2D& b, cons } bool SymbolPartsSelection::selectRect(const SymbolGroup& parent, const Vector2D& a, const Vector2D& b, const Vector2D& c) { bool changes = false; + Bounds ab(a); ab.update(b); + Bounds bc(b); bc.update(c); FOR_EACH_CONST(p, parent.parts) { - bool in_ab = (p->min_pos.x >= min(a.x, b.x) && p->min_pos.y >= min(a.y, b.y) && p->max_pos.x <= max(a.x, b.x) && p->max_pos.y <= max(a.y, b.y)); - bool in_bc = (p->min_pos.x >= min(a.x, c.x) && p->min_pos.y >= min(a.y, c.y) && p->max_pos.x <= max(a.x, c.x) && p->max_pos.y <= max(a.y, c.y)); + bool in_ab = ab.contains(p->bounds); + bool in_bc = bc.contains(p->bounds); if (in_ab != in_bc) { select(p, SELECT_TOGGLE); changes = true; diff --git a/src/render/symbol/viewer.cpp b/src/render/symbol/viewer.cpp index 833582a2..b16f34b6 100644 --- a/src/render/symbol/viewer.cpp +++ b/src/render/symbol/viewer.cpp @@ -151,7 +151,7 @@ void SymbolViewer::combineSymbolPart(DC& dc, const SymbolPart& part, bool& paint } } else if (const SymbolSymmetry* s = part.isSymbolSymmetry()) { // Draw all parts, in reverse order (bottom to top), also draw rotated copies - double b = 2 * atan2(s->handle.y, s->handle.x); + double b = 2 * s->handle.angle(); Matrix2D old_m = multiply; Vector2D old_o = origin; int copies = s->kind == SYMMETRY_REFLECTION ? s->copies / 2 * 2 : s->copies; @@ -177,8 +177,7 @@ void SymbolViewer::combineSymbolPart(DC& dc, const SymbolPart& part, bool& paint // = (p * rot - d * rot + d) * m + o // = p * rot * m + (d - d * rot) * m + o Matrix2D rot(cos(a),-sin(a), sin(a),cos(a)); - multiply.mx = rot.mx * old_m; - multiply.my = rot.my * old_m; + multiply = rot * old_m; origin = old_o + (s->center - s->center * rot) * old_m; } else { // reflection @@ -192,8 +191,7 @@ void SymbolViewer::combineSymbolPart(DC& dc, const SymbolPart& part, bool& paint // = [ cos(a+b) sin(a+b) ! // ! sin(a+b) -cos(a+b) ] Matrix2D rot(cos(a+b),sin(a+b), sin(a+b),-cos(a+b)); - multiply.mx = rot.mx * old_m; - multiply.my = rot.my * old_m; + multiply = rot * old_m; origin = old_o + (s->center - s->center * rot) * old_m; } // draw rotated copy @@ -366,7 +364,7 @@ void SymbolViewer::highlightPart(DC& dc, const SymbolGroup& group, HighlightStyl if (style == HIGHLIGHT_BORDER) { dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.SetPen (wxPen(Color(255,0,0), 2)); - dc.DrawRectangle(rotation.trRectToBB(RealRect(group.min_pos, RealSize(group.max_pos - group.min_pos)))); + dc.DrawRectangle(rotation.trRectToBB(RealRect(group.bounds))); } FOR_EACH_CONST(part, group.parts) { highlightPart(dc, *part, (HighlightStyle)(style | HIGHLIGHT_LESS)); diff --git a/src/util/vector2d.hpp b/src/util/vector2d.hpp index ac929aba..34a1c3e4 100644 --- a/src/util/vector2d.hpp +++ b/src/util/vector2d.hpp @@ -96,6 +96,10 @@ class Vector2D { inline Vector2D normalized() const { return *this / length(); } + /// Angle between this vector and the x axis + inline double angle() const { + return atan2(y,x); + } inline operator wxPoint() const { return wxPoint(to_int(x), to_int(y)); @@ -142,7 +146,7 @@ class Matrix2D { public: Vector2D mx, my; - inline Matrix2D() {} + inline Matrix2D() : mx(1,0), my(0,1) {} inline Matrix2D(const Vector2D& mx, const Vector2D& my) : mx(mx), my(my) {} inline Matrix2D(double a, double b, double c, double d) : mx(a,b), my(c,d) {} }; @@ -151,6 +155,10 @@ class Matrix2D { inline Vector2D operator * (const Vector2D& a, const Matrix2D& m) { return Vector2D(dot(a,m.mx), dot(a,m.my)); } +/// vector-matrix product +inline Matrix2D operator * (const Matrix2D& a, const Matrix2D& m) { + return Matrix2D(a.mx * m, a.my * m); +} // ----------------------------------------------------------------------------- : EOF