前のプログラムからの変更点について:

左の画像

右の画像
「物体ごとに色分けするプログラム」から「視点を変更できるプログラム」へ変更しています。
このページを読んでいる皆さんの目でたとえると、皆さんの頭が右へ動くとき、景色のすべての物体は左へ動いているように見えます。
その理屈を利用して、視点が動くことを表現するために、すべての物体を逆方向へ動かすようにしてプログラムしています。

視点のx,y,z座標をそれぞれ単独で変更しても良いのですが、ちょっと一歩進めて視点をドーム状に沿って動かすプログラムにしてあります。
そういう「一歩進めてある」というのが難しく感じる時は、camDomeKaiten()を呼んでいる行を削除してカメラの座標を単純に変更してください。たとえば、cam.posX = 10; のようにします。
(たとえばこのように変更します。変更例

プログラム中の赤マーカーの部分は、視点の移動に対応するための変更点を示しています。
Skin:
[NORMAL]
[BLUE] [DOS] [LIGHT]  / コピーするための表示 / 実行
比較対象からの変更点をマーキング
このファイル: canvas - 7-1(視点の移動と回転).html
比較対象: canvas - 6-2(複数).html

1 <html>
2 <head>
3 <meta content="text/html; charset=UTF-8" http-equiv="content-type">
4 <title>untitled canvas</title>
5 <script>
6 console.log( "=============== script ==============" );
7 /*
8 『原理を使用した3DCG - 基本プログラム』
9
10 実行すると、視点を回転した景色が描かれる。
11
12
13 3DCG仕様:
14 Z軸は奥へ行くほど増加し、手前に来るほど減少する。
15
16
17
18 */
19 function $( id ) { return document.getElementById( id ); }
20 var HereDocument = /\/\*\s*([^]*?)\s*\*\//;
21 var canvasEL, canvas;
22
23 function onloadx() {
24 canvasEL = $( "canvasELID" );
25 canvas = canvasEL.getContext( '2d' );
26 screenW = canvasEL.width;
27 screenH = canvasEL.height;
28
29
30
31 //マスターモデル
32 masters = new Object();
33 masters.cube = new Object();
34
35 //正六面体の頂点(中心を原点としたときの位置)
36 masters.cube.tens = [
37 { x : -1, y : -1, z : -1 }, //0: 左下 手前
38 { x : +1, y : -1, z : -1 }, //1: 右下 手前
39 { x : +1, y : +1, z : -1 }, //2: 右上 手前
40 { x : -1, y : +1, z : -1 }, //3: 左上 手前
41
42 { x : -1, y : -1, z : +1 }, //4: 左下 奥
43 { x : +1, y : -1, z : +1 }, //5: 右下 奥
44 { x : +1, y : +1, z : +1 }, //6: 右上 奥
45 { x : -1, y : +1, z : +1 }, //7: 左上 奥
46 ];
47
48 masters.cube.mens = [
49 [ 3, 2, 1, 0 ], //向かって正面
50 [ 1, 2, 6, 5 ], //向かって右面
51 [ 0, 4, 7, 3 ], //向かって左面
52 [ 7, 6, 2, 3 ], //向かって上面
53 [ 0, 1, 5, 4 ], //向かって下面
54 [ 5, 6, 7, 4 ], //向かって背面
55 ]; //※各面の点の並びは時計回り
56
57 //正六面体の拡大
58 masters.cube.scale = 10;
59
60 //正六面体の位置
61 masters.cube.posX = 0;
62 masters.cube.posY = 0;
63 masters.cube.posZ = 80;
64
65 //正六面体の回転
66 masters.cube.kaitenH = 3.14 / 8;
67 masters.cube.kaitenV = 0;
68
69 //正六面体のベース色
70 masters.cube.baseColor = { r : 196, g : 196, b : 0 };
71
72 //リンクモデル
73 models = new Array();
74 var makeLink = function( master ) {
75 var model = new Object();
76 model.tens = master.tens; //参照
77 model.mens = master.mens; //参照
78 model.scale = master.scale; //コピー
79 model.posX = master.posX; //コピー
80 model.posY = master.posY; //コピー
81 model.posZ = master.posZ; //コピー
82 model.kaitenH = master.kaitenH; //コピー
83 model.kaitenV = master.kaitenV; //コピー
84 model.baseColor = objCpy( master.baseColor ); //コピー
85 return model;
86 };
87
88 //リンク1 赤
89 var model = makeLink( masters.cube );
90 model.posX = -15;
91 model.posZ -= 1;
92 model.baseColor = { r : 196, g : 48, b : 48 };
93 models.push( model );
94
95 //リンク2 緑
96 var model = makeLink( masters.cube );
97 model.scale *= 1.2;
98 model.posX = 9;
99 model.posY = -models[ 0 ].scale + model.scale; //高さ合わせ
100 model.posZ += 5;
101 model.baseColor = { r : 48, g : 196, b : 48 };
102 models.push( model );
103
104 //リンク3 青
105 var model = makeLink( masters.cube );
106 model.scale *= 0.3;
107 model.posX = 1;
108 model.posY = -models[ 0 ].scale + model.scale; //高さ合わせ
109 model.posZ = 62;
110 model.kaitenH = 0.02;
111 model.baseColor = { r : 48, g : 48, b : 196 };
112 models.push( model );
113
114 cam = new Object();
115
116 //望遠レンズなら200、普通のカメラなら50(レンズサイズまたは焦点距離という)
117 cam.s = 50;
118
119 //視点回転
120 cam.kaitenH = 0;
121 cam.kaitenV = 0;
122
123 //視点位置
124 cam.posX = 0;
125 cam.posY = 0;
126 cam.posZ = 0;
127
128 //ある位置を注視しながら回転する
129 var center = new Object();
130 center.x = models[ 2 ].posX;
131 center.y = models[ 2 ].posY;
132 center.z = models[ 2 ].posZ;
133 doTarget( cam, center );
134 var kaitenH = -3.14 / 4;
135 var kaitenV = -3.14 / 16 * 5;
136 camDomeKaiten( cam, center, kaitenH, kaitenV );
137 draw();
138 }
139
140 function draw() {
141
142 //各モデルについて
143 for( var m = 0; m < models.length; m++ ) {
144 var model = models[ m ];
145 model.tensC = new Array();
146
147 //頂点の位置を計算
148 for( var i = 0; i < model.tens.length; i++ ) {
149 var x = model.tens[ i ].x;
150 var y = model.tens[ i ].y;
151 var z = model.tens[ i ].z;
152
153 //scale倍
154 x *= model.scale;
155 y *= model.scale;
156 z *= model.scale;
157
158 //回転
159 var res = kaiten( x, z, model.kaitenH );
160 x = res.X;
161 z = res.Y;
162
163 var res = kaiten( z, y, model.kaitenV );
164 z = res.X;
165 y = res.Y;
166
167 //位置へ移動
168 x += model.posX;
169 y += model.posY;
170 z += model.posZ;
171
172 //カメラ移動
173 x += -cam.posX;
174 y += -cam.posY;
175 z += -cam.posZ;
176
177
178 //カメラ回転
179 var res = kaiten( x, z, -cam.kaitenH );
180 x = res.X;
181 z = res.Y;
182
183 var res = kaiten( z, y, -cam.kaitenV );
184 z = res.X;
185 y = res.Y;
186
187
188 //3Dの座標を2Dの座標に変換する
189 var h = x * ( cam.s / z );
190 var v = -y * ( cam.s / z );
191
192 //画面を引き延ばし
193 h *= 14.25;
194 v *= 14.25;
195
196 //値を保管
197 model.tensC[ i ] = new Object();
198 model.tensC[ i ].x = x;
199 model.tensC[ i ].y = y;
200 model.tensC[ i ].z = z;
201 model.tensC[ i ].h = h;
202 model.tensC[ i ].v = v;
203
204 }//for i
205 }//for m
206 //頂点の位置を計算済み
207
208
209 //面の配列を作成する
210 var allmens = new Array();
211
212 //各モデルについて
213 for( var m = 0; m < models.length; m++ ) {
214 var model = models[ m ];
215
216 for( var j = 0; j < model.mens.length; j++ ) {
217 var men = model.mens[ j ];
218
219 //check. 面が視点のほうを向いていないなら、その面は除去
220 var tmpTens = new Array();
221 for( var i = 0; i < men.length; i++ )
222 tmpTens.push( model.tensC[ men[ i ] ] );
223
224 if( ! checkVisibility( tmpTens ) ) continue;
225
226 //面を構成する点を順にたどる
227 var sumZ = 0;
228 for( var k = 0; k < men.length; k++ ) {
229 var tenIDX = men[ k ];
230 var ten = model.tensC[ tenIDX ];
231 sumZ += ten.z;
232 }
233
234 //allmensへ追加する
235 var theMen = new Object();
236 theMen.men = men;
237 theMen.avgZ = sumZ / men.length;
238 theMen.model = model;
239 allmens.push( theMen );
240
241 }//for j
242 }//for m
243
244
245 //すべての面を奥から手前の順にソート
246 //(陰面消去1 「画家のアルゴリズム」)
247 allmens.sort( function( a, b ) {
248 if( a.avgZ > b.avgZ ) return -1;
249 else if( a.avgZ < b.avgZ ) return 1;
250 else return 0;
251 } );
252
253
254 //描画
255 for( var i = 0; i < allmens.length; i++ ) {
256 var men = allmens[ i ].men;
257 var model = allmens[ i ].model;
258
259 //面を構成する点を順にたどる
260 canvas.beginPath();
261 for( var j = 0; j < men.length; j++ ) {
262 var tenIDX = men[ j ];
263 var ten = model.tensC[ tenIDX ];
264
265 var h = ten.h;
266 var v = ten.v;
267
268 //最初の点はmoveTo、続く点はlineTo
269 if( j == 0 ) {
270 canvas.moveTo( screenW / 2 + h, screenH / 2 + v );
271 } else {
272 canvas.lineTo( screenW / 2 + h, screenH / 2 + v );
273 }
274 }
275 canvas.closePath();
276
277 //面の中を塗る
278 canvas.fillStyle = rgb2str( model.baseColor );
279 canvas.fill();
280
281 //面の線を描く
282 canvas.strokeStyle = "black";
283 canvas.stroke();
284 }
285 }
286
287 //---その他関数
288
289 function checkVisibility( p ) {
290 var x1 = p[ 0 ].x, y1 = p[ 0 ].y, z1 = p[ 0 ].z;
291 var x2 = p[ 1 ].x, y2 = p[ 1 ].y, z2 = p[ 1 ].z;
292 var x3 = p[ 2 ].x, y3 = p[ 2 ].y, z3 = p[ 2 ].z;
293
294 //法線(housen)
295 var hx = (y2-y1)*(z3-z2)-(z2-z1)*(y3-y2);
296 var hy = (z2-z1)*(x3-x2)-(x2-x1)*(z3-z2);
297 var hz = (x2-x1)*(y3-y2)-(y2-y1)*(x3-x2);
298
299 //視点の方向(eye)
300 var ex = 0, ey = 0, ez = 0;
301 //重心(各成分の平均値)を計算している
302 for( var i = 0; i < p.length; i++ ) {
303 ex += p[ i ].x;
304 ey += p[ i ].y;
305 ez += p[ i ].z;
306 }
307 ex /= -p.length;
308 ey /= -p.length;
309 ez /= -p.length;
310
311 //法線と視点方向の内積(※cosθ)
312 var a = ex * hx + ey * hy + ez * hz;
313
314 return a > 0;
315 }
316
317 function kaiten( x, y, theta2 ) {
318 /*
319 数学分野の汎用関数
320 原点を中心にして、回転する
321 */
322 var theta1 = Math.atan2( y, x );
323 var hankei = Math.sqrt( x * x + y * y );
324 var kaitenX = Math.cos( theta1 + theta2 ) * hankei;
325 var kaitenY = Math.sin( theta1 + theta2 ) * hankei;
326 return { X : kaitenX, Y : kaitenY };
327 }
328
329 function rgb2str( rgb ) {
330 /*
331 便利目的の関数
332 */
333 console.log( "RGB(" + rgb.r + "," + rgb.g + "," + rgb.b + ")" );
334 return "RGB(" + rgb.r + "," + rgb.g + "," + rgb.b + ")";
335 }
336
337 function objCpy( obj ) {
338 /*
339 便利目的の関数
340 */
341 var res = new Object();
342 for( var name in obj ) {
343 res[ name ] = obj[ name ];
344 }
345 return res;
346 }
347
348 function doTarget( cam, targetP ) {
349 /*
350 カメラの位置はそのままで、targetの方向を向かせる。
351 */
352
353 //カメラを0,0,0としたターゲットの位置
354 var tx = targetP.x - cam.posX;
355 var ty = targetP.y - cam.posY;
356 var tz = targetP.z - cam.posZ;
357
358 //カメラから見たターゲットの角度
359 var kakudoTH = Math.atan2( tz, tx );
360 var kakudoTV = Math.atan2( ty, tz );
361
362 //カメラに反映
363 cam.kaitenH = kakudoTH - 1.57; //角度はxの方向から数えるのに対し、カメラはZ方向を見て回転0としているから-1.57
364 cam.kaitenV = kakudoTV;
365 }
366
367 function camDomeKaiten( cam, center, theta2H, theta2V ) {
368 /*
369 カメラを、ある位置を注視しながら回転させる
370 */
371
372 //水平回転
373 if( theta2H != 0 ) {
374 //水平面において、カメラの位置をcenterを中心にtheta2だけ加算回転、
375 var res = kaiten2( center.x, center.z, cam.posX, cam.posZ, theta2H );
376 cam.posX = res.X;
377 cam.posZ = res.Y;
378 cam.kaitenH += theta2H;
379 }
380
381 //垂直回転
382 if( theta2V != 0 ) {
383
384 //check. 真上、真下になったら回転しない
385 /*
386 縦角度の加算後が、真上(-1.57)や真下(1.57)を超えるなら、
387 加算後の縦角度が-1.57か1.57になるように加算値を変更する。
388 */
389 var v = Math.abs( Math.floor( ( cam.kaitenV + theta2V ) * 100 ) / 100 );
390 if( v >= 3.14 / 2 ) {
391 theta2V = 3.14 / 2 * ( theta2V > 0 ? 1 : -1 ) - cam.kaitenV;
392 //check.
393 if( theta2V == 0 ) return;
394 }
395
396 //1. 水平回転分を回転なしへ戻す
397 var kaitenHbak = cam.kaitenH;
398
399 var res = kaiten2( center.x, center.z, cam.posX, cam.posZ, -cam.kaitenH );
400 cam.posX = res.X;
401 cam.posZ = res.Y;
402 cam.kaitenH = 0;
403
404 //2. 垂直回転を行う
405 var res = kaiten2( center.z, center.y, cam.posZ, cam.posY, theta2V );
406 cam.posZ = res.X;
407 cam.posY = res.Y;
408 cam.kaitenV += theta2V;
409
410 //3. 水平回転を復元する
411 var res = kaiten2( center.x, center.z, cam.posX, cam.posZ, kaitenHbak );
412 cam.posX = res.X;
413 cam.posZ = res.Y;
414 cam.kaitenH = kaitenHbak;
415
416 }//if
417 }//camDomeKaiten()
418
419 function kaiten2( centerX, centerY, x, y, theta2 ) {
420 /*
421 数学分野の汎用関数
422 ある場所を中心にして、回転する
423 */
424 x -= centerX;
425 y -= centerY;
426 var theta1 = Math.atan2( y, x );
427 var hankei = Math.sqrt( x * x + y * y );
428 var kaitenX = Math.cos( theta1 + theta2 ) * hankei;
429 var kaitenY = Math.sin( theta1 + theta2 ) * hankei;
430 kaitenX += centerX;
431 kaitenY += centerY;
432 return { X : kaitenX, Y : kaitenY };
433 }
434
435 </script>
436 <style>
437 </style>
438 </head>
439 <body onload="onloadx();" style="
440 background-color : lightgray;
441 height : 100%;
442 margin : 0;
443 ">
444 <canvas id="canvasELID" width="512" height="448" style="
445 display : block;
446 margin : auto;
447 background-color : white;
448 border : solid 1px black;
449 ">There is no canvas.</canvas>
450 </body>
451 </html>