diff --git a/.vscode/launch.json b/.vscode/launch.json index 110830b..af68a68 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,7 @@ "request": "launch", "preLaunchTask": "Build Debug", "name": "Debug", - "program": "${workspaceFolder}/game_debug.exe", + "program": "${workspaceFolder}/build/game_debug.exe", "args": [], "cwd": "${workspaceFolder}" }, diff --git a/.vscode/settings.json b/.vscode/settings.json index 6c4f812..f7964b5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,6 @@ { - "workbench.colorCustomizations": { - "activityBar.background": "#322C2D", - "titleBar.activeBackground": "#463E3F", - "titleBar.activeForeground": "#FAFAFA" - }, "[odin]": { - "editor.formatOnSave": true + "editor.formatOnSave": true, + "editor.tabSize": 4 } } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index cc072d6..784b8a9 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -3,97 +3,90 @@ "command": "", "args": [], "tasks": [ - { - "label": "Build Debug", - "type": "shell", - "windows": { - "command": "${workspaceFolder}/scripts/build_debug.bat", - }, - "linux": { - "command": "${workspaceFolder}/scripts/build_debug.sh", - }, - "osx": { - "command": "${workspaceFolder}/scripts/build_debug.sh", - }, - "group": { - "kind": "build", - "isDefault": false - }, + { + "label": "Build Debug", + "type": "shell", + "windows": { + "command": "${workspaceFolder}/scripts/build_debug.bat" }, - { - "label": "Build Release", - "type": "shell", - "windows": { - "command": "${workspaceFolder}/scripts/build_release.bat", - }, - "linux": { - "command": "${workspaceFolder}/scripts/build_release.sh", - }, - "osx": { - "command": "${workspaceFolder}/scripts/build_release.sh", - }, - "group": "build" + "linux": { + "command": "${workspaceFolder}/scripts/build_debug.sh" }, - { - "label": "Clean build folder(s)", - "type": "shell", - "windows": { - "command": "cd ${workspaceFolder}\\build && rm game*; cd ${workspaceFolder} && rm aseprite_odin_generator*", - }, - // "linux": { - // "command": "${workspaceFolder}/scripts/build_release.sh", - // }, - // "osx": { - // "command": "${workspaceFolder}/scripts/build_release.sh", - // }, - "group": "build" + "osx": { + "command": "${workspaceFolder}/scripts/build_debug.sh" }, - { - "label": "Build Hot Reload", - "type": "shell", - "windows": { - "command": "${workspaceFolder}/scripts/build_hot_reload.bat; start game.exe", - }, - "linux": { - "command": "${workspaceFolder}/scripts/build_hot_reload.sh", - }, - "osx": { - "command": "${workspaceFolder}/scripts/build_hot_reload.sh", - }, - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "dedicated", - "showReuseMessage": false, - "clear": true - }, - "group": { - "kind": "build", - "isDefault": false - }, + "group": { + "kind": "build", + "isDefault": true }, - { - "label": "Build&Run Atlas Generator Test", - "type": "shell", - "windows": { - "command": "${workspaceFolder}/scripts/build_generator_debug.bat && build_generator\\aseprite_odin_generator.exe -input-files:value_of_custom_arg -h", - }, - "options": { - "cwd": "${workspaceFolder}" - }, - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "dedicated", - "showReuseMessage": false, - "clear": true - }, - "group": { - "kind": "build", - "isDefault": true - }, + "problemMatcher": [] + }, + { + "label": "Build Release", + "type": "shell", + "windows": { + "command": "${workspaceFolder}/scripts/build_release.bat" + }, + "linux": { + "command": "${workspaceFolder}/scripts/build_release.sh" + }, + "osx": { + "command": "${workspaceFolder}/scripts/build_release.sh" + }, + "group": "build" + }, + { + "label": "Clean build folder(s)", + "type": "shell", + "windows": { + "command": "cd ${workspaceFolder}\\build && rm game*; cd ${workspaceFolder} && rm aseprite_odin_generator*" + }, + "group": "build" + }, + { + "label": "Build Hot Reload", + "type": "shell", + "windows": { + "command": "${workspaceFolder}/scripts/build_hot_reload.bat; start game.exe" + }, + "linux": { + "command": "${workspaceFolder}/scripts/build_hot_reload.sh" + }, + "osx": { + "command": "${workspaceFolder}/scripts/build_hot_reload.sh" + }, + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "dedicated", + "showReuseMessage": false, + "clear": true + }, + "group": "build", + "problemMatcher": [] + }, + { + "label": "Build&Run Atlas Generator Test", + "type": "shell", + "windows": { + "command": "${workspaceFolder}/scripts/build_generator_debug.bat && build_generator\\aseprite_odin_generator.exe -input-files:value_of_custom_arg -h" + }, + "options": { + "cwd": "${workspaceFolder}" + }, + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "dedicated", + "showReuseMessage": false, + "clear": true + }, + "group": { + "kind": "build", + "isDefault": false } + } ] } \ No newline at end of file diff --git a/src/game.odin b/src/game.odin index 237bfac..b289b82 100644 --- a/src/game.odin +++ b/src/game.odin @@ -14,6 +14,7 @@ package game import "core:fmt" import "core:math" import "core:strings" +import "utils" import rl "vendor:raylib" import diag "dialog" @@ -261,8 +262,8 @@ draw_atlas_settings_and_preview :: proc() { // rl.GuiLine({y = elements_height, width = left_half_rect.width}, "Actions") // elements_height += small_offset - actions_label_y := elements_height { + actions_label_y := elements_height defer rl.GuiGroupBox( { x = small_offset / 2, @@ -286,6 +287,7 @@ draw_atlas_settings_and_preview :: proc() { g_mem.should_open_file_dialog = true g_mem.source_location_type = .SourceFiles } + if rl.GuiButton( { x = left_half_rect.width / 2, @@ -312,6 +314,7 @@ draw_atlas_settings_and_preview :: proc() { ) { g_mem.should_render_atlas = true } + if rl.GuiButton( { x = left_half_rect.width / 2, @@ -336,6 +339,7 @@ draw_atlas_settings_and_preview :: proc() { ) { save_output() } + if rl.GuiButton( { x = left_half_rect.width / 2, @@ -345,6 +349,9 @@ draw_atlas_settings_and_preview :: proc() { }, "Save To...", ) { + if output_folder, ok := g_mem.output_folder_path.(string); ok { + save_metadata_simple(output_folder) + } } elements_height += small_offset * 2 } @@ -467,7 +474,9 @@ open_file_dialog :: proc() { file_path: cstring patterns: []cstring = {"*.png"} if default_path, ok := g_mem.output_folder_path.(string); ok { - default_path_filename := strings.concatenate({default_path, os_file_separator, "atlas.png"}) + default_path_filename := strings.concatenate( + {default_path, os_file_separator, "atlas.png"}, + ) default_path_to_save: cstring = strings.clone_to_cstring(default_path_filename) file_path = cstring( diag.save_file_dialog( @@ -491,9 +500,9 @@ open_file_dialog :: proc() { } clear_atlas_data :: proc() { - if metadata, ok := g_mem.atlas_metadata.([dynamic]SpriteAtlasMetadata); ok { - delete(metadata) - // g_mem.atlas_metadata = nil - } + if metadata, ok := g_mem.atlas_metadata.([dynamic]SpriteAtlasMetadata); ok { + delete(metadata) + // g_mem.atlas_metadata = nil + } g_mem.atlas_render_has_preview = false } diff --git a/src/generator.odin b/src/generator.odin index d0f01d8..571cca2 100644 --- a/src/generator.odin +++ b/src/generator.odin @@ -102,7 +102,7 @@ unmarshall_aseprite_files :: proc( atlas_entry_from_compressed_cells :: proc(document: ase.Document) -> (atlas_entry: AtlasEntry) { atlas_entry.frames = auto_cast len(document.frames) fmt.println("N Frames: ", len(document.frames)) - // note(stefan): Since the expected input for the program is multiple files containing a single sprite + // NOTE(stefan): Since the expected input for the program is multiple files containing a single sprite // it's probably a safe assumption most of the files will be a single layer with 1 or more frames // which means we can first prod the file for information about how many frames are there and // allocate a slice that is going to be [Frames X Layers]CellData. @@ -250,7 +250,7 @@ pack_atlas_entries :: proc( } cell_metadata := SpriteAtlasMetadata { name = cell_name, - location = { + location = { auto_cast rect.x + auto_cast offset_x, auto_cast rect.y + auto_cast offset_y, }, @@ -264,8 +264,10 @@ pack_atlas_entries :: proc( SourceCodeGeneratorMetadata :: struct { file_defines: struct { - top: string, - bottom: string, + top: string, + bottom: string, + file_name: string, + file_extension: string, }, lanugage_settings: struct { first_class_enum_arrays: bool, // for languages that support creating arrays that contain for each enum value an entry in the enum_data.entry_line: .EnumCase = {array entry} @@ -290,18 +292,23 @@ SourceCodeGeneratorMetadata :: struct { } odin_source_generator_metadata := SourceCodeGeneratorMetadata { - file_defines = {top = "package atlas_bindings\n\n", bottom = ""}, - custom_data_type = { + file_defines = { + top = "package atlas_bindings\n\n", + bottom = "", + file_name = "metadata", + file_extension = ".odin", + }, + custom_data_type = { name = "AtlasRect", type_declaration = "%v :: struct {{ x, y, w, h: i32 }}\n\n", }, - enum_data = { + enum_data = { name = "AtlasEnum", begin_line = "%v :: enum {{\n", entry_line = "\t%s,\n", end_line = "}\n\n", }, - array_data = { + array_data = { name = "ATLAS_SPRITES", type = "[]AtlasRect", begin_line = "%v := %v {{\n", @@ -313,18 +320,23 @@ odin_source_generator_metadata := SourceCodeGeneratorMetadata { cpp_source_generator_metadata := SourceCodeGeneratorMetadata { - file_defines = {top = "#include \n\n", bottom = ""}, - custom_data_type = { + file_defines = { + top = "#include \n\n", + bottom = "", + file_name = "metadata", + file_extension = ".hpp", + }, + custom_data_type = { name = "AtlasRect", type_declaration = "struct %v {{\n\tint x;\n\tint y;\n\tint w;\n\tint h;\n}};\n\n", }, - enum_data = { + enum_data = { name = "AtlasEnum", begin_line = "enum %v {{\n", entry_line = "\t%s,\n", end_line = "\n\tCOUNT\n}\n\n", }, - array_data = { + array_data = { name = "ATLAS_SPRITES", type = "AtlasRect[size_t(AtlasEnum::COUNT)-1]", begin_line = "{1} {0} = {{\n", @@ -384,14 +396,9 @@ generate_odin_enums_and_atlas_offsets_file_sb :: proc( metadata_source_code_generate :: proc( metadata: []SpriteAtlasMetadata, - code_generation_metadata: Maybe(SourceCodeGeneratorMetadata), + codegen: SourceCodeGeneratorMetadata, alloc := context.allocator, ) -> strings.Builder { - codegen, ok := code_generation_metadata.(SourceCodeGeneratorMetadata) - - if !ok { - return generate_odin_enums_and_atlas_offsets_file_sb(metadata, alloc) - } sb := strings.builder_make(alloc) // strings.write_string(&sb, "package atlas_bindings\n\n") @@ -452,6 +459,117 @@ metadata_source_code_generate :: proc( } +save_output :: proc() { + output_path, ok := g_mem.output_folder_path.(string) + if !ok || output_path == "" { + fmt.println("Output path is empty!") + return + } + + image := rl.LoadImageFromTexture(g_mem.atlas_render_texture_target.texture) + rl.ImageFlipVertical(&image) + + cstring_atlas_output_path := strings.clone_to_cstring( + strings.concatenate({output_path, os_file_separator, "atlas.png"}), + ) + + rl.ExportImage(image, cstring_atlas_output_path) + + if metadata, ok := g_mem.atlas_metadata.([dynamic]SpriteAtlasMetadata); ok { + fmt.println("Building metadata...") + if json_metadata, jok := json.marshal(metadata); jok == nil { + os.write_entire_file( + strings.concatenate({output_path, os_file_separator, "metadata.json"}), + json_metadata, + ) + } else { + fmt.println("Failed to marshall the atlas metadata to a json!") + } + + // TODO(stefan): Think of a more generic alternative to just straight output to a odin file + // maybe supply a config.json that defines the start, end, line by line entry and enum format strings + // this way you can essentially support any language + sb := generate_odin_enums_and_atlas_offsets_file_sb(metadata[:]) + odin_metadata := strings.to_string(sb) + ok := os.write_entire_file( + strings.concatenate({output_path, os_file_separator, "metadata.odin"}), + transmute([]byte)odin_metadata, + ) + if !ok { + fmt.println("Failed to save 'metadata.odin'") + } + } else { + fmt.println("No metadata to export!") + } + +} + +save_metadata_simple :: proc( + output_path: string, + json_file_name: Maybe(string), + source_file_name: Maybe(string), + source_gen_metadata: Maybe(SourceCodeGeneratorMetadata), +) { + json_file_base_name, json_file_name_ok := json_file_name.(string) + source_file_base_name, source_file_name_ok := source_file_name.(string) + + if !json_file_name_ok && !source_file_name_ok { + fmt.println("Neither a json file name or a source code filename has been provided!") + return + } + + metadata, ok := g_mem.atlas_metadata.([dynamic]SpriteAtlasMetadata);if !ok { + fmt.println("No metadata to export!") + } + + fmt.println("Building metadata...") + if json_file_name_ok { + if json_metadata, jok := json.marshal(metadata); jok == nil { + json_output_path := strings.concatenate( + {output_path, os_file_separator, json_file_base_name}, + ) + if ok = os.write_entire_file(json_output_path, json_metadata); !ok { + fmt.println("Failed to write json to file: ", json_output_path) + } + } else { + fmt.println("Failed to marshall the atlas metadata to a json!") + } + } + + // note(stefan): Having source_file_name & source_gen_metadata is redundant but this is fine for now + if source_file_name_ok { + // if src_gen_metadata + if codegen, cok := source_gen_metadata.(SourceCodeGeneratorMetadata); cok { + sb := metadata_source_code_generate(metadata[:], codegen) + + source_metadata := strings.to_string(sb) + source_output_path := strings.concatenate( + { + output_path, + os_file_separator, + codegen.file_defines.file_name, + codegen.file_defines.file_extension, + }, + ) + ok := os.write_entire_file(source_output_path, transmute([]byte)source_metadata) + if !ok { + fmt.println("Failed to save source code to file:", source_output_path) + } + } else { + sb := metadata_source_code_generate(metadata[:], odin_source_generator_metadata) + odin_metadata := strings.to_string(sb) + source_output_path := strings.concatenate( + {output_path, os_file_separator, "metadata.odin"}, + ) + + ok := os.write_entire_file(source_output_path, transmute([]byte)odin_metadata) + if !ok { + fmt.println("Failed to save source code to file:", source_output_path) + } + } + } +} + save_metadata :: proc( settings: utils.CLIPackerSettings, atlas_entries: []AtlasEntry, @@ -473,46 +591,3 @@ save_metadata :: proc( os.write_entire_file(source_code_path, transmute([]byte)source_code_output_str) } } - -save_output :: proc() { - output_path, ok := g_mem.output_folder_path.(string) - if !ok { - fmt.println("Output path is empty!") - return - } else if output_path == "" { - fmt.println("Output path is empty!") - return - } - - image := rl.LoadImageFromTexture(g_mem.atlas_render_texture_target.texture) - rl.ImageFlipVertical(&image) - - output_path = strings.concatenate({output_path, os_file_separator, "atlas.png"}) - cstring_output_path := strings.clone_to_cstring(output_path) - - rl.ExportImage(image, cstring_output_path) - - if metadata, ok := g_mem.atlas_metadata.([dynamic]SpriteAtlasMetadata); ok { - if json_metadata, jok := json.marshal(metadata); jok == nil { - os.write_entire_file( - strings.concatenate({output_path, os_file_separator, "metadata.json"}), - json_metadata, - ) - } else { - fmt.println("Failed to marshall the atlas metadata to a json!") - } - - // TODO(stefan): Think of a more generic alternative to just straight output to a odin file - // maybe supply a config.json that defines the start, end, line by line entry and enum format strings - // this way you can essentially support any language - sb := generate_odin_enums_and_atlas_offsets_file_sb(metadata[:]) - odin_metadata := strings.to_string(sb) - os.write_entire_file( - strings.concatenate({output_path, os_file_separator, "metadata.odin"}), - transmute([]byte)odin_metadata, - ) - } else { - fmt.println("No metadata to export!") - } - -} diff --git a/src/utils/cli.odin b/src/utils/cli.odin index d6aa08a..8b9411f 100644 --- a/src/utils/cli.odin +++ b/src/utils/cli.odin @@ -97,6 +97,7 @@ parse_arguments :: proc(args: []string) -> (cliargs: map[CLIFlagType]CLIFlag) { for arg in args { arg_name_and_value, err := s.split(arg, ":") + if err != nil {continue} name := arg_name_and_value[0] if name[0] == '-' {