Files
create/src/Gui/SoDatumLabel.cpp
mosfet80 fbfe3a59bf Clean SoDatumLabel.cpp (#23217)
* Clean SoDatumLabel.cpp

removed unused variables

* Update SoDatumLabel.cpp
2025-08-24 23:00:29 -05:00

1871 lines
61 KiB
C++

/***************************************************************************
* Copyright (c) 2011-2012 Luke Parry <l.parry@warwick.ac.uk> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
# ifdef FC_OS_WIN32
# include <windows.h>
# undef min
# undef max
# endif
# ifdef FC_OS_MACOSX
# include <OpenGL/gl.h>
# else
# include <GL/gl.h>
# endif
# include <algorithm>
# include <cmath>
# include <limits>
# include <numbers>
# include <QFontMetrics>
# include <QPainter>
# include <Inventor/SoPrimitiveVertex.h>
# include <Inventor/actions/SoGLRenderAction.h>
# include <Inventor/elements/SoFocalDistanceElement.h>
# include <Inventor/elements/SoViewportRegionElement.h>
# include <Inventor/elements/SoViewVolumeElement.h>
# include <Inventor/misc/SoState.h>
#endif // _PreComp_
#include <Base/Tools.h>
#include <Gui/BitmapFactory.h>
#include <Gui/Tools.h>
#include "SoDatumLabel.h"
// NOLINTBEGIN(readability-magic-numbers,cppcoreguidelines-pro-bounds-pointer-arithmetic)
constexpr const float ZCONSTR {0.006F};
using namespace Gui;
// ------------------------------------------------------
namespace {
void glVertex(const SbVec3f& pt){
glVertex3f(pt[0], pt[1], pt[2]);
}
void glVertexes(const std::vector<SbVec3f>& pts){
for (auto pt: pts){
glVertex3f(pt[0], pt[1], pt[2]);
}
}
void glDrawLine(const SbVec3f& p1, const SbVec3f& p2){
glBegin(GL_LINES);
glVertexes({p1, p2});
glEnd();
}
void glDrawArc(const SbVec3f& center, float radius, float startAngle=0.,
float endAngle=2.0*std::numbers::pi, int countSegments=0){
float range = endAngle - startAngle;
if (countSegments == 0){
countSegments = std::max(6, abs(int(25.0 * range / std::numbers::pi)));
}
float segment = range / (countSegments-1);
glBegin(GL_LINE_STRIP);
for (int i=0; i < countSegments; i++) {
float theta = startAngle + segment*i;
SbVec3f v1 = center + radius * SbVec3f(cos(theta),sin(theta),0);
glVertex(v1);
}
glEnd();
}
void glDrawArrow(const SbVec3f& base, const SbVec3f& dir, float width, float length){
// Calculate arrowhead points
SbVec3f normal(dir[1], -dir[0], 0);
SbVec3f arrowLeft = base - length * dir + width * normal;
SbVec3f arrowRight = base - length * dir - width * normal;
// Draw arrowheads
glBegin(GL_TRIANGLES);
glVertexes({base, arrowLeft, arrowRight});
glEnd();
}
} // namespace
SO_NODE_SOURCE(SoDatumLabel)
void SoDatumLabel::initClass()
{
SO_NODE_INIT_CLASS(SoDatumLabel, SoShape, "Shape");
}
// NOLINTNEXTLINE
SoDatumLabel::SoDatumLabel()
{
SO_NODE_CONSTRUCTOR(SoDatumLabel);
SO_NODE_ADD_FIELD(string, (""));
SO_NODE_ADD_FIELD(textColor, (SbVec3f(1.0F,1.0F,1.0F)));
SO_NODE_ADD_FIELD(pnts, (SbVec3f(.0F,.0F,.0F)));
SO_NODE_ADD_FIELD(norm, (SbVec3f(.0F,.0F,1.F)));
SO_NODE_ADD_FIELD(name, ("Helvetica"));
SO_NODE_ADD_FIELD(size, (10.F));
SO_NODE_ADD_FIELD(lineWidth, (2.F));
SO_NODE_ADD_FIELD(sampling, (2.F));
SO_NODE_ADD_FIELD(datumtype, (SoDatumLabel::DISTANCE));
SO_NODE_DEFINE_ENUM_VALUE(Type, DISTANCE);
SO_NODE_DEFINE_ENUM_VALUE(Type, DISTANCEX);
SO_NODE_DEFINE_ENUM_VALUE(Type, DISTANCEY);
SO_NODE_DEFINE_ENUM_VALUE(Type, ANGLE);
SO_NODE_DEFINE_ENUM_VALUE(Type, RADIUS);
SO_NODE_DEFINE_ENUM_VALUE(Type, DIAMETER);
SO_NODE_DEFINE_ENUM_VALUE(Type, ARCLENGTH);
SO_NODE_SET_SF_ENUM_TYPE(datumtype, Type);
SO_NODE_ADD_FIELD(param1, (0.F));
SO_NODE_ADD_FIELD(param2, (0.F));
SO_NODE_ADD_FIELD(param4, (0.F));
SO_NODE_ADD_FIELD(param5, (0.F));
SO_NODE_ADD_FIELD(param6, (0.F));
SO_NODE_ADD_FIELD(param7, (0.F));
SO_NODE_ADD_FIELD(param8, (0.F));
useAntialiasing = true;
this->imgWidth = 0;
this->imgHeight = 0;
this->glimagevalid = false;
}
void SoDatumLabel::drawImage()
{
const SbString* s = string.getValues(0);
int num = string.getNum();
if (num == 0) {
this->image = SoSFImage();
return;
}
QFont font(QString::fromLatin1(name.getValue(), -1), size.getValue());
QFontMetrics fm(font);
QString str = QString::fromUtf8(s[0].getString());
int w = Gui::QtTools::horizontalAdvance(fm, str);
int h = fm.height();
// No Valid text
if (!w) {
this->image = SoSFImage();
return;
}
const SbColor& t = textColor.getValue();
QColor front;
front.setRgbF(t[0],t[1], t[2]);
QImage image(w * sampling.getValue(), h * sampling.getValue(), QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(sampling.getValue());
image.fill(0x00000000);
QPainter painter(&image);
if (useAntialiasing) {
painter.setRenderHint(QPainter::Antialiasing);
}
painter.setPen(front);
painter.setFont(font);
painter.drawText(0, 0, w, h, Qt::AlignLeft, str);
painter.end();
Gui::BitmapFactory().convert(image, this->image);
}
namespace Gui {
// helper class to determine the bounding box of a datum label
class DatumLabelBox
{
public:
DatumLabelBox(float scale, SoDatumLabel* label)
: scale{scale}
, label{label}
{
}
void computeBBox(SbBox3f& box, SbVec3f& center) const
{
std::vector<SbVec3f> corners;
if (label->datumtype.getValue() == SoDatumLabel::DISTANCE ||
label->datumtype.getValue() == SoDatumLabel::DISTANCEX ||
label->datumtype.getValue() == SoDatumLabel::DISTANCEY ) {
corners = computeDistanceBBox();
}
else if (label->datumtype.getValue() == SoDatumLabel::RADIUS ||
label->datumtype.getValue() == SoDatumLabel::DIAMETER) {
corners = computeRadiusDiameterBBox();
}
else if (label->datumtype.getValue() == SoDatumLabel::ANGLE) {
corners = computeAngleBBox();
}
else if (label->datumtype.getValue() == SoDatumLabel::SYMMETRIC) {
corners = computeSymmetricBBox();
}
else if (label->datumtype.getValue() == SoDatumLabel::ARCLENGTH) {
corners = computeArcLengthBBox();
}
getBBox(corners, box, center);
}
private:
void getBBox(const std::vector<SbVec3f>& corners, SbBox3f& box, SbVec3f& center) const
{
constexpr float floatMax = std::numeric_limits<float>::max();
if (corners.size() > 1) {
float minX = floatMax;
float minY = floatMax;
float maxX = -floatMax;
float maxY = -floatMax;
for (SbVec3f it : corners) {
minX = (it[0] < minX) ? it[0] : minX;
minY = (it[1] < minY) ? it[1] : minY;
maxX = (it[0] > maxX) ? it[0] : maxX;
maxY = (it[1] > maxY) ? it[1] : maxY;
}
// Store the bounding box
box.setBounds(SbVec3f(minX, minY, 0.0F), SbVec3f (maxX, maxY, 0.0F));
center = box.getCenter();
}
}
std::vector<SbVec3f> computeDistanceBBox() const
{
SbVec2s imgsize;
int nc {};
int srcw = 1;
int srch = 1;
const unsigned char * dataptr = label->image.getValue(imgsize, nc);
if (dataptr) {
srcw = imgsize[0];
srch = imgsize[1];
}
float aspectRatio = (float) srcw / (float) srch;
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() < 2) {
return {};
}
// use the shared geometry calculation for consistency
SoDatumLabel::DistanceGeometry geom = label->calculateDistanceGeometry(points);
std::vector<SbVec3f> corners;
float margin = imgHeight / 4.0F;
// include main points and extension line endpoints
corners.push_back(geom.p1);
corners.push_back(geom.p2);
corners.push_back(geom.perp1);
corners.push_back(geom.perp2);
// include text label area
corners.push_back(geom.textOffset + geom.dir * (imgWidth / 2.0F + margin) + geom.normal * (srch + margin));
corners.push_back(geom.textOffset - geom.dir * (imgWidth / 2.0F + margin) + geom.normal * (srch + margin));
corners.push_back(geom.textOffset + geom.dir * (imgWidth / 2.0F + margin) - geom.normal * margin);
corners.push_back(geom.textOffset - geom.dir * (imgWidth / 2.0F + margin) - geom.normal * margin);
// include arrow head positions for better selection
// arrows are positioned at dimension line endpoints (par1, par4)
corners.push_back(geom.par1);
corners.push_back(geom.par4);
corners.push_back(geom.ar1);
corners.push_back(geom.ar2);
corners.push_back(geom.ar3);
corners.push_back(geom.ar4);
return corners;
}
std::vector<SbVec3f> computeRadiusDiameterBBox() const
{
// get the points stored in the pnt field
const SbVec3f *points = label->pnts.getValues(0);
if (label->pnts.getNum() < 2) {
return {};
}
// use the shared geometry calculation for consistency
SoDatumLabel::DiameterGeometry geom = label->calculateDiameterGeometry(points);
std::vector<SbVec3f> corners;
// include main points and line segment points around text
corners.push_back(geom.p1);
corners.push_back(geom.p2);
corners.push_back(geom.pnt1);
corners.push_back(geom.pnt2);
// include arrow head positions for better selection
// first arrow head at p2
corners.push_back(geom.ar0);
corners.push_back(geom.ar1);
corners.push_back(geom.ar2);
// second arrow head for diameter (if applicable)
if (geom.isDiameter) {
corners.push_back(geom.ar0_1);
corners.push_back(geom.ar1_1);
corners.push_back(geom.ar2_1);
}
// sample points along the arc helper
constexpr int numArcSamples = 6;
const auto includeArcHelper = [&corners, &geom](float startAngle, float range) {
if (range != 0.0) {
for (int i = 0; i <= numArcSamples; i++) {
float t = static_cast<float>(i) / static_cast<float>(numArcSamples);
float angle = startAngle + t * range;
SbVec3f arcPoint = geom.center + SbVec3f(geom.radius * cos(angle), geom.radius * sin(angle), 0);
corners.push_back(arcPoint);
}
}
};
includeArcHelper(geom.startAngle, geom.startRange);
includeArcHelper(geom.endAngle, geom.endRange);
return corners;
}
std::vector<SbVec3f> computeAngleBBox() const
{
SbVec2s imgsize;
int nc {};
int srcw = 1;
int srch = 1;
const unsigned char * dataptr = label->image.getValue(imgsize, nc);
if (dataptr) {
srcw = imgsize[0];
srch = imgsize[1];
}
float aspectRatio = (float) srcw / (float) srch;
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() < 1) {
return {};
}
// use the shared geometry calculation for consistency
SoDatumLabel::AngleGeometry geom = label->calculateAngleGeometry(points);
std::vector<SbVec3f> corners;
// include extension line endpoints
corners.push_back(geom.pnt1);
corners.push_back(geom.pnt2);
corners.push_back(geom.pnt3);
corners.push_back(geom.pnt4);
// include text label area
SbVec3f img1 = SbVec3f(-imgWidth / 2.0F, -imgHeight / 2, 0.0F);
SbVec3f img2 = SbVec3f(-imgWidth / 2.0F, imgHeight / 2, 0.0F);
SbVec3f img3 = SbVec3f( imgWidth / 2.0F, -imgHeight / 2, 0.0F);
SbVec3f img4 = SbVec3f( imgWidth / 2.0F, imgHeight / 2, 0.0F);
img1 += geom.textOffset;
img2 += geom.textOffset;
img3 += geom.textOffset;
img4 += geom.textOffset;
corners.push_back(img1);
corners.push_back(img2);
corners.push_back(img3);
corners.push_back(img4);
// include arrow head positions for better selection
corners.push_back(geom.startArrowBase);
corners.push_back(geom.endArrowBase);
// include arrow tips (base + direction * length)
corners.push_back(geom.startArrowBase + geom.dirStart * geom.arrowLength);
corners.push_back(geom.endArrowBase + geom.dirEnd * geom.arrowLength);
return corners;
}
std::vector<SbVec3f> computeSymmetricBBox() const
{
// get the points stored in the pnt field
const SbVec3f *points = label->pnts.getValues(0);
if (label->pnts.getNum() < 2) {
return {};
}
// use shared geometry calculation
SoDatumLabel::SymmetricGeometry geom = label->calculateSymmetricGeometry(points);
// include all visual elements in bounding box
std::vector<SbVec3f> corners;
// main points (existing)
corners.push_back(geom.p1);
corners.push_back(geom.p2);
// first arrow triangle points
corners.push_back(geom.ar0); // arrow tip
corners.push_back(geom.ar1); // arrow base point 1
corners.push_back(geom.ar2); // arrow base point 2
// second arrow triangle points
corners.push_back(geom.ar3); // arrow tip
corners.push_back(geom.ar4); // arrow base point 1
corners.push_back(geom.ar5); // arrow base point 2
return corners;
}
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;
int srch = 1;
const unsigned char * dataptr = label->image.getValue(imgsize, nc);
if (dataptr) {
srcw = imgsize[0];
srch = imgsize[1];
}
float aspectRatio = (float) srcw / (float) srch;
float imgHeight = scale * (float) (srch);
float imgWidth = aspectRatio * imgHeight;
// text orientation
SbVec3f dir = (geom.p2 - geom.p1);
dir.normalize();
SbVec3f normal = SbVec3f (-dir[1], dir[0], 0);
// include all visual elements in bounding box
std::vector<SbVec3f> corners;
// text area (existing coverage)
float margin = imgHeight / 4.0F;
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;
}
private:
float scale;
SoDatumLabel* label;
};
} // namespace Gui
void SoDatumLabel::computeBBox(SoAction * action, SbBox3f &box, SbVec3f &center)
{
SoState *state = action->getState();
float scale = getScaleFactor(state);
Gui::DatumLabelBox datumBox(scale, this);
datumBox.computeBBox(box, center);
}
SbVec3f SoDatumLabel::getLabelTextCenter()
{
// Get the points stored
int numPts = this->pnts.getNum();
if (numPts < 2) {
return {};
}
const SbVec3f* points = this->pnts.getValues(0);
SbVec3f p1 = points[0];
SbVec3f p2 = points[1];
if (datumtype.getValue() == SoDatumLabel::DISTANCE ||
datumtype.getValue() == SoDatumLabel::DISTANCEX ||
datumtype.getValue() == SoDatumLabel::DISTANCEY) {
return getLabelTextCenterDistance(p1, p2);
}
if (datumtype.getValue() == SoDatumLabel::RADIUS ||
datumtype.getValue() == SoDatumLabel::DIAMETER) {
return getLabelTextCenterDiameter(p1, p2);
}
if (datumtype.getValue() == SoDatumLabel::ANGLE) {
return getLabelTextCenterAngle(p1);
}
if (datumtype.getValue() == SoDatumLabel::ARCLENGTH) {
if (numPts >= 3) {
SbVec3f p3 = points[2];
return getLabelTextCenterArcLength(p1, p2, p3);
}
}
return p1;
}
SbVec3f SoDatumLabel::getLabelTextCenterDistance(const SbVec3f& p1, const SbVec3f& p2)
{
float length = param1.getValue();
float length2 = param2.getValue();
SbVec3f dir;
SbVec3f normal;
constexpr float floatEpsilon = std::numeric_limits<float>::epsilon();
if (datumtype.getValue() == SoDatumLabel::DISTANCE) {
dir = (p2 - p1);
}
else if (datumtype.getValue() == SoDatumLabel::DISTANCEX) {
dir = SbVec3f((p2[0] - p1[0] >= floatEpsilon) ? 1 : -1, 0, 0);
}
else if (datumtype.getValue() == SoDatumLabel::DISTANCEY) {
dir = SbVec3f(0, (p2[1] - p1[1] >= floatEpsilon) ? 1 : -1, 0);
}
dir.normalize();
normal = SbVec3f(-dir[1], dir[0], 0);
float normproj12 = (p2 - p1).dot(normal);
SbVec3f p1_ = p1 + normproj12 * normal;
SbVec3f midpos = (p1_ + p2) / 2;
SbVec3f textCenter = midpos + normal * length + dir * length2;
return textCenter;
}
SbVec3f SoDatumLabel::getLabelTextCenterDiameter(const SbVec3f& p1, const SbVec3f& p2)
{
SbVec3f dir = (p2 - p1);
dir.normalize();
float length = this->param1.getValue();
SbVec3f textCenter = p2 + length * dir;
return textCenter;
}
SbVec3f SoDatumLabel::getLabelTextCenterAngle(const SbVec3f& p0)
{
// Load the Parameters
float length = param1.getValue();
float startangle = param2.getValue();
float range = param3.getValue();
float len2 = 2.0F * length;
// Useful Information
// v0 - vector for text position
// p0 - vector for angle intersect
SbVec3f v0(cos(startangle + range / 2), sin(startangle + range / 2), 0);
SbVec3f textCenter = p0 + v0 * len2;
return textCenter;
}
SbVec3f SoDatumLabel::getLabelTextCenterArcLength(const SbVec3f& ctr, const SbVec3f& p1, const SbVec3f& p2) const
{
float length = this->param1.getValue();
// Angles calculations
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>;
}
// Text location
SbVec3f vm = (p1+p2)/2 - ctr;
vm.normalize();
SbVec3f textCenter;
if (endangle - startangle <= std::numbers::pi) {
textCenter = ctr + vm * (length + this->imgHeight);
} else {
textCenter = ctr - vm * (length + 2. * this->imgHeight);
}
return textCenter;
}
void SoDatumLabel::generateDistancePrimitives(SoAction * action, const SbVec3f& p1, const SbVec3f& p2)
{
SbVec3f points[2] = {p1, p2};
DistanceGeometry geom = calculateDistanceGeometry(points);
// generate selectable primitive for txt 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);
float s = sin(geom.angle);
float c = cos(geom.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);
img1 += geom.textOffset;
img2 += geom.textOffset;
img3 += geom.textOffset;
img4 += geom.textOffset;
// text label selection primitive
SoPrimitiveVertex pv;
pv.setNormal(SbVec3f(0.F, 0.F, 1.F));
this->beginShape(action, TRIANGLE_STRIP);
pv.setPoint(img1);
shapeVertex(&pv);
pv.setPoint(img2);
shapeVertex(&pv);
pv.setPoint(img3);
shapeVertex(&pv);
pv.setPoint(img4);
shapeVertex(&pv);
this->endShape();
// beginning of generation of selectable primitives for lines
float lineWidth = geom.margin * 0.8f; // adjust the width for selection
// ext lines
generateLineSelectionPrimitive(action, geom.p1, geom.perp1, lineWidth);
generateLineSelectionPrimitive(action, geom.p2, geom.perp2, lineWidth);
// dim lines
generateLineSelectionPrimitive(action, geom.par1, geom.par2, lineWidth);
generateLineSelectionPrimitive(action, geom.par3, geom.par4, lineWidth);
// begin generation of selectable primitives for arrow-heads
this->beginShape(action, TRIANGLES);
pv.setNormal(SbVec3f(0.F, 0.F, 1.F));
// 1st arrow-head
pv.setPoint(geom.par1);
shapeVertex(&pv);
pv.setPoint(geom.ar1);
shapeVertex(&pv);
pv.setPoint(geom.ar2);
shapeVertex(&pv);
// 2nd arrow-head
pv.setPoint(geom.par4);
shapeVertex(&pv);
pv.setPoint(geom.ar3);
shapeVertex(&pv);
pv.setPoint(geom.ar4);
shapeVertex(&pv);
this->endShape();
}
void SoDatumLabel::generateDiameterPrimitives(SoAction * action, const SbVec3f& p1, const SbVec3f& p2)
{
SbVec3f points[2] = {p1, p2};
DiameterGeometry geom = calculateDiameterGeometry(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);
float s = sin(geom.angle);
float c = cos(geom.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);
img1 += geom.textOffset;
img2 += geom.textOffset;
img3 += geom.textOffset;
img4 += geom.textOffset;
// txt label selection primitive
SoPrimitiveVertex pv;
pv.setNormal(SbVec3f(0.F, 0.F, 1.F));
this->beginShape(action, TRIANGLE_STRIP);
pv.setPoint(img1);
shapeVertex(&pv);
pv.setPoint(img2);
shapeVertex(&pv);
pv.setPoint(img3);
shapeVertex(&pv);
pv.setPoint(img4);
shapeVertex(&pv);
this->endShape();
// generate selectable primitives for dimension lines
float lineWidth = geom.margin * 0.8f;
// main dimension lines (from center/start to text area and from text area to end)
generateLineSelectionPrimitive(action, geom.p1, geom.pnt1, lineWidth);
generateLineSelectionPrimitive(action, geom.pnt2, geom.p2, lineWidth);
// Generate selectable primitives for arrow heads
this->beginShape(action, TRIANGLES);
pv.setNormal(SbVec3f(0.F, 0.F, 1.F));
// first arrow-head
pv.setPoint(geom.ar0);
shapeVertex(&pv);
pv.setPoint(geom.ar1);
shapeVertex(&pv);
pv.setPoint(geom.ar2);
shapeVertex(&pv);
// second arrow-head but only for diameter
if (geom.isDiameter) {
pv.setPoint(geom.ar0_1);
shapeVertex(&pv);
pv.setPoint(geom.ar1_1);
shapeVertex(&pv);
pv.setPoint(geom.ar2_1);
shapeVertex(&pv);
}
this->endShape();
const auto generateSelectablePrimitiveForArcHelper = [&, this](float startAngle, float range) {
if (range != 0.0) {
int countSegments = std::max(6, abs(int(50.0 * range / (2 * std::numbers::pi))));
double segment = range / (countSegments - 1);
// create selectable line segments for the arc
for (int i = 0; i < countSegments - 1; i++) {
double theta1 = startAngle + segment * i;
double theta2 = startAngle + segment * (i + 1);
SbVec3f v1 = geom.center + SbVec3f(geom.radius * cos(theta1), geom.radius * sin(theta1), 0);
SbVec3f v2 = geom.center + SbVec3f(geom.radius * cos(theta2), geom.radius * sin(theta2), 0);
generateLineSelectionPrimitive(action, v1, v2, lineWidth * 0.5f);
}
}
};
generateSelectablePrimitiveForArcHelper(geom.startAngle, geom.startRange);
generateSelectablePrimitiveForArcHelper(geom.endAngle, geom.endRange);
}
void SoDatumLabel::generateAnglePrimitives(SoAction * action, const SbVec3f& p0)
{
// use shared geometry calculation
SbVec3f points[1] = {p0};
AngleGeometry geom = calculateAngleGeometry(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);
img1 += geom.textOffset;
img2 += geom.textOffset;
img3 += geom.textOffset;
img4 += geom.textOffset;
// text label selection primitive
SoPrimitiveVertex pv;
pv.setNormal(SbVec3f(0.F, 0.F, 1.F));
this->beginShape(action, TRIANGLE_STRIP);
pv.setPoint(img1);
shapeVertex(&pv);
pv.setPoint(img2);
shapeVertex(&pv);
pv.setPoint(img3);
shapeVertex(&pv);
pv.setPoint(img4);
shapeVertex(&pv);
this->endShape();
// generate selectable primitives for dimension 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 primitives for arc segments
float arcWidth = geom.margin * 0.6f;
// arc before text
generateArcSelectionPrimitive(action, geom.p0, geom.r, geom.startangle, geom.startangle + geom.range / 2.0 - geom.textMargin, arcWidth);
// arc after text
generateArcSelectionPrimitive(action, geom.p0, geom.r, geom.startangle + geom.range / 2.0 + geom.textMargin, geom.endangle, arcWidth);
// generate selectable primitives for arrow heads
generateArrowSelectionPrimitive(action, geom.startArrowBase, geom.dirStart, geom.arrowWidth, geom.arrowLength);
generateArrowSelectionPrimitive(action, geom.endArrowBase, geom.dirEnd, geom.arrowWidth, geom.arrowLength);
}
void SoDatumLabel::generateSymmetricPrimitives(SoAction * action, const SbVec3f& p1, const SbVec3f& p2)
{
// use shared geometry calculation
SbVec3f points[2] = {p1, p2};
SymmetricGeometry geom = calculateSymmetricGeometry(points);
// generate selectable primitives for lines
float lineWidth = geom.margin * 0.8f;
// lines from endpoints to arrow tips
generateLineSelectionPrimitive(action, geom.p1, geom.ar0, lineWidth);
generateLineSelectionPrimitive(action, geom.p2, geom.ar3, lineWidth);
// generate selectable primitives for arrow heads as triangles
SoPrimitiveVertex pv;
pv.setNormal(SbVec3f(0.F, 0.F, 1.F));
this->beginShape(action, TRIANGLES);
// first arrow
pv.setPoint(geom.ar0);
shapeVertex(&pv);
pv.setPoint(geom.ar1);
shapeVertex(&pv);
pv.setPoint(geom.ar2);
shapeVertex(&pv);
// second arrow
pv.setPoint(geom.ar3);
shapeVertex(&pv);
pv.setPoint(geom.ar4);
shapeVertex(&pv);
pv.setPoint(geom.ar5);
shapeVertex(&pv);
this->endShape();
}
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);
float s = sin(geom.angle);
float c = cos(geom.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);
img1 += geom.textOffset;
img2 += geom.textOffset;
img3 += geom.textOffset;
img4 += geom.textOffset;
// text label selection primitive
SoPrimitiveVertex pv;
pv.setNormal(SbVec3f(0.F, 0.F, 1.F));
this->beginShape(action, TRIANGLE_STRIP);
pv.setPoint(img1);
shapeVertex(&pv);
pv.setPoint(img2);
shapeVertex(&pv);
pv.setPoint(img3);
shapeVertex(&pv);
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)
{
// Initialisation check (needs something more sensible) prevents an infinite loop bug
constexpr float floatEpsilon = std::numeric_limits<float>::epsilon();
if (this->imgHeight <= floatEpsilon || this->imgWidth <= floatEpsilon) {
return;
}
int numPts = this->pnts.getNum();
if (numPts < 2) {
return;
}
// Get the points stored
const SbVec3f *points = this->pnts.getValues(0);
SbVec3f p1 = points[0];
SbVec3f p2 = points[1];
// Change the offset and bounding box parameters depending on Datum Type
if (this->datumtype.getValue() == DISTANCE ||
this->datumtype.getValue() == DISTANCEX ||
this->datumtype.getValue() == DISTANCEY) {
generateDistancePrimitives(action, p1, p2);
}
else if (this->datumtype.getValue() == RADIUS ||
this->datumtype.getValue() == DIAMETER) {
generateDiameterPrimitives(action, p1, p2);
}
else if (this->datumtype.getValue() == ANGLE) {
generateAnglePrimitives(action, p1);
}
else if (this->datumtype.getValue() == SYMMETRIC) {
generateSymmetricPrimitives(action, p1, p2);
}
else if (this->datumtype.getValue() == ARCLENGTH) {
if (numPts >= 3) {
SbVec3f p3 = points[2];
generateArcLengthPrimitives(action, p1, p2, p3);
}
}
}
void SoDatumLabel::notify(SoNotList * l)
{
SoField * f = l->getLastField();
if (f == &this->string) {
this->glimagevalid = false;
}
else if (f == &this->textColor) {
this->glimagevalid = false;
}
else if (f == &this->name) {
this->glimagevalid = false;
}
else if (f == &this->size) {
this->glimagevalid = false;
}
else if (f == &this->image) {
this->glimagevalid = false;
}
inherited::notify(l);
}
float SoDatumLabel::getScaleFactor(SoState* state) const
{
/**Remark from Stefan Tröger:
* The scale calculation is based on knowledge of SbViewVolume::getWorldToScreenScale
* implementation internals. The factor returned from this function is calculated from the view frustums
* nearplane width, height is not taken into account, and hence we divide it with the viewport width
* to get the exact pixel scale factor.
* This is not documented and therefore may change on later coin versions!
*/
const SbViewVolume & vv = SoViewVolumeElement::get(state);
// As reference use the center point the camera is looking at on the focal plane
// because then independent of the camera we get a constant scale factor when panning.
// If we used (0,0,0) instead then the scale factor would change heavily in perspective
// rendering mode. See #0002921 and #0002922.
// It's important to use the distance to the focal plane an not near or far plane because
// depending on additionally displayed objects they may change heavily and thus impact the
// scale factor. See #7082 and #7860.
float focal = SoFocalDistanceElement::get(state);
SbVec3f center = vv.getSightPoint(focal);
float scale = vv.getWorldToScreenScale(center, 1.F);
const SbViewportRegion & vp = SoViewportRegionElement::get(state);
SbVec2s vp_size = vp.getViewportSizePixels();
scale /= float(vp_size[0]);
return scale;
}
void SoDatumLabel::GLRender(SoGLRenderAction * action)
{
SoState *state = action->getState();
if (!shouldGLRender(action)) {
return;
}
if (action->handleTransparency(true)) {
return;
}
const float scale = getScaleFactor(state);
bool hasText = hasDatumText();
int srcw = 1;
int srch = 1;
if (hasText) {
getDimension(scale, srcw, srch);
}
if (this->datumtype.getValue() == SYMMETRIC) {
this->imgHeight = scale*25.0F;
this->imgWidth = scale*25.0F;
}
// Get the points stored in the pnt field
const SbVec3f *points = this->pnts.getValues(0);
state->push();
//Set General OpenGL Properties
glPushAttrib(GL_ENABLE_BIT | GL_PIXEL_MODE_BIT | GL_COLOR_BUFFER_BIT);
glDisable(GL_LIGHTING);
glDisable(GL_CULL_FACE);
//Enable Anti-alias
if (action->isSmoothing()) {
glEnable(GL_LINE_SMOOTH);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
glHint(GL_LINE_SMOOTH_HINT,GL_NICEST);
}
// Position for Datum Text Label
float angle = 0;
// Get the colour
const SbColor& t = textColor.getValue();
// Set GL Properties
glLineWidth(this->lineWidth.getValue());
glColor3f(t[0], t[1], t[2]);
SbVec3f textOffset;
if (this->datumtype.getValue() == DISTANCE ||
this->datumtype.getValue() == DISTANCEX ||
this->datumtype.getValue() == DISTANCEY ) {
drawDistance(points, angle, textOffset);
}
else if (this->datumtype.getValue() == RADIUS || this->datumtype.getValue() == DIAMETER) {
drawRadiusOrDiameter(points, angle, textOffset);
}
else if (this->datumtype.getValue() == ANGLE) {
drawAngle(points, angle, textOffset);
}
else if (this->datumtype.getValue() == SYMMETRIC) {
drawSymmetric(points);
}
else if (this->datumtype.getValue() == ARCLENGTH) {
drawArcLength(points, angle, textOffset);
}
if (hasText) {
drawText(state, srcw, srch, angle, textOffset);
}
glPopAttrib();
state->pop();
}
bool SoDatumLabel::hasDatumText() const
{
const SbString* s = string.getValues(0);
return (s->getLength() > 0);
}
void SoDatumLabel::getDimension(float scale, int& srcw, int& srch)
{
SbVec2s imgsize;
int nc {};
if (!this->glimagevalid) {
drawImage();
this->glimagevalid = true;
}
const unsigned char * dataptr = this->image.getValue(imgsize, nc);
if (!dataptr) { // no image
return;
}
srcw = imgsize[0];
srch = imgsize[1];
float aspectRatio = (float) srcw / (float) srch;
this->imgHeight = scale * (float) (srch) / sampling.getValue();
this->imgWidth = aspectRatio * (float) this->imgHeight;
}
void SoDatumLabel::drawDistance(const SbVec3f* points, float& angle, SbVec3f& textOffset)
{
SoDatumLabel::DistanceGeometry geom = this->calculateDistanceGeometry(points);
angle = geom.angle;
textOffset = geom.textOffset;
// Get the colour
const SbColor& t = textColor.getValue();
// Set GL Properties
glLineWidth(this->lineWidth.getValue());
glColor3f(t[0], t[1], t[2]);
// Perp Lines
glBegin(GL_LINES);
if (this->param1.getValue() != 0.) {
glVertex2f(geom.p1[0], geom.p1[1]);
glVertex2f(geom.perp1[0], geom.perp1[1]);
glVertex2f(geom.p2[0], geom.p2[1]);
glVertex2f(geom.perp2[0], geom.perp2[1]);
}
glVertex2f(geom.par1[0], geom.par1[1]);
glVertex2f(geom.par2[0], geom.par2[1]);
glVertex2f(geom.par3[0], geom.par3[1]);
glVertex2f(geom.par4[0], geom.par4[1]);
glEnd();
// Draw the arrowheads
glBegin(GL_TRIANGLES);
glVertex2f(geom.par1[0], geom.par1[1]);
glVertex2f(geom.ar1[0], geom.ar1[1]);
glVertex2f(geom.ar2[0], geom.ar2[1]);
glVertex2f(geom.par4[0], geom.par4[1]);
glVertex2f(geom.ar3[0], geom.ar3[1]);
glVertex2f(geom.ar4[0], geom.ar4[1]);
glEnd();
if (this->datumtype.getValue() == DISTANCE) {
drawDistance(points);
}
}
void SoDatumLabel::drawDistance(const SbVec3f* points)
{
// Draw arc helpers if needed
float range1 = this->param4.getValue();
if (range1 != 0.0) {
float startAngle1 = this->param3.getValue();
float radius1 = this->param5.getValue();
SbVec3f center1 = points[2];
glDrawArc(center1, radius1, startAngle1, startAngle1 + range1);
}
float range2 = this->param7.getValue();
if (range2 != 0.0) {
float startAngle2 = this->param6.getValue();
float radius2 = this->param8.getValue();
SbVec3f center2 = points[3];
glDrawArc(center2, radius2, startAngle2, startAngle2 + range2);
}
}
void SoDatumLabel::drawRadiusOrDiameter(const SbVec3f* points, float& angle, SbVec3f& textOffset)
{
// Use shared geometry calculation
DiameterGeometry geom = calculateDiameterGeometry(points);
angle = geom.angle;
textOffset = geom.textOffset;
// Draw the Lines
glBegin(GL_LINES);
glVertex2f(geom.p1[0], geom.p1[1]);
glVertex2f(geom.pnt1[0], geom.pnt1[1]);
glVertex2f(geom.pnt2[0], geom.pnt2[1]);
glVertex2f(geom.p2[0], geom.p2[1]);
glEnd();
glBegin(GL_TRIANGLES);
glVertex2f(geom.ar0[0], geom.ar0[1]);
glVertex2f(geom.ar1[0], geom.ar1[1]);
glVertex2f(geom.ar2[0], geom.ar2[1]);
glEnd();
if (geom.isDiameter) {
// Draw second arrowhead
glBegin(GL_TRIANGLES);
glVertex2f(geom.ar0_1[0], geom.ar0_1[1]);
glVertex2f(geom.ar1_1[0], geom.ar1_1[1]);
glVertex2f(geom.ar2_1[0], geom.ar2_1[1]);
glEnd();
}
// Draw arc helpers if needed
if (geom.startRange != 0.0) {
glDrawArc(geom.center, geom.radius, geom.startAngle, geom.startAngle + geom.startRange);
}
if (geom.endRange != 0.0) {
glDrawArc(geom.center, geom.radius, geom.endAngle, geom.endAngle + geom.endRange);
}
}
void SoDatumLabel::drawAngle(const SbVec3f* points, float& angle, SbVec3f& textOffset)
{
// use shared geometry calculation
AngleGeometry geom = calculateAngleGeometry(points);
angle = geom.angle;
textOffset = geom.textOffset;
// draw arc segments
glDrawArc(geom.p0, geom.r, geom.startangle, geom.startangle + geom.range / 2.0 - geom.textMargin);
glDrawArc(geom.p0, geom.r, geom.startangle + geom.range / 2.0 + geom.textMargin, geom.endangle);
// draw extension lines
glDrawLine(geom.pnt1, geom.pnt2);
glDrawLine(geom.pnt3, geom.pnt4);
// draw arrowheads
glDrawArrow(geom.startArrowBase, geom.dirStart, geom.arrowWidth, geom.arrowLength);
glDrawArrow(geom.endArrowBase, geom.dirEnd, geom.arrowWidth, geom.arrowLength);
}
void SoDatumLabel::drawSymmetric(const SbVec3f* points)
{
// use shared geometry calculation
SymmetricGeometry geom = calculateSymmetricGeometry(points);
// draw first arrow
glBegin(GL_LINES);
glVertex3f(geom.p1[0], geom.p1[1], ZCONSTR);
glVertex3f(geom.ar0[0], geom.ar0[1], ZCONSTR);
glVertex3f(geom.ar0[0], geom.ar0[1], ZCONSTR);
glVertex3f(geom.ar1[0], geom.ar1[1], ZCONSTR);
glVertex3f(geom.ar0[0], geom.ar0[1], ZCONSTR);
glVertex3f(geom.ar2[0], geom.ar2[1], ZCONSTR);
glEnd();
// draw second arrow
glBegin(GL_LINES);
glVertex3f(geom.p2[0], geom.p2[1], ZCONSTR);
glVertex3f(geom.ar3[0], geom.ar3[1], ZCONSTR);
glVertex3f(geom.ar3[0], geom.ar3[1], ZCONSTR);
glVertex3f(geom.ar4[0], geom.ar4[1], ZCONSTR);
glVertex3f(geom.ar3[0], geom.ar3[1], ZCONSTR);
glVertex3f(geom.ar5[0], geom.ar5[1], ZCONSTR);
glEnd();
}
void SoDatumLabel::drawArcLength(const SbVec3f* points, float& angle, SbVec3f& textOffset)
{
// use shared geometry calculation
ArcLengthGeometry geom = calculateArcLengthGeometry(points);
// set output parameters
angle = geom.angle;
textOffset = geom.textOffset;
// draw arc
glDrawArc(geom.arcCenter, geom.arcRadius, geom.startangle, geom.endangle);
// draw lines
glDrawLine(geom.pnt1, geom.pnt2);
glDrawLine(geom.pnt3, geom.pnt4);
// create the arrowheads
float arrowLength = geom.margin * 2;
float arrowWidth = geom.margin * 0.5F;
glDrawArrow(geom.pnt2, geom.dirStart, arrowWidth, arrowLength);
glDrawArrow(geom.pnt4, geom.dirEnd, arrowWidth, arrowLength);
}
// NOLINTNEXTLINE
void SoDatumLabel::drawText(SoState *state, int srcw, int srch, float angle, const SbVec3f& textOffset)
{
SbVec2s imgsize;
int nc {};
const unsigned char * dataptr = this->image.getValue(imgsize, nc);
//Get the camera z-direction
const SbViewVolume & vv = SoViewVolumeElement::get(state);
SbVec3f z = vv.zVector();
bool flip = norm.getValue().dot(z) > std::numeric_limits<float>::epsilon();
static bool init = false;
static bool npot = false;
if (!init) {
init = true;
std::string ext = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)); // NOLINT
npot = (ext.find("GL_ARB_texture_non_power_of_two") != std::string::npos);
}
int w = srcw;
int h = srch;
if (!npot) {
// make power of two
if ((w & (w-1)) != 0) {
int i=1;
while (i < 8) {
if ((w >> i) == 0) {
break;
}
i++;
}
w = (1 << i);
}
// make power of two
if ((h & (h-1)) != 0) {
int i=1;
while (i < 8) {
if ((h >> i) == 0) {
break;
}
i++;
}
h = (1 << i);
}
}
glDisable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D); // Enable Textures
glEnable(GL_BLEND);
// glGenTextures/glBindTexture was commented out but it must be active, see:
// #0000971: Tracing over a background image in Sketcher: image is overwritten by first dimensional constraint text
// #0001185: Planer image changes to number graphic when a part design constraint is made after the planar image
//
// Copy the text bitmap into memory and bind
GLuint myTexture {};
// generate a texture
glGenTextures(1, &myTexture);
glBindTexture(GL_TEXTURE_2D, myTexture);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
if (!npot) {
QImage imagedata(w, h,QImage::Format_ARGB32_Premultiplied);
imagedata.fill(0x00000000);
int sx = (w - srcw)/2;
int sy = (h - srch)/2;
glTexImage2D(GL_TEXTURE_2D, 0, nc, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const GLvoid*)imagedata.bits());
glTexSubImage2D(GL_TEXTURE_2D, 0, sx, sy, srcw, srch, GL_RGBA, GL_UNSIGNED_BYTE,(const GLvoid*) dataptr);
}
else {
glTexImage2D(GL_TEXTURE_2D, 0, nc, srcw, srch, 0, GL_RGBA, GL_UNSIGNED_BYTE,(const GLvoid*) dataptr);
}
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
// Apply a rotation and translation matrix
glTranslatef(textOffset[0], textOffset[1], textOffset[2]);
glRotatef(Base::toDegrees<GLfloat>(angle), 0,0,1);
glBegin(GL_QUADS);
glColor3f(1.F, 1.F, 1.F);
glTexCoord2f(flip ? 0.F : 1.F, 1.F); glVertex2f( -this->imgWidth / 2, this->imgHeight / 2);
glTexCoord2f(flip ? 0.F : 1.F, 0.F); glVertex2f( -this->imgWidth / 2, -this->imgHeight / 2);
glTexCoord2f(flip ? 1.F : 0.F, 0.F); glVertex2f( this->imgWidth / 2, -this->imgHeight / 2);
glTexCoord2f(flip ? 1.F : 0.F, 1.F); glVertex2f( this->imgWidth / 2, this->imgHeight / 2);
glEnd();
// Reset the Mode
glPopMatrix();
// wmayer: see bug report below which is caused by generating but not
// deleting the texture.
// #0000721: massive memory leak when dragging an unconstrained model
glDeleteTextures(1, &myTexture);
}
void SoDatumLabel::setPoints(SbVec3f p1, SbVec3f p2)
{
pnts.setNum(2);
SbVec3f* verts = pnts.startEditing();
verts[0] = p1;
verts[1] = p2;
pnts.finishEditing();
}
// NOLINTEND(readability-magic-numbers,cppcoreguidelines-pro-bounds-pointer-arithmetic)
SoDatumLabel::DistanceGeometry SoDatumLabel::calculateDistanceGeometry(const SbVec3f* points) const
{
using std::numbers::pi;
SoDatumLabel::DistanceGeometry geom;
float length = this->param1.getValue();
float length2 = this->param2.getValue();
geom.p1 = points[0];
geom.p2 = points[1];
constexpr float floatEpsilon = std::numeric_limits<float>::epsilon();
if (this->datumtype.getValue() == DISTANCE) {
geom.dir = (geom.p2 - geom.p1);
} else if (this->datumtype.getValue() == DISTANCEX) {
geom.dir = SbVec3f((geom.p2[0] - geom.p1[0] >= floatEpsilon) ? 1 : -1, 0, 0);
} else if (this->datumtype.getValue() == DISTANCEY) {
geom.dir = SbVec3f(0, (geom.p2[1] - geom.p1[1] >= floatEpsilon) ? 1 : -1, 0);
}
geom.dir.normalize();
geom.normal = SbVec3f(-geom.dir[1], geom.dir[0], 0);
// when the datum line is not parallel to p1-p2 the projection of
// p1-p2 on normal is not zero, p2 is considered as reference and p1
// is replaced by its projection p1_
float normproj12 = (geom.p2 - geom.p1).dot(geom.normal);
SbVec3f p1_ = geom.p1 + normproj12 * geom.normal;
geom.midpos = (p1_ + geom.p2) / 2;
// Get magnitude of angle between horizontal
geom.angle = atan2f(geom.dir[1], geom.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;
}
geom.textOffset = geom.midpos + geom.normal * length + geom.dir * length2;
geom.margin = this->imgHeight / 3.0F;
float offset1 = ((length + normproj12 < 0) ? -1.F : 1.F) * geom.margin;
float offset2 = ((length < 0) ? -1 : 1) * geom.margin;
geom.perp1 = p1_ + geom.normal * (length + offset1);
geom.perp2 = geom.p2 + geom.normal * (length + offset2);
// Calculate the coordinates for the parallel datum lines
geom.par1 = p1_ + geom.normal * length;
geom.par2 = geom.midpos + geom.normal * length + geom.dir * (length2 - this->imgWidth / 2 - geom.margin);
geom.par3 = geom.midpos + geom.normal * length + geom.dir * (length2 + this->imgWidth / 2 + geom.margin);
geom.par4 = geom.p2 + geom.normal * length;
geom.flipTriang = false;
if ((geom.par3 - geom.par1).dot(geom.dir) > (geom.par4 - geom.par1).length()) {
// Increase Margin to improve visibility
float tmpMargin = this->imgHeight / 0.75F;
geom.par3 = geom.par4;
if ((geom.par2 - geom.par1).dot(geom.dir) > (geom.par4 - geom.par1).length()) {
geom.par3 = geom.par2;
geom.par2 = geom.par1 - geom.dir * tmpMargin;
geom.flipTriang = true;
}
}
else if ((geom.par2 - geom.par1).dot(geom.dir) < 0.F) {
float tmpMargin = this->imgHeight / 0.75F;
geom.par2 = geom.par1;
if((geom.par3 - geom.par1).dot(geom.dir) < 0.F) {
geom.par2 = geom.par3;
geom.par3 = geom.par4 + geom.dir * tmpMargin;
geom.flipTriang = true;
}
}
geom.arrowWidth = geom.margin * 0.5F;
geom.ar1 = geom.par1 + ((geom.flipTriang) ? -1 : 1) * geom.dir * 0.866F * 2 * geom.margin;
geom.ar2 = geom.ar1 + geom.normal * geom.arrowWidth;
geom.ar1 -= geom.normal * geom.arrowWidth;
geom.ar3 = geom.par4 - ((geom.flipTriang) ? -1 : 1) * geom.dir * 0.866F * 2 * geom.margin;
geom.ar4 = geom.ar3 + geom.normal * geom.arrowWidth;
geom.ar3 -= geom.normal * geom.arrowWidth;
return geom;
}
SoDatumLabel::DiameterGeometry SoDatumLabel::calculateDiameterGeometry(const SbVec3f* points) const
{
DiameterGeometry geom;
// Get the Points
geom.p1 = points[0];
geom.p2 = points[1];
geom.dir = (geom.p2 - geom.p1);
geom.center = geom.p1;
geom.radius = (geom.p2 - geom.p1).length();
geom.isDiameter = (this->datumtype.getValue() == DIAMETER);
if (geom.isDiameter) {
geom.center = (geom.p1 + geom.p2) / 2;
geom.radius = geom.radius / 2;
}
geom.dir.normalize();
geom.normal = SbVec3f(-geom.dir[1], geom.dir[0], 0);
float length = this->param1.getValue();
geom.pos = geom.p2 + length * geom.dir;
// Get magnitude of angle between horizontal
geom.angle = atan2f(geom.dir[1], geom.dir[0]);
if (geom.angle > std::numbers::pi/2 + std::numbers::pi/12) {
geom.angle -= (float)std::numbers::pi;
} else if (geom.angle <= -std::numbers::pi/2 + std::numbers::pi/12) {
geom.angle += (float)std::numbers::pi;
}
geom.textOffset = geom.pos;
geom.margin = this->imgHeight / 3.0F;
// Create the first arrowhead
geom.arrowWidth = geom.margin * 0.5F;
geom.ar0 = geom.p2;
geom.ar1 = geom.p2 - geom.dir * 0.866F * 2 * geom.margin;
geom.ar2 = geom.ar1 + geom.normal * geom.arrowWidth;
geom.ar1 -= geom.normal * geom.arrowWidth;
SbVec3f p3 = geom.pos + geom.dir * (this->imgWidth / 2 + geom.margin);
if ((p3 - geom.p1).length() > (geom.p2 - geom.p1).length()) {
geom.p2 = p3;
}
// Calculate the line segment points around text
geom.pnt1 = geom.pos - geom.dir * (geom.margin + this->imgWidth / 2);
geom.pnt2 = geom.pos + geom.dir * (geom.margin + this->imgWidth / 2);
if (geom.isDiameter) {
// Create second arrowhead for diameter
geom.ar0_1 = geom.p1;
geom.ar1_1 = geom.p1 + geom.dir * 0.866F * 2 * geom.margin;
geom.ar2_1 = geom.ar1_1 + geom.normal * geom.arrowWidth;
geom.ar1_1 -= geom.normal * geom.arrowWidth;
}
// Arc helper parameters
geom.startAngle = this->param3.getValue();
geom.startRange = this->param4.getValue();
geom.endAngle = this->param5.getValue();
geom.endRange = this->param6.getValue();
return geom;
}
SoDatumLabel::AngleGeometry SoDatumLabel::calculateAngleGeometry(const SbVec3f* points) const
{
AngleGeometry geom;
// only the angle intersection point is needed
geom.p0 = points[0];
geom.margin = this->imgHeight / 3.0F;
// load the parameters
geom.length = this->param1.getValue();
geom.startangle = this->param2.getValue();
geom.range = this->param3.getValue();
geom.endangle = geom.startangle + geom.range;
geom.endLineLength1 = std::max(this->param4.getValue(), geom.margin);
geom.endLineLength2 = std::max(this->param5.getValue(), geom.margin);
geom.endLineLength12 = std::max(-this->param4.getValue(), geom.margin);
geom.endLineLength22 = std::max(-this->param5.getValue(), geom.margin);
geom.r = 2 * geom.length;
// set the text label angle to zero
geom.angle = 0.F;
// useful information
// v0 - vector for text position
// p0 - vector for angle intersect
geom.v0 = SbVec3f(cos(geom.startangle + geom.range / 2), sin(geom.startangle + geom.range / 2), 0);
// leave some space for the text
geom.textMargin = std::min(0.2F * abs(geom.range), this->imgWidth / (2 * geom.r));
geom.textOffset = geom.p0 + geom.v0 * geom.r;
// direction vectors for start and end lines
geom.v1 = SbVec3f(cos(geom.startangle), sin(geom.startangle), 0);
geom.v2 = SbVec3f(cos(geom.endangle), sin(geom.endangle), 0);
if (geom.range < 0 || geom.length < 0) {
std::swap(geom.v1, geom.v2);
geom.textMargin = -geom.textMargin;
}
geom.pnt1 = geom.p0 + (geom.r - geom.endLineLength1) * geom.v1;
geom.pnt2 = geom.p0 + (geom.r + geom.endLineLength12) * geom.v1;
geom.pnt3 = geom.p0 + (geom.r - geom.endLineLength2) * geom.v2;
geom.pnt4 = geom.p0 + (geom.r + geom.endLineLength22) * geom.v2;
// create the arrowheads
geom.arrowLength = geom.margin * 2;
geom.arrowWidth = geom.margin * 0.5F;
geom.dirStart = SbVec3f(geom.v1[1], -geom.v1[0], 0);
geom.startArrowBase = geom.p0 + geom.r * geom.v1;
geom.dirEnd = SbVec3f(-geom.v2[1], geom.v2[0], 0);
geom.endArrowBase = geom.p0 + geom.r * geom.v2;
return geom;
}
SoDatumLabel::SymmetricGeometry SoDatumLabel::calculateSymmetricGeometry(const SbVec3f* points) const
{
SymmetricGeometry geom;
geom.p1 = points[0];
geom.p2 = points[1];
geom.dir = (geom.p2 - geom.p1);
geom.dir.normalize();
geom.normal = SbVec3f(-geom.dir[1], geom.dir[0], 0);
geom.margin = this->imgHeight / 4.0F;
// calculate coordinates for the first arrow
geom.ar0 = geom.p1 + geom.dir * 4 * geom.margin; // tip of arrow
geom.ar1 = geom.ar0 - geom.dir * 0.866F * 2 * geom.margin;
geom.ar2 = geom.ar1 + geom.normal * geom.margin;
geom.ar1 -= geom.normal * geom.margin;
// calculate coordinates for the second arrow
geom.ar3 = geom.p2 - geom.dir * 4 * geom.margin; // tip of 2nd arrow
geom.ar4 = geom.ar3 + geom.dir * 0.866F * 2 * geom.margin;
geom.ar5 = geom.ar4 + geom.normal * geom.margin;
geom.ar4 -= geom.normal * geom.margin;
return geom;
}
void SoDatumLabel::generateLineSelectionPrimitive(SoAction* action, const SbVec3f& start, const SbVec3f& end, float width)
{
// create a thicker line used for selection
SbVec3f dir = end - start;
dir.normalize();
SbVec3f perp = SbVec3f(-dir[1], dir[0], 0) * (width / 2.0f);
SbVec3f p1 = start + perp;
SbVec3f p2 = start - perp;
SbVec3f p3 = end + perp;
SbVec3f p4 = end - perp;
SoPrimitiveVertex pv;
pv.setNormal(SbVec3f(0.F, 0.F, 1.F));
this->beginShape(action, TRIANGLE_STRIP);
pv.setPoint(p1);
shapeVertex(&pv);
pv.setPoint(p2);
shapeVertex(&pv);
pv.setPoint(p3);
shapeVertex(&pv);
pv.setPoint(p4);
shapeVertex(&pv);
this->endShape();
}
void SoDatumLabel::generateArcSelectionPrimitive(SoAction* action, const SbVec3f& center, float radius, float startAngle, float endAngle, float width)
{
// create selectable arc by generating line segments
int countSegments = std::max(6, abs(int(50.0 * (endAngle - startAngle) / (2 * std::numbers::pi))));
double segment = (endAngle - startAngle) / (countSegments - 1);
for (int i = 0; i < countSegments - 1; i++) {
double theta1 = startAngle + segment * i;
double theta2 = startAngle + segment * (i + 1);
SbVec3f v1 = center + SbVec3f(radius * cos(theta1), radius * sin(theta1), 0);
SbVec3f v2 = center + SbVec3f(radius * cos(theta2), radius * sin(theta2), 0);
generateLineSelectionPrimitive(action, v1, v2, width);
}
}
void SoDatumLabel::generateArrowSelectionPrimitive(SoAction* action, const SbVec3f& base, const SbVec3f& dir, float width, float length)
{
// create selectable arrow as a triangle
SbVec3f tip = base + dir * length;
SbVec3f perp = SbVec3f(-dir[1], dir[0], 0) * (width / 2.0f);
SbVec3f p1 = base + perp;
SbVec3f p2 = base - perp;
SoPrimitiveVertex pv;
pv.setNormal(SbVec3f(0.F, 0.F, 1.F));
this->beginShape(action, TRIANGLES);
pv.setPoint(tip);
shapeVertex(&pv);
pv.setPoint(p1);
shapeVertex(&pv);
pv.setPoint(p2);
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;
}