import Entity from "./Entity"; import Primitive from "./Primitive"; import Mesh from "./Mesh"; import Scene from "./Scene"; import LineMaterial from "./LineMaterial"; import GeoMath, { Vector3, Matrix } from "./GeoMath"; import GeoPoint from "./GeoPoint"; import GeoRegion from "./GeoRegion"; import AltitudeMode from "./AltitudeMode"; import EntityRegion from "./EntityRegion"; import AreaUtil, { Area } from "./AreaUtil"; import QAreaManager from "./QAreaManager"; import Type from "./animation/Type"; import RenderStage from "./RenderStage"; import DemSampler from "./DemSampler"; import DemBinary from "./DemBinary"; /** * 線エンティティ * * {@link mapray.MarkerLineEntity} と {@link mapray.PathEntity} の共通機能を提供するクラスである。 */ abstract class AbstractLineEntity extends Entity { protected _producer: AbstractLineEntity.FlakePrimitiveProducer | AbstractLineEntity.PrimitiveProducer; private _is_flake_mode: boolean; protected _width!: number; protected _color!: Vector3; protected _opacity!: number; protected _point_array!: Float64Array; readonly is_path: boolean; protected _num_points: number; /** * @param scene 所属可能シーン * @param is_path Pathかどうか * @param opts オプション集合 */ constructor( scene: Scene, is_path: boolean, opts: AbstractLineEntity.Option = {} ) { super( scene, opts ); this.is_path = is_path; this._point_array = new Float64Array( 0 ); this._num_points = 0; if ( this.altitude_mode === AltitudeMode.CLAMP ) { this._producer = new AbstractLineEntity.FlakePrimitiveProducer( this ); this._is_flake_mode = true; } else { this._producer = new AbstractLineEntity.PrimitiveProducer( this ); this._is_flake_mode = false; } } /** */ override getPrimitiveProducer() { return (!this._is_flake_mode) ? this._producer as AbstractLineEntity.PrimitiveProducer: undefined; } /** */ override getFlakePrimitiveProducer() { return (this._is_flake_mode) ? this._producer as AbstractLineEntity.FlakePrimitiveProducer: undefined; } /** */ override onChangeAltitudeMode( _prev_mode: AltitudeMode ) { if ( this.altitude_mode === AltitudeMode.CLAMP ) { this._producer = new AbstractLineEntity.FlakePrimitiveProducer( this ); this._is_flake_mode = true; } else { this._producer = new AbstractLineEntity.PrimitiveProducer( this ); this._is_flake_mode = false; } } /** * 線の太さを取得 * @internal */ getLineWidth(): number { return this._width; } /** * 線の太さを設定 * * @param width 線の太さ (画素単位) */ setLineWidth( width: number ) { if ( this._width !== width ) { this._width = width; this._producer.onChangeProperty(); } } /** * 基本色を取得 * @internal */ getColor(): Vector3 { return this._color; } /** * 基本色を設定 * * @param color 基本色 */ setColor( color: Vector3 ) { if ( this._color[0] !== color[0] || this._color[1] !== color[1] || this._color[2] !== color[2] ) { // 位置が変更された GeoMath.copyVector3( color, this._color ); this._producer.onChangeProperty(); } } /** * 不透明度を設定 * * @param opacity 不透明度 */ setOpacity( opacity: number ) { if ( this._opacity !== opacity ) { this._opacity = opacity; this._producer.onChangeProperty(); } } /** * @internal */ getPointArray() { return this._point_array; } /** * 頂点数 * @experimental */ get num_points(): number { return this._num_points; } /** * すべての頂点のバウンディングを算出 * * @return バウンディング情報を持ったGeoRegion */ override getBounds(): GeoRegion { const region = new GeoRegion(); region.addPointsAsArray( this._point_array ); return region; } /** * 専用マテリアルを取得 */ protected abstract getLineMaterial( render_target: RenderStage.RenderTarget ): LineMaterial; } namespace AbstractLineEntity { export interface Option extends Entity.Option { } export interface Json extends Entity.Json { color: Vector3; opacity: number; line_width: number; } /** * MarkerLineEntity の PrimitiveProducer * * @internal */ export class PrimitiveProducer extends Entity.PrimitiveProducer { private _transform: Matrix; private _pivot: Vector3; private _bbox: [ Vector3, Vector3 ]; private _properties: { width: number; color: Vector3; opacity: number; lower_length?: number; upper_length?: number; }; private _geom_dirty: boolean; private _primitive: Primitive; private _pickPrimitive: Primitive; private _primitives: Primitive[]; private _pickPrimitives: Primitive[]; /** * @param entity */ constructor( entity: AbstractLineEntity ) { super( entity ); // プリミティブの要素 this._transform = GeoMath.setIdentity( GeoMath.createMatrix() ); this._pivot = GeoMath.createVector3(); this._bbox = [GeoMath.createVector3(), GeoMath.createVector3()]; this._properties = { width: 1.0, color: GeoMath.createVector3f(), opacity: 1.0, }; // プリミティブ // @ts-ignore const material = entity.getLineMaterial( RenderStage.RenderTarget.SCENE ); const primitive = new Primitive( entity.scene.glenv, null, material, this._transform ); primitive.pivot = this._pivot; primitive.bbox = this._bbox; primitive.properties = this._properties; this._primitive = primitive; // @ts-ignore const pick_material = entity.getLineMaterial( RenderStage.RenderTarget.RID ); const pickPrimitive = new Primitive( entity.scene.glenv, null, pick_material, this._transform ); pickPrimitive.pivot = this._pivot; pickPrimitive.bbox = this._bbox; pickPrimitive.properties = this._properties; this._pickPrimitive = pickPrimitive; // プリミティブ配列 this._primitives = [primitive]; this._pickPrimitives = [pickPrimitive]; this._geom_dirty = true; } getEntity(): AbstractLineEntity { return super.getEntity() as AbstractLineEntity; } /** */ override createRegions() { const region = new EntityRegion(); region.addPoints( this.getEntity().getPointArray(), 0, 3, this.getEntity().num_points ); return [region]; } /** */ override onChangeElevation( _regions: EntityRegion[] ) { this._geom_dirty = true; } /** */ override getPrimitives( stage: RenderStage ): Primitive[] { if ( this.getEntity().num_points < 2 ) { // 2頂点未満は表示しない return []; } else { this._updatePrimitive(); return stage.getRenderTarget() === RenderStage.RenderTarget.SCENE ? this._primitives : this._pickPrimitives; } } /** * 頂点が変更されたことを通知 */ onChangePoints() { this.needToCreateRegions(); this._geom_dirty = true; } /** * プロパティが変更されたことを通知 */ onChangeProperty() { } /** * プリミティブの更新 * * 条件: this._num_points >= 2 * * 入力: * - this._geom_dirty * - this.entity._point_array * - this.entity._num_points * - this.entity._width * - this.entity._color * - this.entity._opacity * - this.entity._length_array * 出力: * - this._transform * - this._pivot * - this._bbox * - this._properties * - this._primitive.mesh * - this._geom_dirty * */ private _updatePrimitive() { this._updateProperties(); if ( !this._geom_dirty ) { // メッシュは更新する必要がない return; } const entity = this.getEntity(); // GeoPoint 平坦化配列を GOCS 平坦化配列に変換 const num_points = entity.num_points; const gocs_buffer = GeoPoint.toGocsArray( this._getFlatGeoPoints_with_Absolute(), num_points, new Float64Array( num_points * 3 ) ); // プリミティブの更新 // primitive.transform // primitive.pivot // primitive.bbox this._updateTransformPivotBBox( gocs_buffer, num_points ); const add_length = entity.is_path; // @ts-ignore const length_array = add_length ? entity._length_array : undefined; // メッシュ生成 const mesh_data = { vtype: [ { name: "a_position", size: 3 }, { name: "a_direction", size: 3 }, { name: "a_where", size: 2 } ], vertices: this._createVertices( gocs_buffer, num_points, length_array ), indices: this._createIndices() }; if ( add_length ) { mesh_data.vtype.push( { name: "a_length", size: 1 } ); } const mesh = new Mesh( entity.scene.glenv, mesh_data ); // メッシュ設定 // primitive.mesh const primitive = this._primitive; if ( primitive.mesh ) { primitive.mesh.dispose(); } primitive.mesh = mesh; const pickPrimitive = this._pickPrimitive; if ( pickPrimitive.mesh ) { pickPrimitive.mesh.dispose(); } pickPrimitive.mesh = mesh; // 更新終了 this._geom_dirty = false; } /** * @summary プロパティを更新 * * @desc *
* 入力:
* this.entity._width
* this.entity._color
* this.entity._opacity
* this.entity._lower_length
* this.entity._upper_length
* 出力:
* this._properties
*
*
* @private
*/
_updateProperties()
{
let entity = this.getEntity();
let props = this._properties;
props.width = entity.getLineWidth();
GeoMath.copyVector3( entity.getColor(), props.color );
// @ts-ignore
props.opacity = entity._opacity;
if ( entity.is_path ) {
// @ts-ignore
props.lower_length = entity._lower_length;
// @ts-ignore
props.upper_length = entity._upper_length;
}
}
/**
* GeoPoint 平坦化配列を取得 (絶対高度)
*
* @return GeoPoint 平坦化配列
*/
private _getFlatGeoPoints_with_Absolute(): Float64Array
{
const entity = this.getEntity();
const point_array = entity.getPointArray();
const num_points = entity.num_points;
let abs_buffer = null;
switch ( entity.altitude_mode ) {
case AltitudeMode.RELATIVE: {
abs_buffer = new Float64Array( num_points * 3 );
// abs_buffer[] の高度要素に現在の標高を設定
entity.scene.viewer.getExistingElevations( num_points, point_array, 0, 3, abs_buffer, 2, 3 );
// abs_buffer[] に経度要素と緯度要素を設定し、高度要素に絶対高度を設定
let p = 0;
for ( let i = 0; i < num_points; i++ ) {
abs_buffer[p] = point_array[p++]; // 経度
abs_buffer[p] = point_array[p++]; // 緯度
abs_buffer[p] += point_array[p++]; // 絶対高度
}
break;
}
default: // AltitudeMode.ABSOLUTE
abs_buffer = point_array;
break;
}
return abs_buffer;
}
/**
* プリミティブの更新
*
* 出力:
* - this._transform
* - this._pivot
* - this._bbox
*
* @param gocs_buffer 入力頂点配列 (GOCS)
* @param num_points 入力頂点数
*/
private _updateTransformPivotBBox( gocs_buffer: Float64Array, num_points: number )
{
// モデル座標系の原点 (GOCS)
var ox = gocs_buffer[0];
var oy = gocs_buffer[1];
var oz = gocs_buffer[2];
// 変換行列の更新
var transform = this._transform;
transform[12] = ox;
transform[13] = oy;
transform[14] = oz;
// 統計
var xsum = 0;
var ysum = 0;
var zsum = 0;
var xmin = Number.MAX_VALUE;
var ymin = Number.MAX_VALUE;
var zmin = Number.MAX_VALUE;
var xmax = -Number.MAX_VALUE;
var ymax = -Number.MAX_VALUE;
var zmax = -Number.MAX_VALUE;
for ( var i = 0; i < num_points; ++i ) {
var b = 3 * i;
var x = gocs_buffer[b] - ox;
var y = gocs_buffer[b + 1] - oy;
var z = gocs_buffer[b + 2] - oz;
xsum += x;
ysum += y;
zsum += z;
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; }
}
// 中心点
var pivot = this._pivot;
pivot[0] = xsum / num_points;
pivot[1] = ysum / num_points;
pivot[2] = zsum / num_points;
// 境界箱
var bbox = this._bbox;
var bmin = bbox[0];
var bmax = bbox[1];
bmin[0] = xmin;
bmin[1] = ymin;
bmin[2] = zmin;
bmax[0] = xmax;
bmax[1] = ymax;
bmax[2] = zmax;
}
/**
* @summary 頂点配列の生成
*
* @param {Float64Array} gocs_buffer 入力頂点配列 (GOCS)
* @param {number} num_points 入力頂点数
* @return {Float32Array} Mesh 用の頂点配列
*
* @private
*/
_createVertices( gocs_buffer: Float64Array, num_points: number, length_array?: number[] )
{
// 頂点の距離を追加するか
const add_length = (length_array !== undefined);
// モデル座標系の原点 (GOCS)
const ox = gocs_buffer[0];
const oy = gocs_buffer[1];
const oz = gocs_buffer[2];
const num_segments = num_points - 1;
const num_vertices = 4 * num_segments;
const vertices = new Float32Array( (add_length ? 9 : 8) * num_vertices );
for ( let i = 0; i < num_segments; ++i ) {
const b = 3 * i;
const sx = gocs_buffer[b] - ox;
const sy = gocs_buffer[b + 1] - oy;
const sz = gocs_buffer[b + 2] - oz;
const ex = gocs_buffer[b + 3] - ox;
const ey = gocs_buffer[b + 4] - oy;
const ez = gocs_buffer[b + 5] - oz;
const dx = gocs_buffer[b + 3] - gocs_buffer[b];
const dy = gocs_buffer[b + 4] - gocs_buffer[b + 1];
const dz = gocs_buffer[b + 5] - gocs_buffer[b + 2];
const v = (add_length ? 36 : 32) * i;
// 始左、始右、終左、終右のループ
for ( let j = 0; j < 4; ++j ) {
const start = j < 2;
const id = v + j * ( add_length ? 9 : 8 );
vertices[id] = start ? sx : ex; // a_position.x
vertices[id + 1] = start ? sy : ey; // a_position.y
vertices[id + 2] = start ? sz : ez; // a_position.z
vertices[id + 3] = dx; // a_direction.x
vertices[id + 4] = dy; // a_direction.y
vertices[id + 5] = dz; // a_direction.z
switch ( j ) {
case 0:
vertices[id + 6] = -1; // a_where.x
vertices[id + 7] = 1; // a_where.y
break;
case 1:
vertices[id + 6] = -1; // a_where.x
vertices[id + 7] = -1; // a_where.y
break;
case 2:
vertices[id + 6] = 1; // a_where.x
vertices[id + 7] = 1; // a_where.y
break;
case 3:
vertices[id + 6] = 1; // a_where.x
vertices[id + 7] = -1; // a_where.y
break;
}
if ( add_length ) {
// @ts-ignore
vertices[id + 8] = length_array[start ? i : i + 1];
}
}
}
return vertices;
}
/**
* @summary 頂点インデックスの生成
*
* @desc
*
* 条件: this.entity._num_points >= 2
* 入力: this.entity._num_points
*
*
* @return {Uint32Array} インデックス配列
*
* @private
*/
_createIndices()
{
const num_points = this.getEntity().num_points;
const num_segments = num_points - 1;
const num_indices = 6 * num_segments;
const indices = new Uint32Array( num_indices );
for ( let i = 0; i < num_segments; ++i ) {
const base_d = 6 * i;
const base_s = 4 * i;
indices[base_d] = base_s;
indices[base_d + 1] = base_s + 1;
indices[base_d + 2] = base_s + 2;
indices[base_d + 3] = base_s + 2;
indices[base_d + 4] = base_s + 1;
indices[base_d + 5] = base_s + 3;
}
return indices;
}
}
/**
* @summary MarkerLineEntity の FlakePrimitiveProducer
*
* @private
*/
export class FlakePrimitiveProducer extends Entity.FlakePrimitiveProducer {
private _material_map: Map