GameContextProtocol / backend /tools /rendering_tools.py
ArturoNereu's picture
Added tools, and improved player movement
5b29015
"""
Rendering & Lighting Tools
Fine-grained control over scene lighting, materials, background, fog,
post-processing effects, and camera effects.
"""
from typing import Dict, Any, Optional
from backend.storage import storage
def add_light(
scene_id: str,
light_type: str,
name: str,
color: str = "#ffffff",
intensity: float = 1.0,
position: Optional[Dict[str, float]] = None,
target: Optional[Dict[str, float]] = None,
cast_shadow: bool = False,
spot_angle: Optional[float] = None
) -> Dict[str, Any]:
"""
Implementation: Add a new light source to the scene
Args:
scene_id: ID of the scene
light_type: "ambient" | "directional" | "point" | "spot"
name: Light identifier (e.g., "Torch1", "MainLight")
color: Hex color (default: "#ffffff")
intensity: Brightness 0.0-2.0 (default: 1.0)
position: Position for directional/point/spot lights
target: Target position for directional/spot lights
cast_shadow: Enable shadows (default: False)
spot_angle: Cone angle in degrees (spot lights only)
Returns:
Dictionary with light details and message
"""
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene '{scene_id}' not found")
# Validate light type
valid_types = ["ambient", "directional", "point", "spot"]
if light_type not in valid_types:
raise ValueError(f"Invalid light_type '{light_type}'. Must be one of: {valid_types}")
# Check for duplicate name
if "lights" not in scene:
scene["lights"] = []
for light in scene["lights"]:
if light.get("name") == name:
raise ValueError(f"Light with name '{name}' already exists. Use update_light() to modify it.")
# Create light object
light_obj = {
"name": name,
"light_type": light_type,
"color": color,
"intensity": intensity,
"cast_shadow": cast_shadow
}
# Add position for non-ambient lights
if light_type != "ambient":
if position:
light_obj["position"] = position
else:
# Default positions
defaults = {
"directional": {"x": 50, "y": 50, "z": 50},
"point": {"x": 0, "y": 5, "z": 0},
"spot": {"x": 0, "y": 5, "z": 0}
}
light_obj["position"] = defaults.get(light_type, {"x": 0, "y": 5, "z": 0})
# Add target for directional/spot lights
if light_type in ["directional", "spot"] and target:
light_obj["target"] = target
# Add spot angle for spot lights
if light_type == "spot":
light_obj["spot_angle"] = spot_angle if spot_angle else 45.0
scene["lights"].append(light_obj)
storage.save(scene)
return {
"scene_id": scene_id,
"message": f"Added {light_type} light '{name}' to scene",
"light": light_obj
}
def remove_light(scene_id: str, light_name: str) -> Dict[str, Any]:
"""
Implementation: Remove a light from the scene
Args:
scene_id: ID of the scene
light_name: Name of light to remove
Returns:
Dictionary with confirmation message
"""
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene '{scene_id}' not found")
if "lights" not in scene or not scene["lights"]:
raise ValueError(f"Scene has no lights to remove")
# Find and remove light
original_count = len(scene["lights"])
scene["lights"] = [light for light in scene["lights"] if light.get("name") != light_name]
if len(scene["lights"]) == original_count:
raise ValueError(f"Light '{light_name}' not found in scene")
storage.save(scene)
return {
"scene_id": scene_id,
"message": f"Removed light '{light_name}' from scene",
"light_name": light_name
}
def update_light(
scene_id: str,
light_name: str,
color: Optional[str] = None,
intensity: Optional[float] = None,
position: Optional[Dict[str, float]] = None,
cast_shadow: Optional[bool] = None
) -> Dict[str, Any]:
"""
Implementation: Update existing light properties
Args:
scene_id: ID of the scene
light_name: Name of light to update
color: New color (optional)
intensity: New intensity (optional)
position: New position (optional)
cast_shadow: Enable/disable shadows (optional)
Returns:
Dictionary with updated light and message
"""
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene '{scene_id}' not found")
if "lights" not in scene or not scene["lights"]:
raise ValueError(f"Scene has no lights")
# Find light
light = None
for l in scene["lights"]:
if l.get("name") == light_name:
light = l
break
if not light:
raise ValueError(f"Light '{light_name}' not found in scene")
# Update properties
updated_props = []
if color is not None:
light["color"] = color
updated_props.append(f"color={color}")
if intensity is not None:
light["intensity"] = intensity
updated_props.append(f"intensity={intensity}")
if position is not None:
light["position"] = position
updated_props.append(f"position={position}")
if cast_shadow is not None:
light["cast_shadow"] = cast_shadow
updated_props.append(f"shadows={'on' if cast_shadow else 'off'}")
storage.save(scene)
return {
"scene_id": scene_id,
"message": f"Updated light '{light_name}': {', '.join(updated_props)}",
"light": light
}
def get_lights(scene_id: str) -> Dict[str, Any]:
"""
Implementation: Get all lights in the scene
Args:
scene_id: ID of the scene
Returns:
Dictionary with list of all lights
"""
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene '{scene_id}' not found")
lights = scene.get("lights", [])
return {
"scene_id": scene_id,
"lights": lights,
"count": len(lights)
}
def update_object_material(
scene_id: str,
object_id: str,
color: Optional[str] = None,
metalness: Optional[float] = None,
roughness: Optional[float] = None,
opacity: Optional[float] = None,
emissive: Optional[str] = None,
emissive_intensity: Optional[float] = None
) -> Dict[str, Any]:
"""
Implementation: Update an object's material properties
Args:
scene_id: ID of the scene
object_id: Object to update
color: Hex color (optional)
metalness: 0.0-1.0 (optional)
roughness: 0.0-1.0 (optional)
opacity: 0.0-1.0 (optional)
emissive: Emissive color for glow (optional)
emissive_intensity: Glow strength (optional)
Returns:
Dictionary with updated material and message
"""
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene '{scene_id}' not found")
if "objects" not in scene or not scene["objects"]:
raise ValueError(f"Scene has no objects")
# Find object
obj = None
for o in scene["objects"]:
if o.get("object_id") == object_id:
obj = o
break
if not obj:
raise ValueError(f"Object '{object_id}' not found in scene")
# Ensure material exists
if "material" not in obj:
obj["material"] = {}
# Update material properties
updated_props = []
if color is not None:
obj["material"]["color"] = color
updated_props.append(f"color={color}")
if metalness is not None:
obj["material"]["metalness"] = max(0.0, min(1.0, metalness))
updated_props.append(f"metalness={metalness}")
if roughness is not None:
obj["material"]["roughness"] = max(0.0, min(1.0, roughness))
updated_props.append(f"roughness={roughness}")
if opacity is not None:
obj["material"]["opacity"] = max(0.0, min(1.0, opacity))
updated_props.append(f"opacity={opacity}")
if emissive is not None:
obj["material"]["emissive"] = emissive
updated_props.append(f"emissive={emissive}")
if emissive_intensity is not None:
obj["material"]["emissive_intensity"] = emissive_intensity
updated_props.append(f"emissive_intensity={emissive_intensity}")
storage.save(scene)
return {
"scene_id": scene_id,
"object_id": object_id,
"message": f"Updated material: {', '.join(updated_props)}",
"material": obj["material"]
}
def set_background_color(
scene_id: str,
color: Optional[str] = None,
bg_type: str = "solid",
gradient_top: Optional[str] = None,
gradient_bottom: Optional[str] = None
) -> Dict[str, Any]:
"""
Implementation: Set scene background color
Args:
scene_id: ID of the scene
color: Hex color for solid background
bg_type: "solid" | "gradient"
gradient_top: Top color for gradient
gradient_bottom: Bottom color for gradient
Returns:
Dictionary with background settings and message
"""
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene '{scene_id}' not found")
if "environment" not in scene:
scene["environment"] = {}
if bg_type == "gradient":
if not gradient_top or not gradient_bottom:
raise ValueError("gradient_top and gradient_bottom are required for gradient backgrounds")
scene["environment"]["background_type"] = "gradient"
scene["environment"]["background_gradient_top"] = gradient_top
scene["environment"]["background_gradient_bottom"] = gradient_bottom
message = f"Set background to gradient: {gradient_top}{gradient_bottom}"
else:
if not color:
raise ValueError("color is required for solid backgrounds")
scene["environment"]["background_type"] = "solid"
scene["environment"]["background_color"] = color
message = f"Set background to {color}"
storage.save(scene)
return {
"scene_id": scene_id,
"message": message,
"background": scene["environment"]
}
def set_fog(
scene_id: str,
enabled: bool,
color: Optional[str] = None,
near: Optional[float] = None,
far: Optional[float] = None,
density: Optional[float] = None
) -> Dict[str, Any]:
"""
Implementation: Set atmospheric fog
Args:
scene_id: ID of the scene
enabled: Enable/disable fog
color: Fog color (default: "#aaaaaa")
near: Start distance for linear fog
far: End distance for linear fog
density: Density for exponential fog
Returns:
Dictionary with fog settings and message
"""
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene '{scene_id}' not found")
if "environment" not in scene:
scene["environment"] = {}
if "fog" not in scene["environment"]:
scene["environment"]["fog"] = {}
fog = scene["environment"]["fog"]
fog["enabled"] = enabled
if enabled:
fog["color"] = color if color else "#aaaaaa"
# Determine fog type
if density is not None:
fog["type"] = "exponential"
fog["density"] = density
message = f"Enabled exponential fog (density={density}, color={fog['color']})"
elif near is not None and far is not None:
fog["type"] = "linear"
fog["near"] = near
fog["far"] = far
message = f"Enabled linear fog (near={near}, far={far}, color={fog['color']})"
else:
# Default linear fog
fog["type"] = "linear"
fog["near"] = 10
fog["far"] = 50
message = f"Enabled linear fog (near=10, far=50, color={fog['color']})"
else:
message = "Disabled fog"
storage.save(scene)
return {
"scene_id": scene_id,
"message": message,
"fog": fog
}
# =============================================================================
# Post-Processing Tools
# =============================================================================
def set_bloom(
scene_id: str,
enabled: bool,
strength: float = 1.0,
radius: float = 0.4,
threshold: float = 0.8
) -> Dict[str, Any]:
"""
Configure bloom (glow) post-processing effect.
Bloom creates a glow effect around bright areas of the scene,
simulating how cameras capture bright light sources.
Args:
scene_id: ID of the scene
enabled: Enable/disable bloom
strength: Bloom intensity (0.0-3.0, default: 1.0)
radius: Bloom spread/blur radius (0.0-1.0, default: 0.4)
threshold: Brightness threshold to trigger bloom (0.0-1.0, default: 0.8)
Returns:
Dictionary with bloom settings and message
"""
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene '{scene_id}' not found")
if "post_processing" not in scene:
scene["post_processing"] = {}
bloom = {
"enabled": enabled,
"strength": max(0.0, min(3.0, strength)),
"radius": max(0.0, min(1.0, radius)),
"threshold": max(0.0, min(1.0, threshold))
}
scene["post_processing"]["bloom"] = bloom
storage.save(scene)
if enabled:
message = f"Enabled bloom (strength={strength}, radius={radius}, threshold={threshold})"
else:
message = "Disabled bloom"
return {
"scene_id": scene_id,
"message": message,
"bloom": bloom
}
def set_ssao(
scene_id: str,
enabled: bool,
radius: float = 0.5,
intensity: float = 1.0,
bias: float = 0.025
) -> Dict[str, Any]:
"""
Configure Screen Space Ambient Occlusion (SSAO).
SSAO adds soft shadows in corners and crevices where ambient light
would naturally be occluded, adding depth and realism.
Args:
scene_id: ID of the scene
enabled: Enable/disable SSAO
radius: Sample radius in world units (0.1-2.0, default: 0.5)
intensity: Shadow intensity (0.0-2.0, default: 1.0)
bias: Depth bias to prevent self-occlusion (0.001-0.1, default: 0.025)
Returns:
Dictionary with SSAO settings and message
"""
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene '{scene_id}' not found")
if "post_processing" not in scene:
scene["post_processing"] = {}
ssao = {
"enabled": enabled,
"radius": max(0.1, min(2.0, radius)),
"intensity": max(0.0, min(2.0, intensity)),
"bias": max(0.001, min(0.1, bias))
}
scene["post_processing"]["ssao"] = ssao
storage.save(scene)
if enabled:
message = f"Enabled SSAO (radius={radius}, intensity={intensity})"
else:
message = "Disabled SSAO"
return {
"scene_id": scene_id,
"message": message,
"ssao": ssao
}
def set_color_grading(
scene_id: str,
enabled: bool,
brightness: float = 0.0,
contrast: float = 1.0,
saturation: float = 1.0,
hue: float = 0.0,
exposure: float = 1.0,
gamma: float = 1.0
) -> Dict[str, Any]:
"""
Configure color grading post-processing.
Adjust overall image colors for cinematic looks or stylized effects.
Args:
scene_id: ID of the scene
enabled: Enable/disable color grading
brightness: Brightness adjustment (-1.0 to 1.0, default: 0.0)
contrast: Contrast multiplier (0.0-2.0, default: 1.0)
saturation: Color saturation (0.0=grayscale, 1.0=normal, 2.0=vivid)
hue: Hue shift in degrees (-180 to 180, default: 0)
exposure: Exposure adjustment (0.0-3.0, default: 1.0)
gamma: Gamma correction (0.5-2.5, default: 1.0)
Returns:
Dictionary with color grading settings and message
"""
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene '{scene_id}' not found")
if "post_processing" not in scene:
scene["post_processing"] = {}
color_grading = {
"enabled": enabled,
"brightness": max(-1.0, min(1.0, brightness)),
"contrast": max(0.0, min(2.0, contrast)),
"saturation": max(0.0, min(2.0, saturation)),
"hue": max(-180, min(180, hue)),
"exposure": max(0.0, min(3.0, exposure)),
"gamma": max(0.5, min(2.5, gamma))
}
scene["post_processing"]["color_grading"] = color_grading
storage.save(scene)
if enabled:
adjustments = []
if brightness != 0.0:
adjustments.append(f"brightness={brightness}")
if contrast != 1.0:
adjustments.append(f"contrast={contrast}")
if saturation != 1.0:
adjustments.append(f"saturation={saturation}")
if hue != 0.0:
adjustments.append(f"hue={hue}°")
if exposure != 1.0:
adjustments.append(f"exposure={exposure}")
if gamma != 1.0:
adjustments.append(f"gamma={gamma}")
if adjustments:
message = f"Enabled color grading ({', '.join(adjustments)})"
else:
message = "Enabled color grading (default settings)"
else:
message = "Disabled color grading"
return {
"scene_id": scene_id,
"message": message,
"color_grading": color_grading
}
def set_vignette(
scene_id: str,
enabled: bool,
intensity: float = 0.5,
smoothness: float = 0.5
) -> Dict[str, Any]:
"""
Configure vignette effect (darkened edges).
Vignette darkens the corners and edges of the screen,
drawing focus to the center of the image.
Args:
scene_id: ID of the scene
enabled: Enable/disable vignette
intensity: Darkness of the vignette (0.0-1.0, default: 0.5)
smoothness: Softness of the vignette edge (0.0-1.0, default: 0.5)
Returns:
Dictionary with vignette settings and message
"""
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene '{scene_id}' not found")
if "post_processing" not in scene:
scene["post_processing"] = {}
vignette = {
"enabled": enabled,
"intensity": max(0.0, min(1.0, intensity)),
"smoothness": max(0.0, min(1.0, smoothness))
}
scene["post_processing"]["vignette"] = vignette
storage.save(scene)
if enabled:
message = f"Enabled vignette (intensity={intensity}, smoothness={smoothness})"
else:
message = "Disabled vignette"
return {
"scene_id": scene_id,
"message": message,
"vignette": vignette
}
def get_post_processing(scene_id: str) -> Dict[str, Any]:
"""
Get all post-processing settings for the scene.
Args:
scene_id: ID of the scene
Returns:
Dictionary with all post-processing settings
"""
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene '{scene_id}' not found")
post_processing = scene.get("post_processing", {})
return {
"scene_id": scene_id,
"post_processing": post_processing
}
# =============================================================================
# Camera Effects Tools
# =============================================================================
def set_depth_of_field(
scene_id: str,
enabled: bool,
focus_distance: float = 10.0,
aperture: float = 0.025,
max_blur: float = 0.01
) -> Dict[str, Any]:
"""
Configure depth of field (DoF) camera effect.
Depth of field blurs objects that are not at the focus distance,
simulating how real camera lenses focus on a specific plane.
Args:
scene_id: ID of the scene
enabled: Enable/disable depth of field
focus_distance: Distance to the focal plane in units (default: 10.0)
aperture: Aperture size, affects blur amount (0.001-0.1, default: 0.025)
max_blur: Maximum blur strength (0.0-0.05, default: 0.01)
Returns:
Dictionary with DoF settings and message
"""
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene '{scene_id}' not found")
if "camera_effects" not in scene:
scene["camera_effects"] = {}
dof = {
"enabled": enabled,
"focus_distance": max(0.1, focus_distance),
"aperture": max(0.001, min(0.1, aperture)),
"max_blur": max(0.0, min(0.05, max_blur))
}
scene["camera_effects"]["depth_of_field"] = dof
storage.save(scene)
if enabled:
message = f"Enabled depth of field (focus={focus_distance}m, aperture={aperture})"
else:
message = "Disabled depth of field"
return {
"scene_id": scene_id,
"message": message,
"depth_of_field": dof
}
def set_motion_blur(
scene_id: str,
enabled: bool,
intensity: float = 0.5,
samples: int = 8
) -> Dict[str, Any]:
"""
Configure motion blur camera effect.
Motion blur adds blur in the direction of camera or object movement,
creating a sense of speed and smooth motion.
Args:
scene_id: ID of the scene
enabled: Enable/disable motion blur
intensity: Blur intensity (0.0-2.0, default: 0.5)
samples: Quality samples for blur (4-32, default: 8)
Returns:
Dictionary with motion blur settings and message
"""
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene '{scene_id}' not found")
if "camera_effects" not in scene:
scene["camera_effects"] = {}
motion_blur = {
"enabled": enabled,
"intensity": max(0.0, min(2.0, intensity)),
"samples": max(4, min(32, samples))
}
scene["camera_effects"]["motion_blur"] = motion_blur
storage.save(scene)
if enabled:
message = f"Enabled motion blur (intensity={intensity}, samples={samples})"
else:
message = "Disabled motion blur"
return {
"scene_id": scene_id,
"message": message,
"motion_blur": motion_blur
}
def set_chromatic_aberration(
scene_id: str,
enabled: bool,
intensity: float = 0.005
) -> Dict[str, Any]:
"""
Configure chromatic aberration effect.
Chromatic aberration simulates lens imperfection by separating
color channels at the edges of the screen.
Args:
scene_id: ID of the scene
enabled: Enable/disable chromatic aberration
intensity: Effect strength (0.0-0.05, default: 0.005)
Returns:
Dictionary with chromatic aberration settings and message
"""
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene '{scene_id}' not found")
if "camera_effects" not in scene:
scene["camera_effects"] = {}
chromatic = {
"enabled": enabled,
"intensity": max(0.0, min(0.05, intensity))
}
scene["camera_effects"]["chromatic_aberration"] = chromatic
storage.save(scene)
if enabled:
message = f"Enabled chromatic aberration (intensity={intensity})"
else:
message = "Disabled chromatic aberration"
return {
"scene_id": scene_id,
"message": message,
"chromatic_aberration": chromatic
}
def get_camera_effects(scene_id: str) -> Dict[str, Any]:
"""
Get all camera effects settings for the scene.
Args:
scene_id: ID of the scene
Returns:
Dictionary with all camera effects settings
"""
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene '{scene_id}' not found")
camera_effects = scene.get("camera_effects", {})
return {
"scene_id": scene_id,
"camera_effects": camera_effects
}
# =============================================================================
# Toon/Cel Shading Tools
# =============================================================================
def update_material_to_toon(
scene_id: str,
object_id: str,
enabled: bool = True,
color: Optional[str] = None,
gradient_steps: int = 3,
outline: bool = True,
outline_color: str = "#000000",
outline_thickness: float = 0.03
) -> Dict[str, Any]:
"""
Convert an object's material to toon/cel-shaded style.
Toon shading creates a cartoon-like appearance with discrete shading bands
instead of smooth gradients, commonly used in anime and cel-animation styles.
Args:
scene_id: ID of the scene
object_id: ID of the object to update
enabled: Enable/disable toon shading (True to apply, False to revert to standard)
color: Base color for toon material (optional, keeps existing if not set)
gradient_steps: Number of shading steps (2=hard, 3=medium, 5=soft, default: 3)
outline: Add black outline effect (default: True)
outline_color: Color of the outline (default: "#000000")
outline_thickness: Thickness of outline (0.01-0.1, default: 0.03)
Returns:
Dictionary with updated material info and message
"""
scene = storage.get(scene_id)
if not scene:
raise ValueError(f"Scene '{scene_id}' not found")
if "objects" not in scene or not scene["objects"]:
raise ValueError("Scene has no objects")
# Find object
obj = None
for o in scene["objects"]:
if o.get("id") == object_id or o.get("object_id") == object_id:
obj = o
break
if not obj:
raise ValueError(f"Object '{object_id}' not found in scene")
# Ensure material exists
if "material" not in obj:
obj["material"] = {}
# Update toon properties
if enabled:
obj["material"]["toon"] = {
"enabled": True,
"gradient_steps": max(2, min(10, gradient_steps)),
"outline": outline,
"outline_color": outline_color,
"outline_thickness": max(0.01, min(0.1, outline_thickness))
}
if color:
obj["material"]["color"] = color
message = f"Applied toon shading to '{object_id}' ({gradient_steps} steps{', with outline' if outline else ''})"
else:
obj["material"]["toon"] = {"enabled": False}
message = f"Disabled toon shading on '{object_id}' (reverted to standard material)"
storage.save(scene)
return {
"scene_id": scene_id,
"object_id": object_id,
"message": message,
"material": obj["material"]
}