Source: PolygonEntity.js

import Entity from "./Entity";
import Primitive from "./Primitive";
import Mesh from "./Mesh";
import PolygonMaterial from "./PolygonMaterial";
import GeoMath from "./GeoMath";
import GeoPoint from "./GeoPoint";
import GeoRegion from "./GeoRegion";
import AltitudeMode from "./AltitudeMode";
import EntityRegion from "./EntityRegion";
import Triangulator from "./Triangulator";
import QAreaManager from "./QAreaManager";
import ConvexPolygon from "./ConvexPolygon";
import AreaUtil from "./AreaUtil";
import Type from "./animation/Type";
import { RenderTarget } from "./RenderStage";


/**
 * @summary 多角形エンティティ
 * @memberof mapray
 * @extends mapray.Entity
 */
class PolygonEntity extends Entity {

    /**
     * @param {mapray.Scene} scene        所属可能シーン
     * @param {object}       [opts]       オプション集合
     * @param {object}       [opts.json]  生成情報
     * @param {object}       [opts.refs]  参照辞書
     */
    constructor( scene, opts )
    {
        super( scene, opts );

        this._extruded_height = 0.0;
        this._color    = GeoMath.createVector3( [1, 1, 1] );
        this._opacity  = 1.0;

        // 頂点管理
        this._boundaries = [];    // Boundary のリスト
        this._position   = null;  // 中央付近の GeoPoint

        // this._producer
        // this._is_flake_mode
        if ( this.altitude_mode === AltitudeMode.CLAMP ) {
            this._producer = new FlakePrimitiveProducer( this );
            this._is_flake_mode = true;
        }
        else {
            this._producer = new PrimitiveProducer( this );
            this._is_flake_mode = false;
        }

        this._setupAnimationBindingBlock();

        // 生成情報から設定
        if ( opts && opts.json ) {
            this._setupByJson( opts.json );
        }
    }


    /**
     * @summary 押し出し量(0より大きい値)
     * @type {number}
     */
    set extruded_height( value )
    {
        var prev = this._extruded_height;

        if ( prev !== value ) {
            this._extruded_height = value;
            this._producer.onChangeExtruded();
        }
    }


    /**
     * @summary 押し出し量
     * @type {number}
     */
    get extruded_height() { return this._extruded_height; }


    /**
     * @override
     */
    getPrimitiveProducer()
    {
        return (!this._is_flake_mode) ? this._producer : null;
    }


    /**
     * @override
     */
    getFlakePrimitiveProducer()
    {
        return (this._is_flake_mode) ? this._producer : null;
    }


    /**
     * @override
     */
    onChangeAltitudeMode( prev_mode )
    {
        if ( this.altitude_mode === AltitudeMode.CLAMP ) {
            this._producer = new FlakePrimitiveProducer( this );
            this._is_flake_mode = true;
        }
        else {
            this._producer = new PrimitiveProducer( this );
            this._is_flake_mode = false;
        }
    }


    /**
     * アニメーションの BindingBlock を初期化
     *
     * @private
     */
    _setupAnimationBindingBlock()
    {
        const block = this.animation;  // 実体は EasyBindingBlock

        const number  = Type.find( "number"  );
        const vector3 = Type.find( "vector3" );

        // パラメータ名: color
        // パラメータ型: vector3
        //   色
        block.addEntry( "color", [vector3], null, value => {
            this.setColor( value );
        } );
        
        // パラメータ名: opacity
        // パラメータ型: number
        //   不透明度
        block.addEntry( "opacity", [number], null, value => {
            this.setOpacity( value );
        } );
        
        // パラメータ名: height
        // パラメータ型: number
        //   線の太さ
        block.addEntry( "height", [number], null, value => {
            this.setExtrudedHeight( value );
        } );
    }


    /**
     * @summary 基本色を設定
     * @param {mapray.Vector3} color  基本色
     */
    setColor( color )
    {
        if ( this._color[0] !== color[0] ||
             this._color[1] !== color[1] ||
             this._color[2] !== color[2] ) {
            GeoMath.copyVector3( color, this._color );
            this._producer.onChangeProperty();
        }
    }


    /**
     * @summary 不透明度を設定
     * @param {number} opacity  不透明度
     */
    setOpacity( opacity )
    {
        if ( this._opacity !== opacity ) {
            this._opacity = opacity;
            this._producer.onChangeProperty();
        }
    }


    /**
     * @summary 押し出し量を設定
     * @param {number} opacity  押し出し量
     */
    setExtrudedHeight( height )
    {
        this.extruded_height = height;
    }


    /**
     * @summary 外側境界を追加
     *
     * @desc
     * <p>points は [lon_0, lat_0, alt_0, lon_1, lat_1, alt_1, ...] のような形式で配列を与える。</p>
     *
     * @param {number[]} points  頂点の配列
     */
    addOuterBoundary( points )
    {
        this._addBoundary( points, false );
    }


    /**
     * @summary 内側境界を追加
     *
     * @desc
     * <p>points は [lon_0, lat_0, alt_0, lon_1, lat_1, alt_1, ...] のような形式で配列を与える。</p>
     *
     * @param {number[]} points  頂点の配列
     */
    addInnerBoundary( points )
    {
        this._addBoundary( points, true );
    }


    /**
     * @summary すべての頂点のバウンディングを算出
     *
     * @override
     * @return {mapray.GeoRegion}  バウンディング情報を持ったGeoRegion
     */
    getBounds()
    {
        const region = new GeoRegion();
        for ( let bo of this._boundaries ) {
            region.addPointsAsArray( bo.points );
        }
        return region;
    }

    /**
     * @summary 境界を追加
     *
     * @desc
     * <p>addOuterBoundary(), addInnerBoundary() の実装である。</p>
     *
     * @param {number[]} points  頂点の配列
     *
     * @private
     */
    _addBoundary( points, is_inner )
    {
        this._boundaries.push( new Boundary( points, is_inner ) );
        this._position = null;

        // 境界の変更を通知
        this._producer.onChangeBoundary();
    }


    /**
     * @summary 専用マテリアルを取得
     * @private
     */
    _getMaterial( render_target )
    {
        var scene = this.scene;
        if ( render_target === RenderTarget.SCENE ) {
            if ( !scene._PolygonEntity_material ) {
                // scene にマテリアルをキャッシュ
                scene._PolygonEntity_material = new PolygonMaterial( scene.glenv );
            }
            return scene._PolygonEntity_material;
        }
        else if (render_target === RenderTarget.RID) {
            if ( !scene._PolygonEntity_material_pick ) {
                // scene にマテリアルをキャッシュ
                scene._PolygonEntity_material_pick = new PolygonMaterial( scene.glenv, { ridMaterial: true } );
            }
            return scene._PolygonEntity_material_pick;
        }
    }


    /**
     * @private
     */
    _setupByJson( json )
    {
        // json.boundaries
        for ( let boundary of json.boundaries ) {
            if ( boundary.type == "inner" ) {
                this.addInnerBoundary( boundary.points );
            }
            else {
                this.addOuterBoundary( boundary.points );
            }
        }

        // json.extruded_height
        if ( json.extruded_height !== undefined ) {
            this.extruded_height = json.extruded_height;
        }

        // json.color
        //     .opacity
        if ( json.color   !== undefined ) GeoMath.copyVector3( json.color, this._color );
        if ( json.opacity !== undefined ) this._opacity = json.opacity;
    }


