diff --git a/color.go b/color.go index 7679bbb..81e01ea 100644 --- a/color.go +++ b/color.go @@ -3,3 +3,9 @@ package main type Color interface { GetColor(Vector3) Vector3 } + +var RED = Vector3{255, 0, 0} +var GREEN = Vector3{0, 255, 0} +var BLUE = Vector3{0, 0, 255} +var WHITE = Vector3{255, 255, 255} +var GREY = Vector3{127, 127, 127} diff --git a/primitives_sdf.go b/primitives_sdf.go index 843d82f..4b78ee5 100644 --- a/primitives_sdf.go +++ b/primitives_sdf.go @@ -8,7 +8,7 @@ type Sphere struct { } func (s Sphere) Distance(p Vector3) (float64, Color) { - return p.Sub(s.center).Length(), s.color + return p.Sub(s.center).Length() - s.radius, s.color } // Box diff --git a/ray_marching.go b/ray_marching.go index fb8d62b..c589f53 100644 --- a/ray_marching.go +++ b/ray_marching.go @@ -1,44 +1,129 @@ package main import ( + "math" + rl "github.com/gen2brain/raylib-go/raylib" ) +const MAX_DIST = 1000.0 +const MAX_STEP = 100 +const EPS = 0.1 +const WIDTH = 500 +const HEIGHT = 500 +const PI = math.Pi + +// var sphere Sphere = Sphere{Vector3{0, 0, 100}, 10, RED} +// var plane Plane = Plane{Vector3{0, 1, 0}, -10, GREY} +// var scene UnionSDF = UnionSDF{sphere, plane} + +// x right, y forward, z up +var scene Sphere = Sphere{Vector3{0, 100, 0}, 5, RED} +var cameraPos Vector3 = Vector3{0, -10, 0} + +// radius, theta, phi +var screenSpherical Vector3 = Vector3{10, PI / 2, PI / 2} +var screenPhysicalSize float64 = 5 + +func rayMarch(origin Vector3, direction Vector3) (bool, Vector3) { + p := origin + for range MAX_STEP { + dist, color := scene.Distance(p) + // fmt.Println(dist) + if dist < EPS { + // fmt.Println("hit") + return true, color.GetColor(p) + } + if dist > MAX_DIST { + break + } + // fmt.Println("Before") + // fmt.Println(p, dist) + p = p.Add(direction.Scale(dist)) + // fmt.Println("After") + // fmt.Println(p, DistanceOnly(scene, p)) + } + return false, GREY +} + +func genImage() *rl.Image { + screenCartesian, _, theta_vec, phi_vec := screenSpherical.SphericalToCartesian() + screenCenter := cameraPos.Add(screenCartesian) + // fmt.Println(theta_vec, phi_vec) + + image := rl.GenImageColor(WIDTH, HEIGHT, rl.Black) + + for pixel_x := range WIDTH { + for pixel_y := range HEIGHT { + sx := screenPhysicalSize * (float64(pixel_x)/WIDTH - 0.5) + sy := screenPhysicalSize * (float64(pixel_y)/WIDTH - 0.5) + + origin := screenCenter.Add(theta_vec.Scale(-sx)).Add(phi_vec.Scale(-sy)) + direction := (origin.Sub(cameraPos)).Normalized() + // fmt.Println(origin, direction) + + hit, color_vec := rayMarch(origin, direction) + var fr, fg, fb float64 + if hit { + fr, fg, fb = color_vec.Unpack() + } else { + fr, fg, fb = 0, 0, 0 + } + r, g, b := uint8(fr), uint8(fg), uint8(fb) + rl.ImageDrawPixel(image, int32(pixel_x), int32(pixel_y), rl.Color{R: r, G: g, B: b, A: 255}) + } + } + + return image +} + func main() { - screenWidth := int32(800) - screenHeight := int32(450) - - rl.InitWindow(screenWidth, screenHeight, "Bouncing Ball in Go - raylib") - defer rl.CloseWindow() - - rl.SetTargetFPS(60) - - // Ball properties - ballRadius := float32(20) - ballPos := rl.NewVector2(float32(screenWidth)/2.0, float32(screenHeight)/2.0) - ballSpeed := rl.NewVector2(4, 3) - ballColor := rl.Red + rl.InitWindow(WIDTH, HEIGHT, "raymarching") + texture := rl.LoadTextureFromImage(genImage()) for !rl.WindowShouldClose() { - // Update - ballPos.X += ballSpeed.X - ballPos.Y += ballSpeed.Y - - // Bounce on edges - if ballPos.X >= float32(screenWidth)-ballRadius || ballPos.X <= ballRadius { - ballSpeed.X *= -1 - } - if ballPos.Y >= float32(screenHeight)-ballRadius || ballPos.Y <= ballRadius { - ballSpeed.Y *= -1 - } - - // Draw rl.BeginDrawing() - rl.ClearBackground(rl.RayWhite) - - rl.DrawText("Bouncing Ball Example", 10, 10, 20, rl.DarkGray) - rl.DrawCircleV(ballPos, ballRadius, ballColor) - + rl.ClearBackground(rl.Black) + rl.DrawTexture(texture, 0, 0, rl.White) rl.EndDrawing() } } + +// func main() { +// screenWidth := int32(800) +// screenHeight := int32(450) + +// rl.InitWindow(screenWidth, screenHeight, "Bouncing Ball in Go - raylib") +// defer rl.CloseWindow() + +// rl.SetTargetFPS(60) + +// // Ball properties +// ballRadius := float32(20) +// ballPos := rl.NewVector2(float32(screenWidth)/2.0, float32(screenHeight)/2.0) +// ballSpeed := rl.NewVector2(4, 3) +// ballColor := rl.Red + +// for !rl.WindowShouldClose() { +// // Update +// ballPos.X += ballSpeed.X +// ballPos.Y += ballSpeed.Y + +// // Bounce on edges +// if ballPos.X >= float32(screenWidth)-ballRadius || ballPos.X <= ballRadius { +// ballSpeed.X *= -1 +// } +// if ballPos.Y >= float32(screenHeight)-ballRadius || ballPos.Y <= ballRadius { +// ballSpeed.Y *= -1 +// } + +// // Draw +// rl.BeginDrawing() +// rl.ClearBackground(rl.RayWhite) + +// rl.DrawText("Bouncing Ball Example", 10, 10, 20, rl.DarkGray) +// rl.DrawCircleV(ballPos, ballRadius, ballColor) + +// rl.EndDrawing() +// } +// } diff --git a/sdf.go b/sdf.go index 9c70ab9..89f51d5 100644 --- a/sdf.go +++ b/sdf.go @@ -2,8 +2,6 @@ package main import "math" -const EPS = 0.001 - type SDF interface { Distance(Vector3) (float64, Color) } diff --git a/vec3.go b/vec3.go index 0e1729d..8bc99af 100644 --- a/vec3.go +++ b/vec3.go @@ -92,8 +92,20 @@ func (v Vector3) Unpack() (float64, float64, float64) { return v.X, v.Y, v.Z } -// Others maths thingies +// Return position, and units vectors of spherical into cartesian coords (r, theta, phi) units vecs +func (v Vector3) SphericalToCartesian() (Vector3, Vector3, Vector3, Vector3) { + p, theta, phi := v.Unpack() + cos_theta, sin_theta := math.Cos(theta), math.Sin(theta) + cos_phi, sin_phi := math.Cos(phi), math.Sin(phi) + cart_pos := Vector3{p * cos_theta * cos_phi, p * sin_theta * sin_phi, p * cos_theta} + unit_vec_r := Vector3{sin_theta * cos_phi, sin_theta * cos_phi, cos_theta} + unit_vec_theta := Vector3{cos_theta * cos_phi, cos_theta * sin_phi, -sin_theta} + unit_vec_phi := Vector3{-sin_phi, cos_phi, 0} + return cart_pos, unit_vec_r, unit_vec_theta, unit_vec_phi +} + +// Others maths thingies func Clamp(x float64, a float64, b float64) float64 { return min(max(x, a), b) }