mirror of
https://github.com/Open-Cascade-SAS/OCCT.git
synced 2026-08-07 22:07:33 +08:00
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:
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -14,3 +14,4 @@
|
||||
014 trihedron
|
||||
015 trsf
|
||||
016 viewcube
|
||||
017 grid
|
||||
|
||||
Reference in New Issue
Block a user