rgame 0.1.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 (161) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +26 -0
  3. data/README.md +406 -0
  4. data/docs/api/README.md +167 -0
  5. data/docs/api/app.md +192 -0
  6. data/docs/api/assets.md +426 -0
  7. data/docs/api/audio.md +208 -0
  8. data/docs/api/components.md +321 -0
  9. data/docs/api/drawing.md +330 -0
  10. data/docs/api/game.md +99 -0
  11. data/docs/api/images.md +118 -0
  12. data/docs/api/input.md +179 -0
  13. data/docs/api/internals.md +110 -0
  14. data/docs/api/scene_graph.md +159 -0
  15. data/docs/api/signals.md +142 -0
  16. data/docs/api/systems.md +98 -0
  17. data/docs/api/text.md +116 -0
  18. data/docs/api/toolbox.md +240 -0
  19. data/docs/api/values.md +101 -0
  20. data/ext/README.md +225 -0
  21. data/ext/rgame_core/app/app.c +721 -0
  22. data/ext/rgame_core/app/app_gl.h +64 -0
  23. data/ext/rgame_core/app/frame_loop.c +42 -0
  24. data/ext/rgame_core/app/frame_loop.h +54 -0
  25. data/ext/rgame_core/audio/audio.c +466 -0
  26. data/ext/rgame_core/audio/audio_internal.h +45 -0
  27. data/ext/rgame_core/audio/vorbis_decoder.c +282 -0
  28. data/ext/rgame_core/audio/vorbis_decoder.h +45 -0
  29. data/ext/rgame_core/example.rb +188 -0
  30. data/ext/rgame_core/extconf.rb +167 -0
  31. data/ext/rgame_core/graphics/backend.c +52 -0
  32. data/ext/rgame_core/graphics/backend.h +64 -0
  33. data/ext/rgame_core/graphics/canvas.c +247 -0
  34. data/ext/rgame_core/graphics/canvas.h +143 -0
  35. data/ext/rgame_core/graphics/clip.c +87 -0
  36. data/ext/rgame_core/graphics/clip.h +89 -0
  37. data/ext/rgame_core/graphics/draw_queue.c +216 -0
  38. data/ext/rgame_core/graphics/draw_queue.h +174 -0
  39. data/ext/rgame_core/graphics/gl_backend.c +122 -0
  40. data/ext/rgame_core/graphics/gl_backend.h +43 -0
  41. data/ext/rgame_core/graphics/image.c +304 -0
  42. data/ext/rgame_core/graphics/image_internal.h +30 -0
  43. data/ext/rgame_core/graphics/primitives.c +189 -0
  44. data/ext/rgame_core/graphics/primitives.h +111 -0
  45. data/ext/rgame_core/graphics/recording.c +119 -0
  46. data/ext/rgame_core/graphics/recording.h +88 -0
  47. data/ext/rgame_core/graphics/texture.c +181 -0
  48. data/ext/rgame_core/graphics/texture.h +165 -0
  49. data/ext/rgame_core/graphics/transform.c +128 -0
  50. data/ext/rgame_core/graphics/transform.h +106 -0
  51. data/ext/rgame_core/include/rgame/core.h +577 -0
  52. data/ext/rgame_core/input/device_slots.c +103 -0
  53. data/ext/rgame_core/input/device_slots.h +93 -0
  54. data/ext/rgame_core/input/gamepad.c +145 -0
  55. data/ext/rgame_core/input/gamepad.h +63 -0
  56. data/ext/rgame_core/input/input.c +109 -0
  57. data/ext/rgame_core/input/input.h +99 -0
  58. data/ext/rgame_core/ruby/audio_ext.c +321 -0
  59. data/ext/rgame_core/ruby/core_ext.c +513 -0
  60. data/ext/rgame_core/ruby/core_ext.h +51 -0
  61. data/ext/rgame_core/ruby/font_ext.c +168 -0
  62. data/ext/rgame_core/ruby/image_ext.c +230 -0
  63. data/ext/rgame_core/ruby/recording_ext.c +186 -0
  64. data/ext/rgame_core/ruby/renderer_ext.c +376 -0
  65. data/ext/rgame_core/text/atlas.c +59 -0
  66. data/ext/rgame_core/text/atlas.h +85 -0
  67. data/ext/rgame_core/text/font.c +281 -0
  68. data/ext/rgame_core/text/font.h +139 -0
  69. data/ext/rgame_core/text/font_atlas.c +385 -0
  70. data/ext/rgame_core/text/font_internal.h +47 -0
  71. data/ext/rgame_core/text/glyph_cache.c +142 -0
  72. data/ext/rgame_core/text/glyph_cache.h +89 -0
  73. data/ext/rgame_core/vendor/README.md +159 -0
  74. data/ext/rgame_core/vendor/miniaudio.h +95864 -0
  75. data/ext/rgame_core/vendor/miniaudio_impl.c +62 -0
  76. data/ext/rgame_core/vendor/stb_image.h +7988 -0
  77. data/ext/rgame_core/vendor/stb_image_impl.c +31 -0
  78. data/ext/rgame_core/vendor/stb_truetype.h +5079 -0
  79. data/ext/rgame_core/vendor/stb_truetype_impl.c +23 -0
  80. data/ext/rgame_core/vendor/stb_vorbis.c +5584 -0
  81. data/ext/rgame_core/vendor/stb_vorbis_impl.c +29 -0
  82. data/ext/rgame_util/color.c +19 -0
  83. data/ext/rgame_util/color.h +60 -0
  84. data/ext/rgame_util/color_ext.c +156 -0
  85. data/ext/rgame_util/extconf.rb +27 -0
  86. data/ext/rgame_util/tensor.c +186 -0
  87. data/ext/rgame_util/util_ext.c +27 -0
  88. data/ext/rgame_util/util_ext.h +16 -0
  89. data/lib/rgame/boot.rb +13 -0
  90. data/lib/rgame/core/app.rb +82 -0
  91. data/lib/rgame/core/asset_manager.rb +224 -0
  92. data/lib/rgame/core/audio.rb +124 -0
  93. data/lib/rgame/core/font.rb +49 -0
  94. data/lib/rgame/core/gamepad.rb +55 -0
  95. data/lib/rgame/core/image.rb +55 -0
  96. data/lib/rgame/core/input.rb +77 -0
  97. data/lib/rgame/core/nine_slice.rb +163 -0
  98. data/lib/rgame/core/recording.rb +52 -0
  99. data/lib/rgame/core/renderer.rb +363 -0
  100. data/lib/rgame/core/sprite_sheet.rb +108 -0
  101. data/lib/rgame/core/tile_map_renderer.rb +160 -0
  102. data/lib/rgame/core/ui_atlas.rb +86 -0
  103. data/lib/rgame/core.rb +24 -0
  104. data/lib/rgame/engine/actor.rb +53 -0
  105. data/lib/rgame/engine/animation_set.rb +49 -0
  106. data/lib/rgame/engine/animator.rb +44 -0
  107. data/lib/rgame/engine/audio_bus.rb +24 -0
  108. data/lib/rgame/engine/audio_director.rb +29 -0
  109. data/lib/rgame/engine/body.rb +49 -0
  110. data/lib/rgame/engine/cached_label.rb +33 -0
  111. data/lib/rgame/engine/camera.rb +33 -0
  112. data/lib/rgame/engine/camera_view.rb +28 -0
  113. data/lib/rgame/engine/circle_collider.rb +32 -0
  114. data/lib/rgame/engine/collision_box.rb +34 -0
  115. data/lib/rgame/engine/collision_system.rb +44 -0
  116. data/lib/rgame/engine/component.rb +30 -0
  117. data/lib/rgame/engine/components/action_trigger.rb +41 -0
  118. data/lib/rgame/engine/components/animated_sprite.rb +63 -0
  119. data/lib/rgame/engine/components/character_body.rb +70 -0
  120. data/lib/rgame/engine/components/circle_collider.rb +44 -0
  121. data/lib/rgame/engine/components/collision_world.rb +103 -0
  122. data/lib/rgame/engine/components/despawn_offscreen.rb +26 -0
  123. data/lib/rgame/engine/components/path_follow.rb +84 -0
  124. data/lib/rgame/engine/components/player_controller.rb +24 -0
  125. data/lib/rgame/engine/components/pool.rb +53 -0
  126. data/lib/rgame/engine/components/screen_wrap.rb +27 -0
  127. data/lib/rgame/engine/components/sprite.rb +31 -0
  128. data/lib/rgame/engine/components/targeting.rb +54 -0
  129. data/lib/rgame/engine/components/thrust_controller.rb +65 -0
  130. data/lib/rgame/engine/components/tile_world.rb +68 -0
  131. data/lib/rgame/engine/components/timer.rb +75 -0
  132. data/lib/rgame/engine/components/velocity.rb +27 -0
  133. data/lib/rgame/engine/components/wander_controller.rb +60 -0
  134. data/lib/rgame/engine/debug_overlay.rb +106 -0
  135. data/lib/rgame/engine/i18n.rb +97 -0
  136. data/lib/rgame/engine/input/action_mapper.rb +46 -0
  137. data/lib/rgame/engine/input/actions.rb +41 -0
  138. data/lib/rgame/engine/input/player_controller.rb +14 -0
  139. data/lib/rgame/engine/matrix.rb +32 -0
  140. data/lib/rgame/engine/node2d.rb +271 -0
  141. data/lib/rgame/engine/path.rb +78 -0
  142. data/lib/rgame/engine/pool.rb +51 -0
  143. data/lib/rgame/engine/resettable.rb +67 -0
  144. data/lib/rgame/engine/scene/scene_stack.rb +65 -0
  145. data/lib/rgame/engine/signal.rb +75 -0
  146. data/lib/rgame/engine/spatial_hash.rb +71 -0
  147. data/lib/rgame/engine/tile_collision.rb +78 -0
  148. data/lib/rgame/engine/tile_map.rb +149 -0
  149. data/lib/rgame/engine/tileset.rb +101 -0
  150. data/lib/rgame/engine/timer.rb +51 -0
  151. data/lib/rgame/engine.rb +68 -0
  152. data/lib/rgame/fonts/LiberationSans-Regular.ttf +0 -0
  153. data/lib/rgame/fonts/OFL.txt +102 -0
  154. data/lib/rgame/game.rb +129 -0
  155. data/lib/rgame/util/color.rb +27 -0
  156. data/lib/rgame/util/controls.rb +107 -0
  157. data/lib/rgame/util/tensor.rb +12 -0
  158. data/lib/rgame/util.rb +8 -0
  159. data/lib/rgame/version.rb +12 -0
  160. data/lib/rgame.rb +20 -0
  161. metadata +215 -0
