Simple ray tracer for jslibs and SpiderMonkey

W

William James

// Ray tracer for SpiderMonkey and jslibs.
// Converted from the original in C at
// https://wiki.cs.auckland.ac.nz/enggen131/index.php/User:Dols008


LoadModule('jsstd')
LoadModule('jsio')


const width = 512
const height = width

Print( width, "x", height, "\n" )

var start_time = (new Date()).getTime()


// Functions for vectors (arrays containing 3 numbers).

Array.prototype.add =
function( v ) [ this[0] + v[0], this[1] + v[1], this[2] + v[2] ]

Array.prototype.sub =
function( v ) [ this[0] - v[0], this[1] - v[1], this[2] - v[2] ]

Array.prototype.scale =
function(num) [ this[0] * num, this[1] * num, this[2] * num ]

Array.prototype.mul =
function(v) [ this[0] * v[0], this[1] * v[1], this[2] * v[2] ]

Array.prototype.dot =
function( v ) this[0]*v[0] + this[1]*v[1] + this[2]*v[2]

Array.prototype.squared_length = function()
{ var _ = this
return _[0] * _[0] + _[1] * _[1] + _[2] * _[2]
}

Array.prototype.normal = function()
{ var length = Math.sqrt( this.squared_length() )
return [
this[0] / length,
this[1] / length,
this[2] / length ]
}



function Ray( pos, dir ) // Both are vectors.
{ this.pos = pos
this.dir = dir
}

function Light( pos, color ) // Both are vectors.
{ this.pos = pos
this.color = color
}

function Sphere( pos, radius, color, shininess, reflectivity )
{ this.pos = pos
this.radius = radius
this.color = color
this.shininess = shininess
this.reflectivity = reflectivity
this.ray_hit_me = function( ray )
{ var diff = this.pos.sub( ray.pos )
var proj = diff.dot( ray.dir )
var closest = ray.pos.add( ray.dir.scale( proj ))
var tangent = closest.sub( this.pos )
var sq_tangent_length = tangent.squared_length()
var sq_radius = this.radius*this.radius
if (sq_tangent_length > sq_radius)
return 0
return proj - Math.sqrt(sq_radius - sq_tangent_length)
}
}


function calc_lighting(pos, normal, ray, sphere, light)
{ var rel = light.pos.sub( pos ).normal()
var diffuse = rel.dot( normal )
diffuse = Math.max( diffuse, 0 )
var diff_col = light.color.scale( diffuse )
var eye = ray.pos.sub( pos )
var half = eye.add( rel ).normal()
var specular = half.dot( normal )
specular = Math.pow( Math.max( specular, 0 ), 64 )
var spec_col = light.color.scale( specular )
return sphere.color.mul(diff_col).
add( spec_col.scale(sphere.shininess))
}



const NUM_SPHERES = 7
const NUM_LIGHTS = 3
spheres = new Array( NUM_SPHERES )
lights = new Array( NUM_LIGHTS )


function build_scene()
{
// Draw a smile (an arc of spheres)
for ( var i = 0; i < 5; i++ )
{ var theta = (i - 2) * 0.4
var pos = [
Math.sin(theta) * 3, -Math.cos(theta) * 3, 5 ]
spheres = new Sphere(
pos, 1, [0.8, 0.1, 0.1], 0.2, 0.5 )
}
spheres[5] = new Sphere(
[-2, 2, 5], 1.5, [0.75, 0.75, 0.5],
0.5, 0.5 )
spheres[6] = new Sphere(
[2, 2, 5], 1.3, [0.75, 0.75, 0.5],
0.5, 0.5 )
// Place the main light to the right of the eye.
lights[0] = new Light( [2, 2, 1], [1, 1, 1] )
// Place secondary lights to the left and right of the face.
lights[1] = new Light( [-4, 0, 5],
[0.1, 0.5, 0.1] )
lights[2] = new Light( [4, 0, 5],
[0.1, 0.5, 0.1] )
}



const MAX_RECURSION_DEPTH = 2

function trace( ray, depth )
{ var hit = -1
var pos, normal, color = [0, 0, 0]
var reflect_ray = new Ray()
var dist, d
for (var i = 0; i < NUM_SPHERES; i++)
if ( (d = spheres.ray_hit_me( ray )) > 0 )
if (hit == -1 || d < dist)
{ dist = d
hit = i
}

if (hit != -1)
{ pos = ray.pos.add( ray.dir.scale(dist) )
normal = pos.sub( spheres[hit].pos ).normal()
for (var i = 0; i < NUM_LIGHTS; i++)
color = color.add(
calc_lighting(pos, normal, ray, spheres[hit], lights) )
if (depth < MAX_RECURSION_DEPTH)
{
reflect_ray.dir = ray.dir.sub(
normal.scale( 2 * ray.dir.dot( normal)) )
reflect_ray.pos = pos
color = color.add( trace(reflect_ray, depth + 1).
scale( spheres[hit].reflectivity) )
}
return color
}

// No sphere was hit, so use the ray direction
// to make a gradient background.
color = ray.dir.add( [1, 1, 1]).scale( 0.125)
return color
}



var y, x
var r = new Ray( [0,0,0], [0,0,0] )
var color = [0,0,0]
var image = ""


r.pos = [0,0,0]
build_scene()

Print("-----Rendering------\n")


for (y = 0; y < height; y++)
{
if (y % Math.floor(height / 20 + 1) == 0)
Print("#") // Display progress indicator.

for (x = 0; x < width; x++)
{
// Use the screen coordinate to generate a ray direction.
r.dir[0] = x / width - 0.5
// Positive y is up, so flip the y coordinate.
r.dir[1] = (y / height - 0.5) * -1
r.dir[2] = 0.5
r.dir = r.dir.normal()

// Trace the ray for this pixel.
color = trace(r, 0)

// Clamp the color to 1.
color = color.map(function(c) Math.min( c, 1 ) )

image += color.map(
function(c) String.fromCharCode( Math.floor(c * 255)) ).
join("")

}
}


Print("\n")
Print( (new Date().getTime() - start_time)/1000, " seconds\n")


var file = new File( "junk.ppm" )
if ( file.exist )
{ Print( file.name, " already exists.\n" )
Print( "Overwrite? " )
var resp = File.stdin.Read().toUpperCase()
if ( "Y" == resp[0] )
Print( 'Overwriting.' )
else
Halt()
}

file.content = "P6\n" + width + " " + height + "\n255\n" + image
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,042
Latest member
icassiem

Latest Threads

Top