src/document.cpp

changeset 223
ce81db996275
parent 222
72b456f2f3c2
child 225
551c136b459e
equal deleted inserted replaced
222:72b456f2f3c2 223:ce81db996275
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */ 17 */
18 18
19 #include <QMouseEvent> 19 #include <QMouseEvent>
20 #include <QPainter> 20 #include <QPainter>
21 #include "algorithm/earcut.h"
21 #include "document.h" 22 #include "document.h"
22 #include "model.h" 23 #include "model.h"
23 #include "ui/objecteditor.h" 24 #include "ui/objecteditor.h"
24 #include "gl/partrenderer.h" 25 #include "gl/partrenderer.h"
26
27 // Make mapbox::earcut work with glm::vec3
28 namespace mapbox {
29 namespace util {
30 template<> struct nth<0, glm::vec3>
31 {
32 static constexpr float get(const glm::vec3& t) { return t.x; };
33 };
34 template<> struct nth<1, glm::vec3>
35 {
36 static constexpr float get(const glm::vec3& t) { return t.y; };
37 };
38 }
39 }
25 40
26 EditTools::EditTools(QObject* parent) : 41 EditTools::EditTools(QObject* parent) :
27 QObject{parent}, 42 QObject{parent},
28 RenderLayer{} 43 RenderLayer{}
29 { 44 {
70 // And finally transform it back to grid coordinates by transforming it with the 85 // And finally transform it back to grid coordinates by transforming it with the
71 // grid matrix. 86 // grid matrix.
72 this->worldPosition = this->gridMatrix * glm::vec4{*this->worldPosition, 1}; 87 this->worldPosition = this->gridMatrix * glm::vec4{*this->worldPosition, 1};
73 this->polygon.back() = *this->worldPosition; 88 this->polygon.back() = *this->worldPosition;
74 } 89 }
90 this->numpoints = this->polygon.size();
91 if (this->isCloseToExistingPoints()) {
92 this->numpoints -= 1;
93 }
75 } 94 }
76 95
77 static QVector<QPointF> convertWorldPointsToScreenPoints( 96 static QVector<QPointF> convertWorldPointsToScreenPoints(
78 const std::vector<glm::vec3> &worldPoints, 97 const std::vector<glm::vec3> &worldPoints,
79 const PartRenderer* renderer) 98 const PartRenderer* renderer)
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
308 { 411 {
309 for (const ModelAction& action : this->actions()) { 412 for (const ModelAction& action : this->actions()) {
310 Q_EMIT this->modelAction(action); 413 Q_EMIT this->modelAction(action);
311 } 414 }
312 this->polygon.clear(); 415 this->polygon.clear();
313 this->updatePreviewPolygon(); 416 }
314 }

mercurial