Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,131 @@ collection of single-file C programs.

---

## Tutorial

This tutorial walks through every stage of the simulation as it appears in
`rope.c`, from initial state to rendered frame.

### 1. Simulation Constants and State Arrays

The simulation is controlled by a handful of compile-time constants and two
pairs of parallel arrays that hold current and previous positions for each
node:

```c
#define W 80 /* terminal width */
#define H 40 /* terminal height */
#define N 24 /* number of nodes */
```

```c
double x[N], y[N]; /* current positions */
double px[N], py[N]; /* previous positions (Verlet history) */
```

All nodes are initialised in a horizontal line near the top of the screen:

```c
for (int i=0;i<N;i++){ x[i]=20+i; y[i]=2; px[i]=x[i]; py[i]=y[i]; }
```

`seg` holds the rest length of each segment between adjacent nodes:

```c
double seg = 1.0;
```

### 2. The Anchor Node and Driven Motion

Node `0` is the anchor. Instead of being integrated like the other nodes it is
forced to a sinusoidal position every frame, which drives the whole rope:

```c
x[0] = W/2 + 18*sin(t*0.6); y[0] = 2; px[0]=x[0]; py[0]=y[0];
```

Setting `px[0] = x[0]` at the same time zeroes the anchor's stored velocity so
the constraint solver never moves it away from its driven position. `t`
accumulates by `0.08` each frame, giving a smooth oscillation at about 0.6 rad/s.

### 3. Verlet Integration with Damping and Gravity

For every free node (`i >= 1`) the new position is computed from the current
position, the previous position (which encodes velocity implicitly), a damping
factor of `0.99`, and a constant gravity term of `0.05` per frame:

```c
for (int i=1;i<N;i++){
double nx = x[i] + (x[i]-px[i])*0.99;
double ny = y[i] + (y[i]-py[i])*0.99 + 0.05; /* gravity */
px[i]=x[i]; py[i]=y[i]; x[i]=nx; y[i]=ny;
}
```

The expression `(x[i] - px[i])` is the velocity from the previous step.
Multiplying by `0.99` introduces a small drag that prevents the rope from
growing unstable over time.

### 4. Distance Constraint Solving (Jacobi-style Relaxation)

After integration the rope segments may have stretched or compressed. Eight
passes of a pairwise constraint loop restore each segment to its rest length
`seg`. Each pass walks every adjacent pair `(i, i+1)` and nudges both nodes
equally toward the correct distance:

```c
for (int k=0;k<8;k++)
for (int i=0;i<N-1;i++){
double dx=x[i+1]-x[i], dy=y[i+1]-y[i];
double d=sqrt(dx*dx+dy*dy)+1e-9;
double diff=(d-seg)/d*0.5;
double ox=dx*diff, oy=dy*diff;
if (i!=0){ x[i]+=ox; y[i]+=oy; }
x[i+1]-=ox; y[i+1]-=oy;
}
```

The `1e-9` guard prevents division by zero when two nodes overlap exactly.
Node `0` is excluded from the correction (`if (i!=0)`) so the anchor stays
pinned to its driven position.

### 5. Rasterising Nodes into a Character Buffer

The simulation lives in floating-point space; the terminal is a grid of
characters. After physics each frame the buffer is cleared and every node is
projected to integer cell coordinates and stamped in:

```c
memset(buf,' ',sizeof(buf));
for (int i=0;i<N;i++){
int ix=(int)(x[i]+0.5), iy=(int)(y[i]+0.5);
if (ix>=0&&ix<W&&iy>=0&&iy<H) buf[iy][ix]= (i==0?'#':'o');
}
```

The anchor node (`i == 0`) is drawn as `#` to distinguish it; all other nodes
are drawn as `o`. The `+0.5` rounds to the nearest cell rather than truncating.

### 6. Rendering the Frame and Timing

The buffer is printed in a single pass using `fwrite` for each row, preceded by
the ANSI escape `\033[H` which moves the cursor to the top-left without
clearing, giving a flicker-free refresh:

```c
printf("\033[H");
for (int yy=0;yy<H;yy++){ fwrite(buf[yy],1,W,stdout); putchar('\n'); }
fflush(stdout);
t += 0.08;
usleep(33000);
```

`usleep(33000)` targets roughly 30 frames per second (33 ms per frame).
`t` is incremented after the render so the anchor's next position is ready for
the following integration step.

---

## Build

```
Expand Down