The ground every 3D lab stands on: an endless sheet of squared paper, plus the light rig and the environment that go with it. More...
| Import Statement: | import Clayground.Lab |
Whatever a lab is about, it starts on the same well-made surface. The stage owns the environment a lab hands to its View3D, the three-light rig with its measured shadow tuning, and a ground plane whose raster is computed in the fragment shader from world coordinates - millimeter paper on the light palette, blueprint on the dark one, one line weight at any zoom, dissolving into the sky colour long before the quad runs out. There is no image anywhere in it, and no edge to see.
It also carries the two things a lab has to be able to do with its ground: worldAt() answers "what world point is under this pixel", and the overlay budget below says how far above the plane a flat marking has to sit before it stops fighting for the same depth.
Three labs used to build this themselves - a table Model, a sheet Model, a rim, three lights, and between them 585 individual peg Models to draw the grid. A lab now wires the whole stage in about ten lines:
import QtQuick3D import Clayground.Lab View3D { id: view3d LabStage3D { id: stage cellSize: root.cell gridMode: grid // crosses vs dots workExtent: Qt.vector2d(root.boardW, root.boardH) shadowMapFar: 250 // measured, per lab } environment: stage.environment OrbitCamera3D { id: rig } camera: rig.camera }
See also GridMode, LabTheme, and SelectionFrame3D.
cellSize : real |
Spacing of the fine rules and of the snap cue, in world units.
Match it to whatever the lab's placement rounds to - it is the raster a learner is being shown, so it has to be the raster they land on.
cueColor : color |
The snap cue: the peg role, one step further from the paper.
A peg used to be a solid little tile on an otherwise blank sheet, where the plain grid role carried it. Here it lands on top of the rules, so it needs a step of its own to still read as a mark rather than as a thickening of the line it sits on.
cueSize : real |
Arm length of a snap cross / diameter of a free dot, in pixels.
cueWidth : real |
Stroke weight of the snap cue, in pixels.
edgeColor : color |
The work-area boundary.
edgeWidth : real |
Work-area boundary weight, in pixels; 0 hides it.
environment : SceneEnvironment |
The environment the lab assigns to its View3D.
The clear colour is the sky the ground fades into, so the two have to come from the same place or the horizon reappears as a ring.
gridMode : var |
The GridMode whose mode the surface shows, or a plain bool.
GridMode itself draws nothing; this is what draws it. While it snaps the intersections carry crosses, and when placement is free they carry dots - so the board says which mode you are in without being asked. Leave it null on a lab with no placement at all and the cue stays at crosses.
ground : Model |
The pick plane itself, for a lab that has to compare hits.
horizonFar : real |
Distance at which it has become the sky exactly.
Past this the plane is drawn in the environment's own clear colour, so where it ends cannot be seen - that is what makes it read as endless rather than as a very large table.
horizonNear : real |
Distance from the origin at which the surface starts dissolving.
keyBrightness : real |
Strength of the shadow-casting key light.
lightsEnabled : bool |
Set false for a lab that brings its own lighting.
majorColor : color |
The heavy rules.
majorEvery : real |
How many cells make one heavy rule. Defaults to 5, as paper does.
majorWidth : real |
Heavy rule weight, in pixels.
minorColor : color |
The fine rules.
minorWidth : real |
Fine rule weight, in pixels.
overlayMaxBias : int |
Highest one worth using.
overlayMaxY : real |
The highest it may sit and still read as a marking ON the ground.
Above this a ribbon starts to float: its shadow-side gap opens up at grazing angles and it stops belonging to the surface.
overlayMinBias : int |
Lowest sensible depthBias for a marking.
overlayMinY : real |
The lowest a flat overlay may sit and still clear the plane.
overlayStep : real |
Vertical gap between two overlays that must not fight each other.
rasterOrigin : vector2d |
A world point that an intersection of the raster sits on.
Defaults to the origin, which is what a lab whose raster is centred on it wants. A board with an even number of cells is not: electronics-101 has 20 columns of 5, so its pegs land on the half-cells and the crosses have to land with them. Any point on the raster does - the shader only reads it modulo the cell.
shadowMapFar : real |
How far the shadow volume reaches, measured from the camera.
It has to cover the scene at maximum zoom-out and no further: the cascades spend their texels over whatever it is told to cover, so a range set to the horizon buys a blurry smudge instead of a shadow. The default is sized from workExtent; a lab that has measured its own number keeps it.
sheetColor : color |
The working surface.
skyColor : color |
What the surface dissolves into - the environment's clear colour.
snapping : bool |
The mode the surface is showing.
tableColor : color |
Outside the work area.
toneScale : real |
What the light rig gives back on a flat, upward-facing surface.
The ground is lit by the same half-lambert rule as the toon objects standing on it, and three lights all shining down onto one plane add up to more than one - so a paper colour handed over raw comes back white and the sheet/table step disappears into the clipping. This divides it back out, and it is measured against the palette rather than chosen: the plain table region of a render lands on LabTheme.table within a couple of levels per channel (0.71 puts the table on 230/226/222 against the palette's 232/228/221). Retune it if keyBrightness or the fill lights change.
workExtent : vector2d |
Full width and depth of the area the lab actually works in.
The sheet is drawn over it, a quiet rule marks where it ends, the snap cue is kept inside it, and the shadow and horizon defaults are sized from it. (0, 0), the default, means "no boundary": the surface is then one continuous table.
workRadius : real |
Half the work area's diagonal.
real overlayY(int layer) |
Height for the layer-th flat overlay, counted from the ground.
var worldAt(var view, real mx, real my) |
The point on the ground under viewport pixel (mx, my).
Returns a vector3d on the y = 0 plane, or null when the ray never gets there - it is aimed at the sky, or runs parallel to the ground.
Where the ray crosses y = 0, worked out from the ray itself rather than picked against the quad. Two reasons, both of which cost a session elsewhere: a pick is a triangle test, and the quad's own diagonal runs exactly through the world origin - the one point every lab cares about, and the one pixel where the pick comes back empty. And a pick answers "what is in front", so the day a lab makes anything else pickable, its mouse editing quietly stops working over that object.