    /**
     * @summary 中央位置を取得
     *
     * @desc
     * <p>中央位置を計算して返す。多角形が存在しないときは null を返す。</p>
     *
     * <p>中央位置が変化する可能性があるときは this._position にを null を設定すること。</p>
     *
     * <pre>
     * 入力: this._boundaries
     * </pre>
     *
     * @return {mapray.GeoPoint}  中央位置 (高度は 0) または null
     *
     * @private
     */
    _getPosition()
    {
        if ( this._position !== null ) {
            // キャッシュさている値を返す
            return this._position;
        }

        if ( this._boundaries.length == 0 ) {
            // 多角形が存在しない
            return null;
        }

        var min_lon =  Number.MAX_VALUE;
        var max_lon = -Number.MAX_VALUE;
        var min_lat =  Number.MAX_VALUE;
        var max_lat = -Number.MAX_VALUE;

        for ( let bo of this._boundaries ) {
            let count  = bo.num_points;
            let points = bo.points;

            for ( let i = 0; i < count; ++i ) {
                var lon = points[3*i    ];
                var lat = points[3*i + 1];

                if ( lon < min_lon ) min_lon = lon;
                if ( lon > max_lon ) max_lon = lon;
                if ( lat < min_lat ) min_lat = lat;
                if ( lat > max_lat ) max_lat = lat;
            }
        }

        this._position = new GeoPoint( (min_lon + max_lon) / 2,
                                       (min_lat + max_lat) / 2 );

        return this._position;
    }


    /**
     * @summary すべての境界の頂点数の合計を取得
     *
     * @private
     */
    _countNumPointsOnBoundaries()
    {
        let num_points = 0;

        for ( let bo of this._boundaries ) {
            num_points += bo.num_points;
        }

        return num_points;
    }


    /**
     * @summary 結合された境界点列を取得
     *
     * @return {Float64Array}  結合された境界点列
     */
    _getCombinedBoundaryPoints()
    {
        let points = new Float64Array( 3 * this._countNumPointsOnBoundaries() );
        let offset = 0;

        for ( let bo of this._boundaries ) {
            points.set( bo.points, offset );
            offset += 3 * bo.num_points;
        }

        return points;
    }


    /**
     * @summary 結合された 2D 境界点列を取得 (高度なし)
     *
     * @return {Float64Array}  結合された 2D 境界点列
     */
    _getCombinedBoundary2DPoints()
    {
        let dst_points = new Float64Array( 2 * this._countNumPointsOnBoundaries() );
        let di = 0;

        for ( let bo of this._boundaries ) {
            let src_size   = 3 * bo.num_points;
            let src_points = bo.points;
            for ( let si = 0; si < src_size; si += 3 ) {
                dst_points[di++] = src_points[si    ];
                dst_points[di++] = src_points[si + 1];
            }
        }

        return dst_points;
    }


    /**
     * @summary 三角形リストを生成
     *
     * @desc
     * <p>this.entity._boundaries を三角形に変換してリストを返す。ただし変換に失敗したときは null を返す。</p>
     *
     * @return {Uint32Array}  三角形リストまたは null
     *
     * @private
     */
    _createTriangles()
    {
        let src_points     = this._getCombinedBoundary2DPoints();
        let num_src_points = this._countNumPointsOnBoundaries();

        // 境界を登録
        let triangulator = new Triangulator( src_points, 0, 2, num_src_points );
        let index = 0;

        for ( let bo of this._boundaries ) {
            let num_indices = bo.num_points;
            let indices     = new Uint32Array( num_indices );
            for ( let i = 0; i < num_indices; ++i ) {
                indices[i] = index++;
            }
            triangulator.addBoundary( indices );
        }

        try {
            // 変換を実行
            return triangulator.run();
        }
        catch ( e ) {
            // 変換に失敗
            console.error( e.message );
            return null;
        }
    }

}


/**
 * @summary PolygonEntity の PrimitiveProducer
 *
 * @private
 */
class PrimitiveProducer extends Entity.PrimitiveProducer {


    /**
     * @param {mapray.PolygonEntity} entity
     */
    constructor( entity )
    {
        super( entity );

        this._status    = Status.INVALID;
        this._triangles = null;  // 三角形リスト (Uint32Array)

        // プリミティブの要素
        this._transform  = GeoMath.setIdentity( GeoMath.createMatrix() );
        this._pivot      = GeoMath.createVector3();
        this._bbox       = [GeoMath.createVector3(),
                            GeoMath.createVector3()];
        this._properties = {
            color:    GeoMath.createVector3f(),
            opacity:  1.0,
            lighting: false
        };

        // プリミティブ
        var primitive = new Primitive( entity.glenv, null, entity._getMaterial( RenderTarget.SCENE ), this._transform );
        primitive.pivot      = this._pivot;
        primitive.bbox       = this._bbox;
        primitive.properties = this._properties;

        this._primitive = primitive;

        var pickPrimitive = new Primitive( entity.glenv, null, entity._getMaterial( RenderTarget.RID ), this._transform );
        pickPrimitive.pivot      = this._pivot;
        pickPrimitive.bbox       = this._bbox;
        pickPrimitive.properties = this._properties;

        this._pickPrimitive = pickPrimitive;
    }


    /**
     * @override
     */
    needsElevation()
    {
        const owner = this.entity;
        return owner.altitude_mode !== AltitudeMode.ABSOLUTE;
    }


    /**
     * @override
     */
    createRegions()
    {
        let owner = this.entity;

        if ( this._status === Status.INVALID ) {
            // 多角形なし、または三角形に変換できなかったとき
            return [];
        }

        // 正常な多角形のとき

        var region = new EntityRegion();

        for ( let bo of owner._boundaries ) {
            region.addPoints( bo.points, 0, 3, bo.num_points );
        }

        region.addPoint( owner._getPosition() );

        return [region];
    }


    /**
     * @override
     */
    onChangeElevation( regions )
    {
        if ( this._status === Status.NORMAL ) {
            this._status = Status.MESH_DIRTY;
        }
    }


    /**
     * @override
     */
    getPrimitives( stage )
    {
        if ( this._status === Status.INVALID ) {
            // 多角形なし、または三角形に変換できなかったとき
            return [];
        }
        else if ( this._status === Status.TRIANGLE_DIRTY ) {
            this._triangles = this.entity._createTriangles();
            if ( this._triangles === null ) {
                // 多角形の三角形化に失敗
                this._primitive.mesh = null;
                this._pickPrimitive.mesh = null;
                this._status = Status.INVALID;
                return [];
            }
            this._updatePrimitiveMesh();
        }
        else if ( this._status === Status.MESH_DIRTY ) {
            this._updatePrimitiveMesh();
        }

        this._updatePrimitiveProperties();

        this._status = Status.NORMAL;
        return stage.getRenderTarget() === RenderTarget.SCENE ? [this._primitive] : [this._pickPrimitive];
    }


    /**
     * @summary 押し出しモードが変更されたことを通知
     */
    onChangeExtruded()
    {
        if ( this._status === Status.NORMAL ) {
            this._status = Status.MESH_DIRTY;
        }
    }


    /**
     * @summary プロパティが変更されたことを通知
     */
    onChangeProperty()
    {
        // することなし
    }


    /**
     * @summary 境界が変更されたことを通知
     */
    onChangeBoundary()
    {
        this._status    = Status.TRIANGLE_DIRTY;
        this._triangles = null;
        this.needToCreateRegions();
    }


