From c5455ef0fbfc4203a4aa8ad185dfa43bdadc0b82 Mon Sep 17 00:00:00 2001 From: Leander Scherer Date: Sun, 8 Mar 2026 20:04:08 +0100 Subject: feat(deps): add raylib and raytmx dependencies --- include/raytmx.h | 4815 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 4815 insertions(+) create mode 100644 include/raytmx.h (limited to 'include/raytmx.h') diff --git a/include/raytmx.h b/include/raytmx.h new file mode 100644 index 0000000..f446272 --- /dev/null +++ b/include/raytmx.h @@ -0,0 +1,4815 @@ +/* +Copyright (c) 2024-2026 Luke Philipsen + +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/* Usage + + Do this: + #define RAYTMX_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + You can define RAYTMX_DEC with + #define RAYTMX_DEC static + or + #define RAYTMX_DEC extern + to specify raytmx function declarations as static or extern, respectively. + The default specifier is extern. +*/ + +#ifndef RAYTMX_H + #define RAYTMX_H + +#include /* isspace() */ +#include /* floor(), INFINITY */ +#include /* NULL */ +#include /* int32_t, uint32_t */ +#include /* atoi(), strtoul() */ +#include /* memcpy(), memset(), strcpy(), strcpy_s() strlen(), strncpy(), strncpy_s() */ + +#include "raylib.h" +#include "rlgl.h" + +#ifndef RAYTMX_DEC + #define RAYTMX_DEC +#endif /* RAYTMX_DEC */ + +#ifdef __cplusplus + extern "C" { +#endif /* __cpluspus */ + +/***************/ +/* Definitions */ + +/** + * Function signature of raylib's LoadTexture() as a type. For use with SetLoadTextureTMX(). + */ +typedef Texture2D (*LoadTextureCallback)(const char* fileName); + +/** + * Bit flags passed to SetTraceLogFlagsTMX() that optionally disable the logging of specific TMX elements. + */ +enum tmx_log_flags { + LOG_SKIP_PROPERTIES = 1, /**< Skip and child elements. */ + LOG_SKIP_LAYERS = 2, /**< Skip , , , and layers and children thereof. */ + LOG_SKIP_TILE_LAYERS = 4, /**< Skip layers and children thereof. */ + LOG_SKIP_TILES = 8, /**< Skip tiles (GIDs) of tile layers (s). */ + LOG_SKIP_OBJECT_GROUPS = 16, /**< Skip layers and children thereof. */ + LOG_SKIP_OBJECTS = 32, /**< Skip objects of object layers (s). */ + LOG_SKIP_IMAGE_LAYERS = 64, /**< Skip layers and children thereof. */ + LOG_SKIP_IMAGES = 128, /**< Skip images of image layers (s). */ + LOG_SKIP_WANG_SETS = 256, /**< Skip and child elements. */ + LOG_SKIP_WANG_TILES = 512 /**< Skip Wang tiles of Wang sets (s). */ +}; + +/** + * Identifiers for the possible layer types. + */ +typedef enum tmx_layer_type { + LAYER_TYPE_TILE_LAYER = 0, /**< Layer containing a set number of tiles referenced by Global IDs (GIDs). */ + LAYER_TYPE_OBJECT_GROUP, /**< Layer containing an arbitrary number of various object types. */ + LAYER_TYPE_IMAGE_LAYER, /**< Layer consisting of a single image. */ + LAYER_TYPE_GROUP /**< Container layer, with no visuals of its own, that holds other, child layers. */ +} TmxLayerType; + +/** + * Identifiers for the possible property (data) types. + */ +typedef enum tmx_property_type { + PROPERTY_TYPE_STRING = 0, /**< String, or a sequence of characters. */ + PROPERTY_TYPE_INT, /**< Integer number. */ + PROPERTY_TYPE_FLOAT, /**< Floating-point number. */ + PROPERTY_TYPE_BOOL, /**< Boolean, true or false. */ + PROPERTY_TYPE_COLOR, /**< Color with red, green, and blue values and a possible alpha value. */ + PROPERTY_TYPE_FILE, /**< File name or path to a file as a string. */ + PROPERTY_TYPE_OBJECT /**< Object () within the map as an integer ID. */ +} TmxPropertyType; + +/** + * Identifiers for the possible draw orders applicable to object layers. + */ +typedef enum tmx_object_group_draw_order { + OBJECT_GROUP_DRAW_ORDER_TOP_DOWN = 0, /**< Drawn in ascending order by y-coordinate. */ + OBJECT_GROUP_DRAW_ORDER_INDEX /**< Drawn in the order in which the appear in the document. */ +} TmxObjectGroupDrawOrder; + +/** + * Identifiers for the possible alignments of tiles with an object's bounds. + */ +typedef enum tmx_object_alignment { + OBJECT_ALIGNMENT_UNSPECIFIED = 0, /**< For orthogonal, behaves like bottom-left. For isometric, like bottm. */ + OBJECT_ALIGNMENT_TOP_LEFT, /**< Tiles are snapped to the upper-left bound of objects. */ + OBJECT_ALIGNMENT_TOP, /**< Tiles are snapped to the upper-center bound of objects. */ + OBJECT_ALIGNMENT_TOP_RIGHT, /**< Tiles are snapped to the upper-right bound of objects. */ + OBJECT_ALIGNMENT_LEFT, /**< Tiles are snapped to the left-center bound of objects. */ + OBJECT_ALIGNMENT_CENTER, /**< Tiles are snapped to the horizontal and vertical center of objects. */ + OBJECT_ALIGNMENT_RIGHT, /**< Tiles are snapped to the right-center bound of objects. */ + OBJECT_ALIGNMENT_BOTTOM_LEFT, /**< Tiles are snapped to the lower-left bound of objects. */ + OBJECT_ALIGNMENT_BOTTOM, /**< Tiles are snapped to the lower-center bound of objects. */ + OBJECT_ALIGNMENT_BOTTOM_RIGHT /**< Tiles are snapped to the lower-right bound of objects. */ +} TmxObjectAlignment; + +/** + * Identifiers for the possible object types. + */ +typedef enum tmx_object_type { + OBJECT_TYPE_RECTANGLE = 0, /**< Four-sided polygon four right angles and axis-aligned edges. */ + OBJECT_TYPE_ELLIPSE, /**< Ellipse, or a circle when the axes are equal. */ + OBJECT_TYPE_POINT, /**< Individual (X, Y) coordinate with no dimensions. */ + OBJECT_TYPE_POLYGON, /**< Filled polygon formed by an ordered series of points. */ + OBJECT_TYPE_POLYLINE, /**< Unfilled polygon formed by an ordered series of points. */ + OBJECT_TYPE_TEXT, /**< Text, or the visual representation of a string. */ + OBJECT_TYPE_TILE /**< Tile, referenced by a Global ID (GID), from a tileset known to the map. */ +} TmxObjectType; + +/** + * Identifiers for the possible horizontal alignments of text. + */ +typedef enum tmx_horizontal_alignment { + HORIZONTAL_ALIGNMENT_LEFT = 0, /**< Text is to be snapped to the left bound of its object. */ + HORIZONTAL_ALIGNMENT_CENTER, /**< Text is to be centered along the X axis of its object. */ + HORIZONTAL_ALIGNMENT_RIGHT, /**< Text is to be snapped to the right bound of its object. */ + HORIZONTAL_ALIGNMENT_JUSTIFY /**< Text is to be evenly spaced, per line, filling the object's width. */ +} TmxHorizontalAlignment; + +/** + * Identifiers for the possible vertical alignments of text. + */ +typedef enum tmx_vertical_alignment { + VERTICAL_ALIGNMENT_TOP = 0, /**< Text is to be snapped to the upper bound of its object. */ + VERTICAL_ALIGNMENT_CENTER, /**< Text is to be cnetered along the Y axis of its object. */ + VERTICAL_ALIGNMENT_BOTTOM /**< Text is to be snapped to the lower bound of its object. */ +} TmxVerticalAlignment; + +/** + * Identifiers for the possible map orientations. + */ +typedef enum tmx_orientation { + ORIENTATION_NONE = 0, /**< Orientation was not specified. Assumed to be orthogonal. */ + ORIENTATION_ORTHOGONAL, /**< Standard top-down view with rectanglular tiles. */ + ORIENTATION_ISOMETRIC, /**< Subset top-down view from a 45-degree angle. NOT supported. */ + ORIENTATION_STAGGERED, /**< Variation of isometric with staggered axes. */ + ORIENTATION_HEXAGONAL /**< Top-down view in which tiles are six-sided and alternative rows/columns are offset. */ +} TmxOrientation; + +/** + * Identifiers for the possible orders in which tiles in a tile layer are rendered/drawn. + */ +typedef enum tmx_render_order { + RENDER_ORDER_RIGHT_DOWN = 0, /**< Tiles are rendered by row, from left to right, then column, from top to bottom. */ + RENDER_ORDER_RIGHT_UP, /**< Tiles are rendered by row, from left to right, then column, from bottom to top. */ + RENDER_ORDER_LEFT_DOWN, /**< Tiles are rendered by row, from right to left, then column, from top to bottom. */ + RENDER_ORDER_LEFT_UP /**< Tiles are rendered by row, from right to left, then column, from bottom to top. */ +} TmxRenderOrder; + +/** + * Model of an element. Defines an image and relevant attributes along with a loaded texture. + */ +typedef struct tmx_image { + char* source; /**< File name and/or path referencing the image on disk. */ + Color trans; /**< [optional] Color that defines is treated as transparent. Not currently implemented. */ + bool hasTrans; /**< When true, indicates 'trans' has been set with a color to be treated as transparent. */ + uint32_t width; /**< Width of the image in pixels. */ + uint32_t height; /**< Height of the image in pixels. */ + Texture2D texture; /**< The image as a raylib texture loaded into VRAM, if loading was successful. */ +} TmxImage; + +/** + * Model of a element when combined with the 'TmxLayer' model. Defines a tile layer with a fixed-size list of + * tile Global IDs (GIDs). + */ +typedef struct tmx_tile_layer { + uint32_t width; /**< Width of the layer in tiles. */ + uint32_t height; /**< Height of the layer in tiles. */ + char* encoding; /**< [optional] Encoding used to encode tiles. May be NULL, "base64," or "csv." */ + char* compression; /**< [optional] Compression used to compress tiles. May be NULL, "gzip," "zlib," or "zstd." */ + uint32_t* tiles; /**< Array of tile Global IDs (GIDs) contained by this tile layer. */ + uint32_t tilesLength; /**< Length of the 'tiles' array. */ +} TmxTileLayer; + +/** + * Contains the information needed to quickly draw a single line of a element. + */ +typedef struct tmx_text_line { + char* content; /**< The string to be drawn. This may be the whole content of the parent string or partial. */ + Font font; /**< The raylib Font to be used when drawing. */ + Vector2 position; /**< Absolute position of this line. This is separate from its object layer's potential offset. */ + float spacing; /**< Spacing in pixels to be applied between each character when drawing. */ +} TmxTextLine; + +/** + * Model of a element along with some pre-calculated objects for efficient drawing. + */ +typedef struct tmx_text { + char* fontFamily; /**< Font family (e.g. "sans-serif") to be used to render the text. */ + uint32_t pixelSize; /**< Size of the font in pixels. */ + bool wrap; /**< When true, indicates word wrapping should be used when appropriate. */ + Color color; /**< Color of the text. */ + bool bold; /**< When true, indicates the text should be bolded. */ + bool italic; /**< When true, indicates the text should be italicized. */ + bool underline; /**< When true, indicates the text should be underlined. */ + bool strikeOut; /**< When true, indicates the text should be struck/crossed out. */ + bool kerning; /**< When true, indicates kerning should be used when drawing. */ + TmxHorizontalAlignment halign; /**< Horizontal alignment of the text within its object. */ + TmxVerticalAlignment valign; /**< Vertical alignment of the text within its object. */ + char* content; /**< The string to be drawn. */ + TmxTextLine* lines; /**< Array of pre-calculated lines with all values needed to quickly draw this text. */ + uint32_t linesLength; /**< Length of the 'lines' array. */ +} TmxText; + +/** + * Model of a element. Describes a property of the model it's attached to with a name, type, and value. + */ +typedef struct tmx_property { + TmxPropertyType type; /**< The specific (data) type of the property indicating which associated value to read. */ + char* name; /**< Name of the property. */ + char* stringValue; /**< The property's value for string-typed properties. */ + int32_t intValue; /**< The property's value for integer-typed properties. */ + float floatValue; /**< The property's value for floating point-typed properties. */ + bool boolValue; /**< The property's value for boolean-typed properties. */ + Color colorValue; /**< The property's value for color-typed properties. */ +} TmxProperty; + +/** + * Model of an element within an element. Objects are amorphous entities of varying type but all + * are potentially visible with positions and dimensions, although points' dimensions are effectively zero. + */ +typedef struct tmx_object { + TmxObjectType type; /**< The specific object type indicating which optional fields have relevant information. */ + uint32_t id; /**< Unique ID of the object. */ + char* name; /**< Name of the object. */ + char* typeString; /**< The type/class of the object. */ /* 'type' is a reserved keyword hence 'typeString' */ + double x; /**< X coordinate, in pixels, of the object. This is separate from its object layer's potential offset. */ + double y; /**< Y coordinate, in pixels, of the object. This is separate from its object layer's potential offset. */ + double width; /**< Width of the object in pixels. */ + double height; /**< Height of the object in pixels. */ + double rotation; /**< Rotation of the object in (clockwise) degrees around the object's (x, y) position. */ + uint32_t gid; /**< (Semi-optional) Global ID of a tile drawn as the object. If zero, the object is not a tile. */ + bool visible; /**< When true, indicates the object will be drawn. */ + char* templateString; /**< [optional] File name and/or path referencing an object template on disk applied to this + object. If NULL, no template is used. */ + Vector2* points; /**< [optional] Array of ordered points that define a poly(gon|line). Relative, not absolute. */ + uint32_t pointsLength; /**< Length of the 'points' array. */ + Vector2* drawPoints; /**< [optional] Array used as a buffer when drawing. Equal in length to the 'points' array. */ + TmxText* text; /**< [optional] Text to be drawn. */ + TmxProperty* properties; /**< Array of named, typed properties that apply to this object. */ + uint32_t propertiesLength; /**< Length of the 'properties' array. */ + Rectangle aabb; /**< Axis-Aligned Bounding Box (AABB). */ +} TmxObject; + +/** + * Model of an element when combined with the 'TmxLayer' model. Defines an object layer of an arbitrary + * number of objects of varying types. + */ +typedef struct tmx_object_group { + /* uint32_t width; */ /**< Width of the object layer in tiles. TMX documentation describes it as "meaningless." */ + /* uint32_t height; */ /**< Height of the object layer in tiles. TMX documentation describes it as "meaningless." */ + Color color; /**< [optional] Color used to display objects within the layer. */ + bool hasColor; /**< When true, indicates 'color' has been set. */ + TmxObjectGroupDrawOrder drawOrder; /**< Indicates the order in which objects in this layer are drawn. */ + TmxObject* objects; /**< Array of objects contained by this object layer. */ + uint32_t objectsLength; /**< Length of the 'objects' array. */ + uint32_t* ySortedObjects; /**< Array of indexes of 'objects' sorted by the objects' y-coordinates. */ +} TmxObjectGroup; + +/** + * Model of an element when combined with the 'TmxLayer' model. Defines a layer consisting of one image. + */ +typedef struct tmx_image_layer { + bool repeatX; /**< When true, indicates the image is repeated along the X axis. */ + bool repeatY; /**< When true, indicates the image is repeated along the Y axis. */ + TmxImage image; /**< Sole image of this layer. */ + bool hasImage; /**< When true, indicates 'image' has been set. Should always be true. */ +} TmxImageLayer; + +struct tmx_layer; /* Forward declaration of the following type. Contains children of the same type. */ + +/** + * Model of multiple layer elements: , , , or . Defines a layer with attributes + * common to all, more-specific layer types. The more-specific attributes + */ +typedef struct tmx_layer { + TmxLayerType type; /**< The specific layer type indicating which associated layer ('exact') has mspecific values. */ + uint32_t id; /**< Unique integer ID of the layer. */ + char* name; /**< Name of the layer. */ + char* classString; /**< [optional] Class of the layer. If unused, defaults to an empty string. */ + bool visible; /**< When true, indicates the layer and its children will be drawn. */ + double opacity; /**< Opacity of the layer and its children where 0.0 means the layer is fully transparent. */ + Color tintColor; /**< [optional] Tint color applied to the layer and its children. */ + bool hasTintColor; /**< When true, indicates 'tintColor' has been set. */ + int32_t offsetX; /**< Horizontal offset of the layer and its children in pixels. */ + int32_t offsetY; /**< Vertical offset of the layer and its children in pixels. */ + double parallaxX; /**< Horizontal parallax factor. 1.0 means the layers position on the screen changes at the same + rate as the camera. 0.0 means the layer will not move with the camera. */ + double parallaxY; /**< Veritcal parallax factor. 1.0 means the layers position on the screen changes at the same + rate as the camera. 0.0 means the layer will not move with the camera. */ + TmxProperty* properties; /**< Array of named, typed properties that apply to this layer. */ + uint32_t propertiesLength; /**< Length of the 'properties' array. */ + struct tmx_layer* layers; /**< [optional] Array of child layers, may be NULL. Only used by group layers. */ + uint32_t layersLength; /**< Length of the 'layers' array. */ + union layer_type_union { + TmxTileLayer tileLayer; + TmxObjectGroup objectGroup; + TmxImageLayer imageLayer; + } exact; /**< Additional layer information specific to a tile, object, or image layer but not groups. */ +} TmxLayer; + +/** + * Model of a element. Defines a temporal frame of an animation with the Global ID (GID) of the tile to be + * displayed and the duration thereof. + */ +typedef struct tmx_animation_frame { + uint32_t gid; /**< The Global ID (GID) of a tile from the animation's tileset to be drawn for some duration. */ + float duration; /**< Duration in milliseconds that the frame should be displayed. */ +} TmxAnimationFrame; + +/** + * Model of an element. Defines a series of (tile) frames. + */ +typedef struct tmx_animation { + TmxAnimationFrame* frames; /**< Array of frames. These frames identify tiles and durations to be displayed. */ + uint32_t framesLength; /**< Length of the 'frames' array. */ +} TmxAnimation; + +/** + * Model of a element within a element. Contains information about tiles that are not or cannot be + * implicitly determined from the tileset. + */ +typedef struct tmx_tileset_tile { + uint32_t id; /**< Local ID of the tile within its tileset. This is a factor in but different from its Global ID. */ + char* classString; /**< [optional] Class of the tile. If unused, defaults to an empty string. */ + int32_t x; /**< X coordinate, in pixels, of the sub-rectangle within the tileset's image to extract. */ + int32_t y; /**< Y coordinate, in pixels, of the sub-rectangle within the tileset's image to extract. */ + uint32_t width; /**< Width, in pixels, of the sub-rectangle within the tileset's image to extract. */ + uint32_t height; /**< Height, in pixels, of the sub-rectangle within the tileset's image to extract. */ + TmxImage image; /**< [optional] Image to be used as the tile for "collection of images" tilesets. */ + bool hasImage; /**< When true, indicates 'image' is set. */ + TmxAnimation animation; /**< [optional] Animation. Lists GIDs to be drawn temporarily and periodically. */ + bool hasAnimation; /**< When true, indicates 'animation' is assigned. */ + TmxProperty* properties; /**< Array of named, typed properties that apply to this tileset tile. */ + uint32_t propertiesLength; /**< Length of the 'properties' array. */ + TmxObjectGroup objectGroup; /**< [optional] 0+ objects representing collision information unique to the tile. */ +} TmxTilesetTile; + +/** + * Model of a element. Defines an image, or serious of images, from which tiles are drawn along with + * information on how to extract areas from within the image and/or how to align them within an object. + */ +typedef struct tmx_tileset { + uint32_t firstGid; /**< First Global ID (GID) of a tile in this tileset. */ + uint32_t lastGid; /**< Last Global ID (GID) of a tile in this tileset. */ + char* source; /**< [optional] Source of this tileset, may be NULL. Only used for external tilesets. */ + char* name; /**< Name of the tileset. */ + char* classString; /**< [optional] Class of the tileset. If unused, defaults to an empty string. */ + uint32_t tileWidth; /**< Maximum, although typically exact, width of the tiles in this tileset in pixels. */ + uint32_t tileHeight; /**< Maximum, although typically exact, height of the tiles in this tileset in pixels. */ + uint32_t spacing; /**< Spacing in pixels between tiles in this tileset. */ + uint32_t margin; /**< Margin around the tiles in this tileset. */ + uint32_t tileCount; /**< Number of tiles in this tileset. Note: 'lastGid' - 'firstGid' is not always 'tileCount.' */ + uint32_t columns; /**< Number of tile columsn in this tileset. */ + TmxObjectAlignment objectAlignment; /**< Controls the alignment of tiles of this tileset when used as objects. */ + int32_t tileOffsetX; /**< Horizontal offset in pixels applied when drawing tiles from this tileset. */ + int32_t tileOffsetY; /**< Vertical offset in pixels applied when drawing tiles form this tileset. */ + TmxImage image; /**< [optional] Image from which this tilesets tiles are extracted. */ + bool hasImage; /**< When true, indicates 'image' is set. */ + TmxProperty* properties; /**< Array of named, typed properties that apply to this tileset. */ + uint32_t propertiesLength; /**< Length of the 'properties' array. */ + TmxTilesetTile* tiles; /**< Array of explicitly-defined tiles within the tileset. */ + uint32_t tilesLength; /**< Length of the 'tiles' array. */ +} TmxTileset; + +/** + * Contains the information and objects needed to quickly draw a in a raylib application. + */ +typedef struct tmx_tile { + uint32_t gid; /**< Global ID (GID) unique to the tile, used for lookups. 0 means the tile is effectively unused. */ + Rectangle sourceRect; /**< Sub-rectangle within a tileset to extract that is to be drawn. */ + Texture2D texture; /**< Texture in VRAM to be used to draw. May be used whole or as a source of a sub-rectangle. */ + Vector2 offset; /**< Offset in pixels to be applied to the tile, derived from the tileset. */ + TmxAnimation animation; /**< [optional] Animation. Lists GIDs to be drawn temporarily and periodically. */ + bool hasAnimation; /**< When true, indicates 'animation' is assigned. */ + uint32_t frameIndex; /**< For animations, the current animation frame to draw. */ + float frameTime; /**< For animations, an accumulator. The time, in seconds, the current frame has been drawn. */ + TmxObjectGroup objectGroup; /**< [optional] 0+ objects representing collision information unique to the tile. */ +} TmxTile; + +/** + * Model of a element along with some pre-calculated objects for efficient drawing. + */ +typedef struct tmx_map { + char* fileName; /**< File name of the TMX file with extension. */ + TmxOrientation orientation; /**< Map orientation. May be orthogonal, isometric, staggered, or hexagonal. */ + TmxRenderOrder renderOrder; /**< Order in which tiles on tile layers are rendered. */ + uint32_t width; /**< Width of this map in tiles. */ + uint32_t height; /**< Height of htis map in tiles. */ + uint32_t tileWidth; /**< Width of a tile in pixels. */ + uint32_t tileHeight; /**< Height of a tile in pixels. */ + int32_t parallaxOriginX; /**< X coordinate, in pixels, of the parallax origin. */ + int32_t parallaxOriginY; /**< Y coordinate, in pixels, of hte parallax origin. */ + Color backgroundColor; /**< [optional] Background color to be drawn behind the map with its dimensions. */ + bool hasBackgroundColor; /**< When true, indicates 'backgroundColor' is set. */ + TmxProperty* properties; /**< Array of named, typed properties that apply to this map. */ + uint32_t propertiesLength; /**< Length of the 'properties' array. */ + TmxTileset* tilesets; /**< Array of tilesets used by the map. */ + uint32_t tilesetsLength; /**< Length of the 'tilesets' array. */ + TmxLayer* layers; /**< Array of layers and potential child layers that make up this map. */ + uint32_t layersLength; /**< Length of the 'layers' array. */ + TmxTile* gidsToTiles; /**< Array of pre-calculated tile metadata with all the values needed to quickly draw a tile + given its GID. Allocated such that gidsToTiles[1] returns the data of tile GID 1. */ + uint32_t gidsToTilesLength; /**< Length of the 'gidsToTiles' array. */ +} TmxMap; + +/** + * Given a path to TMX document, parse it and create an equivalent model that can be, among other uses, quickly drawn. + * This function allocates memory and loads textures into VRAM. To clean up, use UnloadTMX(). + * + * @param fileName File name and/or path referencing a TMX document on disk to be loaded. + * @return A model of the map as defined by the given TMX document, or NULL if loading failed for any reason. + */ +RAYTMX_DEC TmxMap* LoadTMX(const char* fileName); + +/** + * Unload a given map model by freeing memory allocations and unloading textures. In other words, free the resources + * reserved by LoadTMX(). + * + * @param map A previously-loaded map model to be freed/unloaded. + */ +RAYTMX_DEC void UnloadTMX(TmxMap* map); + +/** + * Draw the entirety of the given map at the given position. + * When a camera is also passed to this function, parallaxed scrolling can be applied to layers with parallax factors + * that are not 1.0 and the screen's surface can be known allowing for occlusion culling and the performance gains that + * come with it. + * The given tint is applied to map and its layers. When layers have their own tints, the two colors are combined. If no + * tint is needed, passing WHITE effectively means no tint is applied. + * + * @param map A loaded map model to be drawn in whole at the given coordinates. + * @param camera [optional] Camera2D to be used for parallax and/or occlusion. Required for parallax. 'viewport' takes + * priority for occlusion. + * @param viewport [optional] Region drawn to. Used for occlusion. Only tiles, objects, etc. in this region are drawn. + * @param posX X coordinate at which to draw the map. This corresponds to the top-left corner of the map. + * @param posY Y coordinate at which to draw the map. This corresponds to the top-left corner of the map. + * @param tint A tint to be applied to the map and its layers. This tint is combined with any individual layer tints. + */ +RAYTMX_DEC void DrawTMX(const TmxMap* map, const Camera2D* camera, const Rectangle* viewport, int posX, int posY, + Color tint); + +/** + * Draw the given layers at the given position. + * When a camera is also passed to this function, parallaxed scrolling can be applied to layers with parallax factors + * that are not 1.0 and the screen's surface can be known allowing for occlusion culling and the performance gains that + * come with it. + * The given tint is applied to the layers. When layers have their own tints, the two colors are combined. If no tint is + * needed, passing WHITE effectively means no tint is applied. + * + * @param map A loaded map model whose layers are to be drawn. + * @param camera [optional] Camera2D to be used for parallax and/or occlusion. Required for parallax. 'viewport' takes + * priority for occlusion. + * @param viewport [optional] Region drawn to. Used for occlusion. Only tiles, objects, etc. in this region are drawn. + * @param layers An array of select layers to be drawn. + * @param layersLength Length of the given array of layers. + * @param posX X coordinate at which to draw the layers. This corresponds to the top-left corner of the layers. + * @param posY Y coordinate at which to draw the layers. This corresponds to the top-left corner of the layers. + * @param tint A tint to be applied to the layers. This tint is combined with any individual layer tints. + */ +RAYTMX_DEC void DrawTMXLayers(const TmxMap* map, const Camera2D* camera, const Rectangle* viewport, + const TmxLayer* layers, uint32_t layersLength, int posX, int posY, Color tint); + +/** + * Progress the animations of the given map in real-time. This is intended to be called once per frame, or once per + * BeginDrawing() an EndDrawing() call. If called more or less frequently, animation speeds will be affected. + * + * @param map A loaded map model to be animated. + */ +RAYTMX_DEC void AnimateTMX(TmxMap* map); + +/** + * Check for collisions between two objects of arbitrary type. Objects that are not primitive shapes, namely text and + * tiles, are treated as rectangles. + * + * @param object1 A TMX to be checked for a collision. + * @param object2 Another TMX to be checked for a collision. + * @return True if the given objects collide with one another, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXObjects(TmxObject object1, TmxObject object2); + +/** + * Check for collisions between the given tile or group layers and the given rectangle. The tiles must have collision + * information created with the Tiled Collision Editor. + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * @param map A loaded map model containing the given layers. + * @param layers An array of select tile or group layers to be checked for collisions. + * @param layersLength Length of the given array of layers. + * @param rec The rectangle to perform collision checks on. + * @param outputObject Output parameter assigned with the object the rectangle collided with. NULL if not wanted. + * @return True if the given rectangle collides with any tile in the given layers, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXTileLayersRec(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Rectangle rec, TmxObject* outputObject); + +/** + * Check for collisions between the given tile or group layers and the given circle. The tiles must have collision + * information created with the Tiled Collision Editor. + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * @param map A loaded map model containing the given layers. + * @param layers An array of select tile or group layers to be checked for collisions. + * @param layersLength Length of the given array of layers. + * @param center The center point of the circle. + * @param radius The radius of the circle. + * @param outputObject Output parameter assigned with the object the circle collided with. NULL if not wanted. + * @return True if the given circle collides with any tile in the given layers, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXTileLayersCircle(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Vector2 center, float radius, TmxObject* outputObject); + +/** + * Check for collisions between the given tile or group layers and the given point. The tiles must have collision + * information created with the Tiled Collision Editor. + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * @param map A loaded map model containing the given layers. + * @param layers An array of select tile or group layers to be checked for collisions. + * @param layersLength Length of the given array of layers. + * @param point The point to perform collision checks on. + * @param outputObject Output parameter assigned with the object the point collided with. NULL if not wanted. + * @return True if the given point collides with any tile in the given layers, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXTileLayersPoint(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Vector2 point, TmxObject* outputObject); + +/** + * Check for collisions between the given tile or group layers and the given polygon. The tiles must have collision + * information created with the Tiled Collision Editor. + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * This function calculates the Axis-Aligned Bounding Box (AABB) of the polygon each time it's called. If the polygon's + * AABB is known, CheckCollisionTMXLayersPolyEx() can be used for better performance. + * + * @param map A loaded map model containing the given layers. + * @param layers An array of select tile or group layers to be checked for collisions. + * @param layersLength Length of the given array of layers. + * @param points An array of vertices defining the polygon. No repeats. + * @param pointCount The length of the array of vertices. + * @param outputObject Output parameter assigned with the object the polygon collided with. NULL if not wanted. + * @return True if the given polygon collides with any tile in the given layers, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXLayersPoly(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Vector2* points, int pointCount, TmxObject* outputObject); + +/** + * Check for collisions between the given tile or group layers and the given polygon with the given Axis-Aligned + * Bounding Box (AABB). The tiles must have collision information created with the Tiled Collision Editor. + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * @param map A loaded map model containing the given layers. + * @param layers An array of select tile or group layers to be checked for collisions. + * @param layersLength Length of the given array of layers. + * @param points An array of vertices defining the polygon. No repeats. + * @param pointCount The length of the array of vertices. + * @param aabb Bounding box of the polygon. Used for quicker, broad collision checks. + * @param outputObject Output parameter assigned with the object the polygon collided with. NULL if not wanted. + * @return True if the given polygon collides with any tile in the given layers, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXLayersPolyEx(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Vector2* points, int pointCount, Rectangle aabb, TmxObject* outputObject); + +/** + * Check for collisions between the given object group, with 0+ objects of arbitrary shape, and the given rectangle. + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * @param group The object group whose 0+ objects will be checked for collisions. + * @param rec The rectangle to perform collision checks on. + * @param outputObject Output parameter assigned with the object the rectangle collided with. NULL if not wanted. + * @return True if the given rectangle collides with any object in the object group, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXObjectGroupRec(TmxObjectGroup group, Rectangle rec, TmxObject* outputObject); + +/** + * Check for collisions between the given object group, with 0+ objects of arbitrary shape, and the given circle. + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * @param group The object group whose 0+ objects will be checked for collisions. + * @param center The center point of the circle. + * @param radius The radius of the circle. + * @param outputObject Output parameter assigned with the object the circle collided with. NULL if not wanted. + * @return True if the given circle collides with any object in the object group, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXObjectGroupCircle(TmxObjectGroup group, Vector2 center, float radius, + TmxObject* outputObject); + +/** + * Check for collisions between the given object group, with 0+ objects of arbitrary shape, and the given point. + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * @param group The object group whose 0+ objects will be checked for collisions. + * @param point The point to perform collision checks on. + * @param outputObject Output parameter assigned with the object the point collided with. NULL if not wanted. + * @return True if the given point collides with any object in the object group, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXObjectGroupPoint(TmxObjectGroup group, Vector2 point, TmxObject* outputObject); + +/** + * Check for collisions between the given object group, with 0+ objects of arbitrary shape, and the given polygon. + * This function calculates the Axis-Aligned Bounding Box (AABB) of the polygon each time it's called. If the polygon's + * AABB is known, CheckCollisionTMXObjectGroupPolyEx() can be used for better performance. + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * @param group The object group whose 0+ objects will be checked for collisions. + * @param points An array of vertices defining the polygon. No repeats. + * @param pointCount The length of the array of vertices. + * @param outputObject Output parameter assigned with the object the polygon collided with. NULL if not wanted. + * @return True if the given polygon collides with any object in the object group, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXObjectGroupPoly(TmxObjectGroup group, Vector2* points, int pointCount, + TmxObject* outputObject); + +/** + * Check for collisions between the given object group, with 0+ objects of arbitrary shape, and the given polygon with + * the given Axis-Aligned Bound Box (AABB). + * Note: This function assumes the map is positioned at (0, 0). If the map is drawn with an offset, normalize. + * + * @param group The object group whose 0+ objects will be checked for collisions. + * @param points An array of vertices defining the polygon. No repeats. + * @param pointCount The length of the array of vertices. + * @param aabb Bounding box of the polygon. Used for quicker, broad collision checks. + * @param outputObject Output parameter assigned with the object the polygon collided with. NULL if not wanted. + * @return True if the given polygon collides with any object in the object group, or false if there is no collision. + */ +RAYTMX_DEC bool CheckCollisionTMXObjectGroupPolyEx(TmxObjectGroup group, Vector2* points, int pointCount, + Rectangle aabb, TmxObject* outputObject); + +/** + * Set a custom callback in place of raylib's LoadTexture(). The callback must return a Texture2D and take a const char* + * as the sole parameter. To unset, pass NULL to this function. + * + * @param callback A function pointer with a "Texture2D CustLoadTexture(const char* fileName)" signature, or NULL if + * unsetting the custom callback. + */ +RAYTMX_DEC void SetLoadTextureTMX(LoadTextureCallback callback); + +/** + * Log properties of the given map as a formatted string. + * SetTraceLogFlagsTMX() may be used to exclude select information. + * + * @param logLevel The level/severity with which to log the string (e.g. LOG_DEBUG, LOG_INFO, etc.). + * @param map A loaded map to be logged. + */ +RAYTMX_DEC void TraceLogTMX(int logLevel, const TmxMap* map); + +/** + * Globally set logging options for TraceLogTMX() allowing for select types of information to be excluded. + * The flags used by this function are defined in the tmx_log_flags enumeration. + * + * @param logFlags Logically OR'd bit flags to be applied to all logging following this call. + */ +RAYTMX_DEC void SetTraceLogFlagsTMX(int logFlags); + +#ifdef __cplusplus + } +#endif /* __cplusplus */ + +#ifdef RAYTMX_IMPLEMENTATION + +#ifndef HOXML_IMPLEMENTATION + #define HOXML_IMPLEMENTATION +#endif +#include "hoxml.h" + +/******************/ +/* Implementation */ + +#define TMX_LINE_THICKNESS 3.0f /* Thickness, in pixels, that outlines of specific objects are drawn with */ + +/* Bit flags that GIDs may be masked with in order to indicate transformations for individual tiles */ +enum tmx_flip_flags { + FLIP_FLAG_HORIZONTAL = 0x80000000, + FLIP_FLAG_VERTICAL = 0x40000000, + FLIP_FLAG_DIAGONAL = 0x20000000, + FLIP_FLAG_ROTATE_120 = 0x10000000 +}; + +typedef enum raytmx_document_format { + FORMAT_TMX = 0, /* Tilemap with tilesets, layers, etc. */ + FORMAT_TSX, /* External tilesets */ + FORMAT_TX /* Object templates */ +} RaytmxDocumentFormat; + +typedef struct raytmx_external_tileset { + TmxTileset tileset; + bool isSuccess; /* 'isSuccess' is true when the external tileset was successfully loaded */ +} RaytmxExternalTileset; + +typedef struct raytmx_object_template { + TmxTileset tileset; + TmxObject object; + bool isSuccess, hasTileset; /* 'isSuccess' is true when the object template was successfully loaded */ +} RaytmxObjectTemplate; + +struct raytmx_cached_texture; /* Forward declaration */ +typedef struct raytmx_cached_texture { + char* fileName; + Texture2D texture; + struct raytmx_cached_texture* next; +} RaytmxCachedTextureNode; /* Associates a file name with a Texture2D allowing for the reuse of textures in VRAM */ + +struct raytmx_cached_template; /* Forward declaration */ +typedef struct raytmx_cached_template { + char* fileName; + RaytmxObjectTemplate objectTemplate; + struct raytmx_cached_template* next; +} RaytmxCachedTemplateNode; /* Associates a file name with an object template */ + +struct raytmx_property_node; /* Forward declaration */ +typedef struct raytmx_property_node { + TmxProperty property; + struct raytmx_property_node* next; +} RaytmxPropertyNode; + +struct raytmx_tileset_node; /* Forward declaration */ +typedef struct raytmx_tileset_node { + TmxTileset tileset; + struct raytmx_tileset_node* next; +} RaytmxTilesetNode; + +struct raytmx_tileset_tile_node; /* Forward declaration */ +typedef struct raytmx_tileset_tile_node { + TmxTilesetTile tile; + struct raytmx_tileset_tile_node* next; +} RaytmxTilesetTileNode; + +struct raytmx_animation_frame_node; /* Forward declaration */ +typedef struct raytmx_animation_frame_node { + TmxAnimationFrame frame; + struct raytmx_animation_frame_node* next; +} RaytmxAnimationFrameNode; + +struct raytmx_layer_node; /* Forward declaration */ +typedef struct raytmx_layer_node { + TmxLayer layer; + uint32_t childrenLength; + struct raytmx_layer_node *next, *parent, *childrenRoot, *childrenTail; +} RaytmxLayerNode; + +struct raytmx_tile_layer_tile_node; /* Forward declaration */ +typedef struct raytmx_tile_layer_tile_node { + uint32_t gid; + struct raytmx_tile_layer_tile_node* next; +} RaytmxTileLayerTileNode; + +struct raytmx_object_node; /* Forward declaration */ +typedef struct raytmx_object_node { + TmxObject object; + struct raytmx_object_node* next; +} RaytmxObjectNode; + +struct raytmx_object_sorting_node; /* Forward declaration */ +typedef struct raytmx_object_sorting_node { + double y; + uint32_t index; + struct raytmx_object_sorting_node* next; +} RaytmxObjectSortingNode; + +struct raytmx_poly_point_node; /* Forward declaration */ +typedef struct raytmx_poly_point_node { + Vector2 point; + struct raytmx_poly_point_node* next; +} RaytmxPolyPointNode; + +struct raytmx_text_line_node; /* Forward declaration */ +typedef struct raytmx_text_line_node { + TmxTextLine line; + struct raytmx_text_line_node* next; +} RaytmxTextLineNode; + +typedef struct raytmx_state { + RaytmxDocumentFormat format; + char documentDirectory[512]; + bool isSuccess; + + /* Variables intended for TMX (map) parsing */ + RaytmxCachedTextureNode* texturesRoot; + RaytmxCachedTemplateNode* templatesRoot; + TmxOrientation mapOrientation; + TmxRenderOrder mapRenderOrder; + uint32_t mapWidth, mapHeight, mapTileWidth, mapTileHeight, mapPropertiesLength; + int32_t mapParallaxOriginX, mapParallaxOriginY; + Color mapBackgroundColor; + bool mapHasBackgroundColor; + TmxProperty* mapProperties; + + /* These variables, when not NULL, are assigned to the current element(s) being parsed */ + TmxProperty* property; + TmxTileset* tileset; + TmxImage* image; + TmxTilesetTile* tilesetTile; + TmxAnimationFrame* animationFrame; + /* TmxWangSet* wangSet; */ /* TODO: Wang sets. Low priority. */ + /* TmxWangColor* wangColor; */ /* TODO: Wang sets. Low priority. */ + TmxLayer* layer; + TmxTileLayer* tileLayer; + TmxObjectGroup* objectGroup; + TmxImageLayer* imageLayer; + TmxObject* object; + + /* These variables are linked lists containing various elements where an arbitrary amount are allowed, such as */ + /* 1+ elements in an , that will be copied to arrays of known sizes later on */ + RaytmxPropertyNode *propertiesRoot, *propertiesTail; + RaytmxTilesetNode *tilesetsRoot, *tilesetsTail; + RaytmxTilesetTileNode *tilesetTilesRoot, *tilesetTilesTail; + RaytmxAnimationFrameNode *animationFramesRoot, *animationFramesTail; + RaytmxLayerNode *layersRoot, *layersTail, *groupNode; + RaytmxTileLayerTileNode *layerTilesRoot, *layerTilesTail; + RaytmxObjectNode *objectsRoot, *objectsTail; + uint32_t tilesetsLength, tilesetTilesLength, animationFramesLength, propertiesLength, layersLength, + layerTilesLength, objectsLength, propertiesDepth; +} RaytmxState; /* Intermediate data used internally to parse TMX (map), TSX (tileset), and TX (template) files */ + +typedef struct raytmx_transform { + Vector2 position; + Vector2 parallax; + Vector2 cameraOffset; +} RaytmxTransform; + +RaytmxExternalTileset LoadTSX(const char* fileName); +RaytmxObjectTemplate LoadTX(const char* fileName); +void ParseDocument(RaytmxState* raytmxState, const char* fileName); +void HandleElementBegin(RaytmxState* raytmxState, hoxml_context_t* hoxmlContext); +void HandleAttribute(RaytmxState* raytmxState, hoxml_context_t* hoxmlContext); +void HandleElementEnd(RaytmxState* raytmxState, hoxml_context_t* hoxmlContext); +void FreeState(RaytmxState* raytmxState); +void FreeString(char* str); +void FreeTileset(TmxTileset tileset); +void FreeProperty(TmxProperty property); +void FreeLayer(TmxLayer layer); +void FreeObject(TmxObject object); +bool IterateTileLayer(const TmxMap* map, const TmxTileLayer* layer, Rectangle viewport, RaytmxTransform transform, + uint32_t* rawGid, TmxTile* tile, Rectangle* tileRect); +void DrawTMXLayersInternal(const TmxMap* map, const Camera2D* camera, const Rectangle* viewport, const TmxLayer* layers, + uint32_t layersLength, RaytmxTransform transform, Color tint); +void DrawTMXTileLayer(const TmxMap* map, Rectangle viewport, TmxLayer layer, RaytmxTransform transform, Color tint); +void DrawTMXLayerTile(const TmxMap* map, Rectangle viewport, uint32_t rawGid, Rectangle destRect, Color tint); +void DrawTMXObjectTile(const TmxMap* map, Rectangle viewport, uint32_t rawGid, RaytmxTransform transform, float width, + float height, Color tint); +void DrawTMXObjectGroup(const TmxMap* map, Rectangle viewport, TmxLayer layer, RaytmxTransform transform, Color tint); +void DrawTMXImageLayer(const TmxMap* map, Rectangle viewport, TmxLayer layer, RaytmxTransform transform, Color tint); +bool CheckCollisionTMXTileLayerObject(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + TmxObject object, TmxObject* outputObject); +bool CheckCollisionTMXObjectGroupObject(TmxObjectGroup group, TmxObject object, TmxObject* outputObject); +void TraceLogTMXTilesets(int logLevel, TmxOrientation orientation, TmxTileset* tilesets, uint32_t tilesetsLength, + int numSpaces); +void TraceLogTMXProperties(int logLevel, TmxProperty* properties, uint32_t propertiesLength, int numSpaces); +void TraceLogTMXLayers(int logLevel, TmxLayer* layers, uint32_t layersLength, int numSpaces); +void TraceLogTMXObject(int logLevel, TmxObject object, int numSpaces); +void StringCopy(char* destination, const char* source); +TmxProperty* AddProperty(RaytmxState* raytmxState); +void AddTileLayerTile(RaytmxState* raytmxState, uint32_t gid); +TmxTileset* AddTileset(RaytmxState* raytmxState); +TmxTilesetTile* AddTilesetTile(RaytmxState* raytmxState); +TmxAnimationFrame* AddAnimationFrame(RaytmxState* raytmxState); +TmxLayer* AddGenericLayer(RaytmxState* raytmxState, bool isGroup); +TmxObject* AddObject(RaytmxState* raytmxState); +void AppendLayerTo(TmxMap* map, RaytmxLayerNode* groupNode, RaytmxLayerNode* layersRoot, uint32_t layersLength); +RaytmxCachedTextureNode* LoadCachedTexture(RaytmxState* raytmxState, const char* fileName); +RaytmxCachedTemplateNode* LoadCachedTemplate(RaytmxState* raytmxState, const char* fileName); +Color GetColorFromHexString(const char* hex); +uint32_t GetGid(uint32_t rawGid, bool* isFlippedHorizontally, bool* isFlippedVertically, bool* isFlippedDiagonally, + bool* isRotatedHexagonal120); +void* MemAllocZero(unsigned int size); +char* GetDirectoryPath2(const char* filePath); +char* JoinPath(const char* prefix, const char* suffix); +void StringCopyN(char* destination, const char* source, size_t number); +void StringConcatenate(char* destination, const char* source); + +/**********************************************************************************************************************/ +/* Public implementation. */ + +RAYTMX_DEC TmxMap* LoadTMX(const char* fileName) { + RaytmxState raytmxState[1]; + memset(raytmxState, 0, sizeof(RaytmxState)); /* Initialize all values to zero, NULL, or an equivalent enum value */ + raytmxState->format = FORMAT_TMX; + + /* Initialize the map object */ + TmxMap* map = (TmxMap*)MemAllocZero(sizeof(TmxMap)); + + /* Do format-agnostic parsing of the document. The state object will be populated with raytmx's models of the */ + /* equivalent TMX, TSX, and/or TX elements. */ + ParseDocument(raytmxState, fileName); + if (!raytmxState->isSuccess) { + UnloadTMX(map); + return NULL; + } + + /* Copy some top-level map properties */ + map->fileName = (char*)MemAllocZero((unsigned int)strlen(fileName) + 1); + StringCopy(map->fileName, GetFileName(fileName)); + map->orientation = raytmxState->mapOrientation; + map->renderOrder = raytmxState->mapRenderOrder; + map->width = raytmxState->mapWidth; + map->height = raytmxState->mapHeight; + map->tileWidth = raytmxState->mapTileWidth; + map->tileHeight = raytmxState->mapTileHeight; + map->backgroundColor = raytmxState->mapBackgroundColor; + map->parallaxOriginX = raytmxState->mapParallaxOriginX; + map->parallaxOriginY = raytmxState->mapParallaxOriginY; + map->hasBackgroundColor = raytmxState->mapHasBackgroundColor; + + uint32_t gidsToTilesLength = 0; /* Can also be seen as the last GID */ + if (raytmxState->tilesetsRoot != NULL) { /* If there is at least one tileset */ + /* Allocate the array of tilesets and zeroize every index */ + TmxTileset* tilesets = (TmxTileset*)MemAllocZero(sizeof(TmxTileset) * raytmxState->tilesetsLength); + /* Copy the TmxTileset pointers into the array */ + RaytmxTilesetNode* tilesetIterator = raytmxState->tilesetsRoot; + for (uint32_t i = 0; tilesetIterator != NULL; i++) { + TmxTileset tileset = tilesetIterator->tileset; + + if (tileset.hasImage) { /* If the tileset has a shared image and implicitly defines tiles */ + /* These tilesets implicitly have X tiles where X = * . But even */ + /* these tilesets may have explicitly defined tiles for things like animations, or anything else. We */ + /* need to know the greatest ID of those explicit tiles because they are allowed to have IDs that */ + /* exceed the tileset's tile count and this affects global IDs, even in other tilesets. */ + uint32_t greatestID = 0; + for (uint32_t i = 0; i < tileset.tilesLength; i++) + { + if (greatestID < tileset.tiles[i].id) + greatestID = tileset.tiles[i].id; + } + /* Determine the last GID, meaning the greatest GID value, from the greatest local ID and tile count */ + if (greatestID >= tileset.tileCount) + tileset.lastGid = tileset.firstGid + greatestID; + else + tileset.lastGid = tileset.firstGid + tileset.tileCount - 1; + } else if (tileset.tilesLength > 0) /* If the tileset is a "collection of images" with explicit tiles */ + tileset.lastGid = tileset.firstGid + tileset.tiles[tileset.tilesLength - 1].id - 1; + + if (gidsToTilesLength < tileset.lastGid + 1) + gidsToTilesLength = tileset.lastGid + 1; /* GIDs start at 1 so the length is the last GID + 1 */ + tilesets[i] = tileset; + tilesetIterator = tilesetIterator->next; + } + /* Add the tilesets array to the map */ + map->tilesets = tilesets; + map->tilesetsLength = raytmxState->tilesetsLength; + } else + TraceLog(LOG_WARNING, "RAYTMX: The map does not contain any tilesets"); + + if (raytmxState->layersRoot != NULL) { /* If there is at least one layer within the map */ + /* Due to the existence of layers, layers can have children of multiple generations. To form the */ + /* resulting tree-like structure, recursion is used. */ + AppendLayerTo(map, NULL, raytmxState->layersRoot, raytmxState->layersLength); + } else + TraceLog(LOG_WARNING, "RAYTMX: The map does not contain any layers"); + + if (gidsToTilesLength > 0) { + TmxTile* gidsToTiles = (TmxTile*)MemAllocZero(sizeof(TmxTile) * gidsToTilesLength); + + for (uint32_t i = 0; i < map->tilesetsLength; i++) { + TmxTileset* tileset = &map->tilesets[i]; + if (tileset->hasImage) { /* If the tileset has a shared image (i.e. not a "collection of images") */ + /* First, iterate through the explicit tiles. These are explicitly defined with elements and */ + /* are used to, among other things, provide animations and specific source rectangles. Note: Their */ + /* IDs may exceed the tileset's tile count. */ + for (uint32_t j = 0; j < tileset->tilesLength; j++) { + TmxTilesetTile tilesetTile = tileset->tiles[j]; + uint32_t gid = tileset->firstGid + tilesetTile.id; + if (tilesetTile.hasAnimation) { /* If the tile is meta, pointing to a series of other tiles */ + gidsToTiles[gid].hasAnimation = true; + gidsToTiles[gid].animation = tilesetTile.animation; + /* Frames' tile (G)IDs are initially set with local values. Now that all tilesets are known */ + /* and this tileset's first global ID is available, update the ID to be global. */ + for (uint32_t k = 0; k < gidsToTiles[gid].animation.framesLength; k++) + gidsToTiles[gid].animation.frames[k].gid += tileset->firstGid; + } + if (tilesetTile.x != 0 || tilesetTile.y != 0 || tilesetTile.width != 0 || tilesetTile.height != 0) { + /* This tile directly tells us the area within the tileset's image to use when drawing, */ + /* overriding the implicit dimensions derived from the map's tile width and height. */ + gidsToTiles[gid].sourceRect.x = (float)tilesetTile.x; + gidsToTiles[gid].sourceRect.y = (float)tilesetTile.y; + gidsToTiles[gid].sourceRect.width = (float)tilesetTile.width; + gidsToTiles[gid].sourceRect.height = (float)tilesetTile.height; + } + /* Tiles may have child object groups. These objects are a form of collision information. The */ + /* object group may be empty or may have objects. A simple assignment covers both. */ + gidsToTiles[gid].objectGroup = tilesetTile.objectGroup; + gidsToTiles[gid].gid = gid; /* Tell the tile its GID */ + } /* for (uint32_t j = 0; j < tileset->tilesLength; j++) */ + /* Second, loop through the implicit tiles. These are inferred from knowing the dimensions of the */ + /* tileset's image, dimensiosn of tiles, and the (right-down) order of tiles within the image. */ + for (uint32_t id = 0; id < tileset->tileCount; id++) { + uint32_t gid = id + tileset->firstGid, x = id % tileset->columns, y = id / tileset->columns; + gidsToTiles[gid].gid = gid; /* Tell the tile its GID */ + /* If that source renctangle within the image was NOT explicitly defined. */ + /* Note: Float comparisons like this are typically unreliable but the GIDs-to-tiles array is */ + /* initialized with zeroes making this accurate. */ + if (gidsToTiles[gid].sourceRect.x == 0.0f && gidsToTiles[gid].sourceRect.y == 0.0f && + gidsToTiles[gid].sourceRect.width == 0.0f && gidsToTiles[gid].sourceRect.height == 0.0f) { + /* Calculate the area within the texture to be drawn from contextual information */ + gidsToTiles[gid].sourceRect.x = (float)(tileset->margin + (x * tileset->tileWidth) + + (x * tileset->spacing)); + gidsToTiles[gid].sourceRect.y = (float)(tileset->margin + (y * tileset->tileHeight) + + (y * tileset->spacing)); + gidsToTiles[gid].sourceRect.width = (float)tileset->tileWidth; + gidsToTiles[gid].sourceRect.height = (float)tileset->tileHeight; + } + gidsToTiles[gid].texture = tileset->image.texture; + gidsToTiles[gid].offset.x = (float)tileset->tileOffsetX; + gidsToTiles[gid].offset.y = (float)tileset->tileOffsetY; + } /* for (uint32_t id = 0; id < tileset->tileCount; id++) */ + } else { /* If the tileset is a collection of images where each tile has its own image */ + for (uint32_t j = 0; j < tileset->tilesLength; j++) { + TmxTilesetTile tilesetTile = tileset->tiles[j]; + if (!tilesetTile.hasImage) { + TraceLog(LOG_WARNING, "RAYTMX: Skipping tile %d of image collection tileset \"%s\" because " + "it has no image", tilesetTile.id, tileset->name); + continue; + } + + int32_t gid = tileset->firstGid + tilesetTile.id; + gidsToTiles[gid].gid = gid; + gidsToTiles[gid].sourceRect.x = (float)tilesetTile.x; /* Defaults to and probably is zero */ + gidsToTiles[gid].sourceRect.y = (float)tilesetTile.y; /* Defaults to and probably is zero */ + if (tilesetTile.width != tilesetTile.image.width) + gidsToTiles[gid].sourceRect.width = (float)tilesetTile.width; + else + gidsToTiles[gid].sourceRect.width = (float)tilesetTile.image.width; + if (tilesetTile.height != tilesetTile.image.height) + gidsToTiles[gid].sourceRect.height = (float)tilesetTile.height; + else + gidsToTiles[gid].sourceRect.height = (float)tilesetTile.image.height; + gidsToTiles[gid].texture = tilesetTile.image.texture; + } + } + } + + map->gidsToTiles = gidsToTiles; + map->gidsToTilesLength = gidsToTilesLength; + } /* gidsToTilesLength > 0 */ + + /* Free the linked lists and zeroize related values */ + FreeState(raytmxState); + + return map; +} + +RAYTMX_DEC void UnloadTMX(TmxMap* map) { + if (map == NULL) + return; + + if (map->fileName != NULL) + MemFree(map->fileName); + + if (map->properties != NULL) { + for (uint32_t i = 0; i < map->propertiesLength; i++) + FreeProperty(map->properties[i]); + MemFree(map->properties); + } + + if (map->tilesets != NULL) { + for (uint32_t i = 0; i < map->tilesetsLength; i++) + FreeTileset(map->tilesets[i]); + MemFree(map->tilesets); + } + + if (map->layers != NULL) { + for (uint32_t i = 0; i < map->layersLength; i++) + FreeLayer(map->layers[i]); + MemFree(map->layers); + } + + if (map->gidsToTiles != NULL) + MemFree(map->gidsToTiles); + + MemFree(map); +} + +RAYTMX_DEC void DrawTMX(const TmxMap* map, const Camera2D* camera, const Rectangle* viewport, int posX, int posY, + Color tint) { + if (map == NULL) + return; + + if (map->hasBackgroundColor) { + Rectangle backgroundRect; + if (viewport != NULL) + backgroundRect = *viewport; + else { + backgroundRect.x = (float)posX; + backgroundRect.y = (float)posY; + backgroundRect.width = (float)(map->width * map->tileWidth); + backgroundRect.height = (float)(map->height * map->tileHeight); + } + + DrawRectangleRec(/* rec: */ backgroundRect, /* color: */ map->backgroundColor); + } + + DrawTMXLayers(map, camera, viewport, map->layers, map->layersLength, posX, posY, tint); +} + +RAYTMX_DEC void DrawTMXLayers(const TmxMap* map, const Camera2D* camera, const Rectangle* viewport, + const TmxLayer* layers, uint32_t layersLength, int posX, int posY, Color tint) +{ + /* Pack some position and related information into an object to pass to various functions. At a minimum, this */ + /* will determine the position each layer is drawn at. With a camera, this will also be used for parallax. */ + RaytmxTransform transform = + { + .position = (Vector2){ .x = (float)posX, .y = (float)posY }, + .parallax = (Vector2){ .x = 1.0f, .y = 1.0f }, + .cameraOffset = (Vector2){ .x = 0.0, .y = 0.0f } + }; + + if (map != NULL && camera != NULL) /* If the map, with its parallax origin, and a camera are available */ + { + /* Calculate the camera's offset, or distance, from the parallax origin. This origin is the reference point */ + /* for parallax scrolling and this offset determines how much a layer will scroll as the camera moves. */ + transform.cameraOffset = (Vector2) + { + .x = camera->target.x - map->parallaxOriginX, + .y = camera->target.y - map->parallaxOriginY + }; + } + + DrawTMXLayersInternal(map, camera, viewport, layers, layersLength, transform, tint); +} + +RAYTMX_DEC void AnimateTMX(TmxMap* map) { + if (map == NULL) + return; + + float dt = GetFrameTime(); /* Returns the duration, in seconds, of the last frame drawn */ + /* Iterate through the tiles, searching for those that are animations */ + for (uint32_t gid = 0; gid < map->gidsToTilesLength; gid++) { + TmxTile* tile = &map->gidsToTiles[gid]; /* A pointer is used in case the frame time needs to be reassigned */ + /* If the GID maps to a valid tile, that tile is an animation, and the bounds check passes */ + if (tile->gid > 0 && tile->hasAnimation && tile->frameIndex < tile->animation.framesLength) { + tile->frameTime += dt; + /* If the current frame has been displayed for its whole duration, or longer */ + if (tile->frameTime > tile->animation.frames[tile->frameIndex].duration) { + tile->frameTime -= tile->animation.frames[tile->frameIndex].duration; + /* Increment the frame index to display the next one... */ + tile->frameIndex += 1; + /* ...unless the last frame was "last" in both senses */ + if (tile->frameIndex == tile->animation.framesLength) + tile->frameIndex = 0; /* Wrap around to the first frame */ + } + } + } +} + +/** + * Helper function that creates a TmxObject equivalent to the given rectangle. + * + * @param rec A rectangle as raylib's Rectangle type. + * @return An equivalent TmxObject. + */ +TmxObject CreateRectangularTMXObject(Rectangle rec) { + TmxObject recAsObject; + memset(&recAsObject, 0, sizeof(TmxObject)); /* Zero initialize */ + + recAsObject.type = OBJECT_TYPE_RECTANGLE; + recAsObject.x = (double)rec.x; + recAsObject.y = (double)rec.y; + recAsObject.width = (double)rec.width; + recAsObject.height = (double)rec.height; + recAsObject.aabb = rec; + + return recAsObject; +} + +RAYTMX_DEC bool CheckCollisionTMXTileLayersRec(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Rectangle rec, TmxObject* outputObject) { + if (map == NULL || layers == NULL || layersLength == 0) + return false; + + /* Check the rectangle against objects associated with tiles in the layers for collisions */ + return CheckCollisionTMXTileLayerObject(map, layers, layersLength, CreateRectangularTMXObject(rec), outputObject); +} + +/** + * Helper function that creates a TmxObject equivalent to the given circle. + * + * @param center The center point of the circle. + * @param radius The radius of the circle. + * @return An equivalent TmxObject. + */ +TmxObject CreateCircularTMXObject(Vector2 center, float radius) { + TmxObject circleAsObject; + memset(&circleAsObject, 0, sizeof(TmxObject)); /* Zero initialize */ + + circleAsObject.type = OBJECT_TYPE_ELLIPSE; + circleAsObject.x = (double)(center.x - radius); + circleAsObject.y = (double)(center.y - radius); + circleAsObject.width = 2.0 * (double)radius; + circleAsObject.height = 2.0 * (double)radius; + circleAsObject.aabb.x = center.x - radius; + circleAsObject.aabb.y = center.y - radius; + circleAsObject.aabb.width = 2.0f * radius; + circleAsObject.aabb.height = 2.0f * radius; + + return circleAsObject; +} + +RAYTMX_DEC bool CheckCollisionTMXTileLayersCircle(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Vector2 center, float radius, TmxObject* outputObject) { + if (map == NULL || layers == NULL || layersLength == 0) + return false; + + /* Check the circle against objects associated with tiles in the layers for collisions */ + return CheckCollisionTMXTileLayerObject(map, layers, layersLength, CreateCircularTMXObject(center, radius), + outputObject); +} + +/** + * Helper function that creates a TmxObject equivalent to the given point. + * + * @param point The point. + * @return An equivalent TmxObject. + */ +TmxObject CreatePointTMXObject(Vector2 point) { + TmxObject pointAsObject; + memset(&pointAsObject, 0, sizeof(TmxObject)); /* Zero initialize */ + + pointAsObject.type = OBJECT_TYPE_POINT; + pointAsObject.x = (float)point.x; + pointAsObject.y = (float)point.y; + pointAsObject.aabb.x = (float)point.x; + pointAsObject.aabb.y = (float)point.y; + + return pointAsObject; +} + +RAYTMX_DEC bool CheckCollisionTMXTileLayersPoint(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Vector2 point, TmxObject* outputObject) { + if (map == NULL || layers == NULL || layersLength == 0) + return false; + + /* Check the point against objects associated with tiles in the layers for collisions */ + return CheckCollisionTMXTileLayerObject(map, layers, layersLength, CreatePointTMXObject(point), outputObject); +} + +/** + * Helper function that creates a TmxObject equivalent to the given polygon with the given Axis-Aligned Bounding Box + * (AABB). If the AABB should be calculated from the given vertices, pass a rectangle with a zero or negative width or + * height. + * + * @param points An array of vertices defining the polygon. No repeats. + * @param pointCount The length of the array of vertices. + * @param aabb Bounding box of the polygon. Used for quicker, broad collision checks. + * @return An equivalent TmxObject. + */ +TmxObject CreatePolygonTMXObject(Vector2* points, int pointCount, Rectangle aabb) { + /* Create a polygon TMX object to represent the given polygon */ + TmxObject polygonAsObject; + memset(&polygonAsObject, 0, sizeof(TmxObject)); /* Zero initialize */ + + polygonAsObject.type = OBJECT_TYPE_POLYGON; + + if (aabb.width <= 0.0f || aabb.height <= 0.0f) { /* If the AABB wasn't pre-calculated or is just wrong */ + /* Calculate the bounding box by searching for minimum and maximum coordinates of the vertices */ + float minX = INFINITY, maxX = -INFINITY, minY = INFINITY, maxY = -INFINITY; + for (int i = 0; i < pointCount; i++) { + Vector2 point = points[i]; + if (point.x < minX) + minX = point.x; + if (point.x > maxX) + maxX = point.x; + if (point.y < minY) + minY = point.y; + if (point.y > maxY) + maxY = point.y; + } + aabb.x = minX; + aabb.y = minY; + aabb.width = maxX - minX; + aabb.height = maxY - minY; + } + + /* Polygons' points are relative to the object's position. Because this function takes points with absolute */ + /* positions, we'll account for this by making the object's position zero. */ + polygonAsObject.x = 0.0; + polygonAsObject.y = 0.0; + polygonAsObject.width = (double)aabb.width; + polygonAsObject.height = (double)aabb.height; + polygonAsObject.aabb = aabb; + polygonAsObject.points = points; + polygonAsObject.pointsLength = pointCount; + + return polygonAsObject; +} + +RAYTMX_DEC bool CheckCollisionTMXLayersPoly(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Vector2* points, int pointCount, TmxObject* outputObject) { + if (map == NULL || layers == NULL || layersLength == 0 || points == NULL || pointCount < 3) + return false; + + /* Check the polygon against objects associated with tiles in the layers for collisions */ + return CheckCollisionTMXTileLayerObject(map, layers, layersLength, + CreatePolygonTMXObject(points, pointCount, (Rectangle){0.0f}), outputObject); +} + +RAYTMX_DEC bool CheckCollisionTMXLayersPolyEx(const TmxMap* map, const TmxLayer* layers, uint32_t layersLength, + Vector2* points, int pointCount, Rectangle aabb, TmxObject* outputObject) { + if (map == NULL || layers == NULL || layersLength == 0 || points == NULL || pointCount < 3) + return false; + + /* Check the polygon against objects associated with tiles in the layers for collisions */ + return CheckCollisionTMXTileLayerObject(map, layers, layersLength, + CreatePolygonTMXObject(points, pointCount, aabb), outputObject); +} + +RAYTMX_DEC bool CheckCollisionTMXObjectGroupRec(TmxObjectGroup group, Rectangle rec, TmxObject* outputObject) { + if (group.objectsLength == 0 || rec.width < 0.0f || rec.height < 0.0f) + return false; /* Early-out opportunity. These cases would always return false. */ + + /* Check the rectangle against TMX objects in the group for collisions */ + return CheckCollisionTMXObjectGroupObject(group, CreateRectangularTMXObject(rec), outputObject); +} + +RAYTMX_DEC bool CheckCollisionTMXObjectGroupCircle(TmxObjectGroup group, Vector2 center, float radius, + TmxObject* outputObject) { + if (group.objectsLength == 0 || radius < 0.0f) + return false; /* Early-out opportunity. These cases would always return false. */ + + /* Check the circle against TMX objects in the group for collisions */ + return CheckCollisionTMXObjectGroupObject(group, CreateCircularTMXObject(center, radius), outputObject); +} + +RAYTMX_DEC bool CheckCollisionTMXObjectGroupPoint(TmxObjectGroup group, Vector2 point, TmxObject* outputObject) { + if (group.objectsLength == 0) + return false; /* Early-out opportunity. This case would always return false. */ + + /* Check the point against TMX objects in the group for collisions */ + return CheckCollisionTMXObjectGroupObject(group, CreatePointTMXObject(point), outputObject); +} + +RAYTMX_DEC bool CheckCollisionTMXObjectGroupPoly(TmxObjectGroup group, Vector2* points, int pointCount, + TmxObject* outputObject) { + if (group.objectsLength == 0 || points == NULL || pointCount < 3) + return false; /* Early-out opportunity. These cases would always return false. */ + + /* Check the polygon TMX object against other TMX objects in the group for collisions */ + return CheckCollisionTMXObjectGroupObject(group, CreatePolygonTMXObject(points, pointCount, (Rectangle){0.0f}), + outputObject); +} + +RAYTMX_DEC bool CheckCollisionTMXObjectGroupPolyEx(TmxObjectGroup group, Vector2* points, int pointCount, + Rectangle aabb, TmxObject* outputObject) { + if (group.objectsLength == 0 || points == NULL || pointCount < 3) + return false; /* Early-out opportunity. These cases would always return false. */ + + /* Check the polygon TMX object against other TMX objects in the group for collisions */ + return CheckCollisionTMXObjectGroupObject(group, CreatePolygonTMXObject(points, pointCount, aabb), outputObject); +} + +static LoadTextureCallback loadTextureOverride = NULL; + +RAYTMX_DEC void SetLoadTextureTMX(LoadTextureCallback callback) { + loadTextureOverride = callback; +} + +static int tmxLogFlags = 0; + +RAYTMX_DEC void TraceLogTMX(int logLevel, const TmxMap* map) { + if (map == NULL) + return; + + switch (map->orientation) { + case ORIENTATION_NONE: TraceLog(logLevel, "orientation: none"); break; + case ORIENTATION_ORTHOGONAL: TraceLog(logLevel, "orientation: orthogonal"); break; + case ORIENTATION_ISOMETRIC: TraceLog(logLevel, "orientation: isometric"); break; + case ORIENTATION_STAGGERED: TraceLog(logLevel, "orientation: staggered"); break; + case ORIENTATION_HEXAGONAL: TraceLog(logLevel, "orientation: hexagonal"); break; + } + + switch (map->renderOrder) { + case RENDER_ORDER_RIGHT_DOWN: TraceLog(logLevel, "render order: right-down"); break; + case RENDER_ORDER_RIGHT_UP: TraceLog(logLevel, "render order: right-up"); break; + case RENDER_ORDER_LEFT_DOWN: TraceLog(logLevel, "render order: left-down"); break; + case RENDER_ORDER_LEFT_UP: TraceLog(logLevel, "render order: left-up"); break; + } + + TraceLog(logLevel, "width: %u tiles", map->width); + TraceLog(logLevel, "height: %u tiles", map->height); + TraceLog(logLevel, "tile width: %u pixels", map->tileWidth); + TraceLog(logLevel, "tile height: %u pixels", map->tileHeight); + TraceLog(logLevel, "parallax origin X: %d pixels", map->parallaxOriginX); + TraceLog(logLevel, "parallax origin Y: %d pixels", map->parallaxOriginY); + if (map->hasBackgroundColor) + TraceLog(logLevel, "background color: 0x%08X", map->backgroundColor); + + TraceLogTMXTilesets(logLevel, map->orientation, map->tilesets, map->tilesetsLength, 0); + TraceLogTMXProperties(logLevel, map->properties, map->propertiesLength, 0); + TraceLogTMXLayers(logLevel, map->layers, map->layersLength, 0); +} + +RAYTMX_DEC void SetTraceLogFlagsTMX(int logFlags) { + tmxLogFlags = logFlags; +} + +/**********************************************************************************************************************/ +/* Private implementation. */ + +RaytmxExternalTileset LoadTSX(const char* fileName) { + RaytmxState raytmxState[1]; + memset(raytmxState, 0, sizeof(RaytmxState)); /* Initialize all values to zero, NULL, or an equivalent enum value */ + raytmxState->format = FORMAT_TSX; + + /* Initialize an external tileset object */ + RaytmxExternalTileset externalTileset; + memset(&externalTileset, 0, sizeof(RaytmxExternalTileset)); + + /* Do format-agnostic parsing of the document. The state object will be populated with raytmx's models of the */ + /* equivalent TMX, TSX, and/or TX elements. */ + ParseDocument(raytmxState, fileName); + if (!raytmxState->isSuccess) + return externalTileset; /* Will have 'isSuccess' set to false to indicate a failure */ + + if (raytmxState->tilesetsRoot != NULL) { /* If there is at least one tileset */ + /* Copy the root tileset so it can be returned */ + externalTileset.tileset = raytmxState->tilesetsRoot->tileset; + externalTileset.isSuccess = true; + /* TSX files should have only one tileset so any others will be freed/unloaded immediately */ + RaytmxTilesetNode* tilesetIterator = raytmxState->tilesetsRoot->next; + while (tilesetIterator != NULL) { + FreeTileset(tilesetIterator->tileset); + tilesetIterator = tilesetIterator->next; + } + } else + TraceLog(LOG_WARNING, "RAYTMX: TSX file (external tileset) \"%s\" does not contain any tilesets", fileName); + + /* Free the linked lists and zeroize related values */ + FreeState(raytmxState); + + return externalTileset; +} + +RaytmxObjectTemplate LoadTX(const char* fileName) { + RaytmxState raytmxState[1]; + memset(raytmxState, 0, sizeof(RaytmxState)); /* Initialize all values to zero, NULL, or an equivalent enum value */ + raytmxState->format = FORMAT_TX; + + /* Initialize an object template object */ + RaytmxObjectTemplate objectTemplate; + memset(&objectTemplate, 0, sizeof(RaytmxObjectTemplate)); + + /* Do format-agnostic parsing of the document. The state object will be populated with raytmx's models of the */ + /* equivalent TMX, TSX, and/or TX elements. */ + ParseDocument(raytmxState, fileName); + if (!raytmxState->isSuccess) + return objectTemplate; /* Will have 'isSuccess' set to false to indicate a failure */ + + if (raytmxState->objectsRoot != NULL) { /* If there is at least one object */ + /* Copy the root object so it can be returned */ + objectTemplate.object = raytmxState->objectsRoot->object; + objectTemplate.isSuccess = true; + /* TX files should have only one object so any others will be freed/unloaded immediately */ + RaytmxObjectNode* objectIterator = raytmxState->objectsRoot->next; + while (objectIterator != NULL) { + FreeObject(objectIterator->object); + objectIterator = objectIterator->next; + } + } else + TraceLog(LOG_WARNING, "RAYTMX: TX file (object template) \"%s\" does not contain any objects", fileName); + + if (raytmxState->tilesetsRoot != NULL) { /* If there is at least one tileset */ + /* Object templates may have a tileset. This is for cases where the object references a tile (i.e. its 'gid' */ + /* attribute is set). Copy the root tileset so it can be returned */ + objectTemplate.tileset = raytmxState->tilesetsRoot->tileset; + objectTemplate.hasTileset = true; + /* TX files should have at most one tileset so any others will be freed/unloaded immediately */ + RaytmxTilesetNode* tilesetsIterator = raytmxState->tilesetsRoot->next; + while (tilesetsIterator != NULL) { + FreeTileset(tilesetsIterator->tileset); + tilesetsIterator = tilesetsIterator->next; + } + } + + /* Free the linked lists and zeroize related values */ + FreeState(raytmxState); + + return objectTemplate; +} + +void ParseDocument(RaytmxState* raytmxState, const char* fileName) { + char* content = LoadFileText(fileName); + if (content == NULL) { + TraceLog(LOG_ERROR, "RAYTMX: Failed to open \"%s\"", fileName); + return; + } + size_t contentLength = strlen(content); + + StringCopy(raytmxState->documentDirectory, GetDirectoryPath2(fileName)); + + hoxml_context_t hoxmlContext[1]; + size_t bufferLength = contentLength; + char* buffer = (char*)MemAlloc((unsigned int)bufferLength); + hoxml_init(hoxmlContext, buffer, bufferLength); + + hoxml_code_t code; + while ((code = hoxml_parse(hoxmlContext, content, contentLength)) != HOXML_END_OF_DOCUMENT) { + if (code > HOXML_END_OF_DOCUMENT) { /* If there's information about an element, attribute, whatever */ + switch (code) { + case HOXML_ELEMENT_BEGIN: HandleElementBegin(raytmxState, hoxmlContext); break; + case HOXML_ELEMENT_END: HandleElementEnd(raytmxState, hoxmlContext); break; + case HOXML_ATTRIBUTE: HandleAttribute(raytmxState, hoxmlContext); + case HOXML_PROCESSING_INSTRUCTION_BEGIN: break; + case HOXML_PROCESSING_INSTRUCTION_END: break; + default: break; /* No other cases to handle but compilers like to complain */ + } + } else if (code < HOXML_END_OF_DOCUMENT) { /* If there was an error, recoverable or not */ + switch (code) { + case HOXML_ERROR_INSUFFICIENT_MEMORY: { + /* This is one we can recover from by expanding the buffer. In this case, it will be doubled. */ + TraceLog(LOG_DEBUG, "RAYTMX: Allocating a new XML parsing buffer due to insufficient memory"); + bufferLength *= 2; + char* newBuffer = (char*)MemAlloc((unsigned int)bufferLength); + hoxml_realloc(hoxmlContext, newBuffer, bufferLength); + MemFree(buffer); + buffer = newBuffer; + continue; + } case HOXML_ERROR_UNEXPECTED_EOF: + TraceLog(LOG_ERROR, "RAYTMX: Unexpected end of file"); + break; + case HOXML_ERROR_SYNTAX: + TraceLog(LOG_ERROR, "RAYTMX: Invalid syntax: line %d, column %d", hoxmlContext->line, + hoxmlContext->column); + break; + case HOXML_ERROR_ENCODING: + TraceLog(LOG_ERROR, "RAYTMX: Character encoding error: line %d, column %d", hoxmlContext->line, + hoxmlContext->column); + break; + case HOXML_ERROR_TAG_MISMATCH: + TraceLog(LOG_ERROR, "RAYTMX: Close tag does not match open tag: line %d, column %d", + hoxmlContext->line, hoxmlContext->column); + break; + case HOXML_ERROR_INVALID_DOCUMENT_TYPE_DECLARATION: + case HOXML_ERROR_INVALID_DOCUMENT_DECLARATION: + TraceLog(LOG_ERROR, "RAYTMX: Document (type) declaration error: line %d, column %d", + hoxmlContext->line, hoxmlContext->column); + break; + default: break; /* Keep the compiler happy */ + } + UnloadFileText(content); + return; + } + } + + UnloadFileText(content); + MemFree(buffer); + raytmxState->isSuccess = true; +} + +void HandleElementBegin(RaytmxState* raytmxState, hoxml_context_t* hoxmlContext) { + if (raytmxState == NULL || hoxmlContext == NULL) + return; + + if (strcmp(hoxmlContext->tag, "map") == 0) + ; + else if (strcmp(hoxmlContext->tag, "properties") == 0) { + /* TMX allows nested properties but they are not (currently?) supported. To avoid memory leaks */ + /* depth is tracked. */ + raytmxState->propertiesDepth += 1; + } else if (strcmp(hoxmlContext->tag, "property") == 0) + raytmxState->property = AddProperty(raytmxState); + else if (strcmp(hoxmlContext->tag, "tileset") == 0) + raytmxState->tileset = AddTileset(raytmxState); + else if (strcmp(hoxmlContext->tag, "image") == 0) { + /* If any of the elements that may have an image is/are open */ + if (raytmxState->tilesetTile != NULL || raytmxState->tileset != NULL || raytmxState->imageLayer != NULL) { + /* If the open element already has an image */ + /* Note: In all cases, the element can contain at most one */ + if ((raytmxState->tilesetTile != NULL && raytmxState->tilesetTile->hasImage) || + (raytmxState->tileset != NULL && raytmxState->tileset->hasImage) || + (raytmxState->imageLayer != NULL && raytmxState->imageLayer->hasImage)) { + TraceLog(LOG_WARNING, "RAYTMX: an element contained multiple images; the image on line %d will be " + "dropped", hoxmlContext->line); + } else { + /* elements can be children of , , or elements */ + if (raytmxState->tilesetTile != NULL) { + raytmxState->tilesetTile->hasImage = true; + raytmxState->image = &raytmxState->tilesetTile->image; + } else if (raytmxState->tileset != NULL) { + raytmxState->tileset->hasImage = true; + raytmxState->image = &raytmxState->tileset->image; + } else if (raytmxState->imageLayer != NULL) { + raytmxState->imageLayer->hasImage = true; + raytmxState->image = &raytmxState->imageLayer->image; + } + } + } + } /* strcmp(hoxmlContext->tag, "image") == 0 */ + else if (strcmp(hoxmlContext->tag, "tile") == 0) { + /* elements can be children of or . They are also not the same element in that they */ + /* have entirely different attributes and a tileset's may have children. */ + if (raytmxState->tileset != NULL) + raytmxState->tilesetTile = AddTilesetTile(raytmxState); + /* Layer s are added during attribute handling because they provide a GID attribute and nothing else */ + } /* strcmp(hoxmlContext->tag, "tile") == 0 */ + else if (strcmp(hoxmlContext->tag, "animation") == 0) { + if (raytmxState->tilesetTile != NULL) + raytmxState->tilesetTile->hasAnimation = true; + } else if (strcmp(hoxmlContext->tag, "frame") == 0) + raytmxState->animationFrame = AddAnimationFrame(raytmxState); + else if (strcmp(hoxmlContext->tag, "layer") == 0) { + /* Allocate a new layer with 'tileLayer' allocated and append it to the current group, if it exists */ + raytmxState->layer = AddGenericLayer(raytmxState, /* isGroup: */ false); + raytmxState->layer->type = LAYER_TYPE_TILE_LAYER; + raytmxState->tileLayer = &raytmxState->layer->exact.tileLayer; + } else if (strcmp(hoxmlContext->tag, "objectgroup") == 0) { + if (raytmxState->tilesetTile != NULL) { /* If the object group is a child of a , it's collision info */ + raytmxState->objectGroup = &raytmxState->tilesetTile->objectGroup; + /* Child objects (rectangles, points, ellipses, or polygons) are expected to follow */ + } else { + /* Allocate a new layer with 'objectGroup' allocated and append it to the current group, if it exists */ + raytmxState->layer = AddGenericLayer(raytmxState, /* isGroup: */ false); + raytmxState->layer->type = LAYER_TYPE_OBJECT_GROUP; + raytmxState->objectGroup = &raytmxState->layer->exact.objectGroup; + } + } else if (strcmp(hoxmlContext->tag, "object") == 0) { + /* elements are typically only allowable as children of s but object templates, TX */ + /* files, contain them as children of root