ruby2d 0.12.0 → 1.0.0

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 (619) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.md +7 -0
  3. data/README.md +141 -0
  4. data/USAGE.md +2435 -0
  5. data/assets/Rakefile +637 -0
  6. data/assets/build_support/CMakeLists.txt +94 -0
  7. data/assets/build_support/mruby/build_config.rb +34 -0
  8. data/assets/build_support/mruby/build_config_wasm.rb +30 -0
  9. data/assets/build_support/sdl_test.c +174 -0
  10. data/assets/deps.yaml +16 -0
  11. data/assets/platform/include/SDL3/SDL.h +91 -0
  12. data/assets/platform/include/SDL3/SDL_assert.h +705 -0
  13. data/assets/platform/include/SDL3/SDL_asyncio.h +550 -0
  14. data/assets/platform/include/SDL3/SDL_atomic.h +692 -0
  15. data/assets/platform/include/SDL3/SDL_audio.h +2370 -0
  16. data/assets/platform/include/SDL3/SDL_begin_code.h +553 -0
  17. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_bits.h +47 -27
  18. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_blendmode.h +59 -55
  19. data/assets/platform/include/SDL3/SDL_camera.h +535 -0
  20. data/assets/platform/include/SDL3/SDL_clipboard.h +331 -0
  21. data/assets/{include/SDL2/close_code.h → platform/include/SDL3/SDL_close_code.h} +9 -8
  22. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_copying.h +3 -1
  23. data/assets/platform/include/SDL3/SDL_cpuinfo.h +374 -0
  24. data/assets/platform/include/SDL3/SDL_dialog.h +343 -0
  25. data/assets/platform/include/SDL3/SDL_dlopennote.h +234 -0
  26. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_egl.h +11 -8
  27. data/assets/platform/include/SDL3/SDL_endian.h +645 -0
  28. data/assets/platform/include/SDL3/SDL_error.h +226 -0
  29. data/assets/platform/include/SDL3/SDL_events.h +1645 -0
  30. data/assets/platform/include/SDL3/SDL_filesystem.h +537 -0
  31. data/assets/platform/include/SDL3/SDL_gamepad.h +1656 -0
  32. data/assets/platform/include/SDL3/SDL_gpu.h +4608 -0
  33. data/assets/platform/include/SDL3/SDL_guid.h +106 -0
  34. data/assets/platform/include/SDL3/SDL_haptic.h +1461 -0
  35. data/assets/platform/include/SDL3/SDL_hidapi.h +571 -0
  36. data/assets/platform/include/SDL3/SDL_hints.h +5001 -0
  37. data/assets/platform/include/SDL3/SDL_init.h +507 -0
  38. data/assets/platform/include/SDL3/SDL_intrin.h +410 -0
  39. data/assets/platform/include/SDL3/SDL_iostream.h +1379 -0
  40. data/assets/platform/include/SDL3/SDL_joystick.h +1385 -0
  41. data/assets/platform/include/SDL3/SDL_keyboard.h +608 -0
  42. data/assets/platform/include/SDL3/SDL_keycode.h +347 -0
  43. data/assets/platform/include/SDL3/SDL_loadso.h +145 -0
  44. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_locale.h +40 -24
  45. data/assets/platform/include/SDL3/SDL_log.h +541 -0
  46. data/assets/platform/include/SDL3/SDL_main.h +704 -0
  47. data/assets/platform/include/SDL3/SDL_main_impl.h +151 -0
  48. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_messagebox.h +85 -48
  49. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_metal.h +29 -29
  50. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_misc.h +13 -12
  51. data/assets/platform/include/SDL3/SDL_mouse.h +813 -0
  52. data/assets/platform/include/SDL3/SDL_mutex.h +1117 -0
  53. data/assets/platform/include/SDL3/SDL_oldnames.h +1339 -0
  54. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengl.h +1247 -278
  55. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengl_glext.h +5 -1
  56. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengles.h +6 -7
  57. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengles2.h +10 -11
  58. data/assets/platform/include/SDL3/SDL_pen.h +198 -0
  59. data/assets/platform/include/SDL3/SDL_pixels.h +1441 -0
  60. data/assets/{include/SDL2/SDL_test_log.h → platform/include/SDL3/SDL_platform.h} +25 -26
  61. data/assets/platform/include/SDL3/SDL_platform_defines.h +497 -0
  62. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_power.h +40 -20
  63. data/assets/platform/include/SDL3/SDL_process.h +441 -0
  64. data/assets/platform/include/SDL3/SDL_properties.h +572 -0
  65. data/assets/platform/include/SDL3/SDL_rect.h +528 -0
  66. data/assets/platform/include/SDL3/SDL_render.h +3028 -0
  67. data/assets/platform/include/SDL3/SDL_revision.h +59 -0
  68. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_scancode.h +59 -68
  69. data/assets/platform/include/SDL3/SDL_sensor.h +321 -0
  70. data/assets/platform/include/SDL3/SDL_stdinc.h +6186 -0
  71. data/assets/platform/include/SDL3/SDL_storage.h +686 -0
  72. data/assets/platform/include/SDL3/SDL_surface.h +1769 -0
  73. data/assets/platform/include/SDL3/SDL_system.h +840 -0
  74. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test.h +16 -22
  75. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_assert.h +27 -34
  76. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_common.h +109 -52
  77. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_compare.h +22 -14
  78. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_crc32.h +27 -30
  79. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_font.h +50 -49
  80. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_fuzzer.h +64 -79
  81. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_harness.h +47 -30
  82. data/assets/platform/include/SDL3/SDL_test_log.h +83 -0
  83. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_md5.h +21 -28
  84. data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_memory.h +19 -16
  85. data/assets/platform/include/SDL3/SDL_thread.h +602 -0
  86. data/assets/platform/include/SDL3/SDL_time.h +249 -0
  87. data/assets/platform/include/SDL3/SDL_timer.h +454 -0
  88. data/assets/platform/include/SDL3/SDL_touch.h +184 -0
  89. data/assets/platform/include/SDL3/SDL_tray.h +544 -0
  90. data/assets/platform/include/SDL3/SDL_version.h +198 -0
  91. data/assets/platform/include/SDL3/SDL_video.h +3497 -0
  92. data/assets/platform/include/SDL3/SDL_vulkan.h +287 -0
  93. data/assets/platform/include/SDL3_image/SDL_image.h +3328 -0
  94. data/assets/platform/include/SDL3_mixer/SDL_mixer.h +3369 -0
  95. data/assets/platform/include/SDL3_ttf/SDL_textengine.h +181 -0
  96. data/assets/platform/include/SDL3_ttf/SDL_ttf.h +2833 -0
  97. data/assets/{include → platform/include}/mrbconf.h +42 -31
  98. data/assets/{include → platform/include}/mruby/array.h +3 -4
  99. data/assets/{include → platform/include}/mruby/boxing_nan.h +10 -0
  100. data/assets/platform/include/mruby/boxing_no.h +59 -0
  101. data/assets/{include → platform/include}/mruby/boxing_word.h +72 -26
  102. data/assets/platform/include/mruby/class.h +157 -0
  103. data/assets/{include → platform/include}/mruby/common.h +30 -1
  104. data/assets/{include → platform/include}/mruby/compile.h +52 -74
  105. data/assets/{include → platform/include}/mruby/data.h +2 -2
  106. data/assets/{include → platform/include}/mruby/debug.h +12 -5
  107. data/assets/{include → platform/include}/mruby/dump.h +7 -9
  108. data/assets/{include → platform/include}/mruby/endian.h +1 -1
  109. data/assets/platform/include/mruby/error.h +173 -0
  110. data/assets/platform/include/mruby/gc.h +79 -0
  111. data/assets/{include → platform/include}/mruby/hash.h +1 -7
  112. data/assets/platform/include/mruby/internal.h +282 -0
  113. data/assets/platform/include/mruby/irep.h +143 -0
  114. data/assets/{include → platform/include}/mruby/istruct.h +1 -1
  115. data/assets/platform/include/mruby/khash.h +455 -0
  116. data/assets/platform/include/mruby/mempool.h +19 -0
  117. data/assets/{include → platform/include}/mruby/numeric.h +7 -10
  118. data/assets/platform/include/mruby/object.h +51 -0
  119. data/assets/{include → platform/include}/mruby/opcode.h +5 -5
  120. data/assets/{include → platform/include}/mruby/ops.h +19 -6
  121. data/assets/platform/include/mruby/presym/id.h +1447 -0
  122. data/assets/{include → platform/include}/mruby/presym/scanning.h +10 -7
  123. data/assets/platform/include/mruby/presym/table.h +2893 -0
  124. data/assets/platform/include/mruby/presym.h +65 -0
  125. data/assets/platform/include/mruby/proc.h +184 -0
  126. data/assets/{include → platform/include}/mruby/range.h +0 -2
  127. data/assets/{include → platform/include}/mruby/string.h +33 -53
  128. data/assets/platform/include/mruby/throw.h +81 -0
  129. data/assets/{include → platform/include}/mruby/value.h +64 -29
  130. data/assets/{include → platform/include}/mruby/variable.h +6 -23
  131. data/assets/{include → platform/include}/mruby/version.h +7 -7
  132. data/assets/{include → platform/include}/mruby.h +238 -116
  133. data/assets/platform/macos-arm64/bin/mrbc +0 -0
  134. data/assets/platform/macos-arm64/lib/libSDL3.a +0 -0
  135. data/assets/platform/macos-arm64/lib/libSDL3_image.a +0 -0
  136. data/assets/platform/macos-arm64/lib/libSDL3_mixer.a +0 -0
  137. data/assets/platform/macos-arm64/lib/libSDL3_ttf.a +0 -0
  138. data/assets/platform/macos-arm64/lib/libmruby.a +0 -0
  139. data/assets/platform/wasm/lib/libFLAC.a +0 -0
  140. data/assets/platform/wasm/lib/libSDL3.a +0 -0
  141. data/assets/platform/wasm/lib/libSDL3_image.a +0 -0
  142. data/assets/platform/wasm/lib/libSDL3_mixer.a +0 -0
  143. data/assets/platform/wasm/lib/libSDL3_ttf.a +0 -0
  144. data/assets/platform/wasm/lib/libfreetype.a +0 -0
  145. data/assets/platform/wasm/lib/libharfbuzz.a +0 -0
  146. data/assets/platform/wasm/lib/libmruby.a +0 -0
  147. data/assets/platform/wasm/lib/libogg.a +0 -0
  148. data/assets/platform/wasm/lib/libplutosvg.a +0 -0
  149. data/assets/platform/wasm/lib/libplutovg.a +0 -0
  150. data/assets/platform/wasm/lib/libpng.a +0 -0
  151. data/assets/platform/wasm/lib/libpng16.a +0 -0
  152. data/assets/platform/wasm/lib/libvorbis.a +0 -0
  153. data/assets/platform/wasm/lib/libvorbisfile.a +0 -0
  154. data/assets/platform/wasm/lib/libzlibstatic.a +0 -0
  155. data/assets/platform/windows-arm64-mingw-ucrt/bin/mrbc.exe +0 -0
  156. data/assets/platform/windows-arm64-mingw-ucrt/lib/libSDL3.a +0 -0
  157. data/assets/platform/windows-arm64-mingw-ucrt/lib/libSDL3_image.a +0 -0
  158. data/assets/platform/windows-arm64-mingw-ucrt/lib/libSDL3_mixer.a +0 -0
  159. data/assets/platform/windows-arm64-mingw-ucrt/lib/libSDL3_ttf.a +0 -0
  160. data/assets/platform/windows-arm64-mingw-ucrt/lib/libmruby.a +0 -0
  161. data/assets/platform/windows-x86_64-mingw-ucrt/bin/mrbc.exe +0 -0
  162. data/assets/platform/windows-x86_64-mingw-ucrt/lib/libSDL3.a +0 -0
  163. data/assets/platform/windows-x86_64-mingw-ucrt/lib/libSDL3_image.a +0 -0
  164. data/assets/platform/windows-x86_64-mingw-ucrt/lib/libSDL3_mixer.a +0 -0
  165. data/assets/platform/windows-x86_64-mingw-ucrt/lib/libSDL3_ttf.a +0 -0
  166. data/assets/platform/windows-x86_64-mingw-ucrt/lib/libmruby.a +0 -0
  167. data/assets/resources/fonts/outfit/OFL.txt +93 -0
  168. data/assets/resources/fonts/outfit/outfit.ttf +0 -0
  169. data/assets/resources/fonts/roboto_mono/OFL.txt +91 -0
  170. data/assets/resources/fonts/roboto_mono/roboto_mono.ttf +0 -0
  171. data/assets/resources/icons/icon.icns +0 -0
  172. data/assets/resources/icons/icon.png +0 -0
  173. data/assets/resources/spritesheets/License.txt +28 -0
  174. data/assets/resources/spritesheets/spritesheet-backgrounds.png +0 -0
  175. data/assets/resources/spritesheets/spritesheet-backgrounds.xml +16 -0
  176. data/assets/resources/spritesheets/spritesheet-characters.png +0 -0
  177. data/assets/resources/spritesheets/spritesheet-characters.xml +47 -0
  178. data/assets/resources/spritesheets/spritesheet-enemies.png +0 -0
  179. data/assets/resources/spritesheets/spritesheet-enemies.xml +62 -0
  180. data/assets/resources/spritesheets/spritesheet-tiles.png +0 -0
  181. data/assets/resources/spritesheets/spritesheet-tiles.xml +316 -0
  182. data/assets/{wasm → resources/web}/template.html +1 -1
  183. data/assets/target.rb +131 -0
  184. data/bin/ruby2d +281 -82
  185. data/examples/astar.rb +269 -0
  186. data/examples/asteroids.rb +403 -0
  187. data/examples/bezier_editor.rb +96 -0
  188. data/examples/boids.rb +191 -0
  189. data/examples/bouncing_balls.rb +119 -0
  190. data/examples/breakout.rb +186 -0
  191. data/examples/constellations.rb +100 -0
  192. data/examples/dashboard.rb +133 -0
  193. data/examples/double_pendulum.rb +254 -0
  194. data/examples/falling_sand.rb +291 -0
  195. data/examples/fireworks.rb +110 -0
  196. data/examples/fluid_dynamics.rb +340 -0
  197. data/examples/fourier_epicycles.rb +142 -0
  198. data/examples/fractal_tree.rb +326 -0
  199. data/examples/game_of_life.rb +148 -0
  200. data/examples/gamepads.rb +333 -0
  201. data/examples/hill_driver.rb +613 -0
  202. data/examples/logo_animation.rb +251 -0
  203. data/examples/lorenz_attractor.rb +115 -0
  204. data/examples/mandelbrot.rb +207 -0
  205. data/examples/marching_squares.rb +200 -0
  206. data/examples/maze.rb +323 -0
  207. data/examples/nbody.rb +338 -0
  208. data/examples/outrun.rb +391 -0
  209. data/examples/pixel_paint.rb +313 -0
  210. data/examples/platformer.rb +335 -0
  211. data/examples/ray_casting_maze.rb +175 -0
  212. data/examples/shadow_casting.rb +151 -0
  213. data/examples/shape_recognizer.rb +908 -0
  214. data/examples/snake.rb +144 -0
  215. data/examples/sprite_sheets.rb +246 -0
  216. data/examples/swarm.rb +206 -0
  217. data/examples/tsp_annealing.rb +104 -0
  218. data/examples/ui_controls.rb +163 -0
  219. data/examples/wireframe_3d.rb +139 -0
  220. data/ext/ruby2d/audio.c +324 -0
  221. data/ext/ruby2d/canvas.c +1715 -494
  222. data/ext/ruby2d/ext.c +541 -0
  223. data/ext/ruby2d/extconf.rb +122 -145
  224. data/ext/ruby2d/font.c +566 -20
  225. data/ext/ruby2d/fps.c +92 -0
  226. data/ext/ruby2d/image.c +466 -41
  227. data/ext/ruby2d/ruby2d.c +374 -1890
  228. data/ext/ruby2d/ruby2d.h +840 -460
  229. data/ext/ruby2d/shapes.c +931 -101
  230. data/ext/ruby2d/text.c +586 -21
  231. data/ext/ruby2d/window.c +1693 -283
  232. data/lib/ruby2d/atlas_parser.rb +194 -0
  233. data/lib/ruby2d/audio.rb +73 -0
  234. data/lib/ruby2d/benchmark.rb +259 -0
  235. data/lib/ruby2d/bitmap_text.rb +155 -0
  236. data/lib/ruby2d/button.rb +402 -0
  237. data/lib/ruby2d/canvas.rb +703 -279
  238. data/lib/ruby2d/circle.rb +206 -29
  239. data/lib/ruby2d/cli/browser.rb +314 -0
  240. data/lib/ruby2d/cli/build.rb +471 -257
  241. data/lib/ruby2d/cli/colorize.rb +7 -5
  242. data/lib/ruby2d/cli/examples.rb +127 -0
  243. data/lib/ruby2d/cli/launch.rb +24 -39
  244. data/lib/ruby2d/cli/lib_files.rb +48 -0
  245. data/lib/ruby2d/cli/messages.rb +40 -0
  246. data/lib/ruby2d/cli/setup.rb +273 -0
  247. data/lib/ruby2d/cli/static_server.rb +160 -0
  248. data/lib/ruby2d/cli/usage.rb +255 -0
  249. data/lib/ruby2d/color.rb +191 -69
  250. data/lib/ruby2d/core.rb +37 -19
  251. data/lib/ruby2d/deps_help.rb +89 -0
  252. data/lib/ruby2d/dsl.rb +51 -12
  253. data/lib/ruby2d/ellipse.rb +228 -0
  254. data/lib/ruby2d/exceptions.rb +0 -2
  255. data/lib/ruby2d/font.rb +34 -74
  256. data/lib/ruby2d/gamepad.rb +323 -0
  257. data/lib/ruby2d/gem_paths.rb +19 -0
  258. data/lib/ruby2d/image.rb +155 -52
  259. data/lib/ruby2d/interactive.rb +103 -0
  260. data/lib/ruby2d/json_parser.rb +268 -0
  261. data/lib/ruby2d/line.rb +194 -85
  262. data/lib/ruby2d/mruby_compat.rb +12 -0
  263. data/lib/ruby2d/polygon.rb +297 -0
  264. data/lib/ruby2d/polyline.rb +368 -0
  265. data/lib/ruby2d/quad.rb +358 -94
  266. data/lib/ruby2d/rectangle.rb +77 -19
  267. data/lib/ruby2d/renderable.rb +411 -11
  268. data/lib/ruby2d/sprite.rb +471 -180
  269. data/lib/ruby2d/sprite_sheet.rb +64 -0
  270. data/lib/ruby2d/square.rb +29 -17
  271. data/lib/ruby2d/text.rb +196 -65
  272. data/lib/ruby2d/tileset.rb +139 -81
  273. data/lib/ruby2d/triangle.rb +324 -89
  274. data/lib/ruby2d/version.rb +1 -3
  275. data/lib/ruby2d/vertices.rb +62 -85
  276. data/lib/ruby2d/warnings.rb +29 -0
  277. data/lib/ruby2d/window/class_methods.rb +218 -0
  278. data/lib/ruby2d/window/gamepad_events.rb +141 -0
  279. data/lib/ruby2d/window/key_events.rb +74 -0
  280. data/lib/ruby2d/window/mouse_events.rb +171 -0
  281. data/lib/ruby2d/window/object_events.rb +168 -0
  282. data/lib/ruby2d/window.rb +632 -585
  283. data/lib/ruby2d.rb +14 -19
  284. metadata +275 -428
  285. data/assets/app.icns +0 -0
  286. data/assets/include/GLES2/gl2.h +0 -656
  287. data/assets/include/GLES2/gl2ext.h +0 -3949
  288. data/assets/include/GLES2/gl2ext_angle.h +0 -701
  289. data/assets/include/GLES2/gl2platform.h +0 -27
  290. data/assets/include/GLES3/gl3.h +0 -1192
  291. data/assets/include/GLES3/gl31.h +0 -1507
  292. data/assets/include/GLES3/gl32.h +0 -1808
  293. data/assets/include/GLES3/gl3platform.h +0 -27
  294. data/assets/include/KHR/khrplatform.h +0 -290
  295. data/assets/include/SDL2/SDL.h +0 -233
  296. data/assets/include/SDL2/SDL_assert.h +0 -326
  297. data/assets/include/SDL2/SDL_atomic.h +0 -415
  298. data/assets/include/SDL2/SDL_audio.h +0 -1500
  299. data/assets/include/SDL2/SDL_clipboard.h +0 -141
  300. data/assets/include/SDL2/SDL_config.h +0 -61
  301. data/assets/include/SDL2/SDL_config_android.h +0 -194
  302. data/assets/include/SDL2/SDL_config_emscripten.h +0 -218
  303. data/assets/include/SDL2/SDL_config_iphoneos.h +0 -217
  304. data/assets/include/SDL2/SDL_config_macosx.h +0 -277
  305. data/assets/include/SDL2/SDL_config_minimal.h +0 -95
  306. data/assets/include/SDL2/SDL_config_ngage.h +0 -89
  307. data/assets/include/SDL2/SDL_config_os2.h +0 -207
  308. data/assets/include/SDL2/SDL_config_pandora.h +0 -141
  309. data/assets/include/SDL2/SDL_config_windows.h +0 -331
  310. data/assets/include/SDL2/SDL_config_wingdk.h +0 -253
  311. data/assets/include/SDL2/SDL_config_winrt.h +0 -220
  312. data/assets/include/SDL2/SDL_config_xbox.h +0 -235
  313. data/assets/include/SDL2/SDL_cpuinfo.h +0 -594
  314. data/assets/include/SDL2/SDL_endian.h +0 -348
  315. data/assets/include/SDL2/SDL_error.h +0 -163
  316. data/assets/include/SDL2/SDL_events.h +0 -1166
  317. data/assets/include/SDL2/SDL_filesystem.h +0 -149
  318. data/assets/include/SDL2/SDL_gamecontroller.h +0 -1074
  319. data/assets/include/SDL2/SDL_gesture.h +0 -117
  320. data/assets/include/SDL2/SDL_guid.h +0 -100
  321. data/assets/include/SDL2/SDL_haptic.h +0 -1341
  322. data/assets/include/SDL2/SDL_hidapi.h +0 -451
  323. data/assets/include/SDL2/SDL_hints.h +0 -2569
  324. data/assets/include/SDL2/SDL_image.h +0 -2173
  325. data/assets/include/SDL2/SDL_joystick.h +0 -1066
  326. data/assets/include/SDL2/SDL_keyboard.h +0 -353
  327. data/assets/include/SDL2/SDL_keycode.h +0 -358
  328. data/assets/include/SDL2/SDL_loadso.h +0 -115
  329. data/assets/include/SDL2/SDL_log.h +0 -404
  330. data/assets/include/SDL2/SDL_main.h +0 -275
  331. data/assets/include/SDL2/SDL_mixer.h +0 -2784
  332. data/assets/include/SDL2/SDL_mouse.h +0 -465
  333. data/assets/include/SDL2/SDL_mutex.h +0 -471
  334. data/assets/include/SDL2/SDL_name.h +0 -33
  335. data/assets/include/SDL2/SDL_pixels.h +0 -644
  336. data/assets/include/SDL2/SDL_platform.h +0 -261
  337. data/assets/include/SDL2/SDL_quit.h +0 -58
  338. data/assets/include/SDL2/SDL_rect.h +0 -376
  339. data/assets/include/SDL2/SDL_render.h +0 -1919
  340. data/assets/include/SDL2/SDL_revision.h +0 -6
  341. data/assets/include/SDL2/SDL_rwops.h +0 -841
  342. data/assets/include/SDL2/SDL_sensor.h +0 -322
  343. data/assets/include/SDL2/SDL_shape.h +0 -155
  344. data/assets/include/SDL2/SDL_stdinc.h +0 -830
  345. data/assets/include/SDL2/SDL_surface.h +0 -997
  346. data/assets/include/SDL2/SDL_system.h +0 -623
  347. data/assets/include/SDL2/SDL_syswm.h +0 -386
  348. data/assets/include/SDL2/SDL_test_images.h +0 -78
  349. data/assets/include/SDL2/SDL_test_random.h +0 -115
  350. data/assets/include/SDL2/SDL_thread.h +0 -464
  351. data/assets/include/SDL2/SDL_timer.h +0 -222
  352. data/assets/include/SDL2/SDL_touch.h +0 -150
  353. data/assets/include/SDL2/SDL_ttf.h +0 -2316
  354. data/assets/include/SDL2/SDL_types.h +0 -29
  355. data/assets/include/SDL2/SDL_version.h +0 -204
  356. data/assets/include/SDL2/SDL_video.h +0 -2150
  357. data/assets/include/SDL2/SDL_vulkan.h +0 -215
  358. data/assets/include/SDL2/begin_code.h +0 -187
  359. data/assets/include/mruby/boxing_no.h +0 -58
  360. data/assets/include/mruby/class.h +0 -108
  361. data/assets/include/mruby/error.h +0 -147
  362. data/assets/include/mruby/gc.h +0 -94
  363. data/assets/include/mruby/irep.h +0 -147
  364. data/assets/include/mruby/khash.h +0 -284
  365. data/assets/include/mruby/object.h +0 -43
  366. data/assets/include/mruby/presym/disable.h +0 -70
  367. data/assets/include/mruby/presym/enable.h +0 -37
  368. data/assets/include/mruby/presym.h +0 -40
  369. data/assets/include/mruby/proc.h +0 -211
  370. data/assets/include/mruby/throw.h +0 -66
  371. data/assets/macos/universal/bin/mrbc +0 -0
  372. data/assets/macos/universal/lib/libFLAC.a +0 -0
  373. data/assets/macos/universal/lib/libSDL2.a +0 -0
  374. data/assets/macos/universal/lib/libSDL2_image.a +0 -0
  375. data/assets/macos/universal/lib/libSDL2_mixer.a +0 -0
  376. data/assets/macos/universal/lib/libSDL2_ttf.a +0 -0
  377. data/assets/macos/universal/lib/libavif.a +0 -0
  378. data/assets/macos/universal/lib/libbrotlicommon-static.a +0 -0
  379. data/assets/macos/universal/lib/libbrotlidec-static.a +0 -0
  380. data/assets/macos/universal/lib/libfreetype.a +0 -0
  381. data/assets/macos/universal/lib/libgraphite2.a +0 -0
  382. data/assets/macos/universal/lib/libharfbuzz.a +0 -0
  383. data/assets/macos/universal/lib/libhwy.a +0 -0
  384. data/assets/macos/universal/lib/libjpeg.a +0 -0
  385. data/assets/macos/universal/lib/libjxl.a +0 -0
  386. data/assets/macos/universal/lib/libmodplug.a +0 -0
  387. data/assets/macos/universal/lib/libmpg123.a +0 -0
  388. data/assets/macos/universal/lib/libmruby.a +0 -0
  389. data/assets/macos/universal/lib/libogg.a +0 -0
  390. data/assets/macos/universal/lib/libpng.a +0 -0
  391. data/assets/macos/universal/lib/libtiff.a +0 -0
  392. data/assets/macos/universal/lib/libvorbis.a +0 -0
  393. data/assets/macos/universal/lib/libvorbisfile.a +0 -0
  394. data/assets/macos/universal/lib/libwebp.a +0 -0
  395. data/assets/macos/universal/lib/libzstd.a +0 -0
  396. data/assets/test_media/README.md +0 -3
  397. data/assets/test_media/bitstream_vera/COPYRIGHT.txt +0 -124
  398. data/assets/test_media/bitstream_vera/vera.ttf +0 -0
  399. data/assets/test_media/boom.png +0 -0
  400. data/assets/test_media/coin.png +0 -0
  401. data/assets/test_media/colors.png +0 -0
  402. data/assets/test_media/controller.png +0 -0
  403. data/assets/test_media/dial.wav +0 -0
  404. data/assets/test_media/hero.png +0 -0
  405. data/assets/test_media/image.bmp +0 -0
  406. data/assets/test_media/image.jpg +0 -0
  407. data/assets/test_media/image.png +0 -0
  408. data/assets/test_media/music.flac +0 -0
  409. data/assets/test_media/music.mp3 +0 -0
  410. data/assets/test_media/music.ogg +0 -0
  411. data/assets/test_media/music.wav +0 -0
  412. data/assets/test_media/originals/boom.pxm +0 -0
  413. data/assets/test_media/originals/coin.pxm +0 -0
  414. data/assets/test_media/originals/controller.sketch +0 -0
  415. data/assets/test_media/originals/hero.pxm +0 -0
  416. data/assets/test_media/originals/image.pxm +0 -0
  417. data/assets/test_media/originals/music.caf +0 -0
  418. data/assets/test_media/originals/texture_atlas.pxm +0 -0
  419. data/assets/test_media/rondo_alla_turka.ogg +0 -0
  420. data/assets/test_media/sound.flac +0 -0
  421. data/assets/test_media/sound.mp3 +0 -0
  422. data/assets/test_media/sound.ogg +0 -0
  423. data/assets/test_media/sound.wav +0 -0
  424. data/assets/test_media/sprite_sheet.png +0 -0
  425. data/assets/test_media/texture_atlas.png +0 -0
  426. data/assets/wasm/libmruby.a +0 -0
  427. data/assets/windows/mingw-w64-ucrt-x86_64/bin/mrbc.exe +0 -0
  428. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libFLAC.a +0 -0
  429. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libLerc.a +0 -0
  430. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libSDL2.a +0 -0
  431. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libSDL2_image.a +0 -0
  432. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libSDL2_mixer.a +0 -0
  433. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libSDL2_ttf.a +0 -0
  434. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libbrotlicommon.a +0 -0
  435. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libbrotlidec.a +0 -0
  436. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libbz2.a +0 -0
  437. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libdeflate.a +0 -0
  438. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libfreetype.a +0 -0
  439. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libglew32.a +0 -0
  440. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libgraphite2.a +0 -0
  441. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libharfbuzz.a +0 -0
  442. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libhwy.a +0 -0
  443. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libjbig.a +0 -0
  444. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libjpeg.a +0 -0
  445. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libjxl.a +0 -0
  446. data/assets/windows/mingw-w64-ucrt-x86_64/lib/liblzma.a +0 -0
  447. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libmodplug.a +0 -0
  448. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libmpg123.a +0 -0
  449. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libmruby.a +0 -0
  450. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libogg.a +0 -0
  451. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libopus.a +0 -0
  452. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libopusfile.a +0 -0
  453. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libpng.a +0 -0
  454. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libsndfile.a +0 -0
  455. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libssp.a +0 -1
  456. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libstdc++.a +0 -0
  457. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libtiff.a +0 -0
  458. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libvorbis.a +0 -0
  459. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libvorbisfile.a +0 -0
  460. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libwebp.a +0 -0
  461. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libz.a +0 -0
  462. data/assets/windows/mingw-w64-ucrt-x86_64/lib/libzstd.a +0 -0
  463. data/assets/windows/mingw-w64-x86_64/bin/mrbc.exe +0 -0
  464. data/assets/windows/mingw-w64-x86_64/lib/libFLAC.a +0 -0
  465. data/assets/windows/mingw-w64-x86_64/lib/libLerc.a +0 -0
  466. data/assets/windows/mingw-w64-x86_64/lib/libSDL2.a +0 -0
  467. data/assets/windows/mingw-w64-x86_64/lib/libSDL2_image.a +0 -0
  468. data/assets/windows/mingw-w64-x86_64/lib/libSDL2_mixer.a +0 -0
  469. data/assets/windows/mingw-w64-x86_64/lib/libSDL2_ttf.a +0 -0
  470. data/assets/windows/mingw-w64-x86_64/lib/libbrotlicommon.a +0 -0
  471. data/assets/windows/mingw-w64-x86_64/lib/libbrotlidec.a +0 -0
  472. data/assets/windows/mingw-w64-x86_64/lib/libbz2.a +0 -0
  473. data/assets/windows/mingw-w64-x86_64/lib/libdeflate.a +0 -0
  474. data/assets/windows/mingw-w64-x86_64/lib/libfreetype.a +0 -0
  475. data/assets/windows/mingw-w64-x86_64/lib/libglew32.a +0 -0
  476. data/assets/windows/mingw-w64-x86_64/lib/libgraphite2.a +0 -0
  477. data/assets/windows/mingw-w64-x86_64/lib/libharfbuzz.a +0 -0
  478. data/assets/windows/mingw-w64-x86_64/lib/libhwy.a +0 -0
  479. data/assets/windows/mingw-w64-x86_64/lib/libjbig.a +0 -0
  480. data/assets/windows/mingw-w64-x86_64/lib/libjpeg.a +0 -0
  481. data/assets/windows/mingw-w64-x86_64/lib/libjxl.a +0 -0
  482. data/assets/windows/mingw-w64-x86_64/lib/liblzma.a +0 -0
  483. data/assets/windows/mingw-w64-x86_64/lib/libmodplug.a +0 -0
  484. data/assets/windows/mingw-w64-x86_64/lib/libmpg123.a +0 -0
  485. data/assets/windows/mingw-w64-x86_64/lib/libmruby.a +0 -0
  486. data/assets/windows/mingw-w64-x86_64/lib/libogg.a +0 -0
  487. data/assets/windows/mingw-w64-x86_64/lib/libopus.a +0 -0
  488. data/assets/windows/mingw-w64-x86_64/lib/libopusfile.a +0 -0
  489. data/assets/windows/mingw-w64-x86_64/lib/libpng.a +0 -0
  490. data/assets/windows/mingw-w64-x86_64/lib/libsndfile.a +0 -0
  491. data/assets/windows/mingw-w64-x86_64/lib/libssp.a +0 -1
  492. data/assets/windows/mingw-w64-x86_64/lib/libstdc++.a +0 -0
  493. data/assets/windows/mingw-w64-x86_64/lib/libtiff.a +0 -0
  494. data/assets/windows/mingw-w64-x86_64/lib/libvorbis.a +0 -0
  495. data/assets/windows/mingw-w64-x86_64/lib/libvorbisfile.a +0 -0
  496. data/assets/windows/mingw-w64-x86_64/lib/libwebp.a +0 -0
  497. data/assets/windows/mingw-w64-x86_64/lib/libz.a +0 -0
  498. data/assets/windows/mingw-w64-x86_64/lib/libzstd.a +0 -0
  499. data/assets/xcode/ios/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -98
  500. data/assets/xcode/ios/Assets.xcassets/Contents.json +0 -6
  501. data/assets/xcode/ios/Base.lproj/LaunchScreen.storyboard +0 -27
  502. data/assets/xcode/ios/Info.plist +0 -43
  503. data/assets/xcode/ios/MRuby.framework/Headers/mrbconf.h +0 -143
  504. data/assets/xcode/ios/MRuby.framework/Headers/mruby/array.h +0 -280
  505. data/assets/xcode/ios/MRuby.framework/Headers/mruby/boxing_nan.h +0 -102
  506. data/assets/xcode/ios/MRuby.framework/Headers/mruby/boxing_no.h +0 -56
  507. data/assets/xcode/ios/MRuby.framework/Headers/mruby/boxing_word.h +0 -144
  508. data/assets/xcode/ios/MRuby.framework/Headers/mruby/class.h +0 -97
  509. data/assets/xcode/ios/MRuby.framework/Headers/mruby/common.h +0 -77
  510. data/assets/xcode/ios/MRuby.framework/Headers/mruby/compile.h +0 -195
  511. data/assets/xcode/ios/MRuby.framework/Headers/mruby/data.h +0 -75
  512. data/assets/xcode/ios/MRuby.framework/Headers/mruby/debug.h +0 -67
  513. data/assets/xcode/ios/MRuby.framework/Headers/mruby/dump.h +0 -196
  514. data/assets/xcode/ios/MRuby.framework/Headers/mruby/error.h +0 -75
  515. data/assets/xcode/ios/MRuby.framework/Headers/mruby/gc.h +0 -91
  516. data/assets/xcode/ios/MRuby.framework/Headers/mruby/hash.h +0 -215
  517. data/assets/xcode/ios/MRuby.framework/Headers/mruby/irep.h +0 -75
  518. data/assets/xcode/ios/MRuby.framework/Headers/mruby/istruct.h +0 -47
  519. data/assets/xcode/ios/MRuby.framework/Headers/mruby/khash.h +0 -274
  520. data/assets/xcode/ios/MRuby.framework/Headers/mruby/numeric.h +0 -161
  521. data/assets/xcode/ios/MRuby.framework/Headers/mruby/object.h +0 -44
  522. data/assets/xcode/ios/MRuby.framework/Headers/mruby/opcode.h +0 -69
  523. data/assets/xcode/ios/MRuby.framework/Headers/mruby/ops.h +0 -117
  524. data/assets/xcode/ios/MRuby.framework/Headers/mruby/proc.h +0 -131
  525. data/assets/xcode/ios/MRuby.framework/Headers/mruby/range.h +0 -49
  526. data/assets/xcode/ios/MRuby.framework/Headers/mruby/re.h +0 -16
  527. data/assets/xcode/ios/MRuby.framework/Headers/mruby/string.h +0 -444
  528. data/assets/xcode/ios/MRuby.framework/Headers/mruby/throw.h +0 -55
  529. data/assets/xcode/ios/MRuby.framework/Headers/mruby/value.h +0 -309
  530. data/assets/xcode/ios/MRuby.framework/Headers/mruby/variable.h +0 -136
  531. data/assets/xcode/ios/MRuby.framework/Headers/mruby/version.h +0 -110
  532. data/assets/xcode/ios/MRuby.framework/Headers/mruby.h +0 -1282
  533. data/assets/xcode/ios/MRuby.framework/Info.plist +0 -20
  534. data/assets/xcode/ios/MRuby.framework/MRuby +0 -0
  535. data/assets/xcode/ios/MyApp.xcodeproj/project.pbxproj +0 -368
  536. data/assets/xcode/ios/MyApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  537. data/assets/xcode/ios/MyApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  538. data/assets/xcode/ios/MyApp.xcodeproj/project.xcworkspace/xcshareddata/MyApp.xcscmblueprint +0 -30
  539. data/assets/xcode/ios/main.c +0 -1
  540. data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json +0 -16
  541. data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Contents.json +0 -6
  542. data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Contents.json +0 -17
  543. data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json +0 -16
  544. data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Contents.json +0 -6
  545. data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json +0 -16
  546. data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Contents.json +0 -6
  547. data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json +0 -16
  548. data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Contents.json +0 -6
  549. data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Contents.json +0 -17
  550. data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json +0 -16
  551. data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Contents.json +0 -6
  552. data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json +0 -16
  553. data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Contents.json +0 -6
  554. data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json +0 -32
  555. data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json +0 -16
  556. data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json +0 -16
  557. data/assets/xcode/tvos/Assets.xcassets/Contents.json +0 -6
  558. data/assets/xcode/tvos/Assets.xcassets/LaunchImage.launchimage/Contents.json +0 -22
  559. data/assets/xcode/tvos/Info.plist +0 -30
  560. data/assets/xcode/tvos/MRuby.framework/Headers/mrbconf.h +0 -143
  561. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/array.h +0 -280
  562. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/boxing_nan.h +0 -102
  563. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/boxing_no.h +0 -56
  564. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/boxing_word.h +0 -144
  565. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/class.h +0 -97
  566. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/common.h +0 -77
  567. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/compile.h +0 -195
  568. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/data.h +0 -75
  569. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/debug.h +0 -67
  570. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/dump.h +0 -196
  571. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/error.h +0 -75
  572. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/gc.h +0 -91
  573. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/hash.h +0 -215
  574. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/irep.h +0 -75
  575. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/istruct.h +0 -47
  576. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/khash.h +0 -274
  577. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/numeric.h +0 -161
  578. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/object.h +0 -44
  579. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/opcode.h +0 -69
  580. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/ops.h +0 -117
  581. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/proc.h +0 -131
  582. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/range.h +0 -49
  583. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/re.h +0 -16
  584. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/string.h +0 -444
  585. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/throw.h +0 -55
  586. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/value.h +0 -309
  587. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/variable.h +0 -136
  588. data/assets/xcode/tvos/MRuby.framework/Headers/mruby/version.h +0 -110
  589. data/assets/xcode/tvos/MRuby.framework/Headers/mruby.h +0 -1282
  590. data/assets/xcode/tvos/MRuby.framework/Info.plist +0 -20
  591. data/assets/xcode/tvos/MRuby.framework/MRuby +0 -0
  592. data/assets/xcode/tvos/MyApp.xcodeproj/project.pbxproj +0 -350
  593. data/assets/xcode/tvos/MyApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  594. data/assets/xcode/tvos/MyApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  595. data/assets/xcode/tvos/MyApp.xcodeproj/project.xcworkspace/xcshareddata/MyApp.xcscmblueprint +0 -30
  596. data/assets/xcode/tvos/main.c +0 -1
  597. data/ext/ruby2d/common.c +0 -185
  598. data/ext/ruby2d/controllers.c +0 -110
  599. data/ext/ruby2d/gl.c +0 -398
  600. data/ext/ruby2d/gl2.c +0 -86
  601. data/ext/ruby2d/gl3.c +0 -304
  602. data/ext/ruby2d/gles.c +0 -302
  603. data/ext/ruby2d/input.c +0 -48
  604. data/ext/ruby2d/music.c +0 -127
  605. data/ext/ruby2d/sound.c +0 -132
  606. data/lib/ruby2d/cli/console.rb +0 -65
  607. data/lib/ruby2d/cli/enable_console.rb +0 -7
  608. data/lib/ruby2d/cli/platform.rb +0 -17
  609. data/lib/ruby2d/music.rb +0 -77
  610. data/lib/ruby2d/pixel.rb +0 -18
  611. data/lib/ruby2d/pixmap.rb +0 -39
  612. data/lib/ruby2d/pixmap_atlas.rb +0 -56
  613. data/lib/ruby2d/sound.rb +0 -61
  614. data/lib/ruby2d/texture.rb +0 -38
  615. /data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengles2_gl2.h +0 -0
  616. /data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengles2_gl2ext.h +0 -0
  617. /data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengles2_gl2platform.h +0 -0
  618. /data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengles2_khrplatform.h +0 -0
  619. /data/assets/{include → platform/include}/mruby/re.h +0 -0
data/ext/ruby2d/canvas.c CHANGED
@@ -2,539 +2,1760 @@
2
2
 
3
3
  #include "ruby2d.h"
4
4
 
5
- typedef SDL_FPoint vector_t;
6
-
7
- static float vector_magnitude(const vector_t *vec)
8
- {
9
- return sqrtf((vec->x * vec->x) + (vec->y * vec->y));
10
- }
11
-
12
- static vector_t *vector_normalize(vector_t *vec)
13
- {
14
- float mag = vector_magnitude(vec);
15
- if (mag == 0)
16
- mag = INFINITY;
17
- vec->x /= mag;
18
- vec->y /= mag;
19
- return vec;
20
- }
21
-
22
- static vector_t *vector_perpendicular(vector_t *vec)
23
- {
24
- float tmp_x = vec->x;
25
- vec->x = vec->y;
26
- vec->y = -tmp_x;
27
- return vec;
28
- }
29
-
30
- static vector_t *vector_times_scalar(vector_t *vec, float value)
31
- {
32
- vec->x *= value;
33
- vec->y *= value;
34
- return vec;
35
- }
36
-
37
- static vector_t *vector_minus_vector(vector_t *vec, const vector_t *other)
38
- {
39
- vec->x -= other->x;
40
- vec->y -= other->y;
41
- return vec;
42
- }
43
-
44
- /* Unused
45
- static vector_t *vector_minus_xy(vector_t *vec, float other_x, float other_y)
46
- {
47
- vec->x -= other_x;
48
- vec->y -= other_y;
49
- return vec;
50
- }
51
- */
52
-
53
- static vector_t *vector_plus_vector(vector_t *vec, const vector_t *other)
54
- {
55
- vec->x += other->x;
56
- vec->y += other->y;
57
- return vec;
58
- }
59
-
60
- /* Unused
61
- static vector_t *vector_plus_xy(vector_t *vec, float other_x, float other_y)
62
- {
63
- vec->x += other_x;
64
- vec->y += other_y;
65
- return vec;
66
- }
67
- */
68
-
69
- typedef enum { CONCAVE = -1, INVALID, CONVEX } poly_type_e;
70
-
71
- //
72
- // Go through each corner and identify and count the number of concave
73
- // ones, also noting down the last concave point.
74
- //
75
- // TODO: collect all the concave points for a later ear-clipper.
76
- // @param [int *] pconcave_ix Pointer to array of slots for concave point
77
- // indices
78
- // @param [int] max_pconcave Max number of clots for concave point indices
79
- //
80
- // @return number of concave points; note this may be > +max_pconcave+ so caller
81
- // needs to check result carefully.
82
- //
83
- static int count_concave_corners(const SDL_FPoint *points, int num_points,
84
- int *pconcave_ix, const int max_pconcave)
85
- {
86
- // triangles are always convex
87
- if (num_points <= 3)
88
- return (0);
89
-
90
- int nconcave = 0;
91
-
92
- // pick first last and first points
93
- int x1 = points[0].x;
94
- int y1 = points[0].y;
95
- int x2 = points[1].x;
96
- int y2 = points[1].y;
97
- // start with second point and proceed point by point inspecting
98
- // each corner
99
- float z, zprev = 0;
100
- for (int i = 1; i < num_points; i++, zprev = z) {
101
- int j = (i + 1) % num_points;
102
- int x3 = points[j].x;
103
- int y3 = points[j].y;
104
- // cross product
105
- z = ((x1 - x2) * (y2 - y3)) - ((y1 - y2) * (x2 - x3));
106
- if (z < 0) {
107
- if (nconcave < max_pconcave)
108
- *pconcave_ix++ = i;
109
- nconcave++;
5
+ R2D_DEFINE_DATA_TYPE(R2D_Canvas);
6
+
7
+ // Canvas-specific ivar IDs. Shared ivar IDs live in ext.c. Note that canvas
8
+ // uses @tint instead of @color, but the underlying r/g/b/a components are
9
+ // the shared ones.
10
+ static R_ID id_ext_canvas;
11
+ static R_ID id_tint;
12
+ static R_ID id_fill_r, id_fill_g, id_fill_b, id_fill_a;
13
+
14
+
15
+ /*
16
+ * Initialize
17
+ */
18
+ void R2D_Canvas_Init() {
19
+ id_ext_canvas = r_id("@ext_canvas");
20
+ id_tint = r_id("@tint");
21
+ id_fill_r = r_id("@fill_r");
22
+ id_fill_g = r_id("@fill_g");
23
+ id_fill_b = r_id("@fill_b");
24
+ id_fill_a = r_id("@fill_a");
25
+
26
+ r_define_class_method(ruby2d_ext_module, "canvas_create", ruby2d_ext_canvas_create, r_args_variadic);
27
+ r_define_class_method(ruby2d_ext_module, "canvas_draw", ruby2d_ext_canvas_draw, r_args_variadic);
28
+ r_define_class_method(ruby2d_ext_module, "canvas_clear", ruby2d_ext_canvas_clear, r_args_variadic);
29
+ r_define_class_method(ruby2d_ext_module, "canvas_fill_triangle", ruby2d_ext_canvas_fill_triangle, r_args_variadic);
30
+ r_define_class_method(ruby2d_ext_module, "canvas_fill_triangle_lerp", ruby2d_ext_canvas_fill_triangle_lerp, r_args_variadic);
31
+ r_define_class_method(ruby2d_ext_module, "canvas_fill_rectangle", ruby2d_ext_canvas_fill_rectangle, r_args_variadic);
32
+ r_define_class_method(ruby2d_ext_module, "canvas_fill_rectangles", ruby2d_ext_canvas_fill_rectangles, r_args_variadic);
33
+ r_define_class_method(ruby2d_ext_module, "canvas_fill_pixel_grid", ruby2d_ext_canvas_fill_pixel_grid, r_args_variadic);
34
+ r_define_class_method(ruby2d_ext_module, "canvas_fill_ellipse", ruby2d_ext_canvas_fill_ellipse, r_args_variadic);
35
+ r_define_class_method(ruby2d_ext_module, "canvas_fill_polygon", ruby2d_ext_canvas_fill_polygon, r_args_variadic);
36
+ r_define_class_method(ruby2d_ext_module, "canvas_fill_polygon_lerp", ruby2d_ext_canvas_fill_polygon_lerp, r_args_variadic);
37
+ r_define_class_method(ruby2d_ext_module, "canvas_draw_line", ruby2d_ext_canvas_draw_line, r_args_variadic);
38
+ r_define_class_method(ruby2d_ext_module, "canvas_draw_line_lerp", ruby2d_ext_canvas_draw_line_lerp, r_args_variadic);
39
+ r_define_class_method(ruby2d_ext_module, "canvas_draw_lines", ruby2d_ext_canvas_draw_lines, r_args_variadic);
40
+ r_define_class_method(ruby2d_ext_module, "canvas_stroke_polygon", ruby2d_ext_canvas_stroke_polygon, r_args_variadic);
41
+ r_define_class_method(ruby2d_ext_module, "canvas_stroke_polyline", ruby2d_ext_canvas_stroke_polyline, r_args_variadic);
42
+ r_define_class_method(ruby2d_ext_module, "canvas_draw_image", ruby2d_ext_canvas_draw_image, r_args_variadic);
43
+ r_define_class_method(ruby2d_ext_module, "canvas_draw_text", ruby2d_ext_canvas_draw_text, r_args_variadic);
44
+ }
45
+
46
+
47
+ /*
48
+ * Ruby2D::Canvas#ext_create
49
+ */
50
+ R_VAL ruby2d_ext_canvas_create(RUBY2D_METHOD_ARGS_VARIADIC) {
51
+ RUBY2D_EXTRACT_VARIADIC;
52
+ if (argc != 1) r_raise("Ruby2D::Ext.canvas_create expects 1 arg (canvas), got %d", (int)argc);
53
+ R_VAL obj = argv[0];
54
+
55
+ // Ensure SDL subsystems are initialized (display_scale is set in R2D_Init)
56
+ if (!R2D_Init()) r_raise("Ruby2D: failed to initialize: %s", SDL_GetError());
57
+
58
+ int width = obj_int(obj, id_width);
59
+ int height = obj_int(obj, id_height);
60
+
61
+ /* Create the surface at physical pixel dimensions so canvas content is
62
+ crisp on HiDPI displays. The Ruby-side width/height remain in logical
63
+ coordinates; drawing methods scale inputs by the same factor.
64
+
65
+ A Canvas captures its scale once, here, and never re-evaluates (unlike
66
+ text, which re-rasterizes from its string when the scale changes — a
67
+ Canvas holds the user's accumulated drawing and can't be recreated).
68
+ If no window exists yet (a Canvas built as the very first Ruby 2D
69
+ object), R2D_GetAssetScale() would report 1.0 and freeze the surface at
70
+ logical resolution. Fall back to the init display scale — known since
71
+ R2D_Init, before any window — so the common HiDPI case stays crisp. */
72
+ float scale = R2D_GetWindow() ? R2D_GetAssetScale() : R2D_GetInitDisplayScale();
73
+ int pixel_w = (int)(width * scale);
74
+ int pixel_h = (int)(height * scale);
75
+
76
+ SDL_Surface *surface = SDL_CreateSurface(pixel_w, pixel_h, SDL_PIXELFORMAT_RGBA32);
77
+ if (!surface) {
78
+ r_raise("SDL_CreateSurface failed: %s", SDL_GetError());
79
+ return R_NIL;
80
+ }
81
+
82
+ // Fill with the fill color
83
+ float fr = obj_float(obj, id_fill_r);
84
+ float fg = obj_float(obj, id_fill_g);
85
+ float fb = obj_float(obj, id_fill_b);
86
+ float fa = obj_float(obj, id_fill_a);
87
+
88
+ Uint8 r = (Uint8)(fr * 255);
89
+ Uint8 g = (Uint8)(fg * 255);
90
+ Uint8 b = (Uint8)(fb * 255);
91
+ Uint8 a = (Uint8)(fa * 255);
92
+
93
+ Uint32 color = SDL_MapSurfaceRGBA(surface, r, g, b, a);
94
+ SDL_FillSurfaceRect(surface, NULL, color);
95
+ SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_BLEND);
96
+
97
+ R2D_Canvas *can = ALLOC(R2D_Canvas);
98
+ can->surface = surface;
99
+ can->texture = NULL;
100
+ can->scale = scale;
101
+ can->dirty = false;
102
+ can->dirty_rect = (SDL_Rect){ 0, 0, 0, 0 };
103
+
104
+ obj_set_struct(obj, id_ext_canvas, R2D_Canvas, can);
105
+
106
+ return R_TRUE;
107
+ }
108
+
109
+
110
+ static void canvas_mark_dirty(R2D_Canvas *can, float min_x, float min_y,
111
+ float max_x, float max_y);
112
+ static void canvas_mark_dirty_all(R2D_Canvas *can);
113
+
114
+
115
+ /*
116
+ * Ruby2D::Canvas#ext_draw
117
+ */
118
+ R_VAL ruby2d_ext_canvas_draw(RUBY2D_METHOD_ARGS_VARIADIC) {
119
+ RUBY2D_EXTRACT_VARIADIC;
120
+ if (argc != 3) r_raise("Ruby2D::Ext.canvas_draw expects 3 args (canvas, rx, ry), got %d", (int)argc);
121
+ R_VAL obj = argv[0];
122
+ float crx = NUM2DBL(argv[1]);
123
+ float cry = NUM2DBL(argv[2]);
124
+
125
+ R2D_Canvas *can;
126
+ obj_struct(obj, id_ext_canvas, R2D_Canvas, can);
127
+
128
+ /* Create the persistent GPU texture on first draw. Created explicitly in
129
+ the surface's pixel format (rather than SDL_CreateTextureFromSurface) so
130
+ the incremental SDL_UpdateTexture uploads below can pass surface pixel
131
+ rows through unconverted. The texture then lives for the canvas's
132
+ lifetime — mutations upload into it instead of re-creating it. */
133
+ if (can->texture == NULL) {
134
+ can->texture = SDL_CreateTexture(R2D_GetRenderer(), SDL_PIXELFORMAT_RGBA32,
135
+ SDL_TEXTUREACCESS_STATIC,
136
+ can->surface->w, can->surface->h);
137
+ if (!can->texture) {
138
+ r_raise("SDL_CreateTexture failed: %s", SDL_GetError());
139
+ return R_NIL;
110
140
  }
111
- // shift points left so we
112
- x1 = x2;
113
- y1 = y2;
114
- x2 = x3;
115
- y2 = y3;
116
- }
117
- return nconcave;
118
- }
119
-
120
- //
121
- // If a polygon is entirely conves, we triangulate trivially
122
- // by using the first point as the anchor to fan out the triangles
123
- static void fill_convex_polygon(SDL_Renderer *render, const SDL_FPoint *points,
124
- int num_points, const SDL_Color *colors,
125
- int num_colors)
126
- {
127
- // Setup the vertices from incoming arguments
128
- SDL_Vertex verts[3];
129
-
130
- // use first vertex as anchor and draw a triangle with
131
- // subsequent pair of points.
132
- verts[0].position = points[0];
133
- verts[0].color = colors[0];
134
- for (int i = 2; i < num_points; i++) {
135
- verts[1].position = points[i - 1];
136
- verts[1].color = colors[(i - 1) % num_colors];
137
- verts[2].position = points[i];
138
- verts[2].color = colors[i % num_colors];
139
- SDL_RenderGeometry(render, NULL, verts, 3, NULL, 0);
140
- }
141
- }
142
-
143
- //
144
- // If a polygon has one concave corner, we can still do trivial
145
- // triangulation as we do for convex polygons using the
146
- // concave corner as the anchor point for the fan-out.
147
- static void fill_concave1pt_polygon(SDL_Renderer *render,
148
- const SDL_FPoint *points, int num_points,
149
- int anchor_ix, const SDL_Color *colors,
150
- int num_colors)
151
- {
152
- // Setup the vertices from incoming arguments
153
- SDL_Vertex verts[3];
154
-
155
- verts[0].position = points[anchor_ix];
156
- verts[0].color = colors[anchor_ix % num_colors];
157
- anchor_ix = (anchor_ix + 1) % num_points;
158
- for (int i = 2; i < num_points; i++) {
159
- verts[1].position = points[anchor_ix];
160
- verts[1].color = colors[anchor_ix % num_colors];
161
- anchor_ix = (anchor_ix + 1) % num_points;
162
- verts[2].position = points[anchor_ix];
163
- verts[2].color = colors[anchor_ix % num_colors];
164
- SDL_RenderGeometry(render, NULL, verts, 3, NULL, 0);
165
- }
166
- }
167
-
168
- #define MAX_CONCAVE_POINT_INDICES 32
169
-
170
- /*
171
- * Fill a polygon with a single colour.
172
- * @param points Array of points
173
- * @param num_points Number of points
174
- * @param colors Array of colours
175
- * @param num_colors Number of colors, must be at least 1, and may be less than
176
- * +num_points+ in which case the colours will be repeated
177
- * via modulo.
178
- * @note Currently supports only: convex polygons or simple polygons with one
179
- * concave corner. For now.
180
- */
181
- void R2D_Canvas_FillPolygon(SDL_Renderer *render, SDL_FPoint *points,
182
- int num_points, SDL_Color *colors, int num_colors)
183
- {
184
- if (num_points < 3)
141
+ SDL_SetTextureBlendMode(can->texture, SDL_BLENDMODE_BLEND);
142
+ canvas_mark_dirty_all(can); // populate with a full first upload
143
+ // Don't destroy the surface — Canvas needs it for future draw operations
144
+ }
145
+
146
+ // Upload pending surface mutations — just the dirty union, not the whole
147
+ // surface. A settled canvas skips this entirely.
148
+ if (can->dirty) {
149
+ const SDL_Rect *dr = &can->dirty_rect;
150
+ const Uint8 *pixels = (const Uint8 *)can->surface->pixels
151
+ + (size_t)dr->y * can->surface->pitch
152
+ + (size_t)dr->x * 4;
153
+ R2D_CheckSDL(SDL_UpdateTexture(can->texture, dr, pixels, can->surface->pitch),
154
+ "SDL_UpdateTexture");
155
+ can->dirty = false;
156
+ }
157
+
158
+ SDL_FRect dst_rect = {
159
+ obj_float(obj, id_x),
160
+ obj_float(obj, id_y),
161
+ obj_float(obj, id_width),
162
+ obj_float(obj, id_height)
163
+ };
164
+
165
+ R_VAL color_obj = r_ivar_get(obj, id_tint);
166
+ SDL_SetTextureColorModFloat(can->texture,
167
+ NUM2DBL(r_ivar_get(color_obj, id_r)),
168
+ NUM2DBL(r_ivar_get(color_obj, id_g)),
169
+ NUM2DBL(r_ivar_get(color_obj, id_b))
170
+ );
171
+ SDL_SetTextureAlphaModFloat(can->texture, NUM2DBL(r_ivar_get(color_obj, id_a)));
172
+
173
+ SDL_FPoint center = {
174
+ crx - obj_float(obj, id_x),
175
+ cry - obj_float(obj, id_y)
176
+ };
177
+
178
+ R2D_CheckSDL(SDL_RenderTextureRotated(
179
+ R2D_GetRenderer(),
180
+ can->texture,
181
+ NULL,
182
+ &dst_rect,
183
+ obj_float(obj, id_rotate),
184
+ &center,
185
+ SDL_FLIP_NONE
186
+ ), "SDL_RenderTextureRotated");
187
+
188
+ return R_TRUE;
189
+ }
190
+
191
+
192
+ /*
193
+ * Swap two float values
194
+ */
195
+ static void swap_float(float *a, float *b) {
196
+ float t = *a;
197
+ *a = *b;
198
+ *b = t;
199
+ }
200
+
201
+
202
+ /*
203
+ * Composite a straight-alpha RGBA source over one RGBA8888 destination pixel,
204
+ * in place, using Porter-Duff "over". Callers handle the opaque (a==255) fast
205
+ * path; this is the partly-transparent case.
206
+ *
207
+ * The canvas stores straight (non-premultiplied) RGBA, so the blended color is
208
+ * divided by the combined output coverage. When the destination is already
209
+ * opaque (da==255) this reduces exactly — bit for bit — to the legacy
210
+ * src*a + dst*(255-a) blend; only partly-transparent destinations (overlay
211
+ * canvases) change, where the old math composited them too dark.
212
+ */
213
+ static inline void canvas_blend_over(Uint8 *pixel, Uint8 r, Uint8 g, Uint8 b, Uint8 a) {
214
+ Uint8 dr = pixel[0], dg = pixel[1], db = pixel[2], da = pixel[3];
215
+ int inv = 255 - a;
216
+
217
+ // Fast path: an opaque destination (the common case — painting onto a freshly
218
+ // cleared opaque canvas) stays opaque, and "over" reduces bit for bit to the
219
+ // legacy src*a + dst*(255-a) blend with a constant /255 divisor. The compiler
220
+ // lowers that to a multiply+shift, dropping the three runtime divides below.
221
+ if (da == 255) {
222
+ pixel[0] = (Uint8)((r * a + dr * inv) / 255);
223
+ pixel[1] = (Uint8)((g * a + dg * inv) / 255);
224
+ pixel[2] = (Uint8)((b * a + db * inv) / 255);
225
+ pixel[3] = 255;
185
226
  return;
227
+ }
186
228
 
187
- // poly_type_e type = polygon_type(points, num_points);
188
- int concave_point_indices[MAX_CONCAVE_POINT_INDICES];
229
+ int dst_w = da * inv / 255; // destination coverage behind src: da*(1-a)
230
+ int out_a = a + dst_w; // combined src-over-dst coverage; divisor below
231
+ if (out_a == 0) { // src and dst both fully transparent
232
+ pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0;
233
+ return;
234
+ }
235
+ pixel[0] = (Uint8)((r * a + dr * dst_w) / out_a);
236
+ pixel[1] = (Uint8)((g * a + dg * dst_w) / out_a);
237
+ pixel[2] = (Uint8)((b * a + db * dst_w) / out_a);
238
+ pixel[3] = (Uint8)out_a;
239
+ }
189
240
 
190
- int nconcave =
191
- num_points == 3
192
- ? 0 // triangles are always convex
193
- : count_concave_corners(points, num_points, concave_point_indices,
194
- MAX_CONCAVE_POINT_INDICES);
195
241
 
196
- if (nconcave == 0) {
197
- // convex
198
- fill_convex_polygon(render, points, num_points, colors, num_colors);
242
+ /*
243
+ * Fill a triangle on an SDL_Surface using scanline rasterization.
244
+ * Coordinates are in surface pixel space. Color components are 0-255.
245
+ */
246
+ static void canvas_fill_triangle_on_surface(
247
+ SDL_Surface *surface,
248
+ float fx1, float fy1, float fx2, float fy2, float fx3, float fy3,
249
+ Uint8 r, Uint8 g, Uint8 b, Uint8 a) {
250
+
251
+ if (a == 0) return; // fully transparent: nothing to draw
252
+
253
+ // A non-finite vertex (NaN/Inf, e.g. from a degenerate user coordinate) would
254
+ // make the (int)ceilf casts below undefined behavior and could index out of
255
+ // the surface. Skip the draw, matching the no-op philosophy for degenerate
256
+ // geometry elsewhere in this file.
257
+ if (!isfinite(fx1) || !isfinite(fy1) || !isfinite(fx2) ||
258
+ !isfinite(fy2) || !isfinite(fx3) || !isfinite(fy3)) return;
259
+
260
+ // Sort vertices by y coordinate
261
+ if (fy1 > fy2) { swap_float(&fx1, &fx2); swap_float(&fy1, &fy2); }
262
+ if (fy1 > fy3) { swap_float(&fx1, &fx3); swap_float(&fy1, &fy3); }
263
+ if (fy2 > fy3) { swap_float(&fx2, &fx3); swap_float(&fy2, &fy3); }
264
+
265
+ // Half-open y coverage: row i is filled iff ceil(ymin) <= i < ceil(ymax),
266
+ // matching the x-extent rule below and canvas_fill_rectangle_on_surface. Using
267
+ // floorf here would close the interval and over-cover integer-aligned bottom
268
+ // edges by one row (visible as 1px-too-thick horizontal lines at 2x scale).
269
+ int top = (int)ceilf(fy1);
270
+ int middle = (int)ceilf(fy2);
271
+ int bottom = (int)ceilf(fy3) - 1;
272
+
273
+ if (top < 0) top = 0;
274
+ if (bottom >= surface->h) bottom = surface->h - 1;
275
+
276
+ float dy13 = fy3 - fy1;
277
+ float dy12 = fy2 - fy1;
278
+ float dy23 = fy3 - fy2;
279
+
280
+ // The fill color is constant across the triangle, so pack it once (endian-safe
281
+ // via SDL_MapSurfaceRGBA) and write opaque pixels with a single 32-bit store
282
+ // instead of four byte stores.
283
+ Uint32 packed = SDL_MapSurfaceRGBA(surface, r, g, b, 255);
284
+
285
+ for (int y = top; y <= bottom; y++) {
286
+ float xa, xb;
287
+
288
+ // Long edge (v1 to v3)
289
+ if (dy13 > 0.0f) {
290
+ xa = fx1 + (fx3 - fx1) * ((float)y - fy1) / dy13;
291
+ } else {
292
+ xa = fx1;
293
+ }
294
+
295
+ // Short edge depends on top or bottom half
296
+ if (y < middle) {
297
+ if (dy12 > 0.0f) {
298
+ xb = fx1 + (fx2 - fx1) * ((float)y - fy1) / dy12;
299
+ } else {
300
+ xb = fx1;
301
+ }
302
+ } else {
303
+ if (dy23 > 0.0f) {
304
+ xb = fx2 + (fx3 - fx2) * ((float)y - fy2) / dy23;
305
+ } else {
306
+ xb = fx2;
307
+ }
308
+ }
309
+
310
+ int x_start = (int)ceilf(fminf(xa, xb));
311
+ int x_end = (int)ceilf(fmaxf(xa, xb)) - 1;
312
+
313
+ if (x_start < 0) x_start = 0;
314
+ if (x_end >= surface->w) x_end = surface->w - 1;
315
+
316
+ // RGBA32 guarantees R,G,B,A byte order in memory
317
+ Uint8 *row = (Uint8 *)surface->pixels + y * surface->pitch;
318
+ for (int x = x_start; x <= x_end; x++) {
319
+ Uint8 *pixel = row + x * 4;
320
+ if (a == 255) {
321
+ SDL_memcpy(pixel, &packed, 4);
322
+ } else {
323
+ canvas_blend_over(pixel, r, g, b, a);
324
+ }
325
+ }
199
326
  }
200
- else if (nconcave == 1) {
201
- // use concave point as the origin if only one concave vertex
202
- fill_concave1pt_polygon(render, points, num_points,
203
- concave_point_indices[0], colors, num_colors);
327
+ }
328
+
329
+
330
+ /*
331
+ * Fill a triangle on an SDL_Surface with per-vertex color interpolation.
332
+ * Uses barycentric coordinates to interpolate RGBA across the triangle.
333
+ * Coordinates are in surface pixel space. Color components are 0-255.
334
+ */
335
+ static void canvas_fill_triangle_lerp_on_surface(
336
+ SDL_Surface *surface,
337
+ float fx1, float fy1, Uint8 r1, Uint8 g1, Uint8 b1, Uint8 a1,
338
+ float fx2, float fy2, Uint8 r2, Uint8 g2, Uint8 b2, Uint8 a2,
339
+ float fx3, float fy3, Uint8 r3, Uint8 g3, Uint8 b3, Uint8 a3) {
340
+
341
+ // Fully transparent: alpha interpolates linearly, so if all three vertices
342
+ // are 0 every interior pixel is too. Skip before walking any scanline,
343
+ // mirroring the solid rasterizer's `a == 0` early-out.
344
+ if (a1 == 0 && a2 == 0 && a3 == 0) return;
345
+
346
+ // A non-finite vertex (NaN/Inf) would make the (int)ceilf casts below
347
+ // undefined behavior; skip the draw rather than risk an out-of-bounds write.
348
+ if (!isfinite(fx1) || !isfinite(fy1) || !isfinite(fx2) ||
349
+ !isfinite(fy2) || !isfinite(fx3) || !isfinite(fy3)) return;
350
+
351
+ // Barycentric denominator (constant for the whole triangle)
352
+ float denom = (fy2 - fy3) * (fx1 - fx3) + (fx3 - fx2) * (fy1 - fy3);
353
+ if (fabsf(denom) < 0.0001f) return;
354
+ float inv_denom = 1.0f / denom;
355
+
356
+ // Sort vertices by y for scanline traversal (keep originals for barycentric)
357
+ float sx1 = fx1, sy1 = fy1;
358
+ float sx2 = fx2, sy2 = fy2;
359
+ float sx3 = fx3, sy3 = fy3;
360
+
361
+ if (sy1 > sy2) { swap_float(&sx1, &sx2); swap_float(&sy1, &sy2); }
362
+ if (sy1 > sy3) { swap_float(&sx1, &sx3); swap_float(&sy1, &sy3); }
363
+ if (sy2 > sy3) { swap_float(&sx2, &sx3); swap_float(&sy2, &sy3); }
364
+
365
+ // Half-open y coverage (see canvas_fill_triangle_on_surface) — keeps the lerp
366
+ // rasterizer consistent with the solid one and avoids over-covering integer-
367
+ // aligned bottom edges by a row.
368
+ int top = (int)ceilf(sy1);
369
+ int middle = (int)ceilf(sy2);
370
+ int bottom = (int)ceilf(sy3) - 1;
371
+
372
+ if (top < 0) top = 0;
373
+ if (bottom >= surface->h) bottom = surface->h - 1;
374
+
375
+ float dy13 = sy3 - sy1;
376
+ float dy12 = sy2 - sy1;
377
+ float dy23 = sy3 - sy2;
378
+
379
+ for (int y = top; y <= bottom; y++) {
380
+ float xa, xb;
381
+
382
+ if (dy13 > 0.0f)
383
+ xa = sx1 + (sx3 - sx1) * ((float)y - sy1) / dy13;
384
+ else
385
+ xa = sx1;
386
+
387
+ if (y < middle) {
388
+ if (dy12 > 0.0f)
389
+ xb = sx1 + (sx2 - sx1) * ((float)y - sy1) / dy12;
390
+ else
391
+ xb = sx1;
392
+ } else {
393
+ if (dy23 > 0.0f)
394
+ xb = sx2 + (sx3 - sx2) * ((float)y - sy2) / dy23;
395
+ else
396
+ xb = sx2;
397
+ }
398
+
399
+ int x_start = (int)ceilf(fminf(xa, xb));
400
+ int x_end = (int)ceilf(fmaxf(xa, xb)) - 1;
401
+
402
+ if (x_start < 0) x_start = 0;
403
+ if (x_end >= surface->w) x_end = surface->w - 1;
404
+
405
+ Uint8 *row = (Uint8 *)surface->pixels + y * surface->pitch;
406
+ for (int x = x_start; x <= x_end; x++) {
407
+ // Barycentric weights from original (unsorted) vertices
408
+ float w1 = ((fy2 - fy3) * ((float)x - fx3) + (fx3 - fx2) * ((float)y - fy3)) * inv_denom;
409
+ float w2 = ((fy3 - fy1) * ((float)x - fx3) + (fx1 - fx3) * ((float)y - fy3)) * inv_denom;
410
+ float w3 = 1.0f - w1 - w2;
411
+
412
+ w1 = fminf(fmaxf(w1, 0.0f), 1.0f);
413
+ w2 = fminf(fmaxf(w2, 0.0f), 1.0f);
414
+ w3 = fminf(fmaxf(w3, 0.0f), 1.0f);
415
+
416
+ Uint8 r = (Uint8)(w1 * r1 + w2 * r2 + w3 * r3);
417
+ Uint8 g = (Uint8)(w1 * g1 + w2 * g2 + w3 * g3);
418
+ Uint8 b = (Uint8)(w1 * b1 + w2 * b2 + w3 * b3);
419
+ Uint8 a = (Uint8)(w1 * a1 + w2 * a2 + w3 * a3);
420
+
421
+ Uint8 *pixel = row + x * 4;
422
+ if (a == 255) {
423
+ pixel[0] = r;
424
+ pixel[1] = g;
425
+ pixel[2] = b;
426
+ pixel[3] = 255;
427
+ } else if (a > 0) {
428
+ canvas_blend_over(pixel, r, g, b, a);
429
+ }
430
+ }
204
431
  }
205
- // else
206
- // printf("TODO: Non-convex polygon with %d concave corners\n", nconcave);
207
432
  }
208
433
 
434
+
435
+ /*
436
+ * Draw a thick line on an SDL_Surface as a quad (two triangles).
437
+ * Coordinates are in surface pixel space. Color components are 0-255.
438
+ */
439
+ static void canvas_draw_line_on_surface(
440
+ SDL_Surface *surface,
441
+ float x1, float y1, float x2, float y2,
442
+ float width,
443
+ Uint8 r, Uint8 g, Uint8 b, Uint8 a) {
444
+
445
+ float dx = x2 - x1;
446
+ float dy = y2 - y1;
447
+ float len = sqrtf(dx * dx + dy * dy);
448
+
449
+ if (len < 0.0001f) return;
450
+
451
+ // Perpendicular unit vector scaled by half width
452
+ float hw = width / 2.0f;
453
+ float ox = (-dy / len) * hw;
454
+ float oy = ( dx / len) * hw;
455
+
456
+ // Four corners of the line quad
457
+ float qx1 = x1 + ox, qy1 = y1 + oy;
458
+ float qx2 = x1 - ox, qy2 = y1 - oy;
459
+ float qx3 = x2 - ox, qy3 = y2 - oy;
460
+ float qx4 = x2 + ox, qy4 = y2 + oy;
461
+
462
+ canvas_fill_triangle_on_surface(surface, qx1, qy1, qx2, qy2, qx3, qy3, r, g, b, a);
463
+ canvas_fill_triangle_on_surface(surface, qx1, qy1, qx3, qy3, qx4, qy4, r, g, b, a);
464
+ }
465
+
466
+
209
467
  /*
210
- * Draw a thick line on a canvas using a pre-converted RGBA colour value.
211
- * @param [int] thickness must be > 1, else does nothing
468
+ * Draw a thick line on an SDL_Surface as a quad with per-vertex colors.
469
+ * The 4 colors map to corners: c1=start+perp, c2=start-perp, c3=end-perp,
470
+ * c4=end+perp, matching R2D_DrawLine's vertex layout.
212
471
  */
213
- void R2D_Canvas_DrawThickLine(SDL_Renderer *render, int x1, int y1, int x2,
214
- int y2, int thickness, int r, int g, int b, int a)
215
- {
472
+ static void canvas_draw_line_lerp_on_surface(
473
+ SDL_Surface *surface,
474
+ float x1, float y1, float x2, float y2,
475
+ float width,
476
+ Uint8 r1, Uint8 g1, Uint8 b1, Uint8 a1,
477
+ Uint8 r2, Uint8 g2, Uint8 b2, Uint8 a2,
478
+ Uint8 r3, Uint8 g3, Uint8 b3, Uint8 a3,
479
+ Uint8 r4, Uint8 g4, Uint8 b4, Uint8 a4) {
480
+
481
+ float dx = x2 - x1;
482
+ float dy = y2 - y1;
483
+ float len = sqrtf(dx * dx + dy * dy);
484
+
485
+ if (len < 0.0001f) return;
486
+
487
+ float hw = width / 2.0f;
488
+ float ox = (-dy / len) * hw;
489
+ float oy = ( dx / len) * hw;
490
+
491
+ float qx1 = x1 + ox, qy1 = y1 + oy;
492
+ float qx2 = x1 - ox, qy2 = y1 - oy;
493
+ float qx3 = x2 - ox, qy3 = y2 - oy;
494
+ float qx4 = x2 + ox, qy4 = y2 + oy;
495
+
496
+ canvas_fill_triangle_lerp_on_surface(surface,
497
+ qx1, qy1, r1, g1, b1, a1,
498
+ qx2, qy2, r2, g2, b2, a2,
499
+ qx3, qy3, r3, g3, b3, a3);
500
+ canvas_fill_triangle_lerp_on_surface(surface,
501
+ qx1, qy1, r1, g1, b1, a1,
502
+ qx3, qy3, r3, g3, b3, a3,
503
+ qx4, qy4, r4, g4, b4, a4);
504
+ }
216
505
 
217
- if (thickness <= 1)
218
- return;
219
506
 
220
- vector_t vec = {.x = (x2 - x1), .y = (y2 - y1)};
507
+ /*
508
+ * Lerp two Uint8 channel values at parameter t in [0, 1].
509
+ */
510
+ static inline Uint8 lerp_u8(Uint8 a, Uint8 b, float t) {
511
+ return (Uint8)((float)a + ((float)b - (float)a) * t + 0.5f);
512
+ }
513
+
514
+
515
+ /*
516
+ * Stroke a polyline on an SDL_Surface with miter joins.
517
+ * When closed is true, the last vertex connects back to the first.
518
+ * When closed is false, endpoints get butt caps.
519
+ * verts is [x0, y0, x1, y1, ...], n is the vertex count.
520
+ * colors is n*4 Uint8s (rgba per vertex); outer/inner at each vertex share its
521
+ * color, so per-edge quads interpolate between adjacent vertex colors.
522
+ * Coordinates are in surface pixel space. Color components are 0-255.
523
+ *
524
+ * Outline geometry is computed by the shared R2D_ComputeStrokeOutline so
525
+ * Canvas strokes match the GPU-side R2D_StrokePath at corners.
526
+ */
527
+ static void canvas_stroke_polyline_on_surface(
528
+ SDL_Surface *surface,
529
+ float *verts, int n, int closed, float width,
530
+ const Uint8 *colors) {
531
+
532
+ if (n < 2) return;
533
+
534
+ // Most strokes are small (triangle, quad, default circle), so serve the
535
+ // outline buffers from the stack and fall back to the heap only past the
536
+ // threshold — no malloc/free per call on the per-frame canvas paint path.
537
+ float outer_stack[R2D_STROKE_STACK_N * 2];
538
+ float inner_stack[R2D_STROKE_STACK_N * 2];
539
+ float *outer, *inner;
540
+ bool heap = n > R2D_STROKE_STACK_N;
541
+
542
+ if (heap) {
543
+ outer = (float *)malloc(n * 2 * sizeof(float));
544
+ inner = (float *)malloc(n * 2 * sizeof(float));
545
+ if (!outer || !inner) { free(outer); free(inner); return; }
546
+ } else {
547
+ outer = outer_stack;
548
+ inner = inner_stack;
549
+ }
550
+
551
+ R2D_ComputeStrokeOutline(verts, n, closed, width, R2D_MITER_LIMIT, outer, inner);
221
552
 
222
- // calculate perpendicular with half-thickness in place
223
- vector_times_scalar(vector_perpendicular(vector_normalize(&vec)),
224
- thickness / 2.0f);
553
+ // Draw a quad for each edge segment
554
+ int edges = closed ? n : n - 1;
555
+ for (int i = 0; i < edges; i++) {
556
+ int j = (i + 1) % n;
225
557
 
226
- // calculate perp vertices based on thickness
227
- SDL_Vertex verts[4];
558
+ Uint8 ri = colors[i * 4], gi = colors[i * 4 + 1];
559
+ Uint8 bi = colors[i * 4 + 2], ai = colors[i * 4 + 3];
560
+ Uint8 rj = colors[j * 4], gj = colors[j * 4 + 1];
561
+ Uint8 bj = colors[j * 4 + 2], aj = colors[j * 4 + 3];
228
562
 
229
- verts[0].position = (SDL_FPoint){.x = x1 + vec.x, .y = y1 + vec.y};
230
- verts[1].position = (SDL_FPoint){.x = x2 + vec.x, .y = y2 + vec.y};
231
- verts[2].position = (SDL_FPoint){.x = x2 - vec.x, .y = y2 - vec.y};
232
- verts[3].position = (SDL_FPoint){.x = x1 - vec.x, .y = y1 - vec.y};
563
+ canvas_fill_triangle_lerp_on_surface(surface,
564
+ outer[i*2], outer[i*2+1], ri, gi, bi, ai,
565
+ inner[i*2], inner[i*2+1], ri, gi, bi, ai,
566
+ inner[j*2], inner[j*2+1], rj, gj, bj, aj);
233
567
 
234
- // set the vertex colour
235
- for (register int i = 0; i < 4; i++) {
236
- verts[i].color = (SDL_Color){.r = r, .g = g, .b = b, .a = a};
568
+ canvas_fill_triangle_lerp_on_surface(surface,
569
+ outer[i*2], outer[i*2+1], ri, gi, bi, ai,
570
+ inner[j*2], inner[j*2+1], rj, gj, bj, aj,
571
+ outer[j*2], outer[j*2+1], rj, gj, bj, aj);
237
572
  }
238
573
 
239
- // sub-divide the two triangles; we know this is convex
240
- int indices[] = {0, 1, 2, 2, 3, 0};
241
- SDL_RenderGeometry(render, NULL, verts, 4, indices, 6);
574
+ if (heap) {
575
+ free(outer);
576
+ free(inner);
577
+ }
242
578
  }
243
579
 
580
+
244
581
  /*
245
- * Draw a thick rectangle on a canvas using a pre-converted RGBA colour value.
246
- * @param [int] thickness must be > 1, else does nothing
582
+ * Union a just-mutated surface region into the canvas's pending dirty rect so
583
+ * the next ext_draw uploads it to the persistent GPU texture (the texture is
584
+ * never destroyed on mutation — a full re-create + full-surface upload per
585
+ * animated frame is exactly what made canvases expensive, especially on
586
+ * WebGL). Also mark the next tick as needing a rendered frame so the mutation
587
+ * is visible in :on_demand render mode without an explicit request_render
588
+ * call. Tolerates being called before the window is shown (Canvases can be
589
+ * drawn into pre-show); the dirty mark is a no-op in :continuous mode but the
590
+ * atomic write is cheap.
591
+ *
592
+ * Bounds are floats in surface pixel space, any corner order. They are
593
+ * expanded outward by one pixel so the differing edge conventions of the
594
+ * rasterizers (ceil starts, inclusive vs half-open ends) are always covered —
595
+ * a pixel of extra upload is harmless, a missed pixel is a stale texel.
247
596
  */
248
- void R2D_Canvas_DrawThickRect(SDL_Renderer *render, int x, int y, int width,
249
- int height, int thickness, int r, int g, int b,
250
- int a)
251
- {
252
- if (thickness <= 1) {
253
- return;
597
+ static void canvas_mark_dirty(R2D_Canvas *can, float min_x, float min_y,
598
+ float max_x, float max_y) {
599
+ int x0, y0, x1, y1;
600
+
601
+ // Non-finite bounds (NaN/Inf) make the int casts below undefined behavior;
602
+ // fall back to the whole surface rather than guess.
603
+ if (!isfinite(min_x) || !isfinite(min_y) || !isfinite(max_x) || !isfinite(max_y)) {
604
+ x0 = 0; y0 = 0; x1 = can->surface->w; y1 = can->surface->h;
605
+ } else {
606
+ if (min_x > max_x) swap_float(&min_x, &max_x);
607
+ if (min_y > max_y) swap_float(&min_y, &max_y);
608
+ x0 = (int)floorf(min_x) - 1;
609
+ y0 = (int)floorf(min_y) - 1;
610
+ x1 = (int)ceilf(max_x) + 1;
611
+ y1 = (int)ceilf(max_y) + 1;
612
+ if (x0 < 0) x0 = 0;
613
+ if (y0 < 0) y0 = 0;
614
+ if (x1 > can->surface->w) x1 = can->surface->w;
615
+ if (y1 > can->surface->h) y1 = can->surface->h;
616
+ if (x0 >= x1 || y0 >= y1) return; // fully off-surface: nothing uploaded
254
617
  }
255
618
 
256
- float half_thick = thickness / 2.0f;
257
- SDL_Vertex verts[8];
258
-
259
- // all points have the same colour so
260
- verts[0].color = (SDL_Color){.r = r, .g = g, .b = b, .a = a};
261
- for (register int i = 1; i < 8; i++) {
262
- verts[i].color = verts[0].color;
263
- }
264
-
265
- // outer coords
266
- verts[0].position = (SDL_FPoint){.x = x - half_thick, .y = y - half_thick};
267
- verts[1].position =
268
- (SDL_FPoint){.x = x + width + half_thick, .y = y - half_thick};
269
- verts[2].position =
270
- (SDL_FPoint){.x = x + width + half_thick, .y = y + height + half_thick};
271
- verts[3].position =
272
- (SDL_FPoint){.x = x - half_thick, .y = y + height + half_thick};
273
-
274
- // inner coords
275
- verts[4].position = (SDL_FPoint){.x = x + half_thick, .y = y + half_thick};
276
- verts[5].position =
277
- (SDL_FPoint){.x = x + width - half_thick, .y = y + half_thick};
278
- verts[6].position =
279
- (SDL_FPoint){.x = x + width - half_thick, .y = y + height - half_thick};
280
- verts[7].position =
281
- (SDL_FPoint){.x = x + half_thick, .y = y + height - half_thick};
282
-
283
- int indices[] = {
284
- 0, 4, 1, // top outer triangle
285
- 4, 1, 5, // inner
286
- 1, 5, 2, // right outer
287
- 5, 2, 6, // inner
288
- 2, 6, 3, // bottom outer
289
- 6, 3, 7, // inner
290
- 3, 7, 0, // left outer
291
- 7, 0, 4 // inner
292
- };
293
- SDL_RenderGeometry(render, NULL, verts, 8, indices, 24);
619
+ if (can->dirty) {
620
+ int ex1 = can->dirty_rect.x + can->dirty_rect.w;
621
+ int ey1 = can->dirty_rect.y + can->dirty_rect.h;
622
+ if (x0 > can->dirty_rect.x) x0 = can->dirty_rect.x;
623
+ if (y0 > can->dirty_rect.y) y0 = can->dirty_rect.y;
624
+ if (x1 < ex1) x1 = ex1;
625
+ if (y1 < ey1) y1 = ey1;
626
+ } else {
627
+ can->dirty = true;
628
+ }
629
+ can->dirty_rect.x = x0;
630
+ can->dirty_rect.y = y0;
631
+ can->dirty_rect.w = x1 - x0;
632
+ can->dirty_rect.h = y1 - y0;
633
+
634
+ if (R2D_GetWindow()) {
635
+ SDL_SetAtomicInt(&R2D_GetWindow()->render_pending, 1);
636
+ }
294
637
  }
295
638
 
639
+
296
640
  /*
297
- * Draw a thick ellipse on a canvas using a pre-converted RGBA colour value.
298
- * @param [int] thickness must be > 1, else does nothing
641
+ * Mark the entire surface dirty the first draw populates the fresh texture
642
+ * with a full upload of everything drawn pre-show.
299
643
  */
300
- void R2D_Canvas_DrawThickEllipse(SDL_Renderer *render, int x, int y,
301
- float xradius, float yradius, float sectors,
302
- int thickness, int r, int g, int b, int a)
303
- {
304
- if (thickness <= 1) {
305
- return;
644
+ static void canvas_mark_dirty_all(R2D_Canvas *can) {
645
+ canvas_mark_dirty(can, 0.0f, 0.0f,
646
+ (float)can->surface->w, (float)can->surface->h);
647
+ }
648
+
649
+
650
+ /*
651
+ * Mark the bounding box of a vertex list ([x0, y0, x1, y1, ...], surface
652
+ * pixel space) dirty, expanded by `pad` on every side — for strokes, whose
653
+ * geometry extends beyond the vertices (up to half-width * miter limit at a
654
+ * miter join).
655
+ */
656
+ static void canvas_mark_dirty_verts(R2D_Canvas *can, const float *verts,
657
+ int n, float pad) {
658
+ float min_x = verts[0], min_y = verts[1];
659
+ float max_x = verts[0], max_y = verts[1];
660
+ for (int i = 1; i < n; i++) {
661
+ float x = verts[i * 2];
662
+ float y = verts[i * 2 + 1];
663
+ if (x < min_x) min_x = x;
664
+ if (x > max_x) max_x = x;
665
+ if (y < min_y) min_y = y;
666
+ if (y > max_y) max_y = y;
306
667
  }
668
+ canvas_mark_dirty(can, min_x - pad, min_y - pad, max_x + pad, max_y + pad);
669
+ }
307
670
 
308
- // renders a thick circle by treating each segment as a monotonic quad
309
- // and rendering as two triangles per segment
310
- SDL_Vertex verts[4];
311
-
312
- // colour all vertices
313
- verts[3].color = verts[2].color = verts[1].color = verts[0].color =
314
- (SDL_Color){.r = r, .g = g, .b = b, .a = a};
315
- float half_thick = thickness / 2.0f;
316
- SDL_FPoint outer_radius = {.x = xradius + half_thick,
317
- .y = yradius + half_thick};
318
- SDL_FPoint inner_radius = {.x = xradius - half_thick,
319
- .y = yradius - half_thick};
320
- float cache_cosf = cosf(0), cache_sinf = sinf(0);
321
- float unit_angle = 2 * M_PI / sectors;
322
-
323
- int indices[] = {0, 1, 3, 3, 1, 2};
324
-
325
- // point at 0 radians
326
- verts[0].position = (SDL_FPoint){.x = x + outer_radius.x * cache_cosf,
327
- .y = y + outer_radius.y * cache_sinf};
328
- verts[3].position = (SDL_FPoint){.x = x + inner_radius.x * cache_cosf,
329
- .y = y + inner_radius.y * cache_sinf};
330
- for (float i = 1; i <= sectors; i++) {
331
- float angle = i * unit_angle;
332
- // re-use index 0 and 3 from previous calculation
333
- verts[1].position = verts[0].position;
334
- verts[2].position = verts[3].position;
335
- // calculate new index 0 and 3 values
336
- cache_cosf = cosf(angle);
337
- cache_sinf = sinf(angle);
338
- verts[0].position = (SDL_FPoint){.x = x + outer_radius.x * cache_cosf,
339
- .y = y + outer_radius.y * cache_sinf};
340
- verts[3].position = (SDL_FPoint){.x = x + inner_radius.x * cache_cosf,
341
- .y = y + inner_radius.y * cache_sinf};
342
- SDL_RenderGeometry(render, NULL, verts, 4, indices, 6);
343
- }
344
- }
345
-
346
- /*
347
- * Draw a thin (single pixel) ellipse on a canvas using a pre-converted RGBA
348
- * colour value.
349
- */
350
- void R2D_Canvas_DrawThinEllipse(SDL_Renderer *render, int x, int y,
351
- float xradius, float yradius, float sectors,
352
- int r, int g, int b, int a)
353
- {
354
- float unit_angle = 2 * M_PI / sectors;
355
- // point at 0 radians
356
- float x1 = x + xradius * cosf(0);
357
- float y1 = y + yradius * sinf(0);
358
- SDL_SetRenderDrawColor(render, r, g, b, a);
359
- for (float i = 1; i <= sectors; i++) {
360
- float angle = i * unit_angle;
361
- // re-use from previous calculation
362
- float x2 = x1;
363
- float y2 = y1;
364
- // calculate new point
365
- x1 = x + xradius * cosf(angle);
366
- y1 = y + yradius * sinf(angle);
367
- SDL_RenderDrawLine(render, x1, y1, x2, y2);
368
- }
369
- }
370
-
371
- /*
372
- * Compute the intersection between two lines represented by the input line
373
- * segments. Unlike a typical line segment collision detector, this function
374
- * will find a possible intersection point even if that point is not on either
375
- * of the input line "segments". This helps find where two line segments "would"
376
- * intersect if they were long enough, in addition to the case when the segments
377
- * intersect.
378
- * @param line1_p1
379
- * @param line1_p2
380
- * @param line2_p1
381
- * @param line2_p2
382
- * @param intersection Pointer into which to write the point of intersection.
383
- * @return true if an intersection is found, false if the lines are parallel
384
- * (collinear).
385
- */
386
- static bool intersect_two_lines(const vector_t *line1_p1,
387
- const vector_t *line1_p2,
388
- const vector_t *line2_p1,
389
- const vector_t *line2_p2,
390
- vector_t *intersection)
391
- {
392
- vector_t alpha = {.x = line1_p2->x - line1_p1->x,
393
- .y = line1_p2->y - line1_p1->y};
394
- vector_t beta = {.x = line2_p1->x - line2_p2->x,
395
- .y = line2_p1->y - line2_p2->y};
396
- float denom = (alpha.y * beta.x) - (alpha.x * beta.y);
397
-
398
- if (denom == 0)
399
- return false; // collinear
400
-
401
- vector_t theta = {.x = line1_p1->x - line2_p1->x,
402
- .y = line1_p1->y - line2_p1->y};
403
- float alpha_numerator = beta.y * theta.x - (beta.x * theta.y);
404
-
405
- intersection->x = alpha.x;
406
- intersection->y = alpha.y;
407
- vector_times_scalar(intersection, alpha_numerator / denom);
408
- vector_plus_vector(intersection, line1_p1);
409
- return true;
410
- }
411
-
412
- #define POLYLINE_RENDER_NVERTS 6
413
- #define POLYLINE_RENDER_NINDICES 6
414
-
415
- /*
416
- * Draw a thick N-point polyline, with a mitre join where two
417
- * segments are joined.
418
- * @param [int] thickness must be > 1, else does nothing
419
- */
420
- void R2D_Canvas_DrawThickPolyline(SDL_Renderer *render, SDL_FPoint *points,
421
- int num_points, int thickness, int r, int g,
422
- int b, int a, bool skip_first_last)
423
- {
424
- if (thickness <= 1)
425
- return;
426
- if (num_points < 3)
427
- return;
428
671
 
429
- float thick_half = thickness / 2.0f;
672
+ /*
673
+ * Ruby2D::Canvas#ext_fill_triangle
674
+ * Arguments: [x1, y1, x2, y2, x3, y3, r, g, b, a]
675
+ */
676
+ R_VAL ruby2d_ext_canvas_fill_triangle(RUBY2D_METHOD_ARGS_VARIADIC) {
677
+ RUBY2D_EXTRACT_VARIADIC;
678
+ if (argc != 11) r_raise("Ruby2D::Ext.canvas_fill_triangle expects 11 args, got %d", (int)argc);
679
+ R_VAL obj = argv[0];
680
+
681
+ R2D_Canvas *can;
682
+ obj_struct(obj, id_ext_canvas, R2D_Canvas, can);
683
+
684
+ float scale = can->scale;
685
+ float x1 = (float)NUM2DBL(argv[1]) * scale;
686
+ float y1 = (float)NUM2DBL(argv[2]) * scale;
687
+ float x2 = (float)NUM2DBL(argv[3]) * scale;
688
+ float y2 = (float)NUM2DBL(argv[4]) * scale;
689
+ float x3 = (float)NUM2DBL(argv[5]) * scale;
690
+ float y3 = (float)NUM2DBL(argv[6]) * scale;
691
+ float r = (float)NUM2DBL(argv[7]);
692
+ float g = (float)NUM2DBL(argv[8]);
693
+ float b = (float)NUM2DBL(argv[9]);
694
+ float af = (float)NUM2DBL(argv[10]);
695
+
696
+ Uint8 cr = (Uint8)(r * 255);
697
+ Uint8 cg = (Uint8)(g * 255);
698
+ Uint8 cb = (Uint8)(b * 255);
699
+ Uint8 ca = (Uint8)(af * 255);
700
+
701
+ if (ca == 0) return R_TRUE; // fully transparent: skip lock and texture rebuild
702
+
703
+ SDL_LockSurface(can->surface);
704
+ canvas_fill_triangle_on_surface(can->surface, x1, y1, x2, y2, x3, y3, cr, cg, cb, ca);
705
+ SDL_UnlockSurface(can->surface);
706
+
707
+ canvas_mark_dirty(can,
708
+ fminf(x1, fminf(x2, x3)), fminf(y1, fminf(y2, y3)),
709
+ fmaxf(x1, fmaxf(x2, x3)), fmaxf(y1, fmaxf(y2, y3)));
710
+
711
+ return R_TRUE;
712
+ }
713
+
714
+
715
+ /*
716
+ * Ruby2D::Canvas#ext_fill_triangle_lerp
717
+ * Arguments: [x1, y1, r1, g1, b1, a1, x2, y2, r2, g2, b2, a2, x3, y3, r3, g3, b3, a3]
718
+ * Each vertex has its own color; colors are interpolated across the triangle.
719
+ */
720
+ R_VAL ruby2d_ext_canvas_fill_triangle_lerp(RUBY2D_METHOD_ARGS_VARIADIC) {
721
+ RUBY2D_EXTRACT_VARIADIC;
722
+ if (argc != 2) r_raise("Ruby2D::Ext.canvas_fill_triangle_lerp expects 2 args, got %d", (int)argc);
723
+ R_VAL obj = argv[0];
724
+ R_VAL a = argv[1];
725
+
726
+ R2D_Canvas *can;
727
+ obj_struct(obj, id_ext_canvas, R2D_Canvas, can);
728
+
729
+ float scale = can->scale;
730
+
731
+ float x1 = (float)NUM2DBL(r_ary_entry(a, 0)) * scale;
732
+ float y1 = (float)NUM2DBL(r_ary_entry(a, 1)) * scale;
733
+ float r1 = (float)NUM2DBL(r_ary_entry(a, 2));
734
+ float g1 = (float)NUM2DBL(r_ary_entry(a, 3));
735
+ float b1 = (float)NUM2DBL(r_ary_entry(a, 4));
736
+ float a1 = (float)NUM2DBL(r_ary_entry(a, 5));
737
+
738
+ float x2 = (float)NUM2DBL(r_ary_entry(a, 6)) * scale;
739
+ float y2 = (float)NUM2DBL(r_ary_entry(a, 7)) * scale;
740
+ float r2 = (float)NUM2DBL(r_ary_entry(a, 8));
741
+ float g2 = (float)NUM2DBL(r_ary_entry(a, 9));
742
+ float b2 = (float)NUM2DBL(r_ary_entry(a, 10));
743
+ float a2 = (float)NUM2DBL(r_ary_entry(a, 11));
744
+
745
+ float x3 = (float)NUM2DBL(r_ary_entry(a, 12)) * scale;
746
+ float y3 = (float)NUM2DBL(r_ary_entry(a, 13)) * scale;
747
+ float r3 = (float)NUM2DBL(r_ary_entry(a, 14));
748
+ float g3 = (float)NUM2DBL(r_ary_entry(a, 15));
749
+ float b3 = (float)NUM2DBL(r_ary_entry(a, 16));
750
+ float a3 = (float)NUM2DBL(r_ary_entry(a, 17));
751
+
752
+ SDL_LockSurface(can->surface);
753
+ canvas_fill_triangle_lerp_on_surface(can->surface,
754
+ x1, y1, (Uint8)(r1 * 255), (Uint8)(g1 * 255), (Uint8)(b1 * 255), (Uint8)(a1 * 255),
755
+ x2, y2, (Uint8)(r2 * 255), (Uint8)(g2 * 255), (Uint8)(b2 * 255), (Uint8)(a2 * 255),
756
+ x3, y3, (Uint8)(r3 * 255), (Uint8)(g3 * 255), (Uint8)(b3 * 255), (Uint8)(a3 * 255));
757
+ SDL_UnlockSurface(can->surface);
758
+
759
+ canvas_mark_dirty(can,
760
+ fminf(x1, fminf(x2, x3)), fminf(y1, fminf(y2, y3)),
761
+ fmaxf(x1, fmaxf(x2, x3)), fmaxf(y1, fmaxf(y2, y3)));
762
+
763
+ return R_TRUE;
764
+ }
430
765
 
431
- // Prepare to store the different points used map
432
- // the triangles that render the thick polyline
433
- SDL_Vertex verts[POLYLINE_RENDER_NVERTS];
434
766
 
435
- // all points have the same colour so
436
- for (int i = 0; i < POLYLINE_RENDER_NVERTS; i++) {
437
- verts[i].color = (SDL_Color){.r = r, .g = g, .b = b, .a = a};
767
+ /*
768
+ * Fill a rectangle on an SDL_Surface.
769
+ * Coordinates are in surface pixel space (pre-scaled). Color components are 0-255.
770
+ * Opaque fills use SDL_FillSurfaceRect; semi-transparent fills alpha-blend
771
+ * per pixel (surface is locked internally for that path).
772
+ */
773
+ static void canvas_fill_rectangle_on_surface(
774
+ SDL_Surface *surface,
775
+ float fx, float fy, float fw, float fh,
776
+ Uint8 cr, Uint8 cg, Uint8 cb, Uint8 ca) {
777
+
778
+ // A non-finite coordinate or size (NaN/Inf) makes the (int)ceilf casts below
779
+ // undefined behavior and could produce an out-of-bounds rect. Skip the draw.
780
+ if (!isfinite(fx) || !isfinite(fy) || !isfinite(fw) || !isfinite(fh)) return;
781
+
782
+ // Half-open pixel coverage: a rect (x, y, w, h) covers the continuous
783
+ // region [x, x+w) x [y, y+h), so pixel i is filled iff ceil(x) <= i < ceil(x+w).
784
+ // Matches the ceilf convention used by the triangle rasterizer, so
785
+ // fill_rectangle and draw_line share pixel edges for non-integer inputs.
786
+ int rx = (int)ceilf(fx);
787
+ int ry = (int)ceilf(fy);
788
+ int rw = (int)ceilf(fx + fw) - rx;
789
+ int rh = (int)ceilf(fy + fh) - ry;
790
+ SDL_Rect rect = { rx, ry, rw, rh };
791
+
792
+ if (ca == 255) {
793
+ // Opaque — use fast SDL fill
794
+ Uint32 color = SDL_MapSurfaceRGBA(surface, cr, cg, cb, 255);
795
+ SDL_FillSurfaceRect(surface, &rect, color);
796
+ } else if (ca > 0) {
797
+ // Semi-transparent — alpha blend per pixel
798
+ // Clamp rect to surface bounds
799
+ int x_start = rx < 0 ? 0 : rx;
800
+ int y_start = ry < 0 ? 0 : ry;
801
+ int x_end = rx + rw;
802
+ int y_end = ry + rh;
803
+ if (x_end > surface->w) x_end = surface->w;
804
+ if (y_end > surface->h) y_end = surface->h;
805
+
806
+ SDL_LockSurface(surface);
807
+ for (int y = y_start; y < y_end; y++) {
808
+ Uint8 *row = (Uint8 *)surface->pixels + y * surface->pitch;
809
+ for (int x = x_start; x < x_end; x++) {
810
+ Uint8 *pixel = row + x * 4;
811
+ canvas_blend_over(pixel, cr, cg, cb, ca);
812
+ }
813
+ }
814
+ SDL_UnlockSurface(surface);
438
815
  }
816
+ }
439
817
 
440
- // the indices into the vertex, always the same two triangles
441
- const int indices[] = {
442
- 0, 1, 2, // left outer triangle
443
- 0, 2, 3, // left inner triangle
444
- };
445
818
 
446
- // Setup the first segment
447
- int x1 = points[0].x, y1 = points[0].y;
448
- int x2 = points[1].x, y2 = points[1].y;
449
-
450
- // calculate the unit perpendicular for each of the line segments
451
- vector_t vec_one_perp = {.x = (x2 - x1), .y = (y2 - y1)};
452
- vector_times_scalar(vector_normalize(vector_perpendicular(&vec_one_perp)),
453
- thick_half);
454
-
455
- // left outer bottom
456
- verts[0].position =
457
- (SDL_FPoint){.x = x1 + vec_one_perp.x, .y = y1 + vec_one_perp.y};
458
- // left outer top
459
- verts[1].position =
460
- (SDL_FPoint){.x = x2 + vec_one_perp.x, .y = y2 + vec_one_perp.y};
461
- // left inner top
462
- verts[2].position =
463
- (SDL_FPoint){.x = x2 - vec_one_perp.x, .y = y2 - vec_one_perp.y};
464
- // left inner bottom
465
- verts[3].position =
466
- (SDL_FPoint){.x = x1 - vec_one_perp.x, .y = y1 - vec_one_perp.y};
467
-
468
- //
469
- // go through each subsequent point to work with the two segments and
470
- // figure out how they join, and then render the
471
- // left segment.
472
- // then shift the vertices left to work on the next and so on
473
- // until we have one last segment left after the loop is done
474
- for (int pix = 2; pix < num_points; pix++) {
475
-
476
- int x3 = points[pix].x, y3 = points[pix].y;
477
- if (x3 == x2 && y3 == y2)
478
- continue;
479
-
480
- vector_t vec_two_perp = {.x = (x3 - x2), .y = (y3 - y2)};
481
- vector_times_scalar(vector_normalize(vector_perpendicular(&vec_two_perp)),
482
- thick_half);
483
-
484
- // right inner bottom
485
- verts[4].position =
486
- (SDL_FPoint){.x = x3 - vec_two_perp.x, .y = y3 - vec_two_perp.y};
487
- // right outer bottom
488
- verts[5].position =
489
- (SDL_FPoint){.x = x3 + vec_two_perp.x, .y = y3 + vec_two_perp.y};
490
-
491
- // temporarily calculate the "right inner top" to
492
- // figure out the intersection with the "left inner top" which
493
- vector_t tmp = {.x = x2 - vec_two_perp.x, .y = y2 - vec_two_perp.y};
494
- // find intersection of the left/right inner lines
495
- bool has_intersect = intersect_two_lines(
496
- &verts[3].position, &verts[2].position, // left inner line
497
- &verts[4].position, &tmp, // right inner line
498
- &verts[2].position // over-write with intersection
499
- );
500
-
501
- if (has_intersect) {
502
- // not collinear
503
- // adjust the "left outer top" point so that it's distance from (x2, y2)
504
- // is the same as the left/right "inner top" intersection we calculated
505
- // above
506
- tmp = (vector_t){.x = x2, .y = y2};
507
- vector_minus_vector(&tmp, &verts[2].position);
508
- verts[1].position = (SDL_FPoint){.x = x2 + tmp.x, .y = y2 + tmp.y};
819
+ /*
820
+ * Ruby2D::Canvas#ext_fill_rectangle
821
+ * Arguments: [x, y, width, height, r, g, b, a]
822
+ */
823
+ R_VAL ruby2d_ext_canvas_fill_rectangle(RUBY2D_METHOD_ARGS_VARIADIC) {
824
+ RUBY2D_EXTRACT_VARIADIC;
825
+ if (argc != 9) r_raise("Ruby2D::Ext.canvas_fill_rectangle expects 9 args, got %d", (int)argc);
826
+ R_VAL obj = argv[0];
827
+
828
+ R2D_Canvas *can;
829
+ obj_struct(obj, id_ext_canvas, R2D_Canvas, can);
830
+
831
+ float scale = can->scale;
832
+ float fx = (float)NUM2DBL(argv[1]) * scale;
833
+ float fy = (float)NUM2DBL(argv[2]) * scale;
834
+ float fw = (float)NUM2DBL(argv[3]) * scale;
835
+ float fh = (float)NUM2DBL(argv[4]) * scale;
836
+ float rf = (float)NUM2DBL(argv[5]);
837
+ float gf = (float)NUM2DBL(argv[6]);
838
+ float bf = (float)NUM2DBL(argv[7]);
839
+ float af = (float)NUM2DBL(argv[8]);
840
+
841
+ Uint8 cr = (Uint8)(rf * 255);
842
+ Uint8 cg = (Uint8)(gf * 255);
843
+ Uint8 cb = (Uint8)(bf * 255);
844
+ Uint8 ca = (Uint8)(af * 255);
845
+
846
+ if (ca == 0) return R_TRUE; // fully transparent: skip fill and texture rebuild
847
+
848
+ canvas_fill_rectangle_on_surface(can->surface, fx, fy, fw, fh, cr, cg, cb, ca);
849
+ canvas_mark_dirty(can, fx, fy, fx + fw, fy + fh);
850
+
851
+ return R_TRUE;
852
+ }
853
+
854
+
855
+ /*
856
+ * Ruby2D::Canvas#ext_fill_rectangles
857
+ * Arguments: [n, x1, y1, w1, h1, x2, y2, w2, h2, ..., r, g, b, a]
858
+ * Fills n rectangles with a single shared color in one FFI crossing.
859
+ */
860
+ R_VAL ruby2d_ext_canvas_fill_rectangles(RUBY2D_METHOD_ARGS_VARIADIC) {
861
+ RUBY2D_EXTRACT_VARIADIC;
862
+ if (argc != 2) r_raise("Ruby2D::Ext.canvas_fill_rectangles expects 2 args, got %d", (int)argc);
863
+ R_VAL obj = argv[0];
864
+ R_VAL a = argv[1];
865
+
866
+ R2D_Canvas *can;
867
+ obj_struct(obj, id_ext_canvas, R2D_Canvas, can);
868
+
869
+ if ((int)r_ary_len(a) < 1) return R_TRUE;
870
+ int n = (int)NUM2INT(r_ary_entry(a, 0));
871
+ if (n < 1) return R_TRUE;
872
+
873
+ // Guard the array length: [count] + n*[x,y,w,h] + [r,g,b,a]. A short array
874
+ // would make r_ary_entry return nil and NUM2DBL(nil) error (mruby aborts).
875
+ long need = 5L + (long)n * 4;
876
+ if ((long)r_ary_len(a) < need)
877
+ r_raise("Ruby2D::Ext.canvas_fill_rectangles: array has %d values for %d rects (need %ld)",
878
+ (int)r_ary_len(a), n, need);
879
+
880
+ int base = 1 + n * 4;
881
+ float rf = (float)NUM2DBL(r_ary_entry(a, base));
882
+ float gf = (float)NUM2DBL(r_ary_entry(a, base + 1));
883
+ float bf = (float)NUM2DBL(r_ary_entry(a, base + 2));
884
+ float af = (float)NUM2DBL(r_ary_entry(a, base + 3));
885
+
886
+ Uint8 cr = (Uint8)(rf * 255);
887
+ Uint8 cg = (Uint8)(gf * 255);
888
+ Uint8 cb = (Uint8)(bf * 255);
889
+ Uint8 ca = (Uint8)(af * 255);
890
+
891
+ if (ca == 0) return R_TRUE;
892
+
893
+ float scale = can->scale;
894
+ for (int i = 0; i < n; i++) {
895
+ int o = 1 + i * 4;
896
+ float fx = (float)NUM2DBL(r_ary_entry(a, o)) * scale;
897
+ float fy = (float)NUM2DBL(r_ary_entry(a, o + 1)) * scale;
898
+ float fw = (float)NUM2DBL(r_ary_entry(a, o + 2)) * scale;
899
+ float fh = (float)NUM2DBL(r_ary_entry(a, o + 3)) * scale;
900
+ canvas_fill_rectangle_on_surface(can->surface, fx, fy, fw, fh, cr, cg, cb, ca);
901
+ canvas_mark_dirty(can, fx, fy, fx + fw, fy + fh);
902
+ }
903
+
904
+ return R_TRUE;
905
+ }
906
+
907
+
908
+ /*
909
+ * Ruby2D::Canvas#ext_fill_pixel_grid
910
+ * Arguments:
911
+ * header = [cols, rows, cell_w, cell_h, ox, oy]
912
+ * colors = flat array of cols*rows*4 floats in 0..1
913
+ * (r0, g0, b0, a0, r1, g1, b1, a1, ...)
914
+ * Fills a regular cols x rows grid of equal-size rectangles, each with its
915
+ * own RGBA color. Cell (col, row) is the rect at
916
+ * (ox + col*cell_w, oy + row*cell_h, cell_w, cell_h), colored by
917
+ * colors[(row*cols + col)*4 .. +3]. Single FFI crossing for the whole grid:
918
+ * one surface lock, one texture invalidate, cells with a==0 skipped.
919
+ */
920
+ R_VAL ruby2d_ext_canvas_fill_pixel_grid(RUBY2D_METHOD_ARGS_VARIADIC) {
921
+ RUBY2D_EXTRACT_VARIADIC;
922
+ if (argc != 3) r_raise("Ruby2D::Ext.canvas_fill_pixel_grid expects 3 args, got %d", (int)argc);
923
+ R_VAL obj = argv[0];
924
+ R_VAL header = argv[1];
925
+ R_VAL colors = argv[2];
926
+
927
+ R2D_Canvas *can;
928
+ obj_struct(obj, id_ext_canvas, R2D_Canvas, can);
929
+
930
+ // Guard array lengths before the read loops: a short array would make
931
+ // r_ary_entry return nil and NUM2DBL/NUM2INT(nil) error (hard-abort on mruby).
932
+ if ((int)r_ary_len(header) < 6)
933
+ r_raise("Ruby2D::Ext.canvas_fill_pixel_grid: header needs 6 values, got %d",
934
+ (int)r_ary_len(header));
935
+
936
+ int cols = (int)NUM2INT(r_ary_entry(header, 0));
937
+ int rows = (int)NUM2INT(r_ary_entry(header, 1));
938
+ if (cols < 1 || rows < 1) return R_TRUE;
939
+
940
+ long need = (long)cols * rows * 4;
941
+ if ((long)r_ary_len(colors) < need)
942
+ r_raise("Ruby2D::Ext.canvas_fill_pixel_grid: %d color values for a %dx%d grid (need %ld)",
943
+ (int)r_ary_len(colors), cols, rows, need);
944
+
945
+ float scale = can->scale;
946
+ float cell_w = (float)NUM2DBL(r_ary_entry(header, 2)) * scale;
947
+ float cell_h = (float)NUM2DBL(r_ary_entry(header, 3)) * scale;
948
+ float ox = (float)NUM2DBL(r_ary_entry(header, 4)) * scale;
949
+ float oy = (float)NUM2DBL(r_ary_entry(header, 5)) * scale;
950
+
951
+ // A non-finite cell size or origin (NaN/Inf) would make the per-cell
952
+ // (int)ceilf casts below undefined behavior; every cell derives from these
953
+ // four values, so skip the whole grid.
954
+ if (!isfinite(cell_w) || !isfinite(cell_h) || !isfinite(ox) || !isfinite(oy))
955
+ return R_TRUE;
956
+
957
+ SDL_Surface *surface = can->surface;
958
+
959
+ SDL_LockSurface(surface);
960
+ for (int gr = 0; gr < rows; gr++) {
961
+ for (int gc = 0; gc < cols; gc++) {
962
+ int o = (gr * cols + gc) * 4;
963
+ // Clamp channels to [0,1] before the 8-bit cast. This is the one
964
+ // user-facing color path that builds no Color object, so it has no
965
+ // Ruby-side clamp; an out-of-range value would wrap (1.5 -> 126) rather
966
+ // than saturate.
967
+ float af = (float)NUM2DBL(r_ary_entry(colors, o + 3));
968
+ if (af < 0.0f) af = 0.0f; else if (af > 1.0f) af = 1.0f;
969
+ Uint8 ca = (Uint8)(af * 255);
970
+ if (ca == 0) continue;
971
+
972
+ float rf = (float)NUM2DBL(r_ary_entry(colors, o));
973
+ float gf = (float)NUM2DBL(r_ary_entry(colors, o + 1));
974
+ float bf = (float)NUM2DBL(r_ary_entry(colors, o + 2));
975
+ if (rf < 0.0f) rf = 0.0f; else if (rf > 1.0f) rf = 1.0f;
976
+ if (gf < 0.0f) gf = 0.0f; else if (gf > 1.0f) gf = 1.0f;
977
+ if (bf < 0.0f) bf = 0.0f; else if (bf > 1.0f) bf = 1.0f;
978
+ Uint8 cr = (Uint8)(rf * 255);
979
+ Uint8 cg = (Uint8)(gf * 255);
980
+ Uint8 cb = (Uint8)(bf * 255);
981
+
982
+ // Half-open pixel coverage, matching canvas_fill_rectangle_on_surface
983
+ float fx = ox + gc * cell_w;
984
+ float fy = oy + gr * cell_h;
985
+ int rx = (int)ceilf(fx);
986
+ int ry = (int)ceilf(fy);
987
+ int rw = (int)ceilf(fx + cell_w) - rx;
988
+ int rh = (int)ceilf(fy + cell_h) - ry;
989
+
990
+ int x_start = rx < 0 ? 0 : rx;
991
+ int y_start = ry < 0 ? 0 : ry;
992
+ int x_end = rx + rw;
993
+ int y_end = ry + rh;
994
+ if (x_end > surface->w) x_end = surface->w;
995
+ if (y_end > surface->h) y_end = surface->h;
996
+
997
+ if (ca == 255) {
998
+ // Color is constant within the cell: pack once, store 32 bits per pixel.
999
+ Uint32 packed = SDL_MapSurfaceRGBA(surface, cr, cg, cb, 255);
1000
+ for (int y = y_start; y < y_end; y++) {
1001
+ Uint8 *row = (Uint8 *)surface->pixels + y * surface->pitch;
1002
+ for (int x = x_start; x < x_end; x++) {
1003
+ Uint8 *pixel = row + x * 4;
1004
+ SDL_memcpy(pixel, &packed, 4);
1005
+ }
1006
+ }
1007
+ } else {
1008
+ for (int y = y_start; y < y_end; y++) {
1009
+ Uint8 *row = (Uint8 *)surface->pixels + y * surface->pitch;
1010
+ for (int x = x_start; x < x_end; x++) {
1011
+ Uint8 *pixel = row + x * 4;
1012
+ canvas_blend_over(pixel, cr, cg, cb, ca);
1013
+ }
1014
+ }
1015
+ }
509
1016
  }
510
- //
511
- // TODO: handle degenerate case that can result in crazy long mitre join
512
- // point
513
- //
514
- if (pix > 2 || !skip_first_last) {
515
- // we only render the "left" segment of this particular segment pair
516
- SDL_RenderGeometry(render, NULL, verts, POLYLINE_RENDER_NVERTS, indices,
517
- POLYLINE_RENDER_NINDICES);
1017
+ }
1018
+ SDL_UnlockSurface(surface);
1019
+
1020
+ canvas_mark_dirty(can, ox, oy, ox + cols * cell_w, oy + rows * cell_h);
1021
+
1022
+ return R_TRUE;
1023
+ }
1024
+
1025
+
1026
+ /*
1027
+ * Ruby2D::Canvas#ext_fill_ellipse
1028
+ * Arguments: [x, y, xradius, yradius, r, g, b, a]
1029
+ */
1030
+ R_VAL ruby2d_ext_canvas_fill_ellipse(RUBY2D_METHOD_ARGS_VARIADIC) {
1031
+ RUBY2D_EXTRACT_VARIADIC;
1032
+ if (argc != 9) r_raise("Ruby2D::Ext.canvas_fill_ellipse expects 9 args, got %d", (int)argc);
1033
+ R_VAL obj = argv[0];
1034
+
1035
+ R2D_Canvas *can;
1036
+ obj_struct(obj, id_ext_canvas, R2D_Canvas, can);
1037
+
1038
+ float scale = can->scale;
1039
+ float cx = (float)NUM2DBL(argv[1]) * scale;
1040
+ float cy = (float)NUM2DBL(argv[2]) * scale;
1041
+ float xradius = (float)NUM2DBL(argv[3]) * scale;
1042
+ float yradius = (float)NUM2DBL(argv[4]) * scale;
1043
+ float rf = (float)NUM2DBL(argv[5]);
1044
+ float gf = (float)NUM2DBL(argv[6]);
1045
+ float bf = (float)NUM2DBL(argv[7]);
1046
+ float af = (float)NUM2DBL(argv[8]);
1047
+
1048
+ Uint8 cr = (Uint8)(rf * 255);
1049
+ Uint8 cg = (Uint8)(gf * 255);
1050
+ Uint8 cb = (Uint8)(bf * 255);
1051
+ Uint8 ca = (Uint8)(af * 255);
1052
+
1053
+ if (ca == 0) return R_TRUE;
1054
+
1055
+ // A non-finite center or radius (NaN/Inf) would make the (int)ceilf/floorf
1056
+ // casts below undefined behavior (and `radius <= 0` does not catch NaN, since
1057
+ // every NaN comparison is false). Skip the draw before the scanline math.
1058
+ if (!isfinite(cx) || !isfinite(cy) || !isfinite(xradius) || !isfinite(yradius))
1059
+ return R_TRUE;
1060
+
1061
+ // A zero (or negative) radius has no area to fill. Guard before the scanline
1062
+ // math below: yradius == 0 makes yr2 == 0, so (dy * dy) / yr2 is 0.0 / 0.0 ==
1063
+ // NaN, and converting the resulting NaN pixel bounds to int is undefined
1064
+ // behavior that produces an out-of-bounds heap write.
1065
+ if (xradius <= 0.0f || yradius <= 0.0f) return R_TRUE;
1066
+
1067
+ SDL_Surface *surface = can->surface;
1068
+ float yr2 = yradius * yradius;
1069
+
1070
+ int y_start = (int)ceilf(cy - yradius);
1071
+ int y_end = (int)floorf(cy + yradius);
1072
+ if (y_start < 0) y_start = 0;
1073
+ if (y_end >= surface->h) y_end = surface->h - 1;
1074
+
1075
+ // The fill color is constant across the ellipse, so pack it once and write
1076
+ // opaque pixels with a single 32-bit store instead of four byte stores.
1077
+ Uint32 packed = SDL_MapSurfaceRGBA(surface, cr, cg, cb, 255);
1078
+
1079
+ SDL_LockSurface(surface);
1080
+ for (int y = y_start; y <= y_end; y++) {
1081
+ float dy = (float)y - cy;
1082
+ // Boundary-row float rounding can push (dy*dy)/yr2 just above 1.0, making
1083
+ // the radicand negative; sqrtf would yield NaN and (int)ceilf(NaN) is UB.
1084
+ float t = 1.0f - (dy * dy) / yr2;
1085
+ if (t < 0.0f) t = 0.0f;
1086
+ float dx = xradius * sqrtf(t);
1087
+ int x_start = (int)ceilf(cx - dx);
1088
+ int x_end = (int)floorf(cx + dx);
1089
+ if (x_start < 0) x_start = 0;
1090
+ if (x_end >= surface->w) x_end = surface->w - 1;
1091
+
1092
+ Uint8 *row = (Uint8 *)surface->pixels + y * surface->pitch;
1093
+ for (int x = x_start; x <= x_end; x++) {
1094
+ Uint8 *pixel = row + x * 4;
1095
+ if (ca == 255) {
1096
+ SDL_memcpy(pixel, &packed, 4);
1097
+ } else {
1098
+ canvas_blend_over(pixel, cr, cg, cb, ca);
1099
+ }
1100
+ }
1101
+ }
1102
+ SDL_UnlockSurface(surface);
1103
+
1104
+ canvas_mark_dirty(can, cx - xradius, cy - yradius, cx + xradius, cy + yradius);
1105
+
1106
+ return R_TRUE;
1107
+ }
1108
+
1109
+
1110
+ /*
1111
+ * Ruby2D::Canvas#ext_fill_polygon
1112
+ * Arguments: [n, x1, y1, x2, y2, ..., r, g, b, a]
1113
+ * Uses ear-clipping triangulation (R2D_TriangulatePolygon) so concave
1114
+ * polygons fill correctly.
1115
+ */
1116
+ R_VAL ruby2d_ext_canvas_fill_polygon(RUBY2D_METHOD_ARGS_VARIADIC) {
1117
+ RUBY2D_EXTRACT_VARIADIC;
1118
+ if (argc != 2) r_raise("Ruby2D::Ext.canvas_fill_polygon expects 2 args, got %d", (int)argc);
1119
+ R_VAL obj = argv[0];
1120
+ R_VAL a = argv[1];
1121
+
1122
+ R2D_Canvas *can;
1123
+ obj_struct(obj, id_ext_canvas, R2D_Canvas, can);
1124
+
1125
+ if ((int)r_ary_len(a) < 1) return R_TRUE;
1126
+ int n = (int)NUM2INT(r_ary_entry(a, 0));
1127
+ if (n < 3) return R_TRUE;
1128
+
1129
+ // [n, x1, y1, ... xn, yn, r, g, b, a] — guard before indexing (a short array
1130
+ // would hard-abort under mruby), matching the sibling fill_* methods.
1131
+ long need = 1 + (long)n * 2 + 4;
1132
+ if ((long)r_ary_len(a) < need)
1133
+ r_raise("Ruby2D::Ext.canvas_fill_polygon: array has %d values for %d vertices (need %ld)",
1134
+ (int)r_ary_len(a), n, need);
1135
+
1136
+ float scale = can->scale;
1137
+ float *verts = (float *)malloc(n * 2 * sizeof(float));
1138
+ if (!verts) return R_TRUE;
1139
+
1140
+ for (int i = 0; i < n; i++) {
1141
+ verts[i*2] = (float)NUM2DBL(r_ary_entry(a, 1 + i*2)) * scale;
1142
+ verts[i*2+1] = (float)NUM2DBL(r_ary_entry(a, 2 + i*2)) * scale;
1143
+ }
1144
+
1145
+ int base = 1 + n * 2;
1146
+ float rf = (float)NUM2DBL(r_ary_entry(a, base));
1147
+ float gf = (float)NUM2DBL(r_ary_entry(a, base + 1));
1148
+ float bf = (float)NUM2DBL(r_ary_entry(a, base + 2));
1149
+ float af = (float)NUM2DBL(r_ary_entry(a, base + 3));
1150
+
1151
+ Uint8 cr = (Uint8)(rf * 255);
1152
+ Uint8 cg = (Uint8)(gf * 255);
1153
+ Uint8 cb = (Uint8)(bf * 255);
1154
+ Uint8 ca = (Uint8)(af * 255);
1155
+
1156
+ if (ca > 0) {
1157
+ int tri_count = 0;
1158
+ int *indices = R2D_TriangulatePolygon(verts, n, &tri_count);
1159
+ if (indices && tri_count > 0) {
1160
+ SDL_LockSurface(can->surface);
1161
+ for (int t = 0; t < tri_count; t++) {
1162
+ int i1 = indices[t * 3];
1163
+ int i2 = indices[t * 3 + 1];
1164
+ int i3 = indices[t * 3 + 2];
1165
+ canvas_fill_triangle_on_surface(can->surface,
1166
+ verts[i1*2], verts[i1*2+1],
1167
+ verts[i2*2], verts[i2*2+1],
1168
+ verts[i3*2], verts[i3*2+1],
1169
+ cr, cg, cb, ca);
1170
+ }
1171
+ SDL_UnlockSurface(can->surface);
1172
+ canvas_mark_dirty_verts(can, verts, n, 0.0f);
1173
+ }
1174
+ free(indices);
1175
+ }
1176
+
1177
+ free(verts);
1178
+ return R_TRUE;
1179
+ }
1180
+
1181
+
1182
+ /*
1183
+ * Ruby2D::Canvas#ext_fill_polygon_lerp
1184
+ * Arguments: [n, x1, y1, r1, g1, b1, a1, x2, y2, r2, g2, b2, a2, ...]
1185
+ * Per-vertex colors. Triangulated via ear clipping; each emitted triangle
1186
+ * is rasterized with barycentric color interpolation between its three
1187
+ * source vertices' colors.
1188
+ */
1189
+ R_VAL ruby2d_ext_canvas_fill_polygon_lerp(RUBY2D_METHOD_ARGS_VARIADIC) {
1190
+ RUBY2D_EXTRACT_VARIADIC;
1191
+ if (argc != 2) r_raise("Ruby2D::Ext.canvas_fill_polygon_lerp expects 2 args, got %d", (int)argc);
1192
+ R_VAL obj = argv[0];
1193
+ R_VAL a = argv[1];
1194
+
1195
+ R2D_Canvas *can;
1196
+ obj_struct(obj, id_ext_canvas, R2D_Canvas, can);
1197
+
1198
+ if ((int)r_ary_len(a) < 1) return R_TRUE;
1199
+ int n = (int)NUM2INT(r_ary_entry(a, 0));
1200
+ if (n < 3) return R_TRUE;
1201
+
1202
+ // [n, x1, y1, r1, g1, b1, a1, ...] — 6 entries per vertex. Guard before
1203
+ // indexing (a short array would hard-abort under mruby).
1204
+ long need = 1 + (long)n * 6;
1205
+ if ((long)r_ary_len(a) < need)
1206
+ r_raise("Ruby2D::Ext.canvas_fill_polygon_lerp: array has %d values for %d vertices (need %ld)",
1207
+ (int)r_ary_len(a), n, need);
1208
+
1209
+ float scale = can->scale;
1210
+ float *verts = (float *)malloc(n * 2 * sizeof(float));
1211
+ Uint8 *colors = (Uint8 *)malloc(n * 4 * sizeof(Uint8));
1212
+ if (!verts || !colors) { free(verts); free(colors); return R_TRUE; }
1213
+
1214
+ // Each vertex packs as (x, y, r, g, b, a) — 6 entries.
1215
+ for (int i = 0; i < n; i++) {
1216
+ int o = 1 + i * 6;
1217
+ verts[i*2] = (float)NUM2DBL(r_ary_entry(a, o)) * scale;
1218
+ verts[i*2+1] = (float)NUM2DBL(r_ary_entry(a, o + 1)) * scale;
1219
+ colors[i*4] = (Uint8)((float)NUM2DBL(r_ary_entry(a, o + 2)) * 255);
1220
+ colors[i*4+1] = (Uint8)((float)NUM2DBL(r_ary_entry(a, o + 3)) * 255);
1221
+ colors[i*4+2] = (Uint8)((float)NUM2DBL(r_ary_entry(a, o + 4)) * 255);
1222
+ colors[i*4+3] = (Uint8)((float)NUM2DBL(r_ary_entry(a, o + 5)) * 255);
1223
+ }
1224
+
1225
+ int tri_count = 0;
1226
+ int *indices = R2D_TriangulatePolygon(verts, n, &tri_count);
1227
+ if (indices && tri_count > 0) {
1228
+ SDL_LockSurface(can->surface);
1229
+ for (int t = 0; t < tri_count; t++) {
1230
+ int i1 = indices[t * 3];
1231
+ int i2 = indices[t * 3 + 1];
1232
+ int i3 = indices[t * 3 + 2];
1233
+ canvas_fill_triangle_lerp_on_surface(can->surface,
1234
+ verts[i1*2], verts[i1*2+1],
1235
+ colors[i1*4], colors[i1*4+1], colors[i1*4+2], colors[i1*4+3],
1236
+ verts[i2*2], verts[i2*2+1],
1237
+ colors[i2*4], colors[i2*4+1], colors[i2*4+2], colors[i2*4+3],
1238
+ verts[i3*2], verts[i3*2+1],
1239
+ colors[i3*4], colors[i3*4+1], colors[i3*4+2], colors[i3*4+3]);
518
1240
  }
1241
+ SDL_UnlockSurface(can->surface);
1242
+ canvas_mark_dirty_verts(can, verts, n, 0.0f);
1243
+ }
1244
+ free(indices);
1245
+
1246
+ free(verts);
1247
+ free(colors);
1248
+ return R_TRUE;
1249
+ }
1250
+
1251
+
1252
+ /*
1253
+ * Ruby2D::Canvas#ext_draw_line
1254
+ * Arguments: [x1, y1, x2, y2, stroke_width, r, g, b, a, dash, gap]
1255
+ * When dash > 0, draws a dashed line; otherwise a solid line.
1256
+ */
1257
+ R_VAL ruby2d_ext_canvas_draw_line(RUBY2D_METHOD_ARGS_VARIADIC) {
1258
+ RUBY2D_EXTRACT_VARIADIC;
1259
+ if (argc != 12) r_raise("Ruby2D::Ext.canvas_draw_line expects 12 args, got %d", (int)argc);
1260
+ R_VAL obj = argv[0];
1261
+
1262
+ R2D_Canvas *can;
1263
+ obj_struct(obj, id_ext_canvas, R2D_Canvas, can);
1264
+
1265
+ float scale = can->scale;
1266
+ float x1 = (float)NUM2DBL(argv[1]) * scale;
1267
+ float y1 = (float)NUM2DBL(argv[2]) * scale;
1268
+ float x2 = (float)NUM2DBL(argv[3]) * scale;
1269
+ float y2 = (float)NUM2DBL(argv[4]) * scale;
1270
+ float sw = (float)NUM2DBL(argv[5]) * scale;
1271
+ float rf = (float)NUM2DBL(argv[6]);
1272
+ float gf = (float)NUM2DBL(argv[7]);
1273
+ float bf = (float)NUM2DBL(argv[8]);
1274
+ float af = (float)NUM2DBL(argv[9]);
1275
+ float dash = (float)NUM2DBL(argv[10]) * scale;
1276
+ float gap = (float)NUM2DBL(argv[11]) * scale;
1277
+
1278
+ Uint8 cr = (Uint8)(rf * 255);
1279
+ Uint8 cg = (Uint8)(gf * 255);
1280
+ Uint8 cb = (Uint8)(bf * 255);
1281
+ Uint8 ca = (Uint8)(af * 255);
1282
+
1283
+ if (ca == 0) return R_TRUE;
1284
+
1285
+ SDL_LockSurface(can->surface);
1286
+
1287
+ if (dash > 0.0f) {
1288
+ if (dash < 0.5f) dash = 0.5f;
1289
+ if (gap < 0.5f) gap = 0.5f;
1290
+
1291
+ float dx = x2 - x1;
1292
+ float dy = y2 - y1;
1293
+ float len = sqrtf(dx * dx + dy * dy);
1294
+ if (len >= 0.0001f) {
1295
+ float ux = dx / len;
1296
+ float uy = dy / len;
1297
+
1298
+ float t = 0.0f;
1299
+ int drawing = 1;
1300
+ while (t < len) {
1301
+ float seg = drawing ? dash : gap;
1302
+ if (t + seg > len) seg = len - t;
1303
+
1304
+ if (drawing) {
1305
+ float sx1 = x1 + ux * t;
1306
+ float sy1 = y1 + uy * t;
1307
+ float sx2 = x1 + ux * (t + seg);
1308
+ float sy2 = y1 + uy * (t + seg);
1309
+ canvas_draw_line_on_surface(can->surface, sx1, sy1, sx2, sy2, sw, cr, cg, cb, ca);
1310
+ }
1311
+
1312
+ t += seg;
1313
+ drawing = !drawing;
1314
+ }
1315
+ }
1316
+ } else {
1317
+ canvas_draw_line_on_surface(can->surface, x1, y1, x2, y2, sw, cr, cg, cb, ca);
1318
+ }
1319
+
1320
+ SDL_UnlockSurface(can->surface);
1321
+ {
1322
+ float hw = sw * 0.5f;
1323
+ canvas_mark_dirty(can, fminf(x1, x2) - hw, fminf(y1, y2) - hw,
1324
+ fmaxf(x1, x2) + hw, fmaxf(y1, y2) + hw);
1325
+ }
1326
+
1327
+ return R_TRUE;
1328
+ }
1329
+
1330
+
1331
+ /*
1332
+ * Ruby2D::Canvas#ext_draw_line_lerp
1333
+ * Arguments: [x1, y1, x2, y2, stroke_width,
1334
+ * r1, g1, b1, a1, r2, g2, b2, a2,
1335
+ * r3, g3, b3, a3, r4, g4, b4, a4,
1336
+ * dash, gap]
1337
+ * 4 per-corner colors mirror R2D_DrawLine's vertex layout
1338
+ * (c1=start+perp, c2=start-perp, c3=end-perp, c4=end+perp). When dash > 0,
1339
+ * draws a dashed line and interpolates endpoint colors along the length.
1340
+ */
1341
+ R_VAL ruby2d_ext_canvas_draw_line_lerp(RUBY2D_METHOD_ARGS_VARIADIC) {
1342
+ RUBY2D_EXTRACT_VARIADIC;
1343
+ if (argc != 2) r_raise("Ruby2D::Ext.canvas_draw_line_lerp expects 2 args, got %d", (int)argc);
1344
+ R_VAL obj = argv[0];
1345
+ R_VAL a = argv[1];
1346
+
1347
+ R2D_Canvas *can;
1348
+ obj_struct(obj, id_ext_canvas, R2D_Canvas, can);
1349
+
1350
+ float scale = can->scale;
1351
+ float x1 = (float)NUM2DBL(r_ary_entry(a, 0)) * scale;
1352
+ float y1 = (float)NUM2DBL(r_ary_entry(a, 1)) * scale;
1353
+ float x2 = (float)NUM2DBL(r_ary_entry(a, 2)) * scale;
1354
+ float y2 = (float)NUM2DBL(r_ary_entry(a, 3)) * scale;
1355
+ float sw = (float)NUM2DBL(r_ary_entry(a, 4)) * scale;
1356
+
1357
+ Uint8 r1 = (Uint8)(NUM2DBL(r_ary_entry(a, 5)) * 255);
1358
+ Uint8 g1 = (Uint8)(NUM2DBL(r_ary_entry(a, 6)) * 255);
1359
+ Uint8 b1 = (Uint8)(NUM2DBL(r_ary_entry(a, 7)) * 255);
1360
+ Uint8 a1 = (Uint8)(NUM2DBL(r_ary_entry(a, 8)) * 255);
1361
+ Uint8 r2 = (Uint8)(NUM2DBL(r_ary_entry(a, 9)) * 255);
1362
+ Uint8 g2 = (Uint8)(NUM2DBL(r_ary_entry(a, 10)) * 255);
1363
+ Uint8 b2 = (Uint8)(NUM2DBL(r_ary_entry(a, 11)) * 255);
1364
+ Uint8 a2 = (Uint8)(NUM2DBL(r_ary_entry(a, 12)) * 255);
1365
+ Uint8 r3 = (Uint8)(NUM2DBL(r_ary_entry(a, 13)) * 255);
1366
+ Uint8 g3 = (Uint8)(NUM2DBL(r_ary_entry(a, 14)) * 255);
1367
+ Uint8 b3 = (Uint8)(NUM2DBL(r_ary_entry(a, 15)) * 255);
1368
+ Uint8 a3 = (Uint8)(NUM2DBL(r_ary_entry(a, 16)) * 255);
1369
+ Uint8 r4 = (Uint8)(NUM2DBL(r_ary_entry(a, 17)) * 255);
1370
+ Uint8 g4 = (Uint8)(NUM2DBL(r_ary_entry(a, 18)) * 255);
1371
+ Uint8 b4 = (Uint8)(NUM2DBL(r_ary_entry(a, 19)) * 255);
1372
+ Uint8 a4 = (Uint8)(NUM2DBL(r_ary_entry(a, 20)) * 255);
1373
+
1374
+ float dash = (float)NUM2DBL(r_ary_entry(a, 21)) * scale;
1375
+ float gap = (float)NUM2DBL(r_ary_entry(a, 22)) * scale;
1376
+
1377
+ SDL_LockSurface(can->surface);
1378
+
1379
+ if (dash > 0.0f) {
1380
+ if (dash < 0.5f) dash = 0.5f;
1381
+ if (gap < 0.5f) gap = 0.5f;
1382
+
1383
+ float dx = x2 - x1;
1384
+ float dy = y2 - y1;
1385
+ float len = sqrtf(dx * dx + dy * dy);
1386
+ if (len >= 0.0001f) {
1387
+ float ux = dx / len;
1388
+ float uy = dy / len;
1389
+
1390
+ float t = 0.0f;
1391
+ int drawing = 1;
1392
+ while (t < len) {
1393
+ float seg = drawing ? dash : gap;
1394
+ if (t + seg > len) seg = len - t;
1395
+
1396
+ if (drawing) {
1397
+ float t1 = t / len;
1398
+ float t2 = (t + seg) / len;
1399
+
1400
+ // Top edge (c1->c4) and bottom edge (c2->c3) interpolated at t1,t2
1401
+ Uint8 sr1 = lerp_u8(r1, r4, t1), sg1 = lerp_u8(g1, g4, t1);
1402
+ Uint8 sb1 = lerp_u8(b1, b4, t1), sa1 = lerp_u8(a1, a4, t1);
1403
+ Uint8 er1 = lerp_u8(r1, r4, t2), eg1 = lerp_u8(g1, g4, t2);
1404
+ Uint8 eb1 = lerp_u8(b1, b4, t2), ea1 = lerp_u8(a1, a4, t2);
1405
+
1406
+ Uint8 sr2 = lerp_u8(r2, r3, t1), sg2 = lerp_u8(g2, g3, t1);
1407
+ Uint8 sb2 = lerp_u8(b2, b3, t1), sa2 = lerp_u8(a2, a3, t1);
1408
+ Uint8 er2 = lerp_u8(r2, r3, t2), eg2 = lerp_u8(g2, g3, t2);
1409
+ Uint8 eb2 = lerp_u8(b2, b3, t2), ea2 = lerp_u8(a2, a3, t2);
1410
+
1411
+ float sx1 = x1 + ux * t, sy1 = y1 + uy * t;
1412
+ float sx2 = x1 + ux * (t + seg), sy2 = y1 + uy * (t + seg);
1413
+
1414
+ canvas_draw_line_lerp_on_surface(can->surface, sx1, sy1, sx2, sy2, sw,
1415
+ sr1, sg1, sb1, sa1,
1416
+ sr2, sg2, sb2, sa2,
1417
+ er2, eg2, eb2, ea2,
1418
+ er1, eg1, eb1, ea1);
1419
+ }
1420
+
1421
+ t += seg;
1422
+ drawing = !drawing;
1423
+ }
1424
+ }
1425
+ } else {
1426
+ canvas_draw_line_lerp_on_surface(can->surface, x1, y1, x2, y2, sw,
1427
+ r1, g1, b1, a1,
1428
+ r2, g2, b2, a2,
1429
+ r3, g3, b3, a3,
1430
+ r4, g4, b4, a4);
1431
+ }
1432
+
1433
+ SDL_UnlockSurface(can->surface);
1434
+ {
1435
+ float hw = sw * 0.5f;
1436
+ canvas_mark_dirty(can, fminf(x1, x2) - hw, fminf(y1, y2) - hw,
1437
+ fmaxf(x1, x2) + hw, fmaxf(y1, y2) + hw);
1438
+ }
1439
+
1440
+ return R_TRUE;
1441
+ }
1442
+
1443
+
1444
+ /*
1445
+ * Ruby2D::Canvas#ext_draw_lines
1446
+ * Arguments: [n, x1a, y1a, x2a, y2a, x1b, y1b, x2b, y2b, ..., stroke_width, r, g, b, a]
1447
+ * Draws n line segments with a single shared color and stroke width in one
1448
+ * FFI crossing.
1449
+ */
1450
+ R_VAL ruby2d_ext_canvas_draw_lines(RUBY2D_METHOD_ARGS_VARIADIC) {
1451
+ RUBY2D_EXTRACT_VARIADIC;
1452
+ if (argc != 2) r_raise("Ruby2D::Ext.canvas_draw_lines expects 2 args, got %d", (int)argc);
1453
+ R_VAL obj = argv[0];
1454
+ R_VAL a = argv[1];
1455
+
1456
+ R2D_Canvas *can;
1457
+ obj_struct(obj, id_ext_canvas, R2D_Canvas, can);
1458
+
1459
+ int n = (int)NUM2INT(r_ary_entry(a, 0));
1460
+ if (n < 1) return R_TRUE;
1461
+
1462
+ float scale = can->scale;
1463
+ int base = 1 + n * 4;
1464
+ float sw = (float)NUM2DBL(r_ary_entry(a, base)) * scale;
1465
+ float rf = (float)NUM2DBL(r_ary_entry(a, base + 1));
1466
+ float gf = (float)NUM2DBL(r_ary_entry(a, base + 2));
1467
+ float bf = (float)NUM2DBL(r_ary_entry(a, base + 3));
1468
+ float af = (float)NUM2DBL(r_ary_entry(a, base + 4));
1469
+
1470
+ Uint8 cr = (Uint8)(rf * 255);
1471
+ Uint8 cg = (Uint8)(gf * 255);
1472
+ Uint8 cb = (Uint8)(bf * 255);
1473
+ Uint8 ca = (Uint8)(af * 255);
1474
+
1475
+ if (ca == 0) return R_TRUE;
1476
+
1477
+ float hw = sw * 0.5f;
1478
+ SDL_LockSurface(can->surface);
1479
+ for (int i = 0; i < n; i++) {
1480
+ int o = 1 + i * 4;
1481
+ float x1 = (float)NUM2DBL(r_ary_entry(a, o)) * scale;
1482
+ float y1 = (float)NUM2DBL(r_ary_entry(a, o + 1)) * scale;
1483
+ float x2 = (float)NUM2DBL(r_ary_entry(a, o + 2)) * scale;
1484
+ float y2 = (float)NUM2DBL(r_ary_entry(a, o + 3)) * scale;
1485
+ canvas_draw_line_on_surface(can->surface, x1, y1, x2, y2, sw, cr, cg, cb, ca);
1486
+ canvas_mark_dirty(can, fminf(x1, x2) - hw, fminf(y1, y2) - hw,
1487
+ fmaxf(x1, x2) + hw, fmaxf(y1, y2) + hw);
1488
+ }
1489
+ SDL_UnlockSurface(can->surface);
1490
+
1491
+ return R_TRUE;
1492
+ }
1493
+
1494
+
1495
+ /*
1496
+ * Shared body for stroke_polygon/stroke_polyline. `vbase` is the array index
1497
+ * of the first vertex coordinate (the leading args differ between the two);
1498
+ * `closed` joins the last vertex back to the first.
1499
+ */
1500
+ static R_VAL canvas_stroke_impl(R2D_Canvas *can, R_VAL a, int n, int vbase, int closed) {
1501
+ if (n < 2) return R_TRUE;
1502
+
1503
+ float scale = can->scale;
1504
+ // Small strokes use stack scratch; heap only past the threshold.
1505
+ float verts_stack[R2D_STROKE_STACK_N * 2];
1506
+ Uint8 colors_stack[R2D_STROKE_STACK_N * 4];
1507
+ float *verts;
1508
+ Uint8 *colors;
1509
+ bool heap = n > R2D_STROKE_STACK_N;
1510
+
1511
+ if (heap) {
1512
+ verts = (float *)malloc(n * 2 * sizeof(float));
1513
+ colors = (Uint8 *)malloc(n * 4 * sizeof(Uint8));
1514
+ if (!verts || !colors) { free(verts); free(colors); return R_TRUE; }
1515
+ } else {
1516
+ verts = verts_stack;
1517
+ colors = colors_stack;
1518
+ }
1519
+
1520
+ for (int i = 0; i < n; i++) {
1521
+ verts[i*2] = (float)NUM2DBL(r_ary_entry(a, vbase + i*2)) * scale;
1522
+ verts[i*2+1] = (float)NUM2DBL(r_ary_entry(a, vbase + 1 + i*2)) * scale;
1523
+ }
1524
+
1525
+ int base = vbase + n * 2;
1526
+ float sw = (float)NUM2DBL(r_ary_entry(a, base)) * scale;
519
1527
 
520
- // shift left
521
- // i.e. (x2, y2) becomes the first point in the next triple
522
- // (x3, y3) becomes the second point
523
- // and their respective calculated thick corners are shifted as well
524
- // ans t
525
- x1 = x2;
526
- y1 = y2;
527
- x2 = x3;
528
- y2 = y3;
529
- verts[0].position = verts[1].position;
530
- verts[3].position = verts[2].position;
531
- verts[2].position = verts[4].position;
532
- verts[1].position = verts[5].position;
533
- }
534
-
535
- if (!skip_first_last) {
536
- // we render the last segment.
537
- SDL_RenderGeometry(render, NULL, verts, POLYLINE_RENDER_NVERTS, indices,
538
- POLYLINE_RENDER_NINDICES);
1528
+ int has_any_alpha = 0;
1529
+ for (int i = 0; i < n; i++) {
1530
+ colors[i*4] = (Uint8)(NUM2DBL(r_ary_entry(a, base + 1 + i*4)) * 255);
1531
+ colors[i*4 + 1] = (Uint8)(NUM2DBL(r_ary_entry(a, base + 2 + i*4)) * 255);
1532
+ colors[i*4 + 2] = (Uint8)(NUM2DBL(r_ary_entry(a, base + 3 + i*4)) * 255);
1533
+ colors[i*4 + 3] = (Uint8)(NUM2DBL(r_ary_entry(a, base + 4 + i*4)) * 255);
1534
+ if (colors[i*4 + 3] > 0) has_any_alpha = 1;
1535
+ }
1536
+
1537
+ if (has_any_alpha) {
1538
+ SDL_LockSurface(can->surface);
1539
+ canvas_stroke_polyline_on_surface(can->surface, verts, n, closed, sw, colors);
1540
+ SDL_UnlockSurface(can->surface);
1541
+ // Miter joins extend past the vertices by up to hw * R2D_MITER_LIMIT
1542
+ canvas_mark_dirty_verts(can, verts, n, 0.5f * sw * R2D_MITER_LIMIT);
1543
+ }
1544
+
1545
+ if (heap) {
1546
+ free(verts);
1547
+ free(colors);
1548
+ }
1549
+ return R_TRUE;
1550
+ }
1551
+
1552
+
1553
+ /*
1554
+ * Ruby2D::Canvas#ext_stroke_polygon
1555
+ * Arguments: [n, x1, y1, x2, y2, ..., stroke_width, r1,g1,b1,a1, ...rn,gn,bn,an]
1556
+ */
1557
+ R_VAL ruby2d_ext_canvas_stroke_polygon(RUBY2D_METHOD_ARGS_VARIADIC) {
1558
+ RUBY2D_EXTRACT_VARIADIC;
1559
+ if (argc != 2) r_raise("Ruby2D::Ext.canvas_stroke_polygon expects 2 args, got %d", (int)argc);
1560
+ R_VAL obj = argv[0];
1561
+ R_VAL a = argv[1];
1562
+
1563
+ R2D_Canvas *can;
1564
+ obj_struct(obj, id_ext_canvas, R2D_Canvas, can);
1565
+
1566
+ int n = (int)NUM2INT(r_ary_entry(a, 0));
1567
+ return canvas_stroke_impl(can, a, n, 1, 1);
1568
+ }
1569
+
1570
+
1571
+ /*
1572
+ * Ruby2D::Canvas#ext_stroke_polyline
1573
+ * Arguments: [closed, n, x1, y1, x2, y2, ..., stroke_width, r1,g1,b1,a1, ...rn,gn,bn,an]
1574
+ */
1575
+ R_VAL ruby2d_ext_canvas_stroke_polyline(RUBY2D_METHOD_ARGS_VARIADIC) {
1576
+ RUBY2D_EXTRACT_VARIADIC;
1577
+ if (argc != 2) r_raise("Ruby2D::Ext.canvas_stroke_polyline expects 2 args, got %d", (int)argc);
1578
+ R_VAL obj = argv[0];
1579
+ R_VAL a = argv[1];
1580
+
1581
+ R2D_Canvas *can;
1582
+ obj_struct(obj, id_ext_canvas, R2D_Canvas, can);
1583
+
1584
+ int closed = (int)NUM2INT(r_ary_entry(a, 0));
1585
+ int n = (int)NUM2INT(r_ary_entry(a, 1));
1586
+ return canvas_stroke_impl(can, a, n, 2, closed);
1587
+ }
1588
+
1589
+
1590
+ /*
1591
+ * Ruby2D::Canvas#ext_draw_image
1592
+ * Blit an Image's SDL_Surface onto this Canvas's SDL_Surface.
1593
+ * Arguments: img_obj (Ruby2D::Image), a = [x, y, width, height]
1594
+ */
1595
+ R_VAL ruby2d_ext_canvas_draw_image(RUBY2D_METHOD_ARGS_VARIADIC) {
1596
+ RUBY2D_EXTRACT_VARIADIC;
1597
+ if (argc != 3) r_raise("Ruby2D::Ext.canvas_draw_image expects 3 args (canvas, image, params), got %d", (int)argc);
1598
+ R_VAL obj = argv[0];
1599
+ R_VAL img_obj = argv[1];
1600
+ R_VAL a = argv[2];
1601
+
1602
+ R2D_Canvas *can;
1603
+ obj_struct(obj, id_ext_canvas, R2D_Canvas, can);
1604
+
1605
+ // Get the R2D_Image struct from the Image object's @ext_image ivar. Guard
1606
+ // against a nil/absent handle (wrong object type, or a failed Image) before
1607
+ // dereferencing — r_data_ptr on a non-data value would otherwise crash.
1608
+ R_VAL ext_image_val = r_ivar_get(img_obj, id_ext_image);
1609
+ if (!r_test(ext_image_val)) {
1610
+ r_error("Canvas#draw_image requires a valid Ruby2D::Image");
1611
+ return R_NIL;
1612
+ }
1613
+ R2D_Image *img = (R2D_Image *)r_data_ptr(ext_image_val);
1614
+
1615
+ if (!img->surface) {
1616
+ r_error("Image surface is not available for canvas blitting");
1617
+ return R_NIL;
1618
+ }
1619
+
1620
+ float scale = can->scale;
1621
+ int dx = (int)(NUM2DBL(r_ary_entry(a, 0)) * scale);
1622
+ int dy = (int)(NUM2DBL(r_ary_entry(a, 1)) * scale);
1623
+ int dw = (int)(NUM2DBL(r_ary_entry(a, 2)) * scale);
1624
+ int dh = (int)(NUM2DBL(r_ary_entry(a, 3)) * scale);
1625
+
1626
+ // Nothing to draw for a degenerate (zero/negative) destination rect.
1627
+ if (dw <= 0 || dh <= 0) return R_TRUE;
1628
+
1629
+ SDL_Rect dst_rect = { dx, dy, dw, dh };
1630
+
1631
+ SDL_SetSurfaceBlendMode(img->surface, SDL_BLENDMODE_BLEND);
1632
+ SDL_BlitSurfaceScaled(img->surface, NULL, can->surface, &dst_rect, SDL_SCALEMODE_LINEAR);
1633
+
1634
+ canvas_mark_dirty(can, (float)dx, (float)dy, (float)(dx + dw), (float)(dy + dh));
1635
+
1636
+ return R_TRUE;
1637
+ }
1638
+
1639
+
1640
+ /*
1641
+ * Ruby2D::Canvas#ext_draw_text
1642
+ * Blit a Text's SDL_Surface onto this Canvas's SDL_Surface with color modulation.
1643
+ * Arguments: txt_obj (Ruby2D::Text), a = [x, y, width, height, r, g, b, a]
1644
+ */
1645
+ R_VAL ruby2d_ext_canvas_draw_text(RUBY2D_METHOD_ARGS_VARIADIC) {
1646
+ RUBY2D_EXTRACT_VARIADIC;
1647
+ if (argc != 3) r_raise("Ruby2D::Ext.canvas_draw_text expects 3 args (canvas, text, params), got %d", (int)argc);
1648
+ R_VAL obj = argv[0];
1649
+ R_VAL txt_obj = argv[1];
1650
+ R_VAL a = argv[2];
1651
+
1652
+ R2D_Canvas *can;
1653
+ obj_struct(obj, id_ext_canvas, R2D_Canvas, can);
1654
+
1655
+ // Get the R2D_Text struct from the Text object's @ext_text ivar. Guard
1656
+ // against a nil/absent handle (wrong object type, or a failed Text) before
1657
+ // dereferencing — r_data_ptr on a non-data value would otherwise crash.
1658
+ R_VAL ext_text_val = r_ivar_get(txt_obj, id_ext_text);
1659
+ if (!r_test(ext_text_val)) {
1660
+ r_error("Canvas#draw_text requires a valid Ruby2D::Text");
1661
+ return R_NIL;
1662
+ }
1663
+ R2D_Text *txt = (R2D_Text *)r_data_ptr(ext_text_val);
1664
+
1665
+ if (txt->empty) return R_TRUE; // empty content: nothing to blit
1666
+
1667
+ if (!txt->surface) {
1668
+ r_error("Text surface is not available for canvas blitting");
1669
+ return R_NIL;
1670
+ }
1671
+
1672
+ float scale = can->scale;
1673
+ int dx = (int)(NUM2DBL(r_ary_entry(a, 0)) * scale);
1674
+ int dy = (int)(NUM2DBL(r_ary_entry(a, 1)) * scale);
1675
+ int dw = (int)(NUM2DBL(r_ary_entry(a, 2)) * scale);
1676
+ int dh = (int)(NUM2DBL(r_ary_entry(a, 3)) * scale);
1677
+
1678
+ // Nothing to draw for a degenerate (zero/negative) destination rect.
1679
+ if (dw <= 0 || dh <= 0) return R_TRUE;
1680
+
1681
+ float rf = (float)NUM2DBL(r_ary_entry(a, 4));
1682
+ float gf = (float)NUM2DBL(r_ary_entry(a, 5));
1683
+ float bf = (float)NUM2DBL(r_ary_entry(a, 6));
1684
+ float af = (float)NUM2DBL(r_ary_entry(a, 7));
1685
+
1686
+ // Apply color/alpha modulation to tint the white text surface
1687
+ SDL_SetSurfaceColorMod(txt->surface, (Uint8)(rf * 255), (Uint8)(gf * 255), (Uint8)(bf * 255));
1688
+ SDL_SetSurfaceAlphaMod(txt->surface, (Uint8)(af * 255));
1689
+ SDL_SetSurfaceBlendMode(txt->surface, SDL_BLENDMODE_BLEND);
1690
+
1691
+ SDL_Rect dst_rect = { dx, dy, dw, dh };
1692
+ SDL_BlitSurfaceScaled(txt->surface, NULL, can->surface, &dst_rect, SDL_SCALEMODE_LINEAR);
1693
+
1694
+ // Reset color/alpha mod to defaults so GPU rendering is unaffected
1695
+ SDL_SetSurfaceColorMod(txt->surface, 255, 255, 255);
1696
+ SDL_SetSurfaceAlphaMod(txt->surface, 255);
1697
+
1698
+ canvas_mark_dirty(can, (float)dx, (float)dy, (float)(dx + dw), (float)(dy + dh));
1699
+
1700
+ return R_TRUE;
1701
+ }
1702
+
1703
+
1704
+ /*
1705
+ * Ruby2D::Canvas#ext_clear
1706
+ * Arguments: [r, g, b, a, x, y, width, height]
1707
+ * Resets pixels on the surface to the given color (no blending).
1708
+ * When x/y are 0 and width/height match the surface, clears the entire canvas.
1709
+ */
1710
+ R_VAL ruby2d_ext_canvas_clear(RUBY2D_METHOD_ARGS_VARIADIC) {
1711
+ RUBY2D_EXTRACT_VARIADIC;
1712
+ if (argc != 2) r_raise("Ruby2D::Ext.canvas_clear expects 2 args, got %d", (int)argc);
1713
+ R_VAL obj = argv[0];
1714
+ R_VAL a = argv[1];
1715
+
1716
+ R2D_Canvas *can;
1717
+ obj_struct(obj, id_ext_canvas, R2D_Canvas, can);
1718
+
1719
+ Uint8 cr = (Uint8)((float)NUM2DBL(r_ary_entry(a, 0)) * 255);
1720
+ Uint8 cg = (Uint8)((float)NUM2DBL(r_ary_entry(a, 1)) * 255);
1721
+ Uint8 cb = (Uint8)((float)NUM2DBL(r_ary_entry(a, 2)) * 255);
1722
+ Uint8 ca = (Uint8)((float)NUM2DBL(r_ary_entry(a, 3)) * 255);
1723
+
1724
+ float scale = can->scale;
1725
+ int rx = (int)(NUM2DBL(r_ary_entry(a, 4)) * scale);
1726
+ int ry = (int)(NUM2DBL(r_ary_entry(a, 5)) * scale);
1727
+ int rw = (int)(NUM2DBL(r_ary_entry(a, 6)) * scale);
1728
+ int rh = (int)(NUM2DBL(r_ary_entry(a, 7)) * scale);
1729
+
1730
+ Uint32 color = SDL_MapSurfaceRGBA(can->surface, cr, cg, cb, ca);
1731
+
1732
+ // If the rect covers the entire surface, pass NULL for a full clear
1733
+ if (rx == 0 && ry == 0 && rw == can->surface->w && rh == can->surface->h) {
1734
+ SDL_FillSurfaceRect(can->surface, NULL, color);
1735
+ } else {
1736
+ SDL_Rect rect = { rx, ry, rw, rh };
1737
+ SDL_FillSurfaceRect(can->surface, &rect, color);
1738
+ }
1739
+
1740
+ canvas_mark_dirty(can, (float)rx, (float)ry, (float)(rx + rw), (float)(ry + rh));
1741
+
1742
+ return R_TRUE;
1743
+ }
1744
+
1745
+
1746
+ /*
1747
+ * Free canvas
1748
+ */
1749
+ static void R2D_Canvas_free(void *p) {
1750
+ if (!p) return;
1751
+ R2D_Canvas *can = (R2D_Canvas *)p;
1752
+ if (can->surface) {
1753
+ SDL_DestroySurface(can->surface);
1754
+ can->surface = NULL;
1755
+ }
1756
+ if (can->texture) {
1757
+ if (R2D_RendererAlive()) SDL_DestroyTexture(can->texture);
1758
+ can->texture = NULL;
539
1759
  }
1760
+ xfree(can);
540
1761
  }