Skip to content

Commit cd812ca

Browse files
committed
docs: fix dead refs, add patterns, separate 2D/3D categories
AGENTS.md: - Add imperatives: never push without permission, always run fantomas Fix dead references: - program.md: Batch3DRenderer.create → Renderer3D.create - shaders.md: ShaderUniformDataType enum names - services.md: Cmd.ofAsync 4-arg → 3-arg signature - program.md/services.md: mutation syntax → record with syntax - materials.md: non-existent builder functions → record update syntax - camera.md: Camera3D.lookAt return type accuracy - rendering.md: Renderer3DConfig.noClear → Renderer2DConfig.noClear - custom-commands.md: remove IRenderCommand2D references Separate categories: - 2D Rendering (categoryindex 4) for graphics2d/ - 3D Rendering (categoryindex 5) for graphics3d/ - Patterns (categoryindex 6) for patterns/ New patterns/ docs: - overview.md: patterns index - system-pipeline.md: composable update phases - async-chunks.md: background world generation - particles-3d.md: billboard particle system - multi-renderer.md: 2D overlay on 3D scene - day-night.md: time-based lighting transitions Updated index.md with Patterns section links.
1 parent f7433b5 commit cd812ca

24 files changed

Lines changed: 1162 additions & 337 deletions

AGENTS.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22

33
Mibo.Raylib is a port of the Mibo micro-framework from MonoGame to raylib-cs, designed to allow F# developers to write games using familiar patterns for all kinds of game genres and sizes.
44

5-
Mibo aims to solve 90% of use cases for enabling developers to focus on game logic rather than boilerplate code, providing guidelines and architecture for structuring game code, handling input, rendering, asset management, and time management among others.
5+
Mibo aims to solve 90% of use cases for enabling developers to focus on game logic rather than boilerplate code, providing guidelines and architecture for structuring game game code, handling input, rendering, asset management, and time management among others.
66

77
General setup and usage instructions can be found in the [README.md](README.md) file.
88

9+
## Imperatives
10+
11+
1. **NEVER PUSH WITHOUT PERMISSION.** Always ask before pushing to the remote.
12+
2. **Always run `dotnet fantomas .` before committing code.** Format all F# files before staging.
13+
914
## Project Structure
1015

1116
All of the projects live in the `src` folder:

docs/camera.md

Lines changed: 182 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -7,198 +7,262 @@ index: 13
77

88
# Camera
99

10-
Mibo.Raylib uses raylib's built-in camera types for both 2D and 3D rendering:
10+
Cameras control what part of the world you see and how it maps to the screen. Mibo.Raylib provides `Camera2D` for 2D games and `Camera3D` for 3D games. Both support single-camera, split-screen, and overlay patterns.
1111

12-
- **2D rendering**: raylib's `Camera2D` mutable struct
13-
- **3D rendering**: raylib's `Camera3D` mutable struct
12+
## What and Why
1413

15-
The 2D renderer consumes cameras via `Draw.beginCamera`. The `Camera3D` module provides helpers that produce a renderer-agnostic `Camera` struct (view + projection matrices) for use with the 3D pipeline.
14+
- **Scroll and zoom** — A 2D camera lets your game world be larger than the screen. Pan, zoom, and follow a player.
15+
- **Perspective** — A 3D camera defines where you look from and where you look at.
16+
- **Coordinate conversion** — Convert between screen pixels and world positions for mouse picking, UI placement, and debug tools.
17+
- **Multi-camera** — Split-screen multiplayer, picture-in-picture minimaps, and HUD overlays on top of the game world.
1618

17-
Both 2D and 3D cameras support config-based rendering via `Camera2DConfig` and `Camera3DConfig`, enabling viewport-based rendering, split-screen, and overlay patterns.
19+
## When to use
1820

