Configuration
These types control viewport appearance, rendering fidelity, and picking. ViewportConfiguration is the single struct passed to MetalViewportView (or mutated on ViewportController.configuration) that governs everything from camera limits and grid style to anti-aliasing and tessellation quality. ClipPlane defines section cuts. PickingConfiguration gates the GPU pick-ID buffer. The supporting enums — RenderingQuality, RenderLayer, AxisStyle, GridStyle, and ViewCubePosition — are referenced by fields inside ViewportConfiguration.
GestureConfiguration and DynamicPivotConfiguration are documented on the Input and Camera pages respectively — ViewportConfiguration embeds them by value.
Topics
- ViewportConfiguration · RenderingQuality · RenderLayer · AxisStyle · GridStyle · ViewCubePosition · PickingConfiguration · ClipPlane
ViewportConfiguration
ViewportConfiguration is the master configuration value type for a viewport instance. It is Sendable and can be constructed with any subset of its parameters overridden; every field has a default. Pass it to MetalViewportView(configuration:) or set ViewportController.configuration at runtime to apply changes.
Camera fields
initialCameraState
public var initialCameraState: CameraState // default: .isometric
The camera state loaded when the viewport first appears. Changing this after the viewport is created has no effect unless the host resets the camera explicitly.
rotationStyle
public var rotationStyle: RotationStyle // default: .turntable
Controls how drag gestures orbit the camera. See RotationStyle on the Camera page.
minDistance
public var minDistance: Float // default: 0.1
Minimum camera-to-pivot distance in world units. Prevents the camera from passing through the orbit center when the user zooms in.
maxDistance
public var maxDistance: Float // default: 10000
Maximum camera-to-pivot distance in world units. Caps the zoom-out range.
defaultFieldOfView
public var defaultFieldOfView: Float // default: 45
Vertical field of view in degrees for the initial perspective projection.
gestureConfiguration
public var gestureConfiguration: GestureConfiguration // default: .default
Platform-specific gesture mapping. See GestureConfiguration.
Display fields
displayMode
public var displayMode: DisplayMode // default: .shaded
Geometry display mode (shaded, wireframe, hidden-line, etc.). See DisplayMode.
lightingConfiguration
public var lightingConfiguration: LightingConfiguration // default: .threePoint
Lighting preset and per-light properties. See LightingConfiguration.
showViewCube
public var showViewCube: Bool // default: true
Whether the 3D navigation cube overlay is visible.
viewCubePosition
public var viewCubePosition: ViewCubePosition // default: .bottomTrailing
Corner of the viewport where the navigation cube is placed. See ViewCubePosition.
showOrientationGnomon
public var showOrientationGnomon: Bool // default: false
Whether the screen-space orientation gnomon (corner RGB axis triad HUD) is shown.
showScaleBar
public var showScaleBar: Bool // default: false
Whether the screen-space scale bar HUD is shown.
scaleBarUnitLabel
public var scaleBarUnitLabel: String // default: ""
Unit suffix appended to the scale bar readout (e.g. "mm"). Pass an empty string for a bare number.
showAxes
public var showAxes: Bool // default: false
Whether the world-space coordinate axes are rendered.
axisLength
public var axisLength: Float // default: 2.0
Length of the coordinate axis cylinders in world units.
axisRadius
public var axisRadius: Float // default: 0.02
Radius of the axis cylinders (or base radius when axisStyle == .constantScreenWidth).
axisStyle
public var axisStyle: AxisStyle // default: .cylinder
Whether axis cylinders scale with camera distance to maintain constant on-screen width. See AxisStyle.
showGrid
public var showGrid: Bool // default: true
Whether the ground grid is rendered.
gridStyle
public var gridStyle: GridStyle // default: .plane
Solid plane or adaptive dot grid. See GridStyle.
gridSize
public var gridSize: Float // default: 100.0
Half-extent of the grid plane in world units (.plane style only).
gridBaseSpacing
public var gridBaseSpacing: Float // default: 1.0
Fundamental grid spacing in world units (.dots style). The renderer snaps to multiples of this value as the camera zooms.
gridSubdivisions
public var gridSubdivisions: Int // default: 10
Number of subdivisions between major grid levels (.dots style).
backgroundColor
public var backgroundColor: SIMD4<Float> // default: SIMD4<Float>(0.95, 0.95, 0.95, 1.0)
Viewport background color as linear RGBA with premultiplied-alpha conventions. The default is a near-white light grey.
Anti-aliasing
msaaSampleCount
public var msaaSampleCount: Int // default: 4
Metal MSAA sample count for the main color/depth attachments. Must be 1 (no MSAA) or 4 (4× MSAA). Use 1 in the .performance preset to reclaim fillrate on mobile.
enableTAA
public var enableTAA: Bool // default: false
Whether temporal anti-aliasing is composited over the rendered frame.
taaBlendFactor
public var taaBlendFactor: Float // default: 0.9
History blend weight for TAA. 0.0 = no history (effectively disables TAA), 1.0 = full history (maximum smoothing, but ghosting on fast camera moves). Meaningful only when enableTAA == true.
Silhouettes
enableSilhouettes
public var enableSilhouettes: Bool // default: true
Whether a screen-space edge-darkening silhouette pass is applied after geometry rendering.
silhouetteThickness
public var silhouetteThickness: Float // default: 1.0
Edge thickness multiplier. 1.0 = normal, 2.0 = twice as thick.
silhouetteIntensity
public var silhouetteIntensity: Float // default: 0.7
Darkness of detected edges. 0.0 = invisible (silhouette pass does nothing), 1.0 = fully darkened.
Frustum culling
enableFrustumCulling
public var enableFrustumCulling: Bool // default: true
When true, bodies whose world-space bounding box falls entirely outside the camera frustum are skipped during draw encoding. Bodies with no boundingBox are never culled. The shadow map pass is exempt (off-screen casters can shadow visible geometry).
Example — disable for debugging:
var config = ViewportConfiguration.cad
config.enableFrustumCulling = false
Normal smoothing
autoSmoothNormals
public var autoSmoothNormals: Bool // default: false
When true, crease-aware normal smoothing is applied to each body’s mesh when its GPU buffers are first built. Flat-shaded meshes (per-face normals from some tessellators) cannot be rounded by Phong tessellation alone; this averages normals across shared vertices while preserving hard edges. Computed once per body and cached. Enabled by the .cadHighQuality preset.
normalSmoothingCreaseAngle
public var normalSmoothingCreaseAngle: Float // default: 0.524 (~30°, in radians)
Crease angle threshold for autoSmoothNormals. Adjacent face normals that differ by more than this angle are treated as a hard edge and kept sharp. Only meaningful when autoSmoothNormals == true.
Picking
pickingConfiguration
public var pickingConfiguration: PickingConfiguration // default: .init()
Governs whether the GPU pick-ID texture is allocated and written. See PickingConfiguration.
Depth of field
enableDepthOfField
public var enableDepthOfField: Bool // default: false
Whether a post-process depth-of-field blur is applied.
dofAperture
public var dofAperture: Float // default: 2.8
Simulated f-number. Smaller values produce a shallower (stronger) blur.
dofFocalDistance
public var dofFocalDistance: Float // default: 0
Focus distance in world units from the camera. 0 activates autofocus, centering focus on the selection or scene center.
dofMaxBlurRadius
public var dofMaxBlurRadius: Float // default: 8.0
Maximum circle-of-confusion radius in pixels. Clamps the blur on very out-of-focus regions.
Rendering quality
renderingQuality
public var renderingQuality: RenderingQuality // default: .standard
Controls tessellation tier and mesh-shader usage. See RenderingQuality.
tessellationMaxFactor
public var tessellationMaxFactor: Int // default: 32
Maximum hardware tessellation factor per edge (1–64). Only used when renderingQuality == .enhanced or .maximum. The .cadHighQuality preset raises this to 48.
adaptiveTessellation
public var adaptiveTessellation: Bool // default: true
When true, the tessellation level adapts per edge to the projected screen-space length and surface curvature, concentrating triangles where the mesh is coarse relative to the viewport. Requires renderingQuality != .standard.
Dynamic pivot
dynamicPivotConfiguration
public var dynamicPivotConfiguration: DynamicPivotConfiguration // default: .default
Configuration for the automatic orbit-pivot heuristic that shifts the orbit center from the scene centroid to the surface under the cursor as the user zooms in. See DynamicPivotConfiguration.
init(...)
public init(
initialCameraState: CameraState = .isometric,
rotationStyle: RotationStyle = .turntable,
minDistance: Float = 0.1,
maxDistance: Float = 10000,
defaultFieldOfView: Float = 45,
gestureConfiguration: GestureConfiguration = .default,
displayMode: DisplayMode = .shaded,
lightingConfiguration: LightingConfiguration = .threePoint,
showViewCube: Bool = true,
viewCubePosition: ViewCubePosition = .bottomTrailing,
showAxes: Bool = false,
axisLength: Float = 2.0,
axisRadius: Float = 0.02,
axisStyle: AxisStyle = .cylinder,
showGrid: Bool = true,
gridStyle: GridStyle = .plane,
gridSize: Float = 100.0,
gridBaseSpacing: Float = 1.0,
gridSubdivisions: Int = 10,
backgroundColor: SIMD4<Float> = SIMD4<Float>(0.95, 0.95, 0.95, 1.0),
msaaSampleCount: Int = 4,
enableSilhouettes: Bool = true,
silhouetteThickness: Float = 1.0,
silhouetteIntensity: Float = 0.7,
enableFrustumCulling: Bool = true,
autoSmoothNormals: Bool = false,
normalSmoothingCreaseAngle: Float = 0.524,
pickingConfiguration: PickingConfiguration = .init(),
enableDepthOfField: Bool = false,
dofAperture: Float = 2.8,
dofFocalDistance: Float = 0,
dofMaxBlurRadius: Float = 8.0,
renderingQuality: RenderingQuality = .standard,
tessellationMaxFactor: Int = 32,
adaptiveTessellation: Bool = true,
enableTAA: Bool = false,
taaBlendFactor: Float = 0.9,
dynamicPivotConfiguration: DynamicPivotConfiguration = .default,
showOrientationGnomon: Bool = false,
showScaleBar: Bool = false,
scaleBarUnitLabel: String = ""
)
All parameters are optional; omit any to accept its default. Construct a preset and then mutate individual fields for targeted changes.
Example — start from .cad, enable scale bar and millimetre label:
var config = ViewportConfiguration.cad
config.showScaleBar = true
config.scaleBarUnitLabel = "mm"
Presets
.cad
public static let cad: ViewportConfiguration
Turntable orbit, ViewCube on, axes on, grid on. All other fields at defaults.
.modelViewer
public static let modelViewer: ViewportConfiguration
Arcball orbit, ViewCube off, axes off, grid off. Suited to 3D model inspection without engineering overlays.
.architectural
public static let architectural: ViewportConfiguration
Turntable orbit, shaded mode, .architectural lighting preset, ViewCube on, grid on, camera pre-positioned at an isometric-front-right view at distance 50.
.performance
public static let performance: ViewportConfiguration
Turntable orbit, shadows disabled, SSAO disabled, MSAA disabled (msaaSampleCount = 1), silhouettes disabled, renderingQuality = .standard. Use on dense many-body scenes (thousands of bodies / hundreds of thousands of triangles) on iPhone or iPad where per-frame whole-scene passes dominate render time.
Example:
MetalViewportView(
bodies: heavyScene,
configuration: .performance
)
.cadHighQuality
public static let cadHighQuality: ViewportConfiguration
Turntable orbit, ViewCube on, axes on, grid on, autoSmoothNormals = true, renderingQuality = .enhanced, tessellationMaxFactor = 48, adaptiveTessellation = true. Enables GPU PN-triangle Phong tessellation so cylinder and fillet silhouettes stay smooth at any zoom. Requires an Apple3+ GPU; falls back to un-tessellated rendering on older hardware.
RenderingQuality
public enum RenderingQuality: Sendable {
case standard
case enhanced
case maximum
}
Controls the tessellation tier used by ViewportRenderer.
| Case | Description |
|---|---|
.standard | Finer CPU-side tessellation combined with crease-aware normal smoothing. No GPU hardware tessellation. Runs on all supported hardware. |
.enhanced | Everything in .standard, plus GPU hardware tessellation using PN triangles (Phong tessellation). Requires an Apple3+ GPU; silently falls back to .standard on earlier hardware. |
.maximum | Everything in .enhanced, plus mesh shaders with per-meshlet culling. Requires Apple9+ / M3+; falls back to .enhanced when unavailable. |
Example — upgrade quality on a known capable device:
if config.renderingQuality == .standard {
config.renderingQuality = .enhanced
config.tessellationMaxFactor = 32
config.adaptiveTessellation = true
}
RenderLayer
public enum RenderLayer: Hashable, Sendable {
case geometry
case overlay
}
Controls when in the render order a ViewportBody is drawn.
| Case | Behaviour |
|---|---|
.geometry | Drawn in the normal geometry pass with standard depth testing. |
.overlay | Drawn after the selection-outline pass using an always-pass depth state, so the body is visible even when behind other geometry. Use for manipulator widgets and similar always-on-top affordances. |
Set on ViewportBody.renderLayer. See ViewportBody for the full body API.
Example:
var gizmo = ViewportBody(/* … */)
gizmo.renderLayer = .overlay // always visible, never occluded
AxisStyle
public enum AxisStyle: Sendable {
case cylinder
case constantScreenWidth
}
Rendering style for the world-space coordinate axes.
| Case | Behaviour |
|---|---|
.cylinder | Axis cylinders have a fixed world-space radius (axisRadius). They appear thinner on screen as the camera zooms out. Default. |
.constantScreenWidth | Cylinder radius auto-scales with camera distance so the axes maintain a constant on-screen width. Useful when axes must remain readable across a wide zoom range. |
GridStyle
public enum GridStyle: Sendable {
case plane
case dots
}
Rendering style for the ground grid.
| Case | Behaviour |
|---|---|
.plane | Solid infinite grid plane. gridSize controls how many world units the plane extends. Default. |
.dots | Instanced adaptive dot grid. The renderer snaps the dot spacing to powers of gridSubdivisions × gridBaseSpacing so the grid always shows a readable density regardless of zoom level. |
ViewCubePosition
public enum ViewCubePosition: String, CaseIterable, Sendable {
case topLeading
case topTrailing
case bottomLeading
case bottomTrailing
}
Corner of the viewport that hosts the navigation cube overlay. Assigned to ViewportConfiguration.viewCubePosition (default .bottomTrailing).
Example — move cube to top-right:
var config = ViewportConfiguration.cad
config.viewCubePosition = .topTrailing
PickingConfiguration
PickingConfiguration controls whether the GPU-accelerated pick-ID texture is allocated and written each frame. When disabled, the second color attachment is not used and no GPU picking results are produced; ViewportController.pickResult will never fire.
isEnabled
public var isEnabled: Bool // default: false
When false, the pick texture is not allocated and no pick-ID pass is rendered. Set to true to enable tap/click picking via ViewportController.pickResult or ViewportController.widgetPickResult.
init(isEnabled:)
public init(isEnabled: Bool = false)
Creates a picking configuration.
Example — enable picking when wiring up a selection handler:
var config = ViewportConfiguration.cad
config.pickingConfiguration = PickingConfiguration(isEnabled: true)
ClipPlane
ClipPlane defines a half-space clipping plane for section views. It is a Sendable, Equatable value type. Fragments where dot(normal, point) + distance < 0 are discarded by the Metal shader. Pass clip planes to ViewportController.clipPlanes to activate them.
The plane equation is:
dot(normal, P) + distance = 0
Points on the normal side (positive half-space) are kept; points on the opposite side are clipped.
normal
public var normal: SIMD3<Float>
Outward-facing unit normal of the clip plane. The initializer normalizes the input automatically. Fragments are discarded when they fall on the opposite side from the normal.
distance
public var distance: Float
Signed distance from the world origin along the normal. Positive values shift the clipping boundary away from the origin in the normal direction; negative values shift it toward the origin.
Example — clip at Y = 5 (keep Y > 5):
// dot((0,1,0), P) + (-5) = 0 → plane at Y=5
let plane = ClipPlane(normal: SIMD3(0, 1, 0), distance: -5)
isEnabled
public var isEnabled: Bool
Whether this plane is active. A disabled plane in the clipPlanes array is ignored by the renderer without requiring removal from the array.
init(normal:distance:isEnabled:)
public init(
normal: SIMD3<Float> = SIMD3(0, 1, 0),
distance: Float = 0,
isEnabled: Bool = true
)
Creates a clip plane. The normal is normalized at init time.
normal: Outward-facing normal (default: Y-up, clips below the ground plane).distance: Signed distance from origin along the normal (default:0).isEnabled: Whether the plane is active immediately (default:true).
Example — section cut at X = 2 (keep X > 2):
let plane = ClipPlane(normal: SIMD3(1, 0, 0), distance: -2)
asFloat4
public var asFloat4: SIMD4<Float> { get }
The plane equation packed as (normal.x, normal.y, normal.z, distance) for direct upload to a Metal uniform buffer.
Example:
uniforms.clipPlane = plane.asFloat4
Presets
.groundPlane
public static let groundPlane: ClipPlane
Y-up plane at the origin — discards all geometry below Y = 0.
.xPlane
public static let xPlane: ClipPlane
X-right plane at the origin — discards geometry on the negative-X side.
.zPlane
public static let zPlane: ClipPlane
Z-forward plane at the origin — discards geometry on the negative-Z side.
Example — combine two planes for a quadrant section view:
controller.clipPlanes = [
ClipPlane(normal: SIMD3(1, 0, 0), distance: 0), // keep X ≥ 0
ClipPlane(normal: SIMD3(0, 0, 1), distance: 0), // keep Z ≥ 0
]