Cesium的官方示例中提供了3D模型裁切获取剖切面的示例3DTilesClippingPlanes,相信很多初次接触cesium的小伙伴第一次看时都会和我一样,虽然本地能跑起来,但是对整个流程缺乏整体的理解,最近整理了一下这个流程并记录下来。
在这里,要简单说明下模型的转换矩阵,根节点的transform,其实就是整个模型对于cesium中心的转移矩阵,他是一个4*4数组,cesium中的getTranslation方法实现了通过转换矩阵获取笛卡尔坐标的方式,通过源码可以发现转换矩阵的13-15项,实际上就是该模型的笛卡尔坐标(x,y,z)。
这个流程里还用到的方法包括:
这部分代码如下:
function loadTileset(url) {
// 定义一个ClippingPlaneCollection类,用来存储裁剪面
clippingPlanes = new Cesium.ClippingPlaneCollection({
planes: [ // ClippingPlane对象数组集合
new Cesium.ClippingPlane( // 裁切面
new Cesium.Cartesian3(0.0, 0.0, -1.0), // 法线方向
0.0 // 原点到平面的最短距离,设置0就好
),
],
edgeWidth: 1.0, // 模型被裁切部分的截面线宽
});
tileset = viewer.scene.primitives.add(
new Cesium.Cesium3DTileset({
url: url,
clippingPlanes: clippingPlanes,
})
);
tileset.debugShowBoundingVolume = true
return tileset.readyPromise
.then(function () {
// cesium.BoundingSphere(center:Cartersian3, radius:number), tileset的球状边界
const boundingSphere = tileset.boundingSphere;
const radius = boundingSphere.radius;
viewer.zoomTo(
tileset,
new Cesium.HeadingPitchRange(0.5, -0.2, radius * 4.0)
);
debugger;
if (
!Cesium.Matrix4.equals( // Cesium.Matrix4.equals(a,b)判断两个四维矩阵是否相等
tileset.root.transform, // 整个根节点模型矩阵,该tileSet=>世界坐标系
Cesium.Matrix4.IDENTITY // 单位矩阵,对角线值为1.0的4*4矩阵
)
) {
// 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.
// 获取模型的世界坐标(笛卡尔)
// Cesium.Matrix4.getTranslation 通过仿射变换矩阵获取该tileSet的世界坐标
const transformCenter = Cesium.Matrix4.getTranslation(
tileset.root.transform,
new Cesium.Cartesian3()
);
debugger;
// 将笛卡尔坐标转换为WGS84经纬度坐标(模型的)
const transformCartographic = Cesium.Cartographic.fromCartesian(
transformCenter
);
// 将笛卡尔坐标转换为WGS84经纬度坐标(截面的)
const boundingSphereCartographic = Cesium.Cartographic.fromCartesian(
tileset.boundingSphere.center
);
const height =
boundingSphereCartographic.height - transformCartographic.height;
// 从一个Cartesian3对象生成Matrix4变换矩阵(裁切面的)
clippingPlanes.modelMatrix = Cesium.Matrix4.fromTranslation(
new Cesium.Cartesian3(0.0, 0.0, height)
);
}
// debugger;
// 创建添加裁剪平面
for (let i = 0; i < clippingPlanes.length; ++i) {
const plane = clippingPlanes.get(i);
const planeEntity = 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(
// 添加绑定事件,不断调用
createPlaneUpdateFunction(plane),
false
),
outline: true,
outlineColor: Cesium.Color.WHITE,
},
});
planeEntities.push(planeEntity);
}
return tileset;
})
.catch(function (error) {
console.log(error);
});
}
分别给鼠标绑定三种事件:LEFT_DOWN,LEFT_UP,MOUSE_MOVE。当MOUSE_MOVE触发时,会不断刷新targetY 的值。LEFT_DOWN,LEFT_UP代表左键按下弹起事件,触发时需要设置scene.screenSpaceCameraController.enableInputs 禁用/恢复原本鼠标默认事件(放大缩小)。targetY值发生变化时,不断回调的CallbackProperty 会获取最新的targetY ,重新设置裁切面的distance属性实现动画效果,至于模型是怎么随着裁切面的位置隐藏的,可以去看一下源码Cesium3DTileset中对clippingPlanes这个参数的具体实现。
// 左键按下
const downHandler = new Cesium.ScreenSpaceEventHandler(
viewer.scene.canvas
);
downHandler.setInputAction(function (movement) {
const pickedObject = scene.pick(movement.position);
console.log('pickedObject---downHandler---', pickedObject)
if (
Cesium.defined(pickedObject) &&
Cesium.defined(pickedObject.id.plane)
) {
selectedPlane = pickedObject.id.plane;
selectedPlane.material = Cesium.Color.WHITE.withAlpha(0.5);
selectedPlane.outlineColor = Cesium.Color.WHITE;
scene.screenSpaceCameraController.enableInputs = false;
}
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);
// Release plane on mouse up
const upHandler = new Cesium.ScreenSpaceEventHandler(
viewer.scene.canvas
);
// 左键松开
upHandler.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);
// Update plane on mouse move
const moveHandler = new Cesium.ScreenSpaceEventHandler(
viewer.scene.canvas
);
// 鼠标移动事件
moveHandler.setInputAction(function (movement) {
// console.log('moveHandler+++++')
if (Cesium.defined(selectedPlane)) {
console.log('moveHandler---')
console.log(movement)
const deltaY = movement.startPosition.y - movement.endPosition.y;
targetY += deltaY;
console.log(targetY)
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
用的是react,直接放到useEffect里了,相当于Vue的mounted生命周期。
// @ts-nocheck
import React, { useState, useEffect, useRef, useCallback } from 'react'
import * as Cesium from 'cesium'
const Clip = () => {
useEffect(() => {
// Add a clipping plane, a plane geometry to show the representation of the
// plane, and control the magnitude of the plane distance with the mouse.
const viewer = new Cesium.Viewer("cesiumContainer", {
infoBox: false,
selectionIndicator: false,
});
const scene = viewer.scene;
let targetY = 0.0;
let planeEntities = [];
let selectedPlane;
let clippingPlanes;
// Select plane when mouse down
// 绑定上移动事件
const downHandler = new Cesium.ScreenSpaceEventHandler(
viewer.scene.canvas
);
downHandler.setInputAction(function (movement) {
const pickedObject = scene.pick(movement.position);
console.log('pickedObject---downHandler---', pickedObject)
if (
Cesium.defined(pickedObject) &&
Cesium.defined(pickedObject.id.plane)
) {
selectedPlane = pickedObject.id.plane;
selectedPlane.material = Cesium.Color.WHITE.withAlpha(0.5);
selectedPlane.outlineColor = Cesium.Color.WHITE;
scene.screenSpaceCameraController.enableInputs = false;
}
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);
// Release plane on mouse up
// 绑定下移动事件
const upHandler = new Cesium.ScreenSpaceEventHandler(
viewer.scene.canvas
);
upHandler.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);
// Update plane on mouse move
const moveHandler = new Cesium.ScreenSpaceEventHandler(
viewer.scene.canvas
);
moveHandler.setInputAction(function (movement) {
// console.log('moveHandler+++++')
if (Cesium.defined(selectedPlane)) {
console.log('moveHandler---')
console.log(movement)
const deltaY = movement.startPosition.y - movement.endPosition.y;
targetY += deltaY;
console.log(targetY)
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
function createPlaneUpdateFunction(plane) {
return function () {
plane.distance = targetY;
return plane;
};
}
let tileset: Cesium.Cesium3DTileset;
function loadTileset(url) {
// 定义一个ClippingPlaneCollection类,用来存储裁剪面
clippingPlanes = new Cesium.ClippingPlaneCollection({
planes: [ // ClippingPlane对象数组集合
new Cesium.ClippingPlane( // 裁切面
new Cesium.Cartesian3(0.0, 0.0, -1.0), // 法线方向
0.0 // 原点到平面的最短距离,设置0就好
),
],
edgeWidth: 1.0, // 模型被裁切部分的截面线宽
});
tileset = viewer.scene.primitives.add(
new Cesium.Cesium3DTileset({
url: url,
clippingPlanes: clippingPlanes,
})
);
tileset.debugShowBoundingVolume = true
return tileset.readyPromise
.then(function () {
// cesium.BoundingSphere(center:Cartersian3, radius:number), tileset的球状边界
const boundingSphere = tileset.boundingSphere;
const radius = boundingSphere.radius;
viewer.zoomTo(
tileset,
new Cesium.HeadingPitchRange(0.5, -0.2, radius * 4.0)
);
debugger;
if (
!Cesium.Matrix4.equals( // Cesium.Matrix4.equals(a,b)判断两个四维矩阵是否相等
tileset.root.transform, // 整个根节点模型矩阵,该tileSet=>世界坐标系
Cesium.Matrix4.IDENTITY // 单位矩阵,对角线值为1.0的4*4矩阵
)
) {
// 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.
// 获取模型的世界坐标(笛卡尔)
// Cesium.Matrix4.getTranslation 通过仿射变换矩阵获取该tileSet的世界坐标
const transformCenter = Cesium.Matrix4.getTranslation(
tileset.root.transform,
new Cesium.Cartesian3()
);
debugger;
// 将笛卡尔坐标转换为WGS84经纬度坐标(模型的)
const transformCartographic = Cesium.Cartographic.fromCartesian(
transformCenter
);
// 将笛卡尔坐标转换为WGS84经纬度坐标(截面的)
const boundingSphereCartographic = Cesium.Cartographic.fromCartesian(
tileset.boundingSphere.center
);
const height =
boundingSphereCartographic.height - transformCartographic.height;
// 从一个Cartesian3对象生成Matrix4变换矩阵(裁切面的)
clippingPlanes.modelMatrix = Cesium.Matrix4.fromTranslation(
new Cesium.Cartesian3(0.0, 0.0, height)
);
}
// debugger;
// 创建添加裁剪平面
for (let i = 0; i < clippingPlanes.length; ++i) {
const plane = clippingPlanes.get(i);
const planeEntity = 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(
// 添加绑定事件,不断调用
createPlaneUpdateFunction(plane),
false
),
outline: true,
outlineColor: Cesium.Color.WHITE,
},
});
planeEntities.push(planeEntity);
}
return tileset;
})
.catch(function (error) {
console.log(error);
});
}
// function loadModel(url) {
// clippingPlanes = new Cesium.ClippingPlaneCollection({
// planes: [
// new Cesium.ClippingPlane(
// new Cesium.Cartesian3(0.0, 0.0, -1.0),
// 0.0
// ),
// ],
// edgeWidth: 1.0 ,
// });
// const position = Cesium.Cartesian3.fromDegrees(
// -123.0744619,
// 44.0503706,
// 300.0
// );
// const heading = Cesium.Math.toRadians(135.0);
// const pitch = 0.0;
// const roll = 0.0;
// const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
// const orientation = Cesium.Transforms.headingPitchRollQuaternion(
// position,
// hpr
// );
// const entity = viewer.entities.add({
// name: url,
// position: position,
// orientation: orientation,
// model: {
// uri: url,
// scale: 8,
// minimumPixelSize: 100.0,
// clippingPlanes: clippingPlanes,
// },
// });
// viewer.trackedEntity = entity;
// for (let i = 0; i < clippingPlanes.length; ++i) {
// const plane = clippingPlanes.get(i);
// const planeEntity = viewer.entities.add({
// position: position,
// plane: {
// dimensions: new Cesium.Cartesian2(300.0, 300.0),
// material: Cesium.Color.WHITE.withAlpha(0.1),
// plane: new Cesium.CallbackProperty(
// createPlaneUpdateFunction(plane),
// false
// ),
// outline: true,
// outlineColor: Cesium.Color.WHITE,
// },
// });
// planeEntities.push(planeEntity);
// }
// }
// Power Plant design model provided by Bentley Systems
const bimUrl = Cesium.IonResource.fromAssetId(8564);
// const pointCloudUrl = Cesium.IonResource.fromAssetId(16421);
// const instancedUrl =
// "../SampleData/Cesium3DTiles/Instanced/InstancedOrientation/tileset.json";
// const modelUrl = "../SampleData/models/CesiumAir/Cesium_Air.glb";
loadTileset(bimUrl);
// Track and create the bindings for the view model
// function reset() {
// viewer.entities.removeAll();
// viewer.scene.primitives.remove(tileset);
// planeEntities = [];
// targetY = 0.0;
// tileset = undefined;
// }
}, [])
return (
<>
<div id="cesiumContainer" className="fullSize" style={{ height: '100%' }}></div>
</>
)
}
export default Clip
经验就是,多查官网文档,多打断点调试看看每一步生成什么,慢慢就理解了。