Files
MagicSetEditor2/src/data/symbol.cpp
T
coppro 26562e03e3 Updated copyright information - added my name and also changed 2007 to 2008
git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@837 0fc631ac-6414-0410-93d0-97cfa31319b6
2008-04-06 18:16:32 +00:00

338 lines
9.8 KiB
C++

//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) 2001 - 2008 Twan van Laarhoven and "coppro" |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
// ----------------------------------------------------------------------------- : Includes
#include <util/prec.hpp>
#include <data/symbol.hpp>
#include <script/to_value.hpp>
#include <gfx/bezier.hpp>
DECLARE_TYPEOF_COLLECTION(ControlPointP);
DECLARE_TYPEOF_COLLECTION(SymbolPartP);
// ----------------------------------------------------------------------------- : ControlPoint
IMPLEMENT_REFLECTION_ENUM(LockMode) {
VALUE_N("free", LOCK_FREE);
VALUE_N("direction", LOCK_DIR);
VALUE_N("size", LOCK_SIZE);
}
IMPLEMENT_REFLECTION_ENUM(SegmentMode) {
VALUE_N("line", SEGMENT_LINE);
VALUE_N("curve", SEGMENT_CURVE);
}
IMPLEMENT_REFLECTION(ControlPoint) {
REFLECT_N("position", pos);
REFLECT_N("lock", lock);
REFLECT_N("line_after", segment_after);
if (tag.reading() || segment_before == SEGMENT_CURVE) {
REFLECT_N("handle_before", delta_before);
}
if (tag.reading() || segment_after == SEGMENT_CURVE) {
REFLECT_N("handle_after", delta_after);
}
}
ControlPoint::ControlPoint()
: segment_before(SEGMENT_LINE), segment_after(SEGMENT_LINE)
, lock(LOCK_FREE)
{}
ControlPoint::ControlPoint(double x, double y)
: pos(x,y)
, segment_before(SEGMENT_LINE), segment_after(SEGMENT_LINE)
, lock(LOCK_FREE)
{}
ControlPoint::ControlPoint(double x, double y, double xb, double yb, double xa, double ya, LockMode lock)
: pos(x,y)
, delta_before(xb,yb)
, delta_after(xa,ya)
, segment_before(SEGMENT_CURVE), segment_after(SEGMENT_CURVE)
, lock(lock)
{}
void ControlPoint::onUpdateHandle(WhichHandle wh) {
// One handle has changed, update only the other one
if (lock == LOCK_DIR) {
getOther(wh) = -getHandle(wh) * getOther(wh).length() / getHandle(wh).length();
} else if (lock == LOCK_SIZE) {
getOther(wh) = -getHandle(wh);
}
}
void ControlPoint::onUpdateLock() {
// The lock has changed, avarage the handle values
if (lock == LOCK_DIR) {
// delta_before = x * delta_after
Vector2D dir = (delta_before - delta_after).normalized();
delta_before = dir * delta_before.length();
delta_after = dir * -delta_after.length();
} else if (lock == LOCK_SIZE) {
// delta_before = -delta_after
delta_before = (delta_before - delta_after) * 0.5;
delta_after = -delta_before;
}
}
Vector2D& ControlPoint::getHandle(WhichHandle wh) {
if (wh == HANDLE_BEFORE) {
return delta_before;
} else {
assert(wh == HANDLE_AFTER);
return delta_after;
}
}
Vector2D& ControlPoint::getOther(WhichHandle wh) {
if (wh == HANDLE_BEFORE) {
return delta_after;
} else {
assert(wh == HANDLE_AFTER);
return delta_before;
}
}
// ----------------------------------------------------------------------------- : SymbolPart
void SymbolPart::calculateBounds() {
min_pos = Vector2D::infinity();
max_pos = -Vector2D::infinity();
}
IMPLEMENT_REFLECTION(SymbolPart) {
REFLECT_IF_NOT_READING {
String type = typeName();
REFLECT(type);
}
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 if (type == _("group")) return new_intrusive<SymbolGroup>();
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
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();
}
}
}
SymbolShape::SymbolShape()
: combine(SYMBOL_COMBINE_OVERLAP), rotation_center(.5, .5)
{}
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);
}
return part;
}
void SymbolShape::enforceConstraints() {
for (int i = 0 ; i < (int)points.size() ; ++i) {
ControlPointP p1 = getPoint(i);
ControlPointP p2 = getPoint(i + 1);
p2->segment_before = p1->segment_after;
p1->onUpdateLock();
}
}
void SymbolShape::calculateBounds() {
min_pos = Vector2D::infinity();
max_pos = -Vector2D::infinity();
for (int i = 0 ; i < (int)points.size() ; ++i) {
segment_bounds(*getPoint(i), *getPoint(i + 1), min_pos, max_pos);
}
}
// ----------------------------------------------------------------------------- : 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 {
SymbolSymmetryP part(new SymbolSymmetry(*this));
// also clone the parts inside
FOR_EACH(p, part->parts) {
p = p->clone();
}
return part;
}
String SymbolSymmetry::expectedName() const {
return capitalize(kind == SYMMETRY_ROTATION ? _TYPE_("rotation") : _TYPE_("reflection"))
+ String::Format(_(" (%d)"), copies);
}
IMPLEMENT_REFLECTION(SymbolSymmetry) {
REFLECT_BASE(SymbolPart);
REFLECT(kind);
REFLECT(copies);
REFLECT(center);
REFLECT(handle);
REFLECT(parts);
REFLECT_IF_READING calculateBoundsNonRec();
}
// ----------------------------------------------------------------------------- : SymbolGroup
SymbolGroup::SymbolGroup() {
name = capitalize(_TYPE_("group"));
}
String SymbolGroup::typeName() const {
return _("group");
}
SymbolPartP SymbolGroup::clone() const {
SymbolGroupP part(new SymbolGroup(*this));
// also clone the parts inside
FOR_EACH(p, part->parts) {
p = p->clone();
}
return part;
}
bool SymbolGroup::isAncestor(const SymbolPart& that) const {
if (this == &that) return true;
FOR_EACH_CONST(p, parts) {
if (p->isAncestor(that)) return true;
}
return false;
}
void SymbolGroup::calculateBounds() {
FOR_EACH(p, parts) p->calculateBounds();
calculateBoundsNonRec();
}
void SymbolGroup::calculateBoundsNonRec() {
min_pos = Vector2D::infinity();
max_pos = -Vector2D::infinity();
FOR_EACH(p, parts) {
min_pos = piecewise_min(min_pos, p->min_pos);
max_pos = piecewise_max(max_pos, p->max_pos);
}
}
IMPLEMENT_REFLECTION(SymbolGroup) {
REFLECT_BASE(SymbolPart);
REFLECT(parts);
REFLECT_IF_READING calculateBoundsNonRec();
}
// ----------------------------------------------------------------------------- : Symbol
IMPLEMENT_REFLECTION(Symbol) {
REFLECT(parts);
REFLECT_IF_READING calculateBoundsNonRec();
}
double Symbol::aspectRatio() const {
// Margin between the edges and the symbol.
// 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)));
// 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);
// The aspect ratio, i.e. width/height
if (delta > 0) {
return 1 / (1 - delta);
} else {
return 1 + delta;
}
}
// ----------------------------------------------------------------------------- : Default symbol
// A default symbol part, a square, moved by d
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));
part->points.push_back(new_intrusive2<ControlPoint>(d + .8, d + .2));
part->name = _("Square");
return part;
}
// A default symbol, a square
SymbolP default_symbol() {
SymbolP symbol = new_intrusive<Symbol>();
symbol->parts.push_back(default_symbol_part(0));
return symbol;
}
// ----------------------------------------------------------------------------- : SymbolView
SymbolView::SymbolView() {}
SymbolView::~SymbolView() {
if (symbol) symbol->actions.removeListener(this);
}
void SymbolView::setSymbol(const SymbolP& newSymbol) {
// no longer listening to old symbol
if (symbol) symbol->actions.removeListener(this);
symbol = newSymbol;
// start listening to new symbol
if (symbol) symbol->actions.addListener(this);
onChangeSymbol();
}