git.haldean.org monte / cda20d7
Initial sphere projection rendering woohoo Will Brown 9 years ago
6 changed file(s) with 205 addition(s) and 9 deletion(s). Raw diff Collapse all Expand all
33 "flag"
44 "fmt"
55 "image"
6 "image/color"
76 "image/png"
7 "monte"
88 "os"
99 )
1010
1212 var Height = flag.Int("height", 300, "height of output image")
1313 var Output = flag.String("output", "output.png", "output PNG file")
1414
15 func NewColor(r, g, b, a uint8) color.Color {
16 return color.NRGBA{R: r, G: g, B: b, A: a}
17 }
18
1915 func main() {
2016 flag.Parse()
2117
2218 println("Monte is go.")
19
20 geom := []monte.Primitive{monte.Sphere{Center: monte.Vect(0, 0, 4), Radius: 1.0}}
21
22 scene := monte.Scene{
23 Geom: geom,
24 Look: monte.NewRay(monte.Vect(0, 0, 0), monte.Vect(0, 0, 1)),
25 U1: monte.Vect(1, 0, 0),
26 U2: monte.Vect(0, 1, 0),
27 FDist: 1}
28
2329 img := image.NewNRGBA(image.Rect(0, 0, *Width, *Height))
30 w, h := float64(*Width), float64(*Height)
2431 for i := 0; i < *Width; i++ {
32 u := (float64(i) - w / 2.0) / (w / 2.0)
2533 for j := 0; j < *Height; j++ {
26 img.Set(i, j, NewColor(255, 0, 0, 255))
34 v := (float64(j) - h / 2.0) / (h / 2.0)
35 go scene.SetColor(i, j, u, v, img)
2736 }
2837 }
2938
30 w, err := os.Create(*Output)
39 out, err := os.Create(*Output)
3140 if err != nil {
3241 fmt.Printf("Could not open output file: %v\n", err)
3342 return
3443 }
35 png.Encode(w, img)
36 w.Close()
44 png.Encode(out, img)
45 out.Close()
3746 }
0 package monte
1
2 type Primitive interface {
3 // Find the intersection between the geometric primitive and the given ray.
4 // Returns the point of intersection and the distance travelled along the
5 // direction vector. Returns (nil, 0) if no intersection.
6 Intersect(ray *Ray) (*Vector, float64)
7
8 // Find the normal vector to the geometric primitive at a point on its
9 // surface. This function should only be called with points on its surface; if
10 // not its behavior is undefined.
11 Normal(loc *Vector) *Vector
12 }
0 package monte
1
2 import (
3 "image"
4 "image/color"
5 "math"
6 )
7
8 func NewColor(r, g, b, a uint8) color.Color {
9 return color.NRGBA{R: r, G: g, B: b, A: a}
10 }
11
12 type Scene struct {
13 Geom []Primitive
14 Look *Ray
15 // The image vector pair; these orthogonal vectors form the UV-space which we
16 // scan to fill in the picture
17 U1, U2 *Vector
18 FDist float64
19 }
20
21 func (s *Scene) DirectionAt(u, v float64) *Vector {
22 u1, u2 := s.U1.ScalarMul(u), s.U2.ScalarMul(v)
23 return s.Look.Direction.ScalarMul(s.FDist).Add(u1).Add(u2).NormalizeInPlace()
24 }
25
26 func (s *Scene) ColorAt(u, v float64) color.Color {
27 ray := NewRay(s.Look.Origin, s.DirectionAt(u, v))
28
29 minDist := math.Inf(1)
30 var minGeom *Primitive = nil
31
32 for _, geom := range s.Geom {
33 i, d := geom.Intersect(ray)
34 if i != nil && d < minDist {
35 minDist = d
36 minGeom = &geom
37 }
38 }
39
40 if minGeom != nil {
41 return NewColor(255, 255, 255, 255)
42 }
43 return NewColor(0, 0, 0, 255)
44 }
45
46 func (s *Scene) SetColor(i, j int, u, v float64, img *image.NRGBA) {
47 img.Set(i, j, s.ColorAt(u, v))
48 }
0 package monte
1
2 import (
3 "math"
4 )
5
6 type Sphere struct {
7 Center *Vector
8 Radius float64
9 }
10
11 // finds the two x's such that ax^2 + bx + c = 0
12 func quadSolve(a, b, c float64) (float64, float64) {
13 disc := math.Sqrt(b * b - 4 * a * c)
14 return (-b + disc) / (2 * a), (-b - disc) / (2 * a)
15 }
16
17 // Finds the intersection of a sphere with a ray. Returns nil if no intersection
18 func (s Sphere) Intersect(ray *Ray) (*Vector, float64) {
19 a := ray.Direction.NormSqr()
20 b := 2 * (ray.Origin.Dot(ray.Direction) - s.Center.Dot(ray.Direction))
21 c := ray.Origin.NormSqr() + s.Center.NormSqr() -
22 2 * ray.Origin.Dot(s.Center) - math.Pow(s.Radius, 2)
23
24 s1, s2 := quadSolve(a, b, c)
25 switch {
26 case math.IsNaN(s1) && math.IsNaN(s2):
27 return nil, 0
28 case math.IsNaN(s1):
29 return ray.Origin.Add(ray.Direction.ScalarMul(s2)), s2
30 case math.IsNaN(s2):
31 return ray.Origin.Add(ray.Direction.ScalarMul(s1)), s1
32 default:
33 if s1 < s2 {
34 return ray.Origin.Add(ray.Direction.ScalarMul(s1)), s1
35 } else {
36 return ray.Origin.Add(ray.Direction.ScalarMul(s2)), s2
37 }
38 }
39
40 return nil, 0
41 }
42
43 func (s Sphere) Normal(loc *Vector) *Vector {
44 return Vect(0, 0, 0)
45 }
0 package monte
1
2 import (
3 "testing"
4 )
5
6 func TestSphereIntersect(t *testing.T) {
7 s := Sphere{Center: Vect(2, 3, 1), Radius: 3}
8
9 r1 := NewRay(Vect(2, 3, 7), Vect(0, 0, -1))
10 if isect, _ := s.Intersect(r1); *isect != *Vect(2, 3, 4) {
11 t.Errorf("Bad intersection: expected 2, 3, 4 but got %v", isect)
12 }
13
14 r2 := NewRay(Vect(2, 6, 7), Vect(0, 0, -1))
15 if isect, _ := s.Intersect(r2); *isect != *Vect(2, 6, 1) {
16 t.Errorf("Bad intersection: expected 2, 6, 1 but got %v", isect)
17 }
18 }
0 package monte
1
2 import "math"
3
4 // Vector
5
6 type Vector struct {
7 X, Y, Z float64
8 }
9
10 func Vect(x, y, z float64) *Vector {
11 return &Vector{X: x, Y: y, Z: z}
12 }
13
14 func (v *Vector) Add(w *Vector) *Vector {
15 return &Vector{X: v.X + w.X, Y: v.Y + w.Y, Z: v.Z + w.Z}
16 }
17
18 func (v *Vector) Copy() *Vector {
19 return &Vector{X: v.X, Y: v.Y, Z: v.Z}
20 }
21
22 func (v *Vector) Dot(w *Vector) float64 {
23 return v.X * w.X + v.Y * w.Y + v.Z * w.Z
24 }
25
26 func (v *Vector) Norm() float64 {
27 return math.Sqrt(v.NormSqr())
28 }
29
30 func (v *Vector) NormSqr() float64 {
31 return v.Dot(v)
32 }
33
34 func (v *Vector) Normalize() *Vector {
35 return v.Copy().NormalizeInPlace()
36 }
37
38 // Returns the changed vector for easy chaining in expressions
39 func (v *Vector) NormalizeInPlace() *Vector {
40 norm := v.Norm()
41 v.X /= norm
42 v.Y /= norm
43 v.Z /= norm
44 return v
45 }
46
47 func (v *Vector) ScalarMul(s float64) *Vector {
48 return &Vector{X: v.X * s, Y: v.Y * s, Z: v.Z * s}
49 }
50
51 // Ray
52
53 type Ray struct {
54 Origin, Direction *Vector
55 }
56
57 func NewRay(o, d *Vector) *Ray {
58 return &Ray{Origin: o, Direction: d.Normalize()}
59 }