Spaces:
Sleeping
Sleeping
| """ | |
| 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 | |
| } | |