moonscratch 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/dist/chunk-DQk6qfdC.mjs +18 -0
  2. package/dist/index.d.mts +1173 -0
  3. package/dist/index.mjs +27135 -0
  4. package/package.json +6 -1
  5. package/.agents/skills/moonbit-agent-guide/LICENSE +0 -202
  6. package/.agents/skills/moonbit-agent-guide/SKILL.mbt.md +0 -1126
  7. package/.agents/skills/moonbit-agent-guide/SKILL.md +0 -1126
  8. package/.agents/skills/moonbit-agent-guide/ide.md +0 -116
  9. package/.agents/skills/moonbit-agent-guide/references/advanced-moonbit-build.md +0 -106
  10. package/.agents/skills/moonbit-agent-guide/references/moonbit-language-fundamentals.mbt.md +0 -422
  11. package/.agents/skills/moonbit-agent-guide/references/moonbit-language-fundamentals.md +0 -422
  12. package/.agents/skills/moonbit-practice/SKILL.md +0 -258
  13. package/.agents/skills/moonbit-practice/assets/ci.yaml +0 -25
  14. package/.agents/skills/moonbit-practice/reference/agents.md +0 -1469
  15. package/.agents/skills/moonbit-practice/reference/configuration.md +0 -228
  16. package/.agents/skills/moonbit-practice/reference/ffi.md +0 -229
  17. package/.agents/skills/moonbit-practice/reference/ide.md +0 -189
  18. package/.agents/skills/moonbit-practice/reference/performance.md +0 -217
  19. package/.agents/skills/moonbit-practice/reference/refactor.md +0 -154
  20. package/.agents/skills/moonbit-practice/reference/stdlib.md +0 -351
  21. package/.agents/skills/moonbit-practice/reference/testing.md +0 -228
  22. package/.agents/skills/moonbit-refactoring/LICENSE +0 -21
  23. package/.agents/skills/moonbit-refactoring/SKILL.md +0 -323
  24. package/.githooks/README.md +0 -23
  25. package/.githooks/pre-commit +0 -3
  26. package/.github/workflows/copilot-setup-steps.yml +0 -40
  27. package/AGENTS.md +0 -91
  28. package/PLAN.md +0 -64
  29. package/TODO.md +0 -120
  30. package/benchmarks/calc.bench.ts +0 -144
  31. package/benchmarks/draw.bench.ts +0 -215
  32. package/benchmarks/load.bench.ts +0 -28
  33. package/benchmarks/render.bench.ts +0 -53
  34. package/benchmarks/run.bench.ts +0 -8
  35. package/benchmarks/types.d.ts +0 -15
  36. package/docs/scratch-vm-specs/eventloop.md +0 -103
  37. package/docs/scratch-vm-specs/moonscratch-time-separation.md +0 -50
  38. package/index.html +0 -91
  39. package/js/AGENTS.md +0 -5
  40. package/js/a.ts +0 -52
  41. package/js/assets/AGENTS.md +0 -5
  42. package/js/assets/base64.test.ts +0 -14
  43. package/js/assets/base64.ts +0 -21
  44. package/js/assets/build-asset.test.ts +0 -26
  45. package/js/assets/build-asset.ts +0 -28
  46. package/js/assets/create.test.ts +0 -142
  47. package/js/assets/create.ts +0 -122
  48. package/js/assets/index.test.ts +0 -15
  49. package/js/assets/index.ts +0 -2
  50. package/js/assets/types.ts +0 -26
  51. package/js/assets/validation.test.ts +0 -34
  52. package/js/assets/validation.ts +0 -25
  53. package/js/assets.test.ts +0 -14
  54. package/js/assets.ts +0 -1
  55. package/js/index.test.ts +0 -26
  56. package/js/index.ts +0 -3
  57. package/js/render/index.test.ts +0 -65
  58. package/js/render/index.ts +0 -13
  59. package/js/render/sharp.ts +0 -87
  60. package/js/render/svg.ts +0 -68
  61. package/js/render/types.ts +0 -35
  62. package/js/render/utils.ts +0 -108
  63. package/js/render/webgl.ts +0 -274
  64. package/js/sharp-optional.d.ts +0 -16
  65. package/js/test/helpers.ts +0 -116
  66. package/js/test/hikkaku-sample.test.ts +0 -37
  67. package/js/test/rubik-components.input-motion.test.ts +0 -60
  68. package/js/test/rubik-components.lists.test.ts +0 -49
  69. package/js/test/rubik-components.operators.test.ts +0 -104
  70. package/js/test/rubik-components.pen.test.ts +0 -112
  71. package/js/test/rubik-components.procedures-loops.test.ts +0 -72
  72. package/js/test/rubik-components.variables-branches.test.ts +0 -57
  73. package/js/test/rubik-components.visibility-entry.test.ts +0 -31
  74. package/js/test/test-projects.ts +0 -598
  75. package/js/test/variable.ts +0 -200
  76. package/js/test/warp.test.ts +0 -59
  77. package/js/vm/AGENTS.md +0 -6
  78. package/js/vm/README.md +0 -183
  79. package/js/vm/bindings.test.ts +0 -13
  80. package/js/vm/bindings.ts +0 -5
  81. package/js/vm/compare-operators.test.ts +0 -145
  82. package/js/vm/constants.test.ts +0 -11
  83. package/js/vm/constants.ts +0 -4
  84. package/js/vm/effect-guards.test.ts +0 -68
  85. package/js/vm/effect-guards.ts +0 -44
  86. package/js/vm/factory.test.ts +0 -486
  87. package/js/vm/factory.ts +0 -615
  88. package/js/vm/headless-vm.test.ts +0 -131
  89. package/js/vm/headless-vm.ts +0 -342
  90. package/js/vm/index.test.ts +0 -28
  91. package/js/vm/index.ts +0 -5
  92. package/js/vm/internal-types.ts +0 -32
  93. package/js/vm/json.test.ts +0 -40
  94. package/js/vm/json.ts +0 -273
  95. package/js/vm/normalize.test.ts +0 -48
  96. package/js/vm/normalize.ts +0 -65
  97. package/js/vm/options.test.ts +0 -30
  98. package/js/vm/options.ts +0 -55
  99. package/js/vm/pen-transparency.test.ts +0 -115
  100. package/js/vm/program-wasm.ts +0 -322
  101. package/js/vm/scheduler-render.test.ts +0 -401
  102. package/js/vm/scratch-assets.test.ts +0 -136
  103. package/js/vm/scratch-assets.ts +0 -202
  104. package/js/vm/types.ts +0 -358
  105. package/js/vm/value-guards.test.ts +0 -25
  106. package/js/vm/value-guards.ts +0 -18
  107. package/moon.mod.json +0 -10
  108. package/scripts/preinstall.ts +0 -4
  109. package/src/AGENTS.md +0 -6
  110. package/src/api.mbt +0 -161
  111. package/src/api_aot_commands.mbt +0 -184
  112. package/src/api_effects_json.mbt +0 -72
  113. package/src/api_options.mbt +0 -60
  114. package/src/api_program_wasm.mbt +0 -1647
  115. package/src/api_program_wat.mbt +0 -2206
  116. package/src/api_snapshot_json.mbt +0 -44
  117. package/src/cmd/AGENTS.md +0 -5
  118. package/src/cmd/main/AGENTS.md +0 -5
  119. package/src/cmd/main/main.mbt +0 -29
  120. package/src/cmd/main/moon.pkg +0 -7
  121. package/src/cmd/main/pkg.generated.mbti +0 -13
  122. package/src/json_helpers.mbt +0 -176
  123. package/src/moon.pkg +0 -65
  124. package/src/moonscratch.mbt +0 -3
  125. package/src/moonscratch_wbtest.mbt +0 -40
  126. package/src/parser_sb3.mbt +0 -890
  127. package/src/pkg.generated.mbti +0 -479
  128. package/src/runtime_eval.mbt +0 -2844
  129. package/src/runtime_exec.mbt +0 -3850
  130. package/src/runtime_render.mbt +0 -2550
  131. package/src/runtime_state.mbt +0 -870
  132. package/src/test/AGENTS.md +0 -3
  133. package/src/test/projects/AGENTS.md +0 -6
  134. package/src/test/projects/moon.pkg +0 -4
  135. package/src/test/projects/moonscratch_compat_test.mbt +0 -642
  136. package/src/test/projects/moonscratch_core_test.mbt +0 -1332
  137. package/src/test/projects/moonscratch_runtime_test.mbt +0 -1087
  138. package/src/test/projects/pkg.generated.mbti +0 -13
  139. package/src/test/projects/test_support.mbt +0 -35
  140. package/src/types_effects.mbt +0 -20
  141. package/src/types_error.mbt +0 -4
  142. package/src/types_options.mbt +0 -31
  143. package/src/types_runtime_structs.mbt +0 -254
  144. package/src/types_vm.mbt +0 -109
  145. package/tsconfig.json +0 -29
  146. package/viewer/index.ts +0 -399
  147. package/viewer/vite.d.ts +0 -1
  148. package/viewer/worker.ts +0 -161
  149. package/vite.config.ts +0 -61
@@ -1,2550 +0,0 @@
1
- ///|
2
- priv struct EffectUniforms {
3
- enable_color : Bool
4
- enable_fisheye : Bool
5
- enable_whirl : Bool
6
- enable_pixelate : Bool
7
- enable_mosaic : Bool
8
- enable_brightness : Bool
9
- enable_ghost : Bool
10
- u_color : Double
11
- u_fisheye : Double
12
- u_whirl : Double
13
- u_pixelate : Double
14
- u_mosaic : Double
15
- u_brightness : Double
16
- u_ghost : Double
17
- }
18
-
19
- ///|
20
- fn render_clamp_int(value : Int, min : Int, max : Int) -> Int {
21
- if value < min {
22
- min
23
- } else if value > max {
24
- max
25
- } else {
26
- value
27
- }
28
- }
29
-
30
- ///|
31
- fn render_clamp_double(value : Double, min : Double, max : Double) -> Double {
32
- if value < min {
33
- min
34
- } else if value > max {
35
- max
36
- } else {
37
- value
38
- }
39
- }
40
-
41
- ///|
42
- fn render_min_double(left : Double, right : Double) -> Double {
43
- if left < right {
44
- left
45
- } else {
46
- right
47
- }
48
- }
49
-
50
- ///|
51
- fn render_max_double(left : Double, right : Double) -> Double {
52
- if left > right {
53
- left
54
- } else {
55
- right
56
- }
57
- }
58
-
59
- ///|
60
- fn render_double_is_close(value : Double, expected : Double) -> Bool {
61
- (value - expected).abs() <= 0.000000001
62
- }
63
-
64
- ///|
65
- fn render_positive_mod_1(value : Double) -> Double {
66
- let raw = value.mod(1.0)
67
- if raw < 0.0 {
68
- raw + 1.0
69
- } else {
70
- raw
71
- }
72
- }
73
-
74
- ///|
75
- fn render_rgb_to_hsv01(
76
- r : Double,
77
- g : Double,
78
- b : Double,
79
- ) -> (Double, Double, Double) {
80
- let max = if r >= g && r >= b { r } else if g >= r && g >= b { g } else { b }
81
- let min = if r <= g && r <= b { r } else if g <= r && g <= b { g } else { b }
82
- let delta = max - min
83
- let mut h = 0.0
84
- if delta != 0.0 {
85
- if max == r {
86
- h = ((g - b) / delta).mod(6.0)
87
- } else if max == g {
88
- h = (b - r) / delta + 2.0
89
- } else {
90
- h = (r - g) / delta + 4.0
91
- }
92
- h /= 6.0
93
- h = render_positive_mod_1(h)
94
- }
95
- let s = if max == 0.0 { 0.0 } else { delta / max }
96
- (h, s, max)
97
- }
98
-
99
- ///|
100
- fn render_hsv01_to_rgb(
101
- h : Double,
102
- s : Double,
103
- v : Double,
104
- ) -> (Double, Double, Double) {
105
- if s <= 0.0 {
106
- return (v, v, v)
107
- }
108
- let hh = render_positive_mod_1(h) * 6.0
109
- let i = hh.floor().to_int()
110
- let f = hh - Double::from_int(i)
111
- let p = v * (1.0 - s)
112
- let q = v * (1.0 - s * f)
113
- let t = v * (1.0 - s * (1.0 - f))
114
- match i.mod(6) {
115
- 0 => (v, t, p)
116
- 1 => (q, v, p)
117
- 2 => (p, v, t)
118
- 3 => (p, q, v)
119
- 4 => (t, p, v)
120
- _ => (v, p, q)
121
- }
122
- }
123
-
124
- ///|
125
- fn render_hsv100_to_rgb(h : Double, s : Double, v : Double) -> (Int, Int, Int) {
126
- let (rr, gg, bb) = render_hsv01_to_rgb(
127
- render_positive_mod_1(h / 100.0),
128
- render_clamp_double(s / 100.0, 0.0, 1.0),
129
- render_clamp_double(v / 100.0, 0.0, 1.0),
130
- )
131
- (
132
- render_clamp_int((rr * 255.0).round().to_int(), 0, 255),
133
- render_clamp_int((gg * 255.0).round().to_int(), 0, 255),
134
- render_clamp_int((bb * 255.0).round().to_int(), 0, 255),
135
- )
136
- }
137
-
138
- ///|
139
- fn render_target_pen_rgba(target : TargetState) -> (Int, Int, Int, Int) {
140
- let (r, g, b) = render_hsv100_to_rgb(
141
- target.pen_color,
142
- target.pen_saturation,
143
- target.pen_brightness,
144
- )
145
- let alpha = render_clamp_int(
146
- ((100.0 - render_clamp_double(target.pen_transparency, 0.0, 100.0)) *
147
- 255.0 /
148
- 100.0)
149
- .round()
150
- .to_int(),
151
- 0,
152
- 255,
153
- )
154
- (r, g, b, alpha)
155
- }
156
-
157
- ///|
158
- fn render_blend_rgba(
159
- dst : (Int, Int, Int, Int),
160
- src : (Int, Int, Int, Int),
161
- ) -> (Int, Int, Int, Int) {
162
- let (dr, dg, db, da) = dst
163
- let (sr, sg, sb, sa) = src
164
- if sa <= 0 {
165
- return dst
166
- }
167
- if da <= 0 {
168
- return src
169
- }
170
- let src_a = Double::from_int(sa) / 255.0
171
- let dst_a = Double::from_int(da) / 255.0
172
- let out_a = src_a + dst_a * (1.0 - src_a)
173
- if out_a <= 0.0 {
174
- return (0, 0, 0, 0)
175
- }
176
- let out_r = (
177
- Double::from_int(sr) * src_a +
178
- Double::from_int(dr) * dst_a * (1.0 - src_a)
179
- ) /
180
- out_a
181
- let out_g = (
182
- Double::from_int(sg) * src_a +
183
- Double::from_int(dg) * dst_a * (1.0 - src_a)
184
- ) /
185
- out_a
186
- let out_b = (
187
- Double::from_int(sb) * src_a +
188
- Double::from_int(db) * dst_a * (1.0 - src_a)
189
- ) /
190
- out_a
191
- (
192
- render_clamp_int(out_r.round().to_int(), 0, 255),
193
- render_clamp_int(out_g.round().to_int(), 0, 255),
194
- render_clamp_int(out_b.round().to_int(), 0, 255),
195
- render_clamp_int((out_a * 255.0).round().to_int(), 0, 255),
196
- )
197
- }
198
-
199
- ///|
200
- fn render_reset_pen_bounds(vm : Vm) -> Unit {
201
- vm.pen_bounds_valid = false
202
- vm.pen_min_x = 0
203
- vm.pen_min_y = 0
204
- vm.pen_max_x = 0
205
- vm.pen_max_y = 0
206
- }
207
-
208
- ///|
209
- fn render_mark_pen_bounds(
210
- vm : Vm,
211
- min_x : Int,
212
- max_x : Int,
213
- min_y : Int,
214
- max_y : Int,
215
- ) -> Unit {
216
- let width = vm.pen_width
217
- let height = vm.pen_height
218
- if width <= 0 || height <= 0 {
219
- return
220
- }
221
- if max_x < 0 || max_y < 0 || min_x >= width || min_y >= height {
222
- return
223
- }
224
- let clamped_min_x = render_clamp_int(min_x, 0, width - 1)
225
- let clamped_max_x = render_clamp_int(max_x, 0, width - 1)
226
- let clamped_min_y = render_clamp_int(min_y, 0, height - 1)
227
- let clamped_max_y = render_clamp_int(max_y, 0, height - 1)
228
- if clamped_min_x > clamped_max_x || clamped_min_y > clamped_max_y {
229
- return
230
- }
231
- if vm.pen_bounds_valid {
232
- if clamped_min_x < vm.pen_min_x {
233
- vm.pen_min_x = clamped_min_x
234
- }
235
- if clamped_max_x > vm.pen_max_x {
236
- vm.pen_max_x = clamped_max_x
237
- }
238
- if clamped_min_y < vm.pen_min_y {
239
- vm.pen_min_y = clamped_min_y
240
- }
241
- if clamped_max_y > vm.pen_max_y {
242
- vm.pen_max_y = clamped_max_y
243
- }
244
- } else {
245
- vm.pen_bounds_valid = true
246
- vm.pen_min_x = clamped_min_x
247
- vm.pen_max_x = clamped_max_x
248
- vm.pen_min_y = clamped_min_y
249
- vm.pen_max_y = clamped_max_y
250
- }
251
- }
252
-
253
- ///|
254
- fn render_mark_pen_pixel_base(vm : Vm, base : Int) -> Unit {
255
- let width = vm.pen_width
256
- if width <= 0 || base < 0 {
257
- return
258
- }
259
- let pixel_index = base / 4
260
- let py = pixel_index / width
261
- let px = pixel_index - py * width
262
- render_mark_pen_bounds(vm, px, px, py, py)
263
- }
264
-
265
- ///|
266
- fn vm_clear_pen_pixels(vm : Vm) -> Unit {
267
- vm.pen_pixels.clear()
268
- vm.pen_vectors.clear()
269
- render_reset_pen_bounds(vm)
270
- vm.render_revision += 1
271
- vm.render_cache_valid = false
272
- }
273
-
274
- ///|
275
- fn ensure_pen_pixels(vm : Vm) -> Unit {
276
- let required = vm.pen_width * vm.pen_height * 4
277
- if required <= 0 {
278
- vm.pen_pixels.clear()
279
- render_reset_pen_bounds(vm)
280
- return
281
- }
282
- if vm.pen_pixels.length() == required {
283
- return
284
- }
285
- vm.pen_pixels.clear()
286
- render_reset_pen_bounds(vm)
287
- for _ in 0..<required {
288
- vm.pen_pixels.push(b'\x00')
289
- }
290
- }
291
-
292
- ///|
293
- fn render_stage_to_pixel_double(
294
- vm : Vm,
295
- x : Double,
296
- y : Double,
297
- ) -> (Double, Double) {
298
- (
299
- (x + 240.0) / 480.0 * Double::from_int(vm.pen_width),
300
- (180.0 - y) / 360.0 * Double::from_int(vm.pen_height),
301
- )
302
- }
303
-
304
- ///|
305
- fn render_blend_pen_pixel_base(
306
- vm : Vm,
307
- base : Int,
308
- src : (Int, Int, Int, Int),
309
- ) -> Unit {
310
- let (sr, sg, sb, sa) = src
311
- if sa <= 0 {
312
- return
313
- }
314
- if sa >= 255 {
315
- vm.pen_pixels[base] = sr.to_byte()
316
- vm.pen_pixels[base + 1] = sg.to_byte()
317
- vm.pen_pixels[base + 2] = sb.to_byte()
318
- vm.pen_pixels[base + 3] = sa.to_byte()
319
- render_mark_pen_pixel_base(vm, base)
320
- return
321
- }
322
- let da = vm.pen_pixels[base + 3].to_int()
323
- if da <= 0 {
324
- vm.pen_pixels[base] = sr.to_byte()
325
- vm.pen_pixels[base + 1] = sg.to_byte()
326
- vm.pen_pixels[base + 2] = sb.to_byte()
327
- vm.pen_pixels[base + 3] = sa.to_byte()
328
- render_mark_pen_pixel_base(vm, base)
329
- return
330
- }
331
- let blended = render_blend_rgba(
332
- (
333
- vm.pen_pixels[base].to_int(),
334
- vm.pen_pixels[base + 1].to_int(),
335
- vm.pen_pixels[base + 2].to_int(),
336
- da,
337
- ),
338
- src,
339
- )
340
- vm.pen_pixels[base] = blended.0.to_byte()
341
- vm.pen_pixels[base + 1] = blended.1.to_byte()
342
- vm.pen_pixels[base + 2] = blended.2.to_byte()
343
- vm.pen_pixels[base + 3] = blended.3.to_byte()
344
- if blended.3 > 0 {
345
- render_mark_pen_pixel_base(vm, base)
346
- }
347
- }
348
-
349
- ///|
350
- fn render_draw_thin_opaque_pen_line(
351
- vm : Vm,
352
- x0 : Double,
353
- y0 : Double,
354
- x1 : Double,
355
- y1 : Double,
356
- rgb : (Int, Int, Int),
357
- ) -> Unit {
358
- let width = vm.pen_width
359
- let height = vm.pen_height
360
- if width <= 0 || height <= 0 {
361
- return
362
- }
363
- let (r, g, b) = rgb
364
- let dx = x1 - x0
365
- let dy = y1 - y0
366
- let steps = render_max_double(dx.abs(), dy.abs()).ceil().to_int()
367
- let draw = fn(px : Int, py : Int) {
368
- if px < 0 || py < 0 || px >= width || py >= height {
369
- return
370
- }
371
- let base = (py * width + px) * 4
372
- vm.pen_pixels[base] = r.to_byte()
373
- vm.pen_pixels[base + 1] = g.to_byte()
374
- vm.pen_pixels[base + 2] = b.to_byte()
375
- vm.pen_pixels[base + 3] = b'\xff'
376
- render_mark_pen_bounds(vm, px, px, py, py)
377
- }
378
- if steps <= 0 {
379
- draw(x0.floor().to_int(), y0.floor().to_int())
380
- return
381
- }
382
- let step_x = dx / Double::from_int(steps)
383
- let step_y = dy / Double::from_int(steps)
384
- let mut cx = x0
385
- let mut cy = y0
386
- for _ in 0..<=steps {
387
- draw(cx.floor().to_int(), cy.floor().to_int())
388
- cx += step_x
389
- cy += step_y
390
- }
391
- }
392
-
393
- ///|
394
- fn render_draw_thin_opaque_pen_axis(
395
- vm : Vm,
396
- x0 : Double,
397
- y0 : Double,
398
- x1 : Double,
399
- y1 : Double,
400
- rgb : (Int, Int, Int),
401
- ) -> Bool {
402
- let width = vm.pen_width
403
- let height = vm.pen_height
404
- if width <= 0 || height <= 0 {
405
- return true
406
- }
407
- let (r, g, b) = rgb
408
- if x0 == x1 {
409
- let px = x0.floor().to_int()
410
- if px < 0 || px >= width {
411
- return true
412
- }
413
- let min_py = render_min_double(y0, y1).floor().to_int()
414
- let max_py = render_max_double(y0, y1).floor().to_int()
415
- if max_py < 0 || min_py >= height {
416
- return true
417
- }
418
- let start_py = render_clamp_int(min_py, 0, height - 1)
419
- let end_py = render_clamp_int(max_py, 0, height - 1)
420
- for py in start_py..<=end_py {
421
- let base = (py * width + px) * 4
422
- vm.pen_pixels[base] = r.to_byte()
423
- vm.pen_pixels[base + 1] = g.to_byte()
424
- vm.pen_pixels[base + 2] = b.to_byte()
425
- vm.pen_pixels[base + 3] = b'\xff'
426
- }
427
- render_mark_pen_bounds(vm, px, px, start_py, end_py)
428
- return true
429
- }
430
- if y0 == y1 {
431
- let py = y0.floor().to_int()
432
- if py < 0 || py >= height {
433
- return true
434
- }
435
- let min_px = render_min_double(x0, x1).floor().to_int()
436
- let max_px = render_max_double(x0, x1).floor().to_int()
437
- if max_px < 0 || min_px >= width {
438
- return true
439
- }
440
- let start_px = render_clamp_int(min_px, 0, width - 1)
441
- let end_px = render_clamp_int(max_px, 0, width - 1)
442
- let row_base = py * width * 4
443
- for px in start_px..<=end_px {
444
- let base = row_base + px * 4
445
- vm.pen_pixels[base] = r.to_byte()
446
- vm.pen_pixels[base + 1] = g.to_byte()
447
- vm.pen_pixels[base + 2] = b.to_byte()
448
- vm.pen_pixels[base + 3] = b'\xff'
449
- }
450
- render_mark_pen_bounds(vm, start_px, end_px, py, py)
451
- return true
452
- }
453
- false
454
- }
455
-
456
- ///|
457
- fn render_pen_line_diameter(vm : Vm, target : TargetState) -> Double {
458
- let sx = Double::from_int(vm.pen_width) / 480.0
459
- let sy = Double::from_int(vm.pen_height) / 360.0
460
- let scale = if sx < sy { sx } else { sy }
461
- render_clamp_double(target.pen_size * scale, 1.0, 1200.0 * scale)
462
- }
463
-
464
- ///|
465
- fn render_pen_line_offset(target : TargetState) -> Double {
466
- if target.pen_size == 1.0 || target.pen_size == 3.0 {
467
- 0.5
468
- } else {
469
- 0.0
470
- }
471
- }
472
-
473
- ///|
474
- fn render_pen_strokes_are_collinear(
475
- first : PenVectorStroke,
476
- second : PenVectorStroke,
477
- ) -> Bool {
478
- let first_dx = first.x1 - first.x0
479
- let first_dy = first.y1 - first.y0
480
- let second_dx = second.x1 - second.x0
481
- let second_dy = second.y1 - second.y0
482
- if (first_dx == 0.0 && first_dy == 0.0) ||
483
- (second_dx == 0.0 && second_dy == 0.0) {
484
- return true
485
- }
486
- let cross = first_dx * second_dy - first_dy * second_dx
487
- if cross.abs() > 0.0000001 {
488
- return false
489
- }
490
- let dot = first_dx * second_dx + first_dy * second_dy
491
- dot > 0.0
492
- }
493
-
494
- ///|
495
- fn render_queue_pen_stroke(vm : Vm, stroke : PenVectorStroke) -> Unit {
496
- let count = vm.pen_vectors.length()
497
- if count > 0 {
498
- let last_index = count - 1
499
- let last = vm.pen_vectors[last_index]
500
- if last.x1 == stroke.x0 &&
501
- last.y1 == stroke.y0 &&
502
- last.diameter == stroke.diameter &&
503
- last.offset == stroke.offset &&
504
- last.r == stroke.r &&
505
- last.g == stroke.g &&
506
- last.b == stroke.b &&
507
- last.a == stroke.a &&
508
- render_pen_strokes_are_collinear(last, stroke) {
509
- vm.pen_vectors[last_index] = {
510
- x0: last.x0,
511
- y0: last.y0,
512
- x1: stroke.x1,
513
- y1: stroke.y1,
514
- diameter: last.diameter,
515
- offset: last.offset,
516
- r: last.r,
517
- g: last.g,
518
- b: last.b,
519
- a: last.a,
520
- }
521
- vm.redraw_requested = true
522
- return
523
- }
524
- }
525
- vm.pen_vectors.push(stroke)
526
- vm.redraw_requested = true
527
- }
528
-
529
- ///|
530
- fn render_rasterize_pen_stroke(vm : Vm, stroke : PenVectorStroke) -> Unit {
531
- if stroke.a <= 0 {
532
- return
533
- }
534
- let (x0, y0) = render_stage_to_pixel_double(vm, stroke.x0, stroke.y0)
535
- let (x1, y1) = render_stage_to_pixel_double(vm, stroke.x1, stroke.y1)
536
- if stroke.diameter <= 1.0 &&
537
- stroke.a >= 255 &&
538
- (stroke.x0 == stroke.x1 || stroke.y0 == stroke.y1) &&
539
- stroke.x0.floor() == stroke.x0 &&
540
- stroke.y0.floor() == stroke.y0 &&
541
- stroke.x1.floor() == stroke.x1 &&
542
- stroke.y1.floor() == stroke.y1 {
543
- if render_draw_thin_opaque_pen_axis(
544
- vm,
545
- x0 + stroke.offset,
546
- y0 + stroke.offset,
547
- x1 + stroke.offset,
548
- y1 + stroke.offset,
549
- (stroke.r, stroke.g, stroke.b),
550
- ) {
551
- return
552
- }
553
- render_draw_thin_opaque_pen_line(
554
- vm,
555
- x0 + stroke.offset,
556
- y0 + stroke.offset,
557
- x1 + stroke.offset,
558
- y1 + stroke.offset,
559
- (stroke.r, stroke.g, stroke.b),
560
- )
561
- return
562
- }
563
- render_draw_pen_capsule(
564
- vm,
565
- x0 + stroke.offset,
566
- y0 + stroke.offset,
567
- x1 + stroke.offset,
568
- y1 + stroke.offset,
569
- stroke.diameter,
570
- (stroke.r, stroke.g, stroke.b, stroke.a),
571
- )
572
- }
573
-
574
- ///|
575
- fn render_flush_pen_vectors(vm : Vm) -> Unit {
576
- if vm.pen_vectors.is_empty() {
577
- return
578
- }
579
- ensure_pen_pixels(vm)
580
- for stroke in vm.pen_vectors {
581
- render_rasterize_pen_stroke(vm, stroke)
582
- }
583
- vm.pen_vectors.clear()
584
- }
585
-
586
- ///|
587
- fn render_draw_pen_capsule(
588
- vm : Vm,
589
- x0 : Double,
590
- y0 : Double,
591
- x1 : Double,
592
- y1 : Double,
593
- diameter : Double,
594
- color : (Int, Int, Int, Int),
595
- ) -> Unit {
596
- let (r, g, b, a) = color
597
- let width = vm.pen_width
598
- let height = vm.pen_height
599
- if a <= 0 || diameter <= 0.0 || width <= 0 || height <= 0 {
600
- return
601
- }
602
- ensure_pen_pixels(vm)
603
- let fade_radius = (diameter + 1.0) / 2.0
604
- let min_x_raw = (render_min_double(x0, x1) - fade_radius).floor().to_int()
605
- let max_x_raw = (render_max_double(x0, x1) + fade_radius).ceil().to_int()
606
- let min_y_raw = (render_min_double(y0, y1) - fade_radius).floor().to_int()
607
- let max_y_raw = (render_max_double(y0, y1) + fade_radius).ceil().to_int()
608
- if max_x_raw < 0 || max_y_raw < 0 || min_x_raw >= width || min_y_raw >= height {
609
- return
610
- }
611
- let min_x = render_clamp_int(min_x_raw, 0, width - 1)
612
- let max_x = render_clamp_int(max_x_raw, 0, width - 1)
613
- let min_y = render_clamp_int(min_y_raw, 0, height - 1)
614
- let max_y = render_clamp_int(max_y_raw, 0, height - 1)
615
- render_mark_pen_bounds(vm, min_x, max_x, min_y, max_y)
616
- let seg_dx = x1 - x0
617
- let seg_dy = y1 - y0
618
- let seg_len_sq = seg_dx * seg_dx + seg_dy * seg_dy
619
- let fade_radius_sq = fade_radius * fade_radius
620
- let solid_radius = render_max_double(fade_radius - 1.0, 0.0)
621
- let solid_radius_sq = solid_radius * solid_radius
622
- for py in min_y..<=max_y {
623
- let sample_y = Double::from_int(py) + 0.5
624
- let row_base = py * width * 4
625
- for px in min_x..<=max_x {
626
- let sample_x = Double::from_int(px) + 0.5
627
- let mut t = 0.0
628
- if seg_len_sq > 0.0 {
629
- t = ((sample_x - x0) * seg_dx + (sample_y - y0) * seg_dy) / seg_len_sq
630
- if t < 0.0 {
631
- t = 0.0
632
- } else if t > 1.0 {
633
- t = 1.0
634
- }
635
- }
636
- let nearest_x = x0 + seg_dx * t
637
- let nearest_y = y0 + seg_dy * t
638
- let dist_x = sample_x - nearest_x
639
- let dist_y = sample_y - nearest_y
640
- let dist_sq = dist_x * dist_x + dist_y * dist_y
641
- if dist_sq >= fade_radius_sq {
642
- continue
643
- }
644
- let coverage = if dist_sq <= solid_radius_sq {
645
- 1.0
646
- } else {
647
- let distance = dist_sq.sqrt()
648
- render_clamp_double(fade_radius - distance, 0.0, 1.0)
649
- }
650
- if coverage > 0.0 {
651
- let alpha = render_clamp_int(
652
- (Double::from_int(a) * coverage).round().to_int(),
653
- 0,
654
- 255,
655
- )
656
- if alpha > 0 {
657
- let base = row_base + px * 4
658
- render_blend_pen_pixel_base(vm, base, (r, g, b, alpha))
659
- }
660
- }
661
- }
662
- }
663
- }
664
-
665
- ///|
666
- fn render_draw_pen_line(
667
- vm : Vm,
668
- target_index : Int,
669
- old_x : Double,
670
- old_y : Double,
671
- new_x : Double,
672
- new_y : Double,
673
- ) -> Unit {
674
- if target_index < 0 || target_index >= vm.targets.length() {
675
- return
676
- }
677
- let target = vm.targets[target_index]
678
- if target.is_stage || target.deleted {
679
- return
680
- }
681
- let (r, g, b, a) = render_target_pen_rgba(target)
682
- if a <= 0 {
683
- return
684
- }
685
- let color = (r, g, b, a)
686
- let diameter = render_pen_line_diameter(vm, target)
687
- let offset = render_pen_line_offset(target)
688
- let stroke : PenVectorStroke = {
689
- x0: old_x,
690
- y0: old_y,
691
- x1: new_x,
692
- y1: new_y,
693
- diameter,
694
- offset,
695
- r: color.0,
696
- g: color.1,
697
- b: color.2,
698
- a: color.3,
699
- }
700
- render_queue_pen_stroke(vm, stroke)
701
- }
702
-
703
- ///|
704
- fn render_draw_pen_point(vm : Vm, target_index : Int) -> Unit {
705
- if target_index < 0 || target_index >= vm.targets.length() {
706
- return
707
- }
708
- let target = vm.targets[target_index]
709
- if target.is_stage || target.deleted {
710
- return
711
- }
712
- let (r, g, b, a) = render_target_pen_rgba(target)
713
- if a <= 0 {
714
- return
715
- }
716
- let diameter = render_pen_line_diameter(vm, target)
717
- let offset = render_pen_line_offset(target)
718
- let stroke : PenVectorStroke = {
719
- x0: target.x,
720
- y0: target.y,
721
- x1: target.x,
722
- y1: target.y,
723
- diameter,
724
- offset,
725
- r,
726
- g,
727
- b,
728
- a,
729
- }
730
- render_queue_pen_stroke(vm, stroke)
731
- }
732
-
733
- ///|
734
- fn move_target_with_pen(
735
- vm : Vm,
736
- target_index : Int,
737
- new_x : Double,
738
- new_y : Double,
739
- ) -> Unit {
740
- if target_index < 0 || target_index >= vm.targets.length() {
741
- return
742
- }
743
- let old_x = vm.targets[target_index].x
744
- let old_y = vm.targets[target_index].y
745
- vm.targets[target_index].x = new_x
746
- vm.targets[target_index].y = new_y
747
- if old_x != new_x || old_y != new_y {
748
- vm.redraw_requested = true
749
- }
750
- let target = vm.targets[target_index]
751
- if target.pen_down && !target.deleted && !target.is_stage {
752
- render_draw_pen_line(vm, target_index, old_x, old_y, new_x, new_y)
753
- vm.redraw_requested = true
754
- }
755
- }
756
-
757
- ///|
758
- fn render_wrap_index(index : Int, count : Int) -> Int {
759
- if count <= 0 {
760
- 0
761
- } else {
762
- let wrapped = index.mod(count)
763
- if wrapped < 0 {
764
- wrapped + count
765
- } else {
766
- wrapped
767
- }
768
- }
769
- }
770
-
771
- ///|
772
- fn render_current_costume(target : TargetState) -> CostumeImage? {
773
- if target.costumes.is_empty() {
774
- None
775
- } else {
776
- Some(
777
- target.costumes[render_wrap_index(
778
- target.current_costume,
779
- target.costumes.length(),
780
- )],
781
- )
782
- }
783
- }
784
-
785
- ///|
786
- fn render_effect_uniforms(target : TargetState) -> EffectUniforms {
787
- let u_color = (target.looks_effect_color / 200.0).mod(1.0)
788
- let u_fisheye = render_max_double(
789
- 0.0,
790
- (target.looks_effect_fisheye + 100.0) / 100.0,
791
- )
792
- let u_whirl = -target.looks_effect_whirl * @math.PI / 180.0
793
- let u_pixelate = target.looks_effect_pixelate.abs() / 10.0
794
- let mosaic_raw = ((target.looks_effect_mosaic.abs() + 10.0) / 10.0)
795
- .round()
796
- .to_int()
797
- let u_mosaic = Double::from_int(render_clamp_int(mosaic_raw, 1, 512))
798
- let u_brightness = render_clamp_double(
799
- target.looks_effect_brightness,
800
- -100.0,
801
- 100.0,
802
- ) /
803
- 100.0
804
- let u_ghost = 1.0 -
805
- render_clamp_double(target.looks_effect_ghost, 0.0, 100.0) / 100.0
806
- {
807
- enable_color: target.looks_effect_color != 0.0,
808
- enable_fisheye: target.looks_effect_fisheye != 0.0,
809
- enable_whirl: target.looks_effect_whirl != 0.0,
810
- enable_pixelate: target.looks_effect_pixelate != 0.0,
811
- enable_mosaic: target.looks_effect_mosaic != 0.0,
812
- enable_brightness: target.looks_effect_brightness != 0.0,
813
- enable_ghost: target.looks_effect_ghost != 0.0,
814
- u_color,
815
- u_fisheye,
816
- u_whirl,
817
- u_pixelate,
818
- u_mosaic,
819
- u_brightness,
820
- u_ghost,
821
- }
822
- }
823
-
824
- ///|
825
- fn render_apply_point_effects(
826
- u : Double,
827
- v : Double,
828
- width : Int,
829
- height : Int,
830
- uniforms : EffectUniforms,
831
- ) -> (Double, Double) {
832
- let mut x = u
833
- let mut y = v
834
- if uniforms.enable_mosaic {
835
- x = render_positive_mod_1(uniforms.u_mosaic * x)
836
- y = render_positive_mod_1(uniforms.u_mosaic * y)
837
- }
838
- if uniforms.enable_pixelate && uniforms.u_pixelate > 0.0 {
839
- let texel_x = Double::from_int(width) / uniforms.u_pixelate
840
- let texel_y = Double::from_int(height) / uniforms.u_pixelate
841
- if texel_x > 0.0 && texel_y > 0.0 {
842
- x = ((x * texel_x).floor() + 0.5) / texel_x
843
- y = ((y * texel_y).floor() + 0.5) / texel_y
844
- }
845
- }
846
- if uniforms.enable_whirl {
847
- let offset_x = x - 0.5
848
- let offset_y = y - 0.5
849
- let offset_magnitude = (offset_x * offset_x + offset_y * offset_y).sqrt()
850
- let whirl_factor = render_max_double(1.0 - offset_magnitude / 0.5, 0.0)
851
- let whirl_actual = uniforms.u_whirl * whirl_factor * whirl_factor
852
- let sin_whirl = @math.sin(whirl_actual)
853
- let cos_whirl = @math.cos(whirl_actual)
854
- x = cos_whirl * offset_x + sin_whirl * offset_y + 0.5
855
- y = -sin_whirl * offset_x + cos_whirl * offset_y + 0.5
856
- }
857
- if uniforms.enable_fisheye {
858
- let vec_x = (x - 0.5) / 0.5
859
- let vec_y = (y - 0.5) / 0.5
860
- let vec_length = (vec_x * vec_x + vec_y * vec_y).sqrt()
861
- if vec_length > 0.0 {
862
- let r = @math.pow(render_min_double(vec_length, 1.0), uniforms.u_fisheye) *
863
- render_max_double(1.0, vec_length)
864
- let unit_x = vec_x / vec_length
865
- let unit_y = vec_y / vec_length
866
- x = 0.5 + r * unit_x * 0.5
867
- y = 0.5 + r * unit_y * 0.5
868
- }
869
- }
870
- (x, y)
871
- }
872
-
873
- ///|
874
- fn render_apply_color_effects(
875
- rgba : (Int, Int, Int, Int),
876
- uniforms : EffectUniforms,
877
- include_ghost : Bool,
878
- ) -> (Int, Int, Int, Int) {
879
- let (raw_r, raw_g, raw_b, raw_a) = rgba
880
- if raw_a <= 0 {
881
- return (0, 0, 0, 0)
882
- }
883
-
884
- let mut r = Double::from_int(raw_r)
885
- let mut g = Double::from_int(raw_g)
886
- let mut b = Double::from_int(raw_b)
887
- let mut a = Double::from_int(raw_a)
888
- let alpha = a / 255.0
889
-
890
- if (uniforms.enable_color || uniforms.enable_brightness) && alpha > 0.0 {
891
- r /= alpha
892
- g /= alpha
893
- b /= alpha
894
-
895
- if uniforms.enable_color {
896
- let (h0, s0, v0) = render_rgb_to_hsv01(r / 255.0, g / 255.0, b / 255.0)
897
- let mut h = h0
898
- let mut s = s0
899
- let mut v = v0
900
- let min_v = 0.11 / 2.0
901
- let min_s = 0.09
902
- if v < min_v {
903
- h = 0.0
904
- s = 1.0
905
- v = min_v
906
- } else if s < min_s {
907
- h = 0.0
908
- s = min_s
909
- }
910
- h = render_positive_mod_1(uniforms.u_color + h + 1.0)
911
- let (rr, gg, bb) = render_hsv01_to_rgb(h, s, v)
912
- r = rr * 255.0
913
- g = gg * 255.0
914
- b = bb * 255.0
915
- }
916
-
917
- if uniforms.enable_brightness {
918
- let shift = uniforms.u_brightness * 255.0
919
- r += shift
920
- g += shift
921
- b += shift
922
- }
923
-
924
- r *= alpha
925
- g *= alpha
926
- b *= alpha
927
- }
928
-
929
- if include_ghost && uniforms.enable_ghost {
930
- r *= uniforms.u_ghost
931
- g *= uniforms.u_ghost
932
- b *= uniforms.u_ghost
933
- a *= uniforms.u_ghost
934
- }
935
-
936
- (
937
- render_clamp_int(r.round().to_int(), 0, 255),
938
- render_clamp_int(g.round().to_int(), 0, 255),
939
- render_clamp_int(b.round().to_int(), 0, 255),
940
- render_clamp_int(a.round().to_int(), 0, 255),
941
- )
942
- }
943
-
944
- ///|
945
- fn render_sample_costume_rgba_uv(
946
- costume : CostumeImage,
947
- u : Double,
948
- v : Double,
949
- ) -> (Int, Int, Int, Int)? {
950
- if costume.width <= 0 || costume.height <= 0 || costume.pixels.length() < 4 {
951
- return None
952
- }
953
- if u < 0.0 || u >= 1.0 || v < 0.0 || v >= 1.0 {
954
- return None
955
- }
956
- let px = (u * Double::from_int(costume.width)).floor().to_int()
957
- let py = (v * Double::from_int(costume.height)).floor().to_int()
958
- if px < 0 || py < 0 || px >= costume.width || py >= costume.height {
959
- return None
960
- }
961
- let base = (py * costume.width + px) * 4
962
- if base + 3 >= costume.pixels.length() {
963
- return None
964
- }
965
- Some(
966
- (
967
- costume.pixels[base].to_int(),
968
- costume.pixels[base + 1].to_int(),
969
- costume.pixels[base + 2].to_int(),
970
- costume.pixels[base + 3].to_int(),
971
- ),
972
- )
973
- }
974
-
975
- ///|
976
- fn render_no_effect_uniforms() -> EffectUniforms {
977
- {
978
- enable_color: false,
979
- enable_fisheye: false,
980
- enable_whirl: false,
981
- enable_pixelate: false,
982
- enable_mosaic: false,
983
- enable_brightness: false,
984
- enable_ghost: false,
985
- u_color: 0.0,
986
- u_fisheye: 0.0,
987
- u_whirl: 0.0,
988
- u_pixelate: 0.0,
989
- u_mosaic: 0.0,
990
- u_brightness: 0.0,
991
- u_ghost: 0.0,
992
- }
993
- }
994
-
995
- ///|
996
- fn render_sample_stage_backdrop_prepared(
997
- costume : CostumeImage?,
998
- uniforms : EffectUniforms,
999
- apply_point_effects : Bool,
1000
- apply_color_effects : Bool,
1001
- stage_x : Double,
1002
- stage_y : Double,
1003
- ) -> (Int, Int, Int, Int) {
1004
- let mut out = (255, 255, 255, 255)
1005
- match costume {
1006
- Some(costume) => {
1007
- let mut u = (stage_x + 240.0) / 480.0
1008
- let mut v = (180.0 - stage_y) / 360.0
1009
- if apply_point_effects {
1010
- let transformed = render_apply_point_effects(
1011
- u,
1012
- v,
1013
- costume.width,
1014
- costume.height,
1015
- uniforms,
1016
- )
1017
- u = transformed.0
1018
- v = transformed.1
1019
- }
1020
- match render_sample_costume_rgba_uv(costume, u, v) {
1021
- Some(raw) => {
1022
- let src = if apply_color_effects {
1023
- render_apply_color_effects(raw, uniforms, true)
1024
- } else {
1025
- raw
1026
- }
1027
- if src.3 <= 0 {
1028
- ()
1029
- } else if src.3 >= 255 {
1030
- out = (src.0, src.1, src.2, 255)
1031
- } else {
1032
- out = render_blend_rgba(out, src)
1033
- }
1034
- }
1035
- None => ()
1036
- }
1037
- }
1038
- None => ()
1039
- }
1040
- out
1041
- }
1042
-
1043
- ///|
1044
- fn render_sample_stage_backdrop_at(
1045
- vm : Vm,
1046
- stage_x : Double,
1047
- stage_y : Double,
1048
- ) -> (Int, Int, Int, Int) {
1049
- if vm.stage_index < 0 || vm.stage_index >= vm.targets.length() {
1050
- return (255, 255, 255, 255)
1051
- }
1052
- let stage = vm.targets[vm.stage_index]
1053
- let uniforms = render_effect_uniforms(stage)
1054
- render_sample_stage_backdrop_prepared(
1055
- render_current_costume(stage),
1056
- uniforms,
1057
- uniforms.enable_mosaic ||
1058
- uniforms.enable_pixelate ||
1059
- uniforms.enable_whirl ||
1060
- uniforms.enable_fisheye,
1061
- uniforms.enable_color || uniforms.enable_brightness || uniforms.enable_ghost,
1062
- stage_x,
1063
- stage_y,
1064
- )
1065
- }
1066
-
1067
- ///|
1068
- fn render_sample_sprite_at(
1069
- vm : Vm,
1070
- target_index : Int,
1071
- stage_x : Double,
1072
- stage_y : Double,
1073
- include_ghost : Bool,
1074
- respect_visibility : Bool,
1075
- ) -> (Int, Int, Int, Int)? {
1076
- if target_index < 0 || target_index >= vm.targets.length() {
1077
- return None
1078
- }
1079
- let target = vm.targets[target_index]
1080
- if target.deleted || target.is_stage {
1081
- return None
1082
- }
1083
- if respect_visibility && !target.visible {
1084
- return None
1085
- }
1086
- let scale = target.size / 100.0
1087
- if scale <= 0.0 {
1088
- return None
1089
- }
1090
- let costume = match render_current_costume(target) {
1091
- Some(value) => value
1092
- None => return None
1093
- }
1094
- if costume.width <= 0 || costume.height <= 0 || costume.bitmap_resolution <= 0 {
1095
- return None
1096
- }
1097
-
1098
- let radians = (90.0 - target.direction) * @math.PI / 180.0
1099
- let dx = stage_x - target.x
1100
- let dy = stage_y - target.y
1101
- let local_x = (dx * @math.cos(radians) + dy * @math.sin(radians)) / scale
1102
- let local_y = (-dx * @math.sin(radians) + dy * @math.cos(radians)) / scale
1103
- let bitmap_resolution = Double::from_int(costume.bitmap_resolution)
1104
- let px = local_x * bitmap_resolution + costume.rotation_center_x
1105
- let py = costume.rotation_center_y - local_y * bitmap_resolution
1106
- let mut u = px / Double::from_int(costume.width)
1107
- let mut v = py / Double::from_int(costume.height)
1108
- let uniforms = render_effect_uniforms(target)
1109
- let transformed = render_apply_point_effects(
1110
- u,
1111
- v,
1112
- costume.width,
1113
- costume.height,
1114
- uniforms,
1115
- )
1116
- u = transformed.0
1117
- v = transformed.1
1118
-
1119
- match render_sample_costume_rgba_uv(costume, u, v) {
1120
- Some(raw) => {
1121
- let transformed_color = render_apply_color_effects(
1122
- raw, uniforms, include_ghost,
1123
- )
1124
- if transformed_color.3 > 0 {
1125
- Some(transformed_color)
1126
- } else {
1127
- None
1128
- }
1129
- }
1130
- None => None
1131
- }
1132
- }
1133
-
1134
- ///|
1135
- fn render_stamp_sprite_to_pen(vm : Vm, target_index : Int) -> Unit {
1136
- if target_index < 0 || target_index >= vm.targets.length() {
1137
- return
1138
- }
1139
- let target = vm.targets[target_index]
1140
- if target.deleted || target.is_stage {
1141
- return
1142
- }
1143
- let scale = target.size / 100.0
1144
- if scale <= 0.0 {
1145
- return
1146
- }
1147
- let costume = match render_current_costume(target) {
1148
- Some(value) => value
1149
- None => return
1150
- }
1151
- if costume.width <= 0 || costume.height <= 0 || costume.bitmap_resolution <= 0 {
1152
- return
1153
- }
1154
-
1155
- let pen_width = vm.pen_width
1156
- let pen_height = vm.pen_height
1157
- if pen_width <= 0 || pen_height <= 0 {
1158
- return
1159
- }
1160
-
1161
- render_flush_pen_vectors(vm)
1162
- ensure_pen_pixels(vm)
1163
-
1164
- let uniforms = render_effect_uniforms(target)
1165
- let has_point_effects = uniforms.enable_mosaic ||
1166
- uniforms.enable_pixelate ||
1167
- uniforms.enable_whirl ||
1168
- uniforms.enable_fisheye
1169
- let has_color_effects = uniforms.enable_color ||
1170
- uniforms.enable_brightness ||
1171
- uniforms.enable_ghost
1172
-
1173
- let radians = (90.0 - target.direction) * @math.PI / 180.0
1174
- let cos_radians = @math.cos(radians)
1175
- let sin_radians = @math.sin(radians)
1176
- let inv_scale = 1.0 / scale
1177
-
1178
- let pen_width_double = Double::from_int(pen_width)
1179
- let pen_height_double = Double::from_int(pen_height)
1180
- let stage_x_step = 480.0 / pen_width_double
1181
- let stage_y_step = -360.0 / pen_height_double
1182
- let stage_x_base = stage_x_step * 0.5 - 240.0 - target.x
1183
- let stage_y_base = 180.0 + stage_y_step * 0.5 - target.y
1184
-
1185
- let local_x_step_px = stage_x_step * cos_radians * inv_scale
1186
- let local_x_step_py = stage_y_step * sin_radians * inv_scale
1187
- let local_x_base = (stage_x_base * cos_radians + stage_y_base * sin_radians) *
1188
- inv_scale
1189
-
1190
- let local_y_step_px = -stage_x_step * sin_radians * inv_scale
1191
- let local_y_step_py = stage_y_step * cos_radians * inv_scale
1192
- let local_y_base = (-stage_x_base * sin_radians + stage_y_base * cos_radians) *
1193
- inv_scale
1194
-
1195
- let bitmap_resolution = Double::from_int(costume.bitmap_resolution)
1196
- let tex_x_step_px = local_x_step_px * bitmap_resolution
1197
- let tex_x_step_py = local_x_step_py * bitmap_resolution
1198
- let tex_x_base = local_x_base * bitmap_resolution + costume.rotation_center_x
1199
-
1200
- let tex_y_step_px = -local_y_step_px * bitmap_resolution
1201
- let tex_y_step_py = -local_y_step_py * bitmap_resolution
1202
- let tex_y_base = costume.rotation_center_y - local_y_base * bitmap_resolution
1203
-
1204
- let costume_width_double = Double::from_int(costume.width)
1205
- let costume_height_double = Double::from_int(costume.height)
1206
- let mut start_px = 0
1207
- let mut end_px = pen_width - 1
1208
- let mut start_py = 0
1209
- let mut end_py = pen_height - 1
1210
- if !has_point_effects {
1211
- let local_x0 = -costume.rotation_center_x / bitmap_resolution
1212
- let local_x1 = (Double::from_int(costume.width) - costume.rotation_center_x) /
1213
- bitmap_resolution
1214
- let local_y0 = costume.rotation_center_y / bitmap_resolution
1215
- let local_y1 = (
1216
- costume.rotation_center_y - Double::from_int(costume.height)
1217
- ) /
1218
- bitmap_resolution
1219
-
1220
- let corner0_x = target.x +
1221
- scale * (local_x0 * cos_radians - local_y0 * sin_radians)
1222
- let corner0_y = target.y +
1223
- scale * (local_x0 * sin_radians + local_y0 * cos_radians)
1224
- let corner1_x = target.x +
1225
- scale * (local_x1 * cos_radians - local_y0 * sin_radians)
1226
- let corner1_y = target.y +
1227
- scale * (local_x1 * sin_radians + local_y0 * cos_radians)
1228
- let corner2_x = target.x +
1229
- scale * (local_x0 * cos_radians - local_y1 * sin_radians)
1230
- let corner2_y = target.y +
1231
- scale * (local_x0 * sin_radians + local_y1 * cos_radians)
1232
- let corner3_x = target.x +
1233
- scale * (local_x1 * cos_radians - local_y1 * sin_radians)
1234
- let corner3_y = target.y +
1235
- scale * (local_x1 * sin_radians + local_y1 * cos_radians)
1236
-
1237
- let mut min_stage_x = corner0_x
1238
- let mut max_stage_x = corner0_x
1239
- let mut min_stage_y = corner0_y
1240
- let mut max_stage_y = corner0_y
1241
-
1242
- if corner1_x < min_stage_x {
1243
- min_stage_x = corner1_x
1244
- } else if corner1_x > max_stage_x {
1245
- max_stage_x = corner1_x
1246
- }
1247
- if corner2_x < min_stage_x {
1248
- min_stage_x = corner2_x
1249
- } else if corner2_x > max_stage_x {
1250
- max_stage_x = corner2_x
1251
- }
1252
- if corner3_x < min_stage_x {
1253
- min_stage_x = corner3_x
1254
- } else if corner3_x > max_stage_x {
1255
- max_stage_x = corner3_x
1256
- }
1257
- if corner1_y < min_stage_y {
1258
- min_stage_y = corner1_y
1259
- } else if corner1_y > max_stage_y {
1260
- max_stage_y = corner1_y
1261
- }
1262
- if corner2_y < min_stage_y {
1263
- min_stage_y = corner2_y
1264
- } else if corner2_y > max_stage_y {
1265
- max_stage_y = corner2_y
1266
- }
1267
- if corner3_y < min_stage_y {
1268
- min_stage_y = corner3_y
1269
- } else if corner3_y > max_stage_y {
1270
- max_stage_y = corner3_y
1271
- }
1272
-
1273
- start_px = render_clamp_int(
1274
- ((min_stage_x + 240.0) / 480.0 * pen_width_double - 1.0).floor().to_int(),
1275
- 0,
1276
- pen_width - 1,
1277
- )
1278
- end_px = render_clamp_int(
1279
- ((max_stage_x + 240.0) / 480.0 * pen_width_double + 1.0).ceil().to_int(),
1280
- 0,
1281
- pen_width - 1,
1282
- )
1283
- start_py = render_clamp_int(
1284
- ((180.0 - max_stage_y) / 360.0 * pen_height_double - 1.0).floor().to_int(),
1285
- 0,
1286
- pen_height - 1,
1287
- )
1288
- end_py = render_clamp_int(
1289
- ((180.0 - min_stage_y) / 360.0 * pen_height_double + 1.0).ceil().to_int(),
1290
- 0,
1291
- pen_height - 1,
1292
- )
1293
- if start_px > end_px || start_py > end_py {
1294
- return
1295
- }
1296
- }
1297
-
1298
- let start_px_double = Double::from_int(start_px)
1299
- for py in start_py..<=end_py {
1300
- let py_double = Double::from_int(py)
1301
- let mut tex_x = tex_x_base +
1302
- tex_x_step_py * py_double +
1303
- tex_x_step_px * start_px_double
1304
- let mut tex_y = tex_y_base +
1305
- tex_y_step_py * py_double +
1306
- tex_y_step_px * start_px_double
1307
- let mut pen_base = (py * pen_width + start_px) * 4
1308
- for _ in start_px..<=end_px {
1309
- if has_point_effects {
1310
- let mut u = tex_x / costume_width_double
1311
- let mut v = tex_y / costume_height_double
1312
- let transformed = render_apply_point_effects(
1313
- u,
1314
- v,
1315
- costume.width,
1316
- costume.height,
1317
- uniforms,
1318
- )
1319
- u = transformed.0
1320
- v = transformed.1
1321
- match render_sample_costume_rgba_uv(costume, u, v) {
1322
- Some(raw) => {
1323
- let src = if has_color_effects {
1324
- render_apply_color_effects(raw, uniforms, true)
1325
- } else {
1326
- raw
1327
- }
1328
- render_blend_pen_pixel_base(vm, pen_base, src)
1329
- }
1330
- None => ()
1331
- }
1332
- } else if tex_x >= 0.0 &&
1333
- tex_y >= 0.0 &&
1334
- tex_x < costume_width_double &&
1335
- tex_y < costume_height_double {
1336
- let tx = tex_x.floor().to_int()
1337
- let ty = tex_y.floor().to_int()
1338
- let sample_base = (ty * costume.width + tx) * 4
1339
- let raw = (
1340
- costume.pixels[sample_base].to_int(),
1341
- costume.pixels[sample_base + 1].to_int(),
1342
- costume.pixels[sample_base + 2].to_int(),
1343
- costume.pixels[sample_base + 3].to_int(),
1344
- )
1345
- let src = if has_color_effects {
1346
- render_apply_color_effects(raw, uniforms, true)
1347
- } else {
1348
- raw
1349
- }
1350
- render_blend_pen_pixel_base(vm, pen_base, src)
1351
- }
1352
- tex_x += tex_x_step_px
1353
- tex_y += tex_y_step_px
1354
- pen_base += 4
1355
- }
1356
- }
1357
- }
1358
-
1359
- ///|
1360
- fn render_collect_visible_sprite_indices(
1361
- vm : Vm,
1362
- excluded_target_index : Int,
1363
- ) -> Array[Int] {
1364
- let out = Array::new(capacity=vm.targets.length())
1365
- for i, target in vm.targets {
1366
- if i == vm.stage_index || i == excluded_target_index {
1367
- continue
1368
- }
1369
- if target.deleted || target.is_stage || !target.visible {
1370
- continue
1371
- }
1372
- if target.size <= 0.0 {
1373
- continue
1374
- }
1375
- let costume = match render_current_costume(target) {
1376
- Some(costume) => costume
1377
- None => continue
1378
- }
1379
- if costume.width <= 0 ||
1380
- costume.height <= 0 ||
1381
- costume.bitmap_resolution <= 0 ||
1382
- costume.pixels.length() < costume.width * costume.height * 4 {
1383
- continue
1384
- }
1385
- out.push(i)
1386
- }
1387
- out
1388
- }
1389
-
1390
- ///|
1391
- fn render_blend_channel_over_opaque_white(src : Int, src_alpha : Int) -> Byte {
1392
- if src_alpha <= 0 {
1393
- b'\xff'
1394
- } else if src_alpha >= 255 {
1395
- src.to_byte()
1396
- } else {
1397
- ((src * src_alpha + 255 * (255 - src_alpha) + 127) / 255).to_byte()
1398
- }
1399
- }
1400
-
1401
- ///|
1402
- fn render_build_opaque_white_pixels(width : Int, height : Int) -> Array[Byte] {
1403
- if width <= 0 || height <= 0 {
1404
- return []
1405
- }
1406
- Array::make(width * height * 4, b'\xff')
1407
- }
1408
-
1409
- ///|
1410
- fn render_build_opaque_stage_pixels(
1411
- costume : CostumeImage,
1412
- width : Int,
1413
- height : Int,
1414
- ) -> Array[Byte] {
1415
- if width <= 0 || height <= 0 {
1416
- return []
1417
- }
1418
- if costume.width <= 0 || costume.height <= 0 {
1419
- return render_build_opaque_white_pixels(width, height)
1420
- }
1421
- let src_width = costume.width
1422
- let src_height = costume.height
1423
- if costume.pixels.length() < src_width * src_height * 4 {
1424
- return render_build_opaque_white_pixels(width, height)
1425
- }
1426
- let pixels = Array::new(capacity=width * height * 4)
1427
- let src_x_step = Double::from_int(src_width) / Double::from_int(width)
1428
- let src_y_step = Double::from_int(src_height) / Double::from_int(height)
1429
- let mut src_y = src_y_step * 0.5
1430
- for _ in 0..<height {
1431
- let ty = render_clamp_int(src_y.floor().to_int(), 0, src_height - 1)
1432
- let src_row_base = ty * src_width * 4
1433
- let mut src_x = src_x_step * 0.5
1434
- for _ in 0..<width {
1435
- let tx = render_clamp_int(src_x.floor().to_int(), 0, src_width - 1)
1436
- let base = src_row_base + tx * 4
1437
- let src_alpha = costume.pixels[base + 3].to_int()
1438
- pixels.push(
1439
- render_blend_channel_over_opaque_white(
1440
- costume.pixels[base].to_int(),
1441
- src_alpha,
1442
- ),
1443
- )
1444
- pixels.push(
1445
- render_blend_channel_over_opaque_white(
1446
- costume.pixels[base + 1].to_int(),
1447
- src_alpha,
1448
- ),
1449
- )
1450
- pixels.push(
1451
- render_blend_channel_over_opaque_white(
1452
- costume.pixels[base + 2].to_int(),
1453
- src_alpha,
1454
- ),
1455
- )
1456
- pixels.push(b'\xff')
1457
- src_x += src_x_step
1458
- }
1459
- src_y += src_y_step
1460
- }
1461
- pixels
1462
- }
1463
-
1464
- ///|
1465
- fn render_blend_pen_over_opaque_pixels(vm : Vm, pixels : Array[Byte]) -> Unit {
1466
- if !vm.pen_bounds_valid {
1467
- return
1468
- }
1469
- let width = vm.pen_width
1470
- let height = vm.pen_height
1471
- if width <= 0 || height <= 0 {
1472
- return
1473
- }
1474
- let start_x = render_clamp_int(vm.pen_min_x, 0, width - 1)
1475
- let end_x = render_clamp_int(vm.pen_max_x, 0, width - 1)
1476
- let start_y = render_clamp_int(vm.pen_min_y, 0, height - 1)
1477
- let end_y = render_clamp_int(vm.pen_max_y, 0, height - 1)
1478
- if start_x > end_x || start_y > end_y {
1479
- return
1480
- }
1481
- let pen_len = vm.pen_pixels.length()
1482
- let pixels_len = pixels.length()
1483
- for py in start_y..<=end_y {
1484
- let mut base = (py * width + start_x) * 4
1485
- for _ in start_x..<=end_x {
1486
- if base + 3 >= pen_len || base + 3 >= pixels_len {
1487
- break
1488
- }
1489
- let pen_alpha = vm.pen_pixels[base + 3].to_int()
1490
- if pen_alpha > 0 {
1491
- if pen_alpha >= 255 {
1492
- pixels[base] = vm.pen_pixels[base]
1493
- pixels[base + 1] = vm.pen_pixels[base + 1]
1494
- pixels[base + 2] = vm.pen_pixels[base + 2]
1495
- pixels[base + 3] = b'\xff'
1496
- } else {
1497
- let keep = 255 - pen_alpha
1498
- pixels[base] = ((
1499
- vm.pen_pixels[base].to_int() * pen_alpha +
1500
- pixels[base].to_int() * keep +
1501
- 127
1502
- ) /
1503
- 255).to_byte()
1504
- pixels[base + 1] = ((
1505
- vm.pen_pixels[base + 1].to_int() * pen_alpha +
1506
- pixels[base + 1].to_int() * keep +
1507
- 127
1508
- ) /
1509
- 255).to_byte()
1510
- pixels[base + 2] = ((
1511
- vm.pen_pixels[base + 2].to_int() * pen_alpha +
1512
- pixels[base + 2].to_int() * keep +
1513
- 127
1514
- ) /
1515
- 255).to_byte()
1516
- pixels[base + 3] = b'\xff'
1517
- }
1518
- }
1519
- base += 4
1520
- }
1521
- }
1522
- }
1523
-
1524
- ///|
1525
- fn render_blend_pixel_over_opaque_base(
1526
- pixels : Array[Byte],
1527
- base : Int,
1528
- src : (Int, Int, Int, Int),
1529
- ) -> Unit {
1530
- let (sr, sg, sb, sa) = src
1531
- if sa <= 0 {
1532
- return
1533
- }
1534
- if sa >= 255 {
1535
- pixels[base] = sr.to_byte()
1536
- pixels[base + 1] = sg.to_byte()
1537
- pixels[base + 2] = sb.to_byte()
1538
- pixels[base + 3] = b'\xff'
1539
- return
1540
- }
1541
- let keep = 255 - sa
1542
- pixels[base] = ((sr * sa + pixels[base].to_int() * keep + 127) / 255).to_byte()
1543
- pixels[base + 1] = ((sg * sa + pixels[base + 1].to_int() * keep + 127) / 255).to_byte()
1544
- pixels[base + 2] = ((sb * sa + pixels[base + 2].to_int() * keep + 127) / 255).to_byte()
1545
- pixels[base + 3] = b'\xff'
1546
- }
1547
-
1548
- ///|
1549
- fn render_blend_pixel_base(
1550
- pixels : Array[Byte],
1551
- base : Int,
1552
- src : (Int, Int, Int, Int),
1553
- ) -> Unit {
1554
- let (sr, sg, sb, sa) = src
1555
- if sa <= 0 {
1556
- return
1557
- }
1558
- if sa >= 255 {
1559
- pixels[base] = sr.to_byte()
1560
- pixels[base + 1] = sg.to_byte()
1561
- pixels[base + 2] = sb.to_byte()
1562
- pixels[base + 3] = b'\xff'
1563
- return
1564
- }
1565
- let da = pixels[base + 3].to_int()
1566
- if da <= 0 {
1567
- pixels[base] = sr.to_byte()
1568
- pixels[base + 1] = sg.to_byte()
1569
- pixels[base + 2] = sb.to_byte()
1570
- pixels[base + 3] = sa.to_byte()
1571
- return
1572
- }
1573
- if da >= 255 {
1574
- let keep = 255 - sa
1575
- pixels[base] = ((sr * sa + pixels[base].to_int() * keep + 127) / 255).to_byte()
1576
- pixels[base + 1] = ((sg * sa + pixels[base + 1].to_int() * keep + 127) / 255).to_byte()
1577
- pixels[base + 2] = ((sb * sa + pixels[base + 2].to_int() * keep + 127) / 255).to_byte()
1578
- pixels[base + 3] = b'\xff'
1579
- return
1580
- }
1581
- let blended = render_blend_rgba(
1582
- (
1583
- pixels[base].to_int(),
1584
- pixels[base + 1].to_int(),
1585
- pixels[base + 2].to_int(),
1586
- da,
1587
- ),
1588
- src,
1589
- )
1590
- pixels[base] = blended.0.to_byte()
1591
- pixels[base + 1] = blended.1.to_byte()
1592
- pixels[base + 2] = blended.2.to_byte()
1593
- pixels[base + 3] = blended.3.to_byte()
1594
- }
1595
-
1596
- ///|
1597
- fn render_blend_sprite_over_pixels(
1598
- vm : Vm,
1599
- target_index : Int,
1600
- pixels : Array[Byte],
1601
- width : Int,
1602
- height : Int,
1603
- dst_opaque : Bool,
1604
- ) -> Unit {
1605
- if target_index < 0 || target_index >= vm.targets.length() {
1606
- return
1607
- }
1608
- if width <= 0 || height <= 0 {
1609
- return
1610
- }
1611
- let target = vm.targets[target_index]
1612
- if target.deleted || target.is_stage || !target.visible {
1613
- return
1614
- }
1615
- let scale = target.size / 100.0
1616
- if scale <= 0.0 {
1617
- return
1618
- }
1619
- let costume = match render_current_costume(target) {
1620
- Some(value) => value
1621
- None => return
1622
- }
1623
- if costume.width <= 0 || costume.height <= 0 || costume.bitmap_resolution <= 0 {
1624
- return
1625
- }
1626
- if pixels.length() < width * height * 4 {
1627
- return
1628
- }
1629
-
1630
- let uniforms = render_effect_uniforms(target)
1631
- let has_point_effects = uniforms.enable_mosaic ||
1632
- uniforms.enable_pixelate ||
1633
- uniforms.enable_whirl ||
1634
- uniforms.enable_fisheye
1635
- let has_color_effects = uniforms.enable_color ||
1636
- uniforms.enable_brightness ||
1637
- uniforms.enable_ghost
1638
-
1639
- let radians = (90.0 - target.direction) * @math.PI / 180.0
1640
- let cos_radians = @math.cos(radians)
1641
- let sin_radians = @math.sin(radians)
1642
- let inv_scale = 1.0 / scale
1643
-
1644
- let width_double = Double::from_int(width)
1645
- let height_double = Double::from_int(height)
1646
- let stage_x_step = 480.0 / width_double
1647
- let stage_y_step = -360.0 / height_double
1648
- let stage_x_base = stage_x_step * 0.5 - 240.0 - target.x
1649
- let stage_y_base = 180.0 + stage_y_step * 0.5 - target.y
1650
-
1651
- let local_x_step_px = stage_x_step * cos_radians * inv_scale
1652
- let local_x_step_py = stage_y_step * sin_radians * inv_scale
1653
- let local_x_base = (stage_x_base * cos_radians + stage_y_base * sin_radians) *
1654
- inv_scale
1655
-
1656
- let local_y_step_px = -stage_x_step * sin_radians * inv_scale
1657
- let local_y_step_py = stage_y_step * cos_radians * inv_scale
1658
- let local_y_base = (-stage_x_base * sin_radians + stage_y_base * cos_radians) *
1659
- inv_scale
1660
-
1661
- let bitmap_resolution = Double::from_int(costume.bitmap_resolution)
1662
- let tex_x_step_px = local_x_step_px * bitmap_resolution
1663
- let tex_x_step_py = local_x_step_py * bitmap_resolution
1664
- let tex_x_base = local_x_base * bitmap_resolution + costume.rotation_center_x
1665
-
1666
- let tex_y_step_px = -local_y_step_px * bitmap_resolution
1667
- let tex_y_step_py = -local_y_step_py * bitmap_resolution
1668
- let tex_y_base = costume.rotation_center_y - local_y_base * bitmap_resolution
1669
-
1670
- let costume_width_double = Double::from_int(costume.width)
1671
- let costume_height_double = Double::from_int(costume.height)
1672
- let mut start_px = 0
1673
- let mut end_px = width - 1
1674
- let mut start_py = 0
1675
- let mut end_py = height - 1
1676
- if !has_point_effects {
1677
- let local_x0 = -costume.rotation_center_x / bitmap_resolution
1678
- let local_x1 = (Double::from_int(costume.width) - costume.rotation_center_x) /
1679
- bitmap_resolution
1680
- let local_y0 = costume.rotation_center_y / bitmap_resolution
1681
- let local_y1 = (
1682
- costume.rotation_center_y - Double::from_int(costume.height)
1683
- ) /
1684
- bitmap_resolution
1685
-
1686
- let corner0_x = target.x +
1687
- scale * (local_x0 * cos_radians - local_y0 * sin_radians)
1688
- let corner0_y = target.y +
1689
- scale * (local_x0 * sin_radians + local_y0 * cos_radians)
1690
- let corner1_x = target.x +
1691
- scale * (local_x1 * cos_radians - local_y0 * sin_radians)
1692
- let corner1_y = target.y +
1693
- scale * (local_x1 * sin_radians + local_y0 * cos_radians)
1694
- let corner2_x = target.x +
1695
- scale * (local_x0 * cos_radians - local_y1 * sin_radians)
1696
- let corner2_y = target.y +
1697
- scale * (local_x0 * sin_radians + local_y1 * cos_radians)
1698
- let corner3_x = target.x +
1699
- scale * (local_x1 * cos_radians - local_y1 * sin_radians)
1700
- let corner3_y = target.y +
1701
- scale * (local_x1 * sin_radians + local_y1 * cos_radians)
1702
-
1703
- let mut min_stage_x = corner0_x
1704
- let mut max_stage_x = corner0_x
1705
- let mut min_stage_y = corner0_y
1706
- let mut max_stage_y = corner0_y
1707
-
1708
- if corner1_x < min_stage_x {
1709
- min_stage_x = corner1_x
1710
- } else if corner1_x > max_stage_x {
1711
- max_stage_x = corner1_x
1712
- }
1713
- if corner2_x < min_stage_x {
1714
- min_stage_x = corner2_x
1715
- } else if corner2_x > max_stage_x {
1716
- max_stage_x = corner2_x
1717
- }
1718
- if corner3_x < min_stage_x {
1719
- min_stage_x = corner3_x
1720
- } else if corner3_x > max_stage_x {
1721
- max_stage_x = corner3_x
1722
- }
1723
- if corner1_y < min_stage_y {
1724
- min_stage_y = corner1_y
1725
- } else if corner1_y > max_stage_y {
1726
- max_stage_y = corner1_y
1727
- }
1728
- if corner2_y < min_stage_y {
1729
- min_stage_y = corner2_y
1730
- } else if corner2_y > max_stage_y {
1731
- max_stage_y = corner2_y
1732
- }
1733
- if corner3_y < min_stage_y {
1734
- min_stage_y = corner3_y
1735
- } else if corner3_y > max_stage_y {
1736
- max_stage_y = corner3_y
1737
- }
1738
-
1739
- start_px = render_clamp_int(
1740
- ((min_stage_x + 240.0) / 480.0 * width_double - 1.0).floor().to_int(),
1741
- 0,
1742
- width - 1,
1743
- )
1744
- end_px = render_clamp_int(
1745
- ((max_stage_x + 240.0) / 480.0 * width_double + 1.0).ceil().to_int(),
1746
- 0,
1747
- width - 1,
1748
- )
1749
- start_py = render_clamp_int(
1750
- ((180.0 - max_stage_y) / 360.0 * height_double - 1.0).floor().to_int(),
1751
- 0,
1752
- height - 1,
1753
- )
1754
- end_py = render_clamp_int(
1755
- ((180.0 - min_stage_y) / 360.0 * height_double + 1.0).ceil().to_int(),
1756
- 0,
1757
- height - 1,
1758
- )
1759
- if start_px > end_px || start_py > end_py {
1760
- return
1761
- }
1762
- }
1763
-
1764
- let start_px_double = Double::from_int(start_px)
1765
- if !has_point_effects && !has_color_effects {
1766
- if render_double_is_close(tex_x_step_py, 0.0) &&
1767
- render_double_is_close(tex_y_step_px, 0.0) {
1768
- let step_x_rounded = tex_x_step_px.round().to_int()
1769
- let step_y_rounded = tex_y_step_py.round().to_int()
1770
- let step_x = if step_x_rounded != 0 &&
1771
- render_double_is_close(tex_x_step_px, Double::from_int(step_x_rounded)) {
1772
- step_x_rounded
1773
- } else {
1774
- 0
1775
- }
1776
- let step_y = if step_y_rounded != 0 &&
1777
- render_double_is_close(tex_y_step_py, Double::from_int(step_y_rounded)) {
1778
- step_y_rounded
1779
- } else {
1780
- 0
1781
- }
1782
- if step_x != 0 && step_y != 0 {
1783
- let tx_start = (tex_x_base + tex_x_step_px * Double::from_int(start_px))
1784
- .floor()
1785
- .to_int()
1786
- let mut ty = (tex_y_base + tex_y_step_py * Double::from_int(start_py))
1787
- .floor()
1788
- .to_int()
1789
- for py in start_py..<=end_py {
1790
- if ty >= 0 && ty < costume.height {
1791
- let mut tx = tx_start
1792
- let mut base = (py * width + start_px) * 4
1793
- for _ in start_px..<=end_px {
1794
- if tx >= 0 && tx < costume.width {
1795
- let sample_base = (ty * costume.width + tx) * 4
1796
- let sa = costume.pixels[sample_base + 3].to_int()
1797
- if sa >= 255 {
1798
- pixels[base] = costume.pixels[sample_base]
1799
- pixels[base + 1] = costume.pixels[sample_base + 1]
1800
- pixels[base + 2] = costume.pixels[sample_base + 2]
1801
- pixels[base + 3] = b'\xff'
1802
- } else if sa > 0 {
1803
- if dst_opaque {
1804
- let keep = 255 - sa
1805
- pixels[base] = ((
1806
- costume.pixels[sample_base].to_int() * sa +
1807
- pixels[base].to_int() * keep +
1808
- 127
1809
- ) /
1810
- 255).to_byte()
1811
- pixels[base + 1] = ((
1812
- costume.pixels[sample_base + 1].to_int() * sa +
1813
- pixels[base + 1].to_int() * keep +
1814
- 127
1815
- ) /
1816
- 255).to_byte()
1817
- pixels[base + 2] = ((
1818
- costume.pixels[sample_base + 2].to_int() * sa +
1819
- pixels[base + 2].to_int() * keep +
1820
- 127
1821
- ) /
1822
- 255).to_byte()
1823
- pixels[base + 3] = b'\xff'
1824
- } else {
1825
- let da = pixels[base + 3].to_int()
1826
- if da <= 0 {
1827
- pixels[base] = costume.pixels[sample_base]
1828
- pixels[base + 1] = costume.pixels[sample_base + 1]
1829
- pixels[base + 2] = costume.pixels[sample_base + 2]
1830
- pixels[base + 3] = sa.to_byte()
1831
- } else if da >= 255 {
1832
- let keep = 255 - sa
1833
- pixels[base] = ((
1834
- costume.pixels[sample_base].to_int() * sa +
1835
- pixels[base].to_int() * keep +
1836
- 127
1837
- ) /
1838
- 255).to_byte()
1839
- pixels[base + 1] = ((
1840
- costume.pixels[sample_base + 1].to_int() * sa +
1841
- pixels[base + 1].to_int() * keep +
1842
- 127
1843
- ) /
1844
- 255).to_byte()
1845
- pixels[base + 2] = ((
1846
- costume.pixels[sample_base + 2].to_int() * sa +
1847
- pixels[base + 2].to_int() * keep +
1848
- 127
1849
- ) /
1850
- 255).to_byte()
1851
- pixels[base + 3] = b'\xff'
1852
- } else {
1853
- let blended = render_blend_rgba(
1854
- (
1855
- pixels[base].to_int(),
1856
- pixels[base + 1].to_int(),
1857
- pixels[base + 2].to_int(),
1858
- da,
1859
- ),
1860
- (
1861
- costume.pixels[sample_base].to_int(),
1862
- costume.pixels[sample_base + 1].to_int(),
1863
- costume.pixels[sample_base + 2].to_int(),
1864
- sa,
1865
- ),
1866
- )
1867
- pixels[base] = blended.0.to_byte()
1868
- pixels[base + 1] = blended.1.to_byte()
1869
- pixels[base + 2] = blended.2.to_byte()
1870
- pixels[base + 3] = blended.3.to_byte()
1871
- }
1872
- }
1873
- }
1874
- }
1875
- tx += step_x
1876
- base += 4
1877
- }
1878
- }
1879
- ty += step_y
1880
- }
1881
- return
1882
- }
1883
- }
1884
-
1885
- for py in start_py..<=end_py {
1886
- let py_double = Double::from_int(py)
1887
- let mut tex_x = tex_x_base +
1888
- tex_x_step_py * py_double +
1889
- tex_x_step_px * start_px_double
1890
- let mut tex_y = tex_y_base +
1891
- tex_y_step_py * py_double +
1892
- tex_y_step_px * start_px_double
1893
- let mut base = (py * width + start_px) * 4
1894
- for _ in start_px..<=end_px {
1895
- if tex_x >= 0.0 &&
1896
- tex_y >= 0.0 &&
1897
- tex_x < costume_width_double &&
1898
- tex_y < costume_height_double {
1899
- let tx = tex_x.floor().to_int()
1900
- let ty = tex_y.floor().to_int()
1901
- let sample_base = (ty * costume.width + tx) * 4
1902
- let sa = costume.pixels[sample_base + 3].to_int()
1903
- if sa >= 255 {
1904
- pixels[base] = costume.pixels[sample_base]
1905
- pixels[base + 1] = costume.pixels[sample_base + 1]
1906
- pixels[base + 2] = costume.pixels[sample_base + 2]
1907
- pixels[base + 3] = b'\xff'
1908
- } else if sa > 0 {
1909
- if dst_opaque {
1910
- let keep = 255 - sa
1911
- pixels[base] = ((
1912
- costume.pixels[sample_base].to_int() * sa +
1913
- pixels[base].to_int() * keep +
1914
- 127
1915
- ) /
1916
- 255).to_byte()
1917
- pixels[base + 1] = ((
1918
- costume.pixels[sample_base + 1].to_int() * sa +
1919
- pixels[base + 1].to_int() * keep +
1920
- 127
1921
- ) /
1922
- 255).to_byte()
1923
- pixels[base + 2] = ((
1924
- costume.pixels[sample_base + 2].to_int() * sa +
1925
- pixels[base + 2].to_int() * keep +
1926
- 127
1927
- ) /
1928
- 255).to_byte()
1929
- pixels[base + 3] = b'\xff'
1930
- } else {
1931
- let da = pixels[base + 3].to_int()
1932
- if da <= 0 {
1933
- pixels[base] = costume.pixels[sample_base]
1934
- pixels[base + 1] = costume.pixels[sample_base + 1]
1935
- pixels[base + 2] = costume.pixels[sample_base + 2]
1936
- pixels[base + 3] = sa.to_byte()
1937
- } else if da >= 255 {
1938
- let keep = 255 - sa
1939
- pixels[base] = ((
1940
- costume.pixels[sample_base].to_int() * sa +
1941
- pixels[base].to_int() * keep +
1942
- 127
1943
- ) /
1944
- 255).to_byte()
1945
- pixels[base + 1] = ((
1946
- costume.pixels[sample_base + 1].to_int() * sa +
1947
- pixels[base + 1].to_int() * keep +
1948
- 127
1949
- ) /
1950
- 255).to_byte()
1951
- pixels[base + 2] = ((
1952
- costume.pixels[sample_base + 2].to_int() * sa +
1953
- pixels[base + 2].to_int() * keep +
1954
- 127
1955
- ) /
1956
- 255).to_byte()
1957
- pixels[base + 3] = b'\xff'
1958
- } else {
1959
- let blended = render_blend_rgba(
1960
- (
1961
- pixels[base].to_int(),
1962
- pixels[base + 1].to_int(),
1963
- pixels[base + 2].to_int(),
1964
- da,
1965
- ),
1966
- (
1967
- costume.pixels[sample_base].to_int(),
1968
- costume.pixels[sample_base + 1].to_int(),
1969
- costume.pixels[sample_base + 2].to_int(),
1970
- sa,
1971
- ),
1972
- )
1973
- pixels[base] = blended.0.to_byte()
1974
- pixels[base + 1] = blended.1.to_byte()
1975
- pixels[base + 2] = blended.2.to_byte()
1976
- pixels[base + 3] = blended.3.to_byte()
1977
- }
1978
- }
1979
- }
1980
- }
1981
- tex_x += tex_x_step_px
1982
- tex_y += tex_y_step_px
1983
- base += 4
1984
- }
1985
- }
1986
- return
1987
- }
1988
-
1989
- for py in start_py..<=end_py {
1990
- let py_double = Double::from_int(py)
1991
- let mut tex_x = tex_x_base +
1992
- tex_x_step_py * py_double +
1993
- tex_x_step_px * start_px_double
1994
- let mut tex_y = tex_y_base +
1995
- tex_y_step_py * py_double +
1996
- tex_y_step_px * start_px_double
1997
- let mut base = (py * width + start_px) * 4
1998
- for _ in start_px..<=end_px {
1999
- if has_point_effects {
2000
- let mut u = tex_x / costume_width_double
2001
- let mut v = tex_y / costume_height_double
2002
- let transformed = render_apply_point_effects(
2003
- u,
2004
- v,
2005
- costume.width,
2006
- costume.height,
2007
- uniforms,
2008
- )
2009
- u = transformed.0
2010
- v = transformed.1
2011
- match render_sample_costume_rgba_uv(costume, u, v) {
2012
- Some(raw) => {
2013
- let src = if has_color_effects {
2014
- render_apply_color_effects(raw, uniforms, true)
2015
- } else {
2016
- raw
2017
- }
2018
- if dst_opaque {
2019
- render_blend_pixel_over_opaque_base(pixels, base, src)
2020
- } else {
2021
- render_blend_pixel_base(pixels, base, src)
2022
- }
2023
- }
2024
- None => ()
2025
- }
2026
- } else if tex_x >= 0.0 &&
2027
- tex_y >= 0.0 &&
2028
- tex_x < costume_width_double &&
2029
- tex_y < costume_height_double {
2030
- let tx = tex_x.floor().to_int()
2031
- let ty = tex_y.floor().to_int()
2032
- let sample_base = (ty * costume.width + tx) * 4
2033
- let raw = (
2034
- costume.pixels[sample_base].to_int(),
2035
- costume.pixels[sample_base + 1].to_int(),
2036
- costume.pixels[sample_base + 2].to_int(),
2037
- costume.pixels[sample_base + 3].to_int(),
2038
- )
2039
- let src = if has_color_effects {
2040
- render_apply_color_effects(raw, uniforms, true)
2041
- } else {
2042
- raw
2043
- }
2044
- if dst_opaque {
2045
- render_blend_pixel_over_opaque_base(pixels, base, src)
2046
- } else {
2047
- render_blend_pixel_base(pixels, base, src)
2048
- }
2049
- }
2050
- tex_x += tex_x_step_px
2051
- tex_y += tex_y_step_px
2052
- base += 4
2053
- }
2054
- }
2055
- }
2056
-
2057
- ///|
2058
- fn render_stage_costume_index_for_cache(stage : TargetState) -> Int {
2059
- if stage.costumes.is_empty() {
2060
- -1
2061
- } else {
2062
- render_wrap_index(stage.current_costume, stage.costumes.length())
2063
- }
2064
- }
2065
-
2066
- ///|
2067
- fn render_backdrop_cache_matches(
2068
- vm : Vm,
2069
- width : Int,
2070
- height : Int,
2071
- stage_index : Int,
2072
- stage_costume_index : Int,
2073
- stage : TargetState?,
2074
- ) -> Bool {
2075
- if !vm.backdrop_cache_valid ||
2076
- vm.backdrop_cache_width != width ||
2077
- vm.backdrop_cache_height != height ||
2078
- vm.backdrop_cache_stage_index != stage_index ||
2079
- vm.backdrop_cache_stage_costume_index != stage_costume_index {
2080
- return false
2081
- }
2082
- match stage {
2083
- Some(stage) =>
2084
- vm.backdrop_cache_effect_color == stage.looks_effect_color &&
2085
- vm.backdrop_cache_effect_fisheye == stage.looks_effect_fisheye &&
2086
- vm.backdrop_cache_effect_whirl == stage.looks_effect_whirl &&
2087
- vm.backdrop_cache_effect_pixelate == stage.looks_effect_pixelate &&
2088
- vm.backdrop_cache_effect_mosaic == stage.looks_effect_mosaic &&
2089
- vm.backdrop_cache_effect_brightness == stage.looks_effect_brightness &&
2090
- vm.backdrop_cache_effect_ghost == stage.looks_effect_ghost
2091
- None =>
2092
- vm.backdrop_cache_effect_color == 0.0 &&
2093
- vm.backdrop_cache_effect_fisheye == 0.0 &&
2094
- vm.backdrop_cache_effect_whirl == 0.0 &&
2095
- vm.backdrop_cache_effect_pixelate == 0.0 &&
2096
- vm.backdrop_cache_effect_mosaic == 0.0 &&
2097
- vm.backdrop_cache_effect_brightness == 0.0 &&
2098
- vm.backdrop_cache_effect_ghost == 0.0
2099
- }
2100
- }
2101
-
2102
- ///|
2103
- fn render_update_backdrop_cache(
2104
- vm : Vm,
2105
- width : Int,
2106
- height : Int,
2107
- stage_index : Int,
2108
- stage_costume_index : Int,
2109
- stage : TargetState?,
2110
- stage_costume : CostumeImage?,
2111
- ) -> Unit {
2112
- vm.backdrop_cache_pixels = match stage_costume {
2113
- Some(costume) => render_build_opaque_stage_pixels(costume, width, height)
2114
- None => render_build_opaque_white_pixels(width, height)
2115
- }
2116
- vm.backdrop_cache_valid = true
2117
- vm.backdrop_cache_width = width
2118
- vm.backdrop_cache_height = height
2119
- vm.backdrop_cache_stage_index = stage_index
2120
- vm.backdrop_cache_stage_costume_index = stage_costume_index
2121
- match stage {
2122
- Some(stage) => {
2123
- vm.backdrop_cache_effect_color = stage.looks_effect_color
2124
- vm.backdrop_cache_effect_fisheye = stage.looks_effect_fisheye
2125
- vm.backdrop_cache_effect_whirl = stage.looks_effect_whirl
2126
- vm.backdrop_cache_effect_pixelate = stage.looks_effect_pixelate
2127
- vm.backdrop_cache_effect_mosaic = stage.looks_effect_mosaic
2128
- vm.backdrop_cache_effect_brightness = stage.looks_effect_brightness
2129
- vm.backdrop_cache_effect_ghost = stage.looks_effect_ghost
2130
- }
2131
- None => {
2132
- vm.backdrop_cache_effect_color = 0.0
2133
- vm.backdrop_cache_effect_fisheye = 0.0
2134
- vm.backdrop_cache_effect_whirl = 0.0
2135
- vm.backdrop_cache_effect_pixelate = 0.0
2136
- vm.backdrop_cache_effect_mosaic = 0.0
2137
- vm.backdrop_cache_effect_brightness = 0.0
2138
- vm.backdrop_cache_effect_ghost = 0.0
2139
- }
2140
- }
2141
- }
2142
-
2143
- ///|
2144
- fn render_ensure_opaque_stage_backdrop_cache(
2145
- vm : Vm,
2146
- width : Int,
2147
- height : Int,
2148
- stage : TargetState?,
2149
- stage_costume : CostumeImage?,
2150
- ) -> Unit {
2151
- let stage_index = match stage {
2152
- Some(_) => vm.stage_index
2153
- None => -1
2154
- }
2155
- let stage_costume_index = match stage {
2156
- Some(stage) => render_stage_costume_index_for_cache(stage)
2157
- None => -1
2158
- }
2159
- if !render_backdrop_cache_matches(
2160
- vm, width, height, stage_index, stage_costume_index, stage,
2161
- ) {
2162
- render_update_backdrop_cache(
2163
- vm, width, height, stage_index, stage_costume_index, stage, stage_costume,
2164
- )
2165
- }
2166
- }
2167
-
2168
- ///|
2169
- fn render_scene_to_frame(vm : Vm) -> RenderFrame {
2170
- let width = vm.pen_width
2171
- let height = vm.pen_height
2172
- let pixels = []
2173
- if width <= 0 || height <= 0 {
2174
- vm.render_cache_valid = false
2175
- return RenderFrame::{ width, height, pixels }
2176
- }
2177
- if vm.render_cache_valid &&
2178
- vm.render_cache_revision == vm.render_revision &&
2179
- vm.pen_vectors.is_empty() &&
2180
- vm.render_cache_pixels.length() == width * height * 4 {
2181
- return RenderFrame::{ width, height, pixels: vm.render_cache_pixels }
2182
- }
2183
- render_flush_pen_vectors(vm)
2184
- let visible_sprite_indices = render_collect_visible_sprite_indices(vm, -1)
2185
- let stage = if vm.stage_index >= 0 && vm.stage_index < vm.targets.length() {
2186
- Some(vm.targets[vm.stage_index])
2187
- } else {
2188
- None
2189
- }
2190
- let stage_uniforms = match stage {
2191
- Some(stage) => render_effect_uniforms(stage)
2192
- None => render_no_effect_uniforms()
2193
- }
2194
- let stage_has_point_effects = stage_uniforms.enable_mosaic ||
2195
- stage_uniforms.enable_pixelate ||
2196
- stage_uniforms.enable_whirl ||
2197
- stage_uniforms.enable_fisheye
2198
- let stage_has_color_effects = stage_uniforms.enable_color ||
2199
- stage_uniforms.enable_brightness ||
2200
- stage_uniforms.enable_ghost
2201
- let stage_costume = match stage {
2202
- Some(stage) => render_current_costume(stage)
2203
- None => None
2204
- }
2205
- if visible_sprite_indices.is_empty() &&
2206
- !stage_has_point_effects &&
2207
- !stage_has_color_effects {
2208
- render_ensure_opaque_stage_backdrop_cache(
2209
- vm, width, height, stage, stage_costume,
2210
- )
2211
- let pixels = if vm.pen_bounds_valid {
2212
- let pixels = vm.backdrop_cache_pixels.copy()
2213
- render_blend_pen_over_opaque_pixels(vm, pixels)
2214
- pixels
2215
- } else {
2216
- vm.backdrop_cache_pixels
2217
- }
2218
- vm.render_cache_pixels = pixels
2219
- vm.render_cache_valid = true
2220
- vm.render_cache_revision = vm.render_revision
2221
- return RenderFrame::{ width, height, pixels }
2222
- }
2223
-
2224
- let stage_is_plain_white = match stage_costume {
2225
- None => true
2226
- Some(costume) =>
2227
- costume.width <= 0 ||
2228
- costume.height <= 0 ||
2229
- costume.pixels.length() < costume.width * costume.height * 4
2230
- }
2231
- let pixels = if !stage_has_point_effects &&
2232
- !stage_has_color_effects &&
2233
- stage_is_plain_white {
2234
- let pixels = render_build_opaque_white_pixels(width, height)
2235
- if vm.pen_bounds_valid {
2236
- render_blend_pen_over_opaque_pixels(vm, pixels)
2237
- }
2238
- pixels
2239
- } else {
2240
- let pixels = Array::new(capacity=width * height * 4)
2241
- let width_double = Double::from_int(width)
2242
- let height_double = Double::from_int(height)
2243
- let stage_x_step = 480.0 / width_double
2244
- let stage_y_step = 360.0 / height_double
2245
- let stage_x_start = stage_x_step * 0.5 - 240.0
2246
- let mut stage_y = 180.0 - stage_y_step * 0.5
2247
- let pen_len = vm.pen_pixels.length()
2248
- for py in 0..<height {
2249
- let mut stage_x = stage_x_start
2250
- let mut pen_base = py * width * 4
2251
- for _ in 0..<width {
2252
- let mut color = render_sample_stage_backdrop_prepared(
2253
- stage_costume, stage_uniforms, stage_has_point_effects, stage_has_color_effects,
2254
- stage_x, stage_y,
2255
- )
2256
- if pen_base + 3 < pen_len {
2257
- let pen_alpha = vm.pen_pixels[pen_base + 3].to_int()
2258
- if pen_alpha > 0 {
2259
- if pen_alpha >= 255 {
2260
- color = (
2261
- vm.pen_pixels[pen_base].to_int(),
2262
- vm.pen_pixels[pen_base + 1].to_int(),
2263
- vm.pen_pixels[pen_base + 2].to_int(),
2264
- 255,
2265
- )
2266
- } else {
2267
- color = render_blend_rgba(
2268
- color,
2269
- (
2270
- vm.pen_pixels[pen_base].to_int(),
2271
- vm.pen_pixels[pen_base + 1].to_int(),
2272
- vm.pen_pixels[pen_base + 2].to_int(),
2273
- pen_alpha,
2274
- ),
2275
- )
2276
- }
2277
- }
2278
- }
2279
- pixels.push(color.0.to_byte())
2280
- pixels.push(color.1.to_byte())
2281
- pixels.push(color.2.to_byte())
2282
- pixels.push(color.3.to_byte())
2283
- stage_x += stage_x_step
2284
- pen_base += 4
2285
- }
2286
- stage_y -= stage_y_step
2287
- }
2288
- pixels
2289
- }
2290
- for sprite_index in visible_sprite_indices {
2291
- render_blend_sprite_over_pixels(
2292
- vm,
2293
- sprite_index,
2294
- pixels,
2295
- width,
2296
- height,
2297
- !stage_has_point_effects && !stage_has_color_effects,
2298
- )
2299
- }
2300
- vm.render_cache_pixels = pixels
2301
- vm.render_cache_valid = true
2302
- vm.render_cache_revision = vm.render_revision
2303
- RenderFrame::{ width, height, pixels }
2304
- }
2305
-
2306
- ///|
2307
- fn render_mask_matches(
2308
- sampled : (Int, Int, Int, Int),
2309
- mask_rgb : (Int, Int, Int),
2310
- ) -> Bool {
2311
- let (sr, sg, sb, sa) = sampled
2312
- let (mr, mg, mb) = mask_rgb
2313
- sa > 0 && sr / 4 == mr / 4 && sg / 4 == mg / 4 && sb / 4 == mb / 4
2314
- }
2315
-
2316
- ///|
2317
- fn render_color_matches(
2318
- sampled_rgb : (Int, Int, Int),
2319
- target_rgb : (Int, Int, Int),
2320
- ) -> Bool {
2321
- let (sr, sg, sb) = sampled_rgb
2322
- let (tr, tg, tb) = target_rgb
2323
- sr / 8 == tr / 8 && sg / 8 == tg / 8 && sb / 16 == tb / 16
2324
- }
2325
-
2326
- ///|
2327
- fn render_hex_digit_value(ch : Char) -> Int? {
2328
- if ch >= '0' && ch <= '9' {
2329
- Some(ch.to_int() - '0'.to_int())
2330
- } else if ch >= 'a' && ch <= 'f' {
2331
- Some(ch.to_int() - 'a'.to_int() + 10)
2332
- } else if ch >= 'A' && ch <= 'F' {
2333
- Some(ch.to_int() - 'A'.to_int() + 10)
2334
- } else {
2335
- None
2336
- }
2337
- }
2338
-
2339
- ///|
2340
- fn render_parse_hex_color(raw : String) -> (Int, Int, Int)? {
2341
- let chars = raw.trim().to_array()
2342
- if chars.length() < 7 || chars[0] != '#' {
2343
- return None
2344
- }
2345
- let parse_byte = fn(at : Int) {
2346
- if at + 1 >= chars.length() {
2347
- return None
2348
- }
2349
- match
2350
- (render_hex_digit_value(chars[at]), render_hex_digit_value(chars[at + 1])) {
2351
- (Some(hi), Some(lo)) => Some(hi * 16 + lo)
2352
- _ => None
2353
- }
2354
- }
2355
- match (parse_byte(1), parse_byte(3), parse_byte(5)) {
2356
- (Some(r), Some(g), Some(b)) => Some((r, g, b))
2357
- _ => None
2358
- }
2359
- }
2360
-
2361
- ///|
2362
- fn render_json_to_rgb(value : Json) -> (Int, Int, Int) {
2363
- match value {
2364
- String(raw) =>
2365
- match render_parse_hex_color(raw) {
2366
- Some(rgb) => rgb
2367
- None => (0, 0, 0)
2368
- }
2369
- Number(n, ..) => {
2370
- let raw = n.floor().to_int()
2371
- let clamped = if raw < 0 { 0 } else { raw }
2372
- let r = (clamped / 65536).mod(256)
2373
- let g = (clamped / 256).mod(256)
2374
- let b = clamped.mod(256)
2375
- (
2376
- render_clamp_int(r, 0, 255),
2377
- render_clamp_int(g, 0, 255),
2378
- render_clamp_int(b, 0, 255),
2379
- )
2380
- }
2381
- _ => (0, 0, 0)
2382
- }
2383
- }
2384
-
2385
- ///|
2386
- fn target_is_touching_color(
2387
- vm : Vm,
2388
- target_index : Int,
2389
- color_rgb : (Int, Int, Int),
2390
- ) -> Bool {
2391
- if target_index < 0 || target_index >= vm.targets.length() {
2392
- return false
2393
- }
2394
- let target = vm.targets[target_index]
2395
- if target.deleted || target.is_stage {
2396
- return false
2397
- }
2398
- render_flush_pen_vectors(vm)
2399
- let width = vm.pen_width
2400
- let height = vm.pen_height
2401
- if width <= 0 || height <= 0 {
2402
- return false
2403
- }
2404
- let visible_sprite_indices = render_collect_visible_sprite_indices(
2405
- vm, target_index,
2406
- )
2407
- let width_double = Double::from_int(width)
2408
- let height_double = Double::from_int(height)
2409
- let stage_x_step = 480.0 / width_double
2410
- let stage_y_step = 360.0 / height_double
2411
- let stage_x_start = stage_x_step * 0.5 - 240.0
2412
- let mut stage_y = 180.0 - stage_y_step * 0.5
2413
- let pen_len = vm.pen_pixels.length()
2414
- for py in 0..<height {
2415
- let mut stage_x = stage_x_start
2416
- let mut pen_base = py * width * 4
2417
- for _ in 0..<width {
2418
- match
2419
- render_sample_sprite_at(
2420
- vm, target_index, stage_x, stage_y, false, false,
2421
- ) {
2422
- Some(mask_color) =>
2423
- if mask_color.3 > 0 {
2424
- let mut sampled = render_sample_stage_backdrop_at(
2425
- vm, stage_x, stage_y,
2426
- )
2427
- if pen_base + 3 < pen_len {
2428
- let pen_alpha = vm.pen_pixels[pen_base + 3].to_int()
2429
- if pen_alpha > 0 {
2430
- sampled = render_blend_rgba(
2431
- sampled,
2432
- (
2433
- vm.pen_pixels[pen_base].to_int(),
2434
- vm.pen_pixels[pen_base + 1].to_int(),
2435
- vm.pen_pixels[pen_base + 2].to_int(),
2436
- pen_alpha,
2437
- ),
2438
- )
2439
- }
2440
- }
2441
- for sprite_index in visible_sprite_indices {
2442
- match
2443
- render_sample_sprite_at(
2444
- vm, sprite_index, stage_x, stage_y, true, true,
2445
- ) {
2446
- Some(sprite_color) =>
2447
- sampled = render_blend_rgba(sampled, sprite_color)
2448
- None => ()
2449
- }
2450
- }
2451
- if render_color_matches(
2452
- (sampled.0, sampled.1, sampled.2),
2453
- color_rgb,
2454
- ) {
2455
- return true
2456
- }
2457
- }
2458
- None => ()
2459
- }
2460
- stage_x += stage_x_step
2461
- pen_base += 4
2462
- }
2463
- stage_y -= stage_y_step
2464
- }
2465
- false
2466
- }
2467
-
2468
- ///|
2469
- fn target_color_is_touching_color(
2470
- vm : Vm,
2471
- target_index : Int,
2472
- target_rgb : (Int, Int, Int),
2473
- mask_rgb : (Int, Int, Int),
2474
- ) -> Bool {
2475
- if target_index < 0 || target_index >= vm.targets.length() {
2476
- return false
2477
- }
2478
- let target = vm.targets[target_index]
2479
- if target.deleted || target.is_stage {
2480
- return false
2481
- }
2482
- render_flush_pen_vectors(vm)
2483
- let width = vm.pen_width
2484
- let height = vm.pen_height
2485
- if width <= 0 || height <= 0 {
2486
- return false
2487
- }
2488
- let visible_sprite_indices = render_collect_visible_sprite_indices(
2489
- vm, target_index,
2490
- )
2491
- let width_double = Double::from_int(width)
2492
- let height_double = Double::from_int(height)
2493
- let stage_x_step = 480.0 / width_double
2494
- let stage_y_step = 360.0 / height_double
2495
- let stage_x_start = stage_x_step * 0.5 - 240.0
2496
- let mut stage_y = 180.0 - stage_y_step * 0.5
2497
- let pen_len = vm.pen_pixels.length()
2498
- for py in 0..<height {
2499
- let mut stage_x = stage_x_start
2500
- let mut pen_base = py * width * 4
2501
- for _ in 0..<width {
2502
- match
2503
- render_sample_sprite_at(
2504
- vm, target_index, stage_x, stage_y, false, false,
2505
- ) {
2506
- Some(mask_color) =>
2507
- if render_mask_matches(mask_color, mask_rgb) {
2508
- let mut sampled = render_sample_stage_backdrop_at(
2509
- vm, stage_x, stage_y,
2510
- )
2511
- if pen_base + 3 < pen_len {
2512
- let pen_alpha = vm.pen_pixels[pen_base + 3].to_int()
2513
- if pen_alpha > 0 {
2514
- sampled = render_blend_rgba(
2515
- sampled,
2516
- (
2517
- vm.pen_pixels[pen_base].to_int(),
2518
- vm.pen_pixels[pen_base + 1].to_int(),
2519
- vm.pen_pixels[pen_base + 2].to_int(),
2520
- pen_alpha,
2521
- ),
2522
- )
2523
- }
2524
- }
2525
- for sprite_index in visible_sprite_indices {
2526
- match
2527
- render_sample_sprite_at(
2528
- vm, sprite_index, stage_x, stage_y, true, true,
2529
- ) {
2530
- Some(sprite_color) =>
2531
- sampled = render_blend_rgba(sampled, sprite_color)
2532
- None => ()
2533
- }
2534
- }
2535
- if render_color_matches(
2536
- (sampled.0, sampled.1, sampled.2),
2537
- target_rgb,
2538
- ) {
2539
- return true
2540
- }
2541
- }
2542
- None => ()
2543
- }
2544
- stage_x += stage_x_step
2545
- pen_base += 4
2546
- }
2547
- stage_y -= stage_y_step
2548
- }
2549
- false
2550
- }