    /**
     * @summary プリミティブの更新
     *
     * 入力:
     *   this.entity
     *   this._triangles
     * 出力:
     *   this._transform
     *   this._pivot
     *   this._bbox
     *   this._primitive.mesh
     *
     * @private
     */
    _updatePrimitiveMesh()
    {
        var cb_data = new BoundaryConbiner( this.entity );

        // プリミティブの更新
        //   primitive.transform
        //   primitive.pivot
        //   primitive.bbox
        this._updateTransformPivotBBox( cb_data );

        // メッシュ生成
        var mesh_data = {
            vtype: [
                { name: "a_position", size: 3 },
                { name: "a_normal",   size: 3 }
            ],
            vertices: this._createVertices( cb_data ),
            indices:  this._createIndices( cb_data )
        };
        var mesh = new Mesh( this.entity.scene.glenv, mesh_data );

        // メッシュ設定
        this._primitive.mesh = mesh;
        this._pickPrimitive.mesh = mesh;
    }


    /**
     * @summary プリミティブの更新
     *
     * @desc
     * <pre>
     * 出力:
     *   this._transform
     *   this._pivot
     *   this._bbox
     * </pre>
     *
     * @param {BoundaryConbiner} cb_data  入力データ
     *
     * @private
     */
    _updateTransformPivotBBox( cb_data )
    {
        // 変換行列の更新
        let transform = this._transform;
        transform[12] = cb_data.origin[0];
        transform[13] = cb_data.origin[1];
        transform[14] = cb_data.origin[2];

        // 統計
        let xmin = Number.MAX_VALUE;
        let ymin = Number.MAX_VALUE;
        let zmin = Number.MAX_VALUE;

        let xmax = -Number.MAX_VALUE;
        let ymax = -Number.MAX_VALUE;
        let zmax = -Number.MAX_VALUE;

        // [cb_data.upper, cb_data.lower]
        let points_array = [cb_data.upper];
        if ( cb_data.lower ) {
            points_array.push( cb_data.lower );
        }

        for ( let j = 0; j < points_array.length; ++j ) {
            let points = points_array[j];
            for ( let i = 0; i < cb_data.num_points; ++i ) {
                let b = 3 * i;
                let x = points[b    ];
                let y = points[b + 1];
                let z = points[b + 2];

                if ( x < xmin ) { xmin = x; }
                if ( y < ymin ) { ymin = y; }
                if ( z < zmin ) { zmin = z; }

                if ( x > xmax ) { xmax = x; }
                if ( y > ymax ) { ymax = y; }
                if ( z > zmax ) { zmax = z; }
            }
        }

        // 中心点
        let pivot = this._pivot;
        pivot[0] = (xmin + xmax) / 2;
        pivot[1] = (ymin + ymax) / 2;
        pivot[2] = (zmin + zmax) / 2;

        // 境界箱
        let bbox  = this._bbox;
        let bmin = bbox[0];
        let bmax = bbox[1];
        bmin[0] = xmin;
        bmin[1] = ymin;
        bmin[2] = zmin;
        bmax[0] = xmax;
        bmax[1] = ymax;
        bmax[2] = zmax;
    }


    /**
     * @summary 頂点配列の生成
     *
     * @desc
     * 生成される形式は [Px, Py, Pz, Nx, Ny, Nz, ...] のような形で、それぞれの座標はローカル座標系になる。
     * 配列の頂点データは 2 つの領域で分かれ、上面ポリゴンの頂点配列(S1) → 側面ポリゴンの頂点配列(S2) の順序で格納される。
     * ただし cb_data.lower == null のとき、配列は S1 部分しか設定されない。
     *
     * S1 は cb_data.upper に対応する頂点データが同じ順序で格納される。
     *
     * S2 は cb_data.num_points 個の四角形に対する頂点データが順番に並んでいる。
     * 各四角形の頂点データは 左下、右下、左上、右上 の順序で格納されている。
     *
     * 入力: this.entity._boundaries
     *
     * @param {BoundaryConbiner} cb_data  入力データ
     *
     * @return {Float32Array}  Mesh 用の頂点配列
     *
     * @private
     */
    _createVertices( cb_data )
    {
        const fpv = 6;  // 1頂点データあたりの float 数

        const s1_num_floats = fpv * cb_data.num_points;                          // 上面のデータサイズ
        const s2_num_floats = cb_data.lower ? fpv * (4*cb_data.num_points) : 0;  // 側面のデータサイズ
        const s3_num_floats = cb_data.lower ? s1_num_floats : 0;                 // 底面のデータサイズ

        let vertices = new Float32Array( s1_num_floats + s2_num_floats + s3_num_floats );

        // 上面の法線を取得
        let unormal = GeoMath.normalize3( cb_data.origin, GeoMath.createVector3() );

        // 上面の頂点データ
        for ( let i = 0; i < cb_data.num_points; ++i ) {
            let b  = 3 * i;
            let px = cb_data.upper[b];
            let py = cb_data.upper[b + 1];
            let pz = cb_data.upper[b + 2];

            let vi = fpv * i;
            vertices[vi    ] = px;  // a_position.x
            vertices[vi + 1] = py;  // a_position.y
            vertices[vi + 2] = pz;  // a_position.z
            setVec3ToArray( unormal, vertices, vi + 3 );  // a_normal
        }

        // 側面の頂点データ
        if ( cb_data.lower ) {
            let p00 = GeoMath.createVector3();  // 左下位置
            let p10 = GeoMath.createVector3();  // 右下位置
            let p01 = GeoMath.createVector3();  // 左上位置
            let p11 = GeoMath.createVector3();  // 右上位置
            let snormal = GeoMath.createVector3();  // 側面の法線

            let beg_i = 0;  // bo の最初の頂点のインデックス

            for ( let bo of this.entity._boundaries ) {
                let end_i = beg_i + bo.num_points;  // bo の最後の頂点のインデックス + 1

                for ( let i = beg_i; i < end_i; ++i ) {
                    let i0 = i;
                    let i1 = (i + 1 < end_i) ? i + 1 : beg_i;

                    // 四隅の位置を取得
                    let b0 = 3 * i0;
                    let b1 = 3 * i1;
                    setArrayToVec3( cb_data.lower, b0, p00 );
                    setArrayToVec3( cb_data.lower, b1, p10 );
                    setArrayToVec3( cb_data.upper, b0, p01 );
                    setArrayToVec3( cb_data.upper, b1, p11 );

                    // 側面の法線を取得
                    setTriangleNormal( p00, p10, p01, snormal );

                    // 四隅の頂点データを設定
                    let vi = s1_num_floats + 4*fpv*i;
                    setVec3ToArray( p00,     vertices, vi     );  // a_position
                    setVec3ToArray( snormal, vertices, vi + 3 );  // a_normal
                    vi += fpv;
                    setVec3ToArray( p10,     vertices, vi     );  // a_position
                    setVec3ToArray( snormal, vertices, vi + 3 );  // a_normal
                    vi += fpv;
                    setVec3ToArray( p01,     vertices, vi     );  // a_position
                    setVec3ToArray( snormal, vertices, vi + 3 );  // a_normal
                    vi += fpv;
                    setVec3ToArray( p11,     vertices, vi     );  // a_position
                    setVec3ToArray( snormal, vertices, vi + 3 );  // a_normal
                }

                beg_i = end_i;
            }
        }

        if ( cb_data.lower ) {
            const bnormal = GeoMath.scale3( -1.0, unormal, GeoMath.createVector3() );

            // 底面の頂点データ
            for ( let i = 0; i < cb_data.num_points; ++i ) {
                let b  = 3 * i;
                let px = cb_data.lower[b];
                let py = cb_data.lower[b + 1];
                let pz = cb_data.lower[b + 2];

                let vi = s1_num_floats + s2_num_floats + fpv * i;
                vertices[vi    ] = px;  // a_position.x
                vertices[vi + 1] = py;  // a_position.y
                vertices[vi + 2] = pz;  // a_position.z
                setVec3ToArray( bnormal, vertices, vi + 3 );  // a_normal
            }
        }

        return vertices;
    }


