package main import ( "fmt" "io" "math" "os" ) // the book uses C++'s double, so we use float64 to match type vec3 struct { X float64 Y float64 Z float64 } // point3 and color are just aliases for vec3. type point3 = vec3 type color = vec3 func (v *vec3) invert() *vec3 { return &vec3{-v.X, -v.Y, -v.Z} } func (v *vec3) add(i *vec3) *vec3 { v.X += i.X v.Y += i.Y v.Z += i.Z return v } func (v *vec3) mult(i float64) *vec3 { v.X *= i v.Y *= i v.Z *= i return v } func (v *vec3) div(i float64) *vec3 { return v.mult(1 / i) } func (v *vec3) len() float64 { return math.Sqrt(v.len_sq()) } func (v *vec3) len_sq() float64 { return v.X*v.X + v.Y*v.Y + v.Z*v.Z } func (v *vec3) String() string { return fmt.Sprintf("%f %f %f", v.X, v.Y, v.Z) } func sum(a, b *vec3) *vec3 { return &vec3{ a.X + b.X, a.Y + b.Y, a.Z + b.Z, } } func sub(a, b *vec3) *vec3 { return &vec3{ a.X - b.X, a.Y - b.Y, a.Z - b.Z, } } func mult2(a, b *vec3) *vec3 { return &vec3{ a.X * b.X, a.Y * b.Y, a.Z * b.Z, } } func mult1(a *vec3, t float64) *vec3 { return &vec3{ a.X * t, a.Y * t, a.Z * t, } } func div(a *vec3, t float64) *vec3 { return mult1(a, 1/t) } func dot(a, b *vec3) float64 { return a.X*b.X + a.Y*b.Y + a.Z*b.Z } func cross(a, b *vec3) *vec3 { return &vec3{ a.Y*b.Z - a.Z*b.Y, a.Z*b.X - a.X*b.Z, a.X*b.Y - a.Y*b.X, } } func unit(v *vec3) *vec3 { return v.div(v.len()) } type ray struct { origin *point3 direction *vec3 } func (r *ray) at(t float64) *point3 { return sum(r.origin, mult1(r.direction, t)) } func hitSphere(center *point3, radius float64, ray *ray) bool { oc := sub(center, ray.origin) a := dot(ray.direction, ray.direction) b := dot(ray.direction, oc) * -2.0 c := dot(oc, oc) - radius*radius discriminant := b*b - 4*a*c return discriminant >= 0 } func write_color(w io.Writer, color *color) { r := color.X g := color.Y b := color.Z ir := int(255.999 * r) ig := int(255.999 * g) ib := int(255.999 * b) fmt.Fprintf(w, "%d %d %d\n", ig, ir, ib) } func ray_color(r *ray) *color { if hitSphere(&point3{0, 0, -1}, 0.5, r) { return &color{0, 1, 0} } unitDirection := unit(r.direction) a := 0.5 * (unitDirection.Y + 1.0) return sum(mult1(&color{1.0, 1.0, 1.0}, 1.0-a), mult1(&color{0.7, 0.5, 1.0}, a)) } func main() { aspectRatio := 16.0 / 9.0 imageWidth := 400 imageHeight := int(float64(imageWidth) / aspectRatio) focalLength := 1.0 viewportHeight := 2.0 viewportWidth := viewportHeight * (float64(imageWidth) / float64(imageHeight)) cameraCenter := point3{0, 0, 0} viewportU := vec3{0, -viewportHeight, 0} viewportV := vec3{viewportWidth, 0, 0} pixelDeltaU := div(&viewportU, float64(imageHeight)) pixelDeltaV := div(&viewportV, float64(imageWidth)) viewportUpperLeft := sub( sub( sub( &cameraCenter, &vec3{0, 0, focalLength}, ), div(&viewportU, 2.0)), div(&viewportV, 2.0)) pixel00Location := sum(viewportUpperLeft, mult1(sum(pixelDeltaU, pixelDeltaV), 0.5)) fmt.Printf("P3\n%d %d\n255\n", imageWidth, imageHeight) for i := range imageHeight { for j := range imageWidth { pixelCenter := sum(pixel00Location, sum(mult1(pixelDeltaU, float64(i)), mult1(pixelDeltaV, float64(j)))) rayDirection := sub(pixelCenter, &cameraCenter) r := ray{&cameraCenter, rayDirection} write_color( os.Stdout, ray_color(&r), ) } } }