Chromium Code Reviews| Index: samples/openglui/src/openglui_canvas_tests.dart |
| =================================================================== |
| --- samples/openglui/src/openglui_canvas_tests.dart (revision 19252) |
| +++ samples/openglui/src/openglui_canvas_tests.dart (working copy) |
| @@ -25,15 +25,19 @@ |
| if (canvasp == null) { |
| log("Allocating canvas"); |
| canvas = new CanvasElement(width: w, height: h); |
| + document.body.nodes.add(canvas); |
| } else { |
| log("Using parent canvas"); |
| canvas = canvasp; |
| - // called from gl_driver.dart. |
| - // This is a kludge; we need a clean way of handling events. |
| - canvas.on.mouseDown.add((e) { |
| - ++testnum; |
| - }); |
| } |
| + canvas.onMouseDown.listen((e) { |
| + ++testnum; |
| + isDirty = true; |
| + }); |
| + canvas.onKeyDown.listen((e) { |
| + ++testnum; |
| + isDirty = true; |
| + }); |
| ctx = canvas.getContext("2d"); |
| if (ctx == null) { |
| throw "Failed to get 2D context"; |
| @@ -649,7 +653,7 @@ |
| ctx.stroke(); |
| } |
| -int testnum = -1; // Start with latest. |
| +int testnum = 0; //-1; // Start with latest. |
|
vsm
2013/03/05 00:23:58
Did you mean to keep this change? If so, delete t
gram
2013/03/05 01:06:49
Done.
|
| void update() { |
| if (testnum == 0) { |
| @@ -724,9 +728,13 @@ |
| case 22: |
| smiley(); |
| break; |
| + case 23: |
| + var rayTracer = new RayTracer(); |
| + rayTracer.render(defaultScene(), ctx, width, height); |
| + break; |
| default: |
| if (testnum < 0) { |
| - testnum = 22; |
| + testnum = 23; |
| } else { |
| testnum = 0; |
| } |
| @@ -736,39 +744,253 @@ |
| isDirty = false; |
| } |
| -/* |
| -TODO(gram): below are the current entry points for input events for the |
| -native code version. We need to integrate these somehow with the WebGL |
| -version: |
| -*/ |
| -onMotionDown(num when, num x, num y) { |
| - ++testnum; |
| - isDirty = true; |
| - log("Marking dirty"); |
| +// Raytracer adapted from https://gist.github.com/mythz/3817303. |
| + |
| +Scene defaultScene() => |
| + new Scene( |
| + [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)], |
| + [new Light(new Vector(-2.0, 2.5, 0.0), new Color(0.49, 0.07, 0.07) ), |
| + new Light(new Vector(1.5, 2.5, 1.5), new Color(0.07, 0.07, 0.49) ), |
| + new Light(new Vector(1.5, 2.5, -1.5), new Color(0.07, 0.49, 0.071) ), |
| + new Light(new Vector(0.0, 3.5, 0.0), new Color(0.21, 0.21, 0.35) )], |
| + new Camera(new Vector(3.0, 2.0, 4.0), new Vector(-1.0, 0.5, 0.0)) |
| + ); |
| + |
| + |
| +class Vector { |
| + num x, y, z; |
| + Vector(this.x, this.y, this.z); |
| + |
| + operator -(Vector v) => new Vector(x - v.x, y - v.y, z - v.z); |
| + operator +(Vector v) => new Vector(x + v.x, y + v.y, z + v.z); |
| + static times(num k, Vector v) => new Vector(k * v.x, k * v.y, k * v.z); |
| + static num dot(Vector v1, Vector v2) => v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; |
|
vsm
2013/03/05 00:23:58
line len
vsm
2013/03/05 00:23:58
len
gram
2013/03/05 01:06:49
Done.
gram
2013/03/05 01:06:49
Done.
|
| + static num mag(Vector v) => Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); |
| + static Vector norm(Vector v) { |
| + var _mag = mag(v); |
| + var div = _mag == 0 ? double.INFINITY : 1.0 / _mag; |
| + return times(div, v); |
| + } |
| + static Vector cross(Vector v1, Vector 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); |
| + } |
| } |
| -onMotionUp(num when, num x, num y) {} |
| -onMotionMove(num when, num x, num y) {} |
| -onMotionCancel(num when, num x, num y) {} |
| -onMotionOutside(num when, num x, num y) {} |
| -onMotionPointerDown(num when, num x, num y) { |
| - ++testnum; |
| - isDirty = true; |
| - log("Marking dirty"); |
| + |
| +class Color { |
| + num r, g, b; |
| + static final white = new Color(1.0, 1.0, 1.0); |
| + static final grey = new Color(0.5, 0.5, 0.5); |
| + static final black = new Color(0.0, 0.0, 0.0); |
| + static final background = Color.black; |
| + static final defaultColor = Color.black; |
| + |
| + Color(this.r,this.g,this.b); |
| + static scale(num k, Color v) => new Color(k * v.r, k * v.g, k * v.b); |
| + operator +(Color v) => new Color(r + v.r, g + v.g, b + v.b); |
| + operator *(Color v) => new Color(r * v.r, g * v.g, b * v.b); |
| + static _intColor(num d) => ((d > 1 ? 1 : d) * 255).toInt(); |
| + static String toDrawingRGB(Color c) =>"rgb(${_intColor(c.r)}, ${_intColor(c.g)}, ${_intColor(c.b)})"; |
|
vsm
2013/03/05 00:23:58
line
gram
2013/03/05 01:06:49
Done.
|
| } |
| -onMotionPointerUp(num when, num x, num y) {} |
| +class Camera { |
| + Vector pos, forward, right, up; |
| + Camera (this.pos, Vector lookAt) { |
| + var down = new Vector(0.0, -1.0, 0.0); |
| + forward = Vector.norm(lookAt - pos); |
| + right = Vector.times(1.5, Vector.norm(Vector.cross(forward, down))); |
| + up = Vector.times(1.5, Vector.norm(Vector.cross(forward, right))); |
| + } |
| +} |
| -onKeyDown(num when, int flags, int keycode, int metastate, int repeat) { |
| - if (keycode == 32) { |
| - ++testnum; |
| - isDirty = true; |
| - log("Marking dirty"); |
| +class Ray { |
| + Vector start, dir; |
| + Ray([this.start, this.dir]); |
| +} |
| + |
| +class Intersection { |
| + Thing thing; |
| + Ray ray; |
| + num dist; |
| + Intersection(this.thing, this.ray, this.dist); |
| +} |
| + |
| +class Light { |
| + Vector pos; |
| + Color color; |
| + Light(this.pos, this.color); |
| +} |
| + |
| +abstract class Surface { |
| + int roughness; |
| + Color diffuse(Vector pos); |
| + Color specular(Vector pos); |
| + num reflect(Vector pos); |
| +} |
| + |
| +abstract class Thing { |
| + Intersection intersect(Ray ray); |
| + Vector normal(Vector pos); |
| + Surface surface; |
| +} |
| + |
| +class Scene { |
| + List<Thing> things; |
| + List<Light> lights; |
| + Camera camera; |
| + Scene([this.things,this.lights,this.camera]); |
| +} |
| + |
| +class Sphere implements Thing { |
| + num radius2, radius; |
| + Vector center; |
| + Surface surface; |
| + |
| + Sphere (this.center, this.radius, this.surface) { |
| + this.radius2 = radius * radius; |
| } |
| + normal(Vector pos) => Vector.norm(pos - center); |
| + intersect(Ray ray) { |
| + var eo = 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); |
| + } |
| + return dist == 0 ? null : new Intersection(this, ray, dist); |
| + } |
| } |
| -onKeyUp(num when, int flags, int keycode, int metastate, int repeat) {} |
| +class Plane implements Thing { |
| + Vector norm; |
| + num offset; |
| + Surface surface; |
| + Plane(this.norm, this.offset, this.surface); |
| + Vector normal(Vector pos) => norm; |
| + Intersection intersect(Ray ray) { |
| + var denom = Vector.dot(norm, ray.dir); |
| + if (denom > 0) { |
| + return null; |
| + } else { |
| + var dist = (Vector.dot(norm, ray.start) + offset) / (-denom); |
| + return new Intersection(this, ray, dist); |
| + } |
| + } |
| +} |
| -onKeyMultiple(num when, int flags, int keycode, int metastate, int repeat) { |
| +class CustomSurface implements Surface { |
| + Color diffuseColor, specularColor; |
| + int roughness; |
| + num reflectPos; |
| + CustomSurface(this.diffuseColor, this.specularColor, this.reflectPos, this.roughness); |
|
vsm
2013/03/05 00:23:58
length
gram
2013/03/05 01:06:49
Done.
|
| + diffuse(pos) => diffuseColor; |
| + specular(pos) => specularColor; |
| + reflect(pos) => reflectPos; |
| } |
| +class CheckerBoardSurface implements Surface { |
| + int roughness; |
| + CheckerBoardSurface([this.roughness=150]); |
| + diffuse(pos) => (pos.z.floor() + pos.x.floor()) % 2 != 0 |
| + ? Color.white |
| + : Color.black; |
| + specular(pos) => Color.white; |
| + reflect(pos) => (pos.z.floor() + pos.x.floor()) % 2 != 0 ? 0.1 : 0.7; |
| +} |
| +class Surfaces { |
| + static final shiny = new CustomSurface(Color.white, Color.grey, 0.7, 250); |
| + static final checkerboard = new CheckerBoardSurface(); |
| +} |
| + |
| +class RayTracer { |
| + num _maxDepth = 5; |
| + |
| + Intersection _intersections(Ray ray, Scene scene) { |
| + var closest = double.INFINITY; |
| + Intersection closestInter = null; |
| + for (Thing thing in scene.things) { |
| + var inter = thing.intersect(ray); |
| + if (inter != null && inter.dist < closest) { |
| + closestInter = inter; |
| + closest = inter.dist; |
| + } |
| + } |
| + return closestInter; |
| + } |
| + |
| + _testRay(Ray ray, Scene scene) { |
| + var isect = _intersections(ray, scene); |
| + return isect != null |
| + ? isect.dist |
| + : null; |
| + } |
| + |
| + _traceRay(Ray ray, Scene scene, num depth) { |
| + var isect = _intersections(ray, scene); |
| + return isect == null |
| + ? Color.background |
| + : _shade(isect, scene, depth); |
| + } |
| + |
| + _shade(Intersection isect, Scene scene, num depth) { |
| + var d = isect.ray.dir; |
| + var pos = Vector.times(isect.dist, d) + isect.ray.start; |
| + var normal = isect.thing.normal(pos); |
|
vsm
2013/03/05 00:23:58
Ditto line length for the next few methods.
gram
2013/03/05 01:06:49
Done.
|
| + var reflectDir = d - Vector.times(2, Vector.times(Vector.dot(normal, d), normal)); |
| + var naturalColor = Color.background + _getNaturalColor(isect.thing, pos, normal, reflectDir, scene); |
| + var reflectedColor = (depth >= _maxDepth) ? Color.grey : _getReflectionColor(isect.thing, pos, normal, reflectDir, scene, depth); |
| + return naturalColor + reflectedColor; |
| + } |
| + |
| + _getReflectionColor(Thing thing, Vector pos, Vector normal, Vector rd, Scene scene, num depth) => |
| + Color.scale(thing.surface.reflect(pos), _traceRay(new Ray(pos, rd), scene, depth + 1)); |
| + |
| + _getNaturalColor(Thing thing, Vector pos, Vector norm, Vector rd, Scene scene) { |
| + var addLight = (col, light) { |
| + var ldis = light.pos - pos; |
| + var livec = Vector.norm(ldis); |
| + var neatIsect = _testRay(new Ray(pos, livec), scene); |
| + var isInShadow = neatIsect == null ? 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 col + (thing.surface.diffuse(pos) * lcolor) |
| + + (thing.surface.specular(pos) * scolor); |
| + } |
| + }; |
| + return scene.lights.reduce(Color.defaultColor, addLight); |
| + } |
| + |
| + render(Scene scene, CanvasRenderingContext2D ctx, num screenWidth, num 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(camera.forward |
| + + Vector.times(recenterX(x), camera.right) |
| + + Vector.times(recenterY(y), camera.up)); |
| + }; |
| + for (int y = 0; y < screenHeight; y++) { |
| + for (int x = 0; x < screenWidth; x++) { |
| + var color = _traceRay(new Ray(scene.camera.pos, getPoint(x, y, scene.camera) ), scene, 0); |
| + ctx.fillStyle = Color.toDrawingRGB(color); |
| + ctx.fillRect(x, y, x + 1, y + 1); |
| + } |
| + } |
| + } |
| +} |
| + |
| + |
| + |
| + |