Thursday, September 11, 2014

A simple ambient occlusion ray trace in Swift

I got my hobby ray tracer producing images.   It is not true diffuse ambient occlusion because the rays are uniform on the hemisphere rather than a cosine distribution.  I include it as one big file because xcode has been under rapid evolution lately.   Here's a 100 sample image.  All at pixel center so not antialiased.


And here's all the code cut-and-pastable into xcode.  You can run it as an executable from the terminal (xcode shows the path as shown)


//
//  main.swift
//
//  Created by Peter Shirley on 7/20/14.
//  This work is in the public domain
//

import Foundation



protocol hitable {
    func hit(r : ray, tmin : Double) -> (Bool, Double, vec3)
}


class sphere : hitable  {
    var center : vec3 = vec3(x: 0.0, y: 0.0, z: 0.0)
    var radius : Double  = 0.0
    func hit(r : ray, tmin : Double) -> (Bool, Double, vec3) {
       
        var A : Double = dot(r.direction, r.direction)
        var B : Double = 2.0*dot(r.direction,r.origin - center)
        var C : Double = dot(r.origin - center,r.origin - center) - radius*radius
        let discriminant = B*B - 4.0*A*C
        if discriminant > 0 {
            var t : Double = (-B - sqrt(discriminant) ) / (2.0*A)
            if t < tmin {
                t  = (-B + sqrt(discriminant)) / (2.0*A)
            }
            return (t > tmin, t, r.location_at_parameter(t) - center)
        } else {
            return (false, 0.0, vec3(x:1.0, y:0.0, z:0.0))
        }
    }
   
}

class hitable_list : hitable  {
    var members : [hitable] = []
    func add(h : hitable) {
        members.append(h)
    }
    func hit(r : ray, tmin : Double) -> (Bool, Double, vec3) {
        var t_clostest_so_far = 1.0e6 // not defined: Double.max
        var hit_anything : Bool = false
        var new_t : Double
        var hit_it : Bool
        var normal : vec3 = vec3(x:1.0, y:0.0, z:0.0)
        var new_normal : vec3
       
        for item in members {
            (hit_it , new_t, new_normal) = item.hit(r, tmin: tmin)
            if (hit_it && new_t < t_clostest_so_far) {
                hit_anything = true
                t_clostest_so_far = new_t
                normal = new_normal
            }
        }
        return (hit_anything, t_clostest_so_far, normal)
    }
   
}



// implicit type inference.  Swift when in doubt assumes Double
struct vec3 : Printable {
    var x = 0.0, y = 0.0, z = 0.0
    var description : String {
        return "hello"
    }
}

func * (left: Double, right: vec3) -> vec3 {
    return vec3(x: left * right.x, y: left * right.y, z: left * right.z)
}

func / (left: Double, right: vec3) -> vec3 {
    return vec3(x: left / right.x, y: left / right.y, z: left / right.z)
}

func * (left: vec3, right: Double) -> vec3 {
    return vec3(x: left.x * right, y: left.y * right, z: left.z * right)
}

func / (left: vec3, right: Double) -> vec3 {
    return vec3(x: left.x / right, y: left.y / right, z: left.z / right)
}

func + (left: vec3, right: vec3) -> vec3 {
    return vec3(x: left.x + right.x, y: left.y + right.y, z: left.z + right.z)
}

func - (left: vec3, right: vec3) -> vec3 {
    return vec3(x: left.x - right.x, y: left.y - right.y, z: left.z - right.z)
}

func * (left: vec3, right: vec3) -> vec3 {
    return vec3(x: left.x * right.x, y: left.y * right.y, z: left.z * right.z)
}

func / (left: vec3, right: vec3) -> vec3 {
    return vec3(x: left.x / right.x, y: left.y / right.y, z: left.z / right.z)
}

func max(v: vec3) -> Double {
    if v.x > v.y && v.x > v.z {
        return v.x
    }
    else if v.y > v.x && v.y > v.z {
        return v.y
    }
    else {
        return v.z
    }
}

func min(v: vec3) -> Double {
    if v.x < v.y && v.x < v.z {
        return v.x
    }
    else if v.y < v.x && v.y < v.z {
        return v.y
    }
    else {
        return v.z
    }
}

