src/layers/edittools.cpp

changeset 322
a39f454a3d7f
parent 320
af6633412a6c
child 323
3c09c937848c
equal deleted inserted replaced
321:180072db4a83 322:a39f454a3d7f
86 // Then round the coordinates to integer precision... 86 // Then round the coordinates to integer precision...
87 this->worldPosition = glm::round(*this->worldPosition); 87 this->worldPosition = glm::round(*this->worldPosition);
88 // And finally transform it back to grid coordinates by transforming it with the 88 // And finally transform it back to grid coordinates by transforming it with the
89 // grid matrix. 89 // grid matrix.
90 this->worldPosition = this->gridMatrix * glm::vec4{*this->worldPosition, 1}; 90 this->worldPosition = this->gridMatrix * glm::vec4{*this->worldPosition, 1};
91 this->polygon.back() = *this->worldPosition; 91 this->inputPolygon.updateCurrentPoint(*this->worldPosition);
92 this->numpoints = this->calcNumPoints(); 92 }
93 }
94 }
95
96 static QVector<QPointF> convertWorldPointsToScreenPoints(
97 const std::vector<glm::vec3> &worldPoints,
98 const PartRenderer* renderer)
99 {
100 QVector<QPointF> points2d;
101 points2d.reserve(static_cast<int>(worldPoints.size()));
102 for (const glm::vec3& point : worldPoints)
103 {
104 points2d.push_back(renderer->modelToScreenCoordinates(point));
105 }
106 return points2d;
107 }
108
109 static Winding worldPolygonWinding(
110 const std::vector<glm::vec3> &points,
111 const PartRenderer* renderer)
112 {
113 return winding(QPolygonF{convertWorldPointsToScreenPoints(points, renderer)});
114 }
115
116 static void drawWorldPoint(
117 QPainter* painter,
118 const glm::vec3& worldPoint,
119 const PartRenderer* renderer)
120 {
121 const QPointF center = renderer->modelToScreenCoordinates(worldPoint);
122 painter->drawEllipse(inscribe(CircleF{center, 5}));
123 }
124
125 static void drawWorldPolyline(
126 QPainter *painter,
127 const std::vector<glm::vec3> &points,
128 const PartRenderer* renderer)
129 {
130 painter->drawPolyline(QPolygonF{convertWorldPointsToScreenPoints(points, renderer)});
131 }
132
133 static void drawWorldPolygon(
134 QPainter* painter,
135 const std::vector<glm::vec3> &points,
136 const PartRenderer* renderer)
137 {
138 painter->drawPolygon(QPolygonF{convertWorldPointsToScreenPoints(points, renderer)});
139 } 93 }
140 94
141 //! \brief Conversion function from PlainPolygonElement to ModelElement 95 //! \brief Conversion function from PlainPolygonElement to ModelElement
142 ModelElement elementFromPolygonAndColor(const PlainPolygonElement& poly, ColorIndex color) 96 ModelElement elementFromPolygonAndColor(const PlainPolygonElement& poly, ColorIndex color)
143 { 97 {
181 { 135 {
182 const QBrush pointBrush; 136 const QBrush pointBrush;
183 const QPen pointPen; 137 const QPen pointPen;
184 const QPen textPen; 138 const QPen textPen;
185 const QPen polygonPen; 139 const QPen polygonPen;
186 const QPen badPolygonPen;
187 const QBrush greenPolygonBrush; 140 const QBrush greenPolygonBrush;
188 const QBrush redPolygonBrush; 141 const QBrush redPolygonBrush;
189 }; 142 };
190 } 143 }
191 144
238 case CircleMode: 191 case CircleMode:
239 return circleModeActions(); 192 return circleModeActions();
240 } 193 }
241 } 194 }
242 195
243 constexpr QPointF vecToQPoint(const glm::vec2& a)
244 {
245 return {a.x, a.y};
246 }
247
248 void EditTools::renderPreview(QPainter* painter, const void* pensptr) 196 void EditTools::renderPreview(QPainter* painter, const void* pensptr)
249 { 197 {
250 const Pens& pens = *reinterpret_cast<const Pens*>(pensptr); 198 const Pens& pens = *reinterpret_cast<const Pens*>(pensptr);
251 painter->setPen(pens.polygonPen); 199 painter->setPen(pens.polygonPen);
252 for (const ModelAction& action : this->modelActions()) { 200 for (const ModelAction& action : this->modelActions()) {
265 } 213 }
266 } 214 }
267 } 215 }
268 painter->setBrush(pens.pointBrush); 216 painter->setBrush(pens.pointBrush);
269 painter->setPen(pens.pointPen); 217 painter->setPen(pens.pointPen);
270 for (const glm::vec3& point : this->polygon) { 218 for (const glm::vec3& point : this->inputPolygon) {
271 drawWorldPoint(painter, point, this->renderer); 219 drawWorldPoint(painter, point, this->renderer);
272 } 220 }
273 if (this->mode == CircleMode and this->polygon.size() >= 2) { 221 if (this->mode == CircleMode and this->inputPolygon.polygonSize() >= 2) {
274 const glm::vec3 circleOrigin = this->polygon[0]; 222 const glm::vec3 circleOrigin = this->inputPolygon[0];
275 const QPointF originScreen = this->renderer->modelToScreenCoordinates(circleOrigin); 223 const QPointF originScreen = this->renderer->modelToScreenCoordinates(circleOrigin);
276 const auto extremity = [this, &originScreen](const glm::vec3& p){ 224 const auto extremity = [this, &originScreen](const glm::vec3& p){
277 const QPointF s2 = this->renderer->modelToScreenCoordinates(p); 225 const QPointF s2 = this->renderer->modelToScreenCoordinates(p);
278 const auto intersection = rayRectangleIntersection( 226 const auto intersection = rayRectangleIntersection(
279 rayFromPoints(toVec2(originScreen), toVec2(s2)), 227 rayFromPoints(toVec2(originScreen), toVec2(s2)),
285 return glm::vec2{s2.x(), s2.y()}; 233 return glm::vec2{s2.x(), s2.y()};
286 } 234 }
287 }; 235 };
288 const glm::vec3 zvec = this->gridMatrix[2]; 236 const glm::vec3 zvec = this->gridMatrix[2];
289 { 237 {
290 const glm::vec2 p1 = extremity(this->polygon[0] + zvec); 238 const glm::vec2 p1 = extremity(this->inputPolygon[0] + zvec);
291 const glm::vec2 p2 = extremity(this->polygon[0] - zvec); 239 const glm::vec2 p2 = extremity(this->inputPolygon[0] - zvec);
292 const glm::vec2 lateral = glm::normalize(glm::mat2{{0, 1}, {-1, 0}} * (p2 - p1)); 240 const glm::vec2 lateral = glm::normalize(glm::mat2{{0, 1}, {-1, 0}} * (p2 - p1));
293 painter->setPen(QPen{Qt::white, 3}); 241 painter->setPen(QPen{Qt::white, 3});
294 painter->drawLine(vecToQPoint(p1), vecToQPoint(p2)); 242 painter->drawLine(vecToQPoint(p1), vecToQPoint(p2));
295 constexpr float notchsize = 40.0f; 243 constexpr float notchsize = 40.0f;
296 for (int a = -30; a <= 30; ++a) { 244 for (int a = -30; a <= 30; ++a) {
297 const glm::vec3 notch = this->polygon[0] + static_cast<float>(a) * zvec; 245 const glm::vec3 notch = this->inputPolygon[0] + static_cast<float>(a) * zvec;
298 const QPointF s_notchcenter = this->renderer->modelToScreenCoordinates(notch); 246 const QPointF s_notchcenter = this->renderer->modelToScreenCoordinates(notch);
299 const QPointF notch_s1 = s_notchcenter + notchsize * 0.5f * vecToQPoint(lateral); 247 const QPointF notch_s1 = s_notchcenter + notchsize * 0.5f * vecToQPoint(lateral);
300 const QPointF notch_s2 = s_notchcenter - notchsize * 0.5f * vecToQPoint(lateral); 248 const QPointF notch_s2 = s_notchcenter - notchsize * 0.5f * vecToQPoint(lateral);
301 painter->drawLine(notch_s1, notch_s2); 249 painter->drawLine(notch_s1, notch_s2);
302 } 250 }
303 } 251 }
304 if (this->polygon.size() >= 3) { 252 if (this->inputPolygon.polygonSize() >= 3) {
305 const opt<float> height = this->cylinderHeight(); 253 const opt<float> height = this->cylinderHeight();
306 if (height.has_value()) { 254 if (height.has_value()) {
307 const glm::vec3 heightvec = height.value_or(0) * zvec; 255 const glm::vec3 heightvec = height.value_or(0) * zvec;
308 const glm::vec3 p = this->polygon[1] + 0.5f * heightvec; 256 const glm::vec3 p = this->inputPolygon[1] + 0.5f * heightvec;
309 QFont font{}; 257 QFont font{};
310 font.setBold(true); 258 font.setBold(true);
311 drawBorderedText(painter, this->renderer->modelToScreenCoordinates(p), font, QString::number(*height)); 259 drawBorderedText(painter, this->renderer->modelToScreenCoordinates(p), font, QString::number(*height));
312 } 260 }
313 } 261 }
314 } 262 }
315 } 263 }
316 264
317 void EditTools::removeLastPoint()
318 {
319 if (this->polygon.size() > 1) {
320 this->polygon.erase(this->polygon.end() - 2);
321 this->numpoints = this->calcNumPoints();
322 }
323 }
324
325 bool EditTools::isCloseToExistingPoints() const
326 {
327 if (this->worldPosition.has_value()) {
328 const glm::vec3& pos = *this->worldPosition;
329 return std::any_of(this->polygon.begin(), this->polygon.end() - 1, [&pos](const glm::vec3& p){
330 return isclose(pos, p);
331 });
332 }
333 else {
334 return false;
335 }
336 }
337
338 std::size_t EditTools::calcNumPoints() const
339 {
340 std::size_t result = this->polygon.size();
341 if (this->isCloseToExistingPoints()) {
342 result -= 1;
343 }
344 return result;
345 }
346
347 opt<float> EditTools::cylinderHeight() const 265 opt<float> EditTools::cylinderHeight() const
348 { 266 {
349 if (this->polygon.size() < 3) { 267 if (this->inputPolygon.bufferSize() < 3) {
350 return {}; 268 return {};
351 } 269 }
352 else { 270 else {
353 const Plane plane{ 271 const Plane plane{
354 .normal = glm::normalize(this->renderer->cameraVector(this->localPosition)), 272 .normal = glm::normalize(this->renderer->cameraVector(this->localPosition)),
355 .anchor = this->polygon[0], 273 .anchor = this->inputPolygon[0],
356 }; 274 };
357 const opt<glm::vec3> p = this->renderer->screenToModelCoordinates(this->localPosition, plane); 275 const opt<glm::vec3> p = this->renderer->screenToModelCoordinates(this->localPosition, plane);
358 if (p.has_value()) { 276 if (p.has_value()) {
359 const glm::vec3 heightvec = glm::normalize(glm::vec3{gridMatrix[2]}); 277 const glm::vec3 heightvec = glm::normalize(glm::vec3{gridMatrix[2]});
360 return std::round(glm::dot(*p - polygon[0], heightvec)); 278 return std::round(glm::dot(*p - this->inputPolygon[0], heightvec));
361 } 279 }
362 else { 280 else {
363 return {}; 281 return {};
364 } 282 }
365 } 283 }
379 Q_EMIT this->select({highlighted}, false); 297 Q_EMIT this->select({highlighted}, false);
380 } 298 }
381 break; 299 break;
382 case DrawMode: 300 case DrawMode:
383 if (event->button() == Qt::LeftButton and this->worldPosition.has_value()) { 301 if (event->button() == Qt::LeftButton and this->worldPosition.has_value()) {
384 if (isCloseToExistingPoints()) { 302 if (this->inputPolygon.currentPointOnExistingPoint()) {
385 this->closeShape(); 303 this->closeShape();
386 } 304 }
387 else { 305 else {
388 this->polygon.push_back(*this->worldPosition); 306 this->inputPolygon.finishCurrentPoint();
389 } 307 }
390 } 308 }
391 break; 309 break;
392 case CircleMode: 310 case CircleMode:
393 if (event->button() == Qt::LeftButton) { 311 if (event->button() == Qt::LeftButton) {
394 if (this->polygon.size() == 3) { 312 if (this->inputPolygon.bufferSize() == 3) {
395 this->closeShape(); 313 this->closeShape();
396 } 314 }
397 else if (this->worldPosition.has_value()) { 315 else if (this->worldPosition.has_value()) {
398 this->polygon.push_back(*this->worldPosition); 316 this->inputPolygon.finishCurrentPoint();
399 } 317 }
400 } 318 }
401 break; 319 break;
402 } 320 }
403 if (event->button() == Qt::RightButton and this->polygon.size() > 1) { 321 if (event->button() == Qt::RightButton) {
404 this->removeLastPoint(); 322 this->inputPolygon.removeLastPoint();
405 } 323 }
406 } 324 }
407 325
408 326
409 const std::vector<ModelAction> EditTools::circleModeActions() const 327 const std::vector<ModelAction> EditTools::circleModeActions() const
410 { 328 {
411 std::vector<ModelAction> result; 329 std::vector<ModelAction> result;
412 if (this->numpoints >= 2) { 330 if (this->inputPolygon.polygonSize() >= 2) {
413 const glm::vec3 x = polygon[1] - polygon[0]; 331 const glm::vec3 x = this->inputPolygon[1] - this->inputPolygon[0];
414 const opt<float> cyliheight = this->cylinderHeight().value_or(1); 332 const opt<float> cyliheight = this->cylinderHeight().value_or(1);
415 glm::mat4 transform{ 333 glm::mat4 transform{
416 glm::vec4{x, 0}, 334 glm::vec4{x, 0},
417 *cyliheight * this->gridMatrix[2], 335 *cyliheight * this->gridMatrix[2],
418 glm::vec4{glm::cross(glm::vec3{-this->gridMatrix[2]}, x), 0}, 336 glm::vec4{glm::cross(glm::vec3{-this->gridMatrix[2]}, x), 0},
419 glm::vec4{this->polygon[0], 1}, 337 glm::vec4{this->inputPolygon[0], 1},
420 }; 338 };
421 Colored<CircularPrimitive> circ{ 339 Colored<CircularPrimitive> circ{
422 CircularPrimitive{ 340 CircularPrimitive{
423 .type = this->circleToolOptions.type, 341 .type = this->circleToolOptions.type,
424 .fraction = this->circleToolOptions.fraction, 342 .fraction = this->circleToolOptions.fraction,
432 } 350 }
433 351
434 const std::vector<ModelAction> EditTools::drawModeActions() const 352 const std::vector<ModelAction> EditTools::drawModeActions() const
435 { 353 {
436 std::vector<ModelAction> result; 354 std::vector<ModelAction> result;
437 if (this->numpoints == 2) { 355 if (this->inputPolygon.polygonSize() == 2) {
438 result.push_back(AppendToModel{edge(this->polygon[0], this->polygon[1])}); 356 result.push_back(AppendToModel{edge(this->inputPolygon[0], this->inputPolygon[1])});
439 } 357 }
440 else if (this->numpoints > 2) { 358 else if (this->inputPolygon.polygonSize() > 2) {
441 for (const PlainPolygonElement& poly : polygonize( 359 for (const PlainPolygonElement& poly : polygonize(
442 this->polygon.begin(), 360 this->inputPolygon.begin(),
443 this->polygon.begin() + static_cast<long>(this->numpoints)) 361 this->inputPolygon.polygonEnd())
444 ) { 362 ) {
445 result.push_back(AppendToModel{ 363 result.push_back(AppendToModel{
446 .newElement = elementFromPolygonAndColor(poly, MAIN_COLOR), 364 .newElement = elementFromPolygonAndColor(poly, MAIN_COLOR),
447 }); 365 });
448 } 366 }
465 void EditTools::closeShape() 383 void EditTools::closeShape()
466 { 384 {
467 for (const ModelAction& action : this->modelActions()) { 385 for (const ModelAction& action : this->modelActions()) {
468 Q_EMIT this->modelAction(action); 386 Q_EMIT this->modelAction(action);
469 } 387 }
470 this->polygon.clear(); 388 this->inputPolygon.clear();
471 this->polygon.push_back(this->worldPosition.value_or(glm::vec3{0, 0, 0})); 389 }
472 }

mercurial