import Mesh from "./Mesh"; import GeoMath from "./GeoMath"; import GeoPoint from "./GeoPoint"; import PointCloudMaterial, { PointCloudDebugWireMaterial, PointCloudDebugFaceMaterial } from "./PointCloudMaterial"; /** * @summary 点群データを表現するクラス * @example * <caption>インスタンスの生成は下記のように行う。</caption> * const provider = new {@link mapray.RawPointCloudProvider}({ * resource: { * prefix: "https://..." * } * }); * const point_cloud = viewer.point_cloud_collection.add( provider ); * point_cloud.setPointShape( {@link mapray.PointCloud.PointShapeType}.GRADIENT_CIRCLE ); * * @see mapray.PointCloudProvider * @see mapray.PointCloudCollection * @memberof mapray */ class PointCloud { /** * @param {mapray.Scene} scene 所属するシーン * @param {mapray.PointCloudProvider} provider プロバイダ */ constructor( scene, provider ) { this._glenv = scene.glenv; this._scene = scene; this._provider = provider; this._root = Box.createRoot( this ); // properties this._points_per_pixel = 0.7; this._point_shape = PointCloud.PointShapeType.CIRCLE; this._point_size_type = PointCloud.PointSizeType.FLEXIBLE; this._point_size = 1; this._point_size_limit = 10; // hidden properties this._dispersion = true; this._debug_shader = false; this._debug_render_box = false; this._debug_render_ellipsoid = false; this._debug_render_axis = false; this._debug_render_section = false; this._checkMaterials(); PointCloud._instances.push(this); } static get PointShapeType() { return PointShapeType; } static get PointSizeType() { return PointSizeType; } /** * @summary 初期化 * mapray.PointCloudBoxCollectorへ追加時に自動的に呼ばれる。 * @private */ async init() { await this._provider.init(); } /** * @summary 破棄 * mapray.PointCloudBoxCollectorから削除時に自動的に呼ばれる。 * @private */ async destroy() { if (this._provider) { await this._provider.destroy(); } if (this._root) { await this._root.dispose(null); this._root = null; } const index = PointCloud._instances.indexOf( this ); if ( index !== -1 ) { PointCloud._instances.splice( index, 1 ); } this._provider = null; } /** * @summary プロバイダ * @type {mapray.PointCloudProvider} */ get provider() { return this._provider; } /** * @summary ルートBox * @type {Box} * @private */ get root() { return this._root }; // Properties /** * @summary 点群Box読み込みを行う際の解像度[points/pixel] * @return {number} */ getPointsPerPixel() { return this._points_per_pixel; } /** * @summary 点群Box読み込みを行う際の解像度[points/pixel]を設定 * @param {number} val 設定する値 */ setPointsPerPixel( val ) { console.assert( val <= 1 ); this._points_per_pixel = val; } /** * @summary 点を描画する際の形状 * @return {mapray.PointCloud.PointShapeType} */ getPointShape() { return this._point_shape; } /** * @summary 点を描画する際の形状を設定 * @param {mapray.PointCloud.PointShapeType} val 設定する値 */ setPointShape( val ) { this._point_shape = val; } /** * @summary 点を描画する際のサイズの指定方法 * @return {mapray.PointCloud.PointSizeType} */ getPointSizeType() { return this._point_size_type; } /** * @summary 点を描画する際のサイズの指定方法を設定 * @param {mapray.PointCloud.PointSizeType} val 設定する値 */ setPointSizeType( val ) { this._point_size_type = val; } /** * @summary 点を描画する際のサイズ * point_size_typeにより単位が異なる * @see mapray.PointCloud#getPointSizeType * @return {number} */ getPointSize() { return this._point_size; } /** * @summary 点を描画する際のサイズを設定。 * {@link mapray.PointCloud#setPointSizeType}により指定された値によって解釈される単位が異なる。 * @param {number} val 設定する値 */ setPointSize( val ) { console.assert( val > 0 ); this._point_size = val; } /** * @summary 点を描画する際の最大ピクセルサイズ * @return {number} */ getPointSizeLimit() { return this._point_size_limit; } /** * @summary 点を描画する際の最大ピクセルサイズを設定 * @param {number} val 設定する値 */ setPointSizeLimit( val ) { console.assert( val > 0 ); this._point_size_limit = val; } // hidden properties /** * @private */ getDispersion() { return this._dispersion } /** * @private */ setDispersion( val ) { this._dispersion = val; } /** * @private */ getDebugShader() { return this._debug_shader; } /** * @private */ setDebugShader( val ) { this._debug_shader = val; } /** * @private */ setDebugRenderBox( val ) { this._debug_render_box = val; this._updateDebugMesh(); } /** * @private */ setDebugRenderEllipsoid( val ) { this._debug_render_ellipsoid = val; this._updateDebugMesh(); } /** * @private */ setDebugRenderAxis( val ) { this._debug_render_axis = val; this._updateDebugMesh(); } /** * @private */ setDebugRenderSection( val ) { this._debug_render_section = val; this._updateDebugMesh(); } /** * @private */ _updateDebugMesh() { if ( this._root ) { this._root._updateDebugMeshes(); } } /** * @summary Traverse結果の統計情報を取得。 * リクエストキューに登録し、{@link mapray.RenderStage}が処理を完了するのを待つ。 * @return {Promise} * @private */ static async requestTraverseSummary() { return new Promise(onSuccess => { const notifier = statistics => { onSuccess(statistics); const index = PointCloud.getTraverseDataRequestQueue().indexOf(notifier); if (index !== -1) PointCloud.getTraverseDataRequestQueue().splice(index, 1); }; PointCloud.getTraverseDataRequestQueue().push( notifier ); }); } /** * @summary Traverse結果取得用のリクエストキューを取得 * @return {Array} * @private */ static getTraverseDataRequestQueue() { return PointCloud._traverseDataRequestQueue || (PointCloud._traverseDataRequestQueue=[]); } /** * @summary 指定された level, x, y, z のURLを生成します * @param {number} level * @param {number} x * @param {number} y * @param {number} z * @return {string} * @private */ getURL( level, x, y, z ) { return this._urlGenerator( level, x, y, z ); } /** * @private */ _checkMaterials() { const viewer = this._scene.viewer; const render_cache = viewer._render_cache || (viewer._render_cache = {}); if ( !render_cache.point_cloud_materials ) { render_cache.point_cloud_materials = Object.keys(PointShapeType).reduce((map, key) => { const point_shape_type = PointShapeType[key]; map[point_shape_type.id] = new PointCloudMaterial( viewer, { point_shape_type, }); return map; }, {}); } if ( !render_cache.point_cloud_debug_wire_material ) { render_cache.point_cloud_debug_wire_material = new PointCloudDebugWireMaterial( viewer ); } if ( !render_cache.point_cloud_debug_face_material ) { render_cache.point_cloud_debug_face_material = new PointCloudDebugFaceMaterial( viewer ); } } /** * @private */ _getMaterial( point_shape ) { return this._scene.viewer._render_cache.point_cloud_materials[ point_shape.id ]; } /** * @private */ static setStatisticsHandler( statistics_handler ) { if (statistics_handler) { PointCloud._statistics = { statistics_obj: new Statistics(), statistics_handler: statistics_handler, }; } } /** * @private */ static getStatistics() { return PointCloud._statistics; } /** * @private */ static getStatisticsHandler() { return PointCloud._statistics_handler; } } PointCloud._instances = []; /** * @private */ class Statistics { constructor() { this._now = performance ? () => performance.now() : () => Date.now(); this.clear(); }; start() { this._start_time = this._now(); } doneTraverse() { this._done_traverse_time = this._now(); this.traverse_time += this._done_traverse_time - this._start_time; } done() { this._done_time = this._now(); this.render_time += this._done_time - this._done_traverse_time; this.total_time += this._done_time - this._start_time; } clear() { this.render_point_count = 0; this.total_point_count = 0; this.render_boxes = 0; this.total_boxes = 0; this.loading_boxes = 0; this.created_boxes = 0; this.disposed_boxes = 0; this.total_time = 0.0; this.traverse_time = 0.0; this.render_time = 0.0; } } /** * @summary 点描画の種類 * @constant * @enum {object} * @memberof mapray.PointCloud */ const PointShapeType = { /** * 矩形 */ RECTANGLE: { id: "RECTANGLE", shader_code: 0 }, /** * 円 */ CIRCLE: { id: "CIRCLE", shader_code: 1 }, /** * 境界線付きの円 */ CIRCLE_WITH_BORDER: { id: "CIRCLE_WITH_BORDER", shader_code: 2 }, /** * グラデーションで塗り潰した円 */ GRADIENT_CIRCLE: { id: "GRADIENT_CIRCLE", shader_code: 3 }, }; /** * @summary 点描画のサイズ指定方法の種類 * @enum {object} * @constant * @memberof mapray.PointCloud */ const PointSizeType = { /** * setPointSize()により指定された値をピクセルとして解釈する */ PIXEL: { id: "PIXEL" }, /** * setPointSize()により指定された値をmmとして解釈する */ MILLIMETERS: { id: "MILLIMETERS" }, /** * setPointSize()により指定された値を参照せず、表示位置に応じて適切なサイズを自動的に指定する。 */ FLEXIBLE: { id: "FLEXIBLE" }, }; /** * @summary 点群ツリーを構成するノード。 * ルート要素(level === 0) は、Box.createRoot()を用いて作成する。 * @memberof mapray.PointCloud * @private */ class Box { /** * @param {Box|null} parent 親Box(level === 0の場合はnull) * @param {number} level レベル * @param {number} x x * @param {number} y y * @param {number} z z * @private */ constructor( parent, level, x, y, z ) { /** * @summary 親Box * @type {Box} */ this._parent = parent; /** * @summary 所属するPointCloud。 * ルート要素の場合は Box.createRoot() で設定される。 * @type {mapray.PointCloud} */ this._owner = parent ? parent._owner : null; /** * @summary レベル * @type {number} */ this.level = level; /** * @summary x * @type {number} */ this.x = x; /** * @summary y * @type {number} */ this.y = y; /** * @summary z * @type {number} */ this.z = z; /* 2次元(X,Y)までを下記に図示する。Z軸についても同様。 ^ Y | +-------------+-------------M | cell (0, 1) | cell (1, 1) | | | | c: gocs_center [GOCS] | | | m: gocs_min [GOCS] | | | M: gocs_max [GOCS] | | | +-------------c-------------+ | cell (0, 0) | cell (1, 0) | | | | | | | | | | | | | m-------------+-------------+ --> X |<--size[m]-->| */ /** * @summary Box一辺の半分の長さ * @type {number} * @private */ const size = this.size = ( level === 0 ? 2147483648: // 2^31 level < 31 ? 1 << (31-level): Math.pow(0.5, level-31) ); /** * @summary 軸方向に投影した際の面積 * @type {number} * @private */ this.proj_area = 4.0 * this.size * this.size; /** * @summary GOCS座標系でのBoxの中心位置 * @type {mapray.Vector3} * @private */ this.gocs_center = GeoMath.createVector3([ MIN_INT + (2 * x + 1) * size, MIN_INT + (2 * y + 1) * size, MIN_INT + (2 * z + 1) * size ]); /** * @summary GOCS座標系でのBoxの最小位置 * @type {mapray.Vector3} * @private */ this.gocs_min = GeoMath.createVector3([ this.gocs_center[0] - size, this.gocs_center[1] - size, this.gocs_center[2] - size ]); /** * @summary GOCS座標系でのBoxの最大位置 * @type {mapray.Vector3} * @private */ this.gocs_max = GeoMath.createVector3([ this.gocs_center[0] + size, this.gocs_center[1] + size, this.gocs_center[2] + size ]); /** * @type {Box.Status} * @private */ this._status = Box.Status.NOT_LOADED; // (this._status === Box.Status.LOADED) において有効な値 /** * @summary 子Box、セルに関する情報 * @type {object} * @private */ this._metaInfo = null; /** * @summary 子Box。 * <code>(u, v, w)</code>のインデックスは <code>(u | v << 1 | w << 2)</code> によって算出される。 * @type {mapray.Box[]} * @private */ this._children = [ null, null, null, null, null, null, null, null ]; /** * @type {mapray.Vector3} * @private */ this.average = null; /** * @type {mapray.Vector3} * @private */ this.eigenVector = null; /** * @type {mapray.Vector3} * @private */ this.eigenVectorLength = null; /** * @private */ this._vertex_buffer = null; /** * @private */ this._vertex_length = null; /** * @private */ this._vertex_attribs = null; /** * @private */ this.debug1 = null; if ( this._owner ) { this._updateDebugMesh(); } } /** * @private */ _updateDebugMeshes() { this._updateDebugMesh(); for (let i=0; i<this._children.length; i++) { if ( this._children[i] ) { this._children[i]._updateDebugMeshes(); } } } _updateDebugMesh() { const vertices = []; const indices = []; const tindices = []; if ( this._owner._debug_render_box ) { /* * 4----------5 * .´: .´| * .´ : .´ | * 0----------1 | * | 6 - - |----7 * | .´ | .´ * |.´ |.´ * 2----------3 */ for ( let i=0; i<Box.CHILDREN_INDICES.length; i++) { vertices.push( this.size * (2 * Box.CHILDREN_INDICES[i][2] - 1), this.size * (2 * Box.CHILDREN_INDICES[i][1] - 1), this.size * (2 * Box.CHILDREN_INDICES[i][0] - 1) ); } indices.push( 0, 1, 1, 3, 3, 2, 2, 0, 4, 5, 5, 7, 7, 6, 6, 4, 0, 4, 1, 5, 3, 7, 2, 6, ); //* tindices.push( 0, 2, 1, 1, 2, 3, 4, 5, 6, 7, 6, 5, 0, 1, 4, 1, 5, 4, 1, 3, 5, 3, 7, 5, 3, 6, 7, 3, 2, 6, 6, 2, 4, 2, 0, 4, ); //*/ } if ( this.average && !isNaN( this.eigenVector[0][0] ) ) { if ( this._owner._debug_render_axis ) { // Render Normal let offset = vertices.length / 3; for ( let i=0; i<3; i++) { const len = Math.max(0.2, this.eigenVectorLength[i]); const ev = this.eigenVector[i]; for ( let j=0; j<3; j++ ) vertices.push( this.average[j] - len * ev[j] ); for ( let j=0; j<3; j++ ) vertices.push( this.average[j] + len * ev[j] ); indices.push( offset++, offset++ ); } } if ( this._owner._debug_render_section ) { if ( this.level > 20 && this.getPointsLength() > 5000 && this.eigenVectorLength[0] < this.size * 0.2 ) { // = 10% = (2 * s) / 10 this._putSectionShapePoints(vertices, indices, tindices); // Render Cross Section } } if ( this._owner._debug_render_ellipsoid ) { this._putVariancePoints(vertices, indices, tindices); // Render Normal Ring } } const meshes = []; if (indices.length > 0) { const mesh_data = { vtype: [ { name: "a_position", size: 3 } ], ptype: "lines", vertices: vertices, indices: indices }; meshes.push( new Mesh( this._owner._glenv, mesh_data ) ); } if (tindices.length > 0) { const mesh_data = { vtype: [ { name: "a_position", size: 3 } ], ptype: "triangles", vertices: vertices, indices: tindices }; meshes.push( new Mesh( this._owner._glenv, mesh_data ) ); } this.debugMesh = meshes; } /** * @private */ _putVariancePoints(vertices, indices, tindices) { const offset = vertices.length / 3; const [ e1, e2, e3 ] = this.eigenVector; const [ e1l, e2l, e3l ] = this.eigenVectorLength; const G = 6; const N = 12; const cache = PointCloud._variance_points_cache || (() => { const c = { cos_ro: [], sin_ro: [], cos_th: [], sin_th: [], }; for ( let j=0; j<=G; ++j ) { const ro = Math.PI * j/G; c.cos_ro[j] = Math.cos(ro); c.sin_ro[j] = Math.sin(ro); } for ( let i=0; i<=N; ++i ) { const th = 2 * Math.PI * i/N; c.cos_th[i] = Math.cos(th); c.sin_th[i] = Math.sin(th); } return PointCloud._variance_points_cache = c; })(); const putPoint = (ro, th, vs) => { const cos_ro = cache.cos_ro[ro]; const sin_ro = cache.sin_ro[ro]; const cos_th = cache.cos_th[th]; const sin_th = cache.sin_th[th]; vs.push( this.average[0] + sin_ro * (e2l * cos_th * e2[0] + e3l * sin_th * e3[0]) + e1l * cos_ro * e1[0], this.average[1] + sin_ro * (e2l * cos_th * e2[1] + e3l * sin_th * e3[1]) + e1l * cos_ro * e1[1], this.average[2] + sin_ro * (e2l * cos_th * e2[2] + e3l * sin_th * e3[2]) + e1l * cos_ro * e1[2] ); } for ( let j=0; j<=G; ++j ) { for ( let i=0; i<=N; ++i ) { putPoint(j, i, vertices); } } for ( let j=0; j<G; ++j ) { for ( let i=0; i<N; ++i ) { const p = offset + j * (N+1) + i; indices.push( p, p+1, p, p+N+1 ); tindices.push( p, p+1, p+N+1, p+N+1, p+1, p+N+2 ); } } } /** * @private */ _putSectionShapePoints( vertices, indices, tindices ) { const offset = vertices.length / 3; const a = this.average; const e = this.eigenVector[0]; const s = this.size; const l = Math.sqrt( e[0] * e[0] + e[1] * e[1] + e[2] * e[2] ); const ue = [ e[0] / l, e[1] / l, e[2] / l ]; const ps = []; /* Compute Intersection(c) of Plane(a, ue) and Line(p, v) \ a: average point \ ue ue: eigenVector (normal vector) a-¯¯ p: point \ v: vector p---> . . c c = p + alpha v v \ \ (a - p) ue |-------->| \ alpha = ------------ alpha v \ v ue */ const q = [] for ( let i=0; i<2; ++i ) { for ( let j=0; j<2; ++j ) { q.push( { p: [i>0?s:-s, j>0?s:-s, 0], v: [0, 0, s] }, { p: [j>0?s:-s, 0, i>0?s:-s], v: [0, s, 0] }, { p: [ 0, i>0?s:-s, j>0?s:-s], v: [s, 0, 0] } ) } } let n, t; for ( let i=0; i<q.length; i++ ) { const p = q[i].p; const v = q[i].v; const alpha = ((a[0]-p[0])*ue[0] + (a[1]-p[1])*ue[1] + (a[2]-p[2])*ue[2]) / (v[0]*ue[0] + v[1]*ue[1] + v[2]*ue[2]); if (Math.abs(alpha) <= 1.0) { const c = [p[0]+alpha*v[0], p[1]+alpha*v[1], p[2]+alpha*v[2]]; const lp = [c[0]-a[0], c[1]-a[1], c[2]-a[2]]; let angle; if (n) { angle = Math.atan2(t[0]*lp[0]+t[1]*lp[1]+t[2]*lp[2], n[0]*lp[0]+n[1]*lp[1]+n[2]*lp[2]); } else { angle = 0; t = [ ue[1]*lp[2]-ue[2]*lp[1], ue[2]*lp[0]-ue[0]*lp[2], ue[0]*lp[1]-ue[1]*lp[0] ]; n = [ t[1]*ue[2]-t[2]*ue[1], t[2]*ue[0]-t[0]*ue[2], t[0]*ue[1]-t[1]*ue[0] ]; } ps.push([...c, angle, lp[0],lp[1],lp[2], n[0]*lp[0]+n[1]*lp[1]+n[2]*lp[2], t[0]*lp[0]+t[1]*lp[1]+t[2]*lp[2]]); } } ps.sort((a, b) => a[3] - b[3]); for ( let i=0; i<ps.length; ++i ) { vertices.push(ps[i][0], ps[i][1], ps[i][2]); indices.push( offset + i ); if (i==ps.length-1) indices.push( offset ); else indices.push( offset + i + 1 ); if (tindices) { tindices.push( offset, offset + i ); if ( i == ps.length - 1 ) tindices.push( offset ); else tindices.push( offset+i + 1 ); } } } /** * @summary 読み込みステータス * @type {mapray.PointCloud.Box.Status} */ get status() { return this._status; } /** * @summary 読み込みが完了しているか * * @type {boolean} */ get is_loaded() { return this._status === Box.Status.LOADED; } /** * @summary 親ノード * * @type {Box} */ get parent() { return this._parent; } /** * @summary 子Boxの情報を取得 * * @param {number} index 番号 * @return {Box} */ getChildInfo( index ) { if (!this._metaInfo) return null; return this._metaInfo.children[ index ]; } /** * @summary Box領域を8分割した領域ごとに点が存在するかを調べる。 * * @param {number} index 子Boxと同様の順番 * @return {boolean} 点が存在する場合に true となる。 */ cellPointsAvailable( index ) { return ( this._metaInfo && (index === 0 ? this._metaInfo.indices[ index ] > 0: this._metaInfo.indices[ index ] > this._metaInfo.indices[ index - 1 ] ) ); } /** * @summary Boxに含まれる点の数 * * @return {number} */ getPointsLength() { return this._metaInfo ? this._metaInfo.indices[7] : 0; } /** * @summary 子Boxの番号を返します。 * @param {Box} child 子Box * @return {number} */ indexOf( child ) { return this._children.indexOf( child ); } /** * @summary カリングするか? * @param {mapray.Vector4[]} clip_planes クリップ平面配列 * @return {boolean} 見えないとき true, 見えるまたは不明のとき false */ isInvisible( clip_planes ) { if ( this.level === 0 ) return false; const xmin = this.gocs_min[0]; const xmax = this.gocs_max[0]; const ymin = this.gocs_min[1]; const ymax = this.gocs_max[1]; const zmin = this.gocs_min[2]; const zmax = this.gocs_max[2]; for ( let i = 0; i < clip_planes.length; ++i ) { const p = clip_planes[i]; const px = p[0]; const py = p[1]; const pz = p[2]; const pw = p[3]; // 以下がすべて成り立つとボックス全体は平面の裏側にある // px*xmin + py*ymin + pz*zmin + pw < 0 // px*xmax + py*ymin + pz*zmin + pw < 0 // px*xmin + py*ymax + pz*zmin + pw < 0 // px*xmax + py*ymax + pz*zmin + pw < 0 // px*xmin + py*ymin + pz*zmax + pw < 0 // px*xmax + py*ymin + pz*zmax + pw < 0 // px*xmin + py*ymax + pz*zmax + pw < 0 // px*xmax + py*ymax + pz*zmax + pw < 0 const c0 = px*xmin + py*ymin; const c1 = px*xmax + py*ymin; const c2 = px*xmin + py*ymax; const c3 = px*xmax + py*ymax; const c4 = -pz*zmin - pw; const c5 = -pz*zmax - pw; if ( c0 < c4 && c1 < c4 && c2 < c4 && c3 < c4 && c0 < c5 && c1 < c5 && c2 < c5 && c3 < c5 ) { // ボックス全体が平面の裏側にあるので見えない return true; } } return false; // 見えている可能性がある } /** * @summary 点群の読み込み処理 * * @return {Promise<void>} */ load() { if ( this._status !== Box.Status.NOT_LOADED ) throw new Error( "illegal status: " + this._status.id ); if ( !this._owner._provider.isReady() ) return; this._status = Box.Status.LOADING; const task = this._owner._provider.load( this.level, this.x, this.y, this.z, true ); this._loadId = task.id; return task.done.then(event => { this._metaInfo = { children: [], indices: event.header.indices, }; { let childFlags = event.header.childFlags; for ( let i=7; i>=0; --i ) { this._metaInfo.children[i] = (childFlags & 1) ? {} : null; childFlags = childFlags >> 1; } } this.average = event.header.average; this.eigenVector = event.header.eigenVector; this.eigenVectorLength = event.header.eigenVectorLength; this.debug1 = event.header.debug1; const values = event.body; console.assert( values.length > 0 ); console.assert( values.length / 6 === this._metaInfo.indices[7] ); ASSERT: { const number_of_points = values.length / 6; for ( let i=0; i<8; ++i ) { if (this._metaInfo.indices[i] > number_of_points) { console.log("warning fix indices"); this._metaInfo.indices[i] = number_of_points; } } } const gl = this._owner._glenv.context; this._vertex_buffer = gl.createBuffer(); this._vertex_length = values.length / 6; gl.bindBuffer(gl.ARRAY_BUFFER, this._vertex_buffer); gl.bufferData(gl.ARRAY_BUFFER, values, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, null); /* * +------------+------------+---- * | a_position | a_color | ... * +------------+------------+---- * * |<--12 bit-->|<--12 bit-->| */ this._vertex_attribs = { "a_position": { buffer: this._vertex_buffer, num_components: 3, component_type: gl.FLOAT, normalized: false, byte_stride: 24, byte_offset: 0 }, "a_color": { buffer: this._vertex_buffer, num_components: 3, component_type: gl.FLOAT, normalized: false, byte_stride: 24, byte_offset: 12 } }; this._status = Box.Status.LOADED; this._updateDebugMesh(); }) .catch(error => { const skip_error = ( error.message === "cancel" || error.message === "not loading" || error.message === "The user aborted a request." || error.is_aborted ); if ( !skip_error ) { console.log(error); this._status = Box.Status.DESTROYED; } }); } /** * @summary 子Boxを生成。(すでに存在する場合は既存のBoxを返す) * LOADED 状態でのみ呼ぶことができる * * @param {number} index 番号 * @param {object} [statistics] 統計情報 * @return {Box} */ newChild( index, statistics ) { const [ u, v, w ] = Box.CHILDREN_INDICES[ index ]; return this.newChildAt( u, v, w, statistics ); } /** * @summary 子Boxを生成。(すでに存在する場合は既存のBoxを返す) * LOADED 状態でのみ呼ぶことができる * * @param {number} u x方向-側は0、+側は1 * @param {number} v y方向-側は0、+側は1 * @param {number} w z方向-側は0、+側は1 * @param {object} [statistics] 統計情報 * @return {Box} */ newChildAt( u, v, w, statistics ) { console.assert( this._status === Box.Status.LOADED ); const index = u | v << 1 | w << 2; const child = this._children[ index ]; if ( child ) return child; if ( !this.getChildInfo( index ) ) return null; if ( statistics ) statistics.created_boxes++; return this._children[ index ] = new Box( this, this.level + 1, this.x << 1 | u, this.y << 1 | v, this.z << 1 | w ); } /** * @summary 子Boxを取得。 * 存在しない場合は null を返却する。 * * @param {number} index 番号 * @return {Box} */ getChild( index ) { return this._children[ index ]; } /** * デバッグメッシュを描画 * * @param {mapray.RenderStage} render_stage レンダリングステージ */ _drawDebugMesh( render_stage ) { if ( !this.debugMesh ) return; const gl = render_stage._glenv.context; const color = Box.STATUS_COLOR_TABLE[ this._status.id ]; gl.disable( gl.CULL_FACE ); for ( let debugMesh of this.debugMesh ) { const debug_material = (debugMesh._draw_mode === 1 ? this._owner._scene.viewer._render_cache.point_cloud_debug_wire_material: this._owner._scene.viewer._render_cache.point_cloud_debug_face_material ); debug_material.bindProgram(); debug_material.setDebugBoundsParameter( render_stage, this.gocs_center, color ); debugMesh.draw( debug_material ); } gl.enable( gl.CULL_FACE ); } /** * @summary Boxを描画する。 * Box全体の描画および、Boxの8分割単位での描画に対応。 * * @param {mapray.RenderStage} render_stage レンダリングステージ * @param {number[]|null} target_cells 描画対象の子番号の配列。ただしnullは全体を表す。 * @param {number[]} points_per_pixels 点の解像度の配列。target_cells同じ順序であり、nullの場合は要素数1となる。 * @param {object} statistics 統計情報 */ draw( render_stage, target_cells, points_per_pixels, statistics ) { if ( this.debugMesh ) { this._drawDebugMesh( render_stage ); } if ( this._status !== Box.Status.LOADED ) return; const gl = render_stage._glenv.context; const point_shape = this._owner._point_shape; const point_size_type = this._owner._point_size_type; const point_size = this._owner._point_size; const point_size_limit = this._owner._point_size_limit; const debug_shader = this._owner._debug_shader; if ( this._status === Box.Status.LOADED ) { const material = this._owner._getMaterial( point_shape ); material.bindProgram(); material.setDebugBoundsParameter( render_stage, this.gocs_center ); material.bindVertexAttribs(this._vertex_attribs); const overlap_scale = 3; if ( target_cells === null ) { // draw whole points const ppp = points_per_pixels[ 0 ]; material.setPointSize( point_size_type === PointCloud.PointSizeType.PIXEL ? point_size: point_size_type === PointCloud.PointSizeType.MILLIMETERS ? -0.001 * point_size / render_stage._pixel_step: Math.min( point_size_limit, Math.max( 1.0, overlap_scale / ppp ) ) ); material.setDebug( debug_shader ? 0.5 / ppp : -1.0 ); gl.drawArrays( gl.POINTS, 0, this._vertex_length ); } else { // draw only target regions for ( let i=0; i<target_cells.length; i++ ) { const ppp = points_per_pixels[ i ]; material.setPointSize( point_size_type === PointCloud.PointSizeType.PIXEL ? point_size: point_size_type === PointCloud.PointSizeType.MILLIMETERS ? -0.001 * point_size / render_stage._pixel_step: Math.min( point_size_limit, Math.max( 1.0, overlap_scale / ppp ) ) ); material.setDebug( debug_shader ? 0.5 / ppp : -1.0 ); const childIndex = target_cells[ i ]; const offset = childIndex > 0 ? this._metaInfo.indices[childIndex - 1] : 0; const length = this._metaInfo.indices[childIndex] - offset; if ( length > 0 ) gl.drawArrays( gl.POINTS, offset, length ); } } gl.bindBuffer(gl.ARRAY_BUFFER, null); if ( statistics ) { statistics.render_boxes++; if ( target_cells && this._metaInfo.indices ) { for ( let childIndex of target_cells ) { const offset = childIndex > 0 ? this._metaInfo.indices[childIndex-1] : 0; const length = this._metaInfo.indices[childIndex] - offset; statistics.render_point_count += length; } } else { statistics.render_point_count += this._vertex_length; } } } } /** * @summary 子孫Boxを全て削除する。 * 全ての状態でこの関数を呼ぶことができ、複数回呼ぶことができる。 * @param {object} [statistics] 統計情報 */ disposeChildren( statistics ) { for (let i=0; i<this._children.length; i++) { if (this._children[i]) { this._children[i].dispose( statistics ); this._children[i] = null; } } } /** * @summary Boxを破棄します。子孫Boxも全て削除する。 * 全ての状態でこの関数を呼ぶことができ、複数回呼ぶことができる。 * @param {object} [statistics] 統計情報 */ dispose( statistics ) { if ( this._status === Box.Status.LOADING ) { if ( this._abort_controller ) { this._abort_controller.abort(); } this._owner._provider.cancel( this._loadId ); } this.disposeChildren( statistics ); if ( this._vertex_buffer ) { const gl = this._owner._glenv.context; gl.deleteBuffer(this._vertex_buffer); this._vertex_buffer = null; } if ( this.debugMesh ) { // this.debugMesh.dispose(); } if ( statistics ) statistics.disposed_boxes++; this._status = Box.Status.DESTROYED; } /** * @summary Boxの文字列表現を返します。 * @return {string} */ toString() { return `Box-${this.level}-${this.x}-${this.y}-${this.z}`; } /** * @summary Boxのツリー形式の文字列表現を返します。 * @param {string} [indent] ルート要素のインデント文字列を指定します。 * @return {string} */ toTreeString( indent = "" ) { return this._children.reduce( (text, child) => ( text + (child ? "\n" + child.toTreeString( indent + " " ) : "") ), indent + this.toString() ); } /** * ルートBoxを生成します。 * @param {mapray.PointCloud} owner * @return {Box} */ static createRoot( owner ) { const box = new Box( null, 0, 0, 0, 0 ); box._owner = owner; return box; } static get Status() { return Status; } } Box.CHILDREN_INDICES = [ [0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1], ]; /** * @summary Boxの状態。 * <pre> * * load() dispose() * NOT_LOADED ---------> LOADING -------> LOADED -----------> DESTROYED * \ / * `------>-----------------´ * error or dispose() * </pre> * @enum {object} * @memberof mapray.PointCloud.Box * @constant * @see mapray.PointCloud.Box#status */ const Status = { /** * 準備中 (初期状態)。 * load()を呼ぶと LOADING へ遷移し読み込み処理が開始される。 */ NOT_LOADED: { id: "NOT_LOADED" }, /** * 読み込み中。 * 読み込み処理が終了すると、LOADED か DESTROYED のいずれかに遷移する。 * 正常に処理が完了すると LOADED 、何らかのエラーが発生した場合は DESTROYED となる。 * また、LOADING 中に dispose() が呼ばれた場合、即座に DESTROYED に遷移する。 */ LOADING: { id: "LOADING" }, /** * 読み込み完了(描画可能)。 * dispose()を呼ぶと DESTROYED に遷移する。 */ LOADED: { id: "LOADED" }, /** * 破棄状態 * 他の状態に遷移することはない。 */ DESTROYED: { id: "DESTROYED" } }; Box.STATUS_COLOR_TABLE = {}; { Box.STATUS_COLOR_TABLE[Box.Status.LOADED.id] = [0.0, 0.8, 1.0, 0.5]; Box.STATUS_COLOR_TABLE[Box.Status.DESTROYED.id] = [1.0, 0.0, 0.0]; Box.STATUS_COLOR_TABLE[Box.Status.LOADING.id] = [1.0, 1.0, 0.0]; Box.STATUS_COLOR_TABLE[Box.Status.NOT_LOADED.id] = [0.0, 1.0, 0.0]; }; const MIN_INT = 1 << 31; export default PointCloud; export { Box, Statistics };