一、简介

相关官方文档地址: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())
}

关于原点的一点补充,原点的选择直接影响到ClippingPlane的构造,ClippingPlaneCollection的文档中提到了这一点,如果root.transform存在,需要使用root.transform对应的坐标原点作为构造ClippingPlane时的坐标原点,如果不存在的话,就使用boundingSphere.center。
这也是显而易见的,因为如果root.transform存在并且不等于Cesium.Matrix4.IDENTITY,就说明3DTiles的根瓦片存在一个自身定义的相对变换。此时包围盒的中心点和3DTiles中对图形顶点定位的局部坐标系坐标原点并不重合。
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)
}

关于凹多边形作为剪裁面

虽然传值是可以正常传入凹多边形的剪裁面,但实际表现情况如下图:
在这里插入图片描述