    /**
     * @summary インデックス配列の生成
     *
     * 入力: this._triangles
     *
     * @param {BoundaryConbiner} cb_data  入力データ
     *
     * @return {Uint32Array}  インデックス配列
     *
     * @private
     */
    _createIndices( cb_data )
    {
        // 頂点の並びは _createVertices() を参照

        let num_upper_triangles = this._triangles.length / 3;
        let num_side_triangles  = cb_data.lower ? 2 * cb_data.num_points : 0;
        let num_bottom_triangles  = cb_data.lower ? num_upper_triangles : 0;

        let indices = new Uint32Array( 3 * (num_upper_triangles + num_side_triangles + num_bottom_triangles) );

        // 前半に上面のポリゴンを設定
        indices.set( this._triangles );

        // 側面のポリゴンを設定
        if ( cb_data.lower ) {
            let num_quads = cb_data.num_points;
            let   ioffset = 3 * num_upper_triangles;  // indices 内の現在の四角形のオフセット
            let   voffset = cb_data.num_points;       // 頂点配列内の現在の四角形のオフセット

            for ( let i = 0; i < num_quads; ++i, ioffset += 6, voffset += 4 ) {
                // 左下三角形
                indices[ioffset    ] = voffset;
                indices[ioffset + 1] = voffset + 1;
                indices[ioffset + 2] = voffset + 2;
                // 右上三角形
                indices[ioffset + 3] = voffset + 2;
                indices[ioffset + 4] = voffset + 1;
                indices[ioffset + 5] = voffset + 3;
            }
        }

        // 底面のポリゴンを設定
        if ( cb_data.lower ) {
            const len =  this._triangles.length / 3;
            const voffset = cb_data.num_points + 4 * cb_data.num_points;
            for ( let i = 0; i < len; ++i ) {
                indices[ (num_upper_triangles + num_side_triangles + i) * 3 + 0 ] = this._triangles[ i * 3 + 0 ] + voffset;
                indices[ (num_upper_triangles + num_side_triangles + i) * 3 + 1 ] = this._triangles[ i * 3 + 2 ] + voffset;
                indices[ (num_upper_triangles + num_side_triangles + i) * 3 + 2 ] = this._triangles[ i * 3 + 1 ] + voffset;
            }
        }

        return indices;
    }


    /**
     * @summary プリミティブのプロパティを更新
     *
     * 入力: this.entity
     * 出力: this._properties
     *
     * @private
     */
    _updatePrimitiveProperties()
    {
        let owner = this.entity;
        let props = this._properties;

        GeoMath.copyVector3( owner._color, props.color );
        props.opacity  = owner._opacity;
        props.lighting = this.extruded_height !== 0.0;
    }

}


/**
 * @summary PolygonEntity の FlakePrimitiveProducer
 *
 * @private
 */
class FlakePrimitiveProducer extends Entity.FlakePrimitiveProducer {

    /**
     * @param {mapray.PolygonEntity} entity
     */
    constructor( entity )
    {
        super( entity );

        this._material_map = Object.keys(RenderTarget).reduce((map, key) => {
                const render_target = RenderTarget[key];
                map.set( render_target, entity._getMaterial( render_target ) );
                return map;
        }, new Map());
        this._properties   = null;
        this._area_manager = new PolygonAreaManager( entity );
    }


    /**
     * @override
     */
    getAreaStatus( area )
    {
        return this._area_manager.getAreaStatus( area );
    }


    /**
     * @override
     */
    createMesh( area, dpows, dem )
    {
        // ConvexPolygon の配列、または Entity.AreaStatus.FULL
        let polygons = this._area_manager.getAreaContent( area );

        let msize = Math.PI * Math.pow( 2, 1 - area.z );
        let x_min = area.x * msize - Math.PI;
        let y_min = Math.PI - (area.y + 1) * msize;

        let div_x = 1 << dpows[0];
        let div_y = 1 << dpows[1];

        // サブメッシュの配列を生成
        let submeshes = this._createSubmeshes( x_min, y_min,
                                               x_min + msize, y_min + msize,
                                               div_x, div_y,
                                               polygons );

        // メッシュ生成
        let mesh_data = {
            vtype: [
                { name: "a_position", size: 3 },
                { name: "a_normal",   size: 3 }
            ],
            vertices: this._createVertices( submeshes, area, dem ),
            indices:  this._createIndices( submeshes )
        };

        return new Mesh( this.entity.scene.glenv, mesh_data );
    }


    /**
     * @override
     */
    getMaterialAndProperties( stage )
    {
        if ( this._properties === null ) {
            let entity = this.entity;

            this._properties = {
                color:    GeoMath.createVector3f( entity._color ),
                opacity:  entity._opacity,
                lighting: false
            };
        }

        return {
            material:     this._material_map.get( stage.getRenderTarget() ),
            properties:   this._properties
        };
    }


    /**
     * @summary 押し出しモードが変更されたことを通知
     */
    onChangeExtruded()
    {
        // flake_mode なので押し出しモードは関係ない
    }


    /**
     * @summary プロパティが変更されたことを通知
     */
    onChangeProperty()
    {
        this._properties = null;
    }


    /**
     * @summary 境界が変更されたことを通知
     */
    onChangeBoundary()
    {
        this._area_manager.notifyForUpdateContent();
        this.notifyForUpdate();
    }


    /**
     * @summary 頂点配列を生成
     *
     * @param {iterable.<Submesh>} submeshes
     * @param {mapray.Area}        area
     * @param {mapray.DemBinary}   dem
     *
     * @return {Float32Array}
     *
     * @private
     */
    _createVertices( submeshes, area, dem )
    {
        let  origin = AreaUtil.getCenter( area, GeoMath.createVector3() );
        let sampler = dem.newSampler( area );

        // 頂点配列を生成
        let num_vertices = 0;
        for ( let smesh of submeshes ) {
            num_vertices += smesh.getNumVertices();
        }
        let vertices = new Float32Array( 6 * num_vertices );

        // 頂点配列に座標を書き込む
        let offset = 0;
        for ( let smesh of submeshes ) {
            offset = smesh.addVertices( origin, sampler, vertices, offset );
        }

        return vertices;
    }


    /**
     * @summary インデックス配列を生成
     *
     * @param {iterable.<Submesh>} submeshes
     *
     * @return {Uint32Array}
     *
     * @private
     */
    _createIndices( submeshes )
    {
        // インデックス配列を生成
        let num_triangles = 0;
        for ( let smesh of submeshes ) {
            num_triangles += smesh.getNumTriangles();
        }
        let indices = new Uint32Array( 3 * num_triangles );

        // インデックス配列にインデックスを書き込む
        let voffset = 0;
        let ioffset = 0;
        for ( let smesh of submeshes ) {
            ioffset = smesh.addIndices( voffset, indices, ioffset );
            voffset += smesh.getNumVertices();
        }

        return indices;
    }


