#include "land.h"
#define _scramble_min(x, y) ((y) < (x) ? (y) : (x))
#define _scramble_max(x, y) ((y) > (x) ? (y) : (x))
typedef struct LagSimulator LagSimulator;
typedef struct Waves Waves;
typedef struct TestData TestData;
typedef struct LandTrianglesPlatform LandTrianglesPlatform;
typedef struct LandTrianglesShader LandTrianglesShader;
typedef struct LandFontPlatform LandFontPlatform;
typedef enum LandWidgetThemeFlags LandWidgetThemeFlags;
typedef enum COLUMN_TYPE COLUMN_TYPE;
typedef struct Stick Stick;
typedef struct Joy Joy;
typedef struct LandAtlasSprite LandAtlasSprite;
typedef struct LandAtlasSheet LandAtlasSheet;
typedef struct LandSoundPlatform LandSoundPlatform;
typedef struct LandStreamPlatform LandStreamPlatform;
typedef enum State State;
typedef struct PlatformThread PlatformThread;
typedef struct PlatformLock PlatformLock;
typedef enum XmlState XmlState;
typedef struct XmlParser XmlParser;
typedef struct CallbackData CallbackData;
typedef struct Data Data;
#include <math.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#if defined WINDOWS
#include <winsock2.h>
#endif
#if defined WINDOWS
#include <ws2tcpip.h>
#endif
#if ! defined ( WINDOWS )
#include <stdlib.h>
#endif
#if ! defined ( WINDOWS )
#include <string.h>
#endif
#if ! defined ( WINDOWS )
#include <signal.h>
#endif
#if ! defined ( WINDOWS )
#include <sys/time.h>
#endif
#if ! defined ( WINDOWS )
#include <sys/socket.h>
#endif
#if ! defined ( WINDOWS )
#include <sys/ioctl.h>
#endif
#include <errno.h>
#if ! defined ( WINDOWS )
#include <arpa/inet.h>
#endif
#if ! defined ( WINDOWS )
#include <netdb.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <stdio.h>
#include <math.h>
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if ! defined ( LAND_NO_COMPRESS )
#include <zlib.h>
#endif
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <ctype.h>
#include <string.h>
#include <math.h>
#include <string.h>
#include <assert.h>
#include <string.h>
#include <allegro5/allegro5.h>
#include <allegro5/allegro_primitives.h>
#include <allegro5/allegro5.h>
#if defined ( ANDROID )
#include <allegro5/allegro_android.h>
#endif
#include <allegro5/allegro5.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include <allegro5/allegro.h>
#include <allegro5/allegro.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_primitives.h>
#include <sys/time.h>
#include <allegro5/allegro5.h>
#include <allegro5/allegro_image.h>
#include <allegro5/allegro_opengl.h>
#include <math.h>
#include <stdbool.h>
#include <allegro5/allegro_audio.h>
#include <allegro5/allegro5.h>
#include <allegro5/allegro_primitives.h>
#include <assert.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <stdarg.h>
#include <allegro5/allegro5.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <allegro5/allegro_color.h>
#if ! defined ( ANDROID )
#include <allegro5/allegro_native_dialog.h>
#endif
#include <math.h>
#include <allegro5/allegro5.h>
#include <allegro5/allegro_primitives.h>
#include <assert.h>
#include <math.h>
#define BB(x1, y1, x2, y2, x3, y3, x4, y4) \
    * bl = x1 * cos(angle) + y1 * sin(angle); \
    * bt = y2 * cos(angle) - x2 * sin(angle); \
    * br = x3 * cos(angle) + y3 * sin(angle); \
    * bb = y4 * cos(angle) - x4 * sin(angle);
static void get_bounding_box(float l, float t, float r, float b, float angle, float * bl, float * bt, float * br, float * bb);
static LandPixelMask* pixelmask_create_flip(LandImage * image, int n, int threshold, bool flipped);
static LandPixelMask* pixelmask_create(LandImage * image, int n, int threshold);
static void pixelmask_destroy(LandPixelMask * mask);
static int mask_get_rotation_frame(LandPixelMask * mask, float angle, bool flipped);
static int pixelmask_part_collision(SinglePixelMask * mask, int x, int y, SinglePixelMask * mask_, int x_, int y_, int w, int h);
static int pixelmask_collision(SinglePixelMask * mask, int x, int y, int w, int h, SinglePixelMask * mask_, int x_, int y_, int w_, int h_);
static void test(char const * name, int want, int got);
static int nonblocking(LandNet * self);
static void split_address(char const * address, char * (* host), int * port);
static void _get_address(struct sockaddr_in sock_addr, char * address);
static void land_net_poll_accept(LandNet * self);
static void land_net_poll_connect(LandNet * self);
static LagSimulator* lag_simulator_new(double delay, double jitter);
static void lag_simulator_add(LandNet * net, char const * packet, int size);
static int _create_datagram_socket(LandNet * self);
static int _send_datagram(LandNet * self, char const * address, char const * packet, int size);
static void land_net_poll_recv(LandNet * self);
static void line(int x1, int y1, int x2, int y2);
static void letter(char glyph, str code);
static void initial_font(void);
static int _wordwrap_helper(char const * text, int w, int h, void(* cb)(int a, int b, void * data), void * data);
static void _print_wordwrap_cb(int a, int b, void * data);
static void land_wordwrap_text_cb(int a, int b, void * data);
static void _smoothen(LandNoise * self);
static void _smoothen_temp(float * noise, int w, int h, int blur_size, bool wrap);
static float* _white(LandNoise * self);
static void _multires_cache(LandNoise * self);
static float _get_resolution(LandNoise * self, void * sub, int i, int x, int y);
static void* _waves_create(LandNoise * noise, int w, int h);
static float _waves_at(Waves * self, float power_modifier, float amplitude, int x, int y);
static float wrap_distance(float x1, float x2, float wrap);
static int get_closest(LandVoronoi * self, int x, int y);
static int cb_free(void * data, void * _);
static int alphacomp(void const * a, void const * b);
static double cielab_f(double x);
static double cielab_f_inv(double x);
static char const* bash_mode(char const * x, char const * mode);
static int read32(FILE * f);
static int star_match(char const * pattern, char const * name);
static LandImage* _load(char const * filename, bool mem);
static void _load2(LandImage * self);
static void _thread_func(void * data);
static int callback(const char * filename, int attrib, void * param);
static int compar(void const * a, void const * b);
static int filter(char const * name, bool is_dir, void * data);
static void defcb(LandImage * image, void * p);
static void shader_setup(LandGLSLShader * self, char const * name, char const * vertex_glsl_s, char const * fragment_glsl_s);
static void shader_cleanup(LandGLSLShader * self);
static void add_files(char const * rel, LandArray * (* array), ALLEGRO_FS_ENTRY * entry, int(* filter)(char const *, bool is_dir, void * data), int flags, void * data);
static char* _get_app_file(char const * appname, int folder, char const * filename);
static void land_tilegrid_draw_cell(LandGrid * self, LandView * view, int cell_x, int cell_y, float pixel_x, float pixel_y);
static void view_x_to_cell_and_pixel_x(LandGrid * self, float view_x, int * cell_x, float * pixel_x);
static void view_y_to_cell_and_pixel_y(LandGrid * self, float view_y, int * cell_y, float * pixel_y);
static inline int centered_offset(int size1, int size2);
static inline void _masked_non_stretched_blit(LandImage * s, int sx, int sy, int w, int h, int dx, int dy, int _, int __);
static inline void _masked_stretched_blit(LandImage * s, int sx, int sy, int w, int h, int dx, int dy, int dw, int dh);
static inline void blit_column(LandWidgetThemeElement * pat, int bx, int bw, int x, int y, int w, int h, int skip_middle);
static void draw_bitmap(LandWidgetThemeElement * pat, int x, int y, int w, int h, int skip_middle);
static void read_int_arg(int argc, LandArray * argv, int * a, int * val);
static LandWidgetThemeElement* find_element(LandList * list, char const * name);
static void _theme_recurse(LandWidget * self, LandWidgetTheme * theme);
static void _layout_recurse(LandWidget * self, LandWidgetTheme * theme);
static int _getx(LandOctree * self, LandFloat x);
static int _gety(LandOctree * self, LandFloat y);
static int _getz(LandOctree * self, LandFloat z);
static int _get_i(LandOctree * self, LandFloat x, LandFloat y, LandFloat z);
static int replacement_main(int argc, char * (* argv));
static int platform_keycode(int ak);
static void land_exit(void);
static void _platform_load(LandImage * super);
static LandAtlasSheet* atlas_find_sheet(LandAtlas * self, char const * name);
static LandAtlasSprite* atlas_load_picture(LandAtlas * self, char const * filename);
static void init_genrand(LandRandom * r, unsigned long s);
static unsigned long genrand_int32(LandRandom * r);
static LandGrid* new(float cell_w1, float cell_h1, float cell_w2, float cell_h2, int x_cells, int y_cells);
static void view_to_cell(LandGrid * self, float view_x, float view_y, int * cell_x, int * cell_y);
static void view_to_cell_wrap(LandGrid * self, float view_x, float view_y, int * cell_x, int * cell_y);
static void cell_to_view(LandGrid * self, float cell_x, float cell_y, float * view_x, float * view_y);
static int find_offset(LandGrid * self, float view_x, float view_y, int * cell_x, int * cell_y, float * pixel_x, float * pixel_y);
static void find_offset_wrap(LandGrid * self, float view_x, float view_y, int * cell_x, int * cell_y, float * pixel_x, float * pixel_y);
static void _yaml_write(YamlParser * p, char const * s);
static void yaml_write(YamlParser * p, char const * s);
static void _pretty_newline(YamlParser * p);
static bool _save_mapping(LandYamlEntry * e, YamlParser * p);
static bool _save_sequence(LandYamlEntry * e, YamlParser * p);
static bool _save_scalar(LandYamlEntry * e, YamlParser * p);
static bool _save_entry(LandYamlEntry * e, YamlParser * p);
static bool get_params(int channels, int bits, int * chan_conf, int * depth);
static float _blend(LandPlasma * self, float c1, float c2, float level);
static float _plasma_read(LandPlasma * self, int x, int y);
static void _plasma_write(LandPlasma * self, int x, int y, float value);
static void _fractal(LandPlasma * self, int x, int y, int w, int h, int i);
static void _subdivide(LandPlasma * self);
static float _no_lerp(float a0, float a1, float w);
static float _linear_lerp(float a0, float a1, float w);
static float _cosine_lerp(float a0, float a1, float w);
static float _smooth_step_lerp(float a0, float a1, float w);
static float _smoother_step_lerp(float a0, float a1, float w);
static float _smoothest_step_lerp(float a0, float a1, float w);
static LandNoiseF2* _gradient(LandPerlin * self, int x, int y);
static float _dot(LandPerlin * self, int ix, int iy, float x, float y);
static void* _get(LandIniSection * s, char const * key);
static void _add(LandIniSection * s, char const * key, void * val);
static void _del(LandIniSection * s);
static LandIniSection* _new(void);
static bool is_whitespace(char c);
static void ERR(char const * format, ...);
static void update_lookup_grid(LandWidget * self);
static LandWidget* lookup_box_in_grid(LandWidget * self, int col, int row);
static int row_min_height(LandWidget * self, int row);
static int column_min_width(LandWidget * self, int col);
static int is_column_expanding(LandWidget * self, int col);
static int is_row_expanding(LandWidget * self, int row);
static int expanding_columns(LandWidget * self);
static int expanding_rows(LandWidget * self);
static int min_height(LandWidget * self);
static int min_width(LandWidget * self);
static int adjust_resize_width(LandWidget * self, int dx);
static int adjust_resize_height(LandWidget * self, int dx);
static void gul_box_bottom_up(LandWidget * self);
static void gul_box_top_down(LandWidget * self);
static void gul_box_fit_children(LandWidget * self);
static bool _get_data(LandHash * self, LandHashIterator * i, void * (* data));
static bool _get_key(LandHash * self, LandHashKeysIterator * i, void * (* data));
static unsigned int hash_function(LandHash * self, char const * thekey);
static LandHashEntry* land_hash_get_entry(LandHash * self, char const * thekey);
static void* proc(void * data);
static void* aproc(ALLEGRO_THREAD * thread, void * arg);
static void dummy(LandSprite * self, LandView * view);
static void dummy_image(LandSprite * self, LandView * view);
static void dummy_animation(LandSprite * self, LandView * view);
static void get_grid_extents(LandSprite * self, LandGrid * grid, int * tl, int * tt, int * tr, int * tb);
static int is_left(float ax, float ay, float bx, float by);
static int is_in_triangle(float x, float y, float p1x, float p1y, float p2x, float p2y, float p3x, float p3y);
static void grid_place(LandSprite * self, LandSpritesGrid * grid);
static void grid_unplace(LandSprite * self, LandSpritesGrid * grid);
static void get_cell_at(LandGrid * self, LandView * view, float view_x, float view_y, float * cell_x, float * cell_y);
static void _add_entry(LandYaml * yaml, LandYamlEntry * entry);
static void _destroy_entry(LandYamlEntry * self);
static void _indent(int indent);
static void land_yaml_dump_entry(LandYamlEntry * self, int indent);
static void scalar(XmlParser * x);
static void opt_scalar(XmlParser * x);
static void discard_scalar(XmlParser * x);
static void add_char(XmlParser * x, char c);
static void create_tag(XmlParser * x);
static void open_tag(XmlParser * x);
static void close_tag(XmlParser * x);
static void xml_write(YamlParser * p, char const * s, bool can_break_before);
static bool xml_save_mapping(LandYamlEntry * e, YamlParser * p);
static bool xml_save_sequence(LandYamlEntry * e, YamlParser * p);
static bool xml_save_scalar(LandYamlEntry * e, YamlParser * p);
static bool xml_save_entry(LandYamlEntry * e, YamlParser * p);
static void _xml(LandYaml * yaml);
static double _copy_sign(double x, double y);
static void sphere_point(LandArray * vertices, LandFloat i, LandFloat j);
static LandVector _sphere_point(LandVector a, LandVector b);
static void _sphere_tri(LandArray * polygons, LandVector a, LandVector b, LandVector c, int divisions, void * shared);
static LandArray* quad_vertices(LandVector a, LandVector b, LandVector c, LandVector d);
static void add_quad(LandArray * polygons, LandVector a, LandVector b, LandVector c, LandVector d, void * shared);
static LandArray* triangle_vertices(LandVector a, LandVector b, LandVector c);
static void add_tri(LandArray * polygons, LandVector a, LandVector b, LandVector c, void * shared);
static void add_tri_flip(LandArray * polygons, LandVector a, LandVector b, LandVector c, void * shared, bool flip);
static void add_quad_flip(LandArray * polygons, LandVector a, LandVector b, LandVector c, LandVector d, void * shared, bool flip);
static void torus_point(LandArray * vertices, LandFloat i, LandFloat j, LandFloat r);
static void _merge_callback_callback(LandArray * array, void * data);
static void _lookup_close_polygons(LandCSG * csg, LandVector pos, LandFloat r, void(* callback)(LandCSGPolygon * p, void * data), void * data);
static void _merge_callback(LandCSGPolygon * p, void * v);
static void _dome_point(LandArray * vertices, LandFloat x, LandFloat y, LandFloat z, LandFloat t);
static void _dome_point_bottom(LandArray * vertices, LandFloat x, LandFloat y, LandFloat z);
static LandCSGVertex _mid_vertex(LandCSGVertex * av, LandCSGVertex * bv);
static void _add_tri_norm(LandArray * polygons, LandCSGVertex * av, LandCSGVertex * bv, LandCSGVertex * cv, void * shared);
static void _split_triangle(LandArray * polygons, LandCSGVertex * av, LandCSGVertex * bv, LandCSGVertex * cv, void * shared);
static void _add_m(LandObjFile * self, char const * name);
static void _add_v(LandObjFile * self, float x, float y, float z);
static void _add_vn(LandObjFile * self, float x, float y, float z);
static void _add_vt(LandObjFile * self, float x, float y);
static void _add_f(LandObjFile * self, LandObjVertex v);
static void _add_o(LandObjFile * self, char const * name);
static void _read_vertex(LandObjFile * self, str row);
static void _read_marker(LandObjFile * self, char * row);
static void _handle_row(LandObjFile * self, char * row);
static void _handle_mtl_row(LandObjFile * self, char * row);
static void _read_mtl(LandObjFile * self, char const * filename);
static void land_widget_really_destroy(LandWidget * self);
static void _test(str name, LandCamera cam, double result, double expected);
static void transfer_mouse_focus(LandWidget * base, LandWidget * child);
static void transfer_keyboard_focus(LandWidget * base);
static void _slider_cb(LandWidget * w);
static int _linedelcb(void * item, void * data);
static int collision_code(LandVector * v, LandCSGAABB * b);
static bool polygon_intersects_aabb(LandCSGPolygon * polygon, LandCSGAABB * box);
static LandArray* clone_vertices(LandCSG * csg, LandArray * vertices);
static void remove_vertices(LandArray * vertices, bool is_pooled);
static void csg_vertex_flip(LandCSGVertex * self);
static LandCSGVertex* csg_vertex_interpolate(LandCSG * csg, LandCSGVertex * self, LandCSGVertex * other, LandFloat t);
static LandCSGPlane csg_plane(LandVector normal, LandFloat w);
static LandCSGPolygon* land_csg_polygon_new_pool(LandMemoryPool * pool, LandArray * vertices, void * shared);
static void csg_plane_split_polygon(LandCSG * csg, LandCSGPlane * self, LandCSGPolygon * polygon, LandArray * coplanar_front, LandArray * coplanar_back, LandArray * front, LandArray * back);
static LandArray* clone_polygons(LandCSG * csg, LandArray * polygons);
static void clear_polygons(LandArray * polygons);
static void csg_node_destroy(LandCSGNode * self);
static void csg_node_invert(LandCSGNode * self);
static void csg_node_clip_polygons(LandCSG * csg, LandCSGNode * self, LandArray * polygons);
static void csg_node_clip_to(LandCSG * csg, LandCSGNode * self, LandCSGNode * bsp);
static LandArray* csg_node_all_polygons(LandCSG * csg, LandCSGNode * self);
static void csg_add_polygons_from_node(LandCSG * csg, LandCSGNode * node);
static void csg_add_polygons(LandCSG * csg, LandArray * polygons);
static void csg_node_build(LandCSG * csg, LandCSGNode * self, LandArray * polygons);
static LandCSGNode* csg_node_new(LandCSG * csg, LandArray * polygons);
static void csg_split_on_bounding_box(LandCSG const * self, LandCSGAABB * box, LandArray * (* inside), LandArray * (* outside));
static void menubutton_clicked(LandWidget * base);
static void menuitem_clicked(LandWidget * base);
static int is_in_menu(LandWidget * self, LandWidget * other);
static int is_related(LandWidget * self, LandWidget * other);
static int get_x_offset(LandWidget * base);
static void land_widget_vbox_renumber(LandWidget * base);
static void scroll_vertical_cb(LandWidget * self, bool update_target, int * _scramble_min, int * _scramble_max, int * range, int * pos);
static void scroll_horizontal_cb(LandWidget * self, bool update_target, int * _scramble_min, int * _scramble_max, int * range, int * pos);
static int get_size(LandWidget * super);
static void land_widget_hbox_renumber(LandWidget * base);
static void _scrolling_layout(LandWidget * self);
static void clicked(LandWidget * button);
static void updated(LandWidget * base);
static void spinning(LandWidget * widget, float amount);
static void get_bounding_box(float l, float t, float r, float b, float angle, float * bl, float * bt, float * br, float * bb) {
    if (angle < LAND_PI / 2) {
        BB(l, t, r, t, r, b, l, b);
    }
    else if (angle < LAND_PI) {
        BB(r, t, r, b, l, b, l, t);
    }
    else if (angle < 3 * LAND_PI / 2) {
        BB(r, b, l, b, l, t, r, t);
    }
    else {
        BB(l, b, l, t, r, t, r, b);
    }
}
#ifdef DEBUG_MASK
static void printout_mask(SinglePixelMask * mask) {
    int i;
    int mask_w = mask->w;
    for (i = 0; i < mask->h; i++) {
        int j;
        for (j = 0; j < mask_w; j++) {
            int m = mask->data [mask_w * i + j];
            int b;
            for (b = 0; b < 32; b++) {
                printf("%c", m & (1 << b) ? '1' : '0');
            }
        }
        printf("\n");
    }
    printf("---\n");
}
#endif
static LandPixelMask* pixelmask_create_flip(LandImage * image, int n, int threshold, bool flipped) {
    LandPixelMask * mask;
    int j;
    int angle_count = n;
    if (flipped) {
        n *= 2;
    }
    mask = land_malloc(sizeof (* mask) + sizeof (SinglePixelMask *) * n);
    mask->n = n;
    mask->flipped = flipped;
    int bmpw = land_image_width(image) - image->l - image->r;
    int bmph = land_image_height(image) - image->t - image->b;
    mask->w = bmpw;
    mask->h = bmph;
    mask->x = image->l;
    mask->y = image->t;
    for (j = 0; j < n; j++) {
        int j2 = j;
        if (j2 >= angle_count) {
            j2 -= angle_count;
        }
        float angle = j2 * LAND_PI * 2 / angle_count;
        float w, h;
        if (angle < LAND_PI / 2) {
            w = bmpw * cos(angle) + bmph * sin(angle);
            h = bmph * cos(angle) + bmpw * sin(angle);
        }
        else if (angle < LAND_PI) {
            w = bmpw * (- cos(angle)) + bmph * sin(angle);
            h = bmph * (- cos(angle)) + bmpw * sin(angle);
        }
        else if (angle < 3 * LAND_PI / 2) {
            w = bmpw * (- cos(angle)) + bmph * (- sin(angle));
            h = bmph * (- cos(angle)) + bmpw * (- sin(angle));
        }
        else {
            w = bmpw * cos(angle) + bmph * (- sin(angle));
            h = bmph * cos(angle) + bmpw * (- sin(angle));
        }
        int tw = ceil(w);
        int th = ceil(h);
        LandImage * temp = land_image_create(tw, th);
        land_set_image_display(temp);
        float backup_x = image->x;
        float backup_y = image->y;
        image->x = image->l + 0.5 * bmpw;
        image->y = image->t + 0.5 * bmph;
        if (flipped && j >= n / 2) {
            land_image_draw_rotated_flipped(image, w / 2.0, h / 2.0, angle);
        }
        else {
            land_image_draw_rotated(image, w / 2.0, h / 2.0, angle);
        }
        image->x = backup_x;
        image->y = backup_y;
        land_unset_image_display();
        int mask_w = 1 + (tw + 31) / 32;
        mask->rotation [j] = land_malloc(sizeof (* mask->rotation [j]) + mask_w * th * sizeof (uint32_t));
        mask->rotation [j]->w = mask_w;
        mask->rotation [j]->h = th;
        unsigned char rgba [tw * th * 4];
        land_image_get_rgba_data(temp, rgba);
        land_image_destroy(temp);
        for (int y = 0; y < th; y++) {
            int x;
            for (x = 0; x < tw; x += 32) {
                int bits = 0;
                for (int i = 0; i < 32 && x + i < tw; i++) {
                    if (rgba [y * tw * 4 + (x + i) * 4 + 3] >= threshold) {
                        bits += 1 << i;
                    }
                }
                mask->rotation [j]->data [y * mask_w + x / 32] = bits;
            }
            mask->rotation [j]->data [y * mask_w + x / 32] = 0;
        }
        #ifdef DEBUG_MASK
        printout_mask(mask->rotation [j]);
        #endif
    }
    return mask;
}
static LandPixelMask* pixelmask_create(LandImage * image, int n, int threshold) {
    bool flipped = n < 0;
    if (flipped) {
        n = - n;
    }
    return pixelmask_create_flip(image, n, threshold, flipped);
}
static void pixelmask_destroy(LandPixelMask * mask) {
    int j;
    for (j = 0; j < mask->n; j++) {
        land_free(mask->rotation [j]);
    }
    land_free(mask);
}
static int mask_get_rotation_frame(LandPixelMask * mask, float angle, bool flipped) {
    int n = mask->n;
    if (mask->flipped) {
        n /= 2;
    }
    float r = n * angle / (2 * LAND_PI);
    if (r > 0) {
        r += 0.5;
    }
    else {
        r -= 0.5;
    }
    int i = (int)(r) % n;
    if (i < 0) {
        i += n;
    }
    if (mask->flipped && flipped) {
        i += mask->n / 2;
    }
    return i;
}
void land_image_debug_pixelmask(LandImage * self, float x, float y, float angle, bool flipped) {
    int i;
    int k = mask_get_rotation_frame(self->mask, angle, flipped);
    int mask_w = self->mask->rotation [k]->w;
    int w = land_image_width(self) - self->l - self->r;
    int h = land_image_height(self) - self->t - self->b;
    float ml, mt, mr, mb;
    get_bounding_box(self->l - self->x, self->t - self->y, w - self->x + self->l, h - self->y + self->t, k * 2.0 * LAND_PI / self->mask->n, & ml, & mt, & mr, & mb);
    land_color(1, 0, 0, 1);
    land_rectangle(x + ml, y + mt, x + mr, y + mb);
    land_color(0, 1, 0, 1);
    for (i = 0; i < self->mask->rotation [k]->h; i++) {
        int j;
        for (j = 0; j < mask_w; j++) {
            int m = self->mask->rotation [k]->data [mask_w * i + j];
            int b;
            for (b = 0; b < 32; b++) {
                if (m & (1 << b)) {
                    land_plot(x + ml + j * 32 + b, y + mt + i);
                }
            }
        }
    }
}
static int pixelmask_part_collision(SinglePixelMask * mask, int x, int y, SinglePixelMask * mask_, int x_, int y_, int w, int h) {
    int mask_w = mask->w;
    int mask_w_ = mask_->w;
    unsigned int * li = mask->data + mask_w * y;
    unsigned int * li_ = mask_->data + mask_w_ * y_;
    unsigned int bit = x & 31;
    unsigned int bit_ = x_ & 31;
    int j;
    for (j = 0; j < h; j++) {
        int lw;
        int i = x >> 5;
        int i_ = x_ >> 5;
        for (lw = w; lw > 0; lw -= 32) {
            unsigned int m, m_;
            if (bit == 0) {
                m = li [i];
            }
            else {
                m = (li [i] >> bit) + (li [i + 1] << (32 - bit));
            }
            if (bit_ == 0) {
                m_ = li_ [i_];
            }
            else {
                m_ = (li_ [i_] >> bit_) + (li_ [i_ + 1] << (32 - bit_));
            }
            if (m & m_) {
                return 1;
            }
            i++;
            i_++;
        }
        li += mask_w;
        li_ += mask_w_;
    }
    return 0;
}
static int pixelmask_collision(SinglePixelMask * mask, int x, int y, int w, int h, SinglePixelMask * mask_, int x_, int y_, int w_, int h_) {
    if (x >= x_ + w_ || x_ >= x + w || y >= y_ + h_ || y_ >= y + h) {
        return 0;
    }
    if (x <= x_) {
        if (y <= y_) {
            return pixelmask_part_collision(mask, x_ - x, y_ - y, mask_, 0, 0, _scramble_min(x + w - x_, w_), _scramble_min(y + h - y_, h_));
        }
        else {
            return pixelmask_part_collision(mask, x_ - x, 0, mask_, 0, y - y_, _scramble_min(x + w - x_, w_), _scramble_min(y_ + h_ - y, h));
        }
    }
    else {
        if (y <= y_) {
            return pixelmask_part_collision(mask, 0, y_ - y, mask_, x - x_, 0, _scramble_min(x_ + w_ - x, w), _scramble_min(y + h - y_, h_));
        }
        else {
            return pixelmask_part_collision(mask, 0, 0, mask_, x - x_, y - y_, _scramble_min(x_ + w_ - x, w), _scramble_min(y_ + h_ - y, h));
        }
    }
}
void land_image_create_pixelmasks(LandImage * self, int n, int threshold) {
    self->mask = pixelmask_create(self, n, threshold);
}
void land_image_destroy_pixelmasks(LandImage * self) {
    if (self->mask) {
        pixelmask_destroy(self->mask);
    }
}
int land_image_overlaps(LandImage * self, float x, float y, float angle, float flipped, LandImage * other, float x_, float y_, float angle_, float flipped_) {
    if (! self->mask) {
        return 0;
    }
    if (! other->mask) {
        return 0;
    }
    int w = self->mask->w;
    int h = self->mask->h;
    int w_ = other->mask->w;
    int h_ = other->mask->h;
    int i = mask_get_rotation_frame(self->mask, angle, flipped);
    int i_ = mask_get_rotation_frame(other->mask, angle_, flipped_);
    int mx = self->mask->x - self->x;
    int my = self->mask->y - self->y;
    int mx_ = other->mask->x - other->x;
    int my_ = other->mask->y - other->y;
    float ml, mt, mr, mb, ml_, mt_, mr_, mb_;
    get_bounding_box(mx, my, mx + w, my + h, i * 2.0 * LAND_PI / self->mask->n, & ml, & mt, & mr, & mb);
    get_bounding_box(mx_, my_, mx_ + w_, my_ + h_, i_ * 2.0 * LAND_PI / other->mask->n, & ml_, & mt_, & mr_, & mb_);
    return pixelmask_collision(self->mask->rotation [i], x + ml, y + mt, mr - ml, mb - mt, other->mask->rotation [i_], x_ + ml_, y_ + mt_, mr_ - ml_, mb_ - mt_);
}
#undef BB
static void test(char const * name, int want, int got) {
    if (want == got) {
        printf("OK   %s\n", name);
    }
    else {
        printf("FAIL %s (want %d but got %d)\n", name, want, got);
    }
}
void csg_test_shapes(void) {
    LandCSG * cube = csg_cube(NULL);
    test("cube", 6, cube->polygons->count);
    land_csg_destroy(cube);
    LandCSG * sphere = csg_sphere(3, 3, NULL);
    test("sphere", 6, sphere->polygons->count);
    land_csg_destroy(sphere);
    LandCSG * sphere2 = csg_sphere(3, 4, NULL);
    test("sphere2", 9, sphere2->polygons->count);
    land_csg_destroy(sphere2);
    LandCSG * sphere3 = csg_sphere(4, 3, NULL);
    test("sphere3", 8, sphere3->polygons->count);
    land_csg_destroy(sphere3);
    LandCSG * cylinder = csg_cylinder(3, NULL);
    test("cylinder", 9, cylinder->polygons->count);
    land_csg_destroy(cylinder);
    LandCSG * cylinder2 = csg_cylinder(4, NULL);
    test("cylinder2", 12, cylinder2->polygons->count);
    land_csg_destroy(cylinder2);
}
void csg_test_union(void) {
    LandCSG * cubeA = csg_cube(NULL);
    LandCSG * cubeB = csg_cube(NULL);
    land_csg_transform(cubeB, land_4x4_matrix_translate(4, 0, 0));
    LandCSG * AB = land_csg_union(cubeA, cubeB);
    test("AB", 12, AB->polygons->count);
    LandCSG * cubeC = csg_cube(NULL);
    land_csg_transform(cubeC, land_4x4_matrix_translate(2, 0, 0));
    LandCSG * AC = land_csg_union(cubeA, cubeC);
    test("AC", 10, AC->polygons->count);
    LandCSG * ABC1 = land_csg_union(AB, cubeC);
    test("ABC1", 14, ABC1->polygons->count);
    LandCSG * ABC2 = land_csg_union(cubeB, AC);
    test("ABC2", 14, ABC2->polygons->count);
    land_csg_destroy(cubeA);
    land_csg_destroy(cubeB);
    land_csg_destroy(cubeC);
    land_csg_destroy(AB);
    land_csg_destroy(AC);
    land_csg_destroy(ABC1);
    land_csg_destroy(ABC2);
}
void csg_test_subtract(void) {
    LandCSG * cubeA = csg_cube(NULL);
    LandCSG * cubeB = csg_cube(NULL);
    land_csg_transform(cubeB, land_4x4_matrix_translate(1, 0, 0));
    LandCSG * A_B = land_csg_subtract(cubeA, cubeB);
    test("A-B", 6, A_B->polygons->count);
    LandCSG * cubeBi = land_csg_inverse(cubeB);
    test("Bi", 6, cubeBi->polygons->count);
    LandCSG * ABi = land_csg_union(cubeA, cubeBi);
    test("ABi", 6, ABi->polygons->count);
    land_csg_destroy(cubeA);
    land_csg_destroy(cubeB);
    land_csg_destroy(A_B);
    land_csg_destroy(cubeBi);
    land_csg_destroy(ABi);
}
void csg_test_intersect(void) {
    LandCSG * cubeA = csg_cube(NULL);
    LandCSG * cubeB = csg_cube(NULL);
    land_csg_transform(cubeB, land_4x4_matrix_translate(1, 0, 0));
    LandCSG * A_B = land_csg_intersect(cubeA, cubeB);
    test("A/B", 6, A_B->polygons->count);
    land_csg_destroy(cubeA);
    land_csg_destroy(cubeB);
    land_csg_destroy(A_B);
}
void csg_test(void) {
    csg_test_shapes();
    csg_test_union();
    csg_test_subtract();
    csg_test_intersect();
}
    /* 0 is the left mouse button
     * 1 is the right mouse button
     * 2 is the middle mouse button
     * 3,4,... are extra mouse buttons
     * A button is either pressed or not. And it either changed since the
     * last time or not. The old API gives us 4 possibilities:
     * 00 not pressed and was not
     * 01 just pressed (was not in last tick)
     * 10 just released (not pressed but was last tick)
     * 11 being held down
     * However, this will not work with clicks that happen "between" two ticks:
     * tick     0          1          2          3          4
     * mouse       D          U  D  U    D   U
     * In the above case the 4 ticks will have:
     * 1: 01 (just pressed, correct)
     * 2: 10 (just released) Completely ignores the click in between
     * 3: 00 (nothing) Completely ignores the click
     * 4: 00
     * There is no good way to solve this with this API. If clicks are detected
     * as 01 (react as soon as pressed) then we should return this:
     * 1: 01
     * 2: 01 (there was a click after all)
     * 3: 01 (same)
     * 4: 10
     * This would capture all 3 clicks but would see only one if they are
     * detected as 10.
     * If clicks are detected as 10 (react when the button is released):
     * 1: 01 (so no click yet)
     * 2: 10 (we lose the really fast click, which would still be fine)
     * 3: 10 (there was a click)
     * 4: 00
     * This would capture 2 of the 3 clicks (which is fine, we can never detect
     * more than one click per tick). But it would only detect one click if
     * detected as 01.
     * Both of those solutions also make it so the previous tick state could
     * differ from the actual one, possibly messing up drag&drop code and so
     * on if not prepared for it.
     * One stop-gap measure we do employ is if there is just one fast click
     * with no click before, we create both a fake 01 and fake 10.
     * tick     0          1          2          3          4
     * D   U
     * 1: 00
     * 2: 01 (fake, but not really)
     * 3: 10 (to go with it)
     * 4: 00
     */
static int mx, my, mz;
static int omx, omy, omz;
static int buttons [5], obuttons [5];
static int _clicks [5];
static int _releases [5];
static float tx [11], ty [11], tb [11], otb [11];
void land_mouse_init(void) {
    land_show_mouse_cursor();
}
void land_mouse_tick(void) {
    omx = mx;
    omy = my;
    omz = mz;
    for (int i = 0; i < 5; i++) {
        if (buttons [i] == 0) {
            obuttons [i] = 0;
        }
        else if (buttons [i] == 1) {
            obuttons [i] = 1;
        }
        else if (buttons [i] == 2) {
            obuttons [i] = 0;
            buttons [i] = 0;
        }
        else if (buttons [i] == 3) {
            obuttons [i] = 1;
            buttons [i] = 1;
        }
        else if (buttons [i] == 7) {
            obuttons [i] = 1;
            buttons [i] = 0;
        }
        _clicks [i] = 0;
        _releases [i] = 0;
    }
    for (int i = 0; i < 11; i += 1) {
        otb [i] = tb [i];
    }
}
void land_mouse_move_event(int x, int y, int z) {
    mx = x;
    my = y;
    mz = z;
}
void land_touch_event(float x, float y, int n, int d) {
    if (n > 10) {
        return ;
    }
    tx [n] = x;
    ty [n] = y;
    if (d == 1) {
        tb [n] = 1;
    }
    if (d == - 1) {
        tb [n] = 0;
    }
}
float land_touch_x(int n) {
    if (n > 10) {
        return 0;
    }
    return tx [n];
}
float land_touch_y(int n) {
    if (n > 10) {
        return 0;
    }
    return ty [n];
}
bool land_touch_down(int n) {
    if (n > 10) {
        return 0;
    }
    return tb [n];
}
bool land_touch_delta(int n) {
    if (n > 10) {
        return 0;
    }
    return otb [n] != tb [n];
}
void land_mouse_button_down_event(int b) {
    _clicks [b]++;
    buttons [b] |= 1 + 2;
}
void land_mouse_button_up_event(int b) {
    _releases [b]++;
    buttons [b] &= ~ 1;
    if (obuttons [b] == 0 && buttons [b] == 2) {
        buttons [b] = 7;
    }
}
int land_mouse_x(void) {
    /* """Return the mouse X coordinate for the current tick."""
     */
    return mx;
}
int land_mouse_y(void) {
    /* """Return the mouse Y coordinate for the current tick."""
     */
    return my;
}
int land_mouse_z(void) {
    /* """Return the mouse wheel coordinate for the current tick."""
     */
    return mz;
}
int land_mouse_b(void) {
    /* """deprecated"""
     */
    int mb = 0;
    for (int i = 0; i < 5; i += 1) {
        if (land_mouse_button(i)) {
            mb |= 1 << i;
        }
    }
    return mb;
}
int land_mouse_button(int i) {
    /* """Return the mouse button state for the current tick."""
     */
    return buttons [i] & 1;
}
int land_mouse_delta_x(void) {
    return mx - omx;
}
int land_mouse_delta_y(void) {
    return my - omy;
}
int land_mouse_delta_z(void) {
    return mz - omz;
}
int land_mouse_delta_b(void) {
    /* """deprecated"""
     */
    int mb = 0;
    for (int i = 0; i < 5; i += 1) {
        if (land_mouse_delta_button(i)) {
            mb |= 1 << i;
        }
    }
    return mb;
}
int land_mouse_delta_button(int i) {
    return (buttons [i] & 1) ^ (obuttons [i] & 1);
}
int land_mouse_button_clicked(int i) {
    return _clicks [i];
}
int land_mouse_button_released(int i) {
    return _releases [i];
}
void land_mouse_set_pos(int x, int y) {
    platform_mouse_set_pos(x, y);
    mx = x;
    my = y;
}
bool land_hide_mouse_cursor(void) {
    platform_hide_mouse_cursor();
    return true;
}
bool land_show_mouse_cursor(void) {
    platform_show_mouse_cursor();
    return true;
}
static LandList * runners;
static LandRunner * active_runner;
void land_runner_register(LandRunner * self) {
    land_log_message("land_runner_register \"%s\"\n", self->name);
    land_add_list_data(& runners, self);
}
void land_runner_initialize(LandRunner * self, char const * name, void(* init)(LandRunner * self), void(* enter)(LandRunner * self), void(* tick)(LandRunner * self), void(* draw)(LandRunner * self), void(* leave)(LandRunner * self), void(* destroy)(LandRunner * self)) {
    self->name = land_strdup(name);
    self->init = init;
    self->enter = enter;
    self->tick = tick;
    self->draw = draw;
    self->leave = leave;
    self->destroy = destroy;
}
LandRunner* land_runner_new(char const * name, void(* init)(LandRunner * self), void(* enter)(LandRunner * self), void(* tick)(LandRunner * self), void(* draw)(LandRunner * self), void(* leave)(LandRunner * self), void(* destroy)(LandRunner * self)) {
    LandRunner * self;
    land_alloc(self);
    self->allocated = 1;
    land_runner_initialize(self, name, init, enter, tick, draw, leave, destroy);
    return self;
}
void land_runner_switch_active(LandRunner * self) {
    land_runner_leave_active();
    active_runner = self;
    if (active_runner && ! active_runner->inited) {
        active_runner->inited = 1;
        if (active_runner->init) {
            active_runner->init(active_runner);
        }
    }
    land_runner_enter_active();
}
void land_runner_enter_active(void) {
    LandRunner * self = active_runner;
    if (self && self->enter) {
        self->enter(self);
    }
}
void land_runner_tick_active(void) {
    LandRunner * self = active_runner;
    if (self && self->tick) {
        self->tick(self);
    }
}
void land_runner_draw_active(void) {
    LandRunner * self = active_runner;
    if (self && self->draw) {
        self->draw(self);
    }
}
void land_runner_leave_active(void) {
    LandRunner * self = active_runner;
    if (self && self->leave) {
        self->leave(self);
    }
}
void land_runner_destroy_all(void) {
    land_log_message("land_runner_destroy_all\n");
    LandListItem * i;
    if (! runners) {
        return ;
    }
    for (i = runners->first; i; i = i->next) {
        LandRunner * self = (LandRunner *) i->data;
        if (self->destroy) {
            self->destroy(self);
        }
        land_log_message("destroyed %s\n", self->name);
        land_free(self->name);
        if (self->allocated) {
            land_free(self);
        }
    }
    land_list_destroy(runners);
}
int land_widget_layout_freeze(LandWidget * self) {
    int nl = self->no_layout;
    self->no_layout = 1;
    return ! nl;
}
int land_widget_layout_unfreeze(LandWidget * self) {
    int nl = self->no_layout;
    self->no_layout = 0;
    return nl;
}
void land_widget_layout_set_grid(LandWidget * self, int columns, int rows) {
    self->box.rows = rows;
    self->box.cols = columns;
    land_widget_layout(self);
}
void land_widget_layout_disable(LandWidget * self) {
    self->box.flags |= GUL_NO_LAYOUT;
}
void land_widget_layout_enable(LandWidget * self) {
    self->box.flags &= ~ GUL_NO_LAYOUT;
}
void land_widget_layout_set_grid_position(LandWidget * self, int column, int row) {
    self->box.col = column;
    self->box.row = row;
    if (self->parent) {
        land_widget_layout(self->parent);
    }
}
void land_widget_layout_set_grid_extra(LandWidget * self, int columns, int rows) {
    self->box.extra_cols = columns;
    self->box.extra_rows = rows;
    if (self->parent) {
        land_widget_layout(self->parent);
    }
}
void land_widget_layout_set_minimum_size(LandWidget * self, int w, int h) {
    self->box.min_width = w;
    self->box.min_height = h;
}
void land_widget_layout_set_maximum_size(LandWidget * self, int w, int h) {
    self->box.max_width = w;
    self->box.max_height = h;
}
void land_widget_layout_set_minimum_height(LandWidget * self, int h) {
    self->box.min_height = h;
}
void land_widget_layout_set_shrinking(LandWidget * self, int x, int y) {
    if (x) {
        self->box.flags |= GUL_SHRINK_X;
    }
    if (y) {
        self->box.flags |= GUL_SHRINK_Y;
    }
    if (self->parent && ! self->parent->no_layout) {
        land_widget_layout(self);
    }
}
void land_widget_layout_set_expanding(LandWidget * self, int x, int y) {
    if (x) {
        self->box.flags &= ~ GUL_SHRINK_X;
    }
    if (y) {
        self->box.flags &= ~ GUL_SHRINK_Y;
    }
    if (self->parent) {
        land_widget_layout(self);
    }
}
void land_widget_layout(LandWidget * self) {
    if (! self->no_layout) {
        land_internal_gul_layout_updated(self);
    }
}
void land_widget_layout_initialize(LandWidget * self, int x, int y, int w, int h) {
    land_internal_land_gul_box_initialize(& self->box);
    self->box.x = x;
    self->box.y = y;
    self->box.w = w;
    self->box.h = h;
}
#ifndef LAND_NO_NET
#ifdef WINDOWS
#define SHUT_RDWR SD_BOTH
#endif
#define D(_) _
#ifdef WINDOWS
static void cleanup(void) {
    WSACleanup();
}
static void sockerror(char const * name) {
    int err = WSAGetLastError();
    char const * what = "unknown";
    switch (err) {
        case WSAEINTR: {
            what = "WSAEINTR";
            break;
        }
        case WSAEBADF: {
            what = "WSAEBADF";
            break;
        }
        case WSAEACCES: {
            what = "WSAEACCES";
            break;
        }
        case WSAEFAULT: {
            what = "WSAEFAULT";
            break;
        }
        case WSAEINVAL: {
            what = "WSAEINVAL";
            break;
        }
        case WSAEMFILE: {
            what = "WSAEMFILE";
            break;
        }
        case WSAEWOULDBLOCK: {
            what = "WSAEWOULDBLOCK";
            break;
        }
        case WSAEINPROGRESS: {
            what = "WSAEINPROGRESS";
            break;
        }
        case WSAEALREADY: {
            what = "WSAEALREADY";
            break;
        }
        case WSAENOTSOCK: {
            what = "WSAENOTSOCK";
            break;
        }
    }
    fprintf(stderr, "%s: %s [%d]\n", name, what, err);
}
#else
#define closesocket close
#define sockerror perror
#endif
static int nonblocking(LandNet * self) {
    int r;
    #if defined(WINDOWS)
    u_long a = 1;
    r = ioctlsocket(self->sock, FIONBIO, & a);
    #else
    int a = 1;
    r = ioctl(self->sock, FIONBIO, & a);
    #endif
    if (r) {
        sockerror("ioctl");
        closesocket(self->sock);
        return - 1;
    }
    return 0;
}
static void split_address(char const * address, char * (* host), int * port) {
    char * colon = strrchr(address, ':');
    if (colon) {
        * host = land_calloc(colon - address + 1);
        strncpy(* host, address, colon - address);
        (* host) [colon - address] = '\0';
        * port = atoi(colon + 1);
    }
    else {
        * host = land_strdup(address);
        * port = 0;
    }
}
static void _get_address(struct sockaddr_in sock_addr, char * address) {
    if (sock_addr.sin_addr.s_addr == INADDR_ANY) {
        sprintf(address, "*.*.*.*");
    }
    else {
        char * ip = (char *) & sock_addr.sin_addr.s_addr;
        sprintf(address, "%d.%d.%d.%d", (unsigned char) ip [0], (unsigned char) ip [1], (unsigned char) ip [2], (unsigned char) ip [3]);
    }
    sprintf(address + strlen(address), ":%d", ntohs(sock_addr.sin_port));
}
char* land_net_get_address(LandNet * self, int remote) {
    /* Return either the local or remote address of the connection.
     */
    int s;
    static char address [256];
    struct sockaddr_in sock_addr;
    socklen_t addrlength = sizeof sock_addr;
    s = (remote ? getpeername : getsockname)(self->sock, (struct sockaddr *) & sock_addr, & addrlength);
    if (s == 0) {
        _get_address(sock_addr, address);
    }
    else {
        sprintf(address, "?:?");
    }
    return address;
}
LandNet* land_net_new(void) {
    LandNet * self;
    land_alloc(self);
    static int once = 1;
    if (once) {
        #ifdef WINDOWS
        WORD wVersionRequested;
        WSADATA wsaData;
        int err;
        wVersionRequested = MAKEWORD(1, 0);
        err = WSAStartup(wVersionRequested, & wsaData);
        if (err != 0) {
            return NULL;
        }
        land_exit_function(cleanup);
        #else
        struct sigaction sa;
        sigset_t mask;
        sigemptyset(& mask);
        sa.sa_handler = SIG_IGN;
        sa.sa_mask = mask;
        sa.sa_flags = 0;
        sigaction(SIGPIPE, & sa, NULL);
        #endif
        once = 0;
    }
    if ((self->sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        sockerror("socket");
        return self;
    }
    nonblocking(self);
    return self;
}
void land_net_listen(LandNet * self, char const * address) {
    int r;
    struct sockaddr_in sock_address;
    char * host;
    int port;
    if (self->state != LAND_NET_INVALID) {
        return ;
    }
    self->local_address = land_strdup(address);
    split_address(address, & host, & port);
    struct hostent * hostinfo;
    if (! (hostinfo = gethostbyname(host))) {
        sockerror("gethostbyname");
        land_free(host);
        return ;
    }
    #ifdef WINDOWS
    char a = 1;
    #else
    int a = 1;
    #endif
    setsockopt(self->sock, IPPROTO_TCP, SO_REUSEADDR, & a, sizeof (a));
    sock_address.sin_family = AF_INET;
    sock_address.sin_addr = * (struct in_addr *) hostinfo->h_addr;
    sock_address.sin_port = htons(port);
    r = bind(self->sock, (struct sockaddr *) & sock_address, sizeof sock_address);
    if (r < 0) {
        sockerror("bind");
        land_free(host);
        return ;
    }
    r = listen(self->sock, SOMAXCONN);
    if (r < 0) {
        sockerror("listen");
        land_free(host);
        return ;
    }
    self->state = LAND_NET_LISTENING;
    D(land_log_message("Listening on host %s port %d (%s).\n", host, port, land_net_get_address(self, 0)));
    land_free(host);
}
static void land_net_poll_accept(LandNet * self) {
    int r;
    struct sockaddr_in address;
    socklen_t address_length = sizeof address;
    if (self->accepted) {
        return ;
    }
    r = accept(self->sock, (struct sockaddr *) & address, & address_length);
    if (r < 0) {
        #if defined(LINUX)
        if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {
            return ;
        }
        #elif defined(WINDOWS)
        if (WSAGetLastError() == WSAEINTR || WSAGetLastError() == WSAEWOULDBLOCK) {
            return ;
        }
        #endif
        sockerror("accept");
        return ;
    }
    self->accepted = land_calloc(sizeof (* self->accepted));
    self->accepted->sock = r;
    nonblocking(self->accepted);
    self->accepted->state = LAND_NET_OK;
    D(land_log_message("Accepted connection (%s ->", land_net_get_address(self->accepted, 0)));
    D(land_log_message_nostamp(" %s).\n", land_net_get_address(self->accepted, 1)));
}
LandNet* land_net_accept(LandNet * self) {
    if (self->state == LAND_NET_LISTENING && self->accepted) {
        LandNet * accepted = self->accepted;
        self->accepted = NULL;
        return accepted;
    }
    return NULL;
}
void land_net_connect(LandNet * self, char const * address) {
    struct sockaddr_in sock_address;
    char * host;
    int port;
    struct hostent * hostinfo;
    if (self->state != LAND_NET_INVALID) {
        return ;
    }
    self->remote_address = land_strdup(address);
    split_address(address, & host, & port);
    if (! (hostinfo = gethostbyname(host))) {
        sockerror("gethostbyname");
        land_free(host);
        return ;
    }
    sock_address.sin_family = AF_INET;
    sock_address.sin_addr = * (struct in_addr *) hostinfo->h_addr;
    sock_address.sin_port = htons(port);
    if (connect(self->sock, (struct sockaddr *) & sock_address, sizeof sock_address)) {
        bool err;
        #if defined(WINDOWS)
        err = WSAGetLastError() != WSAEINPROGRESS && WSAGetLastError() != WSAEWOULDBLOCK;
        #else
        err = errno != EINPROGRESS;
        #endif
        if (err) {
            sockerror("connect");
            closesocket(self->sock);
            land_free(host);
            return ;
        }
    }
    self->state = LAND_NET_CONNECTING;
    D(land_log_message("Connecting to host %s port %d (from %s).\n", host, port, land_net_get_address(self, 0)));
    land_free(host);
}
static void land_net_poll_connect(LandNet * self) {
    int r;
    struct timeval tv;
    fd_set ds;
    FD_ZERO(& ds);
    FD_SET((unsigned) self->sock, & ds);
    tv.tv_sec = 0;
    tv.tv_usec = 0;
    r = select(FD_SETSIZE, NULL, & ds, NULL, & tv);
    if (r > 0) {
        #ifdef WINDOWS
        char a;
        int as = sizeof a;
        #else
        socklen_t a, as = sizeof a;
        #endif
        if (getsockopt(self->sock, SOL_SOCKET, SO_ERROR, & a, & as) == 0) {
            if (a != 0) {
                errno = a;
                self->state = LAND_NET_INVALID;
                sockerror("select(connect)");
                return ;
            }
        }
        self->state = LAND_NET_OK;
        D(land_log_message("Connected (%s ->", land_net_get_address(self, 0)));
        D(land_log_message_nostamp(" %s).\n", land_net_get_address(self, 1)));
        return ;
    }
    bool err;
    #if defined WINDOWS
    err = WSAGetLastError() != WSAEINTR;
    #else
    err = r < 0 && errno != EINTR;
    #endif
    if (err) {
        sockerror("select");
        closesocket(self->sock);
        return ;
    }
}
#ifdef DEBUG_BYTES
static void debug_packet(char const * buffer, int size) {
    land_log_message("Sent %d bytes: ", size);
    int i;
    for (i = 0; i < size; i++) {
        int c = (unsigned char) buffer [i];
        land_log_message_nostamp("%d[%c],", c, c >= 32 && c <= 127 ? c : '.');
    }
    land_log_message_nostamp("\n");
}
#endif
struct LagSimulator {
    int ringpos;
    int ringpos2;
    char packets [256 * 256];
    int size [256];
    double t [256];
    double delay;
    double jitter;
};
static LagSimulator* lag_simulator_new(double delay, double jitter) {
    LagSimulator * self;
    land_alloc(self);
    self->delay = delay;
    self->jitter = jitter;
    return self;
}
static void lag_simulator_add(LandNet * net, char const * packet, int size) {
    LagSimulator * self = net->lag_simulator;
    double t = land_get_time();
    if (packet) {
        memcpy(self->packets + self->ringpos * 256, packet, size);
        self->size [self->ringpos] = size;
        self->t [self->ringpos] = t + land_rnd(- self->jitter, self->jitter);
        self->ringpos++;
        if (self->ringpos == 256) {
            self->ringpos = 0;
        }
    }
    while (self->ringpos2 != self->ringpos) {
        int i = self->ringpos2;
        if (t >= self->t [i] + self->delay) {
            self->ringpos2++;
            if (self->ringpos2 == 256) {
                self->ringpos2 = 0;
            }
            net->lag_simulator = NULL;
            packet = self->packets + i * 256;
            size = self->size [i];
            land_net_send(net, packet, size);
            net->lag_simulator = self;
        }
        else {
            break;
        }
    }
}
void land_net_lag_simulator(LandNet * self, double delay, double jitter) {
    self->lag_simulator = lag_simulator_new(delay, jitter);
}
void land_net_limit_receive_rate(LandNet * self, int rate) {
    self->max_rate = rate;
}
static int _create_datagram_socket(LandNet * self) {
    if (! self->sockd) {
        int r;
        r = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
        if (r >= 0) {
            self->sockd = r;
        }
        else {
            return 1;
        }
        #if defined(WINDOWS)
        u_long b = 1;
        r = ioctlsocket(self->sockd, FIONBIO, & b);
        #else
        int b = 1;
        r = ioctl(self->sockd, FIONBIO, & b);
        #endif
        if (r < 0) {
            sockerror("ioctl");
        }
        #ifdef WINDOWS
        char a = 1;
        #else
        int a = 1;
        #endif
        r = setsockopt(self->sockd, SOL_SOCKET, SO_BROADCAST, & a, sizeof (a));
        if (r < 0) {
            sockerror("setsockopt");
        }
    }
    return 0;
}
static int _send_datagram(LandNet * self, char const * address, char const * packet, int size) {
    char * host;
    int port;
    split_address(address, & host, & port);
    struct hostent * hostinfo = gethostbyname(host);
    if (! hostinfo) {
        land_free(host);
        return 0;
    }
    struct sockaddr_in sock_address;
    sock_address.sin_family = AF_INET;
    sock_address.sin_addr = * (struct in_addr *) hostinfo->h_addr;
    sock_address.sin_port = htons(port);
    int r = sendto(self->sockd, packet, size, 0, (struct sockaddr *) & sock_address, sizeof sock_address);
    land_free(host);
    return r;
}
int land_net_send_datagram(LandNet * self, char const * address, char const * packet, int size) {
    /* [experimental]
     * This is to directly send a datagram to some address. This is an experimental
     * feature and for now, you should use land_net_send when possible.
     */
    _create_datagram_socket(self);
    return _send_datagram(self, address, packet, size);
}
int land_net_recv_datagram(LandNet * self, int port, char * (* address), char * packet, int size) {
    /* [experimental]
     * Receives a datagram. Returns the number of received bytes (less than or
     * equal to size), or 0 if there's no datagram to be received. If address is
     * not None and the return value is > 0, it will point to a static buffer
     * containing the source address. (If you need that address for anything,
     * copy it to your own buffer immediately.)
     */
    _create_datagram_socket(self);
    if (port) {
        struct sockaddr_in laddr;
        laddr.sin_family = AF_INET;
        laddr.sin_port = htons(port);
        laddr.sin_addr.s_addr = INADDR_ANY;
        bind(self->sockd, (struct sockaddr *) & laddr, sizeof laddr);
    }
    struct sockaddr_in sock_address;
    socklen_t addrsize = sizeof sock_address;
    int r = recvfrom(self->sockd, packet, size, 0, (struct sockaddr *) & sock_address, & addrsize);
    if (r > 0) {
        if (address) {
            static char static_address [256];
            _get_address(sock_address, static_address);
            * address = static_address;
        }
        return r;
    }
    return 0;
}
void land_net_send(LandNet * self, char const * buffer, size_t size) {
    size_t bytes = 0;
    int r = 0;
    if (self->state != LAND_NET_OK) {
        return ;
    }
    if (self->lag_simulator) {
        lag_simulator_add(self, buffer, size);
        return ;
    }
    while (1) {
        r = send(self->sock, buffer + bytes, size - bytes, 0);
        if (r < 0) {
            #if defined WINDOWS
            if (WSAGetLastError() == WSAEINTR || WSAGetLastError() == WSAEWOULDBLOCK) {
                r = 0;
            }
            else {
                sockerror("send");
                return ;
            }
            #else
            if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {
                r = 0;
            }
            else {
                sockerror("send");
                return ;
            }
            #endif
        }
        bytes += r;
        if (bytes < size) {
            continue;
        }
        break;
    }
    #ifdef DEBUG_BYTES
    debug_packet(buffer, size);
    #endif
}
void land_net_buffer(LandNet * self, char * buffer, size_t size) {
    /* Assign a networking buffer to use. This buffer keeps being owned by you,
     * Land will never delete it on its own.
     */
    self->buffer = buffer;
    self->size = size;
    self->full = 0;
}
void land_net_flush(LandNet * self, size_t size) {
    if (size == 0) {
        self->full = 0;
    }
    else {
        if (self->full >= size) {
            memmove(self->buffer, self->buffer + size, self->full - size);
        }
        self->full -= size;
    }
}
static void land_net_poll_recv(LandNet * self) {
    int r;
    if (self->size == 0 || self->full == self->size) {
        return ;
    }
    int _scramble_max = self->size - self->full;
    if (self->max_rate) {
        double t = land_get_time();
        double dt = t - self->timestamp;
        if (dt > 1) {
            dt = 1;
        }
        if (_scramble_max > self->max_rate * dt) {
            _scramble_max = self->max_rate * dt;
        }
        self->timestamp = t;
        if (_scramble_max == 0) {
            return ;
        }
    }
    r = recv(self->sock, self->buffer + self->full, _scramble_max, 0);
    if (r < 0) {
        #if defined WINDOWS
        bool err = WSAGetLastError() != WSAEINTR && WSAGetLastError() != WSAEWOULDBLOCK;
        #else
        bool err = errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN;
        #endif
        if (err) {
            sockerror("recv");
            return ;
        }
        return ;
    }
    if (r == 0) {
        self->state = LAND_NET_INVALID;
    }
    self->full += r;
    #ifdef DEBUG_BYTES
    land_log_message("Received %d bytes: ", r);
    int i;
    for (i = 0; i < r; i++) {
        int c = (unsigned char) self->buffer [self->full - r + i];
        land_log_message_nostamp("%d[%c],", c, c >= 32 && c <= 128 ? c : '.');
    }
    land_log_message_nostamp("\n");
    #endif
}
void land_net_disconnect(LandNet * self) {
    if (self->state == LAND_NET_INVALID) {
        return ;
    }
    if (shutdown(self->sock, SHUT_RDWR)) {
        sockerror("shutdown");
        return ;
    }
    self->state = LAND_NET_INVALID;
}
void land_net_del(LandNet * self) {
    /* Deletes a connection. Make sure to reclaim any buffers you have assigned
     * to it first.
     */
    land_net_disconnect(self);
    if (closesocket(self->sock)) {
        sockerror("close");
    }
    if (self->local_address) {
        land_free(self->local_address);
    }
    if (self->remote_address) {
        land_free(self->remote_address);
    }
    land_free(self);
}
void land_net_poll(LandNet * self) {
    switch ((self->state)) {
        case LAND_NET_INVALID: {
            break;
        }
        case LAND_NET_LISTENING: {
            land_net_poll_accept(self);
            break;
        }
        case LAND_NET_CONNECTING: {
            land_net_poll_connect(self);
            break;
        }
        case LAND_NET_OK: {
            if (self->lag_simulator) {
                lag_simulator_add(self, NULL, 0);
            }
            land_net_poll_recv(self);
            break;
        }
    }
}
#endif
#undef SHUT_RDWR
#undef D
#undef closesocket
#undef sockerror
static LandFontState * land_font_state;
static LandFont * initial;
static int active;
static void line(int x1, int y1, int x2, int y2) {
    float px = x1;
    float py = y1;
    int dx = x2 - x1;
    int dy = y2 - y1;
    int d = abs(dx) > abs(dy) ? abs(dx) : abs(dy);
    if (x1 == x2 && y1 == y2) {
        land_filled_rectangle(x1, y1, x1 + 1, y1 + 1);
        return ;
    }
    for (int i = 0; i < d + 1; i += 1) {
        float fx = px + i * dx / (float) d;
        float fy = py + i * dy / (float) d;
        int x = fx;
        int y = fy;
        land_filled_rectangle(x, y, x + 1, y + 1);
    }
}
static void letter(char glyph, str code) {
    int x = glyph * 16;
    int y = 0;
    int n = strlen(code);
    int v [2] = {0, 0};
    int vi = 0;
    int vc = 0;
    int vs = 0;
    int vn = 0;
    land_color(0, 0, 0, 0);
    land_filled_rectangle(x + 1, y + 1, x + 11, y + 10);
    land_color(1, 1, 1, 1);
    for (int i = 0; i < n; i += 1) {
        char c = code [i];
        if (c >= '0' && c <= '9') {
            vc = vc * 10;
            vc += c - '0';
            vn++;
        }
        else if (c == '-') {
            vs = - 1;
        }
        else {
            if (vn) {
                if (vs) {
                    vc = - vc;
                }
                v [vi] = vc;
                vi++;
            }
            vc = vs = vn = 0;
        }
        if (c == 'm') {
            x += v [0];
            y += v [1];
            vi = 0;
        }
        else if (c == 'l') {
            line(1 + x, 1 + y, 1 + x + v [0], 1 + y + v [1]);
            x += v [0];
            y += v [1];
            vi = 0;
        }
    }
}
static void initial_font(void) {
    LandImage * i = land_image_new(128 * 16, 16);
    land_set_image_display(i);
    land_blend(LAND_BLEND_SOLID);
    land_clear(1, 1, 0, 1);
    for (int i = 0; i < 128; i += 1) {
        letter(i, "");
    }
    letter('A', "0 8 m 0 -8 l 8 0 l 0 8 l 0 -3 m -8 0 l");
    letter('B', "0 8 l 6 0 l 2 -2 l -2 -2 l 2 -2 l -2 -2 l -6 0 l 0 4 m 6 0 l");
    letter('C', "8 0 m -8 0 l 0 8 l 8 0 l");
    letter('D', "0 8 l 4 0 l 4 -4 l -4 -4 l -4 0 l");
    letter('E', "0 8 l 8 0 l -8 -4 m 8 0 l -8 -4 m 8 0 l");
    letter('F', "0 8 l 0 -4 m 8 0 l -8 -4 m 8 0 l");
    letter('G', "8 0 m -8 0 l 0 8 l 8 0 l 0 -4 l -4 0 l");
    letter('H', "0 8 l 8 0 m 0 -8 l -8 4 m 8 0 l");
    letter('I', "8 0 l -4 0 m 0 8 l -4 0 m 8 0 l");
    letter('J', "0 8 m 8 0 l 0 -8 l");
    letter('K', "0 8 l 0 -4 m 4 0 l 4 -4 l -4 4 m 4 4 l");
    letter('L', "0 8 l 8 0 l");
    letter('M', "0 8 m 0 -8 l 8 0 l 0 8 l -4 0 m 0 -8 l");
    letter('N', "0 8 m 0 -8 l 8 0 l 0 8 l");
    letter('O', "8 0 l 0 8 l -8 0 l 0 -8 l");
    letter('P', "0 8 l 0 -8 m 6 0 l 2 2 l -2 2 l -6 0 l");
    letter('Q', "8 0 l 0 8 l -8 0 l 0 -8 l 4 4 m 4 4 l");
    letter('R', "0 8 l 0 -8 m 6 0 l 2 2 l -2 2 l -6 0 l 4 0 m 4 4 l");
    letter('S', "8 0 m -8 0 l 0 4 l 8 0 l 0 4 l -8 0 l");
    letter('T', "8 0 l -4 0 m 0 8 l");
    letter('U', "0 8 l 8 0 l 0 -8 l");
    letter('V', "0 4 l 4 4 l 4 -4 l 0 -4 l");
    letter('W', "0 8 l 8 0 l 0 -8 l -4 0 m 0 8 l");
    letter('X', "8 8 l -8 0 m 8 -8 l");
    letter('Y', "4 4 l 0 4 l 0 -4 m 4 -4 l");
    letter('Z', "8 0 l -8 8 l 8 0 l");
    letter('a', "1 2 m 6 0 l 0 6 l -6 0 l 0 -3 l 6 0 l");
    letter('b', "1 0 m 0 8 l 3 0 l 3 -3 l -3 -3 l -3 0 l");
    letter('c', "7 8 m -6 -3 l 6 -3 l");
    letter('d', "7 0 m 0 8 l -3 0 l -3 -3 l 3 -3 l 3 0 l");
    letter('e', "7 8 m -3 0 l -3 -3 l 3 -3 l 3 3 l -6 0 l");
    letter('f', "3 0 m 0 8 l -2 -4 m 4 0 l -2 -4 m 4 0 l 0 4 l");
    letter('g', "1 2 m 6 0 l 0 6 l -6 0 l 0 -2 m 6 0 l -6 0 m 0 -4 l");
    letter('h', "1 0 m 0 8 l 0 -6 m 6 0 l 0 6 l");
    letter('i', "1 4 m 3 0 l 0 4 l 3 0 l -3 -6 m 0 0 l");
    letter('j', "5 4 m 0 4 l -4 0 l 4 -6 m 0 0 l");
    letter('k', "1 0 m 0 8 l 0 -3 m 3 0 l 3 3 l -3 -3 m 3 -3 l");
    letter('l', "1 0 m 3 0 l 0 8 l 3 0 l");
    letter('m', "1 8 m 0 -6 l 6 0 l 0 6 l -3 0 m 0 -6 l");
    letter('n', "1 8 m 0 -6 l 6 0 l 0 6 l");
    letter('o', "1 2 m 6 0 l 0 6 l -6 0 l 0 -6 l");
    letter('p', "1 2 m 6 0 l 0 4 l -6 0 l 0 2 m 0 -6 l");
    letter('q', "1 2 m 6 0 l 0 6 l 0 -2 m -6 0 l 0 -4 l");
    letter('r', "1 2 m 0 6 l 0 -3 m 3 -3 l 3 0 l");
    letter('s', "7 2 m -3 0 l -3 3 l 6 0 l -3 3 l -3 0 l");
    letter('t', "4 0 m 0 8 l -3 -6 m 6 0 l");
    letter('u', "1 2 m 0 6 l 6 0 l 0 -6 l");
    letter('v', "1 2 m 0 3 l 3 3 l 3 -3 l 0 -3 l");
    letter('w', "1 2 m 0 6 l 6 0 l 0 -6 l -3 0 m 0 6 l");
    letter('x', "1 2 m 6 6 l -6 0 m 6 -6 l");
    letter('y', "1 2 m 3 3 l 3 -3 m -6 6 l");
    letter('z', "1 2 m 6 0 l -6 6 l 6 0 l");
    letter('.', "4 8 m 0 0 l");
    letter('/', "0 8 m 8 -8 l");
    letter('\\', "8 8 l");
    letter('-', "1 4 m 6 0 l");
    letter('+', "1 4 m 6 0 l -3 -3 m 0 6 l");
    letter('0', "8 0 l 0 8 l -8 0 l 0 -8 l");
    letter('1', "4 0 m 0 8 l -4 0 m 8 0 l");
    letter('2', "8 0 l 0 4 l -8 4 l 8 0 l");
    letter('3', "8 0 l 0 4 l -8 0 l 8 0 m 0 4 l -8 0 l");
    letter('4', "0 4 l 8 0 l 0 -4 m 0 8 l");
    letter('5', "8 0 m -8 0 l 0 4 l 8 0 l 0 4 l -8 0 l");
    letter('6', "8 0 m -8 0 l 0 8 l 8 0 l 0 -4 l -8 0 l");
    letter('7', "8 0 l -4 8 l");
    letter('8', "2 4 m -2 2 l 2 2 l 4 0 l 2 -2 l -2 -2 l 2 -2 l -2 -2 l -4 0 l -2 2 l 2 2 l 4 0 l");
    letter('9', "8 0 l 0 8 l -8 0 l 0 -8 m 0 4 l 9 0 l");
    land_unset_image_display();
    int r [] = {0, 127};
    LandFont * f = land_font_from_image(i, 1, r);
    land_font_state->font = initial = f;
    land_image_destroy(i);
}
void land_font_init(void) {
    if (active) {
        return ;
    }
    land_log_message("land_font_init\n");
    land_alloc(land_font_state);
    platform_font_init();
    active = 1;
    initial_font();
}
void land_font_exit(void) {
    if (! active) {
        return ;
    }
    land_free(land_font_state);
    platform_font_exit();
    active = 0;
}
int land_font_active(void) {
    return active;
}
LandFont* land_font_load(char const * filename, float size) {
    /* Load the given font file, with the given size. The size usually is the pixel
     * height of a line in the font. But some fonts, e.g. bitmap fonts, will
     * ignore it. The font also us made the current font if successfully loaded.
     */
    char * path = land_path_with_prefix(filename);
    LandFont * self = platform_font_load(path, size);
    land_free(path);
    if (self->flags & LAND_LOADED) {
        land_font_state->font = self;
    }
    return self;
}
void land_font_destroy(LandFont * self) {
    platform_font_destroy(self);
}
LandFont* land_font_new(void) {
    LandFont * f = platform_font_new();
    return f;
}
void land_font_scale(LandFont * f, double scaling) {
    f->xscaling = f->yscaling = scaling;
}
void land_font_yscale(LandFont * f, double scaling) {
    f->yscaling = scaling;
}
void land_font_set(LandFont * self) {
    land_font_state->font = self;
}
void land_text_pos(float x, float y) {
    land_font_state->x_pos = x;
    land_font_state->y_pos = y;
}
void land_text_set_x(float x) {
    land_font_state->x_pos = x;
}
void land_text_set_y(float y) {
    land_font_state->y_pos = y;
}
void land_text_set_width(float w) {
    land_font_state->adjust_width = w;
}
float land_text_x_pos(void) {
    return land_font_state->x_pos;
}
float land_text_y_pos(void) {
    return land_font_state->y_pos;
}
float land_text_x(void) {
    return land_font_state->x;
}
float land_text_y(void) {
    return land_font_state->y;
}
float land_text_width(void) {
    return land_font_state->w;
}
float land_text_height(void) {
    return land_font_state->h;
}
int land_text_state(void) {
    return land_font_state->off;
}
float land_font_height(LandFont * self) {
    return self->size * self->yscaling;
}
LandFont* land_font_current(void) {
    return land_font_state->font;
}
float land_line_height(void) {
    return land_font_height(land_font_current());
}
void land_text_off(void) {
    land_font_state->off = 1;
}
void land_text_on(void) {
    land_font_state->off = 0;
}
void land_paragraph_start(float wrap_width) {
    land_font_state->in_paragraph = 1;
    land_font_state->paragraph_x = land_font_state->x_pos;
    land_font_state->paragraph_wrap = wrap_width;
}
void land_paragraph_end(void) {
    land_font_state->in_paragraph = 0;
}
void land_print_string(char const * s, int newline, int alignment) {
    LandFontState * lfs = land_font_state;
    if (lfs->in_paragraph && lfs->paragraph_wrap > 0) {
        float w = land_text_get_width(s);
        if (lfs->x_pos + w > lfs->paragraph_x + lfs->paragraph_wrap) {
            lfs->x_pos = lfs->paragraph_x;
            lfs->y_pos += lfs->h;
        }
    }
    if (lfs->print_override) {
        PrintFunc * backup = lfs->print_override;
        lfs->print_override = NULL;
        backup(s, alignment);
        lfs->print_override = backup;
    }
    else {
        platform_font_print(land_font_state, s, alignment);
    }
    if (newline) {
        lfs->y_pos = lfs->y + lfs->h;
        if (lfs->in_paragraph) {
            lfs->x_pos = lfs->paragraph_x;
        }
    }
    else {
        lfs->x_pos = lfs->x + lfs->w;
    }
}
void land_font_set_print_override(PrintFunc print_override) {
    land_font_state->print_override = print_override;
}
float land_text_get_width(char const * str) {
    int onoff = land_font_state->off;
    land_font_state->off = 1;
    platform_font_print(land_font_state, str, 0);
    land_font_state->off = onoff;
    return land_font_state->w;
}
int land_text_get_char_offset(char const * str, int nth) {
    char * u = land_strdup(str);
    char * p = u;
    for (int i = 0; i < nth; i++) {
        land_utf8_char(& p);
    }
    * p = 0;
    int x = land_text_get_width(u);
    land_free(u);
    return x;
}
int land_text_get_char_index(char const * str, int x) {
    if (x < 0) {
        return 0;
    }
    int l = 0;
    char * p = (char *) str;
    while (land_utf8_char(& p)) {
        l++;
    }
    for (int i = 0; i <= l; i++) {
        if (land_text_get_char_offset(str, i) > x) {
            return i - 1;
        }
    }
    return l;
}
void land_print(char const * text, ...) {
    VPRINT;
    land_print_string(s, 1, 0);
}
void land_print_right(char const * text, ...) {
    VPRINT;
    land_print_string(s, 1, LandAlignRight);
}
void land_print_center(char const * text, ...) {
    VPRINT;
    land_print_string(s, 1, LandAlignCenter);
}
void land_print_middle(char const * text, ...) {
    VPRINT;
    land_print_string(s, 1, LandAlignCenter | LandAlignMiddle);
}
void land_write(char const * text, ...) {
    VPRINT;
    land_print_string(s, 0, 0);
}
void land_write_right(char const * text, ...) {
    VPRINT;
    land_print_string(s, 0, LandAlignRight);
}
void land_write_center(char const * text, ...) {
    VPRINT;
    land_print_string(s, 0, LandAlignCenter);
}
void land_write_middle(char const * text, ...) {
    VPRINT;
    land_print_string(s, 0, LandAlignCenter | LandAlignMiddle);
}
void land_printv(char const * text, va_list args) {
    va_list args2;
    va_copy(args2, args);
    int n = vsnprintf(NULL, 0, text, args2);
    va_end(args2);
    if (n < 0) {
        n = 1023;
    }
    char s [n + 1];
    vsnprintf(s, n + 1, text, args);
    land_print_string(s, 1, 0);
}
void land_writev(char const * text, va_list args) {
    va_list args2;
    va_copy(args2, args);
    int n = vsnprintf(NULL, 0, text, args2);
    va_end(args2);
    if (n < 0) {
        n = 1023;
    }
    char s [n + 1];
    vsnprintf(s, n + 1, text, args);
    land_print_string(s, 0, 0);
}
static int _wordwrap_helper(char const * text, int w, int h, void(* cb)(int a, int b, void * data), void * data) {
    int y = land_text_y_pos();
    float fh = land_font_state->font->size;
    char const * line_start_p = text;
    land_font_state->adjust_width = w;
    while (1) {
        if (h > 0 && land_text_y_pos() >= y + h) {
            break;
        }
        float width_of_line = 0;
        int word_end_glyphs = 0;
        char const * word_end_p = line_start_p;
        char const * prev_word_end_p = line_start_p;
        char const * ptr;
        int c;
        while (1) {
            bool inside_leading_whitespace = 1;
            ptr = word_end_p;
            int glyphs = word_end_glyphs;
            while (1) {
                c = land_utf8_char_const(& ptr);
                if (c == 0) {
                    break;
                }
                if (c == '\n') {
                    break;
                }
                if (c == ' ') {
                    if (! inside_leading_whitespace) {
                        break;
                    }
                }
                else {
                    inside_leading_whitespace = 0;
                }
                if (inside_leading_whitespace && word_end_glyphs == 0) {
                    line_start_p = ptr;
                }
                else {
                    glyphs++;
                    word_end_p = ptr;
                }
            }
            int x = land_text_get_char_offset(line_start_p, glyphs);
            if (x > w) {
                if (word_end_glyphs == 0) {
                    word_end_glyphs = glyphs;
                    width_of_line = x;
                }
                else {
                    c = ' ';
                    ptr = word_end_p = prev_word_end_p;
                }
                break;
            }
            width_of_line = x;
            word_end_glyphs = glyphs;
            prev_word_end_p = word_end_p;
            if (c == 0 || c == '\n') {
                break;
            }
        }
        if (width_of_line > land_font_state->wordwrap_width) {
            land_font_state->wordwrap_width = width_of_line;
        }
        land_font_state->wordwrap_height += fh;
        if (word_end_p < line_start_p) {
            cb(line_start_p - text, line_start_p - text, data);
        }
        else {
            cb(line_start_p - text, word_end_p - text, data);
        }
        line_start_p = ptr;
        if (c == 0) {
            break;
        }
    }
    return line_start_p - text;
}
static void _print_wordwrap_cb(int a, int b, void * data) {
    void * (* p) = data;
    char * text = p [0];
    int * alignment = p [1];
    char s [b - a + 1];
    strncpy(s, text + a, b - a + 1);
    s [b - a] = 0;
    land_print_string(s, 1, * alignment);
}
int land_print_string_wordwrap(char const * text, int w, int h, int alignment) {
    /* Print text inside, and starts a new line whenever the text goes over the
     * given width, wrapping at whitespace. If a single word is bigger than w, it
     * will be printed in its own line and exceed w. If h is 0, the whole text is
     * printed. Otherwise, only as many lines as fit into h pixels are printed.
     * The return value is the offset into text in bytes of one past the last
     * printed character.
     */
    void * data [] = {(void *) text, & alignment};
    return _wordwrap_helper(text, w, h, _print_wordwrap_cb, data);
}
int land_print_wordwrap(int w, int h, char const * text, ...) {
    VPRINT;
    return land_print_string_wordwrap(s, w, h, 0);
}
int land_print_wordwrap_right(int w, int h, char const * text, ...) {
    VPRINT;
    return land_print_string_wordwrap(s, w, h, 1);
}
int land_print_wordwrap_center(int w, int h, char const * text, ...) {
    VPRINT;
    return land_print_string_wordwrap(s, w, h, 2);
}
static void land_wordwrap_text_cb(int a, int b, void * data) {
    void * (* p) = data;
    char const * text = p [0];
    LandArray * lines = p [1];
    char * s = land_malloc(b - a + 1);
    strncpy(s, text + a, b - a + 1);
    s [b - a] = 0;
    land_array_add(lines, s);
}
LandArray* land_wordwrap_text(int w, int h, char const * str) {
    /* Splits the given string into multiple lines no longer than w pixels. The
     * returned array will have a newly allocated string for each line. You are
     * responsible for freeing those strings again.
     * The calculations will use the current font. Note that for large texts, this
     * can take a while, so you should do calculations on demand and only for the
     * visible text.
     * You can call land_wordwrap_extents after this functions to get the
     * dimensions of a box which will be able to hold all the text.
     */
    LandArray * lines = land_array_new();
    land_font_state->wordwrap_width = 0;
    land_font_state->wordwrap_height = 0;
    if (str) {
        void * data [] = {(void *) str, lines};
        _wordwrap_helper(str, w, h, land_wordwrap_text_cb, data);
    }
    return lines;
}
void land_text_destroy_lines(LandArray * lines) {
    if (! lines) {
        return ;
    }
    land_array_destroy_with_strings(lines);
}
LandArray* land_text_splitlines(char const * str) {
    /* Splits the text into lines, and updates the wordwrap extents.
     */
    land_font_state->wordwrap_width = 0;
    LandArray * lines = land_array_new();
    while (1) {
        char const * p = strchr(str, '\n');
        if (! p) {
            p = str + strlen(str);
        }
        char * s = land_malloc(p - str + 1);
        strncpy(s, str, p - str);
        s [p - str] = 0;
        int w = land_text_get_width(s);
        if (w > land_font_state->wordwrap_width) {
            land_font_state->wordwrap_width = w;
        }
        land_array_add(lines, s);
        if (p [0] == 0) {
            break;
        }
        str = p + 1;
    }
    land_font_state->wordwrap_height = land_font_state->font->size * land_array_count(lines);
    return lines;
}
void land_wordwrap_extents(float * w, float * h) {
    if (w) {
        * w = land_font_state->wordwrap_width;
    }
    if (h) {
        * h = land_font_state->wordwrap_height;
    }
}
void land_print_colored_lines(LandArray * lines, int alignment, LandHash * colors) {
    /* Given an array of lines, print the visible ones.
     */
    float cl, ct, cr, cb;
    land_get_clip(& cl, & ct, & cr, & cb);
    LandColor original = land_color_get();
    float fh = land_line_height();
    float ty = land_text_y_pos();
    int first = (ct - ty) / fh;
    int last = (cb - ty) / fh;
    int n = land_array_count(lines);
    if (first < 0) {
        first = 0;
    }
    if (last > n - 1) {
        last = n - 1;
    }
    if (alignment & LandAlignMiddle) {
        alignment ^= LandAlignMiddle;
        land_font_state->y_pos -= n * fh / 2;
    }
    land_font_state->y_pos += fh * first;
    for (int i = first; i <= last; i++) {
        char * s = land_array_get_nth(lines, i);
        char ckey [] = {i % 256, i / 256, 0};
        if (colors) {
            char * c = land_hash_get(colors, ckey);
            if (c) {
                land_color_set(land_color_name(c));
            }
            else {
                land_color_set(original);
            }
        }
        land_print_string(s, 1, alignment);
    }
    land_color_set(original);
}
void land_set_line_color(LandHash * colors, int i, str col) {
    char ckey [] = {i % 256, i / 256, 0};
    land_hash_insert(colors, ckey, (void *) col);
}
void land_print_lines(LandArray * lines, int alignment) {
    land_print_colored_lines(lines, alignment, NULL);
}
LandFont* land_font_from_image(LandImage * image, int n_ranges, int * ranges) {
    LandFont * self = platform_font_from_image(image, n_ranges, ranges);
    land_font_state->font = self;
    return self;
}
void land_print_multiline(char const * text, ...) {
    VPRINT;
    LandArray * a = land_text_splitlines(s);
    land_print_lines(a, 0);
    land_array_destroy_with_strings(a);
}
uint8_t* land_image_blur_rgba(uint8_t * rgba, int w, int h, double blur_size) {
    float * cache = land_malloc(w * h * sizeof (float) * 4);
    double sigma = blur_size;
    int fs = sigma * 6 + 1;
    double filteri [fs];
    double sigma2 = sigma * sigma;
    double f = 1.0 / sqrt(2 * LAND_PI * sigma2);
    for (int y = 0; y < fs; y += 1) {
        int y2 = y - fs / 2;
        filteri [y] = f * exp(y2 * y2 / sigma2 / (- 2));
    }
    for (int y = 0; y < h; y += 1) {
        for (int x = 0; x < w; x += 1) {
            for (int c = 0; c < 4; c += 1) {
                double s = 0, a = 0;
                for (int v = 0; v < fs; v += 1) {
                    int tx = x;
                    int ty = y + v - fs / 2;
                    double b = filteri [v];
                    a += b;
                    if (ty < 0) {
                        ty = 0;
                    }
                    if (ty > h - 1) {
                        ty = h - 1;
                    }
                    s += rgba [ty * w * 4 + tx * 4 + c] / 255.0 * b;
                }
                cache [x * 4 + w * y * 4 + c] = s / a;
            }
        }
    }
    for (int y = 0; y < h; y += 1) {
        for (int x = 0; x < w; x += 1) {
            for (int c = 0; c < 4; c += 1) {
                double s = 0, a = 0;
                for (int u = 0; u < fs; u += 1) {
                    int tx = x + u - fs / 2;
                    int ty = y;
                    double b = filteri [u];
                    a += b;
                    if (tx < 0) {
                        tx = 0;
                    }
                    if (tx > w - 1) {
                        tx = w - 1;
                    }
                    s += cache [ty * w * 4 + tx * 4 + c] * b;
                }
                rgba [x * 4 + w * y * 4 + c] = s / a * 255;
            }
        }
    }
    land_free(cache);
    return rgba;
}
void land_image_blur(LandImage * image, double blur_size) {
    int w = land_image_width(image);
    int h = land_image_height(image);
    uint8_t * rgba = land_malloc(w * h * 4);
    land_image_get_rgba_data(image, rgba);
    land_image_blur_rgba(rgba, w, h, blur_size);
    land_image_set_rgba_data(image, rgba);
    land_free(rgba);
}
    /* The art of programming tilemaps
     * = Types of tilemaps =
     * == The classic fixed-cell grid-map ==
     * This is the simplest form of a tilemap. Just define a grid and store a tile-id
     * in each cell.
     * == Optimized fixed-cell map ==
     * There are various ways to optimize the simple tilemap to consume less memory.
     * For example, quad-trees or similar techniques. A quad-tree can optimize away
     * large empty areas, and makes it easy to have different-sized tiles as long as
     * bigger ones have double the dimensions of smaller ones.
     * == Layers ==
     * Simply have multiple layers, one on top of another. This can have many uses,
     * for example different tile-sizes and parallax scrolling.
     * == Non-rectangle ==
     * Popular types of tiles don't use a fixed rectangular grid, but use hexagon or
     * isometric maps. We'll deal with both of them.
     * = The basics =
     * == Drawing ==
     * Assume, we have a fixed cell map. The x and y position of a tile can
     * be calculated like this:
     * * pixel_x = tile_x * cell_width
     * * pixel_y = tile_y * cell_height
     * If the map data are stored as an array, and each map position just
     * stores a tile number, we  can retriece it like this:
     * * tile_numer = array[tile_y * map_width + tile_x]
     * So now, we have all we need. We can draw the tile to its position.
     * Doing this for all tiles in the map, we can draw the complete map.
     * == Picking ==
     * Something which is not obvious to do at first, but will be useful soon, is how
     * to pick tiles out of a map. Thinking about it, it is quite simple to
     * do. It is, in a sense, the opposite of drawing.
     * We have a (pixel) position, and want to know which tile lies under it.
     * * tile_x = pixel_x / cell_width
     * * tile_y = pixel_y / cell_height
     * == Collision ==
     * Since we know how to pick a tile from a position, we can easily do
     * collision detection now.
     * = Non-rectangular maps =
     * == Isometric (diamond layout) ==
     * == Hexagon (diamond layout) ==
     * == Isometric (row or column shifted layout) ==
     * == Hexagon (row or coumn shifted layout) ==
     */
void land_grid_draw(LandGrid * self, LandView * view) {
    self->vt->draw(self, view);
}
void land_grid_get_cell_at(LandGrid * self, LandView * view, float view_x, float view_y, float * cell_x, float * cell_y) {
    /* Given a view position, return the corresponding cell position.
     * For a wrapped grid, the returned position will always be normalized to
     * lie within the grid, even if the passed position or the view's scroll
     * position are outside.
     */
    self->vt->get_cell_at(self, view, view_x, view_y, cell_x, cell_y);
}
void land_grid_get_cell_position(LandGrid * self, LandView * view, float cell_x, float cell_y, float * view_x, float * view_y) {
    /* Given a cell position, return the corresponding view position, in pixels.
     * For a wrapped grid, the returned position will first be normalized to
     * lie within the grid, and then related to the view position. Try normalizing
     * the view's scroll position first (land_view_ensure_inside_grid) so it lies
     * within the grid, if you experience unexpected offsets.
     */
    self->vt->get_cell_position(self, view, cell_x, cell_y, view_x, view_y);
}
void land_grid_initialize(LandGrid * self, int cell_w, int cell_h, int x_cells, int y_cells) {
    self->x_cells = x_cells;
    self->y_cells = y_cells;
    self->cell_w = cell_w;
    self->cell_h = cell_h;
}
void land_grid_init(void) {
    land_log_message("land_grid_init\n");
    land_tilemap_init();
    land_isometric_init();
    land_sprites_init();
}
void land_grid_exit(void) {
    land_log_message("land_grid_exit\n");
    land_tilemap_exit();
    land_isometric_exit();
    land_sprites_exit();
}
void land_grid_del(LandGrid * self) {
    land_call_method(self, del, (self));
}
    /* _____
     * Theme
     * a theme determines the styling and border size of each widget.
     * ______
     * Layout
     * Different widgets have different default layout. Some have a fixed size
     * or a fixed minimum size and some grow to take as much or little space as
     * is available.
     * In general by default most of them will set the minimum size to the
     * size given in the constructor or to the minimum size required for the
     * contents given in the constructor (like a picture or text) but grow to
     * use additional space if available.
     * Containers also have different layout for their children.
     * A Board will place children at their x/y coordinate and keep their
     * initial size.
     * A VBox and HBox instead will distribute the available space to their
     * children (while growing itself to use all available space).
     * A Scrolling widget will not change its size but provide scrollbars to
     * scroll the child.
     */
struct Waves {
    int count;
    float * xy;
};
LandNoise* land_noise_new(LandNoiseType t, int seed) {
    LandNoise * self;
    land_alloc(self);
    self->t = t;
    self->noise = land_array_new();
    self->count = 1;
    self->levels = 1;
    self->lerp = LandPerlinLerpCosine;
    self->z_scale = 1;
    self->minval = - 1e9;
    self->maxval = + 1e9;
    self->wrap = 1;
    self->seed = land_random_new(seed);
    return self;
}
void land_noise_set_random(LandNoise * self, LandRandom * random) {
    /* Note: Ownership of the LandRandom object remains at the caller who
     * must make sure it lives as long as the noise is being used.
     */
    if (self->seed && ! self->use_external_seed) {
        land_random_del(self->seed);
    }
    self->seed = random;
    self->use_external_seed = 1;
}
void land_noise_set_size(LandNoise * self, int w, int h) {
    self->w = w;
    self->h = h;
}
void land_noise_set_lerp(LandNoise * self, LandPerlinLerp lerp) {
    self->lerp = lerp;
}
void land_noise_set_count(LandNoise * self, int n) {
    self->count = n;
}
void land_noise_set_levels(LandNoise * self, int n) {
    self->levels = n;
}
void land_noise_set_amplitude(LandNoise * self, float amplitude) {
    self->amplitude = amplitude;
}
void land_noise_set_power_modifier(LandNoise * self, float power_modifier) {
    self->power_modifier = power_modifier;
}
void land_noise_set_randomness(LandNoise * self, float randomness) {
    self->randomness = randomness;
}
void land_noise_set_minmax(LandNoise * self, float minval, float maxval) {
    self->minval = minval;
    self->maxval = maxval;
}
void land_noise_set_warp(LandNoise * self, LandNoise * warp, float x, float y, float sx, float sy) {
    self->warp = 1;
    self->warp_x = x;
    self->warp_y = y;
    self->warp_sx = sx;
    self->warp_sy = sy;
    land_array_add(self->noise, warp);
}
void land_noise_set_blur(LandNoise * self, LandNoise * blur, int size) {
    self->blur = 1;
    self->blur_size = size;
    self->w = blur->w;
    self->h = blur->h;
    land_array_add(self->noise, blur);
}
void land_noise_set_wrap(LandNoise * self, bool wrap) {
    self->wrap = wrap;
}
static void _smoothen(LandNoise * self) {
    int w = self->w;
    int h = self->h;
    self->cache = land_malloc(w * h * sizeof (* self->cache));
    for (int y = 0; y < self->h; y += 1) {
        for (int x = 0; x < self->w; x += 1) {
            self->cache [x + y * self->w] = land_noise_at(land_array_get_nth(self->noise, 0), x, y);
        }
    }
    _smoothen_temp(self->cache, w, h, self->blur_size, self->wrap);
}
static void _smoothen_temp(float * noise, int w, int h, int blur_size, bool wrap) {
    float * cache2 = land_malloc(w * h * sizeof (* cache2));
    double sigma = blur_size;
    int fs = sigma * 6 + 1;
    double filteri [fs];
    double sigma2 = sigma * sigma;
    double f = 1.0 / sqrt(2 * LAND_PI * sigma2);
    for (int y = 0; y < fs; y += 1) {
        int y2 = y - fs / 2;
        filteri [y] = f * exp(y2 * y2 / sigma2 / (- 2));
    }
    for (int y = 0; y < h; y += 1) {
        for (int x = 0; x < w; x += 1) {
            double s = 0, a = 0;
            for (int v = 0; v < fs; v += 1) {
                int tx = x;
                int ty = y + v - fs / 2;
                double b = filteri [v];
                a += b;
                if (ty < 0) {
                    if (wrap) {
                        ty += h;
                    }
                    else {
                        ty = 0;
                    }
                }
                if (ty > h - 1) {
                    if (wrap) {
                        ty -= h;
                    }
                    else {
                        ty = h - 1;
                    }
                }
                s += noise [ty * w + tx] * b;
            }
            cache2 [x + w * y] = s / a;
        }
    }
    for (int y = 0; y < h; y += 1) {
        for (int x = 0; x < w; x += 1) {
            double s = 0, a = 0;
            for (int u = 0; u < fs; u += 1) {
                int tx = x + u - fs / 2;
                int ty = y;
                double b = filteri [u];
                a += b;
                if (tx < 0) {
                    if (wrap) {
                        tx += w;
                    }
                    else {
                        tx = 0;
                    }
                }
                if (tx > w - 1) {
                    if (wrap) {
                        tx -= w;
                    }
                    else {
                        tx = w - 1;
                    }
                }
                s += cache2 [ty * w + tx] * b;
            }
            noise [x + w * y] = s / a;
        }
    }
    land_free(cache2);
}
static float* _white(LandNoise * self) {
    float * noise = land_malloc(self->w * self->h * sizeof (* noise));
    for (int y = 0; y < self->h; y += 1) {
        for (int x = 0; x < self->w; x += 1) {
            noise [x + self->w * y] = land_random_f(self->seed, - 1, 1);
        }
    }
    return noise;
}
void land_noise_prepare(LandNoise * self) {
    if (self->warp) {
        land_noise_prepare(land_array_get_nth(self->noise, 0));
        return ;
    }
    if (self->blur) {
        land_noise_prepare(land_array_get_nth(self->noise, 0));
        _smoothen(self);
        return ;
    }
    if (self->t == LandNoiseWhite) {
        int w = self->w;
        int h = self->h;
        int n = self->levels;
        int size = _scramble_max(w, h) / 8;
        for (int i = 0; i < n; i += 1) {
            float * noise = _white(self);
            _smoothen_temp(noise, w, h, size, self->wrap);
            land_array_add(self->noise, noise);
            size /= 2;
            if (size < 1) {
                size = 1;
            }
        }
        _multires_cache(self);
    }
    if (self->t == LandNoiseVoronoi) {
        void * noise = land_voronoi_create(self->seed, self->w, self->h, self->count, self->randomness);
        land_array_add(self->noise, noise);
    }
    if (self->t == LandNoiseWaves) {
        void * noise = _waves_create(self, self->w, self->h);
        land_array_add(self->noise, noise);
    }
    if (self->t == LandNoisePerlin) {
        int n = self->levels;
        int w = 2;
        int h = 2;
        if (self->h < self->w) {
            w = h * self->w / self->h;
        }
        for (int i = 0; i < n; i += 1) {
            LandPerlin * noise = land_perlin_create(self->seed, w, h);
            land_perlin_set_lerp(noise, self->lerp);
            land_array_add(self->noise, noise);
            if (w < self->w && h < self->h) {
                w *= 2;
                h *= 2;
            }
        }
        _multires_cache(self);
    }
    if (self->t == LandNoisePlasma) {
        void * noise = land_plasma_new(self->seed, self->w, self->h, self->power_modifier, self->amplitude);
        land_plasma_generate(noise);
        land_array_add(self->noise, noise);
    }
    if (self->t == LandNoiseValue) {
        self->cache = land_calloc(self->w * self->h * sizeof (* self->cache));
    }
}
static void _multires_cache(LandNoise * self) {
    self->cache = land_malloc(self->w * self->h * sizeof (* self->cache));
    for (int y = 0; y < self->h; y += 1) {
        for (int x = 0; x < self->w; x += 1) {
            float v = 0.0;
            int i = 0;
            {
                LandArrayIterator __iter0__ = LandArrayIterator_first(self->noise);
                for (void * noise = LandArrayIterator_item(self->noise, &__iter0__); LandArrayIterator_next(self->noise, &__iter0__); noise = LandArrayIterator_item(self->noise, &__iter0__)) {
                    float val = _get_resolution(self, noise, i, x, y);
                    float s = pow(2 + self->power_modifier, - i + self->amplitude);
                    if (s < 1.0 / 128) {
                        break;
                    }
                    v += val * s;
                    i++;
                }
            }
            self->cache [x + self->w * y] = v;
        }
    }
}
static float _get_resolution(LandNoise * self, void * sub, int i, int x, int y) {
    if (self->t == LandNoisePerlin) {
        LandPerlin * sub2 = sub;
        return land_perlin_at(sub2, (float) x * sub2->w / self->w, (float) y * sub2->h / self->h);
    }
    if (self->t == LandNoiseWhite) {
        float * sub2 = sub;
        float j = pow(self->w * self->h, 0.9) / pow(2, 10 + i);
        return sub2 [x + self->w * y] * j;
    }
    return 0;
}
void land_noise_callback(LandNoise * self, float(* cb)(float x)) {
    for (int y = 0; y < self->h; y += 1) {
        for (int x = 0; x < self->w; x += 1) {
            self->cache [x + self->w * y] = cb(self->cache [x + self->w * y]);
        }
    }
}
void land_noise_destroy(LandNoise * self) {
    if (self->seed && ! self->use_external_seed) {
        land_random_del(self->seed);
    }
    land_free(self);
}
float land_noise_at(LandNoise * self, float x, float y) {
    float v = land_noise_at_raw(self, x, y);
    float v2 = v * self->z_scale + self->z_offset;
    if (self->z_ease > 0.0000001) {
        float v3 = 1 - cos(v2 * LAND_PI / 2);
        if (v2 < 0) {
            v3 = - v3;
        }
        v2 = v2 * (1 - self->z_ease) + v3 * self->z_ease;
    }
    if (v2 < self->minval) {
        v2 = self->minval;
    }
    if (v2 > self->maxval) {
        v2 = self->maxval;
    }
    return v2;
}
float land_noise_at_raw(LandNoise * self, float x, float y) {
    if (self->warp) {
        LandNoise * warp = land_array_get_nth(self->noise, 0);
        float qx = land_noise_at(warp, x, y);
        float qy = land_noise_at(warp, x + self->warp_x, y + self->warp_y);
        float v = land_noise_at(warp, x + self->warp_sx * qx, y + self->warp_sy * qy);
        return v;
    }
    if (self->blur || self->t == LandNoiseWhite || self->t == LandNoisePerlin || self->t == LandNoiseValue) {
        int ix = floor(x);
        int iy = floor(y);
        if (self->wrap) {
            ix %= self->w;
            if (ix < 0) {
                ix += self->w;
            }
            iy %= self->h;
            if (iy < 0) {
                iy += self->h;
            }
        }
        else {
            if (ix < 0) {
                ix = 0;
            }
            if (iy < 0) {
                iy = 0;
            }
            if (ix > self->w - 1) {
                ix = self->w - 1;
            }
            if (iy > self->h - 1) {
                iy = self->h - 1;
            }
        }
        return self->cache [ix + iy * self->w];
    }
    if (self->t == LandNoiseVoronoi) {
        return land_voronoi_at(land_array_get_nth(self->noise, 0), x, y);
    }
    if (self->t == LandNoisePlasma) {
        return land_plasma_at(land_array_get_nth(self->noise, 0), x, y);
    }
    if (self->t == LandNoiseWaves) {
        return _waves_at(land_array_get_nth(self->noise, 0), self->power_modifier, self->amplitude, x, y);
    }
    return 0;
}
static void* _waves_create(LandNoise * noise, int w, int h) {
    Waves * waves;
    land_alloc(waves);
    waves->count = noise->count;
    waves->xy = land_calloc(noise->count * 2 * sizeof (* waves->xy));
    for (int i = 0; i < noise->count; i += 1) {
        waves->xy [i + i + 0] = land_random_f(noise->seed, 0, w);
        waves->xy [i + i + 1] = land_random_f(noise->seed, 0, h);
    }
    return waves;
}
static float _waves_at(Waves * self, float power_modifier, float amplitude, int x, int y) {
    float v = 0;
    for (int i = 0; i < self->count; i += 1) {
        float dx = self->xy [i + i + 0] - x;
        float dy = self->xy [i + i + 1] - y;
        v += power_modifier * sin(amplitude * sqrt(dx * dx + dy * dy));
    }
    return v;
}
void land_noise_z_transform(LandNoise * self, float scale, float offset) {
    self->z_scale = scale;
    self->z_offset = offset;
}
void land_noise_z_ease(LandNoise * self, float x) {
    self->z_ease = x;
}
LandWidgetInterface * land_widget_box_interface;
void land_widget_box_draw(LandWidget * self) {
    land_widget_theme_draw(self);
}
LandWidget* land_widget_box_new(LandWidget * parent, int x, int y, int w, int h) {
    LandWidget * self = land_widget_base_new(parent, x, y, w, h);
    land_widget_box_interface_initialize();
    self->vt = land_widget_box_interface;
    land_widget_theme_initialize(self);
    land_call_method(parent, update, (parent));
    return self;
}
void land_widget_box_interface_initialize(void) {
    if (land_widget_box_interface) {
        return ;
    }
    land_widget_box_interface = land_widget_copy_interface(land_widget_base_interface, "box");
    land_widget_box_interface->id = LAND_WIDGET_ID_BASE;
    land_widget_box_interface->draw = land_widget_box_draw;
}
#ifdef LAND_MEMLOG
static char const * LOGFILE = "memlog.log";
static LandLock * lock;
#define MAX_BLOCKS 1024 * 1024
static int installed = 0;
static bool installing;
static struct LandMemBlockInfo not_freed [MAX_BLOCKS];
static int _num = 0;
static int _size = 0;
static int _maxnum = 0;
static int _maxsize = 0;
static void done(void) {
    int n;
    installing = 1;
    land_thread_delete_lock(lock);
    FILE * lf = fopen(LOGFILE, "a");
    fprintf(lf, "Memory statistics:\n");
    fprintf(lf, "Maximum number of simultanously allocated blocks: %d\n", _maxnum);
    fprintf(lf, "Maximum number of simultanously allocated elements: %d\n", _maxsize);
    fprintf(lf, "Memory leaks: %d\n", _num);
    for (n = 0; n < _num; n++) {
        fprintf(lf, "%s: %d: %d elements [%s] not freed: %p\n", not_freed [n].file, not_freed [n].line, not_freed [n].size, not_freed [n].id, not_freed [n].ptr);
        fflush(lf);
        backtrace_symbols_fd(not_freed [n].trace, not_freed [n].trace_depth, fileno(lf));
        if (! strcmp(not_freed [n].id, "")) {
            fprintf(lf, "    first bytes: [");
            for (int i = 0; i < 16; i++) {
                if (i >= not_freed [n].size) {
                    break;
                }
                int c = * ((unsigned char *)(not_freed [n].ptr) + i);
                if (c >= 32 && c <= 127) {
                    fprintf(lf, "%c", c);
                }
                else {
                    fprintf(lf, "«%d»", c);
                }
            }
            fprintf(lf, "]\n");
        }
    }
    fclose(lf);
}
static void install(void) {
    installed++;
    if (installed == 1) {
        installing = 1;
        lock = land_thread_new_lock();
        installing = 0;
        atexit(done);
        FILE * lf = fopen(LOGFILE, "w");
        fprintf(lf, "Land MemLog\n");
        fclose(lf);
    }
}
void land_memory_add(void * ptr, char const * id, int size, const char * f, int l) {
    if (installing) {
        return ;
    }
    if (! installed) {
        install();
    }
    land_thread_lock(lock);
    if (! ptr) {
        if (size) {
            FILE * lf = fopen(LOGFILE, "a");
            fprintf(lf, "%s: %d: allocation of %d elements [%s] failed\n", f, l, size, id);
            fclose(lf);
        }
        goto ret;
    }
    if (_num >= MAX_BLOCKS) {
        FILE * lf = fopen(LOGFILE, "a");
        fprintf(lf, "memlog block number exceeded\n");
        fclose(lf);
        goto ret;
    }
    not_freed [_num].ptr = ptr;
    not_freed [_num].file = f;
    not_freed [_num].line = l;
    not_freed [_num].id = id;
    not_freed [_num].size = size;
    not_freed [_num].trace_depth = backtrace(not_freed [_num].trace, LAND_MEMBLOCK_INFO_MAX_STACK);
    _num++;
    _size += size;
    if (_num > _maxnum) {
        _maxnum = _num;
    }
    if (_size > _maxsize) {
        _maxsize = _size;
    }
    #ifdef LOGALL
    FILE * lf = fopen(LOGFILE, "a");
    fprintf(lf, "%s: %d: allocated: %d elements [%s] at %p\n", f, l, size, id, ptr);
    fclose(lf);
    #endif
    ret:;
    land_thread_unlock(lock);
}
void land_memory_remove(void * ptr, char const * id, int re, const char * f, int l) {
    if (installing) {
        return ;
    }
    int n;
    if (! installed) {
        install();
    }
    land_thread_lock(lock);
    if (! ptr) {
        if (re) {
            #ifdef LOGALL
            FILE * lf = fopen(LOGFILE, "a");
            fprintf(lf, "%s: %d: reallocated: %p [%s]\n", f, l, ptr, id);
            fclose(lf);
            #endif
            goto ret;
        }
        FILE * lf = fopen(LOGFILE, "a");
        fprintf(lf, "%s: %d: freed 0 pointer [%s]\n", f, l, id);
        fclose(lf);
        abort();
        goto ret;
    }
    for (n = 0; n < _num; n++) {
        if (not_freed [n].ptr == ptr) {
            #ifdef LOGALL
            FILE * lf = fopen(LOGFILE, "a");
            fprintf(lf, "%s: %d: freed: %d elements [%s] at %p\n", f, l, not_freed [n].size, id, ptr);
            fclose(lf);
            #endif
            _size -= not_freed [n].size;
            not_freed [n] = not_freed [_num - 1];
            _num--;
            goto ret;
        }
    }
    FILE * lf = fopen(LOGFILE, "a");
    fprintf(lf, "%s: %d: double freed or never allocated: %p [%s]\n", f, l, ptr, id);
    fclose(lf);
    abort();
    ret:;
    land_thread_unlock(lock);
}
void* land_malloc_memlog(int size, char const * f, int l) {
    void * ptr = malloc(size);
    land_memory_add(ptr, "", size, f, l);
    return ptr;
}
void* land_calloc_memlog(int size, char const * f, int l) {
    void * ptr = calloc(1, size);
    land_memory_add(ptr, "", size, f, l);
    return ptr;
}
void* land_realloc_memlog(void * ptr, int size, char const * f, int l) {
    void * p = realloc(ptr, size);
    land_memory_remove(ptr, "", 1, f, l);
    land_memory_add(p, "", size, f, l);
    return p;
}
char* land_strdup_memlog(char const * s, char const * f, int l) {
    void * p = strdup(s);
    land_memory_add(p, "", strlen(p), f, l);
    return p;
}
void land_free_memlog(void * ptr, char const * f, int l) {
    land_memory_remove(ptr, "", 0, f, l);
    free(ptr);
}
#else
void* land_malloc(int size) {
    return malloc(size);
}
void* land_calloc(int size) {
    return calloc(1, size);
}
void* land_realloc(void * ptr, int size) {
    return realloc(ptr, size);
}
char* land_strdup(char const * s) {
    return strdup(s);
}
void land_free(void * ptr) {
    free(ptr);
}
#endif
#undef MAX_BLOCKS
static float wrap_distance(float x1, float x2, float wrap) {
    /* |x1    x2             |x1
     * |x1              x2   |x1
     * |x2              x1   |x2
     */
    float d = fabs(x2 - x1);
    float d2 = fabs(x1 + wrap - x2);
    if (d2 < d) {
        d = d2;
    }
    d2 = fabs(x2 + wrap - x1);
    if (d2 < d) {
        d = d2;
    }
    return d;
}
static int get_closest(LandVoronoi * self, int x, int y) {
    int mi = - 1;
    int md = INT_MAX;
    for (int i = 0; i < self->n; i += 1) {
        int dx = wrap_distance(self->xy [i].x, x, self->w);
        int dy = wrap_distance(self->xy [i].y, y, self->h);
        if (dx * dx + dy * dy < md) {
            mi = i;
            md = dx * dx + dy * dy;
        }
    }
    return mi;
}
LandVoronoi* land_voronoi_new(LandRandom * seed, int w, int h, int n) {
    LandVoronoi * self;
    land_alloc(self);
    self->w = w;
    self->h = h;
    self->map = land_calloc(w * h * sizeof (* self->map));
    self->distance = land_calloc(w * h * sizeof (* self->distance));
    self->n = n;
    self->xy = land_calloc(n * sizeof (* self->xy));
    self->max_distance = 0;
    for (int i = 0; i < n; i += 1) {
        int x = land_random(seed, 0, w - 1);
        int y = land_random(seed, 0, h - 1);
        self->xy [i].x = x;
        self->xy [i].y = y;
    }
    for (int y = 0; y < h; y += 1) {
        for (int x = 0; x < w; x += 1) {
            int i = get_closest(self, x, y);
            self->map [x + w * y] = i;
        }
    }
    return self;
}
LandVoronoi* land_voronoi_create(LandRandom * seed, int w, int h, int n, float randomness) {
    LandVoronoi * self = land_voronoi_new(seed, w, h, n);
    land_voronoi_distort_with_perlin(self, seed, randomness);
    land_voronoi_calculate_distance(self);
    return self;
}
void land_voronoi_calculate_distance(LandVoronoi * self) {
    int w = self->w;
    int h = self->h;
    for (int y = 0; y < h; y += 1) {
        for (int x = 0; x < w; x += 1) {
            int i = self->map [x + self->w * y];
            int dx = wrap_distance(self->xy [i].x, x, self->w);
            int dy = wrap_distance(self->xy [i].y, y, self->h);
            float d = sqrt(dx * dx + dy * dy);
            self->distance [x + self->w * y] = d;
            if (d > self->max_distance) {
                self->max_distance = d;
            }
        }
    }
}
void land_voronoi_distort_with_perlin(LandVoronoi * self, LandRandom * seed, float randomness) {
    int w = self->w;
    int h = self->h;
    int * map2 = land_calloc(w * h * sizeof (* map2));
    float rs = randomness;
    if (rs < 1) {
        rs = 1;
    }
    LandPerlin * perlin = land_perlin_create(seed, w / rs, h / rs);
    for (int y = 0; y < h; y += 1) {
        for (int x = 0; x < w; x += 1) {
            float xd, yd;
            land_perlin_displace(perlin, x / rs, y / rs, & xd, & yd);
            int dx = land_mod(x + xd * randomness, self->w);
            int dy = land_mod(y + yd * randomness, self->h);
            int i = self->map [dx + w * dy];
            map2 [y * w + x] = i;
        }
    }
    land_perlin_destroy(perlin);
    land_free(self->map);
    self->map = map2;
}
void land_voronoi_destroy(LandVoronoi * self) {
    land_free(self->map);
    land_free(self->distance);
    land_free(self->xy);
    land_free(self);
}
float land_voronoi_at(LandVoronoi * self, int x, int y) {
    float value = self->distance [x + self->w * y] / self->max_distance;
    return value * 2 - 1;
}
#ifdef LAND_MEMLOG
#undef land_buffer_new
#undef land_buffer_destroy
#undef land_buffer_finish
#undef land_buffer_read_from_file
#undef land_buffer_split
LandBuffer* land_buffer_new_memlog(char const * f, int l) {
    LandBuffer * self = land_buffer_new();
    land_memory_add(self, "buffer", 1, f, l);
    return self;
}
void land_buffer_destroy_memlog(LandBuffer * self, char const * f, int l) {
    land_memory_remove(self, "buffer", 1, f, l);
    land_buffer_destroy(self);
}
char* land_buffer_finish_memlog(LandBuffer * self, char const * f, int l) {
    land_memory_remove(self, "buffer", 1, f, l);
    char * s = land_buffer_finish(self);
    land_memory_remove(s, "", 1, f, l);
    land_memory_add(s, "", strlen(s), f, l);
    return s;
}
LandBuffer* land_buffer_read_from_file_memlog(char const * filename, char const * f, int l) {
    LandBuffer * self = land_buffer_read_from_file(filename);
    land_memory_add(self, "buffer", 1, f, l);
    return self;
}
LandArray* land_buffer_split_memlog(LandBuffer const * self, char delim, char const * f, int line) {
    LandArray * a = land_array_new_memlog(f, line);
    int start = 0;
    for (int i = 0; i < self->n; i++) {
        if (self->buffer [i] == delim) {
            LandBuffer * l = land_buffer_new_memlog(f, line);
            land_buffer_add(l, self->buffer + start, i - start);
            land_array_add_memlog(a, l, f, line);
            start = i + 1;
        }
    }
    LandBuffer * l = land_buffer_new_memlog(f, line);
    land_buffer_add(l, self->buffer + start, self->n - start);
    land_array_add_memlog(a, l, f, line);
    return a;
}
#endif
LandBuffer* land_buffer_new(void) {
    LandBuffer * self;
    land_alloc(self);
    return self;
}
LandBuffer* land_buffer_copy(LandBuffer * other) {
    LandBuffer * self;
    land_alloc(self);
    land_buffer_add(self, other->buffer, other->n);
    return self;
}
LandBuffer* land_buffer_extract(LandBuffer * other, int x, int n) {
    LandBuffer * self = land_buffer_new();
    if (n > 0) {
        land_buffer_add(self, other->buffer + x, n);
    }
    return self;
}
LandBuffer* land_buffer_copy_from(LandBuffer * other, int x) {
    LandBuffer * self = land_buffer_new();
    land_buffer_add(self, other->buffer + x, other->n - x);
    return self;
}
void land_buffer_destroy(LandBuffer * self) {
    if (self->buffer) {
        land_free(self->buffer);
    }
    land_free(self);
}
void land_buffer_grow(LandBuffer * self, int n) {
    self->n += n;
    if (self->n > self->size) {
        if (! self->size) {
            self->size = 1;
        }
        while (self->size < self->n) {
            self->size *= 2;
        }
        self->buffer = land_realloc(self->buffer, self->size);
    }
}
void land_buffer_insert(LandBuffer * self, int pos, char const * buffer, int n) {
    land_buffer_grow(self, n);
    memmove(self->buffer + pos + n, self->buffer + pos, self->n - n - pos);
    memcpy(self->buffer + pos, buffer, n);
}
void land_buffer_move(LandBuffer * self, int from_pos, int to_pos, int n) {
    if (from_pos < 0) {
        from_pos += self->n;
    }
    if (to_pos < 0) {
        to_pos += self->n;
    }
    memmove(self->buffer + to_pos, self->buffer + from_pos, n);
}
void land_buffer_cut(LandBuffer * self, int pos, int n) {
    memmove(self->buffer + pos, self->buffer + pos + n, self->n - pos - n);
    self->n -= n;
}
void land_buffer_shorten_left(LandBuffer * self, int n) {
    land_buffer_cut(self, 0, n);
}
void land_buffer_add(LandBuffer * self, char const * b, int n) {
    land_buffer_insert(self, self->n, b, n);
}
void land_buffer_addv(LandBuffer * self, char const * format, va_list args) {
    va_list args2;
    va_copy(args2, args);
    int n = vsnprintf(NULL, 0, format, args2);
    va_end(args2);
    if (n < 0) {
        n = 1023;
    }
    char s [n + 1];
    vsnprintf(s, n + 1, format, args);
    land_buffer_add(self, s, n);
}
void land_buffer_addf(LandBuffer * self, char const * format, ...) {
    va_list args;
    va_start(args, format);
    land_buffer_addv(self, format, args);
    va_end(args);
}
void land_buffer_addl(LandBuffer * self, char const * format, ...) {
    va_list args;
    va_start(args, format);
    land_buffer_addv(self, format, args);
    va_end(args);
    land_buffer_add_char(self, '\n');
}
void land_buffer_add_uint32_t(LandBuffer * self, uint32_t i) {
    land_buffer_add_char(self, i & 255);
    land_buffer_add_char(self, (i >> 8) & 255);
    land_buffer_add_char(self, (i >> 16) & 255);
    land_buffer_add_char(self, (i >> 24) & 255);
}
uint32_t land_buffer_get_uint32_t(LandBuffer * self, int pos) {
    uint8_t * uc = (uint8_t *) self->buffer + pos;
    uint32_t u = * (uc++);
    u += * (uc++) << 8;
    u += * (uc++) << 16;
    u += * (uc++) << 24;
    return u;
}
uint16_t land_buffer_get_uint16_t(LandBuffer * self, int pos) {
    uint8_t * uc = (uint8_t *) self->buffer + pos;
    uint16_t u = * (uc++);
    u += * (uc++) << 8;
    return u;
}
uint8_t land_buffer_get_byte(LandBuffer * self, int pos) {
    uint8_t * uc = (uint8_t *) self->buffer + pos;
    return * uc;
}
float land_buffer_get_float(LandBuffer * self, int pos) {
    float * uc = (float *)(self->buffer + pos);
    return * uc;
}
void land_buffer_add_float(LandBuffer * self, float f) {
    uint32_t * i = (void *) & f;
    land_buffer_add_uint32_t(self, * i);
}
void land_buffer_add_char(LandBuffer * self, char c) {
    land_buffer_add(self, & c, 1);
}
void land_buffer_cat(LandBuffer * self, char const * string) {
    /* Appends a zero-terminated string (without the 0 byte) to the buffer.
     */
    land_buffer_add(self, string, strlen(string));
}
void land_buffer_clear(LandBuffer * self) {
    /* Clears the buffer (but keeps any memory allocation for speedy refilling).
     */
    self->n = 0;
}
void land_buffer_crop(LandBuffer * self) {
    /* Make the buffer use up only the minimum required amount of memory.
     */
    self->buffer = land_realloc(self->buffer, self->n);
    self->size = self->n;
}
char* land_buffer_finish(LandBuffer * self) {
    /* Destroys the buffer, but returns a C-string constructed from it by appending
     * a 0 character. You may not access the pointer you pass to this function
     * anymore after it returns. Also, you have to make sure it does not already
     * contain any 0 characters. When no longer needed, you should free the string
     * with land_free.
     */
    char c [] = "";
    land_buffer_add(self, c, 1);
    char * s = self->buffer;
    self->buffer = NULL;
    land_buffer_destroy(self);
    return s;
}
void land_buffer_println(LandBuffer * self) {
    printf("%.*s\n", self->n, self->buffer);
}
bool land_buffer_empty(LandBuffer * self) {
    return self->n == 0;
}
LandArray* land_buffer_split(LandBuffer const * self, str delim) {
    /* Creates an array of buffers. If there are n occurences of string delim
     * in the buffer, the array contains n + 1 entries. No buffer in the array
     * contains the delim string.
     */
    LandArray * a = land_array_new();
    int start = 0;
    for (int i = 0; i < self->n; i += 1) {
        bool matches = 1;
        int j = 0;
        while (delim [j]) {
            if (self->buffer [i + j] != delim [j]) {
                matches = 0;
                break;
            }
            j++;
        }
        if (matches) {
            LandBuffer * l = land_buffer_new();
            land_buffer_add(l, self->buffer + start, i - start);
            land_array_add(a, l);
            start = i + j;
        }
    }
    LandBuffer * l = land_buffer_new();
    land_buffer_add(l, self->buffer + start, self->n - start);
    land_array_add(a, l);
    return a;
}
void land_buffer_strip_right(LandBuffer * self, char const * what) {
    if (self->n == 0) {
        return ;
    }
    int away = 0;
    char * p = self->buffer + self->n;
    while (p > self->buffer) {
        int c = land_utf8_char_back(& p);
        char const * q = what;
        while (1) {
            int d = land_utf8_char_const(& q);
            if (! d) {
                goto done;
            }
            if (c == d) {
                away++;
                break;
            }
        }
    }
    done:;
    self->n -= away;
}
void land_buffer_strip_left(LandBuffer * self, char const * what) {
    if (self->n == 0) {
        return ;
    }
    int away = 0;
    char * p = self->buffer;
    while (1) {
        again:;
        int c = land_utf8_char(& p);
        if (! c) {
            break;
        }
        char const * q = what;
        while (1) {
            int d = land_utf8_char_const(& q);
            if (! d) {
                break;
            }
            if (c == d) {
                away++;
                goto again;
            }
        }
        break;
    }
    self->n -= away;
    memmove(self->buffer, self->buffer + away, self->n);
}
void land_buffer_strip(LandBuffer * self, char const * what) {
    land_buffer_strip_right(self, what);
    land_buffer_strip_left(self, what);
}
bool land_buffer_is(LandBuffer * self, char const * what) {
    int n = strlen(what);
    if (n != self->n) {
        return 0;
    }
    return memcmp(self->buffer, what, self->n) == 0;
}
void land_buffer_remove_if_start(LandBuffer * self, char const * what) {
    if (memcmp(self->buffer, what, strlen(what)) == 0) {
        land_buffer_shorten_left(self, strlen(what));
    }
}
void land_buffer_remove_if_end(LandBuffer * self, char const * what) {
    if (memcmp(self->buffer + self->n - strlen(what), what, strlen(what)) == 0) {
        land_buffer_shorten(self, strlen(what));
    }
}
int land_buffer_rfind(LandBuffer * self, char c) {
    if (self->n == 0) {
        return - 1;
    }
    for (int i = self->n - 1; i >= 0; i--) {
        if (self->buffer [i] == c) {
            return i;
        }
    }
    return - 1;
}
int land_buffer_find(LandBuffer const * self, int offset, char const * what) {
    int n = strlen(what);
    for (int i = offset; i < self->n; i += 1) {
        for (int j = 0; j < n; j += 1) {
            if (self->buffer [i + j] != what [j]) {
                goto mismatch;
            }
        }
        return i;
        mismatch:;
    }
    return - 1;
}
int land_buffer_replace(LandBuffer * self, int offset, char const * wat, char const * wit) {
    int x = land_buffer_find(self, offset, wat);
    if (x < 0) {
        return x;
    }
    land_buffer_cut(self, x, strlen(wat));
    land_buffer_insert(self, x, wit, strlen(wit));
    return x + strlen(wit);
}
int land_buffer_replace_all(LandBuffer * self, char const * wat, char const * wit) {
    int x = 0;
    int count = 0;
    while (1) {
        x = land_buffer_replace(self, x, wat, wit);
        if (x < 0) {
            break;
        }
        count++;
    }
    return count;
}
void land_buffer_set_length(LandBuffer * self, int n) {
    self->n = n;
}
void land_buffer_shorten(LandBuffer * self, int n) {
    self->n -= n;
}
LandBuffer* land_buffer_read_from_file(char const * filename) {
    /* Read a buffer from the given file. If the file cannot be read, return None.
     */
    LandFile * pf = land_file_new(filename, "rb");
    if (! pf) {
        return NULL;
    }
    LandBuffer * self = land_buffer_new();
    land_file_add_to_buffer(pf, self);
    land_file_destroy(pf);
    return self;
}
bool land_buffer_write_to_file(LandBuffer * self, char const * filename) {
    LandFile * pf = land_file_new(filename, "wb");
    if (! pf) {
        return 0;
    }
    int written = land_file_write(pf, self->buffer, self->n);
    land_file_destroy(pf);
    return written == self->n;
}
#ifndef LAND_NO_COMPRESS
void land_buffer_compress(LandBuffer * self) {
    uLongf destlen = self->n * 1.1 + 12;
    Bytef * dest = land_malloc(destlen);
    compress(dest, & destlen, (void *) self->buffer, self->n);
    dest = land_realloc(dest, destlen);
    land_free(self->buffer);
    self->buffer = (void *) dest;
    self->size = self->n = destlen;
}
void land_buffer_decompress(LandBuffer * self) {
    z_stream z;
    z.zalloc = Z_NULL;
    z.zfree = Z_NULL;
    z.opaque = Z_NULL;
    z.next_in = (void *) self->buffer;
    z.avail_in = self->n;
    int err = inflateInit2(& z, 15 | 32);
    if (err != Z_OK) {
        return ;
    }
    LandBuffer * temp = land_buffer_new();
    char * out = malloc(8192);
    while (1) {
        z.avail_out = 8192;
        z.next_out = (void *) out;
        err = inflate(& z, Z_NO_FLUSH);
        if (err < 0) {
            goto break2;
        }
        land_buffer_add(temp, out, 8192 - z.avail_out);
        if (z.avail_out > 0) {
            break;
        }
    }
    break2:;
    land_free(out);
    land_free(self->buffer);
    self->buffer = temp->buffer;
    self->n = temp->n;
    temp->buffer = NULL;
    land_buffer_destroy(temp);
}
#endif
int land_buffer_compare(LandBuffer * self, LandBuffer * other) {
    if (self->n < other->n) {
        return - 1;
    }
    if (self->n > other->n) {
        return 1;
    }
    return memcmp(self->buffer, other->buffer, self->n);
}
#ifdef ANDROID
#include "android/log.h"
#endif
static bool _nolog;
static char * logname = NULL;
void land_log_overwrite(char const * name) {
    FILE * f;
    if (logname) {
        land_free(logname);
    }
    logname = land_strdup(name);
    f = fopen(logname, "w");
    if (f) {
        fclose(f);
    }
}
void land_log_set(char const * name) {
    if (logname) {
        land_free(logname);
    }
    logname = land_strdup(name);
}
void land_log_del(void) {
    if (logname) {
        land_free(logname);
    }
    logname = NULL;
}
void land_log_new(char const * base, int unique) {
    static int once = 0;
    if (logname) {
        land_free(logname);
    }
    logname = land_malloc(strlen(base) + 10);
    if (! once) {
        atexit(land_log_del);
        once++;
    }
    #ifdef ANDROID
    sprintf(logname, "%s.log", base);
    __android_log_print(ANDROID_LOG_INFO, "land", "%s", "******* new log *******\n");
    #else
    FILE * f;
    int i = 0;
    if (unique) {
        while (1) {
            sprintf(logname, "%s%04d.log", base, i);
            f = fopen(logname, "r");
            if (f) {
                fclose(f);
            }
            i++;
            if (! f) {
                break;
            }
        }
    }
    else {
        sprintf(logname, "%s.log", base);
    }
    f = fopen(logname, "w");
    if (f) {
        fprintf(f, "******* new log *******\n");
        fclose(f);
    }
    #endif
}
void land_log_message_nostamp(char const * format, ...) {
    if (_nolog) {
        return ;
    }
    if (! logname) {
        land_log_new("land", 0);
    }
    va_list va_args;
    va_start(va_args, format);
    #ifdef ANDROID
    char s [16382];
    vsprintf(s, format, va_args);
    __android_log_print(ANDROID_LOG_INFO, "land", "%s", s);
    #else
    FILE * logfile = fopen(logname, "a");
    if (logfile) {
        vfprintf(logfile, format, va_args);
        fclose(logfile);
    }
    else {
        _nolog = 1;
    }
    #endif
    va_end(va_args);
}
void land_log_message(char const * format, ...) {
    if (_nolog) {
        return ;
    }
    if (! logname) {
        land_log_new("land", 0);
    }
    va_list va_args;
    va_start(va_args, format);
    #ifdef ANDROID
    char s [16382];
    vsprintf(s, format, va_args);
    __android_log_print(ANDROID_LOG_INFO, "land", "%s", s);
    #else
    struct timeval tv;
    #ifdef WINDOWS
    tv.tv_usec = 0;
    #else
    gettimeofday(& tv, NULL);
    #endif
    time_t t;
    struct tm tm;
    time(& t);
    tm = * gmtime(& t);
    FILE * logfile = fopen(logname, "a");
    if (logfile) {
        fprintf(logfile, "%04d/%02d/%02d %02d:%02d:%02d.%06ld ", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tv.tv_usec);
        vfprintf(logfile, format, va_args);
        fclose(logfile);
    }
    else {
        _nolog = 1;
    }
    #endif
    va_end(va_args);
}
LandArrayIterator LandArrayIterator_first(LandArray * a) {
    LandArrayIterator i = {0};
    return i;
}
void* LandArrayIterator_item(LandArray * a, LandArrayIterator * i) {
    return i->i < a->count ? a->data [i->i] : NULL;
}
bool LandArrayIterator_next(LandArray * a, LandArrayIterator * i) {
    i->i++;
    return i->i <= a->count;
}
int LandArray__len__(LandArray * a) {
    return a->count;
}
LandArray* land_array_new(void) {
    /* Create a new empty array.
     */
    LandArray * self;
    land_alloc(self);
    return self;
}
void land_array_add(LandArray * self, void * data) {
    /* Add data to an array.
     */
    int i = self->count++;
    if (self->count > self->size) {
        if (self->size == 0) {
            self->size = 1;
        }
        else {
            self->size *= 2;
        }
        self->data = land_realloc(self->data, self->size * sizeof (* self->data));
    }
    self->data [i] = data;
}
void land_array_reserve(LandArray * self, int n) {
    /* Allocate n empty (None) entries for the array. Removes any contents
     * of the array if it already has any data added to it.
     */
    self->count = self->size = n;
    self->data = land_realloc(self->data, self->size * sizeof (* self->data));
    memset(self->data, 0, self->count * sizeof (* self->data));
}
void* land_array_pop(LandArray * self) {
    /* Remove the last element in the array and return it. Only the last element
     * in an array can be removed. To remove another element, you could replace
     * it with the last (land_array_replace_nth) and remove the last with this
     * function.
     */
    if (self->count == 0) {
        return NULL;
    }
    int i = --self->count;
    return self->data [i];
}
void* land_array_remove(LandArray * self, int i) {
    /* Return item at position i and replace it with the last item,
     * shortening the array by one.
     * If i is the last item this is identical to land_array_pop.
     */
    void * last = land_array_pop(self);
    if (i == land_array_count(self)) {
        return last;
    }
    return land_array_replace_nth(self, i, last);
}
void* land_array_shift_remove(LandArray * self, int i) {
    /* Return item at position i and then shift all the other items in
     * the array by one.
     */
    void * x = land_array_get(self, i);
    self->count--;
    memmove(self->data + i, self->data + i + 1, sizeof (* self->data) * (self->count - i));
    return x;
}
void land_array_add_data(LandArray * (* array), void * data) {
    /* *deprecated*
     * Use land_array_add in new code, as this function might be removed in a
     * future version.
     * Given a pointer to a (possibly NULL valued) array pointer, create a new node
     * with the given data, and add to the (possibly modified) array.
     */
    LandArray * self = * array;
    if (! self) {
        #if LAND_MEMLOG
        self = land_array_new_memlog(__FILE__, __LINE__);
        #else
        self = land_array_new();
        #endif
        * array = self;
    }
    land_array_add(self, data);
}
int land_array_find(LandArray * self, void * data) {
    /* Searches the array for the given data. If they are contained, return the
     * first index i so that land_array_get_nth(array, i) == data. If the data
     * cannot be found, -1 is returned.
     */
    for (int i = 0; i < self->count; i++) {
        if (self->data [i] == data) {
            return i;
        }
    }
    return - 1;
}
int land_array_find_string(LandArray * self, char const * string) {
    /* Searches a string array for the given string. If it is contained return the
     * first index i so that land_array_get_nth(array, i) equals the string.
     * Otherwise -1 is returned.
     */
    for (int i = 0; i < self->count; i++) {
        if (land_equals(string, self->data [i])) {
            return i;
        }
    }
    return - 1;
}
void* land_array_get_nth(LandArray const * array, int i) {
    if (i < 0) {
        i += array->count;
    }
    return array->data [i];
}
void* land_array_get_or_none(LandArray const * array, int i) {
    if (i < 0 || i >= array->count) {
        return NULL;
    }
    return land_array_get_nth(array, i);
}
void* land_array_get(LandArray const * array, int i) {
    return land_array_get_nth(array, i);
}
bool land_array_is_empty(LandArray const * array) {
    return array->count == 0;
}
void* land_array_replace_nth(LandArray * array, int i, void * data) {
    /* Replace the array entry at the given index, and return the previous
     * contents.
     */
    if (i >= array->count) {
        return NULL;
    }
    void * old = array->data [i];
    array->data [i] = data;
    return old;
}
void* land_array_replace_or_resize(LandArray * array, int i, void * data) {
    /* Replaces the entry at i and returns the previous data.
     * If i is outside the size of the array, resize it.
     */
    while (array->count < i + 1) {
        land_array_add(array, NULL);
    }
    return land_array_replace_nth(array, i, data);
}
void* land_array_get_last(LandArray * array) {
    return land_array_get_nth(array, array->count - 1);
}
void land_array_destroy(LandArray * self) {
    /* Destroys an array. This does not destroy any of the data put into it - loop
     * through the array before and destroy the data if there are no other
     * references to them.
     */
    if (self->data) {
        land_free(self->data);
    }
    land_free(self);
}
static int cb_free(void * data, void * _) {
    land_free(data);
    return 0;
}
void land_array_destroy_with_strings(LandArray * self) {
    land_array_destroy_with_free(self);
}
void land_array_destroy_with_free(LandArray * self) {
    /* Like [land_array_destroy] but also calls land_free on every
     * element.
     */
    land_array_for_each(self, cb_free, NULL);
    land_array_destroy(self);
}
void land_array_sort(LandArray * self, int(* cmpfnc)(void const * a, void const * b)) {
    /* Sorts the entries in the array. The given callback function gets passed
     * two direct pointers to two array elements, and expects a return value
     * determining the order:
     * < 0: a is before b
     * = 0: order is arbitrary
     * > 0: a is after b
     */
    qsort(self->data, self->count, sizeof (void *), cmpfnc);
}
static int alphacomp(void const * a, void const * b) {
    char const * const * as = a;
    char const * const * bs = b;
    int r = strcmp(* as, * bs);
    return r;
}
void land_array_sort_alphabetical(LandArray * self) {
    /* Expects all array members to be strings and sorts alphabetically.
     */
    land_array_sort(self, alphacomp);
}
int land_array_count(LandArray const * self) {
    if (! self) {
        return 0;
    }
    return self->count;
}
int land_array_for_each(LandArray * self, int(* cb)(void * item, void * data), void * data) {
    /* Call the given callback for each array element. If the callback returns
     * anything but 0, the iteration is stopped. The return value is the number
     * of times the callback was called. The data argument simply is passed as-is
     * to the callback.
     */
    if (! self) {
        return 0;
    }
    int i;
    for (i = 0; i < self->count; i++) {
        if (cb(self->data [i], data)) {
            break;
        }
    }
    return i;
}
void land_array_clear(LandArray * self) {
    /* Clear all elements in the array.
     */
    self->count = 0;
}
void land_array_concat(LandArray * self, LandArray const * other) {
    int new_count = self->count + other->count;
    self->size = new_count;
    self->data = land_realloc(self->data, self->size * sizeof (* self->data));
    memcpy(self->data + self->count, other->data, other->count * sizeof (* other->data));
    self->count = self->size;
}
void land_array_merge(LandArray * self, LandArray * other) {
    land_array_concat(self, other);
    land_array_destroy(other);
}
LandArray* land_array_copy(LandArray const * self) {
    LandArray * copy = land_array_new();
    land_array_concat(copy, self);
    return copy;
}
void land_array_swap(LandArray * self, int a, int b) {
    if (a < 0) {
        a += self->count;
    }
    if (b < 0) {
        b += self->count;
    }
    void * temp = self->data [a];
    self->data [a] = self->data [b];
    self->data [b] = temp;
}
void land_array_move_behind(LandArray * self, int a, int b) {
    /* Move item at position a so it is behind b. If b is 0 then a is moved to
     * the beginning. If b is n then a is moved to the end.
     */
    void * temp = self->data [a];
    if (a < b) {
        for (int i = a; i < b - 1; i++) {
            self->data [i] = self->data [i + 1];
        }
        self->data [b - 1] = temp;
    }
    else {
        for (int i = a; i > b; i--) {
            self->data [i] = self->data [i - 1];
        }
        self->data [b] = temp;
    }
}
void land_array_reverse(LandArray * self) {
    for (int i = 0; i < self->count / 2; i += 1) {
        land_array_swap(self, i, self->count - 1 - i);
    }
}
#ifdef LAND_MEMLOG
#undef land_array_new
#undef land_array_destroy
#undef land_array_add
#undef land_array_clear
#undef land_array_merge
#undef land_array_concat
#undef land_array_copy
LandArray* land_array_new_memlog(char const * f, int l) {
    LandArray * array = land_array_new();
    land_memory_add(array, "array", 1, f, l);
    return array;
}
void land_array_destroy_memlog(LandArray * self, char const * f, int l) {
    land_memory_remove(self, "array", 1, f, l);
    land_array_destroy(self);
}
void land_array_add_memlog(LandArray * self, void * data, char const * f, int l) {
    land_array_add(self, data);
    land_memory_remove(self, "array", 1, f, l);
    land_memory_add(self, "array", self->size, f, l);
}
LandArray* land_array_copy_memlog(LandArray const * self, char const * f, int l) {
    LandArray * copy = land_array_copy(self);
    land_memory_add(copy, "array", copy->size, f, l);
    return copy;
}
void land_array_concat_memlog(LandArray * self, LandArray const * other, char const * f, int l) {
    land_array_concat(self, other);
    land_memory_remove(self, "array", 1, f, l);
    land_memory_add(self, "array", self->size, f, l);
}
void land_array_merge_memlog(LandArray * self, LandArray * other, char const * f, int l) {
    land_array_merge(self, other);
    land_memory_remove(self, "array", 1, f, l);
    land_memory_add(self, "array", self->size, f, l);
    land_memory_remove(other, "array", 1, f, l);
}
void land_array_clear_memlog(LandArray * self, char const * f, int l) {
    land_array_clear(self);
    land_memory_remove(self, "array", 1, f, l);
    land_memory_add(self, "array", self->size, f, l);
}
#endif
LandMemoryPool* land_pool_new_initial(int initial) {
    LandMemoryPool * self;
    land_alloc(self);
    self->allocated = initial;
    self->memory = land_calloc(self->allocated);
    self->prev = self;
    return self;
}
LandMemoryPool* land_pool_new(void) {
    return land_pool_new_initial(1024);
}
void land_pool_destroy(LandMemoryPool * self) {
    LandMemoryPool * last = self->prev;
    while (1) {
        LandMemoryPool * prev = last->prev;
        land_free(last->memory);
        land_free(last);
        if (last == self) {
            break;
        }
        last = prev;
    }
}
void* land_pool_alloc(LandMemoryPool * self, int size) {
    LandMemoryPool * last = self->prev;
    while (last->used + size > last->allocated) {
        LandMemoryPool * another = land_pool_new_initial(last->allocated * 2);
        another->prev = last;
        self->prev = another;
        last = another;
    }
    void * p = last->memory + last->used;
    last->used += size;
    return p;
}
static LandHash * cache;
static double const Xn = 0.95047;
static double const Yn = 1.00000;
static double const Zn = 1.08883;
static double const delta = 6.0 / 29;
static double const delta2 = 6.0 / 29 * 6.0 / 29;
static double const delta3 = 6.0 / 29 * 6.0 / 29 * 6.0 / 29;
static double const tf7 = 1.0 / 4 / 4 / 4 / 4 / 4 / 4 / 4;
LandColor land_color_hsv(float hue, float sat, float val) {
    return platform_color_hsv(hue, sat, val);
}
LandColor land_color_rgba(float r, float g, float b, float a) {
    LandColor c = {r, g, b, a};
    return c;
}
LandColor land_rgb(float r, float g, float b) {
    LandColor c = {r, g, b, 1};
    return c;
}
double srgba_gamma_to_linear(double x) {
    double const a = 0.055;
    if (x < 0.04045) {
        return x / 12.92;
    }
    return pow((x + a) / (1 + a), 2.4);
}
double srgba_linear_to_gamma(double x) {
    double const a = 0.055;
    if (x < 0.0031308) {
        return x * 12.92;
    }
    return pow(x, 1 / 2.4) * (1 + a) - a;
}
LandColor land_color_xyz(double x, double y, double z) {
    LandColor c;
    double r = 3.2406 * x + (- 1.5372 * y) + (- 0.4986 * z);
    double g = - 0.9689 * x + 1.8758 * y + 0.0415 * z;
    double b = 0.0557 * x + (- 0.2040 * y) + 1.0570 * z;
    c.r = srgba_linear_to_gamma(r);
    c.g = srgba_linear_to_gamma(g);
    c.b = srgba_linear_to_gamma(b);
    c.a = 1;
    return c;
}
void land_color_to_xyz(LandColor c, double * x, double * y, double * z) {
    double r = srgba_gamma_to_linear(c.r);
    double g = srgba_gamma_to_linear(c.g);
    double b = srgba_gamma_to_linear(c.b);
    * x = r * 0.4124 + g * 0.3576 + b * 0.1805;
    * y = r * 0.2126 + g * 0.7152 + b * 0.0722;
    * z = r * 0.0193 + g * 0.1192 + b * 0.9505;
}
static double cielab_f(double x) {
    if (x > delta3) {
        return pow(x, 1.0 / 3);
    }
    return 4.0 / 29 + x / delta2 / 3;
}
static double cielab_f_inv(double x) {
    if (x > delta) {
        return pow(x, 3);
    }
    return (x - 4.0 / 29) * 3 * delta2;
}
void land_color_to_cielab(LandColor c, double * L, double * a, double * b) {
    double x, y, z;
    land_color_to_xyz(c, & x, & y, & z);
    x /= Xn;
    y /= Yn;
    z /= Zn;
    * L = 1.16 * cielab_f(y) - 0.16;
    * a = 5.00 * (cielab_f(x) - cielab_f(y));
    * b = 2.00 * (cielab_f(y) - cielab_f(z));
}
LandColor land_color_cielab(double L, double a, double b) {
    double x = Xn * cielab_f_inv((L + 0.16) / 1.16 + a / 5.00);
    double y = Yn * cielab_f_inv((L + 0.16) / 1.16);
    double z = Zn * cielab_f_inv((L + 0.16) / 1.16 - b / 2.00);
    return land_color_xyz(x, y, z);
}
LandColor land_color_lch(double l, double c, double h) {
    double a = c * cos(h);
    double b = c * sin(h);
    return land_color_cielab(l, a, b);
}
LandColor land_color_xyy(double x, double y, double Y) {
    double X = x * Y / y;
    double Z = (1 - y - x) * Y / y;
    return land_color_xyz(X, Y, Z);
}
void land_color_to_xyy(LandColor c, double * x, double * y, double * Y) {
    double X, Z;
    land_color_to_xyz(c, & X, Y, & Z);
    * x = X / (X + * Y + Z);
    * y = * Y / (X + * Y + Z);
}
double land_color_distance_cie94(LandColor color, LandColor other) {
    double l1, a1, b1;
    double l2, a2, b2;
    land_color_to_cielab(color, & l1, & a1, & b1);
    land_color_to_cielab(other, & l2, & a2, & b2);
    return land_color_distance_cie94_lab(l1, a1, b1, l2, a2, b2);
}
double land_color_distance_cie94_lab(double l1, double a1, double b1, double l2, double a2, double b2) {
    double dl = l1 - l2;
    double da = a1 - a2;
    double db = b1 - b2;
    double c1 = sqrt(a1 * a1 + b1 * b1);
    double c2 = sqrt(a2 * a2 + b2 * b2);
    double dc = c1 - c2;
    double dh = da * da + db * db - dc * dc;
    dh = dh < 0 ? 0 : sqrt(dh);
    dc /= 1 + 0.045 * c1;
    dh /= 1 + 0.015 * c1;
    return sqrt(dl * dl + dc * dc + dh * dh);
}
double land_color_distance_ciede2000(LandColor color, LandColor other) {
    double l1, a1, b1;
    double l2, a2, b2;
    land_color_to_cielab(color, & l1, & a1, & b1);
    land_color_to_cielab(other, & l2, & a2, & b2);
    return land_color_distance_ciede2000_lab(l1, a1, b1, l2, a2, b2);
}
double land_color_distance_ciede2000_lab(double l1, double a1, double b1, double l2, double a2, double b2) {
    /* http://www.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf
     */
    double dl = l1 - l2;
    double ml = (l1 + l2) / 2;
    double c1 = sqrt(a1 * a1 + b1 * b1);
    double c2 = sqrt(a2 * a2 + b2 * b2);
    double mc = (c1 + c2) / 2;
    double fac = sqrt(pow(mc, 7) / (pow(mc, 7) + tf7));
    double g = 0.5 * (1 - fac);
    a1 *= 1 + g;
    a2 *= 1 + g;
    c1 = sqrt(a1 * a1 + b1 * b1);
    c2 = sqrt(a2 * a2 + b2 * b2);
    double dc = c2 - c1;
    mc = (c1 + c2) / 2;
    fac = sqrt(pow(mc, 7) / (pow(mc, 7) + tf7));
    double h1 = fmod(2 * pi + atan2(b1, a1), 2 * pi);
    double h2 = fmod(2 * pi + atan2(b2, a2), 2 * pi);
    double dh = 0;
    double mh = h1 + h2;
    if (c1 * c2 != 0) {
        dh = h2 - h1;
        if (dh > pi) {
            dh -= 2 * pi;
        }
        if (dh < - pi) {
            dh += 2 * pi;
        }
        if (fabs(h1 - h2) <= pi) {
            mh = (h1 + h2) / 2;
        }
        else if (h1 + h2 < 2 * pi) {
            mh = (h1 + h2 + 2 * pi) / 2;
        }
        else {
            mh = (h1 + h2 - 2 * pi) / 2;
        }
    }
    dh = 2 * sqrt(c1 * c2) * sin(dh / 2);
    double t = 1 - 0.17 * cos(mh - pi / 6) + 0.24 * cos(2 * mh) + 0.32 * cos(3 * mh + pi / 30) - 0.2 * cos(4 * mh - pi * 7 / 20);
    double mls = pow(ml - 0.5, 2);
    double sl = 1 + 1.5 * mls / sqrt(0.002 + mls);
    double sc = 1 + 4.5 * mc;
    double sh = 1 + 1.5 * mc * t;
    double rt = - 2 * fac * sin(pi / 3 * exp(- pow(mh / pi * 36 / 5 - 11, 2)));
    return sqrt(pow(dl / sl, 2) + pow(dc / sc, 2) + pow(dh / sh, 2) + rt * dc / sc * dh / sh);
}
struct TestData {
    double l1, a1, b1, l2, a2, b2, e;
};
static TestData test_data [] = {{50.0000, 2.6772, - 79.7751, 50.0000, 0.0000, - 82.7485, 2.0425}, {50.0000, 3.1571, - 77.2803, 50.0000, 0.0000, - 82.7485, 2.8615}, {50.0000, 2.8361, - 74.0200, 50.0000, 0.0000, - 82.7485, 3.4412}, {50.0000, - 1.3802, - 84.2814, 50.0000, 0.0000, - 82.7485, 1.0000}, {50.0000, - 1.1848, - 84.8006, 50.0000, 0.0000, - 82.7485, 1.0000}, {50.0000, - 0.9009, - 85.5211, 50.0000, 0.0000, - 82.7485, 1.0000}, {50.0000, 0.0000, 0.0000, 50.0000, - 1.0000, 2.0000, 2.3669}, {50.0000, - 1.0000, 2.0000, 50.0000, 0.0000, 0.0000, 2.3669}, {50.0000, 2.4900, - 0.0010, 50.0000, - 2.4900, 0.0009, 7.1792}, {50.0000, 2.4900, - 0.0010, 50.0000, - 2.4900, 0.0010, 7.1792}, {50.0000, 2.4900, - 0.0010, 50.0000, - 2.4900, 0.0011, 7.2195}, {50.0000, 2.4900, - 0.0010, 50.0000, - 2.4900, 0.0012, 7.2195}, {50.0000, - 0.0010, 2.4900, 50.0000, 0.0009, - 2.4900, 4.8045}, {50.0000, - 0.0010, 2.4900, 50.0000, 0.0010, - 2.4900, 4.8045}, {50.0000, - 0.0010, 2.4900, 50.0000, 0.0011, - 2.4900, 4.7461}, {50.0000, 2.5000, 0.0000, 50.0000, 0.0000, - 2.5000, 4.3065}, {50.0000, 2.5000, 0.0000, 73.0000, 25.0000, - 18.0000, 27.1492}, {50.0000, 2.5000, 0.0000, 61.0000, - 5.0000, 29.0000, 22.8977}, {50.0000, 2.5000, 0.0000, 56.0000, - 27.0000, - 3.0000, 31.9030}, {50.0000, 2.5000, 0.0000, 58.0000, 24.0000, 15.0000, 19.4535}, {50.0000, 2.5000, 0.0000, 50.0000, 3.1736, 0.5854, 1.0000}, {50.0000, 2.5000, 0.0000, 50.0000, 3.2972, 0.0000, 1.0000}, {50.0000, 2.5000, 0.0000, 50.0000, 1.8634, 0.5757, 1.0000}, {50.0000, 2.5000, 0.0000, 50.0000, 3.2592, 0.3350, 1.0000}, {60.2574, - 34.0099, 36.2677, 60.4626, - 34.1751, 39.4387, 1.2644}, {63.0109, - 31.0961, - 5.8663, 62.8187, - 29.7946, - 4.0864, 1.2630}, {61.2901, 3.7196, - 5.3901, 61.4292, 2.2480, - 4.9620, 1.8731}, {35.0831, - 44.1164, 3.7933, 35.0232, - 40.0716, 1.5901, 1.8645}, {22.7233, 20.0904, - 46.6940, 23.0331, 14.9730, - 42.5619, 2.0373}, {36.4612, 47.8580, 18.3852, 36.2715, 50.5065, 21.2231, 1.4146}, {90.8027, - 2.0831, 1.4410, 91.1528, - 1.6435, 0.0447, 1.4441}, {90.9257, - 0.5406, - 0.9208, 88.6381, - 0.8985, - 0.7239, 1.5381}, {6.7747, - 0.2908, - 2.4247, 5.8714, - 0.0985, - 2.2286, 0.6377}, {2.0776, 0.0795, - 1.1350, 0.9033, - 0.0636, - 0.5514, 0.9082}};
void test_ciede2000(void) {
    for (int i = 0; i < 34; i += 1) {
        TestData data = test_data [i];
        double e = land_color_distance_ciede2000_lab(data.l1 / 100, data.a1 / 100, data.b1 / 100, data.l2 / 100, data.a2 / 100, data.b2 / 100);
        double e94 = land_color_distance_cie94_lab(data.l1 / 100, data.a1 / 100, data.b1 / 100, data.l2 / 100, data.a2 / 100, data.b2 / 100);
        double d = e * 100 - data.e;
        bool ok = d * d < 0.0001 * 0.0001;
        printf("%s%d: %.4f == %.4f (%.4f)%s\n", land_color_bash(ok ? "green" : "red"), 1 + i, e * 100, data.e, e94 * 100, land_color_bash("end"));
    }
}
LandColor land_color_premul(float r, float g, float b, float a) {
    LandColor c = {r * a, g * a, b * a, a};
    return c;
}
static int hexval(char c) {
    c = tolower(c);
    if (c >= '0' && c <= '9') {
        return (c - '0');
    }
    if (c >= 'a' && c <= 'f') {
        return 10 + (c - 'a');
    }
    return 0;
}
LandColor land_color_name(char const * name) {
    if (name && name [0] == '#') {
        LandColor c;
        c.r = (hexval(name [1]) * 16 + hexval(name [2])) / 255.0;
        c.g = (hexval(name [3]) * 16 + hexval(name [4])) / 255.0;
        c.b = (hexval(name [5]) * 16 + hexval(name [6])) / 255.0;
        c.a = 1;
        return c;
    }
    int i1 = land_find(name, "*");
    int i2 = land_find(name, "/");
    if (i1 >= 0 || i2 >= 0) {
        int i = i1;
        if (i < 0 || i2 < i) {
            i = i2;
        }
        char * name2 = land_substring(name, 0, i);
        LandColor c = platform_color_name(name2);
        char prev = 0;
        while (1) {
            if (name [i] == 0) {
                break;
            }
            if (name [i] >= '1' && name [i] <= '9') {
                float d = name [i] - '0';
                if (prev == '*') {
                    c.r *= d;
                    c.g *= d;
                    c.b *= d;
                }
                else if (prev == '/') {
                    c.r /= d;
                    c.g /= d;
                    c.b /= d;
                }
            }
            prev = name [i];
            i++;
        }
        return c;
    }
    return platform_color_name(name);
}
LandColor land_color_mix(LandColor c, LandColor mix, float p) {
    float q = 1 - p;
    c.r = c.r * q + mix.r * p;
    c.g = c.g * q + mix.g * p;
    c.b = c.b * q + mix.b * p;
    c.a = c.a * q + mix.a * p;
    return c;
}
void land_color_to_html(LandColor c, char html [8]) {
    sprintf(html, "#%02x%02x%02x", (int)(c.r * 255), (int)(c.g * 255), (int)(c.b * 255));
}
LandColor land_color_int(int i) {
    int r = i & 255;
    int g = (i >> 8) & 255;
    int b = (i >> 16) & 255;
    int a = (i >> 24) & 255;
    return land_color_rgba(r / 255.0, g / 255.0, b / 255.0, a / 255.0);
}
int land_color_to_int(LandColor c) {
    int r = c.r * 255;
    int g = c.g * 255;
    int b = c.b * 255;
    int a = c.a * 255;
    return r + (g << 8) + (b << 16) + (a << 24);
}
LandColor land_color_lerp(LandColor a, LandColor b, float t) {
    return land_color_rgba(a.r + t * (b.r - a.r), a.g + t * (b.g - a.g), a.b + t * (b.b - a.b), a.a + t * (b.a - a.a));
}
char const* land_color_bash(char const * x) {
    /* bash("bright red")
     * bash("back white")
     * bash("bold blue back bright green")
     * bash("end")
     * Note: The return value remains property of the color module and is
     * not to be freed.
     */
    return bash_mode(x, "3");
}
#define CAT(X, Y1, Y2) \
    if (land_equals(c, X)) { \
        land_concatenate_with_separator(& m, Y1, ";"); \
        land_concatenate(& m, Y2); \
    }
static char const* bash_mode(char const * x, char const * mode) {
    if (cache == NULL) {
        cache = land_hash_new();
    }
    char * cached = land_hash_get(cache, x);
    if (cached) {
        return cached;
    }
    char const * back = strstr(x, "back");
    if (back) {
        if (back == x) {
            return bash_mode(x + 4, "4");
        }
        char * x2 = land_substring(x, 0, back - x);
        char const * fg = bash_mode(x2, "3");
        char const * bg = bash_mode(back, "4");
        char * r = land_strdup(fg);
        land_concatenate(& r, bg);
        land_free(x2);
        land_hash_insert(cache, x, r);
        return r;
    }
    char * m = land_strdup("");
    LandArray * a = land_split(x, " ");
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(a);
        for (char * c = LandArrayIterator_item(a, &__iter0__); LandArrayIterator_next(a, &__iter0__); c = LandArrayIterator_item(a, &__iter0__)) {
            if (land_equals(c, "bright")) {
                if (land_equals(mode, "3")) {
                    mode = "9";
                }
                else {
                    mode = "10";
                }
            }
            CAT("bold", "", "1");
            CAT("black", mode, "0");
            CAT("red", mode, "1");
            CAT("green", mode, "2");
            CAT("yellow", mode, "3");
            CAT("blue", mode, "4");
            CAT("magenta", mode, "5");
            CAT("cyan", mode, "6");
            CAT("white", mode, "7");
        }
    }
    land_array_destroy_with_strings(a);
    land_prepend(& m, "\x1b[");
    land_concatenate(& m, "m");
    land_hash_insert(cache, x, m);
    return m;
}
LandColor land_premul_alpha(LandColor c, float a) {
    return land_color_premul(c.r, c.g, c.b, a);
}
#undef CAT
LandDataFile * land_datafile;
static int read32(FILE * f) {
    unsigned int u = fgetc(f);
    u += fgetc(f) << 8;
    u += fgetc(f) << 16;
    u += fgetc(f) << 24;
    return u;
}
LandDataFile* land_read_datafile(FILE * file) {
    LandDataFile * self;
    land_alloc(self);
    self->file = file;
    int count = read32(self->file);
    int i;
    char name [1024];
    land_log_message("Data listing:\n");
    for (i = 0; i < count; i++) {
        int s = 0;
        while (s < 1024) {
            int c = fgetc(self->file);
            name [s++] = c;
            if (c == '\0') {
                break;
            }
        }
        LandDataEntry * entry;
        land_alloc(entry);
        entry->name = land_strdup(name);
        entry->offset = read32(self->file);
        entry->size = read32(self->file);
        land_array_add_data(& self->entries, entry);
        land_log_message(" %8d %8d %s\n", entry->offset, entry->size, entry->name);
    }
    return self;
}
LandDataFile* land_open_datafile(char const * filename) {
    FILE * file = fopen(filename, "rb");
    if (! file) {
        return NULL;
    }
    return land_read_datafile(file);
}
LandDataFile* land_open_appended_datafile(char const * filename, char const * marker) {
    FILE * file = fopen(filename, "rb");
    if (! file) {
        return NULL;
    }
    fseek(file, - 4, SEEK_END);
    int size = read32(file);
    land_log_message("Embedded data size: %d\n", size);
    fseek(file, - size - strlen(marker), SEEK_END);
    int i;
    for (i = 0; i < (int) strlen(marker); i++) {
        if (fgetc(file) != marker [i]) {
            fclose(file);
            return NULL;
        }
    }
    int offset = ftell(file);
    LandDataFile * data = land_read_datafile(file);
    for (i = 0; i < data->entries->count; i++) {
        LandDataEntry * entry = land_array_get_nth(data->entries, i);
        entry->offset += offset;
    }
    return data;
}
void* land_datafile_read_entry(LandDataFile * self, char const * filename, int * size) {
    int i;
    for (i = 0; i < self->entries->count; i++) {
        LandDataEntry * entry = land_array_get_nth(self->entries, i);
        if (! strcmp(entry->name, filename)) {
            fseek(self->file, entry->offset, 0);
            unsigned char * buffer = land_calloc(entry->size);
            int r = fread(buffer, entry->size, 1, self->file);
            entry->size = r;
            if (size) {
                * size = entry->size;
            }
            return buffer;
        }
    }
    return NULL;
}
static int star_match(char const * pattern, char const * name) {
    int i = 0;
    int j = 0;
    while (1) {
        char c = pattern [i];
        char d = name [j];
        if (c == '*') {
            int k;
            if (pattern [i + 1] == '\0') {
                return 1;
            }
            for (k = j; k < (int) strlen(name); k++) {
                int r = star_match(pattern + i + 1, name + k);
                if (r) {
                    return r;
                }
            }
            return 0;
        }
        else if (c == '?') {
            ;
        }
        else if (c != d) {
            return 0;
        }
        if (c == '\0') {
            return 1;
        }
        i++;
        j++;
    }
}
int land_datafile_for_each_entry(LandDataFile * self, char const * pattern, int(* callback)(const char * filename, int attrib, void * param), void * param) {
    int i;
    int n = 0;
    for (i = 0; i < self->entries->count; i++) {
        LandDataEntry * entry = land_array_get_nth(self->entries, i);
        if (star_match(pattern, entry->name)) {
            if (callback(entry->name, 0, param)) {
                break;
            }
            n++;
        }
    }
    return n;
}
void land_set_datafile(LandDataFile * datafile) {
    land_datafile = datafile;
}
LandDataFile* land_get_datafile(void) {
    return land_datafile;
}
#define LOG_COLOR_STATS 0
static void(* _cb)(char const * path, LandImage * image);
static int bitmap_count, bitmap_memory;
static LandThread * _loader_thread;
static LandLock * _loader_event;
static LandLock * _loader_mutex;
static LandArray * _loader_queue;
void land_image_set_callback(void(* cb)(char const * path, LandImage * image)) {
    _cb = cb;
}
static LandImage* _load(char const * filename, bool mem) {
    LandImage * self;
    char * path = land_path_with_prefix(filename);
    land_log_message("land_image_load %s..\n", path);
    self = platform_image_load(path, mem);
    land_free(path);
    bitmap_count++;
    _load2(self);
    return self;
}
static void _load2(LandImage * self) {
    if (self->flags & LAND_LOADED) {
        int w = land_image_width(self);
        int h = land_image_height(self);
        land_log_message("loading %s success (%d x %d)\n", self->filename, w, h);
        if (self->flags & LAND_IMAGE_CENTER) {
            land_image_center(self);
        }
        if (self->flags & LAND_AUTOCROP) {
            land_image_auto_crop(self);
        }
        land_log_message(" crop l=%.0f, t=%.0f, r=%.0f, b=%.0f\n", self->l, self->t, self->r, self->b);
        #ifdef LOG_COLOR_STATS
        float red = 0, green = 0, blue = 0, alpha = 0;
        int n = 1;
        n = land_image_color_stats(self, & red, & green, & blue, & alpha);
        land_log_message(" (%.2f|%.2f|%.2f|%.2f).\n", red / n, green / n, blue / n, alpha / n);
        #endif
        bitmap_memory += w * h * 4;
        land_log_message(" %d bitmaps (%.1fMB).\n", bitmap_count, bitmap_memory / 1024.0 / 1024.0);
    }
    else {
        land_log_message_nostamp("failure\n");
    }
    if (_cb) {
        _cb(self->filename, self);
    }
}
LandImage* land_image_load(char const * filename) {
    return _load(filename, 0);
}
bool land_image_was_loaded(LandImage * self) {
    return (self->flags & LAND_LOADED) != 0;
}
LandImage* land_image_load_memory(char const * filename) {
    return _load(filename, 1);
}
LandImage* land_image_new_deferred(char const * filename) {
    LandImage * self = land_image_new(0, 0);
    self->filename = land_path_with_prefix(filename);
    return self;
}
bool land_image_load_on_demand(LandImage * self) {
    /* Load an image that was previously declared "on demand".
     */
    if (self->flags & LAND_LOADING) {
        return 1;
    }
    if (self->flags & LAND_LOADING_COMPLETE) {
        land_image_load_async(self);
        return 1;
    }
    if (self->flags & LAND_LOADED) {
        return 0;
    }
    if (self->flags & LAND_FAILED) {
        return 0;
    }
    land_log_message("land_image_load_on_demand %s..\n", self->filename);
    platform_image_load_on_demand(self);
    bitmap_count += 1;
    _load2(self);
    return 1;
}
static void _thread_func(void * data) {
    while (! _land_quit) {
        while (! _land_quit) {
            land_thread_lock(_loader_mutex);
            LandArray * copy = land_array_copy(_loader_queue);
            land_array_clear(_loader_queue);
            land_thread_unlock(_loader_mutex);
            bool empty = land_array_is_empty(copy);
            land_log_message("image queue has %d images\n", land_array_count(copy));
            {
                LandArrayIterator __iter0__ = LandArrayIterator_first(copy);
                for (LandImage * image = LandArrayIterator_item(copy, &__iter0__); LandArrayIterator_next(copy, &__iter0__); image = LandArrayIterator_item(copy, &__iter0__)) {
                    land_log_message("queing %s\n", image->filename);
                    platform_image_preload_memory(image);
                    bitmap_count += 1;
                    _load2(image);
                    image->flags |= LAND_LOADING_COMPLETE;
                    image->flags &= ~ LAND_LOADING;
                    if (_land_quit) {
                        break;
                    }
                }
            }
            land_array_destroy(copy);
            if (empty) {
                break;
            }
        }
        land_thread_wait_lock(_loader_event);
    }
}
bool land_image_load_async(LandImage * self) {
    if (self->flags & LAND_LOADING) {
        return 1;
    }
    if (self->flags & LAND_LOADING_COMPLETE) {
        self->flags &= ~ LAND_LOADING_COMPLETE;
        platform_image_transfer_from_memory(self);
        return 1;
    }
    if (self->flags & LAND_LOADED) {
        return 0;
    }
    if (self->flags & LAND_FAILED) {
        return 0;
    }
    if (! _loader_thread) {
        _loader_event = land_thread_new_waitable_lock();
        _loader_mutex = land_thread_new_lock();
        _loader_queue = land_array_new();
        _loader_thread = land_thread_new(_thread_func, NULL);
    }
    land_log_message("Asynchronously loading %s\n", self->filename);
    land_thread_lock(_loader_mutex);
    self->flags |= LAND_LOADING;
    land_array_add(_loader_queue, self);
    land_thread_unlock(_loader_mutex);
    land_thread_trigger_lock(_loader_event);
    return 1;
}
bool land_image_exists(LandImage * self) {
    return platform_image_exists(self);
}
LandImage* land_image_memory_new(int w, int h) {
    /* Creates a new image. If w or h are 0, the image will have no contents at
     * all (this can be useful if the contents are to be added later).
     * The image will always be a simple memory rectangle of pixels, with no
     * driver specific optimizations.
     */
    assert(0);
    return NULL;
}
LandImage* land_image_new_flags(int w, int h, int flags) {
    /* Creates a new image. If w and h are 0, the image will have no contents at
     * all (this can be useful if the contents are to be added later).
     */
    LandImage * self = land_display_new_image();
    self->flags = flags;
    self->width = w;
    self->height = h;
    if (w || h) {
        platform_image_empty(self);
    }
    bitmap_count++;
    bitmap_memory += w * h * 4;
    return self;
}
LandImage* land_image_new(int w, int h) {
    return land_image_new_flags(w, h, 0);
}
LandImage* land_image_create(int w, int h) {
    /* Like land_image_new, but clears the image to all 0 initially.
     */
    LandImage * self = land_display_new_image();
    self->width = w;
    self->height = h;
    platform_image_empty(self);
    bitmap_count++;
    bitmap_memory += w * h * 4;
    return self;
}
void land_image_del(LandImage * self) {
    if (! self) {
        return ;
    }
    if (! (self->flags & LAND_SUBIMAGE)) {
        land_image_destroy_pixelmasks(self);
        if (self->name) {
            land_free(self->name);
        }
        if (self->filename && self->filename != self->name) {
            land_free(self->filename);
        }
        bitmap_count--;
        bitmap_memory -= self->width * self->height * 4;
    }
    land_display_del_image(self);
}
void land_image_destroy(LandImage * self) {
    land_image_del(self);
}
void land_image_crop(LandImage * self, int x, int y, int w, int h) {
    /* Crops an image to the specified rectangle. All image contents outside the
     * rectangle will be lost. You can also use this to make an image larger, in
     * which case the additional borders are filled with transparency. The offset
     * need not lie within the image.
     */
    if (self->width == w && self->height == h && x == 0 && y == 0) {
        return ;
    }
    platform_image_crop(self, x, y, w, h);
}
void land_image_auto_crop(LandImage * self) {
    /* This will optimize an image by cropping away any completely transparent
     * borders it may have.
     */
    int w = land_image_width(self);
    int h = land_image_height(self);
    unsigned char * rgba = land_malloc(w * h * 4);
    land_image_get_rgba_data(self, rgba);
    int mini = w;
    int maxi = - 1;
    int minj = h;
    int maxj = - 1;
    for (int j = 0; j < h; j++) {
        uint32_t * row = (void *)(rgba + j * w * 4);
        for (int i = 0; i < w; i++) {
            if (row [i] & 0xff000000) {
                if (i < mini) {
                    mini = i;
                }
                if (i > maxi) {
                    maxi = i;
                }
                if (j < minj) {
                    minj = j;
                }
                if (j > maxj) {
                    maxj = j;
                }
            }
        }
    }
    land_free(rgba);
    if (maxi == - 1) {
        mini = maxi = minj = maxj = 0;
    }
    self->l = mini;
    self->t = minj;
    self->r = w - 1 - maxi;
    self->b = h - 1 - maxj;
}
void land_image_resize(LandImage * self, int new_w, int new_h, int flags) {
    /* Resizes the image to the given dimensions. If the new aspect ratio
     * is different by default the contents will get distorted.
     * flags:
     * LandImageFit - Never enlarge the original and if the aspect ratio
     * changes center the picture.
     */
    float old_w = self->width - self->l - self->r;
    float old_h = self->height - self->t - self->b;
    if (new_w == 0) {
        new_w = old_w;
    }
    if (new_h == 0) {
        new_h = old_h;
    }
    float xs = 1.0 * new_w / old_w;
    float ys = 1.0 * new_h / old_h;
    float xo = 0;
    float yo = 0;
    if (flags & LandImageFit) {
        if (xs > 1) {
            xs = 1;
        }
        if (ys > 1) {
            ys = 1;
        }
        if (xs < ys) {
            ys = xs;
        }
        else {
            xs = ys;
        }
        xo = new_w * 0.5 - old_w * xs * 0.5;
        yo = new_h * 0.5 - old_h * ys * 0.5;
    }
    LandImage * resized = land_image_new(new_w, new_h);
    land_set_image_display(resized);
    land_image_draw_scaled(self, xo - self->l * xs, yo - self->t * ys, xs, ys);
    land_unset_image_display();
    platform_image_merge(self, resized);
}
LandImage* land_image_new_from(LandImage * copy, int x, int y, int w, int h) {
    /* Create a new image, copying pixel data from a rectangle in an existing
     * image.
     */
    land_log_message("land_image_new_from %s..", copy->name);
    LandImage * self = land_image_new(w, h);
    land_set_image_display(self);
    land_blend(LAND_BLEND_SOLID);
    land_image_draw_partial(copy, copy->x, copy->y, x, y, w, h);
    land_unset_image_display();
    land_log_message_nostamp(" success (%d x %d)\n", w, h);
    #ifdef LOG_COLOR_STATS
    float red, green, blue, alpha;
    int n;
    n = land_image_color_stats(self, & red, & green, & blue, & alpha);
    land_log_message(" (%.2f|%.2f|%.2f|%.2f).\n", red / n, green / n, blue / n, alpha / n);
    #endif
    land_log_message(" %d bitmaps (%.1fMB).\n", bitmap_count, bitmap_memory / 1024.0 / 1024.0);
    return self;
}
int land_image_color_stats(LandImage * self, float * red, float * green, float * blue, float * alpha) {
    /* Returns the number of pixels in the image, and the average red, green, blue
     * and alpha component.
     */
    int n = 0;
    int w = land_image_width(self);
    int h = land_image_height(self);
    * red = 0;
    * green = 0;
    * blue = 0;
    * alpha = 0;
    unsigned char * rgba = land_malloc(w * h * 4);
    land_image_get_rgba_data(self, rgba);
    int p = 0;
    for (int j = 0; j < h; j++) {
        for (int i = 0; i < w; i++) {
            * red += rgba [p++] * 1.0 / 255.0;
            * green += rgba [p++] * 1.0 / 255.0;
            * blue += rgba [p++] * 1.0 / 255.0;
            * alpha += rgba [p++] * 1.0 / 255.0;
            n++;
        }
    }
    land_free(rgba);
    return n;
}
void land_image_color_replace(LandImage * self, int r255, int g255, int b255, int a255, int _r255, int _g255, int _b255, int _a255) {
    /* Replaces a color with another.
     */
    assert(0);
}
void land_image_colorkey(LandImage * self, int r255, int g255, int b255) {
    /* Replaces all pixels in the image matching the given RGB triplet (in 0..255
     * format) with full transparency.
     */
    assert(0);
}
void land_image_colorkey_hack(LandImage * self, int allegro_color) {
    /* Like land_image_colorkey, but even more hackish, you directly specify
     * the color in Allegro's format. The only use for this is if you load
     * paletted pictures and want to colorkey by index.
     */
    assert(0);
}
void land_image_colorize(LandImage * self, LandImage * colormask) {
    /* Colorizes the part of the image specified by the mask with the current
     * color. The mask uses (1, 0, 1) for transparent, and the intensity is
     * otherwise used as intensity of the replacement color.
     */
    assert(0);
}
void land_image_colorize_replace(LandImage * self, int n, int * rgb) {
    /* This takes a list of colors and replaces all colors in the image
     * corresponding to one of them with the current color.
     * The colors use integer 0..255 format, since exact comparison with
     * the usual floating point colors would be difficult otherwise. The
     * array ''rgb'' should have 3 * n integers, consisting of consecutive
     * R, G, B triplets to replace.
     * The first rgb triplet has a special meaning - it determines the image color
     * which is mapped to the current color. All matching colors with a larger
     * rgb sum then are mapped to a color between the first color and pure weight,
     * depending on their rgb sum. All colors with a smaller rgb sum are mapped
     * to a range from total black to the first color.
     */
    int w = land_image_width(self);
    int h = land_image_height(self);
    unsigned char rgba [w * h * 4];
    land_image_get_rgba_data(self, rgba);
    unsigned char * p = rgba;
    float fr, fg, fb, fa;
    land_get_color(& fr, & fg, & fb, & fa);
    int red = fr * 255;
    int green = fg * 255;
    int blue = fb * 255;
    int base_red = rgb [0];
    int base_green = rgb [1];
    int base_blue = rgb [2];
    int base_sum = base_red + base_green + base_blue;
    for (int y = 0; y < h; y++) {
        for (int x = 0; x < w; x++) {
            int r = * (p + 0);
            int g = * (p + 1);
            int b = * (p + 2);
            for (int i = 0; i < n; i++) {
                if (rgb [i * 3 + 0] == r && rgb [i * 3 + 1] == g && rgb [i * 3 + 2] == b) {
                    int sum = r + g + b;
                    int nr, ng, nb;
                    if (sum <= base_sum) {
                        nr = red * sum / base_sum;
                        ng = green * sum / base_sum;
                        nb = blue * sum / base_sum;
                    }
                    else {
                        int isum = 255 * 3 - sum;
                        int ibase_sum = 255 * 3 - base_sum;
                        nr = 255 - (255 - red) * isum / ibase_sum;
                        ng = 255 - (255 - green) * isum / ibase_sum;
                        nb = 255 - (255 - blue) * isum / ibase_sum;
                    }
                    * (p + 0) = nr;
                    * (p + 1) = ng;
                    * (p + 2) = nb;
                }
            }
            p += 4;
        }
    }
    land_image_set_rgba_data(self, rgba);
}
LandImage* land_image_split_mask_from_colors(LandImage * self, int n_rgb, int * rgb) {
    /* Takes the same parameters as land_image_colorize_replace - but instead of
     * recoloring the image itself, creates a separate image of the same size,
     * which is transparent except where mask colors have been found in the
     * given image. Here, it is colored in graylevels with the intensity
     * corresponding to the mask colors. In the original image, all mask colors
     * are replaced by transparency.
     * The use of this function is to always draw the mask over the original
     * image, but tint the white mask to other colors.
     */
    assert(0);
}
static int callback(const char * filename, int attrib, void * param) {
    LandArray * (* filenames) = param;
    land_array_add_data(filenames, land_strdup(filename));
    return 0;
}
static int compar(void const * a, void const * b) {
    char * an = * (char * *) a;
    char * bn = * (char * *) b;
    return strcmp(an, bn);
}
static int filter(char const * name, bool is_dir, void * data) {
    char const * pattern = data;
    if (is_dir) {
        return 2;
    }
    if (land_fnmatch(pattern, name)) {
        return 1;
    }
    return 0;
}
LandArray* land_load_images_cb(char const * pattern, void(* cb)(LandImage * image, void * data), void * data) {
    /* Load all images matching the file name pattern, and create an array
     * referencing them all, in alphabetic filename order. The callback function
     * is called on each image along the way.
     */
    LandBuffer * dirbuf = land_buffer_new();
    int j = 0;
    for (int i = 0; pattern [i]; i++) {
        if (pattern [i] == '/' || pattern [i] == '\\') {
            land_buffer_add(dirbuf, pattern + j, i - j);
            j = i;
        }
        if (pattern [i] == '?' || pattern [i] == '*') {
            break;
        }
    }
    char * dir = land_buffer_finish(dirbuf);
    LandArray * filenames = NULL;
    int count = 0;
    if (land_datafile) {
        count = land_datafile_for_each_entry(land_datafile, pattern, callback, & filenames);
    }
    if (! count) {
        filenames = land_filelist(dir, filter, LAND_RELATIVE_PATH, (void *) pattern);
        if (filenames) {
            count = filenames->count;
        }
        else {
            land_log_message("No files at all match %s.\n", pattern);
        }
    }
    land_free(dir);
    if (! filenames) {
        return NULL;
    }
    qsort(filenames->data, count, sizeof (void *), compar);
    LandArray * array = NULL;
    int i;
    for (i = 0; i < filenames->count; i++) {
        char * filename = land_array_get_nth(filenames, i);
        LandImage * image = land_image_load(filename);
        land_free(filename);
        if (image) {
            if (cb) {
                cb(image, data);
            }
            land_array_add_data(& array, image);
        }
    }
    land_array_destroy(filenames);
    return array;
}
static void defcb(LandImage * image, void * p) {
    int * data = p;
    if (data [0]) {
        land_image_center(image);
    }
    if (data [1]) {
        land_image_auto_crop(image);
    }
}
LandArray* land_load_images(char const * pattern, int center, int auto_crop) {
    /* Load all images matching the file name pattern, and create an array
     * referencing them all.
     */
    int data [2] = {center, auto_crop};
    return land_load_images_cb(pattern, defcb, data);
}
LandImage* land_image_sub(LandImage * parent, float x, float y, float w, float h) {
    LandImage * self = platform_image_sub(parent, x, y, w, h);
    return self;
}
LandArray* land_image_load_sheet(char const * filename, int offset_x, int offset_y, int grid_w, int grid_h, int x_gap, int y_gap, int x_count, int y_count, int auto_crop) {
    LandArray * array = NULL;
    LandImage * sheet = land_image_load(filename);
    if (! sheet) {
        return NULL;
    }
    int x, y, i, j;
    for (j = 0; j < y_count; j++) {
        for (i = 0; i < x_count; i++) {
            x = offset_x + i * (grid_w + x_gap);
            y = offset_y + j * (grid_h + y_gap);
            LandImage * sub = land_image_sub(sheet, x, y, grid_w, grid_h);
            if (auto_crop) {
                land_image_auto_crop(sub);
            }
            land_array_add_data(& array, sub);
        }
    }
    if (_cb) {
        _cb(filename, sheet);
    }
    return array;
}
LandArray* land_image_load_split_sheet(char const * filename, int offset_x, int offset_y, int grid_w, int grid_h, int x_gap, int y_gap, int x_count, int y_count, int auto_crop) {
    LandArray * array = NULL;
    LandImage * sheet = land_image_load_memory(filename);
    if (! sheet) {
        return NULL;
    }
    int x, y, i, j;
    for (j = 0; j < y_count; j++) {
        for (i = 0; i < x_count; i++) {
            x = offset_x + i * (grid_w + x_gap);
            y = offset_y + j * (grid_h + y_gap);
            LandImage * sub = land_image_new_from(sheet, x, y, grid_w, grid_h);
            land_array_add_data(& array, sub);
        }
    }
    land_image_del(sheet);
    return array;
}
void land_image_draw_scaled_rotated_tinted_flipped(LandImage * self, float x, float y, float sx, float sy, float angle, float r, float g, float b, float alpha, int flip) {
    platform_image_draw_scaled_rotated_tinted_flipped(self, x, y, sx, sy, angle, r, g, b, alpha, flip);
}
void land_image_draw_scaled_rotated_tinted(LandImage * self, float x, float y, float sx, float sy, float angle, float r, float g, float b, float alpha) {
    land_image_draw_scaled_rotated_tinted_flipped(self, x, y, sx, sy, angle, r, g, b, alpha, 0);
}
void land_image_draw_scaled_rotated(LandImage * self, float x, float y, float sx, float sy, float angle) {
    land_image_draw_scaled_rotated_tinted(self, x, y, sx, sy, angle, 1, 1, 1, 1);
}
void land_image_draw_scaled(LandImage * self, float x, float y, float sx, float sy) {
    land_image_draw_scaled_rotated_tinted(self, x, y, sx, sy, 0, 1, 1, 1, 1);
}
void land_image_draw_rotated(LandImage * self, float x, float y, float a) {
    land_image_draw_scaled_rotated_tinted(self, x, y, 1, 1, a, 1, 1, 1, 1);
}
void land_image_draw_rotated_flipped(LandImage * self, float x, float y, float a) {
    land_image_draw_scaled_rotated_tinted_flipped(self, x, y, 1, 1, a, 1, 1, 1, 1, 1);
}
void land_image_draw_rotated_tinted(LandImage * self, float x, float y, float a, float r, float g, float b, float alpha) {
    land_image_draw_scaled_rotated_tinted(self, x, y, 1, 1, a, r, g, b, alpha);
}
void land_image_draw_scaled_tinted(LandImage * self, float x, float y, float sx, float sy, float r, float g, float b, float alpha) {
    land_image_draw_scaled_rotated_tinted(self, x, y, sx, sy, 0, r, g, b, alpha);
}
void land_image_draw(LandImage * self, float x, float y) {
    land_image_draw_scaled_rotated_tinted(self, x, y, 1, 1, 0, 1, 1, 1, 1);
}
void land_image_draw_flipped(LandImage * self, float x, float y) {
    land_image_draw_scaled_rotated_tinted_flipped(self, x, y, 1, 1, 0, 1, 1, 1, 1, 1);
}
void land_image_draw_tinted(LandImage * self, float x, float y, float r, float g, float b, float alpha) {
    land_image_draw_scaled_rotated_tinted(self, x, y, 1, 1, 0, r, g, b, alpha);
}
void land_image_grab(LandImage * self, int x, int y) {
    platform_image_grab_into(self, x, y, 0, 0, self->width, self->height);
}
void land_image_grab_into(LandImage * self, float x, float y, float tx, float ty, float tw, float th) {
    platform_image_grab_into(self, x, y, tx, ty, tw, th);
}
void land_image_offset(LandImage * self, int x, int y) {
    self->x = x;
    self->y = y;
}
void land_image_memory_draw(LandImage * self, float x, float y) {
    assert(0);
}
void land_image_center(LandImage * self) {
    self->x = 0.5 * self->width;
    self->y = 0.5 * self->height;
    self->flags |= LAND_IMAGE_WAS_CENTERED | LAND_IMAGE_CENTER;
}
void land_image_init(void) {
    ;
}
void land_image_exit(void) {
    ;
}
void land_image_clip(LandImage * self, float x, float y, float x_, float y_) {
    self->l = x;
    self->t = y;
    self->r = self->width - x_;
    self->b = self->height - y_;
}
void land_image_unclip(LandImage * self) {
    self->l = 0;
    self->t = 0;
    self->r = 0;
    self->b = 0;
}
void land_image_draw_partial(LandImage * self, float x, float y, float sx, float sy, float sw, float sh) {
    float l = self->l;
    float t = self->t;
    float r = self->r;
    float b = self->b;
    land_image_clip(self, sx, sy, sx + sw, sy + sh);
    land_image_draw(self, x - sx, y - sy);
    self->l = l;
    self->t = t;
    self->r = r;
    self->b = b;
}
int land_image_height(LandImage * self) {
    return self->height;
}
int land_image_width(LandImage * self) {
    return self->width;
}
void land_image_get_rgba_data(LandImage * self, unsigned char * rgba) {
    /* Copies rgba data into the specified buffer. It has to be large
     * enough (w * h * 4 bytes) to hold all the data.
     */
    platform_image_get_rgba_data(self, rgba);
}
void land_image_set_rgba_data(LandImage * self, unsigned char const * rgba) {
    /* Copies the rgba data, overwriting the image contents. Since data are copied
     * rgba can be safely deleted after returning from the function.
     */
    platform_image_set_rgba_data(self, rgba);
}
void land_image_save(LandImage * self, char const * filename) {
    platform_image_save(self, filename);
}
int land_image_opengl_texture(LandImage * self) {
    return platform_image_opengl_texture(self);
}
void land_image_flip(LandImage * self) {
    int w = land_image_width(self);
    int h = land_image_height(self);
    unsigned char * rgba = land_malloc(w * h * 4);
    land_image_get_rgba_data(self, rgba);
    for (int j = 0; j < h; j++) {
        uint32_t * row = (void *)(rgba + j * w * 4);
        for (int i = 0; i < w / 2; i++) {
            uint32_t temp = row [i];
            row [i] = row [w - 1 - i];
            row [w - 1 - i] = temp;
        }
    }
    land_image_set_rgba_data(self, rgba);
    land_free(rgba);
}
LandImage* land_image_clone(LandImage * self) {
    int w = land_image_width(self);
    int h = land_image_height(self);
    LandImage * clone = land_image_new(w, h);
    unsigned char * rgba = land_malloc(w * h * 4);
    land_image_get_rgba_data(self, rgba);
    land_image_set_rgba_data(clone, rgba);
    land_free(rgba);
    clone->x = self->x;
    clone->y = self->y;
    return clone;
}
void land_image_fade_to_color(LandImage * self) {
    int w = land_image_width(self);
    int h = land_image_height(self);
    unsigned char * rgba = land_malloc(w * h * 4);
    land_image_get_rgba_data(self, rgba);
    float fr, fg, fb, fa;
    land_get_color(& fr, & fg, & fb, & fa);
    int red = fr * 255;
    int green = fg * 255;
    int blue = fb * 255;
    int alpha = fa * 255;
    for (int j = 0; j < h; j++) {
        unsigned char * row = rgba + j * w * 4;
        for (int i = 0; i < w; i++) {
            int a = row [i * 4 + 3];
            if (! a) {
                continue;
            }
            int ar = row [i * 4 + 0];
            int ag = row [i * 4 + 1];
            int ab = row [i * 4 + 2];
            row [i * 4 + 0] = (red * alpha * a + ar * (255 - alpha) * 255) / (255 * 255);
            row [i * 4 + 1] = (green * alpha * a + ag * (255 - alpha) * 255) / (255 * 255);
            row [i * 4 + 2] = (blue * alpha * a + ab * (255 - alpha) * 255) / (255 * 255);
        }
    }
    land_image_set_rgba_data(self, rgba);
    land_free(rgba);
}
LandImage* land_image_from_xpm(char const * (* xpm)) {
    int w, h, palette_size, pixel_size;
    sscanf(xpm [0], "%d %d %d %d", & w, & h, & palette_size, & pixel_size);
    LandColor palette [65536];
    for (int i = 0; i < palette_size; i += 1) {
        char const * entry = xpm [1 + i];
        int p = 0;
        for (int j = 0; j < pixel_size; j += 1) {
            p *= 256;
            p += (unsigned char) entry [j];
        }
        palette [p] = land_color_name(entry + pixel_size + 3);
    }
    LandImage * self = land_image_new(w, h);
    unsigned char * rgba = land_malloc(w * h * 4);
    for (int y = 0; y < h; y += 1) {
        for (int x = 0; x < w; x += 1) {
            char const * pos = xpm [1 + palette_size + y] + x * pixel_size;
            int p = 0;
            for (int j = 0; j < pixel_size; j += 1) {
                p *= 256;
                p += (unsigned char) pos [j];
            }
            LandColor c = palette [p];
            rgba [y * w * 4 + x * 4 + 0] = c.r * 255;
            rgba [y * w * 4 + x * 4 + 1] = c.g * 255;
            rgba [y * w * 4 + x * 4 + 2] = c.b * 255;
            rgba [y * w * 4 + x * 4 + 3] = c.a * 255;
        }
    }
    land_image_set_rgba_data(self, rgba);
    land_free(rgba);
    return self;
}
void land_image_write_callback(LandImage * self, void(* cb)(int x, int y, unsigned char * rgba, void * user), void * user) {
    /* Run a callback for each pixel of a picture. The pointer passed to
     * the callback must be assigned 4 8-bit values in the range 0..255.
     * It must not be read from.
     */
    int w = self->width;
    int h = self->height;
    unsigned char * rgba = land_malloc(w * h * 4);
    unsigned char * p = rgba;
    for (int y = 0; y < h; y += 1) {
        for (int x = 0; x < w; x += 1) {
            cb(x, y, p, user);
            p += 4;
        }
    }
    land_image_set_rgba_data(self, rgba);
    land_free(rgba);
}
void land_image_read_write_callback(LandImage * self, void(* cb)(int x, int y, unsigned char * rgba, void * user), void * user) {
    /* Run a callback for each pixel of a picture. The rgba pointer passed
     * to the callback will contain the current color as 4 consecutive
     * bytes and may be modified.
     */
    int w = self->width;
    int h = self->height;
    unsigned char * rgba = land_malloc(w * h * 4);
    land_image_get_rgba_data(self, rgba);
    unsigned char * p = rgba;
    for (int y = 0; y < h; y += 1) {
        for (int x = 0; x < w; x += 1) {
            cb(x, y, p, user);
            p += 4;
        }
    }
    land_image_set_rgba_data(self, rgba);
    land_free(rgba);
}
void land_image_read_backup_write_callback(LandImage * self, void(* cb)(int x, int y, int w, int h, unsigned char * rgba_in, unsigned char * rgba_out, void * user), void * user) {
    /* Same as land_image_read_write_callback but the previous image is
     * kept in a backup buffer and a separate pointer for reading is
     * provided. This is useful if you also want to read from neighboring
     * pixels. As a convenience the width and height of the buffers is
     * also passed to the callback to allow handling border conditions.
     */
    int w = self->width;
    int h = self->height;
    unsigned char * rgba_in = land_malloc(w * h * 4);
    land_image_get_rgba_data(self, rgba_in);
    unsigned char * p_in = rgba_in;
    unsigned char * rgba_out = land_malloc(w * h * 4);
    unsigned char * p_out = rgba_out;
    for (int y = 0; y < h; y += 1) {
        for (int x = 0; x < w; x += 1) {
            cb(x, y, w, h, p_in, p_out, user);
            p_in += 4;
            p_out += 4;
        }
    }
    land_image_set_rgba_data(self, rgba_out);
    land_free(rgba_in);
    land_free(rgba_out);
}
#undef LOG_COLOR_STATS
struct LandTrianglesPlatform {
    ALLEGRO_VERTEX_DECL * decl;
    ALLEGRO_VERTEX_BUFFER * vb;
    LandTrianglesShader * shader;
};
struct LandTrianglesShader {
    int light_tag;
    ALLEGRO_SHADER * a5;
};
static LandVector _light_direction;
static float _light;
static int _light_tag;
static LandHash * _shader_cache;
void platform_update_vertex_with_normals(LandTriangles * t, int i, float x, float y, float z, float tu, float tv, float r, float g, float b, float a) {
    LandVertexWithNormal * v = land_triangles_get_vertex(t, i);
    v->x = x;
    v->y = y;
    v->z = z;
    v->u = tu;
    v->v = tv;
    v->r = r;
    v->g = g;
    v->b = b;
    v->a = a;
}
void platform_update_vertex_with_normals_no_texture(LandTriangles * t, int i, float x, float y, float z, float r, float g, float b, float a) {
    LandVertexWithNormalNoTexture * v = land_triangles_get_vertex(t, i);
    v->x = x;
    v->y = y;
    v->z = z;
    v->r = r;
    v->g = g;
    v->b = b;
    v->a = a;
}
void platform_update_vertex_allegro(LandTriangles * t, int i, float x, float y, float z, float tu, float tv, float r, float g, float b, float a) {
    LandVertexAllegro * v = land_triangles_get_vertex(t, i);
    v->x = x;
    v->y = y;
    v->z = z;
    v->u = tu;
    v->v = tv;
    v->r = r;
    v->g = g;
    v->b = b;
    v->a = a;
}
void platform_update_vertex(LandTriangles * t, int i, float x, float y, float z, float tu, float tv, float r, float g, float b, float a) {
    if (t->has_normals && t->has_texture) {
        platform_update_vertex_with_normals(t, i, x, y, z, tu, tv, r, g, b, a);
    }
    else if (t->has_normals) {
        platform_update_vertex_with_normals_no_texture(t, i, x, y, z, r, g, b, a);
    }
    else {
        platform_update_vertex_allegro(t, i, x, y, z, tu, tv, r, g, b, a);
    }
}
void platform_set_vertex_normal(LandTriangles * t, float x, float y, float z) {
    if (t->has_normals && t->has_texture) {
        LandVertexWithNormal * v = land_triangles_get_vertex(t, t->n - 1);
        v->nx = x;
        v->ny = y;
        v->nz = z;
    }
    else if (t->has_normals) {
        LandVertexWithNormalNoTexture * v = land_triangles_get_vertex(t, t->n - 1);
        v->nx = x;
        v->ny = y;
        v->nz = z;
    }
}
void platform_set_vertex_index(LandTriangles * t, float i) {
    if (t->has_normals) {
        LandVertexWithNormal * v = land_triangles_get_vertex(t, t->n - 1);
        v->i = i;
    }
}
void platform_triangles_init(LandTriangles * self) {
    LandTrianglesPlatform * platform;
    land_alloc(platform);
    self->platform = platform;
    if (self->has_normals && self->has_texture) {
        ALLEGRO_VERTEX_ELEMENT elem [] = {{ALLEGRO_PRIM_POSITION, ALLEGRO_PRIM_FLOAT_3, 0}, {ALLEGRO_PRIM_TEX_COORD_PIXEL, ALLEGRO_PRIM_FLOAT_2, 12}, {ALLEGRO_PRIM_USER_ATTR + 0, ALLEGRO_PRIM_FLOAT_3, 20}, {ALLEGRO_PRIM_COLOR_ATTR, 0, 32}, {ALLEGRO_PRIM_USER_ATTR + 1, ALLEGRO_PRIM_FLOAT_1, 48}, {0, 0, 0}};
        self->size = 52;
        platform->decl = al_create_vertex_decl(elem, self->size);
    }
    else if (self->has_normals) {
        ALLEGRO_VERTEX_ELEMENT elem [] = {{ALLEGRO_PRIM_POSITION, ALLEGRO_PRIM_FLOAT_3, 0}, {ALLEGRO_PRIM_USER_ATTR + 0, ALLEGRO_PRIM_FLOAT_3, 12}, {ALLEGRO_PRIM_COLOR_ATTR, 0, 24}, {ALLEGRO_PRIM_USER_ATTR + 1, ALLEGRO_PRIM_FLOAT_1, 40}, {0, 0, 0}};
        self->size = 44;
        platform->decl = al_create_vertex_decl(elem, self->size);
    }
    else {
        ALLEGRO_VERTEX_ELEMENT elem [] = {{ALLEGRO_PRIM_POSITION, ALLEGRO_PRIM_FLOAT_3, 0}, {ALLEGRO_PRIM_TEX_COORD_PIXEL, ALLEGRO_PRIM_FLOAT_2, 12}, {ALLEGRO_PRIM_COLOR_ATTR, 0, 20}, {0, 0, 0}};
        self->size = 36;
        platform->decl = al_create_vertex_decl(elem, self->size);
    }
}
void platform_triangles_deinit(LandTriangles * self) {
    LandTrianglesPlatform * platform = self->platform;
    al_destroy_vertex_decl(platform->decl);
    if (platform->vb) {
        al_destroy_vertex_buffer(platform->vb);
    }
    land_free(platform);
}
void platform_triangles_draw(LandTriangles * t, bool more) {
    LandTrianglesPlatform * platform = t->platform;
    if (t->can_cache && ! platform->vb) {
        platform->vb = al_create_vertex_buffer(platform->decl, t->buf->buffer, t->n, 0);
    }
    LandImagePlatform * pim = (void *) t->image;
    platform_check_blending_and_transform();
    if (! more) {
        al_use_shader(platform->shader->a5);
    }
    if (platform->shader->light_tag != _light_tag) {
        platform->shader->light_tag = _light_tag;
        float f2 [3] = {_light_direction.x, _light_direction.y, _light_direction.z};
        al_set_shader_float_vector("light_direction", 3, f2, 1);
        al_set_shader_float("light", _light);
    }
    if (pim) {
        al_set_shader_sampler("al_tex", pim->a5, 0);
    }
    if (platform->vb) {
        al_draw_vertex_buffer(platform->vb, pim ? pim->a5 : NULL, 0, t->n, ALLEGRO_PRIM_TRIANGLE_LIST);
    }
    else {
        al_draw_prim(t->buf->buffer, platform->decl, pim ? pim->a5 : NULL, 0, t->n, ALLEGRO_PRIM_TRIANGLE_LIST);
    }
    platform_uncheck_blending();
}
void platform_triangles_get_xyz(LandTriangles * t, int i, float * x, float * y, float * z) {
    LandVertexAllegro * v = land_triangles_get_vertex(t, i);
    * x = v->x;
    * y = v->y;
    * z = v->z;
}
void platform_triangles_refresh(LandTriangles * t) {
    LandTrianglesPlatform * platform = t->platform;
    if (platform->vb) {
        al_destroy_vertex_buffer(platform->vb);
        platform->vb = al_create_vertex_buffer(platform->decl, t->buf->buffer, t->n, 0);
    }
}
void platform_triangles_shader(LandTriangles * self, str id, str vertex, str fragment) {
    LandTrianglesPlatform * platform = self->platform;
    if (! _shader_cache) {
        _shader_cache = land_hash_new();
    }
    LandTrianglesShader * cached = land_hash_get(_shader_cache, id);
    if (cached) {
        platform->shader = cached;
    }
    else {
        land_alloc(cached);
        cached->a5 = al_create_shader(ALLEGRO_SHADER_GLSL);
        al_attach_shader_source(cached->a5, ALLEGRO_VERTEX_SHADER, vertex);
        al_attach_shader_source(cached->a5, ALLEGRO_PIXEL_SHADER, fragment);
        bool r = al_build_shader(cached->a5);
        if (r) {
            land_log_message("shader \"%s\" compiled successfully\n", id);
        }
        else {
            land_log_message("%s\n", al_get_shader_log(cached->a5));
        }
        land_hash_insert(_shader_cache, id, cached);
        platform->shader = cached;
    }
}
void platform_triangles_set_light_direction(LandVector light) {
    _light_direction = light;
    _light_tag++;
}
void platform_triangles_set_light(float light) {
    _light = light;
    _light_tag++;
}
    /* Simple helper object for maintaining a vertex/fragment shader combination
     * with GLSL.
     */
static void shader_setup(LandGLSLShader * self, char const * name, char const * vertex_glsl_s, char const * fragment_glsl_s) {
    if (name) {
        self->name = land_strdup(name);
    }
    else {
        name = self->name;
    }
    if (vertex_glsl_s) {
        char * vertex_glsl = land_strdup("");
        #ifdef ALLEGRO_OPENGL_ES
        #endif
        land_append(& vertex_glsl, vertex_glsl_s);
        self->vertex_shader = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(self->vertex_shader, 1, (char const * *) & vertex_glsl, NULL);
        glCompileShader(self->vertex_shader);
        GLint success;
        glGetShaderiv(self->vertex_shader, GL_COMPILE_STATUS, & success);
        land_log_message("%s: Vertex Shader compilation %s.\n", name, success ? "succeeded" : "failed");
        if (1) {
            int size;
            glGetShaderiv(self->vertex_shader, GL_INFO_LOG_LENGTH, & size);
            char error [size];
            glGetShaderInfoLog(self->vertex_shader, size, & size, error);
            if (size) {
                land_log_message("%s: Vertex Shader %s:\n%s\n", name, success ? "Warning" : "Error", error);
            }
        }
        land_free(vertex_glsl);
    }
    if (fragment_glsl_s) {
        char * fragment_glsl = land_strdup("");
        #ifdef ALLEGRO_OPENGL_ES
        #endif
        land_append(& fragment_glsl, fragment_glsl_s);
        self->fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(self->fragment_shader, 1, (char const * *) & fragment_glsl, NULL);
        glCompileShader(self->fragment_shader);
        GLint success;
        glGetShaderiv(self->fragment_shader, GL_COMPILE_STATUS, & success);
        if (1) {
            int size;
            glGetShaderiv(self->fragment_shader, GL_INFO_LOG_LENGTH, & size);
            char error [size];
            glGetShaderInfoLog(self->fragment_shader, size, & size, error);
            if (size) {
                land_log_message("%s: Fragment Shader %s:\n%s\n", name, success ? "Warning" : "Error", error);
            }
        }
        land_free(fragment_glsl);
    }
    if (! self->program_object) {
        self->program_object = glCreateProgram();
    }
    if (self->fragment_shader) {
        glAttachShader(self->program_object, self->fragment_shader);
    }
    if (self->vertex_shader) {
        glAttachShader(self->program_object, self->vertex_shader);
    }
    if (self->fragment_shader && self->vertex_shader) {
        glLinkProgram(self->program_object);
        GLint success;
        glGetProgramiv(self->program_object, GL_LINK_STATUS, & success);
        if (1) {
            int size;
            glGetProgramiv(self->program_object, GL_INFO_LOG_LENGTH, & size);
            char error [size];
            glGetProgramInfoLog(self->program_object, size, & size, error);
            if (size) {
                land_log_message("%s: Shader Link Error:\n%s\n", name, error);
            }
        }
    }
}
static void shader_cleanup(LandGLSLShader * self) {
    land_free(self->name);
    if (self->program_object) {
        glDeleteProgram(self->program_object);
        glDeleteShader(self->vertex_shader);
        glDeleteShader(self->fragment_shader);
    }
}
LandGLSLShader* land_glsl_shader_new(char const * name, char const * vertex_glsl, char const * fragment_glsl) {
    LandGLSLShader * self;
    land_alloc(self);
    shader_setup(self, name, vertex_glsl, fragment_glsl);
    return self;
}
LandGLSLShader* land_glsl_shader_new_empty(char const * name) {
    LandGLSLShader * self;
    land_alloc(self);
    shader_setup(self, name, NULL, NULL);
    return self;
}
void land_glsl_shader_set_shaders(LandGLSLShader * self, char const * vertex_glsl, char const * fragment_glsl) {
    shader_setup(self, NULL, vertex_glsl, fragment_glsl);
}
void land_glsl_shader_destroy(LandGLSLShader * self) {
    shader_cleanup(self);
    land_free(self);
}
void* platform_fopen(char const * filename, char const * mode) {
    #ifdef ANDROID
    land_log_message("open %s", filename);
    if (land_starts_with(filename, "/")) {
        al_set_standard_file_interface();
        ALLEGRO_FILE * f = al_fopen(filename, mode);
        al_android_set_apk_file_interface();
        return f;
    }
    #endif
    ALLEGRO_FILE * f = al_fopen(filename, mode);
    return f;
}
void platform_fclose(void * f) {
    al_fclose(f);
}
int platform_fread(void * f, char * buffer, int bytes) {
    return al_fread(f, buffer, bytes);
}
int platform_fwrite(void * f, char const * buffer, int bytes) {
    return al_fwrite(f, buffer, bytes);
}
void platform_ungetc(void * f, int c) {
    al_fungetc(f, c);
}
int platform_fgetc(void * f) {
    return al_fgetc(f);
}
void platform_fputc(void * f, int c) {
    al_fputc(f, c);
}
bool platform_feof(void * f) {
    return al_feof(f);
}
void platform_fseek(void * f, int n) {
    al_fseek(f, n, ALLEGRO_SEEK_CUR);
}
static void add_files(char const * rel, LandArray * (* array), ALLEGRO_FS_ENTRY * entry, int(* filter)(char const *, bool is_dir, void * data), int flags, void * data) {
    if (! al_open_directory(entry)) {
        land_log_message("Cannot open directory (%d).\n", al_get_fs_entry_mode(entry) & ALLEGRO_FILEMODE_ISDIR);
        return ;
    }
    while (true) {
        ALLEGRO_FS_ENTRY * next = al_read_directory(entry);
        if (! next) {
            break;
        }
        ALLEGRO_PATH * path = al_create_path(al_get_fs_entry_name(next));
        char const * name = al_get_path_filename(path);
        if (! name [0]) {
            name = al_get_path_component(path, - 1);
        }
        if (strcmp(name, ".") && strcmp(name, "..")) {
            bool is_dir = al_get_fs_entry_mode(next) & ALLEGRO_FILEMODE_ISDIR;
            char rel2 [strlen(rel) + strlen("/") + strlen(name) + 1];
            strcpy(rel2, rel);
            strcat(rel2, "/");
            strcat(rel2, name);
            char const * fpath;
            if (flags & LAND_FULL_PATH) {
                #ifdef ANDROID
                fpath = rel2;
                #else
                fpath = al_path_cstr(path, '/');
                #endif
            }
            else if (flags & LAND_RELATIVE_PATH) {
                fpath = rel2;
            }
            else {
                fpath = name;
            }
            int f = 3;
            if (filter) {
                f = filter(fpath, is_dir, data);
            }
            if (f & 1) {
                if (! (* array)) {
                    * array = land_array_new();
                }
                land_array_add(* array, land_strdup(fpath));
            }
            if ((f & 2) && is_dir) {
                add_files(rel2, array, next, filter, flags, data);
            }
        }
        al_destroy_fs_entry(next);
        al_destroy_path(path);
    }
    al_close_directory(entry);
}
LandArray* platform_filelist(char const * dir, int(* filter)(char const *, bool is_dir, void * data), int flags, void * data) {
    land_log_message("platform_filelist %s\n", dir);
    ALLEGRO_FS_ENTRY * entry = al_create_fs_entry(dir);
    LandArray * array = NULL;
    add_files(dir, & array, entry, filter, flags, data);
    al_destroy_fs_entry(entry);
    return array;
}
bool platform_is_dir(char const * path) {
    ALLEGRO_FS_ENTRY * fse = al_create_fs_entry(path);
    bool r = al_get_fs_entry_mode(fse) & ALLEGRO_FILEMODE_ISDIR;
    al_destroy_fs_entry(fse);
    return r;
}
bool platform_file_exists(char const * path) {
    return al_filename_exists(path);
}
static char* _get_app_file(char const * appname, int folder, char const * filename) {
    al_set_org_name("");
    al_set_app_name(appname);
    ALLEGRO_PATH * path = al_get_standard_path(folder);
    const char * str = al_path_cstr(path, ALLEGRO_NATIVE_PATH_SEP);
    if (! al_filename_exists(str)) {
        land_log_message("Creating new path %s.\n", str);
        al_make_directory(str);
    }
    al_set_path_filename(path, filename);
    str = al_path_cstr(path, ALLEGRO_NATIVE_PATH_SEP);
    land_log_message("Using file %s.\n", str);
    char * dup = land_strdup(str);
    al_destroy_path(path);
    return dup;
}
char* platform_get_save_file(char const * appname, char const * name) {
    return _get_app_file(appname, ALLEGRO_USER_SETTINGS_PATH, name);
}
char* platform_get_app_settings_file(char const * appname) {
    return _get_app_file(appname, ALLEGRO_USER_SETTINGS_PATH, "settings.cfg");
}
char* platform_get_app_data_file(char const * appname, char const * filename) {
    return _get_app_file(appname, ALLEGRO_USER_DATA_PATH, filename);
}
char* platform_get_current_directory(void) {
    char * d = al_get_current_directory();
    char * dup = land_strdup(d);
    al_free(d);
    return dup;
}
char* platform_get_data_path(void) {
    ALLEGRO_PATH * path = al_get_standard_path(ALLEGRO_RESOURCES_PATH);
    char * dup = land_strdup(al_path_cstr(path, '/'));
    al_destroy_path(path);
    return dup;
}
bool platform_remove_file(char const * path) {
    return al_remove_filename(path);
}
void land_android_apk_filesystem(bool onoff) {
    #ifdef ANDROID
    if (onoff) {
        al_android_set_apk_fs_interface();
        al_change_directory("/");
    }
    else {
        al_set_standard_fs_interface();
    }
    #endif
}
int64_t platform_file_time(char const * path) {
    ALLEGRO_FS_ENTRY * e = al_create_fs_entry(path);
    time_t t = al_get_fs_entry_mtime(e);
    al_destroy_fs_entry(e);
    return t;
}
static LandGridInterface * land_grid_vtable_tilegrid;
LandGrid* land_tilegrid_new(int cell_w, int cell_h, int x_cells, int y_cells) {
    LandTileGrid * self;
    land_alloc(self);
    land_grid_initialize(& self->super, cell_w, cell_h, x_cells, y_cells);
    self->super.vt = land_grid_vtable_tilegrid;
    self->tiles = land_calloc(x_cells * y_cells * sizeof (* self->tiles));
    return & self->super;
}
void land_tilegrid_del(LandGrid * self) {
    land_free(LAND_TILE_GRID (self)->tiles);
    land_free(self);
}
void land_tilegrid_place(LandGrid * super, int cell_x, int cell_y, LandImage * image) {
    if (cell_x < 0 || cell_y < 0 || cell_x >= super->x_cells || cell_y >= super->y_cells) {
        return ;
    }
    LandTileGrid * self = LAND_TILE_GRID(super);
    self->tiles [cell_y * super->x_cells + cell_x] = image;
}
static void land_tilegrid_draw_cell(LandGrid * self, LandView * view, int cell_x, int cell_y, float pixel_x, float pixel_y) {
    LandImage * image = LAND_TILE_GRID (self)->tiles [cell_y * self->x_cells + cell_x];
    if (image) {
        land_image_draw_scaled(image, pixel_x, pixel_y, view->scale_x, view->scale_y);
    }
}
static void view_x_to_cell_and_pixel_x(LandGrid * self, float view_x, int * cell_x, float * pixel_x) {
    if (view_x < 0) {
        * cell_x = 0;
        * pixel_x = - view_x;
    }
    else {
        * cell_x = (unsigned int) view_x / self->cell_w;
        * pixel_x = * cell_x * self->cell_w - view_x;
    }
}
static void view_y_to_cell_and_pixel_y(LandGrid * self, float view_y, int * cell_y, float * pixel_y) {
    if (view_y < 0) {
        * cell_y = 0;
        * pixel_y = - view_y;
    }
    else {
        * cell_y = (unsigned int) view_y / self->cell_h;
        * pixel_y = * cell_y * self->cell_h - view_y;
    }
}
void land_grid_draw_normal(LandGrid * self, LandView * view) {
    int cell_x, cell_y;
    float pixel_x, pixel_y;
    float view_x = view->scroll_x;
    float view_y = view->scroll_y;
    view_y_to_cell_and_pixel_y(self, view_y, & cell_y, & pixel_y);
    pixel_y *= view->scale_y;
    pixel_y += view->y;
    for (; pixel_y < view->y + view->h; cell_y++, pixel_y += self->cell_h * view->scale_y) {
        if (cell_y >= self->y_cells) {
            break;
        }
        view_x_to_cell_and_pixel_x(self, view_x, & cell_x, & pixel_x);
        pixel_x *= view->scale_x;
        pixel_x += view->x;
        for (; pixel_x < view->x + view->w; cell_x++, pixel_x += self->cell_w * view->scale_x) {
            if (cell_x >= self->x_cells) {
                break;
            }
            self->vt->draw_cell(self, view, cell_x, cell_y, pixel_x, pixel_y);
        }
    }
}
void land_tilemap_init(void) {
    land_log_message("land_tilemap_init\n");
    land_alloc(land_grid_vtable_tilegrid);
    land_grid_vtable_tilegrid->draw = land_grid_draw_normal;
    land_grid_vtable_tilegrid->draw_cell = land_tilegrid_draw_cell;
    land_grid_vtable_tilegrid->del = land_tilegrid_del;
}
void land_tilemap_exit(void) {
    land_log_message("land_tilemap_exit\n");
    land_free(land_grid_vtable_tilegrid);
}
struct LandFontPlatform {
    LandFont super;
    ALLEGRO_FONT * a5;
};
void platform_font_init(void) {
    al_init_font_addon();
    al_init_ttf_addon();
}
void platform_font_exit(void) {
    ;
}
LandFont* platform_font_load(char const * filename, float size) {
    land_log_message("Loading font %s..", filename);
    LandFontPlatform * self;
    land_alloc(self);
    self->a5 = al_load_font(filename, size, 0);
    land_log_message_nostamp("%s.\n", self->a5 ? "success" : "failure");
    LandFont * super = (void *) self;
    if (self->a5) {
        super->size = al_get_font_line_height(self->a5);
        super->flags |= LAND_LOADED;
    }
    super->xscaling = 1.0;
    super->yscaling = 1.0;
    return super;
}
LandFont* platform_font_from_image(LandImage * image, int n_ranges, int * ranges) {
    LandFontPlatform * self;
    LandImagePlatform * i = (void *) image;
    land_alloc(self);
    self->a5 = al_grab_font_from_bitmap(i->a5, n_ranges, ranges);
    LandFont * super = (void *) self;
    if (self->a5) {
        super->size = al_get_font_line_height(self->a5);
    }
    super->xscaling = 1.0;
    super->yscaling = 1.0;
    return super;
}
void platform_font_print(LandFontState * lfs, char const * str, int alignment) {
    LandFont * super = lfs->font;
    LandFontPlatform * self = (void *) super;
    if (! self->a5) {
        return ;
    }
    double xscaling = super->xscaling;
    double yscaling = super->yscaling;
    float x = lfs->x = lfs->x_pos;
    float y = lfs->y = lfs->y_pos;
    lfs->w = al_get_text_width(self->a5, str) * xscaling;
    lfs->h = al_get_font_line_height(self->a5) * yscaling;
    if (lfs->off) {
        return ;
    }
    if (xscaling != 1 || yscaling != 1) {
        land_push_transform();
        land_translate(x, y);
        land_scale(xscaling, yscaling);
        land_translate(- x, - y);
    }
    ALLEGRO_STATE state;
    al_store_state(& state, ALLEGRO_STATE_BLENDER);
    LandDisplay * d = _land_active_display;
    al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA);
    land_a5_display_check_transform();
    ALLEGRO_COLOR c = al_map_rgba_f(d->color_r, d->color_g, d->color_b, d->color_a);
    int a = ALLEGRO_ALIGN_INTEGER;
    if (alignment & LandAlignMiddle) {
        y -= lfs->h / 2 / yscaling;
        lfs->y -= lfs->h / 2 / yscaling;
    }
    else if (alignment & LandAlignBottom) {
        y -= lfs->h / yscaling;
        lfs->y -= lfs->h / yscaling;
    }
    if (alignment & LandAlignAdjust) {
        al_draw_justified_text(self->a5, c, x, x + lfs->adjust_width, y, lfs->adjust_width * 0.5, a | ALLEGRO_ALIGN_CENTRE, str);
    }
    else if (alignment & LandAlignCenter) {
        al_draw_text(self->a5, c, x, y, a | ALLEGRO_ALIGN_CENTRE, str);
        lfs->x -= lfs->w / 2;
    }
    else if (alignment & LandAlignRight) {
        al_draw_text(self->a5, c, x, y, a | ALLEGRO_ALIGN_RIGHT, str);
        lfs->x -= lfs->w;
    }
    else {
        al_draw_text(self->a5, c, x, y, a, str);
    }
    al_restore_state(& state);
    if (xscaling != 1 || yscaling != 1) {
        land_pop_transform();
    }
}
LandFont* platform_font_new(void) {
    return NULL;
}
void platform_font_destroy(LandFont * super) {
    LandFontPlatform * self = (void *) super;
    al_destroy_font(self->a5);
    land_free(self);
}
enum LandWidgetThemeFlags {
    TILE_H=0,
    TILE_V=0,
    STRETCH_H=1,
    STRETCH_V=2,
    CENTER_H=4,
    CENTER_V=8,
    ALIGN_H=16,
    ALIGN_V=32
};
static LandWidgetTheme * default_theme;
LandWidgetTheme* land_widget_theme_default(void) {
    return default_theme;
}
void land_widget_theme_set_default(LandWidgetTheme * self) {
    default_theme = self;
}
static inline int centered_offset(int size1, int size2) {
    int center1, center2, o;
    if (! size1 || ! size2) {
        return 0;
    }
    center1 = size1 / 2;
    center2 = size2 / 2;
    o = (center1 - center2) % size2;
    if (o > 0) {
        o -= size2;
    }
    return o;
}
static inline void _masked_non_stretched_blit(LandImage * s, int sx, int sy, int w, int h, int dx, int dy, int _, int __) {
    land_image_clip(s, sx, sy, sx + w, sy + h);
    land_image_draw(s, dx - sx, dy - sy);
}
static inline void _masked_stretched_blit(LandImage * s, int sx, int sy, int w, int h, int dx, int dy, int dw, int dh) {
    land_image_clip(s, sx, sy, sx + w, sy + h);
    land_image_draw_scaled(s, dx - sx, dy - sy, (float) dw / w, (float) dh / h);
}
enum COLUMN_TYPE {
    COLUMN_CENTER=1,
    COLUMN_STRETCH,
    COLUMN_LEFT,
    COLUMN_MIDDLE,
    COLUMN_RIGHT
};
static inline void blit_column(LandWidgetThemeElement * pat, int bx, int bw, int x, int y, int w, int h, int skip_middle) {
    int oy;
    int j;
    int bh = land_image_height(pat->bmp);
    int bm = bh - pat->bt - pat->bb;
    void(* bfunc)(LandImage *, int, int, int, int, int, int, int, int);
    bfunc = _masked_non_stretched_blit;
    if (bm < 1) {
        return ;
    }
    if (pat->flags & ALIGN_V) {
        oy = (y / bm) * bm - y;
    }
    else {
        oy = centered_offset(h, bm);
    }
    if (w != bw) {
        bfunc = _masked_stretched_blit;
    }
    if (pat->flags & CENTER_V) {
        bfunc(pat->bmp, bx, 0, bw, land_image_height(pat->bmp), x, y + h / 2 - land_image_height(pat->bmp) / 2, w, land_image_height(pat->bmp));
    }
    else if (pat->flags & STRETCH_V) {
        _masked_stretched_blit(pat->bmp, bx, 0, bw, land_image_height(pat->bmp), x, y, w, h);
    }
    else {
        int bt = pat->bt;
        int bb = pat->bb;
        if (bt + bb > h) {
            bt = h / 2;
            bb = h - bt;
        }
        if (bt && y + bt >= _land_active_display->clip_y1) {
            land_clip_push();
            land_clip_intersect(0, y, land_display_width(), _scramble_min(y + h, y + bt));
            bfunc(pat->bmp, bx, 0, bw, bt, x, y, w, bt);
            land_clip_pop();
        }
        if (h - pat->bt - pat->bb > 0 && ! skip_middle) {
            land_clip_push();
            land_clip_intersect(0, _scramble_min(y + h, y + pat->bt), land_display_width(), _scramble_max(y, y + h - pat->bb));
            int start = _scramble_max(0, (_land_active_display->clip_y1 - (y + oy)) / bm);
            start = y + oy + start * bm;
            int end = _scramble_min(_land_active_display->clip_y2, y + h);
            for (j = start; j < end; j += bm) {
                bfunc(pat->bmp, bx, pat->bt, bw, bm, x, j, w, bm);
            }
            land_clip_pop();
        }
        if (bb && y + h - bb < _land_active_display->clip_y2) {
            land_clip_push();
            land_clip_intersect(0, _scramble_max(y, y + h - bb), land_display_width(), y + h);
            bfunc(pat->bmp, bx, land_image_height(pat->bmp) - bb, bw, bb, x, y + h - bb, w, bb);
            land_clip_pop();
        }
    }
}
static void draw_bitmap(LandWidgetThemeElement * pat, int x, int y, int w, int h, int skip_middle) {
    int i;
    int bw = land_image_width(pat->bmp);
    int bm = bw - pat->bl - pat->br;
    if (w < 1 || h < 1 || bm < 1) {
        return ;
    }
    land_clip_push();
    land_clip_intersect(x, y, x + w, y + h);
    if (pat->flags & CENTER_H) {
        blit_column(pat, 0, bw, x + w / 2 - bw / 2, y, bw, h, 0);
    }
    else if (pat->flags & STRETCH_H) {
        blit_column(pat, 0, bw, x, y, w, h, 0);
    }
    else {
        int ox;
        if (pat->flags & ALIGN_H) {
            ox = (x / bm) * bm - x;
        }
        else {
            ox = centered_offset(w, bm);
        }
        int bl = pat->bl;
        int br = pat->br;
        if (bl + br > w) {
            bl = w / 2;
            br = w - bl;
        }
        if (bl && x + bl >= _land_active_display->clip_x1) {
            land_clip_push();
            land_clip_intersect(x, 0, _scramble_min(x + w, x + bl), land_display_height());
            blit_column(pat, 0, bl, x, y, bl, h, 0);
            land_clip_pop();
        }
        if (w - pat->bl - pat->br > 0) {
            land_clip_push();
            land_clip_intersect(_scramble_min(x + w, x + pat->bl), 0, _scramble_max(x, x + w - pat->br), land_display_height());
            int start = _scramble_max(0, (_land_active_display->clip_x1 - (x + ox)) / bm);
            start = x + ox + start * bm;
            int end = _scramble_min(_land_active_display->clip_x2, x + w - pat->br);
            for (i = start; i < end; i += bm) {
                blit_column(pat, pat->bl, bm, i, y, bm, h, skip_middle);
            }
            land_clip_pop();
        }
        if (br && x + w - br < _land_active_display->clip_x2) {
            land_clip_push();
            land_clip_intersect(_scramble_max(x, x + w - br), 0, x + w, land_display_height());
            blit_column(pat, bw - br, br, x + w - br, y, br, h, 0);
            land_clip_pop();
        }
    }
    land_clip_pop();
}
static void read_int_arg(int argc, LandArray * argv, int * a, int * val) {
    (* a)++;
    if (* a < argc) {
        LandBuffer * buf = land_array_get_nth(argv, * a);
        char * arg = land_buffer_finish(buf);
        * val = strtoul(arg, NULL, 0);
        land_free(arg);
    }
}
LandWidgetThemeElement* land_widget_theme_element_new(struct LandWidgetTheme * theme, char const * name, char const * argline) {
    LandWidgetThemeElement * self;
    land_alloc(self);
    self->name = land_strdup(name);
    self->a = 1;
    self->minw = 4;
    self->minh = 4;
    self->font = land_font_current();
    self->theme = theme;
    LandBuffer * argbuf = land_buffer_new();
    land_buffer_cat(argbuf, argline);
    land_buffer_strip(argbuf, " ");
    LandArray * argv = land_buffer_split(argbuf, " ");
    land_buffer_del(argbuf);
    int argc = land_array_count(argv);
    LandImage * img = NULL;
    if (argc) {
        char iname [2048];
        LandBuffer * buf = land_array_get_nth(argv, 0);
        char * arg = land_buffer_finish(buf);
        snprintf(iname, sizeof iname, "%s%s%s", theme->prefix, arg, theme->suffix);
        land_free(arg);
        img = land_image_load(iname);
        if (img) {
            for (int a = 1; a < argc; a++) {
                buf = land_array_get_nth(argv, a);
                arg = land_buffer_finish(buf);
                if (! strcmp(arg, "cut")) {
                    int cx = 0, cy = 0, cw = 0, ch = 0;
                    read_int_arg(argc, argv, & a, & cx);
                    read_int_arg(argc, argv, & a, & cy);
                    read_int_arg(argc, argv, & a, & cw);
                    read_int_arg(argc, argv, & a, & ch);
                    if (cw <= 0) {
                        cw += land_image_width(img);
                    }
                    if (ch <= 0) {
                        ch += land_image_height(img);
                    }
                    self->bmp = land_image_new_from(img, cx, cy, cw, ch);
                }
                else if (! strcmp(arg, "halign")) {
                    self->flags |= ALIGN_H;
                }
                else if ((! strcmp(arg, "valign"))) {
                    self->flags |= ALIGN_V;
                }
                else if ((! strcmp(arg, "min"))) {
                    read_int_arg(argc, argv, & a, & self->minw);
                    read_int_arg(argc, argv, & a, & self->minh);
                }
                else if ((! strcmp(arg, "border"))) {
                    read_int_arg(argc, argv, & a, & self->bl);
                    read_int_arg(argc, argv, & a, & self->bt);
                    read_int_arg(argc, argv, & a, & self->br);
                    read_int_arg(argc, argv, & a, & self->bb);
                    self->il = self->bl;
                    self->it = self->bt;
                    self->ir = self->br;
                    self->ib = self->bb;
                }
                else if ((! strcmp(arg, "inner"))) {
                    read_int_arg(argc, argv, & a, & self->il);
                    read_int_arg(argc, argv, & a, & self->it);
                    read_int_arg(argc, argv, & a, & self->ir);
                    read_int_arg(argc, argv, & a, & self->ib);
                }
                else if (! strcmp(arg, "gap")) {
                    read_int_arg(argc, argv, & a, & self->hgap);
                    read_int_arg(argc, argv, & a, & self->vgap);
                }
                else if (! strcmp(arg, "color")) {
                    int c = 0;
                    read_int_arg(argc, argv, & a, & c);
                    self->a = (c & 255) / 255.0;
                    c >>= 8;
                    self->b = (c & 255) / 255.0;
                    c >>= 8;
                    self->g = (c & 255) / 255.0;
                    c >>= 8;
                    self->r = (c & 255) / 255.0;
                    c >>= 8;
                }
                else if ((! strcmp(arg, "transparent"))) {
                    self->transparent = 1;
                }
                land_free(arg);
            }
            if (! self->bmp) {
                self->bmp = land_image_new_from(img, 0, 0, land_image_width(img), land_image_height(img));
            }
            land_log_message("element %s: %d x %d, %d/%d/%d/%d %.1f/%.1f/%.1f/%.1f\n", name, land_image_width(self->bmp), land_image_height(self->bmp), self->bl, self->bt, self->br, self->bb, self->r, self->g, self->b, self->a);
        }
        else {
            land_log_message("element: Error: %s not found!\n", name);
        }
    }
    land_array_destroy(argv);
    if (img) {
        land_image_destroy(img);
    }
    return self;
}
LandWidgetTheme* land_widget_theme_new(char const * filename) {
    /* Load a new theme and make it the default theme.
     */
    LandWidgetTheme * self;
    land_alloc(self);
    LandIniFile * config = land_ini_read(filename);
    LandBuffer * prefix = land_buffer_new();
    land_buffer_cat(prefix, filename);
    int slash = land_buffer_rfind(prefix, '/');
    if (slash >= 0) {
        land_buffer_set_length(prefix, slash + 1);
    }
    else {
        land_buffer_set_length(prefix, 0);
    }
    land_buffer_cat(prefix, land_ini_get_string(config, "agup.cfg", "prefix", ""));
    self->name = land_strdup(land_ini_get_string(config, "agup.cfg", "name", ""));
    self->prefix = land_buffer_finish(prefix);
    self->suffix = land_strdup(land_ini_get_string(config, "agup.cfg", "suffix", ""));
    int n = land_ini_get_number_of_entries(config, "agup.cfg/elements");
    for (int i = 0; i < n; i++) {
        char const * v = land_ini_get_nth_entry(config, "agup.cfg/elements", i);
        char const * k = land_ini_get_string(config, "agup.cfg/elements", v, "");
        LandWidgetThemeElement * elem = land_widget_theme_element_new(self, v, k);
        land_add_list_data(& self->elements, elem);
    }
    land_ini_destroy(config);
    land_widget_theme_set_default(self);
    return self;
}
void land_widget_theme_destroy(LandWidgetTheme * self) {
    LandListItem * item;
    if (self->elements) {
        for (item = self->elements->first; item; item = item->next) {
            LandWidgetThemeElement * elem = item->data;
            land_free(elem->name);
            land_image_destroy(elem->bmp);
            land_free(elem);
        }
        land_list_destroy(self->elements);
    }
    land_free(self->name);
    land_free(self->prefix);
    land_free(self->suffix);
    land_free(self);
}
static LandWidgetThemeElement* find_element(LandList * list, char const * name) {
    if (! list) {
        return NULL;
    }
    LandListItem * item = list->first;
    while (item) {
        LandWidgetThemeElement * elem = item->data;
        if (! strcmp(elem->name, name)) {
            return elem;
        }
        item = item->next;
    }
    return NULL;
}
LandWidgetThemeElement* land_widget_theme_find_element(LandWidgetTheme * theme, LandWidget * widget) {
    if (! theme) {
        return NULL;
    }
    LandWidgetThemeElement * element;
    element = find_element(theme->elements, widget->vt->name);
    if (! element) {
        element = find_element(theme->elements, "base");
    }
    if (! element) {
        land_alloc(element);
        element->name = land_strdup("");
        element->theme = theme;
    }
    if (! element->selected) {
        char name [1024];
        strncpy(name, widget->vt->name, sizeof name);
        strncat(name, ".selected", sizeof name - strlen(name) - 1);
        element->selected = find_element(theme->elements, name);
        if (! element->selected) {
            strncpy(name, element->name, sizeof name);
            strncat(name, ".selected", sizeof name - strlen(name) - 1);
            element->selected = find_element(theme->elements, name);
        }
        if (! element->selected) {
            element->selected = element;
        }
    }
    if (! element->disabled) {
        char name [1024];
        strncpy(name, widget->vt->name, sizeof name);
        strncat(name, ".disabled", sizeof name - strlen(name) - 1);
        element->disabled = find_element(theme->elements, name);
        if (! element->disabled) {
            element->disabled = element;
        }
    }
    return element;
}
LandWidgetThemeElement* land_widget_theme_element(LandWidget * self) {
    if (self->selected) {
        return self->element->selected;
    }
    if (self->disabled) {
        return self->element->disabled;
    }
    return self->element;
}
void land_widget_theme_draw(LandWidget * self) {
    LandWidgetThemeElement * element = land_widget_theme_element(self);
    if (! element) {
        return ;
    }
    if (self->no_decoration) {
        return ;
    }
    if (element->transparent) {
        return ;
    }
    draw_bitmap(element, self->box.x, self->box.y, self->box.w, self->box.h, self->only_border);
}
void land_widget_theme_color(LandWidget * self) {
    LandWidgetThemeElement * element = land_widget_theme_element(self);
    if (! element) {
        return ;
    }
    land_color(element->r, element->g, element->b, element->a);
}
void land_widget_theme_font(LandWidget * self) {
    LandWidgetThemeElement * element = land_widget_theme_element(self);
    if (! element) {
        return ;
    }
    land_font_set(element->font);
}
void land_widget_theme_set_minimum_size_for_contents(LandWidget * self, int w, int h) {
    LandWidgetThemeElement * element = land_widget_theme_element(self);
    if (! element) {
        return ;
    }
    self->inner_w = w;
    self->inner_h = h;
    w += element->il + element->ir;
    h += element->it + element->ib;
    if (w < element->minw) {
        w = element->minw;
    }
    if (h < element->minh) {
        h = element->minh;
    }
    land_widget_layout_set_minimum_size(self, w, h);
}
void land_widget_theme_set_minimum_size_for_text(LandWidget * self, char const * text) {
    LandWidgetThemeElement * element = land_widget_theme_element(self);
    if (! element) {
        return ;
    }
    land_font_set(element->font);
    int w = land_text_get_width(text);
    int h = land_text_height();
    land_widget_theme_set_minimum_size_for_contents(self, w, h);
}
void land_widget_theme_set_minimum_size_for_image(LandWidget * self, LandImage * image) {
    LandWidgetThemeElement * element = land_widget_theme_element(self);
    if (! element) {
        return ;
    }
    int w = land_image_width(image);
    int h = land_image_height(image);
    land_widget_theme_set_minimum_size_for_contents(self, w, h);
}
void land_widget_theme_initialize(LandWidget * self) {
    /* Initialize theming of an item. Must only called once at item creation,
     * as it also calculates the minimum size.
     */
    if (! self->element) {
        return ;
    }
    self->element = land_widget_theme_find_element(self->element->theme, self);
    int w = self->box.min_width - self->element->il - self->element->ir;
    int h = self->box.min_height - self->element->it - self->element->ib;
    land_widget_theme_set_minimum_size_for_contents(self, w, h);
}
void land_widget_theme_update(LandWidget * self) {
    /* Adjust the widget's theme to its class (widgets all start off as "base"
     * otherwise).
     */
    if (! self->element) {
        return ;
    }
    self->element = land_widget_theme_find_element(self->element->theme, self);
    land_widget_theme_set_minimum_size_for_contents(self, self->inner_w, self->inner_h);
}
static void _theme_recurse(LandWidget * self, LandWidgetTheme * theme) {
    if (! self->element) {
        return ;
    }
    self->element = land_widget_theme_find_element(theme, self);
    land_widget_theme_set_minimum_size_for_contents(self, self->inner_w, self->inner_h);
    if (land_widget_is(self, LAND_WIDGET_ID_CONTAINER)) {
        LandWidgetContainer * c = (void *) self;
        LandListItem * i = c->children ? c->children->first : NULL;
        while (i) {
            LandWidget * w = i->data;
            _theme_recurse(w, theme);
            i = i->next;
        }
    }
}
static void _layout_recurse(LandWidget * self, LandWidgetTheme * theme) {
    if (land_widget_is(self, LAND_WIDGET_ID_CONTAINER)) {
        LandWidgetContainer * c = (void *) self;
        LandListItem * i = c->children ? c->children->first : NULL;
        while (i) {
            LandWidget * w = i->data;
            _layout_recurse(w, theme);
            i = i->next;
        }
        if (self->parent && (self->parent->box.flags & GUL_NO_LAYOUT)) {
            land_widget_layout(self);
        }
    }
}
void land_widget_theme_apply(LandWidget * self, LandWidgetTheme * theme) {
    /* Applies the given theme to the widget and all its children.
     */
    _theme_recurse(self, theme);
    _layout_recurse(self, theme);
    land_widget_layout(self);
}
void land_widget_theme_change_font(LandWidgetTheme * theme) {
    {
        LandListIterator __iter0__ = LandListIterator_first(theme->elements);
        for (LandWidgetThemeElement * element = LandListIterator_item(theme->elements, &__iter0__); LandListIterator_next(theme->elements, &__iter0__); element = LandListIterator_item(theme->elements, &__iter0__)) {
            element->font = land_font_current();
            if (element->selected) {
                element->selected->font = land_font_current();
            }
            if (element->disabled) {
                element->disabled->font = land_font_current();
            }
        }
    }
}
LandOctree* land_octree_new(int xs, int ys, int zs, int xo, int yo, int zo, int cx, int cy, int cz) {
    LandOctree * self;
    land_alloc(self);
    self->data = land_calloc(xs * ys * zs * sizeof (* self->data));
    self->xs = xs;
    self->ys = ys;
    self->zs = zs;
    self->xo = xo;
    self->yo = yo;
    self->zo = zo;
    self->cx = cx;
    self->cy = cy;
    self->cz = cz;
    return self;
}
LandOctree* land_octree_new_from_aabb(LandCSGAABB * aabb, int count) {
    return land_octree_new(count, count, count, aabb->x1, aabb->y1, aabb->z1, (aabb->x2 - aabb->x1) / count, (aabb->y2 - aabb->y1) / count, (aabb->z2 - aabb->z1) / count);
}
void land_octree_del(LandOctree * self) {
    int n = self->xs * self->ys * self->zs;
    for (int i = 0; i < n; i += 1) {
        if (self->data [i]) {
            land_array_destroy(self->data [i]);
        }
    }
    land_free(self->data);
    land_free(self);
}
int between(int x, int a, int b) {
    if (x < a) {
        return a;
    }
    if (x > b) {
        return b;
    }
    return x;
}
static int _getx(LandOctree * self, LandFloat x) {
    return between((x - self->xo) / self->cx, 0, self->xs - 1);
}
static int _gety(LandOctree * self, LandFloat y) {
    return between((y - self->yo) / self->cy, 0, self->ys - 1);
}
static int _getz(LandOctree * self, LandFloat z) {
    return between((z - self->zo) / self->cz, 0, self->zs - 1);
}
static int _get_i(LandOctree * self, LandFloat x, LandFloat y, LandFloat z) {
    int xi = _getx(self, x);
    int yi = _getx(self, y);
    int zi = _getx(self, z);
    return xi + yi * self->xs + zi * self->xs * self->ys;
}
void land_octree_insert(LandOctree * self, LandFloat x, LandFloat y, LandFloat z, void * data) {
    int i = _get_i(self, x, y, z);
    LandArray * array = self->data [i];
    if (! array) {
        array = self->data [i] = land_array_new();
    }
    land_array_add(array, data);
}
LandArray* land_octree_get(LandOctree * self, LandFloat x, LandFloat y, LandFloat z) {
    int i = _get_i(self, x, y, z);
    return self->data [i];
}
void land_octree_callback_in_cube(LandOctree * self, LandFloat x1, LandFloat y1, LandFloat z1, LandFloat x2, LandFloat y2, LandFloat z2, void(* callback)(LandArray * array, void * data), void * data) {
    int ix1 = _getx(self, x1);
    int ix2 = _getx(self, x2);
    int iy1 = _gety(self, y1);
    int iy2 = _gety(self, y2);
    int iz1 = _getz(self, z1);
    int iz2 = _getz(self, z2);
    for (int ix = ix1; ix < ix2 + 1; ix += 1) {
        for (int iy = iy1; iy < iy2 + 1; iy += 1) {
            for (int iz = iz1; iz < iz2 + 1; iz += 1) {
                callback(self->data [ix + iy * self->xs + iz * self->xs * self->ys], data);
            }
        }
    }
}
LandTriangles* land_triangles_new(void) {
    LandTriangles * self;
    land_alloc(self);
    self->can_cache = 1;
    self->has_texture = 1;
    platform_triangles_init(self);
    return self;
}
LandTriangles* land_triangles_new_with_normals(void) {
    LandTriangles * self;
    land_alloc(self);
    self->has_normals = 1;
    self->has_texture = 1;
    self->can_cache = 1;
    platform_triangles_init(self);
    return self;
}
LandTriangles* land_triangles_new_with_normals_no_texture(void) {
    LandTriangles * self;
    land_alloc(self);
    self->has_normals = 1;
    self->can_cache = 1;
    platform_triangles_init(self);
    return self;
}
void land_triangles_add_csg(LandTriangles * self, LandCSG * csg) {
    /* Note: All polygons contained in the CSG must be triangles.
     * Note: No reference to the CSG is kept so you can safely destroy it
     * after this function returns.
     */
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(csg->polygons);
        for (LandCSGPolygon * p = LandArrayIterator_item(csg->polygons, &__iter0__); LandArrayIterator_next(csg->polygons, &__iter0__); p = LandArrayIterator_item(csg->polygons, &__iter0__)) {
            for (int j = 0; j < 3; j += 1) {
                LandCSGVertex * v = land_array_get_nth(p->vertices, j);
                land_add_vertex(self, v->pos.x, v->pos.y, v->pos.z, 0, 0, v->rgba.r, v->rgba.g, v->rgba.b, v->rgba.a);
                land_set_vertex_normal(self, v->normal.x, v->normal.y, v->normal.z);
                land_set_vertex_index(self, j);
            }
        }
    }
}
void land_triangles_destroy(LandTriangles * self) {
    return platform_triangles_deinit(self);
}
void land_triangles_destroy_with_image(LandTriangles * self) {
    land_image_destroy(self->image);
    self->image = NULL;
    return platform_triangles_deinit(self);
}
void land_triangles_clear(LandTriangles * self) {
    self->n = 0;
}
void land_triangles_refresh(LandTriangles * self) {
    platform_triangles_refresh(self);
}
void land_triangles_texture(LandTriangles * self, LandImage * texture) {
    /* Ownership remains at the caller.
     */
    self->image = texture;
}
void land_add_vertex(LandTriangles * self, float x, float y, float z, float u, float v, float r, float g, float b, float a) {
    self->n++;
    if (! self->buf) {
        self->buf = land_buffer_new();
    }
    land_buffer_grow(self->buf, self->size);
    land_update_vertex(self, self->n - 1, x, y, z, u, v, r, g, b, a);
}
void land_set_vertex_normal(LandTriangles * self, float x, float y, float z) {
    platform_set_vertex_normal(self, x, y, z);
}
void land_set_vertex_index(LandTriangles * self, float i) {
    platform_set_vertex_index(self, i);
}
void land_duplicate_vertex(LandTriangles * self, int i) {
    /* -1 .. duplicate most recent
     * -2 .. duplicate the second most recent
     * ...
     */
    self->n++;
    land_buffer_grow(self->buf, self->size);
    land_buffer_move(self->buf, self->size * (- 1 + i), - self->size, self->size);
}
void land_update_vertex(LandTriangles * self, int i, float x, float y, float z, float u, float v, float r, float g, float b, float a) {
    platform_update_vertex(self, i, x, y, z, u, v, r, g, b, a);
}
void land_triangles_draw(LandTriangles * self) {
    if (! self->n) {
        return ;
    }
    platform_triangles_draw(self, 0);
}
void land_triangles_draw_more(LandTriangles * self, bool more) {
    if (! self->n) {
        return ;
    }
    platform_triangles_draw(self, more);
}
void* land_triangles_get_vertex(LandTriangles * self, int i) {
    char * pointer = self->buf->buffer;
    pointer += i * self->size;
    return pointer;
}
void land_triangles_draw_debug(LandTriangles * self) {
    for (int i = 0; i < self->n; i += 3) {
        float xy [6], z [1];
        platform_triangles_get_xyz(self, i + 0, xy + 0, xy + 1, z);
        platform_triangles_get_xyz(self, i + 1, xy + 2, xy + 3, z);
        platform_triangles_get_xyz(self, i + 2, xy + 4, xy + 5, z);
        land_polygon(3, xy);
    }
}
void land_triangles_can_cache(LandTriangles * self, bool can_cache) {
    self->can_cache = can_cache;
}
void land_triangles_shader(LandTriangles * self, str id, str vertex, str fragment) {
    platform_triangles_shader(self, id, vertex, fragment);
}
void land_triangles_set_light_direction(LandVector light) {
    platform_triangles_set_light_direction(light);
}
void land_triangles_set_light(float light) {
    platform_triangles_set_light(light);
}
LandFloat land_triangles_get_max_z(LandTriangles * self) {
    float maxz = INT_MIN;
    for (int i = 0; i < self->n; i += 1) {
        float x, y, z;
        platform_triangles_get_xyz(self, i + 0, & x, & y, & z);
        if (z > maxz) {
            maxz = z;
        }
    }
    return maxz;
}
LandFloat land_triangles_get_max_y(LandTriangles * self) {
    float maxy = INT_MIN;
    for (int i = 0; i < self->n; i += 1) {
        float x, y, z;
        platform_triangles_get_xyz(self, i + 0, & x, & y, & z);
        if (y > maxy) {
            maxy = y;
        }
    }
    return maxy;
}
static LandArray * joys;
struct Stick {
    int first_axis;
    int axes;
};
struct Joy {
    ALLEGRO_JOYSTICK * allegro;
    int allegro_button;
    int first_axis;
    int axes;
    int first_button;
    int buttons;
    LandArray * sticks;
};
static LandArray * button_names;
static LandArray * axis_names;
void a5_joystick_create_mapping(void) {
    if (joys) {
        {
            LandArrayIterator __iter0__ = LandArrayIterator_first(joys);
            for (Joy * joy = LandArrayIterator_item(joys, &__iter0__); LandArrayIterator_next(joys, &__iter0__); joy = LandArrayIterator_item(joys, &__iter0__)) {
                land_array_destroy_with_free(joy->sticks);
            }
        }
        land_array_destroy_with_free(joys);
        land_array_destroy_with_free(button_names);
        land_array_destroy_with_free(axis_names);
    }
    joys = land_array_new();
    button_names = land_array_new();
    land_array_add(button_names, land_strdup("none"));
    axis_names = land_array_new();
    land_array_add(axis_names, land_strdup("none"));
    int jn = al_get_num_joysticks();
    int axes = 1;
    int buttons = 1;
    for (int j = 0; j < jn; j += 1) {
        ALLEGRO_JOYSTICK * allegro = al_get_joystick(j);
        Joy * joy = land_calloc(sizeof (* joy));
        joy->allegro = allegro;
        joy->buttons = al_get_joystick_num_buttons(allegro);
        joy->sticks = land_array_new();
        joy->first_axis = axes;
        joy->first_button = buttons;
        joy->axes = 0;
        land_array_add(joys, joy);
        int sn = al_get_joystick_num_sticks(allegro);
        for (int s = 0; s < sn; s += 1) {
            Stick * stick = land_calloc(sizeof (* stick));
            stick->first_axis = axes;
            stick->axes = al_get_joystick_num_axes(allegro, s);
            land_array_add(joy->sticks, stick);
            for (int a = 0; a < stick->axes; a += 1) {
                char * name = land_strdup(al_get_joystick_stick_name(allegro, s));
                land_concatenate_with_separator(& name, al_get_joystick_axis_name(allegro, s, a), " ");
                land_log_message("joystick axis %d: %s\n", axes + a, name);
                land_array_add(axis_names, name);
            }
            joy->axes += stick->axes;
            axes += stick->axes;
            if (axes > LandJoystickAxesCount - 1) {
                axes = LandJoystickAxesCount - 1;
                land_log_message("Error: too many joystick axes!\n");
            }
        }
        for (int b = 0; b < joy->buttons; b += 1) {
            char * name = land_strdup(al_get_joystick_button_name(allegro, b));
            land_log_message("joystick button %d: %s\n", buttons + b, name);
            land_array_add(button_names, name);
        }
        buttons += joy->buttons;
        if (buttons > LandJoystickButtonsCount - 1) {
            buttons = LandJoystickButtonsCount - 1;
            land_log_message("Error: too many joystick buttons!");
        }
    }
}
int a5_joystick_axis_to_land(ALLEGRO_JOYSTICK * allegro, int s, int a) {
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(joys);
        for (Joy * joy = LandArrayIterator_item(joys, &__iter0__); LandArrayIterator_next(joys, &__iter0__); joy = LandArrayIterator_item(joys, &__iter0__)) {
            if (joy->allegro == allegro) {
                Stick * stick = land_array_get_nth(joy->sticks, s);
                return stick->first_axis + a;
            }
        }
    }
    return 0;
}
int a5_joystick_button_to_land(ALLEGRO_JOYSTICK * allegro, int b) {
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(joys);
        for (Joy * joy = LandArrayIterator_item(joys, &__iter0__); LandArrayIterator_next(joys, &__iter0__); joy = LandArrayIterator_item(joys, &__iter0__)) {
            if (joy->allegro == allegro) {
                return joy->first_button + b;
            }
        }
    }
    return 0;
}
int platform_joystick_axis_count(void) {
    return LandArray__len__(axis_names);
}
int platform_joystick_button_count(void) {
    return LandArray__len__(button_names);
}
str platform_joystick_button_name(int b) {
    if (b >= LandArray__len__(button_names)) {
        return "none";
    }
    return land_array_get_nth(button_names, b);
}
str platform_joystick_axis_name(int a) {
    if (a >= LandArray__len__(axis_names)) {
        return "none";
    }
    return land_array_get_nth(axis_names, a);
}
#ifdef ANDROID
#include "allegro5/allegro_android.h"
#endif
#ifdef __EMSCRIPTEN__
#include < emscripten.h>
#endif
static bool redraw;
static LandDisplayPlatform * d;
static ALLEGRO_EVENT_QUEUE * queue;
static ALLEGRO_TIMER * timer;
static void(* global_cb)(void);
static int replacement_main(int argc, char * (* argv)) {
    global_cb();
    return 0;
}
void platform_without_main(void(* cb)(void)) {
    global_cb = cb;
    al_run_main(0, NULL, replacement_main);
}
double platform_get_time(void) {
    return al_current_time();
}
void platform_halt(void) {
    platform_sound_halt();
}
void platform_resume(void) {
    platform_sound_resume();
}
void platform_init(void) {
    land_log_message("Compiled against Allegro %s.\n", ALLEGRO_VERSION_STR);
    if (! al_init()) {
        land_log_message("Allegro initialization failed.\n");
        land_exception("Error in allegro_init.");
    }
    queue = al_create_event_queue();
    al_init_image_addon();
    al_install_keyboard();
    al_install_mouse();
    al_init_primitives_addon();
    if (al_install_joystick()) {
        al_register_event_source(queue, al_get_joystick_event_source());
    }
    a5_joystick_create_mapping();
    #ifdef  ANDROID
    al_android_set_apk_file_interface();
    #endif
}
#define _UnkKey(x) LandKeyUnknown3 + x + 0, LandKeyUnknown3 + x + 1, LandKeyUnknown3 + x + 2, LandKeyUnknown3 + x + 3, LandKeyUnknown3 + x + 4, LandKeyUnknown3 + x + 5, LandKeyUnknown3 + x + 6, LandKeyUnknown3 + x + 7, LandKeyUnknown3 + x + 8, LandKeyUnknown3 + x + 9
static int keyboard_conversion_table [ALLEGRO_KEY_MAX] = {LandKeyNone, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', LandKeyPad + 0, LandKeyPad + 1, LandKeyPad + 2, LandKeyPad + 3, LandKeyPad + 4, LandKeyPad + 5, LandKeyPad + 6, LandKeyPad + 7, LandKeyPad + 8, LandKeyPad + 9, LandKeyFunction + 1, LandKeyFunction + 2, LandKeyFunction + 3, LandKeyFunction + 4, LandKeyFunction + 5, LandKeyFunction + 6, LandKeyFunction + 7, LandKeyFunction + 8, LandKeyFunction + 9, LandKeyFunction + 10, LandKeyFunction + 11, LandKeyFunction + 12, LandKeyEscape, '~', '-', '=', LandKeyBackspace, LandKeyTab, '[', ']', LandKeyEnter, ';', '\'', '\\', LandKeyUnknown + 0, ',', '.', '/', ' ', LandKeyInsert, LandKeyDelete, LandKeyHome, LandKeyEnd, LandKeyPageUp, LandKeyPageDown, LandKeyLeft, LandKeyRight, LandKeyUp, LandKeyDown, LandKeyPadSlash, LandKeyPadStar, LandKeyPadMinus, LandKeyPadPlus, LandKeyPadDelete, LandKeyPadEnter, LandKeyPrint, LandKeyPause, LandKeyUnknown + 1, LandKeyUnknown + 2, LandKeyUnknown + 3, LandKeyUnknown + 4, LandKeyUnknown + 5, '@', '^', ':', LandKeyUnknown + 6, LandKeyUnknown + 7, LandKeyUnknown + 8, LandKeyUnknown + 9, LandKeyUnknown + 10, LandKeyBack, LandKeyUnknown + 12, LandKeyUnknown2 + 0, LandKeyUnknown2 + 1, LandKeyUnknown2 + 2, LandKeyUnknown2 + 3, LandKeyUnknown2 + 4, LandKeyUnknown2 + 5, LandKeyUnknown3 + 0, LandKeyUnknown3 + 1, LandKeyUnknown3 + 2, LandKeyUnknown3 + 3, LandKeyUnknown3 + 4, LandKeyUnknown3 + 5, _UnkKey(6), _UnkKey(16), _UnkKey(26), _UnkKey(36), _UnkKey(46), _UnkKey(56), _UnkKey(66), _UnkKey(76), _UnkKey(86), LandKeyUnknown3 + 96, LandKeyUnknown3 + 97, LandKeyUnknown3 + 98, LandKeyUnknown3 + 99, LandKeyLeftShift, LandKeyRightShift, LandKeyLeftControl, LandKeyRightControl, LandKeyLeftAlt, LandKeyRightAlt, LandKeyLeftWin, LandKeyRightWin, LandKeyMenu, LandKeyScrollLock, LandKeyNumLock, LandKeyCapsLock};
char const* platform_key_name(int lk) {
    int ak = 0;
    for (int i = 0; i < ALLEGRO_KEY_MAX; i++) {
        if (keyboard_conversion_table [i] == lk) {
            ak = i;
            break;
        }
    }
    char const * s = al_keycode_to_name(ak);
    return s;
}
static int platform_keycode(int ak) {
    return keyboard_conversion_table [ak];
}
void platform_hide_mouse_cursor(void) {
    LandDisplayPlatform * d = (void *) _land_active_display;
    al_hide_mouse_cursor(d->a5);
}
void platform_show_mouse_cursor(void) {
    LandDisplayPlatform * d = (void *) _land_active_display;
    al_show_mouse_cursor(d->a5);
}
void platform_mouse_set_pos(float x, float y) {
    LandDisplayPlatform * d = (void *) _land_active_display;
    al_set_mouse_xy(d->a5, x, y);
}
void platform_pause(void) {
    if (timer) {
        al_stop_timer(timer);
    }
}
void platform_unpause(void) {
    if (timer) {
        al_resume_timer(timer);
    }
}
void platform_mainloop(LandParameters * parameters) {
    d = (void *) _land_active_display;
    timer = al_create_timer(1.0 / parameters->fps);
    al_register_event_source(queue, al_get_keyboard_event_source());
    al_register_event_source(queue, al_get_mouse_event_source());
    if (al_install_touch_input()) {
        al_register_event_source(queue, al_get_touch_input_event_source());
    }
    al_register_event_source(queue, al_get_display_event_source(d->a5));
    al_register_event_source(queue, al_get_timer_event_source(timer));
    al_start_timer(timer);
    ALLEGRO_MOUSE_STATE s;
    al_get_mouse_state(& s);
    land_mouse_move_event(s.x, s.y, s.z);
    redraw = 0;
    #ifdef __EMSCRIPTEN__
    _land_synchronized = 1;
    emscripten_set_main_loop(platform_frame, 0, true);
    #else
    while (! _land_quit) {
        platform_frame();
    }
    #endif
}
void platform_frame(void) {
    if (! _land_quit) {
        if (redraw && (_land_synchronized || al_event_queue_is_empty(queue))) {
            if (! _land_halted) {
                land_draw();
            }
            redraw = 0;
        }
        while (1) {
            ALLEGRO_EVENT event;
            #ifdef __EMSCRIPTEN__
            if (! al_get_next_event(queue, & event)) {
                break;
            }
            #else
            al_wait_for_event(queue, & event);
            #endif
            switch (event.type) {
                case ALLEGRO_EVENT_DISPLAY_CLOSE: {
                    land_closebutton_event();
                    break;
                }
                case ALLEGRO_EVENT_TIMER: {
                    if (_land_was_halted && _land_halted) {
                        al_stop_timer(timer);
                    }
                    else {
                        land_tick();
                        _land_frames++;
                        redraw = 1;
                    }
                    break;
                }
                case ALLEGRO_EVENT_KEY_DOWN: {
                    int lk = platform_keycode(event.keyboard.keycode);
                    land_key_press_event(lk);
                    break;
                }
                case ALLEGRO_EVENT_KEY_CHAR: {
                    int lk = platform_keycode(event.keyboard.keycode);
                    land_keyboard_add_char(lk, event.keyboard.unichar);
                    break;
                }
                case ALLEGRO_EVENT_KEY_UP: {
                    int lk = platform_keycode(event.keyboard.keycode);
                    land_key_release_event(lk);
                    break;
                }
                case ALLEGRO_EVENT_MOUSE_AXES: {
                    land_mouse_move_event(event.mouse.x, event.mouse.y, event.mouse.z);
                    if (land_mouse_b() & 1) {
                        land_touch_event(event.mouse.x, event.mouse.y, 10, 0);
                    }
                    break;
                }
                case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN: {
                    land_mouse_button_down_event(event.mouse.button - 1);
                    land_touch_event(land_mouse_x(), land_mouse_y(), 10, 1);
                    break;
                }
                case ALLEGRO_EVENT_MOUSE_BUTTON_UP: {
                    land_mouse_button_up_event(event.mouse.button - 1);
                    land_touch_event(land_mouse_x(), land_mouse_y(), 10, - 1);
                    break;
                }
                case ALLEGRO_EVENT_TOUCH_BEGIN: {
                    land_touch_event(event.touch.x, event.touch.y, event.touch.id, 1);
                    land_mouse_move_event(event.touch.x, event.touch.y, 0);
                    land_mouse_button_down_event(0);
                    break;
                }
                case ALLEGRO_EVENT_TOUCH_END: {
                    land_touch_event(event.touch.x, event.touch.y, event.touch.id, - 1);
                    land_mouse_move_event(event.touch.x, event.touch.y, 0);
                    land_mouse_button_up_event(0);
                    break;
                }
                case ALLEGRO_EVENT_TOUCH_MOVE: {
                    land_touch_event(event.touch.x, event.touch.y, event.touch.id, 0);
                    land_mouse_move_event(event.touch.x, event.touch.y, 0);
                    break;
                }
                case ALLEGRO_EVENT_DISPLAY_RESIZE: {
                    al_acknowledge_resize((ALLEGRO_DISPLAY *) event.any.source);
                    land_resize_event(event.display.width, event.display.height);
                    break;
                }
                case ALLEGRO_EVENT_DISPLAY_SWITCH_OUT: {
                    land_switch_out_event();
                    break;
                }
                case ALLEGRO_EVENT_DISPLAY_HALT_DRAWING: {
                    land_halt();
                    al_acknowledge_drawing_halt(d->a5);
                    break;
                }
                case ALLEGRO_EVENT_DISPLAY_SWITCH_IN: {
                    break;
                }
                case ALLEGRO_EVENT_DISPLAY_RESUME_DRAWING: {
                    land_resume();
                    al_acknowledge_drawing_resume(d->a5);
                    al_start_timer(timer);
                    break;
                }
                case ALLEGRO_EVENT_JOYSTICK_CONFIGURATION: {
                    al_reconfigure_joysticks();
                    a5_joystick_create_mapping();
                    break;
                }
                case ALLEGRO_EVENT_JOYSTICK_AXIS: {
                    int a = a5_joystick_axis_to_land(event.joystick.id, event.joystick.stick, event.joystick.axis);
                    land_joystick_axis_event(a, event.joystick.pos);
                    break;
                }
                case ALLEGRO_EVENT_JOYSTICK_BUTTON_DOWN: {
                    int b = a5_joystick_button_to_land(event.joystick.id, event.joystick.button);
                    land_joystick_button_down_event(b);
                    break;
                }
                case ALLEGRO_EVENT_JOYSTICK_BUTTON_UP: {
                    int b = a5_joystick_button_to_land(event.joystick.id, event.joystick.button);
                    land_joystick_button_up_event(b);
                    break;
                }
            }
            #ifdef __EMSCRIPTEN__
            continue;
            #endif
            break;
        }
    }
}
void platform_wait(double seconds) {
    al_rest(seconds);
}
#undef _UnkKey
void print(char const * s, ...) {
    va_list args;
    va_start(args, s);
    vprintf(s, args);
    va_end(args);
    printf("\n");
}
char* land_read_text(char const * filename) {
    LandBuffer * bytebuffer = land_buffer_read_from_file(filename);
    if (! bytebuffer) {
        return NULL;
    }
    return land_buffer_finish(bytebuffer);
}
int land_utf8_char(char * (* pos)) {
    /* Return the unicode value at the given pointer position, and advance the
     * pointer to the start of the next code point.
     */
    unsigned char * upos = (unsigned char *) * pos;
    int remain;
    int c = * upos++;
    if (c < 128) {
        * pos = (char *) upos;
        return c;
    }
    if (c < 194) {
        return 0;
    }
    if (c < 224) {
        c &= 31;
        remain = 1;
    }
    else if (c < 240) {
        c &= 15;
        remain = 2;
    }
    else if (c < 245) {
        c &= 7;
        remain = 3;
    }
    else {
        return 0;
    }
    while (remain--) {
        int d = * upos++;
        c = (c << 6) | (d & 63);
    }
    * pos = (char *) upos;
    return c;
}
int land_utf8_char_back(char * (* pos)) {
    /* Adjust the pointer back to the previous code point and return its value.
     */
    unsigned char * upos = (unsigned char *) * pos;
    while (((* (--upos) & 0xc0) == 0x80)) {
    }
    * pos = (char *) upos;
    int c = land_utf8_char((char * *) & upos);
    return c;
}
int land_utf8_char_const(char const * (* pos)) {
    char * (* p) = (char * *) pos;
    return land_utf8_char(p);
}
int land_utf8_encode(int c, char * s) {
    uint32_t uc = c;
    if (uc <= 0x7f) {
        if (s) {
            s [0] = uc;
        }
        return 1;
    }
    if (uc <= 0x7ff) {
        if (s) {
            s [0] = 0xC0 | ((uc >> 6) & 0x1F);
            s [1] = 0x80 | (uc & 0x3F);
        }
        return 2;
    }
    if (uc <= 0xffff) {
        if (s) {
            s [0] = 0xE0 | ((uc >> 12) & 0x0F);
            s [1] = 0x80 | ((uc >> 6) & 0x3F);
            s [2] = 0x80 | (uc & 0x3F);
        }
        return 3;
    }
    if (uc <= 0x10ffff) {
        if (s) {
            s [0] = 0xF0 | ((uc >> 18) & 0x07);
            s [1] = 0x80 | ((uc >> 12) & 0x3F);
            s [2] = 0x80 | ((uc >> 6) & 0x3F);
            s [3] = 0x80 | (uc & 0x3F);
        }
        return 4;
    }
    return 0;
}
char* land_utf8_realloc_insert(char * s, int pos, int c) {
    /* (abc, 3, d) -> abcd
     */
    int l = strlen(s);
    int clen = land_utf8_encode(c, NULL);
    s = land_realloc(s, l + clen + 1);
    char * p = s;
    if (pos >= 0) {
        for (int i = 0; i < pos; i++) {
            land_utf8_char(& p);
        }
    }
    else {
        p += l;
    }
    memmove(p + clen, p, l + 1 - (p - s));
    land_utf8_encode(c, p);
    return s;
}
char* land_utf8_realloc_remove(char * s, int pos) {
    /* (abc, 1) -> ac
     */
    int l = strlen(s);
    char * p = s;
    for (int i = 0; i < pos; i++) {
        land_utf8_char(& p);
    }
    char * p2 = p;
    land_utf8_char(& p2);
    memmove(p, p2, l - (p2 - s) + 1);
    s = land_realloc(s, l - (p2 - p) + 1);
    return s;
}
int land_utf8_count(char const * s) {
    int n = 0;
    while (land_utf8_char_const(& s)) {
        n++;
    }
    return n;
}
void land_utf8_copy(char * target, int size, char const * source) {
    int i = 0;
    char const * ptr = source;
    char const * prev = source;
    while (land_utf8_char_const(& ptr)) {
        int s = ptr - prev;
        if (i + s < size) {
            memcpy(target + i, prev, s);
            i += s;
        }
        else {
            break;
        }
        prev = ptr;
    }
    target [i] = 0;
}
bool land_fnmatch(char const * pattern, char const * name) {
    int i = 0, j = 0;
    while (1) {
        switch (pattern [i]) {
            case 0: {
                return name [j] == 0;
            }
            case '?': {
                if (name [j] == 0) {
                    return 0;
                }
                break;
            }
            case '*': {
                while (pattern [i] == '*') {
                    i++;
                }
                if (pattern [i] == 0) {
                    return 1;
                }
                while (name [j] != 0) {
                    if (land_fnmatch(pattern + i, name + j)) {
                        return 1;
                    }
                    j++;
                }
                return false;
            }
            default: {
                if (pattern [i] != name [j]) {
                    return 0;
                }
            }
        }
        i++;
        j++;
    }
}
char* land_string_copy(char * target, char const * source, int size) {
    /* size is the size of target in bytes (including the terminating 0)
     * Returns target.
     */
    strncpy(target, source, size - 1);
    target [size - 1] = 0;
    return target;
}
bool land_equals(char const * s, char const * s2) {
    if (s == NULL) {
        return s2 == NULL;
    }
    if (s2 == NULL) {
        return 0;
    }
    return strcmp(s, s2) == 0;
}
bool land_ends_with(char const * s, char const * end) {
    size_t n = strlen(end);
    return strncmp(s + strlen(s) - n, end, n) == 0;
}
bool land_starts_with(char const * s, char const * start) {
    size_t n = strlen(start);
    return strncmp(s, start, n) == 0;
}
void land_concatenate(char * (* s), char const * cat) {
    int sn = strlen(* s);
    int n = sn + strlen(cat) + 1;
    char * re = land_realloc(* s, n);
    memmove(re + sn, cat, strlen(cat));
    re [n - 1] = 0;
    * s = re;
}
void land_appendv(char * (* s), str format, va_list args) {
    va_list args2;
    va_copy(args2, args);
    int n = vsnprintf(NULL, 0, format, args2);
    va_end(args2);
    if (n < 0) {
        n = 1023;
    }
    char f [n + 1];
    vsnprintf(f, n + 1, format, args);
    land_concatenate(s, f);
}
void land_append(char * (* s), str format, ...) {
    va_list args;
    va_start(args, format);
    land_appendv(s, format, args);
    va_end(args);
}
void land_concatenate_with_separator(char * (* s), char const * cat, char const * sep) {
    if (! (* s) || land_equals(* s, "")) {
        land_concatenate(s, cat);
    }
    else {
        land_concatenate(s, sep);
        land_concatenate(s, cat);
    }
}
void land_prepend(char * (* s), char const * pre) {
    int slen = strlen(* s);
    int n = slen + strlen(pre) + 1;
    char * re = land_realloc(* s, n);
    memmove(re + strlen(pre), re, slen);
    memmove(re, pre, strlen(pre));
    re [n - 1] = 0;
    * s = re;
}
int land_replace(char * (* s), int off, char const * wat, char const * wit) {
    /* Given a pointer to a string, replaces the string with a new string
     * and deletes the original one. The new string will have the
     * first occurence of "wat" replaced with "wit", starting at byte
     * offset off.
     */
    char * r = strstr(* s + off, wat);
    if (! r) {
        return strlen(* s);
    }
    int pn = r - * s;
    int sn = strlen(* s);
    int n = sn + strlen(wit) - strlen(wat) + 1;
    char * re = land_malloc(n);
    memmove(re, * s, r - * s);
    memmove(re + pn, wit, strlen(wit));
    memmove(re + pn + strlen(wit), r + strlen(wat), sn - pn - strlen(wat));
    re [n - 1] = 0;
    land_free(* s);
    * s = re;
    return pn + strlen(wit);
}
bool land_contains(str hay, str needle) {
    return strstr(hay, needle) != NULL;
}
int land_find(str hay, str needle) {
    str x = strstr(hay, needle);
    if (! x) {
        return - 1;
    }
    return x - hay;
}
int land_find_from_back(str hay, str needle) {
    int hn = strlen(hay);
    int nn = strlen(needle);
    int i = hn - nn;
    while (i >= 0) {
        if (strncmp(hay + i, needle, nn) == 0) {
            return i;
        }
        i--;
    }
    return - 1;
}
int land_count(str hay, str needle) {
    str p = hay;
    int n = 0;
    while (1) {
        str x = strstr(p, needle);
        if (! x) {
            return n;
        }
        n += 1;
        p = x + 1;
    }
}
int land_replace_all(char * (* s), char const * wat, char const * wit) {
    /* Like land_replace but replaces all occurences. Returns the number
     * of replacements performed.
     */
    int off = 0;
    int c = 0;
    while (1) {
        off = land_replace(s, off, wat, wit);
        if (! (* s) [off]) {
            return c;
        }
        c++;
    }
}
char* land_lowercase_copy(str s) {
    char const * pos = s;
    char * news = land_strdup("");
    while (1) {
        int c = land_utf8_char_const(& pos);
        if (! c) {
            break;
        }
        news = land_utf8_realloc_insert(news, - 1, tolower(c));
    }
    return news;
}
void land_shorten(char * (* s), int start, int end) {
    /* Shorten a string so it starts at start and ends before end.
     * land_shorten("abcd", 1, 3) -> "bc"
     * land_shorten("abcd", 1, -1) -> "bc"
     */
    char * replace = land_substring(* s, start, strlen(* s) - end);
    land_free(* s);
    * s = replace;
}
void land_replace_string(char * (* s), str replace) {
    /* Free the target string if it is not None and then replace it with
     * a copy of the given replacement.
     */
    if (* s) {
        land_free(* s);
    }
    * s = land_strdup(replace);
}
void land_cut(char * (* s), int start, int end) {
    /* Cut the given sequence from start to before end out of the string.
     */
    char * replace = land_substring(* s, 0, start);
    land_concatenate(& replace, * s + end);
    land_free(* s);
    * s = replace;
}
char* land_substring(char const * s, int a, int b) {
    /* a is inlusive
     * b is exclusive
     */
    if (a < 0) {
        a += strlen(s);
    }
    if (b < 0) {
        b += strlen(s);
    }
    char * r = land_malloc(b - a + 1);
    memmove(r, s + a, b - a);
    r [b - a] = 0;
    return r;
}
void land_strip(char * (* s)) {
    LandBuffer * b = land_buffer_new();
    land_buffer_cat(b, * s);
    land_buffer_strip(b, " \t\n\r");
    land_free(* s);
    * s = land_buffer_finish(b);
}
LandArray* land_filelist(char const * dir, int(* filter)(char const *, bool is_dir, void * data), int flags, void * data) {
    /* Returns an array of files in the given directory. Before a file is added,
     * the filter function is called, with the name about to be added and an
     * indication whether it is a filename or a directory.
     * If flags is LAND_FULL_PATH files are returned as a full path, if
     * LAND_RELATIVE_PATH relative to dir, otherwise as
     * only the filename.
     * The return value of the filter decides what is done with the name:
     * 0 - Discard it.
     * 1 - Append it to the returned list.
     * 2 - If it is a directory, recurse into it.
     * 3 - Like 1 and 2 combined.
     */
    char * dir2 = land_path_with_prefix(dir);
    LandArray * array = platform_filelist(dir2, filter, flags, data);
    land_free(dir2);
    return array;
}
LandArray* land_split_path_name_ext(char const * filename) {
    /* Returns a LandArray with three elements, for example:
     * data/blah/tree.png -> ["data/blah", "tree", "png"]
     * test.txt -> [None, "test", "txt"]
     * /etc/passwd -> ["/etc", "passwd", None]
     * The return value can most conveniently be freed like this:
     * a = land_split_path_name_ext(filename)
     * ...
     * land_array_destroy_with_strings(a)
     */
    LandArray * a = land_array_new();
    char * path = land_strdup(filename);
    char * name;
    char * ext;
    char * slash = strrchr(path, '/');
    if (slash) {
        * slash = 0;
        name = land_strdup(slash + 1);
    }
    else {
        name = path;
        path = NULL;
    }
    char * dot = strrchr(name, '.');
    if (dot) {
        * dot = 0;
        ext = land_strdup(dot + 1);
    }
    else {
        ext = NULL;
    }
    land_array_add(a, path);
    land_array_add(a, name);
    land_array_add(a, ext);
    return a;
}
LandArray* land_split(char const * text, str c) {
    /* Returns an array of strings which you should destroy with
     * land_array_destroy_with_strings
     */
    LandArray * split = land_array_new();
    LandBuffer * buf = land_buffer_new();
    land_buffer_cat(buf, text);
    LandArray * lines = land_buffer_split(buf, c);
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(lines);
        for (LandBuffer * line = LandArrayIterator_item(lines, &__iter0__); LandArrayIterator_next(lines, &__iter0__); line = LandArrayIterator_item(lines, &__iter0__)) {
            char * x = land_buffer_finish(line);
            land_array_add(split, x);
        }
    }
    land_array_destroy(lines);
    land_buffer_destroy(buf);
    return split;
}
bool land_split_two(str text, str sep, char * (* a), char * (* b)) {
    int x = land_find(text, sep);
    if (x < 0) {
        return 0;
    }
    * a = land_substring(text, 0, x);
    * b = land_substring(text, x + strlen(sep), strlen(text));
    return 1;
}
LandArray* land_split_lines(char const * text) {
    return land_split(text, "\n");
}
bool land_null_or_empty(str text) {
    return ! text || ! text [0];
}
LandFloat land_constrain(LandFloat * v, LandFloat v_min, LandFloat v_max) {
    if (* v < v_min) {
        * v = v_min;
    }
    if (* v > v_max) {
        * v = v_max;
    }
    return * v;
}
float land_constrainf(float v, float v_min, float v_max) {
    if (v < v_min) {
        v = v_min;
    }
    if (v > v_max) {
        v = v_max;
    }
    return v;
}
int land_mod(int x, int d) {
    /* Version of % that always is positive.
     */
    x %= d;
    if (x < 0) {
        x += d;
    }
    return x;
}
int land_div(int x, int d) {
    /* Version of / that rounds to negative infinity for negative numbers.
     */
    if (x < 0) {
        x -= d - 1;
    }
    x /= d;
    return x;
}
LandAnimation* land_animation_new(LandArray * frames) {
    /* Ownership of the frames array is transferred to the animation - destroying
     * the animation later will destroy the array.
     */
    LandAnimation * self;
    land_alloc(self);
    self->fps = 10;
    self->frames = frames;
    return self;
}
void land_animation_destroy(LandAnimation * self) {
    int i;
    if (self->frames) {
        for (i = 0; i < self->frames->count; i++) {
            land_image_destroy(land_array_get_nth(self->frames, i));
        }
        land_array_destroy(self->frames);
    }
    land_free(self);
}
LandImage* land_animation_get_frame(LandAnimation * self, int i) {
    return land_array_get_nth(self->frames, i);
}
int land_animation_length(LandAnimation * self) {
    if (self->frames) {
        return land_array_count(self->frames);
    }
    return 0;
}
void land_animation_add_frame(LandAnimation * self, LandImage * frame) {
    if (! self->frames) {
        self->frames = land_array_new();
    }
    land_array_add(self->frames, frame);
}
LandAnimation* land_animation_load_cb(char const * pattern, void(* cb)(LandImage * image, void * data), void * data) {
    /* Create a new animation from all files matching the pattern, sorted
     * alphabetically. The callback function, if present, is called on each
     * frame.
     */
    LandArray * pics = land_load_images_cb(pattern, cb, data);
    if (! pics) {
        land_log_message("Could not locate: %s\n", pattern);
        return NULL;
    }
    return land_animation_new(pics);
}
void land_animation_draw_frame(LandAnimation * self, int i, float x, float y) {
    LandImage * frame = land_animation_get_frame(self, i);
    land_image_draw(frame, x, y);
}
void land_animation_draw_frame_rotated(LandAnimation * self, int i, float x, float y, float angle) {
    LandImage * frame = land_animation_get_frame(self, i);
    land_image_draw_rotated(frame, x, y, angle);
}
void land_animation_draw_frame_scaled_rotated(LandAnimation * self, int i, float x, float y, float xs, float ys, float angle) {
    LandImage * frame = land_animation_get_frame(self, i);
    land_image_draw_scaled_rotated(frame, x, y, xs, ys, angle);
}
void land_animation_mirror(LandAnimation * self) {
    /* Assuming the animation is like this make it -> like this:
     * 1 -> 1
     * 1 2 -> 1 2
     * 1 2 3 -> 1 2 3 2
     * 1 2 3 4 -> 1 2 3 4 3 2
     * 1 2 3 4 5 -> 1 2 3 4 5 4 3 2
     * ...
     */
    if (! self->frames) {
        return ;
    }
    int n = land_array_count(self->frames);
    for (int i = 0; i < n - 2; i += 1) {
        land_animation_add_frame(self, land_array_get_nth(self->frames, n - 2 - i));
    }
}
void land_animation_load_on_demand(LandAnimation * self) {
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(self->frames);
        for (LandImage * image = LandArrayIterator_item(self->frames, &__iter0__); LandArrayIterator_next(self->frames, &__iter0__); image = LandArrayIterator_item(self->frames, &__iter0__)) {
            land_image_load_on_demand(image);
        }
    }
}
void land_animation_load_async(LandAnimation * self) {
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(self->frames);
        for (LandImage * image = LandArrayIterator_item(self->frames, &__iter0__); LandArrayIterator_next(self->frames, &__iter0__); image = LandArrayIterator_item(self->frames, &__iter0__)) {
            land_image_load_async(image);
        }
    }
}
static bool land_active;
bool _land_quit;
static LandParameters * parameters;
bool _land_halted;
bool _land_was_halted;
static bool x_clicked;
int _land_frames;
bool _land_synchronized;
static bool _maximize_fps;
static int skip_counter;
static int _flip_count;
static int _flips_at_tick [256];
static int _flip_tick_i;
static void land_exit(void) {
    if (! land_active) {
        return ;
    }
    land_active = 0;
    land_free(parameters);
    land_log_message("land_exit\n");
}
void land_halt(void) {
    platform_halt();
    _land_halted = 1;
}
void land_resume(void) {
    platform_resume();
    _land_halted = 0;
}
bool land_was_halted(void) {
    return _land_halted;
}
void land_init(void) {
    /* """Initialize Land. This must be called before anything else."""
     */
    if (land_active) {
        return ;
    }
    land_active = 1;
    land_log_message("land_init\n");
    land_alloc(parameters);
    parameters->w = 640;
    parameters->h = 480;
    parameters->fps = 60;
    atexit(land_exit);
    if (! land_exception_handler) {
        land_exception_handler_set(land_default_exception_handler);
    }
    int seed = time(NULL);
    land_seed(seed);
    land_log_message("Random seed is %d.\n", seed);
    char cd [1024];
    if (! getcwd(cd, sizeof cd)) {
        sprintf(cd, "<none>");
    }
    land_log_message("Current path: %s\n", cd);
    platform_init();
}
void land_tick(void) {
    land_display_tick();
    land_runner_tick_active();
    land_mouse_tick();
    land_keyboard_tick();
    land_joystick_tick();
    x_clicked = 0;
    _land_was_halted = _land_halted;
    _flip_tick_i--;
    if (_flip_tick_i < 0) {
        _flip_tick_i = 255;
    }
    _flips_at_tick [_flip_tick_i] = _flip_count;
}
void land_draw(void) {
    if (parameters->skip) {
        skip_counter++;
        if (skip_counter <= parameters->skip) {
            return ;
        }
        skip_counter = 0;
    }
    land_runner_draw_active();
    _flip_count++;
    land_flip();
}
void land_quit(void) {
    /* Quit the Land application. Call it when you want the program to
     * exit.
     */
    _land_quit = 1;
}
void land_closebutton_event(void) {
    x_clicked = 1;
}
int land_closebutton(void) {
    /* """Check if the closebutton has been clicked.
     * * Returns: True if yes, else False.
     */
    return x_clicked;
}
void land_set_fps(int f) {
    /* """Set the frequency in Hz at which Land should tick. Default is 60."""
     */
    land_log_message("land_set_frequency %d\n", f);
    parameters->fps = f;
}
void land_skip_render(int skip) {
    parameters->skip = skip;
}
void land_set_display_parameters(int w, int h, int flags) {
    /* """Set the display parameters to use initially.
     * * w, h Width and height in pixel.
     * * flags, a combination of:
     * ** LAND_WINDOWED
     * ** LAND_FULLSCREEN
     * ** LAND_OPENGL
     * ** LAND_CLOSE_LINES
     */
    parameters->w = w;
    parameters->h = h;
    parameters->flags = flags;
}
void land_set_initial_runner(LandRunner * runner) {
    /* """Set the initial runner."""
     */
    parameters->start = runner;
}
double land_get_fps(void) {
    /* """Return the current frequency."""
     */
    return parameters->fps;
}
int land_get_current_fps(void) {
    int i = (_flip_tick_i + parameters->fps) % 256;
    return _flip_count - _flips_at_tick [i];
}
int land_get_ticks(void) {
    /* """Return the number of ticks Land has executed."""
     */
    return _land_frames;
}
int land_get_flips(void) {
    return _flip_count;
}
double land_get_time(void) {
    /* """Get the time in seconds since Land has started."""
     */
    return platform_get_time();
}
void land_pause(void) {
    /* """Stop time. The tick function of the current runner will not be
     * called any longer and [land_get_ticks] will not advance until the
     * next call to [land_unpause].
     */
    platform_pause();
}
void land_unpause(void) {
    platform_unpause();
}
int land_get_flags(void) {
    return parameters->flags;
}
void land_set_synchronized(bool onoff) {
    _land_synchronized = onoff;
}
void land_maximize_fps(bool onoff) {
    _maximize_fps = onoff;
}
void land_mainloop_prepare(void) {
    land_exit_function(land_exit);
    land_display_init();
    land_font_init();
    land_image_init();
    land_grid_init();
}
void land_mainloop(void) {
    /* """Run Land. This function will use all the parameters set before to
     * initialize everything, then run the initial runner. It will return when
     * you call land_quit() inside the tick function of the active runner.
     */
    land_log_message("land_mainloop\n");
    land_mainloop_prepare();
    LandDisplay * display = land_display_new(parameters->w, parameters->h, parameters->flags);
    land_log_message("About to create the main window.\n");
    land_display_set();
    land_log_message("Video initialized.\n");
    land_sound_init();
    land_log_message("Audio initialized.\n");
    land_mouse_init();
    land_log_message("Mouse initialized.\n");
    land_keyboard_init();
    land_log_message("Keyboard initialized.\n");
    land_runner_switch_active(parameters->start);
    land_log_message("Commencing operations.\n");
    platform_mainloop(parameters);
    land_log_message("Ceasing operations.\n");
    land_runner_switch_active(NULL);
    land_runner_destroy_all();
    land_display_destroy(display);
    land_sound_exit();
    land_grid_exit();
    land_font_exit();
    land_image_exit();
    land_display_exit();
    land_exit_functions();
    land_log_message("exit\n");
}
#ifdef LAND_MEMLOG
#undef land_list_new
#undef land_list_destroy
#undef land_add_list_data
LandList* land_list_new_memlog(char const * f, int l) {
    LandList * list = land_list_new();
    land_memory_add(list, "list", 1, f, l);
    return list;
}
void land_list_destroy_memlog(LandList * self, char const * f, int l) {
    land_memory_remove(self, "list", 1, f, l);
    land_list_destroy(self);
}
void land_add_list_data_memlog(LandList * (* list), void * data, char const * f, int l) {
    if (* list) {
        land_memory_remove(* list, "list", 1, f, l);
        land_add_list_data(list, data);
        land_memory_add(* list, "list", 1, f, l);
    }
    else {
        * list = land_list_new_memlog(f, l);
        land_add_list_data(list, data);
    }
}
#endif
LandListIterator LandListIterator_first(LandList * a) {
    LandListIterator i = {a->first};
    return i;
}
void* LandListIterator_item(LandList * a, LandListIterator * i) {
    return i->i ? i->i->data : NULL;
}
bool LandListIterator_next(LandList * a, LandListIterator * i) {
    if (i->i) {
        i->i = i->i->next;
        return 1;
    }
    return 0;
}
LandList* land_list_new(void) {
    LandList * self;
    land_alloc(self);
    return self;
}
void land_list_clear(LandList * list) {
    LandListItem * item = list->first;
    while (item) {
        LandListItem * next = item->next;
        land_listitem_destroy(item);
        item = next;
    }
    list->first = NULL;
    list->last = NULL;
    list->count = 0;
}
void land_list_destroy(LandList * list) {
    land_list_clear(list);
    land_free(list);
}
LandListItem* land_listitem_new(void * data) {
    LandListItem * self;
    land_alloc(self);
    self->data = data;
    return self;
}
void land_listitem_destroy(LandListItem * self) {
    land_free(self);
}
void land_list_insert_item(LandList * list, LandListItem * item) {
    item->next = NULL;
    item->prev = list->last;
    if (list->last) {
        list->last->next = item;
    }
    else {
        list->first = item;
    }
    list->last = item;
    list->count++;
}
void land_list_insert_item_before(LandList * list, LandListItem * insert, LandListItem * before) {
    if (before) {
        insert->next = before;
        insert->prev = before->prev;
        if (before->prev) {
            before->prev->next = insert;
        }
        else {
            list->first = insert;
        }
        before->prev = insert;
        list->count++;
    }
    else {
        land_list_insert_item(list, insert);
    }
}
void land_list_remove_item(LandList * list, LandListItem * item) {
    if (item->prev) {
        item->prev->next = item->next;
    }
    else {
        list->first = item->next;
    }
    if (item->next) {
        item->next->prev = item->prev;
    }
    else {
        list->last = item->prev;
    }
    list->count--;
}
void land_add_list_data(LandList * (* list), void * data) {
    LandListItem * item = land_listitem_new(data);
    if (! (* list)) {
        * list = land_list_new();
    }
    land_list_insert_item(* list, item);
}
void land_remove_list_data(LandList * (* list), void * data) {
    LandListItem * item = (* list)->first;
    while (item) {
        LandListItem * next = item->next;
        if (item->data == data) {
            land_list_remove_item(* list, item);
            land_listitem_destroy(item);
            return ;
        }
        item = next;
    }
}
void land_list_add(LandList * l, void * data) {
    LandListItem * item = land_listitem_new(data);
    land_list_insert_item(l, item);
}
LandQueue* land_queue_new(int(* cmp_cb)(void * data1, void * data2)) {
    /* Create a new queue, with the given comparison function for its elements.
     */
    LandQueue * self;
    land_alloc(self);
    self->array.data = NULL;
    self->cmp_cb = cmp_cb;
    return self;
}
void land_queue_del(LandQueue * q) {
    /* Delete the queue. This will not touch the elements that might have been
     * added since its creation.
     */
    land_free(q->array.data);
    land_free(q);
}
void land_queue_destroy(LandQueue * q) {
    land_queue_del(q);
}
void land_queue_add(LandQueue * q, void * data) {
    /* Add an element to the queue.
     */
    int i = q->array.count;
    land_array_add(& q->array, data);
    while (i > 0) {
        int parent = (i - 1) / 2;
        if (q->cmp_cb(q->array.data [parent], q->array.data [i]) <= 0) {
            break;
        }
        void * temp = q->array.data [parent];
        q->array.data [parent] = q->array.data [i];
        q->array.data [i] = temp;
        i = parent;
    }
}
void* land_queue_pop(LandQueue * q) {
    /* Return and remove the smallest element in the queue.
     */
    if (q->array.count == 0) {
        return NULL;
    }
    void * data = q->array.data [0];
    q->array.data [0] = q->array.data [q->array.count - 1];
    land_array_pop(& q->array);
    int i = 0;
    while (1) {
        int child1 = i * 2 + 1;
        int child2 = i * 2 + 2;
        if ((child1 >= q->array.count || q->cmp_cb(q->array.data [child1], q->array.data [i]) >= 0) && (child2 >= q->array.count || q->cmp_cb(q->array.data [child2], q->array.data [i]) >= 0)) {
            break;
        }
        if (child2 >= q->array.count || (child1 < q->array.count && q->cmp_cb(q->array.data [child1], q->array.data [child2]) < 0)) {
            void * temp = q->array.data [i];
            q->array.data [i] = q->array.data [child1];
            q->array.data [child1] = temp;
            i = child1;
        }
        else {
            void * temp = q->array.data [i];
            q->array.data [i] = q->array.data [child2];
            q->array.data [child2] = temp;
            i = child2;
        }
    }
    return data;
}
LandArray* land_queue_sort(LandQueue * q) {
    /* Return an array referencing the same data as the queue. The array will be
     * sorted from smallest to largest element. The queue will be destroyed in
     * the process. So you should set the parameter you passed to this function to
     * None after it returns.
     */
    LandArray * a = land_array_new();
    while (1) {
        void * data = land_queue_pop(q);
        if (! data) {
            break;
        }
        land_array_add(a, data);
    }
    land_queue_del(q);
    return a;
}
int land_queue_for_each(LandQueue * self, int(* cb)(void * item, void * data), void * data) {
    /* Like land_array_for_each. The callback will not be called in any particular
     * order, especially it will *not* be sorted. (The first call will be the
     * smallest element, but the subsequent order is random.)
     */
    return land_array_for_each(& self->array, cb, data);
}
int land_queue_count(LandQueue * self) {
    return self->array.count;
}
void land_queue_clear(LandQueue * self) {
    land_array_clear(& self->array);
}
bool land_queue_is_empty(LandQueue * self) {
    return land_array_is_empty(& self->array);
}
void land_layer_draw(LandLayer * self, LandView * view) {
    if (self->hidden) {
        return ;
    }
    LandView v = * view;
    v.scroll_x += self->view_x - self->x;
    v.scroll_y += self->view_y - self->y;
    v.scroll_x *= self->scrolling_x / self->scale_x;
    v.scroll_y *= self->scrolling_y / self->scale_y;
    v.scale_x *= self->scale_x;
    v.scale_y *= self->scale_y;
    v.x += self->view_x;
    v.y += self->view_y;
    v.w += self->view_w;
    v.h += self->view_h;
    v.r *= self->r;
    v.g *= self->g;
    v.b *= self->b;
    v.a *= self->a;
    if (self->grid) {
        land_grid_draw(self->grid, & v);
    }
}
void land_layer_draw_grid(LandLayer * self, LandView * view) {
    /* Draws a debug grid using the layer's cell size.
     */
    LandGrid * grid = self->grid;
    LandView v = * view;
    v.scroll_x += self->view_x - self->x;
    v.scroll_y += self->view_y - self->y;
    v.scroll_x *= self->scrolling_x;
    v.scroll_y *= self->scrolling_y;
    v.scale_x *= self->scale_x;
    v.scale_y *= self->scale_y;
    v.x += self->view_x;
    v.y += self->view_y;
    v.w += self->view_w;
    v.h += self->view_h;
    view = & v;
    land_color(0, 0, 1, 0.5);
    float cx = view->scroll_x / grid->cell_w;
    float cy = view->scroll_y / grid->cell_h;
    float ox = floor(cx) - cx;
    float oy = floor(cy) - cy;
    float sx = grid->cell_w * view->scale_x;
    float sy = grid->cell_h * view->scale_y;
    float min_x = view->x + ox * sx + 0.5;
    float min_y = view->y + oy * sy + 0.5;
    if (cx < 0) {
        min_x -= floor(cx) * sx;
    }
    if (cy < 0) {
        min_y -= floor(cy) * sy;
    }
    float max_x = view->x + sx * (grid->x_cells - cx) + 1;
    float max_y = view->y + sy * (grid->y_cells - cy) + 1;
    float vy1 = view->y;
    float vy2 = view->y + view->h;
    if (vy1 < min_y) {
        vy1 = min_y;
    }
    if (vy2 > max_y) {
        vy2 = max_y;
    }
    float vx1 = view->x;
    float vx2 = view->x + view->w;
    if (vx1 < min_x) {
        vx1 = min_x;
    }
    if (vx2 > max_x) {
        vx2 = max_x;
    }
    for (float x = min_x; x < view->x + view->w; x += sx) {
        if (x > max_x) {
            break;
        }
        land_line(x, vy1, x, vy2);
    }
    for (float y = min_y; y < view->y + view->h; y += sy) {
        if (y > max_y) {
            break;
        }
        land_line(vx1, y, vx2, y);
    }
}
void land_layer_set_name(LandLayer * self, char const * name) {
    if (self->name) {
        land_free(self->name);
    }
    self->name = land_strdup(name);
}
void land_layer_del(LandLayer * self) {
    land_grid_del(self->grid);
    if (self->name) {
        land_free(self->name);
    }
    land_free(self);
}
LandLayer* land_layer_new_with_grid(LandGrid * grid) {
    LandLayer * self;
    land_alloc(self);
    self->scrolling_x = 1;
    self->scrolling_y = 1;
    self->scale_x = 1;
    self->scale_y = 1;
    self->grid = grid;
    self->r = 1;
    self->g = 1;
    self->b = 1;
    self->a = 1;
    return self;
}
LandLayer* land_layer_new(void) {
    return land_layer_new_with_grid(NULL);
}
void land_layer_set_scroll_speed(LandLayer * self, float x, float y) {
    self->scrolling_x = x;
    self->scrolling_y = y;
}
void land_layer_set_scale(LandLayer * self, float x, float y) {
    self->scale_x = x;
    self->scale_y = y;
}
void land_layer_set_position(LandLayer * self, float x, float y) {
    self->x = x;
    self->y = y;
}
void land_layer_set_grid(LandLayer * self, LandGrid * grid) {
    self->grid = grid;
}
void land_layer_hide(LandLayer * self) {
    self->hidden = true;
}
void land_layer_unhide(LandLayer * self) {
    self->hidden = false;
}
static int active;
static LandStream * _default;
LandSound* land_sound_load(char const * filename) {
    char * path = land_path_with_prefix(filename);
    LandSound * sound = platform_sound_load(path);
    land_free(path);
    return sound;
}
LandSound* land_sound_new(int samples, float frequency, int bits, int channels) {
    LandSound * sound = platform_sound_new(samples, frequency, bits, channels);
    return sound;
}
void* land_sound_sample_pointer(LandSound * self) {
    return platform_sound_sample_pointer(self);
}
int land_sound_length(LandSound * self) {
    return platform_sound_length(self);
}
double land_sound_seconds(LandSound * self) {
    return platform_sound_seconds(self);
}
void land_sound_play(LandSound * s, float volume, float pan, float frequency) {
    if (! s) {
        return ;
    }
    platform_sound_play(s, volume, pan, frequency, false);
}
void land_sound_change(LandSound * s, float volume, float pan, float frequency) {
    if (! s) {
        return ;
    }
    platform_sound_change(s, volume, pan, frequency);
}
void land_sound_loop(LandSound * s, float volume, float pan, float frequency) {
    if (! s) {
        return ;
    }
    platform_sound_play(s, volume, pan, frequency, true);
}
void land_sound_stop(LandSound * s) {
    if (! s) {
        return ;
    }
    platform_sound_stop(s);
}
void land_sound_destroy(LandSound * s) {
    if (! s) {
        return ;
    }
    platform_sound_destroy(s);
}
void land_sound_init(void) {
    platform_sound_init();
    active = 1;
}
void land_sound_exit(void) {
    platform_sound_exit();
    active = 0;
}
LandStream* land_stream_new(int samples, int fragments, float frequency, int bits, int channels) {
    return platform_stream_new(samples, fragments, frequency, bits, channels);
}
void land_stream_destroy(LandStream * self) {
    land_free(self->filename);
    platform_stream_destroy(self);
}
void* land_stream_buffer(LandStream * self) {
    return platform_stream_buffer(self);
}
void land_stream_fill(LandStream * self) {
    platform_stream_fill(self);
}
void land_stream_music(LandStream * self, char const * filename) {
    self->filename = land_path_with_prefix(filename);
    platform_stream_music(self, self->filename, true);
}
void land_stream_music_once(LandStream * self, char const * filename) {
    self->filename = land_path_with_prefix(filename);
    platform_stream_music(self, self->filename, false);
}
void land_stream_volume(LandStream * self, float volume) {
    platform_stream_volume(self, volume);
}
bool land_stream_is_playing(LandStream * self) {
    return platform_stream_is_playing(self);
}
void land_stream_set_playing(LandStream * self, bool onoff) {
    platform_stream_set_playing(self, onoff);
}
LandStream* land_stream_default(void) {
    if (_default) {
        return _default;
    }
    _default = land_stream_new(2048, 4, 22050, 16, 2);
    return _default;
}
    /* ![Land!](logo.png)
     * ## the name
     * It has no special meaning, it's just that in computer games, you make
     * virtual worlds or lands - and that inspired it as use as name for this.
     * The only limits of this land should be your imagination, not programming
     * language obstacles. But if you insist, it could also be a recursive
     * acronym for "Land All New Design".
     * ## history
     * Well, I really started working on this version only some days ago. But I
     * made a library called "land", with the very same goals, about 10-20 years
     * ago. I actually recovered some files of that, but they require a program
     * called TASM to work. I actually found a copy of that, and tried to
     * compile it in dosbox, but still, it wouldn't work. Not that the result
     * would have been interesting for anyone but me :)
     * ## what it is
     * Land is, currently, just a simple framework to assist in creating games,
     * which will work under Windows, Linux and OSX. As well as some others,
     * basically everything Allegro/SDL/OpenGL can get to run (currently only
     * Allegro). It doesn't do a lot, just handle a basic game loop for you.
     * Some may not want this, since it takes control away. But for beginners,
     * it may make things somewhat simpler. And especially, and that's the only
     * thing I care about, for me.
     * ## features/limitations
     * - Written in C, preprocessed by a Python-like syntax.
     * - Automated build process using scons.
     * - Load images as single files, from directories, from .zip files, from
     * fixed-grid/transparent/color-keyed sheets.
     * - Free-form multi-layer tilemaps. The layers use no fixed tile-layout,
     * you can place there what and where you want. (Of course this includes
     * classic tiles.)
     * - Pixel-perfect collision between tilemap-sprite and sprite-sprite,
     * efficient for 1000ds of objects and huge maps. (The algorithm is to
     * first check a grid-cash for proximity, then do a bounding-box check,
     * then pixel-perfect with pre-generated bit-masks.)
     * - Parallax scrolling with arbitrary amount of layers. Define some of the
     * tilemap layers to be parallax layers - scrolling is handled by Land.
     * ## source code
     * Normally, the split into .c/.cpp and .h files is not a problem, either you
     * work out the API first in he .h and then implement, or write first the
     * implementation then derive the .h. But in two cases it is very bad: Designing
     * a new library, and maintaining a library. In the former, it means you need
     * to make every interface change at multiple locations. In the latter case it
     * means, you end up with en entangled mess of headers all over the place.
     * Therefore, in Land, I ended up using preprocessed files from which .c and .h
     * files are auto-generated. That way, the source always stays clean and
     * managable. Looking at the changes of the build process would show how hard it
     * was to end up at the current system (but a lot of that happened before the code
     * was stable enough to move to SVN).
     * ## inheritance and polymorphism
     * What does the technical inplementation of Land look like, given it is
     * implemented in C? Well, polymorphism is done by using VTables, similar to e.g.
     * the Allegro drivers. Inheritance is done by manual aggregation (along with
     * VTables).
     * For example, let's say, you want to use a tilemap, but have your own drawing
     * function called for each tile. Simple create a LandGridInterface object,
     * replace the ->draw_cell method with your own, and replace the ->vt member of your
     * LandGrid object with your own LandGridInterface.
     * TODO: Maybe a macro, something like:
     * LandGridInterface *my_grid_vtable =
     * land_override(land_grid_vtable_normal, cell_draw, my_cell_draw);
     * ## user data
     * Instead of inheriting your own types, it is much easier to simply attach data to
     * land types. For example:
     * int index = land_attach_data(sprite, "mydata", mydata);
     * or
     * int index = land_attach_data(sprite, NULL, mydata);
     * In both cases, you can retrieve the data with:
     * mydata = land_retrieve_data(sprite, index);
     * In the first case, also with:
     * mydata = land_retrieve_named_data(sprite, "mydata")
     * ## containers
     * LandList - a doubly linked list of items. Fast insertion and deletion of
     * items.
     * LandArray - a dynamically growing array. The number of used and allocated
     * elements can differ, so can allocate items in advance, or not de-allocate
     * items in case more are added shortly.
     * LandQueue - like an array but the items are always kept sorted. Useful
     * for something like a priority queue or if you want to (heap-)sort some
     * other container.
     * LandHash - a mapping of strings to the data. Useful if there are many strings
     * to look up, in which case this is faster than looping through a list/array.
     * ## maps, layers, tiles, sprites..
     * One question still is.. what to do about maximum sprite size? Two brute force
     * approaches:
     * - render as much overlap cells that the biggest sprite would be catched
     * This leaves the solution very high-level.. simply draw a bigger area.
     * Drawback is possibly drawing more than necessary most of the time.
     * - add a sprite to every cell it covers.. this is somewhat more complicated,
     * but can have other advantages as well, like easy collision detection
     * Another solution would be to have a maximum size of the cell size in each
     * layer - then simply can group large sprites into a layer with a big enough
     * cell size. This also would deal with collision detection - a sprite simply can
     * never be outside of its cell and the adjacent ones.
     * ## graphics primitives
     * You can directly use all of Allegro's API, as well as OpenGL. Additionally, with
     * the time, Land got it's own graphics primitives:
     * land_color(r, g, b) Sets the color
     * land_transparency(a) Sets transparency
     * land_thickness(t) Sets thickness of lines/pixels/rectangles
     * land_line(x, y, x_, y_) Like the one in Allegro
     * land_move_to(x, y) Sets the cursor position
     * land_line_to(x, y) Draws from the current position towards x/y, but not on x/y
     * itself, and sets the cursor to x/y.
     * land_line_end(x, y) Like land_line_to, but doesn't change the cursor, and also
     * draws on x/y.
     * land_pixel(x, y) Draws a single pixel.
     * land_clip(x, y, w, h)
     * How can you draw not to the screen, but into an image?
     * land_target(image)
     * So far, the state maintained by a LandDisplay thus is:
     * - color_r, color_g, color_b, color_a
     * - thickness
     * - font
     * - text_x, text_y
     * - target
     * - clip_x, clip_y, clip_w, clip_h
     * ## The land song
     * + lalalala-land
     * + Land is "Land All New Design"
     * + so new so shiny so well designed
     * + lalalala-land
     * + Land in sight!
     * + lalalala-land
     * + lalalalalala-land
     * + lal-land
     * (this chapter is all the progress I made when I tried to work on it drunk)
     */
static str _version = "1.0.0";
char const* land_version(void) {
    return _version;
}
static LandArray * exit_functions;
static int _exitcode;
void land_without_main(void(* cb)(void)) {
    platform_without_main(cb);
}
void land_set_exitcode(int code) {
    _exitcode = code;
}
int land_get_exitcode(void) {
    return _exitcode;
}
void land_exit_function(void(* function)(void)) {
    land_array_add_data(& exit_functions, function);
}
void land_exit_functions(void) {
    int i, n = land_array_count(exit_functions);
    for (i = n - 1; i >= 0; i--) {
        void(* function)(void) = land_array_get_nth(exit_functions, i);
        function();
    }
    land_array_destroy(exit_functions);
}
void land_wait(double seconds) {
    platform_wait(seconds);
}
void land_callbacks(VoidFunction * init, VoidFunction * tick, VoidFunction * draw, VoidFunction * done) {
    shortcut_runner = land_runner_new("shortcut", (void *) init, NULL, (void *) tick, (void *) draw, NULL, (void *) done);
    land_runner_register(shortcut_runner);
    land_set_initial_runner(shortcut_runner);
}
int land_argc;
char * (* land_argv);
LandRunner * shortcut_runner;
char* land_get(str what) {
    return platform_get(what);
}
static LandDisplay * global_previous_display;
static LandDisplayPlatform global_image_display;
static ALLEGRO_BITMAP * previous;
#define SELF \
    LandImagePlatform * self = (void *) super;
LandImage* platform_new_image(void) {
    LandImagePlatform * self;
    land_alloc(self);
    return (void *) self;
}
void platform_del_image(LandImage * super) {
    SELF;
    if (self->a5) {
        al_destroy_bitmap(self->a5);
    }
    land_free(self);
}
void platform_image_empty(LandImage * super) {
    SELF;
    if (! self->a5) {
        if (super->flags & LAND_IMAGE_DEPTH) {
            al_set_new_bitmap_depth(16);
        }
        self->a5 = al_create_bitmap(super->width, super->height);
        if (super->flags & LAND_IMAGE_DEPTH) {
            al_set_new_bitmap_depth(0);
        }
    }
    int f = al_get_bitmap_format(self->a5);
    ALLEGRO_LOCKED_REGION * lock = al_lock_bitmap(self->a5, f, ALLEGRO_LOCK_WRITEONLY);
    int rowbytes = al_get_pixel_size(f) * super->width;
    for (int i = 0; i < super->height; i++) {
        memset(lock->data + lock->pitch * i, 0, rowbytes);
    }
    al_unlock_bitmap(self->a5);
}
LandImage* platform_image_load(char const * filename, bool mem) {
    LandImage * super = land_display_new_image();
    super->filename = land_strdup(filename);
    if (mem) {
        super->flags |= LAND_IMAGE_MEMORY;
    }
    _platform_load(super);
    return super;
}
static void _platform_load(LandImage * super) {
    super->name = land_strdup(super->filename);
    ALLEGRO_STATE state;
    if (super->flags & LAND_IMAGE_MEMORY) {
        al_store_state(& state, ALLEGRO_STATE_NEW_BITMAP_PARAMETERS);
        al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP);
    }
    #ifdef ANDROID
    land_log_message("open %s", super->filename);
    #endif
    ALLEGRO_BITMAP * bmp = al_load_bitmap(super->filename);
    if (bmp) {
        LandImagePlatform * self = (void *) super;
        if (super->flags & LAND_LOADING) {
            self->memory = bmp;
        }
        else {
            self->a5 = bmp;
        }
        super->width = al_get_bitmap_width(bmp);
        super->height = al_get_bitmap_height(bmp);
        super->flags |= LAND_LOADED;
    }
    else {
        super->flags |= LAND_FAILED;
    }
    if (super->flags & LAND_IMAGE_MEMORY) {
        al_restore_state(& state);
    }
}
void platform_image_preload_memory(LandImage * super) {
    super->flags |= LAND_IMAGE_MEMORY;
    _platform_load(super);
    super->flags &= ~ LAND_IMAGE_MEMORY;
}
bool platform_image_exists(LandImage * super) {
    return al_filename_exists(super->filename);
}
void platform_image_load_on_demand(LandImage * super) {
    LandImagePlatform * self = (void *) super;
    if (self->a5) {
        return ;
    }
    _platform_load(super);
}
LandImage* platform_image_sub(LandImage * parent, float x, float y, float w, float h) {
    LandImage * super = land_display_new_image();
    super->flags |= LAND_SUBIMAGE;
    super->filename = parent->filename;
    super->name = parent->name;
    LandImagePlatform * self = (void *) super;
    LandImagePlatform * parentself = (void *) parent;
    self->a5 = al_create_sub_bitmap(parentself->a5, x, y, w, h);
    super->width = al_get_bitmap_width(self->a5);
    super->height = al_get_bitmap_height(self->a5);
    return super;
}
void platform_image_save(LandImage * super, char const * filename) {
    LandImagePlatform * self = (void *) super;
    al_save_bitmap(filename, self->a5);
}
void platform_image_draw_scaled_rotated_tinted_flipped(LandImage * super, float x, float y, float sx, float sy, float angle, float r, float g, float b, float alpha, int flip) {
    SELF;
    LandDisplay * d = _land_active_display;
    ALLEGRO_STATE state;
    if (super->flags & LAND_LOADING) {
        return ;
    }
    if (super->flags & LAND_LOADING_COMPLETE) {
        land_image_load_async(super);
    }
    if (! self->a5) {
        return ;
    }
    land_a5_display_check_transform();
    bool restore = 0;
    if (d->blend) {
        if (d->blend & LAND_BLEND_SOLID) {
            al_store_state(& state, ALLEGRO_STATE_BLENDER);
            al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ZERO);
            restore = 1;
        }
        if (d->blend & LAND_BLEND_ADD) {
            al_store_state(& state, ALLEGRO_STATE_BLENDER);
            al_set_blender(ALLEGRO_ADD, ALLEGRO_ALPHA, ALLEGRO_ONE);
            restore = 1;
        }
    }
    int flags = 0;
    if (flip == 1 || flip == 3) {
        flags |= ALLEGRO_FLIP_HORIZONTAL;
    }
    if (flip == 2 || flip == 3) {
        flags |= ALLEGRO_FLIP_VERTICAL;
    }
    ALLEGRO_COLOR tint = al_map_rgba_f(r, g, b, alpha);
    al_draw_tinted_scaled_rotated_bitmap_region(self->a5, super->l, super->t, super->width - super->l - super->r, super->height - super->t - super->b, tint, super->x - super->l, super->y - super->t, x, y, sx, sy, - angle, flags);
    if (restore) {
        al_restore_state(& state);
    }
}
void platform_set_image_display(LandImage * super) {
    SELF;
    global_previous_display = _land_active_display;
    LandDisplayPlatform * prev = (void *) global_previous_display;
    LandDisplay * d = (void *) & global_image_display;
    _land_active_display = d;
    if (prev) {
        global_image_display.a5 = prev->a5;
    }
    global_image_display.c = al_map_rgb_f(1, 1, 1);
    d->w = super->width;
    d->h = super->height;
    d->flags = 0;
    d->color_r = 1;
    d->color_g = 1;
    d->color_b = 1;
    d->color_a = 1;
    d->blend = 0;
    d->clip_off = 0;
    d->clip_x1 = 0;
    d->clip_y1 = 0;
    d->clip_x2 = super->width;
    d->clip_y2 = super->height;
    previous = al_get_target_bitmap();
    al_set_target_bitmap(self->a5);
}
void platform_unset_image_display(void) {
    _land_active_display = global_previous_display;
    al_set_target_bitmap(previous);
}
void platform_image_grab_into(LandImage * super, float x, float y, float tx, float ty, float tw, float th) {
    SELF;
    ALLEGRO_STATE state;
    al_store_state(& state, ALLEGRO_STATE_TARGET_BITMAP);
    ALLEGRO_BITMAP * from = al_get_target_bitmap();
    al_set_target_bitmap(self->a5);
    al_draw_bitmap_region(from, x, y, tw, th, tx, ty, 0);
    al_restore_state(& state);
}
void platform_image_get_rgba_data(LandImage * super, unsigned char * rgba) {
    SELF;
    int w = super->width;
    int h = super->height;
    ALLEGRO_BITMAP * a5 = self->a5;
    if (self->memory) {
        a5 = self->memory;
    }
    unsigned char * p = rgba;
    ALLEGRO_LOCKED_REGION * lock;
    lock = al_lock_bitmap(a5, ALLEGRO_PIXEL_FORMAT_ABGR_8888_LE, ALLEGRO_LOCK_READONLY);
    unsigned char * p2 = lock->data;
    for (int y = 0; y < h; y++) {
        unsigned char * p3 = p2;
        for (int x = 0; x < w; x++) {
            unsigned char r, g, b, a;
            r = * (p3++);
            g = * (p3++);
            b = * (p3++);
            a = * (p3++);
            * (p++) = r;
            * (p++) = g;
            * (p++) = b;
            * (p++) = a;
        }
        p2 += lock->pitch;
    }
    al_unlock_bitmap(a5);
}
void platform_image_set_rgba_data(LandImage * super, unsigned char const * rgba) {
    SELF;
    int w = super->width;
    int h = super->height;
    unsigned char const * p = rgba;
    ALLEGRO_LOCKED_REGION * lock;
    lock = al_lock_bitmap(self->a5, ALLEGRO_PIXEL_FORMAT_ABGR_8888_LE, ALLEGRO_LOCK_WRITEONLY);
    unsigned char * p2 = lock->data;
    for (int y = 0; y < h; y++) {
        unsigned char * p3 = p2;
        for (int x = 0; x < w; x++) {
            int r, g, b, a;
            r = * (p++);
            g = * (p++);
            b = * (p++);
            a = * (p++);
            * (p3++) = r;
            * (p3++) = g;
            * (p3++) = b;
            * (p3++) = a;
        }
        p2 += lock->pitch;
    }
    al_unlock_bitmap(self->a5);
}
int platform_image_opengl_texture(LandImage * super) {
    SELF;
    return al_get_opengl_texture(self->a5);
}
void platform_image_crop(LandImage * super, int x, int y, int w, int h) {
    SELF;
    ALLEGRO_STATE state;
    if (x == 0 && y == 0 && w == super->width && h == super->height) {
        return ;
    }
    al_store_state(& state, ALLEGRO_STATE_TARGET_BITMAP | ALLEGRO_STATE_BLENDER);
    ALLEGRO_BITMAP * cropped = al_create_bitmap(w, h);
    if (self->a5) {
        ALLEGRO_LOCKED_REGION * rfrom = al_lock_bitmap_region(self->a5, x, y, w, h, ALLEGRO_PIXEL_FORMAT_ABGR_8888_LE, ALLEGRO_LOCK_READONLY);
        ALLEGRO_LOCKED_REGION * rto = al_lock_bitmap(cropped, ALLEGRO_PIXEL_FORMAT_ABGR_8888_LE, ALLEGRO_LOCK_WRITEONLY);
        for (int i = 0; i < h; i += 1) {
            uint8_t * pfrom = rfrom->data, * pto = rto->data;
            pfrom += rfrom->pitch * i;
            pto += rto->pitch * i;
            memcpy(pto, pfrom, 4 * w);
        }
        al_unlock_bitmap(self->a5);
        al_unlock_bitmap(cropped);
        al_destroy_bitmap(self->a5);
    }
    self->a5 = cropped;
    al_restore_state(& state);
    super->width = w;
    super->height = h;
}
void platform_image_merge(LandImage * super, LandImage * replacement_image) {
    SELF;
    LandImagePlatform * replacement = (void *) replacement_image;
    al_destroy_bitmap(self->a5);
    self->a5 = replacement->a5;
    replacement->a5 = NULL;
    super->width = replacement_image->width;
    super->height = replacement_image->height;
    super->l = 0;
    super->t = 0;
    super->r = 0;
    super->b = 0;
    land_image_destroy(replacement_image);
}
void platform_image_transfer_from_memory(LandImage * super) {
    SELF;
    if (! self->memory) {
        return ;
    }
    self->a5 = al_clone_bitmap(self->memory);
    land_log_message("platform_image_transfer %s: %s -> %s\n", super->name, al_get_bitmap_flags(self->memory) & ALLEGRO_MEMORY_BITMAP ? "mem" : "vid", al_get_bitmap_flags(self->a5) & ALLEGRO_MEMORY_BITMAP ? "mem" : "vid");
    al_destroy_bitmap(self->memory);
    self->memory = NULL;
}
#undef SELF
struct LandAtlasSprite {
    int x, y, w, h, ox, oy;
    LandAtlasSheet * sheet;
};
struct LandAtlasSheet {
    char * filename;
    int id;
    LandImage * image;
};
LandAtlas* land_atlas_new(char const * filename) {
    LandAtlas * self;
    land_alloc(self);
    self->sheets = land_array_new();
    self->sprites = land_hash_new();
    self->filename = land_strdup(filename);
    land_atlas_load_all(self);
    return self;
}
void land_atlas_destroy(LandAtlas * self) {
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(self->sheets);
        for (LandAtlasSheet * sheet = LandArrayIterator_item(self->sheets, &__iter0__); LandArrayIterator_next(self->sheets, &__iter0__); sheet = LandArrayIterator_item(self->sheets, &__iter0__)) {
            land_image_destroy(sheet->image);
            land_free(sheet->filename);
            land_free(sheet);
        }
    }
    {
        LandHashIterator __iter0__ = LandHashIterator_first(self->sprites);
        for (LandAtlasSprite * s = LandHashIterator_item(self->sprites, &__iter0__); LandHashIterator_next(self->sprites, &__iter0__); s = LandHashIterator_item(self->sprites, &__iter0__)) {
            land_free(s);
        }
    }
    land_array_destroy(self->sheets);
    land_hash_destroy(self->sprites);
    land_free(self->filename);
    land_free(self);
}
static LandAtlasSheet* atlas_find_sheet(LandAtlas * self, char const * name) {
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(self->sheets);
        for (LandAtlasSheet * sheet = LandArrayIterator_item(self->sheets, &__iter0__); LandArrayIterator_next(self->sheets, &__iter0__); sheet = LandArrayIterator_item(self->sheets, &__iter0__)) {
            if (land_equals(name, sheet->filename)) {
                return sheet;
            }
        }
    }
    return NULL;
}
void land_atlas_load_all(LandAtlas * self) {
    char * text = land_read_text(self->filename);
    LandArray * a = land_split_lines(text);
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(a);
        for (char * row = LandArrayIterator_item(a, &__iter0__); LandArrayIterator_next(a, &__iter0__); row = LandArrayIterator_item(a, &__iter0__)) {
            char * path = row;
            char * colon = strchr(path, ':');
            if (! colon) {
                break;
            }
            * colon = '\0';
            char * sheet = colon + 2;
            char * space = strchr(sheet, ' ');
            * space = '\0';
            LandAtlasSprite * s;
            land_alloc(s);
            LandAtlasSheet * atlas_sheet = atlas_find_sheet(self, sheet);
            if (! atlas_sheet) {
                land_alloc(atlas_sheet);
                atlas_sheet->filename = land_strdup(sheet);
                atlas_sheet->id = land_array_count(self->sheets);
                land_array_add(self->sheets, atlas_sheet);
                char * sheet_path = land_replace_filename(self->filename, sheet);
                atlas_sheet->image = land_image_load(sheet_path);
                land_free(sheet_path);
            }
            s->sheet = atlas_sheet;
            sscanf(space + 1, " %d %d %d %d %d %d\n", & s->x, & s->y, & s->w, & s->h, & s->ox, & s->oy);
            land_hash_insert(self->sprites, path, s);
        }
    }
    land_free(text);
    land_array_destroy_with_strings(a);
}
static LandAtlasSprite* atlas_load_picture(LandAtlas * self, char const * filename) {
    return land_hash_get(self->sprites, filename);
}
LandImage* land_atlas_image_create(LandAtlas * self, char const * filename) {
    LandAtlasSprite * sprite = atlas_load_picture(self, filename);
    if (! sprite) {
        return NULL;
    }
    LandImage * image = land_image_sub(sprite->sheet->image, sprite->x, sprite->y, sprite->w, sprite->h);
    land_image_offset(image, - sprite->ox, - sprite->oy);
    return image;
}
static char * prefix;
LandFile* land_file_new(char const * path, char const * mode) {
    char * path2;
    if (mode [0] == 'r') {
        path2 = land_path_with_prefix(path);
    }
    else {
        path2 = land_strdup(path);
    }
    void * f = platform_fopen(path2, mode);
    if (! f) {
        land_log_message("Opening file %s (%s) failed.\n", path2, mode);
        land_free(path2);
        return NULL;
    }
    LandFile * self;
    land_alloc(self);
    self->path = path2;
    self->f = f;
    return self;
}
void land_file_destroy(LandFile * self) {
    platform_fclose(self->f);
    land_free(self->path);
    land_free(self);
}
int land_file_read(LandFile * self, char * buffer, int bytes) {
    return platform_fread(self->f, buffer, bytes);
}
int land_file_write(LandFile * self, char const * buffer, int bytes) {
    return platform_fwrite(self->f, buffer, bytes);
}
void land_file_add_to_buffer(LandFile * self, LandBuffer * buf) {
    while (1) {
        char kb [16384];
        size_t n = land_file_read(self, kb, 16384);
        land_buffer_add(buf, kb, n);
        if (n < 16384) {
            break;
        }
    }
}
LandArray* land_file_lines(LandFile * self) {
    /* Return the file contents as an array of strings. Once done with the
     * strings you can destroy them (and the array) with:
     * land_array_destroy_with_strings
     */
    LandBuffer * buf = land_buffer_new();
    land_file_add_to_buffer(self, buf);
    char * text = land_buffer_finish(buf);
    LandArray * lines = land_split(text, "\n");
    land_free(text);
    return lines;
}
void land_file_print(LandFile * self, char const * f, ...) {
    char s [1024];
    va_list args;
    va_start(args, f);
    vsnprintf(s, sizeof s, f, args);
    strcat(s, "\n");
    va_end(args);
    land_file_write(self, s, strlen(s));
}
void land_file_printnn(LandFile * self, char const * f, ...) {
    char s [1024];
    va_list args;
    va_start(args, f);
    vsnprintf(s, sizeof s, f, args);
    va_end(args);
    land_file_write(self, s, strlen(s));
}
int land_file_fputs(LandFile * self, char const * string) {
    int n = strlen(string);
    return land_file_write(self, string, n);
}
int land_file_getc(LandFile * self) {
    return platform_fgetc(self->f);
}
void land_file_putc(LandFile * self, int x) {
    return platform_fputc(self->f, x);
}
void land_file_ungetc(LandFile * self, int c) {
    platform_ungetc(self->f, c);
}
bool land_file_eof(LandFile * self) {
    return platform_feof(self->f) != 0;
}
void land_file_skip(LandFile * self, int n) {
    platform_fseek(self->f, n);
}
uint32_t land_file_get32le(LandFile * self) {
    uint32_t a = platform_fgetc(self->f);
    uint32_t b = platform_fgetc(self->f);
    uint32_t c = platform_fgetc(self->f);
    uint32_t d = platform_fgetc(self->f);
    return a | (b << 8) | (c << 16) | (d << 24);
}
void land_file_put32le(LandFile * self, uint32_t x) {
    uint32_t a = x & 255;
    uint32_t b = (x >> 8) & 255;
    uint32_t c = (x >> 16) & 255;
    uint32_t d = x >> 24;
    platform_fputc(self->f, a);
    platform_fputc(self->f, b);
    platform_fputc(self->f, c);
    platform_fputc(self->f, d);
}
uint16_t land_file_get16le(LandFile * self) {
    uint16_t a = platform_fgetc(self->f);
    uint16_t b = platform_fgetc(self->f);
    return a | (b << 8);
}
uint32_t land_file_get32be(LandFile * self) {
    uint32_t a = platform_fgetc(self->f);
    uint32_t b = platform_fgetc(self->f);
    uint32_t c = platform_fgetc(self->f);
    uint32_t d = platform_fgetc(self->f);
    return d | (c << 8) | (b << 16) | (a << 24);
}
uint16_t land_file_get16be(LandFile * self) {
    uint16_t a = platform_fgetc(self->f);
    uint16_t b = platform_fgetc(self->f);
    return b | (a << 8);
}
bool land_file_is_dir(char const * name) {
    return platform_is_dir(name);
}
bool land_data_file_exists(char const * name) {
    char * path2 = land_path_with_prefix(name);
    bool e = platform_file_exists(path2);
    land_free(path2);
    return e;
}
bool land_file_exists(char const * name) {
    return platform_file_exists(name);
}
char* land_get_save_file(char const * appname, char const * name) {
    /* The returned string is owned by the caller and needs to be freed with
     * land_free.
     */
    return platform_get_save_file(appname, name);
}
char* land_get_current_directory(void) {
    /* Free after use.
     */
    return platform_get_current_directory();
}
char* land_get_data_path(void) {
    return platform_get_data_path();
}
char* land_path_with_prefix(char const * name) {
    /* Returns a new string with has the global data prefix prefixed to
     * the given path.
     */
    if (name && name [0] == '/') {
        return land_strdup(name);
    }
    int n = strlen(name);
    if (prefix) {
        n += strlen(prefix);
    }
    n++;
    char * r = land_malloc(n);
    if (prefix) {
        strcpy(r, prefix);
        strcat(r, name);
    }
    else {
        strcpy(r, name);
    }
    return r;
}
void land_set_prefix(char const * path) {
    if (! path) {
        land_free(prefix);
        prefix = NULL;
        land_log_message("Prefix unset.\n");
    }
    else {
        prefix = land_realloc(prefix, strlen(path) + 1);
        strcpy(prefix, path);
        land_log_message("Prefix set to %s.\n", prefix);
    }
}
void land_find_data_prefix(char const * path) {
    char s [3 * 10 + 1] = "";
    for (int i = 0; i < 10; i += 1) {
        land_set_prefix(s);
        char * p = land_path_with_prefix(path);
        if (land_file_is_dir(p)) {
            land_set_prefix(p);
            land_free(p);
            char * link = land_read_text("link");
            if (link) {
                land_strip(& link);
                p = land_path_with_prefix(link);
                land_set_prefix(p);
                land_free(p);
                land_free(link);
            }
            return ;
        }
        strcat(s, "../");
    }
    land_set_prefix(path);
}
char* land_replace_filename(char const * path, char const * name) {
    char * slash = strrchr(path, '/');
    int n = 0;
    if (slash) {
        n = slash - path;
    }
    char * result = land_malloc(n + strlen("/") + strlen(name) + 1);
    strncpy(result, path, n);
    result [n] = 0;
    if (n > 0) {
        strcat(result, "/");
    }
    strcat(result, name);
    return result;
}
bool land_file_remove(char const * path) {
    return platform_remove_file(path);
}
int64_t land_file_time(char const * path) {
    return platform_file_time(path);
}
char* land_user_data_path(char const * app, char const * path) {
    return platform_get_app_data_file(app, path);
}
#define N LAND_RANDOM_N
#define M 397
#define MATRIX_A 0x9908b0dfUL
#define UPPER_MASK 0x80000000UL
#define LOWER_MASK 0x7fffffffUL
static LandRandom default_state = {.mti = N + 1};
static void init_genrand(LandRandom * r, unsigned long s) {
    r->mt [0] = s & 0xffffffffUL;
    for (r->mti = 1; r->mti < N; r->mti++) {
        r->mt [r->mti] = (1812433253UL * (r->mt [r->mti - 1] ^ (r->mt [r->mti - 1] >> 30)) + r->mti);
        r->mt [r->mti] &= 0xffffffffUL;
    }
}
static unsigned long genrand_int32(LandRandom * r) {
    unsigned long y;
    static const unsigned long mag01 [2] = {0x0UL, MATRIX_A};
    if (r->mti >= N) {
        int kk;
        if (r->mti == N + 1) {
            init_genrand(r, 5489UL);
        }
        for (kk = 0; kk < N - M; kk++) {
            y = (r->mt [kk] & UPPER_MASK) | (r->mt [kk + 1] & LOWER_MASK);
            r->mt [kk] = r->mt [kk + M] ^ (y >> 1) ^ mag01 [y & 0x1UL];
        }
        for (; kk < N - 1; kk++) {
            y = (r->mt [kk] & UPPER_MASK) | (r->mt [kk + 1] & LOWER_MASK);
            r->mt [kk] = r->mt [kk + (M - N)] ^ (y >> 1) ^ mag01 [y & 0x1UL];
        }
        y = (r->mt [N - 1] & UPPER_MASK) | (r->mt [0] & LOWER_MASK);
        r->mt [N - 1] = r->mt [M - 1] ^ (y >> 1) ^ mag01 [y & 0x1UL];
        r->mti = 0;
    }
    y = r->mt [r->mti++];
    y ^= (y >> 11);
    y ^= (y << 7) & 0x9d2c5680UL;
    y ^= (y << 15) & 0xefc60000UL;
    y ^= (y >> 18);
    return y;
}
#define MAX_NUMBER 4294967295U
void land_seed(int seed) {
    init_genrand(& default_state, seed);
}
double land_rnd(double rmin, double rmax) {
    return land_random_f(& default_state, rmin, rmax);
}
int land_rand(int rmin, int rmax) {
    return land_random(& default_state, rmin, rmax);
}
LandRandom* land_random_new(int seed) {
    LandRandom * self;
    land_alloc(self);
    init_genrand(self, seed);
    return self;
}
void land_random_del(LandRandom * self) {
    land_free(self);
}
int land_random(LandRandom * r, int rmin, int rmax) {
    /* rmax is inclusive, so there are (rmax - rmin + 1) total values
     */
    if (rmin >= rmax) {
        return rmin;
    }
    int64_t d = rmax;
    d++;
    d -= rmin;
    return rmin + genrand_int32(r) % d;
}
double land_random_f(LandRandom * r, double rmin, double rmax) {
    /* Random value in the half-open interval [min, max[, that is min is inclusive
     * but max is exclusive.
     */
    if (rmin >= rmax) {
        return rmin;
    }
    return rmin + ((double) genrand_int32(r) / MAX_NUMBER) * (rmax - rmin);
}
void land_shuffle(int * a, int n) {
    for (int i = 0; i < n; i += 1) {
        a [i] = i;
    }
    for (int i = 0; i < n - 1; i += 1) {
        int j = land_rand(i, n - 1);
        int t = a [i];
        a [i] = a [j];
        a [j] = t;
    }
}
#undef N
#undef M
#undef MATRIX_A
#undef UPPER_MASK
#undef LOWER_MASK
#undef MAX_NUMBER
char const * land_sample_vertex_shader = "\n"
    "attribute vec4 al_pos;\n"
    "attribute vec4 al_color;\n"
    "attribute vec2 al_texcoord;\n"
    "uniform mat4 al_projview_matrix;\n"
    "uniform bool al_use_tex_matrix;\n"
    "uniform mat4 al_tex_matrix;\n"
    "varying vec4 varying_color;\n"
    "varying vec2 varying_texcoord;\n"
    "void main()\n"
    "{\n"
    "  varying_color = al_color;\n"
    "  if (al_use_tex_matrix) {\n"
    "    vec4 uv = al_tex_matrix * vec4(al_texcoord, 0, 1);\n"
    "    varying_texcoord = vec2(uv.x, uv.y);\n"
    "  }\n"
    "  else\n"
    "    varying_texcoord = al_texcoord;\n"
    "  gl_Position = al_projview_matrix * al_pos;\n"
    "}\n"
    "";
char const * land_sample_fragment_shader = "\n"
    "#ifdef GL_ES\n"
    "precision lowp float;\n"
    "#endif\n"
    "uniform sampler2D al_tex;\n"
    "uniform bool al_use_tex;\n"
    "varying vec4 varying_color;\n"
    "varying vec2 varying_texcoord;\n"
    "void main()\n"
    "{\n"
    "  vec4 c;\n"
    "  if (al_use_tex)\n"
    "    c = varying_color * texture2D(al_tex, varying_texcoord);\n"
    "  else\n"
    "    c = varying_color;\n"
    "  if (c.a < 0.01) discard;\n"
    "  else gl_FragColor = c;\n"
    "}\n"
    "";
LandShader* land_shader_new(char const * name, char const * vertex_glsl, char const * fragment_glsl) {
    LandShader * self;
    self = platform_shader_new(name, vertex_glsl, fragment_glsl);
    return self;
}
void land_shader_destroy(LandShader * self) {
    platform_shader_destroy(self);
}
void land_thread_run(void(* cb)(void * data), void * data) {
    platform_thread_run(cb, data);
}
LandThread* land_thread_new(void(* cb)(void * data), void * data) {
    return platform_thread_new(cb, data);
}
void land_thread_destroy(LandThread * t) {
    platform_thread_destroy(t);
}
void land_thread_wait_until_complete(LandThread * t) {
    platform_thread_wait_until_complete(t);
}
LandLock* land_thread_new_lock(void) {
    return platform_thread_new_lock();
}
void land_thread_delete_lock(LandLock * l) {
    return platform_thread_delete_lock(l);
}
void land_thread_lock(LandLock * l) {
    platform_thread_lock(l);
}
void land_thread_unlock(LandLock * l) {
    platform_thread_unlock(l);
}
LandLock* land_thread_new_waitable_lock(void) {
    return platform_thread_new_waitable_lock();
}
void land_thread_wait_lock(LandLock * self) {
    /* Wait for a lock to be triggered. This sleeps forever until another
     * thread calls land_thread_trigger_lock on the lock.
     */
    platform_thread_wait_lock(self);
}
void land_thread_trigger_lock(LandLock * self) {
    /* Notifies all threads waiting on the lock to be triggered and wakes
     * up exactly one of them.
     */
    platform_thread_trigger_lock(self);
}
LandGridInterface * land_grid_vtable_isometric;
LandGridInterface * land_grid_vtable_isometric_wrap;
static LandGrid* new(float cell_w1, float cell_h1, float cell_w2, float cell_h2, int x_cells, int y_cells) {
    LandGridIsometric * self;
    land_alloc(self);
    land_grid_initialize(& self->super, cell_w1 + cell_w2, cell_h1 + cell_h2, x_cells, y_cells);
    self->cell_w1 = cell_w1;
    self->cell_h1 = cell_h1;
    self->cell_w2 = cell_w2;
    self->cell_h2 = cell_h2;
    return & self->super;
}
LandGrid* land_isometric_new(float cell_w1, float cell_h1, float cell_w2, float cell_h2, int x_cells, int y_cells) {
    LandGrid * self = new(cell_w1, cell_h1, cell_w2, cell_h2, x_cells, y_cells);
    self->vt = land_grid_vtable_isometric;
    return self;
}
LandGrid* land_isometric_wrap_new(float cell_w1, float cell_h1, float cell_w2, float cell_h2, int x_cells, int y_cells) {
    LandGrid * self = new(cell_w1, cell_h1, cell_w2, cell_h2, x_cells, y_cells);
    self->vt = land_grid_vtable_isometric_wrap;
    self->wrap = true;
    return self;
}
LandGrid* land_isometric_custom_grid(float cell_w1, float cell_h1, float cell_w2, float cell_h2, int x_cells, int y_cells, bool wrap, void(* draw_cell)(LandGrid * self, LandView * view, int cell_x, int cell_y, float x, float y)) {
    LandGrid * self = new(cell_w1, cell_h1, cell_w2, cell_h2, x_cells, y_cells);
    land_alloc(self->vt);
    self->vt->draw = wrap ? land_grid_draw_isometric_wrap : land_grid_draw_isometric;
    self->vt->get_cell_at = wrap ? land_grid_pixel_to_cell_isometric_wrap : land_grid_pixel_to_cell_isometric;
    self->vt->draw_cell = draw_cell;
    self->vt->get_cell_position = wrap ? land_grid_cell_to_pixel_isometric_wrap : land_grid_cell_to_pixel_isometric;
    self->wrap = wrap;
    return self;
}
void land_grid_pixel_to_cell_isometric(LandGrid * self, LandView * view, float mx, float my, float * partial_x, float * partial_y) {
    /* Returns the grid position in cells below the specified pixel position in
     * the given view.
     */
    LandGridIsometric * iso = (void *) self;
    float x = mx;
    float y = my;
    if (view) {
        x += view->scroll_x - view->x;
        y += view->scroll_y - view->y;
    }
    float a = iso->cell_w1 * iso->cell_h2 + iso->cell_w2 * iso->cell_h1;
    * partial_x = (x * iso->cell_h1 + y * iso->cell_w1) / a;
    * partial_y = (y * iso->cell_w2 - x * iso->cell_h2) / a;
}
void land_grid_cell_to_pixel_isometric(LandGrid * self, LandView * view, float cell_x, float cell_y, float * view_x, float * view_y) {
    /* Given a cell position, return the position of the cell's origin in
     * on-screen coordinates, using the current view.
     */
    LandGridIsometric * iso = (void *) self;
    float mx = cell_x * iso->cell_w2 - cell_y * iso->cell_w1;
    float my = cell_x * iso->cell_h2 + cell_y * iso->cell_h1;
    mx = view->x + mx - view->scroll_x;
    my = view->y + my - view->scroll_y;
    * view_x = mx;
    * view_y = my;
}
void land_grid_cell_to_pixel_isometric_wrap(LandGrid * self, LandView * view, float cell_x, float cell_y, float * view_x, float * view_y) {
    /* Given a cell position, return the position of the cell's origin in
     * on-screen coordinates, using the current view.
     */
    LandGridIsometric * iso = (void *) self;
    cell_x = cell_x - floorf(cell_x / self->x_cells) * self->x_cells;
    cell_y = cell_y - floorf(cell_y / self->y_cells) * self->y_cells;
    float mx = cell_x * iso->cell_w2 - cell_y * iso->cell_w1;
    float my = cell_x * iso->cell_h2 + cell_y * iso->cell_h1;
    mx = view->x + mx - view->scroll_x;
    my = view->y + my - view->scroll_y;
    * view_x = mx;
    * view_y = my;
}
void land_grid_pixel_to_cell_isometric_wrap(LandGrid * self, LandView * view, float mx, float my, float * partial_x, float * partial_y) {
    float x, y;
    land_grid_pixel_to_cell_isometric(self, view, mx, my, & x, & y);
    * partial_x = x - floorf(x / self->x_cells) * self->x_cells;
    * partial_y = y - floorf(y / self->y_cells) * self->y_cells;
}
static void view_to_cell(LandGrid * self, float view_x, float view_y, int * cell_x, int * cell_y) {
    LandGridIsometric * iso = (void *) self;
    float x = view_x;
    float y = view_y;
    float a = iso->cell_w1 * iso->cell_h2 + iso->cell_w2 * iso->cell_h1;
    * cell_x = floor((x * iso->cell_h1 + y * iso->cell_w1) / a);
    * cell_y = floor((y * iso->cell_w2 - x * iso->cell_h2) / a);
}
static void view_to_cell_wrap(LandGrid * self, float view_x, float view_y, int * cell_x, int * cell_y) {
    int cx, cy;
    view_to_cell(self, view_x, view_y, & cx, & cy);
    cx %= self->x_cells;
    cy %= self->y_cells;
    if (cx < 0) {
        cx += self->x_cells;
    }
    if (cy < 0) {
        cy += self->y_cells;
    }
    * cell_x = cx;
    * cell_y = cy;
}
static void cell_to_view(LandGrid * self, float cell_x, float cell_y, float * view_x, float * view_y) {
    LandGridIsometric * iso = (void *) self;
    * view_x = cell_x * iso->cell_w2 - cell_y * iso->cell_w1;
    * view_y = cell_x * iso->cell_h2 + cell_y * iso->cell_h1;
}
static int find_offset(LandGrid * self, float view_x, float view_y, int * cell_x, int * cell_y, float * pixel_x, float * pixel_y) {
    LandGridIsometric * iso = (void *) self;
    float h1 = iso->cell_h1;
    float w2 = iso->cell_w2;
    float h2 = iso->cell_h2;
    float right_x = w2 * self->x_cells;
    float right_y = h2 * self->x_cells;
    float left_y = h1 * self->y_cells;
    float bottom_y = right_y + left_y;
    view_to_cell(self, view_x, view_y, cell_x, cell_y);
    if (view_y >= bottom_y) {
        return 0;
    }
    else if (view_x >= right_x) {
        return 0;
    }
    else if (view_x < 0 && view_y < 0) {
        * cell_x = 0;
        * cell_y = 0;
    }
    else if (* cell_x < 0 && view_x < 0 && view_y < left_y) {
        * cell_x = 0;
        * cell_y = view_y / h1;
    }
    else if (* cell_y >= self->y_cells) {
        * cell_x = (view_y - left_y) / h2;
        * cell_y = self->y_cells - 1;
    }
    else if (* cell_y < 0) {
        * cell_x = view_x / w2;
        * cell_y = 0;
    }
    else if (* cell_x >= self->x_cells) {
        return 0;
    }
    cell_to_view(self, * cell_x, * cell_y, pixel_x, pixel_y);
    * pixel_x -= view_x;
    * pixel_y -= view_y;
    return 1;
}
static void find_offset_wrap(LandGrid * self, float view_x, float view_y, int * cell_x, int * cell_y, float * pixel_x, float * pixel_y) {
    float vw = (self->x_cells * self->cell_w) / 2;
    float vh = (self->y_cells * self->cell_h) / 2;
    float x, y;
    view_to_cell_wrap(self, view_x, view_y, cell_x, cell_y);
    cell_to_view(self, * cell_x, * cell_y, & x, & y);
    x -= view_x;
    y -= view_y;
    * pixel_x = x - (1 + floorf(x / vw - 0.5)) * vw;
    * pixel_y = y - (1 + floorf(y / vh - 0.5)) * vh;
}
void land_grid_isometric_placeholder(LandGrid * self, LandView * view, int cell_x, int cell_y, float x, float y) {
    int x_, y_;
    int w = self->cell_w / 2;
    int h = self->cell_h / 2;
    land_color(1, 0, 0, 1);
    x_ = x + w;
    y_ = y + h;
    land_line(x, y, x_, y_);
    x = x_;
    y = y_;
    x_ = x - w;
    y_ = y + h;
    land_line(x, y, x_, y_);
    x = x_;
    y = y_;
    x_ = x - w;
    y_ = y - h;
    land_line(x, y, x_, y_);
    x = x_;
    y = y_;
    x_ = x + w;
    y_ = y - h;
    land_line(x, y, x_, y_);
}
void land_grid_draw_isometric(LandGrid * self, LandView * view) {
    LandGridIsometric * iso = (void *) self;
    float w1 = iso->cell_w1;
    float h1 = iso->cell_h1;
    float w2 = iso->cell_w2;
    float h2 = iso->cell_h2;
    int cell_x, cell_y;
    float pixel_x, pixel_y;
    float view_x = view->scroll_x;
    float view_y = view->scroll_y;
    if (! find_offset(self, view_x, view_y, & cell_x, & cell_y, & pixel_x, & pixel_y)) {
        return ;
    }
    pixel_x += view->x;
    pixel_y += view->y;
    float h = h1;
    if (h2 < h) {
        h = h2;
    }
    float upper_y = view->y;
    int row = 0;
    while (1) {
        float line_pixel_x = pixel_x;
        float line_pixel_y = pixel_y;
        int line_cell_x = cell_x;
        int line_cell_y = cell_y;
        while (1) {
            while (pixel_y + h2 > upper_y && cell_y > 0) {
                pixel_x += w1;
                pixel_y -= h1;
                cell_y--;
            }
            if (pixel_x - w1 >= view->x + view->w) {
                break;
            }
            if (pixel_y + h1 + h2 - upper_y <= h) {
                if (pixel_y >= view->y + view->h) {
                    return ;
                }
                self->vt->draw_cell(self, view, cell_x, cell_y, pixel_x, pixel_y);
            }
            pixel_x += w2;
            pixel_y += h2;
            cell_x++;
            if (cell_x >= self->x_cells) {
                break;
            }
        }
        cell_x = line_cell_x;
        cell_y = line_cell_y;
        pixel_x = line_pixel_x;
        pixel_y = line_pixel_y;
        upper_y += h;
        row++;
        if (pixel_y + h1 + h2 <= upper_y) {
            if (pixel_x + w2 - w1 > view->x && cell_y < self->y_cells - 1) {
                pixel_x -= w1;
                pixel_y += h1;
                cell_y++;
            }
            else {
                if (cell_x >= self->x_cells - 1) {
                    break;
                }
                pixel_x += w2;
                pixel_y += h2;
                cell_x++;
            }
        }
    }
}
void land_grid_draw_isometric_wrap(LandGrid * self, LandView * view) {
    float w = self->cell_w / 2;
    float h = self->cell_h / 2;
    int cell_x, cell_y;
    float pixel_x, pixel_y;
    float view_x = view->scroll_x;
    float view_y = view->scroll_y;
    find_offset_wrap(self, view_x, view_y, & cell_x, & cell_y, & pixel_x, & pixel_y);
    if (pixel_y > - h) {
        cell_y--;
        if (cell_y < 0) {
            cell_y += self->y_cells;
        }
        pixel_x += w;
        pixel_y -= h;
    }
    pixel_x += view->x;
    pixel_y += view->y;
    while (pixel_y < view->y + view->h) {
        float line_pixel_x = pixel_x;
        float line_pixel_y = pixel_y;
        int line_cell_x = cell_x;
        int line_cell_y = cell_y;
        while (pixel_x - w < view->x + view->w) {
            self->vt->draw_cell(self, view, cell_x, cell_y, pixel_x, pixel_y);
            pixel_x += w;
            pixel_y += h;
            cell_x++;
            if (cell_x >= self->x_cells) {
                cell_x -= self->x_cells;
            }
            pixel_x += w;
            pixel_y -= h;
            cell_y--;
            if (cell_y < 0) {
                cell_y += self->y_cells;
            }
        }
        cell_x = line_cell_x;
        cell_y = line_cell_y;
        pixel_x = line_pixel_x;
        pixel_y = line_pixel_y;
        if (pixel_x > view->x) {
            pixel_x -= w;
            pixel_y += h;
            cell_y++;
            if (cell_y >= self->y_cells) {
                cell_y -= self->y_cells;
            }
        }
        else {
            pixel_x += w;
            pixel_y += h;
            cell_x++;
            if (cell_x >= self->x_cells) {
                cell_x -= self->x_cells;
            }
        }
    }
}
void land_isometric_init(void) {
    land_log_message("land_isometric_init\n");
    land_alloc(land_grid_vtable_isometric);
    land_grid_vtable_isometric->draw = land_grid_draw_isometric;
    land_grid_vtable_isometric->draw_cell = land_grid_isometric_placeholder;
    land_grid_vtable_isometric->get_cell_at = land_grid_pixel_to_cell_isometric;
    land_grid_vtable_isometric->get_cell_position = land_grid_cell_to_pixel_isometric;
    land_alloc(land_grid_vtable_isometric_wrap);
    land_grid_vtable_isometric_wrap->draw = land_grid_draw_isometric_wrap;
    land_grid_vtable_isometric_wrap->draw_cell = land_grid_isometric_placeholder;
    land_grid_vtable_isometric_wrap->get_cell_at = land_grid_pixel_to_cell_isometric_wrap;
    land_grid_vtable_isometric_wrap->get_cell_position = land_grid_cell_to_pixel_isometric_wrap;
}
void land_isometric_exit(void) {
    land_free(land_grid_vtable_isometric);
    land_free(land_grid_vtable_isometric_wrap);
}
#ifndef LAND_USE_EXTERNAL_YAML
LandYaml* land_yaml_load(char const * filename) {
    LandFile * f = land_file_new(filename, "rb");
    if (! f) {
        land_log_message("Failed opening %s\n", filename);
        return NULL;
    }
    land_log_message("Parsing yaml %s\n", filename);
    LandYaml * yaml = land_yaml_new(filename);
    LandBuffer * value = land_buffer_new();
    while (1) {
        int c = land_file_getc(f);
        if (c < 0 || strchr("{}[],:\n", c)) {
            if (value->n) {
                land_buffer_add_char(value, 0);
                land_yaml_add_scalar(yaml, land_strdup(value->buffer));
                land_buffer_clear(value);
            }
        }
        if (c < 0) {
            break;
        }
        if (c == '{') {
            land_yaml_add_mapping(yaml);
        }
        else if (c == '[') {
            land_yaml_add_sequence(yaml);
        }
        else if (c == '}') {
            land_yaml_done(yaml);
        }
        else if (c == ']') {
            land_yaml_done(yaml);
        }
        else if (c == ',') {
            ;
        }
        else if (c == ':') {
            ;
        }
        else if (c == '\n') {
            ;
        }
        else {
            land_buffer_add_char(value, c);
        }
    }
    land_file_destroy(f);
    land_buffer_destroy(value);
    return yaml;
}
static void _yaml_write(YamlParser * p, char const * s) {
    int n = strlen(s);
    if (! p->cannot_break) {
        if (p->line_length + n > 80) {
            land_file_write(p->file, "\n", 1);
            p->line_length = 0;
        }
    }
    p->cannot_break = 0;
    land_file_write(p->file, s, n);
    p->line_length += n;
}
static void yaml_write(YamlParser * p, char const * s) {
    _yaml_write(p, s);
}
static void _pretty_newline(YamlParser * p) {
    if (p->flags & LandYamlPretty) {
        yaml_write(p, "\n");
        p->line_length = 0;
        for (int i = 0; i < p->indent; i += 1) {
            yaml_write(p, "    ");
        }
    }
}
static bool _save_mapping(LandYamlEntry * e, YamlParser * p) {
    yaml_write(p, "{");
    p->indent += 1;
    bool prev = 0;
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(e->sequence);
        for (char const * key = LandArrayIterator_item(e->sequence, &__iter0__); LandArrayIterator_next(e->sequence, &__iter0__); key = LandArrayIterator_item(e->sequence, &__iter0__)) {
            if (prev) {
                yaml_write(p, ",");
            }
            _pretty_newline(p);
            _yaml_write(p, key);
            p->cannot_break = 1;
            _yaml_write(p, ": ");
            p->cannot_break = 1;
            _save_entry(land_hash_get(e->mapping, key), p);
            prev = 1;
        }
    }
    p->indent -= 1;
    _pretty_newline(p);
    yaml_write(p, "}");
    return true;
}
static bool _save_sequence(LandYamlEntry * e, YamlParser * p) {
    yaml_write(p, "[");
    p->indent += 1;
    bool prev = 0;
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(e->sequence);
        for (LandYamlEntry * e2 = LandArrayIterator_item(e->sequence, &__iter0__); LandArrayIterator_next(e->sequence, &__iter0__); e2 = LandArrayIterator_item(e->sequence, &__iter0__)) {
            if (prev) {
                yaml_write(p, ",");
            }
            if (p->indent < 2) {
                _pretty_newline(p);
            }
            _save_entry(e2, p);
            prev = 1;
        }
    }
    p->indent -= 1;
    _pretty_newline(p);
    yaml_write(p, "]");
    return true;
}
static bool _save_scalar(LandYamlEntry * e, YamlParser * p) {
    yaml_write(p, e->scalar);
    return 1;
}
static bool _save_entry(LandYamlEntry * e, YamlParser * p) {
    if (e->type == YamlMapping) {
        return _save_mapping(e, p);
    }
    else if (e->type == YamlSequence) {
        return _save_sequence(e, p);
    }
    else if (e->type == YamlScalar) {
        return _save_scalar(e, p);
    }
    return false;
}
void land_yaml_save_flags(LandYaml * yaml, int flags) {
    LandFile * f = land_file_new(yaml->filename, "wb");
    YamlParser * p = NULL;
    if (! f) {
        goto error;
    }
    land_alloc(p);
    p->file = f;
    p->flags = flags;
    if (! _save_entry(yaml->root, p)) {
        goto error;
    }
    error:;
    if (p) {
        land_free(p);
    }
    if (f) {
        land_file_destroy(f);
    }
}
void land_yaml_save(LandYaml * yaml) {
    land_yaml_save_flags(yaml, 0);
}
#endif
struct LandSoundPlatform {
    LandSound super;
    ALLEGRO_SAMPLE * a5;
    ALLEGRO_SAMPLE_ID last_playing;
    void * buffer;
    bool last_failed;
};
struct LandStreamPlatform {
    LandStream super;
    ALLEGRO_AUDIO_STREAM * a5;
    void * fragment;
};
static LandArray * streaming;
static bool get_params(int channels, int bits, int * chan_conf, int * depth) {
    if (channels == 1) {
        * chan_conf = ALLEGRO_CHANNEL_CONF_1;
    }
    else if (channels == 2) {
        * chan_conf = ALLEGRO_CHANNEL_CONF_2;
    }
    else {
        return 0;
    }
    if (bits == 8) {
        * depth = ALLEGRO_AUDIO_DEPTH_INT8;
    }
    else if (bits == 16) {
        * depth = ALLEGRO_AUDIO_DEPTH_INT16;
    }
    else {
        return 0;
    }
    return 1;
}
LandSound* platform_sound_load(char const * filename) {
    LandSoundPlatform * self;
    land_alloc(self);
    self->a5 = al_load_sample(filename);
    self->super.filename = land_strdup(filename);
    if (self->a5) {
        self->super.loaded = 1;
    }
    return (void *) self;
}
LandSound* platform_sound_new(int samples, float frequency, int bits, int channels) {
    LandSoundPlatform * self;
    land_alloc(self);
    int chan_conf = 0, depth = 0;
    get_params(channels, bits, & chan_conf, & depth);
    int sample_size = al_get_channel_count(chan_conf) * al_get_audio_depth_size(depth);
    int bytes = samples * sample_size;
    self->buffer = land_malloc(bytes);
    self->a5 = al_create_sample(self->buffer, samples, frequency, depth, chan_conf, 0);
    return (void *) self;
}
void* platform_sound_sample_pointer(LandSound * super) {
    LandSoundPlatform * self = (void *) super;
    return al_get_sample_data(self->a5);
}
int platform_sound_length(LandSound * super) {
    LandSoundPlatform * self = (void *) super;
    return al_get_sample_length(self->a5);
}
double platform_sound_seconds(LandSound * super) {
    LandSoundPlatform * self = (void *) super;
    double x = al_get_sample_length(self->a5);
    x /= al_get_sample_frequency(self->a5);
    return x;
}
void platform_sound_play(LandSound * s, float volume, float pan, float frequency, bool loop) {
    LandSoundPlatform * self = (void *) s;
    if (! self->a5) {
        return ;
    }
    self->last_failed = 0;
    if (! al_play_sample(self->a5, volume, pan, frequency, loop ? ALLEGRO_PLAYMODE_LOOP : ALLEGRO_PLAYMODE_ONCE, & self->last_playing)) {
        self->last_failed = 1;
    }
}
void platform_sound_change(LandSound * s, float volume, float pan, float frequency) {
    LandSoundPlatform * self = (void *) s;
    ALLEGRO_SAMPLE_INSTANCE * inst = al_lock_sample_id(& self->last_playing);
    al_set_sample_instance_gain(inst, volume);
    al_set_sample_instance_pan(inst, pan);
    al_set_sample_instance_speed(inst, frequency);
    al_unlock_sample_id(& self->last_playing);
}
void platform_sound_stop(LandSound * s) {
    LandSoundPlatform * self = (void *) s;
    if (self->last_failed) {
        return ;
    }
    al_stop_sample(& self->last_playing);
}
void platform_sound_destroy(LandSound * s) {
    LandSoundPlatform * self = (void *) s;
    al_destroy_sample(self->a5);
    if (self->buffer) {
        land_free(self->buffer);
    }
    land_free(s->filename);
    land_free(s);
}
void platform_sound_init(void) {
    al_init_acodec_addon();
    al_install_audio();
    al_reserve_samples(8);
}
void platform_sound_exit(void) {
    if (streaming) {
        {
            LandArrayIterator __iter0__ = LandArrayIterator_first(streaming);
            for (LandStream * s = LandArrayIterator_item(streaming, &__iter0__); LandArrayIterator_next(streaming, &__iter0__); s = LandArrayIterator_item(streaming, &__iter0__)) {
                LandStreamPlatform * s2 = (void *) s;
                al_destroy_audio_stream(s2->a5);
                s2->a5 = NULL;
            }
        }
    }
}
void platform_sound_resume(void) {
    al_restore_default_mixer();
    ALLEGRO_MIXER * mix = al_get_default_mixer();
    if (mix) {
        al_set_mixer_playing(mix, 1);
    }
}
void platform_sound_halt(void) {
    ALLEGRO_MIXER * mix = al_get_default_mixer();
    if (mix) {
        al_set_mixer_playing(al_get_default_mixer(), 0);
    }
    al_set_default_voice(NULL);
}
LandStream* platform_stream_new(int samples, int fragments, float frequency, int bits, int channels) {
    LandStreamPlatform * self;
    land_alloc(self);
    LandStream * super = (void *) self;
    int chan_conf = 0, depth = 0;
    get_params(channels, bits, & chan_conf, & depth);
    super->fragments = fragments;
    super->samples = samples;
    super->sample_size = al_get_channel_count(chan_conf) * al_get_audio_depth_size(depth);
    self->a5 = al_create_audio_stream(fragments, samples, frequency, depth, chan_conf);
    al_attach_audio_stream_to_mixer(self->a5, al_get_default_mixer());
    if (! streaming) {
        streaming = land_array_new();
    }
    land_array_add(streaming, self);
    return super;
}
void platform_stream_destroy(LandStream * super) {
    int i = land_array_find(streaming, super);
    if (i >= 0) {
        land_array_swap(streaming, i, - 1);
        land_array_pop(streaming);
    }
    LandStreamPlatform * self = (void *) super;
    al_destroy_audio_stream(self->a5);
    land_free(super);
}
void* platform_stream_buffer(LandStream * super) {
    LandStreamPlatform * self = (void *) super;
    if (al_get_available_audio_stream_fragments(self->a5) == 0) {
        return NULL;
    }
    self->fragment = al_get_audio_stream_fragment(self->a5);
    return self->fragment;
}
void platform_stream_fill(LandStream * super) {
    LandStreamPlatform * self = (void *) super;
    al_set_audio_stream_fragment(self->a5, self->fragment);
}
void platform_stream_music(LandStream * super, char const * filename, bool looping) {
    LandStreamPlatform * self = (void *) super;
    al_destroy_audio_stream(self->a5);
    self->a5 = al_load_audio_stream(filename, super->fragments, super->samples);
    al_attach_audio_stream_to_mixer(self->a5, al_get_default_mixer());
    if (looping) {
        al_set_audio_stream_playmode(self->a5, ALLEGRO_PLAYMODE_LOOP);
    }
}
void platform_stream_volume(LandStream * super, float volume) {
    LandStreamPlatform * self = (void *) super;
    al_set_audio_stream_gain(self->a5, volume);
}
bool platform_stream_is_playing(LandStream * super) {
    LandStreamPlatform * self = (void *) super;
    return al_get_audio_stream_playing(self->a5);
}
void platform_stream_set_playing(LandStream * super, bool onoff) {
    LandStreamPlatform * self = (void *) super;
    al_set_audio_stream_playing(self->a5, onoff);
}
static int key_state [LandKeysCount];
static int key_pressed [LandKeysCount];
static int keybuffer_keycode [256];
static int keybuffer_unicode [256];
static int keybuffer_first;
static int keybuffer_last;
void land_key_press_event(int k) {
    if (! key_state [k]) {
        key_pressed [k]++;
        key_state [k] = 1;
    }
}
void land_key_release_event(int k) {
    key_state [k] = 0;
}
void land_keyboard_init(void) {
    ;
}
int land_key(int k) {
    return key_state [k];
}
int land_key_pressed(int k) {
    return key_pressed [k];
}
void land_keyboard_tick(void) {
    int i;
    for (i = 0; i < LandKeysCount; i++) {
        key_pressed [i] = 0;
    }
    keybuffer_first = 0;
    keybuffer_last = 0;
}
void land_keyboard_add_char(int keycode, int unicode) {
    if (keybuffer_last == 256) {
        return ;
    }
    keybuffer_keycode [keybuffer_last] = keycode;
    keybuffer_unicode [keybuffer_last] = unicode;
    keybuffer_last++;
}
bool land_keybuffer_empty(void) {
    return keybuffer_last == keybuffer_first;
}
void land_keybuffer_next(int * k, int * u) {
    if (keybuffer_first < keybuffer_last) {
        * k = keybuffer_keycode [keybuffer_first];
        * u = keybuffer_unicode [keybuffer_first];
        keybuffer_first++;
    }
}
char const* land_key_name(int k) {
    return platform_key_name(k);
}
int land_key_get_pressed(int first) {
    for (int i = first; i < LandKeysCount; i += 1) {
        if (key_pressed [i]) {
            return i;
        }
    }
    return 0;
}
static float _blend(LandPlasma * self, float c1, float c2, float level) {
    float rnd = land_random_f(self->rndgen, - 1, 1);
    float scale = pow(2 + self->power_modifier, - level + self->amplitude);
    float ret = (c1 + c2 + rnd * scale) / 2;
    if (ret < - 1) {
        ret = - 1;
    }
    if (ret > 1) {
        ret = 1;
    }
    return ret;
}
LandPlasma* land_plasma_new(LandRandom * rndgen, int w, int h, float power_modifier, float amplitude) {
    LandPlasma * self;
    land_alloc(self);
    self->w = w;
    self->h = h;
    self->power_modifier = power_modifier;
    self->amplitude = amplitude;
    self->rndgen = rndgen;
    self->cache = land_calloc(w * h * sizeof (* self->cache));
    return self;
}
static float _plasma_read(LandPlasma * self, int x, int y) {
    x = land_mod(x, self->w);
    y = land_mod(y, self->h);
    return self->cache [y * self->w + x];
}
static void _plasma_write(LandPlasma * self, int x, int y, float value) {
    x = land_mod(x, self->w);
    y = land_mod(y, self->h);
    self->cache [y * self->w + x] = value;
}
static void _fractal(LandPlasma * self, int x, int y, int w, int h, int i) {
    float c1 = _plasma_read(self, x, y);
    float c2 = _plasma_read(self, x + w, y);
    float c3 = _plasma_read(self, x + w, y + h);
    float c4 = _plasma_read(self, x, y + h);
    float n2 = _blend(self, c1, c2, i);
    float n4 = _blend(self, c1, c4, i);
    float n3 = _blend(self, (c1 + c2) / 2, (c3 + c4) / 2, i);
    w /= 2;
    h /= 2;
    _plasma_write(self, x + w, y, n2);
    _plasma_write(self, x + w, y + h, n3);
    _plasma_write(self, x, y + h, n4);
}
static void _subdivide(LandPlasma * self) {
    int dx = self->w;
    int dy = self->h;
    int x = 0;
    int y = 0;
    int i = 0;
    while (dx > 1 && dy > 1) {
        while (x < self->w) {
            while (y < self->h) {
                _fractal(self, x, y, dx, dy, i);
                y += dy;
            }
            y = 0;
            x += dx;
        }
        x = 0;
        dx /= 2;
        dy /= 2;
        i++;
    }
}
void land_plasma_generate(LandPlasma * self) {
    _plasma_write(self, 0, 0, 0);
    _subdivide(self);
}
void land_plasma_del(LandPlasma * self) {
    land_free(self);
}
float land_plasma_at(LandPlasma * self, int x, int y) {
    x %= self->w;
    if (x < 0) {
        x += self->w;
    }
    y %= self->h;
    if (y < 0) {
        y += self->h;
    }
    float value = self->cache [x + self->w * y];
    return value;
}
void land_map_draw(LandMap * self, LandView * view) {
    /* Render the map using the given ''view''.
     */
    LandLayer * layer = self->first_layer;
    while (layer) {
        land_layer_draw(layer, view);
        layer = layer->next_in_map;
    }
}
void land_map_add_layer(LandMap * map, LandLayer * layer) {
    /* Add another layer to the map, on top of any existing layers.
     */
    if (map->first_layer) {
        LandLayer * l = map->first_layer;
        while (l->next_in_map) {
            l = l->next_in_map;
        }
        l->next_in_map = layer;
    }
    else {
        map->first_layer = layer;
    }
}
LandLayer* land_map_base_layer(LandMap * map) {
    /* Returns the base layer of the map.
     */
    return map->first_layer;
}
LandLayer* land_map_find_layer(LandMap * map, char const * name) {
    for (LandLayer * l = map->first_layer; l; l = l->next_in_map) {
        if (! strcmp(name, l->name)) {
            return l;
        }
    }
    return NULL;
}
LandMap* land_map_new(void) {
    /* Create a new map. This is not called directly normally, as you likely want
     * to use one of the convenience functions to already create layers of the
     * right type along with it.
     */
    LandMap * self;
    land_alloc(self);
    return self;
}
void land_map_del(LandMap * self) {
    /* Destroy a map. This also destroys its layers.
     */
    if (self->first_layer) {
        LandLayer * l = self->first_layer;
        while (l) {
            LandLayer * next = l->next_in_map;
            land_layer_del(l);
            l = next;
        }
    }
    land_free(self);
}
void land_map_destroy(LandMap * self) {
    /* Same as land_map_del.
     */
    land_map_del(self);
}
LandCSGAABB land_csg_aabb_infinite(void) {
    LandCSGAABB a;
    a.x1 = - INFINITY;
    a.x2 = + INFINITY;
    a.y1 = - INFINITY;
    a.y2 = + INFINITY;
    a.z1 = - INFINITY;
    a.z2 = + INFINITY;
    return a;
}
LandCSGAABB land_csg_aabb_empty(void) {
    LandCSGAABB a;
    a.x1 = + INFINITY;
    a.x2 = - INFINITY;
    a.y1 = + INFINITY;
    a.y2 = - INFINITY;
    a.z1 = + INFINITY;
    a.z2 = - INFINITY;
    return a;
}
void land_csg_aabb_update(LandCSGAABB * self, LandArray * polygons) {
    * self = land_csg_aabb_empty();
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(polygons);
        for (LandCSGPolygon * p = LandArrayIterator_item(polygons, &__iter0__); LandArrayIterator_next(polygons, &__iter0__); p = LandArrayIterator_item(polygons, &__iter0__)) {
            {
                LandArrayIterator __iter1__ = LandArrayIterator_first(p->vertices);
                for (LandCSGVertex * v = LandArrayIterator_item(p->vertices, &__iter1__); LandArrayIterator_next(p->vertices, &__iter1__); v = LandArrayIterator_item(p->vertices, &__iter1__)) {
                    if (v->pos.x < self->x1) {
                        self->x1 = v->pos.x;
                    }
                    if (v->pos.x > self->x2) {
                        self->x2 = v->pos.x;
                    }
                    if (v->pos.y < self->y1) {
                        self->y1 = v->pos.y;
                    }
                    if (v->pos.y > self->y2) {
                        self->y2 = v->pos.y;
                    }
                    if (v->pos.z < self->z1) {
                        self->z1 = v->pos.z;
                    }
                    if (v->pos.z > self->z2) {
                        self->z2 = v->pos.z;
                    }
                }
            }
        }
    }
}
LandCSGAABB land_csg_aabb_intersect(LandCSGAABB a, LandCSGAABB b) {
    LandCSGAABB c = a;
    if (b.x1 > c.x1) {
        c.x1 = b.x1;
    }
    if (b.x2 < c.x2) {
        c.x2 = b.x2;
    }
    if (b.y1 > c.y1) {
        c.y1 = b.y1;
    }
    if (b.y2 < c.y2) {
        c.y2 = b.y2;
    }
    if (b.z1 > c.z1) {
        c.z1 = b.z1;
    }
    if (b.z2 < c.z2) {
        c.z2 = b.z2;
    }
    return c;
}
LandCSGAABB land_csg_aabb_combine(LandCSGAABB a, LandCSGAABB b) {
    LandCSGAABB c = a;
    if (b.x1 < c.x1) {
        c.x1 = b.x1;
    }
    if (b.x2 > c.x2) {
        c.x2 = b.x2;
    }
    if (b.y1 < c.y1) {
        c.y1 = b.y1;
    }
    if (b.y2 > c.y2) {
        c.y2 = b.y2;
    }
    if (b.z1 < c.z1) {
        c.z1 = b.z1;
    }
    if (b.z2 > c.z2) {
        c.z2 = b.z2;
    }
    return c;
}
double land_csg_aabb_radius(LandCSGAABB a) {
    double x = _scramble_max(fabs(a.x1), fabs(a.x2));
    double y = _scramble_max(fabs(a.y1), fabs(a.y2));
    double z = _scramble_max(fabs(a.z1), fabs(a.z2));
    double xy = _scramble_max(x, y);
    return _scramble_max(xy, z);
}
LandShader* platform_shader_new(char const * name, char const * vertex_glsl, char const * fragment_glsl) {
    LandShaderPlatform * self;
    land_alloc(self);
    self->super.name = land_strdup(name);
    self->a5 = al_create_shader(ALLEGRO_SHADER_GLSL);
    if (vertex_glsl) {
        al_attach_shader_source(self->a5, ALLEGRO_VERTEX_SHADER, vertex_glsl);
    }
    if (fragment_glsl) {
        al_attach_shader_source(self->a5, ALLEGRO_PIXEL_SHADER, fragment_glsl);
    }
    if (! al_build_shader(self->a5)) {
        land_log_message("Shader build error: %s\n", name);
    }
    if (! al_use_shader(self->a5)) {
        land_log_message("Shader use error: %s\n", name);
    }
    str error = al_get_shader_log(self->a5);
    if (error && error [0]) {
        land_log_message("Shader log:\n%s\n", error);
    }
    return & self->super;
}
void platform_shader_destroy(LandShader * super) {
    land_free(super->name);
    LandShaderPlatform * self = (void *) super;
    al_destroy_shader(self->a5);
    land_free(self);
}
LandProtobuf* land_protobuf_load(char const * filename) {
    LandBuffer * b = land_buffer_read_from_file(filename);
    if (! b) {
        return NULL;
    }
    LandProtobuf * pbuf;
    land_alloc(pbuf);
    pbuf->data = b;
    pbuf->end = b->n;
    return pbuf;
}
static uint64_t varint(LandProtobuf * self) {
    uint64_t x = 0;
    int s = 0;
    while (1) {
        uint8_t c = self->data->buffer [self->pos++];
        x += (c & 127) << s;
        s += 7;
        if (c & 128) {
            continue;
        }
        return x;
    }
}
int land_protobuf_next(LandProtobuf * self, uint64_t * size) {
    if (self->pos >= self->end) {
        return 0;
    }
    int x = varint(self);
    int kind = x & 3;
    if (kind == 2) {
        * size = varint(self);
    }
    return x >> 3;
}
void land_protobuf_sub_start(LandProtobuf * self, uint64_t * size) {
    uint64_t end = self->end;
    self->end = self->pos + * size;
    * size = end;
}
void land_protobuf_sub_end(LandProtobuf * self, uint64_t end) {
    self->pos = self->end;
    self->end = end;
}
#define R(T) \
    T x = * (T *)(self->data->buffer + self->pos); \
    self->pos += sizeof (T); \
    return x;
double land_protobuf_double(LandProtobuf * self) {
    double x;
    void * p = & x;
    memcpy(p, self->data->buffer + self->pos, 8);
    self->pos += 8;
    return x;
}
float land_protobuf_float(LandProtobuf * self) {
    float x;
    void * p = & x;
    memcpy(p, self->data->buffer + self->pos, 4);
    self->pos += 4;
    return x;
}
uint32_t land_protobuf_fixed32(LandProtobuf * self) {
    R(uint32_t);
}
int32_t land_protobuf_sfixed32(LandProtobuf * self) {
    R(int32_t);
}
char* land_protobuf_string(LandProtobuf * self, int size) {
    self->pos += size;
    return self->data->buffer + self->pos - size;
}
void land_protobuf_destroy(LandProtobuf * self) {
    land_buffer_del(self->data);
    land_free(self);
}
#undef R
LandView* land_view_new(int x, int y, int w, int h) {
    /* Specify the view rectangle on the screen.
     */
    LandView * self;
    land_alloc(self);
    self->x = x;
    self->y = y;
    self->w = w;
    self->h = h;
    self->scale_x = 1;
    self->scale_y = 1;
    self->r = 1;
    self->g = 1;
    self->b = 1;
    self->a = 1;
    return self;
}
void land_view_destroy(LandView * self) {
    land_free(self);
}
void land_view_scroll(LandView * self, float dx, float dy) {
    /* Scroll the view by the given amount of screen pixels.
     */
    self->scroll_x += dx;
    self->scroll_y += dy;
}
void land_view_scroll_to(LandView * self, float x, float y) {
    self->scroll_x = x;
    self->scroll_y = y;
}
void land_view_scale(LandView * self, float sx, float sy) {
    float cx = self->scroll_x + (self->w / 2 / self->scale_x);
    float cy = self->scroll_y + (self->h / 2 / self->scale_y);
    self->scale_x *= sx;
    self->scale_y *= sy;
    self->scroll_x = cx - (self->w / 2 / self->scale_x);
    self->scroll_y = cy - (self->h / 2 / self->scale_y);
}
void land_view_zoom(LandView * self, float zx, float zy) {
    land_view_scale(self, zx / self->scale_x, zy / self->scale_y);
}
void land_view_scroll_center(LandView * self, float x, float y) {
    /* Given two absolute map coordinates, make them the center of the view.
     */
    self->scroll_x = x - self->w / 2;
    self->scroll_y = y - self->h / 2;
}
void land_view_scroll_center_on_screen(LandView * self, float x, float y) {
    /* Given an on-screen position, make it the new center of the view.
     */
    x -= self->x;
    y -= self->y;
    x += self->scroll_x;
    y += self->scroll_y;
    land_view_scroll_center(self, x, y);
}
void land_view_ensure_visible(LandView * self, float x, float y, float bx, float by) {
    /* Given an absolute map position, scroll the view so it is not within bx/by
     * pixels to the view's border.
     */
    if (x - self->scroll_x < bx) {
        self->scroll_x = x - bx;
    }
    if (x - self->scroll_x > self->w - bx) {
        self->scroll_x = x - self->w + bx;
    }
    if (y - self->scroll_y < by) {
        self->scroll_y = y - by;
    }
    if (y - self->scroll_y > self->h - by) {
        self->scroll_y = y - self->h + by;
    }
}
void land_view_ensure_visible_on_screen(LandView * self, float x, float y, float bx, float by) {
    /* land_view_ensure_visible, but the given position is in screen coordinates.
     */
    x -= self->x;
    y -= self->y;
    x += self->scroll_x;
    y += self->scroll_y;
    land_view_ensure_visible(self, x, y, bx, by);
}
void land_view_ensure_inside_grid(LandView * self, LandGrid * grid) {
    /* For a non-wrapped grid, move the view so it lies within the grid.
     * For a wrapped grid, where the view always is inside the grid, this function
     * only normalizes the scroll position to lie within the "first quadrant".
     */
    if (grid->wrap) {
        float cx, cy, sx, sy;
        land_grid_get_cell_at(grid, self, self->x, self->y, & cx, & cy);
        self->scroll_x = 0;
        self->scroll_y = 0;
        land_grid_get_cell_position(grid, self, cx, cy, & sx, & sy);
        self->scroll_x = sx - self->x;
        self->scroll_y = sy - self->y;
    }
    else {
        int w = grid->x_cells * grid->cell_w;
        int h = grid->y_cells * grid->cell_h;
        if (self->scroll_x < 0) {
            self->scroll_x = 0;
        }
        if (self->scroll_y < 0) {
            self->scroll_y = 0;
        }
        if (self->scroll_x > w - self->w) {
            self->scroll_x = w - self->w;
        }
        if (self->scroll_y > h - self->h) {
            self->scroll_y = h - self->h;
        }
    }
}
void land_view_clip(LandView * self) {
    land_clip(self->x, self->y, self->x + self->w, self->y + self->h);
}
void land_view_to_world(LandView * self, float vx, float vy, float * wx, float * wy) {
    * wx = self->scroll_x + (vx - self->x) / self->scale_x;
    * wy = self->scroll_y + (vy - self->y) / self->scale_y;
}
void land_world_to_view(LandView * self, float wx, float wy, float * vx, float * vy) {
    * vx = (wx - self->scroll_x) * self->scale_x + self->x;
    * vy = (wy - self->scroll_y) * self->scale_y + self->y;
}
static float _no_lerp(float a0, float a1, float w) {
    return a0;
}
static float _linear_lerp(float a0, float a1, float w) {
    return a0 + (a1 - a0) * w;
}
static float _cosine_lerp(float a0, float a1, float w) {
    float ft = w * LAND_PI;
    float f = (1 - cos(ft)) * 0.5;
    return a0 * (1 - f) + a1 * f;
}
static float _smooth_step_lerp(float a0, float a1, float w) {
    float f = w * w * (3 - 2 * w);
    return a0 * (1 - f) + a1 * f;
}
static float _smoother_step_lerp(float a0, float a1, float w) {
    float f = 6 * pow(w, 5) - 15 * pow(w, 4) + 10 * pow(w, 3);
    return a0 * (1 - f) + a1 * f;
}
static float _smoothest_step_lerp(float a0, float a1, float w) {
    float f = - 20 * pow(w, 7) + 70 * pow(w, 6) - 84 * pow(w, 5) + 35 * pow(w, 4);
    return a0 * (1 - f) + a1 * f;
}
LandPerlin* land_perlin_create(LandRandom * seed, int w, int h) {
    /* Create a Perlin noise of the given resolution.
     * The noise at any integer coordinate is always 0. Other coordinates
     * return a random value in the range -1..1.
     * If w and h are 1, each noise sample will have the identical vector
     * at each corner.
     * For point 0.5/0.5 this means:
     * a = 0.5 * u + 0.5 * v
     * b = -0.5 * u + 0.5 * v
     * c = 0.5 * u + -0.5 * v
     * d = -0.5 * u + -0.5 * v
     * a + b + c + d = 0
     * result = 0
     * Also any other point:
     * a = x * u + y * v
     * b = -X * u + y * v
     * c = x * u + -Y * v
     * d = -X * u + -Y * v
     * e = X * a + x * b = xX * u + yX * v + -xX * u + xy * v
     * f = X * c + x * d = xX * u + -XY * v + -xX * u + -xY * v
     * result = Y * e + y * f =
     * xXY * u + yXY * v + -xXY * u + xyY * v + xXy * u + -XYy * v + -xXy * u + -xYy * v
     * u * (xXY - xXY + xXy - xXy) + v * (yXY + xyY - XYy - xYy)
     * u * 0 + v * 0
     * 0
     * Note that this is not necessarily true for non-linear interpolation.
     */
    LandPerlin * self;
    land_alloc(self);
    self->w = w;
    self->h = h;
    self->xy = land_calloc(w * h * sizeof (* self->xy));
    for (int j = 0; j < h; j += 1) {
        for (int i = 0; i < w; i += 1) {
            float a = land_random_f(seed, 0, LAND_PI * 2);
            self->xy [i + j * w].x = cos(a);
            self->xy [i + j * w].y = sin(a);
        }
    }
    self->lerp = _cosine_lerp;
    return self;
}
void land_perlin_set_lerp_callback(LandPerlin * self, float(* lerp)(float a, float b, float p)) {
    self->lerp = lerp;
}
void land_perlin_set_lerp(LandPerlin * self, LandPerlinLerp lerp) {
    if (lerp == LandPerlinLerpNone) {
        self->lerp = _no_lerp;
    }
    if (lerp == LandPerlinLerpLinear) {
        self->lerp = _linear_lerp;
    }
    if (lerp == LandPerlinLerpCosine) {
        self->lerp = _cosine_lerp;
    }
    if (lerp == LandPerlinLerpSmoothStep) {
        self->lerp = _smooth_step_lerp;
    }
    if (lerp == LandPerlinLerpSmootherStep) {
        self->lerp = _smoother_step_lerp;
    }
    if (lerp == LandPerlinLerpSmoothestStep) {
        self->lerp = _smoothest_step_lerp;
    }
}
void land_perlin_destroy(LandPerlin * self) {
    land_free(self->xy);
    land_free(self);
}
static LandNoiseF2* _gradient(LandPerlin * self, int x, int y) {
    x %= self->w;
    if (x < 0) {
        x += self->w;
    }
    y %= self->h;
    if (y < 0) {
        y += self->h;
    }
    return self->xy + x + y * self->w;
}
static float _dot(LandPerlin * self, int ix, int iy, float x, float y) {
    float dx = x - ix;
    float dy = y - iy;
    LandNoiseF2 * g = _gradient(self, ix, iy);
    return dx * g->x + dy * g->y;
}
float land_perlin_at(LandPerlin * self, float x, float y) {
    /* Get a noise value from a Perlin noise. If x/y are not from inside
     * the resolution of the noise they will wrap around.
     */
    int x0 = floor(x);
    int x1 = x0 + 1;
    int y0 = floor(y);
    int y1 = y0 + 1;
    float sx = x - x0;
    float sy = y - y0;
    float(* lerp)(float, float, float) = self->lerp;
    float n0 = _dot(self, x0, y0, x, y);
    float n1 = _dot(self, x1, y0, x, y);
    float v0 = lerp(n0, n1, sx);
    float n2 = _dot(self, x0, y1, x, y);
    float n3 = _dot(self, x1, y1, x, y);
    float v1 = lerp(n2, n3, sx);
    float value = lerp(v0, v1, sy);
    return value;
}
void land_perlin_displace(LandPerlin * self, float x, float y, float * xd, float * yd) {
    /* Instead of getting the result of the Perlin noise at x/y, directly
     * get the random displacement vector xd/yd.
     */
    int x0 = floor(x);
    int x1 = x0 + 1;
    int y0 = floor(y);
    int y1 = y0 + 1;
    float sx = x - x0;
    float sy = y - y0;
    float(* lerp)(float, float, float) = self->lerp;
    LandNoiseF2 * g00 = _gradient(self, x0, y0);
    LandNoiseF2 * g10 = _gradient(self, x1, y0);
    LandNoiseF2 * g01 = _gradient(self, x0, y1);
    LandNoiseF2 * g11 = _gradient(self, x1, y1);
    float ix0, ix1;
    ix0 = lerp(g00->x, g10->x, sx);
    ix1 = lerp(g01->x, g11->x, sx);
    * xd = lerp(ix0, ix1, sy);
    ix0 = lerp(g00->y, g10->y, sx);
    ix1 = lerp(g01->y, g11->y, sx);
    * yd = lerp(ix0, ix1, sy);
}
static void* _get(LandIniSection * s, char const * key) {
    LandIniEntry * e = land_hash_get(s->lookup, key);
    if (e) {
        return e->val;
    }
    return NULL;
}
static void _add(LandIniSection * s, char const * key, void * val) {
    LandIniEntry * e = land_hash_get(s->lookup, key);
    if (e) {
        land_free(e->val);
        e->val = val;
        return ;
    }
    e = land_calloc(sizeof (* e));
    e->key = land_strdup(key);
    e->val = val;
    land_array_add(s->entries, e);
    land_hash_insert(s->lookup, key, e);
}
static void _del(LandIniSection * s) {
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(s->entries);
        for (LandIniEntry * e = LandArrayIterator_item(s->entries, &__iter0__); LandArrayIterator_next(s->entries, &__iter0__); e = LandArrayIterator_item(s->entries, &__iter0__)) {
            land_free(e->key);
            if (e->val) {
                land_free(e->val);
            }
            land_free(e);
        }
    }
    land_array_destroy(s->entries);
    land_hash_destroy(s->lookup);
    land_free(s);
}
static LandIniSection* _new(void) {
    LandIniSection * s = land_calloc(sizeof (* s));
    s->lookup = land_hash_new();
    s->entries = land_array_new();
    return s;
}
void land_ini_set_string(LandIniFile * ini, char const * section, char const * key, char const * val) {
    LandIniSection * s = _get(ini->sections, section);
    if (! s) {
        s = _new();
        _add(ini->sections, section, s);
    }
    _add(s, key, val ? land_strdup(val) : NULL);
}
void land_ini_set_int(LandIniFile * ini, char const * section, char const * key, int val) {
    char temp [100];
    snprintf(temp, sizeof temp, "%d", val);
    land_ini_set_string(ini, section, key, temp);
}
char const* land_ini_get_string(LandIniFile * ini, char const * section, char const * key, char const * de) {
    LandIniSection * s = _get(ini->sections, section);
    if (! s) {
        return de;
    }
    char * v = _get(s, key);
    if (v) {
        return v;
    }
    return de;
}
int land_ini_get_int(LandIniFile * ini, char const * section, char const * key, int de) {
    char const * s = land_ini_get_string(ini, section, key, NULL);
    if (s == NULL) {
        return de;
    }
    return strtol(s, NULL, 0);
}
int land_ini_get_number_of_entries(LandIniFile * ini, char const * section) {
    LandIniSection * s = ini->sections;
    if (! s) {
        return 0;
    }
    if (section) {
        s = _get(s, section);
        if (! s) {
            return 0;
        }
    }
    return land_array_count(s->entries);
}
char const* land_ini_get_nth_entry(LandIniFile * ini, char const * section, int i) {
    /* Get the n-th entry of an ini section. If section is None get the
     * n-th section instead.
     */
    LandIniSection * s = ini->sections;
    if (section) {
        s = _get(s, section);
    }
    LandIniEntry * e = land_array_get_nth(s->entries, i);
    return e->key;
}
static bool is_whitespace(char c) {
    if (c == ' ' || c == '\t' || c == '\n') {
        return true;
    }
    return false;
}
#define addc(var, l) \
    var [l] = c; \
    if (l < (int) sizeof (var) - 1) { \
        l++; \
    } \
    var [l] = '\0';
enum State {
    OUTSIDE,
    SECTION,
    KEY,
    EQUALS,
    VALUE,
    COMMENT
};
LandIniFile* land_ini_read(char const * filename) {
    char section_name [1024] = "", key_name [1024] = "", value [1024] = "";
    int slen = 0, klen = 0, vlen = 0;
    State state = OUTSIDE;
    LandIniFile * ini = land_calloc(sizeof (* ini));
    ini->filename = land_strdup(filename);
    ini->sections = _new();
    LandFile * f = land_file_new(filename, "rb");
    if (! f) {
        return ini;
    }
    int done = 0;
    while (! done) {
        int c = land_file_getc(f);
        if (c == EOF) {
            done = 1;
            c = '\n';
        }
        if (c == '\r') {
            continue;
        }
        if (state == OUTSIDE) {
            if (c == '[') {
                slen = 0;
                state = SECTION;
            }
            else if (c == '#') {
                state = COMMENT;
            }
            else if (! is_whitespace(c)) {
                klen = 0;
                addc(key_name, klen);
                state = KEY;
            }
        }
        else if (state == SECTION) {
            if (c == ']' || c == '\n') {
                state = OUTSIDE;
            }
            else {
                addc(section_name, slen);
            }
        }
        else if (state == KEY) {
            if (c == '\n') {
                state = OUTSIDE;
            }
            else if (c == '=') {
                state = EQUALS;
            }
            else {
                addc(key_name, klen);
            }
        }
        else if (state == EQUALS) {
            if (c == '\n') {
                value [0] = 0;
                goto got_value;
            }
            if (! is_whitespace(c)) {
                state = VALUE;
                vlen = 0;
                addc(value, vlen);
            }
        }
        else if (state == VALUE) {
            if (c == '\n') {
                got_value:;
                int trailing = strlen(key_name);
                while (trailing > 1) {
                    trailing--;
                    if (is_whitespace(key_name [trailing])) {
                        key_name [trailing] = 0;
                    }
                    else {
                        break;
                    }
                }
                land_ini_set_string(ini, section_name, key_name, value);
                state = OUTSIDE;
            }
            else {
                addc(value, vlen);
            }
        }
        else if (state == COMMENT) {
            if (c == '\n') {
                state = OUTSIDE;
            }
        }
    }
    land_file_destroy(f);
    return ini;
}
LandIniFile* land_ini_new(char const * filename) {
    LandIniFile * ini = land_calloc(sizeof (* ini));
    ini->filename = land_strdup(filename);
    ini->sections = _new();
    return ini;
}
void land_ini_destroy(LandIniFile * ini) {
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(ini->sections->entries);
        for (LandIniEntry * e = LandArrayIterator_item(ini->sections->entries, &__iter0__); LandArrayIterator_next(ini->sections->entries, &__iter0__); e = LandArrayIterator_item(ini->sections->entries, &__iter0__)) {
            LandIniSection * s = e->val;
            _del(s);
            e->val = NULL;
        }
    }
    _del(ini->sections);
    land_free(ini->filename);
    land_free(ini);
}
void land_ini_writeback(LandIniFile * ini) {
    FILE * f = fopen(ini->filename, "wb");
    if (! f) {
        return ;
    }
    LandIniSection * ss = ini->sections;
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(ss->entries);
        for (LandIniEntry * es = LandArrayIterator_item(ss->entries, &__iter0__); LandArrayIterator_next(ss->entries, &__iter0__); es = LandArrayIterator_item(ss->entries, &__iter0__)) {
            LandIniSection * s = es->val;
            char * name = es->key;
            if (name && name [0]) {
                fprintf(f, "[%s]\n", name);
            }
            {
                LandArrayIterator __iter1__ = LandArrayIterator_first(s->entries);
                for (LandIniEntry * e = LandArrayIterator_item(s->entries, &__iter1__); LandArrayIterator_next(s->entries, &__iter1__); e = LandArrayIterator_item(s->entries, &__iter1__)) {
                    if (e->val) {
                        fprintf(f, "%s = %s\n", e->key, (char *) e->val);
                    }
                }
            }
        }
    }
    fclose(f);
}
LandIniFile* land_ini_app_settings(char const * appname) {
    char * name = platform_get_app_settings_file(appname);
    LandIniFile * ini = land_ini_read(name);
    land_free(name);
    return ini;
}
#undef addc
static int gul_debug;
#define D(_) \
    if (gul_debug) { \
        _; \
    }
static void ERR(char const * format, ...) {
    va_list args;
    va_start(args, format);
    char str [1024];
    vsnprintf(str, sizeof str, format, args);
    strcat(str, "\n");
    land_log_message(str);
    va_end(args);
}
void land_internal_land_gul_box_initialize(LandLayoutBox * self) {
    memset(self, 0, sizeof (* self));
}
void land_internal_land_gul_box_deinitialize(LandLayoutBox * self) {
    if (self->lookup_grid) {
        land_free(self->lookup_grid);
        self->lookup_grid = NULL;
    }
}
static void update_lookup_grid(LandWidget * self) {
    if (self->box.lookup_grid) {
        land_free(self->box.lookup_grid);
    }
    self->box.lookup_grid = NULL;
    if (self->box.flags & (GUL_NO_LAYOUT | GUL_HIDDEN)) {
        return ;
    }
    if (land_widget_is(self, LAND_WIDGET_ID_CONTAINER)) {
        LandWidgetContainer * container = LAND_WIDGET_CONTAINER(self);
        if (! container->children) {
            return ;
        }
        if (self->box.cols * self->box.rows == 0) {
            return ;
        }
        self->box.lookup_grid = land_calloc(self->box.cols * self->box.rows * sizeof (* self->box.lookup_grid));
        LandListItem * li = container->children->first;
        for (; li; li = li->next) {
            LandWidget * c = li->data;
            int i, j;
            if (c->box.flags & GUL_HIDDEN) {
                continue;
            }
            for (i = c->box.col; i <= c->box.col + c->box.extra_cols; i++) {
                for (j = c->box.row; j <= c->box.row + c->box.extra_rows; j++) {
                    self->box.lookup_grid [i + j * self->box.cols] = c;
                }
            }
        }
    }
}
static LandWidget* lookup_box_in_grid(LandWidget * self, int col, int row) {
    if (! self->box.lookup_grid) {
        update_lookup_grid(self);
    }
    if (! self->box.lookup_grid) {
        return NULL;
    }
    assert(col < self->box.cols && row < self->box.rows);
    return self->box.lookup_grid [row * self->box.cols + col];
}
static int row_min_height(LandWidget * self, int row) {
    int v = 0;
    for (int i = 0; i < self->box.cols; i += 1) {
        LandWidget * c = lookup_box_in_grid(self, i, row);
        if (c && c->box.current_min_height > v) {
            v = c->box.current_min_height;
        }
    }
    return v;
}
static int column_min_width(LandWidget * self, int col) {
    int v = 0;
    for (int i = 0; i < self->box.rows; i += 1) {
        LandWidget * c = lookup_box_in_grid(self, col, i);
        if (c && c->box.current_min_width > v) {
            v = c->box.current_min_width;
        }
    }
    return v;
}
static int is_column_expanding(LandWidget * self, int col) {
    int i;
    for (i = 0; i < self->box.rows; i++) {
        LandWidget * c = lookup_box_in_grid(self, col, i);
        if (c && c->box.col == col && ! (c->box.flags & GUL_SHRINK_X)) {
            return 1;
        }
    }
    return 0;
}
static int is_row_expanding(LandWidget * self, int row) {
    int i;
    for (i = 0; i < self->box.cols; i++) {
        LandWidget * c = lookup_box_in_grid(self, i, row);
        if (c && c->box.row == row && ! (c->box.flags & GUL_SHRINK_Y)) {
            return 1;
        }
    }
    return 0;
}
static int expanding_columns(LandWidget * self) {
    int i;
    int v = 0;
    for (i = 0; i < self->box.cols; i++) {
        if (is_column_expanding(self, i)) {
            v++;
        }
    }
    return v;
}
static int expanding_rows(LandWidget * self) {
    int i;
    int v = 0;
    for (i = 0; i < self->box.rows; i++) {
        if (is_row_expanding(self, i)) {
            v++;
        }
    }
    return v;
}
static int min_height(LandWidget * self) {
    int v = 0;
    for (int i = 0; i < self->box.rows; i += 1) {
        v += row_min_height(self, i);
    }
    if (self->element) {
        v += self->element->vgap * (self->box.rows - 1);
        v += self->element->it + self->element->ib;
    }
    return v;
}
static int min_width(LandWidget * self) {
    int v = 0;
    for (int i = 0; i < self->box.cols; i += 1) {
        v += column_min_width(self, i);
    }
    if (self->element) {
        v += self->element->hgap * (self->box.cols - 1);
        v += self->element->il + self->element->ir;
    }
    return v;
}
static int adjust_resize_width(LandWidget * self, int dx) {
    for (int i = 0; i < self->box.cols; i += 1) {
        int j;
        for (j = 0; j < self->box.rows; j++) {
            LandWidget * c = lookup_box_in_grid(self, i, j);
            if (c && c->box.flags & GUL_RESIZE) {
                c->box.current_min_width += dx;
                return 1;
            }
        }
    }
    return 0;
}
static int adjust_resize_height(LandWidget * self, int dx) {
    int j;
    for (j = 0; j < self->box.rows; j++) {
        int i;
        for (i = 0; i < self->box.cols; i++) {
            LandWidget * c = lookup_box_in_grid(self, i, j);
            if (c && c->box.flags & GUL_RESIZE) {
                c->box.current_min_height += dx;
                return 1;
            }
        }
    }
    return 0;
}
static void gul_box_bottom_up(LandWidget * self) {
    if (self->box.flags & GUL_HIDDEN) {
        self->box.current_min_width = 0;
        self->box.current_min_height = 0;
        return ;
    }
    LandWidgetContainer * container = NULL;
    if (land_widget_is(self, LAND_WIDGET_ID_CONTAINER)) {
        container = LAND_WIDGET_CONTAINER(self);
    }
    if ((self->box.flags & GUL_NO_LAYOUT) || ! container || ! container->children) {
        self->box.current_min_width = self->box.min_width;
        self->box.current_min_height = self->box.min_height;
        return ;
    }
    {
        LandListIterator __iter0__ = LandListIterator_first(container->children);
        for (LandWidget * c = LandListIterator_item(container->children, &__iter0__); LandListIterator_next(container->children, &__iter0__); c = LandListIterator_item(container->children, &__iter0__)) {
            gul_box_bottom_up(LAND_WIDGET(c));
        }
    }
    int min_w = min_width(self);
    int min_h = min_height(self);
    self->box.current_min_width = _scramble_max(self->box.min_width, min_w);
    self->box.current_min_height = _scramble_max(self->box.min_height, min_h);
}
static void gul_box_top_down(LandWidget * self) {
    /* Recursively fit in all child widgets into the given widget.
     */
    D(printf("Box (%s[%p]): %d[%d] x %d[%d] at %d/%d\n", self->vt->name, self, self->box.w, self->box.cols, self->box.h, self->box.rows, self->box.x, self->box.y));
    if (self->box.flags & GUL_HIDDEN) {
        D(printf("    hidden.\n"));
        return ;
    }
    if (self->box.flags & GUL_NO_LAYOUT) {
        D(printf("    no layout.\n"));
        return ;
    }
    if (self->box.cols == 0 || self->box.rows == 0) {
        D(printf("    empty.\n"));
        return ;
    }
    int minw = min_width(self);
    int minh = min_height(self);
    if (self->box.max_width && minw > self->box.max_width) {
        if (! adjust_resize_width(self, self->box.max_width - minw)) {
            ERR("Fatal: Minimum width of children (%d) ""exceeds available space (%d).", minw, self->box.max_width);
        }
    }
    if (self->box.max_height && minh > self->box.max_height) {
        if (! adjust_resize_height(self, self->box.max_height - minh)) {
            ERR("Fatal: Minimum height of children (%d) ""exceeds available space (%d).", minh, self->box.max_height);
        }
    }
    LandWidgetThemeElement * element = self->element;
    int available_width = self->box.w - minw;
    int available_height = self->box.h - minh;
    int want_width = expanding_columns(self);
    int want_height = expanding_rows(self);
    D(printf("    Children: %d (%d exp) x %d (%d exp)\n", self->box.cols, want_width, self->box.rows, want_height));
    D(printf("              %d x %d min\n", minw, minh));
    int i, j;
    int x = self->box.x;
    if (self->element) {
        x += element->il;
    }
    int share = 0;
    if (want_width) {
        share = available_width / want_width;
    }
    available_width -= share * want_width;
    D(printf("    Columns:"));
    int hgap = self->element ? element->hgap : 0;
    for (i = 0; i < self->box.cols; i++) {
        int cw = column_min_width(self, i);
        int cx = x;
        if (is_column_expanding(self, i)) {
            cw += share;
            if (available_width) {
                cw += 1;
                available_width -= 1;
            }
            D(printf(" <->%d", cw));
        }
        else {
            D(printf(" [-]%d", cw));
        }
        x += cw + hgap;
        for (j = 0; j < self->box.rows; j++) {
            LandWidget * c = lookup_box_in_grid(self, i, j);
            if (c && c->box.row == j) {
                if (c->box.col == i) {
                    c->box.w = cw;
                    land_widget_move(c, cx - c->box.x, 0);
                }
                else {
                    c->box.w += cw + hgap;
                }
            }
        }
    }
    D(printf("\n"));
    D(printf("    Rows:"));
    int y = self->box.y;
    if (self->element) {
        y += element->it;
    }
    share = 0;
    if (want_height) {
        share = available_height / want_height;
    }
    available_height -= share * want_height;
    int vgap = self->element ? element->vgap : 0;
    for (j = 0; j < self->box.rows; j++) {
        int ch = row_min_height(self, j);
        int cy = y;
        if (is_row_expanding(self, j)) {
            ch += share;
            if (available_height) {
                ch += 1;
                available_height -= 1;
            }
            D(printf(" <->%d", ch));
        }
        else {
            D(printf(" [-]%d", ch));
        }
        y += ch;
        y += vgap;
        for (i = 0; i < self->box.cols; i++) {
            LandWidget * c = lookup_box_in_grid(self, i, j);
            if (c && c->box.col == i) {
                if (c->box.row == j) {
                    c->box.h = ch;
                    land_widget_move(c, 0, cy - c->box.y);
                }
                else {
                    c->box.h += ch + vgap;
                }
            }
        }
    }
    D(printf("\n"));
    if (land_widget_is(self, LAND_WIDGET_ID_CONTAINER)) {
        LandWidgetContainer * container = LAND_WIDGET_CONTAINER(self);
        if (container->children) {
            LandListItem * li = container->children->first;
            for (; li; li = li->next) {
                LandWidget * c = li->data;
                if (c->box.w != c->box.ow || c->box.h != c->box.oh) {
                    land_call_method(c, layout_changing, (c));
                }
                gul_box_top_down(c);
                if (c->box.w != c->box.ow || c->box.h != c->box.oh) {
                    land_call_method(c, layout_changed, (c));
                    c->box.ow = c->box.w;
                    c->box.oh = c->box.h;
                }
                if (c->layout_hack) {
                    c->layout_hack = 0;
                    gul_box_top_down(c);
                }
            }
        }
    }
}
static void gul_box_fit_children(LandWidget * self) {
    D(printf("gul_box_fit_children %s[%p]\n", self->vt->name, self));
    gul_box_bottom_up(self);
    if (! (self->box.flags & GUL_STEADFAST)) {
        self->box.w = self->box.current_min_width;
        self->box.h = self->box.current_min_height;
    }
    if (self->no_layout_notify == 0) {
        land_call_method(self, layout_changing, (self));
    }
    gul_box_top_down(self);
    if (self->no_layout_notify == 0) {
        land_call_method(self, layout_changed, (self));
    }
}
void land_internal_gul_layout_updated(LandWidget * self) {
    /* This is used if the size of a widget may have changed and therefore its own
     * as well as its parent's layout needs updating.
     */
    D(printf("gul_layout_updated %s[%p]\n", self->vt->name, self));
    update_lookup_grid(self);
    if (self->parent && ! (self->parent->box.flags & GUL_NO_LAYOUT)) {
        if (self->no_layout_notify == 0) {
            self->no_layout_notify = 1;
            land_internal_gul_layout_updated(self->parent);
            self->no_layout_notify = 0;
        }
    }
    else {
        gul_box_fit_children(self);
    }
}
void land_internal_gul_layout_updated_during_layout(LandWidget * self) {
    /* FIXME: What the hell is this? Can't we do it the proper way?
     * If widgets are added or removed in the middle of a layout algorithm run,
     * this function should be called from within the layout_changed event.
     */
    update_lookup_grid(self);
    gul_box_bottom_up(self);
}
#undef D
static bool _get_data(LandHash * self, LandHashIterator * i, void * (* data)) {
    if (! self->entries) {
        return 0;
    }
    while (i->i < self->size) {
        if (self->entries [i->i]) {
            if (i->j < self->entries [i->i] [0].n) {
                if (data) {
                    * data = self->entries [i->i] [i->j].data;
                }
                return 1;
            }
            i->j = 0;
        }
        i->i++;
    }
    return 0;
}
static bool _get_key(LandHash * self, LandHashKeysIterator * i, void * (* data)) {
    if (! self->entries) {
        return 0;
    }
    while (i->i < self->size) {
        if (self->entries [i->i]) {
            if (i->j < self->entries [i->i] [0].n) {
                if (data) {
                    * data = self->entries [i->i] [i->j].thekey;
                }
                return 1;
            }
            i->j = 0;
        }
        i->i++;
    }
    return 0;
}
int land_hash_count(LandHash * self) {
    return self->count;
}
LandHashIterator LandHashIterator_first(LandHash * self) {
    LandHashIterator i = {0, 0};
    return i;
}
void* LandHashIterator_item(LandHash * self, LandHashIterator * i) {
    void * data = NULL;
    _get_data(self, i, & data);
    return data;
}
bool LandHashIterator_next(LandHash * self, LandHashIterator * i) {
    if (_get_data(self, i, NULL)) {
        i->j++;
        return 1;
    }
    return 0;
}
LandHashKeysIterator LandHashKeysIterator_first(LandHash * self) {
    LandHashKeysIterator i = {0, 0};
    return i;
}
void* LandHashKeysIterator_item(LandHash * self, LandHashKeysIterator * i) {
    void * data = NULL;
    _get_key(self, i, & data);
    return data;
}
bool LandHashKeysIterator_next(LandHash * self, LandHashKeysIterator * i) {
    if (_get_key(self, i, NULL)) {
        i->j++;
        return 1;
    }
    return 0;
}
#ifdef LAND_MEMLOG
#undef land_hash_new
#undef land_hash_destroy
LandHash* land_hash_new_memlog(char const * f, int l) {
    LandHash * hash = land_hash_new();
    land_memory_add(hash, "hash", 1, f, l);
    return hash;
}
void land_hash_destroy_memlog(LandHash * self, char const * f, int l) {
    land_hash_destroy(self);
    land_memory_remove(self, "hash", 1, f, l);
}
#endif
static unsigned int hash_function(LandHash * self, char const * thekey) {
    int i;
    unsigned int hash = 5381;
    for (i = 0; thekey [i]; i++) {
        unsigned char c = thekey [i];
        hash = hash * 33 + c;
    }
    return hash & (self->size - 1);
}
LandHash* land_hash_new(void) {
    /* """Create a new LandHash."""
     */
    LandHash * self;
    land_alloc(self);
    self->hash_function = hash_function;
    return self;
}
void land_hash_clear(LandHash * self) {
    /* Clears the hash. Referenced data are not touched though.
     */
    for (int i = 0; i < self->size; i += 1) {
        LandHashEntry * entry = self->entries [i];
        if (entry) {
            int j;
            for (j = 0; j < entry->n; j++) {
                land_free(entry [j].thekey);
            }
            land_free(entry);
        }
    }
    if (self->entries) {
        land_free(self->entries);
        self->entries = NULL;
    }
}
void land_hash_destroy(LandHash * self) {
    /* Destroy a LandHash. The data inside the hash are not freed (just
     * everything else, like key names and internal data structures).
     */
    if (! self) {
        return ;
    }
    land_hash_clear(self);
    land_free(self);
}
void* land_hash_insert(LandHash * self, char const * thekey, void * data) {
    /* """Insert data into a LandHash.
     * A LandHash simply is a mapping of keys to data pointers - it will never
     * touch the passed data in any way. E.g. you need to make sure to delete any
     * pointers you add to a hash. A copy of the passed key is made so you need
     * not keep it around.
     * If the key already exists, there will be two entries with the same key
     * from now on, and it is undefined behavior which one will get returned when
     * querying for the key."""
     */
    assert(thekey);
    int i;
    if ((self->count + 1) * 2 > self->size) {
        int oldsize = self->size;
        LandHashEntry * (* oldentries) = self->entries;
        if (! self->size) {
            self->bits = 1;
        }
        else {
            self->bits++;
        }
        self->size = 1 << self->bits;
        self->entries = land_calloc(self->size * sizeof (* self->entries));
        self->count = 0;
        for (i = 0; i < oldsize; i++) {
            LandHashEntry * entry = oldentries [i];
            if (entry) {
                int j;
                for (j = 0; j < entry [0].n; j++) {
                    land_hash_insert(self, entry [j].thekey, entry [j].data);
                    land_free(entry [j].thekey);
                }
                land_free(entry);
            }
        }
        if (oldentries) {
            land_free(oldentries);
        }
    }
    i = self->hash_function(self, thekey);
    LandHashEntry * entry = self->entries [i];
    int n = entry ? entry->n + 1 : 1;
    self->entries [i] = land_realloc(entry, n * sizeof (* entry));
    self->entries [i] [n - 1].thekey = land_strdup(thekey);
    self->entries [i] [n - 1].data = data;
    self->entries [i] [0].n = n;
    self->count++;
    return data;
}
void* land_hash_remove(LandHash * self, char const * thekey) {
    /* """Remove the first entry found with the key, and return the associated
     * data.
     * The returned pointer might need to be destroyed after you remove it from
     * the hash, if it has no more use.
     */
    if (! self->size) {
        return NULL;
    }
    int i = self->hash_function(self, thekey);
    if (! self->entries [i]) {
        return NULL;
    }
    int n = self->entries [i] [0].n;
    int j;
    for (j = 0; j < n; j++) {
        if (! strcmp(self->entries [i] [j].thekey, thekey)) {
            void * data = self->entries [i] [j].data;
            land_free(self->entries [i] [j].thekey);
            if (n > 1) {
                self->entries [i] [j] = self->entries [i] [n - 1];
                self->entries [i] = land_realloc(self->entries [i], (n - 1) * sizeof (* self->entries [i]));
                self->entries [i] [0].n = n - 1;
            }
            else {
                land_free(self->entries [i]);
                self->entries [i] = NULL;
            }
            self->count--;
            return data;
        }
    }
    return NULL;
}
static LandHashEntry* land_hash_get_entry(LandHash * self, char const * thekey) {
    if (! self->size || ! thekey) {
        return NULL;
    }
    int i = self->hash_function(self, thekey);
    if (! self->entries [i]) {
        return NULL;
    }
    int j;
    for (j = 0; j < self->entries [i] [0].n; j++) {
        if (! strcmp(self->entries [i] [j].thekey, thekey)) {
            return & self->entries [i] [j];
        }
    }
    return NULL;
}
void* land_hash_replace(LandHash * self, char const * thekey, void * data) {
    /* If an association to the given key exists, replace it with the given data,
     * and return the old data.
     * Else, do the same as land_hash_insert, and return None.
     */
    LandHashEntry * entry = land_hash_get_entry(self, thekey);
    if (entry) {
        void * old = entry->data;
        entry->data = data;
        return old;
    }
    land_hash_insert(self, thekey, data);
    return NULL;
}
void* land_hash_get(LandHash * self, char const * thekey) {
    /* """Return the data associated with a hash key. If the key exists multiple
     * times, it can be not relied on a certain one being returned. It might always
     * be the same, but it might not be - this is especially true if other entries
     * are added which could lead to a re-hashing when it gets too full.
     * If the key is not found, None is returned.
     */
    LandHashEntry * entry = land_hash_get_entry(self, thekey);
    if (entry) {
        return entry->data;
    }
    return NULL;
}
int land_hash_has(LandHash * self, char const * thekey) {
    LandHashEntry * entry = land_hash_get_entry(self, thekey);
    if (entry) {
        return 1;
    }
    return 0;
}
LandArray* land_hash_keys(LandHash * hash, bool alloc) {
    /* """Return an array containing all the keys in the hash.
     * If alloc is true, each key is allocated and must be freed. A
     * convenient way is to use land_array_destroy_with_strings.
     * Otherwise the strings are direct pointers into the hash - so you
     * must not modify or free them, and they will get invalid if the hash
     * is modifed (even just by adding/removing an unrelated key).
     * You are responsible for destroying the array with land_array_destroy
     * when you are done using it.
     */
    LandArray * array = land_array_new();
    int i;
    for (i = 0; i < hash->size; i++) {
        if (hash->entries [i]) {
            int n = hash->entries [i]->n;
            int j;
            for (j = 0; j < n; j++) {
                char * key = hash->entries [i] [j].thekey;
                if (alloc) {
                    key = land_strdup(key);
                }
                land_array_add_data(& array, key);
            }
        }
    }
    return array;
}
LandArray* land_hash_data(LandHash * hash) {
    /* """Return an array with all the data pointers in the hash. If you want to
     * destroy a hash including all its data, this may be a convenient way to
     * do it:
     * LandArray *data = land_hash_data(hash)
     * for int i = 0 while i < land_array_count(data) with i++:
     * void *entry = land_array_get_nth(data, i)
     * land_free(entry)
     * land_array_destroy(data)
     * land_hash_destroy(hash)
     */
    LandArray * array = land_array_new();
    int i;
    for (i = 0; i < hash->size; i++) {
        if (hash->entries [i]) {
            int n = hash->entries [i]->n;
            int j;
            for (j = 0; j < n; j++) {
                land_array_add_data(& array, hash->entries [i] [j].data);
            }
        }
    }
    return array;
}
void land_hash_print_stats(LandHash * hash) {
    /* """This is an internal function for debugging purposes, which will print
     * out statistics about the hash to the console.
     */
    int i;
    int u = 0;
    int c = 0;
    int l = 0;
    for (i = 0; i < hash->size; i++) {
        if (hash->entries [i]) {
            int n = hash->entries [i]->n;
            if (n > 1) {
                c += n;
            }
            if (n > l) {
                l = n;
            }
            u++;
        }
    }
    printf("hash stats: %d/%d[%d%%] full, %d/%d[%d%%] used, %d/%d[%d%%] colliding, longest chain is %d.\n", hash->count, hash->size, hash->size ? 100 * hash->count / hash->size : 0, u, hash->size, hash->size ? 100 * u / hash->size : 0, c, hash->count, hash->count ? 100 * c / hash->count : 0, l);
}
struct PlatformThread {
    LandThread super;
    ALLEGRO_THREAD * a5;
};
struct PlatformLock {
    ALLEGRO_MUTEX * a5;
    ALLEGRO_COND * cond;
    bool triggered;
};
static void* proc(void * data) {
    LandThread * t = data;
    t->cb(t->data);
    land_free(t);
    return NULL;
}
void platform_thread_run(void(* cb)(void *), void * data) {
    LandThread * t;
    land_alloc(t);
    t->cb = cb;
    t->data = data;
    al_run_detached_thread(proc, t);
}
static void* aproc(ALLEGRO_THREAD * thread, void * arg) {
    LandThread * t = arg;
    t->cb(t->data);
    return NULL;
}
LandThread* platform_thread_new(void(* cb)(void * data), void * data) {
    PlatformThread * t;
    land_alloc(t);
    t->super.cb = cb;
    t->super.data = data;
    t->a5 = al_create_thread(aproc, t);
    al_start_thread(t->a5);
    return & t->super;
}
void platform_thread_wait_until_complete(LandThread * self) {
    PlatformThread * t = (void *) self;
    al_join_thread(t->a5, NULL);
}
void platform_thread_destroy(LandThread * self) {
    PlatformThread * t = (void *) self;
    al_destroy_thread(t->a5);
    land_free(self);
}
LandLock* platform_thread_new_lock(void) {
    PlatformLock * l;
    land_alloc(l);
    l->a5 = al_create_mutex();
    return (void *) l;
}
LandLock* platform_thread_new_waitable_lock(void) {
    PlatformLock * l;
    land_alloc(l);
    l->a5 = al_create_mutex();
    l->cond = al_create_cond();
    return (void *) l;
}
void platform_thread_delete_lock(LandLock * lock) {
    PlatformLock * l = (void *) lock;
    al_destroy_mutex(l->a5);
    if (l->cond) {
        al_destroy_cond(l->cond);
    }
    land_free(l);
}
void platform_thread_lock(LandLock * lock) {
    PlatformLock * l = (void *) lock;
    al_lock_mutex(l->a5);
}
void platform_thread_unlock(LandLock * lock) {
    PlatformLock * l = (void *) lock;
    al_unlock_mutex(l->a5);
}
void platform_thread_wait_lock(LandLock * lockp) {
    PlatformLock * lock = (void *) lockp;
    al_lock_mutex(lock->a5);
    while (! lock->triggered) {
        al_wait_cond(lock->cond, lock->a5);
    }
    lock->triggered = 0;
    al_unlock_mutex(lock->a5);
}
void platform_thread_trigger_lock(LandLock * lockp) {
    PlatformLock * lock = (void *) lockp;
    al_lock_mutex(lock->a5);
    lock->triggered = 1;
    al_broadcast_cond(lock->cond);
    al_unlock_mutex(lock->a5);
}
static LandGridInterface * land_grid_vtable_sprites;
LandGrid* land_sprites_grid_new(int cell_w, int cell_h, int x_cells, int y_cells) {
    LandSpritesGrid * self;
    land_alloc(self);
    land_grid_initialize(& self->super, cell_w, cell_h, x_cells, y_cells);
    self->super.vt = land_grid_vtable_sprites;
    self->sprites = land_calloc(x_cells * y_cells * sizeof (* self->sprites));
    return & self->super;
}
void land_sprites_grid_resize(LandGrid * super, int cell_w, int cell_h, int x_cells, int y_cells) {
    LandSpritesGrid * self = (void *) super;
    land_sprites_grid_clear(super);
    land_free(self->sprites);
    self->sprites = land_calloc(x_cells * y_cells * sizeof (* self->sprites));
    super->x_cells = x_cells;
    super->y_cells = y_cells;
    super->cell_w = cell_w;
    super->cell_h = cell_h;
}
void land_sprites_grid_clear(LandGrid * super) {
    /* Removes all sprites from a grid, but does not destroy them.
     */
    LandSpritesGrid * self = LAND_SPRITES_GRID(super);
    int j;
    for (j = 0; j < super->x_cells * super->y_cells; j++) {
        if (self->sprites [j]) {
            land_list_destroy(self->sprites [j]);
            self->sprites [j] = NULL;
        }
    }
}
void land_sprites_grid_del(LandGrid * super) {
    /* Deletes a sprites grid. The sprites themselves are not destroyed.
     */
    land_sprites_grid_clear(super);
    LandSpritesGrid * self = LAND_SPRITES_GRID(super);
    land_free(self->sprites);
    land_free(self);
}
static void dummy(LandSprite * self, LandView * view) {
    float x = (self->x - view->scroll_x) * view->scale_x + view->x;
    float y = (self->y - view->scroll_y) * view->scale_y + view->y;
    land_color(1, 0, 0, 1);
    land_rectangle(x, y, x + self->type->w * view->scale_x, y + self->type->h * view->scale_y);
}
static void dummy_image(LandSprite * self, LandView * view) {
    LandSpriteTypeImage * image = LAND_SPRITE_TYPE_IMAGE(self->type);
    float x = (self->x - view->scroll_x) * view->scale_x + view->x;
    float y = (self->y - view->scroll_y) * view->scale_y + view->y;
    land_image_draw_scaled_rotated_tinted_flipped(image->image, x, y, view->scale_x, view->scale_y, self->angle, view->r, view->g, view->b, view->a, self->flipped);
    #ifdef DEBUG_MASK
    if (image->image->mask) {
        land_image_debug_pixelmask(image->image, x, y, self->angle, self->flipped);
    }
    #endif
}
static void dummy_animation(LandSprite * self, LandView * view) {
    LandSpriteTypeAnimation * animation = (LandSpriteTypeAnimation *) self->type;
    LandSpriteAnimated * animated = (LandSpriteAnimated *) self;
    float x = (self->x - view->scroll_x) * view->scale_x + view->x;
    float y = (self->y - view->scroll_y) * view->scale_y + view->y;
    LandImage * image;
    if (animation->animation) {
        image = land_animation_get_frame(animation->animation, animated->frame);
    }
    else {
        image = animation->super.image;
    }
    land_image_draw_scaled_rotated_tinted(image, x, y, animated->sx * view->scale_x, animated->sy * view->scale_y, self->angle, animated->r * view->r, animated->g * view->g, animated->b * view->b, animated->a * view->b);
    #ifdef DEBUG_MASK
    if (animation->super.image->mask) {
        land_image_debug_pixelmask(animation->super.image, x, y, self->angle, self->flipped);
    }
    #endif
}
void land_sprite_initialize(LandSprite * self, LandSpriteType * type) {
    self->type = type;
    if (self->type->initialize) {
        self->type->initialize(self);
    }
}
LandSprite* land_sprite_new(LandSpriteType * type) {
    if (! strcmp(type->name, "animation")) {
        return land_sprite_animated_new(type);
    }
    LandSprite * self;
    land_alloc(self);
    land_sprite_initialize(self, type);
    return self;
}
LandSprite* land_sprite_with_image_new(LandSpriteType * type, LandImage * image) {
    LandSpriteWithImage * self;
    land_alloc(self);
    land_sprite_initialize(LAND_SPRITE(self), type);
    self->image = image;
    return LAND_SPRITE(self);
}
void land_sprite_image_destroy(LandSprite * self) {
    ;
}
void land_sprite_image_initialize(LandSprite * super) {
    ;
}
void land_sprite_animated_initialize(LandSprite * super) {
    LandSpriteAnimated * self = (void *) super;
    self->sx = 1;
    self->sy = 1;
    self->r = 1;
    self->g = 1;
    self->b = 1;
    self->a = 1;
}
LandSprite* land_sprite_animated_new(LandSpriteType * type) {
    LandSpriteAnimated * self;
    land_alloc(self);
    land_sprite_initialize(LAND_SPRITE(self), type);
    return LAND_SPRITE(self);
}
void land_sprite_animated_destroy(LandSprite * sprite) {
    ;
}
void land_sprite_del(LandSprite * self) {
    /* Destroys a sprite. This will not remove its reference from a grid in case it
     * is inside one - so only use this if you know what you are doing.
     */
    if (self->type->destroy) {
        self->type->destroy(self);
    }
    land_free(self);
}
void land_sprite_destroy(LandSprite * self) {
    /* """Same as land_sprite_del."""
     */
    land_sprite_del(self);
}
void land_sprite_show(LandSprite * self, LandGrid * grid) {
    if (self->shown) {
        return ;
    }
    land_sprite_place_into_grid(self, grid, self->x, self->y);
}
void land_sprite_hide(LandSprite * self, LandGrid * grid) {
    if (! self->shown) {
        return ;
    }
    land_sprite_remove_from_grid(self, grid);
}
int land_sprite_overlap_pixelperfect(LandSprite * self, LandSprite * other) {
    /* Given two sprites who have a type LandSpriteTypeImage, do a pixel overlap
     * test, and return 0 if they don't overlap.
     */
    return land_image_overlaps(LAND_SPRITE_TYPE_IMAGE (self->type)->image, self->x, self->y, self->angle, self->flipped, LAND_SPRITE_TYPE_IMAGE (other->type)->image, other->x, other->y, other->angle, other->flipped);
}
void land_sprite_grid_ysorted(LandGrid * self) {
    LandSpritesGrid * sg = (void *) self;
    sg->ysorted = 1;
}
static void get_grid_extents(LandSprite * self, LandGrid * grid, int * tl, int * tt, int * tr, int * tb) {
    float l = self->x - self->type->x;
    float t = self->y - self->type->y;
    float r = self->x - self->type->x + self->type->w;
    float b = self->y - self->type->y + self->type->h;
    * tl = l / grid->cell_w;
    * tt = t / grid->cell_h;
    * tr = r / grid->cell_w;
    * tb = b / grid->cell_h;
    if (* tl < 0) {
        * tl = 0;
    }
    if (* tt < 0) {
        * tt = 0;
    }
    if (* tr >= grid->x_cells) {
        * tr = grid->x_cells - 1;
    }
    if (* tb >= grid->y_cells) {
        * tb = grid->y_cells - 1;
    }
}
LandArray* land_sprites_grid_get_all(LandGrid * sprites_grid) {
    LandArray * a = land_array_new();
    LandSpritesGrid * grid = LAND_SPRITES_GRID(sprites_grid);
    grid->tag++;
    for (int ty = 0; ty < sprites_grid->y_cells; ty++) {
        for (int tx = 0; tx < sprites_grid->x_cells; tx++) {
            LandList * list = grid->sprites [ty * grid->super.x_cells + tx];
            if (list) {
                LandListItem * item = list->first;
                while (item) {
                    LandSprite * sprite = item->data;
                    if (sprite->tag != grid->tag) {
                        sprite->tag = grid->tag;
                        land_array_add(a, sprite);
                    }
                    item = item->next;
                }
            }
        }
    }
    return a;
}
LandList* land_sprites_grid_overlap(LandSprite * self, LandGrid * sprites_grid) {
    /* Return a list of all sprites overlapping the sprite in the given grid.
     * The sprite itself is not returned.
     * Note that this only works if all sprites in the grid have compatible
     * overlapping, e.g. if the sprite does a pixel-overlap test, then all other
     * sprites must have a pixel mask, or if it does a bounding circle test, then
     * all other sprites must have a bounding circle, and so on.
     */
    LandSpritesGrid * grid = LAND_SPRITES_GRID(sprites_grid);
    LandList * retlist = NULL;
    int tl, tt, tr, tb;
    get_grid_extents(self, sprites_grid, & tl, & tt, & tr, & tb);
    grid->tag++;
    for (int ty = tt; ty <= tb; ty++) {
        for (int tx = tl; tx <= tr; tx++) {
            LandList * list = grid->sprites [ty * grid->super.x_cells + tx];
            if (list) {
                LandListItem * item = list->first;
                while (item) {
                    LandSprite * other = item->data;
                    if (other != self && other->tag != grid->tag) {
                        other->tag = grid->tag;
                        if (self->type->overlap(self, other)) {
                            land_add_list_data(& retlist, other);
                        }
                    }
                    item = item->next;
                }
            }
        }
    }
    return retlist;
}
LandList* land_sprites_grid_get_circle(LandGrid * sprites_grid, float x, float y, float radius) {
    /* Return a list of all sprites in the grid, whose position is inside the given
     * circle (in pixels). The size of the sprite is currently ignored, only its
     * point position is used.
     */
    LandSpritesGrid * grid = LAND_SPRITES_GRID(sprites_grid);
    LandList * retlist = NULL;
    float l = x - radius;
    float t = y - radius;
    float r = x + radius;
    float b = y + radius;
    int tl = l / grid->super.cell_w;
    int tt = t / grid->super.cell_h;
    int tr = r / grid->super.cell_w;
    int tb = b / grid->super.cell_h;
    int tx, ty;
    if (tl < 0) {
        tl = 0;
    }
    if (tt < 0) {
        tt = 0;
    }
    if (tr >= grid->super.x_cells) {
        tr = grid->super.x_cells - 1;
    }
    if (tb >= grid->super.y_cells) {
        tb = grid->super.y_cells - 1;
    }
    grid->tag++;
    for (ty = tt; ty <= tb; ty++) {
        for (tx = tl; tx <= tr; tx++) {
            LandList * list = grid->sprites [ty * grid->super.x_cells + tx];
            if (list) {
                LandListItem * item = list->first;
                while (item) {
                    LandSprite * other = item->data;
                    if (other->tag != grid->tag) {
                        other->tag = grid->tag;
                        float dx = other->x - x;
                        float dy = other->y - y;
                        if (dx * dx + dy * dy < radius * radius) {
                            land_add_list_data(& retlist, other);
                        }
                    }
                    item = item->next;
                }
            }
        }
    }
    return retlist;
}
LandList* land_sprites_grid_get_rectangle(LandGrid * sprites_grid, float l, float t, float r, float b) {
    /* Return a list of all sprites in the given rectangle. All the sprites who
     * are in one of the grid cells overlapped by the rectangle are returned.
     */
    LandSpritesGrid * grid = LAND_SPRITES_GRID(sprites_grid);
    LandList * retlist = NULL;
    int tl = l / grid->super.cell_w;
    int tt = t / grid->super.cell_h;
    int tr = r / grid->super.cell_w;
    int tb = b / grid->super.cell_h;
    int tx, ty;
    if (tl < 0) {
        tl = 0;
    }
    if (tt < 0) {
        tt = 0;
    }
    if (tr >= grid->super.x_cells) {
        tr = grid->super.x_cells - 1;
    }
    if (tb >= grid->super.y_cells) {
        tb = grid->super.y_cells - 1;
    }
    grid->tag++;
    for (ty = tt; ty <= tb; ty++) {
        for (tx = tl; tx <= tr; tx++) {
            LandList * list = grid->sprites [ty * grid->super.x_cells + tx];
            if (list) {
                LandListItem * item = list->first;
                while (item) {
                    LandSprite * other = item->data;
                    if (other->tag != grid->tag) {
                        other->tag = grid->tag;
                        land_add_list_data(& retlist, other);
                    }
                    item = item->next;
                }
            }
        }
    }
    return retlist;
}
LandList* land_sprites_grid_get_in_cell(LandGrid * grid, int cx, int cy) {
    LandList * retlist = NULL;
    LandSpritesGrid * sgrid = LAND_SPRITES_GRID(grid);
    if (cx < 0 || cy < 0 || cx >= grid->x_cells || cy >= grid->y_cells) {
        return NULL;
    }
    LandList * list = sgrid->sprites [cy * grid->x_cells + cx];
    if (list) {
        LandListItem * item = list->first;
        while (item) {
            LandSprite * s = item->data;
            int sx = s->x / grid->cell_w;
            int sy = s->y / grid->cell_h;
            if (sx == cx && sy == cy) {
                land_add_list_data(& retlist, s);
            }
            item = item->next;
        }
    }
    return retlist;
}
static int is_left(float ax, float ay, float bx, float by) {
    return ax * by - ay * bx < 0;
}
static int is_in_triangle(float x, float y, float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) {
    if (is_left(x - p1x, y - p1y, p2x - p1x, p2y - p1y) && is_left(x - p2x, y - p2y, p3x - p2x, p3y - p2y) && is_left(x - p3x, y - p3y, p1x - p3x, p1y - p3y)) {
        return 1;
    }
    return 0;
}
LandList* land_sprites_get_triangle(LandGrid * sprites_grid, float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) {
    /* Return a list of all sprites in the given triangle. This only considers
     * center positions, the size/shape of sprites is completely ignored.
     */
    LandSpritesGrid * grid = LAND_SPRITES_GRID(sprites_grid);
    int l = p1x, t = p1y, r = p1y, b = p1y;
    if (p2x < l) {
        l = p2x;
    }
    if (p3x < l) {
        l = p3x;
    }
    if (p2x > r) {
        r = p2x;
    }
    if (p3x > r) {
        r = p3x;
    }
    if (p2y < t) {
        t = p2y;
    }
    if (p3y < t) {
        t = p3y;
    }
    if (p2y > b) {
        b = p2y;
    }
    if (p3y > b) {
        b = p3y;
    }
    int tl = l / grid->super.cell_w;
    int tt = t / grid->super.cell_h;
    int tr = r / grid->super.cell_w;
    int tb = b / grid->super.cell_h;
    if (tl < 0) {
        tl = 0;
    }
    if (tt < 0) {
        tt = 0;
    }
    if (tr >= grid->super.x_cells) {
        tr = grid->super.x_cells - 1;
    }
    if (tb >= grid->super.y_cells) {
        tb = grid->super.y_cells - 1;
    }
    LandList * retlist = NULL;
    grid->tag++;
    for (int ty = tt; ty <= tb; ty++) {
        for (int tx = tl; tx <= tr; tx++) {
            LandList * list = grid->sprites [ty * grid->super.x_cells + tx];
            if (! list) {
                continue;
            }
            LandListItem * item = list->first;
            while (item) {
                LandSprite * other = item->data;
                if (other->tag != grid->tag) {
                    other->tag = grid->tag;
                    if (is_in_triangle(other->x, other->y, p1x, p1y, p2x, p2y, p3x, p3y)) {
                        land_add_list_data(& retlist, other);
                    }
                }
                item = item->next;
            }
        }
    }
    return retlist;
}
LandList* land_sprites_grid_get_in_view(LandGrid * sprites_grid, LandView * view, float l, float t, float r, float b) {
    l += view->scroll_x + view->x;
    t += view->scroll_y + view->y;
    r += view->scroll_x + view->x + view->w;
    b += view->scroll_y + view->y + view->h;
    return land_sprites_grid_get_rectangle(sprites_grid, l, t, r, b);
}
static void grid_place(LandSprite * self, LandSpritesGrid * grid) {
    /* Place a sprite into a grid.
     * Note: This *must not* be called more than once for a sprite.
     * Note: The sprite *must not* change position or dimension after calling this.
     * Note: Use grid_unplace to undo the effect of this function.
     */
    int tl, tt, tr, tb;
    get_grid_extents(self, & grid->super, & tl, & tt, & tr, & tb);
    self->shown = 1;
    for (int ty = tt; ty <= tb; ty++) {
        for (int tx = tl; tx <= tr; tx++) {
            if (grid->ysorted) {
                LandList * list = grid->sprites [ty * grid->super.x_cells + tx];
                if (! list) {
                    list = land_list_new();
                    grid->sprites [ty * grid->super.x_cells + tx] = list;
                }
                LandListItem * item = land_listitem_new(self);
                LandListItem * i = list->first;
                while (i) {
                    LandSprite * ls = i->data;
                    if (ls->y > self->y) {
                        land_list_insert_item_before(list, item, i);
                        goto done;
                    }
                    if (ls->y == self->y) {
                        if (ls->x > self->x) {
                            land_list_insert_item_before(list, item, i);
                            goto done;
                        }
                    }
                    i = i->next;
                }
                land_list_insert_item(list, item);
                done:;
            }
            else {
                land_add_list_data(& grid->sprites [ty * grid->super.x_cells + tx], self);
            }
        }
    }
}
static void grid_unplace(LandSprite * self, LandSpritesGrid * grid) {
    /* Remove a sprite from a grid.
     */
    int tl, tt, tr, tb;
    get_grid_extents(self, & grid->super, & tl, & tt, & tr, & tb);
    for (int ty = tt; ty <= tb; ty++) {
        for (int tx = tl; tx <= tr; tx++) {
            land_remove_list_data(& grid->sprites [ty * grid->super.x_cells + tx], self);
        }
    }
    self->shown = 0;
}
void land_sprite_remove_from_grid(LandSprite * self, LandGrid * grid) {
    /* Remove a sprite from a grid.
     */
    grid_unplace(self, LAND_SPRITES_GRID(grid));
}
void land_sprite_place_into_grid(LandSprite * self, LandGrid * grid, float x, float y) {
    /* Place a sprite into a grid.
     */
    self->x = x;
    self->y = y;
    grid_place(self, (LandSpritesGrid *) grid);
}
void land_sprite_move(LandSprite * self, LandGrid * grid, float x, float y) {
    /* Move a sprite by the given amount of pixels
     */
    grid_unplace(self, (LandSpritesGrid *) grid);
    self->x += x;
    if (self->x < 0) {
        self->x = 0;
    }
    if (self->x >= grid->cell_w * grid->x_cells) {
        self->x = grid->cell_w * grid->x_cells - 1;
    }
    self->y += y;
    if (self->y < 0) {
        self->y = 0;
    }
    if (self->y >= grid->cell_h * grid->y_cells) {
        self->y = grid->cell_h * grid->y_cells - 1;
    }
    grid_place(self, (LandSpritesGrid *) grid);
}
void land_sprite_move_to(LandSprite * self, LandGrid * grid, float x, float y) {
    /* Move a sprite to the given position in pixels.
     */
    land_sprite_move(self, grid, x - self->x, y - self->y);
}
void land_sprites_grid_draw_cell(LandSpritesGrid * self, LandView * view, int cell_x, int cell_y, float pixel_x, float pixel_y) {
    LandList * list = self->sprites [cell_y * self->super.x_cells + cell_x];
    if (list) {
        LandListItem * item = list->first;
        while (item) {
            LandSprite * sprite = item->data;
            if (sprite->tag != self->tag) {
                sprite->type->draw(sprite, view);
                sprite->tag = self->tag;
            }
            item = item->next;
        }
    }
}
void land_sprites_grid_draw(LandGrid * super, LandView * view) {
    LandSpritesGrid * self = (void *) super;
    self->tag++;
    land_grid_draw_normal(super, view);
}
static void get_cell_at(LandGrid * self, LandView * view, float view_x, float view_y, float * cell_x, float * cell_y) {
    float x = view->scroll_x + (view_x - view->x) / view->scale_x;
    float y = view->scroll_y + (view_y - view->y) / view->scale_y;
    * cell_x = x / self->cell_w;
    * cell_y = y / self->cell_h;
}
void land_sprites_init(void) {
    land_log_message("land_sprites_init\n");
    land_alloc(land_grid_vtable_sprites);
    land_grid_vtable_sprites->draw = land_sprites_grid_draw;
    land_grid_vtable_sprites->draw_cell = (void *) land_sprites_grid_draw_cell;
    land_grid_vtable_sprites->del = land_sprites_grid_del;
    land_grid_vtable_sprites->get_cell_at = get_cell_at;
}
void land_sprites_exit(void) {
    land_log_message("land_sprites_exit\n");
    land_free(land_grid_vtable_sprites);
}
LandSpriteType* land_spritetype_new(void) {
    LandSpriteType * self;
    land_alloc(self);
    self->name = "dummy";
    self->draw = dummy;
    return self;
}
void land_spritetype_destroy(LandSpriteType * self) {
    if (self->destroy_type) {
        self->destroy_type(self);
    }
    land_free(self->name);
    land_free(self);
}
LandSpriteTypeWithImage* land_spritetype_with_image_new(void) {
    return NULL;
}
void land_spritetype_image_initialize(LandSpriteType * super, LandImage * image, bool mask, int n) {
    LandSpriteTypeImage * self = (void *) super;
    super->draw = dummy_image;
    super->overlap = land_sprite_overlap_pixelperfect;
    super->destroy = land_sprite_image_destroy;
    super->initialize = land_sprite_image_initialize;
    if (image) {
        super->x = image->x - image->l;
        super->y = image->y - image->t;
        super->w = land_image_width(image) - image->l - image->r;
        super->h = land_image_height(image) - image->t - image->b;
    }
    else {
        super->x = super->y = super->w = super->h = 0;
    }
    self->image = image;
    LAND_SPRITE_TYPE (self)->name = land_strdup("image");
    if (image && mask && ! image->mask) {
        land_image_create_pixelmasks(image, n, 128);
    }
}
LandSpriteType* land_spritetype_image_new(LandImage * image, bool mask, int n) {
    LandSpriteTypeImage * self;
    land_alloc(self);
    land_spritetype_image_initialize((void *) self, image, mask, n);
    return LAND_SPRITE_TYPE(self);
}
void land_spritetype_animation_initialize(LandSpriteType * super, LandAnimation * animation, LandImage * image, bool mask, int n) {
    LandSpriteTypeAnimation * self = (void *) super;
    if (! image && animation) {
        image = land_animation_get_frame(animation, 0);
    }
    land_spritetype_image_initialize((void *) self, image, 0, 0);
    if (image && mask) {
        land_image_create_pixelmasks(image, n, 128);
    }
    super->draw = dummy_animation;
    super->destroy = land_sprite_animated_destroy;
    super->destroy_type = land_spritetype_animation_destroy;
    super->initialize = land_sprite_animated_initialize;
    self->animation = animation;
    land_free(super->name);
    super->name = land_strdup("animation");
}
LandSpriteType* land_spritetype_animation_new(LandAnimation * animation, LandImage * image, bool mask, int n) {
    /* Create a new animation sprite type with the given animation. The
     * image is used for collision detection. If no image is given, the
     * first frame of the animation is used instead.
     * If a mask is request n is the number of rotations. A negative n
     * means two variants are made for each rotation (one flipped
     * horizontally).
     */
    LandSpriteTypeAnimation * self;
    land_alloc(self);
    land_spritetype_animation_initialize((void *) self, animation, image, mask, n);
    return LAND_SPRITE_TYPE(self);
}
void land_spritetype_animation_destroy(LandSpriteType * base) {
    LandSpriteTypeAnimation * self = LAND_SPRITE_TYPE_ANIMATION(base);
    if (self->animation) {
        land_animation_destroy(self->animation);
    }
}
static jmp_buf exception;
static char exception_string [1024];
int(* land_exception_handler)(char const * str);
static int init = 1;
int land_default_exception_handler(char const * str) {
    fprintf(stderr, "%s\n", str);
    return 1;
}
void land_exception_handler_init(void) {
    again:;
    if (setjmp(exception)) {
        int r = land_exception_handler(exception_string);
        if (! r) {
            goto again;
        }
        abort();
    }
}
void land_exception_handler_set(int(* handler)(char const * str)) {
    if (init) {
        land_exception_handler_init();
        init = 0;
    }
    land_exception_handler = handler;
}
void land_exception(char const * format, ...) {
    va_list args;
    va_start(args, format);
    vsnprintf(exception_string, 1024, format, args);
    va_end(args);
    fprintf(stderr, "%s", exception_string);
    int r = land_exception_handler(exception_string);
    if (r) {
        abort();
    }
}
void land_popup(str title, str message) {
    platform_popup(title, message);
}
LandHash* land_yaml_get_mapping(LandYamlEntry * self) {
    assert(self->type == YamlMapping);
    return self->mapping;
}
LandHash* land_yaml_get_if_mapping(LandYamlEntry * self) {
    if (! self || self->type != YamlMapping) {
        return NULL;
    }
    return self->mapping;
}
LandArray* land_yaml_get_sequence(LandYamlEntry * self) {
    assert(self->type == YamlSequence);
    return self->sequence;
}
LandArray* land_yaml_get_if_sequence(LandYamlEntry * self) {
    if (! self || self->type != YamlSequence) {
        return NULL;
    }
    return self->sequence;
}
char const* land_yaml_get_scalar(LandYamlEntry * self) {
    assert(self->type == YamlScalar);
    return self->scalar;
}
char const* land_yaml_get_if_scalar(LandYamlEntry * self) {
    if (! self || self->type != YamlScalar) {
        return NULL;
    }
    return self->scalar;
}
int land_yaml_get_scalar_int(LandYamlEntry * self) {
    if (! self) {
        return 0;
    }
    return strtol(self->scalar, NULL, 0);
}
double land_yaml_get_scalar_double(LandYamlEntry * self) {
    return strtod(self->scalar, NULL);
}
char const* land_yaml_get_scalar_nth(LandArray * s, int i) {
    return land_yaml_get_scalar(land_array_get_nth(s, i));
}
double land_yaml_get_scalar_nth_double(LandArray * s, int i) {
    return land_yaml_get_scalar_double(land_array_get_nth(s, i));
}
LandYamlEntry* land_yaml_get_entry(LandYamlEntry * self, char const * name) {
    return land_hash_get(self->mapping, name);
}
char const* land_yaml_get_entry_scalar(LandYamlEntry * self, char const * name) {
    return land_yaml_get_if_scalar(land_yaml_get_entry(self, name));
}
void land_yaml_set_entry_scalar(LandYaml * yaml, LandYamlEntry * entry, str key, str val) {
    LandYamlEntry * already = land_yaml_get_entry(entry, key);
    if (already) {
        if (already->scalar) {
            land_free(already->scalar);
        }
        already->scalar = val ? land_strdup(val) : NULL;
    }
    else {
        land_alloc(already);
        already->type = YamlScalar;
        already->scalar = val ? land_strdup(val) : NULL;
        land_yaml_open(yaml, entry);
        land_yaml_add_scalar(yaml, key);
        land_yaml_add_scalar(yaml, val);
    }
}
void land_yaml_add_entry_mapping(LandYaml * yaml, LandYamlEntry * entry, str key) {
    LandYamlEntry * already = land_yaml_get_entry(entry, key);
    if (already) {
        return ;
    }
    land_yaml_open(yaml, entry);
    land_yaml_add_scalar(yaml, key);
    land_yaml_add_mapping(yaml);
    land_yaml_done(yaml);
}
int land_yaml_get_entry_int(LandYamlEntry * self, char const * name) {
    return land_yaml_get_scalar_int(land_yaml_get_entry(self, name));
}
double land_yaml_get_entry_double(LandYamlEntry * self, char const * name) {
    return land_yaml_get_scalar_double(land_yaml_get_entry(self, name));
}
LandYamlEntry* land_yaml_get_nth(LandYamlEntry * self, int i) {
    return land_array_get_nth(land_yaml_get_sequence(self), i);
}
int land_yaml_get_nth_int(LandYamlEntry * self, int i) {
    return land_yaml_get_scalar_int(land_array_get_nth(land_yaml_get_sequence(self), i));
}
double land_yaml_get_nth_double(LandYamlEntry * self, int i) {
    return land_yaml_get_scalar_double(land_array_get_nth(land_yaml_get_sequence(self), i));
}
char const* land_yaml_get_nth_scalar(LandYamlEntry * self, int i) {
    return land_yaml_get_scalar(land_array_get_nth(land_yaml_get_sequence(self), i));
}
LandArray* land_yaml_get_entry_sequence(LandYamlEntry * self, char const * name) {
    return land_yaml_get_sequence(land_yaml_get_entry(self, name));
}
LandYaml* land_yaml_new(char const * filename) {
    LandYaml * yaml;
    land_alloc(yaml);
    yaml->filename = land_strdup(filename);
    yaml->parents = land_array_new();
    return yaml;
}
static void _add_entry(LandYaml * yaml, LandYamlEntry * entry) {
    if (yaml->parent) {
        if (yaml->parent->type == YamlSequence) {
            land_array_add(yaml->parent->sequence, entry);
        }
        else if (yaml->parent->type == YamlMapping) {
            land_hash_insert(yaml->parent->mapping, yaml->key, entry);
            land_array_add(yaml->parent->sequence, land_strdup(yaml->key));
            yaml->expect_key = 1;
            land_free(yaml->key);
            yaml->key = NULL;
        }
    }
    else {
        yaml->root = entry;
    }
    if (entry->type == YamlSequence) {
        land_array_add(yaml->parents, yaml->parent);
        yaml->parent = entry;
        yaml->expect_key = 0;
    }
    else if (entry->type == YamlMapping) {
        land_array_add(yaml->parents, yaml->parent);
        yaml->parent = entry;
        yaml->expect_key = 1;
    }
}
void land_yaml_open(LandYaml * yaml, LandYamlEntry * entry) {
    /* Used to modify a LandYaml after it has been loaded - call on a
     * mapping or sequence then add a value or a key/value pair.
     */
    yaml->parent = entry;
    yaml->expect_key = entry->type == YamlMapping ? 1 : 0;
}
void land_yaml_add_mapping(LandYaml * yaml) {
    /* After calling this, use land_yaml_add_scalar to add a key, and then
     * land_yaml_add_* to add a value. Repeat to add the 2nd and more map entries.
     * Use land_yaml_done when done.
     */
    LandYamlEntry * entry;
    land_alloc(entry);
    entry->type = YamlMapping;
    entry->mapping = land_hash_new();
    entry->sequence = land_array_new();
    _add_entry(yaml, entry);
}
void land_yaml_done(LandYaml * yaml) {
    /* Call this when done with a mapping or sequence.
     */
    yaml->expect_key = 0;
    yaml->parent = land_array_pop(yaml->parents);
    if (yaml->parent && yaml->parent->type == YamlMapping) {
        yaml->expect_key = 1;
    }
}
void land_yaml_add_sequence(LandYaml * yaml) {
    /* After calling this, use land_yaml_add_* to add sequence items,
     * then land_yaml_done when the sequence is done.
     */
    LandYamlEntry * entry;
    land_alloc(entry);
    entry->type = YamlSequence;
    entry->sequence = land_array_new();
    _add_entry(yaml, entry);
}
void land_yaml_add_scalar(LandYaml * yaml, char const * v) {
    /* Call this to add an item to a sequence, a key to a mapping, or a value to
     * a mapping.
     */
    if (yaml->expect_key) {
        yaml->expect_key = 0;
        yaml->key = strdup(v);
    }
    else {
        LandYamlEntry * entry;
        land_alloc(entry);
        entry->type = YamlScalar;
        entry->scalar = v ? land_strdup(v) : NULL;
        _add_entry(yaml, entry);
    }
}
void land_yaml_add_scalar_v(LandYaml * yaml, char const * v, va_list args) {
    va_list args2;
    va_copy(args2, args);
    int n = vsnprintf(NULL, 0, v, args2);
    va_end(args2);
    if (n < 0) {
        n = 1023;
    }
    char s [n + 1];
    vsnprintf(s, n + 1, v, args);
    land_yaml_add_scalar(yaml, s);
}
void land_yaml_add_scalar_f(LandYaml * yaml, char const * v, ...) {
    va_list args;
    va_start(args, v);
    land_yaml_add_scalar_v(yaml, v, args);
    va_end(args);
}
static void _destroy_entry(LandYamlEntry * self) {
    if (self->type == YamlScalar) {
        land_free(self->scalar);
    }
    else if (self->type == YamlSequence) {
        for (int i = 0; i < land_array_count(self->sequence); i++) {
            _destroy_entry(land_array_get_nth(self->sequence, i));
        }
        land_array_destroy(self->sequence);
    }
    else if (self->type == YamlMapping) {
        {
            LandArrayIterator __iter0__ = LandArrayIterator_first(self->sequence);
            for (char * key = LandArrayIterator_item(self->sequence, &__iter0__); LandArrayIterator_next(self->sequence, &__iter0__); key = LandArrayIterator_item(self->sequence, &__iter0__)) {
                _destroy_entry(land_hash_get(self->mapping, key));
                land_free(key);
            }
        }
        land_array_destroy(self->sequence);
        land_hash_destroy(self->mapping);
    }
    land_free(self);
}
static void _indent(int indent) {
    for (int i = 0; i < indent; i += 1) {
        printf("    ");
    }
}
static void land_yaml_dump_entry(LandYamlEntry * self, int indent) {
    if (! self) {
        return ;
    }
    if (self->type == YamlScalar) {
        _indent(indent);
        printf("%s\n", self->scalar);
    }
    else if (self->type == YamlSequence) {
        for (int i = 0; i < land_array_count(self->sequence); i++) {
            _indent(indent);
            printf("-\n");
            land_yaml_dump_entry(land_array_get_nth(self->sequence, i), indent + 1);
        }
    }
    else if (self->type == YamlMapping) {
        LandArray * keys = self->sequence;
        for (int i = 0; i < land_array_count(keys); i++) {
            char const * key = land_array_get_nth(keys, i);
            _indent(indent);
            printf("%s:\n", key);
            land_yaml_dump_entry(land_hash_get(self->mapping, key), indent + 1);
        }
    }
}
void land_yaml_destroy(LandYaml * self) {
    _destroy_entry(self->root);
    land_array_destroy(self->parents);
    land_free(self->filename);
    land_free(self);
}
void land_yaml_dump(LandYaml * self) {
    land_yaml_dump_entry(self->root, 0);
}
void land_yaml_rename(LandYaml * self, str filename) {
    land_free(self->filename);
    self->filename = land_strdup(filename);
}
LandColor platform_color_hsv(float hue, float sat, float val) {
    LandColor c;
    al_color_hsv_to_rgb(hue, sat, val, & c.r, & c.g, & c.b);
    c.a = 1;
    return c;
}
LandColor platform_color_name(char const * name) {
    LandColor c;
    if (land_equals(name, "sequoia")) {
        c.r = 0x90 / 255.0;
        c.g = 0x50 / 255.0;
        c.b = 0x60 / 255.0;
        c.a = 1;
        return c;
    }
    int i = 0;
    while (name [i]) {
        if (name [i] == ' ') {
            char name2 [strlen(name)];
            strncpy(name2, name, i);
            int j = i;
            i++;
            while (name [i]) {
                if (name [i] == ' ') {
                    i++;
                    continue;
                }
                name2 [j++] = name [i++];
            }
            name2 [j] = 0;
            al_color_name_to_rgb(name2, & c.r, & c.g, & c.b);
            c.a = 1;
            return c;
        }
        i++;
    }
    al_color_name_to_rgb(name, & c.r, & c.g, & c.b);
    c.a = 1;
    return c;
}
void platform_popup(str title, str text) {
    #ifdef ANDROID
    #else
    al_show_native_message_box(al_get_current_display(), title, title, text, NULL, ALLEGRO_MESSAGEBOX_ERROR);
    #endif
}
char* platform_get(str what) {
    if (land_equals(what, "opengl")) {
        char * s = land_strdup("");
        if (al_get_opengl_variant() == ALLEGRO_DESKTOP_OPENGL) {
            land_append(& s, "OpenGL");
        }
        if (al_get_opengl_variant() == ALLEGRO_OPENGL_ES) {
            land_append(& s, "OpenGL ES");
        }
        uint32_t v = al_get_opengl_version();
        land_append(& s, " %d.%d.%d", v >> 24, (v >> 16) & 255, (v >> 8) & 255);
        return s;
    }
    return NULL;
}
float land_norm2d(float x, float y) {
    return sqrt(x * x + y * y);
}
float land_dot2d(float ax, float ay, float bx, float by) {
    /* Given two vectors ax/ay and bx/by, returns the dot product.
     * If the result is 0, the two vectors are orthogonal. If the result is
     * > 0, they point into the same general direction. If the result is < 0,
     * they point into opposite directions.
     * This can be geometrically interpreted as "how far one vector goes along the
     * direction of the other vector".
     * For example, if we have two vectors a = (4, -3) and b = (4, 0). Then:
     * |a| = 5
     * |b| = 4
     * a.b = 16
     * cos = a.b / |a| / |b| = 0.8 (36.87°)
     * length of a projected onto b: a.b / |b| = 4
     * length of b projected onto a: a.b / |a| = 3.2
     */
    return ax * bx + ay * by;
}
float land_cross2d(float ax, float ay, float bx, float by) {
    /* Given two vectors ax/ay and bx/by, returns the cross product.
     * If the result is 0, the two vectors are parallel. If the result
     * is > 0, b points more to the left than a (if y goes up). If the
     * result is < 0, b points more to the right than a (if y goes up).
     * Geometrically, this is "how far away does one vector go from the other".
     * For example, if we have two vectors a = (4, -3) and b = (4, 0). Then:
     * |a| = 5
     * |b| = 4
     * axb = 4 * 0 - -3 * 4 = 12
     * sin = axb / |a| / |b| = 0.6 (36.87°)
     * distance of a from b: axb / |b| = 3
     * distance of b from a: axb / |a| = 2.4
     */
    return ax * by - ay * bx;
}
void land_ortho2d(float ax, float ay, float * bx, float * by) {
    /* Returns a vector orthogonal to ax/ay. More specifically, returns a
     * vector rotated 90 degree to the right (with y axis going down).
     */
    * bx = - ay;
    * by = ax;
}
void land_rotate2d(LandFloat * x, LandFloat * y, LandFloat angle) {
    /* Rotate x/y around 0/0 by angle in counter clockwise direction.
     * cos(0) = 1, cos(45°) = 0.7, cos(90°) = 0
     * sin(0) = 0, sin(45°) = 0.7, sin(90°) = 1
     * 0/1
     * -0.7/0.7     0.7/0.7
     * .       .
     * . .
     * 0------1/0
     */
    LandFloat c = cos(angle);
    LandFloat s = sin(angle);
    LandFloat rx = * x * c - * y * s;
    LandFloat ry = * y * c + * x * s;
    * x = rx;
    * y = ry;
}
bool land_line_line_collision2d(LandFloat l1x1, LandFloat l1y1, LandFloat l1x2, LandFloat l1y2, LandFloat l2x1, LandFloat l2y1, LandFloat l2x2, LandFloat l2y2) {
    /* Checks if two line segments collide.
     */
    LandFloat ax = l1x2 - l1x1;
    LandFloat ay = l1y2 - l1y1;
    LandFloat bx = l2x2 - l2x1;
    LandFloat by = l2y2 - l2y1;
    LandFloat cx = l2x1 - l1x1;
    LandFloat cy = l2y1 - l1y1;
    LandFloat ab = land_cross2d(ax, ay, bx, by);
    LandFloat ca = land_cross2d(cx, cy, ax, ay);
    LandFloat cb = land_cross2d(cx, cy, bx, by);
    if (ab == 0) {
        if (ca == 0) {
            LandFloat dac = land_dot2d(ax, ay, cx, cy);
            LandFloat dx = l2x2 - l1x1;
            LandFloat dy = l2y2 - l1y1;
            LandFloat dad = land_dot2d(ax, ay, dx, dy);
            if (dac < 0) {
                if (dad < 0) {
                    return 0;
                }
                return 1;
            }
            if (dad < 0) {
                return 1;
            }
            LandFloat daa = land_dot2d(ax, ay, ax, ay);
            if (dac * dac > daa * daa && dad * dad > daa * daa) {
                return 0;
            }
            return 1;
        }
        return 0;
    }
    if (ab < 0) {
        if (ca > 0 || cb > 0) {
            return 0;
        }
        if (ca < ab || cb < ab) {
            return 0;
        }
    }
    else {
        if (ca < 0 || cb < 0) {
            return 0;
        }
        if (ca > ab || cb > ab) {
            return 0;
        }
    }
    return 1;
}
bool circle_circle_collision2d(LandFloat c1x1, LandFloat c1y1, LandFloat c1r, LandFloat c2x1, LandFloat c2y1, LandFloat c2r) {
    LandFloat ax = c2x1 - c1x1;
    LandFloat ay = c2y1 - c1y1;
    LandFloat r = c1r + c2r;
    return ax * ax + ay * ay <= r * r;
}
bool land_line_circle_collision2d(LandFloat lx1, LandFloat ly1, LandFloat lx2, LandFloat ly2, LandFloat cx1, LandFloat cy1, LandFloat cr) {
    LandFloat ax = lx2 - lx1;
    LandFloat ay = ly2 - ly1;
    LandFloat bx = cx1 - lx1;
    LandFloat by = cy1 - ly1;
    LandFloat cx = cx1 - lx2;
    LandFloat cy = cy1 - ly2;
    LandFloat c = land_cross2d(ax, ay, bx, by);
    LandFloat a = ax * ax + ay * ay;
    if (a == 0) {
        if (bx * bx + by * by <= cr * cr) {
            return 1;
        }
        return 0;
    }
    if (c * c / a > cr * cr) {
        return 0;
    }
    LandFloat ab = land_dot2d(ax, ay, bx, by);
    LandFloat ac = land_dot2d(ax, ay, cx, cy);
    if (ab >= 0 && ac <= 0) {
        return 1;
    }
    if (bx * bx + by * by <= cr * cr) {
        return 1;
    }
    if (cx * cx + cy * cy <= cr * cr) {
        return 1;
    }
    return 0;
}
static int button_state [LandJoystickButtonsCount];
static int button_pressed [LandJoystickButtonsCount];
static float axis [LandJoystickAxesCount];
static float oaxis [LandJoystickAxesCount];
void land_joystick_button_down_event(int button) {
    if (! button_state [button]) {
        button_pressed [button]++;
        button_state [button] = 1;
    }
}
void land_joystick_button_up_event(int button) {
    button_state [button] = 0;
}
void land_joystick_axis_event(int a, float x) {
    axis [a] = x;
}
void land_joystick_tick(void) {
    int bn = land_joystick_button_count();
    for (int i = 1; i < bn; i += 1) {
        button_pressed [i] = 0;
    }
    int an = land_joystick_axis_count();
    for (int i = 1; i < an; i += 1) {
        oaxis [i] = axis [i];
    }
}
int land_joystick_button(int button) {
    /* Is the Joystick button down during this tick. The button is a number
     * between 1 inclusive and land_joystick_button_count() exclusive.
     */
    return button_state [button];
}
int land_joystick_button_pressed(int button) {
    /* How often was the joystick button pressed in this tick (almost
     * always will be either 0 or 1, except you can press your button
     * very fast).
     */
    return button_pressed [button];
}
float land_joystick_axis(int a) {
    /* The joystick axis position of the given axis. The axis is a number
     * between 1 inclusive and land_joystick_axis_count() exclusive.
     */
    return axis [a];
}
float land_joystick_delta_axis(int a) {
    return axis [a] - oaxis [a];
}
str land_joystick_button_name(int b) {
    return platform_joystick_button_name(b);
}
str land_joystick_axis_name(int a) {
    return platform_joystick_axis_name(a);
}
int land_joystick_button_count(void) {
    return platform_joystick_button_count();
}
int land_joystick_axis_count(void) {
    return platform_joystick_axis_count();
}
int land_joystick_find_axis(str name) {
    int an = land_joystick_axis_count();
    for (int ai = 1; ai < an; ai += 1) {
        str axis = land_joystick_axis_name(ai);
        if (land_equals(axis, name)) {
            return ai;
        }
    }
    return 0;
}
int land_joystick_find_button(str name) {
    int bn = land_joystick_button_count();
    for (int bi = 1; bi < bn; bi += 1) {
        str button = land_joystick_button_name(bi);
        if (land_equals(button, name)) {
            return bi;
        }
    }
    return 0;
}
void land_joystick_debug(void) {
    int an = land_joystick_axis_count();
    for (int ai = 1; ai < an; ai += 1) {
        float v = land_joystick_axis(ai);
        if (v) {
            printf("%s: %f\n", land_joystick_axis_name(ai), v);
        }
    }
    int bn = land_joystick_button_count();
    for (int bi = 1; bi < bn; bi += 1) {
        int v = land_joystick_button_pressed(bi);
        if (v) {
            printf("%s: %d\n", land_joystick_button_name(bi), v);
        }
    }
}
enum XmlState {
    Outside,
    ElementName,
    Attributes,
    AttributeName,
    AttributeStart,
    AttributeValue
};
struct XmlParser {
    XmlState state;
    bool closing;
    LandBuffer * value;
    LandYaml * yaml;
};
static void scalar(XmlParser * x) {
    land_buffer_add_char(x->value, 0);
    land_yaml_add_scalar(x->yaml, land_strdup(x->value->buffer));
    land_buffer_clear(x->value);
}
static void opt_scalar(XmlParser * x) {
    if (x->value->n) {
        scalar(x);
    }
}
static void discard_scalar(XmlParser * x) {
    land_buffer_clear(x->value);
}
LandYaml* land_yaml_load_xml(str filename) {
    LandFile * f = land_file_new(filename, "rb");
    if (! f) {
        land_log_message("Failed opening %s\n", filename);
        return NULL;
    }
    land_log_message("Parsing yaml %s\n", filename);
    XmlParser x_;
    XmlParser * x = & x_;
    x->yaml = land_yaml_new(filename);
    x->value = land_buffer_new();
    x->state = Outside;
    x->closing = 0;
    land_yaml_add_sequence(x->yaml);
    while (1) {
        int c = land_file_getc(f);
        if (c < 0) {
            break;
        }
        if (x->state == Outside) {
            if (c == '<') {
                opt_scalar(x);
                x->state = ElementName;
                continue;
            }
        }
        else if (x->state == ElementName) {
            if (c == '/') {
                x->closing = 1;
                continue;
            }
            else if (c == '>') {
                if (x->closing) {
                    discard_scalar(x);
                    close_tag(x);
                    land_yaml_done(x->yaml);
                }
                else {
                    create_tag(x);
                    open_tag(x);
                }
                continue;
            }
            else if (isspace(c)) {
                create_tag(x);
                x->state = Attributes;
                continue;
            }
        }
        else if (x->state == Attributes) {
            if (isspace(c)) {
                continue;
            }
            else if (c == '/') {
                x->closing = 1;
                continue;
            }
            else if (c == '?') {
                x->closing = 1;
                continue;
            }
            else if (c == '>') {
                if (x->closing) {
                    close_tag(x);
                }
                else {
                    open_tag(x);
                }
                continue;
            }
            else if (c == '=') {
                scalar(x);
                x->state = AttributeStart;
                continue;
            }
        }
        else if (x->state == AttributeStart) {
            if (c == '"') {
                x->state = AttributeValue;
            }
            continue;
        }
        else if (x->state == AttributeValue) {
            if (c == '"') {
                x->state = Attributes;
                scalar(x);
                continue;
            }
        }
        add_char(x, c);
    }
    land_yaml_done(x->yaml);
    land_file_destroy(f);
    land_buffer_destroy(x->value);
    return x->yaml;
}
static void add_char(XmlParser * x, char c) {
    land_buffer_add_char(x->value, c);
}
static void create_tag(XmlParser * x) {
    land_yaml_add_mapping(x->yaml);
    land_yaml_add_scalar(x->yaml, land_strdup("<"));
    scalar(x);
}
static void open_tag(XmlParser * x) {
    x->state = Outside;
    land_yaml_add_scalar(x->yaml, land_strdup(">"));
    land_yaml_add_sequence(x->yaml);
}
static void close_tag(XmlParser * x) {
    land_yaml_done(x->yaml);
    x->state = Outside;
    x->closing = 0;
}
static void xml_write(YamlParser * p, char const * s, bool can_break_before) {
    int n = strlen(s);
    if (can_break_before && p->line_length + n > 80) {
        land_file_write(p->file, "\n", 1);
        p->line_length = 0;
    }
    land_file_write(p->file, s, n);
    int i = land_find(s, "\n");
    if (i >= 0) {
        p->line_length = n - 1 - i;
    }
    else {
        p->line_length += n;
    }
}
static bool xml_save_mapping(LandYamlEntry * e, YamlParser * p) {
    str name = land_yaml_get_entry_scalar(e, "<");
    if (! name) {
        return 0;
    }
    xml_write(p, "<", 0);
    xml_write(p, name, 0);
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(e->sequence);
        for (char const * key = LandArrayIterator_item(e->sequence, &__iter0__); LandArrayIterator_next(e->sequence, &__iter0__); key = LandArrayIterator_item(e->sequence, &__iter0__)) {
            if (land_equals(key, "<") || land_equals(key, ">")) {
                continue;
            }
            xml_write(p, " ", 0);
            xml_write(p, key, 1);
            xml_write(p, "=\"", 0);
            str value = land_yaml_get_entry_scalar(e, key);
            xml_write(p, value, 0);
            xml_write(p, "\"", 0);
        }
    }
    LandYamlEntry * contents = land_yaml_get_entry(e, ">");
    if (contents) {
        xml_write(p, ">", 1);
        xml_save_sequence(contents, p);
        xml_write(p, "</", 0);
        xml_write(p, name, 0);
        xml_write(p, ">", 1);
    }
    else {
        xml_write(p, " />", 1);
    }
    return 1;
}
static bool xml_save_sequence(LandYamlEntry * e, YamlParser * p) {
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(e->sequence);
        for (LandYamlEntry * e2 = LandArrayIterator_item(e->sequence, &__iter0__); LandArrayIterator_next(e->sequence, &__iter0__); e2 = LandArrayIterator_item(e->sequence, &__iter0__)) {
            xml_save_entry(e2, p);
        }
    }
    return 1;
}
static bool xml_save_scalar(LandYamlEntry * e, YamlParser * p) {
    xml_write(p, e->scalar, 0);
    return 1;
}
static bool xml_save_entry(LandYamlEntry * e, YamlParser * p) {
    if (e->type == YamlMapping) {
        return xml_save_mapping(e, p);
    }
    else if (e->type == YamlSequence) {
        return xml_save_sequence(e, p);
    }
    else if (e->type == YamlScalar) {
        return xml_save_scalar(e, p);
    }
    return false;
}
void land_yaml_save_xml(LandYaml * yaml) {
    LandFile * f = land_file_new(yaml->filename, "wb");
    if (! f) {
        goto error;
    }
    YamlParser p;
    memset(& p, 0, sizeof p);
    p.file = f;
    if (! xml_save_entry(yaml->root, & p)) {
        goto error;
    }
    error:;
    if (f) {
        land_file_destroy(f);
    }
}
static void _xml(LandYaml * yaml) {
    if (! yaml->root || ! yaml->parent) {
        land_yaml_add_sequence(yaml);
    }
    else if (yaml->parent->type == YamlMapping) {
        land_yaml_add_scalar(yaml, ">");
        land_yaml_add_sequence(yaml);
    }
}
void land_yaml_xml_tag(LandYaml * yaml, str name) {
    _xml(yaml);
    land_yaml_add_mapping(yaml);
    land_yaml_add_scalar(yaml, "<");
    land_yaml_add_scalar(yaml, name);
}
void land_yaml_xml_tag_with_content(LandYaml * yaml, str name, str content) {
    land_yaml_xml_tag(yaml, name);
    land_yaml_xml_content(yaml, content);
    land_yaml_xml_end(yaml);
}
void land_yaml_xml_content(LandYaml * yaml, str content) {
    _xml(yaml);
    land_yaml_add_scalar(yaml, content);
}
void land_yaml_xml_attribute(LandYaml * yaml, str key, str value) {
    land_yaml_add_scalar(yaml, key);
    land_yaml_add_scalar(yaml, value);
}
void land_yaml_xml_end(LandYaml * yaml) {
    land_yaml_done(yaml);
    if (yaml->parent && yaml->parent->type == YamlMapping) {
        land_yaml_done(yaml);
    }
}
    /* translation matrix to translate by xt/yt/zt
     * T = 1 0 0 xt
     * 0 1 0 yt
     * 0 0 1 zt
     * 0 0 0 1
     * rotation matrix into coordinate system given by 3 vectors
     * x=xx/yx/zx, y=xy/yy/zy, z=xz/yz/zz
     * R = xx xy xz 0
     * yx yy yz 0
     * zx zy zz 0
     * 0  0  0  1
     * scaling matrix to scale by xs/ys/zs
     * S = xs 0  0  0
     * 0  ys 0  0
     * 0  0  zs 0
     * 0  0  0  1
     * inv(T) = 1 0 0 -xt
     * 0 1 0 -yt
     * 0 0 1 -zt
     * 0 0 0 1
     * inv(R) = xx yx zx 0
     * xy yy zy 0
     * xz yz zz 0
     * 0  0  0  1
     * T x = x + xt
     * y = y + yt
     * z = z + zt
     * 1 = 1
     * R x = xx x + xy y + xz z
     * y = yx x + yy y + yz z
     * z = zx x + zy y + zz z
     * 1 = 1
     * rotate first then translate
     * T R = xx xy xz xt
     * yx yy yz yt
     * zx zy zz zt
     * 0  0  0  1
     * T R x = xx x + xy y + xz z + xt
     * y   yx x + yy y + yz z + yt
     * z   zx x + zy y + zz z + zt
     * 1   1
     * translate first then rotate
     * R T = xx xy xz xx xt + xy yt + xz zt
     * yx yy yz yx xt + yy yt + yz zt
     * zx zy zz zx xt + zy yt + zz zt
     * 0  0  0  1
     * scale first then translate
     * T S = 1 0 0 xt   xs 0  0  0   xs 0  0  xt
     * 0 1 0 yt * 0  ys 0  0 = 0  ys 0  yt
     * 0 0 1 zt   0  0  zs 0   0  0  zs zt
     * 0 0 0 1    0  0  0  1   0  0  0  1
     * T S x = xs * x + xt
     * y   ys * y + yt
     * z   zs * z + zt
     * 1   1
     * translate first then scale
     * S T = xs 0  0  0   1 0 0 xt   xs 0  0  xs*xt
     * 0  ys 0  0 * 0 1 0 yt = 0  ys 0  ys*yt
     * 0  0  zs 0   0 0 1 zt   0  0  zs zs*zt
     * 0  0  0  1   0 0 0 1    0  0  0  1
     * S T x = xs * x + xs * xt
     * y   ys * y + ys * yt
     * z   zs * z + zs * zt
     * 1   1
     * translate first then arbitrary affine matrix
     * A T = A0 A1 A2 A3   1 0 0 xt    A0 A1 A2 A0*xt+A1*yt+A2*zt+A3
     * A4 A5 A6 A7 * 0 1 0 yt =  A4 A5 A6 A4*xt+A5*yt+A7*zt+A7
     * A8 A9 Aa Ab   0 0 1 zt    A8 A9 Aa A8*xt+A9*yt+Aa*zt+Ab
     * 0  0  0  1    0 0 0 1     0  0  0  1
     * scale first then arbitrary affine matrix
     * A S = A0 A1 A2 A3   xs 0  0  0   A0*xs A1*ys A2*zs A3
     * A4 A5 A6 A7 * 0  ys 0  0 = A4*xs A5*ys A6*zs A7
     * A8 A9 Aa Ab   0  0  zs 0   A8*xs A9*ys Aa*zs Ab
     * 0  0  0  1    0  0  0  1   0     0     0     1
     * rotate by an angle around vector 0/0/1
     * Ra = +cos -sin 0 0
     * +sin +cos 0 0
     * 0    0    1 0
     * 0    0    0 1
     * same but arbitrary affine matrix afterwards
     * A Ra = A0*c+A1*s -A0*s+A1*c A2 A3
     * A4*c+A5*s -A4*s+A5*c A6 A7
     * A8*c+A9*s -A8*s+A9*c Aa Ab
     * 0         0          0  1
     */
#define SQRT sqrt
#define COS cos
#define SIN sin
LandVector land_vector(LandFloat x, LandFloat y, LandFloat z) {
    LandVector v = {x, y, z};
    return v;
}
LandVector land_vector_from_array(LandFloat * a) {
    LandVector v = {a [0], a [1], a [2]};
    return v;
}
void land_vector_iadd(LandVector * v, LandVector w) {
    v->x += w.x;
    v->y += w.y;
    v->z += w.z;
}
void land_vector_isub(LandVector * v, LandVector w) {
    v->x -= w.x;
    v->y -= w.y;
    v->z -= w.z;
}
void land_vector_imul(LandVector * v, LandFloat s) {
    v->x *= s;
    v->y *= s;
    v->z *= s;
}
void land_vector_idiv(LandVector * v, LandFloat s) {
    v->x /= s;
    v->y /= s;
    v->z /= s;
}
LandVector land_vector_neg(LandVector v) {
    LandVector r = {- v.x, - v.y, - v.z};
    return r;
}
LandVector land_vector_mul(LandVector v, LandFloat s) {
    LandVector r = {v.x * s, v.y * s, v.z * s};
    return r;
}
LandVector land_vector_div(LandVector v, LandFloat s) {
    LandVector r = {v.x / s, v.y / s, v.z / s};
    return r;
}
LandVector land_vector_add(LandVector v, LandVector w) {
    LandVector r = {v.x + w.x, v.y + w.y, v.z + w.z};
    return r;
}
LandVector land_vector_add4(LandVector v, LandVector w, LandVector a, LandVector b) {
    LandVector r = {v.x + w.x + a.x + b.x, v.y + w.y + a.y + b.y, v.z + w.z + a.z + b.z};
    return r;
}
LandVector land_vector_add8(LandVector v, LandVector w, LandVector a, LandVector b, LandVector c, LandVector d, LandVector e, LandVector f) {
    LandVector r = {v.x + w.x + a.x + b.x + c.x + d.x + e.x + f.x, v.y + w.y + a.y + b.y + c.y + d.y + e.y + f.y, v.z + w.z + a.z + b.z + c.z + d.z + e.z + f.z};
    return r;
}
LandVector land_vector_sub(LandVector v, LandVector w) {
    LandVector r = {v.x - w.x, v.y - w.y, v.z - w.z};
    return r;
}
LandVector land_vector_lerp(LandVector v, LandVector w, LandFloat t) {
    return land_vector_add(v, land_vector_mul(land_vector_sub(w, v), t));
}
LandFloat land_vector_dot(LandVector v, LandVector w) {
    /* The dot product is a number. The number corresponds to the cosine
     * between the two vectors times their lengths. So the angle between the
     * vectors would be: angle = acos(v . w / (|v| * |w|)). If the dot product
     * is 0, the two vectors conversely are orthogonal. The sign can be used to
     * determine which side of a plane a point is on.
     */
    return v.x * w.x + v.y * w.y + v.z * w.z;
}
LandVector land_vector_cross(LandVector v, LandVector w) {
    /* The cross product results in a vector orthogonal to both v and w. The
     * length of the resulting vector corresponds to the sine of the angle
     * between the two vectors and their lengths. So the angle between the
     * vectors would be: angle = asin(|v x w| / (|v| * |w|)). If the cross
     * product is the 0 vector, the two input vectors are parallel.
     */
    LandVector r = {v.y * w.z - w.y * v.z, v.z * w.x - w.z * v.x, v.x * w.y - w.x * v.y};
    return r;
}
LandFloat land_vector_norm(LandVector v) {
    /* Return the norm of the vector.
     */
    return SQRT(land_vector_dot(v, v));
}
LandVector land_vector_normalize(LandVector v) {
    /* Return a normalized version of the vector.
     */
    return land_vector_div(v, land_vector_norm(v));
}
LandQuaternion land_vector_quatmul(LandVector v, LandQuaternion q) {
    /* Multiply the vector with a quaternion. The result is a quaternion. For
     * example if your vector is a rotation, the resulting quaternion will be a
     * quaternion who rotates whatever it did plus this additional rotation.
     */
    LandQuaternion r = {- v.x * q.x - v.y * q.y - v.z * q.z, v.x * q.w + v.y * q.z - v.z * q.y, v.y * q.w + v.z * q.x - v.x * q.z, v.z * q.w + v.x * q.y - v.y * q.x};
    return r;
}
LandVector land_vector_transform(LandVector v, LandVector p, LandVector r, LandVector u, LandVector b) {
    /* Return a new vector obtained by transforming this vector by a coordinate
     * system with the given origin and given right/up/back vectors. This is
     * used if the vector is in world coordinates, and you want to transform it
     * to camera coordinates, where p/r/u/b define camera position and
     * orientation.
     */
    LandVector w = land_vector_sub(v, p);
    LandVector a = {land_vector_dot(w, r), land_vector_dot(w, u), land_vector_dot(w, b)};
    return a;
}
LandVector land_vector_matmul(LandVector v, Land4x4Matrix * m) {
    LandFloat x = m->v [0] * v.x + m->v [1] * v.y + m->v [2] * v.z + m->v [3];
    LandFloat y = m->v [4] * v.x + m->v [5] * v.y + m->v [6] * v.z + m->v [7];
    LandFloat z = m->v [8] * v.x + m->v [9] * v.y + m->v [10] * v.z + m->v [11];
    return land_vector(x, y, z);
}
LandVector land_vector_backmul3x3(LandVector v, Land4x4Matrix * m) {
    /* Multiplies with the transpose of the 3x3 portion of the matrix.
     */
    LandFloat x = m->v [0] * v.x + m->v [4] * v.y + m->v [8] * v.z;
    LandFloat y = m->v [1] * v.x + m->v [5] * v.y + m->v [9] * v.z;
    LandFloat z = m->v [2] * v.x + m->v [6] * v.y + m->v [10] * v.z;
    return land_vector(x, y, z);
}
LandVector land_vector_project(LandVector v, Land4x4Matrix * m) {
    LandFloat x = m->v [0] * v.x + m->v [1] * v.y + m->v [2] * v.z + m->v [3];
    LandFloat y = m->v [4] * v.x + m->v [5] * v.y + m->v [6] * v.z + m->v [7];
    LandFloat z = m->v [8] * v.x + m->v [9] * v.y + m->v [10] * v.z + m->v [11];
    LandFloat w = m->v [12] * v.x + m->v [13] * v.y + m->v [14] * v.z + m->v [15];
    return land_vector(x / w, y / w, z / w);
}
LandVector land_vector_backtransform(LandVector v, LandVector p, LandVector r, LandVector u, LandVector b) {
    /* Do the inverse of transform, i.e. you can use it to transform from
     * camera back to world coordinates.
     */
    LandVector x = land_vector_mul(r, v.x);
    LandVector y = land_vector_mul(u, v.y);
    LandVector z = land_vector_mul(b, v.z);
    LandVector a = p;
    land_vector_iadd(& a, x);
    land_vector_iadd(& a, y);
    land_vector_iadd(& a, z);
    return a;
}
LandVector land_vector_rotate(LandVector v, LandVector a, double angle) {
    /* Rotate the vector around axis a by angle in counter clockwise direction.
     * If this vector is a point in world space, then the axis of rotation is
     * defined by the origin and the a vector.
     */
    LandFloat c = COS(angle);
    LandFloat s = SIN(angle);
    LandVector r = land_vector_mul(a, a.x * (1 - c));
    LandVector u = land_vector_mul(a, a.y * (1 - c));
    LandVector b = land_vector_mul(a, a.z * (1 - c));
    r.x += c;
    r.y += a.z * s;
    r.z -= a.y * s;
    u.x -= a.z * s;
    u.y += c;
    u.z += a.x * s;
    b.x += a.y * s;
    b.y -= a.x * s;
    b.z += c;
    LandFloat x = land_vector_dot(v, r);
    LandFloat y = land_vector_dot(v, u);
    LandFloat z = land_vector_dot(v, b);
    LandVector ret = {x, y, z};
    return ret;
}
LandVector land_vector_reflect(LandVector v, LandVector n) {
    /* Given the normal of a plane, reflect the vector off the plane. If the
     * vector is a point in 3D space, and the plane goes through the origin,
     * the result is a point reflected by the plane.
     */
    LandFloat d = land_vector_dot(v, n);
    LandVector r = n;
    land_vector_imul(& r, - 2 * d);
    land_vector_iadd(& r, v);
    return r;
}
LandQuaternion land_quaternion(LandFloat w, LandFloat x, LandFloat y, LandFloat z) {
    LandQuaternion q = {w, x, y, z};
    return q;
}
LandQuaternion land_quaternion_from_array(LandFloat * f) {
    LandQuaternion q = {f [0], f [1], f [2], f [3]};
    return q;
}
void land_quaternion_to_array(LandQuaternion * q, LandFloat * f) {
    f [0] = q->w;
    f [1] = q->x;
    f [2] = q->y;
    f [3] = q->z;
}
static double _copy_sign(double x, double y) {
    if (x > 0) {
        if (y > 0) {
            return x;
        }
        return - x;
    }
    if (y > 0) {
        return - x;
    }
    return x;
}
LandQuaternion land_quaternion_from_vectors(LandVector x, LandVector y, LandVector z) {
    double m00 = x.x;
    double m01 = y.x;
    double m02 = z.x;
    double m10 = x.y;
    double m11 = y.y;
    double m12 = z.y;
    double m20 = x.z;
    double m21 = y.z;
    double m22 = z.z;
    LandQuaternion q;
    q.w = sqrt(_scramble_max(0, 1 + m00 + m11 + m22)) / 2;
    q.x = sqrt(_scramble_max(0, 1 + m00 - m11 - m22)) / 2;
    q.y = sqrt(_scramble_max(0, 1 - m00 + m11 - m22)) / 2;
    q.z = sqrt(_scramble_max(0, 1 - m00 - m11 + m22)) / 2;
    q.x = _copy_sign(q.x, m21 - m12);
    q.y = _copy_sign(q.y, m02 - m20);
    q.z = _copy_sign(q.z, m10 - m01);
    return q;
}
void land_quaternion_iadd(LandQuaternion * q, LandQuaternion p) {
    q->w += p.w;
    q->x += p.x;
    q->y += p.y;
    q->z += p.z;
}
void land_quaternion_imul(LandQuaternion * q, LandFloat s) {
    q->w *= s;
    q->x *= s;
    q->y *= s;
    q->z *= s;
}
LandQuaternion land_quaternion_combine(LandQuaternion qa, LandQuaternion qb) {
    LandVector qav = {qa.x, qa.y, qa.z};
    LandVector qbv = {qb.x, qb.y, qb.z};
    LandVector va = land_vector_cross(qav, qbv);
    LandVector vb = land_vector_mul(qav, qb.w);
    LandVector vc = land_vector_mul(qbv, qa.w);
    land_vector_iadd(& va, vb);
    LandVector qrv = land_vector_add(va, vc);
    double w = land_vector_dot(qav, qbv);
    LandQuaternion qr = {qrv.x, qrv.y, qrv.z, w};
    land_quaternion_normalize(& qr);
    return qr;
}
void land_quaternion_vectors(LandQuaternion q, LandVector * r, LandVector * u, LandVector * b) {
    /* Output three orientation vectors for the quaternion. That is, if the
     * quaternion is used as a 3D orientation, return right/up/back vectors
     * representing the same orientation.
     */
    LandFloat ww = q.w * q.w;
    LandFloat xx = q.x * q.x;
    LandFloat yy = q.y * q.y;
    LandFloat zz = q.z * q.z;
    LandFloat wx = q.w * q.x * 2;
    LandFloat wy = q.w * q.y * 2;
    LandFloat wz = q.w * q.z * 2;
    LandFloat xy = q.x * q.y * 2;
    LandFloat xz = q.x * q.z * 2;
    LandFloat yz = q.y * q.z * 2;
    r->x = ww + xx - yy - zz;
    u->x = xy - wz;
    b->x = xz + wy;
    r->y = xy + wz;
    u->y = ww - xx + yy - zz;
    b->y = yz - wx;
    r->z = xz - wy;
    u->z = yz + wx;
    b->z = ww - xx - yy + zz;
}
Land4x4Matrix land_quaternion_4x4_matrix(LandQuaternion q) {
    LandVector r, u, b;
    land_quaternion_vectors(q, & r, & u, & b);
    Land4x4Matrix m;
    m.v [0] = r.x;
    m.v [1] = u.x;
    m.v [2] = b.x;
    m.v [3] = 0;
    m.v [4] = r.y;
    m.v [5] = u.y;
    m.v [6] = b.y;
    m.v [7] = 0;
    m.v [8] = r.z;
    m.v [9] = u.z;
    m.v [10] = b.z;
    m.v [11] = 0;
    m.v [12] = 0;
    m.v [13] = 0;
    m.v [14] = 0;
    m.v [15] = 1;
    return m;
}
Land4x4Matrix land_4x4_matrix_mul(Land4x4Matrix a, Land4x4Matrix b) {
    /* This multiplies two matrices:
     * result = a b
     * When used with 3D transformations, the result has the same effect as first
     * applying b, then a. For example:
     * v = land_vector(1, 0, 0)
     * a = land_4x4_matrix_scale(10, 1, 1)
     * b = land_4x4_matrix_translate(10, 0, 0)
     * # This means first b then a, so v is first translated to 11, then
     * # scaled to 110.
     * land_vector_matmul(v, land_4x4_matrix_mul(a, b))
     * # This means v is first scaled to 10, then translated to 20.
     * land_vector_matmul(v, land_4x4_matrix_mul(b, a))
     * In words, result[row,column] = a[row,...] * b[...,column].
     */
    Land4x4Matrix m;
    for (int i = 0; i < 4; i += 1) {
        for (int j = 0; j < 4; j += 1) {
            LandFloat x = 0;
            for (int k = 0; k < 4; k += 1) {
                x += a.v [i * 4 + k] * b.v [k * 4 + j];
            }
            m.v [i * 4 + j] = x;
        }
    }
    return m;
}
Land4x4Matrix land_4x4_matrix_scale(LandFloat x, LandFloat y, LandFloat z) {
    Land4x4Matrix m;
    m.v [0] = x;
    m.v [1] = 0;
    m.v [2] = 0;
    m.v [3] = 0;
    m.v [4] = 0;
    m.v [5] = y;
    m.v [6] = 0;
    m.v [7] = 0;
    m.v [8] = 0;
    m.v [9] = 0;
    m.v [10] = z;
    m.v [11] = 0;
    m.v [12] = 0;
    m.v [13] = 0;
    m.v [14] = 0;
    m.v [15] = 1;
    return m;
}
Land4x4Matrix land_4x4_matrix_skew(LandFloat xy, LandFloat xz, LandFloat yx, LandFloat yz, LandFloat zx, LandFloat zy) {
    Land4x4Matrix m;
    m.v [0] = 1;
    m.v [1] = xy;
    m.v [2] = xz;
    m.v [3] = 0;
    m.v [4] = yx;
    m.v [5] = 1;
    m.v [6] = yz;
    m.v [7] = 0;
    m.v [8] = zx;
    m.v [9] = zy;
    m.v [10] = 1;
    m.v [11] = 0;
    m.v [12] = 0;
    m.v [13] = 0;
    m.v [14] = 0;
    m.v [15] = 1;
    return m;
}
Land4x4Matrix land_4x4_matrix_rotate(LandFloat x, LandFloat y, LandFloat z, LandFloat angle) {
    Land4x4Matrix m;
    double s = sin(angle);
    double c = cos(angle);
    double cc = 1 - c;
    m.v [0] = (cc * x * x) + c;
    m.v [4] = (cc * x * y) + (z * s);
    m.v [8] = (cc * x * z) - (y * s);
    m.v [12] = 0;
    m.v [1] = (cc * x * y) - (z * s);
    m.v [5] = (cc * y * y) + c;
    m.v [9] = (cc * z * y) + (x * s);
    m.v [13] = 0;
    m.v [2] = (cc * x * z) + (y * s);
    m.v [6] = (cc * y * z) - (x * s);
    m.v [10] = (cc * z * z) + c;
    m.v [14] = 0;
    m.v [3] = 0;
    m.v [7] = 0;
    m.v [11] = 0;
    m.v [15] = 1;
    return m;
}
Land4x4Matrix land_4x4_matrix_identity(void) {
    Land4x4Matrix m;
    m.v [0] = 1;
    m.v [1] = 0;
    m.v [2] = 0;
    m.v [3] = 0;
    m.v [4] = 0;
    m.v [5] = 1;
    m.v [6] = 0;
    m.v [7] = 0;
    m.v [8] = 0;
    m.v [9] = 0;
    m.v [10] = 1;
    m.v [11] = 0;
    m.v [12] = 0;
    m.v [13] = 0;
    m.v [14] = 0;
    m.v [15] = 1;
    return m;
}
Land4x4Matrix land_4x4_matrix_translate(LandFloat x, LandFloat y, LandFloat z) {
    /* T = 1 0 0 xt
     * 0 1 0 yt
     * 0 0 1 zt
     * 0 0 0 1
     */
    Land4x4Matrix m;
    m.v [0] = 1;
    m.v [1] = 0;
    m.v [2] = 0;
    m.v [3] = x;
    m.v [4] = 0;
    m.v [5] = 1;
    m.v [6] = 0;
    m.v [7] = y;
    m.v [8] = 0;
    m.v [9] = 0;
    m.v [10] = 1;
    m.v [11] = z;
    m.v [12] = 0;
    m.v [13] = 0;
    m.v [14] = 0;
    m.v [15] = 1;
    return m;
}
Land4x4Matrix land_4x4_matrix_perspective(LandFloat left, LandFloat bottom, LandFloat nearz, LandFloat right, LandFloat top, LandFloat farz) {
    Land4x4Matrix m;
    LandFloat w = right - left;
    LandFloat h = top - bottom;
    LandFloat depth = farz - nearz;
    LandFloat cx = (right + left) / 2;
    LandFloat cy = (bottom + top) / 2;
    LandFloat cz = (farz + nearz) / 2;
    m.v [0] = 2 * nearz / w;
    m.v [1] = 0;
    m.v [2] = 2 * cx / w;
    m.v [3] = 0;
    m.v [4] = 0;
    m.v [5] = 2 * nearz / h;
    m.v [6] = 0;
    m.v [7] = 2 * cy / h;
    m.v [8] = 0;
    m.v [9] = 0;
    m.v [10] = - 2 * cz / depth;
    m.v [11] = farz * nearz * (- 2 / depth);
    m.v [12] = 0;
    m.v [13] = 0;
    m.v [14] = - 1;
    m.v [15] = 0;
    return m;
}
Land4x4Matrix land_4x4_matrix_orthographic(LandFloat left, LandFloat top, LandFloat nearz, LandFloat right, LandFloat bottom, LandFloat farz) {
    /* Orthographic means no projection so this would be just an identity matrix.
     * But as convenience this scales and translates to fit into the
     * left/top/right/bottom rectangle and also scales depth.
     * The point at (left, top, near) will end up at (-1, -1, -1) and the point
     * at (right, bottom, far) will end up at (1, 1, 1).
     * O = S(2/w, 2/h, 2/d) T(-cx, -cy, -cz)
     * O = 2/w 0   0   2/w*-cx
     * 0   2/h 0   2/h*-cy
     * 0   0   2/d 2/d*-cz
     * 0   0   0   1
     * O x = 2/w*(x-cx)
     * y   2/h*(y-cy)
     * z   2/d*(z-cz)
     * 1   1
     * inv(O) = inv(T) inv(S) = w/2 0   0   cx
     * 0   h/2 0   cy
     * 0   0   d/2 cz
     * 0   0   0   1
     * O inv(O) = 1 0 0 0
     * 0 1 0 0
     * 0 0 1 0
     * 0 0 0 1
     */
    Land4x4Matrix m;
    LandFloat w = right - left;
    LandFloat h = bottom - top;
    LandFloat depth = farz - nearz;
    LandFloat cx = (right + left) / 2;
    LandFloat cy = (bottom + top) / 2;
    LandFloat cz = (farz + nearz) / 2;
    m.v [0] = 2 / w;
    m.v [1] = 0;
    m.v [2] = 0;
    m.v [3] = 2 / w * (- cx);
    m.v [4] = 0;
    m.v [5] = 2 / h;
    m.v [6] = 0;
    m.v [7] = 2 / h * (- cy);
    m.v [8] = 0;
    m.v [9] = 0;
    m.v [10] = 2 / depth;
    m.v [11] = 2 / depth * (- cz);
    m.v [12] = 0;
    m.v [13] = 0;
    m.v [14] = 0;
    m.v [15] = 1;
    return m;
}
Land4x4Matrix land_4x4_matrix_from_vectors(LandVector * p, LandVector * r, LandVector * u, LandVector * b) {
    Land4x4Matrix m;
    m.v [0] = r->x;
    m.v [1] = u->x;
    m.v [2] = b->x;
    m.v [3] = p->x;
    m.v [4] = r->y;
    m.v [5] = u->y;
    m.v [6] = b->y;
    m.v [7] = p->y;
    m.v [8] = r->z;
    m.v [9] = u->z;
    m.v [10] = b->z;
    m.v [11] = p->z;
    m.v [12] = 0;
    m.v [13] = 0;
    m.v [14] = 0;
    m.v [15] = 1;
    return m;
}
Land4x4Matrix land_4x4_matrix_inverse_from_vectors(LandVector * p, LandVector * r, LandVector * u, LandVector * b) {
    Land4x4Matrix m;
    m.v [0] = r->x;
    m.v [1] = r->y;
    m.v [2] = r->z;
    m.v [3] = r->x * (- p->x) + r->y * (- p->y) + r->z * (- p->z);
    m.v [4] = u->x;
    m.v [5] = u->y;
    m.v [6] = u->z;
    m.v [7] = u->x * (- p->x) + u->y * (- p->y) + u->z * (- p->z);
    m.v [8] = b->x;
    m.v [9] = b->y;
    m.v [10] = b->z;
    m.v [11] = b->x * (- p->x) + b->y * (- p->y) + b->z * (- p->z);
    m.v [12] = 0;
    m.v [13] = 0;
    m.v [14] = 0;
    m.v [15] = 1;
    return m;
}
LandVector land_4x4_matrix_get_right(Land4x4Matrix * m) {
    return land_vector(m->v [0], m->v [4], m->v [8]);
}
LandVector land_4x4_matrix_get_up(Land4x4Matrix * m) {
    return land_vector(m->v [1], m->v [5], m->v [9]);
}
LandVector land_4x4_matrix_get_back(Land4x4Matrix * m) {
    return land_vector(m->v [2], m->v [6], m->v [10]);
}
LandVector land_4x4_matrix_get_position(Land4x4Matrix * m) {
    return land_vector(m->v [3], m->v [7], m->v [11]);
}
double land_quaternion_normalize(LandQuaternion * q) {
    /* Normalize the quaternion. This may be useful to prevent deteriorating
     * the quaternion if it is used for a long time, due to floating point
     * inaccuracies.
     */
    LandFloat n = SQRT(q->w * q->w + q->x * q->x + q->y * q->y + q->z * q->z);
    q->w /= n;
    q->x /= n;
    q->y /= n;
    q->z /= n;
    return n;
}
LandQuaternion land_quaternion_slerp(LandQuaternion qa, LandQuaternion qb, double t) {
    /* Given two quaternions, interpolate a quaternion in between. If t is 0
     * this will return qa, if t is 1 it will return qb.
     * The rotation will be along the shortest path (not necessarily the shorter
     * direction though) and the rotation angle will linearly correspond to t.
     */
    LandQuaternion q;
    double c = qa.w * qb.w + qa.x * qb.x + qa.y * qb.y + qa.z * qb.z;
    if (c < 0) {
        c = - c;
        qb.w = - qb.w;
        qb.x = - qb.x;
        qb.y = - qb.y;
        qb.z = - qb.z;
    }
    double theta = acos(c);
    double s = sin(theta);
    double fs = sin((1 - t) * theta) / s;
    double ts = sin(t * theta) / s;
    q.w = qa.w * fs + qb.w * ts;
    q.x = qa.x * fs + qb.x * ts;
    q.y = qa.y * fs + qb.y * ts;
    q.z = qa.z * fs + qb.z * ts;
    return q;
}
LandQuaternion land_quaternion_towards(LandQuaternion qa, LandQuaternion qb, double av) {
    LandQuaternion q;
    double c = qa.w * qb.w + qa.x * qb.x + qa.y * qb.y + qa.z * qb.z;
    if (c < 0) {
        c = - c;
        qb.w = - qb.w;
        qb.x = - qb.x;
        qb.y = - qb.y;
        qb.z = - qb.z;
    }
    double theta = acos(c);
    double s = sin(theta);
    double t = 0.01;
    if (theta > av && theta > t) {
        t = av / theta;
    }
    double fs = sin((1 - t) * theta) / s;
    double ts = sin(t * theta) / s;
    q.w = qa.w * fs + qb.w * ts;
    q.x = qa.x * fs + qb.x * ts;
    q.y = qa.y * fs + qb.y * ts;
    q.z = qa.z * fs + qb.z * ts;
    return q;
}
LandBuffer* land_4x4_matrix_to_string(Land4x4Matrix * m) {
    LandBuffer * b = land_buffer_new();
    for (int i = 0; i < 16; i += 1) {
        land_buffer_addf(b, "%-5.2f%s", m->v [i], i % 4 == 3 ? "\n" : " ");
    }
    return b;
}
#undef SQRT
#undef COS
#undef SIN
static void sphere_point(LandArray * vertices, LandFloat i, LandFloat j) {
    LandFloat theta = 2 * pi * i;
    LandFloat phi = pi * j;
    LandVector normal = land_vector(cos(theta) * sin(phi), cos(phi), sin(theta) * sin(phi));
    LandVector pos = normal;
    land_array_add(vertices, land_csg_vertex_new(pos, normal));
}
LandCSG* csg_sphere(int slices, int rings, void * shared) {
    /* Make a sphere with radius 1.0.
     * It fits within a cube from -1/-1/-1 to 1/1/1.
     * rings determines how many parts the latitude is divided into, a
     * value of 3 means just south pole, equator and north pole.
     * slices determins how many parts the longitude is divided into,
     * a value of 3 means 0-meridian, +120° and -120°.
     */
    if (slices < 3) {
        return NULL;
    }
    if (rings < 3) {
        return NULL;
    }
    LandArray * polygons = land_array_new();
    for (int i = 0; i < slices; i += 1) {
        for (int j = 0; j < rings - 1; j += 1) {
            LandArray * vertices = land_array_new();
            sphere_point(vertices, 1.0 * i / slices, 1.0 * j / (rings - 1));
            if (j > 0) {
                sphere_point(vertices, 1.0 * (i + 1) / slices, 1.0 * j / (rings - 1));
            }
            if (j < rings - 2) {
                sphere_point(vertices, 1.0 * (i + 1) / slices, 1.0 * (j + 1) / (rings - 1));
            }
            sphere_point(vertices, 1.0 * i / slices, 1.0 * (j + 1) / (rings - 1));
            land_array_add(polygons, land_csg_polygon_new(vertices, shared));
        }
    }
    return land_csg_new_from_polygons(polygons);
}
static LandVector _sphere_point(LandVector a, LandVector b) {
    LandVector half = land_vector_mul(land_vector_add(a, b), 0.5);
    return land_vector_normalize(half);
}
static void _sphere_tri(LandArray * polygons, LandVector a, LandVector b, LandVector c, int divisions, void * shared) {
    /* c
     * .
     * / \
     * /     \
     * X---------X
     * /   \     /   \
     * /       \ /       \
     * .---------I---------.
     * a                   b
     */
    if (divisions == 0) {
        LandArray * vertices = land_array_new();
        land_array_add(vertices, land_csg_vertex_new(a, a));
        land_array_add(vertices, land_csg_vertex_new(b, b));
        land_array_add(vertices, land_csg_vertex_new(c, c));
        land_array_add(polygons, land_csg_polygon_new(vertices, shared));
    }
    else {
        LandVector ab2 = _sphere_point(a, b);
        LandVector bc2 = _sphere_point(b, c);
        LandVector ca2 = _sphere_point(c, a);
        _sphere_tri(polygons, a, ab2, ca2, divisions - 1, shared);
        _sphere_tri(polygons, b, bc2, ab2, divisions - 1, shared);
        _sphere_tri(polygons, c, ca2, bc2, divisions - 1, shared);
        _sphere_tri(polygons, ab2, bc2, ca2, divisions - 1, shared);
    }
}
LandCSG* csg_tetrasphere(int divisions, void * shared) {
    /* Make a sphere out of a repeatedly subdivided tetrahedron.
     */
    LandArray * polygons = land_array_new();
    LandFloat r = 1 / sqrt(1.5);
    LandFloat t = 1 / sqrt(3);
    LandVector a = land_vector(- r, 0, - t);
    LandVector b = land_vector(r, 0, - t);
    LandVector c = land_vector(0, - r, t);
    LandVector d = land_vector(0, r, t);
    _sphere_tri(polygons, a, d, b, divisions, shared);
    _sphere_tri(polygons, d, c, b, divisions, shared);
    _sphere_tri(polygons, c, d, a, divisions, shared);
    _sphere_tri(polygons, c, a, b, divisions, shared);
    return land_csg_new_from_polygons(polygons);
}
LandCSG* land_csg_icosphere(int divisions, void * shared) {
    LandArray * polygons = land_array_new();
    const LandFloat u = (1.0 + sqrt(5)) / 2.0;
    const LandFloat r = sqrt(1 + u * u);
    const LandFloat icosahedron_coords [] = {0, 1, u, 0, - 1, u, 0, 1, - u, 0, - 1, - u, 1, u, 0, - 1, u, 0, 1, - u, 0, - 1, - u, 0, u, 0, 1, u, 0, - 1, - u, 0, 1, - u, 0, - 1};
    const int icosahedron_triangles [] = {1, 11, 2, 1, 6, 11, 1, 5, 6, 1, 9, 5, 1, 2, 9, 2, 7, 9, 2, 8, 7, 2, 11, 8, 3, 10, 4, 3, 5, 10, 3, 6, 5, 3, 12, 6, 3, 4, 12, 4, 10, 7, 4, 7, 8, 4, 8, 12, 11, 12, 8, 12, 11, 6, 9, 7, 10, 10, 5, 9};
    for (int i = 0; i < 20; i += 1) {
        int const * vi = icosahedron_triangles + i * 3;
        LandFloat const * va = icosahedron_coords + vi [0] * 3 - 3;
        LandFloat const * vb = icosahedron_coords + vi [1] * 3 - 3;
        LandFloat const * vc = icosahedron_coords + vi [2] * 3 - 3;
        LandVector a = land_vector(va [0] / r, va [1] / r, va [2] / r);
        LandVector b = land_vector(vb [0] / r, vb [1] / r, vb [2] / r);
        LandVector c = land_vector(vc [0] / r, vc [1] / r, vc [2] / r);
        _sphere_tri(polygons, a, b, c, divisions, shared);
    }
    return land_csg_new_from_polygons(polygons);
}
LandCSG* csg_cylinder(int slices, void * shared) {
    return csg_cylinder_open(slices, 1, 0, shared);
}
LandCSG* csg_cut_cone_open_disced(int slices, int discs, bool opened, float top_radius, bool smooth, void * shared) {
    /* Make a cut cone along the z-axis with radius 1.0 at the botton
     * and radius top_radius at the top at height 2.0.
     * It fits within a cube from -1/-1/-1 to 1/1/1.
     */
    if (slices < 3) {
        return NULL;
    }
    LandVector up = land_vector(0, 0, 1);
    LandVector down = land_vector(0, 0, - 1);
    LandArray * polygons = land_array_new();
    for (int j = 0; j < discs; j += 1) {
        for (int i = 0; i < slices; i += 1) {
            LandFloat angle0 = i * 2 * pi / slices;
            LandFloat angle1 = (i + 1) * 2 * pi / slices;
            LandFloat c0 = cos(angle0), s0 = sin(angle0);
            LandFloat c1 = cos(angle1), s1 = sin(angle1);
            LandFloat pbot = j * 1.0 / discs;
            LandFloat ptop = (j + 1) * 1.0 / discs;
            LandFloat zbot = - 1 + pbot * 2;
            LandFloat ztop = - 1 + ptop * 2;
            LandFloat rbot = 1 * (1 - pbot) + top_radius * pbot;
            LandFloat rtop = 1 * (1 - ptop) + top_radius * ptop;
            LandVector side0, side1;
            if (smooth) {
                side0 = land_vector(c0, - s0, 0);
                side1 = land_vector(c1, - s1, 0);
            }
            else {
                side0 = land_vector(c0, - s0, 0);
                side1 = land_vector(c0, - s0, 0);
            }
            LandVector v0d = land_vector(c0 * rbot, - s0 * rbot, zbot);
            LandVector v1d = land_vector(c1 * rbot, - s1 * rbot, zbot);
            LandVector v0u = land_vector(c0 * rtop, - s0 * rtop, ztop);
            LandVector v1u = land_vector(c1 * rtop, - s1 * rtop, ztop);
            LandArray * vertices;
            if (! opened && j == 0) {
                vertices = land_array_new();
                land_array_add(vertices, land_csg_vertex_new(down, down));
                land_array_add(vertices, land_csg_vertex_new(v0d, down));
                land_array_add(vertices, land_csg_vertex_new(v1d, down));
                land_array_add(polygons, land_csg_polygon_new(vertices, shared));
            }
            vertices = land_array_new();
            land_array_add(vertices, land_csg_vertex_new(v1d, side1));
            land_array_add(vertices, land_csg_vertex_new(v0d, side0));
            land_array_add(vertices, land_csg_vertex_new(v0u, side0));
            land_array_add(vertices, land_csg_vertex_new(v1u, side1));
            land_array_add(polygons, land_csg_polygon_new(vertices, shared));
            if (! opened && j == discs - 1) {
                vertices = land_array_new();
                land_array_add(vertices, land_csg_vertex_new(up, up));
                land_array_add(vertices, land_csg_vertex_new(v1u, up));
                land_array_add(vertices, land_csg_vertex_new(v0u, up));
                land_array_add(polygons, land_csg_polygon_new(vertices, shared));
            }
        }
    }
    return land_csg_new_from_polygons(polygons);
}
LandCSG* csg_cut_cone_open(int slices, bool opened, float top_radius, void * shared) {
    return csg_cut_cone_open_disced(slices, 1, opened, top_radius, 1, shared);
}
LandCSG* csg_cylinder_open(int slices, int discs, bool opened, void * shared) {
    return csg_cut_cone_open_disced(slices, discs, opened, 1.0, 1, shared);
}
LandCSG* csg_cone(int slices, void * shared) {
    /* Make a cone along the z-axis with radius 1.0 and height 2.0.
     * The top of the cone is at 0/0/1.
     * It fits within a cube from -1/-1/-1 to 1/1/1.
     */
    if (slices < 3) {
        return NULL;
    }
    LandVector down = land_vector(0, 0, - 1);
    LandVector up = land_vector(0, 0, 1);
    LandArray * polygons = land_array_new();
    for (int i = 0; i < slices; i += 1) {
        LandCSGVertex * start = land_csg_vertex_new(down, down);
        LandFloat angle0 = i * 2 * pi / slices;
        LandFloat angle1 = (i + 1) * 2 * pi / slices;
        LandFloat c0 = cos(angle0), s0 = sin(angle0);
        LandFloat c1 = cos(angle1), s1 = sin(angle1);
        LandVector side0 = land_vector_normalize(land_vector(c0, - s0, 0.5));
        LandVector side1 = land_vector_normalize(land_vector(c1, - s1, 0.5));
        LandVector v0d = land_vector(c0, - s0, - 1);
        LandVector v1d = land_vector(c1, - s1, - 1);
        LandArray * vertices;
        vertices = land_array_new();
        land_array_add(vertices, start);
        land_array_add(vertices, land_csg_vertex_new(v0d, down));
        land_array_add(vertices, land_csg_vertex_new(v1d, down));
        land_array_add(polygons, land_csg_polygon_new(vertices, shared));
        vertices = land_array_new();
        land_array_add(vertices, land_csg_vertex_new(up, up));
        land_array_add(vertices, land_csg_vertex_new(v1d, side1));
        land_array_add(vertices, land_csg_vertex_new(v0d, side0));
        land_array_add(polygons, land_csg_polygon_new(vertices, shared));
    }
    return land_csg_new_from_polygons(polygons);
}
static LandArray* quad_vertices(LandVector a, LandVector b, LandVector c, LandVector d) {
    LandArray * vertices = land_array_new();
    LandVector ab = land_vector_sub(b, a);
    LandVector bc = land_vector_sub(c, b);
    LandVector normal = land_vector_normalize(land_vector_cross(ab, bc));
    land_array_add(vertices, land_csg_vertex_new(a, normal));
    land_array_add(vertices, land_csg_vertex_new(b, normal));
    land_array_add(vertices, land_csg_vertex_new(c, normal));
    land_array_add(vertices, land_csg_vertex_new(d, normal));
    return vertices;
}
static void add_quad(LandArray * polygons, LandVector a, LandVector b, LandVector c, LandVector d, void * shared) {
    LandArray * vertices = quad_vertices(a, b, c, d);
    land_array_add(polygons, land_csg_polygon_new(vertices, shared));
}
static LandArray* triangle_vertices(LandVector a, LandVector b, LandVector c) {
    LandArray * vertices = land_array_new();
    LandVector ab = land_vector_sub(b, a);
    LandVector bc = land_vector_sub(c, b);
    LandVector normal = land_vector_normalize(land_vector_cross(ab, bc));
    land_array_add(vertices, land_csg_vertex_new(a, normal));
    land_array_add(vertices, land_csg_vertex_new(b, normal));
    land_array_add(vertices, land_csg_vertex_new(c, normal));
    return vertices;
}
static void add_tri(LandArray * polygons, LandVector a, LandVector b, LandVector c, void * shared) {
    LandArray * vertices = triangle_vertices(a, b, c);
    land_array_add(polygons, land_csg_polygon_new(vertices, shared));
}
static void add_tri_flip(LandArray * polygons, LandVector a, LandVector b, LandVector c, void * shared, bool flip) {
    add_tri(polygons, a, b, c, shared);
    if (flip) {
        land_csg_polygon_flip(land_array_get_last(polygons));
    }
}
static void add_quad_flip(LandArray * polygons, LandVector a, LandVector b, LandVector c, LandVector d, void * shared, bool flip) {
    add_quad(polygons, a, b, c, d, shared);
    if (flip) {
        land_csg_polygon_flip(land_array_get_last(polygons));
    }
}
LandCSG* csg_irregular_pyramid(LandFloat top_x, LandFloat top_y, LandFloat top_z, void * shared) {
    /* Make a 4-sided pyramid with a side-length of 1 at the base.
     * The top is at top_x/top_y/top_z.
     */
    LandArray * polygons = land_array_new();
    LandVector a = land_vector(- 1, - 1, - 1);
    LandVector b = land_vector(1, - 1, - 1);
    LandVector c = land_vector(1, 1, - 1);
    LandVector d = land_vector(- 1, 1, - 1);
    LandVector e = land_vector(top_x, top_y, top_z);
    add_quad(polygons, a, d, c, b, shared);
    add_tri(polygons, a, b, e, shared);
    add_tri(polygons, b, c, e, shared);
    add_tri(polygons, c, d, e, shared);
    add_tri(polygons, d, a, e, shared);
    return land_csg_new_from_polygons(polygons);
}
LandCSG* csg_pyramid(void * shared) {
    return csg_irregular_pyramid(0, 0, 1, shared);
}
LandCSG* csg_cut_irregular_pyramid_open(bool opened, LandFloat top_x, LandFloat top_y, LandFloat top_z, LandFloat top_w, LandFloat top_h, void * shared) {
    /* Make a 4-sided pyramid with a side-length of 1 at the base.
     * The top is centered at top_x/y/z and extends by
     * top_w, top_h.
     */
    LandFloat x = top_w;
    LandFloat y = top_h;
    LandFloat u = top_x;
    LandFloat v = top_y;
    LandArray * polygons = land_array_new();
    LandVector a = land_vector(- 1, - 1, - 1);
    LandVector b = land_vector(1, - 1, - 1);
    LandVector c = land_vector(1, 1, - 1);
    LandVector d = land_vector(- 1, 1, - 1);
    LandVector e = land_vector(- x + u, - y + v, top_z);
    LandVector f = land_vector(x + u, - y + v, top_z);
    LandVector g = land_vector(x + u, y + v, top_z);
    LandVector h = land_vector(- x + u, y + v, top_z);
    if (! opened) {
        add_quad(polygons, a, d, c, b, shared);
    }
    if (! opened) {
        add_quad(polygons, e, f, g, h, shared);
    }
    add_quad(polygons, h, g, c, d, shared);
    add_quad(polygons, g, f, b, c, shared);
    add_quad(polygons, f, e, a, b, shared);
    add_quad(polygons, e, h, d, a, shared);
    return land_csg_new_from_polygons(polygons);
}
LandCSG* csg_cut_pyramid_open(bool opened, LandFloat top_x, LandFloat top_y, void * shared) {
    /* Make a 4-sided pyramid with a side-length of 1 at the base and a
     * height of 2. The half side-length at the top is top_x times top_y.
     */
    return csg_cut_irregular_pyramid_open(opened, 0, 0, 1, top_x, top_y, shared);
}
LandCSG* csg_tetrahedron(void * shared) {
    /* Make a tetrahedron.
     */
    LandArray * polygons = land_array_new();
    LandFloat s = 1 / sqrt(2);
    LandVector a = land_vector(- 1, 0, - s);
    LandVector b = land_vector(1, 0, - s);
    LandVector c = land_vector(0, - 1, s);
    LandVector d = land_vector(0, 1, s);
    add_tri(polygons, a, d, b, shared);
    add_tri(polygons, d, c, b, shared);
    add_tri(polygons, c, d, a, shared);
    add_tri(polygons, c, a, b, shared);
    return land_csg_new_from_polygons(polygons);
}
LandCSG* csg_cube(void * shared) {
    /* Make a cube from -1/-1/-1 to 1/1/1.
     */
    return csg_trapezoid(- 1, 1, shared);
}
LandCSG* csg_block2(int x, int y, int z, bool outside, void * shared) {
    bool all = ! outside;
    LandArray * polygons = land_array_new();
    LandFloat xs = 1.0 / x;
    LandFloat ys = 1.0 / y;
    LandFloat zs = 1.0 / z;
    for (int i = 0; i < x; i += 1) {
        for (int j = 0; j < y; j += 1) {
            for (int k = 0; k < z; k += 1) {
                LandFloat xp = - 1 + xs + i * xs * 2;
                LandFloat yp = - 1 + ys + j * ys * 2;
                LandFloat zp = - 1 + zs + k * zs * 2;
                LandVector a = land_vector(xp + xs * (- 1), yp + ys * (- 1), zp + zs * (- 1));
                LandVector b = land_vector(xp + xs * 1, yp + ys * (- 1), zp + zs * (- 1));
                LandVector c = land_vector(xp + xs * 1, yp + ys * 1, zp + zs * (- 1));
                LandVector d = land_vector(xp + xs * (- 1), yp + ys * 1, zp + zs * (- 1));
                LandVector e = land_vector(xp + xs * (- 1), yp + ys * (- 1), zp + zs * 1);
                LandVector f = land_vector(xp + xs * 1, yp + ys * (- 1), zp + zs * 1);
                LandVector g = land_vector(xp + xs * 1, yp + ys * 1, zp + zs * 1);
                LandVector h = land_vector(xp + xs * (- 1), yp + ys * 1, zp + zs * 1);
                if (all || k == z - 1) {
                    add_quad(polygons, e, f, g, h, shared);
                }
                if (all || k == 0) {
                    add_quad(polygons, a, d, c, b, shared);
                }
                if (all || j == y - 1) {
                    add_quad(polygons, h, g, c, d, shared);
                }
                if (all || i == x - 1) {
                    add_quad(polygons, g, f, b, c, shared);
                }
                if (all || j == 0) {
                    add_quad(polygons, f, e, a, b, shared);
                }
                if (all || i == 0) {
                    add_quad(polygons, e, h, d, a, shared);
                }
            }
        }
    }
    return land_csg_new_from_polygons(polygons);
}
LandCSG* csg_block(int x, int y, int z, bool outside, void * shared) {
    return csg_block2(x, y, z, outside, shared);
}
LandCSG* csg_grid(int x, int y, void * shared) {
    LandArray * polygons = land_array_new();
    LandFloat xs = 1.0 / x;
    LandFloat ys = 1.0 / y;
    for (int j = 0; j < y; j += 1) {
        for (int i = 0; i < x; i += 1) {
            LandFloat xp = - 1 + xs + i * xs * 2;
            LandFloat yp = - 1 + ys + j * ys * 2;
            LandVector e = land_vector(xp + xs * (- 1), yp + ys * (- 1), 0);
            LandVector f = land_vector(xp + xs * 1, yp + ys * (- 1), 0);
            LandVector g = land_vector(xp + xs * 1, yp + ys * 1, 0);
            LandVector h = land_vector(xp + xs * (- 1), yp + ys * 1, 0);
            add_tri(polygons, e, f, g, shared);
            add_tri(polygons, g, h, e, shared);
        }
    }
    return land_csg_new_from_polygons(polygons);
}
LandCSG* csg_prism(int n, void * shared) {
    /* n is the shape - 3 for triangle, 4 for square, 5 for pentagon...
     * The prism extrudes along the z axis from -1 to 1.
     * The first edge is at y = 1 the others are on a circle around 0/0
     * with radius 1 in the x/y plane.
     */
    LandArray * polygons = land_array_new();
    LandVector a = land_vector(0, 1, 1);
    LandVector b = land_vector(0, 1, - 1);
    LandVector a0 = a;
    LandVector b0 = b;
    for (int i = 1; i < n + 1; i += 1) {
        LandFloat angle = i * 2 * pi / n;
        LandVector c = land_vector(sin(angle), cos(angle), + 1);
        LandVector d = land_vector(sin(angle), cos(angle), - 1);
        add_quad(polygons, a, c, d, b, shared);
        if (i >= 2 && i < n) {
            add_tri(polygons, a0, c, a, shared);
            add_tri(polygons, b0, b, d, shared);
        }
        a = c;
        b = d;
    }
    return land_csg_new_from_polygons(polygons);
}
LandCSG* csg_trapezoid(LandFloat x1, LandFloat x2, void * shared) {
    /* This is a prism, from one shape at z=-1 to another at z=1. The shape
     * is assymetrical, at y=-1 the length is 2, from x=-1 to x=1. At y=1
     * it goes from x1 to x2.
     * If x1=-1 and x2=1 this is identical to csg_cube.
     * y
     * 1
     * x1__.__x2
     * |     \
     * 0 |      \
     * |        \
     * |____.____\
     * -1    0    1 x
     */
    LandArray * polygons = land_array_new();
    LandVector a = land_vector(- 1, - 1, - 1);
    LandVector b = land_vector(1, - 1, - 1);
    LandVector c = land_vector(x2, 1, - 1);
    LandVector d = land_vector(x1, 1, - 1);
    LandVector e = land_vector(- 1, - 1, 1);
    LandVector f = land_vector(1, - 1, 1);
    LandVector g = land_vector(x2, 1, 1);
    LandVector h = land_vector(x1, 1, 1);
    add_quad(polygons, a, d, c, b, shared);
    add_quad(polygons, e, f, g, h, shared);
    add_quad(polygons, h, g, c, d, shared);
    add_quad(polygons, g, f, b, c, shared);
    add_quad(polygons, f, e, a, b, shared);
    add_quad(polygons, e, h, d, a, shared);
    return land_csg_new_from_polygons(polygons);
}
LandCSG* csg_extrude_triangle(LandVector a, LandVector b, LandFloat d, void * shared) {
    /* This extrudes a triangle with points 0/a/b along a distance of d.
     */
    LandArray * polygons = land_array_new();
    LandVector z = land_vector(0, 0, 0);
    LandVector n = land_vector_cross(a, b);
    n = land_vector_normalize(n);
    LandVector z_ = land_vector_add(z, land_vector_mul(n, d));
    LandVector a_ = land_vector_add(a, land_vector_mul(n, d));
    LandVector b_ = land_vector_add(b, land_vector_mul(n, d));
    add_tri_flip(polygons, z, a, b, shared, d > 0);
    add_tri_flip(polygons, z_, b_, a_, shared, d > 0);
    add_quad_flip(polygons, a, z, z_, a_, shared, d > 0);
    add_quad_flip(polygons, z, b, b_, z_, shared, d > 0);
    add_quad_flip(polygons, b, a, a_, b_, shared, d > 0);
    return land_csg_new_from_polygons(polygons);
}
LandCSG* csg_irregular_tetrahedron(LandVector a, LandVector b, LandVector c, LandVector d, void * shared) {
    LandArray * polygons = land_array_new();
    add_tri(polygons, a, b, c, shared);
    add_tri(polygons, a, d, b, shared);
    add_tri(polygons, b, d, c, shared);
    add_tri(polygons, c, d, a, shared);
    return land_csg_new_from_polygons(polygons);
}
static void torus_point(LandArray * vertices, LandFloat i, LandFloat j, LandFloat r) {
    LandFloat theta = 2 * pi * i;
    LandFloat phi = 2 * pi * j;
    LandFloat cx = cos(theta);
    LandFloat cy = sin(theta);
    LandVector pos = land_vector(cx + cx * r * cos(phi), cy + cy * cos(phi) * r, sin(phi) * r);
    LandVector normal = land_vector(cx * cos(phi), cy * cos(phi), sin(phi));
    land_array_add(vertices, land_csg_vertex_new(pos, normal));
}
LandCSG* csg_torus(int slices, int segments, LandFloat diameter, void * shared) {
    /* slices is the longitude subdivisions, or how many "disks" the torus
     * is cut into along its outer circle
     * segments is the "latitude" subdivisions, i.e. how many segments each
     * individual disk has
     * diameter is the size of the tube and must be greater than 0 (thin)
     * and less than 1 (thick).
     * The torus has radius of 1 around the origin and lies in the
     * X/Y plane. The outer radius therefore is 1 + diameter / 2.
     */
    LandArray * polygons = land_array_new();
    for (int i = 0; i < slices; i += 1) {
        for (int j = 0; j < segments; j += 1) {
            LandArray * vertices = land_array_new();
            torus_point(vertices, 1.0 * i / slices, 1.0 * j / segments, diameter / 2);
            torus_point(vertices, 1.0 * (i + 1) / slices, 1.0 * j / segments, diameter / 2);
            torus_point(vertices, 1.0 * (i + 1) / slices, 1.0 * (j + 1) / segments, diameter / 2);
            torus_point(vertices, 1.0 * i / slices, 1.0 * (j + 1) / segments, diameter / 2);
            land_array_add(polygons, land_csg_polygon_new(vertices, shared));
        }
    }
    return land_csg_new_from_polygons(polygons);
}
void land_csg_polygon_recalculate_normal(LandCSGPolygon * p) {
    LandCSGVertex * v0 = land_array_get(p->vertices, 0);
    LandCSGVertex * v1 = land_array_get(p->vertices, 1);
    LandCSGVertex * v2 = land_array_get(p->vertices, 2);
    LandVector a = land_vector_sub(v0->pos, v1->pos);
    LandVector b = land_vector_sub(v1->pos, v2->pos);
    LandVector n = land_vector_normalize(land_vector_cross(a, b));
    p->plane = land_csg_plane_from_points(v0->pos, v1->pos, v2->pos);
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(p->vertices);
        for (LandCSGVertex * v = LandArrayIterator_item(p->vertices, &__iter0__); LandArrayIterator_next(p->vertices, &__iter0__); v = LandArrayIterator_item(p->vertices, &__iter0__)) {
            v->normal = n;
        }
    }
}
struct CallbackData {
    void(* callback)(LandCSGPolygon * p, void * data);
    void * data;
    LandVector pos;
    LandFloat r;
};
static void _merge_callback_callback(LandArray * array, void * data) {
    CallbackData * data2 = data;
    if (! array) {
        return ;
    }
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(array);
        for (LandCSGPolygon * p = LandArrayIterator_item(array, &__iter0__); LandArrayIterator_next(array, &__iter0__); p = LandArrayIterator_item(array, &__iter0__)) {
            bool touches = 0;
            {
                LandArrayIterator __iter1__ = LandArrayIterator_first(p->vertices);
                for (LandCSGVertex * v = LandArrayIterator_item(p->vertices, &__iter1__); LandArrayIterator_next(p->vertices, &__iter1__); v = LandArrayIterator_item(p->vertices, &__iter1__)) {
                    LandVector sub = land_vector_sub(data2->pos, v->pos);
                    LandFloat d = land_vector_dot(sub, sub);
                    if (d <= data2->r * data2->r) {
                        touches = 1;
                        break;
                    }
                }
            }
            if (touches) {
                data2->callback(p, data2->data);
            }
        }
    }
}
static void _lookup_close_polygons(LandCSG * csg, LandVector pos, LandFloat r, void(* callback)(LandCSGPolygon * p, void * data), void * data) {
    CallbackData data2 = {callback, data, pos, r};
    land_octree_callback_in_cube(csg->octree, pos.x - r, pos.y - r, pos.z - r, pos.x + r, pos.y + r, pos.z + r, _merge_callback_callback, & data2);
}
struct Data {
    LandVector normal;
};
static void _merge_callback(LandCSGPolygon * p, void * v) {
    Data * data = v;
    data->normal.x += p->plane.normal.x;
    data->normal.y += p->plane.normal.y;
    data->normal.z += p->plane.normal.z;
}
void land_csg_recalculate_smooth_normals(LandCSG * csg) {
    csg->octree = land_octree_new_from_aabb(& csg->bbox, 10);
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(csg->polygons);
        for (LandCSGPolygon * p = LandArrayIterator_item(csg->polygons, &__iter0__); LandArrayIterator_next(csg->polygons, &__iter0__); p = LandArrayIterator_item(csg->polygons, &__iter0__)) {
            land_csg_polygon_recalculate_normal(p);
            LandFloat x = 0, y = 0, z = 0;
            int i = 0;
            {
                LandArrayIterator __iter1__ = LandArrayIterator_first(p->vertices);
                for (LandCSGVertex * v = LandArrayIterator_item(p->vertices, &__iter1__); LandArrayIterator_next(p->vertices, &__iter1__); v = LandArrayIterator_item(p->vertices, &__iter1__)) {
                    i++;
                    x += v->pos.x;
                    y += v->pos.y;
                    z += v->pos.z;
                }
            }
            if (i > 0) {
                land_octree_insert(csg->octree, x / i, y / i, z / i, p);
            }
        }
    }
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(csg->polygons);
        for (LandCSGPolygon * p = LandArrayIterator_item(csg->polygons, &__iter0__); LandArrayIterator_next(csg->polygons, &__iter0__); p = LandArrayIterator_item(csg->polygons, &__iter0__)) {
            {
                LandArrayIterator __iter1__ = LandArrayIterator_first(p->vertices);
                for (LandCSGVertex * v = LandArrayIterator_item(p->vertices, &__iter1__); LandArrayIterator_next(p->vertices, &__iter1__); v = LandArrayIterator_item(p->vertices, &__iter1__)) {
                    Data data;
                    data.normal.x = 0;
                    data.normal.y = 0;
                    data.normal.z = 0;
                    _lookup_close_polygons(csg, v->pos, 0.001, _merge_callback, & data);
                    v->normal = land_vector_normalize(data.normal);
                }
            }
        }
    }
    land_octree_del(csg->octree);
    csg->octree = NULL;
}
void land_csg_recalculate_face_normals(LandCSG * csg) {
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(csg->polygons);
        for (LandCSGPolygon * p = LandArrayIterator_item(csg->polygons, &__iter0__); LandArrayIterator_next(csg->polygons, &__iter0__); p = LandArrayIterator_item(csg->polygons, &__iter0__)) {
            land_csg_polygon_recalculate_normal(p);
        }
    }
}
LandCSG* land_csg_voxelize(LandCSG * csg, double radius) {
    /* FIXME: implement!
     * The idea is to create a mesh from another mesh which is a voxelized
     * version. Everything is cubes.
     */
    int x0 = floor(csg->bbox.x1 / radius);
    int y0 = floor(csg->bbox.y1 / radius);
    int z0 = floor(csg->bbox.z1 / radius);
    int xn = ceil(csg->bbox.x2 / radius) - x0;
    int yn = ceil(csg->bbox.y2 / radius) - y0;
    int zn = ceil(csg->bbox.z2 / radius) - z0;
    char * voxels = land_calloc(xn * yn * zn);
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(csg->polygons);
        for (LandCSGPolygon * p = LandArrayIterator_item(csg->polygons, &__iter0__); LandArrayIterator_next(csg->polygons, &__iter0__); p = LandArrayIterator_item(csg->polygons, &__iter0__)) {
            {
                LandArrayIterator __iter1__ = LandArrayIterator_first(p->vertices);
                for (LandCSGVertex * v = LandArrayIterator_item(p->vertices, &__iter1__); LandArrayIterator_next(p->vertices, &__iter1__); v = LandArrayIterator_item(p->vertices, &__iter1__)) {
                    int x = floor(v->pos.x / radius) - x0;
                    int y = floor(v->pos.y / radius) - y0;
                    int z = floor(v->pos.z / radius) - z0;
                    voxels [x + y * xn + z * xn * yn] = 1;
                }
            }
        }
    }
    LandArray * polygons = land_array_new();
    LandCSG * csg2 = land_csg_new_from_polygons(polygons);
    land_free(voxels);
    return csg2;
}
static void _dome_point(LandArray * vertices, LandFloat x, LandFloat y, LandFloat z, LandFloat t) {
    LandVector pos = land_vector(x, y, z);
    LandVector normal = t < 0 ? land_vector(x, y, 0) : land_vector(x, y, z);
    land_array_add(vertices, land_csg_vertex_new(pos, normal));
}
static void _dome_point_bottom(LandArray * vertices, LandFloat x, LandFloat y, LandFloat z) {
    LandVector pos = land_vector(x, y, z);
    LandVector normal = land_vector(0, 0, - 1);
    land_array_add(vertices, land_csg_vertex_new(pos, normal));
}
LandCSG* csg_dome(bool open, int slices, int discs, void * shared) {
    /* z 1   __
     * /  \
     * 0 |    |
     * |    |
     * -1 |    |
     */
    LandArray * polygons = land_array_new();
    for (int i = 0; i < discs; i += 1) {
        LandFloat r1, z1, r2, z2;
        LandFloat t1 = - 1.0 + i * 2.0 / discs;
        LandFloat t2 = - 1.0 + (i + 1) * 2.0 / discs;
        if (t1 < 0) {
            r1 = 1;
            z1 = t1;
        }
        else {
            r1 = cos(t1 * pi / 2);
            z1 = sin(t1 * pi / 2);
        }
        if (t2 < 0) {
            r2 = 1;
            z2 = t2;
        }
        else {
            r2 = cos(t2 * pi / 2);
            z2 = sin(t2 * pi / 2);
        }
        for (int j = 0; j < slices; j += 1) {
            LandFloat a1 = j * 2.0 * pi / slices;
            LandFloat a2 = (j + 1) * 2.0 * pi / slices;
            LandArray * vertices = land_array_new();
            _dome_point(vertices, cos(a1) * r1, sin(a1) * r1, z1, t1);
            _dome_point(vertices, cos(a2) * r1, sin(a2) * r1, z1, t1);
            _dome_point(vertices, cos(a2) * r2, sin(a2) * r2, z2, t2);
            if (i < discs - 1) {
                _dome_point(vertices, cos(a1) * r2, sin(a1) * r2, z2, t2);
            }
            land_array_add(polygons, land_csg_polygon_new(vertices, shared));
            if (i == 0 && ! open) {
                LandArray * bv = land_array_new();
                _dome_point_bottom(bv, cos(a2) * r1, sin(a2) * r1, z1);
                _dome_point_bottom(bv, cos(a1) * r1, sin(a1) * r1, z1);
                _dome_point_bottom(bv, 0, 0, z1);
                land_array_add(polygons, land_csg_polygon_new(bv, shared));
            }
        }
    }
    return land_csg_new_from_polygons(polygons);
}
static LandCSGVertex _mid_vertex(LandCSGVertex * av, LandCSGVertex * bv) {
    LandCSGVertex m;
    m.pos = land_vector_mul(land_vector_add(av->pos, bv->pos), 0.5);
    m.normal = land_vector_mul(land_vector_add(av->normal, bv->normal), 0.5);
    return m;
}
static void _add_tri_norm(LandArray * polygons, LandCSGVertex * av, LandCSGVertex * bv, LandCSGVertex * cv, void * shared) {
    LandArray * vertices = land_array_new();
    land_array_add(vertices, land_csg_vertex_new(av->pos, av->normal));
    land_array_add(vertices, land_csg_vertex_new(bv->pos, bv->normal));
    land_array_add(vertices, land_csg_vertex_new(cv->pos, cv->normal));
    land_array_add(polygons, land_csg_polygon_new(vertices, shared));
}
static void _split_triangle(LandArray * polygons, LandCSGVertex * av, LandCSGVertex * bv, LandCSGVertex * cv, void * shared) {
    LandCSGVertex am = _mid_vertex(av, bv);
    LandCSGVertex bm = _mid_vertex(bv, cv);
    LandCSGVertex cm = _mid_vertex(cv, av);
    _add_tri_norm(polygons, av, & am, & cm, shared);
    _add_tri_norm(polygons, bv, & bm, & am, shared);
    _add_tri_norm(polygons, cv, & cm, & bm, shared);
    _add_tri_norm(polygons, & am, & bm, & cm, shared);
}
LandCSG* land_csg_subdivide(LandCSG * csg, void * shared) {
    /* Create a new model where each triangle is split into  4 new ones.
     */
    LandArray * polygons = land_array_new();
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(csg->polygons);
        for (LandCSGPolygon * p = LandArrayIterator_item(csg->polygons, &__iter0__); LandArrayIterator_next(csg->polygons, &__iter0__); p = LandArrayIterator_item(csg->polygons, &__iter0__)) {
            LandCSGVertex * last [3];
            int count = 0;
            {
                LandArrayIterator __iter1__ = LandArrayIterator_first(p->vertices);
                for (LandCSGVertex * v = LandArrayIterator_item(p->vertices, &__iter1__); LandArrayIterator_next(p->vertices, &__iter1__); v = LandArrayIterator_item(p->vertices, &__iter1__)) {
                    last [count++] = v;
                    if (count == 3) {
                        _split_triangle(polygons, last [0], last [1], last [2], shared);
                        count--;
                        last [1] = last [2];
                    }
                }
            }
        }
    }
    return land_csg_new_from_polygons(polygons);
}
static void _add_m(LandObjFile * self, char const * name) {
    if (! self->materials) {
        self->materials = land_hash_new();
    }
    LandObjMaterial * mat;
    land_alloc(mat);
    mat->name = land_strdup(name);
    land_hash_insert(self->materials, name, mat);
    self->mat = mat;
    mat->a = 1;
}
static void _add_v(LandObjFile * self, float x, float y, float z) {
    int i = self->vn;
    self->vn++;
    self->xyz = land_realloc(self->xyz, self->vn * sizeof (float) * 3);
    self->xyz [i * 3 + 0] = x;
    self->xyz [i * 3 + 1] = y;
    self->xyz [i * 3 + 2] = z;
}
static void _add_vn(LandObjFile * self, float x, float y, float z) {
    int i = self->normal_count;
    self->normal_count++;
    self->normal = land_realloc(self->normal, self->normal_count * sizeof (float) * 3);
    self->normal [i * 3 + 0] = x;
    self->normal [i * 3 + 1] = y;
    self->normal [i * 3 + 2] = z;
}
static void _add_vt(LandObjFile * self, float x, float y) {
    int i = self->uv_count;
    self->uv_count++;
    self->uv = land_realloc(self->uv, self->uv_count * sizeof (float) * 2);
    self->uv [i * 2 + 0] = x;
    self->uv [i * 2 + 1] = y;
}
static void _add_f(LandObjFile * self, LandObjVertex v) {
    if (v.xyz < 0) {
        v.xyz += self->vn;
    }
    if (v.uv < 0) {
        v.uv += self->uv_count;
    }
    if (v.normal < 0) {
        v.normal += self->normal_count;
    }
    LandObjObject * o = self->obj;
    int i = o->tv_count;
    o->tv_count++;
    o->triangle_vertices = land_realloc(o->triangle_vertices, o->tv_count * sizeof (* o->triangle_vertices));
    o->triangle_vertices [i] = v;
}
static void _add_o(LandObjFile * self, char const * name) {
    LandObjObject * o = self->obj;
    if (self->objects == NULL) {
        self->objects = land_array_new();
    }
    if (! self->obj || self->obj->tv_count > 1) {
        land_alloc(o);
        o->mat = land_hash_get(self->materials, "");
        land_array_add(self->objects, o);
        self->obj = o;
    }
    if (! o->name && name) {
        o->name = land_strdup(name);
    }
}
static void _read_vertex(LandObjFile * self, str row) {
    str s = row + 2;
    char * ep;
    for (int i = 0; i < 4; i += 1) {
        LandObjVertex v = {0, 0, 0};
        for (int j = 0; j < 3; j += 1) {
            int value = strtol(s, & ep, 0);
            if (j == 0) {
                v.xyz = value;
            }
            if (j == 1) {
                v.uv = value;
            }
            if (j == 2) {
                v.normal = value;
            }
            if (* ep != '/') {
                break;
            }
            s = ep + 1;
        }
        if (i == 3) {
            LandObjObject * o = self->obj;
            LandObjVertex v0 = o->triangle_vertices [o->tv_count - 3];
            LandObjVertex v1 = o->triangle_vertices [o->tv_count - 1];
            _add_f(self, v0);
            _add_f(self, v1);
        }
        _add_f(self, v);
        while (* ep == ' ') {
            ep++;
        }
        if (* ep == 0) {
            break;
        }
        s = ep;
    }
}
static void _read_marker(LandObjFile * self, char * row) {
    float_t px, py, pz, rx, ry, rz, ux, uy, uz, bx, by, bz;
    char name [100];
    sscanf(row, "m %s %f %f %f %f %f %f %f %f %f %f %f %f", name, & px, & py, & pz, & rx, & ry, & rz, & ux, & uy, & uz, & bx, & by, & bz);
    LandObjMarker * m;
    land_alloc(m);
    m->name = land_strdup(name);
    LandVector p = land_vector(px, py, pz);
    LandVector r = land_vector(rx, ry, rz);
    LandVector u = land_vector(bx, by, bz);
    LandVector b = land_vector(- ux, - uy, - uz);
    m->matrix = land_4x4_matrix_from_vectors(& p, & r, & u, & b);
    land_array_add(self->markers, m);
}
static void _handle_row(LandObjFile * self, char * row) {
    char name [1024];
    if (row [0] == '#') {
        return ;
    }
    if (land_starts_with(row, "mtllib ")) {
        sscanf(row, "mtllib %1023s", name);
        _read_mtl(self, name);
    }
    if (land_starts_with(row, "v ")) {
        float_t x, y, z;
        sscanf(row, "v %f %f %f", & x, & y, & z);
        _add_v(self, x, y, z);
    }
    if (land_starts_with(row, "vn ")) {
        float_t x, y, z;
        sscanf(row, "vn %f %f %f", & x, & y, & z);
        _add_vn(self, x, y, z);
    }
    if (land_starts_with(row, "vt ")) {
        float_t x, y, z;
        sscanf(row, "vt %f %f %f", & x, & y, & z);
        _add_vt(self, x, y);
    }
    if (land_starts_with(row, "g ")) {
        sscanf(row, "g %1023s", name);
        _add_o(self, name);
    }
    if (land_starts_with(row, "f ")) {
        _read_vertex(self, row);
    }
    if (land_starts_with(row, "usemtl ")) {
        sscanf(row, "usemtl %1023s", name);
        LandObjMaterial * m = land_hash_get(self->materials, name);
        _add_o(self, NULL);
        self->obj->mat = m;
        if (! self->obj->name) {
            self->obj->name = land_strdup(name);
        }
    }
    if (land_starts_with(row, "m ")) {
        _read_marker(self, row);
    }
}
static void _handle_mtl_row(LandObjFile * self, char * row) {
    char name [1024];
    if (row [0] == '#') {
        return ;
    }
    land_strip(& row);
    if (land_starts_with(row, "newmtl ")) {
        sscanf(row, "newmtl %1023s", name);
        _add_m(self, name);
    }
    if (land_starts_with(row, "Kd ")) {
        float_t r, g, b;
        sscanf(row, "Kd %f %f %f", & r, & g, & b);
        self->mat->r = pow(r / 0.8, 1 / 2.2);
        self->mat->g = pow(g / 0.8, 1 / 2.2);
        self->mat->b = pow(b / 0.8, 1 / 2.2);
    }
    if (land_starts_with(row, "map_Kd ")) {
        sscanf(row, "map_Kd %1023s", name);
        char * path = land_replace_filename(self->filename, name);
        self->mat->texture = land_image_load(path);
        land_free(path);
    }
}
static void _read_mtl(LandObjFile * self, char const * filename) {
    char * filename2 = land_replace_filename(self->filename, filename);
    LandBuffer * fb = land_buffer_read_from_file(filename2);
    LandArray * a = land_buffer_split(fb, "\n");
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(a);
        for (LandBuffer * b = LandArrayIterator_item(a, &__iter0__); LandArrayIterator_next(a, &__iter0__); b = LandArrayIterator_item(a, &__iter0__)) {
            char * row = land_buffer_finish(b);
            _handle_mtl_row(self, row);
        }
    }
    land_array_destroy(a);
    land_free(filename2);
}
LandObjFile* land_objfile_new_from_filename(char const * filename) {
    LandObjFile * self;
    land_alloc(self);
    self->markers = land_array_new();
    _add_v(self, 0, 0, 0);
    _add_vn(self, 0, 0, 0);
    _add_vt(self, 0, 0);
    _add_m(self, "");
    self->filename = land_strdup(filename);
    LandBuffer * fb = land_buffer_read_from_file(self->filename);
    if (! fb) {
        land_log_message("Could not open %s\n", self->filename);
        self->error = 1;
        return self;
    }
    LandArray * a;
    if (land_ends_with(self->filename, ".b")) {
        int i = 0;
        int o = 1;
        while (i < fb->n) {
            uint8_t b = land_buffer_get_byte(fb, i);
            if (b == 'V') {
                float x = land_buffer_get_float(fb, i + 1);
                float y = land_buffer_get_float(fb, i + 5);
                float z = land_buffer_get_float(fb, i + 9);
                i += 13;
                _add_v(self, x, y, z);
            }
            else if (b == 'N') {
                float x = land_buffer_get_float(fb, i + 1);
                float y = land_buffer_get_float(fb, i + 5);
                float z = land_buffer_get_float(fb, i + 9);
                i += 13;
                _add_vn(self, x, y, z);
            }
            else if (b == 'T') {
                float x = land_buffer_get_float(fb, i + 1);
                float y = land_buffer_get_float(fb, i + 5);
                i += 9;
                _add_vt(self, x, y);
            }
            else if (b == 'F') {
                int a = o;
                int b = o + 1;
                int c = o + 2;
                o += 3;
                i += 1;
                LandObjVertex va, vb, vc;
                va.xyz = a;
                va.uv = a;
                va.normal = a;
                vb.xyz = b;
                vb.uv = b;
                vb.normal = b;
                vc.xyz = c;
                vc.uv = c;
                vc.normal = c;
                _add_f(self, va);
                _add_f(self, vb);
                _add_f(self, vc);
            }
            else {
                int j = land_buffer_find(fb, i, "\n");
                fb->buffer [j] = 0;
                _handle_row(self, fb->buffer + i);
                i = j + 1;
            }
        }
    }
    else {
        a = land_buffer_split(fb, "\n");
        {
            LandArrayIterator __iter0__ = LandArrayIterator_first(a);
            for (LandBuffer * b = LandArrayIterator_item(a, &__iter0__); LandArrayIterator_next(a, &__iter0__); b = LandArrayIterator_item(a, &__iter0__)) {
                char * row = land_buffer_finish(b);
                land_strip(& row);
                _handle_row(self, row);
            }
        }
        land_array_destroy(a);
    }
    return self;
}
void land_obj_transform(LandObjFile * self, Land4x4Matrix * matrix, bool include_normals) {
    for (int i = 0; i < self->vn; i += 1) {
        float x = self->xyz [i * 3 + 0];
        float y = self->xyz [i * 3 + 1];
        float z = self->xyz [i * 3 + 2];
        LandVector pos = land_vector(x, y, z);
        pos = land_vector_matmul(pos, matrix);
        self->xyz [i * 3 + 0] = pos.x;
        self->xyz [i * 3 + 1] = pos.y;
        self->xyz [i * 3 + 2] = pos.z;
    }
    if (include_normals) {
        for (int i = 0; i < self->normal_count; i += 1) {
            float nx = self->normal [i * 3 + 0];
            float ny = self->normal [i * 3 + 1];
            float nz = self->normal [i * 3 + 2];
            LandVector normal = land_vector(nx, ny, nz);
            normal = land_vector_matmul(normal, matrix);
            self->normal [i * 3 + 0] = normal.x;
            self->normal [i * 3 + 1] = normal.y;
            self->normal [i * 3 + 2] = normal.z;
        }
    }
}
LandArray* land_obj_triangles(LandObjFile * self) {
    LandArray * a = land_array_new();
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(self->objects);
        for (LandObjObject * obj = LandArrayIterator_item(self->objects, &__iter0__); LandArrayIterator_next(self->objects, &__iter0__); obj = LandArrayIterator_item(self->objects, &__iter0__)) {
            LandTriangles * t = land_triangles_new_with_normals();
            LandObjMaterial * m = obj->mat;
            land_triangles_texture(t, m->texture);
            float tw = 1, th = 1;
            if (m->texture) {
                tw = land_image_width(m->texture);
                th = land_image_height(m->texture);
            }
            for (int i = 0; i < obj->tv_count; i += 1) {
                LandObjVertex * v = obj->triangle_vertices + i;
                float x = self->xyz [v->xyz * 3 + 0];
                float y = self->xyz [v->xyz * 3 + 1];
                float z = self->xyz [v->xyz * 3 + 2];
                float nx = self->normal [v->normal * 3 + 0];
                float ny = self->normal [v->normal * 3 + 1];
                float nz = self->normal [v->normal * 3 + 2];
                float tu = self->uv [v->uv * 2 + 0] * tw;
                float tv = th - self->uv [v->uv * 2 + 1] * th;
                land_add_vertex(t, x, y, z, tu, tv, 1, 1, 1, 1);
                land_set_vertex_normal(t, nx, ny, nz);
            }
            land_array_add(a, t);
        }
    }
    return a;
}
LandArray* land_obj_markers(LandObjFile * self) {
    return self->markers;
}
    /* Drawing Primitives in Land
     * # Positions
     * The most basic information drawing primitives need is, where on the target
     * should they be drawn. There are several ways:
     * Relative screen coordinates. For example, 0/0 is the upper left screen edge, 1/1
     * the lower right screen edge. A rectangle from 0.1/0.1 to 0.9/0.9 would span 80%
     * of the width and the height of the screen, and leave a 10% border all around.
     * Obviously, using such coordinates is most useful if you don't want to care at
     * all about the actual resolution used.
     * Pixel coordinates. A coordinate is given in pixels. So, if you draw a line from
     * 1/1 to 2/2, the pixels at 1/1 and at 2/2 would be lit. This is what Land
     * originally used in its first incarnation, a very long time ago. This has some
     * disadvantages. Besides positions relying on the current resolution, it is also
     * impossible to specify sub-pixel behavior, for example when anti-aliasing is
     * switched on.
     * Subpixel coordinates. This is what Land uses by default, but you can easily
     * change it. It is a mixture of the two modes above. You give coordinates in pixel
     * positions (so positions depend on the resolution), but each integer position is
     * the upper left corner of a pixel, not the pixel as a whole, as in the method
     * before. To get the behavior of the previous method, you would do two things:
     * 1. For rectangles, draw them with their full width.
     * 2. For images, don't do anything.
     * To make clear why, let's compare a rectangle and an image. The rectangle is
     * drawn at pixel 0/0 with the pixel method, and 10x10
     * pixels big. So the pixels that are lit are from 0/0 to 9/9, inclusive. Doing the
     * same with the subpixel method, we would draw a rectangle from 0/0 to 10/10.
     * But what just happened? The rectangle of lit pixels before was 10x10 pixels big,
     * from pixel 0 to pixel 9 inclusive. The new subpixel rectangle also is 10x10
     * pixels big. Just now there is no more inclusive/exclusive pixels, the rectangle
     * coordinates are independent now.
     * The problem is, that what we really want with subpixel coordinates is not just
     * an outline anymore, but a frame which is one pixel thick. Its outer rectangle
     * would actually start at 0/0, and end at 10/10. Its inner rectangle would start
     * at 1/1 and end at 9/9. Drawing a single rectangle from 0.5/0.5 to 9.5/9.5 just
     * works, because we assumed a line-thickness of 1.
     * But with images, there is no line thickness. So if we draw an image which is
     * 10x10 pixels big, and we draw it to 0/0, it will exactly fit the same rectangle.
     * Would we draw it to 0.5/0.5, that just would be wrong.
     * Now, to make things easier, you don't have to use sub-pixel coordinates if you
     * absolutely don't want to. Call:
     * land_use_screen_positions()
     * to use the method which maps the screen to 0/0..1/1,
     * or
     * land_use_pixel_positions()
     * to use pixel positions. That is, Land will always lit the exact pixel you
     * specify, and also draw images correctly to full-pixel positions. In fact, this
     * mode just maps the coordinates like described above, so you still can use
     * non-integer positions (but there really is no reason to not use one of the other
     * two positioning modes then) and enable e.g. anti-aliasing.
     * # Primitives
     * ## line
     * This draws a line from the first point to the second point.
     * ## rectangle
     * This draws a rectangle from the first point to the second point.
     * ## filled_rectangle
     * Like rectangle, but filled.
     * ## circle/ellipse/oval
     * This draws a circle, inscribed into the given rectangle. The alternate names
     * "ellipse" and "oval" for this function actually fit better.
     * ## filled_circle
     * Like circle, but filled.
     */
static int resize_event_counter;
static int was_resized;
static int switch_out_event_counter;
static int was_switched_out;
static LandList * _previous;
LandDisplay * _land_active_display;
LandDisplay* land_display_new(int w, int h, int flags) {
    LandDisplay * self = platform_display_new();
    self->w = w;
    self->h = h;
    self->flags = flags;
    self->clip_x1 = 0;
    self->clip_y1 = 0;
    self->clip_x2 = w;
    self->clip_y2 = h;
    self->clip_off = 0;
    land_display_select(self);
    land_reset_transform();
    return self;
}
void land_display_destroy(LandDisplay * self) {
    if (self == _land_active_display) {
        land_display_unselect();
    }
    if (self->clip_stack_depth) {
        land_log_message("Error: non-empty clip stack in display.\n");
    }
    platform_display_del(self);
    land_free(self);
}
void land_display_del(LandDisplay * self) {
    land_display_destroy(self);
}
double land_scale_to_fit_into(float w, float h, float l, float t, float r, float b, int how) {
    /* If w/h is the dimensions you want to use and l/t/r/b is the actual
     * pixel region available, this will apply an appropriate transformation.
     */
    float dw = r - l;
    float dh = b - t;
    float sx, sy;
    float ox = 0, oy = 0;
    int back = how >> 8;
    how &= 255;
    if (how == 0) {
        how = 2;
        if (w * dh / dw < h) {
            how = 3;
        }
    }
    if (how == 1) {
        how = 2;
        if (w * h / dw < dh) {
            how = 3;
        }
    }
    if (how == 4) {
        how = 6;
        if (w * dh / dw < h) {
            how = 7;
        }
    }
    if (how == 2 || how == 6) {
        sx = sy = dw / w;
        if (how == 2) {
            oy = (dh - h * sy) / 2;
        }
    }
    else if (how == 3 || how == 7) {
        sx = sy = dh / h;
        if (how == 3) {
            ox = (dw - w * sx) / 2;
        }
    }
    else {
        sx = dw / w;
        sy = dh / h;
    }
    land_reset_transform();
    if (back) {
        land_scale(1 / sx, 1 / sy);
        land_translate(- ox - l, - oy - t);
    }
    else {
        land_translate(ox + l, oy + t);
        land_scale(sx, sy);
    }
    return sx;
}
void land_get_scaled_dimensions(float * x, float * y, float * w, float * h) {
    * x = land_get_left();
    * y = land_get_top();
    * w = land_display_width() / land_get_x_scale();
    * h = land_display_height() / land_get_y_scale();
}
double land_scale_to_fit(float w, float h, int how) {
    float dw = land_display_width();
    float dh = land_display_height();
    return land_scale_to_fit_into(w, h, 0, 0, dw, dh, how);
}
LandFloat land_get_left(void) {
    LandFloat * m = _land_active_display->matrix.v;
    return - m [3] / m [0];
}
LandFloat land_get_x_scale(void) {
    LandFloat * m = _land_active_display->matrix.v;
    return m [0];
}
LandFloat land_get_top(void) {
    LandFloat * m = _land_active_display->matrix.v;
    return - m [7] / m [5];
}
LandFloat land_get_y_scale(void) {
    LandFloat * m = _land_active_display->matrix.v;
    return m [5];
}
void land_set_image_display(LandImage * image) {
    /* Change the display of the current thread to an internal display which draws
     * to the specified image. This cannot be nested.
     */
    platform_set_image_display(image);
}
void land_unset_image_display(void) {
    /* Restore the display to what it was before the call to
     * land_set_image_display.
     */
    platform_unset_image_display();
}
void land_display_set(void) {
    /* Make the active display of the current thread the active one. This may
     * involve creating a new window. There is usually no need to call this
     * function directly, as it will be called internally.
     */
    platform_display_set();
    platform_display_color();
    platform_display_clip();
}
LandDisplay* land_display_get(void) {
    /* Retrieve a handle to the currently active display of the calling thread.
     */
    return _land_active_display;
}
void land_display_unset(void) {
    /* Make the current display invalid. Usually there is no need for this.
     */
    _land_active_display = NULL;
}
void land_display_init(void) {
    land_log_message("land_display_init\n");
    _previous = land_list_new();
    platform_display_init();
}
void land_display_exit(void) {
    land_log_message("land_display_exit\n");
    land_list_destroy(_previous);
    platform_display_exit();
}
double land_display_time_flip_speed(double howlong) {
    /* This function is dangerous! It will completely halt Land for the passed
     * time in seconds.
     * The function will try to determine how long flipping of the display takes.
     * This can be used to see if the refresh rate is honored (usually because
     * vsync is enabled).
     */
    land_flip();
    double t = land_get_time();
    double t2;
    int i = 0;
    while ((t2 = land_get_time()) < t + howlong) {
        land_flip();
        i += 1;
    }
    return i / (t2 - t);
}
void land_display_toggle_fullscreen(void) {
    /* Toggle the current thread's display between windowed and fullscreen
     * mode, if possible.
     */
    LandDisplay * d = _land_active_display;
    d->flags ^= LAND_FULLSCREEN;
    if (d->flags & LAND_FULLSCREEN) {
        d->flags &= ~ LAND_WINDOWED;
    }
    else {
        d->flags |= LAND_WINDOWED;
    }
    land_display_set();
}
void land_clear(float r, float g, float b, float a) {
    /* Clear the current thread's display to the specified color. Always set
     * '''a''' to 1, or you may get a transparent background.
     */
    LandDisplay * d = _land_active_display;
    platform_display_clear(d, r, g, b, a);
}
void land_clear_depth(float z) {
    LandDisplay * d = _land_active_display;
    platform_display_clear_depth(d, z);
}
void land_color(float r, float g, float b, float a) {
    /* Change the color of the current thread's active display. This is the color
     * which will be used for subsequent graphics commands.
     */
    LandDisplay * d = _land_active_display;
    d->color_r = r;
    d->color_g = g;
    d->color_b = b;
    d->color_a = a;
    platform_display_color();
}
void land_premul(float r, float g, float b, float a) {
    land_color_set(land_color_premul(r, g, b, a));
}
void land_color_set(LandColor c) {
    land_color(c.r, c.g, c.b, c.a);
}
void land_color_set_named(str name) {
    land_color_set(land_color_name(name));
}
LandColor land_color_get(void) {
    LandColor c;
    land_get_color(& c.r, & c.g, & c.b, & c.a);
    return c;
}
void land_thickness(float t) {
    LandDisplay * d = _land_active_display;
    d->thickness = t;
}
void land_get_color(float * r, float * g, float * b, float * a) {
    /* Retrieve the current color.
     */
    LandDisplay * d = _land_active_display;
    * r = d->color_r;
    * g = d->color_g;
    * b = d->color_b;
    * a = d->color_a;
}
int land_blend(int state) {
    LandDisplay * d = _land_active_display;
    int prev = d->blend;
    d->blend = state;
    return prev;
}
void land_clip(float x, float y, float x_, float y_) {
    LandDisplay * d = _land_active_display;
    d->clip_x1 = x;
    d->clip_y1 = y;
    d->clip_x2 = x_;
    d->clip_y2 = y_;
    platform_display_clip();
}
void land_clip_transformed(float x, float y, float x_, float y_) {
    LandFloat rx = x, ry = y, rz = 0;
    LandFloat rx2 = x_, ry2 = y_, rz2 = 0;
    land_transform(& rx, & ry, & rz);
    land_transform(& rx2, & ry2, & rz2);
    land_clip(rx, ry, rx2, ry2);
}
void land_clip_intersect(float x, float y, float x_, float y_) {
    LandDisplay * d = _land_active_display;
    d->clip_x1 = _scramble_max(d->clip_x1, x);
    d->clip_y1 = _scramble_max(d->clip_y1, y);
    d->clip_x2 = _scramble_min(d->clip_x2, x_);
    d->clip_y2 = _scramble_min(d->clip_y2, y_);
    platform_display_clip();
}
void land_clip_push(void) {
    LandDisplay * d = _land_active_display;
    if (d->clip_stack_depth > LAND_MAX_CLIP_DEPTH) {
        printf("error: exceeded clip depth\n");
        return ;
    }
    int * clip = d->clip_stack + d->clip_stack_depth * 5;
    clip [0] = d->clip_x1;
    clip [1] = d->clip_y1;
    clip [2] = d->clip_x2;
    clip [3] = d->clip_y2;
    clip [4] = d->clip_off;
    d->clip_stack_depth++;
}
void land_clip_pop(void) {
    LandDisplay * d = _land_active_display;
    if (d->clip_stack_depth < 1) {
        return ;
    }
    d->clip_stack_depth--;
    int * clip = d->clip_stack + d->clip_stack_depth * 5;
    d->clip_x1 = clip [0];
    d->clip_y1 = clip [1];
    d->clip_x2 = clip [2];
    d->clip_y2 = clip [3];
    d->clip_off = clip [4];
    platform_display_clip();
}
void land_clip_on(void) {
    LandDisplay * d = _land_active_display;
    d->clip_off = 0;
    platform_display_clip();
}
void land_clip_off(void) {
    LandDisplay * d = _land_active_display;
    d->clip_off = 1;
    platform_display_clip();
}
void land_unclip(void) {
    LandDisplay * d = _land_active_display;
    d->clip_x1 = 0;
    d->clip_y1 = 0;
    d->clip_x2 = land_display_width();
    d->clip_y2 = land_display_height();
    platform_display_clip();
}
int land_get_clip(float * cx1, float * cy1, float * cx2, float * cy2) {
    LandDisplay * d = _land_active_display;
    * cx1 = d->clip_x1;
    * cy1 = d->clip_y1;
    * cx2 = d->clip_x2;
    * cy2 = d->clip_y2;
    return ! d->clip_off;
}
void land_flip(void) {
    platform_display_flip();
}
void land_rectangle(float x, float y, float x_, float y_) {
    platform_rectangle(x, y, x_, y_);
}
void land_filled_rectangle(float x, float y, float x_, float y_) {
    platform_filled_rectangle(x, y, x_, y_);
}
void land_filled_circle(float x, float y, float x_, float y_) {
    platform_filled_circle(x, y, x_, y_);
}
void land_circle(float x, float y, float x_, float y_) {
    platform_circle(x, y, x_, y_);
}
void land_arc(float x, float y, float x_, float y_, float a, float a_) {
    platform_arc(x, y, x_, y_, a, a_);
}
void land_line(float x, float y, float x_, float y_) {
    platform_line(x, y, x_, y_);
}
void land_move_to(float x, float y) {
    LandDisplay * d = _land_active_display;
    d->cursor_x = x;
    d->cursor_y = y;
}
void land_line_to(float x, float y) {
    LandDisplay * d = _land_active_display;
    land_line(d->cursor_x, d->cursor_y, x, y);
    d->cursor_x = x;
    d->cursor_y = y;
}
void land_ribbon(int n, float * xy) {
    platform_ribbon(n, xy);
}
void land_ribbon_loop(int n, float * xy) {
    platform_ribbon_loop(n, xy);
}
void land_filled_ribbon(int n, float * xy) {
    platform_filled_ribbon(n, xy);
}
void land_polygon(int n, float * xy) {
    platform_polygon(n, xy);
}
void land_filled_polygon(int n, float * xy) {
    platform_filled_polygon(n, xy);
}
void land_filled_triangle(float x0, float y0, float x1, float y1, float x2, float y2) {
    float xy [6] = {x0, y0, x1, y1, x2, y2};
    platform_filled_polygon(3, xy);
}
void land_3d_triangles(int n, LandFloat * xyzrgb) {
    platform_3d_triangles(n, xyzrgb);
}
void land_textured_polygon(LandImage * image, int n, float * xy, float * uv) {
    platform_textured_polygon(image, n, xy, uv);
}
void land_textured_colored_polygon(LandImage * image, int n, float * xy, float * uv, float * rgba) {
    platform_textured_colored_polygon(image, n, xy, uv, rgba);
}
void land_filled_polygon_with_holes(int n, float * xy, int * holes) {
    platform_filled_polygon_with_holes(n, xy, holes);
}
void land_filled_colored_polygon(int n, float * xy, float * rgba) {
    platform_filled_colored_polygon(n, xy, rgba);
}
void land_plot(float x, float y) {
    platform_plot(x, y);
}
void land_pick_color(float x, float y) {
    platform_pick_color(x, y);
}
int land_display_width(void) {
    LandDisplay * self = _land_active_display;
    if (! self) {
        return 0;
    }
    return self->w;
}
int land_display_height(void) {
    LandDisplay * self = _land_active_display;
    if (! self) {
        return 0;
    }
    return self->h;
}
void land_display_resize(int w, int h) {
    /* Resize the current display to the given dimensions.
     */
    LandDisplay * d = _land_active_display;
    d->w = w;
    d->h = h;
    platform_display_resize(w, h);
    land_resize_event(w, h);
}
void land_display_move(int x, int y) {
    platform_display_move(x, y);
}
void land_display_desktop_size(int * w, int * h) {
    platform_display_desktop_size(w, h);
}
void land_display_title(char const * title) {
    platform_display_title(title);
}
void land_display_icon(LandImage * icon) {
    platform_display_icon(icon);
}
int land_display_flags(void) {
    LandDisplay * self = _land_active_display;
    return self->flags;
}
LandImage* land_display_new_image(void) {
    LandImage * image = platform_new_image();
    return image;
}
void land_display_del_image(LandImage * image) {
    return platform_del_image(image);
}
void land_display_select(LandDisplay * display) {
    if (_land_active_display) {
        land_add_list_data(& _previous, _land_active_display);
    }
    _land_active_display = display;
}
void land_display_unselect(void) {
    if (_previous && _previous->count) {
        LandListItem * last = _previous->last;
        _land_active_display = last->data;
        land_list_remove_item(_previous, last);
        land_listitem_destroy(last);
    }
    else {
        _land_active_display = NULL;
    }
}
void land_screenshot(char const * filename) {
    int w = land_display_width();
    int h = land_display_height();
    LandImage * screenshot = land_image_new(w, h);
    land_image_grab(screenshot, 0, 0);
    land_image_save(screenshot, filename);
    land_image_destroy(screenshot);
}
void land_screenshot_autoname(char const * name) {
    LandBuffer * b = land_buffer_new();
    time_t t = time(NULL);
    land_buffer_cat(b, name);
    land_buffer_cat(b, "_");
    land_buffer_cat(b, ctime(& t));
    land_buffer_strip(b, " \n");
    land_buffer_cat(b, ".jpg");
    char * path = land_buffer_finish(b);
    land_screenshot(path);
    land_free(path);
}
void land_resize_event(int w, int h) {
    resize_event_counter++;
    _land_active_display->w = w;
    _land_active_display->h = h;
    _land_active_display->clip_x2 = w;
    _land_active_display->clip_y2 = h;
}
void land_switch_out_event(void) {
    switch_out_event_counter++;
}
int land_switched_out(void) {
    return was_switched_out;
}
int land_was_resized(void) {
    return was_resized;
}
void land_display_tick(void) {
    was_resized = resize_event_counter;
    resize_event_counter = 0;
    was_switched_out = switch_out_event_counter;
    switch_out_event_counter = 0;
}
void land_rotate(LandFloat angle) {
    /* Pre-rotate the current transformation.
     */
    LandFloat * m = _land_active_display->matrix.v;
    LandFloat c = cosf(angle);
    LandFloat s = sinf(angle);
    LandFloat x, y;
    x = m [0];
    y = m [1];
    m [0] = x * (+ c) + y * (+ s);
    m [1] = x * (- s) + y * (+ c);
    x = m [4];
    y = m [5];
    m [4] = x * (+ c) + y * (+ s);
    m [5] = x * (- s) + y * (+ c);
    _land_active_display->matrix_modified = 1;
}
void land_scale(LandFloat x, LandFloat y) {
    /* Pre-scale the current transformation.
     */
    LandFloat * m = _land_active_display->matrix.v;
    m [0] *= x;
    m [1] *= y;
    m [4] *= x;
    m [5] *= y;
    _land_active_display->matrix_modified = 1;
}
void land_translate(LandFloat x, LandFloat y) {
    /* Pre-translate the current transformation. That is, any transformations in
     * effect prior to this call will be applied afterwards. And transformations
     * after this call until before the next drawing command will be applied
     * before.
     */
    LandFloat * m = _land_active_display->matrix.v;
    m [3] += x * m [0] + y * m [1];
    m [7] += x * m [4] + y * m [5];
    _land_active_display->matrix_modified = 1;
}
void land_z(LandFloat z) {
    LandFloat * m = _land_active_display->matrix.v;
    m [3] += z * m [2];
    m [7] += z * m [6];
    m [11] += z * m [10];
    _land_active_display->matrix_modified = 1;
}
void land_push_transform(void) {
    if (_land_active_display->matrix_stack_depth < 16) {
        int i = _land_active_display->matrix_stack_depth++;
        _land_active_display->matrix_stack [i] = _land_active_display->matrix;
    }
}
void land_pop_transform(void) {
    if (_land_active_display->matrix_stack_depth > 0) {
        int i = --_land_active_display->matrix_stack_depth;
        _land_active_display->matrix = _land_active_display->matrix_stack [i];
        _land_active_display->matrix_modified = 1;
    }
}
void land_reset_transform(void) {
    LandFloat * m = _land_active_display->matrix.v;
    LandFloat i [16]={1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,};
    memcpy(m, i, sizeof i);
    _land_active_display->matrix_modified = 1;
}
LandVector land_transform_vector(LandVector v) {
    return land_vector_matmul(v, & _land_active_display->matrix);
}
void land_transform(LandFloat * x, LandFloat * y, LandFloat * z) {
    LandVector v = land_vector_matmul(land_vector(* x, * y, * z), & _land_active_display->matrix);
    * x = v.x;
    * y = v.y;
    * z = v.z;
}
Land4x4Matrix land_get_transform(void) {
    return _land_active_display->matrix;
}
void land_projection(Land4x4Matrix m) {
    platform_projection(m);
}
void land_reset_projection(void) {
    platform_reset_projection();
}
void land_display_transform_4x4(Land4x4Matrix * matrix) {
    _land_active_display->matrix = * matrix;
    _land_active_display->matrix_modified = 1;
}
void land_display_compose_transform(Land4x4Matrix matrix) {
    _land_active_display->matrix = land_4x4_matrix_mul(_land_active_display->matrix, matrix);
    _land_active_display->matrix_modified = 1;
}
void land_display_precompose_transform(Land4x4Matrix matrix) {
    _land_active_display->matrix = land_4x4_matrix_mul(matrix, _land_active_display->matrix);
    _land_active_display->matrix_modified = 1;
}
void land_render_state(int state, int value) {
    platform_render_state(state, value);
}
void land_display_set_default_shaders(void) {
    /* When using custom shaders, use this to restore the shader expected by the
     * builtin functions.
     */
    platform_set_default_shaders();
}
int land_display_dpi(void) {
    return platform_get_dpi();
}
    /* Simple themeable widgets to use for in-game user interface elements.
     * This module implements a simple graphical user interface on top of Land.
     * = Some Features =
     * * Simple. This is intended to be used in-game, so no advanced features.
     * * A widget is basically a box. It can contain other boxes, to which mouse and
     * keyboard input is dispatched.
     * * Themeable, either with custom drawing, or bitmap themes.
     * = Mouse Focus =
     * Mouse focus is given to the window under the mouse, if the left button is not
     * being held. Else the focused window retains focus as long as the left mouse
     * button is being held pressed, even if the mouse leaves the window.
     * = Keyboard Focus =
     * Keyboard focus is only given to widgets requesting it. A focused window retains
     * keyboard focus, unless focus is transferred to another window.
     * A widget will normally ask for keyboard focus being transferred to it, when
     * the mouse is clicked over it, or when the TAB key is pressed and it is the
     * next in the widget cycle. This cycle is constructed by walking all widgets in
     * order, starting with the parent, then the children, recursively.
     * = Layout =
     * About using the auto layout:
     * * Each widget has an outer box, which is how much space it takes up inside
     * its parent.
     * * Additionally, it has an inner box, which is the space available to
     * children. The inner box is 6 values: il, it, ir, ib, hgap, vgap. The first
     * 4 are for a border all around the widget, the last 2 are gaps between
     * multiple children.
     * * The space between outer and inner box is usually filled with some kind of
     * border by the themeing.
     * * The layout allows layers, so child windows can share the same space.
     * * By default, container widgets try to fill up as much space as they can, so
     * if you place e.g. a VBox onto the desktop, it fills it up completely when
     * using auto layout. Non-container widgets on the other hand usually try to
     * be as small as possible, e.g. a button will try to fit around the text/image
     * inside it. You can of course change for each widget how it behaves.
     * = Themeing =
     * Each widget has a pointer to a theme. On creation, a widget inherits this
     * theme from its parent. So usually is is enough to set a theme for the
     * desktop, then all widgets spawned from it will inherit the theme. And it's
     * also easy to have per-widget themes, either for a single widget, or for a
     * window or menu. In the latter case, simply set the theme before creating
     * children.
     * Themes are built from bitmaps. This keeps them very simple, and should be
     * enough in most cases. And of course, if you absolutely don't want to, you
     * don't have to use themes. Simply override the draw method of all widgets
     * which you want to draw yourself.
     * = Polymorphism =
     * The widgets are organized in a class hierarchy. To use polymorphism, you need
     * to convert pointers to one class to pointers to a base or specialized class.
     * This is done using land_widget_check, which will use the widgets vtable to
     * assert that the conversion is possible and generate a runtime error
     * otherwise.
     * = Reference counting =
     * The widgets use reference counting to handle deletion. This is a cheap way
     * out of dealing with stale references/dangling pointers. A GUI without either reference counting
     * or garbage collection is possible, it just needs some more design work. In
     * our case here, following a KISS principle, we do simple naive reference counting,
     * and leave the user to deal with possible problems like circular references.
     * In the normal case, it works like this: You create a widget, and have to
     * pass along a parent widget to the constructor. Now, the parent will hold a
     * reference to the new widget. There is no reference from the child to the
     * parent, despite the parent field referencing the parent. This is done to
     * avoid complications with cyclic references. If your own widgets contain
     * cyclic references in another way, you should understand how the reference
     * counting works.
     * The first consequence of the above is, you always should manually reference the
     * top level window, since it has no parent it is referenced by.
     * The apparent problem is the possible dangling pointer of a child to its
     * parent. But it should be save, since whenever the parent is deleted, it will
     * delete all its children anyway.
     * = An example =
     * desktop = desktop_new()
     * reference(desktop)
     * child = window_new(desktop)
     * unreference(desktop)
     * This does what is expected. The only reference to desktop is removed manually,
     * therefore it gets destroyed. The destructor will detach all children. The only
     * child in this case will therefore drop its reference count to zero, and get
     * destroyed as well.
     * desktop = desktop_new()
     * reference(desktop)
     * child = window_new(desktop)
     * reference(child)
     * unreference(desktop)
     * Here, a reference is kept to child. Maybe it is the window with keyboard
     * focus, and the focus handler holds a reference to it. So, when the desktop
     * is destroyed, first all childs are detached again. This means, the parent
     * member of child is set to NULL, and its reference is decreased. Since there
     * is still the manual reference, nothing else will be done. The desktop itself
     * however is destroyed. Also note that any other childs without a reference
     * would be destroyed correctly, and it also would work recursively down for
     * their childs. Only the child window stays, and the focus handler won't
     * crash.
     * unreference(child)
     * If the focus handler is done, the reference of child will now drop to zero,
     * and it is destroyed as well.
     * Now, about cyclic references, just either don't use them, or else take care
     * to resolve them before dropping the last reference into the cycle. As an
     * example, you make a watchdog window, which somehow watches another window.
     * So, you play good, and along with storing a reference to that other window,
     * you increase the reference count of the other window, just so you never get
     * a dangling pointer. In your destructor, you release the reference again, so
     * everything seems to work out. But consider this:
     * desktop = new_widget(NULL)
     * reference(desktop)
     * watchdog = new_widget(desktop)
     * watchdog_watch(desktop)
     * unreference(desktop)
     * Yikes. Now you see the problem. Although nobody holds a reference to watchdog,
     * and we remove the only real reference to desktop, neither of them gets deleted.
     * Worse, neither of them can ever be deleted again, since the only reference to
     * either is from each other.
     * Simple rule here would be: The watchdog only ever should watch a sibling or
     * unrelated widget, never a parent. Of course, in practice, widgets could get
     * reparented and whatever, so things like this need watching out for. And there
     * are many other cases. Also, you never have to use the reference counting. You
     * just need to understand that Land provides no way to directly and forcefully
     * delete one of its widgets, and why it is like that.
     */
LandWidgetInterface * land_widget_base_interface;
static LandArray * land_widget_interfaces;
int land_widget_is(LandWidget const * self, int id) {
    /* Return true if the widget has the given type (or one derived from it).
     */
    int i;
    for (i = 0; i < 7; i++) {
        int digit = id & (0xf << (i * 4));
        if (! digit) {
            break;
        }
        if ((self->vt->id & (0xf << (i * 4))) != digit) {
            return 0;
        }
    }
    return 1;
}
void* land_widget_check(void const * ptr, int id, char const * file, int linenum) {
    LandWidget const * widget = ptr;
    if (land_widget_is(widget, id)) {
        return (void *) ptr;
    }
    land_exception("%s: %d: Widget cannot be converted.", file, linenum);
    return NULL;
}
char const* land_widget_info_string(LandWidget * w) {
    static char str [1024];
    if (! w) {
        strcpy(str, "none");
    }
    else if (land_widget_is(w, LAND_WIDGET_ID_MENU)) {
        LandWidgetContainer * c = (void *) w;
        int n = 0;
        if (c->children) {
            n = c->children->count;
        }
        sprintf(str, "menu (%d items)", n);
    }
    else if (land_widget_is(w, LAND_WIDGET_ID_MENUBAR)) {
        LandWidgetContainer * c = (void *) w;
        int n = 0;
        if (c->children) {
            n = c->children->count;
        }
        sprintf(str, "menubar (%d items)", n);
    }
    else if (land_widget_is(w, LAND_WIDGET_ID_MENUITEM)) {
        LandWidgetButton * b = (void *) w;
        sprintf(str, "menuitem %s", b->text);
    }
    else if (land_widget_is(w, LAND_WIDGET_ID_MENUBUTTON)) {
        LandWidgetButton * b = (void *) w;
        sprintf(str, "menubutton %s", b->text);
    }
    else if (land_widget_is(w, LAND_WIDGET_ID_BUTTON)) {
        LandWidgetButton * b = (void *) w;
        sprintf(str, "button %s", b->text);
    }
    else if (land_widget_is(w, LAND_WIDGET_ID_CHECKBOX)) {
        sprintf(str, "checkbox");
    }
    else if (land_widget_is(w, LAND_WIDGET_ID_BOOK)) {
        sprintf(str, "book");
    }
    else if (land_widget_is(w, LAND_WIDGET_ID_SCROLLING)) {
        sprintf(str, "scrolling");
    }
    else if (land_widget_is(w, LAND_WIDGET_ID_SCROLLBAR)) {
        sprintf(str, "scrollbar");
    }
    else if (land_widget_is(w, LAND_WIDGET_ID_BOOKPAGE)) {
        sprintf(str, "bookpage");
    }
    else if (land_widget_is(w, LAND_WIDGET_ID_LIST)) {
        sprintf(str, "list");
    }
    else if (land_widget_is(w, LAND_WIDGET_ID_LISTITEM)) {
        sprintf(str, "listitem");
    }
    else if (land_widget_is(w, LAND_WIDGET_ID_VBOX)) {
        sprintf(str, "vbox");
    }
    else if (land_widget_is(w, LAND_WIDGET_ID_HBOX)) {
        sprintf(str, "hbox");
    }
    else if (land_widget_is(w, LAND_WIDGET_ID_PANEL)) {
        sprintf(str, "panel");
    }
    else if (land_widget_is(w, LAND_WIDGET_ID_CONTAINER)) {
        sprintf(str, "container");
    }
    else {
        sprintf(str, "unknown (%d)", w->vt->id);
    }
    return str;
}
void land_widget_set_property(LandWidget * self, char const * property, void * data, void(* destroy)(void * data)) {
    if (! self->properties) {
        self->properties = land_hash_new();
    }
    LandWidgetProperty * prop;
    land_alloc(prop);
    prop->data = data;
    prop->destroy = destroy;
    land_hash_insert(self->properties, property, prop);
}
void land_widget_del_property(LandWidget * self, char const * property) {
    if (! self->properties) {
        return ;
    }
    LandWidgetProperty * prop = land_hash_remove(self->properties, property);
    if (prop->destroy) {
        prop->destroy(prop);
    }
}
void* land_widget_get_property(LandWidget * self, char const * property) {
    if (! self->properties) {
        return NULL;
    }
    LandWidgetProperty * prop = land_hash_get(self->properties, property);
    if (prop) {
        return prop->data;
    }
    return NULL;
}
void land_widget_remove_all_properties(LandWidget * self) {
    LandHash * hash = self->properties;
    if (! hash) {
        return ;
    }
    int i;
    for (i = 0; i < hash->size; i++) {
        if (hash->entries [i]) {
            int j;
            for (j = 0; j < hash->entries [i]->n; j++) {
                LandWidgetProperty * prop = hash->entries [i] [j].data;
                if (prop->destroy) {
                    prop->destroy(prop->data);
                }
                land_free(prop);
            }
        }
    }
    land_hash_destroy(self->properties);
    self->properties = NULL;
}
void land_widget_base_initialize(LandWidget * self, LandWidget * parent, int x, int y, int w, int h) {
    land_widget_base_interface_initialize();
    land_widget_layout_initialize(self, x, y, w, h);
    self->vt = land_widget_base_interface;
    land_widget_layout_set_minimum_size(self, w, h);
    if (parent) {
        if (parent->element) {
            self->element = land_widget_theme_find_element(parent->element->theme, self);
        }
        land_call_method(parent, add, (parent, self));
    }
    else {
        self->element = land_widget_theme_find_element(land_widget_theme_default(), self);
    }
}
LandWidget* land_widget_base_new(LandWidget * parent, int x, int y, int w, int h) {
    LandWidget * self;
    land_alloc(self);
    land_widget_base_initialize(self, parent, x, y, w, h);
    return self;
}
void land_widget_remove(LandWidget * self) {
    /* """Remove a widget from its parent.
     * Note: If the reference held by the parent was the last this will
     * also destroy the widget.
     */
    if (! self->parent) {
        return ;
    }
    land_call_method(self->parent, remove, (self->parent, self));
}
void land_widget_interfaces_destroy_all(void) {
    int n = land_array_count(land_widget_interfaces);
    land_log_message("land_widget_interfaces_destroy_all (%d)\n", n);
    int i;
    for (i = 0; i < n; i++) {
        LandWidgetInterface * f = land_array_get_nth(land_widget_interfaces, i);
        land_free(f->name);
        land_free(f);
    }
    land_array_destroy(land_widget_interfaces);
}
void land_widget_interface_register(LandWidgetInterface * vt) {
    /* """Register a new widget interface with Land.
     * The interface is then owned by Land, and you should not try to free the
     * passed pointer. Land will automatically free it when you call land_quit().
     */
    if (! land_widget_interfaces) {
        land_log_message("land_widget_interfaces\n");
        land_exit_function(land_widget_interfaces_destroy_all);
    }
    land_array_add_data(& land_widget_interfaces, vt);
}
LandWidgetInterface* land_widget_copy_interface(LandWidgetInterface * basevt, char const * name) {
    LandWidgetInterface * vt;
    land_alloc(vt);
    memcpy(vt, basevt, sizeof (* vt));
    vt->name = land_strdup(name);
    land_widget_interface_register(vt);
    return vt;
}
void land_widget_create_interface(LandWidget * widget, char const * name) {
    widget->vt = land_widget_copy_interface(widget->vt, name);
    land_widget_theme_update(widget);
}
void land_widget_base_destroy(LandWidget * self) {
    land_widget_remove_all_properties(self);
    land_internal_land_gul_box_deinitialize(& self->box);
    land_free(self);
}
static void land_widget_really_destroy(LandWidget * self) {
    if (self->vt->destroy) {
        self->vt->destroy(self);
    }
    else {
        land_log_message("*** widget without destructor?\n");
        land_widget_base_destroy(self);
    }
}
void land_widget_unreference(LandWidget * self) {
    self->reference--;
    if (self->reference <= 0) {
        land_widget_really_destroy(self);
    }
}
void land_widget_reference(LandWidget * self) {
    self->reference++;
}
void land_widget_base_mouse_enter(LandWidget * self, LandWidget * focus) {
    ;
}
void land_widget_base_mouse_leave(LandWidget * self, LandWidget * focus) {
    ;
}
void land_widget_base_move(LandWidget * self, float dx, float dy) {
    ;
}
void land_widget_move(LandWidget * self, float dx, float dy) {
    /* Moves the widget.
     */
    self->box.x += dx;
    self->box.y += dy;
    land_call_method(self, move, (self, dx, dy));
}
void land_widget_move_to(LandWidget * self, float x, float y) {
    land_widget_move(self, x - self->box.x, y - self->box.y);
}
void land_widget_center(LandWidget * self) {
    land_widget_align(self, LandAlignCenter | LandAlignMiddle);
}
void land_widget_align(LandWidget * self, int align) {
    int dw = land_display_width();
    int dh = land_display_height();
    int x = self->box.x;
    int y = self->box.y;
    int w = self->box.w;
    int h = self->box.h;
    int px = x;
    int py = y;
    if (align & LandAlignRight) {
        px = dw - w;
    }
    if (align & LandAlignCenter) {
        px = (dw - w) / 2;
    }
    if (align & LandAlignBottom) {
        py = dh - h;
    }
    if (align & LandAlignMiddle) {
        py = (dh - h) / 2;
    }
    land_widget_move(self, px - x, py - y);
}
void land_widget_base_size(LandWidget * self, float dx, float dy) {
    ;
}
void land_widget_size(LandWidget * self, float dx, float dy) {
    /* Resizes a widget.
     */
    self->box.w += dx;
    self->box.h += dy;
    land_call_method(self, size, (self, dx, dy));
}
void land_widget_resize(LandWidget * self, float dx, float dy) {
    /* Changes the minimum size of the widget to its current size modified by the
     * given offset.
     */
    self->box.min_width = self->box.w + dx;
    self->box.min_height = self->box.h + dy;
    land_widget_size(self, dx, dy);
}
void land_widget_set_size_permanent(LandWidget * self, float w, float h) {
    land_widget_resize(self, w - self->box.w, h - self->box.h);
}
void land_widget_set_size(LandWidget * self, float w, float h) {
    land_widget_size(self, w - self->box.w, h - self->box.h);
}
void land_widget_set_height(LandWidget * self, float h) {
    land_widget_resize(self, 0, h - self->box.h);
}
void land_widget_retain_mouse_focus(LandWidget * self) {
    /* Called inside mouse_leave, will keep the mouse focus, and no other widget
     * can get highlighted.
     */
    self->got_mouse = 1;
}
void land_widget_refuse_mouse_focus(LandWidget * self) {
    /* Called inside mouse_enter, inhibits highlighting of the widget.
     */
    self->got_mouse = 0;
}
void land_widget_request_keyboard_focus(LandWidget * self) {
    /* Called in mouse_tick (or elsewhere), will cause the widget to receive the
     * keyboard focus.
     */
    LandWidget * w = self;
    while (w) {
        w->want_focus = 1;
        w = w->parent;
    }
}
void land_widget_retain_keyboard_focus(LandWidget * self) {
    /* Called in keyboard_leave to keep the focus. Doesn't usually make sense.
     */
    self->got_keyboard = 1;
}
void land_widget_tick(LandWidget * self) {
    /* Call this regularly on your desktop widget. It's the base function which is
     * needed in any widgets application to handle input to the widgets. The other
     * important function is land_widget_draw, which handles display of widgets.
     */
    land_call_method(self, tick, (self));
}
void land_widget_draw(LandWidget * self) {
    /* Draw a widget on its current position. Call this on your desktop widget to
     * display all of your widgets. This function and land_widget_tick are the two
     * functions you should call on your desktop widget in each widgets using
     * application.
     */
    if (self->hidden) {
        return ;
    }
    int pop = 0;
    if (! self->dont_clip) {
        land_clip_push();
        land_clip_on();
        land_clip_intersect(self->box.x, self->box.y, self->box.x + self->box.w, self->box.y + self->box.h);
        pop = 1;
    }
    land_call_method(self, draw, (self));
    if (pop) {
        land_clip_pop();
    }
}
void land_widget_hide(LandWidget * self) {
    /* Hide the widget. It will not be displayed anymore, and also not take up any
     * more space.
     */
    if (self->hidden) {
        return ;
    }
    self->hidden = 1;
    self->box.flags |= GUL_HIDDEN;
    if (self->parent) {
        land_widget_layout(self->parent);
    }
}
void land_widget_unhide(LandWidget * self) {
    /* Unhide the widget.
     */
    if (! self->hidden) {
        return ;
    }
    self->hidden = 0;
    self->box.flags &= ~ GUL_HIDDEN;
    if (self->parent) {
        land_widget_layout(self->parent);
    }
}
void land_widget_set_hidden(LandWidget * self, bool hidden) {
    if (self->hidden == hidden) {
        return ;
    }
    self->hidden = hidden;
    self->box.flags ^= GUL_HIDDEN;
    if (self->parent) {
        land_widget_layout(self->parent);
    }
}
bool land_widget_is_hidden(LandWidget * self) {
    return self->hidden;
}
void land_widget_outer(LandWidget * self, float * x, float * y, float * w, float * h) {
    * x = self->box.x;
    * y = self->box.y;
    * w = self->box.w;
    * h = self->box.h;
}
void land_widget_inner(LandWidget * self, float * x, float * y, float * w, float * h) {
    * x = self->box.x;
    * y = self->box.y;
    * w = self->box.w;
    * h = self->box.h;
    if (self->element) {
        * x += self->element->il;
        * y += self->element->it;
        * w -= self->element->ir + self->element->ir;
        * h -= self->element->ib + self->element->ib;
    }
}
void land_widget_inner_extents(LandWidget * self, float * l, float * t, float * r, float * b) {
    * l = self->box.x;
    * t = self->box.y;
    * r = self->box.x + self->box.w;
    * b = self->box.y + self->box.h;
    if (self->element) {
        * l += self->element->il;
        * t += self->element->it;
        * r -= self->element->ir;
        * b -= self->element->ib;
    }
}
void land_widget_get_inner_size(LandWidget * self, float * w, float * h) {
    * w = self->box.w;
    * h = self->box.h;
    if (self->element) {
        * w -= self->element->il;
        * h -= self->element->it;
        * w -= self->element->ir;
        * h -= self->element->ib;
    }
}
void land_widget_base_interface_initialize(void) {
    if (land_widget_base_interface) {
        return ;
    }
    land_alloc(land_widget_base_interface);
    land_widget_interface_register(land_widget_base_interface);
    land_widget_base_interface->id = LAND_WIDGET_ID_BASE;
    land_widget_base_interface->name = land_strdup("base");
    land_widget_base_interface->size = land_widget_base_size;
    land_widget_base_interface->destroy = land_widget_base_destroy;
}
void land_widget_debug(LandWidget * w, int indentation) {
    for (int i = 0; i < indentation; i += 1) {
        printf("  ");
    }
    printf("%s %d %d %d %d %s\n", land_widget_info_string(w), w->box.x, w->box.y, w->box.w, w->box.h, land_widget_flags_string(w));
    if (land_widget_is(w, LAND_WIDGET_ID_CONTAINER)) {
        LandWidgetContainer * c = LAND_WIDGET_CONTAINER(w);
        if (c->children) {
            {
                LandListIterator __iter0__ = LandListIterator_first(c->children);
                for (LandWidget * child = LandListIterator_item(c->children, &__iter0__); LandListIterator_next(c->children, &__iter0__); child = LandListIterator_item(c->children, &__iter0__)) {
                    land_widget_debug(child, indentation + 1);
                }
            }
        }
    }
}
void land_widget_keyboard_leave(LandWidget * self) {
    land_call_method(self, keyboard_leave, (self));
}
void land_widget_keyboard_focus(LandWidget * self) {
    land_widget_request_keyboard_focus(self);
}
static char _blah [100];
str land_widget_flags_string(LandWidget * w) {
    strcpy(_blah, "");
    if (w->box.flags & GUL_SHRINK_X) {
        strcat(_blah, " xsh");
    }
    if (w->box.flags & GUL_SHRINK_Y) {
        strcat(_blah, " ysh");
    }
    if (w->box.flags & GUL_HIDDEN) {
        strcat(_blah, " hid");
    }
    if (w->box.flags & GUL_NO_LAYOUT) {
        strcat(_blah, " nol");
    }
    if (w->box.flags & GUL_STEADFAST) {
        strcat(_blah, " stf");
    }
    if (w->got_keyboard) {
        strcat(_blah, " K");
    }
    if (w->got_mouse) {
        strcat(_blah, " M");
    }
    if (w->want_focus) {
        strcat(_blah, " F!");
    }
    return _blah;
}
void land_bind_vbo(GLuint * vbo, void * data, int n, int draw) {
    if (* vbo) {
        glBindBuffer(GL_ARRAY_BUFFER, * vbo);
        if (glGetError() == GL_INVALID_OPERATION) {
            * vbo = 0;
        }
    }
    if (! (* vbo)) {
        glGenBuffers(1, vbo);
        glBindBuffer(GL_ARRAY_BUFFER, * vbo);
        glBufferData(GL_ARRAY_BUFFER, n, data, draw);
    }
}
void land_unbind_vbo(GLuint vbo) {
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}
char const* land_opengl_error(void) {
    GLenum e = glGetError();
    if (e == GL_NO_ERROR) {
        return "GL_NO_ERROR";
    }
    if (e == GL_INVALID_ENUM) {
        return "GL_INVALID_ENUM";
    }
    if (e == GL_INVALID_VALUE) {
        return "GL_INVALID_VALUE";
    }
    if (e == GL_INVALID_OPERATION) {
        return "GL_INVALID_OPERATION";
    }
    if (e == GL_INVALID_FRAMEBUFFER_OPERATION) {
        return "GL_INVALID_FRAMEBUFFER_OPERATION";
    }
    if (e == GL_OUT_OF_MEMORY) {
        return "GL_OUT_OF_MEMORY";
    }
    return "unknown";
}
void land_4x4_matrix_to_gl_float(Land4x4Matrix m, GLfloat * gl) {
    gl [0x0] = m.v [0x0];
    gl [0x1] = m.v [0x4];
    gl [0x2] = m.v [0x8];
    gl [0x3] = m.v [0xc];
    gl [0x4] = m.v [0x1];
    gl [0x5] = m.v [0x5];
    gl [0x6] = m.v [0x9];
    gl [0x7] = m.v [0xd];
    gl [0x8] = m.v [0x2];
    gl [0x9] = m.v [0x6];
    gl [0xa] = m.v [0xa];
    gl [0xb] = m.v [0xe];
    gl [0xc] = m.v [0x3];
    gl [0xd] = m.v [0x7];
    gl [0xe] = m.v [0xb];
    gl [0xf] = m.v [0xf];
}
    /* # About world coordinates
     * There is no one best world coordinate system.
     * Classic 2D APIs usually use the x axis to go right and the y axis to go
     * down. This is the "natural" way the English writing system works and
     * also how video hardware internally used to represent pixel positions.
     * 0----x
     * |
     * |
     * y
     * Mathematicians when plotting a graph usually put the origin in the lower
     * left corner instead, so the x axis still goes right but the y axis now
     * goes up.
     * y
     * |
     * |
     * 0----x
     * A common 3D coordinate system uses x to point right and y to point up
     * (like in a math plot). The z axis then goes either into the screen or
     * towards the viewer. (The into the screen version is sometimes called
     * "left-handed", since the first three fingers of your left hand
     * at right angles would now represent the x, y, z axes - and the towards
     * the viewer one "right-handed").
     * left-handed    right-handed
     * y              y
     * | z            |
     * |/             |
     * 0----x         0----x
     * /
     * z
     * The coordinate system used by Land evolved from games that usually are
     * backed by a 2D map. This map uses x/y coordinates. When changing from
     * map coordinates to 3D world coordinates it was less error-prone to let
     * the x/y from the underlying terrain map keep their meaning. So a point
     * at x=123/y=456 on the map would be x=123/y=456/z=0 in 3D, at sea level.
     * So that means x still points to the right as always - but y now runs
     * along the ground (north/south) and z goes up.
     * z
     * | y
     * |/
     * 0----x
     * Now, in the end this will not really matter except for some functions
     * which implicitly have some kind of "up" direcion, for example
     * land_camera_get_yaw.
     * # About camera coordinates
     * Also note that the world space and camera space are different things.
     * While the world axes define what a coordinate means, the three axes of
     * the camera (defined in that world coordinate system) are interpreted
     * relative to the viewer:
     * world space    | camera space
     * x-axis  east ("right") | right
     * y-axis  north ("up")   | up
     * z-axis  up             | towards viewer
     * So while x/y coordinates are now less confusing betwen 2D and 3D for
     * a top-down-view map, we introduce some confusion about what is up. The
     * vector 0/0/1 points straight up. So if the z-axis of a LandCamera is
     * 0/0/1 they look straight down. If the z-axis is 0/0/-1 they look
     * straight up. If the y-axis of LandCamera is 0/0/1 then the camera up
     * aligns with world up and we look straight ahead.
     */
char* land_camera_to_string(LandCamera * self) {
    /* Free the returned string with land_free!
     */
    LandBuffer * b = land_buffer_new();
    LandVector * p = & self->p;
    LandVector * x = & self->x;
    LandVector * y = & self->y;
    LandVector * z = & self->z;
    land_buffer_addf(b, "%.3f %.3f %.3f ", p->x, p->y, p->z);
    land_buffer_addf(b, "%.3f %.3f %.3f ", x->x, x->y, x->z);
    land_buffer_addf(b, "%.3f %.3f %.3f ", y->x, y->y, y->z);
    land_buffer_addf(b, "%.3f %.3f %.3f ", z->x, z->y, z->z);
    land_buffer_addf(b, "%.3f", self->zoom);
    return land_buffer_finish(b);
}
void land_camera_from_string(LandCamera * self, char const * s) {
    LandVector * p = & self->p;
    LandVector * x = & self->x;
    LandVector * y = & self->y;
    LandVector * z = & self->z;
    sscanf(s, "%lg %lg %lg ""%lg %lg %lg ""%lg %lg %lg ""%lg %lg %lg""%lg", & p->x, & p->y, & p->z, & x->x, & x->y, & x->z, & y->x, & y->y, & y->z, & z->x, & z->y, & z->z, & self->zoom);
}
LandCamera* land_camera_new(void) {
    LandCamera * self;
    land_alloc(self);
    land_camera_init(self);
    return self;
}
void land_camera_init(LandCamera * c) {
    memset(c, 0, sizeof (* c));
    c->x.x = 1;
    c->y.y = 1;
    c->z.z = 1;
}
LandCamera land_camera_identity(void) {
    LandCamera c;
    land_camera_init(& c);
    return c;
}
void land_camera_change_freely(LandCamera * self, float x, float y, float z) {
    /* Rotate the camera orientation by the given angles around its local
     * x, y and z axes.
     */
    LandVector xaxis = self->x;
    self->x = land_vector_rotate(self->x, xaxis, x);
    self->y = land_vector_rotate(self->y, xaxis, x);
    self->z = land_vector_rotate(self->z, xaxis, x);
    LandVector yaxis = self->y;
    self->x = land_vector_rotate(self->x, yaxis, y);
    self->y = land_vector_rotate(self->y, yaxis, y);
    self->z = land_vector_rotate(self->z, yaxis, y);
    LandVector zaxis = self->z;
    self->x = land_vector_rotate(self->x, zaxis, z);
    self->y = land_vector_rotate(self->y, zaxis, z);
    self->z = land_vector_rotate(self->z, zaxis, z);
}
void land_camera_change_locked(LandCamera * self, float x, float z) {
    /* Rotate the camera around its local x axis by the given x angle and
     * around world z by the given z angle.
     */
    LandVector xaxis = self->x;
    self->x = land_vector_rotate(self->x, xaxis, x);
    self->y = land_vector_rotate(self->y, xaxis, x);
    self->z = land_vector_rotate(self->z, xaxis, x);
    LandVector zaxis = land_vector(0, 0, 1);
    self->x = land_vector_rotate(self->x, zaxis, z);
    self->y = land_vector_rotate(self->y, zaxis, z);
    self->z = land_vector_rotate(self->z, zaxis, z);
}
void land_camera_change_locked_constrained(LandCamera * self, float x, float z, float min_x, float max_x) {
    /* Like land_camera_change_locked but limit the maximum x angle
     * against the global z axis.
     */
    LandVector zaxis = land_vector(0, 0, 1);
    double angle = land_camera_get_pitch(self);
    if (angle + x < min_x) {
        x = min_x - angle;
    }
    if (angle + x > max_x) {
        x = max_x - angle;
    }
    LandVector xaxis = self->x;
    self->x = land_vector_rotate(self->x, xaxis, x);
    self->y = land_vector_rotate(self->y, xaxis, x);
    self->z = land_vector_rotate(self->z, xaxis, x);
    self->x = land_vector_rotate(self->x, zaxis, z);
    self->y = land_vector_rotate(self->y, zaxis, z);
    self->z = land_vector_rotate(self->z, zaxis, z);
}
LandFloat land_camera_get_pitch(LandCamera * self) {
    /* Get "how much the camera points up", any roll or yaw is ignored.
     * Z
     * \z.z|     y
     * \  |    /
     * \ |  /  y.z
     * \|/________Y
     * X
     */
    return - atan2(self->y.z, self->z.z);
}
LandFloat land_camera_get_roll(LandCamera * self) {
    /* get rotation around y (back)
     * X
     * |
     * |    x  z.x
     * |  / .
     * |/___.____Z
     * Y
     */
    return - atan2(self->z.x, self->z.z);
}
LandFloat land_camera_get_yaw(LandCamera * self) {
    /* Get rotation around world 0/0/1 (up) - we ignore any roll completey,
     * our yaw is derived from just the back (z) vector.
     * Y
     * |   y
     * |  /
     * | /
     * |/________X
     * Z
     * A return of 0 means looking along 0/1/0. A return of pi/2 means
     * looking along 1/0/0.
     */
    return atan2(self->z.x, self->z.y);
}
LandFloat land_camera_get_up_down(LandCamera * self) {
    /* Get rotation around world 0/1/0 (north).
     */
    return atan2(self->y.x, self->y.y);
}
void land_camera_set_yaw(LandCamera * self, LandFloat a) {
    self->x = land_vector(cos(- a), sin(- a), 0);
    self->z = land_vector(0, 0, 1);
    self->y = land_vector_cross(self->z, self->x);
}
void land_camera_translate(LandCamera * self, LandVector v) {
    self->p = land_vector_add(self->p, v);
}
void land_camera_move(LandCamera * self, float x, float y, float z) {
    land_vector_iadd(& self->p, land_vector_mul(self->x, x));
    LandVector up = land_vector(0, 0, 1);
    LandVector back = land_vector_cross(self->x, up);
    back = land_vector_normalize(back);
    land_vector_iadd(& self->p, land_vector_mul(back, - y));
    land_vector_iadd(& self->p, land_vector_mul(up, z));
}
void land_camera_shift(LandCamera * self, float x, float y) {
    LandVector xv = land_vector_normalize(land_vector(self->x.x, self->x.y, 0));
    LandVector yv = land_vector_normalize(land_vector(self->y.x, self->y.y, 0));
    yv = land_vector_mul(yv, 1 + sin(fabs(self->y.z)));
    land_vector_iadd(& self->p, land_vector_mul(xv, x));
    land_vector_iadd(& self->p, land_vector_mul(yv, y));
}
void land_camera_look_into(LandCamera * self, float dx, float dy) {
    /* Change the camera to look into the given dx/dy direction, with
     * z going straight up.
     */
    double d = sqrt(dx * dx + dy * dy);
    if (d < 0.0001) {
        return ;
    }
    self->y = land_vector(dx / (- d), dy / (- d), 0);
    self->z = land_vector(0, 0, 1);
    self->x = land_vector_cross(self->y, self->z);
}
void land_camera_warp(LandCamera * self, float x, float y, float z) {
    self->p.x = x;
    self->p.y = y;
    self->p.z = z;
}
Land4x4Matrix land_camera_matrix(LandCamera * camera) {
    Land4x4Matrix m = land_4x4_matrix_inverse_from_vectors(& camera->p, & camera->x, & camera->y, & camera->z);
    LandFloat s = pow(2, camera->zoom);
    for (int i = 0; i < 8; i += 1) {
        m.v [i] *= s;
    }
    return m;
}
LandFloat land_camera_get_scale(LandCamera * camera) {
    return pow(2, camera->zoom);
}
void land_camera_scale(LandCamera * camera, float scale) {
    camera->zoom = log2(scale);
}
Land4x4Matrix land_camera_forward_matrix(LandCamera * camera) {
    Land4x4Matrix m = land_4x4_matrix_from_vectors(& camera->p, & camera->x, & camera->y, & camera->z);
    return m;
}
Land4x4Matrix land_camera_forward_matrix_replace_position(LandCamera * camera, LandVector * pos) {
    Land4x4Matrix m = land_4x4_matrix_from_vectors(pos, & camera->x, & camera->y, & camera->z);
    return m;
}
void land_camera_init_from_matrix(LandCamera * camera, Land4x4Matrix matrix) {
    LandFloat * v = matrix.v;
    camera->x = land_vector_normalize(land_vector(v [0], v [1], v [2]));
    camera->y = land_vector_normalize(land_vector(v [4], v [5], v [6]));
    camera->z = land_vector_normalize(land_vector(v [8], v [9], v [10]));
    camera->p = land_vector(v [3], v [7], v [11]);
}
LandVector land_camera_get_on_screen_position(LandCamera * cam, LandVector pos) {
    LandFloat s = pow(2, cam->zoom);
    LandVector t = land_vector_transform(pos, cam->p, cam->x, cam->y, cam->z);
    return land_vector(t.x * s, t.y * s, 0);
}
static void _test(str name, LandCamera cam, double result, double expected) {
    double epsilon = 0.000001;
    double differ = fabs(result - expected);
    if (differ < epsilon) {
        print("OK %s", name);
    }
    else {
        print("FAIL %s", name);
        print("expected %f but got %f", expected, result);
        print("p: % .3f % .3f % .3f", cam.p.x, cam.p.y, cam.p.z);
        print("x: % .3f % .3f % .3f", cam.x.x, cam.x.y, cam.x.z);
        print("y: % .3f % .3f % .3f", cam.y.x, cam.y.y, cam.y.z);
        print("z: % .3f % .3f % .3f", cam.z.x, cam.z.y, cam.z.z);
    }
}
void land_camera_tests(void) {
    LandCamera cam;
    cam = land_camera_identity();
    _test("pitch", cam, land_camera_get_pitch(& cam), 0);
    land_camera_change_freely(& cam, pi / 2, 0, 0);
    _test("pitch2", cam, land_camera_get_pitch(& cam), pi / 2);
    land_camera_change_freely(& cam, pi, 0, 0);
    _test("pitch3", cam, land_camera_get_pitch(& cam), - pi / 2);
    cam = land_camera_identity();
    land_camera_change_freely(& cam, pi * 3 / 4, 0, 0);
    _test("pitch4", cam, land_camera_get_pitch(& cam), pi * 3 / 4);
    cam = land_camera_identity();
    _test("roll", cam, land_camera_get_roll(& cam), 0);
    land_camera_change_freely(& cam, 0, pi / 2, 0);
    _test("roll2", cam, land_camera_get_roll(& cam), pi / 2);
    land_camera_change_freely(& cam, 0, pi, 0);
    _test("roll3", cam, land_camera_get_roll(& cam), - pi / 2);
    land_camera_change_freely(& cam, 0, - pi / 4, 0);
    _test("roll4", cam, land_camera_get_roll(& cam), - pi * 3 / 4);
    cam = land_camera_identity();
    _test("yaw0", cam, land_camera_get_yaw(& cam), 0);
    for (int i = 0; i < 8; i += 1) {
        char yaw [10];
        sprintf(yaw, "yaw%d", 1 + i);
        float exp = 2 * pi * i / 8 - pi;
        if (exp < - pi) {
            exp += 2 * pi;
        }
        cam = land_camera_identity();
        land_camera_change_freely(& cam, - pi / 2, i * 2 * pi / 8, 0);
        _test(yaw, cam, land_camera_get_yaw(& cam), exp);
    }
}
void platform_display_init(void) {
    ;
}
void platform_display_exit(void) {
    ;
}
LandDisplay* platform_display_new(void) {
    LandDisplayPlatform * self;
    land_alloc(self);
    return & self->super;
}
void platform_display_del(LandDisplay * super) {
    LandDisplayPlatform * self = (void *) super;
    al_destroy_display(self->a5);
}
#define SELF \
    LandDisplayPlatform * self = (void *) _land_active_display; \
    LandDisplay * super = & self->super; \
    (void) super;
void platform_display_desktop_size(int * w, int * h) {
    ALLEGRO_MONITOR_INFO info;
    al_get_monitor_info(0, & info);
    * w = info.x2 - info.x1;
    * h = info.y2 - info.y1;
}
void platform_display_title(char const * title) {
    SELF;
    al_set_window_title(self->a5, title);
}
void platform_display_icon(LandImage * icon) {
    SELF;
    al_set_display_icon(self->a5, ((LandImagePlatform *) icon)->a5);
}
void platform_display_move(int x, int y) {
    SELF;
    al_set_window_position(self->a5, x, y);
}
void platform_display_resize(int w, int h) {
    SELF;
    al_resize_display(self->a5, w, h);
}
void land_a5_display_check_transform(void) {
    LandDisplayPlatform * self = (void *) _land_active_display;
    LandDisplay * super = & self->super;
    if (super->matrix_modified) {
        self->transform.m [0] [0] = super->matrix.v [0];
        self->transform.m [1] [0] = super->matrix.v [1];
        self->transform.m [2] [0] = super->matrix.v [2];
        self->transform.m [3] [0] = super->matrix.v [3];
        self->transform.m [0] [1] = super->matrix.v [4];
        self->transform.m [1] [1] = super->matrix.v [5];
        self->transform.m [2] [1] = super->matrix.v [6];
        self->transform.m [3] [1] = super->matrix.v [7];
        self->transform.m [0] [2] = super->matrix.v [8];
        self->transform.m [1] [2] = super->matrix.v [9];
        self->transform.m [2] [2] = super->matrix.v [10];
        self->transform.m [3] [2] = super->matrix.v [11];
        self->transform.m [0] [3] = super->matrix.v [12];
        self->transform.m [1] [3] = super->matrix.v [13];
        self->transform.m [2] [3] = super->matrix.v [14];
        self->transform.m [3] [3] = super->matrix.v [15];
        al_use_transform(& self->transform);
        super->matrix_modified = 0;
    }
}
void platform_check_blending_and_transform(void) {
    SELF;
    land_a5_display_check_transform();
    if (super->blend) {
        al_store_state(& self->blend_state, ALLEGRO_STATE_BLENDER);
        if (super->blend & LAND_BLEND_SOLID) {
            al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ZERO);
        }
        else if (super->blend & LAND_BLEND_ADD) {
            al_set_blender(ALLEGRO_ADD, ALLEGRO_ALPHA, ALLEGRO_ONE);
        }
    }
}
void platform_uncheck_blending(void) {
    SELF;
    if (super->blend) {
        al_restore_state(& self->blend_state);
    }
}
void check_blending_and_transform(void) {
    platform_check_blending_and_transform();
}
void uncheck_blending(void) {
    platform_uncheck_blending();
}
void platform_display_set(void) {
    SELF;
    int f = 0;
    if (self->a5) {
        if (super->flags & LAND_FULLSCREEN) {
            al_set_display_flag(self->a5, ALLEGRO_FULLSCREEN_WINDOW, 1);
        }
        else {
            al_set_display_flag(self->a5, ALLEGRO_FULLSCREEN_WINDOW, 0);
        }
        super->w = al_get_display_width(self->a5);
        super->h = al_get_display_height(self->a5);
        land_resize_event(super->w, super->h);
        return ;
    }
    if (super->flags & LAND_FULLSCREEN) {
        f |= ALLEGRO_FULLSCREEN_WINDOW;
    }
    if (super->flags & LAND_RESIZE) {
        f |= ALLEGRO_RESIZABLE;
    }
    if (super->flags & LAND_OPENGL) {
        f |= ALLEGRO_OPENGL | ALLEGRO_PROGRAMMABLE_PIPELINE;
    }
    if (super->flags & LAND_MULTISAMPLE) {
        al_set_new_display_option(ALLEGRO_SAMPLE_BUFFERS, 1, ALLEGRO_SUGGEST);
        al_set_new_display_option(ALLEGRO_SAMPLES, 4, ALLEGRO_SUGGEST);
    }
    if (super->flags & LAND_DEPTH) {
        al_set_new_display_option(ALLEGRO_DEPTH_SIZE, 16, ALLEGRO_SUGGEST);
    }
    if (super->flags & LAND_LANDSCAPE) {
        al_set_new_display_option(ALLEGRO_SUPPORTED_ORIENTATIONS, ALLEGRO_DISPLAY_ORIENTATION_LANDSCAPE, ALLEGRO_SUGGEST);
    }
    if (super->flags & LAND_FRAMELESS) {
        f |= ALLEGRO_FRAMELESS;
    }
    if (super->flags & LAND_ANTIALIAS) {
        al_set_new_bitmap_flags(ALLEGRO_MAG_LINEAR | ALLEGRO_MIN_LINEAR);
    }
    ALLEGRO_MONITOR_INFO info;
    al_get_monitor_info(0, & info);
    land_log_message("Monitor resolution: %d %d %d %d\n", info.x1, info.y1, info.x2, info.y2);
    int monw = info.x2 - info.x1;
    int monh = info.y2 - info.y1;
    if (super->flags & LAND_RESIZE) {
        if (super->w > monw) {
            super->w = monw;
        }
        if (super->h > monh) {
            super->h = monh;
        }
    }
    if (super->w == monw && super->h == monh) {
        f |= ALLEGRO_FULLSCREEN_WINDOW;
    }
    if (super->w == 0) {
        super->w = monw;
        super->clip_x2 = super->w;
    }
    if (super->h == 0) {
        super->h = monh;
        super->clip_y2 = super->h;
    }
    #ifdef ANDROID
    f |= ALLEGRO_OPENGL | ALLEGRO_PROGRAMMABLE_PIPELINE;
    #endif
    if (f) {
        al_set_new_display_flags(f);
    }
    land_log_message("Calling al_create_display(%d, %d).\n", super->w, super->h);
    self->a5 = al_create_display(super->w, super->h);
    if (self->a5) {
        land_log_message("    Success!\n");
    }
    else {
        land_log_message("    Failed activating Allegro display.\n");
    }
    if (super->flags & LAND_FULLSCREEN) {
        super->w = al_get_display_width(self->a5);
        super->h = al_get_display_height(self->a5);
        super->clip_x2 = super->w;
        super->clip_y2 = super->h;
        land_log_message("Using actual size of %dx%d.\n", super->w, super->h);
    }
    if (f & ALLEGRO_PROGRAMMABLE_PIPELINE) {
        land_display_set_default_shaders();
        land_render_state(LAND_ALPHA_TEST, 0);
    }
}
void platform_display_color(void) {
    SELF;
    self->c = al_map_rgba_f(super->color_r, super->color_g, super->color_b, super->color_a);
}
void platform_display_clip(void) {
    SELF;
    al_set_clipping_rectangle(super->clip_x1, super->clip_y1, super->clip_x2 - super->clip_x1, super->clip_y2 - super->clip_y1);
}
void platform_display_clear(LandDisplay * self, float r, float g, float b, float a) {
    al_clear_to_color(al_map_rgba_f(r, g, b, a));
}
void platform_display_clear_depth(LandDisplay * self, float z) {
    al_clear_depth_buffer(z);
}
void platform_display_flip(void) {
    al_flip_display();
}
void platform_rectangle(float x, float y, float x_, float y_) {
    SELF;
    check_blending_and_transform();
    al_draw_rectangle(x, y, x_, y_, self->c, self->super.thickness);
    uncheck_blending();
}
void platform_filled_rectangle(float x, float y, float x_, float y_) {
    SELF;
    check_blending_and_transform();
    al_draw_filled_rectangle(x, y, x_, y_, self->c);
    uncheck_blending();
}
void platform_filled_circle(float x, float y, float x_, float y_) {
    SELF;
    float cx = (x + x_) * 0.5;
    float cy = (y + y_) * 0.5;
    float rx = (x_ - x) * 0.5;
    float ry = (y_ - y) * 0.5;
    check_blending_and_transform();
    al_draw_filled_ellipse(cx, cy, rx, ry, self->c);
    uncheck_blending();
}
void platform_circle(float x, float y, float x_, float y_) {
    SELF;
    float cx = (x + x_) * 0.5;
    float cy = (y + y_) * 0.5;
    float rx = (x_ - x) * 0.5;
    float ry = (y_ - y) * 0.5;
    check_blending_and_transform();
    al_draw_ellipse(cx, cy, rx, ry, self->c, self->super.thickness);
    uncheck_blending();
}
void platform_arc(float x, float y, float x_, float y_, float a, float a_) {
    SELF;
    float cx = (x + x_) * 0.5;
    float cy = (y + y_) * 0.5;
    float rx = (x_ - x) * 0.5;
    check_blending_and_transform();
    al_draw_arc(cx, cy, rx, a, (a_ - a), self->c, self->super.thickness);
    uncheck_blending();
}
void platform_ribbon(int n, float * xy) {
    SELF;
    check_blending_and_transform();
    for (int i = 0; i < n - 1; i++) {
        float xy8 [8];
        xy8 [2] = xy [i * 2 + 0];
        xy8 [3] = xy [i * 2 + 1];
        xy8 [0] = xy [i * 2 + 0];
        xy8 [1] = xy [i * 2 + 1];
        xy8 [6] = xy [i * 2 + 2];
        xy8 [7] = xy [i * 2 + 3];
        xy8 [4] = xy [i * 2 + 2];
        xy8 [5] = xy [i * 2 + 3];
        float ex = xy8 [0] - xy8 [6];
        float ey = xy8 [1] - xy8 [7];
        float e = sqrt(ex * ex + ey * ey);
        e *= 0.33;
        if (i > 0) {
            float dx = xy [i * 2 + 2] - xy [(i - 1) * 2 + 0];
            float dy = xy [i * 2 + 3] - xy [(i - 1) * 2 + 1];
            float d = sqrt(dx * dx + dy * dy);
            xy8 [2] += e * dx / d;
            xy8 [3] += e * dy / d;
        }
        if (i < n - 2) {
            float dx = xy [i * 2 + 0] - xy [i * 2 + 4];
            float dy = xy [i * 2 + 1] - xy [i * 2 + 5];
            float d = sqrt(dx * dx + dy * dy);
            xy8 [4] += e * dx / d;
            xy8 [5] += e * dy / d;
        }
        al_draw_spline(xy8, self->c, self->super.thickness);
    }
    uncheck_blending();
}
void platform_ribbon_loop(int n, float * xy) {
    SELF;
    check_blending_and_transform();
    for (int i = 0; i < n; i++) {
        float xy8 [8];
        xy8 [2] = xy [i * 2 + 0];
        xy8 [3] = xy [i * 2 + 1];
        int i_n = (i + 1) % n;
        xy8 [0] = xy [i * 2 + 0];
        xy8 [1] = xy [i * 2 + 1];
        xy8 [6] = xy [i_n * 2 + 0];
        xy8 [7] = xy [i_n * 2 + 1];
        xy8 [4] = xy [i_n * 2 + 0];
        xy8 [5] = xy [i_n * 2 + 1];
        float ex = xy8 [0] - xy8 [6];
        float ey = xy8 [1] - xy8 [7];
        float e = sqrt(ex * ex + ey * ey);
        e *= 0.33;
        if (true) {
            int i_p = (i + n - 1) % n;
            float dx = xy [i_n * 2 + 0] - xy [i_p * 2 + 0];
            float dy = xy [i_n * 2 + 1] - xy [i_p * 2 + 1];
            float d = sqrt(dx * dx + dy * dy);
            xy8 [2] += e * dx / d;
            xy8 [3] += e * dy / d;
        }
        if (true) {
            int i_nn = (i_n + 1) % n;
            float dx = xy [i * 2 + 0] - xy [i_nn * 2 + 0];
            float dy = xy [i * 2 + 1] - xy [i_nn * 2 + 1];
            float d = sqrt(dx * dx + dy * dy);
            xy8 [4] += e * dx / d;
            xy8 [5] += e * dy / d;
        }
        al_draw_spline(xy8, self->c, self->super.thickness);
    }
    uncheck_blending();
}
void platform_filled_ribbon(int n, float * xy) {
    SELF;
    check_blending_and_transform();
    int q = 8;
    int points = n * (q - 1);
    float v [points * 2];
    for (int i = 0; i < n; i++) {
        float xy8 [8];
        xy8 [2] = xy [i * 2 + 0];
        xy8 [3] = xy [i * 2 + 1];
        int i_n = (i + 1) % n;
        xy8 [0] = xy [i * 2 + 0];
        xy8 [1] = xy [i * 2 + 1];
        xy8 [6] = xy [i_n * 2 + 0];
        xy8 [7] = xy [i_n * 2 + 1];
        xy8 [4] = xy [i_n * 2 + 0];
        xy8 [5] = xy [i_n * 2 + 1];
        float ex = xy8 [0] - xy8 [6];
        float ey = xy8 [1] - xy8 [7];
        float e = sqrt(ex * ex + ey * ey);
        e *= 0.33;
        if (true) {
            int i_p = (i + n - 1) % n;
            float dx = xy [i_n * 2 + 0] - xy [i_p * 2 + 0];
            float dy = xy [i_n * 2 + 1] - xy [i_p * 2 + 1];
            float d = sqrt(dx * dx + dy * dy);
            xy8 [2] += e * dx / d;
            xy8 [3] += e * dy / d;
        }
        if (true) {
            int i_nn = (i_n + 1) % n;
            float dx = xy [i * 2 + 0] - xy [i_nn * 2 + 0];
            float dy = xy [i * 2 + 1] - xy [i_nn * 2 + 1];
            float d = sqrt(dx * dx + dy * dy);
            xy8 [4] += e * dx / d;
            xy8 [5] += e * dy / d;
        }
        al_calculate_spline(v + (i * (q - 1)) * 2, 2 * sizeof (float), xy8, 0, q);
    }
    int holes [2] = {points, 0};
    platform_filled_polygon_with_holes(points, v, holes);
    uncheck_blending();
}
void platform_line(float x, float y, float x_, float y_) {
    SELF;
    check_blending_and_transform();
    al_draw_line(x, y, x_, y_, self->c, self->super.thickness);
    uncheck_blending();
}
void platform_polygon(int n, float * xy) {
    SELF;
    ALLEGRO_VERTEX v [n];
    memset(v, 0, n * sizeof (ALLEGRO_VERTEX));
    int j = 0;
    for (int i = 0; i < n; i++) {
        v [i].x = xy [j++];
        v [i].y = xy [j++];
        v [i].color = self->c;
    }
    check_blending_and_transform();
    al_draw_prim(v, NULL, NULL, 0, n, ALLEGRO_PRIM_LINE_LOOP);
    uncheck_blending();
}
void platform_filled_polygon(int n, float * xy) {
    SELF;
    ALLEGRO_VERTEX v [n];
    memset(v, 0, n * sizeof (ALLEGRO_VERTEX));
    int j = 0;
    for (int i = 0; i < n; i++) {
        v [i].x = xy [j++];
        v [i].y = xy [j++];
        v [i].color = self->c;
    }
    check_blending_and_transform();
    al_draw_prim(v, NULL, NULL, 0, n, ALLEGRO_PRIM_TRIANGLE_FAN);
    uncheck_blending();
}
void platform_textured_colored_polygon(LandImage * image, int n, float * xy, float * uv, float * rgba) {
    SELF;
    LandImagePlatform * pim = (void *) image;
    ALLEGRO_VERTEX v [n];
    memset(v, 0, n * sizeof (ALLEGRO_VERTEX));
    int j = 0;
    int k = 0;
    int l = 0;
    for (int i = 0; i < n; i++) {
        v [i].x = xy [j++];
        v [i].y = xy [j++];
        if (uv) {
            v [i].u = uv [k++];
            v [i].v = uv [k++];
        }
        else {
            v [i].u = 0;
            v [i].v = 0;
        }
        if (rgba) {
            v [i].color.r = rgba [l++];
            v [i].color.g = rgba [l++];
            v [i].color.b = rgba [l++];
            v [i].color.a = rgba [l++];
        }
        else {
            v [i].color = self->c;
        }
    }
    check_blending_and_transform();
    al_draw_prim(v, NULL, pim ? pim->a5 : NULL, 0, n, ALLEGRO_PRIM_TRIANGLE_FAN);
    uncheck_blending();
}
void platform_textured_polygon(LandImage * image, int n, float * xy, float * uv) {
    platform_textured_colored_polygon(image, n, xy, uv, NULL);
}
void platform_filled_colored_polygon(int n, float * xy, float * rgba) {
    platform_textured_colored_polygon(NULL, n, xy, NULL, rgba);
}
void platform_filled_polygon_with_holes(int n, float * xy, int * holes) {
    SELF;
    check_blending_and_transform();
    al_draw_filled_polygon_with_holes(xy, holes, self->c);
    uncheck_blending();
}
void platform_3d_triangles(int n, LandFloat * xyzrgb) {
    ALLEGRO_VERTEX v [n];
    for (int i = 0; i < n; i += 1) {
        LandFloat * f = xyzrgb + i * 6;
        v [i].x = f [0];
        v [i].y = f [1];
        v [i].z = f [2];
        v [i].u = 0;
        v [i].v = 0;
        v [i].color.r = f [3];
        v [i].color.g = f [4];
        v [i].color.b = f [5];
        v [i].color.a = 1;
    }
    check_blending_and_transform();
    al_draw_prim(v, NULL, NULL, 0, n, ALLEGRO_PRIM_TRIANGLE_LIST);
    uncheck_blending();
}
void platform_plot(float x, float y) {
    SELF;
    check_blending_and_transform();
    al_draw_pixel(x, y, self->c);
    uncheck_blending();
}
void platform_pick_color(float x, float y) {
    SELF;
    float32_t r, g, b, a;
    self->c = al_get_pixel(al_get_target_bitmap(), x, y);
    al_unmap_rgba_f(self->c, & r, & g, & b, & a);
    super->color_r = r;
    super->color_g = g;
    super->color_b = b;
    super->color_a = a;
}
static int a5state [] = {ALLEGRO_ALPHA_TEST, ALLEGRO_ALPHA_FUNCTION, ALLEGRO_ALPHA_TEST_VALUE, ALLEGRO_WRITE_MASK, ALLEGRO_DEPTH_TEST, ALLEGRO_DEPTH_FUNCTION};
static int a5func [] = {ALLEGRO_RENDER_NEVER, ALLEGRO_RENDER_ALWAYS, ALLEGRO_RENDER_LESS, ALLEGRO_RENDER_EQUAL, ALLEGRO_RENDER_LESS_EQUAL, ALLEGRO_RENDER_GREATER, ALLEGRO_RENDER_NOT_EQUAL, ALLEGRO_RENDER_GREATER_EQUAL};
void platform_render_state(int state, int value) {
    int value2 = value;
    if (state == LAND_ALPHA_FUNCTION || state == LAND_DEPTH_FUNCTION) {
        value2 = a5func [value];
    }
    else if (state == LAND_WRITE_MASK) {
        value2 = 0;
        if (value & LAND_RED_MASK) {
            value2 |= ALLEGRO_MASK_RED;
        }
        if (value & LAND_GREEN_MASK) {
            value2 |= ALLEGRO_MASK_GREEN;
        }
        if (value & LAND_BLUE_MASK) {
            value2 |= ALLEGRO_MASK_BLUE;
        }
        if (value & LAND_ALPHA_MASK) {
            value2 |= ALLEGRO_MASK_ALPHA;
        }
        if (value & LAND_DEPTH_MASK) {
            value2 |= ALLEGRO_MASK_DEPTH;
        }
    }
    al_set_render_state(a5state [state], value2);
}
void platform_set_default_shaders(void) {
    SELF;
    if (! self->default_shader) {
        ALLEGRO_SHADER * shader;
        shader = al_create_shader(ALLEGRO_SHADER_GLSL);
        al_attach_shader_source(shader, ALLEGRO_VERTEX_SHADER, al_get_default_shader_source(ALLEGRO_SHADER_AUTO, ALLEGRO_VERTEX_SHADER));
        al_attach_shader_source(shader, ALLEGRO_PIXEL_SHADER, al_get_default_shader_source(ALLEGRO_SHADER_AUTO, ALLEGRO_PIXEL_SHADER));
        al_build_shader(shader);
        self->default_shader = shader;
    }
    al_use_shader(self->default_shader);
}
void platform_reset_projection(void) {
    ALLEGRO_TRANSFORM trans;
    al_identity_transform(& trans);
    al_orthographic_transform(& trans, 0, 0, - 1.0, land_display_width(), land_display_height(), 1.0);
    al_use_projection_transform(& trans);
}
void platform_projection(Land4x4Matrix m) {
    ALLEGRO_TRANSFORM t;
    t.m [0] [0] = m.v [0];
    t.m [1] [0] = m.v [1];
    t.m [2] [0] = m.v [2];
    t.m [3] [0] = m.v [3];
    t.m [0] [1] = m.v [4];
    t.m [1] [1] = m.v [5];
    t.m [2] [1] = m.v [6];
    t.m [3] [1] = m.v [7];
    t.m [0] [2] = m.v [8];
    t.m [1] [2] = m.v [9];
    t.m [2] [2] = m.v [10];
    t.m [3] [2] = m.v [11];
    t.m [0] [3] = m.v [12];
    t.m [1] [3] = m.v [13];
    t.m [2] [3] = m.v [14];
    t.m [3] [3] = m.v [15];
    al_use_projection_transform(& t);
}
int platform_get_dpi(void) {
    return al_get_monitor_dpi(0);
}
#undef SELF
LandWidgetInterface * land_widget_container_interface;
void land_widget_container_destroy(LandWidget * base) {
    /* """Destroy the container and all its children."""
     */
    LandWidgetContainer * self = LAND_WIDGET_CONTAINER(base);
    if (self->mouse) {
        land_widget_unreference(self->mouse);
    }
    if (self->children) {
        LandListItem * item = self->children->first;
        while (item) {
            LandListItem * next = item->next;
            LandWidget * child = item->data;
            child->parent = NULL;
            land_widget_unreference(child);
            item = next;
        }
        land_list_destroy(self->children);
    }
    land_widget_base_destroy(base);
}
void land_widget_container_mouse_enter(LandWidget * super) {
    LandWidgetContainer * self = LAND_WIDGET_CONTAINER(super);
    LandWidget * child = land_widget_container_get_child_at_pos(super, land_mouse_x(), land_mouse_y());
    if (child) {
        land_widget_reference(child);
        child->got_mouse = 1;
        land_call_method(child, mouse_enter, (child));
        self->mouse = child;
    }
}
void land_widget_container_mouse_leave(LandWidget * super) {
    LandWidgetContainer * self = LAND_WIDGET_CONTAINER(super);
    if (self->mouse) {
        self->mouse->got_mouse = 0;
        land_call_method(self->mouse, mouse_leave, (self->mouse));
        if (self->mouse->got_mouse) {
            super->got_mouse = 1;
        }
        else {
            land_widget_unreference(self->mouse);
            self->mouse = NULL;
        }
    }
}
void land_widget_container_keyboard_enter(LandWidget * super) {
    /* """ Give keyboard focus to the container, and to children who requested
     * focus."""
     */
    LandWidgetContainer * self = LAND_WIDGET_CONTAINER(super);
    if (self->children) {
        LandListItem * item, * next;
        item = self->children->first;
        for (; item; item = next) {
            next = item->next;
            LandWidget * child = item->data;
            if (child->want_focus) {
                child->want_focus = 0;
                child->got_keyboard = 1;
                land_call_method(child, keyboard_enter, (child));
                self->keyboard = child;
                land_widget_reference(self->keyboard);
                break;
            }
        }
    }
}
LandWidget* land_widget_container_get_keyboard_focus(LandWidget * super) {
    LandWidgetContainer * self = LAND_WIDGET_CONTAINER(super);
    return self->keyboard;
}
void land_widget_container_keyboard_leave(LandWidget * super) {
    LandWidgetContainer * self = LAND_WIDGET_CONTAINER(super);
    if (self->keyboard) {
        LandWidget * keyboard = self->keyboard;
        self->keyboard = NULL;
        keyboard->got_keyboard = 0;
        land_call_method(keyboard, keyboard_leave, (keyboard));
        if (keyboard->got_keyboard) {
            super->got_keyboard = 1;
            self->keyboard = keyboard;
            return ;
        }
        super->got_keyboard = 0;
        land_widget_unreference(keyboard);
        if (super->parent) {
            land_widget_keyboard_leave(super->parent);
        }
    }
}
LandListItem* land_widget_container_child_item(LandWidget * super, LandWidget * child) {
    LandWidgetContainer * self = LAND_WIDGET_CONTAINER(super);
    if (! self->children) {
        return NULL;
    }
    LandListItem * item = self->children->first;
    while (item) {
        if (item->data == child) {
            return item;
        }
        item = item->next;
    }
    return NULL;
}
void land_widget_container_to_top(LandWidget * super, LandWidget * child) {
    LandWidgetContainer * self = LAND_WIDGET_CONTAINER(super);
    LandListItem * item = land_widget_container_child_item(super, child);
    land_list_remove_item(self->children, item);
    land_list_insert_item(self->children, item);
}
void land_widget_container_draw(LandWidget * base) {
    LandWidgetContainer * self = LAND_WIDGET_CONTAINER(base);
    land_widget_theme_draw(base);
    if (! self->children) {
        return ;
    }
    if (! base->dont_clip) {
        float l, t, r, b;
        land_widget_inner_extents(base, & l, & t, & r, & b);
        land_clip_push();
        land_clip_intersect(l, t, r, b);
    }
    float cl, ct, cr, cb;
    land_get_clip(& cl, & ct, & cr, & cb);
    LandListItem * item = self->children->first;
    for (; item; item = item->next) {
        LandWidget * child = item->data;
        if (child->hidden) {
            continue;
        }
        if (! base->dont_clip && ! child->no_clip_check) {
            if ((child->box.x <= cr && child->box.x + child->box.w >= cl && child->box.y <= cb && child->box.y + child->box.h >= ct)) {
                land_widget_draw(child);
            }
        }
        else {
            land_widget_draw(child);
        }
    }
    if (! base->dont_clip) {
        land_clip_pop();
    }
}
void land_widget_container_move(LandWidget * super, float dx, float dy) {
    /* Move all children of the container when the container itself is moved.
     */
    LandWidgetContainer * self = LAND_WIDGET_CONTAINER(super);
    if (! self->children) {
        return ;
    }
    LandListItem * item = self->children->first;
    while (item) {
        LandWidget * child = item->data;
        land_widget_move(child, dx, dy);
        item = item->next;
    }
}
void land_widget_container_size(LandWidget * super, float dx, float dy) {
    if (dx || dy) {
        land_widget_layout(super);
    }
}
LandWidget* land_widget_container_get_descendant_at_pos(LandWidget * super, int x, int y) {
    /* Returns a descendant under a specific (absolute) position, or else the
     * widget itself.
     */
    LandWidget * child = land_widget_container_get_child_at_pos(super, x, y);
    if (child) {
        if (land_widget_is(child, LAND_WIDGET_ID_CONTAINER)) {
            return land_widget_container_get_descendant_at_pos(child, x, y);
        }
        return child;
    }
    return super;
}
LandWidget* land_widget_container_get_child_at_pos(LandWidget * super, int x, int y) {
    /* Returns the direct child under a specific (absolute) position.
     */
    LandWidgetContainer * self = LAND_WIDGET_CONTAINER(super);
    if (! self->children) {
        return NULL;
    }
    LandListItem * item = self->children->last;
    for (; item; item = item->prev) {
        LandWidget * child = item->data;
        if (child->hidden) {
            continue;
        }
        if ((x >= child->box.x && y >= child->box.y && x < child->box.x + child->box.w && y < child->box.y + child->box.h)) {
            return child;
        }
    }
    return NULL;
}
static void transfer_mouse_focus(LandWidget * base, LandWidget * child) {
    LandWidgetContainer * self = LAND_WIDGET_CONTAINER(base);
    if (child) {
        land_widget_reference(child);
    }
    if (self->mouse) {
        self->mouse->got_mouse = 0;
        land_call_method(self->mouse, mouse_leave, (self->mouse));
        if (self->mouse->got_mouse) {
            if (child) {
                land_widget_unreference(child);
            }
            return ;
        }
        land_widget_unreference(self->mouse);
        self->mouse = NULL;
    }
    if (child) {
        self->mouse = child;
        land_call_method(self->mouse, mouse_enter, (self->mouse));
    }
}
static void transfer_keyboard_focus(LandWidget * base) {
    base->got_keyboard = 0;
    land_widget_container_keyboard_leave(base);
    if (base->got_keyboard) {
        return ;
    }
    base->want_focus = 0;
    base->got_keyboard = 1;
    land_widget_container_keyboard_enter(base);
}
void land_widget_container_mouse_tick(LandWidget * super) {
    LandWidgetContainer * self = LAND_WIDGET_CONTAINER(super);
    if (self->mouse) {
        land_call_method(self->mouse, mouse_tick, (self->mouse));
    }
    LandWidget * mouse = land_widget_container_get_child_at_pos(super, land_mouse_x(), land_mouse_y());
    if (mouse != self->mouse && ! (land_mouse_b() & 1)) {
        transfer_mouse_focus(super, mouse);
    }
    if (self->children) {
        LandListItem * item, * next, * last;
        item = self->children->first;
        last = self->children->last;
        for (; item; item = next) {
            next = item->next;
            LandWidget * child = item->data;
            if (child->want_focus) {
                super->want_focus = 1;
            }
            if (child->send_to_top) {
                land_widget_container_to_top(super, child);
                child->send_to_top = 0;
            }
            if (item == last) {
                break;
            }
        }
    }
}
void land_widget_container_set_mouse_focus(LandWidget * super, LandWidget * mouse) {
    /* Only suceeds if the currently focused window agrees.
     */
    LandWidgetContainer * self = LAND_WIDGET_CONTAINER(super);
    if (mouse != self->mouse) {
        transfer_mouse_focus(super, mouse);
    }
}
void land_widget_container_keyboard_tick(LandWidget * super) {
    LandWidgetContainer * self = LAND_WIDGET_CONTAINER(super);
    if (super->want_focus) {
        transfer_keyboard_focus(super);
    }
    if (self->keyboard) {
        land_call_method(self->keyboard, keyboard_tick, (self->keyboard));
    }
}
void land_widget_container_tick(LandWidget * super) {
    land_widget_reference(super);
    land_call_method(super, mouse_tick, (super));
    land_call_method(super, keyboard_tick, (super));
    land_widget_unreference(super);
}
void land_widget_container_add(LandWidget * super, LandWidget * add) {
    LandWidgetContainer * self = LAND_WIDGET_CONTAINER(super);
    land_add_list_data(& self->children, add);
    land_widget_reference(add);
    add->parent = super;
}
void land_widget_container_remove(LandWidget * base, LandWidget * rem) {
    assert(rem->parent == base);
    rem->parent = NULL;
    land_remove_list_data(& LAND_WIDGET_CONTAINER (base)->children, rem);
    land_widget_unreference(rem);
}
void land_widget_container_remove_all(LandWidget * base) {
    while (1) {
        LandWidget * child = land_widget_container_child(base);
        if (! child) {
            break;
        }
        land_widget_container_remove(base, child);
    }
}
void land_widget_container_update(LandWidget * widget) {
    /* The update method is called after the add method. We simply defer it to
     * our children.
     */
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(widget);
    if (! container->children) {
        return ;
    }
    LandListItem * item = container->children->first;
    while (item) {
        LandWidget * child = item->data;
        land_call_method(child, update, (child));
        item = item->next;
    }
}
LandWidget* land_widget_container_child(LandWidget * super) {
    /* Return the first child of the container or None.
     */
    LandWidgetContainer * self = (LandWidgetContainer *) super;
    LandList * l = self->children;
    if (l) {
        LandListItem * first = l->first;
        if (first) {
            return first->data;
        }
    }
    return NULL;
}
LandWidget* land_widget_container_child_i(LandWidget * super, int i) {
    /* Return the child i or else None.
     */
    LandWidgetContainer * self = (LandWidgetContainer *) super;
    int j = 0;
    {
        LandListIterator __iter0__ = LandListIterator_first(self->children);
        for (LandWidget * w = LandListIterator_item(self->children, &__iter0__); LandListIterator_next(self->children, &__iter0__); w = LandListIterator_item(self->children, &__iter0__)) {
            if (i == j) {
                return w;
            }
            j++;
        }
    }
    return NULL;
}
LandWidget* land_widget_get_sibling(LandWidget * widget, int d) {
    /* Return the previous (d=-1) or next (d=1) sibling. Returns None as
     * previous/next sibling for the first/last widget.
     * Crashes if you pass a widget not in a container.
     */
    LandWidgetContainer * self = (LandWidgetContainer *) widget->parent;
    LandWidget * prev = NULL;
    {
        LandListIterator __iter0__ = LandListIterator_first(self->children);
        for (LandWidget * w = LandListIterator_item(self->children, &__iter0__); LandListIterator_next(self->children, &__iter0__); w = LandListIterator_item(self->children, &__iter0__)) {
            if (w == widget && d == - 1) {
                return prev;
            }
            if (prev == widget && d == 1) {
                return w;
            }
            prev = w;
        }
    }
    return NULL;
}
int land_widget_container_is_empty(LandWidget * super) {
    LandWidgetContainer * self = (LandWidgetContainer *) super;
    return ! self->children || self->children->count == 0;
}
void land_widget_container_initialize(LandWidget * super, LandWidget * parent, int x, int y, int w, int h) {
    land_widget_container_interface_initialize();
    LandWidgetContainer * self = (LandWidgetContainer *) super;
    land_widget_base_initialize(super, parent, x, y, w, h);
    super->vt = land_widget_container_interface;
    self->children = NULL;
    land_widget_layout_disable(super);
    land_widget_theme_initialize(super);
}
LandWidget* land_widget_container_new(LandWidget * parent, int x, int y, int w, int h) {
    LandWidgetContainer * self;
    land_alloc(self);
    land_widget_container_initialize(& self->super, parent, x, y, w, h);
    return & self->super;
}
void land_widget_container_interface_initialize(void) {
    if (land_widget_container_interface) {
        return ;
    }
    land_alloc(land_widget_container_interface);
    land_widget_interface_register(land_widget_container_interface);
    land_widget_container_interface->id = LAND_WIDGET_ID_BASE | LAND_WIDGET_ID_CONTAINER;
    land_widget_container_interface->name = land_strdup("container");
    land_widget_container_interface->destroy = land_widget_container_destroy;
    land_widget_container_interface->draw = land_widget_container_draw;
    land_widget_container_interface->tick = land_widget_container_tick;
    land_widget_container_interface->add = land_widget_container_add;
    land_widget_container_interface->remove = land_widget_container_remove;
    land_widget_container_interface->move = land_widget_container_move;
    land_widget_container_interface->size = land_widget_container_size;
    land_widget_container_interface->update = land_widget_container_update;
    land_widget_container_interface->mouse_tick = land_widget_container_mouse_tick;
    land_widget_container_interface->mouse_enter = land_widget_container_mouse_enter;
    land_widget_container_interface->mouse_leave = land_widget_container_mouse_leave;
    land_widget_container_interface->keyboard_tick = land_widget_container_keyboard_tick;
    land_widget_container_interface->keyboard_enter = land_widget_container_keyboard_enter;
    land_widget_container_interface->keyboard_leave = land_widget_container_keyboard_leave;
}
static LandWidgetInterface * land_widget_slider_interface;
static LandWidgetInterface * land_widget_handle_horizontal_interface;
LandWidget* land_widget_handle_new(LandWidget * parent, float minval, float maxval, bool vertical, void(* update)(LandWidget *), int x, int y, int w, int h) {
    LandWidgetHandle * self;
    land_alloc(self);
    LandWidget * super = (void *) self;
    land_widget_handle_interface_initialize();
    land_widget_base_initialize(super, parent, x, y, w, h);
    super->vt = land_widget_handle_horizontal_interface;
    land_widget_theme_initialize(super);
    self->vertical = vertical;
    self->minval = minval;
    self->maxval = maxval;
    self->update = update;
    self->value = minval;
    super->no_clip_check = 1;
    self->super.box.w = self->super.box.min_width;
    self->super.box.h = self->super.box.min_height;
    return super;
}
LandWidget* land_widget_slider_new(LandWidget * parent, float minval, float maxval, bool vertical, void(* update)(LandWidget *), int x, int y, int w, int h) {
    /* vertical - whether the slider is vertical
     */
    LandWidgetSlider * self;
    land_widget_slider_interface_initialize();
    land_alloc(self);
    LandWidget * super = (void *) self;
    land_widget_container_initialize(super, parent, x, y, w, h);
    super->vt = land_widget_slider_interface;
    land_widget_theme_initialize(super);
    LandWidget * handle = land_widget_handle_new(super, minval, maxval, vertical, update, x + super->element->il, y + super->element->it, 0, 0);
    land_widget_layout_set_minimum_size(super, handle->box.min_width + super->element->il + super->element->it, handle->box.min_height + super->element->il + super->element->it);
    return super;
}
static void _slider_cb(LandWidget * w) {
    LandWidgetSlider * self = (void *) w->parent;
    if (self->valuep) {
        * self->valuep = land_widget_slider_get_value(w->parent);
    }
}
LandWidget* land_widget_slider_new_val(LandWidget * parent, float minval, float maxval, bool vertical, float * val, int x, int y, int w, int h) {
    LandWidget * widget = land_widget_slider_new(parent, minval, maxval, vertical, _slider_cb, x, y, w, h);
    LandWidgetSlider * self = (void *) widget;
    self->valuep = val;
    land_widget_slider_set_value(widget, * val);
    return widget;
}
void land_widget_slider_size(LandWidget * widget, float dx, float dy) {
    if (! (dx || dy)) {
        return ;
    }
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(widget);
    LandListItem * item = container->children->first;
    LandWidget * handle = LAND_WIDGET(item->data);
    land_widget_handle_update(handle, 0);
}
void land_widget_handle_update(LandWidget * super, int set) {
    /* Either set the value from the slider position, or else update the slider
     * position from the value.
     */
    LandWidgetHandle * self = LAND_WIDGET_HANDLE(super);
    float minpos, maxpos;
    float n, i;
    n = self->maxval - self->minval;
    i = self->value - self->minval;
    minpos = super->parent->box.x + super->parent->element->il;
    maxpos = super->parent->box.x + super->parent->box.w - super->parent->element->ir;
    maxpos -= super->box.w;
    if (set) {
        self->value = self->minval + (super->box.x - minpos) * n / (maxpos - minpos);
        if (self->update) {
            self->update(super);
        }
    }
    else {
        super->box.x = minpos + i * (maxpos - minpos) / n;
        super->box.y = super->parent->box.y + super->parent->element->it;
    }
}
void land_widget_handle_draw(LandWidget * super) {
    land_widget_handle_update(super, 0);
    land_widget_theme_draw(super);
}
void land_widget_handle_mouse_tick(LandWidget * super) {
    LandWidgetHandle * self = LAND_WIDGET_HANDLE(super);
    if (land_mouse_delta_b()) {
        if (land_mouse_b() & 1) {
            self->drag_x = land_mouse_x() - super->box.x;
            self->drag_y = land_mouse_y() - super->box.y;
            self->dragged = 1;
        }
        else {
            self->dragged = 0;
        }
    }
    if ((land_mouse_b() & 1) && self->dragged) {
        int newx = land_mouse_x() - self->drag_x;
        int newy = land_mouse_y() - self->drag_y;
        int l = super->parent->box.x + super->parent->element->il;
        int t = super->parent->box.y + super->parent->element->it;
        int r = super->parent->box.x + super->parent->box.w - super->box.w - super->parent->element->ir;
        int b = super->parent->box.y + super->parent->box.h - super->box.h - super->parent->element->ib;
        if (newx > r) {
            newx = r;
        }
        if (newy > b) {
            newy = b;
        }
        if (newx < l) {
            newx = l;
        }
        if (newy < t) {
            newy = t;
        }
        newy = t;
        int dx = newx - super->box.x;
        int dy = newy - super->box.y;
        land_widget_move(super, dx, dy);
        land_widget_handle_update(super, 1);
    }
}
void land_widget_slider_set_value(LandWidget * super, float value) {
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(super);
    LandListItem * item = container->children->first;
    LandWidgetHandle * handle = LAND_WIDGET_HANDLE(item->data);
    handle->value = value;
    land_widget_handle_update(LAND_WIDGET(handle), 0);
}
float land_widget_slider_get_value(LandWidget * super) {
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(super);
    LandListItem * item = container->children->first;
    LandWidgetHandle * handle = LAND_WIDGET_HANDLE(item->data);
    return handle->value;
}
LandWidget* land_widget_slider_get_handle(LandWidget * super) {
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(super);
    LandListItem * item = container->children->first;
    return item->data;
}
void land_widget_slider_interface_initialize(void) {
    if (land_widget_slider_interface) {
        return ;
    }
    land_widget_container_interface_initialize();
    land_widget_slider_interface = land_widget_copy_interface(land_widget_container_interface, "slider.horizontal");
    land_widget_slider_interface->id |= LAND_WIDGET_ID_SLIDER;
    land_widget_slider_interface->size = land_widget_slider_size;
}
void land_widget_handle_interface_initialize(void) {
    if (land_widget_handle_horizontal_interface) {
        return ;
    }
    land_widget_base_interface_initialize();
    land_widget_handle_horizontal_interface = land_widget_copy_interface(land_widget_base_interface, "handle.horizontal");
    land_widget_handle_horizontal_interface->id |= LAND_WIDGET_ID_HANDLE;
    land_widget_handle_horizontal_interface->draw = land_widget_handle_draw;
    land_widget_handle_horizontal_interface->mouse_tick = land_widget_handle_mouse_tick;
}
LandWidgetInterface * land_widget_button_interface;
void land_widget_button_draw(LandWidget * base) {
    LandWidgetButton * self = LAND_WIDGET_BUTTON(base);
    land_widget_box_draw(base);
    if (self->dynamic_text_cb) {
        self->dynamic_text_cb(base);
    }
    if (! base->dont_clip) {
        float l, t, r, b;
        land_widget_inner_extents(base, & l, & t, & r, & b);
        land_clip_push();
        land_clip_intersect(l, t, r, b);
    }
    LandImage * image = self->image;
    if (self->animation) {
        float fps = self->animation->fps;
        int i = (int)(land_get_time() * fps) % self->animation->frames->count;
        image = land_animation_get_frame(self->animation, i);
    }
    if (image) {
        int w = land_image_width(image);
        int h = land_image_height(image);
        if (self->scale_x > 0 && self->scale_y > 0) {
            w *= self->scale_x;
            h *= self->scale_y;
        }
        float x = base->box.x + base->element->il;
        switch (self->xalign) {
            case 1: {
                x = base->box.x + base->box.w - base->element->ir - w;
                break;
            }
            case 2: {
                x = base->box.x + base->element->il + (base->box.w - base->element->il - base->element->ir - w) * 0.5;
                break;
            }
        }
        float y = base->box.y + base->element->it;
        switch (self->yalign) {
            case 1: {
                y = base->box.y + base->box.h - base->element->ib - h;
                break;
            }
            case 2: {
                y = base->box.y + base->element->it + (base->box.h - base->element->it - base->element->ib - h) * 0.5;
                break;
            }
        }
        x += self->xshift;
        y += self->yshift;
        if (self->scale_x > 0 && self->scale_y > 0) {
            land_image_draw_scaled(image, x + image->x * self->scale_x, y + image->y * self->scale_y, self->scale_x, self->scale_y);
        }
        else {
            land_image_draw(image, x + image->x, y + image->y);
        }
    }
    if (self->text) {
        int x, y = base->box.y + base->element->it;
        if (self->color_override) {
            land_color_set(self->color);
        }
        else {
            land_widget_theme_color(base);
        }
        land_widget_theme_font(base);
        int th = land_font_height(land_font_current());
        switch (self->yalign) {
            case 1: {
                y = base->box.y + base->box.h - base->element->ib - th;
                break;
            }
            case 2: {
                y = base->box.y + (base->box.h - base->element->it + base->element->ib - th) / 2;
                break;
            }
        }
        y += self->yshift;
        switch (self->xalign) {
            case 0: {
                x = base->box.x + base->element->il;
                x += self->xshift;
                land_text_pos(x, y);
                if (self->multiline) {
                    land_print_colored_lines(self->lines, 0, self->line_colors);
                }
                else {
                    land_print("%s", self->text);
                }
                break;
            }
            case 1: {
                x = base->box.x + base->box.w - base->element->ir;
                x += self->xshift;
                land_text_pos(x, y);
                if (self->multiline) {
                    land_print_lines(self->lines, 1);
                }
                else {
                    land_print_right("%s", self->text);
                }
                break;
            }
            case 2: {
                x = base->box.x + (base->box.w - base->element->il + base->element->ir) / 2;
                x += self->xshift;
                land_text_pos(x, y);
                if (self->multiline) {
                    land_print_lines(self->lines, 2);
                }
                else {
                    land_print_center("%s", self->text);
                }
                break;
            }
        }
    }
    if (! base->dont_clip) {
        land_clip_pop();
    }
}
void land_widget_button_size(LandWidget * base, float dx, float dy) {
    if (! (dx || dy)) {
        return ;
    }
    LandWidgetButton * button = LAND_WIDGET_BUTTON(base);
    land_widget_button_multiline(base, button->multiline);
}
void land_widget_button_mouse_tick(LandWidget * base) {
    LandWidgetButton * button = LAND_WIDGET_BUTTON(base);
    if (! base->disabled && button->clicked) {
        if (land_mouse_button_clicked(0)) {
            button->clicked(base);
        }
    }
    if (button->rclicked) {
        if (land_mouse_button_clicked(1)) {
            button->rclicked(base);
        }
    }
}
void land_widget_button_initialize(LandWidget * base, LandWidget * parent, char const * text, LandImage * image, bool destroy, void(* clicked)(LandWidget * self), int x, int y, int w, int h) {
    land_widget_base_initialize(base, parent, x, y, w, h);
    land_widget_button_interface_initialize();
    base->vt = land_widget_button_interface;
    land_widget_theme_initialize(base);
    LandWidgetButton * self = LAND_WIDGET_BUTTON(base);
    if (text) {
        self->text = land_strdup(text);
        land_widget_theme_set_minimum_size_for_text(base, text);
        w = _scramble_max(w, base->box.min_width);
        h = _scramble_max(h, base->box.min_height);
        land_widget_layout_set_minimum_size(base, w, h);
    }
    if (image) {
        self->image = image;
        self->want_image_destroyed = destroy;
        land_widget_theme_set_minimum_size_for_image(base, image);
    }
    self->clicked = clicked;
    if (parent) {
        land_widget_layout(parent);
    }
}
LandWidget* land_widget_button_new(LandWidget * parent, char const * text, void(* clicked)(LandWidget * self), int x, int y, int w, int h) {
    LandWidgetButton * button;
    land_alloc(button);
    LandWidget * self = (LandWidget *) button;
    land_widget_button_initialize(self, parent, text, NULL, false, clicked, x, y, w, h);
    return self;
}
LandWidget* land_widget_button_new_with_image(LandWidget * parent, char const * text, LandImage * image, bool destroy, void(* clicked)(LandWidget * self), int x, int y, int w, int h) {
    LandWidgetButton * button;
    land_alloc(button);
    LandWidget * self = (LandWidget *) button;
    land_widget_button_initialize(self, parent, text, image, destroy, clicked, x, y, w, h);
    return self;
}
void land_widget_button_image_scale(LandWidget * base, float xs, float ys) {
    LandWidgetButton * button = LAND_WIDGET_BUTTON(base);
    button->scale_x = xs;
    button->scale_y = ys;
}
void land_widget_button_set_image(LandWidget * base, LandImage * image, bool destroy) {
    LandWidgetButton * self = LAND_WIDGET_BUTTON(base);
    self->image = image;
    self->want_image_destroyed = destroy;
}
LandWidget* land_widget_button_new_with_animation(LandWidget * parent, char const * text, LandAnimation * animation, void(* clicked)(LandWidget * self), int x, int y, int w, int h) {
    LandWidgetButton * button;
    land_alloc(button);
    LandWidget * self = (LandWidget *) button;
    land_widget_button_initialize(self, parent, text, NULL, false, clicked, x, y, w, h);
    button->animation = animation;
    return self;
}
LandWidget* land_widget_text_initialize(LandWidget * self, LandWidget * parent, char const * text, int multiline, int x, int y, int w, int h) {
    land_widget_button_initialize(self, parent, multiline ? NULL : text, NULL, false, NULL, x, y, w, h);
    self->no_decoration = 1;
    LandWidgetButton * button = LAND_WIDGET_BUTTON(self);
    button->multiline = multiline;
    land_widget_theme_initialize(self);
    if (multiline) {
        land_widget_button_set_text(self, text);
    }
    else {
        land_widget_layout(parent);
    }
    return self;
}
LandWidget* land_widget_text_new(LandWidget * parent, char const * text, int multiline, int x, int y, int w, int h) {
    /* For normal text the minimum size is the rectangle to fit it into a
     * single line. (multiline == 0)
     * For multi-line text it is the rectangle to contain all lines of
     * text. (multiline == 1)
     * For multi-line text with word-wrap the initial width is taken for
     * word-wrapping and then the rectangle to fit the word-wrapped text is
     * used. This can be less than w if the longest line is less, or more
     * than w if wordwrapping needs more width (i.e. the longest word is
     * longer than w). (multiline == 2)
     */
    LandWidgetButton * button;
    land_alloc(button);
    LandWidget * self = (LandWidget *) button;
    land_widget_text_initialize(self, parent, text, multiline, x, y, w, h);
    return self;
}
static int _linedelcb(void * item, void * data) {
    land_free(item);
    return 0;
}
void land_widget_button_replace_text(LandWidget * base, char const * text) {
    /* Same as set_text but does not trigger any layout updates.
     */
    LandWidgetButton * button = LAND_WIDGET_BUTTON(base);
    if (button->text) {
        land_free(button->text);
    }
    button->text = NULL;
    if (text) {
        button->text = land_strdup(text);
    }
    if (button->multiline) {
        land_widget_button_multiline(base, button->multiline);
    }
}
void land_widget_button_set_text(LandWidget * base, char const * text) {
    land_widget_button_replace_text(base, text);
    land_widget_button_layout_text(base);
}
str land_widget_button_get_text(LandWidget * base) {
    LandWidgetButton * button = LAND_WIDGET_BUTTON(base);
    return button->text;
}
void land_widget_button_layout_text(LandWidget * base) {
    LandWidgetButton * button = LAND_WIDGET_BUTTON(base);
    if (button->text) {
        if (button->multiline) {
            land_widget_button_multiline(base, button->multiline);
        }
        else {
            land_widget_theme_set_minimum_size_for_text(base, button->text);
        }
    }
    if (base->parent) {
        land_widget_layout(base->parent);
    }
}
void land_widget_button_append(LandWidget * base, char const * text) {
    LandWidgetButton * button = LAND_WIDGET_BUTTON(base);
    char * newt = button->text;
    if (! newt) {
        newt = land_strdup("");
    }
    land_concatenate(& newt, text);
    button->text = newt;
    land_widget_button_layout_text(base);
}
void land_widget_button_append_row(LandWidget * base, char const * text) {
    LandWidgetButton * button = LAND_WIDGET_BUTTON(base);
    char * newt = button->text;
    if (! newt) {
        newt = land_strdup("");
    }
    if (newt [0] != 0) {
        land_concatenate(& newt, "\n");
    }
    land_concatenate(& newt, text);
    button->text = newt;
    land_widget_button_layout_text(base);
}
int land_widget_button_line_count(LandWidget * base) {
    LandWidgetButton * button = LAND_WIDGET_BUTTON(base);
    if (! button->text [0]) {
        return 0;
    }
    if (! button->lines) {
        return 1;
    }
    return land_array_count(button->lines);
}
void land_widget_button_multiline(LandWidget * self, int style) {
    /* If style is 0, the text of this widget is a single line. No newline
     * characters are allowed.
     * If style is 1, the text can have multiple lines.
     * If style is 2, the text can have multiple lines, and long lines are
     * word wrapped.
     */
    LandWidgetButton * button = LAND_WIDGET_BUTTON(self);
    button->multiline = style;
    if (button->lines) {
        land_array_for_each(button->lines, _linedelcb, NULL);
        land_array_destroy(button->lines);
        button->lines = NULL;
    }
    if (style && button->text) {
        float x, y, w, h;
        land_widget_theme_font(self);
        land_widget_inner(self, & x, & y, & w, & h);
        if (style == 0 || style == 1) {
            button->lines = land_text_splitlines(button->text);
            int ww = 0;
            int wh = 0;
            {
                LandArrayIterator __iter0__ = LandArrayIterator_first(button->lines);
                for (char * row = LandArrayIterator_item(button->lines, &__iter0__); LandArrayIterator_next(button->lines, &__iter0__); row = LandArrayIterator_item(button->lines, &__iter0__)) {
                    ww = _scramble_max(ww, land_text_get_width(row));
                    wh += land_line_height();
                }
            }
            land_widget_theme_set_minimum_size_for_contents(self, ww, wh);
            land_widget_layout(self);
        }
        else {
            button->lines = land_wordwrap_text(w, 0, button->text);
            float ww, wh;
            land_wordwrap_extents(& ww, & wh);
            if (ww - w > 0.1) {
                land_array_for_each(button->lines, _linedelcb, NULL);
                land_array_destroy(button->lines);
                button->lines = land_wordwrap_text(ww, 0, button->text);
                land_wordwrap_extents(& ww, & wh);
            }
            land_widget_theme_set_minimum_size_for_contents(self, ww, wh);
        }
    }
}
void land_widget_button_align(LandWidget * self, int x, int y) {
    /* 0 = left/top
     * 1 = right/bottom
     * 2 = center
     */
    LandWidgetButton * button = LAND_WIDGET_BUTTON(self);
    button->xalign = x;
    button->yalign = y;
}
void land_widget_button_shift(LandWidget * self, int x, int y) {
    LandWidgetButton * button = LAND_WIDGET_BUTTON(self);
    button->xshift = x;
    button->yshift = y;
}
void land_widget_button_interface_initialize(void) {
    if (land_widget_button_interface) {
        return ;
    }
    land_widget_button_interface = land_widget_copy_interface(land_widget_base_interface, "button");
    land_widget_button_interface->id |= LAND_WIDGET_ID_BUTTON;
    land_widget_button_interface->destroy = land_widget_button_destroy;
    land_widget_button_interface->draw = land_widget_button_draw;
    land_widget_button_interface->mouse_tick = land_widget_button_mouse_tick;
    land_widget_button_interface->size = land_widget_button_size;
}
void land_widget_button_destroy(LandWidget * base) {
    LandWidgetButton * button = LAND_WIDGET_BUTTON(base);
    if (button->text) {
        land_free(button->text);
    }
    if (button->lines) {
        land_array_for_each(button->lines, _linedelcb, NULL);
        land_array_destroy(button->lines);
    }
    if (button->line_colors) {
        land_hash_destroy(button->line_colors);
    }
    if (button->image && button->want_image_destroyed) {
        land_image_destroy(button->image);
    }
    land_widget_base_destroy(base);
}
void land_widget_button_set_minimum_text(LandWidget * base, char const * text) {
    land_widget_theme_set_minimum_size_for_text(base, text);
}
void land_widget_button_set_dynamic_text_callback(LandWidget * self, void(* cb)(LandWidget *)) {
    LandWidgetButton * button = LAND_WIDGET_BUTTON(self);
    button->dynamic_text_cb = cb;
}
void land_widget_button_set_color(LandWidget * base, LandColor c) {
    LandWidgetButton * button = LAND_WIDGET_BUTTON(base);
    button->color_override = 1;
    button->color = c;
}
void land_widget_button_set_line_color(LandWidget * base, int i, str color) {
    LandWidgetButton * button = LAND_WIDGET_BUTTON(base);
    if (! button->line_colors) {
        button->line_colors = land_hash_new();
    }
    land_set_line_color(button->line_colors, i, color);
}
LandWidgetInterface * land_widget_board_interface;
void land_widget_board_initialize(LandWidget * base, LandWidget * parent, int x, int y, int w, int h) {
    LandWidgetBoard * self = (LandWidgetBoard *) base;
    land_widget_board_interface_initialize();
    LandWidgetContainer * super = & self->super;
    land_widget_container_initialize(& super->super, parent, x, y, w, h);
    base->vt = land_widget_board_interface;
    land_widget_theme_initialize(base);
}
LandWidget* land_widget_board_new(LandWidget * parent, int x, int y, int w, int h) {
    LandWidgetBoard * self;
    land_alloc(self);
    land_widget_board_initialize((LandWidget *) self, parent, x, y, w, h);
    return LAND_WIDGET(self);
}
void land_widget_board_add(LandWidget * base, LandWidget * add) {
    land_widget_container_add(base, add);
}
void land_widget_board_size(LandWidget * super, float dx, float dy) {
    ;
}
void land_widget_board_update(LandWidget * widget) {
    ;
}
void land_widget_board_interface_initialize(void) {
    land_widget_container_interface_initialize();
    land_widget_board_interface = land_widget_copy_interface(land_widget_container_interface, "board");
    land_widget_board_interface->id |= LAND_WIDGET_ID_BOARD;
    land_widget_board_interface->add = land_widget_board_add;
    land_widget_board_interface->size = land_widget_board_size;
    land_widget_board_interface->update = land_widget_board_update;
}
static const int COPLANAR = 0;
static const int FRONT = 1;
static const int BACK = 2;
static const int SPANNING = 3;
LandCSGVertex* land_csg_vertex_new(LandVector pos, LandVector normal) {
    LandCSGVertex * self;
    land_alloc(self);
    self->pos = pos;
    self->normal = normal;
    return self;
}
void land_csg_vertex_destroy(LandCSGVertex * self) {
    land_free(self);
}
LandCSGVertex* land_csg_vertex_new_pool(LandMemoryPool * pool) {
    LandCSGVertex * self = land_pool_alloc(pool, sizeof (* self));
    return self;
}
LandCSGVertex* csg_vertex_clone(LandCSG * csg, LandCSGVertex * self, bool pool) {
    LandCSGVertex * v;
    if (pool) {
        v = land_csg_vertex_new_pool(csg->pool);
    }
    else {
        land_alloc(v);
    }
    v->pos = self->pos;
    v->normal = self->normal;
    v->rgba = self->rgba;
    return v;
}
static int collision_code(LandVector * v, LandCSGAABB * b) {
    int c = 0;
    if (v->x < b->x1) {
        c |= 1;
    }
    if (v->x > b->x2) {
        c |= 2;
    }
    if (v->y < b->y1) {
        c |= 4;
    }
    if (v->y > b->y2) {
        c |= 8;
    }
    if (v->z < b->z1) {
        c |= 16;
    }
    if (v->z > b->z2) {
        c |= 32;
    }
    return c;
}
static bool polygon_intersects_aabb(LandCSGPolygon * polygon, LandCSGAABB * box) {
    int a = 0, b = 0;
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(polygon->vertices);
        for (LandCSGVertex * v = LandArrayIterator_item(polygon->vertices, &__iter0__); LandArrayIterator_next(polygon->vertices, &__iter0__); v = LandArrayIterator_item(polygon->vertices, &__iter0__)) {
            int c = collision_code(& v->pos, box);
            if (c == 0) {
                return 1;
            }
            a |= c;
            b |= ~ c;
        }
    }
    if (((a ^ b) & 63) != 0) {
        return 0;
    }
    return 1;
}
static LandArray* clone_vertices(LandCSG * csg, LandArray * vertices) {
    LandArray * clone = land_array_copy(vertices);
    int n = land_array_count(clone);
    for (int i = 0; i < n; i += 1) {
        clone->data [i] = csg_vertex_clone(csg, clone->data [i], 1);
    }
    return clone;
}
static void remove_vertices(LandArray * vertices, bool is_pooled) {
    if (! is_pooled) {
        for (int i = 0; i < vertices->count; i += 1) {
            land_csg_vertex_destroy(vertices->data [i]);
        }
    }
    land_array_destroy(vertices);
}
static void csg_vertex_flip(LandCSGVertex * self) {
    self->normal = land_vector_mul(self->normal, - 1);
}
static LandCSGVertex* csg_vertex_interpolate(LandCSG * csg, LandCSGVertex * self, LandCSGVertex * other, LandFloat t) {
    LandCSGVertex * v = land_csg_vertex_new_pool(csg->pool);
    v->pos = land_vector_lerp(self->pos, other->pos, t);
    v->normal = land_vector_lerp(self->normal, other->normal, t);
    v->rgba = land_color_lerp(self->rgba, other->rgba, t);
    return v;
}
static LandCSGPlane csg_plane(LandVector normal, LandFloat w) {
    LandCSGPlane self;
    self.normal = normal;
    self.w = w;
    return self;
}
static const LandFloat LandCSGPlaneEPSILON = 0.00001;
LandCSGPlane land_csg_plane_from_points(LandVector a, LandVector b, LandVector c) {
    LandVector ac = land_vector_sub(c, a);
    LandVector ab = land_vector_sub(b, a);
    LandVector n = land_vector_cross(ab, ac);
    n = land_vector_normalize(n);
    return csg_plane(n, land_vector_dot(n, a));
}
void land_csg_polygon_init(LandCSGPolygon * self, LandArray * vertices, void * shared) {
    self->vertices = vertices;
    self->shared = shared;
    LandCSGVertex * v0 = land_array_get_nth(vertices, 0);
    LandCSGVertex * v1 = land_array_get_nth(vertices, 1);
    LandCSGVertex * v2 = land_array_get_nth(vertices, 2);
    self->plane = land_csg_plane_from_points(v0->pos, v1->pos, v2->pos);
}
LandCSGPolygon* land_csg_polygon_new(LandArray * vertices, void * shared) {
    LandCSGPolygon * self;
    land_alloc(self);
    land_csg_polygon_init(self, vertices, shared);
    return self;
}
static LandCSGPolygon* land_csg_polygon_new_pool(LandMemoryPool * pool, LandArray * vertices, void * shared) {
    LandCSGPolygon * p = land_pool_alloc(pool, sizeof (* p));
    p->is_pooled = 1;
    land_csg_polygon_init(p, vertices, shared);
    return p;
}
void csg_plane_flip(LandCSGPlane * self) {
    self->normal = land_vector_mul(self->normal, - 1);
    self->w *= - 1;
}
static void csg_plane_split_polygon(LandCSG * csg, LandCSGPlane * self, LandCSGPolygon * polygon, LandArray * coplanar_front, LandArray * coplanar_back, LandArray * front, LandArray * back) {
    int polygon_type = 0;
    int n = land_array_count(polygon->vertices);
    int i = 0;
    int types [n];
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(polygon->vertices);
        for (LandCSGVertex * v = LandArrayIterator_item(polygon->vertices, &__iter0__); LandArrayIterator_next(polygon->vertices, &__iter0__); v = LandArrayIterator_item(polygon->vertices, &__iter0__)) {
            LandFloat dot = land_vector_dot(self->normal, v->pos) - self->w;
            int t = (dot < - LandCSGPlaneEPSILON ? BACK : dot > LandCSGPlaneEPSILON ? FRONT : COPLANAR);
            polygon_type |= t;
            types [i++] = t;
        }
    }
    assert(i == n);
    if (polygon_type == COPLANAR) {
        if (land_vector_dot(self->normal, polygon->plane.normal) > 0) {
            land_array_add(coplanar_front, polygon);
        }
        else {
            land_array_add(coplanar_back, polygon);
        }
    }
    else if (polygon_type == FRONT) {
        land_array_add(front, polygon);
    }
    else if (polygon_type == BACK) {
        land_array_add(back, polygon);
    }
    else if (polygon_type == SPANNING) {
        LandArray * f = land_array_new();
        LandArray * b = land_array_new();
        for (i = 0; i < n; i += 1) {
            int j = (i + 1) % n;
            int ti = types [i];
            int tj = types [j];
            LandCSGVertex * vi = land_array_get_nth(polygon->vertices, i);
            LandCSGVertex * vj = land_array_get_nth(polygon->vertices, j);
            if (ti == FRONT) {
                land_array_add(f, csg_vertex_clone(csg, vi, 1));
            }
            else if (ti == BACK) {
                land_array_add(b, csg_vertex_clone(csg, vi, 1));
            }
            else if (ti == COPLANAR) {
                land_array_add(f, csg_vertex_clone(csg, vi, 1));
                land_array_add(b, csg_vertex_clone(csg, vi, 1));
            }
            if ((ti | tj) == SPANNING) {
                LandFloat t = self->w - land_vector_dot(self->normal, vi->pos);
                t /= land_vector_dot(self->normal, land_vector_sub(vj->pos, vi->pos));
                LandCSGVertex * v = csg_vertex_interpolate(csg, vi, vj, t);
                land_array_add(f, v);
                land_array_add(b, csg_vertex_clone(csg, v, 1));
            }
        }
        if (land_array_count(f) >= 3) {
            LandCSGPolygon * add = land_csg_polygon_new_pool(csg->pool, f, polygon->shared);
            land_array_add(front, add);
        }
        else {
            remove_vertices(f, true);
        }
        if (land_array_count(b) >= 3) {
            LandCSGPolygon * add = land_csg_polygon_new_pool(csg->pool, b, polygon->shared);
            land_array_add(back, add);
        }
        else {
            remove_vertices(b, true);
        }
        land_csg_polygon_destroy(polygon);
    }
}
void land_csg_polygon_destroy(LandCSGPolygon * self) {
    if (self->is_pooled) {
        land_array_destroy(self->vertices);
    }
    else {
        remove_vertices(self->vertices, false);
        land_free(self);
    }
}
LandCSGPolygon* land_csg_polygon_clone(LandCSG * csg, LandCSGPolygon const * self) {
    LandCSGPolygon * clone = land_csg_polygon_new_pool(csg->pool, clone_vertices(csg, self->vertices), self->shared);
    return clone;
}
void land_csg_polygon_flip(LandCSGPolygon * self) {
    land_array_reverse(self->vertices);
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(self->vertices);
        for (LandCSGVertex * v = LandArrayIterator_item(self->vertices, &__iter0__); LandArrayIterator_next(self->vertices, &__iter0__); v = LandArrayIterator_item(self->vertices, &__iter0__)) {
            csg_vertex_flip(v);
        }
    }
    csg_plane_flip(& self->plane);
}
static LandArray* clone_polygons(LandCSG * csg, LandArray * polygons) {
    LandArray * clone = land_array_copy(polygons);
    int n = land_array_count(clone);
    for (int i = 0; i < n; i += 1) {
        clone->data [i] = land_csg_polygon_clone(csg, clone->data [i]);
    }
    return clone;
}
static void clear_polygons(LandArray * polygons) {
    for (int i = 0; i < polygons->count; i += 1) {
        land_csg_polygon_destroy(polygons->data [i]);
    }
    land_array_clear(polygons);
}
static void csg_node_destroy(LandCSGNode * self) {
    if (self->front) {
        csg_node_destroy(self->front);
    }
    if (self->back) {
        csg_node_destroy(self->back);
    }
    clear_polygons(self->polygons);
    land_array_destroy(self->polygons);
    land_free(self);
}
static void csg_node_invert(LandCSGNode * self) {
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(self->polygons);
        for (LandCSGPolygon * p = LandArrayIterator_item(self->polygons, &__iter0__); LandArrayIterator_next(self->polygons, &__iter0__); p = LandArrayIterator_item(self->polygons, &__iter0__)) {
            land_csg_polygon_flip(p);
        }
    }
    csg_plane_flip(& self->plane);
    if (self->front) {
        csg_node_invert(self->front);
    }
    if (self->back) {
        csg_node_invert(self->back);
    }
    LandCSGNode * temp = self->front;
    self->front = self->back;
    self->back = temp;
}
static void csg_node_clip_polygons(LandCSG * csg, LandCSGNode * self, LandArray * polygons) {
    if (! self->plane.normal.z && ! self->plane.normal.y && ! self->plane.normal.x) {
        return ;
    }
    LandArray * front = land_array_new();
    LandArray * back = land_array_new();
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(polygons);
        for (LandCSGPolygon * p = LandArrayIterator_item(polygons, &__iter0__); LandArrayIterator_next(polygons, &__iter0__); p = LandArrayIterator_item(polygons, &__iter0__)) {
            csg_plane_split_polygon(csg, & self->plane, p, front, back, front, back);
        }
    }
    land_array_clear(polygons);
    if (self->front) {
        csg_node_clip_polygons(csg, self->front, front);
    }
    if (self->back) {
        csg_node_clip_polygons(csg, self->back, back);
    }
    else {
        clear_polygons(back);
    }
    land_array_concat(polygons, front);
    land_array_concat(polygons, back);
    land_array_destroy(front);
    land_array_destroy(back);
}
static void csg_node_clip_to(LandCSG * csg, LandCSGNode * self, LandCSGNode * bsp) {
    csg_node_clip_polygons(csg, bsp, self->polygons);
    if (self->front) {
        csg_node_clip_to(csg, self->front, bsp);
    }
    if (self->back) {
        csg_node_clip_to(csg, self->back, bsp);
    }
}
static LandArray* csg_node_all_polygons(LandCSG * csg, LandCSGNode * self) {
    LandArray * polygons = clone_polygons(csg, self->polygons);
    if (self->front) {
        land_array_merge(polygons, csg_node_all_polygons(csg, self->front));
    }
    if (self->back) {
        land_array_merge(polygons, csg_node_all_polygons(csg, self->back));
    }
    return polygons;
}
static void csg_add_polygons_from_node(LandCSG * csg, LandCSGNode * node) {
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(node->polygons);
        for (LandCSGPolygon * p = LandArrayIterator_item(node->polygons, &__iter0__); LandArrayIterator_next(node->polygons, &__iter0__); p = LandArrayIterator_item(node->polygons, &__iter0__)) {
            land_array_add(csg->polygons, land_csg_polygon_clone(csg, p));
        }
    }
    if (node->front) {
        csg_add_polygons_from_node(csg, node->front);
    }
    if (node->back) {
        csg_add_polygons_from_node(csg, node->back);
    }
}
static void csg_add_polygons(LandCSG * csg, LandArray * polygons) {
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(polygons);
        for (LandCSGPolygon * p = LandArrayIterator_item(polygons, &__iter0__); LandArrayIterator_next(polygons, &__iter0__); p = LandArrayIterator_item(polygons, &__iter0__)) {
            land_array_add(csg->polygons, land_csg_polygon_clone(csg, p));
        }
    }
}
static void csg_node_build(LandCSG * csg, LandCSGNode * self, LandArray * polygons) {
    if (! land_array_count(polygons)) {
        land_array_destroy(polygons);
        return ;
    }
    int original = land_array_count(polygons);
    if (! self->plane.normal.z && ! self->plane.normal.y && ! self->plane.normal.x) {
        LandCSGPolygon * p0 = land_array_get_nth(polygons, 0);
        self->plane = p0->plane;
        if (1) {
            LandCSGPolygon * plast = land_array_pop(polygons);
            land_array_replace_nth(polygons, 0, plast);
            land_array_add(self->polygons, p0);
        }
    }
    LandArray * front = land_array_new();
    LandArray * back = land_array_new();
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(polygons);
        for (LandCSGPolygon * p = LandArrayIterator_item(polygons, &__iter0__); LandArrayIterator_next(polygons, &__iter0__); p = LandArrayIterator_item(polygons, &__iter0__)) {
            csg_plane_split_polygon(csg, & self->plane, p, self->polygons, self->polygons, front, back);
        }
    }
    land_array_destroy(polygons);
    int fc = land_array_count(front);
    if (fc) {
        if (fc == original) {
            LandCSGPolygon * hack = land_array_pop(front);
            land_array_add(self->polygons, hack);
        }
        if (! self->front) {
            self->front = csg_node_new(csg, NULL);
        }
        csg_node_build(csg, self->front, front);
    }
    else {
        land_array_destroy(front);
    }
    int bc = land_array_count(back);
    if (bc) {
        if (bc == original) {
            LandCSGPolygon * hack = land_array_pop(back);
            land_array_add(self->polygons, hack);
        }
        if (! self->back) {
            self->back = csg_node_new(csg, NULL);
        }
        csg_node_build(csg, self->back, back);
    }
    else {
        land_array_destroy(back);
    }
}
static LandCSGNode* csg_node_new(LandCSG * csg, LandArray * polygons) {
    LandCSGNode * self;
    land_alloc(self);
    self->polygons = land_array_new();
    if (polygons) {
        csg_node_build(csg, self, polygons);
    }
    return self;
}
void land_csg_transform(LandCSG * self, Land4x4Matrix matrix) {
    Land4x4Matrix matrix2 = * (& matrix);
    matrix2.v [3] = 0;
    matrix2.v [7] = 0;
    matrix2.v [11] = 0;
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(self->polygons);
        for (LandCSGPolygon * p = LandArrayIterator_item(self->polygons, &__iter0__); LandArrayIterator_next(self->polygons, &__iter0__); p = LandArrayIterator_item(self->polygons, &__iter0__)) {
            {
                LandArrayIterator __iter1__ = LandArrayIterator_first(p->vertices);
                for (LandCSGVertex * v = LandArrayIterator_item(p->vertices, &__iter1__); LandArrayIterator_next(p->vertices, &__iter1__); v = LandArrayIterator_item(p->vertices, &__iter1__)) {
                    v->pos = land_vector_matmul(v->pos, & matrix);
                    v->normal = land_vector_normalize(land_vector_matmul(v->normal, & matrix2));
                }
            }
        }
    }
}
void land_csg_destroy(LandCSG * self) {
    clear_polygons(self->polygons);
    land_array_destroy(self->polygons);
    land_pool_destroy(self->pool);
    land_free(self);
}
void land_csg_triangles(LandCSG * self) {
    LandArray * triangles = NULL;
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(self->polygons);
        for (LandCSGPolygon * p = LandArrayIterator_item(self->polygons, &__iter0__); LandArrayIterator_next(self->polygons, &__iter0__); p = LandArrayIterator_item(self->polygons, &__iter0__)) {
            int n = land_array_count(p->vertices);
            if (n == 3) {
                continue;
            }
            if (! triangles) {
                triangles = land_array_new();
            }
            LandCSGVertex * a = land_array_get_nth(p->vertices, 0);
            LandCSGVertex * b = land_array_get_nth(p->vertices, 2);
            for (int i = 3; i < n; i += 1) {
                LandCSGVertex * c = land_array_get_nth(p->vertices, i);
                LandArray * v = land_array_new();
                land_array_add(v, csg_vertex_clone(self, a, p->is_pooled));
                land_array_add(v, csg_vertex_clone(self, b, p->is_pooled));
                land_array_add(v, c);
                LandCSGPolygon * triangle;
                if (p->is_pooled) {
                    triangle = land_csg_polygon_new_pool(self->pool, v, p->shared);
                }
                else {
                    triangle = land_csg_polygon_new(v, p->shared);
                }
                land_array_add(triangles, triangle);
                b = c;
            }
            p->vertices->count = 3;
        }
    }
    if (triangles) {
        land_array_merge(self->polygons, triangles);
    }
}
LandCSG* land_csg_new(void) {
    LandCSG * self;
    land_alloc(self);
    self->pool = land_pool_new();
    return self;
}
LandCSG* land_csg_new_from_polygons(LandArray * polygons) {
    LandCSG * self = land_csg_new();
    self->polygons = polygons;
    return self;
}
LandCSG* land_csg_clone(LandCSG * self) {
    LandCSG * csg = land_csg_new();
    csg->polygons = clone_polygons(csg, self->polygons);
    return csg;
}
static void csg_split_on_bounding_box(LandCSG const * self, LandCSGAABB * box, LandArray * (* inside), LandArray * (* outside)) {
    /* Keep every polygon intersecting the bounding box, return the removed ones.
     * TODO: we could use a cached BSP for an extra fast bounding box check,
     * as soon as we see that the bounding box is on one side of a BSP node we
     * can ignore the other side completely.
     */
    * inside = land_array_new();
    * outside = land_array_new();
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(self->polygons);
        for (LandCSGPolygon * p = LandArrayIterator_item(self->polygons, &__iter0__); LandArrayIterator_next(self->polygons, &__iter0__); p = LandArrayIterator_item(self->polygons, &__iter0__)) {
            if (polygon_intersects_aabb(p, box)) {
                land_array_add(* inside, p);
            }
            else {
                land_array_add(* outside, p);
            }
        }
    }
}
LandCSG* land_csg_union(LandCSG * csg_a, LandCSG * csg_b) {
    LandCSG * c = land_csg_new();
    land_csg_aabb_update(& csg_a->bbox, csg_a->polygons);
    land_csg_aabb_update(& csg_b->bbox, csg_b->polygons);
    LandCSGAABB box = land_csg_aabb_intersect(csg_a->bbox, csg_b->bbox);
    LandArray * a_out, * a_in, * b_out, * b_in;
    csg_split_on_bounding_box(csg_a, & box, & a_in, & a_out);
    csg_split_on_bounding_box(csg_b, & box, & b_in, & b_out);
    LandCSGNode * a = csg_node_new(c, clone_polygons(c, a_in));
    LandCSGNode * b = csg_node_new(c, clone_polygons(c, b_in));
    csg_node_clip_to(c, a, b);
    csg_node_clip_to(c, b, a);
    if (1) {
        csg_node_invert(b);
        csg_node_clip_to(c, b, a);
        csg_node_invert(b);
    }
    c->polygons = land_array_new();
    csg_add_polygons_from_node(c, a);
    csg_add_polygons_from_node(c, b);
    csg_add_polygons(c, a_out);
    csg_add_polygons(c, b_out);
    land_array_destroy(a_in);
    land_array_destroy(a_out);
    land_array_destroy(b_in);
    land_array_destroy(b_out);
    csg_node_destroy(a);
    csg_node_destroy(b);
    return c;
}
LandCSG* land_csg_subtract(LandCSG * self, LandCSG * csg) {
    LandCSG * c = land_csg_new();
    LandCSGNode * a = csg_node_new(c, clone_polygons(c, self->polygons));
    LandCSGNode * b = csg_node_new(c, clone_polygons(c, csg->polygons));
    csg_node_invert(a);
    csg_node_clip_to(c, a, b);
    csg_node_clip_to(c, b, a);
    csg_node_invert(b);
    csg_node_clip_to(c, b, a);
    csg_node_invert(b);
    csg_node_build(c, a, csg_node_all_polygons(c, b));
    csg_node_invert(a);
    c->polygons = csg_node_all_polygons(c, a);
    csg_node_destroy(a);
    csg_node_destroy(b);
    return c;
}
LandCSG* land_csg_intersect(LandCSG * self, LandCSG * csg) {
    LandCSG * c = land_csg_new();
    LandCSGNode * a = csg_node_new(c, clone_polygons(c, self->polygons));
    LandCSGNode * b = csg_node_new(c, clone_polygons(c, csg->polygons));
    csg_node_invert(a);
    csg_node_clip_to(c, b, a);
    csg_node_invert(b);
    csg_node_clip_to(c, a, b);
    csg_node_clip_to(c, b, a);
    csg_node_build(c, a, csg_node_all_polygons(c, b));
    csg_node_invert(a);
    c->polygons = csg_node_all_polygons(c, a);
    csg_node_destroy(a);
    csg_node_destroy(b);
    return c;
}
LandCSG* land_csg_inverse(LandCSG * self) {
    LandCSG * csg = land_csg_clone(self);
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(csg->polygons);
        for (LandCSGPolygon * p = LandArrayIterator_item(csg->polygons, &__iter0__); LandArrayIterator_next(csg->polygons, &__iter0__); p = LandArrayIterator_item(csg->polygons, &__iter0__)) {
            land_csg_polygon_flip(p);
        }
    }
    return csg;
}
void land_csg_polygon_paint(LandCSGPolygon * self, float r, float g, float b, float a) {
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(self->vertices);
        for (LandCSGVertex * v = LandArrayIterator_item(self->vertices, &__iter0__); LandArrayIterator_next(self->vertices, &__iter0__); v = LandArrayIterator_item(self->vertices, &__iter0__)) {
            v->rgba.r = r;
            v->rgba.g = g;
            v->rgba.b = b;
            v->rgba.a = a;
        }
    }
}
void land_csg_paint_all(LandCSG * self, float r, float g, float b, float a) {
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(self->polygons);
        for (LandCSGPolygon * p = LandArrayIterator_item(self->polygons, &__iter0__); LandArrayIterator_next(self->polygons, &__iter0__); p = LandArrayIterator_item(self->polygons, &__iter0__)) {
            land_csg_polygon_paint(p, r, g, b, a);
        }
    }
}
void land_csg_merge(LandCSG * self, LandCSG * add) {
    {
        LandArrayIterator __iter0__ = LandArrayIterator_first(add->polygons);
        for (LandCSGPolygon * p = LandArrayIterator_item(add->polygons, &__iter0__); LandArrayIterator_next(add->polygons, &__iter0__); p = LandArrayIterator_item(add->polygons, &__iter0__)) {
            land_array_add(self->polygons, p);
        }
    }
    land_array_clear(add->polygons);
    land_csg_destroy(add);
}
LandWidgetInterface * land_widget_panel_interface;
void land_widget_panel_initialize(LandWidget * base, LandWidget * parent, int x, int y, int w, int h) {
    LandWidgetPanel * self = (LandWidgetPanel *) base;
    land_widget_panel_interface_initialize();
    LandWidgetContainer * super = & self->super;
    land_widget_container_initialize(& super->super, parent, x, y, w, h);
    base->vt = land_widget_panel_interface;
    land_widget_layout_enable(base);
    land_widget_theme_initialize(base);
}
LandWidget* land_widget_panel_new(LandWidget * parent, int x, int y, int w, int h) {
    LandWidgetPanel * self;
    land_alloc(self);
    land_widget_panel_initialize((LandWidget *) self, parent, x, y, w, h);
    return LAND_WIDGET(self);
}
void land_widget_panel_add(LandWidget * base, LandWidget * add) {
    land_widget_container_add(base, add);
    int f = land_widget_layout_freeze(base);
    land_widget_layout_set_grid_position(add, 0, 0);
    land_widget_layout_set_grid(base, 1, 1);
    if (f) {
        land_widget_layout_unfreeze(base);
    }
    land_widget_layout(base);
}
void land_widget_panel_interface_initialize(void) {
    if (land_widget_panel_interface) {
        return ;
    }
    land_widget_container_interface_initialize();
    land_widget_panel_interface = land_widget_copy_interface(land_widget_container_interface, "panel");
    land_widget_panel_interface->id |= LAND_WIDGET_ID_PANEL;
    land_widget_panel_interface->add = land_widget_panel_add;
}
LandWidgetInterface * land_widget_checkbox_interface;
LandWidgetInterface * land_widget_checkbox_button_interface;
LandWidgetInterface * land_widget_checkbox_description_interface;
void land_widget_checkbox_clicked(LandWidget * widget) {
    LandWidgetCheckBox * cb = LAND_WIDGET_CHECKBOX(widget->parent);
    if (widget->selected) {
        widget->selected = 0;
        land_widget_button_replace_text(widget, cb->checkbox_unselected);
    }
    else {
        widget->selected = 1;
        land_widget_button_replace_text(widget, cb->checkbox_selected);
    }
    if (cb->value) {
        * cb->value = widget->selected;
    }
    if (cb->changed) {
        cb->changed(widget);
    }
}
void land_widget_checkbox_initialize(LandWidget * base, LandWidget * parent, char const * checkbox_selected, char const * checkbox_unselected, char const * text, bool * b, int x, int y, int w, int h) {
    LandWidgetCheckBox * self = (void *) base;
    self->checkbox_selected = land_strdup(checkbox_selected);
    self->checkbox_unselected = land_strdup(checkbox_unselected);
    self->value = b;
    land_widget_checkbox_interface_initialize();
    land_widget_container_initialize(base, parent, x, y, w, h);
    base->vt = land_widget_checkbox_interface;
    LandWidget * checkbox = land_widget_button_new(base, checkbox_selected, land_widget_checkbox_clicked, 0, 0, 8, 8);
    checkbox->vt = land_widget_checkbox_button_interface;
    land_widget_theme_initialize(checkbox);
    land_widget_layout_set_shrinking(checkbox, 1, 0);
    LandWidget * description = land_widget_text_new(base, text, 0, 0, 0, 8, 8);
    description->vt = land_widget_checkbox_description_interface;
    land_widget_theme_initialize(description);
    land_widget_layout_set_grid(base, 2, 1);
    land_widget_layout_set_grid_position(checkbox, 0, 0);
    land_widget_layout_set_grid_position(description, 1, 0);
    land_widget_theme_initialize(base);
    land_widget_layout_enable(base);
    land_widget_layout(base);
    if (b && * b) {
        land_widget_button_replace_text(checkbox, checkbox_selected);
        checkbox->selected = 1;
    }
    else {
        land_widget_button_replace_text(checkbox, checkbox_unselected);
    }
}
LandWidget* land_widget_checkbox_new(LandWidget * parent, char const * checkbox_selected, char const * checkbox_unselected, char const * text, int x, int y, int w, int h) {
    LandWidgetCheckBox * self;
    land_alloc(self);
    land_widget_checkbox_initialize((LandWidget *) self, parent, checkbox_selected, checkbox_unselected, text, NULL, x, y, w, h);
    return (LandWidget *) self;
}
LandWidget* land_widget_checkbox_new_boolean(LandWidget * parent, char const * checkbox_selected, char const * checkbox_unselected, char const * text, bool * b, int x, int y, int w, int h) {
    LandWidgetCheckBox * self;
    land_alloc(self);
    land_widget_checkbox_initialize((LandWidget *) self, parent, checkbox_selected, checkbox_unselected, text, b, x, y, w, h);
    return (LandWidget *) self;
}
bool land_widget_checkbox_is_checked(LandWidget * self) {
    return land_widget_container_child (self)->selected;
}
void land_widget_checkbox_set(LandWidget * base, bool checked) {
    LandWidget * box = land_widget_container_child((void *) LAND_WIDGET_CONTAINER(base));
    bool was = box->selected;
    if (was != checked) {
        land_widget_checkbox_clicked(box);
    }
}
void land_widget_checkbox_interface_initialize(void) {
    if (land_widget_checkbox_interface) {
        return ;
    }
    land_widget_container_interface_initialize();
    land_widget_checkbox_interface = land_widget_copy_interface(land_widget_container_interface, "checkbox");
    land_widget_checkbox_interface->id |= LAND_WIDGET_ID_CHECKBOX;
    land_widget_checkbox_interface->destroy = land_widget_checkbox_destroy;
    land_widget_button_interface_initialize();
    land_widget_checkbox_button_interface = land_widget_copy_interface(land_widget_button_interface, "checkbox.box");
    land_widget_checkbox_description_interface = land_widget_copy_interface(land_widget_button_interface, "checkbox.description");
}
void land_widget_checkbox_destroy(LandWidget * base) {
    LandWidgetCheckBox * self = LAND_WIDGET_CHECKBOX(base);
    if (self->checkbox_selected) {
        land_free(self->checkbox_selected);
    }
    if (self->checkbox_unselected) {
        land_free(self->checkbox_unselected);
    }
    land_widget_container_destroy(base);
}
static LandWidgetInterface * land_widget_mover_interface;
void land_widget_mover_mouse_tick(LandWidget * super) {
    LandWidgetMover * self = LAND_WIDGET_MOVER(super);
    if ((land_mouse_delta_b())) {
        if (land_mouse_b() & 1) {
            self->target->send_to_top = 1;
            self->dragged = 1;
        }
        else {
            self->dragged = 0;
        }
    }
    if ((land_mouse_b() & 1) && self->dragged) {
        land_widget_move(self->target, land_mouse_delta_x(), land_mouse_delta_y());
    }
}
LandWidget* land_widget_mover_new(LandWidget * parent, char const * text, int x, int y, int w, int h) {
    /* """Create a new mover widget.
     * By default, the widget will stretch in the horizontal and shrink in the
     * vertical direction.
     */
    LandWidgetMover * self;
    land_widget_mover_interface_initialize();
    land_alloc(self);
    LandWidget * base = (LandWidget *) self;
    land_widget_button_initialize(base, parent, text, NULL, false, NULL, x, y, w, h);
    base->vt = land_widget_mover_interface;
    land_widget_layout_set_shrinking(base, 0, 1);
    land_widget_theme_initialize(base);
    if (parent) {
        land_widget_layout(parent);
    }
    self->target = parent;
    self->dragged = 0;
    return base;
}
void land_widget_mover_set_target(LandWidget * self, LandWidget * target) {
    LAND_WIDGET_MOVER (self)->target = target;
}
void land_widget_mover_interface_initialize(void) {
    if (land_widget_mover_interface) {
        return ;
    }
    land_widget_button_interface_initialize();
    land_widget_mover_interface = land_widget_copy_interface(land_widget_button_interface, "mover");
    land_widget_mover_interface->mouse_tick = land_widget_mover_mouse_tick;
}
static LandWidgetInterface * land_widget_menu_interface;
static LandWidgetInterface * land_widget_menubutton_interface;
static LandWidgetInterface * land_widget_menubar_interface;
static LandWidgetInterface * land_widget_menuitem_interface;
LandWidget* land_widget_menubar_new(LandWidget * parent, float x, float y, float w, float h) {
    /* Create a new menubar. That is, a menu which is layout horizontally instead
     * of vertically. By default, a menubar will expand horizontally, and shrink
     * vertically.
     */
    LandWidget * base = land_widget_menu_new(parent, x, y, w, h);
    base->vt = land_widget_menubar_interface;
    land_widget_theme_initialize(base);
    land_widget_layout_set_shrinking(base, 1, 1);
    return base;
}
LandWidget* land_widget_menu_new(LandWidget * parent, float x, float y, float w, float h) {
    /* Create a new menu, with menu items layout out vertically.
     */
    land_widget_menu_interface_initialize();
    LandWidgetMenu * self;
    land_alloc(self);
    LandWidget * base = (LandWidget *) self;
    land_widget_container_initialize(base, parent, x, y, w, h);
    base->vt = land_widget_menu_interface;
    land_widget_layout_enable(base);
    land_widget_theme_initialize(base);
    return base;
}
void land_widget_menu_hide_sub(LandWidget * base) {
    /* Hide the given menu and all its sub-menus.
     */
    LandWidgetMenu * menu = LAND_WIDGET_MENU(base);
    if (menu->submenu) {
        land_widget_menu_hide_sub(menu->submenu);
        menu->submenu = NULL;
    }
    land_widget_hide(base);
}
void land_widget_menu_hide_complete(LandWidget * base) {
    /* Hide the complete menu the given one is part of.
     */
    while (1) {
        LandWidgetMenu * menu = LAND_WIDGET_MENU(base);
        if (! menu->menubutton) {
            break;
        }
        LandWidgetMenuButton * button = LAND_WIDGET_MENUBUTTON(menu->menubutton);
        if (button->menu) {
            base = button->menu;
        }
        else {
            break;
        }
    }
    if (((base->vt->id & LAND_WIDGET_ID_MENUBAR) == LAND_WIDGET_ID_MENUBAR)) {
        LandWidgetMenu * menu = LAND_WIDGET_MENU(base);
        base = menu->submenu;
        if (! base) {
            return ;
        }
        menu->submenu = NULL;
    }
    land_widget_menu_hide_sub(base);
}
static void menubutton_clicked(LandWidget * base) {
    LandWidgetMenuButton * self = LAND_WIDGET_MENUBUTTON(base);
    if (self->below) {
        land_widget_move(self->submenu, base->box.x - self->submenu->box.x, base->box.y + base->box.h - self->submenu->box.y);
    }
    else {
        land_widget_move(self->submenu, base->box.x + base->box.w - self->submenu->box.x, base->box.y - self->submenu->box.y);
    }
    if ((self->menu && (self->menu->vt->id & LAND_WIDGET_ID_MENU) == LAND_WIDGET_ID_MENU)) {
        LandWidgetMenu * menu = LAND_WIDGET_MENU(self->menu);
        if (menu->submenu) {
            land_widget_menu_hide_sub(menu->submenu);
        }
        menu->submenu = self->submenu;
    }
    self->submenu->send_to_top = 1;
    land_widget_unhide(self->submenu);
    land_widget_layout(self->submenu);
}
static void menuitem_clicked(LandWidget * base) {
    LandWidgetMenuItem * self = LAND_WIDGET_MENUITEM(base);
    if (self->menu && land_widget_is(self->menu, LAND_WIDGET_ID_MENU)) {
        land_widget_menu_hide_complete(self->menu);
    }
    if (self->callback) {
        self->callback(LAND_WIDGET(self));
    }
}
LandWidget* land_widget_menubutton_new(LandWidget * parent, char const * name, LandWidget * submenu, float x, float y, float w, float h) {
    /* Create a submenu, i.e. a button in a menu to open another menu when clicked.
     */
    land_widget_menubutton_interface_initialize();
    LandWidgetMenuButton * self;
    land_alloc(self);
    land_widget_reference(submenu);
    self->submenu = submenu;
    self->menu = parent;
    if (((parent->vt->id & LAND_WIDGET_ID_MENUBAR) == LAND_WIDGET_ID_MENUBAR)) {
        self->below = 1;
    }
    LandWidget * base = (void *) self;
    land_widget_button_initialize(base, parent, name, NULL, false, menubutton_clicked, x, y, w, h);
    LAND_WIDGET_MENU (submenu)->menubutton = base;
    base->vt = land_widget_menubutton_interface;
    land_widget_theme_initialize(base);
    return base;
}
void land_widget_menubutton_destroy(LandWidget * self) {
    LandWidgetMenuButton * menubutton = LAND_WIDGET_MENUBUTTON(self);
    if (menubutton->submenu) {
        LandWidgetMenu * menu = LAND_WIDGET_MENU(menubutton->submenu);
        menu->menubutton = NULL;
        land_widget_unreference(menubutton->submenu);
    }
    land_widget_button_destroy(self);
}
void land_widget_menu_add(LandWidget * base, LandWidget * item) {
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(base);
    land_widget_container_add(base, item);
    int n = container->children->count;
    land_widget_layout_freeze(base);
    land_widget_layout_set_grid(base, 1, n);
    land_widget_layout_set_grid_position(item, 0, n - 1);
    land_widget_layout_set_shrinking(item, 1, 1);
    land_widget_layout_unfreeze(base);
}
void land_widget_menubar_add(LandWidget * base, LandWidget * item) {
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(base);
    land_widget_container_add(base, item);
    int n = container->children->count;
    land_widget_layout_freeze(base);
    land_widget_layout_set_grid(base, n, 1);
    land_widget_layout_set_grid_position(item, n - 1, 0);
    land_widget_layout_set_shrinking(item, 1, 1);
    land_widget_layout_unfreeze(base);
}
LandWidget* land_widget_menuitem_new(LandWidget * parent, char const * name, void(* callback)(LandWidget * widget)) {
    /* Create a new menu item, i.e. en entry which can be clicked to execute the
     * given callback.
     */
    int tw = land_text_get_width(name);
    int th = land_font_height(land_font_current());
    LandWidgetMenuItem * menuitem;
    land_alloc(menuitem);
    land_widget_menubutton_interface_initialize();
    menuitem->menu = parent;
    menuitem->callback = callback;
    land_widget_button_initialize((LandWidget *) menuitem, parent, name, NULL, false, menuitem_clicked, 0, 0, 10, 10);
    LandWidget * self = LAND_WIDGET(menuitem);
    self->vt = land_widget_menuitem_interface;
    land_widget_theme_initialize(self);
    land_widget_layout_set_minimum_size(self, self->element->il + self->element->ir + tw, self->element->it + self->element->ib + th);
    land_widget_layout(parent);
    return self;
}
LandWidget* land_widget_submenuitem_new(LandWidget * parent, char const * name, LandWidget * submenu) {
    int tw = land_text_get_width(name);
    int th = land_font_height(land_font_current());
    LandWidget * button = land_widget_menubutton_new(parent, name, submenu, 0, 0, 0, 0);
    LAND_WIDGET_MENU (submenu)->menubutton = button;
    land_widget_theme_initialize(button);
    land_widget_layout_set_minimum_size(button, button->element->il + button->element->ir + tw, button->element->it + button->element->ib + th);
    land_widget_layout(parent);
    return button;
}
LandWidget* land_widget_menu_spacer_new(LandWidget * parent) {
    LandWidget * button = land_widget_box_new(parent, 0, 0, 0, 0);
    land_widget_theme_initialize(button);
    land_widget_layout(parent);
    return button;
}
LandWidget* land_widget_menu_find(LandWidget * super, int n, char const * names []) {
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(super);
    LandList * l = container->children;
    if (l) {
        LandListItem * listitem = l->first;
        while (listitem) {
            if (land_widget_is(listitem->data, LAND_WIDGET_ID_BUTTON)) {
                LandWidgetButton * button = listitem->data;
                if (! strcmp(button->text, names [0])) {
                    if (n <= 1) {
                        return (void *) button;
                    }
                    if (land_widget_is(listitem->data, LAND_WIDGET_ID_MENUBUTTON)) {
                        LandWidgetMenuButton * mb = listitem->data;
                        return land_widget_menu_find(mb->submenu, n - 1, names + 1);
                    }
                }
            }
            listitem = listitem->next;
        }
    }
    return NULL;
}
void land_widget_menu_mouse_enter(LandWidget * self) {
    ;
}
static int is_in_menu(LandWidget * self, LandWidget * other) {
    /* Check if the given menu has other as an ancestor.
     */
    while (self) {
        if (self == other) {
            return 1;
        }
        LandWidgetMenu * menu = LAND_WIDGET_MENU(self);
        LandWidget * buttonwidget = menu->menubutton;
        if (! buttonwidget) {
            break;
        }
        LandWidgetMenuButton * button = LAND_WIDGET_MENUBUTTON(buttonwidget);
        self = button->menu;
    }
    return 0;
}
static int is_related(LandWidget * self, LandWidget * other) {
    /* Check if other is a menu item or menu button related to this menu.
     */
    if (land_widget_is(other, LAND_WIDGET_ID_MENUITEM)) {
        LandWidgetMenuItem * othermenuitem = LAND_WIDGET_MENUITEM(other);
        LandWidget * othermenuwidget = othermenuitem->menu;
        while (othermenuwidget) {
            if (is_in_menu(self, othermenuwidget)) {
                return 1;
            }
            LandWidgetMenu * othermenu = LAND_WIDGET_MENU(othermenuwidget);
            LandWidget * otherbuttonwidget = othermenu->menubutton;
            if (! otherbuttonwidget) {
                break;
            }
            LandWidgetMenuButton * othermenubutton = LAND_WIDGET_MENUBUTTON(otherbuttonwidget);
            othermenuwidget = othermenubutton->menu;
        }
    }
    else if (land_widget_is(other, LAND_WIDGET_ID_MENUBUTTON)) {
        while (other) {
            LandWidgetMenuButton * otherbutton = LAND_WIDGET_MENUBUTTON(other);
            LandWidget * othermenuwidget = otherbutton->menu;
            if (! othermenuwidget) {
                break;
            }
            if (is_in_menu(self, othermenuwidget)) {
                return 1;
            }
            LandWidgetMenu * othermenu = LAND_WIDGET_MENU(othermenuwidget);
            other = othermenu->menubutton;
        }
    }
    return 0;
}
void land_widget_menu_mouse_leave(LandWidget * self) {
    if (self->hidden) {
        return ;
    }
    LandWidget * up = self;
    while (up->parent) {
        up = up->parent;
    }
    LandWidget * target = land_widget_container_get_descendant_at_pos(up, land_mouse_x(), land_mouse_y());
    if (is_related(self, target)) {
        return ;
    }
    land_widget_retain_mouse_focus(self);
}
void land_widget_menubutton_mouse_enter(LandWidget * self) {
    menubutton_clicked(self);
}
void land_widget_menubutton_mouse_leave(LandWidget * self) {
    ;
}
void land_widget_menubar_mouse_leave(LandWidget * self) {
    return ;
}
void land_widget_menu_mouse_tick(LandWidget * self) {
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(self);
    land_widget_container_mouse_tick(self);
    if ((! container->mouse && land_mouse_delta_b() && land_mouse_b())) {
        land_widget_menu_hide_complete(self);
        return ;
    }
}
void land_widget_menu_interface_initialize(void) {
    if (land_widget_menu_interface) {
        return ;
    }
    land_widget_container_interface_initialize();
    land_widget_menu_interface = land_widget_copy_interface(land_widget_container_interface, "menu");
    land_widget_menu_interface->id |= LAND_WIDGET_ID_MENU;
    land_widget_menu_interface->add = land_widget_menu_add;
    land_widget_menu_interface->mouse_enter = land_widget_menu_mouse_enter;
    land_widget_menu_interface->mouse_leave = land_widget_menu_mouse_leave;
    land_widget_menu_interface->mouse_tick = land_widget_menu_mouse_tick;
    land_widget_menubar_interface = land_widget_copy_interface(land_widget_menu_interface, "menubar");
    land_widget_menubar_interface->id |= LAND_WIDGET_ID_MENUBAR;
    land_widget_menubar_interface->add = land_widget_menubar_add;
    land_widget_menubar_interface->mouse_leave = land_widget_menubar_mouse_leave;
}
void land_widget_menubutton_interface_initialize(void) {
    if (land_widget_menubutton_interface) {
        return ;
    }
    land_widget_button_interface_initialize();
    land_widget_menubutton_interface = land_widget_copy_interface(land_widget_button_interface, "menubutton");
    land_widget_menubutton_interface->id |= LAND_WIDGET_ID_MENUBUTTON;
    land_widget_menubutton_interface->mouse_enter = land_widget_menubutton_mouse_enter;
    land_widget_menubutton_interface->mouse_leave = land_widget_menubutton_mouse_leave;
    land_widget_menubutton_interface->destroy = land_widget_menubutton_destroy;
    land_widget_menuitem_interface = land_widget_copy_interface(land_widget_button_interface, "menuitem");
    land_widget_menuitem_interface->id |= LAND_WIDGET_ID_MENUITEM;
}
static LandWidgetInterface * land_widget_sizer_interface [8];
void land_widget_sizer_draw(LandWidget * widget) {
    land_widget_theme_draw(widget);
}
void land_widget_sizer_mouse_tick(LandWidget * super) {
    LandWidgetSizer * self = LAND_WIDGET_SIZER(super);
    if ((land_mouse_delta_b())) {
        if ((land_mouse_b() & 1)) {
            self->dragged = 1;
            self->drag_x = land_mouse_x() - self->target->box.x;
            self->drag_y = land_mouse_y() - self->target->box.y;
        }
        else {
            self->dragged = 0;
        }
    }
    if (((land_mouse_b() & 1) && self->dragged)) {
        float mx = 0, my = 0, sx = 0, sy = 0;
        float dx = land_mouse_x() - self->target->box.x - self->drag_x;
        float dy = land_mouse_y() - self->target->box.y - self->drag_y;
        if (self->position == 0) {
            sy = - dy;
            my = 1;
        }
        else if (self->position == 1) {
            sx = dx;
            sy = - dy;
            my = 1;
        }
        else if (self->position == 2) {
            sx = dx;
        }
        else if (self->position == 3) {
            sx = dx;
            sy = dy;
        }
        else if (self->position == 4) {
            sy = dy;
        }
        else if (self->position == 5) {
            sx = - dx;
            sy = dy;
            mx = 1;
        }
        else if (self->position == 6) {
            sx = - dx;
            mx = 1;
        }
        else if (self->position == 7) {
            sx = - dx;
            sy = - dy;
            mx = 1;
            my = 1;
        }
        float pw = self->target->box.w;
        float ph = self->target->box.h;
        self->target->box.flags |= GUL_RESIZE;
        land_widget_resize(self->target, sx, sy);
        sx = self->target->box.w - pw;
        sy = self->target->box.h - ph;
        land_widget_move(self->target, mx * (- sx), my * (- sy));
        self->drag_x += sx - mx * sx;
        self->drag_y += sy - my * sy;
        self->target->box.flags &= ~ GUL_RESIZE;
    }
}
LandWidget* land_widget_sizer_new(LandWidget * parent, int position, int x, int y, int w, int h) {
    /* Create a new sizer widget. By default it will use its parent as target,
     * and shrink depending on the position parameter.
     */
    LandWidgetSizer * self;
    land_widget_sizer_interface_initialize();
    land_alloc(self);
    LandWidget * super = & self->super;
    land_widget_base_initialize(super, parent, x, y, w, h);
    super->vt = land_widget_sizer_interface [position];
    if (position == 0 || position == 4) {
        land_widget_layout_set_shrinking(super, 0, 1);
    }
    if (position == 2 || position == 6) {
        land_widget_layout_set_shrinking(super, 1, 0);
    }
    else {
        land_widget_layout_set_shrinking(super, 1, 1);
    }
    land_widget_theme_initialize(super);
    if (parent) {
        land_widget_layout(parent);
    }
    self->target = parent;
    self->position = position;
    self->dragged = 0;
    return super;
}
void land_widget_sizer_set_target(LandWidget * self, LandWidget * target) {
    /* This does not set any references, as it is assumed the sizer is a descendant
     * of the target anyway.
     */
    LAND_WIDGET_SIZER (self)->target = target;
}
void land_widget_sizer_interface_initialize(void) {
    if (land_widget_sizer_interface [0]) {
        return ;
    }
    char const * dir [8] = {"t", "rt", "r", "rb", "b", "lb", "l", "lt"};
    for (int i = 0; i < 8; i++) {
        char str [256];
        snprintf(str, sizeof str, "sizer.%s", dir [i]);
        land_widget_sizer_interface [i] = land_widget_copy_interface(land_widget_base_interface, str);
        land_widget_sizer_interface [i]->draw = land_widget_sizer_draw;
        land_widget_sizer_interface [i]->mouse_tick = land_widget_sizer_mouse_tick;
    }
}
static double land_widget_cursor_blink_rate = 2;
static LandWidgetInterface * land_widget_edit_interface;
static int get_x_offset(LandWidget * base) {
    int x = base->box.x + base->element->il;
    LandWidgetEdit * self = LAND_WIDGET_EDIT(base);
    if (self->align_right) {
        int w = land_text_get_width(self->text);
        x = base->box.x + base->box.w - base->element->r - w - 0.5;
    }
    return x;
}
void land_widget_edit_draw(LandWidget * base) {
    LandWidgetEdit * self = LAND_WIDGET_EDIT(base);
    if (! base->no_decoration) {
        land_widget_box_draw(base);
    }
    if (! base->dont_clip) {
        int l = base->box.x + base->element->il;
        int t = base->box.y + base->element->it;
        int r = base->box.x + base->box.w - base->element->r;
        int b = base->box.y + base->box.h - base->element->ib;
        land_clip_push();
        land_clip_intersect(l, t, r, b);
    }
    if (self->text) {
        int x = get_x_offset(base);
        int y = base->box.y + base->box.h - base->element->ib;
        y -= land_font_height(land_font_current());
        land_widget_theme_color(base);
        land_text_pos(x - self->scroll, y);
        land_print(self->text);
        if (base->got_keyboard) {
            double pos = land_get_time() * land_widget_cursor_blink_rate;
            pos -= floor(pos);
            if (pos < 0.5) {
                int cx = land_text_get_char_offset(self->text, self->cursor);
                cx -= self->scroll;
                if (cx < 0) {
                    self->scroll += cx;
                }
                if (cx > base->box.w) {
                    self->scroll += cx - base->box.w;
                }
                land_line(x + cx + 0.5, y, x + cx + 0.5, base->box.y + base->box.h - base->element->ib);
            }
        }
    }
    if (! base->dont_clip) {
        land_clip_pop();
    }
}
void land_widget_edit_mouse_tick(LandWidget * base) {
    LandWidgetEdit * edit = LAND_WIDGET_EDIT(base);
    if (land_mouse_delta_b() & 1) {
        if (land_mouse_b() & 1) {
            base->want_focus = 1;
            int x = land_mouse_x() - get_x_offset(base);
            edit->cursor = land_text_get_char_index(edit->text, x - edit->scroll);
        }
    }
}
#define M \
    if (edit->modified) { \
        edit->modified(base); \
    }
void land_widget_edit_keyboard_tick(LandWidget * base) {
    LandWidgetEdit * edit = LAND_WIDGET_EDIT(base);
    while (! land_keybuffer_empty()) {
        int k, u;
        land_keybuffer_next(& k, & u);
        edit->last_key = k;
        edit->last_char = u;
        if (u > 31 && u != 127) {
            edit->text = land_utf8_realloc_insert(edit->text, edit->cursor, u);
            edit->cursor++;
            M;
        }
        else {
            char * pos = edit->text;
            int l = 0;
            while (land_utf8_char(& pos)) {
                l++;
            }
            if (k == LandKeyLeft) {
                edit->cursor--;
                if (edit->cursor < 0) {
                    edit->cursor = 0;
                }
            }
            else if (k == LandKeyRight) {
                edit->cursor++;
                if (edit->cursor > l) {
                    edit->cursor = l;
                }
            }
            else if (k == LandKeyDelete) {
                if (edit->cursor < l) {
                    edit->text = land_utf8_realloc_remove(edit->text, edit->cursor);
                    M;
                }
            }
            else if (k == LandKeyBackspace) {
                if (edit->cursor > 0) {
                    edit->cursor--;
                    edit->text = land_utf8_realloc_remove(edit->text, edit->cursor);
                    M;
                }
            }
            else if (k == LandKeyHome) {
                edit->cursor = 0;
            }
            else if (k == LandKeyEnd) {
                edit->cursor = l;
            }
            else if (k == LandKeyEnter) {
                if (base->parent) {
                    land_widget_keyboard_leave(base->parent);
                }
                M;
            }
            else if (k == LandKeyUp) {
                M;
            }
            else if (k == LandKeyDown) {
                M;
            }
        }
    }
}
void land_widget_edit_destroy(LandWidget * base) {
    LandWidgetEdit * edit = LAND_WIDGET_EDIT(base);
    if (edit->text) {
        land_free(edit->text);
    }
    land_widget_base_destroy(base);
}
void land_widget_edit_initialize(LandWidget * base, LandWidget * parent, char const * text, void(* modified)(LandWidget * self), int x, int y, int w, int h) {
    land_widget_base_initialize(base, parent, x, y, w, h);
    land_widget_edit_interface_initialize();
    base->vt = land_widget_edit_interface;
    LandWidgetEdit * self = LAND_WIDGET_EDIT(base);
    if (text) {
        self->text = land_strdup(text);
        land_widget_theme_set_minimum_size_for_text(base, text);
        if (base->box.min_width < w) {
            base->box.min_width = w;
        }
    }
    self->modified = modified;
}
LandWidget* land_widget_edit_new(LandWidget * parent, char const * text, void(* modified)(LandWidget * self), int x, int y, int w, int h) {
    LandWidgetEdit * edit;
    land_alloc(edit);
    LandWidget * self = (LandWidget *) edit;
    land_widget_edit_initialize(self, parent, text, modified, x, y, w, h);
    land_widget_theme_initialize(self);
    land_widget_layout(parent);
    return self;
}
void land_widget_edit_set_text(LandWidget * base, char const * text) {
    /* Changes the text of the edit widget to a copy of the given string. The
     * string itself is not touched nor referenced - if you allocated it, you
     * can immediately free it after this function returns.
     */
    LandWidgetEdit * edit = LAND_WIDGET_EDIT(base);
    land_free(edit->text);
    edit->text = land_strdup(text);
    land_widget_theme_set_minimum_size_for_text(base, text);
    if (edit->cursor > land_utf8_count(text)) {
        edit->cursor = land_utf8_count(text);
    }
}
void land_widget_edit_align_right(LandWidget * base, bool yes) {
    LandWidgetEdit * edit = LAND_WIDGET_EDIT(base);
    edit->align_right = yes;
}
char const* land_widget_edit_get_text(LandWidget * base) {
    /* Note: Points directly to the widget's text, only valid as long
     * as the widget is alive.
     */
    LandWidgetEdit * edit = LAND_WIDGET_EDIT(base);
    return edit->text;
}
void land_widget_edit_interface_initialize(void) {
    if (land_widget_edit_interface) {
        return ;
    }
    land_widget_edit_interface = land_widget_copy_interface(land_widget_base_interface, "edit");
    land_widget_edit_interface->id |= LAND_WIDGET_ID_EDIT;
    land_widget_edit_interface->destroy = land_widget_edit_destroy;
    land_widget_edit_interface->draw = land_widget_edit_draw;
    land_widget_edit_interface->mouse_tick = land_widget_edit_mouse_tick;
    land_widget_edit_interface->keyboard_tick = land_widget_edit_keyboard_tick;
}
int land_widget_edit_last_key(LandWidget * self) {
    LandWidgetEdit * edit = LAND_WIDGET_EDIT(self);
    return edit->last_key;
}
#undef M
LandWidgetInterface * land_widget_vbox_interface;
void land_widget_vbox_disable_updates(LandWidget * base) {
    /* Call this before adding *many* items to the vbox, then call
     * land_widget_vbox_update when done. This can speed things up, since there is
     * no need to calculate intermediate layouts for each single added item.
     */
    LAND_WIDGET_VBOX (base)->disable_updates = 1;
}
void land_widget_vbox_do_update(LandWidget * base) {
    /* Update the vbox, after updates have previously been disabled with
     * land_widget_vbox_disable_updates.
     */
    LAND_WIDGET_VBOX (base)->disable_updates = 0;
    land_widget_layout(base);
}
void land_widget_vbox_update(LandWidget * base) {
    /* This is called when a child is done adding itself. It's the earliest time
     * we can calculate the layout.
     * FIXME: the layout also is calculated in the add method, but that's wrong
     */
    land_widget_container_update(base);
    if (! LAND_WIDGET_VBOX (base)->disable_updates) {
        land_widget_layout(base);
    }
}
static void land_widget_vbox_renumber(LandWidget * base) {
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(base);
    LandWidgetVBox * vbox = LAND_WIDGET_VBOX(base);
    if (container->children) {
        int x = 0, y = 0;
        LandListItem * item = container->children->first;
        while (item) {
            LandWidget * child = item->data;
            land_widget_layout_set_grid_position(child, x, y);
            x++;
            if (x == vbox->columns) {
                x = 0;
                y++;
            }
            item = item->next;
        }
    }
    if (! vbox->disable_updates) {
        land_widget_vbox_do_update(base);
    }
}
void land_widget_vbox_add(LandWidget * base, LandWidget * add) {
    /* Add a widget to the vbox. It will be put to the end, going left to right
     * in columns and top to bottom in rows.
     */
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(base);
    LandWidgetVBox * vbox = LAND_WIDGET_VBOX(base);
    land_widget_container_add(base, add);
    int n = container->children->count;
    int rows = (n + vbox->columns - 1) / vbox->columns;
    int row = rows - 1;
    int column = n - row * vbox->columns - 1;
    int layout = land_widget_layout_freeze(base);
    land_widget_layout_set_grid_position(add, column, row);
    land_widget_layout_set_grid(base, vbox->columns, rows);
    if (layout) {
        land_widget_layout_unfreeze(base);
    }
    if (! vbox->disable_updates) {
        land_widget_vbox_do_update(base);
    }
}
void land_widget_vbox_remove(LandWidget * base, LandWidget * rem) {
    /* """Remove a widget from the vbox."""
     */
    int layout = land_widget_layout_freeze(base);
    land_widget_container_remove(base, rem);
    if (layout) {
        land_widget_layout_unfreeze(base);
    }
    land_widget_vbox_renumber(base);
}
void land_widget_vbox_set_columns(LandWidget * base, int n) {
    /* """Specify the number of columns for the vbox. By default, it is 1."""
     */
    LAND_WIDGET_VBOX (base)->columns = n;
}
void land_widget_vbox_initialize(LandWidget * base, LandWidget * parent, int x, int y, int w, int h) {
    /* """Initialize the given vbox widget."""
     */
    land_widget_vbox_interface_initialize();
    LandWidgetVBox * self = (LandWidgetVBox *) base;
    land_widget_container_initialize(base, parent, x, y, w, h);
    base->vt = land_widget_vbox_interface;
    land_widget_layout_enable(base);
    self->columns = 1;
    land_widget_theme_initialize(base);
}
LandWidget* land_widget_vbox_new(LandWidget * parent, int x, int y, int w, int h) {
    /* """Create a new vbox widget."""
     */
    LandWidgetVBox * self;
    land_alloc(self);
    LandWidget * widget = (LandWidget *) self;
    land_widget_vbox_initialize(widget, parent, x, y, w, h);
    return widget;
}
void land_widget_vbox_interface_initialize(void) {
    if (land_widget_vbox_interface) {
        return ;
    }
    land_widget_container_interface_initialize();
    land_widget_vbox_interface = land_widget_copy_interface(land_widget_container_interface, "vbox");
    land_widget_vbox_interface->id |= LAND_WIDGET_ID_VBOX;
    land_widget_vbox_interface->add = land_widget_vbox_add;
    land_widget_vbox_interface->update = land_widget_vbox_update;
    land_widget_vbox_interface->remove = land_widget_vbox_remove;
}
LandWidgetInterface * land_widget_scrollbar_vertical_interface;
LandWidgetInterface * land_widget_scrollbar_horizontal_interface;
static void scroll_vertical_cb(LandWidget * self, bool update_target, int * _scramble_min, int * _scramble_max, int * range, int * pos) {
    /* If update_target is not 0, then the target window is scrolled according to the
     * scrollbar position.
     * If update_target is 0, then the min/max/range/pos parameters are updated.
     */
    LandWidgetScrollbar * bar = LAND_WIDGET_SCROLLBAR(self);
    LandWidget * target = bar->target;
    if (target) {
        LandWidget * viewport = target->parent;
        if (update_target) {
            int ty = viewport->box.y + viewport->element->it;
            if (target->box.y > ty) {
                ty = target->box.y;
            }
            ty -= * pos;
            land_widget_move(target, 0, ty - target->box.y);
        }
        else {
            * _scramble_min = 0;
            * _scramble_max = target->box.h - 1;
            * range = viewport->box.h - viewport->element->it - viewport->element->ib;
            * pos = viewport->box.y + viewport->element->it - target->box.y;
            if (* pos < * _scramble_min) {
                * _scramble_min = * pos;
            }
            if (* pos + * range - 1 > * _scramble_max) {
                * _scramble_max = * pos + * range - 1;
            }
        }
    }
    else {
        if (! update_target) {
            * _scramble_min = 0;
            * _scramble_max = 0;
            * range = 1;
            * pos = 0;
        }
    }
}
static void scroll_horizontal_cb(LandWidget * self, bool update_target, int * _scramble_min, int * _scramble_max, int * range, int * pos) {
    LandWidgetScrollbar * bar = LAND_WIDGET_SCROLLBAR(self);
    LandWidget * target = bar->target;
    if (target) {
        LandWidget * viewport = target->parent;
        if (update_target) {
            int tx = viewport->box.x + viewport->element->il;
            if (target->box.x > tx) {
                tx = target->box.x;
            }
            tx -= * pos;
            land_widget_move(target, tx - target->box.x, 0);
        }
        else {
            * _scramble_min = 0;
            * _scramble_max = target->box.w - 1;
            * range = viewport->box.w - viewport->element->il - viewport->element->ir;
            * pos = viewport->box.x + viewport->element->il - target->box.x;
            if (* pos < * _scramble_min) {
                * _scramble_min = * pos;
            }
            if (* pos + * range - 1 > * _scramble_max) {
                * _scramble_max = * pos + * range - 1;
            }
        }
    }
    else {
        if (! update_target) {
            * _scramble_min = 0;
            * _scramble_max = 0;
            * range = 1;
            * pos = 0;
        }
    }
}
static int get_size(LandWidget * super) {
    LandWidgetScrollbar * self = LAND_WIDGET_SCROLLBAR(super);
    if (self->vertical) {
        return super->box.h;
    }
    else {
        return super->box.w;
    }
}
void land_widget_scrollbar_update(LandWidget * handle, bool update_target) {
    /* If update_target is set, then the target is updated from the scrollbar. Else the
     * scrollbar adjusts to the target's scrolled position.
     */
    LandWidgetScrollbar * self = LAND_WIDGET_SCROLLBAR(handle);
    int minval, maxval, val, valrange;
    int minpos, maxpos, pos, minlen;
    LandWidget * bar_area = handle->parent;
    self->callback(handle, 0, & minval, & maxval, & valrange, & val);
    if (self->vertical) {
        minpos = bar_area->box.y + bar_area->element->it;
        maxpos = bar_area->box.y + bar_area->box.h - bar_area->element->ib - 1;
        pos = handle->box.y;
        minlen = handle->element->minh;
    }
    else {
        minpos = bar_area->box.x + bar_area->element->il;
        maxpos = bar_area->box.x + bar_area->box.w - bar_area->element->ir - 1;
        pos = handle->box.x;
        minlen = handle->element->minw;
    }
    int posrange = 0;
    if (maxval > minval) {
        posrange = (1 + maxpos - minpos) * valrange / (1 + maxval - minval);
    }
    if (posrange < minlen) {
        posrange = minlen;
    }
    if (update_target) {
        maxpos -= posrange - 1;
        maxval -= valrange - 1;
        if (maxpos <= minpos) {
            return ;
        }
        else {
            int rounded = maxpos - minpos - 1;
            val = (minval + (pos - minpos) * (maxval - minval) + rounded) / (maxpos - minpos);
        }
        self->callback(handle, 1, & minval, & maxval, & valrange, & val);
    }
    else {
        maxpos -= posrange - 1;
        maxval -= valrange - 1;
        if (maxval == minval) {
            pos = minpos;
        }
        else {
            pos = minpos + (val - minval) * (maxpos - minpos) / (maxval - minval);
        }
        int dx = 0, dy = 0;
        if (self->vertical) {
            handle->box.w = bar_area->box.w - (bar_area->element->ir + bar_area->element->il);
            handle->box.h = posrange;
            dx = bar_area->box.x + bar_area->element->il - handle->box.x;
            dy = pos - handle->box.y;
        }
        else {
            handle->box.w = posrange;
            handle->box.h = bar_area->box.h - (bar_area->element->ib + bar_area->element->it);
            dx = pos - handle->box.x;
            dy = bar_area->box.y + bar_area->element->it - handle->box.y;
        }
        handle->box.min_width = handle->box.w;
        handle->box.min_height = handle->box.h;
        land_widget_move(handle, dx, dy);
    }
}
void land_widget_scrollbar_draw(LandWidget * self) {
    land_widget_theme_draw(self);
}
void land_widget_scrollbar_mouse_tick(LandWidget * handle) {
    LandWidgetScrollbar * self = LAND_WIDGET_SCROLLBAR(handle);
    if (land_mouse_delta_b()) {
        if (land_mouse_b() & 1) {
            self->drag_x = land_mouse_x() - handle->box.x;
            self->drag_y = land_mouse_y() - handle->box.y;
            self->dragged = 1;
        }
        else {
            self->dragged = 0;
        }
    }
    LandWidget * bar_area = handle->parent;
    if ((land_mouse_b() & 1) && self->dragged) {
        int newx = land_mouse_x() - self->drag_x;
        int newy = land_mouse_y() - self->drag_y;
        int l = bar_area->box.x + bar_area->element->il;
        int t = bar_area->box.y + bar_area->element->it;
        int r = bar_area->box.x + bar_area->box.w - handle->box.w - bar_area->element->ir;
        int b = bar_area->box.y + bar_area->box.h - handle->box.h - bar_area->element->ib;
        if (newx > r) {
            newx = r;
        }
        if (newy > b) {
            newy = b;
        }
        if (newx < l) {
            newx = l;
        }
        if (newy < t) {
            newy = t;
        }
        int dx = newx - handle->box.x;
        int dy = newy - handle->box.y;
        land_widget_move(handle, dx, dy);
        int old_size = get_size(handle);
        land_widget_scrollbar_update(handle, 1);
        land_widget_scrollbar_update(handle, 0);
        int new_size = get_size(handle);
        if (new_size > old_size) {
            if (self->vertical && dy > 0) {
                self->drag_y += new_size - old_size;
            }
            if (! self->vertical && dx > 0) {
                self->drag_x += new_size - old_size;
            }
        }
    }
}
LandWidget* land_widget_scrollbar_new(LandWidget * parent, LandWidget * target, int vertical, int x, int y, int w, int h) {
    LandWidgetScrollbar * self;
    land_widget_scrollbar_interface_initialize();
    land_alloc(self);
    LandWidget * super = & self->super;
    land_widget_base_initialize(super, parent, x, y, w, h);
    self->target = target;
    self->vertical = vertical;
    if (vertical) {
        self->callback = scroll_vertical_cb;
        super->vt = land_widget_scrollbar_vertical_interface;
    }
    else {
        self->callback = scroll_horizontal_cb;
        super->vt = land_widget_scrollbar_horizontal_interface;
    }
    land_widget_theme_initialize(super);
    return super;
}
void land_widget_scrollbar_interface_initialize(void) {
    if (! land_widget_scrollbar_vertical_interface) {
        LandWidgetInterface * i = land_widget_copy_interface(land_widget_base_interface, "scrollbar.vertical");
        i->id = LAND_WIDGET_ID_SCROLLBAR;
        i->draw = land_widget_scrollbar_draw;
        i->move = land_widget_base_move;
        i->mouse_tick = land_widget_scrollbar_mouse_tick;
        land_widget_scrollbar_vertical_interface = i;
    }
    if (! land_widget_scrollbar_horizontal_interface) {
        LandWidgetInterface * i = land_widget_copy_interface(land_widget_base_interface, "scrollbar.horizontal");
        i->id = LAND_WIDGET_ID_SCROLLBAR;
        i->draw = land_widget_scrollbar_draw;
        i->move = land_widget_base_move;
        i->mouse_tick = land_widget_scrollbar_mouse_tick;
        land_widget_scrollbar_horizontal_interface = i;
    }
}
LandWidgetInterface * land_widget_hbox_interface;
void land_widget_hbox_disable_updates(LandWidget * base) {
    LAND_WIDGET_HBOX (base)->disable_updates = 1;
}
void land_widget_hbox_do_update(LandWidget * base) {
    LAND_WIDGET_HBOX (base)->disable_updates = 0;
    land_widget_layout(base);
}
void land_widget_hbox_update(LandWidget * base) {
    land_widget_container_update(base);
    if (! LAND_WIDGET_HBOX (base)->disable_updates) {
        land_widget_layout(base);
    }
}
static void land_widget_hbox_renumber(LandWidget * base) {
    int layout = land_widget_layout_freeze(base);
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(base);
    LandWidgetHBox * hbox = LAND_WIDGET_HBOX(base);
    if (container->children) {
        int x = 0, y = 0;
        LandListItem * item = container->children->first;
        while (item) {
            LandWidget * child = item->data;
            land_widget_layout_set_grid_position(child, x, y);
            y++;
            if (y == hbox->rows) {
                x++;
                y = 0;
            }
            item = item->next;
        }
    }
    if (layout) {
        land_widget_layout_unfreeze(base);
    }
    if (! hbox->disable_updates) {
        land_widget_hbox_do_update(base);
    }
}
void land_widget_hbox_add(LandWidget * base, LandWidget * add) {
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(base);
    LandWidgetHBox * hbox = LAND_WIDGET_HBOX(base);
    land_widget_container_add(base, add);
    int n = container->children->count;
    int columns = (n + hbox->rows - 1) / hbox->rows;
    int column = columns - 1;
    int row = n - column * hbox->rows - 1;
    int f = land_widget_layout_freeze(base);
    land_widget_layout_set_grid_position(add, column, row);
    land_widget_layout_set_grid(base, columns, hbox->rows);
    if (f) {
        land_widget_layout_unfreeze(base);
    }
    if (! hbox->disable_updates) {
        land_widget_hbox_do_update(base);
    }
}
void land_widget_hbox_remove(LandWidget * base, LandWidget * rem) {
    int layout = land_widget_layout_freeze(base);
    land_widget_container_remove(base, rem);
    if (layout) {
        land_widget_layout_unfreeze(base);
    }
    land_widget_hbox_renumber(base);
}
void land_widget_hbox_set_rows(LandWidget * base, int n) {
    LAND_WIDGET_HBOX (base)->rows = n;
}
void land_widget_hbox_initialize(LandWidget * base, LandWidget * parent, int x, int y, int w, int h) {
    land_widget_hbox_interface_initialize();
    LandWidgetHBox * self = (LandWidgetHBox *) base;
    land_widget_container_initialize(base, parent, x, y, w, h);
    base->vt = land_widget_hbox_interface;
    land_widget_layout_enable(base);
    self->rows = 1;
    land_widget_theme_initialize(base);
}
LandWidget* land_widget_hbox_new(LandWidget * parent, int x, int y, int w, int h) {
    LandWidgetHBox * self;
    land_alloc(self);
    LandWidget * widget = (LandWidget *) self;
    land_widget_hbox_initialize(widget, parent, x, y, w, h);
    return widget;
}
void land_widget_hbox_interface_initialize(void) {
    if (land_widget_hbox_interface) {
        return ;
    }
    land_widget_container_interface_initialize();
    land_widget_hbox_interface = land_widget_copy_interface(land_widget_container_interface, "hbox");
    land_widget_hbox_interface->id |= LAND_WIDGET_ID_HBOX;
    land_widget_hbox_interface->add = land_widget_hbox_add;
    land_widget_hbox_interface->remove = land_widget_hbox_remove;
    land_widget_hbox_interface->update = land_widget_hbox_update;
}
LandWidgetInterface * land_widget_scrolling_interface;
static LandWidgetInterface * land_widget_scrolling_contents_container_interface;
static LandWidgetInterface * land_widget_scrolling_vertical_container_interface;
static LandWidgetInterface * land_widget_scrolling_horizontal_container_interface;
LandWidget* land_widget_scrolling_get_container(LandWidget * base) {
    LandList * children = LAND_WIDGET_CONTAINER (base)->children;
    LandWidget * w = children->first->data;
    return w;
}
LandWidget* land_widget_scrolling_get_vertical(LandWidget * base) {
    LandList * children = LAND_WIDGET_CONTAINER (base)->children;
    LandWidget * w = children->first->next->data;
    return w;
}
LandWidget* land_widget_scrolling_get_horizontal(LandWidget * base) {
    LandList * children = LAND_WIDGET_CONTAINER (base)->children;
    LandWidget * w = children->first->next->next->data;
    return w;
}
LandWidget* land_widget_scrolling_get_empty(LandWidget * base) {
    LandList * children = LAND_WIDGET_CONTAINER (base)->children;
    LandWidget * w = children->first->next->next->next->data;
    return w;
}
void land_widget_scrolling_move(LandWidget * widget, float dx, float dy) {
    land_widget_container_move(widget, dx, dy);
}
void land_widget_scrolling_autohide(LandWidget * widget, int hori, int vert, int empty) {
    /* Set hori/vert to 1 if the horizontal/vertical scrollbar should be auto
     * hidden. Set empty to 1 if the empty should be auto hidden, and set empty to
     * 2 if the empty should be hidden as soon as one bar is hidden.
     */
    LandWidgetScrolling * self = LAND_WIDGET_SCROLLING(widget);
    self->autohide = hori + 2 * vert + 4 * empty;
    land_widget_scrolling_update(widget);
}
bool land_widget_scrolling_need_vertical_bar(LandWidget * widget) {
    LandWidget * vbox = land_widget_scrolling_get_vertical(widget);
    return ! vbox->hidden;
}
void land_widget_scrolling_update(LandWidget * widget) {
    _scrolling_layout(widget);
}
void land_widget_scrolling_size(LandWidget * widget, float dx, float dy) {
    if (! (dx || dy)) {
        return ;
    }
    land_widget_scrolling_update(widget);
}
void land_widget_scrolling_get_scroll_position(LandWidget * base, float * x, float * y) {
    LandWidget * contents = LAND_WIDGET_CONTAINER (base)->children->first->data;
    LandWidget * child = land_widget_scrolling_get_child(base);
    if (! child) {
        * x = 0;
        * y = 0;
        return ;
    }
    * x = child->box.x - contents->box.x - contents->element->il;
    * y = child->box.y - contents->box.y - contents->element->it;
}
void land_widget_scrolling_get_scrollable_area(LandWidget * base, float * x, float * y) {
    /* Determines the size of the entire scrollable area.
     */
    LandWidget * child = land_widget_scrolling_get_child(base);
    if (! child) {
        * x = * y = 0;
        return ;
    }
    * x = child->box.w;
    * y = child->box.h;
}
void land_widget_scrolling_get_scroll_extents(LandWidget * base, float * x, float * y) {
    /* Determines the amount, in pixels, the contents could be scrolled.
     */
    LandWidget * contents = land_widget_scrolling_get_container(base);
    LandWidget * child = land_widget_scrolling_get_child(base);
    if (! child) {
        * x = * y = 0;
        return ;
    }
    float cw, ch;
    land_widget_get_inner_size(contents, & cw, & ch);
    * x = child->box.w - cw;
    * y = child->box.h - ch;
    if (* x < 0) {
        * x = 0;
    }
    if (* y < 0) {
        * y = 0;
    }
}
void land_widget_scrolling_get_view(LandWidget * base, float * l, float * t, float * r, float * b) {
    /* Determine pixel coordinates of the viewable area.
     */
    LandWidget * contents = land_widget_scrolling_get_container(base);
    land_widget_inner_extents(contents, l, t, r, b);
}
void land_widget_scrolling_scrollto(LandWidget * base, float x, float y) {
    LandWidget * contents = LAND_WIDGET_CONTAINER (base)->children->first->data;
    LandList * children = LAND_WIDGET_CONTAINER (contents)->children;
    if (! children) {
        return ;
    }
    LandWidget * child = children->first->data;
    land_widget_move(child, contents->box.x + contents->element->il + x - child->box.x, contents->box.y + contents->element->it + y - child->box.y);
    land_widget_scrolling_update(base);
}
void land_widget_scrolling_scroll(LandWidget * base, float dx, float dy) {
    float x, y;
    land_widget_scrolling_get_scroll_position(base, & x, & y);
    land_widget_scrolling_scrollto(base, x + dx, y + dy);
}
void land_widget_scrolling_scroll_to_bottom(LandWidget * base) {
    float x, y;
    land_widget_scrolling_get_scroll_extents(base, & x, & y);
    land_widget_scrolling_scrollto(base, 0, - y);
}
void land_widget_scrolling_limit(LandWidget * base) {
    float x, y, w, h;
    land_widget_scrolling_get_scroll_position(base, & x, & y);
    land_widget_scrolling_get_scroll_extents(base, & w, & h);
    if (y < - h) {
        land_widget_scrolling_scrollto(base, x, - h);
    }
    if (y > 0) {
        land_widget_scrolling_scrollto(base, x, 0);
    }
}
void land_widget_scrolling_mouse_tick(LandWidget * base) {
    LandWidgetScrolling * self = LAND_WIDGET_SCROLLING(base);
    if (land_mouse_delta_z() && self->scrollwheel != 0) {
        if (self->scrollwheel == 1) {
            float w, h;
            land_widget_scrolling_get_scroll_extents(base, & w, & h);
            if (h <= 0) {
                return ;
            }
        }
        int dy = land_mouse_delta_z() * land_font_height(base->element->font);
        land_widget_scrolling_scroll(base, 0, dy);
        if (self->scrollwheel == 1) {
            land_widget_scrolling_limit(base);
        }
    }
    land_widget_container_mouse_tick(base);
}
void land_widget_scrolling_tick(LandWidget * super) {
    ;
}
void land_widget_scrolling_add(LandWidget * widget, LandWidget * add) {
    /* Add a widget to the scrolling widget. The child widget can be bigger than
     * the parent, and scrollbars will appear to allow scrolling around.
     */
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(widget);
    LandListItem * item = container->children->first;
    LandWidget * contents = LAND_WIDGET(item->data);
    land_widget_container_add(contents, add);
    add->box.x = contents->box.x + contents->element->il;
    add->box.y = contents->box.y + contents->element->it;
    item = item->next;
    LandWidgetContainer * right = LAND_WIDGET_CONTAINER(item->data);
    LandListItem * item2 = right->children->first;
    LandWidgetScrollbar * rightbar = LAND_WIDGET_SCROLLBAR(item2->data);
    rightbar->target = add;
    item = item->next;
    LandWidgetContainer * bottom = LAND_WIDGET_CONTAINER(item->data);
    item2 = bottom->children->first;
    LandWidgetScrollbar * bottombar = LAND_WIDGET_SCROLLBAR(item2->data);
    bottombar->target = add;
}
LandWidget* land_widget_scrolling_get_child(LandWidget * base) {
    /* Return the child window of the scrolling window. Usually, a scrolling
     * window has exactly one child window, which is controlled by the scrollbars.
     * That window is returned.
     */
    LandWidget * contents = LAND_WIDGET_CONTAINER (base)->children->first->data;
    LandList * children = LAND_WIDGET_CONTAINER (contents)->children;
    return children ? children->first->data : NULL;
}
void land_widget_scrolling_remove_child(LandWidget * base) {
    /* Detach the window managed inside the scrolled window. If there are no
     * other references to it, it will be destroyed.
     */
    LandList * list = LAND_WIDGET_CONTAINER (base)->children;
    LandWidget * contents = list->first->data;
    LandList * children = LAND_WIDGET_CONTAINER (contents)->children;
    LandWidget * child = children ? children->first->data : NULL;
    if (child) {
        land_widget_container_remove(contents, child);
    }
    LandWidgetContainer * c;
    c = LAND_WIDGET_CONTAINER(list->first->next->data);
    LAND_WIDGET_SCROLLBAR (c->children->first->data)->target = NULL;
    c = LAND_WIDGET_CONTAINER(list->first->next->next->data);
    LAND_WIDGET_SCROLLBAR (c->children->first->data)->target = NULL;
}
void land_widget_scrolling_initialize(LandWidget * widget, LandWidget * parent, int x, int y, int w, int h) {
    land_widget_scrolling_interface_initialize();
    land_widget_container_initialize(widget, parent, x, y, w, h);
    widget->vt = land_widget_container_interface;
    LandWidgetScrolling * scrolling = (void *) widget;
    scrolling->scrollwheel = 1;
    LandWidget * contents = land_widget_container_new(widget, 0, 0, 0, 0);
    contents->vt = land_widget_scrolling_contents_container_interface;
    land_widget_theme_initialize(contents);
    land_widget_layout_disable(contents);
    contents->box.flags |= GUL_STEADFAST;
    LandWidget * right = land_widget_container_new(widget, 0, 0, 0, 0);
    right->vt = land_widget_scrolling_vertical_container_interface;
    land_widget_theme_initialize(right);
    land_widget_scrollbar_new(right, NULL, 1, right->element->il, right->element->it, 0, 0);
    right->box.flags |= GUL_STEADFAST;
    LandWidget * bottom = land_widget_container_new(widget, 0, 0, 0, 0);
    bottom->vt = land_widget_scrolling_horizontal_container_interface;
    land_widget_theme_initialize(bottom);
    land_widget_scrollbar_new(bottom, NULL, 0, bottom->element->il, bottom->element->it, 0, 0);
    bottom->box.flags |= GUL_STEADFAST;
    land_widget_box_new(widget, 0, 0, 0, 0);
    widget->vt = land_widget_scrolling_interface;
    land_widget_theme_initialize(widget);
    _scrolling_layout(widget);
}
static void _scrolling_layout(LandWidget * self) {
    LandWidgetScrolling * scrolling = LAND_WIDGET_SCROLLING(self);
    LandWidget * container = land_widget_scrolling_get_container(self);
    LandWidget * horizontal = land_widget_scrolling_get_horizontal(self);
    LandWidget * vertical = land_widget_scrolling_get_vertical(self);
    LandWidget * empty = land_widget_scrolling_get_empty(self);
    LandWidget * hbar = land_widget_container_child(horizontal);
    LandWidget * vbar = land_widget_container_child(vertical);
    LandWidgetThemeElement * element = land_widget_theme_element(self);
    float l, t, r, b;
    land_widget_inner_extents(self, & l, & t, & r, & b);
    int ver_w = vbar->box.w;
    int hor_h = hbar->box.h;
    int hgap = element->hgap;
    int vgap = element->vgap;
    land_widget_theme_set_minimum_size_for_contents(horizontal, 0, hor_h);
    land_widget_theme_set_minimum_size_for_contents(vertical, ver_w, 0);
    hor_h = horizontal->box.min_height;
    ver_w = vertical->box.min_width;
    land_widget_move_to(container, l, t);
    land_widget_move_to(horizontal, l, b - hor_h);
    land_widget_move_to(vertical, r - ver_w, t);
    land_widget_move_to(empty, r - ver_w, b - hor_h);
    land_widget_set_size(container, r - l, b - t);
    float vw, vh;
    land_widget_get_inner_size(container, & vw, & vh);
    float sx, sy, sw, sh;
    land_widget_scrolling_get_scroll_position(self, & sx, & sy);
    land_widget_scrolling_get_scrollable_area(self, & sw, & sh);
    if (scrolling->fixed_width) {
        sx = 0;
        sw = vw;
    }
    if (scrolling->fixed_height) {
        sy = 0;
        sh = vh;
    }
    float su = 0, sd = 0, sl = 0, sr = 0, sr2 = 0, sd2 = 0;
    if (sx < 0) {
        sl = - sx;
    }
    if (sy < 0) {
        su = - sy;
    }
    if (sx + sw > vw) {
        sr = sx + sw - vw;
    }
    if (sy + sh > vh) {
        sd = sy + sh - vh;
    }
    if (sx + sw > vw - hgap - ver_w) {
        sr2 = sx + sw - vw + hgap + ver_w;
    }
    if (sy + sh > vh - vgap - hor_h) {
        sd2 = sy + sh - vh + vgap + hor_h;
    }
    if (scrolling->fixed_width) {
        sr2 = 0;
    }
    if (scrolling->fixed_height) {
        sd2 = 0;
    }
    int w = r - l;
    int h = b - t;
    if (sl + sr == 0 && su + sd == 0) {
        if (scrolling->autohide & 2) {
            hor_h = 0;
        }
        if (scrolling->autohide & 1) {
            ver_w = 0;
        }
    }
    else if (su + sd > 0 && sl + sr2 == 0) {
        if (scrolling->autohide & 2) {
            hor_h = 0;
        }
    }
    else if (sl + sr > 0 && su + sd2 == 0) {
        if (scrolling->autohide & 1) {
            ver_w = 0;
        }
    }
    else {
        ;
    }
    if (ver_w > 0) {
        w -= hgap + ver_w;
    }
    if (hor_h > 0) {
        h -= vgap + hor_h;
    }
    if (scrolling->fixed_width) {
        LandWidget * child = land_widget_scrolling_get_child(self);
        if (child) {
            land_widget_move(child, l - child->box.x, 0);
            land_widget_resize(child, w - child->box.w, 0);
            hor_h = 0;
        }
    }
    if (scrolling->fixed_height) {
        LandWidget * child = land_widget_scrolling_get_child(self);
        if (child) {
            land_widget_move(child, 0, t - child->box.y);
            land_widget_resize(child, 0, h - child->box.h);
            ver_w = 0;
        }
    }
    land_widget_set_size(container, w, h);
    land_widget_set_size(horizontal, w, hor_h);
    land_widget_set_size(vertical, ver_w, h);
    land_widget_set_size(empty, ver_w, hor_h);
    land_widget_set_hidden(horizontal, hor_h == 0);
    land_widget_set_hidden(vertical, ver_w == 0);
    land_widget_set_hidden(empty, hor_h == 0 || ver_w == 0);
    land_widget_scrollbar_update(hbar, 0);
    land_widget_scrollbar_update(vbar, 0);
}
void land_widget_scrolling_wheel(LandWidget * widget, int wheel) {
    /* 0: no wheel scrolling
     * 1: wheel scrolling [default]
     * 2: unlimited scrolling
     */
    LandWidgetScrolling * self = LAND_WIDGET_SCROLLING(widget);
    self->scrollwheel = wheel;
}
LandWidget* land_widget_scrolling_new(LandWidget * parent, int x, int y, int w, int h) {
    /* Creates a new Scrolling widget. You can add a child widget to it, and it
     * will automatically display scrollbars and translate mouse coordinates.
     * By default, the widget will expand in all directions.
     */
    LandWidgetScrolling * self;
    land_alloc(self);
    LandWidget * widget = (LandWidget *) self;
    land_widget_scrolling_initialize(widget, parent, x, y, w, h);
    return widget;
}
void land_widget_scrolling_layout_changed(LandWidget * widget) {
    if (widget->box.w != widget->box.ow || widget->box.h != widget->box.oh) {
        _scrolling_layout(widget);
    }
}
void land_widget_scrolling_layout_changing(LandWidget * widget) {
    ;
}
void land_widget_scrolling_interface_initialize(void) {
    if (land_widget_scrolling_interface) {
        return ;
    }
    land_widget_container_interface_initialize();
    land_widget_scrolling_interface = land_widget_copy_interface(land_widget_container_interface, "scrolling");
    land_widget_scrolling_interface->id |= LAND_WIDGET_ID_SCROLLING;
    land_widget_scrolling_interface->tick = land_widget_scrolling_tick;
    land_widget_scrolling_interface->add = land_widget_scrolling_add;
    land_widget_scrolling_interface->update = land_widget_scrolling_update;
    land_widget_scrolling_interface->move = land_widget_scrolling_move;
    land_widget_scrolling_interface->size = land_widget_scrolling_size;
    land_widget_scrolling_interface->layout_changed = land_widget_scrolling_layout_changed;
    land_widget_scrolling_interface->layout_changing = land_widget_scrolling_layout_changing;
    land_widget_scrolling_interface->mouse_tick = land_widget_scrolling_mouse_tick;
    land_widget_scrolling_contents_container_interface = land_widget_copy_interface(land_widget_container_interface, "scrolling.contents.container");
    land_widget_scrolling_vertical_container_interface = land_widget_copy_interface(land_widget_container_interface, "scrolling.vertical.container");
    land_widget_scrolling_horizontal_container_interface = land_widget_copy_interface(land_widget_container_interface, "scrolling.horizontal.container");
}
void land_widget_scrolling_set_fixed_width(LandWidget * widget, bool yes) {
    /* If this is set there will not be a horizontal scrollbar, instead
     * the width of the child will always be set to fit the width exactly.
     */
    LandWidgetScrolling * self = LAND_WIDGET_SCROLLING(widget);
    self->fixed_width = 1;
}
void land_widget_scrolling_set_fixed_height(LandWidget * widget, bool yes) {
    /* If this is set there will not be a vertical scrollbar, instead
     * the height of the child will always be set to fit the height exactly.
     */
    LandWidgetScrolling * self = LAND_WIDGET_SCROLLING(widget);
    self->fixed_height = 1;
}
static LandWidgetInterface * land_widget_book_interface;
static LandWidgetInterface * land_widget_tab_interface;
static LandWidgetInterface * land_widget_tab_l_interface;
static LandWidgetInterface * land_widget_tab_m_interface;
static LandWidgetInterface * land_widget_tab_r_interface;
static LandWidgetInterface * land_widget_tabbar_interface;
static LandWidgetInterface * land_widget_bookpage_interface;
static LandWidgetInterface * land_widget_bookpage_tabless_interface;
void land_widget_book_initialize(LandWidget * base, LandWidget * parent, int x, int y, int w, int h) {
    land_widget_book_interface_initialize();
    land_widget_container_initialize(base, parent, x, y, w, h);
    land_widget_layout_enable(base);
    LandWidget * page = land_widget_hbox_new(base, 0, 0, 10, 10);
    page->vt = land_widget_bookpage_interface;
    land_widget_theme_initialize(page);
    LandWidget * tabbar = land_widget_hbox_new(base, 0, 0, 10, 10);
    tabbar->dont_clip = 1;
    tabbar->vt = land_widget_tabbar_interface;
    land_widget_theme_initialize(tabbar);
    land_widget_layout_set_grid(base, 1, 2);
    land_widget_layout_set_grid_position(tabbar, 0, 0);
    land_widget_layout_set_grid_position(page, 0, 1);
    land_widget_layout_set_shrinking(tabbar, 0, 1);
    base->vt = land_widget_book_interface;
    land_widget_theme_initialize(base);
    land_call_method(parent, update, (parent));
}
void land_widget_book_show_page(LandWidget * self, LandWidget * page) {
    /* Change the visible page of the notebook. If ''page'' is None or not a
     * child of the notebook, then an empty tab will be shown.
     */
    LandWidgetContainer * book = LAND_WIDGET_CONTAINER(self);
    LandWidgetContainer * panel = LAND_WIDGET_CONTAINER(book->children->first->data);
    LandWidgetContainer * tabbar = LAND_WIDGET_CONTAINER(book->children->first->next->data);
    LandListItem * panelitem = panel->children->first;
    LandListItem * tabitem = tabbar->children->first;
    while (panelitem) {
        land_widget_hide(panelitem->data);
        LAND_WIDGET (tabitem->data)->selected = 0;
        panelitem = panelitem->next;
        tabitem = tabitem->next;
    }
    panelitem = panel->children->first;
    tabitem = tabbar->children->first;
    while (panelitem) {
        LandWidget * tab = LAND_WIDGET(tabitem->data);
        if (panelitem->data == page) {
            LandWidget * tabpanel = LAND_WIDGET(panelitem->data);
            land_widget_unhide(tabpanel);
            land_call_method(tab, update, (tabpanel));
            tab->selected = 1;
        }
        if (tabitem == tabbar->children->first) {
            if (tabitem->next) {
                tab->vt = land_widget_tab_l_interface;
            }
            else {
                tab->vt = land_widget_tab_interface;
            }
        }
        else if (tabitem->next) {
            tab->vt = land_widget_tab_m_interface;
        }
        else {
            tab->vt = land_widget_tab_r_interface;
        }
        land_widget_theme_update(tab);
        panelitem = panelitem->next;
        tabitem = tabitem->next;
    }
    land_widget_layout(LAND_WIDGET(book));
}
void land_widget_book_remove_page(LandWidget * widget, LandWidget * rem) {
    /* """Remove a widget from the book."""
     */
    LandWidgetContainer * book = LAND_WIDGET_CONTAINER(widget);
    LandWidgetContainer * panel = LAND_WIDGET_CONTAINER(book->children->first->data);
    LandWidgetContainer * tabbar = LAND_WIDGET_CONTAINER(book->children->first->next->data);
    LandListItem * panelitem = panel->children->first;
    LandListItem * tabitem = tabbar->children->first;
    while (panelitem) {
        if (panelitem->data == rem) {
            LandWidget * tab = tabitem->data;
            land_widget_remove(rem);
            land_widget_remove(tab);
            break;
        }
        panelitem = panelitem->next;
        tabitem = tabitem->next;
    }
    land_widget_book_show_page(widget, land_widget_book_get_current_page(widget));
}
static void clicked(LandWidget * button) {
    LandWidgetContainer * hbox = LAND_WIDGET_CONTAINER(button->parent);
    LandWidgetContainer * book = LAND_WIDGET_CONTAINER(button->parent->parent);
    LandWidgetContainer * panel = LAND_WIDGET_CONTAINER(book->children->first->data);
    LandListItem * panelitem = panel->children->first;
    LandListItem * item = hbox->children->first;
    while (item) {
        if (item->data == button) {
            land_widget_book_show_page(button->parent->parent, panelitem->data);
            break;
        }
        item = item->next;
        panelitem = panelitem->next;
    }
}
void land_widget_book_add(LandWidget * widget, LandWidget * add) {
    /* Add a new item to the notebook. This will not make it visible yet, use
     * [land_widget_book_show_page] for that.
     */
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(widget);
    LandWidget * tabbar = container->children->first->next->data;
    LandWidget * panel = LAND_WIDGET(container->children->first->data);
    LandWidget * tab = land_widget_button_new(tabbar, "", clicked, 0, 0, 10, 10);
    tab->vt = land_widget_tab_interface;
    land_widget_theme_initialize(tab);
    land_widget_container_add(panel, add);
    land_widget_hide(add);
    land_widget_layout_set_grid(panel, 1, 1);
    land_widget_layout_set_grid_position(add, 0, 0);
}
void land_widget_book_pagename(LandWidget * widget, char const * name) {
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(widget);
    LandWidgetContainer * hbox = LAND_WIDGET_CONTAINER(container->children->first->next->data);
    LandWidget * button = hbox->children->last->data;
    land_widget_button_set_text(button, name);
}
LandWidget* land_widget_book_new(LandWidget * parent, int x, int y, int w, int h) {
    LandWidgetBook * self;
    land_alloc(self);
    land_widget_book_initialize((LandWidget *) self, parent, x, y, w, h);
    return LAND_WIDGET(self);
}
LandWidget* land_widget_book_get_tabbar(LandWidget * widget) {
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(widget);
    LandWidget * tabbar = container->children->first->next->data;
    return tabbar;
}
void land_widget_book_hide_tabbar(LandWidget * widget) {
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(widget);
    LandWidget * tabbar = land_widget_book_get_tabbar(widget);
    LandWidget * panel = LAND_WIDGET(container->children->first->data);
    land_widget_hide(tabbar);
    panel->vt = land_widget_bookpage_tabless_interface;
    land_widget_theme_update(panel);
}
void land_widget_book_interface_initialize(void) {
    if (land_widget_book_interface) {
        return ;
    }
    land_widget_container_interface_initialize();
    land_widget_book_interface = land_widget_copy_interface(land_widget_container_interface, "book");
    land_widget_book_interface->id |= LAND_WIDGET_ID_BOOK;
    land_widget_book_interface->add = land_widget_book_add;
    land_widget_button_interface_initialize();
    land_widget_tab_interface = land_widget_copy_interface(land_widget_button_interface, "tab");
    land_widget_tab_interface->id |= LAND_WIDGET_ID_TAB;
    land_widget_tab_l_interface = land_widget_copy_interface(land_widget_tab_interface, "tab.l");
    land_widget_tab_m_interface = land_widget_copy_interface(land_widget_tab_interface, "tab.m");
    land_widget_tab_r_interface = land_widget_copy_interface(land_widget_tab_interface, "tab.r");
    land_widget_hbox_interface_initialize();
    land_widget_tabbar_interface = land_widget_copy_interface(land_widget_hbox_interface, "tabbar");
    land_widget_tabbar_interface->id |= LAND_WIDGET_ID_TABBAR;
    land_widget_bookpage_interface = land_widget_copy_interface(land_widget_hbox_interface, "bookpage");
    land_widget_bookpage_interface->id |= LAND_WIDGET_ID_BOOKPAGE;
    land_widget_bookpage_tabless_interface = land_widget_copy_interface(land_widget_bookpage_interface, "bookpage.tabless");
}
LandWidget* land_widget_book_get_current_page(LandWidget * self) {
    LandWidgetContainer * book = LAND_WIDGET_CONTAINER(self);
    LandWidgetContainer * panel = LAND_WIDGET_CONTAINER(book->children->first->data);
    LandListItem * panelitem = panel->children->first;
    while (panelitem) {
        LandWidget * page = LAND_WIDGET(panelitem->data);
        if (! page->hidden) {
            return page;
        }
        panelitem = panelitem->next;
    }
    return NULL;
}
int land_widget_book_get_page_n(LandWidget * self, LandWidget * page) {
    LandWidgetContainer * book = LAND_WIDGET_CONTAINER(self);
    LandWidgetContainer * panel = LAND_WIDGET_CONTAINER(book->children->first->data);
    LandListItem * panelitem = panel->children->first;
    int i = 0;
    while (panelitem) {
        LandWidget * page_ = LAND_WIDGET(panelitem->data);
        if (page_ == page) {
            return i;
        }
        panelitem = panelitem->next;
        i++;
    }
    return - 1;
}
LandWidget* land_widget_book_get_last_page(LandWidget * self) {
    LandWidgetContainer * book = LAND_WIDGET_CONTAINER(self);
    LandWidgetContainer * panel = LAND_WIDGET_CONTAINER(book->children->first->data);
    LandListItem * panelitem = panel->children->last;
    if (panelitem) {
        return LAND_WIDGET(panelitem->data);
    }
    return NULL;
}
LandWidget* land_widget_book_get_nth_page(LandWidget * self, int n) {
    LandWidgetContainer * book = LAND_WIDGET_CONTAINER(self);
    LandWidgetContainer * panel = LAND_WIDGET_CONTAINER(book->children->first->data);
    LandListItem * panelitem = panel->children->first;
    int i = 0;
    while (panelitem) {
        LandWidget * page = LAND_WIDGET(panelitem->data);
        if (i == n) {
            return page;
        }
        panelitem = panelitem->next;
        i++;
    }
    return NULL;
}
void land_widget_book_show_nth(LandWidget * self, int n) {
    LandWidget * page = land_widget_book_get_nth_page(self, n);
    if (page) {
        land_widget_book_show_page(self, page);
    }
}
static LandWidgetInterface * land_widget_list_interface;
static LandWidgetInterface * land_widget_listitem_interface;
void land_widget_list_disable_updates(LandWidget * base) {
    land_widget_vbox_disable_updates(base);
}
void land_widget_list_update(LandWidget * base) {
    land_widget_vbox_update(base);
}
void land_widget_list_set_columns(LandWidget * base, int n) {
    land_widget_vbox_set_columns(base, n);
}
void land_widget_list_initialize(LandWidget * base, LandWidget * parent, int x, int y, int w, int h) {
    land_widget_list_interface_initialize();
    land_widget_vbox_initialize(base, parent, x, y, w, h);
    base->vt = land_widget_list_interface;
    land_widget_theme_initialize(base);
    land_call_method(parent, update, (parent));
}
LandWidget* land_widget_list_new(LandWidget * parent, int x, int y, int w, int h) {
    LandWidgetList * self;
    land_alloc(self);
    LandWidget * widget = (LandWidget *) self;
    land_widget_list_initialize(widget, parent, x, y, w, h);
    return widget;
}
LandWidget* land_widget_listitem_new(LandWidget * parent, char const * text, void(* clicked)(LandWidget * self), int x, int y, int w, int h) {
    LandWidget * self = land_widget_button_new(parent, text, clicked, x, y, w, h);
    land_widget_listitem_interface_initialize();
    self->vt = land_widget_listitem_interface;
    land_widget_theme_initialize(self);
    land_widget_layout(parent);
    return self;
}
void land_widget_list_interface_initialize(void) {
    if (land_widget_list_interface) {
        return ;
    }
    land_widget_vbox_interface_initialize();
    land_widget_list_interface = land_widget_copy_interface(land_widget_vbox_interface, "list");
    land_widget_list_interface->id |= LAND_WIDGET_ID_LIST;
}
void land_widget_listitem_interface_initialize(void) {
    if (land_widget_listitem_interface) {
        return ;
    }
    land_widget_button_interface_initialize();
    land_widget_listitem_interface = land_widget_copy_interface(land_widget_button_interface, "listitem");
    land_widget_listitem_interface->id |= LAND_WIDGET_ID_LISTITEM;
}
LandArray* land_widget_list_get_selected_items(LandWidget * self) {
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(self);
    LandArray * array = land_array_new();
    LandListItem * item;
    for (item = container->children->first; item; item = item->next) {
        LandWidget * child = item->data;
        if (child->selected) {
            land_array_add_data(& array, child);
        }
    }
    return array;
}
void land_widget_list_clear_selection(LandWidget * self) {
    LandWidgetContainer * container = LAND_WIDGET_CONTAINER(self);
    LandListItem * item;
    for (item = container->children->first; item; item = item->next) {
        LandWidget * child = item->data;
        if (child->selected) {
            child->selected = 0;
        }
    }
}
LandWidgetInterface * land_widget_scrolling_text_interface;
void land_widget_scrolling_text_initialize(LandWidget * base, LandWidget * parent, char const * text, int wordwrap, int x, int y, int w, int h) {
    land_widget_scrolling_initialize(base, parent, x, y, w, h);
    land_widget_scrolling_text_interface_initialize();
    base->vt = land_widget_scrolling_text_interface;
    land_widget_theme_initialize(base);
    LandWidget * container = land_widget_scrolling_get_container(base);
    float cx, cy, cw, ch;
    land_widget_inner(container, & cx, & cy, & cw, & ch);
    land_widget_text_new(base, text, wordwrap, cx, cy, cw, ch);
}
LandWidget* land_widget_scrolling_text_new(LandWidget * parent, char const * text, int wordwrap, int x, int y, int w, int h) {
    LandWidgetScrollingText * self;
    land_alloc(self);
    LandWidget * widget = (LandWidget *) self;
    land_widget_scrolling_text_initialize(widget, parent, text, wordwrap, x, y, w, h);
    return widget;
}
void land_widget_scrolling_text_size(LandWidget * widget, float dx, float dy) {
    if (! (dx || dy)) {
        return ;
    }
    LandWidget * container = land_widget_scrolling_get_container(widget);
    float cx, cy, cw, ch;
    land_widget_layout(widget);
    land_widget_inner(container, & cx, & cy, & cw, & ch);
    LandWidget * button = land_widget_container_child(container);
    land_widget_size(button, cw - button->box.w, ch - button->box.h);
    land_widget_scrolling_size(widget, dx, dy);
}
void land_widget_scrolling_text_interface_initialize(void) {
    if (land_widget_scrolling_text_interface) {
        return ;
    }
    land_widget_scrolling_interface_initialize();
    land_widget_scrolling_text_interface = land_widget_copy_interface(land_widget_scrolling_interface, "scrolling");
    land_widget_scrolling_text_interface->id |= LAND_WIDGET_ID_SCROLLING_TEXT;
    land_widget_scrolling_text_interface->size = land_widget_scrolling_text_size;
}
LandWidget* land_widget_scrolling_text_get_text_widget(LandWidget * widget) {
    return land_widget_scrolling_get_child(widget);
}
static LandWidgetInterface * land_widget_spin_interface;
static LandWidgetInterface * land_widget_spinbutton_interface;
static LandImage * image_up;
static LandImage * image_down;
LandWidget* land_widget_spin_get_edit(LandWidget * spin) {
    LandListItem * item = LAND_WIDGET_CONTAINER (spin)->children->first;
    LandWidget * edit = item->data;
    return edit;
}
static void updated(LandWidget * base) {
    LandListItem * item = LAND_WIDGET_CONTAINER (base)->children->first;
    LandWidgetEdit * edit = LAND_WIDGET_EDIT(item->data);
    if (edit->modified) {
        edit->modified(LAND_WIDGET(edit));
    }
}
LandWidget* land_widget_spinbutton_new(LandWidget * parent, LandImage * image, void(* clicked)(LandWidget * self), int x, int y, int w, int h) {
    LandWidgetSpinButton * spinbutton;
    land_alloc(spinbutton);
    LandWidget * self = (LandWidget *) spinbutton;
    land_widget_button_initialize(self, parent, NULL, image, false, clicked, x, y, w, h);
    land_widget_spinbutton_interface_initialize();
    self->vt = land_widget_spinbutton_interface;
    land_widget_theme_initialize(self);
    land_widget_theme_set_minimum_size_for_image(self, image);
    return self;
}
void land_widget_spin_initialize(LandWidget * base, LandWidget * parent, float val, float _scramble_min, float _scramble_max, float step, void(* modified)(LandWidget * self), int x, int y, int w, int h) {
    land_widget_spin_interface_initialize();
    land_widget_hbox_initialize(base, parent, x, y, w, h);
    base->vt = land_widget_spin_interface;
    LandWidgetSpin * spin = LAND_WIDGET_SPIN(base);
    spin->_scramble_min = _scramble_min;
    spin->_scramble_max = _scramble_max;
    spin->step = step;
    LandWidget * edit = land_widget_edit_new(base, "", modified, 0, 0, 1, 1);
    land_widget_edit_align_right(edit, true);
    LandWidget * spinner = land_widget_vbox_new(base, 0, 0, 1, 1);
    LandWidgetSpinButton * buttonup = LAND_WIDGET_SPINBUTTON(land_widget_spinbutton_new(spinner, image_up, NULL, 0, 0, 1, 1));
    buttonup->initial_delay = 0.25;
    buttonup->rate = 0.1;
    buttonup->spin = base;
    buttonup->dir = 1;
    LandWidgetSpinButton * buttondown = LAND_WIDGET_SPINBUTTON(land_widget_spinbutton_new(spinner, image_down, NULL, 0, 0, 1, 1));
    buttondown->initial_delay = 0.25;
    buttondown->rate = 0.1;
    buttondown->spin = base;
    buttondown->dir = - 1;
    land_widget_layout_set_shrinking(LAND_WIDGET(buttonup), 1, 0);
    land_widget_layout_set_shrinking(LAND_WIDGET(buttondown), 1, 0);
    land_widget_layout_set_shrinking(spinner, 1, 0);
    land_widget_layout_set_expanding(edit, 1, 0);
    land_widget_spin_set_value(base, val);
    land_widget_layout_set_expanding(base, 1, 0);
    land_widget_theme_initialize(base);
    if (parent) {
        land_widget_layout(parent);
    }
}
LandWidget* land_widget_spin_new(LandWidget * parent, float val, float _scramble_min, float _scramble_max, float step, void(* modified)(LandWidget * self), int x, int y, int w, int h) {
    LandWidgetSpin * spin;
    land_alloc(spin);
    LandWidget * self = (LandWidget *) spin;
    land_widget_spin_initialize(self, parent, val, _scramble_min, _scramble_max, step, modified, x, y, w, h);
    return self;
}
void land_widget_spin_set_value(LandWidget * base, float val) {
    LandWidgetSpin * spin = LAND_WIDGET_SPIN(base);
    if (spin->wrap) {
        if (val < spin->_scramble_min) {
            val += spin->_scramble_max - spin->_scramble_min;
        }
        if (val > spin->_scramble_max) {
            val -= spin->_scramble_max - spin->_scramble_min;
        }
    }
    else {
        if (val < spin->_scramble_min && spin->_scramble_min < spin->_scramble_max) {
            val = spin->_scramble_min;
        }
        if (val > spin->_scramble_max && spin->_scramble_max > spin->_scramble_min) {
            val = spin->_scramble_max;
        }
    }
    LandListItem * item = LAND_WIDGET_CONTAINER (base)->children->first;
    LandWidget * edit = LAND_WIDGET(item->data);
    char text [256];
    char format [256];
    if (spin->step > 0 && spin->step < 1) {
        snprintf(format, sizeof format, "%%.%df", (int)(0.9 - log10(spin->step)));
    }
    else {
        strcpy(format, "%.0f");
    }
    snprintf(text, sizeof text, format, val);
    land_widget_edit_set_text(edit, text);
}
void land_widget_spin_set_minimum_text(LandWidget * base, char const * text) {
    LandListItem * item = LAND_WIDGET_CONTAINER (base)->children->first;
    LandWidget * edit = LAND_WIDGET(item->data);
    land_widget_theme_set_minimum_size_for_text(edit, text);
}
float land_widget_spin_get_value(LandWidget * base) {
    LandWidgetSpin * spin = LAND_WIDGET_SPIN(base);
    LandListItem * item = LAND_WIDGET_CONTAINER (base)->children->first;
    LandWidget * edit = LAND_WIDGET(item->data);
    char const * text = land_widget_edit_get_text(edit);
    float val = strtod(text, NULL);
    if (val < spin->_scramble_min && spin->_scramble_min < spin->_scramble_max) {
        val = spin->_scramble_min;
    }
    if (val > spin->_scramble_max && spin->_scramble_max > spin->_scramble_min) {
        val = spin->_scramble_max;
    }
    return val;
}
static void spinning(LandWidget * widget, float amount) {
    LandWidget * super = widget->parent->parent;
    float val = land_widget_spin_get_value(super);
    LandWidgetSpin * spin = LAND_WIDGET_SPIN(super);
    val += amount;
    land_widget_spin_set_value(LAND_WIDGET(spin), val);
    updated(super);
}
void land_widget_spinbutton_mouse_tick(LandWidget * base) {
    LandWidgetSpinButton * spinbutton = LAND_WIDGET_SPINBUTTON(base);
    LandWidgetSpin * spin = LAND_WIDGET_SPIN(spinbutton->spin);
    if ((land_mouse_delta_b() & 1)) {
        if ((land_mouse_b() & 1)) {
            spinbutton->seconds = land_get_time();
            spinbutton->delay = spinbutton->initial_delay;
            spinbutton->count = 0;
            spinbutton->step = spin->step * spinbutton->dir;
            spinning(base, spinbutton->step);
        }
    }
    else {
        if ((land_mouse_b() & 1)) {
            double seconds = land_get_time();
            if (seconds > spinbutton->seconds + spinbutton->delay) {
                spinbutton->seconds = seconds;
                spinbutton->delay = spinbutton->rate;
                spinbutton->count++;
                if (spinbutton->count >= 20) {
                    spinbutton->step *= 10;
                    spinbutton->count = 0;
                }
                spinning(base, spinbutton->step);
            }
        }
    }
}
void land_widget_spinbutton_interface_initialize(void) {
    if (land_widget_spinbutton_interface) {
        return ;
    }
    land_widget_button_interface_initialize();
    land_widget_spinbutton_interface = land_widget_copy_interface(land_widget_button_interface, "spinbutton");
    land_widget_spinbutton_interface->id |= LAND_WIDGET_ID_SPINBUTTON;
    land_widget_spinbutton_interface->mouse_tick = land_widget_spinbutton_mouse_tick;
}
void land_widget_spin_interface_initialize(void) {
    if (land_widget_spin_interface) {
        return ;
    }
    land_widget_hbox_interface_initialize();
    land_widget_spin_interface = land_widget_copy_interface(land_widget_hbox_interface, "spin");
    land_widget_spin_interface->id |= LAND_WIDGET_ID_SPIN;
    image_up = land_image_create(2, 2);
    image_down = land_image_create(2, 2);
}
