BLOG

ブログ

2024/12/09 技術系

【kotlin】アニメーションで拡散表現~日常の疑問解決編その1~

この記事を書いた人 K.Y

皆さんは日常生活でどのようなことに疑問を感じるでしょうか?

「植物の水やりって朝と夜どっちがいいんだろう」とか「なんで鳥は飛ぶのだろう」とか哲学的なことまで、誰しも一度は考えたことがあると思います。

今回はそんな日常的に感じる疑問を、Androidのアニメーションで解決するコーナーを始めてみます。そのシリーズ1となります。

唐突ではありますが、まずはこのコーナーを始めようと思った理由について軽く触れていきたいと思います。

※ 最初に注意点

本記事は、私が思っている疑問に対してそれを表現するようなアニメーションの作成を行うという内容です。あくまで実務で使うものではなく私個人がアニメーションで表現したいだけというものですので、あまり実用的じゃありません(面白半分で閲覧いただけると)

また、可能な限り化学要素の箇所は嘘を書かないよう、できる範囲で調べてから書いてますが、もしかしたら嘘を記述している可能性があります。もし間違いがあれば訂正しますので、ご指摘お願いします。

動機

  • 日常の疑問を調べるいい機会になる

普段はのほほんと日々を過ごしている私ですが、日常生活で疑問に感じることはたくさんあります。
基本的には気になったその場でスマホで調べるのですが、それがアウトプットできると面白そうだなと思いました。

  • 人に聞かれた時のため答えられるようになりたい

果たして今回の疑問が必要になるのかどうかは微妙ですが、子供に限らず人から聞かれた時に曖昧にではなくきちんと答えられるようになりたいので、その踏み台にできればと考えています。

  • 最近Androidアプリでアニメーションを触る機会がない

これはエンジニア的理由ですが、最近Androidアプリにてアニメーションに触る機会がないのでその機会を無理やり作ろうと思いました。

環境情報

  • MacBook Air M2
  • Android Studio Koala | 2024.1.1
  • kotlin バージョン1.9.0

では、今から今回の疑問と私が理解したアニメーションについて述べていきます。

今回の疑問「空はなぜ青いのか」

では早速今回の疑問になりますが、「空はなぜ青いのか」です。

・・・・・・・。

皆さんは説明できますでしょうか?

  • 海が青いからそれが映し出されている?
  • 宇宙が青色でそれが見えてるだけ?

などといった子供っぽい理由なら思い浮かびましたが、お恥ずかしながら私は説明ができませんでした。

回答

その答えはズバリ「レイリー散乱」によるもの、となります。

・・・聞き覚えがない単語です。どういったものか説明していきます。

レイリー散乱とは

あまり出典としてはよくないかもですが、参考としてWikipediaにはこう記載があります。

レイリー散乱(レイリーさんらん、: Rayleigh scattering)とは、波長よりも小さいサイズの粒子や構造ゆらぎによる光の散乱である。透明液体固体中でも起きるが、典型的な現象は気体中の散乱であり、日中の空が青く見えるのは、レイリー散乱の周波数特性によるものである

なんとなく光の散乱が起こっていることがわかりますが、なんのこっちゃわかりません。この説明で分からなかったので、少しずつ調べていきます。

前提 -光の波長について-

まず普段私たちは陽の光、つまり太陽光を浴びて生活していますが、この太陽光は実は色々な波長が含まれています。

このようなものを見たことがないでしょうか。理科の授業とかで習いましたね。

波長が長いもの(赤色。この中で最も遠くまで届く)ものから波長が短い(遠くまで届きにくい)ものまで存在しています。

参考:化学実験データベース

この光が太陽から発せられ、地球に届きます。

地球は大気に覆われている

地球には大気があり、その大部分は酸素や窒素など、小さな分子で構成されています。

この大気中の小さなものが散乱の説明には重要になってきます。

先ほどの光の図だと、届く光は虹色に見えそうなものですが、どうして空は青いのでしょうか🤔

届く光は、波長の短い光ほどぶつかりやすい

光は太陽から地球に届き大気中を通り抜けていきますが、その際、波長の短い光は長い光より空気や塵にぶつかって散乱しやすいという性質を持っています。先ほど出てきた酸素や窒素といった、分子のような小さいものにぶつかります。

実は、この光がぶつかって光の散乱が起きることを「レイリー散乱」と呼びます。(かっこいい)

このレイリー散乱の特徴として、青色のような波長が短い波長ほど強く(大きく)、赤色のような長い波長ほど弱く(小さく)なります。

つまり、大気中で分子などにぶつかって散乱された青色の光が私たちの目に映っているため、空は全体が青く見えるのです!

なんとなく理解できるようになりました!

では次にわかりやすく図示するために、Androidアプリのアニメーションで表現してみようと思います。

アニメーション

太陽からの光は白色でいろいろな色が混ざっていますが、今回は白色とします。

※ また、色のわかりやすさのため背景をグレーにしたり分子を黒色にしていますが、太陽光の白色や分子から散乱する色を見やすくするため色を分けているだけですので、今回の散乱の仕組みとは関係がないのことをお含みおきください。

実際に動かしてみるとこんな感じになりました。

光が分子にあたって光が散乱するフローを作成できました!見た目自体は簡素なものですが、イメージしていた散乱の流れはできました。

ポイント

このアニメーションにおけるポイントは光が分子にぶつかった際、青色に散乱させている部分です。

// 散乱用アニメーションを生成
val moleculeAnimations = remember {
  List(50) {
    Animatable(0f) to Random.nextFloat() * 360f
  }
}

LaunchedEffect(startRadiation) {
  if (startRadiation) {
    // アニメーションの開始
    moleculeAnimations.forEach { (anim, _) ->
      launch {
         // ランダムな遅延をつける
        delay(Random.nextLong(0, 500))
         anim.animateTo(
           // 拡散距離をランダムに
           targetValue = Random.nextFloat() * 200f,
           // アニメーションの時間をランダムに
           animationSpec = tween(durationMillis = Random.nextInt(1000, 2000), easing = LinearEasing)
         )
      }
    }
  }
}

// 今回は分子の位置は固定に
val molecules = listOf(
    Offset(200f, 1600f),
    Offset(500f, 1600f),
    Offset(800f, 1600f)
)

// 散乱の描画 
molecules.forEach { molecule ->
  moleculeAnimations.forEach { (anim, angle) ->
    // animが距離、angleが角度
    // X軸方向にどれだけ移動するか
    val offsetX = anim.value * Math.cos(Math.toRadians(angle.toDouble())).toFloat()
    // Y軸方向にどれだけ移動するか
    val offsetY = anim.value * Math.sin(Math.toRadians(angle.toDouble())).toFloat()

    // 透明度
    val alpha = (1f - anim.value / 200f).coerceAtLeast(0f)
    // 色
    val color = Color.Blue.copy(alpha = alpha)
    // サイズ
    val moleculesSize = Random.nextFloat() * 5f + 2f

    drawCircle(
      color = color,
      radius = moleculesSize,
      center = Offset(molecule.x + offsetX, molecule.y + offsetY)
    )
  }
}

散乱部分のコードになります。(他部分のコードは割愛しています)

コードの説明

  1. moleculeAnimationsの生成
    • 50個のアニメーションのリストとして、初期値が0のAnimatableを生成しています。これは、後で実際にアニメーションさせる際に使います。
  2. LaunchedEffectでアニメーション開始
    • startRadiation(別管理のBooleanのフラグ。上記コードには記載していません。)がtrueになると実行され、アニメーションが開始されます。delayを使うことで0〜500ミリ秒のランダムな遅延を追加し、散乱のタイミングにばらつきを与えています。animateToでアニメーションのターゲット距離とアニメーションの所要時間をランダムな範囲で設定し、LinearEasingで一定の速度で拡散させています。これによりばらばらに拡散するように見えます。
  3. 分子の位置と散乱の計算
    • 分子役として、3つの位置(Offset(200f, 1600f)など)に固定で配置しています。これが散乱の基準点となります。(ランダム値で配置してそこから散乱させるのは時間が足りませんでした)
  4. 散乱の描画
    • anim.value(現在の拡散距離)とangle(角度)を基に、分子の位置を計算しています。角度に応じてX方向とY方向の距離が変わるため、散乱がランダムに散らばるように見えます。alphaで拡散距離に基づいて透明度を計算し、遠くになるほど薄く見えるようにしています。colorで青色の分子を、透明度を調整しながら描画します。particleSizeで分子のサイズをランダムで調整しています。

バラバラに拡散するようなアニメーションなので、もっと色々な色を使ってみると綺麗なアニメーションが作れそうです👀

逆に夕焼けが赤いのは

逆に夕焼けなどは、太陽が地平線に近くなり、光が届く距離が長くなるため、波長の短い青色は散乱しきり、波長の長い赤色が目に見えるようになるため赤色に見えます。

我々の目からはその散乱した波長が見えていたから色が変わっていた!ということなんですね。

参考資料

感想

タイトルに1とついているように、できれば疑問解決シリーズとして今後も続けていければと考えています。アニメーションチャレンジ企画として続くことを祈ります。

実用的ではなくても「こうすればこうなる」という勉強をしていくといずれ何かの役に立つと信じて、、、



株式会社ウイングドアは福岡のシステム開発会社です。
現在、私達と一緒に"楽しく仕事が出来る仲間"として、新卒・中途採用を絶賛募集しています!
ウイングドアの仲間達となら楽しく仕事できるかも?と興味をもった方、
お気軽にお問い合わせ下さい!

アーカイブ