    /**
     * @summary サブメッシュの配列を生成
     *
     * <p>polygons は領域と交差する ConvexPolygon の配列である。ただし領域が多角形で覆われているときは
     * Entity.AreaStatus.FULL になる場合がある。</p>
     *
     * @param {number} x_min  領域の最小 x 座標
     * @param {number} y_min  領域の最小 y 座標
     * @param {number} x_max  領域の最大 x 座標
     * @param {number} y_max  領域の最大 y 座標
     * @param {number} div_x  領域の x 方向の分割数
     * @param {number} div_y  領域の y 方向の分割数
     * @param {iterable.<mapray.ConvexPolygon>|mapray.Entity.AreaStatus} polygons
     *
     * @return {iterable.<Submesh>}  サブメッシュの配列
     *
     * @private
     */
    _createSubmeshes( x_min, y_min, x_max, y_max, div_x, div_y, polygons )
    {
        if ( polygons === Entity.AreaStatus.FULL ) {
            // 領域内は多角形に覆われている
            return [ new RectSubmesh( x_min, y_min, x_max, y_max, div_x, div_y ) ];
        }
        else if ( polygons.length == 0 ) {
            // 領域内に多角形は無い
            return [];
        }
        else if ( div_x == 1 && div_y == 1 ) {
            // これ以上分割できないので切り取り多角形を返す
            let t1 = [x_min, y_min, x_max, y_min, x_min, y_max];  // 左下三角形
            let t2 = [x_min, y_max, x_max, y_min, x_max, y_max];  // 右上三角形

            let m1 = this._create_clipped_polygons_submeshes( t1, polygons );
            let m2 = this._create_clipped_polygons_submeshes( t2, polygons );
            return m1.concat( m2 );
        }
        else {
            if ( div_x >= div_y ) {
                // 左右分割
                let msize = (x_max - x_min) / 2;
                let div_w = div_x / 2;

                let m1 = this._create_submeshes_sp( x_min,         y_min, x_min + msize, y_max, div_w, div_y, polygons );
                let m2 = this._create_submeshes_sp( x_min + msize, y_min, x_max,         y_max, div_w, div_y, polygons );
                return m1.concat( m2 );
            }
            else {
                // 上下分割
                let msize = (y_max - y_min) / 2;
                let div_w = div_y / 2;

                let m1 = this._create_submeshes_sp( x_min, y_min,         x_max, y_min + msize, div_x, div_w, polygons );
                let m2 = this._create_submeshes_sp( x_min, y_min + msize, x_max, y_max,         div_x, div_w, polygons );
                return m1.concat( m2 );
            }
        }
    }


    /**
     * @summary サブメッシュの配列を生成
     *
     * @desc
     * <p>_createSubmeshes() との違いは polygons に Entity.AreaStatus.FULL を指定できない。
     * また、polygons には領域の外側の多角形が含まれている可能性がある。</p>
     *
     * @param {number} x_min  領域の最小 x 座標
     * @param {number} y_min  領域の最小 y 座標
     * @param {number} x_max  領域の最大 x 座標
     * @param {number} y_max  領域の最大 y 座標
     * @param {number} div_x  領域の x 方向の分割数
     * @param {number} div_y  領域の y 方向の分割数
     * @param {iterable.<mapray.ConvexPolygon>} polygons
     *
     * @return {Submesh[]}  サブメッシュの配列
     *
     * @private
     */
    _create_submeshes_sp( x_min, y_min, x_max, y_max, div_x, div_y, polygons )
    {
        // 領域を凸多角形に変換
        let area_rect = ConvexPolygon.createByRectangle( x_min, y_min, x_max, y_max );

        let selected_polygons = [];

        for ( let polygon of polygons ) {
            if ( polygon.includes( area_rect ) ) {
                // polygon は area_rect を覆う
                // つまり area_rect は全体の多角形に覆われている
                selected_polygons = Entity.AreaStatus.FULL;
                break;
            }

            try {
                if ( area_rect.hasIntersection( polygon ) ) {
                    // 領域と交差しているので polygon 追加
                    selected_polygons.push( polygon );
                }
            }
            catch ( e ) {
                // polygon の交差判定に失敗したときは polygon は無いことにする
            }
        }

        return this._createSubmeshes( x_min, y_min, x_max, y_max, div_x, div_y, selected_polygons );
    }


    /**
     * @summary 凸多角形のサブメッシュの配列を生成
     * 
     * @desc
     * <p>area_triangle の三角形で src_polygons の凸多角形を切り取り、それらの切り取られた凸多角形に対応する
     * PolygonsSubmesh インスタンスの配列を生成する。</p>
     *
     * arit = Area Right Isosceles Triangle (領域直角二等辺三角形)
     *
     * @param {number[]}                        arit_coords   領域の三角形座標配列 (左下または右上の三角形)
     * @param {iterable.<mapray.ConvexPolygon>} src_polygons  切り取り対象の凸多角形の配列
     *
     * @return {PolygonsSubmesh[]}  PolygonsSubmesh の配列
     *
     * @private
     */
    _create_clipped_polygons_submeshes( arit_coords, src_polygons )
    {
        let area_polygon = new ConvexPolygon( arit_coords );

        let clipped_polygons = [];

        for ( let polygon of src_polygons ) {
            try {
                let clipped = area_polygon.getIntersection( polygon );
                if ( clipped !== null ) {
                    clipped_polygons.push( clipped );
                }
            }
            catch ( e ) {
                // polygon の切り抜きに失敗したときは polygon の切り抜きは返さない
            }
        }

        if ( clipped_polygons.length > 0 ) {
            return [ new PolygonsSubmesh( arit_coords, clipped_polygons ) ];
        }
        else {
            return [];
        }
    }

}


/**
 * @summary 多角形の境界
 *
 * @classdesc
 * <p>多角形の1つの境界を表現する。</p>
 * <p>外側境界のときは反時計回り、内側境界のときは時計回りで格納される。</p>
 *
 * @private
 */
class Boundary {

    /**
     * @desc
     * <p>points は addOuterBoundary(), addInnerBoundary() と同じ形式である。</p>
     *
     * @param {number[]} points    境界の頂点データ
     * @param {boolean}  is_inner  内側境界か?
     */
    constructor( points, is_inner )
    {
        let num_points = Math.floor( points.length / 3 );

        this._points     = new Float64Array( 3 * num_points );
        this._num_points = num_points;

        let is_ccw = Boundary.isCCW( points, num_points );

        let si;
        let si_step;

        if ( (!is_inner && is_ccw) || (is_inner && !is_ccw) ) {
            // 順方向
            si      = 0;
            si_step = 3;
        }
        else {
            // 逆方向
            si      = 3 * (num_points - 1);
            si_step = -3;
        }

        // 内部の配列にコピー
        for ( let i = 0; i < num_points; ++i ) {
            this._points[3*i    ] = points[si    ];
            this._points[3*i + 1] = points[si + 1];
            this._points[3*i + 2] = points[si + 2];
            si += si_step;
        }
    }


    /**
     * @summary 頂点座標の配列
     * @type {number[]}
     * @readonly
     */
    get points() { return this._points; }


    /**
     * @summary 頂点数
     * @type {number}
     * @readonly
     */
    get num_points() { return this._num_points; }


    /**
     * @summary 境界は反時計回りか?
     *
     * @param {number[]} points  境界の頂点データ
     *
     * @return {boolean}  反時計回りのとき true, それ以外のとき false
     */
    static
    isCCW( points, num_points )
    {
        // 頂上の点、同じ高さなら左側優先
        let top_i;
        let top_x = -Number.MAX_VALUE;
        let top_y = -Number.MAX_VALUE;

        for ( let i = 0; i < num_points; ++i ) {
            let x = points[3*i    ];
            let y = points[3*i + 1];
            if ( (y > top_y) || (y == top_y && x < top_x)) {
                top_i = i;
                top_x = x;
                top_y = y;
            }
        }

        // top の前方の点
        let next_i = (top_i == num_points - 1) ? 0 : top_i + 1;
        let next_x = points[3*next_i    ];
        let next_y = points[3*next_i + 1];

        // top の後方の点
        let prev_i = (top_i == 0) ? num_points - 1 : top_i - 1;
        let prev_x = points[3*prev_i    ];
        let prev_y = points[3*prev_i + 1];

        // prev と next は top より下または同じ高さだが、少なくともどちらか一方は top より下になる
        // またエッジは交差しないことが前提なので、2 つのエッジの内角は 0 度より大きく 180 度未満になる
        // したがって a, b の行列式が正のとき反時計回り、それ以外のとき時計回り
        let ax = next_x - top_x;
        let ay = next_y - top_y;
        let bx = prev_x - top_x;
        let by = prev_y - top_y;

        return ax*by - bx*ay > 0;
    }

}


/**
 * @summary 境界線データを結合
 *
 * @classdesc
 * <p>pe._bounaries に対応する上頂点と底頂点の LOCS 平坦化配列を取得する。</p>
 * <p>pe._extruded_height === 0 のときは lower に null を設定する。</p>
 *
 * <pre>
 * プロパティ:
 *   origin: Vector3       // LOCS の原点位置 (GOCS)
 *   num_points: number    // upper の頂点数
 *   upper: Float64Array   // 上頂点 (LOCS, 順序は pe._bounaries.points の連結)
 *   lower: Float64Array   // 底頂点 (LOCS, 順序は upper と同じ, nullable)
 * </pre>
 *
 * @private
 */
class BoundaryConbiner {

    /**
     * @desc
     * <pre>
     * 入力:
     *   pe.viewer
     *   pe.altitude_mode
     *   pe._extruded_height
     *   pe._bounaries
     * </pre>
     *
     * @param {mapray.PolygonEntity} pe  呼び出し側のポリゴンエンティティ
     */
    constructor( pe )
    {
        /*
        pe._extruded_height !== 0             == 0    
                                                      
                    ---  _.-*---*._        _.-*---*._ 
        upper_points    *-_      _-*      *-_      _-*
                    --- |  *----*  |         *----*   
                        |  |    |  |                  
                    --- |  |    |  |                  
        lower_points    *-_|    |_-*         (null)   
                    ---    *----*                     
        */
        let        viewer = pe.scene.viewer;
        let altitude_mode = pe.altitude_mode;

        let src_points = pe._getCombinedBoundaryPoints();
        let num_points = pe._countNumPointsOnBoundaries();

        let base_points = Float64Array.from( src_points );

        if ( altitude_mode === AltitudeMode.RELATIVE ) {
            let elevation = viewer.getExistingElevation( pe._getPosition() );
            for ( let i = 0; i < num_points; ++i ) {
                let ai = 3 * i + 2;
                base_points[ai] +=  elevation;
            }
        }

        let upper_points = null;
        let lower_points = null;
        if ( pe._extruded_height !== 0 ) {
            if ( altitude_mode === AltitudeMode.CLAMP ) {
                upper_points = base_points;
                lower_points = Float64Array.from( src_points );
                for ( let i = 0; i < num_points; ++i ) {
                    let ai = 3 * i + 2;
                    lower_points[ai] = 0;
                }
            }
            else { // altitude_mode !== AltitudeMode.ABSOLUTE || altitude_mode !== AltitudeMode.RELATIVE
                lower_points = base_points;
                upper_points = Float64Array.from( src_points );
                for ( let i = 0; i < num_points; ++i ) {
                    let ai = 3 * i + 2;
                    upper_points[ai] = lower_points[ai] + pe._extruded_height;
                }
            }
        }
        else {
            upper_points = base_points;
        }

        let origin = pe._getPosition().getAsGocs( GeoMath.createVector3() );

        // LOCS 平坦化配列
        let upper_ocs_points = GeoPoint.toGocsArray( upper_points, num_points,
                                                      new Float64Array( 3 * num_points ) );
        for ( let i = 0; i < num_points; ++i ) {
            let d = 3 * i;
            upper_ocs_points[d    ] -= origin[0];
            upper_ocs_points[d + 1] -= origin[1];
            upper_ocs_points[d + 2] -= origin[2];
        }

        let lower_ocs_points = null;
        if ( lower_points ) {
            // ASSERT: lower_points != null
            lower_ocs_points = GeoPoint.toGocsArray( lower_points, num_points,
                                                      new Float64Array( 3 * num_points ) );
            for ( let i = 0; i < num_points; ++i ) {
                let d = 3 * i;
                lower_ocs_points[d    ] -= origin[0];
                lower_ocs_points[d + 1] -= origin[1];
                lower_ocs_points[d + 2] -= origin[2];
            }
        }

        // プロパティを設定
        this.origin     = origin;
        this.num_points = num_points;
        this.upper      = upper_ocs_points;
        this.lower      = lower_ocs_points;
    }

}


/**
 * @summary 多角形の領域管理
 *
 * @private
 */
class PolygonAreaManager extends QAreaManager {

    /**
     * @param {mapray.PolygonEntity} entity  管理対象のエンティティ
     */
    constructor( entity )
    {
        super();

        this._entity = entity;
    }


    /**
     * @override
     */
    getInitialContent()
    {
        let     src_indices = this._entity._createTriangles() || [];
        let num_src_indices = src_indices.length;

        let src_coords = this._entity._getCombinedBoundary2DPoints();
        let    content = [];  // ConvexPolygon の配列

        for ( let si = 0; si < num_src_indices; si += 3 ) {
            let i0 = src_indices[si    ];
            let i1 = src_indices[si + 1];
            let i2 = src_indices[si + 2];
            this._add_polygon_to_array( src_coords, i0, i1, i2, content );
        }

        return content;
    }


    /**
     * @override
     */
    createAreaContent( min_x, min_y, msize, parent_content )
    {
        // 単位球メルカトルでの領域に変換
        const x_area_min = Math.PI * min_x;
        const y_area_min = Math.PI * min_y;
        const x_area_max = Math.PI * (min_x + msize);
        const y_area_max = Math.PI * (min_y + msize);

        // 領域を凸多角形に変換
        const area_rect = ConvexPolygon.createByRectangle( x_area_min, y_area_min,
                                                           x_area_max, y_area_max );

        let content = [];  // ConvexPolygon の配列

        for ( let polygon of parent_content ) {

            if ( polygon.includes( area_rect ) ) {
                // polygon は area_rect を覆う
                // つまり area_rect は全体の多角形に覆われている
                return Entity.AreaStatus.FULL;
            }

            try {
                if ( area_rect.hasIntersection( polygon ) ) {
                    // 領域と交差しているので polygon 追加
                    content.push( polygon );
                }
            }
            catch ( e ) {
                // polygon の交差判定に失敗したときは polygon は無いことにする
            }
        }

        return (content.length > 0) ? content : Entity.AreaStatus.EMPTY;
    }


