package main import "fmt" // for IO and standard library import "os" // for handling the progress bar import "math" // for maths import "unsafe" // ================ VEC3 CLASS ===================== type Vec3 struct { E [3]float32 } //Basic vector functions func NewVec3(e0, e1, e2 float32) Vec3 { return Vec3{E: [3]float32{e0, e1, e2}} } func (v Vec3) X() float32 { return v.E[0] } // can be executed as v.X() in main func (v Vec3) Y() float32 { return v.E[1] } func (v Vec3) Z() float32 { return v.E[2] } func (v Vec3) Neg() Vec3 { return NewVec3(-v.E[0], -v.E[1], -v.E[2]) } func (v Vec3) Get(i int) float32 { return v.E[i] } func (v *Vec3) Set(i int, val float32) { v.E[i] = val } func (v Vec3) Add(v2 Vec3) Vec3 { return NewVec3(v.E[0]+v2.E[0], v.E[1]+v2.E[1], v.E[2]+v2.E[2]) } func (v Vec3) Mult(t float32) Vec3 { return NewVec3(v.E[0]*t, v.E[1]*t, v.E[2]*t) } func (v Vec3) Div(t float32) Vec3 { if t != 0 { return NewVec3(v.E[0] / t, v.E[1] / t, v.E[2] / t) } return v } func (v Vec3) Length() float32 { return float32(math.Sqrt(float64(v.E[0]*v.E[0] + v.E[1]*v.E[1] + v.E[2]*v.E[2]))) } func (v Vec3) Length_squared() float32 { return v.E[0]*v.E[0] + v.E[1]*v.E[1] + v.E[2]*v.E[2] } // Vector utility functions func (v Vec3) String() string { return fmt.Sprintf("%v %v %v", v.E[0], v.E[1], v.E[2]) } func (v Vec3) Sub(v2 Vec3) Vec3 { return NewVec3(v.E[0]-v2.E[0], v.E[1]-v2.E[1], v.E[2]-v2.E[2]) } func (v Vec3) MultVec(v2 Vec3) Vec3 { return NewVec3(v.E[0]*v2.E[0], v.E[1]*v2.E[1], v.E[2]*v2.E[2]) } func (v Vec3) DivVec(v2 Vec3) Vec3 { if v2.E[0] != 0 && v2.E[1] != 0 && v2.E[2] != 0 { return NewVec3(v.E[0]/v2.E[0], v.E[1]/v2.E[1], v.E[2]/v2.E[2]) } return v } func Dot(v1 Vec3, v2 Vec3) float32 { return (v1.E[0]*v2.E[0] + v1.E[1]*v2.E[1] + v1.E[2]*v2.E[2]) } func Cross(v1 Vec3, v2 Vec3) Vec3 { return NewVec3(v1.E[1]*v2.E[2] - v1.E[2]*v2.E[1], v1.E[2]*v2.E[0] - v1.E[0]*v2.E[2], v1.E[0]*v2.E[1] - v1.E[1]*v2.E[0]) } func q_rsqrt(v Vec3) float32 { var x float32 = v.E[0]*v.E[0] + v.E[1]*v.E[1] + v.E[2]*v.E[2] i := *(*int32)(unsafe.Pointer(&x)) // evil floating point bit level hacking i = 0x5f3759df - (i >> 1) // what the fuck? y := *(*float32)(unsafe.Pointer(&i)) return y } func Unit_vector(v Vec3) Vec3 { new_v := v.Mult(q_rsqrt(v)) return new_v } const pi float32 = 3.1415926535897932385 func Degrees_to_radians(degrees float32) float32 { return (degrees * pi) / 180.0 } // ============== COLOUR CLASS ============== func Write_color(v Vec3) { fmt.Println(int(255.999*v.E[0]), int(255.999*v.E[1]), int(255.999*v.E[2])) } func NewColor(e0, e1, e2 float32) Vec3 { return Vec3{E: [3]float32{e0, e1, e2}} } // ============== RAY CLASS ================= func NewPoint3(e0, e1, e2 float32) Vec3 { return Vec3{E: [3]float32{e0, e1, e2}} } type Ray struct { Orig Vec3 Dir Vec3 } func NewRay(orig Vec3, dir Vec3) *Ray { return &Ray{Orig: orig, Dir: dir} } func (r *Ray) Origin() Vec3 { return r.Orig } func (r *Ray) Direction() Vec3 { return r.Dir } func (r *Ray) At(t float32) Vec3 { return Vec3{ E: [3]float32{ r.Orig.X() + t*r.Dir.X(), r.Orig.Y() + t*r.Dir.Y(), r.Orig.Z() + t*r.Dir.Z(), }, } } // =============== HIT ====================== type Hit_record struct { p Vec3 normal Vec3 t float32 front_face bool } func (rec *Hit_record) Set_face_normal(r *Ray, outward_normal Vec3) { if (Dot(r.Direction(), outward_normal) < 0) { rec.front_face = true } if (rec.front_face) { rec.normal = outward_normal } else { rec.normal = outward_normal.Neg() } } // =============== SPHERE =================== type Sphere struct { center Vec3 radius float32 } func (s Sphere) Hit_sphere(r *Ray, ray_tmin float32, ray_tmax float32, rec *Hit_record) bool { oc := r.Origin().Sub(s.center) a := r.Direction().Length_squared() half_b := Dot(oc, r.Direction()) c := oc.Length_squared() - s.radius*s.radius discriminant := half_b*half_b - a*c if (discriminant < 0) { return false } sqrtd := float32(math.Sqrt(float64(discriminant))) // find the nearest root that lies in the acceptable range root := (-half_b - sqrtd) / a if (root <= ray_tmin || ray_tmax<= root) { root = (sqrtd-half_b) / a if (root <= ray_tmin || ray_tmax <= root) { return false } } rec.t = root rec.p = r.At(rec.t) outward_normal := rec.p.Sub(s.center).Div(s.radius) rec.Set_face_normal(r, outward_normal) return true } // =============== HITTABLE ================== type Hittable struct { spheres []Sphere } func NewHittable() *Hittable { return &Hittable{} } func (hl *Hittable) Add(s Sphere) { hl.spheres = append(hl.spheres, s) } func (hl *Hittable) Clear() { hl.spheres = []Sphere{} } func (hl Hittable) Hit(r *Ray, ray_tmin float32, ray_tmax float32, rec *Hit_record) bool { var temp_rec Hit_record var hit_anything bool = false var closest_so_far float32 = ray_tmax for _, sphere := range hl.spheres { if (sphere.Hit_sphere(r, ray_tmin, closest_so_far, &temp_rec)) { hit_anything = true closest_so_far = temp_rec.t *rec = temp_rec } } return hit_anything } func Ray_color(r Ray, world *Hittable) Vec3 { var rec Hit_record if (world.Hit(&r, 0, float32(math.Inf(1)), &rec)) { return rec.normal.Add(NewColor(1,1,1)).Mult(0.5) } unit_direction := Unit_vector(r.Direction()) a := (unit_direction.Y() + 1.0)*0.5 return NewColor(1.0,1.0,1.0).Mult(float32(1.0-a)).Add(NewColor(0.5,0.7,1.0).Mult(a)) } // =============== MAIN ===================== func main() { // Image aspect_ratio := 16.0 / 9.0; var image_width int = 400; // Calculate the image height var image_height int = int(float64(image_width) / aspect_ratio) if image_height < 1 { image_height = 1 } // World world := NewHittable() world.Add(Sphere{ center: NewVec3(0, 0, -1), radius: 0.5, }) world.Add(Sphere{ center: NewVec3(0, -100.5, -1), radius: 100, }) // Camera var focal_length float32 = 1.0 var viewport_height float32 = 2.0 var viewport_width float32 = viewport_height * float32(image_width)/float32(image_height) camera_center := NewVec3(0,0,0) // Calculate the vectors across the horizontal and down the vertical viewport edges viewport_u := NewVec3(viewport_width, 0, 0) viewport_v := NewVec3(0, -viewport_height, 0) //Calculate the horizontal and vertical delta vectors from pixel to pixel pixel_delta_u := viewport_u.Div(float32(image_width)) pixel_delta_v := viewport_v.Div(float32(image_height)) // Calculate the location of the upper left pixel viewport_upper_left := camera_center.Sub(camera_center).Sub(NewVec3(0, 0, focal_length)).Sub(viewport_u.Div(2)).Sub(viewport_v.Div(2)) pixel00_loc := viewport_upper_left.Add((pixel_delta_u.Add(pixel_delta_v)).Div(2)) // Rendering fmt.Println("P3") fmt.Println(image_width, " ", image_height, "\n255") for j := 0; j < image_height; j++ { fmt.Fprintf(os.Stderr, "\rScanlines remaining: %d ", image_height-j) for i := 0; i < image_width; i++ { pixel_center := pixel00_loc.Add(pixel_delta_u.Mult(float32(i))).Add(pixel_delta_v.Mult(float32(j))) ray_direction := pixel_center.Sub(camera_center) r := NewRay(camera_center, ray_direction) pixel_color := Ray_color(*r, world) Write_color(pixel_color) } } // INFO: The pixels are written out in rows. // Image file can be created with // go run main.go > image.ppm }