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
+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