# cube
Spinning ASCII cubes: three cubes rotated in 3D and projected through a z-buffer into a 160 x 44 character
frame (the classic rotating-cube demo). Core-only Spiral: loops, mutable cells, arrays and float math run on every
backend; the only effect is printing (the portable `!!!!Printf`). A run renders `frames` frames, each preceded
by ESC[H (cursor home, so a terminal redraws in place), then prints a checksum of all frames.
## cube
### rotation

<!-- livebook:{"chunks":null,"kind":"Elixir.Spiral.Kino.SmartCell","attrs":{"timeout":300,"source":"nominal rotation =\n    {\n        a : f64\n        b : f64\n        c : f64\n    }","print_code":false},"livebook_object":"smart_cell"} -->

```elixir
Spiral.Kino.eval!(
  ~S"""
  nominal rotation =
      {
          a : f64
          b : f64
          c : f64
      }
  """,
  timeout: 300_000
)
```

### settings

<!-- livebook:{"chunks":null,"kind":"Elixir.Spiral.Kino.SmartCell","attrs":{"timeout":300,"source":"inl width () = 160i32\ninl height () = 44i32\ninl distance_from_cam () = 100f64\ninl k1 () = 40f64\ninl increment_speed () = 0.6f64\ninl background () = 46i32 // '.'","print_code":false},"livebook_object":"smart_cell"} -->

```elixir
Spiral.Kino.eval!(
  ~S"""
  inl width () = 160i32
  inl height () = 44i32
  inl distance_from_cam () = 100f64
  inl k1 () = 40f64
  inl increment_speed () = 0.6f64
  inl background () = 46i32 // '.'
  """,
  timeout: 300_000
)
```

### math

<!-- livebook:{"chunks":null,"kind":"Elixir.Spiral.Kino.SmartCell","attrs":{"timeout":300,"source":"inl sin64 (x : f64) : f64 = !!!!Sin(x)\ninl cos64 (x : f64) : f64 = !!!!Cos(x)\ninl to_i32 (x : f64) : i32 = !!!!Conv(`i32, x)\ninl to_f64 (x : i32) : f64 = !!!!Conv(`f64, x)","print_code":false},"livebook_object":"smart_cell"} -->

```elixir
Spiral.Kino.eval!(
  ~S"""
  inl sin64 (x : f64) : f64 = !!!!Sin(x)
  inl cos64 (x : f64) : f64 = !!!!Cos(x)
  inl to_i32 (x : f64) : i32 = !!!!Conv(`i32, x)
  inl to_f64 (x : i32) : f64 = !!!!Conv(`f64, x)
  """,
  timeout: 300_000
)
```

### calculate_x

<!-- livebook:{"chunks":null,"kind":"Elixir.Spiral.Kino.SmartCell","attrs":{"timeout":300,"source":"inl calculate_x (i : f64) (j : f64) (k : f64) (rotation rot) : f64 =\n    inl a, b, c = rot.a, rot.b, rot.c\n    j * sin64 a * sin64 b * cos64 c - k * cos64 a * sin64 b * cos64 c +\n    j * cos64 a * sin64 c + k * sin64 a * sin64 c + i * cos64 b * cos64 c","print_code":false},"livebook_object":"smart_cell"} -->

```elixir
Spiral.Kino.eval!(
  ~S"""
  inl calculate_x (i : f64) (j : f64) (k : f64) (rotation rot) : f64 =
      inl a, b, c = rot.a, rot.b, rot.c
      j * sin64 a * sin64 b * cos64 c - k * cos64 a * sin64 b * cos64 c +
      j * cos64 a * sin64 c + k * sin64 a * sin64 c + i * cos64 b * cos64 c
  """,
  timeout: 300_000
)
```

### calculate_y

<!-- livebook:{"chunks":null,"kind":"Elixir.Spiral.Kino.SmartCell","attrs":{"timeout":300,"source":"inl calculate_y (i : f64) (j : f64) (k : f64) (rotation rot) : f64 =\n    inl a, b, c = rot.a, rot.b, rot.c\n    j * cos64 a * cos64 c + k * sin64 a * cos64 c -\n    j * sin64 a * sin64 b * sin64 c + k * cos64 a * sin64 b * sin64 c -\n    i * cos64 b * sin64 c","print_code":false},"livebook_object":"smart_cell"} -->

```elixir
Spiral.Kino.eval!(
  ~S"""
  inl calculate_y (i : f64) (j : f64) (k : f64) (rotation rot) : f64 =
      inl a, b, c = rot.a, rot.b, rot.c
      j * cos64 a * cos64 c + k * sin64 a * cos64 c -
      j * sin64 a * sin64 b * sin64 c + k * cos64 a * sin64 b * sin64 c -
      i * cos64 b * sin64 c
  """,
  timeout: 300_000
)
```

### calculate_z

<!-- livebook:{"chunks":null,"kind":"Elixir.Spiral.Kino.SmartCell","attrs":{"timeout":300,"source":"inl calculate_z (i : f64) (j : f64) (k : f64) (rotation rot) : f64 =\n    inl a, b = rot.a, rot.b\n    k * cos64 a * cos64 b - j * sin64 a * cos64 b + i * sin64 b","print_code":false},"livebook_object":"smart_cell"} -->

```elixir
Spiral.Kino.eval!(
  ~S"""
  inl calculate_z (i : f64) (j : f64) (k : f64) (rotation rot) : f64 =
      inl a, b = rot.a, rot.b
      k * cos64 a * cos64 b - j * sin64 a * cos64 b + i * sin64 b
  """,
  timeout: 300_000
)
```

### plot
Projects one surface point; the z-buffer keeps the nearest one (the largest 1/z) per cell, the first on ties.

<!-- livebook:{"chunks":null,"kind":"Elixir.Spiral.Kino.SmartCell","attrs":{"timeout":300,"source":"let plot (depth : a i32 f64) (chars : a i32 i32) (rot : rotation) (offset : f64) (i : f64) (j : f64) (k : f64) (ch : i32) : () =\n    inl x = calculate_x i j k rot\n    inl y = calculate_y i j k rot\n    inl z = calculate_z i j k rot + distance_from_cam ()\n    inl ooz = 1f64 / z\n    inl xp = to_i32 (to_f64 (width ()) / 2f64 + offset + k1 () * ooz * x * 2f64)\n    inl yp = to_i32 (to_f64 (height ()) / 2f64 + k1 () * ooz * y)\n    inl idx = xp + yp * width ()\n    if idx >= 0i32 && idx < width () * height () then\n        if ooz > index depth idx then\n            set depth idx ooz\n            set chars idx ch","print_code":false},"livebook_object":"smart_cell"} -->

```elixir
Spiral.Kino.eval!(
  ~S"""
  let plot (depth : a i32 f64) (chars : a i32 i32) (rot : rotation) (offset : f64) (i : f64) (j : f64) (k : f64) (ch : i32) : () =
      inl x = calculate_x i j k rot
      inl y = calculate_y i j k rot
      inl z = calculate_z i j k rot + distance_from_cam ()
      inl ooz = 1f64 / z
      inl xp = to_i32 (to_f64 (width ()) / 2f64 + offset + k1 () * ooz * x * 2f64)
      inl yp = to_i32 (to_f64 (height ()) / 2f64 + k1 () * ooz * y)
      inl idx = xp + yp * width ()
      if idx >= 0i32 && idx < width () * height () then
          if ooz > index depth idx then
              set depth idx ooz
              set chars idx ch
  """,
  timeout: 300_000
)
```

### draw_cube

<!-- livebook:{"chunks":null,"kind":"Elixir.Spiral.Kino.SmartCell","attrs":{"timeout":300,"source":"let draw_cube (depth : a i32 f64) (chars : a i32 i32) (rot : rotation) (cw : f64) (offset : f64) : () =\n    inl plot = plot depth chars rot offset\n    inl cube_x = mut (0f64 - cw)\n    loopw.while (fun () => *cube_x < cw) fun () =>\n        inl cube_y = mut (0f64 - cw)\n        loopw.while (fun () => *cube_y < cw) fun () =>\n            inl x, y = *cube_x, *cube_y\n            plot x y (0f64 - cw) 59i32 // ';'\n            plot cw y x 92i32 // '\\'\n            plot (0f64 - cw) y (0f64 - x) 47i32 // '/'\n            plot (0f64 - x) y cw 61i32 // '='\n            plot x (0f64 - cw) (0f64 - y) 62i32 // '>'\n            plot x cw y 60i32 // '<'\n            cube_y <- *cube_y + increment_speed ()\n        cube_x <- *cube_x + increment_speed ()","print_code":false},"livebook_object":"smart_cell"} -->

```elixir
Spiral.Kino.eval!(
  ~S"""
  let draw_cube (depth : a i32 f64) (chars : a i32 i32) (rot : rotation) (cw : f64) (offset : f64) : () =
      inl plot = plot depth chars rot offset
      inl cube_x = mut (0f64 - cw)
      loopw.while (fun () => *cube_x < cw) fun () =>
          inl cube_y = mut (0f64 - cw)
          loopw.while (fun () => *cube_y < cw) fun () =>
              inl x, y = *cube_x, *cube_y
              plot x y (0f64 - cw) 59i32 // ';'
              plot cw y x 92i32 // '\'
              plot (0f64 - cw) y (0f64 - x) 47i32 // '/'
              plot (0f64 - x) y cw 61i32 // '='
              plot x (0f64 - cw) (0f64 - y) 62i32 // '>'
              plot x cw y 60i32 // '<'
              cube_y <- *cube_y + increment_speed ()
          cube_x <- *cube_x + increment_speed ()
  """,
  timeout: 300_000
)
```

### cell_text

<!-- livebook:{"chunks":null,"kind":"Elixir.Spiral.Kino.SmartCell","attrs":{"timeout":300,"source":"inl cell_text (ch : i32) : string =\n    if ch = 59i32 then \";\"\n    elif ch = 92i32 then \"\\\\\"\n    elif ch = 47i32 then \"/\"\n    elif ch = 61i32 then \"=\"\n    elif ch = 62i32 then \">\"\n    elif ch = 60i32 then \"<\"\n    else \".\"","print_code":false},"livebook_object":"smart_cell"} -->

```elixir
Spiral.Kino.eval!(
  ~S"""
  inl cell_text (ch : i32) : string =
      if ch = 59i32 then ";"
      elif ch = 92i32 then "\\"
      elif ch = 47i32 then "/"
      elif ch = 61i32 then "="
      elif ch = 62i32 then ">"
      elif ch = 60i32 then "<"
      else "."
  """,
  timeout: 300_000
)
```

### render
Clears the buffers, draws the three cubes, prints the frame row by row and returns the updated checksum.

<!-- livebook:{"chunks":null,"kind":"Elixir.Spiral.Kino.SmartCell","attrs":{"timeout":300,"source":"let render (depth : a i32 f64) (chars : a i32 i32) (rot : rotation) (checksum : i32) : i32 =\n    inl size = width () * height ()\n    loopw.for' {from=0i32; nearTo=size} fun i =>\n        set depth i 0f64\n        set chars i (background ())\n    draw_cube depth chars rot 20f64 (0f64 - 40f64)\n    draw_cube depth chars rot 10f64 10f64\n    draw_cube depth chars rot 5f64 40f64\n    !!!!Printf(\"\\u001b[H\")\n    inl sum = mut checksum\n    loopw.for' {from=0i32; nearTo=height ()} fun row =>\n        loopw.for' {from=0i32; nearTo=width ()} fun col =>\n            inl ch = index chars (col + row * width ())\n            !!!!Printf(\"%s\", cell_text ch)\n            sum <- (*sum * 31i32 + ch) % 1000003i32\n        !!!!Printf(\"\\n\")\n    *sum","print_code":false},"livebook_object":"smart_cell"} -->

```elixir
Spiral.Kino.eval!(
  ~S"""
  let render (depth : a i32 f64) (chars : a i32 i32) (rot : rotation) (checksum : i32) : i32 =
      inl size = width () * height ()
      loopw.for' {from=0i32; nearTo=size} fun i =>
          set depth i 0f64
          set chars i (background ())
      draw_cube depth chars rot 20f64 (0f64 - 40f64)
      draw_cube depth chars rot 10f64 10f64
      draw_cube depth chars rot 5f64 40f64
      !!!!Printf("\u001b[H")
      inl sum = mut checksum
      loopw.for' {from=0i32; nearTo=height ()} fun row =>
          loopw.for' {from=0i32; nearTo=width ()} fun col =>
              inl ch = index chars (col + row * width ())
              !!!!Printf("%s", cell_text ch)
              sum <- (*sum * 31i32 + ch) % 1000003i32
          !!!!Printf("\n")
      *sum
  """,
  timeout: 300_000
)
```

### next_rotation

<!-- livebook:{"chunks":null,"kind":"Elixir.Spiral.Kino.SmartCell","attrs":{"timeout":300,"source":"inl next_rotation (rotation r) : rotation = rotation { a = r.a + 0.05f64; b = r.b + 0.05f64; c = r.c + 0.01f64 }","print_code":false},"livebook_object":"smart_cell"} -->

```elixir
Spiral.Kino.eval!(
  ~S"""
  inl next_rotation (rotation r) : rotation = rotation { a = r.a + 0.05f64; b = r.b + 0.05f64; c = r.c + 0.01f64 }
  """,
  timeout: 300_000
)
```

### main

<!-- livebook:{"chunks":null,"kind":"Elixir.Spiral.Kino.SmartCell","attrs":{"timeout":300,"source":"inl main () : i32 =\n    inl frames = 60i32\n    inl size = width () * height ()\n    inl depth : a i32 f64 = am.init size (fun _ => 0f64)\n    inl chars : a i32 i32 = am.init size (fun _ => background ())\n    inl rot = mut (rotation { a = 0f64; b = 0f64; c = 0f64 })\n    inl checksum = mut 0i32\n    loopw.for' {from=0i32; nearTo=frames} fun _ =>\n        checksum <- render depth chars *rot *checksum\n        rot <- next_rotation *rot\n    inl checksum = *checksum\n    !!!!Printf(\"cube: %d frames, checksum %d\\n\", frames, checksum)\n    0i32","print_code":false},"livebook_object":"smart_cell"} -->

```elixir
Spiral.Kino.eval!(
  ~S"""
  inl main () : i32 =
      inl frames = 60i32
      inl size = width () * height ()
      inl depth : a i32 f64 = am.init size (fun _ => 0f64)
      inl chars : a i32 i32 = am.init size (fun _ => background ())
      inl rot = mut (rotation { a = 0f64; b = 0f64; c = 0f64 })
      inl checksum = mut 0i32
      loopw.for' {from=0i32; nearTo=frames} fun _ =>
          checksum <- render depth chars *rot *checksum
          rot <- next_rotation *rot
      inl checksum = *checksum
      !!!!Printf("cube: %d frames, checksum %d\n", frames, checksum)
      0i32
  """,
  timeout: 300_000
)
```
