Sketcher: Make ArcLength constraint more interactable

This commit is contained in:
tetektoza
2025-06-11 00:00:31 +02:00
committed by Kacper Donat
parent ba0e9ac5a8
commit 44b3686459
2 changed files with 189 additions and 155 deletions

View File

@@ -477,6 +477,16 @@ private:
std::vector<SbVec3f> 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<float>;
}
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<SbVec3f> 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;
}

View File

@@ -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);