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 { |
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 } |