initial commit

This commit is contained in:
Maciej Samborski 2025-09-04 09:08:36 +02:00
commit 314ae8b573
16 changed files with 1409 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.zig-cache/
zig-out/
libs/libopenwindow.so

42
build.zig Normal file
View File

@ -0,0 +1,42 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe_mod = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe_mod.addIncludePath(.{ .cwd_relative = "./deps/"});
const exe = b.addExecutable(.{
.name = "game",
.root_module = exe_mod,
});
exe.root_module.addCMacro("GL_GLEXT_PROTOTYPES", "1");
exe.linkLibC();
exe.linkSystemLibrary("OpenGl");
exe.linkSystemLibrary("X11");
exe.linkSystemLibrary("GLX");
exe.linkSystemLibrary("gallium");
exe.addLibraryPath(.{ .cwd_relative = "./libs/" });
exe.linkSystemLibrary("openwindow");
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}

86
build.zig.zon Normal file
View File

@ -0,0 +1,86 @@
.{
// This is the default name used by packages depending on this one. For
// example, when a user runs `zig fetch --save <url>`, this field is used
// as the key in the `dependencies` table. Although the user can choose a
// different name, most users will stick with this provided value.
//
// It is redundant to include "zig" in this name because it is already
// within the Zig package namespace.
.name = .game,
// This is a [Semantic Version](https://semver.org/).
// In a future version of Zig it will be used for package deduplication.
.version = "0.0.0",
// Together with name, this represents a globally unique package
// identifier. This field is generated by the Zig toolchain when the
// package is first created, and then *never changes*. This allows
// unambiguous detection of one package being an updated version of
// another.
//
// When forking a Zig project, this id should be regenerated (delete the
// field and run `zig build`) if the upstream project is still maintained.
// Otherwise, the fork is *hostile*, attempting to take control over the
// original project's identity. Thus it is recommended to leave the comment
// on the following line intact, so that it shows up in code reviews that
// modify the field.
.fingerprint = 0x232b318ceacc9a92, // Changing this has security and trust implications.
// Tracks the earliest Zig version that the package considers to be a
// supported use case.
.minimum_zig_version = "0.14.1",
// This field is optional.
// Each dependency must either provide a `url` and `hash`, or a `path`.
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
// Once all dependencies are fetched, `zig build` no longer requires
// internet connectivity.
.dependencies = .{
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
//.example = .{
// // When updating this field to a new URL, be sure to delete the corresponding
// // `hash`, otherwise you are communicating that you expect to find the old hash at
// // the new URL. If the contents of a URL change this will result in a hash mismatch
// // which will prevent zig from using it.
// .url = "https://example.com/foo.tar.gz",
//
// // This is computed from the file contents of the directory of files that is
// // obtained after fetching `url` and applying the inclusion rules given by
// // `paths`.
// //
// // This field is the source of truth; packages do not come from a `url`; they
// // come from a `hash`. `url` is just one of many possible mirrors for how to
// // obtain a package matching this `hash`.
// //
// // Uses the [multihash](https://multiformats.io/multihash/) format.
// .hash = "...",
//
// // When this is provided, the package is found in a directory relative to the
// // build root. In this case the package's hash is irrelevant and therefore not
// // computed. This field and `url` are mutually exclusive.
// .path = "foo",
//
// // When this is set to `true`, a package is declared to be lazily
// // fetched. This makes the dependency only get fetched if it is
// // actually used.
// .lazy = false,
//},
},
// Specifies the set of files and directories that are included in this package.
// Only files and directories listed here are included in the `hash` that
// is computed for this package. Only files listed here will remain on disk
// when using the zig package manager. As a rule of thumb, one should list
// files required for compilation plus any license(s).
// Paths are relative to the build root. Use the empty string (`""`) to refer to
// the build root itself.
// A directory listed here means that all files within, recursively, are included.
.paths = .{
"build.zig",
"build.zig.zon",
"src",
// For example...
//"LICENSE",
//"README.md",
},
}

227
deps/openwindow.h vendored Normal file
View File

@ -0,0 +1,227 @@
#ifndef _OPEN_WINDOW_H_
#define _OPEN_WINDOW_H_
#define WINDOW_DEFAULT_FPS 60
#define WINDOW_KEY_COUNT 256
#define WINDOW_MOUSE_BUTTON_COUNT 5
#define WINDOW_MOUSE_BUTTON1 (Button1)
#define WINDOW_MOUSE_BUTTON2 (Button2)
#define WINDOW_MOUSE_BUTTON3 (Button3)
#define WINDOW_MOUSE_BUTTON4 (Button4)
#define WINDOW_MOUSE_BUTTON5 (Button5)
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int gladLoadGL(void);
#include "GL/gl.h"
#ifndef _WIN32
#define Window X11Window
#include <GL/glx.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#undef Window
typedef struct {
GLXFBConfig config;
GLXContext context;
} GLX;
typedef struct {
Display * display;
X11Window window;
Colormap cmap;
long eventMask;
} X11;
typedef struct {
struct {
char current[WINDOW_KEY_COUNT];
char last[WINDOW_KEY_COUNT];
} keyboard;
struct {
bool current[WINDOW_MOUSE_BUTTON_COUNT];
bool last[WINDOW_MOUSE_BUTTON_COUNT];
int x;
int y;
} mouse;
} WindowIOState;
typedef struct {
struct timespec * t1;
struct timespec * t2;
double dt;
} WindowDeltaTime;
typedef struct Window Window;
typedef struct Window {
WindowIOState io;
GLX glx;
X11 x;
WindowDeltaTime dt;
uint32_t fps;
int width;
int height;
} Window;
#define WINDOW_KEY_Q ("q")
#define WINDOW_KEY_W ("w")
#define WINDOW_KEY_E ("e")
#define WINDOW_KEY_R ("r")
#define WINDOW_KEY_T ("t")
#define WINDOW_KEY_Y ("y")
#define WINDOW_KEY_U ("u")
#define WINDOW_KEY_I ("i")
#define WINDOW_KEY_O ("o")
#define WINDOW_KEY_P ("p")
#define WINDOW_KEY_A ("a")
#define WINDOW_KEY_S ("s")
#define WINDOW_KEY_D ("d")
#define WINDOW_KEY_F ("f")
#define WINDOW_KEY_G ("g")
#define WINDOW_KEY_H ("h")
#define WINDOW_KEY_J ("j")
#define WINDOW_KEY_K ("k")
#define WINDOW_KEY_L ("l")
#define WINDOW_KEY_Z ("z")
#define WINDOW_KEY_X ("x")
#define WINDOW_KEY_C ("c")
#define WINDOW_KEY_V ("v")
#define WINDOW_KEY_B ("b")
#define WINDOW_KEY_N ("n")
#define WINDOW_KEY_M ("m")
#define WINDOW_KEY_1 ("1")
#define WINDOW_KEY_2 ("2")
#define WINDOW_KEY_3 ("3")
#define WINDOW_KEY_4 ("4")
#define WINDOW_KEY_5 ("5")
#define WINDOW_KEY_6 ("6")
#define WINDOW_KEY_7 ("7")
#define WINDOW_KEY_8 ("8")
#define WINDOW_KEY_9 ("9")
#define WINDOW_KEY_0 ("0")
#define WINDOW_KEY_ESC ("Escape")
#define WINDOW_KEY_SPACE ("space")
bool windowKeyPressed(Window * window, char * key);
bool windowKeyReleased(Window * window, char * key);
bool windowKeyHeld(Window * window, char * key);
bool windowKeyShift(Window * window);
bool windowKeyCtrl(Window * window);
bool windowKeyAlt(Window * window);
bool windowMousePressed(Window * window, int button);
bool windowMouseHeld(Window * window, int button);
bool windowMouseReleased(Window * window, int button);
#else
#include <windows.h>
typedef struct {
HINSTANCE instance;
HDC display;
HWND window;
HGLRC context;
} WGL;
typedef struct {
BYTE current[WINDOW_KEY_COUNT];
BYTE last[WINDOW_KEY_COUNT];
} WindowIOState;
typedef struct {
LARGE_INTEGER t1;
LARGE_INTEGER t2;
LARGE_INTEGER freq;
double dt;
} WindowDeltaTime;
typedef struct Window Window;
typedef struct Window {
WindowIOState io;
WGL wgl;
WindowDeltaTime dt;
uint32_t fps;
int width;
int height;
} Window;
#define WINDOW_KEY_Q ('Q')
#define WINDOW_KEY_W ('W')
#define WINDOW_KEY_E ('E')
#define WINDOW_KEY_R ('R')
#define WINDOW_KEY_T ('T')
#define WINDOW_KEY_Y ('Y')
#define WINDOW_KEY_U ('U')
#define WINDOW_KEY_I ('I')
#define WINDOW_KEY_O ('O')
#define WINDOW_KEY_P ('P')
#define WINDOW_KEY_A ('A')
#define WINDOW_KEY_S ('S')
#define WINDOW_KEY_D ('D')
#define WINDOW_KEY_F ('F')
#define WINDOW_KEY_G ('G')
#define WINDOW_KEY_H ('H')
#define WINDOW_KEY_J ('J')
#define WINDOW_KEY_K ('K')
#define WINDOW_KEY_L ('L')
#define WINDOW_KEY_Z ('Z')
#define WINDOW_KEY_X ('X')
#define WINDOW_KEY_C ('C')
#define WINDOW_KEY_V ('V')
#define WINDOW_KEY_B ('B')
#define WINDOW_KEY_N ('N')
#define WINDOW_KEY_M ('M')
#define WINDOW_KEY_1 ('1')
#define WINDOW_KEY_2 ('2')
#define WINDOW_KEY_3 ('3')
#define WINDOW_KEY_4 ('4')
#define WINDOW_KEY_5 ('5')
#define WINDOW_KEY_6 ('6')
#define WINDOW_KEY_7 ('7')
#define WINDOW_KEY_8 ('8')
#define WINDOW_KEY_9 ('9')
#define WINDOW_KEY_0 ('0')
#define WINDOW_KEY_ESC (VK_ESCAPE)
#define WINDOW_KEY_SPACE (VK_SPACE)
bool windowKeyPressed(Window * window, int key);
bool windowKeyReleased(Window * window, int key);
bool windowKeyHeld(Window * window, int key);
bool windowKeyShift(Window * window);
bool windowKeyCtrl(Window * window);
bool windowKeyAlt(Window * window);
#endif // _WIN32
Window * openWindow(const char * name, size_t width, size_t height);
void windowSetFps(Window * window, uint32_t fps);
double windowGetDeltaTime(Window * window);
void windowGetSize(Window * window, int * width, int * height);
void windowGetMousePosition(Window * window, int * x, int * y);
void windowDraw(Window * window);
void windowHandleEvents(Window * window);
void closeWindow(Window * window);
int getOpenGLProcs(void);
#endif // _OPEN_WINDOW_H_

3
libs/LIBS.md Normal file
View File

@ -0,0 +1,3 @@
# Libs
1. [openwindow](https://gitea.hskl.org/MaciejSamborski/openwindow)

14
src/camera.zig Normal file
View File

@ -0,0 +1,14 @@
const std = @import("std");
const math = @import("math.zig");
const Camera = @This();
position: math.Vec2,
scale: f32,
pub fn init(position: math.Vec2) Camera {
return .{
.position = position,
.scale = 1.0,
};
}

15
src/common.zig Normal file
View File

@ -0,0 +1,15 @@
pub const Color = struct {
r: f32,
g: f32,
b: f32,
a: f32,
pub fn fromHex(hex: u32) Color {
return Color{
.r = @as(f32, @floatFromInt(((hex >> (8 * 3)) & 0xFF))) / @as(f32, @floatFromInt(0xFF)),
.g = @as(f32, @floatFromInt(((hex >> (8 * 2)) & 0xFF))) / @as(f32, @floatFromInt(0xFF)),
.b = @as(f32, @floatFromInt(((hex >> (8 * 1)) & 0xFF))) / @as(f32, @floatFromInt(0xFF)),
.a = @as(f32, @floatFromInt(((hex >> (8 * 0)) & 0xFF))) / @as(f32, @floatFromInt(0xFF)),
};
}
};

360
src/draw.zig Normal file
View File

@ -0,0 +1,360 @@
const std = @import("std");
const common = @import("common.zig");
const math = @import("math.zig");
const gl = @cImport(@cInclude("GL/gl.h"));
const Draw = @This();
const Renderer = @import("renderer.zig");
renderer: *Renderer,
allocator: std.mem.Allocator,
drawables: struct {
general: struct {
triangles: Drawables(gl.GL_TRIANGLES),
triangleFan: Drawables(gl.GL_TRIANGLE_FAN),
},
instanced: struct {
triangles: DrawablesInstanced(gl.GL_TRIANGLES),
triangleFan: DrawablesInstanced(gl.GL_TRIANGLE_FAN),
},
},
attribs: struct {
general: std.ArrayList(Attribute.General),
instanced: std.ArrayList(Attribute.Instanced),
},
pub fn init(renderer: *Renderer) !Draw {
return Draw{
.renderer = renderer,
.allocator = renderer.allocator,
.drawables = .{
.general = .{
.triangles = .init(renderer.allocator),
.triangleFan = .init(renderer.allocator),
},
.instanced = .{
.triangles = .init(renderer.allocator),
.triangleFan = .init(renderer.allocator),
},
},
.attribs = .{
.general = .init(renderer.allocator),
.instanced = .init(renderer.allocator),
},
};
}
pub const Attribute = union(enum) {
instanced: Instanced,
general: General,
pub const AttributeOptions = struct { size: gl.GLint, type: gl.GLenum, normalized: gl.GLboolean };
const AttributeData = struct {
name: []const u8,
loc: gl.GLuint,
size: gl.GLint,
type: gl.GLenum,
normalized: gl.GLboolean,
offset: usize,
};
pub const Instanced = struct {
data: AttributeData,
pub var offset: usize = 0;
fn new(draw: *Draw, name: []const u8, options: AttributeOptions) Instanced {
offset += @intCast(options.size);
return .{
.data = .{
.name = name,
.loc = @intCast(gl.glGetAttribLocation(draw.renderer.program, @ptrCast(name))),
.size = options.size,
.type = options.type,
.normalized = options.normalized,
.offset = offset - @as(usize, @intCast(options.size)),
},
};
}
};
pub const General = struct {
data: AttributeData,
pub var offset: usize = 0;
fn new(draw: *Draw, name: []const u8, options: AttributeOptions) General {
offset += @intCast(options.size);
return .{
.data = .{
.name = name,
.loc = @intCast(gl.glGetAttribLocation(draw.renderer.program, @ptrCast(name))),
.size = options.size,
.type = options.type,
.normalized = options.normalized,
.offset = offset - @as(usize, @intCast(options.size)),
},
};
}
};
};
pub fn addAttrib(draw: *Draw, name: []const u8, options: Attribute.AttributeOptions, instanced: bool) !void {
if (instanced)
try draw.attribs.instanced.append(.new(draw, name, options))
else
try draw.attribs.general.append( .new(draw, name, options))
;
}
pub fn getAttrib(draw: *Draw, name: []const u8) ?*Attribute {
for (draw.attribs.items) |attr| {
if (std.mem.eql(u8, name, attr.name)) {
return &attr;
}
}
return null;
}
pub const Uniform = struct {
loc: gl.GLuint,
};
pub fn getUniform(draw: *Draw, name: []const u8) Uniform {
return .{
.loc = @intCast(gl.glGetUniformLocation(draw.renderer.program, @ptrCast(name))),
};
}
pub fn Drawables(mode: gl.GLenum) type {
return struct {
data: switch (mode) {
gl.GL_TRIANGLES => std.ArrayList(f32),
gl.GL_TRIANGLE_FAN => std.ArrayList(std.ArrayList(f32)),
else => unreachable,
},
drawMode: gl.GLenum = mode,
fn init(allocator: std.mem.Allocator) @This() {
return .{
.data = .init(allocator),
};
}
};
}
pub fn DrawablesInstanced(mode: gl.GLenum) type {
return struct {
data: std.ArrayList(std.ArrayList(f32)),
instanceData: std.ArrayList(std.ArrayList(f32)),
drawMode: gl.GLenum = mode,
fn init(allocator: std.mem.Allocator) @This() {
return .{
.data = .init(allocator),
.instanceData = .init(allocator),
};
}
};
}
pub fn drawRectangle(draw: *Draw, corners: [4]@Vector(2, f32), depth: f32, color: common.Color) !void {
try draw.drawables.general.triangles.data.appendSlice(&.{
corners[3][0], corners[3][1], depth, color.r, color.g, color.b, color.a,
corners[0][0], corners[0][1], depth, color.r, color.g, color.b, color.a,
corners[1][0], corners[1][1], depth, color.r, color.g, color.b, color.a,
corners[3][0], corners[3][1], depth, color.r, color.g, color.b, color.a,
corners[1][0], corners[1][1], depth, color.r, color.g, color.b, color.a,
corners[2][0], corners[2][1], depth, color.r, color.g, color.b, color.a,
});
}
pub fn drawRectangleInstanced(draw: *Draw, corners: [4]@Vector(2, f32), offsets: []const @Vector(2, f32), depth: f32, color: common.Color) !void {
var data = std.ArrayList(f32).init(draw.allocator);
var instanceData = std.ArrayList(f32).init(draw.allocator);
try data.appendSlice(&.{
corners[3][0], corners[3][1], depth, color.r, color.g, color.b, color.a,
corners[0][0], corners[0][1], depth, color.r, color.g, color.b, color.a,
corners[1][0], corners[1][1], depth, color.r, color.g, color.b, color.a,
corners[3][0], corners[3][1], depth, color.r, color.g, color.b, color.a,
corners[1][0], corners[1][1], depth, color.r, color.g, color.b, color.a,
corners[2][0], corners[2][1], depth, color.r, color.g, color.b, color.a,
});
for (offsets) |offset| {
try instanceData.appendSlice(&.{
offset[0], offset[1], depth
});
}
try draw.drawables.instanced.triangles.data.append(data);
try draw.drawables.instanced.triangles.instanceData.append(instanceData);
}
pub fn drawTriangle(draw: *Draw, corners: [3]@Vector(2, f32), depth: f32, color: common.Color) !void {
try draw.drawables.general.triangles.data.appendSlice(&.{
corners[0][0], corners[0][1], depth, color.r, color.g, color.b, color.a,
corners[1][0], corners[1][1], depth, color.r, color.g, color.b, color.a,
corners[2][0], corners[2][1], depth, color.r, color.g, color.b, color.a,
});
}
pub fn drawTriangleInstanced(draw: *Draw, corners: [3]@Vector(2, f32), offsets: []const @Vector(2, f32), depth: f32, color: common.Color) !void {
var data = std.ArrayList(f32).init(draw.allocator);
var instanceData = std.ArrayList(f32).init(draw.allocator);
try data.appendSlice(&.{
corners[0][0], corners[0][1], depth, color.r, color.g, color.b, color.a,
corners[1][0], corners[1][1], depth, color.r, color.g, color.b, color.a,
corners[2][0], corners[2][1], depth, color.r, color.g, color.b, color.a,
});
for (offsets) |offset| {
try instanceData.appendSlice(&.{
offset[0], offset[1], depth
});
}
try draw.drawables.instanced.triangles.data.append(data);
try draw.drawables.instanced.triangles.instanceData.append(instanceData);
}
pub const CirclePrecission = 64;
pub fn drawCircle(
draw: *Draw,
origin: @Vector(2, f32),
depth: f32,
radius: f32,
arc: f32,
wound: enum { clockwise, counterclockwise },
color: common.Color
) !void {
var data: std.ArrayList(f32) = .init(draw.allocator);
try data.appendSlice(&.{
origin[0], origin[1], depth, color.r, color.g, color.b, color.a,
});
const r = if (wound == .clockwise) radius else -radius;
for (0..@intFromFloat(std.math.round((CirclePrecission + 1) * arc))) |i| {
var v = math.Vec2{ 0, r };
math.rotateVector2(&v, 2.0 * std.math.pi / @as(f32, @floatFromInt(CirclePrecission)) * @as(f32, @floatFromInt(i)));
v += origin;
try data.appendSlice(&.{
v[0], v[1], depth, color.r, color.g, color.b, color.a,
});
}
try draw.drawables.general.triangleFan.data.append(data);
}
pub fn drawCircleInstanced(
draw: *Draw,
origin: @Vector(2, f32),
offsets: []const @Vector(2, f32),
depth: f32,
radius: f32,
arc: f32,
wound: enum { clockwise, counterclockwise },
color: common.Color
) !void {
var data = std.ArrayList(f32).init(draw.allocator);
var instanceData = std.ArrayList(f32).init(draw.allocator);
try data.appendSlice(&.{
origin[0], origin[1], depth, color.r, color.g, color.b, color.a,
});
for (0..@intFromFloat(std.math.round((CirclePrecission + 1) * arc))) |i| {
var v = math.Vec2{ 0, radius };
math.rotateVector2(&v, 2.0 * std.math.pi / @as(f32, @floatFromInt(CirclePrecission)) * @as(f32, @floatFromInt(i)));
if (wound == .counterclockwise) v *= @splat(-1);
v += origin;
try data.appendSlice(&.{
v[0], v[1], depth, color.r, color.g, color.b, color.a,
});
}
for (offsets) |offset| {
try instanceData.appendSlice(&.{
offset[0], offset[1], depth
});
}
try draw.drawables.instanced.triangleFan.data.append(data);
try draw.drawables.instanced.triangleFan.instanceData.append(instanceData);
}
pub fn drawLine(draw: *Draw, points: [2]@Vector(2, f32), width: f32, depth: f32, color: common.Color) !void {
const dx = points[1][0] - points[0][0];
const dy = points[1][1] - points[0][1];
var angle = std.math.atan2(dy, dx);
if (angle < 0) angle += std.math.pi * 2;
const inverse = @mod((angle + std.math.pi), (std.math.pi * 2));
const ps1 = math.findLinePoints(points[0], width, angle);
const ps2 = math.findLinePoints(points[1], width, inverse);
try draw.drawables.general.triangles.data.appendSlice(&.{
ps1.p1[0], ps1.p1[1], depth, color.r, color.g, color.b, color.a,
ps1.p2[0], ps1.p2[1], depth, color.r, color.g, color.b, color.a,
ps2.p1[0], ps2.p1[1], depth, color.r, color.g, color.b, color.a,
ps2.p1[0], ps2.p1[1], depth, color.r, color.g, color.b, color.a,
ps2.p2[0], ps2.p2[1], depth, color.r, color.g, color.b, color.a,
ps1.p1[0], ps1.p1[1], depth, color.r, color.g, color.b, color.a,
});
}
pub fn drawLineInstanced(draw: *Draw, points: [2]@Vector(2, f32), offsets: []const @Vector(2, f32), width: f32, depth: f32, color: common.Color) !void {
var data = std.ArrayList(f32).init(draw.allocator);
var instanceData = std.ArrayList(f32).init(draw.allocator);
const dx = points[1][0] - points[0][0];
const dy = points[1][1] - points[0][1];
var angle = std.math.atan2(dy, dx);
if (angle < 0) angle += std.math.pi * 2;
const inverse = @mod((angle + std.math.pi), (std.math.pi * 2));
const ps1 = math.findLinePoints(points[0], width, angle);
const ps2 = math.findLinePoints(points[1], width, inverse);
try data.appendSlice(&.{
ps1.p1[0], ps1.p1[1], depth, color.r, color.g, color.b, color.a,
ps1.p2[0], ps1.p2[1], depth, color.r, color.g, color.b, color.a,
ps2.p1[0], ps2.p1[1], depth, color.r, color.g, color.b, color.a,
ps2.p1[0], ps2.p1[1], depth, color.r, color.g, color.b, color.a,
ps2.p2[0], ps2.p2[1], depth, color.r, color.g, color.b, color.a,
ps1.p1[0], ps1.p1[1], depth, color.r, color.g, color.b, color.a,
});
for (offsets) |offset| {
try instanceData.appendSlice(&.{
offset[0], offset[1], depth
});
}
try draw.drawables.instanced.triangles.data.append(data);
try draw.drawables.instanced.triangles.instanceData.append(instanceData);
}

42
src/game.zig Normal file
View File

@ -0,0 +1,42 @@
const std = @import("std");
const Renderer = @import("renderer.zig");
const Camera = @import("camera.zig");
const Draw = @import("draw.zig");
const Grid = @import("grid.zig");
const Game = @This();
const World = struct {
grid: Grid,
size: @Vector(2, usize),
fn init(allocator: std.mem.Allocator, width: usize, height: usize) !World {
return World{
.grid = try Grid.init(allocator, width, height),
.size = .{ width, height },
};
}
};
const Player = struct {
position: PlayerPosition = .{ 0, 0 },
const PlayerPosition = @Vector(2, f32);
const default: Player = .{};
};
camera: Camera,
draw: Draw,
world: World,
player: Player,
pub fn init(renderer: *Renderer, width: u32, height: u32) !Game {
return .{
.camera = .init(.{ @as(f32, @floatFromInt(renderer.width.*))/2.0, @as(f32, @floatFromInt(renderer.height.*))/2.0 }),
.draw = try .init(renderer),
.world = try World.init(renderer.allocator, width, height),
.player = .default,
};
}

59
src/grid.zig Normal file
View File

@ -0,0 +1,59 @@
const std = @import("std");
const common = @import("common.zig");
const Grid = @This();
pub const Cell = struct {
const Face = struct {
color: common.Color,
};
topFace: Face = .{
.color = common.Color.fromHex(0xFF0000FF),
},
leftFace: Face = .{
.color = common.Color.fromHex(0x00FF00FF),
},
rightFace: Face = .{
.color = common.Color.fromHex(0x0000FFFF),
},
};
pub const CellMapKey = struct {
x: usize,
y: usize,
};
pub fn coord(x: usize, y: usize) CellMapKey {
return .{
.x = x,
.y = y,
};
}
const CellMap = std.AutoHashMap(CellMapKey, Cell);
map: CellMap,
pub fn init(allocator: std.mem.Allocator, width: usize, height: usize) !Grid {
var map: CellMap = CellMap.init(allocator);
try map.ensureTotalCapacity(@intCast(width * height));
for (0..width) |w| {
for (0..height) |h| {
try map.put(coord(w, h), Cell{});
}
}
return .{
.map = map,
};
}
pub fn get(grid: *Grid, key: CellMapKey) ?Cell {
return grid.map.get(key);
}
pub fn put(grid: *Grid, key: CellMapKey, cell: Cell) !void {
try grid.map.put(key, cell);
}

148
src/main.zig Normal file
View File

@ -0,0 +1,148 @@
const std = @import("std");
const gl = @cImport(@cInclude("GL/gl.h"));
const ow = @cImport(@cInclude("openwindow.h"));
const Renderer = @import("renderer.zig");
const Game = @import("game.zig");
const Grid = @import("grid.zig");
const Draw = @import("draw.zig");
const Transform = @import("transform.zig");
const math = @import("math.zig");
const common = @import("common.zig");
pub fn main() !void {
const window: *ow.Window = ow.openWindow("game", 1920, 1080);
defer ow.closeWindow(window);
var aa = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer aa.deinit();
const allocator = aa.allocator();
var renderer = try Renderer.init(allocator, @ptrCast(&window.width), @ptrCast(&window.height));
var g = try Game.init(&renderer, 4, 4);
var d = try Draw.init(&renderer);
try addCommonAttribs(&d);
try d.drawLineInstanced(
.{ .{ 0, 1004 }, .{ 0, -4 }},
&.{
.{ 0, 0 },
.{ 100, 0 },
.{ 200, 0 },
.{ 300, 0 },
.{ 400, 0 },
.{ 500, 0 },
.{ 600, 0 },
.{ 700, 0 },
.{ 800, 0 },
.{ 900, 0 },
.{ 1000, 0 },
},
4,
0,
common.Color.fromHex(0x000000FF)
);
try d.drawLineInstanced(
.{ .{ 1004, 0 }, .{ -4, 0 }},
&.{
.{ 0, 0 },
.{ 0, 100 },
.{ 0, 200 },
.{ 0, 300 },
.{ 0, 400 },
.{ 0, 500 },
.{ 0, 600 },
.{ 0, 700 },
.{ 0, 800 },
.{ 0, 900 },
.{ 0, 1000 },
},
4,
0,
common.Color.fromHex(0x000000FF)
);
try d.drawRectangle(.{
.{ 0, 0 },
.{ 100, 0 },
.{ 100, 100 },
.{ 0, 100 },
}, 0, common.Color.fromHex(0xFF0000FF),
);
try d.drawCircle(.{ 50, 50 },
0.01,
50,
1.0,
.clockwise,
common.Color.fromHex(0x00FF00FF)
);
var globalTransform = Transform.init();
while (!ow.windowKeyPressed(window, @ptrCast(@constCast(ow.WINDOW_KEY_ESC)))) {
ow.windowHandleEvents(window);
defer ow.windowDraw(window);
handleInput(window, &g);
const ortho = math.Mat4x4.orthographic(0.0, @floatFromInt(window.width), 0.0, @floatFromInt(window.height), -1.0, 1.0);
globalTransform.set(.orthographic, ortho);
globalTransform.set(.translation, math.Mat4x4.translation(g.camera.position));
globalTransform.set(.scale, math.Mat4x4.scale(g.camera.scale));
gl.glUniformMatrix4fv(@intCast(d.getUniform("uTransform").loc), 1, gl.GL_FALSE, &globalTransform.get().data[0][0]);
try renderer.renderDraw(&d);
}
}
fn addCommonAttribs(d: *Draw) !void {
// General
try d.addAttrib("aPos", .{
.size = 3,
.type = gl.GL_FLOAT,
.normalized = gl.GL_FALSE,
}, false);
try d.addAttrib("aColor", .{
.size = 4,
.type = gl.GL_FLOAT,
.normalized = gl.GL_FALSE,
}, false);
// Instanced
try d.addAttrib("aInstanceOffset", .{
.size = 3,
.type = gl.GL_FLOAT,
.normalized = gl.GL_FALSE,
}, true);
}
fn handleInput(window: *ow.Window, game: *Game) void {
const dt: f32 = @floatCast(ow.windowGetDeltaTime(window));
const speed = 300;
if (ow.windowKeyHeld(window, @ptrCast(@constCast(ow.WINDOW_KEY_W))))
game.camera.position[1] -= speed*dt;
if (ow.windowKeyHeld(window, @ptrCast(@constCast(ow.WINDOW_KEY_S))))
game.camera.position[1] += speed*dt;
if (ow.windowKeyHeld(window, @ptrCast(@constCast(ow.WINDOW_KEY_A))))
game.camera.position[0] += speed*dt;
if (ow.windowKeyHeld(window, @ptrCast(@constCast(ow.WINDOW_KEY_D))))
game.camera.position[0] -= speed*dt;
if (ow.windowKeyHeld(window, @ptrCast(@constCast(ow.WINDOW_KEY_Q))))
game.camera.scale += 2*dt;
if (ow.windowKeyHeld(window, @ptrCast(@constCast(ow.WINDOW_KEY_E))))
game.camera.scale -= 2*dt;
}

107
src/math.zig Normal file
View File

@ -0,0 +1,107 @@
const std = @import("std");
pub const Mat4x4 = struct {
const Self = @This();
data: [4][4]f32,
pub fn init(data: [16]f32) Mat4x4 {
return .{
.data = .{
data[0..4].*,
data[4..8].*,
data[8..12].*,
data[12..16].*,
},
};
}
pub fn mult(matrix: *Self, other: Mat4x4) void {
var result: [4][4]f32 = undefined;
for (&result, 0..) |*col, j| {
for (col, 0..) |*elem, i| {
elem.* = matrix.data[0][i] * other.data[j][0] +
matrix.data[1][i] * other.data[j][1] +
matrix.data[2][i] * other.data[j][2] +
matrix.data[3][i] * other.data[j][3];
}
}
matrix.data = result;
}
pub fn mults(matrix: *Self, scalar: f32) void {
for (&matrix.data) |*col| {
for (col) |*row| {
row.* *= scalar;
}
}
}
pub fn transform(matrix: *const Self, point: *Vec2) void {
const x = point[0];
const y = point[1];
point[0] = matrix.data[0][0]*x + matrix.data[1][0]*y + matrix.data[2][0]*0.0 + matrix.data[3][0]*1.0;
point[1] = matrix.data[0][1]*x + matrix.data[1][1]*y + matrix.data[2][1]*0.0 + matrix.data[3][1]*1.0;
}
pub const zero: Mat4x4 = .init(@splat(0));
pub const id: Mat4x4 = .init(.{
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
});
pub fn orthographic(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) Mat4x4 {
return .init(.{
2 / (right - left), 0, 0, 0,
0, 2 / (top - bottom), 0, 0,
0, 0, -2 / (far - near), 0,
-((right + left)/(right - left)), -((top + bottom)/(top - bottom)), -((far + near)/(far - near)), 1
});
}
pub fn translation(offset: Vec2) Mat4x4 {
return .init(.{
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
offset[0], offset[1], 0.0, 1.0,
});
}
pub fn scale(scalar: f32) Mat4x4 {
return .init(.{
scalar, 0.0, 0.0, 0.0,
0.0, scalar, 0.0, 0.0,
0.0, 0.0, scalar, 0.0,
0.0, 0.0, 0.0, 1.0,
});
}
};
pub const Vec2 = @Vector(2, f32);
pub fn rotateVector2(vec: *Vec2, angle: f32) void {
const x: f32 = vec[0];
const y: f32 = vec[1];
vec[0] = x * std.math.cos(angle) - y * std.math.sin(angle);
vec[1] = x * std.math.sin(angle) + y * std.math.cos(angle);
}
pub fn vector2FromRotation(angle: f32) Vec2 {
return .{
std.math.cos(angle),
std.math.sin(angle),
};
}
pub fn findLinePoints(point: Vec2, width: f32, angle: f32) struct { p1: Vec2, p2: Vec2 } {
const p1 = Vec2 { point[0] - width * std.math.sin(angle), point[1] + width * std.math.cos(angle) };
const p2 = Vec2 { point[0] + width * std.math.sin(angle), point[1] - width * std.math.cos(angle) };
return .{ .p1 = p1, .p2 = p2 };
}

225
src/renderer.zig Normal file
View File

@ -0,0 +1,225 @@
const std = @import("std");
const Game = @import("game.zig");
const Grid = @import("grid.zig");
const Draw = @import("draw.zig");
const gl = @cImport(@cInclude("GL/gl.h"));
const Renderer = @This();
const ShaderCompileError = error{
COMPILATION_FAILED,
};
const opengl = struct {
const shaderKind = enum {
VERTEX,
FRAGMENT,
};
fn createShader(allocator: std.mem.Allocator, source: [:0]const u8, kind: shaderKind) !gl.GLuint {
const stype: c_uint = switch (kind) {
.VERTEX => gl.GL_VERTEX_SHADER,
.FRAGMENT => gl.GL_FRAGMENT_SHADER,
};
const shader = gl.glCreateShader(stype);
gl.glShaderSource(shader, 1, @ptrCast(&source.ptr), null);
gl.glCompileShader(shader);
var len: c_int = 0;
var info: []u8 = undefined;
gl.glGetShaderiv(shader, gl.GL_INFO_LOG_LENGTH, &len);
if (len > 0) {
info = try allocator.alloc(u8, @intCast(len));
gl.glGetShaderInfoLog(shader, len, &len, @ptrCast(info));
std.debug.print("{s}\n", .{info});
allocator.free(info);
return ShaderCompileError.COMPILATION_FAILED;
}
return shader;
}
fn createProgram(allocator: std.mem.Allocator, vertex: [:0]const u8, fragment: [:0]const u8) !gl.GLuint {
const program = gl.glCreateProgram();
const vertexShader = try createShader(allocator, vertex, .VERTEX);
const fragmentShader = try createShader(allocator, fragment, .FRAGMENT);
gl.glAttachShader(program, vertexShader);
gl.glAttachShader(program, fragmentShader);
gl.glLinkProgram(program);
return program;
}
fn init(allocator: std.mem.Allocator) !struct { program: c_uint, vao: c_uint, vbo: c_uint, vboi: c_uint } {
const program = try opengl.createProgram(allocator, vss, fss);
var vao: gl.GLuint = 0;
gl.glCreateVertexArrays(1, @ptrCast(&vao));
var vbo: gl.GLuint = 0;
gl.glCreateBuffers(1, @ptrCast(&vbo));
var vboi: gl.GLuint = 0;
gl.glCreateBuffers(1, @ptrCast(&vboi));
gl.glBindVertexArray(vao);
gl.glUseProgram(program);
gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA);
gl.glEnable(gl.GL_BLEND);
gl.glEnable(gl.GL_DEPTH_TEST);
gl.glDepthFunc(gl.GL_LESS);
// TODO: enable
//gl.glEnable(gl.GL_CULL_FACE);
//gl.glCullFace(gl.GL_BACK);
return .{
.program = program,
.vao = vao,
.vbo = vbo,
.vboi = vboi
};
}
};
const vss = @embedFile("shaders/main.vs");
const fss = @embedFile("shaders/main.fs");
allocator: std.mem.Allocator,
program: gl.GLuint = 0,
vao: gl.GLuint = 0,
vbo: gl.GLuint = 0,
vboi: gl.GLuint = 0,
width: *u32 = undefined,
height: *u32 = undefined,
pub fn init(allocator: std.mem.Allocator, width: *u32, height: *u32) !Renderer {
// destructuring no worky :(
const initState = try opengl.init(allocator);
return .{
.allocator = allocator,
.program = initState.program,
.vao = initState.vao,
.vbo = initState.vbo,
.vboi = initState.vboi,
.width = width,
.height = height,
};
}
//pub fn renderGame(renderer: *const Renderer, allocator: std.mem.Allocator, game: *Game) !void {
// _ = game;
//
// var data = std.ArrayList(f32).init(allocator);
//
// try data.appendSlice(&[_]f32{ -0.8, -0.8, 1.0, 0.0, 0.0, -0.3, -0.8, 0.0, 1.0, 0.0, -0.8, -0.3, 0.0, 0.0, 1.0 });
//
// var acc: usize = 0;
// for (renderer.attribs.items) |attrib| {
// acc += @intCast(attrib.size);
// }
//
// gl.glBindBuffer(gl.GL_ARRAY_BUFFER, renderer.vbo);
// gl.glBufferData(gl.GL_ARRAY_BUFFER, @as(gl.GLsizeiptr, @intCast(data.items.len)) * @sizeOf(f32), @ptrCast(data.items.ptr), gl.GL_STATIC_DRAW);
//
// for (renderer.attribs.items) |attrib| {
// gl.glEnableVertexAttribArray(attrib.loc);
// gl.glVertexAttribPointer(attrib.loc, attrib.size, attrib.type, attrib.normalized, @intCast(renderer.stride * @sizeOf(f32)), @ptrFromInt(attrib.offset));
// }
//
// gl.glClearColor(0.8, 0.8, 0.8, 1.0);
// gl.glClear(gl.GL_COLOR_BUFFER_BIT);
//
// gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3);
//}
pub fn renderDraw(renderer: *Renderer, draw: *Draw) !void {
gl.glViewport(0, 0, @intCast(renderer.width.*), @intCast(renderer.height.*));
gl.glClearColor(1.0, 1.0, 1.0, 1.0);
gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT);
gl.glUniform1i(@intCast(draw.getUniform("uInstanced").loc), 0);
inline for (@typeInfo(@TypeOf(draw.drawables.general)).@"struct".fields) |field| {
const drawables = @field(draw.drawables.general, field.name);
renderer.render(draw, drawables);
}
gl.glUniform1i(@intCast(draw.getUniform("uInstanced").loc), 1);
inline for (@typeInfo(@TypeOf(draw.drawables.instanced)).@"struct".fields) |field| {
const drawables = @field(draw.drawables.instanced, field.name);
renderer.renderInstanced(draw, drawables);
}
}
fn render(renderer: *Renderer, draw: *Draw, drawables: anytype) void {
if (drawables.data.items.len == 0) return;
switch (@TypeOf(drawables)) {
Draw.Drawables(gl.GL_TRIANGLES) => {
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, renderer.vbo);
gl.glBufferData(gl.GL_ARRAY_BUFFER, @as(gl.GLsizeiptr, @intCast(drawables.data.items.len)) * @sizeOf(f32), @ptrCast(drawables.data.items.ptr), gl.GL_STATIC_DRAW);
for (draw.attribs.general.items) |attrib| {
gl.glEnableVertexAttribArray(attrib.data.loc);
gl.glVertexAttribPointer(attrib.data.loc, attrib.data.size, attrib.data.type, attrib.data.normalized, @intCast(Draw.Attribute.General.offset * @sizeOf(f32)), @ptrFromInt(attrib.data.offset * @sizeOf(f32)));
}
gl.glDrawArrays(gl.GL_TRIANGLES, 0, @intCast(drawables.data.items.len / Draw.Attribute.General.offset));
},
Draw.Drawables(gl.GL_TRIANGLE_FAN) => {
for (drawables.data.items) |data| {
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, renderer.vbo);
gl.glBufferData(gl.GL_ARRAY_BUFFER, @as(gl.GLsizeiptr, @intCast(data.items.len)) * @sizeOf(f32), @ptrCast(data.items.ptr), gl.GL_STATIC_DRAW);
for (draw.attribs.general.items) |attrib| {
gl.glEnableVertexAttribArray(attrib.data.loc);
gl.glVertexAttribPointer(attrib.data.loc, attrib.data.size, attrib.data.type, attrib.data.normalized, @intCast(Draw.Attribute.General.offset * @sizeOf(f32)), @ptrFromInt(attrib.data.offset * @sizeOf(f32)));
}
gl.glDrawArrays(gl.GL_TRIANGLE_FAN, 0, @intCast(data.items.len / Draw.Attribute.General.offset));
}
},
else => unreachable,
}
}
fn renderInstanced(renderer: *Renderer, draw: *Draw, drawables: anytype) void {
if (drawables.data.items.len == 0) return;
for (drawables.data.items, drawables.instanceData.items) |data, instanceData| {
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, renderer.vbo);
gl.glBufferData(gl.GL_ARRAY_BUFFER, @as(gl.GLsizeiptr, @intCast(data.items.len)) * @sizeOf(f32), @ptrCast(data.items.ptr), gl.GL_STATIC_DRAW);
for (draw.attribs.general.items) |attrib| {
gl.glEnableVertexAttribArray(attrib.data.loc);
gl.glVertexAttribPointer(attrib.data.loc, attrib.data.size, attrib.data.type, attrib.data.normalized, @intCast(Draw.Attribute.General.offset * @sizeOf(f32)), @ptrFromInt(attrib.data.offset * @sizeOf(f32)));
}
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, renderer.vboi);
gl.glBufferData(gl.GL_ARRAY_BUFFER, @as(gl.GLsizeiptr, @intCast(instanceData.items.len)) * @sizeOf(f32), @ptrCast(instanceData.items.ptr), gl.GL_STATIC_DRAW);
for (draw.attribs.instanced.items) |attrib| {
gl.glEnableVertexAttribArray(attrib.data.loc);
gl.glVertexAttribPointer(attrib.data.loc, attrib.data.size, attrib.data.type, attrib.data.normalized, @intCast(Draw.Attribute.Instanced.offset * @sizeOf(f32)), @ptrFromInt(attrib.data.offset * @sizeOf(f32)));
gl.glVertexAttribDivisor(renderer.vao, 1);
}
const mode = if (@TypeOf(drawables) == Draw.DrawablesInstanced(gl.GL_TRIANGLES)) gl.GL_TRIANGLES else gl.GL_TRIANGLE_FAN;
gl.glDrawArraysInstanced(mode, 0, @intCast(data.items.len / Draw.Attribute.General.offset), @intCast(instanceData.items.len / Draw.Attribute.Instanced.offset));
}
}

