Skip to content

Instantly share code, notes, and snippets.

@skht777
Last active January 23, 2017 14:28
Show Gist options
  • Save skht777/5f181f978a41ed4e390eb3447669b8c4 to your computer and use it in GitHub Desktop.
Save skht777/5f181f978a41ed4e390eb3447669b8c4 to your computer and use it in GitHub Desktop.
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)
}
@skht777
Copy link
Author

skht777 commented Jan 20, 2017

libraryDependencies += "org.scalafx" %% "scalafx" % "8.0.102-R11"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment