diff --git a/README.md b/README.md index cb954ceb..ba6685b2 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@

GPU-first editing with 32 effects, 37 blend modes, 79 AI tools, native WebGPU 3D, and only 15 runtime dependencies.
Built from scratch in 2,700+ lines of WGSL and 165k lines of TypeScript.
- Import .lottie, Lottie JSON, OBJ, glTF, GLB, PLY, SPLAT, KSPLAT, SPZ, SOG, LCC assets and play PLY / GLB sequences directly on the timeline. + Import .lottie, .riv, Lottie JSON, OBJ, glTF, GLB, PLY, SPLAT, KSPLAT, SPZ, SOG, LCC assets and play PLY / GLB sequences directly on the timeline.

@@ -55,7 +55,7 @@ Decoding depends on what the **browser** supports — the container is just the Video codecsH.264 (AVC), H.265 (HEVC)¹, VP8, VP9, AV1 Audio filesWAV, MP3, OGG, FLAC, AAC, M4A, WMA, AIFF, OPUS ImagePNG, JPG/JPEG, WebP, GIF, BMP, SVG -Vector animation.lottie packages and Lottie JSON files (content-sniffed) +Vector animation.lottie packages, .riv files, and Lottie JSON files (content-sniffed) 3D ModelsOBJ, glTF, GLB - rendered through the native WebGPU shared-scene path 3D sequencesPLY and GLB frame sequences played as timeline media Gaussian SplatsPLY, compressed PLY, SPLAT, KSPLAT, SPZ, SOG, LCC, SOG-style ZIP payloads @@ -87,7 +87,7 @@ Most browser-based video editors share a pattern: Canvas 2D compositing, heavywe **3-tier scrubbing cache.** **300 GPU textures in VRAM** for instant scrub (Tier 1), per-video last-frame cache for seek transitions (Tier 2), and a **900-frame RAM Preview** with CPU/GPU promotion (Tier 3). When the cache is warm, **scrubbing doesn't decode at all**. -**15 runtime dependencies.** React/React DOM, Zustand, MediaBunny, mp4box, PlayCanvas / splat-transform helpers, dotLottie, HuggingFace Transformers, ONNX Runtime, SoundTouch, WebGPU types, plus an **experimental FFmpeg WASM path**. **Everything else is custom-built from scratch**: the WebGPU compositor, all 32 effect shaders, the keyframe animation system, the export engine, the audio mixer, the text renderer, the mask engine, the video scope renderers, the dock/panel system, the timeline UI, and the native shared 3D scene path. Zero runtime abstraction layers between your timeline and the GPU. +**16 runtime dependencies.** React/React DOM, Zustand, MediaBunny, mp4box, PlayCanvas / splat-transform helpers, dotLottie, Rive WASM, HuggingFace Transformers, ONNX Runtime, SoundTouch, WebGPU types, plus an **experimental FFmpeg WASM path**. **Everything else is custom-built from scratch**: the WebGPU compositor, all 32 effect shaders, the keyframe animation system, the export engine, the audio mixer, the text renderer, the mask engine, the video scope renderers, the dock/panel system, the timeline UI, and the native shared 3D scene path. Zero runtime abstraction layers between your timeline and the GPU. **Nested composition rendering.** Compositions within compositions, each with their own resolution. Rendered to **pooled GPU textures** with frame-level caching, composited in the parent's ping-pong pass, all in a **single `device.queue.submit()`**. @@ -146,7 +146,7 @@ This requires the Native Helper to be running, a MasterSelects editor tab to be | [**Export Pipeline**](docs/Features/Export.md) | WebCodecs Fast/Precise, FFmpeg intermediates, image/audio-only export, FCPXML, and project-persistent presets | | [**Live EQ & Audio**](docs/Features/Audio.md) | 10-band parametric EQ with real-time Web Audio preview | | [**Download Panel**](docs/Features/Download-Panel.md) | YouTube, TikTok, Instagram, Twitter/X, Vimeo, and other yt-dlp-supported sites via Native Helper | -| [**Vector Animation**](docs/Features/Vector-Animation.md) | `.lottie` and Lottie JSON clips with bounce playback, render resolution overrides, keyframed state machines, and deterministic preview/export | +| [**Vector Animation**](docs/Features/Vector-Animation.md) | `.lottie`, `.riv`, and Lottie JSON clips with bounce playback, render resolution overrides, state-machine keyframes, Rive data binding, and preview/export | | [**Text & Solids**](docs/Features/Text-Clips.md) | 50 Google Fonts, stroke, shadow, and solid color clips | | [**Proxy System**](docs/Features/Proxy-System.md) | GPU-accelerated proxies with resume and cache indicator | | [**Output Manager**](docs/Features/Preview.md) | Multi-window outputs, source routing, corner pin warping, slice masks | @@ -324,7 +324,7 @@ src/ │ ├── nativeHelper/ # Native decoder + WebSocket client │ ├── layerBuilder/ # Layer building + video sync │ ├── mediaRuntime/ # Media runtime bindings + playback -│ ├── vectorAnimation/ # Lottie metadata sniffing + runtime canvas playback +│ ├── vectorAnimation/ # Lottie/Rive metadata + runtime canvas playback │ └── export/ # FCPXML export ├── shaders/ # WGSL (composite, effects, output, optical flow, slice) ├── hooks/ # React hooks (useEngine, useGlobalHistory, useMIDI, useTheme) diff --git a/docs/Features/Color-Correction.md b/docs/Features/Color-Correction.md index c3ff3288..38693ce2 100644 --- a/docs/Features/Color-Correction.md +++ b/docs/Features/Color-Correction.md @@ -90,7 +90,7 @@ This is the core product rule: same grade, different professional control surfac Add a `Color` tab beside `Transform`, `Effects`, and `Masks` for visual clips. -Recommended tab order for video/image/text/solid/Lottie/3D clips: +Recommended tab order for video/image/text/solid/Lottie/Rive/3D clips: ```text Transform | Color | Effects | Masks | Transcript | Analysis @@ -500,7 +500,7 @@ Integration points: - Add `color` to `PropertiesTab` in `src/components/panels/properties/index.tsx`. - Add `color-workspace` to the dock panel system when the expanded workspace is implemented. -- Insert the tab for visual clips, including text, solid, Lottie, image, video, model, gaussian avatar, and gaussian splat clips. Keep it hidden for audio-only clips and camera/controller clips. +- Insert the tab for visual clips, including text, solid, Lottie, Rive, image, video, model, gaussian avatar, and gaussian splat clips. Keep it hidden for audio-only clips and camera/controller clips. - Keep the active tab reset logic aware of `color` so switching selected clips does not bounce the user back to Transform. - Reuse existing `DraggableNumber`, `KeyframeToggle`, `MIDIParameterLabel`, and history batching patterns from `EffectsTab`. - Share list/inspector editing primitives between the Properties tab and workspace where practical. The workspace owns the large node graph layout. diff --git a/docs/Features/Export.md b/docs/Features/Export.md index 1c472b7d..8fc1a13d 100644 --- a/docs/Features/Export.md +++ b/docs/Features/Export.md @@ -43,7 +43,7 @@ FCPXML is exposed as a selectable export container for NLE interchange. `FrameExporter` is used for both the WebCodecs and HTMLVideo export buttons. -Canvas-backed sources such as text, solids, and Lottie are re-rendered for every export frame before capture, so the exported frame matches the current timeline time instead of reusing a stale first-frame texture. Motion shape clips are built as `motion` layer sources and rendered by the WebGPU motion renderer at export frame time before compositing. +Canvas-backed sources such as text, solids, Lottie, and Rive are re-rendered for every export frame before capture, so the exported frame matches the current timeline time instead of reusing a stale first-frame texture. Motion shape clips are built as `motion` layer sources and rendered by the WebGPU motion renderer at export frame time before compositing. ### Fast Mode diff --git a/docs/Features/Keyframes.md b/docs/Features/Keyframes.md index 65a1842c..8836ac80 100644 --- a/docs/Features/Keyframes.md +++ b/docs/Features/Keyframes.md @@ -83,14 +83,15 @@ The numeric mask properties use the same curve and easing behavior as transform ### Vector Animation Properties -Lottie state machines use the same keyframe store as transform and effect properties: +Vector animation state machines and Rive Data Binding use the same keyframe store as transform and effect properties: ```text lottieState.{stateMachine} lottieInput.{stateMachine}.{input} +riveData.{property} ``` -`lottieState.*` keyframes are discrete named states. They render as blue diamonds and stepped curves because a state change should hold until the next state keyframe, not ease between values. Boolean and numeric `lottieInput.*` properties use the normal stopwatch/keyframe workflow. +`lottieState.*` keyframes are discrete named states. They render as blue diamonds and stepped curves because a state change should hold until the next state keyframe, not ease between values. Boolean and numeric `lottieInput.*` properties use the normal stopwatch/keyframe workflow. Rive Data Binding properties use `riveData.*` for numeric, integer, boolean, and color values; string and enum bindings remain static clip settings. ### Motion Shape Properties @@ -177,7 +178,7 @@ When recording is enabled: - Dragging a handle updates the stored handle position and switches the keyframe to Bezier mode. - `Shift+drag` on a keyframe constrains movement to one axis in the curve editor. - Right-clicking a handle resets it to the default 1/3-distance handle for that segment. -- Lottie state keyframes show state labels on the value axis and draw stepped segments instead of Bezier curves. +- Vector animation state keyframes show state labels on the value axis and draw stepped segments instead of Bezier curves. - Mask path rows expose timing and easing in the timeline; their value is a whole shape snapshot rather than a numeric scalar. ### Delete and Copy/Paste diff --git a/docs/Features/Media-Panel.md b/docs/Features/Media-Panel.md index f7c01290..8d806377 100644 --- a/docs/Features/Media-Panel.md +++ b/docs/Features/Media-Panel.md @@ -32,14 +32,14 @@ Import, organize, and manage media assets with folder structure, proxy generatio | **Video** | MP4, WebM, MOV, AVI, MKV, WMV, M4V, FLV | | **Audio** | WAV, MP3, OGG, FLAC, AAC, M4A, WMA, AIFF, OPUS | | **Image** | PNG, JPG/JPEG, GIF, WebP, BMP, SVG | -| **Vector Animation** | `.lottie`, Lottie JSON (`.json`, content-sniffed) | +| **Vector Animation** | `.lottie`, `.riv`, Lottie JSON (`.json`, content-sniffed) | The panel also accepts a few specialized asset types that flow into the timeline as 3D clips: - `model` files: OBJ, glTF/GLB - `gaussian-splat` files: PLY, compressed PLY, SPLAT, KSPLAT, SPZ, SOG, LCC, and SOG-style ZIP payloads -Lottie imports are treated as first-class media items. `.json` files are only accepted when their contents actually match Lottie structure, so arbitrary JSON data is not misclassified as animation. +Lottie and Rive imports are treated as first-class media items. `.json` files are only accepted when their contents actually match Lottie structure, so arbitrary JSON data is not misclassified as animation. ### Import Methods @@ -462,7 +462,7 @@ interface MediaFile { ### Drag Types | Item Type | Drag Payload Kind | Data Transfer Key | |-----------|-------------------|-------------------| -| Media file (video/image/lottie) | `media-file` | `application/x-media-file-id` | +| Media file (video/image/lottie/rive) | `media-file` | `application/x-media-file-id` | | Media file (audio) | `media-file` (marked as audio) | `application/x-media-file-id` | | Composition | `composition` | `application/x-composition-id` | | Text item | `text` | `application/x-text-item-id` | @@ -481,7 +481,7 @@ interface MediaFile { ### Track Type Enforcement | Media Type | Allowed Tracks | |------------|----------------| -| Video/Image/Lottie/Composition/Text/Solid/Mesh | Video tracks only | +| Video/Image/Lottie/Rive/Composition/Text/Solid/Mesh | Video tracks only | | Audio | Audio tracks only | --- diff --git a/docs/Features/README.md b/docs/Features/README.md index 216257b3..cf6911ee 100644 --- a/docs/Features/README.md +++ b/docs/Features/README.md @@ -24,7 +24,7 @@ The docs in this folder were re-audited against the current codebase and now tra | **AI Control** | OpenAI/Cloud or local Lemonade chat with 79 exported tools plus local/native bridge access for external agents | | **AI Video Workspace** | Classic AI Video plus FlashBoard board-mode generation and media import | | **3D Layers** | Shared-scene 3D layers, camera clips, Gaussian splats, and splat effectors | -| **Vector Animation** | Lottie clips with deterministic canvas playback, bounce modes, render resolution overrides, keyframed state machines, and export | +| **Vector Animation** | Lottie and Rive clips with canvas playback, bounce modes, render resolution overrides, keyframed state/data inputs, and export | | **Audio** | Element-synced playback, drift correction, waveform extraction, EQ, and audio export | | **Project Storage** | `project.json` source of truth, RAW-copy-first media flow, autosave, relink, backups | | **Native Helper** | Firefox storage backend, yt-dlp download flow, local AI bridge, native jobs | @@ -59,7 +59,7 @@ The docs in this folder were re-audited against the current codebase and now tra | [Text Clips](./Text-Clips.md) | Canvas-backed text rendering, typography controls, and timeline text items | | [Motion Design](./Motion-Design.md) | Motion layer schema, property registry, rectangle/ellipse shape editing, GPU renderer, and persistence/export plumbing | | [3D Layers](./3D-Layers.md) | Shared-scene path, native Gaussian splats, cameras, and splat effectors | -| [Vector Animation](./Vector-Animation.md) | Lottie import, runtime playback, bounce modes, state-machine keyframes, and export behavior | +| [Vector Animation](./Vector-Animation.md) | Lottie/Rive import, runtime playback, bounce modes, state-machine keyframes, Rive data binding, and export behavior | | [Audio](./Audio.md) | Playback sync, EQ, waveform extraction, audio clip behavior, and export | | [Export](./Export.md) | WebCodecs fast/precise export, animated GIF, FFmpeg intermediates, image frame/sequence export, audio-only export, FCPXML, and project-persistent presets | | [Proxy System](./Proxy-System.md) | Proxy generation, on-disk frame layout, audio proxies, and warmup behavior | diff --git a/docs/Features/Timeline.md b/docs/Features/Timeline.md index ba351337..88d301d0 100644 --- a/docs/Features/Timeline.md +++ b/docs/Features/Timeline.md @@ -2,14 +2,14 @@ [<- Back to Index](./README.md) -The Timeline is the core editing interface for multi-track editing. It now covers video, audio, image, Lottie, text, solid, motion shape, mesh, composition, camera, and splat-effector clips, with keyframe lanes, transitions, multicam grouping, pick-whip parenting, and slot-grid playback. +The Timeline is the core editing interface for multi-track editing. It now covers video, audio, image, Lottie, Rive, text, solid, motion shape, mesh, composition, camera, and splat-effector clips, with keyframe lanes, transitions, multicam grouping, pick-whip parenting, and slot-grid playback. --- ## Track Types ### Video Tracks -- Hold video, image, Lottie, text, solid, motion shape, mesh, composition, camera, and splat-effector clips. +- Hold video, image, Lottie, Rive, text, solid, motion shape, mesh, composition, camera, and splat-effector clips. - Higher tracks render on top of lower tracks. - Expanded tracks can show keyframe property rows and curve editors. - Default layout starts with `Video 2` above `Video 1`. @@ -53,12 +53,14 @@ getTrackChildren() // Query child tracks - Created through the timeline text slice. - Supports typography, stroke, shadow, and path text. -### Lottie -- Imported from `.lottie` packages or Lottie JSON files from the Media Panel. -- Uses the same canvas-backed render path as text and solids, so preview, nested comps, and export stay deterministic. +### Vector Animation +- Lottie is imported from `.lottie` packages or Lottie JSON files from the Media Panel. +- Rive is imported from `.riv` files and rendered through the Rive WASM canvas runtime. +- Both providers use the same canvas-backed render path as text and solids, so preview, nested comps, and export stay aligned. - Exposes per-clip loop, end behavior, playback mode, fit, render resolution, animation selection, and background controls in the Properties panel. -- `.lottie` state machines can be selected in the Lottie tab, with state changes stored as blue stepped keyframes. -- Boolean and numeric `.lottie` state-machine inputs appear as normal stopwatch-keyframed properties. +- State machines can be selected in the provider tab, with state changes stored as blue stepped keyframes when state names are available. +- Boolean and numeric state-machine inputs appear as normal stopwatch-keyframed properties. +- Rive Data Binding exposes view models, instances, static string/enum values, and keyframed numeric/boolean/color values. - When loop is enabled, the clip can be extended beyond its source duration on the right trim edge without freezing on the first pass. ### Solid @@ -107,7 +109,7 @@ getTrackChildren() // Query child tracks ### Copy and Paste - Copying clips includes linked audio automatically when the video clip is selected. -- Copy/paste preserves Lottie clip type and vector animation settings. +- Copy/paste preserves vector animation clip type and vector animation settings. - Copy/paste preserves motion shape definitions. - Copying keyframes stores them relative to the earliest copied keyframe. - Pasting keyframes targets the selected clip when exactly one clip is selected; otherwise it falls back to the original clip from the clipboard data. @@ -143,7 +145,7 @@ getTrackChildren() // Query child tracks - The UI hides `rotation.x`, `rotation.y`, `position.z`, and `scale.z` for 2D clips. - Camera clips and native-render gaussian splats keep the camera-style property model visible. - Numeric effect parameters appear as `effect.{effectId}.{paramName}` lanes. -- Lottie state changes appear as `lottieState.{stateMachine}` lanes; state-machine inputs appear as `lottieInput.{stateMachine}.{input}` lanes. +- Vector animation state changes appear as `lottieState.{stateMachine}` lanes; state-machine inputs appear as `lottieInput.{stateMachine}.{input}` lanes. Rive Data Binding values appear as `riveData.{property}` lanes. - Motion shape numeric lanes use registry paths such as `shape.size.w` and `appearance.{id}.stroke.width`. - Audio EQ lanes sort `volume` and the band parameters first. @@ -162,7 +164,7 @@ getTrackChildren() // Query child tracks - Composition changes propagate into nested render data. - Selected clips can be converted into a new nested composition from the clip context menu. - Composition switches trigger clip entrance/exit animations in the timeline UI. -- Lottie clips inside nested comps render through the same canvas path used in the primary timeline and export flow. +- Vector animation clips inside nested comps render through the same canvas path used in the primary timeline and export flow. ### Transitions - Transitions operate between adjacent clips on the same track. diff --git a/docs/Features/UI-Panels.md b/docs/Features/UI-Panels.md index 7ed2ce32..7c5afe94 100644 --- a/docs/Features/UI-Panels.md +++ b/docs/Features/UI-Panels.md @@ -343,7 +343,7 @@ The unified Properties panel adapts its tabs to the selected clip type and to sl | Clip Type | Tabs | |-----------|------| -| **Lottie** | Lottie, Transform, Effects, Masks | +| **Vector Animation** | Lottie/Rive, Transform, Effects, Masks | | **Gaussian avatar** | Blendshapes, Transform, Effects, Masks | | **Gaussian splat** | Transform, Gaussian Splat, Effects, Masks | | **Camera** | Transform | diff --git a/docs/Features/Vector-Animation.md b/docs/Features/Vector-Animation.md index 6edfa995..9c557e01 100644 --- a/docs/Features/Vector-Animation.md +++ b/docs/Features/Vector-Animation.md @@ -2,9 +2,7 @@ # Vector Animation -Vector animation clips currently ship through the Lottie path. `.lottie` packages and Lottie JSON files import as first-class media items, render through the same timeline/export pipeline as other clips, and expose clip-specific controls in the Properties panel. - -`rive` is still only a reserved type in the data model. It is not wired into import, runtime playback, or export yet. +Vector animation clips support Lottie and Rive as first-class media items. `.lottie`, Lottie JSON, and `.riv` files import into the Media panel, render through the same timeline/export pipeline as other clips, and expose clip-specific controls in the Properties panel. --- @@ -12,15 +10,16 @@ Vector animation clips currently ship through the Lottie path. `.lottie` package - `.lottie` packages - Lottie JSON files when the JSON structure is positively identified as a Lottie animation +- `.riv` Rive files via `@rive-app/canvas` -The import path does not treat arbitrary `.json` files as animation. Files are sniffed first, then promoted to `type: 'lottie'` only when the payload matches expected Lottie structure. +The import path does not treat arbitrary `.json` files as animation. Files are sniffed first, then promoted to `type: 'lottie'` only when the payload matches expected Lottie structure. Rive imports are extension-based and use `type: 'rive'`. --- ## Timeline Behavior -- Lottie clips live on video tracks. -- The clip bar shows an `L` badge in the timeline. +- Vector animation clips live on video tracks. +- The clip bar shows an `L` badge for Lottie and an `R` badge for Rive. - `naturalDuration`, frame rate, dimensions, animation names, and other vector metadata are extracted during import. - Loop-enabled clips can be extended beyond their source duration on the right trim edge. - Copy/paste, nested compositions, slot decks, and background-layer playback preserve the clip type and vector animation settings. @@ -29,7 +28,7 @@ The import path does not treat arbitrary `.json` files as animation. Files are s ## Properties Panel -Lottie clips add a dedicated `Lottie` tab in the unified Properties panel. +Vector animation clips add a dedicated provider tab in the unified Properties panel: `Lottie` for Lottie clips and `Rive` for Rive clips. Current controls: @@ -38,10 +37,14 @@ Current controls: - Playback mode: `forward`, `reverse`, `bounce`, or `reverse-bounce` - Fit: `contain`, `cover`, or `fill` - Render resolution override with fallback to the imported animation size -- Animation picker when a `.lottie` package exposes multiple animations -- State Machine picker when a `.lottie` package exposes state machines +- Rive artboard picker when the file exposes multiple artboards +- Animation picker when the file exposes multiple animations +- State Machine picker when the file exposes state machines - State override plus stepped state keyframes for discrete timeline-driven state changes - Boolean and numeric state-machine inputs as normal stopwatch keyframe properties +- Rive view model and instance picker for Data Binding +- Rive boolean, numeric, integer, and color Data Binding properties as stopwatch keyframe properties +- Rive string and enum Data Binding properties as static clip settings - Background color override The tab also shows the clip name plus imported width, height, and frame rate metadata when available. @@ -50,7 +53,10 @@ The tab also shows the clip name plus imported width, height, and frame rate met ## Rendering -Lottie playback is driven by `src/services/vectorAnimation/LottieRuntimeManager.ts`. +Runtime playback is split by provider and routed through `src/services/vectorAnimation/VectorAnimationRuntimeManager.ts`. + +- Lottie playback is driven by `src/services/vectorAnimation/LottieRuntimeManager.ts`. +- Rive playback is driven by `src/services/vectorAnimation/RiveRuntimeManager.ts` using `@rive-app/canvas`. - Each clip gets a dedicated runtime canvas. - The runtime canvas can use the imported animation size or the clip-level render resolution override. @@ -58,6 +64,9 @@ Lottie playback is driven by `src/services/vectorAnimation/LottieRuntimeManager. - Bounce modes are resolved in the timeline-time mapping, so preview and export render the same ping-pong frames. - If a state machine is selected, `lottieState.{stateMachine}` keyframes resolve the active state at the current timeline time before the frame is rendered. - If state-machine inputs are keyframed, the interpolated input values are applied before the frame is rendered. +- Rive Data Binding values use `riveData.{property}` keyframes for numeric, boolean, integer, and color properties and are applied before draw. +- Rive Events are subscribed through `EventType.RiveEvent` with automatic event side effects disabled. Events are logged for debugging rather than opening URLs or running implicit browser actions. +- Rive runtime asset loading keeps the Rive CDN fallback enabled and leaves a custom asset-loader hook in place for future project-local asset resolution. - The runtime canvas is marked as dynamic, so `TextureManager` re-uploads it every frame instead of caching only the first frame. - The same canvas-backed source flows through preview, nested comps, slot/background playback, thumbnails, and export. @@ -71,11 +80,11 @@ Saved data includes: - media-level vector metadata - clip-level `vectorAnimationSettings` -- Lottie playback mode, render resolution, state machine selection, static state override, state keyframes, and state-machine input values -- serialized timeline clip type `lottie` +- playback mode, render resolution, artboard, state machine selection, static state override, state keyframes, state-machine input values, view model selection, and Data Binding values +- serialized timeline clip type `lottie` or `rive` - clipboard payloads and nested-composition clip data -On project load, the app restores the Lottie clip metadata from project data and recreates the runtime from the file, the copied `Raw/` media, or a recovered file handle. +On project load, the app restores vector animation metadata from project data and recreates the runtime from the file, the copied `Raw/` media, or a recovered file handle. If a retained `File` object still exists after refresh but the browser object URL is dead, the Media panel regenerates the missing URL and image/video thumbnail automatically. @@ -83,21 +92,21 @@ If a retained `File` object still exists after refresh but the browser object UR ## Export -Lottie export does not use a separate renderer. +Vector animation export does not use a separate renderer. - The export layer builder asks the runtime for the correct frame at the current export time. - That frame is composited through the normal GPU path with effects, transforms, masks, nested comps, and other layers. - Output is rasterized into the final render like any other canvas-backed source. -This keeps Lottie clips deterministic in fast preview, precise export, and image export. +This keeps vector animation clips aligned in fast preview, precise export, and image export. --- ## Current Limits -- Only Lottie is implemented today. Rive is not. -- State machine support currently targets `.lottie` packages through `@lottiefiles/dotlottie-web`; Rive state machines are still not wired. -- Boolean and numeric state-machine inputs are exposed as keyframe controls. String inputs are static for now, and trigger/event inputs are not deterministic timeline controls yet. +- Rive state machines use the public high-level WASM runtime. Input values are timeline-driven, but state-machine internal progression is limited by the high-level runtime API. +- Boolean and numeric state-machine inputs are exposed as keyframe controls. String inputs are static for Lottie, and trigger/event inputs are not deterministic timeline controls yet. +- Rive image/font/audio asset loading currently relies on embedded assets or the Rive CDN fallback. Project-local asset binding is a future extension point. - State selection uses stepped `lottieState.{stateMachine}` keyframes rather than bezier curves because named states are discrete strings. - Export output is rasterized; there is no vector-native export target. - If no `Raw/` copy or file handle is available after reload, the clip still needs the normal relink flow. diff --git a/package-lock.json b/package-lock.json index 3f009099..47924526 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@huggingface/transformers": "^3.8.1", "@lottiefiles/dotlottie-web": "^0.71.0", "@playcanvas/splat-transform": "^1.10.1", + "@rive-app/canvas": "^2.37.7", "@webgpu/types": "^0.1.66", "fflate": "^0.8.2", "gifenc": "^1.0.3", @@ -2113,6 +2114,12 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "license": "BSD-3-Clause" }, + "node_modules/@rive-app/canvas": { + "version": "2.37.7", + "resolved": "https://registry.npmjs.org/@rive-app/canvas/-/canvas-2.37.7.tgz", + "integrity": "sha512-dS2W4igbETc3zxWhDO8x8wyB8HrtZCG48ofODBijHbV5lINYTz8Xx3P1mHtK/ywzKWhaSSAI95QTz9FqJG1ghQ==", + "license": "MIT" + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.47", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz", diff --git a/package.json b/package.json index 5e8c897a..634953f3 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@huggingface/transformers": "^3.8.1", "@lottiefiles/dotlottie-web": "^0.71.0", "@playcanvas/splat-transform": "^1.10.1", + "@rive-app/canvas": "^2.37.7", "@webgpu/types": "^0.1.66", "fflate": "^0.8.2", "gifenc": "^1.0.3", diff --git a/src/components/panels/properties/LottieTab.tsx b/src/components/panels/properties/LottieTab.tsx index 9c1b73e7..87ccb79c 100644 --- a/src/components/panels/properties/LottieTab.tsx +++ b/src/components/panels/properties/LottieTab.tsx @@ -4,17 +4,24 @@ import { useMediaStore } from '../../../stores/mediaStore'; import { useTimelineStore } from '../../../stores/timeline'; import { DEFAULT_VECTOR_ANIMATION_CLIP_SETTINGS, + coerceVectorAnimationDataBindingValue, coerceVectorAnimationInputValue, + createVectorAnimationDataBindingProperty, createVectorAnimationInputProperty, createVectorAnimationStateProperty, + getVectorAnimationDataBindingDefaultValue, getVectorAnimationInputNumericValue, getVectorAnimationStateIndex, + isVectorAnimationSourceType, normalizeVectorAnimationRenderDimension, normalizeVectorAnimationStateCues, normalizeVectorAnimationStateName, + vectorAnimationDataBindingValueToNumber, vectorAnimationInputValueToNumber, type VectorAnimationPlaybackMode, type VectorAnimationClipSettings, + type VectorAnimationDataBindingProperty, + type VectorAnimationDataBindingValue, type VectorAnimationStateMachineInput, type VectorAnimationStateMachineInputValue, } from '../../../types/vectorAnimation'; @@ -49,10 +56,31 @@ function formatInputType(input: VectorAnimationStateMachineInput): string { return 'Trigger'; } +function formatDataBindingType(property: VectorAnimationDataBindingProperty): string { + if (property.type === 'boolean') return 'Bool'; + if (property.type === 'integer') return 'Integer'; + if (property.type === 'number') return 'Number'; + if (property.type === 'color') return 'Color'; + if (property.type === 'enum') return 'Enum'; + if (property.type === 'string') return 'Text'; + return 'Trigger'; +} + function formatDimensionValue(value: number | undefined): string { return value === undefined ? '' : String(value); } +function riveColorToHex(value: VectorAnimationDataBindingValue | undefined): string { + const numericValue = vectorAnimationDataBindingValueToNumber(value); + const rgb = numericValue & 0xffffff; + return `#${rgb.toString(16).padStart(6, '0')}`; +} + +function hexToRiveColor(value: string): number { + const normalized = /^#[0-9a-f]{6}$/i.test(value) ? value.slice(1) : '000000'; + return 0xff000000 | Number.parseInt(normalized, 16); +} + export function LottieTab({ clipId }: LottieTabProps) { const clip = useTimelineStore((state) => state.clips.find((current) => current.id === clipId)); const playheadPosition = useTimelineStore((state) => state.playheadPosition); @@ -69,11 +97,13 @@ export function LottieTab({ clipId }: LottieTabProps) { ? files.find((file) => file.id === clip.source?.mediaFileId) : undefined; const metadata = mediaFile?.vectorAnimation; + const providerName = metadata?.provider === 'rive' ? 'Rive' : 'Lottie'; const settings: VectorAnimationClipSettings = { ...DEFAULT_VECTOR_ANIMATION_CLIP_SETTINGS, ...clip?.source?.vectorAnimationSettings, }; const animationNames = metadata?.animationNames ?? []; + const artboardNames = metadata?.artboardNames ?? []; const stateMachineNames = metadata?.stateMachineNames ?? []; const selectedStateMachineName = settings.stateMachineName ?? ''; const stateMachineStateNames = selectedStateMachineName @@ -113,11 +143,17 @@ export function LottieTab({ clipId }: LottieTabProps) { : settings; const currentStateName = liveSettings.stateMachineState ?? settings.stateMachineState ?? stateMachineStateNames[0] ?? ''; const currentStateIndex = getVectorAnimationStateIndex(stateMachineStateNames, currentStateName); + const viewModels = metadata?.viewModels ?? []; + const selectedViewModelName = settings.viewModelName ?? metadata?.defaultViewModelName ?? viewModels[0]?.name ?? ''; + const selectedViewModel = selectedViewModelName + ? viewModels.find((viewModel) => viewModel.name === selectedViewModelName) + : undefined; + const dataBindingProperties = selectedViewModel?.properties ?? []; const updateSettings = useCallback((updates: Partial) => { const { clips } = useTimelineStore.getState(); const current = clips.find((candidate) => candidate.id === clipId); - if (!current?.source || current.source.type !== 'lottie') { + if (!current?.source || !isVectorAnimationSourceType(current.source.type)) { return; } @@ -247,6 +283,45 @@ export function LottieTab({ clipId }: LottieTabProps) { ); }; + const getDataBindingValue = ( + property: VectorAnimationDataBindingProperty, + ): VectorAnimationDataBindingValue => ( + coerceVectorAnimationDataBindingValue( + property, + liveSettings.dataBindingValues?.[property.name] ?? + settings.dataBindingValues?.[property.name] ?? + getVectorAnimationDataBindingDefaultValue(property), + ) + ); + + const updateDataBindingValue = ( + property: VectorAnimationDataBindingProperty, + value: VectorAnimationDataBindingValue, + ) => { + if (property.type === 'trigger') { + return; + } + + const normalizedValue = coerceVectorAnimationDataBindingValue(property, value); + if (property.type === 'string' || property.type === 'enum') { + updateSettings({ + viewModelName: selectedViewModelName || settings.viewModelName, + dataBindingValues: { + ...(settings.dataBindingValues ?? {}), + [property.name]: normalizedValue, + }, + }); + return; + } + + const propertyPath = createVectorAnimationDataBindingProperty(property.name); + setPropertyValue( + clipId, + propertyPath as AnimatableProperty, + vectorAnimationDataBindingValueToNumber(normalizedValue), + ); + }; + const commitRenderDimensions = (draft: Pick = resolutionDraft) => { const width = normalizeVectorAnimationRenderDimension(Number(draft.width)); const height = normalizeVectorAnimationRenderDimension(Number(draft.height)); @@ -276,7 +351,7 @@ export function LottieTab({ clipId }: LottieTabProps) { } }; - if (!clip || clip.source?.type !== 'lottie') { + if (!clip || !isVectorAnimationSourceType(clip.source?.type)) { return null; } @@ -287,7 +362,7 @@ export function LottieTab({ clipId }: LottieTabProps) {

{clip.name}
- {metadata?.width && metadata?.height ? `${metadata.width} x ${metadata.height}` : 'Canvas-backed animation'} + {metadata?.width && metadata?.height ? `${metadata.width} x ${metadata.height}` : `${providerName} canvas animation`} {metadata?.fps ? ` - ${metadata.fps.toFixed(2)} fps` : ''}
@@ -364,6 +439,33 @@ export function LottieTab({ clipId }: LottieTabProps) { + {artboardNames.length > 0 && ( + + )} + {animationNames.length > 0 && (