Cesium clipping planes 3dtiles模型剖切 3dtiles模型贴地 vue代码


介绍

使用Cesium的clippingPlanes实现对3dtiles模型的剖切效果。
相关官方文档地址:ClippingPlaneCollectionCesium3DTileset
官方Demo地址:3D Tiles Clipping Planes
官方介绍:Cesium Feature Highlight: Clipping Planes


一、效果

在这里插入图片描述

二、实现步骤

1.add3DTiles()

函数用于加载和显示3D Tiles模型以及剪切平面clipping planes。
在函数内部,首先创建了一个 Cesium.PrimitiveCollection 对象,用于容纳3D Tiles的各个元素。
然后,定义了一个 Cesium.ClippingPlaneCollection 对象,用于存储裁剪平面的信息,并设置了一些裁剪平面的属性。
接着,创建了一个 Cesium.Cesium3DTileset 对象,该对象表示一个3D Tiles数据集,同时将裁剪平面集合传递给这个对象。
最后,将3D Tiles添加到 PrimitiveCollection 中,再将 PrimitiveCollection 添加到Cesium场景的 primitives 中。

代码如下:

add3DTiles() {
        let _this = this
        let tileset = _this.tileset;
        // 启用地形深度测试
        viewer.scene.globe.depthTestAgainstTerrain = true;
      //创建一个primitiveCollection
        var primitiveCollection = new Cesium.PrimitiveCollection();
        let clippingPlanes = _this.clippingPlanes
        // 定义一个ClippingPlaneCollection类,用来存储裁剪面
         clippingPlanes = new Cesium.ClippingPlaneCollection({
            planes: [
                new Cesium.ClippingPlane(
                    new Cesium.Cartesian3(0.0, 0.0, -1.0),
                    0.0
                ),
            ],
            edgeColor : Cesium.Color.RED,
            edgeWidth: 1.0, // 模型被裁切部分的截面线宽
        });
        _this.clippingPlanes = clippingPlanes;
      // 创建一个3D Tileset
         tileset = new Cesium.Cesium3DTileset({
            url:"http://data.mars3d.cn/3dtiles/qx-dyt/tileset.json",
            clippingPlanes: clippingPlanes,
             skipLevelOfDetail: true,
             preferLeaves: true,
             maximumMemoryUsage: 1024,
        });
             _this.tileset = tileset;
        // 将3D Tileset添加到PrimitiveCollection中
         primitiveCollection.add(tileset);
        // 将PrimitiveCollection添加到场景中
        viewer.scene.primitives.add(primitiveCollection);
        viewer.zoomTo(tileset);
      var heightOffset = 6305.8;
      
      //3d tileset模型加载后
        tileset.readyPromise.then(function(tileset) {
        // Position tileset
        var boundingSphere = tileset.boundingSphere;
        var radius = boundingSphere.radius;
        //用于调整模型位置使其贴地
        var cartographic = Cesium.Cartographic.fromCartesian(boundingSphere.center);
        var surface = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0.0);
        var offset = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, heightOffset);
        var translation = Cesium.Cartesian3.subtract(offset, surface, new Cesium.Cartesian3());
        tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation);
        //调整镜头飞向模型
        viewer.zoomTo(tileset,new Cesium.HeadingPitchRange(0.5, -0.2, radius * 4.0))
        viewer.scene.camera.flyToBoundingSphere(tileset.boundingSphere, { duration: 2.0 });
        //处理与裁剪面(clippingPlanes)的位置和瓦片集(tileset)根节点的模型矩阵之间的关系,以确保裁剪面的位置正确与瓦片集对齐
            if (
                !Cesium.Matrix4.equals(
                    tileset.root.transform,
                    Cesium.Matrix4.IDENTITY
                )
            ) {
                // The clipping plane is initially positioned at the tileset's root transform.
                // Apply an additional matrix to center the clipping plane on the bounding sphere center.
                const transformCenter = Cesium.Matrix4.getTranslation(
                    tileset.root.transform,
                    new Cesium.Cartesian3()
                );
                const transformCartographic = Cesium.Cartographic.fromCartesian(
                    transformCenter
                );
                const boundingSphereCartographic = Cesium.Cartographic.fromCartesian(
                    tileset.boundingSphere.center
                );
                const height =
                    boundingSphereCartographic.height - transformCartographic.height;
                clippingPlanes.modelMatrix = Cesium.Matrix4.fromTranslation(
                    new Cesium.Cartesian3(0.0, 0.0, height)
                );
            }

              //加载切面到场景中
          for (var i = 0; i < clippingPlanes.length; ++i) {
              // 创建一个新的plane对象
              var plane = new Cesium.ClippingPlane(
                  clippingPlanes.get(i).normal,
                  clippingPlanes.get(i).distance
                  )
              const planeEntities = viewer.entities.add({
                      position:  boundingSphere.center,
                      plane: {
                          dimensions: new Cesium.Cartesian2(radius * 2.5, radius * 2.5),
                          material: Cesium.Color.WHITE.withAlpha(0.1),
                          plane: new Cesium.CallbackProperty(_this.createPlaneUpdateFunction(plane,i), false),
                          outline: true,
                          outlineColor: Cesium.Color.WHITE,
                      },
                  });
             _this.planeEntities.push(planeEntities);
              }
          return tileset;
      })
    },

