Spaces:
Sleeping
Sleeping
Commit ·
5b29015
1
Parent(s): 39f3262
Added tools, and improved player movement
Browse files- README.md +81 -33
- app.py +79 -22
- backend/game_models.py +2 -2
- backend/main.py +10 -0
- backend/mcp_server.py +396 -1
- backend/storage.py +78 -19
- backend/tools/rendering_tools.py +81 -0
- backend/tools/scene_tools.py +103 -0
- chat_client.py +316 -0
- frontend/game_viewer.html +555 -5
README.md
CHANGED
|
@@ -41,15 +41,28 @@ Simply describe what you want:
|
|
| 41 |
|
| 42 |
### 🎨 Scene Building
|
| 43 |
- **6 primitive types**: cube, sphere, cylinder, plane, cone, torus
|
|
|
|
| 44 |
- **Flexible positioning**: Place objects anywhere in 3D space
|
| 45 |
-
- **Material system**: Colors, metalness, roughness, opacity
|
| 46 |
- **Dynamic scaling**: Custom size for each object
|
| 47 |
|
| 48 |
-
### 💡 Lighting
|
| 49 |
-
- **4 presets**: day, night, sunset, studio
|
|
|
|
| 50 |
- **Multiple light types**: ambient, directional, point, spot
|
|
|
|
| 51 |
- **Automatic shadows**: Realistic lighting effects
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
### 🎮 FPS Controller
|
| 54 |
- **Physics-based movement**: Cannon.js integration with gravity, jumping, collisions
|
| 55 |
- **WASD controls**: Smooth keyboard-based movement
|
|
@@ -65,9 +78,9 @@ Simply describe what you want:
|
|
| 65 |
|
| 66 |
### 🤖 AI Integration
|
| 67 |
- **MCP protocol**: Works with Claude, GPT, and other AI assistants
|
| 68 |
-
- **Natural language**: Simple commands like "add a blue sphere" or "
|
| 69 |
- **Context aware**: Builds on existing scenes
|
| 70 |
-
- **
|
| 71 |
- **No coding required**: Pure natural language scene building
|
| 72 |
|
| 73 |
---
|
|
@@ -99,9 +112,14 @@ Open `http://localhost:7860` in your browser.
|
|
| 99 |
```
|
| 100 |
Add a red cube at 0,2,0
|
| 101 |
Add a blue sphere at 5,1,5
|
| 102 |
-
Add a
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
Set lighting to night
|
| 104 |
-
Create a level 100 units wide
|
| 105 |
```
|
| 106 |
|
| 107 |
---
|
|
@@ -110,30 +128,59 @@ Create a level 100 units wide
|
|
| 110 |
|
| 111 |
### For AI Assistants (MCP)
|
| 112 |
|
| 113 |
-
The MCP server exposes
|
| 114 |
-
|
| 115 |
-
**Scene Building (
|
| 116 |
-
- `
|
| 117 |
-
- `
|
| 118 |
-
- `
|
| 119 |
-
- `
|
| 120 |
-
- `
|
| 121 |
-
|
| 122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
- `set_player_speed` - Movement speed
|
| 124 |
- `set_jump_force` - Jump height
|
| 125 |
-
- `set_mouse_sensitivity` - Mouse look
|
| 126 |
- `set_gravity` - World gravity
|
| 127 |
- `set_player_dimensions` - Player size
|
| 128 |
-
|
| 129 |
-
**Player Controller Phase 2 (4 tools):**
|
| 130 |
- `set_movement_acceleration` - Movement feel
|
| 131 |
- `set_air_control` - Airborne control
|
| 132 |
- `set_camera_fov` - Field of view
|
| 133 |
- `set_vertical_look_limits` - Look angle limits
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
### For Developers (HTTP API)
|
| 139 |
|
|
@@ -246,25 +293,26 @@ app.py # Gradio chat interface
|
|
| 246 |
### ✅ Completed
|
| 247 |
- **Phase 1**: Player Controller - Core Controls (5 tools)
|
| 248 |
- **Phase 2**: Player Controller - Enhanced Feel (4 tools)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
- Physics engine integration (Cannon.js)
|
| 250 |
- FPS controls (WASD + mouse look)
|
| 251 |
|
| 252 |
-
###
|
| 253 |
-
-
|
| 254 |
-
-
|
| 255 |
-
- Change object materials (color, metalness, roughness)
|
| 256 |
-
- Set background color
|
| 257 |
-
|
| 258 |
-
### 🔮 Phase 3: World Building
|
| 259 |
-
- glTF model loading (Kenney assets)
|
| 260 |
-
- Prefab system (props, buildings, terrain)
|
| 261 |
-
- Scene templates
|
| 262 |
- Export to Unity/Unreal
|
|
|
|
| 263 |
|
| 264 |
### 💭 Future Ideas
|
| 265 |
- NPC system with behaviors
|
| 266 |
- Multiplayer support
|
| 267 |
- Procedural generation
|
|
|
|
| 268 |
|
| 269 |
---
|
| 270 |
|
|
|
|
| 41 |
|
| 42 |
### 🎨 Scene Building
|
| 43 |
- **6 primitive types**: cube, sphere, cylinder, plane, cone, torus
|
| 44 |
+
- **LEGO-style bricks**: 10 brick types from Kenney kit (1x1, 2x4, slopes, etc.)
|
| 45 |
- **Flexible positioning**: Place objects anywhere in 3D space
|
| 46 |
+
- **Material system**: Colors, metalness, roughness, opacity, toon shading
|
| 47 |
- **Dynamic scaling**: Custom size for each object
|
| 48 |
|
| 49 |
+
### 💡 Lighting & Environment
|
| 50 |
+
- **4 lighting presets**: day, night, sunset, studio
|
| 51 |
+
- **Procedural skybox**: Realistic sky with sun positioning
|
| 52 |
- **Multiple light types**: ambient, directional, point, spot
|
| 53 |
+
- **Fog effects**: Linear and exponential fog
|
| 54 |
- **Automatic shadows**: Realistic lighting effects
|
| 55 |
|
| 56 |
+
### ✨ Particle Effects
|
| 57 |
+
- **5 presets**: fire, smoke, sparkle, rain, snow
|
| 58 |
+
- **Localized effects**: Fire, smoke at specific positions
|
| 59 |
+
- **Weather effects**: Rain and snow cover entire world
|
| 60 |
+
|
| 61 |
+
### 📊 UI System
|
| 62 |
+
- **2D text overlay**: Render text on screen at any position
|
| 63 |
+
- **Progress bars**: Health bars, mana bars with labels
|
| 64 |
+
- **HUD elements**: Score displays, status indicators
|
| 65 |
+
|
| 66 |
### 🎮 FPS Controller
|
| 67 |
- **Physics-based movement**: Cannon.js integration with gravity, jumping, collisions
|
| 68 |
- **WASD controls**: Smooth keyboard-based movement
|
|
|
|
| 78 |
|
| 79 |
### 🤖 AI Integration
|
| 80 |
- **MCP protocol**: Works with Claude, GPT, and other AI assistants
|
| 81 |
+
- **Natural language**: Simple commands like "add a blue sphere" or "add rain"
|
| 82 |
- **Context aware**: Builds on existing scenes
|
| 83 |
+
- **40+ MCP tools**: Scene (6) + Player (10) + Rendering (10) + Environment (4) + UI (4) + Post-processing (8)
|
| 84 |
- **No coding required**: Pure natural language scene building
|
| 85 |
|
| 86 |
---
|
|
|
|
| 112 |
```
|
| 113 |
Add a red cube at 0,2,0
|
| 114 |
Add a blue sphere at 5,1,5
|
| 115 |
+
Add a sunset skybox
|
| 116 |
+
Add fire particles at 0,1,0
|
| 117 |
+
Add rain
|
| 118 |
+
Render "Score: 100" at the top of the screen
|
| 119 |
+
Add a health bar with value 75
|
| 120 |
+
Add a red brick_2x4 at 0,0,0
|
| 121 |
+
Apply toon shading to the cube
|
| 122 |
Set lighting to night
|
|
|
|
| 123 |
```
|
| 124 |
|
| 125 |
---
|
|
|
|
| 128 |
|
| 129 |
### For AI Assistants (MCP)
|
| 130 |
|
| 131 |
+
The MCP server exposes 40+ tools that AI assistants can call:
|
| 132 |
+
|
| 133 |
+
**Scene Building (6 tools):**
|
| 134 |
+
- `create_scene` - Create a new 3D scene/level
|
| 135 |
+
- `add_object` - Add primitive objects (cube, sphere, etc.)
|
| 136 |
+
- `add_brick` - Add LEGO-style bricks from Kenney kit
|
| 137 |
+
- `remove_object` - Remove objects from scene
|
| 138 |
+
- `set_lighting` - Change lighting preset
|
| 139 |
+
- `get_scene_info` - Get scene details
|
| 140 |
+
|
| 141 |
+
**Environment (4 tools):**
|
| 142 |
+
- `add_skybox` - Add procedural sky (day, sunset, night, etc.)
|
| 143 |
+
- `remove_skybox` - Remove skybox
|
| 144 |
+
- `add_particles` - Add particle effects (fire, smoke, rain, snow)
|
| 145 |
+
- `remove_particles` - Remove particle systems
|
| 146 |
+
|
| 147 |
+
**UI Overlay (4 tools):**
|
| 148 |
+
- `render_text_on_screen` - Display 2D text
|
| 149 |
+
- `render_bar_on_screen` - Display health/progress bars
|
| 150 |
+
- `remove_ui_element` - Remove UI elements
|
| 151 |
+
- `get_ui_elements` - List all UI elements
|
| 152 |
+
|
| 153 |
+
**Rendering (10 tools):**
|
| 154 |
+
- `add_light` - Add light sources
|
| 155 |
+
- `remove_light` - Remove lights
|
| 156 |
+
- `update_light` - Modify light properties
|
| 157 |
+
- `get_lights` - List all lights
|
| 158 |
+
- `update_object_material` - Change material properties
|
| 159 |
+
- `update_material_to_toon` - Apply toon/cel shading
|
| 160 |
+
- `set_background_color` - Set solid or gradient background
|
| 161 |
+
- `set_fog` - Add atmospheric fog
|
| 162 |
+
|
| 163 |
+
**Player Controller (10 tools):**
|
| 164 |
- `set_player_speed` - Movement speed
|
| 165 |
- `set_jump_force` - Jump height
|
| 166 |
+
- `set_mouse_sensitivity` - Mouse look + Y-invert
|
| 167 |
- `set_gravity` - World gravity
|
| 168 |
- `set_player_dimensions` - Player size
|
|
|
|
|
|
|
| 169 |
- `set_movement_acceleration` - Movement feel
|
| 170 |
- `set_air_control` - Airborne control
|
| 171 |
- `set_camera_fov` - Field of view
|
| 172 |
- `set_vertical_look_limits` - Look angle limits
|
| 173 |
+
- `get_player_config` - Get all settings
|
| 174 |
+
|
| 175 |
+
**Post-Processing (8 tools):**
|
| 176 |
+
- `set_bloom` - Glow effect
|
| 177 |
+
- `set_ssao` - Ambient occlusion
|
| 178 |
+
- `set_color_grading` - Color adjustments
|
| 179 |
+
- `set_vignette` - Vignette effect
|
| 180 |
+
- `set_depth_of_field` - Focus blur
|
| 181 |
+
- `set_motion_blur` - Movement blur
|
| 182 |
+
- `set_chromatic_aberration` - Lens effect
|
| 183 |
+
- `get_post_processing` - Get all effects
|
| 184 |
|
| 185 |
### For Developers (HTTP API)
|
| 186 |
|
|
|
|
| 293 |
### ✅ Completed
|
| 294 |
- **Phase 1**: Player Controller - Core Controls (5 tools)
|
| 295 |
- **Phase 2**: Player Controller - Enhanced Feel (4 tools)
|
| 296 |
+
- **Phase 3**: Rendering & Lighting Tools (10 tools)
|
| 297 |
+
- **Phase 4**: Post-Processing Effects (8 tools)
|
| 298 |
+
- **Phase 5**: Environment Tools - Skybox & Particles (4 tools)
|
| 299 |
+
- **Phase 6**: UI Overlay System (4 tools)
|
| 300 |
+
- **Phase 7**: LEGO Brick Kit Integration (Kenney assets)
|
| 301 |
+
- **Phase 8**: Toon/Cel Shading Materials
|
| 302 |
- Physics engine integration (Cannon.js)
|
| 303 |
- FPS controls (WASD + mouse look)
|
| 304 |
|
| 305 |
+
### 🔮 Next Steps
|
| 306 |
+
- Transform controls for object manipulation
|
| 307 |
+
- Scene templates and prefabs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
- Export to Unity/Unreal
|
| 309 |
+
- Persistent scene storage
|
| 310 |
|
| 311 |
### 💭 Future Ideas
|
| 312 |
- NPC system with behaviors
|
| 313 |
- Multiplayer support
|
| 314 |
- Procedural generation
|
| 315 |
+
- Audio system
|
| 316 |
|
| 317 |
---
|
| 318 |
|
app.py
CHANGED
|
@@ -421,49 +421,70 @@ with gr.Blocks(title="GCP - Game Context Protocol") as demo:
|
|
| 421 |
# Tools panel - full width below chat and viewer
|
| 422 |
with gr.Row(elem_id="tools-panel"):
|
| 423 |
gr.Markdown("""
|
| 424 |
-
### GCP Available Tools
|
| 425 |
|
| 426 |
-
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(
|
| 427 |
|
| 428 |
<div>
|
| 429 |
|
| 430 |
**🎬 Scene Tools**
|
| 431 |
-
- `
|
| 432 |
-
- `
|
| 433 |
-
- `remove_object` - Remove an object
|
| 434 |
-
- `set_lighting` -
|
| 435 |
-
- `get_scene_info` - Get
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
|
| 437 |
</div>
|
| 438 |
|
| 439 |
<div>
|
| 440 |
|
| 441 |
**🎮 Player Tools**
|
| 442 |
-
- `set_player_speed` -
|
| 443 |
-
- `set_jump_force` -
|
| 444 |
-
- `set_mouse_sensitivity` -
|
| 445 |
-
- `set_gravity` -
|
| 446 |
-
- `
|
| 447 |
-
- `
|
| 448 |
-
- `get_player_config` - Get current player settings
|
| 449 |
|
| 450 |
</div>
|
| 451 |
|
| 452 |
<div>
|
| 453 |
|
| 454 |
-
**💡 Lighting
|
| 455 |
- `add_light` - Add light (ambient, directional, point, spot)
|
| 456 |
-
- `remove_light` - Remove
|
| 457 |
-
- `
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 458 |
|
| 459 |
</div>
|
| 460 |
|
| 461 |
<div>
|
| 462 |
|
| 463 |
-
**
|
| 464 |
-
- `
|
| 465 |
-
- `
|
| 466 |
-
- `
|
|
|
|
| 467 |
|
| 468 |
</div>
|
| 469 |
|
|
@@ -517,7 +538,15 @@ with gr.Blocks(title="GCP - Game Context Protocol") as demo:
|
|
| 517 |
"setBackground", "setFog",
|
| 518 |
# Player tools
|
| 519 |
"setPlayerSpeed", "setJumpForce", "setGravity",
|
| 520 |
-
"setCameraFov", "setMouseSensitivity", "setPlayerDimensions"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 521 |
# Build action JSON for the JavaScript watcher
|
| 522 |
import json
|
| 523 |
import time
|
|
@@ -566,6 +595,34 @@ with gr.Blocks(title="GCP - Game Context Protocol") as demo:
|
|
| 566 |
elif action_type == "setPlayerDimensions":
|
| 567 |
height = action_result["data"].get("height", 1.7)
|
| 568 |
toast_message = f"Player height: {height}m"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 569 |
|
| 570 |
# Create JSON payload for the .then() JavaScript handler
|
| 571 |
# Include timestamp to ensure Gradio detects change even for repeated actions
|
|
|
|
| 421 |
# Tools panel - full width below chat and viewer
|
| 422 |
with gr.Row(elem_id="tools-panel"):
|
| 423 |
gr.Markdown("""
|
| 424 |
+
### GCP Available Tools (40+)
|
| 425 |
|
| 426 |
+
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 15px; margin-top: 10px; font-size: 0.9em;">
|
| 427 |
|
| 428 |
<div>
|
| 429 |
|
| 430 |
**🎬 Scene Tools**
|
| 431 |
+
- `add_object` - Add primitives (cube, sphere, cylinder, cone, torus, plane)
|
| 432 |
+
- `add_brick` - Add LEGO bricks (1x1, 2x4, slopes, plates)
|
| 433 |
+
- `remove_object` - Remove an object
|
| 434 |
+
- `set_lighting` - Presets: day, night, sunset, studio
|
| 435 |
+
- `get_scene_info` - Get scene details
|
| 436 |
+
|
| 437 |
+
</div>
|
| 438 |
+
|
| 439 |
+
<div>
|
| 440 |
+
|
| 441 |
+
**🌅 Environment**
|
| 442 |
+
- `add_skybox` - Add sky (day, sunset, night, dawn)
|
| 443 |
+
- `remove_skybox` - Remove skybox
|
| 444 |
+
- `add_particles` - Effects: fire, smoke, rain, snow, sparkle
|
| 445 |
+
- `remove_particles` - Remove particles
|
| 446 |
|
| 447 |
</div>
|
| 448 |
|
| 449 |
<div>
|
| 450 |
|
| 451 |
**🎮 Player Tools**
|
| 452 |
+
- `set_player_speed` - Movement speed
|
| 453 |
+
- `set_jump_force` - Jump strength
|
| 454 |
+
- `set_mouse_sensitivity` - Look sensitivity
|
| 455 |
+
- `set_gravity` - World gravity
|
| 456 |
+
- `set_camera_fov` - Field of view
|
| 457 |
+
- `get_player_config` - Get settings
|
|
|
|
| 458 |
|
| 459 |
</div>
|
| 460 |
|
| 461 |
<div>
|
| 462 |
|
| 463 |
+
**💡 Lighting**
|
| 464 |
- `add_light` - Add light (ambient, directional, point, spot)
|
| 465 |
+
- `remove_light` - Remove light
|
| 466 |
+
- `update_light` - Modify light properties
|
| 467 |
+
- `get_lights` - List all lights
|
| 468 |
+
|
| 469 |
+
</div>
|
| 470 |
+
|
| 471 |
+
<div>
|
| 472 |
+
|
| 473 |
+
**🎨 Materials**
|
| 474 |
+
- `update_object_material` - Color, metalness, roughness, opacity
|
| 475 |
+
- `update_material_to_toon` - Apply cel-shading
|
| 476 |
+
- `set_background_color` - Solid or gradient
|
| 477 |
+
- `set_fog` - Atmospheric fog
|
| 478 |
|
| 479 |
</div>
|
| 480 |
|
| 481 |
<div>
|
| 482 |
|
| 483 |
+
**📊 UI Overlay**
|
| 484 |
+
- `render_text_on_screen` - 2D text at any position
|
| 485 |
+
- `render_bar_on_screen` - Health/progress bars
|
| 486 |
+
- `remove_ui_element` - Remove UI element
|
| 487 |
+
- `get_ui_elements` - List UI elements
|
| 488 |
|
| 489 |
</div>
|
| 490 |
|
|
|
|
| 538 |
"setBackground", "setFog",
|
| 539 |
# Player tools
|
| 540 |
"setPlayerSpeed", "setJumpForce", "setGravity",
|
| 541 |
+
"setCameraFov", "setMouseSensitivity", "setPlayerDimensions",
|
| 542 |
+
# Environment tools
|
| 543 |
+
"addSkybox", "removeSkybox", "addParticles", "removeParticles",
|
| 544 |
+
# UI tools
|
| 545 |
+
"renderText", "renderBar", "removeUIElement",
|
| 546 |
+
# Toon shading
|
| 547 |
+
"updateToonMaterial",
|
| 548 |
+
# Brick blocks
|
| 549 |
+
"addBrick"]:
|
| 550 |
# Build action JSON for the JavaScript watcher
|
| 551 |
import json
|
| 552 |
import time
|
|
|
|
| 595 |
elif action_type == "setPlayerDimensions":
|
| 596 |
height = action_result["data"].get("height", 1.7)
|
| 597 |
toast_message = f"Player height: {height}m"
|
| 598 |
+
# Environment tool toast messages
|
| 599 |
+
elif action_type == "addSkybox":
|
| 600 |
+
preset = action_result["data"].get("preset", "custom")
|
| 601 |
+
toast_message = f"Skybox added: {preset}"
|
| 602 |
+
elif action_type == "removeSkybox":
|
| 603 |
+
toast_message = "Skybox removed"
|
| 604 |
+
elif action_type == "addParticles":
|
| 605 |
+
preset = action_result["data"].get("preset", "effect")
|
| 606 |
+
toast_message = f"Particles added: {preset}"
|
| 607 |
+
elif action_type == "removeParticles":
|
| 608 |
+
toast_message = "Particles removed"
|
| 609 |
+
# UI tool toast messages
|
| 610 |
+
elif action_type == "renderText":
|
| 611 |
+
text = action_result["data"].get("text", "")[:20]
|
| 612 |
+
toast_message = f"Text rendered: {text}..."
|
| 613 |
+
elif action_type == "renderBar":
|
| 614 |
+
label = action_result["data"].get("label", "Bar")
|
| 615 |
+
toast_message = f"Bar rendered: {label}"
|
| 616 |
+
elif action_type == "removeUIElement":
|
| 617 |
+
toast_message = "UI element removed"
|
| 618 |
+
# Toon shading toast
|
| 619 |
+
elif action_type == "updateToonMaterial":
|
| 620 |
+
enabled = action_result["data"].get("enabled", True)
|
| 621 |
+
toast_message = "Toon shading " + ("enabled" if enabled else "disabled")
|
| 622 |
+
# Brick toast
|
| 623 |
+
elif action_type == "addBrick":
|
| 624 |
+
brick_type = action_result["data"].get("brick_type", "brick")
|
| 625 |
+
toast_message = f"Added {brick_type.replace('_', ' ')}"
|
| 626 |
|
| 627 |
# Create JSON payload for the .then() JavaScript handler
|
| 628 |
# Include timestamp to ensure Gradio detects change even for repeated actions
|
backend/game_models.py
CHANGED
|
@@ -127,7 +127,7 @@ def create_player(
|
|
| 127 |
|
| 128 |
|
| 129 |
def create_player_config(
|
| 130 |
-
move_speed: float =
|
| 131 |
jump_force: float = 5.0,
|
| 132 |
mouse_sensitivity: float = 0.002,
|
| 133 |
invert_y: bool = False,
|
|
@@ -136,7 +136,7 @@ def create_player_config(
|
|
| 136 |
player_radius: float = 0.3,
|
| 137 |
eye_height: float = 1.6,
|
| 138 |
player_mass: float = 80.0,
|
| 139 |
-
linear_damping: float = 0.
|
| 140 |
movement_acceleration: float = 0.0,
|
| 141 |
air_control: float = 1.0,
|
| 142 |
camera_fov: float = 75.0,
|
|
|
|
| 127 |
|
| 128 |
|
| 129 |
def create_player_config(
|
| 130 |
+
move_speed: float = 8.0,
|
| 131 |
jump_force: float = 5.0,
|
| 132 |
mouse_sensitivity: float = 0.002,
|
| 133 |
invert_y: bool = False,
|
|
|
|
| 136 |
player_radius: float = 0.3,
|
| 137 |
eye_height: float = 1.6,
|
| 138 |
player_mass: float = 80.0,
|
| 139 |
+
linear_damping: float = 0.0,
|
| 140 |
movement_acceleration: float = 0.0,
|
| 141 |
air_control: float = 1.0,
|
| 142 |
camera_fov: float = 75.0,
|
backend/main.py
CHANGED
|
@@ -4,12 +4,17 @@ FastAPI server for the 3D scene viewer HTTP endpoints.
|
|
| 4 |
MCP tools are defined in mcp_server.py
|
| 5 |
"""
|
| 6 |
import os
|
|
|
|
| 7 |
from fastapi import FastAPI, HTTPException
|
| 8 |
from fastapi.responses import HTMLResponse, JSONResponse
|
| 9 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
| 10 |
|
| 11 |
from backend.storage import storage
|
| 12 |
|
|
|
|
|
|
|
|
|
|
| 13 |
# Create FastAPI app
|
| 14 |
app = FastAPI(
|
| 15 |
title="GCP - Game Context Protocol",
|
|
@@ -26,6 +31,11 @@ app.add_middleware(
|
|
| 26 |
allow_headers=["*"],
|
| 27 |
)
|
| 28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
# =============================================================================
|
| 31 |
# HTTP Endpoints (for viewer and health checks)
|
|
|
|
| 4 |
MCP tools are defined in mcp_server.py
|
| 5 |
"""
|
| 6 |
import os
|
| 7 |
+
from pathlib import Path
|
| 8 |
from fastapi import FastAPI, HTTPException
|
| 9 |
from fastapi.responses import HTMLResponse, JSONResponse
|
| 10 |
from fastapi.middleware.cors import CORSMiddleware
|
| 11 |
+
from fastapi.staticfiles import StaticFiles
|
| 12 |
|
| 13 |
from backend.storage import storage
|
| 14 |
|
| 15 |
+
# Get project root directory
|
| 16 |
+
PROJECT_ROOT = Path(__file__).parent.parent
|
| 17 |
+
|
| 18 |
# Create FastAPI app
|
| 19 |
app = FastAPI(
|
| 20 |
title="GCP - Game Context Protocol",
|
|
|
|
| 31 |
allow_headers=["*"],
|
| 32 |
)
|
| 33 |
|
| 34 |
+
# Mount static files for models
|
| 35 |
+
models_path = PROJECT_ROOT / "models"
|
| 36 |
+
if models_path.exists():
|
| 37 |
+
app.mount("/static/models", StaticFiles(directory=str(models_path)), name="models")
|
| 38 |
+
|
| 39 |
|
| 40 |
# =============================================================================
|
| 41 |
# HTTP Endpoints (for viewer and health checks)
|
backend/mcp_server.py
CHANGED
|
@@ -16,6 +16,8 @@ from backend.tools.scene_tools import (
|
|
| 16 |
remove_game_object,
|
| 17 |
set_scene_lighting,
|
| 18 |
get_scene_info,
|
|
|
|
|
|
|
| 19 |
)
|
| 20 |
from backend.tools.player_tools import (
|
| 21 |
set_player_speed,
|
|
@@ -48,6 +50,20 @@ from backend.tools.rendering_tools import (
|
|
| 48 |
set_motion_blur,
|
| 49 |
set_chromatic_aberration,
|
| 50 |
get_camera_effects,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
)
|
| 52 |
from backend.game_models import create_vector3, create_material
|
| 53 |
|
|
@@ -186,6 +202,53 @@ Returns: scene details including name, objects, lights, and viewer_url""",
|
|
| 186 |
"required": ["scene_id"],
|
| 187 |
},
|
| 188 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
]
|
| 190 |
|
| 191 |
PLAYER_TOOLS = [
|
|
@@ -817,8 +880,249 @@ Returns: All camera effects settings (depth of field, motion blur, chromatic abe
|
|
| 817 |
),
|
| 818 |
]
|
| 819 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 820 |
# Combine all tools
|
| 821 |
-
ALL_TOOLS = SCENE_TOOLS + PLAYER_TOOLS + RENDERING_TOOLS + POST_PROCESSING_TOOLS + CAMERA_EFFECTS_TOOLS
|
| 822 |
|
| 823 |
|
| 824 |
# =============================================================================
|
|
@@ -897,6 +1201,17 @@ async def _execute_tool(name: str, args: dict) -> Any:
|
|
| 897 |
elif name == "get_scene_info":
|
| 898 |
return get_scene_info(args["scene_id"], BASE_URL)
|
| 899 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 900 |
# Player tools
|
| 901 |
elif name == "set_player_speed":
|
| 902 |
return set_player_speed(
|
|
@@ -1094,6 +1409,86 @@ async def _execute_tool(name: str, args: dict) -> Any:
|
|
| 1094 |
elif name == "get_camera_effects":
|
| 1095 |
return get_camera_effects(args["scene_id"])
|
| 1096 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1097 |
else:
|
| 1098 |
raise ValueError(f"Unknown tool: {name}")
|
| 1099 |
|
|
|
|
| 16 |
remove_game_object,
|
| 17 |
set_scene_lighting,
|
| 18 |
get_scene_info,
|
| 19 |
+
add_brick,
|
| 20 |
+
BRICK_TYPES,
|
| 21 |
)
|
| 22 |
from backend.tools.player_tools import (
|
| 23 |
set_player_speed,
|
|
|
|
| 50 |
set_motion_blur,
|
| 51 |
set_chromatic_aberration,
|
| 52 |
get_camera_effects,
|
| 53 |
+
# Toon shading
|
| 54 |
+
update_material_to_toon,
|
| 55 |
+
)
|
| 56 |
+
from backend.tools.environment_tools import (
|
| 57 |
+
add_skybox,
|
| 58 |
+
remove_skybox,
|
| 59 |
+
add_particles,
|
| 60 |
+
remove_particles,
|
| 61 |
+
)
|
| 62 |
+
from backend.tools.ui_tools import (
|
| 63 |
+
render_text_on_screen,
|
| 64 |
+
render_bar_on_screen,
|
| 65 |
+
remove_ui_element,
|
| 66 |
+
get_ui_elements,
|
| 67 |
)
|
| 68 |
from backend.game_models import create_vector3, create_material
|
| 69 |
|
|
|
|
| 202 |
"required": ["scene_id"],
|
| 203 |
},
|
| 204 |
),
|
| 205 |
+
Tool(
|
| 206 |
+
name="add_brick",
|
| 207 |
+
description="""Add a LEGO-style brick from the Kenney brick kit.
|
| 208 |
+
|
| 209 |
+
Args:
|
| 210 |
+
scene_id: ID of the scene (required)
|
| 211 |
+
brick_type: Type of brick (required) - brick_1x1, brick_1x2, brick_1x4, brick_2x2, brick_2x4,
|
| 212 |
+
plate_1x2, plate_2x2, plate_2x4, plate_4x4, slope_2x2
|
| 213 |
+
position: {x, y, z} position
|
| 214 |
+
rotation: {x, y, z} rotation in degrees
|
| 215 |
+
color: Hex color code (default: #ff0000 red)
|
| 216 |
+
name: Optional name for the brick
|
| 217 |
+
|
| 218 |
+
Examples:
|
| 219 |
+
add_brick(scene_id, "brick_2x4", position={x:0, y:0, z:0}, color="#ff0000")
|
| 220 |
+
add_brick(scene_id, "slope_2x2", position={x:1, y:0.5, z:0}, color="#0000ff")""",
|
| 221 |
+
inputSchema={
|
| 222 |
+
"type": "object",
|
| 223 |
+
"properties": {
|
| 224 |
+
"scene_id": {"type": "string"},
|
| 225 |
+
"brick_type": {
|
| 226 |
+
"type": "string",
|
| 227 |
+
"enum": ["brick_1x1", "brick_1x2", "brick_1x4", "brick_2x2", "brick_2x4",
|
| 228 |
+
"plate_1x2", "plate_2x2", "plate_2x4", "plate_4x4", "slope_2x2"]
|
| 229 |
+
},
|
| 230 |
+
"position": {
|
| 231 |
+
"type": "object",
|
| 232 |
+
"properties": {
|
| 233 |
+
"x": {"type": "number"},
|
| 234 |
+
"y": {"type": "number"},
|
| 235 |
+
"z": {"type": "number"},
|
| 236 |
+
}
|
| 237 |
+
},
|
| 238 |
+
"rotation": {
|
| 239 |
+
"type": "object",
|
| 240 |
+
"properties": {
|
| 241 |
+
"x": {"type": "number"},
|
| 242 |
+
"y": {"type": "number"},
|
| 243 |
+
"z": {"type": "number"},
|
| 244 |
+
}
|
| 245 |
+
},
|
| 246 |
+
"color": {"type": "string", "default": "#ff0000"},
|
| 247 |
+
"name": {"type": "string"},
|
| 248 |
+
},
|
| 249 |
+
"required": ["scene_id", "brick_type"],
|
| 250 |
+
},
|
| 251 |
+
),
|
| 252 |
]
|
| 253 |
|
| 254 |
PLAYER_TOOLS = [
|
|
|
|
| 880 |
),
|
| 881 |
]
|
| 882 |
|
| 883 |
+
ENVIRONMENT_TOOLS = [
|
| 884 |
+
Tool(
|
| 885 |
+
name="add_skybox",
|
| 886 |
+
description="""Add a procedural sky to the scene.
|
| 887 |
+
|
| 888 |
+
Creates realistic outdoor environments with atmospheric scattering.
|
| 889 |
+
|
| 890 |
+
Args:
|
| 891 |
+
scene_id: Scene to modify (required)
|
| 892 |
+
preset: Quick preset - "day", "sunset", "noon", "dawn", "night" (default: "day")
|
| 893 |
+
turbidity: Haziness 2.0=clear, 10.0=hazy, 20.0=foggy (default: 10.0)
|
| 894 |
+
rayleigh: Blue sky intensity 0.0-4.0 (default: 2.0)
|
| 895 |
+
sun_elevation: Sun angle from horizon -90 to 90 degrees (default: 45)
|
| 896 |
+
sun_azimuth: Sun compass direction 0-360 degrees (default: 180)
|
| 897 |
+
|
| 898 |
+
Examples:
|
| 899 |
+
add_skybox(scene_id, preset="sunset")
|
| 900 |
+
add_skybox(scene_id, sun_elevation=5, turbidity=4)""",
|
| 901 |
+
inputSchema={
|
| 902 |
+
"type": "object",
|
| 903 |
+
"properties": {
|
| 904 |
+
"scene_id": {"type": "string"},
|
| 905 |
+
"preset": {
|
| 906 |
+
"type": "string",
|
| 907 |
+
"enum": ["day", "sunset", "noon", "dawn", "night"],
|
| 908 |
+
"default": "day"
|
| 909 |
+
},
|
| 910 |
+
"turbidity": {"type": "number", "default": 10.0},
|
| 911 |
+
"rayleigh": {"type": "number", "default": 2.0},
|
| 912 |
+
"sun_elevation": {"type": "number", "default": 45.0},
|
| 913 |
+
"sun_azimuth": {"type": "number", "default": 180.0},
|
| 914 |
+
},
|
| 915 |
+
"required": ["scene_id"],
|
| 916 |
+
},
|
| 917 |
+
),
|
| 918 |
+
Tool(
|
| 919 |
+
name="remove_skybox",
|
| 920 |
+
description="""Remove the skybox from the scene.
|
| 921 |
+
|
| 922 |
+
Reverts to solid background color.
|
| 923 |
+
|
| 924 |
+
Args:
|
| 925 |
+
scene_id: Scene to modify (required)""",
|
| 926 |
+
inputSchema={
|
| 927 |
+
"type": "object",
|
| 928 |
+
"properties": {
|
| 929 |
+
"scene_id": {"type": "string"},
|
| 930 |
+
},
|
| 931 |
+
"required": ["scene_id"],
|
| 932 |
+
},
|
| 933 |
+
),
|
| 934 |
+
Tool(
|
| 935 |
+
name="add_particles",
|
| 936 |
+
description="""Add a particle effect to the scene.
|
| 937 |
+
|
| 938 |
+
Use presets for quick setup of common effects.
|
| 939 |
+
|
| 940 |
+
Args:
|
| 941 |
+
scene_id: Scene to modify (required)
|
| 942 |
+
preset: Effect type - "fire", "smoke", "sparkle", "rain", "snow" (required)
|
| 943 |
+
position: {x, y, z} for localized effects (fire, smoke, sparkle)
|
| 944 |
+
particle_id: Optional unique identifier
|
| 945 |
+
|
| 946 |
+
Examples:
|
| 947 |
+
add_particles(scene_id, "fire", position={x:0, y:1, z:0})
|
| 948 |
+
add_particles(scene_id, "rain") # Weather covers entire world""",
|
| 949 |
+
inputSchema={
|
| 950 |
+
"type": "object",
|
| 951 |
+
"properties": {
|
| 952 |
+
"scene_id": {"type": "string"},
|
| 953 |
+
"preset": {
|
| 954 |
+
"type": "string",
|
| 955 |
+
"enum": ["fire", "smoke", "sparkle", "rain", "snow"]
|
| 956 |
+
},
|
| 957 |
+
"position": {
|
| 958 |
+
"type": "object",
|
| 959 |
+
"properties": {
|
| 960 |
+
"x": {"type": "number"},
|
| 961 |
+
"y": {"type": "number"},
|
| 962 |
+
"z": {"type": "number"},
|
| 963 |
+
}
|
| 964 |
+
},
|
| 965 |
+
"particle_id": {"type": "string"},
|
| 966 |
+
},
|
| 967 |
+
"required": ["scene_id", "preset"],
|
| 968 |
+
},
|
| 969 |
+
),
|
| 970 |
+
Tool(
|
| 971 |
+
name="remove_particles",
|
| 972 |
+
description="""Remove a particle system from the scene.
|
| 973 |
+
|
| 974 |
+
Args:
|
| 975 |
+
scene_id: Scene to modify (required)
|
| 976 |
+
particle_id: ID of the particle system to remove (required)""",
|
| 977 |
+
inputSchema={
|
| 978 |
+
"type": "object",
|
| 979 |
+
"properties": {
|
| 980 |
+
"scene_id": {"type": "string"},
|
| 981 |
+
"particle_id": {"type": "string"},
|
| 982 |
+
},
|
| 983 |
+
"required": ["scene_id", "particle_id"],
|
| 984 |
+
},
|
| 985 |
+
),
|
| 986 |
+
]
|
| 987 |
+
|
| 988 |
+
UI_TOOLS = [
|
| 989 |
+
Tool(
|
| 990 |
+
name="render_text_on_screen",
|
| 991 |
+
description="""Render text on the screen as a 2D overlay.
|
| 992 |
+
|
| 993 |
+
Args:
|
| 994 |
+
scene_id: Scene to modify (required)
|
| 995 |
+
text: The text to display (required)
|
| 996 |
+
x: Horizontal position in % (0=left, 50=center, 100=right, default: 50)
|
| 997 |
+
y: Vertical position in % (0=top, 50=center, 100=bottom, default: 10)
|
| 998 |
+
font_size: Font size in pixels (default: 24)
|
| 999 |
+
color: Text color hex (default: "#ffffff")
|
| 1000 |
+
text_id: Optional unique ID for updates/removal
|
| 1001 |
+
background_color: Optional background color for text box
|
| 1002 |
+
|
| 1003 |
+
Examples:
|
| 1004 |
+
render_text_on_screen(scene_id, "Score: 100", x=10, y=5)
|
| 1005 |
+
render_text_on_screen(scene_id, "Game Over", x=50, y=50, font_size=48)""",
|
| 1006 |
+
inputSchema={
|
| 1007 |
+
"type": "object",
|
| 1008 |
+
"properties": {
|
| 1009 |
+
"scene_id": {"type": "string"},
|
| 1010 |
+
"text": {"type": "string"},
|
| 1011 |
+
"x": {"type": "number", "default": 50.0},
|
| 1012 |
+
"y": {"type": "number", "default": 10.0},
|
| 1013 |
+
"font_size": {"type": "integer", "default": 24},
|
| 1014 |
+
"color": {"type": "string", "default": "#ffffff"},
|
| 1015 |
+
"text_id": {"type": "string"},
|
| 1016 |
+
"font_family": {"type": "string", "default": "Arial"},
|
| 1017 |
+
"text_align": {
|
| 1018 |
+
"type": "string",
|
| 1019 |
+
"enum": ["left", "center", "right"],
|
| 1020 |
+
"default": "center"
|
| 1021 |
+
},
|
| 1022 |
+
"background_color": {"type": "string"},
|
| 1023 |
+
"padding": {"type": "integer", "default": 8},
|
| 1024 |
+
},
|
| 1025 |
+
"required": ["scene_id", "text"],
|
| 1026 |
+
},
|
| 1027 |
+
),
|
| 1028 |
+
Tool(
|
| 1029 |
+
name="render_bar_on_screen",
|
| 1030 |
+
description="""Render a progress/health bar on the screen.
|
| 1031 |
+
|
| 1032 |
+
Args:
|
| 1033 |
+
scene_id: Scene to modify (required)
|
| 1034 |
+
x: Horizontal position in % (default: 10)
|
| 1035 |
+
y: Vertical position in % (default: 10)
|
| 1036 |
+
width: Bar width in pixels (default: 200)
|
| 1037 |
+
height: Bar height in pixels (default: 20)
|
| 1038 |
+
value: Current value (default: 100)
|
| 1039 |
+
max_value: Maximum value (default: 100)
|
| 1040 |
+
bar_color: Fill color (default: "#00ff00" green)
|
| 1041 |
+
background_color: Background color (default: "#333333")
|
| 1042 |
+
bar_id: Optional unique ID for updates/removal
|
| 1043 |
+
label: Optional label above the bar
|
| 1044 |
+
show_value: Show numeric value on bar (default: false)
|
| 1045 |
+
|
| 1046 |
+
Examples:
|
| 1047 |
+
render_bar_on_screen(scene_id, value=75, bar_color="#ff0000", label="Health")
|
| 1048 |
+
render_bar_on_screen(scene_id, x=10, y=90, value=50, bar_color="#0088ff", label="Mana")""",
|
| 1049 |
+
inputSchema={
|
| 1050 |
+
"type": "object",
|
| 1051 |
+
"properties": {
|
| 1052 |
+
"scene_id": {"type": "string"},
|
| 1053 |
+
"x": {"type": "number", "default": 10.0},
|
| 1054 |
+
"y": {"type": "number", "default": 10.0},
|
| 1055 |
+
"width": {"type": "number", "default": 200.0},
|
| 1056 |
+
"height": {"type": "number", "default": 20.0},
|
| 1057 |
+
"value": {"type": "number", "default": 100.0},
|
| 1058 |
+
"max_value": {"type": "number", "default": 100.0},
|
| 1059 |
+
"bar_color": {"type": "string", "default": "#00ff00"},
|
| 1060 |
+
"background_color": {"type": "string", "default": "#333333"},
|
| 1061 |
+
"border_color": {"type": "string", "default": "#ffffff"},
|
| 1062 |
+
"bar_id": {"type": "string"},
|
| 1063 |
+
"label": {"type": "string"},
|
| 1064 |
+
"show_value": {"type": "boolean", "default": False},
|
| 1065 |
+
},
|
| 1066 |
+
"required": ["scene_id"],
|
| 1067 |
+
},
|
| 1068 |
+
),
|
| 1069 |
+
Tool(
|
| 1070 |
+
name="remove_ui_element",
|
| 1071 |
+
description="""Remove a UI element from the screen.
|
| 1072 |
+
|
| 1073 |
+
Args:
|
| 1074 |
+
scene_id: Scene to modify (required)
|
| 1075 |
+
element_id: ID of the text or bar to remove (required)""",
|
| 1076 |
+
inputSchema={
|
| 1077 |
+
"type": "object",
|
| 1078 |
+
"properties": {
|
| 1079 |
+
"scene_id": {"type": "string"},
|
| 1080 |
+
"element_id": {"type": "string"},
|
| 1081 |
+
},
|
| 1082 |
+
"required": ["scene_id", "element_id"],
|
| 1083 |
+
},
|
| 1084 |
+
),
|
| 1085 |
+
]
|
| 1086 |
+
|
| 1087 |
+
TOON_TOOLS = [
|
| 1088 |
+
Tool(
|
| 1089 |
+
name="update_material_to_toon",
|
| 1090 |
+
description="""Apply toon/cel-shading to an object for cartoon-like appearance.
|
| 1091 |
+
|
| 1092 |
+
Creates discrete shading bands instead of smooth gradients, common in anime styles.
|
| 1093 |
+
|
| 1094 |
+
Args:
|
| 1095 |
+
scene_id: Scene to modify (required)
|
| 1096 |
+
object_id: Object to update (required)
|
| 1097 |
+
enabled: Enable/disable toon shading (default: true)
|
| 1098 |
+
color: Base color (optional, keeps existing)
|
| 1099 |
+
gradient_steps: Shading steps 2=hard, 3=medium, 5=soft (default: 3)
|
| 1100 |
+
outline: Add black outline effect (default: true)
|
| 1101 |
+
outline_color: Outline color (default: "#000000")
|
| 1102 |
+
outline_thickness: Outline thickness 0.01-0.1 (default: 0.03)
|
| 1103 |
+
|
| 1104 |
+
Examples:
|
| 1105 |
+
update_material_to_toon(scene_id, object_id, gradient_steps=2)
|
| 1106 |
+
update_material_to_toon(scene_id, object_id, enabled=False) # Revert""",
|
| 1107 |
+
inputSchema={
|
| 1108 |
+
"type": "object",
|
| 1109 |
+
"properties": {
|
| 1110 |
+
"scene_id": {"type": "string"},
|
| 1111 |
+
"object_id": {"type": "string"},
|
| 1112 |
+
"enabled": {"type": "boolean", "default": True},
|
| 1113 |
+
"color": {"type": "string"},
|
| 1114 |
+
"gradient_steps": {"type": "integer", "default": 3},
|
| 1115 |
+
"outline": {"type": "boolean", "default": True},
|
| 1116 |
+
"outline_color": {"type": "string", "default": "#000000"},
|
| 1117 |
+
"outline_thickness": {"type": "number", "default": 0.03},
|
| 1118 |
+
},
|
| 1119 |
+
"required": ["scene_id", "object_id"],
|
| 1120 |
+
},
|
| 1121 |
+
),
|
| 1122 |
+
]
|
| 1123 |
+
|
| 1124 |
# Combine all tools
|
| 1125 |
+
ALL_TOOLS = SCENE_TOOLS + PLAYER_TOOLS + RENDERING_TOOLS + POST_PROCESSING_TOOLS + CAMERA_EFFECTS_TOOLS + ENVIRONMENT_TOOLS + UI_TOOLS + TOON_TOOLS
|
| 1126 |
|
| 1127 |
|
| 1128 |
# =============================================================================
|
|
|
|
| 1201 |
elif name == "get_scene_info":
|
| 1202 |
return get_scene_info(args["scene_id"], BASE_URL)
|
| 1203 |
|
| 1204 |
+
elif name == "add_brick":
|
| 1205 |
+
return add_brick(
|
| 1206 |
+
args["scene_id"],
|
| 1207 |
+
args["brick_type"],
|
| 1208 |
+
args.get("position"),
|
| 1209 |
+
args.get("rotation"),
|
| 1210 |
+
args.get("color", "#ff0000"),
|
| 1211 |
+
args.get("name"),
|
| 1212 |
+
BASE_URL
|
| 1213 |
+
)
|
| 1214 |
+
|
| 1215 |
# Player tools
|
| 1216 |
elif name == "set_player_speed":
|
| 1217 |
return set_player_speed(
|
|
|
|
| 1409 |
elif name == "get_camera_effects":
|
| 1410 |
return get_camera_effects(args["scene_id"])
|
| 1411 |
|
| 1412 |
+
# Environment tools (skybox, particles)
|
| 1413 |
+
elif name == "add_skybox":
|
| 1414 |
+
return add_skybox(
|
| 1415 |
+
args["scene_id"],
|
| 1416 |
+
args.get("preset", "day"),
|
| 1417 |
+
args.get("turbidity", 10.0),
|
| 1418 |
+
args.get("rayleigh", 2.0),
|
| 1419 |
+
args.get("sun_elevation", 45.0),
|
| 1420 |
+
args.get("sun_azimuth", 180.0)
|
| 1421 |
+
)
|
| 1422 |
+
|
| 1423 |
+
elif name == "remove_skybox":
|
| 1424 |
+
return remove_skybox(args["scene_id"])
|
| 1425 |
+
|
| 1426 |
+
elif name == "add_particles":
|
| 1427 |
+
return add_particles(
|
| 1428 |
+
args["scene_id"],
|
| 1429 |
+
args["preset"],
|
| 1430 |
+
args.get("position"),
|
| 1431 |
+
args.get("particle_id")
|
| 1432 |
+
)
|
| 1433 |
+
|
| 1434 |
+
elif name == "remove_particles":
|
| 1435 |
+
return remove_particles(
|
| 1436 |
+
args["scene_id"],
|
| 1437 |
+
args["particle_id"]
|
| 1438 |
+
)
|
| 1439 |
+
|
| 1440 |
+
# UI tools
|
| 1441 |
+
elif name == "render_text_on_screen":
|
| 1442 |
+
return render_text_on_screen(
|
| 1443 |
+
args["scene_id"],
|
| 1444 |
+
args["text"],
|
| 1445 |
+
args.get("x", 50.0),
|
| 1446 |
+
args.get("y", 10.0),
|
| 1447 |
+
args.get("font_size", 24),
|
| 1448 |
+
args.get("color", "#ffffff"),
|
| 1449 |
+
args.get("text_id"),
|
| 1450 |
+
args.get("font_family", "Arial"),
|
| 1451 |
+
args.get("text_align", "center"),
|
| 1452 |
+
args.get("background_color"),
|
| 1453 |
+
args.get("padding", 8)
|
| 1454 |
+
)
|
| 1455 |
+
|
| 1456 |
+
elif name == "render_bar_on_screen":
|
| 1457 |
+
return render_bar_on_screen(
|
| 1458 |
+
args["scene_id"],
|
| 1459 |
+
args.get("x", 10.0),
|
| 1460 |
+
args.get("y", 10.0),
|
| 1461 |
+
args.get("width", 200.0),
|
| 1462 |
+
args.get("height", 20.0),
|
| 1463 |
+
args.get("value", 100.0),
|
| 1464 |
+
args.get("max_value", 100.0),
|
| 1465 |
+
args.get("bar_color", "#00ff00"),
|
| 1466 |
+
args.get("background_color", "#333333"),
|
| 1467 |
+
args.get("border_color", "#ffffff"),
|
| 1468 |
+
args.get("bar_id"),
|
| 1469 |
+
args.get("label"),
|
| 1470 |
+
args.get("show_value", False)
|
| 1471 |
+
)
|
| 1472 |
+
|
| 1473 |
+
elif name == "remove_ui_element":
|
| 1474 |
+
return remove_ui_element(
|
| 1475 |
+
args["scene_id"],
|
| 1476 |
+
args["element_id"]
|
| 1477 |
+
)
|
| 1478 |
+
|
| 1479 |
+
# Toon shading tools
|
| 1480 |
+
elif name == "update_material_to_toon":
|
| 1481 |
+
return update_material_to_toon(
|
| 1482 |
+
args["scene_id"],
|
| 1483 |
+
args["object_id"],
|
| 1484 |
+
args.get("enabled", True),
|
| 1485 |
+
args.get("color"),
|
| 1486 |
+
args.get("gradient_steps", 3),
|
| 1487 |
+
args.get("outline", True),
|
| 1488 |
+
args.get("outline_color", "#000000"),
|
| 1489 |
+
args.get("outline_thickness", 0.03)
|
| 1490 |
+
)
|
| 1491 |
+
|
| 1492 |
else:
|
| 1493 |
raise ValueError(f"Unknown tool: {name}")
|
| 1494 |
|
backend/storage.py
CHANGED
|
@@ -49,39 +49,89 @@ class Storage:
|
|
| 49 |
storage = Storage()
|
| 50 |
|
| 51 |
|
| 52 |
-
# Initialize with
|
| 53 |
def initialize_default_scene():
|
| 54 |
-
"""Create a
|
| 55 |
-
from backend.game_models import
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
-
# Create lights for
|
| 58 |
lights = [
|
| 59 |
create_light(
|
| 60 |
name="Sun",
|
| 61 |
light_type="directional",
|
| 62 |
-
color="#
|
| 63 |
-
intensity=1.
|
| 64 |
-
position=create_vector3(50,
|
| 65 |
),
|
| 66 |
create_light(
|
| 67 |
name="Ambient",
|
| 68 |
light_type="ambient",
|
| 69 |
-
color="#
|
| 70 |
-
intensity=0.
|
| 71 |
),
|
| 72 |
]
|
| 73 |
|
| 74 |
-
# Create environment
|
| 75 |
-
env = create_environment(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
-
# Create
|
| 78 |
-
# Ground plane and walls are created by physics system in viewer
|
| 79 |
scene = create_scene(
|
| 80 |
-
name="Welcome
|
| 81 |
-
description="
|
| 82 |
world_width=25,
|
| 83 |
world_height=10,
|
| 84 |
world_depth=25,
|
|
|
|
| 85 |
lights=lights,
|
| 86 |
environment=env,
|
| 87 |
)
|
|
@@ -89,12 +139,21 @@ def initialize_default_scene():
|
|
| 89 |
# Use a predictable scene ID for easy linking
|
| 90 |
scene["scene_id"] = "welcome"
|
| 91 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
# Save to storage
|
| 93 |
storage.save(scene)
|
| 94 |
-
print(f"✓ Initialized
|
| 95 |
-
print(f" - 25x25 world with
|
| 96 |
-
print(f" -
|
| 97 |
-
print(f" -
|
| 98 |
|
| 99 |
|
| 100 |
# Initialize on module load
|
|
|
|
| 49 |
storage = Storage()
|
| 50 |
|
| 51 |
|
| 52 |
+
# Initialize with an impressive default Welcome Scene for HuggingFace Spaces
|
| 53 |
def initialize_default_scene():
|
| 54 |
+
"""Create a visually impressive Welcome Scene on startup"""
|
| 55 |
+
from backend.game_models import (
|
| 56 |
+
create_scene, create_light, create_environment, create_vector3,
|
| 57 |
+
create_game_object, create_material
|
| 58 |
+
)
|
| 59 |
|
| 60 |
+
# Create lights for sunset preset (warm, dramatic lighting)
|
| 61 |
lights = [
|
| 62 |
create_light(
|
| 63 |
name="Sun",
|
| 64 |
light_type="directional",
|
| 65 |
+
color="#ff9944", # Warm sunset orange
|
| 66 |
+
intensity=1.2,
|
| 67 |
+
position=create_vector3(50, 30, 50),
|
| 68 |
),
|
| 69 |
create_light(
|
| 70 |
name="Ambient",
|
| 71 |
light_type="ambient",
|
| 72 |
+
color="#ffcc88", # Warm ambient
|
| 73 |
+
intensity=0.4,
|
| 74 |
),
|
| 75 |
]
|
| 76 |
|
| 77 |
+
# Create environment with sunset preset
|
| 78 |
+
env = create_environment(
|
| 79 |
+
lighting_preset="sunset",
|
| 80 |
+
background_color="#1a0a20", # Dark purple (will be overridden by skybox)
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
# Create some visually interesting starter objects
|
| 84 |
+
objects = [
|
| 85 |
+
# Red cube - classic demo object
|
| 86 |
+
create_game_object(
|
| 87 |
+
object_type="cube",
|
| 88 |
+
name="RedCube",
|
| 89 |
+
position=create_vector3(3, 1, -3),
|
| 90 |
+
scale=create_vector3(2, 2, 2),
|
| 91 |
+
material=create_material(color="#ff4444", metalness=0.3, roughness=0.4),
|
| 92 |
+
),
|
| 93 |
+
# Blue sphere - metallic
|
| 94 |
+
create_game_object(
|
| 95 |
+
object_type="sphere",
|
| 96 |
+
name="BlueSphere",
|
| 97 |
+
position=create_vector3(-4, 1.5, -2),
|
| 98 |
+
scale=create_vector3(3, 3, 3),
|
| 99 |
+
material=create_material(color="#4488ff", metalness=0.8, roughness=0.2),
|
| 100 |
+
),
|
| 101 |
+
# Green cylinder
|
| 102 |
+
create_game_object(
|
| 103 |
+
object_type="cylinder",
|
| 104 |
+
name="GreenCylinder",
|
| 105 |
+
position=create_vector3(0, 1.5, -6),
|
| 106 |
+
scale=create_vector3(1.5, 3, 1.5),
|
| 107 |
+
material=create_material(color="#44ff44", metalness=0.2, roughness=0.6),
|
| 108 |
+
),
|
| 109 |
+
# Yellow torus
|
| 110 |
+
create_game_object(
|
| 111 |
+
object_type="torus",
|
| 112 |
+
name="YellowTorus",
|
| 113 |
+
position=create_vector3(-3, 2, -7),
|
| 114 |
+
scale=create_vector3(2, 2, 2),
|
| 115 |
+
material=create_material(color="#ffcc00", metalness=0.6, roughness=0.3),
|
| 116 |
+
),
|
| 117 |
+
# Purple cone
|
| 118 |
+
create_game_object(
|
| 119 |
+
object_type="cone",
|
| 120 |
+
name="PurpleCone",
|
| 121 |
+
position=create_vector3(5, 1, -5),
|
| 122 |
+
scale=create_vector3(1.5, 3, 1.5),
|
| 123 |
+
material=create_material(color="#aa44ff", metalness=0.4, roughness=0.5),
|
| 124 |
+
),
|
| 125 |
+
]
|
| 126 |
|
| 127 |
+
# Create scene with starter objects
|
|
|
|
| 128 |
scene = create_scene(
|
| 129 |
+
name="Welcome to GCP",
|
| 130 |
+
description="AI-powered 3D scene builder - Try 'add a red sphere' or 'set lighting to night'",
|
| 131 |
world_width=25,
|
| 132 |
world_height=10,
|
| 133 |
world_depth=25,
|
| 134 |
+
objects=objects,
|
| 135 |
lights=lights,
|
| 136 |
environment=env,
|
| 137 |
)
|
|
|
|
| 139 |
# Use a predictable scene ID for easy linking
|
| 140 |
scene["scene_id"] = "welcome"
|
| 141 |
|
| 142 |
+
# Add skybox configuration (sunset preset)
|
| 143 |
+
scene["skybox"] = {
|
| 144 |
+
"preset": "sunset",
|
| 145 |
+
"turbidity": 8,
|
| 146 |
+
"rayleigh": 2,
|
| 147 |
+
"elevation": 5,
|
| 148 |
+
"azimuth": 180
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
# Save to storage
|
| 152 |
storage.save(scene)
|
| 153 |
+
print(f"✓ Initialized Welcome Scene (ID: welcome)")
|
| 154 |
+
print(f" - 25x25 world with sunset skybox")
|
| 155 |
+
print(f" - 5 starter objects (cube, sphere, cylinder, torus, cone)")
|
| 156 |
+
print(f" - FPS physics controller ready")
|
| 157 |
|
| 158 |
|
| 159 |
# Initialize on module load
|
backend/tools/rendering_tools.py
CHANGED
|
@@ -846,3 +846,84 @@ def get_camera_effects(scene_id: str) -> Dict[str, Any]:
|
|
| 846 |
"scene_id": scene_id,
|
| 847 |
"camera_effects": camera_effects
|
| 848 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 846 |
"scene_id": scene_id,
|
| 847 |
"camera_effects": camera_effects
|
| 848 |
}
|
| 849 |
+
|
| 850 |
+
|
| 851 |
+
# =============================================================================
|
| 852 |
+
# Toon/Cel Shading Tools
|
| 853 |
+
# =============================================================================
|
| 854 |
+
|
| 855 |
+
def update_material_to_toon(
|
| 856 |
+
scene_id: str,
|
| 857 |
+
object_id: str,
|
| 858 |
+
enabled: bool = True,
|
| 859 |
+
color: Optional[str] = None,
|
| 860 |
+
gradient_steps: int = 3,
|
| 861 |
+
outline: bool = True,
|
| 862 |
+
outline_color: str = "#000000",
|
| 863 |
+
outline_thickness: float = 0.03
|
| 864 |
+
) -> Dict[str, Any]:
|
| 865 |
+
"""
|
| 866 |
+
Convert an object's material to toon/cel-shaded style.
|
| 867 |
+
|
| 868 |
+
Toon shading creates a cartoon-like appearance with discrete shading bands
|
| 869 |
+
instead of smooth gradients, commonly used in anime and cel-animation styles.
|
| 870 |
+
|
| 871 |
+
Args:
|
| 872 |
+
scene_id: ID of the scene
|
| 873 |
+
object_id: ID of the object to update
|
| 874 |
+
enabled: Enable/disable toon shading (True to apply, False to revert to standard)
|
| 875 |
+
color: Base color for toon material (optional, keeps existing if not set)
|
| 876 |
+
gradient_steps: Number of shading steps (2=hard, 3=medium, 5=soft, default: 3)
|
| 877 |
+
outline: Add black outline effect (default: True)
|
| 878 |
+
outline_color: Color of the outline (default: "#000000")
|
| 879 |
+
outline_thickness: Thickness of outline (0.01-0.1, default: 0.03)
|
| 880 |
+
|
| 881 |
+
Returns:
|
| 882 |
+
Dictionary with updated material info and message
|
| 883 |
+
"""
|
| 884 |
+
scene = storage.get(scene_id)
|
| 885 |
+
if not scene:
|
| 886 |
+
raise ValueError(f"Scene '{scene_id}' not found")
|
| 887 |
+
|
| 888 |
+
if "objects" not in scene or not scene["objects"]:
|
| 889 |
+
raise ValueError("Scene has no objects")
|
| 890 |
+
|
| 891 |
+
# Find object
|
| 892 |
+
obj = None
|
| 893 |
+
for o in scene["objects"]:
|
| 894 |
+
if o.get("id") == object_id or o.get("object_id") == object_id:
|
| 895 |
+
obj = o
|
| 896 |
+
break
|
| 897 |
+
|
| 898 |
+
if not obj:
|
| 899 |
+
raise ValueError(f"Object '{object_id}' not found in scene")
|
| 900 |
+
|
| 901 |
+
# Ensure material exists
|
| 902 |
+
if "material" not in obj:
|
| 903 |
+
obj["material"] = {}
|
| 904 |
+
|
| 905 |
+
# Update toon properties
|
| 906 |
+
if enabled:
|
| 907 |
+
obj["material"]["toon"] = {
|
| 908 |
+
"enabled": True,
|
| 909 |
+
"gradient_steps": max(2, min(10, gradient_steps)),
|
| 910 |
+
"outline": outline,
|
| 911 |
+
"outline_color": outline_color,
|
| 912 |
+
"outline_thickness": max(0.01, min(0.1, outline_thickness))
|
| 913 |
+
}
|
| 914 |
+
if color:
|
| 915 |
+
obj["material"]["color"] = color
|
| 916 |
+
|
| 917 |
+
message = f"Applied toon shading to '{object_id}' ({gradient_steps} steps{', with outline' if outline else ''})"
|
| 918 |
+
else:
|
| 919 |
+
obj["material"]["toon"] = {"enabled": False}
|
| 920 |
+
message = f"Disabled toon shading on '{object_id}' (reverted to standard material)"
|
| 921 |
+
|
| 922 |
+
storage.save(scene)
|
| 923 |
+
|
| 924 |
+
return {
|
| 925 |
+
"scene_id": scene_id,
|
| 926 |
+
"object_id": object_id,
|
| 927 |
+
"message": message,
|
| 928 |
+
"material": obj["material"]
|
| 929 |
+
}
|
backend/tools/scene_tools.py
CHANGED
|
@@ -360,3 +360,106 @@ def get_scene_info(
|
|
| 360 |
},
|
| 361 |
"objects": objects_info,
|
| 362 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 360 |
},
|
| 361 |
"objects": objects_info,
|
| 362 |
}
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
# Valid brick types from Kenney brick kit
|
| 366 |
+
BRICK_TYPES = {
|
| 367 |
+
"brick_1x1": "brick_1x1.glb",
|
| 368 |
+
"brick_1x2": "brick_1x2.glb",
|
| 369 |
+
"brick_1x4": "brick_1x4.glb",
|
| 370 |
+
"brick_2x2": "brick_2x2.glb",
|
| 371 |
+
"brick_2x4": "brick_2x4.glb",
|
| 372 |
+
"plate_1x2": "plate_1x2.glb",
|
| 373 |
+
"plate_2x2": "plate_2x2.glb",
|
| 374 |
+
"plate_2x4": "plate_2x4.glb",
|
| 375 |
+
"plate_4x4": "plate_4x4.glb",
|
| 376 |
+
"slope_2x2": "slope_2x2.glb",
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
def add_brick(
|
| 381 |
+
scene_id: str,
|
| 382 |
+
brick_type: str = "brick_2x4",
|
| 383 |
+
position: Optional[Dict[str, float]] = None,
|
| 384 |
+
rotation: Optional[Dict[str, float]] = None,
|
| 385 |
+
color: str = "#ff0000",
|
| 386 |
+
name: Optional[str] = None,
|
| 387 |
+
base_url: str = "http://localhost:8000"
|
| 388 |
+
) -> Dict[str, Any]:
|
| 389 |
+
"""
|
| 390 |
+
Add a LEGO-style brick from the Kenney brick kit.
|
| 391 |
+
|
| 392 |
+
Args:
|
| 393 |
+
scene_id: ID of the scene
|
| 394 |
+
brick_type: Type of brick - brick_1x1, brick_1x2, brick_1x4, brick_2x2, brick_2x4,
|
| 395 |
+
plate_1x2, plate_2x2, plate_2x4, plate_4x4, slope_2x2
|
| 396 |
+
position: Position {x, y, z} (default: {0, 0, 0})
|
| 397 |
+
rotation: Rotation in degrees {x, y, z} (default: {0, 0, 0})
|
| 398 |
+
color: Hex color code for the brick (default: #ff0000 red)
|
| 399 |
+
name: Optional name for the brick
|
| 400 |
+
base_url: Base URL for the deployed space
|
| 401 |
+
|
| 402 |
+
Returns:
|
| 403 |
+
Dictionary with brick info and message
|
| 404 |
+
"""
|
| 405 |
+
scene = storage.get(scene_id)
|
| 406 |
+
if not scene:
|
| 407 |
+
raise ValueError(f"Scene '{scene_id}' not found")
|
| 408 |
+
|
| 409 |
+
if brick_type not in BRICK_TYPES:
|
| 410 |
+
raise ValueError(f"Invalid brick_type '{brick_type}'. Valid types: {list(BRICK_TYPES.keys())}")
|
| 411 |
+
|
| 412 |
+
# Default position
|
| 413 |
+
if position is None:
|
| 414 |
+
position = {"x": 0, "y": 0, "z": 0}
|
| 415 |
+
|
| 416 |
+
# Default rotation
|
| 417 |
+
if rotation is None:
|
| 418 |
+
rotation = {"x": 0, "y": 0, "z": 0}
|
| 419 |
+
|
| 420 |
+
# Validate position is within world bounds
|
| 421 |
+
x = position.get('x', 0)
|
| 422 |
+
z = position.get('z', 0)
|
| 423 |
+
WORLD_HALF = 5.0
|
| 424 |
+
if abs(x) > WORLD_HALF or abs(z) > WORLD_HALF:
|
| 425 |
+
raise ValueError(
|
| 426 |
+
f"Brick position ({x}, {z}) is outside the 10x10 world bounds. "
|
| 427 |
+
f"X and Z must be between -{WORLD_HALF} and {WORLD_HALF}."
|
| 428 |
+
)
|
| 429 |
+
|
| 430 |
+
# Generate unique ID
|
| 431 |
+
brick_id = f"brick_{len(scene['objects'])}_{brick_type}"
|
| 432 |
+
|
| 433 |
+
# Generate name if not provided
|
| 434 |
+
if not name:
|
| 435 |
+
name = f"{brick_type.replace('_', ' ').title()}"
|
| 436 |
+
|
| 437 |
+
# Model path relative to static files
|
| 438 |
+
model_path = f"/static/models/kenney/brick_kit/{BRICK_TYPES[brick_type]}"
|
| 439 |
+
|
| 440 |
+
brick_obj = {
|
| 441 |
+
"id": brick_id,
|
| 442 |
+
"type": "brick",
|
| 443 |
+
"brick_type": brick_type,
|
| 444 |
+
"name": name,
|
| 445 |
+
"position": position,
|
| 446 |
+
"rotation": rotation,
|
| 447 |
+
"scale": {"x": 1, "y": 1, "z": 1},
|
| 448 |
+
"model_path": model_path,
|
| 449 |
+
"material": {
|
| 450 |
+
"color": color,
|
| 451 |
+
"metalness": 0.1,
|
| 452 |
+
"roughness": 0.7
|
| 453 |
+
}
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
scene["objects"].append(brick_obj)
|
| 457 |
+
storage.save(scene)
|
| 458 |
+
|
| 459 |
+
return {
|
| 460 |
+
"scene_id": scene_id,
|
| 461 |
+
"brick_id": brick_id,
|
| 462 |
+
"brick_type": brick_type,
|
| 463 |
+
"message": f"Added {name} at ({position['x']}, {position['y']}, {position['z']})",
|
| 464 |
+
"brick": brick_obj
|
| 465 |
+
}
|
chat_client.py
CHANGED
|
@@ -21,6 +21,7 @@ from backend.tools.scene_tools import (
|
|
| 21 |
remove_game_object,
|
| 22 |
set_scene_lighting,
|
| 23 |
get_scene_info,
|
|
|
|
| 24 |
)
|
| 25 |
from backend.tools.player_tools import (
|
| 26 |
set_player_speed,
|
|
@@ -42,6 +43,18 @@ from backend.tools.rendering_tools import (
|
|
| 42 |
update_object_material,
|
| 43 |
set_background_color,
|
| 44 |
set_fog,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
)
|
| 46 |
from backend.game_models import create_vector3, create_material
|
| 47 |
|
|
@@ -353,6 +366,179 @@ TOOLS = [
|
|
| 353 |
}
|
| 354 |
}
|
| 355 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
]
|
| 357 |
|
| 358 |
|
|
@@ -528,6 +714,97 @@ Be concise but helpful. After making changes, briefly confirm what was done."""
|
|
| 528 |
args.get("density")
|
| 529 |
)
|
| 530 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 531 |
else:
|
| 532 |
return {"error": f"Unknown tool: {name}"}
|
| 533 |
|
|
@@ -710,6 +987,45 @@ If the user DOES specify a position (e.g., "add a cube at 0, 0, 0"), use their s
|
|
| 710 |
elif tool == "set_fog":
|
| 711 |
return {"action": "setFog", "data": result.get("fog")}
|
| 712 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 713 |
return None
|
| 714 |
|
| 715 |
def clear_history(self):
|
|
|
|
| 21 |
remove_game_object,
|
| 22 |
set_scene_lighting,
|
| 23 |
get_scene_info,
|
| 24 |
+
add_brick,
|
| 25 |
)
|
| 26 |
from backend.tools.player_tools import (
|
| 27 |
set_player_speed,
|
|
|
|
| 43 |
update_object_material,
|
| 44 |
set_background_color,
|
| 45 |
set_fog,
|
| 46 |
+
update_material_to_toon,
|
| 47 |
+
)
|
| 48 |
+
from backend.tools.environment_tools import (
|
| 49 |
+
add_skybox,
|
| 50 |
+
remove_skybox,
|
| 51 |
+
add_particles,
|
| 52 |
+
remove_particles,
|
| 53 |
+
)
|
| 54 |
+
from backend.tools.ui_tools import (
|
| 55 |
+
render_text_on_screen,
|
| 56 |
+
render_bar_on_screen,
|
| 57 |
+
remove_ui_element,
|
| 58 |
)
|
| 59 |
from backend.game_models import create_vector3, create_material
|
| 60 |
|
|
|
|
| 366 |
}
|
| 367 |
}
|
| 368 |
},
|
| 369 |
+
# Environment Tools
|
| 370 |
+
{
|
| 371 |
+
"type": "function",
|
| 372 |
+
"function": {
|
| 373 |
+
"name": "add_skybox",
|
| 374 |
+
"description": "Add a procedural sky to the scene. Presets: day, sunset, noon, dawn, night",
|
| 375 |
+
"parameters": {
|
| 376 |
+
"type": "object",
|
| 377 |
+
"properties": {
|
| 378 |
+
"scene_id": {"type": "string", "description": "ID of the scene"},
|
| 379 |
+
"preset": {"type": "string", "enum": ["day", "sunset", "noon", "dawn", "night"], "description": "Sky preset"},
|
| 380 |
+
"turbidity": {"type": "number", "description": "Haziness 2-20 (default: 10)"},
|
| 381 |
+
"rayleigh": {"type": "number", "description": "Blue sky intensity 0-4 (default: 2)"},
|
| 382 |
+
"sun_elevation": {"type": "number", "description": "Sun angle from horizon -90 to 90 (default: 45)"},
|
| 383 |
+
"sun_azimuth": {"type": "number", "description": "Sun compass direction 0-360 (default: 180)"},
|
| 384 |
+
},
|
| 385 |
+
"required": ["scene_id"]
|
| 386 |
+
}
|
| 387 |
+
}
|
| 388 |
+
},
|
| 389 |
+
{
|
| 390 |
+
"type": "function",
|
| 391 |
+
"function": {
|
| 392 |
+
"name": "remove_skybox",
|
| 393 |
+
"description": "Remove the skybox from the scene",
|
| 394 |
+
"parameters": {
|
| 395 |
+
"type": "object",
|
| 396 |
+
"properties": {
|
| 397 |
+
"scene_id": {"type": "string", "description": "ID of the scene"},
|
| 398 |
+
},
|
| 399 |
+
"required": ["scene_id"]
|
| 400 |
+
}
|
| 401 |
+
}
|
| 402 |
+
},
|
| 403 |
+
{
|
| 404 |
+
"type": "function",
|
| 405 |
+
"function": {
|
| 406 |
+
"name": "add_particles",
|
| 407 |
+
"description": "Add particle effects. Presets: fire, smoke, sparkle, rain, snow",
|
| 408 |
+
"parameters": {
|
| 409 |
+
"type": "object",
|
| 410 |
+
"properties": {
|
| 411 |
+
"scene_id": {"type": "string", "description": "ID of the scene"},
|
| 412 |
+
"preset": {"type": "string", "enum": ["fire", "smoke", "sparkle", "rain", "snow"], "description": "Particle preset"},
|
| 413 |
+
"x": {"type": "number", "description": "X position for localized effects"},
|
| 414 |
+
"y": {"type": "number", "description": "Y position for localized effects"},
|
| 415 |
+
"z": {"type": "number", "description": "Z position for localized effects"},
|
| 416 |
+
"particle_id": {"type": "string", "description": "Optional unique ID"},
|
| 417 |
+
},
|
| 418 |
+
"required": ["scene_id", "preset"]
|
| 419 |
+
}
|
| 420 |
+
}
|
| 421 |
+
},
|
| 422 |
+
{
|
| 423 |
+
"type": "function",
|
| 424 |
+
"function": {
|
| 425 |
+
"name": "remove_particles",
|
| 426 |
+
"description": "Remove a particle system from the scene",
|
| 427 |
+
"parameters": {
|
| 428 |
+
"type": "object",
|
| 429 |
+
"properties": {
|
| 430 |
+
"scene_id": {"type": "string", "description": "ID of the scene"},
|
| 431 |
+
"particle_id": {"type": "string", "description": "ID of the particle system to remove"},
|
| 432 |
+
},
|
| 433 |
+
"required": ["scene_id", "particle_id"]
|
| 434 |
+
}
|
| 435 |
+
}
|
| 436 |
+
},
|
| 437 |
+
# UI Tools
|
| 438 |
+
{
|
| 439 |
+
"type": "function",
|
| 440 |
+
"function": {
|
| 441 |
+
"name": "render_text_on_screen",
|
| 442 |
+
"description": "Render text on the screen as a 2D overlay",
|
| 443 |
+
"parameters": {
|
| 444 |
+
"type": "object",
|
| 445 |
+
"properties": {
|
| 446 |
+
"scene_id": {"type": "string", "description": "ID of the scene"},
|
| 447 |
+
"text": {"type": "string", "description": "Text to display"},
|
| 448 |
+
"x": {"type": "number", "description": "Horizontal position 0-100% (default: 50)"},
|
| 449 |
+
"y": {"type": "number", "description": "Vertical position 0-100% (default: 10)"},
|
| 450 |
+
"font_size": {"type": "integer", "description": "Font size in pixels (default: 24)"},
|
| 451 |
+
"color": {"type": "string", "description": "Text color hex (default: #ffffff)"},
|
| 452 |
+
"text_id": {"type": "string", "description": "Optional unique ID for updates"},
|
| 453 |
+
"background_color": {"type": "string", "description": "Optional background color"},
|
| 454 |
+
},
|
| 455 |
+
"required": ["scene_id", "text"]
|
| 456 |
+
}
|
| 457 |
+
}
|
| 458 |
+
},
|
| 459 |
+
{
|
| 460 |
+
"type": "function",
|
| 461 |
+
"function": {
|
| 462 |
+
"name": "render_bar_on_screen",
|
| 463 |
+
"description": "Render a progress/health bar on screen",
|
| 464 |
+
"parameters": {
|
| 465 |
+
"type": "object",
|
| 466 |
+
"properties": {
|
| 467 |
+
"scene_id": {"type": "string", "description": "ID of the scene"},
|
| 468 |
+
"x": {"type": "number", "description": "Horizontal position 0-100% (default: 10)"},
|
| 469 |
+
"y": {"type": "number", "description": "Vertical position 0-100% (default: 10)"},
|
| 470 |
+
"width": {"type": "number", "description": "Bar width in pixels (default: 200)"},
|
| 471 |
+
"height": {"type": "number", "description": "Bar height in pixels (default: 20)"},
|
| 472 |
+
"value": {"type": "number", "description": "Current value (default: 100)"},
|
| 473 |
+
"max_value": {"type": "number", "description": "Max value (default: 100)"},
|
| 474 |
+
"bar_color": {"type": "string", "description": "Fill color (default: #00ff00)"},
|
| 475 |
+
"bar_id": {"type": "string", "description": "Optional unique ID for updates"},
|
| 476 |
+
"label": {"type": "string", "description": "Optional label above bar"},
|
| 477 |
+
"show_value": {"type": "boolean", "description": "Show numeric value"},
|
| 478 |
+
},
|
| 479 |
+
"required": ["scene_id"]
|
| 480 |
+
}
|
| 481 |
+
}
|
| 482 |
+
},
|
| 483 |
+
{
|
| 484 |
+
"type": "function",
|
| 485 |
+
"function": {
|
| 486 |
+
"name": "remove_ui_element",
|
| 487 |
+
"description": "Remove a UI element from the screen",
|
| 488 |
+
"parameters": {
|
| 489 |
+
"type": "object",
|
| 490 |
+
"properties": {
|
| 491 |
+
"scene_id": {"type": "string", "description": "ID of the scene"},
|
| 492 |
+
"element_id": {"type": "string", "description": "ID of the text or bar to remove"},
|
| 493 |
+
},
|
| 494 |
+
"required": ["scene_id", "element_id"]
|
| 495 |
+
}
|
| 496 |
+
}
|
| 497 |
+
},
|
| 498 |
+
# Toon Material
|
| 499 |
+
{
|
| 500 |
+
"type": "function",
|
| 501 |
+
"function": {
|
| 502 |
+
"name": "update_material_to_toon",
|
| 503 |
+
"description": "Apply toon/cel-shading to an object for cartoon-like appearance",
|
| 504 |
+
"parameters": {
|
| 505 |
+
"type": "object",
|
| 506 |
+
"properties": {
|
| 507 |
+
"scene_id": {"type": "string", "description": "ID of the scene"},
|
| 508 |
+
"object_id": {"type": "string", "description": "ID of the object"},
|
| 509 |
+
"enabled": {"type": "boolean", "description": "Enable/disable toon shading (default: true)"},
|
| 510 |
+
"color": {"type": "string", "description": "Base color (optional)"},
|
| 511 |
+
"gradient_steps": {"type": "integer", "description": "Shading steps 2-10 (default: 3)"},
|
| 512 |
+
"outline": {"type": "boolean", "description": "Add outline effect (default: true)"},
|
| 513 |
+
"outline_color": {"type": "string", "description": "Outline color (default: #000000)"},
|
| 514 |
+
"outline_thickness": {"type": "number", "description": "Outline thickness 0.01-0.1 (default: 0.03)"},
|
| 515 |
+
},
|
| 516 |
+
"required": ["scene_id", "object_id"]
|
| 517 |
+
}
|
| 518 |
+
}
|
| 519 |
+
},
|
| 520 |
+
# Brick Blocks
|
| 521 |
+
{
|
| 522 |
+
"type": "function",
|
| 523 |
+
"function": {
|
| 524 |
+
"name": "add_brick",
|
| 525 |
+
"description": "Add a LEGO-style brick from the Kenney brick kit. Types: brick_1x1, brick_1x2, brick_1x4, brick_2x2, brick_2x4, plate_1x2, plate_2x2, plate_2x4, plate_4x4, slope_2x2",
|
| 526 |
+
"parameters": {
|
| 527 |
+
"type": "object",
|
| 528 |
+
"properties": {
|
| 529 |
+
"scene_id": {"type": "string", "description": "ID of the scene"},
|
| 530 |
+
"brick_type": {"type": "string", "enum": ["brick_1x1", "brick_1x2", "brick_1x4", "brick_2x2", "brick_2x4", "plate_1x2", "plate_2x2", "plate_2x4", "plate_4x4", "slope_2x2"], "description": "Type of brick"},
|
| 531 |
+
"x": {"type": "number", "description": "X position"},
|
| 532 |
+
"y": {"type": "number", "description": "Y position"},
|
| 533 |
+
"z": {"type": "number", "description": "Z position"},
|
| 534 |
+
"rotation_y": {"type": "number", "description": "Y rotation in degrees"},
|
| 535 |
+
"color": {"type": "string", "description": "Hex color (default: #ff0000)"},
|
| 536 |
+
"name": {"type": "string", "description": "Optional name"},
|
| 537 |
+
},
|
| 538 |
+
"required": ["scene_id", "brick_type"]
|
| 539 |
+
}
|
| 540 |
+
}
|
| 541 |
+
},
|
| 542 |
]
|
| 543 |
|
| 544 |
|
|
|
|
| 714 |
args.get("density")
|
| 715 |
)
|
| 716 |
|
| 717 |
+
# Environment tools
|
| 718 |
+
elif name == "add_skybox":
|
| 719 |
+
return add_skybox(
|
| 720 |
+
args["scene_id"],
|
| 721 |
+
args.get("preset", "day"),
|
| 722 |
+
args.get("turbidity", 10.0),
|
| 723 |
+
args.get("rayleigh", 2.0),
|
| 724 |
+
args.get("sun_elevation", 45.0),
|
| 725 |
+
args.get("sun_azimuth", 180.0)
|
| 726 |
+
)
|
| 727 |
+
|
| 728 |
+
elif name == "remove_skybox":
|
| 729 |
+
return remove_skybox(args["scene_id"])
|
| 730 |
+
|
| 731 |
+
elif name == "add_particles":
|
| 732 |
+
position = None
|
| 733 |
+
if "x" in args or "y" in args or "z" in args:
|
| 734 |
+
position = {"x": args.get("x", 0), "y": args.get("y", 1), "z": args.get("z", 0)}
|
| 735 |
+
return add_particles(
|
| 736 |
+
args["scene_id"],
|
| 737 |
+
args["preset"],
|
| 738 |
+
position,
|
| 739 |
+
args.get("particle_id")
|
| 740 |
+
)
|
| 741 |
+
|
| 742 |
+
elif name == "remove_particles":
|
| 743 |
+
return remove_particles(args["scene_id"], args["particle_id"])
|
| 744 |
+
|
| 745 |
+
# UI tools
|
| 746 |
+
elif name == "render_text_on_screen":
|
| 747 |
+
return render_text_on_screen(
|
| 748 |
+
args["scene_id"],
|
| 749 |
+
args["text"],
|
| 750 |
+
args.get("x", 50.0),
|
| 751 |
+
args.get("y", 10.0),
|
| 752 |
+
args.get("font_size", 24),
|
| 753 |
+
args.get("color", "#ffffff"),
|
| 754 |
+
args.get("text_id"),
|
| 755 |
+
args.get("font_family", "Arial"),
|
| 756 |
+
args.get("text_align", "center"),
|
| 757 |
+
args.get("background_color"),
|
| 758 |
+
args.get("padding", 8)
|
| 759 |
+
)
|
| 760 |
+
|
| 761 |
+
elif name == "render_bar_on_screen":
|
| 762 |
+
return render_bar_on_screen(
|
| 763 |
+
args["scene_id"],
|
| 764 |
+
args.get("x", 10.0),
|
| 765 |
+
args.get("y", 10.0),
|
| 766 |
+
args.get("width", 200.0),
|
| 767 |
+
args.get("height", 20.0),
|
| 768 |
+
args.get("value", 100.0),
|
| 769 |
+
args.get("max_value", 100.0),
|
| 770 |
+
args.get("bar_color", "#00ff00"),
|
| 771 |
+
args.get("background_color", "#333333"),
|
| 772 |
+
args.get("border_color", "#ffffff"),
|
| 773 |
+
args.get("bar_id"),
|
| 774 |
+
args.get("label"),
|
| 775 |
+
args.get("show_value", False)
|
| 776 |
+
)
|
| 777 |
+
|
| 778 |
+
elif name == "remove_ui_element":
|
| 779 |
+
return remove_ui_element(args["scene_id"], args["element_id"])
|
| 780 |
+
|
| 781 |
+
# Toon material
|
| 782 |
+
elif name == "update_material_to_toon":
|
| 783 |
+
return update_material_to_toon(
|
| 784 |
+
args["scene_id"],
|
| 785 |
+
args["object_id"],
|
| 786 |
+
args.get("enabled", True),
|
| 787 |
+
args.get("color"),
|
| 788 |
+
args.get("gradient_steps", 3),
|
| 789 |
+
args.get("outline", True),
|
| 790 |
+
args.get("outline_color", "#000000"),
|
| 791 |
+
args.get("outline_thickness", 0.03)
|
| 792 |
+
)
|
| 793 |
+
|
| 794 |
+
# Brick blocks
|
| 795 |
+
elif name == "add_brick":
|
| 796 |
+
position = {"x": args.get("x", 0), "y": args.get("y", 0), "z": args.get("z", 0)}
|
| 797 |
+
rotation = {"x": 0, "y": args.get("rotation_y", 0), "z": 0}
|
| 798 |
+
return add_brick(
|
| 799 |
+
args["scene_id"],
|
| 800 |
+
args["brick_type"],
|
| 801 |
+
position,
|
| 802 |
+
rotation,
|
| 803 |
+
args.get("color", "#ff0000"),
|
| 804 |
+
args.get("name"),
|
| 805 |
+
self.base_url
|
| 806 |
+
)
|
| 807 |
+
|
| 808 |
else:
|
| 809 |
return {"error": f"Unknown tool: {name}"}
|
| 810 |
|
|
|
|
| 987 |
elif tool == "set_fog":
|
| 988 |
return {"action": "setFog", "data": result.get("fog")}
|
| 989 |
|
| 990 |
+
# Environment tools
|
| 991 |
+
elif tool == "add_skybox":
|
| 992 |
+
return {"action": "addSkybox", "data": result.get("skybox")}
|
| 993 |
+
|
| 994 |
+
elif tool == "remove_skybox":
|
| 995 |
+
return {"action": "removeSkybox", "data": {}}
|
| 996 |
+
|
| 997 |
+
elif tool == "add_particles":
|
| 998 |
+
return {"action": "addParticles", "data": result.get("particle_system")}
|
| 999 |
+
|
| 1000 |
+
elif tool == "remove_particles":
|
| 1001 |
+
return {"action": "removeParticles", "data": {"particle_id": action["args"].get("particle_id")}}
|
| 1002 |
+
|
| 1003 |
+
# UI tools
|
| 1004 |
+
elif tool == "render_text_on_screen":
|
| 1005 |
+
return {"action": "renderText", "data": result.get("text_element")}
|
| 1006 |
+
|
| 1007 |
+
elif tool == "render_bar_on_screen":
|
| 1008 |
+
return {"action": "renderBar", "data": result.get("bar_element")}
|
| 1009 |
+
|
| 1010 |
+
elif tool == "remove_ui_element":
|
| 1011 |
+
return {"action": "removeUIElement", "data": {"element_id": action["args"].get("element_id")}}
|
| 1012 |
+
|
| 1013 |
+
# Toon shading
|
| 1014 |
+
elif tool == "update_material_to_toon":
|
| 1015 |
+
return {"action": "updateToonMaterial", "data": {
|
| 1016 |
+
"object_id": action["args"].get("object_id"),
|
| 1017 |
+
"enabled": action["args"].get("enabled", True),
|
| 1018 |
+
"color": action["args"].get("color"),
|
| 1019 |
+
"gradient_steps": action["args"].get("gradient_steps", 3),
|
| 1020 |
+
"outline": action["args"].get("outline", True),
|
| 1021 |
+
"outline_color": action["args"].get("outline_color", "#000000"),
|
| 1022 |
+
"outline_thickness": action["args"].get("outline_thickness", 0.03)
|
| 1023 |
+
}}
|
| 1024 |
+
|
| 1025 |
+
# Brick blocks
|
| 1026 |
+
elif tool == "add_brick":
|
| 1027 |
+
return {"action": "addBrick", "data": result.get("brick")}
|
| 1028 |
+
|
| 1029 |
return None
|
| 1030 |
|
| 1031 |
def clear_history(self):
|
frontend/game_viewer.html
CHANGED
|
@@ -65,8 +65,24 @@
|
|
| 65 |
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
|
| 66 |
import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js';
|
| 67 |
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
|
|
|
|
|
|
|
| 68 |
import * as CANNON from 'cannon-es';
|
| 69 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
// Get scene ID from URL
|
| 71 |
const sceneId = window.location.pathname.split('/').pop();
|
| 72 |
const baseUrl = window.location.origin;
|
|
@@ -101,7 +117,7 @@
|
|
| 101 |
const MAX_SELECT_DISTANCE = 10; // Max raycast distance for selection
|
| 102 |
|
| 103 |
// FPS movement and look variables (configurable via player_config)
|
| 104 |
-
let moveSpeed =
|
| 105 |
const velocity = new THREE.Vector3();
|
| 106 |
let isMouseLocked = false;
|
| 107 |
let cameraRotationX = 0; // Pitch (up/down)
|
|
@@ -126,7 +142,7 @@
|
|
| 126 |
let JUMP_FORCE = 5.0;
|
| 127 |
let GRAVITY = -9.82;
|
| 128 |
let PLAYER_MASS = 80.0;
|
| 129 |
-
let LINEAR_DAMPING = 0.
|
| 130 |
// World size from scene data (default 25x25)
|
| 131 |
let WORLD_SIZE = 25;
|
| 132 |
let WORLD_HALF = WORLD_SIZE / 2;
|
|
@@ -238,6 +254,43 @@
|
|
| 238 |
console.log('✅ Player configuration applied successfully');
|
| 239 |
}
|
| 240 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
async function init() {
|
| 242 |
try {
|
| 243 |
// Check for embedded scene data first (used when served via Gradio)
|
|
@@ -278,6 +331,9 @@
|
|
| 278 |
// Render all game objects
|
| 279 |
renderGameObjects();
|
| 280 |
|
|
|
|
|
|
|
|
|
|
| 281 |
// Start animation loop
|
| 282 |
animate();
|
| 283 |
|
|
@@ -479,14 +535,14 @@
|
|
| 479 |
physicsWorld = new CANNON.World();
|
| 480 |
physicsWorld.gravity.set(0, GRAVITY, 0);
|
| 481 |
|
| 482 |
-
// Set up collision materials
|
| 483 |
const defaultMaterial = new CANNON.Material('default');
|
| 484 |
const defaultContactMaterial = new CANNON.ContactMaterial(
|
| 485 |
defaultMaterial,
|
| 486 |
defaultMaterial,
|
| 487 |
{
|
| 488 |
-
friction: 0.
|
| 489 |
-
restitution: 0.0,
|
| 490 |
}
|
| 491 |
);
|
| 492 |
physicsWorld.addContactMaterial(defaultContactMaterial);
|
|
@@ -996,6 +1052,9 @@
|
|
| 996 |
// Update crosshair floor intersection and send to parent
|
| 997 |
updateCrosshairPosition();
|
| 998 |
|
|
|
|
|
|
|
|
|
|
| 999 |
// Render using composer (for outlines) instead of direct renderer
|
| 1000 |
if (composer) {
|
| 1001 |
composer.render();
|
|
@@ -1322,6 +1381,38 @@
|
|
| 1322 |
}
|
| 1323 |
console.log('Player dimensions updated:', { height: PLAYER_HEIGHT, radius: PLAYER_RADIUS });
|
| 1324 |
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1325 |
default:
|
| 1326 |
console.warn('Unknown postMessage action:', action);
|
| 1327 |
}
|
|
@@ -1718,6 +1809,465 @@
|
|
| 1718 |
console.log('Fog enabled:', fogData.type);
|
| 1719 |
}
|
| 1720 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1721 |
// Start the application
|
| 1722 |
init();
|
| 1723 |
</script>
|
|
|
|
| 65 |
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
|
| 66 |
import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js';
|
| 67 |
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
|
| 68 |
+
import { Sky } from 'three/addons/objects/Sky.js';
|
| 69 |
+
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
| 70 |
import * as CANNON from 'cannon-es';
|
| 71 |
|
| 72 |
+
// Skybox and environment references
|
| 73 |
+
let sky = null;
|
| 74 |
+
let sun = new THREE.Vector3();
|
| 75 |
+
|
| 76 |
+
// Particle systems
|
| 77 |
+
let particleSystems = new Map();
|
| 78 |
+
|
| 79 |
+
// UI overlay container
|
| 80 |
+
let uiContainer = null;
|
| 81 |
+
let uiElements = new Map();
|
| 82 |
+
|
| 83 |
+
// GLTF loader for brick models
|
| 84 |
+
const gltfLoader = new GLTFLoader();
|
| 85 |
+
|
| 86 |
// Get scene ID from URL
|
| 87 |
const sceneId = window.location.pathname.split('/').pop();
|
| 88 |
const baseUrl = window.location.origin;
|
|
|
|
| 117 |
const MAX_SELECT_DISTANCE = 10; // Max raycast distance for selection
|
| 118 |
|
| 119 |
// FPS movement and look variables (configurable via player_config)
|
| 120 |
+
let moveSpeed = 8.0; // Default walking speed in units/sec
|
| 121 |
const velocity = new THREE.Vector3();
|
| 122 |
let isMouseLocked = false;
|
| 123 |
let cameraRotationX = 0; // Pitch (up/down)
|
|
|
|
| 142 |
let JUMP_FORCE = 5.0;
|
| 143 |
let GRAVITY = -9.82;
|
| 144 |
let PLAYER_MASS = 80.0;
|
| 145 |
+
let LINEAR_DAMPING = 0.0; // No damping - we control velocity directly
|
| 146 |
// World size from scene data (default 25x25)
|
| 147 |
let WORLD_SIZE = 25;
|
| 148 |
let WORLD_HALF = WORLD_SIZE / 2;
|
|
|
|
| 254 |
console.log('✅ Player configuration applied successfully');
|
| 255 |
}
|
| 256 |
|
| 257 |
+
function applyInitialEnvironment() {
|
| 258 |
+
/**
|
| 259 |
+
* Apply initial environment settings from scene data
|
| 260 |
+
* Loads skybox, particles, and UI elements on startup
|
| 261 |
+
*/
|
| 262 |
+
if (!sceneData) return;
|
| 263 |
+
|
| 264 |
+
// Apply skybox if defined in scene data
|
| 265 |
+
if (sceneData.skybox) {
|
| 266 |
+
console.log('Applying initial skybox:', sceneData.skybox);
|
| 267 |
+
handleAddSkybox(sceneData.skybox);
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
// Apply particles if defined in scene data
|
| 271 |
+
if (sceneData.particles && Array.isArray(sceneData.particles)) {
|
| 272 |
+
sceneData.particles.forEach(particleConfig => {
|
| 273 |
+
console.log('Applying initial particles:', particleConfig);
|
| 274 |
+
handleAddParticles(particleConfig);
|
| 275 |
+
});
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
// Apply UI elements if defined in scene data
|
| 279 |
+
if (sceneData.ui_elements && Array.isArray(sceneData.ui_elements)) {
|
| 280 |
+
sceneData.ui_elements.forEach(uiConfig => {
|
| 281 |
+
if (uiConfig.type === 'text') {
|
| 282 |
+
console.log('Applying initial UI text:', uiConfig);
|
| 283 |
+
handleRenderText(uiConfig);
|
| 284 |
+
} else if (uiConfig.type === 'bar') {
|
| 285 |
+
console.log('Applying initial UI bar:', uiConfig);
|
| 286 |
+
handleRenderBar(uiConfig);
|
| 287 |
+
}
|
| 288 |
+
});
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
console.log('✅ Initial environment applied');
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
async function init() {
|
| 295 |
try {
|
| 296 |
// Check for embedded scene data first (used when served via Gradio)
|
|
|
|
| 331 |
// Render all game objects
|
| 332 |
renderGameObjects();
|
| 333 |
|
| 334 |
+
// Apply initial environment (skybox, particles, UI from scene data)
|
| 335 |
+
applyInitialEnvironment();
|
| 336 |
+
|
| 337 |
// Start animation loop
|
| 338 |
animate();
|
| 339 |
|
|
|
|
| 535 |
physicsWorld = new CANNON.World();
|
| 536 |
physicsWorld.gravity.set(0, GRAVITY, 0);
|
| 537 |
|
| 538 |
+
// Set up collision materials - zero friction since we control velocity directly
|
| 539 |
const defaultMaterial = new CANNON.Material('default');
|
| 540 |
const defaultContactMaterial = new CANNON.ContactMaterial(
|
| 541 |
defaultMaterial,
|
| 542 |
defaultMaterial,
|
| 543 |
{
|
| 544 |
+
friction: 0.0, // No friction - we set velocity directly each frame
|
| 545 |
+
restitution: 0.0, // No bounce
|
| 546 |
}
|
| 547 |
);
|
| 548 |
physicsWorld.addContactMaterial(defaultContactMaterial);
|
|
|
|
| 1052 |
// Update crosshair floor intersection and send to parent
|
| 1053 |
updateCrosshairPosition();
|
| 1054 |
|
| 1055 |
+
// Update particle systems
|
| 1056 |
+
updateParticleSystems(delta);
|
| 1057 |
+
|
| 1058 |
// Render using composer (for outlines) instead of direct renderer
|
| 1059 |
if (composer) {
|
| 1060 |
composer.render();
|
|
|
|
| 1381 |
}
|
| 1382 |
console.log('Player dimensions updated:', { height: PLAYER_HEIGHT, radius: PLAYER_RADIUS });
|
| 1383 |
break;
|
| 1384 |
+
// Skybox actions
|
| 1385 |
+
case 'addSkybox':
|
| 1386 |
+
handleAddSkybox(data);
|
| 1387 |
+
break;
|
| 1388 |
+
case 'removeSkybox':
|
| 1389 |
+
handleRemoveSkybox();
|
| 1390 |
+
break;
|
| 1391 |
+
// Particle actions
|
| 1392 |
+
case 'addParticles':
|
| 1393 |
+
handleAddParticles(data);
|
| 1394 |
+
break;
|
| 1395 |
+
case 'removeParticles':
|
| 1396 |
+
handleRemoveParticles(data.particle_id);
|
| 1397 |
+
break;
|
| 1398 |
+
// UI actions
|
| 1399 |
+
case 'renderText':
|
| 1400 |
+
handleRenderText(data);
|
| 1401 |
+
break;
|
| 1402 |
+
case 'renderBar':
|
| 1403 |
+
handleRenderBar(data);
|
| 1404 |
+
break;
|
| 1405 |
+
case 'removeUIElement':
|
| 1406 |
+
handleRemoveUIElement(data.element_id);
|
| 1407 |
+
break;
|
| 1408 |
+
// Toon shading
|
| 1409 |
+
case 'updateToonMaterial':
|
| 1410 |
+
handleUpdateToonMaterial(data);
|
| 1411 |
+
break;
|
| 1412 |
+
// Brick blocks
|
| 1413 |
+
case 'addBrick':
|
| 1414 |
+
handleAddBrick(data);
|
| 1415 |
+
break;
|
| 1416 |
default:
|
| 1417 |
console.warn('Unknown postMessage action:', action);
|
| 1418 |
}
|
|
|
|
| 1809 |
console.log('Fog enabled:', fogData.type);
|
| 1810 |
}
|
| 1811 |
|
| 1812 |
+
// ==================== Skybox Handlers ====================
|
| 1813 |
+
|
| 1814 |
+
function handleAddSkybox(skyboxData) {
|
| 1815 |
+
// Remove existing skybox if any
|
| 1816 |
+
if (sky) {
|
| 1817 |
+
scene.remove(sky);
|
| 1818 |
+
}
|
| 1819 |
+
|
| 1820 |
+
// Create Sky mesh
|
| 1821 |
+
sky = new Sky();
|
| 1822 |
+
sky.scale.setScalar(450000);
|
| 1823 |
+
scene.add(sky);
|
| 1824 |
+
|
| 1825 |
+
const skyUniforms = sky.material.uniforms;
|
| 1826 |
+
skyUniforms['turbidity'].value = skyboxData.turbidity || 10;
|
| 1827 |
+
skyUniforms['rayleigh'].value = skyboxData.rayleigh || 2;
|
| 1828 |
+
skyUniforms['mieCoefficient'].value = 0.005;
|
| 1829 |
+
skyUniforms['mieDirectionalG'].value = 0.8;
|
| 1830 |
+
|
| 1831 |
+
// Calculate sun position from elevation and azimuth
|
| 1832 |
+
const phi = THREE.MathUtils.degToRad(90 - (skyboxData.sun_elevation || 45));
|
| 1833 |
+
const theta = THREE.MathUtils.degToRad(skyboxData.sun_azimuth || 180);
|
| 1834 |
+
sun.setFromSphericalCoords(1, phi, theta);
|
| 1835 |
+
skyUniforms['sunPosition'].value.copy(sun);
|
| 1836 |
+
|
| 1837 |
+
// Update scene background to use sky
|
| 1838 |
+
scene.background = null; // Sky will render as background
|
| 1839 |
+
|
| 1840 |
+
console.log('🌤️ Skybox added:', skyboxData.preset || 'custom',
|
| 1841 |
+
`elevation=${skyboxData.sun_elevation}°`);
|
| 1842 |
+
}
|
| 1843 |
+
|
| 1844 |
+
function handleRemoveSkybox() {
|
| 1845 |
+
if (sky) {
|
| 1846 |
+
scene.remove(sky);
|
| 1847 |
+
sky = null;
|
| 1848 |
+
}
|
| 1849 |
+
// Revert to solid background
|
| 1850 |
+
const bgColor = sceneData?.environment?.background_color || '#87CEEB';
|
| 1851 |
+
scene.background = new THREE.Color(bgColor);
|
| 1852 |
+
console.log('🌤️ Skybox removed');
|
| 1853 |
+
}
|
| 1854 |
+
|
| 1855 |
+
// ==================== Particle System Handlers ====================
|
| 1856 |
+
|
| 1857 |
+
function handleAddParticles(particleData) {
|
| 1858 |
+
const id = particleData.id || particleData.particle_id;
|
| 1859 |
+
|
| 1860 |
+
// Remove existing particle system with same ID
|
| 1861 |
+
if (particleSystems.has(id)) {
|
| 1862 |
+
const existingSystem = particleSystems.get(id);
|
| 1863 |
+
scene.remove(existingSystem.points);
|
| 1864 |
+
particleSystems.delete(id);
|
| 1865 |
+
}
|
| 1866 |
+
|
| 1867 |
+
const config = particleData;
|
| 1868 |
+
const count = config.count || 100;
|
| 1869 |
+
|
| 1870 |
+
// Create particle geometry
|
| 1871 |
+
const geometry = new THREE.BufferGeometry();
|
| 1872 |
+
const positions = new Float32Array(count * 3);
|
| 1873 |
+
const velocities = new Float32Array(count * 3);
|
| 1874 |
+
const lifetimes = new Float32Array(count);
|
| 1875 |
+
|
| 1876 |
+
const spread = config.spread || 1.0;
|
| 1877 |
+
const pos = config.position || { x: 0, y: 0, z: 0 };
|
| 1878 |
+
|
| 1879 |
+
for (let i = 0; i < count; i++) {
|
| 1880 |
+
const i3 = i * 3;
|
| 1881 |
+
|
| 1882 |
+
if (config.localized !== false) {
|
| 1883 |
+
// Localized effect (fire, smoke, sparkle)
|
| 1884 |
+
positions[i3] = pos.x + (Math.random() - 0.5) * spread;
|
| 1885 |
+
positions[i3 + 1] = pos.y + Math.random() * spread;
|
| 1886 |
+
positions[i3 + 2] = pos.z + (Math.random() - 0.5) * spread;
|
| 1887 |
+
} else {
|
| 1888 |
+
// Weather effect (rain, snow) - covers world
|
| 1889 |
+
positions[i3] = (Math.random() - 0.5) * WORLD_SIZE * 2;
|
| 1890 |
+
positions[i3 + 1] = Math.random() * 20;
|
| 1891 |
+
positions[i3 + 2] = (Math.random() - 0.5) * WORLD_SIZE * 2;
|
| 1892 |
+
}
|
| 1893 |
+
|
| 1894 |
+
const vel = config.velocity || { x: 0, y: 1, z: 0 };
|
| 1895 |
+
velocities[i3] = vel.x + (Math.random() - 0.5) * 0.5;
|
| 1896 |
+
velocities[i3 + 1] = vel.y + (Math.random() - 0.5) * 0.5;
|
| 1897 |
+
velocities[i3 + 2] = vel.z + (Math.random() - 0.5) * 0.5;
|
| 1898 |
+
|
| 1899 |
+
lifetimes[i] = Math.random() * (config.lifetime || 2.0);
|
| 1900 |
+
}
|
| 1901 |
+
|
| 1902 |
+
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
| 1903 |
+
|
| 1904 |
+
// Create particle material
|
| 1905 |
+
const startColor = new THREE.Color(config.color_start || '#ffffff');
|
| 1906 |
+
const material = new THREE.PointsMaterial({
|
| 1907 |
+
size: config.size || 0.1,
|
| 1908 |
+
color: startColor,
|
| 1909 |
+
transparent: true,
|
| 1910 |
+
opacity: 0.8,
|
| 1911 |
+
blending: THREE.AdditiveBlending,
|
| 1912 |
+
depthWrite: false
|
| 1913 |
+
});
|
| 1914 |
+
|
| 1915 |
+
const points = new THREE.Points(geometry, material);
|
| 1916 |
+
points.name = `particles_${id}`;
|
| 1917 |
+
scene.add(points);
|
| 1918 |
+
|
| 1919 |
+
// Store particle system data for animation
|
| 1920 |
+
particleSystems.set(id, {
|
| 1921 |
+
points,
|
| 1922 |
+
geometry,
|
| 1923 |
+
velocities,
|
| 1924 |
+
lifetimes,
|
| 1925 |
+
config,
|
| 1926 |
+
maxLifetime: config.lifetime || 2.0,
|
| 1927 |
+
startColor,
|
| 1928 |
+
endColor: new THREE.Color(config.color_end || config.color_start || '#ffffff')
|
| 1929 |
+
});
|
| 1930 |
+
|
| 1931 |
+
console.log('✨ Particles added:', id, config.preset);
|
| 1932 |
+
}
|
| 1933 |
+
|
| 1934 |
+
function handleRemoveParticles(particleId) {
|
| 1935 |
+
if (particleSystems.has(particleId)) {
|
| 1936 |
+
const system = particleSystems.get(particleId);
|
| 1937 |
+
scene.remove(system.points);
|
| 1938 |
+
system.geometry.dispose();
|
| 1939 |
+
system.points.material.dispose();
|
| 1940 |
+
particleSystems.delete(particleId);
|
| 1941 |
+
console.log('✨ Particles removed:', particleId);
|
| 1942 |
+
}
|
| 1943 |
+
}
|
| 1944 |
+
|
| 1945 |
+
function updateParticleSystems(delta) {
|
| 1946 |
+
particleSystems.forEach((system, id) => {
|
| 1947 |
+
const positions = system.geometry.attributes.position.array;
|
| 1948 |
+
const count = positions.length / 3;
|
| 1949 |
+
const config = system.config;
|
| 1950 |
+
const pos = config.position || { x: 0, y: 0, z: 0 };
|
| 1951 |
+
const spread = config.spread || 1.0;
|
| 1952 |
+
|
| 1953 |
+
for (let i = 0; i < count; i++) {
|
| 1954 |
+
const i3 = i * 3;
|
| 1955 |
+
|
| 1956 |
+
// Update position based on velocity
|
| 1957 |
+
positions[i3] += system.velocities[i3] * delta;
|
| 1958 |
+
positions[i3 + 1] += system.velocities[i3 + 1] * delta;
|
| 1959 |
+
positions[i3 + 2] += system.velocities[i3 + 2] * delta;
|
| 1960 |
+
|
| 1961 |
+
// Update lifetime
|
| 1962 |
+
system.lifetimes[i] += delta;
|
| 1963 |
+
|
| 1964 |
+
// Reset particle if lifetime exceeded
|
| 1965 |
+
if (system.lifetimes[i] >= system.maxLifetime) {
|
| 1966 |
+
system.lifetimes[i] = 0;
|
| 1967 |
+
|
| 1968 |
+
if (config.localized !== false) {
|
| 1969 |
+
positions[i3] = pos.x + (Math.random() - 0.5) * spread;
|
| 1970 |
+
positions[i3 + 1] = pos.y;
|
| 1971 |
+
positions[i3 + 2] = pos.z + (Math.random() - 0.5) * spread;
|
| 1972 |
+
} else {
|
| 1973 |
+
// Weather - respawn at top
|
| 1974 |
+
positions[i3] = (Math.random() - 0.5) * WORLD_SIZE * 2;
|
| 1975 |
+
positions[i3 + 1] = 20;
|
| 1976 |
+
positions[i3 + 2] = (Math.random() - 0.5) * WORLD_SIZE * 2;
|
| 1977 |
+
}
|
| 1978 |
+
}
|
| 1979 |
+
}
|
| 1980 |
+
|
| 1981 |
+
system.geometry.attributes.position.needsUpdate = true;
|
| 1982 |
+
});
|
| 1983 |
+
}
|
| 1984 |
+
|
| 1985 |
+
// ==================== UI Overlay Handlers ====================
|
| 1986 |
+
|
| 1987 |
+
function ensureUIContainer() {
|
| 1988 |
+
if (!uiContainer) {
|
| 1989 |
+
uiContainer = document.createElement('div');
|
| 1990 |
+
uiContainer.id = 'ui-overlay';
|
| 1991 |
+
uiContainer.style.cssText = `
|
| 1992 |
+
position: absolute;
|
| 1993 |
+
top: 0;
|
| 1994 |
+
left: 0;
|
| 1995 |
+
width: 100%;
|
| 1996 |
+
height: 100%;
|
| 1997 |
+
pointer-events: none;
|
| 1998 |
+
z-index: 50;
|
| 1999 |
+
`;
|
| 2000 |
+
document.getElementById('viewer-container').appendChild(uiContainer);
|
| 2001 |
+
}
|
| 2002 |
+
}
|
| 2003 |
+
|
| 2004 |
+
function handleRenderText(textData) {
|
| 2005 |
+
ensureUIContainer();
|
| 2006 |
+
|
| 2007 |
+
const id = textData.id || textData.text_id;
|
| 2008 |
+
|
| 2009 |
+
// Remove existing element with same ID
|
| 2010 |
+
if (uiElements.has(id)) {
|
| 2011 |
+
uiContainer.removeChild(uiElements.get(id));
|
| 2012 |
+
}
|
| 2013 |
+
|
| 2014 |
+
const element = document.createElement('div');
|
| 2015 |
+
element.id = `ui-${id}`;
|
| 2016 |
+
|
| 2017 |
+
let bgStyle = '';
|
| 2018 |
+
if (textData.background_color) {
|
| 2019 |
+
bgStyle = `background-color: ${textData.background_color}; padding: ${textData.padding || 8}px; border-radius: 4px;`;
|
| 2020 |
+
}
|
| 2021 |
+
|
| 2022 |
+
element.style.cssText = `
|
| 2023 |
+
position: absolute;
|
| 2024 |
+
left: ${textData.x}%;
|
| 2025 |
+
top: ${textData.y}%;
|
| 2026 |
+
transform: translate(-50%, 0);
|
| 2027 |
+
color: ${textData.color || '#ffffff'};
|
| 2028 |
+
font-family: ${textData.font_family || 'Arial'}, sans-serif;
|
| 2029 |
+
font-size: ${textData.font_size || 24}px;
|
| 2030 |
+
text-align: ${textData.text_align || 'center'};
|
| 2031 |
+
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
| 2032 |
+
white-space: nowrap;
|
| 2033 |
+
${bgStyle}
|
| 2034 |
+
`;
|
| 2035 |
+
element.textContent = textData.text;
|
| 2036 |
+
|
| 2037 |
+
uiContainer.appendChild(element);
|
| 2038 |
+
uiElements.set(id, element);
|
| 2039 |
+
|
| 2040 |
+
console.log('📝 UI text rendered:', id);
|
| 2041 |
+
}
|
| 2042 |
+
|
| 2043 |
+
function handleRenderBar(barData) {
|
| 2044 |
+
ensureUIContainer();
|
| 2045 |
+
|
| 2046 |
+
const id = barData.id || barData.bar_id;
|
| 2047 |
+
|
| 2048 |
+
// Remove existing element with same ID
|
| 2049 |
+
if (uiElements.has(id)) {
|
| 2050 |
+
uiContainer.removeChild(uiElements.get(id));
|
| 2051 |
+
}
|
| 2052 |
+
|
| 2053 |
+
const percentage = barData.percentage ||
|
| 2054 |
+
((barData.value / barData.max_value) * 100);
|
| 2055 |
+
|
| 2056 |
+
const container = document.createElement('div');
|
| 2057 |
+
container.id = `ui-${id}`;
|
| 2058 |
+
container.style.cssText = `
|
| 2059 |
+
position: absolute;
|
| 2060 |
+
left: ${barData.x}%;
|
| 2061 |
+
top: ${barData.y}%;
|
| 2062 |
+
`;
|
| 2063 |
+
|
| 2064 |
+
// Add label if provided
|
| 2065 |
+
if (barData.label) {
|
| 2066 |
+
const label = document.createElement('div');
|
| 2067 |
+
label.style.cssText = `
|
| 2068 |
+
color: #ffffff;
|
| 2069 |
+
font-family: Arial, sans-serif;
|
| 2070 |
+
font-size: 14px;
|
| 2071 |
+
margin-bottom: 4px;
|
| 2072 |
+
text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
|
| 2073 |
+
`;
|
| 2074 |
+
label.textContent = barData.label;
|
| 2075 |
+
container.appendChild(label);
|
| 2076 |
+
}
|
| 2077 |
+
|
| 2078 |
+
// Create bar container
|
| 2079 |
+
const barContainer = document.createElement('div');
|
| 2080 |
+
barContainer.style.cssText = `
|
| 2081 |
+
width: ${barData.width || 200}px;
|
| 2082 |
+
height: ${barData.height || 20}px;
|
| 2083 |
+
background-color: ${barData.background_color || '#333333'};
|
| 2084 |
+
border: 2px solid ${barData.border_color || '#ffffff'};
|
| 2085 |
+
border-radius: 4px;
|
| 2086 |
+
overflow: hidden;
|
| 2087 |
+
position: relative;
|
| 2088 |
+
`;
|
| 2089 |
+
|
| 2090 |
+
// Create fill bar
|
| 2091 |
+
const fill = document.createElement('div');
|
| 2092 |
+
fill.style.cssText = `
|
| 2093 |
+
width: ${percentage}%;
|
| 2094 |
+
height: 100%;
|
| 2095 |
+
background-color: ${barData.bar_color || '#00ff00'};
|
| 2096 |
+
transition: width 0.3s ease;
|
| 2097 |
+
`;
|
| 2098 |
+
barContainer.appendChild(fill);
|
| 2099 |
+
|
| 2100 |
+
// Show value if requested
|
| 2101 |
+
if (barData.show_value) {
|
| 2102 |
+
const valueText = document.createElement('div');
|
| 2103 |
+
valueText.style.cssText = `
|
| 2104 |
+
position: absolute;
|
| 2105 |
+
top: 50%;
|
| 2106 |
+
left: 50%;
|
| 2107 |
+
transform: translate(-50%, -50%);
|
| 2108 |
+
color: #ffffff;
|
| 2109 |
+
font-family: Arial, sans-serif;
|
| 2110 |
+
font-size: 12px;
|
| 2111 |
+
text-shadow: 1px 1px 2px rgba(0,0,0,0.8);
|
| 2112 |
+
`;
|
| 2113 |
+
valueText.textContent = `${Math.round(barData.value)}/${Math.round(barData.max_value)}`;
|
| 2114 |
+
barContainer.appendChild(valueText);
|
| 2115 |
+
}
|
| 2116 |
+
|
| 2117 |
+
container.appendChild(barContainer);
|
| 2118 |
+
uiContainer.appendChild(container);
|
| 2119 |
+
uiElements.set(id, container);
|
| 2120 |
+
|
| 2121 |
+
console.log('📊 UI bar rendered:', id);
|
| 2122 |
+
}
|
| 2123 |
+
|
| 2124 |
+
function handleRemoveUIElement(elementId) {
|
| 2125 |
+
if (uiElements.has(elementId)) {
|
| 2126 |
+
uiContainer.removeChild(uiElements.get(elementId));
|
| 2127 |
+
uiElements.delete(elementId);
|
| 2128 |
+
console.log('🗑️ UI element removed:', elementId);
|
| 2129 |
+
}
|
| 2130 |
+
}
|
| 2131 |
+
|
| 2132 |
+
// ==================== Toon Material Handler ====================
|
| 2133 |
+
|
| 2134 |
+
function handleUpdateToonMaterial(data) {
|
| 2135 |
+
const obj = scene.children.find(child =>
|
| 2136 |
+
child.userData.id === data.object_id ||
|
| 2137 |
+
child.userData.object_id === data.object_id
|
| 2138 |
+
);
|
| 2139 |
+
|
| 2140 |
+
if (!obj) {
|
| 2141 |
+
console.error('Object not found for toon material:', data.object_id);
|
| 2142 |
+
return;
|
| 2143 |
+
}
|
| 2144 |
+
|
| 2145 |
+
if (data.enabled !== false) {
|
| 2146 |
+
// Create toon material
|
| 2147 |
+
const existingColor = obj.material?.color?.getHex() || 0xffffff;
|
| 2148 |
+
const color = data.color ? new THREE.Color(data.color) : new THREE.Color(existingColor);
|
| 2149 |
+
|
| 2150 |
+
// Create gradient texture for toon shading
|
| 2151 |
+
const steps = data.gradient_steps || 3;
|
| 2152 |
+
const gradientMap = createToonGradientMap(steps);
|
| 2153 |
+
|
| 2154 |
+
const toonMaterial = new THREE.MeshToonMaterial({
|
| 2155 |
+
color: color,
|
| 2156 |
+
gradientMap: gradientMap
|
| 2157 |
+
});
|
| 2158 |
+
|
| 2159 |
+
// Dispose old material
|
| 2160 |
+
if (obj.material) obj.material.dispose();
|
| 2161 |
+
obj.material = toonMaterial;
|
| 2162 |
+
|
| 2163 |
+
// Store toon settings in userData for reference
|
| 2164 |
+
obj.userData.toonEnabled = true;
|
| 2165 |
+
obj.userData.toonSettings = data;
|
| 2166 |
+
|
| 2167 |
+
console.log('🎨 Toon material applied to:', data.object_id);
|
| 2168 |
+
} else {
|
| 2169 |
+
// Revert to standard material
|
| 2170 |
+
const existingColor = obj.material?.color?.getHex() || 0xffffff;
|
| 2171 |
+
|
| 2172 |
+
const standardMaterial = new THREE.MeshStandardMaterial({
|
| 2173 |
+
color: existingColor,
|
| 2174 |
+
roughness: 0.7,
|
| 2175 |
+
metalness: 0.0
|
| 2176 |
+
});
|
| 2177 |
+
|
| 2178 |
+
if (obj.material) obj.material.dispose();
|
| 2179 |
+
obj.material = standardMaterial;
|
| 2180 |
+
obj.userData.toonEnabled = false;
|
| 2181 |
+
|
| 2182 |
+
console.log('🎨 Reverted to standard material:', data.object_id);
|
| 2183 |
+
}
|
| 2184 |
+
}
|
| 2185 |
+
|
| 2186 |
+
function createToonGradientMap(steps) {
|
| 2187 |
+
const canvas = document.createElement('canvas');
|
| 2188 |
+
canvas.width = steps;
|
| 2189 |
+
canvas.height = 1;
|
| 2190 |
+
const ctx = canvas.getContext('2d');
|
| 2191 |
+
|
| 2192 |
+
for (let i = 0; i < steps; i++) {
|
| 2193 |
+
const value = Math.floor((i / (steps - 1)) * 255);
|
| 2194 |
+
ctx.fillStyle = `rgb(${value},${value},${value})`;
|
| 2195 |
+
ctx.fillRect(i, 0, 1, 1);
|
| 2196 |
+
}
|
| 2197 |
+
|
| 2198 |
+
const texture = new THREE.CanvasTexture(canvas);
|
| 2199 |
+
texture.minFilter = THREE.NearestFilter;
|
| 2200 |
+
texture.magFilter = THREE.NearestFilter;
|
| 2201 |
+
|
| 2202 |
+
return texture;
|
| 2203 |
+
}
|
| 2204 |
+
|
| 2205 |
+
// ==================== Brick Block Handler ====================
|
| 2206 |
+
|
| 2207 |
+
function handleAddBrick(brickData) {
|
| 2208 |
+
if (!scene || !sceneData) {
|
| 2209 |
+
console.error('Scene not initialized yet');
|
| 2210 |
+
return;
|
| 2211 |
+
}
|
| 2212 |
+
|
| 2213 |
+
const modelPath = brickData.model_path;
|
| 2214 |
+
const position = brickData.position || { x: 0, y: 0, z: 0 };
|
| 2215 |
+
const rotation = brickData.rotation || { x: 0, y: 0, z: 0 };
|
| 2216 |
+
const color = new THREE.Color(brickData.material?.color || '#ff0000');
|
| 2217 |
+
|
| 2218 |
+
// Load the GLTF model
|
| 2219 |
+
gltfLoader.load(
|
| 2220 |
+
modelPath,
|
| 2221 |
+
(gltf) => {
|
| 2222 |
+
const model = gltf.scene;
|
| 2223 |
+
|
| 2224 |
+
// Apply position
|
| 2225 |
+
model.position.set(position.x, position.y, position.z);
|
| 2226 |
+
|
| 2227 |
+
// Apply rotation (convert degrees to radians)
|
| 2228 |
+
model.rotation.set(
|
| 2229 |
+
THREE.MathUtils.degToRad(rotation.x),
|
| 2230 |
+
THREE.MathUtils.degToRad(rotation.y),
|
| 2231 |
+
THREE.MathUtils.degToRad(rotation.z)
|
| 2232 |
+
);
|
| 2233 |
+
|
| 2234 |
+
// Apply color to all meshes in the model
|
| 2235 |
+
model.traverse((child) => {
|
| 2236 |
+
if (child.isMesh) {
|
| 2237 |
+
child.material = new THREE.MeshStandardMaterial({
|
| 2238 |
+
color: color,
|
| 2239 |
+
metalness: brickData.material?.metalness || 0.1,
|
| 2240 |
+
roughness: brickData.material?.roughness || 0.7
|
| 2241 |
+
});
|
| 2242 |
+
child.castShadow = true;
|
| 2243 |
+
child.receiveShadow = true;
|
| 2244 |
+
}
|
| 2245 |
+
});
|
| 2246 |
+
|
| 2247 |
+
// Store metadata
|
| 2248 |
+
model.userData.id = brickData.id;
|
| 2249 |
+
model.userData.type = 'brick';
|
| 2250 |
+
model.userData.brick_type = brickData.brick_type;
|
| 2251 |
+
model.userData.name = brickData.name;
|
| 2252 |
+
|
| 2253 |
+
// Add to scene
|
| 2254 |
+
scene.add(model);
|
| 2255 |
+
|
| 2256 |
+
// Add to scene data for tracking
|
| 2257 |
+
if (!sceneData.objects) sceneData.objects = [];
|
| 2258 |
+
sceneData.objects.push(brickData);
|
| 2259 |
+
|
| 2260 |
+
console.log('🧱 Brick added:', brickData.brick_type, 'at', position);
|
| 2261 |
+
},
|
| 2262 |
+
(xhr) => {
|
| 2263 |
+
console.log(`Loading brick: ${(xhr.loaded / xhr.total * 100).toFixed(0)}%`);
|
| 2264 |
+
},
|
| 2265 |
+
(error) => {
|
| 2266 |
+
console.error('Error loading brick:', error);
|
| 2267 |
+
}
|
| 2268 |
+
);
|
| 2269 |
+
}
|
| 2270 |
+
|
| 2271 |
// Start the application
|
| 2272 |
init();
|
| 2273 |
</script>
|