Cesium保姆教程之地形挖掘效果
本文是对本段时间中涉及到的地形挖掘效果进行简单的提炼总结。
思路:
1、创建多边形,挖掘多边形所在的地方,要首先获取多边形的顶点; 对于多边形的绘制请看这篇文章,Cesium保姆教程之绘制任意多边形 - 掘金 (juejin.cn)
2、根据鼠标点击的点计算出相邻点之间的中间点和根据切割数量计算出法线和每一个点的坐标值,进而计算出裁剪平面;
3、根据计算出来的坐标点向下剖切一定深度,并将剖切出来的地下地形赋予相对应的材质,进而表现出一种地质剖切的效果。
4、可更新挖掘深度,进而使效果更加真实。
实现过程
地形开挖类
获取鼠标点击的点
startCreate() {
const self = this;
//绘制任意多边形
CreatePolygonOnGround(
self.viewer,
{
color: Cesium.Color.RED.withAlpha(0.1),
outlineColor: Cesium.Color.YELLOW,
outlineWidth: 2,
},
function (polygon) {
const points = polygon.pottingPoint;
self.viewer.entities.remove(polygon);
//将获取到的坐标点进行下一步的地形剖切
self.updateData(points);
}
);
return;
}
地形剖切
根据获取到的鼠标点击点计算裁剪相邻点的中间点和发现进而计算出裁剪平面,实现过程如下:
updateData(points) {
let viewer = this.viewer;
//剖切之前将所有面清除
this.clear();
let clippingPlanesList = [];
//计算两个笛卡尔函数的分量差异
let car3Difference = Cesium.Cartesian3.subtract(
activePoints[0],
activePoints[1],
new Cesium.Cartesian3()
);
//初始化挖掘深度
this.excavateMinHeight = 9999;
for (let index = 0; index < points.length; ++index) {
let s = (index + 1) % activePoints.length;
//计算中间点
let curMidPoint = Cesium.Cartesian3.midpoint(
activePoints[index],
activePoints[s],
new Cesium.Cartesian3()
);
let cartographic = Cesium.Cartographic.fromCartesian(points[index]);
let curHeight = cartographic.height;
if (curHeight < this.excavateMinHeight) {
this.excavateMinHeight = curHeight;
}
//中间点标准化
let curMidPointNormal = Cesium.Cartesian3.normalize(
curMidPoint,
new Cesium.Cartesian3()
);
//相邻向量相减
let curMidPointDifference = Cesium.Cartesian3.subtract(
activePoints[index],
curMidPoint,
new Cesium.Cartesian3()
)
curMidPointDifference = Cesium.Cartesian3.normalize(
curMidPointDifference,
curMidPointDifference
);
//叉乘
let curMidPointCross = Cesium.Cartesian3.cross(
curMidPointDifference,
curMidPointNormal,
new Cesium.Cartesian3()
);
curMidPointCross = Cesium.Cartesian3.normalize(
curMidPointCross,
curMidPointCross
);
let plane = new Cesium.Plane(curMidPointCross, 0);
let distance = Cesium.Plane.getPointDistance(plane, curMidPoint);
clippingPlanesList.push(
new Cesium.ClippingPlane(curMidPointCross, distance)
);
}
this.viewer.scene.globe.clippingPlanes = new Cesium.ClippingPlaneCollection(
{
planes: clippingPlanesList,
edgeWidth: 1,
edgeColor: Cesium.Color.WHITE,
enabled: true,
}
);
this.createWall(points);
this.createWallPrimitive(this.wallData);
this.viewer.entities.remove(this.drawGeomtry);
}
//计算并更新wallData
createWall(points) {
let pointLength = points.length;
let heightDiff = this.excavateMinHeight;
let no_height_top = [],
bottom_pos = [],
lerp_pos = [];
for (let m = 0; m < pointLength; m++) {
let n = m == pointLength - 1 ? 0 : m + 1;
let point0 = [
Cesium.Cartographic.fromCartesian(activePoints[m]).longitude,
Cesium.Cartographic.fromCartesian(activePoints[m]).latitude,
];
let point1 = [
Cesium.Cartographic.fromCartesian(activePoints[n]).longitude,
Cesium.Cartographic.fromCartesian(activePoints[n]).latitude,
];
if (0 == m) {
lerp_pos.push(new Cesium.Cartographic(point0[0], point0[1]));
bottom_pos.push(
Cesium.Cartesian3.fromRadians(point0[0], point0[1], heightDiff)
);
no_height_top.push(
Cesium.Cartesian3.fromRadians(point0[0], point0[1], 0)
);
}
for (let p = 1; p <= this.splitNum; p++) {
let m = Cesium.Math.lerp(point0[0], point1[0], p / this.splitNum);
let g = Cesium.Math.lerp(point0[1], point1[1], p / this.splitNum);
(m == pointLength - 1 && p == this.splitNum) ||
(lerp_pos.push(new Cesium.Cartographic(m, g)),
bottom_pos.push(Cesium.Cartesian3.fromRadians(m, g, heightDiff)),
no_height_top.push(Cesium.Cartesian3.fromRadians(m, g, 0)));
}
}
this.wallData = {
lerp_pos: lerp_pos, //地形坐标点包括高度
bottom_pos: bottom_pos, //相对应的地底坐标点
no_height_top: no_height_top, //地形坐标点不包括高度
};
}
//开始创建底面和侧面
createWallPrimitive(wallData) {
let self = this;
if (self.viewer.terrainProvider._layers) {
//创建底面
self.createBottomSurface(wallData.bottom_pos);
//Cesium.sampleTerrainMostDetailed可以获取地形点所对应的精确地形高度
let positions = Cesium.sampleTerrainMostDetailed(
self.viewer.terrainProvider,
wallData.lerp_pos
).then(function (pos) {
let positionList = [];
for (let index = 0; index < pos.length; index++) {
const element = pos[index];
let curPos = Cesium.Cartesian3.fromRadians(
element.longitude,
element.latitude,
element.height
);
//所有地形点
positionList.push(curPos);
}
//创建侧面对象
self.createWallPrimitive(wallData.bottom_pos, positionList);
});
} else {
this.createBottomSurface(wallData.bottom_pos);
this.createWallPrimitive(wallData.bottom_pos, wallData.no_height_top);
}
}
//坐标转换,转出经纬度格式
ellipsoidToDegree(position) {
let cartesian3 = new Cesium.Cartesian3(position.x, position.y, position.z);
let cartographic =
this.viewer.scene.globe.ellipsoid.cartesianToCartographic(cartesian3);
return {
longitude: Cesium.Math.toDegrees(cartographic.longitude),
latitude: Cesium.Math.toDegrees(cartographic.latitude),
altitude: cartographic.height,
};
}
//创建地形开挖的底面对象
createBottomSurface(points) {
if (points.length) {
let minHeight = this.getMinHeight(points);
let positions = [];
for (let i = 0; i < points.length; i++) {
let curPoint = this.ellipsoidToDegree(points[i]);
positions.push(curPoint.longitude, curPoint.latitude, minHeight);
}
let polygon = new Cesium.PolygonGeometry({
polygonHierarchy: new Cesium.PolygonHierarchy(
Cesium.Cartesian3.fromDegreesArrayHeights(positions)
),
perPositionHeight: true,
});
let material = new Cesium.Material({
fabric: {
type: "Image",
uniforms: {
image: this.bottomImg,
},
},
});
let appearance = new Cesium.MaterialAppearance({
translucent: false,
flat: true,
material: material,
});
this.bottomSurface = new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: Cesium.PolygonGeometry.createGeometry(polygon),
}),
appearance: appearance,
asynchronous: false,
});
this.viewer.scene.primitives.add(this.bottomSurface);
}
}
//创建地形底层对象
createBottomSurface(points) {
if (points.length) {
let minHeight = this.getMinHeight(points);
let positions = [];
for (let i = 0; i < points.length; i++) {
let curPoint = this.ellipsoidToDegree(points[i]);
positions.push(curPoint.longitude, curPoint.latitude, minHeight);
}
let polygon = new Cesium.PolygonGeometry({
polygonHierarchy: new Cesium.PolygonHierarchy(
Cesium.Cartesian3.fromDegreesArrayHeights(positions)
),
perPositionHeight: true,
});
let material = new Cesium.Material({
fabric: {
type: "Image",
uniforms: {
image: this.bottomImg,
},
},
});
let appearance = new Cesium.MaterialAppearance({
translucent: false,
flat: true,
material: material,
});
this.bottomSurface = new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: Cesium.PolygonGeometry.createGeometry(polygon),
}),
appearance: appearance,
asynchronous: false,
});
this.viewer.scene.primitives.add(this.bottomSurface);
}
}
// 创建地形开挖的侧面墙对象
createWallPrimitive(bottomPos, positionList) {
let minHeight = this.getMinHeight(bottomPos);
let maxHeights = [],
minHeights = [];
for (let i = 0; i < positionList.length; i++) {
maxHeights.push(this.ellipsoidToDegree(positionList[i]).altitude);
minHeights.push(this.ellipsoidToDegree(minHeights);
}
let wall = new Cesium.WallGeometry({
positions: positionList,
maximumHeights: maxHeights,
minimumHeights: minHeights,
});
let geometry = Cesium.WallGeometry.createGeometry(wall);
let material = new Cesium.Material({
fabric: {
type: "DiffuseMap",
uniforms: {
image: this.wallImg,
repeat: new Cesium.Cartesian2(1, 1)
},
},
});
let appearance = new Cesium.MaterialAppearance({
translucent: false,
flat: true,
material: material,
});
this.createWallPrimitive = new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: geometry,
}),
appearance: appearance,
asynchronous: false,
});
this.viewer.scene.primitives.add(this.createWallPrimitive);
}
变化挖掘深度
updateExcavateDepth(height) {
this.viewer.scene.primitives.remove(this.bottomSurface);
this.viewer.scene.primitives.remove(this.createWallPrimitive);
let lerp_pos = this.wallData.lerp_pos;
let posList = [];
for (let n = 0; n < lerp_pos.length; n++) {
posList.push(
Cesium.Cartesian3.fromRadians(
lerp_pos[n].longitude,
lerp_pos[n].latitude,
this.excavateMinHeight - height
)
);
}
this.wallData.bottom_pos = posList;
this.createWallPrimitive(this.wallData);
挖掘类(完整代码)
//地形开挖
import * as Cesium from "cesium"
import CreatePolygonOnGround from "./ground";
class TerrainExcavation {
constructor(viewer, options) {
if (!viewer) throw new Error("no viewer object!");
this.viewer = viewer;
this.options = options || {};
this._height = this.options.height || 0;
this.bottomImg = options.bottomImg;
this.wallImg = options.wallImg;
this.splitNum = Cesium.defaultValue(options.splitNum, 50);
this.init();
}
init() {
Object.defineProperties(TerrainExcavation.prototype, {
show: {
get: function () {
return this._show;
},
set: function (e) {
this._show = e;
this.switchExcavate(e);
},
},
height: {
get: function () {
return this._height;
},
set: function (e) {
this._height = e;
this.updateExcavateDepth(e);
},
},
});
}
startCreate() {
const self = this;
//绘制任意多边形
CreatePolygonOnGround(
self.viewer,
{
color: Cesium.Color.RED.withAlpha(0.1),
outlineColor: Cesium.Color.YELLOW,
outlineWidth: 2,
},
function (polygon) {
const points = polygon.pottingPoint;
self.viewer.entities.remove(polygon);
//将获取到的坐标点进行下一步的地形剖切
self.updateData(points);
}
);
return;
}
updateData(points) {
let viewer = this.viewer;
//剖切之前将所有面清除
this.clear();
let clippingPlanesList = [];
//计算两个笛卡尔函数的分量差异
let car3Difference = Cesium.Cartesian3.subtract(
activePoints[0],
activePoints[1],
new Cesium.Cartesian3()
);
//初始化挖掘深度
this.excavateMinHeight = 9999;
for (let index = 0; index < points.length; ++index) {
let s = (index + 1) % points.length;
//计算中间点
let curMidPoint = Cesium.Cartesian3.midpoint(
activePoints[index],
activePoints[s],
new Cesium.Cartesian3()
);
let cartographic = Cesium.Cartographic.fromCartesian(points[index]);
let curHeight = cartographic.height;
if (curHeight < this.excavateMinHeight) {
this.excavateMinHeight = curHeight;
}
//中间点标准化
let curMidPointNormal = Cesium.Cartesian3.normalize(
curMidPoint,
new Cesium.Cartesian3()
);
//相邻向量相减
let curMidPointDifference = Cesium.Cartesian3.subtract(
activePoints[index],
curMidPoint,
new Cesium.Cartesian3()
)
curMidPointDifference = Cesium.Cartesian3.normalize(
curMidPointDifference,
curMidPointDifference
);
//叉乘
let curMidPointCross = Cesium.Cartesian3.cross(
curMidPointDifference,
curMidPointNormal,
new Cesium.Cartesian3()
);
curMidPointCross = Cesium.Cartesian3.normalize(
curMidPointCross,
curMidPointCross
);
let plane = new Cesium.Plane(curMidPointCross, 0);
let distance = Cesium.Plane.getPointDistance(plane, curMidPoint);
clippingPlanesList.push(
new Cesium.ClippingPlane(curMidPointCross, distance)
);
}
this.viewer.scene.globe.clippingPlanes = new Cesium.ClippingPlaneCollection(
{
planes: clippingPlanesList,
edgeWidth: 1,
edgeColor: Cesium.Color.WHITE,
enabled: true,
}
);
this.createWall(points);
this.createWallPrimitive(this.wallData);
this.viewer.entities.remove(this.drawGeomtry);
}
//计算并更新wallData
createWall(points) {
let pointLength = points.length;
let heightDiff = this.excavateMinHeight;
let no_height_top = [],
bottom_pos = [],
lerp_pos = [];
for (let m = 0; m < pointLength; m++) {
let n = m == pointLength - 1 ? 0 : m + 1;
let point0 = [
Cesium.Cartographic.fromCartesian(activePoints[m]).longitude,
Cesium.Cartographic.fromCartesian(activePoints[m]).latitude,
];
let point1 = [
Cesium.Cartographic.fromCartesian(activePoints[n]).longitude,
Cesium.Cartographic.fromCartesian(activePoints[n]).latitude,
];
if (0 == m) {
lerp_pos.push(new Cesium.Cartographic(point0[0], point0[1]));
bottom_pos.push(
Cesium.Cartesian3.fromRadians(point0[0], point0[1], heightDiff)
);
no_height_top.push(
Cesium.Cartesian3.fromRadians(point0[0], point0[1], 0)
);
}
for (let p = 1; p <= this.splitNum; p++) {
let m = Cesium.Math.lerp(point0[0], point1[0], p / this.splitNum);
let g = Cesium.Math.lerp(point0[1], point1[1], p / this.splitNum);
(m == pointLength - 1 && p == this.splitNum) ||
(lerp_pos.push(new Cesium.Cartographic(m, g)),
bottom_pos.push(Cesium.Cartesian3.fromRadians(m, g, heightDiff)),
no_height_top.push(Cesium.Cartesian3.fromRadians(m, g, 0)));
}
}
this.wallData = {
lerp_pos: lerp_pos, //地形坐标点包括高度
bottom_pos: bottom_pos, //相对应的地底坐标点
no_height_top: no_height_top, //地形坐标点不包括高度
};
}
//开始创建底面和侧面
createWallPrimitive(wallData) {
let self = this;
if (self.viewer.terrainProvider._layers) {
//创建底面
self.createBottomSurface(wallData.bottom_pos);
//Cesium.sampleTerrainMostDetailed可以获取地形点所对应的精确地形高度
let positions = Cesium.sampleTerrainMostDetailed(
self.viewer.terrainProvider,
wallData.lerp_pos
).then(function (pos) {
let positionList = [];
for (let index = 0; index < pos.length; index++) {
const element = pos[index];
let curPos = Cesium.Cartesian3.fromRadians(
element.longitude,
element.latitude,
element.height
);
//所有地形点
positionList.push(curPos);
}
//创建侧面对象
self.createWallPrimitive(wallData.bottom_pos, positionList);
});
} else {
this.createBottomSurface(wallData.bottom_pos);
this.createWallPrimitive(wallData.bottom_pos, wallData.no_height_top);
}
}
//坐标转换,转出经纬度格式
ellipsoidToDegree(position) {
let cartesian3 = new Cesium.Cartesian3(position.x, position.y, position.z);
let cartographic =
this.viewer.scene.globe.ellipsoid.cartesianToCartographic(cartesian3);
return {
longitude: Cesium.Math.toDegrees(cartographic.longitude),
latitude: Cesium.Math.toDegrees(cartographic.latitude),
altitude: cartographic.height,
};
}
//创建地形开挖的底面对象
createBottomSurface(points) {
if (points.length) {
let minHeight = this.getMinHeight(points);
let positions = [];
for (let i = 0; i < points.length; i++) {
let curPoint = this.ellipsoidToDegree(points[i]);
positions.push(curPoint.longitude, curPoint.latitude, minHeight);
}
let polygon = new Cesium.PolygonGeometry({
polygonHierarchy: new Cesium.PolygonHierarchy(
Cesium.Cartesian3.fromDegreesArrayHeights(positions)
),
perPositionHeight: true,
});
let material = new Cesium.Material({
fabric: {
type: "Image",
uniforms: {
image: this.bottomImg,
},
},
});
let appearance = new Cesium.MaterialAppearance({
translucent: false,
flat: true,
material: material,
});
this.bottomSurface = new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: Cesium.PolygonGeometry.createGeometry(polygon),
}),
appearance: appearance,
asynchronous: false,
});
this.viewer.scene.primitives.add(this.bottomSurface);
}
}
//创建地形底层对象
createBottomSurface(points) {
if (points.length) {
let minHeight = this.getMinHeight(points);
let positions = [];
for (let i = 0; i < points.length; i++) {
let curPoint = this.ellipsoidToDegree(points[i]);
positions.push(curPoint.longitude, curPoint.latitude, minHeight);
}
let polygon = new Cesium.PolygonGeometry({
polygonHierarchy: new Cesium.PolygonHierarchy(
Cesium.Cartesian3.fromDegreesArrayHeights(positions)
),
perPositionHeight: true,
});
let material = new Cesium.Material({
fabric: {
type: "Image",
uniforms: {
image: this.bottomImg,
},
},
});
let appearance = new Cesium.MaterialAppearance({
translucent: false,
flat: true,
material: material,
});
this.bottomSurface = new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: Cesium.PolygonGeometry.createGeometry(polygon),
}),
appearance: appearance,
asynchronous: false,
});
this.viewer.scene.primitives.add(this.bottomSurface);
}
}
// 创建地形开挖的侧面墙对象
createWallPrimitive(bottomPos, positionList) {
let minHeight = this.getMinHeight(bottomPos);
let maxHeights = [],
minHeights = [];
for (let i = 0; i < positionList.length; i++) {
maxHeights.push(this.ellipsoidToDegree(positionList[i]).altitude);
minHeights.push(this.ellipsoidToDegree(minHeights);
}
let wall = new Cesium.WallGeometry({
positions: positionList,
maximumHeights: maxHeights,
minimumHeights: minHeights,
});
let geometry = Cesium.WallGeometry.createGeometry(wall);
let material = new Cesium.Material({
fabric: {
type: "DiffuseMap",
uniforms: {
image: this.wallImg,
repeat: new Cesium.Cartesian2(1, 1)
},
},
});
let appearance = new Cesium.MaterialAppearance({
translucent: false,
flat: true,
material: material,
});
this.createWallPrimitive = new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: geometry,
}),
appearance: appearance,
asynchronous: false,
});
this.viewer.scene.primitives.add(this.createWallPrimitive);
}
//获取地形开挖最低点高程值
getMinHeight(points) {
let minHeight = 5000000;
let minPoint = null;
for (let i = 0; i < points.length; i++) {
let height = points[i]["z"];
if (height < minHeight) {
minHeight = height;
minPoint = this.ellipsoidToDegree(points[i]);
}
}
return minPoint.altitude;
}
switchExcavate(show) {
if (show) {
this.viewer.scene.globe.material = null;
this.createWallPrimitive.show = true;
this.bottomSurface.show = true;
} else {
this.viewer.scene.globe.material = null;
this.createWallPrimitive.show = false;
this.bottomSurface.show = false;
}
}
//挖掘深度变化
updateExcavateDepth(height) {
this.viewer.scene.primitives.remove(this.bottomSurface);
this.viewer.scene.primitives.remove(this.createWallPrimitive);
let lerp_pos = this.wallData.lerp_pos;
let posList = [];
for (let n = 0; n < lerp_pos.length; n++) {
posList.push(
Cesium.Cartesian3.fromRadians(
lerp_pos[n].longitude,
lerp_pos[n].latitude,
this.excavateMinHeight - height
)
);
}
this.wallData.bottom_pos = posList;
this.createWallPrimitive(this.wallData);
}
export default TerrainExcavation;
前端调用
<template>
<div>
<div id="cesiumContainer"></div>
<div class="edit">
<el-button size="mini" @click="terrainClip(true)">开始创建</el-button>
<div style="margin-top: 5px">
开挖高度:
<el-input-number size="mini" controls-position="right" v-model="height" :step="10" :min="0" :max="300"
@change="heightChange"></el-input-number>
</div>
</div>
</div>
</template>
<script setup>
import * as Cesium from "cesium";
import "cesium/Source/Widgets/widgets.css";
import initCesium from "@/cesiumUtils/initCesium";
import TerrainExcavation from "@/material/TerrainExa";
let viewer = null;
let height = 20
//生命周期钩子
onMounted(async () => {
viewer = await initCesium("cesiumContainer");
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(116, 35, 100000)
})
})
const heightChange = (val) => {
if (terrainClipPlanObj) {
terrainClipPlanObj.height = val;
}
}
const terrainClip = (bool) => {
if (!terrainClipPlanObj) {
terrainClipPlanObj = new TerrainExcavation(viewer, {
height: height.value,
splitNum: 1000,
bottomImg: require("./gg.png"),
wallImg: require("./gg.png"),
});
}
if (bool) {
console.log("开始地形开挖");
terrainClipPlanObj.startCreate();
} else {
console.log("结束地形开挖");
terrainClipPlanObj.clear();
}
}
</script>
<style lang="less" scoped>
#cesiumContainer {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
overflow: hidden;
position: relative;
}
.edit {
position: absolute;
top: 0;
left: 0;
padding: 10px;
}
</style>
实现效果
由于时间比较仓促,本篇描写的比较简单,语言也比较杂乱,没有对这篇文章进行认真的打磨,导致本文的质量不是很高,没有达到自己的期待,希望下次会改成,也希望大家在评论区指出这篇文章的错误,对于不懂的地方也可以在评论区指出,本人必定修改。