mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-11 05:07:00 -04:00
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user