# Physics-based Animations

Tesserax includes a built-in, deterministic, baked physics engine. Unlike real-time game engines that calculate physics *during* the render loop, Tesserax calculates the entire simulation upfront and "bakes" it into standard `KeyframeAnimation` tracks.

This architecture offers two major advantages:

1.  **Precision**: You can simulate at extremely high resolutions (e.g., 1000 steps per second) for accuracy, while playing back at a standard 30 or 60 FPS.
2.  **Predictability**: The resulting animation is a standard Tesserax object that can be paused, reversed, looped, or composed with other animations.

## The Basic

A high-level overview of the workflow is as follows:

1.  Create a `World`.
2.  Add `Shape` objects to the world (creates `Body` wrappers).
3.  Add `Field`s (like Gravity) and global constraints.
4.  Call `world.simulate(duration)` to generate an `Animation`.
5.  Play the animation in your `Scene`.

Here is a complete example of a ball falling under gravity and bouncing on a static floor. First we will create the scene.

```{python}
from tesserax import Canvas, Circle, Rect

# 1. Setup the Scene
with Canvas() as canvas:
    floor = Rect(600, 50, fill="#333").translated(300, 380)
    ball = Circle(20, fill="#ff0055").translated(300, 50)
```

Next, we define the physical world. This involves wrapping the existing shapes that will undergo physical simulation as `Body`s and place them in a `World`. Once set up, we bake the physics animation with `world.simulate(...)`.

```{python}
from tesserax.physics import World, Gravity, Material, CircleCollider

world = World()
world.fields.append(Gravity())  # Downward acceleration (pixels/s^2)
world.add(floor, static=True)
world.add(
    ball,
    material=Material(restitution=0.8, friction=0.5),
    collider=CircleCollider(ball.r),
)

simulation = world.simulate(duration=3.0, dt=0.005)
```

Finally, we run the animation as normal. Notice we can simulate at some fixed framerate and render at a completely different rate.

```{python}
from tesserax.animation import Scene

canvas.fit(10)
scene = Scene(canvas)
scene.play(simulation, duration=3.0)
scene.display()

```

## Constraints

Tesserax physics engine also supports some simple constraints like `Spring`s (soft constraints) and `Rod`s (hard constraints). These work by applying forces and corrections to bodies during simulation.

```{python}
from tesserax import Canvas, Rect, Circle, Line, Point
from tesserax.animation import Scene
from tesserax.physics import World, Gravity, Material
from tesserax.physics.constraints import Spring, Rod

with Canvas() as canvas:
    floor = Rect(600, 50, fill="#333").translated(300, 380)
    box = Rect(50, 50, fill="steelblue").translated(200, 100)
    ball = Circle(15, fill="orange").translated(300, 100)
    ball2 = Rect(20, 20, fill="green").translated(400, 100)
    spring_line = Line(
        lambda: box.anchor("center"),
        lambda: ball.anchor("center"),
        stroke="gray",
        width=3,
    )
    rod_line = Line(
        lambda: ball.anchor("center"),
        lambda: ball2.anchor("center"),
        stroke="gray",
        width=3,
    )

canvas.fit(10)

world = World()
world.fields.append(Gravity())
world.add(floor, static=True)

body_box = world.add(box, material=Material(restitution=0.6))
body_box.angular_vel = 5.0  # Radians/sec
body_ball = world.add(
    ball, material=Material(restitution=0.8), collider=CircleCollider(15)
)
body_ball2 = world.add(
    ball2, material=Material(restitution=0.1), mass=2
)

# Physics Constraint
world.constraint(Spring(body_box, body_ball, length=100, k=5.0))
world.constraint(Rod(body_ball, body_ball2, length=100))

# 3. Bake
simulation = world.simulate(duration=4.0)

# 4. Play
scene = Scene(canvas)
scene.play(simulation, duration=4.0)
scene.display()
```