--- name: composable-svelte-graphics description: 3D graphics and WebGPU/WebGL rendering with Composable Svelte. Use when building 3D scenes, working with cameras, lights, meshes, materials, or implementing WebGPU/WebGL graphics. Covers Scene, Camera, Light, Mesh components, geometry types (box, sphere, cylinder, torus, plane), material properties, and state-driven 3D rendering. --- # Composable Svelte Graphics State-driven 3D graphics for Composable Svelte using WebGPU/WebGL with Babylon.js. --- ## PACKAGE OVERVIEW **Package**: `@composable-svelte/graphics` **Purpose**: Declarative 3D graphics with automatic WebGPU/WebGL renderer selection. **Technology Stack**: - **Babylon.js**: Industry-standard 3D engine - **WebGPU**: Modern high-performance graphics API (auto-detected) - **WebGL**: Fallback for broader browser support **Renderer Selection**: 1. Try WebGPU (if browser supports it) 2. Fallback to WebGL (universal support) 3. Transparent to the user **Core Components**: - `Scene` - Root container, manages renderer lifecycle - `Camera` - Viewpoint and projection - `Light` - Illumination (ambient, directional, point, spot) - `Mesh` - 3D objects with geometry and materials **State Management**: - `graphicsReducer` - Pure reducer for all graphics state - `createInitialGraphicsState()` - Initial state factory - Store-driven updates sync automatically to Babylon.js --- ## QUICK START ```typescript import { createStore } from '@composable-svelte/core'; import { Scene, Camera, Light, Mesh, graphicsReducer, createInitialGraphicsState } from '@composable-svelte/graphics'; // Create graphics store const store = createStore({ initialState: createInitialGraphicsState({ backgroundColor: '#1a1a2e' }), reducer: graphicsReducer, dependencies: {} }); // Track rotation for animation let rotation = $state(0); function rotateShapes() { rotation += Math.PI / 4; } // Render 3D scene ``` --- ## SCENE COMPONENT **Purpose**: Root container for 3D rendering. Manages Babylon.js engine lifecycle and syncs store state to the renderer. **Props**: - `store: Store` - Graphics store (required) - `width: string | number` - Canvas width (default: '100%') - `height: string | number` - Canvas height (default: '600px') - `children: Snippet` - Child components (Camera, Light, Mesh) **Behavior**: 1. Creates canvas element 2. Initializes Babylon.js engine 3. Attempts WebGPU, falls back to WebGL 4. Dispatches `rendererInitialized` action with capabilities 5. Syncs store updates to Babylon.js scene 6. Cleans up engine on unmount **Usage**: ```typescript ``` **State Synchronization**: Scene uses manual subscription (like MapPrimitive) to avoid infinite loops: - Tracks previous state values - Only updates Babylon.js when state actually changes - Uses JSON.stringify for deep comparison **Renderer Info**: Access renderer info from store: ```typescript $store.renderer.activeRenderer // 'webgpu' | 'webgl' $store.renderer.isInitialized // boolean $store.renderer.capabilities // { maxTextureSize, ... } $store.renderer.error // string | null ``` --- ## CAMERA COMPONENT **Purpose**: Defines the viewpoint and projection for the scene. **Props**: - `store: Store` - Graphics store (required) - `type: 'perspective' | 'orthographic'` - Camera type (default: 'perspective') - `position: [number, number, number]` - Camera position (required) - `lookAt: [number, number, number]` - Target point to look at (required) - `fov: number` - Field of view in degrees (default: 45, perspective only) - `near: number` - Near clipping plane (optional) - `far: number` - Far clipping plane (optional) **Behavior**: - Dispatches `updateCamera` action on mount - Re-dispatches when props change - Does not render visual output (state-only component) **Usage**: ```typescript ``` **Common Camera Positions**: - Front view: `position={[0, 0, 10]}, lookAt={[0, 0, 0]}` - Top-down: `position={[0, 10, 0]}, lookAt={[0, 0, 0]}` - Isometric: `position={[5, 5, 5]}, lookAt={[0, 0, 0]}` - Close-up: `position={[0, 2, 5]}, lookAt={[0, 1, 0]}` --- ## LIGHT COMPONENT **Purpose**: Add illumination to the scene. Supports multiple light types. **Light Types**: ### Ambient Light Uniform light from all directions (no position/direction). **Props**: - `type: 'ambient'` - `intensity: number` - Light intensity (0-1 typical, can exceed) - `color: string` - Light color (hex or CSS color, default: '#ffffff') **Usage**: ```typescript ``` ### Directional Light Parallel rays from a specific direction (like sunlight). **Props**: - `type: 'directional'` - `position: [number, number, number]` - Light position (defines direction) - `intensity: number` - Light intensity - `color: string` - Light color (optional) **Usage**: ```typescript ``` ### Point Light Emits light in all directions from a point (like a light bulb). **Props**: - `type: 'point'` - `position: [number, number, number]` - Light position - `intensity: number` - Light intensity - `radius: number` - Light radius/range (optional) - `color: string` - Light color (optional) **Usage**: ```typescript ``` ### Spot Light Cone-shaped light (like a flashlight). **Props**: - `type: 'spot'` - `position: [number, number, number]` - Light position - `direction: [number, number, number]` - Light direction vector - `angle: number` - Cone angle in radians (default: Math.PI / 4) - `intensity: number` - Light intensity - `color: string` - Light color (optional) **Usage**: ```typescript ``` **Common Lighting Setups**: ```typescript ``` --- ## MESH COMPONENT **Purpose**: Render 3D objects with geometry and materials. **Props**: - `store: Store` - Graphics store (required) - `id: string` - Unique identifier (required) - `geometry: GeometryConfig` - Geometry configuration (required) - `material: MaterialConfig` - Material configuration (required) - `position: [number, number, number]` - Position (required) - `rotation: [number, number, number]` - Rotation in radians (default: [0, 0, 0]) - `scale: [number, number, number]` - Scale (default: [1, 1, 1]) - `visible: boolean` - Visibility (default: true) **Lifecycle**: - `onMount`: Dispatches `addMesh` action - Props change: Dispatches `updateMesh` action - `onDestroy`: Dispatches `removeMesh` action **Usage**: ```typescript ``` --- ## GEOMETRY TYPES ### Box Rectangular prism. **Config**: ```typescript { type: 'box'; size: number } ``` **Example**: ```typescript ``` ### Sphere Spherical geometry. **Config**: ```typescript { type: 'sphere'; radius: number; segments?: number; // Default: 32 (higher = smoother) } ``` **Example**: ```typescript ``` **Segments**: Higher values create smoother spheres but increase draw calls. - Low poly (16 segments): Retro/stylized look - Medium (32 segments): Default, good balance - High poly (64 segments): Smooth, more expensive ### Cylinder Cylindrical geometry. **Config**: ```typescript { type: 'cylinder'; height: number; diameter: number; } ``` **Example**: ```typescript ``` ### Torus Donut-shaped geometry. **Config**: ```typescript { type: 'torus'; diameter: number; // Outer diameter thickness: number; // Tube thickness segments?: number; // Default: 32 } ``` **Example**: ```typescript ``` ### Plane Flat rectangular surface. **Config**: ```typescript { type: 'plane'; width: number; height: number; } ``` **Example**: ```typescript ``` **Note**: Planes are initially vertical (facing Z-axis). Rotate by `[Math.PI / 2, 0, 0]` to make horizontal (ground). --- ## MATERIAL PROPERTIES **MaterialConfig Interface**: ```typescript interface MaterialConfig { color: string; // Hex or CSS color metallic?: number; // 0-1 (default: 0.5) roughness?: number; // 0-1 (default: 0.5) emissive?: string; // Emissive color (optional) alpha?: number; // 0-1 transparency (optional) wireframe?: boolean; // Wireframe mode (default: false) } ``` **PBR Workflow**: Materials use Physically Based Rendering (PBR) with metallic/roughness workflow. ### Metallic (0-1) Controls how metal-like the surface appears. - `0.0`: Non-metallic (plastic, rubber, wood, stone) - `0.5`: Semi-metallic (painted metal, worn surfaces) - `1.0`: Fully metallic (polished metal, chrome) **Examples**: ```typescript // Plastic { color: '#ff0000', metallic: 0.0, roughness: 0.5 } // Painted metal { color: '#4ecdc4', metallic: 0.5, roughness: 0.4 } // Polished chrome { color: '#ffffff', metallic: 1.0, roughness: 0.1 } ``` ### Roughness (0-1) Controls surface smoothness/reflectivity. - `0.0`: Mirror-smooth (glossy, high reflections) - `0.5`: Semi-rough (satin finish) - `1.0`: Very rough (matte, diffuse) **Examples**: ```typescript // Glass/mirror { color: '#ffffff', metallic: 0.0, roughness: 0.0 } // Satin finish { color: '#ff6b6b', metallic: 0.3, roughness: 0.5 } // Matte rubber { color: '#333333', metallic: 0.0, roughness: 1.0 } ``` ### Common Material Presets ```typescript // Polished gold const gold = { color: '#ffd700', metallic: 1.0, roughness: 0.2 }; // Brushed aluminum const aluminum = { color: '#c0c0c0', metallic: 1.0, roughness: 0.4 }; // Copper const copper = { color: '#b87333', metallic: 1.0, roughness: 0.3 }; // Wood const wood = { color: '#8b4513', metallic: 0.0, roughness: 0.8 }; // Plastic const plastic = { color: '#ff6b6b', metallic: 0.0, roughness: 0.4 }; // Stone const stone = { color: '#808080', metallic: 0.0, roughness: 0.9 }; // Rubber const rubber = { color: '#1a1a1a', metallic: 0.0, roughness: 1.0 }; ``` --- ## COMPLETE EXAMPLE Full scene with all geometry types: ```typescript
{#if $store.renderer.isInitialized} Renderer: {$store.renderer.activeRenderer?.toUpperCase()} Max Texture: {$store.renderer.capabilities.maxTextureSize}px {:else if $store.renderer.error} Error: {$store.renderer.error} {:else} Initializing... {/if}
``` --- ## STATE MANAGEMENT ### GraphicsState Interface ```typescript interface GraphicsState { // Renderer renderer: { activeRenderer: 'webgpu' | 'webgl' | null; isInitialized: boolean; capabilities: { supportsWebGPU: boolean; supportsWebGL: boolean; maxTextureSize: number; maxVertexAttributes: number; }; error: string | null; }; // Scene scene: SceneNode; backgroundColor: string; // Camera camera: CameraConfig; // Lights lights: LightConfig[]; // Meshes meshes: MeshConfig[]; // Animations (future) animations: AnimationState[]; // Loading isLoading: boolean; loadingProgress: number; // 0-1 } ``` ### GraphicsAction Types ```typescript type GraphicsAction = // Renderer | { type: 'rendererInitialized'; renderer: 'webgpu' | 'webgl'; capabilities: RendererCapabilities } | { type: 'rendererError'; error: string } // Camera | { type: 'updateCamera'; camera: Partial } | { type: 'setCameraPosition'; position: Vector3 } | { type: 'setCameraLookAt'; lookAt: Vector3 } // Mesh | { type: 'addMesh'; mesh: MeshConfig } | { type: 'removeMesh'; id: string } | { type: 'updateMesh'; id: string; updates: Partial } | { type: 'setMeshPosition'; id: string; position: Vector3 } | { type: 'setMeshRotation'; id: string; rotation: Vector3 } | { type: 'setMeshScale'; id: string; scale: Vector3 } | { type: 'toggleMeshVisibility'; id: string } // Light | { type: 'addLight'; light: LightConfig } | { type: 'removeLight'; index: number } | { type: 'updateLight'; index: number; light: Partial } // Scene | { type: 'setBackgroundColor'; color: string } | { type: 'clearScene' }; ``` ### Reducer Pattern Graphics reducer is pure and testable: ```typescript import { graphicsReducer } from '@composable-svelte/graphics'; import { TestStore } from '@composable-svelte/core'; const store = new TestStore({ initialState: createInitialGraphicsState(), reducer: graphicsReducer, dependencies: {} }); // Add mesh await store.send({ type: 'addMesh', mesh: { id: 'test-cube', geometry: { type: 'box', size: 1 }, material: { color: '#ff0000' }, position: [0, 0, 0] } }, (state) => { expect(state.meshes.length).toBe(1); expect(state.meshes[0].id).toBe('test-cube'); }); // Update position await store.send({ type: 'setMeshPosition', id: 'test-cube', position: [1, 2, 3] }, (state) => { expect(state.meshes[0].position).toEqual([1, 2, 3]); }); ``` --- ## PERFORMANCE CONSIDERATIONS ### Geometry Complexity **Segments**: Higher segment counts create smoother geometry but increase draw calls. ```typescript // Low poly (fast, retro look) geometry={{ type: 'sphere', radius: 1, segments: 16 }} // Default (good balance) geometry={{ type: 'sphere', radius: 1, segments: 32 }} // High poly (slow, smooth) geometry={{ type: 'sphere', radius: 1, segments: 64 }} ``` ### Draw Calls Each mesh = 1 draw call. Minimize meshes for better performance. **Good**: ```typescript // 3 meshes = 3 draw calls ``` **Bad**: ```typescript // 1000 meshes = 1000 draw calls (very slow!) {#each items as item} {/each} ``` **Solution**: Use instancing for many similar objects (future feature). ### Update Frequency Scene sync uses deep comparison (JSON.stringify). Avoid updating mesh props every frame. **Good**: ```typescript // Update rotation only when button clicked let rotation = $state(0); function rotate() { rotation += Math.PI / 4; } ``` **Bad**: ```typescript // Updates every frame (60 FPS) - expensive! let time = $state(0); setInterval(() => { time += 0.01; }, 16); ``` **Solution**: Use animation system (future feature) or throttle updates. --- ## COMMON PATTERNS ### Rotation Animation ```typescript let rotation = $state(0); function rotateObject() { rotation += Math.PI / 4; // 45 degrees } ``` ### Camera Controls ```typescript let cameraDistance = $state(12); function zoomIn() { cameraDistance = Math.max(5, cameraDistance - 2); } function zoomOut() { cameraDistance = Math.min(20, cameraDistance + 2); } ``` ### Dynamic Lighting ```typescript let lightIntensity = $state(1.0); function adjustBrightness(delta: number) { lightIntensity = Math.max(0, Math.min(2, lightIntensity + delta)); } ``` ### Toggle Visibility ```typescript let showObject = $state(true); // Option 1: Conditional rendering {#if showObject} {/if} // Option 2: Visible prop ``` --- ## FUTURE FEATURES These features are planned but not yet implemented: ### Custom Shaders ```typescript // Future API ``` ### Textures ```typescript // Future API ``` ### Animations ```typescript // Future API ``` ### Post-Processing ```typescript // Future API ... ``` --- ## CROSS-REFERENCES **Related Skills**: - **composable-svelte-core**: Store, reducer, Effect system - **composable-svelte-components**: UI components that complement 3D scenes - **composable-svelte-testing**: TestStore for testing graphics reducers **When to Use Each Package**: - **graphics**: 3D scenes, WebGPU/WebGL rendering - **charts**: 2D data visualization (see composable-svelte-charts) - **maps**: Geospatial data (see composable-svelte-maps) - **code**: Code editors, syntax highlighting (see composable-svelte-code) --- ## TROUBLESHOOTING **Scene not rendering**: - Check browser WebGPU/WebGL support - Verify store is created with `graphicsReducer` - Check console for renderer errors in `$store.renderer.error` **Objects not visible**: - Ensure Camera is pointing at objects (`lookAt` prop) - Add at least one Light (scene is dark by default) - Check mesh `visible` prop - Verify position values (objects might be off-screen) **Poor performance**: - Reduce segment counts on spheres/toruses - Minimize number of meshes (each mesh = 1 draw call) - Avoid updating mesh props every frame - Use simpler geometry (box vs sphere) **TypeScript errors**: - Ensure `@composable-svelte/graphics` is installed - Check geometry config matches type (e.g., `box` requires `size`, not `radius`) - Verify Vector3 arrays are exactly 3 numbers `[x, y, z]`