8
src/shaders/main.fs Normal file
View File

@ -0,0 +1,8 @@
#version 330
in vec4 vColor;
out vec4 outColor;
void main() {
outColor = vColor;
}

18
src/shaders/main.vs Normal file
View File

@ -0,0 +1,18 @@
#version 330
uniform bool uInstanced;
uniform mat4 uTransform;
in vec3 aPos;
in vec3 aInstanceOffset;
in vec4 aColor;
out vec4 vColor;
void main() {
if (uInstanced) {
gl_Position = uTransform * vec4(aPos.xyz + aInstanceOffset.xyz, 1.0);
} else {
gl_Position = uTransform * vec4(aPos.xyz, 1.0);
}
vColor = aColor;
}

51
src/transform.zig Normal file
View File

@ -0,0 +1,51 @@
const Transform = @This();
const math = @import("math.zig");
base: math.Mat4x4,
translation: math.Mat4x4,
scale: math.Mat4x4,
pub fn init() Transform {
return .{
.base = .id,
.translation = .id,
.scale = .id,
};
}
const TransformType = enum {
orthographic,
translation,
scale,
};
pub fn set(transform: *Transform, tag: TransformType, transformation: math.Mat4x4) void {
switch (tag) {
.orthographic => transform.base = transformation,
.translation => transform.translation = transformation,
.scale => transform.scale = transformation,
}
}
pub fn add(transform: *Transform, tag: TransformType, transformation: math.Mat4x4) void {
switch (tag) {
.orthographic => @compileError("you shouldn't add to orthographic transformation, use `.set` instead!"),
.translation => transform.translation.mult(transformation),
.scale => transform.scale.mult(transformation),
}
}
pub fn get(transform: *Transform) math.Mat4x4 {
const width: f32 = 2.0 / transform.base.data[0][0];
const height: f32 = 2.0 / transform.base.data[1][1];
var result = transform.base;
result.mult(math.Mat4x4.translation(math.Vec2 { width/2.0, height/2.0 }));
result.mult(transform.scale);
result.mult(math.Mat4x4.translation(math.Vec2 { -width/2.0, -height/2.0 }));
result.mult(transform.translation);
return result;
}