Cesium学习笔记——随机生成大面积云及封装

 前言

        在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>

        这样,就算大功告成了。

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凕雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值