Added the necessery classes to handle symmetry objects/mirrors in symbols; What used to be SymbolPart is now SymbolShape, SymbolPart is a base class.

This should also pave the way for grouping.

git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@526 0fc631ac-6414-0410-93d0-97cfa31319b6
This commit is contained in:
twanvl
2007-07-08 01:12:55 +00:00
parent 0deb8513fd
commit b46d979f9e
31 changed files with 616 additions and 375 deletions
+105 -74
View File
@@ -8,12 +8,19 @@
#include <data/action/symbol.hpp>
#include <data/action/symbol_part.hpp>
#include <util/error.hpp>
DECLARE_TYPEOF_COLLECTION(pair<SymbolPartP COMMA SymbolPartCombine>);
DECLARE_TYPEOF_COLLECTION(pair<SymbolPartP COMMA size_t >);
DECLARE_TYPEOF_COLLECTION(pair<SymbolShapeP COMMA SymbolShapeCombine>);
DECLARE_TYPEOF_COLLECTION(pair<SymbolPartP COMMA size_t >);
DECLARE_TYPEOF_COLLECTION(SymbolPartP);
DECLARE_TYPEOF_COLLECTION(ControlPointP);
// ----------------------------------------------------------------------------- : Utility
String action_name_for(const set<SymbolPartP>& parts, const String& action) {
return format_string(action, parts.size() == 1 ? _TYPE_("shape") : _TYPE_("shapes"));
}
// ----------------------------------------------------------------------------- : Moving symbol parts
SymbolPartMoveAction::SymbolPartMoveAction(const set<SymbolPartP>& parts)
@@ -23,23 +30,31 @@ SymbolPartMoveAction::SymbolPartMoveAction(const set<SymbolPartP>& parts)
, 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);
FOR_EACH(p, parts) {
if (SymbolShape* s = p->isSymbolShape()) {
min_pos = piecewise_min(min_pos, s->min_pos);
max_pos = piecewise_max(max_pos, s->max_pos);
}
}
}
String SymbolPartMoveAction::getName(bool to_undo) const {
return parts.size() == 1 ? _("Move shape") : _("Move shapes");
return action_name_for(parts, _ACTION_("move"));
}
void SymbolPartMoveAction::perform(bool to_undo) {
// move the points back
FOR_EACH(p, parts) {
p->min_pos -= moved;
p->max_pos -= moved;
FOR_EACH(pnt, p->points) {
pnt->pos -= moved;
if (SymbolShape* s = p->isSymbolShape()) {
s->min_pos -= moved;
s->max_pos -= moved;
FOR_EACH(pnt, s->points) {
pnt->pos -= moved;
}
} else if (SymbolSymmetry* s = p->isSymbolSymmetry()) {
s->center -= moved;
} else {
throw InternalError(_("Invalid symbol part type"));
}
}
moved = -moved;
@@ -49,15 +64,10 @@ 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 dd = d - moved;
Vector2D dd = d - moved; // move this much more
// Move each point by d
FOR_EACH(p, parts) {
p->min_pos += dd;
p->max_pos += dd;
FOR_EACH(pnt, p->points) {
pnt->pos += dd;
}
}
moved = -dd;
perform(false); // (ab)use perform to move by +dd
moved = d;
}
@@ -71,13 +81,19 @@ SymbolPartMatrixAction::SymbolPartMatrixAction(const set<SymbolPartP>& parts, co
void SymbolPartMatrixAction::transform(const Vector2D& mx, const Vector2D& my) {
// Transform each point
FOR_EACH(p, parts) {
FOR_EACH(pnt, p->points) {
pnt->pos = (pnt->pos - center).mul(mx,my) + center;
pnt->delta_before = pnt->delta_before.mul(mx,my);
pnt->delta_after = pnt->delta_after .mul(mx,my);
if (SymbolShape* s = p->isSymbolShape()) {
FOR_EACH(pnt, s->points) {
pnt->pos = (pnt->pos - center).mul(mx,my) + center;
pnt->delta_before = pnt->delta_before.mul(mx,my);
pnt->delta_after = pnt->delta_after .mul(mx,my);
}
// bounds change after transforming
s->calculateBounds();
} else if (SymbolSymmetry* s = p->isSymbolSymmetry()) {
s->handle = s->handle.mul(mx,my);
} else {
throw InternalError(_("Invalid symbol part type"));
}
// bounds change after transforming
p->calculateBounds();
}
}
@@ -89,7 +105,7 @@ SymbolPartRotateAction::SymbolPartRotateAction(const set<SymbolPartP>& parts, co
{}
String SymbolPartRotateAction::getName(bool to_undo) const {
return parts.size() == 1 ? _("Rotate shape") : _("Rotate shapes");
return action_name_for(parts, _ACTION_("rotate"));
}
void SymbolPartRotateAction::perform(bool to_undo) {
@@ -128,7 +144,7 @@ SymbolPartShearAction::SymbolPartShearAction(const set<SymbolPartP>& parts, cons
{}
String SymbolPartShearAction::getName(bool to_undo) const {
return parts.size() == 1 ? _("Shear shape") : _("Shear shapes");
return action_name_for(parts, _ACTION_("shear"));
}
void SymbolPartShearAction::perform(bool to_undo) {
@@ -169,89 +185,104 @@ SymbolPartScaleAction::SymbolPartScaleAction(const set<SymbolPartP>& parts, int
, snap(0)
{
// Find min and max coordinates
oldMin = Vector2D::infinity();
Vector2D oldMax = -Vector2D::infinity();
old_min = Vector2D( 1e6, 1e6);
Vector2D old_max (-1e6,-1e6);
FOR_EACH(p, parts) {
oldMin = piecewise_min(oldMin, p->min_pos);
oldMax = piecewise_max(oldMax, p->max_pos);
if (SymbolShape* s = p->isSymbolShape()) {
old_min = piecewise_min(old_min, s->min_pos);
old_max = piecewise_max(old_max, s->max_pos);
}
}
// new == old
newMin = newRealMin = oldMin;
newSize = newRealSize = oldSize = oldMax - oldMin;
new_min = new_real_min = old_min;
new_size = new_real_size = old_size = old_max - old_min;
}
String SymbolPartScaleAction::getName(bool to_undo) const {
return parts.size() == 1 ? _("Scale shape") : _("Scale shapes");
return action_name_for(parts, _ACTION_("scale"));
}
void SymbolPartScaleAction::perform(bool to_undo) {
swap(oldMin, newMin);
swap(oldSize, newSize);
swap(old_min, new_min);
swap(old_size, new_size);
transformAll();
}
void SymbolPartScaleAction::move(const Vector2D& deltaMin, const Vector2D& deltaMax) {
newRealMin += deltaMin;
newRealSize += deltaMax - deltaMin;
void SymbolPartScaleAction::move(const Vector2D& delta_min, const Vector2D& delta_max) {
new_real_min += delta_min;
new_real_size += delta_max - delta_min;
update();
}
void SymbolPartScaleAction::update() {
// Move each point so the range <oldMin...oldMax> maps to <newMin...newMax>
// we have already moved to the current <newMin...newMax>
Vector2D tmpMin = oldMin, tmpSize = oldSize; // the size before any scaling
oldMin = newMin; oldSize = newSize; // the size before this move
// Move each point so the range [old_min...old_max] maps to [new_min...new_max]
// we have already moved to the current [new_min...new_max]
Vector2D tmp_min = old_min, tmp_size = old_size; // the size before any scaling
old_min = new_min; old_size = new_size; // the size before this move
// the size after the move
newMin = newRealMin; newSize = newRealSize;
new_min = new_real_min; new_size = new_real_size;
if (constrain && scaleX != 0 && scaleY != 0) {
Vector2D scale = newSize.div(tmpSize);
Vector2D scale = new_size.div(tmp_size);
scale = constrain_vector(scale, true, true);
newSize = tmpSize.mul(scale);
newMin += (newRealSize - newSize).mul(Vector2D(scaleX == -1 ? 1 : 0, scaleY == -1 ? 1 : 0));
new_size = tmp_size.mul(scale);
new_min += (new_real_size - new_size).mul(Vector2D(scaleX == -1 ? 1 : 0, scaleY == -1 ? 1 : 0));
// TODO : snapping
} else if (snap >= 0) {
if (scaleX + scaleY < 0) {
newMin = snap_vector(newMin, snap);
newSize += newRealMin - newMin;
new_min = snap_vector(new_min, snap);
new_size += new_real_min - new_min;
} else {
Vector2D newMax = snap_vector(newMin + newSize, snap);
newSize = newMax - newMin;
Vector2D new_max = snap_vector(new_min + new_size, snap);
new_size = new_max - new_min;
}
}
// now move all points
transformAll();
// restore oldMin/Size
oldMin = tmpMin; oldSize = tmpSize;
// restore old_min/size
old_min = tmp_min; old_size = tmp_size;
}
void SymbolPartScaleAction::transformAll() {
Vector2D scale = newSize.div(oldSize);
Vector2D scale = new_size.div(old_size);
FOR_EACH(p, parts) {
p->min_pos = transform(p->min_pos);
p->max_pos = transform(p->max_pos);
// make sure that max >= min
if (p->min_pos.x > p->max_pos.x) swap(p->min_pos.x, p->max_pos.x);
if (p->min_pos.y > p->max_pos.y) swap(p->min_pos.y, p->max_pos.y);
// scale all points
FOR_EACH(pnt, p->points) {
pnt->pos = transform(pnt->pos);
// also scale handles
pnt->delta_before = pnt->delta_before.mul(scale);
pnt->delta_after = pnt->delta_after .mul(scale);
if (SymbolShape* s = p->isSymbolShape()) {
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
FOR_EACH(pnt, s->points) {
pnt->pos = transform(pnt->pos);
// also scale handles
pnt->delta_before = pnt->delta_before.mul(scale);
pnt->delta_after = pnt->delta_after .mul(scale);
}
} else if (SymbolSymmetry* s = p->isSymbolSymmetry()) {
throw "TODO";
} else {
throw InternalError(_("Invalid symbol part type"));
}
}
}
Vector2D SymbolPartScaleAction::transform(const Vector2D& v) {
// TODO: prevent div by 0
return (v - old_min).div(old_size).mul(new_size) + new_min;
}
// ----------------------------------------------------------------------------- : Change combine mode
CombiningModeAction::CombiningModeAction(const set<SymbolPartP>& parts, SymbolPartCombine mode) {
CombiningModeAction::CombiningModeAction(const set<SymbolPartP>& parts, SymbolShapeCombine mode) {
FOR_EACH(p, parts) {
this->parts.push_back(make_pair(p,mode));
if (p->isSymbolShape()) {
this->parts.push_back(make_pair(static_pointer_cast<SymbolShape>(p),mode));
}
}
}
String CombiningModeAction::getName(bool to_undo) const {
return _("Change combine mode");
return _ACTION_("change combine mode");
}
void CombiningModeAction::perform(bool to_undo) {
@@ -263,15 +294,15 @@ void CombiningModeAction::perform(bool to_undo) {
// ----------------------------------------------------------------------------- : Change name
SymbolPartNameAction::SymbolPartNameAction(const SymbolPartP& part, const String& name)
: part(part), partName(name)
: part(part), part_name(name)
{}
String SymbolPartNameAction::getName(bool to_undo) const {
return _("Change shape name");
return _ACTION_("change shape name");
}
void SymbolPartNameAction::perform(bool to_undo) {
swap(part->name, partName);
swap(part->name, part_name);
}
// ----------------------------------------------------------------------------- : Add symbol part
@@ -281,7 +312,7 @@ AddSymbolPartAction::AddSymbolPartAction(Symbol& symbol, const SymbolPartP& part
{}
String AddSymbolPartAction::getName(bool to_undo) const {
return _("Add ") + part->name;
return format_string(_ACTION_("add"), part->name);
}
void AddSymbolPartAction::perform(bool to_undo) {
@@ -308,7 +339,7 @@ RemoveSymbolPartsAction::RemoveSymbolPartsAction(Symbol& symbol, const set<Symbo
}
String RemoveSymbolPartsAction::getName(bool to_undo) const {
return removals.size() == 1 ? _("Remove shape") : _("Remove shapes");
return format_string(_ACTION_("remove parts"), removals.size() == 1 ? _TYPE_("shape") : _TYPE_("shapes"));
}
void RemoveSymbolPartsAction::perform(bool to_undo) {
@@ -346,7 +377,7 @@ DuplicateSymbolPartsAction::DuplicateSymbolPartsAction(Symbol& symbol, const set
}
String DuplicateSymbolPartsAction::getName(bool to_undo) const {
return duplications.size() == 1 ? _("Duplicate shape") : _("Duplicate shapes");
return format_string(_ACTION_("duplicate"), duplications.size() == 1 ? _TYPE_("shape") : _TYPE_("shapes"));
}
void DuplicateSymbolPartsAction::perform(bool to_undo) {
@@ -380,7 +411,7 @@ ReorderSymbolPartsAction::ReorderSymbolPartsAction(Symbol& symbol, size_t part_i
{}
String ReorderSymbolPartsAction::getName(bool to_undo) const {
return _("Reorder");
return _ACTION_("reorder parts");
}
void ReorderSymbolPartsAction::perform(bool to_undo) {
+15 -16
View File
@@ -63,7 +63,7 @@ class SymbolPartMatrixAction : public SymbolPartAction {
/// Perform the transformation using the given matrix
void transform(const Vector2D& mx, const Vector2D& my);
set<SymbolPartP> parts; ///< Parts to transform
set<SymbolPartP> parts; ///< Parts to transform
Vector2D center; ///< Center to transform around
};
@@ -122,22 +122,20 @@ class SymbolPartScaleAction : public SymbolPartAction {
virtual void perform(bool to_undo);
/// Change min and max coordinates
void move(const Vector2D& deltaMin, const Vector2D& deltaMax);
void move(const Vector2D& delta_min, const Vector2D& delta_max);
/// Update the action's effect
void update();
private:
set<SymbolPartP> parts; ///< Parts to scale
Vector2D oldMin, oldSize; ///< the original pos/size
Vector2D newRealMin, newRealSize; ///< the target pos/sizevoid shearBy(const Vector2D& shear)
Vector2D newMin, newSize; ///< the target pos/size after applying constrains
int scaleX, scaleY; ///< to what corner are we attached?
set<SymbolPartP> parts; ///< Parts to scale
Vector2D old_min, old_size; ///< the original pos/size
Vector2D new_real_min, new_real_size; ///< the target pos/sizevoid shearBy(const Vector2D& shear)
Vector2D new_min, new_size; ///< the target pos/size after applying constrains
int scaleX, scaleY; ///< to what corner are we attached?
/// Transform everything in the parts
void transformAll();
/// Transform a single vector
inline Vector2D transform(const Vector2D& v) {
return (v - oldMin).div(oldSize).mul(newSize) + newMin;
}
inline Vector2D transform(const Vector2D& v);
public:
bool constrain; ///< Constrain movement?
int snap; ///< Snap to grid?
@@ -148,13 +146,14 @@ class SymbolPartScaleAction : public SymbolPartAction {
/// Change the name of a symbol part
class CombiningModeAction : public SymbolPartListAction {
public:
CombiningModeAction(const set<SymbolPartP>& parts, SymbolPartCombine mode);
// All parts must be SymbolParts
CombiningModeAction(const set<SymbolPartP>& parts, SymbolShapeCombine mode);
virtual String getName(bool to_undo) const;
virtual void perform(bool to_undo);
private:
vector<pair<SymbolPartP,SymbolPartCombine> > parts; ///< Affected parts with new combining modes
vector<pair<SymbolShapeP,SymbolShapeCombine> > parts; ///< Affected parts with new combining modes
};
// ----------------------------------------------------------------------------- : Change name
@@ -168,8 +167,8 @@ class SymbolPartNameAction : public SymbolPartListAction {
virtual void perform(bool to_undo);
private:
SymbolPartP part; ///< Affected part
String partName; ///< New name
SymbolPartP part; ///< Affected part
String part_name; ///< New name
};
// ----------------------------------------------------------------------------- : Add symbol part
@@ -183,8 +182,8 @@ class AddSymbolPartAction : public SymbolPartListAction {
virtual void perform(bool to_undo);
private:
Symbol& symbol; ///< Symbol to add the part to
SymbolPartP part; ///< Part to add
Symbol& symbol; ///< Symbol to add the part to
SymbolPartP part; ///< Part to add
};
// ----------------------------------------------------------------------------- : Remove symbol part
+36 -32
View File
@@ -80,6 +80,10 @@ Vector2D constrain_snap_vector_offset(const Vector2D& off1, const Vector2D& off2
return dd;
}
String action_name_for(const set<ControlPointP>& points, const String& action) {
return format_string(action, points.size() == 1 ? _TYPE_("point") : _TYPE_("points"));
}
// ----------------------------------------------------------------------------- : Move control point
@@ -95,7 +99,7 @@ ControlPointMoveAction::ControlPointMoveAction(const set<ControlPointP>& points)
}
String ControlPointMoveAction::getName(bool to_undo) const {
return points.size() == 1 ? _("Move point") : _("Move points");
return action_name_for(points, _ACTION_("move"));
}
void ControlPointMoveAction::perform(bool to_undo) {
@@ -126,7 +130,7 @@ HandleMoveAction::HandleMoveAction(const SelectedHandle& handle)
{}
String HandleMoveAction::getName(bool to_undo) const {
return _("Move handle");
return _ACTION_("move handle");
}
void HandleMoveAction::perform(bool to_undo) {
@@ -171,8 +175,8 @@ SegmentModeAction::SegmentModeAction(const ControlPointP& p1, const ControlPoint
}
String SegmentModeAction::getName(bool to_undo) const {
SegmentMode mode = to_undo ? point1.point->segment_after : point1.other.segment_after;
if (mode == SEGMENT_LINE) return _("Convert to line");
else return _("Convert to curve");
if (mode == SEGMENT_LINE) return _ACTION_("convert to line");
else return _ACTION_("convert to curve");
}
void SegmentModeAction::perform(bool to_undo) {
@@ -191,7 +195,7 @@ LockModeAction::LockModeAction(const ControlPointP& p, LockMode lock)
}
String LockModeAction::getName(bool to_undo) const {
return _("Lock point");
return _ACTION_("lock point");
}
void LockModeAction::perform(bool to_undo) {
@@ -206,7 +210,7 @@ CurveDragAction::CurveDragAction(const ControlPointP& point1, const ControlPoint
{}
String CurveDragAction::getName(bool to_undo) const {
return _("Move curve");
return _ACTION_("move curve");
}
void CurveDragAction::perform(bool to_undo) {
@@ -239,12 +243,12 @@ void CurveDragAction::move(const Vector2D& delta, double t) {
// ----------------------------------------------------------------------------- : Add control point
ControlPointAddAction::ControlPointAddAction(const SymbolPartP& part, UInt insert_after, double t)
: part(part)
ControlPointAddAction::ControlPointAddAction(const SymbolShapeP& shape, UInt insert_after, double t)
: shape(shape)
, new_point(new ControlPoint())
, insert_after(insert_after)
, point1(part->getPoint(insert_after))
, point2(part->getPoint(insert_after + 1))
, point1(shape->getPoint(insert_after))
, point2(shape->getPoint(insert_after + 1))
{
// calculate new point
if (point1.other.segment_after == SEGMENT_CURVE) {
@@ -265,14 +269,14 @@ ControlPointAddAction::ControlPointAddAction(const SymbolPartP& part, UInt inser
}
String ControlPointAddAction::getName(bool to_undo) const {
return _("Add control point");
return _ACTION_("add control point");
}
void ControlPointAddAction::perform(bool to_undo) {
if (to_undo) { // remove the point
part->points.erase( part->points.begin() + insert_after + 1);
shape->points.erase( shape->points.begin() + insert_after + 1);
} else {
part->points.insert(part->points.begin() + insert_after + 1, new_point);
shape->points.insert(shape->points.begin() + insert_after + 1, new_point);
}
// update points before/after
point1.perform();
@@ -291,24 +295,24 @@ double ssqrt(double x) {
// Remove a single control point
class SinglePointRemoveAction : public Action, public IntrusivePtrBase<SinglePointRemoveAction> {
public:
SinglePointRemoveAction(const SymbolPartP& part, UInt position);
SinglePointRemoveAction(const SymbolShapeP& shape, UInt position);
virtual String getName(bool to_undo) const { return _("Delete point"); }
virtual void perform(bool to_undo);
private:
SymbolPartP part;
SymbolShapeP shape;
UInt position;
ControlPointP point; ///< Removed point
ControlPointUpdate point1, point2; ///< Points before/after
};
SinglePointRemoveAction::SinglePointRemoveAction(const SymbolPartP& part, UInt position)
: part(part)
SinglePointRemoveAction::SinglePointRemoveAction(const SymbolShapeP& shape, UInt position)
: shape(shape)
, position(position)
, point (part->getPoint(position))
, point1(part->getPoint(position - 1))
, point2(part->getPoint(position + 1))
, point (shape->getPoint(position))
, point1(shape->getPoint(position - 1))
, point2(shape->getPoint(position + 1))
{
if (point1.other.segment_after == SEGMENT_CURVE || point2.other.segment_before == SEGMENT_CURVE) {
// try to preserve curve
@@ -359,10 +363,10 @@ SinglePointRemoveAction::SinglePointRemoveAction(const SymbolPartP& part, UInt p
void SinglePointRemoveAction::perform(bool to_undo) {
if (to_undo) {
// reinsert the point
part->points.insert(part->points.begin() + position, point);
shape->points.insert(shape->points.begin() + position, point);
} else {
// remove the point
part->points.erase( part->points.begin() + position);
shape->points.erase( shape->points.begin() + position);
}
// update points around removed point
point1.perform();
@@ -373,12 +377,12 @@ DECLARE_POINTER_TYPE(SinglePointRemoveAction);
DECLARE_TYPEOF_COLLECTION(SinglePointRemoveActionP);
// Remove a set of points from a symbol part.
// Remove a set of points from a symbol shape.
// Internally represented as a list of Single Point Remove Actions.
// Not all points mat be removed, at least two points must remain.
class ControlPointRemoveAction : public Action {
public:
ControlPointRemoveAction(const SymbolPartP& part, const set<ControlPointP>& toDelete);
ControlPointRemoveAction(const SymbolShapeP& shape, const set<ControlPointP>& to_delete);
virtual String getName(bool to_undo) const;
virtual void perform(bool to_undo);
@@ -387,20 +391,20 @@ class ControlPointRemoveAction : public Action {
vector<SinglePointRemoveActionP> removals;
};
ControlPointRemoveAction::ControlPointRemoveAction(const SymbolPartP& part, const set<ControlPointP>& toDelete) {
ControlPointRemoveAction::ControlPointRemoveAction(const SymbolShapeP& shape, const set<ControlPointP>& to_delete) {
int index = 0;
// find points to remove, in reverse order
FOR_EACH(point, part->points) {
if (toDelete.find(point) != toDelete.end()) {
FOR_EACH(point, shape->points) {
if (to_delete.find(point) != to_delete.end()) {
// remove this point
removals.push_back(new_intrusive2<SinglePointRemoveAction>(part, index));
removals.push_back(new_intrusive2<SinglePointRemoveAction>(shape, index));
}
++index;
}
}
String ControlPointRemoveAction::getName(bool to_undo) const {
return removals.size() == 1 ? _("Delete point") : _("Delete points");
return removals.size() == 1 ? _ACTION_("delete point") : _ACTION_("delete points");
}
void ControlPointRemoveAction::perform(bool to_undo) {
@@ -414,12 +418,12 @@ void ControlPointRemoveAction::perform(bool to_undo) {
}
Action* controlPointRemoveAction(const SymbolPartP& part, const set<ControlPointP>& toDelete) {
if (part->points.size() - toDelete.size() < 2) {
Action* control_point_remove_action(const SymbolShapeP& shape, const set<ControlPointP>& to_delete) {
if (shape->points.size() - to_delete.size() < 2) {
// TODO : remove part?
//new_intrusive<ControlPointRemoveAllAction>(part);
return 0; // no action
} else {
return new ControlPointRemoveAction(part, toDelete);
return new ControlPointRemoveAction(shape, to_delete);
}
}
+9 -9
View File
@@ -9,7 +9,7 @@
/** @file data/action/symbol_part.hpp
*
* Actions operating on the insides of SymbolParts (ControlPoints and the like).
* Actions operating on the insides of SymbolParts/SymbolShapes (ControlPoints and the like).
*/
// ----------------------------------------------------------------------------- : Includes
@@ -147,11 +147,11 @@ class CurveDragAction : public SegmentModeAction {
// ----------------------------------------------------------------------------- : Add control point
/// Insert a new point in a symbol part
/// Insert a new point in a symbol shape
class ControlPointAddAction : public Action {
public:
/// Insert a new point in part, after position insertAfter_, at the time t on the segment
ControlPointAddAction(const SymbolPartP& part, UInt insert_after, double t);
/// Insert a new point in shape, after position insertAfter_, at the time t on the segment
ControlPointAddAction(const SymbolShapeP& shape, UInt insert_after, double t);
virtual String getName(bool to_undo) const;
virtual void perform(bool to_undo);
@@ -159,17 +159,17 @@ class ControlPointAddAction : public Action {
inline ControlPointP getNewPoint() const { return new_point; }
private:
SymbolPartP part; ///< SymbolPart we are in
ControlPointP new_point; ///< The point to insert
SymbolShapeP shape; ///< SymbolShape we are in
ControlPointP new_point; ///< The point to insert
UInt insert_after; ///< Insert after index .. in the array
ControlPointUpdate point1, point2; ///< Update the points around the new point
};
// ----------------------------------------------------------------------------- : Remove control point
/// Action that removes any number of points from a symbol part
/// TODO: If less then 3 points are left removes the entire part?
Action* controlPointRemoveAction(const SymbolPartP& part, const set<ControlPointP>& toDelete);
/// Action that removes any number of points from a symbol shape
/// TODO: If less then 3 points are left removes the entire shape?
Action* control_point_remove_action(const SymbolShapeP& shape, const set<ControlPointP>& to_delete);
// ----------------------------------------------------------------------------- : EOF
+77 -75
View File
@@ -121,7 +121,7 @@ struct ImageData {
}
};
bool find_symbol_part_start(const ImageData& data, int& x_out, int& y_out) {
bool find_symbol_shape_start(const ImageData& data, int& x_out, int& y_out) {
for (int x = 0 ; x < data.width ; ++x) {
for (int y = 0 ; y < data.height ; ++y) {
if (data(x, y) == FULL && data(x, y-1) == EMPTY) {
@@ -136,13 +136,13 @@ bool find_symbol_part_start(const ImageData& data, int& x_out, int& y_out) {
return false;
}
SymbolPartP read_symbol_part(const ImageData& data) {
SymbolShapeP read_symbol_shape(const ImageData& data) {
// find start point
int xs, ys;
if (!find_symbol_part_start(data, xs, ys)) return SymbolPartP();
if (!find_symbol_shape_start(data, xs, ys)) return SymbolShapeP();
data(xs, ys) |= MARKED;
SymbolPartP part(new SymbolPart);
SymbolShapeP shape(new SymbolShape);
// walk around, clockwise
xs += 1; // start right of the found point, otherwise last_move might think we came from above
@@ -178,12 +178,12 @@ SymbolPartP read_symbol_part(const ImageData& data) {
throw InternalError(_("in the ground/air"));
}
// add to part and place a mark
part->points.push_back(new_intrusive2<ControlPoint>(
// add to shape and place a mark
shape->points.push_back(new_intrusive2<ControlPoint>(
double(x) / data.width,
double(y) / data.height
));
if (x > old_x) data(old_x, y) |= MARKED; // mark when moving right -> only mark the top of the part
if (x > old_x) data(old_x, y) |= MARKED; // mark when moving right -> only mark the top of the shape
last_move = (x + y) - (old_x + old_y);
old_x = x;
old_y = y;
@@ -191,11 +191,11 @@ SymbolPartP read_symbol_part(const ImageData& data) {
// are we on the inside or the outside?
if (data(x-2,y-1) & FULL) {
part->combine = PART_SUBTRACT;
shape->combine = SYMBOL_COMBINE_SUBTRACT;
} else {
part->combine = PART_MERGE;
shape->combine = SYMBOL_COMBINE_MERGE;
}
return part;
return shape;
}
@@ -204,13 +204,13 @@ SymbolP image_to_symbol(Image& img) {
// 1. threshold the image
greyscale(img);
threshold(img.GetData(), w, h);
// 2. read as many symbol parts as we can
// 2. read as many symbol shapes as we can
ImageData data = {w,h,img.GetData()};
SymbolP symbol(new Symbol);
while (true) {
SymbolPartP part = read_symbol_part(data);
if (!part) break;
symbol->parts.push_back(part);
SymbolShapeP shape = read_symbol_shape(data);
if (!shape) break;
symbol->parts.push_back(shape);
}
reverse(symbol->parts.begin(), symbol->parts.end());
return symbol;
@@ -238,11 +238,11 @@ SymbolP import_symbol(Image& img) {
/// Finds corners, marks corners as LOCK_FREE, non-corners as LOCK_DIR
/** A corner is a point that has an angle between tangent greater then a treshold
*/
void mark_corners(SymbolPart& part) {
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
ControlPoint& current = *part.getPoint(i);
Vector2D before = .6 * part.getPoint(i-1)->pos + .2 * part.getPoint(i-2)->pos + .1 * part.getPoint(i-3)->pos + .1 * part.getPoint(i-4)->pos;
Vector2D after = .6 * part.getPoint(i+1)->pos + .2 * part.getPoint(i+2)->pos + .1 * part.getPoint(i+3)->pos + .1 * part.getPoint(i+4)->pos;
void mark_corners(SymbolShape& shape) {
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
ControlPoint& current = *shape.getPoint(i);
Vector2D before = .6 * shape.getPoint(i-1)->pos + .2 * shape.getPoint(i-2)->pos + .1 * shape.getPoint(i-3)->pos + .1 * shape.getPoint(i-4)->pos;
Vector2D after = .6 * shape.getPoint(i+1)->pos + .2 * shape.getPoint(i+2)->pos + .1 * shape.getPoint(i+3)->pos + .1 * shape.getPoint(i+4)->pos;
before = (before - current.pos).normalized();
after = (after - current.pos).normalized();
if (before.dot(after) >= -0.25f) {
@@ -271,10 +271,10 @@ void mark_corners(SymbolPart& part) {
* Where these two lines (one for each corner) intersect,
* is the merged corner. If it is too far away, don't merge
*/
void merge_corners(SymbolPart& part) {
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
ControlPoint& cur = *part.getPoint(i);
ControlPoint& prev = *part.getPoint(i - 1);
void merge_corners(SymbolShape& shape) {
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
ControlPoint& cur = *shape.getPoint(i);
ControlPoint& prev = *shape.getPoint(i - 1);
if (prev.lock != LOCK_FREE || cur.lock != LOCK_FREE) continue;
// step 1. find tangent lines: try tangent lines to the first point, the second, etc.
// and take the one that has the largest angle with ab, i.e. the smallest dot,
@@ -283,8 +283,8 @@ void merge_corners(SymbolPart& part) {
double min_a_dot = 1e100, min_b_dot = 1e100;
Vector2D a, b;
for (int j = 0 ; j < 4 ; ++j) {
Vector2D a_ = (part.getPoint(i-j-1)->pos - prev.pos).normalized();
Vector2D b_ = (part.getPoint(i+j)->pos - cur.pos).normalized();
Vector2D a_ = (shape.getPoint(i-j-1)->pos - prev.pos).normalized();
Vector2D b_ = (shape.getPoint(i+j)->pos - cur.pos).normalized();
double a_dot = a_.dot(ab);
double b_dot = -b_.dot(ab);
if (a_dot < min_a_dot) {
@@ -306,22 +306,22 @@ void merge_corners(SymbolPart& part) {
// if so, then the intersection point is the merged point
if (t >= 0 && t < 20.0) {
prev.pos += a * -t;
part.points.erase(part.points.begin() + i);
shape.points.erase(shape.points.begin() + i);
i -= 1;
}
}
}
/// Avarage/'blur' a symbol part
void avarage(SymbolPart& part) {
/// Avarage/'blur' a symbol shape
void avarage(SymbolShape& shape) {
// create a copy of the points
vector<Vector2D> old_points;
FOR_EACH(p, part.points) {
FOR_EACH(p, shape.points) {
old_points.push_back(p->pos);
}
// avarage points
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
ControlPoint& p = *part.getPoint(i);
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
ControlPoint& p = *shape.getPoint(i);
if (p.lock == LOCK_DIR) {
p.pos = .25 * old_points[mod(i-1, old_points.size())]
+ .50 * p.pos
@@ -330,29 +330,29 @@ void avarage(SymbolPart& part) {
}
}
/// Convert a symbol part to curves
void convert_to_curves(SymbolPart& part) {
/// Convert a symbol shape to curves
void convert_to_curves(SymbolShape& shape) {
// mark all segments as curves
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
ControlPoint& cur = *part.getPoint(i);
ControlPoint& next = *part.getPoint(i + 1);
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
ControlPoint& cur = *shape.getPoint(i);
ControlPoint& next = *shape.getPoint(i + 1);
cur.segment_after = SEGMENT_CURVE;
cur.segment_before = SEGMENT_CURVE;
cur.delta_after = (next.pos - cur.pos) / 3.0;
next.delta_before = (cur.pos - next.pos) / 3.0;
}
// make the curves smooth by enforcing direction constraints
FOR_EACH(p, part.points) {
FOR_EACH(p, shape.points) {
p->onUpdateLock();
}
}
/// Convert almost straight curves in a symbol part to lines
void straighten(SymbolPart& part) {
/// Convert almost straight curves in a symbol shape to lines
void straighten(SymbolShape& shape) {
const double treshold = 0.2;
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
ControlPoint& cur = *part.getPoint(i);
ControlPoint& next = *part.getPoint(i + 1);
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
ControlPoint& cur = *shape.getPoint(i);
ControlPoint& next = *shape.getPoint(i + 1);
Vector2D ab = (next.pos - cur.pos).normalized();
Vector2D aa = cur.delta_after.normalized();
Vector2D bb = next.delta_before.normalized();
@@ -368,37 +368,37 @@ void straighten(SymbolPart& part) {
}
/// Remove unneeded points between straight lines
void merge_lines(SymbolPart& part) {
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
ControlPoint& cur = *part.getPoint(i);
void merge_lines(SymbolShape& shape) {
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
ControlPoint& cur = *shape.getPoint(i);
if (cur.segment_before != cur.segment_after) continue;
Vector2D a = part.getPoint(i-1)->pos, b = cur.pos, c = part.getPoint(i+1)->pos;
Vector2D a = shape.getPoint(i-1)->pos, b = cur.pos, c = shape.getPoint(i+1)->pos;
Vector2D ab = (a-b).normalized();
Vector2D bc = (b-c).normalized();
double angle_len = abs( atan2(ab.x,ab.y) - atan2(bc.x,bc.y)) * (a-c).lengthSqr();
bool keep = angle_len >= .0001;
if (!keep) {
part.points.erase(part.points.begin() + i);
shape.points.erase(shape.points.begin() + i);
i -= 1;
}
}
}
double cost_of_point_removal(SymbolPart& part, int i);
void remove_point(SymbolPart& part, int i);
double cost_of_point_removal(SymbolShape& shape, int i);
void remove_point(SymbolShape& shape, int i);
/// Simplify a symbol part by removing points
/// Simplify a symbol shape by removing points
/** Always remove the point with the lowest cost,
* stop when the cost becomes too high
*/
void remove_points(SymbolPart& part) {
void remove_points(SymbolShape& shape) {
const double treshold = 0.0002; // maximum cost
while (true) {
// Find the point with the lowest cost of removal
int best = -1;
double best_cost = 1e100;
for (int i = 0 ; (size_t)i < part.points.size() ; ++i) {
double cost = cost_of_point_removal(part, i);
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
double cost = cost_of_point_removal(shape, i);
if (cost < best_cost) {
best_cost = cost;
best = i;
@@ -406,14 +406,14 @@ void remove_points(SymbolPart& part) {
}
if (best_cost > treshold) break;
// ... and remove it
remove_point(part, best);
remove_point(shape, best);
}
}
/// Cost of removing point i from a symbol part
double cost_of_point_removal(SymbolPart& part, int i) {
ControlPoint& cur = *part.getPoint(i);
ControlPoint& prev = *part.getPoint(i-1);
ControlPoint& next = *part.getPoint(i+1);
/// Cost of removing point i from a symbol shape
double cost_of_point_removal(SymbolShape& shape, int i) {
ControlPoint& cur = *shape.getPoint(i);
ControlPoint& prev = *shape.getPoint(i-1);
ControlPoint& next = *shape.getPoint(i+1);
if (cur.lock != LOCK_DIR) return 1e100; // don't remove corners
Vector2D before = cur.delta_before;
@@ -430,15 +430,15 @@ double cost_of_point_removal(SymbolPart& part, int i) {
BezierCurve c(prev.pos, prev.pos + after0, next.pos + before2, next.pos);
double t = bl/totl;
Vector2D np = cur.pos - c.pointAt(t);
// cost is distance to new point * length of line ~= area added/removed from part
// cost is distance to new point * length of line ~= area added/removed from shape
return np.length() * ac.length();
}
/// Remove a point from a bezier curve
/** See SinglePointRemoveAction for algorithm */
void remove_point(SymbolPart& part, int i) {
ControlPoint& cur = *part.getPoint(i);
ControlPoint& prev = *part.getPoint(i-1);
ControlPoint& next = *part.getPoint(i+1);
void remove_point(SymbolShape& shape, int i) {
ControlPoint& cur = *shape.getPoint(i);
ControlPoint& prev = *shape.getPoint(i-1);
ControlPoint& next = *shape.getPoint(i+1);
Vector2D before = cur.delta_before;
Vector2D after = cur.delta_after;
// Based on SinglePointRemoveAction
@@ -449,24 +449,26 @@ void remove_point(SymbolPart& part, int i) {
prev.delta_after *= totl / bl;
next.delta_before *= totl / al;
// remove
part.points.erase(part.points.begin() + i);
shape.points.erase(shape.points.begin() + i);
}
void simplify_symbol_part(SymbolPart& part) {
mark_corners(part);
merge_corners(part);
void simplify_symbol_shape(SymbolShape& shape) {
mark_corners(shape);
merge_corners(shape);
for (int i = 0 ; i < 3 ; ++i) {
avarage(part);
avarage(shape);
}
convert_to_curves(part);
remove_points(part);
straighten(part);
merge_lines(part);
convert_to_curves(shape);
remove_points(shape);
straighten(shape);
merge_lines(shape);
}
void simplify_symbol(Symbol& symbol) {
FOR_EACH(p, symbol.parts) {
simplify_symbol_part(*p);
FOR_EACH(pb, symbol.parts) {
if (SymbolShape* p = pb->isSymbolShape()) {
simplify_symbol_shape(*p);
}
}
}
+1 -1
View File
@@ -31,7 +31,7 @@ SymbolP image_to_symbol(Image& img);
void simplify_symbol(Symbol&);
/// Simplify a symbol parts, i.e. use bezier curves instead of lots of lines
void simplify_symbol_part(SymbolPart&);
void simplify_symbol_shape(SymbolShape&);
// ----------------------------------------------------------------------------- : EOF
#endif
+80 -18
View File
@@ -10,7 +10,6 @@
#include <script/to_value.hpp>
#include <gfx/bezier.hpp>
DECLARE_TYPEOF_COLLECTION(SymbolPartP);
DECLARE_TYPEOF_COLLECTION(ControlPointP);
// ----------------------------------------------------------------------------- : ControlPoint
@@ -94,17 +93,39 @@ Vector2D& ControlPoint::getOther(WhichHandle wh) {
// ----------------------------------------------------------------------------- : SymbolPart
IMPLEMENT_REFLECTION_ENUM(SymbolPartCombine) {
VALUE_N("overlap", PART_OVERLAP);
VALUE_N("merge", PART_MERGE);
VALUE_N("subtract", PART_SUBTRACT);
VALUE_N("intersection", PART_INTERSECTION);
VALUE_N("difference", PART_DIFFERENCE);
VALUE_N("border", PART_BORDER);
IMPLEMENT_REFLECTION(SymbolPart) {
REFLECT_IF_NOT_READING {
String type = typeName();
REFLECT(type);
}
REFLECT(name);
}
IMPLEMENT_REFLECTION(SymbolPart) {
REFLECT(name);
template <>
SymbolPartP read_new<SymbolPart>(Reader& reader) {
// there must be a type specified
String type;
reader.handle(_("type"), type);
if (type == _("shape") || type.empty()) return new_intrusive<SymbolShape>();
else if (type == _("symmetry")) return new_intrusive<SymbolSymmetry>();
else {
throw ParseError(_("Unsupported symbol part type: '") + type + _("'"));
}
}
// ----------------------------------------------------------------------------- : SymbolShape
IMPLEMENT_REFLECTION_ENUM(SymbolShapeCombine) {
VALUE_N("overlap", SYMBOL_COMBINE_OVERLAP);
VALUE_N("merge", SYMBOL_COMBINE_MERGE);
VALUE_N("subtract", SYMBOL_COMBINE_SUBTRACT);
VALUE_N("intersection", SYMBOL_COMBINE_INTERSECTION);
VALUE_N("difference", SYMBOL_COMBINE_DIFFERENCE);
VALUE_N("border", SYMBOL_COMBINE_BORDER);
}
IMPLEMENT_REFLECTION(SymbolShape) {
REFLECT_BASE(SymbolPart);
REFLECT(combine);
REFLECT(points);
// Fixes after reading
@@ -126,12 +147,16 @@ IMPLEMENT_REFLECTION(SymbolPart) {
}
}
SymbolPart::SymbolPart()
: combine(PART_OVERLAP), rotation_center(.5, .5)
SymbolShape::SymbolShape()
: combine(SYMBOL_COMBINE_OVERLAP), rotation_center(.5, .5)
{}
SymbolPartP SymbolPart::clone() const {
SymbolPartP part = new_intrusive1<SymbolPart>(*this);
String SymbolShape::typeName() const {
return _("shape");
}
SymbolPartP SymbolShape::clone() const {
SymbolShapeP part(new SymbolShape(*this));
// also clone the control points
FOR_EACH(p, part->points) {
p = new_intrusive1<ControlPoint>(*p);
@@ -139,7 +164,7 @@ SymbolPartP SymbolPart::clone() const {
return part;
}
void SymbolPart::enforceConstraints() {
void SymbolShape::enforceConstraints() {
for (int i = 0 ; i < (int)points.size() ; ++i) {
ControlPointP p1 = getPoint(i);
ControlPointP p2 = getPoint(i + 1);
@@ -148,7 +173,7 @@ void SymbolPart::enforceConstraints() {
}
}
void SymbolPart::calculateBounds() {
void SymbolShape::calculateBounds() {
min_pos = Vector2D::infinity();
max_pos = -Vector2D::infinity();
for (int i = 0 ; i < (int)points.size() ; ++i) {
@@ -156,6 +181,43 @@ void SymbolPart::calculateBounds() {
}
}
// ----------------------------------------------------------------------------- : SymbolSymmetry
IMPLEMENT_REFLECTION_ENUM(SymbolSymmetryType) {
VALUE_N("rotation", SYMMETRY_ROTATION);
VALUE_N("reflection", SYMMETRY_REFLECTION);
}
SymbolSymmetry::SymbolSymmetry()
: kind(SYMMETRY_ROTATION), copies(2)
{}
String SymbolSymmetry::typeName() const {
return _("symmetry");
}
SymbolPartP SymbolSymmetry::clone() const {
return new_intrusive1<SymbolSymmetry>(*this);
}
IMPLEMENT_REFLECTION(SymbolSymmetry) {
REFLECT_BASE(SymbolPart);
REFLECT(kind);
REFLECT(copies);
REFLECT(center);
REFLECT(handle);
// Fixes after reading
REFLECT_IF_READING {
if (name.empty()) {
if (kind == SYMMETRY_REFLECTION) {
name = _("Mirror");
} else {
name = _("Symmetry");
}
}
}
}
// ----------------------------------------------------------------------------- : Symbol
IMPLEMENT_REFLECTION(Symbol) {
@@ -165,8 +227,8 @@ IMPLEMENT_REFLECTION(Symbol) {
// ----------------------------------------------------------------------------- : Default symbol
// A default symbol part, a square, moved by d
SymbolPartP default_symbol_part(double d) {
SymbolPartP part = new_intrusive<SymbolPart>();
SymbolShapeP default_symbol_part(double d) {
SymbolShapeP part = new_intrusive<SymbolShape>();
part->points.push_back(new_intrusive2<ControlPoint>(d + .2, d + .2));
part->points.push_back(new_intrusive2<ControlPoint>(d + .2, d + .8));
part->points.push_back(new_intrusive2<ControlPoint>(d + .8, d + .8));
+74 -16
View File
@@ -16,6 +16,8 @@
DECLARE_POINTER_TYPE(ControlPoint);
DECLARE_POINTER_TYPE(SymbolPart);
DECLARE_POINTER_TYPE(SymbolShape);
DECLARE_POINTER_TYPE(SymbolSymmetry);
DECLARE_POINTER_TYPE(Symbol);
// ----------------------------------------------------------------------------- : ControlPoint
@@ -43,7 +45,7 @@ enum WhichHandle
, HANDLE_AFTER
};
/// A control point (corner) of a SymbolPart (polygon/bezier-gon)
/// A control point (corner) of a SymbolShape (polygon/bezier-gon)
class ControlPoint : public IntrusivePtrBase<ControlPoint> {
public:
Vector2D pos; ///< position of the control point itself
@@ -104,14 +106,41 @@ class SelectedHandle {
// ----------------------------------------------------------------------------- : SymbolPart
/// A part of a symbol, not necesserly a shape
class SymbolPart : public IntrusivePtrVirtualBase {
public:
/// Name/label for this part
String name;
/// Type of this part
virtual String typeName() const = 0;
/// Create a clone of this symbol part
virtual SymbolPartP clone() const = 0;
/// Icon for this part
virtual int icon() const = 0;
/// Convert tot SymbolShape?
virtual SymbolShape* isSymbolShape() { return nullptr; }
virtual const SymbolShape* isSymbolShape() const { return nullptr; }
/// Convert tot SymbolSymmetry?
virtual SymbolSymmetry* isSymbolSymmetry() { return nullptr; }
virtual const SymbolSymmetry* isSymbolSymmetry() const { return nullptr; }
DECLARE_REFLECTION_VIRTUAL();
};
template <> SymbolPartP read_new<SymbolPart>(Reader& reader);
// ----------------------------------------------------------------------------- : SymbolShape
/// How are symbol parts combined with parts below it?
enum SymbolPartCombine
{ PART_MERGE
, PART_SUBTRACT
, PART_INTERSECTION
, PART_DIFFERENCE
, PART_OVERLAP
, PART_BORDER
enum SymbolShapeCombine
{ SYMBOL_COMBINE_MERGE
, SYMBOL_COMBINE_SUBTRACT
, SYMBOL_COMBINE_INTERSECTION
, SYMBOL_COMBINE_DIFFERENCE
, SYMBOL_COMBINE_OVERLAP
, SYMBOL_COMBINE_BORDER
};
/// A sane mod function, always returns a result in the range [0..size)
@@ -120,25 +149,26 @@ inline size_t mod(int a, size_t size) {
return m >= 0 ? m : m + size;
}
/// A single part (polygon/bezier-gon) in a Symbol
class SymbolPart : public IntrusivePtrBase<SymbolPart> {
/// A single shape (polygon/bezier-gon) in a Symbol
class SymbolShape : public SymbolPart {
public:
/// The points of this polygon
vector<ControlPointP> points;
/// Name/label for this part
String name;
/// How is this part combined with parts below it?
SymbolPartCombine combine;
SymbolShapeCombine combine;
// Center of rotation, relative to the part, when the part is scaled to [0..1]
Vector2D rotation_center;
/// Position and size of the part
/// this is the smallest axis aligned bounding box that fits around the part
Vector2D min_pos, max_pos;
SymbolPart();
SymbolShape();
/// Create a clone of this symbol part
SymbolPartP clone() const;
virtual String typeName() const;
virtual SymbolPartP clone() const;
virtual int icon() const { return combine; }
virtual SymbolShape* isSymbolShape() { return this; }
virtual const SymbolShape* isSymbolShape() const { return this; }
/// Get a control point, wraps around
inline ControlPointP getPoint(int id) const {
@@ -155,6 +185,34 @@ class SymbolPart : public IntrusivePtrBase<SymbolPart> {
};
// ----------------------------------------------------------------------------- : SymbolSymmetry
enum SymbolSymmetryType
{ SYMMETRY_ROTATION = SYMBOL_COMBINE_BORDER + 1 // for icons
, SYMMETRY_REFLECTION
};
/// A mirror, reflecting part of the symbol
/** Can handle rotation symmetry with any number of reflections */
class SymbolSymmetry : public SymbolPart {
public:
SymbolSymmetryType kind; ///< What kind of symmetry
int copies; ///< How many times is the orignal reflected (including the original itself)
bool clip; ///< Clip the orignal so it doesn't intersect the mirror(s)
Vector2D center; ///< Center point of the mirror
Vector2D handle; ///< A handle pointing in the direction of the original, relative to the center
SymbolSymmetry();
virtual String typeName() const;
virtual SymbolPartP clone() const;
virtual int icon() const { return kind; }
virtual SymbolSymmetry* isSymbolSymmetry() { return this; }
virtual const SymbolSymmetry* isSymbolSymmetry() const { return this; }
DECLARE_REFLECTION();
};
// ----------------------------------------------------------------------------- : Symbol
/// An editable symbol, consists of any number of SymbolParts