19-
## 2D cameras (`Camera2D`)
21+
| Situation | Use |
22+
|-----------|-----|
23+
| 2D game with scrolling world | `Camera2D.create` + `Draw.beginCamera` |
24+
| 2D game with split-screen or HUD | `Camera2DConfig` + `Draw.beginCameraWith` |
25+
| 3D game | `Camera3D` struct + `Draw3D.beginCamera` |
26+
| 3D split-screen or overlay | `Camera3DConfig` + `Draw3D.beginCameraWith` |
27+
| Mouse picking in 3D | `Camera3D.screenPointToRay` |
28+
| Culling off-screen objects | `Camera2D.viewportBounds` |
2029

21-
Raylib's `Camera2D` is a mutable struct:
30+
---
2231

23-
```fsharp
24-
let mutable cam = Camera2D()
25-
cam.Target <- Vector2(400f, 300f) // world position to center on
26-
cam.Offset <- Vector2(640f, 360f) // screen offset (typically center)
27-
cam.Rotation <- 0f // rotation in radians
28-
cam.Zoom <- 1.0f // zoom factor
29-
```
32+
## 2D cameras
33+
34+
### Creating a camera
3035

31-
In the Mibo.Raylib 2D renderer, you use `Draw.beginCamera` with raylib's `Camera2D` struct:
36+
`Camera2D.create` centers the camera on a world position:
3237

3338
```fsharp
3439
let camera = Camera2D.create (Vector2(400f, 300f)) 1.0f viewportSize
40+
```
41+
42+
- `position` — world position to center on
43+
- `zoom` — zoom factor (`1.0f` = no zoom)
44+
- `viewportSize` — screen size in pixels (used to compute the offset)
45+
46+
### Using in a view
3547

48+
Wrap your world-space draw commands between `beginCamera` and `endCamera`. The `layer` parameter controls draw order — camera and content must share the same layer range.
49+
50+
```fsharp
3651
buffer
3752
|> Draw.beginCamera 0<RenderLayer> camera
38-
|> // ... draw world content ...
39-
|> Draw.endCamera 1000<RenderLayer>
53+
|> Draw.fillRect (0<RenderLayer>, Color.Green) groundRect
54+
|> Draw.fillCircle (0<RenderLayer>, Color.Red) (playerPos, 16f)
55+
|> Draw.endCamera 999<RenderLayer>
56+
|> Draw.text (1000<RenderLayer>, Color.White) hudTextState
4057
```
4158

42-
The `Camera2D` module in `Mibo.Elmish` provides helpers:
59+
> _**TIP**_: Put UI draws *after* `endCamera` on a higher layer so they render in screen space, not world space.
4360
44-
- `Camera2D.create position zoom viewportSize` — create a camera centered on a position
45-
- `Camera2D.screenToWorld camera screenPos` — convert screen to world coordinates
46-
- `Camera2D.worldToScreen camera worldPos` — convert world to screen coordinates
47-
- `Camera2D.viewportBounds camera width height` — get visible world rectangle (for culling)
48-
- `Camera2D.smoothFollow &camera target speed` — smooth camera tracking
49-
- `Camera2D.clampTarget &camera minX minY maxX maxY` — clamp within bounds
61+
### Camera movement
5062

51-
## `Camera` (renderer-agnostic)
63+
Use `smoothFollow` to lerp the camera toward a target. Use `clampTarget` to keep the camera within world bounds. Both take the camera by reference.
5264

53-
The core library provides a `Camera` struct with precomputed view and projection matrices, plus helper modules to build them:
65+
```fsharp
66+
let mutable cam = Camera2D.create startPos 1.0f viewportSize
67+
68+
// In your update function, each frame:
69+
Camera2D.smoothFollow &cam playerPos 0.1f
70+
Camera2D.clampTarget &cam 0f 0f worldWidth worldHeight
71+
```
72+
73+
### Coordinate conversion
74+
75+
Convert between screen pixels and world positions:
76+
77+
```fsharp
78+
// Mouse click in world space
79+
let worldPos = Camera2D.screenToWorld camera mousePos
80+
81+
// Where does a world object appear on screen?
82+
let screenPos = Camera2D.worldToScreen camera enemyPos
83+
```
84+
85+
Use `viewportBounds` to get the visible world rectangle — useful for culling off-screen objects:
5486

