150 }; |
169 }; |
151 static const Pens brightPens{ |
170 static const Pens brightPens{ |
152 .pointBrush = {Qt::white}, |
171 .pointBrush = {Qt::white}, |
153 .pointPen = {QBrush{Qt::black}, 2.0}, |
172 .pointPen = {QBrush{Qt::black}, 2.0}, |
154 .polygonPen = {QBrush{Qt::black}, 2.0, Qt::DashLine}, |
173 .polygonPen = {QBrush{Qt::black}, 2.0, Qt::DashLine}, |
155 .badPolygonPen = {QBrush{Qt::red}, 2.0, Qt::DashLine}, |
|
156 .greenPolygonBrush = {QColor{64, 255, 128, 192}}, |
174 .greenPolygonBrush = {QColor{64, 255, 128, 192}}, |
157 .redPolygonBrush = {QColor{255, 96, 96, 192}}, |
175 .redPolygonBrush = {QColor{255, 96, 96, 192}}, |
158 }; |
176 }; |
159 static const Pens darkPens{ |
177 static const Pens darkPens{ |
160 .pointBrush = {Qt::black}, |
178 .pointBrush = {Qt::black}, |
161 .pointPen = {QBrush{Qt::white}, 2.0}, |
179 .pointPen = {QBrush{Qt::white}, 2.0}, |
162 .polygonPen = {QBrush{Qt::white}, 2.0, Qt::DashLine}, |
180 .polygonPen = {QBrush{Qt::white}, 2.0, Qt::DashLine}, |
163 .badPolygonPen = {QBrush{Qt::red}, 2.0, Qt::DashLine}, |
|
164 .greenPolygonBrush = {QColor{64, 255, 128, 192}}, |
181 .greenPolygonBrush = {QColor{64, 255, 128, 192}}, |
165 .redPolygonBrush = {QColor{255, 96, 96, 192}}, |
182 .redPolygonBrush = {QColor{255, 96, 96, 192}}, |
166 }; |
183 }; |
167 const Pens& pens = (this->renderer->isDark() ? darkPens : brightPens); |
184 const Pens& pens = (this->renderer->isDark() ? darkPens : brightPens); |
168 switch(this->mode) { |
185 switch(this->mode) { |
169 case SelectMode: |
186 case SelectMode: |
170 break; |
187 break; |
171 case DrawMode: |
188 case DrawMode: |
172 painter->setPen(this->isconcave ? pens.badPolygonPen : pens.polygonPen); |
189 painter->setPen(pens.polygonPen); |
173 for (const ModelAction& action : this->actions()) { |
190 for (const ModelAction& action : this->actions()) { |
174 const opt<std::vector<glm::vec3>> points = modelActionPoints(action); |
191 const opt<std::vector<glm::vec3>> points = modelActionPoints(action); |
175 if (points.has_value()) { |
192 if (points.has_value()) { |
176 if (worldPolygonWinding(*points, this->renderer) == Winding::Clockwise) { |
193 if (worldPolygonWinding(*points, this->renderer) == Winding::Clockwise) { |
177 painter->setBrush(pens.greenPolygonBrush); |
194 painter->setBrush(pens.greenPolygonBrush); |
199 painter->drawEllipse(pos, 5, 5); |
216 painter->drawEllipse(pos, 5, 5); |
200 painter->drawText(pos + QPointF{5, 5}, vectorToString(*this->worldPosition)); |
217 painter->drawText(pos + QPointF{5, 5}, vectorToString(*this->worldPosition)); |
201 } |
218 } |
202 } |
219 } |
203 |
220 |
204 void EditTools::updatePreviewPolygon() |
|
205 { |
|
206 if (this->polygon.size() > 2) { |
|
207 this->isconcave = not isConvex(this->polygon); |
|
208 } |
|
209 } |
|
210 |
|
211 void EditTools::removeLastPoint() |
221 void EditTools::removeLastPoint() |
212 { |
222 { |
213 if (this->polygon.size() > 1) { |
223 if (this->polygon.size() > 1) { |
214 this->polygon.erase(this->polygon.end() - 1); |
224 this->polygon.erase(this->polygon.end() - 1); |
215 } |
225 } |
216 } |
226 } |
217 |
227 |
218 template<typename T> |
228 bool EditTools::isCloseToExistingPoints() const |
219 bool isCloseToExistingPoints(T begin, T end, const glm::vec3 &pos) |
229 { |
220 { |
230 if (this->worldPosition.has_value()) { |
221 return std::any_of(begin, end, [&pos](const glm::vec3& p){ |
231 const glm::vec3& pos = *this->worldPosition; |
222 return isclose(pos, p); |
232 return std::any_of(this->polygon.begin(), this->polygon.end() - 1, [&pos](const glm::vec3& p){ |
223 }); |
233 return isclose(pos, p); |
|
234 }); |
|
235 } |
|
236 else { |
|
237 return false; |
|
238 } |
224 } |
239 } |
225 |
240 |
226 EditingMode EditTools::currentEditingMode() const |
241 EditingMode EditTools::currentEditingMode() const |
227 { |
242 { |
228 return this->mode; |
243 return this->mode; |
237 Q_EMIT this->select({highlighted}, false); |
252 Q_EMIT this->select({highlighted}, false); |
238 } |
253 } |
239 break; |
254 break; |
240 case DrawMode: |
255 case DrawMode: |
241 if (event->button() == Qt::LeftButton and this->worldPosition.has_value()) { |
256 if (event->button() == Qt::LeftButton and this->worldPosition.has_value()) { |
242 if (isCloseToExistingPoints(this->polygon.begin(), this->polygon.end() - 1, *this->worldPosition)) { |
257 if (isCloseToExistingPoints()) { |
243 this->closeShape(); |
258 this->closeShape(); |
244 } |
259 } |
245 else { |
260 else { |
246 this->polygon.push_back(*this->worldPosition); |
261 this->polygon.push_back(*this->worldPosition); |
247 this->updatePreviewPolygon(); |
|
248 } |
262 } |
249 } |
263 } |
250 else if (true |
264 else if (true |
251 and event->button() == Qt::RightButton |
265 and event->button() == Qt::RightButton |
252 and this->polygon.size() > 1 |
266 and this->polygon.size() > 1 |
253 ) { |
267 ) { |
254 this->removeLastPoint(); |
268 this->removeLastPoint(); |
255 updatePreviewPolygon(); |
|
256 } |
269 } |
257 break; |
270 break; |
258 } |
271 } |
259 } |
272 } |
260 |
273 |
|
274 constexpr float distancesquared(const glm::vec3& p1, const glm::vec3& p2) |
|
275 { |
|
276 const float dx = p2.x - p1.x; |
|
277 const float dy = p2.y - p1.y; |
|
278 const float dz = p2.z - p1.z; |
|
279 return (dx * dx) + (dy * dy) + (dz * dz); |
|
280 } |
|
281 |
|
282 inline float area(const Quadrilateral& q) |
|
283 { |
|
284 return 0.5 * ( |
|
285 glm::length(glm::cross(q.p2 - q.p1, q.p3 - q.p1)) + |
|
286 glm::length(glm::cross(q.p3 - q.p2, q.p4 - q.p2)) |
|
287 ); |
|
288 } |
|
289 |
|
290 inline float energy(const Quadrilateral& q) |
|
291 { |
|
292 const float L2 = distancesquared(q.p1, q.p2) |
|
293 + distancesquared(q.p2, q.p3) |
|
294 + distancesquared(q.p3, q.p4) |
|
295 + distancesquared(q.p4, q.p1); |
|
296 return 1 - 6.928203230275509 * area(q) / L2; |
|
297 } |
|
298 |
|
299 struct MergedTriangles |
|
300 { |
|
301 std::vector<Quadrilateral> quadrilaterals; |
|
302 std::set<std::size_t> cutTriangles; |
|
303 }; |
|
304 |
|
305 static MergedTriangles mergeTriangles( |
|
306 const std::vector<std::uint16_t>& indices, |
|
307 const std::vector<glm::vec3>& polygon) |
|
308 { |
|
309 MergedTriangles result; |
|
310 using indextype = std::uint16_t; |
|
311 using indexpair = std::pair<indextype, indextype>; |
|
312 struct boundaryinfo { indextype third; std::size_t triangleid; }; |
|
313 std::map<indexpair, boundaryinfo> boundaries; |
|
314 for (std::size_t i = 0; i < indices.size(); i += 3) { |
|
315 const auto add = [&](int o1, int o2, int o3){ |
|
316 const auto key = std::make_pair(indices[i + o1], indices[i + o2]); |
|
317 boundaries[key] = {indices[i + o3], i}; |
|
318 }; |
|
319 add(0, 1, 2); |
|
320 add(1, 2, 0); |
|
321 add(2, 0, 1); |
|
322 } |
|
323 std::vector<std::array<indextype, 4>> quadindices; |
|
324 std::vector<Quadrilateral> quads; |
|
325 bool repeat = true; |
|
326 const auto iscut = [&result](const std::size_t i){ |
|
327 return result.cutTriangles.find(i) != result.cutTriangles.end(); |
|
328 }; |
|
329 while (repeat) { |
|
330 repeat = false; |
|
331 // TODO: make this a function that returns quadrilateral indices |
|
332 // Go through triangle boundaries |
|
333 for (const auto& it1 : boundaries) { |
|
334 const indexpair& pair1 = it1.first; |
|
335 const boundaryinfo& boundary1 = it1.second; |
|
336 // .. the ones we haven't already merged anyway |
|
337 if (not iscut(boundary1.triangleid)) { |
|
338 // Look for its inverse boundary to find the touching triangle |
|
339 const auto pair2 = std::make_pair(pair1.second, pair1.first); |
|
340 const auto it2 = boundaries.find(pair2); |
|
341 // Also if that hasn't been cut |
|
342 if (it2 != boundaries.end() and not iscut(it2->second.triangleid)) { |
|
343 const Quadrilateral quad{ |
|
344 polygon[pair1.first], |
|
345 polygon[it2->second.third], |
|
346 polygon[pair1.second], |
|
347 polygon[boundary1.third], |
|
348 }; |
|
349 if (isConvex(quad)) { |
|
350 result.quadrilaterals.push_back(quad); |
|
351 result.cutTriangles.insert(boundary1.triangleid); |
|
352 result.cutTriangles.insert(it2->second.triangleid); |
|
353 repeat = true; |
|
354 } |
|
355 } |
|
356 } |
|
357 } |
|
358 } |
|
359 return result; |
|
360 } |
|
361 |
261 const std::vector<ModelAction> EditTools::actions() const |
362 const std::vector<ModelAction> EditTools::actions() const |
262 { |
363 { |
263 std::vector<ModelAction> result; |
364 std::vector<ModelAction> result; |
264 if (this->polygon.size() >= 2 and this->polygon.size() <= 4) { |
365 if (this->numpoints == 2) { |
265 switch (this->polygon.size()) { |
366 result.push_back(AppendToModel{ |
266 case 2: |
367 .newElement = Colored<LineSegment>{ |
|
368 LineSegment{ |
|
369 .p1 = this->polygon[0], |
|
370 .p2 = this->polygon[1], |
|
371 }, |
|
372 EDGE_COLOR, |
|
373 } |
|
374 }); |
|
375 } |
|
376 else if (this->numpoints > 2) { |
|
377 const glm::mat4 inverseGrid = glm::inverse(this->gridMatrix); |
|
378 std::vector<std::vector<glm::vec3>> polygons{1}; |
|
379 std::vector<glm::vec3>& polygon2d = polygons.back(); |
|
380 polygon2d.reserve(this->numpoints); |
|
381 for (std::size_t i = 0; i < this->numpoints; ++i) { |
|
382 polygon2d.push_back(inverseGrid * glm::vec4{this->polygon[i], 1}); |
|
383 } |
|
384 using indextype = std::uint16_t; |
|
385 const std::vector<indextype> indices = mapbox::earcut<std::uint16_t>(polygons); |
|
386 MergedTriangles mergedTriangles = mergeTriangles(indices, this->polygon); |
|
387 for (const Quadrilateral& quad : mergedTriangles.quadrilaterals) { |
267 result.push_back(AppendToModel{ |
388 result.push_back(AppendToModel{ |
268 .newElement = Colored<LineSegment>{ |
389 .newElement = Colored<Quadrilateral>{quad, MAIN_COLOR}, |
269 LineSegment{ |
|
270 .p1 = this->polygon[0], |
|
271 .p2 = this->polygon[1], |
|
272 }, |
|
273 EDGE_COLOR, |
|
274 } |
|
275 }); |
390 }); |
276 break; |
391 } |
277 case 3: |
392 for (std::size_t i = 0; i < indices.size(); i += 3) { |
278 result.push_back(AppendToModel{ |
393 if (mergedTriangles.cutTriangles.find(i) == mergedTriangles.cutTriangles.end()) { |
279 .newElement = Colored<Triangle>{ |
394 result.push_back(AppendToModel{ |
280 Triangle{ |
395 .newElement = Colored<Triangle>{ |
281 .p1 = this->polygon[0], |
396 Triangle{ |
282 .p2 = this->polygon[1], |
397 .p1 = this->polygon[indices[i]], |
283 .p3 = this->polygon[2], |
398 .p2 = this->polygon[indices[i + 1]], |
284 }, |
399 .p3 = this->polygon[indices[i + 2]], |
285 MAIN_COLOR, |
400 }, |
286 } |
401 MAIN_COLOR, |
287 }); |
402 } |
288 break; |
403 }); |
289 case 4: |
404 } |
290 result.push_back(AppendToModel{ |
|
291 .newElement = Colored<Quadrilateral>{ |
|
292 Quadrilateral{ |
|
293 .p1 = this->polygon[0], |
|
294 .p2 = this->polygon[1], |
|
295 .p3 = this->polygon[2], |
|
296 .p4 = this->polygon[3], |
|
297 }, |
|
298 MAIN_COLOR, |
|
299 } |
|
300 }); |
|
301 break; |
|
302 } |
405 } |
303 } |
406 } |
304 return result; |
407 return result; |
305 } |
408 } |
306 |
409 |