/
Lightning.scala
154 lines (135 loc) · 5.44 KB
/
Lightning.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package net.ladstatt.apps
import scala.collection.JavaConversions.seqAsJavaList
import scala.util.Random
import javafx.animation.FadeTransition
import javafx.application.Application
import javafx.event.ActionEvent
import javafx.event.Event
import javafx.event.EventHandler
import javafx.scene.Group
import javafx.scene.Scene
import javafx.scene.effect.Bloom
import javafx.scene.effect.GaussianBlur
import javafx.scene.input.MouseEvent
import javafx.scene.layout.BorderPane
import javafx.scene.media.Media
import javafx.scene.media.MediaPlayer
import javafx.scene.paint.Color
import javafx.scene.shape.Line
import javafx.scene.shape.Rectangle
import javafx.stage.Stage
import javafx.util.Duration
import javafx.scene.web.WebView
object Lightning {
def main(args: Array[String]): Unit = {
Application.launch(classOf[Lightning], args: _*)
}
}
class Lightning extends javafx.application.Application {
val canvasWidth = 1024
val canvasHeight = 768
val jaggingSections = 100
val jaggedFactor = 2
val lightningTime = 400
def mkEventHandler[E <: Event](f: E => Unit) = new EventHandler[E] { def handle(e: E) = f(e) }
val mediaResource = new Media(getClass.getResource("/strike.mp3").toString)
val catUrl = getClass.getResource("/cat.html").toString
var cnt = 0
var catCount = 20
override def start(primaryStage: Stage): Unit = {
primaryStage.setTitle("Lightning strikes (with easter egg)")
val mainGroup = new Group
val borderPane = new BorderPane()
val drawingBoard = new Group()
drawingBoard.getChildren().add({
val b = new Rectangle(0, 0, canvasWidth, canvasHeight)
b.setFill(Color.BLACK)
b.addEventHandler(MouseEvent.MOUSE_CLICKED, mkEventHandler(
(e: MouseEvent) => {
cnt = cnt + 1
if (cnt < catCount) {
new MediaPlayer(mediaResource).play
val startVec = Vec(canvasWidth / 2, 100)
val destVec = Vec(e.getX, e.getY())
val jaggedLines = jaggedlines(startVec, destVec, jaggingSections, Color.WHITESMOKE)
val duration = (Random.nextDouble * lightningTime).toInt
drawingBoard.getChildren.addAll(jaggedLines.map(withFade(_, duration, Random.nextDouble + 0.2)))
} else {
lazy val browser = new WebView()
browser.setPrefHeight(canvasHeight)
browser.setPrefWidth(canvasWidth)
lazy val webEngine = browser.getEngine()
webEngine.load(catUrl)
mainGroup.getChildren.clear()
mainGroup.getChildren().add(browser)
}
}))
b
})
borderPane.setCenter(drawingBoard)
mainGroup.getChildren().add(borderPane)
primaryStage.setScene(new Scene(mainGroup, canvasWidth, canvasHeight))
primaryStage.show()
}
def withFade(group: Group, duration: Int, brightness: Double) = {
val ft = new FadeTransition(Duration.millis(duration), group)
ft.setFromValue(brightness / 8)
ft.setToValue(brightness)
ft.setCycleCount(4)
ft.setAutoReverse(true)
ft.setOnFinished(mkEventHandler((e: ActionEvent) => {
group.getChildren().clear()
}))
ft.play()
group
}
def mkPos(source: Vec, dest: Vec, count: Int, color: Color): List[Vec] = {
val totalVec = (source - dest)
val maxLen = totalVec.length
val onedir = totalVec.onedir
val length = totalVec.length / count
val normal = onedir.normal
val elongation = jaggedFactor * maxLen / count
val wiggle = Random.nextInt(5).toDouble
List(source) ++ (for (i <- 1 to (count - 1)) yield source + onedir * length * i + normal * wiggle * elongation * Random.nextDouble) ++ List(dest)
}
def jaggedlines(source: Vec, dest: Vec, count: Int, color: Color): List[Group] = {
val mainPositions = mkPos(source, dest, count, color)
// val mainSize = mainPositions.size
// val randIdx = mainSize * Random.nextDouble.toInt
// val rest = mainSize - randIdx
// val randPt = mainPositions(randIdx)
// val branchPositions = mkPos(randPt, randPt + ((dest - randPt).onedir.spin(scala.math.Pi / 8) * rest), rest, color)
// val allPositions = mainPositions ++ branchPositions
val allPositions = mainPositions
(for (List(a, b) <- allPositions.sliding(2)) yield mkLine(a, b, color)).toList
}
case class Vec(x: Double, y: Double) {
def -(that: Vec) = Vec(that.x - x, that.y - y)
def +(that: Vec) = Vec(x + that.x, y + that.y)
def *(factor: Double) = Vec(factor * x, factor * y)
def /(l: Double) = if (l != 0) Vec(x / l, y / l) else sys.error("div.0")
def length = scala.math.sqrt(x * x + y * y)
def onedir = this / length
def normal = Vec(-y, x)
def spin(phi: Double) = Vec(x * scala.math.cos(phi) - y * scala.math.sin(phi), x * scala.math.sin(phi) + y * scala.math.cos(phi))
}
def mkRandColor = {
def randInt = (Random.nextFloat * 255).toInt
Color.rgb(randInt, randInt, randInt)
}
def mkLine(source: Vec, dest: Vec, color: Color): Group = {
val g = new Group()
val refline = new Line(source.x, source.y, dest.x, dest.y)
refline.setStroke(color)
val line = new Line(source.x, source.y, dest.x, dest.y)
line.setStroke(color)
line.setStrokeWidth(4)
val bloom = new Bloom(1.0)
val blur = new GaussianBlur()
blur.setInput(bloom)
line.setEffect(blur)
g.getChildren().addAll(refline, line)
g
}
}