2.createPlaneUpdateFunction()

使用回调函数创建了平面的更新函数,以便根据鼠标操作动态更新平面的位置。

代码如下:

createPlaneUpdateFunction(plane,i) {
        let _this = this
            return function () {
                plane.distance = _this.targetY;
                _this.clippingPlanes.get(i).distance = _this.targetY-_this.heightOffset
                return plane;

            };
        },

3.mouseHandler()

定义了鼠标在3D视图中的交互事件处理程序。它包括以下事件处理程序:
LEFT_DOWN:注册了鼠标左键点击事件,如果点击到了一个剪切面对象,则选择该剪切面对象,更改其外观,并禁用了默认的鼠标输入。
LEFT_UP:注册了鼠标左键释放事件,恢复所选剪切面的外观,并重新启用了默认的鼠标输入。
MOUSE_MOVE:注册了鼠标移动事件,计算鼠标在垂直方向上的移动距离,并更新 _this.targetY,用来控制剪切面高度的属性。

代码如下:

mouseHandler(){
        let _this = this;
        let selectedPlane;
        // 注册鼠标按下事件
        viewer.screenSpaceEventHandler.setInputAction(function (movement) {
            var pickedObject = scene.pick(movement.position);
            if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id) && Cesium.defined(pickedObject.id.plane)) {
                selectedPlane = pickedObject.id.plane;
                selectedPlane.material = Cesium.Color.WHITE.withAlpha(0.05);//改变选中切面的颜色
                selectedPlane.outlineColor = Cesium.Color.WHITE;
                scene.screenSpaceCameraController.enableInputs = false; // 取消默认的鼠标一切输入事件
            }
        }, Cesium.ScreenSpaceEventType.LEFT_DOWN);

// 注册鼠标松开事件
        viewer.screenSpaceEventHandler.setInputAction(function () {
            if (Cesium.defined(selectedPlane)) {
                selectedPlane.material = Cesium.Color.WHITE.withAlpha(0.1);//恢复选中前面的颜色
                selectedPlane.outlineColor = Cesium.Color.WHITE;
                selectedPlane = undefined;
            }
            scene.screenSpaceCameraController.enableInputs = true; // 恢复默认的鼠标一切输入事件
        }, Cesium.ScreenSpaceEventType.LEFT_UP);

// 注册鼠标移动事件
        viewer.screenSpaceEventHandler.setInputAction(function (movement) {
            if (Cesium.defined(selectedPlane)) {
                var deltaY = movement.startPosition.y - movement.endPosition.y; // 计算鼠标移动的过程中产生的垂直高度距离
                _this.targetY += deltaY;
            }
        }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
    },

难点总结

需要特别注意的难点是,为了使3dtiles模型贴地,需要有一个高度偏移量heightOffset

 var heightOffset = 6305.8;
      
      //3d tileset模型加载后
        tileset.readyPromise.then(function(tileset) {
        // Position tileset
        var boundingSphere = tileset.boundingSphere;
        var radius = boundingSphere.radius;
        //用于调整模型位置使其贴地
        var cartographic = Cesium.Cartographic.fromCartesian(boundingSphere.center);
        var surface = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0.0);
        var offset = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, heightOffset);
        var translation = Cesium.Cartesian3.subtract(offset, surface, new Cesium.Cartesian3());
        tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation);

这个高度偏移量导致了,实际起剪切效果的剪切面clippingPlanes 和 加载到场景中用于鼠标操控的剪切面plane需要处于不同的高度上,高度应该相差一个heightOffset。
举个例子来讲,当可见可操控的剪切面plane的distance=0时,该剪切面正好与模型相交,但此时实际起剪切效果的剪切面clippingPlanes,需要distance=-heightOffest才能有对应的剪切效果出现。

