-
Notifications
You must be signed in to change notification settings - Fork 1
/
futswirl.fut
328 lines (297 loc) · 14.4 KB
/
futswirl.fut
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
import "lib/github.com/diku-dk/lys/lys"
import "swirl/base"
import "swirl/manual"
import "swirl/fractals"
import "swirl/float_dual"
module f2d_base = import "swirl/fractals_2d"
module f3d_base = import "swirl/fractals_3d"
module f2d = fractals_wrapper manual_2d (f2d_base.fractals f32e) (f2d_base.fractals f64e)
module f3d = fractals_wrapper manual_3d (f3d_base.fractals f32e) (f3d_base.fractals f64e)
type text_content = (i32, i32, i32, i32, i32, i32, i32, i32, i32, f32, i32, f32, f32, f32,
i32, i32, i32, f32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32)
module lys: lys with text_content = text_content = {
type text_content = text_content
type dim_info 'manual32 'manual64 =
{auto_mode: bool, cur_start: float_dual, fractal_id: i32,
manual: (manual32, manual64)}
-- A hack because we need this type in a module type as well.
type sized_state [h][w] =
{height: i64, width: i64, rng: rng,
iterations2: i32, iterations3: i32, iterations4: i32,
time: float_dual, vp_zoom: float_dual, vp_center: vec2_float_dual.vector,
render: render_result_base [h][w] float_dual, render_approach: render_approach,
paused: bool, shift_key: bool, mouse: (i32, i32),
auto_zoom: bool, auto_zoom_zoom_factor: float_dual,
float_bits: float_bits,
dim: #dim2 | #dim3,
dim2_info: dim_info f2d.manual32 f2d.manual64,
dim3_info: dim_info f3d.manual32 f3d.manual64}
type~ state = sized_state [][]
module type lys_fractals_base = {
type fractal
type manual32
type manual64
type manual = (manual32, manual64)
val gen_manual: rng -> gen_manual_constraint -> (rng, manual)
val fractal_from_id: i32 -> fractal
val fractal_name: fractal -> string []
val render_fractal: float_bits -> fractal -> float_dual -> manual ->
(h: i64) -> (w: i64) -> i32 ->
float_dual -> vec2_float_dual.vector -> render_approach ->
render_result_base [h][w] float_dual
val fractal_choices: i32
val dim_info [h][w]: sized_state [h][w] -> dim_info manual32 manual64
val set_dim_info [h][w]: sized_state [h][w] -> dim_info manual32 manual64 -> sized_state [h][w]
val dim_id: i32
val fid_offset: i32
}
module fractals_extended (f: lys_fractals_base) = {
-- | Set the id of the fractal to show.
def fractal_id (s: state): i32 =
(f.dim_info s).fractal_id % f.fractal_choices
-- | Get the id of the fractal to show.
def set_fractal_id (s: state) (i: i32): state =
f.set_dim_info s (f.dim_info s with fractal_id = i)
-- | Get the number of iterations for the current fractal.
def scalarloop_iterations (s: state): i32 =
if s.render.n_trans == 2
then s.iterations2
else if s.render.n_trans == 3
then s.iterations3
else s.iterations4
-- We are currently limited by Futhark using 32-bit integers. We could work
-- around this, but in practice having that many particles is very slow, so
-- we we use this check for now. Only applies to the scalarloop render
-- approach.
def max_iterations (n_trans: i32): i32 =
t32 (f32.log (2**31 - 4096) / f32.log (r32 n_trans))
-- | Set the number of iterations for the current fractal.
def set_scalarloop_iterations (s: state) (iter: i32): state =
if s.render.n_trans == 2 then s with iterations2 = iter
else if s.render.n_trans == 3 then s with iterations3 = iter
else s with iterations4 = iter
def zoom_at_mouse (zoom_factor: float_dual) (s: state): state =
let xy_factor = float_dual.i64 (i64.min s.height s.width) float_dual.* s.vp_zoom
let xb = float_dual.i64 (i64.i32 s.mouse.0 - s.width / 2)
let xd = xb float_dual./ xy_factor float_dual.- xb float_dual./ (xy_factor float_dual.* zoom_factor)
let yb = float_dual.i64 (i64.i32 s.mouse.1 - s.height / 2)
let yd = yb float_dual./ xy_factor float_dual.- yb float_dual./ (xy_factor float_dual.* zoom_factor)
in s with vp_zoom = s.vp_zoom float_dual.* zoom_factor
with vp_center.x = s.vp_center.x float_dual.+ xd
with vp_center.y = s.vp_center.y float_dual.+ yd
def event (e: event) (s: state): state =
match e
case #step td ->
let (rng, manual, cur_start) =
if (f.dim_info s).auto_mode
then let tdiff = s.time float_dual.- (f.dim_info s).cur_start
let (rng, x) = f32dist.rand (0, 10000 / float_dual.to_f32 tdiff) s.rng
in if x < 0.95
-- There is 95% chance of generating a new fractal after showing
-- the current one for 10 seconds.
then let (rng, manual) = f.gen_manual rng #none
in (rng, manual, s.time)
else (rng, (f.dim_info s).manual, (f.dim_info s).cur_start)
else (s.rng, (f.dim_info s).manual, (f.dim_info s).cur_start)
let s = s with time = (if s.paused then s.time else s.time float_dual.+ float_dual.f32 td float_dual./ s.vp_zoom)
with rng = rng
with render = let iter = scalarloop_iterations s
let iter' = match s.render_approach
case #scalarloop ->
let max_iter = max_iterations (s.render.n_trans)
in i32.min iter max_iter
case #cullbranches -> iter
in f.render_fractal s.float_bits (f.fractal_from_id (fractal_id s))
s.time (f.dim_info s).manual
s.height s.width iter'
s.vp_zoom s.vp_center
s.render_approach
let s = if s.auto_zoom
then zoom_at_mouse s.auto_zoom_zoom_factor s
else s
in f.set_dim_info s (f.dim_info s
with manual = manual
with cur_start = cur_start)
case #keydown {key} ->
if key == SDLK_SPACE
then s with paused = !s.paused
else if key == SDLK_LSHIFT || key == SDLK_RSHIFT
then s with shift_key = true
else if key == SDLK_PAGEUP
then s with vp_zoom = s.vp_zoom float_dual.* (fdc 1.1)
else if key == SDLK_PAGEDOWN
then s with vp_zoom = float_dual.max (fdc 0.001) (s.vp_zoom float_dual./ (fdc 1.1))
else if key == SDLK_HOME
then s with vp_zoom = fdc 1
with vp_center = {x=fdc 0, y=fdc 0}
else if key == SDLK_LEFT && s.shift_key
then s with vp_center.x = s.vp_center.x float_dual.- fdc 0.01 float_dual./ s.vp_zoom
else if key == SDLK_RIGHT && s.shift_key
then s with vp_center.x = s.vp_center.x float_dual.+ fdc 0.01 float_dual./ s.vp_zoom
else if key == SDLK_UP && s.shift_key
then s with vp_center.y = s.vp_center.y float_dual.- fdc 0.01 float_dual./ s.vp_zoom
else if key == SDLK_DOWN && s.shift_key
then s with vp_center.y = s.vp_center.y float_dual.+ fdc 0.01 float_dual./ s.vp_zoom
else if key == SDLK_LEFT
then set_fractal_id s (fractal_id s - 1)
else if key == SDLK_RIGHT
then set_fractal_id s (fractal_id s + 1)
else if key == SDLK_DOWN
then set_scalarloop_iterations s (i32.max 0 (scalarloop_iterations s - 1))
else if key == SDLK_UP
then set_scalarloop_iterations s (scalarloop_iterations s + 1)
else if key == SDLK_d
then s with dim = match s.dim
case #dim2 -> #dim3
case #dim3 -> #dim2
else if key == SDLK_f
then s with float_bits = if settings.enable_f64
then match s.float_bits
case #f32 -> #f64
case #f64 -> #f32
else s.float_bits
else if key == SDLK_r
then s with render_approach = match s.render_approach
case #scalarloop -> #cullbranches
case #cullbranches -> #scalarloop
else if key == SDLK_0 || key == SDLK_KP_0
then f.set_dim_info s (f.dim_info s
with auto_mode = !(f.dim_info s).auto_mode)
else let (k1, k2, k3, k4) =
(key == SDLK_1 || key == SDLK_KP_1,
key == SDLK_2 || key == SDLK_KP_2,
key == SDLK_3 || key == SDLK_KP_3,
key == SDLK_4 || key == SDLK_KP_4)
in if k1 || k2 || k3 || k4
then let c = if k1 then #none
else if k2 then #trans 2i32
else if k3 then #trans 3i32
else #trans 4i32
let (rng, manual) = f.gen_manual s.rng c
in f.set_dim_info (s with rng = rng)
(f.dim_info s with manual = manual
with cur_start = s.time)
else s
case #keyup {key} ->
if key == SDLK_LSHIFT || key == SDLK_RSHIFT
then s with shift_key = false
else s
case #mouse {buttons, x, y} ->
let x_diff = s.mouse.0 - x
let y_diff = s.mouse.1 - y
let s = s with mouse = (x, y)
let xy_factor = float_dual.i64 (i64.min s.height s.width) float_dual.* s.vp_zoom
let s = if buttons & 1 == 1 || buttons & 4 == 4
then s with vp_center.x = s.vp_center.x float_dual.+ float_dual.i32 x_diff float_dual./ xy_factor
with vp_center.y = s.vp_center.y float_dual.+ float_dual.i32 y_diff float_dual./ xy_factor
else s
let s = if buttons & 4 == 4
then s with auto_zoom = true
else s with auto_zoom = false
in s
case #wheel {dx=_, dy} ->
if s.auto_zoom
then s with auto_zoom_zoom_factor = s.auto_zoom_zoom_factor float_dual.+ float_dual.i32 dy float_dual.* fdc 0.01
else let zoom_factor = fdc 1 float_dual.+ float_dual.i32 dy float_dual.* fdc 0.01
in zoom_at_mouse zoom_factor s
case _ -> s
def render (s: state) =
s.render.render
def text_content (fps: f32) (s: state): text_content =
let n_trans = s.render.n_trans
in (i32.bool (f.dim_id == 0), i32.bool (f.dim_id == 1),
i32.bool (s.float_bits == #f32), i32.bool (s.float_bits == #f64),
f.fid_offset + fractal_id s,
n_trans,
s.render.n_iterations,
n_trans, s.render.n_iterations, f32.i32 n_trans**f32.i32 s.render.n_iterations,
s.render.n_points,
float_dual.to_f32 s.vp_center.x, float_dual.to_f32 s.vp_center.y, float_dual.to_f32 s.vp_zoom,
i32.bool (f.dim_info s).auto_mode,
t32 fps,
i32.bool (s.render_approach == #cullbranches),
float_dual.to_f32 s.render.rot_square_radius,
i32.bool (s.render_approach == #scalarloop),
i32.bool (n_trans == 2), s.iterations2, max_iterations 2,
i32.bool (n_trans == 3), s.iterations3, max_iterations 3,
i32.bool (n_trans == 4), s.iterations4, max_iterations 4)
}
module f2de = fractals_extended {
open f2d
def dim_id = 0i32
def fid_offset = 0i32
def dim_info (s: state): dim_info manual32 manual64 = s.dim2_info
def set_dim_info (s: state) (d: dim_info manual32 manual64): state =
s with dim2_info = d
}
module f3de = fractals_extended {
open f3d
def dim_id = 1i32
def fid_offset = f2d.fractal_choices
def dim_info (s: state): dim_info manual32 manual64 = s.dim3_info
def set_dim_info (s: state) (d: dim_info manual32 manual64): state =
s with dim3_info = d
}
def grab_mouse = false
def init (seed: u32) (h: i64) (w: i64): state =
let rng = rnge.rng_from_seed [i32.u32 seed]
let (rng, manual_2d) = f2d.gen_manual rng (#trans 3)
let (rng, manual_3d) = f3d.gen_manual rng (#trans 3)
in {height=h, width=w,
rng=rng,
iterations2=settings.iterations2,
iterations3=settings.iterations3,
iterations4=settings.iterations4,
time=fdc 0, vp_zoom=fdc 1, vp_center={x=fdc 0, y=fdc 0},
paused=false, shift_key=false,
mouse=(0, 0), auto_zoom=false, auto_zoom_zoom_factor=fdc 1.01,
render={n_trans=0, n_points=0, n_iterations=0,
rot_square_radius=fdc 0,
render=replicate h (replicate w 0)},
render_approach=#cullbranches,
float_bits=#f32,
dim=#dim2,
dim2_info={auto_mode=false, cur_start=fdc 0,
fractal_id=0, manual=manual_2d},
dim3_info={auto_mode=false, cur_start=fdc 0,
fractal_id=0, manual=manual_3d}}
def resize h w (s: state) =
s with height = h with width = w
def event (e: event) (s: state): state =
match s.dim
case #dim2 -> f2de.event e s
case #dim3 -> f3de.event e s
def render (s: state) =
match s.dim
case #dim2 -> f2de.render s
case #dim3 -> f3de.render s
def text_content (fps: f32) (s: state): text_content =
match s.dim
case #dim2 -> f2de.text_content fps s
case #dim3 -> f3de.text_content fps s
def text_format () =
"Dimensions: [%[ |X]] 2D [%[ |X]] 3D\n"
++ "Float size:" ++ " [%[ |X]] 32 bits" ++ (if settings.enable_f64 then " [%[ |X]] 64 bits" else "%[]") ++ "\n"
++ "Fractal: %["
++ (loop s = "" for i < f2d.fractal_choices do
s ++ "|" ++ f2d.fractal_name (f2d.fractal_from_id i))[1:]
++ (loop s = "" for i < f3d.fractal_choices do
s ++ "|" ++ f3d.fractal_name (f3d.fractal_from_id i))
++ "]\n"
++ "Branch factor: %d\n"
++ "Iterations: %d\n"
++ "Particles (without culling): %d^%d ≈ %.03le\n"
++ "Particles (with culling): %d\n"
++ "Viewport: center (%.03le, %.03le); zoom %.03le\n"
++ "Auto mode: %[disabled|enabled]\n"
++ "FPS: %d\n"
++ "\n"
++ "[%[ |X]] cullbranches rendering:\n"
++ "Rotated square radius: %.03f\n"
++ "\n"
++ "[%[ |X]] scalarloop rendering:\n"
++ "%[ |Current:] Iterations (2 transforms): %d (max: %d)\n"
++ "%[ |Current:] Iterations (3 transforms): %d (max: %d)\n"
++ "%[ |Current:] Iterations (4 transforms): %d (max: %d)"
def text_colour = const argb.white
}