rgame 0.2.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +135 -1
- data/README.md +67 -65
- data/docs/api/README.md +86 -82
- data/docs/api/app.md +125 -68
- data/docs/api/assets.md +203 -165
- data/docs/api/audio.md +130 -89
- data/docs/api/cli.md +259 -0
- data/docs/api/components.md +1032 -265
- data/docs/api/drawing.md +160 -160
- data/docs/api/examples.md +263 -0
- data/docs/api/game.md +149 -50
- data/docs/api/images.md +39 -39
- data/docs/api/input.md +226 -148
- data/docs/api/internals.md +241 -62
- data/docs/api/localization.md +285 -0
- data/docs/api/scene_graph.md +397 -244
- data/docs/api/signals.md +81 -76
- data/docs/api/systems.md +186 -73
- data/docs/api/text.md +58 -41
- data/docs/api/tile_maps.md +237 -0
- data/docs/api/toolbox.md +395 -146
- data/docs/api/ui.md +883 -54
- data/docs/api/values.md +216 -33
- data/examples/assets/README.md +322 -0
- data/examples/assets/blip.ogg +0 -0
- data/examples/assets/glyphs.json +5 -0
- data/examples/assets/glyphs.png +0 -0
- data/examples/assets/hero.json +12 -0
- data/examples/assets/hero.png +0 -0
- data/examples/assets/icons.json +13 -0
- data/examples/assets/icons.png +0 -0
- data/examples/assets/music.ogg +0 -0
- data/examples/assets/skills.json +10 -0
- data/examples/assets/skills.png +0 -0
- data/examples/assets/tileset.png +0 -0
- data/examples/assets/tileset.tsx +65 -0
- data/examples/assets/town.tmx +26 -0
- data/examples/assets/ui.json +11 -0
- data/examples/assets/ui.png +0 -0
- data/examples/collision/locales/en.yml +8 -0
- data/examples/collision/main.rb +316 -0
- data/examples/collision_tiles/locales/en.yml +9 -0
- data/examples/collision_tiles/main.rb +274 -0
- data/examples/fullscreen/locales/en.yml +10 -0
- data/examples/fullscreen/main.rb +216 -0
- data/examples/game_menu/locales/en.yml +8 -0
- data/examples/game_menu/main.rb +170 -0
- data/examples/input_glyphs/locales/en.yml +14 -0
- data/examples/input_glyphs/main.rb +213 -0
- data/examples/jump_topdown/locales/en.yml +9 -0
- data/examples/jump_topdown/main.rb +178 -0
- data/examples/localization/locales/de.yml +12 -0
- data/examples/localization/locales/en.yml +13 -0
- data/examples/localization/main.rb +158 -0
- data/examples/menu_navigation/locales/en.yml +23 -0
- data/examples/menu_navigation/main.rb +365 -0
- data/examples/music/locales/en.yml +7 -0
- data/examples/music/main.rb +134 -0
- data/examples/pathfinding/locales/en.yml +17 -0
- data/examples/pathfinding/main.rb +298 -0
- data/examples/pooling/locales/en.yml +7 -0
- data/examples/pooling/main.rb +259 -0
- data/examples/quick_wheel/locales/en.yml +16 -0
- data/examples/quick_wheel/main.rb +184 -0
- data/examples/radial_menu/locales/en.yml +16 -0
- data/examples/radial_menu/main.rb +184 -0
- data/examples/save_load/locales/en.yml +11 -0
- data/examples/save_load/main.rb +207 -0
- data/examples/save_load_ids/locales/en.yml +11 -0
- data/examples/save_load_ids/main.rb +322 -0
- data/examples/scroll_map/locales/en.yml +4 -0
- data/examples/scroll_map/main.rb +140 -0
- data/examples/signals/locales/en.yml +6 -0
- data/examples/signals/main.rb +278 -0
- data/examples/skill_bar/locales/en.yml +14 -0
- data/examples/skill_bar/main.rb +159 -0
- data/examples/sound/locales/en.yml +6 -0
- data/examples/sound/main.rb +122 -0
- data/examples/split_screen/locales/en.yml +9 -0
- data/examples/split_screen/main.rb +304 -0
- data/examples/sprite/locales/en.yml +8 -0
- data/examples/sprite/main.rb +180 -0
- data/examples/timer/locales/en.yml +12 -0
- data/examples/timer/main.rb +273 -0
- data/examples/velocity/locales/en.yml +6 -0
- data/examples/velocity/main.rb +196 -0
- data/examples/walk/locales/en.yml +4 -0
- data/examples/walk/main.rb +99 -0
- data/exe/rgame +9 -0
- data/ext/rgame_core/app/app.c +33 -3
- data/ext/rgame_core/app/locale.c +67 -0
- data/ext/rgame_core/app/locale.h +28 -0
- data/ext/rgame_core/audio/audio.c +39 -2
- data/ext/rgame_core/example.rb +0 -49
- data/ext/rgame_core/extconf.rb +0 -125
- data/ext/rgame_core/include/rgame/core.h +38 -2
- data/ext/rgame_core/ruby/audio_ext.c +10 -5
- data/ext/rgame_core/ruby/core_ext.c +30 -7
- data/ext/rgame_core/ruby/core_ext.h +3 -0
- data/ext/rgame_core/ruby/locale_ext.c +44 -0
- data/ext/rgame_core/ruby/recording_ext.c +1 -1
- data/ext/rgame_core/ruby/renderer_ext.c +20 -20
- data/ext/rgame_util/extconf.rb +2 -20
- data/ext/rgame_util/route_search.c +305 -0
- data/ext/rgame_util/route_search.h +86 -0
- data/ext/rgame_util/route_search_ext.c +150 -0
- data/ext/rgame_util/solid_grid.c +58 -0
- data/ext/rgame_util/solid_grid.h +49 -0
- data/ext/rgame_util/solid_grid_ext.c +161 -0
- data/ext/rgame_util/tile_sweep.c +164 -0
- data/ext/rgame_util/tile_sweep.h +62 -0
- data/ext/rgame_util/tile_sweep_ext.c +155 -0
- data/ext/rgame_util/util_ext.c +3 -0
- data/ext/rgame_util/util_ext.h +15 -0
- data/lib/rgame/boot.rb +0 -10
- data/lib/rgame/cli/new_project.rb +139 -0
- data/lib/rgame/cli/templates/Gemfile.tt +23 -0
- data/lib/rgame/cli/templates/README.md.tt +93 -0
- data/lib/rgame/cli/templates/Rakefile.tt +9 -0
- data/lib/rgame/cli/templates/assets/locales/en.yml.tt +10 -0
- data/lib/rgame/cli/templates/game.rb.tt +23 -0
- data/lib/rgame/cli/templates/gitignore.tt +12 -0
- data/lib/rgame/cli/templates/main.rb.tt +11 -0
- data/lib/rgame/cli/templates/nodes/root.rb.tt +24 -0
- data/lib/rgame/cli/templates/rspec.tt +2 -0
- data/lib/rgame/cli/templates/rubocop.yml.tt +75 -0
- data/lib/rgame/cli/templates/ruby-version.tt +1 -0
- data/lib/rgame/cli/templates/spec/locales_spec.rb.tt +18 -0
- data/lib/rgame/cli/templates/spec/nodes/root_spec.rb.tt +18 -0
- data/lib/rgame/cli/templates/spec/spec_helper.rb.tt +37 -0
- data/lib/rgame/cli.rb +66 -0
- data/lib/rgame/core/app.rb +6 -44
- data/lib/rgame/core/asset_manager.rb +13 -31
- data/lib/rgame/core/audio.rb +37 -16
- data/lib/rgame/core/font.rb +0 -3
- data/lib/rgame/core/locale.rb +22 -0
- data/lib/rgame/core/nine_slice.rb +0 -21
- data/lib/rgame/core/renderer.rb +6 -63
- data/lib/rgame/core/sprite_sheet.rb +0 -3
- data/lib/rgame/core/tile_map_renderer.rb +2 -19
- data/lib/rgame/core/ui_atlas.rb +28 -13
- data/lib/rgame/core.rb +1 -8
- data/lib/rgame/engine/actor_blockers.rb +131 -0
- data/lib/rgame/engine/animation_set.rb +1 -0
- data/lib/rgame/engine/audio_director.rb +36 -6
- data/lib/rgame/engine/bounds_blockers.rb +74 -0
- data/lib/rgame/engine/camera.rb +3 -3
- data/lib/rgame/engine/circle_collider.rb +4 -2
- data/lib/rgame/engine/collision_box.rb +26 -1
- data/lib/rgame/engine/collision_system.rb +110 -22
- data/lib/rgame/engine/component.rb +34 -10
- data/lib/rgame/engine/components/action_trigger.rb +0 -1
- data/lib/rgame/engine/components/animated_sprite.rb +27 -25
- data/lib/rgame/engine/components/box_collider.rb +99 -0
- data/lib/rgame/engine/components/camera_follow.rb +6 -5
- data/lib/rgame/engine/components/character_body.rb +20 -61
- data/lib/rgame/engine/components/circle_collider.rb +47 -11
- data/lib/rgame/engine/components/collision_world.rb +159 -31
- data/lib/rgame/engine/components/despawn_offscreen.rb +24 -8
- data/lib/rgame/engine/components/feet_collider.rb +61 -0
- data/lib/rgame/engine/components/hop.rb +76 -0
- data/lib/rgame/engine/components/identity.rb +73 -0
- data/lib/rgame/engine/components/mover.rb +285 -0
- data/lib/rgame/engine/components/navigator.rb +145 -0
- data/lib/rgame/engine/components/path_follow.rb +123 -31
- data/lib/rgame/engine/components/player_controller.rb +5 -2
- data/lib/rgame/engine/components/pool.rb +1 -1
- data/lib/rgame/engine/components/screen_wrap.rb +33 -11
- data/lib/rgame/engine/components/sprite.rb +12 -6
- data/lib/rgame/engine/components/targeting.rb +9 -11
- data/lib/rgame/engine/components/thrust_controller.rb +1 -1
- data/lib/rgame/engine/components/tile_world.rb +41 -13
- data/lib/rgame/engine/components/timer.rb +1 -1
- data/lib/rgame/engine/components/velocity.rb +23 -7
- data/lib/rgame/engine/components/wander_controller.rb +6 -2
- data/lib/rgame/engine/components/world.rb +133 -0
- data/lib/rgame/engine/contact_set.rb +74 -0
- data/lib/rgame/engine/culling.rb +4 -6
- data/lib/rgame/engine/debug_overlay.rb +5 -11
- data/lib/rgame/engine/i18n/plural.rb +45 -0
- data/lib/rgame/engine/i18n/plural_rules.rb +82 -0
- data/lib/rgame/engine/i18n/template.rb +59 -0
- data/lib/rgame/engine/i18n.rb +276 -51
- data/lib/rgame/engine/input/action_mapper.rb +0 -26
- data/lib/rgame/engine/input/actions.rb +2 -8
- data/lib/rgame/engine/input/input_map.rb +39 -21
- data/lib/rgame/engine/nav_grid.rb +87 -0
- data/lib/rgame/engine/node2d.rb +248 -133
- data/lib/rgame/engine/path.rb +4 -6
- data/lib/rgame/engine/players.rb +6 -13
- data/lib/rgame/engine/presentation.rb +171 -0
- data/lib/rgame/engine/scene/scene_stack.rb +4 -4
- data/lib/rgame/engine/sealed_privates.rb +54 -0
- data/lib/rgame/engine/spatial_hash.rb +51 -19
- data/lib/rgame/engine/text.rb +194 -0
- data/lib/rgame/engine/tile_blockers.rb +63 -0
- data/lib/rgame/engine/tile_map.rb +2 -3
- data/lib/rgame/engine/tile_map_layer.rb +0 -2
- data/lib/rgame/engine/tileset.rb +2 -4
- data/lib/rgame/engine/timer.rb +2 -2
- data/lib/rgame/engine/ui/button.rb +248 -0
- data/lib/rgame/engine/ui/column.rb +20 -0
- data/lib/rgame/engine/ui/icon_button.rb +93 -0
- data/lib/rgame/engine/ui/menu.rb +246 -71
- data/lib/rgame/engine/ui/navigation.rb +57 -0
- data/lib/rgame/engine/ui/nine_slice_style.rb +50 -0
- data/lib/rgame/engine/ui/option_button.rb +163 -0
- data/lib/rgame/engine/ui/panel_button.rb +32 -0
- data/lib/rgame/engine/ui/panel_menu.rb +36 -0
- data/lib/rgame/engine/ui/pointing.rb +146 -0
- data/lib/rgame/engine/ui/radial_menu.rb +85 -0
- data/lib/rgame/engine/ui/ring.rb +55 -0
- data/lib/rgame/engine/ui/row.rb +21 -0
- data/lib/rgame/engine/ui/shape_style.rb +102 -0
- data/lib/rgame/engine/ui/stack.rb +58 -0
- data/lib/rgame/engine/ui/stepping.rb +93 -0
- data/lib/rgame/engine/ui/text_button.rb +59 -0
- data/lib/rgame/engine/viewports.rb +2 -5
- data/lib/rgame/engine/world_view.rb +5 -4
- data/lib/rgame/engine.rb +31 -25
- data/lib/rgame/game.rb +99 -27
- data/lib/rgame/rubocop/cop/game/draw_in_local_space.rb +103 -0
- data/lib/rgame/rubocop/cop/game/hot_path.rb +36 -0
- data/lib/rgame/rubocop/cop/game/layer_boundary.rb +43 -0
- data/lib/rgame/rubocop/cop/game/no_core_in_engine_layer.rb +100 -0
- data/lib/rgame/rubocop/cop/game/no_engine_in_core_layer.rb +84 -0
- data/lib/rgame/rubocop/cop/game/no_interpolation_in_hot_path.rb +50 -0
- data/lib/rgame/rubocop/cop/game/no_literal_text.rb +41 -0
- data/lib/rgame/rubocop/cop/game/no_needless_allocation.rb +112 -0
- data/lib/rgame/rubocop/default.yml +39 -0
- data/lib/rgame/rubocop/plugin.rb +45 -0
- data/lib/rgame/rubocop.rb +11 -0
- data/lib/rgame/util/color.rb +20 -24
- data/lib/rgame/util/controls.rb +14 -28
- data/lib/rgame/util/route_search.rb +27 -0
- data/lib/rgame/util/save_file.rb +107 -0
- data/lib/rgame/util/solid_grid.rb +37 -0
- data/lib/rgame/util/tensor.rb +0 -9
- data/lib/rgame/util/tile_sweep.rb +36 -0
- data/lib/rgame/util/z.rb +4 -14
- data/lib/rgame/util.rb +4 -3
- data/lib/rgame/version.rb +1 -1
- data/lib/rgame.rb +0 -15
- metadata +152 -11
- data/lib/rgame/engine/actor.rb +0 -53
- data/lib/rgame/engine/body.rb +0 -49
- data/lib/rgame/engine/cached_label.rb +0 -33
- data/lib/rgame/engine/input/player_controller.rb +0 -14
- data/lib/rgame/engine/matrix.rb +0 -32
- data/lib/rgame/engine/resettable.rb +0 -67
- data/lib/rgame/engine/tile_collision.rb +0 -78
- data/lib/rgame/engine/ui/menu_item.rb +0 -84
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* solid_grid.c — the solidity store of a tile grid. Pure; see solid_grid.h.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#include "solid_grid.h"
|
|
6
|
+
|
|
7
|
+
#include <stdlib.h>
|
|
8
|
+
|
|
9
|
+
bool rgame_solid_grid_size_ok(int64_t width, int64_t height) {
|
|
10
|
+
return width >= 0 && height >= 0 && (height == 0 || width <= INT32_MAX / height);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
bool rgame_solid_grid_init(rgame_solid_grid *grid, int32_t width, int32_t height) {
|
|
14
|
+
if (!rgame_solid_grid_size_ok(width, height)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
size_t count = (size_t)width * (size_t)height;
|
|
18
|
+
uint8_t *cells = NULL;
|
|
19
|
+
if (count > 0) {
|
|
20
|
+
cells = calloc(count, 1);
|
|
21
|
+
if (!cells) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
grid->width = width;
|
|
26
|
+
grid->height = height;
|
|
27
|
+
grid->cells = cells;
|
|
28
|
+
grid->revision = 0;
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
void rgame_solid_grid_free(rgame_solid_grid *grid) {
|
|
33
|
+
free(grid->cells);
|
|
34
|
+
grid->cells = NULL;
|
|
35
|
+
grid->width = 0;
|
|
36
|
+
grid->height = 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
bool rgame_solid_grid_inside(const rgame_solid_grid *grid, int32_t col, int32_t row) {
|
|
40
|
+
return col >= 0 && row >= 0 && col < grid->width && row < grid->height;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
bool rgame_solid_grid_solid(const rgame_solid_grid *grid, int32_t col, int32_t row) {
|
|
44
|
+
return rgame_solid_grid_inside(grid, col, row) && grid->cells[row * grid->width + col];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
bool rgame_solid_grid_set(rgame_solid_grid *grid, int32_t col, int32_t row, bool solid) {
|
|
48
|
+
if (!rgame_solid_grid_inside(grid, col, row)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
uint8_t *cell = &grid->cells[row * grid->width + col];
|
|
52
|
+
uint8_t value = solid ? 1 : 0;
|
|
53
|
+
if (*cell != value) {
|
|
54
|
+
*cell = value;
|
|
55
|
+
grid->revision++;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#ifndef RGAME_SOLID_GRID_H
|
|
2
|
+
#define RGAME_SOLID_GRID_H
|
|
3
|
+
|
|
4
|
+
#include <stdbool.h>
|
|
5
|
+
#include <stdint.h>
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* Which cells of a tile grid are solid — pure, no Ruby, no SDL, no GL.
|
|
9
|
+
*
|
|
10
|
+
* This is the one store of a tile world's solidity: the blockers that stop a
|
|
11
|
+
* walker and the route search that plans one both read it, so a cell changed
|
|
12
|
+
* here is changed for both at once and the two cannot disagree about a wall.
|
|
13
|
+
*
|
|
14
|
+
* `revision` is how a reader that keeps something derived from the cells (a
|
|
15
|
+
* search's region labels) knows the cells have moved under it. It changes on
|
|
16
|
+
* every write that changes a cell and on no other, so a derived value keyed to
|
|
17
|
+
* it is rebuilt exactly when it has to be.
|
|
18
|
+
*
|
|
19
|
+
* Anything outside the grid is open, which is what a tile map answers past its
|
|
20
|
+
* edges.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
typedef struct {
|
|
24
|
+
int32_t width;
|
|
25
|
+
int32_t height;
|
|
26
|
+
uint8_t *cells; /* width * height, row-major; 1 solid, 0 open */
|
|
27
|
+
uint64_t revision; /* moves whenever a cell changes, and only then — and 64
|
|
28
|
+
bits, so it never wraps back onto a value a reader kept */
|
|
29
|
+
} rgame_solid_grid;
|
|
30
|
+
|
|
31
|
+
/* Whether a grid of this size can exist: neither side negative, and the cell
|
|
32
|
+
* count within int32_t, so every in-bounds flat index fits the type the search
|
|
33
|
+
* stores. Zero on either side is a valid, empty grid. */
|
|
34
|
+
bool rgame_solid_grid_size_ok(int64_t width, int64_t height);
|
|
35
|
+
|
|
36
|
+
/* Every cell open, revision 0. False — with nothing to free — for a size that is
|
|
37
|
+
* not ok or when allocation fails. */
|
|
38
|
+
bool rgame_solid_grid_init(rgame_solid_grid *grid, int32_t width, int32_t height);
|
|
39
|
+
void rgame_solid_grid_free(rgame_solid_grid *grid);
|
|
40
|
+
|
|
41
|
+
bool rgame_solid_grid_inside(const rgame_solid_grid *grid, int32_t col, int32_t row);
|
|
42
|
+
|
|
43
|
+
/* False outside the grid. */
|
|
44
|
+
bool rgame_solid_grid_solid(const rgame_solid_grid *grid, int32_t col, int32_t row);
|
|
45
|
+
|
|
46
|
+
/* False, changing nothing, for a cell outside the grid. */
|
|
47
|
+
bool rgame_solid_grid_set(rgame_solid_grid *grid, int32_t col, int32_t row, bool solid);
|
|
48
|
+
|
|
49
|
+
#endif /* RGAME_SOLID_GRID_H */
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* solid_grid_ext.c — the Ruby binding for RGame::Util::SolidGrid.
|
|
3
|
+
*
|
|
4
|
+
* The store lives in solid_grid.{c,h}, which includes no ruby.h and is covered
|
|
5
|
+
* directly by the Check suite. This file is argument checking and wrapping.
|
|
6
|
+
*
|
|
7
|
+
* A grid's size is fixed at construction and there is no allocator to reach it
|
|
8
|
+
* any other way: a RouteSearch keeps a pointer into the grid it was built over,
|
|
9
|
+
* sized to its cells, so a grid that could be re-initialized or copied into a
|
|
10
|
+
* different size would leave that search indexing past the end.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
#include "util_ext.h"
|
|
14
|
+
|
|
15
|
+
#include "solid_grid.h"
|
|
16
|
+
|
|
17
|
+
static long live_grids = 0;
|
|
18
|
+
|
|
19
|
+
static void solid_grid_free(void *ptr) {
|
|
20
|
+
rgame_solid_grid_free(ptr);
|
|
21
|
+
xfree(ptr);
|
|
22
|
+
live_grids--;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static size_t solid_grid_memsize(const void *ptr) {
|
|
26
|
+
const rgame_solid_grid *grid = ptr;
|
|
27
|
+
return sizeof(*grid) + (size_t)grid->width * (size_t)grid->height;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static const rb_data_type_t solid_grid_data_type = {
|
|
31
|
+
.wrap_struct_name = "rgame_solid_grid",
|
|
32
|
+
.function = {
|
|
33
|
+
.dmark = NULL, /* no Ruby objects inside */
|
|
34
|
+
.dfree = solid_grid_free,
|
|
35
|
+
.dsize = solid_grid_memsize,
|
|
36
|
+
},
|
|
37
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
rgame_solid_grid *rgame_util_solid_grid(VALUE value) {
|
|
41
|
+
return rb_check_typeddata(value, &solid_grid_data_type);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/*
|
|
45
|
+
* A cell coordinate: an Integer, or a TypeError. An Integer too large for the
|
|
46
|
+
* grid's index type is not an error but a cell outside the grid, answered like
|
|
47
|
+
* any other — which is why a Bignum is checked for before converting, where
|
|
48
|
+
* NUM2INT would raise RangeError instead.
|
|
49
|
+
*/
|
|
50
|
+
bool rgame_util_cell_coordinate(VALUE value, int32_t *out) {
|
|
51
|
+
if (FIXNUM_P(value)) {
|
|
52
|
+
long long number = NUM2LL(value);
|
|
53
|
+
if (number < INT32_MIN || number > INT32_MAX) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
*out = (int32_t)number;
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
if (RB_TYPE_P(value, T_BIGNUM)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
rb_raise(rb_eTypeError, "a cell coordinate must be an Integer, got %" PRIsVALUE,
|
|
63
|
+
rb_obj_class(value));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static int64_t dimension(VALUE value, const char *name) {
|
|
67
|
+
if (RB_TYPE_P(value, T_BIGNUM)) {
|
|
68
|
+
rb_raise(rb_eArgError, "%s is too large for a grid", name);
|
|
69
|
+
}
|
|
70
|
+
if (!FIXNUM_P(value)) {
|
|
71
|
+
rb_raise(rb_eTypeError, "%s must be an Integer, got %" PRIsVALUE, name, rb_obj_class(value));
|
|
72
|
+
}
|
|
73
|
+
return NUM2LL(value);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* SolidGrid.new(width, height) — every cell open.
|
|
77
|
+
*
|
|
78
|
+
* Refuses a negative size, and one whose cell count passes 2**31 - 1, with
|
|
79
|
+
* ArgumentError. A zero size is an empty grid with no inside. */
|
|
80
|
+
static VALUE solid_grid_s_new(VALUE klass, VALUE width_value, VALUE height_value) {
|
|
81
|
+
int64_t width = dimension(width_value, "width");
|
|
82
|
+
int64_t height = dimension(height_value, "height");
|
|
83
|
+
if (width < 0 || height < 0) {
|
|
84
|
+
rb_raise(rb_eArgError, "a grid cannot be %lldx%lld", (long long)width, (long long)height);
|
|
85
|
+
}
|
|
86
|
+
if (!rgame_solid_grid_size_ok(width, height)) {
|
|
87
|
+
rb_raise(rb_eArgError, "a %lldx%lld grid has more cells than it can index", (long long)width,
|
|
88
|
+
(long long)height);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
rgame_solid_grid *grid;
|
|
92
|
+
VALUE object = TypedData_Make_Struct(klass, rgame_solid_grid, &solid_grid_data_type, grid);
|
|
93
|
+
live_grids++;
|
|
94
|
+
if (!rgame_solid_grid_init(grid, (int32_t)width, (int32_t)height)) {
|
|
95
|
+
rb_memerror();
|
|
96
|
+
}
|
|
97
|
+
return object;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
static VALUE solid_grid_width(VALUE self) { return INT2NUM(rgame_util_solid_grid(self)->width); }
|
|
101
|
+
static VALUE solid_grid_height(VALUE self) { return INT2NUM(rgame_util_solid_grid(self)->height); }
|
|
102
|
+
|
|
103
|
+
/* A count of the changes made to the grid: it moves whenever a cell changes and
|
|
104
|
+
* only then, so something derived from the cells can tell whether it is stale. */
|
|
105
|
+
static VALUE solid_grid_revision(VALUE self) {
|
|
106
|
+
return ULL2NUM(rgame_util_solid_grid(self)->revision);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* solid?(col, row) — false outside the grid. */
|
|
110
|
+
static VALUE solid_grid_solid_p(VALUE self, VALUE col_value, VALUE row_value) {
|
|
111
|
+
int32_t col = 0;
|
|
112
|
+
int32_t row = 0;
|
|
113
|
+
bool col_fits = rgame_util_cell_coordinate(col_value, &col);
|
|
114
|
+
bool row_fits = rgame_util_cell_coordinate(row_value, &row);
|
|
115
|
+
return col_fits && row_fits && rgame_solid_grid_solid(rgame_util_solid_grid(self), col, row)
|
|
116
|
+
? Qtrue
|
|
117
|
+
: Qfalse;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* set_solid(col, row, solid) — raises IndexError for a cell outside the grid,
|
|
121
|
+
* since a write that silently went nowhere is a wall that is not there. */
|
|
122
|
+
static VALUE solid_grid_set_solid(VALUE self, VALUE col_value, VALUE row_value, VALUE solid) {
|
|
123
|
+
int32_t col = 0;
|
|
124
|
+
int32_t row = 0;
|
|
125
|
+
bool col_fits = rgame_util_cell_coordinate(col_value, &col);
|
|
126
|
+
bool row_fits = rgame_util_cell_coordinate(row_value, &row);
|
|
127
|
+
rgame_solid_grid *grid = rgame_util_solid_grid(self);
|
|
128
|
+
if (!col_fits || !row_fits || !rgame_solid_grid_set(grid, col, row, RTEST(solid))) {
|
|
129
|
+
rb_raise(rb_eIndexError, "cell (%" PRIsVALUE ", %" PRIsVALUE ") is outside a %dx%d grid",
|
|
130
|
+
col_value, row_value, grid->width, grid->height);
|
|
131
|
+
}
|
|
132
|
+
return solid;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
static VALUE solid_grid_inspect(VALUE self) {
|
|
136
|
+
rgame_solid_grid *grid = rgame_util_solid_grid(self);
|
|
137
|
+
return rb_sprintf("#<%" PRIsVALUE " %dx%d revision=%llu>", rb_obj_class(self), grid->width,
|
|
138
|
+
grid->height, (unsigned long long)grid->revision);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* SolidGrid.debug_live_grids — how many grids are holding cells. Test-only, and
|
|
142
|
+
* named so: it is what lets a spec see a grid's cells freed with it. */
|
|
143
|
+
static VALUE solid_grid_s_debug_live_grids(VALUE klass) {
|
|
144
|
+
(void)klass;
|
|
145
|
+
return LONG2NUM(live_grids);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
void rgame_init_solid_grid(VALUE mUtil) {
|
|
149
|
+
VALUE cSolidGrid = rb_define_class_under(mUtil, "SolidGrid", rb_cObject);
|
|
150
|
+
|
|
151
|
+
rb_undef_alloc_func(cSolidGrid);
|
|
152
|
+
rb_define_singleton_method(cSolidGrid, "new", solid_grid_s_new, 2);
|
|
153
|
+
rb_define_singleton_method(cSolidGrid, "debug_live_grids", solid_grid_s_debug_live_grids, 0);
|
|
154
|
+
|
|
155
|
+
rb_define_method(cSolidGrid, "width", solid_grid_width, 0);
|
|
156
|
+
rb_define_method(cSolidGrid, "height", solid_grid_height, 0);
|
|
157
|
+
rb_define_method(cSolidGrid, "revision", solid_grid_revision, 0);
|
|
158
|
+
rb_define_method(cSolidGrid, "solid?", solid_grid_solid_p, 2);
|
|
159
|
+
rb_define_method(cSolidGrid, "set_solid", solid_grid_set_solid, 3);
|
|
160
|
+
rb_define_method(cSolidGrid, "inspect", solid_grid_inspect, 0);
|
|
161
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* tile_sweep.c — a box against a solid grid's tiles. Pure; see tile_sweep.h.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#include "tile_sweep.h"
|
|
6
|
+
|
|
7
|
+
#include <math.h>
|
|
8
|
+
|
|
9
|
+
#ifdef __clang__
|
|
10
|
+
#pragma STDC FP_CONTRACT OFF
|
|
11
|
+
#endif
|
|
12
|
+
|
|
13
|
+
/* How far inside an edge a box's far side is measured, so a box resting flush
|
|
14
|
+
* against a tile does not count as overlapping it. */
|
|
15
|
+
static const double EDGE_EPS = 1e-9;
|
|
16
|
+
|
|
17
|
+
/* A travel window's stride, as a fraction of a tile. A window spans two strides. */
|
|
18
|
+
static const double STRIDE = 0.25;
|
|
19
|
+
|
|
20
|
+
static bool tile_index(double edge, double tile_size, int32_t count, int32_t *out) {
|
|
21
|
+
double index = floor(edge / tile_size);
|
|
22
|
+
if (!(index >= 0.0 && index < (double)count)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
*out = (int32_t)index;
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static bool tile_span(double near_edge, double far_edge, double tile_size, int32_t count, int32_t *first,
|
|
30
|
+
int32_t *last) {
|
|
31
|
+
double low = floor(near_edge / tile_size);
|
|
32
|
+
double high = floor(far_edge / tile_size);
|
|
33
|
+
if (isnan(low) || isnan(high)) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (low < 0.0) {
|
|
37
|
+
low = 0.0;
|
|
38
|
+
}
|
|
39
|
+
if (high > (double)count - 1.0) {
|
|
40
|
+
high = (double)count - 1.0;
|
|
41
|
+
}
|
|
42
|
+
if (low > high) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
*first = (int32_t)low;
|
|
46
|
+
*last = (int32_t)high;
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static bool solid_in_column(const rgame_solid_grid *grid, int32_t col, int32_t first_row, int32_t last_row) {
|
|
51
|
+
for (int32_t row = first_row; row <= last_row; row++) {
|
|
52
|
+
if (grid->cells[row * grid->width + col]) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static bool solid_in_row(const rgame_solid_grid *grid, int32_t row, int32_t first_col, int32_t last_col) {
|
|
60
|
+
for (int32_t col = first_col; col <= last_col; col++) {
|
|
61
|
+
if (grid->cells[row * grid->width + col]) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
double rgame_tile_sweep_resolve_x(const rgame_tile_sweep *sweep, double x, double y, double w, double h,
|
|
69
|
+
double dx) {
|
|
70
|
+
double landed = x + dx;
|
|
71
|
+
if (dx == 0.0) {
|
|
72
|
+
return landed;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const rgame_solid_grid *grid = sweep->grid;
|
|
76
|
+
int32_t first_row = 0;
|
|
77
|
+
int32_t last_row = 0;
|
|
78
|
+
if (!tile_span(y, y + h - EDGE_EPS, sweep->tile_height, grid->height, &first_row, &last_row)) {
|
|
79
|
+
return landed;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
int32_t col = 0;
|
|
83
|
+
if (dx > 0.0) {
|
|
84
|
+
if (tile_index(landed + w - EDGE_EPS, sweep->tile_width, grid->width, &col) &&
|
|
85
|
+
solid_in_column(grid, col, first_row, last_row)) {
|
|
86
|
+
return col * sweep->tile_width - w;
|
|
87
|
+
}
|
|
88
|
+
} else if (tile_index(landed, sweep->tile_width, grid->width, &col) &&
|
|
89
|
+
solid_in_column(grid, col, first_row, last_row)) {
|
|
90
|
+
return (col + 1) * sweep->tile_width;
|
|
91
|
+
}
|
|
92
|
+
return landed;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
double rgame_tile_sweep_resolve_y(const rgame_tile_sweep *sweep, double x, double y, double w, double h,
|
|
96
|
+
double dy) {
|
|
97
|
+
double landed = y + dy;
|
|
98
|
+
if (dy == 0.0) {
|
|
99
|
+
return landed;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const rgame_solid_grid *grid = sweep->grid;
|
|
103
|
+
int32_t first_col = 0;
|
|
104
|
+
int32_t last_col = 0;
|
|
105
|
+
if (!tile_span(x, x + w - EDGE_EPS, sweep->tile_width, grid->width, &first_col, &last_col)) {
|
|
106
|
+
return landed;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
int32_t row = 0;
|
|
110
|
+
if (dy > 0.0) {
|
|
111
|
+
if (tile_index(landed + h - EDGE_EPS, sweep->tile_height, grid->height, &row) &&
|
|
112
|
+
solid_in_row(grid, row, first_col, last_col)) {
|
|
113
|
+
return row * sweep->tile_height - h;
|
|
114
|
+
}
|
|
115
|
+
} else if (tile_index(landed, sweep->tile_height, grid->height, &row) &&
|
|
116
|
+
solid_in_row(grid, row, first_col, last_col)) {
|
|
117
|
+
return (row + 1) * sweep->tile_height;
|
|
118
|
+
}
|
|
119
|
+
return landed;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Short of the intended landing: behind it, in the direction of travel. A
|
|
123
|
+
* landing past it is not short, which is how CollisionSystem reads a source. */
|
|
124
|
+
static bool short_of(double landed, double intended, double delta) {
|
|
125
|
+
return delta > 0.0 ? landed < intended : delta < 0.0 && landed > intended;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static bool window_clear(const rgame_tile_sweep *sweep, double x, double y, double w, double h, double dx,
|
|
129
|
+
double dy) {
|
|
130
|
+
return !short_of(rgame_tile_sweep_resolve_x(sweep, x, y, w, h, dx), x + dx, dx) &&
|
|
131
|
+
!short_of(rgame_tile_sweep_resolve_y(sweep, x + dx, y, w, h, dy), y + dy, dy) &&
|
|
132
|
+
!short_of(rgame_tile_sweep_resolve_y(sweep, x, y, w, h, dy), y + dy, dy) &&
|
|
133
|
+
!short_of(rgame_tile_sweep_resolve_x(sweep, x, y + dy, w, h, dx), x + dx, dx);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
double rgame_tile_sweep_travel_windows(const rgame_tile_sweep *sweep, double dx, double dy) {
|
|
137
|
+
double across = ceil(fabs(dx) / (sweep->tile_width * STRIDE));
|
|
138
|
+
double down = ceil(fabs(dy) / (sweep->tile_height * STRIDE));
|
|
139
|
+
if (isnan(across) || isnan(down)) {
|
|
140
|
+
return NAN;
|
|
141
|
+
}
|
|
142
|
+
double steps = across > down ? across : down;
|
|
143
|
+
return steps > 1.0 ? steps : 1.0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
bool rgame_tile_sweep_travel(const rgame_tile_sweep *sweep, double x, double y, double w, double h, double dx,
|
|
147
|
+
double dy) {
|
|
148
|
+
double steps = rgame_tile_sweep_travel_windows(sweep, dx, dy);
|
|
149
|
+
if (!(steps <= (double)INT32_MAX)) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
int32_t count = (int32_t)steps;
|
|
153
|
+
double step_x = dx / count;
|
|
154
|
+
double step_y = dy / count;
|
|
155
|
+
int32_t span = count < 2 ? count : 2;
|
|
156
|
+
|
|
157
|
+
for (int32_t sample = 0; sample <= count - span; sample++) {
|
|
158
|
+
if (!window_clear(sweep, x + step_x * sample, y + step_y * sample, w, h, step_x * span,
|
|
159
|
+
step_y * span)) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#ifndef RGAME_TILE_SWEEP_H
|
|
2
|
+
#define RGAME_TILE_SWEEP_H
|
|
3
|
+
|
|
4
|
+
#include <stdbool.h>
|
|
5
|
+
#include <stdint.h>
|
|
6
|
+
|
|
7
|
+
#include "solid_grid.h"
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* An axis-aligned box against the solid tiles of a grid — pure, no Ruby, no
|
|
11
|
+
* SDL, no GL.
|
|
12
|
+
*
|
|
13
|
+
* Two questions, answered by one piece of arithmetic so they cannot disagree:
|
|
14
|
+
*
|
|
15
|
+
* - **Resolve**: where does the box's left (top) edge land moving dx (dy)? A
|
|
16
|
+
* step that would enter a solid tile snaps the box flush against that tile's
|
|
17
|
+
* edge. Each axis is resolved on its own; feeding one axis the other's result
|
|
18
|
+
* (and so sliding along walls) is the caller's order to choose.
|
|
19
|
+
* - **Travel**: can the box move by (dx, dy) without any resolve along the way
|
|
20
|
+
* falling short of where it was heading? This is how a route is checked
|
|
21
|
+
* against the same resolver that will stop the walker on it.
|
|
22
|
+
*
|
|
23
|
+
* Resolving assumes a step smaller than a tile: a longer one can pass through a
|
|
24
|
+
* tile without seeing it. Travel is built for exactly that limit. It sweeps the
|
|
25
|
+
* box in overlapping windows half a tile long at a quarter-tile stride, and
|
|
26
|
+
* resolves each window X-then-Y and Y-then-X. Both orders together cover every
|
|
27
|
+
* position between a window's ends, and the overlap covers a walker's own
|
|
28
|
+
* X-then-Y step straddling two windows — so travel is sound for a walker whose
|
|
29
|
+
* step is under a quarter tile.
|
|
30
|
+
*
|
|
31
|
+
* Anything outside the grid is open. Tile ranges are clamped to the grid before
|
|
32
|
+
* any conversion to an integer, so no coordinate, however large, is undefined
|
|
33
|
+
* behaviour or a long loop. A NaN coordinate reads as open ground; callers that
|
|
34
|
+
* need to refuse one (the Ruby binding does) check before calling.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
typedef struct {
|
|
38
|
+
const rgame_solid_grid *grid; /* must outlive the sweep */
|
|
39
|
+
double tile_width; /* > 0 */
|
|
40
|
+
double tile_height; /* > 0 */
|
|
41
|
+
} rgame_tile_sweep;
|
|
42
|
+
|
|
43
|
+
/* Where the box's left edge lands moving dx. dx of zero returns x unchanged. */
|
|
44
|
+
double rgame_tile_sweep_resolve_x(const rgame_tile_sweep *sweep, double x, double y, double w, double h,
|
|
45
|
+
double dx);
|
|
46
|
+
|
|
47
|
+
/* Where the box's top edge lands moving dy. dy of zero returns y unchanged. */
|
|
48
|
+
double rgame_tile_sweep_resolve_y(const rgame_tile_sweep *sweep, double x, double y, double w, double h,
|
|
49
|
+
double dy);
|
|
50
|
+
|
|
51
|
+
/* How many windows a travel of (dx, dy) sweeps — at least 1, or NaN for a NaN
|
|
52
|
+
* delta. Exposed so a caller can refuse a travel too long to sweep before
|
|
53
|
+
* starting it. */
|
|
54
|
+
double rgame_tile_sweep_travel_windows(const rgame_tile_sweep *sweep, double dx, double dy);
|
|
55
|
+
|
|
56
|
+
/* Whether the box at (x, y, w, h) travels (dx, dy) with no resolve along the
|
|
57
|
+
* way falling short of the landing it was heading for. A NaN delta, or a travel
|
|
58
|
+
* of more than INT32_MAX windows, is answered false without sweeping. */
|
|
59
|
+
bool rgame_tile_sweep_travel(const rgame_tile_sweep *sweep, double x, double y, double w, double h, double dx,
|
|
60
|
+
double dy);
|
|
61
|
+
|
|
62
|
+
#endif /* RGAME_TILE_SWEEP_H */
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* tile_sweep_ext.c — the Ruby binding for RGame::Util::TileSweep.
|
|
3
|
+
*
|
|
4
|
+
* The arithmetic lives in tile_sweep.{c,h}, pure and covered by the Check
|
|
5
|
+
* suite. What this file adds is lifetime and refusals: the C sweep holds a
|
|
6
|
+
* pointer into the SolidGrid it reads, so the Ruby object holds the grid too
|
|
7
|
+
* and marks it; and a coordinate crosses into C through NUM2DBL, so anything
|
|
8
|
+
* but a number is a TypeError.
|
|
9
|
+
*
|
|
10
|
+
* resolve_x and resolve_y run once per axis per frame for every mover blocked
|
|
11
|
+
* by tiles, so they convert and call and nothing else — no allocation, and a
|
|
12
|
+
* Float comes back as an immediate value.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
#include "util_ext.h"
|
|
16
|
+
|
|
17
|
+
#include <math.h>
|
|
18
|
+
|
|
19
|
+
#include "tile_sweep.h"
|
|
20
|
+
|
|
21
|
+
typedef struct {
|
|
22
|
+
VALUE grid;
|
|
23
|
+
rgame_tile_sweep sweep;
|
|
24
|
+
} tile_sweep_ref;
|
|
25
|
+
|
|
26
|
+
static long live_sweeps = 0;
|
|
27
|
+
|
|
28
|
+
static void tile_sweep_mark(void *ptr) {
|
|
29
|
+
tile_sweep_ref *ref = ptr;
|
|
30
|
+
rb_gc_mark(ref->grid);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static void tile_sweep_free(void *ptr) {
|
|
34
|
+
xfree(ptr);
|
|
35
|
+
live_sweeps--;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static size_t tile_sweep_memsize(const void *ptr) {
|
|
39
|
+
(void)ptr;
|
|
40
|
+
return sizeof(tile_sweep_ref);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static const rb_data_type_t tile_sweep_data_type = {
|
|
44
|
+
.wrap_struct_name = "rgame_tile_sweep",
|
|
45
|
+
.function = {
|
|
46
|
+
.dmark = tile_sweep_mark,
|
|
47
|
+
.dfree = tile_sweep_free,
|
|
48
|
+
.dsize = tile_sweep_memsize,
|
|
49
|
+
},
|
|
50
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
static tile_sweep_ref *unwrap(VALUE self) {
|
|
54
|
+
tile_sweep_ref *ref;
|
|
55
|
+
TypedData_Get_Struct(self, tile_sweep_ref, &tile_sweep_data_type, ref);
|
|
56
|
+
return ref;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static double tile_size(VALUE value, const char *name) {
|
|
60
|
+
double size = NUM2DBL(value);
|
|
61
|
+
if (!(isfinite(size) && size > 0.0)) {
|
|
62
|
+
rb_raise(rb_eArgError, "%s must be a positive, finite size, got %" PRIsVALUE, name, value);
|
|
63
|
+
}
|
|
64
|
+
return size;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* TileSweep.new(grid, tile_width, tile_height) — a sweep over a SolidGrid's
|
|
68
|
+
* cells at that tile size, which keeps the grid alive. TypeError for anything
|
|
69
|
+
* but a grid; ArgumentError for a tile size that is not positive and finite. */
|
|
70
|
+
static VALUE tile_sweep_s_new(VALUE klass, VALUE grid, VALUE tile_width, VALUE tile_height) {
|
|
71
|
+
rgame_solid_grid *cells = rgame_util_solid_grid(grid);
|
|
72
|
+
double width = tile_size(tile_width, "tile_width");
|
|
73
|
+
double height = tile_size(tile_height, "tile_height");
|
|
74
|
+
|
|
75
|
+
tile_sweep_ref *ref;
|
|
76
|
+
VALUE object = TypedData_Make_Struct(klass, tile_sweep_ref, &tile_sweep_data_type, ref);
|
|
77
|
+
live_sweeps++;
|
|
78
|
+
ref->grid = grid;
|
|
79
|
+
ref->sweep = (rgame_tile_sweep){ .grid = cells, .tile_width = width, .tile_height = height };
|
|
80
|
+
return object;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static VALUE tile_sweep_grid(VALUE self) { return unwrap(self)->grid; }
|
|
84
|
+
static VALUE tile_sweep_tile_width(VALUE self) { return DBL2NUM(unwrap(self)->sweep.tile_width); }
|
|
85
|
+
static VALUE tile_sweep_tile_height(VALUE self) { return DBL2NUM(unwrap(self)->sweep.tile_height); }
|
|
86
|
+
|
|
87
|
+
static void refuse_non_finite(double values[], int count) {
|
|
88
|
+
for (int i = 0; i < count; i++) {
|
|
89
|
+
if (!isfinite(values[i])) {
|
|
90
|
+
rb_raise(rb_eFloatDomainError, "a box cannot be resolved at %f", values[i]);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* A step with no movement returns the box where it is, whatever the numbers;
|
|
96
|
+
* any other step through a non-finite number is a FloatDomainError. */
|
|
97
|
+
static VALUE resolve(VALUE self, VALUE x, VALUE y, VALUE w, VALUE h, VALUE delta,
|
|
98
|
+
double (*axis)(const rgame_tile_sweep *, double, double, double, double, double)) {
|
|
99
|
+
double values[] = { NUM2DBL(x), NUM2DBL(y), NUM2DBL(w), NUM2DBL(h), NUM2DBL(delta) };
|
|
100
|
+
if (values[4] != 0.0) {
|
|
101
|
+
refuse_non_finite(values, 5);
|
|
102
|
+
}
|
|
103
|
+
return DBL2NUM(axis(&unwrap(self)->sweep, values[0], values[1], values[2], values[3], values[4]));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* resolve_x(x, y, w, h, dx) — where the box's left edge lands moving dx,
|
|
107
|
+
* snapped flush against a solid tile it would enter. Always a Float. */
|
|
108
|
+
static VALUE tile_sweep_resolve_x(VALUE self, VALUE x, VALUE y, VALUE w, VALUE h, VALUE dx) {
|
|
109
|
+
return resolve(self, x, y, w, h, dx, rgame_tile_sweep_resolve_x);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* resolve_y(x, y, w, h, dy) — the mirror of resolve_x. */
|
|
113
|
+
static VALUE tile_sweep_resolve_y(VALUE self, VALUE x, VALUE y, VALUE w, VALUE h, VALUE dy) {
|
|
114
|
+
return resolve(self, x, y, w, h, dy, rgame_tile_sweep_resolve_y);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* travel?(x, y, w, h, dx, dy) — whether the box travels (dx, dy) without any
|
|
118
|
+
* resolve along the way falling short of where it was heading. Sound for a
|
|
119
|
+
* walker stepping under a quarter tile at a time. FloatDomainError for a
|
|
120
|
+
* non-finite number; ArgumentError for a travel too long to sweep. */
|
|
121
|
+
static VALUE tile_sweep_travel_p(VALUE self, VALUE x, VALUE y, VALUE w, VALUE h, VALUE dx, VALUE dy) {
|
|
122
|
+
double values[] = { NUM2DBL(x), NUM2DBL(y), NUM2DBL(w), NUM2DBL(h), NUM2DBL(dx), NUM2DBL(dy) };
|
|
123
|
+
refuse_non_finite(values, 6);
|
|
124
|
+
|
|
125
|
+
rgame_tile_sweep *sweep = &unwrap(self)->sweep;
|
|
126
|
+
double windows = rgame_tile_sweep_travel_windows(sweep, values[4], values[5]);
|
|
127
|
+
if (windows > (double)INT32_MAX) {
|
|
128
|
+
rb_raise(rb_eArgError, "a travel of (%f, %f) is too long to sweep", values[4], values[5]);
|
|
129
|
+
}
|
|
130
|
+
return rgame_tile_sweep_travel(sweep, values[0], values[1], values[2], values[3], values[4], values[5])
|
|
131
|
+
? Qtrue
|
|
132
|
+
: Qfalse;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* TileSweep.debug_live_sweeps — how many sweeps are allocated. Test-only, and
|
|
136
|
+
* named so. */
|
|
137
|
+
static VALUE tile_sweep_s_debug_live_sweeps(VALUE klass) {
|
|
138
|
+
(void)klass;
|
|
139
|
+
return LONG2NUM(live_sweeps);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
void rgame_init_tile_sweep(VALUE mUtil) {
|
|
143
|
+
VALUE cTileSweep = rb_define_class_under(mUtil, "TileSweep", rb_cObject);
|
|
144
|
+
|
|
145
|
+
rb_undef_alloc_func(cTileSweep);
|
|
146
|
+
rb_define_singleton_method(cTileSweep, "new", tile_sweep_s_new, 3);
|
|
147
|
+
rb_define_singleton_method(cTileSweep, "debug_live_sweeps", tile_sweep_s_debug_live_sweeps, 0);
|
|
148
|
+
|
|
149
|
+
rb_define_method(cTileSweep, "grid", tile_sweep_grid, 0);
|
|
150
|
+
rb_define_method(cTileSweep, "tile_width", tile_sweep_tile_width, 0);
|
|
151
|
+
rb_define_method(cTileSweep, "tile_height", tile_sweep_tile_height, 0);
|
|
152
|
+
rb_define_method(cTileSweep, "resolve_x", tile_sweep_resolve_x, 5);
|
|
153
|
+
rb_define_method(cTileSweep, "resolve_y", tile_sweep_resolve_y, 5);
|
|
154
|
+
rb_define_method(cTileSweep, "travel?", tile_sweep_travel_p, 6);
|
|
155
|
+
}
|
data/ext/rgame_util/util_ext.c
CHANGED
data/ext/rgame_util/util_ext.h
CHANGED
|
@@ -12,5 +12,20 @@
|
|
|
12
12
|
|
|
13
13
|
void rgame_init_tensor(VALUE mUtil);
|
|
14
14
|
void rgame_init_color(VALUE mUtil);
|
|
15
|
+
void rgame_init_solid_grid(VALUE mUtil);
|
|
16
|
+
void rgame_init_route_search(VALUE mUtil);
|
|
17
|
+
void rgame_init_tile_sweep(VALUE mUtil);
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
* The two things a RouteSearch needs from the SolidGrid binding: the grid
|
|
21
|
+
* behind a Ruby object (TypeError for anything else), and the one reading of a
|
|
22
|
+
* cell coordinate both classes share — an Integer or a TypeError, and false
|
|
23
|
+
* for an Integer too large to be inside any grid.
|
|
24
|
+
*/
|
|
25
|
+
#include <stdbool.h>
|
|
26
|
+
#include "solid_grid.h"
|
|
27
|
+
|
|
28
|
+
rgame_solid_grid *rgame_util_solid_grid(VALUE value);
|
|
29
|
+
bool rgame_util_cell_coordinate(VALUE value, int32_t *out);
|
|
15
30
|
|
|
16
31
|
#endif /* RGAME_UTIL_EXT_H */
|