first commit

This commit is contained in:
2026-03-28 22:36:32 +01:00
commit 4ab7f93ac6
39 changed files with 2142 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.zig-cache
**.bak
zig-out

69
build.zig Normal file
View File

@@ -0,0 +1,69 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "PCG_6",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
.imports = &.{},
}),
.use_llvm = true,
});
const zgl = b.dependency("zgl", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("zgl", zgl.module("zgl"));
const zglfw = b.dependency("zglfw", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("zglfw", zglfw.module("root"));
exe.root_module.linkLibrary(zglfw.artifact("glfw"));
const zmath = b.dependency("zmath", .{});
exe.root_module.addImport("zmath", zmath.module("root"));
const zmesh = b.dependency("zmesh", .{ .target = target, .optimize = optimize, .shape_use_32bit_indices = false });
exe.root_module.addImport("zmesh", zmesh.module("root"));
const zstbi = b.dependency("zstbi", .{});
exe.root_module.addImport("zstbi", zstbi.module("root"));
const zgui = b.dependency("zgui", .{
.shared = false,
.backend = .glfw_opengl3,
.with_implot = false,
.with_gizmo = false,
.with_node_editor = false,
.with_te = false,
.with_freetype = false,
.with_knobs = false,
.disable_obsolete = true,
});
exe.root_module.addImport("zgui", zgui.module("root"));
exe.root_module.linkLibrary(zgui.artifact("imgui"));
b.installArtifact(exe);
const run_step = b.step("run", "Run the app");
const run_cmd = b.addRunArtifact(exe);
run_step.dependOn(&run_cmd.step);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const exe_tests = b.addTest(.{
.root_module = exe.root_module,
});
const run_exe_tests = b.addRunArtifact(exe_tests);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_exe_tests.step);
}

37
build.zig.zon Normal file
View File

@@ -0,0 +1,37 @@
.{
.name = .computer_graphics_lab,
.version = "0.0.0",
.fingerprint = 0x44a1624c4685a0df, // Changing this has security and trust implications.
.minimum_zig_version = "0.15.2",
.dependencies = .{
.zmath = .{
.url = "git+https://github.com/zig-gamedev/zmath.git#3a5955b2b72cd081563fbb084eff05bffd1e3fbb",
.hash = "zmath-0.11.0-dev-wjwivdMsAwD-xaLj76YHUq3t9JDH-X16xuMTmnDzqbu2",
},
.zglfw = .{
.url = "git+https://github.com/zig-gamedev/zglfw.git#0dd29d8073487c9fe1e45e6b729b3aac271d5a71",
.hash = "zglfw-0.10.0-dev-zgVDNIG4IQBWN_sfMD-xfC9bJS2hbBN2W7jNlDLovcdC",
},
.zgl = .{
.url = "git+https://github.com/ziglibs/zgl.git#6ee54c287f3c4f176bd05a92746828bb791a1ad0",
.hash = "zgl-1.1.0-p_NpAJNECgCzUGx0aF5KFsmD5VOwfw8LaOsN0fcoU2bi",
},
.zmesh = .{
.url = "git+https://github.com/zig-gamedev/zmesh.git#a9c23ba7440b8c03cbc2bec89a3285fe84cbb50f",
.hash = "zmesh-0.11.0-dev-oO3A5lKRCgCGK8Krro4Rj_F_MhO8LT487re5u_DNIzvl",
},
.zstbi = .{
.url = "git+https://github.com/zig-gamedev/zstbi#664305dd52640be15cbebd7cd73d1199679933e1",
.hash = "zstbi-0.11.0-dev-L0Ea_2yVBwA4uh-tHfgdhZcQaWRoZVYlNGs4Dl_WTeuR",
},
.zgui = .{
.url = "git+https://github.com/zig-gamedev/zgui.git#ce016156a8520c438e886cd6c0b605e10ee7af3d",
.hash = "zgui-0.6.0-dev--L6sZKkSbgCUBXLVfwJDPpMkETz7ll-mmQYQae-nMxjt",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}

44
shaders/default.frag Normal file
View File

@@ -0,0 +1,44 @@
#version 460 core
in vec2 v_texcoord;
in vec3 v_normal;
in vec3 v_position;
layout (binding = 1, std140) uniform Light {
uniform vec4 light_color;
uniform vec3 light_pos;
uniform vec3 camera_pos;
};
uniform sampler2D u_texture;
out vec4 f_color;
vec4 pointLight() {
vec3 light_vector = light_pos - v_position;
float dist = length(light_vector);
float a = 1.0;
float b = 0.7;
float intensity = 1 / (a * dist * dist + b * dist + 1.0f);
// ambient lighting
float ambient = 0.2;
// diffuse lighting
float diffuse_light = 0.7;
vec3 normal = normalize(v_normal);
vec3 light_direction = normalize(light_vector);
float diffuse = diffuse_light * max(dot(normal, light_direction), 0);
// specular lighting
float specular_light = 0.7;
vec3 view_direction = normalize(camera_pos - v_position);
vec3 reflection_direction = reflect(-light_direction, normal);
vec3 halfway_vec = normalize(view_direction + light_direction);
float specular = specular_light * pow(max(dot(normal, halfway_vec), 0), 100);
return (texture(u_texture, v_texcoord) * (diffuse * intensity + ambient) + specular * intensity) * light_color;
}
void main() {
f_color = pointLight();
}

72
shaders/default.vert Normal file
View File

@@ -0,0 +1,72 @@
#version 460 core
struct PositionData {
float x;
float y;
float z;
};
struct UvData {
float u;
float v;
};
struct NormalData {
float x;
float y;
float z;
};
layout (binding = 0, std430) restrict readonly buffer _positions {
PositionData pos_data[];
};
layout (binding = 1, std430) restrict readonly buffer _uvs {
UvData uv_data[];
};
layout (binding = 2, std430) restrict readonly buffer _normals {
NormalData norm_data[];
};
vec4 getPosition(int index) {
return vec4(
pos_data[index].x,
pos_data[index].y,
pos_data[index].z,
1
);
}
vec2 getUv(int index) {
return vec2(
uv_data[index].u,
uv_data[index].v
);
}
vec3 getNormal(int index) {
return vec3(
norm_data[index].x,
norm_data[index].y,
norm_data[index].z
);
}
uniform mat4 u_model_matrix;
uniform mat4 u_normal_matrix;
layout (binding = 0, std140) uniform SharedMatrices {
mat4 projection;
mat4 cam_matrix;
};
out vec2 v_texcoord;
out vec3 v_normal;
out vec3 v_position;
void main() {
gl_Position = projection * cam_matrix * u_model_matrix * getPosition(gl_VertexID);
v_texcoord = getUv(gl_VertexID);
v_normal = mat3(u_normal_matrix) * getNormal(gl_VertexID);
v_position = vec3(u_model_matrix * getPosition(gl_VertexID));
}

13
shaders/light.frag Normal file
View File

@@ -0,0 +1,13 @@
#version 460 core
layout (binding = 1, std140) uniform Light {
uniform vec4 light_color;
uniform vec3 light_pos;
uniform vec3 camera_pos;
};
out vec4 f_color;
void main() {
f_color = light_color;
}

19
shaders/light.vert Normal file
View File

@@ -0,0 +1,19 @@
#version 460 core
layout (binding = 0, std140) uniform SharedMatrices {
mat4 projection;
mat4 cam_matrix;
};
layout (binding = 1, std140) uniform Light {
uniform vec4 light_color;
uniform vec3 light_pos;
uniform vec3 camera_pos;
};
uniform vec4 u_position;
void main() {
gl_Position = projection * cam_matrix * u_position;
gl_PointSize = 20 / distance(u_position.xyz, camera_pos);
}

10
shaders/skybox.frag Normal file
View File

@@ -0,0 +1,10 @@
#version 460 core
in vec3 v_texcoord;
out vec4 f_color;
uniform samplerCube cubemap_texture;
void main() {
f_color = texture(cubemap_texture, v_texcoord);
}

35
shaders/skybox.vert Normal file
View File

@@ -0,0 +1,35 @@
#version 460 core
// layout (location = 0) in vec3 a_position;
struct PositionData {
float x;
float y;
float z;
};
layout (binding = 0, std430) restrict readonly buffer _positions {
PositionData pos_data[];
};
vec4 getPosition(int index) {
return vec4(
pos_data[index].x,
pos_data[index].y,
pos_data[index].z,
1
);
}
layout (binding = 0, std140) uniform SharedMatrices {
mat4 projection;
mat4 cam_matrix;
};
out vec3 v_texcoord;
void main() {
vec4 a_position = getPosition(gl_VertexID);
vec4 WVP_position = projection * mat4(mat3(cam_matrix)) * a_position;
gl_Position = WVP_position.xyww;
v_texcoord = a_position.xyz;
}

27
src/cube.zig Normal file
View File

@@ -0,0 +1,27 @@
const IndexType = @import("zmesh").Shape.IndexType;
pub const positions = [_][3]f32{
[3]f32{ -1.0, 1.0, -1.0 },
[3]f32{ -1.0, -1.0, -1.0 },
[3]f32{ 1.0, -1.0, -1.0 },
[3]f32{ 1.0, 1.0, -1.0 },
[3]f32{ -1.0, -1.0, 1.0 },
[3]f32{ -1.0, 1.0, 1.0 },
[3]f32{ 1.0, -1.0, 1.0 },
[3]f32{ 1.0, 1.0, 1.0 },
};
pub const indices = [_]IndexType{
0, 1, 2,
2, 3, 0,
4, 1, 0,
0, 5, 4,
2, 6, 7,
7, 3, 2,
4, 5, 7,
7, 6, 4,
0, 3, 7,
7, 5, 0,
1, 4, 2,
2, 4, 6,
};

113
src/gl_helper/camera.zig Normal file
View File

@@ -0,0 +1,113 @@
const std = @import("std");
const approxEqAbs = std.math.approxEqAbs;
const floatEps = std.math.floatEps;
const degToRad = std.math.degreesToRadians;
const glfw = @import("zglfw");
const zm = @import("zmath");
const Shader = @import("shader.zig");
const Camera = @This();
width: f32,
height: f32,
position: zm.Vec,
orientation: zm.Vec = zm.f32x4(0, 0, -1, 1),
up: zm.Vec = zm.f32x4(0, 1, 0, 1),
view: zm.Mat,
projection: zm.Mat,
initial_speed: f32,
sensitivity: f32 = 100.0,
controller: struct {
forward: bool = false,
backward: bool = false,
left: bool = false,
right: bool = false,
up: bool = false,
down: bool = false,
accelerate: bool = false,
},
pub fn init(position: zm.Vec, width: f32, height: f32, initial_speed: f32) Camera {
return .{
.width = width,
.height = height,
.position = position,
.initial_speed = initial_speed,
.controller = .{},
.view = zm.identity(),
.projection = zm.identity(),
};
}
pub fn updateProjection(self: *Camera, near: f32, far: f32) void {
self.projection = zm.perspectiveFovRhGl(std.math.degreesToRadians(60), self.width / self.height, near, far);
}
pub fn updateView(self: *Camera) void {
self.view = zm.lookAtRh(self.position, self.position + self.orientation, self.up);
}
pub fn updateControls(self: *Camera, key: glfw.Key, action: glfw.Action) void {
const bool_action = @intFromEnum(action) != 0;
switch (key) {
.a => self.controller.left = bool_action,
.w => self.controller.forward = bool_action,
.s => self.controller.backward = bool_action,
.d => self.controller.right = bool_action,
.space => self.controller.up = bool_action,
.left_control => self.controller.down = bool_action,
.left_shift => self.controller.accelerate = bool_action,
else => {},
}
}
pub fn move(self: *Camera, delta_time: f32) void {
var current_speed = zm.f32x4s(self.initial_speed * delta_time);
if (self.controller.accelerate) {
current_speed = zm.f32x4s(self.initial_speed * delta_time * 2);
}
if (self.controller.forward) {
self.position += self.orientation * current_speed;
}
if (self.controller.backward) {
self.position -= self.orientation * current_speed;
}
if (self.controller.left) {
self.position -= zm.normalize3(zm.cross3(self.orientation, self.up)) * current_speed;
}
if (self.controller.right) {
self.position += zm.normalize3(zm.cross3(self.orientation, zm.normalize3(self.up))) * current_speed;
}
if (self.controller.up) {
self.position += zm.normalize3(zm.cross3(zm.cross3(self.orientation, self.up), self.orientation)) * current_speed;
}
if (self.controller.down) {
self.position -= zm.normalize3(zm.cross3(zm.cross3(self.orientation, self.up), self.orientation)) * current_speed;
}
}
pub fn updateAngle(self: *Camera, xpos: f32, ypos: f32) void {
// Normalizes and shifts the coordinates of the cursor such that they begin in the middle of the screen
// and then "transforms" them into degrees
const rot_x: f32 = self.sensitivity * (ypos - (self.height / 2)) / self.height;
const rot_y: f32 = self.sensitivity * (xpos - (self.width / 2)) / self.width;
// Calculates upcoming vertical change in the Orientation
// Rotate the view vector by the horizontal angle around the vertical axis
const horizontal_axis_rotation = zm.quatFromNormAxisAngle(self.up, degToRad(-rot_y));
self.orientation = zm.normalize3(zm.rotate(horizontal_axis_rotation, self.orientation));
// Rotate the view vector by the vertical angle around the horizontal axis
const u = zm.cross3(self.orientation, self.up);
const vertical_axis_rotation = zm.quatFromNormAxisAngle(u, degToRad(-rot_x));
const new_orientation = zm.normalize3(zm.rotate(vertical_axis_rotation, self.orientation));
if (!approxEqAbs(f32, new_orientation[1], -1, 0.001)) {
self.orientation = new_orientation;
}
}

63
src/gl_helper/color.zig Normal file
View File

@@ -0,0 +1,63 @@
const zm = @import("zmath");
pub const Byte = struct {
// Custom raylib color palette for amazing visuals on WHITE background
pub const light_gray = [_]u8{ 200, 200, 200, 255 }; // Light Gray
pub const gray = [_]u8{ 130, 130, 130, 255 }; // Gray
pub const dark_gray = [_]u8{ 80, 80, 80, 255 }; // Dark Gray
pub const yellow = [_]u8{ 253, 249, 0, 255 }; // Yellow
pub const gold = [_]u8{ 255, 203, 0, 255 }; // Gold
pub const orange = [_]u8{ 255, 161, 0, 255 }; // Orange
pub const pink = [_]u8{ 255, 109, 194, 255 }; // Pink
pub const red = [_]u8{ 230, 41, 55, 255 }; // Red
pub const maroon = [_]u8{ 190, 33, 55, 255 }; // Maroon
pub const green = [_]u8{ 0, 228, 48, 255 }; // Green
pub const lime = [_]u8{ 0, 158, 47, 255 }; // Lime
pub const dark_green = [_]u8{ 0, 117, 44, 255 }; // Dark Green
pub const sky_blue = [_]u8{ 102, 191, 255, 255 }; // Sky Blue
pub const blue = [_]u8{ 0, 121, 241, 255 }; // Blue
pub const dark_blue = [_]u8{ 0, 82, 172, 255 }; // Dark Blue
pub const purple = [_]u8{ 200, 122, 255, 255 }; // Purple
pub const violet = [_]u8{ 135, 60, 190, 255 }; // Violet
pub const dark_purple = [_]u8{ 112, 31, 126, 255 }; // Dark Purple
pub const beige = [_]u8{ 211, 176, 131, 255 }; // Beige
pub const brown = [_]u8{ 127, 106, 79, 255 }; // Brown
pub const dark_brown = [_]u8{ 76, 63, 47, 255 }; // Dark Brown
pub const white = [_]u8{ 255, 255, 255, 255 }; // White
pub const black = [_]u8{ 0, 0, 0, 255 }; // Black
pub const blank = [_]u8{ 0, 0, 0, 0 }; // Blank (Transparent)
pub const magenta = [_]u8{ 255, 0, 255, 255 }; // Magenta
pub const ray_white = [_]u8{ 245, 245, 245, 255 }; // Raylib whire (raylib logo)
};
pub const Float = struct {
// Custom raylib color palette for amazing visuals on WHITE background
pub const light_gray = zm.normalize4(zm.f32x4(200, 200, 200, 255)); // Light Gray
pub const gray = zm.normalize4(zm.f32x4(130, 130, 130, 255)); // Gray
pub const dark_gray = zm.normalize4(zm.f32x4(80, 80, 80, 255)); // Dark Gray
pub const yellow = zm.normalize4(zm.f32x4(253, 249, 0, 255)); // Yellow
pub const gold = zm.normalize4(zm.f32x4(255, 203, 0, 255)); // Gold
pub const orange = zm.normalize4(zm.f32x4(255, 161, 0, 255)); // Orange
pub const pink = zm.normalize4(zm.f32x4(255, 109, 194, 255)); // Pink
pub const red = zm.normalize4(zm.f32x4(230, 41, 55, 255)); // Red
pub const maroon = zm.normalize4(zm.f32x4(190, 33, 55, 255)); // Maroon
pub const green = zm.normalize4(zm.f32x4(0, 228, 48, 255)); // Green
pub const lime = zm.normalize4(zm.f32x4(0, 158, 47, 255)); // Lime
pub const dark_green = zm.normalize4(zm.f32x4(0, 117, 44, 255)); // Dark Green
pub const sky_blue = zm.normalize4(zm.f32x4(102, 191, 255, 255)); // Sky Blue
pub const blue = zm.normalize4(zm.f32x4(0, 121, 241, 255)); // Blue
pub const dark_blue = zm.normalize4(zm.f32x4(0, 82, 172, 255)); // Dark Blue
pub const purple = zm.normalize4(zm.f32x4(200, 122, 255, 255)); // Purple
pub const violet = zm.normalize4(zm.f32x4(135, 60, 190, 255)); // Violet
pub const dark_purple = zm.normalize4(zm.f32x4(112, 31, 126, 255)); // Dark Purple
pub const beige = zm.normalize4(zm.f32x4(211, 176, 131, 255)); // Beige
pub const brown = zm.normalize4(zm.f32x4(127, 106, 79, 255)); // Brown
pub const dark_brown = zm.normalize4(zm.f32x4(76, 63, 47, 255)); // Dark Brown
pub const white = zm.normalize4(zm.f32x4(255, 255, 255, 255)); // White
pub const black = zm.normalize4(zm.f32x4(0, 0, 0, 255)); // Black
pub const blank = zm.normalize4(zm.f32x4(0, 0, 0, 0)); // Blank (Transparent)
pub const magenta = zm.normalize4(zm.f32x4(255, 0, 255, 255)); // Magenta
pub const ray_white = zm.normalize4(zm.f32x4(245, 245, 245, 255)); // Raylib whire (raylib logo)
};

125
src/gl_helper/mesh.zig Normal file
View File

@@ -0,0 +1,125 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const zgl = @import("zgl");
const Shape = @import("zmesh").Shape;
const helper = @import("root.zig");
const Camera = @import("camera.zig");
const Shader = @import("shader.zig");
const Texture = @import("texture.zig");
index_count: usize,
primitive_type: zgl.PrimitiveType,
textures: ?[]const Texture,
position_data_buffer: zgl.Buffer,
uv_data_buffer: ?zgl.Buffer,
normal_data_buffer: ?zgl.Buffer,
vao: zgl.VertexArray,
pub fn initShape(shape: Shape, textures: ?[]const Texture, primitive_type: zgl.PrimitiveType) @This() {
return initVertices(
shape.positions,
shape.texcoords,
shape.normals,
shape.indices,
textures,
primitive_type,
);
}
pub fn initVertices(
positions: []const [3]f32,
maybe_texcoords: ?[]const [2]f32,
maybe_normals: ?[]const [3]f32,
indices: []const Shape.IndexType,
textures: ?[]const Texture,
primitive_type: zgl.PrimitiveType,
) @This() {
const vao = zgl.VertexArray.create();
const position_data_buffer = helper.initSsbo([3]f32, positions, .{});
var uv_data_buffer: ?zgl.Buffer = null;
var normal_data_buffer: ?zgl.Buffer = null;
if (maybe_texcoords) |texcoords| {
uv_data_buffer = helper.initSsbo([2]f32, texcoords, .{});
}
if (maybe_normals) |normals| {
normal_data_buffer = helper.initSsbo([3]f32, normals, .{});
}
const ebo = helper.initEbo(Shape.IndexType, indices);
defer ebo.delete();
vao.elementBuffer(ebo);
return .{
.index_count = indices.len,
.textures = textures,
.primitive_type = primitive_type,
.vao = vao,
.position_data_buffer = position_data_buffer,
.uv_data_buffer = uv_data_buffer,
.normal_data_buffer = normal_data_buffer,
};
}
pub fn draw(self: @This(), shader: Shader) void {
drawInstanced(self, shader, 1);
}
pub fn drawInstanced(self: @This(), shader: Shader, instance_count: u32) void {
shader.use();
self.vao.bind();
if (self.textures) |textures| {
textures[0].bind();
shader.updateUniformInt("u_texture", @intCast(textures[0].unit));
}
zgl.bindBufferBase(.shader_storage_buffer, 0, self.position_data_buffer);
if (self.uv_data_buffer) |uv_buffer| {
zgl.bindBufferBase(.shader_storage_buffer, 1, uv_buffer);
}
if (self.normal_data_buffer) |normal_buffer| {
zgl.bindBufferBase(.shader_storage_buffer, 2, normal_buffer);
}
if (Shape.IndexType == u32) {
zgl.drawElementsInstanced(self.primitive_type, self.index_count, .unsigned_int, 0, instance_count);
} else {
zgl.drawElementsInstanced(self.primitive_type, self.index_count, .unsigned_short, 0, instance_count);
}
if (self.textures) |textures| {
for (textures) |tex| {
zgl.bindTextureUnit(.invalid, tex.unit);
}
}
zgl.bindBufferBase(.shader_storage_buffer, 0, .invalid);
zgl.bindBufferBase(.shader_storage_buffer, 1, .invalid);
zgl.bindBufferBase(.shader_storage_buffer, 2, .invalid);
}
pub fn delete(self: @This()) void {
self.vao.delete();
if (self.textures) |textures| {
for (textures) |tex| {
tex.delete();
}
}
self.position_data_buffer.delete();
if (self.uv_data_buffer) |uv_buffer| {
uv_buffer.delete();
}
if (self.normal_data_buffer) |normal_buffer| {
normal_buffer.delete();
}
}

44
src/gl_helper/model.zig Normal file
View File

@@ -0,0 +1,44 @@
const std = @import("std");
const ArrayList = std.ArrayList;
const zm = @import("zmath");
const Mesh = @import("mesh.zig");
const Shader = @import("shader.zig");
const Tree = @import("../tree.zig").Tree;
pub const JointData = struct {
mesh: Mesh,
translation: zm.Mat,
rotation: zm.Quat,
};
data: Tree(JointData),
pub fn init(
data: Tree(JointData),
) @This() {
return .{ .data = data };
}
pub fn draw(self: *@This(), shader: Shader) void {
var iter = self.data.depthFirstIterator();
while (iter.next()) |node| {
var rotation = node.val.rotation;
var current = node;
while (current.parent) |parent| {
rotation = zm.qmul(rotation, parent.val.rotation);
current = parent;
}
const model_matrix = zm.mul(zm.quatToMat(rotation), node.val.translation);
const normal_matrix = zm.transpose(zm.inverse(model_matrix));
shader.updateUniformMatrix("u_model_matrix", &.{model_matrix});
shader.updateUniformMatrix("u_normal_matrix", &.{normal_matrix});
node.val.mesh.draw(shader);
}
}
pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
self.data.deinit(allocator);
}

53
src/gl_helper/root.zig Normal file
View File

@@ -0,0 +1,53 @@
const refAllDeclsRecursive = @import("std").testing.refAllDeclsRecursive;
const zgl = @import("zgl");
const zm = @import("zmath");
pub const Camera = @import("camera.zig");
pub const Color = @import("color.zig");
pub const Mesh = @import("mesh.zig");
pub const Model = @import("model.zig");
pub const Shader = @import("shader.zig");
pub const Texture = @import("texture.zig");
pub fn initEbo(T: type, indices: []const T) zgl.Buffer {
var buffer = zgl.Buffer.create();
buffer.storage(T, indices.len, indices.ptr, .{});
return buffer;
}
pub fn initVbo(T: type, items: []const T, flags: zgl.BufferStorageFlags) zgl.Buffer {
const buffer = zgl.Buffer.create();
buffer.storage(T, items.len, items.ptr, flags);
return buffer;
}
pub fn initUbo(T: type, shaders: []const Shader, name: [:0]const u8, binding: u32, size: u32) zgl.Buffer {
for (shaders) |shader| {
if (zgl.getUniformBlockIndex(shader.id, name)) |index| {
zgl.uniformBlockBinding(shader.id, index, binding);
}
}
var buffer = zgl.Buffer.create();
buffer.storage(T, size, null, .{ .dynamic_storage = true });
zgl.bindBufferRange(.uniform_buffer, binding, buffer, 0, size * @sizeOf(T));
return buffer;
}
pub fn initSsbo(T: type, vertices: []const T, flags: zgl.BufferStorageFlags) zgl.Buffer {
var buffer = zgl.Buffer.create();
buffer.storage(T, vertices.len, vertices.ptr, flags);
return buffer;
}
pub fn linkAttribute(vao: zgl.VertexArray, vbo: zgl.Buffer, location: u32, binding_index: u32, size: u32, attrib_type: zgl.Type, normalised: bool, offset: usize, stride: usize) void {
vao.vertexBuffer(binding_index, vbo, offset, stride);
vao.enableVertexAttribute(location);
vao.attribFormat(location, size, attrib_type, normalised, offset);
vao.attribBinding(location, binding_index);
}
comptime {
refAllDeclsRecursive(@This());
}

72
src/gl_helper/shader.zig Normal file
View File

@@ -0,0 +1,72 @@
const std = @import("std");
// const gl = @import("zopengl").wrapper;
const zgl = @import("zgl");
const zm = @import("zmath");
const Shader = @This();
id: zgl.Program,
pub fn initSource(vertex_source: []const u8, fragment_source: []const u8) !Shader {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const vertex = zgl.Shader.create(.vertex);
vertex.source(1, &vertex_source);
vertex.compile();
const fragment = zgl.Shader.create(.fragment);
fragment.source(1, &fragment_source);
fragment.compile();
const program = zgl.Program.create();
program.attach(vertex);
program.attach(fragment);
program.link();
return .{ .id = program };
}
pub fn initFile(vertex_path: []const u8, fragment_path: []const u8) !Shader {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const vertex_source = try readFromFile(arena.allocator(), vertex_path);
const fragment_source = try readFromFile(arena.allocator(), fragment_path);
return initSource(vertex_source, fragment_source);
}
pub fn use(self: Shader) void {
self.id.use();
}
pub fn updateUniformMatrix(self: Shader, name: [:0]const u8, matrices: []const zm.Mat) void {
self.id.uniformMatrix4(self.id.uniformLocation(name), false, matrices);
}
pub fn updateUniformVec4(self: Shader, name: [:0]const u8, vectors: []const zm.Vec) void {
if (self.id.uniformLocation(name)) |location| {
zgl.binding.programUniform4fv(@intFromEnum(self.id), @intCast(location), @intCast(vectors.len), @ptrCast(vectors));
}
}
pub fn updateUniformVec3(self: Shader, name: [:0]const u8, vectors: []const [3]f32) void {
if (self.id.uniformLocation(name)) |location| {
zgl.binding.programUniform3fv(@intFromEnum(self.id), @intCast(location), @intCast(vectors.len), @ptrCast(vectors));
}
// zgl.uniform3fv(self.id.uniformLocation(name), vectors);
}
pub fn updateUniformInt(self: Shader, name: [:0]const u8, value: i32) void {
self.id.uniform1i(self.id.uniformLocation(name), value);
}
pub fn delete(self: Shader) void {
self.id.delete();
}
/// The caller is responsible for freeing the content.
fn readFromFile(allocator: std.mem.Allocator, file_path: []const u8) ![:0]u8 {
return std.fs.cwd().readFileAllocOptions(allocator, file_path, std.math.maxInt(usize), null, .@"1", 0);
}

108
src/gl_helper/texture.zig Normal file
View File

@@ -0,0 +1,108 @@
const std = @import("std");
const zgl = @import("zgl");
const zstbi = @import("zstbi");
const Image = zstbi.Image;
const Texture = @This();
id: zgl.Texture,
target: zgl.TextureTarget,
unit: u32,
pub fn init(unit: u32, target: zgl.TextureTarget) Texture {
const id = zgl.Texture.create(target);
return .{ .id = id, .unit = unit, .target = target };
}
pub fn loadCubemapFromFile(self: Texture, pathnames: []const [:0]const u8) !void {
const info = Image.info(pathnames[0]);
var internal_formal: zgl.TextureInternalFormat = undefined;
var pixel_formal: zgl.PixelFormat = undefined;
internal_formal, pixel_formal = switch (info.num_components) {
1 => .{ .r8, .red },
2 => .{ .rg8, .rg },
3 => .{ .rgb8, .rgb },
4 => .{ .rgba8, .rgba },
else => unreachable,
};
self.id.storage2D(1, internal_formal, info.width, info.height);
for (pathnames, 0..) |pathname, i| {
var image = try Image.loadFromFile(pathname, info.num_components);
defer image.deinit();
self.id.subImage3D(
0,
0,
0,
i,
info.width,
info.height,
1,
pixel_formal,
if (zstbi.is16bit(pathname)) .unsigned_short else .unsigned_byte,
image.data.ptr,
);
}
}
pub fn loadCubemap(self: Texture, images: []const [*]const u8, width: usize, height: usize, pixel_type: zgl.PixelType, channels: u32) void {
var internal_formal: zgl.TextureInternalFormat = undefined;
var pixel_formal: zgl.PixelFormat = undefined;
internal_formal, pixel_formal = switch (channels) {
1 => .{ .r8, .red },
2 => .{ .rg8, .rg },
3 => .{ .rgb8, .rgb },
4 => .{ .rgba8, .rgba },
else => unreachable,
};
self.id.storage2D(1, internal_formal, width, height);
for (0..6) |i| {
self.id.subImage3D(0, 0, 0, i, width, height, 1, pixel_formal, pixel_type, images[i]);
}
}
pub fn loadFromFile(self: Texture, pathname: [:0]const u8) !void {
var image = try Image.loadFromFile(pathname, 0);
defer image.deinit();
load(
self,
image.data.ptr,
image.width,
image.height,
if (zstbi.is16bit(pathname)) .unsigned_short else .unsigned_byte,
image.num_components,
);
}
pub fn load(self: Texture, image: [*]const u8, width: usize, height: usize, pixel_type: zgl.PixelType, channels: u32) void {
var internal_formal: zgl.TextureInternalFormat = undefined;
var pixel_formal: zgl.PixelFormat = undefined;
internal_formal, pixel_formal = switch (channels) {
1 => .{ .r8, .red },
2 => .{ .rg8, .rg },
3 => .{ .rgb8, .rgb },
4 => .{ .rgba8, .rgba },
else => unreachable,
};
self.id.storage2D(1, internal_formal, width, height);
self.id.subImage2D(0, 0, 0, width, height, pixel_formal, pixel_type, image);
self.id.generateMipmap();
}
pub fn bind(self: Texture) void {
self.id.bindTo(self.unit);
}
pub fn unbind(self: Texture) void {
zgl.bindTextureUnit(.invalid, self.unit);
}
pub fn delete(self: Texture) void {
self.id.delete();
}

View File

@@ -0,0 +1,49 @@
const zgl = @import("zgl");
const zgui = @import("zgui");
const zm = @import("zmath");
const glfw = @import("zglfw");
const Window = glfw.Window;
const glfw_helper = @import("root.zig");
const AppState = glfw_helper.AppState;
pub fn cursorPosCallback(window: *Window, xpos: f64, ypos: f64) callconv(.c) void {
const app_state = window.getUserPointer(AppState).?;
if (!app_state.render_gui) {
app_state.camera.updateAngle(@floatCast(xpos), @floatCast(ypos));
glfw.setCursorPos(window, @as(f32, @floatFromInt(app_state.width)) / 2, @as(f32, @floatFromInt(app_state.height)) / 2);
}
}
pub fn keyCallback(window: *Window, key: glfw.Key, _: c_int, action: glfw.Action, _: glfw.Mods) callconv(.c) void {
const app_state = window.getUserPointer(AppState).?;
if (action == .press) {
switch (key) {
.tab => {
app_state.render_gui = !app_state.render_gui;
if (app_state.render_gui) {
window.setInputMode(.cursor, .normal) catch {};
} else {
window.setInputMode(.cursor, .disabled) catch {};
app_state.camera.updateAngle(@as(f32, @floatFromInt(app_state.width)) / 2, @as(f32, @floatFromInt(app_state.height)) / 2);
}
glfw.setCursorPos(window, @as(f32, @floatFromInt(app_state.width)) / 2, @as(f32, @floatFromInt(app_state.height)) / 2);
},
.escape => window.setShouldClose(true),
else => {},
}
}
if (!app_state.render_gui) {
app_state.camera.updateControls(key, action);
}
}
pub fn messageCallback(source: zgl.DebugSource, msg_type: zgl.DebugMessageType, id: usize, severity: zgl.DebugSeverity, message: []const u8) void {
const print = @import("std").debug.print;
print("{t}, {t}, {t}, {d}: {s}\n", .{ source, msg_type, severity, id, message });
}

96
src/glfw_helper/root.zig Normal file
View File

@@ -0,0 +1,96 @@
const std = @import("std");
const refAllDeclsRecursive = std.testing.refAllDeclsRecursive;
const zgl = @import("zgl");
const zm = @import("zmath");
const glfw = @import("zglfw");
const Window = glfw.Window;
const gl_helper = @import("../gl_helper/root.zig");
const callbacks = @import("callbacks.zig");
const Scene = @import("scene.zig").Scene;
pub const AppState = struct {
height: i32,
width: i32,
camera: gl_helper.Camera,
scene: Scene,
light_pos: zm.Vec,
render_gui: bool,
pub fn init(height: i32, width: i32, scene: Scene, camera_pos: zm.Vec, light_pos: zm.Vec, initial_speed: f32) AppState {
return .{
.height = height,
.width = width,
.scene = scene,
.camera = .init(camera_pos, @floatFromInt(width), @floatFromInt(height), initial_speed),
.light_pos = light_pos,
.render_gui = true,
};
}
};
pub const GlfwContext = struct {
window: *Window,
pub fn init(app_state: *AppState, title: [:0]const u8) !GlfwContext {
// glfw: initialize and configure
try glfw.init();
glfw.windowHint(.client_api, .opengl_api);
glfw.windowHint(.context_version_major, 4);
glfw.windowHint(.context_version_minor, 6);
glfw.windowHint(.opengl_profile, .opengl_core_profile);
glfw.windowHint(.opengl_forward_compat, true);
glfw.windowHint(.doublebuffer, true);
glfw.windowHint(.focused, true);
glfw.windowHint(.resizable, false);
// glfw window creation
const window = try Window.create(app_state.width, app_state.height, title, null, null);
glfw.makeContextCurrent(window);
glfw.swapInterval(1);
if (glfw.rawMouseMotionSupported()) {
try window.setInputMode(.raw_mouse_motion, true);
}
_ = window.setKeyCallback(callbacks.keyCallback);
_ = window.setCursorPosCallback(callbacks.cursorPosCallback);
// zgl: load all OpenGL function pointers
try zgl.loadExtensions(@as(glfw.GlProc, undefined), getProcAddress);
zgl.enable(.debug_output);
zgl.enable(.debug_output_synchronous);
zgl.debugMessageCallback({}, callbacks.messageCallback);
window.setUserPointer(app_state);
return .{ .window = window };
}
pub fn postRender(self: GlfwContext) void {
self.window.swapBuffers();
glfw.pollEvents();
}
pub fn cleanUp(self: GlfwContext) void {
self.window.destroy();
glfw.terminate();
}
};
pub fn getTime() f64 {
return glfw.getTime();
}
fn getProcAddress(_: glfw.GlProc, proc: [:0]const u8) ?zgl.binding.FunctionPointer {
return @alignCast(glfw.getProcAddress(proc));
}
comptime {
refAllDeclsRecursive(@This());
}

View File

@@ -0,0 +1,7 @@
const std = @import("std");
const glfw = @import("zglfw");
pub const Scene = enum(i32) {
robot,
fish,
};

370
src/main.zig Normal file
View File

@@ -0,0 +1,370 @@
const std = @import("std");
const glfw = @import("zglfw");
const zgl = @import("zgl");
const zgui = @import("zgui");
const zm = @import("zmath");
const Mat = zm.Mat;
const zmesh = @import("zmesh");
const Shape = zmesh.Shape;
const zstbi = @import("zstbi");
const gl_helper = @import("gl_helper/root.zig");
const Shader = gl_helper.Shader;
const Texture = gl_helper.Texture;
const Mesh = gl_helper.Mesh;
const Model = gl_helper.Model;
const ByteColor = gl_helper.Color.Byte;
const FloatColor = gl_helper.Color.Float;
const robot_texture_data = ByteColor.green;
const glfw_helper = @import("glfw_helper/root.zig");
const GlfwContect = glfw_helper.GlfwContext;
const AppState = glfw_helper.AppState;
const Tree = @import("tree.zig").Tree;
const floor_texture_data =
ByteColor.blue ++ ByteColor.white ++
ByteColor.white ++ ByteColor.blue;
var app_state = AppState.init(
600,
600,
.robot,
zm.f32x4(0, 1, 3, 1),
zm.f32x4(0, 1, 0.5, 1),
1,
);
const window_flags = zgui.WindowFlags{
.no_move = true,
.no_resize = true,
.no_title_bar = true,
.no_nav_inputs = true,
};
pub fn main() !void {
var debug_allocator = std.heap.DebugAllocator(.{}).init;
defer _ = debug_allocator.deinit();
const allocator = debug_allocator.allocator();
const context = try GlfwContect.init(&app_state, "Lab 6");
zmesh.init(allocator);
defer zmesh.deinit();
zstbi.init(allocator);
defer zstbi.deinit();
zgui.init(allocator);
defer zgui.deinit();
zgui.io.setIniFilename(null);
zgui.backend.init(context.window);
defer zgui.backend.deinit();
const style = zgui.getStyle();
style.setColorsBuiltin(.dark);
style.window_border_size = 0;
// shaders
const model_shader = try Shader.initFile("shaders/default.vert", "shaders/default.frag");
defer model_shader.delete();
const light_shader = try Shader.initFile("shaders/light.vert", "shaders/light.frag");
defer light_shader.delete();
const skybox_shader = try Shader.initFile("shaders/skybox.vert", "shaders/skybox.frag");
defer skybox_shader.delete();
// textures
const floor_texture: []const Texture = &.{
Texture.init(0, .@"2d"),
};
floor_texture[0].id.parameter(.mag_filter, .nearest);
floor_texture[0].id.parameter(.min_filter, .nearest);
floor_texture[0].load(&floor_texture_data, 2, 2, .unsigned_byte, 4);
const robot_texture: []const Texture = &.{
Texture.init(0, .@"2d"),
};
robot_texture[0].id.parameter(.mag_filter, .nearest);
robot_texture[0].id.parameter(.min_filter, .nearest);
robot_texture[0].load(&robot_texture_data, 1, 1, .unsigned_byte, 4);
// meshes
const floor = initFloor(floor_texture);
defer floor.delete();
var robot = try initRobot(allocator, robot_texture);
defer robot.deinit(allocator);
// uniform data
var light_color = zm.f32x4(1, 1, 1, 1);
light_shader.updateUniformVec4("u_position", &.{app_state.light_pos});
model_shader.updateUniformMatrix("u_model_matrix", &.{zm.identity()});
model_shader.updateUniformMatrix("u_normal_matrix", &.{zm.identity()});
// uniform buffer objects
const matrix_ubo = gl_helper.initUbo(
zm.Mat,
&.{ model_shader, light_shader, skybox_shader },
"SharedMatrices",
0,
2,
);
defer matrix_ubo.delete();
app_state.camera.updateProjection(0.1, 200);
matrix_ubo.subData(0, zm.Mat, &.{app_state.camera.projection});
const light_ubo = gl_helper.initUbo(
zm.Vec,
&.{ model_shader, light_shader },
"Light",
1,
3,
);
defer light_ubo.delete();
light_ubo.subData(0, zm.Vec, &.{light_color});
light_ubo.subData(@sizeOf(zm.Vec), zm.Vec, &.{app_state.light_pos});
// time handling
var previous_time = glfw_helper.getTime();
var current_time: f64 = 0;
var delta_time: f64 = undefined;
// var frame_counter: u16 = 0;
// OpenGL setup
const clear_color = FloatColor.sky_blue;
zgl.clearColor(clear_color[0], clear_color[1], clear_color[2], clear_color[3]);
zgl.enable(.program_point_size);
zgl.enable(.depth_test);
zgl.enable(.cull_face);
while (!context.window.shouldClose()) : (context.postRender()) {
// input
app_state.camera.move(@floatCast(delta_time));
// ubo updates
matrix_ubo.subData(0, Mat, &.{app_state.camera.projection});
app_state.camera.updateView();
matrix_ubo.subData(@sizeOf(Mat), Mat, &.{app_state.camera.view});
light_ubo.subData(@sizeOf(zm.Vec) * 2, zm.Vec, &.{app_state.camera.position});
// render
zgl.clear(.{ .color = true, .depth = true });
switch (app_state.scene) {
.robot => {
model_shader.updateUniformMatrix("u_model_matrix", &.{zm.identity()});
model_shader.updateUniformMatrix("u_normal_matrix", &.{zm.identity()});
floor.draw(model_shader);
// animateRobot(&frame_counter, robot);
robot.draw(model_shader);
},
.fish => {},
}
light_shader.use();
zgl.drawArrays(.points, 0, 1);
// GUI
drawGui(light_ubo, light_shader, &light_color);
// time
current_time = glfw_helper.getTime();
delta_time = current_time - previous_time;
previous_time = current_time;
}
}
fn initFloor(textures: ?[]const Texture) Mesh {
var shape = Shape.initPlane(1, 1);
var inverted_shape = Shape.initPlane(1, 1);
defer shape.deinit();
defer inverted_shape.deinit();
inverted_shape.invert(0, 0);
shape.merge(inverted_shape);
shape.translate(-0.5, -0.5, 0);
shape.rotate(std.math.degreesToRadians(-90), 1, 0, 0);
shape.scale(2, 1, 2);
if (shape.texcoords) |texcoords| {
for (texcoords) |*coord| {
coord[0] *= 6;
coord[1] *= 6;
}
}
return Mesh.initShape(shape, textures, .triangles);
}
fn initRobot(allocator: std.mem.Allocator, textures: ?[]const Texture) !Model {
// var meshes = try std.ArrayList(Mesh).initCapacity(allocator, 14);
// var translations = try std.ArrayList(Mat).initCapacity(allocator, 14);
var shape = Shape.initCube();
defer shape.deinit();
shape.unweld();
shape.computeNormals();
shape.translate(-0.5, -1, -0.5);
// shape.rotate(std.math.degreesToRadians(180), 1, 0, 0);
// var foot = shape.clone();
// defer foot.deinit();
// foot.scale(0.1, 0.025, 0.15);
// meshes.appendNTimesAssumeCapacity(Mesh.initShape(foot, textures, .triangles), 2);
// translations.appendAssumeCapacity(zm.translation(-0.07, 0.025, 0));
// translations.appendAssumeCapacity(zm.translation(0.07, 0.025, 0));
// var leg = shape.clone();
// defer leg.deinit();
// leg.scale(0.08, 0.2, 0.08);
// meshes.appendNTimesAssumeCapacity(Mesh.initShape(leg, textures, .triangles), 4);
// translations.appendAssumeCapacity(zm.translation(-0.07, 0.225, 0));
// translations.appendAssumeCapacity(zm.translation(0.07, 0.225, 0));
// translations.appendAssumeCapacity(zm.translation(-0.07, 0.425, 0));
// translations.appendAssumeCapacity(zm.translation(0.07, 0.425, 0));
// var arm = shape.clone();
// defer arm.deinit();
// arm.scale(0.08, 0.2, 0.08);
// meshes.appendNTimesAssumeCapacity(Mesh.initShape(arm, textures, .triangles), 4);
// translations.appendAssumeCapacity(zm.translation(-0.3 / 2.0 - 0.04, 0.225 + 0.4, 0));
// translations.appendAssumeCapacity(zm.translation(0.3 / 2.0 + 0.04, 0.225 + 0.4, 0));
// translations.appendAssumeCapacity(zm.translation(-0.3 / 2.0 - 0.04, 0.425 + 0.4, 0));
// translations.appendAssumeCapacity(zm.translation(0.3 / 2.0 + 0.04, 0.425 + 0.4, 0));
// var hand = shape.clone();
// defer hand.deinit();
// hand.scale(0.025, 0.08, 0.07);
// meshes.appendNTimesAssumeCapacity(Mesh.initShape(hand, textures, .triangles), 2);
// translations.appendAssumeCapacity(zm.translation(-0.3 / 2.0 - 0.04, 0.425, 0));
// translations.appendAssumeCapacity(zm.translation(0.3 / 2.0 + 0.04, 0.425, 0));
var chest = shape.clone();
defer chest.deinit();
chest.scale(0.3, 0.4, 0.15);
// meshes.appendAssumeCapacity(Mesh.initShape(chest, textures, .triangles));
// translations.appendAssumeCapacity(zm.translation(0, 0.425 + 0.4, 0));
var model_data = Tree(Model.JointData).init(
.{
.mesh = Mesh.initShape(chest, textures, .triangles),
.translation = zm.translation(0, 0.425 + 0.4, 0),
.rotation = zm.qidentity(),
},
);
var head = shape.clone();
defer head.deinit();
head.scale(0.1, 0.1, 0.1);
try model_data.root.append(allocator, .{
.mesh = Mesh.initShape(head, textures, .triangles),
.translation = zm.translation(0, 0.425 + 0.4 + 0.1, 0),
.rotation = zm.qidentity(),
});
// meshes.appendAssumeCapacity(Mesh.initShape(head, textures, .triangles));
// translations.appendAssumeCapacity(zm.translation(0, 0.425 + 0.4 + 0.1, 0));
return Model.init(model_data);
}
// fn initRobot(allocator: std.mem.Allocator, textures: ?[]const Texture) Model {
// var shape = Shape.initCube();
// defer shape.deinit();
// shape.unweld();
// shape.computeNormals();
// shape.translate(-0.5, -1, -0.5);
// var chest = shape.clone();
// defer chest.deinit();
// chest.scale(0.3, 0.4, 0.15);
// var model_data = Tree(Model.JointData).init(.{
// .mesh = Mesh.initShape(chest, textures, .triangles),
// .translation = zm.translation(0, 0.425 + 0.4, 0),
// .rotation = zm.qidentity(),
// });
// var head = shape.clone();
// defer head.deinit();
// head.scale(0.1, 0.1, 0.1);
// try model_data.root.append(allocator, .{
// .mesh = Mesh.initShape(head, textures, .triangles),
// .translation = zm.translation(0, 0.425 + 0.4 + 0.1, 0),
// .rotation = zm.qidentity(),
// });
// return Model.init(model_data);
// }
// fn animateRobot(frame_counter: *u16, robot: Model) void {
// if (frame_counter.* > robot.animation_frames) {
// robot.translations.items[2] = zm.mul(zm.rotationX(-std.math.degreesToRadians(1)), robot.translations.items[2]);
// robot.translations.items[3] = zm.mul(zm.rotationX(-std.math.degreesToRadians(1)), robot.translations.items[3]);
// } else {
// robot.translations.items[2] = zm.mul(zm.rotationX(std.math.degreesToRadians(1)), robot.translations.items[2]);
// robot.translations.items[3] = zm.mul(zm.rotationX(std.math.degreesToRadians(1)), robot.translations.items[3]);
// }
// frame_counter.* += 1;
// if (frame_counter.* > robot.animation_frames * 2) {
// frame_counter.* = 0;
// }
// }
fn drawGui(light_ubo: zgl.Buffer, light_shader: gl_helper.Shader, light_color: *zm.Vec) void {
if (app_state.render_gui) {
zgui.backend.newFrame(@intCast(app_state.width), @intCast(app_state.height));
zgui.setNextWindowPos(.{ .x = 0, .y = 0, .cond = .appearing });
zgui.setNextWindowSize(.{ .w = @floatFromInt(app_state.width), .h = 0, .cond = .appearing });
if (zgui.begin(
"##Window",
.{ .flags = window_flags },
)) {
_ = zgui.radioButtonStatePtr("Robot", .{ .v_button = 0, .v = @ptrCast(&app_state.scene) });
zgui.sameLine(.{});
_ = zgui.radioButtonStatePtr("Fish", .{ .v_button = 1, .v = @ptrCast(&app_state.scene) });
if (zgui.sliderFloat3(
"Light position",
.{
.v = @ptrCast(&app_state.light_pos),
.min = -2,
.max = 2,
.cfmt = "%.2f",
.flags = .{ .always_clamp = true },
},
)) {
light_ubo.subData(@sizeOf(zm.Vec), zm.Vec, &.{app_state.light_pos});
light_shader.updateUniformVec4("u_position", &.{app_state.light_pos});
}
if (zgui.colorEdit3(
"Light Color",
.{
.col = @ptrCast(light_color),
.flags = .{},
},
)) {
light_ubo.subData(0, zm.Vec, &.{light_color.*});
}
}
zgui.end();
zgui.backend.draw();
}
}
comptime {
std.testing.refAllDeclsRecursive(@This());
}

244
src/main.zig.bak Normal file
View File

@@ -0,0 +1,244 @@
const std = @import("std");
const glfw = @import("zglfw");
const zgl = @import("zgl");
const zm = @import("zmath");
const Mat = zm.Mat;
const zmesh = @import("zmesh");
const Shape = zmesh.Shape;
const glfw_helper = @import("glfw_helper/root.zig");
const GlfwContect = glfw_helper.GlfwContext;
const AppState = glfw_helper.AppState;
const gl_helper = @import("gl_helper/root.zig");
const Shader = gl_helper.Shader;
const Texture = gl_helper.Texture;
const Mesh = gl_helper.Mesh;
const ByteColor = gl_helper.Color.Byte;
const FloatColor = gl_helper.Color.Float;
pub const texture =
ByteColor.red ++ ByteColor.gold ++
ByteColor.gold ++ ByteColor.red;
pub const pyramid_texture =
ByteColor.violet ++ ByteColor.violet ++ ByteColor.violet ++ ByteColor.violet ++
ByteColor.blue ++ ByteColor.blue ++ ByteColor.blue ++ ByteColor.blue ++
ByteColor.green ++ ByteColor.green ++ ByteColor.green ++ ByteColor.green ++
ByteColor.red ++ ByteColor.red ++ ByteColor.red ++ ByteColor.red;
var app_state = AppState.init(
600,
600,
.diffuse,
zm.f32x4(0, 2, 3, 1),
3,
);
pub fn main() !void {
var debug_allocator = std.heap.DebugAllocator(.{}).init;
defer _ = debug_allocator.deinit();
const allocator = debug_allocator.allocator();
const context = try GlfwContect.init(&app_state, "ZGL Test");
zmesh.init(allocator);
defer zmesh.deinit();
// shaders
var model_shader = try Shader.initFile("shaders/default.vert", "shaders/default.frag");
defer model_shader.delete();
var instanced_shader = try Shader.initFile("shaders/instanced.vert", "shaders/default.frag");
defer instanced_shader.delete();
var diffuse_shader = try Shader.initFile("shaders/default.vert", "shaders/diffuse.frag");
defer diffuse_shader.delete();
var specular_shader = try Shader.initFile("shaders/default.vert", "shaders/specular.frag");
defer specular_shader.delete();
var light_shader = try Shader.initFile("shaders/light.vert", "shaders/light.frag");
defer light_shader.delete();
// textures
const textures: []const Texture = &.{Texture.init(0, .@"2d")};
textures[0].load(&texture, 2, 2, .rgba8, .rgba, .unsigned_byte);
const p_textures: []const Texture = &.{Texture.init(0, .@"2d")};
p_textures[0].load(&pyramid_texture, 4, 4, .rgba8, .rgba, .unsigned_byte);
// meshes
var floor_shape = Shape.initPlane(1, 1);
defer floor_shape.deinit();
floor_shape.translate(-0.5, -0.5, 0);
floor_shape.rotate(std.math.degreesToRadians(-90), 1, 0, 0);
floor_shape.scale(6, 1, 6);
if (floor_shape.texcoords) |texcoords| {
for (texcoords) |*coord| {
coord[0] *= 6;
coord[1] *= 6;
}
}
const floor = Mesh.initShape(floor_shape, textures, .triangles);
defer floor.delete();
var pyramid_shape = Shape.initCone(4, 1);
defer pyramid_shape.deinit();
pyramid_shape.rotate(std.math.degreesToRadians(-90), 1, 0, 0);
pyramid_shape.scale(1, 1.5, 1);
for (pyramid_shape.texcoords.?) |*texcoord| {
texcoord[0] *= 3;
}
const pyramid = Mesh.initShape(pyramid_shape, p_textures, .triangles);
defer pyramid.delete();
var cube_shape = Shape.initCube();
defer cube_shape.deinit();
cube_shape.computeNormals();
cube_shape.translate(-3, 0, -3);
const cube = Mesh.initShape(cube_shape, textures, .triangles);
defer cube.delete();
// instancing uniforms
var cube_transformations: [27]Mat = undefined;
var cube_normals: [27]Mat = undefined;
for (0..3) |i| {
for (0..3) |j| {
for (0..3) |k| {
cube_transformations[i + 3 * (j + k * 3)] = zm.translation(
@as(f32, @floatFromInt(i)) * 2.5,
@as(f32, @floatFromInt(j)) * 2.5,
@as(f32, @floatFromInt(k)) * 2.5,
);
cube_normals[i + 3 * (j + k * 3)] = zm.transpose(zm.inverse(cube_transformations[i + 3 * (j + k * 3)]));
}
}
}
// uniform data
const light_color = zm.f32x4s(1);
var pyramid_rotation = zm.quatFromRollPitchYaw(0, std.math.degreesToRadians(45), 0);
var light_pos = zm.f32x4(0, 2, 0, 1);
// uniform buffer objects
const matrix_ubo = gl_helper.initUbo(zm.Mat, &.{
model_shader,
instanced_shader,
diffuse_shader,
specular_shader,
light_shader,
}, "SharedMatrices", 0, 1);
defer matrix_ubo.delete();
const light_ubo = gl_helper.initUbo(f32, &.{
model_shader,
instanced_shader,
diffuse_shader,
specular_shader,
}, "Light", 1, 12);
defer light_ubo.delete();
light_ubo.subData(0, zm.Vec, &.{light_color});
// const uniforms
diffuse_shader.updateUniformMatrix("u_model_matrix", &.{zm.identity()});
diffuse_shader.updateUniformMatrix("u_normal_matrix", &.{zm.identity()});
specular_shader.updateUniformMatrix("u_model_matrix", &.{zm.identity()});
specular_shader.updateUniformMatrix("u_normal_matrix", &.{zm.identity()});
instanced_shader.updateUniformMatrix("u_model_matrix[0]", &cube_transformations);
instanced_shader.updateUniformMatrix("u_normal_matrix[0]", &cube_normals);
light_shader.updateUniformVec4("light_color", &.{light_color});
// time handling
var previous_time = glfw_helper.getTime();
var current_time: f64 = 0;
var delta_time: f64 = undefined;
// OpenGL setup
const clear_color = FloatColor.sky_blue;
zgl.clearColor(clear_color[0], clear_color[1], clear_color[2], clear_color[3]);
zgl.enable(.program_point_size);
zgl.enable(.depth_test);
zgl.enable(.cull_face);
while (!context.window.shouldClose()) : (context.postRender()) {
// input
app_state.camera.move(@floatCast(delta_time));
moveLight(&light_pos, @floatCast(delta_time));
// ubo updates
app_state.camera.updateMatrix(60, 0.1, 200);
matrix_ubo.subData(0, Mat, &.{app_state.camera.matrix});
light_ubo.subData(@sizeOf(f32) * 4, zm.Vec, &.{light_pos});
light_ubo.subData(@sizeOf(f32) * 4 * 2, zm.Vec, &.{app_state.camera.position});
// render
zgl.clear(.{ .color = true, .depth = true });
switch (app_state.scene) {
.pyramid => {
model_shader.updateUniformMatrix("u_model_matrix", &.{zm.identity()});
model_shader.updateUniformMatrix("u_normal_matrix", &.{zm.identity()});
floor.draw(model_shader);
pyramid_rotation = zm.qmul(pyramid_rotation, zm.quatFromRollPitchYaw(0, @floatCast(-1 * delta_time), 0));
const pyramid_matrix = zm.quatToMat(pyramid_rotation);
model_shader.updateUniformMatrix("u_model_matrix", &.{pyramid_matrix});
model_shader.updateUniformMatrix("u_normal_matrix", &.{zm.transpose(zm.inverse(pyramid_matrix))});
pyramid.draw(model_shader);
},
.cubes => {
model_shader.updateUniformMatrix("u_model_matrix", &.{zm.identity()});
model_shader.updateUniformMatrix("u_normal_matrix", &.{zm.identity()});
floor.draw(model_shader);
cube.drawInstanced(instanced_shader, 27);
},
.diffuse => {
floor.draw(diffuse_shader);
},
.specular => {
floor.draw(specular_shader);
},
}
light_shader.use();
light_shader.updateUniformVec4("u_position", &.{light_pos});
light_shader.updateUniformVec3("camera_pos", &.{zm.vecToArr3(app_state.camera.position)});
zgl.drawArrays(.points, 0, 1);
// time
current_time = glfw_helper.getTime();
delta_time = current_time - previous_time;
previous_time = current_time;
}
}
fn moveLight(light_pos: *zm.Vec, delta_time: f32) void {
if (app_state.light_controls.forward) {
light_pos[2] -= 3 * delta_time;
}
if (app_state.light_controls.left) {
light_pos[0] -= 3 * delta_time;
}
if (app_state.light_controls.backward) {
light_pos[2] += 3 * delta_time;
}
if (app_state.light_controls.right) {
light_pos[0] += 3 * delta_time;
}
if (app_state.light_controls.up) {
light_pos[1] += 3 * delta_time;
}
if (app_state.light_controls.down) {
light_pos[1] -= 3 * delta_time;
}
}
comptime {
std.testing.refAllDeclsRecursive(@This());
}

150
src/tree.zig Normal file
View File

@@ -0,0 +1,150 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
pub fn Tree(comptime T: type) type {
return struct {
// const Self = @This();
root: Node,
const Children = std.ArrayList(Node);
const Node = struct {
val: T,
parent: ?*Node,
index: usize,
children: std.ArrayList(Node),
pub fn init(val: T, parent: ?*Node, index: usize) Node {
return .{
.val = val,
.children = .empty,
.parent = parent,
.index = index,
};
}
pub fn append(self: *Node, allocator: Allocator, val: T) !void {
const child = Node.init(val, self, self.children.items.len);
try self.children.append(allocator, child);
}
};
pub fn init(val: T) @This() {
return .{ .root = .init(val, null, 0) };
}
pub const DepthFirstIterator = struct {
const State = enum {
GoDeeper,
GoBroader,
};
tree: *Tree(T),
current: ?*Node,
state: State,
pub fn init(tree: *Tree(T)) DepthFirstIterator {
return DepthFirstIterator{
.tree = tree,
.current = &tree.root,
.state = State.GoDeeper,
};
}
pub fn next(it: *DepthFirstIterator) ?*Node {
// State machine
while (it.current) |current| {
switch (it.state) {
State.GoDeeper => {
// Follow child node until deepest possible level
if (current.children.items.len > 0) {
it.current = &current.children.items[0];
} else {
it.state = State.GoBroader;
return current;
}
},
State.GoBroader => {
if (current.parent) |parent| {
if (parent.children.items.len - 1 > (current.index)) {
it.current = &parent.children.items[current.index + 1];
it.state = .GoDeeper;
} else {
it.current = current.parent;
return current.parent;
}
} else {
return null;
}
},
}
}
return null;
}
pub fn reset(it: *DepthFirstIterator) void {
it.current = it.tree.root;
}
};
pub fn depthFirstIterator(tree: *@This()) DepthFirstIterator {
return DepthFirstIterator.init(tree);
}
pub fn deinit(self: *@This(), allocator: Allocator) void {
var iterator = self.depthFirstIterator();
while (iterator.next()) |node| {
node.children.deinit(allocator);
}
}
};
}
pub fn main() !void {
var debug_allocator = std.heap.DebugAllocator(.{}).init;
defer _ = debug_allocator.deinit();
const allocator = debug_allocator.allocator();
const UTree = Tree(u16);
var tree = UTree.init(10);
defer tree.deinit(allocator);
try tree.root.append(allocator, 20);
try tree.root.append(allocator, 500);
var iterator = tree.depthFirstIterator();
std.debug.print("\n", .{});
while (iterator.next()) |node| {
var accumulator: u32 = node.val;
var current = node;
while (current.parent) |parent| {
accumulator *= parent.val;
current = parent;
}
std.debug.print("Val: {} Acc: {}\n", .{ node.val, accumulator });
}
}
test "Tree" {
const allocator = std.testing.allocator;
const UTree = Tree(u16);
var tree = UTree.init(10);
defer tree.deinit(allocator);
try tree.root.append(allocator, 20);
try tree.root.append(allocator, 500);
var iterator = tree.depthFirstIterator();
std.debug.print("\n", .{});
while (iterator.next()) |node| {
var accumulator: u32 = node.val;
var current = node;
while (current.parent) |parent| {
accumulator *= parent.val;
current = parent;
}
std.debug.print("Val: {} Acc: {}\n", .{ node.val, accumulator });
}
}

319
src/tree.zig.bak Normal file
View File

@@ -0,0 +1,319 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const testing = std.testing;
const assert = std.debug.assert;
const FixedBufferAllocator = std.heap.FixedBufferAllocator;
/// Generic k-ary tree represented as a "left-child right-sibling" binary tree.
pub fn Tree(comptime T: type) type {
return struct {
const Self = @This();
root: Node,
/// Node inside the tree.
pub const Node = struct {
value: T,
parent: ?*Node,
leftmost_child: ?*Node,
right_sibling: ?*Node,
fn init(value: T) Node {
return Node{
.value = value,
.parent = null,
.leftmost_child = null,
.right_sibling = null,
};
}
};
/// Initialize a tree.
///
/// Arguments:
/// value: Value (aka weight, key, etc.) of the root node.
///
/// Returns:
/// A tree containing one node with specified value.
pub fn init(value: T) Self {
return Self{
.root = Node.init(value),
};
}
/// Allocate a new node. Caller owns returned Node and must free with `destroyNode`.
///
/// Arguments:
/// allocator: Dynamic memory allocator.
///
/// Returns:
/// A pointer to the new node.
///
/// Errors:
/// If a new node cannot be allocated.
pub fn allocateNode(tree: *Self, allocator: *Allocator) !*Node {
_ = tree; // autofix
return allocator.create(Node);
}
/// Deallocate a node. Node must have been allocated with `allocator`.
///
/// Arguments:
/// node: Pointer to the node to deallocate.
/// allocator: Dynamic memory allocator.
pub fn destroyNode(tree: *Self, node: *Node, allocator: *Allocator) void {
assert(tree.containsNode(node));
allocator.destroy(node);
}
/// Allocate and initialize a node and its value.
///
/// Arguments:
/// value: Value (aka weight, key, etc.) of newly created node.
/// allocator: Dynamic memory allocator.
///
/// Returns:
/// A pointer to the new node.
///
/// Errors:
/// If a new node cannot be allocated.
pub fn createNode(tree: *Self, value: T, allocator: *Allocator) !*Node {
const node = try tree.allocateNode(allocator);
node.* = Node.init(value);
return node;
}
/// Insert a node at the specified position inside the tree.
///
/// Arguments:
/// node: Pointer to the node to insert.
/// parent: Pointer to node which the newly created node will be a child of.
///
/// Returns:
/// A pointer to the new node.
pub fn insert(tree: *Self, node: *Node, parent: *Node) void {
_ = tree; // autofix
node.parent = parent;
node.right_sibling = parent.leftmost_child;
parent.leftmost_child = node;
}
/// Add another tree at the specified position inside this tree.
///
/// Arguments:
/// other: Pointer to the tree to insert.
/// parent: Pointer to node which the newly created node will be a parent of.
pub fn graft(tree: *Self, other: *Self, parent: *Node) void {
_ = tree; // autofix
_ = other; // autofix
_ = parent; // autofix
}
/// Remove (detach) a "branch" from the tree (remove node and all its descendants).
/// Does nothing when applied to root node.
///
/// Arguments:
/// node: Pointer to node to be removed.
pub fn prune(tree: *Self, node: *Node) void {
assert(tree.containsNode(node));
if (node.parent) |parent| {
var ptr = &parent.leftmost_child;
while (ptr.*) |sibling| : (ptr = &sibling.right_sibling) {
if (sibling == node) {
ptr.* = node.right_sibling;
break;
}
}
node.right_sibling = null;
node.parent = null;
}
}
/// Remove a node preserving all its children, which take its place inside the tree.
/// Does nothing when applied to root node.
///
/// Arguments:
/// node: Pointer to node to be removed.
pub fn remove(tree: *Self, node: *Node) void {
assert(tree.containsNode(node));
if (node.parent) |parent| {
var ptr = &parent.leftmost_child;
while (ptr.*) |sibling| : (ptr = &sibling.right_sibling) {
if (sibling == node) break;
}
ptr.* = node.leftmost_child;
while (ptr.*) |old_child| : (ptr = &old_child.right_sibling) {
old_child.parent = parent;
}
ptr.* = node.right_sibling;
node.parent = null;
node.leftmost_child = null;
node.right_sibling = null;
}
}
/// Iterator that performs a depth-first post-order traversal of the tree.
/// It is non-recursive and uses constant memory (no allocator needed).
pub const DepthFirstIterator = struct {
const State = enum {
GoDeeper,
GoBroader,
};
tree: *Self,
current: ?*Node,
state: State,
// NB:
// If not children_done:
// Go as deep as possible
// Yield node
// If can move right:
// children_done = false;
// Move right
// Else:
// children_done = true;
// Move up
pub fn init(tree: *Self) DepthFirstIterator {
return DepthFirstIterator{
.tree = tree,
.current = &tree.root,
.state = State.GoDeeper,
};
}
pub fn next(it: *DepthFirstIterator) ?*Node {
// State machine
while (it.current) |current| {
switch (it.state) {
State.GoDeeper => {
// Follow child node until deepest possible level
if (current.leftmost_child) |child| {
it.current = child;
} else {
it.state = State.GoBroader;
return current;
}
},
State.GoBroader => {
if (current.right_sibling) |sibling| {
it.current = sibling;
it.state = State.GoDeeper;
} else {
it.current = current.parent;
return current.parent;
}
},
}
}
return null;
}
pub fn reset(it: *DepthFirstIterator) void {
it.current = it.tree.root;
}
};
/// Get a depth-first iterator over the nodes of this tree.
///
/// Returns:
/// An iterator struct (one containing `next` and `reset` member functions).
pub fn depthFirstIterator(tree: *Self) DepthFirstIterator {
return DepthFirstIterator.init(tree);
}
/// Check if a node is contained in this tree.
///
/// Arguments:
/// target: Pointer to node to be searched for.
///
/// Returns:
/// A bool telling whether it has been found.
pub fn containsNode(tree: *Self, target: *Node) bool {
var iter = tree.depthFirstIterator();
while (iter.next()) |node| {
if (node == target) {
return true;
}
}
return false;
}
};
}
test "tree node insertion" {
var tree = Tree(u32).init(1);
const allocator = std.debug.global_allocator;
//var buffer: [5000]u8 = undefined;
// const allocator = &FixedBufferAllocator.init(buffer[0..]).allocator;
const two = try tree.createNode(2, allocator);
const three = try tree.createNode(3, allocator);
const four = try tree.createNode(4, allocator);
const five = try tree.createNode(5, allocator);
const six = try tree.createNode(6, allocator);
const fortytwo = try tree.createNode(42, allocator);
defer {
tree.destroyNode(two, allocator);
tree.destroyNode(three, allocator);
tree.destroyNode(four, allocator);
tree.destroyNode(five, allocator);
tree.destroyNode(six, allocator);
allocator.destroy(fortytwo);
}
tree.insert(two, &tree.root);
tree.insert(three, &tree.root);
tree.insert(four, &tree.root);
tree.insert(five, two);
tree.insert(six, two);
testing.expect(tree.root.value == 1);
testing.expect(two.parent == &tree.root);
testing.expect(three.parent == &tree.root);
testing.expect(tree.containsNode(four));
testing.expect(five.parent == two);
testing.expect(!tree.containsNode(fortytwo));
var iter = tree.depthFirstIterator();
while (iter.next()) |node| {
std.debug.warn("{} ", node.value);
}
}
test "tree node removal" {
var tree = Tree(u32).init(1);
const allocator = std.debug.global_allocator;
const two = try tree.createNode(2, allocator);
const three = try tree.createNode(3, allocator);
const four = try tree.createNode(4, allocator);
const five = try tree.createNode(5, allocator);
const six = try tree.createNode(6, allocator);
defer {
tree.destroyNode(three, allocator);
tree.destroyNode(four, allocator);
allocator.destroy(two);
allocator.destroy(five);
allocator.destroy(six);
}
tree.insert(two, &tree.root);
tree.insert(three, &tree.root);
tree.insert(four, &tree.root);
tree.insert(five, two);
tree.insert(six, two);
tree.prune(two);
testing.expect(tree.containsNode(three));
testing.expect(tree.containsNode(four));
testing.expect(!tree.containsNode(two));
testing.expect(!tree.containsNode(five));
testing.expect(!tree.containsNode(six));
var iter = tree.depthFirstIterator();
while (iter.next()) |node| {
std.debug.warn("{} ", node.value);
}
}

BIN
textures/earth.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 KiB

BIN
textures/planks.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 MiB

BIN
textures/planks_spec.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
textures/skybox/back.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 KiB

BIN
textures/skybox/bottom.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

BIN
textures/skybox/front.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 KiB

BIN
textures/skybox/left.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 KiB

BIN
textures/skybox/right.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 KiB

BIN
textures/skybox/top.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 KiB