-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpenrose-triangle.js
223 lines (188 loc) · 6.68 KB
/
penrose-triangle.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
'use strict';
class PenroseTrinagle {
constructor(canvas, {
triangleEdge = 300,
cubeEdge = 30,
cubesPerTriangleEdge = 6,
padding = [ 50.5, 0.5 ],
loopFrames = 100,
lineWidth = 3,
lineColor = '#0041a3',
cubeColors = [ '#4f9bf7', '#c0d8fc', '#87b7ff' ]
} = {}) {
// set options
this.triangleEdge = triangleEdge;
this.cubeEdge = cubeEdge;
this.cubesPerTriangleEdge = cubesPerTriangleEdge;
this.padding = [ padding[0], padding[1] ];
this.loopFrames = loopFrames;
this.lineWidth = lineWidth;
this.lineColor = lineColor;
this.cubeColors = [ cubeColors[0], cubeColors[1], cubeColors[2] ];
// prepare graphics context
this.canvas = canvas;
this.context = this.canvas.getContext('2d');
this.context.lineJoin = 'round';
this.context.lineWidth = this.lineWidth;
this.context.strokeStyle = this.lineColor;
// precalculate lengths and cube coordinates
this.triangleHeight = this.triangleEdge * Math.sqrt(3) / 2;
this.ch = this.cubeEdge * Math.sqrt(3) / 2;
this.chb = this.cubeEdge / 2;
this.calculateCubesCoords();
// start at frame 0
this.frame = 0;
}
// calcultes coordinates for the cubes with the established parameters
calculateCubesCoords() {
// triangle vertices
let va = [ // bottom-left
this.padding[0],
this.triangleEdge + this.padding[1]
];
let vb = [ // bottom-right
this.triangleEdge + this.padding[0],
this.triangleEdge + this.padding[1]
];
let vc = [ // top
this.triangleEdge / 2.0 + this.padding[0],
this.triangleEdge - this.triangleHeight + this.padding[1]
];
let minc = this.cubesPerTriangleEdge * this.loopFrames;
this.finc = this.triangleEdge / minc; // length increment for a frame
this.vinc1 = [ // increment vector along the right edge
(vc[0] - vb[0]) / minc,
(vc[1] - vb[1]) / minc
];
this.vinc2 = [ // vector increment along the left edge
(va[0] - vc[0]) / minc,
(va[1] - vc[1]) / minc
];
// cubes' coordinates
this.cubeMid = ((this.cubesPerTriangleEdge - 1) / 2) | 0; // the 1st cube to draw
let inc = this.triangleEdge / this.cubesPerTriangleEdge; // separation between cubes
this.v = new Float64Array(6 * this.cubesPerTriangleEdge); // coordinates array
this.vt = new Float64Array(6 * this.cubesPerTriangleEdge); // coords array for render
let j = 0;
for (let i = this.cubeMid; i < this.cubesPerTriangleEdge; ++i) { // bottom-right
this.v[j++] = va[0] + inc * i;
this.v[j++] = va[1];
}
let vdir = [ // right edge Euclidean vector
(vc[0] - vb[0]) / this.cubesPerTriangleEdge,
(vc[1] - vb[1]) / this.cubesPerTriangleEdge
];
for (let i = 0; i < this.cubesPerTriangleEdge; ++i) { // right edge
this.v[j++] = vb[0] + vdir[0] * i;
this.v[j++] = vb[1] + vdir[1] * i;
}
vdir = [ // left edge vector
(va[0] - vc[0]) / this.cubesPerTriangleEdge,
(va[1] - vc[1]) / this.cubesPerTriangleEdge
];
for (let i = 0; i < this.cubesPerTriangleEdge; ++i) { // left edge
this.v[j++] = vc[0] + vdir[0] * i;
this.v[j++] = vc[1] + vdir[1] * i;
}
for (let i = 0; i < this.cubeMid; ++i) { // bottom-left
this.v[j++] = va[0] + inc * i;
this.v[j++] = va[1];
}
}
render() {
// calculate cube positions
this.updateCubesPositions();
// clear canvas and draw
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.drawTriangle();
// increment current frame
if (++this.frame == this.loopFrames) this.frame = 0;
}
// 'renderLoop(timestamp)' is invoked at every repaint
renderLoop = timestamp => {
this.render();
requestAnimationFrame(this.renderLoop);
}
// call 'start()' to begin the animation
start() {
requestAnimationFrame(this.renderLoop);
}
// calculate cubes' positions for the current frame
updateCubesPositions() {
// length increments for current frame
let inc = this.finc * this.frame;
let inc1X = this.vinc1[0] * this.frame;
let inc1Y = this.vinc1[1] * this.frame;
let inc2X = this.vinc2[0] * this.frame;
let inc2Y = this.vinc2[1] * this.frame;
let j = 0;
for (let i = this.cubeMid; i < this.cubesPerTriangleEdge; ++i) { // bottom-right
this.vt[j] = this.v[j++] + inc;
this.vt[j] = this.v[j++];
}
for (let i = 0; i < this.cubesPerTriangleEdge; ++i) { // right edge
this.vt[j] = this.v[j++] + inc1X;
this.vt[j] = this.v[j++] + inc1Y;
}
for (let i = 0; i < this.cubesPerTriangleEdge; ++i) { // left edge
this.vt[j] = this.v[j++] + inc2X;
this.vt[j] = this.v[j++] + inc2Y;
}
for (let i = 0; i < this.cubeMid; ++i) { // bottom-left
this.vt[j] = this.v[j++] + inc;
this.vt[j] = this.v[j++];
}
}
// draw face 0
drawCubePart1(x, y) {
this.drawCubeSide(
x, y,
x + this.chb, y - this.ch,
x + this.cubeEdge, y,
x + this.chb, y + this.ch,
this.cubeColors[0]
);
}
// draw faces 1 and 2
drawCubePart2(x, y) {
this.drawCubeSide(
x, y,
x - this.cubeEdge, y,
x - this.chb, y - this.ch,
x + this.chb, y - this.ch,
this.cubeColors[1]
);
this.drawCubeSide(
x, y,
x + this.chb, y + this.ch,
x - this.chb, y + this.ch,
x - this.cubeEdge, y,
this.cubeColors[2]
);
}
drawCubeSide(x0, y0, x1, y1, x2, y2, x3, y3, color) {
this.context.beginPath();
this.context.moveTo(x0, y0);
this.context.lineTo(x1, y1);
this.context.lineTo(x2, y2);
this.context.lineTo(x3, y3);
this.context.closePath();
this.context.fillStyle = color;
this.context.stroke();
this.context.fill();
}
// draw the whole cube, centered at (x, y)
drawCube(x, y) {
this.drawCubePart1(x, y);
this.drawCubePart2(x, y);
}
// draw the triangle
drawTriangle() {
this.drawCubePart1(this.vt[0], this.vt[1]);
let j = 2;
while (j < this.vt.length) {
this.drawCube(this.vt[j++], this.vt[j++]);
}
this.drawCubePart2(this.vt[0], this.vt[1]);
}
}