Index: samples/openglui/src/openglui_canvas_tests.dart |
diff --git a/samples/openglui/src/openglui_canvas_tests.dart b/samples/openglui/src/openglui_canvas_tests.dart |
deleted file mode 100644 |
index b9b394704b5abc4a7c08630624e7e5cae7353b89..0000000000000000000000000000000000000000 |
--- a/samples/openglui/src/openglui_canvas_tests.dart |
+++ /dev/null |
@@ -1,1047 +0,0 @@ |
-// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
-// |
-// Canvas API tests. Some of these are adapted from: |
-// |
-// http://www.html5canvastutorials.com/ |
- |
-library openglui_canvas_tests; |
- |
-import 'gl.dart'; |
-import 'dart:math' as Math; |
- |
-var ctx; |
-var width, height; |
-bool isDirty = true; |
-var canvas; |
- |
-void resize(int w, int h) { |
- width = w; |
- height = h; |
-} |
- |
-void setup(canvasp, int w, int h, int f) { |
- 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; |
- } |
- 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"; |
- } |
- resize(w, h); |
- window.requestAnimationFrame(update); |
- log("Done setup"); |
-} |
- |
-resetCanvas() { |
- ctx.globalCompositeOperation = "source-over"; |
- ctx.setTransform(1, 0, 0, 1, 0, 0); |
- ctx.fillStyle = "#FFFFFF"; |
- ctx.clearRect(0, 0, width, height); |
- ctx.shadowOffsetX = ctx.shadowOffsetY = ctx.shadowBlur = 0.0; |
- ctx.beginPath(); |
-} |
- |
-initTest(title) { |
- resetCanvas(); |
- ctx.font = "15px Courier"; |
- ctx.textAlign = 'left'; |
- ctx.fillStyle = "black"; |
- ctx.fillText(title, 20, 20); |
-} |
- |
-helloWorld() { |
- initTest("Hello world"); |
- ctx.font = "30px Courier"; |
- ctx.strokeStyle = "#7F7F7F"; |
- ctx.fillStyle = "#7F7F7F"; |
- ctx.textAlign = "center"; |
- ctx.lineWidth = 2; |
- ctx.strokeText("Align Center", width / 2, height / 4); |
- ctx.textAlign = "left"; |
- ctx.fillText("Align Left", width / 2, height / 2); |
- ctx.textAlign = "right"; |
- ctx.fillText("Align Right", width / 2, 3 * height / 4); |
-} |
- |
-blocks() { |
- initTest("fillRect/strokeRect"); |
- ctx.fillStyle = "#FF0000"; |
- ctx.fillRect(width / 10, height / 10, width / 2, height / 25); |
- ctx.fillStyle = "#00FF00"; |
- ctx.fillRect(width / 4, height / 5, width / 20, height / 8); |
- //ctx.fillStyle = "rgba(0,0,255,0.8)"; |
- ctx.strokeStyle = "rgba(128,128,128, 0.5)"; |
- //ctx.fillStyle = "#7F7F7F"; |
- ctx.strokeRect(width / 5, height / 10, width / 2, height / 8); |
-} |
- |
-square(left, top, width, height) { |
- ctx.beginPath(); |
- ctx.moveTo(left, top); |
- ctx.lineTo(left + width, top); |
- ctx.lineTo(left + width, top + height); |
- ctx.lineTo(left, top + height); |
- ctx.closePath(); //lineTo(left, top); |
-} |
- |
-squares() { |
- initTest("fill/stroke paths"); |
- ctx.strokeStyle = "black"; |
- ctx.fillStyle = "#FF0000"; |
- ctx.lineWidth = 4; |
- square(width / 10, height / 10, width / 2, height / 25); |
- ctx.fill(); |
- ctx.stroke(); |
- ctx.fillStyle = "#00FF00"; |
- square(width / 4, height / 5, width / 20, height / 8); |
- ctx.fill(); |
- ctx.stroke(); |
- ctx.fillStyle = "rgba(128,128,128, 0.5)"; |
- square(width / 5, height / 10, width / 2, height / 8); |
- ctx.fill(); |
- ctx.stroke(); |
-} |
- |
-lineJoin() { |
- initTest("Line joins"); |
- ctx.strokeStyle = "black"; |
- ctx.fillStyle = "#FF0000"; |
- ctx.lineWidth = 8; |
- ctx.lineJoin = "round"; |
- square(width / 10, height / 10, width / 2, height / 25); |
- ctx.stroke(); |
- ctx.fillStyle = "#00FF00"; |
- ctx.lineJoin = "bevel"; |
- square(width / 4, height / 5, width / 20, height / 8); |
- ctx.stroke(); |
- ctx.fillStyle = "rgba(128,128,128, 0.5)"; |
- ctx.lineJoin = "miter"; |
- square(width / 5, height / 10, width / 2, height / 8); |
- ctx.stroke(); |
-} |
- |
-grid() { |
- initTest("line strokes"); |
- ctx.lineWidth = 1; |
- for (var x = 0.5; x < width; x += 10) { |
- ctx.moveTo(x, 0); |
- ctx.lineTo(x, height); |
- } |
- for (var y = 0.5; y < height; y += 10) { |
- ctx.moveTo(0, y); |
- ctx.lineTo(width, y); |
- } |
- ctx.strokeStyle = "#eee"; |
- ctx.stroke(); |
-} |
- |
-line(x1, y1, x2, y2) { |
- ctx.beginPath(); |
- ctx.moveTo(x1, y1); |
- ctx.lineTo(x2, y2); |
-} |
- |
-strokeLines() { |
- initTest("line caps"); |
- ctx.lineWidth = height / 40; |
- ctx.strokeStyle = '#0000ff'; |
- // butt line cap (top line) |
- line(width / 4, height / 4, 3 * width / 4, height / 4); |
- ctx.lineCap = 'butt'; |
- ctx.stroke(); |
- |
- // round line cap (middle line) |
- line(width / 4, height / 2, 3 * width / 4, height / 2); |
- ctx.lineCap = 'round'; |
- ctx.stroke(); |
- |
- // square line cap (bottom line) |
- line(width / 4, 3 * height / 4, 3 * width / 4, 3 * height / 4); |
- ctx.lineCap = 'square'; |
- ctx.stroke(); |
-} |
- |
-colors() { |
- initTest("Colors"); |
- var colors = [ |
- "maroon", |
- "red", |
- "orange", |
- "yellow", |
- "olive", |
- "purple", |
- "fuschia", |
- "white", |
- "lime", |
- "green", |
- "navy", |
- "blue", |
- "aqua", |
- "teal", |
- "silver", |
- "gray", |
- "black" |
- ]; |
- |
- var i = 1; |
- var yinc = height / (2 * colors.length + 1); |
- |
- ctx.textAlign = "center"; |
- ctx.font = "${yinc}px Courier"; |
- |
- for (var color in colors) { |
- ctx.fillStyle = color; |
- ctx.fillRect(width / 4, i * 2 * yinc, width / 2, 3 * yinc / 2); |
- ctx.fillStyle = (color == "gray") ? "white" : "gray"; |
- ctx.fillText(color, width / 2, i * 2 * yinc + 7 * yinc / 8); |
- ++i; |
- } |
-} |
- |
-smiley() { |
- initTest("arcs"); |
- ctx.translate(width / 2 - 80, height / 2 - 75); |
- |
- ctx.beginPath(); |
- ctx.arc(100,80,75,0,Math.PI*2,true); |
- ctx.fillStyle = "rgb(255,255,204)"; |
- ctx.fill(); |
- ctx.stroke(); |
- |
- ctx.fillStyle = "black"; |
- ctx.beginPath(); |
- ctx.arc(80,55,8,0,Math.PI*2,true); |
- ctx.fill(); |
- |
- ctx.beginPath(); |
- ctx.arc(120,55,8,0,Math.PI*2,true); |
- ctx.fill(); |
- |
- ctx.beginPath(); |
- ctx.arc(100,85,10,4,Math.PI*2,true); |
- ctx.stroke(); |
- |
- ctx.beginPath(); |
- ctx.arc(100,95,30,0,Math.PI,false); |
- ctx.stroke(); |
- |
- ctx.setTransform(1, 0, 0, 1, 0, 0); |
-} |
- |
-rot(txt1, [txt2, sx = 1.0, sy = 1.0]) { |
- if (txt2 == null) txt2 = txt1; |
- ctx.font = "50px sans serif"; |
- ctx.translate(width / 2, height / 2); |
- ctx.textAlign = "right"; |
- ctx.fillStyle = "red"; |
- ctx.fillText(txt1, 0, 0); |
- ctx.rotate(Math.PI / 2); |
- ctx.scale(sx, sy); |
- ctx.fillStyle = "green"; |
- ctx.fillText(txt2, 0, 0); |
- ctx.scale(sx, sy); |
- ctx.rotate(Math.PI / 2); |
- ctx.fillStyle = "blue"; |
- ctx.fillText(txt1, 0, 0); |
- ctx.scale(sx, sy); |
- ctx.rotate(Math.PI / 2); |
- ctx.fillStyle = "yellow"; |
- ctx.fillText(txt2, 0, 0); |
- ctx.setTransform(1, 0, 0, 1, 0, 0); |
-} |
- |
-rotate() { |
- initTest("Rotation"); |
- rot("Dart"); |
-} |
- |
-alpha() { |
- initTest("Global alpha"); |
- ctx.fillStyle = "gray"; |
- ctx.fillRect(0, 0, width, height); |
- grid(); |
- ctx.globalAlpha = 0.5; |
- rot("Global", "Alpha"); |
- ctx.globalAlpha = 1.0; |
-} |
- |
-scale() { |
- initTest("Scale"); |
- rot("Scale", "Test", 0.8, 0.5); |
-} |
- |
-curves() { |
- initTest("Curves"); |
- ctx.beginPath(); |
- ctx.moveTo(188, 150); |
- ctx.quadraticCurveTo(288, 0, 388, 150); |
- ctx.lineWidth = 2; |
- ctx.strokeStyle = 'red'; |
- ctx.stroke(); |
- ctx.beginPath(); |
- ctx.moveTo(188, 130); |
- ctx.bezierCurveTo(140, 10, 388, 10, 388, 170); |
- ctx.lineWidth = 5; |
- ctx.strokeStyle = 'blue'; |
- ctx.stroke(); |
- var x1, x2, y1, y2, ex, ey; |
- ctx.beginPath(); |
- x1 = width / 4; |
- y1 = height / 2; |
- x2 = width / 2; |
- y2 = height / 4; |
- ex = 3 * width / 4; |
- ey = y1; |
- ctx.moveTo(x1, x2); |
- ctx.quadraticCurveTo(x2, y2, ex, ey); |
- ctx.lineWidth = 10; |
- ctx.strokeStyle = 'green'; |
- ctx.stroke(); |
- |
- ctx.beginPath(); |
- ctx.moveTo(188, 130); |
- x1 = width / 8; |
- x2 = 7 * width / 8; |
- y1 = height / 50; |
- y2 = y1; |
- ex = x2; |
- ey = height / 2; |
- ctx.bezierCurveTo(x1, y1, x2, y2, ex, ey); |
- ctx.lineWidth = 7; |
- ctx.strokeStyle = 'black'; |
- ctx.stroke(); |
- |
- // Draw a cloud |
- ctx.beginPath(); |
- var wscale = width / 578; |
- var hscale = height / 800; |
- ctx.translate(0, height / 2); |
- ctx.moveTo(170 * wscale, 80 * hscale); |
- ctx.bezierCurveTo(130 * wscale, 100 * hscale, |
- 130 * wscale, 150 * hscale, |
- 230 * wscale, 150 * hscale); |
- ctx.bezierCurveTo(250 * wscale, 180 * hscale, |
- 320 * wscale, 180 * hscale, |
- 340 * wscale, 150 * hscale); |
- ctx.bezierCurveTo(420 * wscale, 150 * hscale, |
- 420 * wscale, 120 * hscale, |
- 390 * wscale, 100 * hscale); |
- ctx.bezierCurveTo(430 * wscale, 40 * hscale, |
- 370 * wscale, 30 * hscale, |
- 340 * wscale, 50 * hscale); |
- ctx.bezierCurveTo(320 * wscale, 5 * hscale, |
- 250 * wscale, 20 * hscale, |
- 250 * wscale, 50 * hscale); |
- ctx.bezierCurveTo(200 * wscale, 5 * hscale, |
- 150 * wscale, 20 * hscale, |
- 170 * wscale, 80 * hscale); |
- ctx.closePath(); |
- ctx.lineWidth = 5; |
- ctx.fillStyle = 'gray'; |
- ctx.fill(); |
-} |
- |
-void shadows() { |
- initTest("Shadows"); |
- ctx.shadowBlur=20; |
- ctx.shadowColor="black"; |
- ctx.fillStyle="red"; |
- var w = width / 2; |
- if (w > height / 2) w = height / 2; |
- ctx.fillRect(width / 2 - w / 2, height / 4 - w / 2, w, w); |
- ctx.shadowOffsetX = 10; |
- ctx.shadowOffsetY = 10; |
- ctx.shadowColor="green"; |
- ctx.fillRect(width / 2 - w / 2, 3 * height / 4 - w / 2, w, w); |
-} |
- |
-void lineJoins() { |
- initTest("Line joins"); |
- ctx.lineWidth=10; |
- ctx.lineJoin="miter"; |
- ctx.moveTo(width / 2 - 25, height / 4 - 10); |
- ctx.lineTo(width /2 + 25, height / 4); |
- ctx.lineTo(width / 2 - 25, height / 4 + 10); |
- ctx.stroke(); |
- ctx.lineJoin="round"; |
- ctx.moveTo(width / 2 - 25, height / 2 - 10); |
- ctx.lineTo(width /2 + 25, height / 2); |
- ctx.lineTo(width / 2 - 25, height / 2 + 10); |
- ctx.stroke(); |
- ctx.lineJoin="bevel"; |
- ctx.moveTo(width / 2 - 25, 3 * height / 4 - 10); |
- ctx.lineTo(width /2 + 25, 3 * height / 4); |
- ctx.lineTo(width / 2 - 25, 3 * height / 4 + 10); |
- ctx.stroke(); |
-} |
- |
-void saveRestore() { |
- initTest("Save/restore state"); |
- ctx.font = "30px courier"; |
- ctx.fillStyle = "red"; |
- ctx.strokeStyle = "black"; |
- ctx.shadowBlur = 5; |
- ctx.shadowColor = "green"; |
- ctx.lineWidth = 1; |
- ctx.textAlign = "left"; |
- ctx.rotate(Math.PI / 30); |
- ctx.fillText("State 1", width /2, height / 6); |
- ctx.strokeText("State 1", width /2, height / 6); |
- ctx.save(); |
- |
- ctx.font = "40px sans serif"; |
- ctx.fillStyle = "blue"; |
- ctx.strokeStyle = "orange"; |
- ctx.shadowBlur = 8; |
- ctx.shadowOffsetX = 5; |
- ctx.shadowColor = "black"; |
- ctx.lineWidth = 2; |
- ctx.textAlign = "right"; |
- ctx.rotate(Math.PI / 30); |
- ctx.fillText("State 2", width /2, 2 * height / 6); |
- ctx.strokeText("State 2", width /2, 2 * height / 6); |
- ctx.save(); |
- |
- ctx.font = "50px times roman"; |
- ctx.fillStyle = "yellow"; |
- ctx.strokeStyle = "gray"; |
- ctx.shadowBlur = 8; |
- ctx.shadowOffsetX = 5; |
- ctx.shadowColor = "red"; |
- ctx.lineWidth = 3; |
- ctx.textAlign = "center"; |
- ctx.rotate(-Math.PI / 15); |
- ctx.fillText("State 3", width /2, 3 * height / 6); |
- ctx.strokeText("State 3", width /2, 3 * height / 6); |
- |
- ctx.restore(); |
- ctx.fillText("State 2", width /2, 4 * height / 6); |
- ctx.strokeText("State 2", width /2, 4 * height / 6); |
- |
- ctx.restore(); |
- ctx.fillText("State 1", width /2, 5 * height / 6); |
- ctx.strokeText("State 1", width /2, 5 * height / 6); |
-} |
- |
-void mirror() { |
- initTest("Mirror"); |
- // translate context to center of canvas |
- ctx.translate(width / 2, height / 2); |
- |
- // flip context horizontally |
- ctx.scale(-1, 1); |
- |
- ctx.font = '30pt Calibri'; |
- ctx.textAlign = 'center'; |
- ctx.fillStyle = 'blue'; |
- ctx.fillText('Magic Mirror', 0, 0); |
-} |
- |
-void oval() { |
- initTest("Path - pop state - stroke"); |
- var centerX = 0; |
- var centerY = 0; |
- var radius = 50; |
- |
- // save state |
- ctx.save(); |
- |
- // translate context |
- ctx.translate(width / 2, height / 2); |
- |
- // scale context horizontally |
- ctx.scale(2, 1); |
- |
- // draw circle which will be stretched into an oval |
- ctx.beginPath(); |
- ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false); |
- |
- // restore to original state |
- ctx.restore(); |
- |
- // apply styling |
- ctx.fillStyle = '#8ED6FF'; |
- ctx.fill(); |
- ctx.lineWidth = 5; |
- ctx.strokeStyle = 'black'; |
- ctx.stroke(); |
-} |
- |
-void lineDash() { |
- initTest("Line dash"); |
- ctx.setLineDash([ 5, 8, 3 ]); |
- ctx.strokeStyle = "#FF0000"; |
- ctx.strokeRect(width / 10, height / 10, width / 2, height / 25); |
- ctx.lineDashOffset = 1; |
- ctx.strokeStyle = "#00FF00"; |
- ctx.strokeRect(width / 4, height / 5, width / 20, height / 8); |
- ctx.setLineDash([]); |
- ctx.strokeStyle = "#0000FF"; |
- ctx.strokeStyle = "rgba(128,128,128, 0.5)"; |
- ctx.strokeRect(width / 5, height / 10, width / 2, height / 8); |
- log("Width = $width"); |
-} |
- |
-void loadImage() { |
- initTest("Image loading"); |
- var imageObj = new ImageElement(); |
- // Setting src before onLoad is a more interesting test. |
- imageObj.src = 'chrome.png'; |
- imageObj.onLoad.listen((e) { |
- ctx.drawImage(e.target, 0, 0, width, height, 0, 0, width, height); |
- }); |
-} |
- |
-void clip() { |
- initTest("Clipping"); |
- var x = width / 2; |
- var y = height / 2; |
- var radius = height / 4; |
- var offset = 2 * radius / 3; |
- |
- ctx.save(); |
- ctx.beginPath(); |
- ctx.arc(x, y, radius, 0, 2 * Math.PI, false); |
- ctx.clip(); |
- |
- // draw blue circle inside clipping region |
- ctx.beginPath(); |
- ctx.arc(x - offset, y - offset, radius, 0, 2 * Math.PI, false); |
- ctx.fillStyle = 'blue'; |
- ctx.fill(); |
- |
- // draw yellow circle inside clipping region |
- ctx.beginPath(); |
- ctx.arc(x + offset, y, radius, 0, 2 * Math.PI, false); |
- ctx.fillStyle = 'yellow'; |
- ctx.fill(); |
- |
- // draw red circle inside clipping region |
- ctx.beginPath(); |
- ctx.arc(x, y + offset, radius, 0, 2 * Math.PI, false); |
- ctx.fillStyle = 'red'; |
- ctx.fill(); |
- |
- // Restore the canvas context to its original state |
- // before we defined the clipping region |
- ctx.restore(); |
- ctx.beginPath(); |
- ctx.arc(x, y, radius, 0, 2 * Math.PI, false); |
- ctx.lineWidth = 10; |
- ctx.strokeStyle = 'blue'; |
- ctx.stroke(); |
-} |
- |
-void shear() { |
- initTest("Transform"); |
- var rectWidth = width / 4; |
- var rectHeight = height / 8; |
- |
- // shear matrix: |
- // 1 sx 0 |
- // sy 1 0 |
- // 0 0 1 |
- |
- var sx = 0.75; |
- // .75 horizontal shear |
- var sy = 0; |
- // no vertical shear |
- |
- // translate context to center of canvas |
- ctx.translate(width / 2, height / 2); |
- |
- // apply custom transform |
- ctx.transform(1, sy, sx, 1, 0, 0); |
- |
- ctx.fillStyle = 'blue'; |
- ctx.fillRect(-rectWidth / 2, rectHeight / -2, rectWidth, rectHeight); |
-} |
- |
-void composite() { |
- initTest("Composition"); |
- var num = 0; |
- var numPerRow = width ~/ 150; |
- var tempCanvas = new CanvasElement(width: width, height:height); |
- var tempContext = tempCanvas.getContext("2d"); |
- log("Width = $width, numPerRow = $numPerRow\n"); |
- for (var mode in [ 'source-atop', 'source-in', |
- 'source-out', 'source-over', |
- 'destination-atop', 'destination-in', |
- 'destination-out', 'destination-over', |
- 'lighter', 'darker', |
- 'xor', 'copy']) { |
- tempContext.save(); |
- tempContext.clearRect(0, 0, width, height); |
- tempContext.beginPath(); |
- tempContext.rect(0, 0, 55, 55); |
- tempContext.fillStyle = 'blue'; |
- tempContext.fill(); |
- |
- tempContext.globalCompositeOperation = mode; |
- tempContext.beginPath(); |
- tempContext.arc(50, 50, 35, 0, 2 * Math.PI, false); |
- tempContext.fillStyle = 'red'; |
- tempContext.fill(); |
- tempContext.restore(); |
- tempContext.font = '10pt Verdana'; |
- tempContext.fillStyle = 'black'; |
- tempContext.fillText(mode, 0, 100); |
- if (num > 0) { |
- if ((num % numPerRow) == 0) { |
- ctx.translate(-150 * (numPerRow-1), 150); |
- } else { |
- ctx.translate(150, 0); |
- } |
- } |
- ctx.drawImage(tempCanvas, 0, 0); |
- ++num; |
- } |
-} |
- |
-class Rectangle { |
- num x, y, width, height, borderWidth; |
-} |
- |
-var startTime = 0; |
-var myRectangle = null; |
- |
-void anim() { |
- if (myRectangle == null) { |
- myRectangle = new Rectangle(); |
- myRectangle.x = 250; |
- myRectangle.y = 70; |
- myRectangle.width = 100; |
- myRectangle.height = 50; |
- myRectangle.borderWidth = 5; |
- startTime = (new DateTime.now()).millisecondsSinceEpoch; |
- } |
- |
- var now = (new DateTime.now()).millisecondsSinceEpoch; |
- var time = now - startTime; |
- var amplitude = 150; |
- |
- // in ms |
- var period = 2000; |
- var centerX = width / 2 - myRectangle.width / 2; |
- var nextX = amplitude * Math.sin(time * 2 * Math.PI / period) + centerX; |
- myRectangle.x = nextX; |
- |
- // clear |
- ctx.clearRect(0, 0, width, height); |
- |
- // draw |
- |
- ctx.beginPath(); |
- ctx.rect(myRectangle.x, myRectangle.y, myRectangle.width, myRectangle.height); |
- ctx.fillStyle = '#8ED6FF'; |
- ctx.fill(); |
- ctx.lineWidth = myRectangle.borderWidth; |
- ctx.strokeStyle = 'black'; |
- ctx.stroke(); |
-} |
- |
-void linearGradient() { |
- initTest("Linear Gradient"); |
- ctx.rect(0, 0, width, height); |
- var grd = ctx.createLinearGradient(0, 0, width, height); |
- // light blue |
- grd.addColorStop(0, '#8ED6FF'); |
- // dark blue |
- grd.addColorStop(1, '#004CB3'); |
- ctx.fillStyle = grd; |
- ctx.fill(); |
-} |
- |
-void radialGradient() { |
- initTest("Radial Gradient"); |
- ctx.rect(0, 0, width, height); |
- var grd = ctx.createRadialGradient(238, 50, 10, 238, 50, 300); |
- // light blue |
- grd.addColorStop(0, '#8ED6FF'); |
- // dark blue |
- grd.addColorStop(1, '#004CB3'); |
- ctx.fillStyle = grd; |
- ctx.fill(); |
-} |
- |
-int testnum = 0; // Set this to -1 to start with last test. |
- |
-double x, y, z; |
- |
-onAccelerometer(double xx, double yy, double zz) { |
- x = xx; |
- y = yy; |
- z = zz; |
-} |
- |
-void update(num when) { |
- window.requestAnimationFrame(update); |
- if (testnum == 0) { |
- anim(); |
- return; |
- } |
- if (!isDirty) return; |
- switch(testnum) { |
- case 1: |
- helloWorld(); |
- break; |
- case 2: |
- blocks(); |
- break; |
- case 3: |
- squares(); |
- break; |
- case 4: |
- grid(); |
- break; |
- case 5: |
- strokeLines(); |
- break; |
- case 6: |
- lineJoin(); |
- break; |
- case 7: |
- colors(); |
- break; |
- case 8: |
- rotate(); |
- break; |
- case 9: |
- alpha(); |
- break; |
- case 10: |
- scale(); |
- break; |
- case 11: |
- curves(); |
- break; |
- case 12: |
- shadows(); |
- break; |
- case 13: |
- lineJoins(); |
- break; |
- case 14: |
- saveRestore(); |
- break; |
- case 15: |
- mirror(); |
- break; |
- case 16: |
- oval(); |
- break; |
- case 17: |
- lineDash(); |
- break; |
- case 18: |
- loadImage(); |
- break; |
- case 19: |
- clip(); |
- break; |
- case 20: |
- shear(); |
- break; |
- case 21: |
- composite(); |
- break; |
- case 22: |
- smiley(); |
- break; |
- case 23: |
- linearGradient(); |
- break; |
- case 24: |
- radialGradient(); |
- break; |
- case 25: |
- break; // Skip for now; this is really slow. |
- var rayTracer = new RayTracer(); |
- rayTracer.render(defaultScene(), ctx, width, height); |
- break; |
- default: |
- if (testnum < 0) { |
- testnum = 24; |
- } else { |
- testnum = 0; |
- } |
- return; |
- } |
- |
- isDirty = false; |
-} |
- |
-// 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; |
- 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); |
- } |
-} |
- |
-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)})"; |
-} |
- |
-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))); |
- } |
-} |
- |
-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); |
- } |
-} |
- |
-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); |
- } |
- } |
-} |
- |
-class CustomSurface implements Surface { |
- Color diffuseColor, specularColor; |
- int roughness; |
- num reflectPos; |
- CustomSurface(this.diffuseColor, this.specularColor, |
- this.reflectPos, this.roughness); |
- 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); |
- 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.fold(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); |
- } |
- } |
- } |
-} |
- |