Skip to content

Instantly share code, notes, and snippets.

@shinaisan
Created May 29, 2012 12:52
Show Gist options
  • Save shinaisan/2828219 to your computer and use it in GitHub Desktop.
Save shinaisan/2828219 to your computer and use it in GitHub Desktop.
An illustration of a proof of Pythagorean theorem using Processing.js
<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>
/* -*- 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