diff options
| author | Xander Swan <no email> | 2025-12-23 20:58:32 -0500 |
|---|---|---|
| committer | Xander Swan <no email> | 2025-12-23 20:58:32 -0500 |
| commit | 2838358993ade2ac1b770d675af9126749074e8f (patch) | |
| tree | b423ac832dd86cec1863c725fd2957eddd83c28f | |
| parent | ce4d64bd41937d7dff18ca607122188dc338d696 (diff) | |
Fix lotsa memory leaks mhm
| -rw-r--r-- | editor/editor.odin | 32 | ||||
| -rwxr-xr-x | editor/run.sh | 3 | ||||
| -rw-r--r-- | res/room_begin.tmj | 51 | ||||
| -rw-r--r-- | res/tileset.tsj | 125 | ||||
| -rw-r--r-- | res/tilesets.png | bin | 0 -> 2904 bytes | |||
| -rw-r--r-- | res/tilesets.qoi | bin | 3135 -> 2998 bytes | |||
| -rw-r--r-- | res/world.tiled-project | 14 | ||||
| -rw-r--r-- | res/world.tiled-session | 26 | ||||
| -rwxr-xr-x | run.sh | 2 | ||||
| -rw-r--r-- | src/draw/draw.odin | 11 | ||||
| -rw-r--r-- | src/main.odin | 57 | ||||
| -rw-r--r-- | src/phys/world.odin | 3 | ||||
| -rw-r--r-- | src/player.odin | 2 | ||||
| -rw-r--r-- | src/tiled/json.odin | 208 | ||||
| -rw-r--r-- | src/tiled/tiled.odin | 585 |
15 files changed, 1066 insertions, 53 deletions
diff --git a/editor/editor.odin b/editor/editor.odin deleted file mode 100644 index f508514..0000000 --- a/editor/editor.odin +++ /dev/null @@ -1,32 +0,0 @@ -package editor - -import rl "vendor:raylib" -import mu "vendor:microui" - -state := struct{ - muctx: mu.Context -} - -main :: proc() { - rl.InitWindow(800, 400, "Editor") - defer rl.CloseWindow() - - ctx := &state.muctx - mu.init(ctx) - - ctx.text_width = mu.default_atlas_text_width - ctx.text_height = mu.default_atlas_text_height - - atlas_texture = rl.LoadRenderTexture(int(mu.DEFAULT_ATLAS_WIDTH), int(mu.DEFAULT_ATLAS_HEIGHT)) - defer rl.UnloadRenderTexture(state.atlas_texture) - - for rl.WindowShouldClose() { - mu.begin(ctx) - mu.label(ctx, "Test") - mu.end(ctx) - - rl.BeginDrawing() - - rl.EndDrawing() - } -} diff --git a/editor/run.sh b/editor/run.sh deleted file mode 100755 index 5c479e9..0000000 --- a/editor/run.sh +++ /dev/null @@ -1,3 +0,0 @@ -#! /bin/bash - -odin run editor -vet -disallow-do -o:speed diff --git a/res/room_begin.tmj b/res/room_begin.tmj new file mode 100644 index 0000000..1a294fc --- /dev/null +++ b/res/room_begin.tmj @@ -0,0 +1,51 @@ +{ "compressionlevel":-1, + "height":20, + "infinite":false, + "layers":[ + { + "data":[40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, + 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, + 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 27, 31, 31, 19, 19, 28, + 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 21, 31, 31, 31, 11, 0, 0, 0, 0, 16, + 40, 40, 40, 40, 40, 40, 40, 40, 27, 31, 19, 31, 19, 19, 19, 31, 19, 31, 19, 19, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 40, 40, 40, 40, 21, 31, 31, 31, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 40, 40, 21, 19, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, + 40, 21, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, + 27, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, + 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, + 32, 0, 0, 0, 0, 0, 0, 5, 9, 20, 9, 20, 20, 20, 18, 20, 20, 18, 18, 9, 18, 9, 18, 6, 0, 0, 0, 0, 0, 33, + 32, 0, 0, 0, 0, 0, 0, 7, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 8, 0, 0, 0, 0, 0, 7, + 11, 0, 0, 0, 0, 0, 0, 7, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 38, 9, 9, 42, 0, 0, 7, + 0, 0, 0, 5, 9, 20, 9, 44, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 21, 19, 19, 17, 0, 0, 33, + 0, 0, 0, 7, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 32, 0, 0, 0, 0, 0, 33, + 20, 9, 18, 39, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 27, 17, 0, 41, 18, 20, 20, 44, + 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 27, 17, 0, 0, 33, 40, 40, 40, 40, + 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 8, 0, 41, 20, 44, 40, 40, 40, 40], + "height":20, + "id":1, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":30, + "x":0, + "y":0 + }], + "nextlayerid":2, + "nextobjectid":1, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.11.2", + "tileheight":16, + "tilesets":[ + { + "firstgid":1, + "source":"tileset.tsj" + }], + "tilewidth":16, + "type":"map", + "version":"1.10", + "width":30 +} diff --git a/res/tileset.tsj b/res/tileset.tsj new file mode 100644 index 0000000..2f717e9 --- /dev/null +++ b/res/tileset.tsj @@ -0,0 +1,125 @@ +{ "backgroundcolor":"#444444", + "columns":11, + "image":"tilesets.png", + "imageheight":64, + "imagewidth":176, + "margin":0, + "name":"tileset", + "spacing":0, + "tilecount":44, + "tiledversion":"1.11.2", + "tileheight":16, + "tilewidth":16, + "type":"tileset", + "version":"1.10", + "wangsets":[ + { + "colors":[ + { + "color":"#ff0000", + "name":"", + "probability":1, + "tile":-1 + }], + "name":"Carrabassett", + "tile":-1, + "type":"corner", + "wangtiles":[ + { + "tileid":4, + "wangid":[0, 0, 0, 1, 0, 0, 0, 0] + }, + { + "tileid":5, + "wangid":[0, 0, 0, 0, 0, 1, 0, 0] + }, + { + "tileid":6, + "wangid":[0, 1, 0, 1, 0, 0, 0, 0] + }, + { + "tileid":7, + "wangid":[0, 0, 0, 0, 0, 1, 0, 1] + }, + { + "tileid":8, + "wangid":[0, 0, 0, 1, 0, 1, 0, 0] + }, + { + "tileid":9, + "wangid":[0, 1, 0, 0, 0, 0, 0, 0] + }, + { + "tileid":15, + "wangid":[0, 1, 0, 0, 0, 0, 0, 0] + }, + { + "tileid":16, + "wangid":[0, 0, 0, 0, 0, 0, 0, 1] + }, + { + "tileid":17, + "wangid":[0, 0, 0, 1, 0, 1, 0, 0] + }, + { + "tileid":18, + "wangid":[0, 1, 0, 0, 0, 0, 0, 1] + }, + { + "tileid":19, + "wangid":[0, 0, 0, 1, 0, 1, 0, 0] + }, + { + "tileid":20, + "wangid":[0, 1, 0, 0, 0, 1, 0, 1] + }, + { + "tileid":26, + "wangid":[0, 1, 0, 0, 0, 1, 0, 1] + }, + { + "tileid":27, + "wangid":[0, 1, 0, 1, 0, 0, 0, 1] + }, + { + "tileid":28, + "wangid":[0, 1, 0, 0, 0, 1, 0, 0] + }, + { + "tileid":29, + "wangid":[0, 0, 0, 1, 0, 0, 0, 1] + }, + { + "tileid":30, + "wangid":[0, 1, 0, 0, 0, 0, 0, 1] + }, + { + "tileid":31, + "wangid":[0, 0, 0, 0, 0, 1, 0, 1] + }, + { + "tileid":37, + "wangid":[0, 0, 0, 1, 0, 1, 0, 1] + }, + { + "tileid":38, + "wangid":[0, 1, 0, 1, 0, 1, 0, 0] + }, + { + "tileid":39, + "wangid":[0, 1, 0, 1, 0, 1, 0, 1] + }, + { + "tileid":40, + "wangid":[0, 0, 0, 1, 0, 0, 0, 0] + }, + { + "tileid":41, + "wangid":[0, 0, 0, 0, 0, 1, 0, 0] + }, + { + "tileid":42, + "wangid":[0, 0, 0, 1, 0, 1, 0, 1] + }] + }] +}
\ No newline at end of file diff --git a/res/tilesets.png b/res/tilesets.png Binary files differnew file mode 100644 index 0000000..4a431fe --- /dev/null +++ b/res/tilesets.png diff --git a/res/tilesets.qoi b/res/tilesets.qoi Binary files differindex 9bd31c9..ecc869f 100644 --- a/res/tilesets.qoi +++ b/res/tilesets.qoi diff --git a/res/world.tiled-project b/res/world.tiled-project new file mode 100644 index 0000000..d0eb592 --- /dev/null +++ b/res/world.tiled-project @@ -0,0 +1,14 @@ +{ + "automappingRulesFile": "", + "commands": [ + ], + "compatibilityVersion": 1100, + "extensionsPath": "extensions", + "folders": [ + "." + ], + "properties": [ + ], + "propertyTypes": [ + ] +} diff --git a/res/world.tiled-session b/res/world.tiled-session new file mode 100644 index 0000000..42585f3 --- /dev/null +++ b/res/world.tiled-session @@ -0,0 +1,26 @@ +{ + "Map/SizeTest": { + "height": 4300, + "width": 2 + }, + "activeFile": "", + "expandedProjectPaths": [ + ], + "fileStates": { + }, + "last.imagePath": "/home/xswan/demonchime/res", + "map.lastUsedFormat": "json", + "map.tileHeight": 16, + "map.tileWidth": 16, + "openFiles": [ + ], + "project": "world.tiled-project", + "property.type": "int", + "recentFiles": [ + ], + "tileset.lastUsedFormat": "json", + "tileset.tileSize": { + "height": 16, + "width": 16 + } +} @@ -1,2 +1,2 @@ sokol-shdc -i src/draw/default.glsl -o src/draw/default_shader.odin -l glsl430:metal_macos:hlsl5 -f sokol_odin -odin run src -disallow-do -debug -sanitize:address -show-timings +odin run src -disallow-do -debug -show-timings diff --git a/src/draw/draw.odin b/src/draw/draw.odin index 6761b36..3e80d42 100644 --- a/src/draw/draw.odin +++ b/src/draw/draw.odin @@ -18,7 +18,7 @@ Mat4 :: matrix[4, 4]f32 Color :: [4]f32 SCREEN_WIDTH :: 480 -SCREEN_HEIGHT :: 360 +SCREEN_HEIGHT :: 320 renderer: struct { screen: rl.RenderTexture2D, @@ -98,10 +98,13 @@ texture_quad :: proc( rotation: f32 = 0, scale := Vec2{1, 1}, ) { - // assert(quad.size.x > 0 && quad.size.y > 0) - quad := quad - quad.size *= scale + if scale.x < 0 { + quad.size.x *= -1 + } + if scale.y < 0 { + quad.size.y *= -1 + } rl.DrawTexturePro( img, diff --git a/src/main.odin b/src/main.odin index 9a6c09a..6b04e02 100644 --- a/src/main.odin +++ b/src/main.odin @@ -8,11 +8,13 @@ import "base:runtime" import "core:log" import "core:fmt" +import "core:mem" import rl "vendor:raylib" import "draw" import "phys" +import "tiled" Vec2 :: [2]f32 @@ -24,6 +26,8 @@ Rect :: struct { state: struct { player: Player, platform_list: Entity_List(Platform), + + room: tiled.Map, } init :: proc() { @@ -33,6 +37,12 @@ init :: proc() { init_player(&state.player) + room, err := tiled.load_map("res/room_begin.tmj") + if err != .NONE { + log.errorf("Error loading map (%v)", err) + } + state.room = room + make_platform(Rect{ start = {50, 50}, size = {64, 20} @@ -41,7 +51,7 @@ init :: proc() { i := f32(20) for i < draw.SCREEN_WIDTH - 20 - 64 { make_platform(Rect{ - start = {i, 340}, + start = {i, draw.SCREEN_HEIGHT - 20}, size = {64, 20} }) i += 65 @@ -52,14 +62,6 @@ init :: proc() { size = {20, 20} }) make_platform(Rect{ - start = {240, 280}, - size = {40, 20} - }) - make_platform(Rect{ - start = {260, 260}, - size = {20, 20} - }) - make_platform(Rect{ start = {300, 220}, size = {40, 20} }) @@ -90,23 +92,54 @@ frame :: proc() { draw.new_frame() + draw_platforms() + draw_player(state.player) + + tiled.draw_map(state.room) + fps_text := fmt.caprintf( "FPS: %v", rl.GetFPS(), allocator = context.temp_allocator, ) rl.DrawText(fps_text, 5, 5, 8, rl.GREEN) - draw_platforms() - draw_player(state.player) - draw.end_frame() } cleanup :: proc() { + delete_player(&state.player) delete_entity_list(state.platform_list) + tiled.delete_map(state.room) + phys.destroy_world() } main :: proc() { + when ODIN_DEBUG { + track: mem.Tracking_Allocator + mem.tracking_allocator_init(&track, context.allocator) + context.allocator = mem.tracking_allocator(&track) + + defer { + if len(track.allocation_map) > 0 { + fmt.eprintf("=== %v allocations not freed: ===\n", len(track.allocation_map)) + total := 0 + for _, entry in track.allocation_map { + fmt.eprintf( + "- %v bytes @ %v (%v, mode %v)\n", + entry.size, + entry.location, + entry.err, + entry.mode, + ) + total += entry.size + } + + fmt.eprintf("=== a total of %v bytes unfreed ===\n", total) + } + mem.tracking_allocator_destroy(&track) + } + } + logger := log.create_console_logger() context.logger = logger defer log.destroy_console_logger(logger) diff --git a/src/phys/world.odin b/src/phys/world.odin index 3154a0b..66479e8 100644 --- a/src/phys/world.odin +++ b/src/phys/world.odin @@ -21,6 +21,9 @@ destroy_world :: proc() { for bin in world.bins { delete(bin) } + delete(world.handles) + delete(world.unused_handles) + delete(world.bodies) } @(private="file") diff --git a/src/player.odin b/src/player.odin index 6c34d14..80672a9 100644 --- a/src/player.odin +++ b/src/player.odin @@ -47,7 +47,7 @@ init_player :: proc(p: ^Player) { } } -deinit_player :: proc(p: ^Player) { +delete_player :: proc(p: ^Player) { phys.remove_body(p.body_handle) draw.destroy_sprite(p.sprite) diff --git a/src/tiled/json.odin b/src/tiled/json.odin new file mode 100644 index 0000000..874430e --- /dev/null +++ b/src/tiled/json.odin @@ -0,0 +1,208 @@ +package tiled + +Json_Map :: struct { + backgroundcolor: string, + class: string, + compressionlevel: i32, + width: i32, + height: i32, + infinite: bool, + layers: []Json_Layer, + nextlayerid: i32, + nextobjectid: i32, + orientation: string, + parallaxoriginx: f64, + parallaxoriginy: f64, + properties: []Json_Property, + renderorder: string, + staggeraxis: string, + staggerindex: string, + tilewidth: i32, + tileheight: i32, + tilesets: []Json_Tile_Set, +} + +Json_Layer :: struct { + class: string, + compression: string, + data: union { + []i32, + string, + }, + draworder: string, + encoding: string, + id: i32, + image: string, + image_width: i32, + image_height: i32, + layers: []Json_Layer, + // locked: bool, + name: string, + objects: []Json_Object, + offsetx: f64, + offsety: f64, + opacity: f32, + parallaxx: f64, + parallaxy: f64, + properties: []Json_Property, + repeatx: bool, + repeaty: bool, + tintcolor: string, + transparentcolor: string, + type: string, + visible: bool, + x: i32, + y: i32, + width: i32, + height: i32, +} + +Json_Object :: struct { + ellipse: bool, + gid: i32, + width: f64, + height: f64, + id: i32, + name: string, + point: bool, + polygon: []Json_Point, + polyline: []Json_Point, + properties: []Json_Property, + rotation: f64, + template: string, + text: Maybe(struct { + fontfamily: string, + color: string, + text: string, + halign: string, + valign: string, + pixelsize: i32, + wrap: bool, + underline: bool, + bold: bool, + italic: bool, + kerning: bool, + strikeout: bool, + }), + type: string, + visible: bool, + x: f64, + y: f64, +} + +Json_Tile_Set :: struct { + background_color: string, + class: string, + rows: i32, + columns: i32, + fillmode: string, + firstgid: i32, + grid: Maybe(Json_Grid), + image: string, + imagewidth: i32, + imageheight: i32, + margin: i32, + name: string, + objectalignment: string, + properties: []Json_Property, + source: string, + spacing: i32, + terrains: []Json_Terrain, + tilecount: i32, + tilewidth: i32, + tileheight: i32, + tileoffset: Json_Tile_Offset, + tilerendersize: string, + tiles: []Json_Tile, + transformations: Json_Transformations, + transparentcolor: string, + type: string, + wangsets: []Json_Wang_Set, +} + +Json_Grid :: struct { + width: i32, + height: i32, + orientation: string, +} + +Json_Tile_Offset :: struct { + x: i32, + y: i32, +} + +Json_Transformations :: struct { + hflip: bool, + vflip: bool, + rotate: bool, + preferuntransformed: bool, +} + +Json_Tile :: struct { + animation: []Json_Frame, + id: i32, + image: string, + imagewidth: i32, + imageheight: i32, + x: i32, + y: i32, + width: i32, + height: i32, + objectgroup: Json_Layer, + probability: f64, + properties: []Json_Property, + terrain: []i32, + type: string, +} + +Json_Frame :: struct { + duration: i32, + tileid: i32, +} + +Json_Terrain :: struct { + name: string, + properties: []Json_Property, + tile: i32, +} + +Json_Wang_Set :: struct { + class: string, + colors: []Json_Wang_Color, + name: string, + properties: []Json_Property, + tile: i32, + type: string, + wangtiles: []Json_Wang_Tile, +} + +Json_Wang_Color :: struct { + class: string, + color: string, + name: string, + probability: f64, + properties: []Json_Property, + tile: i32, +} + +Json_Wang_Tile :: struct { + tileid: i32, + wangid: []u8, +} + +Json_Object_Template :: struct { + tileset: Maybe(Json_Tile_Set), + object: Json_Object, +} + +Json_Property :: struct { + name: string, + type: string, + propertytype: string, + value: any, +} + +Json_Point :: struct { + x: f64, + y: f64, +} diff --git a/src/tiled/tiled.odin b/src/tiled/tiled.odin new file mode 100644 index 0000000..e1627a5 --- /dev/null +++ b/src/tiled/tiled.odin @@ -0,0 +1,585 @@ +package tiled + +import os "core:os/os2" +import mem "core:mem" +import strings "core:strings" +import "core:path/filepath" +import "core:log" +import "core:encoding/json" + +import rl "vendor:raylib" + +Color :: [4]f32 + +Error :: enum { + NONE = 0, + COULD_NOT_LOAD, + NON_ORTHOGONAL_NOT_SUPPORTED, + INFINITE_NOT_SUPPORTED, + ELLIPSE_OBJ_NOT_SUPPORTED, + POLYGON_OBJ_NOT_SUPPORTED, + POLYLINE_OBJ_NOT_SUPPORTED, + TEXT_OBJ_NOT_SUPPORTED, +} + +Axis :: enum { + X, + Y, +} + +Properties :: map[string]any + +Map :: struct { + // Currently, only orthogonal maps are allowed. Any non-orthogonal maps + // throw an error. + // Infinite maps will also throw an error for now. + + background_color: Color, + class: string, + + width: i32, + height: i32, + + layers: []Layer, + + next_layer_id: i32, + next_object_id: i32, + + parallax_origin_x: f64, + parallax_origin_y: f64, + + properties: Properties, + + // stagger_axis: Axis, + // stagger_offset: i32, // To replace stagger index. + + tile_width: i32, + tile_height: i32, + + tile_sets: []Tile_Set, + + tiles: [dynamic]Tile, +} + +Layer :: struct { + // Layers that aren't visible are entirely ignored. + name: string, + properties: Properties, + + parallax_x: f64, + parallax_y: f64, + + tint: Color, + id: i32, + + layer: union #no_nil { + Tile_Layer, + Image_Layer, + Object_Layer, + } +} + +Tile_Layer :: struct { + // Data will automatically become uncompressed. + width: i32, + height: i32, + data: []i32, +} + +Image_Layer :: struct { + texture: rl.Texture2D, + transparent_color: Color, + repeat_x: bool, + repeat_y: bool, +} + +Object_Layer :: []Object + +Object_Type :: enum { + POINT, + TILE, + RECT, +} + +Object :: struct { + // For now, anything that *isn't* one of these is ignored and + // a warning is thrown: + // - Point + // - Tile + // - Rectangle + // + // Objects that aren't visible will be ignored entirely. + + type: Object_Type, + name: string, + class: string, + properties: Properties, + position: [2]f32, + size: [2]f32, + tile_id: i32, +} + +Tile_Set :: struct { + // For now, any autotiling information is ignored. + + properties: Properties, + + rows: i32, + columns: i32, + + first_gid: i32, + + texture: rl.Texture2D, + margin: i32, + spacing: i32, + + tile_count: i32, + tile_width: i32, + tile_height: i32, + tile_offset: [2]i32, // in pixels + + tiles: []Tile, +} + +Tile :: struct { + // For now, animations are ignored. Support in the future is somewhat likely. + // And similar to above, autotiling information is ignored. + + is_image_collection: bool, + + tile_set: i32, // index for the tile set in Map + + id: i32, + gid: i32, + + texture: Maybe(rl.Texture2D), // If the tile is part of an image collection. + + // Where on the texture is this tile + src_start: [2]i32, + src_size: [2]i32, + + object_group: Layer, // Object layer for collisions. + + properties: Properties, +} + +load_map :: proc(path: string) -> (Map, Error) { + defer free_all(context.temp_allocator) + + jmap_text, read_err := os.read_entire_file(path, context.temp_allocator) + if read_err != nil { + log.errorf("Failed to read file %v (%v)", path, read_err) + return {}, .COULD_NOT_LOAD + } + + jmap: Json_Map + unmarshal_err := json.unmarshal( + jmap_text, + &jmap, + allocator = context.temp_allocator, + ) + if unmarshal_err != nil { + log.errorf("Failed to unmarshal file %v (%v)", path, unmarshal_err) + return {}, .COULD_NOT_LOAD + } + + log.info(jmap) + + tmap: Map + err := convert_json_map(jmap, &tmap, path) + if err != .NONE { + return {}, err + } + return tmap, .NONE +} + +delete_map :: proc(tmap: Map) { + delete(tmap.class) + delete(tmap.properties) + + for layer in tmap.layers { + delete_layer(layer) + } + delete(tmap.layers) + + for tile_set in tmap.tile_sets { + delete(tile_set.properties) + delete(tile_set.tiles) // all tiles are deleted later + rl.UnloadTexture(tile_set.texture) + } + delete(tmap.tile_sets) + + for tile in tmap.tiles { + delete(tile.properties) + + if tile.texture != nil { + rl.UnloadTexture(tile.texture.(rl.Texture2D)) + } + + delete_layer(tile.object_group) + } + delete(tmap.tiles) +} + +@(private) +delete_layer :: proc(layer: Layer) { + delete(layer.name) + delete(layer.properties) + + switch l in layer.layer { + case Tile_Layer: + delete(l.data) + case Image_Layer: + rl.UnloadTexture(l.texture) + case Object_Layer: + for obj in l { + delete(obj.name) + delete(obj.class) + delete(obj.properties) + } + delete(l) + } +} + +@(private) +convert_json_color :: proc(jcolor: string) -> Color { + return Color{1, 1, 1, 1} +} + +@(private) +convert_json_properties :: proc(jprops: []Json_Property) -> Properties { + props := make(Properties) + + for jprop in jprops { + props[jprop.name] = jprop.value + } + + return props +} + +@(private) +convert_json_map :: proc(jmap: Json_Map, tmap: ^Map, path: string) -> Error { + // ensure the map is orthogonal + if strings.compare(jmap.orientation, "orthogonal") != 0 { + return .NON_ORTHOGONAL_NOT_SUPPORTED + } + + // ensure the map is not infinite + if jmap.infinite { + return .INFINITE_NOT_SUPPORTED + } + + tmap.background_color = convert_json_color(jmap.backgroundcolor) + tmap.class = strings.clone(jmap.class) + + tmap.width = jmap.width + tmap.height = jmap.height + + tmap.tile_width = jmap.tilewidth + tmap.tile_height = jmap.tileheight + + tmap.next_layer_id = jmap.nextlayerid + tmap.next_object_id = jmap.nextobjectid + + tmap.parallax_origin_x = jmap.parallaxoriginx + tmap.parallax_origin_y = jmap.parallaxoriginy + + tmap.properties = convert_json_properties(jmap.properties) + + // TODO: flatten groups + layer_count := len(jmap.layers) + for jlayer in jmap.layers { + layer_count += len(jlayer.layers) // take into consideration groups + } + + tmap.layers = make([]Layer, layer_count) + current_index := 0 + + for jlayer in jmap.layers { + if !jlayer.visible { + continue // ignore hidden layers + } + + // FIXME: Some duplicated code down here + if strings.compare(jlayer.type, "group") == 0 { + for jlayer2 in jlayer.layers { + if !jlayer2.visible { + continue // ignore hidden layers + } + + layer, err := convert_json_layer(jlayer2) + if err != .NONE { + return err + } + tmap.layers[current_index] = layer + current_index += 1 + } + continue + } + + layer, err := convert_json_layer(jlayer) + if err != .NONE { + return err + } + tmap.layers[current_index] = layer + current_index += 1 + } + + tmap.tile_sets = make([]Tile_Set, len(jmap.tilesets)) + + res_dir := filepath.dir(path, allocator = context.temp_allocator) + + for jtile_set, i in jmap.tilesets { + tile_set, err := convert_json_tile_set(tmap, res_dir, jtile_set) + if err != .NONE { + return err + } + tmap.tile_sets[i] = tile_set + + for j in int(tile_set.first_gid)..<len(tmap.tiles) { + tile_set.tiles[j].tile_set = i32(i) + } + } + + return .NONE +} + +draw_map :: proc(tmap: Map) { + for layer in tmap.layers { + tile_layer, ok := layer.layer.(Tile_Layer) + if !ok { + continue + } + + x: i32 = 0 + y: i32 = 0 + + for cell in tile_layer.data { + if cell != 0 { + tile := tmap.tiles[cell - 1] + tile_set := tmap.tile_sets[tile.tile_set] + + rl.DrawTexturePro( + tile_set.texture, + rl.Rectangle{ + x = f32(tile.src_start.x), + y = f32(tile.src_start.y), + width = f32(tile.src_size.x), + height = f32(tile.src_size.y), + }, + rl.Rectangle{ + x = f32(x), + y = f32(y), + width = f32(tile.src_size.x), + height = f32(tile.src_size.y), + }, + {0, 0}, + 0, + rl.WHITE, + ) + } + + x += tmap.tile_width + if x > (tmap.width - 1) * tmap.tile_width { + x = 0 + y += tmap.tile_height + } + } + } +} + +@(private) +load_image :: proc(filename: string, res_dir: string) -> rl.Texture2D { + path := strings.concatenate({res_dir, "/", filename}) + defer delete(path) + + byte_path := make([]u8, len(path) + 1, context.temp_allocator) + copy(byte_path, path) + byte_path[len(path)] = 0 + return rl.LoadTexture(transmute(cstring)&byte_path[0]) +} + +@(private) +convert_json_layer :: proc(jlayer: Json_Layer) -> (Layer, Error) { + // TODO: uncompress tile data + + layer: Layer + + layer.name = strings.clone(jlayer.name) + + layer.parallax_x = jlayer.parallaxx + layer.parallax_y = jlayer.parallaxy + + layer.id = jlayer.id + + layer.tint = convert_json_color(jlayer.tintcolor) + + layer.properties = convert_json_properties(jlayer.properties) + + switch jlayer.type { + case "tilelayer": + tile_layer := Tile_Layer{ + width = jlayer.width, + height = jlayer.height, + } + + data := jlayer.data.([]i32) + tile_layer.data = make([]i32, len(data)) + copy(tile_layer.data, data) + + layer.layer = tile_layer + + case "objectgroup": + obj_layer := make(Object_Layer, len(jlayer.objects)) + + for jobj, i in jlayer.objects { + if !jobj.visible { + continue // ignore hidden objects + } + + obj, err := convert_json_object(jobj) + if err != .NONE { + return {}, err + } + obj_layer[i] = obj + } + + layer.layer = obj_layer + + case "imagelayer": + image_layer := Image_Layer{ + texture = load_image(jlayer.image, "res"), + transparent_color = convert_json_color(jlayer.transparentcolor), + repeat_x = jlayer.repeatx, + repeat_y = jlayer.repeaty, + } + + layer.layer = image_layer + + case "group": + // groups are handled elsewhere + } + + return layer, .NONE +} + +@(private) +convert_json_object :: proc(jobj: Json_Object) -> (Object, Error) { + obj: Object + + if jobj.ellipse { + return {}, .ELLIPSE_OBJ_NOT_SUPPORTED + } + if jobj.polygon != nil { + return {}, .POLYGON_OBJ_NOT_SUPPORTED + } + if jobj.polyline != nil { + return {}, .POLYLINE_OBJ_NOT_SUPPORTED + } + if jobj.text != nil { + return {}, .TEXT_OBJ_NOT_SUPPORTED + } + + obj.position = { + f32(jobj.x), + f32(jobj.y), + } + obj.size = { + f32(jobj.width), + f32(jobj.height), + } + obj.tile_id = jobj.gid + + obj.name = strings.clone(jobj.name) + obj.class = strings.clone(jobj.type) + obj.properties = convert_json_properties(jobj.properties) + + return obj, .NONE +} + +@(private) +convert_json_tile_set :: proc( + tmap: ^Map, + res_dir: string, + jtile_set: Json_Tile_Set, +) -> (Tile_Set, Error) { + if len(jtile_set.source) > 0 { + // Tileset links somewhere else + path := strings.concatenate({res_dir, "/", jtile_set.source}) + defer delete(path) + + data, err := os.read_entire_file(path, allocator = context.temp_allocator) + if err != nil { + log.errorf("Could not load external tile set '%v'", path) + return {}, .COULD_NOT_LOAD + } + + new_jtile_set: Json_Tile_Set + marshal_err := json.unmarshal( + data, + &new_jtile_set, + allocator = context.temp_allocator, + ) + if marshal_err != nil { + log.errorf("Could not unmarshal external tile set '%v'", path) + return {}, .COULD_NOT_LOAD + } + + return convert_json_tile_set(tmap, res_dir, new_jtile_set) + } + + tile_set: Tile_Set + + tile_set.rows = jtile_set.rows + tile_set.columns = jtile_set.columns + + tile_set.first_gid = jtile_set.firstgid + + tile_set.texture = load_image(jtile_set.image, res_dir) + tile_set.margin = jtile_set.margin + tile_set.spacing = jtile_set.spacing + + tile_set.tile_count = jtile_set.tilecount + tile_set.tile_width = jtile_set.tilewidth + tile_set.tile_height = jtile_set.tileheight + tile_set.tile_offset = { + jtile_set.tileoffset.x, + jtile_set.tileoffset.y, + } + + tile_set.properties = convert_json_properties(jtile_set.properties) + + tile_set.tiles = make([]Tile, jtile_set.tilecount) + + // Ignore margin and spacing for now + // TODO: account for margin and spacing + tile_x := tile_set.texture.width / tile_set.tile_width + tile_y := tile_set.texture.height / tile_set.tile_height + + current_index := 0 + for y in 0..<tile_y { + for x in 0..<tile_x { + tile := &tile_set.tiles[current_index] + + tile.id = i32(current_index) + + tile.src_start = { + x * tile_set.tile_width, + y * tile_set.tile_height, + } + tile.src_size = { + tile_set.tile_width, + tile_set.tile_height, + } + + tile.gid = i32(len(tmap.tiles)) + append(&tmap.tiles, tile^) + + current_index += 1 + } + } + + // TODO: here, go through jtile_set.tiles and load in more tile data + + return tile_set, .NONE +} |
