Class Viewport3D
RenderTarget (with
MSAA) and composites it as a normal 2D layer, proving that GPU-rendered content
respects the scene's layering: overlays, dialogs, tooltips and clipping all
apply to it because it draws as one quad in the paint order.
For this milestone it shows the backend's built-in demo scene (a spinning,
depth-tested cube). It drives a continuous repaint while showing (like the
animation system) and pauses automatically when hidden; static content should
opt out via setAnimated(false) to repaint only on
invalidation. Its render target is released on detach via
Scene.disposeLater(limn.graphics.GpuSurface) (deferred to a frame with the owning GL context
current).
Size axis: this widget does not participate. Its content is a GPU render
target sized in device pixels by setPreferredSize(float, float) and the layout, not a
control laid out from a metric row, so no ControlSize step
changes what the 3D scene looks like. The one thing that is chrome (the
"no GPU backend" placeholder frame and its message) reads the row resolved on this
widget like every other component, so a viewport dropped into an XSMALL panel does
not fall back with a MEDIUM corner radius and MEDIUM body type.
Direction axis: this widget does not participate either, and for a sharper reason
than the size one. A rendered scene is content, so a right-to-left window shows the same world
from the same side; and rayAt(float, float) takes viewport-local pixels, which are where the pointer
physically is, so picking, orbiting and the camera's normalised device coordinates all stay
physical. Reflecting any of them would put the pick on the far side of the scene from the
cursor. The chrome carve-out is the same one: the placeholder message is shaped for the
direction resolved on this widget, and stays centred, because a centre does not move.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic interfaceDraws the 3D scene into a pass each frame;timeSecondsadvances while showing. -
Constructor Summary
ConstructorsConstructorDescriptionAn empty viewport; give it something to draw withsetRenderer(limn.components.Viewport3D.Renderer). -
Method Summary
Modifier and TypeMethodDescriptioncamera()The camera this viewport renders with: mutable, and mutated by any controller.captureNext(Consumer<Image> sink) Hands the next rendered frame of this viewport tosinkas anImage, then forgets it: one capture per call, not a subscription.frameContent(Aabb box) Points the camera atboxand backs it off far enough to hold the whole of it, keeping the current viewing direction and field of view, and moving the clip planes to bracket what it framed.frameContent(Aabb box, float margin) frameContent(Aabb)with an explicit margin, the factor the fitted distance is multiplied by, so 1 touches the edges and 1.1 leaves a tenth of the frame around the content.Fires with the world-space ray on a click (no drag); feed it toPicker.protected voidCalled when this widget leaves the scene (detached from the tree).Registers cleanup for GPU resources owned by the renderer, typically a retained scene'sScene3D::dispose.protected voidonLayout()Containers position children here (measure +Widget.layoutBox(float, float, float, float)per child).protected SizeonMeasure(Constraints constraints) Reports the size this widget wants withinconstraints.protected voidonMouseEvent(MouseEvent event) Mouse events (bubbling); callevent.consume()when handled.protected voidWidget's own background/content, in local coordinates.rayAt(float localX, float localY) The world-space ray through a viewport-local pixel (for picking).floatThe fraction of device resolution the 3D content is rendered at; 1 by default.setAnimated(boolean animated) Whether the viewport drives a continuous repaint while showing (defaulttrue: right for content that moves every frame;timeSecondskeeps advancing).Replaces the camera.setController(CameraController controller) Installs a camera controller (e.g.setPreferredSize(float width, float height) The size this viewport asks for, in logical points.setRenderer(Viewport3D.Renderer renderer) Sets the render callback; without one, the built-in demo cube is shown.setRenderScale(float scale) Renders the 3D content at a fraction of the viewport's device resolution and upsamples it into the widget's box.Methods inherited from class limn.scene.Widget
acceptsTextInput, add, baselineOffset, baselineOffsetOf, caretRect, children, clipboard, clipsChildren, controlSize, cursor, declaredControlSize, declaredLayoutDirection, declaredLocale, focusArrivedByTraversal, height, hitTest, imageCursor, invalidate, invalidate, isEnabled, isFocusable, isFocused, isShowing, isVisible, layoutBox, layoutDirection, locale, localToSceneX, localToSceneY, markNeedsContainedLayout, markNeedsLayout, measure, moveChild, neutralBase, onAttached, onCharTyped, onFileDrop, onFocusGained, onFocusLost, onKeyEvent, onPaintOverlay, onPreedit, overlayPassesPointer, paintChildren, paintOutset, paintWidget, parent, remove, requestFocus, revealInView, scene, sceneToLocalX, sceneToLocalY, setControlSize, setCursor, setEnabled, setFocusable, setImageCursor, setInheritanceHost, setLayoutDirection, setLocale, setTooltip, setTooltip, setVisible, shapeText, textRuler, tooltip, width, x, y
-
Constructor Details
-
Viewport3D
public Viewport3D()An empty viewport; give it something to draw withsetRenderer(limn.components.Viewport3D.Renderer).
-
-
Method Details
-
setRenderer
Sets the render callback; without one, the built-in demo cube is shown. -
setAnimated
Whether the viewport drives a continuous repaint while showing (defaulttrue: right for content that moves every frame;timeSecondskeeps advancing). Setfalsefor static content: it then re-renders only when invalidated: camera controllers already invalidate on drag/zoom; callWidget.invalidate()after mutating the scene. An idle static viewport costs zero CPU/GPU, like the rest of the toolkit. -
onDispose
Registers cleanup for GPU resources owned by the renderer, typically a retained scene'sScene3D::dispose. It runs when this viewport is detached from the tree, deferred to the owning window's next frame so the GL context is current (the same path that releases the render target). Kept across re-attachments: a renderer that rebuilds lazily should also reset its own reference in this callback. -
setController
Installs a camera controller (e.g.new OrbitController(viewport.camera())). -
captureNext
Hands the next rendered frame of this viewport tosinkas anImage, then forgets it: one capture per call, not a subscription. Requests a repaint, so it works on a viewport that has stopped animating.The image is display-referred: what the viewport shows, with exposure, tonemap and sRGB encode applied, straight alpha, top-down. It is ready to hand to
Images.encode(limn.graphics.Image, limn.graphics.ImageEncodeOptions, java.io.OutputStream). The render target's own scene-referred contents are a different picture and are reached through the target itself, not here; seeReadableSurface.sinkruns on the UI thread inside that frame, so the read cannot be moved off it; a large capture should encode asynchronously (Images.saveAsync(limn.graphics.Image, limn.graphics.ImageEncodeOptions, java.nio.file.Path)) rather than inside the sink.- Throws:
NullPointerException- ifsinkis null
-
onClick
Fires with the world-space ray on a click (no drag); feed it toPicker. -
setRenderScale
Renders the 3D content at a fraction of the viewport's device resolution and upsamples it into the widget's box. Clamped to[0.25, 1]; 1 (the default) renders at full resolution.This is the cost lever for a heavy 3D view. The target holds four half-float channels per pixel, so both its memory and its fill cost fall with the square of the scale: 0.5 is a quarter of both. Nothing else changes: the widget's box, the 2D layout around it and
rayAt(float, float)picking are all in logical points and do not move.What it costs is sharpness. The upsample is bilinear, so edges soften; geometry with thin features shows it first. Multisampling still applies, at the reduced resolution.
-
renderScale
public float renderScale()The fraction of device resolution the 3D content is rendered at; 1 by default. -
frameContent
Points the camera atboxand backs it off far enough to hold the whole of it, keeping the current viewing direction and field of view, and moving the clip planes to bracket what it framed.Fitting needs the viewport's aspect ratio, so a call made before the first layout takes effect on the layout pass that supplies one instead of being lost, which is how to frame content once, at construction. An empty box is ignored.
The viewport does not know what its renderer draws, so it cannot find the box itself; a scene graph reports one through
Scene3D.bounds(). -
frameContent
frameContent(Aabb)with an explicit margin, the factor the fitted distance is multiplied by, so 1 touches the edges and 1.1 leaves a tenth of the frame around the content. Values below 1 crop it. -
rayAt
The world-space ray through a viewport-local pixel (for picking).localXis physical and stays physical in a right-to-left viewport: it is a distance from the viewport's left edge, because that is what the pointer reports and what an application computing its own coordinates will pass. A layout direction is a reading order and this argument is not read; reflecting it here would return the ray through the mirror image of the pixel the caller named. -
onMouseEvent
Description copied from class:WidgetMouse events (bubbling); callevent.consume()when handled.- Overrides:
onMouseEventin classWidget
-
setCamera
Replaces the camera. The instance is kept, so a controller can keep mutating it. -
camera
The camera this viewport renders with: mutable, and mutated by any controller. -
setPreferredSize
The size this viewport asks for, in logical points. The layout may still override it. -
onMeasure
Description copied from class:WidgetReports the size this widget wants withinconstraints. Called once per layout pass, and the result is cached against the constraints and the resolved axes (size step, layout direction, locale), so it must be a pure function of them and of this widget's own state.Resolve the
ControlSizeand theLayoutDirectiononce each here and thread them down; never read either in a constructor. The locale needs no threading: it is in scope, andI18n.locale()answers it wherever text is resolved. -
onLayout
protected void onLayout()Description copied from class:WidgetContainers position children here (measure +Widget.layoutBox(float, float, float, float)per child). -
onPaint
Description copied from class:WidgetWidget's own background/content, in local coordinates. -
onDetached
protected void onDetached()Description copied from class:WidgetCalled when this widget leaves the scene (detached from the tree).Widget.scene()still answers the scene being left, and becomesnullonce this returns, so releasing something the scene owns needs no field of your own to remember it by. Fires bottom-up, so children run before their parent, and every one of them can still reach the scene.Release resources here, but GPU resources must be handed to
Scene.disposeLater(limn.graphics.GpuSurface)(disposal needs the owning GL context, which is only current inside a frame). Not called onWidget.setVisible(boolean). Default no-op.- Overrides:
onDetachedin classWidget
-