因此,在createPlaneUpdateFunction()函数中,需要分别控制plane.distance和 _this.clippingPlanes.get(i).distance。


 plane.distance = _this.targetY;
 _this.clippingPlanes.get(i).distance = _this.targetY-_this.heightOffset
           

由于上述的需要,又引出了第二个难点:在 JavaScript 中,变量之间赋值时,如果它们引用的是同一个对象或引用类型,那么修改其中一个变量会影响到另一个变量,因为它们实际上引用的是相同的内存地址。
在分别控制plane.distance和 _this.clippingPlanes.get(i).distance的过程中就遇到了这样的问题。

一开始在加载剪切面到场景中时,plane直接等于clippingPlanes.get(i),这就导致了plane.distance 和 _this.clippingPlanes.get(i).distance,一直互相影响,保持相同的值。

    //加载切面到场景中
          for (var i = 0; i < clippingPlanes.length; ++i) {
             
              // plane直接等于clippingPlanes.get(i)
              var plane = clippingPlanes.get(i),
              
              const planeEntities = viewer.entities.add({
                      position:  boundingSphere.center,
                      plane: {
                          dimensions: new Cesium.Cartesian2(radius * 2.5, radius * 2.5),
                          material: Cesium.Color.WHITE.withAlpha(0.1),
                          plane: new Cesium.CallbackProperty(_this.createPlaneUpdateFunction(plane,i), false),
                          outline: true,
                          outlineColor: Cesium.Color.WHITE,
                      },
                  });
             _this.planeEntities.push(planeEntities);
              }
          return tileset;

为了分别控制plane.distance和 _this.clippingPlanes.get(i).distance,给他们赋不同的值,需要创建一个新的plane对象。这样就能正确实现剪切效果了。正确代码如下所示。


              //加载切面到场景中
          for (var i = 0; i < clippingPlanes.length; ++i) {
              // 创建一个新的plane对象
              var plane = new Cesium.ClippingPlane(
                  clippingPlanes.get(i).normal,
                  clippingPlanes.get(i).distance
                  )
              const planeEntities = viewer.entities.add({
                      position:  boundingSphere.center,
                      plane: {
                          dimensions: new Cesium.Cartesian2(radius * 2.5, radius * 2.5),
                          material: Cesium.Color.WHITE.withAlpha(0.1),
                          plane: new Cesium.CallbackProperty(_this.createPlaneUpdateFunction(plane,i), false),
                          outline: true,
                          outlineColor: Cesium.Color.WHITE,
                      },
                  });
             _this.planeEntities.push(planeEntities);
              }
          return tileset;

主要代码

主要vue代码如下所示


