先日のPlaneのコードを読み解いたところで、プリミティブの書き方が少しはわかった。
しかし簡易的なコードらしく、他のプリミティブと比べて足りないところが少しある。
今度はこれを付け加えてみよう。
具体的には
だ。
Object3Dを継承し、そこにMeshを作ってaddChild()していたが、これではMeshをメンバにしなくてはアクセスできないし、Planeを指定してMeshの各種継承メソッドを使うこともできない。
他のプリミティブはMeshを継承したクラスになっているので、Planeでも同じようにしよう。
これでgeometryプロパティにもgetSurface()メソッドにもアクセスできるようになる。
次に、テクスチャマッピングはuv座標を
var uvt:Array = [0, 1, 0, 1, 0, 1, 0, 1];
と指定している。
uv座標とは、このように画像の左下を(0,0)、右上を(1,1)としたときにポリゴンの各頂点がどの位置にあたるかを指定するものだ。
先のコードでは4つの頂点に対しすべて(0,1)が割り当てられているため、画像を貼り付けることができていない。
同じ例で見るなら、
var uvt:Array = [0, 0, 0, 1, 1, 1, 1, 0];
とすべきだろう。
そして、分割数がsegmentsW=1、segmentsH=1に固定されてしまっている。
簡易的にはこれで十分だが他のプリミティブにはこれを指定できるので引数に分割数を加えて指定できるようにしよう。
最後にclone()メソッドだが、普通内部的にどうするものなのかは知らないが、引数をprivateに持っておいてcloneメソッド内でPlaneをnewして返せばいいだろう。
今回、説明では簡易的にsegmentsW=4、segmentsH=3とする。
中心が原点に来るようにすると、このようなPlaneができる。
直感的にわかるだろうが、頂点の数は(segmentsW+1)*(segmentsH+1)=20個になる。
各方向の頂点数は分割数に1を足したものになるが、何度か出てくるため
w方向の頂点数をnumW=segmentsW+1、
h方向の頂点数をnumH=segmentsH+1
としておこう。
この20個の頂点だが、インデックス番号をつけなくてはならない。
番号の降り方に定番のようなものがあったり、Alternativa3Dに決まりがあるのかもしれないが、
今回は好みでこのように番号を振った。
左下を0とし、上に向かう。一番上まで来たら、右にずれて下に戻るようになっている。
まずuvマッピングから考える。
xy座標にそのまま対応させ、0が(0,0)、3が(0,1)、16が(1,0)、19が(1,1)となるようにすればよいだろう。
頂点数numVertex=numW*numHの分だけループさせ、そこでuv座標を割り振ろう。
前回uv座標の配列はArrayで作って後々Vector.<Number>にキャストしていたが
特に意味はなさそうなので最初からVectorで作ろう。
var uvt:Vector.<Number> = new Vector.<Number>(numVertex * 2);
Vectorにはuとvの二つがセットで連続してはいるので長さは頂点数の2倍だ。
u座標は0~3は0となる。4~7は0.25、8~11は0.5だ。
このように連続した数iに対応した値がある一定の区切りkを超えるごとに一定数d増える場合はiを区切りの数kで割った整数部を使い、
arr[i]=int(i/k)*d
とするのが常套手段?だ。
今回は一定数dは全幅1.0を幅分割数segmentsWで割ると出てくる。for文の中で何度も同じ計算はしたくないので
offsetW=1.0/_segW;
offsetH=1.0/_segH;
としておこう。区切りの数はnumHなのでi番目の頂点のu座標u[i]は
u[i] = offsetW * (i / numH >> 0);
となる。
v座標は0は0、1は1/3、2は2/3、3は1となり、4になると下に戻ってまた0となる。
このように連続した数iに対応した値がiの増加に対して一定数k増え、ある一定の区切りkを超えるごとにリセットされるような場合はiを区切りの数kで割った余りを使い、
arr[i]=(i%k)*d
とする。d=1.0/segmentsH、k=numHなのでi番目の頂点のv座標v[i]は
v[i] = offsetH * (i % numH);
このuv座標のVectorは頂点分uとvを交互に入れるので最終的には
var uvt:Vector.<Number> = new Vector.<Number>(numVertex * 2);
for (i = 0; i < numVertex; i++){
n = i << 1;
uvt[n] = offsetW * (i / numH >> 0);
uvt[uint(n + 1)] = offsetH * (i % numH);
}
となる。頂点の数iに対して配列uvtにはi*2とi*2+1の場所に入れる。
まあpush()するともっと楽なんだけどね。
xyz座標のほうも同じように考えることができる。
基準点がずれ、左下の0が(-w/2、-h/2)になり、offsetが変わってw/segmentsW、h/segmentsHになっただけだ。
先ほどのoffsetWには1/segmentsWが入ってるのでw倍してやればよい。
また、Vectorの長さは頂点数の3倍になり、z座標には0を入れる。
var biasW:Number = -_w / 2;
var biasH:Number = -_h / 2;
offsetW *= _w;
offsetH *= _h;
var vertices:Vector.<Number> = new Vector.<Number>(numVertex * 3);
for (i = 0; i < numVertex; i++){
n = i * 3;
vertices[n] = biasW + offsetW * (i / numH >> 0);
vertices[uint(n + 1)] = biasH + offsetH * (i % numH);
vertices[uint(n + 2)] = 0;
}
さて、分割がある場合三角形の指定が少しやっかいだ。
1つの四角形ポリゴンは2つの三角形ポリゴンからできるので、四角形ポリゴンの数、numTiles=segmentsW*segmentsHとするとポリゴン数numTriangles=numTiles*2となる(片面の場合)
1つのポリゴンは3点から成るので三角形の結びを指定する配列vec_indicesは長さがnumTriangles*3になる。
ポリゴンを結ぶ順番やポリゴンのインデックスも定石があるのかもしれないが好みで頂点と同じように左下から数えることにする。
ポリゴン0を0→5→4、1を0→1→5とし、1つのタイルをセットにして全部のタイルを数え上げよう。
1つのタイルの左下の頂点のインデックスをjとおくと、
赤ポリゴンはj→j+1+numH→j+numH、青ポリゴンはj→j+1→j+1+numHとなることがわかる。
タイル0の時j=0、1の時j=1、2の時j=2となるが、3になるとj=4となる。また、タイル6だとj=8だ。
一番左の列はj=i、1つ右の列はj=i+1、もうひとつ右の列はj=i+2となる。
タイルの左下からのインデックスをiとおくとj=i+int(i/segmentsH)と直感で計算。
タイルの数でループすると配列vec_indicesには1つのタイルにつき6つ入るのでこんなかんじになる。
var vec_indices:Vector.<uint> = new Vector.<uint>(numTriangles * 3);
for (i = 0; i < numTiles; i++){
j = (i / _segH >> 0) + i;
n = i * 6;
vec_indices[n] = j;
vec_indices[uint(n + 1)] = j + 1 + numH;
vec_indices[uint(n + 2)] = j + numH;
vec_indices[uint(n + 3)] = j;
vec_indices[uint(n + 4)] = j + 1;
vec_indices[uint(n + 5)] = j + 1 + numH;
}
ここで、裏面の設定だが、もうめんどくさくなったので各ポリゴンの順番はそのままで裏から見て時計回りになるように2つめの数字と3つめの数字を入れ替えることにした。
if (_doubleSided){
for (i = 0; i < numTiles; i++){
j = i * 6;
n = j + numTiles * 6;
vec_indices[n] = vec_indices[j];
vec_indices[uint(n + 1)] = vec_indices[uint(j + 2)];
vec_indices[uint(n + 2)] = vec_indices[uint(j + 1)];
vec_indices[uint(n + 3)] = vec_indices[uint(j + 3)];
vec_indices[uint(n + 4)] = vec_indices[uint(j + 5)];
vec_indices[uint(n + 5)] = vec_indices[uint(j + 4)];
}
}
これで分割を指定したときも正常に割り振ってくれるPlaneができた。
ところで、uv座標は0~1の間である必要はない。たとえば2を指定するとそのぶん画像が繰り返されるようだ。
また、最大値が1である必要もない。端が0.5になるようにすると、元の画像の半分までが引き延ばされて表示される。
よって、現在等間隔に0~1の間でスケールされているuv座標の各成分に_uNumをかけると_uNumが2以上の整数の場合はu成分方向に画像が繰り返され、_uNumが1未満の場合はそこだけ切り抜いたようにマッピングされる。
以前、テクスチャ画像が2の乗数でなければならないので、整数比しか使えないのかよと思ったが、ここでマッピングにスケールをかけてやればv*=0.3とすることで
2の乗数*2の乗数のテクスチャ画像から好きな形に切り出せるような気がする。
というわけでu、vの繰り返し数(かつ切り出し比)でuNumとvNumを引数に追加して、clone()を加えて
新しいプリミティブPlaneはできた。
package { import alternativa.engine3d.core.Object3D; import alternativa.engine3d.core.VertexAttributes; import alternativa.engine3d.materials.Material; import alternativa.engine3d.objects.Mesh; import alternativa.engine3d.resources.Geometry; /** * ... * @author 9ballsyndrome * @see http://9ballsyndrome.blog.fc2.com/ * @version 1.0 * @date 07/05/2011 */ public class Plane extends Mesh { private var _h:uint; private var _w:uint; private var _segW:uint; private var _segH:uint; private var _uNum:Number; private var _vNum:Number; private var _material:Material; private var _doubleSided:Boolean; public function Plane(width:Number = 100, height:Number = 100, widthSegments:uint = 1, heightSegments:uint = 1, doubleSided:Boolean = false, material:Material = null, uNum:Number = 1, vNum:Number = 1){ this._w = width; this._h = height; this._segW = widthSegments; this._segH = heightSegments; _uNum = uNum; _vNum = vNum; this._doubleSided = doubleSided; this._material = material; setPlane(); } private function setPlane():void { //set parameter var numW:uint = _segW + 1; var numH:uint = _segH + 1; var numVertex:uint = numW * numH; var numTiles:uint = _segW * _segH; var numTriangles:uint = (_doubleSided) ? numTiles * 4 : numTiles * 2; // var attributes:Array = new Array(); attributes[0] = VertexAttributes.POSITION; attributes[1] = VertexAttributes.POSITION; attributes[2] = VertexAttributes.POSITION; attributes[3] = VertexAttributes.TEXCOORDS[0]; attributes[4] = VertexAttributes.TEXCOORDS[0]; var geometry:Geometry = new Geometry(); geometry.addVertexStream(attributes); geometry.numVertices = numVertex; var i:uint; var j:uint; var n:uint; //uv var offsetW:Number = _uNum / _segW; var offsetH:Number = _vNum / _segH; var uvt:Vector.<Number> = new Vector.<Number>(numVertex * 2); for (i = 0; i < numVertex; i++){ n = i << 1; uvt[n] = offsetW * (i / numH >> 0); uvt[uint(n + 1)] = offsetH * (i % numH); } //xyz var biasW:Number = -_w / 2; var biasH:Number = -_h / 2; offsetW = _w / _segW; offsetH = _h / _segH; var vertices:Vector.<Number> = new Vector.<Number>(numVertex * 3); for (i = 0; i < numVertex; i++){ n = i * 3; vertices[n] = biasW + offsetW * (i / numH >> 0); vertices[uint(n + 1)] = biasH + offsetH * (i % numH); vertices[uint(n + 2)] = 0; } //indices var vec_indices:Vector.<uint> = new Vector.<uint>(numTriangles * 3); for (i = 0; i < numTiles; i++){ j = (i / _segH >> 0) + i; n = i * 6; vec_indices[n] = j; vec_indices[uint(n + 1)] = j + 1 + numH; vec_indices[uint(n + 2)] = j + numH; vec_indices[uint(n + 3)] = j; vec_indices[uint(n + 4)] = j + 1; vec_indices[uint(n + 5)] = j + 1 + numH; } if (_doubleSided){ for (i = 0; i < numTiles; i++){ j = i * 6; n = j + numTiles * 6; vec_indices[n] = vec_indices[j]; vec_indices[uint(n + 1)] = vec_indices[uint(j + 2)]; vec_indices[uint(n + 2)] = vec_indices[uint(j + 1)]; vec_indices[uint(n + 3)] = vec_indices[uint(j + 3)]; vec_indices[uint(n + 4)] = vec_indices[uint(j + 5)]; vec_indices[uint(n + 5)] = vec_indices[uint(j + 4)]; } } geometry.setAttributeValues(VertexAttributes.POSITION, vertices); geometry.setAttributeValues(VertexAttributes.TEXCOORDS[0], uvt); geometry.indices = vec_indices; this.geometry = geometry; this.addSurface(_material, 0, numTriangles); this.calculateBoundBox(); } override public function clone():Object3D { return new Plane(_w, _h, _segW, _segH, _doubleSided, _material, _uNum, _vNum); } } }
まあそのうち公式で追加される気がするけど。
<<Alternativa3D 8.8.0 で頂点制御(前編) | ホーム | Alternativa3D 8.8.0 たぶんMeshの作り方>>
Author:9ballsyn
ActionScriptについて
最近はMolehill