@series-inc/rundot-3d-engine 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,286 +1,286 @@
1
- # InstancedRenderer
2
-
3
- InstancedRenderer uses GPU instancing to efficiently render many copies of the same mesh. Perfect for coins, trees, bullets, or any object that appears many times in the scene.
4
-
5
- ## Quick Start
6
-
7
- ```typescript
8
- import { GameObject, InstancedRenderer } from "@series-ai/rundot-3d-engine"
9
- import { InstancedMeshManager } from "@series-ai/rundot-3d-engine/render"
10
-
11
- // 1. Pre-register a batch (optional - auto-creates if not done)
12
- await InstancedMeshManager.getInstance().registerMeshForInstancing(
13
- "coin_batch", // Batch key
14
- "restaurant_display_Money", // Mesh asset name
15
- 100 // Initial capacity
16
- )
17
-
18
- // 2. Create many instances efficiently
19
- for (let i = 0; i < 50; i++) {
20
- const coin = new GameObject(`Coin_${i}`)
21
- coin.position.set(Math.random() * 20, 1, Math.random() * 20)
22
-
23
- // All coins share same geometry/material - very efficient!
24
- coin.addComponent(new InstancedRenderer("coin_batch"))
25
- }
26
- ```
27
-
28
- ## Common Use Cases
29
-
30
- ### Dynamic Objects (Moving)
31
-
32
- ```typescript
33
- // Default - updates matrix every frame
34
- const coin = new GameObject("Coin")
35
- coin.addComponent(new InstancedRenderer("coin_batch"))
36
-
37
- // Coin can move/rotate freely
38
- coin.position.x += 5 * deltaTime
39
- ```
40
-
41
- ### Static Objects (Non-Moving)
42
-
43
- ```typescript
44
- // Static mode - only updates when markDirty() called
45
- const tree = new GameObject("Tree")
46
- const renderer = new InstancedRenderer("tree_batch", {
47
- isDynamic: false, // Static optimization
48
- castShadow: true,
49
- receiveShadow: true
50
- })
51
- tree.addComponent(renderer)
52
-
53
- // If tree needs to move later
54
- tree.position.x = 10
55
- renderer.markDirty() // Manually trigger update
56
- ```
57
-
58
- ### With Options
59
-
60
- ```typescript
61
- const renderer = new InstancedRenderer("prop_batch", {
62
- isDynamic: true, // Updates every frame
63
- castShadow: true, // Cast shadows
64
- receiveShadow: false, // Don't receive
65
- initialCapacity: 200 // Pre-allocate space
66
- })
67
- ```
68
-
69
- ### Show/Hide Instances
70
-
71
- ```typescript
72
- class Collectable extends Component {
73
- private renderer?: InstancedRenderer
74
-
75
- protected onCreate(): void {
76
- this.renderer = new InstancedRenderer("collectable_batch")
77
- this.gameObject.addComponent(this.renderer)
78
- }
79
-
80
- public collect(): void {
81
- // Hide instance without removing it
82
- this.renderer?.hide()
83
- // or this.renderer?.setVisible(false)
84
- }
85
- }
86
- ```
87
-
88
- ## API Overview
89
-
90
- ### Constructor
91
-
92
- ```typescript
93
- new InstancedRenderer(
94
- batchKey: string,
95
- options?: InstancedRendererOptions | boolean
96
- )
97
-
98
- interface InstancedRendererOptions {
99
- isDynamic?: boolean // Default: true
100
- castShadow?: boolean // Default: false
101
- receiveShadow?: boolean // Default: false
102
- initialCapacity?: number // Default: 16
103
- }
104
- ```
105
-
106
- ### Methods
107
-
108
- - `setVisible(visible: boolean): void` - Show/hide this instance
109
- - `getVisible(): boolean` - Check if instance is visible
110
- - `show(): void` - Show instance (convenience)
111
- - `hide(): void` - Hide instance (convenience)
112
- - `setDynamic(dynamic: boolean): void` - Switch dynamic/static mode
113
- - `isDynamic(): boolean` - Check current mode
114
- - `markDirty(): void` - Force matrix update (for static instances)
115
- - `getBatchKey(): string` - Get batch key
116
- - `isRegistered(): boolean` - Check if successfully registered
117
- - `getInstanceId(): string | null` - Get instance ID (debugging)
118
-
119
- ## Patterns & Best Practices
120
-
121
- ### Pre-Register Batches for Better Control
122
-
123
- ```typescript
124
- // Good - Explicit batch registration with settings
125
- await InstancedMeshManager.getInstance().registerMeshForInstancing(
126
- "enemy_batch",
127
- "enemy_mesh",
128
- 500, // Max enemies
129
- { castShadow: true, receiveShadow: true }
130
- )
131
-
132
- // Then create instances
133
- for (let i = 0; i < 100; i++) {
134
- const enemy = new GameObject("Enemy")
135
- enemy.addComponent(new InstancedRenderer("enemy_batch"))
136
- }
137
- ```
138
-
139
- ### Use Static Mode for Stationary Objects
140
-
141
- ```typescript
142
- // Terrain decorations, buildings, etc.
143
- const decoration = new GameObject("Rock")
144
- const renderer = new InstancedRenderer("rock_batch", {
145
- isDynamic: false // Save CPU
146
- })
147
- decoration.addComponent(renderer)
148
- ```
149
-
150
- ### Batch Similar Objects Together
151
-
152
- ```typescript
153
- // Good - One batch per mesh type
154
- InstancedRenderer("tree_oak_batch") // All oak trees
155
- InstancedRenderer("tree_pine_batch") // All pine trees
156
- InstancedRenderer("coin_gold_batch") // All gold coins
157
-
158
- // Avoid - Different meshes in same batch won't work
159
- ```
160
-
161
- ### Hide Instead of Dispose for Recycling
162
-
163
- ```typescript
164
- // Good - Reuse instances by hiding/showing
165
- collectCoin() {
166
- this.renderer.hide() // Fast, can show() later
167
- }
168
-
169
- // Less efficient - Dispose and recreate
170
- collectCoin() {
171
- this.gameObject.dispose() // Slower, needs new instance
172
- }
173
- ```
174
-
175
- ## Performance Benefits
176
-
177
- ### GPU Instancing Advantages
178
-
179
- Single draw call for all instances:
180
- - **1 instance**: 1 draw call
181
- - **100 instances**: Still 1 draw call! (vs 100 without instancing)
182
- - **1000 instances**: Still 1 draw call! (vs 1000 without instancing)
183
-
184
- ### When to Use InstancedRenderer
185
-
186
- Use for:
187
- - Projectiles/bullets
188
- - Coins/collectables
189
- - Trees/rocks
190
- - Particles
191
- - Repeated props
192
- - Anything with 10+ copies
193
-
194
- ### When to Use MeshRenderer Instead
195
-
196
- Use MeshRenderer for:
197
- - Unique objects (player, boss)
198
- - Objects needing different materials
199
- - Objects with different meshes
200
- - Fewer than 5-10 copies
201
-
202
- ## Anti-Patterns
203
-
204
- ### Don't Mix Mesh Types in Same Batch
205
-
206
- ```typescript
207
- // Bad - Same batch key, different meshes
208
- registerMeshForInstancing("props", "tree_mesh", 100)
209
- registerMeshForInstancing("props", "rock_mesh", 100) // Overwrites!
210
-
211
- // Good - Different keys
212
- registerMeshForInstancing("tree_batch", "tree_mesh", 100)
213
- registerMeshForInstancing("rock_batch", "rock_mesh", 100)
214
- ```
215
-
216
- ### Don't Use for Unique Objects
217
-
218
- ```typescript
219
- // Bad - Only one player, instancing provides no benefit
220
- const player = new GameObject("Player")
221
- player.addComponent(new InstancedRenderer("player_batch"))
222
-
223
- // Good - Use MeshRenderer for unique objects
224
- const renderer = new MeshRenderer("player_mesh")
225
- const renderObj = new GameObject("PlayerMesh")
226
- renderObj.addComponent(renderer)
227
- player.add(renderObj)
228
- ```
229
-
230
- ### Don't Forget to Mark Static Objects Dirty
231
-
232
- ```typescript
233
- // Bad - Static object moves but never updates
234
- const tree = new GameObject("Tree")
235
- tree.addComponent(new InstancedRenderer("tree_batch", { isDynamic: false }))
236
- // Later...
237
- tree.position.x = 10 // Won't show movement!
238
-
239
- // Good - Mark dirty after moving
240
- tree.position.x = 10
241
- tree.getComponent(InstancedRenderer)?.markDirty()
242
- ```
243
-
244
- ## Batch Management
245
-
246
- ### Creating Batches Explicitly
247
-
248
- ```typescript
249
- import { InstancedMeshManager } from "@series-ai/rundot-3d-engine/render"
250
-
251
- // Initialize manager (done automatically by VenusGame)
252
- const manager = InstancedMeshManager.getInstance()
253
-
254
- // Register batch
255
- await manager.registerMeshForInstancing(
256
- "batch_key",
257
- "mesh_asset_name",
258
- 100, // Initial capacity (grows automatically)
259
- {
260
- castShadow: true,
261
- receiveShadow: true
262
- }
263
- )
264
- ```
265
-
266
- ### Auto-Creating from GameObject
267
-
268
- If you don't pre-register, the first instance auto-creates the batch:
269
- ```typescript
270
- // First instance creates the batch from this GameObject's mesh
271
- const firstCoin = new GameObject("Coin")
272
- firstCoin.addComponent(new MeshRenderer("coin_mesh")) // Has mesh
273
- firstCoin.addComponent(new InstancedRenderer("coin_batch")) // Auto-creates batch
274
-
275
- // Subsequent instances use the batch
276
- const secondCoin = new GameObject("Coin2")
277
- secondCoin.addComponent(new InstancedRenderer("coin_batch")) // Uses existing
278
- ```
279
-
280
- ## Related Systems
281
-
282
- - [MeshRenderer](MeshRenderer.md) - For unique/few objects
283
- - [InstancedMeshManager](../../src/engine/render/InstancedMeshManager.ts) - Manages batches
284
- - [GameObject](../core/GameObject.md) - Entity class for components
285
- - [Component](../core/Component.md) - Base component class
286
-
1
+ # InstancedRenderer
2
+
3
+ InstancedRenderer uses GPU instancing to efficiently render many copies of the same mesh. Perfect for coins, trees, bullets, or any object that appears many times in the scene.
4
+
5
+ ## Quick Start
6
+
7
+ ```typescript
8
+ import { GameObject, InstancedRenderer } from "@series-ai/rundot-3d-engine"
9
+ import { InstancedMeshManager } from "@series-ai/rundot-3d-engine/render"
10
+
11
+ // 1. Pre-register a batch (optional - auto-creates if not done)
12
+ await InstancedMeshManager.getInstance().registerMeshForInstancing(
13
+ "coin_batch", // Batch key
14
+ "restaurant_display_Money", // Mesh asset name
15
+ 100 // Initial capacity
16
+ )
17
+
18
+ // 2. Create many instances efficiently
19
+ for (let i = 0; i < 50; i++) {
20
+ const coin = new GameObject(`Coin_${i}`)
21
+ coin.position.set(Math.random() * 20, 1, Math.random() * 20)
22
+
23
+ // All coins share same geometry/material - very efficient!
24
+ coin.addComponent(new InstancedRenderer("coin_batch"))
25
+ }
26
+ ```
27
+
28
+ ## Common Use Cases
29
+
30
+ ### Dynamic Objects (Moving)
31
+
32
+ ```typescript
33
+ // Default - updates matrix every frame
34
+ const coin = new GameObject("Coin")
35
+ coin.addComponent(new InstancedRenderer("coin_batch"))
36
+
37
+ // Coin can move/rotate freely
38
+ coin.position.x += 5 * deltaTime
39
+ ```
40
+
41
+ ### Static Objects (Non-Moving)
42
+
43
+ ```typescript
44
+ // Static mode - only updates when markDirty() called
45
+ const tree = new GameObject("Tree")
46
+ const renderer = new InstancedRenderer("tree_batch", {
47
+ isDynamic: false, // Static optimization
48
+ castShadow: true,
49
+ receiveShadow: true
50
+ })
51
+ tree.addComponent(renderer)
52
+
53
+ // If tree needs to move later
54
+ tree.position.x = 10
55
+ renderer.markDirty() // Manually trigger update
56
+ ```
57
+
58
+ ### With Options
59
+
60
+ ```typescript
61
+ const renderer = new InstancedRenderer("prop_batch", {
62
+ isDynamic: true, // Updates every frame
63
+ castShadow: true, // Cast shadows
64
+ receiveShadow: false, // Don't receive
65
+ initialCapacity: 200 // Pre-allocate space
66
+ })
67
+ ```
68
+
69
+ ### Show/Hide Instances
70
+
71
+ ```typescript
72
+ class Collectable extends Component {
73
+ private renderer?: InstancedRenderer
74
+
75
+ protected onCreate(): void {
76
+ this.renderer = new InstancedRenderer("collectable_batch")
77
+ this.gameObject.addComponent(this.renderer)
78
+ }
79
+
80
+ public collect(): void {
81
+ // Hide instance without removing it
82
+ this.renderer?.hide()
83
+ // or this.renderer?.setVisible(false)
84
+ }
85
+ }
86
+ ```
87
+
88
+ ## API Overview
89
+
90
+ ### Constructor
91
+
92
+ ```typescript
93
+ new InstancedRenderer(
94
+ batchKey: string,
95
+ options?: InstancedRendererOptions | boolean
96
+ )
97
+
98
+ interface InstancedRendererOptions {
99
+ isDynamic?: boolean // Default: true
100
+ castShadow?: boolean // Default: false
101
+ receiveShadow?: boolean // Default: false
102
+ initialCapacity?: number // Default: 16
103
+ }
104
+ ```
105
+
106
+ ### Methods
107
+
108
+ - `setVisible(visible: boolean): void` - Show/hide this instance
109
+ - `getVisible(): boolean` - Check if instance is visible
110
+ - `show(): void` - Show instance (convenience)
111
+ - `hide(): void` - Hide instance (convenience)
112
+ - `setDynamic(dynamic: boolean): void` - Switch dynamic/static mode
113
+ - `isDynamic(): boolean` - Check current mode
114
+ - `markDirty(): void` - Force matrix update (for static instances)
115
+ - `getBatchKey(): string` - Get batch key
116
+ - `isRegistered(): boolean` - Check if successfully registered
117
+ - `getInstanceId(): string | null` - Get instance ID (debugging)
118
+
119
+ ## Patterns & Best Practices
120
+
121
+ ### Pre-Register Batches for Better Control
122
+
123
+ ```typescript
124
+ // Good - Explicit batch registration with settings
125
+ await InstancedMeshManager.getInstance().registerMeshForInstancing(
126
+ "enemy_batch",
127
+ "enemy_mesh",
128
+ 500, // Max enemies
129
+ { castShadow: true, receiveShadow: true }
130
+ )
131
+
132
+ // Then create instances
133
+ for (let i = 0; i < 100; i++) {
134
+ const enemy = new GameObject("Enemy")
135
+ enemy.addComponent(new InstancedRenderer("enemy_batch"))
136
+ }
137
+ ```
138
+
139
+ ### Use Static Mode for Stationary Objects
140
+
141
+ ```typescript
142
+ // Terrain decorations, buildings, etc.
143
+ const decoration = new GameObject("Rock")
144
+ const renderer = new InstancedRenderer("rock_batch", {
145
+ isDynamic: false // Save CPU
146
+ })
147
+ decoration.addComponent(renderer)
148
+ ```
149
+
150
+ ### Batch Similar Objects Together
151
+
152
+ ```typescript
153
+ // Good - One batch per mesh type
154
+ InstancedRenderer("tree_oak_batch") // All oak trees
155
+ InstancedRenderer("tree_pine_batch") // All pine trees
156
+ InstancedRenderer("coin_gold_batch") // All gold coins
157
+
158
+ // Avoid - Different meshes in same batch won't work
159
+ ```
160
+
161
+ ### Hide Instead of Dispose for Recycling
162
+
163
+ ```typescript
164
+ // Good - Reuse instances by hiding/showing
165
+ collectCoin() {
166
+ this.renderer.hide() // Fast, can show() later
167
+ }
168
+
169
+ // Less efficient - Dispose and recreate
170
+ collectCoin() {
171
+ this.gameObject.dispose() // Slower, needs new instance
172
+ }
173
+ ```
174
+
175
+ ## Performance Benefits
176
+
177
+ ### GPU Instancing Advantages
178
+
179
+ Single draw call for all instances:
180
+ - **1 instance**: 1 draw call
181
+ - **100 instances**: Still 1 draw call! (vs 100 without instancing)
182
+ - **1000 instances**: Still 1 draw call! (vs 1000 without instancing)
183
+
184
+ ### When to Use InstancedRenderer
185
+
186
+ Use for:
187
+ - Projectiles/bullets
188
+ - Coins/collectables
189
+ - Trees/rocks
190
+ - Particles
191
+ - Repeated props
192
+ - Anything with 10+ copies
193
+
194
+ ### When to Use MeshRenderer Instead
195
+
196
+ Use MeshRenderer for:
197
+ - Unique objects (player, boss)
198
+ - Objects needing different materials
199
+ - Objects with different meshes
200
+ - Fewer than 5-10 copies
201
+
202
+ ## Anti-Patterns
203
+
204
+ ### Don't Mix Mesh Types in Same Batch
205
+
206
+ ```typescript
207
+ // Bad - Same batch key, different meshes
208
+ registerMeshForInstancing("props", "tree_mesh", 100)
209
+ registerMeshForInstancing("props", "rock_mesh", 100) // Overwrites!
210
+
211
+ // Good - Different keys
212
+ registerMeshForInstancing("tree_batch", "tree_mesh", 100)
213
+ registerMeshForInstancing("rock_batch", "rock_mesh", 100)
214
+ ```
215
+
216
+ ### Don't Use for Unique Objects
217
+
218
+ ```typescript
219
+ // Bad - Only one player, instancing provides no benefit
220
+ const player = new GameObject("Player")
221
+ player.addComponent(new InstancedRenderer("player_batch"))
222
+
223
+ // Good - Use MeshRenderer for unique objects
224
+ const renderer = new MeshRenderer("player_mesh")
225
+ const renderObj = new GameObject("PlayerMesh")
226
+ renderObj.addComponent(renderer)
227
+ player.add(renderObj)
228
+ ```
229
+
230
+ ### Don't Forget to Mark Static Objects Dirty
231
+
232
+ ```typescript
233
+ // Bad - Static object moves but never updates
234
+ const tree = new GameObject("Tree")
235
+ tree.addComponent(new InstancedRenderer("tree_batch", { isDynamic: false }))
236
+ // Later...
237
+ tree.position.x = 10 // Won't show movement!
238
+
239
+ // Good - Mark dirty after moving
240
+ tree.position.x = 10
241
+ tree.getComponent(InstancedRenderer)?.markDirty()
242
+ ```
243
+
244
+ ## Batch Management
245
+
246
+ ### Creating Batches Explicitly
247
+
248
+ ```typescript
249
+ import { InstancedMeshManager } from "@series-ai/rundot-3d-engine/render"
250
+
251
+ // Initialize manager (done automatically by VenusGame)
252
+ const manager = InstancedMeshManager.getInstance()
253
+
254
+ // Register batch
255
+ await manager.registerMeshForInstancing(
256
+ "batch_key",
257
+ "mesh_asset_name",
258
+ 100, // Initial capacity (grows automatically)
259
+ {
260
+ castShadow: true,
261
+ receiveShadow: true
262
+ }
263
+ )
264
+ ```
265
+
266
+ ### Auto-Creating from GameObject
267
+
268
+ If you don't pre-register, the first instance auto-creates the batch:
269
+ ```typescript
270
+ // First instance creates the batch from this GameObject's mesh
271
+ const firstCoin = new GameObject("Coin")
272
+ firstCoin.addComponent(new MeshRenderer("coin_mesh")) // Has mesh
273
+ firstCoin.addComponent(new InstancedRenderer("coin_batch")) // Auto-creates batch
274
+
275
+ // Subsequent instances use the batch
276
+ const secondCoin = new GameObject("Coin2")
277
+ secondCoin.addComponent(new InstancedRenderer("coin_batch")) // Uses existing
278
+ ```
279
+
280
+ ## Related Systems
281
+
282
+ - [MeshRenderer](MeshRenderer.md) - For unique/few objects
283
+ - [InstancedMeshManager](../../src/engine/render/InstancedMeshManager.ts) - Manages batches
284
+ - [GameObject](../core/GameObject.md) - Entity class for components
285
+ - [Component](../core/Component.md) - Base component class
286
+