Commit 2961a3be authored by twanvl's avatar twanvl

Cleaned up the calculation of bounds of symbols, this fixes bounds calculation with symmetries.

parent 2562da1e
...@@ -32,14 +32,12 @@ SymbolPartsAction::SymbolPartsAction(const set<SymbolPartP>& parts) ...@@ -32,14 +32,12 @@ SymbolPartsAction::SymbolPartsAction(const set<SymbolPartP>& parts)
SymbolPartMoveAction::SymbolPartMoveAction(const set<SymbolPartP>& parts, const Vector2D& delta) SymbolPartMoveAction::SymbolPartMoveAction(const set<SymbolPartP>& parts, const Vector2D& delta)
: SymbolPartsAction(parts) : SymbolPartsAction(parts)
, delta(delta), moved(-delta) , delta(delta), moved(-delta)
, min_pos(Vector2D::infinity()), max_pos(-Vector2D::infinity())
, constrain(false) , constrain(false)
, snap(0) , snap(0)
{ {
// Determine min/max_pos // Determine min/max_pos
FOR_EACH(p, parts) { FOR_EACH(p, parts) {
min_pos = piecewise_min(min_pos, p->min_pos); bounds.update(p->bounds);
max_pos = piecewise_max(max_pos, p->max_pos);
} }
} }
...@@ -55,9 +53,9 @@ void SymbolPartMoveAction::perform(bool to_undo) { ...@@ -55,9 +53,9 @@ void SymbolPartMoveAction::perform(bool to_undo) {
moved = -moved; moved = -moved;
} }
void SymbolPartMoveAction::movePart(SymbolPart& part) { void SymbolPartMoveAction::movePart(SymbolPart& part) {
part.bounds.min -= moved;
part.bounds.max -= moved;
if (SymbolShape* s = part.isSymbolShape()) { if (SymbolShape* s = part.isSymbolShape()) {
s->min_pos -= moved;
s->max_pos -= moved;
FOR_EACH(pnt, s->points) { FOR_EACH(pnt, s->points) {
pnt->pos -= moved; pnt->pos -= moved;
} }
...@@ -68,14 +66,13 @@ void SymbolPartMoveAction::movePart(SymbolPart& part) { ...@@ -68,14 +66,13 @@ void SymbolPartMoveAction::movePart(SymbolPart& part) {
FOR_EACH(p, g->parts) { FOR_EACH(p, g->parts) {
movePart(*p); movePart(*p);
} }
g->calculateBoundsNonRec();
} }
} }
void SymbolPartMoveAction::move(const Vector2D& deltaDelta) { void SymbolPartMoveAction::move(const Vector2D& deltaDelta) {
delta += deltaDelta; delta += deltaDelta;
// Determine actual delta, possibly constrained and snapped // 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 Vector2D dd = d - moved; // move this much more
// Move each point by d // Move each point by d
moved = -dd; moved = -dd;
...@@ -94,6 +91,7 @@ void SymbolPartMatrixAction::transform(const Matrix2D& m) { ...@@ -94,6 +91,7 @@ void SymbolPartMatrixAction::transform(const Matrix2D& m) {
// Transform each part // Transform each part
FOR_EACH(p, parts) { FOR_EACH(p, parts) {
transform(*p, m); transform(*p, m);
p->updateBounds();
} }
} }
void SymbolPartMatrixAction::transform(SymbolPart& part, const Matrix2D& m) { void SymbolPartMatrixAction::transform(SymbolPart& part, const Matrix2D& m) {
...@@ -103,8 +101,6 @@ 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_before = pnt->delta_before * m;
pnt->delta_after = pnt->delta_after * m; pnt->delta_after = pnt->delta_after * m;
} }
// bounds change after transforming
s->calculateBounds();
} else if (SymbolSymmetry* s = part.isSymbolSymmetry()) { } else if (SymbolSymmetry* s = part.isSymbolSymmetry()) {
s->center = (s->center - center) * m + center; s->center = (s->center - center) * m + center;
s->handle = s->handle * m; s->handle = s->handle * m;
...@@ -113,7 +109,6 @@ void SymbolPartMatrixAction::transform(SymbolPart& part, const Matrix2D& m) { ...@@ -113,7 +109,6 @@ void SymbolPartMatrixAction::transform(SymbolPart& part, const Matrix2D& m) {
FOR_EACH(p, g->parts) { FOR_EACH(p, g->parts) {
transform(*p, m); transform(*p, m);
} }
g->calculateBoundsNonRec();
} }
} }
...@@ -205,15 +200,13 @@ SymbolPartScaleAction::SymbolPartScaleAction(const set<SymbolPartP>& parts, int ...@@ -205,15 +200,13 @@ SymbolPartScaleAction::SymbolPartScaleAction(const set<SymbolPartP>& parts, int
, snap(0) , snap(0)
{ {
// Find min and max coordinates // Find min and max coordinates
old_min = Vector2D( 1e6, 1e6); Bounds bounds;
Vector2D old_max (-1e6,-1e6);
FOR_EACH(p, parts) { FOR_EACH(p, parts) {
old_min = piecewise_min(old_min, p->min_pos); bounds.update(p->bounds);
old_max = piecewise_max(old_max, p->max_pos);
} }
// new == old // new == old
new_min = new_real_min = old_min; new_min = new_real_min = old_min = bounds.min;
new_size = new_real_size = old_size = old_max - old_min; new_size = new_real_size = old_size = bounds.max - bounds.min;
} }
String SymbolPartScaleAction::getName(bool to_undo) const { String SymbolPartScaleAction::getName(bool to_undo) const {
...@@ -266,14 +259,15 @@ void SymbolPartScaleAction::transformAll() { ...@@ -266,14 +259,15 @@ void SymbolPartScaleAction::transformAll() {
} }
} }
void SymbolPartScaleAction::transformPart(SymbolPart& part) { 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()) { 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 // scale all points
Vector2D scale = new_size.div(old_size);
FOR_EACH(pnt, s->points) { FOR_EACH(pnt, s->points) {
pnt->pos = transform(pnt->pos); pnt->pos = transform(pnt->pos);
// also scale handles // also scale handles
...@@ -288,7 +282,6 @@ void SymbolPartScaleAction::transformPart(SymbolPart& part) { ...@@ -288,7 +282,6 @@ void SymbolPartScaleAction::transformPart(SymbolPart& part) {
FOR_EACH(p, g->parts) { FOR_EACH(p, g->parts) {
transformPart(*p); transformPart(*p);
} }
g->calculateBoundsNonRec();
} }
} }
...@@ -538,7 +531,7 @@ GroupSymbolPartsAction::GroupSymbolPartsAction(SymbolGroup& root, const set<Symb ...@@ -538,7 +531,7 @@ GroupSymbolPartsAction::GroupSymbolPartsAction(SymbolGroup& root, const set<Symb
old_part_list.push_back(p); old_part_list.push_back(p);
} }
} }
group->calculateBounds(); group->updateBounds();
} }
String GroupSymbolPartsAction::getName(bool to_undo) const { String GroupSymbolPartsAction::getName(bool to_undo) const {
return group->isSymbolSymmetry() ? _ACTION_("add symmetry") : _ACTION_("group parts"); return group->isSymbolSymmetry() ? _ACTION_("add symmetry") : _ACTION_("group parts");
......
...@@ -48,14 +48,14 @@ class SymbolPartMoveAction : public SymbolPartsAction { ...@@ -48,14 +48,14 @@ class SymbolPartMoveAction : public SymbolPartsAction {
void move(const Vector2D& delta); void move(const Vector2D& delta);
private: private:
Vector2D delta; ///< How much to move Vector2D delta; ///< How much to move
Vector2D moved; ///< How much has been moved Vector2D moved; ///< How much has been moved
Vector2D min_pos, max_pos; ///< Bounding box of the thing we are moving Bounds bounds; ///< Bounding box of the thing we are moving
void movePart(SymbolPart& part); ///< Move a single part void movePart(SymbolPart& part); ///< Move a single part
public: public:
bool constrain; ///< Constrain movement? bool constrain; ///< Constrain movement?
int snap; ///< Snap to grid? int snap; ///< Snap to grid?
}; };
// ----------------------------------------------------------------------------- : Rotating symbol parts // ----------------------------------------------------------------------------- : Rotating symbol parts
......
...@@ -94,11 +94,36 @@ Vector2D& ControlPoint::getOther(WhichHandle wh) { ...@@ -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 // ----------------------------------------------------------------------------- : SymbolPart
void SymbolPart::calculateBounds() { void SymbolPart::updateBounds() {
min_pos = Vector2D::infinity(); calculateBounds(Vector2D(), Matrix2D(), true);
max_pos = -Vector2D::infinity();
} }
IMPLEMENT_REFLECTION(SymbolPart) { IMPLEMENT_REFLECTION(SymbolPart) {
...@@ -133,6 +158,23 @@ IMPLEMENT_REFLECTION_ENUM(SymbolShapeCombine) { ...@@ -133,6 +158,23 @@ IMPLEMENT_REFLECTION_ENUM(SymbolShapeCombine) {
VALUE_N("border", SYMBOL_COMBINE_BORDER); VALUE_N("border", SYMBOL_COMBINE_BORDER);
} }
template<typename T> 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) { IMPLEMENT_REFLECTION(SymbolShape) {
REFLECT_BASE(SymbolPart); REFLECT_BASE(SymbolPart);
REFLECT(combine); REFLECT(combine);
...@@ -141,21 +183,11 @@ IMPLEMENT_REFLECTION(SymbolShape) { ...@@ -141,21 +183,11 @@ IMPLEMENT_REFLECTION(SymbolShape) {
REFLECT_IF_READING { REFLECT_IF_READING {
// enforce constraints // enforce constraints
enforceConstraints(); enforceConstraints();
calculateBounds(); fix(tag,*this);
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();
}
} }
} }
SymbolShape::SymbolShape() SymbolShape::SymbolShape()
: combine(SYMBOL_COMBINE_OVERLAP), rotation_center(.5, .5) : combine(SYMBOL_COMBINE_OVERLAP), rotation_center(.5, .5)
{} {}
...@@ -182,13 +214,13 @@ void SymbolShape::enforceConstraints() { ...@@ -182,13 +214,13 @@ void SymbolShape::enforceConstraints() {
} }
} }
void SymbolShape::calculateBounds() { Bounds SymbolShape::calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity) {
min_pos = Vector2D::infinity(); Bounds bounds;
max_pos = -Vector2D::infinity();
Rotation rot(0);
for (int i = 0 ; i < (int)points.size() ; ++i) { 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 // ----------------------------------------------------------------------------- : SymbolSymmetry
...@@ -220,6 +252,32 @@ String SymbolSymmetry::expectedName() const { ...@@ -220,6 +252,32 @@ String SymbolSymmetry::expectedName() const {
+ String::Format(_(" (%d)"), copies); + 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) { IMPLEMENT_REFLECTION(SymbolSymmetry) {
REFLECT_BASE(SymbolPart); REFLECT_BASE(SymbolPart);
REFLECT(kind); REFLECT(kind);
...@@ -227,7 +285,6 @@ IMPLEMENT_REFLECTION(SymbolSymmetry) { ...@@ -227,7 +285,6 @@ IMPLEMENT_REFLECTION(SymbolSymmetry) {
REFLECT(center); REFLECT(center);
REFLECT(handle); REFLECT(handle);
REFLECT(parts); REFLECT(parts);
REFLECT_IF_READING calculateBoundsNonRec();
} }
// ----------------------------------------------------------------------------- : SymbolGroup // ----------------------------------------------------------------------------- : SymbolGroup
...@@ -257,30 +314,25 @@ bool SymbolGroup::isAncestor(const SymbolPart& that) const { ...@@ -257,30 +314,25 @@ bool SymbolGroup::isAncestor(const SymbolPart& that) const {
return false; return false;
} }
void SymbolGroup::calculateBounds() { Bounds SymbolGroup::calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity) {
FOR_EACH(p, parts) p->calculateBounds(); Bounds bounds;
calculateBoundsNonRec();
}
void SymbolGroup::calculateBoundsNonRec() {
min_pos = Vector2D::infinity();
max_pos = -Vector2D::infinity();
FOR_EACH(p, parts) { FOR_EACH(p, parts) {
min_pos = piecewise_min(min_pos, p->min_pos); bounds.update(p->calculateBounds(origin, m, is_identity));
max_pos = piecewise_max(max_pos, p->max_pos);
} }
if (is_identity) this->bounds = bounds;
return bounds;
} }
IMPLEMENT_REFLECTION(SymbolGroup) { IMPLEMENT_REFLECTION(SymbolGroup) {
REFLECT_BASE(SymbolPart); REFLECT_BASE(SymbolPart);
REFLECT(parts); REFLECT(parts);
REFLECT_IF_READING calculateBoundsNonRec();
} }
// ----------------------------------------------------------------------------- : Symbol // ----------------------------------------------------------------------------- : Symbol
IMPLEMENT_REFLECTION(Symbol) { IMPLEMENT_REFLECTION(Symbol) {
REFLECT(parts); REFLECT(parts);
REFLECT_IF_READING calculateBoundsNonRec(); REFLECT_IF_READING updateBounds();
} }
double Symbol::aspectRatio() const { double Symbol::aspectRatio() const {
...@@ -288,8 +340,8 @@ double Symbol::aspectRatio() const { ...@@ -288,8 +340,8 @@ double Symbol::aspectRatio() const {
// In each direction take the lowest one // In each direction take the lowest one
// This is at most 0.5 (if the symbol is just a line in the middle) // 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] // 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_x = min(0.4999, max(0., min(bounds.min.x, 1-bounds.max.x)));
double margin_y = min(0.4999, max(0., min(min_pos.y, 1-max_pos.y))); double margin_y = min(0.4999, max(0., min(bounds.min.y, 1-bounds.max.y)));
// The difference between these two, // The difference between these two,
// e.g. if the vertical margin is more then the horizontal one, the symbol is 'flat' // e.g. if the vertical margin is more then the horizontal one, the symbol is 'flat'
double delta = 2 * (margin_y - margin_x); double delta = 2 * (margin_y - margin_x);
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include <util/reflect.hpp> #include <util/reflect.hpp>
#include <util/action_stack.hpp> #include <util/action_stack.hpp>
#include <util/vector2d.hpp> #include <util/vector2d.hpp>
#include <util/real_point.hpp>
DECLARE_POINTER_TYPE(ControlPoint); DECLARE_POINTER_TYPE(ControlPoint);
DECLARE_POINTER_TYPE(SymbolPart); DECLARE_POINTER_TYPE(SymbolPart);
...@@ -105,6 +106,32 @@ class SelectedHandle { ...@@ -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 // ----------------------------------------------------------------------------- : SymbolPart
/// A part of a symbol, not necesserly a shape /// A part of a symbol, not necesserly a shape
...@@ -114,7 +141,7 @@ class SymbolPart : public IntrusivePtrVirtualBase { ...@@ -114,7 +141,7 @@ class SymbolPart : public IntrusivePtrVirtualBase {
String name; String name;
/// Position and size of the part. /// Position and size of the part.
/** this is the smallest axis aligned bounding box that fits around 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 /// Type of this part
virtual String typeName() const = 0; virtual String typeName() const = 0;
...@@ -137,8 +164,10 @@ class SymbolPart : public IntrusivePtrVirtualBase { ...@@ -137,8 +164,10 @@ class SymbolPart : public IntrusivePtrVirtualBase {
/** also true if this==that*/ /** also true if this==that*/
virtual bool isAncestor(const SymbolPart& that) const { return 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) /// Calculate the position and size of the part (bounds)
virtual void calculateBounds(); 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(); DECLARE_REFLECTION_VIRTUAL();
}; };
...@@ -189,8 +218,8 @@ class SymbolShape : public SymbolPart { ...@@ -189,8 +218,8 @@ class SymbolShape : public SymbolPart {
/// Enforce lock constraints /// Enforce lock constraints
void enforceConstraints(); void enforceConstraints();
/// Calculate the position and size of the part /// Calculate the position and size of the part using the given rotation matrix
virtual void calculateBounds(); virtual Bounds calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity);
DECLARE_REFLECTION(); DECLARE_REFLECTION();
}; };
...@@ -212,9 +241,7 @@ class SymbolGroup : public SymbolPart { ...@@ -212,9 +241,7 @@ class SymbolGroup : public SymbolPart {
virtual bool isAncestor(const SymbolPart& that) const; virtual bool isAncestor(const SymbolPart& that) const;
virtual void calculateBounds(); virtual Bounds calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity);
/// re-calculate the bounds, but not of the contained parts
void calculateBoundsNonRec();
DECLARE_REFLECTION(); DECLARE_REFLECTION();
}; };
...@@ -245,6 +272,7 @@ class SymbolSymmetry : public SymbolGroup { ...@@ -245,6 +272,7 @@ class SymbolSymmetry : public SymbolGroup {
virtual const SymbolSymmetry* isSymbolSymmetry() const { return this; } virtual const SymbolSymmetry* isSymbolSymmetry() const { return this; }
String expectedName() const; String expectedName() const;
virtual Bounds calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity);
DECLARE_REFLECTION(); DECLARE_REFLECTION();
}; };
......
...@@ -94,23 +94,28 @@ void segment_subdivide(const ControlPoint& p0, const ControlPoint& p1, const Vec ...@@ -94,23 +94,28 @@ void segment_subdivide(const ControlPoint& p0, const ControlPoint& p1, const Vec
// ----------------------------------------------------------------------------- : Bounds // ----------------------------------------------------------------------------- : 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); assert(p1.segment_after == p2.segment_before);
if (p1.segment_after == SEGMENT_LINE) { 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 { } 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); 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 // First of all, the corners should be in the bounding box
point_bounds(rot, p1.pos, min, max); Bounds bounds(r1);
point_bounds(rot, p2.pos, min, max); bounds.update(r4);
// Solve the derivative of the bezier curve to find its extremes // Solve the derivative of the bezier curve to find its extremes
// It's only a quadtratic equation :) // It's only a quadtratic equation :)
BezierCurve curve(p1,p2); BezierCurve curve(r1,r2,r3,r4);
double roots[4]; double roots[4];
UInt count; UInt count;
count = solve_quadratic(3*curve.a.x, 2*curve.b.x, curve.c.x, roots); 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 ...@@ -119,35 +124,24 @@ void bezier_bounds(const Rotation& rot, const ControlPoint& p1, const ControlPoi
for (UInt i = 0 ; i < count ; ++i) { for (UInt i = 0 ; i < count ; ++i) {
double t = roots[i]; double t = roots[i];
if (t >=0 && t <= 1) { 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) { Bounds line_bounds(const Vector2D& origin, const Matrix2D& m, const Vector2D& p1, const Vector2D& p2) {
point_bounds(rot, p1, min, max); Bounds bounds(origin + p1 * m);
point_bounds(rot, p2, min, max); 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 <min...max>?
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 // ----------------------------------------------------------------------------- : Point tests
// Is a point inside a symbol shape? // Is a point inside a symbol shape?
bool point_in_shape(const Vector2D& pos, const SymbolShape& shape) { bool point_in_shape(const Vector2D& pos, const SymbolShape& shape) {
// Step 1. compare bounding box of the part // 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 // Step 2. trace ray outward, count intersections
int count = 0; int count = 0;
......
...@@ -78,25 +78,19 @@ void segment_subdivide(const ControlPoint& p0, const ControlPoint& p1, const Vec ...@@ -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, * min is only changed if the minimum is smaller then the current value in min,
* max only if the maximum is larger. * 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. /// 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, /** min is only changed if the minimum is smaller then the current value in min,
* max only if the maximum is larger * 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 /// 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, /** min is only changed if the minimum is smaller then the current value in min,
* max only if the maximum is larger * max only if the maximum is larger
*/ */
void line_bounds(const Rotation& rot, const Vector2D& p1, const Vector2D& p2, Vector2D& min, Vector2D& max); Bounds line_bounds(const Vector2D& origin, const Matrix2D& m, const Vector2D& p1, const Vector2D& p2);
/// 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);
// ----------------------------------------------------------------------------- : Point tests // ----------------------------------------------------------------------------- : Point tests
......
...@@ -39,7 +39,7 @@ SymbolSelectEditor::SymbolSelectEditor(SymbolControl* control, bool rotate) ...@@ -39,7 +39,7 @@ SymbolSelectEditor::SymbolSelectEditor(SymbolControl* control, bool rotate)
handleShearY = wxBitmap(rotate_image(shear,90)); handleShearY = wxBitmap(rotate_image(shear,90));
handleCenter = wxBitmap(load_resource_image(_("handle_center"))); handleCenter = wxBitmap(load_resource_image(_("handle_center")));
// Make sure all parts have updated bounds // Make sure all parts have updated bounds
getSymbol()->calculateBounds(); getSymbol()->updateBounds();
resetActions(); resetActions();
} }
...@@ -87,7 +87,7 @@ void SymbolSelectEditor::drawHandles(DC& dc) { ...@@ -87,7 +87,7 @@ void SymbolSelectEditor::drawHandles(DC& dc) {
} }
void SymbolSelectEditor::drawHandle(DC& dc, int dx, int dy) { 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.x += 4 * dx;
p.y += 4 * dy; p.y += 4 * dy;
if (rotate) { if (rotate) {
...@@ -312,7 +312,7 @@ void SymbolSelectEditor::onMouseDrag (const Vector2D& from, const Vector2D& to, ...@@ -312,7 +312,7 @@ void SymbolSelectEditor::onMouseDrag (const Vector2D& from, const Vector2D& to,
if (rotate) { if (rotate) {
if (scaleX == 0 || scaleY == 0) { if (scaleX == 0 || scaleY == 0) {
// shear, center/fixed point on the opposite side // 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); addAction(shearAction);
} else { } else {
// rotate // rotate
...@@ -360,7 +360,7 @@ void SymbolSelectEditor::onMouseDrag (const Vector2D& from, const Vector2D& to, ...@@ -360,7 +360,7 @@ void SymbolSelectEditor::onMouseDrag (const Vector2D& from, const Vector2D& to,
// shear the selected parts // shear the selected parts
Vector2D delta = to-from; Vector2D delta = to-from;
delta = delta.mul(Vector2D(scaleY, scaleX)); delta = delta.mul(Vector2D(scaleY, scaleX));
delta = delta.div(maxV - minV); delta = delta.div(bounds.max - bounds.min);
// shearAction->constrain = ev.ControlDown(); // shearAction->constrain = ev.ControlDown();
shearAction->snap = snap(ev); shearAction->snap = snap(ev);
shearAction->move(delta); shearAction->move(delta);
...@@ -426,15 +426,8 @@ bool SymbolSelectEditor::isEditing() { ...@@ -426,15 +426,8 @@ bool SymbolSelectEditor::isEditing() {
// ----------------------------------------------------------------------------- : Other // ----------------------------------------------------------------------------- : 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) { 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); wxPoint mp = control.rotation.tr(mpos);
p.x = p.x + 4 * dx; p.x = p.x + 4 * dx;
p.y = p.y + 4 * dy; p.y = p.y + 4 * dy;
...@@ -460,11 +453,9 @@ double SymbolSelectEditor::angleTo(const Vector2D& pos) { ...@@ -460,11 +453,9 @@ double SymbolSelectEditor::angleTo(const Vector2D& pos) {
void SymbolSelectEditor::updateBoundingBox() { void SymbolSelectEditor::updateBoundingBox() {
// Find min and max coordinates // Find min and max coordinates
minV = Vector2D::infinity(); bounds = Bounds();
maxV = -Vector2D::infinity();
FOR_EACH(p, control.selected_parts.get()) { FOR_EACH(p, control.selected_parts.get()) {
minV = piecewise_min(minV, p->min_pos); bounds.update(p->bounds);
maxV = piecewise_max(maxV, p->max_pos);
} }
/* // Find rotation center /* // Find rotation center
center = Vector2D(0,0); center = Vector2D(0,0);
...@@ -475,7 +466,7 @@ void SymbolSelectEditor::updateBoundingBox() { ...@@ -475,7 +466,7 @@ void SymbolSelectEditor::updateBoundingBox() {
} }
center /= control.selected_parts.size(); center /= control.selected_parts.size();
*/ */
center = (minV + maxV) / 2; center = bounds.corner(0,0);
} }
void SymbolSelectEditor::resetActions() { void SymbolSelectEditor::resetActions() {
......
...@@ -71,7 +71,7 @@ class SymbolSelectEditor : public SymbolEditorBase { ...@@ -71,7 +71,7 @@ class SymbolSelectEditor : public SymbolEditorBase {
SymbolPartRotateAction* rotateAction; SymbolPartRotateAction* rotateAction;
SymbolPartShearAction* shearAction; SymbolPartShearAction* shearAction;
// Bounding box of selection // Bounding box of selection
Vector2D minV, maxV; Bounds bounds;
// Where is the rotation center? // Where is the rotation center?
Vector2D center; Vector2D center;
// What kind of clicking/dragging are we doing // What kind of clicking/dragging are we doing
...@@ -112,9 +112,6 @@ class SymbolSelectEditor : public SymbolEditorBase { ...@@ -112,9 +112,6 @@ class SymbolSelectEditor : public SymbolEditorBase {
/// Angle between center and pos /// Angle between center and pos
double angleTo(const Vector2D& 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 /// Update minV and maxV to be the bounding box of the selected_parts
/// Updates center to be the rotation center of the parts /// Updates center to be the rotation center of the parts
void updateBoundingBox(); void updateBoundingBox();
......
...@@ -116,9 +116,11 @@ bool SymbolPartsSelection::selectRect(const Vector2D& a, const Vector2D& b, cons ...@@ -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 SymbolPartsSelection::selectRect(const SymbolGroup& parent, const Vector2D& a, const Vector2D& b, const Vector2D& c) {
bool changes = false; bool changes = false;
Bounds ab(a); ab.update(b);
Bounds bc(b); bc.update(c);
FOR_EACH_CONST(p, parent.parts) { 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_ab = ab.contains(p->bounds);
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_bc = bc.contains(p->bounds);
if (in_ab != in_bc) { if (in_ab != in_bc) {
select(p, SELECT_TOGGLE); select(p, SELECT_TOGGLE);
changes = true; changes = true;
......
...@@ -151,7 +151,7 @@ void SymbolViewer::combineSymbolPart(DC& dc, const SymbolPart& part, bool& paint ...@@ -151,7 +151,7 @@ void SymbolViewer::combineSymbolPart(DC& dc, const SymbolPart& part, bool& paint
} }
} else if (const SymbolSymmetry* s = part.isSymbolSymmetry()) { } else if (const SymbolSymmetry* s = part.isSymbolSymmetry()) {
// Draw all parts, in reverse order (bottom to top), also draw rotated copies // 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; Matrix2D old_m = multiply;
Vector2D old_o = origin; Vector2D old_o = origin;
int copies = s->kind == SYMMETRY_REFLECTION ? s->copies / 2 * 2 : s->copies; 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 ...@@ -177,8 +177,7 @@ void SymbolViewer::combineSymbolPart(DC& dc, const SymbolPart& part, bool& paint
// = (p * rot - d * rot + d) * m + o // = (p * rot - d * rot + d) * m + o
// = p * rot * m + (d - d * rot) * m + o // = p * rot * m + (d - d * rot) * m + o
Matrix2D rot(cos(a),-sin(a), sin(a),cos(a)); Matrix2D rot(cos(a),-sin(a), sin(a),cos(a));
multiply.mx = rot.mx * old_m; multiply = rot * old_m;
multiply.my = rot.my * old_m;
origin = old_o + (s->center - s->center * rot) * old_m; origin = old_o + (s->center - s->center * rot) * old_m;
} else { } else {
// reflection // reflection
...@@ -192,8 +191,7 @@ void SymbolViewer::combineSymbolPart(DC& dc, const SymbolPart& part, bool& paint ...@@ -192,8 +191,7 @@ void SymbolViewer::combineSymbolPart(DC& dc, const SymbolPart& part, bool& paint
// = [ cos(a+b) sin(a+b) ! // = [ cos(a+b) sin(a+b) !
// ! sin(a+b) -cos(a+b) ] // ! sin(a+b) -cos(a+b) ]
Matrix2D rot(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 = rot * old_m;
multiply.my = rot.my * old_m;
origin = old_o + (s->center - s->center * rot) * old_m; origin = old_o + (s->center - s->center * rot) * old_m;
} }
// draw rotated copy // draw rotated copy
...@@ -366,7 +364,7 @@ void SymbolViewer::highlightPart(DC& dc, const SymbolGroup& group, HighlightStyl ...@@ -366,7 +364,7 @@ void SymbolViewer::highlightPart(DC& dc, const SymbolGroup& group, HighlightStyl
if (style == HIGHLIGHT_BORDER) { if (style == HIGHLIGHT_BORDER) {
dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.SetPen (wxPen(Color(255,0,0), 2)); 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) { FOR_EACH_CONST(part, group.parts) {
highlightPart(dc, *part, (HighlightStyle)(style | HIGHLIGHT_LESS)); highlightPart(dc, *part, (HighlightStyle)(style | HIGHLIGHT_LESS));
......
...@@ -96,6 +96,10 @@ class Vector2D { ...@@ -96,6 +96,10 @@ class Vector2D {
inline Vector2D normalized() const { inline Vector2D normalized() const {
return *this / length(); return *this / length();
} }
/// Angle between this vector and the x axis
inline double angle() const {
return atan2(y,x);
}
inline operator wxPoint() const { inline operator wxPoint() const {
return wxPoint(to_int(x), to_int(y)); return wxPoint(to_int(x), to_int(y));
...@@ -142,7 +146,7 @@ class Matrix2D { ...@@ -142,7 +146,7 @@ class Matrix2D {
public: public:
Vector2D mx, my; 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(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) {} inline Matrix2D(double a, double b, double c, double d) : mx(a,b), my(c,d) {}
}; };
...@@ -151,6 +155,10 @@ class Matrix2D { ...@@ -151,6 +155,10 @@ class Matrix2D {
inline Vector2D operator * (const Vector2D& a, const Matrix2D& m) { inline Vector2D operator * (const Vector2D& a, const Matrix2D& m) {
return Vector2D(dot(a,m.mx), dot(a,m.my)); 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 // ----------------------------------------------------------------------------- : EOF
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment