iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 29
0
Modern Web

《你的地圖會說話? WebGIS與JavaScript的情感交織》系列 第 29

[3D地圖-CesiumJS系列] 三、車輛廢氣排放地圖 - 以粒子系統(Particle system)實作

本系列文章已出版實體書籍:
「你的地圖會說話?WebGIS 與 JavaScript 的情感交織」(博碩文化)
WebGIS啟蒙首選✖五家地圖API✖近百個程式範例✖實用簡易口訣✖學習難度分級✖補充ES6小知識


本篇文章請搭配
[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


上一篇
[3D地圖-CesiumJS系列] 二、建立飛航軌跡及動畫
下一篇
《你的地圖會說話? WebGIS與JavaScript的情感交織》結束,才是真正的開始。
系列文
《你的地圖會說話? WebGIS與JavaScript的情感交織》30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言