i3s定向边界盒(OBB)前端实例化与转换方法

背景知识

首先了解一下i3s的基本概念以及树状结构的介绍。参考资料:https://github.com/Esri/i3s-spec
索引3D场景图层(i3s)格式是一种开放的3D内容交付格式,用于快速流式传输和分发大量3D GIS数据到移动、Web和桌面客户端。ArcGIS场景层和场景服务使用i3s基础结构。场景图层为客户提供了一种结构化的方式来存储和可视化大量3D数据。i3s将信息组织到节点层次结构中,这些节点层次结构包含具有几何,纹理和属性的要素。
树状结构:为确保可视化3D内容时的高性能,数据在空间上分为节点。递归地重复分组过程以创建节点树。给定节点的空间范围包含其所有子级,以创建边界体积层次结构。支持数据的空间规则(例如四叉树)和空间不规则(例如R树)组织。

边界体积定义为最小边界球(MBS)或定向边界盒(OBB)表示。

包围在最小边界球中的3D对象。
包围在smalles边界盒中的3D对象

OBB是更理想的表示形式,建议实现者以OBB格式输出节点边界量。点云配置文件仅支持OBB表示。
为了提供原始数据的可伸缩表示,父节点包含其子级的简化表示,从而创建了Level of Details(LOD)。


空间分布数据的示意图以及将节点递归分组为包围的体积层次结构的示意图
表示为节点树的包围卷层次结构的示例

定向边界盒(Oriented Bounding Box)

定向边界盒(OBB)是紧凑的边界体积表示形式,与它所表示的几何形状紧密匹配。OBB对平移和旋转的不变性使其成为i3s中最佳和默认边界体积表示形式的理想选择。
构造供i3s使用的OBB时,需要基于该层的坐标参考系统(CRS)来考虑实现者两个方面:

为全球场景图层构造OBB

在WGS84地理坐标系的一个i3s图层中,被称为全球场景图层和通过的4326的WKID值标识:

  • OBB应该在与图层的CRS等效的地心固定地球坐标系(ECEF)中构建。
  • OBB的中心部分指定为经度,纬度(以十进制度度)和以米为单位的高程(z)。
  • OBB的halfSize组件以米为单位指定。
  • 四元数分量参考ECEF坐标系。

为全局场景层构造OBB时,必须执行以下步骤:

  • 将感兴趣的顶点投影到ECEF。
  • 在ECEF中计算OBB。
  • 仅将OBB的中心从ECEF转换回该图层的CRS。

为本地场景图层构造OBB

对于i3s层,具有CRS 其他比4326 WKID值,被称为本地场景图层

  • OBB应该在与图层相同的CRS中构建。
  • OBB的center和halfSize组件的单位应为CRS的单位。
  • 参考该图层的CRS来指定中心和四元数。

上面的全局场景层用例中描述的OBB构造步骤也适用于此。但是,对于局部场景层,顶点,OBB计算和所得的OBB分量都保留在该层的CRS中。OBB组件的单位与图层的单位相同。

OBB对象的属性

属性 类型 描述
                                 
center                                 
                                 
number[3]                                 
以CRS单位指定的定向边界盒的中心点。对于全局场景图层,将中心指定为经度和纬度(以十进制度为单位),以及高程(以米为单位)。
harfSize number[3] 以CRS为单位的定向边界盒的一半大小。对于全局场景层,所有值均以米为单位。
quaternion number[4] 定向边界盒的定向为4分量四元数。对于全局场景层,四元数位于以地球为中心,固定于地球的(ECEF)笛卡尔空间中。(Z +:北,Y +:东,X +:lon = lat = 0.0)。注意:四元数是一个四元素矢量,可用于编码3D坐标系中的任何旋转。四元数分量的顺序为x,y,z,w。

示例

/*局部场景图层 (Lambert, Wkid: 2227)*/
{
    "center": [
        6011913.2692229711,
        2117599.0498975096,
        441.1241036703866
    ],
    "halfSize": [
        100.45386505126953,
        91.120384216308594,
        426.03338623046875
    ],
    "quaternion": [
        0.64432936906814575,
        0.76474469900131226,
        -0.0020481476094573736,
        0.0010012148413807154
    ]
}

/*全球场景图层 (WSG84, wkid: 4326)*/
{
    "center": [
        -122.40277014424709,
        37.795204290863012,
        134.5439856108278
    ],
    "halfSize": [
        30.701572418212891,
        27.71544075012207,
        129.72760009765625
    ],
    "quaternion": [
        -0.50688880681991577,
        0.74475228786468506,
        0.1719556450843811,
        0.39854612946510315
    ]
}

定向边界盒(OBB)前端实体类

由于定向边界盒(OBB)决定着模型的整体位置、朝向。为了能让用户在网页端进行场景图层的平移、旋转、改变高度等交互式操作,选择作为定向边界盒(OBB)的前端实体类的条件为:

  • 可视化
  • 可交互
  • 可转换

由于需要实现一个盒(Box),最初尝试使用ArcGIS API for JavaScript的Mesh几何类型的Box来实现Oriented Bounding Box。

require(["esri/geometry/Mesh"], function(Mesh) { /* code goes here */ });
// Create a box mesh geometry
var mesh = Mesh.createBox(location, {
  size: {
    width: 100,
    height: 50,
    depth: 50
  },
  material: {
    color: "red"
  }
});
// Create a graphic and add it to the view
var graphic = new Graphic({
  geometry: mesh,
  symbol: {
    type: "mesh-3d",
    symbolLayers: [ { type: "fill" } ]
  }
});

但是发现Mesh的Box盒没有方向属性,无法模拟"定向"边界盒。并且没有现成的交互编辑微件。所以几何类型Mesh是无法胜任这项工作的。
然后,在示例Edit features in 3D with the Editor widget中,我找到了灵感。点要素Graphic在三维中通过设置PointSymbol3D并进一步设置为ObjectSymbol3DLayer可以被编辑、可以作为三维盒可视化。

ObjectSymbol3DLayer

ObjectSymbol3DLayer用于在SceneView中使用带有PointSymbol3D的体积3D形状(例如,球体或圆柱体)来渲染Point几何。列举一下用到的属性。

属性 类型 示例 描述
resource Object {primitive: "cube"} 用于使点可视化的原始形状(primitive)或外部3D模型(href)。
width Number 10 物体的宽度或直径,从东到西,以米为单位。
depth Number 10 从北到南的深度或直径,以米为单位。
height Number 10 对象的高度(以米为单位)。
material Object {color: [51, 204, 51, 0.3]} 用于为对象着色的材料。此属性定义对象的颜色。
tilt Number 45 符号在纵向垂直平面中(即绕x轴)的旋转。旋转以度为单位指定,并且相对于y轴。在0度时,模型是水平的。正值点将抬高模型的前面,并降低模型的后面。如果将符号资源对齐,使其前向侧面指向y轴方向(y轴始终在WGS84或WebMercator坐标中指向北,则其朝上的一面指向z轴方向,并且其右侧指向x轴方向(x轴始终在WGS84或WebMercator坐标中指向东),因此该角度对应于符号的tilt。
roll Number 90 符号在横向垂直平面(即绕y轴)中的旋转。旋转以度为单位指定,并且相对于x轴。在0度时,模型是水平的。正值可提升模型的左侧部分,并降低其右侧部分。如果将符号资源对齐,使其朝前的侧面指向y轴的方向(y轴始终在WGS84或WebMercator坐标中指向北,则其朝上的一面指向z轴的方向,并且其右侧指向x轴方向(x轴始终在WGS84或WebMercator坐标中指向东),因此该角度对应于符号的roll。
heading Number 180 符号在水平面(即绕z轴)中的顺时针旋转。旋转以度为单位指定,并且相对于y轴。如果将符号资源对齐,使其前向侧面指向y轴方向(y轴始终在WGS84或WebMercator坐标中指向北,则其朝上的一面指向z轴方向,并且其右侧指向x轴方向(x轴始终在WGS84或WebMercator坐标中指向东),因此该角度对应于符号的heading。

示例

let graphic = new Graphic({
geometry: point,
symbol: {
  type: "point-3d",  // autocasts as new PointSymbol3D()
  symbolLayers: [{
    type: "object",  // autocasts as new ObjectSymbol3DLayer()
    width: 5,  // diameter of the object from east to west in meters
    height: 20,  // height of the object in meters
    depth: 15,  // diameter of the object from north to south in meters
    resource: { primitive: "cube" },
    material: { color: [255,0,0,0.5] }, //0.5 transparent
    tilt:45,//around x axis degrees
    roll:0,// around the y axis degrees
    heading:90,//around z degrees
   }]
},
attributes: {
    ObjectId: nodepage[i].index,
    all:nodepage[i]
  }
});

ObjectSymbol3DLayer满足功能需求,接下来就是转换问题了。

i3s obb 转换为ObjectSymbol3DLayer的Graphic

想要将一个obb对象变为前端带有立方体样式的Graphic。分为位置转换、形状转换、角度转换(四元数转欧拉角)。

位置转换

将i3s obb的center(中心点)赋值一个Point对象即可,坐标系取场景图层的坐标系。

var point=new Point({
    type: "point", // autocasts as new Point()
    x: obb.center[0],
    y: obb.center[1],
    z: obb.center[2],
    spatialReference:{wkid:wkid}
});

形状转换

将i3s obb的halfSize(半长)乘2,分别赋值给ObjectSymbol3DLayer的width、depth、height即可。

width: obb.halfSize[0]*2,  // diameter of the object from east to west in meters
depth: obb.halfSize[1]*2,  // diameter of the object from north to south in meters
height: obb.halfSize[2]*2,  // height of the object in meters

角度转换

将i3s obb的quaternion(四元数)转换为欧拉角再由弧度转角度。赋值给ObjectSymbol3DLayer的tilt、roll、heading。这里引入three.js的四元数转欧拉角的方法。

//Three.js四元数转欧拉角
var vectorQuaternion = new THREE.Quaternion();
vectorQuaternion.w = obb.quaternion[3];
vectorQuaternion.x = obb.quaternion[0];
vectorQuaternion.y = obb.quaternion[1];
vectorQuaternion.z = obb.quaternion[2];
var vectorEuler = new THREE.Euler(0, 0, 0, eulerOrder);
vectorEuler.setFromQuaternion(vectorQuaternion, eulerOrder);

//弧度转角度degree的方法
function radToSpecific(radiansIn) {
    if (eulerAngleFormat == "Radians") {return radiansIn;}
        return THREE.Math.radToDeg(radiansIn);
}
//样式赋值
symbol:{
  tilt:radToSpecific(vectorEuler.x),//around x axis
  roll:radToSpecific(vectorEuler.y),
  heading:radToSpecific(vectorEuler.z),
}

汇总上述三个属性的转换,完整代码如下:

//将i3s obb转换为Graphic的完整代码
                            const obb=nodepage[i].obb;
                            var point=new Point({
                                type: "point", // autocasts as new Point()
                                x: obb.center[0],
                                y: obb.center[1],
                                z: obb.center[2],
                                spatialReference:{wkid:wkid}
                            });     
                            var vectorQuaternion = new THREE.Quaternion();
                            vectorQuaternion.w = obb.quaternion[3];
                            vectorQuaternion.x = obb.quaternion[0];
                            vectorQuaternion.y = obb.quaternion[1];
                            vectorQuaternion.z = obb.quaternion[2];
                            var vectorEuler = new THREE.Euler(0, 0, 0, eulerOrder);
                            vectorEuler.setFromQuaternion(vectorQuaternion, eulerOrder);
                        if (point !== null) {               
                            let graphic = new Graphic({
                            geometry: satelliteLoc,
                            symbol: {
                                type: "point-3d",  // autocasts as new PointSymbol3D()
                                symbolLayers: [{
                                type: "object",  // autocasts as new ObjectSymbol3DLayer()
                                width: obb.halfSize[0]*2,  // diameter of the object from east to west in meters
                                height: obb.halfSize[2]*2,  // height of the object in meters
                                depth: obb.halfSize[1]*2,  // diameter of the object from north to south in meters
                                resource: { primitive: "cube" },
                                material: { color: [255,0,0,0.5] },
                                tilt:radToSpecific(vectorEuler.x),//around x axis
                                roll:radToSpecific(vectorEuler.y),// around the y axis
                                heading:radToSpecific(vectorEuler.z),//around z     
                                }]
                            },
                            attributes: {
                                ObjectId: nodepage[i].index,
                                all:nodepage[i]
                            }
                            });
                            satelliteLayer.add(graphic);
            
                        }

ObjectSymbol3DLayer的Graphic转换为i3s obb

在前端对obb进行各种变换(如旋转、平移、拉高)的业务结束后,i3s obb最终还是需要以center、halfSize、quaternion的三个属性入库来持久化obb的变更。这时就需要把Graphic对象反向转换回i3s obb json对象里去。和上面同理但过程相反。

//角度转弧度方法
function specificToRad(DegreesIn) {
    if (eulerAngleFormat == "Radians") {return DegreesIn;}
        return THREE.Math.degToRad(DegreesIn);
}
//将名叫grf的graphic赋值给obb
let grf=grfs[k];
let symbolLayer=grf.symbol.symbolLayers.items[0]
let vectorQuaternionresp = new THREE.Quaternion();
let vectorEulerresp = new THREE.Euler(specificToRad(symbolLayer.tilt), 
  specificToRad(symbolLayer.roll), 
  specificToRad(symbolLayer.heading), 
  eulerOrder);
vectorQuaternionresp.setFromEuler(vectorEulerresp);
let obbresp={
  "center":[grf.geometry.x, grf.geometry.y, grf.geometry.z],
  "halfSize":[symbolLayer.width/2, symbolLayer.height/2, symbolLayer.depth/2],
  "quaternion":[vectorQuaternionresp.x, vectorQuaternionresp.y,vectorQuaternionresp.z,vectorQuaternionresp.w]
}

总结

将obb在前端实例化,然后持久化,是实现index 3D SceneLayer在前端整体交互操作的前提。后续我还将介绍如何在Web端交互操作i3s obb 实现已发布的场景图层通过web端进行平移旋转拉高等编辑功能。

你可能感兴趣的:(i3s定向边界盒(OBB)前端实例化与转换方法)