/*
どうなっているのか?
App()
App.onloadx() htmlのonloadで呼ばれる。画像のプリロードからも呼ばれ、loadをカウントしてる。
App.onloadx1() htmlとimageのloadが完了したら呼ばれる。固定的な内容。onloadx2を呼ぶ。
App.onresizex() 固定的な内容。通常は変更しないと思う。
App.onloadx2() ★ここから書く★
App.start() stopをクリックした後再開するとき呼ばれる。
App.run() 書いたもの。消しても良い
App.draw() 書いたもの。消しても良い
【説明】
このファイルを html ファイルの script タグの src 属性で指定するだけで、動作開始するように作ったつもり。
目的のソフトウェアを App というクラスにまとめてある。
コメントで、「以下 変更しない部分」、「以下 変更する部分」のようにファイルを大きく2分してある。
このファイルを再利用するためです。
html の読み込みが完了した時点で、App クラスの onloadx1() 関数が呼ばれる。
onloadx1() と onloadx2() は処理的に分ける必要はないが、変更する必要のない部分と、
目的のソフトウェアを実装する部分とに分別するために分けた。
リサイズされたら、目的の部分(canvas 要素)はサイズを自動で合わせるようになっている。
【目的のソフトウェア 部分】
ページのロードが完了すると、このプログラムは画面に大きく、多数の四角形の 3DCG を描画する。
右上の STOP ボタンで停止と再開を行う。
アニメを行う基本的なプログラムになっている。
onloadx2() で 3DCG の各モデルの定義を行い、その後すぐに draw() している。
つづく start() でアニメが開始される。( setInterval() による run() の定期実行を行う)
run() は各アニメの数値の推移を行い、
"すい星"(大きな四角形の後に小さな四角形がパラパラと続く様子)
の尾の作成、消去など行い、最後に draw() している。
draw() は 3DCG の描画の基本的なプログラムが書かれている。
1. 画面に描く各四角形の4頂点の座標計算
2. 奥から手前の順になるようにソート(画家のアルゴリズム)
3. 描画
webGL やその他 3DCG などのライブラリは一切使用していません。
*/
//---以下 変更しない部分
/*
→ relative, z-index=0; に変更される
STOPやSPEEDなどのリンク
自動作成される
自動作成される
*/
var indexjs = new App();
App.prototype.onloadx = function( e ) {
this.onloadxCount++;
console.log( this.onloadxCount + " / " + this.onloadxCountMax );
//check.
if( this.onloadxCount == this.onloadxCountMax ) {
var parentEL = document.getElementById( "whiteareaID" );
this.systemStart( parentEL ); //systemStartは最後にonloadx2を呼んでいる
}
};
//ページ読み込み完了で開始
//---画像プリロード
indexjs.images = {
"red" : "20180901-indexJS/imgs/red.png",
"green" : "20180901-indexJS/imgs/green.png",
"blue" : "20180901-indexJS/imgs/blue.png",
"mountainLeft" : "20180901-indexJS/imgs/mountainLeft.png",
"title" : "20180901-indexJS/imgs/title.png",
}
indexjs.onloadxCount = 0;
indexjs.onloadxCountMax = Object.keys( indexjs.images ).length + 1;
for( var name in indexjs.images ) {
var src = indexjs.images[ name ];
indexjs.images[ name ] = new Image();
indexjs.images[ name ].onload = indexjs.onloadx.bind( indexjs );
indexjs.images[ name ].src = src;
}
addEventListener( "load", indexjs.onloadx.bind( indexjs ) );
function App() { //Appクラスのコンストラクタ
this.name = "test";
}
App.prototype.systemStart = function( parentEL ) {
/*
固定的な内容。
*/
console.log( "systemStart()" );
this.speedstep = [ 10, 100, 200, 500 ];
this.timerMS = this.speedstep[ 1 ];
//(※正直言うとcanvasのサイズについて多少混乱中…)
//check.
if( ! parentEL ) parentEL = document.getElementsByTagName( "body" )[ 0 ];
//canvasを置く親要素
this.parentEL = parentEL;
with( this.parentEL.style ) {
position = "relative";
zIndex = 0;
}
//アニメの停止ボタン 親要素の上部に配置
this.swEL = document.createElement( "div" );
this.parentEL.appendChild( this.swEL );
with( this.swEL.style ) {
border = "solid 0px black";
position = "absolute";
right = "0px";
top = "0px";
boxSizing = "border-box";
zIndex = 2;
padding = ".5em 1em";
}
this.swEL.innerHTML = "";
this.swEL.innerHTML += "STOP
";
this.swEL.innerHTML += 'SPEED';
//CANVAS要素 親要素の上部に配置
this.canvasEL = document.createElement( "canvas" );
this.parentEL.appendChild( this.canvasEL );
this.canvasEL.setAttribute( "id", "indexjs_canvasELID" );
with( this.canvasEL.style ) {
border = "solid 0px black";
position = "absolute";
left = "0px";
top = "0px";
boxSizing = "border-box";
zIndex = -1;
}
//true にするとドットがシャープになる。falseはアンチエイリアスが入る(通常)。IEは非対応
if( true ) {
this.canvasEL.style.imageRendering = "pixelated";
this.canvasEL.style.imageRendering = "optimizeSpeed";
}
this.lowmode = true;
this.cc = this.canvasEL.getContext( "2d" );
this.onresizex();
//resize設定
addEventListener( "resize", ( function( e ) { this.onresizex( e ); } ).bind( this ), false );
//canvasが画面外に出たらCPUパワーを抑えるために停止する処理
addEventListener( "scroll", function( e ) {
var scrollEL = document.documentElement ? document.documentElement : document.body;
var r = indexjs.cc.canvas.getBoundingClientRect();
if( scrollEL.scrollTop + r.top + r.height / 2 < 0 )
indexjs.stop();
else
if( ! indexjs.timerID ) indexjs.start();
}, false );
this.programStart();
}//systemStart
App.prototype.onresizex = function( e ) {
console.log( "onresizex()" );
/*
systemStart()で呼ばれている
リサイズすると呼ばれる
*/
//リサイズされた親要素に合わせて、サイズ変更
var wa = this.parentEL;
var waRect = wa.getBoundingClientRect();
this.canvasW = Math.round( waRect.width );
//最初のh2タグに合わせる
var htags = document.getElementsByTagName( "h2" );
if( htags.length > 0 ) {
this.canvasH = Math.round( htags[ 0 ].getBoundingClientRect().top - wa.getBoundingClientRect().top ) - 16;
} else {
this.canvasH = 480;
}
this.canvasEL.style.width = this.canvasW + "px";
this.canvasEL.style.height = this.canvasH + "px";
this.canvasEL.setAttribute( "width", this.canvasW );
this.canvasEL.setAttribute( "height", this.canvasH );
//true にすると解像度を下げる。false は通常。
if( this.lowmode ) {
var pixelsize = 2; //ドットの大きさ2~
this.canvasEL.style.width = this.canvasEL.width + "px"; //実物画面大きさとして
this.canvasEL.style.height = this.canvasEL.height + "px";
this.canvasEL.width /= pixelsize; //解像度として
this.canvasEL.height /= pixelsize;
this.cc.scale( 1 / pixelsize, 1 / pixelsize );
}
if( e ) this.draw();
}//onresizex
//---以上 変更しない部分
//---以下 変更する部分 目的のソフトウェア
App.prototype.programStart = function() {
/*
基本的にはここから書く
*/
console.log( "programStart()" );
d3_onloadx();
this.start();
onkeydown = function( e ) {
console.log( e.which );
indexjs.restart();
};
};
//--- タイマ制御系
App.prototype.start = function() {
console.log( "start()", this.timerMS );
this.draw();
this.timerID = setInterval( this.run.bind( this ), this.timerMS );
};
App.prototype.stop = function() {
if( this.timerID ) {
console.log( "stop()" );
clearInterval( this.timerID );
this.timerID = null;
}
};
App.prototype.reset = function() {
this.stop();
this.start();
};
App.prototype.restart = function() {
this.stop();
this.programStart();
};
allstop = false;
constMax = 5;
allcount = 0;
App.prototype.run = function() {
allcount++;
for( var i = models.length - 1; i >= 0; i-- ) {
var model = models[ i ];
model.kaitenH += -.05;
model.kaitenV += .025;
//
if( allcount > 300 ) {
indexjs.restart();
return;
}
//check.
if( model.animateFLG == false ) continue;
model.animateValue += 1;
//check.
if( model.animateValue > model.animateValueMax ) {
model.animateFLG = false;
if( allstop ) {
continue;
}
var array = splitMen( model, model.mens[ 0 ] );
for( var j = 0; j < array.length; j++ ) {
var tmpmodel = array[ j ];
setAnimate( tmpmodel );
}
cnt = 4;
if( models.length == Math.pow(2,cnt*2+1)-1 ) {
allstop = true;
continue;
}
}
}
this.draw();
};
function setAnimate( object ) {
object.hideproc = false;
object.animateFLG = true;
}
App.prototype.draw = function() {
var cc = this.cc;
d3_draw();
};
//---extend
//https://qiita.com/Layzie/items/465e715dae14e2f601de
function is(type, obj) {
var clas = Object.prototype.toString.call(obj).slice(8, -1);
return obj !== undefined && obj !== null && clas === type;
}
console.logO = function() {
var res = "";
for( var j = 0; j < arguments.length; j++ ) {
var arg = arguments[ j ];
var st = "";
if( arg instanceof Object ) {
for( var name in arg ) {
st += name + ":" + arg[ name ] + ", ";
}
}
else {
st = arg;
}
res += " " + st;
}
console.log( res );
}
console.logA = function() {
var res = "";
for( var j = 0; j < arguments.length; j++ ) {
var arg = arguments[ j ];
var st = "";
if( arg instanceof Object ) {
for( var i = 0; i < arg.length; i++ ) {
st += "[" + i + "] " + arg[ i ] + ", ";
}
}
else {
st = arg;
}
res += " " + st;
}
console.log( res );
}
//値に注目して、配列にその要素があるかどうか
function arrayIndexOf2( array, element ) {
if( element instanceof Array ) {
//要素が配列のとき
for( var i = 0; i < array.length; i++ ) {
var res = true;
for( var j = 0; j < element.length; j++ ) if( element[ j ] != array[ i ][ j ] ) { res = false; break; }
if( res ) return i;
}
} else if( element instanceof Object ) {
//要素がオブジェクトのとき
for( var i = 0; i < array.length; i++ ) {
var res = true;
for( var n in element ) if( element[ n ] != array[ i ][ n ] ) { res = false; break; }
if( res ) return i;
}
} else {
//要素が値のとき
return array.indexOf( element );
}
return -1;
}
//面を分割する
function splitMen( model, men ) {
var array = new Array();
var calcTens = new Array();
var seek1 = model.tens.length;
for( var j = 0; j < men.tenIDXs.length; j++ ) {
var tenIDX = men.tenIDXs[ j ];
var ten = model.tens[ tenIDX ];
var x = ten.x;
var y = ten.y;
var z = ten.z;
//scale倍
x *= model.scale;
y *= model.scale;
z *= model.scale;
//anim
u = model.getJusin();
n = toNorm( { x:u.x, y:u.y, z:u.z } );
x += model.animateValue * n.x;
y += model.animateValue * n.y;
z += model.animateValue * n.z;
x /= model.scale;
y /= model.scale;
z /= model.scale;
var newTen = { x:x, y:y, z:z };
calcTens.push( newTen );
model.tens.push( newTen );
}
/*
calcTens
calcTens
calcTens
calcTens
tmpTen
tmpTen
tmpTen
tmpTen
jusin
*/
var seek2 = model.tens.length;
var jusin = xyz( 0, 0, 0 );
var tmpTenIdxies = new Array();
//分割に使用する新点を作成
for( var i = 0; i < calcTens.length; i++ ) {
var ten = calcTens[ i ];
//面の重心計算1
jusin.x += ten.x;
jusin.y += ten.y;
jusin.z += ten.z;
//2点間の中点
var bakTen = calcTens[ i == 0 ? 3 : ( i - 1 ) ];
var x = ( ten.x + bakTen.x ) / 2;
var y = ( ten.y + bakTen.y ) / 2;
var z = ( ten.z + bakTen.z ) / 2;
var newTen = new xyz( x, y, z );
tmpTenIdxies.push( model.tens.length );
model.tens.push( newTen );
}
//面の重心計算2
jusin.x /= men.tenIDXs.length;
jusin.y /= men.tenIDXs.length;
jusin.z /= men.tenIDXs.length;
var jusinSeek = model.tens.length;
model.tens.push( jusin );
//元の面は削除
men.display = false;
//新しい面を作成
for( var i = 0; i < men.tenIDXs.length; i++ ) {
var tenIDX = men.tenIDXs[ i ];
var newMen = new Men( [
tmpTenIdxies[ i ],
seek1 + i,
tmpTenIdxies[ i == 3 ? 0 : ( i + 1 ) ],
jusinSeek,
] );
//men.children.push( newMen );
newModel = floatMen( model, newMen );
newModel.animateValue = 0;
newModel.animateValueMax = model.animateValue + constMax;
array.push( newModel );
}
return array;
}
//面を独立させる
function floatMen( model, men ) {
men.isFloat = true;
//面が使っている点を独立させる
newmodel = new Model();
newmodel.name = "men";
newmodel.mens[ 0 ] = new Men();
for( var i = 0; i < men.tenIDXs.length; i++ ) {
var tenIDX = men.tenIDXs[ i ];
var ten = objcopy( model.tens[ tenIDX ] );
var newTenIDX = newmodel.tens.length;
newmodel.tens.push( ten );
newmodel.mens[ 0 ].tenIDXs[ i ] = newTenIDX;
}
newmodel.scale = model.scale;
newmodel.posX = model.posX;
newmodel.posY = model.posY;
newmodel.posZ = model.posZ;
newmodel.kaitenH = model.kaitenH;
newmodel.kaitenV = model.kaitenV;
newmodel.parent = model;
if( model.mens.indexOf( men ) > -1 ) model.mens.splice( model.mens.indexOf( men ), 1 );
models.push( newmodel );
return newmodel;
}
function objcopy( object ) {
if( object instanceof Object ) {
var newobject = new Array();
for( var n in object ) {
newobject[ n ] = objcopy( object[ n ] );
}
return newobject;
} else if( object instanceof Array ) {
var newarray = new Array();
for( var i = 0; i < object.length; i++ ) {
newarray[ i ] = objcopy( object[ i ] );
}
return newarray;
} else {
return object;
}
}
//参照を切り離す
function independentFromMaster( model ) {
model.tens = objcopy( model.tens );
model.mens = objcopy( model.mens );
}
function Men( tenIDXs ) {
this.tenIDXs = tenIDXs == null ? new Array : tenIDXs;
this.children = new Array();
this.display = true;
this.isFloat = false;
}
//---3DCG
function $( id ) { return document.getElementById( id ); }
var HereDocument = /\/\*\s*([^]*?)\s*\*\//;
var canvasEL, canvas;
function d3_onloadx() {
canvasEL = indexjs.canvasEL;
canvas = canvasEL.getContext( '2d' );
screenW = canvasEL.width;
screenH = canvasEL.height;
colorselect = Math.floor( Math.random() * 6 );
allcount = 0;
allstop = false;
//マスターモデル
masters = new Object();
masters.cube = new Object();
//正六面体の頂点(中心を原点としたときの位置)
masters.cube.tens = [
{ x : -1, y : -1, z : -1 }, //0: 左下 手前
{ x : +1, y : -1, z : -1 }, //1: 右下 手前
{ x : +1, y : +1, z : -1 }, //2: 右上 手前
{ x : -1, y : +1, z : -1 }, //3: 左上 手前
{ x : -1, y : -1, z : +1 }, //4: 左下 奥
{ x : +1, y : -1, z : +1 }, //5: 右下 奥
{ x : +1, y : +1, z : +1 }, //6: 右上 奥
{ x : -1, y : +1, z : +1 }, //7: 左上 奥
];
masters.cube.mens = [
new Men( [ 3, 2, 1, 0 ] ), //0: 向かって正面
new Men( [ 1, 2, 6, 5 ] ), //1: 向かって右面
new Men( [ 0, 4, 7, 3 ] ), //2: 向かって左面
new Men( [ 7, 6, 2, 3 ] ), //3: 向かって上面
new Men( [ 0, 1, 5, 4 ] ), //4: 向かって下面
new Men( [ 5, 6, 7, 4 ] ), //5: 向かって背面
]; //※各面の点の並びは時計回り
//正六面体の拡大
masters.cube.scale = 10;
//正六面体の位置
masters.cube.posX = 0;
masters.cube.posY = 0;
masters.cube.posZ = 80;
//正六面体の回転
masters.cube.kaitenH = 0;
masters.cube.kaitenV = 0;
//正六面体のベース色
masters.cube.baseColor = { r : 196, g : 196, b : 0 };
//リンクモデル
models = new Array();
//リンク1 赤
var model = new Model( masters.cube );
model.baseColor = { r : 196, g : 48, b : 48 };
model.kaitenH = .3;
model.kaitenV = -.5;
model.name = "red";
models.push( model );
cam = new Object();
//望遠レンズなら200、普通のカメラなら50(レンズサイズまたは焦点距離という)
cam.s = 300;
cam.zoom = 1; //14.25;
//視点回転
cam.kaitenH = 0;
cam.kaitenV = 0;
//視点位置
cam.posX = 0;
cam.posY = 0;
cam.posZ = 0;
/*
//ある位置を注視しながら回転する
var center = new Object();
center.x = models[ 0 ].posX;
center.y = models[ 0 ].posY;
center.z = models[ 0 ].posZ;
doTarget( cam, center );
var kaitenH = -3.14 / 4;
var kaitenV = -3.14 / 16 * 5;
camDomeKaiten( cam, center, kaitenH, kaitenV );
*/
//光源
kougen = new Object();
kougen.pos = xyz( 0, 0, 0 );
//
model = models[ 0 ];
independentFromMaster( model );
list = new Array();
for( var i = 0; i < model.mens.length; i++ ) {
list.push( model.mens[ i ] );
}
for( var i = 0; i < list.length; i++ ) {
floatMen( model, list[ i ] );
setAnimate( models[ models.length - 1 ] );
}
d3_draw();
}//d3_onloadx
function Model( master ) {
if( master == null ) {
this.tens = new Array();
this.mens = new Array();
this.scale = 1;
this.posX = 0;
this.posY = 0;
this.posZ = 100;
this.kaitenH = 0;
this.kaitenV = 0;
this.baseColor = [
{ r : 127, g : 127, b : 255 },
{ r : 127, g : 255, b : 127 },
{ r : 255, g : 127, b : 127 },
{ r : 127, g : 255, b : 255 },
{ r : 255, g : 127, b : 255 },
{ r : 255, g : 255, b : 127 },
][ colorselect ];
} else {
this.tens = master.tens; //参照
this.mens = master.mens; //参照
this.scale = master.scale; //コピー
this.posX = master.posX; //コピー
this.posY = master.posY; //コピー
this.posZ = master.posZ; //コピー
this.kaitenH = master.kaitenH; //コピー
this.kaitenV = master.kaitenV; //コピー
this.baseColor = objCpy( master.baseColor ); //コピー
}
this.children = new Array();
this.animateValue = 0;
this.animateValueMax = constMax;
this.animateFLG = false;
this.hideproc = true;
}
Model.prototype.getJusin = function() {
var jusin = xyz( 0, 0, 0 );
var count = 0;
for( var i = 0; i < this.mens.length; i++ ) {
var men = this.mens[ i ];
for( var j = 0; j < men.tenIDXs.length; j++ ) {
var tenIDX = men.tenIDXs[ j ];
var ten = this.tens[ tenIDX ];
jusin.x += ten.x;
jusin.y += ten.y;
jusin.z += ten.z;
count ++;
}
}
jusin.x /= count;
jusin.y /= count;
jusin.z /= count;
return jusin;
}
function d3_draw() {
canvas.clearRect( 0, 0, indexjs.canvasW, indexjs.canvasH );
if( 0 ) {
var sz = 32;
canvas.strokeStyle = "lightgreen";
for( var y = 0; y < indexjs.canvasH; y += sz ) {
canvas.lineWidth = y % 10 == 0 ? 2 : 1;
canvas.beginPath();
canvas.moveTo( 0, y );
canvas.lineTo( indexjs.canvasW, y );
canvas.closePath();
canvas.stroke();
}
for( var x = 0; x < indexjs.canvasW; x += sz ) {
canvas.lineWidth = x % 10 == 0 ? 2 : 1;
canvas.beginPath();
canvas.moveTo( x, 0 );
canvas.lineTo( x, indexjs.canvasH );
canvas.closePath();
canvas.stroke();
}
}
//各モデルについて
for( var m = 0; m < models.length; m++ ) {
var model = models[ m ];
model.tensC = new Array();
//頂点の位置を計算
for( var i = 0; i < model.tens.length; i++ ) {
var x = model.tens[ i ].x;
var y = model.tens[ i ].y;
var z = model.tens[ i ].z;
//scale倍
x *= model.scale;
y *= model.scale;
z *= model.scale;
//anim
u = model.getJusin();
n = toNorm( { x:u.x, y:u.y, z:u.z } );
x += model.animateValue * n.x;
y += model.animateValue * n.y;
z += model.animateValue * n.z;
//回転
var res = kaiten( x, z, model.kaitenH );
x = res.X;
z = res.Y;
var res = kaiten( z, y, model.kaitenV );
z = res.X;
y = res.Y;
//位置へ移動
x += model.posX;
y += model.posY;
z += model.posZ;
//カメラ移動
x += -cam.posX;
y += -cam.posY;
z += -cam.posZ;
//カメラ回転
var res = kaiten( x, z, -cam.kaitenH );
x = res.X;
z = res.Y;
var res = kaiten( z, y, -cam.kaitenV );
z = res.X;
y = res.Y;
//3Dの座標を2Dの座標に変換する
var h = x * ( cam.s / z );
var v = -y * ( cam.s / z );
//画面を引き延ばし
h *= cam.zoom;
v *= cam.zoom;
//値を保管
model.tensC[ i ] = new Object();
model.tensC[ i ].x = x;
model.tensC[ i ].y = y;
model.tensC[ i ].z = z;
model.tensC[ i ].h = h;
model.tensC[ i ].v = v;
}//for i
}//for m
//頂点の位置を計算済み
//面の配列を作成する
var allmens = new Array();
//各モデルについて
var recursive = function( model, mens ) {
for( var j = 0; j < mens.length; j++ ) {
var men = mens[ j ];
recursive( model, men.children );
//check.
if( men.display == false ) continue;
var jusin; //陰面消去1と陰面消去2で使用する値
var housen; //陰面消去2と陰影処理で使用する値
var tmpTens = new Array();
for( var i = 0; i < men.tenIDXs.length; i++ )
tmpTens.push( model.tensC[ men.tenIDXs[ i ] ] );
jusin = getJusin( tmpTens );
housen = getHousen( tmpTens );
//check. 面が視点のほうを向いていないなら、その面は除去
//(陰面消去2 「法線ベクトル法」)
// if( model.hideproc && ! checkVisibility( housen, jusin ) ) continue;
//allmensへ追加する
var theMen = new Object();
theMen.men = men;
theMen.jusin = jusin;
theMen.housen = housen;
theMen.model = model;
allmens.push( theMen );
}//for j
}//
for( var m = 0; m < models.length; m++ ) {
var model = models[ m ];
recursive( model, model.mens );
}//for m
//すべての面を奥から手前の順にソート
//(陰面消去1 「画家のアルゴリズム」)
allmens.sort( function( a, b ) {
if( a.jusin.z > b.jusin.z ) return -1;
else if( a.jusin.z < b.jusin.z ) return 1;
else return 0;
} );
//描画
canvas.save();
canvas.translate( indexjs.canvasW / 2, indexjs.canvasH / 2 );
test:for( var i = 0; i < allmens.length; i++ ) {
var men = allmens[ i ].men;
var model = allmens[ i ].model;
var housen = allmens[ i ].housen;
var jusin = allmens[ i ].jusin;
//面を構成する点を順にたどる
canvas.beginPath();
for( var j = 0; j < men.tenIDXs.length; j++ ) {
var tenIDX = men.tenIDXs[ j ];
var ten = model.tensC[ tenIDX ];
//check.
if( ten.z <= 0 ) {
continue test;
}
var h = ten.h;
var v = ten.v;
//最初の点はmoveTo、続く点はlineTo
if( j == 0 ) {
canvas.moveTo( h, v );
} else {
canvas.lineTo( h, v );
}
}
canvas.closePath();
//面の中を塗る
//陰影処理(ランバート反射)を加える
//kougen.posは原点から見た位置ですが、これを面から見た位置に直し、kougenPとします。
var kougenP = xyz( kougen.pos.x - jusin.x, kougen.pos.y - jusin.y, kougen.pos.z - jusin.z );
canvas.fillStyle = rgb2str( lambelt( toNorm( kougenP ), housen, model.baseColor ) );
canvas.fill();
//面の線を描く
canvas.strokeStyle = "black";
canvas.stroke();
}
canvas.restore();
var w = 320;
var h = 18;
canvas.font = h + "px 'MS Pゴシック'";
canvas.fillText( "3DCGモデルの面を繰り返し分割する", indexjs.canvasW - w, indexjs.canvasH - h, w );
}
//---その他関数
function checkVisibility( housen, jusin ) {
//視点の方向(eye)
var sisen = objCpy( jusin );
sisen.x *= -1;
sisen.y *= -1;
sisen.z *= -1;
//法線と視点方向の内積(※cosθ)
var a = naiseki( housen, sisen );
return a > 0;
}
function kaiten( x, y, theta2 ) {
/*
数学分野の汎用関数
原点を中心にして、回転する
*/
var theta1 = Math.atan2( y, x );
var hankei = Math.sqrt( x * x + y * y );
var kaitenX = Math.cos( theta1 + theta2 ) * hankei;
var kaitenY = Math.sin( theta1 + theta2 ) * hankei;
return { X : kaitenX, Y : kaitenY };
}
function rgb2str( rgb ) {
/*
便利目的の関数
*/
return "RGB(" + rgb.r + "," + rgb.g + "," + rgb.b + ")";
}
function objCpy( obj ) {
/*
便利目的の関数
*/
var res = new Object();
for( var name in obj ) {
res[ name ] = obj[ name ];
}
return res;
}
function xyz( x, y, z ) {
/*
便利目的の関数
*/
return { x : x, y : y, z : z };
}
function getJusin( p ) {
/*
数学分野の汎用関数
面を構成する各頂点の、x,y,z各成分の平均値を得る(重心)
*/
var res = xyz( 0, 0, 0 );
for( var i = 0; i < p.length; i++ ) {
res.x += p[ i ].x;
res.y += p[ i ].y;
res.z += p[ i ].z;
}
res.x /= p.length;
res.y /= p.length;
res.z /= p.length;
return res;
}
function doTarget( cam, targetP ) {
/*
カメラの位置はそのままで、targetの方向を向かせる。
*/
//カメラを0,0,0としたターゲットの位置
var tx = targetP.x - cam.posX;
var ty = targetP.y - cam.posY;
var tz = targetP.z - cam.posZ;
//カメラから見たターゲットの角度
var kakudoTH = Math.atan2( tz, tx );
var kakudoTV = Math.atan2( ty, tz );
//カメラに反映
cam.kaitenH = kakudoTH - 1.57; //角度はxの方向から数えるのに対し、カメラはZ方向を見て回転0としているから-1.57
cam.kaitenV = kakudoTV;
}
function camDomeKaiten( cam, center, theta2H, theta2V ) {
/*
カメラを、ある位置を注視しながら回転させる
*/
//水平回転
if( theta2H != 0 ) {
//水平面において、カメラの位置をcenterを中心にtheta2だけ加算回転、
var res = kaiten2( center.x, center.z, cam.posX, cam.posZ, theta2H );
cam.posX = res.X;
cam.posZ = res.Y;
cam.kaitenH += theta2H;
}
//垂直回転
if( theta2V != 0 ) {
//check. 真上、真下になったら回転しない
/*
縦角度の加算後が、真上(-1.57)や真下(1.57)を超えるなら、
加算後の縦角度が-1.57か1.57になるように加算値を変更する。
*/
var v = Math.abs( Math.floor( ( cam.kaitenV + theta2V ) * 100 ) / 100 );
if( v >= 3.14 / 2 ) {
theta2V = 3.14 / 2 * ( theta2V > 0 ? 1 : -1 ) - cam.kaitenV;
//check.
if( theta2V == 0 ) return;
}
//1. 水平回転分を回転なしへ戻す
var kaitenHbak = cam.kaitenH;
var res = kaiten2( center.x, center.z, cam.posX, cam.posZ, -cam.kaitenH );
cam.posX = res.X;
cam.posZ = res.Y;
cam.kaitenH = 0;
//2. 垂直回転を行う
var res = kaiten2( center.z, center.y, cam.posZ, cam.posY, theta2V );
cam.posZ = res.X;
cam.posY = res.Y;
cam.kaitenV += theta2V;
//3. 水平回転を復元する
var res = kaiten2( center.x, center.z, cam.posX, cam.posZ, kaitenHbak );
cam.posX = res.X;
cam.posZ = res.Y;
cam.kaitenH = kaitenHbak;
}//if
}//camDomeKaiten()
function kaiten2( centerX, centerY, x, y, theta2 ) {
/*
数学分野の汎用関数
ある場所を中心にして、回転する
*/
x -= centerX;
y -= centerY;
var theta1 = Math.atan2( y, x );
var hankei = Math.sqrt( x * x + y * y );
var kaitenX = Math.cos( theta1 + theta2 ) * hankei;
var kaitenY = Math.sin( theta1 + theta2 ) * hankei;
kaitenX += centerX;
kaitenY += centerY;
return { X : kaitenX, Y : kaitenY };
}
function naiseki( v1, v2 ) {
/*
数学分野の汎用関数
内積を得る
*/
var a = v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
return a;
}
function getHousen( p ) {
/*
数学分野の汎用関数
法線を得る
*/
var x1 = p[ 0 ].x, y1 = p[ 0 ].y, z1 = p[ 0 ].z;
var x2 = p[ 1 ].x, y2 = p[ 1 ].y, z2 = p[ 1 ].z;
var x3 = p[ 2 ].x, y3 = p[ 2 ].y, z3 = p[ 2 ].z;
//法線
var res = new Object();
res.x = (y2-y1)*(z3-z2)-(z2-z1)*(y3-y2);
res.y = (z2-z1)*(x3-x2)-(x2-x1)*(z3-z2);
res.z = (x2-x1)*(y3-y2)-(y2-y1)*(x3-x2);
res = toNorm( res );
return res;
}
function toNorm( p ) {
/*
数学分野の汎用関数
原点から座標pまでの距離を1にした座標を返す(単位ベクトル化)
(※陰面消去2では本当は必要ないがランバート反射で必要になったので用意した)
*/
var len = Math.sqrt( p.x * p.x + p.y * p.y + p.z * p.z );
var res = new Object();
res.x = p.x / len;
res.y = p.y / len;
res.z = p.z / len;
return res;
}
function lambelt( housen, kougen, rgb ) {
var cosTheta = naiseki( housen, kougen );
var directPer = 0.6; //直接光の割合
var ambientPer = 1 - directPer; //環境光の割合
//色成分をさらに直接光と環境光の割合で分割、直接光についてはcosTheta(0~1)でさらにしぼる
var dirR = rgb.r * directPer * cosTheta;
var ambR = rgb.r * ambientPer;
var dirG = rgb.g * directPer * cosTheta;
var ambG = rgb.g * ambientPer;
var dirB = rgb.b * directPer * cosTheta;
var ambB = rgb.b * ambientPer;
//分割したものを再度合体(直接光分にcosThetaを掛けたかっただけ)
var res = new Object();
res.r = Math.round( dirR + ambR );
res.g = Math.round( dirG + ambG );
res.b = Math.round( dirB + ambB );
return res;
}