| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 // | 4 // |
| 5 // Canvas API tests. Some of these are adapted from: | 5 // Canvas API tests. Some of these are adapted from: |
| 6 // | 6 // |
| 7 // http://www.html5canvastutorials.com/ | 7 // http://www.html5canvastutorials.com/ |
| 8 | 8 |
| 9 library openglui_canvas_tests; | 9 library openglui_canvas_tests; |
| 10 | 10 |
| 11 import 'gl.dart'; | 11 import 'gl.dart'; |
| 12 import 'dart:math' as Math; | 12 import 'dart:math' as Math; |
| 13 | 13 |
| 14 var ctx; | 14 var ctx; |
| 15 var width, height; | 15 var width, height; |
| 16 bool isDirty = true; | 16 bool isDirty = true; |
| 17 var canvas; | 17 var canvas; |
| 18 | 18 |
| 19 void resize(int w, int h) { | 19 void resize(int w, int h) { |
| 20 width = w; | 20 width = w; |
| 21 height = h; | 21 height = h; |
| 22 } | 22 } |
| 23 | 23 |
| 24 void setup(canvasp, int w, int h) { | 24 void setup(canvasp, int w, int h) { |
| 25 if (canvasp == null) { | 25 if (canvasp == null) { |
| 26 log("Allocating canvas"); | 26 log("Allocating canvas"); |
| 27 canvas = new CanvasElement(width: w, height: h); | 27 canvas = new CanvasElement(width: w, height: h); |
| 28 document.body.nodes.add(canvas); |
| 28 } else { | 29 } else { |
| 29 log("Using parent canvas"); | 30 log("Using parent canvas"); |
| 30 canvas = canvasp; | 31 canvas = canvasp; |
| 31 // called from gl_driver.dart. | |
| 32 // This is a kludge; we need a clean way of handling events. | |
| 33 canvas.on.mouseDown.add((e) { | |
| 34 ++testnum; | |
| 35 }); | |
| 36 } | 32 } |
| 33 canvas.onMouseDown.listen((e) { |
| 34 ++testnum; |
| 35 isDirty = true; |
| 36 }); |
| 37 canvas.onKeyDown.listen((e) { |
| 38 ++testnum; |
| 39 isDirty = true; |
| 40 }); |
| 37 ctx = canvas.getContext("2d"); | 41 ctx = canvas.getContext("2d"); |
| 38 if (ctx == null) { | 42 if (ctx == null) { |
| 39 throw "Failed to get 2D context"; | 43 throw "Failed to get 2D context"; |
| 40 } | 44 } |
| 41 resize(w, h); | 45 resize(w, h); |
| 42 log("Done setup"); | 46 log("Done setup"); |
| 43 } | 47 } |
| 44 | 48 |
| 45 resetCanvas() { | 49 resetCanvas() { |
| 46 ctx.globalCompositeOperation = "source-over"; | 50 ctx.globalCompositeOperation = "source-over"; |
| (...skipping 595 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 642 | 646 |
| 643 ctx.beginPath(); | 647 ctx.beginPath(); |
| 644 ctx.rect(myRectangle.x, myRectangle.y, myRectangle.width, myRectangle.height); | 648 ctx.rect(myRectangle.x, myRectangle.y, myRectangle.width, myRectangle.height); |
| 645 ctx.fillStyle = '#8ED6FF'; | 649 ctx.fillStyle = '#8ED6FF'; |
| 646 ctx.fill(); | 650 ctx.fill(); |
| 647 ctx.lineWidth = myRectangle.borderWidth; | 651 ctx.lineWidth = myRectangle.borderWidth; |
| 648 ctx.strokeStyle = 'black'; | 652 ctx.strokeStyle = 'black'; |
| 649 ctx.stroke(); | 653 ctx.stroke(); |
| 650 } | 654 } |
| 651 | 655 |
| 652 int testnum = -1; // Start with latest. | 656 int testnum = 0; // Set this to -1 to start with last test. |
| 653 | 657 |
| 654 void update() { | 658 void update() { |
| 655 if (testnum == 0) { | 659 if (testnum == 0) { |
| 656 anim(); | 660 anim(); |
| 657 return; | 661 return; |
| 658 } | 662 } |
| 659 if (!isDirty) return; | 663 if (!isDirty) return; |
| 660 switch(testnum) { | 664 switch(testnum) { |
| 661 case 1: | 665 case 1: |
| 662 helloWorld(); | 666 helloWorld(); |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 717 break; | 721 break; |
| 718 case 20: | 722 case 20: |
| 719 shear(); | 723 shear(); |
| 720 break; | 724 break; |
| 721 case 21: | 725 case 21: |
| 722 composite(); | 726 composite(); |
| 723 break; | 727 break; |
| 724 case 22: | 728 case 22: |
| 725 smiley(); | 729 smiley(); |
| 726 break; | 730 break; |
| 731 case 23: |
| 732 var rayTracer = new RayTracer(); |
| 733 rayTracer.render(defaultScene(), ctx, width, height); |
| 734 break; |
| 727 default: | 735 default: |
| 728 if (testnum < 0) { | 736 if (testnum < 0) { |
| 729 testnum = 22; | 737 testnum = 23; |
| 730 } else { | 738 } else { |
| 731 testnum = 0; | 739 testnum = 0; |
| 732 } | 740 } |
| 733 update(); | 741 update(); |
| 734 break; | 742 break; |
| 735 } | 743 } |
| 736 isDirty = false; | 744 isDirty = false; |
| 737 } | 745 } |
| 738 | 746 |
| 739 /* | 747 // Raytracer adapted from https://gist.github.com/mythz/3817303. |
| 740 TODO(gram): below are the current entry points for input events for the | 748 |
| 741 native code version. We need to integrate these somehow with the WebGL | 749 Scene defaultScene() => |
| 742 version: | 750 new Scene( |
| 743 */ | 751 [new Plane(new Vector(0.0, 1.0, 0.0), 0.0, Surfaces.checkerboard), |
| 744 onMotionDown(num when, num x, num y) { | 752 new Sphere(new Vector(0.0, 1.0, -0.25), 1.0, Surfaces.shiny), |
| 745 ++testnum; | 753 new Sphere(new Vector(-1.0, 0.5, 1.5), 0.5, Surfaces.shiny)], |
| 746 isDirty = true; | 754 [new Light(new Vector(-2.0, 2.5, 0.0), new Color(0.49, 0.07, 0.07) ), |
| 747 log("Marking dirty"); | 755 new Light(new Vector(1.5, 2.5, 1.5), new Color(0.07, 0.07, 0.49) ), |
| 748 } | 756 new Light(new Vector(1.5, 2.5, -1.5), new Color(0.07, 0.49, 0.071) ), |
| 749 onMotionUp(num when, num x, num y) {} | 757 new Light(new Vector(0.0, 3.5, 0.0), new Color(0.21, 0.21, 0.35) )], |
| 750 onMotionMove(num when, num x, num y) {} | 758 new Camera(new Vector(3.0, 2.0, 4.0), new Vector(-1.0, 0.5, 0.0)) |
| 751 onMotionCancel(num when, num x, num y) {} | 759 ); |
| 752 onMotionOutside(num when, num x, num y) {} | 760 |
| 753 onMotionPointerDown(num when, num x, num y) { | 761 |
| 754 ++testnum; | 762 class Vector { |
| 755 isDirty = true; | 763 num x, y, z; |
| 756 log("Marking dirty"); | 764 Vector(this.x, this.y, this.z); |
| 757 } | 765 |
| 758 | 766 operator -(Vector v) => new Vector(x - v.x, y - v.y, z - v.z); |
| 759 onMotionPointerUp(num when, num x, num y) {} | 767 operator +(Vector v) => new Vector(x + v.x, y + v.y, z + v.z); |
| 760 | 768 static times(num k, Vector v) => new Vector(k * v.x, k * v.y, k * v.z); |
| 761 onKeyDown(num when, int flags, int keycode, int metastate, int repeat) { | 769 static num dot(Vector v1, Vector v2) => |
| 762 if (keycode == 32) { | 770 v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; |
| 763 ++testnum; | 771 static num mag(Vector v) => Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); |
| 764 isDirty = true; | 772 static Vector norm(Vector v) { |
| 765 log("Marking dirty"); | 773 var _mag = mag(v); |
| 766 } | 774 var div = _mag == 0 ? double.INFINITY : 1.0 / _mag; |
| 767 } | 775 return times(div, v); |
| 768 | 776 } |
| 769 onKeyUp(num when, int flags, int keycode, int metastate, int repeat) {} | 777 static Vector cross(Vector v1, Vector v2) { |
| 770 | 778 return new Vector(v1.y * v2.z - v1.z * v2.y, |
| 771 onKeyMultiple(num when, int flags, int keycode, int metastate, int repeat) { | 779 v1.z * v2.x - v1.x * v2.z, |
| 772 } | 780 v1.x * v2.y - v1.y * v2.x); |
| 773 | 781 } |
| 774 | 782 } |
| 783 |
| 784 class Color { |
| 785 num r, g, b; |
| 786 static final white = new Color(1.0, 1.0, 1.0); |
| 787 static final grey = new Color(0.5, 0.5, 0.5); |
| 788 static final black = new Color(0.0, 0.0, 0.0); |
| 789 static final background = Color.black; |
| 790 static final defaultColor = Color.black; |
| 791 |
| 792 Color(this.r,this.g,this.b); |
| 793 static scale(num k, Color v) => new Color(k * v.r, k * v.g, k * v.b); |
| 794 operator +(Color v) => new Color(r + v.r, g + v.g, b + v.b); |
| 795 operator *(Color v) => new Color(r * v.r, g * v.g, b * v.b); |
| 796 static _intColor(num d) => ((d > 1 ? 1 : d) * 255).toInt(); |
| 797 static String toDrawingRGB(Color c) => |
| 798 "rgb(${_intColor(c.r)}, ${_intColor(c.g)}, ${_intColor(c.b)})"; |
| 799 } |
| 800 |
| 801 class Camera { |
| 802 Vector pos, forward, right, up; |
| 803 Camera (this.pos, Vector lookAt) { |
| 804 var down = new Vector(0.0, -1.0, 0.0); |
| 805 forward = Vector.norm(lookAt - pos); |
| 806 right = Vector.times(1.5, Vector.norm(Vector.cross(forward, down))); |
| 807 up = Vector.times(1.5, Vector.norm(Vector.cross(forward, right))); |
| 808 } |
| 809 } |
| 810 |
| 811 class Ray { |
| 812 Vector start, dir; |
| 813 Ray([this.start, this.dir]); |
| 814 } |
| 815 |
| 816 class Intersection { |
| 817 Thing thing; |
| 818 Ray ray; |
| 819 num dist; |
| 820 Intersection(this.thing, this.ray, this.dist); |
| 821 } |
| 822 |
| 823 class Light { |
| 824 Vector pos; |
| 825 Color color; |
| 826 Light(this.pos, this.color); |
| 827 } |
| 828 |
| 829 abstract class Surface { |
| 830 int roughness; |
| 831 Color diffuse(Vector pos); |
| 832 Color specular(Vector pos); |
| 833 num reflect(Vector pos); |
| 834 } |
| 835 |
| 836 abstract class Thing { |
| 837 Intersection intersect(Ray ray); |
| 838 Vector normal(Vector pos); |
| 839 Surface surface; |
| 840 } |
| 841 |
| 842 class Scene { |
| 843 List<Thing> things; |
| 844 List<Light> lights; |
| 845 Camera camera; |
| 846 Scene([this.things,this.lights,this.camera]); |
| 847 } |
| 848 |
| 849 class Sphere implements Thing { |
| 850 num radius2, radius; |
| 851 Vector center; |
| 852 Surface surface; |
| 853 |
| 854 Sphere (this.center, this.radius, this.surface) { |
| 855 this.radius2 = radius * radius; |
| 856 } |
| 857 normal(Vector pos) => Vector.norm(pos - center); |
| 858 intersect(Ray ray) { |
| 859 var eo = this.center - ray.start; |
| 860 var v = Vector.dot(eo, ray.dir); |
| 861 var dist = 0; |
| 862 if (v >= 0) { |
| 863 var disc = this.radius2 - (Vector.dot(eo, eo) - v * v); |
| 864 if (disc >= 0) { |
| 865 dist = v - Math.sqrt(disc); |
| 866 } |
| 867 } |
| 868 return dist == 0 ? null : new Intersection(this, ray, dist); |
| 869 } |
| 870 } |
| 871 |
| 872 class Plane implements Thing { |
| 873 Vector norm; |
| 874 num offset; |
| 875 Surface surface; |
| 876 Plane(this.norm, this.offset, this.surface); |
| 877 Vector normal(Vector pos) => norm; |
| 878 Intersection intersect(Ray ray) { |
| 879 var denom = Vector.dot(norm, ray.dir); |
| 880 if (denom > 0) { |
| 881 return null; |
| 882 } else { |
| 883 var dist = (Vector.dot(norm, ray.start) + offset) / (-denom); |
| 884 return new Intersection(this, ray, dist); |
| 885 } |
| 886 } |
| 887 } |
| 888 |
| 889 class CustomSurface implements Surface { |
| 890 Color diffuseColor, specularColor; |
| 891 int roughness; |
| 892 num reflectPos; |
| 893 CustomSurface(this.diffuseColor, this.specularColor, |
| 894 this.reflectPos, this.roughness); |
| 895 diffuse(pos) => diffuseColor; |
| 896 specular(pos) => specularColor; |
| 897 reflect(pos) => reflectPos; |
| 898 } |
| 899 |
| 900 class CheckerBoardSurface implements Surface { |
| 901 int roughness; |
| 902 CheckerBoardSurface([this.roughness=150]); |
| 903 diffuse(pos) => (pos.z.floor() + pos.x.floor()) % 2 != 0 |
| 904 ? Color.white |
| 905 : Color.black; |
| 906 specular(pos) => Color.white; |
| 907 reflect(pos) => (pos.z.floor() + pos.x.floor()) % 2 != 0 ? 0.1 : 0.7; |
| 908 } |
| 909 |
| 910 class Surfaces { |
| 911 static final shiny = new CustomSurface(Color.white, Color.grey, 0.7, 250); |
| 912 static final checkerboard = new CheckerBoardSurface(); |
| 913 } |
| 914 |
| 915 class RayTracer { |
| 916 num _maxDepth = 5; |
| 917 |
| 918 Intersection _intersections(Ray ray, Scene scene) { |
| 919 var closest = double.INFINITY; |
| 920 Intersection closestInter = null; |
| 921 for (Thing thing in scene.things) { |
| 922 var inter = thing.intersect(ray); |
| 923 if (inter != null && inter.dist < closest) { |
| 924 closestInter = inter; |
| 925 closest = inter.dist; |
| 926 } |
| 927 } |
| 928 return closestInter; |
| 929 } |
| 930 |
| 931 _testRay(Ray ray, Scene scene) { |
| 932 var isect = _intersections(ray, scene); |
| 933 return isect != null ? isect.dist : null; |
| 934 } |
| 935 |
| 936 _traceRay(Ray ray, Scene scene, num depth) { |
| 937 var isect = _intersections(ray, scene); |
| 938 return isect == null ? Color.background : _shade(isect, scene, depth); |
| 939 } |
| 940 |
| 941 _shade(Intersection isect, Scene scene, num depth) { |
| 942 var d = isect.ray.dir; |
| 943 var pos = Vector.times(isect.dist, d) + isect.ray.start; |
| 944 var normal = isect.thing.normal(pos); |
| 945 var reflectDir = d - |
| 946 Vector.times(2, Vector.times(Vector.dot(normal, d), normal)); |
| 947 var naturalColor = Color.background + |
| 948 _getNaturalColor(isect.thing, pos, normal, reflectDir, scene); |
| 949 var reflectedColor = (depth >= _maxDepth) ? Color.grey : |
| 950 _getReflectionColor(isect.thing, pos, normal, reflectDir, |
| 951 scene, depth); |
| 952 return naturalColor + reflectedColor; |
| 953 } |
| 954 |
| 955 _getReflectionColor(Thing thing, Vector pos, Vector normal, Vector rd, |
| 956 Scene scene, num depth) => |
| 957 Color.scale(thing.surface.reflect(pos), |
| 958 _traceRay(new Ray(pos, rd), scene, depth + 1)); |
| 959 |
| 960 _getNaturalColor(Thing thing, Vector pos, Vector norm, Vector rd, |
| 961 Scene scene) { |
| 962 var addLight = (col, light) { |
| 963 var ldis = light.pos - pos; |
| 964 var livec = Vector.norm(ldis); |
| 965 var neatIsect = _testRay(new Ray(pos, livec), scene); |
| 966 var isInShadow = neatIsect == null ? false : |
| 967 (neatIsect <= Vector.mag(ldis)); |
| 968 if (isInShadow) { |
| 969 return col; |
| 970 } else { |
| 971 var illum = Vector.dot(livec, norm); |
| 972 var lcolor = (illum > 0) ? Color.scale(illum, light.color) |
| 973 : Color.defaultColor; |
| 974 var specular = Vector.dot(livec, Vector.norm(rd)); |
| 975 var scolor = (specular > 0) |
| 976 ? Color.scale(Math.pow(specular, thing.surface.roughness), |
| 977 light.color) |
| 978 : Color.defaultColor; |
| 979 return col + (thing.surface.diffuse(pos) * lcolor) |
| 980 + (thing.surface.specular(pos) * scolor); |
| 981 } |
| 982 }; |
| 983 return scene.lights.reduce(Color.defaultColor, addLight); |
| 984 } |
| 985 |
| 986 render(Scene scene, CanvasRenderingContext2D ctx, num screenWidth, |
| 987 num screenHeight) { |
| 988 var getPoint = (x, y, camera) { |
| 989 var recenterX = (x) => (x - (screenWidth / 2.0)) / 2.0 / screenWidth; |
| 990 var recenterY = (y) => - (y - (screenHeight / 2.0)) / 2.0 / screenHeight; |
| 991 return Vector.norm(camera.forward |
| 992 + Vector.times(recenterX(x), camera.right) |
| 993 + Vector.times(recenterY(y), camera.up)); |
| 994 }; |
| 995 for (int y = 0; y < screenHeight; y++) { |
| 996 for (int x = 0; x < screenWidth; x++) { |
| 997 var color = _traceRay(new Ray(scene.camera.pos, |
| 998 getPoint(x, y, scene.camera) ), scene, 0); |
| 999 ctx.fillStyle = Color.toDrawingRGB(color); |
| 1000 ctx.fillRect(x, y, x + 1, y + 1); |
| 1001 } |
| 1002 } |
| 1003 } |
| 1004 } |
| 1005 |
| 1006 |
| 1007 |
| 1008 |
| OLD | NEW |