前言
在Cesium的学习中,学会读文档十分重要!!!在这里附上中文文档Cesium中文文档1.95
在Cesium学习笔记——点击位置生成积云 笔记中,我们了解了如何用Cesium内置的CumulusCloud制作积云,但是,在实际项目中一般都是大面积生成云朵,而不是手动生成。这一次,我们来学习一下如何大面积生成积云。
随机生成大面积云一共分3步,首先,绘制一个面,然后,在面内随机生成点的坐标,最后在这些地方生成云朵就好了。这里,我们直接封装成一个类。
一、类的初始化
这里我们先设计一下类的构造函数。前面的export default表示默认导出,可以将该类导出别的地方使用。constructor是构造器,相当于一个构造函数,viewer是传入的参数,在这里是Cesium.viewer。_points是用来存放随机生成的点的集合,drawEntityGraphic 是绘制的面实体店工具对象,clouds是云集合。
export default class DrawCloud {
constructor(viewer) {
this.viewer = viewer;
this._points = [];//点集合
this._drawEntityGraphic = null;//绘制面实体的工具对象
this._clouds = null;//云集合
}
}
二、激活函数
激活函数的作用是与按钮等特点行为进行绑定,比如说这里是画面的按钮。
2.1、绘制面实体
首先,我们要先绘制面实体,再在此面内生成点。绘制面实体用到了上一节Cesium学习笔记——Entity点线面的绘制及封装(二)的绘制点线面工具类,我们要先将其引入。
import * as Cesium from 'cesium';
import DrawEntityGraphic from './drawEntityGraphic';
绘制云可以由调用者确定云的长度和宽度,及密度,因此我们可以将其作为参数传入。绘制面的工具的使用,要先将该工具实例化,然后激活其绘制事件。同时,通过一个云集合来存放生成的云。这个云集合就是CloudCollection类实例,将其加入primitives里后,在其中加入云实例就可以直接渲染在地球中来。
/**
* 激活绘制大片云
* @param {*} cloudWidth 云的宽度
* @param {*} cloudLength 云的长度
* @param {*} density 云的密度
*/
activateDrawCloud(cloudWidth, cloudLength, density) {
this._drawEntityGraphic = new DrawEntityGraphic(this.viewer);//实例化绘制实体
this._clouds = this.viewer.scene.primitives.add(new Cesium.CloudCollection()); //实例化云集合
this._drawEntityGraphic.activate('polygon')
}
2.2、在面内随机生成点
面的绘制已经完成,现在我们要在面内随机生成点。在面内随机生成点,我的方法是先找出面的最小外接矩形,然后在矩形内随机生成点,再用射线法判断其在不在面内。因为矩形内更容易生成随机点,通过用最小外接矩形,提高生成在面内的概率。
/**
* 生成随机点函数
* @param {*} polygonCartesian 多边形顶点坐标
* @param {*} numPoints 生成云朵数量
* @param {*} h 生成云朵距离地面的高度
* @returns
*/
_generateRandomPointsInPolygon(polygonCartesian, numPoints, h) {
// 将Cartesian3顶点转换为经纬度数组
const polygon = polygonCartesian.map(cartesian => {
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
return {
lon: Cesium.Math.toDegrees(cartographic.longitude),
lat: Cesium.Math.toDegrees(cartographic.latitude),
height: cartographic.height + h,
};
});
// 计算经纬度包围盒
const lonArray = polygon.map(p => p.lon);
const latArray = polygon.map(p => p.lat);
const minLon = Math.min(...lonArray);
const maxLon = Math.max(...lonArray);
const minLat = Math.min(...latArray);
const maxLat = Math.max(...latArray);
const heightAvg = polygon.reduce((sum, p) => sum + p.height, 0) / polygon.length;
// 射线法判断点是否在多边形内
const isInside = (point, polygon) => {
let inside = false;
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
const xi = polygon[i].lon, yi = polygon[i].lat;
const xj = polygon[j].lon, yj = polygon[j].lat;
const intersect = ((yi > point.lat) !== (yj > point.lat)) &&
(point.lon < (xj - xi) * (point.lat - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
}
return inside;
};
// 生成随机点
const points = [];
while (points.length < numPoints) {
const lon = Math.random() * (maxLon - minLon) + minLon;
const lat = Math.random() * (maxLat - minLat) + minLat;
if (isInside({ lon, lat }, polygon)) {
points.push(Cesium.Cartesian3.fromDegrees(lon, lat, heightAvg));
}
}
return points;
}
2.3、在点的位置生成随机的云
最后是在随机的点的位置生成随机的云。在绘制面的右击事件中,我们保留了callback回调函数,目的就是方便后续对其进行处理。比如说,在绘制完成面后,我们才能在里面随机生成点,所以我们要等绘制面完成后在生成,这样,就可以用一个回调函数来处理。(当然,也可以用await来处理)
为了使随机生成的云朵分布均匀,我们可以计算一下绘制的面的面积,除以一朵云的面积,得到云朵数量,在这个数量上,可以乘上参数density来控制云的密度。
/**
* 激活绘制大片云
* @param {*} cloudWidth 云的宽度
* @param {*} cloudLength 云的长度
* @param {*} density 云的密度
*/
activateDrawCloud(cloudWidth, cloudLength, density) {
this._drawEntityGraphic = new DrawEntityGraphic(this.viewer);//实例化绘制实体
this._clouds = this.viewer.scene.primitives.add(new Cesium.CloudCollection()); //实例化云集合
this._drawEntityGraphic.activate('polygon', (points) => {
const width = Math.max(...points.map(p => p.x)) - Math.min(...points.map(p => p.x));
const length = Math.max(...points.map(p => p.y)) - Math.min(...points.map(p => p.y));
const num = Math.floor(width * length / (cloudLength * cloudWidth)) * density;
this._points = this._generateRandomPointsInPolygon(points, num, 5000 + (cloudLength + cloudWidth) / 2);//生成随机点
});
}
然后,就是云朵样式的随机生成了。我们需要遍历随机生成的点,在每个点上生成云。我们通过Math.random来随机生成云朵样式。
/**
* 激活绘制大片云
* @param {*} cloudWidth 云的宽度
* @param {*} cloudLength 云的长度
* @param {*} density 云的密度
*/
activateDrawCloud(cloudWidth, cloudLength, density) {
this._drawEntityGraphic = new DrawEntityGraphic(this.viewer);//实例化绘制实体
this._clouds = this.viewer.scene.primitives.add(new Cesium.CloudCollection()); //实例化云集合
this._drawEntityGraphic.activate('polygon', (points) => {
const width = Math.max(...points.map(p => p.x)) - Math.min(...points.map(p => p.x));
const length = Math.max(...points.map(p => p.y)) - Math.min(...points.map(p => p.y));
const num = Math.floor(width * length / (cloudLength * cloudWidth)) * density;
this._points = this._generateRandomPointsInPolygon(points, num, 5000 + (cloudLength + cloudWidth) / 2);//生成随机点
this._drawEntityGraphic.clearAll();//清除绘制实体
this._points.forEach(point => {
const r = Math.random();
this._clouds.add({
show: true,
position: point,
maximumSize: new Cesium.Cartesian3(
5 + r * 15, // X方向尺寸
4 + r * 10, // Y方向尺寸
3 + r * 5 // Z方向尺寸
),
scale: new Cesium.Cartesian2(
cloudWidth * (0.8 + Math.random() * 0.4), // 随机宽度变化
cloudLength * (0.8 + Math.random() * 0.4) // 随机长度变化
),
slice: 0.2 + Math.random() * 0.3, // 调整切分比例范围
brightness: 0.8 + Math.random() * 0.4, // 亮度变化范围
// color: new Cesium.Color(
// 0.95 + Math.random() * 0.05, // R
// 0.95 + Math.random() * 0.05, // G
// 0.95 + Math.random() * 0.05, // B
// 0.85 + Math.random() * 0.1 // Alpha(透明度变化)
// ),
color: new Cesium.Color(
0.9 + Math.random() * 0.1, // R
0.7 + Math.random() * 0.2, // G
0.6 + Math.random() * 0.2, // B
0.8
),
noiseDetail: 3.0 + Math.random(), // 噪声细节
noiseOffset: new Cesium.Cartesian3(
Math.random() * 100,
Math.random() * 100,
Math.random() * 100
),
orientation: Cesium.Quaternion.fromAxisAngle(
Cesium.Cartesian3.UNIT_Z,
Math.random() * Cesium.Math.TWO_PI // 随机旋转角度
)
});
});
});
}
在颜色里,我写了两个云朵颜色样式,第一个是随机生成白色的云,第二个是粉色的云(封面里的)。这样,大面积生成云的类的封装就算完成了。
2.4、资源释放
最后,记得写一个clearAll函数,释放不用的资源。我这里直接所有的释放了,大家可以根据需要修改。
/**
* 清除所有绘制内容
*/
clearAll() {
this._drawEntityGraphic.clearAll();
this._clouds.removeAll();
this._points = [];
this._drawEntityGraphic = null;
this.viewer.scene.primitives.remove(this._clouds);
this._clouds = null;
}
三、类的调用
最后,就是类的调用了,我这里是vue3框架下的写法,用pinia存储了viewer,用element写了一个按钮,大家也可以用别的按钮。在绘制完后,大家记得及时释放资源,比如我这里是在切换页面后释放。
<template>
<el-button :icon="Location" @click="addCloud()"></el-button>
</template>
<script setup>
import useStore from '@/stores';
import { Location } from '@element-plus/icons-vue';
import { onUnmounted } from 'vue';
import DrawCloud from '../../utils/drawCloud';
const { viewer } = useStore();
const drawCloud = new DrawCloud(viewer);
const addCloud = () => {
drawCloud.activateDrawCloud(100000, 100000, 10);
};
onUnmounted(() => {
drawCloud.clearAll();
});
</script>
<style scoped></style>
这样,就算大功告成了。