aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authoriamcheeseman <[hidden email]>2026-01-12 20:51:28 -0500
committeriamcheeseman <[hidden email]>2026-01-12 20:51:28 -0500
commit7fb83578b99aa224f7545f4118a46e84b58a9295 (patch)
tree515e294ac7de167c4501cc0a8d375b13213faf36 /tools
parent2b3a3ea9f4bc902b1b357fd149952d4570b25bf3 (diff)
NEW ASSET SYSTEM WOOOOOOOOHOOOOOOOOOOOOOOOOOOOOOOOOO
Diffstat (limited to 'tools')
-rw-r--r--tools/compile_assets/loaders.odin97
-rw-r--r--tools/compile_assets/main.odin129
-rw-r--r--tools/compile_assets/tiled.odin447
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
+}