@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,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
+