diff --git a/data/en.mse-locale/locale b/data/en.mse-locale/locale index e745bce9..b862e2fa 100644 --- a/data/en.mse-locale/locale +++ b/data/en.mse-locale/locale @@ -78,14 +78,17 @@ menu: store symbol: S&tore Ctrl+Enter close symbol editor:Close Alt+F4 - duplicate: Duplicate Ctrl+D + duplicate: &Duplicate Ctrl+D + group: &Group Ctrl+G + ungroup: &Ungroup Ctrl+U tool: &Tool select: &Select F5 rotate: &Rotate F6 points: &Points F7 basic shapes: &Basic Shapes F8 - paint: P&aint F9 + symmetry: S&ymmetry F9 + paint: P&aint F10 ############################################################## Menu help texts help: @@ -179,6 +182,8 @@ help: close symbol editor:Closes the symbol editor duplicate: Duplicates the selected shapes + group: Group the selected shapes together + ungroup: Break up the selected group grid: Show gridlines snap: Snap shapes and points to gridlines @@ -188,6 +193,7 @@ help: rotate: Rotate and shear shapes points: Edit control points for a shape in the symbol basic shapes: Draw basic shapes, such as rectangles and circles + symmetry: Add symmetries to the symbol paint: Paint on the shape using a paintbrush select editor: @@ -247,6 +253,7 @@ tool: rotate: Rotate points: Points basic shapes: Basic Shapes + symmetry: Symmetry paint: Paint merge: Merge @@ -261,6 +268,9 @@ tool: polygon: Polygon star: Star + rotation: Rotation + reflection: Reflection + line segment: Line curve segment: Curve free point: Free @@ -314,7 +324,8 @@ tooltip: rotate: Rotate (F6) points: Points (F7) basic shapes: Basic Shapes (F8) - paint: Paint on Shape (F9) + symmetry: Symmetry (F9) + paint: Paint on Shape (F10) merge: Merge with shapes below subtract: Subtract from shapes below @@ -328,6 +339,9 @@ tooltip: polygon: Polygon star: Star + rotation: Rotational symmetry (wheel) + reflection: Reflectional symmetry (mirror) + line segment: To straigt line curve segment: To curve free point: Unlock point @@ -513,6 +527,8 @@ action: reorder parts: Reorder change combine mode:Change combine mode change shape name: Change shape name + group parts: Group + ungroup parts: Ungroup # Symbol Part Actions convert to line: Convert to line @@ -608,6 +624,10 @@ type: polygon: polygon star: star + rotation: rotation + reflection: reflection + group: group + point: point points: points handle: handle diff --git a/src/data/action/symbol.cpp b/src/data/action/symbol.cpp index 4508d87e..7113e85e 100644 --- a/src/data/action/symbol.cpp +++ b/src/data/action/symbol.cpp @@ -62,13 +62,12 @@ void SymbolPartMoveAction::movePart(SymbolPart& part) { } } else if (SymbolSymmetry* s = part.isSymbolSymmetry()) { s->center -= moved; - } else if (SymbolGroup* g = part.isSymbolGroup()) { + } + if (SymbolGroup* g = part.isSymbolGroup()) { FOR_EACH(p, g->parts) { movePart(*p); } g->calculateBoundsNonRec(); - } else { - throw InternalError(_("Invalid symbol part type")); } } @@ -108,13 +107,12 @@ void SymbolPartMatrixAction::transform(SymbolPart& part, const Matrix2D& m) { } else if (SymbolSymmetry* s = part.isSymbolSymmetry()) { s->center = (s->center - center) * m + center; s->handle = s->handle * m; - } else if (SymbolGroup* g = part.isSymbolGroup()) { + } + if (SymbolGroup* g = part.isSymbolGroup()) { FOR_EACH(p, g->parts) { transform(*p, m); } g->calculateBoundsNonRec(); - } else { - throw InternalError(_("Invalid symbol part type")); } } @@ -284,13 +282,12 @@ void SymbolPartScaleAction::transformPart(SymbolPart& part) { } else if (SymbolSymmetry* s = part.isSymbolSymmetry()) { transform(s->center); s->handle.mul(new_size.div(old_size)); - } else if (SymbolGroup* g = part.isSymbolGroup()) { + } + if (SymbolGroup* g = part.isSymbolGroup()) { FOR_EACH(p, g->parts) { transformPart(*p); } g->calculateBoundsNonRec(); - } else { - throw InternalError(_("Invalid symbol part type")); } } diff --git a/src/gfx/bezier.cpp b/src/gfx/bezier.cpp index 75800d85..43e64bc9 100644 --- a/src/gfx/bezier.cpp +++ b/src/gfx/bezier.cpp @@ -59,7 +59,7 @@ void deCasteljau(const Vector2D& a1, Vector2D& a21, Vector2D& a34, const Vector2 // ----------------------------------------------------------------------------- : Drawing -void curve_subdivide(const BezierCurve& c, const Vector2D& p0, const Vector2D& p1, double t0, double t1, const Rotation& rot, vector& out, UInt level) { +void curve_subdivide(const BezierCurve& c, const Vector2D& p0, const Vector2D& p1, double t0, double t1, const Vector2D& origin, const Matrix2D& m, vector& out, UInt level) { if (level <= 0) return; double midtime = (t0+t1) * 0.5f; Vector2D midpoint = c.pointAt(midtime); @@ -70,23 +70,23 @@ void curve_subdivide(const BezierCurve& c, const Vector2D& p0, const Vector2D& p double treshold = fabs( atan2(d0.x,d0.y) - atan2(d1.x,d1.y)) * (p0-p1).lengthSqr(); bool subdivide = treshold >= .0001; // subdivide left - curve_subdivide(c, p0, midpoint, t0, midtime, rot, out, level - 1); + curve_subdivide(c, p0, midpoint, t0, midtime, origin, m, out, level - 1); // add midpoint if (subdivide) { - out.push_back(rot.tr(midpoint)); + out.push_back(origin + midpoint * m); } // subdivide right - curve_subdivide(c, midpoint, p1, midtime, t1, rot, out, level - 1); + curve_subdivide(c, midpoint, p1, midtime, t1, origin, m, out, level - 1); } -void segment_subdivide(const ControlPoint& p0, const ControlPoint& p1, const Rotation& rot, vector& out) { +void segment_subdivide(const ControlPoint& p0, const ControlPoint& p1, const Vector2D& origin, const Matrix2D& m, vector& out) { assert(p0.segment_after == p1.segment_before); // always the start - out.push_back(rot.tr(p0.pos)); + out.push_back(origin + p0.pos * m); if (p0.segment_after == SEGMENT_CURVE) { // need more points? BezierCurve curve(p0,p1); - curve_subdivide(curve, p0.pos, p1.pos, 0, 1, rot, out, 5); + curve_subdivide(curve, p0.pos, p1.pos, 0, 1, origin, m, out, 5); } } diff --git a/src/gfx/bezier.hpp b/src/gfx/bezier.hpp index 9e0ba6cd..0b987e6e 100644 --- a/src/gfx/bezier.hpp +++ b/src/gfx/bezier.hpp @@ -15,7 +15,7 @@ // ----------------------------------------------------------------------------- : Includes #include -#include +#include #include // ----------------------------------------------------------------------------- : Evaluation @@ -65,9 +65,9 @@ void deCasteljau(const Vector2D& a1, Vector2D& a21, Vector2D& a34, const Vector2 /// Devide a segment into a number of straight lines for display purposes /** Adds the resulting corner points of those lines to out, the last point is not added. - * All points are converted to display coordinates using rot.tr + * All points are converted to display coordinates by multiplying with m and adding origin */ -void segment_subdivide(const ControlPoint& p0, const ControlPoint& p1, const Rotation& rot, vector& out); +void segment_subdivide(const ControlPoint& p0, const ControlPoint& p1, const Vector2D& origin, const Matrix2D& m, vector& out); // ----------------------------------------------------------------------------- : Bounds diff --git a/src/gui/symbol/control.cpp b/src/gui/symbol/control.cpp index cb96e082..0101d9c3 100644 --- a/src/gui/symbol/control.cpp +++ b/src/gui/symbol/control.cpp @@ -235,7 +235,7 @@ void SymbolControl::onChar(wxKeyEvent& ev) { void SymbolControl::onSize(wxSizeEvent& ev) { wxSize s = GetClientSize(); - rotation.setZoom(min(s.GetWidth(), s.GetHeight())); + setZoom(min(s.GetWidth(), s.GetHeight())); Refresh(false); } void SymbolControl::onUpdateUI(wxUpdateUIEvent& ev) { @@ -248,6 +248,9 @@ void SymbolControl::onUpdateUI(wxUpdateUIEvent& ev) { // can only edit points when a shape is available ev.Enable(selected_parts.getAShape()); } + if (ev.GetId() == ID_MODE_SYMMETRY) { + ev.Enable(!selected_parts.empty()); + } break; case ID_MODE_PAINT: ev.Enable(false); // TODO diff --git a/src/gui/symbol/selection.cpp b/src/gui/symbol/selection.cpp index 36e0b549..4fb3007d 100644 --- a/src/gui/symbol/selection.cpp +++ b/src/gui/symbol/selection.cpp @@ -24,10 +24,11 @@ void SymbolPartsSelection::clear() { } bool SymbolPartsSelection::select(const SymbolPartP& part, SelectMode mode) { + assert(part); // make sure part is not the decendent of a part that is already selected if (mode != SELECT_OVERRIDE) { FOR_EACH(s, selection) { - if (isAncestor(s.get(), part.get())) return false; + if (s != part && s->isAncestor(*part)) return false; } } // select @@ -70,15 +71,6 @@ void SymbolPartsSelection::clearChildren(SymbolPart* part) { } } -bool SymbolPartsSelection::isAncestor(SymbolPart* ancestor, SymbolPart* part) { - if (SymbolGroup* g = ancestor->isSymbolGroup()) { - FOR_EACH(p, g->parts) { - if (part == p.get()) return true; - if (isAncestor(p.get(), part)) return true; - } - } - return false; -} // ----------------------------------------------------------------------------- : Position based @@ -116,7 +108,7 @@ bool SymbolPartsSelection::selectRect(const Vector2D& a, const Vector2D& b, cons } bool SymbolPartsSelection::selectRect(const SymbolGroup& parent, const Vector2D& a, const Vector2D& b, const Vector2D& c) { bool changes = false; - FOR_EACH_CONST(p, root->parts) { + FOR_EACH_CONST(p, parent.parts) { bool in_ab = (p->min_pos.x >= min(a.x, b.x) && p->min_pos.y >= min(a.y, b.y) && p->max_pos.x <= max(a.x, b.x) && p->max_pos.y <= max(a.y, b.y)); bool in_bc = (p->min_pos.x >= min(a.x, c.x) && p->min_pos.y >= min(a.y, c.y) && p->max_pos.x <= max(a.x, c.x) && p->max_pos.y <= max(a.y, c.y)); if (in_ab != in_bc) { diff --git a/src/gui/symbol/selection.hpp b/src/gui/symbol/selection.hpp index 95df0d8e..af38b955 100644 --- a/src/gui/symbol/selection.hpp +++ b/src/gui/symbol/selection.hpp @@ -73,8 +73,6 @@ class SymbolPartsSelection { /// Make sure not both a parent and its child/decendant are selected void clearChildren (SymbolPart* part); - /// Is a part another's ancestor? - static bool isAncestor(SymbolPart* ancestor, SymbolPart* part); }; // ----------------------------------------------------------------------------- : EOF diff --git a/src/gui/symbol/symmetry_editor.hpp b/src/gui/symbol/symmetry_editor.hpp index 918afc89..1bafcb5d 100644 --- a/src/gui/symbol/symmetry_editor.hpp +++ b/src/gui/symbol/symmetry_editor.hpp @@ -17,6 +17,8 @@ /// Editor for adding symmetries class SymbolSymmetryEditor : public SymbolEditorBase { public: + /** The symmetry parameter is optional, if it is not set, then only new ones can be created */ + //%SymbolSymmetryEditor(SymbolControl* control, SymbolSymmetryP symmetry); SymbolSymmetryEditor(SymbolControl* control); // --------------------------------------------------- : Drawing diff --git a/src/gui/symbol/window.cpp b/src/gui/symbol/window.cpp index 7bd3fa05..ee2a3b9d 100644 --- a/src/gui/symbol/window.cpp +++ b/src/gui/symbol/window.cpp @@ -29,7 +29,7 @@ SymbolWindow::SymbolWindow(Window* parent) { SymbolWindow::SymbolWindow(Window* parent, const String& filename) { // open file - Reader reader(filename); + Reader reader(new_shared1(filename), filename); SymbolP symbol; reader.handle_greedy(symbol); init(parent, symbol); diff --git a/src/render/symbol/viewer.cpp b/src/render/symbol/viewer.cpp index 53e77d46..32fa7b88 100644 --- a/src/render/symbol/viewer.cpp +++ b/src/render/symbol/viewer.cpp @@ -15,12 +15,12 @@ DECLARE_TYPEOF_COLLECTION(SymbolPartP); // ----------------------------------------------------------------------------- : Simple rendering Image render_symbol(const SymbolP& symbol, double border_radius, int size) { - SymbolViewer viewer(symbol, border_radius); + SymbolViewer viewer(symbol, size, border_radius); Bitmap bmp(size, size); wxMemoryDC dc; dc.SelectObject(bmp); clearDC(dc, Color(0,128,0)); - viewer.rotation.setZoom(size); + viewer.setZoom(size); viewer.draw(dc); dc.SelectObject(wxNullBitmap); return bmp.ConvertToImage(); @@ -28,13 +28,20 @@ Image render_symbol(const SymbolP& symbol, double border_radius, int size) { // ----------------------------------------------------------------------------- : Constructor -SymbolViewer::SymbolViewer(const SymbolP& symbol, double border_radius) +SymbolViewer::SymbolViewer(const SymbolP& symbol, double size, double border_radius) : border_radius(border_radius) - , rotation(0, RealRect(0,0,500,500), 500) + , rotation(0, RealRect(0,0,size,size), size) + , multiply(size,0,0,size) + , origin(0,0) { setSymbol(symbol); } +void SymbolViewer::setZoom(double zoom) { + rotation.setZoom(zoom); + multiply = Matrix2D(zoom,0, 0,zoom); +} + // ----------------------------------------------------------------------------- : Drawing typedef shared_ptr MemoryDCP; @@ -73,16 +80,16 @@ void SymbolViewer::draw(DC& dc) { } } // Draw all parts - combineSymbolPart(dc, *symbol, paintedSomething, buffersFilled, borderDC, interiorDC); + combineSymbolPart(dc, *symbol, paintedSomething, buffersFilled, true, borderDC, interiorDC); // Output the final parts from the buffer if (buffersFilled) { combineBuffers(dc, borderDC.get(), interiorDC.get()); } } -void SymbolViewer::combineSymbolPart(DC& dc, const SymbolPart& part, bool& paintedSomething, bool& buffersFilled, MemoryDCP& borderDC, MemoryDCP& interiorDC) { +void SymbolViewer::combineSymbolPart(DC& dc, const SymbolPart& part, bool& paintedSomething, bool& buffersFilled, bool allow_overlap, MemoryDCP& borderDC, MemoryDCP& interiorDC) { if (const SymbolShape* s = part.isSymbolShape()) { - if (s->combine == SYMBOL_COMBINE_OVERLAP && buffersFilled) { + if (s->combine == SYMBOL_COMBINE_OVERLAP && buffersFilled && allow_overlap) { // We will be overlapping some previous parts, write them to the screen combineBuffers(dc, borderDC.get(), interiorDC.get()); // Clear the buffers @@ -111,14 +118,61 @@ void SymbolViewer::combineSymbolPart(DC& dc, const SymbolPart& part, bool& paint combineSymbolShape(*s, *borderDC, *interiorDC, false, false); buffersFilled = true; } - // Paint symmetric versions of this part - // TODO } else if (const SymbolSymmetry* s = part.isSymbolSymmetry()) { - // symmetry, already handled above + // Draw all parts, in reverse order (bottom to top), also draw rotated copies + double b = 2 * atan2(s->handle.y, s->handle.x); + Matrix2D old_m = multiply; + Vector2D old_o = origin; + int copies = s->kind == SYMMETRY_REFLECTION ? s->copies / 2 * 2 : s->copies; + FOR_EACH_CONST_REVERSE(p, s->parts) { + for (int i = 0 ; i < copies ; ++i) { + if (s->clip) { + // todo: clip + } + double a = i * 2 * M_PI / copies; + if (s->kind == SYMMETRY_ROTATION || i % 2 == 0) { + // set matrix + // Calling: + // - p the input point + // - p' the output point + // - rot our rotation matrix + // - d out origin + // - o the current origin (old_o) + // - m the current matrix (old_m) + // We want: + // p' = ((p - d) * rot + d) * m + o + // = (p * rot - d * rot + d) * m + o + // = p * rot * m + (d - d * rot) * m + o + Matrix2D rot(cos(a),-sin(a), sin(a),cos(a)); + multiply.mx = rot.mx * old_m; + multiply.my = rot.my * old_m; + origin = old_o + (s->center - s->center * rot) * old_m; + } else { + // reflection + // Calling angle = b + // Matrix2D ref(cos(b),sin(b), sin(b),-cos(b)); + // Matrix2D rot(cos(a),-sin(a), sin(a),cos(a)); + // + // ref * rot + // /cos b sin b\ /cos a -sin a\ + // = \sin b -cos b/ \sin a cos a/ + // = /cos(a+b) sin(a+b)\ + // \sin(a+b) -cos(a+b)/ + Matrix2D rot(cos(a+b),sin(a+b), sin(a+b),-cos(a+b)); + multiply.mx = rot.mx * old_m; + multiply.my = rot.my * old_m; + origin = old_o + (s->center - s->center * rot) * old_m; + } + // draw rotated copy + combineSymbolPart(dc, *p, paintedSomething, buffersFilled, allow_overlap && i == 0, borderDC, interiorDC); + } + } + multiply = old_m; + origin = old_o; } else if (const SymbolGroup* g = part.isSymbolGroup()) { // Draw all parts, in reverse order (bottom to top) FOR_EACH_CONST_REVERSE(p, g->parts) { - combineSymbolPart(dc, *p, paintedSomething, buffersFilled, borderDC, interiorDC); + combineSymbolPart(dc, *p, paintedSomething, buffersFilled, allow_overlap, borderDC, interiorDC); } } } @@ -139,7 +193,7 @@ void SymbolViewer::highlightPart(DC& dc, const SymbolShape& shape, HighlightStyl vector points; size_t size = shape.points.size(); for(size_t i = 0 ; i < size ; ++i) { - segment_subdivide(*shape.getPoint((int)i), *shape.getPoint((int)i+1), rotation, points); + segment_subdivide(*shape.getPoint((int)i), *shape.getPoint((int)i+1), origin, multiply, points); } // draw if (style == HIGHLIGHT_BORDER) { @@ -238,7 +292,7 @@ void SymbolViewer::drawSymbolShape(const SymbolShape& shape, DC* border, DC* int vector points; size_t size = shape.points.size(); for(size_t i = 0 ; i < size ; ++i) { - segment_subdivide(*shape.getPoint((int)i), *shape.getPoint((int)i+1), rotation, points); + segment_subdivide(*shape.getPoint((int)i), *shape.getPoint((int)i+1), origin, multiply, points); } // draw border if (border && border_radius > 0) { diff --git a/src/render/symbol/viewer.hpp b/src/render/symbol/viewer.hpp index ecb7817b..32d99122 100644 --- a/src/render/symbol/viewer.hpp +++ b/src/render/symbol/viewer.hpp @@ -32,14 +32,18 @@ enum HighlightStyle class SymbolViewer : public SymbolView { public: // --------------------------------------------------- : Data - SymbolViewer(const SymbolP& symbol, double border_radius = 0.05); + SymbolViewer(const SymbolP& symbol, double size = 500, double border_radius = 0.05); // drawing double border_radius; // --------------------------------------------------- : Point translation + void setZoom(double zoom); + Rotation rotation; ///< Object that handles rotation, scaling and translation + Matrix2D multiply; ///< Scaling/rotation of actual parts + Vector2D origin; ///< Origin of parts // --------------------------------------------------- : Drawing @@ -57,7 +61,7 @@ class SymbolViewer : public SymbolView { typedef shared_ptr MemoryDCP; /// Combine a symbol part with the dc - void SymbolViewer::combineSymbolPart(DC& dc, const SymbolPart& part, bool& paintedSomething, bool& buffersFilled, MemoryDCP& borderDC, MemoryDCP& interiorDC); + void SymbolViewer::combineSymbolPart(DC& dc, const SymbolPart& part, bool& paintedSomething, bool& buffersFilled, bool allow_overlap, MemoryDCP& borderDC, MemoryDCP& interiorDC); /// Combines a symbol part with what is currently drawn, the border and interior are drawn separatly /** directB/directI are true if the border/interior is the screen dc, false if it