20 #include "magicWandMode.h" |
20 #include "magicWandMode.h" |
21 #include "../lddocument.h" |
21 #include "../lddocument.h" |
22 #include "../mainwindow.h" |
22 #include "../mainwindow.h" |
23 #include "../canvas.h" |
23 #include "../canvas.h" |
24 |
24 |
25 MagicWandMode::MagicWandMode (Canvas* canvas) : |
25 MagicWandMode::MagicWandMode(Canvas* canvas) : |
26 Super (canvas) |
26 Super {canvas} |
27 { |
27 { |
|
28 QSet<LineSegment> boundarySegments; |
|
29 |
28 // Get vertex<->object data |
30 // Get vertex<->object data |
29 for (LDObject* obj : currentDocument()->objects()) |
31 for (const QModelIndex& index : currentDocument()->indices()) |
30 { |
32 { |
31 // Note: this deliberately only takes vertex-objects into account. |
33 // Note: this deliberately only takes vertex-objects into account. |
32 // The magic wand does not process subparts. |
34 // The magic wand does not process subparts. |
33 for (int i = 0; i < obj->numVertices(); ++i) |
35 LDObject* object = currentDocument()->lookup(index); |
34 m_vertices[obj->vertex (i)] << obj; |
36 |
|
37 for (int i = 0; i < object->numVertices(); ++i) |
|
38 { |
|
39 LineSegment segment { |
|
40 object->vertex(i), |
|
41 object->vertex((i + 1) % object->numVertices()) |
|
42 }; |
|
43 m_vertices[object->vertex(i)].insert(index); |
|
44 |
|
45 if (object->type() == LDObjectType::EdgeLine) |
|
46 boundarySegments.insert(segment); |
|
47 else |
|
48 this->segments[segment].insert(index); |
|
49 } |
35 } |
50 } |
|
51 |
|
52 // Remove all edge lines from the set of available segments because they get in the way. |
|
53 for (const LineSegment& boundarySegment : boundarySegments) |
|
54 this->segments.remove(boundarySegment); |
36 } |
55 } |
37 |
56 |
38 EditModeType MagicWandMode::type() const |
57 EditModeType MagicWandMode::type() const |
39 { |
58 { |
40 return EditModeType::MagicWand; |
59 return EditModeType::MagicWand; |
41 } |
60 } |
42 |
61 |
43 void MagicWandMode::fillBoundaries (LDObject* obj, QVector<BoundaryType>& boundaries, QSet<LDObject*>& candidates) |
62 void MagicWandMode::edgeFill( |
44 { |
63 QModelIndex index, |
45 // All boundaries obviously share vertices with the object, therefore they're all in the list |
64 QItemSelection& selection, |
46 // of candidates. |
65 QSet<QModelIndex>& processed |
47 for (LDObject* candidate : candidates) |
66 ) const { |
|
67 QItemSelection result; |
|
68 processed.insert(index); |
|
69 result.select(index, index); |
|
70 QSet<QPersistentModelIndex> candidates; |
|
71 LDObject* object = currentDocument()->lookup(index); |
|
72 |
|
73 // Get the list of objects that touch this object, i.e. share a vertex with it. |
|
74 for (int i = 0; i < object->numVertices(); ++i) |
|
75 candidates |= m_vertices[object->vertex(i)]; |
|
76 |
|
77 candidates.remove(index); |
|
78 |
|
79 for (const QModelIndex& candidate : candidates) |
48 { |
80 { |
49 if (not isOneOf(candidate->type(), LDObjectType::EdgeLine, LDObjectType::ConditionalEdge) |
81 LDObject* candidateObject = currentDocument()->lookup(candidate); |
50 or candidate->vertex (0) == candidate->vertex (1)) |
82 |
|
83 if (candidateObject->type() == LDObjectType::EdgeLine |
|
84 and candidateObject->color() == object->color() |
|
85 ) { |
|
86 selection.select(candidate, candidate); |
|
87 |
|
88 if (not processed.contains(candidate)) |
|
89 edgeFill(candidate, selection, processed); |
|
90 } |
|
91 } |
|
92 } |
|
93 |
|
94 void MagicWandMode::surfaceFill( |
|
95 QModelIndex index, |
|
96 QItemSelection& selection, |
|
97 QSet<QModelIndex>& processed |
|
98 ) const { |
|
99 LDObject* object = currentDocument()->lookup(index); |
|
100 selection.select(index, index); |
|
101 processed.insert(index); |
|
102 |
|
103 for (int i = 0; i < object->numVertices(); i += 1) |
|
104 { |
|
105 Vertex v_1 = object->vertex(i); |
|
106 Vertex v_2 = object->vertex((i + 1) % object->numVertices()); |
|
107 LineSegment segment {v_1, v_2}; |
|
108 |
|
109 for (const QModelIndex& candidate : this->segments[segment]) |
51 { |
110 { |
52 continue; |
111 if (currentDocument()->lookup(candidate)->color() == object->color()) |
53 } |
112 { |
|
113 selection.select(candidate, candidate); |
54 |
114 |
55 int matches = 0; |
115 if (not processed.contains(candidate)) |
56 |
116 surfaceFill(candidate, selection, processed); |
57 for (int i = 0; i < obj->numVertices(); ++i) |
|
58 { |
|
59 if (not isOneOf (obj->vertex (i), candidate->vertex (0), candidate->vertex (1))) |
|
60 continue; |
|
61 |
|
62 if (++matches == 2) |
|
63 { |
|
64 // Boundary found. If it's an edgeline, add it to the boundaries list, if a |
|
65 // conditional line, select it. |
|
66 if (candidate->type() == LDObjectType::ConditionalEdge) |
|
67 m_selection << candidate; |
|
68 else |
|
69 boundaries.append (std::make_tuple (candidate->vertex (0), candidate->vertex (1))); |
|
70 |
|
71 break; |
|
72 } |
117 } |
73 } |
118 } |
74 } |
119 } |
75 } |
120 } |
76 |
121 |
77 void MagicWandMode::doMagic (LDObject* obj, MagicWandMode::MagicType type) |
122 QItemSelection MagicWandMode::doMagic(const QModelIndex& index) const |
78 { |
123 { |
79 if (obj == nullptr) |
124 QItemSelection selection; |
|
125 LDObject* object = currentDocument()->lookup(index); |
|
126 |
|
127 if (object) |
80 { |
128 { |
81 if (type == Set) |
129 QSet<QModelIndex> processed; |
82 currentDocument()->clearSelection(); |
|
83 |
130 |
84 return; |
131 if (object->type() == LDObjectType::EdgeLine) |
|
132 edgeFill(index, selection, processed); |
|
133 else if (object->numPolygonVertices() >= 3) |
|
134 surfaceFill(index, selection, processed); |
85 } |
135 } |
86 |
136 |
87 int matchesneeded = 0; |
137 return selection; |
88 QVector<BoundaryType> boundaries; |
|
89 LDObjectType objtype = obj->type(); |
|
90 |
|
91 if (type != InternalRecursion) |
|
92 { |
|
93 m_selection.clear(); |
|
94 m_selection.append (obj); |
|
95 } |
|
96 |
|
97 switch (obj->type()) |
|
98 { |
|
99 case LDObjectType::EdgeLine: |
|
100 case LDObjectType::ConditionalEdge: |
|
101 matchesneeded = 1; |
|
102 break; |
|
103 |
|
104 case LDObjectType::Triangle: |
|
105 case LDObjectType::Quadrilateral: |
|
106 matchesneeded = 2; |
|
107 break; |
|
108 |
|
109 default: |
|
110 return; |
|
111 } |
|
112 |
|
113 QSet<LDObject*> candidates; |
|
114 |
|
115 // Get the list of objects that touch this object, i.e. share a vertex |
|
116 // with this. |
|
117 for (int i = 0; i < obj->numVertices(); ++i) |
|
118 candidates += m_vertices[obj->vertex (i)]; |
|
119 |
|
120 // If we're dealing with surfaces, get a list of boundaries. |
|
121 if (matchesneeded > 1) |
|
122 fillBoundaries (obj, boundaries, candidates); |
|
123 |
|
124 for (LDObject* candidate : candidates) |
|
125 { |
|
126 try |
|
127 { |
|
128 // If we're doing this on lines, we need exact type match. Surface types (quads and |
|
129 // triangles) can be mixed. Also don't consider self a candidate, and don't consider |
|
130 // objects we have already processed. |
|
131 if ((candidate == obj) or |
|
132 (candidate->color() != obj->color()) or |
|
133 (m_selection.contains (candidate)) or |
|
134 (matchesneeded == 1 and (candidate->type() != objtype)) or |
|
135 ((candidate->numVertices() > 2) ^ (matchesneeded == 2))) |
|
136 { |
|
137 throw 0; |
|
138 } |
|
139 |
|
140 // Now ensure the two objects share enough vertices. |
|
141 QVector<Vertex> matches; |
|
142 |
|
143 for (int i = 0; i < obj->numVertices(); ++i) |
|
144 { |
|
145 for (int j = 0; j < candidate->numVertices(); ++j) |
|
146 { |
|
147 if (obj->vertex(i) == candidate->vertex(j)) |
|
148 { |
|
149 matches << obj->vertex(i); |
|
150 break; |
|
151 } |
|
152 } |
|
153 } |
|
154 |
|
155 if (countof(matches) < matchesneeded) |
|
156 throw 0; // Not enough matches. |
|
157 |
|
158 // Check if a boundary gets in between the objects. |
|
159 for (auto boundary : boundaries) |
|
160 { |
|
161 if (isOneOf (matches[0], std::get<0> (boundary), std::get<1> (boundary)) and |
|
162 isOneOf (matches[1], std::get<0> (boundary), std::get<1> (boundary))) |
|
163 { |
|
164 throw 0; |
|
165 } |
|
166 } |
|
167 |
|
168 m_selection.append (candidate); |
|
169 doMagic (candidate, InternalRecursion); |
|
170 } |
|
171 catch (int&) |
|
172 { |
|
173 continue; |
|
174 } |
|
175 } |
|
176 |
|
177 switch (type) |
|
178 { |
|
179 case Set: |
|
180 currentDocument()->clearSelection(); |
|
181 case Additive: |
|
182 for (LDObject* obj : m_selection) |
|
183 currentDocument()->addToSelection(obj); |
|
184 break; |
|
185 |
|
186 case Subtractive: |
|
187 for (LDObject* obj : m_selection) |
|
188 currentDocument()->removeFromSelection(obj); |
|
189 break; |
|
190 |
|
191 case InternalRecursion: |
|
192 break; |
|
193 } |
|
194 } |
138 } |
195 |
139 |
196 bool MagicWandMode::mouseReleased (MouseEventData const& data) |
140 bool MagicWandMode::mouseReleased (MouseEventData const& data) |
197 { |
141 { |
198 if (Super::mouseReleased (data)) |
142 if (Super::mouseReleased (data)) |
199 return true; |
143 return true; |
200 |
144 |
201 if (data.releasedButtons & Qt::LeftButton and not data.mouseMoved) |
145 if (data.releasedButtons & Qt::LeftButton and not data.mouseMoved) |
202 { |
146 { |
203 MagicType wandtype = MagicWandMode::Set; |
147 QItemSelection selection = this->doMagic(renderer()->pick(data.ev->x(), data.ev->y())); |
|
148 QItemSelectionModel::SelectionFlags command = QItemSelectionModel::ClearAndSelect; |
204 |
149 |
205 if (data.keymods & Qt::ShiftModifier) |
150 if (data.keymods & Qt::ShiftModifier) |
206 wandtype = MagicWandMode::Additive; |
151 command = QItemSelectionModel::Select; |
207 else if (data.keymods & Qt::ControlModifier) |
152 else if (data.keymods & Qt::ControlModifier) |
208 wandtype = MagicWandMode::Subtractive; |
153 command = QItemSelectionModel::Deselect; |
209 |
154 |
210 doMagic (renderer()->pick (data.ev->x(), data.ev->y()), wandtype); |
155 renderer()->selectionModel()->select(selection, command); |
211 return true; |
156 return true; |
212 } |
157 } |
213 |
158 |
214 return false; |
159 return false; |
215 } |
160 } |