working demo of what the metdata exporter is supposed to do
This commit is contained in:
parent
194550335d
commit
67d7b3e227
10 changed files with 150 additions and 54 deletions
4
.vscode/tasks.json
vendored
4
.vscode/tasks.json
vendored
|
|
@ -70,7 +70,7 @@
|
|||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
"isDefault": false
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -92,7 +92,7 @@
|
|||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": false
|
||||
"isDefault": true
|
||||
},
|
||||
}
|
||||
]
|
||||
|
|
|
|||
6
odinfmt.json
Normal file
6
odinfmt.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/odinfmt.schema.json",
|
||||
"character_width": 100,
|
||||
"sort_imports": true,
|
||||
"tabs": true
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Binary file not shown.
1
src/aseprite_odin_generator/metadata.json
Normal file
1
src/aseprite_odin_generator/metadata.json
Normal file
|
|
@ -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]}]
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue