[3D地图-CesiumJS系列] 三、车辆废气排放地图 - 以粒子系统(Particle system)实作

本篇文章请搭配
[3D地图-CesiumJS系列] 一、快速上手
[3D地图-CesiumJS系列] 二、建立飞航轨迹及动画


今天要来介绍CesiumJS的粒子系统
粒子系统是什麽呢?
粒子系统(Particle system)是一种图形技术,可以把许多小图像的集合来模拟物理现象。
当它们计算过後叠在一起时,会形成复杂的模糊对象,
可以仿造出烟雾、火、天气现象等效果。

↓ 今天使用的粒子图像如下,为官方SampleData中提供的smoke.png档
https://ithelp.ithome.com.tw/upload/images/20201014/201306044rnmeuU3VV.jpg
↓ 将它们叠在一起仿制的烟雾图像
https://ithelp.ithome.com.tw/upload/images/20201014/201306046ksddlmo3k.jpg

那就让我们一步一步开始吧!

初始化3D地图

在根目录下建立一个html页面,取名为Cesium_particle.html。
如果还不会CesiumJS专案建置的人,请参考前天的文章

↓ 建立一个存放地图的div

    <div id="cmap"></div>

↓ 引入Cesium.js

    <script src="../Build/Cesium/Cesium.js"></script>

↓ 引入css

    <link rel="stylesheet" href="../Build/Cesium/Widgets/widgets.css" />

↓ css让地图满版

    <style>
        html,
        body,
        #cmap {
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
            overflow: hidden;
        }
    </style>

↓ 初始化地图,新建一个Cesium.Viewer的物件,第一个参数存放地图的容器Id,第二个参数为设定(选填)。

        const viewer = new Cesium.Viewer('cmap');

↓ 结果
https://ithelp.ithome.com.tw/upload/images/20201013/20130604mgY1KdXK7D.png

时间轴设定

        const start = Cesium.JulianDate.fromDate(new Date("2020-10-14T21:00:00Z"));
        const stop = Cesium.JulianDate.addSeconds(start, 100, new Cesium.JulianDate());

        viewer.clock.startTime = start.clone();
        viewer.clock.stopTime = stop.clone();
        viewer.clock.currentTime = start.clone();
        viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;  // 结束後循环播放 
        viewer.clock.multiplier = 1;
        viewer.clock.shouldAnimate = true;
        viewer.timeline.zoomTo(start, stop);

↓ 时间轴
图片

车机及路线设定

↓ 设定起始座标及终点座标

        const positionStart = Cesium.Cartesian3.fromDegrees(
            -75.15787310614596,
            39.97862668312678
        );
        const positionEnd = Cesium.Cartesian3.fromDegrees(
            -75.1633691390455,
            39.95355089912078
        );

↓ Cesium.SampledPositionProperty的物件提供内插计算方法,可以计算每个时间区间相对应的座标

        let position = new Cesium.SampledPositionProperty();
        position.addSample(start, positionStart);  // 填入起始时间及座标
        position.addSample(stop, positionEnd);  // 填入结束时间及座标

↓ 在地图上新增车机模型

        let entity = viewer.entities.add({
            availability: new Cesium.TimeIntervalCollection([
                new Cesium.TimeInterval({
                    start: start,
                    stop: stop,
                }),
            ]),
            model: {
                uri: "Apps/SampleData/models/CesiumMilkTruck/CesiumMilkTruck.glb",
                minimumPixelSize: 64,
            },
            viewFrom: new Cesium.Cartesian3(-150.0, -100.0, 100.0),  // 右方150,前方100,上方100
            position: position,
            orientation: new Cesium.VelocityOrientationProperty(position),
        });
  • availability: 填入开始及结束的时间区间集合
  • model: 实体的模型,这边使用官网下载包SampleData资料夹中的的车机模型
  • viewFrom: 观看视角,这边代表与车机的相对位置,为一个三维座标
  • position: 路径点座标,由刚刚Cesium.SampledPositionProperty()物件内插计算出来
  • orientation: 方向,可以用Cesium.VelocityOrientationProperty()物件用座标组计算旋转角度

↓ 将车机实体设定在地图上

        viewer.trackedEntity = entity;

↓ 结果
图片

粒子系统

要计算粒子随机排放,并且用4x4矩阵去储存当下粒子集合的状态,要使用以下CesiumJS提供的物件及方法。

↓ 排放模型计算工具

        const emitterModelMatrix = new Cesium.Matrix4();  // 4x4矩阵
        const translation = new Cesium.Cartesian3();  // 3维座标点
        const trs = new Cesium.TranslationRotationScale();  // 座标转换
        
        // 除了3维座标外,加入旋转角度
        const rotation = new Cesium.Quaternion();  
        
        // 一种旋转表达方式,heading表示z轴,pitch表示y轴,roll表示x轴
        let hpr = new Cesium.HeadingPitchRoll();  

↓ 计算排放模型的阵列

        function ComputeEmitterModelMatrix() {
            hpr = Cesium.HeadingPitchRoll.fromDegrees(0.0, 0.0, 0.0, hpr);
            trs.translation = Cesium.Cartesian3.fromElements(-4.0, 0.0, 1.4, translation);
            trs.rotation = Cesium.Quaternion.fromHeadingPitchRoll(hpr, rotation);

            return Cesium.Matrix4.fromTranslationRotationScale(trs, emitterModelMatrix);
        }

↓ 新增一个物件,用来做为粒子系统的参数设定

        let viewModel = {
            emissionRate: 5.0,
            minimumParticleLife: 1.2,
            maximumParticleLife: 1.2,
            minimumSpeed: 1.0,
            maximumSpeed: 4.0,
            startScale: 1.0,
            endScale: 5.0,
        };

Cesium.ParticleSystem设定

  • image: 粒子的基础图片
  • startColor: 粒子生成时的起始颜色
  • endColor: 粒子消失前的颜色
  • emissionRate: 每秒粒子的排放数量
  • minimumParticleLife: 每个粒子的存在时间为随机生成,这个参数可以界定粒子最短的存在时间。
  • maximumParticleLife: 每个粒子的存在时间为随机生成,这个参数可以界定粒子最长的存在时间。
  • minimumSpeed: 每个粒子的移动速度为随机生成,这个参数可以界定粒子最低的移动速度(m/s)。
  • maximumSpeed: 每个粒子的移动速度为随机生成,这个参数可以界定粒子最高的移动速度(m/s)。
  • startScale: 粒子刚生成时图像的放大倍率,预设为1
  • endScale: 粒子消失前图像的放大倍率,预设为1
  • bursts: 粒子排放的周期
  • lifetime: 粒子存活的时间
  • emitter: 排放方式
  • emitterModelMatrix: 排放的座标计算,为一4x4的矩阵
  • updateCallback: 粒子排放更新时的回调函式

↓ 在地图上新增粒子系统物件

        let particleSystem = viewer.scene.primitives.add(
            new Cesium.ParticleSystem({
                image: "Apps/SampleData/smoke.png",
                startColor: Cesium.Color.LIGHTSEAGREEN.withAlpha(0.7),
                endColor: Cesium.Color.WHITE.withAlpha(0.0),
                startScale: viewModel.startScale,
                endScale: viewModel.endScale,
                minimumParticleLife: viewModel.minimumParticleLife,
                maximumParticleLife: viewModel.maximumParticleLife,
                minimumSpeed: viewModel.minimumSpeed,
                maximumSpeed: viewModel.maximumSpeed,
                imageSize: new Cesium.Cartesian2(
                    viewModel.particleSize,
                    viewModel.particleSize
                ),
                emissionRate: viewModel.emissionRate,
                bursts: [
                    new Cesium.ParticleBurst({
                        time: 5.0,
                        minimum: 10,
                        maximum: 100,
                    }),
                    new Cesium.ParticleBurst({
                        time: 10.0,
                        minimum: 50,
                        maximum: 100,
                    }),
                    new Cesium.ParticleBurst({
                        time: 15.0,
                        minimum: 200,
                        maximum: 300,
                    }),
                ],
                lifetime: 16.0,
                emitter: new Cesium.CircleEmitter(2.0),
                emitterModelMatrix: ComputeEmitterModelMatrix(),
                updateCallback: ApplyGravity,
            })
        );

↓ 粒子系统的updateCallback function

        const gravityScratch = new Cesium.Cartesian3();

        function ApplyGravity(p, dt) {
            Cesium.Cartesian3.normalize(p.position, gravityScratch);
            Cesium.Cartesian3.multiplyByScalar(gravityScratch
                                    , viewModel.gravity * dt, gravityScratch);
            p.velocity = Cesium.Cartesian3.add(p.velocity
                                            , gravityScratch, p.velocity);
        }

↓ 地图新增视觉更新前的事件,并且在每次更新时,重新计算粒子系统的烟雾排放。

        viewer.scene.preUpdate.addEventListener(function (scene, time) {
            particleSystem.modelMatrix = entity.computeModelMatrix(time, new Cesium.Matrix4());
            particleSystem.emitterModelMatrix = ComputeEmitterModelMatrix();
        });

↓ 结果
图片

展示

可以用Cesium.knockout来把粒子设定参数跟dom标签进行绑定,这边就不赘述作法,直接使用UI面板修改粒子设定。

↓ 当scale设定很小时,烟雾范围很小
图片

↓ 当scale设定很大时,烟雾范围很大
图片

↓ gravity设定很高时,烟雾往上飘
图片

↓ life设定很长时,粒子存在时间很久,烟雾会拉很长
图片

↓ rate设定很快时,烟雾较为密集
图片


假如要计算整个城市里面的车辆废气排放,
或者要监测工厂黑烟排放,
活用CesiumJS的粒子系统想必能一目了然!/images/emoticon/emoticon37.gif


<<:  Vue 动态组件

>>:  [Day 29]-【STM32系列】实作-步进马达 + ULN2003 控制

比起懂最新的知识,工程师更应该懂这些.......

有些公司永远在徵人(人员一直在流动),实际去应徵过後,会深刻理解到为什麽。 前几天提到GitHub时...

[Day11] Tableau 轻松学 - Workbook/Worksheet/Dashboard/Story

前言 档案架构是在开发前应该要先了解的事,可以让我们在对的地方做对的事情,以节省宝贵的时间。主要有四...

Day27_是不是跟个资法卯上了~哈哈~CBPR-2021/10/10

想说CBPR是什麽? 与GDPR不同的是,CBPR并非是一套要求所有国家遵循的规范, 而更像是一种参...

D31 - 用 Swift 和公开资讯,打造投资理财的 Apps { 台股申购功能扩充,算出价差.2}

上一篇,提到了可以在 tableView(_:willDisplay:forRowAt:) 中发动 ...

Day 13 Mailhog - 模拟 SMTP 邮件服务的开发利器

由於 Mautic 是一个自动化行销利器,那麽寄发电子邮件便是一个必须的功能。不过在开发时一再的利用...