Here are the things I know:
(1) The ship object is getting rendered to some degree, because I put an extra circle in its render code and that's being drawn.
(2) There's nothing wrong (at least, nothing obvious to me) with my rotation methods, as the numbers show up correctly in the console. And the points top.x
, top.y
, bottomLeft.x
, bottomLeft.y
, bottomRight.x
, and bottomRight.y
are all the numbers they should be.
(3) The actual code using the variables mentioned in (2) to draw the ship is working OK. When I replace the variables by hard-coded numbers, the ship is drawn. So what gives?
The faulty code is in the Ship object at line 140 and you can test it yourself here: http://noetherherenorthere.com/practice/landscape.html.
1 /* landscape.js */
2
3 var canvas;
4 var context;
5 var landscape;
6 var ship;
7
8 function init(){
9
10 canvas = document.getElementById('canvas');
11 context = canvas.getContext('2d');
12 landscape = new Landscape();
13 ship = new Ship(375, 400);
14 animate();
15
16 window.addEventListener('keydown', function(e){
17 switch(e.keyCode){
18 case 37: // left
19 ship.theta -= 0.1;
20 ship.theta %= 2*Math.PI;
21 break;
22 case 38: // up
23 ship.v_x += 1*Math.cos(ship.theta);
24 ship.v_y -= 1*Math.sin(ship.theta);
25 break;
26 case 39: // right
27 ship.theta += 0.1;
28 ship.theta %= 2*Math.PI;
29 break;
30 case 40: // down
31 // do nothing
32 break;
33 default:
34 }
35 });
36
37 }
38
39 function animate(){
40
41 if(this.i == null){
42 this.i = 0;
43 } else{
44 this.i = (this.i + 0.1)%628; // switch it out after 100*2*PI cycles so i doesn't get too big.
45 }
46
47 landscape.sun.y += 10*Math.sin(this.i);
48 landscape.moon.y -= 10*Math.cos(this.i);
49 ship.v = Math.sqrt(Math.pow(ship.v_x, 2) + Math.pow(ship.v_y, 2));
50 ship.x += (0.1 * ship.v_x);
51 ship.y += (0.1 * ship.v_y);
52 if(ship.x < 0){
53 ship.x = canvas.width;
54 }
55 if(ship.x > canvas.width){
56 ship.x = 0;
57 }
58 if(ship.y < 0){
59 ship.y = canvas.height;
60 }
61 if(ship.y > canvas.height){
62 ship.y = 0;
63 }
64 if(ship.v > ship.vMax){
65 ship.v = ship.vMax;
66 }
67 if(ship.v < -ship.vMax){
68 ship.v = -ship.vMax;
69 }
70
71 context.clearRect(0, 0, canvas.width, canvas.height);
72
73 landscape.render();
74
75 ship.render();
76
77 // draw the dialog box
78 context.font = "14px Verdana";
79 context.fillStyle = 'DodgerBlue';
80 context.fillText("Velocity: (x: " + this.ship.v_x.toFixed(2) + ", y: " + this.ship.v_y.toFixed(2) +
81 ", angle: " + radToDeg(this.ship.theta).toFixed(2) + ")", 420, 465);
82 context.fillText("Position: (x: " + this.ship.x.toFixed(2) + ", y: " + this.ship.y.toFixed(2) + ")", 420, 485);
83
84 window.setTimeout(animate, 40);
85
86 }
87
88 function radToDeg(radians){
89
90 return radians*(180/Math.PI);
91
92 }
93
94 function degToRad(degrees){
95
96 return degrees*(Math.PI/180);
97
98 }
99
100 function Landscape(){
101
102 // order of the elements matters here
103
104 this.sky = new Sky();
105 this.sun = new Sun(600, 150, 50);
106 this.moon = new Moon(100, 100, 50);
107
108 this.render = function(){
109
110 for(var element in this){
111 if(this[element].hasOwnProperty("render")){
112 this[element].render();
113 }
114 }
115
116 }
117 }
118
119 function Ship(x, y){
120
121 this.x = x;
122 this.y = y;
123 this.r = 10;
124 this.v = 0; // initial velocity of zero
125 this.v_x = 0;
126 this.v_y = 0;
127 this.theta = Math.PI/2; // starts out pointing upwards
128 this.vMax = 200;
129 this.render = function(){
130 var top = new Vector(this.x, this.y);
131 var bottomLeft = new Vector(this.x - 10, this.y + 30);
132 var bottomRight = new Vector(this.x + 10, this.y + 30);
133
134 top.rotate(this.theta, true);
135 bottomLeft.rotate(this.theta, true);
136 bottomRight.rotate(this.theta, true);
137
138 // console.log("top: " + top + ", bottomLeft: " + bottomLeft + ", bottomRight: " + bottomRight);
139
140 context.fillStyle = 'SlateGray';
141 context.beginPath();
142 context.moveTo(top.x, top.y);
143 context.lineTo(bottomLeft.x, bottomLeft.y);
144 context.lineTo(bottomRight.x, bottomRight.y);
145 context.closePath();
146 context.fill();
147
148 // this code works even though the code above doesn't.
149 // context.fillStyle = 'SlateGray';
150 // context.beginPath();
151 // context.moveTo(this.x, this.y);
152 // context.lineTo(this.x - 10, this.y + 30);
153 // context.lineTo(this.x + 10, this.y + 30);
154 // context.closePath();
155 // context.fill();
156
157 context.fillStyle = 'LightGreen';
158 context.beginPath();
159 context.arc(200, 200, 30, 0, 2*Math.PI);
160 context.closePath();
161 context.fill();
162 }
163
164 function Vector(x, y){
165
166 this.x = x;
167 this.y = y;
168 this.rotate = function(theta, round){
169
170 var rotationMatrix = new Matrix(2, 2, Math.cos(theta), -Math.sin(theta), Math.sin(theta), Math.cos(theta));
171 var vector = new Matrix(2, 1, this.x, this.y);
172 var resultVector = Matrix.multiply(rotationMatrix, vector);
173 this.x = resultVector[0][0];
174 this.y = resultVector[1][0];
175 if(round){
176 this.x = Math.floor(this.x);
177 this.y = Math.floor(this.y);
178 }
179
180 return this;
181
182 }
183
184 this.toString = function(){
185
186 return "x: " + x + ", y: " + y;
187
188 }
189
190 }
191
192 function Matrix(rows, cols /*, var args */){
193
194 // constructor
195
196 if(rows == null || cols == null){
197 throw new Error("null rows or cols argument");
198 } else if(!isPositiveInteger(rows) || !isPositiveInteger(cols)){
199 throw new Error("rows and cols must be whole numbers");
200 } else if(rows > 1000 || cols > 1000){
201 throw new Error("rows and cols must be < 1000 in size");
202 }
203
204 this.numRows = rows;
205 this.numCols = cols;
206 if(arguments.length - 2 > rows*cols){
207 throw new Error("too many arguments to Matrix constructor");
208 } else if(arguments.length > 2 && arguments.length - 2 < rows*cols){
209 throw new Error("too few arguments to Matrix constructor for initializing Matrix." +
210 " Usage: rows, cols [, row-major list of row and col entries]");
211 }
212
213 if(rows === undefined){
214 console.log(arguments);
215 }
216
217 for(var a = 0; a < rows; a++){
218 this[a] = new Array();
219 }
220
221 for(var a = 2; a < arguments.length; a++){
222 try{
223 var row = Math.floor((a - 2)/rows);
224 var col = Math.floor((a - 2)%rows);
225 this[row][col] = arguments[a];
226 } catch(e){
227 console.log(row + ", " + col);
228 throw e;
229 }
230 }
231
232 }
233
234 Matrix.multiply = function(matrixA, matrixB){
235
236 if(matrixA.numCols != matrixB.numRows){
237 throw new Error("# of cols in first matrix must equal # of rows in second matrix");
238 }
239
240 var resultMatrix = new Matrix(matrixA.numRows, matrixB.numCols);
241 var sum, i, j, k;
242
243 for(i = 0; i < matrixA.numRows; i++){
244 for(j = 0; j < matrixB.numCols; j++){
245 sum = 0;
246 for(k = 0; k < matrixA.numCols; k++){
247 sum += matrixA[i][k] * matrixB[k][j];
248 }
249 resultMatrix[i][j] = sum;
250 }
251 }
252
253 return resultMatrix;
254 }
255
256 function isPositiveInteger(n){
257
258 if(n == null){ return false; }
259 if(typeof n != "number"){ return false; }
260 if(!isFinite(n)){ return false; }
261 if(n <= 0){ return false; }
262 if(n%1 !== 0){ return false; }
263 return true;
264
265 }
266
267 }
268
269 function Sky(){
270
271 this.x = 0;
272 this.y = 0;
273 this.width = canvas.width;
274 this.height = canvas.height;
275 this.render = function(){
276 context.fillStyle = 'Black'; // previously Indigo
277 context.fillRect(this.x, this.y, this.width, this.height);
278 }
279
280 }
281
282 function Sun(x, y, radius){
283
284 this.x = x;
285 this.y = y;
286 this.r = radius;
287 this.render = function(){
288 context.fillStyle = 'Gold';
289 context.beginPath();
290 context.arc(this.x, this.y, this.r, 0, 2*Math.PI);
291 context.closePath();
292 context.fill();
293 }
294
295 }
296
297 function Moon(x, y, radius){
298
299 this.x = x;
300 this.y = y;
301 this.r = radius;
302 this.render = function(){
303 context.fillStyle = 'LightYellow';
304 context.beginPath();
305 context.arc(this.x, this.y, this.r, 0, 2*Math.PI);
306 context.closePath();
307 context.fill();
308 }
309
310 }