-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcubeanim.js
More file actions
executable file
·340 lines (281 loc) · 12.1 KB
/
Copy pathcubeanim.js
File metadata and controls
executable file
·340 lines (281 loc) · 12.1 KB
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
/******************************************************************************
SIMPLE 3D CUBE SHIFT ANIMATION
I constructed this animation to play with HTML5's new canvas API and for the
intellectual challenge of realistically rendering a 3-dimensional model onto a
2-dimensional canvas. I would of course use a real game engine or graphics API
(like WebGL) into make any 3D games or animations worth releasing into the wild.
NOTE:
All 3D geometric code assumes the camera is located at cartesian coordinate
(0, 0, 0) and is directed in vector [0, 0, 1] in non-camera-relative space. In
all math/trig below, any points on the line (0, 0, z) are intended to be in the
very center of the viewable area (canvas). This schema doesn't align with how
the canvas expects geometric input -- we translate all points to be consistent
with the canvas API in the cavisifyPt() function.
Author: Skye Isard
******************************************************************************/
var ARROW_LEFT_KEY_CODE = 37,
ARROW_UP_KEY_CODE = 38,
ARROW_RIGHT_KEY_CODE = 39,
ARROW_DOWN_KEY_CODE = 40,
SPACEBAR_KEY_CODE = 32;
var timerIds = [],
hasStarted = false,
paused = false,
viewRange = {
lr_angle: Math.PI / 2,
ud_angle: Math.PI / 2
},
cube,
canv,
g;
/********************************************************************
* Represents a 3D cartesian point that is NOT RELATIVE to our camera
********************************************************************/
function CartesianPt3D(arr) {
this.x = arr[0];
this.y = arr[1];
this.z = arr[2];
}
/********************************************************
* Represents a point in the 2D view of our camera/canvas
********************************************************/
function CanvasPt2D(x, y) {
this.x = x;
this.y = y;
}
function toDegrees(radians) {
return 360 * radians / (2 * Math.PI);
}
function toRadians(degrees) {
return 2 * Math.PI * (degrees / 360);
}
function updateMetricsDisplay() {
var html = "<label>Canvas Width: " + canv.width.toFixed(1) + "; Canvas Height: " + canv.height.toFixed(1);
if (paused) {
var cubeTopLeft = cube.points[0];
var lr_angle = toDegrees(Math.atan(cubeTopLeft.x / cubeTopLeft.z));
var ud_angle = toDegrees(Math.atan(cubeTopLeft.y / cubeTopLeft.z));
html += "; Cube Position: ("
html += (cubeTopLeft.x.toFixed(2) + ", " + cubeTopLeft.y.toFixed(2) + ", " + cubeTopLeft.z.toFixed(2) + ", lrangle: " + lr_angle.toFixed(1) +
", udangle: " + ud_angle.toFixed(1) + ")");
}
html += "</label>";
$("#metrics").html(html);
}
function handleKeydown(keyEvent) {
if (keyEvent.which == SPACEBAR_KEY_CODE) {
keyEvent.preventDefault();
$("#pause").click();
}
else if (paused) {
var moveDrawUpdate = function (keyEvent, xshift, yshift, zshift) {
keyEvent.preventDefault();
cube.move(xshift, yshift, zshift);
cube.draw();
updateMetricsDisplay();
}
if (keyEvent.shiftKey) {
switch (keyEvent.which) {
case ARROW_UP_KEY_CODE:
moveDrawUpdate(keyEvent, 0, 0, 5);
break;
case ARROW_DOWN_KEY_CODE:
moveDrawUpdate(keyEvent, 0, 0, -5);
break;
default:
// NO-OP
}
} else {
switch (keyEvent.which) {
case ARROW_UP_KEY_CODE:
moveDrawUpdate(keyEvent, 0, 5, 0);
break;
case ARROW_DOWN_KEY_CODE:
moveDrawUpdate(keyEvent, 0, -5, 0);
break;
case ARROW_LEFT_KEY_CODE:
moveDrawUpdate(keyEvent, -5, 0, 0);
break;
case ARROW_RIGHT_KEY_CODE:
moveDrawUpdate(keyEvent, 5, 0, 0);
break;
default:
// NO-OP
}
}
}
}
/*******************************************************************
* Waits until helper scripts are loaded and then runs the animation
*******************************************************************/
function startAnimation() {
if (typeof $ !== "undefined" && typeof _ !== "undefined") {
var screenDim = Math.min($(window).width(), $(window).height());
// $("body").attr("width", screenDim).attr("height", screenDim);
$("#acanvas-div").attr("width", screenDim - 1).attr("height", screenDim - 1);
$("#acanvas").attr("width", screenDim - 2).attr("height", screenDim - 2);
if (!hasStarted) {
var restartAnimation = _.throttle(startAnimation, 1500, {
leading: false
});
$(window).resize(function () {
_.each(timerIds, clearInterval);
// Apparently the idiomatic way to clear an array in JS, lol.
timerIds.length = 0;
restartAnimation();
});
$("#pause").click(function () {
paused = !paused;
if (paused) {
$(this).text("Resume");
$("body").append("<div id='key-message'><label>Use <span>Arrow " +
"Keys</span> and <span>Shift</span> to move " +
"cube.</label></div>");
}
else {
$(this).text("Pause");
$("#key-message").remove();
}
updateMetricsDisplay();
});
$(document).keydown(handleKeydown);
hasStarted = true;
}
runAnimation();
updateMetricsDisplay();
} else {
setInterval(startAnimation, 100);
}
}
/*********************************************************************
* Performs all the setup and scheduling associated with the animation
*********************************************************************/
function runAnimation() {
canv = $("#acanvas").get(0);
g = canv.getContext("2d");
/**************************************************************************
* Do our 3D modeling ****************************************************
**************************************************************************/
/*
* A cube shape that is meant to move across the screen.
*/
var width = 25,
height = 25,
length = 25;
var cubeDepth = 100;
var leftMotionBound = -1 * Math.tan(viewRange.lr_angle / 2) * cubeDepth,
topMotionBound = Math.tan(viewRange.ud_angle / 2) * cubeDepth,
rightMotionBound = -1 * leftMotionBound,
bottomMotionBound = -1 * topMotionBound;
var p1 = new CartesianPt3D([leftMotionBound, topMotionBound, cubeDepth]),
p2 = new CartesianPt3D([leftMotionBound + width, topMotionBound, cubeDepth]),
p3 = new CartesianPt3D([leftMotionBound + width, topMotionBound - height, cubeDepth]),
p4 = new CartesianPt3D([leftMotionBound, topMotionBound - height, cubeDepth]),
p5 = new CartesianPt3D([leftMotionBound, topMotionBound, cubeDepth + length]),
p6 = new CartesianPt3D([leftMotionBound + width, topMotionBound, cubeDepth + length]),
p7 = new CartesianPt3D([leftMotionBound + width, topMotionBound - height, cubeDepth + length]),
p8 = new CartesianPt3D([leftMotionBound, topMotionBound - height, cubeDepth + length]);
cube = {
points: [p1, p2, p3, p4, p5, p6, p7, p8],
motionVector: {
x: 1,
y: -4
},
move: function () {
var moveArgs = arguments;
if (moveArgs.length == 3) {
_.each(this.points, function (point) {
point.x += moveArgs[0];
point.y += moveArgs[1];
point.z += moveArgs[2];
});
return;
}
var vect = this.motionVector;
if (p1.x + vect.x > leftMotionBound && p2.x + vect.x < rightMotionBound) {
_.each(this.points, function (point) {
point.x = point.x + vect.x;
});
} else {
vect.x = -1 * vect.x;
if (p1.y + vect.y < topMotionBound && p3.y + vect.y > bottomMotionBound) {
_.each(this.points, function (point) {
point.y = point.y + vect.y;
});
} else {
vect.y = -1 * vect.y;
}
}
},
draw: function () {
var cps = _.map(this.points, canvisifyPt);
g.strokeStyle = "black";
var drawFace = function (cp1, cp2, cp3, cp4, color) {
g.fillStyle = color;
g.beginPath();
g.moveTo(cp1.x, cp1.y);
g.lineTo(cp2.x, cp2.y);
g.lineTo(cp3.x, cp3.y);
g.lineTo(cp4.x, cp4.y);
g.closePath();
g.stroke();
g.fill();
}
/*
* NOTE: Did I mention this code is not intended to EVER be part of a 3D graphics
* engine? Everything about this is not generic...
*/
g.clearRect(0, 0, canv.width, canv.height);
if (this.points[0].z > 0) {
// Draw the front face
drawFace(cps[0], cps[1], cps[2], cps[3], "red");
// Draw the left face if appropriate
if (cps[0].x > canv.width / 2) {
drawFace(cps[0], cps[3], cps[7], cps[4], "blue");
}
// Draw the right face if appropriate
if (cps[1].x < canv.width / 2) {
drawFace(cps[1], cps[2], cps[6], cps[5], "green");
}
// Draw the top face if appropriate
if (cps[0].y > canv.height / 2) {
drawFace(cps[0], cps[1], cps[5], cps[4], "purple");
}
// Draw the bottom face if appropriate
if (cps[3].y < canv.height / 2) {
drawFace(cps[2], cps[3], cps[7], cps[6], "yellow");
}
}
}
};
/**************************************************************************
* Define our 2D rendering functions **************************************
**************************************************************************/
/*
* Converts a 3D cartesian modeling point to a drawable 2D canvas point.
*/
function canvisifyPt(cartesianPt) {
var polar_lr = Math.atan(cartesianPt.x / cartesianPt.z);
var polar_ud = Math.atan(cartesianPt.y / cartesianPt.z);
// These locations are calculated assuming (0, 0) is in the center of the 2D
// field of vision.
var d2x = polar_lr * canv.width / viewRange.lr_angle;
var d2y = polar_ud * canv.height / viewRange.ud_angle;
// Translate these locations so they match the expectation of the canvis API. In other
// words, assume (0, 0) is in the upper-left corner of the canvas.
d2x = (canv.width / 2) + d2x;
d2y = (canv.height / 2) - d2y;
return new CanvasPt2D(d2x, d2y);
}
/**************************************************************************
* Animation Setup ********************************************************
**************************************************************************/
cube.draw();
timerIds.push(setInterval(function () {
if (!paused) {
cube.move();
cube.draw();
}
}, 5));
}
$(document).ready(startAnimation);