const flags = {
|
// 剖切是否开启
|
opened: false,
|
// 是否选中剖面框
|
selected: false,
|
};
|
|
let leftDownFlag = false;
|
let dragedAnchor = null;
|
let prevPickPosition = null;
|
|
// 裁剪目标
|
let clippingTargetArray = [];
|
// 事件
|
let clippingHandler = null;
|
|
// 剖面框
|
let clippingEntityArray = [];
|
// 剖面框6个面的锚点
|
let anchorEntityArray = [];
|
// 剖面框6个面的方向
|
const directions = ['front', 'behind', 'left', 'right', 'top', 'bottom'];
|
// 颜色:默认、移动、选中
|
const colors = [Cesium.Color.WHITE, Cesium.Color.YELLOW, Cesium.Color.YELLOW.withAlpha(0.5)];
|
|
// 记录剖面框数据
|
const clipdata = {
|
// 记录模型原点
|
center: null,
|
// 记录6个剖面中心点
|
centers: [],
|
// 记录6个剖面方向向量(含变换)
|
normals: [],
|
};
|
|
/**
|
* 剖面框
|
*
|
* @alias ClippingBox
|
* @constructor
|
*
|
* @param {Cesium.Viewer} viewer Cesium.Viewer的实例对象
|
*/
|
function ClippingBox(viewer) {
|
this._viewer = viewer;
|
|
// 鼠标移动事件
|
clippingHandler = new Cesium.ScreenSpaceEventHandler(this._viewer.scene.canvas);
|
clippingHandler.setInputAction((e) => {
|
if (flags.opened === true) {
|
const pickedObject = this._viewer.scene.pick(e.endPosition);
|
if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id) && Cesium.defined(pickedObject.id.box)) {
|
if (clippingEntityArray.length > 0) {
|
clippingEntityArray[0].box.outlineColor = colors[1];
|
}
|
} else {
|
if (clippingEntityArray.length > 0) {
|
clippingEntityArray[0].box.outlineColor = flags.selected ? colors[2] : colors[0];
|
}
|
}
|
}
|
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
|
|
// 鼠标点击事件
|
clippingHandler.setInputAction((e) => {
|
if (flags.opened === true) {
|
// 删除锚点
|
for (let i = 0; i < anchorEntityArray.length; i++) {
|
this._viewer.entities.remove(anchorEntityArray[i]);
|
}
|
anchorEntityArray = [];
|
|
const pickedObject = this._viewer.scene.pick(e.position);
|
if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id) && Cesium.defined(pickedObject.id.box)) {
|
if (clippingEntityArray.length > 0) {
|
clippingEntityArray[0].box.outlineColor = colors[2];
|
flags.selected = true;
|
}
|
|
// 重新计算锚点
|
for (let i = 0; i < clipdata.centers.length; i++) {
|
const anchorEntity = addPoint(this._viewer, clipdata.centers[i], directions[i]);
|
anchorEntityArray.push(anchorEntity);
|
}
|
} else if (flags.selected) {
|
flags.selected = false;
|
if (clippingEntityArray.length > 0) {
|
clippingEntityArray[0].box.outlineColor = colors[0];
|
}
|
}
|
}
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
|
// 是否是锚点
|
function isAnchor(pickedObject) {
|
return pickedObject && pickedObject.id && pickedObject.id.direction && directions.indexOf(pickedObject.id.direction) !== -1;
|
}
|
|
// 同步当前锚点(处理前后左右)
|
function updateFBLRAnchor(dragedAnchor, index, pickPosition) {
|
// 计算投影向量
|
const translation = new Cesium.Cartesian3();
|
if (prevPickPosition) {
|
Cesium.Cartesian3.subtract(pickPosition, prevPickPosition, translation);
|
Cesium.Cartesian3.projectVector(translation, clipdata.normals[index], translation);
|
|
// 计算新的中心点并同步锚点
|
const position = new Cesium.Cartesian3();
|
Cesium.Cartesian3.add(clipdata.centers[index], translation, position);
|
clipdata.centers[index] = position.clone();
|
dragedAnchor.id.position = position.clone();
|
}
|
prevPickPosition = pickPosition;
|
return translation;
|
}
|
|
// 同步当前锚点(处理上下)
|
function updateTBAnchor(dragedAnchor, index, startPosition, endPosition) {
|
// 计算投影向量
|
const v = Cesium.Cartesian2.subtract(startPosition, endPosition, new Cesium.Cartesian2());
|
const delta = Math.abs(v.x) > Math.abs(v.y) ? v.x : v.y;
|
|
const translation = new Cesium.Cartesian3();
|
Cesium.Cartesian3.multiplyByScalar(Cesium.Cartesian3.UNIT_Z, delta, translation);
|
Cesium.Cartesian3.projectVector(translation, clipdata.normals[index], translation);
|
|
// 计算新的中心点并同步锚点
|
const position = new Cesium.Cartesian3();
|
Cesium.Cartesian3.add(clipdata.centers[index], translation, position);
|
clipdata.centers[index] = position.clone();
|
dragedAnchor.id.position = position.clone();
|
|
return translation;
|
}
|
|
// 同步相邻锚点
|
function updateAdjoinAnchorEntity(index, originCenter, translation) {
|
const position = new Cesium.Cartesian3();
|
Cesium.Cartesian3.add(originCenter, translation, position);
|
clipdata.centers[index] = position.clone();
|
anchorEntityArray[index].position = position.clone();
|
// 更新中心点
|
updateOringin();
|
}
|
|
// 同步虚拟剖面框
|
function updateClippingEntityPosition(clippingEntity, center, oppCenter) {
|
const position = new Cesium.Cartesian3();
|
Cesium.Cartesian3.midpoint(center, oppCenter, position);
|
clippingEntity.position = position;
|
}
|
|
// 更新真实剖面框
|
function updateClippingPlanes(index, translation) {
|
if (index < 0 || index > 5) return;
|
if (clippingTargetArray.length > 0) {
|
const clippingPlanes = clippingTargetArray[0].clippingPlanes;
|
const plane = clippingPlanes.get(index);
|
if (plane.direction === 'front' || plane.direction === 'behind') {
|
const distance = Cesium.Cartesian3.distance(clipdata.centers[0], clipdata.centers[1]);
|
const plane1 = clippingPlanes.get(0);
|
plane1.distance = distance / 2;
|
const plane2 = clippingPlanes.get(1);
|
plane2.distance = distance / 2;
|
} else if (plane.direction === 'left' || plane.direction === 'right') {
|
const distance = Cesium.Cartesian3.distance(clipdata.centers[2], clipdata.centers[3]);
|
const plane1 = clippingPlanes.get(2);
|
plane1.distance = distance / 2;
|
const plane2 = clippingPlanes.get(3);
|
plane2.distance = distance / 2;
|
} else if (plane.direction === 'top' || plane.direction === 'bottom') {
|
const distance = Cesium.Cartesian3.distance(clipdata.centers[4], clipdata.centers[5]);
|
const plane1 = clippingPlanes.get(4);
|
plane1.distance = distance / 2;
|
const plane2 = clippingPlanes.get(5);
|
plane2.distance = distance / 2;
|
}
|
// 同步真实剖面框
|
const m3 = new Cesium.Matrix3();
|
Cesium.Matrix4.getMatrix3(clippingTargetArray[0].root.transform, m3);
|
Cesium.Matrix3.inverse(m3, m3);
|
const vector = Cesium.Matrix3.multiplyByVector(m3, translation, new Cesium.Cartesian3());
|
const m4 = new Cesium.Matrix4();
|
Cesium.Matrix4.fromTranslation(vector, m4);
|
Cesium.Matrix4.multiply(clippingPlanes.modelMatrix, m4, m4);
|
clippingPlanes.modelMatrix = m4;
|
}
|
}
|
|
// 拖拽锚点,缩放剖面框
|
this._viewer.screenSpaceEventHandler.setInputAction((e) => {
|
const pickedObject = this._viewer.scene.pick(e.position);
|
if (isAnchor(pickedObject)) {
|
leftDownFlag = true;
|
dragedAnchor = pickedObject;
|
this._viewer.scene.screenSpaceCameraController.enableInputs = false;
|
}
|
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);
|
this._viewer.screenSpaceEventHandler.setInputAction((e) => {
|
this._viewer.scene.screenSpaceCameraController.enableInputs = true;
|
|
leftDownFlag = false;
|
dragedAnchor = null;
|
prevPickPosition = null;
|
}, Cesium.ScreenSpaceEventType.LEFT_UP);
|
this._viewer.screenSpaceEventHandler.setInputAction((e) => {
|
if (leftDownFlag === true && dragedAnchor != null) {
|
const ray = this._viewer.camera.getPickRay(e.endPosition);
|
const pickPosition = this._viewer.scene.globe.pick(ray, this._viewer.scene);
|
if (pickPosition && clipdata.centers.length === 6 && clipdata.normals.length === 6 && clippingEntityArray.length > 0) {
|
// 计算锚点索引
|
const index = directions.indexOf(dragedAnchor.id.direction);
|
if (dragedAnchor.id.direction === 'front' || dragedAnchor.id.direction === 'behind') {
|
const translation = updateFBLRAnchor(dragedAnchor, index, pickPosition);
|
// 同步和中心相邻的点:前后左右上下中的左右上下,同时同步锚点
|
Cesium.Cartesian3.divideByScalar(translation, 2, translation);
|
updateAdjoinAnchorEntity(2, clipdata.centers[2], translation);
|
updateAdjoinAnchorEntity(3, clipdata.centers[3], translation);
|
updateAdjoinAnchorEntity(4, clipdata.centers[4], translation);
|
updateAdjoinAnchorEntity(5, clipdata.centers[5], translation);
|
// 同步虚拟剖面框
|
updateClippingEntityPosition(clippingEntityArray[0], clipdata.centers[0], clipdata.centers[1]);
|
// 同步真实剖面框
|
updateClippingPlanes(index, translation);
|
} else if (dragedAnchor.id.direction === 'left' || dragedAnchor.id.direction === 'right') {
|
const translation = updateFBLRAnchor(dragedAnchor, index, pickPosition);
|
// 同步和中心相邻的点:前后左右上下中的前后上下,同时同步锚点
|
Cesium.Cartesian3.divideByScalar(translation, 2, translation);
|
updateAdjoinAnchorEntity(0, clipdata.centers[0], translation);
|
updateAdjoinAnchorEntity(1, clipdata.centers[1], translation);
|
updateAdjoinAnchorEntity(4, clipdata.centers[4], translation);
|
updateAdjoinAnchorEntity(5, clipdata.centers[5], translation);
|
// 同步虚拟剖面框
|
updateClippingEntityPosition(clippingEntityArray[0], clipdata.centers[2], clipdata.centers[3]);
|
// 同步真实剖面框
|
updateClippingPlanes(index, translation);
|
} else if (dragedAnchor.id.direction === 'top' || dragedAnchor.id.direction === 'bottom') {
|
const translation = updateTBAnchor(dragedAnchor, index, e.startPosition, e.endPosition);
|
// 同步和中心相邻的点:前后左右上下中的前后左右,同时同步锚点
|
Cesium.Cartesian3.divideByScalar(translation, 2, translation);
|
updateAdjoinAnchorEntity(0, clipdata.centers[0], translation);
|
updateAdjoinAnchorEntity(1, clipdata.centers[1], translation);
|
updateAdjoinAnchorEntity(2, clipdata.centers[2], translation);
|
updateAdjoinAnchorEntity(3, clipdata.centers[3], translation);
|
// 同步虚拟剖面框
|
updateClippingEntityPosition(clippingEntityArray[0], clipdata.centers[4], clipdata.centers[5]);
|
// 同步真实剖面框
|
updateClippingPlanes(index, translation);
|
}
|
}
|
}
|
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
|
}
|
|
/**
|
* 剖切是否开启
|
*/
|
ClippingBox.prototype.isOpened = function () {
|
return flags.opened;
|
};
|
|
/**
|
* 开启剖切
|
*/
|
ClippingBox.prototype.open = function () {
|
if (flags.opened === false) {
|
flags.opened = true;
|
}
|
};
|
|
/**
|
* 关闭剖切
|
*/
|
ClippingBox.prototype.close = function () {
|
if (flags.opened === true) {
|
flags.opened = false;
|
flags.selected = false;
|
}
|
|
// 删除锚点
|
for (let i = 0; i < anchorEntityArray.length; i++) {
|
this._viewer.entities.remove(anchorEntityArray[i]);
|
}
|
anchorEntityArray = [];
|
|
// 删除剖面框
|
if (clippingEntityArray.length > 0) {
|
this._viewer.entities.remove(clippingEntityArray[0]);
|
clippingEntityArray = [];
|
}
|
|
// 清除目标对象
|
if (clippingTargetArray.length > 0) {
|
clippingTargetArray[0].clippingPlanes.enabled = false;
|
clippingTargetArray = [];
|
}
|
|
// 清除data
|
clipdata.centers = [];
|
clipdata.normals = [];
|
};
|
|
/**
|
* 设置剖切对象
|
*
|
* @param {Cesium.Cesium3DTileset} target
|
*/
|
ClippingBox.prototype.setTarget = function (target) {
|
if (!flags.opened) return;
|
if (clippingTargetArray.length > 0 && clippingTargetArray[0] === target) return;
|
|
if (target instanceof Cesium.Cesium3DTileset) {
|
this.set3DTileset(target);
|
}
|
};
|
|
ClippingBox.prototype.set3DTileset = function (tileset) {
|
clippingTargetArray.push(tileset);
|
let that = this;
|
tileset.readyPromise.then(() => {
|
tileset.clippingPlanes = createClippingPlanes();
|
|
const boundingSphere = tileset.boundingSphere;
|
const center = boundingSphere.center;
|
const radius = boundingSphere.radius;
|
|
// 计算剖面框中心位置
|
clipdata.center = center.clone();
|
const cartCenter = Cesium.Cartographic.fromCartesian(clipdata.center);
|
|
// 计算剖面框旋转
|
const m3 = Cesium.Matrix4.getMatrix3(tileset.root.transform, new Cesium.Matrix3());
|
const quaternion = Cesium.Quaternion.fromRotationMatrix(m3, new Cesium.Quaternion());
|
|
const clippingPlanes = tileset.clippingPlanes;
|
for (let i = 0; i < clippingPlanes.length; ++i) {
|
const plane = clippingPlanes.get(i);
|
plane.distance = radius / 2.0;
|
|
if (plane.direction === 'front' || plane.direction === 'behind' || plane.direction === 'left' || plane.direction === 'right') {
|
// 先将平面向量于模型矩阵对齐
|
const hpr = this.quaternionToHeadingPitchRoll(clipdata.center, quaternion);
|
const m = Cesium.Matrix3.fromHeadingPitchRoll(hpr, new Cesium.Matrix3());
|
const normal = Cesium.Matrix3.multiplyByVector(m, plane.normal, new Cesium.Cartesian3());
|
// 参照中心点计算投影点
|
const localCartesian3 = Cesium.Cartesian3.multiplyByScalar(normal, plane.distance, new Cesium.Cartesian3());
|
const tangentPlane = new Cesium.EllipsoidTangentPlane(clipdata.center);
|
const projectCartesian3 = tangentPlane.projectPointOntoEllipsoid(localCartesian3);
|
const projectCart = Cesium.Cartographic.fromCartesian(projectCartesian3);
|
const anchorCenter = new Cesium.Cartographic(projectCart.longitude, projectCart.latitude, cartCenter.height);
|
clipdata.centers.push(Cesium.Cartographic.toCartesian(anchorCenter));
|
} else if (plane.direction === 'top' || plane.direction === 'bottom') {
|
const offset = Cesium.Cartesian3.multiplyByScalar(plane.normal, plane.distance, new Cesium.Cartesian3());
|
const anchorCenter = new Cesium.Cartographic(cartCenter.longitude, cartCenter.latitude, cartCenter.height + offset.z);
|
clipdata.centers.push(Cesium.Cartographic.toCartesian(anchorCenter));
|
}
|
}
|
|
// 计算法向量
|
function normalize(startPosition, endPosition) {
|
const normal = new Cesium.Cartesian3();
|
Cesium.Cartesian3.subtract(startPosition, endPosition, normal);
|
Cesium.Cartesian3.normalize(normal, normal);
|
return normal;
|
}
|
|
// 计算法向量
|
clipdata.normals.push(normalize(clipdata.centers[0], clipdata.centers[1]));
|
clipdata.normals.push(normalize(clipdata.centers[1], clipdata.centers[0]));
|
clipdata.normals.push(normalize(clipdata.centers[2], clipdata.centers[3]));
|
clipdata.normals.push(normalize(clipdata.centers[3], clipdata.centers[2]));
|
clipdata.normals.push(normalize(clipdata.centers[4], clipdata.centers[5]));
|
clipdata.normals.push(normalize(clipdata.centers[5], clipdata.centers[4]));
|
|
// 更新中心点
|
updateOringin();
|
|
const clippingEntity = this._viewer.entities.add({
|
id: 'ClippingEntity',
|
position: new Cesium.CallbackProperty(() => {
|
return clipdata.center;
|
}, false),
|
orientation: quaternion,
|
box: {
|
dimensions: new Cesium.CallbackProperty(updateDimensions(), false),
|
material: Cesium.Color.RED.withAlpha(0),
|
outline: true,
|
outlineColor: Cesium.Color.WHITE,
|
},
|
});
|
clippingEntityArray.push(clippingEntity);
|
|
// 移动真实剖面框(真实剖面框是根据transform变换的)
|
const transformCenter = Cesium.Matrix4.getTranslation(tileset.root.transform, new Cesium.Cartesian3());
|
const translation = new Cesium.Cartesian3();
|
Cesium.Cartesian3.subtract(clipdata.center, transformCenter, translation);
|
Cesium.Matrix3.inverse(m3, m3);
|
Cesium.Matrix3.multiplyByVector(m3, translation, translation);
|
const m4 = new Cesium.Matrix4();
|
Cesium.Matrix4.fromTranslation(translation, m4);
|
Cesium.Matrix4.multiply(clippingPlanes.modelMatrix, m4, m4);
|
clippingPlanes.modelMatrix = m4;
|
});
|
};
|
|
ClippingBox.prototype.quaternionToHeadingPitchRoll = function (position, quaternion) {
|
const m3 = Cesium.Matrix3.fromQuaternion(quaternion);
|
const m4 = Cesium.Matrix4.fromRotationTranslation(m3, position);
|
return Cesium.Transforms.fixedFrameToHeadingPitchRoll(m4);
|
};
|
|
// 计算中心点
|
function updateOringin() {
|
if (clipdata.centers.length === 6) {
|
// 前后的中点
|
const fbMid = Cesium.Cartesian3.midpoint(clipdata.centers[0], clipdata.centers[1], new Cesium.Cartesian3());
|
// 左右的中点
|
const lrMid = Cesium.Cartesian3.midpoint(clipdata.centers[2], clipdata.centers[3], new Cesium.Cartesian3());
|
// 计算前后左右的中点(数据准确,只使用前后、左右或者上下均可)
|
clipdata.center = Cesium.Cartesian3.midpoint(fbMid, lrMid, new Cesium.Cartesian3());
|
}
|
}
|
|
// 动态更新剖面框大小
|
function updateDimensions() {
|
return () => {
|
if (clipdata.centers.length === 6) {
|
return new Cesium.Cartesian3(
|
Cesium.Cartesian3.distance(clipdata.centers[2], clipdata.centers[3]),
|
Cesium.Cartesian3.distance(clipdata.centers[0], clipdata.centers[1]),
|
Cesium.Cartesian3.distance(clipdata.centers[4], clipdata.centers[5]),
|
);
|
}
|
};
|
}
|
|
// 模拟锚点
|
function addPoint(viewer, position, direction) {
|
return viewer.entities.add({
|
position: position,
|
direction: direction,
|
name: direction,
|
point: {
|
pixelSize: 10,
|
color: Cesium.Color.RED,
|
outlineColor: Cesium.Color.YELLOW,
|
outlineWidth: 3.82,
|
verticalOrigin: Cesium.VerticalOrigin.CENTER,
|
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, Number.MAX_VALUE),
|
disableDepthTestDistance: Number.MAX_VALUE,
|
},
|
});
|
}
|
|
// 创建剖面框
|
function createClippingPlanes() {
|
// ['front', 'behind', 'left', 'right', 'top', 'bottom'];
|
const clippingPlanes = new Cesium.ClippingPlaneCollection({
|
planes: [
|
// new Cesium.ClippingPlane(new Cesium.Cartesian3(0, -1, 0), 0), // 前
|
// new Cesium.ClippingPlane(new Cesium.Cartesian3(0, 1, 0), 0), // 后
|
// new Cesium.ClippingPlane(new Cesium.Cartesian3(1, 0, 0), 0), // 左
|
// new Cesium.ClippingPlane(new Cesium.Cartesian3(-1, 0, 0), 0), // 右
|
// new Cesium.ClippingPlane(new Cesium.Cartesian3(0, 0, -1), 0), // 上
|
// new Cesium.ClippingPlane(new Cesium.Cartesian3(0, 0, 1), 0), // 下
|
|
new Cesium.ClippingPlane(new Cesium.Cartesian3(0, 1, 0), 0), // 前
|
new Cesium.ClippingPlane(new Cesium.Cartesian3(0, -1, 0), 0), // 后
|
new Cesium.ClippingPlane(new Cesium.Cartesian3(1, 0, 0), 0), // 左
|
new Cesium.ClippingPlane(new Cesium.Cartesian3(-1, 0, 0), 0), // 右
|
new Cesium.ClippingPlane(new Cesium.Cartesian3(0, 0, 1), 0), // 上
|
new Cesium.ClippingPlane(new Cesium.Cartesian3(0, 0, -1), 0), // 下
|
],
|
unionClippingRegions: true, // true 才能多个切割
|
});
|
|
// 设定方向,以备后续拖拽使用
|
const planes = clippingPlanes._planes;
|
for (let i = 0; i < clippingPlanes.length; i++) {
|
planes[i].direction = directions[i];
|
}
|
|
return clippingPlanes;
|
}
|
|
export default ClippingBox;
|