diff --git a/src/Gui/SoDatumLabel.cpp b/src/Gui/SoDatumLabel.cpp index 5149ae2d82..4016f0872f 100644 --- a/src/Gui/SoDatumLabel.cpp +++ b/src/Gui/SoDatumLabel.cpp @@ -477,6 +477,16 @@ private: std::vector computeArcLengthBBox() const { + // get the points stored in the pnt field + const SbVec3f *points = label->pnts.getValues(0); + if (label->pnts.getNum() < 3) { + return {}; + } + + // use shared geometry calculation + SoDatumLabel::ArcLengthGeometry geom = label->calculateArcLengthGeometry(points); + + // get text area for existing text coverage SbVec2s imgsize; int nc {}; int srcw = 1; @@ -492,69 +502,42 @@ private: float imgHeight = scale * (float) (srch); float imgWidth = aspectRatio * imgHeight; - // Get the points stored in the pnt field - const SbVec3f *points = label->pnts.getValues(0); - if (label->pnts.getNum() < 3) { - return {}; - } - - SbVec3f ctr = points[0]; - SbVec3f p1 = points[1]; - SbVec3f p2 = points[2]; - - SbVec3f img1 = SbVec3f(-imgWidth / 2, -imgHeight / 2, 0.F); - SbVec3f img2 = SbVec3f(-imgWidth / 2, imgHeight / 2, 0.F); - SbVec3f img3 = SbVec3f( imgWidth / 2, -imgHeight / 2, 0.F); - SbVec3f img4 = SbVec3f( imgWidth / 2, imgHeight / 2, 0.F); - - //Text orientation - SbVec3f dir = (p2 - p1); + // text orientation + SbVec3f dir = (geom.p2 - geom.p1); dir.normalize(); SbVec3f normal = SbVec3f (-dir[1], dir[0], 0); - float angle = atan2f(dir[1],dir[0]); - // Rotate through an angle - float s = sin(angle); - float c = cos(angle); - img1 = SbVec3f((img1[0] * c) - (img1[1] * s), (img1[0] * s) + (img1[1] * c), 0.F); - img2 = SbVec3f((img2[0] * c) - (img2[1] * s), (img2[0] * s) + (img2[1] * c), 0.F); - img3 = SbVec3f((img3[0] * c) - (img3[1] * s), (img3[0] * s) + (img3[1] * c), 0.F); - img4 = SbVec3f((img4[0] * c) - (img4[1] * s), (img4[0] * s) + (img4[1] * c), 0.F); - - float length = label->param1.getValue(); - - - // Text location - SbVec3f vm = (p1+p2)/2 - ctr; - vm.normalize(); - - SbVec3f vc1 = (p1 - ctr); - SbVec3f vc2 = (p2 - ctr); - - float startangle = atan2f(vc1[1], vc1[0]); - float endangle = atan2f(vc2[1], vc2[0]); - if (endangle < startangle) { - endangle += 2.F * std::numbers::pi_v; - } - - SbVec3f textCenter; - if (endangle - startangle <= std::numbers::pi) { - textCenter = ctr + vm * (length + imgHeight); - } else { - textCenter = ctr - vm * (length + 2. * imgHeight); - } - img1 += textCenter; - img2 += textCenter; - img3 += textCenter; - img4 += textCenter; - - // Finds the mins and maxes + // include all visual elements in bounding box std::vector corners; + + // text area (existing coverage) float margin = imgHeight / 4.0F; - corners.push_back(textCenter + dir * (imgWidth / 2.0F + margin) - normal * (imgHeight / 2.0F + margin)); - corners.push_back(textCenter - dir * (imgWidth / 2.0F + margin) - normal * (imgHeight / 2.0F + margin)); - corners.push_back(textCenter + dir * (imgWidth / 2.0F + margin) + normal * (imgHeight / 2.0F + margin)); - corners.push_back(textCenter - dir * (imgWidth / 2.0F + margin) + normal * (imgHeight / 2.0F + margin)); + corners.push_back(geom.textOffset + dir * (imgWidth / 2.0F + margin) - normal * (imgHeight / 2.0F + margin)); + corners.push_back(geom.textOffset - dir * (imgWidth / 2.0F + margin) - normal * (imgHeight / 2.0F + margin)); + corners.push_back(geom.textOffset + dir * (imgWidth / 2.0F + margin) + normal * (imgHeight / 2.0F + margin)); + corners.push_back(geom.textOffset - dir * (imgWidth / 2.0F + margin) + normal * (imgHeight / 2.0F + margin)); + + // extension line endpoints + corners.push_back(geom.pnt1); // start point + corners.push_back(geom.pnt2); // extension end 1 + corners.push_back(geom.pnt3); // end point + corners.push_back(geom.pnt4); // extension end 2 + + // arc sample points (8 points along the curve for better coverage) + int numSamples = 8; + for (int i = 0; i < numSamples; i++) { + float t = (float)i / (numSamples - 1); + float angle = geom.startangle + t * (geom.endangle - geom.startangle); + SbVec3f arcPoint = geom.arcCenter + SbVec3f(geom.arcRadius * cos(angle), geom.arcRadius * sin(angle), 0); + corners.push_back(arcPoint); + } + + // arrow head tips (base + direction * length) + float arrowLength = geom.margin * 2; + SbVec3f startArrowTip = geom.pnt2 + geom.dirStart * arrowLength; + SbVec3f endArrowTip = geom.pnt4 + geom.dirEnd * arrowLength; + corners.push_back(startArrowTip); + corners.push_back(endArrowTip); return corners; } @@ -667,7 +650,7 @@ SbVec3f SoDatumLabel::getLabelTextCenterAngle(const SbVec3f& p0) return textCenter; } -SbVec3f SoDatumLabel::getLabelTextCenterArcLength(const SbVec3f& ctr, const SbVec3f& p1, const SbVec3f& p2) +SbVec3f SoDatumLabel::getLabelTextCenterArcLength(const SbVec3f& ctr, const SbVec3f& p1, const SbVec3f& p2) const { float length = this->param1.getValue(); @@ -954,52 +937,60 @@ void SoDatumLabel::generateSymmetricPrimitives(SoAction * action, const SbVec3f& void SoDatumLabel::generateArcLengthPrimitives(SoAction * action, const SbVec3f& ctr, const SbVec3f& p1, const SbVec3f& p2) { + // use shared geometry calculation + SbVec3f points[3] = {ctr, p1, p2}; + ArcLengthGeometry geom = calculateArcLengthGeometry(points); + + // generate selectable primitive for text label SbVec3f img1 = SbVec3f(-this->imgWidth / 2, -this->imgHeight / 2, 0.F); SbVec3f img2 = SbVec3f(-this->imgWidth / 2, this->imgHeight / 2, 0.F); SbVec3f img3 = SbVec3f( this->imgWidth / 2, -this->imgHeight / 2, 0.F); SbVec3f img4 = SbVec3f( this->imgWidth / 2, this->imgHeight / 2, 0.F); - //Text orientation - SbVec3f dir = (p2 - p1); - dir.normalize(); - float angle = atan2f(dir[1],dir[0]); + float s = sin(geom.angle); + float c = cos(geom.angle); - // Rotate through an angle - float s = sin(angle); - float c = cos(angle); img1 = SbVec3f((img1[0] * c) - (img1[1] * s), (img1[0] * s) + (img1[1] * c), 0.F); img2 = SbVec3f((img2[0] * c) - (img2[1] * s), (img2[0] * s) + (img2[1] * c), 0.F); img3 = SbVec3f((img3[0] * c) - (img3[1] * s), (img3[0] * s) + (img3[1] * c), 0.F); img4 = SbVec3f((img4[0] * c) - (img4[1] * s), (img4[0] * s) + (img4[1] * c), 0.F); - //Text location - SbVec3f textOffset = getLabelTextCenterArcLength(ctr, p1, p2); - img1 += textOffset; - img2 += textOffset; - img3 += textOffset; - img4 += textOffset; + img1 += geom.textOffset; + img2 += geom.textOffset; + img3 += geom.textOffset; + img4 += geom.textOffset; - // Primitive Shape is only for text as this should only be selectable + // text label selection primitive SoPrimitiveVertex pv; + pv.setNormal(SbVec3f(0.F, 0.F, 1.F)); this->beginShape(action, TRIANGLE_STRIP); - - pv.setNormal( SbVec3f(0.F, 0.F, 1.F) ); - - // Set coordinates - pv.setPoint( img1 ); + pv.setPoint(img1); shapeVertex(&pv); - - pv.setPoint( img2 ); + pv.setPoint(img2); shapeVertex(&pv); - - pv.setPoint( img3 ); + pv.setPoint(img3); shapeVertex(&pv); - - pv.setPoint( img4 ); + pv.setPoint(img4); shapeVertex(&pv); - this->endShape(); + + // generate selectable primitives for lines + float lineWidth = geom.margin * 0.8f; + + // extension lines + generateLineSelectionPrimitive(action, geom.pnt1, geom.pnt2, lineWidth); + generateLineSelectionPrimitive(action, geom.pnt3, geom.pnt4, lineWidth); + + // generate selectable primitive for arc + generateArcSelectionPrimitive(action, geom.arcCenter, geom.arcRadius, geom.startangle, geom.endangle, lineWidth); + + // generate selectable primitives for arrow heads + float arrowLength = geom.margin * 2; + float arrowWidth = geom.margin * 0.5F; + + generateArrowSelectionPrimitive(action, geom.pnt2, geom.dirStart, arrowWidth, arrowLength); + generateArrowSelectionPrimitive(action, geom.pnt4, geom.dirEnd, arrowWidth, arrowLength); } void SoDatumLabel::generatePrimitives(SoAction * action) @@ -1366,80 +1357,26 @@ void SoDatumLabel::drawSymmetric(const SbVec3f* points) void SoDatumLabel::drawArcLength(const SbVec3f* points, float& angle, SbVec3f& textOffset) { - using std::numbers::pi; + // use shared geometry calculation + ArcLengthGeometry geom = calculateArcLengthGeometry(points); - SbVec3f ctr = points[0]; - SbVec3f p1 = points[1]; - SbVec3f p2 = points[2]; - float length = this->param1.getValue(); + // set output parameters + angle = geom.angle; + textOffset = geom.textOffset; - float margin = this->imgHeight / 3.0F; + // draw arc + glDrawArc(geom.arcCenter, geom.arcRadius, geom.startangle, geom.endangle); - // Angles calculations - SbVec3f vc1 = (p1 - ctr); - SbVec3f vc2 = (p2 - ctr); + // draw lines + glDrawLine(geom.pnt1, geom.pnt2); + glDrawLine(geom.pnt3, geom.pnt4); - float startangle = atan2f(vc1[1], vc1[0]); - float endangle = atan2f(vc2[1], vc2[0]); - if (endangle < startangle) { - endangle += 2.0F * (float)pi; - } + // create the arrowheads + float arrowLength = geom.margin * 2; + float arrowWidth = geom.margin * 0.5F; - float range = endangle - startangle; - - float radius = vc1.length(); - - //Text orientation - SbVec3f dir = (p2 - p1); - dir.normalize(); - // Get magnitude of angle between horizontal - angle = atan2f(dir[1],dir[0]); - if (angle > pi/2 + pi/12) { - angle -= (float)pi; - } else if (angle <= -pi/2 + pi/12) { - angle += (float)pi; - } - // Text location - textOffset = getLabelTextCenterArcLength(ctr, p1, p2); - - // lines direction - SbVec3f vm = (p1+p2)/2 - ctr; - vm.normalize(); - - // Lines points - SbVec3f pnt1 = p1; - SbVec3f pnt2 = p1 + (length-radius) * vm; - SbVec3f pnt3 = p2; - SbVec3f pnt4 = p2 + (length-radius) * vm; - - // Draw arc - if (range <= pi) { - glDrawArc(ctr + (length-radius)*vm, radius, startangle, endangle); - } - else { - pnt2 = p1 + length * vm; - pnt4 = p2 + length * vm; - vc1 = (pnt2 - ctr); - vc2 = (pnt4 - ctr); - startangle = atan2f(vc1[1], vc1[0]); - endangle = atan2f(vc2[1], vc2[0]); - glDrawArc(ctr, vc1.length(), startangle, endangle); - } - - //Draw lines - glDrawLine(pnt1, pnt2); - glDrawLine(pnt3, pnt4); - - // Create the arrowheads - float arrowLength = margin * 2; - float arrowWidth = margin * 0.5F; - - // Normals for the arrowheads at arc start and end - SbVec3f dirStart(sin(startangle), -cos(startangle), 0); - SbVec3f dirEnd(-sin(endangle), cos(endangle), 0); - - glDrawArrow(pnt2, dirStart, arrowWidth, arrowLength); - glDrawArrow(pnt4, dirEnd, arrowWidth, arrowLength); + glDrawArrow(geom.pnt2, geom.dirStart, arrowWidth, arrowLength); + glDrawArrow(geom.pnt4, geom.dirEnd, arrowWidth, arrowLength); } // NOLINTNEXTLINE @@ -1868,3 +1805,82 @@ void SoDatumLabel::generateArrowSelectionPrimitive(SoAction* action, const SbVec shapeVertex(&pv); this->endShape(); } + +SoDatumLabel::ArcLengthGeometry SoDatumLabel::calculateArcLengthGeometry(const SbVec3f* points) const +{ + using std::numbers::pi; + + ArcLengthGeometry geom; + + geom.ctr = points[0]; + geom.p1 = points[1]; + geom.p2 = points[2]; + geom.length = this->param1.getValue(); + + geom.margin = this->imgHeight / 3.0F; + + // angles calculations + SbVec3f vc1 = (geom.p1 - geom.ctr); + SbVec3f vc2 = (geom.p2 - geom.ctr); + + geom.startangle = atan2f(vc1[1], vc1[0]); + geom.endangle = atan2f(vc2[1], vc2[0]); + if (geom.endangle < geom.startangle) { + geom.endangle += 2.0F * (float)pi; + } + + geom.range = geom.endangle - geom.startangle; + geom.radius = vc1.length(); + + // text orientation + SbVec3f dir = (geom.p2 - geom.p1); + dir.normalize(); + // get magnitude of angle between horizontal + geom.angle = atan2f(dir[1],dir[0]); + if (geom.angle > pi/2 + pi/12) { + geom.angle -= (float)pi; + } else if (geom.angle <= -pi/2 + pi/12) { + geom.angle += (float)pi; + } + + // text location + geom.textOffset = getLabelTextCenterArcLength(geom.ctr, geom.p1, geom.p2); + + // lines direction + geom.vm = (geom.p1+geom.p2)/2 - geom.ctr; + geom.vm.normalize(); + + // determine if this is a large arc (> pi) + geom.isLargeArc = (geom.range > pi); + + // lines points + geom.pnt1 = geom.p1; + geom.pnt3 = geom.p2; + + if (geom.isLargeArc) { + geom.pnt2 = geom.p1 + geom.length * geom.vm; + geom.pnt4 = geom.p2 + geom.length * geom.vm; + + // recalculate angles for the outer arc + SbVec3f vc1_outer = (geom.pnt2 - geom.ctr); + SbVec3f vc2_outer = (geom.pnt4 - geom.ctr); + geom.arcCenter = geom.ctr; + geom.arcRadius = vc1_outer.length(); + // update angles for outer arc + geom.startangle = atan2f(vc1_outer[1], vc1_outer[0]); + geom.endangle = atan2f(vc2_outer[1], vc2_outer[0]); + } else { + geom.pnt2 = geom.p1 + (geom.length - geom.radius) * geom.vm; + geom.pnt4 = geom.p2 + (geom.length - geom.radius) * geom.vm; + + // arc center and radius for inner arc + geom.arcCenter = geom.ctr + (geom.length - geom.radius) * geom.vm; + geom.arcRadius = geom.radius; + } + + // normals for the arrowheads at arc start and end + geom.dirStart = SbVec3f(sin(geom.startangle), -cos(geom.startangle), 0); + geom.dirEnd = SbVec3f(-sin(geom.endangle), cos(geom.endangle), 0); + + return geom; +} diff --git a/src/Gui/SoDatumLabel.h b/src/Gui/SoDatumLabel.h index c6115cb18d..f2a5fd3fc6 100644 --- a/src/Gui/SoDatumLabel.h +++ b/src/Gui/SoDatumLabel.h @@ -159,6 +159,23 @@ private: float margin; // margin for calculations }; + struct ArcLengthGeometry { + SbVec3f ctr, p1, p2; + float length; + float margin; + float startangle, endangle; + float range; + float radius; + SbVec3f vm; // middle direction vector + SbVec3f pnt1, pnt2, pnt3, pnt4; // line endpoints + SbVec3f dirStart, dirEnd; // arrow directions + SbVec3f textOffset; + float angle; + bool isLargeArc; // whether range > pi + SbVec3f arcCenter; // center for arc drawing + float arcRadius; // radius for arc drawing + }; + float getScaleFactor(SoState*) const; void generateDistancePrimitives(SoAction * action, const SbVec3f&, const SbVec3f&); void generateDiameterPrimitives(SoAction * action, const SbVec3f&, const SbVec3f&); @@ -168,13 +185,14 @@ private: SbVec3f getLabelTextCenterDistance(const SbVec3f&, const SbVec3f&); SbVec3f getLabelTextCenterDiameter(const SbVec3f&, const SbVec3f&); SbVec3f getLabelTextCenterAngle(const SbVec3f&); - SbVec3f getLabelTextCenterArcLength(const SbVec3f&, const SbVec3f&, const SbVec3f&); + SbVec3f getLabelTextCenterArcLength(const SbVec3f&, const SbVec3f&, const SbVec3f&) const; bool hasDatumText() const; void getDimension(float scale, int& srcw, int& srch); DistanceGeometry calculateDistanceGeometry(const SbVec3f* points, float scale, int srch) const; DiameterGeometry calculateDiameterGeometry(const SbVec3f* points) const; AngleGeometry calculateAngleGeometry(const SbVec3f* points) const; SymmetricGeometry calculateSymmetricGeometry(const SbVec3f* points) const; + ArcLengthGeometry calculateArcLengthGeometry(const SbVec3f* points) const; void generateLineSelectionPrimitive(SoAction* action, const SbVec3f& start, const SbVec3f& end, float width); void generateArcSelectionPrimitive(SoAction* action, const SbVec3f& center, float radius, float startAngle, float endAngle, float width); void generateArrowSelectionPrimitive(SoAction* action, const SbVec3f& base, const SbVec3f& dir, float width, float length);