diff options
| author | iamcheeseman <[hidden email]> | 2026-01-12 20:51:28 -0500 |
|---|---|---|
| committer | iamcheeseman <[hidden email]> | 2026-01-12 20:51:28 -0500 |
| commit | 7fb83578b99aa224f7545f4118a46e84b58a9295 (patch) | |
| tree | 515e294ac7de167c4501cc0a8d375b13213faf36 /tools | |
| parent | 2b3a3ea9f4bc902b1b357fd149952d4570b25bf3 (diff) | |
NEW ASSET SYSTEM WOOOOOOOOHOOOOOOOOOOOOOOOOOOOOOOOOO
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/compile_assets/loaders.odin | 97 | ||||
| -rw-r--r-- | tools/compile_assets/main.odin | 129 | ||||
| -rw-r--r-- | tools/compile_assets/tiled.odin | 447 |
3 files changed, 604 insertions, 69 deletions
diff --git a/tools/compile_assets/loaders.odin b/tools/compile_assets/loaders.odin index 136ba07..648fe49 100644 --- a/tools/compile_assets/loaders.odin +++ b/tools/compile_assets/loaders.odin @@ -11,20 +11,35 @@ import "core:image/qoi" import ase "aseprite" import aseutil "aseprite/utils" -load_map :: proc(path: string, _: ^os.File, _: ^os.File) { - line := fmt.aprintf("#load(\"%v\")", path) - maps[filepath.stem(path)] = line - paths_to_res_type[path] = "Map_Id" +load_world :: proc(path: string, file: ^os.File, _: ^os.File) { + load_json_world(path) +} + +load_room :: proc(path: string, file: ^os.File, _: ^os.File) { + load_json_room(path, file) + paths_to_res_type[path] = "Room_Id" } load_tileset :: proc(path: string, _: ^os.File, _: ^os.File) { - line := fmt.aprintf("#load(\"%v\")", path) - tilesets[filepath.stem(path)] = line - paths_to_res_type[path] = "Tileset_Id" + load_json_tileset(path) } load_qoi :: proc(path: string, qoi: ^os.File, output: ^os.File) { - line := fmt.aprintf("{{data = #load(%w)}}", path) + rel_path, rel_err := filepath.rel( + output_dir, + path, + context.temp_allocator, + ) + if rel_err != nil { + die( + "Could not find relative path from %v to %v (%v)", + output_dir, + path, + rel_err, + ) + } + + line := fmt.aprintf("{{data = #load(%w), anim = .NONE}}", rel_path) images[filepath.stem(path)] = line paths_to_res_type[path] = "Image_Id" @@ -60,22 +75,39 @@ load_png :: proc(path: string, png_file: ^os.File, output: ^os.File) { die("Could not convert PNG to QOI (%v)", qoi_err) } - absolute_path, abs_ok := filepath.abs( + abs_path, found_abs := filepath.abs( compiled_path, - allocator = context.temp_allocator, + context.temp_allocator ) - if !abs_ok { - die("Could not find absolute path to a compiled file (%v)", compiled_path) + if !found_abs { + die("Could not find absolute path for %v", compiled_path) } - line := fmt.aprintf("{{data = #load(%w)}}", absolute_path) + rel_path, rel_err := filepath.rel( + output_dir, + abs_path, + context.temp_allocator, + ) + if rel_err != nil { + die( + "Could not find relative path from %v to %v (%v)", + output_dir, + compiled_path, + rel_err, + ) + } + + line := fmt.aprintf("{{data = #load(%w), anim = .NONE}}", rel_path) images[filepath.stem(path)] = line paths_to_res_type[path] = "Image_Id" } @(private="file") -load_sprite_sheet :: proc(path: string, doc: ^ase.Document) { +load_sprite_sheet :: proc( + path: string, + doc: ^ase.Document +) { sprite_sheet, ss_err := aseutil.create_sprite_sheet(doc, { size = {int(doc.header.width), int(doc.header.height)}, count = int(doc.header.frames), @@ -89,8 +121,6 @@ load_sprite_sheet :: proc(path: string, doc: ^ase.Document) { } defer aseutil.destroy(sprite_sheet) - fmt.println("loaded ss") - pixels := make( []image.RGBA_Pixel, sprite_sheet.width * sprite_sheet.height, @@ -126,15 +156,34 @@ load_sprite_sheet :: proc(path: string, doc: ^ase.Document) { die("Could not save spritesheet %v (%v)", path, qoi_err) } - absolute_path, abs_ok := filepath.abs( + abs_path, found_abs := filepath.abs( compiled_path, - allocator = context.temp_allocator, + context.temp_allocator + ) + if !found_abs { + die("Could not find absolute path for %v", compiled_path) + } + + rel_path, rel_err := filepath.rel( + output_dir, + abs_path, + context.temp_allocator, ) - if !abs_ok { - die("Could not find absolute path to a compiled file (%v)", compiled_path) + if rel_err != nil { + die( + "Could not find relative path from %v to %v (%v)", + output_dir, + compiled_path, + rel_err, + ) } - line := fmt.aprintf("{{data = #load(%w)}}", absolute_path) + anim_name := strings.to_screaming_snake_case( + filepath.stem(path), + allocator = context.temp_allocator, + ) + + line := fmt.aprintf("{{data = #load(%w), anim = .%v}}", rel_path, anim_name) images[filepath.stem(path)] = line paths_to_res_type[path] = "Image_Id" } @@ -191,9 +240,9 @@ load_ase :: proc(path: string, ase_file: ^os.File, output: ^os.File) { die("Could not unmarshal aseprite file %v (%v)", path, unmarshal_err) } - // Load sprite sheet - load_sprite_sheet(path, &doc) - // Load animation load_animation(path, &doc) + + // Load sprite sheet + load_sprite_sheet(path, &doc) } diff --git a/tools/compile_assets/main.odin b/tools/compile_assets/main.odin index 60c8a8c..68c5625 100644 --- a/tools/compile_assets/main.odin +++ b/tools/compile_assets/main.odin @@ -27,8 +27,8 @@ Image_Id :: enum { Animation_Id :: enum { <anim-enum>} -Map_Id :: enum { -<map-enum>} +Room_Id :: enum { +<room-enum>} Tileset_Id :: enum { <tileset-enum>} @@ -36,49 +36,46 @@ Tileset_Id :: enum { Resource_Id :: union { Image_Id, Animation_Id, - Map_Id, + Room_Id, Tileset_Id, } -images: [Image_Id]Image_Resource -animations: [Animation_Id]Animation_Resource -maps: [Map_Id]Map_Resource -tilesets: [Tileset_Id]Tileset_Resource +images: [Image_Id]Image_Resource = { +<image-load>} -path_to_id: map[string]Resource_Id +animations: [Animation_Id]Animation_Resource = { +<anim-load>} -load_resources :: proc() { - load_images() - load_anims() - load_maps() - load_tilesets() +rooms: [Room_Id]Room_Resource = { +<room-load>} - // Allow conversion from paths to a resource id, since it's a better way to - // reference resources in other resources (JSON is a good example). -<resource-paths>} +tilesets: [Tileset_Id]Tileset_Resource = { +<tileset-load>} -@(private="file") -load_images :: proc() { -<image-load>} +tiles: []Tile_Resource = { +<tiles-load>} -@(private="file") -load_anims :: proc() { -<anim-load>} +world: []Room_Position_Resource = { +<room-positions>} -@(private="file") -load_maps :: proc() { -<maps-load>} +path_to_id: map[string]Resource_Id = { +<resource-paths>} -@(private="file") -load_tilesets :: proc() { -<tileset-load>} +load_resources :: proc() { +} ` images: map[string]string animations: map[string]string -maps: map[string]string +rooms: map[string]string + +tiles: [dynamic]string tilesets: map[string]string +world: [dynamic]string + +output_dir: string + paths_to_res_type: map[string]string die :: proc(msg: string, args: ..any, exit_code := 1) { @@ -168,13 +165,11 @@ create_loads :: proc( for element in elements { load = strings.concatenate({ load, - " ", - map_name, - "[.", + " .", strings.to_upper_snake_case(element, context.temp_allocator), - "] = ", + " = ", elements[element], - "\n", + ",\n", }, allocator = context.temp_allocator) } return set_placeholder(content, placeholder, load) @@ -199,9 +194,29 @@ main :: proc() { print_help() } + // add dummy + append(&tiles, "") + + // Need to copy, cause you can't just free executable memory, you fool + no_anim_line := "{frame_count=1, frame_durations={100}, tags={}}" + no_anim_line_owned := make([]u8, len(no_anim_line)) + copy_from_string(no_anim_line_owned, no_anim_line) + animations["NONE"] = string(no_anim_line_owned) + input_dir := os.args[1] output_file_path := os.args[2] + abs_output_path, found_abs := filepath.abs( + output_file_path, + context.temp_allocator + ) + if !found_abs { + die("Could not find absolute path to output file") + } + + output_dir = filepath.dir(abs_output_path) + defer delete(output_dir) + output_file, open_err := os.open(output_file_path, {.Write, .Create, .Trunc}) if open_err != nil { die("Could not create output file '%v' (%v)", output_file_path, open_err) @@ -233,8 +248,8 @@ main :: proc() { loaders: map[string]Asset_Loader defer delete(loaders) - loaders["tmj"] = load_map - // loaders["world"] = load_json + loaders["tmj"] = load_room + loaders["world"] = load_world loaders["tsj"] = load_tileset loaders["qoi"] = load_qoi loaders["png"] = load_png @@ -248,7 +263,7 @@ main :: proc() { loader, has := &loaders[ext] if !has { fmt.printfln( - "%-25v Skipped\tNo loader for '%v'", + "%-25v Skipped\tNo loader for '.%v'", file.name, ext, ) @@ -262,7 +277,6 @@ main :: proc() { } defer os.close(f) - fmt.printfln("%-25v Loading...", file.name) loader^(file.fullpath, f, output_file) fmt.printfln("%-25v Loaded", file.name) @@ -276,14 +290,39 @@ main :: proc() { content = create_enum(content, "<image-enum>", images) content = create_enum(content, "<anim-enum>", animations) - content = create_enum(content, "<map-enum>", maps) + content = create_enum(content, "<room-enum>", rooms) content = create_enum(content, "<tileset-enum>", tilesets) content = create_loads(content, "images", "<image-load>", images) content = create_loads(content, "animations", "<anim-load>", animations) - content = create_loads(content, "maps", "<maps-load>", maps) + content = create_loads(content, "rooms", "<room-load>", rooms) content = create_loads(content, "tilesets", "<tileset-load>", tilesets) + tile_loads := "" + for tile, i in tiles { + if i == 0 { + continue + } + tile_loads = strings.concatenate({ + tile_loads, + " ", + tile, + ",\n", + }, allocator = context.temp_allocator) + } + content = set_placeholder(content, "<tiles-load>", tile_loads) + + room_pos_loads := "" + for room_pos, i in world { + room_pos_loads = strings.concatenate({ + room_pos_loads, + " ", + room_pos, + ",\n", + }, allocator = context.temp_allocator) + } + content = set_placeholder(content, "<room-positions>", room_pos_loads) + res_paths := "" cwd, _ := os.get_working_directory(context.temp_allocator) @@ -300,13 +339,13 @@ main :: proc() { rel_path, _ := filepath.rel(cwd, file.fullpath, context.temp_allocator) res_paths = strings.concatenate({ res_paths, - " path_to_id[\"", + " \"", rel_path, - "\"] = ", + "\" = ", res_type, ".", res_name, - "\n", + ",\n", }, allocator = context.temp_allocator) } content = set_placeholder(content, "<resource-paths>", res_paths) @@ -319,8 +358,8 @@ main :: proc() { for anim in animations { delete(animations[anim]) } - for tmap in maps { - delete(maps[tmap]) + for room in rooms { + delete(rooms[room]) } free_all(context.temp_allocator) diff --git a/tools/compile_assets/tiled.odin b/tools/compile_assets/tiled.odin new file mode 100644 index 0000000..7752273 --- /dev/null +++ b/tools/compile_assets/tiled.odin @@ -0,0 +1,447 @@ +package assets_gen + +import os "core:os/os2" +import "core:fmt" +import "core:encoding/json" +import "core:path/filepath" +import "core:strings" + +Rect :: struct { + start: [2]f32, + size: [2]f32, +} + +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_Tileset, +} + +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_Tileset :: 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_Tileset), + object: Json_Object, +} + +Json_Property :: struct { + name: string, + type: string, + propertytype: string, + value: any, +} + +Json_Point :: struct { + x: f64, + y: f64, +} + +Json_World_Room :: struct { + file_name: string `json:"fileName"`, + x: i32, + y: i32, + width: i32, + height: i32, +} + +Json_World :: struct { + maps: []Json_World_Room, +} + +Gids :: struct { + first: u32, + last: u32, +} + +gids: map[string]Gids + +load_json_world :: proc(path: string) { + json_text, read_err := os.read_entire_file(path, context.temp_allocator) + if read_err != nil { + die("Could not load world %v (%v)", path, read_err) + } + + json_dat: Json_World + unmarshal_err := json.unmarshal( + json_text, + &json_dat, + allocator = context.temp_allocator, + ) + if unmarshal_err != nil { + die("Could not parse world %v (%v)", path, unmarshal_err) + } + + for room in json_dat.maps { + room_name := strings.to_screaming_snake_case( + filepath.stem(room.file_name), + allocator = context.temp_allocator, + ) + + line := fmt.aprintf( + "{{id=.%v, x=%w, y=%w, width=%w, height=%w}}", + room_name, + room.x, + room.y, + room.width, + room.height, + ) + append(&world, line) + } +} + +create_tile :: proc( + tileset_enum: string, + rect: Rect, + collisions: []Rect, +) -> string { + line := fmt.aprintf( + "{{tileset=.%v, rect=%w, id=%w, collisions=%w}}", + tileset_enum, + rect, + len(tiles), + collisions, + ) + append(&tiles, line) + return line +} + +load_json_tileset :: proc(path: string) { + json_text, read_err := os.read_entire_file(path, context.temp_allocator) + if read_err != nil { + die("Could not load tileset %v (%v)", path, read_err) + } + + json_dat: Json_Tileset + unmarshal_err := json.unmarshal( + json_text, + &json_dat, + allocator = context.temp_allocator, + ) + if unmarshal_err != nil { + die("Failed to parse tileset %v (%v)", path, unmarshal_err) + } + + tileset_enum := strings.to_screaming_snake_case( + filepath.stem(path), + allocator = context.temp_allocator, + ) + + image_enum := strings.to_screaming_snake_case( + filepath.stem(json_dat.image), + allocator = context.temp_allocator, + ) + + tiles_x := json_dat.imagewidth / json_dat.tilewidth + tiles_y := json_dat.imageheight / json_dat.tileheight + + local_tiles := make([dynamic]string, allocator = context.temp_allocator) + + tile_size: [2]f32 = {f32(json_dat.tilewidth), f32(json_dat.tileheight)} + + first_gid := u32(len(tiles)) + + gids[filepath.stem(path)] = { + first = first_gid, + last = u32(len(tiles) + int(json_dat.tilecount)), + } + + collisions := make([][dynamic]Rect, json_dat.tilecount) + defer { + for collision in collisions { + delete(collision) + } + delete(collisions) + } + + for tile in json_dat.tiles { + collision := &collisions[tile.id] + for object in tile.objectgroup.objects { + append( + collision, + Rect{ + start = {f32(object.x), f32(object.y)}, + size = {f32(object.width), f32(object.height)} + } + ) + } + } + + for y in 0..<tiles_y { + for x in 0..<tiles_x { + append(&local_tiles, create_tile( + tileset_enum, + Rect{ + start = {f32(x * json_dat.tilewidth), f32(y * json_dat.tileheight)}, + size = tile_size, + }, + collisions[y * tiles_x + x][:], + )) + } + } + + gids := make([]u32, len(local_tiles)) + for tile, i in local_tiles { + gids[i] = first_gid + u32(i) + } + + line := fmt.aprintf("{{tiles=%w, image=.%v}}", gids, image_enum) + tilesets[filepath.stem(path)] = line +} + +load_json_room :: proc(path: string, file: ^os.File) { + json_text, read_err := os.read_entire_file(file, context.temp_allocator) + if read_err != nil { + die("Could not load room %v (%v)", path, read_err) + } + + json_dat: Json_Map + unmarshal_err := json.unmarshal( + json_text, + &json_dat, + allocator = context.temp_allocator, + ) + if unmarshal_err != nil { + die("Failed to parse room %v (%v)", path, unmarshal_err) + } + + width := json_dat.width + height := json_dat.height + + tile_width := json_dat.tilewidth + tile_height := json_dat.tileheight + + tiled_to_real_gid: map[i32]u32 + defer delete(tiled_to_real_gid) + + // map tiled's map-based GIDs to my actually global GIDs + id: i32 = 1 + for tileset in json_dat.tilesets { + tileset_name := filepath.stem(tileset.source) + gid, exists := gids[tileset_name] + if !exists { + load_json_tileset(strings.concatenate({ + filepath.dir(path), + "/", + tileset.source, + })) + gid = gids[tileset_name] + } + + for i in gid.first..<gid.last { + tiled_to_real_gid[id] = i + id += 1 + } + } + + layers: [dynamic][]u32 + defer delete(layers) + + for layer in json_dat.layers { + if strings.compare(layer.type, "tilelayer") != 0 { + continue + } + + json_layer_dat, ok := layer.data.([]i32) + if !ok { + continue + } + + data := make([]u32, len(json_layer_dat), allocator = context.temp_allocator) + + for cell, i in json_layer_dat { + data[i] = tiled_to_real_gid[cell] + } + + append(&layers, data) + } + + line := fmt.aprintf( + "{{width=%v, height=%v, tile_width=%v, tile_height=%v, layers=%w, objects={{}}, background_image=nil}}", + width, height, + tile_width, tile_height, + layers[:], + ) + rooms[filepath.stem(path)] = line +} |
