Skip to content

Commit

Permalink
feat: much more optimized version with better lighting
Browse files Browse the repository at this point in the history
chore: add a link to the running version

Signed-off-by: Harikrishnan Balagopal <[email protected]>
  • Loading branch information
HarikrishnanBalagopal committed Sep 25, 2022
1 parent aa5becb commit 0dd37e1
Show file tree
Hide file tree
Showing 13 changed files with 353 additions and 261 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ go.work

# Build output
/bin/
/profiles/
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,11 @@ copy:
.PHONY: serve
serve:
cd docs/ && python3 -m http.server 8080

.PHONY: benchmark
benchmark:
go test -cpuprofile profiles/cpu.prof -memprofile profiles/mem.prof -bench . ./utils

.PHONY: visualize-benchmark
visualize-benchmark:
go tool pprof -http=: profiles/cpu.prof
50 changes: 27 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
# Spinning 3D donut

See it running at https://haribala.dev/asciidonut

This implementation uses path tracing of signed distance functions to render the donut.
Meant to be compiled into a WASM module and displayed on a website.

```
@@@@@
@@@@@o0o-000-*o*oo00@
#@@#*o,o. * + - -.+-oo00
#@@o*0.o 0 - , --+o00
##@#*o,@+ +* ,. .-.--**o0
##00@+#### # @o +++-, ., . ---+,++*o0
##@##0*-,-*#0@*o . ..-....,+***o0
##@##0*++o#0000 ..,,-,,,-+***o0
###o@o#+o@0@@@ ,------+-++*oo
##@@@o###o#@##@ -++++-++*+**oo0
####0##@##@0@## +*+++********oo
#@##o#@00@#@#### ********+**+**oo
#@##o@0o#o0@###### ooooooo***+**+**oo
@###o@o##@###@###########@@@@@00000ooooo**+*+***o
@@#@@#o#@0ooo0@############@@@@000oo***********oo
@@0#o@*@o*###########@@@@0@@@00000ooo+**+*+***o
@#0#o@*@o+#0o******##@@@@@*******oo++*-*+*+**
@@0o0@*@o+@0*++@0*+++@+++*o0+++*o-+*+*++***
@@0@@*0+@*-@o+--@o*+++*o0--+*o-+*-*-**+++
@0o@@*@*@0+-@0o*+---++*oo,-*o-*-*+-+-
00o00@*0o+@0o*++--++*oo-+*-*++,-,
oo0oo0+0*+,ooooo,-+*,+---,.
**o*-*-,,,-+.--,.
-::;;;;;;;;;;::~-
:;==!!!!!!!!!!!!!==;;:~
:=!!*****************!!!=;:~
:=**####****!!************!!=;:~
:!*##$$##**!!!=!!!***#######**!!=;:
=*#$$$$$#*!!=;;;;;=!**#########**!=;:
-!#$@@@@$#*=;~ ,~;=!*###$$$$$##**!=;~
~!#$@@@@$*=~ ~;!*##$$$$$$$##**!=;-
-!#$@@@@$*; ;=*##$$$@$$$$##*!=;:
=*$@@@@$*: ;!*#$$$@@@@$$$#**!=:,
:!#$@@@$*; -=!*#$$@@@@@@$$##*!=:-
=*$$@@$#!, ;!*#$$@@@@@@@$$##*!=:-
-=*#$$$#*= :=*#$$@@@@@@@@$$##*!=:
~!*#$$##!=- :=*#$$@@@@@@@@@$$#*!=;~
~=*####*!=: ~;!*#$$@@@@@@@@@$$##*!=:-
~=!*****!=;:- -:;=**#$$@@@@@@@@@$$##*!=;~
;=!****!!=;;;;;;=!**##$$@@@@@@@@@$$##*!=;~
~=!!****!!!!!!!!**##$$$@@@@@@@@$$$##*!=;~
:=!!!*********###$$$$$@@@@@@$$$##**!=;~
:==!!******#####$$$$$$$$$$$$##**!=;:-
:;=!!!****######$$$$$$$####**!==:~
-:;==!!*****##########***!!=;:~
-:;;==!!!!********!!!==;:~-
-::;;==========;;::~,
-~~~~~~~~-,
```
3 changes: 2 additions & 1 deletion docs/assets/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ async function main() {

let last_t = null;
const TIME_STEP = 1;
function draw(t) {
function draw(_t) {
t = _t*10 + 20000.0;
requestAnimationFrame(draw);
if (!last_t) last_t = t;
const delta_t = t - last_t;
Expand Down
Binary file modified docs/assets/wasm/asciidonut.wasm
Binary file not shown.
15 changes: 15 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D donut</title>
<style>
body {
background-color: black;
color: white;
}
a {
font-family: monospace;
color: white;
}
</style>
</head>

<body>
<a target="_blank"
href="https://github.com/HarikrishnanBalagopal/asciidonut">https://github.com/HarikrishnanBalagopal/asciidonut</a>
<pre id="output-pre"></pre>
<script src="assets/js/wasm_exec.js"></script>
<script src="assets/js/main.js"></script>
</body>

</html>
242 changes: 5 additions & 237 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,248 +1,16 @@
package main

import (
// "fmt"
"math"
// "strings"
// "syscall/js"
"github.com/HarikrishnanBalagopal/asciidonut/utils"
)

type Vec3 struct {
X, Y, Z float64
}

func (v1 Vec3) Add(v2 Vec3) Vec3 {
return Vec3{X: v1.X + v2.X, Y: v1.Y + v2.Y, Z: v1.Z + v2.Z}
}

func (v1 Vec3) Sub(v2 Vec3) Vec3 {
return Vec3{X: v1.X - v2.X, Y: v1.Y - v2.Y, Z: v1.Z - v2.Z}
}

func (v Vec3) Scale(amount float64) Vec3 {
return Vec3{X: v.X * amount, Y: v.Y * amount, Z: v.Z * amount}
}

func (v Vec3) Length() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y + v.Z*v.Z)
}

func (v Vec3) Normalize() Vec3 {
l := v.Length()
return Vec3{X: v.X / l, Y: v.Y / l, Z: v.Z / l}
}

func (v1 Vec3) Dot(v2 Vec3) float64 {
return v1.X*v2.X + v1.Y*v2.Y + v1.Z*v2.Z
}

func (v1 Vec3) Cross(v2 Vec3) Vec3 {
return Vec3{X: v1.Y*v2.Z - v1.Z*v2.Y, Y: v1.Z*v2.X - v1.X*v2.Z, Z: v1.X*v2.Y - v1.Y*v2.X}
}

func (v Vec3) XZ() Vec3 {
return Vec3{X: v.X, Y: 0, Z: v.Z}
}

// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------

type Mat3x3 struct {
XX, XY, XZ float64
YX, YY, YZ float64
ZX, ZY, ZZ float64
}

func (m Mat3x3) Mul(v Vec3) Vec3 {
return Vec3{
X: m.XX*v.X + m.XY*v.Y + m.XZ*v.Z,
Y: m.YX*v.X + m.YY*v.Y + m.YZ*v.Z,
Z: m.ZX*v.X + m.ZY*v.Y + m.ZZ*v.Z,
}
}

func (Mat3x3) Rot(axis Vec3, angle float64) Mat3x3 {
return Mat3x3{
XX: math.Cos(angle) + axis.X*axis.X*(1-math.Cos(angle)),
XY: axis.X*axis.Y*(1-math.Cos(angle)) - axis.Z*math.Sin(angle),
XZ: axis.X*axis.Z*(1-math.Cos(angle)) + axis.Y*math.Sin(angle),

YX: axis.Y*axis.X*(1-math.Cos(angle)) + axis.Z*math.Sin(angle),
YY: math.Cos(angle) + axis.Y*axis.Y*(1-math.Cos(angle)),
YZ: axis.Y*axis.Z*(1-math.Cos(angle)) - axis.X*math.Sin(angle),

ZX: axis.Z*axis.X*(1-math.Cos(angle)) - axis.Y*math.Sin(angle),
ZY: axis.Z*axis.Y*(1-math.Cos(angle)) + axis.X*math.Sin(angle),
ZZ: math.Cos(angle) + axis.Z*axis.Z*(1-math.Cos(angle)),
}
}

func Radians(degrees float64) float64 {
return math.Pi * degrees / 180.0
}

// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------

const (
N = 32
W = N * 2
H = N
MAX_ITERATIONS = N
EPS = 0.001
// https://en.wikipedia.org/wiki/Phong_reflection_model
AMBIENT_REFLECTION_CONSTANT = 0.05
AMBIENT_LIGHT_INTENSITY = 255.0
DIFFUSE_REFLECTION_CONSTANT = 0.95
DIFFUSE_LIGHT_INTENSITY = 255.0
// SPECULAR_REFLECTION_CONSTANT = 0.1
// MATERIAL_SHININESS_CONSTANT = 0.1
)

var (
ASCII = []byte{' ', '.', ',', '-', '+', '*', 'o', '0', '@', '#'}
BUFFER_1 = [H][W + 1]byte{}
)

// func sphere_sd(p Vec3, r float64) float64 {
// return p.Length() - r
// }

func torus_sd(p Vec3, tx, ty float64) float64 {
q := Vec3{X: p.XZ().Length() - tx, Y: p.Y, Z: 0}
return q.Length() - ty
}

func scene_sd(p Vec3, t float64) float64 {
p1 := Mat3x3{}.Rot(Vec3{1, 0, 0}, t).Mul(p)
// p1 := Mat3x3{}.Rot(Vec3{1, 1, math.Sin(7 * t)}.Normalize(), 10*t).Mul(p)
d := torus_sd(p1, 2., .75)
// d := sphere_sd(p, 1.)
return d
}

func normals_sd(p Vec3, t float64) Vec3 {
dx1 := scene_sd(p.Add(Vec3{EPS, 0, 0}), t)
dx2 := scene_sd(p.Add(Vec3{-EPS, 0, 0}), t)

dy1 := scene_sd(p.Add(Vec3{0, EPS, 0}), t)
dy2 := scene_sd(p.Add(Vec3{0, -EPS, 0}), t)

dz1 := scene_sd(p.Add(Vec3{0, 0, EPS}), t)
dz2 := scene_sd(p.Add(Vec3{0, 0, -EPS}), t)

return Vec3{
X: dx2 - dx1/2*EPS,
Y: dy2 - dy1/2*EPS,
Z: dz2 - dz1/2*EPS,
}.Normalize()
}

func calc_pixel_color(x, y, t float64) float64 {
nx := 2*x/W - 1
ny := 2*y/H - 1

// fmt.Println("nx", nx, "ny", ny)

cam_pos := Vec3{0, 0, 4}
cam_dir := Vec3{0, 0, -1}
screen_dist := 1.0
screen_cen := cam_pos.Add(cam_dir.Scale(screen_dist))

vec_up := Vec3{0, 1, 0}
cam_right := cam_dir.Cross(vec_up).Normalize()
cam_up := cam_right.Cross(cam_dir).Normalize()
screen_pos := screen_cen.Add(cam_right.Scale(nx)).Add(cam_up.Scale(ny))
light_pos := Vec3{0, 4, 2}

ray_dir := screen_pos.Sub(cam_pos).Normalize()

p := cam_pos
d := 10000.0
for i := 0; i < MAX_ITERATIONS; i++ {
d = scene_sd(p, t)
if d < EPS {
// fmt.Println("***************************************")
break
}
p = p.Add(ray_dir.Scale(d))
}

// fmt.Println("...")

if d >= EPS {
return 0.
}

// https://en.wikipedia.org/wiki/Phong_reflection_model
normal := normals_sd(p, t)
light_dir := light_pos.Sub(p).Normalize()

dot := light_dir.Dot(normal)
if dot < 0 {
dot = 0
}
// dot2 := 0
Ip := AMBIENT_REFLECTION_CONSTANT*AMBIENT_LIGHT_INTENSITY + DIFFUSE_REFLECTION_CONSTANT*(dot)*DIFFUSE_LIGHT_INTENSITY //+ Ks*pow(dot2,alpha)*Ims
// fmt.Println("Dot", dot, "Ip", Ip)
return Ip
}

// func step(t float64) {
// for y := 0; y < H; y++ {
// for x := 0; x < W; x++ {
// color := calc_pixel_color(float64(x), float64(y), t)
// idx := int(10.0*color/256.0) % 10 // len(ASCII) == 10
// // fmt.Println("color", color, "idx", idx)
// BUFFER_1[y][x] = ASCII[idx]
// }
// }
// }

//go:export Step
func Step(t float64) {
for y := 0; y < H; y++ {
for x := 0; x < W; x++ {
color := calc_pixel_color(float64(x), float64(y), t)
idx := int(10.0*color/256.0) % 10 // len(ASCII) == 10
// fmt.Println("color", color, "idx", idx)
BUFFER_1[y][x] = ASCII[idx]
}
}
}

// func draw(buffer [H][W]byte) {
// for _, row := range buffer {
// fmt.Println(string(row[:]))
// }
// }

// func CalculateDonut(_ js.Value, args []js.Value) interface{} {
// t := args[0].Float()
// step(BUFFER_1, t)
// // sb := strings.Builder{}
// ss := []string{}
// for _, row := range BUFFER_1 {
// ss = append(ss, string(row[:]))
// }
// return strings.Join(ss, "\n")
// }

//go:export GetBufferAddress
func GetBufferAddress() *[H][W + 1]byte {
return &BUFFER_1
}

func main() {
// js.Global().Set("CalculateDonut", js.FuncOf(CalculateDonut))
for i := range BUFFER_1 {
BUFFER_1[i][W] = '\n'
}
utils.InitializeBuffer()
select {}
// t := 0.1
// for {
// step(t)
// draw(BUFFER_1)
// t += .1
// utils.Step(t)
// utils.Draw()
// t += .01
// }
}
27 changes: 27 additions & 0 deletions utils/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package utils

const (
N = 32
W = N * 2
H = N
MAX_ITERATIONS = N
EPS = 0.001
// https://en.wikipedia.org/wiki/Phong_reflection_model
AMBIENT_REFLECTION_CONSTANT = 0.1
AMBIENT_LIGHT_INTENSITY = 255.0
DIFFUSE_REFLECTION_CONSTANT = 0.9
DIFFUSE_LIGHT_INTENSITY = 255.0
// SPECULAR_REFLECTION_CONSTANT = 0.1
// MATERIAL_SHININESS_CONSTANT = 0.1
)

var (
// ASCII (old set of characters), these are my own
// ASCII = []byte{' ', '.', ',', '-', '+', '*', 'o', '0', '@', '#'}
// ASCII (new set of characters) taken from https://www.a1k0n.net/2011/07/20/donut-math.html
// output[xp, yp] = ".,-~:;=!*#$@"[luminance_index];
ASCII = []byte{' ', '.', ',', '-', '~', ':', ';', '=', '!', '*', '#', '$', '@'}
BUFFER_1 = [H][W + 1]byte{}
)

const ASCII_LEN = 13 // (old set has length 10)
Loading

0 comments on commit 0dd37e1

Please sign in to comment.