@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,338 +1,338 @@
1
- # Mesh Colliders Pattern
2
-
3
- How to load 3D meshes and attach collision bounds in the Rundot 3D Engine.
4
-
5
- ## Overview
6
-
7
- When loading meshes, you need to decide between:
8
- - **Auto-fitted bounds colliders** - Accurate collision that matches the mesh shape
9
- - **Primitive colliders** - Simpler, more performant approximate collision
10
-
11
- ## Auto-Fitted Bounds Pattern
12
-
13
- ### Basic Example: fitToMesh
14
-
15
- The simplest approach - automatically fit the collider to the mesh bounds:
16
-
17
- ```typescript
18
- import { Component, GameObject, MeshRenderer } from "@series-ai/rundot-3d-engine"
19
- import { RigidBodyComponentThree, RigidBodyType, ColliderShape } from "@series-ai/rundot-3d-engine/systems"
20
-
21
- class Crate extends Component {
22
- private rendererObject: GameObject | null = null
23
-
24
- protected onCreate(): void {
25
- this.createMeshWithCollider()
26
- }
27
-
28
- private createMeshWithCollider(): void {
29
- // 1. Create and attach mesh renderer
30
- const renderer = new MeshRenderer("crate_mesh")
31
- this.rendererObject = new GameObject("CrateRenderer")
32
- this.rendererObject.addComponent(renderer)
33
- this.gameObject.add(this.rendererObject)
34
-
35
- // 2. Add auto-fitted collision
36
- const rigidBody = new RigidBodyComponentThree({
37
- type: RigidBodyType.DYNAMIC,
38
- shape: ColliderShape.BOX,
39
- fitToMesh: true, // ✨ Automatically calculates collision bounds
40
- mass: 10
41
- })
42
- this.gameObject.addComponent(rigidBody)
43
- }
44
-
45
- protected onCleanup(): void {
46
- this.rendererObject?.dispose()
47
- }
48
- }
49
- ```
50
-
51
- ### Advanced Example: Manual Bounds with Timing
52
-
53
- Wait for mesh to load before calculating bounds:
54
-
55
- ```typescript
56
- import { Component, GameObject, MeshRenderer } from "@series-ai/rundot-3d-engine"
57
- import { RigidBodyComponentThree, RigidBodyType, ColliderShape } from "@series-ai/rundot-3d-engine/systems"
58
-
59
- class ComplexObject extends Component {
60
- private rendererObject: GameObject | null = null
61
- private renderer: MeshRenderer | null = null
62
- private hasAddedCollider: boolean = false
63
-
64
- protected onCreate(): void {
65
- // 1. Create mesh renderer
66
- this.renderer = new MeshRenderer("complex_mesh")
67
- this.rendererObject = new GameObject("ComplexRenderer")
68
- this.rendererObject.addComponent(this.renderer)
69
- this.gameObject.add(this.rendererObject)
70
- }
71
-
72
- public update(deltaTime: number): void {
73
- // 2. Check if mesh is loaded and collider hasn't been added yet
74
- if (!this.hasAddedCollider && this.renderer && this.renderer.isLoaded()) {
75
- this.addColliderFromBounds()
76
- this.hasAddedCollider = true
77
- }
78
- }
79
-
80
- private addColliderFromBounds(): void {
81
- // 3. Calculate bounds from loaded mesh
82
- const bounds = this.renderer!.getBounds()
83
-
84
- if (bounds) {
85
- // 4. Create collider with calculated bounds
86
- const rigidBody = RigidBodyComponentThree.fromBounds(bounds, {
87
- type: RigidBodyType.STATIC,
88
- shape: ColliderShape.BOX
89
- })
90
- this.gameObject.addComponent(rigidBody)
91
-
92
- console.log(`Created collider with bounds: ${bounds.x}, ${bounds.y}, ${bounds.z}`)
93
- }
94
- }
95
-
96
- protected onCleanup(): void {
97
- this.rendererObject?.dispose()
98
- }
99
- }
100
- ```
101
-
102
- ### Using getMeshBounds Static Method
103
-
104
- Calculate bounds from the entire GameObject hierarchy:
105
-
106
- ```typescript
107
- private addBoundsCollider(): void {
108
- // Calculate bounds from all meshes in GameObject
109
- const bounds = RigidBodyComponentThree.getMeshBounds(this.gameObject)
110
-
111
- console.log(`Calculated bounds: width=${bounds.x}, height=${bounds.y}, depth=${bounds.z}`)
112
-
113
- // Create collider with bounds
114
- const rigidBody = new RigidBodyComponentThree({
115
- type: RigidBodyType.STATIC,
116
- shape: ColliderShape.BOX,
117
- size: bounds
118
- })
119
- this.gameObject.addComponent(rigidBody)
120
- }
121
- ```
122
-
123
- ## Primitive Colliders for Performance
124
-
125
- For complex meshes or many instances, use simpler primitive colliders instead of bounds:
126
-
127
- ### Example: Complex Mesh with Simple Collider
128
-
129
- ```typescript
130
- class Tree extends Component {
131
- private rendererObject: GameObject | null = null
132
-
133
- protected onCreate(): void {
134
- // 1. Load detailed tree mesh
135
- const renderer = new MeshRenderer("detailed_tree_mesh")
136
- this.rendererObject = new GameObject("TreeRenderer")
137
- this.rendererObject.addComponent(renderer)
138
- this.gameObject.add(this.rendererObject)
139
-
140
- // 2. Use simple capsule collider (not bounds)
141
- // Much better performance than fitting to complex tree mesh
142
- const rigidBody = new RigidBodyComponentThree({
143
- type: RigidBodyType.STATIC,
144
- shape: ColliderShape.CAPSULE,
145
- radius: 0.5, // Approximate trunk radius
146
- height: 8.0, // Approximate tree height
147
- mass: 1000 // Heavy (won't move)
148
- })
149
- this.gameObject.addComponent(rigidBody)
150
- }
151
- }
152
- ```
153
-
154
- ### Example: Character with Mixed Colliders
155
-
156
- Use simple colliders for the character, but bounds for attachments:
157
-
158
- ```typescript
159
- class Character extends Component {
160
- protected onCreate(): void {
161
- // Character uses simple capsule (better for movement)
162
- const characterBody = new RigidBodyComponentThree({
163
- type: RigidBodyType.DYNAMIC,
164
- shape: ColliderShape.CAPSULE,
165
- radius: 0.5,
166
- height: 1.8,
167
- lockRotationX: true,
168
- lockRotationY: true,
169
- lockRotationZ: true
170
- })
171
- this.gameObject.addComponent(characterBody)
172
-
173
- // Add visual mesh (doesn't need to match collision exactly)
174
- const renderer = new MeshRenderer("character_mesh")
175
- const rendererObj = new GameObject("CharacterRenderer")
176
- rendererObj.addComponent(renderer)
177
- this.gameObject.add(rendererObj)
178
- }
179
- }
180
- ```
181
-
182
- ## When to Use Each Approach
183
-
184
- ### Use Auto-Fitted Bounds When:
185
-
186
- ✅ **Accurate collision is critical**
187
- - Pickups that need precise click detection
188
- - Interactive objects with complex shapes
189
- - Puzzle pieces that must fit together
190
- - Static environment meshes (walls, platforms)
191
-
192
- ✅ **Mesh shape is regular**
193
- - Box-like objects (crates, buildings)
194
- - Objects without many protrusions
195
- - Static decorations
196
-
197
- ✅ **Performance isn't critical**
198
- - Small number of objects (< 50)
199
- - Static objects that don't move
200
-
201
- ### Use Primitive Colliders When:
202
-
203
- ✅ **Performance is critical**
204
- - Many instances (trees, rocks, debris)
205
- - Complex meshes with thousands of vertices
206
- - Moving/dynamic objects
207
- - Objects using InstancedRenderer
208
-
209
- ✅ **Approximate collision is acceptable**
210
- - Background decorations
211
- - Organic shapes (trees, bushes, rocks)
212
- - Characters (capsule works better than mesh)
213
-
214
- ✅ **Simpler physics behavior desired**
215
- - Spheres for smooth rolling
216
- - Capsules for smooth character movement
217
- - Boxes for stackable objects
218
-
219
- ## Performance Comparison
220
-
221
- ```typescript
222
- // ❌ BAD: Complex mesh with many instances using bounds
223
- for (let i = 0; i < 100; i++) {
224
- const rock = new GameObject(`Rock_${i}`)
225
- const renderer = new MeshRenderer("complex_rock_mesh") // 10k vertices
226
- rock.addComponent(renderer)
227
-
228
- rock.addComponent(new RigidBodyComponentThree({
229
- shape: ColliderShape.BOX,
230
- fitToMesh: true // Calculates bounds for each rock
231
- }))
232
- }
233
- // Result: Expensive bounds calculations, potential performance issues
234
-
235
- // ✅ GOOD: Complex mesh with simple sphere collider
236
- for (let i = 0; i < 100; i++) {
237
- const rock = new GameObject(`Rock_${i}`)
238
- const renderer = new MeshRenderer("complex_rock_mesh")
239
- rock.addComponent(renderer)
240
-
241
- rock.addComponent(new RigidBodyComponentThree({
242
- shape: ColliderShape.SPHERE,
243
- radius: 0.8 // Simple approximate collision
244
- }))
245
- }
246
- // Result: Fast, efficient collision with acceptable accuracy
247
- ```
248
-
249
- ## Complete Example: Pickup Item
250
-
251
- Combining mesh loading with auto-fitted trigger collision:
252
-
253
- ```typescript
254
- import { Component, GameObject, MeshRenderer } from "@series-ai/rundot-3d-engine"
255
- import { RigidBodyComponentThree, RigidBodyType, ColliderShape } from "@series-ai/rundot-3d-engine/systems"
256
-
257
- class CoinPickup extends Component {
258
- private rendererObject: GameObject | null = null
259
-
260
- protected onCreate(): void {
261
- // 1. Create visual mesh
262
- const renderer = new MeshRenderer("coin_mesh")
263
- this.rendererObject = new GameObject("CoinRenderer")
264
- this.rendererObject.addComponent(renderer)
265
- this.gameObject.add(this.rendererObject)
266
-
267
- // 2. Add trigger collider fitted to mesh bounds
268
- const trigger = new RigidBodyComponentThree({
269
- type: RigidBodyType.STATIC,
270
- shape: ColliderShape.BOX,
271
- fitToMesh: true, // Automatically fit to coin mesh
272
- isSensor: true // Make it a trigger
273
- })
274
- this.gameObject.addComponent(trigger)
275
-
276
- // 3. Handle pickup collision
277
- trigger.onTriggerEnter((other: GameObject) => {
278
- if (other.name === "Player") {
279
- console.log("Coin collected!")
280
- this.collect()
281
- }
282
- })
283
- }
284
-
285
- private collect(): void {
286
- // Animate and remove
287
- this.gameObject.dispose()
288
- }
289
-
290
- protected onCleanup(): void {
291
- this.rendererObject?.dispose()
292
- }
293
- }
294
- ```
295
-
296
- ## Tips & Best Practices
297
-
298
- ### 1. fitToMesh Timing
299
- `fitToMesh` calculates bounds during onCreate, so meshes should be loaded or loading. The calculation works even if the mesh isn't fully loaded yet (it recalculates when ready).
300
-
301
- ### 2. Scaling Considerations
302
- If you scale your GameObject, bounds are calculated in world space:
303
-
304
- ```typescript
305
- this.gameObject.scale.set(2, 2, 2) // Double size
306
- const rigidBody = new RigidBodyComponentThree({
307
- fitToMesh: true // Bounds will account for scale
308
- })
309
- ```
310
-
311
- ### 3. Multiple Meshes
312
- `getMeshBounds()` combines all meshes in the GameObject hierarchy:
313
-
314
- ```typescript
315
- // Parent with multiple child meshes
316
- const composite = new GameObject("Composite")
317
- composite.add(createMesh("part1"))
318
- composite.add(createMesh("part2"))
319
-
320
- // Bounds will include both meshes
321
- const bounds = RigidBodyComponentThree.getMeshBounds(composite)
322
- ```
323
-
324
- ### 4. Debug Visualization
325
- Enable physics debug rendering to see collision bounds:
326
-
327
- ```typescript
328
- // In your game class
329
- PhysicsSystem.getInstance().setDebugRender(true)
330
- ```
331
-
332
- ## Related Patterns
333
-
334
- - [Mesh Loading](MeshLoading.md) - Loading and displaying meshes
335
- - [Creating GameObjects](CreatingGameObjects.md) - GameObject best practices
336
- - [Colliders](../physics/Colliders.md) - Full collider documentation
337
- - [RigidBodyComponent](../physics/RigidBodyComponent.md) - Physics body documentation
338
-
1
+ # Mesh Colliders Pattern
2
+
3
+ How to load 3D meshes and attach collision bounds in the Rundot 3D Engine.
4
+
5
+ ## Overview
6
+
7
+ When loading meshes, you need to decide between:
8
+ - **Auto-fitted bounds colliders** - Accurate collision that matches the mesh shape
9
+ - **Primitive colliders** - Simpler, more performant approximate collision
10
+
11
+ ## Auto-Fitted Bounds Pattern
12
+
13
+ ### Basic Example: fitToMesh
14
+
15
+ The simplest approach - automatically fit the collider to the mesh bounds:
16
+
17
+ ```typescript
18
+ import { Component, GameObject, MeshRenderer } from "@series-ai/rundot-3d-engine"
19
+ import { RigidBodyComponentThree, RigidBodyType, ColliderShape } from "@series-ai/rundot-3d-engine/systems"
20
+
21
+ class Crate extends Component {
22
+ private rendererObject: GameObject | null = null
23
+
24
+ protected onCreate(): void {
25
+ this.createMeshWithCollider()
26
+ }
27
+
28
+ private createMeshWithCollider(): void {
29
+ // 1. Create and attach mesh renderer
30
+ const renderer = new MeshRenderer("crate_mesh")
31
+ this.rendererObject = new GameObject("CrateRenderer")
32
+ this.rendererObject.addComponent(renderer)
33
+ this.gameObject.add(this.rendererObject)
34
+
35
+ // 2. Add auto-fitted collision
36
+ const rigidBody = new RigidBodyComponentThree({
37
+ type: RigidBodyType.DYNAMIC,
38
+ shape: ColliderShape.BOX,
39
+ fitToMesh: true, // ✨ Automatically calculates collision bounds
40
+ mass: 10
41
+ })
42
+ this.gameObject.addComponent(rigidBody)
43
+ }
44
+
45
+ protected onCleanup(): void {
46
+ this.rendererObject?.dispose()
47
+ }
48
+ }
49
+ ```
50
+
51
+ ### Advanced Example: Manual Bounds with Timing
52
+
53
+ Wait for mesh to load before calculating bounds:
54
+
55
+ ```typescript
56
+ import { Component, GameObject, MeshRenderer } from "@series-ai/rundot-3d-engine"
57
+ import { RigidBodyComponentThree, RigidBodyType, ColliderShape } from "@series-ai/rundot-3d-engine/systems"
58
+
59
+ class ComplexObject extends Component {
60
+ private rendererObject: GameObject | null = null
61
+ private renderer: MeshRenderer | null = null
62
+ private hasAddedCollider: boolean = false
63
+
64
+ protected onCreate(): void {
65
+ // 1. Create mesh renderer
66
+ this.renderer = new MeshRenderer("complex_mesh")
67
+ this.rendererObject = new GameObject("ComplexRenderer")
68
+ this.rendererObject.addComponent(this.renderer)
69
+ this.gameObject.add(this.rendererObject)
70
+ }
71
+
72
+ public update(deltaTime: number): void {
73
+ // 2. Check if mesh is loaded and collider hasn't been added yet
74
+ if (!this.hasAddedCollider && this.renderer && this.renderer.isLoaded()) {
75
+ this.addColliderFromBounds()
76
+ this.hasAddedCollider = true
77
+ }
78
+ }
79
+
80
+ private addColliderFromBounds(): void {
81
+ // 3. Calculate bounds from loaded mesh
82
+ const bounds = this.renderer!.getBounds()
83
+
84
+ if (bounds) {
85
+ // 4. Create collider with calculated bounds
86
+ const rigidBody = RigidBodyComponentThree.fromBounds(bounds, {
87
+ type: RigidBodyType.STATIC,
88
+ shape: ColliderShape.BOX
89
+ })
90
+ this.gameObject.addComponent(rigidBody)
91
+
92
+ console.log(`Created collider with bounds: ${bounds.x}, ${bounds.y}, ${bounds.z}`)
93
+ }
94
+ }
95
+
96
+ protected onCleanup(): void {
97
+ this.rendererObject?.dispose()
98
+ }
99
+ }
100
+ ```
101
+
102
+ ### Using getMeshBounds Static Method
103
+
104
+ Calculate bounds from the entire GameObject hierarchy:
105
+
106
+ ```typescript
107
+ private addBoundsCollider(): void {
108
+ // Calculate bounds from all meshes in GameObject
109
+ const bounds = RigidBodyComponentThree.getMeshBounds(this.gameObject)
110
+
111
+ console.log(`Calculated bounds: width=${bounds.x}, height=${bounds.y}, depth=${bounds.z}`)
112
+
113
+ // Create collider with bounds
114
+ const rigidBody = new RigidBodyComponentThree({
115
+ type: RigidBodyType.STATIC,
116
+ shape: ColliderShape.BOX,
117
+ size: bounds
118
+ })
119
+ this.gameObject.addComponent(rigidBody)
120
+ }
121
+ ```
122
+
123
+ ## Primitive Colliders for Performance
124
+
125
+ For complex meshes or many instances, use simpler primitive colliders instead of bounds:
126
+
127
+ ### Example: Complex Mesh with Simple Collider
128
+
129
+ ```typescript
130
+ class Tree extends Component {
131
+ private rendererObject: GameObject | null = null
132
+
133
+ protected onCreate(): void {
134
+ // 1. Load detailed tree mesh
135
+ const renderer = new MeshRenderer("detailed_tree_mesh")
136
+ this.rendererObject = new GameObject("TreeRenderer")
137
+ this.rendererObject.addComponent(renderer)
138
+ this.gameObject.add(this.rendererObject)
139
+
140
+ // 2. Use simple capsule collider (not bounds)
141
+ // Much better performance than fitting to complex tree mesh
142
+ const rigidBody = new RigidBodyComponentThree({
143
+ type: RigidBodyType.STATIC,
144
+ shape: ColliderShape.CAPSULE,
145
+ radius: 0.5, // Approximate trunk radius
146
+ height: 8.0, // Approximate tree height
147
+ mass: 1000 // Heavy (won't move)
148
+ })
149
+ this.gameObject.addComponent(rigidBody)
150
+ }
151
+ }
152
+ ```
153
+
154
+ ### Example: Character with Mixed Colliders
155
+
156
+ Use simple colliders for the character, but bounds for attachments:
157
+
158
+ ```typescript
159
+ class Character extends Component {
160
+ protected onCreate(): void {
161
+ // Character uses simple capsule (better for movement)
162
+ const characterBody = new RigidBodyComponentThree({
163
+ type: RigidBodyType.DYNAMIC,
164
+ shape: ColliderShape.CAPSULE,
165
+ radius: 0.5,
166
+ height: 1.8,
167
+ lockRotationX: true,
168
+ lockRotationY: true,
169
+ lockRotationZ: true
170
+ })
171
+ this.gameObject.addComponent(characterBody)
172
+
173
+ // Add visual mesh (doesn't need to match collision exactly)
174
+ const renderer = new MeshRenderer("character_mesh")
175
+ const rendererObj = new GameObject("CharacterRenderer")
176
+ rendererObj.addComponent(renderer)
177
+ this.gameObject.add(rendererObj)
178
+ }
179
+ }
180
+ ```
181
+
182
+ ## When to Use Each Approach
183
+
184
+ ### Use Auto-Fitted Bounds When:
185
+
186
+ ✅ **Accurate collision is critical**
187
+ - Pickups that need precise click detection
188
+ - Interactive objects with complex shapes
189
+ - Puzzle pieces that must fit together
190
+ - Static environment meshes (walls, platforms)
191
+
192
+ ✅ **Mesh shape is regular**
193
+ - Box-like objects (crates, buildings)
194
+ - Objects without many protrusions
195
+ - Static decorations
196
+
197
+ ✅ **Performance isn't critical**
198
+ - Small number of objects (< 50)
199
+ - Static objects that don't move
200
+
201
+ ### Use Primitive Colliders When:
202
+
203
+ ✅ **Performance is critical**
204
+ - Many instances (trees, rocks, debris)
205
+ - Complex meshes with thousands of vertices
206
+ - Moving/dynamic objects
207
+ - Objects using InstancedRenderer
208
+
209
+ ✅ **Approximate collision is acceptable**
210
+ - Background decorations
211
+ - Organic shapes (trees, bushes, rocks)
212
+ - Characters (capsule works better than mesh)
213
+
214
+ ✅ **Simpler physics behavior desired**
215
+ - Spheres for smooth rolling
216
+ - Capsules for smooth character movement
217
+ - Boxes for stackable objects
218
+
219
+ ## Performance Comparison
220
+
221
+ ```typescript
222
+ // ❌ BAD: Complex mesh with many instances using bounds
223
+ for (let i = 0; i < 100; i++) {
224
+ const rock = new GameObject(`Rock_${i}`)
225
+ const renderer = new MeshRenderer("complex_rock_mesh") // 10k vertices
226
+ rock.addComponent(renderer)
227
+
228
+ rock.addComponent(new RigidBodyComponentThree({
229
+ shape: ColliderShape.BOX,
230
+ fitToMesh: true // Calculates bounds for each rock
231
+ }))
232
+ }
233
+ // Result: Expensive bounds calculations, potential performance issues
234
+
235
+ // ✅ GOOD: Complex mesh with simple sphere collider
236
+ for (let i = 0; i < 100; i++) {
237
+ const rock = new GameObject(`Rock_${i}`)
238
+ const renderer = new MeshRenderer("complex_rock_mesh")
239
+ rock.addComponent(renderer)
240
+
241
+ rock.addComponent(new RigidBodyComponentThree({
242
+ shape: ColliderShape.SPHERE,
243
+ radius: 0.8 // Simple approximate collision
244
+ }))
245
+ }
246
+ // Result: Fast, efficient collision with acceptable accuracy
247
+ ```
248
+
249
+ ## Complete Example: Pickup Item
250
+
251
+ Combining mesh loading with auto-fitted trigger collision:
252
+
253
+ ```typescript
254
+ import { Component, GameObject, MeshRenderer } from "@series-ai/rundot-3d-engine"
255
+ import { RigidBodyComponentThree, RigidBodyType, ColliderShape } from "@series-ai/rundot-3d-engine/systems"
256
+
257
+ class CoinPickup extends Component {
258
+ private rendererObject: GameObject | null = null
259
+
260
+ protected onCreate(): void {
261
+ // 1. Create visual mesh
262
+ const renderer = new MeshRenderer("coin_mesh")
263
+ this.rendererObject = new GameObject("CoinRenderer")
264
+ this.rendererObject.addComponent(renderer)
265
+ this.gameObject.add(this.rendererObject)
266
+
267
+ // 2. Add trigger collider fitted to mesh bounds
268
+ const trigger = new RigidBodyComponentThree({
269
+ type: RigidBodyType.STATIC,
270
+ shape: ColliderShape.BOX,
271
+ fitToMesh: true, // Automatically fit to coin mesh
272
+ isSensor: true // Make it a trigger
273
+ })
274
+ this.gameObject.addComponent(trigger)
275
+
276
+ // 3. Handle pickup collision
277
+ trigger.onTriggerEnter((other: GameObject) => {
278
+ if (other.name === "Player") {
279
+ console.log("Coin collected!")
280
+ this.collect()
281
+ }
282
+ })
283
+ }
284
+
285
+ private collect(): void {
286
+ // Animate and remove
287
+ this.gameObject.dispose()
288
+ }
289
+
290
+ protected onCleanup(): void {
291
+ this.rendererObject?.dispose()
292
+ }
293
+ }
294
+ ```
295
+
296
+ ## Tips & Best Practices
297
+
298
+ ### 1. fitToMesh Timing
299
+ `fitToMesh` calculates bounds during onCreate, so meshes should be loaded or loading. The calculation works even if the mesh isn't fully loaded yet (it recalculates when ready).
300
+
301
+ ### 2. Scaling Considerations
302
+ If you scale your GameObject, bounds are calculated in world space:
303
+
304
+ ```typescript
305
+ this.gameObject.scale.set(2, 2, 2) // Double size
306
+ const rigidBody = new RigidBodyComponentThree({
307
+ fitToMesh: true // Bounds will account for scale
308
+ })
309
+ ```
310
+
311
+ ### 3. Multiple Meshes
312
+ `getMeshBounds()` combines all meshes in the GameObject hierarchy:
313
+
314
+ ```typescript
315
+ // Parent with multiple child meshes
316
+ const composite = new GameObject("Composite")
317
+ composite.add(createMesh("part1"))
318
+ composite.add(createMesh("part2"))
319
+
320
+ // Bounds will include both meshes
321
+ const bounds = RigidBodyComponentThree.getMeshBounds(composite)
322
+ ```
323
+
324
+ ### 4. Debug Visualization
325
+ Enable physics debug rendering to see collision bounds:
326
+
327
+ ```typescript
328
+ // In your game class
329
+ PhysicsSystem.getInstance().setDebugRender(true)
330
+ ```
331
+
332
+ ## Related Patterns
333
+
334
+ - [Mesh Loading](MeshLoading.md) - Loading and displaying meshes
335
+ - [Creating GameObjects](CreatingGameObjects.md) - GameObject best practices
336
+ - [Colliders](../physics/Colliders.md) - Full collider documentation
337
+ - [RigidBodyComponent](../physics/RigidBodyComponent.md) - Physics body documentation
338
+