diff --git a/.vscode/tasks.json b/.vscode/tasks.json index b1eca51..edc8dcf 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -59,7 +59,13 @@ { "label": "Build&Run Tile Generator Test", "type": "shell", - "command": "odin run src/aseprite_odin_generator -out:build/aseprite_odin_generator.exe", + "command": "odin run", + "args": [ + "src/aseprite_odin_generator", + "-define:RAYLIB_SHARED=true", + "-out:build_generator/aseprite_odin_generator.exe", + "-debug" + ], "options": { "cwd": "${workspaceFolder}" }, diff --git a/src/aseprite_odin_generator/aseprite_odin_generator.odin b/src/aseprite_odin_generator/aseprite_odin_generator.odin new file mode 100644 index 0000000..b0012e9 --- /dev/null +++ b/src/aseprite_odin_generator/aseprite_odin_generator.odin @@ -0,0 +1,44 @@ +package generator + +import ase "../aseprite" +import "core:fmt" +import "core:mem" +import "core:os" +import fp "core:path/filepath" +import "core:slice" +import "core:strings" +import "core:testing" +import rl "vendor:raylib" + +import gen ".." + +ATLAS_SIZE :: 512 +IMPORT_PATH :: "./src/aseprite_odin_generator/big.aseprite" +EXPORT_PATH :: "./src/aseprite_odin_generator/atlas.png" + +main :: proc() { + fmt.println("Hello!") + ase_file, ase_ok := os.read_entire_file(IMPORT_PATH) + if !ase_ok { + fmt.panicf("Couldn't load file!") + } + + doc: ase.Document + read, um_err := ase.unmarshal_from_slice(ase_file, &doc) + if um_err != nil { + fmt.panicf("Couldn't unmarshall file!") + } else { + fmt.printfln("Read {0} bytes from file", read) + } + + fmt.println("Header:\n\t", doc.header) + // fmt.println("Frames:\n\t", doc.frames) + + atlas: rl.Image = rl.GenImageColor(ATLAS_SIZE, ATLAS_SIZE, rl.BLANK) + + atlas_entry := gen.atlas_entry_from_compressed_cells(doc) + // Packs the cells & blits them to the atlas + gen.pack_atlas_entries({atlas_entry}, &atlas) + + rl.ExportImage(atlas, EXPORT_PATH) +} diff --git a/src/aseprite_odin_generator/big.aseprite b/src/aseprite_odin_generator/big.aseprite index bfc55aa..e656ff8 100644 Binary files a/src/aseprite_odin_generator/big.aseprite and b/src/aseprite_odin_generator/big.aseprite differ diff --git a/src/aseprite_odin_generator/generator.odin b/src/aseprite_odin_generator/generator.odin deleted file mode 100644 index fd18903..0000000 --- a/src/aseprite_odin_generator/generator.odin +++ /dev/null @@ -1,82 +0,0 @@ -package generator - -import ase "../aseprite" -import "core:fmt" -import "core:mem" -import "core:os" -import fp "core:path/filepath" -import "core:slice" -import "core:strings" -import "core:testing" -import rl "vendor:raylib" - -ATLAS_SIZE :: 512 -EXPORT_PATH :: "E:/dev/odin-atlas-packer/src/aseprite_odin_generator/atlas.png" - -main :: proc() { - fmt.println("Hello!") - ase_file, ase_ok := os.read_entire_file( - "E:/dev/odin-atlas-packer/src/aseprite_odin_generator/big.aseprite", - ) - if !ase_ok { - fmt.panicf("Couldn't load file!") - } - - doc: ase.Document - read, um_err := ase.unmarshal_from_slice(ase_file, &doc) - if um_err != nil { - fmt.panicf("Couldn't unmarshall file!") - } else { - fmt.printfln("Read {0} bytes from file", read) - } - - fmt.println("Header:\n\t", doc.header) - // fmt.println("Frames:\n\t", doc.frames) - - images: [dynamic]rl.Image - atlas: rl.Image = rl.GenImageColor(ATLAS_SIZE, ATLAS_SIZE, rl.BLANK) - - for frame in doc.frames { - for chunk in frame.chunks { - cel_chunk, cok := chunk.(ase.Cel_Chunk) - if !cok { - continue - } - - cel_img, ci_ok := cel_chunk.cel.(ase.Com_Image_Cel) - if !ci_ok { - continue - } - append( - &images, - rl.Image { - data = rawptr(&cel_img.pixel[0]), - width = auto_cast cel_img.width, - height = auto_cast cel_img.height, - format = .UNCOMPRESSED_R8G8B8A8, - }, - ) - } - } - curr_x, curr_y: i32 - for img, img_i in images { - fmt.printfln("Image_{0}: {1}", img_i, img) - rl.ImageDraw( - &atlas, - img, - {0, 0, auto_cast img.width, auto_cast img.height}, - {auto_cast curr_x, auto_cast curr_y, auto_cast img.width, auto_cast img.height}, - rl.WHITE, - ) - curr_x += img.width - curr_y += img.height - } - - // todo: pack the rectangles - - // todo: blit them to the atlas - - // todo: generate metadata (json, odin enums) - - rl.ExportImage(atlas, EXPORT_PATH) -} diff --git a/src/generator.odin b/src/generator.odin new file mode 100644 index 0000000..3886bc3 --- /dev/null +++ b/src/generator.odin @@ -0,0 +1,147 @@ +package game + +import ase "./aseprite" +import "core:fmt" +import "core:os" +import fp "core:path/filepath" +import rl "vendor:raylib" +import stbrp "vendor:stb/rect_pack" + +AtlasEntry :: struct { + path: string, + cells: [dynamic]rl.Image, +} + +unmarshall_aseprite_dir :: proc(path: string, atlas_entries: ^[dynamic]AtlasEntry) { + fp.dir(path) + if fd, ok := os.open(path); ok == 0 { + if fi_files, fi_ok := os.read_dir(fd, -1); fi_ok == 0 { + unmarshall_aseprite_files_file_info(fi_files, atlas_entries) + } + } +} + +unmarshall_aseprite_files_file_info :: proc( + files: []os.File_Info, + atlas_entries: ^[dynamic]AtlasEntry, +) { + paths: [dynamic]string + for f in files { + append(&paths, f.fullpath) + } + unmarshall_aseprite_files(paths[:], atlas_entries) +} + +unmarshall_aseprite_files :: proc(file_paths: []string, atlas_entries: ^[dynamic]AtlasEntry) { + current_document: ase.Document + for fp in file_paths { + atlas_entry := atlas_entry_from_compressed_cells(current_document) + ase.unmarshal_from_filename(fp, ¤t_document) + append(atlas_entries, atlas_entry) + } +} + +/* + Goes through all the chunks in an aseprite document & copies the `Com_Image_Cel` cells in a separate image +*/ +atlas_entry_from_compressed_cells :: proc(document: ase.Document) -> (atlas_entry: AtlasEntry) { + for frame in document.frames { + for chunk in frame.chunks { + cel_chunk, cok := chunk.(ase.Cel_Chunk) + if !cok { + continue + } + + cel_img, ci_ok := cel_chunk.cel.(ase.Com_Image_Cel) + if !ci_ok { + continue + } + append( + &atlas_entry.cells, + rl.Image { + data = rawptr(&cel_img.pixel[0]), + width = auto_cast cel_img.width, + height = auto_cast cel_img.height, + format = .UNCOMPRESSED_R8G8B8A8, + }, + ) + } + } + return +} + +/* + Takes in a slice of entries, an output texture, the width & height +*/ +pack_atlas_entries :: proc( + entries: []AtlasEntry, + atlas: ^rl.Image, + offset_x: int = 0, + offset_y: int = 0, +) { + all_entries: [dynamic]rl.Image // it's fine to store it like this, rl.Image just stores a pointer to the data + // todo: set up the stb_rect_pack rectangles + { + for entry in entries { + append(&all_entries, ..entry.cells[:]) + } + } + + num_entries := len(all_entries) + nodes := make([]stbrp.Node, num_entries) + rects := make([]stbrp.Rect, num_entries) + + EntryAndCell :: struct { + entry: ^AtlasEntry, + cell_of_entry: ^rl.Image, + } + rect_idx_to_entry_and_cell: map[int]EntryAndCell + + // Set the custom IDs + cellIdx: int + for &entry, entryIdx in entries { + for &cell in entry.cells { + // I can probably infer this information with just the id of the rect but I'm being lazy right now + map_insert(&rect_idx_to_entry_and_cell, cellIdx, EntryAndCell{&entry, &cell}) + rects[cellIdx].id = auto_cast entryIdx + cellIdx += 1 + } + } + + for entry, entryIdx in all_entries { + entry_stb_rect := &rects[entryIdx] + entry_stb_rect.w = auto_cast entry.width + entry_stb_rect.h = auto_cast entry.height + } + + ctx: stbrp.Context + stbrp.init_target(&ctx, atlas.width, atlas.height, &nodes[0], auto_cast num_entries) + res := stbrp.pack_rects(&ctx, &rects[0], auto_cast num_entries) + if bool(res) { + fmt.println("Packed everything successfully!") + fmt.printfln("Rects: {0}", rects[:]) + } else { + fmt.println("Failed to pack everything!") + } + + for rect, rectIdx in rects { + entry_and_cell := rect_idx_to_entry_and_cell[auto_cast rectIdx] + cell := entry_and_cell.cell_of_entry + // We're grabbing the whole cell (the image itself) + src_rect := rl.Rectangle { + x = 0, + y = 0, + width = auto_cast cell.width, + height = auto_cast cell.height, + } + // Placing it in the atlas in the calculated offsets (in the packing step) + dst_rect := rl.Rectangle { + auto_cast rect.x, + auto_cast rect.y, + auto_cast cell.width, + auto_cast cell.height, + } + + rl.ImageDraw(atlas, cell^, src_rect, dst_rect, rl.WHITE) + } +}