├── .editorconfig ├── .gitattributes ├── .gitignore ├── .gitmodules ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── README.md ├── aya ├── aya.zig ├── deps │ ├── fontstash │ │ ├── build.zig │ │ ├── fontstash.zig │ │ └── src │ │ │ ├── fontstash.c │ │ │ ├── fontstash.h │ │ │ └── stb_truetype.h │ ├── imgui │ │ ├── Makefile │ │ ├── build.zig │ │ ├── font_awesome.zig │ │ ├── imgui.zig │ │ ├── imgui_gl.zig │ │ ├── temporary_hacks.cpp │ │ └── wrapper.zig │ ├── sdl │ │ ├── build.zig │ │ └── sdl.zig │ └── stb │ │ ├── build.zig │ │ ├── src │ │ ├── stb_image.h │ │ ├── stb_image_write.h │ │ ├── stb_impl.c │ │ └── stb_rect_pack.h │ │ ├── stb.zig │ │ ├── stb_image.zig │ │ ├── stb_image_write.zig │ │ └── stb_rect_pack.zig ├── src │ ├── debug.zig │ ├── fs.zig │ ├── gfx │ │ ├── assets │ │ │ ├── ProggyTiny.ttf │ │ │ ├── sprite_fs.glsl │ │ │ └── sprite_vs.glsl │ │ ├── atlas_batch.zig │ │ ├── batcher.zig │ │ ├── draw.zig │ │ ├── fontbook.zig │ │ ├── gfx.zig │ │ ├── mesh.zig │ │ ├── multi_batcher.zig │ │ ├── offscreen_pass.zig │ │ ├── post_process_stack.zig │ │ ├── resolution_policy.zig │ │ ├── shader.zig │ │ ├── texture.zig │ │ └── triangle_batcher.zig │ ├── imgui │ │ ├── assets │ │ │ ├── fa-regular-400.ttf │ │ │ └── fa-solid-900.ttf │ │ └── implementation.zig │ ├── input.zig │ ├── input_types.zig │ ├── math │ │ ├── axis.zig │ │ ├── color.zig │ │ ├── edge.zig │ │ ├── mat32.zig │ │ ├── mat4.zig │ │ ├── math.zig │ │ ├── quad.zig │ │ ├── rand.zig │ │ ├── rect.zig │ │ ├── vec2.zig │ │ ├── vec3.zig │ │ └── vec4.zig │ ├── mem │ │ ├── allocator.zig │ │ ├── mem.zig │ │ ├── scratch_allocator.zig │ │ ├── sdl_allocator.zig │ │ └── sdl_stream.zig │ ├── physics │ │ ├── colliders.zig │ │ ├── collision_filter.zig │ │ ├── multi_hash_map.zig │ │ ├── physics.zig │ │ └── spatial_hash.zig │ ├── tilemap │ │ ├── collision.zig │ │ ├── collision_iterator.zig │ │ ├── renderer.zig │ │ └── tilemap.zig │ ├── time.zig │ ├── utils │ │ ├── array.zig │ │ ├── fixed_list.zig │ │ └── utils.zig │ └── window.zig └── tests.zig ├── build.zig ├── editor ├── asset_manager.zig ├── camera.zig ├── colors.zig ├── data │ ├── app_state.zig │ ├── brushset.zig │ ├── components.zig │ ├── data.zig │ ├── entity.zig │ ├── rules.zig │ ├── tilemap.zig │ ├── tileset.zig │ └── tileset_animations.zig ├── inspectors.zig ├── layers │ ├── auto_tilemap_layer.zig │ ├── entity_layer.zig │ ├── layer.zig │ ├── layers.zig │ └── tilemap_layer.zig ├── main.zig ├── menu.zig ├── persistence.zig ├── utils.zig ├── utils │ ├── file_picker.zig │ ├── image.zig │ ├── known-folders.zig │ └── texture_packer.zig └── windows │ ├── assets.zig │ ├── component_editor.zig │ ├── layers.zig │ ├── scene.zig │ ├── timeline.zig │ └── windows.zig └── examples ├── assets ├── ProggyTiny.ttf ├── ZigTiledExport.js ├── effects.zig ├── platformer.json ├── shaders │ ├── cube_fs.glsl │ ├── cube_vs.glsl │ ├── depth_fs.glsl │ ├── dissolve_fs.glsl │ ├── instanced_fs.glsl │ ├── instanced_vs.glsl │ ├── lines_fs.glsl │ ├── meta_flames_fs.glsl │ ├── mode7_fs.glsl │ ├── multi_batcher.gl.fs │ ├── multi_batcher.gl.vs │ ├── noise_fs.glsl │ ├── pixel_glitch_fs.glsl │ ├── rgb_shift_fs.glsl │ ├── sepia_fs.glsl │ ├── shader_src.glsl │ ├── shaders.zig │ ├── sprite_fs.glsl │ ├── sprite_vs.glsl │ └── vignette_fs.glsl ├── textures │ ├── SimpleTileset.png │ ├── blacknwhite.png │ ├── block.png │ ├── clouds.png │ ├── flames_scene.png │ ├── font.png │ ├── mario_kart.png │ ├── markov.png │ ├── minimal_tiles.png │ ├── sword_dude.png │ └── zelda_map.png └── tileset.png ├── atlas_batch.zig ├── batcher.zig ├── clipped_sprite.zig ├── dynamic_mesh.zig ├── empty.zig ├── entities.zig ├── flames.zig ├── fonts.zig ├── imgui.zig ├── instanced_mesh.zig ├── markov.zig ├── mesh.zig ├── mesh3d.zig ├── mode7.zig ├── multi_shaders.zig ├── offscreen.zig ├── primitives.zig └── tilemap.zig /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache 2 | imgui.ini 3 | .DS_Store 4 | zig-arm-cache 5 | zig-out 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "aya/deps/imgui/cimgui"] 2 | path = aya/deps/imgui/cimgui 3 | url = https://github.com/cimgui/cimgui.git 4 | [submodule "aya/deps/renderkit"] 5 | path = aya/deps/renderkit 6 | url = https://github.com/prime31/zig-renderkit.git 7 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb-mi", 9 | "request": "launch", 10 | "name": "LLDB", 11 | "target": "./test", 12 | "cwd": "${workspaceRoot}", 13 | "valuesFormatting": "parseText" 14 | }, 15 | { 16 | "type": "gdb", 17 | "request": "launch", 18 | "name": "Launch Program", 19 | "target": "./test", 20 | "cwd": "${workspaceRoot}", 21 | "valuesFormatting": "parseText" 22 | }, 23 | { 24 | "name": "Debug", 25 | "type": "gdb", 26 | "request": "launch", 27 | "target": "${workspaceFolder}/zig-cache/bin/run", 28 | "cwd": "${workspaceRoot}", 29 | "valuesFormatting": "parseText" 30 | }, 31 | { 32 | "name": "Debug Windows", 33 | "type": "cppvsdbg", 34 | "request": "launch", 35 | "program": "${workspaceFolder}/zig-cache/bin/run.exe", 36 | "args": [], 37 | "stopAtEntry": false, 38 | "cwd": "${workspaceFolder}/zig-cache/bin", 39 | "symbolSearchPath": "${workspaceFolder}/zig-cache/bin", 40 | "environment": [], 41 | "externalConsole": false, 42 | "logging": { 43 | "moduleLoad": false 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.eol": "\n" 3 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "options": { 6 | "env": { 7 | "ZIG_SYSTEM_LINKER_HACK": "1", 8 | "MTL_SHADER_VALIDATION": "1", 9 | "MTL_SHADER_VALIDATION_GLOBAL_MEMORY": "1", 10 | "MTL_SHADER_VALIDATION_TEXTURE_USAGE": "1", 11 | "MTL_DEBUG_LAYER": "1", 12 | "METAL_DEVICE_WRAPPER_TYPE": "1", 13 | } 14 | }, 15 | "tasks": [ 16 | { 17 | "label": "Build Project", 18 | "type": "shell", 19 | "command": "zig build", 20 | "problemMatcher": [ 21 | "$gcc" 22 | ], 23 | "group": "none" 24 | }, 25 | { 26 | "label": "Build and Run Project", 27 | "type": "shell", 28 | "command": "zig build run", 29 | "problemMatcher": [ 30 | "$gcc" 31 | ], 32 | "group": { 33 | "kind": "build", 34 | "isDefault": true 35 | }, 36 | "presentation": { 37 | "clear": true 38 | } 39 | }, 40 | { 41 | "label": "Build and Run Project (x64 on arm)", 42 | "type": "shell", 43 | "command": "~/zig/zig-x64/zig build run", 44 | "problemMatcher": [ 45 | "$gcc" 46 | ], 47 | "group": "build", 48 | "presentation": { 49 | "clear": true 50 | } 51 | }, 52 | { 53 | "label": "Build and Run Project (release-fast)", 54 | "type": "shell", 55 | "command": "zig build run -Doptimize=ReleaseFast", 56 | "problemMatcher": [ 57 | "$gcc" 58 | ], 59 | "group": { 60 | "kind": "build", 61 | "isDefault": true 62 | }, 63 | "presentation": { 64 | "clear": true 65 | } 66 | }, 67 | { 68 | "label": "Build and Run Project (release-small)", 69 | "type": "shell", 70 | "command": "zig build run -Doptimize=ReleaseSmall", 71 | "problemMatcher": [ 72 | "$gcc" 73 | ], 74 | "group": { 75 | "kind": "build", 76 | "isDefault": true 77 | }, 78 | "presentation": { 79 | "clear": true 80 | } 81 | }, 82 | { 83 | "label": "Test Project", 84 | "type": "shell", 85 | "command": "zig build test", 86 | "problemMatcher": [ 87 | "$gcc" 88 | ], 89 | "group": { 90 | "kind": "build", 91 | "isDefault": true 92 | }, 93 | "presentation": { 94 | "clear": true 95 | } 96 | }, 97 | { 98 | "label": "Build and Run Tests in Current File", 99 | "type": "shell", 100 | "command": "zig test ${file}", 101 | "problemMatcher": [ 102 | "$gcc" 103 | ], 104 | "presentation": { 105 | "clear": true 106 | }, 107 | "group": "none" 108 | }, 109 | { 110 | "label": "Compile Shaders", 111 | "type": "shell", 112 | "command": "zig build compile-shaders", 113 | "problemMatcher": [ 114 | "$gcc" 115 | ], 116 | "group": { 117 | "kind": "build", 118 | "isDefault": true 119 | }, 120 | "presentation": { 121 | "clear": true 122 | } 123 | } 124 | ] 125 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aya 2D Zig Framework 2 | Import aya via `const aya = @import("aya");` to gain access to the public interface. 3 | 4 | ### Project Structure Notes 5 | - **aya**: core aya code 6 | - **aya/deps**: C dependency packages that each include a `build.zig` file and either a submodule with the C code or direct copies of it. 7 | - **src/examples**: basic, mostly single-file examples 8 | - **src/deps**: zig packages (mostly C dependencies) required for some examples 9 | -------------------------------------------------------------------------------- /aya/aya.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub const WindowConfig = @import("src/window.zig").WindowConfig; 4 | 5 | // libs 6 | pub const renderkit = @import("renderkit"); 7 | pub const sdl = @import("sdl"); 8 | pub const imgui = @import("imgui"); 9 | const imgui_impl = @import("src/imgui/implementation.zig"); 10 | 11 | // aya namespaces 12 | pub const gfx = @import("src/gfx/gfx.zig"); 13 | pub const draw = gfx.draw; 14 | pub const fs = @import("src/fs.zig"); 15 | 16 | pub const math = @import("src/math/math.zig"); 17 | pub const mem = @import("src/mem/mem.zig"); 18 | pub const utils = @import("src/utils/utils.zig"); 19 | pub const tilemap = @import("src/tilemap/tilemap.zig"); 20 | 21 | // aya objects 22 | pub var window: Window = undefined; 23 | pub var time: Time = undefined; 24 | pub var input: Input = undefined; 25 | pub var debug: Debug = undefined; 26 | 27 | const Window = @import("src/window.zig").Window; 28 | const Time = @import("src/time.zig").Time; 29 | const Input = @import("src/input.zig").Input; 30 | const Debug = @import("src/debug.zig").Debug; 31 | 32 | // search path: root.build_options, root.enable_imgui, default to false 33 | pub const enable_imgui: bool = if (@hasDecl(@import("root"), "build_options")) blk: { 34 | break :blk @field(@import("root"), "build_options").enable_imgui; 35 | } else if (@hasDecl(@import("root"), "enable_imgui")) 36 | blk: { 37 | break :blk @field(@import("root"), "enable_imgui"); 38 | } else blk: { 39 | break :blk false; 40 | }; 41 | 42 | pub const Config = struct { 43 | init: fn () anyerror!void, 44 | update: ?fn () anyerror!void = null, 45 | render: fn () anyerror!void, 46 | shutdown: ?fn () anyerror!void = null, 47 | onFileDropped: ?fn ([:0]const u8) void = null, 48 | 49 | gfx: gfx.Config = gfx.Config{}, 50 | window: WindowConfig = WindowConfig{}, 51 | 52 | update_rate: f64 = 60, // desired fps 53 | imgui_icon_font: bool = true, 54 | imgui_viewports: bool = true, // whether imgui viewports should be enabled 55 | imgui_docking: bool = true, // whether imgui docking should be enabled 56 | }; 57 | 58 | pub fn run(comptime config: Config) !void { 59 | if (sdl.SDL_Init(sdl.SDL_INIT_VIDEO | sdl.SDL_INIT_HAPTIC | sdl.SDL_INIT_GAMECONTROLLER) != 0) { 60 | sdl.SDL_Log("Unable to initialize SDL: %s", sdl.SDL_GetError()); 61 | return error.SDLInitializationFailed; 62 | } 63 | 64 | mem.initTmpAllocator(); 65 | window = try Window.init(config.window); 66 | 67 | renderkit.setup(.{ .gl_loader = sdl.SDL_GL_GetProcAddress }, mem.allocator); 68 | 69 | gfx.init(config.gfx); 70 | time = Time.init(config.update_rate); 71 | input = Input.init(window.scale()); 72 | debug = try Debug.init(); 73 | defer debug.deinit(); 74 | 75 | if (enable_imgui) imgui_impl.init(window, config.imgui_docking, config.imgui_viewports, config.imgui_icon_font); 76 | 77 | try config.init(); 78 | 79 | while (!pollEvents(config.onFileDropped)) { 80 | time.tick(); 81 | gfx.beginFrame(); 82 | 83 | if (config.update) |update| try update(); 84 | try config.render(); 85 | 86 | if (enable_imgui) { 87 | gfx.blitToScreen(math.Color.black); 88 | imgui_impl.render(); 89 | _ = sdl.SDL_GL_MakeCurrent(window.sdl_window, window.gl_ctx); 90 | } 91 | 92 | sdl.SDL_GL_SwapWindow(window.sdl_window); 93 | gfx.commitFrame(); 94 | input.newFrame(); 95 | } 96 | 97 | if (config.shutdown) |shutdown| try shutdown(); 98 | 99 | if (enable_imgui) imgui_impl.deinit(); 100 | gfx.deinit(); 101 | renderkit.shutdown(); 102 | window.deinit(); 103 | sdl.SDL_Quit(); 104 | } 105 | 106 | fn pollEvents(comptime onFileDropped: ?fn ([:0]const u8) void) bool { 107 | var event: sdl.SDL_Event = undefined; 108 | while (sdl.SDL_PollEvent(&event) != 0) { 109 | if (enable_imgui and imgui_impl.handleEvent(&event)) continue; 110 | 111 | switch (event.type) { 112 | sdl.SDL_QUIT => return true, 113 | sdl.SDL_WINDOWEVENT => { 114 | if (event.window.windowID == window.id) { 115 | if (event.window.event == sdl.SDL_WINDOWEVENT_CLOSE) return true; 116 | window.handleEvent(&event.window); 117 | } 118 | }, 119 | sdl.SDL_DROPFILE => { 120 | if (onFileDropped) |fileDropped| fileDropped(std.mem.span(event.drop.file)); 121 | sdl.SDL_free(event.drop.file); 122 | }, 123 | else => input.handleEvent(&event), 124 | } 125 | } 126 | 127 | // if ImGui is running we force a timer resync every frame. This ensures we get exactly one update call and one render call 128 | // each frame which prevents ImGui from flickering due to skipped/doubled update calls. 129 | if (enable_imgui) { 130 | imgui_impl.newFrame(); 131 | time.resync(); 132 | } 133 | 134 | return false; 135 | } 136 | -------------------------------------------------------------------------------- /aya/deps/fontstash/build.zig: -------------------------------------------------------------------------------- 1 | const builtin = @import("builtin"); 2 | const std = @import("std"); 3 | const Builder = std.build.Builder; 4 | 5 | pub fn build(b: *Builder) void { 6 | const exe = b.addStaticLibrary("JunkLib", null); 7 | linkArtifact(b, exe, b.standardTargetOptions(.{})); 8 | exe.install(); 9 | } 10 | 11 | /// prefix_path is used to add package paths. It should be the the same path used to include this build file 12 | pub fn linkArtifact(b: *Builder, exe: *std.build.LibExeObjStep, target: std.zig.CrossTarget, comptime prefix_path: []const u8) void { 13 | _ = b; 14 | _ = target; 15 | if (prefix_path.len > 0 and !std.mem.endsWith(u8, prefix_path, "/")) @panic("prefix-path must end with '/' if it is not empty"); 16 | exe.linkLibC(); 17 | 18 | const lib_cflags = &[_][]const u8{"-O3"}; 19 | exe.addCSourceFile(std.Build.Step.Compile.CSourceFile{ 20 | .file = std.Build.LazyPath.relative(prefix_path ++ "aya/deps/fontstash/src/fontstash.c"), 21 | .flags = lib_cflags, 22 | }); 23 | } 24 | 25 | pub fn getModule(b: *std.Build, comptime prefix_path: []const u8) *std.build.Module { 26 | if (prefix_path.len > 0 and !std.mem.endsWith(u8, prefix_path, "/")) @panic("prefix-path must end with '/' if it is not empty"); 27 | return b.createModule(.{ 28 | .source_file = .{ .path = prefix_path ++ "aya/deps/fontstash/fontstash.zig" }, 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /aya/deps/fontstash/fontstash.zig: -------------------------------------------------------------------------------- 1 | pub const Context = extern struct { 2 | params: Params, 3 | itw: f32, 4 | ith: f32, 5 | tex_data: [*c]u8, 6 | // omitted rest of struct 7 | 8 | pub fn init(params: *Params) !*Context { 9 | return fonsCreateInternal(params) orelse error.FailedToCreateFONS; 10 | } 11 | 12 | pub fn deinit(self: *Context) void { 13 | fonsDeleteInternal(self); 14 | } 15 | }; 16 | 17 | pub const Params = extern struct { 18 | width: c_int = 256, 19 | height: c_int = 256, 20 | flags: Flags = .top_left, 21 | user_ptr: *anyopaque, 22 | renderCreate: ?*const fn (?*anyopaque, c_int, c_int) callconv(.C) c_int = null, 23 | renderResize: ?*const fn (?*anyopaque, c_int, c_int) callconv(.C) c_int = null, 24 | renderUpdate: ?*const fn (?*anyopaque, [*c]c_int, [*c]const u8) callconv(.C) c_int = null, 25 | }; 26 | 27 | pub const Flags = enum(u8) { 28 | top_left = 1, 29 | bottom_left = 2, 30 | }; 31 | 32 | pub const Align = enum(c_int) { 33 | // horizontal 34 | left = 1, // Default 35 | center = 2, 36 | right = 4, 37 | // vertical 38 | top = 8, 39 | middle = 16, 40 | bottom = 32, 41 | baseline = 64, 42 | default = 65, 43 | // combos 44 | left_middle = 17, 45 | center_middle = 18, 46 | right_middle = 20, 47 | top_left = 9, 48 | }; 49 | 50 | pub const ErrorCode = enum(c_int) { 51 | atlas_full = 1, 52 | scratch_full = 2, // Scratch memory used to render glyphs is full, requested size reported in 'val', you may need to bump up FONS_SCRATCH_BUF_SIZE. 53 | overflow = 3, // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES. 54 | underflow = 4, // Trying to pop too many states fonsPopState(). 55 | }; 56 | 57 | pub const Quad = extern struct { 58 | x0: f32, 59 | y0: f32, 60 | s0: f32, 61 | t0: f32, 62 | x1: f32, 63 | y1: f32, 64 | s1: f32, 65 | t1: f32, 66 | }; 67 | 68 | pub const FONSfont = opaque {}; 69 | 70 | pub const TextIter = extern struct { 71 | x: f32, 72 | y: f32, 73 | nextx: f32, 74 | nexty: f32, 75 | scale: f32, 76 | spacing: f32, 77 | color: c_uint, 78 | codepoint: c_uint, 79 | isize: c_short, 80 | iblur: c_short, 81 | font: ?*FONSfont, 82 | prevGlyphIndex: c_int, 83 | str: [*c]const u8, 84 | next: [*c]const u8, 85 | end: [*c]const u8, 86 | utf8state: c_uint, 87 | }; 88 | 89 | pub const FONS_INVALID = -1; 90 | 91 | pub extern fn fonsCreateInternal(params: [*c]Params) ?*Context; 92 | pub extern fn fonsDeleteInternal(s: ?*Context) void; 93 | pub extern fn fonsSetErrorCallback(s: ?*Context, callback: ?fn (?*anyopaque, c_int, c_int) callconv(.C) void, uptr: ?*anyopaque) void; 94 | pub extern fn fonsGetAtlasSize(s: ?*Context, width: [*c]c_int, height: [*c]c_int) void; 95 | pub extern fn fonsExpandAtlas(s: ?*Context, width: c_int, height: c_int) c_int; 96 | pub extern fn fonsResetAtlas(stash: ?*Context, width: c_int, height: c_int) c_int; 97 | pub extern fn fonsAddFontMem(stash: ?*Context, name: [*c]const u8, data: [*c]const u8, dataSize: c_int, freeData: c_int) c_int; 98 | pub extern fn fonsGetFontByName(s: ?*Context, name: [*c]const u8) c_int; 99 | pub extern fn fonsAddFallbackFont(stash: ?*Context, base: c_int, fallback: c_int) c_int; 100 | pub extern fn fonsPushState(s: ?*Context) void; 101 | pub extern fn fonsPopState(s: ?*Context) void; 102 | pub extern fn fonsClearState(s: ?*Context) void; 103 | pub extern fn fonsSetSize(s: ?*Context, size: f32) void; 104 | pub extern fn fonsSetColor(s: ?*Context, color: c_uint) void; 105 | pub extern fn fonsSetSpacing(s: ?*Context, spacing: f32) void; 106 | pub extern fn fonsSetBlur(s: ?*Context, blur: f32) void; 107 | pub extern fn fonsSetAlign(s: ?*Context, alignment: Align) void; 108 | pub extern fn fonsSetFont(s: ?*Context, font: c_int) void; 109 | pub extern fn fonsTextBounds(s: ?*Context, x: f32, y: f32, string: [*c]const u8, end: [*c]const u8, bounds: [*c]f32) f32; 110 | pub extern fn fonsLineBounds(s: ?*Context, y: f32, miny: [*c]f32, maxy: [*c]f32) void; 111 | pub extern fn fonsVertMetrics(s: ?*Context, ascender: [*c]f32, descender: [*c]f32, lineh: [*c]f32) void; 112 | pub extern fn fonsTextIterInit(stash: ?*Context, iter: [*c]TextIter, x: f32, y: f32, str: [*c]const u8, len: c_int) c_int; 113 | pub extern fn fonsTextIterNext(stash: ?*Context, iter: [*c]TextIter, quad: [*c]Quad) c_int; 114 | pub extern fn fonsGetTextureData(stash: ?*Context, width: [*c]c_int, height: [*c]c_int) [*c]const u8; 115 | pub extern fn fonsValidateTexture(s: ?*Context, dirty: [*c]c_int) c_int; 116 | -------------------------------------------------------------------------------- /aya/deps/fontstash/src/fontstash.c: -------------------------------------------------------------------------------- 1 | #define FONTSTASH_IMPLEMENTATION 2 | #include "fontstash.h" 3 | -------------------------------------------------------------------------------- /aya/deps/imgui/Makefile: -------------------------------------------------------------------------------- 1 | # You will need SDL2 (http://www.libsdl.org): 2 | # Linux: 3 | # apt-get install libsdl2-dev 4 | # Mac OS X: 5 | # brew install sdl2 6 | # MSYS2: 7 | # pacman -S mingw-w64-i686-SDL2 8 | 9 | TARGET := 10 | 11 | CP := cp 12 | CIMGUI := cimgui 13 | IMGUI := $(CIMGUI)/imgui 14 | $(shell mkdir -p build) 15 | 16 | CFLAGS := -I. 17 | CFLAGS += -DCIMGUI_DEFINE_ENUMS_AND_STRUCTS=1 18 | CFLAGS += -DIMGUI_DISABLE_OBSOLETE_FUNCTIONS=1 19 | CFLAGS += -DIMGUI_IMPL_OPENGL_LOADER_GL3W 20 | CFLAGS += -DIMGUI_IMPL_API=extern\ \"C\" 21 | CFLAGS += `sdl2-config --cflags` 22 | 23 | ## Using OpenGL loader: gl3w [default] 24 | GL3W := $(CIMGUI)/imgui/examples/libs/gl3w 25 | CFLAGS += -I$(GL3W) 26 | 27 | TARGET += gl3w.o 28 | TARGET += imgui_impl_sdl.o 29 | TARGET += imgui_impl_opengl3.o 30 | 31 | ##--------------------------------------------------------------------- 32 | ## BUILD FLAGS PER PLATFORM 33 | ##--------------------------------------------------------------------- 34 | 35 | UNAME_S := $(shell uname -s) 36 | LIB_FOLDER := 37 | 38 | ifeq ($(UNAME_S), Linux) #LINUX 39 | CFLAGS += `sdl2-config --cflags` 40 | 41 | SED := sed -i 42 | TARGET += cimgui.so # not yet supported 43 | else ifeq ($(UNAME_S), Darwin) #APPLE 44 | CFLAGS += `sdl2-config --cflags` 45 | CFLAGS += -I/usr/local/include 46 | CFLAGS += -Icimgui/imgui/examples/libs/gl3w 47 | 48 | SED := sed -i '' 49 | endif 50 | 51 | ##--------------------------------------------------------------------- 52 | ## BUILD RULES 53 | ##--------------------------------------------------------------------- 54 | 55 | all: $(TARGET) 56 | @echo build complete 57 | 58 | gl3w.o: $(IMGUI)/examples/libs/gl3w/GL/gl3w.c 59 | $(CC) -fPIC -c -o build/$@ $^ -I$(GL3W) -fno-threadsafe-statics 60 | 61 | imgui_impl_sdl.o: $(IMGUI)/examples/imgui_impl_sdl.cpp 62 | $(CXX) -fPIC -c -o build/$@ $^ -I$(IMGUI) `sdl2-config --cflags` -DIMGUI_IMPL_API=extern\ \"C\" -fno-threadsafe-statics 63 | 64 | imgui_impl_opengl3.o: $(IMGUI)/examples/imgui_impl_opengl3.cpp 65 | $(CXX) -fPIC -c -o build/$@ $^ -I$(IMGUI) -I$(GL3W) $(CFLAGS) -DIMGUI_IMPL_API=extern\ \"C\" -fno-threadsafe-statics 66 | 67 | clean: 68 | $(RM) *.o *.so *.h $(TARGET) 69 | 70 | clobber: clean 71 | $(RM) -Rf build 72 | -------------------------------------------------------------------------------- /aya/deps/imgui/build.zig: -------------------------------------------------------------------------------- 1 | const builtin = @import("builtin"); 2 | const std = @import("std"); 3 | const Builder = std.build.Builder; 4 | 5 | const build_impl_type: enum { exe, static_lib, object_files } = .static_lib; 6 | var framework_dir: ?[]u8 = null; 7 | 8 | pub fn build(b: *std.build.Builder) anyerror!void { 9 | const exe = b.addStaticLibrary("JunkLib", null); 10 | linkArtifact(b, exe, b.standardTargetOptions(.{}), .static, ""); 11 | exe.install(); 12 | } 13 | 14 | /// prefix_path is used to add package paths. It should be the the same path used to include this build file 15 | pub fn linkArtifact(b: *Builder, exe: *std.build.LibExeObjStep, target: std.zig.CrossTarget, comptime prefix_path: []const u8) void { 16 | if (prefix_path.len > 0 and !std.mem.endsWith(u8, prefix_path, "/")) @panic("prefix-path must end with '/' if it is not empty"); 17 | exe.addModule("imgui_gl", getImGuiModule(b, prefix_path)); 18 | 19 | if (target.isWindows()) { 20 | exe.linkSystemLibrary("user32"); 21 | exe.linkSystemLibrary("gdi32"); 22 | } else if (target.isDarwin()) { 23 | macosAddSdkDirs(b, exe) catch unreachable; 24 | exe.linkFramework("Foundation"); 25 | exe.linkFramework("Cocoa"); 26 | exe.linkFramework("Quartz"); 27 | exe.linkFramework("QuartzCore"); 28 | exe.linkFramework("OpenGL"); 29 | exe.linkFramework("Audiotoolbox"); 30 | exe.linkFramework("CoreAudio"); 31 | exe.linkSystemLibrary("c++"); 32 | } else { 33 | exe.linkLibC(); 34 | exe.linkSystemLibrary("c++"); 35 | } 36 | 37 | const base_path = prefix_path ++ "aya/deps/imgui/"; 38 | exe.addIncludePath(std.Build.LazyPath.relative(base_path ++ "cimgui/imgui")); 39 | exe.addIncludePath(std.Build.LazyPath.relative(base_path ++ "cimgui/imgui/examples")); 40 | 41 | const cpp_args = [_][]const u8{"-Wno-return-type-c-linkage"}; 42 | exe.addCSourceFiles(.{ .files = &[_][]const u8{ 43 | base_path ++ "cimgui/imgui/imgui.cpp", 44 | base_path ++ "cimgui/imgui/imgui_demo.cpp", 45 | base_path ++ "cimgui/imgui/imgui_draw.cpp", 46 | base_path ++ "cimgui/imgui/imgui_widgets.cpp", 47 | base_path ++ "cimgui/cimgui.cpp", 48 | base_path ++ "temporary_hacks.cpp", 49 | }, .flags = &cpp_args }); 50 | 51 | addImGuiGlImplementation(b, exe, target, prefix_path); 52 | } 53 | 54 | fn addImGuiGlImplementation(_: *Builder, exe: *std.build.LibExeObjStep, _: std.zig.CrossTarget, comptime prefix_path: []const u8) void { 55 | const base_path = prefix_path ++ "aya/deps/imgui/"; 56 | const cpp_args = [_][]const u8{ "-Wno-return-type-c-linkage", "-DIMGUI_IMPL_API=extern \"C\"", "-DIMGUI_IMPL_OPENGL_LOADER_GL3W" }; 57 | 58 | // what we actually want to work but for some reason on macos it doesnt 59 | exe.linkSystemLibrary("SDL2"); 60 | exe.addIncludePath(std.Build.LazyPath.relative(base_path ++ "cimgui/imgui/examples/libs/gl3w")); 61 | exe.addIncludePath(std.Build.LazyPath{ .cwd_relative = "/usr/local/include/SDL2" }); 62 | exe.addIncludePath(std.Build.LazyPath{ .cwd_relative = "/opt/homebrew/include/SDL2" }); 63 | 64 | exe.addCSourceFiles(.{ .files = &[_][]const u8{ 65 | base_path ++ "cimgui/imgui/examples/libs/gl3w/GL/gl3w.c", 66 | base_path ++ "cimgui/imgui/examples/imgui_impl_opengl3.cpp", 67 | base_path ++ "cimgui/imgui/examples/imgui_impl_sdl.cpp", 68 | }, .flags = &cpp_args }); 69 | } 70 | 71 | /// helper function to get SDK path on Mac 72 | fn macosFrameworksDir(b: *Builder) ![]u8 { 73 | if (framework_dir) |dir| return dir; 74 | 75 | var str = b.exec(&[_][]const u8{ "xcrun", "--show-sdk-path" }); 76 | const strip_newline = std.mem.lastIndexOf(u8, str, "\n"); 77 | if (strip_newline) |index| { 78 | str = str[0..index]; 79 | } 80 | framework_dir = try std.mem.concat(b.allocator, u8, &[_][]const u8{ str, "/System/Library/Frameworks" }); 81 | return framework_dir.?; 82 | } 83 | 84 | fn macosAddSdkDirs(b: *Builder, step: *std.build.LibExeObjStep) !void { 85 | var sdk_dir = b.exec(&[_][]const u8{ "xcrun", "--show-sdk-path" }); 86 | const newline_index = std.mem.lastIndexOf(u8, sdk_dir, "\n"); 87 | if (newline_index) |idx| { 88 | sdk_dir = sdk_dir[0..idx]; 89 | } 90 | framework_dir = try std.mem.concat(b.allocator, u8, &[_][]const u8{ sdk_dir, "/System/Library/Frameworks" }); 91 | const usrinclude_dir = try std.mem.concat(b.allocator, u8, &[_][]const u8{ sdk_dir, "/usr/include" }); 92 | step.addFrameworkPath(std.Build.LazyPath{ .cwd_relative = framework_dir.? }); 93 | step.addFrameworkPath(std.Build.LazyPath{ .cwd_relative = usrinclude_dir }); 94 | } 95 | 96 | pub fn getImGuiModule(b: *std.Build, comptime prefix_path: []const u8) *std.build.Module { 97 | if (prefix_path.len > 0 and !std.mem.endsWith(u8, prefix_path, "/")) @panic("prefix-path must end with '/' if it is not empty"); 98 | return b.createModule(.{ 99 | .source_file = .{ .path = prefix_path ++ "aya/deps/imgui/imgui.zig" }, 100 | }); 101 | } 102 | 103 | pub fn getImGuiGlModule(b: *std.Build, comptime prefix_path: []const u8) *std.build.Module { 104 | if (prefix_path.len > 0 and !std.mem.endsWith(u8, prefix_path, "/")) @panic("prefix-path must end with '/' if it is not empty"); 105 | return b.createModule(.{ 106 | .source_file = .{ .path = prefix_path ++ "aya/deps/imgui/imgui_gl.zig" }, 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /aya/deps/imgui/imgui_gl.zig: -------------------------------------------------------------------------------- 1 | const imgui = @import("imgui"); 2 | 3 | pub extern fn gl3wInit() void; 4 | 5 | // ImGui SDL2 and OpenGL3 implementation 6 | pub extern fn ImGui_ImplSDL2_InitForOpenGL(window: ?*anyopaque, sdl_gl_context: ?*anyopaque) bool; 7 | pub extern fn ImGui_ImplSDL2_ProcessEvent(event: ?*anyopaque) bool; 8 | pub extern fn ImGui_ImplSDL2_NewFrame(window: ?*anyopaque) void; 9 | pub extern fn ImGui_ImplSDL2_Shutdown() void; 10 | 11 | pub extern fn ImGui_ImplOpenGL3_Init(glsl_version: [*c]const u8) bool; 12 | pub extern fn ImGui_ImplOpenGL3_NewFrame() void; 13 | pub extern fn ImGui_ImplOpenGL3_RenderDrawData(draw_data: ?*anyopaque) void; 14 | pub extern fn ImGui_ImplOpenGL3_Shutdown() void; 15 | 16 | // ImGui lifecycle helpers, wrapping ImGui, SDL2 Impl and GL Impl methods 17 | // BEFORE calling init_for_gl a gl loader lib must be called! You must use the same one 18 | // used in the makefile when imgui was compiled! 19 | pub fn initForGl(glsl_version: [*c]const u8, window: ?*anyopaque, gl_context: ?*anyopaque) void { 20 | gl3wInit(); 21 | _ = ImGui_ImplSDL2_InitForOpenGL(window, gl_context); 22 | _ = ImGui_ImplOpenGL3_Init(glsl_version); 23 | } 24 | 25 | pub fn newFrame(window: ?*anyopaque) void { 26 | ImGui_ImplOpenGL3_NewFrame(); 27 | ImGui_ImplSDL2_NewFrame(window); 28 | imgui.igNewFrame(); 29 | } 30 | 31 | pub fn render() void { 32 | imgui.igRender(); 33 | 34 | var io = imgui.igGetIO(); 35 | ImGui_ImplOpenGL3_RenderDrawData(imgui.igGetDrawData()); 36 | 37 | if ((io.ConfigFlags & imgui.ImGuiConfigFlags_ViewportsEnable) != 0) { 38 | imgui.igUpdatePlatformWindows(); 39 | imgui.igRenderPlatformWindowsDefault(null, null); 40 | } 41 | } 42 | 43 | pub fn shutdown() void { 44 | ImGui_ImplOpenGL3_Shutdown(); 45 | ImGui_ImplSDL2_Shutdown(); 46 | imgui.igDestroyContext(null); 47 | } 48 | -------------------------------------------------------------------------------- /aya/deps/imgui/temporary_hacks.cpp: -------------------------------------------------------------------------------- 1 | #include "cimgui/imgui/imgui.h" 2 | #include "cimgui/imgui/imgui_internal.h" 3 | #include "cimgui/cimgui.h" 4 | 5 | CIMGUI_API void _ogImDrawData_ScaleClipRects(ImDrawData* self, const float fb_scale) { 6 | ImVec2 fb_scale_vec; 7 | fb_scale_vec.x = fb_scale; 8 | fb_scale_vec.y = fb_scale; 9 | self->ScaleClipRects(fb_scale_vec); 10 | } 11 | 12 | // workaround for Windows not functioning with ImVec4s 13 | CIMGUI_API void _ogImage(ImTextureID user_texture_id, const ImVec2* size, const ImVec2* uv0, const ImVec2* uv1) { 14 | ImVec4 tint_col; 15 | tint_col.x = tint_col.y = tint_col.z = tint_col.w = 1; 16 | ImVec4 border_col; 17 | return ImGui::Image(user_texture_id, *size, *uv0, *uv1, tint_col, border_col); 18 | } 19 | 20 | CIMGUI_API bool _ogImageButton(ImTextureID user_texture_id, const ImVec2* size, const ImVec2* uv0, const ImVec2* uv1, int frame_padding) { 21 | ImVec4 tint_col; 22 | tint_col.x = tint_col.y = tint_col.z = tint_col.w = 1; 23 | ImVec4 border_col; 24 | return ImGui::ImageButton(user_texture_id, *size, *uv0, *uv1, frame_padding, border_col, tint_col); 25 | } 26 | 27 | CIMGUI_API bool _ogImageButtonEx(ImTextureID user_texture_id, const ImVec2* size, const ImVec2* uv0, const ImVec2* uv1, int frame_padding, const ImVec4* bg_col, const ImVec4* tint_col) { 28 | return ImGui::ImageButton(user_texture_id, *size, *uv0, *uv1, frame_padding, *bg_col, *tint_col); 29 | } 30 | 31 | 32 | CIMGUI_API void _ogColoredText(float r, float g, float b, const char* text) { 33 | ImVec4 tint_col; 34 | tint_col.x = r; 35 | tint_col.y = g; 36 | tint_col.z = b; 37 | tint_col.w = 1; 38 | ImGui::TextColored(tint_col, text); 39 | } 40 | 41 | // M1 needs this for some reason... 42 | CIMGUI_API bool _ogButton(const char* label, const float x, const float y) { 43 | ImVec2 size; 44 | size.x = x; 45 | size.y = y; 46 | return ImGui::Button(label, size); 47 | } 48 | 49 | CIMGUI_API void _ogDockBuilderSetNodeSize(ImGuiID node_id, const ImVec2* size) { 50 | return ImGui::DockBuilderSetNodeSize(node_id, *size); 51 | } 52 | 53 | CIMGUI_API void _ogSetNextWindowPos(const ImVec2* pos, ImGuiCond cond, const ImVec2* pivot) { 54 | return ImGui::SetNextWindowPos(*pos, cond, *pivot); 55 | } 56 | 57 | CIMGUI_API void _ogSetNextWindowSize(const ImVec2* size, ImGuiCond cond) { 58 | return ImGui::SetNextWindowSize(*size, cond); 59 | } 60 | 61 | CIMGUI_API void _ogPushStyleVarVec2(ImGuiStyleVar idx, const float x, const float y) { 62 | ImVec2 pos; 63 | pos.x = x; 64 | pos.y = y; 65 | return ImGui::PushStyleVar(idx, pos); 66 | } 67 | 68 | CIMGUI_API bool _ogInvisibleButton(const char* str_id, const float w, const float h, ImGuiButtonFlags flags) { 69 | ImVec2 size; 70 | size.x = w; 71 | size.y = h; 72 | return ImGui::InvisibleButton(str_id, size, flags); 73 | } 74 | 75 | CIMGUI_API bool _ogSelectableBool(const char* label, bool selected, ImGuiSelectableFlags flags, const float w, const float h) { 76 | ImVec2 size; 77 | size.x = w; 78 | size.y = h; 79 | return ImGui::Selectable(label, selected, flags, size); 80 | } 81 | 82 | CIMGUI_API void _ogDummy(const float w, const float h) { 83 | ImVec2 size; 84 | size.x = w; 85 | size.y = h; 86 | return ImGui::Dummy(size); 87 | } 88 | 89 | CIMGUI_API bool _ogBeginChildFrame(ImGuiID id, const float w, const float h, ImGuiWindowFlags flags) { 90 | ImVec2 size; 91 | size.x = w; 92 | size.y = h; 93 | return ImGui::BeginChildFrame(id, size, flags); 94 | } 95 | 96 | CIMGUI_API bool _ogBeginChildEx(const char* name, ImGuiID id, const ImVec2* size_arg, bool border, ImGuiWindowFlags flags) { 97 | return ImGui::BeginChildEx(name, id, *size_arg, border, flags); 98 | } 99 | 100 | CIMGUI_API void _ogDockSpace(ImGuiID id, const float w, const float h, ImGuiDockNodeFlags flags, const ImGuiWindowClass* window_class) { 101 | ImVec2 size; 102 | size.x = w; 103 | size.y = h; 104 | ImGui::DockSpace(id, size, flags, window_class); 105 | } 106 | 107 | CIMGUI_API void _ogImDrawList_AddQuad(ImDrawList* self, const ImVec2* p1, const ImVec2* p2, const ImVec2* p3, const ImVec2* p4, ImU32 col, float thickness) { 108 | self->AddQuad(*p1, *p2, *p3, *p4, col, thickness); 109 | } 110 | 111 | CIMGUI_API void _ogImDrawList_AddQuadFilled(ImDrawList* self, const ImVec2* p1, const ImVec2* p2, const ImVec2* p3, const ImVec2* p4, ImU32 col) { 112 | self->AddQuadFilled(*p1, *p2, *p3, *p4, col); 113 | } 114 | 115 | CIMGUI_API void _ogImDrawList_AddTriangleFilled(ImDrawList* self, const ImVec2* tl, const ImVec2* bl, const ImVec2* br, ImU32 col) { 116 | self->AddTriangleFilled(*tl, *bl, *br, col); 117 | } 118 | 119 | CIMGUI_API void _ogImDrawList_AddLine(ImDrawList* self, const ImVec2* p1, const ImVec2* p2, ImU32 col, float thickness) { 120 | self->AddLine(*p1, *p2, col, thickness); 121 | } 122 | 123 | CIMGUI_API void _ogImDrawList_AddImage(ImDrawList* self, ImTextureID texture_id, const ImVec2* p_min, const ImVec2* p_max, const ImVec2* uv_min, const ImVec2* uv_max, ImU32 col) { 124 | self->AddImage(texture_id, *p_min, *p_max, *uv_min, *uv_max, col); 125 | } 126 | 127 | CIMGUI_API void _ogSetCursorScreenPos(const ImVec2* pos) { 128 | ImGui::SetCursorScreenPos(*pos); 129 | } 130 | 131 | CIMGUI_API bool _ogListBoxHeaderVec2(const char* label, const ImVec2* size) { 132 | return ImGui::ListBoxHeader(label, *size); 133 | } 134 | 135 | CIMGUI_API bool _ogBeginChildID(ImGuiID id, const ImVec2* size, bool border, ImGuiWindowFlags flags) { 136 | return ImGui::BeginChild(id, *size, border, flags); 137 | } 138 | -------------------------------------------------------------------------------- /aya/deps/sdl/build.zig: -------------------------------------------------------------------------------- 1 | const builtin = @import("builtin"); 2 | const std = @import("std"); 3 | const Builder = std.build.Builder; 4 | 5 | pub fn build(_: *Builder) void {} 6 | 7 | pub fn linkArtifact(b: *Builder, exe: *std.build.LibExeObjStep, _: std.zig.CrossTarget, comptime prefix_path: []const u8) void { 8 | if (prefix_path.len > 0 and !std.mem.endsWith(u8, prefix_path, "/")) @panic("prefix-path must end with '/' if it is not empty"); 9 | exe.linkSystemLibrary("c"); 10 | exe.linkSystemLibrary("c++"); 11 | exe.linkSystemLibrary("sdl2"); 12 | exe.addLibraryPath(.{ .cwd_relative = "/opt/homebrew/lib" }); 13 | 14 | if (@import("builtin").os.tag == .windows) { 15 | // Windows include dirs for SDL2. This requires downloading SDL2 dev and extracting to c:\SDL2 16 | exe.addLibraryPath(.{ .cwd_relative = "c:\\SDL2\\lib\\x64" }); 17 | exe.addIncludePath(.{ .cwd_relative = "C:\\SDL2\\include" }); 18 | 19 | // SDL2.dll needs to be copied to the zig-cache/bin folder 20 | // TODO: installFile doesnt seeem to work so manually copy the file over 21 | b.installFile("c:\\SDL2\\lib\\x64\\SDL2.dll", "bin\\SDL2.dll"); 22 | 23 | std.fs.cwd().makePath("zig-out\\bin") catch unreachable; 24 | const src_dir = std.fs.cwd().openDir("c:\\SDL2\\lib\\x64", .{}) catch unreachable; 25 | src_dir.copyFile("SDL2.dll", std.fs.cwd(), "zig-out\\bin\\SDL2.dll", .{}) catch unreachable; 26 | } 27 | 28 | exe.addModule("sdl", getModule(b, prefix_path)); 29 | } 30 | 31 | pub fn getModule(b: *std.Build, comptime prefix_path: []const u8) *std.build.Module { 32 | if (prefix_path.len > 0 and !std.mem.endsWith(u8, prefix_path, "/")) @panic("prefix-path must end with '/' if it is not empty"); 33 | return b.createModule(.{ 34 | .source_file = .{ .path = prefix_path ++ "aya/deps/sdl/sdl.zig" }, 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /aya/deps/stb/build.zig: -------------------------------------------------------------------------------- 1 | const builtin = @import("builtin"); 2 | const std = @import("std"); 3 | const Builder = std.build.Builder; 4 | 5 | pub fn build(b: *Builder) void { 6 | const exe = b.addStaticLibrary("JunkLib", null); 7 | linkArtifact(b, exe, b.standardTargetOptions(.{}), .static, ""); 8 | exe.install(); 9 | } 10 | 11 | pub fn linkArtifact(b: *Builder, exe: *std.build.LibExeObjStep, target: std.zig.CrossTarget, comptime prefix_path: []const u8) void { 12 | _ = b; 13 | _ = target; 14 | exe.linkLibC(); 15 | exe.addIncludePath(std.Build.LazyPath.relative(prefix_path ++ "aya/deps/stb/src")); 16 | 17 | const lib_cflags = &[_][]const u8{"-std=c99"}; 18 | exe.addCSourceFile(std.Build.Step.Compile.CSourceFile{ 19 | .file = std.Build.LazyPath.relative(prefix_path ++ "aya/deps/stb/src/stb_impl.c"), 20 | .flags = lib_cflags, 21 | }); 22 | } 23 | 24 | pub fn getModule(b: *std.Build, comptime prefix_path: []const u8) *std.build.Module { 25 | if (prefix_path.len > 0 and !std.mem.endsWith(u8, prefix_path, "/")) @panic("prefix-path must end with '/' if it is not empty"); 26 | return b.createModule(.{ 27 | .source_file = .{ .path = prefix_path ++ "aya/deps/stb/stb.zig" }, 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /aya/deps/stb/src/stb_impl.c: -------------------------------------------------------------------------------- 1 | #define STB_IMAGE_IMPLEMENTATION 2 | #define STBI_NO_STDIO 3 | #include "stb_image.h" 4 | 5 | #define STB_IMAGE_WRITE_IMPLEMENTATION 6 | #include "stb_image_write.h" 7 | 8 | #define STB_RECT_PACK_IMPLEMENTATION 9 | #include "stb_rect_pack.h" -------------------------------------------------------------------------------- /aya/deps/stb/stb.zig: -------------------------------------------------------------------------------- 1 | pub usingnamespace @import("stb_image.zig"); 2 | pub usingnamespace @import("stb_image_write.zig"); 3 | pub usingnamespace @import("stb_rect_pack.zig"); 4 | -------------------------------------------------------------------------------- /aya/deps/stb/stb_image.zig: -------------------------------------------------------------------------------- 1 | pub const STBI_default = @intFromEnum(enum_unnamed_1.STBI_default); 2 | pub const STBI_grey = @intFromEnum(enum_unnamed_1.STBI_grey); 3 | pub const STBI_grey_alpha = @intFromEnum(enum_unnamed_1.STBI_grey_alpha); 4 | pub const STBI_rgb = @intFromEnum(enum_unnamed_1.STBI_rgb); 5 | pub const STBI_rgb_alpha = @intFromEnum(enum_unnamed_1.STBI_rgb_alpha); 6 | const enum_unnamed_1 = enum(c_int) { 7 | STBI_default = 0, 8 | STBI_grey = 1, 9 | STBI_grey_alpha = 2, 10 | STBI_rgb = 3, 11 | STBI_rgb_alpha = 4, 12 | _, 13 | }; 14 | 15 | pub const stbi_uc = u8; 16 | pub const stbi_us = c_ushort; 17 | const struct_unnamed_9 = extern struct { 18 | read: ?fn (?*anyopaque, [*c]u8, c_int) callconv(.C) c_int, 19 | skip: ?fn (?*anyopaque, c_int) callconv(.C) void, 20 | eof: ?fn (?*anyopaque) callconv(.C) c_int, 21 | }; 22 | pub const stbi_io_callbacks = struct_unnamed_9; 23 | pub extern fn stbi_load_from_memory(buffer: [*c]const stbi_uc, len: c_int, x: [*c]c_int, y: [*c]c_int, channels_in_file: [*c]c_int, desired_channels: c_int) [*c]stbi_uc; 24 | pub extern fn stbi_load_from_callbacks(clbk: [*c]const stbi_io_callbacks, user: ?*anyopaque, x: [*c]c_int, y: [*c]c_int, channels_in_file: [*c]c_int, desired_channels: c_int) [*c]stbi_uc; 25 | pub extern fn stbi_load_gif_from_memory(buffer: [*c]const stbi_uc, len: c_int, delays: [*c][*c]c_int, x: [*c]c_int, y: [*c]c_int, z: [*c]c_int, comp: [*c]c_int, req_comp: c_int) [*c]stbi_uc; 26 | pub extern fn stbi_load_16_from_memory(buffer: [*c]const stbi_uc, len: c_int, x: [*c]c_int, y: [*c]c_int, channels_in_file: [*c]c_int, desired_channels: c_int) [*c]stbi_us; 27 | pub extern fn stbi_load_16_from_callbacks(clbk: [*c]const stbi_io_callbacks, user: ?*anyopaque, x: [*c]c_int, y: [*c]c_int, channels_in_file: [*c]c_int, desired_channels: c_int) [*c]stbi_us; 28 | pub extern fn stbi_loadf_from_memory(buffer: [*c]const stbi_uc, len: c_int, x: [*c]c_int, y: [*c]c_int, channels_in_file: [*c]c_int, desired_channels: c_int) [*c]f32; 29 | pub extern fn stbi_loadf_from_callbacks(clbk: [*c]const stbi_io_callbacks, user: ?*anyopaque, x: [*c]c_int, y: [*c]c_int, channels_in_file: [*c]c_int, desired_channels: c_int) [*c]f32; 30 | pub extern fn stbi_hdr_to_ldr_gamma(gamma: f32) void; 31 | pub extern fn stbi_hdr_to_ldr_scale(scale: f32) void; 32 | pub extern fn stbi_ldr_to_hdr_gamma(gamma: f32) void; 33 | pub extern fn stbi_ldr_to_hdr_scale(scale: f32) void; 34 | pub extern fn stbi_is_hdr_from_callbacks(clbk: [*c]const stbi_io_callbacks, user: ?*anyopaque) c_int; 35 | pub extern fn stbi_is_hdr_from_memory(buffer: [*c]const stbi_uc, len: c_int) c_int; 36 | pub extern fn stbi_failure_reason() [*c]const u8; 37 | pub extern fn stbi_image_free(retval_from_stbi_load: ?*anyopaque) void; 38 | pub extern fn stbi_info_from_memory(buffer: [*c]const stbi_uc, len: c_int, x: [*c]c_int, y: [*c]c_int, comp: [*c]c_int) c_int; 39 | pub extern fn stbi_info_from_callbacks(clbk: [*c]const stbi_io_callbacks, user: ?*anyopaque, x: [*c]c_int, y: [*c]c_int, comp: [*c]c_int) c_int; 40 | pub extern fn stbi_is_16_bit_from_memory(buffer: [*c]const stbi_uc, len: c_int) c_int; 41 | pub extern fn stbi_is_16_bit_from_callbacks(clbk: [*c]const stbi_io_callbacks, user: ?*anyopaque) c_int; 42 | pub extern fn stbi_set_unpremultiply_on_load(flag_true_if_should_unpremultiply: c_int) void; 43 | pub extern fn stbi_convert_iphone_png_to_rgb(flag_true_if_should_convert: c_int) void; 44 | pub extern fn stbi_set_flip_vertically_on_load(flag_true_if_should_flip: c_int) void; 45 | pub extern fn stbi_set_flip_vertically_on_load_thread(flag_true_if_should_flip: c_int) void; 46 | pub extern fn stbi_zlib_decode_malloc_guesssize(buffer: [*c]const u8, len: c_int, initial_size: c_int, outlen: [*c]c_int) [*c]u8; 47 | pub extern fn stbi_zlib_decode_malloc_guesssize_headerflag(buffer: [*c]const u8, len: c_int, initial_size: c_int, outlen: [*c]c_int, parse_header: c_int) [*c]u8; 48 | pub extern fn stbi_zlib_decode_malloc(buffer: [*c]const u8, len: c_int, outlen: [*c]c_int) [*c]u8; 49 | pub extern fn stbi_zlib_decode_buffer(obuffer: [*c]u8, olen: c_int, ibuffer: [*c]const u8, ilen: c_int) c_int; 50 | pub extern fn stbi_zlib_decode_noheader_malloc(buffer: [*c]const u8, len: c_int, outlen: [*c]c_int) [*c]u8; 51 | pub extern fn stbi_zlib_decode_noheader_buffer(obuffer: [*c]u8, olen: c_int, ibuffer: [*c]const u8, ilen: c_int) c_int; 52 | -------------------------------------------------------------------------------- /aya/deps/stb/stb_image_write.zig: -------------------------------------------------------------------------------- 1 | pub extern var stbi_write_tga_with_rle: c_int; 2 | pub extern var stbi_write_png_compression_level: c_int; 3 | pub extern var stbi_write_force_png_filter: c_int; 4 | 5 | pub extern fn stbi_write_png(filename: [*c]const u8, w: c_int, h: c_int, comp: c_int, data: ?*const anyopaque, stride_in_bytes: c_int) c_int; 6 | pub extern fn stbi_write_bmp(filename: [*c]const u8, w: c_int, h: c_int, comp: c_int, data: ?*const anyopaque) c_int; 7 | pub extern fn stbi_write_tga(filename: [*c]const u8, w: c_int, h: c_int, comp: c_int, data: ?*const anyopaque) c_int; 8 | pub extern fn stbi_write_hdr(filename: [*c]const u8, w: c_int, h: c_int, comp: c_int, data: [*c]const f32) c_int; 9 | pub extern fn stbi_write_jpg(filename: [*c]const u8, x: c_int, y: c_int, comp: c_int, data: ?*const anyopaque, quality: c_int) c_int; 10 | 11 | pub const stbi_write_func = fn (?*anyopaque, ?*anyopaque, c_int) callconv(.C) void; 12 | pub extern fn stbi_write_png_to_func(func: ?stbi_write_func, context: ?*anyopaque, w: c_int, h: c_int, comp: c_int, data: ?*const anyopaque, stride_in_bytes: c_int) c_int; 13 | pub extern fn stbi_write_bmp_to_func(func: ?stbi_write_func, context: ?*anyopaque, w: c_int, h: c_int, comp: c_int, data: ?*const anyopaque) c_int; 14 | pub extern fn stbi_write_tga_to_func(func: ?stbi_write_func, context: ?*anyopaque, w: c_int, h: c_int, comp: c_int, data: ?*const anyopaque) c_int; 15 | pub extern fn stbi_write_hdr_to_func(func: ?stbi_write_func, context: ?*anyopaque, w: c_int, h: c_int, comp: c_int, data: [*c]const f32) c_int; 16 | pub extern fn stbi_write_jpg_to_func(func: ?stbi_write_func, context: ?*anyopaque, x: c_int, y: c_int, comp: c_int, data: ?*const anyopaque, quality: c_int) c_int; 17 | pub extern fn stbi_flip_vertically_on_write(flip_boolean: c_int) void; 18 | -------------------------------------------------------------------------------- /aya/deps/stb/stb_rect_pack.zig: -------------------------------------------------------------------------------- 1 | pub const stbrp_context = extern struct { 2 | width: c_int, 3 | height: c_int, 4 | @"align": c_int, 5 | init_mode: c_int, 6 | heuristic: c_int, 7 | num_nodes: c_int, 8 | active_head: [*c]stbrp_node, 9 | free_head: [*c]stbrp_node, 10 | extra: [2]stbrp_node, 11 | }; 12 | 13 | pub const stbrp_node = extern struct { 14 | x: c_ushort, 15 | y: c_ushort, 16 | next: [*c]stbrp_node, 17 | }; 18 | 19 | pub const stbrp_rect = extern struct { 20 | id: c_int, 21 | w: c_ushort, 22 | h: c_ushort, 23 | x: c_ushort = 0, 24 | y: c_ushort = 0, 25 | was_packed: c_int = 0, 26 | }; 27 | 28 | pub extern fn stbrp_pack_rects(context: [*c]stbrp_context, rects: [*c]stbrp_rect, num_rects: c_int) c_int; 29 | pub extern fn stbrp_init_target(context: [*c]stbrp_context, width: c_int, height: c_int, nodes: [*c]stbrp_node, num_nodes: c_int) void; 30 | pub extern fn stbrp_setup_allow_out_of_mem(context: [*c]stbrp_context, allow_out_of_mem: c_int) void; 31 | pub extern fn stbrp_setup_heuristic(context: [*c]stbrp_context, heuristic: c_int) void; 32 | 33 | pub const STBRP_HEURISTIC_Skyline_default = @intFromEnum(enum_unnamed_1.STBRP_HEURISTIC_Skyline_default); 34 | // pub const STBRP_HEURISTIC_Skyline_BL_sortHeight = @enumToInt(enum_unnamed_1.STBRP_HEURISTIC_Skyline_BL_sortHeight); 35 | pub const STBRP_HEURISTIC_Skyline_BF_sortHeight = @intFromEnum(enum_unnamed_1.STBRP_HEURISTIC_Skyline_BF_sortHeight); 36 | const enum_unnamed_1 = enum(c_int) { 37 | STBRP_HEURISTIC_Skyline_default = 0, 38 | // STBRP_HEURISTIC_Skyline_BL_sortHeight = 0, 39 | STBRP_HEURISTIC_Skyline_BF_sortHeight = 1, 40 | _, 41 | }; 42 | -------------------------------------------------------------------------------- /aya/src/debug.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("../aya.zig"); 3 | const math = @import("math/math.zig"); 4 | pub const draw = aya.gfx.draw; 5 | 6 | pub const Debug = struct { 7 | debug_items: std.ArrayList(DebugDrawCommand), 8 | 9 | const DebugDrawCommand = union(enum) { 10 | point: Point, 11 | line: Line, 12 | rect: Rect, 13 | circle: Circle, 14 | text: Text, 15 | }; 16 | 17 | const Point = struct { 18 | pos: math.Vec2, 19 | size: f32, 20 | color: math.Color, 21 | }; 22 | 23 | const Line = struct { 24 | pt1: math.Vec2, 25 | pt2: math.Vec2, 26 | thickness: f32, 27 | color: math.Color, 28 | }; 29 | 30 | const Rect = struct { 31 | pos: math.Vec2, 32 | w: f32, 33 | h: f32, 34 | thickness: f32 = 0, 35 | hollow: bool = false, 36 | color: math.Color, 37 | }; 38 | 39 | const Circle = struct { 40 | center: math.Vec2, 41 | r: f32, 42 | thickness: f32, 43 | hollow: bool = true, 44 | color: math.Color, 45 | }; 46 | 47 | const Text = struct { 48 | pos: math.Vec2, 49 | text: []const u8, 50 | color: math.Color, 51 | }; 52 | 53 | pub fn init() !Debug { 54 | return Debug{ 55 | .debug_items = try std.ArrayList(DebugDrawCommand).initCapacity(aya.mem.allocator, 5), 56 | }; 57 | } 58 | 59 | pub fn deinit(self: Debug) void { 60 | self.debug_items.deinit(); 61 | } 62 | 63 | pub fn render(self: *Debug, enabled: bool) void { 64 | if (enabled and self.debug_items.items.len > 0) { 65 | for (self.debug_items.items) |item| { 66 | switch (item) { 67 | .point => |pt| draw.point(pt.pos, pt.size, pt.color), 68 | .line => |line| draw.line(line.pt1, line.pt2, line.thickness, line.color), 69 | .rect => |rect| { 70 | if (rect.hollow) { 71 | draw.hollowRect(rect.pos, rect.w, rect.h, rect.thickness, rect.color); 72 | } else { 73 | draw.rect(rect.pos, rect.w, rect.h, rect.color); 74 | } 75 | }, 76 | .circle => |circle| draw.circle(circle.center, circle.r, circle.thickness, 12, circle.color), 77 | .text => |text| draw.text(text.text, text.pos.x, text.pos.y, null), 78 | } 79 | } 80 | aya.gfx.flush(); 81 | } 82 | self.debug_items.items.len = 0; 83 | } 84 | 85 | pub fn drawPoint(self: *Debug, pos: math.Vec2, size: f32, color: ?math.Color) void { 86 | const point = Point{ .pos = pos, .size = size, .color = color orelse math.Color.white }; 87 | self.debug_items.append(.{ .point = point }) catch return; 88 | } 89 | 90 | pub fn drawLine(self: *Debug, pt1: math.Vec2, pt2: math.Vec2, thickness: f32, color: ?math.Color) void { 91 | const line = Line{ .pt2 = pt1, .pt2 = pt2, .thickness = thickness, .color = color orelse math.Color.white }; 92 | self.debug_items.append(.{ .line = line }) catch return; 93 | } 94 | 95 | pub fn drawRect(self: *Debug, pos: math.Vec2, width: f32, height: f32, color: ?math.Color) void { 96 | const rect = Rect{ .pos = pos, .w = width, .h = height, .hollow = false, .color = color orelse math.Color.white }; 97 | self.debug_items.append(.{ .rect = rect }) catch return; 98 | } 99 | 100 | pub fn drawHollowRect(self: *Debug, pos: math.Vec2, width: f32, height: f32, thickness: f32, color: ?math.Color) void { 101 | const rect = Rect{ .pos = pos, .w = width, .h = height, .thickness = thickness, .hollow = true, .color = color orelse math.Color.white }; 102 | self.debug_items.append(.{ .rect = rect }) catch return; 103 | } 104 | 105 | pub fn drawHollowCircle(self: *Debug, center: math.Vec2, radius: f32, thickness: f32, color: ?math.Color) void { 106 | const circle = Circle{ .center = center, .radius = radius, .thickness = thickness, .color = color orelse math.Color.white }; 107 | self.debug_items.append(.{ .circle = circle }) catch return; 108 | } 109 | 110 | pub fn drawText(self: *Debug, text: []const u8, pos: math.Vec2, color: ?math.Color) void { 111 | const text_item = Text{ .pos = pos, .text = text, .color = color orelse math.Color.white }; 112 | self.debug_items.append(.{ .text = text_item }) catch return; 113 | } 114 | 115 | pub fn drawTextFmt(self: *Debug, comptime fmt: []const u8, args: anytype, pos: math.Vec2, color: ?math.Color) void { 116 | const text = std.fmt.allocPrint(aya.mem.tmp_allocator, fmt, args) catch |err| { 117 | std.debug.print("drawTextFormat error: {}\n", .{err}); 118 | return; 119 | }; 120 | 121 | const text_item = Text{ .pos = pos, .text = text, .color = color orelse math.Color.white }; 122 | self.debug_items.append(.{ .text = text_item }) catch return; 123 | } 124 | }; 125 | -------------------------------------------------------------------------------- /aya/src/fs.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const fs = std.fs; 3 | const aya = @import("../aya.zig"); 4 | 5 | /// reads the contents of a file. Returned value is owned by the caller and must be freed! 6 | pub fn read(_: std.mem.Allocator, filename: []const u8) ![]u8 { 7 | const file = try std.fs.cwd().openFile(filename, .{}); 8 | defer file.close(); 9 | 10 | const file_size = try file.getEndPos(); 11 | var buffer = try aya.mem.allocator.alloc(u8, file_size); 12 | _ = try file.read(buffer); 13 | 14 | return buffer; 15 | } 16 | 17 | pub fn readZ(_: std.mem.Allocator, filename: []const u8) ![:0]u8 { 18 | const file = try std.fs.cwd().openFile(filename, .{}); 19 | defer file.close(); 20 | 21 | const file_size = try file.getEndPos(); 22 | var buffer = try aya.mem.allocator.alloc(u8, file_size + 1); 23 | _ = try file.read(buffer); 24 | buffer[file_size] = 0; 25 | 26 | return buffer[0..file_size :0]; 27 | } 28 | 29 | pub fn write(filename: []const u8, data: []u8) !void { 30 | const file = try std.fs.cwd().openFile(filename, .{ .write = true }); 31 | defer file.close(); 32 | 33 | // const file_size = try file.getEndPos(); 34 | try file.writeAll(data); 35 | } 36 | 37 | /// gets a path to `filename` in the save games directory 38 | pub fn getSaveGamesFile(app: []const u8, filename: []const u8) ![]u8 { 39 | const dir = try std.fs.getAppDataDir(aya.mem.tmp_allocator, app); 40 | try std.fs.cwd().makePath(dir); 41 | return try std.fs.path.join(aya.mem.tmp_allocator, &[_][]const u8{ dir, filename }); 42 | } 43 | 44 | /// saves a serializable struct to disk 45 | pub fn savePrefs(app: []const u8, filename: []const u8, data: anytype) !void { 46 | const file = try getSaveGamesFile(app, filename); 47 | var handle = try std.fs.cwd().createFile(file, .{}); 48 | defer handle.close(); 49 | 50 | var serializer = std.io.serializer(.Little, .Byte, handle.writer()); 51 | try serializer.serialize(data); 52 | } 53 | 54 | pub fn readPrefs(comptime T: type, app: []const u8, filename: []const u8) !T { 55 | const file = try getSaveGamesFile(app, filename); 56 | var handle = try std.fs.cwd().openFile(file, .{}); 57 | defer handle.close(); 58 | 59 | var deserializer = std.io.deserializer(.Little, .Byte, handle.reader()); 60 | return deserializer.deserialize(T); 61 | } 62 | 63 | pub fn savePrefsJson(app: []const u8, filename: []const u8, data: anytype) !void { 64 | const file = try getSaveGamesFile(app, filename); 65 | var handle = try std.fs.cwd().createFile(file, .{}); 66 | defer handle.close(); 67 | 68 | try std.json.stringify(data, .{ .whitespace = .{} }, handle.writer()); 69 | } 70 | 71 | pub fn readPrefsJson(comptime T: type, app: []const u8, filename: []const u8) !T { 72 | const file = try getSaveGamesFile(app, filename); 73 | var bytes = try aya.fs.read(aya.mem.tmp_allocator, file); 74 | var tokens = std.json.TokenStream.init(bytes); 75 | 76 | const options = std.json.ParseOptions{ .allocator = aya.mem.allocator }; 77 | return try std.json.parse(T, &tokens, options); 78 | } 79 | 80 | /// for prefs loaded with `readPrefsJson` that have allocated fields, this must be called to free them 81 | pub fn freePrefsJson(data: anytype) void { 82 | const options = std.json.ParseOptions{ .allocator = aya.mem.allocator }; 83 | std.json.parseFree(@TypeOf(data), data, options); 84 | } 85 | 86 | /// returns a slice of all the files with extension. The caller owns the slice AND each path in the slice. 87 | pub fn getAllFilesOfType(allocator: std.mem.Allocator, root_directory: []const u8, extension: []const u8, recurse: bool) [][]const u8 { 88 | const recursor = struct { 89 | fn search(directory: []const u8, recursive: bool, filelist: *std.ArrayList([]const u8), ext: []const u8) void { 90 | var dir = fs.cwd().openIterableDir(directory, .{ .access_sub_paths = true }) catch unreachable; 91 | defer dir.close(); 92 | 93 | var iter = dir.iterate(); 94 | while (iter.next() catch unreachable) |entry| { 95 | if (entry.kind == .file) { 96 | if (std.mem.endsWith(u8, entry.name, ext)) { 97 | const abs_path = fs.path.join(filelist.allocator, &[_][]const u8{ directory, entry.name }) catch unreachable; 98 | filelist.append(abs_path) catch unreachable; 99 | } 100 | } else if (entry.kind == .directory) { 101 | const abs_path = fs.path.join(aya.mem.tmp_allocator, &[_][]const u8{ directory, entry.name }) catch unreachable; 102 | search(abs_path, recursive, filelist, ext); 103 | } 104 | } 105 | } 106 | }.search; 107 | 108 | var list = std.ArrayList([]const u8).init(allocator); 109 | recursor(root_directory, recurse, &list, extension); 110 | 111 | return list.toOwnedSlice() catch unreachable; 112 | } 113 | 114 | test "test fs read" { 115 | aya.mem.initTmpAllocator(); 116 | std.testing.expectError(error.FileNotFound, read(std.testing.allocator, "junk.png")); 117 | } 118 | -------------------------------------------------------------------------------- /aya/src/gfx/assets/ProggyTiny.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prime31/zig-aya/fd03a2cbddee6773f19d6ce849f6d1a60ed39324/aya/src/gfx/assets/ProggyTiny.ttf -------------------------------------------------------------------------------- /aya/src/gfx/assets/sprite_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform sampler2D main_tex; 4 | 5 | layout(location = 0) out vec4 frag_color; 6 | in vec2 uv_out; 7 | in vec4 color_out; 8 | 9 | vec4 effect(sampler2D tex, vec2 tex_coord, vec4 vert_color) 10 | { 11 | return texture(tex, tex_coord) * vert_color; 12 | } 13 | 14 | void main() 15 | { 16 | vec2 param = uv_out; 17 | vec4 param_1 = color_out; 18 | frag_color = effect(main_tex, param, param_1); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /aya/src/gfx/assets/sprite_vs.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform vec4 VertexParams[2]; 4 | 5 | layout(location = 0) in vec2 pos_in; 6 | layout(location = 1) in vec2 uv_in; 7 | layout(location = 2) in vec4 color_in; 8 | 9 | out vec2 uv_out; 10 | out vec4 color_out; 11 | 12 | void main() 13 | { 14 | uv_out = uv_in; 15 | color_out = color_in; 16 | gl_Position = vec4(mat3x2(vec2(VertexParams[0].x, VertexParams[0].y), vec2(VertexParams[0].z, VertexParams[0].w), vec2(VertexParams[1].x, VertexParams[1].y)) * vec3(pos_in, 1.0), 0.0, 1.0); 17 | } 18 | 19 | -------------------------------------------------------------------------------- /aya/src/gfx/atlas_batch.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("../../aya.zig"); 3 | const fna = @import("fna"); 4 | const Vertex = aya.gfx.Vertex; 5 | const DynamicMesh = @import("mesh.zig").DynamicMesh; 6 | 7 | // TODO: who should own and deinit the Texture? 8 | // TODO: dont return errors for adds and just dynamically expand the vertex/index buffers 9 | pub const AtlasBatch = struct { 10 | mesh: DynamicMesh(u16, Vertex), 11 | max_sprites: usize, 12 | sprite_count: usize = 0, 13 | texture: aya.gfx.Texture, 14 | buffer_dirty: bool = false, 15 | dirty_range: struct { start: i32, end: i32 }, 16 | 17 | /// AtlasBatch does not take ownership of the texture passed in 18 | pub fn init(allocator: ?std.mem.Allocator, texture: aya.gfx.Texture, max_sprites: usize) !AtlasBatch { 19 | const alloc = allocator orelse aya.mem.allocator; 20 | 21 | return AtlasBatch{ 22 | .mesh = try DynamicMesh(u16, Vertex).init(alloc, max_sprites * 4, try getIndexBufferData(max_sprites)), 23 | .max_sprites = max_sprites, 24 | .texture = texture, 25 | .dirty_range = .{ .start = 0, .end = 0 }, 26 | }; 27 | } 28 | 29 | pub fn deinit(self: AtlasBatch) void { 30 | self.mesh.deinit(); 31 | } 32 | 33 | fn getIndexBufferData(max_sprites: usize) ![]u16 { 34 | var indices = try aya.mem.tmp_allocator.alloc(u16, max_sprites * 6); 35 | var i: usize = 0; 36 | while (i < max_sprites) : (i += 1) { 37 | indices[i * 3 * 2 + 0] = @as(u16, @intCast(i)) * 4 + 0; 38 | indices[i * 3 * 2 + 1] = @as(u16, @intCast(i)) * 4 + 1; 39 | indices[i * 3 * 2 + 2] = @as(u16, @intCast(i)) * 4 + 2; 40 | indices[i * 3 * 2 + 3] = @as(u16, @intCast(i)) * 4 + 0; 41 | indices[i * 3 * 2 + 4] = @as(u16, @intCast(i)) * 4 + 2; 42 | indices[i * 3 * 2 + 5] = @as(u16, @intCast(i)) * 4 + 3; 43 | } 44 | return indices; 45 | } 46 | 47 | /// TODO: fills in the IndexBuffer and uploads it to the GPU 48 | fn setIndexBufferData(_: *AtlasBatch, max_sprites: usize) !void { 49 | _ = getIndexBufferData(max_sprites); 50 | // self.mesh.index_buffer.setData(i16, indices, 0, .none); 51 | @panic("oh no"); 52 | } 53 | 54 | /// makes sure the mesh buffers are large enough. Expands them by 50% if they are not. Can fail horribly. 55 | fn ensureCapacity(self: *AtlasBatch) !void { 56 | if (self.sprite_count < self.max_sprites) return; 57 | @panic("mesh.expandBuffers not implemented"); 58 | 59 | // we dont update the max_sprites value unless all allocations succeed. If they dont, we bail. 60 | // const new_max_sprites = self.max_sprites + @floatToInt(usize, @intToFloat(f32, self.max_sprites) * 0.5); 61 | // try self.mesh.expandBuffers(new_max_sprites * 4, new_max_sprites * 6); 62 | // try self.setIndexBufferData(new_max_sprites); 63 | // self.max_sprites = new_max_sprites; 64 | } 65 | 66 | /// the workhorse. Sets the actual verts in the mesh and keeps track of if the mesh is dirty. 67 | pub fn set(self: *AtlasBatch, index: usize, quad: aya.math.Quad, mat: ?aya.math.Mat32, color: aya.math.Color) void { 68 | var vert_index = index * 4; 69 | 70 | const matrix = mat orelse aya.math.Mat32.identity; 71 | 72 | // copy the quad positions, uvs and color into vertex array transforming them with the matrix as we do it 73 | matrix.transformQuad(self.mesh.verts[vert_index .. vert_index + 4], quad, color); 74 | self.buffer_dirty = true; 75 | } 76 | 77 | pub fn setViewport(self: *AtlasBatch, index: usize, viewport: aya.math.RectI, mat: ?aya.math.Mat32, color: aya.math.Color) void { 78 | var quad = aya.math.Quad.init(@as(f32, @floatFromInt(viewport.x)), @as(f32, @floatFromInt(viewport.y)), @as(f32, @floatFromInt(viewport.w)), @as(f32, @floatFromInt(viewport.h)), self.texture.width, self.texture.height); 79 | self.set(index, quad, mat, color); 80 | } 81 | 82 | /// adds a new quad to the batch returning the index so that it can be updated with set* later 83 | pub fn add(self: *AtlasBatch, quad: aya.math.Quad, mat: ?aya.math.Mat32, color: aya.math.Color) usize { 84 | self.ensureCapacity() catch |err| { 85 | std.debug.print("failed to ensureCapacity with error: {}\n", .{err}); 86 | return 0; 87 | }; 88 | self.set(self.sprite_count, quad, mat, color); 89 | 90 | self.sprite_count += 1; 91 | return self.sprite_count - 1; 92 | } 93 | 94 | /// adds a new quad to the batch returning the index so that it can be updated with set* later 95 | pub fn addViewport(self: *AtlasBatch, viewport: aya.math.RectI, mat: ?aya.math.Mat32, color: aya.math.Color) usize { 96 | var quad = aya.math.Quad.init(@as(f32, @floatFromInt(viewport.x)), @as(f32, @floatFromInt(viewport.y)), @as(f32, @floatFromInt(viewport.w)), @as(f32, @floatFromInt(viewport.h)), self.texture.width, self.texture.height); 97 | return self.add(quad, mat, color); 98 | } 99 | 100 | pub fn draw(self: *AtlasBatch) void { 101 | if (self.buffer_dirty) { 102 | self.mesh.updateVertSlice(self.sprite_count * 4); 103 | self.buffer_dirty = false; 104 | } 105 | 106 | self.mesh.bindImage(self.texture.img, 0); 107 | self.mesh.draw(0, @as(i32, @intCast(self.sprite_count * 6))); 108 | } 109 | }; 110 | -------------------------------------------------------------------------------- /aya/src/gfx/multi_batcher.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const rk = @import("renderkit"); 3 | const aya = @import("aya"); 4 | const math = aya.math; 5 | 6 | const IndexBuffer = rk.IndexBuffer; 7 | const VertexBuffer = rk.VertexBuffer; 8 | 9 | pub const MultiVertex = extern struct { 10 | pos: math.Vec2, 11 | uv: math.Vec2 = .{ .x = 0, .y = 0 }, 12 | col: u32 = 0xFFFFFFFF, 13 | tid: f32 = 0, 14 | }; 15 | 16 | pub const MultiBatcher = struct { 17 | mesh: aya.gfx.DynamicMesh(u16, MultiVertex), 18 | vert_index: usize = 0, // current index into the vertex array 19 | textures: [8]rk.Image = undefined, 20 | last_texture: usize = 0, 21 | 22 | pub fn init(allocator: std.mem.Allocator, max_sprites: usize) MultiBatcher { 23 | if (max_sprites * 6 > std.math.maxInt(u16)) @panic("max_sprites exceeds u16 index buffer size"); 24 | 25 | var indices = allocator.alloc(u16, max_sprites * 6) catch unreachable; 26 | var i: usize = 0; 27 | while (i < max_sprites) : (i += 1) { 28 | indices[i * 3 * 2 + 0] = @as(u16, @intCast(i)) * 4 + 0; 29 | indices[i * 3 * 2 + 1] = @as(u16, @intCast(i)) * 4 + 1; 30 | indices[i * 3 * 2 + 2] = @as(u16, @intCast(i)) * 4 + 2; 31 | indices[i * 3 * 2 + 3] = @as(u16, @intCast(i)) * 4 + 0; 32 | indices[i * 3 * 2 + 4] = @as(u16, @intCast(i)) * 4 + 2; 33 | indices[i * 3 * 2 + 5] = @as(u16, @intCast(i)) * 4 + 3; 34 | } 35 | 36 | return .{ 37 | .mesh = aya.gfx.DynamicMesh(u16, MultiVertex).init(allocator, max_sprites * 4, indices) catch unreachable, 38 | .textures = [_]rk.Image{0} ** 8, 39 | }; 40 | } 41 | 42 | pub fn deinit(self: *MultiBatcher) void { 43 | self.mesh.deinit(); 44 | } 45 | 46 | pub fn begin(self: *MultiBatcher) void { 47 | self.vert_index = 0; 48 | } 49 | 50 | pub fn end(self: *MultiBatcher) void { 51 | self.flush(); 52 | } 53 | 54 | pub fn flush(self: *MultiBatcher) void { 55 | if (self.vert_index == 0) return; 56 | 57 | // send data to gpu 58 | self.mesh.updateVertSlice(0, self.vert_index); 59 | 60 | // bind textures 61 | for (self.textures, 0..) |tid, slot| { 62 | if (slot == self.last_texture) break; 63 | self.mesh.bindImage(tid, @as(c_uint, @intCast(slot))); 64 | } 65 | 66 | // draw 67 | const quads = @divExact(self.vert_index, 4); 68 | self.mesh.draw(@as(c_int, @intCast(quads * 6))); 69 | 70 | // reset state 71 | for (self.textures, 0..) |*tid, slot| { 72 | if (slot == self.last_texture) break; 73 | self.mesh.bindImage(tid.*, @as(c_uint, @intCast(slot))); 74 | tid.* = 0; 75 | } 76 | 77 | self.vert_index = 0; 78 | self.last_texture = 0; 79 | } 80 | 81 | inline fn submitTexture(self: *MultiBatcher, img: rk.Image) f32 { 82 | if (std.mem.indexOfScalar(rk.Image, &self.textures, img)) |index| return @as(f32, @floatFromInt(index)); 83 | 84 | self.textures[self.last_texture] = img; 85 | self.last_texture += 1; 86 | return @as(f32, @floatFromInt(self.last_texture - 1)); 87 | } 88 | 89 | pub fn drawTex(self: *MultiBatcher, pos: math.Vec2, col: u32, texture: aya.gfx.Texture) void { 90 | if (self.vert_index >= self.mesh.verts.len) { 91 | self.flush(); 92 | } 93 | 94 | const tid = self.submitTexture(texture.img); 95 | 96 | var verts = self.mesh.verts[self.vert_index .. self.vert_index + 4]; 97 | verts[0].pos = pos; // bl 98 | verts[0].uv = .{ .x = 0, .y = 1 }; 99 | verts[0].col = col; 100 | verts[0].tid = tid; 101 | 102 | verts[1].pos = .{ .x = pos.x + texture.width, .y = pos.y }; // br 103 | verts[1].uv = .{ .x = 1, .y = 1 }; 104 | verts[1].col = col; 105 | verts[1].tid = tid; 106 | 107 | verts[2].pos = .{ .x = pos.x + texture.width, .y = pos.y + texture.height }; // tr 108 | verts[2].uv = .{ .x = 1, .y = 0 }; 109 | verts[2].col = col; 110 | verts[2].tid = tid; 111 | 112 | verts[3].pos = .{ .x = pos.x, .y = pos.y + texture.height }; // tl 113 | verts[3].uv = .{ .x = 0, .y = 0 }; 114 | verts[3].col = col; 115 | verts[3].tid = tid; 116 | 117 | self.vert_index += 4; 118 | } 119 | }; 120 | -------------------------------------------------------------------------------- /aya/src/gfx/offscreen_pass.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("../../aya.zig"); 3 | const rk = @import("renderkit"); 4 | const gfx = @import("gfx.zig"); 5 | 6 | pub const OffscreenPass = struct { 7 | pass: rk.Pass, 8 | color_texture: gfx.Texture, 9 | depth_stencil_texture: ?gfx.Texture = null, 10 | 11 | pub fn init(width: i32, height: i32) OffscreenPass { 12 | return initWithOptions(width, height, .nearest, .clamp); 13 | } 14 | 15 | pub fn initWithOptions(width: i32, height: i32, filter: rk.TextureFilter, wrap: rk.TextureWrap) OffscreenPass { 16 | const color_tex = gfx.Texture.initOffscreen(width, height, filter, wrap); 17 | 18 | const pass = rk.createPass(.{ 19 | .color_img = color_tex.img, 20 | }); 21 | return .{ .pass = pass, .color_texture = color_tex }; 22 | } 23 | 24 | pub fn initWithStencil(width: i32, height: i32, filter: rk.TextureFilter, wrap: rk.TextureWrap) OffscreenPass { 25 | const color_tex = gfx.Texture.initOffscreen(width, height, filter, wrap); 26 | const depth_stencil_img = gfx.Texture.initStencil(width, height, filter, wrap); 27 | 28 | const pass = rk.createPass(.{ 29 | .color_img = color_tex.img, 30 | .depth_stencil_img = depth_stencil_img.img, 31 | }); 32 | return .{ .pass = pass, .color_texture = color_tex, .depth_stencil_texture = depth_stencil_img }; 33 | } 34 | 35 | pub fn deinit(self: *const OffscreenPass) void { 36 | // Pass MUST be destroyed first! It relies on the Textures being present. 37 | rk.destroyPass(self.pass); 38 | self.color_texture.deinit(); 39 | if (self.depth_stencil_texture) |depth_stencil| { 40 | depth_stencil.deinit(); 41 | } 42 | } 43 | 44 | pub fn resize(self: *OffscreenPass, width: i32, height: i32) void { 45 | self.deinit(); 46 | self.* = if (self.depth_stencil_texture != null) OffscreenPass.initWithStencil(width, height, .nearest, .clamp) else OffscreenPass.init(width, height); 47 | } 48 | }; 49 | 50 | pub const DefaultOffscreenPass = struct { 51 | pass: OffscreenPass, 52 | policy: gfx.ResolutionPolicy, 53 | scaler: gfx.ResolutionScaler, 54 | design_w: i32, 55 | design_h: i32, 56 | 57 | pub fn init(w: i32, h: i32, filter: rk.TextureFilter, policy: gfx.ResolutionPolicy) DefaultOffscreenPass { 58 | // fetch the Resolution_Scaler first since it will decide the render texture size 59 | var scaler = policy.getScaler(w, h); 60 | 61 | const pass = DefaultOffscreenPass{ 62 | .pass = if (policy != .none) OffscreenPass.initWithOptions(w, h, filter, .clamp) else undefined, 63 | .policy = policy, 64 | .scaler = scaler, 65 | .design_w = w, 66 | .design_h = h, 67 | }; 68 | 69 | // TODO: remove the hack from gfx.blitToScreen that calls onWindowResizedCallback when this works 70 | //aya.window.subscribe(.resize, onWindowResizedCallback, pass, false); 71 | 72 | return pass; 73 | } 74 | 75 | pub fn deinit(self: DefaultOffscreenPass) void { 76 | // TODO: unsubscribe from window resize event 77 | if (self.policy != .none) { 78 | self.pass.deinit(); 79 | } 80 | } 81 | 82 | pub fn onWindowResizedCallback(self: *DefaultOffscreenPass) void { 83 | const size = aya.window.drawableSize(); 84 | if (size.w != 0 and size.h != 0 and self.policy == .default and (size.w != self.design_w or size.h != self.design_h)) { 85 | self.pass.resize(size.w, size.h); 86 | self.design_w = size.w; 87 | self.design_h = size.h; 88 | } 89 | self.scaler = self.policy.getScaler(self.design_w, self.design_h); 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /aya/src/gfx/post_process_stack.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("../../aya.zig"); 3 | const OffscreenPass = aya.gfx.OffscreenPass; 4 | 5 | /// manaages a list of PostProcessors that are used to process a render texture before blitting it to the screen. 6 | pub const PostProcessStack = struct { 7 | processors: std.ArrayList(*PostProcessor), 8 | allocator: std.mem.Allocator, 9 | pass: OffscreenPass, 10 | 11 | pub fn init(allocator: ?std.mem.Allocator, design_w: i32, _: i32) PostProcessStack { 12 | _ = design_w; 13 | const alloc = allocator orelse aya.mem.allocator; 14 | return .{ 15 | .processors = std.ArrayList(*PostProcessor).init(alloc), 16 | .allocator = alloc, 17 | .pass = OffscreenPass.init(aya.window.width(), aya.window.height()), 18 | }; 19 | } 20 | 21 | pub fn deinit(self: PostProcessStack) void { 22 | for (self.processors.items) |p| { 23 | p.deinit(p, self.allocator); 24 | } 25 | self.processors.deinit(); 26 | self.pass.deinit(); 27 | } 28 | 29 | pub fn add(self: *PostProcessStack, comptime T: type, data: anytype) *T { 30 | std.debug.assert(@hasDecl(T, "initialize")); 31 | std.debug.assert(@hasDecl(T, "deinit")); 32 | std.debug.assert(@hasDecl(T, "process")); 33 | std.debug.assert(@hasField(T, "postprocessor")); 34 | 35 | var processor = self.allocator.create(T) catch unreachable; 36 | processor.initialize(data); 37 | 38 | // get a closure so that we can safely deinit this later 39 | processor.postprocessor.deinit = struct { 40 | fn deinit(proc: *PostProcessor, allocator: std.mem.Allocator) void { 41 | proc.getParent(T).deinit(); 42 | allocator.destroy(@fieldParentPtr(T, "postprocessor", proc)); 43 | } 44 | }.deinit; 45 | 46 | self.processors.append(&processor.postprocessor) catch unreachable; 47 | return processor; 48 | } 49 | 50 | pub fn process(self: *PostProcessStack, pass: OffscreenPass) void { 51 | // keep our OffscreenPass size in sync with the default pass 52 | if (pass.color_texture.width != self.pass.color_texture.width or pass.color_texture.height != self.pass.color_texture.height) { 53 | self.pass.resize(@as(i32, @intFromFloat(pass.color_texture.width)), @as(i32, @intFromFloat(pass.color_texture.height))); 54 | } 55 | 56 | for (self.processors.items, 0..) |p, i| { 57 | const offscreen_pass = if (!aya.math.isEven(i)) pass else self.pass; 58 | const tex = if (aya.math.isEven(i)) pass.color_texture else self.pass.color_texture; 59 | p.process(p, offscreen_pass, tex); 60 | } 61 | 62 | // if there was an odd number of post processors blit to the OffscreenPass that was passed in so it gets blitted to the backbuffer 63 | if (!aya.math.isEven(self.processors.items.len)) { 64 | aya.gfx.beginPass(.{ .clear_color = false, .pass = pass }); 65 | aya.draw.tex(self.pass.color_texture, 0, 0); 66 | aya.gfx.endPass(); 67 | } 68 | } 69 | }; 70 | 71 | /// implementors must have initialize, deinit and process methods defined and a postprocessor: *PostProcessor field. 72 | /// The initialize method is where default values should be set since this will be a heap allocated object. 73 | pub const PostProcessor = struct { 74 | process: *const fn (*PostProcessor, OffscreenPass, aya.gfx.Texture) void, 75 | deinit: *const fn (self: *PostProcessor, allocator: std.mem.Allocator) void = undefined, 76 | 77 | /// can be used externally, to get the original PostProcessor by Type or by the PostProcessor to get itself interface methods. 78 | pub fn getParent(self: *PostProcessor, comptime T: type) *T { 79 | return @fieldParentPtr(T, "postprocessor", self); 80 | } 81 | 82 | // helper method for taking the final texture from a postprocessor and blitting it. Simple postprocessors 83 | // can get away with just calling this method directly. 84 | pub fn blit(_: *PostProcessor, pass: OffscreenPass, tex: aya.gfx.Texture, shader: *aya.gfx.Shader) void { 85 | aya.gfx.beginPass(.{ .clear_color = false, .pass = pass, .shader = shader }); 86 | aya.draw.tex(tex, 0, 0); 87 | aya.gfx.endPass(); 88 | } 89 | }; 90 | -------------------------------------------------------------------------------- /aya/src/gfx/triangle_batcher.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const renderkit = @import("renderkit"); 3 | const aya = @import("../../aya.zig"); 4 | const math = aya.math; 5 | 6 | const Vertex = aya.gfx.Vertex; 7 | const DynamicMesh = aya.gfx.DynamicMesh; 8 | 9 | pub const TriangleBatcher = struct { 10 | mesh: DynamicMesh(void, Vertex), 11 | draw_calls: std.ArrayList(i32), 12 | white_tex: aya.gfx.Texture = undefined, 13 | 14 | begin_called: bool = false, 15 | frame: u32 = 0, // tracks when a batch is started in a new frame so that state can be reset 16 | vert_index: usize = 0, // current index into the vertex array 17 | vert_count: i32 = 0, // total verts that we have not yet rendered 18 | buffer_offset: i32 = 0, // offset into the vertex buffer of the first non-rendered vert 19 | 20 | fn createDynamicMesh(allocator: std.mem.Allocator, max_tris: u16) !DynamicMesh(void, Vertex) { 21 | return try DynamicMesh(void, Vertex).init(allocator, max_tris * 3, &[_]void{}); 22 | } 23 | 24 | pub fn init(allocator: ?std.mem.Allocator, max_tris: u16) !TriangleBatcher { 25 | const alloc = allocator orelse aya.mem.allocator; 26 | 27 | var batcher = TriangleBatcher{ 28 | .mesh = try createDynamicMesh(alloc, max_tris * 3), 29 | .draw_calls = try std.ArrayList(i32).initCapacity(alloc, 10), 30 | }; 31 | errdefer batcher.deinit(); 32 | 33 | var pixels = [_]u32{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; 34 | batcher.white_tex = aya.gfx.Texture.initWithData(u32, 2, 2, pixels[0..]); 35 | 36 | return batcher; 37 | } 38 | 39 | pub fn deinit(self: *TriangleBatcher) void { 40 | self.mesh.deinit(); 41 | self.draw_calls.deinit(); 42 | self.white_tex.deinit(); 43 | } 44 | 45 | pub fn begin(self: *TriangleBatcher) void { 46 | std.debug.assert(!self.begin_called); 47 | 48 | // reset all state for new frame 49 | if (self.frame != aya.time.frames()) { 50 | self.frame = aya.time.frames(); 51 | self.vert_index = 0; 52 | self.buffer_offset = 0; 53 | } 54 | self.begin_called = true; 55 | } 56 | 57 | /// call at the end of the frame when all drawing is complete. Flushes the batch and resets local state. 58 | pub fn end(self: *TriangleBatcher) void { 59 | std.debug.assert(self.begin_called); 60 | self.flush(); 61 | self.begin_called = false; 62 | } 63 | 64 | pub fn flush(self: *TriangleBatcher) void { 65 | if (self.vert_index == 0) return; 66 | 67 | self.mesh.updateVertSlice(self.vert_index); 68 | self.mesh.bindImage(self.white_tex.img, 0); 69 | 70 | // draw 71 | const tris = self.vert_index / 3; 72 | self.mesh.draw(0, @as(c_int, @intCast(tris * 3))); 73 | 74 | self.vert_index = 0; 75 | } 76 | 77 | /// ensures the vert buffer has enough space 78 | fn ensureCapacity(self: *TriangleBatcher) !void { 79 | // if we run out of buffer we have to flush the batch and possibly discard the whole buffer 80 | if (self.vert_index + 3 > self.mesh.verts.len) { 81 | self.flush(); 82 | 83 | self.vert_index = 0; 84 | self.buffer_offset = 0; 85 | 86 | // with GL we can just orphan the buffer 87 | self.mesh.updateAllVerts(); 88 | self.mesh.bindings.vertex_buffer_offsets[0] = 0; 89 | } 90 | 91 | // start a new draw call if we dont already have one going 92 | if (self.draw_calls.items.len == 0) { 93 | try self.draw_calls.append(0); 94 | } 95 | } 96 | 97 | pub fn drawTriangle(self: *TriangleBatcher, pt1: math.Vec2, pt2: math.Vec2, pt3: math.Vec2, color: math.Color) void { 98 | self.ensureCapacity() catch |err| { 99 | std.debug.print("TriangleBatcher.draw failed to append a draw call with error: {}\n", .{err}); 100 | return; 101 | }; 102 | 103 | // copy the triangle positions, uvs and color into vertex array transforming them with the matrix after we do it 104 | self.mesh.verts[self.vert_index].pos = pt1; 105 | self.mesh.verts[self.vert_index].col = color.value; 106 | self.mesh.verts[self.vert_index + 1].pos = pt2; 107 | self.mesh.verts[self.vert_index + 1].col = color.value; 108 | self.mesh.verts[self.vert_index + 2].pos = pt3; 109 | self.mesh.verts[self.vert_index + 2].col = color.value; 110 | 111 | const mat = math.Mat32.identity; 112 | mat.transformVertexSlice(self.mesh.verts[self.vert_index .. self.vert_index + 3]); 113 | 114 | self.draw_calls.items[self.draw_calls.items.len - 1] += 3; 115 | self.vert_count += 3; 116 | self.vert_index += 3; 117 | } 118 | }; 119 | 120 | test "test triangle batcher" { 121 | var batcher = try TriangleBatcher.init(null, 10); 122 | _ = try batcher.ensureCapacity(null); 123 | batcher.flush(false); 124 | batcher.deinit(); 125 | } 126 | -------------------------------------------------------------------------------- /aya/src/imgui/assets/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prime31/zig-aya/fd03a2cbddee6773f19d6ce849f6d1a60ed39324/aya/src/imgui/assets/fa-regular-400.ttf -------------------------------------------------------------------------------- /aya/src/imgui/assets/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prime31/zig-aya/fd03a2cbddee6773f19d6ce849f6d1a60ed39324/aya/src/imgui/assets/fa-solid-900.ttf -------------------------------------------------------------------------------- /aya/src/imgui/implementation.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const imgui = @import("imgui"); 3 | const imgui_gl = @import("imgui_gl"); 4 | const sdl = @import("sdl"); 5 | const aya = @import("../../aya.zig"); 6 | 7 | const Window = @import("../window.zig").Window; 8 | 9 | // public methods 10 | pub fn init(window: Window, docking: bool, viewports: bool, icon_font: bool) void { 11 | _ = imgui.igCreateContext(null); 12 | var io = imgui.igGetIO(); 13 | io.ConfigFlags |= imgui.ImGuiConfigFlags_NavEnableKeyboard; 14 | if (docking) io.ConfigFlags |= imgui.ImGuiConfigFlags_DockingEnable; 15 | if (viewports) io.ConfigFlags |= imgui.ImGuiConfigFlags_ViewportsEnable; 16 | imgui_gl.initForGl(null, window.sdl_window, window.gl_ctx); 17 | 18 | _ = imgui.ImFontAtlas_AddFontDefault(io.Fonts, null); 19 | 20 | // add FontAwesome optionally 21 | if (icon_font) { 22 | var icons_config = imgui.ImFontConfig_ImFontConfig(); 23 | icons_config[0].MergeMode = true; 24 | icons_config[0].PixelSnapH = true; 25 | icons_config[0].FontDataOwnedByAtlas = false; 26 | icons_config[0].GlyphOffset = .{ .x = 0, .y = 2 }; 27 | 28 | const font_awesome_range: [3]imgui.ImWchar = [_]imgui.ImWchar{ imgui.icons.icon_range_min, imgui.icons.icon_range_max, 0 }; 29 | var data = @embedFile("assets/" ++ imgui.icons.font_icon_filename_fas); 30 | _ = imgui.ImFontAtlas_AddFontFromMemoryTTF(io.Fonts, data, data.len, 14, icons_config, &font_awesome_range); 31 | _ = imgui.ImFontAtlas_Build(io.Fonts); 32 | } 33 | 34 | var style = imgui.igGetStyle(); 35 | style.WindowRounding = 0; 36 | } 37 | 38 | pub fn deinit() void { 39 | imgui_gl.shutdown(); 40 | } 41 | 42 | pub fn newFrame() void { 43 | imgui_gl.newFrame(aya.window.sdl_window); 44 | } 45 | 46 | pub fn render() void { 47 | imgui_gl.render(); 48 | } 49 | 50 | /// returns true if the event is handled by imgui and should be ignored 51 | pub fn handleEvent(event: *sdl.SDL_Event) bool { 52 | if (imgui_gl.ImGui_ImplSDL2_ProcessEvent(event)) { 53 | return switch (event.type) { 54 | sdl.SDL_MOUSEWHEEL, sdl.SDL_MOUSEBUTTONDOWN => return imgui.igGetIO().WantCaptureMouse, 55 | sdl.SDL_KEYDOWN, sdl.SDL_KEYUP, sdl.SDL_TEXTINPUT => return imgui.igGetIO().WantCaptureKeyboard, 56 | sdl.SDL_WINDOWEVENT => return true, 57 | else => return false, 58 | }; 59 | } 60 | return false; 61 | } 62 | -------------------------------------------------------------------------------- /aya/src/input_types.zig: -------------------------------------------------------------------------------- 1 | pub const Keys = enum(c_int) { 2 | unknown = 0, 3 | a = 4, 4 | b = 5, 5 | c = 6, 6 | d = 7, 7 | e = 8, 8 | f = 9, 9 | g = 10, 10 | h = 11, 11 | i = 12, 12 | j = 13, 13 | k = 14, 14 | l = 15, 15 | m = 16, 16 | n = 17, 17 | o = 18, 18 | p = 19, 19 | q = 20, 20 | r = 21, 21 | s = 22, 22 | t = 23, 23 | u = 24, 24 | v = 25, 25 | w = 26, 26 | x = 27, 27 | y = 28, 28 | z = 29, 29 | num_1 = 30, 30 | num_2 = 31, 31 | num_3 = 32, 32 | num_4 = 33, 33 | num_5 = 34, 34 | num_6 = 35, 35 | num_7 = 36, 36 | num_8 = 37, 37 | num_9 = 38, 38 | num_0 = 39, 39 | key_return = 40, 40 | escape = 41, 41 | backspace = 42, 42 | tab = 43, 43 | space = 44, 44 | minus = 45, 45 | equals = 46, 46 | leftbracket = 47, 47 | rightbracket = 48, 48 | backslash = 49, 49 | nonushash = 50, 50 | semicolon = 51, 51 | apostrophe = 52, 52 | grave = 53, 53 | comma = 54, 54 | period = 55, 55 | slash = 56, 56 | capslock = 57, 57 | f1 = 58, 58 | f2 = 59, 59 | f3 = 60, 60 | f4 = 61, 61 | f5 = 62, 62 | f6 = 63, 63 | f7 = 64, 64 | f8 = 65, 65 | f9 = 66, 66 | f10 = 67, 67 | f11 = 68, 68 | f12 = 69, 69 | printscreen = 70, 70 | scrolllock = 71, 71 | pause = 72, 72 | insert = 73, 73 | home = 74, 74 | pageup = 75, 75 | delete = 76, 76 | end = 77, 77 | pagedown = 78, 78 | right = 79, 79 | left = 80, 80 | down = 81, 81 | up = 82, 82 | numlockclear = 83, 83 | kp_divide = 84, 84 | kp_multiply = 85, 85 | kp_minus = 86, 86 | kp_plus = 87, 87 | kp_enter = 88, 88 | kp_1 = 89, 89 | kp_2 = 90, 90 | kp_3 = 91, 91 | kp_4 = 92, 92 | kp_5 = 93, 93 | kp_6 = 94, 94 | kp_7 = 95, 95 | kp_8 = 96, 96 | kp_9 = 97, 97 | kp_0 = 98, 98 | kp_period = 99, 99 | nonusbackslash = 100, 100 | application = 101, 101 | power = 102, 102 | kp_equals = 103, 103 | f13 = 104, 104 | f14 = 105, 105 | f15 = 106, 106 | f16 = 107, 107 | f17 = 108, 108 | f18 = 109, 109 | f19 = 110, 110 | f20 = 111, 111 | f21 = 112, 112 | f22 = 113, 113 | f23 = 114, 114 | f24 = 115, 115 | execute = 116, 116 | help = 117, 117 | menu = 118, 118 | select = 119, 119 | stop = 120, 120 | again = 121, 121 | undo = 122, 122 | cut = 123, 123 | copy = 124, 124 | paste = 125, 125 | find = 126, 126 | mute = 127, 127 | volumeup = 128, 128 | volumedown = 129, 129 | kp_comma = 133, 130 | kp_equalsas400 = 134, 131 | international1 = 135, 132 | international2 = 136, 133 | international3 = 137, 134 | international4 = 138, 135 | international5 = 139, 136 | international6 = 140, 137 | international7 = 141, 138 | international8 = 142, 139 | international9 = 143, 140 | lang1 = 144, 141 | lang2 = 145, 142 | lang3 = 146, 143 | lang4 = 147, 144 | lang5 = 148, 145 | lang6 = 149, 146 | lang7 = 150, 147 | lang8 = 151, 148 | lang9 = 152, 149 | alterase = 153, 150 | sysreq = 154, 151 | cancel = 155, 152 | clear = 156, 153 | prior = 157, 154 | return2 = 158, 155 | separator = 159, 156 | out = 160, 157 | oper = 161, 158 | clearagain = 162, 159 | crsel = 163, 160 | exsel = 164, 161 | kp_00 = 176, 162 | kp_000 = 177, 163 | thousandsseparator = 178, 164 | decimalseparator = 179, 165 | currencyunit = 180, 166 | currencysubunit = 181, 167 | kp_leftparen = 182, 168 | kp_rightparen = 183, 169 | kp_leftbrace = 184, 170 | kp_rightbrace = 185, 171 | kp_tab = 186, 172 | kp_backspace = 187, 173 | kp_a = 188, 174 | kp_b = 189, 175 | kp_c = 190, 176 | kp_d = 191, 177 | kp_e = 192, 178 | kp_f = 193, 179 | kp_xor = 194, 180 | kp_power = 195, 181 | kp_percent = 196, 182 | kp_less = 197, 183 | kp_greater = 198, 184 | kp_ampersand = 199, 185 | kp_dblampersand = 200, 186 | kp_verticalbar = 201, 187 | kp_dblverticalbar = 202, 188 | kp_colon = 203, 189 | kp_hash = 204, 190 | kp_space = 205, 191 | kp_at = 206, 192 | kp_exclam = 207, 193 | kp_memstore = 208, 194 | kp_memrecall = 209, 195 | kp_memclear = 210, 196 | kp_memadd = 211, 197 | kp_memsubtract = 212, 198 | kp_memmultiply = 213, 199 | kp_memdivide = 214, 200 | kp_plusminus = 215, 201 | kp_clear = 216, 202 | kp_clearentry = 217, 203 | kp_binary = 218, 204 | kp_octal = 219, 205 | kp_decimal = 220, 206 | kp_hexadecimal = 221, 207 | lctrl = 224, 208 | lshift = 225, 209 | lalt = 226, 210 | lgui = 227, 211 | rctrl = 228, 212 | rshift = 229, 213 | ralt = 230, 214 | rgui = 231, 215 | mode = 257, 216 | audionext = 258, 217 | audioprev = 259, 218 | audiostop = 260, 219 | audioplay = 261, 220 | audiomute = 262, 221 | mediaselect = 263, 222 | www = 264, 223 | mail = 265, 224 | calculator = 266, 225 | computer = 267, 226 | ac_search = 268, 227 | ac_home = 269, 228 | ac_back = 270, 229 | ac_forward = 271, 230 | ac_stop = 272, 231 | ac_refresh = 273, 232 | ac_bookmarks = 274, 233 | brightnessdown = 275, 234 | brightnessup = 276, 235 | displayswitch = 277, 236 | kbdillumtoggle = 278, 237 | kbdillumdown = 279, 238 | kbdillumup = 280, 239 | eject = 281, 240 | sleep = 282, 241 | app1 = 283, 242 | app2 = 284, 243 | audiorewind = 285, 244 | audiofastforward = 286, 245 | num_keys = 287, 246 | }; 247 | -------------------------------------------------------------------------------- /aya/src/math/axis.zig: -------------------------------------------------------------------------------- 1 | pub const Axis = enum(u8) { 2 | x, 3 | y, 4 | }; 5 | -------------------------------------------------------------------------------- /aya/src/math/edge.zig: -------------------------------------------------------------------------------- 1 | pub const Edge = enum(u8) { 2 | right, 3 | left, 4 | top, 5 | bottom, 6 | 7 | pub fn opposing(self: Edge) Edge { 8 | return switch (self) { 9 | .right => .left, 10 | .left => .right, 11 | .top => .bottom, 12 | .bottom => .top, 13 | }; 14 | } 15 | 16 | pub fn horizontal(self: Edge) bool { 17 | return self == .right or self == .left; 18 | } 19 | 20 | pub fn vertical(self: Edge) bool { 21 | return self == .top or self == .bottom; 22 | } 23 | 24 | pub fn max(self: Edge) bool { 25 | return self == .right or self == .bottom; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /aya/src/math/math.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub const pi = std.math.pi; 4 | pub const pi_over_2 = std.math.pi / 2.0; 5 | 6 | pub const Vec2 = @import("vec2.zig").Vec2; 7 | pub const Vec3 = @import("vec3.zig").Vec3; 8 | pub const Vec4 = @import("vec4.zig").Vec4; 9 | pub const Rect = @import("rect.zig").Rect; 10 | pub const RectI = @import("rect.zig").RectI; 11 | pub const Mat32 = @import("mat32.zig").Mat32; 12 | pub const Mat4 = @import("mat4.zig").Mat4; 13 | pub const Color = @import("color.zig").Color; 14 | pub const Quad = @import("quad.zig").Quad; 15 | pub const Edge = @import("edge.zig").Edge; 16 | pub const Axis = @import("axis.zig").Axis; 17 | 18 | pub const rand = @import("rand.zig"); 19 | 20 | /// Converts degrees to radian 21 | pub fn toRadians(deg: anytype) @TypeOf(deg) { 22 | return pi * deg / 180.0; 23 | } 24 | 25 | /// Converts radian to degree 26 | pub fn toDegrees(rad: anytype) @TypeOf(rad) { 27 | return 180.0 * rad / pi; 28 | } 29 | 30 | pub fn isEven(val: anytype) bool { 31 | std.debug.assert(@typeInfo(@TypeOf(val)) == .Int or @typeInfo(@TypeOf(val)) == .ComptimeInt); 32 | return @mod(val, 2) == 0; 33 | } 34 | 35 | pub fn ifloor(comptime T: type, val: f32) T { 36 | return @as(T, @intFromFloat(@floor(val))); 37 | } 38 | 39 | pub fn iclamp(x: i32, a: i32, b: i32) i32 { 40 | return @max(a, @min(b, x)); 41 | } 42 | 43 | // returns true if val is between start and end 44 | pub fn between(val: anytype, start: anytype, end: anytype) bool { 45 | return start <= val and val <= end; 46 | } 47 | 48 | pub fn repeat(t: f32, len: f32) f32 { 49 | return t - std.math.floor(t / len) * len; 50 | } 51 | 52 | pub fn pingpong(t: f32, len: f32) f32 { 53 | const tt = repeat(t, len * 2); 54 | return len - @abs(tt - len); 55 | } 56 | 57 | test "test math.rand" { 58 | rand.seed(0); 59 | 60 | try std.testing.expect(rand.int(i32) >= 0); 61 | 62 | try std.testing.expect(rand.range(i32, 5, 10) >= 5); 63 | try std.testing.expect(rand.range(i32, 5, 10) < 10); 64 | 65 | try std.testing.expect(rand.range(u32, 5, 10) >= 5); 66 | try std.testing.expect(rand.range(u32, 5, 10) < 10); 67 | 68 | try std.testing.expect(rand.range(f32, 5.0, 10.0) >= 5); 69 | try std.testing.expect(rand.range(f32, 5.0, 10.0) < 10); 70 | 71 | try std.testing.expect(rand.uintLessThan(u32, 5) < 5); 72 | 73 | try std.testing.expect(isEven(666)); 74 | } 75 | -------------------------------------------------------------------------------- /aya/src/math/quad.zig: -------------------------------------------------------------------------------- 1 | const Vec2 = @import("vec2.zig").Vec2; 2 | const Rect = @import("rect.zig").Rect; 3 | const RectI = @import("rect.zig").RectI; 4 | 5 | pub const Quad = struct { 6 | img_w: f32, 7 | img_h: f32, 8 | positions: [4]Vec2 = undefined, 9 | uvs: [4]Vec2 = undefined, 10 | 11 | pub fn init(x: f32, y: f32, width: f32, height: f32, img_w: f32, img_h: f32) Quad { 12 | var q = Quad{ 13 | .img_w = img_w, 14 | .img_h = img_h, 15 | }; 16 | q.setViewport(x, y, width, height); 17 | 18 | return q; 19 | } 20 | 21 | pub fn setViewportRect(self: *Quad, viewport: Rect) void { 22 | self.setViewport(viewport.x, viewport.y, viewport.w, viewport.h); 23 | } 24 | 25 | pub fn setViewportRectI(self: *Quad, viewport: RectI) void { 26 | self.setViewport(@as(f32, @floatFromInt(viewport.x)), @as(f32, @floatFromInt(viewport.y)), @as(f32, @floatFromInt(viewport.w)), @as(f32, @floatFromInt(viewport.h))); 27 | } 28 | 29 | pub fn setViewport(self: *Quad, x: f32, y: f32, width: f32, height: f32) void { 30 | self.positions[0] = Vec2{ .x = 0, .y = 0 }; // tl 31 | self.positions[1] = Vec2{ .x = width, .y = 0 }; // tr 32 | self.positions[2] = Vec2{ .x = width, .y = height }; // br 33 | self.positions[3] = Vec2{ .x = 0, .y = height }; // bl 34 | 35 | // squeeze texcoords in by 128th of a pixel to avoid bleed 36 | const w_tol = (1.0 / self.img_w) / 128.0; 37 | const h_tol = (1.0 / self.img_h) / 128.0; 38 | 39 | const inv_w = 1.0 / self.img_w; 40 | const inv_h = 1.0 / self.img_h; 41 | 42 | self.uvs[0] = Vec2{ .x = x * inv_w + w_tol, .y = y * inv_h + h_tol }; 43 | self.uvs[1] = Vec2{ .x = (x + width) * inv_w - w_tol, .y = y * inv_h + h_tol }; 44 | self.uvs[2] = Vec2{ .x = (x + width) * inv_w - w_tol, .y = (y + height) * inv_h - h_tol }; 45 | self.uvs[3] = Vec2{ .x = x * inv_w + w_tol, .y = (y + height) * inv_h - h_tol }; 46 | } 47 | 48 | /// sets the Quad to be the full size of the texture 49 | pub fn setFill(self: *Quad, img_w: f32, img_h: f32) void { 50 | self.setImageDimensions(img_w, img_h); 51 | self.setViewport(0, 0, img_w, img_h); 52 | } 53 | 54 | pub fn setImageDimensions(self: *Quad, w: f32, h: f32) void { 55 | self.img_w = w; 56 | self.img_h = h; 57 | } 58 | }; 59 | 60 | test "quad tests" { 61 | var q1 = Quad.init(0, 0, 50, 50, 600, 400); 62 | q1.setImageDimensions(600, 400); 63 | q1.setViewportRect(.{ .x = 0, .y = 0, .w = 50, .h = 0 }); 64 | } 65 | -------------------------------------------------------------------------------- /aya/src/math/rand.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Color = @import("color.zig").Color; 3 | 4 | var rng = std.rand.DefaultPrng.init(0x12345678); 5 | 6 | pub fn seed(new_seed: u64) void { 7 | rng.seed(new_seed); 8 | } 9 | 10 | pub fn boolean() bool { 11 | return rng.random().boolean(); 12 | } 13 | 14 | /// Returns a random int `i` such that `0 <= i <= maxInt(T)` 15 | pub fn int(comptime T: type) T { 16 | return rng.random().int(T); 17 | } 18 | 19 | /// Return a floating point value evenly distributed in the range [0, 1). 20 | pub fn float(comptime T: type) T { 21 | return rng.random().float(T); 22 | } 23 | 24 | pub fn color() Color { 25 | return Color.fromBytes(range(u8, 0, 255), range(u8, 0, 255), range(u8, 0, 255), 255); 26 | } 27 | 28 | /// Returns an evenly distributed random integer `at_least <= i < less_than`. 29 | pub fn range(comptime T: type, at_least: T, less_than: T) T { 30 | if (@typeInfo(T) == .Int) { 31 | return rng.random().intRangeLessThanBiased(T, at_least, less_than); 32 | } else if (@typeInfo(T) == .Float) { 33 | return at_least + rng.random().float(T) * (less_than - at_least); 34 | } 35 | unreachable; 36 | } 37 | 38 | /// Returns an evenly distributed random unsigned integer `0 <= i < less_than`. 39 | pub fn uintLessThan(comptime T: type, less_than: T) T { 40 | return rng.random().uintLessThanBiased(T, less_than); 41 | } 42 | 43 | /// returns true if the next random is less than percent. Percent should be between 0 and 1 44 | pub fn chance(percent: f32) bool { 45 | return rng.random().float(f32) < percent; 46 | } 47 | 48 | pub fn choose(comptime T: type, first: T, second: T) T { 49 | if (rng.random().int(u1) == 0) return first; 50 | return second; 51 | } 52 | 53 | pub fn choose3(comptime T: type, first: T, second: T, third: T) T { 54 | return switch (rng.random().int(u2)) { 55 | 0 => first, 56 | 1 => second, 57 | 2 => third, 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /aya/src/math/rect.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Edge = @import("edge.zig").Edge; 3 | const math = @import("math.zig"); 4 | 5 | pub const Rect = struct { 6 | x: f32 = 0, 7 | y: f32 = 0, 8 | w: f32 = 0, 9 | h: f32 = 0, 10 | 11 | pub fn right(self: Rect) f32 { 12 | return self.x + self.w; 13 | } 14 | 15 | pub fn left(self: Rect) f32 { 16 | return self.x; 17 | } 18 | 19 | pub fn top(self: Rect) f32 { 20 | return self.y; 21 | } 22 | 23 | pub fn bottom(self: Rect) f32 { 24 | return self.y + self.h; 25 | } 26 | 27 | pub fn center(self: Rect) math.Vec2 { 28 | return .{ .x = self.x + self.w / 2, .y = self.y + self.h / 2 }; 29 | } 30 | 31 | pub fn contains(self: Rect, x: f32, y: f32) bool { 32 | return self.x <= x and x < self.right() and self.y <= y and y < self.bottom(); 33 | } 34 | 35 | pub fn intersects(self: Rect, other: Rect) bool { 36 | return other.left() < self.right() and self.left() < other.right() and 37 | other.top() < self.bottom() and self.top() < other.bottom(); 38 | } 39 | 40 | pub fn contract(self: Rect, horiz: f32, vert: f32) Rect { 41 | var rect = self; 42 | rect.x += horiz; 43 | rect.y += vert; 44 | rect.w -= horiz * 2; 45 | rect.h -= vert * 2; 46 | return rect; 47 | } 48 | 49 | pub fn expand(self: Rect, horiz: f32, vert: f32) Rect { 50 | return self.contract(-horiz, -vert); 51 | } 52 | 53 | pub fn unionRect(self: Rect, r2: Rect) Rect { 54 | var res = Rect{}; 55 | res.x = @min(self.x, r2.x); 56 | res.y = @min(self.y, r2.y); 57 | res.w = @max(self.right(), r2.right()) - res.x; 58 | res.h = @max(self.bottom(), r2.bottom()) - res.y; 59 | return res; 60 | } 61 | }; 62 | 63 | pub const RectI = struct { 64 | x: i32 = 0, 65 | y: i32 = 0, 66 | w: i32 = 0, 67 | h: i32 = 0, 68 | 69 | pub fn init(x: i32, y: i32, w: i32, h: i32) RectI { 70 | return .{ .x = x, .y = y, .w = w, .h = h }; 71 | } 72 | 73 | pub fn right(self: RectI) i32 { 74 | return self.x + self.w; 75 | } 76 | 77 | pub fn left(self: RectI) i32 { 78 | return self.x; 79 | } 80 | 81 | pub fn top(self: RectI) i32 { 82 | return self.y; 83 | } 84 | 85 | pub fn bottom(self: RectI) i32 { 86 | return self.y + self.h; 87 | } 88 | 89 | pub fn centerX(self: RectI) i32 { 90 | return self.x + @divTrunc(self.w, 2); 91 | } 92 | 93 | pub fn centerY(self: RectI) i32 { 94 | return self.y + @divTrunc(self.h, 2); 95 | } 96 | 97 | pub fn halfRect(self: RectI, edge: Edge) RectI { 98 | return switch (edge) { 99 | .top => RectI{ .x = self.x, .y = self.y, .w = self.w, .h = @divTrunc(self.h, 2) }, 100 | .bottom => RectI{ .x = self.x, .y = self.y + @divTrunc(self.h, 2), .w = self.w, .h = @divTrunc(self.h, 2) }, 101 | .left => RectI{ .x = self.x, .y = self.y, .w = @divTrunc(self.w, 2), .h = self.h }, 102 | .right => RectI{ .x = self.x + @divTrunc(self.w, 2), .y = self.y, .w = @divTrunc(self.w, 2), .h = self.h }, 103 | }; 104 | } 105 | 106 | pub fn contract(self: *RectI, horiz: i32, vert: i32) void { 107 | self.x += horiz; 108 | self.y += vert; 109 | self.w -= horiz * 2; 110 | self.h -= vert * 2; 111 | } 112 | 113 | pub fn expandEdge(self: *RectI, edge: Edge, move_x: i32) void { 114 | const amt = @as(i32, @intCast(@abs(move_x))); 115 | 116 | switch (edge) { 117 | .top => { 118 | self.y -= amt; 119 | self.h += amt; 120 | }, 121 | .bottom => { 122 | self.h += amt; 123 | }, 124 | .left => { 125 | self.x -= amt; 126 | self.w += amt; 127 | }, 128 | .right => { 129 | self.w += amt; 130 | }, 131 | } 132 | } 133 | 134 | pub fn side(self: RectI, edge: Edge) i32 { 135 | return switch (edge) { 136 | .left => self.x, 137 | .right => self.x + self.w, 138 | .top => self.y, 139 | .bottom => self.y + self.h, 140 | }; 141 | } 142 | 143 | pub fn contains(self: RectI, x: i32, y: i32) bool { 144 | return self.x <= x and x < self.right() and self.y <= y and y < self.bottom(); 145 | } 146 | 147 | pub fn unionRect(self: RectI, r2: RectI) RectI { 148 | var res = RectI{}; 149 | res.x = @min(self.x, r2.x); 150 | res.y = @min(self.y, r2.y); 151 | res.w = @max(self.right(), r2.right()) - res.x; 152 | res.h = @max(self.bottom(), r2.bottom()) - res.y; 153 | return res; 154 | } 155 | 156 | pub fn unionPoint(self: RectI, x: i32, y: i32) RectI { 157 | return self.unionRect(.{ .x = x, .y = y }); 158 | } 159 | }; 160 | -------------------------------------------------------------------------------- /aya/src/math/vec2.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const math = std.math; 3 | 4 | pub const Vec2 = extern struct { 5 | x: f32 = 0, 6 | y: f32 = 0, 7 | 8 | pub fn init(x: f32, y: f32) Vec2 { 9 | return .{ .x = x, .y = y }; 10 | } 11 | 12 | fn getField(vec: Vec2, comptime index: comptime_int) f32 { 13 | switch (index) { 14 | 0 => return vec.x, 15 | 1 => return vec.y, 16 | else => @compileError("index out of bounds!"), 17 | } 18 | } 19 | 20 | pub fn angleToVec(radians: f32, length: f32) Vec2 { 21 | return .{ .x = math.cos(radians) * length, .y = math.sin(radians) * length }; 22 | } 23 | 24 | pub fn orthogonal(self: Vec2) Vec2 { 25 | return .{ .x = -self.y, .y = self.x }; 26 | } 27 | 28 | pub fn add(self: Vec2, other: Vec2) Vec2 { 29 | return .{ .x = self.x + other.x, .y = self.y + other.y }; 30 | } 31 | 32 | pub fn subtract(self: Vec2, other: Vec2) Vec2 { 33 | return .{ .x = self.x - other.x, .y = self.y - other.y }; 34 | } 35 | 36 | pub fn mul(self: Vec2, other: Vec2) Vec2 { 37 | return .{ .x = self.x * other.x, .y = self.y * other.y }; 38 | } 39 | 40 | pub fn scale(self: *Vec2, s: f32) void { 41 | self.x *= s; 42 | self.y *= s; 43 | } 44 | 45 | pub fn clamp(self: Vec2, min: Vec2, max: Vec2) Vec2 { 46 | return .{ .x = math.clamp(self.x, min.x, max.x), .y = math.clamp(self.y, min.y, max.y) }; 47 | } 48 | 49 | /// snaps the Vec2 parts to the nearest snap value. ex: 53 snapTo 5 would be 55. 50 | pub fn snapTo(self: Vec2, snap: f32) Vec2 { 51 | if (snap <= 0) return self; 52 | return .{ .x = @round(self.x / snap) * snap, .y = @round(self.y / snap) * snap }; 53 | } 54 | 55 | pub fn angleBetween(self: Vec2, to: Vec2) f32 { 56 | return math.atan2(f32, to.y - self.y, to.x - self.x); 57 | } 58 | 59 | pub fn distanceSq(self: Vec2, v: Vec2) f32 { 60 | const v1 = self.x - v.x; 61 | const v2 = self.y - v.y; 62 | return v1 * v1 + v2 * v2; 63 | } 64 | 65 | pub fn distance(self: Vec2, v: Vec2) f32 { 66 | return math.sqrt(self.distanceSq(v)); 67 | } 68 | 69 | pub fn perpindicular(self: Vec2, v: Vec2) Vec2 { 70 | return .{ .x = -1 * (v.y - self.y), .y = v.x - self.x }; 71 | } 72 | }; 73 | 74 | test "vec2 tests" { 75 | const v = Vec2{ .x = 1, .y = 5 }; 76 | const v2 = v.orthogonal(); 77 | const v_orth = Vec2{ .x = -5, .y = 1 }; 78 | 79 | std.testing.expectEqual(v2, v_orth); 80 | std.testing.expect(math.approxEq(f32, -2.55, v.angleBetween(v2), 0.01)); 81 | std.testing.expect(math.approxEq(f32, 52, v.distanceSq(v2), 0.01)); 82 | std.testing.expect(math.approxEq(f32, 7.21, v.distance(v2), 0.01)); 83 | std.testing.expect(math.approxEq(f32, -6, v.perpindicular(v2).y, 0.01)); 84 | } 85 | -------------------------------------------------------------------------------- /aya/src/math/vec3.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub const Vec3 = extern struct { 4 | x: f32 = 0, 5 | y: f32 = 0, 6 | z: f32 = 0, 7 | 8 | pub const zero = init(0, 0, 0); 9 | pub const up = init(0, 1, 0); 10 | 11 | pub fn init(x: f32, y: f32, z: f32) Vec3 { 12 | return .{ .x = x, .y = y, .z = z }; 13 | } 14 | 15 | pub fn lengthSq(a: Vec3) f32 { 16 | return Vec3.dot(a, a); 17 | } 18 | 19 | pub fn length(a: Vec3) f32 { 20 | return std.math.sqrt(a.lengthSq()); 21 | } 22 | 23 | pub fn dot(a: Vec3, b: Vec3) f32 { 24 | var result: f32 = 0; 25 | inline for (@typeInfo(Vec3).Struct.fields) |fld| { 26 | result += @field(a, fld.name) * @field(b, fld.name); 27 | } 28 | return result; 29 | } 30 | 31 | pub fn scale(a: Vec3, b: f32) Vec3 { 32 | var result: Vec3 = undefined; 33 | inline for (@typeInfo(Vec3).Struct.fields) |fld| { 34 | @field(result, fld.name) = @field(a, fld.name) * b; 35 | } 36 | return result; 37 | } 38 | 39 | pub fn cross(a: Vec3, b: Vec3) Vec3 { 40 | return .{ 41 | .x = a.y * b.z - a.z * b.y, 42 | .y = a.z * b.x - a.x * b.z, 43 | .z = a.x * b.y - a.y * b.x, 44 | }; 45 | } 46 | 47 | pub fn normalize(vec: Vec3) Vec3 { 48 | return vec.scale(1.0 / vec.length()); 49 | } 50 | 51 | pub fn sub(a: Vec3, b: Vec3) Vec3 { 52 | var result: Vec3 = undefined; 53 | inline for (@typeInfo(Vec3).Struct.fields) |fld| { 54 | @field(result, fld.name) = @field(a, fld.name) - @field(b, fld.name); 55 | } 56 | return result; 57 | } 58 | 59 | pub fn add(a: Vec3, b: Vec3) Vec3 { 60 | var result: Vec3 = undefined; 61 | inline for (@typeInfo(Vec3).Struct.fields) |fld| { 62 | @field(result, fld.name) = @field(a, fld.name) + @field(b, fld.name); 63 | } 64 | return result; 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /aya/src/math/vec4.zig: -------------------------------------------------------------------------------- 1 | pub const Vec4 = extern struct { 2 | x: f32 = 0, 3 | y: f32 = 0, 4 | z: f32 = 0, 5 | w: f32 = 0, 6 | 7 | pub fn asArray(self: Vec4) [4]f32 { 8 | return [_]f32{ self.x, self.y, self.z, self.w }; 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /aya/src/mem/allocator.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub const c_allocator = &c_allocator_state; 4 | var c_allocator_state = std.mem.Allocator{ 5 | .allocFn = cAlloc, 6 | .resizeFn = cResize, 7 | }; 8 | 9 | fn cAlloc(allocator: std.mem.Allocator, len: usize, ptr_align: u29, len_align: u29, ret_addr: usize) ![]u8 { 10 | std.debug.assert(ptr_align <= @alignOf(c_longdouble)); 11 | const ptr = @as([*]u8, @ptrCast(std.c.malloc(len) orelse return error.OutOfMemory)); 12 | 13 | if (len_align == 0) { 14 | return ptr[0..len]; 15 | } 16 | 17 | return ptr[0..std.mem.alignBackwardAnyAlign(len, len_align)]; 18 | } 19 | 20 | fn cResize(self: std.mem.Allocator, old_mem: []u8, buf_align: u29, new_len: usize, len_align: u29, ret_addr: usize) std.mem.Allocator.Error!usize { 21 | if (new_len == 0) { 22 | std.c.free(old_mem.ptr); 23 | return 0; 24 | } 25 | 26 | if (new_len <= old_mem.len) { 27 | _ = std.c.realloc(old_mem.ptr, new_len) orelse return error.OutOfMemory; 28 | return std.mem.alignAllocLen(old_mem.len, new_len, len_align); 29 | } 30 | 31 | return error.OutOfMemory; 32 | } 33 | -------------------------------------------------------------------------------- /aya/src/mem/mem.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const ScratchAllocator = @import("scratch_allocator.zig").ScratchAllocator; 3 | 4 | pub const SdlBufferStream = @import("sdl_stream.zig").SdlBufferStream; 5 | 6 | // temp allocator is a ring buffer so memory doesnt need to be freed 7 | pub var tmp_allocator: std.mem.Allocator = undefined; 8 | var tmp_allocator_instance: ScratchAllocator = undefined; 9 | 10 | pub const allocator = @import("sdl_allocator.zig").sdl_allocator; 11 | 12 | // optionally use the GPA 13 | // var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 14 | // pub const allocator = gpa.allocator(); 15 | 16 | pub fn initTmpAllocator() void { 17 | tmp_allocator_instance = ScratchAllocator.init(allocator); 18 | tmp_allocator = tmp_allocator_instance.allocator(); 19 | } 20 | 21 | /// Compares two slices and returns whether they are equal up to the index of the smallest slice. 22 | pub fn eqlSub(comptime T: type, base: []const T, b: []const T) bool { 23 | if (base.ptr == b.ptr) return true; 24 | for (base, 0..) |item, index| { 25 | if (b[index] != item) return false; 26 | } 27 | return true; 28 | } 29 | 30 | /// Copy all of source into dest at position 0. dest.len must be >= source.len + 1. 31 | pub fn copyZ(comptime T: type, dest: []T, source: []const T) void { 32 | @setRuntimeSafety(false); 33 | std.debug.assert(dest.len >= source.len); 34 | for (source, 0..) |s, i| 35 | dest[i] = s; 36 | dest[source.len] = 0; 37 | } 38 | -------------------------------------------------------------------------------- /aya/src/mem/scratch_allocator.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const Allocator = mem.Allocator; 4 | 5 | pub const ScratchAllocator = struct { 6 | backup_allocator: Allocator, 7 | end_index: usize, 8 | buffer: []u8, 9 | 10 | pub fn init(backing_allocator: Allocator) ScratchAllocator { 11 | const scratch_buffer = backing_allocator.alloc(u8, 2 * 1024 * 1024) catch unreachable; 12 | 13 | return ScratchAllocator{ 14 | .backup_allocator = backing_allocator, 15 | .buffer = scratch_buffer, 16 | .end_index = 0, 17 | }; 18 | } 19 | 20 | pub fn allocator(self: *ScratchAllocator) Allocator { 21 | return .{ 22 | .ptr = self, 23 | .vtable = &.{ 24 | .alloc = alloc, 25 | .resize = Allocator.noResize, 26 | .free = Allocator.noFree, 27 | }, 28 | }; 29 | // return Allocator.init(self, alloc, Allocator.NoResize(ScratchAllocator).noResize, Allocator.NoOpFree(ScratchAllocator).noOpFree); 30 | } 31 | 32 | // fn alloc(self: *ScratchAllocator, n: usize, ptr_align: u29, _: u29, _: usize) std.mem.Allocator.Error![]u8 { 33 | fn alloc(ctx: *anyopaque, n: usize, log2_ptr_align: u8, ra: usize) ?[*]u8 { 34 | const self = @as(*ScratchAllocator, @ptrCast(@alignCast(ctx))); 35 | _ = ra; 36 | 37 | const ptr_align = @as(usize, 1) << @as(Allocator.Log2Align, @intCast(log2_ptr_align)); 38 | const addr = @intFromPtr(self.buffer.ptr) + self.end_index; 39 | const adjusted_addr = mem.alignForward(usize, addr, ptr_align); 40 | const adjusted_index = self.end_index + (adjusted_addr - addr); 41 | const new_end_index = adjusted_index + n; 42 | 43 | if (new_end_index > self.buffer.len) { 44 | // if more memory is requested then we have in our buffer leak like a sieve! 45 | if (n > self.buffer.len) { 46 | std.debug.print("\n---------\nwarning: tmp allocated more than is in our temp allocator. This memory WILL leak!\n--------\n", .{}); 47 | // return self.backup_allocator.alloc(allocator, n, ptr_align, len_align, ret_addr); 48 | return null; 49 | } 50 | 51 | const result = self.buffer[0..n]; 52 | self.end_index = n; 53 | return result.ptr; 54 | } 55 | const result = self.buffer[adjusted_index..new_end_index]; 56 | self.end_index = new_end_index; 57 | 58 | return result.ptr; 59 | } 60 | }; 61 | 62 | test "scratch allocator" { 63 | var allocator_instance = ScratchAllocator.init(@import("mem.zig").allocator); 64 | 65 | var slice = try allocator_instance.allocator.alloc(*i32, 100); 66 | std.testing.expect(slice.len == 100); 67 | 68 | _ = try allocator_instance.allocator.create(i32); 69 | 70 | slice = try allocator_instance.allocator.realloc(slice, 20000); 71 | std.testing.expect(slice.len == 20000); 72 | allocator_instance.allocator.free(slice); 73 | } 74 | -------------------------------------------------------------------------------- /aya/src/mem/sdl_allocator.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const sdl = @import("sdl"); 3 | 4 | pub const sdl_allocator = std.mem.Allocator{ 5 | .ptr = undefined, 6 | .vtable = &sdlAllocator_vtable, 7 | }; 8 | 9 | const sdlAllocator_vtable = std.mem.Allocator.VTable{ 10 | .alloc = sdlAlloc, 11 | .resize = sdlResize, 12 | .free = sdlFree, 13 | }; 14 | 15 | fn sdlAlloc(_: *anyopaque, len: usize, ptr_align: u8, len_align: usize) ?[*]u8 { 16 | std.debug.assert(ptr_align <= @alignOf(c_longdouble)); 17 | const ptr = @as([*]u8, @ptrCast(sdl.SDL_malloc(len))); 18 | 19 | if (len_align == 0) { 20 | return ptr; 21 | } 22 | 23 | return ptr; 24 | } 25 | 26 | fn sdlResize(_: *anyopaque, old_mem: []u8, _: u8, new_len: usize, len_align: usize) bool { 27 | _ = len_align; 28 | if (new_len == 0) { 29 | sdl.SDL_free(old_mem.ptr); 30 | return false; 31 | } 32 | 33 | if (new_len <= old_mem.len) { 34 | //std.mem.alignAllocLen(old_mem.len, new_len, len_align); 35 | return true; 36 | } 37 | 38 | return false; 39 | } 40 | 41 | fn sdlFree(_: *anyopaque, buf: []u8, _: u8, _: usize) void { 42 | sdl.SDL_free(buf.ptr); 43 | } 44 | 45 | test "sdl allocator" { 46 | var slice = try sdl_allocator.alloc(*i32, 100); 47 | std.testing.expect(slice.len == 100); 48 | 49 | var float = try sdl_allocator.create(f32); 50 | float.* = 666.66; 51 | std.testing.expectEqual(float.*, 666.66); 52 | sdl_allocator.destroy(float); 53 | } 54 | -------------------------------------------------------------------------------- /aya/src/mem/sdl_stream.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("../../aya.zig"); 3 | const sdl = @import("sdl"); 4 | const io = std.io; 5 | 6 | pub const SdlBufferStream = struct { 7 | rw: [*c]sdl.SDL_RWops, 8 | 9 | pub const ReadError = error{}; 10 | pub const WriteError = error{}; 11 | pub const Reader = io.Reader(*SdlBufferStream, ReadError, read); 12 | pub const Writer = io.Writer(*SdlBufferStream, WriteError, write); 13 | 14 | pub fn init(file: []const u8, mode: enum { read, write }) SdlBufferStream { 15 | const c_file = aya.mem.tmp_allocator.dupeZ(u8, file) catch unreachable; 16 | var rw = sdl.SDL_RWFromFile(c_file, if (mode == .read) "rb" else "w"); 17 | 18 | return .{ .rw = rw }; 19 | } 20 | 21 | pub fn deinit(self: SdlBufferStream) void { 22 | _ = sdl.SDL_RWclose(self.rw); 23 | } 24 | 25 | pub fn reader(self: *SdlBufferStream) Reader { 26 | return .{ .context = self }; 27 | } 28 | 29 | pub fn writer(self: *SdlBufferStream) Writer { 30 | return .{ .context = self }; 31 | } 32 | 33 | pub fn read(self: *SdlBufferStream, dest: []u8) !usize { 34 | return sdl.SDL_RWread(self.rw, dest.ptr, 1, dest.len); 35 | } 36 | 37 | pub fn write(self: *SdlBufferStream, bytes: []const u8) !usize { 38 | return sdl.SDL_RWwrite(self.rw, bytes.ptr, 1, bytes.len); 39 | } 40 | }; 41 | 42 | test "SdlBufferStream output" { 43 | aya.mem.initTmpAllocator(); 44 | var buf = SdlBufferStream.init("/Users/desaro/Desktop/poop.txt", .write); 45 | const stream = buf.writer(); 46 | 47 | try stream.print("{}{}!", .{ "Hello", "World" }); 48 | buf.deinit(); 49 | 50 | const written = try aya.fs.read(std.testing.allocator, "/Users/desaro/Desktop/poop.txt"); 51 | std.testing.expectEqualSlices(u8, "HelloWorld!", written); 52 | std.testing.allocator.free(written); 53 | } 54 | -------------------------------------------------------------------------------- /aya/src/physics/colliders.zig: -------------------------------------------------------------------------------- 1 | const math = @import("../math/math.zig"); 2 | const CollisionFilter = @import("collision_filter.zig").CollisionFilter; 3 | 4 | pub const Collider = struct { 5 | filter: CollisionFilter, 6 | trigger: bool = false, 7 | kind: ColliderKind, 8 | 9 | pub fn init(kind: ColliderKind) Collider { 10 | return .{ 11 | .filter = CollisionFilter.init(), 12 | .kind = kind, 13 | }; 14 | } 15 | 16 | pub fn bounds(self: Collider) math.Rect { 17 | _ = self; 18 | return .{}; 19 | } 20 | }; 21 | 22 | pub const ColliderKind = union(enum) { 23 | box: BoxCollider, 24 | circle: CircleCollider, 25 | polygon: PolygonCollider, 26 | }; 27 | 28 | pub const BoxCollider = struct { 29 | x: f32, 30 | y: f32, 31 | w: f32, 32 | h: f32, 33 | 34 | pub fn init(x: f32, y: f32, w: f32, h: f32) BoxCollider { 35 | return .{ .x = x, .y = y, .w = w, .h = h }; 36 | } 37 | }; 38 | 39 | pub const CircleCollider = struct { 40 | x: f32, 41 | y: f32, 42 | r: f32, 43 | }; 44 | 45 | pub const PolygonCollider = struct { 46 | x: f32, 47 | y: f32, 48 | verts: []math.Vec2, 49 | }; 50 | -------------------------------------------------------------------------------- /aya/src/physics/collision_filter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | /// Optional Group info: 4 | /// - If the value in both objects is equal and positive, the objects always collide. 5 | /// - If the value in both objects is equal and negative, the objects never collide. 6 | pub const CollisionFilter = struct { 7 | // bit mask describing which layers this object belongs to 8 | category: u32 = 0xffffffff, 9 | // bit mask describing which layers this object can collide with 10 | mask: u32 = 0xffffffff, 11 | // optional override for the bit mask checks 12 | group: i32 = 0, 13 | 14 | pub fn init() CollisionFilter { 15 | return .{}; 16 | } 17 | 18 | pub fn collidesWith(self: CollisionFilter, other: CollisionFilter) bool { 19 | if (self.group > 0 and self.group == other.group) { 20 | return true; 21 | } 22 | 23 | if (self.group < 0 and self.group == other.group) { 24 | return false; 25 | } 26 | 27 | return (self.category & other.mask) != 0 and (other.category & self.mask) != 0; 28 | } 29 | }; 30 | 31 | test "collidesWith" { 32 | var a = CollisionFilter.init(); 33 | var b = CollisionFilter.init(); 34 | 35 | std.testing.expect(a.collidesWith(b)); 36 | std.testing.expect(b.collidesWith(a)); 37 | 38 | a.category = 0xff000000; 39 | b.mask = 0x00ff0000; 40 | std.testing.expect(!a.collidesWith(b)); 41 | 42 | a.category = 0x0f000000; 43 | b.mask = 0x0f000000; 44 | std.testing.expect(a.collidesWith(b)); 45 | 46 | a.group = -1; 47 | b.group = -1; 48 | std.testing.expect(!a.collidesWith(b)); 49 | 50 | a.group = 1; 51 | b.group = 1; 52 | std.testing.expect(a.collidesWith(b)); 53 | } 54 | -------------------------------------------------------------------------------- /aya/src/physics/multi_hash_map.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("../../aya.zig"); 3 | 4 | pub fn MultiHashMap(comptime K: type, comptime V: type) type { 5 | return struct { 6 | const Self = @This(); 7 | map: std.AutoHashMap(K, std.ArrayList(V)), 8 | allocator: std.mem.Allocator, 9 | 10 | pub fn init(allocator: ?std.mem.Allocator) Self { 11 | const alloc = allocator orelse aya.mem.allocator; 12 | 13 | return .{ 14 | .map = std.AutoHashMap(K, std.ArrayList(V)).init(alloc), 15 | .allocator = alloc, 16 | }; 17 | } 18 | 19 | pub fn deinit(self: *Self) void { 20 | var iter = self.map.iterator(); 21 | while (iter.next()) |item| { 22 | item.value.deinit(); 23 | } 24 | self.map.deinit(); 25 | } 26 | 27 | pub fn append(self: *Self, key: K, value: V) void { 28 | var res = self.map.getOrPut(key) catch unreachable; 29 | if (!res.found_existing) { 30 | res.entry.value = std.ArrayList(V).init(self.allocator); 31 | } 32 | _ = res.entry.value.append(value) catch unreachable; 33 | } 34 | 35 | pub fn get(self: Self, key: K) ?std.ArrayList(V) { 36 | return self.map.get(key); 37 | } 38 | }; 39 | } 40 | 41 | test { 42 | var map = MultiHashMap(u64, i32).init(std.testing.allocator); 43 | defer map.deinit(); 44 | 45 | map.append(6, -6); 46 | 47 | var bucket6 = map.get(6).?; 48 | std.testing.expectEqual(bucket6.items.len, 1); 49 | std.testing.expectEqual(bucket6.items[0], -6); 50 | } 51 | -------------------------------------------------------------------------------- /aya/src/physics/physics.zig: -------------------------------------------------------------------------------- 1 | pub const Collider = @import("colliders.zig").Collider; 2 | pub const ColliderKind = @import("colliders.zig").ColliderKind; 3 | pub const BoxCollider = @import("colliders.zig").BoxCollider; 4 | pub const CircleCollider = @import("colliders.zig").CircleCollider; 5 | pub const PolygonCollider = @import("colliders.zig").PolygonCollider; 6 | pub const CollisionFilter = @import("collision_filter.zig").CollisionFilter; 7 | 8 | pub const MultiHashMap = @import("multi_hash_map.zig").MultiHashMap; 9 | pub const SpatialHash = @import("spatial_hash.zig").SpatialHash; 10 | -------------------------------------------------------------------------------- /aya/src/physics/spatial_hash.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("../../aya.zig"); 3 | const physics = @import("physics.zig"); 4 | 5 | // stores a mapping of the hashed position -> list of id's in the cell 6 | const CellMap = physics.MultiHashMap(u64, u32); 7 | 8 | pub const SpatialHash = struct { 9 | cells: CellMap, 10 | colliders: std.AutoHashMap(u32, physics.Collider), 11 | cell_size: u32, 12 | inv_cell_size: f32, 13 | id_counter: u32 = 0, 14 | bounds: aya.math.RectI = .{}, 15 | allocator: std.mem.Allocator, 16 | 17 | pub fn init(allocator: ?std.mem.Allocator, cell_size: u32) SpatialHash { 18 | const alloc = allocator orelse aya.mem.allocator; 19 | 20 | return .{ 21 | .cells = CellMap.init(alloc), 22 | .colliders = std.AutoHashMap(u32, physics.Collider).init(alloc), 23 | .cell_size = cell_size, 24 | .inv_cell_size = 1 / @as(f32, @floatFromInt(cell_size)), 25 | .allocator = alloc, 26 | }; 27 | } 28 | 29 | pub fn deinit(self: *SpatialHash) void { 30 | self.cells.deinit(); 31 | self.colliders.deinit(); 32 | } 33 | 34 | fn expandBounds(self: *SpatialHash, x: i32, y: i32, x1: i32, y1: i32) void { 35 | // expand our bounds to encompass the new collider 36 | if (!self.bounds.contains(x, y)) { 37 | self.bounds = self.bounds.unionPoint(x, y); 38 | } 39 | if (!self.bounds.contains(x1, y1)) { 40 | self.bounds = self.bounds.unionPoint(x1, y1); 41 | } 42 | } 43 | 44 | /// gets the cell x,y values for a world-space x,y value 45 | fn cellCoords(self: SpatialHash, x: f32, y: f32) struct { x: i32, y: i32 } { 46 | return .{ 47 | .x = @as(i32, @intFromFloat(@trunc(x * self.inv_cell_size))), 48 | .y = @as(i32, @intFromFloat(@trunc(y * self.inv_cell_size))), 49 | }; 50 | } 51 | 52 | pub fn add(self: *SpatialHash, collider: physics.Collider) u32 { 53 | const id = self.id_counter; 54 | self.id_counter += 1; 55 | 56 | const bounds = collider.bounds(); 57 | const tl = self.cellCoords(bounds.x, bounds.y); 58 | const br = self.cellCoords(bounds.right(), bounds.bottom()); 59 | 60 | // expand our bounds to encompass the new collider 61 | self.expandBounds(tl.x, tl.y, br.x, br.y); 62 | 63 | return id; 64 | } 65 | }; 66 | 67 | test { 68 | var hash = SpatialHash.init(std.testing.allocator, 150); 69 | defer hash.deinit(); 70 | 71 | const circle = physics.Collider.init(physics.ColliderKind{ 72 | .circle = .{ 73 | .x = 10, 74 | .y = 10, 75 | .r = 20, 76 | }, 77 | }); 78 | const circle_id = hash.add(circle); 79 | _ = circle_id; 80 | } 81 | -------------------------------------------------------------------------------- /aya/src/tilemap/collision_iterator.zig: -------------------------------------------------------------------------------- 1 | const aya = @import("../../aya.zig"); 2 | const tilemap = aya.tilemap; 3 | const math = aya.math; 4 | 5 | pub const CollisionIterator = struct { 6 | is_h: bool, 7 | first_primary: i32, 8 | last_primary: i32, 9 | prim_incr: i32, 10 | first_secondary: i32, 11 | last_secondary: i32, 12 | secondary_incr: i32, 13 | primary: i32, 14 | secondary: i32, 15 | 16 | const Vec2I = struct { x: i32, y: i32 }; 17 | 18 | pub fn init(map: *tilemap.Map, bounds: math.RectI, edge: math.Edge) CollisionIterator { 19 | const is_h = edge.horizontal(); 20 | const prim_axis = if (is_h) math.Axis.x else math.Axis.y; 21 | const op_axis = if (prim_axis == .x) math.Axis.y else math.Axis.x; 22 | 23 | const op_dir = edge.opposing(); 24 | const frst_prim = worldToTile(map, bounds.side(op_dir), prim_axis); 25 | const lst_prim = worldToTile(map, bounds.side(edge), prim_axis); 26 | const prim_incr: i32 = if (edge.max()) 1 else -1; 27 | 28 | const min = worldToTile(map, if (is_h) bounds.top() else bounds.left(), op_axis); 29 | const mid = worldToTile(map, if (is_h) bounds.centerY() else bounds.centerX(), op_axis); 30 | const max = worldToTile(map, if (is_h) bounds.bottom() else bounds.right(), op_axis); 31 | 32 | const is_pos = mid - min < max - mid; 33 | const frst_secondary = if (is_pos) min else max; 34 | const lst_secondary = if (!is_pos) min else max; 35 | const secondary_incr: i32 = if (is_pos) 1 else -1; 36 | 37 | return .{ 38 | .is_h = is_h, 39 | .first_primary = frst_prim, 40 | .last_primary = lst_prim, 41 | .prim_incr = prim_incr, 42 | .first_secondary = frst_secondary, 43 | .last_secondary = lst_secondary, 44 | .secondary_incr = secondary_incr, 45 | .primary = frst_prim, 46 | .secondary = frst_secondary - secondary_incr, 47 | }; 48 | } 49 | 50 | pub fn next(self: *CollisionIterator) ?Vec2I { 51 | // increment the inner loop 52 | self.secondary += self.secondary_incr; 53 | if (self.secondary != self.last_secondary + self.secondary_incr) { 54 | return self.current(); 55 | } 56 | 57 | // reset the inner loop 58 | self.secondary = self.first_secondary; 59 | 60 | // increment the outer loop 61 | self.primary += self.prim_incr; 62 | if (self.primary == self.last_primary + self.prim_incr) { 63 | return null; 64 | } 65 | 66 | return self.current(); 67 | } 68 | 69 | fn current(self: CollisionIterator) Vec2I { 70 | if (self.is_h) { 71 | return .{ .x = self.primary, .y = self.secondary }; 72 | } 73 | return .{ .x = self.secondary, .y = self.primary }; 74 | } 75 | }; 76 | 77 | fn worldToTile(map: *tilemap.Map, pos: i32, axis: math.Axis) i32 { 78 | return if (axis == .x) map.worldToTileX(@as(f32, @floatFromInt(pos))) else map.worldToTileY(@as(f32, @floatFromInt(pos))); 79 | } 80 | -------------------------------------------------------------------------------- /aya/src/tilemap/renderer.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("../../aya.zig"); 3 | const gfx = aya.gfx; 4 | const tilemap = @import("tilemap.zig"); 5 | 6 | const Map = tilemap.Map; 7 | 8 | const flipped_h: i32 = 0x08000000; 9 | const flipped_v: i32 = 0x04000000; 10 | const flipped_d: i32 = 0x02000000; 11 | 12 | const TileRenderInfo = struct { 13 | id: i32, 14 | rot: f32 = 0, 15 | sx: f32 = 1, 16 | sy: f32 = 1, 17 | ox: f32 = 0, 18 | oy: f32 = 0, 19 | 20 | pub fn init(id: tilemap.TileId, tile_size: i32) TileRenderInfo { 21 | var info = TileRenderInfo{ .id = id & ~(flipped_h | flipped_v | flipped_d) }; 22 | 23 | const flip_h = (id & flipped_h) != 0; 24 | const flip_v = (id & flipped_v) != 0; 25 | 26 | // deal with flipping/rotating if necessary 27 | if (id > flipped_d) { 28 | // set the origin based on the tile_size if we are rotated 29 | info.ox = @as(f32, @floatFromInt(tile_size)) / 2.0; 30 | info.oy = @as(f32, @floatFromInt(tile_size)) / 2.0; 31 | 32 | if (flip_h) info.sx *= -1; 33 | if (flip_v) info.sy *= -1; 34 | 35 | if ((id & flipped_d) != 0) { 36 | if (flip_h and flip_v) { 37 | info.rot = aya.math.pi_over_2; 38 | info.sy *= -1; 39 | } else if (flip_h) { 40 | info.rot = -aya.math.pi_over_2; 41 | info.sy *= -1; 42 | } else if (flip_v) { 43 | info.rot = aya.math.pi_over_2; 44 | info.sx *= -1; 45 | } else { 46 | info.rot = -aya.math.pi_over_2; 47 | info.sx *= -1; 48 | } 49 | } 50 | } 51 | 52 | return info; 53 | } 54 | 55 | pub fn transformMatrix(self: TileRenderInfo, x: f32, y: f32) aya.math.Mat32 { 56 | return aya.math.Mat32.initTransform(.{ .x = x, .y = y, .angle = self.rot, .sx = self.sx, .sy = self.sy, .ox = self.ox, .oy = self.oy }); 57 | } 58 | }; 59 | 60 | /// Renders a layer into an AtlasBatch and returns it 61 | pub fn renderTileLayerIntoAtlasBatch(map: *Map, layer: tilemap.TileLayer, texture: gfx.Texture) gfx.AtlasBatch { 62 | var batch = gfx.AtlasBatch.init(null, texture, layer.totalNonEmptyTiles()) catch unreachable; 63 | renderTileLayer(map, layer, texture, &batch); 64 | return batch; 65 | } 66 | 67 | /// Renders all visible Tile and Object layers 68 | pub fn render(map: *Map, texture: gfx.Texture) void { 69 | for (map.tile_layers) |tl| { 70 | if (tl.visible) { 71 | renderTileLayer(map, tl, texture, null); 72 | } 73 | } 74 | 75 | for (map.object_layers) |ol| { 76 | if (ol.visible) { 77 | renderObjectLayer(ol); 78 | } 79 | } 80 | } 81 | 82 | pub fn renderTileLayer(map: *Map, layer: tilemap.TileLayer, texture: gfx.Texture, atlas_batch: ?*gfx.AtlasBatch) void { 83 | var i: usize = 0; 84 | var y: usize = 0; 85 | while (y < layer.height) : (y += 1) { 86 | var x: usize = 0; 87 | while (x < layer.width) : (x += 1) { 88 | var tile_id = layer.tiles[i]; 89 | i += 1; 90 | if (tile_id >= 0) { 91 | const info = TileRenderInfo.init(tile_id, map.tile_size); 92 | const vp = map.tilesets[0].viewportForTile(info.id); 93 | 94 | const tx = @as(f32, @floatFromInt(@as(i32, @intCast(x)) * map.tile_size)) + info.ox; 95 | const ty = @as(f32, @floatFromInt(@as(i32, @intCast(y)) * map.tile_size)) + info.oy; 96 | const mat = info.transformMatrix(tx, ty); 97 | 98 | if (atlas_batch) |batch| { 99 | _ = batch.addViewport(vp, mat, aya.math.Color.white); 100 | } else { 101 | aya.draw.texViewport(texture, vp, mat); 102 | } 103 | } 104 | } 105 | } 106 | } 107 | 108 | pub fn renderObjectLayer(layer: tilemap.ObjectLayer) void { 109 | for (layer.objects) |obj| { 110 | switch (obj.shape) { 111 | .box => aya.draw.hollowRect(.{ .x = obj.x, .y = obj.y }, obj.w, obj.h, 1, aya.math.Color.yellow), 112 | .circle, .ellipse => { 113 | const rad = obj.w / 2; 114 | aya.draw.circle(.{ .x = obj.x + rad, .y = obj.y + rad }, rad, 5, 1, aya.math.Color.yellow); 115 | }, 116 | .point => aya.draw.point(.{ .x = obj.x, .y = obj.y }, 6, aya.math.Color.yellow), 117 | .polygon => std.debug.print("polygon draw not implemented\n", .{}), 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /aya/src/utils/array.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("../../aya.zig"); 3 | 4 | // provides methods like ArrayList making working with slices more ergonomic. 5 | // assumes memory is managed by aya.mem.allocator. 6 | 7 | pub fn insert(comptime T: type, slice: *[]T, n: usize, item: T) !void { 8 | slice.* = try aya.mem.allocator.realloc(slice.*, slice.len + 1); 9 | std.mem.copyBackwards(T, slice.*[n + 1 .. slice.len], slice.*[n .. slice.len - 1]); 10 | slice.*[n] = item; 11 | } 12 | 13 | pub fn append(comptime T: type, slice: *[]T, item: T) !void { 14 | slice.* = try aya.mem.allocator.realloc(slice.*, slice.len + 1); 15 | slice.*[slice.len - 1] = item; 16 | } 17 | 18 | pub fn orderedRemove(comptime T: type, slice: *[]T, i: usize) T { 19 | const newlen = slice.len - 1; 20 | if (newlen == i) return pop(T, slice); 21 | 22 | const old_item = slice.*[i]; 23 | for (slice.*[i..newlen], 0..) |*b, j| b.* = slice.*[i + 1 + j]; 24 | _ = aya.mem.allocator.resize(slice.*, newlen); 25 | return old_item; 26 | } 27 | 28 | pub fn pop(comptime T: type, slice: *[]T) T { 29 | const val = slice.*[slice.len - 1]; 30 | _ = aya.mem.allocator.resize(slice.*, slice.len - 1); 31 | return val; 32 | } 33 | -------------------------------------------------------------------------------- /aya/src/utils/fixed_list.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | /// fixed size array wrapper that provides ArrayList-like semantics. Appending more items than fit in the 4 | /// list ignores the item and logs a warning. Use the FixedList.len field to get the actual number of items present. 5 | pub fn FixedList(comptime T: type, comptime len: usize) type { 6 | return struct { 7 | const Self = @This(); 8 | 9 | items: [len]T = undefined, 10 | len: usize = 0, 11 | 12 | pub const Iterator = struct { 13 | list: Self, 14 | index: usize = 0, 15 | 16 | pub fn next(self: *Iterator) ?T { 17 | if (self.index == self.list.len) return null; 18 | var next_item = self.list.items[self.index]; 19 | self.index += 1; 20 | return next_item; 21 | } 22 | }; 23 | 24 | pub fn init() Self { 25 | return Self{ .items = [_]T{0} ** len }; 26 | } 27 | 28 | pub fn initWithValue(comptime value: T) Self { 29 | return Self{ .items = [_]T{value} ** len }; 30 | } 31 | 32 | pub fn append(self: *Self, item: T) void { 33 | if (self.len == self.items.len) { 34 | std.debug.print("attemped to append to a full FixedList\n", .{}); 35 | return; 36 | } 37 | self.items[self.len] = item; 38 | self.len += 1; 39 | } 40 | 41 | pub fn clear(self: *Self) void { 42 | self.len = 0; 43 | } 44 | 45 | pub fn pop(self: *Self) T { 46 | var item = self.items[self.len - 1]; 47 | self.len -= 1; 48 | return item; 49 | } 50 | 51 | pub fn contains(self: Self, value: T) bool { 52 | return self.indexOf(value) != null; 53 | } 54 | 55 | pub fn indexOf(self: Self, value: T) ?usize { 56 | var i: usize = 0; 57 | while (i < self.len) : (i += 1) { 58 | if (self.items[i] == value) return i; 59 | } 60 | return null; 61 | } 62 | 63 | /// Removes the element at the specified index and returns it. The empty slot is filled from the end of the list. 64 | pub fn swapRemove(self: *Self, i: usize) T { 65 | if (self.len - 1 == i) return self.pop(); 66 | 67 | const old_item = self.items[i]; 68 | self.items[i] = self.pop(); 69 | return old_item; 70 | } 71 | 72 | pub fn iter(self: Self) Iterator { 73 | return Iterator{ .list = self }; 74 | } 75 | }; 76 | } 77 | 78 | test "fixed list" { 79 | var list = FixedList(u32, 4).init(); 80 | list.append(4); 81 | std.testing.expectEqual(list.len, 1); 82 | 83 | list.append(46); 84 | list.append(146); 85 | list.append(4456); 86 | std.testing.expectEqual(list.len, 4); 87 | 88 | _ = list.pop(); 89 | std.testing.expectEqual(list.len, 3); 90 | 91 | list.clear(); 92 | std.testing.expectEqual(list.len, 0); 93 | } 94 | -------------------------------------------------------------------------------- /aya/tests.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | // include all files with tests 4 | comptime { 5 | _ = @import("src/fs.zig"); 6 | // _ = @import("src/input.zig"); // Zig bug: TODO buf_read_value_bytes union type 7 | 8 | _ = @import("src/mem/scratch_allocator.zig"); 9 | 10 | _ = @import("src/math/math.zig"); 11 | _ = @import("src/math/vec2.zig"); 12 | _ = @import("src/math/color.zig"); 13 | // _ = @import("src/math/mat32.zig"); 14 | 15 | // _ = @import("src/gfx/textures.zig"); // requires firing up a Window and sets up a Device in its test 16 | // _ = @import("src/gfx/gfx.zig"); // Zig bug: anything that imports aya fails due to debug.zig 17 | // _ = @import("src/gfx/shader.zig"); 18 | // _ = @import("src/gfx/buffers.zig"); 19 | // _ = @import("src/gfx/mesh.zig"); 20 | // _ = @import("src/gfx/batcher.zig"); 21 | // _ = @import("src/gfx/triangle_batcher.zig"); 22 | // _ = @import("src/gfx/atlas_batch.zig"); 23 | // _ = @import("src/gfx/offscreen_pass.zig"); // Zig bug: TODO buf_read_value_bytes union type 24 | 25 | _ = @import("src/utils/utils.zig"); 26 | 27 | _ = @import("src/physics/multi_hash_map.zig"); 28 | _ = @import("src/physics/spatial_hash.zig"); 29 | } 30 | -------------------------------------------------------------------------------- /editor/camera.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | const math = aya.math; 4 | const imgui = @import("imgui"); 5 | 6 | pub const Camera = struct { 7 | pos: math.Vec2 = .{}, 8 | window_size: math.Vec2 = .{}, 9 | zoom: f32 = 1, 10 | 11 | pub fn init() Camera { 12 | return .{}; 13 | } 14 | 15 | pub fn transMat(self: Camera) math.Mat32 { 16 | var window_half_size = self.window_size.mul(.{ .x = 0.5, .y = 0.5 }); 17 | var transform = math.Mat32.identity; 18 | 19 | var tmp = math.Mat32.identity; 20 | tmp.translate(-self.pos.x, -self.pos.y); 21 | transform = tmp.mul(transform); 22 | 23 | tmp = math.Mat32.identity; 24 | tmp.scale(self.zoom, self.zoom); 25 | transform = tmp.mul(transform); 26 | 27 | tmp = math.Mat32.identity; 28 | tmp.translate(window_half_size.x, window_half_size.y); 29 | transform = tmp.mul(transform); 30 | 31 | return transform; 32 | } 33 | 34 | pub fn clampZoom(self: *Camera) void { 35 | self.zoom = std.math.clamp(self.zoom, 0.5, 5); 36 | } 37 | 38 | pub fn screenToWorld(self: Camera, pos: math.Vec2) math.Vec2 { 39 | var inv_trans_mat = self.transMat().invert(); 40 | return inv_trans_mat.transformVec2(pos); 41 | } 42 | 43 | /// calls screenToWorld converting to ImVec2s 44 | pub fn igScreenToWorld(self: Camera, pos: imgui.ImVec2) imgui.ImVec2 { 45 | const tmp = self.screenToWorld(math.Vec2{ .x = pos.x, .y = pos.y }); 46 | return .{ .x = tmp.x, .y = tmp.y }; 47 | } 48 | 49 | pub fn bounds(self: Camera) math.Rect { 50 | var tl = self.screenToWorld(.{}); 51 | var br = self.screenToWorld(.{ .x = self.window_size.x, .y = self.window_size.y }); 52 | 53 | return math.Rect{ .x = tl.x, .y = tl.y, .w = br.x - tl.x, .h = br.y - tl.y }; 54 | } 55 | 56 | /// clamps the map to (0,0) - (width,height) with some optional padding around the outside 57 | pub fn clampToMap(self: *Camera, width: usize, height: usize, padding: f32) void { 58 | const bnds = self.bounds(); 59 | var half_screen = math.Vec2{ .x = bnds.w, .y = bnds.h }; 60 | half_screen.scale(0.5); 61 | half_screen = half_screen.subtract(.{ .x = padding, .y = padding }); 62 | 63 | const max = math.Vec2{ .x = @as(f32, @floatFromInt(width)) - half_screen.x, .y = @as(f32, @floatFromInt(height)) - half_screen.y }; 64 | // ensure we dont zoom out so far that our clamp becomes pointless 65 | if (half_screen.x > max.x or half_screen.y > max.y) { 66 | self.pos = half_screen; 67 | } else { 68 | self.pos = self.pos.clamp(half_screen, max); 69 | } 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /editor/colors.zig: -------------------------------------------------------------------------------- 1 | const aya = @import("aya"); 2 | const imgui = @import("imgui"); 3 | 4 | const Color = aya.math.Color; 5 | 6 | pub var ui_tint: imgui.ImVec4 = rgbaToVec4(135, 45, 176, 255); 7 | 8 | pub var brush_required: imgui.ImU32 = 0; 9 | pub var brush_negated: imgui.ImU32 = 0; 10 | pub var brush_selected: imgui.ImU32 = 0; 11 | 12 | pub var pattern_center: imgui.ImU32 = 0; 13 | pub var rule_result_selected_outline: imgui.ImU32 = 0; 14 | pub var rule_result_selected_fill: imgui.ImU32 = 0; 15 | 16 | pub var scene_toolbar_btn: imgui.ImU32 = aya.math.Color.aya.value; 17 | 18 | // TODO: these are duplicated in Tileset 19 | pub var brushes = [_]imgui.ImU32{ 20 | Color.fromRgbBytes(189, 63, 110).value, Color.fromRgbBytes(242, 165, 59).value, Color.fromRgbBytes(252, 234, 87).value, 21 | Color.fromRgbBytes(103, 223, 84).value, Color.fromRgbBytes(82, 172, 247).value, Color.fromRgbBytes(128, 118, 152).value, 22 | Color.fromRgbBytes(237, 127, 166).value, Color.fromRgbBytes(246, 205, 174).value, Color.fromRgbBytes(115, 45, 81).value, 23 | }; 24 | 25 | pub fn init() void { 26 | setDefaultImGuiStyle(); 27 | 28 | brush_required = rgbToU32(117, 249, 76); 29 | brush_negated = rgbToU32(235, 50, 35); 30 | brush_selected = rgbToU32(82, 172, 247); 31 | 32 | pattern_center = rgbToU32(255, 253, 84); 33 | rule_result_selected_outline = rgbToU32(116, 252, 253); 34 | rule_result_selected_fill = rgbaToU32(116, 252, 253, 100); 35 | } 36 | 37 | fn setDefaultImGuiStyle() void { 38 | var style = imgui.igGetStyle(); 39 | style.TabRounding = 2; 40 | style.FrameRounding = 4; 41 | style.WindowBorderSize = 1; 42 | style.WindowRounding = 0; 43 | style.WindowMenuButtonPosition = imgui.ImGuiDir_None; 44 | style.Colors[imgui.ImGuiCol_WindowBg] = imgui.ogColorConvertU32ToFloat4(rgbaToU32(25, 25, 25, 255)); 45 | style.Colors[imgui.ImGuiCol_TextSelectedBg] = imgui.ogColorConvertU32ToFloat4(rgbaToU32(66, 150, 250, 187)); 46 | style.Colors[imgui.ImGuiCol_PopupBg] = rgbaToVec4(20, 20, 20, 255); 47 | 48 | setTintColor(ui_tint); 49 | } 50 | 51 | pub fn setTintColor(color: imgui.ImVec4) void { 52 | var colors = &imgui.igGetStyle().Colors; 53 | colors[imgui.ImGuiCol_FrameBg] = hsvShiftColor(color, 0, 0, -0.2, colors[imgui.ImGuiCol_FrameBg].w); 54 | colors[imgui.ImGuiCol_FrameBgHovered] = hsvShiftColor(color, 0, -0.2, 0, colors[imgui.ImGuiCol_FrameBgHovered].w); 55 | colors[imgui.ImGuiCol_FrameBgActive] = hsvShiftColor(color, 0, -0.2, 0, colors[imgui.ImGuiCol_FrameBgActive].w); 56 | colors[imgui.ImGuiCol_Border] = hsvShiftColor(color, 0, 0, -0.2, colors[imgui.ImGuiCol_Border].w); 57 | 58 | const header = hsvShiftColor(color, 0, -0.2, 0, colors[imgui.ImGuiCol_Header].w); 59 | colors[imgui.ImGuiCol_Header] = header; 60 | colors[imgui.ImGuiCol_HeaderHovered] = hsvShiftColor(header, 0, 0, 0.1, colors[imgui.ImGuiCol_HeaderHovered].w); 61 | colors[imgui.ImGuiCol_HeaderActive] = hsvShiftColor(header, 0, 0, -0.1, colors[imgui.ImGuiCol_HeaderActive].w); 62 | 63 | colors[imgui.ImGuiCol_TitleBg] = hsvShiftColor(color, 0, 0.1, 0, colors[imgui.ImGuiCol_TitleBg].w); 64 | colors[imgui.ImGuiCol_TitleBgActive] = hsvShiftColor(color, 0, 0.1, 0, colors[imgui.ImGuiCol_TitleBgActive].w); 65 | 66 | const tab = hsvShiftColor(color, 0, 0.1, 0, 1.0); 67 | colors[imgui.ImGuiCol_Tab] = tab; 68 | colors[imgui.ImGuiCol_TabActive] = hsvShiftColor(tab, 0.05, 0.2, 0.2, colors[imgui.ImGuiCol_TabActive].w); 69 | colors[imgui.ImGuiCol_TabHovered] = hsvShiftColor(tab, 0.02, 0.1, 0.2, colors[imgui.ImGuiCol_TabHovered].w); 70 | colors[imgui.ImGuiCol_TabUnfocused] = hsvShiftColor(tab, 0, -0.1, 0, colors[imgui.ImGuiCol_TabUnfocused].w); 71 | colors[imgui.ImGuiCol_TabUnfocusedActive] = colors[imgui.ImGuiCol_TabActive]; 72 | 73 | const button = hsvShiftColor(color, -0.05, 0, 0, 1.0); 74 | colors[imgui.ImGuiCol_Button] = button; 75 | colors[imgui.ImGuiCol_ButtonHovered] = hsvShiftColor(button, 0, 0, 0.1, colors[imgui.ImGuiCol_ButtonHovered].w); 76 | colors[imgui.ImGuiCol_ButtonActive] = hsvShiftColor(button, 0, 0, -0.1, colors[imgui.ImGuiCol_ButtonActive].w); 77 | } 78 | 79 | pub fn hsvShiftColor(color: imgui.ImVec4, h_shift: f32, s_shift: f32, v_shift: f32, alpha: f32) imgui.ImVec4 { 80 | _ = alpha; 81 | var h: f32 = undefined; 82 | var s: f32 = undefined; 83 | var v: f32 = undefined; 84 | imgui.igColorConvertRGBtoHSV(color.x, color.y, color.z, &h, &s, &v); 85 | 86 | h += h_shift; 87 | s += s_shift; 88 | v += v_shift; 89 | 90 | var out_color = color; 91 | imgui.igColorConvertHSVtoRGB(h, s, v, &out_color.x, &out_color.y, &out_color.z); 92 | return out_color; 93 | } 94 | 95 | pub fn rgbToU32(r: i32, g: i32, b: i32) imgui.ImU32 { 96 | return aya.math.Color.fromI32(r, g, b, 255).value; 97 | } 98 | 99 | pub fn rgbaToU32(r: i32, g: i32, b: i32, a: i32) imgui.ImU32 { 100 | return aya.math.Color.fromI32(r, g, b, a).value; 101 | } 102 | 103 | pub fn rgbToVec4(r: i32, g: i32, b: i32) imgui.ImVec4 { 104 | return .{ .x = @as(f32, @floatFromInt(r)) / 255, .y = @as(f32, @floatFromInt(g)) / 255, .z = @as(f32, @floatFromInt(b)) / 255, .w = 1 }; 105 | } 106 | 107 | pub fn rgbaToVec4(r: i32, g: i32, b: i32, a: i32) imgui.ImVec4 { 108 | return .{ .x = @as(f32, @floatFromInt(r)) / 255, .y = @as(f32, @floatFromInt(g)) / 255, .z = @as(f32, @floatFromInt(b)) / 255, .w = @as(f32, @floatFromInt(a)) }; 109 | } 110 | -------------------------------------------------------------------------------- /editor/data/components.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | const math = aya.math; 4 | const data = @import("data.zig"); 5 | 6 | pub const Component = struct { 7 | id: u8, 8 | name: [25:0]u8 = undefined, 9 | props: []Property = &[_]Property{}, 10 | next_property_id: u8 = 0, 11 | 12 | pub fn init(id: u8, name: []const u8) Component { 13 | var comp = Component{ .id = id }; 14 | @memset(&comp.name, 0); 15 | aya.mem.copyZ(u8, &comp.name, name); 16 | return comp; 17 | } 18 | 19 | pub fn deinit(self: Component) void { 20 | for (self.props) |*prop| prop.deinit(); 21 | aya.mem.allocator.free(self.props); 22 | } 23 | 24 | pub fn addProperty(self: *Component, prop: PropertyValue) void { 25 | defer self.next_property_id += 1; 26 | aya.utils.array.append(Property, &self.props, Property.init(self.next_property_id, prop)) catch unreachable; 27 | } 28 | 29 | pub fn spawnInstance(self: Component) ComponentInstance { 30 | return ComponentInstance.init(self); 31 | } 32 | 33 | pub fn propertyWithId(self: @This(), id: u8) *Property { 34 | for (self.props) |*prop| { 35 | if (prop.id == id) return prop; 36 | } 37 | unreachable; 38 | } 39 | }; 40 | 41 | pub const Property = struct { 42 | id: u8, 43 | name: [25:0]u8, 44 | value: PropertyValue, 45 | 46 | pub fn init(id: u8, value: PropertyValue) Property { 47 | return .{ .id = id, .name = [_:0]u8{0} ** 25, .value = value }; 48 | } 49 | 50 | pub fn deinit(self: Property) void { 51 | // if we have allocated memory clean it up 52 | if (self.value == .enum_values) 53 | aya.mem.allocator.free(self.value.enum_values); 54 | } 55 | }; 56 | 57 | pub const PropertyValue = union(enum) { 58 | string: [25:0]u8, 59 | int: i32, 60 | float: f32, 61 | bool: bool, 62 | vec2: math.Vec2, 63 | enum_values: [][25]u8, // TODO: used to be `[][25:0]u8` before zig alloc broke and couldnt allocate it 64 | entity_link: u8, 65 | 66 | pub fn propertyInstanceValue(self: @This()) PropertyInstanceValue { 67 | return switch (self) { 68 | .string => |str| .{ .string = str }, 69 | .int => |i| .{ .int = i }, 70 | .float => |f| .{ .float = f }, 71 | .bool => |b| .{ .bool = b }, 72 | .vec2 => |v| .{ .vec2 = v }, 73 | .enum_values => .{ .enum_value = .{} }, 74 | .entity_link => |link| .{ .entity_link = .{ .entity = link } }, 75 | }; 76 | } 77 | }; 78 | 79 | pub const PropEnumValue = struct { index: u8 = 0 }; 80 | pub const PropLinkValue = struct { entity: u8 = 0 }; 81 | 82 | // if any new int-like values are needed they need to be wrapped in structs that have unique field names! 83 | pub const PropertyInstanceValue = union(enum) { 84 | string: [25:0]u8, 85 | int: i32, 86 | float: f32, 87 | bool: bool, 88 | vec2: math.Vec2, 89 | enum_value: PropEnumValue, 90 | entity_link: PropLinkValue, 91 | }; 92 | 93 | /// ComponentInstances represent the component's data on an Entity. Only the actual values are stored along with the Component.id 94 | /// so that the field names can be looked up and property add/delete can be identified and synced. 95 | pub const ComponentInstance = struct { 96 | component_id: u8, 97 | props: std.ArrayList(PropertyInstance), 98 | 99 | pub fn init(src_component: Component) ComponentInstance { 100 | var comp = ComponentInstance{ 101 | .component_id = src_component.id, 102 | .props = std.ArrayList(PropertyInstance).init(aya.mem.allocator), 103 | }; 104 | 105 | for (src_component.props) |prop| comp.addProperty(prop); 106 | 107 | return comp; 108 | } 109 | 110 | pub fn deinit(self: ComponentInstance) void { 111 | for (self.props.items) |*prop| prop.deinit(); 112 | self.props.deinit(); 113 | } 114 | 115 | pub fn clone(self: ComponentInstance, state: *data.AppState) ComponentInstance { 116 | var src_component = state.componentWithId(self.component_id); 117 | var comp = init(src_component.*); 118 | for (self.props.items, 0..) |prop, i| comp.props.items[i] = prop; 119 | return comp; 120 | } 121 | 122 | pub fn removeProperty(self: *@This(), property_id: u8) void { 123 | var id = for (self.props.items, 0..) |prop, i| { 124 | if (prop.property_id == property_id) { 125 | break i; 126 | } 127 | } else std.math.maxInt(usize); 128 | _ = self.props.orderedRemove(id); 129 | } 130 | 131 | pub fn addProperty(self: *@This(), prop: Property) void { 132 | self.props.append(PropertyInstance.init(prop.id, prop.value.propertyInstanceValue())) catch unreachable; 133 | } 134 | }; 135 | 136 | pub const PropertyInstance = struct { 137 | property_id: u8, 138 | value: PropertyInstanceValue, 139 | 140 | pub fn init(id: u8, value: PropertyInstanceValue) PropertyInstance { 141 | return .{ .property_id = id, .value = value }; 142 | } 143 | 144 | pub fn deinit(_: PropertyInstance) void {} 145 | }; 146 | -------------------------------------------------------------------------------- /editor/data/data.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | const math = aya.math; 4 | const imgui = @import("imgui"); 5 | 6 | pub const AppState = @import("app_state.zig").AppState; 7 | pub const Level = @import("app_state.zig").Level; 8 | pub const Tilemap = @import("tilemap.zig").Tilemap; 9 | pub const Tileset = @import("tileset.zig").Tileset; 10 | pub const Brushset = @import("brushset.zig").Brushset; 11 | 12 | pub const Rule = @import("rules.zig").Rule; 13 | pub const RuleSet = @import("rules.zig").RuleSet; 14 | pub const RuleTile = @import("rules.zig").RuleTile; 15 | 16 | // entity and built-in components 17 | pub const Entity = @import("entity.zig").Entity; 18 | pub const Transform = @import("entity.zig").Transform; 19 | pub const Sprite = @import("entity.zig").Sprite; 20 | pub const Collider = @import("entity.zig").Collider; 21 | pub const BoxCollider = @import("entity.zig").BoxCollider; 22 | pub const CircleCollider = @import("entity.zig").CircleCollider; 23 | 24 | pub const Component = @import("components.zig").Component; 25 | pub const Property = @import("components.zig").Property; 26 | pub const ComponentInstance = @import("components.zig").ComponentInstance; 27 | 28 | const Camera = @import("../camera.zig").Camera; 29 | pub const Point = struct { x: usize, y: usize }; 30 | 31 | pub const Size = struct { 32 | w: usize, 33 | h: usize, 34 | 35 | pub fn init(width: usize, height: usize) Size { 36 | return .{ .w = width, .h = height }; 37 | } 38 | }; 39 | 40 | /// convenience struct for housing a tile that saves the trouble of bitshifting for setting/getting upper bits 41 | pub const Tile = extern union { 42 | value: u16, 43 | comps: packed struct { 44 | tile_index: u12, 45 | reserved: u1, 46 | horizontal: u1, 47 | vertical: u1, 48 | diagonal: u1, 49 | }, 50 | 51 | pub fn init(value: u16) Tile { 52 | return .{ .value = value }; 53 | } 54 | 55 | pub fn flipH(self: *Tile) void { 56 | self.comps.horizontal = @intFromBool(self.comps.horizontal != 1); 57 | } 58 | 59 | pub fn flipV(self: *Tile) void { 60 | self.comps.vertical = @intFromBool(self.comps.vertical != 1); 61 | } 62 | 63 | pub fn flipD(self: *Tile) void { 64 | self.comps.diagonal = @intFromBool(self.comps.diagonal != 1); 65 | } 66 | }; 67 | 68 | pub const TileRenderInfo = struct { 69 | id: u16, 70 | rot: f32 = 0, 71 | sx: f32 = 1, 72 | sy: f32 = 1, 73 | ox: f32 = 0, 74 | oy: f32 = 0, 75 | 76 | pub fn init(tid: u16, tile_size: usize) TileRenderInfo { 77 | const tile = Tile{ .value = tid }; 78 | var info = TileRenderInfo{ .id = tile.comps.tile_index }; 79 | 80 | const flip_h = tile.comps.horizontal == 1; 81 | const flip_v = tile.comps.vertical == 1; 82 | 83 | // deal with flipping/rotating if necessary 84 | if (flip_h or flip_v or tile.comps.diagonal == 1) { 85 | // set the origin based on the tile_size if we are rotated 86 | info.ox = @as(f32, @floatFromInt(tile_size)) / 2.0; 87 | info.oy = @as(f32, @floatFromInt(tile_size)) / 2.0; 88 | 89 | if (flip_h) info.sx *= -1; 90 | if (flip_v) info.sy *= -1; 91 | 92 | if (tile.comps.diagonal == 1) { 93 | if (flip_h and flip_v) { 94 | info.rot = aya.math.pi_over_2; 95 | info.sy *= -1; 96 | } else if (flip_h) { 97 | info.rot = -aya.math.pi_over_2; 98 | info.sy *= -1; 99 | } else if (flip_v) { 100 | info.rot = aya.math.pi_over_2; 101 | info.sx *= -1; 102 | } else { 103 | info.rot = -aya.math.pi_over_2; 104 | info.sx *= -1; 105 | } 106 | } 107 | } 108 | 109 | return info; 110 | } 111 | 112 | pub fn transformMatrix(self: TileRenderInfo, x: f32, y: f32) math.Mat32 { 113 | return aya.math.Mat32.initTransform(.{ .x = x, .y = y, .angle = self.rot, .sx = self.sx, .sy = self.sy, .ox = self.ox, .oy = self.oy }); 114 | } 115 | 116 | /// tileset can be a Tileset or a Brushset 117 | pub fn draw(self: TileRenderInfo, tileset: anytype, position: math.Vec2) void { 118 | const vp = tileset.viewportForTile(self.id); 119 | 120 | const tx = position.x + self.ox; 121 | const ty = position.y + self.oy; 122 | const mat = self.transformMatrix(tx, ty); 123 | 124 | aya.draw.texViewport(tileset.tex, vp, mat); 125 | } 126 | }; 127 | -------------------------------------------------------------------------------- /editor/data/tilemap.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | const math = aya.math; 4 | const imgui = @import("imgui"); 5 | 6 | const data = @import("data.zig"); 7 | 8 | const Size = data.Size; 9 | const Point = data.Point; 10 | const Tileset = data.Tileset; 11 | const TileRenderInfo = data.TileRenderInfo; 12 | 13 | pub const Tilemap = struct { 14 | size: Size, 15 | data: []u16, 16 | 17 | pub fn init(size: Size) Tilemap { 18 | var tmp_data = aya.mem.allocator.alloc(u16, size.w * size.h) catch unreachable; 19 | @memset(tmp_data, 0); 20 | return .{ 21 | .size = size, 22 | .data = tmp_data, 23 | }; 24 | } 25 | 26 | pub fn initWithData(the_data: []const u16, size: Size) Tilemap { 27 | return .{ 28 | .size = size, 29 | .data = std.mem.dupe(aya.mem.allocator, u16, the_data) catch unreachable, 30 | }; 31 | } 32 | 33 | pub fn deinit(self: Tilemap) void { 34 | aya.mem.allocator.free(self.data); 35 | } 36 | 37 | pub fn clear(self: Tilemap) void { 38 | @memset(self.data, 0); 39 | } 40 | 41 | pub fn getTile(self: Tilemap, tile: Point) u16 { 42 | if (tile.x > self.size.w or tile.y > self.size.h) { 43 | return 0; 44 | } 45 | return self.data[tile.x + tile.y * self.size.w]; 46 | } 47 | 48 | pub fn setTile(self: Tilemap, tile: Point, value: u16) void { 49 | self.data[tile.x + tile.y * self.size.w] = value; 50 | } 51 | 52 | /// draws the tilemap. If map_data is passed in (for the final map in an auto tilemap) it will be used, else 53 | /// Tilemap.data will be used. tileset can be either a Tileset or a Brushset. 54 | pub fn draw(self: @This(), tileset: anytype, map_data: ?[]u16) void { 55 | var draw_data = map_data orelse self.data; 56 | 57 | var y: usize = 0; 58 | while (y < self.size.h) : (y += 1) { 59 | var x: usize = 0; 60 | while (x < self.size.w) : (x += 1) { 61 | const tile = draw_data[x + y * self.size.w]; 62 | if (tile == 0) continue; 63 | 64 | var info = TileRenderInfo.init(tile - 1, tileset.tile_size); 65 | info.draw(tileset, .{ .x = @as(f32, @floatFromInt(x * tileset.tile_size)), .y = @as(f32, @floatFromInt(y * tileset.tile_size)) }); 66 | } 67 | } 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /editor/layers/layer.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | const math = aya.math; 4 | const imgui = @import("imgui"); 5 | 6 | const root = @import("../main.zig"); 7 | const data = root.data; 8 | const AppState = data.AppState; 9 | pub const TilemapLayer = root.layers.TilemapLayer; 10 | pub const AutoTilemapLayer = root.layers.AutoTilemapLayer; 11 | pub const EntityLayer = root.layers.EntityLayer; 12 | const Size = data.Size; 13 | const Camera = @import("../camera.zig").Camera; 14 | 15 | pub const LayerType = enum(u8) { 16 | tilemap, 17 | auto_tilemap, 18 | entity, 19 | }; 20 | 21 | pub const Layer = union(LayerType) { 22 | tilemap: TilemapLayer, 23 | auto_tilemap: AutoTilemapLayer, 24 | entity: EntityLayer, 25 | 26 | pub fn init(layer_type: LayerType, layer_name: []const u8, size: Size, tile_size: usize) Layer { 27 | return switch (layer_type) { 28 | .tilemap => .{ 29 | .tilemap = TilemapLayer.init(layer_name, size, tile_size), 30 | }, 31 | .auto_tilemap => .{ 32 | .auto_tilemap = AutoTilemapLayer.init(layer_name, size, tile_size), 33 | }, 34 | .entity => .{ 35 | .entity = EntityLayer.init(layer_name, size), 36 | }, 37 | }; 38 | } 39 | 40 | pub fn deinit(self: @This()) void { 41 | switch (self) { 42 | .tilemap => |layer| layer.deinit(), 43 | .auto_tilemap => |layer| layer.deinit(), 44 | .entity => |layer| layer.deinit(), 45 | } 46 | } 47 | 48 | pub fn name(self: @This()) [25:0]u8 { 49 | return switch (self) { 50 | .tilemap => |layer| layer.name, 51 | .auto_tilemap => |layer| layer.name, 52 | .entity => |layer| layer.name, 53 | }; 54 | } 55 | 56 | pub fn setName(self: *@This(), new_name: []const u8) void { 57 | switch (self.*) { 58 | .tilemap => |*layer| aya.mem.copyZ(u8, &layer.name, new_name), 59 | .auto_tilemap => |*layer| aya.mem.copyZ(u8, &layer.name, new_name), 60 | .entity => |*layer| aya.mem.copyZ(u8, &layer.name, new_name), 61 | } 62 | } 63 | 64 | pub fn visible(self: @This()) bool { 65 | return switch (self) { 66 | .tilemap => |layer| layer.visible, 67 | .auto_tilemap => |layer| layer.visible, 68 | .entity => |layer| layer.visible, 69 | }; 70 | } 71 | 72 | pub fn setVisible(self: *@This(), is_visible: bool) void { 73 | switch (self.*) { 74 | .tilemap => |*layer| layer.visible = is_visible, 75 | .auto_tilemap => |*layer| layer.visible = is_visible, 76 | .entity => |*layer| layer.visible = is_visible, 77 | } 78 | } 79 | 80 | pub fn onFileDropped(self: *@This(), state: *AppState, file: [:0]const u8) void { 81 | switch (self.*) { 82 | .tilemap => |*layer| layer.onFileDropped(state, file), 83 | .auto_tilemap => |*layer| layer.onFileDropped(state, file), 84 | .entity => |*layer| layer.onFileDropped(state, file), 85 | } 86 | } 87 | 88 | /// used for doing the actual drawing of the layer as it appears in-game (and its associated imgui windows/popups), not the 89 | /// editing UI that is rendered in the Scene when this is the selected Layer 90 | pub fn draw(self: *@This(), state: *AppState, is_selected: bool) void { 91 | switch (self.*) { 92 | .tilemap => |*layer| layer.draw(state, is_selected), 93 | .auto_tilemap => |*layer| layer.draw(state, is_selected), 94 | .entity => |*layer| layer.draw(state, is_selected), 95 | } 96 | } 97 | 98 | /// used for the editing UI, called after all other drawing so it can render on top of everything. Called only for the selected Layer. 99 | /// Shortcut keys can be handled here. 100 | pub fn handleSceneInput(self: *@This(), state: *AppState, camera: Camera, mouse_world: imgui.ImVec2) void { 101 | switch (self.*) { 102 | .tilemap => |*layer| layer.handleSceneInput(state, camera, mouse_world), 103 | .auto_tilemap => |*layer| layer.handleSceneInput(state, camera, mouse_world), 104 | .entity => |*layer| layer.handleSceneInput(state, camera, mouse_world), 105 | } 106 | } 107 | }; 108 | -------------------------------------------------------------------------------- /editor/layers/layers.zig: -------------------------------------------------------------------------------- 1 | pub const Layer = @import("layer.zig").Layer; 2 | pub const LayerType = @import("layer.zig").LayerType; 3 | pub const TilemapLayer = @import("tilemap_layer.zig").TilemapLayer; 4 | pub const AutoTilemapLayer = @import("auto_tilemap_layer.zig").AutoTilemapLayer; 5 | pub const EntityLayer = @import("entity_layer.zig").EntityLayer; 6 | -------------------------------------------------------------------------------- /editor/utils.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | const imgui = @import("imgui"); 4 | const root = @import("root"); 5 | 6 | pub const Image = @import("utils/image.zig").Image; 7 | 8 | pub const known_folders = @import("utils/known-folders.zig"); 9 | pub const texture_packer = @import("utils/texture_packer.zig"); 10 | pub const file_picker = @import("utils/file_picker.zig"); 11 | 12 | const AppState = root.data.AppState; 13 | const Point = root.data.Point; 14 | const Tilemap = root.data.Tilemap; 15 | const Camera = @import("camera.zig").Camera; 16 | 17 | /// given a mouse position returns the tile under it 18 | pub fn tileIndexUnderMouse(state: *AppState, position: imgui.ImVec2, tile_size: usize, camera: Camera) ?Point { 19 | // mouse positions need to be subtracted from origin of content rect to get into screen space (window space really) 20 | const mouse_screen = position.subtract(imgui.ogGetCursorScreenPos()); 21 | return tileIndexUnderPos(state, camera.igScreenToWorld(mouse_screen), tile_size); 22 | } 23 | 24 | /// given a world-space position returns the tile under it or null if position is out of bounds 25 | pub fn tileIndexUnderPos(state: *AppState, position: imgui.ImVec2, tile_size: usize) ?Point { 26 | if (position.x < 0 or position.y < 0) return null; 27 | if (position.x > @as(f32, @floatFromInt(state.level.map_size.w * state.tile_size))) return null; 28 | if (position.y > @as(f32, @floatFromInt(state.level.map_size.h * state.tile_size))) return null; 29 | 30 | return Point{ .x = @divTrunc(@as(usize, @intFromFloat(position.x)), tile_size), .y = @divTrunc(@as(usize, @intFromFloat(position.y)), tile_size) }; 31 | } 32 | 33 | /// fill in all the tiles between the two mouse positions using bresenham's line algo 34 | pub fn bresenham(tilemap: *Tilemap, in_x1: f32, in_y1: f32, in_x2: f32, in_y2: f32, color: u16) void { 35 | var x1 = in_x1; 36 | var y1 = in_y1; 37 | var x2 = in_x2; 38 | var y2 = in_y2; 39 | 40 | const steep = @abs(y2 - y1) > @abs(x2 - x1); 41 | if (steep) { 42 | std.mem.swap(f32, &x1, &y1); 43 | std.mem.swap(f32, &x2, &y2); 44 | } 45 | 46 | if (x1 > x2) { 47 | std.mem.swap(f32, &x1, &x2); 48 | std.mem.swap(f32, &y1, &y2); 49 | } 50 | 51 | const dx: f32 = x2 - x1; 52 | const dy: f32 = @abs(y2 - y1); 53 | 54 | var err: f32 = dx / 2.0; 55 | var ystep: i32 = if (y1 < y2) 1 else -1; 56 | var y: i32 = @as(i32, @intFromFloat(y1)); 57 | 58 | const maxX: i32 = @as(i32, @intFromFloat(x2)); 59 | 60 | var x: i32 = @as(i32, @intFromFloat(x1)); 61 | while (x <= maxX) : (x += 1) { 62 | if (steep) { 63 | const index = @as(usize, @intCast(y)) + @as(usize, @intCast(x)) * tilemap.size.w; 64 | _ = index; 65 | tilemap.setTile(.{ .x = @as(usize, @intCast(y)), .y = @as(usize, @intCast(x)) }, color); 66 | } else { 67 | const index = @as(usize, @intCast(x)) + @as(usize, @intCast(y)) * tilemap.size.w; 68 | _ = index; 69 | tilemap.setTile(.{ .x = @as(usize, @intCast(x)), .y = @as(usize, @intCast(y)) }, color); 70 | } 71 | 72 | err -= dy; 73 | if (err < 0) { 74 | y += ystep; 75 | err += dx; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /editor/utils/image.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | const stb = @import("stb"); 4 | const Texture = aya.gfx.Texture; 5 | 6 | /// Image is a CPU side array of color data with some helper methods that can be used to prep data 7 | /// before creating a Texture 8 | pub const Image = struct { 9 | w: usize = 0, 10 | h: usize = 0, 11 | pixels: []u32, 12 | 13 | pub fn init(width: usize, height: usize) Image { 14 | return .{ .w = width, .h = height, .pixels = aya.mem.allocator.alloc(u32, width * height) catch unreachable }; 15 | } 16 | 17 | pub fn initFromFile(file: []const u8) Image { 18 | const image_contents = aya.fs.read(aya.mem.tmp_allocator, file) catch unreachable; 19 | 20 | var w: c_int = undefined; 21 | var h: c_int = undefined; 22 | var channels: c_int = undefined; 23 | const load_res = stb.stbi_load_from_memory(image_contents.ptr, @as(c_int, @intCast(image_contents.len)), &w, &h, &channels, 4); 24 | if (load_res == null) unreachable; 25 | defer stb.stbi_image_free(load_res); 26 | 27 | var img = init(@as(usize, @intCast(w)), @as(usize, @intCast(h))); 28 | var pixels = std.mem.bytesAsSlice(u32, load_res[0..@as(usize, @intCast(w * h * channels))]); 29 | for (pixels, 0..) |p, i| { 30 | img.pixels[i] = p; 31 | } 32 | 33 | return img; 34 | } 35 | 36 | pub fn deinit(self: Image) void { 37 | aya.mem.allocator.free(self.pixels); 38 | } 39 | 40 | pub fn fillRect(self: *Image, rect: aya.math.RectI, color: aya.math.Color) void { 41 | const x = @as(usize, @intCast(rect.x)); 42 | var y = @as(usize, @intCast(rect.y)); 43 | const w = @as(usize, @intCast(rect.w)); 44 | var h = @as(usize, @intCast(rect.h)); 45 | 46 | var data = self.pixels[x + y * self.w ..]; 47 | while (h > 0) : (h -= 1) { 48 | var i: usize = 0; 49 | while (i < w) : (i += 1) { 50 | data[i] = color.value; 51 | } 52 | 53 | y += 1; 54 | data = self.pixels[x + y * self.w ..]; 55 | } 56 | } 57 | 58 | pub fn blit(self: *Image, src: Image, x: usize, y: usize) void { 59 | var yy = y; 60 | var h = src.h; 61 | 62 | var data = self.pixels[x + yy * self.w ..]; 63 | var src_y: usize = 0; 64 | while (h > 0) : (h -= 1) { 65 | const src_row = src.pixels[src_y * src.w .. (src_y * src.w) + src.w]; 66 | std.mem.copy(u32, data, src_row); 67 | 68 | // next row and move our slice to it as well 69 | src_y += 1; 70 | yy += 1; 71 | data = self.pixels[x + yy * self.w ..]; 72 | } 73 | } 74 | 75 | /// resizes the Image taking the max dimension and constraining it to max_width_or_height 76 | pub fn resizeConstrainedToMaxSize(self: *Image, max_width_or_height: usize) void { 77 | const scale = @as(f32, @floatFromInt(max_width_or_height)) / @as(f32, @floatFromInt(@max(self.w, self.h))); 78 | self.resize(@as(usize, @intFromFloat(scale * @as(f32, @floatFromInt(self.w)))), @as(usize, @intFromFloat(scale * @as(f32, @floatFromInt(self.h))))); 79 | } 80 | 81 | /// extremely lossy image resizing, really useful only for quicky thumbnail creation 82 | pub fn resize(self: *Image, w: usize, h: usize) void { 83 | if (self.w == w and self.h == h) return; 84 | 85 | var img = init(w, h); 86 | const scale_w = @as(f32, @floatFromInt(self.w)) / @as(f32, @floatFromInt(w)); 87 | const scale_h = @as(f32, @floatFromInt(self.h)) / @as(f32, @floatFromInt(h)); 88 | 89 | var y: usize = 0; 90 | while (y < img.h) : (y += 1) { 91 | var x: usize = 0; 92 | while (x < img.w) : (x += 1) { 93 | const src_x = @as(usize, @intFromFloat(@as(f32, @floatFromInt(x)) * scale_w)); 94 | const src_y = @as(usize, @intFromFloat(@as(f32, @floatFromInt(y)) * scale_h)); 95 | img.pixels[x + y * img.w] = self.pixels[src_x + src_y * self.w]; 96 | } 97 | } 98 | 99 | self.deinit(); 100 | self.* = img; 101 | } 102 | 103 | pub fn asTexture(self: Image) Texture { 104 | return Texture.initWithData(u32, @as(i32, @intCast(self.w)), @as(i32, @intCast(self.h)), self.pixels); 105 | } 106 | 107 | pub fn save(self: Image, file: []const u8) void { 108 | var bytes = std.mem.sliceAsBytes(self.pixels); 109 | const file_posix = std.os.toPosixPath(file) catch unreachable; 110 | _ = stb.stbi_write_png(&file_posix, @as(c_int, @intCast(self.w)), @as(c_int, @intCast(self.h)), 4, bytes.ptr, @as(c_int, @intCast(self.w * 4))); 111 | } 112 | 113 | /// returns true if the image was loaded successfully 114 | pub fn getTextureSize(file: []const u8, w: *c_int, h: *c_int) bool { 115 | if (aya.fs.read(aya.mem.tmp_allocator, file)) |image_contents| { 116 | var comp: c_int = undefined; 117 | if (stb.stbi_info_from_memory(image_contents.ptr, @as(c_int, @intCast(image_contents.len)), w, h, &comp) == 1) { 118 | return true; 119 | } 120 | } else |err| { 121 | std.debug.print("fs.read failed: {}\n", .{err}); 122 | } 123 | 124 | return false; 125 | } 126 | }; 127 | -------------------------------------------------------------------------------- /editor/windows/timeline.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | const root = @import("../main.zig"); 4 | const imgui = @import("imgui"); 5 | 6 | pub fn draw(_: *root.AppState) void { 7 | imgui.igPushStyleColorU32(imgui.ImGuiCol_ModalWindowDimBg, root.colors.rgbaToU32(20, 20, 20, 200)); 8 | defer imgui.igPopStyleColor(1); 9 | 10 | imgui.ogSetNextWindowSize(.{ .x = 500, .y = -1 }, imgui.ImGuiCond_Always); 11 | var open: bool = true; 12 | if (imgui.igBeginPopupModal("Timeline Editor", &open, imgui.ImGuiWindowFlags_AlwaysAutoResize)) { 13 | defer imgui.igEndPopup(); 14 | 15 | imgui.igText("Timeline Editor"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /editor/windows/windows.zig: -------------------------------------------------------------------------------- 1 | pub const Scene = @import("scene.zig").Scene; 2 | 3 | pub const layers = @import("layers.zig"); 4 | pub const component_editor = @import("component_editor.zig"); 5 | pub const timeline = @import("timeline.zig"); 6 | pub const assets = @import("assets.zig"); 7 | -------------------------------------------------------------------------------- /examples/assets/ProggyTiny.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prime31/zig-aya/fd03a2cbddee6773f19d6ce849f6d1a60ed39324/examples/assets/ProggyTiny.ttf -------------------------------------------------------------------------------- /examples/assets/effects.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | const shaders = @import("shaders/shaders.zig"); 4 | 5 | const PostProcessor = aya.gfx.PostProcessor; 6 | const OffscreenPass = aya.gfx.OffscreenPass; 7 | 8 | pub const Sepia = struct { 9 | postprocessor: PostProcessor, 10 | shader: shaders.SepiaShader, 11 | 12 | pub fn deinit(self: @This()) void { 13 | self.shader.deinit(); 14 | } 15 | 16 | pub fn initialize(self: *@This(), data: anytype) void { 17 | _ = data; 18 | self.postprocessor = .{ .process = process }; 19 | self.shader = shaders.createSepiaShader(); 20 | self.shader.frag_uniform.sepia_tone = .{ .x = 1.2, .y = 1.0, .z = 0.8 }; 21 | } 22 | 23 | pub fn process(processor: *PostProcessor, pass: OffscreenPass, tex: aya.gfx.Texture) void { 24 | const self = processor.getParent(@This()); 25 | processor.blit(pass, tex, &self.shader.shader); 26 | } 27 | 28 | pub fn setTone(self: *@This(), tone: aya.math.Vec3) void { 29 | self.shader.frag_uniform.sepia_tone = tone; 30 | } 31 | }; 32 | 33 | pub const Vignette = struct { 34 | postprocessor: PostProcessor, 35 | shader: shaders.VignetteShader, 36 | 37 | pub fn deinit(self: @This()) void { 38 | self.shader.deinit(); 39 | } 40 | 41 | pub fn initialize(self: *@This(), data: anytype) void { 42 | _ = data; 43 | self.postprocessor = .{ .process = process }; 44 | self.shader = shaders.createVignetteShader(); 45 | self.shader.frag_uniform.radius = 1.2; 46 | self.shader.frag_uniform.power = 1; 47 | } 48 | 49 | pub fn process(processor: *PostProcessor, pass: OffscreenPass, tex: aya.gfx.Texture) void { 50 | const self = processor.getParent(@This()); 51 | processor.blit(pass, tex, &self.shader.shader); 52 | } 53 | 54 | pub fn setUniforms(self: *@This(), radius: f32, power: f32) void { 55 | _ = radius; 56 | _ = power; 57 | self.shader.frag_uniform.radius = 1.2; 58 | self.shader.frag_uniform.power = 1; 59 | } 60 | }; 61 | 62 | pub const PixelGlitch = struct { 63 | postprocessor: PostProcessor, 64 | shader: shaders.PixelGlitchShader, 65 | 66 | pub fn deinit(self: @This()) void { 67 | self.shader.deinit(); 68 | } 69 | 70 | pub fn initialize(self: *@This(), data: anytype) void { 71 | _ = data; 72 | self.postprocessor = .{ .process = process }; 73 | self.shader = shaders.createPixelGlitchShader(); 74 | 75 | const size = aya.window.size(); 76 | self.shader.frag_uniform.screen_size = .{ .x = @as(f32, @floatFromInt(size.w)), .y = @as(f32, @floatFromInt(size.h)) }; 77 | self.shader.frag_uniform.vertical_size = 0.5; 78 | self.shader.frag_uniform.horizontal_offset = 10; 79 | } 80 | 81 | pub fn process(processor: *PostProcessor, pass: OffscreenPass, tex: aya.gfx.Texture) void { 82 | const self = processor.getParent(@This()); 83 | processor.blit(pass, tex, &self.shader.shader); 84 | } 85 | 86 | pub fn setUniforms(self: *@This(), vertical_size: f32, horizontal_offset: f32) void { 87 | _ = vertical_size; 88 | _ = horizontal_offset; 89 | const size = aya.window.size(); 90 | self.params.screen_size = .{ .x = @as(f32, @floatFromInt(size.w)), .y = @as(f32, @floatFromInt(size.h)) }; 91 | self.shader.frag_uniform.vertical_size = 0.5; 92 | self.shader.frag_uniform.horizontal_offset = 10; 93 | } 94 | }; 95 | -------------------------------------------------------------------------------- /examples/assets/shaders/cube_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform sampler2D tex; 4 | 5 | layout(location = 0) out vec4 frag_color; 6 | in vec2 uv; 7 | in vec4 color; 8 | 9 | void main() 10 | { 11 | frag_color = texture(tex, uv) * color; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /examples/assets/shaders/cube_vs.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform vec4 CubeParamsVS[4]; 4 | layout(location = 0) in vec4 pos; 5 | out vec4 color; 6 | layout(location = 1) in vec4 color0; 7 | out vec2 uv; 8 | layout(location = 2) in vec2 texcoord0; 9 | 10 | void main() 11 | { 12 | gl_Position = mat4(CubeParamsVS[0], CubeParamsVS[1], CubeParamsVS[2], CubeParamsVS[3]) * pos; 13 | color = color0; 14 | uv = texcoord0; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /examples/assets/shaders/depth_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform vec4 DepthParamsFS[1]; 4 | layout(location = 0) out vec4 frag_color; 5 | in vec2 uv_out; 6 | in vec4 color_out; 7 | 8 | float linearizeDepth(float depth) 9 | { 10 | return ((2.0 * DepthParamsFS[0].x) * DepthParamsFS[0].y) / ((DepthParamsFS[0].y + DepthParamsFS[0].x) - (((depth * 2.0) - 1.0) * (DepthParamsFS[0].y - DepthParamsFS[0].x))); 11 | } 12 | 13 | void main() 14 | { 15 | float param = gl_FragCoord.z; 16 | float _61 = linearizeDepth(param) / DepthParamsFS[0].y; 17 | frag_color = vec4(_61, _61, _61, 1.0); 18 | } 19 | 20 | -------------------------------------------------------------------------------- /examples/assets/shaders/dissolve_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform vec4 DissolveParams[2]; 4 | uniform sampler2D main_tex; 5 | uniform sampler2D dissolve_tex; 6 | 7 | layout(location = 0) out vec4 frag_color; 8 | in vec2 uv_out; 9 | in vec4 color_out; 10 | 11 | vec4 effect(sampler2D tex, vec2 tex_coord, vec4 vert_color) 12 | { 13 | float _46 = DissolveParams[0].x + DissolveParams[0].y; 14 | vec4 _50 = texture(tex, tex_coord); 15 | vec4 _56 = texture(dissolve_tex, tex_coord); 16 | float _60 = 1.0 - _56.x; 17 | float _65 = _46 - DissolveParams[0].y; 18 | if (_60 < _65) 19 | { 20 | discard; 21 | } 22 | return mix(_50, _50 * mix(vec4(0.0, 0.0, 0.0, 1.0), DissolveParams[1], vec4(mix(1.0, 0.0, 1.0 - clamp(abs(_65 - _60) / DissolveParams[0].y, 0.0, 1.0)))), vec4(float(_60 < _46))); 23 | } 24 | 25 | void main() 26 | { 27 | vec2 param = uv_out; 28 | vec4 param_1 = color_out; 29 | vec4 _32 = effect(main_tex, param, param_1); 30 | frag_color = _32; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /examples/assets/shaders/instanced_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform sampler2D main_tex; 4 | 5 | layout(location = 0) out vec4 frag_color; 6 | in vec2 uv_out; 7 | in vec4 color_out; 8 | 9 | vec4 effect(sampler2D tex, vec2 tex_coord, vec4 vert_color) 10 | { 11 | return texture(tex, tex_coord) * vert_color; 12 | } 13 | 14 | void main() 15 | { 16 | vec2 param = uv_out; 17 | vec4 param_1 = color_out; 18 | frag_color = effect(main_tex, param, param_1); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /examples/assets/shaders/instanced_vs.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform vec4 InstancedVertParams[2]; 4 | out vec2 uv_out; 5 | layout(location = 1) in vec2 uv_in; 6 | out vec4 color_out; 7 | layout(location = 2) in vec4 color_in; 8 | layout(location = 0) in vec2 pos_in; 9 | layout(location = 3) in vec2 instance_pos_in; 10 | 11 | void main() 12 | { 13 | uv_out = uv_in; 14 | color_out = color_in; 15 | gl_Position = vec4(mat3x2(vec2(InstancedVertParams[0].x, InstancedVertParams[0].y), vec2(InstancedVertParams[0].z, InstancedVertParams[0].w), vec2(InstancedVertParams[1].x, InstancedVertParams[1].y)) * vec3(pos_in + instance_pos_in, 1.0), 0.0, 1.0); 16 | } 17 | 18 | -------------------------------------------------------------------------------- /examples/assets/shaders/lines_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform vec4 LinesParams[2]; 4 | uniform sampler2D main_tex; 5 | 6 | layout(location = 0) out vec4 frag_color; 7 | in vec2 uv_out; 8 | in vec4 color_out; 9 | 10 | vec4 effect(sampler2D tex, vec2 tex_coord, vec4 vert_color) 11 | { 12 | return mix(vec4(0.0), LinesParams[0], vec4(mod(floor(gl_FragCoord.y / LinesParams[1].x), 2.0))) * texture(tex, tex_coord).w; 13 | } 14 | 15 | void main() 16 | { 17 | vec2 param = uv_out; 18 | vec4 param_1 = color_out; 19 | frag_color = effect(main_tex, param, param_1); 20 | } 21 | 22 | -------------------------------------------------------------------------------- /examples/assets/shaders/meta_flames_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform vec4 MetaFlamesParams[2]; 4 | uniform sampler2D main_tex; 5 | 6 | layout(location = 0) out vec4 frag_color; 7 | in vec2 uv_out; 8 | in vec4 color_out; 9 | vec2 dither_amount; 10 | vec2 metaball_pos[20]; 11 | float metaball_radius[20]; 12 | float metaball_influence[20]; 13 | 14 | vec4 effect(sampler2D tex, vec2 tex_coord, vec4 vert_color) 15 | { 16 | for (int i = 0; i < 20; i++) 17 | { 18 | float _54 = float(i); 19 | float _69 = MetaFlamesParams[1].x * 1000.0; 20 | float _74 = 500.0 + (_54 * 200.0); 21 | metaball_pos[i].x = ((sin(_69 / (_74 + (mod(_54, 6.0) * 500.0))) * 0.31847131252288818359375) + 0.5) * MetaFlamesParams[1].z; 22 | metaball_pos[i].y = ((cos(_69 / (_74 - (mod(_54, 5.0) * 500.0))) * 0.31847131252288818359375) + 0.5) * MetaFlamesParams[1].w; 23 | metaball_radius[i] = 150.0; 24 | metaball_influence[i] = 0.5; 25 | } 26 | float _128 = sin(MetaFlamesParams[1].x); 27 | metaball_pos[1].x = ((_128 * 0.31847131252288818359375) + 0.5) * MetaFlamesParams[1].z; 28 | metaball_pos[1].y = ((cos(MetaFlamesParams[1].x * 0.25) * 0.31847131252288818359375) + 0.5) * MetaFlamesParams[1].w; 29 | metaball_radius[1] = 400.0; 30 | metaball_influence[1] = 1.0; 31 | metaball_pos[2].x = ((sin(MetaFlamesParams[1].x * 0.5) * 0.31847131252288818359375) + 0.5) * MetaFlamesParams[1].z; 32 | metaball_pos[2].y = ((cos(MetaFlamesParams[1].x * 0.3333333432674407958984375) * 0.31847131252288818359375) + 0.5) * MetaFlamesParams[1].w; 33 | metaball_radius[2] = 400.0; 34 | metaball_influence[2] = 1.0; 35 | float influence = 0.0; 36 | vec4 fg = texture(tex, tex_coord); 37 | for (int i_1 = 0; i_1 < 20; i_1++) 38 | { 39 | float _199 = MetaFlamesParams[1].w - gl_FragCoord.y; 40 | float _211 = gl_FragCoord.x + sin((_199 / dither_amount.x) + (cos(MetaFlamesParams[1].x) * 4.0)); 41 | float _223 = _199 + cos((_211 / dither_amount.y) + (_128 * 4.0)); 42 | float _323 = min(1.0, ((((distance(vec2(_211, _223), metaball_pos[i_1]) + pow((distance(metaball_pos[i_1].x, _211) * MetaFlamesParams[0].x) / distance(_223, (metaball_pos[i_1].y - metaball_radius[i_1]) - (sin((_211 / MetaFlamesParams[0].y) + ((MetaFlamesParams[1].x * MetaFlamesParams[0].z) / metaball_radius[i_1])) * MetaFlamesParams[0].w)), 2.0)) + ((sin((_223 * 3.1400001049041748046875) + MetaFlamesParams[1].x) * metaball_radius[i_1]) / dither_amount.y)) + ((cos((_211 * 3.1400001049041748046875) + MetaFlamesParams[1].x) * metaball_radius[i_1]) / dither_amount.x)) / pow((fg.x * 0.89999997615814208984375) + 0.10000002384185791015625, 2.0)) / metaball_radius[i_1]); 43 | float _327 = 1.0 - (_323 * _323); 44 | influence += ((_327 * _327) * metaball_influence[i_1]); 45 | } 46 | float _342 = influence; 47 | float _348 = (_342 + fg.y) - fg.z; 48 | influence = _348; 49 | vec4 _370 = fg; 50 | _370.x = step(0.00999999977648258209228515625, _348); 51 | vec4 _372 = _370; 52 | _372.y = step(0.300000011920928955078125, _348); 53 | vec4 _374 = _372; 54 | _374.z = step(0.75, _348); 55 | vec4 _376 = _374; 56 | _376.w = 1.0; 57 | fg = _376; 58 | return _376; 59 | } 60 | 61 | void main() 62 | { 63 | dither_amount = vec2(20.0, 40.0); 64 | vec2 param = uv_out; 65 | vec4 param_1 = color_out; 66 | vec4 _37 = effect(main_tex, param, param_1); 67 | frag_color = _37; 68 | } 69 | 70 | -------------------------------------------------------------------------------- /examples/assets/shaders/mode7_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform vec4 Mode7Params[3]; 4 | uniform sampler2D main_tex; 5 | uniform sampler2D map_tex; 6 | 7 | layout(location = 0) out vec4 frag_color; 8 | in vec2 uv_out; 9 | in vec4 color_out; 10 | 11 | vec4 effect(sampler2D tex, vec2 tex_coord, vec4 vert_color) 12 | { 13 | vec2 _86 = vec2((0.5 - tex_coord.x) * Mode7Params[1].x, (Mode7Params[1].z - tex_coord.y) * (Mode7Params[1].x / Mode7Params[1].y)) * mat2(vec2(Mode7Params[2].x, Mode7Params[2].z), vec2(Mode7Params[2].y, Mode7Params[2].w)); 14 | float _100 = ((_86.x / tex_coord.y) + Mode7Params[0].z) / Mode7Params[0].x; 15 | float _113 = ((_86.y / tex_coord.y) + Mode7Params[0].w) / Mode7Params[0].y; 16 | bool _119 = Mode7Params[1].w == 0.0; 17 | bool _146; 18 | if (_119) 19 | { 20 | bool _124 = _100 < 0.0; 21 | bool _131; 22 | if (!_124) 23 | { 24 | _131 = _100 > 1.0; 25 | } 26 | else 27 | { 28 | _131 = _124; 29 | } 30 | bool _138; 31 | if (!_131) 32 | { 33 | _138 = _113 < 0.0; 34 | } 35 | else 36 | { 37 | _138 = _131; 38 | } 39 | bool _145; 40 | if (!_138) 41 | { 42 | _145 = _113 > 1.0; 43 | } 44 | else 45 | { 46 | _145 = _138; 47 | } 48 | _146 = _145; 49 | } 50 | else 51 | { 52 | _146 = _119; 53 | } 54 | if (_146) 55 | { 56 | return vec4(0.0); 57 | } 58 | else 59 | { 60 | return texture(map_tex, mod(vec2(_100, _113), vec2(1.0))) * vert_color; 61 | } 62 | } 63 | 64 | void main() 65 | { 66 | vec2 param = uv_out; 67 | vec4 param_1 = color_out; 68 | frag_color = effect(main_tex, param, param_1); 69 | } 70 | 71 | -------------------------------------------------------------------------------- /examples/assets/shaders/multi_batcher.gl.fs: -------------------------------------------------------------------------------- 1 | #version 330 2 | uniform sampler2D samplers[8]; 3 | 4 | in vec2 VaryingTexCoord; 5 | in vec4 VaryingColor; 6 | in float VaryingTextureId; 7 | 8 | vec4 effect(vec4 vcolor, sampler2D tex, vec2 texcoord); 9 | 10 | layout (location=0) out vec4 frag_color; 11 | void main() { 12 | int tid = int(VaryingTextureId + 0.1); 13 | frag_color = effect(VaryingColor, samplers[tid], VaryingTexCoord.st); 14 | } 15 | 16 | vec4 effect(vec4 vcolor, sampler2D tex, vec2 texcoord) { 17 | return texture(tex, texcoord) * vcolor; 18 | } -------------------------------------------------------------------------------- /examples/assets/shaders/multi_batcher.gl.vs: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform vec4 VertexParams[2]; 4 | 5 | layout (location=0) in vec2 VertPosition; 6 | layout (location=1) in vec2 VertTexCoord; 7 | layout (location=2) in vec4 VertColor; 8 | layout (location=3) in float TextureId; 9 | 10 | out vec2 VaryingTexCoord; 11 | out vec4 VaryingColor; 12 | out float VaryingTextureId; 13 | 14 | vec4 position(mat3x2 transMat, vec2 localPosition); 15 | 16 | void main() { 17 | VaryingTexCoord = VertTexCoord; 18 | VaryingColor = VertColor; 19 | VaryingTextureId = TextureId; 20 | gl_Position = position(mat3x2(vec2(VertexParams[0].x, VertexParams[0].y), vec2(VertexParams[0].z, VertexParams[0].w), vec2(VertexParams[1].x, VertexParams[1].y)), VertPosition); 21 | } 22 | 23 | vec4 position(mat3x2 transMat, vec2 localPosition) { 24 | return vec4(transMat * vec3(localPosition, 1), 0, 1); 25 | } -------------------------------------------------------------------------------- /examples/assets/shaders/noise_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform vec4 NoiseParams[1]; 4 | uniform sampler2D main_tex; 5 | 6 | layout(location = 0) out vec4 frag_color; 7 | in vec2 uv_out; 8 | in vec4 color_out; 9 | 10 | vec4 effect(sampler2D tex, vec2 tex_coord, vec4 vert_color) 11 | { 12 | vec4 _36 = texture(tex, tex_coord); 13 | float _61 = ((tex_coord.x + 4.0) * (tex_coord.y + 4.0)) * (sin(NoiseParams[0].x) * 10.0); 14 | vec3 _87 = _36.xyz + (vec3(mod((mod(_61, 13.0) + 1.0) * (mod(_61, 123.0) + 1.0), 0.00999999977648258209228515625) - 0.004999999888241291046142578125) * NoiseParams[0].y); 15 | return vec4(_87.x, _87.y, _87.z, _36.w); 16 | } 17 | 18 | void main() 19 | { 20 | vec2 param = uv_out; 21 | vec4 param_1 = color_out; 22 | frag_color = effect(main_tex, param, param_1); 23 | } 24 | 25 | -------------------------------------------------------------------------------- /examples/assets/shaders/pixel_glitch_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform vec4 PixelGlitchParams[1]; 4 | uniform sampler2D main_tex; 5 | 6 | layout(location = 0) out vec4 frag_color; 7 | in vec2 uv_out; 8 | in vec4 color_out; 9 | 10 | float hash11(float p) 11 | { 12 | vec3 _47 = fract(vec3(p, p, p) * 0.103100001811981201171875); 13 | vec3 _57 = _47 + vec3(dot(_47, _47.yzx + vec3(19.1900005340576171875))); 14 | return fract((_57.x + _57.y) * _57.z); 15 | } 16 | 17 | vec4 effect(sampler2D tex, vec2 tex_coord, vec4 vert_color) 18 | { 19 | float param = floor(tex_coord.y * (PixelGlitchParams[0].z / PixelGlitchParams[0].x)); 20 | return texture(tex, vec2(tex_coord.x + (((hash11(param) * 2.0) - 1.0) * (PixelGlitchParams[0].y / PixelGlitchParams[0].w)), tex_coord.y)); 21 | } 22 | 23 | void main() 24 | { 25 | vec2 param = uv_out; 26 | vec4 param_1 = color_out; 27 | frag_color = effect(main_tex, param, param_1); 28 | } 29 | 30 | -------------------------------------------------------------------------------- /examples/assets/shaders/rgb_shift_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform vec4 RgbShiftParams[1]; 4 | uniform sampler2D main_tex; 5 | 6 | layout(location = 0) out vec4 frag_color; 7 | in vec2 uv_out; 8 | in vec4 color_out; 9 | 10 | vec4 effect(sampler2D tex, vec2 tex_coord, vec4 vert_color) 11 | { 12 | float _63 = RgbShiftParams[0].x * (1.0 / RgbShiftParams[0].z); 13 | float _71 = RgbShiftParams[0].x * (1.0 / RgbShiftParams[0].w); 14 | float _72 = tex_coord.y - _71; 15 | vec4 _74 = texture(tex, vec2(tex_coord.x + _63, _72)); 16 | vec4 _88 = texture(tex, vec2(tex_coord.x, tex_coord.y + _71)); 17 | vec4 _108 = texture(tex, vec2(tex_coord.x - _63, _72)); 18 | return vec4(_74.x, _88.y, _108.z, ((_74.w + _88.w) + (_108.w * 0.3333333432674407958984375)) * RgbShiftParams[0].y); 19 | } 20 | 21 | void main() 22 | { 23 | vec2 param = uv_out; 24 | vec4 param_1 = color_out; 25 | frag_color = effect(main_tex, param, param_1); 26 | } 27 | 28 | -------------------------------------------------------------------------------- /examples/assets/shaders/sepia_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform vec4 SepiaParams[1]; 4 | uniform sampler2D main_tex; 5 | 6 | layout(location = 0) out vec4 frag_color; 7 | in vec2 uv_out; 8 | in vec4 color_out; 9 | 10 | vec4 effect(sampler2D tex, vec2 tex_coord, vec4 vert_color) 11 | { 12 | vec4 _36 = texture(tex, tex_coord); 13 | vec3 _41 = _36.xyz; 14 | vec3 _61 = mix(_41, SepiaParams[0].xyz * dot(_41, vec3(0.300000011920928955078125, 0.589999973773956298828125, 0.10999999940395355224609375)), vec3(0.75)); 15 | return vec4(_61.x, _61.y, _61.z, _36.w); 16 | } 17 | 18 | void main() 19 | { 20 | vec2 param = uv_out; 21 | vec4 param_1 = color_out; 22 | frag_color = effect(main_tex, param, param_1); 23 | } 24 | 25 | -------------------------------------------------------------------------------- /examples/assets/shaders/sprite_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform sampler2D main_tex; 4 | 5 | layout(location = 0) out vec4 frag_color; 6 | in vec2 uv_out; 7 | in vec4 color_out; 8 | 9 | vec4 effect(sampler2D tex, vec2 tex_coord, vec4 vert_color) 10 | { 11 | return texture(tex, tex_coord) * vert_color; 12 | } 13 | 14 | void main() 15 | { 16 | vec2 param = uv_out; 17 | vec4 param_1 = color_out; 18 | frag_color = effect(main_tex, param, param_1); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /examples/assets/shaders/sprite_vs.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform vec4 VertexParams[2]; 4 | out vec2 uv_out; 5 | layout(location = 1) in vec2 uv_in; 6 | out vec4 color_out; 7 | layout(location = 2) in vec4 color_in; 8 | layout(location = 0) in vec2 pos_in; 9 | 10 | void main() 11 | { 12 | uv_out = uv_in; 13 | color_out = color_in; 14 | gl_Position = vec4(mat3x2(vec2(VertexParams[0].x, VertexParams[0].y), vec2(VertexParams[0].z, VertexParams[0].w), vec2(VertexParams[1].x, VertexParams[1].y)) * vec3(pos_in, 1.0), 0.0, 1.0); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /examples/assets/shaders/vignette_fs.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform vec4 VignetteParams[1]; 4 | uniform sampler2D main_tex; 5 | 6 | layout(location = 0) out vec4 frag_color; 7 | in vec2 uv_out; 8 | in vec4 color_out; 9 | 10 | vec4 effect(sampler2D tex, vec2 tex_coord, vec4 vert_color) 11 | { 12 | vec4 _36 = texture(tex, tex_coord); 13 | vec2 _50 = (tex_coord - vec2(0.5)) * VignetteParams[0].x; 14 | vec3 _69 = _36.xyz * (1.0 - (dot(_50, _50) * VignetteParams[0].y)); 15 | return vec4(_69.x, _69.y, _69.z, _36.w); 16 | } 17 | 18 | void main() 19 | { 20 | vec2 param = uv_out; 21 | vec4 param_1 = color_out; 22 | frag_color = effect(main_tex, param, param_1); 23 | } 24 | 25 | -------------------------------------------------------------------------------- /examples/assets/textures/SimpleTileset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prime31/zig-aya/fd03a2cbddee6773f19d6ce849f6d1a60ed39324/examples/assets/textures/SimpleTileset.png -------------------------------------------------------------------------------- /examples/assets/textures/blacknwhite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prime31/zig-aya/fd03a2cbddee6773f19d6ce849f6d1a60ed39324/examples/assets/textures/blacknwhite.png -------------------------------------------------------------------------------- /examples/assets/textures/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prime31/zig-aya/fd03a2cbddee6773f19d6ce849f6d1a60ed39324/examples/assets/textures/block.png -------------------------------------------------------------------------------- /examples/assets/textures/clouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prime31/zig-aya/fd03a2cbddee6773f19d6ce849f6d1a60ed39324/examples/assets/textures/clouds.png -------------------------------------------------------------------------------- /examples/assets/textures/flames_scene.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prime31/zig-aya/fd03a2cbddee6773f19d6ce849f6d1a60ed39324/examples/assets/textures/flames_scene.png -------------------------------------------------------------------------------- /examples/assets/textures/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prime31/zig-aya/fd03a2cbddee6773f19d6ce849f6d1a60ed39324/examples/assets/textures/font.png -------------------------------------------------------------------------------- /examples/assets/textures/mario_kart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prime31/zig-aya/fd03a2cbddee6773f19d6ce849f6d1a60ed39324/examples/assets/textures/mario_kart.png -------------------------------------------------------------------------------- /examples/assets/textures/markov.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prime31/zig-aya/fd03a2cbddee6773f19d6ce849f6d1a60ed39324/examples/assets/textures/markov.png -------------------------------------------------------------------------------- /examples/assets/textures/minimal_tiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prime31/zig-aya/fd03a2cbddee6773f19d6ce849f6d1a60ed39324/examples/assets/textures/minimal_tiles.png -------------------------------------------------------------------------------- /examples/assets/textures/sword_dude.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prime31/zig-aya/fd03a2cbddee6773f19d6ce849f6d1a60ed39324/examples/assets/textures/sword_dude.png -------------------------------------------------------------------------------- /examples/assets/textures/zelda_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prime31/zig-aya/fd03a2cbddee6773f19d6ce849f6d1a60ed39324/examples/assets/textures/zelda_map.png -------------------------------------------------------------------------------- /examples/assets/tileset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prime31/zig-aya/fd03a2cbddee6773f19d6ce849f6d1a60ed39324/examples/assets/tileset.png -------------------------------------------------------------------------------- /examples/atlas_batch.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | 4 | var batch: aya.gfx.AtlasBatch = undefined; 5 | var font_tex: aya.gfx.Texture = undefined; 6 | var quad: aya.math.Quad = undefined; 7 | var index: usize = 0; 8 | 9 | pub fn main() anyerror!void { 10 | try aya.run(.{ 11 | .init = init, 12 | .update = update, 13 | .render = render, 14 | }); 15 | } 16 | 17 | fn init() !void { 18 | font_tex = aya.gfx.Texture.initFromFile("examples/assets/textures/font.png", .linear) catch unreachable; 19 | batch = aya.gfx.AtlasBatch.init(null, font_tex, 200) catch unreachable; 20 | quad = aya.math.Quad.init(0, 0, font_tex.width, font_tex.height, font_tex.width, font_tex.height); 21 | 22 | var mat = aya.math.Mat32.identity; 23 | 24 | var x: usize = 0; 25 | var y = @as(usize, 0); 26 | const w = 506 / 10; 27 | const h = 616 / 10; 28 | const scale = 0.3; 29 | while (y < 10) : (y += 1) { 30 | while (x < 10) : (x += 1) { 31 | const x_pos = @as(f32, @floatFromInt(x * w)); 32 | const y_pos = @as(f32, @floatFromInt(y * h)); 33 | const x_gap = @as(f32, @floatFromInt(x)); 34 | const y_gap = @as(f32, @floatFromInt(y)); 35 | mat.setTransform(.{ .x = x_pos * scale + x_gap, .y = y_pos * scale + y_gap, .sx = scale, .sy = scale }); 36 | _ = batch.addViewport(.{ .x = @as(i32, @intFromFloat(x_pos)), .y = @as(i32, @intFromFloat(y_pos)), .w = w, .h = h }, mat, aya.math.Color.white); 37 | mat.translate(w * scale, 0); 38 | } 39 | x = 0; 40 | mat.translate(0, h * scale); 41 | } 42 | 43 | mat.setTransform(.{ .x = 300, .y = 150, .angle = 0.3, .sx = 0.2, .sy = 0.2, .ox = font_tex.width / 2.0, .oy = font_tex.height / 2.0 }); 44 | _ = batch.add(quad, mat, aya.math.Color.green); 45 | index = batch.addViewport(.{ .w = 20, .h = 20 }, null, aya.math.Color.blue); 46 | } 47 | 48 | fn update() !void { 49 | if (aya.math.rand.chance(0.9)) return; 50 | 51 | const rx = aya.math.rand.range(f32, -2, 2); 52 | const ry = aya.math.rand.range(f32, -2, 2); 53 | 54 | var mat = aya.math.Mat32.identity; 55 | mat.translate(300 + rx, 300 + ry); 56 | 57 | batch.setViewport(index, .{ .w = 20, .h = 20 }, mat, aya.math.rand.color()); 58 | } 59 | 60 | fn render() !void { 61 | aya.gfx.beginPass(.{}); 62 | batch.draw(); 63 | aya.gfx.endPass(); 64 | } 65 | -------------------------------------------------------------------------------- /examples/batcher.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | 4 | var checker_tex: aya.gfx.Texture = undefined; 5 | var font_tex: aya.gfx.Texture = undefined; 6 | var checker_quad: aya.math.Quad = undefined; 7 | var font_quad: aya.math.Quad = undefined; 8 | 9 | pub fn main() !void { 10 | try aya.run(.{ .init = init, .update = update, .render = render, .shutdown = shutdown, .window = .{ .disable_vsync = false } }); 11 | } 12 | 13 | fn init() !void { 14 | checker_tex = aya.gfx.Texture.initCheckerTexture(1); 15 | font_tex = aya.gfx.Texture.initFromFile("examples/assets/textures/font.png", .linear) catch unreachable; 16 | 17 | checker_quad = aya.math.Quad.init(0, 0, checker_tex.width, checker_tex.height, checker_tex.width, checker_tex.height); 18 | font_quad = aya.math.Quad.init(0, 0, font_tex.width, font_tex.height, font_tex.width, font_tex.height); 19 | } 20 | 21 | fn shutdown() !void { 22 | checker_tex.deinit(); 23 | font_tex.deinit(); 24 | } 25 | 26 | fn update() !void {} 27 | 28 | fn render() !void { 29 | aya.gfx.beginPass(.{}); 30 | 31 | var x: usize = 0; 32 | var y = @as(usize, 0); 33 | while (x < 640 / 40) : (x += 1) { 34 | while (y < 480 / 40) : (y += 1) { 35 | aya.draw.tex(checker_tex, @as(f32, @floatFromInt(x * 5)), @as(f32, @floatFromInt(y * 5))); 36 | } 37 | y = 0; 38 | } 39 | 40 | aya.draw.texScale(font_tex, 200, 200, 0.2); 41 | aya.draw.texScale(checker_tex, 10, 10, 10); 42 | aya.draw.texScale(font_tex, 400, 100, 0.2); 43 | aya.draw.texScale(font_tex, 600, 200, 0.2); 44 | 45 | aya.gfx.endPass(); 46 | } 47 | -------------------------------------------------------------------------------- /examples/dynamic_mesh.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | 4 | var mesh: aya.gfx.DynamicMesh(u16, aya.gfx.Vertex) = undefined; 5 | var tex: aya.gfx.Texture = undefined; 6 | var rng = std.rand.DefaultPrng.init(0); 7 | 8 | pub fn main() !void { 9 | try aya.run(.{ 10 | .init = init, 11 | .update = update, 12 | .render = render, 13 | .shutdown = shutdown, 14 | }); 15 | } 16 | 17 | fn init() !void { 18 | var indices = [_]u16{ 19 | 0, 1, 2, 2, 3, 0, 20 | }; //0, 1, 2, 0, 2, 3, 21 | mesh = aya.gfx.DynamicMesh(u16, aya.gfx.Vertex).init(null, 4, indices[0..]) catch unreachable; 22 | 23 | mesh.verts[0] = .{ .pos = .{ .x = 220, .y = 20 }, .uv = .{ .x = 1, .y = 0 }, .col = 0xFFFF0FFF }; 24 | mesh.verts[1] = .{ .pos = .{ .x = 20, .y = 20 }, .uv = .{ .x = 0, .y = 0 }, .col = 0xFF00FFFF }; 25 | mesh.verts[2] = .{ .pos = .{ .x = 20, .y = 220 }, .uv = .{ .x = 0, .y = 1 }, .col = 0xFFFFFFFF }; 26 | mesh.verts[3] = .{ .pos = .{ .x = 220, .y = 220 }, .uv = .{ .x = 1, .y = 1 }, .col = 0xFFFFFFFF }; 27 | mesh.updateAllVerts(); 28 | 29 | tex = aya.gfx.Texture.initFromFile("examples/assets/textures/sword_dude.png", .nearest) catch unreachable; 30 | mesh.bindImage(tex.img, 0); 31 | } 32 | 33 | fn shutdown() !void { 34 | mesh.deinit(); 35 | tex.deinit(); 36 | } 37 | 38 | fn update() !void { 39 | const rx = aya.math.rand.range(f32, -2, 2); 40 | const ry = aya.math.rand.range(f32, -2, 2); 41 | 42 | var i: usize = 0; 43 | while (i < 4) : (i += 1) { 44 | mesh.verts[i].pos.x += rx; 45 | mesh.verts[i].pos.y += ry; 46 | } 47 | mesh.updateAllVerts(); 48 | } 49 | 50 | fn render() !void { 51 | aya.gfx.beginPass(.{}); 52 | mesh.drawAllVerts(); 53 | aya.debug.drawRect(.{ .x = 300, .y = 200 }, 40, 40, null); 54 | aya.draw.tex(tex, 10, 10); 55 | aya.draw.tex(tex, 400, 400); 56 | aya.gfx.endPass(); 57 | } 58 | -------------------------------------------------------------------------------- /examples/empty.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | 4 | pub fn main() !void { 5 | try aya.run(.{ 6 | .init = init, 7 | .render = render, 8 | }); 9 | } 10 | 11 | fn init() !void {} 12 | 13 | fn render() !void { 14 | aya.gfx.beginPass(.{}); 15 | aya.gfx.endPass(); 16 | } 17 | -------------------------------------------------------------------------------- /examples/flames.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | const shaders = @import("assets/shaders/shaders.zig"); 4 | usingnamespace @import("imgui"); 5 | 6 | const Shader = aya.gfx.Shader; 7 | const effects = @import("assets/effects.zig"); 8 | 9 | pub const enable_imgui = true; 10 | 11 | pub const Flames = struct { 12 | postprocessor: aya.gfx.PostProcessor, 13 | shader: shaders.MetaFlamesShader, 14 | 15 | pub fn deinit(self: @This()) void { 16 | self.shader.deinit(); 17 | } 18 | 19 | pub fn initialize(self: *@This(), data: anytype) void { 20 | _ = data; 21 | self.postprocessor = .{ .process = process }; 22 | self.shader = shaders.createMetaFlamesShader(); 23 | self.shader.frag_uniform.tear_sharpness = 7; 24 | self.shader.frag_uniform.tear_wave_length = 5; 25 | self.shader.frag_uniform.tear_wave_speed = 500; 26 | self.shader.frag_uniform.tear_wave_amplitude = 10; 27 | self.shader.frag_uniform.screen_size = .{ .x = 785, .y = 577 }; 28 | } 29 | 30 | pub fn process(processor: *aya.gfx.PostProcessor, pass: aya.gfx.OffscreenPass, texture: aya.gfx.Texture) void { 31 | const self = processor.getParent(@This()); 32 | processor.blit(pass, texture, &self.shader.shader); 33 | } 34 | 35 | pub fn setTone(self: *@This(), tone: aya.math.Vec3) void { 36 | self.shader.frag_uniform.sepia_tone = tone; 37 | } 38 | }; 39 | 40 | var tex: aya.gfx.Texture = undefined; 41 | var stack: aya.gfx.PostProcessStack = undefined; 42 | var flames_params: shaders.MetaFlamesParams = undefined; 43 | 44 | pub fn main() !void { 45 | try aya.run(.{ 46 | .init = init, 47 | .update = update, 48 | .render = render, 49 | .shutdown = shutdown, 50 | .window = .{ 51 | .width = 785, 52 | .height = 577, 53 | }, 54 | }); 55 | } 56 | 57 | fn init() !void { 58 | tex = aya.gfx.Texture.initFromFile("examples/assets/textures/flames_scene.png", .nearest) catch unreachable; 59 | 60 | stack = aya.gfx.createPostProcessStack(); 61 | _ = stack.add(effects.Vignette, {}); 62 | _ = stack.add(Flames, {}); 63 | } 64 | 65 | fn shutdown() !void { 66 | tex.deinit(); 67 | stack.deinit(); 68 | } 69 | 70 | fn update() !void { 71 | var flames = stack.processors.items[1].getParent(Flames); 72 | flames.shader.frag_uniform.time = aya.time.seconds(); 73 | 74 | flames_params = flames.shader.frag_uniform; 75 | if (aya.utils.inspect(shaders.MetaFlamesParams, "Flames", &flames_params)) { 76 | flames.shader.frag_uniform = flames_params; 77 | } 78 | } 79 | 80 | fn render() !void { 81 | aya.gfx.beginPass(.{}); 82 | aya.draw.texScale(tex, 0, 0, 1); 83 | aya.gfx.endPass(); 84 | 85 | aya.gfx.postProcess(&stack); 86 | } 87 | -------------------------------------------------------------------------------- /examples/fonts.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | const sdl = aya.sdl; 4 | const math = aya.math; 5 | const Color = math.Color; 6 | 7 | var book: *aya.gfx.FontBook = undefined; 8 | var font: i32 = 0; 9 | 10 | pub fn main() anyerror!void { 11 | try aya.run(.{ 12 | .init = init, 13 | .update = update, 14 | .render = render, 15 | }); 16 | } 17 | 18 | fn init() !void { 19 | book = aya.gfx.FontBook.init(null, 128, 128, .nearest) catch unreachable; 20 | font = book.addFont("examples/assets/ProggyTiny.ttf"); 21 | book.setSize(10); 22 | } 23 | 24 | fn update() !void { 25 | aya.debug.drawText("what the fuck, im at 0,450. top-left", .{ .x = 0, .y = 450 }, null); 26 | } 27 | 28 | fn render() !void { 29 | aya.gfx.beginPass(.{}); 30 | book.setAlign(.top_left); 31 | aya.draw.text("top-left", 0, 0, book); 32 | book.setAlign(.left); 33 | aya.draw.text("top-left", 100, 18, book); 34 | 35 | book.setAlign(.middle); 36 | book.pushState(); 37 | book.setBlur(1.2); 38 | book.setColor(aya.math.Color.blue); 39 | aya.draw.text("top-left", 250, 58, book); 40 | book.popState(); 41 | 42 | if (aya.input.mouseDown(.left)) { 43 | aya.draw.text("pooop, left button", 200, 50, null); 44 | } 45 | 46 | if (aya.input.mouseDown(.right)) { 47 | aya.draw.text("fucker, right button", 10, 150, book); 48 | } 49 | 50 | aya.draw.textOptions("fucker", null, .{ .x = 300, .y = 150, .sx = 3, .sy = 3, .rot = 0.785, .color = Color.yellow }); 51 | 52 | aya.draw.hollowRect(.{ .x = 100, .y = 250 }, 128, 128, 1, Color.pink); 53 | aya.draw.tex(book.texture.?, 100, 250); 54 | aya.gfx.endPass(); 55 | } 56 | -------------------------------------------------------------------------------- /examples/imgui.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | const imgui = @import("imgui"); 4 | 5 | pub const enable_imgui = true; 6 | var demo_open: bool = true; 7 | 8 | pub fn main() !void { 9 | try aya.run(.{ 10 | .init = init, 11 | .update = update, 12 | .render = render, 13 | .window = .{ 14 | .width = 1024, 15 | .height = 768, 16 | }, 17 | }); 18 | } 19 | 20 | fn init() !void {} 21 | 22 | fn update() !void { 23 | imgui.igShowDemoWindow(&demo_open); 24 | } 25 | 26 | fn render() !void { 27 | aya.gfx.beginPass(.{}); 28 | aya.gfx.endPass(); 29 | } 30 | -------------------------------------------------------------------------------- /examples/instanced_mesh.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | const shaders = @import("assets/shaders/shaders.zig"); 4 | 5 | var tex: aya.gfx.Texture = undefined; 6 | var instanced_mesh: aya.gfx.InstancedMesh(u16, aya.gfx.Vertex, InstancedVert) = undefined; 7 | var shader: aya.gfx.Shader = undefined; 8 | 9 | const InstancedVert = struct { 10 | pos: aya.math.Vec2, 11 | }; 12 | 13 | pub fn main() !void { 14 | try aya.run(.{ 15 | .init = init, 16 | .update = update, 17 | .render = render, 18 | .shutdown = shutdown, 19 | .gfx = .{ 20 | .resolution_policy = .none, 21 | }, 22 | }); 23 | } 24 | 25 | fn init() !void { 26 | tex = aya.gfx.Texture.initCheckerTexture(1); 27 | 28 | var vertices = [_]aya.gfx.Vertex{ 29 | .{ .pos = .{ .x = 10, .y = 110 }, .uv = .{ .x = 1, .y = 0 }, .col = 0xFFFFFFFF }, // bl 30 | .{ .pos = .{ .x = 110, .y = 110 }, .uv = .{ .x = 0, .y = 0 }, .col = 0xFF0000FF }, // br 31 | .{ .pos = .{ .x = 110, .y = 10 }, .uv = .{ .x = 0, .y = 1 }, .col = 0xFFFF0000 }, // tr 32 | .{ .pos = .{ .x = 10, .y = 10 }, .uv = .{ .x = 1, .y = 1 }, .col = 0xFF000000 }, // tl 33 | }; 34 | var indices = [_]u16{ 0, 1, 2, 0, 2, 3 }; 35 | 36 | vertices[0].pos = .{ .x = 0, .y = 16 }; // bl 37 | vertices[0].col = 0xFFFFFFFF; 38 | vertices[1].pos = .{ .x = 16, .y = 16 }; // br 39 | vertices[1].col = 0xFFFFFFFF; 40 | vertices[2].pos = .{ .x = 16, .y = 0 }; // tr 41 | vertices[2].col = 0xFFFFFFFF; 42 | vertices[3].pos = .{ .x = 0, .y = 0 }; // tl 43 | vertices[3].col = 0xFFFFFFFF; 44 | instanced_mesh = aya.gfx.InstancedMesh(u16, aya.gfx.Vertex, InstancedVert).init(null, 100, indices[0..], vertices[0..]); 45 | 46 | // const instances_per_row = @divTrunc(aya.window.width(), @as(i32, 40)); 47 | var i: usize = 0; 48 | var y: i32 = 200; 49 | blk: while (y < aya.window.height() - 50) : (y += 24) { 50 | var x: i32 = 18; 51 | while (x < aya.window.width() - 50) : (x += 50) { 52 | instanced_mesh.instance_data[i].pos = .{ .x = @as(f32, @floatFromInt(x)), .y = @as(f32, @floatFromInt(y)) }; 53 | i += 1; 54 | if (i == 100) break :blk; 55 | } 56 | } 57 | instanced_mesh.updateInstanceData(); 58 | instanced_mesh.bindImage(tex.img, 0); 59 | 60 | shader = try shaders.createInstancedShader(); 61 | } 62 | 63 | fn shutdown() !void { 64 | instanced_mesh.deinit(); 65 | tex.deinit(); 66 | } 67 | 68 | fn update() !void {} 69 | 70 | fn render() !void { 71 | aya.gfx.beginNullPass(); 72 | aya.gfx.beginPass(.{ .color = aya.math.Color.gold }); 73 | 74 | aya.gfx.setShader(&shader); 75 | instanced_mesh.drawAll(); 76 | 77 | aya.gfx.endPass(); 78 | } 79 | -------------------------------------------------------------------------------- /examples/mesh.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | 4 | var mesh: aya.gfx.Mesh = undefined; 5 | var tex: aya.gfx.Texture = undefined; 6 | 7 | pub fn main() !void { 8 | try aya.run(.{ 9 | .init = init, 10 | .update = update, 11 | .render = render, 12 | .shutdown = shutdown, 13 | .gfx = .{ 14 | .resolution_policy = .none, 15 | }, 16 | }); 17 | } 18 | 19 | fn init() !void { 20 | var vertices = [_]aya.gfx.Vertex{ 21 | .{ .pos = .{ .x = 10, .y = 100 }, .uv = .{ .x = 1, .y = 0 }, .col = 0xFFFFFFFF }, // bl 22 | .{ .pos = .{ .x = 100, .y = 100 }, .uv = .{ .x = 0, .y = 0 }, .col = 0xFF0000FF }, // br 23 | .{ .pos = .{ .x = 100, .y = 10 }, .uv = .{ .x = 0, .y = 1 }, .col = 0xFFFF0000 }, // tr 24 | .{ .pos = .{ .x = 10, .y = 10 }, .uv = .{ .x = 1, .y = 1 }, .col = 0xFF000000 }, // tl 25 | }; 26 | var indices = [_]u16{ 27 | 0, 1, 2, 0, 2, 3, 28 | }; 29 | 30 | mesh = aya.gfx.Mesh.init(u16, indices[0..], aya.gfx.Vertex, vertices[0..]); 31 | 32 | tex = aya.gfx.Texture.initCheckerTexture(1); 33 | mesh.bindImage(tex.img, 0); 34 | } 35 | 36 | fn shutdown() !void { 37 | mesh.deinit(); 38 | tex.deinit(); 39 | } 40 | 41 | fn update() !void {} 42 | 43 | fn render() !void { 44 | aya.gfx.beginNullPass(); 45 | aya.gfx.beginPass(.{ .color = aya.math.Color.gold }); 46 | mesh.draw(); 47 | aya.gfx.endPass(); 48 | } 49 | -------------------------------------------------------------------------------- /examples/multi_shaders.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | const imgui = @import("imgui"); 4 | const shaders = @import("assets/shaders/shaders.zig"); 5 | 6 | const Shader = aya.gfx.Shader; 7 | const effects = @import("assets/effects.zig"); 8 | 9 | pub const enable_imgui = true; 10 | 11 | var post_process = false; 12 | var tex: aya.gfx.Texture = undefined; 13 | var clouds_tex: aya.gfx.Texture = undefined; 14 | var lines_shader: shaders.LinesShader = undefined; 15 | var noise_shader: shaders.NoiseShader = undefined; 16 | var dissolve_shader: shaders.DissolveShader = undefined; 17 | var stack: aya.gfx.PostProcessStack = undefined; 18 | 19 | pub fn main() !void { 20 | try aya.run(.{ 21 | .init = init, 22 | .update = update, 23 | .render = render, 24 | .shutdown = shutdown, 25 | .window = .{ 26 | .width = 1024, 27 | .height = 768, 28 | }, 29 | }); 30 | } 31 | 32 | fn init() !void { 33 | tex = aya.gfx.Texture.initFromFile("examples/assets/textures/sword_dude.png", .nearest) catch unreachable; 34 | clouds_tex = aya.gfx.Texture.initFromFile("examples/assets/textures/clouds.png", .linear) catch unreachable; 35 | 36 | lines_shader = shaders.createLinesShader(); 37 | lines_shader.frag_uniform.line_color = [_]f32{ 0.9, 0.8, 0.2, 1 }; 38 | lines_shader.frag_uniform.line_size = 4; 39 | 40 | noise_shader = shaders.createNoiseShader(); 41 | noise_shader.frag_uniform.power = 100; 42 | 43 | dissolve_shader = shaders.createDissolveShader(); 44 | dissolve_shader.frag_uniform.threshold = 0.04; 45 | dissolve_shader.frag_uniform.threshold_color = [_]f32{ 1, 0.6, 0, 1 }; 46 | 47 | stack = aya.gfx.createPostProcessStack(); 48 | _ = stack.add(effects.PixelGlitch, {}); 49 | _ = stack.add(effects.Sepia, {}); 50 | _ = stack.add(effects.Vignette, {}); 51 | } 52 | 53 | fn shutdown() !void { 54 | tex.deinit(); 55 | clouds_tex.deinit(); 56 | 57 | lines_shader.deinit(); 58 | noise_shader.deinit(); 59 | dissolve_shader.deinit(); 60 | stack.deinit(); 61 | } 62 | 63 | fn update() !void { 64 | _ = imgui.igCheckbox("Enable PostProcessing", &post_process); 65 | 66 | var pixel_glitch = stack.processors.items[0].getParent(effects.PixelGlitch); 67 | _ = stack.processors.items[1].getParent(effects.Sepia); 68 | _ = stack.processors.items[2].getParent(effects.Vignette); 69 | 70 | _ = aya.utils.inspect(@TypeOf(lines_shader.frag_uniform), "lines", &lines_shader.frag_uniform); 71 | _ = aya.utils.inspect(@TypeOf(noise_shader.frag_uniform), "noise", &noise_shader.frag_uniform); 72 | _ = aya.utils.inspect(@TypeOf(dissolve_shader.frag_uniform), "dissolve", &dissolve_shader.frag_uniform); 73 | 74 | if (@mod(aya.time.frames(), 5) == 0) { 75 | pixel_glitch.shader.frag_uniform.vertical_size = aya.math.rand.range(f32, 1, 5); 76 | pixel_glitch.shader.frag_uniform.horizontal_offset = aya.math.rand.range(f32, -20, 20); 77 | } 78 | } 79 | 80 | fn render() !void { 81 | aya.gfx.beginPass(.{}); 82 | aya.draw.text("Hold space to disable noise/lines effects", 10, 30, null); 83 | aya.draw.text("Hold d to disable dissolve effect", 10, 50, null); 84 | aya.draw.texScale(tex, 30, 30, 3); 85 | 86 | if (!aya.input.keyDown(.space)) { 87 | aya.gfx.setShader(&lines_shader.shader); 88 | } 89 | aya.draw.texScale(tex, 230, 230, 3); 90 | 91 | if (!aya.input.keyDown(.space)) { 92 | noise_shader.frag_uniform.time = aya.time.seconds(); 93 | aya.gfx.setShader(&noise_shader.shader); 94 | } 95 | aya.draw.texScale(tex, 100, 230, 3); 96 | aya.gfx.flush(); 97 | 98 | if (!aya.input.keyDown(.d)) { 99 | dissolve_shader.frag_uniform.progress = aya.math.pingpong(aya.time.seconds(), 1); 100 | aya.gfx.setShader(&dissolve_shader.shader); 101 | aya.draw.bindTexture(clouds_tex, 1); 102 | } else { 103 | aya.gfx.setShader(null); 104 | } 105 | aya.draw.texScale(tex, 330, 30, 3); 106 | 107 | aya.gfx.endPass(); 108 | aya.draw.unbindTexture(1); 109 | 110 | if (post_process) aya.gfx.postProcess(&stack); 111 | } 112 | -------------------------------------------------------------------------------- /examples/offscreen.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | const math = aya.math; 4 | 5 | const Sepia = @import("assets/effects.zig").Sepia; 6 | 7 | var checker_tex: aya.gfx.Texture = undefined; 8 | var font_tex: aya.gfx.Texture = undefined; 9 | var pass: aya.gfx.OffscreenPass = undefined; 10 | var stack: aya.gfx.PostProcessStack = undefined; 11 | 12 | pub fn main() !void { 13 | try aya.run(.{ 14 | .init = init, 15 | .update = update, 16 | .render = render, 17 | .shutdown = shutdown, 18 | .gfx = .{ 19 | .resolution_policy = .default, 20 | }, 21 | }); 22 | } 23 | 24 | fn init() !void { 25 | checker_tex = aya.gfx.Texture.initCheckerTexture(1); 26 | font_tex = aya.gfx.Texture.initFromFile("examples/assets/textures/font.png", .linear) catch unreachable; 27 | pass = aya.gfx.OffscreenPass.init(52, 52); 28 | 29 | stack = aya.gfx.createPostProcessStack(); 30 | _ = stack.add(Sepia, {}); 31 | } 32 | 33 | fn shutdown() !void { 34 | stack.deinit(); 35 | checker_tex.deinit(); 36 | font_tex.deinit(); 37 | pass.deinit(); 38 | } 39 | 40 | fn update() !void {} 41 | 42 | fn render() !void { 43 | // render offscreen 44 | aya.gfx.beginPass(.{ .pass = pass, .color = aya.math.Color.lime }); 45 | var i = @as(f32, 0); 46 | while (i <= @divFloor(pass.color_texture.width, checker_tex.width)) : (i += 1) { 47 | aya.draw.tex(checker_tex, i * 4, i * 2 + 10); 48 | } 49 | aya.gfx.endPass(); 50 | 51 | // render into our default render target 52 | aya.gfx.beginPass(.{}); 53 | aya.draw.texScale(checker_tex, 5, 5, 10); 54 | aya.draw.texScale(checker_tex, 55, 55, 2); 55 | 56 | aya.draw.texScale(font_tex, 200, 100, 0.2); 57 | aya.draw.texScale(font_tex, 300, 200, 0.2); 58 | 59 | aya.draw.line(.{ .x = 0, .y = 0 }, aya.math.Vec2.init(640, 480), 2, aya.math.Color.blue); 60 | aya.draw.point(.{ .x = 350, .y = 350 }, 10, math.Color.sky_blue); 61 | aya.draw.point(.{ .x = 380, .y = 380 }, 15, math.Color.magenta); 62 | aya.draw.rect(.{ .x = 387, .y = 372 }, 40, 15, math.Color.dark_brown); 63 | aya.draw.hollowRect(.{ .x = 430, .y = 372 }, 40, 15, 2, math.Color.yellow); 64 | aya.draw.circle(.{ .x = 100, .y = 350 }, 20, 1, 12, math.Color.orange); 65 | 66 | const poly = [_]math.Vec2{ .{ .x = 400, .y = 30 }, .{ .x = 420, .y = 10 }, .{ .x = 430, .y = 80 }, .{ .x = 410, .y = 60 }, .{ .x = 375, .y = 40 } }; 67 | aya.draw.hollowPolygon(poly[0..], 2, math.Color.gold); 68 | aya.gfx.endPass(); 69 | 70 | // blit the default render target to the screen 71 | aya.gfx.postProcess(&stack); 72 | aya.gfx.blitToScreen(aya.math.Color.black); 73 | 74 | // now render directly to the backbuffer 75 | aya.gfx.beginPass(.{ .clear_color = false }); 76 | aya.debug.drawPoint(.{ .x = 40, .y = 400 }, 60, aya.math.Color.yellow); 77 | aya.draw.texScale(pass.color_texture, 20, 200, 2); 78 | aya.gfx.endPass(); 79 | } 80 | -------------------------------------------------------------------------------- /examples/primitives.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | const math = aya.math; 4 | const Color = math.Color; 5 | 6 | var tri_batch: aya.gfx.TriangleBatcher = undefined; 7 | 8 | pub fn main() !void { 9 | try aya.run(.{ 10 | .init = init, 11 | .update = update, 12 | .render = render, 13 | .shutdown = shutdown, 14 | }); 15 | } 16 | 17 | fn init() !void { 18 | tri_batch = aya.gfx.TriangleBatcher.init(null, 100) catch unreachable; 19 | } 20 | 21 | fn update() !void {} 22 | 23 | fn render() !void { 24 | aya.gfx.beginPass(.{}); 25 | aya.draw.line(math.Vec2.init(0, 0), aya.math.Vec2.init(640, 480), 2, aya.math.Color.blue); 26 | aya.draw.point(math.Vec2.init(350, 350), 10, math.Color.sky_blue); 27 | aya.draw.point(math.Vec2.init(380, 380), 15, math.Color.magenta); 28 | aya.draw.rect(math.Vec2.init(387, 372), 40, 15, math.Color.dark_brown); 29 | aya.draw.hollowRect(math.Vec2.init(430, 372), 40, 15, 2, math.Color.yellow); 30 | aya.draw.circle(math.Vec2.init(100, 350), 20, 1, 12, math.Color.orange); 31 | 32 | const poly = [_]math.Vec2{ .{ .x = 400, .y = 30 }, .{ .x = 420, .y = 10 }, .{ .x = 430, .y = 80 }, .{ .x = 410, .y = 60 }, .{ .x = 375, .y = 40 } }; 33 | aya.draw.hollowPolygon(poly[0..], 2, math.Color.gold); 34 | aya.gfx.endPass(); 35 | 36 | aya.gfx.beginPass(.{ .clear_color = false }); 37 | tri_batch.begin(); 38 | tri_batch.drawTriangle(.{ .x = 50, .y = 50 }, .{ .x = 150, .y = 150 }, .{ .x = 0, .y = 150 }, Color.black); 39 | tri_batch.drawTriangle(.{ .x = 300, .y = 50 }, .{ .x = 350, .y = 150 }, .{ .x = 200, .y = 150 }, Color.lime); 40 | tri_batch.end(); 41 | aya.gfx.endPass(); 42 | } 43 | 44 | fn shutdown() !void { 45 | tri_batch.deinit(); 46 | } 47 | -------------------------------------------------------------------------------- /examples/tilemap.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const aya = @import("aya"); 3 | const sokol = @import("sokol"); 4 | const Map = aya.tilemap.Map; 5 | 6 | var map: *Map = undefined; 7 | var texture: aya.gfx.Texture = undefined; 8 | var batch: aya.gfx.AtlasBatch = undefined; 9 | var player: aya.math.RectI = undefined; 10 | var speed: f32 = 1.0; 11 | 12 | pub fn main() anyerror!void { 13 | try aya.run(.{ 14 | .init = init, 15 | .update = update, 16 | .render = render, 17 | .gfx = .{ 18 | .resolution_policy = .show_all_pixel_perfect, 19 | .design_width = 640, 20 | .design_height = 480, 21 | }, 22 | }); 23 | } 24 | 25 | fn init() !void { 26 | map = Map.initFromFile("examples/assets/platformer.json"); 27 | texture = map.loadTexture("examples/assets", .nearest); 28 | batch = aya.tilemap.renderer.renderTileLayerIntoAtlasBatch(map, map.tile_layers[0], texture); 29 | 30 | const spawn = map.object_layers[0].getObject("spawn"); 31 | player = aya.math.RectI{ .x = @as(i32, @intFromFloat(spawn.x)), .y = @as(i32, @intFromFloat(spawn.y)), .w = @as(i32, @intFromFloat(spawn.w)), .h = @as(i32, @intFromFloat(spawn.h)) }; 32 | } 33 | 34 | fn update() !void { 35 | var move = aya.math.Vec2{}; 36 | 37 | if (aya.input.keyDown(.right)) { 38 | move.x += speed; 39 | } else if (aya.input.keyDown(.left)) { 40 | move.x -= speed; 41 | } 42 | 43 | if (aya.input.keyDown(.up)) { 44 | move.y -= speed; 45 | } else if (aya.input.keyDown(.down)) { 46 | move.y += speed; 47 | } 48 | 49 | if (move.x != 0 or move.y != 0) { 50 | aya.tilemap.move(map, player, &move); 51 | player.x += @as(i32, @intFromFloat(move.x)); 52 | player.y += @as(i32, @intFromFloat(move.y)); 53 | } 54 | } 55 | 56 | fn render() !void { 57 | aya.gfx.beginPass(.{}); 58 | batch.draw(); 59 | aya.draw.hollowRect(.{ .x = @as(f32, @floatFromInt(player.x)), .y = @as(f32, @floatFromInt(player.y)) }, @as(f32, @floatFromInt(player.w)), @as(f32, @floatFromInt(player.h)), 1, aya.math.Color.white); 60 | aya.gfx.endPass(); 61 | } 62 | --------------------------------------------------------------------------------