| OLD | NEW | 
 | (Empty) | 
|     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 |  | 
|     3 // BSD-style license that can be found in the LICENSE file. |  | 
|     4 // |  | 
|     5 // Canvas API tests. Some of these are adapted from: |  | 
|     6 // |  | 
|     7 // http://www.html5canvastutorials.com/ |  | 
|     8  |  | 
|     9 library openglui_canvas_tests; |  | 
|    10  |  | 
|    11 import 'gl.dart'; |  | 
|    12 import 'dart:math' as Math; |  | 
|    13  |  | 
|    14 var ctx; |  | 
|    15 var width, height; |  | 
|    16 bool isDirty = true; |  | 
|    17 var canvas; |  | 
|    18  |  | 
|    19 void resize(int w, int h) { |  | 
|    20   width = w; |  | 
|    21   height = h; |  | 
|    22 } |  | 
|    23  |  | 
|    24 void setup(canvasp, int w, int h, int f) { |  | 
|    25   if (canvasp == null) { |  | 
|    26     log("Allocating canvas"); |  | 
|    27     canvas = new CanvasElement(width: w, height: h); |  | 
|    28     document.body.nodes.add(canvas); |  | 
|    29   } else { |  | 
|    30     log("Using parent canvas"); |  | 
|    31     canvas = canvasp; |  | 
|    32   } |  | 
|    33   canvas.onMouseDown.listen((e) { |  | 
|    34     ++testnum; |  | 
|    35     isDirty = true; |  | 
|    36   }); |  | 
|    37   canvas.onKeyDown.listen((e) { |  | 
|    38     ++testnum; |  | 
|    39     isDirty = true; |  | 
|    40   }); |  | 
|    41   ctx = canvas.getContext("2d"); |  | 
|    42   if (ctx == null) { |  | 
|    43     throw "Failed to get 2D context"; |  | 
|    44   } |  | 
|    45   resize(w, h); |  | 
|    46   window.requestAnimationFrame(update); |  | 
|    47   log("Done setup"); |  | 
|    48 } |  | 
|    49  |  | 
|    50 resetCanvas() { |  | 
|    51   ctx.globalCompositeOperation = "source-over"; |  | 
|    52   ctx.setTransform(1, 0, 0, 1, 0, 0); |  | 
|    53   ctx.fillStyle = "#FFFFFF"; |  | 
|    54   ctx.clearRect(0, 0, width, height); |  | 
|    55   ctx.shadowOffsetX = ctx.shadowOffsetY = ctx.shadowBlur = 0.0; |  | 
|    56   ctx.beginPath(); |  | 
|    57 } |  | 
|    58  |  | 
|    59 initTest(title) { |  | 
|    60   resetCanvas(); |  | 
|    61   ctx.font = "15px Courier"; |  | 
|    62   ctx.textAlign = 'left'; |  | 
|    63   ctx.fillStyle = "black"; |  | 
|    64   ctx.fillText(title, 20, 20); |  | 
|    65 } |  | 
|    66  |  | 
|    67 helloWorld() { |  | 
|    68   initTest("Hello world"); |  | 
|    69   ctx.font = "30px Courier"; |  | 
|    70   ctx.strokeStyle = "#7F7F7F"; |  | 
|    71   ctx.fillStyle = "#7F7F7F"; |  | 
|    72   ctx.textAlign = "center"; |  | 
|    73   ctx.lineWidth = 2; |  | 
|    74   ctx.strokeText("Align Center", width / 2, height / 4); |  | 
|    75   ctx.textAlign = "left"; |  | 
|    76   ctx.fillText("Align Left", width / 2, height / 2); |  | 
|    77   ctx.textAlign = "right"; |  | 
|    78   ctx.fillText("Align Right", width / 2, 3 * height / 4); |  | 
|    79 } |  | 
|    80  |  | 
|    81 blocks() { |  | 
|    82   initTest("fillRect/strokeRect"); |  | 
|    83   ctx.fillStyle = "#FF0000"; |  | 
|    84   ctx.fillRect(width / 10, height / 10, width / 2, height / 25); |  | 
|    85   ctx.fillStyle = "#00FF00"; |  | 
|    86   ctx.fillRect(width / 4, height / 5, width / 20, height / 8); |  | 
|    87   //ctx.fillStyle = "rgba(0,0,255,0.8)"; |  | 
|    88   ctx.strokeStyle = "rgba(128,128,128, 0.5)"; |  | 
|    89   //ctx.fillStyle = "#7F7F7F"; |  | 
|    90   ctx.strokeRect(width / 5, height / 10, width / 2, height / 8); |  | 
|    91 } |  | 
|    92  |  | 
|    93 square(left, top, width, height) { |  | 
|    94   ctx.beginPath(); |  | 
|    95   ctx.moveTo(left, top); |  | 
|    96   ctx.lineTo(left + width, top); |  | 
|    97   ctx.lineTo(left + width, top + height); |  | 
|    98   ctx.lineTo(left, top + height); |  | 
|    99   ctx.closePath(); //lineTo(left, top); |  | 
|   100 } |  | 
|   101  |  | 
|   102 squares() { |  | 
|   103   initTest("fill/stroke paths"); |  | 
|   104   ctx.strokeStyle = "black"; |  | 
|   105   ctx.fillStyle = "#FF0000"; |  | 
|   106   ctx.lineWidth = 4; |  | 
|   107   square(width / 10, height / 10, width / 2, height / 25); |  | 
|   108   ctx.fill(); |  | 
|   109   ctx.stroke(); |  | 
|   110   ctx.fillStyle = "#00FF00"; |  | 
|   111   square(width / 4, height / 5, width / 20, height / 8); |  | 
|   112   ctx.fill(); |  | 
|   113   ctx.stroke(); |  | 
|   114   ctx.fillStyle = "rgba(128,128,128, 0.5)"; |  | 
|   115   square(width / 5, height / 10, width / 2, height / 8); |  | 
|   116   ctx.fill(); |  | 
|   117   ctx.stroke(); |  | 
|   118 } |  | 
|   119  |  | 
|   120 lineJoin() { |  | 
|   121   initTest("Line joins"); |  | 
|   122   ctx.strokeStyle = "black"; |  | 
|   123   ctx.fillStyle = "#FF0000"; |  | 
|   124   ctx.lineWidth = 8; |  | 
|   125   ctx.lineJoin = "round"; |  | 
|   126   square(width / 10, height / 10, width / 2, height / 25); |  | 
|   127   ctx.stroke(); |  | 
|   128   ctx.fillStyle = "#00FF00"; |  | 
|   129   ctx.lineJoin = "bevel"; |  | 
|   130   square(width / 4, height / 5, width / 20, height / 8); |  | 
|   131   ctx.stroke(); |  | 
|   132   ctx.fillStyle = "rgba(128,128,128, 0.5)"; |  | 
|   133   ctx.lineJoin = "miter"; |  | 
|   134   square(width / 5, height / 10, width / 2, height / 8); |  | 
|   135   ctx.stroke(); |  | 
|   136 } |  | 
|   137  |  | 
|   138 grid() { |  | 
|   139   initTest("line strokes"); |  | 
|   140   ctx.lineWidth = 1; |  | 
|   141   for (var x = 0.5; x < width; x += 10) { |  | 
|   142     ctx.moveTo(x, 0); |  | 
|   143     ctx.lineTo(x, height); |  | 
|   144   } |  | 
|   145   for (var y = 0.5; y < height; y += 10) { |  | 
|   146     ctx.moveTo(0, y); |  | 
|   147     ctx.lineTo(width, y); |  | 
|   148   } |  | 
|   149   ctx.strokeStyle = "#eee"; |  | 
|   150   ctx.stroke(); |  | 
|   151 } |  | 
|   152  |  | 
|   153 line(x1, y1, x2, y2) { |  | 
|   154   ctx.beginPath(); |  | 
|   155   ctx.moveTo(x1, y1); |  | 
|   156   ctx.lineTo(x2, y2); |  | 
|   157 } |  | 
|   158  |  | 
|   159 strokeLines() { |  | 
|   160   initTest("line caps"); |  | 
|   161   ctx.lineWidth = height / 40; |  | 
|   162   ctx.strokeStyle = '#0000ff'; |  | 
|   163   // butt line cap (top line) |  | 
|   164   line(width / 4, height / 4, 3 * width / 4, height / 4); |  | 
|   165   ctx.lineCap = 'butt'; |  | 
|   166   ctx.stroke(); |  | 
|   167  |  | 
|   168   // round line cap (middle line) |  | 
|   169   line(width / 4, height / 2, 3 * width / 4, height / 2); |  | 
|   170   ctx.lineCap = 'round'; |  | 
|   171   ctx.stroke(); |  | 
|   172  |  | 
|   173   // square line cap (bottom line) |  | 
|   174   line(width / 4,  3 * height / 4, 3 * width / 4, 3 * height / 4); |  | 
|   175   ctx.lineCap = 'square'; |  | 
|   176   ctx.stroke(); |  | 
|   177 } |  | 
|   178  |  | 
|   179 colors() { |  | 
|   180   initTest("Colors"); |  | 
|   181   var colors = [ |  | 
|   182     "maroon", |  | 
|   183     "red", |  | 
|   184     "orange", |  | 
|   185     "yellow", |  | 
|   186     "olive", |  | 
|   187     "purple", |  | 
|   188     "fuschia", |  | 
|   189     "white", |  | 
|   190     "lime", |  | 
|   191     "green", |  | 
|   192     "navy", |  | 
|   193     "blue", |  | 
|   194     "aqua", |  | 
|   195     "teal", |  | 
|   196     "silver", |  | 
|   197     "gray", |  | 
|   198     "black" |  | 
|   199   ]; |  | 
|   200  |  | 
|   201   var i = 1; |  | 
|   202   var yinc = height / (2 * colors.length + 1); |  | 
|   203    |  | 
|   204   ctx.textAlign = "center"; |  | 
|   205   ctx.font = "${yinc}px Courier"; |  | 
|   206    |  | 
|   207   for (var color in colors) { |  | 
|   208     ctx.fillStyle = color; |  | 
|   209     ctx.fillRect(width / 4, i * 2 * yinc, width / 2, 3 * yinc / 2); |  | 
|   210     ctx.fillStyle = (color == "gray") ? "white" : "gray"; |  | 
|   211     ctx.fillText(color, width / 2, i * 2 * yinc + 7 * yinc / 8); |  | 
|   212     ++i; |  | 
|   213   } |  | 
|   214 } |  | 
|   215  |  | 
|   216 smiley() { |  | 
|   217   initTest("arcs"); |  | 
|   218   ctx.translate(width / 2 - 80, height / 2 - 75); |  | 
|   219  |  | 
|   220   ctx.beginPath(); |  | 
|   221   ctx.arc(100,80,75,0,Math.PI*2,true); |  | 
|   222   ctx.fillStyle = "rgb(255,255,204)"; |  | 
|   223   ctx.fill(); |  | 
|   224   ctx.stroke(); |  | 
|   225  |  | 
|   226   ctx.fillStyle = "black"; |  | 
|   227   ctx.beginPath(); |  | 
|   228   ctx.arc(80,55,8,0,Math.PI*2,true); |  | 
|   229   ctx.fill(); |  | 
|   230  |  | 
|   231   ctx.beginPath(); |  | 
|   232   ctx.arc(120,55,8,0,Math.PI*2,true); |  | 
|   233   ctx.fill(); |  | 
|   234  |  | 
|   235   ctx.beginPath(); |  | 
|   236   ctx.arc(100,85,10,4,Math.PI*2,true); |  | 
|   237   ctx.stroke(); |  | 
|   238  |  | 
|   239   ctx.beginPath(); |  | 
|   240   ctx.arc(100,95,30,0,Math.PI,false); |  | 
|   241   ctx.stroke(); |  | 
|   242  |  | 
|   243   ctx.setTransform(1, 0, 0, 1, 0, 0); |  | 
|   244 } |  | 
|   245  |  | 
|   246 rot(txt1, [txt2, sx = 1.0, sy = 1.0]) { |  | 
|   247   if (txt2 == null) txt2 = txt1; |  | 
|   248   ctx.font = "50px sans serif"; |  | 
|   249   ctx.translate(width / 2, height / 2); |  | 
|   250   ctx.textAlign = "right"; |  | 
|   251   ctx.fillStyle = "red"; |  | 
|   252   ctx.fillText(txt1, 0, 0); |  | 
|   253   ctx.rotate(Math.PI / 2); |  | 
|   254   ctx.scale(sx, sy); |  | 
|   255   ctx.fillStyle = "green"; |  | 
|   256   ctx.fillText(txt2, 0, 0); |  | 
|   257   ctx.scale(sx, sy); |  | 
|   258   ctx.rotate(Math.PI / 2); |  | 
|   259   ctx.fillStyle = "blue"; |  | 
|   260   ctx.fillText(txt1, 0, 0); |  | 
|   261   ctx.scale(sx, sy); |  | 
|   262   ctx.rotate(Math.PI / 2); |  | 
|   263   ctx.fillStyle = "yellow"; |  | 
|   264   ctx.fillText(txt2, 0, 0); |  | 
|   265   ctx.setTransform(1, 0, 0, 1, 0, 0); |  | 
|   266 } |  | 
|   267  |  | 
|   268 rotate() { |  | 
|   269   initTest("Rotation"); |  | 
|   270   rot("Dart"); |  | 
|   271 } |  | 
|   272  |  | 
|   273 alpha() { |  | 
|   274   initTest("Global alpha"); |  | 
|   275   ctx.fillStyle = "gray"; |  | 
|   276   ctx.fillRect(0, 0, width, height); |  | 
|   277   grid(); |  | 
|   278   ctx.globalAlpha = 0.5; |  | 
|   279   rot("Global", "Alpha"); |  | 
|   280   ctx.globalAlpha = 1.0; |  | 
|   281 } |  | 
|   282  |  | 
|   283 scale() { |  | 
|   284   initTest("Scale"); |  | 
|   285   rot("Scale", "Test", 0.8, 0.5); |  | 
|   286 } |  | 
|   287  |  | 
|   288 curves() { |  | 
|   289   initTest("Curves"); |  | 
|   290   ctx.beginPath(); |  | 
|   291   ctx.moveTo(188, 150); |  | 
|   292   ctx.quadraticCurveTo(288, 0, 388, 150); |  | 
|   293   ctx.lineWidth = 2; |  | 
|   294   ctx.strokeStyle = 'red'; |  | 
|   295   ctx.stroke(); |  | 
|   296   ctx.beginPath(); |  | 
|   297   ctx.moveTo(188, 130); |  | 
|   298   ctx.bezierCurveTo(140, 10, 388, 10, 388, 170); |  | 
|   299   ctx.lineWidth = 5; |  | 
|   300   ctx.strokeStyle = 'blue'; |  | 
|   301   ctx.stroke(); |  | 
|   302   var x1, x2, y1, y2, ex, ey; |  | 
|   303   ctx.beginPath(); |  | 
|   304   x1 = width / 4; |  | 
|   305   y1 = height / 2; |  | 
|   306   x2 = width / 2; |  | 
|   307   y2 = height / 4; |  | 
|   308   ex = 3 * width / 4; |  | 
|   309   ey = y1; |  | 
|   310   ctx.moveTo(x1, x2); |  | 
|   311   ctx.quadraticCurveTo(x2, y2, ex, ey); |  | 
|   312   ctx.lineWidth = 10; |  | 
|   313   ctx.strokeStyle = 'green'; |  | 
|   314   ctx.stroke(); |  | 
|   315  |  | 
|   316   ctx.beginPath(); |  | 
|   317   ctx.moveTo(188, 130); |  | 
|   318   x1 = width / 8; |  | 
|   319   x2 = 7 * width / 8; |  | 
|   320   y1 = height / 50; |  | 
|   321   y2 = y1; |  | 
|   322   ex = x2; |  | 
|   323   ey = height / 2; |  | 
|   324   ctx.bezierCurveTo(x1, y1, x2, y2, ex, ey); |  | 
|   325   ctx.lineWidth = 7; |  | 
|   326   ctx.strokeStyle = 'black'; |  | 
|   327   ctx.stroke(); |  | 
|   328  |  | 
|   329   // Draw a cloud |  | 
|   330   ctx.beginPath(); |  | 
|   331   var wscale = width / 578; |  | 
|   332   var hscale = height / 800; |  | 
|   333   ctx.translate(0, height / 2); |  | 
|   334   ctx.moveTo(170 * wscale, 80 * hscale); |  | 
|   335   ctx.bezierCurveTo(130 * wscale, 100 * hscale, |  | 
|   336                     130 * wscale, 150 * hscale, |  | 
|   337                     230 * wscale, 150 * hscale); |  | 
|   338   ctx.bezierCurveTo(250 * wscale, 180 * hscale, |  | 
|   339                     320 * wscale, 180 * hscale, |  | 
|   340                     340 * wscale, 150 * hscale); |  | 
|   341   ctx.bezierCurveTo(420 * wscale, 150 * hscale, |  | 
|   342                     420 * wscale, 120 * hscale, |  | 
|   343                     390 * wscale, 100 * hscale); |  | 
|   344   ctx.bezierCurveTo(430 * wscale, 40 * hscale, |  | 
|   345                     370 * wscale, 30 * hscale, |  | 
|   346                     340 * wscale, 50 * hscale); |  | 
|   347   ctx.bezierCurveTo(320 * wscale, 5 * hscale, |  | 
|   348                     250 * wscale, 20 * hscale, |  | 
|   349                     250 * wscale, 50 * hscale); |  | 
|   350   ctx.bezierCurveTo(200 * wscale, 5 * hscale, |  | 
|   351                     150 * wscale, 20 * hscale, |  | 
|   352                     170 * wscale, 80 * hscale); |  | 
|   353   ctx.closePath(); |  | 
|   354   ctx.lineWidth = 5; |  | 
|   355   ctx.fillStyle = 'gray'; |  | 
|   356   ctx.fill(); |  | 
|   357 } |  | 
|   358  |  | 
|   359 void shadows() { |  | 
|   360   initTest("Shadows"); |  | 
|   361   ctx.shadowBlur=20; |  | 
|   362   ctx.shadowColor="black"; |  | 
|   363   ctx.fillStyle="red"; |  | 
|   364   var w = width / 2; |  | 
|   365   if (w > height / 2) w = height / 2; |  | 
|   366   ctx.fillRect(width / 2 - w / 2, height / 4 - w / 2, w, w); |  | 
|   367   ctx.shadowOffsetX = 10; |  | 
|   368   ctx.shadowOffsetY = 10; |  | 
|   369   ctx.shadowColor="green"; |  | 
|   370   ctx.fillRect(width / 2 - w / 2, 3 * height / 4 - w / 2, w, w); |  | 
|   371 } |  | 
|   372  |  | 
|   373 void lineJoins() { |  | 
|   374   initTest("Line joins"); |  | 
|   375   ctx.lineWidth=10; |  | 
|   376   ctx.lineJoin="miter"; |  | 
|   377   ctx.moveTo(width / 2 - 25, height / 4 - 10); |  | 
|   378   ctx.lineTo(width /2 + 25, height / 4); |  | 
|   379   ctx.lineTo(width / 2 - 25, height / 4 + 10); |  | 
|   380   ctx.stroke(); |  | 
|   381   ctx.lineJoin="round"; |  | 
|   382   ctx.moveTo(width / 2 - 25, height / 2 - 10); |  | 
|   383   ctx.lineTo(width /2 + 25, height / 2); |  | 
|   384   ctx.lineTo(width / 2 - 25, height / 2 + 10); |  | 
|   385   ctx.stroke(); |  | 
|   386   ctx.lineJoin="bevel"; |  | 
|   387   ctx.moveTo(width / 2 - 25, 3 * height / 4 - 10); |  | 
|   388   ctx.lineTo(width /2 + 25, 3 * height / 4); |  | 
|   389   ctx.lineTo(width / 2 - 25, 3 * height / 4 + 10); |  | 
|   390   ctx.stroke(); |  | 
|   391 } |  | 
|   392  |  | 
|   393 void saveRestore() { |  | 
|   394   initTest("Save/restore state"); |  | 
|   395   ctx.font = "30px courier"; |  | 
|   396   ctx.fillStyle = "red"; |  | 
|   397   ctx.strokeStyle = "black"; |  | 
|   398   ctx.shadowBlur = 5; |  | 
|   399   ctx.shadowColor = "green"; |  | 
|   400   ctx.lineWidth = 1; |  | 
|   401   ctx.textAlign = "left"; |  | 
|   402   ctx.rotate(Math.PI / 30); |  | 
|   403   ctx.fillText("State 1", width /2, height / 6); |  | 
|   404   ctx.strokeText("State 1", width /2, height / 6); |  | 
|   405   ctx.save(); |  | 
|   406  |  | 
|   407   ctx.font = "40px sans serif"; |  | 
|   408   ctx.fillStyle = "blue"; |  | 
|   409   ctx.strokeStyle = "orange"; |  | 
|   410   ctx.shadowBlur = 8; |  | 
|   411   ctx.shadowOffsetX = 5; |  | 
|   412   ctx.shadowColor = "black"; |  | 
|   413   ctx.lineWidth = 2; |  | 
|   414   ctx.textAlign = "right"; |  | 
|   415   ctx.rotate(Math.PI / 30); |  | 
|   416   ctx.fillText("State 2", width /2, 2 * height / 6); |  | 
|   417   ctx.strokeText("State 2", width /2, 2 * height / 6); |  | 
|   418   ctx.save(); |  | 
|   419  |  | 
|   420   ctx.font = "50px times roman"; |  | 
|   421   ctx.fillStyle = "yellow"; |  | 
|   422   ctx.strokeStyle = "gray"; |  | 
|   423   ctx.shadowBlur = 8; |  | 
|   424   ctx.shadowOffsetX = 5; |  | 
|   425   ctx.shadowColor = "red"; |  | 
|   426   ctx.lineWidth = 3; |  | 
|   427   ctx.textAlign = "center"; |  | 
|   428   ctx.rotate(-Math.PI / 15); |  | 
|   429   ctx.fillText("State 3", width /2, 3 * height / 6); |  | 
|   430   ctx.strokeText("State 3", width /2, 3 * height / 6); |  | 
|   431  |  | 
|   432   ctx.restore(); |  | 
|   433   ctx.fillText("State 2", width /2, 4 * height / 6); |  | 
|   434   ctx.strokeText("State 2", width /2, 4 * height / 6); |  | 
|   435  |  | 
|   436   ctx.restore(); |  | 
|   437   ctx.fillText("State 1", width /2, 5 * height / 6); |  | 
|   438   ctx.strokeText("State 1", width /2, 5 * height / 6); |  | 
|   439 } |  | 
|   440  |  | 
|   441 void mirror() { |  | 
|   442   initTest("Mirror"); |  | 
|   443   // translate context to center of canvas |  | 
|   444   ctx.translate(width / 2, height / 2); |  | 
|   445  |  | 
|   446   // flip context horizontally |  | 
|   447   ctx.scale(-1, 1); |  | 
|   448  |  | 
|   449   ctx.font = '30pt Calibri'; |  | 
|   450   ctx.textAlign = 'center'; |  | 
|   451   ctx.fillStyle = 'blue'; |  | 
|   452   ctx.fillText('Magic Mirror', 0, 0); |  | 
|   453 } |  | 
|   454  |  | 
|   455 void oval() { |  | 
|   456   initTest("Path - pop state - stroke"); |  | 
|   457   var centerX = 0; |  | 
|   458   var centerY = 0; |  | 
|   459   var radius = 50; |  | 
|   460  |  | 
|   461   // save state |  | 
|   462   ctx.save(); |  | 
|   463  |  | 
|   464   // translate context |  | 
|   465   ctx.translate(width / 2, height / 2); |  | 
|   466  |  | 
|   467   // scale context horizontally |  | 
|   468   ctx.scale(2, 1); |  | 
|   469  |  | 
|   470   // draw circle which will be stretched into an oval |  | 
|   471   ctx.beginPath(); |  | 
|   472   ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false); |  | 
|   473  |  | 
|   474   // restore to original state |  | 
|   475   ctx.restore(); |  | 
|   476  |  | 
|   477   // apply styling |  | 
|   478   ctx.fillStyle = '#8ED6FF'; |  | 
|   479   ctx.fill(); |  | 
|   480   ctx.lineWidth = 5; |  | 
|   481   ctx.strokeStyle = 'black'; |  | 
|   482   ctx.stroke(); |  | 
|   483 } |  | 
|   484  |  | 
|   485 void lineDash() { |  | 
|   486   initTest("Line dash"); |  | 
|   487   ctx.setLineDash([ 5, 8, 3 ]); |  | 
|   488   ctx.strokeStyle = "#FF0000"; |  | 
|   489   ctx.strokeRect(width / 10, height / 10, width / 2, height / 25); |  | 
|   490   ctx.lineDashOffset = 1; |  | 
|   491   ctx.strokeStyle = "#00FF00"; |  | 
|   492   ctx.strokeRect(width / 4, height / 5, width / 20, height / 8); |  | 
|   493   ctx.setLineDash([]); |  | 
|   494   ctx.strokeStyle = "#0000FF"; |  | 
|   495   ctx.strokeStyle = "rgba(128,128,128, 0.5)"; |  | 
|   496   ctx.strokeRect(width / 5, height / 10, width / 2, height / 8); |  | 
|   497   log("Width = $width"); |  | 
|   498 } |  | 
|   499  |  | 
|   500 void loadImage() { |  | 
|   501   initTest("Image loading"); |  | 
|   502   var imageObj = new ImageElement(); |  | 
|   503   // Setting src before onLoad is a more interesting test. |  | 
|   504   imageObj.src = 'chrome.png'; |  | 
|   505   imageObj.onLoad.listen((e) { |  | 
|   506     ctx.drawImage(e.target, 0, 0, width, height, 0, 0, width, height); |  | 
|   507   }); |  | 
|   508 } |  | 
|   509  |  | 
|   510 void clip() { |  | 
|   511   initTest("Clipping"); |  | 
|   512   var x = width / 2; |  | 
|   513   var y = height / 2; |  | 
|   514   var radius = height / 4; |  | 
|   515   var offset = 2 * radius / 3; |  | 
|   516  |  | 
|   517   ctx.save(); |  | 
|   518   ctx.beginPath(); |  | 
|   519   ctx.arc(x, y, radius, 0, 2 * Math.PI, false); |  | 
|   520   ctx.clip(); |  | 
|   521  |  | 
|   522   // draw blue circle inside clipping region |  | 
|   523   ctx.beginPath(); |  | 
|   524   ctx.arc(x - offset, y - offset, radius, 0, 2 * Math.PI, false); |  | 
|   525   ctx.fillStyle = 'blue'; |  | 
|   526   ctx.fill(); |  | 
|   527  |  | 
|   528   // draw yellow circle inside clipping region |  | 
|   529   ctx.beginPath(); |  | 
|   530   ctx.arc(x + offset, y, radius, 0, 2 * Math.PI, false); |  | 
|   531   ctx.fillStyle = 'yellow'; |  | 
|   532   ctx.fill(); |  | 
|   533  |  | 
|   534   // draw red circle inside clipping region |  | 
|   535   ctx.beginPath(); |  | 
|   536   ctx.arc(x, y + offset, radius, 0, 2 * Math.PI, false); |  | 
|   537   ctx.fillStyle = 'red'; |  | 
|   538   ctx.fill(); |  | 
|   539  |  | 
|   540   // Restore the canvas context to its original state |  | 
|   541   // before we defined the clipping region |  | 
|   542   ctx.restore(); |  | 
|   543   ctx.beginPath(); |  | 
|   544   ctx.arc(x, y, radius, 0, 2 * Math.PI, false); |  | 
|   545   ctx.lineWidth = 10; |  | 
|   546   ctx.strokeStyle = 'blue'; |  | 
|   547   ctx.stroke(); |  | 
|   548 } |  | 
|   549  |  | 
|   550 void shear() { |  | 
|   551   initTest("Transform"); |  | 
|   552   var rectWidth = width / 4; |  | 
|   553   var rectHeight = height / 8; |  | 
|   554  |  | 
|   555   // shear matrix: |  | 
|   556   //  1  sx  0 |  | 
|   557   //  sy  1  0 |  | 
|   558   //  0  0  1 |  | 
|   559  |  | 
|   560   var sx = 0.75; |  | 
|   561   // .75 horizontal shear |  | 
|   562   var sy = 0; |  | 
|   563   // no vertical shear |  | 
|   564  |  | 
|   565   // translate context to center of canvas |  | 
|   566   ctx.translate(width / 2, height / 2); |  | 
|   567  |  | 
|   568   // apply custom transform |  | 
|   569   ctx.transform(1, sy, sx, 1, 0, 0); |  | 
|   570  |  | 
|   571   ctx.fillStyle = 'blue'; |  | 
|   572   ctx.fillRect(-rectWidth / 2, rectHeight / -2, rectWidth, rectHeight); |  | 
|   573 } |  | 
|   574  |  | 
|   575 void composite() { |  | 
|   576   initTest("Composition"); |  | 
|   577   var num = 0; |  | 
|   578   var numPerRow = width ~/ 150; |  | 
|   579   var tempCanvas = new CanvasElement(width: width, height:height); |  | 
|   580   var tempContext = tempCanvas.getContext("2d"); |  | 
|   581   log("Width = $width, numPerRow = $numPerRow\n"); |  | 
|   582   for (var mode in [ 'source-atop', 'source-in', |  | 
|   583                      'source-out', 'source-over', |  | 
|   584                      'destination-atop', 'destination-in', |  | 
|   585                      'destination-out', 'destination-over', |  | 
|   586                      'lighter', 'darker', |  | 
|   587                      'xor', 'copy']) { |  | 
|   588     tempContext.save(); |  | 
|   589     tempContext.clearRect(0, 0, width, height); |  | 
|   590     tempContext.beginPath(); |  | 
|   591     tempContext.rect(0, 0, 55, 55); |  | 
|   592     tempContext.fillStyle = 'blue'; |  | 
|   593     tempContext.fill(); |  | 
|   594  |  | 
|   595     tempContext.globalCompositeOperation = mode; |  | 
|   596     tempContext.beginPath(); |  | 
|   597     tempContext.arc(50, 50, 35, 0, 2 * Math.PI, false); |  | 
|   598     tempContext.fillStyle = 'red'; |  | 
|   599     tempContext.fill(); |  | 
|   600     tempContext.restore(); |  | 
|   601     tempContext.font = '10pt Verdana'; |  | 
|   602     tempContext.fillStyle = 'black'; |  | 
|   603     tempContext.fillText(mode, 0, 100); |  | 
|   604     if (num > 0) { |  | 
|   605       if ((num % numPerRow) == 0) { |  | 
|   606         ctx.translate(-150 * (numPerRow-1), 150); |  | 
|   607       } else { |  | 
|   608         ctx.translate(150, 0); |  | 
|   609       } |  | 
|   610     } |  | 
|   611     ctx.drawImage(tempCanvas, 0, 0); |  | 
|   612     ++num; |  | 
|   613   } |  | 
|   614 } |  | 
|   615  |  | 
|   616 class Rectangle { |  | 
|   617   num x, y, width, height, borderWidth; |  | 
|   618 } |  | 
|   619  |  | 
|   620 var startTime = 0; |  | 
|   621 var myRectangle = null; |  | 
|   622  |  | 
|   623 void anim() { |  | 
|   624   if (myRectangle == null) { |  | 
|   625     myRectangle = new Rectangle(); |  | 
|   626     myRectangle.x = 250; |  | 
|   627     myRectangle.y = 70; |  | 
|   628     myRectangle.width = 100; |  | 
|   629     myRectangle.height = 50; |  | 
|   630     myRectangle.borderWidth = 5; |  | 
|   631     startTime = (new DateTime.now()).millisecondsSinceEpoch; |  | 
|   632   } |  | 
|   633  |  | 
|   634   var now = (new DateTime.now()).millisecondsSinceEpoch; |  | 
|   635   var time = now - startTime; |  | 
|   636   var amplitude = 150; |  | 
|   637  |  | 
|   638   // in ms |  | 
|   639   var period = 2000; |  | 
|   640   var centerX = width / 2 - myRectangle.width / 2; |  | 
|   641   var nextX = amplitude * Math.sin(time * 2 * Math.PI / period) + centerX; |  | 
|   642   myRectangle.x = nextX; |  | 
|   643  |  | 
|   644   // clear |  | 
|   645   ctx.clearRect(0, 0, width, height); |  | 
|   646  |  | 
|   647   // draw |  | 
|   648  |  | 
|   649   ctx.beginPath(); |  | 
|   650   ctx.rect(myRectangle.x, myRectangle.y, myRectangle.width, myRectangle.height); |  | 
|   651   ctx.fillStyle = '#8ED6FF'; |  | 
|   652   ctx.fill(); |  | 
|   653   ctx.lineWidth = myRectangle.borderWidth; |  | 
|   654   ctx.strokeStyle = 'black'; |  | 
|   655   ctx.stroke(); |  | 
|   656 } |  | 
|   657  |  | 
|   658 void linearGradient() { |  | 
|   659   initTest("Linear Gradient"); |  | 
|   660   ctx.rect(0, 0, width, height); |  | 
|   661   var grd = ctx.createLinearGradient(0, 0, width, height); |  | 
|   662   // light blue |  | 
|   663   grd.addColorStop(0, '#8ED6FF');    |  | 
|   664   // dark blue |  | 
|   665   grd.addColorStop(1, '#004CB3'); |  | 
|   666   ctx.fillStyle = grd; |  | 
|   667   ctx.fill(); |  | 
|   668 } |  | 
|   669  |  | 
|   670 void radialGradient() { |  | 
|   671   initTest("Radial Gradient"); |  | 
|   672   ctx.rect(0, 0, width, height); |  | 
|   673   var grd = ctx.createRadialGradient(238, 50, 10, 238, 50, 300); |  | 
|   674   // light blue |  | 
|   675   grd.addColorStop(0, '#8ED6FF'); |  | 
|   676   // dark blue |  | 
|   677   grd.addColorStop(1, '#004CB3'); |  | 
|   678   ctx.fillStyle = grd; |  | 
|   679   ctx.fill(); |  | 
|   680 } |  | 
|   681  |  | 
|   682 int testnum = 0; // Set this to -1 to start with last test. |  | 
|   683  |  | 
|   684 double x, y, z; |  | 
|   685  |  | 
|   686 onAccelerometer(double xx, double yy, double zz) { |  | 
|   687  x = xx; |  | 
|   688  y = yy; |  | 
|   689  z = zz; |  | 
|   690 } |  | 
|   691  |  | 
|   692 void update(num when) { |  | 
|   693   window.requestAnimationFrame(update); |  | 
|   694   if (testnum == 0) { |  | 
|   695     anim(); |  | 
|   696     return; |  | 
|   697   } |  | 
|   698   if (!isDirty) return; |  | 
|   699   switch(testnum) { |  | 
|   700     case 1: |  | 
|   701       helloWorld(); |  | 
|   702       break; |  | 
|   703     case 2: |  | 
|   704       blocks(); |  | 
|   705       break; |  | 
|   706     case 3: |  | 
|   707       squares(); |  | 
|   708       break; |  | 
|   709     case 4: |  | 
|   710       grid(); |  | 
|   711       break; |  | 
|   712     case 5: |  | 
|   713       strokeLines(); |  | 
|   714       break; |  | 
|   715     case 6: |  | 
|   716       lineJoin(); |  | 
|   717       break; |  | 
|   718     case 7: |  | 
|   719       colors(); |  | 
|   720       break; |  | 
|   721     case 8: |  | 
|   722       rotate(); |  | 
|   723       break; |  | 
|   724     case 9: |  | 
|   725       alpha(); |  | 
|   726       break; |  | 
|   727     case 10: |  | 
|   728       scale(); |  | 
|   729       break; |  | 
|   730     case 11: |  | 
|   731       curves(); |  | 
|   732       break; |  | 
|   733     case 12: |  | 
|   734       shadows(); |  | 
|   735       break; |  | 
|   736     case 13: |  | 
|   737       lineJoins(); |  | 
|   738       break; |  | 
|   739     case 14: |  | 
|   740       saveRestore(); |  | 
|   741       break; |  | 
|   742     case 15: |  | 
|   743       mirror(); |  | 
|   744       break; |  | 
|   745     case 16: |  | 
|   746       oval(); |  | 
|   747       break; |  | 
|   748     case 17: |  | 
|   749       lineDash(); |  | 
|   750       break; |  | 
|   751     case 18: |  | 
|   752       loadImage(); |  | 
|   753       break; |  | 
|   754     case 19: |  | 
|   755       clip(); |  | 
|   756       break; |  | 
|   757     case 20: |  | 
|   758       shear(); |  | 
|   759       break; |  | 
|   760     case 21: |  | 
|   761       composite(); |  | 
|   762       break; |  | 
|   763     case 22: |  | 
|   764       smiley(); |  | 
|   765       break; |  | 
|   766     case 23: |  | 
|   767       linearGradient(); |  | 
|   768       break; |  | 
|   769     case 24: |  | 
|   770       radialGradient(); |  | 
|   771       break; |  | 
|   772     case 25: |  | 
|   773       break; // Skip for now; this is really slow. |  | 
|   774       var rayTracer = new RayTracer(); |  | 
|   775       rayTracer.render(defaultScene(), ctx, width, height); |  | 
|   776       break; |  | 
|   777     default: |  | 
|   778       if (testnum < 0) { |  | 
|   779         testnum = 24; |  | 
|   780       } else { |  | 
|   781         testnum = 0; |  | 
|   782       } |  | 
|   783       return; |  | 
|   784   } |  | 
|   785  |  | 
|   786   isDirty = false; |  | 
|   787 } |  | 
|   788  |  | 
|   789 // Raytracer adapted from https://gist.github.com/mythz/3817303. |  | 
|   790  |  | 
|   791 Scene defaultScene() => |  | 
|   792    new Scene( |  | 
|   793       [new Plane(new Vector(0.0, 1.0, 0.0), 0.0, Surfaces.checkerboard), |  | 
|   794        new Sphere(new Vector(0.0, 1.0, -0.25), 1.0, Surfaces.shiny), |  | 
|   795        new Sphere(new Vector(-1.0, 0.5, 1.5), 0.5, Surfaces.shiny)], |  | 
|   796       [new Light(new Vector(-2.0, 2.5, 0.0), new Color(0.49, 0.07, 0.07) ), |  | 
|   797        new Light(new Vector(1.5, 2.5, 1.5),  new Color(0.07, 0.07, 0.49) ), |  | 
|   798        new Light(new Vector(1.5, 2.5, -1.5), new Color(0.07, 0.49, 0.071) ), |  | 
|   799        new Light(new Vector(0.0, 3.5, 0.0),  new Color(0.21, 0.21, 0.35) )], |  | 
|   800       new Camera(new Vector(3.0, 2.0, 4.0), new Vector(-1.0, 0.5, 0.0)) |  | 
|   801    ); |  | 
|   802  |  | 
|   803  |  | 
|   804 class Vector { |  | 
|   805    num x, y, z; |  | 
|   806    Vector(this.x, this.y, this.z); |  | 
|   807  |  | 
|   808    operator -(Vector v)  => new Vector(x - v.x, y - v.y, z - v.z); |  | 
|   809    operator +(Vector v)  => new Vector(x + v.x, y + v.y, z + v.z); |  | 
|   810    static times(num k, Vector v) => new Vector(k * v.x, k * v.y, k * v.z); |  | 
|   811    static num dot(Vector v1, Vector v2)  => |  | 
|   812        v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; |  | 
|   813    static num mag(Vector v)  => Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); |  | 
|   814    static Vector norm(Vector v) { |  | 
|   815      var _mag = mag(v); |  | 
|   816      var div = _mag == 0 ? double.INFINITY : 1.0 / _mag; |  | 
|   817      return times(div, v); |  | 
|   818    } |  | 
|   819    static Vector cross(Vector v1, Vector v2) { |  | 
|   820      return new Vector(v1.y * v2.z - v1.z * v2.y, |  | 
|   821          v1.z * v2.x - v1.x * v2.z, |  | 
|   822          v1.x * v2.y - v1.y * v2.x); |  | 
|   823    } |  | 
|   824 } |  | 
|   825  |  | 
|   826 class Color { |  | 
|   827    num r, g, b; |  | 
|   828    static final white = new Color(1.0, 1.0, 1.0); |  | 
|   829    static final grey = new Color(0.5, 0.5, 0.5); |  | 
|   830    static final black = new Color(0.0, 0.0, 0.0); |  | 
|   831    static final background = Color.black; |  | 
|   832    static final defaultColor = Color.black; |  | 
|   833  |  | 
|   834    Color(this.r,this.g,this.b); |  | 
|   835    static scale(num k, Color v) => new Color(k * v.r, k * v.g, k * v.b); |  | 
|   836    operator +(Color v) => new Color(r + v.r, g + v.g, b + v.b); |  | 
|   837    operator *(Color v) => new Color(r * v.r, g * v.g, b * v.b); |  | 
|   838    static _intColor(num d) => ((d > 1 ? 1 : d) * 255).toInt(); |  | 
|   839    static String toDrawingRGB(Color c) => |  | 
|   840        "rgb(${_intColor(c.r)}, ${_intColor(c.g)}, ${_intColor(c.b)})"; |  | 
|   841 } |  | 
|   842  |  | 
|   843 class Camera { |  | 
|   844   Vector pos, forward, right, up; |  | 
|   845   Camera (this.pos, Vector lookAt) { |  | 
|   846      var down = new Vector(0.0, -1.0, 0.0); |  | 
|   847      forward = Vector.norm(lookAt - pos); |  | 
|   848      right = Vector.times(1.5, Vector.norm(Vector.cross(forward, down))); |  | 
|   849      up = Vector.times(1.5, Vector.norm(Vector.cross(forward, right))); |  | 
|   850   } |  | 
|   851 } |  | 
|   852  |  | 
|   853 class Ray { |  | 
|   854   Vector start, dir; |  | 
|   855   Ray([this.start, this.dir]); |  | 
|   856 } |  | 
|   857  |  | 
|   858 class Intersection { |  | 
|   859   Thing thing; |  | 
|   860   Ray ray; |  | 
|   861   num dist; |  | 
|   862   Intersection(this.thing, this.ray, this.dist); |  | 
|   863 } |  | 
|   864  |  | 
|   865 class Light { |  | 
|   866   Vector pos; |  | 
|   867   Color color; |  | 
|   868   Light(this.pos, this.color); |  | 
|   869 } |  | 
|   870  |  | 
|   871 abstract class Surface { |  | 
|   872   int roughness; |  | 
|   873   Color diffuse(Vector pos); |  | 
|   874   Color specular(Vector pos); |  | 
|   875   num reflect(Vector pos); |  | 
|   876 } |  | 
|   877  |  | 
|   878 abstract class Thing { |  | 
|   879   Intersection intersect(Ray ray); |  | 
|   880   Vector normal(Vector pos); |  | 
|   881   Surface surface; |  | 
|   882 } |  | 
|   883  |  | 
|   884 class Scene { |  | 
|   885   List<Thing> things; |  | 
|   886   List<Light> lights; |  | 
|   887   Camera camera; |  | 
|   888   Scene([this.things,this.lights,this.camera]); |  | 
|   889 } |  | 
|   890  |  | 
|   891 class Sphere implements Thing { |  | 
|   892   num radius2, radius; |  | 
|   893   Vector center; |  | 
|   894   Surface surface; |  | 
|   895  |  | 
|   896   Sphere (this.center, this.radius, this.surface) { |  | 
|   897        this.radius2 = radius * radius; |  | 
|   898   } |  | 
|   899   normal(Vector pos) => Vector.norm(pos - center); |  | 
|   900   intersect(Ray ray) { |  | 
|   901     var eo = this.center - ray.start; |  | 
|   902     var v = Vector.dot(eo, ray.dir); |  | 
|   903     var dist = 0; |  | 
|   904     if (v >= 0) { |  | 
|   905       var disc = this.radius2 - (Vector.dot(eo, eo) - v * v); |  | 
|   906       if (disc >= 0) { |  | 
|   907          dist = v - Math.sqrt(disc); |  | 
|   908       } |  | 
|   909     } |  | 
|   910     return dist == 0 ? null : new Intersection(this, ray, dist); |  | 
|   911   } |  | 
|   912 } |  | 
|   913  |  | 
|   914 class Plane implements Thing { |  | 
|   915   Vector norm; |  | 
|   916   num offset; |  | 
|   917   Surface surface; |  | 
|   918   Plane(this.norm, this.offset, this.surface); |  | 
|   919   Vector normal(Vector pos) => norm; |  | 
|   920   Intersection intersect(Ray ray) { |  | 
|   921     var denom = Vector.dot(norm, ray.dir); |  | 
|   922     if (denom > 0) { |  | 
|   923       return null; |  | 
|   924     } else { |  | 
|   925       var dist = (Vector.dot(norm, ray.start) + offset) / (-denom); |  | 
|   926       return new Intersection(this, ray, dist); |  | 
|   927     } |  | 
|   928   } |  | 
|   929 } |  | 
|   930  |  | 
|   931 class CustomSurface implements Surface { |  | 
|   932   Color diffuseColor, specularColor; |  | 
|   933   int roughness; |  | 
|   934   num reflectPos; |  | 
|   935   CustomSurface(this.diffuseColor, this.specularColor, |  | 
|   936       this.reflectPos, this.roughness); |  | 
|   937   diffuse(pos) => diffuseColor; |  | 
|   938   specular(pos) => specularColor; |  | 
|   939   reflect(pos) => reflectPos; |  | 
|   940 } |  | 
|   941  |  | 
|   942 class CheckerBoardSurface implements Surface { |  | 
|   943   int roughness; |  | 
|   944   CheckerBoardSurface([this.roughness=150]); |  | 
|   945   diffuse(pos) => (pos.z.floor() + pos.x.floor()) % 2 != 0 |  | 
|   946     ? Color.white |  | 
|   947     : Color.black; |  | 
|   948   specular(pos) => Color.white; |  | 
|   949   reflect(pos)  => (pos.z.floor() + pos.x.floor()) % 2 != 0 ? 0.1 : 0.7; |  | 
|   950 } |  | 
|   951  |  | 
|   952 class Surfaces { |  | 
|   953   static final shiny = new CustomSurface(Color.white, Color.grey, 0.7, 250); |  | 
|   954   static final checkerboard = new CheckerBoardSurface(); |  | 
|   955 } |  | 
|   956  |  | 
|   957 class RayTracer { |  | 
|   958   num _maxDepth = 5; |  | 
|   959  |  | 
|   960   Intersection _intersections(Ray ray, Scene scene) { |  | 
|   961     var closest = double.INFINITY; |  | 
|   962     Intersection closestInter = null; |  | 
|   963     for (Thing thing in scene.things) { |  | 
|   964       var inter = thing.intersect(ray); |  | 
|   965       if (inter != null && inter.dist < closest) { |  | 
|   966         closestInter = inter; |  | 
|   967         closest = inter.dist; |  | 
|   968       } |  | 
|   969     } |  | 
|   970     return closestInter; |  | 
|   971   } |  | 
|   972  |  | 
|   973   _testRay(Ray ray, Scene scene) { |  | 
|   974     var isect = _intersections(ray, scene); |  | 
|   975     return isect != null ? isect.dist : null; |  | 
|   976   } |  | 
|   977  |  | 
|   978   _traceRay(Ray ray, Scene scene, num depth) { |  | 
|   979     var isect = _intersections(ray, scene); |  | 
|   980     return isect == null ? Color.background : _shade(isect, scene, depth); |  | 
|   981   } |  | 
|   982  |  | 
|   983   _shade(Intersection isect, Scene scene, num depth) { |  | 
|   984     var d = isect.ray.dir; |  | 
|   985     var pos = Vector.times(isect.dist, d) + isect.ray.start; |  | 
|   986     var normal = isect.thing.normal(pos); |  | 
|   987     var reflectDir = d - |  | 
|   988         Vector.times(2, Vector.times(Vector.dot(normal, d), normal)); |  | 
|   989     var naturalColor = Color.background + |  | 
|   990         _getNaturalColor(isect.thing, pos, normal, reflectDir, scene); |  | 
|   991     var reflectedColor = (depth >= _maxDepth) ? Color.grey : |  | 
|   992          _getReflectionColor(isect.thing, pos, normal, reflectDir, |  | 
|   993              scene, depth); |  | 
|   994     return naturalColor + reflectedColor; |  | 
|   995   } |  | 
|   996  |  | 
|   997   _getReflectionColor(Thing thing, Vector pos, Vector normal, Vector rd, |  | 
|   998       Scene scene, num depth) => |  | 
|   999           Color.scale(thing.surface.reflect(pos), |  | 
|  1000               _traceRay(new Ray(pos, rd), scene, depth + 1)); |  | 
|  1001  |  | 
|  1002   _getNaturalColor(Thing thing, Vector pos, Vector norm, Vector rd, |  | 
|  1003       Scene scene) { |  | 
|  1004     var addLight = (col, light) { |  | 
|  1005       var ldis = light.pos - pos; |  | 
|  1006       var livec = Vector.norm(ldis); |  | 
|  1007       var neatIsect = _testRay(new Ray(pos, livec), scene); |  | 
|  1008       var isInShadow = neatIsect == null ? false : |  | 
|  1009           (neatIsect <= Vector.mag(ldis)); |  | 
|  1010       if (isInShadow) { |  | 
|  1011         return col; |  | 
|  1012       } else { |  | 
|  1013         var illum = Vector.dot(livec, norm); |  | 
|  1014         var lcolor = (illum > 0) ? Color.scale(illum, light.color) |  | 
|  1015                                  : Color.defaultColor; |  | 
|  1016         var specular = Vector.dot(livec, Vector.norm(rd)); |  | 
|  1017         var scolor = (specular > 0) |  | 
|  1018             ? Color.scale(Math.pow(specular, thing.surface.roughness), |  | 
|  1019                 light.color) |  | 
|  1020             : Color.defaultColor; |  | 
|  1021         return col + (thing.surface.diffuse(pos)  * lcolor) |  | 
|  1022             + (thing.surface.specular(pos) * scolor); |  | 
|  1023       } |  | 
|  1024     }; |  | 
|  1025     return scene.lights.fold(Color.defaultColor, addLight); |  | 
|  1026   } |  | 
|  1027  |  | 
|  1028   render(Scene scene, CanvasRenderingContext2D ctx, num screenWidth, |  | 
|  1029          num screenHeight) { |  | 
|  1030     var getPoint = (x, y, camera) { |  | 
|  1031       var recenterX = (x) => (x - (screenWidth / 2.0)) / 2.0 / screenWidth; |  | 
|  1032       var recenterY = (y) => - (y - (screenHeight / 2.0)) / 2.0 / screenHeight; |  | 
|  1033       return Vector.norm(camera.forward |  | 
|  1034           + Vector.times(recenterX(x), camera.right) |  | 
|  1035           + Vector.times(recenterY(y), camera.up)); |  | 
|  1036     }; |  | 
|  1037     for (int y = 0; y < screenHeight; y++) { |  | 
|  1038       for (int x = 0; x < screenWidth; x++) { |  | 
|  1039         var color = _traceRay(new Ray(scene.camera.pos, |  | 
|  1040             getPoint(x, y, scene.camera) ), scene, 0); |  | 
|  1041         ctx.fillStyle = Color.toDrawingRGB(color); |  | 
|  1042         ctx.fillRect(x, y, x + 1, y + 1); |  | 
|  1043       } |  | 
|  1044     } |  | 
|  1045   } |  | 
|  1046 } |  | 
|  1047  |  | 
| OLD | NEW |