Get Started with Aeion Motion Studio
Create your first interactive 3D scene in 5 steps. AI mesh generation, ARKit lip-sync animation, Rapier WASM physics, and real-time data binding — no WebGL expertise required.
Create a Motion Scene
| Rig | Description | Variables |
|---|---|---|
| Neural Globe | Holographic KPI sphere | glowIntensity, ringRadius, ringColor |
| Holographic Card | Glass panel with metrics | opacity, borderGlow, textColor |
| Liquid Bar | 3D animated progress | fillLevel, fluidColor, bubbleCount |
| Product Spin | Interactive product model | rotationSpeed, zoomLevel, colorSwatch |
| Custom | Empty scene, import glTF | Any custom variables |
Generate a 3D Model with AI
| Input | Use Case | Output |
|---|---|---|
| Text prompt | "low-poly geometric fox", "floating crystal" | glTF/GLB model |
| Reference image | Upload product photo | 3D reconstruction |
| Sketch | Hand-drawn concept | Cleaned 3D mesh |
Create Lip-Sync Animation
Generate a per-frame viseme lip-sync track from an audio file — a deterministic amplitude-envelope analyzer returns a jawOpen weight plus a coarse viseme hint for each frame. (Full ARKit 52-channel expansion is a downstream, rig-specific concern handled by the character-rendering layer that consumes this track — it is not part of the lip-sync API's own output.)
> GA across the board. Audio→viseme lip-sync is GA via a deterministic amplitude-envelope generator, and text-to-animation is GA via a deterministic procedural baseline that maps an action vocabulary (walk/run/wave/jump/nod/bow/turn…) to real Mixamo-standard keyframe tracks — both have learned-model swap-in paths behind the same API for higher fidelity / free-form prompts. Step 2 AI 3D mesh generation is GA; Step 4 Rapier physics compilation (source generation) is GA with the compiled-Wasm-binary edge runtime rolling out.
Generate Lip-Sync (API):
bash
POST /motion/lip-sync
{
"audioUrl": "https://cdn.example.com/audio/ai-narrator.mp3"
}
Response:
typescript
{
"data": {
"success": true,
"blendshapesData": {
"method": "amplitude_envelope_v1",
"frameRate": 60,
"frameCount": 750,
"durationMs": 12500,
"frames": [
{ "t": 0, "jawOpen": 0.0, "viseme": "sil" },
{ "t": 17, "jawOpen": 0.62, "viseme": "aa" }
]
},
"animationId": "lipsync_1715364000000"
}
}
Blendshape Output Structure:
```typescript interface LipSyncFrame { t: number; // ms from start jawOpen: number; // mouth-open weight, 0.0 (closed) – 1.0 (wide open) viseme: string; // coarse viseme hint (e.g. "sil", "aa", "ee", "oo") }
// Frame 0: t=0, jawOpen=0.0, viseme="sil" // Frame 1: t=17, jawOpen=0.62, viseme="aa" // ... up to 750 frames (12.5s × 60fps) ```
Connect to AI Avatar (via Connect module):
bash
# Use with Connect neural voice agent
POST /connect/agents/{agentId}/assign-lipsync
{
"animationId": "lipsync_1715364000000",
"blendshapeRig": "arkit_52"
}
# Avatar animates in sync with AI-generated voice
Generate Text-to-Animation (API):
bash
POST /motion/generate-animation
{
"prompt": "A character waving with a friendly gesture",
"targetRig": "mixamo_standard"
}
Response:
typescript
{
"data": {
"success": true,
"animationData": { /* skeletal keyframe data */ },
"animationId": "anim_1715364000000"
}
}
Target Rigs: mixamo_standard, mixamo_fbx, custom
Compile Physics (Rapier)
| Type | Behavior | Use Case |
|---|---|---|
| `static` | Immovable, infinite mass | Ground, walls, platforms |
| `rigid` | Dynamic, affected by gravity | Balls, boxes, falling objects |
| `kinematic` | Moved by script, no gravity | Moving platforms, elevators |
| Type | Description | Use Case |
| ----------- | ------------------------ | ------------------ |
| `revolute` | Rotation around one axis | Doors, hinges |
| `prismatic` | Slide along one axis | Elevators, sliders |
| `ball` | Full rotation freedom | Ragdoll joints |
| `fixed` | Rigid connection | Welded parts |
| Environment | Target latency | Use Case |
| ------------- | -------------- | ----------------------- |
| `browser` | ~5ms | Client-side WebAssembly |
| `edge_worker` | ~1ms | CDN-edge execution |
Bind Live Data & Publish
| Rig | Variable | Data Source |
|---|---|---|
| Neural Globe | glowIntensity | `finance.mrr` |
| Neural Globe | ringRadius | `finance.target` |
| Liquid Bar | fillLevel | `crm.pipeline_value / 1000000` |
| Holographic Card | borderGlow | `helpdesk.open_tickets` (red if > 10) |
| Product Spin | colorSwatch | `commerce_products.colors[selected]` |
Quick Reference
Scene API:
```bash # Create scene (standard collection endpoint) POST /api/v1/motion_scenes { "title": "...", "status": "draft" }
# Publish scene POST /motion/scenes/{id}/publish
# Unpublish scene POST /motion/scenes/{id}/unpublish
# List scenes (standard collection endpoint) GET /api/v1/motion_scenes ```
3D Generation API:
```bash # Generate 3D mesh from text POST /motion/generate-3d-mesh { "prompt": "..." }
# Generate from image POST /motion/generate-3d-mesh { "prompt": "...", "sourceImageUrl": "https://..." }
# Generate lip-sync POST /motion/lip-sync { "audioUrl": "https://..." }
# Generate animation POST /motion/generate-animation { "prompt": "...", "targetRig": "mixamo_standard" }
# Compile physics POST /motion/compile-physics { "sceneData": {...}, "targetEnvironment": "browser" } ```
Embed Formats:
```html <!-- iframe --> <iframe src="https://app.aeionos.com/embed/motion/{embedToken}"></iframe>
<!-- Script embed --> <div id="aeion-motion-scene" data-scene="{embedToken}"></div> <script src="https://app.aeionos.com/motion/embed.js" defer></script> ```
tsx
// React
import { MotionScene } from "@aeion/motion";
<MotionScene embedToken="{embedToken}" />;
TipTap: insert a Motion block from the block picker in the page editor — no shortcode syntax.
Embed Tokens: A unique, opaque token is generated on every publish.
FAQ
Send a text prompt (and optionally a reference image) to the generate-3D-mesh endpoint. It returns a ready-to-use model URL plus a token you can reuse later. Supports text prompts and reference images.
A per-frame viseme track at 60fps (`method: "amplitude_envelope_v1"`) — a JSON array of frames, each carrying a timestamp, a `jawOpen` weight (0.0-1.0), and a coarse viseme hint (e.g. "sil", "aa"), plus frame count and duration. It's a deterministic amplitude-envelope baseline (same audio in, same track out). Full ARKit 52-channel expansion is a downstream, rig-specific step handled by the character-rendering layer that consumes this track, not part of the API's own output.
Submit your scene's bodies, joints, and gravity settings to the compile-physics endpoint targeting `browser` or `edge_worker`. Today it validates the graph and returns generated source plus an engine ID; the compiled `.wasm` binary for client-side execution is rolling out.
Uploads go through a background job queue with schema-validated payloads and per-file deduplication, so re-uploading the same file never queues a duplicate optimization pass.
3D scene variables bind to Aeion OS collection fields. When events fire (e.g., `crm.deal.closed`), bound variables update automatically. Example: `glowIntensity` bound to `finance.mrr` → updates when invoices post.
`static` (immovable, infinite mass), `rigid` (dynamic, affected by gravity), `kinematic` (script-controlled). Joints: `revolute` (hinge), `prismatic` (slider), `ball` (ragdoll), `fixed` (welded).
~5ms per step in the browser, ~1ms on an edge worker.
`POST /motion/scenes/{id}/publish` sets the scene to published and generates a unique embed token. Unpublish sets it back to draft and clears the token.
Import: glTF, GLB. On upload, the optimizer automatically decimates the mesh (roughly 50% triangle reduction) to help hit 60fps; texture-codec compression (Draco/KTX2) is on the roadmap but not applied yet. Scene _export_ is a separate feature — it renders the live canvas out as MP4, WebM, GIF, or a PNG sequence, not a mesh-format converter.
A unique, opaque token generated on every publish — safe to drop into a public embed URL.
Yes. Upload via the Files module and reference it in your scene. Supported: glTF, GLB. Geometry optimization runs automatically as a background job.
When a rig is placed in a page block, the discovery panel auto-scans the rig's variables and shows available collection bindings. Map a variable to a field → the binding is live.