Created
May 29, 2012 12:52
-
-
Save shinaisan/2828219 to your computer and use it in GitHub Desktop.
An illustration of a proof of Pythagorean theorem using Processing.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<title>An illustration of a proof of Pythagorean theorem using Processing.js</title> | |
<script type="text/javascript" src="processing.js"></script> | |
</head> | |
<body> | |
<h1>An illustration of a proof of Pythagorean theorem using Processing.js</h1> | |
Drag the point C:<br/> | |
<script type="application/processing" src="pythagorean.processing"></script><canvas></canvas> | |
<br/> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* -*- mode: java; indent-tabs-mode: nil -*- */ | |
// Configuration start | |
int scale = 100; | |
int baseWidth = 200 * scale; | |
int textHeightPixel = 20; | |
// Configuration end | |
int canvasWidth; | |
int canvasHeight; | |
int diagonalLength; | |
int frameCounter = 0; | |
Point pointO; // Center of the canvas | |
Point pointA; | |
Point pointB; | |
Point pointC; | |
Point pointD; | |
Point pointE; | |
Point pointF; | |
Point pointG; | |
Point pointH; | |
Point pointI; | |
Point pointJ; | |
Point pointK; | |
Point pointL; | |
Point pointM; | |
Point pointN; | |
Label[] pointLabels; | |
Circle centerCircle; | |
Square squareA; | |
Square squareB; | |
Square baseSquare; | |
Line lineAB; | |
Line lineIH; | |
Line lineDE; | |
Line lineFG; | |
Line lineCJ; | |
Line lineAI; | |
Line lineBH; | |
Quadrilateral quadAMJC; | |
Quadrilateral quadAILK; | |
Quadrilateral quadBNJC; | |
Quadrilateral quadBHLK; | |
DraggableCircle draggableCircle; | |
Balloon balloon; | |
BrokenLineFunc draggableCircleAlpha; | |
BrokenLineFunc quadAlpha1; | |
BrokenLineFunc quadAlpha2; | |
BrokenLineFunc quadAlpha3; | |
BrokenLineFunc balloonAlpha; | |
void setup() { | |
canvasWidth = 3 * baseWidth; | |
canvasHeight = 3 * baseWidth; | |
frameRate(8); | |
size(canvasWidth / scale, canvasHeight / scale); | |
diagonalLength = sqrt(canvasWidth * canvasWidth + canvasHeight * canvasHeight); | |
pointO = new Point(canvasWidth / 2, canvasHeight / 2); | |
pointA = new Point(pointO.x - baseWidth / 2, pointO.y); | |
pointB = new Point(pointO.x + baseWidth / 2, pointO.y); | |
centerCircle = new Circle(pointO.x, pointO.y, baseWidth / 2); | |
Point circumferencePoint = centerCircle.getNearestPoint(new Point(pointA.x, 0)); | |
draggableCircle = new DraggableCircle(circumferencePoint.x, circumferencePoint.y, 16 * scale); | |
pointC = new Point(draggableCircle.centerX, draggableCircle.centerY); | |
squareA = new Square(pointA, pointC, 1); | |
squareB = new Square(pointB, pointC, -1); | |
baseSquare = new Square(pointA, pointB, -1); | |
pointD = squareA.points[2]; | |
pointE = squareA.points[3]; | |
pointF = squareB.points[2]; | |
pointG = squareB.points[3]; | |
pointH = baseSquare.points[2]; | |
pointI = baseSquare.points[3]; | |
lineAB = new Line(pointA, pointB); | |
lineIH = new Line(pointI, pointH); | |
lineAI = new Line(pointA, pointI); | |
lineBH = new Line(pointB, pointH); | |
lineDE = new Line(pointD, pointE); | |
lineFG = new Line(pointF, pointG); | |
lineCJ = new Line(pointC, null); | |
quadAMJC = new Quadrilateral(pointA, pointM, pointJ, pointC); | |
quadAILK = new Quadrilateral(pointA, pointI, pointL, pointK); | |
quadBNJC = new Quadrilateral(pointB, pointN, pointJ, pointC); | |
quadBHLK = new Quadrilateral(pointB, pointH, pointL, pointK); | |
balloon = new Balloon(0, 0, textHeightPixel * scale, "DRAG ME"); | |
balloon.balloonColor = #ff3344; | |
balloon.messageColor = #ffffff; | |
draggableCircleAlpha = new BrokenLineFunc({0, 1, 2}, {0, 255, 0}); | |
quadAlpha1 = new BrokenLineFunc({0, 1, 2, 3, 4}, {0, 0, 0, 128, 0}); | |
quadAlpha2 = new BrokenLineFunc({0, 1, 2, 3, 4}, {0, 128, 0, 0, 0}); | |
quadAlpha3 = new BrokenLineFunc({0, 1, 2, 3, 4}, {0, 128, 0, 128, 0}); | |
balloonAlpha = null; | |
} | |
Point[] collectPoints() { | |
return ({pointA, pointB, pointC, pointD, pointE, pointF, pointG, pointH, pointI, | |
pointJ, pointK, pointL, pointM, pointN}); | |
} | |
Label[] generatePointLabels(Point[] points, String letters) { | |
if (points == null || points.length > letters.length) { | |
return (null); | |
} | |
Label[] labels = new Label[points.length]; | |
for (int i = 0; i < points.length; i++) { | |
labels[i] = new Label(points[i], textHeightPixel * scale, new String(letters.charAt(i))); | |
} | |
return (labels); | |
} | |
void draw() { | |
Drawable[] drawables; | |
currentTimeSec = millis() / 1000; | |
frameCounter++; | |
update(); | |
background(0); | |
noFill(); | |
strokeWeight(3); | |
stroke(#c0c0c0, 128); | |
drawDrawables({centerCircle, squareA, squareB, baseSquare, lineAI, lineBH, lineDE, lineFG, lineCJ}); | |
strokeWeight(8); | |
stroke(#7cfba0); | |
drawDrawables(collectPoints()); | |
fill(255); | |
drawDrawables(pointLabels); | |
strokeWeight(4); | |
stroke(#abcdef, draggableCircleAlpha.calc(currentTimeSec)); | |
noFill(); | |
draggableCircle.draw(); | |
noStroke(); | |
fill(#00ffff, quadAlpha1.calc(currentTimeSec)); | |
squareA.draw(); | |
fill(#00ffff, quadAlpha2.calc(currentTimeSec)); | |
quadAMJC.draw(); | |
fill(#00ffff, quadAlpha3.calc(currentTimeSec)); | |
quadAILK.draw(); | |
fill(#ffff00, quadAlpha1.calc(currentTimeSec)); | |
squareB.draw(); | |
fill(#ffff00, quadAlpha2.calc(currentTimeSec)); | |
quadBNJC.draw(); | |
fill(#ffff00, quadAlpha3.calc(currentTimeSec)); | |
quadBHLK.draw(); | |
if (balloonAlpha == null) { | |
balloon.alpha = 255; | |
} else if (draggableCircle.isBusy()) { | |
balloon.alpha = 0; | |
} else { | |
balloon.alpha = balloonAlpha.calc(currentTimeSec); | |
} | |
balloon.draw(); | |
} | |
void drawDrawables(Drawable[] drawables) { | |
for (int i = 0; i < drawables.length; i++) { | |
if (drawables[i] != null) { | |
drawables[i].draw(); | |
} | |
} | |
} | |
void update() { | |
int mx = mouseX * scale; | |
int my = mouseY * scale; | |
Point circumferencePoint = centerCircle.getNearestPoint(new Point(mx, my)); | |
draggableCircle.track(circumferencePoint.x, circumferencePoint.y); | |
pointC.moveTo(draggableCircle.centerX, draggableCircle.centerY); | |
balloon.setTailTip(draggableCircle.centerX, draggableCircle.centerY); | |
balloon.moveTo(draggableCircle.centerX + textHeightPixel*scale, draggableCircle.centerY - textHeightPixel*scale); | |
squareA.update(); | |
squareB.update(); | |
baseSquare.update(); | |
pointJ = lineDE.meet(lineFG); | |
lineCJ.setPoints(pointC, pointJ); | |
pointK = lineAB.meet(lineCJ); | |
pointL = lineIH.meet(lineCJ); | |
pointM = lineAI.meet(lineDE); | |
pointN = lineBH.meet(lineFG); | |
quadAMJC.setPoints(pointA, pointM, pointJ, pointC); | |
quadAILK.setPoints(pointA, pointI, pointL, pointK); | |
quadBNJC.setPoints(pointB, pointN, pointJ, pointC); | |
quadBHLK.setPoints(pointB, pointH, pointL, pointK); | |
pointLabels = generatePointLabels(collectPoints(), "ABCDEFGHIJKLMN"); | |
} | |
void mousePressed() { | |
balloonAlpha = new BrokenLineFunc({0, 1, 5, 60}, {0, 255, 0, 0}); | |
draggableCircle.mousePressed(); | |
} | |
void mouseReleased() { | |
draggableCircle.mouseReleased(); | |
} | |
interface Drawable { | |
void draw(); | |
} | |
class Point implements Drawable { | |
int x; | |
int y; | |
Point(int x, int y) { | |
this.x = x; | |
this.y = y; | |
} | |
float distance(Point p) { | |
return (distance(p.x, p.y)); | |
} | |
float distance(int x, int y) { | |
return ((int)sqrt((this.x - x)*(this.x - x) + (this.y - y)*(this.y - y))); | |
} | |
Point moveTo(int x, int y) { | |
this.x = x; | |
this.y = y; | |
return (this); | |
} | |
Point moveTo(Point p) { | |
this.x = p.x; | |
this.y = p.y; | |
return (this); | |
} | |
void draw() { | |
point(this.x / scale, this.y / scale); | |
} | |
} | |
class Circle implements Drawable { | |
int centerX; | |
int centerY; | |
int radius; | |
Circle(int x, int y, int r) { | |
this.centerX = x; | |
this.centerY = y; | |
this.radius = r; | |
} | |
Point getNearestPoint(Point p) { | |
int dx = p.x - this.centerX; | |
int dy = p.y - this.centerY; | |
Point result = new Point(this.centerX, this.centerY); | |
int dist = round(result.distance(p)); | |
if (dist <= 0) { | |
return (result); | |
} | |
int newX = this.centerX + (int)(dx * this.radius / dist); | |
int newY = this.centerY + (int)(dy * this.radius / dist); | |
return (result.moveTo(newX, newY)); | |
} | |
void draw() { | |
ellipse(this.centerX / scale, this.centerY / scale, 2 * this.radius / scale, 2 * this.radius / scale); | |
} | |
} | |
class DraggableCircle extends Circle { | |
boolean dragging; | |
DraggableCircle(int x, int y, int r) { | |
super(x, y, r); | |
this.dragging = false; | |
} | |
void draw() { | |
if (this.isBusy()) { | |
ellipse(this.centerX / scale, this.centerY / scale, 2 * this.radius / scale, 2 * this.radius / scale); | |
} | |
} | |
boolean isBusy() { | |
return (this.isMouseOver() || this.dragging); | |
} | |
boolean isMouseOver() { | |
int mx = mouseX * scale; | |
int my = mouseY * scale; | |
int dx = this.centerX - mx; | |
int dy = this.centerY - my; | |
return (dx * dx + dy * dy <= this.radius * this.radius); | |
} | |
void mousePressed() { | |
this.dragging = isMouseOver(); | |
} | |
void mouseReleased() { | |
this.dragging = false; | |
} | |
void moveTo(int x, int y) { | |
this.centerX = x; | |
this.centerY = y; | |
} | |
void moveTo(Point p) { | |
moveTo(p.x, p.y); | |
} | |
void track(int mx, int my) { | |
if (this.dragging) { | |
moveTo(mx, my); | |
} | |
} | |
} | |
class Balloon implements Drawable { | |
int left; | |
int top; | |
int tailTipX; | |
int tailTipY; | |
String message; | |
PFont font; | |
int messageWidth; | |
int messageHeight; | |
color balloonColor; | |
color messageColor; | |
int alpha; | |
Balloon(int x, int y, int th, String message) { | |
this.left = x; | |
this.top = y; | |
this.tailTipX = x; | |
this.tailTipY = y; | |
this.message = message; | |
this.messageHeight = th; | |
this.font = loadFont(PFont.list()[0], this.messageHeight / scale); | |
textFont(this.font); | |
this.messageWidth = textWidth(this.message) * scale; | |
this.alpha = 255; | |
} | |
void setTailTip(int x, int y) { | |
this.tailTipX = x; | |
this.tailTipY = y; | |
} | |
void moveTo(int x, int y) { | |
this.left = x; | |
this.top = y; | |
} | |
void draw() { | |
if (this.alpha <= 0) { | |
return; | |
} | |
int c = this.messageWidth * 3 / 4; | |
int d = this.messageHeight * 3 / 4; | |
int a = (d + sqrt(d*d + 4*c*c)) / 2; | |
int b = sqrt(a*a - c*c); | |
int cx = this.left + a; | |
int cy = this.top + b; | |
noStroke(); | |
fill(this.balloonColor, this.alpha); | |
ellipse(cx / scale, cy / scale, a*2 / scale, b*2 / scale); | |
triangle(this.tailTipX / scale, this.tailTipY / scale, | |
(cx - c) / scale, cy / scale, | |
(cx) / scale, cy / scale); | |
fill(this.messageColor, this.alpha); | |
textFont(this.font); | |
textAlign(LEFT, BOTTOM); | |
text(this.message, (cx - this.messageWidth / 2) / scale, (cy + this.messageHeight / 2) / scale); | |
} | |
} | |
class Quadrilateral implements Drawable { | |
Point[] points; | |
Quadrilateral(Point p1, Point p2, Point p3, Point p4) { | |
this.points = new Point[4]; | |
setPoints(p1, p2, p3, p4); | |
} | |
void draw() { | |
for (int i = 0; i < this.points.length; i++) { | |
if (this.points[i] == null) { | |
return; | |
} | |
} | |
quad(this.points[0].x / scale, this.points[0].y / scale, | |
this.points[1].x / scale, this.points[1].y / scale, | |
this.points[2].x / scale, this.points[2].y / scale, | |
this.points[3].x / scale, this.points[3].y / scale); | |
} | |
void setPoints(Point p1, Point p2, Point p3, Point p4) { | |
this.points[0] = p1; | |
this.points[1] = p2; | |
this.points[2] = p3; | |
this.points[3] = p4; | |
} | |
} | |
class Square extends Quadrilateral { | |
int direction; | |
Square(Point p1, Point p2, int direction) { | |
super(p1, p2, null, null); | |
this.direction = direction; | |
this.points[2] = new Point(0, 0); | |
this.points[3] = new Point(0, 0); | |
this.update(); | |
} | |
void update() { | |
int ox = this.points[0].x; | |
int oy = this.points[0].y; | |
int dx = this.points[1].x - ox; | |
int dy = this.points[1].y - oy; | |
int d = (this.direction < 0 ? -1 : 1); | |
int x2, y2, x3, y3; | |
x3 = ox + d * dy; | |
y3 = oy - d * dx; | |
x2 = x3 + dx; | |
y2 = y3 + dy; | |
this.points[2].x = x2; | |
this.points[2].y = y2; | |
this.points[3].x = x3; | |
this.points[3].y = y3; | |
} | |
} | |
class Line implements Drawable { | |
Point[] points; | |
Line(Point p1, Point p2) { | |
this.points = new Point[2]; | |
setPoints(p1, p2); | |
} | |
void setPoints(Point p1, Point p2) { | |
this.points[0] = p1; | |
this.points[1] = p2; | |
} | |
void draw() { | |
if (this.points[0] == null || this.points[1] == null) { | |
return; | |
} | |
float d = points[1].distance(points[0]); | |
int dx = points[1].x - points[0].x; | |
int dy = points[1].y - points[0].y; | |
int x1 = points[0].x + (dx * (float)diagonalLength / d); | |
int x2 = points[0].x - (dx * (float)diagonalLength / d); | |
int y1 = points[0].y + (dy * (float)diagonalLength / d); | |
int y2 = points[0].y - (dy * (float)diagonalLength / d); | |
line(x1 / scale, y1 / scale, x2 / scale, y2 / scale); | |
} | |
Point meet(Line line) { | |
if (line == null) { | |
return (null); | |
} | |
if (this.points[0] == null || this.points[1] == null) { | |
return (null); | |
} | |
if (line.points[0] == null || line.points[1] == null) { | |
return (null); | |
} | |
int a11 = this.points[0].x - this.points[1].x; | |
int a12 = line.points[0].x - line.points[1].x; | |
int a21 = this.points[0].y - this.points[1].y; | |
int a22 = line.points[0].y - line.points[1].y; | |
int b1 = line.points[1].x - this.points[1].x; | |
int b2 = line.points[1].y - this.points[1].y; | |
int det = a11*a22 - a12*a21; | |
if (det == 0) { | |
return (null); | |
} | |
float s = (float)(b1*a22 - b2*a12) / det; | |
float t = (float)(a11*b2 - a12*b1) / det; | |
int x = round(s * this.points[0].x + (1 - s) * this.points[1].x); | |
int y = round(s * this.points[0].y + (1 - s) * this.points[1].y); | |
return (new Point(x, y)); | |
} | |
} | |
class Label implements Drawable { | |
String caption; | |
Point point; | |
int textHeight; | |
PFont font; | |
Label(Point point, int th, String caption) { | |
this.point = point; | |
this.textHeight = th; | |
this.caption = caption; | |
this.font = loadFont(PFont.list()[0], th / scale); | |
textFont(this.font); | |
} | |
void draw() { | |
if (this.point == null) { | |
return; | |
} | |
int margin = 4; | |
textFont(this.font); | |
textAlign(LEFT, BOTTOM); | |
text(this.caption, this.point.x / scale + margin, (this.point.y + this.textHeight) / scale + margin); | |
} | |
} | |
class BrokenLineFunc { | |
int[] times; | |
int[] values; | |
int invalidValue; | |
boolean repeat; | |
int maxTime; | |
BrokenLineFunc(int[] times, int[] values) { | |
this.maxTime = 1; | |
if ((times.length != values.length) || (times.length <= 1)) { | |
times = null; | |
values = null; | |
} | |
for (int i = 1; i < times.length; i++) { // Ensure that times is sorted. | |
if (times[i] < times[i - 1]) { | |
times = null; | |
values = null; | |
break; | |
} | |
} | |
this.times = times; | |
this.values = values; | |
this.maxTime = times[times.length - 1]; | |
this.invalidValue = 0; | |
this.repeat = true; | |
} | |
int calc(int t) { | |
if (this.times == null || this.values == null || this.maxTime == 0) { | |
return (this.invalidValue); | |
} | |
if (this.repeat) { | |
t %= this.maxTime; | |
} | |
for (int i = 0; i < this.times.length - 1; i++) { | |
if (this.times[i] <= t && t < this.times[i + 1]) { | |
float slope = (this.values[i + 1] - this.values[i]) / (this.times[i + 1] - this.times[i]); | |
return (this.values[i] + round(slope * (t - this.times[i]))); | |
} | |
} | |
return (this.invalidValue); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment