はじめに
本日2/14はバレンタインデー。
「もしかすると机の中や靴箱にチョコが入っているかも」とかいうドキドキした気持ちなんて十数年前から忘れてしまった自分ですが、こんな日くらいは甘い気持ちに包まれたいと思い、あめを降らせてみることにしました。
完成品
その名の通り「あめちゃん」を降らせています。
少し解説
完全に出オチネタなので、少しコードの説明。
オープンソースのGISエンジン、Cesiumを使っています。
Cesiumの基本的なはじめかたはこちらを参照。
CesiumのSandcastleから、Particle System Weatherを参考にしました。
これは、Cesium上でサポートしているパーティクルで表現しています。
パーティクルについての詳しい説明は公式サイトをご覧いただければ幸いですが、こんな表現や、
こんな表現
ができるようです。
あめの表現としては以下のようにパーティクルを発生しています。
const viewer = new Cesium.Viewer("cesiumContainer", {
shouldAnimate: true,
terrain: Cesium.Terrain.fromWorldTerrain(),
});
const scene = viewer.scene;
const snowParticleSize = 12.0;
const snowRadius = 100000.0;
const minimumSnowImageSize = new Cesium.Cartesian2(
snowParticleSize,
snowParticleSize
);
const maximumSnowImageSize = new Cesium.Cartesian2(
snowParticleSize * 2.0,
snowParticleSize * 2.0
);
let snowGravityScratch = new Cesium.Cartesian3();
//callback
const snowUpdate = function (particle, dt) {
snowGravityScratch = Cesium.Cartesian3.normalize(
particle.position,
snowGravityScratch
);
Cesium.Cartesian3.multiplyByScalar( //重力の強さ
snowGravityScratch,
Cesium.Math.randomBetween(-10.0, -50.0),
snowGravityScratch
);
particle.velocity = Cesium.Cartesian3.add(
particle.velocity,
snowGravityScratch,
particle.velocity
);
};
function startCandy(image_file) {
//カラーをランダムに決定
let rand_red= Math.floor( Math.random() *100)/100;
let rand_green = Math.floor( Math.random() *100)/100;
let rand_blue= Math.floor( Math.random() *100)/100;
let col = new Cesium.Color(rand_red, rand_green, rand_blue, 1);
scene.primitives.removeAll();
scene.primitives.add(
new Cesium.ParticleSystem({
modelMatrix: new Cesium.Matrix4.fromTranslation(
scene.camera.position
),
minimumSpeed: 1.0, //particleのランダムスピードの最小値
maximumSpeed: 2.0, //particleのランダムスピードの最大値
lifetime: 15.0,
emitter: new Cesium.SphereEmitter(1000), //particleの種類
startScale: 2, //particleのスタート時のサイズ
endScale: 1.0, //particleの終了時のサイズ
image: image_file, //画像ファイル
emissionRate: 700.0, //particleの量/秒
startColor: col, //particleのスタート時の色(今回はランダムの色)
endColor: Cesium.Color.WHITE.withAlpha(1.0), //particleの終了時の色
minimumImageSize: minimumSnowImageSize,
maximumImageSize: maximumSnowImageSize,
updateCallback: rainUpdate,
})
);
}
scene.primitives.add()
でパーティクルをシーンに追加して、new Cesium.ParticleSystem()
で発生させるパーティクルのプロパティを設定するようです。
html部分はメニューを追加しただけで、特に何も説明するものはありません。
<div id="cesiumContainer" class="cesium"></div>
<div id = "menubar">
<div>
<div>降飴量</div>
<input id ="range_slide" type="range" min="0.0" max="1000.0" step="1" data-bind="value: height, valueUpdate: 'input'">
</div>
<div>
<select name="menu" id="menu">
<option value="img/candy.png">あめ</option>
<option value="img/heart_love.png">チョコ</option>
<option value="img/ringoame.png">りんご飴</option>
<option value="other">アップロード</option>
</select>
<input type="file" id="upload_img" name="upload_img" accept="image/png, image/jpeg" />
</div>
<div>
<button id="button_reset">リセット</button>
</div>
</div>
「あめ」を降らすのに手を加えたParticleSystemの主なプロパティを紹介します。
emitter
発生させるパーティクルの形状を指定します。
emitter: new Cesium.BoxEmitter(new Cesium.Cartesian3(0.5, 0.5, 0.5))
とすればボックス状にパーティクルを発生
emitter: new Cesium.CircleEmitter(0.5)
とすれば円形にパーティクルを発生
emitter: new Cesium.ConeEmitter(Cesium.Math.toRadians(30.0))
円錐形にパーティクルを発生
emitter: new Cesium.SphereEmitter(Cesium.Math.toRadians(30.0))
同じくSandCastleに上がっているFireworksもSphereでパーティクルを発生させているようです。
image
パーティクルで表示させる画像のURLを指定します。
今回は、「あめ」のほか、チョコレートやリンゴ飴、あとアップロードした画像も降らせることができるようにしています。
それぞれの選択に合わせて、このイメージURLを変更してパーティクル発生を実行しています。
document.getElementById("menu").addEventListener("change", function () {
if(document.getElementById("menu").value=="other"){
document.getElementById("upload_img").style.display = "block";
}else{
document.getElementById("upload_img").style.display = "none";
let value = document.getElementById("menu").value;
startCandy(value);
}
});
emissionRate
1秒間におけるパーティクルを発生させる量を指定します。
降雨量ならぬ降飴量として、スライドバーを設けてこのemissionRateの数値を変化できるようにしています。
document.getElementById("range_slide").addEventListener("input", function () {
let rangeValue = document.getElementById("range_slide").value;
scene.primitives._primitives[0].emissionRate = rangeValue;
});
updateCallback
パーティクルの更新として、毎フレーム呼び出されるコールバック関数を指定しています。コールバック関数の内容は以下のようになっていますが、ここでそれぞれのパーティクルの重力やスピードなどをランダムに変化させているようです。
//callback
const snowUpdate = function (particle, dt) {
snowGravityScratch = Cesium.Cartesian3.normalize(
particle.position,
snowGravityScratch
);
Cesium.Cartesian3.multiplyByScalar( //重力の強さ
snowGravityScratch,
Cesium.Math.randomBetween(-10.0, -50.0),
snowGravityScratch
);
particle.velocity = Cesium.Cartesian3.add(
particle.velocity,
snowGravityScratch,
particle.velocity
);
};
そのほかのプロパティはこちらのドキュメントをご覧ください。
リセットボタン
今回のパーティクルはカメラの表示位置に対してパーティクルが発生しているので、地図を移動すると降水域(降飴域)の外に出る。その際はリセットを押してパーティクルを発生し直せるようになっています。
document.getElementById("button_reset").addEventListener("click", function () {
let value = scene.primitives._primitives[0].image;
startCandy(value);
});
思ってたんとちゃう
このコールバック関数内に、パーティクルの色をランダムで設定されるようにすれば、いろんな色のあめが降ってきて、さらに幸せな気持ちになるんちゃう、と思い以下を追記したところ
particle.startColor.red= Math.floor( Math.random() *100)/100;
particle.startColor.green = Math.floor( Math.random() *100)/100;
particle.startColor.blue= Math.floor( Math.random() *100)/100;
思ってたんとちゃうけど、これはこれで楽しいからいっか。
パーティクル、ちょっと難しいイメージやったけど、ちょっと仲良くできてよかった。
皆さんも少しでも甘い気持ちになってくれれば幸いです。