    /**
     * @summary 三角形を凸多角形として追加
     *
     * @param {number[]} src_coords  入力頂点の座標配列 (経緯度)
     * @param {number}   si0         三角形の頂点 0
     * @param {number}   si1         三角形の頂点 1
     * @param {number}   si2         三角形の頂点 2
     * @param {mapray.ConvexPolygon[]} dst_polygons  出力先の ConvexPolygon 配列
     *
     * @private
     */
    _add_polygon_to_array( src_coords, si0, si1, si2, dst_polygons )
    {
        const Degree = GeoMath.DEGREE;
        const RAngle = Math.PI / 2;  // 直角
        const TwoPI  = 2 * Math.PI;  // 2π

        // 三角形の頂点座標配列 (単位球メルカトル座標系) を作成
        let vertices = [];
        let mx_min_1 = Number.MAX_VALUE;  // オフセット処理前の最小 mx 座標

        for ( let si of [si0, si1, si2] ) {
            let lon = src_coords[2*si    ] * Degree;
            let lat = src_coords[2*si + 1] * Degree;

            if ( Math.abs( lat ) >= RAngle ) {
                // 緯度の絶対値が RAngle 以上の頂点が存在する三角形は除外
                // ※ まだ検討していないので、とりあえずの処置
                return;
            }

            let mx = lon;
            let my = GeoMath.invGudermannian( lat );

            vertices.push( mx );
            vertices.push( my );

            mx_min_1 = Math.min( mx, mx_min_1 );
        }

        // mx_min_2: mx 座標が mx_min_1 だった頂点のオフセット後の mx 座標
        let mx_min_2 = mx_min_1 - TwoPI * (Math.floor( (mx_min_1 - Math.PI) / TwoPI ) + 1);
        if ( mx_min_2 < -Math.PI || mx_min_2 >= Math.PI ) {
            // 数値計算誤差により稀に区間からはみ出る可能性があるので
            mx_min_2 = -Math.PI;
        }
        // Assert: -Math.PI <= mx_min_2 < Math.PI

        // mx 座標にオフセットを適用
        let mx_max_2 = -Number.MAX_VALUE;  // オフセット後の最大 mx 座標

        for ( let i = 0; i < 3; ++i ) {
            let ix   = 2 * i;
            let mx_1 = vertices[ix];  // オフセット前の mx 座標

            // mx_2: オフセット後の mx 座標
            let dx_1 = mx_1 - mx_min_1;  // Assert: dx_1 >= 0
            let mx_2 = mx_min_2 + dx_1;  // Assert: mx_2 >= mx_min_2
            // Assert: (mx_1 == mx_min_1) ⇒ (mx_2 == mx_min_2)

            vertices[ix] = mx_2;

            mx_max_2 = Math.max( mx_2, mx_max_2 );
        }

        // オフセットを適用した三角形を加える
        dst_polygons.push( new ConvexPolygon( vertices ) );

        // 三角形が 180 度子午線をまたぐとき
        // 360 度左にずらした三角形をもう1つ加える
        if ( mx_max_2 > Math.PI ) {
            for ( let i = 0; i < 3; ++i ) {
                let ix   = 2 * i;
                let mx_2 = vertices[ix];  // オフセット後の mx 座標
                let mx_3 = mx_2 - TwoPI;  // 360 度左にずらした mx 座標
                vertices[ix] = mx_3;
            }

            dst_polygons.push( new ConvexPolygon( vertices ) );
        }
    }

}


/**
 * @summary サブメッシュ
 *
 * @private
 */
class Submesh {

    /**
     */
    constructor()
    {
    }


    /**
     * @summary 頂点数を取得
     *
     * @return {number}  頂点数
     *
     * @abstract
     */
    getNumVertices()
    {
        throw "";
    }


    /**
     * @summary 三角形数を取得
     *
     * @return {number}  三角形数
     *
     * @abstract
     */
    getNumTriangles()
    {
        throw "";
    }


    /**
     * @summary 頂点配列に頂点データを書き込む
     *
     * @param {mapray.Vector3} origin    座標系の原点 (GOCS)
     * @param {mapray.Sampler} sampler   DEM サンプラー
     * @param {number[]}       vertices  書き込み先の配列
     * @param {number}         offset    書き込み開始インデックス
     *
     * @return {number}  offset + 書き込んだ要素数
     *
     * @abstract
     */
    addVertices( origin, sampler, vertices, offset )
    {
        throw "";
    }


    /**
     * @summary インデックス配列にインデックスを書き込む
     *
     * @param {number}   voffset  this 用頂点の先頭の頂点インデックス
     * @param {number[]} indices  書き込み先の配列
     * @param {number}   ioffset  書き込み開始インデックス
     *
     * @return {number}  ioffset + 書き込んだ要素数
     *
     * @abstract
     */
    addIndices( voffset, indices, ioffset )
    {
        throw "";
    }

}


/**
 * @summary 矩形サブメッシュ
 *
 * @private
 */
class RectSubmesh extends Submesh {

    /**
     * @param {number} x_min
     * @param {number} y_min
     * @param {number} x_max
     * @param {number} y_max
     * @param {number} div_x
     * @param {number} div_y
     */
    constructor( x_min, y_min, x_max, y_max, div_x, div_y )
    {
        super();

        this._x_min = x_min;
        this._y_min = y_min;
        this._x_max = x_max;
        this._y_max = y_max;
        this._div_x = div_x;
        this._div_y = div_y;
    }


    /**
     * @override
     */
    getNumVertices()
    {
        return (this._div_x + 1) * (this._div_y + 1);
    }


    /**
     * @override
     */
    getNumTriangles()
    {
        return 2 * this._div_x * this._div_y;
    }


    /**
     * @override
     */
    addVertices( origin, sampler, vertices, offset )
    {
        // 刻み幅
        let mx_step = (this._x_max - this._x_min) / this._div_x;
        let my_step = (this._y_max - this._y_min) / this._div_y;

        let end_iu = this._div_x + 1;
        let end_iv = this._div_y + 1;

        let index = offset;

        for ( let iv = 0, my = this._y_min; iv < end_iv; ++iv, my += my_step ) {
            let ey    = Math.exp( my );
            let ey2   = ey * ey;
            let sinφ = (ey2 - 1) / (ey2 + 1);
            let cosφ =   2 * ey  / (ey2 + 1);
            for ( let iu = 0, mx = this._x_min; iu < end_iu; ++iu, mx += mx_step ) {
                let sinλ = Math.sin( mx );
                let cosλ = Math.cos( mx );

                let height = sampler.sample( mx, my );
                let radius = GeoMath.EARTH_RADIUS + height;

                // 法線 (GOCS)
                let nx = cosφ * cosλ;
                let ny = cosφ * sinλ;
                let nz = sinφ;

                // 位置 (GOCS)
                let gx = radius * nx;
                let gy = radius * ny;
                let gz = radius * nz;

                vertices[index++] = gx - origin[0];  // x
                vertices[index++] = gy - origin[1];  // y
                vertices[index++] = gz - origin[2];  // z
                vertices[index++] = nx;              // nx
                vertices[index++] = ny;              // ny
                vertices[index++] = nz;              // nz
            }
        }

        return index;
    }


    /**
     * @override
     */
    addIndices( voffset, indices, ioffset )
    {
        let div_x = this._div_x;
        let div_y = this._div_y;

        let index = ioffset;

        for ( let y = 0; y < div_y; ++y ) {
            for ( let x = 0; x < div_x; ++x ) {
                var i00 = voffset + (div_x + 1) * y + x;  // 左下頂点
                var i10 = i00 + 1;                        // 右下頂点
                var i01 = i00 + div_x + 1;                // 左上頂点
                var i11 = i01 + 1;                        // 右上頂点

                // 左下三角形
                indices[index++] = i00;
                indices[index++] = i10;
                indices[index++] = i01;

                // 右上三角形
                indices[index++] = i01;
                indices[index++] = i10;
                indices[index++] = i11;
            }
        }

        return index;
    }

}


