// translated from http://www.ffconsultancy.com/languages/ray_tracer/code/1/ray.java // by Derek Young - Sep 6, 07 import java.io._ import java.lang.Math object scalaRay { val delta=Math.sqrt(Math.ulp(1.0)) val infinity=java.lang.Float.POSITIVE_INFINITY class Vec(val x: Double, val y: Double, val z: Double) def add(a: Vec, b: Vec) = new Vec(a.x+b.x, a.y+b.y, a.z+b.z) def sub(a: Vec, b: Vec) = new Vec(a.x-b.x, a.y-b.y, a.z-b.z) def scale(s: Double, a: Vec) = new Vec(s*a.x, s*a.y, s*a.z) def dot(a: Vec, b: Vec) = a.x*b.x + a.y*b.y + a.z*b.z def unitise(a: Vec) = scale(1 / Math.sqrt(dot(a, a)), a) class Ray(val orig: Vec, val dir: Vec) class Hit(val lambda: Double, val normal: Vec) abstract class Scene { def intersect(i: Hit, ray: Ray) : Hit } class Sphere(val center: Vec, val radius: Double) extends Scene { def ray_sphere(ray: Ray): Double = { val v = sub(center, ray.orig) val b = dot(v, ray.dir) val disc = b*b - dot(v, v) + radius*radius if (disc < 0) return infinity val d = Math.sqrt(disc) val t2 = b+d if (t2 < 0) return infinity val t1 = b-d if (t1 > 0) t1 else t2 } def intersect(i: Hit, ray: Ray): Hit = { val l = ray_sphere(ray) if (l >= i.lambda) return i val n = add(ray.orig, sub(scale(l, ray.dir), center)) new Hit(l, unitise(n)) } } class Group(val bound: Sphere) extends Scene { var objs : List[Scene] = Nil def intersect(i: Hit, ray: Ray): Hit = { val l = bound.ray_sphere(ray) if (l >= i.lambda) return i var hit = i for (scene <- objs) hit = scene.intersect(hit, ray) hit } } def ray_trace(light: Vec, ray: Ray, scene: Scene) : Double = { val i = scene.intersect(new Hit(infinity, new Vec(0, 0, 0)), ray) if (i.lambda == infinity) return 0 val o = add(ray.orig, add(scale(i.lambda, ray.dir), scale(delta, i.normal))) val g = dot(i.normal, light) if (g >= 0) return 0. val sray = new Ray(o, scale(-1, light)) val si = scene.intersect(new Hit(infinity, new Vec(0, 0, 0)), sray) if (si.lambda == infinity) -g else 0 } def create(level: Int, c: Vec, r: Double): Scene = { val sphere = new Sphere(c, r) if (level == 1) return sphere val group = new Group(new Sphere(c, 3*r)) group.objs = group.objs ::: List(sphere) val rn = 3*r/Math.sqrt(12) for { dz <- List(-1, 1) dx <- List(-1, 1) } { val c2 = new Vec(c.x+dx*rn, c.y+rn, c.z+dz*rn) group.objs = group.objs ::: List(create(level - 1, c2, r / 2)) } group } def run(n: int, level: int, ss: int) : Unit = { val scene = create(level, new Vec(0, -1, 0), 1) val out = new FileOutputStream("scalaimage.pgm") out.write(("P5\n"+n+" "+n+"\n255\n").getBytes()) for { yi <- 0 to (n - 1) x <- 0 to (n - 1) } { val y = (n - 1) - yi var g : Double = 0 for { dx <- 0 to (ss - 1) dy <- 0 to (ss - 1) } { val d = new Vec(x+dx*1./ss-n/2., y+dy*1./ss-n/2., n) val ray = new Ray(new Vec(0, 0, -4), unitise(d)) g += ray_trace(unitise(new Vec(-1, -3, 2)), ray, scene) } out.write((.5+255.*g/(ss*ss)).toInt); } out.close() } def main(args: Array[String]) { run(Integer.parseInt(args(1)), Integer.parseInt(args(0)), 4) } }