first commit
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.zig-cache
|
||||
**.bak
|
||||
zig-out
|
||||
69
build.zig
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
49
src/glfw_helper/callbacks.zig
Normal 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
@@ -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());
|
||||
}
|
||||
7
src/glfw_helper/scene.zig
Normal 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
@@ -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
@@ -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
@@ -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 = ¤t.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
@@ -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
|
After Width: | Height: | Size: 486 KiB |
BIN
textures/planks.png
Normal file
|
After Width: | Height: | Size: 5.5 MiB |
BIN
textures/planks_spec.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
textures/skybox/back.jpg
Normal file
|
After Width: | Height: | Size: 723 KiB |
BIN
textures/skybox/bottom.jpg
Normal file
|
After Width: | Height: | Size: 274 KiB |
BIN
textures/skybox/front.jpg
Normal file
|
After Width: | Height: | Size: 462 KiB |
BIN
textures/skybox/left.jpg
Normal file
|
After Width: | Height: | Size: 588 KiB |
BIN
textures/skybox/right.jpg
Normal file
|
After Width: | Height: | Size: 525 KiB |
BIN
textures/skybox/top.jpg
Normal file
|
After Width: | Height: | Size: 338 KiB |
BIN
textures/textures/plank_flooring_04_ao_1k.jpg
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
textures/textures/plank_flooring_04_arm_1k.jpg
Normal file
|
After Width: | Height: | Size: 762 KiB |
BIN
textures/textures/plank_flooring_04_diff_1k.jpg
Normal file
|
After Width: | Height: | Size: 572 KiB |
BIN
textures/textures/plank_flooring_04_disp_1k.jpg
Normal file
|
After Width: | Height: | Size: 131 KiB |
BIN
textures/textures/plank_flooring_04_nor_gl_1k.jpg
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
textures/textures/plank_flooring_04_rough_1k.jpg
Normal file
|
After Width: | Height: | Size: 686 KiB |