@@ -0,0 +1,376 @@
1
+ /*
2
+ * renderer_ext.c — the Ruby binding for RGame::Core::Renderer's primitives.
3
+ *
4
+ * This is the low half of the renderer. Every method here is a thin call into
5
+ * the engine's drawing API, taking plain numbers and a packed colour; the
6
+ * pleasant half — keyword arguments, default z values, colour coercion and the
7
+ * `rotated { }` block forms — is pure Ruby in lib/rgame/core/renderer.rb,
8
+ * which reopens this class.
9
+ *
10
+ * The split is deliberate. Keyword parsing and colour handling in C would be a
11
+ * lot of rb_get_kwargs for no gain, while the arithmetic behind a circle or a
12
+ * thick line genuinely belongs in C (primitives.c) where it is Check-tested.
13
+ * So C draws, and Ruby makes it comfortable.
14
+ *
15
+ * ---------------------------------------------------------------------------
16
+ * Why a renderer object at all, rather than methods on App
17
+ * ---------------------------------------------------------------------------
18
+ *
19
+ * The engine layer (RGame::Engine) may not name RGame::Core. It receives a
20
+ * renderer in `draw` and calls methods on it that it knows only by name — so
21
+ * "the thing you draw with" has to be a separate object that a spec can
22
+ * substitute with a recording fake. An App with draw methods on it could not be
23
+ * faked without faking the window too.
24
+ */
25
+
26
+ #include "ruby/core_ext.h"
27
+
28
+ #include "rgame/core.h"
29
+
30
+ typedef struct {
31
+ rgame_app *app;
32
+ VALUE app_object; /* marked: the window must outlive anything drawing into it */
33
+ /*
34
+ * While a recording is open, every Image drawn is collected here and handed
35
+ * to the finished Recording, which then keeps them alive. A baked batch
36
+ * holds a GL texture *number*, so an Image collected out from under it
37
+ * would leave the recording drawing whatever the driver put there next.
38
+ * Qnil when not recording.
39
+ */
40
+ VALUE recorded_images;
41
+ } rgame_renderer_ref;
42
+
43
+ static void renderer_mark(void *ptr) {
44
+ rgame_renderer_ref *ref = ptr;
45
+ rb_gc_mark(ref->app_object);
46
+ rb_gc_mark(ref->recorded_images);
47
+ }
48
+
49
+ static size_t renderer_size(const void *ptr) {
50
+ (void)ptr;
51
+ return sizeof(rgame_renderer_ref);
52
+ }
53
+
54
+ static const rb_data_type_t renderer_data_type = {
55
+ .wrap_struct_name = "rgame_renderer",
56
+ .function = {
57
+ .dmark = renderer_mark,
58
+ .dfree = RUBY_TYPED_DEFAULT_FREE, /* the app is not ours to free */
59
+ .dsize = renderer_size,
60
+ },
61
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
62
+ };
63
+
64
+ static VALUE cRenderer;
65
+
66
+ /*
67
+ * The app to draw into, or an exception.
68
+ *
69
+ * Drawing outside `draw` is refused rather than ignored. The canvas would
70
+ * accept the vertices and drop them at the next frame's begin, so the failure
71
+ * would otherwise be an invisible one: no error, no output, nothing to search
72
+ * for. This is the same reasoning as the engine bracketing the frame itself —
73
+ * the mistake that cannot be made silently is the one worth designing for.
74
+ */
75
+ static rgame_app *drawing_app(VALUE self) {
76
+ rgame_renderer_ref *ref;
77
+ TypedData_Get_Struct(self, rgame_renderer_ref, &renderer_data_type, ref);
78
+ if (!ref->app) {
79
+ rb_raise(rb_eRuntimeError, "renderer is not initialized");
80
+ }
81
+ if (!rgame_app_is_drawing(ref->app)) {
82
+ rb_raise(rb_eRuntimeError,
83
+ "drawing is only allowed inside #draw (the frame is not open)");
84
+ }
85
+ return ref->app;
86
+ }
87
+
88
+ static VALUE renderer_alloc(VALUE klass) {
89
+ rgame_renderer_ref *ref;
90
+ VALUE object = TypedData_Make_Struct(klass, rgame_renderer_ref, &renderer_data_type, ref);
91
+ ref->app = NULL;
92
+ ref->app_object = Qnil;
93
+ ref->recorded_images = Qnil;
94
+ return object;
95
+ }
96
+
97
+ /* Renderer.new(app) */
98
+ static VALUE renderer_initialize(VALUE self, VALUE app) {
99
+ rgame_renderer_ref *ref;
100
+ TypedData_Get_Struct(self, rgame_renderer_ref, &renderer_data_type, ref);
101
+
102
+ ref->app = rgame_app_unwrap(app); /* raises TypeError on anything else */
103
+ ref->app_object = app;
104
+ ref->recorded_images = Qnil;
105
+ return self;
106
+ }
107
+
108
+ /*
109
+ * The App this renderer draws into.
110
+ *
111
+ * Exposed because the Ruby half builds the default font lazily and needs an app
112
+ * to build it from — see lib/rgame/core/renderer.rb. A renderer already holds
113
+ * the app reachable, so handing it back adds no lifetime question.
114
+ */
115
+ static VALUE renderer_app(VALUE self) {
116
+ rgame_renderer_ref *ref;
117
+ TypedData_Get_Struct(self, rgame_renderer_ref, &renderer_data_type, ref);
118
+ return ref->app_object;
119
+ }
120
+
121
+ /* True while a frame is open — that is, inside the app's #draw. */
122
+ static VALUE renderer_drawing_p(VALUE self) {
123
+ rgame_renderer_ref *ref;
124
+ TypedData_Get_Struct(self, rgame_renderer_ref, &renderer_data_type, ref);
125
+ return ref->app && rgame_app_is_drawing(ref->app) ? Qtrue : Qfalse;
126
+ }
127
+
128
+ /* Colours arrive already packed as 0xRRGGBBAA — see RGame::Util::Color#packed.
129
+ * NUM2UINT would reject the high bit on some widths, so go via unsigned long. */
130
+ static unsigned int packed_color(VALUE color) {
131
+ return (unsigned int)(NUM2ULONG(color) & 0xFFFFFFFFul);
132
+ }
133
+
134
+ static VALUE renderer_draw_rect(VALUE self, VALUE x, VALUE y, VALUE width, VALUE height,
135
+ VALUE z, VALUE color) {
136
+ rgame_app_draw_rect(drawing_app(self), (float)NUM2DBL(x), (float)NUM2DBL(y),
137
+ (float)NUM2DBL(width), (float)NUM2DBL(height), packed_color(color),
138
+ NUM2DBL(z));
139
+ return self;
140
+ }
141
+
142
+ static VALUE renderer_draw_quad(int argc, VALUE *argv, VALUE self) {
143
+ /* Ten positional arguments is past the point where naming each parameter
144
+ * helps, so this one takes an argv and unpacks it in a loop. */
145
+ rb_check_arity(argc, 10, 10);
146
+
147
+ float xy8[8];
148
+ for (int i = 0; i < 8; i++) {
149
+ xy8[i] = (float)NUM2DBL(argv[i]);
150
+ }
151
+ rgame_app_draw_quad(drawing_app(self), xy8, packed_color(argv[9]), NUM2DBL(argv[8]));
152
+ return self;
153
+ }
154
+
155
+ static VALUE renderer_draw_triangle(int argc, VALUE *argv, VALUE self) {
156
+ rb_check_arity(argc, 8, 8);
157
+
158
+ float xy6[6];
159
+ for (int i = 0; i < 6; i++) {
160
+ xy6[i] = (float)NUM2DBL(argv[i]);
161
+ }
162
+ rgame_app_draw_triangle(drawing_app(self), xy6, packed_color(argv[7]), NUM2DBL(argv[6]));
163
+ return self;
164
+ }
165
+
166
+ static VALUE renderer_draw_line(int argc, VALUE *argv, VALUE self) {
167
+ rb_check_arity(argc, 7, 7);
168
+
169
+ rgame_app_draw_line(drawing_app(self), (float)NUM2DBL(argv[0]), (float)NUM2DBL(argv[1]),
170
+ (float)NUM2DBL(argv[2]), (float)NUM2DBL(argv[3]),
171
+ (float)NUM2DBL(argv[4]), packed_color(argv[6]), NUM2DBL(argv[5]));
172
+ return self;
173
+ }
174
+
175
+ static VALUE renderer_draw_circle(int argc, VALUE *argv, VALUE self) {
176
+ rb_check_arity(argc, 6, 6);
177
+
178
+ rgame_app_draw_circle(drawing_app(self), (float)NUM2DBL(argv[0]), (float)NUM2DBL(argv[1]),
179
+ (float)NUM2DBL(argv[2]), NUM2INT(argv[3]), packed_color(argv[5]),
180
+ NUM2DBL(argv[4]));
181
+ return self;
182
+ }
183
+
184
+ /* An image or font drawn through the wrong app's renderer samples nothing and
185
+ * paints white quads — silently. Every such call reports the mismatch so it can
186
+ * be an error instead of a mystery on screen. */
187
+ static void check_drawn(int drawn, VALUE subject) {
188
+ if (!drawn) {
189
+ rb_raise(rb_eArgError,
190
+ "%" PRIsVALUE " belongs to a different App than this renderer; "
191
+ "a texture cannot be drawn into another window's GL context",
192
+ subject);
193
+ }
194
+ }
195
+
196
+ /* If a recording is open, remember the image so the finished Recording can keep
197
+ * its texture alive. Cheap and only while recording — a bake happens once. */
198
+ static void note_recorded_image(VALUE self, VALUE image) {
199
+ rgame_renderer_ref *ref;
200
+ TypedData_Get_Struct(self, rgame_renderer_ref, &renderer_data_type, ref);
201
+ if (!NIL_P(ref->recorded_images)) {
202
+ rb_ary_push(ref->recorded_images, image);
203
+ }
204
+ }
205
+
206
+ static VALUE renderer_draw_image(VALUE self, VALUE image, VALUE x, VALUE y, VALUE z,
207
+ VALUE color) {
208
+ check_drawn(rgame_app_draw_image(drawing_app(self), rgame_image_unwrap(image),
209
+ (float)NUM2DBL(x), (float)NUM2DBL(y), packed_color(color),
210
+ NUM2DBL(z)),
211
+ image);
212
+ note_recorded_image(self, image);
213
+ return self;
214
+ }
215
+
216
+ /* (image, x, y, scale_x, scale_y, z, color) — seven arguments, so -1 arity and
217
+ * an explicit check rather than seven named parameters. */
218
+ static VALUE renderer_draw_image_scaled(int argc, VALUE *argv, VALUE self) {
219
+ rb_check_arity(argc, 7, 7);
220
+
221
+ check_drawn(rgame_app_draw_image_scaled(drawing_app(self), rgame_image_unwrap(argv[0]),
222
+ (float)NUM2DBL(argv[1]), (float)NUM2DBL(argv[2]),
223
+ (float)NUM2DBL(argv[3]), (float)NUM2DBL(argv[4]),
224
+ packed_color(argv[6]), NUM2DBL(argv[5])),
225
+ argv[0]);
226
+ note_recorded_image(self, argv[0]);
227
+ return self;
228
+ }
229
+
230
+ static VALUE renderer_draw_image_rot(int argc, VALUE *argv, VALUE self) {
231
+ rb_check_arity(argc, 7, 7);
232
+
233
+ check_drawn(rgame_app_draw_image_rot(drawing_app(self), rgame_image_unwrap(argv[0]),
234
+ (float)NUM2DBL(argv[1]), (float)NUM2DBL(argv[2]),
235
+ (float)NUM2DBL(argv[3]), (float)NUM2DBL(argv[4]),
236
+ packed_color(argv[6]), NUM2DBL(argv[5])),
237
+ argv[0]);
238
+ note_recorded_image(self, argv[0]);
239
+ return self;
240
+ }
241
+
242
+ /*
243
+ * #draw_text(font, string, x, y, z, rgba)
244
+ *
245
+ * The string's bytes go to C as they are: no `each_char`, no codepoints array,
246
+ * nothing allocated per call. Walking UTF-8 is what font.c is for.
247
+ */
248
+ static VALUE renderer_draw_text(int argc, VALUE *argv, VALUE self) {
249
+ rb_check_arity(argc, 6, 6);
250
+
251
+ VALUE font = argv[0];
252
+ VALUE string = argv[1];
253
+ /* RSTRING_PTR on a non-String reads whatever the object's second word
254
+ * happens to be and hands it to C as a char* — a segfault, not an
255
+ * exception. Everything else here goes through NUM2DBL or an unwrap, which
256
+ * type-check on the way; this is the one argument that has to say so.
257
+ * StringValue converts what can convert and raises TypeError otherwise. */
258
+ StringValue(string);
259
+ const char *text = RSTRING_PTR(string);
260
+ long length = RSTRING_LEN(string);
261
+
262
+ int drawn = rgame_app_draw_text(drawing_app(self), rgame_font_unwrap(font), text,
263
+ (size_t)length, (float)NUM2DBL(argv[2]),
264
+ (float)NUM2DBL(argv[3]), packed_color(argv[5]),
265
+ NUM2DBL(argv[4]));
266
+ /* RSTRING_PTR hands out a pointer the collector does not know about. */
267
+ RB_GC_GUARD(string);
268
+
269
+ check_drawn(drawn, font);
270
+ return self;
271
+ }
272
+
273
+ static VALUE renderer_push_translate(VALUE self, VALUE dx, VALUE dy) {
274
+ rgame_app_push_translate(drawing_app(self), (float)NUM2DBL(dx), (float)NUM2DBL(dy));
275
+ return self;
276
+ }
277
+
278
+ static VALUE renderer_push_rotate(VALUE self, VALUE degrees, VALUE pivot_x, VALUE pivot_y) {
279
+ rgame_app_push_rotate(drawing_app(self), (float)NUM2DBL(degrees), (float)NUM2DBL(pivot_x),
280
+ (float)NUM2DBL(pivot_y));
281
+ return self;
282
+ }
283
+
284
+ static VALUE renderer_push_scale(VALUE self, VALUE sx, VALUE sy) {
285
+ rgame_app_push_scale(drawing_app(self), (float)NUM2DBL(sx), (float)NUM2DBL(sy));
286
+ return self;
287
+ }
288
+
289
+ static VALUE renderer_push_clip(VALUE self, VALUE x, VALUE y, VALUE width, VALUE height) {
290
+ if (!rgame_app_push_clip(drawing_app(self), NUM2INT(x), NUM2INT(y), NUM2INT(width),
291
+ NUM2INT(height))) {
292
+ /* Clipping cannot be baked into a recording; see core.h. Saying so
293
+ * beats recording geometry that quietly ignores the clip. */
294
+ rb_raise(rb_eRuntimeError,
295
+ "a clip cannot be recorded — wrap the replay in #clipped instead");
296
+ }
297
+ return self;
298
+ }
299
+
300
+ /* ------------------------------------------------------------------------- *
301
+ * Recording
302
+ *
303
+ * Three primitives rather than one block-taking method: Renderer#record in
304
+ * lib/rgame/core/renderer.rb wraps them in begin/ensure, which is where a
305
+ * `raise` mid-bake gets unwound. Doing that in C would mean rb_protect for no
306
+ * benefit.
307
+ * ------------------------------------------------------------------------- */
308
+
309
+ static VALUE renderer_begin_record(VALUE self) {
310
+ rgame_renderer_ref *ref;
311
+ TypedData_Get_Struct(self, rgame_renderer_ref, &renderer_data_type, ref);
312
+
313
+ if (!rgame_app_begin_record(drawing_app(self))) {
314
+ rb_raise(rb_eRuntimeError, "already recording (recordings do not nest)");
315
+ }
316
+ ref->recorded_images = rb_ary_new();
317
+ return self;
318
+ }
319
+
320
+ static VALUE renderer_end_record(VALUE self) {
321
+ rgame_renderer_ref *ref;
322
+ TypedData_Get_Struct(self, rgame_renderer_ref, &renderer_data_type, ref);
323
+
324
+ VALUE images = ref->recorded_images;
325
+ ref->recorded_images = Qnil;
326
+
327
+ rgame_recording *recording = rgame_app_end_record(ref->app);
328
+ if (!recording) {
329
+ rb_raise(rb_eRuntimeError, "no recording is open");
330
+ }
331
+
332
+ return rgame_recording_wrap(recording, ref->app_object, ref->app, images);
333
+ }
334
+
335
+ static VALUE renderer_cancel_record(VALUE self) {
336
+ rgame_renderer_ref *ref;
337
+ TypedData_Get_Struct(self, rgame_renderer_ref, &renderer_data_type, ref);
338
+
339
+ ref->recorded_images = Qnil;
340
+ rgame_app_cancel_record(ref->app);
341
+ return self;
342
+ }
343
+
344
+ static VALUE renderer_pop(VALUE self) {
345
+ rgame_app_pop(drawing_app(self));
346
+ return self;
347
+ }
348
+
349
+ void rgame_init_renderer(VALUE mCore) {
350
+ cRenderer = rb_define_class_under(mCore, "Renderer", rb_cObject);
351
+
352
+ rb_define_alloc_func(cRenderer, renderer_alloc);
353
+ rb_define_method(cRenderer, "initialize", renderer_initialize, 1);
354
+ rb_define_method(cRenderer, "drawing?", renderer_drawing_p, 0);
355
+ rb_define_method(cRenderer, "app", renderer_app, 0);
356
+
357
+ rb_define_method(cRenderer, "draw_rect", renderer_draw_rect, 6);
358
+ rb_define_method(cRenderer, "draw_quad", renderer_draw_quad, -1);
359
+ rb_define_method(cRenderer, "draw_triangle", renderer_draw_triangle, -1);
360
+ rb_define_method(cRenderer, "draw_line", renderer_draw_line, -1);
361
+ rb_define_method(cRenderer, "draw_circle", renderer_draw_circle, -1);
362
+ rb_define_method(cRenderer, "draw_image", renderer_draw_image, 5);
363
+ rb_define_method(cRenderer, "draw_image_scaled", renderer_draw_image_scaled, -1);
364
+ rb_define_method(cRenderer, "draw_image_rot", renderer_draw_image_rot, -1);
365
+ rb_define_method(cRenderer, "draw_text", renderer_draw_text, -1);
366
+
367
+ rb_define_method(cRenderer, "push_translate", renderer_push_translate, 2);
368
+ rb_define_method(cRenderer, "push_rotate", renderer_push_rotate, 3);
369
+ rb_define_method(cRenderer, "push_scale", renderer_push_scale, 2);
370
+ rb_define_method(cRenderer, "push_clip", renderer_push_clip, 4);
371
+ rb_define_method(cRenderer, "pop", renderer_pop, 0);
372
+
373
+ rb_define_method(cRenderer, "begin_record", renderer_begin_record, 0);
374
+ rb_define_method(cRenderer, "end_record", renderer_end_record, 0);
375
+ rb_define_method(cRenderer, "cancel_record", renderer_cancel_record, 0);
376
+ }
@@ -0,0 +1,59 @@
1
+ /*
2
+ * atlas.c — the shelf packer. See atlas.h for the shape of it and why shelves
3
+ * are enough.
4
+ */
5
+
6
+ #include "text/atlas.h"
7
+
8
+ void rgame_atlas_init(rgame_atlas *atlas, int width, int height) {
9
+ atlas->width = width > 0 ? width : 0;
10
+ atlas->height = height > 0 ? height : 0;
11
+ atlas->shelf_y = 0;
12
+ atlas->shelf_height = 0;
13
+ atlas->cursor_x = 0;
14
+ }
15
+
16
+ int rgame_atlas_place(rgame_atlas *atlas, int width, int height, rgame_rect *out) {
17
+ if (!atlas || !out) {
18
+ return 0;
19
+ }
20
+
21
+ /* Nothing to pack. Succeeds with an empty rectangle: see atlas.h. */
22
+ if (width <= 0 || height <= 0) {
23
+ *out = rgame_rect_make(atlas->cursor_x, atlas->shelf_y, 0, 0);
24
+ return 1;
25
+ }
26
+
27
+ /* No amount of shelving will make it fit, so say so now rather than after
28
+ * opening an empty shelf for it. */
29
+ if (width > atlas->width || height > atlas->height) {
30
+ return 0;
31
+ }
32
+
33
+ /* Out of room on this shelf? Drop to a new one below the *tallest* glyph
34
+ * on the old one — not below the last one placed, which may be short and
35
+ * would let the new shelf overlap a taller neighbour. */
36
+ if (atlas->cursor_x + width > atlas->width) {
37
+ atlas->shelf_y += atlas->shelf_height + RGAME_ATLAS_GUTTER;
38
+ atlas->shelf_height = 0;
39
+ atlas->cursor_x = 0;
40
+ }
41
+
42
+ if (atlas->shelf_y + height > atlas->height) {
43
+ /* The page is full. Nothing has been changed that a caller could
44
+ * observe except the shelf move above, which only ever makes the page
45
+ * emptier-looking; the next place() will fail the same way. */
46
+ return 0;
47
+ }
48
+
49
+ *out = rgame_rect_make(atlas->cursor_x, atlas->shelf_y, width, height);
50
+
51
+ /* The gutter goes after the glyph, so the next one on this shelf starts a
52
+ * pixel clear of it, and the next shelf starts a pixel below this one. */
53
+ atlas->cursor_x += width + RGAME_ATLAS_GUTTER;
54
+ if (height > atlas->shelf_height) {
55
+ atlas->shelf_height = height;
56
+ }
57
+
58
+ return 1;
59
+ }
@@ -0,0 +1,85 @@
1
+ #ifndef RGAME_ATLAS_H
2
+ #define RGAME_ATLAS_H
3
+
4
+ #include "graphics/clip.h"
5
+
6
+ /*
7
+ * Where the next glyph goes on a texture page. Pure arithmetic — no GL, no
8
+ * font, no idea what a glyph even is.
9
+ *
10
+ * Uploading one small texture per character would be hopeless: a string of
11
+ * twenty characters would be twenty texture binds and twenty draw calls. So
12
+ * every glyph of a font is packed into one big page and drawn from a
13
+ * sub-rectangle of it, exactly like sprites out of a sprite sheet — which means
14
+ * a whole string is one batch.
15
+ *
16
+ * ---------------------------------------------------------------------------
17
+ * Shelves, and why that is enough
18
+ * ---------------------------------------------------------------------------
19
+ *
20
+ * Rectangle packing in general is a hard problem with a literature attached.
21
+ * This is not the general problem: glyphs arrive one at a time, in whatever
22
+ * order the game happens to draw them, and they are nearly all the same height
23
+ * because they came from one font at one size. For that, shelves are the right
24
+ * amount of cleverness — fill a row left to right, start a new row underneath
25
+ * when it runs out, and give up when the page does:
26
+ *
27
+ * +-----------------------------+
28
+ * | A | B | C | D | | <- a full shelf, shelf_height = tallest
29
+ * +---+---+---+---+-------------+
30
+ * | E | F |^cursor_x | <- the shelf being filled
31
+ * +-------+ |
32
+ * | |
33
+ * +-----------------------------+
34
+ *
35
+ * The waste is the ragged right edge of each shelf plus the gap under short
36
+ * glyphs, which for one font at one size is a few percent. A smarter packer
37
+ * would buy back that few percent in exchange for being much harder to be sure
38
+ * about, and pages are cheap: when one fills, the caller opens another.
39
+ *
40
+ * ---------------------------------------------------------------------------
41
+ * The gutter is this module's job
42
+ * ---------------------------------------------------------------------------
43
+ *
44
+ * Glyph atlases are sampled with linear filtering, so a texel at the edge of one
45
+ * glyph blends with whatever is next to it. Packed flush, that is the
46
+ * neighbouring letter, and every glyph draws with a sliver of the next one down
47
+ * its side.
48
+ *
49
+ * The fix is a one-pixel gap, and it is reserved *inside* `rgame_atlas_place`
50
+ * rather than added by each caller. A caller that has to remember is a caller
51
+ * that eventually does not, and the result looks like a rendering bug rather
52
+ * than the packing bug it is.
53
+ */
54
+
55
+ /* Kept between glyphs so linear sampling cannot blend two of them together. */
56
+ #define RGAME_ATLAS_GUTTER 1
57
+
58
+ typedef struct {
59
+ int width, height;
60
+
61
+ int shelf_y; /* top edge of the row currently being filled */
62
+ int shelf_height; /* the tallest glyph placed on that row so far */
63
+ int cursor_x; /* next free x on that row */
64
+ } rgame_atlas;
65
+
66
+ /* An empty page of the given size. A degenerate size yields a page that refuses
67
+ * everything, rather than one that hands out rectangles outside itself. */
68
+ void rgame_atlas_init(rgame_atlas *atlas, int width, int height);
69
+
70
+ /*
71
+ * Reserves room for a `width` x `height` glyph and writes where it went.
72
+ *
73
+ * Returns 1 when it fit and 0 when the page is full — that 0 is the caller's
74
+ * signal to start another page, not an error. `out` is left untouched on
75
+ * failure, so a caller that ignores the return value gets a stale rectangle
76
+ * rather than a garbage one.
77
+ *
78
+ * A glyph with no pixels (the space character rasterises to nothing) succeeds
79
+ * and reserves nothing: the answer is an empty rectangle, which uploads nothing
80
+ * and samples nothing. That is the honest result rather than a special case the
81
+ * caller has to know about.
82
+ */
83
+ int rgame_atlas_place(rgame_atlas *atlas, int width, int height, rgame_rect *out);
84
+
85
+ #endif /* RGAME_ATLAS_H */