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": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -92,7 +92,7 @@
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"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
|
package generator
|
||||||
|
|
||||||
import ase "../aseprite"
|
import ase "../aseprite"
|
||||||
|
import "core:encoding/json"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
import fp "core:path/filepath"
|
import fp "core:path/filepath"
|
||||||
import "core:slice"
|
import "core:slice"
|
||||||
import "core:strings"
|
import s "core:strings"
|
||||||
import "core:testing"
|
import "core:testing"
|
||||||
|
|
||||||
import rl "vendor:raylib"
|
import rl "vendor:raylib"
|
||||||
|
import stbrp "vendor:stb/rect_pack"
|
||||||
|
|
||||||
import gen ".."
|
import gen ".."
|
||||||
|
|
||||||
|
|
@ -24,14 +27,17 @@ main :: proc() {
|
||||||
}
|
}
|
||||||
|
|
||||||
cwd := os.get_current_directory()
|
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: rl.Image = rl.GenImageColor(ATLAS_SIZE, ATLAS_SIZE, rl.BLANK)
|
||||||
atlas_entries: [dynamic]gen.AtlasEntry
|
atlas_entries: [dynamic]gen.AtlasEntry
|
||||||
gen.unmarshall_aseprite_dir(target_dir, &atlas_entries)
|
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)
|
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
|
package game
|
||||||
|
|
||||||
import ase "./aseprite"
|
import ase "./aseprite"
|
||||||
|
import "core:encoding/json"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
|
import "core:mem"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
import fp "core:path/filepath"
|
import fp "core:path/filepath"
|
||||||
|
import "core:strings"
|
||||||
import rl "vendor:raylib"
|
import rl "vendor:raylib"
|
||||||
import stbrp "vendor:stb/rect_pack"
|
import stbrp "vendor:stb/rect_pack"
|
||||||
|
|
||||||
|
CellData :: struct {
|
||||||
|
layer_index: u16,
|
||||||
|
opacity: u8,
|
||||||
|
frame_index: i32,
|
||||||
|
img: rl.Image,
|
||||||
|
}
|
||||||
|
|
||||||
AtlasEntry :: struct {
|
AtlasEntry :: struct {
|
||||||
path: string,
|
path: string,
|
||||||
cells: [dynamic]rl.Image,
|
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) {
|
unmarshall_aseprite_dir :: proc(path: string, atlas_entries: ^[dynamic]AtlasEntry) {
|
||||||
|
|
@ -27,14 +53,22 @@ unmarshall_aseprite_files_file_info :: proc(
|
||||||
files: []os.File_Info,
|
files: []os.File_Info,
|
||||||
atlas_entries: ^[dynamic]AtlasEntry,
|
atlas_entries: ^[dynamic]AtlasEntry,
|
||||||
) {
|
) {
|
||||||
paths: [dynamic]string
|
if len(files) == 0 do return
|
||||||
for f in files {
|
|
||||||
append(&paths, f.fullpath)
|
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(paths[:], atlas_entries)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unmarshall_aseprite_files :: proc(file_paths: []string, atlas_entries: ^[dynamic]AtlasEntry) {
|
unmarshall_aseprite_files :: proc(file_paths: []string, atlas_entries: ^[dynamic]AtlasEntry) {
|
||||||
|
if len(file_paths) == 0 do return
|
||||||
|
|
||||||
aseprite_document: ase.Document
|
aseprite_document: ase.Document
|
||||||
for file in file_paths {
|
for file in file_paths {
|
||||||
extension := fp.ext(file)
|
extension := fp.ext(file)
|
||||||
|
|
@ -43,6 +77,7 @@ unmarshall_aseprite_files :: proc(file_paths: []string, atlas_entries: ^[dynamic
|
||||||
fmt.println("Unmarshalling file: ", file)
|
fmt.println("Unmarshalling file: ", file)
|
||||||
ase.unmarshal_from_filename(file, &aseprite_document)
|
ase.unmarshal_from_filename(file, &aseprite_document)
|
||||||
atlas_entry := atlas_entry_from_compressed_cells(aseprite_document)
|
atlas_entry := atlas_entry_from_compressed_cells(aseprite_document)
|
||||||
|
atlas_entry.path = file
|
||||||
|
|
||||||
append(atlas_entries, atlas_entry)
|
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
|
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) {
|
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 {
|
for chunk in frame.chunks {
|
||||||
cel_chunk, cok := chunk.(ase.Cel_Chunk)
|
if cel_chunk, ok := chunk.(ase.Cel_Chunk); ok {
|
||||||
if !cok {
|
cel_img, ci_ok := cel_chunk.cel.(ase.Com_Image_Cel)
|
||||||
continue
|
if !ci_ok do continue
|
||||||
}
|
|
||||||
|
|
||||||
cel_img, ci_ok := cel_chunk.cel.(ase.Com_Image_Cel)
|
fmt.println(cel_chunk.layer_index)
|
||||||
if !ci_ok {
|
|
||||||
continue
|
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
|
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)
|
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) {
|
pack_atlas_entries :: proc(
|
||||||
assert(atlas.width != 0, "This shouldn't be 0!")
|
entries: []AtlasEntry,
|
||||||
assert(atlas.height != 0, "This shouldn't be 0!")
|
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
|
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 entry in entries {
|
for cell in entry.cells {
|
||||||
append(&all_entries, ..entry.cells[:])
|
append(&all_cell_images, cell.img)
|
||||||
}
|
}
|
||||||
|
entry.layer_cell_count = make([dynamic]i32, len(entry.cells), allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
num_entries := len(all_entries)
|
num_entries := len(all_cell_images)
|
||||||
nodes := make([]stbrp.Node, num_entries)
|
nodes := make([]stbrp.Node, num_entries, allocator)
|
||||||
rects := make([]stbrp.Rect, num_entries)
|
rects := make([]stbrp.Rect, num_entries, allocator)
|
||||||
|
|
||||||
EntryAndCell :: struct {
|
EntryAndCell :: struct {
|
||||||
entry: ^AtlasEntry,
|
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
|
// Set the custom IDs
|
||||||
cellIdx: int
|
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})
|
map_insert(&rect_idx_to_entry_and_cell, cellIdx, EntryAndCell{&entry, &cell})
|
||||||
rects[cellIdx].id = auto_cast entryIdx
|
rects[cellIdx].id = auto_cast entryIdx
|
||||||
cellIdx += 1
|
cellIdx += 1
|
||||||
|
|
||||||
|
entry.layer_cell_count[cell.layer_index] += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for entry, entryIdx in all_entries {
|
for cell_image, cell_index in all_cell_images {
|
||||||
entry_stb_rect := &rects[entryIdx]
|
entry_stb_rect := &rects[cell_index]
|
||||||
entry_stb_rect.w = stbrp.Coord(entry.width + offset_x)
|
entry_stb_rect.w = stbrp.Coord(cell_image.width + offset_x)
|
||||||
entry_stb_rect.h = stbrp.Coord(entry.height + offset_y)
|
entry_stb_rect.h = stbrp.Coord(cell_image.height + offset_y)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx: stbrp.Context
|
ctx: stbrp.Context
|
||||||
|
|
@ -131,22 +183,53 @@ pack_atlas_entries :: proc(entries: []AtlasEntry, atlas: ^rl.Image, offset_x: i3
|
||||||
for rect, rectIdx in rects {
|
for rect, rectIdx in rects {
|
||||||
entry_and_cell := rect_idx_to_entry_and_cell[auto_cast rectIdx]
|
entry_and_cell := rect_idx_to_entry_and_cell[auto_cast rectIdx]
|
||||||
cell := entry_and_cell.cell_of_entry
|
cell := entry_and_cell.cell_of_entry
|
||||||
// We're grabbing the whole cell (the image itself)
|
|
||||||
src_rect := rl.Rectangle {
|
src_rect := rl.Rectangle {
|
||||||
x = 0,
|
x = 0,
|
||||||
y = 0,
|
y = 0,
|
||||||
width = auto_cast cell.width,
|
width = auto_cast cell.img.width,
|
||||||
height = auto_cast cell.height,
|
height = auto_cast cell.img.height,
|
||||||
}
|
}
|
||||||
// Placing it in the atlas in the calculated offsets (in the packing step)
|
|
||||||
dst_rect := rl.Rectangle {
|
dst_rect := rl.Rectangle {
|
||||||
auto_cast rect.x + auto_cast offset_x,
|
auto_cast rect.x + auto_cast offset_x,
|
||||||
auto_cast rect.y + auto_cast offset_y,
|
auto_cast rect.y + auto_cast offset_y,
|
||||||
auto_cast cell.width,
|
auto_cast cell.img.width,
|
||||||
auto_cast cell.height,
|
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_x_int: i32,
|
||||||
pixel_padding_y_int: i32,
|
pixel_padding_y_int: i32,
|
||||||
padding_enabled: bool,
|
padding_enabled: bool,
|
||||||
fix_pixel_bleeding: bool,
|
|
||||||
output_json: bool,
|
output_json: bool,
|
||||||
output_odin: bool,
|
output_odin: bool,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,7 @@ save_output :: proc() {
|
||||||
output_path := strings.concatenate({output_path, atlas_path})
|
output_path := strings.concatenate({output_path, atlas_path})
|
||||||
cstring_output_path := strings.clone_to_cstring(output_path)
|
cstring_output_path := strings.clone_to_cstring(output_path)
|
||||||
rl.ExportImage(image, 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() {
|
game_init_window :: proc() {
|
||||||
rl.SetConfigFlags({.WINDOW_RESIZABLE})
|
rl.SetConfigFlags({.WINDOW_RESIZABLE})
|
||||||
rl.InitWindow(1400, 800, "YAAP - Yet Another Atlas Packer")
|
rl.InitWindow(1400, 800, "YAAP - Yet Another Atlas Packer")
|
||||||
rl.SetWindowPosition(200, 200)
|
|
||||||
rl.SetWindowMinSize(1400, 800)
|
rl.SetWindowMinSize(1400, 800)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue