Skip to content

Instantly share code, notes, and snippets.

@thunder9
Last active December 10, 2015 07:58
Show Gist options
  • Save thunder9/4404365 to your computer and use it in GitHub Desktop.
Save thunder9/4404365 to your computer and use it in GitHub Desktop.
A port of AO render benchmark to CoffeeScript
###
A port of AO render benchmark to CoffeeScript
by thunder9 (https://github.com/thunder9)
Original program (C) Syoyo Fujita in Javascript (and other languages)
http://lucille.atso-net.jp/blog/?p=642
http://lucille.atso-net.jp/blog/?p=711
###
IMAGE_WIDTH = 256
IMAGE_HEIGHT = 256
NSUBSAMPLES = 2
NAO_SAMPLES = 8
class Vec
constructor: (@x, @y, @z) ->
vadd: (b) ->
new Vec @x + b.x, @y + b.y, @z + b.z
vsub: (b) ->
new Vec @x - b.x, @y - b.y, @z - b.z
vcross: (b) ->
new Vec @y * b.z - @z * b.y, @z * b.x - @x * b.z, @x * b.y - @y * b.x
vdot: (b) ->
r = @x * b.x + @y * b.y + @z * b.z
vlength: ->
Math.sqrt @x * @x + @y * @y + @z * @z
vnormalize: ->
len = @vlength()
v = new Vec @x, @y, @z
if len > 1.0e-17
v.x = v.x / len
v.y = v.y / len
v.z = v.z / len
v
class Sphere
constructor: (@center, @radius) ->
intersect: (ray, isect) ->
rs = ray.org.vsub @center
b = rs.vdot ray.dir
c = (rs.vdot rs) - @radius * @radius
d = b * b - c
if d > 0.0
t = - b - Math.sqrt d
if t > 0.0 and t < isect.t
isect.t = t
isect.hit = true
isect.pl = new Vec ray.org.x + ray.dir.x * t, ray.org.y + ray.dir.y * t, ray.org.z + ray.dir.z * t
n = isect.pl.vsub @center
isect.n = n.vnormalize()
return
class Plane
constructor: (@p, @n) ->
intersect: (ray, isect) ->
d = -@p.vdot @n
v = ray.dir.vdot @n
v0 = v
if v < 0
v0 = -v
if v0 < 1.0e-17
return
t = -((ray.org.vdot @n) + d) / v
if t > 0 and t < isect.t
isect.hit = true
isect.t = t
isect.n = @n
isect.pl = new Vec ray.org.x + t * ray.dir.x, ray.org.y + t * ray.dir.y, ray.org.z + t * ray.dir.z
return
class Ray
constructor: (@org, @dir) ->
class Isect
constructor: ->
@t = 10000000
@hit = false
@pl = new Vec 0, 0, 0
@n = new Vec 0, 0, 0
clamp = (f) ->
i = f * 255.5
if i > 255
i = 255
if i < 0
i = 0
Math.floor i
otherBasis = (basis, n) ->
basis[2] = new Vec n.x, n.y, n.z
basis[1] = new Vec 0, 0, 0
if n.x < 0.6 and n.x > -0.6
basis[1].x = 1
else if n.y < 0.6 and n.y > -0.6
basis[1].y = 1
else if n.z < 0.6 and n.z > -0.6
basis[1].z = 1
else
basis[1].x = 1
basis[0] = basis[1].vcross basis[2]
basis[0] = basis[0].vnormalize()
basis[1] = basis[2].vcross basis[0]
basis[1] = basis[1].vnormalize()
return
class Scene
constructor: ->
@spheres = []
@spheres[0] = new Sphere (new Vec -2, 0, -3.5), 0.5
@spheres[1] = new Sphere (new Vec -0.5, 0, -3), 0.5
@spheres[2] = new Sphere (new Vec 1, 0, -2.2), 0.5
@plane = new Plane (new Vec 0, -0.5, 0), new Vec 0, 1, 0
ambient_occlusion: (isect) ->
basis = []
otherBasis basis, isect.n
ntheta = NAO_SAMPLES
nphi = NAO_SAMPLES
eps = 0.0001
occlusion = 0
p0 = new Vec isect.pl.x + eps * isect.n.x, isect.pl.y + eps * isect.n.y, isect.pl.z + eps * isect.n.z
j = 0
while j < nphi
i = 0
while i < ntheta
r = Math.random()
phi = 2.0 * Math.PI * Math.random()
x = (Math.cos phi) * Math.sqrt 1 - r
y = (Math.sin phi) * Math.sqrt 1 - r
z = Math.sqrt r
rx = x * basis[0].x + y * basis[1].x + z * basis[2].x
ry = x * basis[0].y + y * basis[1].y + z * basis[2].y
rz = x * basis[0].z + y * basis[1].z + z * basis[2].z
raydir = new Vec rx, ry, rz
ray = new Ray p0, raydir
occisect = new Isect
@spheres[0].intersect ray, occisect
@spheres[1].intersect ray, occisect
@spheres[2].intersect ray, occisect
@plane.intersect ray, occisect
occlusion++ if occisect.hit
i++
j++
occlusion = (ntheta * nphi - occlusion) / (ntheta * nphi)
new Vec occlusion, occlusion, occlusion
render: (w, h, nsubsamples) ->
ppm = ""
cnt = 0
ns = nsubsamples
y = 0
while y < h
x = 0
while x < w
rad = new Vec 0, 0, 0
# Subsmpling
v = 0
while v < nsubsamples
u = 0
while u < nsubsamples
cnt++
px = (x + (u / ns) - (w / 2)) / (w / 2)
py = -(y + (v / ns) - (h / 2)) / (h / 2)
eye = (new Vec px, py, -1).vnormalize()
ray = new Ray (new Vec 0, 0, 0), eye
isect = new Isect
@spheres[0].intersect ray, isect
@spheres[1].intersect ray, isect
@spheres[2].intersect ray, isect
@plane.intersect ray, isect
if isect.hit
col = @ambient_occlusion isect
rad.x = rad.x + col.x
rad.y = rad.y + col.y
rad.z = rad.z + col.z
u++
v++
x++
r = rad.x / (ns * ns)
g = rad.y / (ns * ns)
b = rad.z / (ns * ns)
ppm += String.fromCharCode clamp r
ppm += String.fromCharCode clamp g
ppm += String.fromCharCode clamp b
y++
ppm
@getPPM = ->
ppm =
"""
P6
#{IMAGE_WIDTH} #{IMAGE_HEIGHT}
255
"""
ppm += (new Scene).render IMAGE_WIDTH, IMAGE_HEIGHT, 2
ppm
@ppmToCanvas = (ppm, canvas) ->
getLine = (->
end = -1
(delimiter) ->
start = end + 1
if delimiter?
ppm.slice start, end = ppm.indexOf delimiter, start
else
ppm.slice start
)()
return unless getLine('\n') is "P6"
size = getLine('\n').split ' '
w = canvas.width = parseInt size[0]
h = canvas.height = parseInt size[1]
data = getLine()
context = canvas.getContext "2d"
imgd = context.createImageData w, h
pix = imgd.data
i = p = 0
color = []
for c in data
color[i % 3] = c.charCodeAt 0
if i % 3 is 2
pix[p++] = color[0] # red
pix[p++] = color[1] # green
pix[p++] = color[2] # blue
pix[p++] = 255 # alpha
i++
context.putImageData imgd, 0, 0
return
# Example:
# @ppmToCanvas @getPPM(), document.getElementById "canvas"
(function() {
/*
A port of AO render benchmark to CoffeeScript
by thunder9 (https://github.com/thunder9)
Original program (C) Syoyo Fujita in Javascript (and other languages)
http://lucille.atso-net.jp/blog/?p=642
http://lucille.atso-net.jp/blog/?p=711
*/
var IMAGE_HEIGHT, IMAGE_WIDTH, Isect, NAO_SAMPLES, NSUBSAMPLES, Plane, Ray, Scene, Sphere, Vec, clamp, otherBasis;
IMAGE_WIDTH = 256;
IMAGE_HEIGHT = 256;
NSUBSAMPLES = 2;
NAO_SAMPLES = 8;
Vec = (function() {
function Vec(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
Vec.prototype.vadd = function(b) {
return new Vec(this.x + b.x, this.y + b.y, this.z + b.z);
};
Vec.prototype.vsub = function(b) {
return new Vec(this.x - b.x, this.y - b.y, this.z - b.z);
};
Vec.prototype.vcross = function(b) {
return new Vec(this.y * b.z - this.z * b.y, this.z * b.x - this.x * b.z, this.x * b.y - this.y * b.x);
};
Vec.prototype.vdot = function(b) {
var r;
return r = this.x * b.x + this.y * b.y + this.z * b.z;
};
Vec.prototype.vlength = function() {
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
};
Vec.prototype.vnormalize = function() {
var len, v;
len = this.vlength();
v = new Vec(this.x, this.y, this.z);
if (len > 1.0e-17) {
v.x = v.x / len;
v.y = v.y / len;
v.z = v.z / len;
}
return v;
};
return Vec;
})();
Sphere = (function() {
function Sphere(center, radius) {
this.center = center;
this.radius = radius;
}
Sphere.prototype.intersect = function(ray, isect) {
var b, c, d, n, rs, t;
rs = ray.org.vsub(this.center);
b = rs.vdot(ray.dir);
c = (rs.vdot(rs)) - this.radius * this.radius;
d = b * b - c;
if (d > 0.0) {
t = -b - Math.sqrt(d);
if (t > 0.0 && t < isect.t) {
isect.t = t;
isect.hit = true;
isect.pl = new Vec(ray.org.x + ray.dir.x * t, ray.org.y + ray.dir.y * t, ray.org.z + ray.dir.z * t);
n = isect.pl.vsub(this.center);
isect.n = n.vnormalize();
}
}
};
return Sphere;
})();
Plane = (function() {
function Plane(p, n) {
this.p = p;
this.n = n;
}
Plane.prototype.intersect = function(ray, isect) {
var d, t, v, v0;
d = -this.p.vdot(this.n);
v = ray.dir.vdot(this.n);
v0 = v;
if (v < 0) {
v0 = -v;
}
if (v0 < 1.0e-17) {
return;
}
t = -((ray.org.vdot(this.n)) + d) / v;
if (t > 0 && t < isect.t) {
isect.hit = true;
isect.t = t;
isect.n = this.n;
isect.pl = new Vec(ray.org.x + t * ray.dir.x, ray.org.y + t * ray.dir.y, ray.org.z + t * ray.dir.z);
}
};
return Plane;
})();
Ray = (function() {
function Ray(org, dir) {
this.org = org;
this.dir = dir;
}
return Ray;
})();
Isect = (function() {
function Isect() {
this.t = 10000000;
this.hit = false;
this.pl = new Vec(0, 0, 0);
this.n = new Vec(0, 0, 0);
}
return Isect;
})();
clamp = function(f) {
var i;
i = f * 255.5;
if (i > 255) {
i = 255;
}
if (i < 0) {
i = 0;
}
return Math.floor(i);
};
otherBasis = function(basis, n) {
basis[2] = new Vec(n.x, n.y, n.z);
basis[1] = new Vec(0, 0, 0);
if (n.x < 0.6 && n.x > -0.6) {
basis[1].x = 1;
} else if (n.y < 0.6 && n.y > -0.6) {
basis[1].y = 1;
} else if (n.z < 0.6 && n.z > -0.6) {
basis[1].z = 1;
} else {
basis[1].x = 1;
}
basis[0] = basis[1].vcross(basis[2]);
basis[0] = basis[0].vnormalize();
basis[1] = basis[2].vcross(basis[0]);
basis[1] = basis[1].vnormalize();
};
Scene = (function() {
function Scene() {
this.spheres = [];
this.spheres[0] = new Sphere(new Vec(-2, 0, -3.5), 0.5);
this.spheres[1] = new Sphere(new Vec(-0.5, 0, -3), 0.5);
this.spheres[2] = new Sphere(new Vec(1, 0, -2.2), 0.5);
this.plane = new Plane(new Vec(0, -0.5, 0), new Vec(0, 1, 0));
}
Scene.prototype.ambient_occlusion = function(isect) {
var basis, eps, i, j, nphi, ntheta, occisect, occlusion, p0, phi, r, ray, raydir, rx, ry, rz, x, y, z;
basis = [];
otherBasis(basis, isect.n);
ntheta = NAO_SAMPLES;
nphi = NAO_SAMPLES;
eps = 0.0001;
occlusion = 0;
p0 = new Vec(isect.pl.x + eps * isect.n.x, isect.pl.y + eps * isect.n.y, isect.pl.z + eps * isect.n.z);
j = 0;
while (j < nphi) {
i = 0;
while (i < ntheta) {
r = Math.random();
phi = 2.0 * Math.PI * Math.random();
x = (Math.cos(phi)) * Math.sqrt(1 - r);
y = (Math.sin(phi)) * Math.sqrt(1 - r);
z = Math.sqrt(r);
rx = x * basis[0].x + y * basis[1].x + z * basis[2].x;
ry = x * basis[0].y + y * basis[1].y + z * basis[2].y;
rz = x * basis[0].z + y * basis[1].z + z * basis[2].z;
raydir = new Vec(rx, ry, rz);
ray = new Ray(p0, raydir);
occisect = new Isect;
this.spheres[0].intersect(ray, occisect);
this.spheres[1].intersect(ray, occisect);
this.spheres[2].intersect(ray, occisect);
this.plane.intersect(ray, occisect);
if (occisect.hit) {
occlusion++;
}
i++;
}
j++;
}
occlusion = (ntheta * nphi - occlusion) / (ntheta * nphi);
return new Vec(occlusion, occlusion, occlusion);
};
Scene.prototype.render = function(w, h, nsubsamples) {
var b, cnt, col, eye, g, isect, ns, ppm, px, py, r, rad, ray, u, v, x, y;
ppm = "";
cnt = 0;
ns = nsubsamples;
y = 0;
while (y < h) {
x = 0;
while (x < w) {
rad = new Vec(0, 0, 0);
v = 0;
while (v < nsubsamples) {
u = 0;
while (u < nsubsamples) {
cnt++;
px = (x + (u / ns) - (w / 2)) / (w / 2);
py = -(y + (v / ns) - (h / 2)) / (h / 2);
eye = (new Vec(px, py, -1)).vnormalize();
ray = new Ray(new Vec(0, 0, 0), eye);
isect = new Isect;
this.spheres[0].intersect(ray, isect);
this.spheres[1].intersect(ray, isect);
this.spheres[2].intersect(ray, isect);
this.plane.intersect(ray, isect);
if (isect.hit) {
col = this.ambient_occlusion(isect);
rad.x = rad.x + col.x;
rad.y = rad.y + col.y;
rad.z = rad.z + col.z;
}
u++;
}
v++;
}
x++;
r = rad.x / (ns * ns);
g = rad.y / (ns * ns);
b = rad.z / (ns * ns);
ppm += String.fromCharCode(clamp(r));
ppm += String.fromCharCode(clamp(g));
ppm += String.fromCharCode(clamp(b));
}
y++;
}
return ppm;
};
return Scene;
})();
this.getPPM = function() {
var ppm;
ppm = "P6\n" + IMAGE_WIDTH + " " + IMAGE_HEIGHT + "\n255";
ppm += (new Scene).render(IMAGE_WIDTH, IMAGE_HEIGHT, 2);
return ppm;
};
this.ppmToCanvas = function(ppm, canvas) {
var c, color, context, data, getLine, h, i, imgd, p, pix, size, w, _i, _len;
getLine = (function() {
var end;
end = -1;
return function(delimiter) {
var start;
start = end + 1;
if (delimiter != null) {
return ppm.slice(start, end = ppm.indexOf(delimiter, start));
} else {
return ppm.slice(start);
}
};
})();
if (getLine('\n') !== "P6") {
return;
}
size = getLine('\n').split(' ');
w = canvas.width = parseInt(size[0]);
h = canvas.height = parseInt(size[1]);
data = getLine();
context = canvas.getContext("2d");
imgd = context.createImageData(w, h);
pix = imgd.data;
i = p = 0;
color = [];
for (_i = 0, _len = data.length; _i < _len; _i++) {
c = data[_i];
color[i % 3] = c.charCodeAt(0);
if (i % 3 === 2) {
pix[p++] = color[0];
pix[p++] = color[1];
pix[p++] = color[2];
pix[p++] = 255;
}
i++;
}
context.putImageData(imgd, 0, 0);
};
}).call(this);
// Example:
// ppmToCanvas(getPPM(), document.getElementById("canvas"));
@thunder9
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment