Cesium加载3Dtiles模型的平移和旋转_3dtiles先旋转再平移示例-CSDN博客
Cesium 平移cesiumlab生产的3Dtiles切片模型到目标经纬度-CSDN博客
【ArcGIS+CityEngine】自行制作Lod1城市大尺度白膜数据_cityengine 生成指定坐标集指定区域的白模-CSDN博客
以上次ArcGIS+CityEngine制作的白膜为例:
导出至FBX/OBJ
然后在CesiumLab中转化为3D Tiles模型
但是发现模型位置不正确,默认位置在北京天安门上空,需要将模型调整至指定经纬度位置!!
起初以为只需做平移变化,平移至指定经纬度:
tileset.readyPromise.then(function() {
// // 获取tileset的中心点坐标
const boundingSphere = tileset.boundingSphere
const center = boundingSphere.center
// 将中心点坐标转换为WGS84坐标系下的经纬度
const cartographic = Cesium.Cartographic.fromCartesian(center)
console.log(cartographic)
const longitude = Cesium.Math.toDegrees(cartographic.longitude)
const latitude = Cesium.Math.toDegrees(cartographic.latitude)
const height = Cesium.Math.toDegrees(cartographic.height)
// const startLon = 116.391005;
// const startLat = 39.906623
// 将经纬度调整为研究区域的经纬度
const areaLongitude = 118.792930
// const areaLongitude = 114.150301
const areaLatitude = 31.9707912
// 计算tileset的平移量,并将其应用到modelMatrix中
const translation = Cesium.Cartesian3.fromDegrees(areaLongitude, areaLatitude)
const centerNew = Cesium.Cartesian3.fromDegrees(longitude, latitude)
const translationVector = Cesium.Cartesian3.subtract(translation, centerNew, new Cesium.Cartesian3())
const translationMatrix = Cesium.Matrix4.fromTranslation(translationVector);
console.log(translationMatrix)
tileset.modelMatrix = translationMatrix
viewer.zoomTo(tileset)
})
但是平移的结果存在问题!!!模型为倾斜的,发生旋转了,未贴地,需要调整!!!
why:
在Cesium中,当你尝试在全局坐标系下直接平移3D Tiles数据时,可能会遇到模型变得不平(即模型的几何形状在视觉上扭曲或倾斜)的问题。这通常是因为平移操作没有考虑到原始模型与地球表面之间的相对位置关系。
How:
-
使用地球表面的局部坐标系:
尝试将平移操作转换为地球表面的局部坐标系中的移动。这通常涉及到计算目标位置与当前位置之间的地球表面距离,并沿着地球表面移动模型。然而,Cesium的API可能不直接支持这种操作,因此你可能需要实现一些自定义的逻辑。 -
调整模型矩阵以考虑地球曲率:
如果你直接在模型矩阵中设置平移,你需要确保这个平移是沿着地球表面的切线方向进行的,而不是简单地沿着全局坐标系的X、Y、Z轴。这可能需要你根据目标经纬度计算出一个合适的平移向量,该向量应该考虑到地球表面的曲率
Solution:
将模型平移回世界坐标系原点(地心)
将局部坐标Z轴调整到与世界坐标Z轴重合
将局部坐标X,Y轴调整到与世界坐标X,Y轴重合
将目标位置的eastNorthUp局部坐标系平移回世界坐标系原点(地心)
旋转物体坐标系与目标坐标系重合
平移到目标位置,即为最终变换矩阵
理论基础:GAMES101 Lecture 04 Transformation Cont._哔哩哔哩_bilibili
Cesium中局部平移、旋转3D Tiles模型Cesium局部平移、缩放、旋转思路及代码实现 - 小专栏
平移
解释:
- 红点:frompoint(地表点)
- 蓝点:targetpoint(frompoint局部坐标向东向北向上偏移各 310、-140、10米 后得到的目标点)
- 红向量:地表点向量
- 蓝向量:目标点向量
- 绿向量:平移向量。如果是局部坐标,那么就是 (310, -140, 10),如果是世界坐标下的,那就是 蓝向量 - 红向量
cesium 的场景数据最终都是世界坐标的,所以要求的是绿向量的世界坐标表达,然后构造平移矩阵。
现在是已知红向量和局部坐标的绿向量,要先求蓝向量,才能得到世界坐标的绿向量。
tileset.readyPromise.then(function() { let params = { position: tileset.boundingSphere.center, tx: -1974, ty: 2433, tz: -33.9 } // 重定向坐标 const m = Cesium.Transforms.eastNorthUpToFixedFrame(params.position);//获取到以模型中心为原点,Z轴垂直地表的局部坐标系,以矩阵表示,此矩阵为将局部坐标系变换到世界坐标系的变换矩阵 //设置平移向量 const tempTranslation = new Cesium.Cartesian3(params.tx, params.ty, params.tz);//平移向量 // 获取平移矩阵 const offset = Cesium.Matrix4.multiplyByPoint(m, tempTranslation, new Cesium.Cartesian3(0, 0, 0));//局部坐标中(tx,ty,tz)在世界坐标系中位置 const translation = Cesium.Cartesian3.subtract(offset, params.position, new Cesium.Cartesian3());//终点世界坐标减去原点世界坐标得到世界坐标系下平移向量 // 重新设置其位置大小 tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation); viewer.scene.primitives.add(tileset) viewer.flyTo(tileset) }) },
旋转
function moveModel(tileset,longitude,latitude,height) {
//计算世界坐标系中的目标位置offset
var cartographic = new Cesium.Cartographic.fromCartesian(
tileset.boundingSphere.center
);
var offset = Cesium.Cartesian3.fromDegrees(longitude,latitude,cartographic.height+height);
//将模型位移至地心
const origin = tileset.boundingSphere.center;
const originMatrix = tileset.modelMatrix;//模型的初始变换矩阵
const backToEarthCenter = new Cesium.Cartesian3(-origin.x,-origin.y,-origin.z);//初始位置到地心的位移向量
let backToEarthCenterMatrix = Cesium.Matrix4.fromTranslation(backToEarthCenter);//初始位置到地心的变换矩阵
Cesium.Matrix4.multiply(backToEarthCenterMatrix, originMatrix, backToEarthCenterMatrix);//移动模型到地心的矩阵
// 旋转模型使得Z轴与世界坐标Z轴重合
let arrowX = new Cesium.Cartesian3(1, 0, 0);
let arrowZ = new Cesium.Cartesian3(0, 0, 1);
let angleToXZ = Cesium.Cartesian3.angleBetween(arrowX, new Cesium.Cartesian3(origin.x, origin.y, 0));//局部Z轴在世界坐标系XY平面上投影到X轴角度,即绕Z顺时针旋转这个角度可以到XZ平面上
let angleToZ = Cesium.Cartesian3.angleBetween(origin, arrowZ);//然后绕Y轴顺时针旋转此角度可使得Z轴与世界坐标系Z轴重合
const rotationAngleToXZ = Cesium.Matrix3.fromRotationZ((origin.y>0?-1:+1)*angleToXZ);//绕Z轴旋转的Matrix3矩阵,正角度逆时针旋转
const rotationAngleToZ = Cesium.Matrix3.fromRotationY(-angleToZ);//绕Y轴旋转的Matrix3矩阵,负角度顺时针旋转
let rotationAngleToZMatrix = Cesium.Matrix3.multiply(rotationAngleToZ, rotationAngleToXZ, new Cesium.Matrix3);//连续旋转的Matrix3矩阵,即先绕Z轴旋转,后绕Y旋转的矩阵。
rotationAngleToZMatrix = Cesium.Matrix4.fromRotationTranslation(rotationAngleToZMatrix);//连续旋转的Matrix4矩阵
Cesium.Matrix4.multiply(rotationAngleToZMatrix, backToEarthCenterMatrix, rotationAngleToZMatrix);//将移动至地心模型,旋转至Z轴重合的矩阵
// 旋转模型使得X,Y轴与世界坐标X,Y轴重合
const rotationZ = Cesium.Matrix3.fromRotationZ(-Math.PI/2); // 绕Z轴旋转90°的Matrix3变换矩阵
let rotationMatrix = Cesium.Matrix4.fromRotationTranslation(rotationZ); // 绕Z轴旋转90°的Matrix4变换矩阵
Cesium.Matrix4.multiply(rotationMatrix, rotationAngleToZMatrix, rotationMatrix);//将移动至地心模型的物体坐标系,旋转到与世界坐标系重合的矩阵
//在地心位置,旋转物体坐标系和世界坐标系重合的模型,使得与目标坐标系重合
const offsetToWorldMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(offset);//获取到以目标位置为原点,的eastNorthUp局部坐标系的变换矩阵
const backToEarthCenterOffset = new Cesium.Cartesian3(-offset.x, -offset.y, -offset.z);//目标位置到地心的位移向量
let backToEarthCenterMatrixOffset = Cesium.Matrix4.fromTranslation(backToEarthCenterOffset);//目标位置到地心的变换矩阵
Cesium.Matrix4.multiply(backToEarthCenterMatrixOffset, offsetToWorldMatrix, backToEarthCenterMatrixOffset);//获得从世界坐标系旋转至目标坐标系的旋转矩阵(只有旋转,没有位移)
Cesium.Matrix4.multiply(backToEarthCenterMatrixOffset, rotationMatrix, backToEarthCenterMatrixOffset);//将移动至地心模型的物体坐标系,旋转到与目标坐标系重合的矩阵(完成模型的最终旋转,没有位移)
//移动到目标位置
const backToOriginMatrix = Cesium.Matrix4.fromTranslation(offset);//地心到目标位置位移向量
const lastMatrix = Cesium.Matrix4.multiply(backToOriginMatrix,backToEarthCenterMatrixOffset,new Cesium.Matrix4());//最终矩阵,即将地心位置的模型移动到目标位置(完成模型的最终旋转,最终位移)
console.log('最终变换矩阵',lastMatrix);
return lastMatrix //返回最终变换矩阵
}
------------------------------------------------------------------------------------------
let tileset = viewer.scene.primitives.add(
new Cesium.Cesium3DTileset({
url: ".../tileset.json",
})
);
tileset.readyPromise.then(function (tileset) {
window.tileset = tileset
let longitude = 104.98680
let latitude = 32.20795
let height = 100
let modelMatrix = moveModel(tileset,longitude,latitude,height)
tileset.modelMatrix = modelMatrix;//移动模型
// 创建圆形包围盒
let boundingSphere = new Cesium.BoundingSphere(
tileset.boundingSphere.center,
tileset.boundingSphere.radius
);
//飞向该包围盒
viewer.camera.flyToBoundingSphere(boundingSphere);
});