import GeoMath, { Vector3, Matrix } from "./GeoMath"; import AreaUtil, { Area } from "./AreaUtil"; import type GLEnv from "./GLEnv"; import type Globe from "./Globe"; import type DemBinary from "./DemBinary"; import type FlakeMaterial from "./FlakeMaterial"; import type { AttributeBindInfoDict } from "./Material"; import { cfa_assert } from "./util/assertion"; /** * 地表断片メッシュ * @internal */ class FlakeMesh { /** * @param glenv - WebGL 環境 * @param flake - 地表断片 * @param dpows - 地表断片の分割指数 * @param dem - DEM バイナリ */ constructor( glenv: GLEnv, flake: Globe.Flake, dpows: [number, number], dem: DemBinary ) { const gl = glenv.context; this._flake = flake; // オブジェクト座標系の中心位置 (GOCS) this._center = this._createCenter( flake ); // 頂点データを生成 const vdata = this._createVertices( gl, flake, dpows, dem ); this._vertices = vdata.vertices; this._num_vertices = vdata.num_vertices; this._num_quads_x = vdata.num_quads_x; this._num_quads_y = vdata.num_quads_y; // 頂点属性辞書 this._vertex_attribs = this._getVertexAttribs( gl ); // インデックス型 this._index_type = (this._num_vertices < 65536) ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT; // インデックス (GL_TRIANGLES) this._indices = null; this._num_indices = 0; // インデックス (GL_LINES) this._wire_indices = null; this._num_wire_indices = 0; this._gl = gl; } /** * 中心位置を生成 * * @param area - 地表断片の領域 * * @return 中心位置 (GOCS) */ private _createCenter( area: Area ): Vector3 { return AreaUtil.getCenter( area, GeoMath.createVector3() ); } get center() { return this._center; } /** * 頂点データとその情報を作成 */ private _createVertices( gl: WebGL2RenderingContext, area: Area, dpows: [number, number], dem: DemBinary ) /* auto-type */ { const target = gl.ARRAY_BUFFER; const vbo = gl.createBuffer(); const data = this._createVerticesData( area, dpows, dem ); if ( vbo === null ) { throw new Error( "failed to gl.createBuffer" ); } gl.bindBuffer( target, vbo ); gl.bufferData( target, data.array, gl.STATIC_DRAW ); gl.bindBuffer( target, null ); return { vertices: vbo, num_vertices: data.num_vertices, num_quads_x: data.num_quads_x, num_quads_y: data.num_quads_y, }; } /** * 頂点データを作成 */ private _createVerticesData( area: Area, dpows: [number, number], dem: DemBinary ) /* auto-type */ { // 開始位置 (単位球メルカトル座標系) var msize = Math.pow( 2, 1 - area.z ) * Math.PI; var mx_min = area.x * msize - Math.PI; var my_min = Math.PI - (area.y + 1) * msize; // 分割数 var u_count = 1 << dpows[0]; var v_count = 1 << dpows[1]; // 刻み幅 var u_step = 1 / u_count; var v_step = 1 / v_count; var mx_step = msize / u_count; var my_step = msize / v_count; const u_step_count = u_count + 2; // for edge bending const v_step_count = v_count + 2; // for edge bending var center = this._center; var demSampler = dem.newSampler( area ); var num_vertices = (u_step_count + 1) * (v_step_count + 1); var array = new Float32Array( FlakeMesh.VERTEX_SIZE * num_vertices ); var index = 0; const edge_depth = (GeoMath.EARTH_RADIUS * 0.1) / ((area.z + 1) * (area.z + 1)); const angle = 2.0 * GeoMath.DEGREE; const angle_unit = Math.PI / (GeoMath.EARTH_RADIUS * Math.PI); const edge_length = edge_depth * angle * angle_unit; for ( var iv = 0, my = my_min; iv < v_step_count + 1; ++iv, my += (iv == 1 || iv == v_step_count ? 0 : my_step) ) { let my_edge = my; if ( iv === 0 ) { my_edge -= edge_length; }; if ( iv === v_step_count ) { my_edge += edge_length; }; var ey = Math.exp( my_edge ); var ey2 = ey * ey; var sinφ = (ey2 - 1) / (ey2 + 1); var cosφ = 2 * ey / (ey2 + 1); for ( var iu = 0, mx = mx_min; iu < u_step_count + 1; ++iu, mx += (iu == 1 || iu == u_step_count ? 0 : mx_step) ) { let mx_edge = mx; if ( iu === 0 ) { mx_edge -= edge_length; }; if ( iu === u_step_count ) { mx_edge += edge_length; }; var sinλ = Math.sin( mx_edge ); var cosλ = Math.cos( mx_edge ); var height = iv === 0 || iv === v_step_count || iu === 0 || iu === u_step_count ? demSampler.sample( mx, my ) - edge_depth : demSampler.sample( mx, my ); var radius = GeoMath.EARTH_RADIUS + height; // 法線 (GOCS) var nx = cosφ * cosλ; var ny = cosφ * sinλ; var nz = sinφ; // 位置 (GOCS) var gx = radius * nx; var gy = radius * ny; var gz = radius * nz; array[index++] = gx - center[0]; // x array[index++] = gy - center[1]; // y array[index++] = gz - center[2]; // z array[index++] = iu < 1 ? 0.0: iu > u_step_count - 1 ? 1.0: ( iu - 1 ) * u_step; // mu array[index++] = iv < 1 ? 0.0: iv > v_step_count - 1 ? 1.0: ( iv - 1 ) * v_step; // mv array[index++] = demSampler.sample( mx, my ); // height } } return { array: array, num_vertices: num_vertices, num_quads_x: u_step_count, num_quads_y: v_step_count }; } /** * 頂点属性の辞書を取得 */ private _getVertexAttribs( gl: WebGL2RenderingContext ): AttributeBindInfoDict { const type = gl.FLOAT; const stride = FlakeMesh.VERTEX_BYTES; // Mesh.AttribData の辞書 return { "a_position": { buffer: this._vertices, num_components: 3, component_type: type, normalized: false, byte_stride: stride, byte_offset: FlakeMesh.OFFSET_P }, "a_uv": { buffer: this._vertices, num_components: 2, component_type: type, normalized: false, byte_stride: stride, byte_offset: FlakeMesh.OFFSET_UV }, "a_height": { buffer: this._vertices, num_components: 1, component_type: type, normalized: false, byte_stride: stride, byte_offset: FlakeMesh.OFFSET_HEIGHT }, }; } static getCache( globe: Globe, size: number ) { const key = "FLAKE_MESH_" + size; // @ts-ignore return globe.cache[key] ?? (globe.cache[key] = {}) as { [key: string] : WebGLBuffer }; } static disposeCache( globe: Globe, glenv: GLEnv ): void { const gl = glenv.context; for ( const size of [16, 32] ) { // @ts-ignore const cache = FlakeMesh.getCache( globe, size ); for ( const key of Object.keys( cache ) ) { gl.deleteBuffer( cache[key] ); delete cache[key]; } } } /** * 頂点情報が他のFlakeMeshと共有されているかを意味する。 */ private _indices_shared: boolean = false; /** * `GL_TRIANGLES` 用のインデックス配列を生成 * * `_indices` と `_num_indices` を設定する。 */ private _createIndices(): void { const gl = this._gl; const num_quads = this._num_quads_x * this._num_quads_y; this._num_indices = num_quads * 6; const cache = FlakeMesh.getCache( this._flake.belt.globe, this._index_type === gl.UNSIGNED_INT ? 32 : 16 ); if ( this._num_quads_x === this._num_quads_y ) { const c = cache[this._num_quads_x]; if ( c ) { this._indices = c; this._indices_shared = true; return; } } const array = (this._index_type === gl.UNSIGNED_INT) ? new Int32Array( this._num_indices ) : new Int16Array( this._num_indices ); let index = 0; const addQuad = ( x: number, y: number, index: number ) => { const i00 = ( this._num_quads_x + 1 ) * y + x; // 左下頂点 const i10 = i00 + 1; // 右下頂点 const i01 = i00 + this._num_quads_x + 1; // 左上頂点 const i11 = i01 + 1; // 右上頂点 // 左下三角形 array[index++] = i00; array[index++] = i10; array[index++] = i01; // 右上三角形 array[index++] = i01; array[index++] = i10; array[index++] = i11; return index; }; const max_x = this._num_quads_x; const max_y = this._num_quads_y; for ( let y = 0; y < max_y; ++y ) { for ( let x = 0; x < max_x; ++x ) { index = addQuad( x, y, index ); } } const target = gl.ELEMENT_ARRAY_BUFFER; const vbo = gl.createBuffer(); gl.bindBuffer( target, vbo ); gl.bufferData( target, array, gl.STATIC_DRAW ); gl.bindBuffer( target, null ); this._indices = vbo; if ( vbo && ( this._num_quads_x === this._num_quads_y ) ) { cache[this._num_quads_x] = vbo; this._indices_shared = true; } } /** * `GL_LINES` 用のインデックス配列を生成 * * `_wire_indices` と `_num_wire_indices` を設定する。 */ private _createWireIndices(): void { var gl = this._gl; var typedArray = (this._index_type === gl.UNSIGNED_INT) ? Int32Array : Int16Array; var num_indices = 2 * (2 * this._num_quads_x * this._num_quads_y + this._num_quads_x + this._num_quads_y); var array = new typedArray( num_indices ); var index = 0; // 水平線 for ( var y = 1; y < this._num_quads_y; ++y ) { for ( var x = 1; x < this._num_quads_x - 1; ++x ) { var i00 = (this._num_quads_x + 1) * y + x; // 左下頂点 var i10 = i00 + 1; // 右下頂点 // 下水平線 array[index++] = i00; array[index++] = i10; } } // 垂直線 for ( x = 1; x < this._num_quads_x; ++x ) { for ( y = 1; y < this._num_quads_y - 1; ++y ) { var j00 = (this._num_quads_x + 1) * y + x; // 左下頂点 var j01 = j00 + this._num_quads_x + 1; // 左上頂点 // 左垂直線 array[index++] = j00; array[index++] = j01; } } var target = gl.ELEMENT_ARRAY_BUFFER; var vbo = gl.createBuffer(); gl.bindBuffer( target, vbo ); gl.bufferData( target, array, gl.STATIC_DRAW ); gl.bindBuffer( target, null ); this._wire_indices = vbo; this._num_wire_indices = num_indices; } /** * 頂点数 */ get num_vertices(): number { return this._num_vertices; } /** * インデックス (GL_TRIANGLES) */ get indices(): WebGLBuffer { if ( this._indices === null ) { this._createIndices(); cfa_assert( this._indices !== null ); } return this._indices; } /** * インデックス数 (`GL_TRIANGLES`) */ get num_indices(): number { if ( this._indices === null ) { this._createIndices(); } return this._num_indices; } /** * インデックス (`GL_LINES`) */ get wire_indices(): WebGLBuffer { if ( this._wire_indices === null ) { this._createWireIndices(); cfa_assert( this._wire_indices !== null ); } return this._wire_indices; } /** * インデックス数 (`GL_LINES`) */ get num_wire_indices(): number { if ( this._wire_indices === null ) { this._createWireIndices(); } return this._num_wire_indices; } /** * リソースを破棄 */ dispose(): void { var gl = this._gl; // @ts-ignore - 以降、this のメソッドは呼び出されない約束なので OK this._vertex_attribs = {}; gl.deleteBuffer( this._vertices ); // @ts-ignore - 同上 this._vertices = null; if ( this._indices ) { if ( !this._indices_shared ) { gl.deleteBuffer( this._indices ); } this._indices = null; } if ( this._wire_indices ) { gl.deleteBuffer( this._wire_indices ); this._wire_indices = null; } } /** * 変換行列を計算 * * `mat` に地表断片座標系から GOCS への変換行列を掛ける。 * * @param mat - 行列 * @param dst - 結果 * * @return `dst` */ mul_flake_to_gocs