Cesium,ClippingPlanes,任意剪裁面對3DTiles剪裁

一、簡介

相關官方文檔地址:ClippingPlaneCollectionCesium3DTileset
官方Demo地址:3D Tiles Clipping Planes
官方介紹:Cesium Feature Highlight: Clipping Planes
對於3DTiles和gltf,均可以通過ClippingPlaneCollection來實現顯示時的模型剪裁顯示,即僅顯示模型的一部分區域。該項特性在cesium1.4開始提供。
由於官方demo和文檔的信息依然較少,在下在業務需求中又遇到了需要將3DTiles按照任意多邊形進行剪裁的需求,因此此篇文章主要是總結了下如何實現該需求。

二、具體實現

業務需求是希望將3DTiles按照任意平行於地表的多邊形進行剪裁顯示,換句話說,剪裁面都是垂直於地表的,因此只考慮這種需求。

圖1 未剪裁前:
未切割的原始3DTiles
圖2 剪裁後:
切割後的3DTiles
關於ClippingPlanes的基本使用在api文檔和demo中有詳細描述。
下面給出一個簡單示例:

 tileSet.clippingPlanes=new Cesium.ClippingPlaneCollection({
      planes: planes,
      unionClippingRegions: true,
    })

各參數不再贅述。
這裏的重點是planes的計算,planes對應一個ClippingPlane對象的數組。ClippingPlane也就是實際使用的剪裁面。

ClippingPlane的計算

構造:

Cesium.ClippingPlane(normal, distance)

由文檔可知,需要計算剪裁面的法向量作爲normal和原點到該剪裁面的最短距離作爲distance。
需注意的是,對於平面的normal,它指向的方向將不會被裁剪,反向的方向纔會被裁剪。
根據文檔中的介紹可知,ClippingPlane所位於的座標系應該是,以3DTiles原點爲座標原點,該點正東爲x軸正方向,該點正北爲y軸正方向,該點的地球橢球體法線爲z軸,遠離地心的那一端爲z軸正方向。
因此可以先將剪裁面的相應計算所需的點,轉換到上述座標系,然後進行計算。
假設一條位於地表的線段p1,p2定義一個剪裁面,那麼計算如下:

1.計算座標轉換需要用到的矩陣的方法:

function getInverseTransform (tileSet) {
  let transform
  let tmp = tileSet.root.transform
  if ((tmp && tmp.equals(Cesium.Matrix4.IDENTITY)) || !tmp) {
  	// 如果root.transform不存在,則3DTiles的原點變成了boundingSphere.center
    transform = Cesium.Transforms.eastNorthUpToFixedFrame(tileSet.boundingSphere.center)
  } else {
    transform = Cesium.Matrix4.fromArray(tileSet.root.transform)
  }
  return Cesium.Matrix4.inverseTransformation(transform, new Cesium.Matrix4())
}

2.對點進行座標轉換的方法:

function getOriginCoordinateSystemPoint (point, inverseTransform) {
  let val = Cesium.Cartesian3.fromDegrees(point.lng, point.lat)
  return Cesium.Matrix4.multiplyByPoint(
    inverseTransform, val, new Cesium.Cartesian3(0, 0, 0))
}

3.具體的計算:

function createPlane (p1, p2, inverseTransform) {
  // 將僅包含經緯度信息的p1,p2,轉換爲相應座標系的cartesian3對象
  let p1C3 = getOriginCoordinateSystemPoint(p1, inverseTransform)
  let p2C3 = getOriginCoordinateSystemPoint(p2, inverseTransform)
 
  // 定義一個垂直向上的向量up
  let up = new Cesium.Cartesian3(0, 0, 10)
  //  right 實際上就是由p1指向p2的向量
  let right = Cesium.Cartesian3.subtract(p2C3, p1C3, new Cesium.Cartesian3())
  
  // 計算normal, right叉乘up,得到平面法向量,這個法向量指向right的右側
  let normal = Cesium.Cartesian3.cross(right, up, new Cesium.Cartesian3())
  normal = Cesium.Cartesian3.normalize(normal, normal)

  //由於已經獲得了法向量和過平面的一點,因此可以直接構造Plane,並進一步構造ClippingPlane
  let planeTmp = Cesium.Plane.fromPointNormal(p1C3, normal)
  return Cesium.ClippingPlane.fromPlane(planeTmp)
}

上面就構造了一個由地平面兩點p1,p2定義的剪裁面,這個剪裁面始終剪裁掉位於p1指向p2的向量的左側。

4.數組的構造
由3可以得到一個剪裁面了。那麼對於數組[p1,p2,p3…pN],當該數組中的點是按照順時針排序時(因爲3中叉乘的方向是right × up),相鄰點兩兩構成一個剪裁面,就可以達到如上圖2的效果。如果數組是按照逆時針排序,還需要將數組reverse下。

如何判斷位於數組中的點,是按照順時針還是逆時針排序?
網上能找到很多資料,這裏我用的是下面的方法:
判斷一個多變形是順時針還是逆時針的方法(含凹多邊形)
下面給出我使用上面算法的具體實現:

function isClockWise (latLngArr) {
  if (latLngArr.length < 3) {
    return null
  }
  if (latLngArr[0] === latLngArr[latLngArr.length - 1]) {
    latLngArr = latLngArr.slice(0, latLngArr.length - 1)
  }
  let latMin = { i: -1, val: 90 }
  for (let i = 0; i < latLngArr.length; i++) {
    let { lat } = latLngArr[i]
    if (lat < latMin.val) {
      latMin.val = lat
      latMin.i = i
    }
  }
  let i1 = (latMin.i + latLngArr.length - 1) % latLngArr.length
  let i2 = latMin.i
  let i3 = (latMin.i + 1) % latLngArr.length

  let v2_1 = {
    lat: latLngArr[i2].lat - latLngArr[i1].lat,
    lng: latLngArr[i2].lng - latLngArr[i1].lng
  }
  let v3_2 = {
    lat: latLngArr[i3].lat - latLngArr[i2].lat,
    lng: latLngArr[i3].lng - latLngArr[i2].lng
  }
  let result = v3_2.lng * v2_1.lat - v2_1.lng * v3_2.lat
  // result>0 3-2在2-1的順時針方向 result<0 3-2在2-1的逆時針方向 result==0 3-2和2-1共線,可能同向也可能反向
  return result === 0 ? (latLngArr[i3].lng < latLngArr[i1].lng) : (result > 0)
}

關於凹多邊形作爲剪裁面

雖然傳值是可以正常傳入凹多邊形的剪裁面,但實際表現情況如下圖:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章