82 } |
82 } |
83 |
83 |
84 void EditTools::mouseMoved(const QMouseEvent* event) |
84 void EditTools::mouseMoved(const QMouseEvent* event) |
85 { |
85 { |
86 this->worldPosition = this->renderer->screenToModelCoordinates(event->pos(), this->gridPlane); |
86 this->worldPosition = this->renderer->screenToModelCoordinates(event->pos(), this->gridPlane); |
|
87 this->localPosition = event->localPos(); |
87 if (this->worldPosition.has_value()) |
88 if (this->worldPosition.has_value()) |
88 { |
89 { |
89 // Snap the position to grid. This procedure is basically the "change of basis" and almost follows the |
90 // Snap the position to grid. This procedure is basically the "change of basis" and almost follows the |
90 // A⁻¹ × M × A formula which is used to perform a transformation in some other coordinate system, except |
91 // A⁻¹ × M × A formula which is used to perform a transformation in some other coordinate system, except |
91 // we actually use the inverted matrix first and the regular one last to perform the transformation of |
92 // we actually use the inverted matrix first and the regular one last to perform the transformation of |
97 this->worldPosition = glm::round(*this->worldPosition); |
98 this->worldPosition = glm::round(*this->worldPosition); |
98 // And finally transform it back to grid coordinates by transforming it with the |
99 // And finally transform it back to grid coordinates by transforming it with the |
99 // grid matrix. |
100 // grid matrix. |
100 this->worldPosition = this->gridMatrix * glm::vec4{*this->worldPosition, 1}; |
101 this->worldPosition = this->gridMatrix * glm::vec4{*this->worldPosition, 1}; |
101 this->polygon.back() = *this->worldPosition; |
102 this->polygon.back() = *this->worldPosition; |
102 } |
103 this->numpoints = this->calcNumPoints(); |
103 this->numpoints = this->polygon.size(); |
|
104 if (this->isCloseToExistingPoints()) { |
|
105 this->numpoints -= 1; |
|
106 } |
104 } |
107 } |
105 } |
108 |
106 |
109 static QVector<QPointF> convertWorldPointsToScreenPoints( |
107 static QVector<QPointF> convertWorldPointsToScreenPoints( |
110 const std::vector<glm::vec3> &worldPoints, |
108 const std::vector<glm::vec3> &worldPoints, |
251 case CircleMode: |
249 case CircleMode: |
252 return circleModeActions(); |
250 return circleModeActions(); |
253 } |
251 } |
254 } |
252 } |
255 |
253 |
|
254 constexpr QPointF vecToQPoint(const glm::vec2& a) |
|
255 { |
|
256 return {a.x, a.y}; |
|
257 } |
|
258 |
256 void EditTools::renderPreview(QPainter* painter, const void* pensptr) |
259 void EditTools::renderPreview(QPainter* painter, const void* pensptr) |
257 { |
260 { |
258 const Pens& pens = *reinterpret_cast<const Pens*>(pensptr); |
261 const Pens& pens = *reinterpret_cast<const Pens*>(pensptr); |
259 painter->setPen(pens.polygonPen); |
262 painter->setPen(pens.polygonPen); |
260 for (const ModelAction& action : this->modelActions()) { |
263 for (const ModelAction& action : this->modelActions()) { |
276 painter->setBrush(pens.pointBrush); |
279 painter->setBrush(pens.pointBrush); |
277 painter->setPen(pens.pointPen); |
280 painter->setPen(pens.pointPen); |
278 for (const glm::vec3& point : this->polygon) { |
281 for (const glm::vec3& point : this->polygon) { |
279 drawWorldPoint(painter, point, this->renderer); |
282 drawWorldPoint(painter, point, this->renderer); |
280 } |
283 } |
|
284 if (this->mode == CircleMode and this->polygon.size() >= 2) { |
|
285 const glm::vec3 circleOrigin = this->polygon[0]; |
|
286 const QPointF originScreen = this->renderer->modelToScreenCoordinates(circleOrigin); |
|
287 const auto extremity = [this, &originScreen](const glm::vec3& p){ |
|
288 const QPointF s2 = this->renderer->modelToScreenCoordinates(p); |
|
289 const auto intersection = rayRectangleIntersection( |
|
290 rayFromPoints(toVec2(originScreen), toVec2(s2)), |
|
291 this->renderer->rect()); |
|
292 if (intersection.has_value()) { |
|
293 return intersection->position; |
|
294 } |
|
295 else { |
|
296 return glm::vec2{s2.x(), s2.y()}; |
|
297 } |
|
298 }; |
|
299 const glm::vec3 zvec = this->gridMatrix[2]; |
|
300 const glm::vec2 p1 = extremity(this->polygon[0] + zvec); |
|
301 const glm::vec2 p2 = extremity(this->polygon[0] - zvec); |
|
302 const glm::vec2 lateral = glm::normalize(glm::mat2{{0, 1}, {-1, 0}} * (p2 - p1)); |
|
303 painter->setPen(QPen{Qt::white, 3}); |
|
304 painter->drawLine(vecToQPoint(p1), vecToQPoint(p2)); |
|
305 constexpr float notchsize = 40.0f; |
|
306 for (int a = -30; a <= 30; ++a) { |
|
307 const glm::vec3 notch = this->polygon[0] + static_cast<float>(a) * zvec; |
|
308 const QPointF s_notchcenter = this->renderer->modelToScreenCoordinates(notch); |
|
309 const QPointF notch_s1 = s_notchcenter + notchsize * 0.5f * vecToQPoint(lateral); |
|
310 const QPointF notch_s2 = s_notchcenter - notchsize * 0.5f * vecToQPoint(lateral); |
|
311 painter->drawLine(notch_s1, notch_s2); |
|
312 } |
|
313 } |
281 } |
314 } |
282 |
315 |
283 void EditTools::removeLastPoint() |
316 void EditTools::removeLastPoint() |
284 { |
317 { |
285 if (this->polygon.size() > 1) { |
318 if (this->polygon.size() > 1) { |
286 this->polygon.erase(this->polygon.end() - 2); |
319 this->polygon.erase(this->polygon.end() - 2); |
|
320 this->numpoints = this->calcNumPoints(); |
287 } |
321 } |
288 } |
322 } |
289 |
323 |
290 bool EditTools::isCloseToExistingPoints() const |
324 bool EditTools::isCloseToExistingPoints() const |
291 { |
325 { |
404 |
447 |
405 |
448 |
406 const std::vector<ModelAction> EditTools::circleModeActions() const |
449 const std::vector<ModelAction> EditTools::circleModeActions() const |
407 { |
450 { |
408 std::vector<ModelAction> result; |
451 std::vector<ModelAction> result; |
409 if (this->numpoints == 2) { |
452 if (this->numpoints == 3) { |
410 const glm::vec3 x = polygon[1] - polygon[0]; |
453 const glm::vec3 x = polygon[1] - polygon[0]; |
411 glm::mat4 transform{ |
454 const opt<float> cyliheight = [&]() -> opt<float> { |
412 glm::vec4{x, 0}, |
455 const Plane plane{ |
413 this->gridMatrix[2], |
456 .normal = glm::normalize(this->renderer->cameraVector(this->localPosition)), |
414 glm::vec4{glm::cross(glm::vec3{-this->gridMatrix[2]}, x), 0}, |
457 .anchor = this->polygon[0], |
415 glm::vec4{this->polygon[0], 1}, |
458 }; |
416 }; |
459 const opt<glm::vec3> p = this->renderer->screenToModelCoordinates(this->localPosition, plane); |
417 Colored<CircularPrimitive> circ{ |
460 if (p.has_value()) { |
418 CircularPrimitive{ |
461 const glm::vec3 heightvec = glm::normalize(glm::vec3{gridMatrix[2]}); |
419 .type = this->circleToolOptions.type, |
462 return std::round(glm::dot(*p - polygon[0], heightvec)); |
420 .fraction = this->circleToolOptions.fraction, |
463 } |
421 .transformation = transform, |
464 else { |
422 }, |
465 return {}; |
423 MAIN_COLOR |
466 } |
424 }; |
467 }(); |
425 result.push_back(AppendToModel{.newElement = circ}); |
468 if (cyliheight.has_value()) { |
|
469 glm::mat4 transform{ |
|
470 glm::vec4{x, 0}, |
|
471 *cyliheight * this->gridMatrix[2], |
|
472 glm::vec4{glm::cross(glm::vec3{-this->gridMatrix[2]}, x), 0}, |
|
473 glm::vec4{this->polygon[0], 1}, |
|
474 }; |
|
475 Colored<CircularPrimitive> circ{ |
|
476 CircularPrimitive{ |
|
477 .type = this->circleToolOptions.type, |
|
478 .fraction = this->circleToolOptions.fraction, |
|
479 .transformation = transform, |
|
480 }, |
|
481 MAIN_COLOR |
|
482 }; |
|
483 result.push_back(AppendToModel{.newElement = circ}); |
|
484 } |
426 } |
485 } |
427 return result; |
486 return result; |
428 } |
487 } |
429 |
488 |
430 const std::vector<ModelAction> EditTools::drawModeActions() const |
489 const std::vector<ModelAction> EditTools::drawModeActions() const |