mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 04:57:00 -04:00
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
This commit is contained in:
+17
-24
@@ -32,14 +32,12 @@ SymbolPartsAction::SymbolPartsAction(const set<SymbolPartP>& parts)
|
||||
SymbolPartMoveAction::SymbolPartMoveAction(const set<SymbolPartP>& 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<SymbolPartP>& 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 set<Symb
|
||||
old_part_list.push_back(p);
|
||||
}
|
||||
}
|
||||
group->calculateBounds();
|
||||
group->updateBounds();
|
||||
}
|
||||
String GroupSymbolPartsAction::getName(bool to_undo) const {
|
||||
return group->isSymbolSymmetry() ? _ACTION_("add symmetry") : _ACTION_("group parts");
|
||||
|
||||
@@ -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
|
||||
|
||||
+86
-34
@@ -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<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) {
|
||||
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);
|
||||
|
||||
+36
-8
@@ -13,6 +13,7 @@
|
||||
#include <util/reflect.hpp>
|
||||
#include <util/action_stack.hpp>
|
||||
#include <util/vector2d.hpp>
|
||||
#include <util/real_point.hpp>
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user