@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.
- package/LICENSE.txt +6 -6
- package/docs/core/Component.md +321 -321
- package/docs/core/GameObject.md +204 -204
- package/docs/core/VenusGame.md +316 -316
- package/docs/patterns/ComponentCommunication.md +337 -337
- package/docs/patterns/CreatingGameObjects.md +290 -290
- package/docs/patterns/MeshColliders.md +338 -338
- package/docs/patterns/MeshLoading.md +316 -316
- package/docs/physics/Colliders.md +249 -249
- package/docs/physics/PhysicsSystem.md +151 -151
- package/docs/physics/RigidBodyComponent.md +201 -201
- package/docs/rendering/AssetManager.md +308 -308
- package/docs/rendering/InstancedRenderer.md +286 -286
- package/docs/rendering/MeshRenderer.md +286 -286
- package/docs/rendering/SkeletalRenderer.md +308 -308
- package/docs/systems/AnimationSystem.md +75 -75
- package/docs/systems/AudioSystem.md +79 -79
- package/docs/systems/InputManager.md +101 -101
- package/docs/systems/LightingSystem.md +101 -101
- package/docs/systems/ParticleSystem.md +44 -44
- package/docs/systems/PrefabSystem.md +60 -60
- package/docs/systems/StowKitSystem.md +77 -77
- package/docs/systems/TweenSystem.md +132 -132
- package/docs/systems/UISystem.md +73 -73
- package/package.json +1 -1
- package/scripts/postinstall.mjs +63 -51
|
@@ -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
|
+
|