func dot (left: vec3, right: vec3) -> Double {
    return left.x * right.x + left.y * right.y + left.z * right.z
}

func cross (left: vec3, right: vec3) -> vec3 {
    return vec3(x: left.y * right.z - left.z * right.y, y: left.z * right.x - left.z * right.x, z: left.x * right.y - left.y * right.x)
}

func unit_vector(v: vec3) -> vec3 {
    var length : Double = sqrt(dot(v, v))
    return vec3(x: v.x/length, y: v.y/length, z: v.z/length)
}

protocol Printable {
    var description: String { get }
}



struct ray  {
    var origin : vec3
    var direction : vec3
    func location_at_parameter(t: Double) -> vec3 {
        return origin + t*direction
    }
}




var u : vec3 = vec3(x: 1.0, y: 0.0, z: 0.0);
var v : vec3 = vec3(x: 0.0, y: 1.0, z: 0.0);
var w : vec3 = cross(u, v)
var n : vec3 = u

var spheres : hitable_list = hitable_list()

var my_sphere1 : sphere = sphere()
my_sphere1.center = vec3(x: 0.0, y: 0.0, z: 0.0)
my_sphere1.radius = 1.0
spheres.add(my_sphere1)

var my_sphere2 : sphere = sphere()
my_sphere2.center = vec3(x: 2.0, y: 0.0, z: 2.0)
my_sphere2.radius = 1.0
spheres.add(my_sphere2)

var my_sphere3 : sphere = sphere()
my_sphere3.center = vec3(x: -2.0, y: 0.0, z: 2.0)
my_sphere3.radius = 1.0
spheres.add(my_sphere3)

var my_sphere4 : sphere = sphere()
my_sphere4.center = vec3(x: 0.0, y: -1001.0, z: 0.0)
my_sphere4.radius = 1000.0
spheres.add(my_sphere4)





let the_world : hitable = spheres

let ray_origin = vec3(x: 0.0, y: 2.5, z: -10.0)
let nx = 256
let ny = 256
let ns = 100

println("P3");
println ("\(nx) \(ny)");
println ("255");

for j in 0...255 {
    for i in 0...255 {
        var accum :vec3 =  vec3(x: 0.0, y: 0.0, z: 0.0)
        var red = 0, green = 0, blue = 0
        for s in 1...ns {
           
            var attenuation : vec3 =  vec3(x: 1.0, y: 1.0, z: 1.0)
           
            var not_yet_missed : Bool = true
            var ray_target : vec3 = vec3(x: -1.0 + 2.0*Double(i)/Double(nx),  y: 2.5 + -1.0 + 2.0*Double(255-j)/Double(ny), z: -8.0)
            var the_ray : ray = ray(origin: ray_origin, direction: ray_target-ray_origin)
            while not_yet_missed {
                let hit_info  = the_world.hit(the_ray, tmin: 0.01)
               
                if hit_info.0 {
                    attenuation = 0.5*attenuation
                    let new_ray_origin = the_ray.location_at_parameter(hit_info.1)
                    var scattered_direction : vec3 = hit_info.2
                    do  {
                        do {
                           
                            scattered_direction = vec3(x: -1.0 + 2.0*drand48(), y: -1.0 + 2.0*drand48(), z: -1.0 + 2.0*drand48())
                        } while dot(scattered_direction,scattered_direction) > 1.0
                       
                    } while dot(scattered_direction, hit_info.2) < 0.001
                    the_ray = ray(origin: new_ray_origin, direction: scattered_direction)
                }
                else {
                    not_yet_missed = false
                }
               
               
            }
            accum = accum + attenuation
        }
        red = Int(255.5*accum.x/Double(ns))
        green = Int(255.5*accum.y/Double(ns))
        blue = Int(255.5*accum.z/Double(ns))
       
        println("\(red) \(green) \(blue)");
    }
}











1 comment:

Jerry M. Mingus said...

After a long time I got something fresh and quality content on massage therapy services. I searched a lot for the related material but got almost replica work. Keep it up! It is really very informative. clipping path service