Last active
December 10, 2015 07:58
-
-
Save thunder9/4404365 to your computer and use it in GitHub Desktop.
A port of AO render benchmark to CoffeeScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
### | |
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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")); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
jsfiddle sample