Major refactor:
* Removed any hotreload functionality to simplify the code-base, may introduce it back again if there's a need to redesign the UI as it was used for dynamic prototyping. * Split the code base into two packages: generator and frontend. The 'generator' package is reponsible for the functionality of making the atlases, etc... while the frontend is pure the immediate mode UI implemented with raygui and application globals * New build scripts, windows only for now.
This commit is contained in:
parent
84db74586b
commit
3f1c523ad9
35 changed files with 397 additions and 1023 deletions
6
.gitmodules
vendored
6
.gitmodules
vendored
|
|
@ -1,6 +0,0 @@
|
|||
[submodule "src/aseprite"]
|
||||
path = src/aseprite
|
||||
url = https://github.com/bersK/odin-aseprite.git
|
||||
[submodule "src/dialog/libtinyfiledialogs"]
|
||||
path = src/dialog/libtinyfiledialogs
|
||||
url = https://github.com/native-toolkit/libtinyfiledialogs.git
|
||||
|
|
@ -4,14 +4,16 @@ Yet-Another-Atlas-Packer by Stefan Stefanov
|
|||
|
||||
## Description
|
||||
|
||||
Simple atlas packer using `stb_rect_pack` from the `stb` family of header libraries & `raylib` for rendering/ui. Here's a quick preview on [youtube](https://youtu.be/4_dKq7G57Lw) of the application.
|
||||
Simple atlas packer for .aseprite files. Generates metadata and potentially embeds in an output source file of your choosing.
|
||||
|
||||
Uses `stb_rect_pack` from the `stb` family of header libraries & `raylib` for rendering/UI. Here's a quick preview on [youtube](https://youtu.be/4_dKq7G57Lw) of the application.
|
||||
|
||||
<a href="https://youtu.be/4_dKq7G57Lw">
|
||||
<img src="https://raw.githubusercontent.com/bersK/yaap/master/repo_assets/image.png" />
|
||||
<a/>
|
||||
|
||||
The goal of the tool is to take in multiple aseprite files and pack them into a single atlas, outputting some metadata in the process in the form of
|
||||
json and/or source files for direct use in odin (maybe more languages too).
|
||||
The goal of the tool is to take in multiple aseprite files and pack them into a single atlas, outputting metadata in the process in the form of
|
||||
JSON and/or source files for direct use in odin (or other languages through a customization file).
|
||||
|
||||
I'm using a library for marshalling the aseprite files found [here](https://github.com/blob1807/odin-aseprite) on github.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,42 +1,29 @@
|
|||
package generator
|
||||
package cli
|
||||
|
||||
import ase "../aseprite"
|
||||
import ase "../vendors/aseprite"
|
||||
import "core:encoding/json"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import fp "core:path/filepath"
|
||||
import "core:slice"
|
||||
import s "core:strings"
|
||||
import "core:testing"
|
||||
|
||||
import rl "vendor:raylib"
|
||||
import stbrp "vendor:stb/rect_pack"
|
||||
|
||||
import gen ".."
|
||||
import utils "../utils"
|
||||
import gen "../src/generator"
|
||||
|
||||
ATLAS_SIZE :: 512
|
||||
IMPORT_PATH :: "./src/aseprite_odin_generator/big.aseprite"
|
||||
EXPORT_PATH :: "./src/aseprite_odin_generator/atlas.png"
|
||||
IMPORT_PATH :: "./example.aseprite"
|
||||
EXPORT_PATH :: "./atlas.png"
|
||||
|
||||
main :: proc() {
|
||||
args := utils.parse_arguments(os.args[1:])
|
||||
fmt.println(args)
|
||||
|
||||
if ok := utils.CLIFlagType.Help in args; ok {
|
||||
fmt.println("Help called!")
|
||||
utils.print_help()
|
||||
return
|
||||
}
|
||||
|
||||
ase_file, ase_ok := os.read_entire_file(IMPORT_PATH)
|
||||
if !ase_ok {
|
||||
fmt.panicf("Couldn't load file!")
|
||||
}
|
||||
|
||||
cwd := os.get_current_directory()
|
||||
target_dir := s.concatenate({cwd, "\\src\\aseprite_odin_generator\\"})
|
||||
target_dir := os.get_current_directory()
|
||||
|
||||
atlas: rl.Image = rl.GenImageColor(ATLAS_SIZE, ATLAS_SIZE, rl.BLANK)
|
||||
atlas_entries: [dynamic]gen.AtlasEntry = make([dynamic]gen.AtlasEntry)
|
||||
|
|
@ -45,13 +32,10 @@ main :: proc() {
|
|||
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)
|
||||
os.write_entire_file("./metadata.json", json_bytes)
|
||||
sb := gen.metadata_source_code_generate(metadata[:], gen.odin_source_generator_metadata)
|
||||
odin_output_str := s.to_string(sb)
|
||||
os.write_entire_file(
|
||||
"src/aseprite_odin_generator/output.odino",
|
||||
transmute([]byte)odin_output_str,
|
||||
)
|
||||
os.write_entire_file("./output.odin", transmute([]byte)odin_output_str)
|
||||
|
||||
rl.ExportImage(atlas, EXPORT_PATH)
|
||||
}
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
2
scripts/build_atlas.bat
Normal file
2
scripts/build_atlas.bat
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
@echo off
|
||||
odin build src/frontend -define:RAYLIB_SHARED=true -out:build/yaap-debug.exe -debug
|
||||
2
scripts/build_atlas_release.bat
Normal file
2
scripts/build_atlas_release.bat
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
@echo off
|
||||
odin build src/frontend -define:RAYLIB_SHARED=true -out:build/yaap.exe -o:speed
|
||||
2
scripts/build_cli.bat
Normal file
2
scripts/build_cli.bat
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
@echo off
|
||||
odin build examples/aseprite_odin_generator.odin -file -define:RAYLIB_SHARED=true -out:build/yaap-cli-debug.exe -debug
|
||||
2
scripts/build_cli_release.bat
Normal file
2
scripts/build_cli_release.bat
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
@echo off
|
||||
odin build examples/aseprite_odin_generator.odin -file -define:RAYLIB_SHARED=true -out:build/yaap-cli.exe -o:speed
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
@echo off
|
||||
odin build src/main_release -define:RAYLIB_SHARED=true -out:build/game_debug.exe -debug
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
odin build src/main_release -out:build/game_debug.bin -no-bounds-check -debug
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
@echo off
|
||||
odin build src/aseprite_odin_generator -define:RAYLIB_SHARED=true -out:build_generator/aseprite_odin_generator.exe -debug
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
@echo off
|
||||
|
||||
rem Build game.dll
|
||||
odin build src -show-timings -use-separate-modules -define:RAYLIB_SHARED=true -build-mode:dll -out:build/game.dll -strict-style -vet-unused -vet-using-stmt -vet-using-param -vet-style -vet-semicolon -debug
|
||||
IF %ERRORLEVEL% NEQ 0 exit /b 1
|
||||
|
||||
rem If game.exe already running: Then only compile game.dll and exit cleanly
|
||||
QPROCESS "game.exe">NUL
|
||||
IF %ERRORLEVEL% EQU 0 exit /b 1
|
||||
|
||||
rem build game.exe
|
||||
odin build src/main_hot_reload -use-separate-modules -out:build/game.exe -strict-style -vet-using-stmt -vet-using-param -vet-style -vet-semicolon -debug
|
||||
IF %ERRORLEVEL% NEQ 0 exit /b 1
|
||||
|
||||
rem copy raylib.dll from odin folder to here
|
||||
if not exist "raylib.dll" (
|
||||
echo "Please copy raylib.dll from <your_odin_compiler>/vendor/raylib/windows/raylib.dll to the same directory as game.exe"
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
exit /b 0
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
VET="-strict-style -vet-unused -vet-using-stmt -vet-using-param -vet-style -vet-semicolon"
|
||||
|
||||
# NOTE: this is a recent addition to the Odin compiler, if you don't have this command
|
||||
# you can change this to the path to the Odin folder that contains vendor, eg: "~/Odin".
|
||||
ROOT=$(odin root)
|
||||
if [ ! $? -eq 0 ]; then
|
||||
echo "Your Odin compiler does not have the 'odin root' command, please update or hardcode it in the script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -eu
|
||||
|
||||
# Figure out the mess that is dynamic libraries.
|
||||
case $(uname) in
|
||||
"Darwin")
|
||||
case $(uname -m) in
|
||||
"arm64") LIB_PATH="macos-arm64" ;;
|
||||
*) LIB_PATH="macos" ;;
|
||||
esac
|
||||
|
||||
DLL_EXT=".dylib"
|
||||
EXTRA_LINKER_FLAGS="-Wl,-rpath $ROOT/vendor/raylib/$LIB_PATH"
|
||||
;;
|
||||
*)
|
||||
DLL_EXT=".so"
|
||||
EXTRA_LINKER_FLAGS="'-Wl,-rpath=\$ORIGIN/linux'"
|
||||
|
||||
# Copy the linux libraries into the project automatically.
|
||||
if [ ! -d "linux" ]; then
|
||||
mkdir linux
|
||||
cp -r $ROOT/vendor/raylib/linux/libraylib*.so* linux
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# Build the game.
|
||||
odin build src -use-separate-modules -extra-linker-flags:"$EXTRA_LINKER_FLAGS" -show-timings -define:RAYLIB_SHARED=true -build-mode:dll -out:build/game_tmp$DLL_EXT -debug $VET
|
||||
|
||||
# Need to use a temp file on Linux because it first writes an empty `game.so`, which the game will load before it is actually fully written.
|
||||
mv ./build/game_tmp$DLL_EXT ./build/game$DLL_EXT
|
||||
|
||||
# Do not build the game.bin if it is already running.
|
||||
if ! pgrep game.bin > /dev/null; then
|
||||
odin build src/main_hot_reload -use-separate-modules -out:build/game.bin $VET -debug
|
||||
fi
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
@echo off
|
||||
odin build src/main_release -define:RAYLIB_SHARED=true -out:build/game_release.exe -no-bounds-check -o:speed -strict-style -vet-unused -vet-using-stmt -vet-using-param -vet-style -vet-semicolon -subsystem:windows
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
odin build src/main_release -out:build/game_release.bin -no-bounds-check -o:speed -strict-style -vet-unused -vet-using-stmt -vet-using-param -vet-style -vet-semicolon
|
||||
14
scripts/setup.bat
Normal file
14
scripts/setup.bat
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
@echo off
|
||||
|
||||
mkdir build
|
||||
|
||||
FOR /F "tokens=*" %%g IN ('odin root') do (SET ODIN_ROOT=%%g)
|
||||
|
||||
echo %ODIN_ROOT%
|
||||
|
||||
@REM If it fails to find your odin root folder, copy the raylib.dll manually into the build folder, it's a runtime requirement
|
||||
copy %ODIN_ROOT%\vendor\raylib\windows\raylib.dll build\raylib.dll
|
||||
|
||||
pushd vendors\dialog
|
||||
call .\build.bat
|
||||
popd
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
|
|
@ -1 +0,0 @@
|
|||
[{"name":"Edinica","location":[95,10],"size":[58,57]},{"name":"Dvoika_0","location":[234,10],"size":[55,31]},{"name":"Dvoika_1","location":[163,10],"size":[61,33]},{"name":"Troika","location":[10,10],"size":[75,75]}]
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
package frontend
|
||||
|
||||
package game
|
||||
|
||||
import generator "../generator"
|
||||
import rl "vendor:raylib"
|
||||
|
||||
PixelWindowHeight :: 180
|
||||
PIXEL_WINDOW_HEIGHT :: 180
|
||||
FILE_DIALOG_SIZE :: 1000
|
||||
|
||||
scaling: f32 = 2
|
||||
w, h: f32
|
||||
|
||||
WindowInformation :: struct {
|
||||
w: f32,
|
||||
|
|
@ -38,7 +37,7 @@ PackerSettings :: struct {
|
|||
output_odin: bool,
|
||||
}
|
||||
|
||||
GameMemory :: struct {
|
||||
ApplicationState :: struct {
|
||||
file_dialog_text_buffer: [FILE_DIALOG_SIZE + 1]u8,
|
||||
is_packing_whole_source_folder: bool,
|
||||
should_open_file_dialog: bool,
|
||||
|
|
@ -59,5 +58,7 @@ GameMemory :: struct {
|
|||
should_render_atlas: bool,
|
||||
atlas_render_has_preview: bool,
|
||||
atlas_render_size: i32,
|
||||
atlas_metadata: Maybe([dynamic]SpriteAtlasMetadata),
|
||||
atlas_metadata: Maybe([dynamic]generator.SpriteAtlasMetadata),
|
||||
}
|
||||
|
||||
g_mem: ApplicationState
|
||||
|
|
@ -1,35 +1,65 @@
|
|||
// This file is compiled as part of the `odin.dll` file. It contains the
|
||||
// procs that `game.exe` will call, such as:
|
||||
//
|
||||
// game_init: Sets up the game state
|
||||
// game_update: Run once per frame
|
||||
// game_shutdown: Shuts down game and frees memory
|
||||
// game_memory: Run just before a hot reload, so game.exe has a pointer to the
|
||||
// game's memory.
|
||||
// game_hot_reloaded: Run after a hot reload so that the `g_mem` global variable
|
||||
// can be set to whatever pointer it was in the old DLL.
|
||||
|
||||
package game
|
||||
package frontend
|
||||
|
||||
import "core:fmt"
|
||||
import "core:log"
|
||||
import "core:math"
|
||||
import "core:mem"
|
||||
import "core:mem/virtual"
|
||||
import "core:os"
|
||||
import "core:strings"
|
||||
import "utils"
|
||||
import rl "vendor:raylib"
|
||||
|
||||
import diag "dialog"
|
||||
import diag "../../vendors/dialog"
|
||||
import generator "../generator"
|
||||
|
||||
g_mem: ^GameMemory
|
||||
main :: proc() {
|
||||
default_allocator := context.allocator
|
||||
tracking_allocator: mem.Tracking_Allocator
|
||||
mem.tracking_allocator_init(&tracking_allocator, default_allocator)
|
||||
context.allocator = mem.tracking_allocator(&tracking_allocator)
|
||||
|
||||
game_camera :: proc() -> rl.Camera2D {
|
||||
w = f32(rl.GetScreenWidth())
|
||||
h = f32(rl.GetScreenHeight())
|
||||
mode: int = 0
|
||||
when ODIN_OS == .Linux || ODIN_OS == .Darwin {
|
||||
mode = os.S_IRUSR | os.S_IWUSR | os.S_IRGRP | os.S_IROTH
|
||||
}
|
||||
|
||||
return {zoom = h / PixelWindowHeight, target = {}, offset = {w / 2, h / 2}}
|
||||
}
|
||||
logh, logh_err := os.open("log.txt", (os.O_CREATE | os.O_TRUNC | os.O_RDWR), mode)
|
||||
|
||||
ui_camera :: proc() -> rl.Camera2D {
|
||||
return {zoom = scaling}
|
||||
if logh_err == os.ERROR_NONE {
|
||||
os.stdout = logh
|
||||
os.stderr = logh
|
||||
}
|
||||
|
||||
logger :=
|
||||
logh_err == os.ERROR_NONE ? log.create_file_logger(logh) : log.create_console_logger()
|
||||
context.logger = logger
|
||||
defer if logh_err == os.ERROR_NONE {
|
||||
log.destroy_file_logger(logger)
|
||||
}
|
||||
|
||||
rl.SetConfigFlags({.WINDOW_RESIZABLE})
|
||||
rl.InitWindow(1400, 800, "YAAP - Yet Another Atlas Packer")
|
||||
defer rl.CloseWindow()
|
||||
rl.SetWindowMinSize(1400, 800)
|
||||
|
||||
for !rl.WindowShouldClose() {
|
||||
update()
|
||||
draw()
|
||||
|
||||
for b in tracking_allocator.bad_free_array {
|
||||
log.error("Bad free at: %v", b.location)
|
||||
}
|
||||
|
||||
clear(&tracking_allocator.bad_free_array)
|
||||
|
||||
free_all(context.temp_allocator)
|
||||
}
|
||||
|
||||
for key, value in tracking_allocator.allocation_map {
|
||||
log.error("%v: Leaked %v bytes\n", value.location, value.size)
|
||||
}
|
||||
|
||||
mem.tracking_allocator_destroy(&tracking_allocator)
|
||||
}
|
||||
|
||||
update :: proc() {
|
||||
|
|
@ -39,11 +69,10 @@ update :: proc() {
|
|||
win_info.h = f32(rl.GetScreenHeight())
|
||||
win_info.height_scaled = win_info.h / scaling
|
||||
win_info.width_scaled = win_info.w / scaling
|
||||
w = win_info.w
|
||||
h = win_info.h
|
||||
|
||||
// Update the virtual mouse position (needed for GUI interaction to work properly for instance)
|
||||
rl.SetMouseScale(1 / scaling, 1 / scaling)
|
||||
mouse_scale := 1 / scaling
|
||||
rl.SetMouseScale(mouse_scale, mouse_scale)
|
||||
|
||||
if g_mem.should_open_file_dialog {
|
||||
open_file_dialog()
|
||||
|
|
@ -65,6 +94,10 @@ draw :: proc() {
|
|||
free_all(context.temp_allocator)
|
||||
}
|
||||
|
||||
ui_camera :: proc() -> rl.Camera2D {
|
||||
return {zoom = scaling}
|
||||
}
|
||||
|
||||
draw_screen_ui :: proc() {
|
||||
rl.BeginMode2D(ui_camera())
|
||||
defer rl.EndMode2D()
|
||||
|
|
@ -72,19 +105,47 @@ draw_screen_ui :: proc() {
|
|||
draw_atlas_settings_and_preview()
|
||||
}
|
||||
|
||||
pick_sources :: proc() {
|
||||
g_mem.should_open_file_dialog = true
|
||||
g_mem.source_location_type = .SourceFiles
|
||||
}
|
||||
|
||||
pick_output :: proc() {
|
||||
g_mem.should_open_file_dialog = true
|
||||
g_mem.source_location_type = .OutputFolder
|
||||
}
|
||||
|
||||
pack_atlas :: proc() {
|
||||
g_mem.should_render_atlas = true
|
||||
}
|
||||
|
||||
save :: proc() {
|
||||
generator.save_output(
|
||||
g_mem.output_folder_path,
|
||||
g_mem.atlas_metadata,
|
||||
g_mem.atlas_render_texture_target,
|
||||
)
|
||||
}
|
||||
|
||||
save_to :: proc() {
|
||||
// if output_folder, ok := g_mem.output_folder_path.(string); ok {
|
||||
// generator.save_metadata_simple(output_folder, g_mem.atlas_metadata, nil, nil, nil)
|
||||
// }
|
||||
}
|
||||
|
||||
draw_screen_target :: proc() {
|
||||
atlas_render_target := &g_mem.atlas_render_texture_target
|
||||
|
||||
rl.BeginTextureMode(atlas_render_target^)
|
||||
defer rl.EndTextureMode()
|
||||
|
||||
atlas_entries: [dynamic]AtlasEntry
|
||||
atlas_entries: [dynamic]generator.AtlasEntry
|
||||
delete(atlas_entries)
|
||||
|
||||
if files, ok := g_mem.source_files_to_pack.([]string); ok {
|
||||
unmarshall_aseprite_files(files, &atlas_entries)
|
||||
generator.unmarshall_aseprite_files(files, &atlas_entries)
|
||||
} else {
|
||||
fmt.println("No source folder or files set! Can't pack the void!!!")
|
||||
log.error("No source folder or files set! Can't pack the void!!!")
|
||||
g_mem.should_render_atlas = false
|
||||
return
|
||||
}
|
||||
|
|
@ -97,12 +158,17 @@ draw_screen_target :: proc() {
|
|||
padding_y :=
|
||||
g_mem.packer_settings.pixel_padding_y_int if g_mem.packer_settings.padding_enabled else 0
|
||||
|
||||
g_mem.atlas_metadata = pack_atlas_entries(atlas_entries[:], &atlas, padding_x, padding_y)
|
||||
g_mem.atlas_metadata = generator.pack_atlas_entries(
|
||||
atlas_entries[:],
|
||||
&atlas,
|
||||
padding_x,
|
||||
padding_y,
|
||||
)
|
||||
|
||||
// OpenGL's Y buffer is flipped
|
||||
rl.ImageFlipVertical(&atlas)
|
||||
// rl.UnloadTexture(atlas_render_target.texture)
|
||||
fmt.println("Packed everything!")
|
||||
log.info("Packed everything!")
|
||||
atlas_render_target.texture = rl.LoadTextureFromImage(atlas)
|
||||
|
||||
g_mem.should_render_atlas = false
|
||||
|
|
@ -113,14 +179,14 @@ draw_atlas_settings_and_preview :: proc() {
|
|||
left_half_rect := rl.Rectangle {
|
||||
x = 0,
|
||||
y = 0,
|
||||
width = auto_cast g_mem.window_info.width_scaled / 3,
|
||||
height = auto_cast g_mem.window_info.height_scaled,
|
||||
width = cast(f32)g_mem.window_info.width_scaled / 3,
|
||||
height = cast(f32)g_mem.window_info.height_scaled,
|
||||
}
|
||||
right_half_rect := rl.Rectangle {
|
||||
x = auto_cast g_mem.window_info.width_scaled / 3,
|
||||
x = cast(f32)g_mem.window_info.width_scaled / 3,
|
||||
y = 0,
|
||||
width = auto_cast (g_mem.window_info.width_scaled / 3) * 2,
|
||||
height = auto_cast g_mem.window_info.height_scaled,
|
||||
width = cast(f32)(g_mem.window_info.width_scaled / 3) * 2,
|
||||
height = cast(f32)g_mem.window_info.height_scaled,
|
||||
}
|
||||
rl.DrawRectangleRec(left_half_rect, rl.WHITE)
|
||||
rl.DrawRectangleRec(right_half_rect, rl.MAROON)
|
||||
|
|
@ -149,9 +215,9 @@ draw_atlas_settings_and_preview :: proc() {
|
|||
elements_height += small_offset / 2
|
||||
|
||||
@(static)
|
||||
DropdownBox000EditMode: bool
|
||||
dropdown_resolution_edit_mode: bool
|
||||
@(static)
|
||||
DropdownBox000Active: i32
|
||||
dropdown_resolution_mode: i32
|
||||
|
||||
dropdown_rect := rl.Rectangle {
|
||||
x = small_offset,
|
||||
|
|
@ -159,20 +225,18 @@ draw_atlas_settings_and_preview :: proc() {
|
|||
width = left_half_rect.width - small_offset * 2,
|
||||
height = small_offset,
|
||||
}
|
||||
|
||||
// Because we want to render this ontop of everything else, we can just 'defer' it at the end of the draw function
|
||||
defer {
|
||||
if DropdownBox000EditMode {rl.GuiLock()}
|
||||
if dropdown_resolution_edit_mode {rl.GuiLock()}
|
||||
|
||||
if rl.GuiDropdownBox(
|
||||
dropdown_rect,
|
||||
"256x;512x;1024x;2048x;4096x",
|
||||
&DropdownBox000Active,
|
||||
DropdownBox000EditMode,
|
||||
&dropdown_resolution_mode,
|
||||
dropdown_resolution_edit_mode,
|
||||
) {
|
||||
DropdownBox000EditMode = !DropdownBox000EditMode
|
||||
fmt.println(DropdownBox000Active)
|
||||
g_mem.atlas_render_size = 256 * auto_cast math.pow(2, f32(DropdownBox000Active))
|
||||
dropdown_resolution_edit_mode = !dropdown_resolution_edit_mode
|
||||
g_mem.atlas_render_size = 256 * auto_cast math.pow(2, f32(dropdown_resolution_mode))
|
||||
}
|
||||
rl.GuiUnlock()
|
||||
}
|
||||
|
|
@ -183,77 +247,90 @@ draw_atlas_settings_and_preview :: proc() {
|
|||
if SettingsDropdownBoxActive == 0 {
|
||||
padding_settings_y := elements_height
|
||||
{
|
||||
defer rl.GuiGroupBox(
|
||||
{
|
||||
x = small_offset / 2,
|
||||
y = padding_settings_y,
|
||||
width = left_half_rect.width - small_offset,
|
||||
defer {
|
||||
padding_settings_rect := rl.Rectangle {
|
||||
x = small_offset / 2,
|
||||
y = padding_settings_y,
|
||||
width = left_half_rect.width - small_offset,
|
||||
height = elements_height - padding_settings_y,
|
||||
},
|
||||
"Padding Settings",
|
||||
)
|
||||
}
|
||||
rl.GuiGroupBox(padding_settings_rect, "Padding Settings")
|
||||
}
|
||||
elements_height += small_offset
|
||||
|
||||
enable_padding_rect := rl.Rectangle {
|
||||
x = small_offset,
|
||||
y = elements_height,
|
||||
width = small_offset,
|
||||
height = small_offset,
|
||||
}
|
||||
rl.GuiCheckBox(
|
||||
{
|
||||
x = small_offset,
|
||||
y = elements_height,
|
||||
width = small_offset,
|
||||
height = small_offset,
|
||||
},
|
||||
enable_padding_rect,
|
||||
" Enable padding",
|
||||
&g_mem.packer_settings.padding_enabled,
|
||||
)
|
||||
elements_height += small_offset * 2
|
||||
|
||||
if (rl.GuiSpinner(
|
||||
{
|
||||
x = small_offset,
|
||||
y = elements_height,
|
||||
width = big_offset * 2,
|
||||
height = small_offset,
|
||||
},
|
||||
"",
|
||||
&g_mem.packer_settings.pixel_padding_x_int,
|
||||
0,
|
||||
10,
|
||||
spinner_edit_mode,
|
||||
)) >
|
||||
0 {spinner_edit_mode = !spinner_edit_mode}
|
||||
rl.GuiLabel(
|
||||
{
|
||||
x = (small_offset * 2) + big_offset * 2,
|
||||
y = elements_height,
|
||||
width = big_offset,
|
||||
// Padding X spinner and label
|
||||
{
|
||||
padding_x_spinner_rect := rl.Rectangle {
|
||||
x = small_offset,
|
||||
y = elements_height,
|
||||
width = big_offset * 2,
|
||||
height = small_offset,
|
||||
},
|
||||
"Padding X",
|
||||
)
|
||||
}
|
||||
padding_x_spinner := rl.GuiSpinner(
|
||||
padding_x_spinner_rect,
|
||||
"",
|
||||
&g_mem.packer_settings.pixel_padding_x_int,
|
||||
0,
|
||||
10,
|
||||
spinner_edit_mode,
|
||||
)
|
||||
if (padding_x_spinner) > 0 {
|
||||
spinner_edit_mode = !spinner_edit_mode
|
||||
}
|
||||
rl.GuiLabel(
|
||||
{
|
||||
x = (small_offset * 2) + big_offset * 2,
|
||||
y = elements_height,
|
||||
width = big_offset,
|
||||
height = small_offset,
|
||||
},
|
||||
"Padding X",
|
||||
)
|
||||
}
|
||||
elements_height += small_offset * 2
|
||||
|
||||
if (rl.GuiSpinner(
|
||||
{
|
||||
x = small_offset,
|
||||
y = elements_height,
|
||||
width = big_offset * 2,
|
||||
height = small_offset,
|
||||
},
|
||||
"",
|
||||
&g_mem.packer_settings.pixel_padding_y_int,
|
||||
0,
|
||||
10,
|
||||
spinner_edit_mode,
|
||||
)) >
|
||||
0 {spinner_edit_mode = !spinner_edit_mode}
|
||||
rl.GuiLabel(
|
||||
{
|
||||
x = (small_offset * 2) + big_offset * 2,
|
||||
y = elements_height,
|
||||
width = big_offset,
|
||||
// Padding Y spinner and label
|
||||
{
|
||||
padding_y_spinner_rect := rl.Rectangle {
|
||||
x = small_offset,
|
||||
y = elements_height,
|
||||
width = big_offset * 2,
|
||||
height = small_offset,
|
||||
},
|
||||
"Padding Y",
|
||||
)
|
||||
}
|
||||
padding_y_spinner := rl.GuiSpinner(
|
||||
padding_y_spinner_rect,
|
||||
"",
|
||||
&g_mem.packer_settings.pixel_padding_y_int,
|
||||
0,
|
||||
10,
|
||||
spinner_edit_mode,
|
||||
)
|
||||
if (padding_y_spinner) > 0 {
|
||||
spinner_edit_mode = !spinner_edit_mode
|
||||
}
|
||||
rl.GuiLabel(
|
||||
{
|
||||
x = (small_offset * 2) + big_offset * 2,
|
||||
y = elements_height,
|
||||
width = big_offset,
|
||||
height = small_offset,
|
||||
},
|
||||
"Padding Y",
|
||||
)
|
||||
}
|
||||
elements_height += small_offset * 2
|
||||
|
||||
}
|
||||
|
|
@ -264,94 +341,78 @@ draw_atlas_settings_and_preview :: proc() {
|
|||
|
||||
{
|
||||
actions_label_y := elements_height
|
||||
defer rl.GuiGroupBox(
|
||||
{
|
||||
x = small_offset / 2,
|
||||
y = actions_label_y,
|
||||
width = left_half_rect.width - small_offset,
|
||||
|
||||
defer {
|
||||
actions_rect := rl.Rectangle {
|
||||
x = small_offset / 2,
|
||||
y = actions_label_y,
|
||||
width = left_half_rect.width - small_offset,
|
||||
height = elements_height - actions_label_y,
|
||||
},
|
||||
"Actions",
|
||||
)
|
||||
}
|
||||
rl.GuiGroupBox(actions_rect, "Actions")
|
||||
}
|
||||
elements_height += small_offset
|
||||
|
||||
if rl.GuiButton(
|
||||
{
|
||||
x = small_offset,
|
||||
y = elements_height,
|
||||
width = left_half_rect.width / 2 - small_offset,
|
||||
height = small_offset,
|
||||
},
|
||||
"Pick Source(s)",
|
||||
) {
|
||||
g_mem.should_open_file_dialog = true
|
||||
g_mem.source_location_type = .SourceFiles
|
||||
pick_sources_rect := rl.Rectangle {
|
||||
x = small_offset,
|
||||
y = elements_height,
|
||||
width = left_half_rect.width / 2 - small_offset,
|
||||
height = small_offset,
|
||||
}
|
||||
if rl.GuiButton(pick_sources_rect, "Pick Source(s)") {
|
||||
pick_sources()
|
||||
}
|
||||
|
||||
if rl.GuiButton(
|
||||
{
|
||||
x = left_half_rect.width / 2,
|
||||
y = elements_height,
|
||||
width = left_half_rect.width / 2 - small_offset,
|
||||
height = small_offset,
|
||||
},
|
||||
"Pick Output",
|
||||
) {
|
||||
g_mem.should_open_file_dialog = true
|
||||
g_mem.source_location_type = .OutputFolder
|
||||
pick_output_rect := rl.Rectangle {
|
||||
x = left_half_rect.width / 2,
|
||||
y = elements_height,
|
||||
width = left_half_rect.width / 2 - small_offset,
|
||||
height = small_offset,
|
||||
}
|
||||
if rl.GuiButton(pick_output_rect, "Pick Output") {
|
||||
pick_output()
|
||||
}
|
||||
elements_height += small_offset * 2
|
||||
|
||||
|
||||
if rl.GuiButton(
|
||||
{
|
||||
x = small_offset,
|
||||
y = elements_height,
|
||||
width = left_half_rect.width / 2 - small_offset,
|
||||
height = small_offset,
|
||||
},
|
||||
"Pack Atlas",
|
||||
) {
|
||||
g_mem.should_render_atlas = true
|
||||
pack_atlas_rect := rl.Rectangle {
|
||||
x = small_offset,
|
||||
y = elements_height,
|
||||
width = left_half_rect.width / 2 - small_offset,
|
||||
height = small_offset,
|
||||
}
|
||||
if rl.GuiButton(pack_atlas_rect, "Pack Atlas") {
|
||||
pack_atlas()
|
||||
}
|
||||
|
||||
if rl.GuiButton(
|
||||
{
|
||||
x = left_half_rect.width / 2,
|
||||
y = elements_height,
|
||||
width = left_half_rect.width / 2 - small_offset,
|
||||
height = small_offset,
|
||||
},
|
||||
"Clear Atlas",
|
||||
) {
|
||||
clear_atlas_rect := rl.Rectangle {
|
||||
x = left_half_rect.width / 2,
|
||||
y = elements_height,
|
||||
width = left_half_rect.width / 2 - small_offset,
|
||||
height = small_offset,
|
||||
}
|
||||
if rl.GuiButton(clear_atlas_rect, "Clear Atlas") {
|
||||
clear_atlas_data()
|
||||
}
|
||||
elements_height += small_offset * 2
|
||||
|
||||
if rl.GuiButton(
|
||||
{
|
||||
x = small_offset,
|
||||
y = elements_height,
|
||||
width = left_half_rect.width / 2 - small_offset,
|
||||
height = small_offset,
|
||||
},
|
||||
"Save",
|
||||
) {
|
||||
save_output()
|
||||
save_rect := rl.Rectangle {
|
||||
x = small_offset,
|
||||
y = elements_height,
|
||||
width = left_half_rect.width / 2 - small_offset,
|
||||
height = small_offset,
|
||||
}
|
||||
if rl.GuiButton(save_rect, "Save") {
|
||||
save()
|
||||
}
|
||||
|
||||
if rl.GuiButton(
|
||||
{
|
||||
x = left_half_rect.width / 2,
|
||||
y = elements_height,
|
||||
width = left_half_rect.width / 2 - small_offset,
|
||||
height = small_offset,
|
||||
},
|
||||
"Save To...",
|
||||
) {
|
||||
if output_folder, ok := g_mem.output_folder_path.(string); ok {
|
||||
save_metadata_simple(output_folder)
|
||||
}
|
||||
save_to_rect := rl.Rectangle {
|
||||
x = left_half_rect.width / 2,
|
||||
y = elements_height,
|
||||
width = left_half_rect.width / 2 - small_offset,
|
||||
height = small_offset,
|
||||
}
|
||||
if rl.GuiButton(save_to_rect, "Save To...") {
|
||||
save_to()
|
||||
}
|
||||
elements_height += small_offset * 2
|
||||
}
|
||||
|
|
@ -437,9 +498,9 @@ open_file_dialog :: proc() {
|
|||
source_files_to_pack := strings.clone_from_cstring(file_paths_conc, context.allocator)
|
||||
g_mem.source_files_to_pack = strings.split(source_files_to_pack, "|")
|
||||
|
||||
fmt.println(g_mem.source_files_to_pack)
|
||||
log.info(g_mem.source_files_to_pack)
|
||||
} else {
|
||||
fmt.println("No files were selected!")
|
||||
log.error("No files were selected!")
|
||||
}
|
||||
|
||||
case .SourceFolder:
|
||||
|
|
@ -451,9 +512,9 @@ open_file_dialog :: proc() {
|
|||
)
|
||||
if len(file) > 0 {
|
||||
g_mem.source_location_to_pack = strings.clone_from_cstring(file)
|
||||
fmt.println(g_mem.source_location_to_pack)
|
||||
log.info(g_mem.source_location_to_pack)
|
||||
} else {
|
||||
fmt.println("Got an empty path from the file dialog!")
|
||||
log.error("Got an empty path from the file dialog!")
|
||||
}
|
||||
|
||||
case .OutputFolder:
|
||||
|
|
@ -465,9 +526,9 @@ open_file_dialog :: proc() {
|
|||
)
|
||||
if len(file) > 0 {
|
||||
g_mem.output_folder_path = strings.clone_from_cstring(file)
|
||||
fmt.println(g_mem.output_folder_path)
|
||||
log.info(g_mem.output_folder_path)
|
||||
} else {
|
||||
fmt.println("Got an empty path from the file dialog!")
|
||||
log.error("Got an empty path from the file dialog!")
|
||||
}
|
||||
|
||||
case .SaveFileAs:
|
||||
|
|
@ -475,7 +536,7 @@ open_file_dialog :: proc() {
|
|||
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, generator.OS_FILE_SEPARATOR, "atlas.png"},
|
||||
)
|
||||
default_path_to_save: cstring = strings.clone_to_cstring(default_path_filename)
|
||||
file_path = cstring(
|
||||
|
|
@ -491,7 +552,11 @@ open_file_dialog :: proc() {
|
|||
file_path = cstring(diag.save_file_dialog("Save as...", "", 1, &patterns[0], "Atlas"))
|
||||
}
|
||||
if file_path != nil {
|
||||
save_output()
|
||||
generator.save_output(
|
||||
g_mem.output_folder_path,
|
||||
g_mem.atlas_metadata,
|
||||
g_mem.atlas_render_texture_target,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -500,9 +565,8 @@ open_file_dialog :: proc() {
|
|||
}
|
||||
|
||||
clear_atlas_data :: proc() {
|
||||
if metadata, ok := g_mem.atlas_metadata.([dynamic]SpriteAtlasMetadata); ok {
|
||||
if metadata, ok := g_mem.atlas_metadata.([dynamic]generator.SpriteAtlasMetadata); ok {
|
||||
delete(metadata)
|
||||
// g_mem.atlas_metadata = nil
|
||||
}
|
||||
g_mem.atlas_render_has_preview = false
|
||||
}
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
package utils
|
||||
package generator
|
||||
|
||||
import "core:fmt"
|
||||
import s "core:strings"
|
||||
|
||||
// todo(stefan): Simplify this whole flags business,
|
||||
// this can be implemented in a simpler fashion
|
||||
|
||||
CLIFlagType :: enum {
|
||||
Unknown,
|
||||
InputFiles,
|
||||
|
|
@ -18,7 +21,7 @@ CLIFlagType :: enum {
|
|||
CLI_FLAG_STRINGS := [CLIFlagType][]string {
|
||||
.Unknown = {""},
|
||||
.Help = {"h", "help"},
|
||||
.InputFiles = {"f", "input-files"},
|
||||
.InputFiles = {"i", "input-files"},
|
||||
.InputFolder = {"d", "input-directory"},
|
||||
.OutputFolder = {"o", "out"},
|
||||
.EnableMetadataOutput = {"m", "export-metadata"},
|
||||
|
|
@ -30,13 +33,13 @@ CLI_FLAG_STRINGS := [CLIFlagType][]string {
|
|||
CLI_FLAG_DESCRIPTIONS := [CLIFlagType]string {
|
||||
.Unknown = "Invalid flag",
|
||||
.Help = "Prints the help message... hello!",
|
||||
.InputFiles = "(real) path the source files for the packer (realpaths only), for multiple files you can provide one string of concateneted paths, separated by a ';'",
|
||||
.InputFolder = "(real) path to a folder full of source files. This is an alternative to the -i[,input-files] flag",
|
||||
.OutputFolder = "(real) path to the output folder for all the resulting files to be saved to.",
|
||||
.InputFiles = "Full path to the source files for the packer, for multiple files you can provide one string of concateneted paths, separated by a ';'",
|
||||
.InputFolder = "Full path to a folder full of source files. This is an alternative to the -i[,input-files] flag",
|
||||
.OutputFolder = "Full path to the output folder for all the resulting files to be saved to.",
|
||||
.EnableMetadataOutput = "Whether or not to export metadata (JSON or source files with the offsets for the packer sprites in the atlas)",
|
||||
.ConfigPath = "(real) path to a config file (json) that contains string definitions for exporting custom source files. More on this in the docs.",
|
||||
.MetadataJSONOutputPath = "(real) path for the resulting JSON that will be generated for the atlas. It overrides the name & location in regards to the -o[,output-folder] flag",
|
||||
.SourceCodeOutputPathOutputPath = "(real) path for the resulting source code file that will be generated for the atlas. It overrides the name & location in regards to the -o[,output-folder] flag",
|
||||
.ConfigPath = "Full path to a config file (json) that contains string definitions for exporting custom source files. More on this in the docs.",
|
||||
.MetadataJSONOutputPath = "Full path for the resulting JSON that will be generated for the atlas. It overrides the name & location in regards to the -o[,output-folder] flag",
|
||||
.SourceCodeOutputPathOutputPath = "Full path for the resulting source code file that will be generated for the atlas. It overrides the name & location in regards to the -o[,output-folder] flag",
|
||||
}
|
||||
|
||||
CLIOutputSettings :: struct {
|
||||
|
|
@ -83,12 +86,9 @@ print_help :: proc() {
|
|||
for flag in CLIFlagType {
|
||||
if flag == .Unknown do continue
|
||||
|
||||
fmt.printfln(
|
||||
"Flag: -%v,%v \t -- %v",
|
||||
CLI_FLAG_STRINGS[flag][0],
|
||||
CLI_FLAG_STRINGS[flag][1],
|
||||
CLI_FLAG_DESCRIPTIONS[flag],
|
||||
)
|
||||
flag_info := CLI_FLAG_STRINGS[flag]
|
||||
flag_desc := CLI_FLAG_DESCRIPTIONS[flag]
|
||||
fmt.printfln("Flag: -%v,%v \t -- %v", flag_info[0], flag_info[1], flag_desc)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
package game
|
||||
package generator
|
||||
|
||||
import ase "./aseprite"
|
||||
import ase "../../vendors/aseprite"
|
||||
import "core:encoding/json"
|
||||
import "core:fmt"
|
||||
import "core:log"
|
||||
import "core:os"
|
||||
import fp "core:path/filepath"
|
||||
import "core:slice"
|
||||
|
|
@ -10,15 +11,12 @@ import "core:strings"
|
|||
import rl "vendor:raylib"
|
||||
import stbrp "vendor:stb/rect_pack"
|
||||
|
||||
import utils "./utils"
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
os_file_separator :: "\\"
|
||||
OS_FILE_SEPARATOR :: "\\"
|
||||
} else {
|
||||
os_file_separator :: "/"
|
||||
OS_FILE_SEPARATOR :: "/"
|
||||
}
|
||||
|
||||
|
||||
CellData :: struct {
|
||||
layer_index: u16,
|
||||
opacity: u8,
|
||||
|
|
@ -40,6 +38,35 @@ SpriteAtlasMetadata :: struct {
|
|||
size: [2]i32,
|
||||
}
|
||||
|
||||
SourceCodeGeneratorMetadata :: struct {
|
||||
file_defines: struct {
|
||||
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}
|
||||
},
|
||||
custom_data_type: struct {
|
||||
name: string,
|
||||
type_declaration: string, // contains one param: custom_data_type.name + the rest of the type declaration like braces of the syntax & the type members
|
||||
},
|
||||
enum_data: struct {
|
||||
name: string,
|
||||
begin_line: string, // contains one params: enum_data.name
|
||||
entry_line: string,
|
||||
end_line: string,
|
||||
},
|
||||
array_data: struct {
|
||||
name: string,
|
||||
type: string,
|
||||
begin_line: string, // array begin line contains 2 params in the listed order: array.name, array.type
|
||||
entry_line: string, // array entry contains 5 params in the listed order: cell.name, cell.location.x, cell.location.y, cell.size.x, cell.size.y,
|
||||
end_line: string,
|
||||
},
|
||||
}
|
||||
|
||||
unmarshall_aseprite_dir :: proc(
|
||||
path: string,
|
||||
atlas_entries: ^[dynamic]AtlasEntry,
|
||||
|
|
@ -53,7 +80,7 @@ unmarshall_aseprite_dir :: proc(
|
|||
unmarshall_aseprite_files_file_info(fis, atlas_entries, alloc)
|
||||
}
|
||||
} else {
|
||||
fmt.println("Couldn't open folder: ", path)
|
||||
log.errorf("Couldn't open folder: ", path)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +114,7 @@ unmarshall_aseprite_files :: proc(
|
|||
extension := fp.ext(file)
|
||||
if extension != ".aseprite" do continue
|
||||
|
||||
fmt.println("Unmarshalling file: ", file)
|
||||
log.infof("Unmarshalling file: ", file)
|
||||
ase.unmarshal_from_filename(file, &aseprite_document, alloc)
|
||||
atlas_entry := atlas_entry_from_compressed_cells(aseprite_document)
|
||||
atlas_entry.path = file
|
||||
|
|
@ -97,11 +124,11 @@ unmarshall_aseprite_files :: proc(
|
|||
}
|
||||
|
||||
/*
|
||||
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.frames = auto_cast len(document.frames)
|
||||
fmt.println("N Frames: ", len(document.frames))
|
||||
log.infof("N Frames: ", len(document.frames))
|
||||
// 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
|
||||
|
|
@ -110,13 +137,13 @@ atlas_entry_from_compressed_cells :: proc(document: ase.Document) -> (atlas_entr
|
|||
// instead of iterating all layers for each frame
|
||||
// might be even quicker to first get that information an allocate at once the amount of cells we need
|
||||
for frame, frameIdx in document.frames {
|
||||
fmt.printfln("Frame_{0} Chunks: ", frameIdx, len(frame.chunks))
|
||||
log.infof("Frame_{0} Chunks: ", frameIdx, len(frame.chunks))
|
||||
for chunk in frame.chunks {
|
||||
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
|
||||
|
||||
fmt.println(cel_chunk.layer_index)
|
||||
log.info(cel_chunk.layer_index)
|
||||
|
||||
cell := CellData {
|
||||
img = rl.Image {
|
||||
|
|
@ -134,7 +161,7 @@ atlas_entry_from_compressed_cells :: proc(document: ase.Document) -> (atlas_entr
|
|||
}
|
||||
|
||||
if layer_chunk, ok := chunk.(ase.Layer_Chunk); ok {
|
||||
fmt.println("Layer chunk: ", layer_chunk)
|
||||
log.infof("Layer chunk: ", layer_chunk)
|
||||
append(&atlas_entry.layer_names, layer_chunk.name)
|
||||
}
|
||||
}
|
||||
|
|
@ -148,7 +175,7 @@ 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,
|
||||
|
|
@ -201,10 +228,10 @@ pack_atlas_entries :: proc(
|
|||
stbrp.init_target(&ctx, atlas.width, atlas.height, &nodes[0], i32(num_entries))
|
||||
res := stbrp.pack_rects(&ctx, &rects[0], i32(num_entries))
|
||||
if res == 1 {
|
||||
fmt.println("Packed everything successfully!")
|
||||
fmt.printfln("Rects: {0}", rects[:])
|
||||
log.info("Packed everything successfully!")
|
||||
log.infof("Rects: {0}", rects[:])
|
||||
} else {
|
||||
fmt.println("Failed to pack everything!")
|
||||
log.error("Failed to pack everything!")
|
||||
}
|
||||
|
||||
for rect, rectIdx in rects {
|
||||
|
|
@ -228,7 +255,7 @@ pack_atlas_entries :: proc(
|
|||
// 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)
|
||||
log.infof("Src rect: {0}\nDst rect:{1}", src_rect, dst_rect)
|
||||
}
|
||||
|
||||
metadata := make([dynamic]SpriteAtlasMetadata, allocator)
|
||||
|
|
@ -250,7 +277,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,
|
||||
},
|
||||
|
|
@ -262,53 +289,24 @@ pack_atlas_entries :: proc(
|
|||
return metadata
|
||||
}
|
||||
|
||||
SourceCodeGeneratorMetadata :: struct {
|
||||
file_defines: struct {
|
||||
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}
|
||||
},
|
||||
custom_data_type: struct {
|
||||
name: string,
|
||||
type_declaration: string, // contains one param: custom_data_type.name + the rest of the type declaration like braces of the syntax & the type members
|
||||
},
|
||||
enum_data: struct {
|
||||
name: string,
|
||||
begin_line: string, // contains one params: enum_data.name
|
||||
entry_line: string,
|
||||
end_line: string,
|
||||
},
|
||||
array_data: struct {
|
||||
name: string,
|
||||
type: string,
|
||||
begin_line: string, // array begin line contains 2 params in the listed order: array.name, array.type
|
||||
entry_line: string, // array entry contains 5 params in the listed order: cell.name, cell.location.x, cell.location.y, cell.size.x, cell.size.y,
|
||||
end_line: string,
|
||||
},
|
||||
}
|
||||
|
||||
odin_source_generator_metadata := SourceCodeGeneratorMetadata {
|
||||
file_defines = {
|
||||
file_defines = {
|
||||
top = "package atlas_bindings\n\n",
|
||||
bottom = "",
|
||||
file_name = "metadata",
|
||||
file_extension = ".odin",
|
||||
},
|
||||
custom_data_type = {
|
||||
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",
|
||||
|
|
@ -320,23 +318,23 @@ odin_source_generator_metadata := SourceCodeGeneratorMetadata {
|
|||
|
||||
|
||||
cpp_source_generator_metadata := SourceCodeGeneratorMetadata {
|
||||
file_defines = {
|
||||
file_defines = {
|
||||
top = "#include <iostream>\n\n",
|
||||
bottom = "",
|
||||
file_name = "metadata",
|
||||
file_extension = ".hpp",
|
||||
},
|
||||
custom_data_type = {
|
||||
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",
|
||||
|
|
@ -346,9 +344,9 @@ cpp_source_generator_metadata := SourceCodeGeneratorMetadata {
|
|||
}
|
||||
|
||||
/*
|
||||
Generates a barebones file with the package name "atlas_bindings",
|
||||
the file contains an array of offsets, indexed by an enum.
|
||||
The enum has unique names
|
||||
Generates a barebones file with the package name "atlas_bindings",
|
||||
the file contains an array of offsets, indexed by an enum.
|
||||
The enum has unique names
|
||||
*/
|
||||
generate_odin_enums_and_atlas_offsets_file_sb :: proc(
|
||||
metadata: []SpriteAtlasMetadata,
|
||||
|
|
@ -389,7 +387,7 @@ generate_odin_enums_and_atlas_offsets_file_sb :: proc(
|
|||
// end offsets array
|
||||
strings.write_string(&sb, "}\n\n")
|
||||
|
||||
fmt.println("\n", strings.to_string(sb))
|
||||
log.info("\n", strings.to_string(sb))
|
||||
|
||||
return sb
|
||||
}
|
||||
|
|
@ -405,26 +403,21 @@ metadata_source_code_generate :: proc(
|
|||
strings.write_string(&sb, codegen.file_defines.top)
|
||||
|
||||
// Introduce the Rect type
|
||||
// strings.write_string(&sb, "AtlasRect :: struct { x, y, w, h: i32 }\n\n")
|
||||
strings.write_string(
|
||||
&sb,
|
||||
fmt.aprintf(codegen.custom_data_type.type_declaration, codegen.custom_data_type.name),
|
||||
)
|
||||
// start enum
|
||||
// strings.write_string(&sb, "AtlasSprite :: enum {\n")
|
||||
strings.write_string(&sb, fmt.aprintf(codegen.enum_data.begin_line, codegen.enum_data.name))
|
||||
{
|
||||
for cell in metadata {
|
||||
// strings.write_string(&sb, fmt.aprintf("\t%s,\n", cell.name))
|
||||
strings.write_string(&sb, fmt.aprintf(codegen.enum_data.entry_line, cell.name))
|
||||
}
|
||||
}
|
||||
// end enum
|
||||
// strings.write_string(&sb, "}\n\n")
|
||||
strings.write_string(&sb, codegen.enum_data.end_line)
|
||||
|
||||
// start offsets array
|
||||
// strings.write_string(&sb, "ATLAS_SPRITES := []AtlasRect {\n")
|
||||
strings.write_string(
|
||||
&sb,
|
||||
fmt.aprintf(
|
||||
|
|
@ -448,42 +441,46 @@ metadata_source_code_generate :: proc(
|
|||
}
|
||||
}
|
||||
// end offsets array
|
||||
// strings.write_string(&sb, "}\n\n")
|
||||
|
||||
strings.write_string(&sb, codegen.array_data.end_line)
|
||||
|
||||
strings.write_string(&sb, codegen.file_defines.bottom)
|
||||
|
||||
fmt.println("\n", strings.to_string(sb))
|
||||
log.info("\n", strings.to_string(sb))
|
||||
|
||||
return sb
|
||||
|
||||
}
|
||||
|
||||
save_output :: proc() {
|
||||
output_path, ok := g_mem.output_folder_path.(string)
|
||||
save_output :: proc(
|
||||
output_folder_path: Maybe(string),
|
||||
atlas_metadata: Maybe([dynamic]SpriteAtlasMetadata),
|
||||
atlas_render_texture_target: rl.RenderTexture2D,
|
||||
) {
|
||||
output_path, ok := output_folder_path.(string)
|
||||
if !ok || output_path == "" {
|
||||
fmt.println("Output path is empty!")
|
||||
log.error("Output path is empty!")
|
||||
return
|
||||
}
|
||||
|
||||
image := rl.LoadImageFromTexture(g_mem.atlas_render_texture_target.texture)
|
||||
image := rl.LoadImageFromTexture(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"}),
|
||||
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 metadata, ok := atlas_metadata.([dynamic]SpriteAtlasMetadata); ok {
|
||||
log.info("Building metadata...")
|
||||
if json_metadata, jok := json.marshal(metadata); jok == nil {
|
||||
os.write_entire_file(
|
||||
strings.concatenate({output_path, os_file_separator, "metadata.json"}),
|
||||
strings.concatenate({output_path, OS_FILE_SEPARATOR, "metadata.json"}),
|
||||
json_metadata,
|
||||
)
|
||||
} else {
|
||||
fmt.println("Failed to marshall the atlas metadata to a json!")
|
||||
log.error("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
|
||||
|
|
@ -492,14 +489,14 @@ save_output :: proc() {
|
|||
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"}),
|
||||
strings.concatenate({output_path, OS_FILE_SEPARATOR, "metadata.odin"}),
|
||||
transmute([]byte)odin_metadata,
|
||||
)
|
||||
if !ok {
|
||||
fmt.println("Failed to save 'metadata.odin'")
|
||||
log.error("Failed to save 'metadata.odin'")
|
||||
}
|
||||
} else {
|
||||
fmt.println("No metadata to export!")
|
||||
log.error("No metadata to export!")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -509,30 +506,31 @@ save_metadata_simple :: proc(
|
|||
json_file_name: Maybe(string),
|
||||
source_file_name: Maybe(string),
|
||||
source_gen_metadata: Maybe(SourceCodeGeneratorMetadata),
|
||||
atlas_metadata: Maybe([dynamic]SpriteAtlasMetadata),
|
||||
) {
|
||||
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!")
|
||||
log.error("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!")
|
||||
metadata, ok := atlas_metadata.([dynamic]SpriteAtlasMetadata);if !ok {
|
||||
log.error("No metadata to export!")
|
||||
}
|
||||
|
||||
fmt.println("Building metadata...")
|
||||
log.info("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},
|
||||
{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)
|
||||
log.errorf("Failed to write json to file: ", json_output_path)
|
||||
}
|
||||
} else {
|
||||
fmt.println("Failed to marshall the atlas metadata to a json!")
|
||||
log.error("Failed to marshall the atlas metadata to a json!")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -544,45 +542,45 @@ save_metadata_simple :: proc(
|
|||
|
||||
source_metadata := strings.to_string(sb)
|
||||
source_output_path := strings.concatenate(
|
||||
{
|
||||
{
|
||||
output_path,
|
||||
os_file_separator,
|
||||
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)
|
||||
log.errorf("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"},
|
||||
{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)
|
||||
log.errorf("Failed to save source code to file:", source_output_path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
save_metadata :: proc(
|
||||
settings: utils.CLIPackerSettings,
|
||||
settings: CLIPackerSettings,
|
||||
atlas_entries: []AtlasEntry,
|
||||
atlas_metadata: []SpriteAtlasMetadata,
|
||||
) {
|
||||
metadata, ok := settings.metadata.(utils.CLIMetadataSettings);if !ok do return
|
||||
metadata, ok := settings.metadata.(CLIMetadataSettings);if !ok do return
|
||||
|
||||
if json_path, ok := metadata.json_path.(string); ok {
|
||||
json_bytes, jerr := json.marshal(atlas_metadata)
|
||||
if jerr == nil {
|
||||
os.write_entire_file(json_path, json_bytes)
|
||||
} else {
|
||||
fmt.println("Failed to marshall metadata")
|
||||
log.error("Failed to marshall metadata")
|
||||
}
|
||||
}
|
||||
if source_code_path, ok := metadata.source_code_path.(string); ok {
|
||||
|
|
@ -1,196 +0,0 @@
|
|||
// Development game exe. Loads game.dll and reloads it whenever it changes.
|
||||
|
||||
package main
|
||||
|
||||
import "core:c/libc"
|
||||
import "core:dynlib"
|
||||
import "core:fmt"
|
||||
import "core:log"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
DLL_EXT :: ".dll"
|
||||
} else when ODIN_OS == .Darwin {
|
||||
DLL_EXT :: ".dylib"
|
||||
} else {
|
||||
DLL_EXT :: ".so"
|
||||
}
|
||||
|
||||
copy_dll :: proc(to: string) -> bool {
|
||||
exit: i32
|
||||
when ODIN_OS == .Windows {
|
||||
exit = libc.system(fmt.ctprintf("copy game.dll {0}", to))
|
||||
} else {
|
||||
exit = libc.system(fmt.ctprintf("cp game" + DLL_EXT + " {0}", to))
|
||||
}
|
||||
|
||||
if exit != 0 {
|
||||
fmt.printfln("Failed to copy game" + DLL_EXT + " to {0}", to)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
GameAPI :: struct {
|
||||
lib: dynlib.Library,
|
||||
init_window: proc(),
|
||||
init: proc(),
|
||||
update: proc() -> bool,
|
||||
shutdown: proc(),
|
||||
shutdown_window: proc(),
|
||||
memory: proc() -> rawptr,
|
||||
memory_size: proc() -> int,
|
||||
hot_reloaded: proc(mem: rawptr),
|
||||
force_reload: proc() -> bool,
|
||||
force_restart: proc() -> bool,
|
||||
modification_time: os.File_Time,
|
||||
api_version: int,
|
||||
}
|
||||
|
||||
load_game_api :: proc(api_version: int) -> (api: GameAPI, ok: bool) {
|
||||
mod_time, mod_time_error := os.last_write_time_by_name("game" + DLL_EXT)
|
||||
if mod_time_error != os.ERROR_NONE {
|
||||
fmt.printfln(
|
||||
"Failed getting last write time of game" + DLL_EXT + ", error code: {1}",
|
||||
mod_time_error,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE: this needs to be a relative path for Linux to work.
|
||||
game_dll_name := fmt.tprintf(
|
||||
"{0}game_{1}" + DLL_EXT,
|
||||
"./" when ODIN_OS != .Windows else "",
|
||||
api_version,
|
||||
)
|
||||
copy_dll(game_dll_name) or_return
|
||||
|
||||
_, ok = dynlib.initialize_symbols(&api, game_dll_name, "game_", "lib")
|
||||
if !ok {
|
||||
fmt.printfln("Failed initializing symbols: {0}", dynlib.last_error())
|
||||
}
|
||||
|
||||
api.api_version = api_version
|
||||
api.modification_time = mod_time
|
||||
ok = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
unload_game_api :: proc(api: ^GameAPI) {
|
||||
if api.lib != nil {
|
||||
if !dynlib.unload_library(api.lib) {
|
||||
fmt.printfln("Failed unloading lib: {0}", dynlib.last_error())
|
||||
}
|
||||
}
|
||||
|
||||
if os.remove(fmt.tprintf("game_{0}" + DLL_EXT, api.api_version)) != 0 {
|
||||
fmt.printfln("Failed to remove game_{0}" + DLL_EXT + " copy", api.api_version)
|
||||
}
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
context.logger = log.create_console_logger()
|
||||
|
||||
default_allocator := context.allocator
|
||||
tracking_allocator: mem.Tracking_Allocator
|
||||
mem.tracking_allocator_init(&tracking_allocator, default_allocator)
|
||||
context.allocator = mem.tracking_allocator(&tracking_allocator)
|
||||
|
||||
reset_tracking_allocator :: proc(a: ^mem.Tracking_Allocator) -> bool {
|
||||
err := false
|
||||
|
||||
for _, value in a.allocation_map {
|
||||
fmt.printf("%v: Leaked %v bytes\n", value.location, value.size)
|
||||
err = true
|
||||
}
|
||||
|
||||
mem.tracking_allocator_clear(a)
|
||||
return err
|
||||
}
|
||||
|
||||
game_api_version := 0
|
||||
game_api, game_api_ok := load_game_api(game_api_version)
|
||||
|
||||
if !game_api_ok {
|
||||
fmt.println("Failed to load Game API")
|
||||
return
|
||||
}
|
||||
|
||||
game_api_version += 1
|
||||
game_api.init_window()
|
||||
game_api.init()
|
||||
|
||||
old_game_apis := make([dynamic]GameAPI, default_allocator)
|
||||
|
||||
window_open := true
|
||||
for window_open {
|
||||
window_open = game_api.update()
|
||||
force_reload := game_api.force_reload()
|
||||
force_restart := game_api.force_restart()
|
||||
reload := force_reload || force_restart
|
||||
game_dll_mod, game_dll_mod_err := os.last_write_time_by_name("game" + DLL_EXT)
|
||||
|
||||
if game_dll_mod_err == os.ERROR_NONE && game_api.modification_time != game_dll_mod {
|
||||
reload = true
|
||||
}
|
||||
|
||||
if reload {
|
||||
new_game_api, new_game_api_ok := load_game_api(game_api_version)
|
||||
|
||||
if new_game_api_ok {
|
||||
if game_api.memory_size() != new_game_api.memory_size() || force_restart {
|
||||
game_api.shutdown()
|
||||
reset_tracking_allocator(&tracking_allocator)
|
||||
|
||||
for &g in old_game_apis {
|
||||
unload_game_api(&g)
|
||||
}
|
||||
|
||||
clear(&old_game_apis)
|
||||
unload_game_api(&game_api)
|
||||
game_api = new_game_api
|
||||
game_api.init()
|
||||
} else {
|
||||
append(&old_game_apis, game_api)
|
||||
game_memory := game_api.memory()
|
||||
game_api = new_game_api
|
||||
game_api.hot_reloaded(game_memory)
|
||||
}
|
||||
|
||||
game_api_version += 1
|
||||
}
|
||||
}
|
||||
|
||||
for b in tracking_allocator.bad_free_array {
|
||||
log.error("Bad free at: %v", b.location)
|
||||
}
|
||||
|
||||
clear(&tracking_allocator.bad_free_array)
|
||||
free_all(context.temp_allocator)
|
||||
}
|
||||
|
||||
free_all(context.temp_allocator)
|
||||
game_api.shutdown()
|
||||
reset_tracking_allocator(&tracking_allocator)
|
||||
|
||||
for &g in old_game_apis {
|
||||
unload_game_api(&g)
|
||||
}
|
||||
|
||||
delete(old_game_apis)
|
||||
|
||||
game_api.shutdown_window()
|
||||
unload_game_api(&game_api)
|
||||
mem.tracking_allocator_destroy(&tracking_allocator)
|
||||
}
|
||||
|
||||
// make game use good GPU on laptops etc
|
||||
|
||||
@(export)
|
||||
NvOptimusEnablement: u32 = 1
|
||||
|
||||
@(export)
|
||||
AmdPowerXpressRequestHighPerformance: i32 = 1
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
// For making a release exe that does not use hot reload.
|
||||
|
||||
package main_release
|
||||
|
||||
import "core:log"
|
||||
import "core:os"
|
||||
|
||||
import game ".."
|
||||
|
||||
UseTrackingAllocator :: #config(UseTrackingAllocator, false)
|
||||
|
||||
main :: proc() {
|
||||
when UseTrackingAllocator {
|
||||
default_allocator := context.allocator
|
||||
tracking_allocator: Tracking_Allocator
|
||||
tracking_allocator_init(&tracking_allocator, default_allocator)
|
||||
context.allocator = allocator_from_tracking_allocator(&tracking_allocator)
|
||||
}
|
||||
|
||||
mode: int = 0
|
||||
when ODIN_OS == .Linux || ODIN_OS == .Darwin {
|
||||
mode = os.S_IRUSR | os.S_IWUSR | os.S_IRGRP | os.S_IROTH
|
||||
}
|
||||
|
||||
logh, logh_err := os.open("log.txt", (os.O_CREATE | os.O_TRUNC | os.O_RDWR), mode)
|
||||
|
||||
if logh_err == os.ERROR_NONE {
|
||||
os.stdout = logh
|
||||
os.stderr = logh
|
||||
}
|
||||
|
||||
logger :=
|
||||
logh_err == os.ERROR_NONE ? log.create_file_logger(logh) : log.create_console_logger()
|
||||
context.logger = logger
|
||||
|
||||
game.game_init_window()
|
||||
game.game_init()
|
||||
|
||||
window_open := true
|
||||
for window_open {
|
||||
window_open = game.game_update()
|
||||
|
||||
when UseTrackingAllocator {
|
||||
for b in tracking_allocator.bad_free_array {
|
||||
log.error("Bad free at: %v", b.location)
|
||||
}
|
||||
|
||||
clear(&tracking_allocator.bad_free_array)
|
||||
}
|
||||
|
||||
free_all(context.temp_allocator)
|
||||
}
|
||||
|
||||
free_all(context.temp_allocator)
|
||||
game.game_shutdown()
|
||||
game.game_shutdown_window()
|
||||
|
||||
if logh_err == os.ERROR_NONE {
|
||||
log.destroy_file_logger(&logger)
|
||||
}
|
||||
|
||||
when UseTrackingAllocator {
|
||||
for key, value in tracking_allocator.allocation_map {
|
||||
log.error("%v: Leaked %v bytes\n", value.location, value.size)
|
||||
}
|
||||
|
||||
tracking_allocator_destroy(&tracking_allocator)
|
||||
}
|
||||
}
|
||||
|
||||
// make game use good GPU on laptops etc
|
||||
|
||||
@(export)
|
||||
NvOptimusEnablement: u32 = 1
|
||||
|
||||
@(export)
|
||||
AmdPowerXpressRequestHighPerformance: i32 = 1
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
package game
|
||||
|
||||
import rl "vendor:raylib"
|
||||
|
||||
@(export)
|
||||
game_update :: proc() -> bool {
|
||||
update()
|
||||
draw()
|
||||
return !rl.WindowShouldClose()
|
||||
}
|
||||
|
||||
@(export)
|
||||
game_init_window :: proc() {
|
||||
rl.SetConfigFlags({.WINDOW_RESIZABLE})
|
||||
rl.InitWindow(1400, 800, "YAAP - Yet Another Atlas Packer")
|
||||
rl.SetWindowMinSize(1400, 800)
|
||||
}
|
||||
|
||||
@(export)
|
||||
game_init :: proc() {
|
||||
g_mem = new(GameMemory)
|
||||
|
||||
g_mem^ = GameMemory{}
|
||||
|
||||
game_hot_reloaded(g_mem)
|
||||
|
||||
|
||||
when !ODIN_DEBUG {
|
||||
rl.SetExitKey(nil)
|
||||
}
|
||||
|
||||
current_monitor := rl.GetCurrentMonitor()
|
||||
g_mem.monitor_info = MonitorInformation {
|
||||
max_width = auto_cast rl.GetMonitorWidth(current_monitor),
|
||||
max_height = auto_cast rl.GetMonitorHeight(current_monitor),
|
||||
}
|
||||
|
||||
g_mem.window_info = WindowInformation {
|
||||
w = 1280,
|
||||
h = 720,
|
||||
}
|
||||
|
||||
g_mem.atlas_render_texture_target = rl.LoadRenderTexture(256, 256)
|
||||
g_mem.atlas_render_size = 256
|
||||
|
||||
checkered_img := rl.GenImageChecked(256, 256, 256 / 4, 256 / 4, rl.GRAY, rl.DARKGRAY)
|
||||
defer rl.UnloadImage(checkered_img)
|
||||
g_mem.atlas_checked_background.texture = rl.LoadTextureFromImage(checkered_img)
|
||||
|
||||
rl.SetTargetFPS(rl.GetMonitorRefreshRate(current_monitor))
|
||||
rl.GuiLoadStyle("./styles/style_candy.rgs")
|
||||
}
|
||||
|
||||
@(export)
|
||||
game_shutdown :: proc() {
|
||||
free(g_mem)
|
||||
}
|
||||
|
||||
@(export)
|
||||
game_shutdown_window :: proc() {
|
||||
rl.CloseWindow()
|
||||
}
|
||||
|
||||
@(export)
|
||||
game_memory :: proc() -> rawptr {
|
||||
return g_mem
|
||||
}
|
||||
|
||||
@(export)
|
||||
game_memory_size :: proc() -> int {
|
||||
return size_of(GameMemory)
|
||||
}
|
||||
|
||||
@(export)
|
||||
game_hot_reloaded :: proc(mem: rawptr) {
|
||||
g_mem = (^GameMemory)(mem)
|
||||
rl.GuiLoadStyle("./styles/style_candy.rgs")
|
||||
}
|
||||
|
||||
@(export)
|
||||
game_force_reload :: proc() -> bool {
|
||||
return rl.IsKeyPressed(.F5)
|
||||
}
|
||||
|
||||
@(export)
|
||||
game_force_restart :: proc() -> bool {
|
||||
return rl.IsKeyPressed(.F6)
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
// This implements simple animations using sprite sheets. The texture in the
|
||||
// `Animation` struct is assumed to contain a horizontal strip of the frames
|
||||
// in the animation. Call `animation_update` to update and then call
|
||||
// `animation_rect` when you wish to know the source rect to use in the texture
|
||||
// With the source rect you can run rl.DrawTextureRec to draw the current frame.
|
||||
|
||||
package utils
|
||||
|
||||
import "core:log"
|
||||
|
||||
Animation :: struct {
|
||||
texture: Texture,
|
||||
num_frames: int,
|
||||
current_frame: int,
|
||||
frame_timer: f32,
|
||||
frame_length: f32,
|
||||
}
|
||||
|
||||
animation_create :: proc(tex: Texture, num_frames: int, frame_length: f32) -> Animation {
|
||||
return(
|
||||
Animation {
|
||||
texture = tex,
|
||||
num_frames = num_frames,
|
||||
frame_length = frame_length,
|
||||
frame_timer = frame_length,
|
||||
} \
|
||||
)
|
||||
}
|
||||
|
||||
animation_update :: proc(a: ^Animation, dt: f32) {
|
||||
a.frame_timer -= dt
|
||||
|
||||
if a.frame_timer <= 0 {
|
||||
a.frame_timer = a.frame_length + a.frame_timer
|
||||
a.current_frame += 1
|
||||
|
||||
if a.current_frame >= a.num_frames {
|
||||
a.current_frame = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animation_rect :: proc(a: Animation) -> Rect {
|
||||
if a.num_frames == 0 {
|
||||
log.error("Animation has zero frames")
|
||||
return RectEmpty
|
||||
}
|
||||
|
||||
w := f32(a.texture.width) / f32(a.num_frames)
|
||||
h := f32(a.texture.height)
|
||||
|
||||
return {x = f32(a.current_frame) * w, y = 0, width = w, height = h}
|
||||
}
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
// This handle-based array gives you a statically allocated array where you can
|
||||
// use index based handles instead of pointers. The handles have a generation
|
||||
// that makes sure you don't get bugs when slots are re-used.
|
||||
// Read more about it here: https://floooh.github.io/2018/06/17/handles-vs-pointers.html */
|
||||
|
||||
package utils
|
||||
|
||||
Handle :: struct($T: typeid) {
|
||||
// idx 0 means unused. Note that slot 0 is a dummy slot, it can never be used.
|
||||
idx: u32,
|
||||
gen: u32,
|
||||
}
|
||||
|
||||
HandleArrayItem :: struct($T: typeid) {
|
||||
item: T,
|
||||
handle: Handle(T),
|
||||
}
|
||||
|
||||
// TODO: Add a freelist that uses some kind of bit array... We should be able to
|
||||
// check 64 item slots at a time that way, but without any dynamic array.
|
||||
HandleArray :: struct($T: typeid, $N: int) {
|
||||
items: #soa[N]HandleArrayItem(T),
|
||||
num_items: u32,
|
||||
}
|
||||
|
||||
ha_add :: proc(a: ^HandleArray($T, $N), v: T) -> (Handle(T), bool) #optional_ok {
|
||||
for idx in 1 ..< a.num_items {
|
||||
i := &a.items[idx]
|
||||
|
||||
if idx != 0 && i.handle.idx == 0 {
|
||||
i.handle.idx = u32(idx)
|
||||
i.item = v
|
||||
return i.handle, true
|
||||
}
|
||||
}
|
||||
|
||||
// Index 0 is dummy
|
||||
if a.num_items == 0 {
|
||||
a.num_items += 1
|
||||
}
|
||||
|
||||
if a.num_items == len(a.items) {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
idx := a.num_items
|
||||
i := &a.items[a.num_items]
|
||||
a.num_items += 1
|
||||
i.handle.idx = idx
|
||||
i.handle.gen = 1
|
||||
i.item = v
|
||||
return i.handle, true
|
||||
}
|
||||
|
||||
ha_get :: proc(a: HandleArray($T, $N), h: Handle(T)) -> (T, bool) {
|
||||
if h.idx == 0 {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
if int(h.idx) < len(a.items) && h.idx < a.num_items && a.items[h.idx].handle == h {
|
||||
return a.items[h.idx].item, true
|
||||
}
|
||||
|
||||
return {}, false
|
||||
}
|
||||
|
||||
ha_get_ptr :: proc(a: HandleArray($T, $N), h: Handle(T)) -> ^T {
|
||||
if h.idx == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if int(h.idx) < len(a.items) && h.idx < a.num_items && a.items[h.idx].handle == h {
|
||||
return &ha.items[h.idx].item
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
ha_remove :: proc(a: ^HandleArray($T, $N), h: Handle(T)) {
|
||||
if h.idx == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if int(h.idx) < len(a.items) && h.idx < a.num_items && a.items[h.idx].handle == h {
|
||||
a.items[h.idx].handle.idx = 0
|
||||
a.items[h.idx].handle.gen += 1
|
||||
}
|
||||
}
|
||||
|
||||
ha_valid :: proc(a: HandleArray($T, $N), h: Handle(T)) -> bool {
|
||||
if h.idx == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return int(h.idx) < len(a.items) && h.idx < a.num_items && a.items[h.idx].handle == h
|
||||
}
|
||||
|
||||
HandleArrayIter :: struct($T: typeid, $N: int) {
|
||||
a: ^HandleArray(T, N),
|
||||
index: int,
|
||||
}
|
||||
|
||||
ha_make_iter :: proc(a: ^HandleArray($T, $N)) -> HandleArrayIter(T, N) {
|
||||
return HandleArrayIter(T, N){a = a}
|
||||
}
|
||||
|
||||
ha_iter :: proc(it: ^HandleArrayIter($T, $N)) -> (val: T, h: Handle(T), cond: bool) {
|
||||
cond = it.index < int(it.a.num_items)
|
||||
|
||||
for ; cond; cond = it.index < int(it.a.num_items) {
|
||||
if it.a.items[it.index].handle.idx == 0 {
|
||||
it.index += 1
|
||||
continue
|
||||
}
|
||||
|
||||
val = it.a.items[it.index].item
|
||||
h = it.a.items[it.index].handle
|
||||
it.index += 1
|
||||
break
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ha_iter_ptr :: proc(it: ^HandleArrayIter($T, $N)) -> (val: ^T, h: Handle(T), cond: bool) {
|
||||
cond = it.index < int(it.a.num_items)
|
||||
|
||||
for ; cond; cond = it.index < int(it.a.num_items) {
|
||||
if it.a.items[it.index].handle.idx == 0 {
|
||||
it.index += 1
|
||||
continue
|
||||
}
|
||||
|
||||
val = &it.a.items[it.index].item
|
||||
h = it.a.items[it.index].handle
|
||||
it.index += 1
|
||||
break
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
// generic odin helpers
|
||||
|
||||
package utils
|
||||
|
||||
import "core:intrinsics"
|
||||
import "core:reflect"
|
||||
import "core:strings"
|
||||
import rl "vendor:raylib"
|
||||
|
||||
Texture :: rl.Texture
|
||||
Color :: rl.Color
|
||||
|
||||
Rect :: rl.Rectangle
|
||||
RectEmpty :: Rect{}
|
||||
|
||||
increase_or_wrap_enum :: proc(e: $T) -> T {
|
||||
ei := int(e) + 1
|
||||
|
||||
if ei >= len(T) {
|
||||
ei = 0
|
||||
}
|
||||
|
||||
return T(ei)
|
||||
}
|
||||
|
||||
union_type :: proc(a: any) -> typeid {
|
||||
return reflect.union_variant_typeid(a)
|
||||
}
|
||||
|
||||
temp_cstring :: proc(s: string) -> cstring {
|
||||
return strings.clone_to_cstring(s, context.temp_allocator)
|
||||
}
|
||||
|
||||
// There is a remap in core:math but it doesn't clamp in the new range, which I
|
||||
// always want.
|
||||
remap :: proc "contextless" (
|
||||
old_value, old_min, old_max, new_min, new_max: $T,
|
||||
) -> (
|
||||
x: T,
|
||||
) where intrinsics.type_is_numeric(T),
|
||||
!intrinsics.type_is_array(T) {
|
||||
old_range := old_max - old_min
|
||||
new_range := new_max - new_min
|
||||
if old_range == 0 {
|
||||
return new_range / 2
|
||||
}
|
||||
return clamp(((old_value - old_min) / old_range) * new_range + new_min, new_min, new_max)
|
||||
}
|
||||
|
||||
Vec2i :: [2]int
|
||||
Vec2 :: [2]f32
|
||||
|
||||
vec2_from_vec2i :: proc(p: Vec2i) -> Vec2 {
|
||||
return {f32(p.x), f32(p.y)}
|
||||
}
|
||||
0
src/aseprite → vendors/aseprite
vendored
0
src/aseprite → vendors/aseprite
vendored
0
src/dialog/build.sh → vendors/dialog/build.sh
vendored
Executable file → Normal file
0
src/dialog/build.sh → vendors/dialog/build.sh
vendored
Executable file → Normal file
Loading…
Add table
Add a link
Reference in a new issue