diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..67d7551 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +logs/* +!.gitkeep +node_modules/ +bower_components/ +*.log +tmp +!.gitkeep +logs/* +*.sublime-* +credentials.* +models/ +tmp +.DS_Store +.idea +*.map diff --git a/raytracer/README.txt b/raytracer/README.txt index 56022c7..e340d5a 100644 --- a/raytracer/README.txt +++ b/raytracer/README.txt @@ -6,4 +6,4 @@ This sample shows a raytracer implementation in TypeScript. === Running === tsc --sourcemap raytracer.ts -start raytracer.html +start ./raytracer.html diff --git a/raytracer/raytracer.js b/raytracer/raytracer.js new file mode 100644 index 0000000..595762a --- /dev/null +++ b/raytracer/raytracer.js @@ -0,0 +1,260 @@ +/* Translated automatically using http://www.typescriptlang.org/Playground */ +var Vector = (function () { + function Vector(x, y, z) { + this.x = x; + this.y = y; + this.z = z; + } + Vector.times = function (k, v) { + return new Vector(k * v.x, k * v.y, k * v.z); + }; + Vector.minus = function (v1, v2) { + return new Vector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); + }; + Vector.plus = function (v1, v2) { + return new Vector(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z); + }; + Vector.dot = function (v1, v2) { + return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; + }; + Vector.mag = function (v) { + return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); + }; + Vector.norm = function (v) { + var mag = Vector.mag(v); + var div = (mag === 0) ? Infinity : 1.0 / mag; + return Vector.times(div, v); + }; + Vector.cross = function (v1, v2) { + return new Vector(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x); + }; + return Vector; +})(); +var Color = (function () { + function Color(r, g, b) { + this.r = r; + this.g = g; + this.b = b; + } + Color.scale = function (k, v) { + return new Color(k * v.r, k * v.g, k * v.b); + }; + Color.plus = function (v1, v2) { + return new Color(v1.r + v2.r, v1.g + v2.g, v1.b + v2.b); + }; + Color.times = function (v1, v2) { + return new Color(v1.r * v2.r, v1.g * v2.g, v1.b * v2.b); + }; + Color.toDrawingColor = function (c) { + var legalize = function (d) { return d > 1 ? 1 : d; }; + return { + r: Math.floor(legalize(c.r) * 255), + g: Math.floor(legalize(c.g) * 255), + b: Math.floor(legalize(c.b) * 255) + }; + }; + Color.white = new Color(1.0, 1.0, 1.0); + Color.grey = new Color(0.5, 0.5, 0.5); + Color.black = new Color(0.0, 0.0, 0.0); + Color.background = Color.black; + Color.defaultColor = Color.black; + return Color; +})(); +var Camera = (function () { + function Camera(pos, lookAt) { + this.pos = pos; + var down = new Vector(0.0, -1.0, 0.0); + this.forward = Vector.norm(Vector.minus(lookAt, this.pos)); + this.right = Vector.times(1.5, Vector.norm(Vector.cross(this.forward, down))); + this.up = Vector.times(1.5, Vector.norm(Vector.cross(this.forward, this.right))); + } + return Camera; +})(); +var Sphere = (function () { + function Sphere(center, radius, surface) { + this.center = center; + this.surface = surface; + this.radius2 = radius * radius; + } + Sphere.prototype.normal = function (pos) { + return Vector.norm(Vector.minus(pos, this.center)); + }; + Sphere.prototype.intersect = function (ray) { + var eo = Vector.minus(this.center, ray.start); + var v = Vector.dot(eo, ray.dir); + var dist = 0; + if (v >= 0) { + var disc = this.radius2 - (Vector.dot(eo, eo) - v * v); + if (disc >= 0) { + dist = v - Math.sqrt(disc); + } + } + if (dist === 0) { + return null; + } + else { + return { thing: this, ray: ray, dist: dist }; + } + }; + return Sphere; +})(); +var Plane = (function () { + function Plane(norm, offset, surface) { + this.surface = surface; + this.normal = function (pos) { + return norm; + }; + this.intersect = function (ray) { + var denom = Vector.dot(norm, ray.dir); + if (denom > 0) { + return null; + } + else { + var dist = (Vector.dot(norm, ray.start) + offset) / (-denom); + return { thing: this, ray: ray, dist: dist }; + } + }; + } + return Plane; +})(); +var Surfaces; +(function (Surfaces) { + Surfaces.shiny = { + diffuse: function (pos) { + return Color.white; + }, + specular: function (pos) { + return Color.grey; + }, + reflect: function (pos) { + return 0.7; + }, + roughness: 250 + }; + Surfaces.checkerboard = { + diffuse: function (pos) { + if ((Math.floor(pos.z) + Math.floor(pos.x)) % 2 !== 0) { + return Color.white; + } + else { + return Color.black; + } + }, + specular: function (pos) { + return Color.white; + }, + reflect: function (pos) { + if ((Math.floor(pos.z) + Math.floor(pos.x)) % 2 !== 0) { + return 0.1; + } + else { + return 0.7; + } + }, + roughness: 150 + }; +})(Surfaces || (Surfaces = {})); +var RayTracer = (function () { + function RayTracer() { + this.maxDepth = 5; + } + RayTracer.prototype.intersections = function (ray, scene) { + var closest = +Infinity; + var closestInter = undefined; + for (var i in scene.things) { + var inter = scene.things[i].intersect(ray); + if (inter != null && inter.dist < closest) { + closestInter = inter; + closest = inter.dist; + } + } + return closestInter; + }; + RayTracer.prototype.testRay = function (ray, scene) { + var isect = this.intersections(ray, scene); + if (isect != null) { + return isect.dist; + } + else { + return undefined; + } + }; + RayTracer.prototype.traceRay = function (ray, scene, depth) { + var isect = this.intersections(ray, scene); + if (isect === undefined) { + return Color.background; + } + else { + return this.shade(isect, scene, depth); + } + }; + RayTracer.prototype.shade = function (isect, scene, depth) { + var d = isect.ray.dir; + var pos = Vector.plus(Vector.times(isect.dist, d), isect.ray.start); + var normal = isect.thing.normal(pos); + var reflectDir = Vector.minus(d, Vector.times(2, Vector.times(Vector.dot(normal, d), normal))); + var naturalColor = Color.plus(Color.background, this.getNaturalColor(isect.thing, pos, normal, reflectDir, scene)); + var reflectedColor = (depth >= this.maxDepth) ? Color.grey : this.getReflectionColor(isect.thing, pos, normal, reflectDir, scene, depth); + return Color.plus(naturalColor, reflectedColor); + }; + RayTracer.prototype.getReflectionColor = function (thing, pos, normal, rd, scene, depth) { + return Color.scale(thing.surface.reflect(pos), this.traceRay({ start: pos, dir: rd }, scene, depth + 1)); + }; + RayTracer.prototype.getNaturalColor = function (thing, pos, norm, rd, scene) { + var _this = this; + var addLight = function (col, light) { + var ldis = Vector.minus(light.pos, pos); + var livec = Vector.norm(ldis); + var neatIsect = _this.testRay({ start: pos, dir: livec }, scene); + var isInShadow = (neatIsect === undefined) ? false : (neatIsect <= Vector.mag(ldis)); + if (isInShadow) { + return col; + } + else { + var illum = Vector.dot(livec, norm); + var lcolor = (illum > 0) ? Color.scale(illum, light.color) : Color.defaultColor; + var specular = Vector.dot(livec, Vector.norm(rd)); + var scolor = (specular > 0) ? Color.scale(Math.pow(specular, thing.surface.roughness), light.color) : Color.defaultColor; + return Color.plus(col, Color.plus(Color.times(thing.surface.diffuse(pos), lcolor), Color.times(thing.surface.specular(pos), scolor))); + } + }; + return scene.lights.reduce(addLight, Color.defaultColor); + }; + RayTracer.prototype.render = function (scene, ctx, screenWidth, screenHeight) { + var getPoint = function (x, y, camera) { + var recenterX = function (x) { return (x - (screenWidth / 2.0)) / 2.0 / screenWidth; }; + var recenterY = function (y) { return -(y - (screenHeight / 2.0)) / 2.0 / screenHeight; }; + return Vector.norm(Vector.plus(camera.forward, Vector.plus(Vector.times(recenterX(x), camera.right), Vector.times(recenterY(y), camera.up)))); + }; + for (var y = 0; y < screenHeight; y++) { + for (var x = 0; x < screenWidth; x++) { + var color = this.traceRay({ start: scene.camera.pos, dir: getPoint(x, y, scene.camera) }, scene, 0); + var c = Color.toDrawingColor(color); + ctx.fillStyle = "rgb(" + String(c.r) + ", " + String(c.g) + ", " + String(c.b) + ")"; + ctx.fillRect(x, y, x + 1, y + 1); + } + } + }; + return RayTracer; +})(); +function defaultScene() { + return { + things: [new Plane(new Vector(0.0, 1.0, 0.0), 0.0, Surfaces.checkerboard), new Sphere(new Vector(0.0, 1.0, -0.25), 1.0, Surfaces.shiny), new Sphere(new Vector(-1.0, 0.5, 1.5), 0.5, Surfaces.shiny)], + lights: [{ pos: new Vector(-2.0, 2.5, 0.0), color: new Color(0.49, 0.07, 0.07) }, { pos: new Vector(1.5, 2.5, 1.5), color: new Color(0.07, 0.07, 0.49) }, { pos: new Vector(1.5, 2.5, -1.5), color: new Color(0.07, 0.49, 0.071) }, { pos: new Vector(0.0, 3.5, 0.0), color: new Color(0.21, 0.21, 0.35) }], + camera: new Camera(new Vector(3.0, 2.0, 4.0), new Vector(-1.0, 0.5, 0.0)) + }; +} +function exec() { + // this is crashing the Intl display driver, strange var width = 1024, height = 1024; + var width = 512, height = 512; + console.log('Bootstrapping TS'); + var canv = document.createElement("canvas"); + canv.width = width; + canv.height = height; + document.body.appendChild(canv); + var ctx = canv.getContext("2d"); + var rayTracer = new RayTracer(); + return rayTracer.render(defaultScene(), ctx, width, height); +} +exec(); +//# sourceMappingURL=raytracer.js.map \ No newline at end of file diff --git a/raytracer/raytracer.ts b/raytracer/raytracer.ts index 56f6cec..3a8229e 100644 --- a/raytracer/raytracer.ts +++ b/raytracer/raytracer.ts @@ -1,277 +1,282 @@ - -class Vector { - constructor(public x: number, - public y: number, - public z: number) { - } - static times(k: number, v: Vector) { return new Vector(k * v.x, k * v.y, k * v.z); } - static minus(v1: Vector, v2: Vector) { return new Vector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); } - static plus(v1: Vector, v2: Vector) { return new Vector(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z); } - static dot(v1: Vector, v2: Vector) { return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; } - static mag(v: Vector) { return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); } - static norm(v: Vector) { - var mag = Vector.mag(v); - var div = (mag === 0) ? Infinity : 1.0 / mag; - return Vector.times(div, v); - } - static cross(v1: Vector, v2: Vector) { - return new Vector(v1.y * v2.z - v1.z * v2.y, - v1.z * v2.x - v1.x * v2.z, - v1.x * v2.y - v1.y * v2.x); - } -} - -class Color { - constructor(public r: number, - public g: number, - public b: number) { - } - static scale(k: number, v: Color) { return new Color(k * v.r, k * v.g, k * v.b); } - static plus(v1: Color, v2: Color) { return new Color(v1.r + v2.r, v1.g + v2.g, v1.b + v2.b); } - static times(v1: Color, v2: Color) { return new Color(v1.r * v2.r, v1.g * v2.g, v1.b * v2.b); } - static white = new Color(1.0, 1.0, 1.0); - static grey = new Color(0.5, 0.5, 0.5); - static black = new Color(0.0, 0.0, 0.0); - static background = Color.black; - static defaultColor = Color.black; - static toDrawingColor(c: Color) { - var legalize = d => d > 1 ? 1 : d; - return { - r: Math.floor(legalize(c.r) * 255), - g: Math.floor(legalize(c.g) * 255), - b: Math.floor(legalize(c.b) * 255) - } - } -} - -class Camera { - public forward: Vector; - public right: Vector; - public up: Vector; - - constructor(public pos: Vector, lookAt: Vector) { - var down = new Vector(0.0, -1.0, 0.0); - this.forward = Vector.norm(Vector.minus(lookAt, this.pos)); - this.right = Vector.times(1.5, Vector.norm(Vector.cross(this.forward, down))); - this.up = Vector.times(1.5, Vector.norm(Vector.cross(this.forward, this.right))); - } -} - -interface Ray { - start: Vector; - dir: Vector; -} - -interface Intersection { - thing: Thing; - ray: Ray; - dist: number; -} - -interface Surface { - diffuse: (pos: Vector) => Color; - specular: (pos: Vector) => Color; - reflect: (pos: Vector) => number; - roughness: number; -} - -interface Thing { - intersect: (ray: Ray) => Intersection; - normal: (pos: Vector) => Vector; - surface: Surface; -} - -interface Light { - pos: Vector; - color: Color; -} - -interface Scene { - things: Thing[]; - lights: Light[]; - camera: Camera; -} - -class Sphere implements Thing { - public radius2: number; - - constructor(public center: Vector, radius: number, public surface: Surface) { - this.radius2 = radius * radius; - } - normal(pos: Vector): Vector { return Vector.norm(Vector.minus(pos, this.center)); } - intersect(ray: Ray) { - var eo = Vector.minus(this.center, ray.start); - var v = Vector.dot(eo, ray.dir); - var dist = 0; - if (v >= 0) { - var disc = this.radius2 - (Vector.dot(eo, eo) - v * v); - if (disc >= 0) { - dist = v - Math.sqrt(disc); - } - } - if (dist === 0) { - return null; - } else { - return { thing: this, ray: ray, dist: dist }; - } - } -} - -class Plane implements Thing { - public normal: (pos: Vector) =>Vector; - public intersect: (ray: Ray) =>Intersection; - constructor(norm: Vector, offset: number, public surface: Surface) { - this.normal = function(pos: Vector) { return norm; } - this.intersect = function(ray: Ray): Intersection { - var denom = Vector.dot(norm, ray.dir); - if (denom > 0) { - return null; - } else { - var dist = (Vector.dot(norm, ray.start) + offset) / (-denom); - return { thing: this, ray: ray, dist: dist }; - } - } - } -} - -module Surfaces { - export var shiny: Surface = { - diffuse: function(pos) { return Color.white; }, - specular: function(pos) { return Color.grey; }, - reflect: function(pos) { return 0.7; }, - roughness: 250 - } - export var checkerboard: Surface = { - diffuse: function(pos) { - if ((Math.floor(pos.z) + Math.floor(pos.x)) % 2 !== 0) { - return Color.white; - } else { - return Color.black; - } - }, - specular: function(pos) { return Color.white; }, - reflect: function(pos) { - if ((Math.floor(pos.z) + Math.floor(pos.x)) % 2 !== 0) { - return 0.1; - } else { - return 0.7; - } - }, - roughness: 150 - } -} - - -class RayTracer { - private maxDepth = 5; - - private intersections(ray: Ray, scene: Scene) { - var closest = +Infinity; - var closestInter: Intersection = undefined; - for (var i in scene.things) { - var inter = scene.things[i].intersect(ray); - if (inter != null && inter.dist < closest) { - closestInter = inter; - closest = inter.dist; - } - } - return closestInter; - } - - private testRay(ray: Ray, scene: Scene) { - var isect = this.intersections(ray, scene); - if (isect != null) { - return isect.dist; - } else { - return undefined; - } - } - - private traceRay(ray: Ray, scene: Scene, depth: number): Color { - var isect = this.intersections(ray, scene); - if (isect === undefined) { - return Color.background; - } else { - return this.shade(isect, scene, depth); - } - } - - private shade(isect: Intersection, scene: Scene, depth: number) { - var d = isect.ray.dir; - var pos = Vector.plus(Vector.times(isect.dist, d), isect.ray.start); - var normal = isect.thing.normal(pos); - var reflectDir = Vector.minus(d, Vector.times(2, Vector.times(Vector.dot(normal, d), normal))); - var naturalColor = Color.plus(Color.background, - this.getNaturalColor(isect.thing, pos, normal, reflectDir, scene)); - var reflectedColor = (depth >= this.maxDepth) ? Color.grey : this.getReflectionColor(isect.thing, pos, normal, reflectDir, scene, depth); - return Color.plus(naturalColor, reflectedColor); - } - - private getReflectionColor(thing: Thing, pos: Vector, normal: Vector, rd: Vector, scene: Scene, depth: number) { - return Color.scale(thing.surface.reflect(pos), this.traceRay({ start: pos, dir: rd }, scene, depth + 1)); - } - - private getNaturalColor(thing: Thing, pos: Vector, norm: Vector, rd: Vector, scene: Scene) { - var addLight = (col, light) => { - var ldis = Vector.minus(light.pos, pos); - var livec = Vector.norm(ldis); - var neatIsect = this.testRay({ start: pos, dir: livec }, scene); - var isInShadow = (neatIsect === undefined) ? false : (neatIsect <= Vector.mag(ldis)); - if (isInShadow) { - return col; - } else { - var illum = Vector.dot(livec, norm); - var lcolor = (illum > 0) ? Color.scale(illum, light.color) - : Color.defaultColor; - var specular = Vector.dot(livec, Vector.norm(rd)); - var scolor = (specular > 0) ? Color.scale(Math.pow(specular, thing.surface.roughness), light.color) - : Color.defaultColor; - return Color.plus(col, Color.plus(Color.times(thing.surface.diffuse(pos), lcolor), - Color.times(thing.surface.specular(pos), scolor))); - } - } - return scene.lights.reduce(addLight, Color.defaultColor); - } - - render(scene, ctx, screenWidth, screenHeight) { - var getPoint = (x, y, camera) => { - var recenterX = x =>(x - (screenWidth / 2.0)) / 2.0 / screenWidth; - var recenterY = y => - (y - (screenHeight / 2.0)) / 2.0 / screenHeight; - return Vector.norm(Vector.plus(camera.forward, Vector.plus(Vector.times(recenterX(x), camera.right), Vector.times(recenterY(y), camera.up)))); - } - for (var y = 0; y < screenHeight; y++) { - for (var x = 0; x < screenWidth; x++) { - var color = this.traceRay({ start: scene.camera.pos, dir: getPoint(x, y, scene.camera) }, scene, 0); - var c = Color.toDrawingColor(color); - ctx.fillStyle = "rgb(" + String(c.r) + ", " + String(c.g) + ", " + String(c.b) + ")"; - ctx.fillRect(x, y, x + 1, y + 1); - } - } - } -} - - -function defaultScene(): Scene { - return { - things: [new Plane(new Vector(0.0, 1.0, 0.0), 0.0, Surfaces.checkerboard), - new Sphere(new Vector(0.0, 1.0, -0.25), 1.0, Surfaces.shiny), - new Sphere(new Vector(-1.0, 0.5, 1.5), 0.5, Surfaces.shiny)], - lights: [{ pos: new Vector(-2.0, 2.5, 0.0), color: new Color(0.49, 0.07, 0.07) }, - { pos: new Vector(1.5, 2.5, 1.5), color: new Color(0.07, 0.07, 0.49) }, - { pos: new Vector(1.5, 2.5, -1.5), color: new Color(0.07, 0.49, 0.071) }, - { pos: new Vector(0.0, 3.5, 0.0), color: new Color(0.21, 0.21, 0.35) }], - camera: new Camera(new Vector(3.0, 2.0, 4.0), new Vector(-1.0, 0.5, 0.0)) - }; -} - -function exec() { - var canv = document.createElement("canvas"); - canv.width = 256; - canv.height = 256; - document.body.appendChild(canv); - var ctx = canv.getContext("2d"); - var rayTracer = new RayTracer(); - return rayTracer.render(defaultScene(), ctx, 256, 256); -} - -exec(); +/* tsc --sourcemap raytracer.ts */ +class Vector { + constructor(public x: number, + public y: number, + public z: number) { + } + static times(k: number, v: Vector) { return new Vector(k * v.x, k * v.y, k * v.z); } + static minus(v1: Vector, v2: Vector) { return new Vector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); } + static plus(v1: Vector, v2: Vector) { return new Vector(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z); } + static dot(v1: Vector, v2: Vector) { return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; } + static mag(v: Vector) { return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); } + static norm(v: Vector) { + var mag = Vector.mag(v); + var div = (mag === 0) ? Infinity : 1.0 / mag; + return Vector.times(div, v); + } + static cross(v1: Vector, v2: Vector) { + return new Vector(v1.y * v2.z - v1.z * v2.y, + v1.z * v2.x - v1.x * v2.z, + v1.x * v2.y - v1.y * v2.x); + } +} + +class Color { + constructor(public r: number, + public g: number, + public b: number) { + } + static scale(k: number, v: Color) { return new Color(k * v.r, k * v.g, k * v.b); } + static plus(v1: Color, v2: Color) { return new Color(v1.r + v2.r, v1.g + v2.g, v1.b + v2.b); } + static times(v1: Color, v2: Color) { return new Color(v1.r * v2.r, v1.g * v2.g, v1.b * v2.b); } + static white = new Color(1.0, 1.0, 1.0); + static grey = new Color(0.5, 0.5, 0.5); + static black = new Color(0.0, 0.0, 0.0); + static background = Color.black; + static defaultColor = Color.black; + static toDrawingColor(c: Color) { + var legalize = d => d > 1 ? 1 : d; + return { + r: Math.floor(legalize(c.r) * 255), + g: Math.floor(legalize(c.g) * 255), + b: Math.floor(legalize(c.b) * 255) + } + } +} + +class Camera { + public forward: Vector; + public right: Vector; + public up: Vector; + + constructor(public pos: Vector, lookAt: Vector) { + var down = new Vector(0.0, -1.0, 0.0); + this.forward = Vector.norm(Vector.minus(lookAt, this.pos)); + this.right = Vector.times(1.5, Vector.norm(Vector.cross(this.forward, down))); + this.up = Vector.times(1.5, Vector.norm(Vector.cross(this.forward, this.right))); + } +} + +interface Ray { + start: Vector; + dir: Vector; +} + +interface Intersection { + thing: Thing; + ray: Ray; + dist: number; +} + +interface Surface { + diffuse: (pos: Vector) => Color; + specular: (pos: Vector) => Color; + reflect: (pos: Vector) => number; + roughness: number; +} + +interface Thing { + intersect: (ray: Ray) => Intersection; + normal: (pos: Vector) => Vector; + surface: Surface; +} + +interface Light { + pos: Vector; + color: Color; +} + +interface Scene { + things: Thing[]; + lights: Light[]; + camera: Camera; +} + +class Sphere implements Thing { + public radius2: number; + + constructor(public center: Vector, radius: number, public surface: Surface) { + this.radius2 = radius * radius; + } + normal(pos: Vector): Vector { return Vector.norm(Vector.minus(pos, this.center)); } + intersect(ray: Ray) { + var eo = Vector.minus(this.center, ray.start); + var v = Vector.dot(eo, ray.dir); + var dist = 0; + if (v >= 0) { + var disc = this.radius2 - (Vector.dot(eo, eo) - v * v); + if (disc >= 0) { + dist = v - Math.sqrt(disc); + } + } + if (dist === 0) { + return null; + } else { + return { thing: this, ray: ray, dist: dist }; + } + } +} + +class Plane implements Thing { + public normal: (pos: Vector) =>Vector; + public intersect: (ray: Ray) =>Intersection; + constructor(norm: Vector, offset: number, public surface: Surface) { + this.normal = function(pos: Vector) { return norm; } + this.intersect = function(ray: Ray): Intersection { + var denom = Vector.dot(norm, ray.dir); + if (denom > 0) { + return null; + } else { + var dist = (Vector.dot(norm, ray.start) + offset) / (-denom); + return { thing: this, ray: ray, dist: dist }; + } + } + } +} + +module Surfaces { + export var shiny: Surface = { + diffuse: function(pos) { return Color.white; }, + specular: function(pos) { return Color.grey; }, + reflect: function(pos) { return 0.7; }, + roughness: 250 + } + export var checkerboard: Surface = { + diffuse: function(pos) { + if ((Math.floor(pos.z) + Math.floor(pos.x)) % 2 !== 0) { + return Color.white; + } else { + return Color.black; + } + }, + specular: function(pos) { return Color.white; }, + reflect: function(pos) { + if ((Math.floor(pos.z) + Math.floor(pos.x)) % 2 !== 0) { + return 0.1; + } else { + return 0.7; + } + }, + roughness: 150 + } +} + + +class RayTracer { + private maxDepth = 5; + + private intersections(ray: Ray, scene: Scene) { + var closest = +Infinity; + var closestInter: Intersection = undefined; + for (var i in scene.things) { + var inter = scene.things[i].intersect(ray); + if (inter != null && inter.dist < closest) { + closestInter = inter; + closest = inter.dist; + } + } + return closestInter; + } + + private testRay(ray: Ray, scene: Scene) { + var isect = this.intersections(ray, scene); + if (isect != null) { + return isect.dist; + } else { + return undefined; + } + } + + private traceRay(ray: Ray, scene: Scene, depth: number): Color { + var isect = this.intersections(ray, scene); + if (isect === undefined) { + return Color.background; + } else { + return this.shade(isect, scene, depth); + } + } + + private shade(isect: Intersection, scene: Scene, depth: number) { + var d = isect.ray.dir; + var pos = Vector.plus(Vector.times(isect.dist, d), isect.ray.start); + var normal = isect.thing.normal(pos); + var reflectDir = Vector.minus(d, Vector.times(2, Vector.times(Vector.dot(normal, d), normal))); + var naturalColor = Color.plus(Color.background, + this.getNaturalColor(isect.thing, pos, normal, reflectDir, scene)); + var reflectedColor = (depth >= this.maxDepth) ? Color.grey : this.getReflectionColor(isect.thing, pos, normal, reflectDir, scene, depth); + return Color.plus(naturalColor, reflectedColor); + } + + private getReflectionColor(thing: Thing, pos: Vector, normal: Vector, rd: Vector, scene: Scene, depth: number) { + return Color.scale(thing.surface.reflect(pos), this.traceRay({ start: pos, dir: rd }, scene, depth + 1)); + } + + private getNaturalColor(thing: Thing, pos: Vector, norm: Vector, rd: Vector, scene: Scene) { + var addLight = (col, light) => { + var ldis = Vector.minus(light.pos, pos); + var livec = Vector.norm(ldis); + var neatIsect = this.testRay({ start: pos, dir: livec }, scene); + var isInShadow = (neatIsect === undefined) ? false : (neatIsect <= Vector.mag(ldis)); + if (isInShadow) { + return col; + } else { + var illum = Vector.dot(livec, norm); + var lcolor = (illum > 0) ? Color.scale(illum, light.color) + : Color.defaultColor; + var specular = Vector.dot(livec, Vector.norm(rd)); + var scolor = (specular > 0) ? Color.scale(Math.pow(specular, thing.surface.roughness), light.color) + : Color.defaultColor; + return Color.plus(col, Color.plus(Color.times(thing.surface.diffuse(pos), lcolor), + Color.times(thing.surface.specular(pos), scolor))); + } + } + return scene.lights.reduce(addLight, Color.defaultColor); + } + + render(scene, ctx, screenWidth, screenHeight) { + var getPoint = (x, y, camera) => { + var recenterX = x =>(x - (screenWidth / 2.0)) / 2.0 / screenWidth; + var recenterY = y => - (y - (screenHeight / 2.0)) / 2.0 / screenHeight; + return Vector.norm(Vector.plus(camera.forward, Vector.plus(Vector.times(recenterX(x), camera.right), Vector.times(recenterY(y), camera.up)))); + } + for (var y = 0; y < screenHeight; y++) { + for (var x = 0; x < screenWidth; x++) { + var color = this.traceRay({ start: scene.camera.pos, dir: getPoint(x, y, scene.camera) }, scene, 0); + var c = Color.toDrawingColor(color); + ctx.fillStyle = "rgb(" + String(c.r) + ", " + String(c.g) + ", " + String(c.b) + ")"; + ctx.fillRect(x, y, x + 1, y + 1); + } + } + } +} + + +function defaultScene(): Scene { + return { + things: [new Plane(new Vector(0.0, 1.0, 0.0), 0.0, Surfaces.checkerboard), + new Sphere(new Vector(0.0, 1.0, -0.25), 1.0, Surfaces.shiny), + new Sphere(new Vector(-1.0, 0.5, 1.5), 0.5, Surfaces.shiny)], + lights: [{ pos: new Vector(-2.0, 2.5, 0.0), color: new Color(0.49, 0.07, 0.07) }, + { pos: new Vector(1.5, 2.5, 1.5), color: new Color(0.07, 0.07, 0.49) }, + { pos: new Vector(1.5, 2.5, -1.5), color: new Color(0.07, 0.49, 0.071) }, + { pos: new Vector(0.0, 3.5, 0.0), color: new Color(0.21, 0.21, 0.35) }], + camera: new Camera(new Vector(3.0, 2.0, 4.0), new Vector(-1.0, 0.5, 0.0)) + }; +} + +function exec() { + // this is crashing the Intl display driver, strange var width = 1024, height = 1024; + var width = 512, height = 512; + + console.log('Bootstrapping TS'); + + var canv = document.createElement("canvas"); + canv.width = width; + canv.height = height; + document.body.appendChild(canv); + var ctx = canv.getContext("2d"); + var rayTracer = new RayTracer(); + return rayTracer.render(defaultScene(), ctx, width, height); +} + +exec();