/**
 * @summary 凸多角形集合サブメッシュ
 *
 * @private
 */
class PolygonsSubmesh extends Submesh {

    /**
     * this の生存中はパラメータのオブジェクトを変更しないこと。
     *
     * @param {number[]}                     arit_coords  領域の三角形座標配列 (左下または右上の三角形)
     * @param {iterable.<mapray.ConvexPolygon>} polygons  arit_coords の上にある凸多角形集合
     */
    constructor( arit_coords, polygons )
    {
        super();

        this._arit_coords = arit_coords;
        this._polygons    = polygons;

        this._num_vertices  = 0;
        this._num_triangles = 0;

        for ( let polygon of polygons ) {
            this._num_vertices  += polygon.num_vertices;
            this._num_triangles += polygon.num_vertices - 2;
        }
    }


    /**
     * @override
     */
    getNumVertices()
    {
        return this._num_vertices;
    }


    /**
     * @override
     */
    getNumTriangles()
    {
        return this._num_triangles;
    }


    /**
     * @override
     */
    addVertices( origin, sampler, vertices, offset )
    {
        let plane = this._get_elevation_plane( sampler );

        let index = offset;

        for ( let polygon of this._polygons ) {
            index = this._add_polygon_vertices( polygon, plane, origin, vertices, index );
        }

        return index;
    }


    /**
     * @override
     */
    addIndices( voffset, indices, ioffset )
    {
        let iofs_next = ioffset;
        let vofs_next = voffset;

        for ( let polygon of this._polygons ) {
            iofs_next  = this._add_polygon_indices( polygon, vofs_next, indices, iofs_next );
            vofs_next += polygon.num_vertices;
        }

        return iofs_next;
    }


    /**
     * @summary 凸多角形の頂点を追加
     *
     * @param {mapray.ConvexPolygon} polygon   凸多角形
     * @param {number[]}             plane     平面係数
     * @param {mapray.Vector3}       origin    座標系の原点 (GOCS)
     * @param {number[]}             vertices  書き込み先の配列
     * @param {number}               offset    書き込み開始インデックス
     *
     * @return {number}  offset + 書き込んだ要素数
     *
     * @private
     */
    _add_polygon_vertices( polygon, plane, origin, vertices, offset )
    {
        let index = offset;

        let num_vertices = polygon.num_vertices;
        let src_vertices = polygon.vertices;

        for ( let vi = 0; vi < num_vertices; ++vi ) {
            let mx = src_vertices[2*vi    ];
            let my = src_vertices[2*vi + 1];

            let ey  = Math.exp( my );
            let ey2 = ey * ey;

            let sinλ = Math.sin( mx );
            let cosλ = Math.cos( mx );
            let sinφ = (ey2 - 1) / (ey2 + 1);
            let cosφ =   2 * ey  / (ey2 + 1);

            // mx*plane[0] + my*plane[1] + height*plane[2] + plane[3] == 0
            let height = -(mx*plane[0] + my*plane[1] + plane[3]) / plane[2];
            let radius = GeoMath.EARTH_RADIUS + height;

            // 法線 (GOCS)
            let nx = cosφ * cosλ;
            let ny = cosφ * sinλ;
            let nz = sinφ;

            // 位置 (GOCS)
            let gx = radius * nx;
            let gy = radius * ny;
            let gz = radius * nz;

            vertices[index++] = gx - origin[0];  // x
            vertices[index++] = gy - origin[1];  // y
            vertices[index++] = gz - origin[2];  // z
            vertices[index++] = nx;              // nx
            vertices[index++] = ny;              // ny
            vertices[index++] = nz;              // nz
        }

        return index;
    }


    /**
     * @summary 凸多角形のインデックスを追加
     *
     * @param {mapray.ConvexPolygon} polygon  凸多角形
     * @param {number}               voffset  this 用頂点の先頭の頂点インデックス
     * @param {number[]}             indices  書き込み先の配列
     * @param {number}               ioffset  書き込み開始インデックス
     *
     * @return {number}  ioffset + 書き込んだ要素数
     *
     * @private
     */
    _add_polygon_indices( polygon, voffset, indices, ioffset )
    {
        let index = ioffset;

        let num_triangles = polygon.num_vertices - 2;

        for ( let i = 1; i <= num_triangles; ++i ) {
            indices[index++] = voffset;
            indices[index++] = voffset + i;
            indices[index++] = voffset + i + 1;
        }

        return index;
    }


    /**
     * @summary 平面ベースで標高を計算するための係数を取得
     *
     * @param {mapray.Sampler} sampler
     *
     * @return {number[]}  平面係数 [x, y, z, w]
     *
     * @private
     */
    _get_elevation_plane( sampler )
    {
        let coords = this._arit_coords;

        // 三角形の頂点の高さを取得
        let z_coords = new Array( 3 );

        for ( let i = 0; i < 3; ++i ) {
            let mx = coords[2*i    ];
            let my = coords[2*i + 1];
            z_coords[i] = sampler.sample( mx, my );
        }

        let ox =   coords[0];
        let oy =   coords[1];
        let oz = z_coords[0];

        let x1 =   coords[2] - ox;
        let y1 =   coords[3] - oy;
        let z1 = z_coords[1] - oz;

        let x2 =   coords[4] - ox;
        let y2 =   coords[5] - oy;
        let z2 = z_coords[2] - oz;

        // [nx, ny, nz] = [x1, y1, z1] x [x2, y2, z2]
        let nx = y1*z2 - z1*y2;
        let ny = z1*x2 - x1*z2;
        let nz = x1*y2 - y1*x2;

        return [nx, ny, nz, -ox*nx - oy*ny - oz*nz];
    }

}


/**
 * @summary 配列からベクトルを設定
 *
 * array[index] から vec に設定する。
 *
 * @private
 */
function
setArrayToVec3( array, index, vec )
{
    vec[0] = array[index];
    vec[1] = array[index + 1];
    vec[2] = array[index + 2];
}


/**
 * @summary 配列からベクトルを設定
 *
 * vec から array[index] に設定する。
 *
 * @private
 */
function
setVec3ToArray( vec, array, index )
{
    array[index]     = vec[0];
    array[index + 1] = vec[1];
    array[index + 2] = vec[2];
}


/**
 * @summary 3頂点から正規化法線ベクトルを設定
 * @private
 */
function
setTriangleNormal( p0, p1, p2, normal )
{
    for ( let i = 0; i < 3; ++i ) {
        temp_normal_ax[i] = p1[i] - p0[i];
        temp_normal_ay[i] = p2[i] - p0[i];
    }

    GeoMath.cross3( temp_normal_ax, temp_normal_ay, normal );
    GeoMath.normalize3( normal, normal );

    return normal;
}


var temp_normal_ax = GeoMath.createVector3();
var temp_normal_ay = GeoMath.createVector3();


/**
 * @summary 内部ステータス
 * @enum {object}
 * @constant
 * @private
 */
var Status = {

    INVALID:        { id: "INVALID" },
    NORMAL:         { id: "NORMAL" },
    TRIANGLE_DIRTY: { id: "TRIANGLE_DIRTY" },
    MESH_DIRTY:     { id: "MESH_DIRTY" }

};


export default PolygonEntity;