initial checkin of C++ port (in progress)

git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@2 0fc631ac-6414-0410-93d0-97cfa31319b6
This commit is contained in:
twanvl
2006-10-01 14:08:07 +00:00
parent f5c0071da6
commit ddfb1a5089
56 changed files with 8781 additions and 0 deletions
+368
View File
@@ -0,0 +1,368 @@
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
// ----------------------------------------------------------------------------- : Includes
#include <data/action/symbol.hpp>
#include <data/action/symbol_part.hpp>
typedef pair<SymbolPartP,SymbolPartCombine> pair_part_combine_t;
typedef pair<SymbolPartP,size_t > pair_part_size_t;
DECLARE_TYPEOF_COLLECTION(pair_part_combine_t);
DECLARE_TYPEOF_COLLECTION(pair_part_size_t);
// ----------------------------------------------------------------------------- : Moving symbol parts
SymbolPartMoveAction::SymbolPartMoveAction(const set<SymbolPartP>& parts)
: parts(parts)
, constrain(false)
{}
String SymbolPartMoveAction::getName(bool toUndo) const {
return parts.size() == 1 ? _("Move shape") : _("Move shapes");
}
void SymbolPartMoveAction::perform(bool toUndo) {
// move the points back
FOR_EACH(p, parts) {
p->minPos -= moved;
p->maxPos -= moved;
FOR_EACH(pnt, p->points) {
pnt->pos -= moved;
}
}
moved = -moved;
}
void SymbolPartMoveAction::move(const Vector2D& deltaDelta) {
delta += deltaDelta;
// Move each point by deltaDelta, possibly constrained
Vector2D d = constrainVector(delta, constrain);
Vector2D dd = d - moved;
FOR_EACH(p, parts) {
p->minPos += dd;
p->maxPos += dd;
FOR_EACH(pnt, p->points) {
pnt->pos += dd;
}
}
moved = d;
}
// ----------------------------------------------------------------------------- : Rotating symbol parts
SymbolPartMatrixAction::SymbolPartMatrixAction(const set<SymbolPartP>& parts, const Vector2D& center)
: parts(parts)
, center(center)
{}
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->deltaBefore = pnt->deltaBefore.mul(mx,my);
pnt->deltaAfter = pnt->deltaAfter .mul(mx,my);
}
// bounds change after transforming
p->calculateBounds();
}
}
SymbolPartRotateAction::SymbolPartRotateAction(const set<SymbolPartP>& parts, const Vector2D& center)
: SymbolPartMatrixAction(parts, center)
, constrain(false)
, angle(0)
{}
String SymbolPartRotateAction::getName(bool toUndo) const {
return parts.size() == 1 ? _("Rotate shape") : _("Rotate shapes");
}
void SymbolPartRotateAction::perform(bool toUndo) {
// move the points back
rotateBy(-angle);
angle = -angle;
}
void SymbolPartRotateAction::rotateTo(double newAngle) {
double oldAngle = angle;
angle = newAngle;
// constrain?
if (constrain) {
// multiples of 2pi/24 i.e. 24 stops
double mult = (2 * M_PI) / 24;
angle = floor(angle / mult + 0.5) * mult;
}
if (oldAngle != angle) rotateBy(angle - oldAngle);
}
void SymbolPartRotateAction::rotateBy(double deltaAngle) {
// Rotation 'matrix'
transform(
Vector2D(cos(deltaAngle), -sin(deltaAngle)),
Vector2D(sin(deltaAngle), cos(deltaAngle))
);
}
// ----------------------------------------------------------------------------- : Shearing symbol parts
SymbolPartShearAction::SymbolPartShearAction(const set<SymbolPartP>& parts, const Vector2D& center)
: SymbolPartMatrixAction(parts, center)
, constrain(false)
{}
String SymbolPartShearAction::getName(bool toUndo) const {
return parts.size() == 1 ? _("Shear shape") : _("Shear shapes");
}
void SymbolPartShearAction::perform(bool toUndo) {
// move the points back
// the vector shear = (x,y) is used as:
// <1 x>
// <y 1>
// inverse is:
// <1 -x> /
// <-y 1> / (1 - xy)
// we have: xy = 0 => (1 - xy) = 1
shearBy(-shear);
}
void SymbolPartShearAction::move(const Vector2D& deltaShear) {
shear += deltaShear;
shearBy(deltaShear);
}
void SymbolPartShearAction::shearBy(const Vector2D& shear) {
// Shear 'matrix'
transform(
Vector2D(1, shear.x),
Vector2D(shear.y, 1)
);
}
// ----------------------------------------------------------------------------- : Scaling symbol parts
SymbolPartScaleAction::SymbolPartScaleAction(const set<SymbolPartP>& parts, int scaleX, int scaleY)
: parts(parts)
, constrain(false)
, scaleX(scaleX), scaleY(scaleY)
{
// Find min and max coordinates
oldMin = Vector2D::infinity();
Vector2D oldMax = -Vector2D::infinity();
FOR_EACH(p, parts) {
oldMin = piecewise_min(oldMin, p->minPos);
oldMax = piecewise_max(oldMax, p->maxPos);
}
// new == old
newMin = newRealMin = oldMin;
newSize = newRealSize = oldSize = oldMax - oldMin;
}
String SymbolPartScaleAction::getName(bool toUndo) const {
return parts.size() == 1 ? _("Scale shape") : _("Scale shapes");
}
void SymbolPartScaleAction::perform(bool toUndo) {
swap(oldMin, newMin);
swap(oldSize, newSize);
transformAll();
}
void SymbolPartScaleAction::move(const Vector2D& deltaMin, const Vector2D& deltaMax) {
newRealMin += deltaMin;
newRealSize += deltaMax - deltaMin;
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
// the size after the move
newMin = newRealMin; newSize = newRealSize;
if (constrain && scaleX != 0 && scaleY != 0) {
Vector2D scale = newSize.div(tmpSize);
scale = constrainVector(scale, true, true);
newSize = tmpSize.mul(scale);
newMin += (newRealSize - newSize).mul(Vector2D(scaleX == -1 ? 1 : 0, scaleY == -1 ? 1 : 0));
}
// now move all points
transformAll();
// restore oldMin/Size
oldMin = tmpMin; oldSize = tmpSize;
}
void SymbolPartScaleAction::transformAll() {
Vector2D scale = newSize.div(oldSize);
FOR_EACH(p, parts) {
p->minPos = transform(p->minPos);
p->maxPos = transform(p->maxPos);
// make sure that max >= min
if (p->minPos.x > p->maxPos.x) swap(p->minPos.x, p->maxPos.x);
if (p->minPos.y > p->maxPos.y) swap(p->minPos.y, p->maxPos.y);
// scale all points
FOR_EACH(pnt, p->points) {
pnt->pos = transform(pnt->pos);
// also scale handles
pnt->deltaBefore = pnt->deltaBefore.mul(scale);
pnt->deltaAfter = pnt->deltaAfter .mul(scale);
}
}
}
// ----------------------------------------------------------------------------- : Change combine mode
CombiningModeAction::CombiningModeAction(const set<SymbolPartP>& parts, SymbolPartCombine mode) {
FOR_EACH(p, parts) {
this->parts.push_back(make_pair(p,mode));
}
}
String CombiningModeAction::getName(bool toUndo) const {
return _("Change combine mode");
}
void CombiningModeAction::perform(bool toUndo) {
FOR_EACH(pm, parts) {
swap(pm.first->combine, pm.second);
}
}
// ----------------------------------------------------------------------------- : Change name
SymbolPartNameAction::SymbolPartNameAction(const SymbolPartP& part, const String& name)
: part(part), partName(name)
{}
String SymbolPartNameAction::getName(bool toUndo) const {
return _("Change shape name");
}
void SymbolPartNameAction::perform(bool toUndo) {
swap(part->name, partName);
}
// ----------------------------------------------------------------------------- : Add symbol part
AddSymbolPartAction::AddSymbolPartAction(Symbol& symbol, const SymbolPartP& part)
: symbol(symbol), part(part)
{}
String AddSymbolPartAction::getName(bool toUndo) const {
return _("Add ") + part->name;
}
void AddSymbolPartAction::perform(bool toUndo) {
if (toUndo) {
assert(!symbol.parts.empty());
symbol.parts.erase (symbol.parts.begin());
} else {
symbol.parts.insert(symbol.parts.begin(), part);
}
}
// ----------------------------------------------------------------------------- : Remove symbol part
RemoveSymbolPartsAction::RemoveSymbolPartsAction(Symbol& symbol, const set<SymbolPartP>& parts)
: symbol(symbol)
{
size_t index = 0;
FOR_EACH(p, symbol.parts) {
if (parts.find(p) != parts.end()) {
removals.push_back(make_pair(p, index)); // remove this part
}
++index;
}
}
String RemoveSymbolPartsAction::getName(bool toUndo) const {
return removals.size() == 1 ? _("Remove shape") : _("Remove shapes");
}
void RemoveSymbolPartsAction::perform(bool toUndo) {
if (toUndo) {
// reinsert the parts
// ascending order, this is the reverse of removal
FOR_EACH(r, removals) {
assert(r.second <= symbol.parts.size());
symbol.parts.insert(symbol.parts.begin() + r.second, r.first);
}
} else {
// remove the parts
// descending order, because earlier removals shift the rest of the vector
FOR_EACH_REVERSE(r, removals) {
assert(r.second < symbol.parts.size());
symbol.parts.erase(symbol.parts.begin() + r.second);
}
}
}
// ----------------------------------------------------------------------------- : Duplicate symbol parts
DuplicateSymbolPartsAction::DuplicateSymbolPartsAction(Symbol& symbol, const set<SymbolPartP>& parts)
: symbol(symbol)
{
UInt index = 0;
FOR_EACH(p, symbol.parts) {
index += 1;
if (parts.find(p) != parts.end()) {
// duplicate this part
duplications.push_back(make_pair(p->clone(), index));
index += 1; // the clone also takes up space on the vector
}
}
}
String DuplicateSymbolPartsAction::getName(bool toUndo) const {
return duplications.size() == 1 ? _("Duplicate shape") : _("Duplicate shapes");
}
void DuplicateSymbolPartsAction::perform(bool toUndo) {
if (toUndo) {
// remove the clones
// walk in reverse order, otherwise we will shift the vector
FOR_EACH_REVERSE(d, duplications) {
assert(d.second < symbol.parts.size());
symbol.parts.erase(symbol.parts.begin() + d.second);
}
} else {
// insert the clones
FOR_EACH(d, duplications) {
assert(d.second <= symbol.parts.size());
symbol.parts.insert(symbol.parts.begin() + d.second, d.first);
}
}
}
void DuplicateSymbolPartsAction::getParts(set<SymbolPartP>& parts) {
parts.clear();
FOR_EACH(d, duplications) {
parts.insert(d.first);
}
}
// ----------------------------------------------------------------------------- : Reorder symbol parts
ReorderSymbolPartsAction::ReorderSymbolPartsAction(Symbol& symbol, size_t partId1, size_t partId2)
: symbol(symbol), partId1(partId1), partId2(partId2)
{}
String ReorderSymbolPartsAction::getName(bool toUndo) const {
return _("Reorder");
}
void ReorderSymbolPartsAction::perform(bool toUndo) {
assert(partId1 < symbol.parts.size());
assert(partId2 < symbol.parts.size());
swap(symbol.parts[partId1], symbol.parts[partId2]);
}
+241
View File
@@ -0,0 +1,241 @@
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
#ifndef HEADER_DATA_ACTION_SYMBOL
#define HEADER_DATA_ACTION_SYMBOL
/** @file data/action/symbol.hpp
*
* Actions operating on Symbols or whole SymbolParts.
*/
// ----------------------------------------------------------------------------- : Includes
#include <util/prec.hpp>
#include <util/action_stack.hpp>
#include <data/symbol.hpp>
// ----------------------------------------------------------------------------- : Transform symbol part
/// Anything that changes a part
class SymbolPartAction : public Action {};
/// Anything that changes a part as displayed in the part list
class SymbolPartListAction : public SymbolPartAction {};
// ----------------------------------------------------------------------------- : Moving symbol parts
/// Move some symbol parts
class SymbolPartMoveAction : public SymbolPartAction {
public:
SymbolPartMoveAction(const set<SymbolPartP>& parts);
virtual String getName(bool toUndo) const;
virtual void perform(bool toUndo);
/// Update this action to move some more
void move(const Vector2D& delta);
private:
set<SymbolPartP> parts; //^ Parts to move
Vector2D delta; //^ How much to move
Vector2D moved; //^ How much has been moved
public:
bool constrain; //^ Constrain movement?
};
// ----------------------------------------------------------------------------- : Rotating symbol parts
/// Transforming symbol parts using a matrix
class SymbolPartMatrixAction : public SymbolPartAction {
public:
SymbolPartMatrixAction(const set<SymbolPartP>& parts, const Vector2D& center);
/// Update this action to move some more
void move(const Vector2D& delta);
protected:
/// Perform the transformation using the given matrix
void transform(const Vector2D& mx, const Vector2D& my);
set<SymbolPartP> parts; //^ Parts to transform
Vector2D center; //^ Center to transform around
};
/// Rotate some symbol parts
class SymbolPartRotateAction : public SymbolPartMatrixAction {
public:
SymbolPartRotateAction(const set<SymbolPartP>& parts, const Vector2D& center);
virtual String getName(bool toUndo) const;
virtual void perform(bool toUndo);
/// Update this action to rotate to a different angle
void rotateTo(double newAngle);
/// Update this action to rotate by a deltaAngle
void rotateBy(double deltaAngle);
private:
double angle; //^ How much to rotate?
public:
bool constrain; //^ Constrain movement?
};
// ----------------------------------------------------------------------------- : Shearing symbol parts
/// Shear some symbol parts
class SymbolPartShearAction : public SymbolPartMatrixAction {
public:
SymbolPartShearAction(const set<SymbolPartP>& parts, const Vector2D& center);
virtual String getName(bool toUndo) const;
virtual void perform(bool toUndo);
/// Change shear by a given amount
void move(const Vector2D& deltaShear);
private:
Vector2D shear; //^ Shearing, shear.x == 0 || shear.y == 0
void shearBy(const Vector2D& shear);
public:
bool constrain; //^ Constrain movement?
};
// ----------------------------------------------------------------------------- : Scaling symbol parts
/// Scale some symbol parts
class SymbolPartScaleAction : public SymbolPartAction {
public:
SymbolPartScaleAction(const set<SymbolPartP>& parts, int scaleX, int scaleY);
virtual String getName(bool toUndo) const;
virtual void perform(bool toUndo);
/// Change min and max coordinates
void move(const Vector2D& deltaMin, const Vector2D& deltaMax);
/// 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?
/// 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;
}
public:
bool constrain; //^ Constrain movement?
};
// ----------------------------------------------------------------------------- : Change combine mode
/// Change the name of a symbol part
class CombiningModeAction : public SymbolPartListAction {
public:
CombiningModeAction(const set<SymbolPartP>& parts, SymbolPartCombine mode);
virtual String getName(bool toUndo) const;
virtual void perform(bool toUndo);
private:
vector<pair<SymbolPartP,SymbolPartCombine> > parts; //^ Affected parts with new combining modes
};
// ----------------------------------------------------------------------------- : Change name
/// Change the name of a symbol part
class SymbolPartNameAction : public SymbolPartListAction {
public:
SymbolPartNameAction(const SymbolPartP& part, const String& name);
virtual String getName(bool toUndo) const;
virtual void perform(bool toUndo);
private:
SymbolPartP part; //^ Affected part
String partName; //^ New name
};
// ----------------------------------------------------------------------------- : Add symbol part
/// Adding a part to a symbol, added at the front of the list
/// front = drawn on top
class AddSymbolPartAction : public SymbolPartListAction {
public:
AddSymbolPartAction(Symbol& symbol, const SymbolPartP& part);
virtual String getName(bool toUndo) const;
virtual void perform(bool toUndo);
private:
Symbol& symbol; //^ Symbol to add the part to
SymbolPartP part; //^ Part to add
};
// ----------------------------------------------------------------------------- : Remove symbol part
/// Removing parts from a symbol
class RemoveSymbolPartsAction : public SymbolPartListAction {
public:
RemoveSymbolPartsAction(Symbol& symbol, const set<SymbolPartP>& parts);
virtual String getName(bool toUndo) const;
virtual void perform(bool toUndo);
private:
Symbol& symbol;
/// Removed parts and their positions, sorted by ascending pos
vector<pair<SymbolPartP, size_t> > removals;
};
// ----------------------------------------------------------------------------- : Duplicate symbol parts
/// Duplicating parts in a symbol
class DuplicateSymbolPartsAction : public SymbolPartListAction {
public:
DuplicateSymbolPartsAction(Symbol& symbol, const set<SymbolPartP>& parts);
virtual String getName(bool toUndo) const;
virtual void perform(bool toUndo);
/// Fill a set with all the new parts
void getParts(set<SymbolPartP>& parts);
private:
Symbol& symbol;
/// Duplicates of parts and their positions, sorted by ascending pos
vector<pair<SymbolPartP, size_t> > duplications;
};
// ----------------------------------------------------------------------------- : Reorder symbol parts
/// Change the position of a part in a symbol.
/// This is done by swapping two parts.
class ReorderSymbolPartsAction : public SymbolPartListAction {
public:
ReorderSymbolPartsAction(Symbol& symbol, size_t partId1, size_t partId2);
virtual String getName(bool toUndo) const;
virtual void perform(bool toUndo);
private:
Symbol& symbol; //^ Symbol to swap the parts in
public:
size_t partId1, partId2; //^ Indeces of parts to swap
};
// ----------------------------------------------------------------------------- : EOF
#endif
+379
View File
@@ -0,0 +1,379 @@
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
// ----------------------------------------------------------------------------- : Includes
#include <data/action/symbol_part.hpp>
#include <gfx/bezier.hpp>
DECLARE_TYPEOF_COLLECTION(Vector2D);
// ----------------------------------------------------------------------------- : Utility
inline double sgn(double v) { return v > 0 ? 1 : -1; }
Vector2D constrainVector(const Vector2D& v, bool constrain, bool onlyDiagonal) {
if (!constrain) return v;
double ax = abs(v.x), ay = abs(v.y);
if (ax * 2 < ay && !onlyDiagonal) {
return Vector2D(0, v.y); // vertical
} else if(ay * 2 < ax && !onlyDiagonal) {
return Vector2D(v.x, 0); // horizontal
} else {
return Vector2D( // diagonal
sgn(v.x) * (ax + ay) / 2,
sgn(v.y) * (ax + ay) / 2
);
}
}
// ----------------------------------------------------------------------------- : Move control point
ControlPointMoveAction::ControlPointMoveAction(const set<ControlPointP>& points)
: points(points)
, constrain(false)
{
oldValues.reserve(points.size());
FOR_EACH(p, points) {
oldValues.push_back(p->pos);
}
}
String ControlPointMoveAction::getName(bool toUndo) const {
return points.size() == 1 ? _("Move point") : _("Move points");
}
void ControlPointMoveAction::perform(bool toUndo) {
/*
set<ControlPointP>::const_iterator it = points.begin();
vector<Vector2D> ::iterator it2 = oldValues.begin();
for( ; it != points.end() && it2 != oldValues.end() ; ++it, ++it2) {
swap<Vector2D>((*it)->pos, *it2);
}
*/
FOR_EACH_2(p,points, op,oldValues) {
swap(p->pos, op);
}
}
void ControlPointMoveAction::move (const Vector2D& deltaDelta) {
delta += deltaDelta;
// Move each point by delta, possibly constrained
Vector2D d = constrainVector(delta, constrain);
set<ControlPointP>::const_iterator it = points.begin();
vector<Vector2D> ::iterator it2 = oldValues.begin();
for( ; it != points.end() && it2 != oldValues.end() ; ++it, ++it2) {
(*it)->pos = (*it2) + d;
}
}
// ----------------------------------------------------------------------------- : Move handle
HandleMoveAction::HandleMoveAction(const SelectedHandle& handle)
: handle(handle)
, constrain(false)
, oldHandle(handle.getHandle())
, oldOther (handle.getOther())
{}
String HandleMoveAction::getName(bool toUndo) const {
return _("Move handle");
}
void HandleMoveAction::perform(bool toUndo) {
swap(oldHandle, handle.getHandle());
swap(oldOther, handle.getOther());
}
void HandleMoveAction::move(const Vector2D& deltaDelta) {
delta += deltaDelta;
handle.getHandle() = constrainVector(oldHandle + delta, constrain);
handle.getOther() = oldOther;
handle.onUpdateHandle();
}
// ----------------------------------------------------------------------------- : Segment mode
ControlPointUpdate::ControlPointUpdate(const ControlPointP& pnt)
: other(*pnt)
, point(pnt)
{}
void ControlPointUpdate::perform() {
swap(other, *point);
}
SegmentModeAction::SegmentModeAction(const ControlPointP& p1, const ControlPointP& p2, SegmentMode mode)
: point1(p1), point2(p2)
{
if (p1->segmentAfter == mode) return;
point1.other.segmentAfter = point2.other.segmentBefore = mode;
if (mode == SEGMENT_LINE) {
point1.other.deltaAfter = Vector2D(0,0);
point2.other.deltaBefore = Vector2D(0,0);
point1.other.lock = LOCK_FREE;
point2.other.lock = LOCK_FREE;
} else if (mode == SEGMENT_CURVE) {
point1.other.deltaAfter = (p2->pos - p1->pos) / 3.0f;
point2.other.deltaBefore = (p1->pos - p2->pos) / 3.0f;
}
}
String SegmentModeAction::getName(bool toUndo) const {
SegmentMode mode = toUndo ? point1.point->segmentAfter : point1.other.segmentAfter;
if (mode == SEGMENT_LINE) return _("Convert to line");
else return _("Convert to curve");
}
void SegmentModeAction::perform(bool toUndo) {
point1.perform();
point2.perform();
}
// ----------------------------------------------------------------------------- : Locking mode
LockModeAction::LockModeAction(const ControlPointP& p, LockMode lock)
: point(p)
{
point.other.lock = lock;
point.other.onUpdateLock();
}
String LockModeAction::getName(bool toUndo) const {
return _("Lock point");
}
void LockModeAction::perform(bool toUndo) {
point.perform();
}
// ----------------------------------------------------------------------------- : Move curve
CurveDragAction::CurveDragAction(const ControlPointP& point1, const ControlPointP& point2)
: SegmentModeAction(point1, point2, SEGMENT_CURVE)
{}
String CurveDragAction::getName(bool toUndo) const {
return _("Move curve");
}
void CurveDragAction::perform(bool toUndo) {
SegmentModeAction::perform(toUndo);
}
void CurveDragAction::move(const Vector2D& delta, double t) {
// Logic:
// Assuming old point is p, new point is p'
// Point on old bezier curve is:
// p = a t^3 + 3b (1-t) t^2 + 3c (1-t)^2 t + d (1-t)^2
// Point on new bezier curve is:
// p_(' = a t^3 + 3b') (1-t) t^2 + 3c' (1-t)^2 t + d (1-t)^2
// We now want to change control points b and c, the closer we are to b (t close to 0)
// the more effect we have on b, so we substitute:
// b' = b + x t
// c' = c + x (1-t)
// Solving for x we get:
// x = (p'-p) / ( t (1-t) ( t^2 + (1-t)^2) )
// Naming:
// delta = p' - p
// pointDelta = x * t * (1-t)
Vector2D pointDelta = delta / (3 * (t * t + (1-t) * (1-t)));
point1.point->deltaAfter += pointDelta / t;
point2.point->deltaBefore += pointDelta / (1-t);
point1.point->onUpdateHandle(HANDLE_AFTER);
point2.point->onUpdateHandle(HANDLE_BEFORE);
}
// ----------------------------------------------------------------------------- : Add control point
ControlPointAddAction::ControlPointAddAction(const SymbolPartP& part, UInt insertAfter, double t)
: point1(part->getPoint(insertAfter))
, point2(part->getPoint(insertAfter + 1))
, part(part)
, insertAfter(insertAfter)
, newPoint(new ControlPoint())
{
// calculate new point
if (point1.other.segmentAfter == SEGMENT_CURVE) {
// calculate new handles using de Casteljau's subdivision algorithm
deCasteljau(point1.other, point2.other, t, *newPoint);
// unlock if needed
if (point1.other.lock == LOCK_SIZE) point1.other.lock = LOCK_DIR;
if (point2.other.lock == LOCK_SIZE) point2.other.lock = LOCK_DIR;
newPoint->lock = LOCK_DIR;
newPoint->segmentBefore = SEGMENT_CURVE;
newPoint->segmentAfter = SEGMENT_CURVE;
} else {
newPoint->pos = point1.other.pos * (1 - t) + point2.other.pos * t;
newPoint->lock = LOCK_FREE;
newPoint->segmentBefore = SEGMENT_LINE;
newPoint->segmentAfter = SEGMENT_LINE;
}
}
String ControlPointAddAction::getName(bool toUndo) const {
return _("Add control point");
}
void ControlPointAddAction::perform(bool toUndo) {
if (toUndo) { // remove the point
part->points.erase( part->points.begin() + insertAfter + 1);
} else {
part->points.insert(part->points.begin() + insertAfter + 1, newPoint);
}
// update points before/after
point1.perform();
point2.perform();
}
// ----------------------------------------------------------------------------- : Remove control point
/// Sqaure root that caries the sign over the root
/// or formally: ssqrt(x) = Re<sqrt(x)> - Im<sqrt(x)> = x / sqrt(|x|)
double ssqrt(double x) {
if (x > 0) return sqrt(x);
else return -sqrt(-x);
}
// Remove a single control point
class SinglePointRemoveAction : public Action {
public:
SinglePointRemoveAction(const SymbolPartP& part, UInt position);
virtual String getName(bool toUndo) const { return _("Delete point"); }
virtual void perform(bool toUndo);
private:
SymbolPartP part;
UInt position;
ControlPointP point; //^ Removed point
ControlPointUpdate point1, point2; //^ Points before/after
};
SinglePointRemoveAction::SinglePointRemoveAction(const SymbolPartP& part, UInt position)
: part(part)
, position(position)
, point (part->getPoint(position - 1))
, point1(part->getPoint(position - 1))
, point2(part->getPoint(position + 1))
{
if (point1.other.segmentAfter == SEGMENT_CURVE || point2.other.segmentBefore == SEGMENT_CURVE) {
// try to preserve curve
Vector2D before = point->deltaBefore;
Vector2D after = point->deltaAfter;
// convert both segments to curves first
if (point1.other.segmentAfter != SEGMENT_CURVE) {
point1.other.deltaAfter = -
before = (point1.other.pos - point->pos) / 3.0;
point1.other.segmentAfter = SEGMENT_CURVE;
}
if (point2.other.segmentBefore != SEGMENT_CURVE) {
point2.other.deltaBefore = -
after = (point2.other.pos - point->pos) / 3.0;
point2.other.segmentBefore = SEGMENT_CURVE;
}
// The inverse of adding a point, reconstruct the original handles
// before being subdivided using de Casteljau's algorithm
// length of handles
double bl = before.length() + 0.00000001; // prevent division by 0
double al = after .length() + 0.00000001;
double totl = bl + al;
// set new handle sizes
point1.other.deltaAfter *= totl / bl;
point2.other.deltaBefore *= totl / al;
// Also take in acount cases where the point does not correspond to a freshly added point.
// distance from the point to the curve as it would be in the above case can be used,
// in the case of a point just added this distance = 0
BezierCurve c(point1.other, point2.other);
double t = bl / totl;
Vector2D p = c.pointAt(t);
Vector2D distP = point->pos - p;
// adjust handle sizes
point1.other.deltaAfter *= ssqrt(distP.dot(point1.other.deltaAfter) /point1.other.deltaAfter.lengthSqr()) + 1;
point2.other.deltaBefore *= ssqrt(distP.dot(point2.other.deltaBefore)/point2.other.deltaBefore.lengthSqr()) + 1;
// unlock if needed
if (point1.other.lock == LOCK_SIZE) point1.other.lock = LOCK_DIR;
if (point2.other.lock == LOCK_SIZE) point2.other.lock = LOCK_DIR;
} else {
// just lines, keep it that way
}
}
void SinglePointRemoveAction::perform(bool toUndo) {
if (toUndo) {
// reinsert the point
part->points.insert(part->points.begin() + position, point);
} else {
// remove the point
part->points.erase( part->points.begin() + position);
}
// update points around removed point
point1.perform();
point2.perform();
}
DECLARE_POINTER_TYPE(SinglePointRemoveAction);
DECLARE_TYPEOF_COLLECTION(SinglePointRemoveActionP);
/// Remove a set of points from a symbol part
/// 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);
virtual String getName(bool toUndo) const;
virtual void perform(bool toUndo);
private:
vector<SinglePointRemoveActionP> removals;
};
ControlPointRemoveAction::ControlPointRemoveAction(const SymbolPartP& part, const set<ControlPointP>& toDelete) {
int index = 0;
// find points to remove, in reverse order
FOR_EACH(point, part->points) {
if (toDelete.find(point) != toDelete.end()) {
// remove this point
removals.push_back(new_shared2<SinglePointRemoveAction>(part, index));
}
++index;
}
}
String ControlPointRemoveAction::getName(bool toUndo) const {
return removals.size() == 1 ? _("Delete point") : _("Delete points");
}
void ControlPointRemoveAction::perform(bool toUndo) {
if (toUndo) {
FOR_EACH(r, removals) r->perform(toUndo);
} else {
// in reverse order, because positions of later points will
// change after removal of earlier points.
FOR_EACH_REVERSE(r, removals) r->perform(toUndo);
}
}
Action* controlPointRemoveAction(const SymbolPartP& part, const set<ControlPointP>& toDelete) {
if (part->points.size() - toDelete.size() < 2) {
// TODO : remove part?
//new_shared<ControlPointRemoveAllAction>(part);
return 0; // no action
} else {
return new ControlPointRemoveAction(part, toDelete);
}
}
+158
View File
@@ -0,0 +1,158 @@
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
#ifndef HEADER_DATA_ACTION_SYMBOL_PART
#define HEADER_DATA_ACTION_SYMBOL_PART
/** @file data/action/symbol_part.hpp
*
* Actions operating on the insides of SymbolParts (ControlPoints and the like).
*/
// ----------------------------------------------------------------------------- : Includes
#include <util/prec.hpp>
#include <util/action_stack.hpp>
#include <data/symbol.hpp>
// ----------------------------------------------------------------------------- : Utility
/// Constrain a vector to be horizontal, vertical or diagonal
/// If constraint==false does nothing
Vector2D constrainVector(const Vector2D& v, bool constrain = true, bool onlyDiagonal = false);
// ----------------------------------------------------------------------------- : Move control point
/// Moving a control point in a symbol
class ControlPointMoveAction : public Action {
public:
ControlPointMoveAction(const set<ControlPointP>& points);
virtual String getName(bool toUndo) const;
virtual void perform(bool toUndo);
/// Update this action to move some more
void move(const Vector2D& delta);
private:
set<ControlPointP> points; //^ Points to move
vector<Vector2D> oldValues; //^ Their old positions
Vector2D delta; //^ Amount we moved
public:
bool constrain; //^ Constrain movement?
};
// ----------------------------------------------------------------------------- : Move handle
/// Moving a handle(before/after) of a control point in a symbol
class HandleMoveAction : public Action {
public:
HandleMoveAction(const SelectedHandle& handle);
virtual String getName(bool toUndo) const;
virtual void perform(bool toUndo);
/// Update this action to move some more
void move(const Vector2D& delta);
private:
SelectedHandle handle; //^ The handle to move
Vector2D oldHandle; //^ Old value of this handle
Vector2D oldOther; //^ Old value of other handle, needed for contraints
Vector2D delta; //^ Amount we moved
public:
bool constrain; //^ Constrain movement?
};
// ----------------------------------------------------------------------------- : Segment mode
/// Utility class to update a control point
class ControlPointUpdate {
public:
ControlPointUpdate(const ControlPointP& pnt);
/// Perform or undo an update on this control point
void perform();
/// Other value that is swapped with the current one.
/// Should be changed to make perform have an effect
ControlPoint other;
/// The point that is to be changed, should not be updated before perform()
ControlPointP point;
};
/// Changing a line to a curve and vice versa
class SegmentModeAction : public Action {
public:
SegmentModeAction(const ControlPointP& p1, const ControlPointP& p2, SegmentMode mode);
virtual String getName(bool toUndo) const;
virtual void perform(bool toUndo);
protected:
ControlPointUpdate point1, point2;
};
// ----------------------------------------------------------------------------- : Locking mode
/// Locking a control point
class LockModeAction : public Action {
public:
LockModeAction(const ControlPointP& p, LockMode mode);
virtual String getName(bool toUndo) const;
virtual void perform(bool toUndo);
private:
ControlPointUpdate point; //^ The affected point
};
// ----------------------------------------------------------------------------- : Move curve
/// Dragging a curve; also coverts lines to curves
/** Inherits from SegmentModeAction because it also has that effect
*/
class CurveDragAction : public SegmentModeAction {
public:
CurveDragAction(const ControlPointP& point1, const ControlPointP& point2);
virtual String getName(bool toUndo) const;
virtual void perform(bool toUndo);
// Move the curve by this much, it is grabbed at time t
void move(const Vector2D& delta, double t);
};
// ----------------------------------------------------------------------------- : Add control point
/// Insert a new point in a symbol part
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 insertAfter, double t);
virtual String getName(bool toUndo) const;
virtual void perform(bool toUndo);
inline ControlPointP getNewPoint() const { return newPoint; }
private:
SymbolPartP part; //^ SymbolPart we are in
ControlPointP newPoint; //^ The point to insert
UInt insertAfter; //^ 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);
// ----------------------------------------------------------------------------- : EOF
#endif