Visualization - Fix V3d shader grid issues (#1295)

- Updated shader grid rendering to intersect the grid plane in view space and draw it with a single full-screen triangle.
- Added shader grid bounds to camera Z-fit, so the grid is visible in empty views and after object display without an extra vfit.
- Fixed shader grid echo to use the active shader grid plane, effective scale, bounds and clip-safe display point.
- Kept the viewer-managed CPU grid path separate from the per-view shader grid path and preserved CPU grid type/mode handling in vgrid.
- Added grid tests for empty/no-vfit visibility and perspective rendering without mirrored fragments.
This commit is contained in:
Pasukhin Dmitry
2026-06-05 13:45:42 +01:00
committed by GitHub
parent 02530f0217
commit 239c41eeb0
15 changed files with 1701 additions and 632 deletions
@@ -5038,19 +5038,20 @@ static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgV
return 1;
}
Aspect_GridType aType = aViewer->GridType();
Aspect_GridDrawMode aMode = aViewer->GridDrawMode();
Aspect_GridType aType = Aspect_GT_Rectangular;
Aspect_GridDrawMode aMode = Aspect_GDM_Lines;
NCollection_Vec2<double> aNewOriginXY, aNewStepXY, aNewSizeXY;
double aNewRadius = 0.0, aNewRotAngle = 0.0, aNewZOffset = 0.0;
double aNewArcStart = 0.0, aNewArcEnd = 0.0;
Quantity_Color aNewColor, aNewTenthColor;
bool hasOrigin = false, hasStep = false, hasRotAngle = false, hasSize = false, hasRadius = false,
hasZOffset = false;
bool isGpuGrid = false, hasGridOff = false, hasScale = false, hasArc = false;
hasZOffset = false;
bool isShaderGrid = false, hasGridOff = false, hasScale = false, hasArc = false;
bool hasGridType = false, hasGridMode = false;
bool hasColor = false, hasTenthColor = false;
// Tracks whether any GPU-grid-only option was passed; used to warn when
// such options are silently ignored on the CPU path.
bool hasGpuOnlyOpt = false;
// Tracks whether any shader-grid-only option was passed; used to warn when
// such options are ignored by the non-shader grid path.
bool hasShaderOnlyOpt = false;
Aspect_GridParams aGridParams;
ViewerTest_AutoUpdater anUpdateTool(ViewerTest::GetAISContext(), aView);
for (int anArgIter = 1; anArgIter < theArgNb; ++anArgIter)
@@ -5067,15 +5068,17 @@ static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgV
anArgNext.LowerCase();
if (anArgNext == "r" || anArgNext == "rect" || anArgNext == "rectangular")
{
aType = Aspect_GT_Rectangular;
aType = Aspect_GT_Rectangular;
hasGridType = true;
}
else if (anArgNext == "c" || anArgNext == "circ" || anArgNext == "circular")
{
aType = Aspect_GT_Circular;
aType = Aspect_GT_Circular;
hasGridType = true;
}
else if (anArgNext == "gpu" || anArgNext == "shader")
{
isGpuGrid = true;
isShaderGrid = true;
}
else
{
@@ -5089,11 +5092,13 @@ static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgV
anArgNext.LowerCase();
if (anArgNext == "l" || anArgNext == "line" || anArgNext == "lines")
{
aMode = Aspect_GDM_Lines;
aMode = Aspect_GDM_Lines;
hasGridMode = true;
}
else if (anArgNext == "p" || anArgNext == "point" || anArgNext == "points")
{
aMode = Aspect_GDM_Points;
aMode = Aspect_GDM_Points;
hasGridMode = true;
}
else
{
@@ -5158,9 +5163,8 @@ static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgV
}
else if (anArgIter + 3 < theArgNb && (anArg == "-color"))
{
// Colors feed both backends through different sinks: aGridParams here
// for the GPU path, and Aspect_Grid::SetColors at the end of this
// function for the CPU path (so V3d_Viewer::syncViews picks them up).
// Colors feed both implementations through different sinks: aGridParams here
// for the shader path, and Aspect_Grid::SetColors at the end of this function.
hasColor = true;
aNewColor = Quantity_Color(Draw::Atof(theArgVec[anArgIter + 1]),
Draw::Atof(theArgVec[anArgIter + 2]),
@@ -5181,36 +5185,36 @@ static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgV
}
else if (anArgIter + 1 < theArgNb && anArg == "-scale")
{
hasScale = true;
hasGpuOnlyOpt = true;
hasScale = true;
hasShaderOnlyOpt = true;
aGridParams.SetScale(Draw::Atof(theArgVec[++anArgIter]));
}
else if (anArgIter + 1 < theArgNb && (anArg == "-linethickness" || anArg == "-thickness"))
{
hasGpuOnlyOpt = true;
hasShaderOnlyOpt = true;
aGridParams.SetLineThickness(Draw::Atof(theArgVec[++anArgIter]));
}
else if (anArgIter + 1 < theArgNb && anArg == "-background")
{
hasGpuOnlyOpt = true;
const int aVal = Draw::Atoi(theArgVec[++anArgIter]);
hasShaderOnlyOpt = true;
const int aVal = Draw::Atoi(theArgVec[++anArgIter]);
aGridParams.SetIsBackground(aVal != 0);
}
else if (anArgIter + 1 < theArgNb && anArg == "-drawaxis")
{
hasGpuOnlyOpt = true;
const int aVal = Draw::Atoi(theArgVec[++anArgIter]);
hasShaderOnlyOpt = true;
const int aVal = Draw::Atoi(theArgVec[++anArgIter]);
aGridParams.SetIsDrawAxis(aVal != 0);
}
else if (anArgIter + 1 < theArgNb && (anArg == "-viewadaptive" || anArg == "-adaptive"))
{
hasGpuOnlyOpt = true;
const int aVal = Draw::Atoi(theArgVec[++anArgIter]);
hasShaderOnlyOpt = true;
const int aVal = Draw::Atoi(theArgVec[++anArgIter]);
aGridParams.SetIsViewAdaptive(aVal != 0);
}
else if (anArg == "-gpu" || anArg == "-shader")
{
isGpuGrid = true;
isShaderGrid = true;
}
else if (anArgIter + 2 < theArgNb && anArg == "-arc")
{
@@ -5223,19 +5227,23 @@ static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgV
}
else if (anArg == "r" || anArg == "rect" || anArg == "rectangular")
{
aType = Aspect_GT_Rectangular;
aType = Aspect_GT_Rectangular;
hasGridType = true;
}
else if (anArg == "c" || anArg == "circ" || anArg == "circular")
{
aType = Aspect_GT_Circular;
aType = Aspect_GT_Circular;
hasGridType = true;
}
else if (anArg == "l" || anArg == "line" || anArg == "lines")
{
aMode = Aspect_GDM_Lines;
aMode = Aspect_GDM_Lines;
hasGridMode = true;
}
else if (anArg == "p" || anArg == "point" || anArg == "points")
{
aMode = Aspect_GDM_Points;
aMode = Aspect_GDM_Points;
hasGridMode = true;
}
else if (anArg == "off")
{
@@ -5248,77 +5256,100 @@ static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgV
}
}
if (isGpuGrid && hasGridOff)
if (isShaderGrid && hasGridOff)
{
Message::SendFail("Syntax error: 'off' cannot be combined with GPU grid display");
Message::SendFail("Syntax error: 'off' cannot be combined with shader grid display");
return 1;
}
// GPU-only options (-scale, -lineThickness, -background, -drawAxis,
// Shader-only options (-scale, -lineThickness, -background, -drawAxis,
// -viewAdaptive) are stored on Aspect_GridParams and consumed only by the
// shader path. Silently ignoring them on the CPU path leaves the user
// shader path. Silently ignoring them on the non-shader path leaves the user
// wondering why nothing changed; warn explicitly.
if (hasGpuOnlyOpt && !isGpuGrid && !hasGridOff)
if (hasShaderOnlyOpt && !isShaderGrid && !hasGridOff)
{
Message::SendWarning("vgrid: -scale / -lineThickness / -background / -drawAxis / "
"-viewAdaptive are GPU-grid only; pass -gpu (or -type gpu) to apply.");
"-viewAdaptive are shader-grid only; pass -gpu (or -type gpu) to apply.");
}
if (isGpuGrid || hasGridOff)
if (!isShaderGrid)
{
if (!hasGridType)
{
aType = aViewer->GridType();
}
if (!hasGridMode)
{
aMode = aViewer->GridDrawMode();
}
}
if (isShaderGrid || hasGridOff)
{
if (hasGridOff)
{
aView->GridErase();
}
if (isGpuGrid)
if (isShaderGrid)
{
// The shader grid keeps a real Aspect_Grid to back snap selection, so we
// route through ActivateGrid(rect|circ) first and override the display
// with shader-specific params afterwards. Decide the shape from aType
// or from the presence of circular-only options.
const bool isGpuCircular = aType == Aspect_GT_Circular || hasRadius || hasArc;
const double anOrigX = hasOrigin ? aNewOriginXY.x() : 0.0;
const double anOrigY = hasOrigin ? aNewOriginXY.y() : 0.0;
const double aRotAngle = hasRotAngle ? aNewRotAngle : 0.0;
const bool isShaderCircular = aType == Aspect_GT_Circular || hasRadius || hasArc;
NCollection_Vec2<double> anOrigXY(0.0, 0.0);
double aRotAngle = 0.0;
if (isGpuCircular)
if (isShaderCircular)
{
// Radial step + angular divisions. `-step R N` supplies both; otherwise
// use sensible defaults that keep snap consistent with the visible grid.
double aRadiusStep = 1.0;
int aDivisionCount = 16;
double aRadiusStep = 0.0;
int aDivisionCount = 0;
aViewer->CircularGridValues(anOrigXY.x(),
anOrigXY.y(),
aRadiusStep,
aDivisionCount,
aRotAngle);
if (hasOrigin)
{
anOrigXY = aNewOriginXY;
}
if (hasRotAngle)
{
aRotAngle = aNewRotAngle;
}
if (hasStep)
{
aRadiusStep = aNewStepXY.x();
aDivisionCount = int(aNewStepXY.y() > 0 ? aNewStepXY.y() : aDivisionCount);
aDivisionCount = int(aNewStepXY.y());
}
else if (hasScale && aGridParams.Scale() > 0.0)
{
aRadiusStep = 1.0 / aGridParams.Scale();
}
aViewer->SetCircularGridValues(anOrigX, anOrigY, aRadiusStep, aDivisionCount, aRotAngle);
// Arc range reaches the circular grid base before ActivateGrid's first
// syncViews fires (otherwise syncViews would overwrite our override).
if (hasArc)
if (aRadiusStep <= 0.0)
{
if (occ::handle<Aspect_CircularGrid> aCircGrid =
occ::down_cast<Aspect_CircularGrid>(aViewer->Grid(true)))
{
aCircGrid->SetArcRange(aNewArcStart, aNewArcEnd);
}
aRadiusStep = 1.0;
}
if (aDivisionCount <= 0)
{
aDivisionCount = 16;
}
aViewer->ActivateGrid(Aspect_GT_Circular, aMode);
aGridParams.SetScale(1.0 / aRadiusStep);
aGridParams.SetScaleY(0.0); // unused in circular mode
aGridParams.SetScaleY(0.0);
aGridParams.SetAngularDivisions(aDivisionCount);
}
else
{
// Rectangular shader grid. Derive step from explicit -step or from the
// Aspect_GridParams scale; fall back to 1 world unit so the default is
// immediately useful (Aspect_GridParams::Scale defaults to 0.01 which
// would give step 100 - too coarse for typical scenes).
NCollection_Vec2<double> aStepXY(1.0, 1.0);
aViewer->RectangularGridValues(anOrigXY.x(),
anOrigXY.y(),
aStepXY.x(),
aStepXY.y(),
aRotAngle);
if (hasOrigin)
{
anOrigXY = aNewOriginXY;
}
if (hasRotAngle)
{
aRotAngle = aNewRotAngle;
}
if (!hasScale)
{
if (hasStep)
@@ -5328,27 +5359,17 @@ static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgV
}
else
{
aGridParams.SetScale(1.0);
aGridParams.SetScaleY(1.0);
aGridParams.SetScale(aStepXY.x() > 0.0 ? 1.0 / aStepXY.x() : 1.0);
aGridParams.SetScaleY(aStepXY.y() > 0.0 ? 1.0 / aStepXY.y() : 1.0);
}
}
const double aInfStepX = 1.0 / aGridParams.Scale();
const double aInfStepY = 1.0 / aGridParams.EffectiveScaleY();
aViewer->SetRectangularGridValues(anOrigX, anOrigY, aInfStepX, aInfStepY, aRotAngle);
aViewer->ActivateGrid(Aspect_GT_Rectangular, aMode);
aGridParams.SetAngularDivisions(0);
}
// Convert origin to the same world-offset convention used by V3d
// syncViews so the shader's aPlaneOrigin matches snap's aPnt0.
const gp_Ax3 aPlane = aViewer->PrivilegedPlane();
const gp_XYZ aOriginOffset =
aPlane.XDirection().XYZ() * -anOrigX + aPlane.YDirection().XYZ() * -anOrigY;
aGridParams.SetOrigin(gp_Pnt(aOriginOffset));
aGridParams.SetOrigin(gp_Pnt(-anOrigXY.x(), -anOrigXY.y(), 0.0));
aGridParams.SetDrawMode(aMode);
aGridParams.SetRotationAngle(aRotAngle);
if (hasSize && !isGpuCircular)
if (hasSize && !isShaderCircular)
{
aGridParams.SetSizeX(aNewSizeXY.x());
aGridParams.SetSizeY(aNewSizeXY.y());
@@ -5363,9 +5384,9 @@ static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgV
}
aView->GridDisplay(aGridParams);
}
if (hasGridOff && !isGpuGrid)
if (hasGridOff && !isShaderGrid)
{
// plain 'vgrid off' still deactivates the classical grid
// Plain 'vgrid off' still deactivates the viewer-managed grid.
aViewer->DeactivateGrid();
}
return 0;
@@ -5476,7 +5497,7 @@ static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgV
}
}
// Apply -color / -tenthColor to the active grid so V3d syncViews picks
// them up on the next display. GPU-grid display drives these through
// them up on the next display. Shader-grid display drives these through
// aGridParams directly (set earlier in the parser loop).
if (hasColor || hasTenthColor)
{
@@ -6581,8 +6602,7 @@ static int VMoveTo(Draw_Interpretor& theDI, int theNbArgs, const char** theArgVe
return 1;
}
const bool toEchoGrid =
aContext->CurrentViewer()->IsGridActive() && aContext->CurrentViewer()->GridEcho();
const bool toEchoGrid = aView->IsGridActive() && aContext->CurrentViewer()->GridEcho();
if (toEchoGrid)
{
aContext->CurrentViewer()->HideGridEcho(aView);
@@ -14284,17 +14304,14 @@ vgrid [off] [-type {rect|circ|gpu|shader}] [-gpu] [-mode {line|point}] [-origin
[-step StepRadius NbDivisions] [-radius Radius] [-arc AngleStart AngleEnd]
[-color R G B] [-tenthColor R G B] [-scale N] [-lineThickness T]
[-background {0|1}] [-drawAxis {0|1}] [-viewAdaptive {0|1}]
Two render backends are available:
- '-type rect' / '-type circ' (default) draws the legacy CPU grid into a
bounded Graphic3d_Structure shared by every active view.
- '-type gpu' / '-gpu' activates the shader-rendered grid on the active view.
Two grid implementations are available:
- '-type rect' / '-type circ' draws the viewer grid shared by every active view.
- '-type gpu' / '-gpu' activates the shader grid on the active view.
Without '-size' or '-radius' it is unbounded; combine with '-size DX DY'
for a rectangle, '-radius R' for a disc, '-arc S E' for a wedge.
'-color', '-tenthColor' apply to both backends. '-scale', '-lineThickness',
'-background', '-drawAxis', '-viewAdaptive {0|1}' are GPU-grid only. Switching
to GPU-grid display hides the CPU grid in the viewer; switching back to a CPU
grid type erases the shader grid in the active view. 'off' deactivates whichever
grid is currently visible.
'-color', '-tenthColor' apply to both implementations. '-scale',
'-lineThickness', '-background', '-drawAxis', '-viewAdaptive {0|1}' are
shader-grid only. 'off' deactivates the active grid in the current view/viewer.
)" /* [vgrid] */);
addCmd("vpriviledgedplane", VPriviledgedPlane, /* [vpriviledgedplane] */ R"(
@@ -27,6 +27,8 @@ set(OCCT_OpenGl_FILES
OpenGl_FrameStatsPrs.hxx
OpenGl_Group.hxx
OpenGl_Group.cxx
OpenGl_ShaderGrid.hxx
OpenGl_ShaderGrid.cxx
OpenGl_Structure.hxx
OpenGl_Structure.cxx
OpenGl_StructureShadow.hxx
@@ -0,0 +1,929 @@
// Copyright (c) 2026 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.
#include <OpenGl_ShaderGrid.hxx>
#include <Precision.hxx>
#include <algorithm>
#include <cmath>
namespace
{
static constexpr double THE_TWO_PI = 2.0 * M_PI;
static bool isFiniteCoord(const double theValue)
{
return std::isfinite(theValue) && !Precision::IsInfinite(theValue);
}
static bool isFinitePoint(const gp_Pnt& thePoint)
{
return isFiniteCoord(thePoint.X()) && isFiniteCoord(thePoint.Y()) && isFiniteCoord(thePoint.Z());
}
static bool isFinitePoint(const gp_XYZ& thePoint)
{
return isFiniteCoord(thePoint.X()) && isFiniteCoord(thePoint.Y()) && isFiniteCoord(thePoint.Z());
}
static double normalizedAngle(const double theAngle)
{
double anAngle = std::fmod(theAngle, THE_TWO_PI);
if (anAngle < 0.0)
{
anAngle += THE_TWO_PI;
}
return anAngle;
}
static double positiveAngleSpan(const double theStart, const double theEnd)
{
double aSpan = std::fmod(theEnd - theStart, THE_TWO_PI);
if (aSpan < 0.0)
{
aSpan += THE_TWO_PI;
}
if (std::abs(aSpan) <= Precision::Angular()
&& std::abs(theEnd - theStart) >= THE_TWO_PI - Precision::Angular())
{
return THE_TWO_PI;
}
return aSpan;
}
static bool isSamePoint(const gp_Pnt& theFirst, const gp_Pnt& theSecond)
{
return theFirst.SquareDistance(theSecond) <= Precision::SquareConfusion();
}
static bool isSameDirection(const gp_Dir& theFirst, const gp_Dir& theSecond)
{
return theFirst.Angle(theSecond) <= Precision::Angular();
}
static bool isSameScalar(const double theFirst, const double theSecond)
{
return std::abs(theFirst - theSecond) <= Precision::Confusion();
}
static bool isSameAngle(const double theFirst, const double theSecond)
{
double aDelta = std::abs(normalizedAngle(theFirst) - normalizedAngle(theSecond));
aDelta = std::min(aDelta, THE_TWO_PI - aDelta);
return aDelta <= Precision::Angular();
}
} // namespace
//=================================================================================================
bool OpenGl_ShaderGrid::Display(const Aspect_GridParams& theParams,
const gp_Ax3& thePlane,
const occ::handle<Graphic3d_Camera>& theCamera,
const occ::handle<OpenGl_Context>& theContext)
{
if (theParams.DrawMode() == Aspect_GDM_None)
{
Erase();
return true;
}
const bool wasShown = myIsShown;
const bool wasBackground = wasShown && myParams.IsBackground();
const bool hasSameAnchor = wasShown && hasSameAnchorFrame(theParams, thePlane);
const bool toCaptureFrame = theParams.IsBackground() && !theContext.IsNull()
&& (!wasShown || !wasBackground || !hasSameAnchor);
myParams = theParams;
myPlane = thePlane;
myIsShown = true;
if (theParams.IsViewAdaptive())
{
const double aReferenceScale =
!theCamera.IsNull() && theCamera->Scale() > Precision::Confusion() ? theCamera->Scale() : 1.0;
myParams.SetScale(theParams.Scale() * aReferenceScale);
if (theParams.ScaleY() > 0.0)
{
myParams.SetScaleY(theParams.ScaleY() * aReferenceScale);
}
}
if (toCaptureFrame)
{
myRefViewMatrix = theContext->WorldViewState.Current();
}
return true;
}
//=================================================================================================
void OpenGl_ShaderGrid::Erase()
{
myIsShown = false;
}
//=================================================================================================
void OpenGl_ShaderGrid::frame(gp_Pnt& theOrigin, gp_XYZ& theX, gp_XYZ& theY, gp_XYZ& theN) const
{
const double aCosA = std::cos(myParams.RotationAngle());
const double aSinA = std::sin(myParams.RotationAngle());
const gp_XYZ aRawX = myPlane.XDirection().XYZ();
const gp_XYZ aRawY = myPlane.YDirection().XYZ();
theX = aRawX * aCosA - aRawY * aSinA;
theY = aRawX * aSinA + aRawY * aCosA;
theN = myPlane.Direction().XYZ();
const gp_Pnt& anOriginLocal = myParams.Origin();
theOrigin.SetXYZ(myPlane.Location().XYZ() + aRawX * anOriginLocal.X() + aRawY * anOriginLocal.Y()
+ theN * (anOriginLocal.Z() + myParams.ZOffset()));
}
//=================================================================================================
void OpenGl_ShaderGrid::effectiveScale(const occ::handle<Graphic3d_Camera>& theCamera,
double& theScaleX,
double& theScaleY) const
{
theScaleX = myParams.Scale();
theScaleY = myParams.EffectiveScaleY();
if (!myParams.IsViewAdaptive())
{
return;
}
const double aCurrentScale = !theCamera.IsNull() && theCamera->Scale() > Precision::Confusion()
? theCamera->Scale()
: Precision::Confusion();
theScaleX /= aCurrentScale;
theScaleY /= aCurrentScale;
}
//=================================================================================================
bool OpenGl_ShaderGrid::angleInArc(const double theStart,
const double theEnd,
const double theAngle)
{
const double aSpan = positiveAngleSpan(theStart, theEnd);
if (aSpan >= THE_TWO_PI - Precision::Angular())
{
return true;
}
double aDelta = normalizedAngle(theAngle) - normalizedAngle(theStart);
if (aDelta < 0.0)
{
aDelta += THE_TWO_PI;
}
return aDelta <= aSpan + Precision::Angular();
}
//=================================================================================================
bool OpenGl_ShaderGrid::isPointInBounds(const double theLocalX, const double theLocalY) const
{
if (myParams.IsCircular())
{
const double aRadius = std::sqrt(theLocalX * theLocalX + theLocalY * theLocalY);
if (myParams.Radius() > 0.0 && aRadius > myParams.Radius())
{
return false;
}
if (myParams.IsArc())
{
if (!angleInArc(myParams.AngleStart(), myParams.AngleEnd(), std::atan2(theLocalY, theLocalX)))
{
return false;
}
}
return true;
}
return (myParams.SizeX() <= 0.0 || std::abs(theLocalX) <= myParams.SizeX() * 0.5)
&& (myParams.SizeY() <= 0.0 || std::abs(theLocalY) <= myParams.SizeY() * 0.5);
}
//=================================================================================================
void OpenGl_ShaderGrid::addLocalPoint(Bnd_Box& theBox,
const gp_Pnt& theOrigin,
const gp_XYZ& theX,
const gp_XYZ& theY,
const double theLocalX,
const double theLocalY)
{
const gp_Pnt aPoint(theOrigin.XYZ() + theX * theLocalX + theY * theLocalY);
if (isFinitePoint(aPoint))
{
theBox.Add(aPoint);
}
}
//=================================================================================================
void OpenGl_ShaderGrid::addFiniteBounds(Bnd_Box& theBox) const
{
gp_Pnt anOrigin;
gp_XYZ aGridX, aGridY, aGridN;
frame(anOrigin, aGridX, aGridY, aGridN);
addLocalPoint(theBox, anOrigin, aGridX, aGridY, 0.0, 0.0);
if (myParams.IsCircular())
{
const double aRadius = myParams.Radius();
if (aRadius <= 0.0)
{
return;
}
if (!myParams.IsArc())
{
addLocalPoint(theBox, anOrigin, aGridX, aGridY, aRadius, 0.0);
addLocalPoint(theBox, anOrigin, aGridX, aGridY, -aRadius, 0.0);
addLocalPoint(theBox, anOrigin, aGridX, aGridY, 0.0, aRadius);
addLocalPoint(theBox, anOrigin, aGridX, aGridY, 0.0, -aRadius);
return;
}
auto addArcPoint = [&](const double theAngle) {
addLocalPoint(theBox,
anOrigin,
aGridX,
aGridY,
aRadius * std::cos(theAngle),
aRadius * std::sin(theAngle));
};
addArcPoint(myParams.AngleStart());
addArcPoint(myParams.AngleEnd());
const double aCardinalAngles[] = {0.0, M_PI * 0.5, M_PI, -M_PI * 0.5};
for (const double anAngle : aCardinalAngles)
{
if (angleInArc(myParams.AngleStart(), myParams.AngleEnd(), anAngle))
{
addArcPoint(anAngle);
}
}
return;
}
const double aHalfX = myParams.SizeX() > 0.0 ? myParams.SizeX() * 0.5 : 0.0;
const double aHalfY = myParams.SizeY() > 0.0 ? myParams.SizeY() * 0.5 : 0.0;
if (aHalfX <= 0.0 && aHalfY <= 0.0)
{
return;
}
if (aHalfX > 0.0 && aHalfY > 0.0)
{
addLocalPoint(theBox, anOrigin, aGridX, aGridY, -aHalfX, -aHalfY);
addLocalPoint(theBox, anOrigin, aGridX, aGridY, aHalfX, -aHalfY);
addLocalPoint(theBox, anOrigin, aGridX, aGridY, aHalfX, aHalfY);
addLocalPoint(theBox, anOrigin, aGridX, aGridY, -aHalfX, aHalfY);
return;
}
if (aHalfX > 0.0)
{
addLocalPoint(theBox, anOrigin, aGridX, aGridY, -aHalfX, 0.0);
addLocalPoint(theBox, anOrigin, aGridX, aGridY, aHalfX, 0.0);
}
if (aHalfY > 0.0)
{
addLocalPoint(theBox, anOrigin, aGridX, aGridY, 0.0, -aHalfY);
addLocalPoint(theBox, anOrigin, aGridX, aGridY, 0.0, aHalfY);
}
}
//=================================================================================================
bool OpenGl_ShaderGrid::planeLocalHit(const occ::handle<Graphic3d_Camera>& theCamera,
const double theNdcX,
const double theNdcY,
double& theLocalX,
double& theLocalY,
gp_XYZ* theHit,
const bool theToRejectBehind) const
{
if (theCamera.IsNull())
{
return false;
}
gp_Pnt aPlaneOrigin;
gp_XYZ aPlaneX, aPlaneY, aPlaneN;
frame(aPlaneOrigin, aPlaneX, aPlaneY, aPlaneN);
const double aNearZ = theCamera->IsZeroToOneDepth() ? 0.0 : -1.0;
const gp_Pnt aNearP = theCamera->UnProject(gp_Pnt(theNdcX, theNdcY, aNearZ));
const gp_Pnt aFarP = theCamera->UnProject(gp_Pnt(theNdcX, theNdcY, 1.0));
const bool isPerspective = !theCamera->IsOrthographic();
const gp_Pnt aRayOriginP = isPerspective ? theCamera->Eye() : aNearP;
const gp_XYZ aRay = aFarP.XYZ() - aRayOriginP.XYZ();
const double aRayMod = aRay.Modulus();
if (aRayMod <= Precision::Confusion())
{
return false;
}
const double aDenom = aPlaneN.Dot(aRay);
if (std::abs(aDenom / aRayMod) <= Precision::Angular())
{
return false;
}
const double aT = aPlaneN.Dot(aPlaneOrigin.XYZ() - aRayOriginP.XYZ()) / aDenom;
if (theToRejectBehind && isPerspective && aT < 0.0)
{
return false;
}
const gp_XYZ aHit = aRayOriginP.XYZ() + aRay * aT;
const gp_XYZ aLocal3 = aHit - aPlaneOrigin.XYZ();
theLocalX = aLocal3.Dot(aPlaneX);
theLocalY = aLocal3.Dot(aPlaneY);
if (theHit != nullptr)
{
*theHit = aHit;
}
return true;
}
//=================================================================================================
void OpenGl_ShaderGrid::addViewFootprintBounds(Bnd_Box& theBox,
const occ::handle<Graphic3d_Camera>& theCamera) const
{
const double aSamples[][2] = {{-1.0, -1.0}, {1.0, -1.0}, {1.0, 1.0}, {-1.0, 1.0}, {0.0, 0.0}};
for (const double* aSample : aSamples)
{
double aLocalX = 0.0;
double aLocalY = 0.0;
gp_XYZ aHit;
if (!planeLocalHit(theCamera, aSample[0], aSample[1], aLocalX, aLocalY, &aHit))
{
continue;
}
if (!isPointInBounds(aLocalX, aLocalY))
{
continue;
}
if (isFinitePoint(aHit))
{
theBox.Add(gp_Pnt(aHit));
}
}
}
//=================================================================================================
void OpenGl_ShaderGrid::AddZFitBounds(Bnd_Box& theGraphicBox,
const occ::handle<Graphic3d_Camera>& theCamera) const
{
if (!myIsShown || myParams.DrawMode() == Aspect_GDM_None || myParams.IsBackground())
{
return;
}
Bnd_Box aGridBox;
if (myParams.IsBounded())
{
addFiniteBounds(aGridBox);
}
else
{
addViewFootprintBounds(aGridBox, theCamera);
}
if (aGridBox.IsVoid())
{
addFiniteBounds(aGridBox);
}
if (!aGridBox.IsVoid())
{
theGraphicBox.Add(aGridBox);
}
}
//=================================================================================================
bool OpenGl_ShaderGrid::hasSameAnchorFrame(const Aspect_GridParams& theParams,
const gp_Ax3& thePlane) const
{
return isSamePoint(myPlane.Location(), thePlane.Location())
&& isSameDirection(myPlane.Direction(), thePlane.Direction())
&& isSameDirection(myPlane.XDirection(), thePlane.XDirection())
&& isSameDirection(myPlane.YDirection(), thePlane.YDirection())
&& isSamePoint(myParams.Origin(), theParams.Origin())
&& isSameAngle(myParams.RotationAngle(), theParams.RotationAngle())
&& isSameScalar(myParams.ZOffset(), theParams.ZOffset());
}
//=================================================================================================
bool OpenGl_ShaderGrid::referenceLocal(const occ::handle<Graphic3d_Camera>& theCamera,
double& theLocalX,
double& theLocalY) const
{
const double aSamples[][2] = {{0.0, 0.0},
{-1.0, -1.0},
{1.0, -1.0},
{1.0, 1.0},
{-1.0, 1.0},
{0.0, -1.0},
{1.0, 0.0},
{0.0, 1.0},
{-1.0, 0.0}};
for (const double* aSample : aSamples)
{
if (planeLocalHit(theCamera, aSample[0], aSample[1], theLocalX, theLocalY))
{
return true;
}
}
return false;
}
//=================================================================================================
double OpenGl_ShaderGrid::snappedLocalShift(const double theLocal, const double theScale)
{
if (theScale <= Precision::Confusion())
{
return 0.0;
}
const double aStep = 1.0 / theScale;
return std::round(theLocal / aStep) * aStep;
}
//=================================================================================================
NCollection_Vec3<float> OpenGl_ShaderGrid::viewPoint(const NCollection_Mat4<float>& theWorldView,
const gp_Pnt& thePoint)
{
const NCollection_Vec4<float> aPoint(float(thePoint.X()),
float(thePoint.Y()),
float(thePoint.Z()),
1.0f);
const NCollection_Vec4<float> aView = theWorldView * aPoint;
return NCollection_Vec3<float>(aView.x(), aView.y(), aView.z());
}
//=================================================================================================
NCollection_Vec3<float> OpenGl_ShaderGrid::viewDirection(
const NCollection_Mat4<float>& theWorldView,
const gp_XYZ& theDirection)
{
const NCollection_Vec4<float> aDir(float(theDirection.X()),
float(theDirection.Y()),
float(theDirection.Z()),
0.0f);
const NCollection_Vec4<float> aView = theWorldView * aDir;
return NCollection_Vec3<float>(aView.x(), aView.y(), aView.z());
}
//=================================================================================================
bool OpenGl_ShaderGrid::acceptEchoCandidate(const occ::handle<Graphic3d_Camera>& theCamera,
const int theWidth,
const int theHeight,
const int theX,
const int theY,
const gp_Pnt& theGridOrigin,
const gp_XYZ& theGridX,
const gp_XYZ& theGridY,
const double theLocalX,
const double theLocalY,
gp_XYZ& theBestSnapped,
double& theBestDist2,
bool& theHasBestPoint) const
{
if (!isPointInBounds(theLocalX, theLocalY))
{
return false;
}
const gp_XYZ aCandidate = theGridOrigin.XYZ() + theGridX * theLocalX + theGridY * theLocalY;
if (!isFinitePoint(aCandidate))
{
return false;
}
if (!theCamera->IsOrthographic()
&& theCamera->Direction().XYZ().Dot(aCandidate - theCamera->Eye().XYZ())
<= Precision::Confusion())
{
return false;
}
const gp_Pnt aProj = theCamera->Project(gp_Pnt(aCandidate));
if (!isFinitePoint(aProj))
{
return false;
}
const double aPx = (aProj.X() + 1.0) * 0.5 * double(theWidth);
const double aPy = double(theHeight - 1) - (aProj.Y() + 1.0) * 0.5 * double(theHeight);
const double aDistX = aPx - double(theX);
const double aDistY = aPy - double(theY);
if (!isFiniteCoord(aDistX) || !isFiniteCoord(aDistY))
{
return false;
}
const double aDist2 = aDistX * aDistX + aDistY * aDistY;
if (!isFiniteCoord(aDist2))
{
return false;
}
if (!theHasBestPoint || aDist2 < theBestDist2)
{
theHasBestPoint = true;
theBestDist2 = aDist2;
theBestSnapped = aCandidate;
}
return true;
}
//=================================================================================================
bool OpenGl_ShaderGrid::echoDisplayPoint(const occ::handle<Graphic3d_Camera>& theCamera,
const gp_XYZ& theSnapped,
gp_Pnt& theDisplayPoint)
{
const gp_Pnt aProjSnapped = theCamera->Project(gp_Pnt(theSnapped));
if (!isFinitePoint(aProjSnapped))
{
return false;
}
const double aDisplayZ = theCamera->IsZeroToOneDepth() ? 0.5 : 0.0;
theDisplayPoint = theCamera->UnProject(gp_Pnt(aProjSnapped.X(), aProjSnapped.Y(), aDisplayZ));
return isFinitePoint(theDisplayPoint);
}
//=================================================================================================
bool OpenGl_ShaderGrid::Echo(const occ::handle<Graphic3d_Camera>& theCamera,
const int theWidth,
const int theHeight,
const int theX,
const int theY,
Graphic3d_Vertex& thePoint,
Graphic3d_Vertex& theDisplayPoint) const
{
if (!myIsShown || myParams.DrawMode() == Aspect_GDM_None || myParams.IsBackground()
|| theCamera.IsNull() || theWidth <= 0 || theHeight <= 0)
{
return false;
}
const double aNdcX = 2.0 * double(theX) / double(theWidth) - 1.0;
const double aNdcY = 2.0 * double(theHeight - 1 - theY) / double(theHeight) - 1.0;
double aLocalX = 0.0;
double aLocalY = 0.0;
if (!planeLocalHit(theCamera, aNdcX, aNdcY, aLocalX, aLocalY))
{
return false;
}
if (!isPointInBounds(aLocalX, aLocalY))
{
return false;
}
double aScaleX = 0.0;
double aScaleY = 0.0;
effectiveScale(theCamera, aScaleX, aScaleY);
if (aScaleX <= Precision::Confusion() || aScaleY <= Precision::Confusion())
{
return false;
}
gp_Pnt aGridOrigin;
gp_XYZ aGridX, aGridY, aGridN;
frame(aGridOrigin, aGridX, aGridY, aGridN);
gp_XYZ aSnapped = aGridOrigin.XYZ();
const bool toSnapByScreen = myParams.DrawMode() == Aspect_GDM_Points;
double aBestDist2 = RealLast();
bool hasBestPoint = false;
auto addCandidate = [&](const double theLocalX, const double theLocalY) {
acceptEchoCandidate(theCamera,
theWidth,
theHeight,
theX,
theY,
aGridOrigin,
aGridX,
aGridY,
theLocalX,
theLocalY,
aSnapped,
aBestDist2,
hasBestPoint);
};
if (myParams.IsCircular())
{
const double aRadius = std::sqrt(aLocalX * aLocalX + aLocalY * aLocalY);
const double anAngle = std::atan2(aLocalY, aLocalX);
const double aRadiusStep = 1.0 / aScaleX;
const int aNbDivisions = myParams.AngularDivisions();
const double anAngleStep = aNbDivisions > 0 ? M_PI / double(aNbDivisions) : M_PI;
if (toSnapByScreen)
{
const double aRadiusIndex = std::floor(aRadius / aRadiusStep);
const double anAngleIndex = std::floor(anAngle / anAngleStep);
const double aRadii[2] = {std::max(0.0, aRadiusIndex * aRadiusStep),
std::max(0.0, (aRadiusIndex + 1.0) * aRadiusStep)};
const double anAngles[2] = {anAngleIndex * anAngleStep, (anAngleIndex + 1.0) * anAngleStep};
for (double aCandidateRadius : aRadii)
{
for (double aCandidateAngle : anAngles)
{
addCandidate(aCandidateRadius * std::cos(aCandidateAngle),
aCandidateRadius * std::sin(aCandidateAngle));
}
}
if (!hasBestPoint)
{
return false;
}
}
else
{
const double aSnapRadius = std::round(aRadius / aRadiusStep) * aRadiusStep;
const double aSnapAngle = std::round(anAngle / anAngleStep) * anAngleStep;
if (!acceptEchoCandidate(theCamera,
theWidth,
theHeight,
theX,
theY,
aGridOrigin,
aGridX,
aGridY,
aSnapRadius * std::cos(aSnapAngle),
aSnapRadius * std::sin(aSnapAngle),
aSnapped,
aBestDist2,
hasBestPoint))
{
return false;
}
}
}
else
{
const double aStepX = 1.0 / aScaleX;
const double aStepY = 1.0 / aScaleY;
if (toSnapByScreen)
{
const double anIndexX = std::floor(aLocalX / aStepX);
const double anIndexY = std::floor(aLocalY / aStepY);
const double aLocalXs[2] = {anIndexX * aStepX, (anIndexX + 1.0) * aStepX};
const double aLocalYs[2] = {anIndexY * aStepY, (anIndexY + 1.0) * aStepY};
for (double aCandidateX : aLocalXs)
{
for (double aCandidateY : aLocalYs)
{
addCandidate(aCandidateX, aCandidateY);
}
}
if (!hasBestPoint)
{
return false;
}
}
else
{
aLocalX = std::round(aLocalX / aStepX) * aStepX;
aLocalY = std::round(aLocalY / aStepY) * aStepY;
if (!acceptEchoCandidate(theCamera,
theWidth,
theHeight,
theX,
theY,
aGridOrigin,
aGridX,
aGridY,
aLocalX,
aLocalY,
aSnapped,
aBestDist2,
hasBestPoint))
{
return false;
}
}
}
if (!hasBestPoint)
{
return false;
}
thePoint.SetCoord(aSnapped.X(), aSnapped.Y(), aSnapped.Z());
gp_Pnt aDisplayP;
if (!echoDisplayPoint(theCamera, aSnapped, aDisplayP))
{
return false;
}
theDisplayPoint.SetCoord(aDisplayP.X(), aDisplayP.Y(), aDisplayP.Z());
return true;
}
//=================================================================================================
bool OpenGl_ShaderGrid::SnapPoint(const occ::handle<Graphic3d_Camera>& theCamera,
const Graphic3d_Vertex& thePoint,
Graphic3d_Vertex& theGridPoint) const
{
if (!myIsShown || myParams.DrawMode() == Aspect_GDM_None || myParams.IsBackground())
{
return false;
}
double aScaleX = 0.0;
double aScaleY = 0.0;
effectiveScale(theCamera, aScaleX, aScaleY);
if (aScaleX <= Precision::Confusion() || aScaleY <= Precision::Confusion())
{
return false;
}
gp_Pnt aGridOrigin;
gp_XYZ aGridX, aGridY, aGridN;
frame(aGridOrigin, aGridX, aGridY, aGridN);
const gp_XYZ aPoint(thePoint.X(), thePoint.Y(), thePoint.Z());
const gp_XYZ aLocal3 = aPoint - aGridOrigin.XYZ();
double aLocalX = aLocal3.Dot(aGridX);
double aLocalY = aLocal3.Dot(aGridY);
gp_XYZ aSnapped = aGridOrigin.XYZ();
if (myParams.IsCircular())
{
const double aRadius = std::sqrt(aLocalX * aLocalX + aLocalY * aLocalY);
const double anAngle = std::atan2(aLocalY, aLocalX);
const double aRadiusStep = 1.0 / aScaleX;
const int aNbDivisions = myParams.AngularDivisions();
const double anAngleStep = aNbDivisions > 0 ? M_PI / double(aNbDivisions) : M_PI;
const double aSnapRadius = std::round(aRadius / aRadiusStep) * aRadiusStep;
const double aSnapAngle = std::round(anAngle / anAngleStep) * anAngleStep;
aLocalX = aSnapRadius * std::cos(aSnapAngle);
aLocalY = aSnapRadius * std::sin(aSnapAngle);
if (!isPointInBounds(aLocalX, aLocalY))
{
return false;
}
aSnapped += aGridX * aLocalX + aGridY * aLocalY;
}
else
{
const double aStepX = 1.0 / aScaleX;
const double aStepY = 1.0 / aScaleY;
aLocalX = std::round(aLocalX / aStepX) * aStepX;
aLocalY = std::round(aLocalY / aStepY) * aStepY;
if (!isPointInBounds(aLocalX, aLocalY))
{
return false;
}
aSnapped += aGridX * aLocalX + aGridY * aLocalY;
}
theGridPoint.SetCoord(aSnapped.X(), aSnapped.Y(), aSnapped.Z());
return true;
}
//=================================================================================================
NCollection_Mat4<float> OpenGl_ShaderGrid::DrawWorldView(
const NCollection_Mat4<float>& theCurrentWorldView) const
{
if (!myParams.IsBackground())
{
return theCurrentWorldView;
}
NCollection_Mat4<float> aRefInv;
if (!myRefViewMatrix.Inverted(aRefInv))
{
aRefInv.InitIdentity();
}
return theCurrentWorldView * aRefInv;
}
//=================================================================================================
void OpenGl_ShaderGrid::SetUniforms(const occ::handle<OpenGl_Context>& theContext,
const occ::handle<OpenGl_ShaderProgram>& theProgram,
const occ::handle<Graphic3d_Camera>& theCamera,
const NCollection_Mat4<float>& theWorldView) const
{
double aScaleX = 0.0;
double aScaleY = 0.0;
effectiveScale(theCamera, aScaleX, aScaleY);
gp_Pnt aPlaneOrigin;
gp_XYZ aXRotated, aYRotated, aNDir;
frame(aPlaneOrigin, aXRotated, aYRotated, aNDir);
theProgram->SetUniform(theContext, "uScaleX", GLfloat(aScaleX));
theProgram->SetUniform(theContext, "uScaleY", GLfloat(aScaleY));
theProgram->SetUniform(theContext, "uThickness", GLfloat(myParams.LineThickness()));
theProgram->SetUniform(theContext,
"uColor",
NCollection_Vec3<float>((float)myParams.Color().Red(),
(float)myParams.Color().Green(),
(float)myParams.Color().Blue()));
theProgram->SetUniform(theContext,
"uAccentColor",
NCollection_Vec3<float>((float)myParams.AccentColor().Red(),
(float)myParams.AccentColor().Green(),
(float)myParams.AccentColor().Blue()));
theProgram->SetUniform(theContext, "uAccentScaleX", GLfloat(myParams.AccentScaleX()));
theProgram->SetUniform(theContext, "uAccentScaleY", GLfloat(myParams.AccentScaleY()));
theProgram->SetUniform(theContext, "uAccentAngularScale", GLfloat(myParams.AccentAngularScale()));
theProgram->SetUniform(theContext, "uIsDrawAxis", myParams.IsDrawAxis() ? 1 : 0);
theProgram->SetUniform(theContext, "uGridType", myParams.IsCircular() ? 1 : 0);
theProgram->SetUniform(theContext, "uIsBackground", myParams.IsBackground() ? 1 : 0);
const double aAngularScale =
myParams.IsCircular() ? double(myParams.AngularDivisions()) / M_PI : 0.0;
theProgram->SetUniform(theContext, "uAngularScale", GLfloat(aAngularScale));
theProgram->SetUniform(theContext, "uDrawMode", myParams.DrawMode() == Aspect_GDM_Points ? 1 : 0);
theProgram->SetUniform(theContext, "uIsPerspective", theCamera->IsOrthographic() ? 0 : 1);
theProgram->SetUniform(theContext, "uIsZeroToOneDepth", theCamera->IsZeroToOneDepth() ? 1 : 0);
theProgram->SetUniform(theContext, "uParallelTolerance", GLfloat(Precision::Angular()));
NCollection_Vec2<float> aLocalOriginShift(0.0f, 0.0f);
NCollection_Vec2<float> anAccentLocalOriginShift(0.0f, 0.0f);
float aRadialOriginShift = 0.0f;
float anAccentRadialOriginShift = 0.0f;
double aLocalRefX = 0.0;
double aLocalRefY = 0.0;
if (referenceLocal(theCamera, aLocalRefX, aLocalRefY))
{
if (myParams.IsCircular())
{
const double aRadiusRef = std::sqrt(aLocalRefX * aLocalRefX + aLocalRefY * aLocalRefY);
aRadialOriginShift = float(snappedLocalShift(aRadiusRef, aScaleX));
anAccentRadialOriginShift = myParams.AccentScaleX() > Precision::Confusion()
? float(snappedLocalShift(aRadiusRef, myParams.AccentScaleX()))
: aRadialOriginShift;
}
else
{
aLocalOriginShift.SetValues(float(snappedLocalShift(aLocalRefX, aScaleX)),
float(snappedLocalShift(aLocalRefY, aScaleY)));
anAccentLocalOriginShift.SetValues(
myParams.AccentScaleX() > Precision::Confusion()
? float(snappedLocalShift(aLocalRefX, myParams.AccentScaleX()))
: aLocalOriginShift.x(),
myParams.AccentScaleY() > Precision::Confusion()
? float(snappedLocalShift(aLocalRefY, myParams.AccentScaleY()))
: aLocalOriginShift.y());
}
}
const gp_Pnt aPlaneRef(aPlaneOrigin.XYZ() + aXRotated * double(aLocalOriginShift.x())
+ aYRotated * double(aLocalOriginShift.y()));
theProgram->SetUniform(theContext, "uPlaneOriginView", viewPoint(theWorldView, aPlaneOrigin));
theProgram->SetUniform(theContext, "uPlaneRefView", viewPoint(theWorldView, aPlaneRef));
theProgram->SetUniform(theContext, "uPlaneXView", viewDirection(theWorldView, aXRotated));
theProgram->SetUniform(theContext, "uPlaneYView", viewDirection(theWorldView, aYRotated));
theProgram->SetUniform(theContext, "uPlaneNView", viewDirection(theWorldView, aNDir));
theProgram->SetUniform(theContext, "uLocalOriginShift", aLocalOriginShift);
theProgram->SetUniform(theContext, "uAccentLocalOriginShift", anAccentLocalOriginShift);
theProgram->SetUniform(theContext, "uRadialOriginShift", GLfloat(aRadialOriginShift));
theProgram->SetUniform(theContext,
"uAccentRadialOriginShift",
GLfloat(anAccentRadialOriginShift));
const float aHalfX = myParams.SizeX() > 0.0 ? float(myParams.SizeX() * 0.5) : 0.0f;
const float aHalfY = myParams.SizeY() > 0.0 ? float(myParams.SizeY() * 0.5) : 0.0f;
const float aRadius = myParams.Radius() > 0.0 ? float(myParams.Radius()) : 0.0f;
theProgram->SetUniform(theContext, "uBounds", NCollection_Vec3<float>(aHalfX, aHalfY, aRadius));
theProgram->SetUniform(theContext,
"uIsBoundFade",
myParams.IsBounded() && !myParams.IsViewAdaptive() ? 1 : 0);
theProgram->SetUniform(
theContext,
"uArcRange",
NCollection_Vec2<float>(float(myParams.AngleStart()), float(myParams.AngleEnd())));
theProgram->SetUniform(theContext, "uArcBounded", myParams.IsArc() ? 1 : 0);
}
@@ -0,0 +1,160 @@
// Copyright (c) 2026 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.
#ifndef _OpenGl_ShaderGrid_HeaderFile
#define _OpenGl_ShaderGrid_HeaderFile
#include <Aspect_GridParams.hxx>
#include <Bnd_Box.hxx>
#include <Graphic3d_Camera.hxx>
#include <Graphic3d_Vertex.hxx>
#include <NCollection_Mat4.hxx>
#include <NCollection_Vec2.hxx>
#include <OpenGl_Context.hxx>
#include <OpenGl_ShaderProgram.hxx>
#include <gp_Ax3.hxx>
//! State and geometry model of the OpenGl shader-rendered grid.
class OpenGl_ShaderGrid
{
public:
//! Return TRUE if the grid is currently shown.
bool IsShown() const { return myIsShown; }
//! Return TRUE if the grid is rendered as a background.
bool IsBackground() const { return myParams.IsBackground(); }
//! Return current parameters.
const Aspect_GridParams& Params() const { return myParams; }
//! Store grid state. Returns FALSE when parameters cannot produce a shader grid.
bool Display(const Aspect_GridParams& theParams,
const gp_Ax3& thePlane,
const occ::handle<Graphic3d_Camera>& theCamera,
const occ::handle<OpenGl_Context>& theContext);
//! Clear grid state.
void Erase();
//! Add helper bounds required by camera Z fitting.
void AddZFitBounds(Bnd_Box& theGraphicBox, const occ::handle<Graphic3d_Camera>& theCamera) const;
//! Return snapped point for the grid under the window pixel.
bool Echo(const occ::handle<Graphic3d_Camera>& theCamera,
const int theWidth,
const int theHeight,
const int theX,
const int theY,
Graphic3d_Vertex& thePoint,
Graphic3d_Vertex& theDisplayPoint) const;
//! Return snapped point for an arbitrary world point.
bool SnapPoint(const occ::handle<Graphic3d_Camera>& theCamera,
const Graphic3d_Vertex& thePoint,
Graphic3d_Vertex& theGridPoint) const;
//! Upload shader uniforms for current grid state.
void SetUniforms(const occ::handle<OpenGl_Context>& theContext,
const occ::handle<OpenGl_ShaderProgram>& theProgram,
const occ::handle<Graphic3d_Camera>& theCamera,
const NCollection_Mat4<float>& theWorldView) const;
//! Return worldview matrix to use for drawing.
NCollection_Mat4<float> DrawWorldView(const NCollection_Mat4<float>& theCurrentWorldView) const;
private:
//! Compute grid plane frame.
void frame(gp_Pnt& theOrigin, gp_XYZ& theX, gp_XYZ& theY, gp_XYZ& theN) const;
//! Compute effective scales for the current camera.
void effectiveScale(const occ::handle<Graphic3d_Camera>& theCamera,
double& theScaleX,
double& theScaleY) const;
//! Return TRUE if local coordinates are inside configured grid domain.
bool isPointInBounds(const double theLocalX, const double theLocalY) const;
//! Add local point to box.
static void addLocalPoint(Bnd_Box& theBox,
const gp_Pnt& theOrigin,
const gp_XYZ& theX,
const gp_XYZ& theY,
const double theLocalX,
const double theLocalY);
//! Add configured finite bounds to the box.
void addFiniteBounds(Bnd_Box& theBox) const;
//! Add view footprint bounds to the box.
void addViewFootprintBounds(Bnd_Box& theBox,
const occ::handle<Graphic3d_Camera>& theCamera) const;
//! Intersect camera ray at NDC point with the grid plane.
bool planeLocalHit(const occ::handle<Graphic3d_Camera>& theCamera,
const double theNdcX,
const double theNdcY,
double& theLocalX,
double& theLocalY,
gp_XYZ* theHit = nullptr,
const bool theToRejectBehind = true) const;
//! Return stable visible reference point in local coordinates.
bool referenceLocal(const occ::handle<Graphic3d_Camera>& theCamera,
double& theLocalX,
double& theLocalY) const;
//! Return local shift snapped to a grid phase.
static double snappedLocalShift(const double theLocal, const double theScale);
//! Return TRUE if previous and new grid definitions share the same background anchor frame.
bool hasSameAnchorFrame(const Aspect_GridParams& theParams, const gp_Ax3& thePlane) const;
//! Accept echo candidate if it is visible and closer to the requested pixel.
bool acceptEchoCandidate(const occ::handle<Graphic3d_Camera>& theCamera,
const int theWidth,
const int theHeight,
const int theX,
const int theY,
const gp_Pnt& theGridOrigin,
const gp_XYZ& theGridX,
const gp_XYZ& theGridY,
const double theLocalX,
const double theLocalY,
gp_XYZ& theBestSnapped,
double& theBestDist2,
bool& theHasBestPoint) const;
//! Convert point to current draw view coordinates.
static NCollection_Vec3<float> viewPoint(const NCollection_Mat4<float>& theWorldView,
const gp_Pnt& thePoint);
//! Convert direction to current draw view coordinates.
static NCollection_Vec3<float> viewDirection(const NCollection_Mat4<float>& theWorldView,
const gp_XYZ& theDirection);
//! Return display-safe echo marker point.
static bool echoDisplayPoint(const occ::handle<Graphic3d_Camera>& theCamera,
const gp_XYZ& theSnapped,
gp_Pnt& theDisplayPoint);
//! Return TRUE if angle belongs to arc.
static bool angleInArc(const double theStart, const double theEnd, const double theAngle);
private:
Aspect_GridParams myParams;
gp_Ax3 myPlane;
NCollection_Mat4<float> myRefViewMatrix;
bool myIsShown = false;
};
#endif // _OpenGl_ShaderGrid_HeaderFile
+67 -345
View File
@@ -43,6 +43,7 @@
#include <OpenGl_Window.hxx>
#include <OpenGl_Workspace.hxx>
#include <OSD_Parallel.hxx>
#include <Precision.hxx>
#include <Standard_CLocaleSentry.hxx>
#include "../Textures/Textures_EnvLUT.pxx"
@@ -77,66 +78,6 @@ static bool checkWasFailedFbo(const occ::handle<OpenGl_FrameBuffer>& theFboToChe
theFboRef->NbSamples());
}
//! Unproject window-space (theWinX, theWinY) to a near/far ray, intersect the
//! grid plane, express the hit in plane-local (X, Y). Returns FALSE if the
//! unprojection fails or the ray is near-parallel to the plane.
static bool unprojectGridPointToPlaneLocal(const occ::handle<OpenGl_Context>& theCtx,
const int* theViewport,
const float theWinX,
const float theWinY,
const NCollection_Vec3<float>& thePlaneN,
const NCollection_Vec3<float>& thePlaneOriginV,
const NCollection_Vec3<float>& thePlaneX,
const NCollection_Vec3<float>& thePlaneY,
double& theOutLocalX,
double& theOutLocalY)
{
if (theViewport == nullptr)
{
return false;
}
float aNearX = 0.0f, aNearY = 0.0f, aNearZ = 0.0f;
float aFarX = 0.0f, aFarY = 0.0f, aFarZ = 0.0f;
if (!Graphic3d_TransformUtils::UnProject<float>(theWinX,
theWinY,
0.0f,
theCtx->WorldViewState.Current(),
theCtx->ProjectionState.Current(),
theViewport,
aNearX,
aNearY,
aNearZ))
{
return false;
}
if (!Graphic3d_TransformUtils::UnProject<float>(theWinX,
theWinY,
1.0f,
theCtx->WorldViewState.Current(),
theCtx->ProjectionState.Current(),
theViewport,
aFarX,
aFarY,
aFarZ))
{
return false;
}
const NCollection_Vec3<float> aNearP(aNearX, aNearY, aNearZ);
const NCollection_Vec3<float> aFarP(aFarX, aFarY, aFarZ);
const NCollection_Vec3<float> aDir = aFarP - aNearP;
const float aDenom = thePlaneN.Dot(aDir);
if (std::abs(aDenom) <= 1.0e-6f)
{
return false;
}
const float aT = thePlaneN.Dot(thePlaneOriginV - aNearP) / aDenom;
const NCollection_Vec3<float> aHit = aNearP + aDir * aT;
const NCollection_Vec3<float> aLocal3 = aHit - thePlaneOriginV;
theOutLocalX = double(aLocal3.Dot(thePlaneX));
theOutLocalY = double(aLocal3.Dot(thePlaneY));
return true;
}
//! Chooses compatible internal color format for OIT frame buffer.
static bool chooseOitColorConfiguration(const occ::handle<OpenGl_Context>& theGlContext,
const int theConfigIndex,
@@ -201,7 +142,6 @@ OpenGl_View::OpenGl_View(const occ::handle<Graphic3d_StructureManager>& theMgr,
myCubeMapParams(new OpenGl_Aspects()),
myColoredQuadParams(new OpenGl_Aspects()),
myGridVao(0),
myToShowGrid(false),
myPBREnvState(OpenGl_PBREnvState_NONEXISTENT),
myPBREnvRequest(false),
// ray-tracing fields initialization
@@ -965,6 +905,15 @@ Bnd_Box OpenGl_View::MinMaxValues(const bool theToIncludeAuxiliary) const
//=================================================================================================
void OpenGl_View::ZFitAllBounds(Bnd_Box& thePrimaryBox, Bnd_Box& theGraphicBox) const
{
thePrimaryBox = base_type::MinMaxValues(false);
theGraphicBox = MinMaxValues(true);
myShaderGrid.AddZFitBounds(theGraphicBox, Camera());
}
//=================================================================================================
occ::handle<Standard_Transient> OpenGl_View::FBO() const
{
return occ::handle<Standard_Transient>(myFBO);
@@ -3635,40 +3584,65 @@ void OpenGl_View::updatePBREnvironment(const occ::handle<OpenGl_Context>& theCtx
void OpenGl_View::GridDisplay(const Aspect_GridParams& theParams, const gp_Ax3& thePlane)
{
// Reference view matrix (for background-mode anchoring) is captured only on
// transitions into background mode or into the showing state. Otherwise live
// param edits (SetArcRange, SetSize, etc.) while the camera is mid-orbit would
// re-snapshot the current view and visually snap the anchored grid.
const bool wasShowing = myToShowGrid;
const bool wasBackground = wasShowing && myGridParams.IsBackground();
const bool toCapture = theParams.IsBackground() && (!wasShowing || !wasBackground);
myGridParams = theParams;
myGridPlane = thePlane;
myToShowGrid = true;
if (toCapture)
const occ::handle<OpenGl_Context>& aCtx = myWorkspace->GetGlContext();
if (!aCtx.IsNull() && aCtx->core30 == nullptr)
{
const occ::handle<OpenGl_Context>& aCtx = myWorkspace->GetGlContext();
if (!aCtx.IsNull())
{
myGridRefViewMatrix = aCtx->WorldViewState.Current();
}
myShaderGrid.Erase();
Invalidate();
return;
}
myShaderGrid.Display(theParams, thePlane, Camera(), aCtx);
Invalidate();
}
//=================================================================================================
void OpenGl_View::GridErase()
{
myToShowGrid = false;
myShaderGrid.Erase();
Invalidate();
}
//=================================================================================================
bool OpenGl_View::ShaderGridEcho(const int theX, const int theY, Graphic3d_Vertex& thePoint) const
{
Graphic3d_Vertex aDisplayPoint;
return ShaderGridEcho(theX, theY, thePoint, aDisplayPoint);
}
//=================================================================================================
bool OpenGl_View::ShaderGridEcho(const int theX,
const int theY,
Graphic3d_Vertex& thePoint,
Graphic3d_Vertex& theDisplayPoint) const
{
if (Window().IsNull())
{
return false;
}
int aWidth = 0;
int aHeight = 0;
Window()->Size(aWidth, aHeight);
return myShaderGrid.Echo(Camera(), aWidth, aHeight, theX, theY, thePoint, theDisplayPoint);
}
//=================================================================================================
bool OpenGl_View::ShaderGridSnapPoint(const Graphic3d_Vertex& thePoint,
Graphic3d_Vertex& theGridPoint) const
{
return myShaderGrid.SnapPoint(Camera(), thePoint, theGridPoint);
}
//=================================================================================================
void OpenGl_View::renderGrid()
{
if (!myToShowGrid || myGridParams.DrawMode() == Aspect_GDM_None)
if (!myShaderGrid.IsShown() || myShaderGrid.Params().DrawMode() == Aspect_GDM_None)
{
return;
}
@@ -3680,7 +3654,7 @@ void OpenGl_View::renderGrid()
}
if (aContext->core30 == nullptr)
{
// The shader grid requires GL 3.0+ / GLES 3.0+ (VAO + gl_VertexID + gl_FragDepth).
// The shader grid requires GL 3.0+ / GLES 3.0+ (VAO + gl_VertexID).
// Warn once per process so the caller knows why the grid isn't drawn; snap
// math still works. The process scope avoids per-view state bloat - a stray
// missed warning on a second view is less costly than a data member.
@@ -3710,7 +3684,7 @@ void OpenGl_View::renderGrid()
aContext->core30->glGenVertexArrays(1, &myGridVao);
if (myGridVao == 0)
{
myToShowGrid = false;
myShaderGrid.Erase();
return;
}
}
@@ -3731,58 +3705,20 @@ void OpenGl_View::renderGrid()
aContext->core11fwd->glGetIntegerv(GL_BLEND_DST_RGB, &aPrevBlendDstRgb);
aContext->core11fwd->glGetIntegerv(GL_BLEND_SRC_ALPHA, &aPrevBlendSrcA);
aContext->core11fwd->glGetIntegerv(GL_BLEND_DST_ALPHA, &aPrevBlendDstA);
const bool hasDepthClamp = aContext->arbDepthClamp;
const bool wasDepthClamp =
hasDepthClamp && aContext->core11fwd->glIsEnabled(GL_DEPTH_CLAMP) == GL_TRUE;
const occ::handle<OpenGl_ShaderProgram> aPrevProgram = aContext->ActiveProgram();
// Capture the user-set camera ZRange BEFORE any grid-specific adjustment.
// ZFitAll below mutates the camera; the restore block at the end of this
// method targets these original values, otherwise vconvert and other APIs
// observing ZRange between frames see values inflated by the grid bounds.
const double aZNearKeep = aCamera->ZNear();
const double aZFarKeep = aCamera->ZFar();
const Graphic3d_Camera::Projection aProjKeep = aCamera->ProjectionType();
Bnd_Box aBnd = MinMaxValues(true);
const gp_Pnt aPlaneLoc = myGridPlane.Location();
if (myGridParams.IsBackground() || aBnd.IsVoid() || aBnd.IsOut(aPlaneLoc))
{
aBnd.Add(aPlaneLoc);
// ZFitAll asserts ZFar > ZNear on return (Graphic3d_Camera.cxx:1636),
// so no post-hoc SetZRange nudge is needed.
aCamera->ZFitAll(1.0, aBnd, aBnd);
}
if (myGridParams.IsBackground())
{
aCamera->SetProjectionType(Graphic3d_Camera::Projection_Orthographic);
}
aContext->ProjectionState.Push();
aContext->ProjectionState.SetCurrent(aCamera->ProjectionMatrixF());
aContext->ApplyProjectionMatrix();
const NCollection_Mat4<float> aWorldViewCurrent = aContext->WorldViewState.Current();
aContext->WorldViewState.Push();
if (myGridParams.IsBackground())
{
// In background mode the grid lives in an unchanging reference frame.
// Derive the camera-motion delta from the captured reference view matrix so the
// grid stays fixed in world coords during pan/rotate, without exposing
// PanningVector / RotationPoint on Graphic3d_Camera.
NCollection_Mat4<float> aRefInv;
if (!myGridRefViewMatrix.Inverted(aRefInv))
{
aRefInv.InitIdentity();
}
NCollection_Mat4<float> aDelta = aWorldViewCurrent * aRefInv;
aContext->WorldViewState.SetCurrent(aDelta);
}
aContext->WorldViewState.SetCurrent(myShaderGrid.DrawWorldView(aWorldViewCurrent));
aContext->ApplyWorldViewMatrix();
aContext->core11fwd->glEnable(GL_DEPTH_TEST);
aContext->core11fwd->glDepthFunc(GL_LESS);
aContext->core11fwd->glDepthMask(GL_TRUE);
aContext->core11fwd->glDepthFunc(GL_LEQUAL);
aContext->core11fwd->glDepthMask(GL_FALSE);
aContext->core11fwd->glEnable(GL_BLEND);
aContext->core11fwd->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// The clip-space full-screen quad winds CW; if back-face culling is on
@@ -3790,238 +3726,28 @@ void OpenGl_View::renderGrid()
// is culled and the grid silently vanishes. Disable culling for the draw
// call and restore at the end.
aContext->core11fwd->glDisable(GL_CULL_FACE);
if (hasDepthClamp && !wasDepthClamp)
{
aContext->core11fwd->glEnable(GL_DEPTH_CLAMP);
}
aContext->core30->glBindVertexArray(myGridVao);
double aScaleX = myGridParams.Scale();
double aScaleY = myGridParams.EffectiveScaleY();
if (myGridParams.IsViewAdaptive())
{
const double aTargetCellsY =
std::max(1.0, std::min(200.0, aScaleY > 0.0 ? 1.0 / aScaleY : 10.0));
const double aViewHeight = std::max(aCamera->Scale(), 1.0e-9);
const double aCellSize = aViewHeight / aTargetCellsY;
aScaleX = 1.0 / aCellSize;
aScaleY = aScaleX;
}
if (aContext->ShaderManager()->BindGridProgram())
{
const occ::handle<OpenGl_ShaderProgram>& aProg = aContext->ActiveProgram();
aProg->SetUniform(aContext, "uScaleX", GLfloat(aScaleX));
aProg->SetUniform(aContext, "uScaleY", GLfloat(aScaleY));
aProg->SetUniform(aContext, "uThickness", GLfloat(myGridParams.LineThickness()));
aProg->SetUniform(aContext,
"uColor",
NCollection_Vec3<float>((float)myGridParams.Color().Red(),
(float)myGridParams.Color().Green(),
(float)myGridParams.Color().Blue()));
aProg->SetUniform(aContext,
"uAccentColor",
NCollection_Vec3<float>((float)myGridParams.AccentColor().Red(),
(float)myGridParams.AccentColor().Green(),
(float)myGridParams.AccentColor().Blue()));
aProg->SetUniform(aContext, "uAccentScaleX", GLfloat(myGridParams.AccentScaleX()));
aProg->SetUniform(aContext, "uAccentScaleY", GLfloat(myGridParams.AccentScaleY()));
aProg->SetUniform(aContext, "uAccentAngularScale", GLfloat(myGridParams.AccentAngularScale()));
aProg->SetUniform(aContext, "uIsDrawAxis", myGridParams.IsDrawAxis() ? 1 : 0);
aProg->SetUniform(aContext, "uIsBackground", myGridParams.IsBackground() ? 1 : 0);
aProg->SetUniform(aContext, "uGridType", myGridParams.IsCircular() ? 1 : 0);
// Angular spokes per radian: N spokes in 180 deg = N / pi spokes per radian.
const double aAngularScale =
myGridParams.IsCircular() ? double(myGridParams.AngularDivisions()) / M_PI : 0.0;
aProg->SetUniform(aContext, "uAngularScale", GLfloat(aAngularScale));
aProg->SetUniform(aContext, "uDrawMode", myGridParams.DrawMode() == Aspect_GDM_Points ? 1 : 0);
// Plane basis used for both the view-adaptive bounds search and the final
// shader uniforms.
// - In-plane rotation rotates X/Y around the plane normal. Sign matches
// V3d_View::SetGrid's Trsf2 so snap (V3d_View::Compute) and the drawn
// grid use the same basis: gridX = cos*planeX - sin*planeY,
// gridY = sin*planeX + cos*planeY.
// - ZOffset pushes the displayed plane along its normal to avoid
// z-fighting with coplanar geometry. Snap math uses the unshifted
// plane, so selection still lands on the true plane.
const double aCosA = std::cos(myGridParams.RotationAngle());
const double aSinA = std::sin(myGridParams.RotationAngle());
const gp_Dir aRawX = myGridPlane.XDirection();
const gp_Dir aRawY = myGridPlane.YDirection();
const gp_Dir aNDir = myGridPlane.Direction();
const gp_XYZ aXRotated = aRawX.XYZ() * aCosA - aRawY.XYZ() * aSinA;
const gp_XYZ aYRotated = aRawX.XYZ() * aSinA + aRawY.XYZ() * aCosA;
const double aZOffset = myGridParams.ZOffset();
const gp_Pnt aOriginLocal = myGridParams.Origin();
const gp_Pnt aPlaneOrigin(aPlaneLoc.X() + aOriginLocal.X() + aNDir.X() * aZOffset,
aPlaneLoc.Y() + aOriginLocal.Y() + aNDir.Y() * aZOffset,
aPlaneLoc.Z() + aOriginLocal.Z() + aNDir.Z() * aZOffset);
const NCollection_Vec3<float> aPlaneNV((float)aNDir.X(), (float)aNDir.Y(), (float)aNDir.Z());
const NCollection_Vec3<float> aPlaneOriginV((float)aPlaneOrigin.X(),
(float)aPlaneOrigin.Y(),
(float)aPlaneOrigin.Z());
const NCollection_Vec3<float> aPlaneXV((float)aXRotated.X(),
(float)aXRotated.Y(),
(float)aXRotated.Z());
const NCollection_Vec3<float> aPlaneYV((float)aYRotated.X(),
(float)aYRotated.Y(),
(float)aYRotated.Z());
const int* aViewport = aContext->Viewport();
// Bounded work area (HalfSizeX, HalfSizeY, Radius). 0 = unbounded along that axis.
float aHalfX = myGridParams.SizeX() > 0.0 ? float(myGridParams.SizeX() * 0.5) : 0.0f;
float aHalfY = myGridParams.SizeY() > 0.0 ? float(myGridParams.SizeY() * 0.5) : 0.0f;
float aRadius = myGridParams.Radius() > 0.0 ? float(myGridParams.Radius()) : 0.0f;
if (myGridParams.IsViewAdaptive())
{
// Derive a tight world-space bound from the visible region: unproject
// the four viewport corners + center to the grid plane and enclose
// their plane-local extents. The center sample is a safety net when
// a corner ray is near-parallel to the plane and gets rejected.
double aMinLocalX = 0.0;
double aMaxLocalX = 0.0;
double aMinLocalY = 0.0;
double aMaxLocalY = 0.0;
double aMaxLocalRadius = 0.0;
bool aHasBounds = false;
if (aViewport != nullptr)
{
const float aWinMinX = float(aViewport[0]);
const float aWinMinY = float(aViewport[1]);
const float aWinMaxX = float(aViewport[0] + aViewport[2]);
const float aWinMaxY = float(aViewport[1] + aViewport[3]);
const float aWinMidX = (aWinMinX + aWinMaxX) * 0.5f;
const float aWinMidY = (aWinMinY + aWinMaxY) * 0.5f;
const NCollection_Vec2<float> aSamples[] = {NCollection_Vec2<float>(aWinMinX, aWinMinY),
NCollection_Vec2<float>(aWinMaxX, aWinMinY),
NCollection_Vec2<float>(aWinMinX, aWinMaxY),
NCollection_Vec2<float>(aWinMaxX, aWinMaxY),
NCollection_Vec2<float>(aWinMidX, aWinMidY)};
for (const NCollection_Vec2<float>& aSample : aSamples)
{
double aLocalX = 0.0, aLocalY = 0.0;
if (!unprojectGridPointToPlaneLocal(aContext,
aViewport,
aSample.x(),
aSample.y(),
aPlaneNV,
aPlaneOriginV,
aPlaneXV,
aPlaneYV,
aLocalX,
aLocalY))
{
continue;
}
const double aHitR = std::sqrt(aLocalX * aLocalX + aLocalY * aLocalY);
if (!aHasBounds)
{
aMinLocalX = aLocalX;
aMaxLocalX = aLocalX;
aMinLocalY = aLocalY;
aMaxLocalY = aLocalY;
aMaxLocalRadius = aHitR;
aHasBounds = true;
}
else
{
aMinLocalX = std::min(aMinLocalX, aLocalX);
aMaxLocalX = std::max(aMaxLocalX, aLocalX);
aMinLocalY = std::min(aMinLocalY, aLocalY);
aMaxLocalY = std::max(aMaxLocalY, aLocalY);
aMaxLocalRadius = std::max(aMaxLocalRadius, aHitR);
}
}
}
const double aStepX = aScaleX > 0.0 ? 1.0 / aScaleX : 1.0;
const double aStepY = aScaleY > 0.0 ? 1.0 / aScaleY : aStepX;
if (myGridParams.IsCircular())
{
const double aPad = std::max(aStepX, aStepY) * 4.0;
if (aHasBounds)
{
aRadius = std::max(aRadius, float(aMaxLocalRadius + aPad));
}
else
{
aRadius = std::max(aRadius, float(std::max(aCamera->Scale(), aPad) * 2.0));
}
}
else if (aHasBounds)
{
const double aPadX = std::max((aMaxLocalX - aMinLocalX) * 0.10, aStepX * 4.0);
const double aPadY = std::max((aMaxLocalY - aMinLocalY) * 0.10, aStepY * 4.0);
aHalfX =
std::max(aHalfX, float(std::max(std::abs(aMinLocalX), std::abs(aMaxLocalX)) + aPadX));
aHalfY =
std::max(aHalfY, float(std::max(std::abs(aMinLocalY), std::abs(aMaxLocalY)) + aPadY));
}
else
{
aHalfX = std::max(aHalfX, float(std::max(aCamera->Scale(), aStepX * 8.0)));
aHalfY = std::max(aHalfY, float(std::max(aCamera->Scale(), aStepY * 8.0)));
}
}
aProg->SetUniform(aContext, "uBounds", NCollection_Vec3<float>(aHalfX, aHalfY, aRadius));
aProg->SetUniform(
aContext,
"uArcRange",
NCollection_Vec2<float>(float(myGridParams.AngleStart()), float(myGridParams.AngleEnd())));
aProg->SetUniform(aContext, "uArcBounded", myGridParams.IsArc() ? 1 : 0);
aProg->SetUniform(aContext, "uPlaneOrigin", aPlaneOriginV);
aProg->SetUniform(aContext, "uPlaneX", aPlaneXV);
aProg->SetUniform(aContext, "uPlaneY", aPlaneYV);
// Stable per-frame rectangular-grid reference point in plane-local
// coordinates. Keeps shader fract() arguments bounded at shallow angles
// without re-running extra unproject/intersection work for every fragment.
int aHasStableRef = 0;
NCollection_Vec2<float> aStableRefLocal(0.0f, 0.0f);
if (!myGridParams.IsCircular() && aViewport != nullptr)
{
const float aWinX = float(aViewport[0]) + float(aViewport[2]) * 0.5f;
const float aWinY = float(aViewport[1]) + float(aViewport[3]) * 0.5f;
double aLocalX = 0.0, aLocalY = 0.0;
if (unprojectGridPointToPlaneLocal(aContext,
aViewport,
aWinX,
aWinY,
aPlaneNV,
aPlaneOriginV,
aPlaneXV,
aPlaneYV,
aLocalX,
aLocalY))
{
aStableRefLocal.SetValues(float(aLocalX), float(aLocalY));
aHasStableRef = 1;
}
}
aProg->SetUniform(aContext, "uStableRefLocal", aStableRefLocal);
aProg->SetUniform(aContext, "uHasStableRef", aHasStableRef);
aProg->SetUniform(aContext, "uPlaneN", aPlaneNV);
aContext->core11fwd->glDrawArrays(GL_TRIANGLES, 0, 6);
myShaderGrid.SetUniforms(aContext, aProg, aCamera, aContext->WorldViewState.Current());
aContext->core11fwd->glDrawArrays(GL_TRIANGLES, 0, 3);
}
aContext->BindProgram(aPrevProgram);
aContext->core30->glBindVertexArray((GLuint)aPrevVao);
aCamera->SetZRange(aZNearKeep, aZFarKeep);
aCamera->SetProjectionType(aProjKeep);
aContext->WorldViewState.Pop();
aContext->ProjectionState.Pop();
aContext->ApplyWorldViewMatrix();
aContext->ApplyProjectionMatrix();
if (wasDepthTest == GL_FALSE)
if (wasDepthTest == GL_TRUE)
{
aContext->core11fwd->glEnable(GL_DEPTH_TEST);
}
else
{
aContext->core11fwd->glDisable(GL_DEPTH_TEST);
}
@@ -4039,8 +3765,4 @@ void OpenGl_View::renderGrid()
{
aContext->core11fwd->glEnable(GL_CULL_FACE);
}
if (hasDepthClamp && !wasDepthClamp)
{
aContext->core11fwd->glDisable(GL_DEPTH_CLAMP);
}
}
@@ -25,6 +25,7 @@
#include <OpenGl_GraduatedTrihedron.hxx>
#include <OpenGl_LayerList.hxx>
#include <OpenGl_SceneGeometry.hxx>
#include <OpenGl_ShaderGrid.hxx>
#include <OpenGl_Structure.hxx>
#include <OpenGl_TileSampler.hxx>
#include <TCollection_AsciiString.hxx>
@@ -162,6 +163,9 @@ public:
//! @return computed bounding box
Standard_EXPORT Bnd_Box MinMaxValues(const bool theToIncludeAuxiliary) const override;
//! Return primary and graphical bounding boxes used by camera Z fitting.
Standard_EXPORT void ZFitAllBounds(Bnd_Box& thePrimaryBox, Bnd_Box& theGraphicBox) const override;
//! Returns pointer to an assigned framebuffer object.
Standard_EXPORT occ::handle<Standard_Transient> FBO() const override;
@@ -229,6 +233,21 @@ public:
//! Erase the shader-rendered grid.
Standard_EXPORT void GridErase() override;
//! Return snapped point for the shader-rendered grid under the window pixel.
Standard_EXPORT bool ShaderGridEcho(const int theX,
const int theY,
Graphic3d_Vertex& thePoint) const override;
//! Return snapped point and clip-safe display point for the shader-rendered grid echo marker.
Standard_EXPORT bool ShaderGridEcho(const int theX,
const int theY,
Graphic3d_Vertex& thePoint,
Graphic3d_Vertex& theDisplayPoint) const override;
//! Return snapped point for the shader-rendered grid from an arbitrary world point.
Standard_EXPORT bool ShaderGridSnapPoint(const Graphic3d_Vertex& thePoint,
Graphic3d_Vertex& theGridPoint) const override;
//! Returns number of mipmap levels used in specular IBL map.
//! 0 if PBR environment is not created.
Standard_EXPORT unsigned int SpecIBLMapLevels() const;
@@ -539,11 +558,8 @@ protected: //! @name Background parameters
OpenGl_Aspects* myTextureParams; //!< Stores texture and its parameters for textured background
OpenGl_Aspects* myCubeMapParams; //!< Stores cubemap and its parameters for cubemap background
OpenGl_Aspects* myColoredQuadParams; //!< Stores parameters for gradient (corner mode) background
Aspect_GridParams myGridParams; //!< parameters of shader grid
gp_Ax3 myGridPlane; //!< grid plane in world coordinates
NCollection_Mat4<float> myGridRefViewMatrix; //!< worldview captured at GridDisplay() for pan/rotate compensation
OpenGl_ShaderGrid myShaderGrid; //!< shader grid state and geometry model
unsigned int myGridVao; //!< dedicated VAO for textureless grid draw
bool myToShowGrid; //!< flag indicating the grid is active
OpenGl_BackgroundArray* myBackgrounds[Graphic3d_TypeOfBackground_NB]; //!< Array of primitive arrays of different background types
// clang-format on
occ::handle<OpenGl_TextureSet> myTextureEnv;
@@ -22,10 +22,8 @@
#include <Quantity_Color.hxx>
#include <gp_Pnt.hxx>
//! Shader grid appearance (color, scale, bounds, arc, draw mode, background / adaptive flags).
//! Consumed only by the GPU path: V3d_View::GridDisplay -> OpenGl_View::renderGrid.
//! No effect on the CPU path (V3d_Viewer::ActivateGrid). Snap math is independent and
//! lives on Aspect_RectangularGrid / Aspect_CircularGrid.
//! Grid appearance for V3d_View::GridDisplay: color, scale, bounds, arc, draw mode,
//! background and adaptive flags.
class Aspect_GridParams
{
public:
@@ -183,8 +181,7 @@ public:
//! Return signed plane-normal offset applied at render time.
double ZOffset() const { return myZOffset; }
//! Set signed plane-normal offset applied at render time (display only;
//! snap math stays on the unshifted plane).
//! Set signed plane-normal offset applied at render and echo time.
void SetZOffset(const double theOffset) { myZOffset = theOffset; }
//! Return arc start angle (radians). Meaningful only when IsArc() is true.
@@ -229,10 +226,8 @@ public:
//! Return TRUE if grid spacing and visible extents adapt to the camera view.
bool IsViewAdaptive() const { return myIsViewAdaptive; }
//! Set view-adaptive grid on/off. When enabled, renderer derives temporary
//! cell spacing and bounds from the current camera. The inverse of ScaleY()
//! (or Scale() when ScaleY() is zero) is used as the target number of cells
//! across the view height.
//! Set view-adaptive grid on/off. When enabled, shader renderer keeps the
//! screen-space grid step stable by scaling the cell spacing with camera zoom.
void SetIsViewAdaptive(const bool theIsViewAdaptive) { myIsViewAdaptive = theIsViewAdaptive; }
private:
@@ -24,6 +24,7 @@
#include <Graphic3d_DataStructureManager.hxx>
#include <Graphic3d_DiagnosticInfo.hxx>
#include <Graphic3d_GraduatedTrihedron.hxx>
#include <Graphic3d_Vertex.hxx>
#include <Standard_Transient.hxx>
#include <NCollection_Map.hxx>
#include <NCollection_Shared.hxx>
@@ -174,6 +175,13 @@ public:
//! @return computed bounding box
Standard_EXPORT virtual Bnd_Box MinMaxValues(const bool theToIncludeAuxiliary = false) const;
//! Return primary and graphical bounding boxes used by camera Z fitting.
virtual void ZFitAllBounds(Bnd_Box& thePrimaryBox, Bnd_Box& theGraphicBox) const
{
thePrimaryBox = MinMaxValues(false);
theGraphicBox = MinMaxValues(true);
}
//! Returns the coordinates of the boundary box of all structures in the set <theSet>.
//! If <theToIgnoreInfiniteFlag> is TRUE, then the boundary box
//! also includes minimum and maximum limits of graphical elements
@@ -454,6 +462,43 @@ public:
//! The default implementation is a no-op; drivers with shader support override it.
virtual void GridErase() {}
//! Return snapped point for the shader-rendered grid under the window pixel.
//! The default implementation is a no-op; drivers with shader grid support override it.
virtual bool ShaderGridEcho(const int theX, const int theY, Graphic3d_Vertex& thePoint) const
{
(void)theX;
(void)theY;
(void)thePoint;
return false;
}
//! Return snapped point and display point for the shader-rendered grid under the window pixel.
//! The snapped point is the geometric grid point in world coordinates.
//! The display point is a clip-safe proxy projected to the same window position for echo marker
//! presentation; it should not be used as the geometric snap result.
virtual bool ShaderGridEcho(const int theX,
const int theY,
Graphic3d_Vertex& thePoint,
Graphic3d_Vertex& theDisplayPoint) const
{
if (!ShaderGridEcho(theX, theY, thePoint))
{
return false;
}
theDisplayPoint = thePoint;
return true;
}
//! Return snapped point for the shader-rendered grid from an arbitrary world point.
//! The default implementation is a no-op; drivers with shader grid support override it.
virtual bool ShaderGridSnapPoint(const Graphic3d_Vertex& thePoint,
Graphic3d_Vertex& theGridPoint) const
{
(void)thePoint;
(void)theGridPoint;
return false;
}
//! Returns environment texture set for the view.
const occ::handle<Graphic3d_TextureEnv>& TextureEnv() const { return myTextureEnvData; }
@@ -2156,12 +2156,8 @@ occ::handle<Graphic3d_ShaderProgram> Graphic3d_ShaderManager::getGridProgram() c
occ::handle<Graphic3d_ShaderProgram> aProgSrc = new Graphic3d_ShaderProgram();
Graphic3d_ShaderObject::ShaderVariableList aUniforms, aStageInOuts;
// Pass only NDC.xy to the fragment. Near/Far world points are computed
// per-pixel because the perspective-divide that unproject() performs is
// nonlinear in NDC, and linearly interpolating the resulting world-space
// points across the full-screen quad produces wrong rays along the
// diagonal seam between the two triangles (visible as empty triangular
// dead zones at oblique camera angles).
// Pass only NDC.xy to the fragment; each fragment reconstructs its view-space
// ray and intersects it with the grid plane.
aStageInOuts.Append(
Graphic3d_ShaderObject::ShaderVariable("vec2 vNdc",
Graphic3d_TOS_VERTEX | Graphic3d_TOS_FRAGMENT));
@@ -2182,21 +2178,38 @@ occ::handle<Graphic3d_ShaderProgram> Graphic3d_ShaderManager::getGridProgram() c
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("int uIsDrawAxis", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("int uIsBackground", Graphic3d_TOS_FRAGMENT));
Graphic3d_ShaderObject::ShaderVariable("vec3 uPlaneOriginView", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("vec3 uPlaneOrigin", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(Graphic3d_ShaderObject::ShaderVariable("vec3 uPlaneX", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(Graphic3d_ShaderObject::ShaderVariable("vec3 uPlaneY", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(Graphic3d_ShaderObject::ShaderVariable("vec3 uPlaneN", Graphic3d_TOS_FRAGMENT));
Graphic3d_ShaderObject::ShaderVariable("vec3 uPlaneRefView", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("vec3 uPlaneXView", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("vec3 uPlaneYView", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("vec3 uPlaneNView", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(Graphic3d_ShaderObject::ShaderVariable("int uGridType", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("float uAngularScale", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(Graphic3d_ShaderObject::ShaderVariable("int uDrawMode", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("vec2 uStableRefLocal", Graphic3d_TOS_FRAGMENT));
Graphic3d_ShaderObject::ShaderVariable("int uIsPerspective", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("int uHasStableRef", Graphic3d_TOS_FRAGMENT));
Graphic3d_ShaderObject::ShaderVariable("int uIsZeroToOneDepth", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("int uIsBackground", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("float uParallelTolerance", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("vec2 uLocalOriginShift", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("vec2 uAccentLocalOriginShift", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("float uRadialOriginShift", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(Graphic3d_ShaderObject::ShaderVariable("float uAccentRadialOriginShift",
Graphic3d_TOS_FRAGMENT));
aUniforms.Append(Graphic3d_ShaderObject::ShaderVariable("vec3 uBounds", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("int uIsBoundFade", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("vec2 uArcRange", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
@@ -2204,171 +2217,165 @@ occ::handle<Graphic3d_ShaderProgram> Graphic3d_ShaderManager::getGridProgram() c
TCollection_AsciiString aSrcVert =
TCollection_AsciiString()
+ EOL "const vec3 gridPlane[6] = vec3[] (" EOL
" vec3( 1.0, 1.0, 0.0), vec3(-1.0, -1.0, 0.0), vec3(-1.0, 1.0, 0.0)," EOL
" vec3(-1.0, -1.0, 0.0), vec3( 1.0, 1.0, 0.0), vec3( 1.0, -1.0, 0.0));"
+ EOL "const vec2 gridPlane[3] = vec2[] (" EOL
" vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));"
EOL "void main()" EOL "{" EOL " vec3 aVertex = gridPlane[gl_VertexID];" EOL
" vNdc = aVertex.xy;" EOL " gl_Position = vec4 (aVertex, 1.0);" EOL "}";
EOL "void main()" EOL "{" EOL " vec2 aVertex = gridPlane[gl_VertexID];" EOL
" vNdc = aVertex;" EOL " gl_Position = vec4 (aVertex, 0.0, 1.0);" EOL "}";
TCollection_AsciiString aSrcFrag =
TCollection_AsciiString()
+ EOL
"vec4 gridLines2d (vec2 theUV, vec2 theAxisUV, vec3 theColor, vec2 theScale," EOL
" bool theIsDrawAxis, float theThickness)" EOL "{" EOL
" vec2 aCoord = theUV * theScale;" EOL " vec2 aFwidth = fwidth (aCoord);" EOL
" vec2 aDerivative = max (aFwidth, vec2 (theThickness));" EOL
" vec2 aGrid = abs (fract (aCoord - 0.5) - 0.5) / aDerivative;" EOL
// Per-axis Nyquist fade: when more than ~1.5 grid cells map to a pixel in a given
// direction, that set of lines fades to zero instead of smearing into a bright band.
// Lines mode (uDrawMode!=1): bright when either axis shows a line -> max of per-axis alphas.
// Points mode (uDrawMode==1): bright only at intersections -> product of per-axis alphas.
EOL " vec2 aNyq = vec2 (1.0) - smoothstep (vec2 (1.0), vec2 (2.0), aFwidth);" EOL
" float aAlphaX = (1.0 - min (aGrid.x, 1.0)) * aNyq.x;" EOL
" float aAlphaY = (1.0 - min (aGrid.y, 1.0)) * aNyq.y;" EOL
" float aAlpha = uDrawMode == 1 ? aAlphaX * aAlphaY : max (aAlphaX, aAlphaY);" EOL
" float aMinY = min (aDerivative.y, 1.0);" EOL
" float aMinX = min (aDerivative.x, 1.0);" EOL
" vec4 aColor = vec4 (theColor, aAlpha);" EOL
// Axis colouring in lines mode only; a "point" has no axis direction.
EOL " if (uIsDrawAxis != 0 && theIsDrawAxis && uDrawMode != 1)" EOL " {" EOL
" vec2 aAxisCoord = theAxisUV * theScale;" EOL
" bool isYAxis = abs (aAxisCoord.x) < aMinX;" EOL
" bool isXAxis = abs (aAxisCoord.y) < aMinY;" EOL
" if (isXAxis && isYAxis) { aColor.xyz = vec3 (0.0, 0.0, 1.0); }" EOL
" else if (isXAxis) { aColor.xyz = vec3 (1.0, 0.0, 0.0); }" EOL
" else if (isYAxis) { aColor.xyz = vec3 (0.0, 1.0, 0.0); }" EOL " }" EOL
" return aColor;" EOL "}"
"float gridLine1d (float theCoord, float theShift, float theScale, float theThickness)" EOL
"{" EOL " float aCoord = (theCoord - theShift) * theScale;" EOL
" float aPixelWidth = fwidth (aCoord);" EOL " if (!(aPixelWidth >= 0.0)) { return 0.0; }" EOL
" float aDist = abs (fract (aCoord + 0.5) - 0.5);" EOL
" float aWidth = max (aPixelWidth, theThickness);" EOL
" if (aWidth == 0.0) { return 0.0; }" EOL " return 1.0 - min (aDist / aWidth, 1.0);" EOL "}"
EOL "vec4 gridLines1d (float theCoord, float theScale, vec3 theColor, float theThickness)" EOL
"{" EOL " float aCoord = theCoord * theScale;" EOL
" float aFwidth = fwidth (aCoord);" EOL
" float aDerivative = max (aFwidth, theThickness);" EOL
" float aGrid = abs (fract (aCoord - 0.5) - 0.5) / aDerivative;" EOL
" float aNyq = 1.0 - smoothstep (1.0, 2.0, aFwidth);" EOL
" return vec4 (theColor, (1.0 - min (aGrid, 1.0)) * aNyq);" EOL "}"
EOL "float axisLine1d (float theCoord, float theThickness)" EOL "{" EOL
" float aWidth = max (fwidth (theCoord), theThickness);" EOL
" if (aWidth == 0.0) { return 0.0; }" EOL
" return 1.0 - min (abs (theCoord) / aWidth, 1.0);" EOL "}"
EOL "vec4 gridLines2d (vec2 theUV, vec2 theAxisUV, vec3 theColor, vec2 theScale," EOL
" vec2 theShift, bool theIsDrawAxis, float theThickness)" EOL "{" EOL
" float aAlphaX = gridLine1d (theUV.x, theShift.x, theScale.x, theThickness);" EOL
" float aAlphaY = gridLine1d (theUV.y, theShift.y, theScale.y, theThickness);" EOL
" float aAlpha = uDrawMode == 1 ? aAlphaX * aAlphaY : max (aAlphaX, aAlphaY);" EOL
" vec4 aColor = vec4 (theColor, aAlpha);" EOL
" if (uIsDrawAxis != 0 && theIsDrawAxis && uDrawMode != 1)" EOL " {" EOL
" float anAxisX = axisLine1d (theAxisUV.y, theThickness);" EOL
" float anAxisY = axisLine1d (theAxisUV.x, theThickness);" EOL
" if (anAxisX > 0.0 && anAxisY > 0.0) { aColor = vec4 (0.0, 0.0, 1.0, max "
"(aColor.a, max (anAxisX, anAxisY))); }" EOL
" else if (anAxisX > 0.0) { aColor = vec4 (1.0, 0.0, 0.0, max "
"(aColor.a, anAxisX)); }" EOL
" else if (anAxisY > 0.0) { aColor = vec4 (0.0, 1.0, 0.0, max "
"(aColor.a, anAxisY)); }" EOL " }" EOL " return aColor;" EOL "}"
EOL "vec4 gridLines1d (float theCoord, float theShift, float theScale, vec3 theColor, float "
"theThickness)" EOL "{" EOL
" return vec4 (theColor, gridLine1d (theCoord, theShift, theScale, theThickness));" EOL "}"
EOL "vec4 overlayGrid (vec4 theBase, vec4 theAccent)" EOL "{" EOL
" return theAccent.a >= theBase.a ? theAccent : theBase;" EOL "}"
EOL "vec3 unproject (float theX, float theY, float theZ)" EOL "{" EOL
" vec4 aUnproj = occModelWorldMatrixInverse * occWorldViewMatrixInverse" EOL
" * occProjectionMatrixInverse * vec4 (theX, theY, theZ, 1.0);" EOL
" return aUnproj.xyz / aUnproj.w;" EOL "}"
EOL "vec3 unprojectView (float theX, float theY, float theZ)" EOL "{" EOL
" vec4 aView = occProjectionMatrixInverse * vec4 (theX, theY, theZ, 1.0);" EOL
" return aView.xyz / aView.w;" EOL "}"
// sin(angle) threshold for "ray parallel to plane" - ~5.7e-5 deg.
EOL "const float GRID_PARALLEL_EPS = 1e-6;"
EOL "float gridNdcNear()" EOL "{" EOL " return uIsZeroToOneDepth != 0 ? 0.0 : -1.0;" EOL "}"
EOL "bool intersectPlane (vec3 theNearPoint, vec3 theFarPoint, out vec3 theHit)" EOL "{" EOL
" vec3 aDir = theFarPoint - theNearPoint;" EOL
" float aDenomN = dot (uPlaneN, normalize (aDir));" EOL
" if (abs (aDenomN) < GRID_PARALLEL_EPS)" EOL " {" EOL " theHit = theNearPoint;" EOL
" return false;" EOL " }" EOL " float aDenom = dot (uPlaneN, aDir);" EOL
" float aT = dot (uPlaneN, uPlaneOrigin - theNearPoint) / aDenom;" EOL
" theHit = theNearPoint + aT * aDir;" EOL " return true;" EOL "}" EOL
EOL "float gridDepthFromNdc (float theNdcZ)" EOL "{" EOL
" return uIsZeroToOneDepth != 0 ? theNdcZ : (theNdcZ * 0.5 + 0.5);" EOL "}"
EOL "float computeDepth (vec3 thePos)" EOL "{" EOL
" mat4 aMVP = occProjectionMatrix * occWorldViewMatrix * occModelWorldMatrix;" EOL
" vec4 aClip = aMVP * vec4 (thePos, 1.0);" EOL " return aClip.z / aClip.w;" EOL "}"
EOL "float fragmentDepthFromView (vec3 theViewPnt)" EOL "{" EOL
" vec4 aClip = occProjectionMatrix * vec4 (theViewPnt, 1.0);" EOL
" float aNdcZ = aClip.z / aClip.w;" EOL " return gridDepthFromNdc (aNdcZ);" EOL "}"
// Fade-band width (as a fraction of bound) applied inside the bound edge.
// Narrow smoothstep softens rectangular/disc cuts; angular cut stays hard.
EOL "const float GRID_FADE_BAND = 0.95;"
// Keep gl_FragDepth strictly less than 1.0 so the grid never lands exactly
// on the far plane (some drivers cull there).
EOL "const float GRID_DEPTH_EPS = 1e-5;" EOL "const float GRID_TWO_PI = 6.28318530718;"
EOL "bool intersectPlaneView (vec3 theNearPoint, vec3 theFarPoint, out vec3 theHit)" EOL "{" EOL
" vec3 aRayOrigin = uIsPerspective != 0 ? vec3 (0.0) : theNearPoint;" EOL
" vec3 aRayTarget = theFarPoint;" EOL " vec3 aDir = aRayTarget - aRayOrigin;" EOL
" float aDirLen = length (aDir);" EOL " if (aDirLen == 0.0) { return false; }" EOL
" float aDenomN = dot (uPlaneNView, aDir / aDirLen);" EOL
" if (abs (aDenomN) <= uParallelTolerance) { return false; }" EOL
" float aDenom = dot (uPlaneNView, aDir);" EOL
" float aT = dot (uPlaneNView, uPlaneOriginView - aRayOrigin) / aDenom;" EOL
" if (uIsBackground == 0 && uIsPerspective != 0 && aT < 0.0)" EOL " {" EOL
" theHit = theNearPoint;" EOL " return false;" EOL " }" EOL
" theHit = aRayOrigin + aT * aDir;" EOL " return true;" EOL "}" EOL
EOL "const float GRID_TWO_PI = 6.28318530718;"
EOL "float gridNormalizeAngle (float theAngle)" EOL "{" EOL
" float anAngle = mod (theAngle, GRID_TWO_PI);" EOL
" if (anAngle < 0.0) { anAngle += GRID_TWO_PI; }" EOL " return anAngle;" EOL "}"
EOL "float gridPositiveAngleSpan (float theStart, float theEnd)" EOL "{" EOL
" float aSpan = mod (theEnd - theStart, GRID_TWO_PI);" EOL
" if (aSpan < 0.0) { aSpan += GRID_TWO_PI; }" EOL
" if (abs (aSpan) <= uParallelTolerance && abs (theEnd - theStart) >= GRID_TWO_PI - "
"uParallelTolerance)" EOL " {" EOL " return GRID_TWO_PI;" EOL " }" EOL
" return aSpan;" EOL "}"
EOL "bool gridAngleInArc (float theAngle)" EOL "{" EOL
" float aSpan = gridPositiveAngleSpan (uArcRange.x, uArcRange.y);" EOL
" if (aSpan >= GRID_TWO_PI - uParallelTolerance) { return true; }" EOL
" float aDelta = gridNormalizeAngle (theAngle) - gridNormalizeAngle (uArcRange.x);" EOL
" if (aDelta < 0.0) { aDelta += GRID_TWO_PI; }" EOL
" return aDelta <= aSpan + uParallelTolerance;" EOL "}"
EOL "void main()" EOL "{"
// Unproject per-pixel from vNdc (linearly interpolated NDC.xy) to avoid the
// nonlinearity of the perspective divide when ray endpoints are passed as
// varyings. Each fragment reconstructs its own Near/Far world points.
EOL " vec3 aNearPoint = unproject (vNdc.x, vNdc.y, -1.0);" EOL
" vec3 aFarPoint = unproject (vNdc.x, vNdc.y, 1.0);" EOL " vec3 aHit;" EOL
" if (!intersectPlane (aNearPoint, aFarPoint, aHit)) { discard; }"
// Plane-local 2D coords, origin-centered to tame fp precision at large world offsets.
EOL " vec3 aLocal3 = aHit - uPlaneOrigin;" EOL
" vec2 aLocal = vec2 (dot (aLocal3, uPlaneX), dot (aLocal3, uPlaneY));"
// Intersect in view space: the camera is the numerical origin, so zoom and
// world-coordinate magnitude do not destabilize the line phase.
EOL " vec3 aNearPoint = unprojectView (vNdc.x, vNdc.y, gridNdcNear());" EOL
" vec3 aFarPoint = unprojectView (vNdc.x, vNdc.y, 1.0);" EOL " vec3 aHit;" EOL
" if (!intersectPlaneView (aNearPoint, aFarPoint, aHit)) { discard; }" EOL
" float aFragDepth = fragmentDepthFromView (aHit);" EOL " if (uIsBackground == 0)" EOL
" {" EOL " if (aFragDepth < 0.0) { discard; }" EOL
" gl_FragDepth = min (aFragDepth, 1.0);" EOL " }" EOL " else" EOL " {" EOL
" gl_FragDepth = 1.0;" EOL " }" EOL " vec3 aLocalAbs3 = aHit - uPlaneOriginView;" EOL
" vec2 aLocal = vec2 (dot (aLocalAbs3, uPlaneXView), dot (aLocalAbs3, uPlaneYView));" EOL
" vec3 aLocalGrid3 = aHit - uPlaneRefView;" EOL
" vec2 aLocalGrid = vec2 (dot (aLocalGrid3, uPlaneXView), dot (aLocalGrid3, "
"uPlaneYView));"
// Bounded work area. Rectangular, radial and angular clipping can be mixed.
EOL " float aR = length (aLocal);" EOL " float aA = atan (aLocal.y, aLocal.x);" EOL
" float aBoundFade = 1.0;" EOL " if (uBounds.z > 0.0)" EOL " {" EOL
" float aFwBR = fwidth (aR);" EOL " if (aR > uBounds.z + aFwBR) { discard; }" EOL
" aBoundFade *= 1.0 - smoothstep (GRID_FADE_BAND * uBounds.z, uBounds.z + aFwBR, aR);" EOL
" }" EOL " if (uArcBounded != 0)" EOL " {" EOL
" float aSpan = uArcRange.y - uArcRange.x;" EOL
" if (aSpan < 0.0) { aSpan += GRID_TWO_PI; }" EOL " float aDelta = aA - uArcRange.x;" EOL
" if (aDelta < 0.0) { aDelta += GRID_TWO_PI; }" EOL
" if (aDelta > aSpan) { discard; }" EOL " }" EOL " if (uBounds.x > 0.0)" EOL " {" EOL
// Extend the hard discard and smoothstep range by fwidth so the rectangular
// boundary gets sub-pixel AA (one screen-pixel transition) instead of a
// hard binary staircase at the clipping edge.
" float aFwBR = fwidth (aR);" EOL " if (aR > uBounds.z) { discard; }" EOL
" if (uIsBoundFade != 0)" EOL " {" EOL
" aBoundFade *= 1.0 - smoothstep (uBounds.z - aFwBR, uBounds.z, aR);" EOL " }" EOL
" }" EOL " if (uArcBounded != 0 && !gridAngleInArc (aA)) { discard; }" EOL
" if (uBounds.x > 0.0)" EOL " {" EOL
// Keep the bounded area geometrically strict; fwidth is used only for the
// optional fade inside the boundary, never to expand the valid area.
" float aFwBX = fwidth (abs (aLocal.x));" EOL
" if (abs (aLocal.x) > uBounds.x + aFwBX) { discard; }" EOL
" aBoundFade *= 1.0 - smoothstep (GRID_FADE_BAND * uBounds.x, uBounds.x + aFwBX, abs "
"(aLocal.x));" EOL " }" EOL " if (uBounds.y > 0.0)" EOL " {" EOL
" if (abs (aLocal.x) > uBounds.x) { discard; }" EOL " if (uIsBoundFade != 0)" EOL
" {" EOL " aBoundFade *= 1.0 - smoothstep (uBounds.x - aFwBX, uBounds.x, abs "
"(aLocal.x));" EOL " }" EOL " }" EOL " if (uBounds.y > 0.0)" EOL " {" EOL
" float aFwBY = fwidth (abs (aLocal.y));" EOL
" if (abs (aLocal.y) > uBounds.y + aFwBY) { discard; }" EOL
" aBoundFade *= 1.0 - smoothstep (GRID_FADE_BAND * uBounds.y, uBounds.y + aFwBY, abs "
"(aLocal.y));" EOL " }"
" if (abs (aLocal.y) > uBounds.y) { discard; }" EOL " if (uIsBoundFade != 0)" EOL
" {" EOL " aBoundFade *= 1.0 - smoothstep (uBounds.y - aFwBY, uBounds.y, abs "
"(aLocal.y));" EOL " }" EOL " }"
// Grid coordinates depend on uGridType: 0=rectangular (X/Y), 1=circular (radius/angle).
// For rectangular grid, rebase UVs around a CPU-provided stable reference
// (same for all fragments in the frame) to keep fract() arguments small.
EOL " vec2 aStableLocal = aLocal;" EOL " if (uGridType == 0)" EOL " {" EOL
" if (uHasStableRef != 0)" EOL " {" EOL
" vec2 aScaleSafe = max (abs (vec2 (uScaleX, uScaleY)), vec2 (1e-9));" EOL
" vec2 aShift = floor (uStableRefLocal * aScaleSafe) / aScaleSafe;" EOL
" aStableLocal = aLocal - aShift;" EOL " }" EOL " }" EOL " vec2 aGridUv;" EOL
" vec2 aScale;" EOL " vec2 aAxisUv;" EOL " if (uGridType == 1)" EOL " {" EOL
" aGridUv = vec2 (aR, aA);" EOL " aScale = vec2 (uScaleX, uAngularScale);" EOL
" aAxisUv = aLocal;" EOL " }" EOL " else" EOL " {" EOL " aGridUv = aStableLocal;" EOL
EOL " vec2 aGridUv;" EOL " vec2 aScale;" EOL " vec2 aAxisUv;" EOL " if (uGridType == 1)" EOL
" {" EOL " aGridUv = vec2 (aR, aA);" EOL " aScale = vec2 (uScaleX, uAngularScale);" EOL
" aAxisUv = aLocal;" EOL " }" EOL " else" EOL " {" EOL " aGridUv = aLocalGrid;" EOL
" aScale = vec2 (uScaleX, uScaleY);" EOL " aAxisUv = aLocal;" EOL " }" EOL
" vec4 aColor = gridLines2d (aGridUv, aAxisUv, uColor, aScale, uGridType == 0, "
"uThickness);" EOL " if (uDrawMode != 1)" EOL " {" EOL " if (uGridType == 0)" EOL
" vec2 aGridShift = uGridType == 1 ? vec2 (uRadialOriginShift, 0.0) : vec2 (0.0);" EOL
" vec4 aColor = gridLines2d (aGridUv, aAxisUv, uColor, aScale, aGridShift, "
"uGridType == 0, uThickness);" EOL " if (uDrawMode != 1)" EOL " {" EOL
" if (uGridType == 0)" EOL " {" EOL " if (uAccentScaleX > 0.0)" EOL " {" EOL
" aColor = overlayGrid (aColor, gridLines1d (aLocal.x, uAccentLocalOriginShift.x, "
"uAccentScaleX, uAccentColor, uThickness));" EOL " }" EOL
" if (uAccentScaleY > 0.0)" EOL " {" EOL
" aColor = overlayGrid (aColor, gridLines1d (aLocal.y, uAccentLocalOriginShift.y, "
"uAccentScaleY, uAccentColor, uThickness));" EOL " }" EOL " }" EOL " else" EOL
" {" EOL " if (uAccentScaleX > 0.0)" EOL " {" EOL
" float aAccX = aLocal.x;" EOL " if (uHasStableRef != 0)" EOL " {" EOL
" float aScaleAccX = max (abs (uAccentScaleX), 1e-9);" EOL
" float aShiftAccX = floor (uStableRefLocal.x * aScaleAccX) / aScaleAccX;" EOL
" aAccX -= aShiftAccX;" EOL " }" EOL
" aColor = overlayGrid (aColor, gridLines1d (aAccX, uAccentScaleX, uAccentColor, "
"uThickness));" EOL " }" EOL " if (uAccentScaleY > 0.0)" EOL " {" EOL
" float aAccY = aLocal.y;" EOL " if (uHasStableRef != 0)" EOL " {" EOL
" float aScaleAccY = max (abs (uAccentScaleY), 1e-9);" EOL
" float aShiftAccY = floor (uStableRefLocal.y * aScaleAccY) / aScaleAccY;" EOL
" aAccY -= aShiftAccY;" EOL " }" EOL
" aColor = overlayGrid (aColor, gridLines1d (aAccY, uAccentScaleY, uAccentColor, "
"uThickness));" EOL " }" EOL " }" EOL " else" EOL " {" EOL
" if (uAccentScaleX > 0.0)" EOL " {" EOL
" aColor = overlayGrid (aColor, gridLines1d (aR, uAccentScaleX, uAccentColor, "
"uThickness));" EOL " }" EOL " if (uAccentAngularScale > 0.0)" EOL " {" EOL
" aColor = overlayGrid (aColor, gridLines1d (aA, uAccentAngularScale, uAccentColor, "
"uThickness));" EOL " }" EOL " }" EOL
" aColor = overlayGrid (aColor, gridLines1d (aR, uAccentRadialOriginShift, "
"uAccentScaleX, uAccentColor, uThickness));" EOL " }" EOL
" if (uAccentAngularScale > 0.0)" EOL " {" EOL
" aColor = overlayGrid (aColor, gridLines1d (aA, 0.0, uAccentAngularScale, "
"uAccentColor, uThickness));" EOL " }" EOL " }" EOL
// For circular grid, paint X/Y axis lines explicitly using plane-local coords.
" if (uIsDrawAxis != 0 && uGridType == 1)" EOL " {" EOL
" vec2 aDerivAxis = max (fwidth (aAxisUv), vec2 (uThickness));" EOL
" if (abs (aAxisUv.x) < aDerivAxis.x && abs (aAxisUv.y) < aDerivAxis.y)" EOL " {" EOL
" float anAxisX = axisLine1d (aAxisUv.y, uThickness);" EOL
" float anAxisY = axisLine1d (aAxisUv.x, uThickness);" EOL
" if (anAxisX > 0.0 && anAxisY > 0.0)" EOL " {" EOL
" aColor = vec4 (0.0, 0.0, 1.0, 1.0);" EOL " }" EOL
" else if (abs (aAxisUv.y) < aDerivAxis.y)" EOL " {" EOL
" aColor = vec4 (1.0, 0.0, 0.0, 1.0);" EOL " }" EOL
" else if (abs (aAxisUv.x) < aDerivAxis.x)" EOL " {" EOL
" aColor = vec4 (0.0, 1.0, 0.0, 1.0);" EOL " }" EOL " }" EOL " }"
" else if (anAxisX > 0.0)" EOL " {" EOL
" aColor = vec4 (1.0, 0.0, 0.0, max (aColor.a, anAxisX));" EOL " }" EOL
" else if (anAxisY > 0.0)" EOL " {" EOL
" aColor = vec4 (0.0, 1.0, 0.0, max (aColor.a, anAxisY));" EOL " }" EOL " }" EOL
" }"
EOL " float aDepth = computeDepth (aHit);" EOL " float aFar = gl_DepthRange.far;" EOL
" float aNear = gl_DepthRange.near;" EOL
" aDepth = ((aFar - aNear) * aDepth + aNear + aFar) * 0.5;"
// No aT-sign discard: when the grid plane passes through / close to the
// near clip (the "grid going into camera view" case), aT can be <= 0 for
// fragments whose Near point is on the opposite side of the plane. The ray
// still intersects the plane; we just clamp depth via gl_FragDepth's [0,1]
// range so those fragments read as "in front of everything", which reads
// naturally as the ground/plane reaching under the camera.
EOL " if (aColor.a == 0.0) { discard; }"
EOL " if (aColor.a == 0.0) { discard; }" EOL " aColor.a *= aBoundFade;" EOL
" occFragColor = aColor;" EOL "}";
EOL " float aMaxDepth = 1.0 - GRID_DEPTH_EPS;" EOL
" gl_FragDepth = uIsBackground != 0 ? aMaxDepth : min (aDepth, aMaxDepth);" EOL
" aColor.a *= aBoundFade;" EOL " occFragColor = aColor;" EOL "}";
// Requires gl_VertexID (GL 3.0/ES 3.0+), fwidth, gl_FragDepth.
// Requires gl_VertexID (GL 3.0/ES 3.0+) and fwidth.
if (myGapi == Aspect_GraphicsLibrary_OpenGL)
{
aProgSrc->SetHeader(IsGapiGreaterEqual(3, 2) ? "#version 150" : "#version 130");
@@ -2264,11 +2264,22 @@ void AIS_ViewController::handleCameraActions(const occ::handle<AIS_InteractiveCo
const occ::handle<V3d_View>& theView,
const AIS_WalkDelta& theWalk)
{
const bool hasGridEcho = theView->IsGridActive() && theView->Viewer()->GridEcho();
const bool hasShaderGridEcho = hasGridEcho && theView->IsShaderGridActive();
NCollection_Vec2<int> aMoveToAfterCamera =
HasPreviousMoveTo() ? PreviousMoveTo() : LastMousePosition();
bool toUpdateMoveToAfterCamera = hasShaderGridEcho && !theWalk.IsEmpty();
bool toHideGridEchoAfterCamera = false;
const bool isMouseRotation =
(myGL.OrbitRotation.ToRotate || myGL.ViewRotation.ToRotate || myGL.ZRotate.ToRotate)
&& myToAllowRotation;
// apply view actions
if (myGL.Orientation.ToSetViewOrient)
{
theView->SetProj(myGL.Orientation.ViewOrient);
myGL.Orientation.ToFitAll = true;
toUpdateMoveToAfterCamera = hasShaderGridEcho;
}
// apply fit all
@@ -2278,6 +2289,7 @@ void AIS_ViewController::handleCameraActions(const occ::handle<AIS_InteractiveCo
theView->FitAll(aFitMargin, false);
theView->Invalidate();
myGL.Orientation.ToFitAll = false;
toUpdateMoveToAfterCamera = hasShaderGridEcho;
}
NCollection_List<occ::handle<AIS_InteractiveObject>> anObjects;
@@ -2360,6 +2372,14 @@ void AIS_ViewController::handleCameraActions(const occ::handle<AIS_InteractiveCo
handlePanning(theView);
handleZRotate(theView);
if (hasShaderGridEcho && myGL.Panning.ToPan && myToAllowPanning)
{
toUpdateMoveToAfterCamera = true;
}
if (hasShaderGridEcho && myGL.ZRotate.ToRotate && myToAllowRotation)
{
toHideGridEchoAfterCamera = true;
}
}
if ((myNavigationMode == AIS_NavigationMode_Orbit || myGL.OrbitRotation.ToStart
@@ -2397,6 +2417,10 @@ void AIS_ViewController::handleCameraActions(const occ::handle<AIS_InteractiveCo
handleOrbitRotation(theView,
aGravPnt,
myToLockOrbitZUp || myNavigationMode != AIS_NavigationMode_Orbit);
if (hasShaderGridEcho && myGL.OrbitRotation.ToRotate && myToAllowRotation)
{
toHideGridEchoAfterCamera = isMouseRotation;
}
}
if ((myNavigationMode != AIS_NavigationMode_Orbit || myGL.ViewRotation.ToStart
@@ -2428,6 +2452,10 @@ void AIS_ViewController::handleCameraActions(const occ::handle<AIS_InteractiveCo
theWalk[AIS_WalkRotation_Pitch].Value,
aRoll,
myNavigationMode == AIS_NavigationMode_FirstPersonFlight);
if (hasShaderGridEcho && myGL.ViewRotation.ToRotate && myToAllowRotation)
{
toHideGridEchoAfterCamera = isMouseRotation;
}
}
if (!myGL.ZoomActions.IsEmpty())
@@ -2448,6 +2476,10 @@ void AIS_ViewController::handleCameraActions(const occ::handle<AIS_InteractiveCo
{
continue;
}
if (aZoomParams.HasPoint())
{
aMoveToAfterCamera = aZoomParams.Point;
}
if (!theView->Camera()->IsOrthographic())
{
@@ -2456,6 +2488,7 @@ void AIS_ViewController::handleCameraActions(const occ::handle<AIS_InteractiveCo
&& PickPoint(aPnt, theCtx, theView, aZoomParams.Point, myToStickToRayOnZoom))
{
handleZoom(theView, aZoomParams, &aPnt);
toUpdateMoveToAfterCamera = hasGridEcho;
continue;
}
@@ -2465,13 +2498,33 @@ void AIS_ViewController::handleCameraActions(const occ::handle<AIS_InteractiveCo
{
aZoomParams.ResetPoint(); // do not pretend to zoom at 'nothing'
handleZoom(theView, aZoomParams, &aPnt);
toUpdateMoveToAfterCamera = hasGridEcho;
continue;
}
}
handleZoom(theView, aZoomParams, nullptr);
toUpdateMoveToAfterCamera = hasGridEcho;
}
myGL.ZoomActions.Clear();
}
if (toHideGridEchoAfterCamera)
{
theView->Viewer()->HideGridEcho(theView);
theView->InvalidateImmediate();
ResetPreviousMoveTo();
}
else if (toUpdateMoveToAfterCamera)
{
int aWidth = 0, aHeight = 0;
theView->Window()->Size(aWidth, aHeight);
if (aMoveToAfterCamera.x() >= 0 && aMoveToAfterCamera.x() < aWidth
&& aMoveToAfterCamera.y() >= 0 && aMoveToAfterCamera.y() < aHeight)
{
ResetPreviousMoveTo();
contextLazyMoveTo(theCtx, theView, aMoveToAfterCamera);
}
}
}
//=================================================================================================
@@ -2865,14 +2918,19 @@ void AIS_ViewController::contextLazyMoveTo(const occ::handle<AIS_InteractiveCont
theView->Camera()->SetZRange(aZNear, aZFar);
occ::handle<SelectMgr_EntityOwner> aNewPicked = theCtx->DetectedOwner();
if (theView->Viewer()->IsGridActive() && theView->Viewer()->GridEcho())
if (theView->IsGridActive() && theView->Viewer()->GridEcho())
{
if (aNewPicked.IsNull())
{
NCollection_Vec3<double> aPnt3d;
theView->ConvertToGrid(thePnt.x(), thePnt.y(), aPnt3d[0], aPnt3d[1], aPnt3d[2]);
theView->Viewer()->ShowGridEcho(theView, Graphic3d_Vertex(aPnt3d[0], aPnt3d[1], aPnt3d[2]));
Graphic3d_Vertex aGridPoint, anEchoPoint;
if (theView->ConvertToGridEcho(thePnt.x(), thePnt.y(), aGridPoint, anEchoPoint))
{
theView->Viewer()->ShowGridEcho(theView, anEchoPoint);
}
else
{
theView->Viewer()->HideGridEcho(theView);
}
theView->InvalidateImmediate();
}
else
+72 -14
View File
@@ -445,10 +445,9 @@ void V3d_View::AutoZFit() const
void V3d_View::ZFitAll(const double theScaleFactor) const
{
Bnd_Box aMinMaxBox = myView->MinMaxValues(false); // applicative min max boundaries
// clang-format off
Bnd_Box aGraphicBox = myView->MinMaxValues (true); // real graphical boundaries (not accounting infinite flag).
// clang-format on
Bnd_Box aMinMaxBox;
Bnd_Box aGraphicBox;
myView->ZFitAllBounds(aMinMaxBox, aGraphicBox);
myView->Camera()->ZFitAll(theScaleFactor, aMinMaxBox, aGraphicBox);
}
@@ -1785,6 +1784,29 @@ void V3d_View::ConvertToGrid(const int theXp,
double& theYg,
double& theZg) const
{
Graphic3d_Vertex aGridPoint;
if (ConvertToGrid(theXp, theYp, aGridPoint))
{
aGridPoint.Coord(theXg, theYg, theZg);
return;
}
NCollection_Vec3<double> anXYZ;
Convert(theXp, theYp, anXYZ.x(), anXYZ.y(), anXYZ.z());
theXg = anXYZ.x();
theYg = anXYZ.y();
theZg = anXYZ.z();
}
//=================================================================================================
bool V3d_View::ConvertToGrid(const int theXp, const int theYp, Graphic3d_Vertex& theGridPoint) const
{
if (myShaderGridActive)
{
return myView->ShaderGridEcho(theXp, theYp, theGridPoint);
}
NCollection_Vec3<double> anXYZ;
Convert(theXp, theYp, anXYZ.x(), anXYZ.y(), anXYZ.z());
@@ -1792,13 +1814,32 @@ void V3d_View::ConvertToGrid(const int theXp,
aVrp.SetCoord(anXYZ.x(), anXYZ.y(), anXYZ.z());
if (MyViewer->IsGridActive())
{
Graphic3d_Vertex aNewVrp = Compute(aVrp);
aNewVrp.Coord(theXg, theYg, theZg);
theGridPoint = Compute(aVrp);
return true;
}
else
return false;
}
//=================================================================================================
bool V3d_View::ConvertToGridEcho(const int theXp,
const int theYp,
Graphic3d_Vertex& theGridPoint,
Graphic3d_Vertex& theEchoPoint) const
{
if (myShaderGridActive)
{
aVrp.Coord(theXg, theYg, theZg);
return myView->ShaderGridEcho(theXp, theYp, theGridPoint, theEchoPoint);
}
if (!ConvertToGrid(theXp, theYp, theGridPoint))
{
return false;
}
theEchoPoint = theGridPoint;
return true;
}
//=================================================================================================
@@ -1810,6 +1851,22 @@ void V3d_View::ConvertToGrid(const double theX,
double& theYg,
double& theZg) const
{
if (myShaderGridActive)
{
const Graphic3d_Vertex aPoint(theX, theY, theZ);
Graphic3d_Vertex aShaderGridPoint;
if (myView->ShaderGridSnapPoint(aPoint, aShaderGridPoint))
{
aShaderGridPoint.Coord(theXg, theYg, theZg);
return;
}
theXg = theX;
theYg = theY;
theZg = theZ;
return;
}
if (MyViewer->IsGridActive())
{
Graphic3d_Vertex aVrp(theX, theY, theZ);
@@ -3523,6 +3580,10 @@ void V3d_View::SetGrid(const gp_Ax3& aPlane, const occ::handle<Aspect_Grid>& aGr
void V3d_View::SetGridActivity(const bool AFlag)
{
if (MyGrid.IsNull())
{
return;
}
if (AFlag)
{
MyGrid->Activate();
@@ -3544,16 +3605,13 @@ void V3d_View::GridDisplay(const Aspect_GridParams& theParams)
void V3d_View::GridDisplay(const Aspect_GridParams& theParams, const gp_Ax3& thePlane)
{
// Mutual exclusion: the CPU grid renders into a viewer-wide Graphic3d_Structure
// (visible in every active view), so enabling the per-view shader grid hides the
// CPU rendering at the viewer level. Snap geometry (Aspect_*Grid) is left alive
// and only the structure is erased; SetGrid / ActivateGrid restores it.
myView->GridDisplay(theParams, thePlane);
if (!MyGrid.IsNull() && MyGrid->IsDisplayed())
{
MyGrid->Erase();
}
myShaderGridActive = true;
myView->GridDisplay(theParams, thePlane);
myShaderGridActive = theParams.DrawMode() != Aspect_GDM_None;
}
//=================================================================================================
+28 -10
View File
@@ -36,6 +36,7 @@ class Aspect_Window;
class Graphic3d_Group;
class Graphic3d_Structure;
class Graphic3d_TextureEnv;
class Graphic3d_Vertex;
//! Defines the application object VIEW for the
//! VIEWER application.
@@ -671,6 +672,21 @@ public:
double& Yg,
double& Zg) const;
//! Converts the projected point into the nearest visible grid point.
//! @return TRUE when an active grid accepts the point; FALSE otherwise.
//! Unlike the double-output overload, this method has no unproject fallback
//! and is intended for grid echo / snap-hit callers.
Standard_EXPORT bool ConvertToGrid(const int Xp,
const int Yp,
Graphic3d_Vertex& theGridPoint) const;
//! Converts the projected point into the nearest visible grid point and echo display point.
//! The echo display point is suitable only for displaying the grid echo marker.
Standard_EXPORT bool ConvertToGridEcho(const int Xp,
const int Yp,
Graphic3d_Vertex& theGridPoint,
Graphic3d_Vertex& theEchoPoint) const;
//! Converts the point into the nearest grid point
//! and display the grid marker.
Standard_EXPORT void ConvertToGrid(const double X,
@@ -907,11 +923,9 @@ public:
const double theResolution = 0.0,
const bool theToEnlargeIfLine = true) const;
public: //! @name CPU grid plumbing (deprecated, fed by V3d_Viewer::ActivateGrid)
//! Snap + CPU rendering. The CPU grid lives on the viewer's structure manager
//! and is visible in every active view; SetGrid on a view that has the shader
//! grid enabled erases the shader grid on this view, the CPU grid is left
//! intact (or re-displayed by V3d_Viewer::ActivateGrid).
public: //! @name Viewer grid plumbing
//! Viewer-managed grid plane and snap object. It is separate from the per-view
//! shader grid controlled by GridDisplay().
//! Defines or updates the grid plane and snap object on this view.
//! @param[in] aPlane grid plane (origin + axes)
@@ -922,11 +936,15 @@ public: //! @name CPU grid plumbing (deprecated, fed by V3d_Viewer::ActivateGrid
//! @param[in] aFlag true to enable snap, false to disable
Standard_EXPORT void SetGridActivity(const bool aFlag);
public: //! @name GPU shader grid (recommended)
//! Per-view immediate-mode shader; supports unbounded extents, AA, background, arc range.
//! GridDisplay erases the viewer-wide CPU grid rendering on entry (snap geometry
//! on Aspect_*Grid is preserved). GridErase only tears down the shader grid on
//! this view; restoring the CPU rendering needs V3d_Viewer::ActivateGrid.
//! Return TRUE if either viewer-managed grid or per-view shader grid is active.
bool IsGridActive() const { return MyViewer->IsGridActive() || myShaderGridActive; }
//! Return TRUE if the per-view shader grid is active.
bool IsShaderGridActive() const { return myShaderGridActive; }
public: //! @name Shader grid
//! Per-view immediate-mode shader grid; supports unbounded extents, AA, background,
//! circular grids, arc range and view-adaptive spacing.
//! Display a shader-rendered grid on the viewer's privileged plane.
//! @param[in] theParams appearance: color, scale, bounds, arc, draw-mode, background /
+2 -2
View File
@@ -797,8 +797,8 @@ void V3d_Viewer::ShowGridEcho(const occ::handle<V3d_View>& theView,
myGridEchoGroup->SetPrimitivesAspect(myGridEchoAspect);
}
if (theVertex.X() == myGridEchoLastVert.X() && theVertex.Y() == myGridEchoLastVert.Y()
&& theVertex.Z() == myGridEchoLastVert.Z())
if (!theView->IsShaderGridActive() && theVertex.X() == myGridEchoLastVert.X()
&& theVertex.Y() == myGridEchoLastVert.Y() && theVertex.Z() == myGridEchoLastVert.Z())
{
return;
}
+22
View File
@@ -0,0 +1,22 @@
puts "=================================================================="
puts "GPU shader grid keeps foreground plane semantics in perspective view"
puts "=================================================================="
pload MODELING VISUALIZATION
vclear
vinit View1 w=400 h=400
vaxo
box b 10 10 10
vdisplay b -dispMode 1
vfit
vgrid -type gpu -size 100 100 -drawAxis 0
vcamera -persp
vdump $imagedir/${casename}_persp.png
vrotate 0 0 0.5
vdump $imagedir/${casename}_rotated.png
vgrid off
+20
View File
@@ -0,0 +1,20 @@
puts "=================================================================="
puts "GPU shader grid is visible without scene objects and without vfit"
puts "=================================================================="
pload MODELING VISUALIZATION
vclear
vinit View1 w=400 h=400
vaxo
# Empty view: shader grid must not depend on scene bounding boxes.
vgrid -type gpu -size 100 100 -drawAxis 0
vdump $imagedir/${casename}_empty.png
# Adding an object without vfit must not be required to make the grid visible.
box b 10 10 10
vdisplay b -dispMode 1
vdump $imagedir/${casename}_display_no_vfit.png
vgrid off