diff --git a/.vscode/tasks.json b/.vscode/tasks.json index e054793..a419bf5 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -70,7 +70,7 @@ }, "group": { "kind": "build", - "isDefault": true + "isDefault": false }, }, { @@ -92,7 +92,7 @@ }, "group": { "kind": "build", - "isDefault": false + "isDefault": true }, } ] diff --git a/odinfmt.json b/odinfmt.json new file mode 100644 index 0000000..12c1d35 --- /dev/null +++ b/odinfmt.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/odinfmt.schema.json", + "character_width": 100, + "sort_imports": true, + "tabs": true +} \ No newline at end of file diff --git a/src/aseprite_odin_generator/aseprite_odin_generator.odin b/src/aseprite_odin_generator/aseprite_odin_generator.odin index d897541..225578e 100644 --- a/src/aseprite_odin_generator/aseprite_odin_generator.odin +++ b/src/aseprite_odin_generator/aseprite_odin_generator.odin @@ -1,14 +1,17 @@ package generator import ase "../aseprite" +import "core:encoding/json" import "core:fmt" import "core:mem" import "core:os" import fp "core:path/filepath" import "core:slice" -import "core:strings" +import s "core:strings" import "core:testing" + import rl "vendor:raylib" +import stbrp "vendor:stb/rect_pack" import gen ".." @@ -24,14 +27,17 @@ main :: proc() { } cwd := os.get_current_directory() - target_dir := strings.concatenate({cwd, "\\src\\aseprite_odin_generator\\"}) + target_dir := s.concatenate({cwd, "\\src\\aseprite_odin_generator\\"}) atlas: rl.Image = rl.GenImageColor(ATLAS_SIZE, ATLAS_SIZE, rl.BLANK) atlas_entries: [dynamic]gen.AtlasEntry gen.unmarshall_aseprite_dir(target_dir, &atlas_entries) + metadata := gen.pack_atlas_entries(atlas_entries[:], &atlas, 10, 10) + + json_bytes, jerr := json.marshal(metadata) + os.write_entire_file("src/aseprite_odin_generator/metadata.json", json_bytes) - gen.pack_atlas_entries(atlas_entries[:], &atlas, 10, 10) rl.ExportImage(atlas, EXPORT_PATH) } diff --git a/src/aseprite_odin_generator/atlas.png b/src/aseprite_odin_generator/atlas.png index afb2d71..94facee 100644 Binary files a/src/aseprite_odin_generator/atlas.png and b/src/aseprite_odin_generator/atlas.png differ diff --git a/src/aseprite_odin_generator/big.aseprite b/src/aseprite_odin_generator/big.aseprite index e656ff8..889aabc 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/metadata.json b/src/aseprite_odin_generator/metadata.json new file mode 100644 index 0000000..a07b92b --- /dev/null +++ b/src/aseprite_odin_generator/metadata.json @@ -0,0 +1 @@ +[{"name":"Edinica","location":[95,10],"size":[58,57]},{"name":"Dvoika_0","location":[234,10],"size":[55,31]},{"name":"Troika","location":[10,10],"size":[75,75]},{"name":"Dvoika_1","location":[163,10],"size":[61,33]}] \ No newline at end of file diff --git a/src/generator.odin b/src/generator.odin index f1ad8e3..62483a8 100644 --- a/src/generator.odin +++ b/src/generator.odin @@ -1,15 +1,41 @@ package game import ase "./aseprite" +import "core:encoding/json" import "core:fmt" +import "core:mem" import "core:os" import fp "core:path/filepath" +import "core:strings" import rl "vendor:raylib" import stbrp "vendor:stb/rect_pack" +CellData :: struct { + layer_index: u16, + opacity: u8, + frame_index: i32, + img: rl.Image, +} + AtlasEntry :: struct { - path: string, - cells: [dynamic]rl.Image, + path: string, + cells: [dynamic]CellData, + frames: i32, + layer_names: [dynamic]string, + layer_cell_count: [dynamic]i32, +} + +SingleFrameSprite :: distinct rl.Rectangle + +AnimatedSprite :: struct { + x, y: i32, + width, height: i32, +} + +SpriteAtlasMetadata :: struct { + name: string, + location: [2]i32, + size: [2]i32, } unmarshall_aseprite_dir :: proc(path: string, atlas_entries: ^[dynamic]AtlasEntry) { @@ -27,14 +53,22 @@ 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) + if len(files) == 0 do return + + paths := make([]string, len(files)) + defer delete(paths) + + for f, fi in files { + paths[fi] = f.fullpath } + unmarshall_aseprite_files(paths[:], atlas_entries) + } unmarshall_aseprite_files :: proc(file_paths: []string, atlas_entries: ^[dynamic]AtlasEntry) { + if len(file_paths) == 0 do return + aseprite_document: ase.Document for file in file_paths { extension := fp.ext(file) @@ -43,6 +77,7 @@ unmarshall_aseprite_files :: proc(file_paths: []string, atlas_entries: ^[dynamic fmt.println("Unmarshalling file: ", file) ase.unmarshal_from_filename(file, &aseprite_document) atlas_entry := atlas_entry_from_compressed_cells(aseprite_document) + atlas_entry.path = file append(atlas_entries, atlas_entry) } @@ -52,26 +87,34 @@ unmarshall_aseprite_files :: proc(file_paths: []string, atlas_entries: ^[dynamic 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 { + atlas_entry.frames = auto_cast len(document.frames) + fmt.println("N Frames: ", len(document.frames)) + for frame, frameIdx in document.frames { + fmt.printfln("Frame_{0} Chunks: ", frameIdx, len(frame.chunks)) for chunk in frame.chunks { - cel_chunk, cok := chunk.(ase.Cel_Chunk) - if !cok { - continue - } + if cel_chunk, ok := chunk.(ase.Cel_Chunk); ok { + cel_img, ci_ok := cel_chunk.cel.(ase.Com_Image_Cel) + if !ci_ok do continue - cel_img, ci_ok := cel_chunk.cel.(ase.Com_Image_Cel) - if !ci_ok { - continue + fmt.println(cel_chunk.layer_index) + + cell := CellData { + img = rl.Image { + data = rawptr(&cel_img.pixel[0]), + width = auto_cast cel_img.width, + height = auto_cast cel_img.height, + format = .UNCOMPRESSED_R8G8B8A8, + }, + frame_index = auto_cast frameIdx, + opacity = cel_chunk.opacity_level, + layer_index = cel_chunk.layer_index, + } + append(&atlas_entry.cells, cell) + } + if layer_chunk, ok := chunk.(ase.Layer_Chunk); ok { + fmt.println("Layer chunk: ", layer_chunk) + append(&atlas_entry.layer_names, layer_chunk.name) } - 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 @@ -80,26 +123,33 @@ atlas_entry_from_compressed_cells :: proc(document: ase.Document) -> (atlas_entr /* Takes in a slice of entries, an output texture and offsets (offset_x/y) */ -pack_atlas_entries :: proc(entries: []AtlasEntry, atlas: ^rl.Image, offset_x: i32, offset_y: i32) { - assert(atlas.width != 0, "This shouldn't be 0!") - assert(atlas.height != 0, "This shouldn't be 0!") +pack_atlas_entries :: proc( + entries: []AtlasEntry, + atlas: ^rl.Image, + offset_x: i32, + offset_y: i32, + allocator := context.allocator, +) -> [dynamic]SpriteAtlasMetadata { + assert(atlas.width != 0, "Atlas width shouldn't be 0!") + assert(atlas.height != 0, "Atlas height shouldn't be 0!") - all_entries: [dynamic]rl.Image // it's fine to store it like this, rl.Image just stores a pointer to the data - { - for entry in entries { - append(&all_entries, ..entry.cells[:]) + all_cell_images := make([dynamic]rl.Image, allocator) // it's fine to store it like this, rl.Image just stores a pointer to the data + for &entry in entries { + for cell in entry.cells { + append(&all_cell_images, cell.img) } + entry.layer_cell_count = make([dynamic]i32, len(entry.cells), allocator) } - num_entries := len(all_entries) - nodes := make([]stbrp.Node, num_entries) - rects := make([]stbrp.Rect, num_entries) + num_entries := len(all_cell_images) + nodes := make([]stbrp.Node, num_entries, allocator) + rects := make([]stbrp.Rect, num_entries, allocator) EntryAndCell :: struct { entry: ^AtlasEntry, - cell_of_entry: ^rl.Image, + cell_of_entry: ^CellData, } - rect_idx_to_entry_and_cell: map[int]EntryAndCell + rect_idx_to_entry_and_cell := make(map[int]EntryAndCell, 100, allocator) // Set the custom IDs cellIdx: int @@ -109,13 +159,15 @@ pack_atlas_entries :: proc(entries: []AtlasEntry, atlas: ^rl.Image, offset_x: i3 map_insert(&rect_idx_to_entry_and_cell, cellIdx, EntryAndCell{&entry, &cell}) rects[cellIdx].id = auto_cast entryIdx cellIdx += 1 + + entry.layer_cell_count[cell.layer_index] += 1 } } - for entry, entryIdx in all_entries { - entry_stb_rect := &rects[entryIdx] - entry_stb_rect.w = stbrp.Coord(entry.width + offset_x) - entry_stb_rect.h = stbrp.Coord(entry.height + offset_y) + for cell_image, cell_index in all_cell_images { + entry_stb_rect := &rects[cell_index] + entry_stb_rect.w = stbrp.Coord(cell_image.width + offset_x) + entry_stb_rect.h = stbrp.Coord(cell_image.height + offset_y) } ctx: stbrp.Context @@ -131,22 +183,53 @@ pack_atlas_entries :: proc(entries: []AtlasEntry, atlas: ^rl.Image, offset_x: i3 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, + width = auto_cast cell.img.width, + height = auto_cast cell.img.height, } - // Placing it in the atlas in the calculated offsets (in the packing step) + dst_rect := rl.Rectangle { auto_cast rect.x + auto_cast offset_x, auto_cast rect.y + auto_cast offset_y, - auto_cast cell.width, - auto_cast cell.height, + auto_cast cell.img.width, + auto_cast cell.img.height, } - fmt.printfln("Src rect: {0}\nDst rect:{1}", src_rect, dst_rect) - rl.ImageDraw(atlas, cell^, src_rect, dst_rect, rl.WHITE) + // note(stefan): drawing the sprite in the atlas in the packed coordinates + rl.ImageDraw(atlas, cell.img, src_rect, dst_rect, rl.WHITE) + + fmt.printfln("Src rect: {0}\nDst rect:{1}", src_rect, dst_rect) } + + metadata := make([dynamic]SpriteAtlasMetadata, allocator) + for rect, rectIdx in rects { + entry_and_cell := rect_idx_to_entry_and_cell[auto_cast rectIdx] + entry := entry_and_cell.entry + cell := entry_and_cell.cell_of_entry + + cell_name : string + if entry.layer_cell_count[cell.layer_index] > 1 { + cell_name = fmt.aprintf( + "{0}_%d", + entry.layer_names[cell.layer_index], + cell.frame_index, + allocator, + ) + } else { + cell_name = entry.layer_names[cell.layer_index] + } + cell_metadata := SpriteAtlasMetadata { + name = cell_name, + location = { + auto_cast rect.x + auto_cast offset_x, + auto_cast rect.y + auto_cast offset_y, + }, + size = {auto_cast cell.img.width, auto_cast cell.img.height}, + } + append(&metadata, cell_metadata) + } + return metadata } diff --git a/src/globals.odin b/src/globals.odin index b2af01b..42a8b54 100644 --- a/src/globals.odin +++ b/src/globals.odin @@ -34,7 +34,6 @@ PackerSettings :: struct { pixel_padding_x_int: i32, pixel_padding_y_int: i32, padding_enabled: bool, - fix_pixel_bleeding: bool, output_json: bool, output_odin: bool, } diff --git a/src/save_output.odin b/src/save_output.odin index 34d1914..68363fe 100644 --- a/src/save_output.odin +++ b/src/save_output.odin @@ -22,5 +22,7 @@ save_output :: proc() { output_path := strings.concatenate({output_path, atlas_path}) cstring_output_path := strings.clone_to_cstring(output_path) rl.ExportImage(image, cstring_output_path) - } + } else { + fmt.println("Output path is empty!") + } } diff --git a/src/symbol_exports.odin b/src/symbol_exports.odin index 8774c81..d5ada25 100644 --- a/src/symbol_exports.odin +++ b/src/symbol_exports.odin @@ -13,7 +13,6 @@ game_update :: proc() -> bool { game_init_window :: proc() { rl.SetConfigFlags({.WINDOW_RESIZABLE}) rl.InitWindow(1400, 800, "YAAP - Yet Another Atlas Packer") - rl.SetWindowPosition(200, 200) rl.SetWindowMinSize(1400, 800) }