Scenes and the view
A KausticsScene is everything a KausticsView draws: the light, the casters that throw caustics, the surfaces the light lands on, and a camera. It is @MainActor and @Observable, so you can change any of it from the main actor and the view shows the change on the next frame.
#Making a scene
let scene = KausticsScene(
light: KausticsLight(position: [1.6, 0.7, 1.4], intensity: 12, radius: 0.025),
casters: [KausticsCaster(mesh: try .ring(), material: .metal(.copper))],
surfaces: [.init(.floor(size: [2, 2]))],
camera: .init(position: [0, 1.15, 1.45], target: [0, 0, 0]))#Casters
A KausticsCaster is a mesh, a material and a transform. The transform takes the mesh from its own space to the world’s, and it is the one thing you will change most often. The package has no helpers for matrices, so use simd:
var lift = matrix_identity_float4x4
lift.columns.3 = [0, 0.5, 0, 1]
KausticsCaster(mesh: try .sphere(), material: .glass(), transform: lift)A sphere is centred on its own origin, so this one is lifted by its radius to sit on the floor. See Meshes and Materials.
#Surfaces
A surface is a rectangle that light lands on, and how it looks.
.init(.floor(size: [3, 2.6]), color: [0.74, 0.72, 0.69])
.init(.wall(center: [0, 0.9, -1.6], facing: [0, 0, 1], size: [3, 1.8]),
color: [0.62, 0.62, 0.64])| Property | Meaning |
|---|---|
receiver |
The rectangle. See Receivers. |
color |
Its albedo, linear. The default is [0.8, 0.8, 0.8]. |
tiles |
An optional tile pattern drawn over it. |
receivesCaustics |
Whether caustics land on it. The default is true. A surface that never gets any, such as a pool’s deck, is cheaper without. |
Tiles are square, with grout lines between them:
KausticsScene.Tiles(size: 0.1, groutWidth: 0.006, groutColor: [0.86, 0.9, 0.9])#Camera
.init(position: [0, 1.4, 1.25], target: [0, 0.02, -0.12], fieldOfView: 0.95)fieldOfView is the vertical field of view in radians. The default is 0.7.
#The studio
Metal, glass and water show what is around them. Environment is the small studio they reflect: a zenith, a horizon and a ground color, and how bright its two soft panels are.
scene.environment = .init(
zenith: [0.32, 0.34, 0.38], horizon: [0.55, 0.55, 0.56],
ground: [0.32, 0.3, 0.28], panels: 3)The defaults are [0.55, 0.6, 0.68], [0.85, 0.85, 0.85], [0.25, 0.24, 0.23] and 2.5.
#Settings
| Property | Default | Meaning |
|---|---|---|
background |
[0.06, 0.06, 0.07] |
What shows where there is no surface, linear. |
ambient |
0.03 |
Light every surface receives from everywhere. |
exposure |
1 |
Scales the final image. |
causticGain |
1 |
Multiplies the caustic light. |
resolution |
1024 |
Texels along each receiver’s longer side. |
lightPivot |
[0, 0, 0] |
The point dragging circles the light about. nil leaves the light where it is. |
showsLight |
true |
Whether the light’s glowing handle is drawn. |
#Animation
onFrame is called every frame, before drawing, with the seconds since the view started.
scene.onFrame = { scene, time in
let turn = simd_quatf(angle: Float(time) * 0.4, axis: [0, 1, 0])
scene.casters[0].transform = simd_float4x4(turn)
}A mesh that changes shape is covered in Meshes.
#Moving the light
Dragging across the view circles the light about lightPivot: left and right around it, up and down over it. dragLight(by:) does the same from your own gesture, and you can set scene.light.position directly.
#The view
KausticsView(scene: scene, preferredFramesPerSecond: 120)preferredFramesPerSecond is the frame rate to aim for, 60 by default. The view is a UIViewRepresentable around an MTKView, and a one finger drag on it moves the light. If Kaustics cannot be set up, because there is no Metal device for example, the view stays empty and the reason is printed to the console.