Visualization - Shader-based infinite grid for V3d (#1223)

The classical V3d_RectangularGrid / V3d_CircularGrid path generated all grid
lines on the CPU into a Graphic3d_Structure and recomputed them whenever any
parameter, the camera, or the privileged plane moved. Dense grids paid an
O(NxM) vertex rebuild per interaction and were effectively capped in extent;
there was no way to draw an infinite grid, a background (sky-plane) grid, or
a grid with sub-pixel line antialiasing.

This change promotes a shader-based grid to the single rendering path for
both rectangular and circular grids. Classical API, snap math and the vgrid
Draw command are preserved; only the presentation pipeline moves under the
shader.

Aspect layer:

- Add Aspect_GridParams: POD carrying shader-only appearance knobs (color,
  origin, Scale, ScaleY, LineThickness, RotationAngle, AngularDivisions,
  IsBackground, IsDrawAxis, IsInfinity, DrawMode, SizeX/SizeY, Radius,
  AngleStart/AngleEnd). EffectiveScaleY() returns ScaleY when non-zero and
  Scale() otherwise; IsCircular() is shorthand for AngularDivisions() > 0;
  IsBounded() / IsArc() report active clipping.
- Extend Aspect_RectangularGrid with SizeX/SizeY/ZOffset fields and
  accessors; extend Aspect_CircularGrid with Radius/ZOffset/AngleStart/
  AngleEnd and IsArc(). Snap math is unchanged.
- Add Graphic3d_CView::GridDisplay(Aspect_GridParams, gp_Ax3) and GridErase()
  virtuals with no-op defaults; include Aspect_GridParams.hxx and gp_Ax3.hxx
  from Graphic3d_CView.hxx.

Shader (Graphic3d_ShaderManager::getGridProgram):

- Full-screen triangle unprojected per-fragment to world-space Near/Far
  points, ray-plane intersection against an arbitrary plane supplied through
  uPlaneOrigin / uPlaneX / uPlaneY / uPlaneN uniforms. Per-fragment unproject
  avoids perspective-divide nonlinearity that plagues varying-based rays.
- Helper intersectPlane() uses a normalized direction for the parallel test,
  so GRID_PARALLEL_EPS = 1e-6 is a dimensionless |sin(angle)| threshold
  (~5.7e-5 deg) that stays scale-invariant under any world-depth range.
- gridLines2d / gridLines1d: fwidth-based AA plus per-axis Nyquist fade
  (smoothstep(1.0, 2.0, fwidth)). Once a grid period fits inside a single
  pixel, that axis fades to zero instead of smearing into a bright haze at
  grazing angles. Lines mode uses max() of per-axis alphas; points mode
  uses the product so only intersections light up.
- Bounded work area (rectangular SizeX/SizeY, circular Radius, optional arc
  range): hard discard and smoothstep endpoint are both extended by
  fwidth(coord), giving a single-screen-pixel AA transition instead of a
  binary staircase at the clipping edge.
- Stable-reference rebasing for the rectangular grid: CPU unprojects the
  screen center to the plane every frame and uploads the plane-local hit as
  uStableRefLocal + uHasStableRef. The shader subtracts
  floor(ref * scale) / scale before fract(), keeping the fract() argument
  bounded at far world offsets without changing the visible line pattern.
- Branch on uGridType (0 = rectangular, 1 = circular). Circular path uses
  polar coords (length, atan2) scaled by uScaleX and uAngularScale;
  plane-local X/Y axis colouring is applied uniformly in both modes so the
  red/green/blue cardinal lines stay straight.
- uDrawMode switches lines vs points (Aspect_GDM_Points); uIsBackground
  pins gl_FragDepth to 1.0 - 1e-5 for the sky-plane look. Explicit GL 3.2 /
  GLES 3.0 version headers; requires gl_VertexID and gl_FragDepth.

OpenGL plumbing:

- Add OpenGl_ShaderManager::BindGridProgram(): lazy Create + cache, routed
  through bindProgramWithState so the standard OCCT matrix uniforms
  (occProjectionMatrix, occWorldViewMatrix, occModelWorldMatrix and their
  inverses, occViewport) are pushed onto the grid program every bind.
  myGridProgram is nullified in clear() so context resets release the
  compiled program.
- Add OpenGl_View::GridDisplay / GridErase overrides and private
  renderGrid(). The renderer binds a dedicated VAO (core-profile safe) and
  saves/restores program, depth test / func / mask, blend enable,
  blend-func-separate, and depth-clamp. ProjectionState / WorldViewState are
  push/pop-guarded. Original ZNear/ZFar/ProjType are captured before any
  mutation so a mid-function ZFitAll cannot clobber user-set vzrange on
  restore.
- Background-mode pan/rotate compensation is derived from the view-matrix
  delta (currentView * refView^-1) captured at GridDisplay(); no public
  Graphic3d_Camera API change is needed.
- Compute the plane-local stable reference in renderGrid via
  Graphic3d_TransformUtils::UnProject on the viewport center; upload
  uStableRefLocal / uHasStableRef alongside the other uniforms.
- Insert renderGrid() between renderScene() and renderTrihedron() in the
  non-immediate draw pass; release myGridVao in ReleaseGlResources.

V3d layer:

- Add V3d_View::GridDisplay(params) / GridDisplay(params, plane) / GridErase
  as thin pass-throughs; the single-argument overload uses
  V3d_Viewer::PrivilegedPlane().
- Rewrite V3d_RectangularGrid and V3d_CircularGrid: drop the nested
  RectangularGridStructure / CircularGridStructure classes, myGroup,
  DefineLines, DefinePoints, and all myCur* caching flags. Display() /
  Erase() / UpdateDisplay() now call syncViews() which builds an
  Aspect_GridParams from the Aspect_{Rectangular,Circular}Grid state
  (XStep/YStep -> Scale/ScaleY, RadiusStep -> Scale, DivisionNumber ->
  AngularDivisions, XOrigin/YOrigin/OffSet -> Origin, RotationAngle ->
  RotationAngle, SizeX/SizeY/Radius/ArcRange -> bounds, DrawMode)
  and broadcasts it over V3d_Viewer::DefinedViews(). Snap math in
  Aspect_RectangularGrid / Aspect_CircularGrid is untouched.
- V3d_RectangularGrid is unbounded by default (SizeX = SizeY = 0). The grid
  renderer honours an explicit SetSizeX / SetSizeY to activate in-shader
  clipping with AA edges; applications that want the old bounded behaviour
  set the size explicitly.

Draw / tests:

- vgrid Draw command gains -type {rect|circ|inf|infinite}, -color R G B,
  -scale N, -lineThickness T, -background {0|1}, -drawAxis {0|1},
  -inf {0|1}. Existing -origin, -step, -rotAngle, -zoffset, -size, -radius,
  -mode flags are retained and now drive the shader path.
- tests/v3d/grid/ (new group) adds ortho, persp, inf_pan, inf_rotate,
  inf_plane, rect_shader, circ_shader, rect_points, inf_options,
  bounded_rect, bounded_circ Draw regressions covering background mode,
  matrix-derived pan/rotate stability, non-XY privileged planes,
  anisotropic rectangular cells, polar divisions, points draw mode, and
  in-shader bounded clipping. Registered in tests/v3d/grids.list.
- Add src/Visualization/TKService/GTests/Aspect_GridParams_Test.cxx covering
  defaults, round-trip, copy, EffectiveScaleY fallback, RotationAngle
  round-trip, AngularDivisions / IsCircular toggle, and DrawMode round-trip.
- Add src/Visualization/TKService/GTests/Aspect_Grid_Bounds_Test.cxx
  covering SizeX/SizeY/Radius/ArcRange on the base grid classes.

Behaviour notes:

- V3d_RectangularGrid and V3d_CircularGrid no longer rely on
  Graphic3d_Structure view affinity, so new views added to the viewer after
  V3d_Viewer::ActivateGrid() must trigger a re-broadcast (call
  Grid()->Display() on the viewer or re-activate) to pick up the grid.
- Aspect_GDM_Points is now rendered as dots at grid intersections through
  uDrawMode (not as the old CPU point markers). Applications that depended
  on point markers as selectable entities should present them through a
  dedicated AIS object.
- V3d_RectangularGrid is unbounded by default; the old behaviour of
  capping to 0.5 * DefaultViewSize() is available via an explicit
  SetSizeX / SetSizeY call.
- Aspect_GridParams::Origin is a plane-local offset; the plane itself is
  supplied as a gp_Ax3 (defaults to V3d_Viewer::PrivilegedPlane()).
This commit is contained in:
Pasukhin Dmitry
2026-04-23 11:35:16 +01:00
committed by GitHub
parent b1646d7564
commit a2e851b254
36 changed files with 2263 additions and 710 deletions
@@ -20,6 +20,8 @@
#include <ViewerTest.hxx>
#include <Aspect_CircularGrid.hxx>
#include <Aspect_GridParams.hxx>
#include <AIS_AnimationAxisRotation.hxx>
#include <AIS_AnimationCamera.hxx>
#include <AIS_AnimationObject.hxx>
@@ -5002,8 +5004,14 @@ static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgV
Aspect_GridType aType = aViewer->GridType();
Aspect_GridDrawMode aMode = aViewer->GridDrawMode();
NCollection_Vec2<double> aNewOriginXY, aNewStepXY, aNewSizeXY;
double aNewRotAngle = 0.0, aNewZOffset = 0.0;
bool hasOrigin = false, hasStep = false, hasRotAngle = false, hasSize = false, hasZOffset = false;
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 isInfinite = false, hasInfOff = false, hasScale = false, hasArc = false;
bool hasColor = false, hasTenthColor = false;
Aspect_GridParams aGridParams;
ViewerTest_AutoUpdater anUpdateTool(ViewerTest::GetAISContext(), aView);
for (int anArgIter = 1; anArgIter < theArgNb; ++anArgIter)
{
@@ -5025,6 +5033,10 @@ static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgV
{
aType = Aspect_GT_Circular;
}
else if (anArgNext == "inf" || anArgNext == "infinite")
{
isInfinite = true;
}
else
{
Message::SendFail() << "Syntax error at '" << anArgNext << "'";
@@ -5082,10 +5094,10 @@ static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgV
}
else if (anArgIter + 1 < theArgNb && anArg == "-radius")
{
hasSize = true;
hasRadius = true;
++anArgIter;
aNewSizeXY.SetValues(Draw::Atof(theArgVec[anArgIter]), 0.0);
if (aNewStepXY.x() <= 0.0)
aNewRadius = Draw::Atof(theArgVec[anArgIter]);
if (aNewRadius <= 0.0)
{
Message::SendFail() << "Syntax error: wrong size '" << theArgVec[anArgIter] << "'";
return 1;
@@ -5096,7 +5108,7 @@ static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgV
hasSize = true;
aNewSizeXY.SetValues(Draw::Atof(theArgVec[anArgIter + 1]),
Draw::Atof(theArgVec[anArgIter + 2]));
if (aNewStepXY.x() <= 0.0 || aNewStepXY.y() <= 0.0)
if (aNewSizeXY.x() <= 0.0 || aNewSizeXY.y() <= 0.0)
{
Message::SendFail() << "Syntax error: wrong size '" << theArgVec[anArgIter + 1] << " "
<< theArgVec[anArgIter + 2] << "'";
@@ -5104,6 +5116,59 @@ static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgV
}
anArgIter += 2;
}
else if (anArgIter + 3 < theArgNb && (anArg == "-color"))
{
hasColor = true;
aNewColor = Quantity_Color(Draw::Atof(theArgVec[anArgIter + 1]),
Draw::Atof(theArgVec[anArgIter + 2]),
Draw::Atof(theArgVec[anArgIter + 3]),
Quantity_TOC_RGB);
aGridParams.SetColor(aNewColor);
anArgIter += 3;
}
else if (anArgIter + 3 < theArgNb && (anArg == "-tenthcolor" || anArg == "-accentcolor"))
{
hasTenthColor = true;
aNewTenthColor = Quantity_Color(Draw::Atof(theArgVec[anArgIter + 1]),
Draw::Atof(theArgVec[anArgIter + 2]),
Draw::Atof(theArgVec[anArgIter + 3]),
Quantity_TOC_RGB);
aGridParams.SetAccentColor(aNewTenthColor);
anArgIter += 3;
}
else if (anArgIter + 1 < theArgNb && anArg == "-scale")
{
hasScale = true;
aGridParams.SetScale(Draw::Atof(theArgVec[++anArgIter]));
}
else if (anArgIter + 1 < theArgNb && (anArg == "-linethickness" || anArg == "-thickness"))
{
aGridParams.SetLineThickness(Draw::Atof(theArgVec[++anArgIter]));
}
else if (anArgIter + 1 < theArgNb && anArg == "-background")
{
const int aVal = Draw::Atoi(theArgVec[++anArgIter]);
aGridParams.SetIsBackground(aVal != 0);
}
else if (anArgIter + 1 < theArgNb && anArg == "-drawaxis")
{
const int aVal = Draw::Atoi(theArgVec[++anArgIter]);
aGridParams.SetIsDrawAxis(aVal != 0);
}
else if (anArgIter + 1 < theArgNb && (anArg == "-inf" || anArg == "-infinity"))
{
const int aVal = Draw::Atoi(theArgVec[++anArgIter]);
aGridParams.SetIsInfinity(aVal != 0);
}
else if (anArgIter + 2 < theArgNb && anArg == "-arc")
{
// Angular range for circular grids (radians). Equal start/end = full circle.
hasArc = true;
aNewArcStart = Draw::Atof(theArgVec[anArgIter + 1]);
aNewArcEnd = Draw::Atof(theArgVec[anArgIter + 2]);
aGridParams.SetArcRange(aNewArcStart, aNewArcEnd);
anArgIter += 2;
}
else if (anArg == "r" || anArg == "rect" || anArg == "rectangular")
{
aType = Aspect_GT_Rectangular;
@@ -5120,10 +5185,9 @@ static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgV
{
aMode = Aspect_GDM_Points;
}
else if (anArgIter + 1 >= theArgNb && anArg == "off")
else if (anArg == "off")
{
aViewer->DeactivateGrid();
return 0;
hasInfOff = true;
}
else
{
@@ -5132,6 +5196,119 @@ static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgV
}
}
if (isInfinite && hasInfOff)
{
Message::SendFail("Syntax error: 'off' cannot be combined with '-type inf'");
return 1;
}
if (isInfinite || hasInfOff)
{
if (hasInfOff)
{
aView->GridErase();
}
if (isInfinite)
{
// An infinite grid needs a real Aspect_Grid to back snap selection, so we
// route through ActivateGrid(rect|circ) first and override the display
// with the inf-specific params afterwards. Decide the shape from aType
// (user explicitly asked) or from the presence of circular-only options.
const bool isInfCircular = 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;
if (isInfCircular)
{
// 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;
if (hasStep)
{
aRadiusStep = aNewStepXY.x();
aDivisionCount = int(aNewStepXY.y() > 0 ? aNewStepXY.y() : aDivisionCount);
}
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 (occ::handle<Aspect_CircularGrid> aCircGrid =
occ::down_cast<Aspect_CircularGrid>(aViewer->Grid(true)))
{
aCircGrid->SetArcRange(aNewArcStart, aNewArcEnd);
}
}
aViewer->ActivateGrid(Aspect_GT_Circular, aMode);
aGridParams.SetScale(1.0 / aRadiusStep);
aGridParams.SetScaleY(0.0); // unused in circular mode
aGridParams.SetAngularDivisions(aDivisionCount);
}
else
{
// Rectangular infinite 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).
if (!hasScale)
{
if (hasStep)
{
aGridParams.SetScale(1.0 / aNewStepXY.x());
aGridParams.SetScaleY(1.0 / aNewStepXY.y());
}
else
{
aGridParams.SetScale(1.0);
aGridParams.SetScaleY(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.SetDrawMode(aMode);
aGridParams.SetRotationAngle(aRotAngle);
if (hasSize && !isInfCircular)
{
aGridParams.SetSizeX(aNewSizeXY.x());
aGridParams.SetSizeY(aNewSizeXY.y());
}
if (hasRadius)
{
aGridParams.SetRadius(aNewRadius);
}
if (hasZOffset)
{
aGridParams.SetZOffset(aNewZOffset);
}
aView->GridDisplay(aGridParams);
}
if (hasInfOff && !isInfinite)
{
// plain 'vgrid off' still deactivates the classical grid
aViewer->DeactivateGrid();
}
return 0;
}
if (aType == Aspect_GT_Rectangular)
{
NCollection_Vec2<double> anOrigXY, aStepXY;
@@ -5205,18 +5382,18 @@ static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgV
aRadiusStep,
aDivisionNumber,
aRotAngle);
if (hasSize || hasZOffset)
if (hasSize)
{
Message::SendFail("Syntax error: circular size should be specified as radius");
return 1;
}
if (hasRadius || hasZOffset)
{
double aRadius = 0.0, aZOffset = 0.0;
aViewer->CircularGridGraphicValues(aRadius, aZOffset);
if (hasSize)
if (hasRadius)
{
aRadius = aNewSizeXY.x();
if (aNewSizeXY.y() != 0.0)
{
Message::SendFail("Syntax error: circular size should be specified as radius");
return 1;
}
aRadius = aNewRadius;
}
if (hasZOffset)
{
@@ -5224,6 +5401,37 @@ static int VGrid(Draw_Interpretor& /*theDI*/, int theArgNb, const char** theArgV
}
aViewer->SetCircularGridGraphicValues(aRadius, aZOffset);
}
// Angular range must hit the Aspect_CircularGrid base before ActivateGrid
// fires syncViews - syncViews copies AngleStart/End from the grid, so any
// value set earlier only on aGridParams would get overwritten.
if (hasArc)
{
if (occ::handle<Aspect_CircularGrid> aCircGrid =
occ::down_cast<Aspect_CircularGrid>(aViewer->Grid(true)))
{
aCircGrid->SetArcRange(aNewArcStart, aNewArcEnd);
}
}
}
// Apply -color / -tenthColor to the active grid so V3d syncViews picks
// them up on the next display. Raw -type inf users still drive these
// through aGridParams directly (set earlier in the parser loop).
if (hasColor || hasTenthColor)
{
if (occ::handle<Aspect_Grid> aGrid = aViewer->Grid(true))
{
Quantity_Color aMainColor, aTenthColor;
aGrid->Colors(aMainColor, aTenthColor);
if (hasColor)
{
aMainColor = aNewColor;
}
if (hasTenthColor)
{
aTenthColor = aNewTenthColor;
}
aGrid->SetColors(aMainColor, aTenthColor);
}
}
aViewer->ActivateGrid(aType, aMode);
return 0;
@@ -13951,9 +14159,16 @@ vlayerline x1 y1 x2 y2 [linewidth=0.5] [linetype=0] [transparency=1.0]
)" /* [vlayerline] */);
addCmd("vgrid", VGrid, /* [vgrid] */ R"(
vgrid [off] [-type {rect|circ}] [-mode {line|point}] [-origin X Y] [-rotAngle Angle] [-zoffset DZ]
vgrid [off] [-type {rect|circ|inf}] [-mode {line|point}] [-origin X Y] [-rotAngle Angle] [-zoffset DZ]
[-step X Y] [-size DX DY]
[-step StepRadius NbDivisions] [-radius Radius]
[-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}] [-inf {0|1}]
'-type inf' activates a shader-rendered infinite (or bounded) grid; combine
with '-size DX DY' for a rectangle, '-radius R' for a disc, '-arc S E' for a
wedge. '-color', '-tenthColor', '-scale', '-lineThickness' drive every grid
since rendering is shader-based. '-background', '-drawAxis', '-inf {0|1}' are
inf-mode only. 'off' deactivates the grid and cannot be combined with '-type inf'.
)" /* [vgrid] */);
addCmd("vpriviledgedplane", VPriviledgedPlane, /* [vpriviledgedplane] */ R"(
@@ -174,6 +174,7 @@ void OpenGl_ShaderManager::clear()
myBlitPrograms[1].Init(occ::handle<OpenGl_ShaderProgram>());
myBoundBoxProgram.Nullify();
myBoundBoxVertBuffer.Nullify();
myGridProgram.Nullify();
for (int aModeIter = 0; aModeIter < Graphic3d_StereoMode_NB; ++aModeIter)
{
myStereoPrograms[aModeIter].Nullify();
@@ -1476,6 +1477,36 @@ const occ::handle<Graphic3d_ShaderProgram>& OpenGl_ShaderManager::GetColoredQuad
//=================================================================================================
bool OpenGl_ShaderManager::BindGridProgram()
{
if (myGridProgram.IsNull())
{
occ::handle<Graphic3d_ShaderProgram> aProgramSrc = getGridProgram();
TCollection_AsciiString aKey;
if (!Create(aProgramSrc, aKey, myGridProgram))
{
myContext->PushMessage(GL_DEBUG_SOURCE_APPLICATION,
GL_DEBUG_TYPE_ERROR,
0,
GL_DEBUG_SEVERITY_HIGH,
"Error: infinite-grid shader failed to compile/link");
myGridProgram = new OpenGl_ShaderProgram(); // mark as invalid so we don't retry every frame
return false;
}
}
if (!myGridProgram->IsValid())
{
return false;
}
// Route through bindProgramWithState so OCCT built-in uniforms (occProjectionMatrix,
// occWorldViewMatrix, occModelWorldMatrix, their inverses, viewport, etc.) are uploaded
// to the grid program. The shader's unproject() path depends on these matrices; without
// PushState they stay at zero and every fragment is discarded (the grid never draws).
return bindProgramWithState(myGridProgram, Graphic3d_TypeOfShadingModel_Unlit);
}
//=================================================================================================
bool OpenGl_ShaderManager::bindProgramWithState(const occ::handle<OpenGl_ShaderProgram>& theProgram,
Graphic3d_TypeOfShadingModel theShadingModel)
{
@@ -254,6 +254,10 @@ public:
//! Generates shader program to render correctly colored quad.
Standard_EXPORT const occ::handle<Graphic3d_ShaderProgram>& GetColoredQuadProgram();
//! Compile (once) and bind the infinite-grid shader program.
//! Returns FALSE if shader compilation fails or the GAPI is not supported.
Standard_EXPORT bool BindGridProgram();
//! Resets PBR shading models to corresponding non-PBR ones if PBR is not allowed.
static Graphic3d_TypeOfShadingModel PBRShadingModelFallback(
Graphic3d_TypeOfShadingModel theShadingModel,
@@ -803,6 +807,7 @@ protected:
occ::handle<Graphic3d_ShaderProgram> myBgCubeMapProgram; //!< program for background cubemap rendering
occ::handle<Graphic3d_ShaderProgram> myBgSkydomeProgram; //!< program for background cubemap rendering
occ::handle<Graphic3d_ShaderProgram> myColoredQuadProgram; //!< program for correct quad rendering
occ::handle<OpenGl_ShaderProgram> myGridProgram; //!< shader program for infinite grid
occ::handle<OpenGl_ShaderProgram> myStereoPrograms[Graphic3d_StereoMode_NB]; //!< standard stereo programs
@@ -15,12 +15,15 @@
#include <OpenGl_View.hxx>
#include <cmath>
#include <Aspect_NeutralWindow.hxx>
#include <Aspect_RenderingContext.hxx>
#include <Aspect_XRSession.hxx>
#include <Graphic3d_AspectFillArea3d.hxx>
#include <Graphic3d_Texture2D.hxx>
#include <Graphic3d_TextureEnv.hxx>
#include <Graphic3d_TransformUtils.hxx>
#include <Image_AlienPixMap.hxx>
#include <OpenGl_ArbFBO.hxx>
#include <OpenGl_BackgroundArray.hxx>
@@ -28,6 +31,7 @@
#include <OpenGl_DepthPeeling.hxx>
#include <OpenGl_FrameBuffer.hxx>
#include <OpenGl_GlCore11.hxx>
#include <OpenGl_GlCore32.hxx>
#include <OpenGl_GraduatedTrihedron.hxx>
#include <OpenGl_GraphicDriver.hxx>
#include <OpenGl_RenderFilter.hxx>
@@ -134,6 +138,8 @@ OpenGl_View::OpenGl_View(const occ::handle<Graphic3d_StructureManager>& theMgr,
myTextureParams(new OpenGl_Aspects()),
myCubeMapParams(new OpenGl_Aspects()),
myColoredQuadParams(new OpenGl_Aspects()),
myGridVao(0),
myToShowGrid(false),
myPBREnvState(OpenGl_PBREnvState_NONEXISTENT),
myPBREnvRequest(false),
// ray-tracing fields initialization
@@ -275,6 +281,13 @@ void OpenGl_View::ReleaseGlResources(const occ::handle<OpenGl_Context>& theCtx)
{
myPBREnvironment->Release(theCtx.get());
}
if (myGridVao != 0 && !theCtx.IsNull() && theCtx->core30 != nullptr)
{
theCtx->core30->glDeleteVertexArrays(1, &myGridVao);
}
myGridVao = 0;
ReleaseXR();
}
@@ -2580,6 +2593,12 @@ void OpenGl_View::render(Graphic3d_Camera::Projection theProjection,
myWorkspace->SetEnvironmentTexture(occ::handle<OpenGl_TextureSet>());
// Render shader-based infinite grid on top of opaque scene, before trihedron.
if (!theToDrawImmediate)
{
renderGrid();
}
// ===============================
// Step 4: Trihedron
// ===============================
@@ -3545,3 +3564,363 @@ void OpenGl_View::updatePBREnvironment(const occ::handle<OpenGl_Context>& theCtx
aGlTextureSet.Nullify();
OpenGl_Element::Destroy(theCtx.get(), aTmpGlAspects);
}
//=================================================================================================
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())
{
myGridRefViewMatrix = aCtx->WorldViewState.Current();
}
}
}
//=================================================================================================
void OpenGl_View::GridErase()
{
myToShowGrid = false;
}
//=================================================================================================
void OpenGl_View::renderGrid()
{
if (!myToShowGrid || myGridParams.DrawMode() == Aspect_GDM_None)
{
return;
}
const occ::handle<OpenGl_Context>& aContext = myWorkspace->GetGlContext();
if (aContext.IsNull())
{
return;
}
if (aContext->core30 == nullptr)
{
// The shader grid requires GL 3.0+ / GLES 3.0+ (VAO + gl_VertexID + gl_FragDepth).
// 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.
static bool THE_GRID_GL30_WARNED = false;
if (!THE_GRID_GL30_WARNED)
{
THE_GRID_GL30_WARNED = true;
aContext->PushMessage(GL_DEBUG_SOURCE_APPLICATION,
GL_DEBUG_TYPE_OTHER,
0,
GL_DEBUG_SEVERITY_MEDIUM,
"Warning: shader-based grid requires GL 3.0 / GLES 3.0 or later; "
"grid will not be rendered on this driver. Snap selection remains "
"functional.");
}
return;
}
const occ::handle<Graphic3d_Camera>& aCamera = aContext->Camera();
if (aCamera.IsNull())
{
return;
}
if (myGridVao == 0)
{
aContext->core30->glGenVertexArrays(1, &myGridVao);
if (myGridVao == 0)
{
myToShowGrid = false;
return;
}
}
GLint aPrevVao = 0;
aContext->core11fwd->glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &aPrevVao);
GLboolean wasDepthTest = aContext->core11fwd->glIsEnabled(GL_DEPTH_TEST);
GLboolean wasBlend = aContext->core11fwd->glIsEnabled(GL_BLEND);
GLboolean wasCullFace = aContext->core11fwd->glIsEnabled(GL_CULL_FACE);
GLboolean wasDepthWrite = GL_TRUE;
aContext->core11fwd->glGetBooleanv(GL_DEPTH_WRITEMASK, &wasDepthWrite);
GLint aPrevDepthFunc = GL_LESS;
aContext->core11fwd->glGetIntegerv(GL_DEPTH_FUNC, &aPrevDepthFunc);
GLint aPrevBlendSrcRgb = GL_ONE, aPrevBlendDstRgb = GL_ZERO;
GLint aPrevBlendSrcA = GL_ONE, aPrevBlendDstA = GL_ZERO;
aContext->core11fwd->glGetIntegerv(GL_BLEND_SRC_RGB, &aPrevBlendSrcRgb);
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->ApplyWorldViewMatrix();
aContext->core11fwd->glEnable(GL_DEPTH_TEST);
aContext->core11fwd->glDepthFunc(GL_LESS);
aContext->core11fwd->glDepthMask(GL_TRUE);
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
// (leftover from prior scene render with solid interiors), every triangle
// 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.IsInfinity())
{
const double aCamScale = aCamera->Scale();
aScaleX = 10.0 / std::pow(10.0, std::floor(std::log10(std::max(aCamScale, 1.0))) + 1.0);
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);
// Bounded work area (HalfSizeX, HalfSizeY, Radius). 0 = unbounded along that axis.
const float aHalfX = myGridParams.SizeX() > 0.0 ? float(myGridParams.SizeX() * 0.5) : 0.0f;
const float aHalfY = myGridParams.SizeY() > 0.0 ? float(myGridParams.SizeY() * 0.5) : 0.0f;
const float aRadius = myGridParams.Radius() > 0.0 ? float(myGridParams.Radius()) : 0.0f;
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);
// In-plane rotation: rotate the plane's X/Y basis around the plane normal
// so the grid lines follow the requested RotationAngle. Sign matches
// V3d_View::SetGrid's Trsf2 so snap (V3d_View::Compute) and the drawn
// grid use the same basis: aGridX = cos*planeX - sin*planeY,
// aGridY = sin*planeX + cos*planeY.
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_XYZ aXRotated = aRawX.XYZ() * aCosA - aRawY.XYZ() * aSinA;
const gp_XYZ aYRotated = aRawX.XYZ() * aSinA + aRawY.XYZ() * aCosA;
// 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 gp_Dir aNDir = myGridPlane.Direction();
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);
aProg->SetUniform(aContext,
"uPlaneOrigin",
NCollection_Vec3<float>((float)aPlaneOrigin.X(),
(float)aPlaneOrigin.Y(),
(float)aPlaneOrigin.Z()));
aProg->SetUniform(
aContext,
"uPlaneX",
NCollection_Vec3<float>((float)aXRotated.X(), (float)aXRotated.Y(), (float)aXRotated.Z()));
aProg->SetUniform(
aContext,
"uPlaneY",
NCollection_Vec3<float>((float)aYRotated.X(), (float)aYRotated.Y(), (float)aYRotated.Z()));
// Build a stable per-frame rectangular-grid reference point in plane-local
// coordinates. This 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())
{
const int* aViewport = aContext->Viewport();
if (aViewport != nullptr)
{
const float aWinX = float(aViewport[0]) + float(aViewport[2]) * 0.5f;
const float aWinY = float(aViewport[1]) + float(aViewport[3]) * 0.5f;
float aNearX = 0.0f, aNearY = 0.0f, aNearZ = 0.0f;
float aFarX = 0.0f, aFarY = 0.0f, aFarZ = 0.0f;
const bool isNearOk =
Graphic3d_TransformUtils::UnProject<float>(aWinX,
aWinY,
0.0f,
aContext->WorldViewState.Current(),
aContext->ProjectionState.Current(),
aViewport,
aNearX,
aNearY,
aNearZ);
const bool isFarOk =
Graphic3d_TransformUtils::UnProject<float>(aWinX,
aWinY,
1.0f,
aContext->WorldViewState.Current(),
aContext->ProjectionState.Current(),
aViewport,
aFarX,
aFarY,
aFarZ);
if (isNearOk && isFarOk)
{
const NCollection_Vec3<float> aNearP(aNearX, aNearY, aNearZ);
const NCollection_Vec3<float> aFarP(aFarX, aFarY, aFarZ);
const NCollection_Vec3<float> aDir = aFarP - aNearP;
const NCollection_Vec3<float> aPlaneN((float)aNDir.X(),
(float)aNDir.Y(),
(float)aNDir.Z());
const float aDenom = aPlaneN.Dot(aDir);
if (std::abs(aDenom) > 1.0e-6f)
{
const NCollection_Vec3<float> aPlaneOriginV((float)aPlaneOrigin.X(),
(float)aPlaneOrigin.Y(),
(float)aPlaneOrigin.Z());
const float aT = aPlaneN.Dot(aPlaneOriginV - aNearP) / aDenom;
const NCollection_Vec3<float> aHit = aNearP + aDir * aT;
const NCollection_Vec3<float> aLocal3 = aHit - aPlaneOriginV;
const NCollection_Vec3<float> aPlaneX((float)aXRotated.X(),
(float)aXRotated.Y(),
(float)aXRotated.Z());
const NCollection_Vec3<float> aPlaneY((float)aYRotated.X(),
(float)aYRotated.Y(),
(float)aYRotated.Z());
aStableRefLocal.SetValues(aLocal3.Dot(aPlaneX), aLocal3.Dot(aPlaneY));
aHasStableRef = 1;
}
}
}
}
aProg->SetUniform(aContext, "uStableRefLocal", aStableRefLocal);
aProg->SetUniform(aContext, "uHasStableRef", aHasStableRef);
aProg->SetUniform(
aContext,
"uPlaneN",
NCollection_Vec3<float>((float)aNDir.X(), (float)aNDir.Y(), (float)aNDir.Z()));
aContext->core11fwd->glDrawArrays(GL_TRIANGLES, 0, 6);
}
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)
{
aContext->core11fwd->glDisable(GL_DEPTH_TEST);
}
aContext->core11fwd->glDepthFunc(aPrevDepthFunc);
aContext->core11fwd->glDepthMask(wasDepthWrite);
if (wasBlend == GL_FALSE)
{
aContext->core11fwd->glDisable(GL_BLEND);
}
aContext->core15fwd->glBlendFuncSeparate(aPrevBlendSrcRgb,
aPrevBlendDstRgb,
aPrevBlendSrcA,
aPrevBlendDstA);
if (wasCullFace == GL_TRUE)
{
aContext->core11fwd->glEnable(GL_CULL_FACE);
}
if (hasDepthClamp && !wasDepthClamp)
{
aContext->core11fwd->glDisable(GL_DEPTH_CLAMP);
}
}
@@ -220,9 +220,15 @@ public:
//! Enables or disables IBL (Image Based Lighting) from background cubemap.
//! Has no effect if PBR is not used.
//! @param[in] theToEnableIBL enable or disable IBL from background cubemap
//! @param[in] theToUpdate redraw the view
Standard_EXPORT void SetImageBasedLighting(bool theToEnableIBL) override;
//! Display a shader-rendered infinite grid on the given plane.
Standard_EXPORT void GridDisplay(const Aspect_GridParams& theParams,
const gp_Ax3& thePlane) override;
//! Erase the shader-rendered infinite grid.
Standard_EXPORT void GridErase() override;
//! Returns number of mipmap levels used in specular IBL map.
//! 0 if PBR environment is not created.
Standard_EXPORT unsigned int SpecIBLMapLevels() const;
@@ -424,6 +430,10 @@ protected: //! @name Rendering of GL graphics (with prepared drawing buffer).
//! Renders frame statistics.
void renderFrameStats();
//! Render the shader-based infinite grid.
//! No-op unless GridDisplay() has been called and the GAPI supports the grid shader.
void renderGrid();
private:
//! Adds the structure to display lists of the view.
Standard_EXPORT void displayStructure(const occ::handle<Graphic3d_CStructure>& theStructure,
@@ -529,6 +539,11 @@ 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 infinite grid
gp_Ax3 myGridPlane; //!< grid plane in world coordinates
NCollection_Mat4<float> myGridRefViewMatrix; //!< worldview captured at GridDisplay() for pan/rotate compensation
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;
@@ -26,7 +26,14 @@ Aspect_CircularGrid::Aspect_CircularGrid(const double aRadiusStep,
const double aRotationAngle)
: Aspect_Grid(anXOrigin, anYOrigin, aRotationAngle),
myRadiusStep(aRadiusStep),
myDivisionNumber(aDivisionNumber)
myDivisionNumber(aDivisionNumber),
myAlpha(0.0),
myA1(0.0),
myB1(0.0),
myRadius(0.0),
myZOffset(0.0),
myAngleStart(0.0),
myAngleEnd(0.0)
{
}
@@ -170,6 +177,36 @@ void Aspect_CircularGrid::Init()
myB1 = std::sin(myAlpha);
}
void Aspect_CircularGrid::SetRadius(const double theRadius)
{
Standard_NegativeValue_Raise_if(theRadius < 0.0, "invalid grid radius");
if (myRadius != theRadius)
{
myRadius = theRadius;
UpdateDisplay();
}
}
void Aspect_CircularGrid::SetZOffset(const double theOffset)
{
if (myZOffset != theOffset)
{
myZOffset = theOffset;
UpdateDisplay();
}
}
void Aspect_CircularGrid::SetArcRange(const double theStart, const double theEnd)
{
if (myAngleStart == theStart && myAngleEnd == theEnd)
{
return;
}
myAngleStart = theStart;
myAngleEnd = theEnd;
UpdateDisplay();
}
//=================================================================================================
void Aspect_CircularGrid::DumpJson(Standard_OStream& theOStream, int theDepth) const
@@ -183,4 +220,8 @@ void Aspect_CircularGrid::DumpJson(Standard_OStream& theOStream, int theDepth) c
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myAlpha)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myA1)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myB1)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myRadius)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myZOffset)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myAngleStart)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myAngleEnd)
}
@@ -56,6 +56,36 @@ public:
//! returns the x step of the grid.
Standard_EXPORT int DivisionNumber() const;
//! Set the circular grid radius (plane-local units). 0.0 (default) means
//! unbounded - the shader draws the grid to the horizon.
Standard_EXPORT void SetRadius(const double theRadius);
//! Return the bounded radius. 0.0 means unbounded.
double Radius() const { return myRadius; }
//! Set signed offset along the plane normal for display only; snap math
//! stays on the plane. Use a small negative value to avoid z-fighting with
//! coplanar geometry.
Standard_EXPORT void SetZOffset(const double theOffset);
//! Return the display-time Z-offset along the plane normal.
double ZOffset() const { return myZOffset; }
//! Restrict the grid to an angular wedge, walking counter-clockwise from
//! @p theStart to @p theEnd (radians, measured from the rotated plane X
//! axis). Setting both values equal (e.g. both 0.0) returns to full-circle
//! rendering - the sentinel used for unbounded.
Standard_EXPORT void SetArcRange(const double theStart, const double theEnd);
//! Return the arc start angle (radians). Meaningful only when IsArc() is true.
double AngleStart() const { return myAngleStart; }
//! Return the arc end angle (radians). Meaningful only when IsArc() is true.
double AngleEnd() const { return myAngleEnd; }
//! Return TRUE when the grid is restricted to an angular wedge.
bool IsArc() const { return myAngleStart != myAngleEnd; }
Standard_EXPORT void Init() override;
//! Dumps the content of me into the stream
@@ -67,6 +97,10 @@ private:
double myAlpha;
double myA1;
double myB1;
double myRadius;
double myZOffset;
double myAngleStart;
double myAngleEnd;
};
#endif // _Aspect_CircularGrid_HeaderFile
@@ -0,0 +1,266 @@
// 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 _Aspect_GridParams_HeaderFile
#define _Aspect_GridParams_HeaderFile
#include <Standard.hxx>
#include <Standard_DefineAlloc.hxx>
#include <Standard_NegativeValue.hxx>
#include <Aspect_GridDrawMode.hxx>
#include <Quantity_Color.hxx>
#include <gp_Pnt.hxx>
//! Render-only appearance parameters for a shader-rendered grid.
//!
//! Drives a screen-space quad intersected with a grid plane in the fragment
//! shader (see Graphic3d_ShaderManager::getGridProgram). This is a pure value
//! struct; it does NOT own any snap state. The authoritative grid geometry for
//! snap selection still lives on Aspect_RectangularGrid / Aspect_CircularGrid,
//! and V3d_{Rectangular,Circular}Grid::syncViews produces an Aspect_GridParams
//! from the grid on every display. If you introduce a new field here that has
//! a matching concept on Aspect_Grid, update syncViews too so both layers stay
//! in sync.
class Aspect_GridParams
{
public:
DEFINE_STANDARD_ALLOC
//! Construct with sensible defaults: grey lines on the plane origin with
//! axis coloring enabled, 1/100 plane-unit spacing, overlay mode,
//! unbounded in extent and radius.
Aspect_GridParams()
: myColor(Quantity_NOC_GRAY70),
myAccentColor(Quantity_NOC_GRAY50),
myOrigin(0.0, 0.0, 0.0),
myScale(0.01),
myScaleY(0.0),
myAccentScaleX(0.0),
myAccentScaleY(0.0),
myAccentAngularScale(0.0),
myLineThickness(0.01),
myRotationAngle(0.0),
mySizeX(0.0),
mySizeY(0.0),
myRadius(0.0),
myZOffset(0.0),
myAngleStart(0.0),
myAngleEnd(0.0),
myAngularDivisions(0),
myDrawMode(Aspect_GDM_Lines),
myIsBackground(false),
myIsDrawAxis(true),
myIsInfinity(false)
{
}
//! Return grid line color.
const Quantity_Color& Color() const { return myColor; }
//! Set grid line color.
void SetColor(const Quantity_Color& theColor) { myColor = theColor; }
//! Return local offset of the grid origin within the plane.
const gp_Pnt& Origin() const { return myOrigin; }
//! Set local offset of the grid origin within the plane.
void SetOrigin(const gp_Pnt& theOrigin) { myOrigin = theOrigin; }
//! Return accent color used by the classical V3d grid emulation path.
const Quantity_Color& AccentColor() const { return myAccentColor; }
//! Set accent color used by the classical V3d grid emulation path.
void SetAccentColor(const Quantity_Color& theColor) { myAccentColor = theColor; }
//! Return accent overlay scale along the plane X/radial direction.
//! Zero disables the accent layer on that axis.
double AccentScaleX() const { return myAccentScaleX; }
//! Set accent overlay scale along the plane X/radial direction.
void SetAccentScaleX(const double theScale) { myAccentScaleX = theScale; }
//! Return accent overlay scale along the plane Y direction.
//! Zero disables the accent layer on that axis.
double AccentScaleY() const { return myAccentScaleY; }
//! Set accent overlay scale along the plane Y direction.
void SetAccentScaleY(const double theScale) { myAccentScaleY = theScale; }
//! Return accent overlay angular scale for circular-grid spokes.
//! Zero disables the angular accent layer.
double AccentAngularScale() const { return myAccentAngularScale; }
//! Set accent overlay angular scale for circular-grid spokes.
void SetAccentAngularScale(const double theScale) { myAccentAngularScale = theScale; }
//! Return major-grid scale factor along the plane X direction (cells per plane unit).
double Scale() const { return myScale; }
//! Set major-grid scale factor along the plane X direction (cells per plane unit).
//! Must be non-negative; zero is a valid "unused" sentinel.
void SetScale(const double theScale)
{
Standard_NegativeValue_Raise_if(theScale < 0.0, "invalid grid scale");
myScale = theScale;
}
//! Return explicit Y-direction scale. When 0.0, renderer falls back to Scale() (isotropic).
double ScaleY() const { return myScaleY; }
//! Set explicit Y-direction scale. Pass 0.0 to mirror Scale() (isotropic, default).
void SetScaleY(const double theScaleY)
{
Standard_NegativeValue_Raise_if(theScaleY < 0.0, "invalid grid Y-scale");
myScaleY = theScaleY;
}
//! Effective Y-direction scale actually consumed by the renderer.
double EffectiveScaleY() const { return myScaleY > 0.0 ? myScaleY : myScale; }
//! Return line thickness in plane units (minimum pixel-space line width is derived from fwidth).
double LineThickness() const { return myLineThickness; }
//! Set line thickness in plane units.
void SetLineThickness(const double theThickness)
{
Standard_NegativeValue_Raise_if(theThickness < 0.0, "invalid grid line thickness");
myLineThickness = theThickness;
}
//! Return in-plane rotation angle (radians) applied to the grid axes around the plane normal.
double RotationAngle() const { return myRotationAngle; }
//! Set in-plane rotation angle (radians) applied to the grid axes around the plane normal.
void SetRotationAngle(const double theAngle) { myRotationAngle = theAngle; }
//! Return the angular subdivision count of the half-circle for circular grids.
//! Zero means rectangular grid (default); any positive value switches the
//! renderer to polar rings (Scale -> radial step) and spokes at pi/N rad.
int AngularDivisions() const { return myAngularDivisions; }
//! Set angular subdivision count (0 = rectangular grid, N>0 = circular with N spokes per 180
//! deg).
void SetAngularDivisions(const int theDivisions) { myAngularDivisions = theDivisions; }
//! Return TRUE when the parameters describe a circular (polar) grid.
bool IsCircular() const { return myAngularDivisions > 0; }
//! Return rectangular bounded extent along plane X; 0.0 means unbounded.
double SizeX() const { return mySizeX; }
//! Set rectangular bounded extent along plane X; 0.0 means unbounded.
void SetSizeX(const double theSize)
{
Standard_NegativeValue_Raise_if(theSize < 0.0, "invalid grid X-size");
mySizeX = theSize;
}
//! Return rectangular bounded extent along plane Y; 0.0 means unbounded.
double SizeY() const { return mySizeY; }
//! Set rectangular bounded extent along plane Y; 0.0 means unbounded.
void SetSizeY(const double theSize)
{
Standard_NegativeValue_Raise_if(theSize < 0.0, "invalid grid Y-size");
mySizeY = theSize;
}
//! Return circular bounded radius; 0.0 means unbounded.
double Radius() const { return myRadius; }
//! Set circular bounded radius; 0.0 means unbounded.
void SetRadius(const double theRadius)
{
Standard_NegativeValue_Raise_if(theRadius < 0.0, "invalid grid radius");
myRadius = theRadius;
}
//! 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).
void SetZOffset(const double theOffset) { myZOffset = theOffset; }
//! Return arc start angle (radians). Meaningful only when IsArc() is true.
double AngleStart() const { return myAngleStart; }
//! Return arc end angle (radians). Meaningful only when IsArc() is true.
double AngleEnd() const { return myAngleEnd; }
//! Restrict the circular grid to an angular wedge [start, end], walking CCW.
//! Equal start and end (e.g. 0.0 and 0.0) returns to full-circle rendering.
void SetArcRange(const double theStart, const double theEnd)
{
myAngleStart = theStart;
myAngleEnd = theEnd;
}
//! Return TRUE when the parameters describe a bounded rectangle or disc.
bool IsBounded() const { return mySizeX > 0.0 || mySizeY > 0.0 || myRadius > 0.0; }
//! Return TRUE when the circular grid is restricted to a sub-arc.
bool IsArc() const { return myAngleStart != myAngleEnd; }
//! Return draw mode: lines, points at grid intersections, or none.
Aspect_GridDrawMode DrawMode() const { return myDrawMode; }
//! Set draw mode. Aspect_GDM_None suppresses rendering entirely; Points draws
//! dots at grid-line intersections, Lines (default) draws the full grid.
void SetDrawMode(const Aspect_GridDrawMode theMode) { myDrawMode = theMode; }
//! Return TRUE if grid is drawn as a view-space background (behind all geometry).
bool IsBackground() const { return myIsBackground; }
//! Set background-mode rendering on/off.
void SetIsBackground(const bool theIsBackground) { myIsBackground = theIsBackground; }
//! Return TRUE if axis lines on the grid plane are drawn in red/green/blue.
bool IsDrawAxis() const { return myIsDrawAxis; }
//! Set axis coloring on/off.
void SetIsDrawAxis(const bool theIsDrawAxis) { myIsDrawAxis = theIsDrawAxis; }
//! Return TRUE if the scale adapts to camera zoom to keep an apparent grid density.
bool IsInfinity() const { return myIsInfinity; }
//! Set camera-adaptive scale on/off. When enabled, Scale() is ignored in favour
//! of a derived value based on camera distance.
void SetIsInfinity(const bool theIsInfinity) { myIsInfinity = theIsInfinity; }
private:
Quantity_Color myColor;
Quantity_Color myAccentColor;
gp_Pnt myOrigin;
double myScale;
double myScaleY;
double myAccentScaleX;
double myAccentScaleY;
double myAccentAngularScale;
double myLineThickness;
double myRotationAngle;
double mySizeX;
double mySizeY;
double myRadius;
double myZOffset;
double myAngleStart;
double myAngleEnd;
int myAngularDivisions;
Aspect_GridDrawMode myDrawMode;
bool myIsBackground;
bool myIsDrawAxis;
bool myIsInfinity;
};
#endif // _Aspect_GridParams_HeaderFile
@@ -30,7 +30,10 @@ Aspect_RectangularGrid::Aspect_RectangularGrid(const double aXStep,
myXStep(aXStep),
myYStep(aYStep),
myFirstAngle(aFirstAngle),
mySecondAngle(aSecondAngle)
mySecondAngle(aSecondAngle),
mySizeX(0.0),
mySizeY(0.0),
myZOffset(0.0)
{
Standard_NumericError_Raise_if(!CheckAngle(aFirstAngle, mySecondAngle), "networks are parallel");
@@ -68,6 +71,35 @@ void Aspect_RectangularGrid::SetAngle(const double anAngle1, const double anAngl
UpdateDisplay();
}
void Aspect_RectangularGrid::SetSizeX(const double theSize)
{
Standard_NegativeValue_Raise_if(theSize < 0.0, "invalid grid size X");
if (mySizeX != theSize)
{
mySizeX = theSize;
UpdateDisplay();
}
}
void Aspect_RectangularGrid::SetSizeY(const double theSize)
{
Standard_NegativeValue_Raise_if(theSize < 0.0, "invalid grid size Y");
if (mySizeY != theSize)
{
mySizeY = theSize;
UpdateDisplay();
}
}
void Aspect_RectangularGrid::SetZOffset(const double theOffset)
{
if (myZOffset != theOffset)
{
myZOffset = theOffset;
UpdateDisplay();
}
}
void Aspect_RectangularGrid::SetGridValues(const double theXOrigin,
const double theYOrigin,
const double theXStep,
@@ -186,6 +218,9 @@ void Aspect_RectangularGrid::DumpJson(Standard_OStream& theOStream, int theDepth
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myYStep)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myFirstAngle)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, mySecondAngle)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, mySizeX)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, mySizeY)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myZOffset)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, a1)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, b1)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, c1)
@@ -72,6 +72,28 @@ public:
//! returns the y Angle of the grid, relatively to the vertical.
Standard_EXPORT double SecondAngle() const;
//! Set full extent of the bounded grid along the plane X direction (plane-local units).
//! 0.0 (default) means unbounded - the shader draws the grid to the horizon.
Standard_EXPORT void SetSizeX(const double theSize);
//! Return the bounded-region extent along plane X. 0.0 means unbounded.
double SizeX() const { return mySizeX; }
//! Set full extent of the bounded grid along the plane Y direction (plane-local units).
//! 0.0 (default) means unbounded.
Standard_EXPORT void SetSizeY(const double theSize);
//! Return the bounded-region extent along plane Y. 0.0 means unbounded.
double SizeY() const { return mySizeY; }
//! Set signed offset (plane-local units) applied along the plane normal for
//! display only - snap math stays on the plane. Use a small negative value
//! to push the grid slightly below coplanar geometry and avoid z-fighting.
Standard_EXPORT void SetZOffset(const double theOffset);
//! Return the display-time Z-offset along the plane normal.
double ZOffset() const { return myZOffset; }
Standard_EXPORT void Init() override;
//! Dumps the content of me into the stream
@@ -85,6 +107,9 @@ private:
double myYStep;
double myFirstAngle;
double mySecondAngle;
double mySizeX;
double mySizeY;
double myZOffset;
double a1;
double b1;
double c1;
@@ -29,6 +29,7 @@ set(OCCT_Aspect_FILES
Aspect_Grid.cxx
Aspect_Grid.hxx
Aspect_GridDrawMode.hxx
Aspect_GridParams.hxx
Aspect_GridType.hxx
Aspect_NeutralWindow.cxx
Aspect_NeutralWindow.hxx
@@ -0,0 +1,266 @@
// 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 <Aspect_GridParams.hxx>
#include <Precision.hxx>
#include <Quantity_Color.hxx>
#include <Standard_NegativeValue.hxx>
#include <gp_Pnt.hxx>
#include <gtest/gtest.h>
TEST(Aspect_GridParamsTest, Defaults_AreReasonable)
{
Aspect_GridParams aParams;
EXPECT_EQ(Quantity_NOC_GRAY70, aParams.Color().Name()) << "default color is grey";
EXPECT_EQ(Quantity_NOC_GRAY50, aParams.AccentColor().Name())
<< "default accent color is darker grey";
EXPECT_DOUBLE_EQ(0.01, aParams.Scale());
EXPECT_DOUBLE_EQ(0.0, aParams.ScaleY()) << "default ScaleY is 0 (isotropic sentinel)";
EXPECT_DOUBLE_EQ(0.01, aParams.EffectiveScaleY()) << "isotropic: EffectiveScaleY mirrors Scale";
EXPECT_DOUBLE_EQ(0.0, aParams.AccentScaleX());
EXPECT_DOUBLE_EQ(0.0, aParams.AccentScaleY());
EXPECT_DOUBLE_EQ(0.0, aParams.AccentAngularScale());
EXPECT_DOUBLE_EQ(0.01, aParams.LineThickness());
EXPECT_DOUBLE_EQ(0.0, aParams.RotationAngle());
EXPECT_EQ(0, aParams.AngularDivisions()) << "default grid is rectangular";
EXPECT_FALSE(aParams.IsCircular());
EXPECT_EQ(Aspect_GDM_Lines, aParams.DrawMode());
EXPECT_FALSE(aParams.IsBackground());
EXPECT_TRUE(aParams.IsDrawAxis());
EXPECT_FALSE(aParams.IsInfinity());
EXPECT_NEAR(0.0, aParams.Origin().X(), Precision::Confusion());
EXPECT_NEAR(0.0, aParams.Origin().Y(), Precision::Confusion());
EXPECT_NEAR(0.0, aParams.Origin().Z(), Precision::Confusion());
EXPECT_DOUBLE_EQ(0.0, aParams.SizeX());
EXPECT_DOUBLE_EQ(0.0, aParams.SizeY());
EXPECT_DOUBLE_EQ(0.0, aParams.Radius());
EXPECT_DOUBLE_EQ(0.0, aParams.ZOffset());
EXPECT_DOUBLE_EQ(0.0, aParams.AngleStart());
EXPECT_DOUBLE_EQ(0.0, aParams.AngleEnd());
EXPECT_FALSE(aParams.IsBounded());
EXPECT_FALSE(aParams.IsArc());
}
TEST(Aspect_GridParamsTest, Bounds_RoundTrip)
{
Aspect_GridParams aParams;
EXPECT_FALSE(aParams.IsBounded());
aParams.SetSizeX(10.0);
EXPECT_DOUBLE_EQ(10.0, aParams.SizeX());
EXPECT_TRUE(aParams.IsBounded());
aParams.SetSizeX(0.0);
EXPECT_FALSE(aParams.IsBounded())
<< "clearing SizeX returns to unbounded when other bounds are 0";
aParams.SetSizeY(5.0);
EXPECT_TRUE(aParams.IsBounded());
aParams.SetSizeY(0.0);
aParams.SetRadius(3.0);
EXPECT_DOUBLE_EQ(3.0, aParams.Radius());
EXPECT_TRUE(aParams.IsBounded());
aParams.SetRadius(0.0);
EXPECT_FALSE(aParams.IsBounded());
aParams.SetZOffset(-0.01);
EXPECT_DOUBLE_EQ(-0.01, aParams.ZOffset());
EXPECT_FALSE(aParams.IsBounded()) << "ZOffset alone is not a bound";
}
TEST(Aspect_GridParamsTest, ArcRange_RoundTrip)
{
Aspect_GridParams aParams;
EXPECT_FALSE(aParams.IsArc());
aParams.SetArcRange(0.0, M_PI);
EXPECT_DOUBLE_EQ(0.0, aParams.AngleStart());
EXPECT_DOUBLE_EQ(M_PI, aParams.AngleEnd());
EXPECT_TRUE(aParams.IsArc());
// Equal start and end reverts to full circle sentinel.
aParams.SetArcRange(1.0, 1.0);
EXPECT_FALSE(aParams.IsArc());
// Wraparound: Start > End is allowed; renderer walks CCW through the wrap.
aParams.SetArcRange(2.356, -2.356);
EXPECT_TRUE(aParams.IsArc());
}
TEST(Aspect_GridParamsTest, DrawMode_RoundTrip)
{
Aspect_GridParams aParams;
aParams.SetDrawMode(Aspect_GDM_Points);
EXPECT_EQ(Aspect_GDM_Points, aParams.DrawMode());
aParams.SetDrawMode(Aspect_GDM_None);
EXPECT_EQ(Aspect_GDM_None, aParams.DrawMode());
aParams.SetDrawMode(Aspect_GDM_Lines);
EXPECT_EQ(Aspect_GDM_Lines, aParams.DrawMode());
}
TEST(Aspect_GridParamsTest, AngularDivisions_ToggleCircular)
{
Aspect_GridParams aParams;
EXPECT_FALSE(aParams.IsCircular());
aParams.SetAngularDivisions(8);
EXPECT_EQ(8, aParams.AngularDivisions());
EXPECT_TRUE(aParams.IsCircular());
aParams.SetAngularDivisions(0);
EXPECT_FALSE(aParams.IsCircular()) << "zero divisions falls back to rectangular";
}
TEST(Aspect_GridParamsTest, EffectiveScaleY_FollowsScaleWhenZero)
{
Aspect_GridParams aParams;
aParams.SetScale(0.25);
EXPECT_DOUBLE_EQ(0.25, aParams.EffectiveScaleY());
aParams.SetScaleY(0.5);
EXPECT_DOUBLE_EQ(0.5, aParams.EffectiveScaleY());
aParams.SetScaleY(0.0);
EXPECT_DOUBLE_EQ(0.25, aParams.EffectiveScaleY()) << "clearing ScaleY restores isotropy";
}
TEST(Aspect_GridParamsTest, RotationAngle_RoundTrip)
{
Aspect_GridParams aParams;
aParams.SetRotationAngle(0.5);
EXPECT_DOUBLE_EQ(0.5, aParams.RotationAngle());
aParams.SetRotationAngle(-1.25);
EXPECT_DOUBLE_EQ(-1.25, aParams.RotationAngle());
}
TEST(Aspect_GridParamsTest, Setters_RoundTrip)
{
Aspect_GridParams aParams;
const Quantity_Color aBlue(Quantity_NOC_BLUE1);
aParams.SetColor(aBlue);
EXPECT_EQ(aBlue, aParams.Color());
const Quantity_Color aRed(Quantity_NOC_RED);
aParams.SetAccentColor(aRed);
EXPECT_EQ(aRed, aParams.AccentColor());
const gp_Pnt aOrigin(1.0, 2.0, 3.0);
aParams.SetOrigin(aOrigin);
EXPECT_NEAR(1.0, aParams.Origin().X(), Precision::Confusion());
EXPECT_NEAR(2.0, aParams.Origin().Y(), Precision::Confusion());
EXPECT_NEAR(3.0, aParams.Origin().Z(), Precision::Confusion());
aParams.SetScale(0.5);
EXPECT_DOUBLE_EQ(0.5, aParams.Scale());
aParams.SetScaleY(0.75);
EXPECT_DOUBLE_EQ(0.75, aParams.ScaleY());
EXPECT_DOUBLE_EQ(0.75, aParams.EffectiveScaleY());
aParams.SetAccentScaleX(0.05);
aParams.SetAccentScaleY(0.1);
aParams.SetAccentAngularScale(2.5);
EXPECT_DOUBLE_EQ(0.05, aParams.AccentScaleX());
EXPECT_DOUBLE_EQ(0.1, aParams.AccentScaleY());
EXPECT_DOUBLE_EQ(2.5, aParams.AccentAngularScale());
aParams.SetLineThickness(0.02);
EXPECT_DOUBLE_EQ(0.02, aParams.LineThickness());
aParams.SetRotationAngle(0.123);
EXPECT_DOUBLE_EQ(0.123, aParams.RotationAngle());
aParams.SetAngularDivisions(12);
EXPECT_EQ(12, aParams.AngularDivisions());
EXPECT_TRUE(aParams.IsCircular());
aParams.SetIsBackground(true);
EXPECT_TRUE(aParams.IsBackground());
aParams.SetIsDrawAxis(false);
EXPECT_FALSE(aParams.IsDrawAxis());
aParams.SetIsInfinity(true);
EXPECT_TRUE(aParams.IsInfinity());
}
TEST(Aspect_GridParamsTest, Copy_PreservesFields)
{
Aspect_GridParams aSrc;
aSrc.SetColor(Quantity_Color(0.1, 0.2, 0.3, Quantity_TOC_RGB));
aSrc.SetOrigin(gp_Pnt(4.0, 5.0, 6.0));
aSrc.SetAccentColor(Quantity_Color(0.7, 0.6, 0.5, Quantity_TOC_RGB));
aSrc.SetScale(0.25);
aSrc.SetScaleY(0.125);
aSrc.SetAccentScaleX(0.025);
aSrc.SetAccentScaleY(0.0125);
aSrc.SetAccentAngularScale(3.0);
aSrc.SetLineThickness(0.04);
aSrc.SetRotationAngle(-0.5);
aSrc.SetAngularDivisions(16);
aSrc.SetIsBackground(true);
aSrc.SetIsDrawAxis(false);
aSrc.SetIsInfinity(true);
aSrc.SetSizeX(7.0);
aSrc.SetSizeY(3.0);
aSrc.SetRadius(2.0);
aSrc.SetZOffset(-0.002);
aSrc.SetArcRange(0.25, 2.0);
const Aspect_GridParams aCopy = aSrc;
EXPECT_NEAR(0.1, aCopy.Color().Red(), Precision::Confusion());
EXPECT_NEAR(0.2, aCopy.Color().Green(), Precision::Confusion());
EXPECT_NEAR(0.3, aCopy.Color().Blue(), Precision::Confusion());
EXPECT_NEAR(4.0, aCopy.Origin().X(), Precision::Confusion());
EXPECT_NEAR(5.0, aCopy.Origin().Y(), Precision::Confusion());
EXPECT_NEAR(6.0, aCopy.Origin().Z(), Precision::Confusion());
EXPECT_NEAR(0.7, aCopy.AccentColor().Red(), Precision::Confusion());
EXPECT_NEAR(0.6, aCopy.AccentColor().Green(), Precision::Confusion());
EXPECT_NEAR(0.5, aCopy.AccentColor().Blue(), Precision::Confusion());
EXPECT_DOUBLE_EQ(0.25, aCopy.Scale());
EXPECT_DOUBLE_EQ(0.125, aCopy.ScaleY());
EXPECT_DOUBLE_EQ(0.125, aCopy.EffectiveScaleY());
EXPECT_DOUBLE_EQ(0.025, aCopy.AccentScaleX());
EXPECT_DOUBLE_EQ(0.0125, aCopy.AccentScaleY());
EXPECT_DOUBLE_EQ(3.0, aCopy.AccentAngularScale());
EXPECT_DOUBLE_EQ(0.04, aCopy.LineThickness());
EXPECT_DOUBLE_EQ(-0.5, aCopy.RotationAngle());
EXPECT_EQ(16, aCopy.AngularDivisions());
EXPECT_TRUE(aCopy.IsCircular());
EXPECT_TRUE(aCopy.IsBackground());
EXPECT_FALSE(aCopy.IsDrawAxis());
EXPECT_TRUE(aCopy.IsInfinity());
EXPECT_DOUBLE_EQ(7.0, aCopy.SizeX());
EXPECT_DOUBLE_EQ(3.0, aCopy.SizeY());
EXPECT_DOUBLE_EQ(2.0, aCopy.Radius());
EXPECT_DOUBLE_EQ(-0.002, aCopy.ZOffset());
EXPECT_DOUBLE_EQ(0.25, aCopy.AngleStart());
EXPECT_DOUBLE_EQ(2.0, aCopy.AngleEnd());
EXPECT_TRUE(aCopy.IsBounded());
EXPECT_TRUE(aCopy.IsArc());
}
#ifndef No_Exception
TEST(Aspect_GridParamsTest, Setters_RejectNegativeValues)
{
Aspect_GridParams aParams;
EXPECT_THROW(aParams.SetScale(-1.0), Standard_NegativeValue);
EXPECT_THROW(aParams.SetScaleY(-1.0), Standard_NegativeValue);
EXPECT_THROW(aParams.SetLineThickness(-0.1), Standard_NegativeValue);
EXPECT_THROW(aParams.SetSizeX(-1.0), Standard_NegativeValue);
EXPECT_THROW(aParams.SetSizeY(-1.0), Standard_NegativeValue);
EXPECT_THROW(aParams.SetRadius(-1.0), Standard_NegativeValue);
}
#endif
@@ -0,0 +1,116 @@
// 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 <Aspect_CircularGrid.hxx>
#include <Aspect_RectangularGrid.hxx>
#include <gtest/gtest.h>
namespace
{
// Concrete shims: Aspect_RectangularGrid / Aspect_CircularGrid are abstract
// because Display/Erase/IsDisplayed/UpdateDisplay are pure virtual on the
// base. V3d_RectangularGrid supplies them in production, but pulling V3d into
// this test would need a live viewer/window. Since we only exercise the new
// bounds accessors, a minimal stub is enough.
class RectGridStub : public Aspect_RectangularGrid
{
public:
RectGridStub(const double theXStep, const double theYStep)
: Aspect_RectangularGrid(theXStep, theYStep)
{
}
void Display() override {}
void Erase() const override {}
bool IsDisplayed() const override { return false; }
protected:
void UpdateDisplay() override {}
};
class CircGridStub : public Aspect_CircularGrid
{
public:
CircGridStub(const double theRadiusStep, const int theDivisions)
: Aspect_CircularGrid(theRadiusStep, theDivisions)
{
}
void Display() override {}
void Erase() const override {}
bool IsDisplayed() const override { return false; }
protected:
void UpdateDisplay() override {}
};
} // namespace
TEST(Aspect_GridBoundsTest, Rectangular_Defaults)
{
RectGridStub aGrid(1.0, 1.0);
EXPECT_DOUBLE_EQ(0.0, aGrid.SizeX());
EXPECT_DOUBLE_EQ(0.0, aGrid.SizeY());
EXPECT_DOUBLE_EQ(0.0, aGrid.ZOffset());
}
TEST(Aspect_GridBoundsTest, Rectangular_RoundTrip)
{
RectGridStub aGrid(1.0, 1.0);
aGrid.SetSizeX(8.0);
aGrid.SetSizeY(4.0);
aGrid.SetZOffset(-0.005);
EXPECT_DOUBLE_EQ(8.0, aGrid.SizeX());
EXPECT_DOUBLE_EQ(4.0, aGrid.SizeY());
EXPECT_DOUBLE_EQ(-0.005, aGrid.ZOffset());
}
TEST(Aspect_GridBoundsTest, Circular_Defaults)
{
CircGridStub aGrid(1.0, 8);
EXPECT_DOUBLE_EQ(0.0, aGrid.Radius());
EXPECT_DOUBLE_EQ(0.0, aGrid.ZOffset());
EXPECT_DOUBLE_EQ(0.0, aGrid.AngleStart());
EXPECT_DOUBLE_EQ(0.0, aGrid.AngleEnd());
EXPECT_FALSE(aGrid.IsArc());
}
TEST(Aspect_GridBoundsTest, Circular_RoundTrip)
{
CircGridStub aGrid(1.0, 8);
aGrid.SetRadius(2.5);
aGrid.SetZOffset(-0.005);
EXPECT_DOUBLE_EQ(2.5, aGrid.Radius());
EXPECT_DOUBLE_EQ(-0.005, aGrid.ZOffset());
}
TEST(Aspect_GridBoundsTest, Circular_ArcRange)
{
CircGridStub aGrid(1.0, 8);
EXPECT_FALSE(aGrid.IsArc());
aGrid.SetArcRange(0.0, M_PI);
EXPECT_TRUE(aGrid.IsArc());
EXPECT_DOUBLE_EQ(0.0, aGrid.AngleStart());
EXPECT_DOUBLE_EQ(M_PI, aGrid.AngleEnd());
aGrid.SetArcRange(0.0, 0.0);
EXPECT_FALSE(aGrid.IsArc()) << "equal start/end is the full-circle sentinel";
aGrid.SetArcRange(2.356, -2.356);
EXPECT_TRUE(aGrid.IsArc()) << "wraparound (Start > End) is valid";
}
@@ -2,6 +2,8 @@
set(OCCT_TKService_GTests_FILES_LOCATION "${CMAKE_CURRENT_LIST_DIR}")
set(OCCT_TKService_GTests_FILES
Aspect_GridParams_Test.cxx
Aspect_Grid_Bounds_Test.cxx
Graphic3d_Aspects_Test.cxx
Graphic3d_BndBox_Test.cxx
Graphic3d_Flipper_Test.cxx
@@ -14,9 +14,11 @@
#ifndef _Graphic3d_CView_HeaderFile
#define _Graphic3d_CView_HeaderFile
#include <Aspect_GridParams.hxx>
#include <Aspect_RenderingContext.hxx>
#include <Aspect_SkydomeBackground.hxx>
#include <Aspect_Window.hxx>
#include <gp_Ax3.hxx>
#include <Graphic3d_BufferType.hxx>
#include <Graphic3d_CubeMap.hxx>
#include <Graphic3d_DataStructureManager.hxx>
@@ -438,6 +440,20 @@ public:
//! @param[in] theToEnableIBL enable or disable IBL from background cubemap
virtual void SetImageBasedLighting(bool theToEnableIBL) = 0;
//! Display a shader-rendered infinite grid on the given plane.
//! The default implementation is a no-op; drivers with shader support override it.
//! @param[in] theParams appearance parameters
//! @param[in] thePlane grid plane in world coordinates (origin + X/Y directions)
virtual void GridDisplay(const Aspect_GridParams& theParams, const gp_Ax3& thePlane)
{
(void)theParams;
(void)thePlane;
}
//! Erase the shader-rendered infinite grid.
//! The default implementation is a no-op; drivers with shader support override it.
virtual void GridErase() {}
//! Returns environment texture set for the view.
const occ::handle<Graphic3d_TextureEnv>& TextureEnv() const { return myTextureEnvData; }
@@ -2148,3 +2148,243 @@ occ::handle<Graphic3d_ShaderProgram> Graphic3d_ShaderManager::getColoredQuadProg
return aProgSrc;
}
//=================================================================================================
occ::handle<Graphic3d_ShaderProgram> Graphic3d_ShaderManager::getGridProgram() const
{
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).
aStageInOuts.Append(
Graphic3d_ShaderObject::ShaderVariable("vec2 vNdc",
Graphic3d_TOS_VERTEX | Graphic3d_TOS_FRAGMENT));
aUniforms.Append(Graphic3d_ShaderObject::ShaderVariable("float uScaleX", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(Graphic3d_ShaderObject::ShaderVariable("float uScaleY", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("float uThickness", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(Graphic3d_ShaderObject::ShaderVariable("vec3 uColor", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("vec3 uAccentColor", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("float uAccentScaleX", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("float uAccentScaleY", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("float uAccentAngularScale", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("int uIsDrawAxis", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("int uIsBackground", 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));
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));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("int uHasStableRef", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(Graphic3d_ShaderObject::ShaderVariable("vec3 uBounds", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("vec2 uArcRange", Graphic3d_TOS_FRAGMENT));
aUniforms.Append(
Graphic3d_ShaderObject::ShaderVariable("int uArcBounded", Graphic3d_TOS_FRAGMENT));
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 "void main()" EOL "{" EOL " vec3 aVertex = gridPlane[gl_VertexID];" EOL
" vNdc = aVertex.xy;" EOL " gl_Position = vec4 (aVertex, 1.0);" EOL "}";
TCollection_AsciiString aSrcFrag =
TCollection_AsciiString()
+ EOL
"vec4 gridLines2d (vec2 theUV, 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
" bool isYAxis = abs (aCoord.x) < aMinX;" EOL
" bool isXAxis = abs (aCoord.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 "}"
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 "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 "}"
// sin(angle) threshold for "ray parallel to plane" - ~5.7e-5 deg.
EOL "const float GRID_PARALLEL_EPS = 1e-6;"
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 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 "}"
// 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 "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));"
// 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 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
" 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 " }"
// 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
" aScale = vec2 (uScaleX, uScaleY);" EOL " aAxisUv = aLocal;" EOL " }" EOL
" vec4 aColor = gridLines2d (aGridUv, uColor, aScale, uGridType == 0, uThickness);" EOL
" if (uDrawMode != 1)" EOL " {" EOL " if (uGridType == 0)" 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
// 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
" 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 " }"
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 " 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.
if (myGapi == Aspect_GraphicsLibrary_OpenGL)
{
aProgSrc->SetHeader(IsGapiGreaterEqual(3, 2) ? "#version 150" : "#version 130");
}
else if (myGapi == Aspect_GraphicsLibrary_OpenGLES && IsGapiGreaterEqual(3, 0))
{
aProgSrc->SetHeader("#version 300 es");
}
aProgSrc->AttachShader(Graphic3d_ShaderObject::CreateFromSource(aSrcVert,
Graphic3d_TOS_VERTEX,
aUniforms,
aStageInOuts));
aProgSrc->AttachShader(Graphic3d_ShaderObject::CreateFromSource(aSrcFrag,
Graphic3d_TOS_FRAGMENT,
aUniforms,
aStageInOuts));
return aProgSrc;
}
@@ -156,6 +156,11 @@ protected:
//! Generates shader program to render correctly colored quad.
Standard_EXPORT occ::handle<Graphic3d_ShaderProgram> getColoredQuadProgram() const;
//! Generates shader program to render an infinite grid on an arbitrary plane.
//! Uses a screen-space triangle unprojected to world rays and ray-plane intersection
//! driven by uniforms uPlaneOrigin / uPlaneX / uPlaneY / uPlaneN.
Standard_EXPORT occ::handle<Graphic3d_ShaderProgram> getGridProgram() const;
//! Prepare GLSL source for IBL generation used in PBR pipeline.
Standard_EXPORT occ::handle<Graphic3d_ShaderProgram> getPBREnvBakingProgram(int theIndex) const;
+97 -315
View File
@@ -13,374 +13,169 @@
#include <V3d_CircularGrid.hxx>
#include <Graphic3d_ArrayOfPoints.hxx>
#include <Graphic3d_ArrayOfPolylines.hxx>
#include <Graphic3d_ArrayOfSegments.hxx>
#include <Graphic3d_AspectLine3d.hxx>
#include <Graphic3d_AspectMarker3d.hxx>
#include <Graphic3d_Group.hxx>
#include <Aspect_GridParams.hxx>
#include <Quantity_Color.hxx>
#include <Standard_Type.hxx>
#include <gp_Pnt.hxx>
#include <NCollection_Sequence.hxx>
#include <V3d_View.hxx>
#include <V3d_Viewer.hxx>
#include <gp_Ax3.hxx>
#include <gp_Pnt.hxx>
IMPLEMENT_STANDARD_RTTIEXT(V3d_CircularGrid, Aspect_CircularGrid)
namespace
{
constexpr double THE_DEFAULT_GRID_STEP = 10.0;
constexpr int THE_DIVISION = 8;
constexpr int THE_DEFAULT_DIVISION = 8;
constexpr double THE_MYFACTOR = 50.0;
} // namespace
//! Dummy implementation of Graphic3d_Structure overriding ::Compute() method for handling Device
//! Lost.
class V3d_CircularGrid::CircularGridStructure : public Graphic3d_Structure
{
public:
//! Main constructor.
CircularGridStructure(const occ::handle<Graphic3d_StructureManager>& theManager,
V3d_CircularGrid* theGrid)
: Graphic3d_Structure(theManager),
myGrid(theGrid)
{
}
//! Override method initiating recomputing in V3d_CircularGrid.
void Compute() override
{
GraphicClear(false);
myGrid->myGroup = NewGroup();
myGrid->myCurAreDefined = false;
myGrid->UpdateDisplay();
}
private:
V3d_CircularGrid* myGrid;
};
/*----------------------------------------------------------------------*/
//=================================================================================================
V3d_CircularGrid::V3d_CircularGrid(const V3d_ViewerPointer& aViewer,
const Quantity_Color& aColor,
const Quantity_Color& aTenthColor)
: Aspect_CircularGrid(1., 8),
: Aspect_CircularGrid(1., THE_DEFAULT_DIVISION),
myViewer(aViewer),
myCurAreDefined(false),
myToComputePrs(false),
myCurDrawMode(Aspect_GDM_Lines),
myCurXo(0.0),
myCurYo(0.0),
myCurAngle(0.0),
myCurStep(0.0),
myCurDivi(0),
myRadius(0.5 * aViewer->DefaultViewSize()),
myOffSet(THE_DEFAULT_GRID_STEP / THE_MYFACTOR)
myIsDisplayed(false)
{
myColor = aColor;
myTenthColor = aTenthColor;
myStructure = new CircularGridStructure(aViewer->StructureManager(), this);
myGroup = myStructure->NewGroup();
myStructure->SetInfiniteState(true);
SetRadiusStep(THE_DEFAULT_GRID_STEP);
Aspect_CircularGrid::SetRadius(0.5 * aViewer->DefaultViewSize());
Aspect_CircularGrid::SetZOffset(THE_DEFAULT_GRID_STEP / THE_MYFACTOR);
}
//=================================================================================================
V3d_CircularGrid::~V3d_CircularGrid()
{
myGroup.Nullify();
if (!myStructure.IsNull())
if (myIsDisplayed)
{
myStructure->Erase();
syncViews(false);
}
}
//=================================================================================================
void V3d_CircularGrid::SetColors(const Quantity_Color& aColor, const Quantity_Color& aTenthColor)
{
if (myColor != aColor || myTenthColor != aTenthColor)
{
myColor = aColor;
myTenthColor = aTenthColor;
myCurAreDefined = false;
myColor = aColor;
myTenthColor = aTenthColor;
UpdateDisplay();
}
}
//=================================================================================================
void V3d_CircularGrid::Display()
{
myStructure->SetDisplayPriority(Graphic3d_DisplayPriority_AlmostBottom);
myStructure->Display();
UpdateDisplay();
myIsDisplayed = true;
syncViews(true);
}
//=================================================================================================
void V3d_CircularGrid::Erase() const
{
myStructure->Erase();
myIsDisplayed = false;
syncViews(false);
}
//=================================================================================================
bool V3d_CircularGrid::IsDisplayed() const
{
return myStructure->IsDisplayed();
return myIsDisplayed;
}
//=================================================================================================
void V3d_CircularGrid::UpdateDisplay()
{
gp_Ax3 ThePlane = myViewer->PrivilegedPlane();
double xl, yl, zl;
double xdx, xdy, xdz;
double ydx, ydy, ydz;
double dx, dy, dz;
ThePlane.Location().Coord(xl, yl, zl);
ThePlane.XDirection().Coord(xdx, xdy, xdz);
ThePlane.YDirection().Coord(ydx, ydy, ydz);
ThePlane.Direction().Coord(dx, dy, dz);
bool MakeTransform = !myCurAreDefined;
if (!MakeTransform)
if (myIsDisplayed)
{
MakeTransform = (RotationAngle() != myCurAngle || XOrigin() != myCurXo || YOrigin() != myCurYo);
if (!MakeTransform)
{
double curxl, curyl, curzl;
double curxdx, curxdy, curxdz;
double curydx, curydy, curydz;
double curdx, curdy, curdz;
myCurViewPlane.Location().Coord(curxl, curyl, curzl);
myCurViewPlane.XDirection().Coord(curxdx, curxdy, curxdz);
myCurViewPlane.YDirection().Coord(curydx, curydy, curydz);
myCurViewPlane.Direction().Coord(curdx, curdy, curdz);
if (xl != curxl || yl != curyl || zl != curzl || xdx != curxdx || xdy != curxdy
|| xdz != curxdz || ydx != curydx || ydy != curydy || ydz != curydz || dx != curdx
|| dy != curdy || dz != curdz)
MakeTransform = true;
}
syncViews(true);
}
if (MakeTransform)
{
const double CosAlpha = std::cos(RotationAngle());
const double SinAlpha = std::sin(RotationAngle());
gp_Trsf aTrsf;
// Translation
// Transformation of change of marker
aTrsf.SetValues(xdx, ydx, dx, xl, xdy, ydy, dy, yl, xdz, ydz, dz, zl);
// Translation of the origin
// Rotation Alpha around axis -Z
gp_Trsf aTrsf2;
aTrsf2.SetValues(CosAlpha,
SinAlpha,
0.0,
-XOrigin(),
-SinAlpha,
CosAlpha,
0.0,
-YOrigin(),
0.0,
0.0,
1.0,
0.0);
aTrsf.Multiply(aTrsf2);
myStructure->SetTransformation(new TopLoc_Datum3D(aTrsf));
myCurAngle = RotationAngle();
myCurXo = XOrigin(), myCurYo = YOrigin();
myCurViewPlane = ThePlane;
}
switch (myDrawMode)
{
case Aspect_GDM_Points:
DefinePoints();
myCurDrawMode = Aspect_GDM_Points;
break;
case Aspect_GDM_Lines:
DefineLines();
myCurDrawMode = Aspect_GDM_Lines;
break;
case Aspect_GDM_None:
myCurDrawMode = Aspect_GDM_None;
break;
}
myCurAreDefined = true;
}
void V3d_CircularGrid::DefineLines()
{
const double aStep = RadiusStep();
const double aDivision = DivisionNumber();
const bool toUpdate = !myCurAreDefined || myCurDrawMode != Aspect_GDM_Lines
|| aDivision != myCurDivi || aStep != myCurStep;
if (!toUpdate && !myToComputePrs)
{
return;
}
else if (!myStructure->IsDisplayed())
{
myToComputePrs = true;
return;
}
myToComputePrs = false;
myGroup->Clear();
const int Division = (int)((aDivision >= THE_DIVISION ? aDivision : THE_DIVISION));
int nbpnts = 2 * Division;
// diametres
double alpha = M_PI / aDivision;
myGroup->SetGroupPrimitivesAspect(
new Graphic3d_AspectLine3d(myTenthColor, Aspect_TOL_SOLID, 1.0));
occ::handle<Graphic3d_ArrayOfSegments> aPrims1 = new Graphic3d_ArrayOfSegments(2 * nbpnts);
const gp_Pnt p0(0., 0., -myOffSet);
for (int i = 1; i <= nbpnts; i++)
{
aPrims1->AddVertex(p0);
aPrims1->AddVertex(std::cos(alpha * i) * myRadius, std::sin(alpha * i) * myRadius, -myOffSet);
}
myGroup->AddPrimitiveArray(aPrims1, false);
// circles
nbpnts = 2 * Division + 1;
alpha = M_PI / Division;
int nblines = 0;
NCollection_Sequence<gp_Pnt> aSeqLines, aSeqTenth;
for (double r = aStep; r <= myRadius; r += aStep, nblines++)
{
const bool isTenth = (Modulus(nblines, 10) == 0);
for (int i = 0; i < nbpnts; i++)
{
const gp_Pnt pt(std::cos(alpha * i) * r, std::sin(alpha * i) * r, -myOffSet);
(isTenth ? aSeqTenth : aSeqLines).Append(pt);
}
}
if (aSeqTenth.Length())
{
myGroup->SetGroupPrimitivesAspect(
new Graphic3d_AspectLine3d(myTenthColor, Aspect_TOL_SOLID, 1.0));
int n, np;
const int nbl = aSeqTenth.Length() / nbpnts;
occ::handle<Graphic3d_ArrayOfPolylines> aPrims2 =
new Graphic3d_ArrayOfPolylines(aSeqTenth.Length(), nbl);
for (np = 1, n = 0; n < nbl; n++)
{
aPrims2->AddBound(nbpnts);
for (int i = 0; i < nbpnts; i++, np++)
aPrims2->AddVertex(aSeqTenth(np));
}
myGroup->AddPrimitiveArray(aPrims2, false);
}
if (aSeqLines.Length())
{
myGroup->SetPrimitivesAspect(new Graphic3d_AspectLine3d(myColor, Aspect_TOL_SOLID, 1.0));
int n, np;
const int nbl = aSeqLines.Length() / nbpnts;
occ::handle<Graphic3d_ArrayOfPolylines> aPrims3 =
new Graphic3d_ArrayOfPolylines(aSeqLines.Length(), nbl);
for (np = 1, n = 0; n < nbl; n++)
{
aPrims3->AddBound(nbpnts);
for (int i = 0; i < nbpnts; i++, np++)
aPrims3->AddVertex(aSeqLines(np));
}
myGroup->AddPrimitiveArray(aPrims3, false);
}
myGroup->SetMinMaxValues(-myRadius, -myRadius, -myOffSet, myRadius, myRadius, -myOffSet);
myCurStep = aStep, myCurDivi = (int)aDivision;
// update bounding box
myStructure->CalculateBoundBox();
myViewer->StructureManager()->Update(myStructure->GetZLayer());
}
void V3d_CircularGrid::DefinePoints()
{
const double aStep = RadiusStep();
const double aDivision = DivisionNumber();
const bool toUpdate = !myCurAreDefined || myCurDrawMode != Aspect_GDM_Points
|| aDivision != myCurDivi || aStep != myCurStep;
if (!toUpdate && !myToComputePrs)
{
return;
}
else if (!myStructure->IsDisplayed())
{
myToComputePrs = true;
return;
}
myToComputePrs = false;
myGroup->Clear();
occ::handle<Graphic3d_AspectMarker3d> MarkerAttrib = new Graphic3d_AspectMarker3d();
MarkerAttrib->SetColor(myColor);
MarkerAttrib->SetType(Aspect_TOM_POINT);
MarkerAttrib->SetScale(3.);
const int nbpnts = int(2 * aDivision);
double r, alpha = M_PI / aDivision;
// diameters
NCollection_Sequence<gp_Pnt> aSeqPnts;
aSeqPnts.Append(gp_Pnt(0.0, 0.0, -myOffSet));
for (r = aStep; r <= myRadius; r += aStep)
{
for (int i = 0; i < nbpnts; i++)
aSeqPnts.Append(gp_Pnt(std::cos(alpha * i) * r, std::sin(alpha * i) * r, -myOffSet));
}
myGroup->SetGroupPrimitivesAspect(MarkerAttrib);
if (aSeqPnts.Length())
{
double X, Y, Z;
const int nbv = aSeqPnts.Length();
occ::handle<Graphic3d_ArrayOfPoints> Cercle = new Graphic3d_ArrayOfPoints(nbv);
for (int i = 1; i <= nbv; i++)
{
aSeqPnts(i).Coord(X, Y, Z);
Cercle->AddVertex(X, Y, Z);
}
myGroup->AddPrimitiveArray(Cercle, false);
}
myGroup->SetMinMaxValues(-myRadius, -myRadius, -myOffSet, myRadius, myRadius, -myOffSet);
myCurStep = aStep, myCurDivi = (int)aDivision;
// update bounding box
myStructure->CalculateBoundBox();
myViewer->StructureManager()->Update(myStructure->GetZLayer());
}
//=================================================================================================
void V3d_CircularGrid::GraphicValues(double& theRadius, double& theOffSet) const
{
theRadius = myRadius;
theOffSet = myOffSet;
theRadius = Radius();
theOffSet = ZOffset();
}
//=================================================================================================
void V3d_CircularGrid::SetGraphicValues(const double theRadius, const double theOffSet)
{
if (!myCurAreDefined)
// Base-class setters each trigger UpdateDisplay only on real change.
SetRadius(theRadius);
SetZOffset(theOffSet);
}
//=================================================================================================
void V3d_CircularGrid::syncViews(const bool theDoDisplay) const
{
if (myViewer == nullptr)
{
myRadius = theRadius;
myOffSet = theOffSet;
return;
}
if (myRadius != theRadius)
const double aRadiusStep = RadiusStep() > 0.0 ? RadiusStep() : THE_DEFAULT_GRID_STEP;
const int aDivisions = DivisionNumber() > 0 ? DivisionNumber() : THE_DEFAULT_DIVISION;
const gp_Ax3 aPlane = myViewer->PrivilegedPlane();
// Same convention as V3d_RectangularGrid::syncViews: origin is a world-space
// offset aligned with the plane basis so the shader's aPlaneOrigin matches
// V3d_View::Compute's aPnt0 used for snap selection.
const gp_XYZ aOriginOffset =
aPlane.XDirection().XYZ() * -XOrigin() + aPlane.YDirection().XYZ() * -YOrigin();
Aspect_GridParams aParams;
aParams.SetColor(myColor);
aParams.SetAccentColor(myTenthColor);
aParams.SetOrigin(gp_Pnt(aOriginOffset));
aParams.SetScale(1.0 / aRadiusStep);
aParams.SetScaleY(0.0); // unused in circular mode
aParams.SetAccentScaleX(0.1 / aRadiusStep);
// Angular accent defaults OFF: classical OCCT circular grids have no
// "tenth spoke" concept. Spokes come from DivisionNumber and are already
// drawn by the base layer at full count. Setting this to a positive value
// (e.g. aDivisions / (k * M_PI) for k > 1) would highlight every k-th spoke.
aParams.SetAccentAngularScale(0.0);
aParams.SetRotationAngle(RotationAngle());
aParams.SetAngularDivisions(aDivisions);
aParams.SetDrawMode(DrawMode());
aParams.SetRadius(Radius());
aParams.SetZOffset(ZOffset());
aParams.SetArcRange(AngleStart(), AngleEnd());
aParams.SetIsBackground(false);
aParams.SetIsDrawAxis(false);
aParams.SetIsInfinity(false);
for (const occ::handle<V3d_View>& aView : myViewer->DefinedViews())
{
myRadius = theRadius;
myCurAreDefined = false;
if (aView.IsNull())
{
continue;
}
if (theDoDisplay)
{
aView->GridDisplay(aParams, aPlane);
}
else
{
aView->GridErase();
}
}
if (myOffSet != theOffSet)
{
myOffSet = theOffSet;
myCurAreDefined = false;
}
if (!myCurAreDefined)
UpdateDisplay();
}
//=================================================================================================
@@ -390,19 +185,6 @@ void V3d_CircularGrid::DumpJson(Standard_OStream& theOStream, int theDepth) cons
OCCT_DUMP_TRANSIENT_CLASS_BEGIN(theOStream)
OCCT_DUMP_BASE_CLASS(theOStream, theDepth, Aspect_CircularGrid)
OCCT_DUMP_FIELD_VALUES_DUMPED(theOStream, theDepth, myStructure.get())
OCCT_DUMP_FIELD_VALUES_DUMPED(theOStream, theDepth, myGroup.get())
OCCT_DUMP_FIELD_VALUES_DUMPED(theOStream, theDepth, &myCurViewPlane)
OCCT_DUMP_FIELD_VALUE_POINTER(theOStream, myViewer)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myCurAreDefined)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myToComputePrs)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myCurDrawMode)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myCurXo)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myCurYo)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myCurAngle)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myCurStep)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myCurDivi)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myRadius)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myOffSet)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myIsDisplayed)
}
@@ -19,15 +19,12 @@
#include <Standard.hxx>
#include <gp_Ax3.hxx>
#include <V3d_ViewerPointer.hxx>
#include <Standard_Boolean.hxx>
#include <Standard_Real.hxx>
#include <Standard_Integer.hxx>
#include <Aspect_CircularGrid.hxx>
class Graphic3d_Structure;
class Graphic3d_Group;
#include <V3d_ViewerPointer.hxx>
//! Circular grid bound to a V3d_Viewer. Snap math (Compute/Hit) is inherited
//! from Aspect_CircularGrid; rendering goes through the shader-based infinite
//! grid (OpenGl_View::renderGrid) with a polar branch.
class V3d_CircularGrid : public Aspect_CircularGrid
{
DEFINE_STANDARD_RTTIEXT(V3d_CircularGrid, Aspect_CircularGrid)
@@ -58,29 +55,13 @@ protected:
Standard_EXPORT void UpdateDisplay() override;
private:
Standard_EXPORT void DefineLines();
Standard_EXPORT void DefinePoints();
//! Broadcast current parameters to every view owned by the viewer.
//! When theDoDisplay is false, erases the shader grid from each view instead.
void syncViews(const bool theDoDisplay) const;
private:
//! Custom Graphic3d_Structure implementation.
class CircularGridStructure;
private:
occ::handle<Graphic3d_Structure> myStructure;
occ::handle<Graphic3d_Group> myGroup;
gp_Ax3 myCurViewPlane;
V3d_ViewerPointer myViewer;
bool myCurAreDefined;
bool myToComputePrs;
Aspect_GridDrawMode myCurDrawMode;
double myCurXo;
double myCurYo;
double myCurAngle;
double myCurStep;
int myCurDivi;
double myRadius;
double myOffSet;
V3d_ViewerPointer myViewer;
mutable bool myIsDisplayed;
};
#endif // _V3d_CircularGrid_HeaderFile
@@ -13,16 +13,13 @@
#include <V3d_RectangularGrid.hxx>
#include <Graphic3d_ArrayOfPoints.hxx>
#include <Graphic3d_ArrayOfSegments.hxx>
#include <Graphic3d_AspectLine3d.hxx>
#include <Graphic3d_AspectMarker3d.hxx>
#include <Graphic3d_Group.hxx>
#include <Aspect_GridParams.hxx>
#include <Quantity_Color.hxx>
#include <Standard_Type.hxx>
#include <gp_Pnt.hxx>
#include <NCollection_Sequence.hxx>
#include <V3d_View.hxx>
#include <V3d_Viewer.hxx>
#include <gp_Ax3.hxx>
#include <gp_Pnt.hxx>
IMPLEMENT_STANDARD_RTTIEXT(V3d_RectangularGrid, Aspect_RectangularGrid)
@@ -32,360 +29,158 @@ constexpr double THE_DEFAULT_GRID_STEP = 10.0;
constexpr double THE_MYFACTOR = 50.0;
} // namespace
//! Dummy implementation of Graphic3d_Structure overriding ::Compute() method for handling Device
//! Lost.
class V3d_RectangularGrid::RectangularGridStructure : public Graphic3d_Structure
{
public:
//! Main constructor.
RectangularGridStructure(const occ::handle<Graphic3d_StructureManager>& theManager,
V3d_RectangularGrid* theGrid)
: Graphic3d_Structure(theManager),
myGrid(theGrid)
{
}
//! Override method initiating recomputing in V3d_RectangularGrid.
void Compute() override
{
GraphicClear(false);
myGrid->myGroup = NewGroup();
myGrid->myCurAreDefined = false;
myGrid->UpdateDisplay();
}
private:
V3d_RectangularGrid* myGrid;
};
/*----------------------------------------------------------------------*/
//=================================================================================================
V3d_RectangularGrid::V3d_RectangularGrid(const V3d_ViewerPointer& aViewer,
const Quantity_Color& aColor,
const Quantity_Color& aTenthColor)
: Aspect_RectangularGrid(1., 1.),
myViewer(aViewer),
myCurAreDefined(false),
myToComputePrs(true),
myCurDrawMode(Aspect_GDM_Lines),
myCurXo(0.0),
myCurYo(0.0),
myCurAngle(0.0),
myCurXStep(0.0),
myCurYStep(0.0),
myXSize(0.5 * aViewer->DefaultViewSize()),
myYSize(0.5 * aViewer->DefaultViewSize()),
myOffSet(THE_DEFAULT_GRID_STEP / THE_MYFACTOR)
myIsDisplayed(false)
{
myColor = aColor;
myTenthColor = aTenthColor;
myStructure = new RectangularGridStructure(aViewer->StructureManager(), this);
myGroup = myStructure->NewGroup();
myStructure->SetInfiniteState(true);
SetXStep(THE_DEFAULT_GRID_STEP);
SetYStep(THE_DEFAULT_GRID_STEP);
// Keep rectangular grid unbounded by default; explicit -size / viewer API
// requests set non-zero SizeX/SizeY and activate clipping in the shader.
Aspect_RectangularGrid::SetZOffset(THE_DEFAULT_GRID_STEP / THE_MYFACTOR);
}
//=================================================================================================
V3d_RectangularGrid::~V3d_RectangularGrid()
{
myGroup.Nullify();
if (!myStructure.IsNull())
if (myIsDisplayed)
{
myStructure->Erase();
syncViews(false);
}
}
//=================================================================================================
void V3d_RectangularGrid::SetColors(const Quantity_Color& aColor, const Quantity_Color& aTenthColor)
{
if (myColor != aColor || myTenthColor != aTenthColor)
{
myColor = aColor;
myTenthColor = aTenthColor;
myCurAreDefined = false;
myColor = aColor;
myTenthColor = aTenthColor;
UpdateDisplay();
}
}
//=================================================================================================
void V3d_RectangularGrid::Display()
{
myStructure->SetDisplayPriority(Graphic3d_DisplayPriority_AlmostBottom);
myStructure->Display();
UpdateDisplay();
myIsDisplayed = true;
syncViews(true);
}
//=================================================================================================
void V3d_RectangularGrid::Erase() const
{
myStructure->Erase();
myIsDisplayed = false;
syncViews(false);
}
//=================================================================================================
bool V3d_RectangularGrid::IsDisplayed() const
{
return myStructure->IsDisplayed();
return myIsDisplayed;
}
//=================================================================================================
void V3d_RectangularGrid::UpdateDisplay()
{
gp_Ax3 ThePlane = myViewer->PrivilegedPlane();
bool MakeTransform = false;
double xl, yl, zl;
double xdx, xdy, xdz;
double ydx, ydy, ydz;
double dx, dy, dz;
ThePlane.Location().Coord(xl, yl, zl);
ThePlane.XDirection().Coord(xdx, xdy, xdz);
ThePlane.YDirection().Coord(ydx, ydy, ydz);
ThePlane.Direction().Coord(dx, dy, dz);
if (!myCurAreDefined)
MakeTransform = true;
else
if (myIsDisplayed)
{
if (RotationAngle() != myCurAngle || XOrigin() != myCurXo || YOrigin() != myCurYo)
MakeTransform = true;
if (!MakeTransform)
{
double curxl, curyl, curzl;
double curxdx, curxdy, curxdz;
double curydx, curydy, curydz;
double curdx, curdy, curdz;
myCurViewPlane.Location().Coord(curxl, curyl, curzl);
myCurViewPlane.XDirection().Coord(curxdx, curxdy, curxdz);
myCurViewPlane.YDirection().Coord(curydx, curydy, curydz);
myCurViewPlane.Direction().Coord(curdx, curdy, curdz);
if (xl != curxl || yl != curyl || zl != curzl || xdx != curxdx || xdy != curxdy
|| xdz != curxdz || ydx != curydx || ydy != curydy || ydz != curydz || dx != curdx
|| dy != curdy || dz != curdz)
MakeTransform = true;
}
syncViews(true);
}
if (MakeTransform)
{
const double CosAlpha = std::cos(RotationAngle());
const double SinAlpha = std::sin(RotationAngle());
gp_Trsf aTrsf;
// Translation
// Transformation of change of marker
aTrsf.SetValues(xdx, ydx, dx, xl, xdy, ydy, dy, yl, xdz, ydz, dz, zl);
// Translation of the origin
// Rotation Alpha around axis -Z
gp_Trsf aTrsf2;
aTrsf2.SetValues(CosAlpha,
SinAlpha,
0.0,
-XOrigin(),
-SinAlpha,
CosAlpha,
0.0,
-YOrigin(),
0.0,
0.0,
1.0,
0.0);
aTrsf.Multiply(aTrsf2);
myStructure->SetTransformation(new TopLoc_Datum3D(aTrsf));
myCurAngle = RotationAngle();
myCurXo = XOrigin(), myCurYo = YOrigin();
myCurViewPlane = ThePlane;
}
switch (myDrawMode)
{
case Aspect_GDM_Points:
DefinePoints();
myCurDrawMode = Aspect_GDM_Points;
break;
case Aspect_GDM_Lines:
DefineLines();
myCurDrawMode = Aspect_GDM_Lines;
break;
case Aspect_GDM_None:
myCurDrawMode = Aspect_GDM_None;
break;
}
myCurAreDefined = true;
}
void V3d_RectangularGrid::DefineLines()
{
const double aXStep = XStep();
const double aYStep = YStep();
const bool toUpdate = !myCurAreDefined || myCurDrawMode != Aspect_GDM_Lines
|| aXStep != myCurXStep || aYStep != myCurYStep;
if (!toUpdate && !myToComputePrs)
{
return;
}
else if (!myStructure->IsDisplayed())
{
myToComputePrs = true;
return;
}
myToComputePrs = false;
myGroup->Clear();
int nblines;
double xl, yl, zl = myOffSet;
NCollection_Sequence<gp_Pnt> aSeqLines, aSeqTenth;
// verticals
aSeqTenth.Append(gp_Pnt(0., -myYSize, -zl));
aSeqTenth.Append(gp_Pnt(0., myYSize, -zl));
for (nblines = 1, xl = aXStep; xl < myXSize; xl += aXStep, nblines++)
{
NCollection_Sequence<gp_Pnt>& aSeq = (Modulus(nblines, 10) != 0) ? aSeqLines : aSeqTenth;
aSeq.Append(gp_Pnt(xl, -myYSize, -zl));
aSeq.Append(gp_Pnt(xl, myYSize, -zl));
aSeq.Append(gp_Pnt(-xl, -myYSize, -zl));
aSeq.Append(gp_Pnt(-xl, myYSize, -zl));
}
// horizontals
aSeqTenth.Append(gp_Pnt(-myXSize, 0., -zl));
aSeqTenth.Append(gp_Pnt(myXSize, 0., -zl));
for (nblines = 1, yl = aYStep; yl < myYSize; yl += aYStep, nblines++)
{
NCollection_Sequence<gp_Pnt>& aSeq = (Modulus(nblines, 10) != 0) ? aSeqLines : aSeqTenth;
aSeq.Append(gp_Pnt(-myXSize, yl, -zl));
aSeq.Append(gp_Pnt(myXSize, yl, -zl));
aSeq.Append(gp_Pnt(-myXSize, -yl, -zl));
aSeq.Append(gp_Pnt(myXSize, -yl, -zl));
}
if (aSeqLines.Length())
{
occ::handle<Graphic3d_AspectLine3d> aLineAspect =
new Graphic3d_AspectLine3d(myColor, Aspect_TOL_SOLID, 1.0);
myGroup->SetPrimitivesAspect(aLineAspect);
const int nbv = aSeqLines.Length();
occ::handle<Graphic3d_ArrayOfSegments> aPrims = new Graphic3d_ArrayOfSegments(nbv);
int n = 1;
while (n <= nbv)
aPrims->AddVertex(aSeqLines(n++));
myGroup->AddPrimitiveArray(aPrims, false);
}
if (aSeqTenth.Length())
{
occ::handle<Graphic3d_AspectLine3d> aLineAspect =
new Graphic3d_AspectLine3d(myTenthColor, Aspect_TOL_SOLID, 1.0);
myGroup->SetPrimitivesAspect(aLineAspect);
const int nbv = aSeqTenth.Length();
occ::handle<Graphic3d_ArrayOfSegments> aPrims = new Graphic3d_ArrayOfSegments(nbv);
int n = 1;
while (n <= nbv)
aPrims->AddVertex(aSeqTenth(n++));
myGroup->AddPrimitiveArray(aPrims, false);
}
myGroup->SetMinMaxValues(-myXSize, -myYSize, -myOffSet, myXSize, myYSize, -myOffSet);
myCurXStep = aXStep, myCurYStep = aYStep;
// update bounding box
myStructure->CalculateBoundBox();
myViewer->StructureManager()->Update(myStructure->GetZLayer());
}
void V3d_RectangularGrid::DefinePoints()
{
const double aXStep = XStep();
const double aYStep = YStep();
const bool toUpdate = !myCurAreDefined || myCurDrawMode != Aspect_GDM_Points
|| aXStep != myCurXStep || aYStep != myCurYStep;
if (!toUpdate && !myToComputePrs)
{
return;
}
else if (!myStructure->IsDisplayed())
{
myToComputePrs = true;
return;
}
myToComputePrs = false;
myGroup->Clear();
// horizontals
double xl, yl;
NCollection_Sequence<gp_Pnt> aSeqPnts;
for (xl = 0.0; xl <= myXSize; xl += aXStep)
{
aSeqPnts.Append(gp_Pnt(xl, 0.0, -myOffSet));
aSeqPnts.Append(gp_Pnt(-xl, 0.0, -myOffSet));
for (yl = aYStep; yl <= myYSize; yl += aYStep)
{
aSeqPnts.Append(gp_Pnt(xl, yl, -myOffSet));
aSeqPnts.Append(gp_Pnt(xl, -yl, -myOffSet));
aSeqPnts.Append(gp_Pnt(-xl, yl, -myOffSet));
aSeqPnts.Append(gp_Pnt(-xl, -yl, -myOffSet));
}
}
if (aSeqPnts.Length())
{
int i;
double X, Y, Z;
const int nbv = aSeqPnts.Length();
occ::handle<Graphic3d_ArrayOfPoints> Vertical = new Graphic3d_ArrayOfPoints(nbv);
for (i = 1; i <= nbv; i++)
{
aSeqPnts(i).Coord(X, Y, Z);
Vertical->AddVertex(X, Y, Z);
}
occ::handle<Graphic3d_AspectMarker3d> aMarkerAspect =
new Graphic3d_AspectMarker3d(Aspect_TOM_POINT, myColor, 3.0);
myGroup->SetGroupPrimitivesAspect(aMarkerAspect);
myGroup->AddPrimitiveArray(Vertical, false);
}
myGroup->SetMinMaxValues(-myXSize, -myYSize, -myOffSet, myXSize, myYSize, -myOffSet);
myCurXStep = aXStep, myCurYStep = aYStep;
// update bounding box
myStructure->CalculateBoundBox();
myViewer->StructureManager()->Update(myStructure->GetZLayer());
}
//=================================================================================================
void V3d_RectangularGrid::GraphicValues(double& theXSize, double& theYSize, double& theOffSet) const
{
theXSize = myXSize;
theYSize = myYSize;
theOffSet = myOffSet;
theXSize = SizeX();
theYSize = SizeY();
theOffSet = ZOffset();
}
//=================================================================================================
void V3d_RectangularGrid::SetGraphicValues(const double theXSize,
const double theYSize,
const double theOffSet)
{
if (!myCurAreDefined)
// The Aspect_RectangularGrid setters each trigger UpdateDisplay() only when the
// value actually changes, so the final UpdateDisplay fires at most once.
SetSizeX(theXSize);
SetSizeY(theYSize);
SetZOffset(theOffSet);
}
//=================================================================================================
void V3d_RectangularGrid::syncViews(const bool theDoDisplay) const
{
if (myViewer == nullptr)
{
myXSize = theXSize;
myYSize = theYSize;
myOffSet = theOffSet;
return;
}
if (myXSize != theXSize)
// XStep/YStep carry world-unit spacing; the shader consumes cells-per-unit (1/step).
// Guard zero/negative step that could come from Aspect_RectangularGrid defaults.
const double aXStep = XStep() > 0.0 ? XStep() : THE_DEFAULT_GRID_STEP;
const double aYStep = YStep() > 0.0 ? YStep() : THE_DEFAULT_GRID_STEP;
const gp_Ax3 aPlane = myViewer->PrivilegedPlane();
// Pass the grid origin as a world-space offset from the plane origin, so the
// shader's aPlaneOrigin = planeLoc + Origin matches V3d_View::Compute's
// aPnt0 = planeLoc - XOrigin * planeX - YOrigin * planeY. Any other
// convention leaves selection snapping to a world point that doesn't line up
// with the drawn grid for tilted privileged planes.
const gp_XYZ aOriginOffset =
aPlane.XDirection().XYZ() * -XOrigin() + aPlane.YDirection().XYZ() * -YOrigin();
Aspect_GridParams aParams;
aParams.SetColor(myColor);
aParams.SetAccentColor(myTenthColor);
aParams.SetOrigin(gp_Pnt(aOriginOffset));
aParams.SetScale(1.0 / aXStep);
aParams.SetScaleY(1.0 / aYStep);
aParams.SetAccentScaleX(0.1 / aXStep);
aParams.SetAccentScaleY(0.1 / aYStep);
aParams.SetRotationAngle(RotationAngle());
aParams.SetDrawMode(DrawMode());
aParams.SetSizeX(SizeX());
aParams.SetSizeY(SizeY());
aParams.SetZOffset(ZOffset());
aParams.SetIsBackground(false);
aParams.SetIsDrawAxis(false);
aParams.SetIsInfinity(false);
for (const occ::handle<V3d_View>& aView : myViewer->DefinedViews())
{
myXSize = theXSize;
myCurAreDefined = false;
if (aView.IsNull())
{
continue;
}
if (theDoDisplay)
{
aView->GridDisplay(aParams, aPlane);
}
else
{
aView->GridErase();
}
}
if (myYSize != theYSize)
{
myYSize = theYSize;
myCurAreDefined = false;
}
if (myOffSet != theOffSet)
{
myOffSet = theOffSet;
myCurAreDefined = false;
}
if (!myCurAreDefined)
UpdateDisplay();
}
//=================================================================================================
@@ -395,20 +190,6 @@ void V3d_RectangularGrid::DumpJson(Standard_OStream& theOStream, int theDepth) c
OCCT_DUMP_TRANSIENT_CLASS_BEGIN(theOStream)
OCCT_DUMP_BASE_CLASS(theOStream, theDepth, Aspect_RectangularGrid)
OCCT_DUMP_FIELD_VALUES_DUMPED(theOStream, theDepth, myStructure.get())
OCCT_DUMP_FIELD_VALUES_DUMPED(theOStream, theDepth, myGroup.get())
OCCT_DUMP_FIELD_VALUES_DUMPED(theOStream, theDepth, &myCurViewPlane)
OCCT_DUMP_FIELD_VALUE_POINTER(theOStream, myViewer)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myCurAreDefined)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myToComputePrs)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myCurDrawMode)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myCurXo)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myCurYo)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myCurAngle)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myCurXStep)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myCurYStep)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myXSize)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myYSize)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myOffSet)
OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myIsDisplayed)
}
@@ -19,14 +19,12 @@
#include <Standard.hxx>
#include <gp_Ax3.hxx>
#include <V3d_ViewerPointer.hxx>
#include <Standard_Boolean.hxx>
#include <Standard_Real.hxx>
#include <Aspect_RectangularGrid.hxx>
class Graphic3d_Structure;
class Graphic3d_Group;
#include <V3d_ViewerPointer.hxx>
//! Rectangular grid bound to a V3d_Viewer. Snap math (Compute/Hit) is inherited
//! from Aspect_RectangularGrid; rendering goes through the shader-based infinite
//! grid (OpenGl_View::renderGrid) instead of CPU-generated line segments.
class V3d_RectangularGrid : public Aspect_RectangularGrid
{
DEFINE_STANDARD_RTTIEXT(V3d_RectangularGrid, Aspect_RectangularGrid)
@@ -59,30 +57,13 @@ protected:
Standard_EXPORT void UpdateDisplay() override;
private:
Standard_EXPORT void DefineLines();
Standard_EXPORT void DefinePoints();
//! Broadcast current parameters to every view owned by the viewer.
//! When theDoDisplay is false, erases the shader grid from each view instead.
void syncViews(const bool theDoDisplay) const;
private:
//! Custom Graphic3d_Structure implementation.
class RectangularGridStructure;
private:
occ::handle<Graphic3d_Structure> myStructure;
occ::handle<Graphic3d_Group> myGroup;
gp_Ax3 myCurViewPlane;
V3d_ViewerPointer myViewer;
bool myCurAreDefined;
bool myToComputePrs;
Aspect_GridDrawMode myCurDrawMode;
double myCurXo;
double myCurYo;
double myCurAngle;
double myCurXStep;
double myCurYStep;
double myXSize;
double myYSize;
double myOffSet;
V3d_ViewerPointer myViewer;
mutable bool myIsDisplayed;
};
#endif // _V3d_RectangularGrid_HeaderFile
+21
View File
@@ -3402,6 +3402,27 @@ void V3d_View::SetGridActivity(const bool AFlag)
//=================================================================================================
void V3d_View::GridDisplay(const Aspect_GridParams& theParams)
{
GridDisplay(theParams, MyViewer->PrivilegedPlane());
}
//=================================================================================================
void V3d_View::GridDisplay(const Aspect_GridParams& theParams, const gp_Ax3& thePlane)
{
myView->GridDisplay(theParams, thePlane);
}
//=================================================================================================
void V3d_View::GridErase()
{
myView->GridErase();
}
//=================================================================================================
void toPolarCoords(const double theX, const double theY, double& theR, double& thePhi)
{
theR = std::sqrt(theX * theX + theY * theY);
+17
View File
@@ -17,6 +17,7 @@
#ifndef _V3d_View_HeaderFile
#define _V3d_View_HeaderFile
#include <Aspect_GridParams.hxx>
#include <Graphic3d_ClipPlane.hxx>
#include <Graphic3d_Texture2D.hxx>
#include <Graphic3d_TypeOfShadingModel.hxx>
@@ -914,6 +915,22 @@ public:
//! grid in <me>
Standard_EXPORT void SetGridActivity(const bool aFlag);
//! Display a shader-rendered grid on the viewer's privileged plane.
//! @param[in] theParams render-only appearance (color, scale, bounds, arc,
//! draw-mode, background/inf flags); snap geometry still comes
//! from the classical Aspect_*Grid on the viewer.
Standard_EXPORT void GridDisplay(const Aspect_GridParams& theParams);
//! Display a shader-rendered grid on an explicit plane (overrides the
//! viewer's privileged plane for this view only).
//! @param[in] theParams appearance parameters; see the single-argument overload.
//! @param[in] thePlane world-space grid plane (origin + axes).
Standard_EXPORT void GridDisplay(const Aspect_GridParams& theParams, const gp_Ax3& thePlane);
//! Erase the shader-rendered grid from this view. Does not touch the
//! viewer's classical grid activation used by snap.
Standard_EXPORT void GridErase();
//! Dumps the full contents of the View into the image file. This is an alias for ToPixMap() with
//! Image_AlienPixMap.
//! @param theFile destination image file (image format is determined by file extension like .png,
+3
View File
@@ -12,6 +12,7 @@ vraytrace 0
vgrid -type rectangular
vraytrace 1
checkcolor 198 197 0.5 0.5 0.5
vdump $imagedir/${casename}_rectangular.png
vclose
# Circular Grid
@@ -21,3 +22,5 @@ vraytrace 0
vgrid -type circular
vraytrace 1
checkcolor 198 197 0.5 0.5 0.5
vdump $imagedir/${casename}_circular.png
vclose
+33
View File
@@ -0,0 +1,33 @@
puts "=================================================================="
puts "0030979: Bounded circular shader grid (-radius, -arc support)"
puts "=================================================================="
pload MODELING VISUALIZATION
vclear
vinit View1 w=400 h=400
vaxo
box b 1 1 0.1
vdisplay b -dispMode 1
vfit
vzoom 0.3
# Full disc within radius 2.5
vgrid -type circ -step 0.25 16 -radius 2.5
vdump $imagedir/${casename}_disc.png
# Half circle (0..pi, walking CCW).
vgrid -type circ -step 0.25 16 -radius 2.5 -arc 0 3.14159
vdump $imagedir/${casename}_halfcircle.png
# Quarter (upper-right).
vgrid -type circ -step 0.25 16 -radius 2.5 -arc 0 1.5707
vdump $imagedir/${casename}_quarter.png
# Wraparound: 3pi/4 .. -3pi/4 covers the back pi/2 region by going CCW
# through +pi and wrapping to -pi (270-degree arc around the far side).
vgrid -type circ -step 0.25 16 -radius 2.5 -arc 2.356 -2.356
vdump $imagedir/${casename}_wraparound.png
vgrid off
+30
View File
@@ -0,0 +1,30 @@
puts "=================================================================="
puts "0030979: Bounded rectangular shader grid (-size X Y clips the area)"
puts "=================================================================="
pload MODELING VISUALIZATION
vclear
vinit View1 w=400 h=400
vaxo
box b 3 3 0.1
vdisplay b -dispMode 1
vfit
vzoom 0.5
# Full square bound: 4x4 world-unit patch centered at plane origin.
vgrid -type rect -step 0.5 0.5 -size 4 4
vdump $imagedir/${casename}_full_square.png
# Rectangular bound with different extents in X/Y.
# NOTE: vgrid -size expects strictly positive X and Y values.
vgrid -type rect -step 0.5 0.5 -size 4 8
vdump $imagedir/${casename}_strip_x.png
# Same width but with a slight z-offset so the grid sinks below the box's
# top face - used to avoid z-fighting in dense-coplanar scenes.
vgrid -type rect -step 0.5 0.5 -size 4 4 -zoffset -0.01
vdump $imagedir/${casename}_zoffset.png
vgrid off
+28
View File
@@ -0,0 +1,28 @@
puts "=================================================================="
puts "0030979: V3d_CircularGrid now renders through the polar shader path"
puts "=================================================================="
pload MODELING VISUALIZATION
vclear
vinit View1
vaxo
box b 1 2 3
vdisplay b -dispMode 1
vfit
vzoom 0.2
# Classical circular grid: 8 divisions (22.5 deg spokes), radius step 0.5
vgrid -type circ -step 0.5 8
vdump $imagedir/${casename}_circ.png
# Finer angular resolution, bigger radial spacing
vgrid -type circ -step 1.0 24
vdump $imagedir/${casename}_circ_fine.png
# Rotated
vgrid -type circ -step 0.5 12 -rotAngle 0.4
vdump $imagedir/${casename}_circ_rotated.png
vgrid off
+31
View File
@@ -0,0 +1,31 @@
puts "=================================================================="
puts "0030979: vgrid -type inf keeps step, mode, rotation and disc/wedge"
puts "=================================================================="
pload MODELING VISUALIZATION
vclear
vinit View1 w=400 h=400
vaxo
box b 2 2 0.2
vdisplay b -dispMode 1
vfit
vzoom 0.25
# Step-driven anisotropic spacing and rotation must survive the final GridDisplay
# override; before the fix the display snapped back to default line mode / zero
# rotation whenever -type inf was used.
vgrid -type inf -step 0.5 1.0 -rotAngle 0.4
vdump $imagedir/${casename}_rotated.png
# Points mode used to be lost in the same override path.
vgrid -type inf -step 0.5 1.0 -mode points
vdump $imagedir/${casename}_points.png
# Radius/arc now clip the infinite rectangular grid to a disc sector instead of
# being silently ignored.
vgrid -type inf -step 0.5 0.5 -radius 2.0 -arc 0 1.5707
vdump $imagedir/${casename}_sector.png
vgrid off
+21
View File
@@ -0,0 +1,21 @@
puts "======================================================"
puts "0030979: shader infinite grid stays fixed during panning"
puts "======================================================"
pload MODELING VISUALIZATION
vclear
vinit View1
vaxo
box b 1 2 3
vdisplay b -dispMode 1
vfit
vzoom 0.2
vgrid -type inf -background 1
vdump $imagedir/${casename}_before.png
vpan 80 40
vdump $imagedir/${casename}_after.png
vgrid off
+20
View File
@@ -0,0 +1,20 @@
puts "==========================================================="
puts "0030979: shader infinite grid follows non-default privileged"
puts "plane (YZ plane: normal +X)."
puts "==========================================================="
pload MODELING VISUALIZATION
vclear
vinit View1
vaxo
box b 1 2 3
vdisplay b -dispMode 1
vfit
vzoom 0.2
vpriviledgedplane 0 0 0 1 0 0 0 1 0
vgrid -type inf -color 0.2 0.8 0.3
vdump $imagedir/${casename}_yz.png
vgrid off
+21
View File
@@ -0,0 +1,21 @@
puts "======================================================="
puts "0030979: shader infinite grid stays fixed during rotation"
puts "======================================================="
pload MODELING VISUALIZATION
vclear
vinit View1
vaxo
box b 1 2 3
vdisplay b -dispMode 1
vfit
vzoom 0.2
vgrid -type inf -background 1
vdump $imagedir/${casename}_before.png
vrotate 0 0 0.5
vdump $imagedir/${casename}_after.png
vgrid off
+26
View File
@@ -0,0 +1,26 @@
puts "=================================================="
puts "0030979: Visualization - shader-based infinite grid"
puts "Orthographic projection"
puts "=================================================="
pload MODELING VISUALIZATION
vclear
vinit View1
vaxo
box b 1 2 3
vdisplay b -dispMode 1
vcamera -ortho
vfit
vzoom 0.1
vgrid -type inf
vdump $imagedir/${casename}_0.png
vgrid off
vgrid -type inf -background 1 -drawAxis 0 -color 0 0 1 -origin 1 1 -inf 1 -lineThickness 0.05
vdump $imagedir/${casename}_1.png
vgrid off
+26
View File
@@ -0,0 +1,26 @@
puts "=================================================="
puts "0030979: Visualization - shader-based infinite grid"
puts "Perspective projection"
puts "=================================================="
pload MODELING VISUALIZATION
vclear
vinit View1
vaxo
box b 1 2 3
vdisplay b -dispMode 1
vcamera -persp
vfit
vzoom 0.1
vgrid -type inf
vdump $imagedir/${casename}_0.png
vgrid off
vgrid -type inf -background 1 -drawAxis 0 -color 0 0 1 -origin 1 1 -inf 1 -lineThickness 0.05
vdump $imagedir/${casename}_1.png
vgrid off
+29
View File
@@ -0,0 +1,29 @@
puts "=================================================================="
puts "0030979: Aspect_GDM_Points renders dots at rectangular grid intersections"
puts "=================================================================="
pload MODELING VISUALIZATION
vclear
vinit View1
vaxo
box b 1 2 3
vdisplay b -dispMode 1
vfit
vzoom 0.2
# Lines mode (default): full grid with horizontal and vertical lines.
vgrid -type rect -step 0.5 0.5 -mode lines
vdump $imagedir/${casename}_lines.png
# Points mode: only the intersections light up (uDrawMode=1 in shader,
# alpha = aAlphaX * aAlphaY instead of max(aAlphaX, aAlphaY)).
vgrid -type rect -step 0.5 0.5 -mode points
vdump $imagedir/${casename}_points.png
# Circular grid with points (dots at ring x spoke intersections).
vgrid -type circ -step 0.5 8 -mode points
vdump $imagedir/${casename}_circ_points.png
vgrid off
+29
View File
@@ -0,0 +1,29 @@
puts "=================================================================="
puts "0030979: classical vgrid -type rect now renders through the shader"
puts "=================================================================="
pload MODELING VISUALIZATION
vclear
vinit View1
vaxo
box b 1 2 3
vdisplay b -dispMode 1
vfit
vzoom 0.2
# Classical rectangular grid, routed through the infinite-grid shader:
# XStep/YStep map to Scale/ScaleY, RotationAngle rotates in-plane basis.
vgrid -type rect -step 0.5 0.5 -rotAngle 0
vdump $imagedir/${casename}_rect.png
# Non-isotropic step (different X and Y spacing)
vgrid -type rect -step 0.5 1.0
vdump $imagedir/${casename}_rect_aniso.png
# In-plane rotation
vgrid -type rect -step 0.5 0.5 -rotAngle 0.5
vdump $imagedir/${casename}_rect_rotated.png
vgrid off
+1
View File
@@ -14,3 +14,4 @@
014 trihedron
015 trsf
016 viewcube
017 grid