GameContextProtocol / backend /tools /scene_tools.py
ArturoNereu's picture
Improvements
0614dda
"""
Scene Tools
Create, modify, and query 3D scenes
"""
from typing import Optional, Dict, Any
from backend.game_models import (
create_scene,
create_game_object,
create_light,
create_environment,
create_vector3,
create_material,
)
from backend.storage import storage
def generate_viewer_url(scene_id: str, base_url: str = "http://localhost:8000") -> str:
"""Generate viewer URL for a scene"""
return f"{base_url}/view/scene/{scene_id}"
def create_game_scene(
name: str = "New Scene",
description: Optional[str] = None,
world_width: float = 100.0,
world_height: float = 100.0,
world_depth: float = 100.0,
lighting_preset: str = "day",
base_url: str = "http://localhost:8000"
) -> Dict[str, Any]:
"""
Create a new 3D scene
Args:
name: Scene name
description: Scene description
world_width: Width of the world
world_height: Height of the world
world_depth: Depth of the world
lighting_preset: Lighting preset (day, night, sunset, studio)
base_url: Base URL for the deployed space
Returns:
Dict with scene_id, viewer_url, and message
"""
# Create default lights based on preset
lights = []
if lighting_preset == "day":
lights = [
create_light(
name="Sun",
light_type="directional",
color="#ffffff",
intensity=1.0,
position=create_vector3(50, 50, 50),
),
create_light(
name="Ambient",
light_type="ambient",
color="#ffffff",
intensity=0.5,
),
]
elif lighting_preset == "night":
lights = [
create_light(
name="Moon",
light_type="directional",
color="#6699cc",
intensity=0.3,
position=create_vector3(-50, 50, -50),
),
create_light(
name="Ambient",
light_type="ambient",
color="#1a1a3a",
intensity=0.2,
),
]
# Create environment
env = create_environment(lighting_preset=lighting_preset)
if lighting_preset == "night":
env["background_color"] = "#0a0a1a"
elif lighting_preset == "sunset":
env["background_color"] = "#ff6b35"
# Create scene
scene = create_scene(
name=name,
description=description,
world_width=world_width,
world_height=world_height,
world_depth=world_depth,
lights=lights,
environment=env,
)
# Save to storage
storage.save(scene)
# Generate viewer URL
viewer_url = generate_viewer_url(scene["scene_id"], base_url)
return {
"scene_id": scene["scene_id"],
"viewer_url": viewer_url,
"message": f"Created scene '{scene['name']}' with 10x10 world (white ground plane and boundary walls)",
}
def add_game_object(
scene_id: str,
object_type: str = "cube",
name: Optional[str] = None,
position: Optional[Dict[str, float]] = None,
rotation: Optional[Dict[str, float]] = None,
scale: Optional[Dict[str, float]] = None,
material: Optional[Dict[str, Any]] = None,
model_path: Optional[str] = None,
base_url: str = "http://localhost:8000"
) -> Dict[str, Any]:
"""
Add an object to the scene
Args:
scene_id: ID of the scene
object_type: Type of object (cube, sphere, cylinder, etc.)
name: Object name
position: Position vector {x, y, z}
rotation: Rotation vector {x, y, z}
scale: Scale vector {x, y, z}
material: Material properties dict
model_path: Path to 3D model file
base_url: Base URL for the deployed space
Returns:
Dict with object_id, scene_id, viewer_url, and message
"""
# Get existing scene
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene with ID '{scene_id}' not found")
# Validate position is within 10x10 world bounds (-5 to 5 in X and Z)
if position:
x = position.get('x', 0)
z = position.get('z', 0)
WORLD_HALF = 5.0
if abs(x) > WORLD_HALF or abs(z) > WORLD_HALF:
raise ValueError(
f"Object position ({x}, {z}) is outside the 10x10 world bounds. "
f"X and Z must be between -{WORLD_HALF} and {WORLD_HALF}."
)
# Create game object
obj = create_game_object(
object_type=object_type,
name=name or f"{object_type}_{len(scene['objects'])}",
position=position,
rotation=rotation,
scale=scale,
material=material,
model_path=model_path,
)
# Add to scene
scene["objects"].append(obj)
# Save updated scene
storage.save(scene)
# Generate viewer URL
viewer_url = generate_viewer_url(scene["scene_id"], base_url)
pos = obj["position"]
return {
"object_id": obj["id"],
"scene_id": scene["scene_id"],
"viewer_url": viewer_url,
"message": f"Added {obj['name']} ({object_type}) at position ({pos['x']}, {pos['y']}, {pos['z']})",
}
def remove_game_object(
scene_id: str,
object_id: str,
base_url: str = "http://localhost:8000"
) -> Dict[str, Any]:
"""
Remove an object from the scene
Args:
scene_id: ID of the scene
object_id: ID of the object to remove
base_url: Base URL for the deployed space
Returns:
Dict with scene_id, viewer_url, and message
"""
# Get existing scene
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene with ID '{scene_id}' not found")
# Find and remove object
original_count = len(scene["objects"])
scene["objects"] = [obj for obj in scene["objects"] if obj["id"] != object_id]
if len(scene["objects"]) == original_count:
raise ValueError(f"Object with ID '{object_id}' not found in scene")
# Save updated scene
storage.save(scene)
# Generate viewer URL
viewer_url = generate_viewer_url(scene["scene_id"], base_url)
return {
"scene_id": scene["scene_id"],
"viewer_url": viewer_url,
"message": f"Removed object {object_id}",
}
def set_scene_lighting(
scene_id: str,
preset: str = "day",
base_url: str = "http://localhost:8000"
) -> Dict[str, Any]:
"""
Set lighting preset for the scene
Args:
scene_id: ID of the scene
preset: Lighting preset (day, night, sunset, studio)
base_url: Base URL for the deployed space
Returns:
Dict with scene_id, viewer_url, and message
"""
# Get existing scene
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene with ID '{scene_id}' not found")
# Update lighting preset
scene["environment"]["lighting_preset"] = preset
# Update lights based on preset
if preset == "day":
scene["lights"] = [
create_light(
name="Sun",
light_type="directional",
color="#ffffff",
intensity=1.0,
position=create_vector3(50, 50, 50),
),
create_light(
name="Ambient",
light_type="ambient",
color="#ffffff",
intensity=0.5,
),
]
scene["environment"]["background_color"] = "#87CEEB"
elif preset == "night":
scene["lights"] = [
create_light(
name="Moon",
light_type="directional",
color="#6699cc",
intensity=0.3,
position=create_vector3(-50, 50, -50),
),
create_light(
name="Ambient",
light_type="ambient",
color="#1a1a3a",
intensity=0.2,
),
]
scene["environment"]["background_color"] = "#0a0a1a"
elif preset == "sunset":
scene["lights"] = [
create_light(
name="Sun",
light_type="directional",
color="#ff6b35",
intensity=0.8,
position=create_vector3(100, 20, 50),
),
create_light(
name="Ambient",
light_type="ambient",
color="#ff9966",
intensity=0.4,
),
]
scene["environment"]["background_color"] = "#ff6b35"
# Save updated scene
storage.save(scene)
# Generate viewer URL
viewer_url = generate_viewer_url(scene["scene_id"], base_url)
return {
"scene_id": scene["scene_id"],
"viewer_url": viewer_url,
"message": f"Set lighting to {preset}",
}
def get_scene_info(
scene_id: str,
base_url: str = "http://localhost:8000"
) -> Dict[str, Any]:
"""
Get information about a scene
Args:
scene_id: ID of the scene to retrieve
base_url: Base URL for the deployed space
Returns:
Dict with scene details including objects and lights
"""
# Get scene
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene with ID '{scene_id}' not found")
# Generate viewer URL
viewer_url = generate_viewer_url(scene["scene_id"], base_url)
# Format object list
objects_info = [
{
"id": obj["id"],
"name": obj["name"],
"type": obj["type"],
"position": obj["position"],
"color": obj["material"]["color"],
}
for obj in scene["objects"]
]
return {
"scene_id": scene["scene_id"],
"name": scene["name"],
"viewer_url": viewer_url,
"object_count": len(scene["objects"]),
"light_count": len(scene["lights"]),
"world_bounds": {
"width": scene["world_width"],
"height": scene["world_height"],
"depth": scene["world_depth"],
},
"objects": objects_info,
}
# Valid brick types from Kenney brick kit
BRICK_TYPES = {
"brick_1x1": "brick_1x1.glb",
"brick_1x2": "brick_1x2.glb",
"brick_1x4": "brick_1x4.glb",
"brick_2x2": "brick_2x2.glb",
"brick_2x4": "brick_2x4.glb",
"plate_1x2": "plate_1x2.glb",
"plate_2x2": "plate_2x2.glb",
"plate_2x4": "plate_2x4.glb",
"plate_4x4": "plate_4x4.glb",
"slope_2x2": "slope_2x2.glb",
}
def add_brick(
scene_id: str,
brick_type: str = "brick_2x4",
position: Optional[Dict[str, float]] = None,
rotation: Optional[Dict[str, float]] = None,
color: str = "#ff0000",
name: Optional[str] = None,
base_url: str = "http://localhost:8000"
) -> Dict[str, Any]:
"""
Add a LEGO-style brick from the Kenney brick kit.
Args:
scene_id: ID of the scene
brick_type: Type of brick - brick_1x1, brick_1x2, brick_1x4, brick_2x2, brick_2x4,
plate_1x2, plate_2x2, plate_2x4, plate_4x4, slope_2x2
position: Position {x, y, z} (default: {0, 0, 0})
rotation: Rotation in degrees {x, y, z} (default: {0, 0, 0})
color: Hex color code for the brick (default: #ff0000 red)
name: Optional name for the brick
base_url: Base URL for the deployed space
Returns:
Dictionary with brick info and message
"""
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene '{scene_id}' not found")
if brick_type not in BRICK_TYPES:
raise ValueError(f"Invalid brick_type '{brick_type}'. Valid types: {list(BRICK_TYPES.keys())}")
# Default position
if position is None:
position = {"x": 0, "y": 0, "z": 0}
# Default rotation
if rotation is None:
rotation = {"x": 0, "y": 0, "z": 0}
# Validate position is within world bounds
x = position.get('x', 0)
z = position.get('z', 0)
WORLD_HALF = 5.0
if abs(x) > WORLD_HALF or abs(z) > WORLD_HALF:
raise ValueError(
f"Brick position ({x}, {z}) is outside the 10x10 world bounds. "
f"X and Z must be between -{WORLD_HALF} and {WORLD_HALF}."
)
# Generate unique ID
brick_id = f"brick_{len(scene['objects'])}_{brick_type}"
# Generate name if not provided
if not name:
name = f"{brick_type.replace('_', ' ').title()}"
# Model path relative to models folder (static_base_url will be prepended by frontend)
model_path = f"kenney/brick_kit/{BRICK_TYPES[brick_type]}"
brick_obj = {
"id": brick_id,
"type": "brick",
"brick_type": brick_type,
"name": name,
"position": position,
"rotation": rotation,
"scale": {"x": 1, "y": 1, "z": 1},
"model_path": model_path,
"material": {
"color": color,
"metalness": 0.1,
"roughness": 0.7
}
}
scene["objects"].append(brick_obj)
storage.save(scene)
return {
"scene_id": scene_id,
"brick_id": brick_id,
"brick_type": brick_type,
"message": f"Added {name} at ({position['x']}, {position['y']}, {position['z']})",
"brick": brick_obj
}