Last active
January 23, 2017 14:28
-
-
Save skht777/5f181f978a41ed4e390eb3447669b8c4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scalafx.Includes._ | |
import scalafx.animation.AnimationTimer | |
import scalafx.application.JFXApp | |
import scalafx.application.JFXApp.PrimaryStage | |
import scalafx.scene.canvas.{Canvas, GraphicsContext} | |
import scalafx.scene.effect.BlendMode | |
import scalafx.scene.image._ | |
import scalafx.scene.paint.Color | |
import scalafx.scene.{Group, Scene} | |
object ParticleTest extends JFXApp { | |
def reset() = { | |
gc.globalBlendMode = BlendMode.SrcOver | |
gc.fill = Color.Black | |
gc.fillRect(0, 0, canvas.getWidth, canvas.getHeight) | |
} | |
stage = new PrimaryStage | |
// キャンバスを作成 | |
val canvas: Canvas = new Canvas { | |
width = 600 | |
height = 400 | |
} | |
// シーングラフを作成 | |
val root = new Group(canvas) | |
// シーンを作成 | |
stage.scene = new Scene(root) | |
// グラフィクス・コンテキストを取得し、初期表示画面を描画 | |
val gc: GraphicsContext = canvas.getGraphicsContext2D | |
reset() | |
gc.fill = Color.White | |
gc.fillText("画面をクリックするとアニメーションが再生されます", 0, 20) | |
// 画像カタログ表示 | |
Particle.images.indices map (i => (i, i % 10 + 1, i / 10)) foreach { | |
case (i, row, column) => gc.drawImage(Particle.images(i), column * Particle.imgW, row * Particle.imgH) | |
} | |
// 初期パーティクルを作成 | |
var particles: Seq[Particle] = Seq() | |
// アニメーション開始 | |
def timer: AnimationTimer = { | |
var startTime: Long = 0 | |
var beforeTime: Long = 0 | |
var current: Long = 0 | |
// t ns | |
AnimationTimer((t: Long) => { | |
def count: Long = { | |
val rate = 100 // ms | |
(t - startTime) / (rate * math.pow(10, 6).toLong) | |
} | |
def drawCount() = { | |
// FPSを計算 | |
val fps = math.pow(10, 9).toLong / (t - beforeTime) | |
// FPSを描画 | |
// パーティクル数を描画 | |
gc.fill = Color.White | |
gc.fillText("FPS :" + fps, 0, 20) | |
gc.fillText("particle :" + particles.size, 0, 40) | |
} | |
// 開始時間を取得 | |
if (startTime == 0) startTime = t | |
// パーティクルの位置を更新し、ライフサイクルを終えたものを削除 | |
particles = particles map (_.update) filter (_.isBright) | |
// 画面を初期化 | |
reset() | |
// パーティクルを描画 | |
gc.globalBlendMode = BlendMode.Add | |
particles foreach (_.draw(gc)) | |
// カウントを描画 | |
drawCount() | |
// カウントアップ | |
current = count | |
beforeTime = t | |
}) | |
} | |
root.onMouseClicked = e => { | |
// 画面クリック時にアニメーションを開始 | |
if (particles.isEmpty) timer.start() | |
// パーティクルを追加 | |
particles ++= Particle(10, e.getX, e.getY) | |
} | |
} | |
object Particle { | |
// パーティクルに設定可能な最大輝度レベル | |
val brightNum = 100 | |
// パーティクル画像の幅、高さ | |
val (imgW, imgH) = (32, 32) | |
// 画像ファイルを準備 | |
val images: Array[Image] = preCreateImage | |
def apply(n: Int, x: Double, y: Double): Seq[Particle] = 1 to n map (_ => apply(x, y)) | |
private[this] def apply(x: Double, y: Double) = { | |
// ランダムな輝度を設定 | |
def bright = (1 + Math.random) * (brightNum / 2) | |
// ランダムな速度を設定 | |
def radian = math.toRadians(math.random * 360) | |
// パーティクル輝度の減衰率 | |
val decay = 0.32 | |
val (vecX, vecY) = (math.sin(radian), math.cos(radian)) | |
Particle(x, y, bright, vecX, vecY, decay) | |
} | |
/** | |
* 画像を作成 | |
* | |
*/ | |
private[this] def preCreateImage: Array[Image] = { | |
// 画像を作成 | |
def createImage(i: Int): Image = { | |
// パーティクルの輝度レベル間のギャップ | |
val span = 50 | |
// 輝度を計算 | |
val bright: Double = i * span | |
// ピクセル配列を作成 | |
val pixels: Array[Int] = new Array[Int](imgW * imgH) | |
for (y <- 0 until imgH) { | |
for (x <- 0 until imgW) { | |
def rgbCode(r: Int, g: Int, b: Int, a: Int) = { | |
// 上限値を設定 | |
def hex(n: Int) = if (n < 255) n else 255 | |
(a << 24) | (hex(r) << 16) | (hex(g) << 8) | hex(b) | |
} | |
// インデックスを計算 | |
val index = (y * imgW) + x | |
// ピクセルを設定 | |
val Seq(disX, disY) = Seq((x, imgW), (y, imgH)) map { | |
case (pos, max) => pos - max / 2.0 | |
} map math.abs | |
// 距離を計算 | |
val distance = math.sqrt(disX * disX + disY * disY) | |
// 光の強さを計算 | |
val strength = bright / distance / distance | |
// RGB値を計算 | |
val Seq(r, g, b, a) = Seq(strength / 1.5, strength, strength, 255) map (_.toInt) | |
pixels(index) = rgbCode(r, g, b, a) // RGB値を設定 | |
} | |
} | |
val img: WritableImage = new WritableImage(imgW, imgH) | |
// ピクセル配列から画像を作成 | |
img.getPixelWriter.setPixels(0, 0, imgW, imgH, PixelFormat.getIntArgbInstance, pixels, 0, imgW) | |
img | |
} | |
// リストに画像を追加 | |
0 until brightNum map createImage toArray | |
} | |
} | |
/** | |
* パーティクル(1つ)をあらわすクラス | |
* | |
* @author tomo, skht777 | |
* | |
*/ | |
sealed case class Particle(x: Double, y: Double, bright: Double, | |
vecX: Double, vecY: Double, private val decay: Double) { | |
def update: Particle = copy(x + vecX, y + vecY, bright - decay) | |
def isBright: Boolean = bright > 0 | |
def draw(gc: GraphicsContext) = gc.drawImage(Particle.images(bright toInt), x, y) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
libraryDependencies += "org.scalafx" %% "scalafx" % "8.0.102-R11"