5587
```fsharp
56-
type Camera = {
57-
View: Matrix4x4
58-
Projection: Matrix4x4
59-
}
88+
let visible = Camera2D.viewportBounds camera screenWidth screenHeight
6089
```
6190

62-
### `Camera3D` module helpers
91+
---
92+
93+
## 2D multi-camera
94+
95+
`Camera2DConfig` lets you control viewport, clear color, and rendering behavior per camera. Build one with `Camera2D.render` and chain `with*` modifiers.
6396

64-
> These helpers produce a renderer-agnostic `Camera` struct. Use them with `Draw3D.beginCamera` or store for later.
97+
### Config modifiers
98+
99+
| Modifier | Description |
100+
|----------|-------------|
101+
| `Camera2D.withViewport rect` | Viewport in normalized screen coordinates (0–1) |
102+
| `Camera2D.withClear color` | Clear with this color before rendering |
103+
104+
### Using a config in a view
65105

66106
```fsharp
67-
// Look-at camera
68-
let cam = Camera3D.lookAt
69-
(Vector3(0f, 10f, 20f)) // position
70-
Vector3.Zero // target
71-
Vector3.Up // up
72-
(MathF.PI / 4.0f) // 45° FOV
73-
(16f / 9f) // aspect ratio
74-
0.1f // near plane
75-
1000f // far plane
76-
77-
// Orbit camera
78-
let orbiting = Camera3D.orbit target yaw pitch radius fov aspect near far
79-
80-
// Screen-to-ray picking
81-
let ray = Camera3D.screenPointToRay camera mousePos screenWidth screenHeight
107+
let config =
108+
Camera2D.render worldCamera
109+
|> Camera2D.withClear Color.CornflowerBlue
110+
111+
buffer
112+
|> Draw.beginCameraWith 0<RenderLayer> config
113+
|> // ... world content ...
114+
|> Draw.endCamera 999<RenderLayer>
82115
```
83116

84-
### Camera3D rendering config
117+
### Split-screen
85118

86-
`Camera3DConfig` controls viewport, clear color, and post-processing per camera. Use `Camera3D.render` to create one, then chain `with*` modifiers:
119+
Pre-built helpers for two-player split-screen. Each clears with the given color.
87120

88121
```fsharp
89-
let mainCamera = Camera3D.lookAt (Vector3(0f, 10f, 20f)) Vector3.Zero Vector3.Up (MathF.PI / 4f) (16f/9f) 0.1f 1000f
122+
let left = Camera2D.splitScreenLeft player1Camera Color.CornflowerBlue
123+
let right = Camera2D.splitScreenRight player2Camera Color.DarkGreen
90124
91-
let config =
92-
Camera3D.render mainCamera
93-
|> Camera3D.withClear Color.SkyBlue
125+
buffer
126+
|> Draw.beginCameraWith 0<RenderLayer> left
127+
|> // ... player 1 content ...
128+
|> Draw.endCamera 99<RenderLayer>
129+
|> Draw.beginCameraWith 100<RenderLayer> right
130+
|> // ... player 2 content ...
131+
|> Draw.endCamera 199<RenderLayer>
132+
|> Draw.text (200<RenderLayer>, Color.White) hudState
94133
```
95134

96-
Use `Draw3D.beginCameraWith` to apply a config in your view:
135+
Available split-screen helpers:
136+
137+
| Helper | Viewport |
138+
|--------|----------|
139+
| `Camera2D.splitScreenLeft` | Left half (0, 0, 0.5, 1) |
140+
| `Camera2D.splitScreenRight` | Right half (0.5, 0, 0.5, 1) |
141+
| `Camera2D.splitScreenTop` | Top half (0, 0, 1, 0.5) |
142+
| `Camera2D.splitScreenBottom` | Bottom half (0, 0.5, 1, 0.5) |
143+
144+
### Overlay
145+
146+
Picture-in-picture overlay. Clears with black by default.
97147

98148
```fsharp
149+
let minimapRect = Rectangle(0.75f, 0.0f, 0.25f, 0.25f)
150+
let minimap = Camera2D.overlay topDownCamera minimapRect
151+
99152
buffer
100-
|> Draw3D.beginCameraWith config
153+
|> Draw.beginCameraWith 0<RenderLayer> worldConfig
154+
|> // ... main game ...
155+
|> Draw.endCamera 99<RenderLayer>
156+
|> Draw.beginCameraWith 100<RenderLayer> minimap
157+
|> // ... minimap content ...
158+
|> Draw.endCamera 199<RenderLayer>
159+
```
160+
161+
---
162+
163+
## 3D cameras
164+
165+
### Creating a camera
166+
167+
For 3D rendering, create a `Camera3D` (raylib struct) directly. This is what `Draw3D.beginCamera` and `Camera3D.render` expect:
168+
169+
```fsharp
170+
let camera = Camera3D(
171+
Vector3(0f, 10f, 20f), // position
172+
Vector3.Zero, // target
173+
Vector3.UnitY, // up
174+
45.0f, // FOV in degrees
175+
CameraProjection.Perspective
176+
)
177+
```
178+
179+
For third-person or inspection cameras, use `Camera3D.orbit` which returns a `Camera3D` via spherical coordinates:
180+
181+
```fsharp
182+
let orbitCam = Camera3D.orbit target yaw pitch radius fov aspect near far
183+
```
184+
185+
> _**NOTE**_: `Camera3D.lookAt` and `Camera3D.orbit` return a `Camera` struct (view + projection matrices). This is useful for ray casting (`Camera3D.screenPointToRay`) but not for rendering. For rendering, create `Camera3D` directly or use `Camera3D.render` to build a config.
186+
187+
### Using in a view
188+
189+
```fsharp
190+
buffer
191+
|> Draw3D.beginCamera camera
101192
|> Draw3D.drawModel playerModel playerTransform
193+
|> Draw3D.addPointLight { Position = torchPos; Color = Color.White; Intensity = 1f; Radius = 10f; CastsShadows = false; ShadowBias = ValueNone }
102194
|> Draw3D.endCamera
195+
|> Draw3D.drop
103196
```
104197

105-
Available modifiers:
198+
### 3D config modifiers
199+
200+
`Camera3DConfig` controls viewport, clear color, and post-processing. Build with `Camera3D.render` and chain modifiers:
106201

107202
| Modifier | Description |
108203
|----------|-------------|
109-
| `Camera3D.withViewport rect` | Set viewport in normalized screen coordinates (0–1) |
204+
| `Camera3D.withViewport rect` | Viewport in normalized screen coordinates (0–1) |
110205
| `Camera3D.withClear color` | Clear with this color before rendering |
111206
| `Camera3D.withPostProcess passes` | Use only specific post-process pass indices |
112207
| `Camera3D.withoutPostProcess` | Disable post-processing for this camera |
113208

209+
```fsharp
210+
let config =
211+
Camera3D.render mainCamera
212+
|> Camera3D.withClear Color.SkyBlue
213+
|> Camera3D.withoutPostProcess
214+
215+
buffer
216+
|> Draw3D.beginCameraWith config
217+
|> Draw3D.drawModel sceneModel sceneTransform
218+
|> Draw3D.endCamera
219+
|> Draw3D.drop
220+
```
221+
114222
### Split-screen (3D)
115223

116224
```fsharp
117-
let leftConfig = Camera3D.splitScreenLeft player1Camera Color.SkyBlue
118-
let rightConfig = Camera3D.splitScreenRight player2Camera Color.SkyBlue
225+
let left = Camera3D.splitScreenLeft player1Camera Color.SkyBlue
226+
let right = Camera3D.splitScreenRight player2Camera Color.SkyBlue
119227
120228
buffer
121-
|> Draw3D.beginCameraWith leftConfig
229+
|> Draw3D.beginCameraWith left
122230
|> // ... player 1 scene ...
123231
|> Draw3D.endCamera
124-
|> Draw3D.beginCameraWith rightConfig
232+
|> Draw3D.beginCameraWith right
125233
|> // ... player 2 scene ...
126234
|> Draw3D.endCamera
235+
|> Draw3D.drop
127236
```
128237

129-
### Picture-in-picture overlay (3D)
238+
### Overlay (3D)
130239

131240
```fsharp
132241
let minimapRect = Rectangle(0.75f, 0.0f, 0.25f, 0.25f)
133-
let minimapConfig = Camera3D.overlay topDownCamera minimapRect
242+
let minimap = Camera3D.overlay topDownCamera minimapRect
134243
135244
buffer
136245
|> Draw3D.beginCameraWith mainConfig
137246
|> // ... main scene ...
138247
|> Draw3D.endCamera
139-
|> Draw3D.beginCameraWith minimapConfig
248+
|> Draw3D.beginCameraWith minimap
140249
|> // ... minimap scene ...
141250
|> Draw3D.endCamera
251+
|> Draw3D.drop
142252
```
143253

144-
### Camera2D rendering config
254+
> _**TIP**_: `Camera3D.overlay` disables post-processing by default. Re-enable it with `Camera3D.withPostProcess` if needed.
145255
146-
`Camera2DConfig` works the same way for 2D cameras:
256+
### Mouse picking
147257

148-
```fsharp
149-
let hudCamera = Camera2D.create Vector2.Zero 1.0f viewportSize
150-
151-
let hudConfig =
152-
Camera2D.render hudCamera
153-
|> Camera2D.withClear Color.Black
154-
155-
buffer
156-
|> Draw.beginCameraWith 0<RenderLayer> config
157-
|> // ... world content ...
158-
|> Draw.endCamera 1000<RenderLayer>
159-
```
160-
161-
Split-screen helpers: `Camera2D.splitScreenLeft`, `splitScreenRight`, `splitScreenTop`, `splitScreenBottom`, and `Camera2D.overlay`.
162-
163-
### Multi-camera patterns
164-
165-
Combine multiple cameras for split-screen, minimaps, or layered rendering:
258+
Cast a ray from a screen position into the 3D scene:
166259

167260
```fsharp
168-
// Split-screen 2D
169-
let left = Camera2D.splitScreenLeft cam1 Color.CornflowerBlue
170-
let right = Camera2D.splitScreenRight cam2 Color.DarkGreen
171-
172-
buffer
173-
|> Draw.beginCameraWith 0<RenderLayer> left
174-
|> // ... left viewport content ...
175-
|> Draw.endCamera 100<RenderLayer>
176-
|> Draw.beginCameraWith 200<RenderLayer> right
177-
|> // ... right viewport content ...
178-
|> Draw.endCamera 300<RenderLayer>
261+
let ray = Camera3D.screenPointToRay camera mousePos viewportWidth viewportHeight
262+
// ray.Position — origin point
263+
// ray.Direction — normalized direction into the scene
179264
```
180265

181-
### Camera2D module helpers
182-
183-
The `Camera2D` module works with raylib's `Camera2D` struct and the 2D renderer:
184-
185-
- `Camera2D.create position zoom viewportSize`
186-
- `Camera2D.screenToWorld camera screenPos`
187-
- `Camera2D.worldToScreen camera worldPos`
188-
- `Camera2D.viewportBounds camera width height`
189-
- `Camera2D.smoothFollow &camera target speed`
190-
- `Camera2D.clampTarget &camera minX minY maxX maxY`
191-
192-
## Camera Movement Examples
193-
194-
### Smooth follow (2D)
195-
196-
```fsharp
197-
let mutable cam = Camera2D.create startPos 1.0f viewportSize
198-
199-
// Each frame:
200-
Camera2D.smoothFollow &cam targetPos 0.1f
201-
Camera2D.clampTarget &cam 0f 0f worldWidth worldHeight
202-
```
266+
---
203267

204-
See also: [Culling](culling.html) and [Rendering overview](rendering.html).
268+
See also: [2D Rendering Overview](graphics2d/overview.html), [3D Rendering](graphics3d/overview.html), [Lighting & Shadows](graphics2d/lighting.html)

0 commit comments

Comments
 (0)