# Scene3D Camera System

The `Scene3D` + `Step` + `Transform3D` system in remotion-bits provides After Effects-style camera movement — smooth flying between scenes with automatic transitions.

## Core Concept

Place content in 3D space using `Step` components. The "camera" automatically flies between them with configurable timing and easing.

```tsx
import { Scene3D, Step, Element3D, Transform3D } from 'remotion-bits';

<Scene3D
  perspective={1200}
  transitionDuration={60}  // frames to transition between steps
  stepDuration={80}        // frames to hold on each step
  easing="easeInOutCubic"
>
  <Step id="intro" x={0} y={0} z={0}>
    <IntroScene />
  </Step>
  
  <Step id="feature1" x={200} y={0} z={-100} rotateY={-15}>
    <FeatureScene />
  </Step>
  
  <Step id="outro" x={400} y={50} z={0}>
    <OutroScene />
  </Step>
</Scene3D>
```

The camera smoothly flies from intro → feature1 → outro with quaternion SLERP (no gimbal lock).

---

## Scene3D Props

| Prop | Type | Description |
|------|------|-------------|
| `perspective` | `number` | CSS perspective depth (1000-1500 typical) |
| `transitionDuration` | `number` | Frames for camera to move between steps |
| `stepDuration` | `number` | Frames to hold on each step |
| `easing` | `string` | Easing function for transitions |

**Easing options:** `"linear"`, `"easeInOut"`, `"easeInOutCubic"`, `"easeInOutQuart"`, etc.

---

## Step Props

Position the camera when this step is active:

| Prop | Type | Description |
|------|------|-------------|
| `id` | `string` | Unique identifier |
| `x`, `y`, `z` | `number` | Camera position |
| `rotateX`, `rotateY`, `rotateZ` | `number` | Camera rotation (degrees) |
| `scale` | `number` | Zoom level |
| `duration` | `number` | Override stepDuration for this step |
| `transition` | `object` | Enter transition effects |
| `exitTransition` | `object` | Exit transition effects |
| `style` | `CSSProperties` | Additional styles |

### Transition Effects

```tsx
<Step 
  id="scene1"
  transition={{ blur: [20, 0] }}      // blur in
  exitTransition={{ blur: [0, 20] }}  // blur out
>
```

---

## Transform3D API

Chainable, matrix-based transforms with quaternion interpolation.

### Creating Transforms

```tsx
import { Transform3D } from 'remotion-bits';
import { Vector3 } from 'three';

// Start from identity (no transform)
const t = Transform3D.identity();

// Or from Euler angles + position
const t2 = Transform3D.fromEuler(
  Math.PI / 2,  // rotationX
  0,            // rotationY  
  0,            // rotationZ
  new Vector3(0, 100, 50)  // position
);
```

### Chaining Transforms

```tsx
const transform = Transform3D.identity()
  .translate(100, 0, -50)     // move in 3D space
  .rotateX(10)                // rotate around X
  .rotateY(-15)               // rotate around Y
  .rotateZ(Math.PI / 8)       // rotate around Z
  .scaleBy(1.5, 1.5, 1.5);    // scale uniformly
```

### Applying to Steps

```tsx
const baseTransform = Transform3D.identity()
  .translate(0, -100, 0)
  .rotateZ(Math.PI / 8);

<Step id="intro" {...baseTransform.toProps()}>
  <Content />
</Step>

<Step id="next" {...baseTransform.translate(200, 0, 0).rotateY(-15).toProps()}>
  <NextContent />
</Step>
```

### Transform Methods

| Method | Description |
|--------|-------------|
| `.translate(x, y, z)` | Move in 3D space |
| `.rotateX(deg)` | Rotate around X axis |
| `.rotateY(deg)` | Rotate around Y axis |
| `.rotateZ(deg)` | Rotate around Z axis |
| `.scaleBy(x, y, z)` | Scale transform |
| `.multiply(other)` | Combine with another transform |
| `.apply(point)` | Transform a point |
| `.toProps()` | Convert to Step props |
| `.toCSSMatrix3D()` | Get CSS matrix3d string |

---

## Element3D

For 3D-positioned elements within a Step:

```tsx
<Step id="scene1" x={0} y={0} z={0}>
  <Element3D x={50} y={0} z={-20} rotateY={45}>
    <Card />
  </Element3D>
  
  <Element3D {...orbitTransform.toProps()}>
    <Satellite />
  </Element3D>
</Step>
```

### Nested Elements

Elements can be nested for compound transforms:

```tsx
<Element3D x={0} y={0} z={0} scale={1.5}>
  <Element3D {...nestedTransform.toProps()}>
    <Element3D {...nestedTransform.toProps()}>
      <Element3D {...nestedTransform.toProps()}>
        <Box />
      </Element3D>
    </Element3D>
  </Element3D>
</Element3D>
```

---

## Orbiting Elements

Create satellites that orbit a center point:

```tsx
const createOrbitTransform = (angle: number, distance: number, height: number) => {
  const x = Math.cos(angle) * distance;
  const z = Math.sin(angle) * distance;
  
  return Transform3D.identity()
    .translate(x, height, z)
    .rotateY(angle + Math.PI / 2);
};

// Animate orbit based on frame
const frame = useCurrentFrame();
const orbitProgress = (frame % 100) / 100;

const orbit1 = createOrbitTransform(orbitProgress * Math.PI * 2, 400, 50);
const orbit2 = createOrbitTransform((orbitProgress + 0.33) * Math.PI * 2, 400, 50);
const orbit3 = createOrbitTransform((orbitProgress + 0.66) * Math.PI * 2, 400, 50);

<Element3D {...orbit1.toProps()}>
  <Satellite />
</Element3D>
```

---

## Keyframe Interpolation

Smooth interpolation between transform keyframes:

```tsx
import { interpolateTransform } from 'remotion-bits';

const keyframes = [
  Transform3D.fromEuler(0, 0, 0, new Vector3(0, 0, 0)),
  Transform3D.fromEuler(Math.PI / 2, 0, 0, new Vector3(0, 300, 500)),
  Transform3D.fromEuler(Math.PI, Math.PI / 2, 0, new Vector3(600, 150, 300)),
];

// Interpolate with quaternion SLERP - no gimbal lock!
const currentTransform = interpolateTransform(
  keyframes[0],
  keyframes[1],
  progress,
  "easeInOutCubic"
);
```

**Benefits of quaternion SLERP:**
- Natural rotation paths
- No gimbal lock artifacts
- Single matrix operation (performant)

---

## Complete Example

```tsx
import React from "react";
import { AbsoluteFill, useCurrentFrame } from "remotion";
import { Scene3D, Step, Element3D, Transform3D, useViewportRect } from "remotion-bits";

export const FlyingCameraDemo: React.FC = () => {
  const rect = useViewportRect();
  const frame = useCurrentFrame();
  
  const baseTransform = Transform3D.identity()
    .translate(0, rect.vmin * -10, 0)
    .rotateZ(Math.PI / 8);
  
  const enterExit = {
    transition: { blur: [20, 0] },
    exitTransition: { blur: [0, 20] },
  };

  return (
    <AbsoluteFill style={{ background: '#0a0a0a' }}>
      <Scene3D
        perspective={1200}
        transitionDuration={60}
        stepDuration={80}
        easing="easeInOutCubic"
      >
        {/* Scene 1: Title */}
        <Step id="intro" {...baseTransform.toProps()} {...enterExit}>
          <h1 style={{ fontSize: rect.vmin * 6, color: 'white' }}>
            Welcome
          </h1>
        </Step>

        {/* Scene 2: Feature - camera flies right and rotates */}
        <Step 
          id="feature"
          {...baseTransform.translate(rect.vmin * 100, 0, 0).rotateY(-15).toProps()}
          {...enterExit}
        >
          <div style={{ color: 'white' }}>
            <h2>Feature One</h2>
            <p>Camera flew here smoothly</p>
          </div>
        </Step>

        {/* Scene 3: Deep dive - camera zooms in (z-axis) */}
        <Step
          id="deep"
          x={rect.vmin * 200}
          y={rect.vmin * 20}
          z={rect.vmin * -50}
          rotateX={10}
          {...enterExit}
        >
          <div style={{ color: 'white' }}>
            <h2>Going Deeper</h2>
          </div>
        </Step>

        {/* Scene 4: Outro */}
        <Step
          id="outro"
          x={rect.vmin * 300}
          y={rect.vmin * -20}
          z={0}
          rotateY={-10}
          {...enterExit}
        />
      </Scene3D>
    </AbsoluteFill>
  );
};
```

---

## Tips

1. **Use `useViewportRect()`** for responsive positioning — multiply by `rect.vmin` for values that scale with viewport

2. **Blur transitions** add polish — `transition: { blur: [20, 0] }` makes scenes feel like they're coming into focus

3. **Chain transforms** for readability — easier to understand than raw x/y/z/rotateY props

4. **Override duration per step** — use `duration={120}` on a Step for longer holds on important content

5. **Perspective affects depth feel** — lower values (800) = more dramatic perspective, higher (1500) = more subtle
