@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,316 +1,316 @@
|
|
|
1
|
-
# Mesh Loading Pattern
|
|
2
|
-
|
|
3
|
-
The correct pattern for loading and displaying 3D meshes in the Rundot 3D Engine.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
**Use MeshRenderer or InstancedRenderer to load and display 3D meshes:**
|
|
8
|
-
|
|
9
|
-
- **MeshRenderer**: For individual meshes or small numbers of unique objects
|
|
10
|
-
- **InstancedRenderer**: For large numbers of the same mesh (GPU instancing for performance)
|
|
11
|
-
|
|
12
|
-
## Standard Pattern: MeshRenderer
|
|
13
|
-
|
|
14
|
-
**Use MeshRenderer with a child GameObject for most cases:**
|
|
15
|
-
|
|
16
|
-
```typescript
|
|
17
|
-
private createMeshRenderer(): void {
|
|
18
|
-
const renderer = new MeshRenderer("restaurant_display_Money")
|
|
19
|
-
this.rendererObject = new GameObject("RendererObject")
|
|
20
|
-
this.rendererObject.addComponent(renderer)
|
|
21
|
-
this.gameObject.add(this.rendererObject)
|
|
22
|
-
|
|
23
|
-
// Optional: Position the mesh
|
|
24
|
-
this.rendererObject.position.set(0, 2, 0)
|
|
25
|
-
}
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Why This Pattern?
|
|
29
|
-
|
|
30
|
-
1. **Component Lifecycle**: MeshRenderer handles loading, cleanup, and disposal automatically
|
|
31
|
-
2. **Hierarchy Control**: Child GameObject allows independent positioning/rotation
|
|
32
|
-
3. **Clean Architecture**: Follows the component-based design
|
|
33
|
-
4. **Automatic Loading**: Mesh loads asynchronously without blocking
|
|
34
|
-
|
|
35
|
-
## Complete Example
|
|
36
|
-
|
|
37
|
-
```typescript
|
|
38
|
-
import { Component, GameObject, MeshRenderer } from "@series-ai/rundot-3d-engine"
|
|
39
|
-
|
|
40
|
-
class Pickup extends Component {
|
|
41
|
-
private rendererObject: GameObject | null = null
|
|
42
|
-
|
|
43
|
-
protected onCreate(): void {
|
|
44
|
-
this.createMeshRenderer()
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
private createMeshRenderer(): void {
|
|
48
|
-
// 1. Create MeshRenderer component
|
|
49
|
-
const renderer = new MeshRenderer("restaurant_display_Money")
|
|
50
|
-
|
|
51
|
-
// 2. Create child GameObject
|
|
52
|
-
this.rendererObject = new GameObject("RendererObject")
|
|
53
|
-
|
|
54
|
-
// 3. Add component to GameObject
|
|
55
|
-
this.rendererObject.addComponent(renderer)
|
|
56
|
-
|
|
57
|
-
// 4. Add as child
|
|
58
|
-
this.gameObject.add(this.rendererObject)
|
|
59
|
-
|
|
60
|
-
// 5. Optional: Position/rotate mesh
|
|
61
|
-
this.rendererObject.position.set(0, 2, 0)
|
|
62
|
-
this.rendererObject.rotation.y = Math.PI / 4
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
protected onCleanup(): void {
|
|
66
|
-
// Cleanup happens automatically when GameObject is disposed
|
|
67
|
-
this.rendererObject?.dispose()
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## What NOT to Do
|
|
73
|
-
|
|
74
|
-
### ❌ Don't Use StowKitSystem Directly
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
// BAD - Bypasses component system
|
|
78
|
-
private async createMeshRenderer(): Promise<void> {
|
|
79
|
-
const stowkit = StowKitSystem.getInstance()
|
|
80
|
-
const mesh = await stowkit.getMesh("restaurant_display_Money")
|
|
81
|
-
this.gameObject.add(mesh.clone())
|
|
82
|
-
}
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
Problems:
|
|
86
|
-
- No automatic cleanup
|
|
87
|
-
- No component lifecycle
|
|
88
|
-
- Manual async handling required
|
|
89
|
-
- Harder to manage
|
|
90
|
-
|
|
91
|
-
### ❌ Don't Add MeshRenderer Directly to Parent
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
// BAD - No hierarchy control
|
|
95
|
-
this.gameObject.addComponent(new MeshRenderer("mesh_name"))
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
Problems:
|
|
99
|
-
- Can't position mesh independently
|
|
100
|
-
- Can't have multiple meshes on same GameObject
|
|
101
|
-
- Less flexible
|
|
102
|
-
|
|
103
|
-
## Benefits of the Pattern
|
|
104
|
-
|
|
105
|
-
### Independent Positioning
|
|
106
|
-
|
|
107
|
-
```typescript
|
|
108
|
-
// Parent GameObject at (0, 0, 0)
|
|
109
|
-
const parent = new GameObject("Parent")
|
|
110
|
-
|
|
111
|
-
// Mesh offset from parent
|
|
112
|
-
const renderer = new MeshRenderer("mesh")
|
|
113
|
-
const meshObj = new GameObject("Mesh")
|
|
114
|
-
meshObj.addComponent(renderer)
|
|
115
|
-
parent.add(meshObj)
|
|
116
|
-
|
|
117
|
-
meshObj.position.set(0, -0.5, 0) // Visual offset
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### Multiple Meshes
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
// Multiple meshes on one GameObject
|
|
124
|
-
const character = new GameObject("Character")
|
|
125
|
-
|
|
126
|
-
const body = new GameObject("Body")
|
|
127
|
-
body.addComponent(new MeshRenderer("body_mesh"))
|
|
128
|
-
character.add(body)
|
|
129
|
-
|
|
130
|
-
const weapon = new GameObject("Weapon")
|
|
131
|
-
weapon.addComponent(new MeshRenderer("weapon_mesh"))
|
|
132
|
-
character.add(weapon)
|
|
133
|
-
weapon.position.set(0.5, 1, 0) // Weapon offset
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Automatic Async Loading
|
|
137
|
-
|
|
138
|
-
```typescript
|
|
139
|
-
// Mesh loads in background
|
|
140
|
-
const renderer = new MeshRenderer("large_mesh")
|
|
141
|
-
const obj = new GameObject("Mesh")
|
|
142
|
-
obj.addComponent(renderer)
|
|
143
|
-
this.gameObject.add(obj)
|
|
144
|
-
|
|
145
|
-
// No await needed - appears when ready!
|
|
146
|
-
// Component continues working while mesh loads
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## High-Performance Pattern: InstancedRenderer
|
|
150
|
-
|
|
151
|
-
**Use InstancedRenderer when displaying many copies of the same mesh:**
|
|
152
|
-
|
|
153
|
-
InstancedRenderer uses GPU instancing to render hundreds or thousands of the same mesh efficiently. Each instance can have its own position, rotation, scale, and color.
|
|
154
|
-
|
|
155
|
-
### When to Use InstancedRenderer
|
|
156
|
-
|
|
157
|
-
- **Many copies** of the same mesh (50+)
|
|
158
|
-
- **Same geometry** for all instances (coins, trees, enemies, projectiles)
|
|
159
|
-
- **Performance critical** scenarios (large crowds, particle-like effects)
|
|
160
|
-
- **Different transforms** per instance (position, rotation, scale)
|
|
161
|
-
|
|
162
|
-
### Basic Example
|
|
163
|
-
|
|
164
|
-
```typescript
|
|
165
|
-
import { Component, GameObject } from "@series-ai/rundot-3d-engine"
|
|
166
|
-
import { InstancedRenderer } from "@series-ai/rundot-3d-engine/rendering"
|
|
167
|
-
import * as THREE from "three"
|
|
168
|
-
|
|
169
|
-
class CoinSpawner extends Component {
|
|
170
|
-
private instancedRenderer: InstancedRenderer | null = null
|
|
171
|
-
private rendererObject: GameObject | null = null
|
|
172
|
-
|
|
173
|
-
protected onCreate(): void {
|
|
174
|
-
this.spawnCoins()
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
private spawnCoins(): void {
|
|
178
|
-
// 1. Create InstancedRenderer with mesh name and instance count
|
|
179
|
-
this.instancedRenderer = new InstancedRenderer(
|
|
180
|
-
"restaurant_display_Money",
|
|
181
|
-
100 // Number of instances
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
// 2. Create child GameObject
|
|
185
|
-
this.rendererObject = new GameObject("CoinsRenderer")
|
|
186
|
-
|
|
187
|
-
// 3. Add component
|
|
188
|
-
this.rendererObject.addComponent(this.instancedRenderer)
|
|
189
|
-
|
|
190
|
-
// 4. Add as child
|
|
191
|
-
this.gameObject.add(this.rendererObject)
|
|
192
|
-
|
|
193
|
-
// 5. Set positions for each instance
|
|
194
|
-
for (let i = 0; i < 100; i++) {
|
|
195
|
-
const x = (i % 10) * 2 - 10
|
|
196
|
-
const z = Math.floor(i / 10) * 2 - 10
|
|
197
|
-
const position = new THREE.Vector3(x, 0, z)
|
|
198
|
-
|
|
199
|
-
this.instancedRenderer.setInstancePosition(i, position)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// 6. Update to apply transforms
|
|
203
|
-
this.instancedRenderer.updateInstances()
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
protected onCleanup(): void {
|
|
207
|
-
this.rendererObject?.dispose()
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
### Setting Instance Properties
|
|
213
|
-
|
|
214
|
-
```typescript
|
|
215
|
-
// Set position
|
|
216
|
-
instancedRenderer.setInstancePosition(0, new THREE.Vector3(0, 5, 0))
|
|
217
|
-
|
|
218
|
-
// Set rotation
|
|
219
|
-
instancedRenderer.setInstanceRotation(0, new THREE.Quaternion())
|
|
220
|
-
|
|
221
|
-
// Set scale
|
|
222
|
-
instancedRenderer.setInstanceScale(0, new THREE.Vector3(1, 1, 1))
|
|
223
|
-
|
|
224
|
-
// Set color (tint)
|
|
225
|
-
instancedRenderer.setInstanceColor(0, new THREE.Color(0xff0000))
|
|
226
|
-
|
|
227
|
-
// IMPORTANT: Always call updateInstances() after making changes
|
|
228
|
-
instancedRenderer.updateInstances()
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### Dynamic Updates Example
|
|
232
|
-
|
|
233
|
-
```typescript
|
|
234
|
-
class FloatingCoins extends Component {
|
|
235
|
-
private instancedRenderer: InstancedRenderer | null = null
|
|
236
|
-
private elapsedTime: number = 0
|
|
237
|
-
|
|
238
|
-
update(deltaTime: number): void {
|
|
239
|
-
if (!this.instancedRenderer) return
|
|
240
|
-
|
|
241
|
-
this.elapsedTime += deltaTime
|
|
242
|
-
|
|
243
|
-
// Animate each instance
|
|
244
|
-
for (let i = 0; i < 100; i++) {
|
|
245
|
-
const offset = i * 0.1
|
|
246
|
-
const y = Math.sin(this.elapsedTime + offset) * 2
|
|
247
|
-
const pos = new THREE.Vector3(
|
|
248
|
-
(i % 10) * 2 - 10,
|
|
249
|
-
y,
|
|
250
|
-
Math.floor(i / 10) * 2 - 10
|
|
251
|
-
)
|
|
252
|
-
this.instancedRenderer.setInstancePosition(i, pos)
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Update GPU buffer with new transforms
|
|
256
|
-
this.instancedRenderer.updateInstances()
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
### Showing/Hiding Instances
|
|
262
|
-
|
|
263
|
-
```typescript
|
|
264
|
-
// Hide an instance (moves it far away)
|
|
265
|
-
instancedRenderer.setInstanceVisible(5, false)
|
|
266
|
-
|
|
267
|
-
// Show an instance
|
|
268
|
-
instancedRenderer.setInstanceVisible(5, true)
|
|
269
|
-
|
|
270
|
-
// Don't forget to update!
|
|
271
|
-
instancedRenderer.updateInstances()
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
## Choosing Between MeshRenderer and InstancedRenderer
|
|
275
|
-
|
|
276
|
-
### Use MeshRenderer When:
|
|
277
|
-
- ✅ You have unique objects or small numbers (< 50)
|
|
278
|
-
- ✅ Each mesh is different
|
|
279
|
-
- ✅ Each object has unique components/behavior
|
|
280
|
-
- ✅ Objects are added/removed dynamically
|
|
281
|
-
- ✅ Standard use case
|
|
282
|
-
|
|
283
|
-
### Use InstancedRenderer When:
|
|
284
|
-
- ✅ You need many copies of the same mesh (50+)
|
|
285
|
-
- ✅ All instances share the same geometry
|
|
286
|
-
- ✅ Performance is critical
|
|
287
|
-
- ✅ You can manage instances in a batch
|
|
288
|
-
- ✅ Instances have similar behavior
|
|
289
|
-
|
|
290
|
-
### Performance Comparison
|
|
291
|
-
|
|
292
|
-
```typescript
|
|
293
|
-
// BAD - 1000 individual MeshRenderers
|
|
294
|
-
for (let i = 0; i < 1000; i++) {
|
|
295
|
-
const obj = new GameObject(`Coin_${i}`)
|
|
296
|
-
const renderer = new MeshRenderer("coin_mesh")
|
|
297
|
-
obj.addComponent(renderer)
|
|
298
|
-
scene.add(obj)
|
|
299
|
-
// Result: 1000 draw calls, poor performance
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// GOOD - 1 InstancedRenderer with 1000 instances
|
|
303
|
-
const renderer = new InstancedRenderer("coin_mesh", 1000)
|
|
304
|
-
const obj = new GameObject("Coins")
|
|
305
|
-
obj.addComponent(renderer)
|
|
306
|
-
scene.add(obj)
|
|
307
|
-
// Result: 1 draw call, excellent performance
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
## Related Patterns
|
|
311
|
-
|
|
312
|
-
- [Creating GameObjects](CreatingGameObjects.md) - GameObject best practices
|
|
313
|
-
- [Component Communication](ComponentCommunication.md) - Inter-component patterns
|
|
314
|
-
- [MeshRenderer](../rendering/MeshRenderer.md) - Full MeshRenderer documentation
|
|
315
|
-
- [InstancedRenderer](../rendering/InstancedRenderer.md) - Full InstancedRenderer documentation
|
|
316
|
-
|
|
1
|
+
# Mesh Loading Pattern
|
|
2
|
+
|
|
3
|
+
The correct pattern for loading and displaying 3D meshes in the Rundot 3D Engine.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
**Use MeshRenderer or InstancedRenderer to load and display 3D meshes:**
|
|
8
|
+
|
|
9
|
+
- **MeshRenderer**: For individual meshes or small numbers of unique objects
|
|
10
|
+
- **InstancedRenderer**: For large numbers of the same mesh (GPU instancing for performance)
|
|
11
|
+
|
|
12
|
+
## Standard Pattern: MeshRenderer
|
|
13
|
+
|
|
14
|
+
**Use MeshRenderer with a child GameObject for most cases:**
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
private createMeshRenderer(): void {
|
|
18
|
+
const renderer = new MeshRenderer("restaurant_display_Money")
|
|
19
|
+
this.rendererObject = new GameObject("RendererObject")
|
|
20
|
+
this.rendererObject.addComponent(renderer)
|
|
21
|
+
this.gameObject.add(this.rendererObject)
|
|
22
|
+
|
|
23
|
+
// Optional: Position the mesh
|
|
24
|
+
this.rendererObject.position.set(0, 2, 0)
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Why This Pattern?
|
|
29
|
+
|
|
30
|
+
1. **Component Lifecycle**: MeshRenderer handles loading, cleanup, and disposal automatically
|
|
31
|
+
2. **Hierarchy Control**: Child GameObject allows independent positioning/rotation
|
|
32
|
+
3. **Clean Architecture**: Follows the component-based design
|
|
33
|
+
4. **Automatic Loading**: Mesh loads asynchronously without blocking
|
|
34
|
+
|
|
35
|
+
## Complete Example
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { Component, GameObject, MeshRenderer } from "@series-ai/rundot-3d-engine"
|
|
39
|
+
|
|
40
|
+
class Pickup extends Component {
|
|
41
|
+
private rendererObject: GameObject | null = null
|
|
42
|
+
|
|
43
|
+
protected onCreate(): void {
|
|
44
|
+
this.createMeshRenderer()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private createMeshRenderer(): void {
|
|
48
|
+
// 1. Create MeshRenderer component
|
|
49
|
+
const renderer = new MeshRenderer("restaurant_display_Money")
|
|
50
|
+
|
|
51
|
+
// 2. Create child GameObject
|
|
52
|
+
this.rendererObject = new GameObject("RendererObject")
|
|
53
|
+
|
|
54
|
+
// 3. Add component to GameObject
|
|
55
|
+
this.rendererObject.addComponent(renderer)
|
|
56
|
+
|
|
57
|
+
// 4. Add as child
|
|
58
|
+
this.gameObject.add(this.rendererObject)
|
|
59
|
+
|
|
60
|
+
// 5. Optional: Position/rotate mesh
|
|
61
|
+
this.rendererObject.position.set(0, 2, 0)
|
|
62
|
+
this.rendererObject.rotation.y = Math.PI / 4
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
protected onCleanup(): void {
|
|
66
|
+
// Cleanup happens automatically when GameObject is disposed
|
|
67
|
+
this.rendererObject?.dispose()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## What NOT to Do
|
|
73
|
+
|
|
74
|
+
### ❌ Don't Use StowKitSystem Directly
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// BAD - Bypasses component system
|
|
78
|
+
private async createMeshRenderer(): Promise<void> {
|
|
79
|
+
const stowkit = StowKitSystem.getInstance()
|
|
80
|
+
const mesh = await stowkit.getMesh("restaurant_display_Money")
|
|
81
|
+
this.gameObject.add(mesh.clone())
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Problems:
|
|
86
|
+
- No automatic cleanup
|
|
87
|
+
- No component lifecycle
|
|
88
|
+
- Manual async handling required
|
|
89
|
+
- Harder to manage
|
|
90
|
+
|
|
91
|
+
### ❌ Don't Add MeshRenderer Directly to Parent
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// BAD - No hierarchy control
|
|
95
|
+
this.gameObject.addComponent(new MeshRenderer("mesh_name"))
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Problems:
|
|
99
|
+
- Can't position mesh independently
|
|
100
|
+
- Can't have multiple meshes on same GameObject
|
|
101
|
+
- Less flexible
|
|
102
|
+
|
|
103
|
+
## Benefits of the Pattern
|
|
104
|
+
|
|
105
|
+
### Independent Positioning
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// Parent GameObject at (0, 0, 0)
|
|
109
|
+
const parent = new GameObject("Parent")
|
|
110
|
+
|
|
111
|
+
// Mesh offset from parent
|
|
112
|
+
const renderer = new MeshRenderer("mesh")
|
|
113
|
+
const meshObj = new GameObject("Mesh")
|
|
114
|
+
meshObj.addComponent(renderer)
|
|
115
|
+
parent.add(meshObj)
|
|
116
|
+
|
|
117
|
+
meshObj.position.set(0, -0.5, 0) // Visual offset
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Multiple Meshes
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// Multiple meshes on one GameObject
|
|
124
|
+
const character = new GameObject("Character")
|
|
125
|
+
|
|
126
|
+
const body = new GameObject("Body")
|
|
127
|
+
body.addComponent(new MeshRenderer("body_mesh"))
|
|
128
|
+
character.add(body)
|
|
129
|
+
|
|
130
|
+
const weapon = new GameObject("Weapon")
|
|
131
|
+
weapon.addComponent(new MeshRenderer("weapon_mesh"))
|
|
132
|
+
character.add(weapon)
|
|
133
|
+
weapon.position.set(0.5, 1, 0) // Weapon offset
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Automatic Async Loading
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// Mesh loads in background
|
|
140
|
+
const renderer = new MeshRenderer("large_mesh")
|
|
141
|
+
const obj = new GameObject("Mesh")
|
|
142
|
+
obj.addComponent(renderer)
|
|
143
|
+
this.gameObject.add(obj)
|
|
144
|
+
|
|
145
|
+
// No await needed - appears when ready!
|
|
146
|
+
// Component continues working while mesh loads
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## High-Performance Pattern: InstancedRenderer
|
|
150
|
+
|
|
151
|
+
**Use InstancedRenderer when displaying many copies of the same mesh:**
|
|
152
|
+
|
|
153
|
+
InstancedRenderer uses GPU instancing to render hundreds or thousands of the same mesh efficiently. Each instance can have its own position, rotation, scale, and color.
|
|
154
|
+
|
|
155
|
+
### When to Use InstancedRenderer
|
|
156
|
+
|
|
157
|
+
- **Many copies** of the same mesh (50+)
|
|
158
|
+
- **Same geometry** for all instances (coins, trees, enemies, projectiles)
|
|
159
|
+
- **Performance critical** scenarios (large crowds, particle-like effects)
|
|
160
|
+
- **Different transforms** per instance (position, rotation, scale)
|
|
161
|
+
|
|
162
|
+
### Basic Example
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { Component, GameObject } from "@series-ai/rundot-3d-engine"
|
|
166
|
+
import { InstancedRenderer } from "@series-ai/rundot-3d-engine/rendering"
|
|
167
|
+
import * as THREE from "three"
|
|
168
|
+
|
|
169
|
+
class CoinSpawner extends Component {
|
|
170
|
+
private instancedRenderer: InstancedRenderer | null = null
|
|
171
|
+
private rendererObject: GameObject | null = null
|
|
172
|
+
|
|
173
|
+
protected onCreate(): void {
|
|
174
|
+
this.spawnCoins()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private spawnCoins(): void {
|
|
178
|
+
// 1. Create InstancedRenderer with mesh name and instance count
|
|
179
|
+
this.instancedRenderer = new InstancedRenderer(
|
|
180
|
+
"restaurant_display_Money",
|
|
181
|
+
100 // Number of instances
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
// 2. Create child GameObject
|
|
185
|
+
this.rendererObject = new GameObject("CoinsRenderer")
|
|
186
|
+
|
|
187
|
+
// 3. Add component
|
|
188
|
+
this.rendererObject.addComponent(this.instancedRenderer)
|
|
189
|
+
|
|
190
|
+
// 4. Add as child
|
|
191
|
+
this.gameObject.add(this.rendererObject)
|
|
192
|
+
|
|
193
|
+
// 5. Set positions for each instance
|
|
194
|
+
for (let i = 0; i < 100; i++) {
|
|
195
|
+
const x = (i % 10) * 2 - 10
|
|
196
|
+
const z = Math.floor(i / 10) * 2 - 10
|
|
197
|
+
const position = new THREE.Vector3(x, 0, z)
|
|
198
|
+
|
|
199
|
+
this.instancedRenderer.setInstancePosition(i, position)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 6. Update to apply transforms
|
|
203
|
+
this.instancedRenderer.updateInstances()
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
protected onCleanup(): void {
|
|
207
|
+
this.rendererObject?.dispose()
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Setting Instance Properties
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
// Set position
|
|
216
|
+
instancedRenderer.setInstancePosition(0, new THREE.Vector3(0, 5, 0))
|
|
217
|
+
|
|
218
|
+
// Set rotation
|
|
219
|
+
instancedRenderer.setInstanceRotation(0, new THREE.Quaternion())
|
|
220
|
+
|
|
221
|
+
// Set scale
|
|
222
|
+
instancedRenderer.setInstanceScale(0, new THREE.Vector3(1, 1, 1))
|
|
223
|
+
|
|
224
|
+
// Set color (tint)
|
|
225
|
+
instancedRenderer.setInstanceColor(0, new THREE.Color(0xff0000))
|
|
226
|
+
|
|
227
|
+
// IMPORTANT: Always call updateInstances() after making changes
|
|
228
|
+
instancedRenderer.updateInstances()
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Dynamic Updates Example
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
class FloatingCoins extends Component {
|
|
235
|
+
private instancedRenderer: InstancedRenderer | null = null
|
|
236
|
+
private elapsedTime: number = 0
|
|
237
|
+
|
|
238
|
+
update(deltaTime: number): void {
|
|
239
|
+
if (!this.instancedRenderer) return
|
|
240
|
+
|
|
241
|
+
this.elapsedTime += deltaTime
|
|
242
|
+
|
|
243
|
+
// Animate each instance
|
|
244
|
+
for (let i = 0; i < 100; i++) {
|
|
245
|
+
const offset = i * 0.1
|
|
246
|
+
const y = Math.sin(this.elapsedTime + offset) * 2
|
|
247
|
+
const pos = new THREE.Vector3(
|
|
248
|
+
(i % 10) * 2 - 10,
|
|
249
|
+
y,
|
|
250
|
+
Math.floor(i / 10) * 2 - 10
|
|
251
|
+
)
|
|
252
|
+
this.instancedRenderer.setInstancePosition(i, pos)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Update GPU buffer with new transforms
|
|
256
|
+
this.instancedRenderer.updateInstances()
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Showing/Hiding Instances
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
// Hide an instance (moves it far away)
|
|
265
|
+
instancedRenderer.setInstanceVisible(5, false)
|
|
266
|
+
|
|
267
|
+
// Show an instance
|
|
268
|
+
instancedRenderer.setInstanceVisible(5, true)
|
|
269
|
+
|
|
270
|
+
// Don't forget to update!
|
|
271
|
+
instancedRenderer.updateInstances()
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Choosing Between MeshRenderer and InstancedRenderer
|
|
275
|
+
|
|
276
|
+
### Use MeshRenderer When:
|
|
277
|
+
- ✅ You have unique objects or small numbers (< 50)
|
|
278
|
+
- ✅ Each mesh is different
|
|
279
|
+
- ✅ Each object has unique components/behavior
|
|
280
|
+
- ✅ Objects are added/removed dynamically
|
|
281
|
+
- ✅ Standard use case
|
|
282
|
+
|
|
283
|
+
### Use InstancedRenderer When:
|
|
284
|
+
- ✅ You need many copies of the same mesh (50+)
|
|
285
|
+
- ✅ All instances share the same geometry
|
|
286
|
+
- ✅ Performance is critical
|
|
287
|
+
- ✅ You can manage instances in a batch
|
|
288
|
+
- ✅ Instances have similar behavior
|
|
289
|
+
|
|
290
|
+
### Performance Comparison
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
// BAD - 1000 individual MeshRenderers
|
|
294
|
+
for (let i = 0; i < 1000; i++) {
|
|
295
|
+
const obj = new GameObject(`Coin_${i}`)
|
|
296
|
+
const renderer = new MeshRenderer("coin_mesh")
|
|
297
|
+
obj.addComponent(renderer)
|
|
298
|
+
scene.add(obj)
|
|
299
|
+
// Result: 1000 draw calls, poor performance
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// GOOD - 1 InstancedRenderer with 1000 instances
|
|
303
|
+
const renderer = new InstancedRenderer("coin_mesh", 1000)
|
|
304
|
+
const obj = new GameObject("Coins")
|
|
305
|
+
obj.addComponent(renderer)
|
|
306
|
+
scene.add(obj)
|
|
307
|
+
// Result: 1 draw call, excellent performance
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Related Patterns
|
|
311
|
+
|
|
312
|
+
- [Creating GameObjects](CreatingGameObjects.md) - GameObject best practices
|
|
313
|
+
- [Component Communication](ComponentCommunication.md) - Inter-component patterns
|
|
314
|
+
- [MeshRenderer](../rendering/MeshRenderer.md) - Full MeshRenderer documentation
|
|
315
|
+
- [InstancedRenderer](../rendering/InstancedRenderer.md) - Full InstancedRenderer documentation
|
|
316
|
+
|