Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

BoxGeometryの中身を読んでみたメモ

Three.jsに興味を持ったので、直方体を作るBoxGeometryがどのような処理で実現しているかソースコードを読んでみました。

リビジョンは r125 です。

基本的な変数の代入処理

7-22行目
    constructor( width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1 ) {

        super();

        this.type = 'BoxGeometry';

        this.parameters = {
            width: width,
            height: height,
            depth: depth,
            widthSegments: widthSegments,
            heightSegments: heightSegments,
            depthSegments: depthSegments
        };

        const scope = this;

見たままですが、幅や高さなどの必要な情報をメンバ変数に代入しています。

面の分割数の設定

24-28行目
        // segments

        widthSegments = Math.floor( widthSegments );
        heightSegments = Math.floor( heightSegments );
        depthSegments = Math.floor( depthSegments );

分割数の引数に整数でない数値を指定した場合は小数点以下を切り捨てます。

座標情報等を保持する変数

インデックス(=三角形を作る頂点の組み合わせ?)、頂点、頂点の向き、UV座標を保持する配列の準備

30-35行目
        // buffers

        const indices = [];
        const vertices = [];
        const normals = [];
        const uvs = [];

頂点の合計と、グループ化する際の起点を保持する変数

37-40行目
        // helper variables

        let numberOfVertices = 0;
        let groupStart = 0;

各面の頂点等の座標情報を定義した配列に追加する

詳細は後で書きますが、前述の座標情報用の変数に定義しているようです。

42-49行目
        // build each side of the box geometry

        buildPlane( 'z', 'y', 'x', - 1, - 1, depth, height, width, depthSegments, heightSegments, 0 ); // px
        buildPlane( 'z', 'y', 'x', 1, - 1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx
        buildPlane( 'x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2 ); // py
        buildPlane( 'x', 'z', 'y', 1, - 1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny
        buildPlane( 'x', 'y', 'z', 1, - 1, width, height, depth, widthSegments, heightSegments, 4 ); // pz
        buildPlane( 'x', 'y', 'z', - 1, - 1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz

ジオメトリの作成

51-56行目
        // build geometry

        this.setIndex( indices );
        this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
        this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
        this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );

本題

buildPlaneという関数がコンストラクタ内に定義されており、この関数が角頂点の座標や、インデックスなどの定義を行っていました。
BoxGeometryを作成する根幹となる処理のようです。

58行目
function buildPlane( u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex ) {

それぞれの引数が何を受け取るのか、素人にはわかりづらいのでコメントでまとめると下記の通りかなと

58行目(JSDoc追記)
/**
 * @param {string} u 横として扱う座標軸の指定
 * @param {string} v 縦として扱う座標軸の指定
 * @param {string} w 奥行きとして扱う座標軸の指定
 * @param {number} udir 横の向き 1(右) or -1(左)
 * @param {number} vdir 縦の向き 1(上) or -1(下)
 * @param {number} width 横幅
 * @param {number} height 縦幅
 * @param {number} depth 奥行き幅
 * @param {number} gridX 横軸の分割数
 * @param {number} gridY 縦軸の分割数
 * @param {number} materialIndex 面の番号?
 */
function buildPlane( u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex ) {

変数の定義

60-73行目
            const segmentWidth = width / gridX;
            const segmentHeight = height / gridY;

            const widthHalf = width / 2;
            const heightHalf = height / 2;
            const depthHalf = depth / 2;

            const gridX1 = gridX + 1;
            const gridY1 = gridY + 1;

            let vertexCounter = 0;
            let groupCount = 0;

            const vector = new Vector3();

各変数は下記コメントの通りの値が入っています

60-73行目(コメント追記)
            const segmentWidth = width / gridX;   // 分割した1つあたりの横幅を計算した値
            const segmentHeight = height / gridY; // 分割した1つあたりの縦幅を計算した値

            const widthHalf = width / 2;          // 横幅の中央を計算した値
            const heightHalf = height / 2;        // 縦幅の中央を計算した値
            const depthHalf = depth / 2;          // 奥行き幅の中央を計算した値

            // 中央値を計算するのは 指定した幅から-座標と+座標の値を算出するため

            const gridX1 = gridX + 1;             // 縦軸の辺の数を計算(分割数に+1)した値
            const gridY1 = gridY + 1;             // 横軸の辺の数を計算(分割数に+1)した値

            let vertexCounter = 0;                // 頂点の数
            let groupCount = 0;                   // インデックスの数

            const vector = new Vector3();         // 三次元ベクトルの準備。使い方としてはMapの代用に見えます

頂点、法線ベクトル、UVの計算

75-116行目
            // generate vertices, normals and uvs

            for ( let iy = 0; iy < gridY1; iy ++ ) {

                const y = iy * segmentHeight - heightHalf;

                for ( let ix = 0; ix < gridX1; ix ++ ) {

                    const x = ix * segmentWidth - widthHalf;

                    // set values to correct vector component

                    vector[ u ] = x * udir;
                    vector[ v ] = y * vdir;
                    vector[ w ] = depthHalf;

                    // now apply vector to vertex buffer

                    vertices.push( vector.x, vector.y, vector.z );

                    // set values to correct vector component

                    vector[ u ] = 0;
                    vector[ v ] = 0;
                    vector[ w ] = depth > 0 ? 1 : - 1;

                    // now apply vector to normal buffer

                    normals.push( vector.x, vector.y, vector.z );

                    // uvs

                    uvs.push( ix / gridX );
                    uvs.push( 1 - ( iy / gridY ) );

                    // counters

                    vertexCounter += 1;

                }

            }

長いので少しずつ絞り込んで意味を考えます

もう少し詳細

77-79行目
            for ( let iy = 0; iy < gridY1; iy ++ ) {

                const y = iy * segmentHeight - heightHalf;

頂点を計算しないといけないので、横軸の辺の数だけループを回し、縦軸の頂点座標を計算しています。

81-83行目
                for ( let ix = 0; ix < gridX1; ix ++ ) {

                    const x = ix * segmentWidth - widthHalf;

頂点を計算しないといけないので、縦軸の辺の数だけループで回して、横軸の頂点座標を計算しています。

87-89行目
                    // set values to correct vector component

                    vector[ u ] = x * udir;
                    vector[ v ] = y * vdir;
                    vector[ w ] = depthHalf;

                    // now apply vector to vertex buffer

                    vertices.push( vector.x, vector.y, vector.z );

角頂点の座標を計算しています。

vector[ u ] は横軸の座標

vector[ v ] は縦軸の座標

vector[ w ] は奥行き軸の座標。基本的にはXY座標を計算する関数なので、常に同じ軸の座標が指定されるようになっている

※変数u,v,wには各面を正面にしたときの横、縦、奥行きの座標軸が定義されています。

95-103行目
                    // set values to correct vector component

                    vector[ u ] = 0;
                    vector[ v ] = 0;
                    vector[ w ] = depth > 0 ? 1 : - 1;

                    // now apply vector to normal buffer

                    normals.push( vector.x, vector.y, vector.z );

各頂点の法線ベクトルを計算をしています。
言葉が難しいのですが、関数自体が対象の面を正面にしたときを前提にしているので、縦横は無視して奥行きの指定によって分岐させています。

※変数u,v,wには各面を正面にしたときの横、縦、奥行きの座標軸が定義されています。

105-108行目
                    // uvs

                    uvs.push( ix / gridX );
                    uvs.push( 1 - ( iy / gridY ) );

UV座標の計算をしています。

U座標には分割数に応じて0-1の間を等分した値が入っています。V座標には1からU座標を引いた値が入ります
(何故そうなるのかはまだわかっていません・・・)

110-112行目
                    // counters

                    vertexCounter += 1;

頂点の数をカウントしています。
関数を呼び出すごとに各面の頂点の数が加算されていきます。

各面を構成するインデックスを計算

118-144行目
            // indices

            // 1. you need three indices to draw a single face
            // 2. a single segment consists of two faces
            // 3. so we need to generate six (2*3) indices per segment

            for ( let iy = 0; iy < gridY; iy ++ ) {

                for ( let ix = 0; ix < gridX; ix ++ ) {

                    const a = numberOfVertices + ix + gridX1 * iy;
                    const b = numberOfVertices + ix + gridX1 * ( iy + 1 );
                    const c = numberOfVertices + ( ix + 1 ) + gridX1 * ( iy + 1 );
                    const d = numberOfVertices + ( ix + 1 ) + gridX1 * iy;

                    // faces

                    indices.push( a, b, d );
                    indices.push( b, c, d );

                    // increase counter

                    groupCount += 6;

                }

            }

こちらも長いので少しずつ絞り込んで考えます。

124-126行目
            for ( let iy = 0; iy < gridY; iy ++ ) {

                for ( let ix = 0; ix < gridX; ix ++ ) {

インデックスは頂点ごとではなく、面ごとなので、分割数でループさせます。

128-131行目
                    const a = numberOfVertices + ix + gridX1 * iy;
                    const b = numberOfVertices + ix + gridX1 * ( iy + 1 );
                    const c = numberOfVertices + ( ix + 1 ) + gridX1 * ( iy + 1 );
                    const d = numberOfVertices + ( ix + 1 ) + gridX1 * iy;

numberOfVerticesには各面の頂点の合計値が加算されている変数で、
面ごとの頂点を算出する起点として使われているようです。

133-136行目
                    // faces

                    indices.push( a, b, d );
                    indices.push( b, c, d );

三角形を構成する頂点ごとにまとめてindeces変数に追加しています。
面を作るために三角形が二つ必要なので、ループ1回ごとに2つ追加されていきます。

138-140行目
                    // increase counter

                    groupCount += 6;

面を構成している三角形の頂点の数を加算しています。

146-148行目
            // add a group to the geometry. this will ensure multi material support

            scope.addGroup( groupStart, groupCount, materialIndex );

面ごとの頂点をグループとしてまとめています

150-152行目
            // calculate new start value for groups

            groupStart += groupCount;

各面で使用している頂点の合計値を加算して、次のグループが配列のどのindexから始まるかの基準としています。

154-156行目
            // update total number of vertices

            numberOfVertices += vertexCounter;

頂点の数を加算して、面ごとの頂点がどこから始まるかの基準としています。

まとめ

言葉がまとめられていない気がしますが、少しずつ理解を深めていければと思います。

a_haru
Webプログラミングの勉強をしています。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away