<script>
export default {
  data() {
    return {
        viewers: null,

        targetY: 0,
        planeEntities:[],
        selectedPlane:undefined,
        clippingPlanes:undefined,
        tileset: undefined,
        heightOffset:6305.8,

    };
  },

    methods: {
        
    // 初始化viewer后的传入回调:添加底图和清除加载动画
    created() {
        //TODO 添加三维模型
        this.addMoudleText();
        this.mouseHandler();
    },
	mouseHandler(){
	     let _this = this;
	     let selectedPlane;
	     // 注册鼠标按下事件
	     viewer.screenSpaceEventHandler.setInputAction(function (movement) {
	         var pickedObject = scene.pick(movement.position);
	         if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id) && Cesium.defined(pickedObject.id.plane)) {
	             selectedPlane = pickedObject.id.plane;
	             selectedPlane.material = Cesium.Color.WHITE.withAlpha(0.05);//改变选中切面的颜色
	             selectedPlane.outlineColor = Cesium.Color.WHITE;
	             scene.screenSpaceCameraController.enableInputs = false; // 取消默认的鼠标一切输入事件
	         }
	     }, Cesium.ScreenSpaceEventType.LEFT_DOWN);
	
	// 注册鼠标松开事件
	     viewer.screenSpaceEventHandler.setInputAction(function () {
	         if (Cesium.defined(selectedPlane)) {
	             selectedPlane.material = Cesium.Color.WHITE.withAlpha(0.1);//恢复选中前面的颜色
	             selectedPlane.outlineColor = Cesium.Color.WHITE;
	             selectedPlane = undefined;
	         }
	         scene.screenSpaceCameraController.enableInputs = true; // 恢复默认的鼠标一切输入事件
	     }, Cesium.ScreenSpaceEventType.LEFT_UP);
	
	// 注册鼠标移动事件
	     viewer.screenSpaceEventHandler.setInputAction(function (movement) {
	         if (Cesium.defined(selectedPlane)) {
	             var deltaY = movement.startPosition.y - movement.endPosition.y; // 计算鼠标移动的过程中产生的垂直高度距离
	             _this.targetY += deltaY;
	         }
	     }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
	 },
    add3DTiles() {
        let _this = this
             let tileset = _this.tileset;
        // 启用地形深度测试
        viewer.scene.globe.depthTestAgainstTerrain = true;
      //创建一个primitiveCollection
        var primitiveCollection = new Cesium.PrimitiveCollection();
             let clippingPlanes = _this.clippingPlanes
        // 定义一个ClippingPlaneCollection类,用来存储裁剪面
         clippingPlanes = new Cesium.ClippingPlaneCollection({
            planes: [
                new Cesium.ClippingPlane(
                    new Cesium.Cartesian3(0.0, 0.0, -1.0),
                    0.0
                ),
            ],
            edgeColor : Cesium.Color.RED,
            edgeWidth: 1.0, // 模型被裁切部分的截面线宽
        });
             _this.clippingPlanes = clippingPlanes;
      // 创建一个3D Tileset
         tileset = new Cesium.Cesium3DTileset({
            url:"http://data.mars3d.cn/3dtiles/qx-dyt/tileset.json",
            clippingPlanes: clippingPlanes,
             skipLevelOfDetail: true,
             preferLeaves: true,
             maximumMemoryUsage: 1024,
        });
             _this.tileset = tileset;
             console.log("tileset",tileset)
        // 将3D Tileset添加到PrimitiveCollection中
         primitiveCollection.add(tileset);
        // 将PrimitiveCollection添加到场景中
        viewer.scene.primitives.add(primitiveCollection);
        viewer.zoomTo(tileset);
      var heightOffset = 6305.8;

        tileset.readyPromise.then(function(tileset) {
        // Position tileset
        var boundingSphere = tileset.boundingSphere;
        var radius = boundingSphere.radius;

        var cartographic = Cesium.Cartographic.fromCartesian(boundingSphere.center);
        var surface = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0.0);
        var offset = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, heightOffset);
        var translation = Cesium.Cartesian3.subtract(offset, surface, new Cesium.Cartesian3());
        tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation);
        // var modelMatrix = tileset.modelMatrix

        viewer.zoomTo(tileset,new Cesium.HeadingPitchRange(0.5, -0.2, radius * 4.0))
        // viewer.flyTo(tileset)
        viewer.scene.camera.flyToBoundingSphere(tileset.boundingSphere, { duration: 2.0 });
            if (
                !Cesium.Matrix4.equals(
                    tileset.root.transform,
                    Cesium.Matrix4.IDENTITY
                )
            ) {
                // The clipping plane is initially positioned at the tileset's root transform.
                // Apply an additional matrix to center the clipping plane on the bounding sphere center.
                const transformCenter = Cesium.Matrix4.getTranslation(
                    tileset.root.transform,
                    new Cesium.Cartesian3()
                );
                const transformCartographic = Cesium.Cartographic.fromCartesian(
                    transformCenter
                );
                const boundingSphereCartographic = Cesium.Cartographic.fromCartesian(
                    tileset.boundingSphere.center
                );
                const height =
                    boundingSphereCartographic.height - transformCartographic.height;
                clippingPlanes.modelMatrix = Cesium.Matrix4.fromTranslation(
                    new Cesium.Cartesian3(0.0, 0.0, height)
                );
            }

              //加载切面到场景中
          for (var i = 0; i < clippingPlanes.length; ++i) {
              var plane = new Cesium.ClippingPlane(
                  clippingPlanes.get(i).normal,
                  clippingPlanes.get(i).distance
                  )// 创建一个新的plane对象
              const planeEntities = viewer.entities.add({
                      position:  boundingSphere.center,
                      plane: {
                          dimensions: new Cesium.Cartesian2(radius * 2.5, radius * 2.5),
                          material: Cesium.Color.WHITE.withAlpha(0.1),
                          plane: new Cesium.CallbackProperty(_this.createPlaneUpdateFunction(plane,i), false),
                          outline: true,
                          outlineColor: Cesium.Color.WHITE,
                      },
                  });
             _this.planeEntities.push(planeEntities);
              }
          return tileset;
      })
    },
        createPlaneUpdateFunction(plane,i) {
        let _this = this
            return function () {
                plane.distance = _this.targetY;
                _this.clippingPlanes.get(i).distance = _this.targetY-_this.heightOffset
                return plane;

            };
        },
    addMoudleText() {
      this.add3DTiles();
    }
</script>
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值