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.
- checksums.yaml +7 -0
- data/LICENSE +26 -0
- data/README.md +406 -0
- data/docs/api/README.md +167 -0
- data/docs/api/app.md +192 -0
- data/docs/api/assets.md +426 -0
- data/docs/api/audio.md +208 -0
- data/docs/api/components.md +321 -0
- data/docs/api/drawing.md +330 -0
- data/docs/api/game.md +99 -0
- data/docs/api/images.md +118 -0
- data/docs/api/input.md +179 -0
- data/docs/api/internals.md +110 -0
- data/docs/api/scene_graph.md +159 -0
- data/docs/api/signals.md +142 -0
- data/docs/api/systems.md +98 -0
- data/docs/api/text.md +116 -0
- data/docs/api/toolbox.md +240 -0
- data/docs/api/values.md +101 -0
- data/ext/README.md +225 -0
- data/ext/rgame_core/app/app.c +721 -0
- data/ext/rgame_core/app/app_gl.h +64 -0
- data/ext/rgame_core/app/frame_loop.c +42 -0
- data/ext/rgame_core/app/frame_loop.h +54 -0
- data/ext/rgame_core/audio/audio.c +466 -0
- data/ext/rgame_core/audio/audio_internal.h +45 -0
- data/ext/rgame_core/audio/vorbis_decoder.c +282 -0
- data/ext/rgame_core/audio/vorbis_decoder.h +45 -0
- data/ext/rgame_core/example.rb +188 -0
- data/ext/rgame_core/extconf.rb +167 -0
- data/ext/rgame_core/graphics/backend.c +52 -0
- data/ext/rgame_core/graphics/backend.h +64 -0
- data/ext/rgame_core/graphics/canvas.c +247 -0
- data/ext/rgame_core/graphics/canvas.h +143 -0
- data/ext/rgame_core/graphics/clip.c +87 -0
- data/ext/rgame_core/graphics/clip.h +89 -0
- data/ext/rgame_core/graphics/draw_queue.c +216 -0
- data/ext/rgame_core/graphics/draw_queue.h +174 -0
- data/ext/rgame_core/graphics/gl_backend.c +122 -0
- data/ext/rgame_core/graphics/gl_backend.h +43 -0
- data/ext/rgame_core/graphics/image.c +304 -0
- data/ext/rgame_core/graphics/image_internal.h +30 -0
- data/ext/rgame_core/graphics/primitives.c +189 -0
- data/ext/rgame_core/graphics/primitives.h +111 -0
- data/ext/rgame_core/graphics/recording.c +119 -0
- data/ext/rgame_core/graphics/recording.h +88 -0
- data/ext/rgame_core/graphics/texture.c +181 -0
- data/ext/rgame_core/graphics/texture.h +165 -0
- data/ext/rgame_core/graphics/transform.c +128 -0
- data/ext/rgame_core/graphics/transform.h +106 -0
- data/ext/rgame_core/include/rgame/core.h +577 -0
- data/ext/rgame_core/input/device_slots.c +103 -0
- data/ext/rgame_core/input/device_slots.h +93 -0
- data/ext/rgame_core/input/gamepad.c +145 -0
- data/ext/rgame_core/input/gamepad.h +63 -0
- data/ext/rgame_core/input/input.c +109 -0
- data/ext/rgame_core/input/input.h +99 -0
- data/ext/rgame_core/ruby/audio_ext.c +321 -0
- data/ext/rgame_core/ruby/core_ext.c +513 -0
- data/ext/rgame_core/ruby/core_ext.h +51 -0
- data/ext/rgame_core/ruby/font_ext.c +168 -0
- data/ext/rgame_core/ruby/image_ext.c +230 -0
- data/ext/rgame_core/ruby/recording_ext.c +186 -0
- data/ext/rgame_core/ruby/renderer_ext.c +376 -0
- data/ext/rgame_core/text/atlas.c +59 -0
- data/ext/rgame_core/text/atlas.h +85 -0
- data/ext/rgame_core/text/font.c +281 -0
- data/ext/rgame_core/text/font.h +139 -0
- data/ext/rgame_core/text/font_atlas.c +385 -0
- data/ext/rgame_core/text/font_internal.h +47 -0
- data/ext/rgame_core/text/glyph_cache.c +142 -0
- data/ext/rgame_core/text/glyph_cache.h +89 -0
- data/ext/rgame_core/vendor/README.md +159 -0
- data/ext/rgame_core/vendor/miniaudio.h +95864 -0
- data/ext/rgame_core/vendor/miniaudio_impl.c +62 -0
- data/ext/rgame_core/vendor/stb_image.h +7988 -0
- data/ext/rgame_core/vendor/stb_image_impl.c +31 -0
- data/ext/rgame_core/vendor/stb_truetype.h +5079 -0
- data/ext/rgame_core/vendor/stb_truetype_impl.c +23 -0
- data/ext/rgame_core/vendor/stb_vorbis.c +5584 -0
- data/ext/rgame_core/vendor/stb_vorbis_impl.c +29 -0
- data/ext/rgame_util/color.c +19 -0
- data/ext/rgame_util/color.h +60 -0
- data/ext/rgame_util/color_ext.c +156 -0
- data/ext/rgame_util/extconf.rb +27 -0
- data/ext/rgame_util/tensor.c +186 -0
- data/ext/rgame_util/util_ext.c +27 -0
- data/ext/rgame_util/util_ext.h +16 -0
- data/lib/rgame/boot.rb +13 -0
- data/lib/rgame/core/app.rb +82 -0
- data/lib/rgame/core/asset_manager.rb +224 -0
- data/lib/rgame/core/audio.rb +124 -0
- data/lib/rgame/core/font.rb +49 -0
- data/lib/rgame/core/gamepad.rb +55 -0
- data/lib/rgame/core/image.rb +55 -0
- data/lib/rgame/core/input.rb +77 -0
- data/lib/rgame/core/nine_slice.rb +163 -0
- data/lib/rgame/core/recording.rb +52 -0
- data/lib/rgame/core/renderer.rb +363 -0
- data/lib/rgame/core/sprite_sheet.rb +108 -0
- data/lib/rgame/core/tile_map_renderer.rb +160 -0
- data/lib/rgame/core/ui_atlas.rb +86 -0
- data/lib/rgame/core.rb +24 -0
- data/lib/rgame/engine/actor.rb +53 -0
- data/lib/rgame/engine/animation_set.rb +49 -0
- data/lib/rgame/engine/animator.rb +44 -0
- data/lib/rgame/engine/audio_bus.rb +24 -0
- data/lib/rgame/engine/audio_director.rb +29 -0
- data/lib/rgame/engine/body.rb +49 -0
- data/lib/rgame/engine/cached_label.rb +33 -0
- data/lib/rgame/engine/camera.rb +33 -0
- data/lib/rgame/engine/camera_view.rb +28 -0
- data/lib/rgame/engine/circle_collider.rb +32 -0
- data/lib/rgame/engine/collision_box.rb +34 -0
- data/lib/rgame/engine/collision_system.rb +44 -0
- data/lib/rgame/engine/component.rb +30 -0
- data/lib/rgame/engine/components/action_trigger.rb +41 -0
- data/lib/rgame/engine/components/animated_sprite.rb +63 -0
- data/lib/rgame/engine/components/character_body.rb +70 -0
- data/lib/rgame/engine/components/circle_collider.rb +44 -0
- data/lib/rgame/engine/components/collision_world.rb +103 -0
- data/lib/rgame/engine/components/despawn_offscreen.rb +26 -0
- data/lib/rgame/engine/components/path_follow.rb +84 -0
- data/lib/rgame/engine/components/player_controller.rb +24 -0
- data/lib/rgame/engine/components/pool.rb +53 -0
- data/lib/rgame/engine/components/screen_wrap.rb +27 -0
- data/lib/rgame/engine/components/sprite.rb +31 -0
- data/lib/rgame/engine/components/targeting.rb +54 -0
- data/lib/rgame/engine/components/thrust_controller.rb +65 -0
- data/lib/rgame/engine/components/tile_world.rb +68 -0
- data/lib/rgame/engine/components/timer.rb +75 -0
- data/lib/rgame/engine/components/velocity.rb +27 -0
- data/lib/rgame/engine/components/wander_controller.rb +60 -0
- data/lib/rgame/engine/debug_overlay.rb +106 -0
- data/lib/rgame/engine/i18n.rb +97 -0
- data/lib/rgame/engine/input/action_mapper.rb +46 -0
- data/lib/rgame/engine/input/actions.rb +41 -0
- data/lib/rgame/engine/input/player_controller.rb +14 -0
- data/lib/rgame/engine/matrix.rb +32 -0
- data/lib/rgame/engine/node2d.rb +271 -0
- data/lib/rgame/engine/path.rb +78 -0
- data/lib/rgame/engine/pool.rb +51 -0
- data/lib/rgame/engine/resettable.rb +67 -0
- data/lib/rgame/engine/scene/scene_stack.rb +65 -0
- data/lib/rgame/engine/signal.rb +75 -0
- data/lib/rgame/engine/spatial_hash.rb +71 -0
- data/lib/rgame/engine/tile_collision.rb +78 -0
- data/lib/rgame/engine/tile_map.rb +149 -0
- data/lib/rgame/engine/tileset.rb +101 -0
- data/lib/rgame/engine/timer.rb +51 -0
- data/lib/rgame/engine.rb +68 -0
- data/lib/rgame/fonts/LiberationSans-Regular.ttf +0 -0
- data/lib/rgame/fonts/OFL.txt +102 -0
- data/lib/rgame/game.rb +129 -0
- data/lib/rgame/util/color.rb +27 -0
- data/lib/rgame/util/controls.rb +107 -0
- data/lib/rgame/util/tensor.rb +12 -0
- data/lib/rgame/util.rb +8 -0
- data/lib/rgame/version.rb +12 -0
- data/lib/rgame.rb +20 -0
- 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 */
|