Compare commits
18 Commits
b73f0d446a
...
tabletop
Author | SHA1 | Date | |
---|---|---|---|
|
0658c2fdfa | ||
|
9d3a98b2d9 | ||
|
49267e629f | ||
|
640e7bed86 | ||
|
fc6ebb73b8 | ||
|
c59d833542 | ||
|
9933a956c2 | ||
|
f836ab8b20 | ||
|
92ca641f89 | ||
|
d317694056 | ||
|
39db8ed3a1 | ||
|
1d3f89e5f5 | ||
|
6f933449a1 | ||
|
3f1c0aa2f8 | ||
|
9fdd997be6 | ||
|
1ef69d1545 | ||
|
855194acea | ||
|
f3d2eff20e |
5
.ignore
Normal file
5
.ignore
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/data/**/*.png
|
||||||
|
/data/**/*.bin
|
||||||
|
/data/**/*.blend
|
||||||
|
/data/**/*.blend1
|
||||||
|
/.git
|
116
build.zig
116
build.zig
@@ -6,76 +6,135 @@ const SHADERS: []const []const u8 = &.{
|
|||||||
"data/shaders/basic.vert",
|
"data/shaders/basic.vert",
|
||||||
"data/shaders/basic.frag",
|
"data/shaders/basic.frag",
|
||||||
};
|
};
|
||||||
|
const SDL_LIBS: []const []const u8 = &.{
|
||||||
|
"SDL3",
|
||||||
|
"SDL3_image",
|
||||||
|
};
|
||||||
|
|
||||||
pub fn build(b: *Build) void {
|
pub fn build(b: *Build) void {
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
const exe = stepBuildMain(b, target, optimize);
|
const sdl_module, const sdl_step = stepSdlModule(b, target, optimize);
|
||||||
b.installArtifact(exe);
|
|
||||||
|
const client_exe = stepBuildClient(b, target, optimize, sdl_module, sdl_step);
|
||||||
|
const client_install = b.addInstallArtifact(client_exe, .{});
|
||||||
|
|
||||||
|
const server_exe = stepBuildServer(b, target, optimize);
|
||||||
|
const server_install = b.addInstallArtifact(server_exe, .{});
|
||||||
|
|
||||||
|
const offline_exe = stepBuildOffline(b, target, optimize, sdl_module, sdl_step);
|
||||||
|
const offline_install = b.addInstallArtifact(offline_exe, .{});
|
||||||
|
|
||||||
const copy_data = stepCopyData(b, target, optimize);
|
const copy_data = stepCopyData(b, target, optimize);
|
||||||
|
|
||||||
|
b.getInstallStep().dependOn(&client_install.step);
|
||||||
|
b.getInstallStep().dependOn(&server_install.step);
|
||||||
|
b.getInstallStep().dependOn(&offline_install.step);
|
||||||
b.getInstallStep().dependOn(copy_data);
|
b.getInstallStep().dependOn(copy_data);
|
||||||
|
|
||||||
const run = b.addRunArtifact(exe);
|
const run = b.addRunArtifact(offline_exe);
|
||||||
run.step.dependOn(b.getInstallStep());
|
run.step.dependOn(&offline_install.step);
|
||||||
|
run.step.dependOn(copy_data);
|
||||||
|
|
||||||
// Why is this not the default behavoir?
|
// Why is this not the default behavior?
|
||||||
run.setCwd(b.path(std.fs.path.relative(b.allocator, b.build_root.path.?, b.exe_dir) catch unreachable));
|
run.setCwd(b.path(std.fs.path.relative(b.allocator, b.build_root.path.?, b.exe_dir) catch unreachable));
|
||||||
|
|
||||||
if (b.args) |args| {
|
if (b.args) |args| {
|
||||||
run.addArgs(args);
|
run.addArgs(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
const run_step = b.step("run", "Run the app");
|
const run_step = b.step("run", "Build and Run tabletop in offline mode");
|
||||||
run_step.dependOn(&run.step);
|
run_step.dependOn(&run.step);
|
||||||
|
|
||||||
const check_step = b.step("check", "Check for build errors");
|
const check_step = b.step("check", "Check for build errors (offline build only)");
|
||||||
check_step.dependOn(&exe.step);
|
check_step.dependOn(&offline_exe.step);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stepBuildMain(
|
fn stepBuildClient(
|
||||||
|
b: *Build,
|
||||||
|
target: Build.ResolvedTarget,
|
||||||
|
optimize: std.builtin.OptimizeMode,
|
||||||
|
sdl_module: *Build.Module,
|
||||||
|
sdl_step: *Build.Step,
|
||||||
|
) *Build.Step.Compile {
|
||||||
|
const exe = b.addExecutable(.{
|
||||||
|
.name = "tabletop_client",
|
||||||
|
.root_module = b.createModule(.{
|
||||||
|
.root_source_file = b.path("src/client.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
exe.root_module.addImport("sdl", sdl_module);
|
||||||
|
exe.step.dependOn(sdl_step);
|
||||||
|
|
||||||
|
return exe;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stepBuildServer(
|
||||||
b: *Build,
|
b: *Build,
|
||||||
target: Build.ResolvedTarget,
|
target: Build.ResolvedTarget,
|
||||||
optimize: std.builtin.OptimizeMode,
|
optimize: std.builtin.OptimizeMode,
|
||||||
) *Build.Step.Compile {
|
) *Build.Step.Compile {
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "spacefarer",
|
.name = "tabletop_server",
|
||||||
.root_source_file = b.path("src/main.zig"),
|
.root_module = b.createModule(.{
|
||||||
|
.root_source_file = b.path("src/server.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return exe;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stepBuildOffline(
|
||||||
|
b: *Build,
|
||||||
|
target: Build.ResolvedTarget,
|
||||||
|
optimize: std.builtin.OptimizeMode,
|
||||||
|
sdl_module: *Build.Module,
|
||||||
|
sdl_step: *Build.Step,
|
||||||
|
) *Build.Step.Compile {
|
||||||
|
const exe = b.addExecutable(.{
|
||||||
|
.name = "tabletop",
|
||||||
|
.root_module = b.createModule(.{
|
||||||
|
.root_source_file = b.path("src/offline.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const sdl_module, const sdl_step = stepSdlModule(b, target, optimize);
|
|
||||||
exe.root_module.addImport("sdl", sdl_module);
|
exe.root_module.addImport("sdl", sdl_module);
|
||||||
exe.step.dependOn(sdl_step);
|
exe.step.dependOn(sdl_step);
|
||||||
|
|
||||||
exe.addIncludePath(b.path("lib/clibs"));
|
|
||||||
|
|
||||||
return exe;
|
return exe;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stepBuildSdlTranslator(
|
fn stepBuildSdlTranslator(
|
||||||
b: *Build,
|
b: *Build,
|
||||||
target: Build.ResolvedTarget,
|
target: Build.ResolvedTarget,
|
||||||
optimize: std.builtin.OptimizeMode,
|
|
||||||
) *Build.Step.Compile {
|
) *Build.Step.Compile {
|
||||||
const sdl_translator = b.addExecutable(.{
|
const sdl_translator = b.addExecutable(.{
|
||||||
.name = "sdl_header_translator",
|
.name = "sdl_header_translator",
|
||||||
|
.root_module = b.createModule(.{
|
||||||
.root_source_file = b.path("utils/sdl_translator.zig"),
|
.root_source_file = b.path("utils/sdl_translator.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = .Debug,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
sdl_translator.linkSystemLibrary("SDL3");
|
for (SDL_LIBS) |lib| {
|
||||||
|
sdl_translator.linkSystemLibrary(lib);
|
||||||
|
}
|
||||||
return sdl_translator;
|
return sdl_translator;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stepTranslateSdl(
|
fn stepTranslateSdl(
|
||||||
b: *Build,
|
b: *Build,
|
||||||
target: Build.ResolvedTarget,
|
target: Build.ResolvedTarget,
|
||||||
optimize: std.builtin.OptimizeMode,
|
|
||||||
) struct { *Build.Step, Build.LazyPath } {
|
) struct { *Build.Step, Build.LazyPath } {
|
||||||
const sdl_translator = stepBuildSdlTranslator(b, target, optimize);
|
const sdl_translator = stepBuildSdlTranslator(b, target);
|
||||||
const translate = b.addRunArtifact(sdl_translator);
|
const translate = b.addRunArtifact(sdl_translator);
|
||||||
const sdl_rename = translate.addOutputFileArg("sdl_rename.h");
|
const sdl_rename = translate.addOutputFileArg("sdl_rename.h");
|
||||||
return .{
|
return .{
|
||||||
@@ -89,20 +148,25 @@ fn stepSdlModule(
|
|||||||
target: Build.ResolvedTarget,
|
target: Build.ResolvedTarget,
|
||||||
optimize: std.builtin.OptimizeMode,
|
optimize: std.builtin.OptimizeMode,
|
||||||
) struct { *Build.Module, *Build.Step } {
|
) struct { *Build.Module, *Build.Step } {
|
||||||
const sdl_module = b.addModule("sdl", .{
|
const translate_sdl_step, const sdl_rename = stepTranslateSdl(b, target);
|
||||||
.root_source_file = b.path("lib/sdl.zig"),
|
|
||||||
|
const translate_c = b.addTranslateC(.{
|
||||||
|
.root_source_file = b.path("lib/sdl.h"),
|
||||||
.link_libc = true,
|
.link_libc = true,
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
sdl_module.linkSystemLibrary("SDL3", .{});
|
translate_c.addIncludePath(sdl_rename.dirname());
|
||||||
|
const translate_module = translate_c.createModule();
|
||||||
|
for (SDL_LIBS) |lib| {
|
||||||
|
translate_module.linkSystemLibrary(lib, .{ .needed = true });
|
||||||
|
}
|
||||||
|
|
||||||
const translate_step, const sdl_rename = stepTranslateSdl(b, target, optimize);
|
translate_c.step.dependOn(translate_sdl_step);
|
||||||
sdl_module.addIncludePath(sdl_rename.dirname());
|
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
sdl_module,
|
translate_module,
|
||||||
translate_step,
|
&translate_c.step,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
.{
|
.{
|
||||||
.name = .spacefarer,
|
.name = .tabletop,
|
||||||
.version = "0.0.0",
|
.version = "0.0.0",
|
||||||
.fingerprint = 0x946ddccb5911fb15,
|
.fingerprint = 0x9467f2a6727b4ca5,
|
||||||
.minimum_zig_version = "0.15.0",
|
.minimum_zig_version = "0.14.0",
|
||||||
.dependencies = .{},
|
.dependencies = .{},
|
||||||
.paths = .{
|
.paths = .{
|
||||||
"build.zig",
|
"build.zig",
|
||||||
|
BIN
data/cubemap.bin
Normal file
BIN
data/cubemap.bin
Normal file
Binary file not shown.
142
data/cubemap.gltf
Normal file
142
data/cubemap.gltf
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
{
|
||||||
|
"asset":{
|
||||||
|
"generator":"Khronos glTF Blender I/O v4.5.48",
|
||||||
|
"version":"2.0"
|
||||||
|
},
|
||||||
|
"extensionsUsed":[
|
||||||
|
"KHR_materials_unlit"
|
||||||
|
],
|
||||||
|
"scene":0,
|
||||||
|
"scenes":[
|
||||||
|
{
|
||||||
|
"name":"Scene",
|
||||||
|
"nodes":[
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nodes":[
|
||||||
|
{
|
||||||
|
"mesh":0,
|
||||||
|
"name":"cubemap"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"materials":[
|
||||||
|
{
|
||||||
|
"extensions":{
|
||||||
|
"KHR_materials_unlit":{}
|
||||||
|
},
|
||||||
|
"name":"Material_0",
|
||||||
|
"pbrMetallicRoughness":{
|
||||||
|
"baseColorTexture":{
|
||||||
|
"index":0
|
||||||
|
},
|
||||||
|
"metallicFactor":0,
|
||||||
|
"roughnessFactor":0.9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meshes":[
|
||||||
|
{
|
||||||
|
"name":"Cube.001",
|
||||||
|
"primitives":[
|
||||||
|
{
|
||||||
|
"attributes":{
|
||||||
|
"POSITION":0,
|
||||||
|
"NORMAL":1,
|
||||||
|
"TEXCOORD_0":2
|
||||||
|
},
|
||||||
|
"indices":3,
|
||||||
|
"material":0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"textures":[
|
||||||
|
{
|
||||||
|
"sampler":0,
|
||||||
|
"source":0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"images":[
|
||||||
|
{
|
||||||
|
"mimeType":"image/png",
|
||||||
|
"name":"cubemap.png",
|
||||||
|
"uri":"cubemap.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accessors":[
|
||||||
|
{
|
||||||
|
"bufferView":0,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":24,
|
||||||
|
"max":[
|
||||||
|
0.4999999701976776,
|
||||||
|
0.5,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"min":[
|
||||||
|
-0.4999999701976776,
|
||||||
|
-0.5,
|
||||||
|
-0.5
|
||||||
|
],
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":1,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":24,
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":2,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":24,
|
||||||
|
"type":"VEC2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":3,
|
||||||
|
"componentType":5123,
|
||||||
|
"count":36,
|
||||||
|
"type":"SCALAR"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bufferViews":[
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":288,
|
||||||
|
"byteOffset":0,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":288,
|
||||||
|
"byteOffset":288,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":192,
|
||||||
|
"byteOffset":576,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":72,
|
||||||
|
"byteOffset":768,
|
||||||
|
"target":34963
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"samplers":[
|
||||||
|
{
|
||||||
|
"magFilter":9729,
|
||||||
|
"minFilter":9987
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buffers":[
|
||||||
|
{
|
||||||
|
"byteLength":840,
|
||||||
|
"uri":"cubemap.bin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
BIN
data/cubemap.png
Normal file
BIN
data/cubemap.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 327 B |
BIN
data/hand.bin
Normal file
BIN
data/hand.bin
Normal file
Binary file not shown.
144
data/hand.gltf
Normal file
144
data/hand.gltf
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
{
|
||||||
|
"asset":{
|
||||||
|
"generator":"Khronos glTF Blender I/O v4.5.48",
|
||||||
|
"version":"2.0"
|
||||||
|
},
|
||||||
|
"extensionsUsed":[
|
||||||
|
"KHR_materials_unlit"
|
||||||
|
],
|
||||||
|
"scene":0,
|
||||||
|
"scenes":[
|
||||||
|
{
|
||||||
|
"name":"Scene",
|
||||||
|
"nodes":[
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nodes":[
|
||||||
|
{
|
||||||
|
"mesh":0,
|
||||||
|
"name":"hand"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"materials":[
|
||||||
|
{
|
||||||
|
"alphaMode":"BLEND",
|
||||||
|
"doubleSided":true,
|
||||||
|
"extensions":{
|
||||||
|
"KHR_materials_unlit":{}
|
||||||
|
},
|
||||||
|
"name":"Material",
|
||||||
|
"pbrMetallicRoughness":{
|
||||||
|
"baseColorTexture":{
|
||||||
|
"index":0
|
||||||
|
},
|
||||||
|
"metallicFactor":0,
|
||||||
|
"roughnessFactor":0.9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meshes":[
|
||||||
|
{
|
||||||
|
"name":"Plane.001",
|
||||||
|
"primitives":[
|
||||||
|
{
|
||||||
|
"attributes":{
|
||||||
|
"POSITION":0,
|
||||||
|
"NORMAL":1,
|
||||||
|
"TEXCOORD_0":2
|
||||||
|
},
|
||||||
|
"indices":3,
|
||||||
|
"material":0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"textures":[
|
||||||
|
{
|
||||||
|
"sampler":0,
|
||||||
|
"source":0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"images":[
|
||||||
|
{
|
||||||
|
"mimeType":"image/png",
|
||||||
|
"name":"hand.png",
|
||||||
|
"uri":"hand.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accessors":[
|
||||||
|
{
|
||||||
|
"bufferView":0,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":4,
|
||||||
|
"max":[
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"min":[
|
||||||
|
-0.5,
|
||||||
|
-0.5,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":1,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":4,
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":2,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":4,
|
||||||
|
"type":"VEC2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":3,
|
||||||
|
"componentType":5123,
|
||||||
|
"count":6,
|
||||||
|
"type":"SCALAR"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bufferViews":[
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":48,
|
||||||
|
"byteOffset":0,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":48,
|
||||||
|
"byteOffset":48,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":32,
|
||||||
|
"byteOffset":96,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":12,
|
||||||
|
"byteOffset":128,
|
||||||
|
"target":34963
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"samplers":[
|
||||||
|
{
|
||||||
|
"magFilter":9728,
|
||||||
|
"minFilter":9984
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buffers":[
|
||||||
|
{
|
||||||
|
"byteLength":140,
|
||||||
|
"uri":"hand.bin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
BIN
data/hand.png
Normal file
BIN
data/hand.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 353 B |
@@ -7,4 +7,7 @@ layout(set = 2, binding = 0) uniform sampler2D texture_sampler;
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
fragColor = texture(texture_sampler, inUV);
|
fragColor = texture(texture_sampler, inUV);
|
||||||
|
if (fragColor.a < 0.5) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,5 +13,6 @@ layout(set = 1, binding = 1) uniform Object{
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = vec4(inCoord, 1.0) * object.transform * camera.transform;
|
gl_Position = vec4(inCoord, 1.0) * object.transform * camera.transform;
|
||||||
|
gl_ClipDistance[0] = gl_Position.w - gl_Position.z;
|
||||||
outUV = inUV;
|
outUV = inUV;
|
||||||
}
|
}
|
||||||
|
BIN
data/yakuza/card.bin
Normal file
BIN
data/yakuza/card.bin
Normal file
Binary file not shown.
144
data/yakuza/card.gltf
Normal file
144
data/yakuza/card.gltf
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
{
|
||||||
|
"asset":{
|
||||||
|
"generator":"Khronos glTF Blender I/O v4.5.48",
|
||||||
|
"version":"2.0"
|
||||||
|
},
|
||||||
|
"extensionsUsed":[
|
||||||
|
"KHR_materials_unlit"
|
||||||
|
],
|
||||||
|
"scene":0,
|
||||||
|
"scenes":[
|
||||||
|
{
|
||||||
|
"name":"Scene",
|
||||||
|
"nodes":[
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nodes":[
|
||||||
|
{
|
||||||
|
"mesh":0,
|
||||||
|
"name":"card"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"materials":[
|
||||||
|
{
|
||||||
|
"alphaMode":"BLEND",
|
||||||
|
"doubleSided":true,
|
||||||
|
"extensions":{
|
||||||
|
"KHR_materials_unlit":{}
|
||||||
|
},
|
||||||
|
"name":"Material",
|
||||||
|
"pbrMetallicRoughness":{
|
||||||
|
"baseColorTexture":{
|
||||||
|
"index":0
|
||||||
|
},
|
||||||
|
"metallicFactor":0,
|
||||||
|
"roughnessFactor":0.9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meshes":[
|
||||||
|
{
|
||||||
|
"name":"Plane.002",
|
||||||
|
"primitives":[
|
||||||
|
{
|
||||||
|
"attributes":{
|
||||||
|
"POSITION":0,
|
||||||
|
"NORMAL":1,
|
||||||
|
"TEXCOORD_0":2
|
||||||
|
},
|
||||||
|
"indices":3,
|
||||||
|
"material":0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"textures":[
|
||||||
|
{
|
||||||
|
"sampler":0,
|
||||||
|
"source":0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"images":[
|
||||||
|
{
|
||||||
|
"mimeType":"image/png",
|
||||||
|
"name":"game.png",
|
||||||
|
"uri":"game.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accessors":[
|
||||||
|
{
|
||||||
|
"bufferView":0,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":120,
|
||||||
|
"max":[
|
||||||
|
0.25,
|
||||||
|
0.25,
|
||||||
|
0.00390625
|
||||||
|
],
|
||||||
|
"min":[
|
||||||
|
-0.25,
|
||||||
|
-0.25,
|
||||||
|
-0.00390625
|
||||||
|
],
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":1,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":120,
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":2,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":120,
|
||||||
|
"type":"VEC2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":3,
|
||||||
|
"componentType":5123,
|
||||||
|
"count":228,
|
||||||
|
"type":"SCALAR"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bufferViews":[
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":1440,
|
||||||
|
"byteOffset":0,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":1440,
|
||||||
|
"byteOffset":1440,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":960,
|
||||||
|
"byteOffset":2880,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":456,
|
||||||
|
"byteOffset":3840,
|
||||||
|
"target":34963
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"samplers":[
|
||||||
|
{
|
||||||
|
"magFilter":9728,
|
||||||
|
"minFilter":9984
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buffers":[
|
||||||
|
{
|
||||||
|
"byteLength":4296,
|
||||||
|
"uri":"card.bin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
BIN
data/yakuza/game.png
Normal file
BIN
data/yakuza/game.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
BIN
data/yakuza/pad.bin
Normal file
BIN
data/yakuza/pad.bin
Normal file
Binary file not shown.
144
data/yakuza/pad.gltf
Normal file
144
data/yakuza/pad.gltf
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
{
|
||||||
|
"asset":{
|
||||||
|
"generator":"Khronos glTF Blender I/O v4.5.48",
|
||||||
|
"version":"2.0"
|
||||||
|
},
|
||||||
|
"extensionsUsed":[
|
||||||
|
"KHR_materials_unlit"
|
||||||
|
],
|
||||||
|
"scene":0,
|
||||||
|
"scenes":[
|
||||||
|
{
|
||||||
|
"name":"Scene",
|
||||||
|
"nodes":[
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nodes":[
|
||||||
|
{
|
||||||
|
"mesh":0,
|
||||||
|
"name":"pad"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"materials":[
|
||||||
|
{
|
||||||
|
"alphaMode":"BLEND",
|
||||||
|
"doubleSided":true,
|
||||||
|
"extensions":{
|
||||||
|
"KHR_materials_unlit":{}
|
||||||
|
},
|
||||||
|
"name":"Material",
|
||||||
|
"pbrMetallicRoughness":{
|
||||||
|
"baseColorTexture":{
|
||||||
|
"index":0
|
||||||
|
},
|
||||||
|
"metallicFactor":0,
|
||||||
|
"roughnessFactor":0.9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meshes":[
|
||||||
|
{
|
||||||
|
"name":"Plane.003",
|
||||||
|
"primitives":[
|
||||||
|
{
|
||||||
|
"attributes":{
|
||||||
|
"POSITION":0,
|
||||||
|
"NORMAL":1,
|
||||||
|
"TEXCOORD_0":2
|
||||||
|
},
|
||||||
|
"indices":3,
|
||||||
|
"material":0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"textures":[
|
||||||
|
{
|
||||||
|
"sampler":0,
|
||||||
|
"source":0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"images":[
|
||||||
|
{
|
||||||
|
"mimeType":"image/png",
|
||||||
|
"name":"game.png",
|
||||||
|
"uri":"game.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accessors":[
|
||||||
|
{
|
||||||
|
"bufferView":0,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":1272,
|
||||||
|
"max":[
|
||||||
|
0.4140625,
|
||||||
|
0.4140625,
|
||||||
|
0.00390625
|
||||||
|
],
|
||||||
|
"min":[
|
||||||
|
-0.4140625,
|
||||||
|
-0.4140625,
|
||||||
|
-0.00390625
|
||||||
|
],
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":1,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":1272,
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":2,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":1272,
|
||||||
|
"type":"VEC2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":3,
|
||||||
|
"componentType":5123,
|
||||||
|
"count":2532,
|
||||||
|
"type":"SCALAR"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bufferViews":[
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":15264,
|
||||||
|
"byteOffset":0,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":15264,
|
||||||
|
"byteOffset":15264,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":10176,
|
||||||
|
"byteOffset":30528,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":5064,
|
||||||
|
"byteOffset":40704,
|
||||||
|
"target":34963
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"samplers":[
|
||||||
|
{
|
||||||
|
"magFilter":9728,
|
||||||
|
"minFilter":9984
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buffers":[
|
||||||
|
{
|
||||||
|
"byteLength":45768,
|
||||||
|
"uri":"pad.bin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
BIN
data/yakuza/table.bin
Normal file
BIN
data/yakuza/table.bin
Normal file
Binary file not shown.
144
data/yakuza/table.gltf
Normal file
144
data/yakuza/table.gltf
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
{
|
||||||
|
"asset":{
|
||||||
|
"generator":"Khronos glTF Blender I/O v4.5.48",
|
||||||
|
"version":"2.0"
|
||||||
|
},
|
||||||
|
"extensionsUsed":[
|
||||||
|
"KHR_materials_unlit"
|
||||||
|
],
|
||||||
|
"scene":0,
|
||||||
|
"scenes":[
|
||||||
|
{
|
||||||
|
"name":"Scene",
|
||||||
|
"nodes":[
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nodes":[
|
||||||
|
{
|
||||||
|
"mesh":0,
|
||||||
|
"name":"table"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"materials":[
|
||||||
|
{
|
||||||
|
"alphaMode":"BLEND",
|
||||||
|
"doubleSided":true,
|
||||||
|
"extensions":{
|
||||||
|
"KHR_materials_unlit":{}
|
||||||
|
},
|
||||||
|
"name":"Material",
|
||||||
|
"pbrMetallicRoughness":{
|
||||||
|
"baseColorTexture":{
|
||||||
|
"index":0
|
||||||
|
},
|
||||||
|
"metallicFactor":0,
|
||||||
|
"roughnessFactor":0.9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meshes":[
|
||||||
|
{
|
||||||
|
"name":"Plane",
|
||||||
|
"primitives":[
|
||||||
|
{
|
||||||
|
"attributes":{
|
||||||
|
"POSITION":0,
|
||||||
|
"NORMAL":1,
|
||||||
|
"TEXCOORD_0":2
|
||||||
|
},
|
||||||
|
"indices":3,
|
||||||
|
"material":0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"textures":[
|
||||||
|
{
|
||||||
|
"sampler":0,
|
||||||
|
"source":0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"images":[
|
||||||
|
{
|
||||||
|
"mimeType":"image/png",
|
||||||
|
"name":"game.png",
|
||||||
|
"uri":"game.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accessors":[
|
||||||
|
{
|
||||||
|
"bufferView":0,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":24,
|
||||||
|
"max":[
|
||||||
|
4,
|
||||||
|
4,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"min":[
|
||||||
|
-4,
|
||||||
|
-4,
|
||||||
|
-0.5
|
||||||
|
],
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":1,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":24,
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":2,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":24,
|
||||||
|
"type":"VEC2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":3,
|
||||||
|
"componentType":5123,
|
||||||
|
"count":36,
|
||||||
|
"type":"SCALAR"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bufferViews":[
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":288,
|
||||||
|
"byteOffset":0,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":288,
|
||||||
|
"byteOffset":288,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":192,
|
||||||
|
"byteOffset":576,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":72,
|
||||||
|
"byteOffset":768,
|
||||||
|
"target":34963
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"samplers":[
|
||||||
|
{
|
||||||
|
"magFilter":9728,
|
||||||
|
"minFilter":9984
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buffers":[
|
||||||
|
{
|
||||||
|
"byteLength":840,
|
||||||
|
"uri":"table.bin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
3
lib/sdl.h
Normal file
3
lib/sdl.h
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#include "SDL3/SDL.h"
|
||||||
|
#include "SDL3_image/SDL_image.h"
|
||||||
|
#include "sdl_rename.h"
|
@@ -1,4 +0,0 @@
|
|||||||
pub usingnamespace @cImport({
|
|
||||||
@cInclude("SDL3/SDL.h");
|
|
||||||
@cInclude("sdl_rename.h");
|
|
||||||
});
|
|
358
src/assets.zig
358
src/assets.zig
@@ -1,104 +1,314 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const sdl = @import("sdl");
|
const builtin = @import("builtin");
|
||||||
const err = @import("error.zig");
|
const err = @import("error.zig");
|
||||||
const c = @import("c.zig");
|
|
||||||
const comp = @import("components.zig");
|
|
||||||
const Game = @import("game.zig");
|
const Game = @import("game.zig");
|
||||||
const Graphics = @import("graphics.zig");
|
const FileLoader = @import("assets/file.zig");
|
||||||
|
const TextureLoader = @import("assets/texture.zig");
|
||||||
|
const GltfLoader = @import("assets/gltf.zig");
|
||||||
const Assets = @This();
|
const Assets = @This();
|
||||||
|
|
||||||
const Storage = comp.Storage(Asset, .{});
|
// TODO: Unload assets in correct order to account for asset dependencies
|
||||||
|
|
||||||
var storage: Storage = undefined;
|
pub const File = AssetContainer(FileLoader);
|
||||||
|
pub const Texture = AssetContainer(TextureLoader);
|
||||||
|
pub const Object = AssetContainer(GltfLoader);
|
||||||
|
|
||||||
|
const WORKERS_MAX = 4;
|
||||||
|
var next_worker_update: usize = 0;
|
||||||
|
var workers: [WORKERS_MAX]WorkerState = undefined;
|
||||||
|
const WorkerState = struct {
|
||||||
|
running: bool = false,
|
||||||
|
thread: ?std.Thread = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const AssetMap = std.HashMapUnmanaged(AssetId, *AssetCell, AssetContext, 80);
|
||||||
|
var asset_map_mutex: std.Thread.Mutex = .{};
|
||||||
|
var asset_map: AssetMap = undefined;
|
||||||
|
|
||||||
|
const RequestBoard = std.ArrayListUnmanaged(*AssetCell);
|
||||||
|
var request_board_mutex: std.Thread.Mutex = .{};
|
||||||
|
var request_board: RequestBoard = undefined;
|
||||||
|
var request_board_counter: usize = 0;
|
||||||
|
|
||||||
|
const FreeBoard = std.ArrayListUnmanaged(*AssetCell);
|
||||||
|
var free_board_mutex: std.Thread.Mutex = .{};
|
||||||
|
var free_board: FreeBoard = undefined;
|
||||||
|
|
||||||
|
const AssetId = struct {
|
||||||
|
type: AssetType,
|
||||||
|
path: []const u8,
|
||||||
|
};
|
||||||
|
const AssetContext = struct {
|
||||||
|
pub fn hash(self: @This(), key: AssetId) u64 {
|
||||||
|
_ = self;
|
||||||
|
var hasher = std.hash.Wyhash.init(@intFromEnum(key.type));
|
||||||
|
hasher.update(key.path);
|
||||||
|
return hasher.final();
|
||||||
|
}
|
||||||
|
pub fn eql(self: @This(), a: AssetId, b: AssetId) bool {
|
||||||
|
_ = self;
|
||||||
|
return a.type == b.type and std.mem.eql(u8, a.path, b.path);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const LoadError = error{
|
||||||
|
DependencyError,
|
||||||
|
ParsingError,
|
||||||
|
SdlError,
|
||||||
|
FileTooBig,
|
||||||
|
UnsupportedAsset,
|
||||||
|
} || std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError || std.json.ParseError(std.json.Scanner);
|
||||||
|
|
||||||
pub const AssetType = enum {
|
pub const AssetType = enum {
|
||||||
|
file,
|
||||||
texture,
|
texture,
|
||||||
};
|
gltf,
|
||||||
pub const Texture = struct {
|
|
||||||
handle: Storage.Key,
|
pub fn getType(comptime self: @This()) type {
|
||||||
|
return switch (self) {
|
||||||
|
.file => FileLoader,
|
||||||
|
.texture => TextureLoader,
|
||||||
|
.gltf => GltfLoader,
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const Asset = struct {
|
const AssetState = union(enum) {
|
||||||
|
not_loaded,
|
||||||
|
loaded,
|
||||||
|
fail: LoadError,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const AssetCell = struct {
|
||||||
|
mutex: std.Thread.Mutex,
|
||||||
|
type: AssetType,
|
||||||
|
data: *void,
|
||||||
path: []const u8,
|
path: []const u8,
|
||||||
data: union(AssetType) {
|
loader: *const fn (*AssetCell, std.mem.Allocator) LoadError!void,
|
||||||
texture: AssetTexture,
|
unloader: *const fn (*AssetCell, std.mem.Allocator) void,
|
||||||
|
state: AssetState,
|
||||||
|
counter: usize,
|
||||||
|
|
||||||
|
fn load(self: *AssetCell, alloc: std.mem.Allocator) void {
|
||||||
|
self.loader(self, alloc) catch |e| {
|
||||||
|
if (builtin.mode == .Debug)
|
||||||
|
std.debug.panic("Asset loading error: {s} - {}!\n", .{ self.path, e });
|
||||||
|
self.state = .{ .fail = e };
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.state = .loaded;
|
||||||
|
}
|
||||||
|
fn unload(self: *AssetCell, alloc: std.mem.Allocator) void {
|
||||||
|
self.unloader(self, alloc);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn AssetContainer(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
data_pointer: ?*T = null,
|
||||||
|
asset_pointer: *AssetCell,
|
||||||
|
last_state: AssetState = .not_loaded,
|
||||||
|
|
||||||
|
pub fn get(self: *@This()) ?*T {
|
||||||
|
switch (self.last_state) {
|
||||||
|
.loaded => {
|
||||||
|
@branchHint(.likely);
|
||||||
|
return self.data_pointer;
|
||||||
},
|
},
|
||||||
};
|
.fail => {
|
||||||
pub const AssetTexture = struct {
|
return null;
|
||||||
texture: *sdl.GPUTexture,
|
},
|
||||||
sampler: *sdl.GPUSampler,
|
.not_loaded => {
|
||||||
};
|
if (self.asset_pointer.mutex.tryLock()) {
|
||||||
|
defer self.asset_pointer.mutex.unlock();
|
||||||
|
self.last_state = self.asset_pointer.state;
|
||||||
|
}
|
||||||
|
if (self.last_state == .loaded) {
|
||||||
|
self.data_pointer = @ptrCast(@alignCast(self.asset_pointer.data));
|
||||||
|
return self.data_pointer;
|
||||||
|
} else return null;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: Add smth like `Assets.immediateLoad`
|
||||||
|
|
||||||
|
/// To be used by worker threads to request other assets
|
||||||
|
pub fn getSync(self: *@This()) !*T {
|
||||||
|
sw: switch (self.last_state) {
|
||||||
|
.loaded => {
|
||||||
|
return self.data_pointer.?;
|
||||||
|
},
|
||||||
|
.fail => |e| {
|
||||||
|
return e;
|
||||||
|
},
|
||||||
|
.not_loaded => {
|
||||||
|
// TODO: Do something else while the asset is locked?
|
||||||
|
self.asset_pointer.mutex.lock();
|
||||||
|
defer self.asset_pointer.mutex.unlock();
|
||||||
|
self.last_state = self.asset_pointer.state;
|
||||||
|
if (self.last_state == .not_loaded) {
|
||||||
|
self.asset_pointer.load(Game.alloc);
|
||||||
|
self.last_state = self.asset_pointer.state;
|
||||||
|
}
|
||||||
|
if (self.last_state == .loaded) {
|
||||||
|
self.data_pointer = @ptrCast(@alignCast(self.asset_pointer.data));
|
||||||
|
}
|
||||||
|
continue :sw self.last_state;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn init() void {
|
pub fn init() void {
|
||||||
Assets.storage = Storage.init();
|
Assets.next_worker_update = 0;
|
||||||
|
Assets.workers = .{WorkerState{}} ** WORKERS_MAX;
|
||||||
|
Assets.asset_map_mutex = .{};
|
||||||
|
Assets.asset_map = AssetMap.empty;
|
||||||
|
Assets.request_board_mutex = .{};
|
||||||
|
Assets.request_board = RequestBoard.empty;
|
||||||
|
Assets.request_board_counter = 0;
|
||||||
|
Assets.free_board_mutex = .{};
|
||||||
|
Assets.free_board = FreeBoard.empty;
|
||||||
}
|
}
|
||||||
pub fn deinit() void {
|
pub fn deinit() void {
|
||||||
var iter = Assets.storage.iter();
|
for (&Assets.workers) |*worker| {
|
||||||
|
if (worker.thread == null) continue;
|
||||||
|
worker.thread.?.join();
|
||||||
|
}
|
||||||
|
updateFree();
|
||||||
|
var iter = Assets.asset_map.valueIterator();
|
||||||
while (iter.next()) |asset| {
|
while (iter.next()) |asset| {
|
||||||
Assets.freeAsset(asset);
|
std.debug.assert(asset.*.counter == 0);
|
||||||
|
if (asset.*.state == .loaded)
|
||||||
|
asset.*.unload(Game.alloc);
|
||||||
|
Game.alloc.free(asset.*.path);
|
||||||
|
Game.alloc.destroy(asset.*);
|
||||||
}
|
}
|
||||||
Assets.storage.deinit();
|
Assets.asset_map.clearAndFree(Game.alloc);
|
||||||
|
Assets.request_board.clearAndFree(Game.alloc);
|
||||||
|
Assets.free_board.clearAndFree(Game.alloc);
|
||||||
}
|
}
|
||||||
pub fn load(comptime asset_type: AssetType, path: []const u8) typeFromAssetType(asset_type) {
|
pub fn update() void {
|
||||||
switch (asset_type) {
|
const worker = &Assets.workers[Assets.next_worker_update];
|
||||||
.texture => {
|
if (!@atomicLoad(bool, &worker.running, .acquire) and worker.thread != null) {
|
||||||
const data = loadFile(Game.alloc, path) catch |e| err.file(e, path);
|
worker.thread.?.join();
|
||||||
var x: i32 = undefined;
|
worker.thread = null;
|
||||||
var y: i32 = undefined;
|
|
||||||
var z: i32 = undefined;
|
|
||||||
const image = c.stbi_load_from_memory(@ptrCast(data), @intCast(data.len), &x, &y, &z, 4);
|
|
||||||
Game.alloc.free(data);
|
|
||||||
if (image == null) err.stbi();
|
|
||||||
const image_slice = image[0..@intCast(x * y * z)];
|
|
||||||
const texture, const sampler = Graphics.loadTexture(@intCast(x), @intCast(y), image_slice);
|
|
||||||
c.stbi_image_free(image);
|
|
||||||
return .{ .handle = Assets.storage.add(.{
|
|
||||||
.path = path,
|
|
||||||
.data = .{ .texture = .{
|
|
||||||
.texture = texture,
|
|
||||||
.sampler = sampler,
|
|
||||||
} },
|
|
||||||
}) };
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
if (worker.thread == null and @atomicLoad(usize, &Assets.request_board_counter, .monotonic) > 4 * Assets.next_worker_update) {
|
||||||
pub fn free(asset: anytype) void {
|
worker.running = true;
|
||||||
if (Assets.storage.free(asset.handle)) |stored| {
|
worker.thread = std.Thread.spawn(.{}, loaderLoop, .{Assets.next_worker_update}) catch err.oom();
|
||||||
freeAsset(stored);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
pub fn freeAsset(asset: *Asset) void {
|
Assets.next_worker_update += 1;
|
||||||
switch (asset.data) {
|
if (Assets.next_worker_update >= WORKERS_MAX) {
|
||||||
.texture => {
|
Assets.next_worker_update = 0;
|
||||||
Graphics.unloadTexture(asset.data.texture.texture, asset.data.texture.sampler);
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
pub fn get(asset: anytype) ?assetTypeFromType(@TypeOf(asset)) {
|
updateFree();
|
||||||
if (Assets.storage.get(asset.handle)) |stored| {
|
|
||||||
switch (@TypeOf(asset)) {
|
|
||||||
Texture => {
|
|
||||||
return stored.data.texture;
|
|
||||||
},
|
|
||||||
else => @compileError("Cannot get asset of type " ++ @typeName(@TypeOf(asset))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unreachable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loadFile(alloc: std.mem.Allocator, path: []const u8) ![]u8 {
|
fn updateFree() void {
|
||||||
const file = try std.fs.cwd().openFile(path, .{});
|
// TODO: Delegate freeing to worker threads?
|
||||||
defer file.close();
|
Assets.asset_map_mutex.lock();
|
||||||
return file.readToEndAlloc(alloc, std.math.maxInt(i32));
|
defer Assets.asset_map_mutex.unlock();
|
||||||
|
|
||||||
|
Assets.free_board_mutex.lock();
|
||||||
|
while (Assets.free_board.pop()) |request| {
|
||||||
|
if (@atomicLoad(usize, &request.counter, .monotonic) == 0) {
|
||||||
|
if (!Assets.asset_map.remove(.{ .type = request.type, .path = request.path })) continue;
|
||||||
|
if (request.state == .loaded) {
|
||||||
|
Assets.free_board_mutex.unlock();
|
||||||
|
request.unload(Game.alloc);
|
||||||
|
Assets.free_board_mutex.lock();
|
||||||
|
}
|
||||||
|
Game.alloc.free(request.path);
|
||||||
|
Game.alloc.destroy(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assets.free_board_mutex.unlock();
|
||||||
}
|
}
|
||||||
fn typeFromAssetType(comptime asset_type: AssetType) type {
|
|
||||||
return switch (asset_type) {
|
pub fn load(comptime asset_type: AssetType, path: []const u8) AssetContainer(asset_type.getType()) {
|
||||||
.texture => Texture,
|
const asset = mapAsset(asset_type, path);
|
||||||
|
{
|
||||||
|
Assets.request_board_mutex.lock();
|
||||||
|
Assets.request_board.append(Game.alloc, asset) catch err.oom();
|
||||||
|
_ = @atomicRmw(usize, &Assets.request_board_counter, .Add, 1, .monotonic);
|
||||||
|
Assets.request_board_mutex.unlock();
|
||||||
|
}
|
||||||
|
return .{ .asset_pointer = asset };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn free(asset: anytype) void {
|
||||||
|
const prev = @atomicRmw(usize, &asset.asset_pointer.counter, .Sub, 1, .monotonic);
|
||||||
|
if (prev == 1) {
|
||||||
|
Assets.free_board_mutex.lock();
|
||||||
|
Assets.free_board.append(Game.alloc, asset.asset_pointer) catch err.oom();
|
||||||
|
Assets.free_board_mutex.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loaderLoop(worker_id: usize) void {
|
||||||
|
var processed: usize = 0;
|
||||||
|
defer @atomicStore(bool, &Assets.workers[worker_id].running, false, .release);
|
||||||
|
while (true) {
|
||||||
|
const asset = blk: {
|
||||||
|
Assets.request_board_mutex.lock();
|
||||||
|
defer Assets.request_board_mutex.unlock();
|
||||||
|
|
||||||
|
const request = Assets.request_board.pop() orelse return;
|
||||||
|
_ = @atomicRmw(usize, &Assets.request_board_counter, .Sub, 1, .monotonic);
|
||||||
|
break :blk request;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
defer processed += 1;
|
||||||
|
asset.mutex.lock();
|
||||||
|
if (asset.state == .not_loaded)
|
||||||
|
asset.load(Game.alloc);
|
||||||
|
asset.mutex.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn assetTypeFromType(comptime T: type) type {
|
|
||||||
return switch (T) {
|
fn mapAsset(comptime asset_type: AssetType, path: []const u8) *AssetCell {
|
||||||
Texture => AssetTexture,
|
Assets.asset_map_mutex.lock();
|
||||||
else => unreachable,
|
defer Assets.asset_map_mutex.unlock();
|
||||||
|
|
||||||
|
const res = Assets.asset_map.getOrPut(Game.alloc, .{ .type = asset_type, .path = path }) catch err.oom();
|
||||||
|
if (!res.found_existing) {
|
||||||
|
res.value_ptr.* = Game.alloc.create(AssetCell) catch err.oom();
|
||||||
|
res.value_ptr.*.* = .{
|
||||||
|
.mutex = .{},
|
||||||
|
.type = asset_type,
|
||||||
|
.data = undefined,
|
||||||
|
.path = Game.alloc.dupe(u8, path) catch err.oom(),
|
||||||
|
.loader = Assets.makeLoader(asset_type.getType(), asset_type.getType().load),
|
||||||
|
.unloader = Assets.makeUnloader(asset_type.getType(), asset_type.getType().unload),
|
||||||
|
.state = .not_loaded,
|
||||||
|
.counter = 1,
|
||||||
};
|
};
|
||||||
|
} else _ = @atomicRmw(usize, &res.value_ptr.*.counter, .Add, 1, .monotonic);
|
||||||
|
return res.value_ptr.*;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn makeLoader(comptime T: type, comptime func: *const fn ([]const u8, std.mem.Allocator) LoadError!T) *const fn (*AssetCell, std.mem.Allocator) LoadError!void {
|
||||||
|
const Container = struct {
|
||||||
|
pub fn loader(cell: *AssetCell, alloc: std.mem.Allocator) LoadError!void {
|
||||||
|
const mem = try alloc.create(T);
|
||||||
|
errdefer alloc.destroy(mem);
|
||||||
|
mem.* = try func(cell.path, alloc);
|
||||||
|
cell.data = @ptrCast(mem);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return Container.loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn makeUnloader(comptime T: type, comptime func: *const fn (T, std.mem.Allocator) void) *const fn (*AssetCell, std.mem.Allocator) void {
|
||||||
|
const Container = struct {
|
||||||
|
pub fn unloader(cell: *AssetCell, alloc: std.mem.Allocator) void {
|
||||||
|
func(@as(*T, @ptrCast(@alignCast(cell.data))).*, alloc);
|
||||||
|
alloc.destroy(@as(*T, @ptrCast(@alignCast(cell.data))));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return Container.unloader;
|
||||||
}
|
}
|
||||||
|
14
src/assets/file.zig
Normal file
14
src/assets/file.zig
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Assets = @import("../assets.zig");
|
||||||
|
|
||||||
|
bytes: []u8,
|
||||||
|
|
||||||
|
pub fn load(path: []const u8, alloc: std.mem.Allocator) Assets.LoadError!@This() {
|
||||||
|
const file = try std.fs.cwd().openFile(path, .{});
|
||||||
|
defer file.close();
|
||||||
|
return .{ .bytes = try file.readToEndAlloc(alloc, std.math.maxInt(i32)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unload(self: @This(), alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.bytes);
|
||||||
|
}
|
389
src/assets/gltf.zig
Normal file
389
src/assets/gltf.zig
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const sdl = @import("sdl");
|
||||||
|
const err = @import("../error.zig");
|
||||||
|
const Assets = @import("../assets.zig");
|
||||||
|
const Graphics = @import("../graphics.zig");
|
||||||
|
|
||||||
|
nodes: []Node,
|
||||||
|
meshes: []Mesh,
|
||||||
|
|
||||||
|
const Node = struct {
|
||||||
|
mesh: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Mesh = struct {
|
||||||
|
primitives: []Primitive,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Primitive = struct {
|
||||||
|
vertex_buffer: *sdl.GPUBuffer,
|
||||||
|
index_buffer: ?*sdl.GPUBuffer = null,
|
||||||
|
|
||||||
|
vertices: u32,
|
||||||
|
indices: u32 = 0,
|
||||||
|
texture: Assets.Texture,
|
||||||
|
};
|
||||||
|
|
||||||
|
const GltfJson = struct {
|
||||||
|
scene: u32,
|
||||||
|
scenes: []GltfSceneJson,
|
||||||
|
nodes: []GltfNodeJson,
|
||||||
|
materials: []GltfMaterialJson,
|
||||||
|
meshes: []GltfMeshJson,
|
||||||
|
textures: []GltfTextureJson,
|
||||||
|
images: []GltfImageJson,
|
||||||
|
accessors: []GltfAccessorJson,
|
||||||
|
bufferViews: []GltfBufferViewJson,
|
||||||
|
samplers: []GltfSamplerJson,
|
||||||
|
buffers: []GltfBufferJson,
|
||||||
|
|
||||||
|
const GltfSceneJson = struct {
|
||||||
|
nodes: []u32,
|
||||||
|
};
|
||||||
|
const GltfNodeJson = struct {
|
||||||
|
mesh: u32,
|
||||||
|
};
|
||||||
|
const GltfMaterialJson = struct {
|
||||||
|
pbrMetallicRoughness: GltfPbrMRJson,
|
||||||
|
|
||||||
|
const GltfPbrMRJson = struct {
|
||||||
|
baseColorTexture: GltfPbrMRBaseColorTexture,
|
||||||
|
|
||||||
|
const GltfPbrMRBaseColorTexture = struct {
|
||||||
|
index: u32,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const GltfMeshJson = struct {
|
||||||
|
primitives: []GltfPrimitiveJson,
|
||||||
|
|
||||||
|
const GltfPrimitiveJson = struct {
|
||||||
|
attributes: GltfAttributesJson,
|
||||||
|
indices: u32,
|
||||||
|
material: u32,
|
||||||
|
|
||||||
|
const GltfAttributesJson = struct {
|
||||||
|
POSITION: u32,
|
||||||
|
TEXCOORD_0: u32,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const GltfTextureJson = struct {
|
||||||
|
sampler: ?u32,
|
||||||
|
source: u32,
|
||||||
|
};
|
||||||
|
const GltfImageJson = struct {
|
||||||
|
uri: []u8,
|
||||||
|
};
|
||||||
|
const GltfAccessorJson = struct {
|
||||||
|
bufferView: u32,
|
||||||
|
componentType: GltfComponentTypeJson,
|
||||||
|
count: u32,
|
||||||
|
type: GltfAccessorTypeJson,
|
||||||
|
|
||||||
|
const GltfComponentTypeJson = enum(u32) {
|
||||||
|
i8 = 5120,
|
||||||
|
u8 = 5121,
|
||||||
|
i16 = 5122,
|
||||||
|
u16 = 5123,
|
||||||
|
i32 = 5125,
|
||||||
|
f32 = 5126,
|
||||||
|
};
|
||||||
|
const GltfAccessorTypeJson = enum {
|
||||||
|
SCALAR,
|
||||||
|
VEC2,
|
||||||
|
VEC3,
|
||||||
|
VEC4,
|
||||||
|
MAT2,
|
||||||
|
MAT3,
|
||||||
|
MAT4,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn slice(accessor: @This(), comptime T: type, views: []const GltfBufferViewJson, buffers: []Assets.File) !?[]align(1) T {
|
||||||
|
if (accessor.bufferView >= views.len) return null;
|
||||||
|
const view = &views[accessor.bufferView];
|
||||||
|
|
||||||
|
if (view.buffer >= buffers.len) return null;
|
||||||
|
const buffer = try buffers[view.buffer].getSync();
|
||||||
|
|
||||||
|
const component_length = switch (T) {
|
||||||
|
[3]f32 => 12,
|
||||||
|
[2]f32 => 8,
|
||||||
|
u16 => 2,
|
||||||
|
else => @compileError("Accessor of " ++ @tagName(accessor.type) ++ " of " ++ @tagName(accessor.componentType) ++ " is not supported."),
|
||||||
|
};
|
||||||
|
|
||||||
|
const start = view.byteOffset;
|
||||||
|
const length = component_length * accessor.count;
|
||||||
|
const end = start + length;
|
||||||
|
|
||||||
|
if (length != view.byteLength) return error.ParsingError;
|
||||||
|
|
||||||
|
return @as([]align(1) T, @ptrCast(buffer.bytes[start..end]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const GltfBufferViewJson = struct {
|
||||||
|
buffer: u32,
|
||||||
|
byteLength: u32,
|
||||||
|
byteOffset: u32 = 0,
|
||||||
|
};
|
||||||
|
const GltfSamplerJson = struct {
|
||||||
|
magFilter: GltfFilterJson = .NEAREST,
|
||||||
|
minFilter: GltfFilterJson = .LINEAR,
|
||||||
|
wrapS: GltfWrapJson = .CLAMP_TO_EDGE,
|
||||||
|
wrapT: GltfWrapJson = .CLAMP_TO_EDGE,
|
||||||
|
|
||||||
|
const GltfFilterJson = enum(u32) {
|
||||||
|
NEAREST = 9728,
|
||||||
|
LINEAR = 9729,
|
||||||
|
NEAREST_MIPMAP_NEAREST = 9984,
|
||||||
|
LINEAR_MIPMAP_NEAREST = 9985,
|
||||||
|
NEAREST_MIPMAP_LINEAR = 9986,
|
||||||
|
LINEAR_MIPMAP_LINEAR = 9987,
|
||||||
|
};
|
||||||
|
const GltfWrapJson = enum(u32) {
|
||||||
|
CLAMP_TO_EDGE = 33071,
|
||||||
|
MIRRORED_REPEAT = 33648,
|
||||||
|
REPEAT = 10497,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const GltfBufferJson = struct {
|
||||||
|
// byteLength: u32,
|
||||||
|
uri: []u8,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn load(path: []const u8, alloc: std.mem.Allocator) Assets.LoadError!@This() {
|
||||||
|
var json = Assets.load(.file, path);
|
||||||
|
defer Assets.free(json);
|
||||||
|
|
||||||
|
const parsed_gltf = try std.json.parseFromSlice(
|
||||||
|
GltfJson,
|
||||||
|
alloc,
|
||||||
|
(try json.getSync()).bytes,
|
||||||
|
.{ .ignore_unknown_fields = true },
|
||||||
|
);
|
||||||
|
defer parsed_gltf.deinit();
|
||||||
|
const gltf = &parsed_gltf.value;
|
||||||
|
|
||||||
|
if (gltf.scene >= gltf.scenes.len) return error.ParsingError;
|
||||||
|
const scene = &gltf.scenes[gltf.scene];
|
||||||
|
|
||||||
|
var buffers_init: u32 = 0;
|
||||||
|
const buffers = try alloc.alloc(Assets.File, gltf.buffers.len);
|
||||||
|
defer alloc.free(buffers);
|
||||||
|
|
||||||
|
defer for (buffers[0..buffers_init]) |buffer| {
|
||||||
|
Assets.free(buffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (0.., buffers) |i, *buffer| {
|
||||||
|
const buffer_path = try std.fs.path.join(alloc, &.{ std.fs.path.dirname(path) orelse return error.ParsingError, gltf.buffers[i].uri });
|
||||||
|
defer alloc.free(buffer_path);
|
||||||
|
buffer.* = Assets.load(.file, buffer_path);
|
||||||
|
buffers_init += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodes = try alloc.alloc(Node, scene.nodes.len);
|
||||||
|
errdefer alloc.free(nodes);
|
||||||
|
|
||||||
|
var meshes_init: u32 = 0;
|
||||||
|
const meshes = try alloc.alloc(Mesh, gltf.meshes.len);
|
||||||
|
errdefer alloc.free(nodes);
|
||||||
|
|
||||||
|
errdefer for (meshes[0..meshes_init]) |*mesh| {
|
||||||
|
for (mesh.primitives) |*primitive| {
|
||||||
|
sdl.ReleaseGPUBuffer(Graphics.device, primitive.vertex_buffer);
|
||||||
|
if (primitive.index_buffer) |buf| sdl.ReleaseGPUBuffer(Graphics.device, buf);
|
||||||
|
Assets.free(primitive.texture);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (0.., nodes) |i, *node| {
|
||||||
|
const node_index = scene.nodes[i];
|
||||||
|
if (node_index >= gltf.nodes.len) return error.ParsingError;
|
||||||
|
const gltf_node = &gltf.nodes[node_index];
|
||||||
|
if (gltf_node.mesh >= gltf.meshes.len) return error.ParsingError;
|
||||||
|
node.mesh = gltf_node.mesh;
|
||||||
|
}
|
||||||
|
for (0.., meshes) |i, *mesh| {
|
||||||
|
const gltf_mesh = &gltf.meshes[i];
|
||||||
|
|
||||||
|
var primitivs_init: u32 = 0;
|
||||||
|
const primitives = try alloc.alloc(Primitive, gltf_mesh.primitives.len);
|
||||||
|
errdefer alloc.free(primitives);
|
||||||
|
|
||||||
|
errdefer for (primitives[0..primitivs_init]) |*primitive| {
|
||||||
|
sdl.ReleaseGPUBuffer(Graphics.device, primitive.vertex_buffer);
|
||||||
|
if (primitive.index_buffer) |buf| sdl.ReleaseGPUBuffer(Graphics.device, buf);
|
||||||
|
Assets.free(primitive.texture);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (0.., primitives) |j, *primitive| {
|
||||||
|
const gltf_primitive = gltf_mesh.primitives[j];
|
||||||
|
|
||||||
|
if (gltf_primitive.attributes.POSITION >= gltf.accessors.len) return error.ParsingError;
|
||||||
|
if (gltf_primitive.attributes.TEXCOORD_0 >= gltf.accessors.len) return error.ParsingError;
|
||||||
|
if (gltf_primitive.material >= gltf.materials.len) return error.ParsingError;
|
||||||
|
const material = &gltf.materials[gltf_primitive.material];
|
||||||
|
const texture_index = material.pbrMetallicRoughness.baseColorTexture.index;
|
||||||
|
if (texture_index >= gltf.textures.len) return error.ParsingError;
|
||||||
|
const texture = &gltf.textures[texture_index];
|
||||||
|
if (texture.source >= gltf.images.len) return error.ParsingError;
|
||||||
|
const image = &gltf.images[texture.source];
|
||||||
|
|
||||||
|
const position = try gltf.accessors[gltf_primitive.attributes.POSITION].slice([3]f32, gltf.bufferViews, buffers) orelse return error.ParsingError;
|
||||||
|
const uv = try gltf.accessors[gltf_primitive.attributes.TEXCOORD_0].slice([2]f32, gltf.bufferViews, buffers) orelse return error.ParsingError;
|
||||||
|
const index = try gltf.accessors[gltf_primitive.indices].slice(u16, gltf.bufferViews, buffers) orelse return error.ParsingError;
|
||||||
|
|
||||||
|
primitive.vertex_buffer, primitive.index_buffer = try loadMesh(position, uv, index, alloc);
|
||||||
|
errdefer sdl.ReleaseGPUBuffer(Graphics.device, primitive.vertex_buffer);
|
||||||
|
errdefer sdl.ReleaseGPUBuffer(Graphics.device, primitive.index_buffer);
|
||||||
|
|
||||||
|
primitive.vertices = @intCast(position.len);
|
||||||
|
primitive.indices = @intCast(index.len);
|
||||||
|
|
||||||
|
const texture_path = try std.fs.path.join(alloc, &.{ std.fs.path.dirname(path) orelse return error.ParsingError, image.uri });
|
||||||
|
defer alloc.free(texture_path);
|
||||||
|
primitive.texture = Assets.load(.texture, texture_path);
|
||||||
|
|
||||||
|
primitivs_init += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh.primitives = primitives;
|
||||||
|
meshes_init += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.nodes = nodes,
|
||||||
|
.meshes = meshes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unload(self: @This(), alloc: std.mem.Allocator) void {
|
||||||
|
for (self.meshes) |mesh| {
|
||||||
|
for (mesh.primitives) |*primitive| {
|
||||||
|
sdl.ReleaseGPUBuffer(Graphics.device, primitive.vertex_buffer);
|
||||||
|
sdl.ReleaseGPUBuffer(Graphics.device, primitive.index_buffer);
|
||||||
|
Assets.free(primitive.texture);
|
||||||
|
}
|
||||||
|
alloc.free(mesh.primitives);
|
||||||
|
}
|
||||||
|
alloc.free(self.meshes);
|
||||||
|
alloc.free(self.nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn loadMesh(position: []align(1) const [3]f32, uv: []align(1) const [2]f32, index: []align(1) const u16, alloc: std.mem.Allocator) !struct { *sdl.GPUBuffer, *sdl.GPUBuffer } {
|
||||||
|
if (position.len != uv.len) return error.ParsingError;
|
||||||
|
const vertices: u32 = @intCast(position.len);
|
||||||
|
const indices: u32 = @intCast(index.len);
|
||||||
|
|
||||||
|
const BYTES_PER_VERTEX = 20;
|
||||||
|
const BYTES_PER_INDEX = 2;
|
||||||
|
|
||||||
|
const vertex_buffer = sdl.CreateGPUBuffer(Graphics.device, &.{
|
||||||
|
.size = vertices * BYTES_PER_VERTEX,
|
||||||
|
.usage = sdl.GPU_BUFFERUSAGE_VERTEX,
|
||||||
|
}) orelse return error.SdlError;
|
||||||
|
errdefer sdl.ReleaseGPUBuffer(Graphics.device, vertex_buffer);
|
||||||
|
|
||||||
|
const index_buffer = sdl.CreateGPUBuffer(Graphics.device, &.{
|
||||||
|
.size = indices * 2,
|
||||||
|
.usage = sdl.GPU_BUFFERUSAGE_INDEX,
|
||||||
|
}) orelse return error.SdlError;
|
||||||
|
errdefer sdl.ReleaseGPUBuffer(Graphics.device, index_buffer);
|
||||||
|
|
||||||
|
const TRANSFER_CAPACITY = Graphics.TRANSFER_BUFFER_DEFAULT_CAPACITY;
|
||||||
|
|
||||||
|
const transfer_buffer = sdl.CreateGPUTransferBuffer(Graphics.device, &.{
|
||||||
|
.size = TRANSFER_CAPACITY,
|
||||||
|
.usage = sdl.GPU_TRANSFERBUFFERUSAGE_UPLOAD,
|
||||||
|
}) orelse return error.SdlError;
|
||||||
|
defer sdl.ReleaseGPUTransferBuffer(Graphics.device, transfer_buffer);
|
||||||
|
|
||||||
|
const buffer = try alloc.alloc(u8, TRANSFER_CAPACITY);
|
||||||
|
defer alloc.free(buffer);
|
||||||
|
|
||||||
|
var vertices_uploaded: u32 = 0;
|
||||||
|
while (vertices_uploaded < vertices) {
|
||||||
|
const vertices_to_upload = @min(vertices - vertices_uploaded, TRANSFER_CAPACITY / BYTES_PER_VERTEX);
|
||||||
|
if (vertices_to_upload == 0) return error.FileTooBig;
|
||||||
|
|
||||||
|
for (0..vertices_to_upload) |i| {
|
||||||
|
const V = packed struct { x: f32, y: f32, z: f32, u: f32, v: f32 };
|
||||||
|
std.mem.copyForwards(
|
||||||
|
u8,
|
||||||
|
buffer[BYTES_PER_VERTEX * i ..],
|
||||||
|
&@as([BYTES_PER_VERTEX]u8, @bitCast(V{
|
||||||
|
.x = position[vertices_uploaded + i][0],
|
||||||
|
.y = position[vertices_uploaded + i][1],
|
||||||
|
.z = position[vertices_uploaded + i][2],
|
||||||
|
.u = uv[vertices_uploaded + i][0],
|
||||||
|
.v = uv[vertices_uploaded + i][1],
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse return error.SdlError;
|
||||||
|
{
|
||||||
|
errdefer _ = sdl.CancelGPUCommandBuffer(command_buffer);
|
||||||
|
const copy_pass = sdl.BeginGPUCopyPass(command_buffer) orelse return error.SdlError;
|
||||||
|
defer sdl.EndGPUCopyPass(copy_pass);
|
||||||
|
|
||||||
|
const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(Graphics.device, transfer_buffer, false) orelse err.sdl());
|
||||||
|
@memcpy(map, buffer[0 .. vertices_to_upload * BYTES_PER_VERTEX]);
|
||||||
|
sdl.UnmapGPUTransferBuffer(Graphics.device, transfer_buffer);
|
||||||
|
|
||||||
|
sdl.UploadToGPUBuffer(copy_pass, &.{
|
||||||
|
.transfer_buffer = transfer_buffer,
|
||||||
|
}, &.{
|
||||||
|
.buffer = vertex_buffer,
|
||||||
|
.offset = vertices_uploaded * BYTES_PER_VERTEX,
|
||||||
|
.size = vertices_to_upload * BYTES_PER_VERTEX,
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
vertices_uploaded += vertices_to_upload;
|
||||||
|
const fence = sdl.SubmitGPUCommandBufferAndAcquireFence(command_buffer) orelse return error.SdlError;
|
||||||
|
defer sdl.ReleaseGPUFence(Graphics.device, fence);
|
||||||
|
if (!sdl.WaitForGPUFences(Graphics.device, true, &fence, 1)) return error.SdlError;
|
||||||
|
}
|
||||||
|
|
||||||
|
var indices_uploaded: u32 = 0;
|
||||||
|
while (indices_uploaded < indices) {
|
||||||
|
const indices_to_upload = @min(indices - indices_uploaded, TRANSFER_CAPACITY / BYTES_PER_INDEX);
|
||||||
|
if (indices_to_upload == 0) return error.FileTooBig;
|
||||||
|
|
||||||
|
for (0..indices_to_upload) |i| {
|
||||||
|
std.mem.copyForwards(
|
||||||
|
u8,
|
||||||
|
buffer[BYTES_PER_INDEX * i ..],
|
||||||
|
&@as([BYTES_PER_INDEX]u8, @bitCast(index[i])),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse return error.SdlError;
|
||||||
|
{
|
||||||
|
errdefer _ = sdl.CancelGPUCommandBuffer(command_buffer);
|
||||||
|
const copy_pass = sdl.BeginGPUCopyPass(command_buffer) orelse return error.SdlError;
|
||||||
|
defer sdl.EndGPUCopyPass(copy_pass);
|
||||||
|
|
||||||
|
const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(Graphics.device, transfer_buffer, false) orelse err.sdl());
|
||||||
|
@memcpy(map, buffer[0 .. indices_to_upload * BYTES_PER_INDEX]);
|
||||||
|
sdl.UnmapGPUTransferBuffer(Graphics.device, transfer_buffer);
|
||||||
|
|
||||||
|
sdl.UploadToGPUBuffer(copy_pass, &.{
|
||||||
|
.transfer_buffer = transfer_buffer,
|
||||||
|
}, &.{
|
||||||
|
.buffer = index_buffer,
|
||||||
|
.offset = indices_uploaded * BYTES_PER_INDEX,
|
||||||
|
.size = indices_to_upload * BYTES_PER_INDEX,
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
indices_uploaded += indices_to_upload;
|
||||||
|
const fence = sdl.SubmitGPUCommandBufferAndAcquireFence(command_buffer) orelse return error.SdlError;
|
||||||
|
defer sdl.ReleaseGPUFence(Graphics.device, fence);
|
||||||
|
if (!sdl.WaitForGPUFences(Graphics.device, true, &fence, 1)) return error.SdlError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ vertex_buffer, index_buffer };
|
||||||
|
}
|
139
src/assets/texture.zig
Normal file
139
src/assets/texture.zig
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const sdl = @import("sdl");
|
||||||
|
const err = @import("../error.zig");
|
||||||
|
const Assets = @import("../assets.zig");
|
||||||
|
const Graphics = @import("../graphics.zig");
|
||||||
|
|
||||||
|
texture: *sdl.GPUTexture,
|
||||||
|
sampler: *sdl.GPUSampler,
|
||||||
|
|
||||||
|
pub fn load(path: []const u8, alloc: std.mem.Allocator) Assets.LoadError!@This() {
|
||||||
|
_ = alloc;
|
||||||
|
var file = Assets.load(.file, path);
|
||||||
|
defer Assets.free(file);
|
||||||
|
const data = (file.getSync() catch return error.DependencyError).bytes;
|
||||||
|
|
||||||
|
const image: *sdl.Surface = @ptrCast(sdl.IMG_Load_IO(sdl.IOFromConstMem(data.ptr, data.len), true) orelse return error.ParsingError);
|
||||||
|
defer sdl.DestroySurface(image);
|
||||||
|
const format = image.format;
|
||||||
|
|
||||||
|
const width: u32 = @intCast(image.w);
|
||||||
|
const height: u32 = @intCast(image.h);
|
||||||
|
const channels: u32 = 4;
|
||||||
|
|
||||||
|
const image_slice = @as([*]u8, @ptrCast(image.pixels))[0..@intCast(width * height * channels)];
|
||||||
|
|
||||||
|
if (width > 8192 or height > 8192) return error.FileTooBig;
|
||||||
|
|
||||||
|
const target_format = sdl.GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||||
|
const bytes_per_pixel = 4;
|
||||||
|
const mip_level = if (std.math.isPowerOfTwo(width) and width == height) @as(u32, Graphics.MIP_LEVEL) else @as(u32, 1);
|
||||||
|
|
||||||
|
const texture = sdl.CreateGPUTexture(Graphics.device, &.{
|
||||||
|
.width = width,
|
||||||
|
.height = height,
|
||||||
|
.layer_count_or_depth = 1,
|
||||||
|
.format = target_format,
|
||||||
|
.usage = sdl.GPU_TEXTUREUSAGE_SAMPLER | sdl.GPU_TEXTUREUSAGE_COLOR_TARGET,
|
||||||
|
.num_levels = mip_level,
|
||||||
|
}) orelse err.sdl();
|
||||||
|
errdefer Graphics.freeTexture(texture);
|
||||||
|
|
||||||
|
const transfer_buffer_capacity = Graphics.TRANSFER_BUFFER_DEFAULT_CAPACITY;
|
||||||
|
const transfer_buffer = sdl.CreateGPUTransferBuffer(Graphics.device, &.{
|
||||||
|
.size = transfer_buffer_capacity,
|
||||||
|
.usage = sdl.GPU_TRANSFERBUFFERUSAGE_UPLOAD,
|
||||||
|
}) orelse return error.SdlError;
|
||||||
|
defer sdl.ReleaseGPUTransferBuffer(Graphics.device, transfer_buffer);
|
||||||
|
|
||||||
|
var rows_uploaded: u32 = 0;
|
||||||
|
while (rows_uploaded < height) {
|
||||||
|
const rows_to_upload = @min(height - rows_uploaded, transfer_buffer_capacity / width / bytes_per_pixel);
|
||||||
|
if (rows_to_upload == 0) return error.FileTooBig;
|
||||||
|
|
||||||
|
const command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse return error.SdlError;
|
||||||
|
{
|
||||||
|
errdefer _ = sdl.CancelGPUCommandBuffer(command_buffer);
|
||||||
|
const copy_pass = sdl.BeginGPUCopyPass(command_buffer) orelse return error.SdlError;
|
||||||
|
defer sdl.EndGPUCopyPass(copy_pass);
|
||||||
|
|
||||||
|
const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(Graphics.device, transfer_buffer, false) orelse err.sdl());
|
||||||
|
var pixel: u32 = rows_uploaded * width * bytes_per_pixel;
|
||||||
|
var mapped: u32 = 0;
|
||||||
|
while (pixel < (rows_uploaded + rows_to_upload) * width * bytes_per_pixel) {
|
||||||
|
defer pixel += bytes_per_pixel;
|
||||||
|
defer mapped += bytes_per_pixel;
|
||||||
|
switch (format) {
|
||||||
|
// Convert to RGBA8888
|
||||||
|
sdl.PIXELFORMAT_ABGR8888 => {
|
||||||
|
map[mapped + 0] = image_slice[pixel + 3];
|
||||||
|
map[mapped + 1] = image_slice[pixel + 2];
|
||||||
|
map[mapped + 2] = image_slice[pixel + 1];
|
||||||
|
map[mapped + 3] = image_slice[pixel + 0];
|
||||||
|
},
|
||||||
|
sdl.PIXELFORMAT_ARGB8888 => {
|
||||||
|
map[mapped + 0] = image_slice[pixel + 1];
|
||||||
|
map[mapped + 1] = image_slice[pixel + 2];
|
||||||
|
map[mapped + 2] = image_slice[pixel + 3];
|
||||||
|
map[mapped + 3] = image_slice[pixel + 0];
|
||||||
|
},
|
||||||
|
sdl.PIXELFORMAT_RGBA8888 => {
|
||||||
|
map[mapped + 0] = image_slice[pixel + 0];
|
||||||
|
map[mapped + 1] = image_slice[pixel + 1];
|
||||||
|
map[mapped + 2] = image_slice[pixel + 2];
|
||||||
|
map[mapped + 3] = image_slice[pixel + 3];
|
||||||
|
},
|
||||||
|
sdl.PIXELFORMAT_BGRA8888 => {
|
||||||
|
map[mapped + 0] = image_slice[pixel + 2];
|
||||||
|
map[mapped + 1] = image_slice[pixel + 1];
|
||||||
|
map[mapped + 2] = image_slice[pixel + 0];
|
||||||
|
map[mapped + 3] = image_slice[pixel + 3];
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
sdl.UnmapGPUTransferBuffer(Graphics.device, transfer_buffer);
|
||||||
|
return error.UnsupportedAsset;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@memcpy(map, image_slice[(rows_uploaded * width * bytes_per_pixel)..((rows_uploaded + rows_to_upload) * width * bytes_per_pixel)]);
|
||||||
|
sdl.UnmapGPUTransferBuffer(Graphics.device, transfer_buffer);
|
||||||
|
|
||||||
|
sdl.UploadToGPUTexture(copy_pass, &sdl.GPUTextureTransferInfo{
|
||||||
|
.offset = 0,
|
||||||
|
.pixels_per_row = width,
|
||||||
|
.rows_per_layer = rows_to_upload,
|
||||||
|
.transfer_buffer = transfer_buffer,
|
||||||
|
}, &sdl.GPUTextureRegion{
|
||||||
|
.texture = texture,
|
||||||
|
.mip_level = 0,
|
||||||
|
.layer = 0,
|
||||||
|
.x = 0,
|
||||||
|
.y = rows_uploaded,
|
||||||
|
.z = 0,
|
||||||
|
.w = width,
|
||||||
|
.h = rows_to_upload,
|
||||||
|
.d = 1,
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
rows_uploaded += rows_to_upload;
|
||||||
|
if (rows_uploaded == height and mip_level > 1) {
|
||||||
|
sdl.GenerateMipmapsForGPUTexture(command_buffer, texture);
|
||||||
|
}
|
||||||
|
const fence = sdl.SubmitGPUCommandBufferAndAcquireFence(command_buffer) orelse return error.SdlError;
|
||||||
|
defer sdl.ReleaseGPUFence(Graphics.device, fence);
|
||||||
|
if (!sdl.WaitForGPUFences(Graphics.device, true, &fence, 1)) return error.SdlError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sampler = Graphics.createSampler(mip_level);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.texture = texture,
|
||||||
|
.sampler = sampler,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unload(self: @This(), alloc: std.mem.Allocator) void {
|
||||||
|
_ = alloc;
|
||||||
|
Graphics.freeTexture(self.texture);
|
||||||
|
Graphics.freeSampler(self.sampler);
|
||||||
|
}
|
@@ -1,8 +0,0 @@
|
|||||||
pub usingnamespace @cImport({
|
|
||||||
@cDefine("STBI_NO_GIF", "1");
|
|
||||||
@cDefine("STBI_NO_JPEG", "1");
|
|
||||||
@cDefine("STBI_NO_HDR", "1");
|
|
||||||
@cDefine("STBI_NO_TGA", "1");
|
|
||||||
@cDefine("STB_IMAGE_IMPLEMENTATION", "1");
|
|
||||||
@cInclude("stb_image.h");
|
|
||||||
});
|
|
118
src/entity.zig
118
src/entity.zig
@@ -1,118 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const sdl = @import("sdl");
|
|
||||||
const Game = @import("game.zig");
|
|
||||||
const Graphics = @import("graphics.zig");
|
|
||||||
const Time = @import("time.zig");
|
|
||||||
const World = @import("world.zig");
|
|
||||||
const math = @import("math.zig");
|
|
||||||
|
|
||||||
position: @Vector(2, i32),
|
|
||||||
player: bool = false,
|
|
||||||
enemy: bool = false,
|
|
||||||
controller: Controller = .{},
|
|
||||||
next_update: Time = Time.ZERO,
|
|
||||||
|
|
||||||
const Controller = struct {
|
|
||||||
const Action = union(enum) {
|
|
||||||
move: @Vector(2, i32),
|
|
||||||
};
|
|
||||||
wanted_action: ?Action = null,
|
|
||||||
move_units: f32 = 0.125,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
pub fn update(self: *Self) void {
|
|
||||||
if (!World.time.past(self.next_update)) return;
|
|
||||||
|
|
||||||
if (self.player) self.updatePlayer();
|
|
||||||
if (self.enemy) self.updateEnemy();
|
|
||||||
self.updateController();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn updatePlayer(self: *Self) void {
|
|
||||||
var delta: @Vector(2, i32) = .{ 0, 0 };
|
|
||||||
if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_UP)) {
|
|
||||||
delta[1] += 1;
|
|
||||||
}
|
|
||||||
if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_DOWN)) {
|
|
||||||
delta[1] -= 1;
|
|
||||||
}
|
|
||||||
if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_RIGHT)) {
|
|
||||||
delta[0] += 1;
|
|
||||||
}
|
|
||||||
if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_LEFT)) {
|
|
||||||
delta[0] -= 1;
|
|
||||||
}
|
|
||||||
if (@reduce(.Or, delta != @Vector(2, i32){ 0, 0 }))
|
|
||||||
self.controller.wanted_action = .{ .move = delta }
|
|
||||||
else
|
|
||||||
self.controller.wanted_action = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn updateEnemy(self: *Self) void {
|
|
||||||
if (World.getPlayer()) |player| {
|
|
||||||
var delta = player.position - self.position;
|
|
||||||
if (@reduce(.And, @abs(delta) <= @Vector(2, i64){ 1, 1 })) {
|
|
||||||
self.controller.wanted_action = null;
|
|
||||||
} else {
|
|
||||||
delta[0] = @max(-1, @min(1, delta[0]));
|
|
||||||
delta[1] = @max(-1, @min(1, delta[1]));
|
|
||||||
self.controller.wanted_action = .{ .move = delta };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn updateController(self: *Self) void {
|
|
||||||
if (self.controller.wanted_action) |action| {
|
|
||||||
switch (action) {
|
|
||||||
.move => |delta| {
|
|
||||||
const target = self.position + delta;
|
|
||||||
if (World.isFree(target)) {
|
|
||||||
self.next_update = World.time.offset(self.controller.move_units * math.lengthInt(delta));
|
|
||||||
self.position[0] += delta[0];
|
|
||||||
self.position[1] += delta[1];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw(self: *Self, delta: f32) void {
|
|
||||||
const transform = Graphics.Transform{
|
|
||||||
.position = .{
|
|
||||||
@floatFromInt(self.position[0]),
|
|
||||||
@floatFromInt(self.position[1]),
|
|
||||||
0.5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Graphics.drawMesh(World.cube_mesh, World.texture, transform);
|
|
||||||
|
|
||||||
if (!self.player) return;
|
|
||||||
|
|
||||||
Graphics.camera.transform.position = math.lerpTimeLn(
|
|
||||||
Graphics.camera.transform.position,
|
|
||||||
transform.position + @Vector(3, f32){ 0.0, -2.0, 5.0 },
|
|
||||||
delta,
|
|
||||||
-25,
|
|
||||||
);
|
|
||||||
|
|
||||||
const ORIGIN_DIR = @Vector(3, f32){ 0.0, 0.0, -1.0 };
|
|
||||||
const INIT_ROTATION = Graphics.Transform.rotationByAxis(.{ 1.0, 0.0, 0.0 }, std.math.pi * 0.5);
|
|
||||||
|
|
||||||
const ROTATED_DIR = Graphics.Transform.rotateVector(ORIGIN_DIR, INIT_ROTATION);
|
|
||||||
|
|
||||||
const target_rotation = Graphics.Transform.combineRotations(
|
|
||||||
INIT_ROTATION,
|
|
||||||
Graphics.Transform.rotationToward(
|
|
||||||
ROTATED_DIR,
|
|
||||||
transform.position - Graphics.camera.transform.position,
|
|
||||||
.{ .normalize_to = true },
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Graphics.camera.transform.rotation = Graphics.Transform.normalizeRotation(math.slerpTimeLn(
|
|
||||||
Graphics.camera.transform.rotation,
|
|
||||||
target_rotation,
|
|
||||||
delta,
|
|
||||||
-2,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
@@ -14,7 +14,3 @@ const FileError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.Fil
|
|||||||
pub fn file(err: FileError, path: []const u8) noreturn {
|
pub fn file(err: FileError, path: []const u8) noreturn {
|
||||||
std.debug.panic("Error while reading \"{s}\": {any}", .{ path, err });
|
std.debug.panic("Error while reading \"{s}\": {any}", .{ path, err });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stbi() noreturn {
|
|
||||||
std.debug.panic("STBI error!\n", .{});
|
|
||||||
}
|
|
||||||
|
42
src/game.zig
42
src/game.zig
@@ -27,7 +27,16 @@ pub fn init(game_alloc: std.mem.Allocator) void {
|
|||||||
Game.running = false;
|
Game.running = false;
|
||||||
Game.time = Time{ .now = 0, .delta = 0 };
|
Game.time = Time{ .now = 0, .delta = 0 };
|
||||||
Game.keyboard = .{};
|
Game.keyboard = .{};
|
||||||
Game.mouse = .{ .x = 0, .y = 0, .dx = 0, .dy = 0 };
|
Game.mouse = .{
|
||||||
|
.buttons = .{},
|
||||||
|
.x_screen = 0,
|
||||||
|
.y_screen = 0,
|
||||||
|
.x_norm = 0,
|
||||||
|
.y_norm = 0,
|
||||||
|
.dx = 0,
|
||||||
|
.dy = 0,
|
||||||
|
.wheel = 0,
|
||||||
|
};
|
||||||
Graphics.create();
|
Graphics.create();
|
||||||
Assets.init();
|
Assets.init();
|
||||||
World.initDebug();
|
World.initDebug();
|
||||||
@@ -46,11 +55,14 @@ pub fn run() void {
|
|||||||
} else err.sdl();
|
} else err.sdl();
|
||||||
|
|
||||||
Game.processEvents();
|
Game.processEvents();
|
||||||
World.updateReal(Game.time.delta);
|
Game.mouse.x_norm = (Game.mouse.x_screen / @as(f32, @floatFromInt(Graphics.window_width))) * 2 - 1;
|
||||||
|
Game.mouse.y_norm = (Game.mouse.y_screen / @as(f32, @floatFromInt(Graphics.window_height))) * -2 + 1;
|
||||||
|
World.update(Game.time.delta);
|
||||||
if (Game.beginDraw()) {
|
if (Game.beginDraw()) {
|
||||||
World.draw(Game.time.delta);
|
World.draw();
|
||||||
Game.endDraw();
|
Game.endDraw();
|
||||||
}
|
}
|
||||||
|
Assets.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +78,7 @@ fn processEvents() void {
|
|||||||
Game.mouse.dx = 0.0;
|
Game.mouse.dx = 0.0;
|
||||||
Game.mouse.dy = 0.0;
|
Game.mouse.dy = 0.0;
|
||||||
Game.keyboard.keys.reset();
|
Game.keyboard.keys.reset();
|
||||||
|
Game.mouse.reset();
|
||||||
|
|
||||||
sdl.PumpEvents();
|
sdl.PumpEvents();
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -77,14 +90,10 @@ fn processEvents() void {
|
|||||||
sdl.EVENT_QUIT => {
|
sdl.EVENT_QUIT => {
|
||||||
Game.running = false;
|
Game.running = false;
|
||||||
},
|
},
|
||||||
sdl.EVENT_WINDOW_RESIZED => {
|
|
||||||
if (event.window.windowID != Graphics.windowId()) continue;
|
|
||||||
Graphics.resize(@intCast(event.window.data1), @intCast(event.window.data2));
|
|
||||||
},
|
|
||||||
sdl.EVENT_MOUSE_MOTION => {
|
sdl.EVENT_MOUSE_MOTION => {
|
||||||
if (event.motion.windowID != Graphics.windowId()) continue;
|
if (event.motion.windowID != Graphics.windowId()) continue;
|
||||||
Game.mouse.x = event.motion.x;
|
Game.mouse.x_screen = event.motion.x;
|
||||||
Game.mouse.y = event.motion.y;
|
Game.mouse.y_screen = event.motion.y;
|
||||||
Game.mouse.dx += event.motion.xrel;
|
Game.mouse.dx += event.motion.xrel;
|
||||||
Game.mouse.dy += event.motion.yrel;
|
Game.mouse.dy += event.motion.yrel;
|
||||||
},
|
},
|
||||||
@@ -104,6 +113,21 @@ fn processEvents() void {
|
|||||||
if (event.button.windowID != Graphics.windowId()) continue;
|
if (event.button.windowID != Graphics.windowId()) continue;
|
||||||
Game.mouse.buttons.release(event.button.button);
|
Game.mouse.buttons.release(event.button.button);
|
||||||
},
|
},
|
||||||
|
sdl.EVENT_MOUSE_WHEEL => {
|
||||||
|
Game.mouse.wheel += event.wheel.integer_y;
|
||||||
|
},
|
||||||
|
sdl.EVENT_WINDOW_RESIZED => {
|
||||||
|
if (event.window.data1 < 1 or event.window.data2 < 1) continue;
|
||||||
|
|
||||||
|
Graphics.window_width = @intCast(event.window.data1);
|
||||||
|
Graphics.window_height = @intCast(event.window.data2);
|
||||||
|
},
|
||||||
|
sdl.EVENT_WINDOW_PIXEL_SIZE_CHANGED => {
|
||||||
|
if (event.window.data1 < 1 or event.window.data2 < 1) continue;
|
||||||
|
|
||||||
|
Graphics.pixel_width = @intCast(event.window.data1);
|
||||||
|
Graphics.pixel_height = @intCast(event.window.data2);
|
||||||
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
529
src/graphics.zig
529
src/graphics.zig
@@ -2,65 +2,114 @@ const std = @import("std");
|
|||||||
const sdl = @import("sdl");
|
const sdl = @import("sdl");
|
||||||
const err = @import("error.zig");
|
const err = @import("error.zig");
|
||||||
const presets = @import("graphics/presets.zig");
|
const presets = @import("graphics/presets.zig");
|
||||||
|
const Game = @import("game.zig");
|
||||||
const Assets = @import("assets.zig");
|
const Assets = @import("assets.zig");
|
||||||
|
|
||||||
pub const Transform = @import("graphics/transform.zig");
|
pub const Transform = @import("graphics/transform.zig");
|
||||||
pub const Camera = @import("graphics/camera.zig");
|
pub const Camera = @import("graphics/camera.zig");
|
||||||
|
|
||||||
pub const Mesh = struct {
|
pub var window: *sdl.Window = undefined;
|
||||||
vertex_start: usize,
|
pub var device: *sdl.GPUDevice = undefined;
|
||||||
vertex_count: usize,
|
|
||||||
};
|
|
||||||
|
|
||||||
var window: *sdl.Window = undefined;
|
|
||||||
var renderer: *sdl.Renderer = undefined;
|
|
||||||
var device: *sdl.GPUDevice = undefined;
|
|
||||||
/// Only available while drawing
|
/// Only available while drawing
|
||||||
var command_buffer: ?*sdl.GPUCommandBuffer = null;
|
var command_buffer: ?*sdl.GPUCommandBuffer = null;
|
||||||
var render_pass: ?*sdl.GPURenderPass = null;
|
var render_pass: ?*sdl.GPURenderPass = null;
|
||||||
|
var render_target: ?*sdl.GPUTexture = null;
|
||||||
|
var batches: Batches = undefined;
|
||||||
|
|
||||||
var shader_vert: *sdl.GPUShader = undefined;
|
var shader_vert: *sdl.GPUShader = undefined;
|
||||||
var shader_frag: *sdl.GPUShader = undefined;
|
var shader_frag: *sdl.GPUShader = undefined;
|
||||||
|
|
||||||
var vertex_buffer: *sdl.GPUBuffer = undefined;
|
|
||||||
var vertex_buffer_capacity: usize = undefined;
|
|
||||||
var vertex_buffer_used: usize = undefined;
|
|
||||||
|
|
||||||
var transfer_buffer: *sdl.GPUTransferBuffer = undefined;
|
|
||||||
var transfer_buffer_capacity: usize = undefined;
|
|
||||||
|
|
||||||
var depth_texture: *sdl.GPUTexture = undefined;
|
var depth_texture: *sdl.GPUTexture = undefined;
|
||||||
var msaa_resolve: *sdl.GPUTexture = undefined;
|
var antialias: Antialias = undefined;
|
||||||
|
var aa_target: *sdl.GPUTexture = undefined;
|
||||||
var pipeline: *sdl.GPUGraphicsPipeline = undefined;
|
var pipeline: *sdl.GPUGraphicsPipeline = undefined;
|
||||||
|
|
||||||
var window_size: [2]u32 = undefined;
|
pub var window_width: u32 = undefined;
|
||||||
|
pub var window_height: u32 = undefined;
|
||||||
|
pub var pixel_width: u32 = undefined;
|
||||||
|
pub var pixel_height: u32 = undefined;
|
||||||
|
var render_width: u32 = undefined;
|
||||||
|
var render_height: u32 = undefined;
|
||||||
|
|
||||||
pub var camera: Camera = undefined;
|
pub var camera: Camera = undefined;
|
||||||
|
|
||||||
var to_resize: ?[2]u32 = null;
|
|
||||||
|
|
||||||
const VERTEX_BUFFER_DEFAULT_CAPACITY = 1024;
|
|
||||||
const VERTEX_BUFFER_GROWTH_MULTIPLIER = 2;
|
|
||||||
const TRANSFER_BUFFER_DEFAULT_CAPACITY = 4096;
|
|
||||||
const BYTES_PER_VERTEX = 5 * 4;
|
const BYTES_PER_VERTEX = 5 * 4;
|
||||||
|
const DEPTH_FORMAT = sdl.GPU_TEXTUREFORMAT_D32_FLOAT;
|
||||||
|
pub const TRANSFER_BUFFER_DEFAULT_CAPACITY = 512 * 1024;
|
||||||
|
pub const MIP_LEVEL = 4;
|
||||||
|
|
||||||
|
const Antialias = union(enum) {
|
||||||
|
none,
|
||||||
|
msaa: enum(sdl.GPUSampleCount) {
|
||||||
|
@"2" = sdl.GPU_SAMPLECOUNT_2,
|
||||||
|
@"4" = sdl.GPU_SAMPLECOUNT_4,
|
||||||
|
@"8" = sdl.GPU_SAMPLECOUNT_8,
|
||||||
|
},
|
||||||
|
fsaa: enum {
|
||||||
|
@"2",
|
||||||
|
@"4",
|
||||||
|
@"8",
|
||||||
|
},
|
||||||
|
|
||||||
|
pub fn getMsaaSamples(self: @This()) sdl.GPUSampleCount {
|
||||||
|
if (self != .msaa) return sdl.GPU_SAMPLECOUNT_1;
|
||||||
|
return @intFromEnum(self.msaa);
|
||||||
|
}
|
||||||
|
pub fn getFsaaScale(self: @This()) u8 {
|
||||||
|
if (self != .fsaa) return 1;
|
||||||
|
return switch (self.fsaa) {
|
||||||
|
.@"2" => 2,
|
||||||
|
.@"4" => 4,
|
||||||
|
.@"8" => 8,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn getFsaaLevel(self: @This()) u8 {
|
||||||
|
if (self != .fsaa) return 1;
|
||||||
|
return switch (self.fsaa) {
|
||||||
|
.@"2" => 2,
|
||||||
|
.@"4" => 3,
|
||||||
|
.@"8" => 4,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Batch = struct {
|
||||||
|
object: *Assets.Object,
|
||||||
|
transform: Transform,
|
||||||
|
z: f32,
|
||||||
|
|
||||||
|
fn orderLessThan(ctx: void, lhs: Batch, rhs: Batch) bool {
|
||||||
|
_ = ctx;
|
||||||
|
return lhs.z > rhs.z;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const Batches = std.ArrayListUnmanaged(Batch);
|
||||||
|
|
||||||
const Graphics = @This();
|
const Graphics = @This();
|
||||||
pub fn create() void {
|
pub fn create() void {
|
||||||
// Init
|
// Init
|
||||||
if (!sdl.Init(sdl.INIT_VIDEO | sdl.INIT_EVENTS)) err.sdl();
|
if (!sdl.Init(sdl.INIT_VIDEO | sdl.INIT_EVENTS)) err.sdl();
|
||||||
|
if (!sdl.SetHint(sdl.HINT_LOGGING, "*=info")) err.sdl();
|
||||||
|
if (!sdl.SetHint(sdl.HINT_GPU_DRIVER, "vulkan")) err.sdl();
|
||||||
|
|
||||||
// Window and Renderer
|
// Window and Renderer
|
||||||
if (!sdl.CreateWindowAndRenderer(
|
Graphics.window = sdl.CreateWindow(
|
||||||
"",
|
"",
|
||||||
1600,
|
1600,
|
||||||
900,
|
900,
|
||||||
sdl.WINDOW_VULKAN | sdl.WINDOW_RESIZABLE,
|
sdl.WINDOW_VULKAN | sdl.WINDOW_RESIZABLE | sdl.WINDOW_HIGH_PIXEL_DENSITY,
|
||||||
@ptrCast(&Graphics.window),
|
) orelse err.sdl();
|
||||||
@ptrCast(&Graphics.renderer),
|
if (!sdl.GetWindowSizeInPixels(Graphics.window, @ptrCast(&Graphics.window_width), @ptrCast(&Graphics.window_height))) {
|
||||||
)) err.sdl();
|
Graphics.window_width = 1600;
|
||||||
Graphics.window_size = .{ 1600, 900 };
|
Graphics.window_height = 900;
|
||||||
|
}
|
||||||
|
const scale = sdl.GetWindowDisplayScale(Graphics.window);
|
||||||
|
Graphics.pixel_width = @intFromFloat(@round(scale * @as(f32, @floatFromInt(Graphics.window_width))));
|
||||||
|
Graphics.pixel_height = @intFromFloat(@round(scale * @as(f32, @floatFromInt(Graphics.window_height))));
|
||||||
|
Graphics.render_width = pixel_width;
|
||||||
|
Graphics.render_height = pixel_height;
|
||||||
|
|
||||||
if (!sdl.SetRenderVSync(renderer, sdl.RENDERER_VSYNC_ADAPTIVE)) err.sdl();
|
Graphics.antialias = .none;
|
||||||
|
|
||||||
// Device
|
// Device
|
||||||
Graphics.device = sdl.CreateGPUDevice(
|
Graphics.device = sdl.CreateGPUDevice(
|
||||||
@@ -92,29 +141,27 @@ pub fn create() void {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Graphics.vertex_buffer = sdl.CreateGPUBuffer(Graphics.device, &.{
|
|
||||||
.usage = sdl.GPU_BUFFERUSAGE_VERTEX,
|
|
||||||
.size = VERTEX_BUFFER_DEFAULT_CAPACITY,
|
|
||||||
}) orelse err.sdl();
|
|
||||||
Graphics.vertex_buffer_capacity = VERTEX_BUFFER_DEFAULT_CAPACITY;
|
|
||||||
Graphics.vertex_buffer_used = 0;
|
|
||||||
|
|
||||||
Graphics.transfer_buffer = sdl.CreateGPUTransferBuffer(Graphics.device, &.{
|
|
||||||
.size = TRANSFER_BUFFER_DEFAULT_CAPACITY,
|
|
||||||
.usage = sdl.GPU_TRANSFERBUFFERUSAGE_UPLOAD | sdl.GPU_TRANSFERBUFFERUSAGE_DOWNLOAD,
|
|
||||||
}) orelse err.sdl();
|
|
||||||
Graphics.transfer_buffer_capacity = TRANSFER_BUFFER_DEFAULT_CAPACITY;
|
|
||||||
|
|
||||||
const target_format = sdl.GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window);
|
const target_format = sdl.GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window);
|
||||||
if (target_format == sdl.GPU_TEXTUREFORMAT_INVALID) err.sdl();
|
if (target_format == sdl.GPU_TEXTUREFORMAT_INVALID) err.sdl();
|
||||||
|
|
||||||
// TODO: Clean
|
Graphics.depth_texture = sdl.CreateGPUTexture(device, &.{
|
||||||
var window_width: c_int = 1;
|
.width = Graphics.render_width * Graphics.antialias.getFsaaScale(),
|
||||||
var window_height: c_int = 1;
|
.height = Graphics.render_height * Graphics.antialias.getFsaaScale(),
|
||||||
if (!sdl.GetWindowSizeInPixels(Graphics.window, &window_width, &window_height)) err.sdl();
|
.layer_count_or_depth = 1,
|
||||||
|
.format = DEPTH_FORMAT,
|
||||||
Graphics.depth_texture = createDepthTexture(@intCast(window_width), @intCast(window_height));
|
.usage = sdl.GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET,
|
||||||
Graphics.msaa_resolve = createTexture(@intCast(window_width), @intCast(window_height), target_format);
|
.num_levels = 1,
|
||||||
|
.sample_count = Graphics.antialias.getMsaaSamples(),
|
||||||
|
}) orelse err.sdl();
|
||||||
|
Graphics.aa_target = sdl.CreateGPUTexture(device, &.{
|
||||||
|
.width = Graphics.render_width * Graphics.antialias.getFsaaScale(),
|
||||||
|
.height = Graphics.render_height * Graphics.antialias.getFsaaScale(),
|
||||||
|
.layer_count_or_depth = 1,
|
||||||
|
.format = target_format,
|
||||||
|
.usage = if (Graphics.antialias == .fsaa) sdl.GPU_TEXTUREUSAGE_COLOR_TARGET | sdl.GPU_TEXTUREUSAGE_SAMPLER else sdl.GPU_TEXTUREUSAGE_COLOR_TARGET,
|
||||||
|
.num_levels = Graphics.antialias.getFsaaLevel(),
|
||||||
|
.sample_count = Graphics.antialias.getMsaaSamples(),
|
||||||
|
}) orelse err.sdl();
|
||||||
|
|
||||||
Graphics.pipeline = sdl.CreateGPUGraphicsPipeline(Graphics.device, &.{
|
Graphics.pipeline = sdl.CreateGPUGraphicsPipeline(Graphics.device, &.{
|
||||||
.vertex_shader = Graphics.shader_vert,
|
.vertex_shader = Graphics.shader_vert,
|
||||||
@@ -142,14 +189,14 @@ pub fn create() void {
|
|||||||
},
|
},
|
||||||
.num_vertex_attributes = 2,
|
.num_vertex_attributes = 2,
|
||||||
},
|
},
|
||||||
|
.multisample_state = .{
|
||||||
|
.sample_count = Graphics.antialias.getMsaaSamples(),
|
||||||
|
},
|
||||||
.primitive_type = sdl.GPU_PRIMITIVETYPE_TRIANGLELIST,
|
.primitive_type = sdl.GPU_PRIMITIVETYPE_TRIANGLELIST,
|
||||||
.rasterizer_state = presets.RASTERIZER_CULL,
|
.rasterizer_state = presets.RASTERIZER_CULL,
|
||||||
.multisample_state = .{
|
|
||||||
.sample_count = sdl.GPU_SAMPLECOUNT_4,
|
|
||||||
},
|
|
||||||
.depth_stencil_state = presets.DEPTH_ENABLED,
|
.depth_stencil_state = presets.DEPTH_ENABLED,
|
||||||
.target_info = .{
|
.target_info = .{
|
||||||
.depth_stencil_format = sdl.GPU_TEXTUREFORMAT_D16_UNORM,
|
.depth_stencil_format = DEPTH_FORMAT,
|
||||||
.color_target_descriptions = &sdl.GPUColorTargetDescription{
|
.color_target_descriptions = &sdl.GPUColorTargetDescription{
|
||||||
.format = target_format,
|
.format = target_format,
|
||||||
.blend_state = presets.BLEND_NORMAL,
|
.blend_state = presets.BLEND_NORMAL,
|
||||||
@@ -159,25 +206,24 @@ pub fn create() void {
|
|||||||
},
|
},
|
||||||
}) orelse err.sdl();
|
}) orelse err.sdl();
|
||||||
|
|
||||||
|
Graphics.batches = Batches.empty;
|
||||||
|
|
||||||
Graphics.camera = Camera{
|
Graphics.camera = Camera{
|
||||||
.transform = .{},
|
.transform = .{},
|
||||||
.near = 1.0,
|
.near = 1.0 / 16.0,
|
||||||
.far = 1024.0,
|
|
||||||
.lens = 1.5,
|
.lens = 1.5,
|
||||||
.aspect = 16.0 / 9.0,
|
.aspect = 16.0 / 9.0,
|
||||||
|
.matrix = undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy() void {
|
pub fn destroy() void {
|
||||||
sdl.ReleaseWindowFromGPUDevice(Graphics.device, Graphics.window);
|
sdl.ReleaseWindowFromGPUDevice(Graphics.device, Graphics.window);
|
||||||
sdl.DestroyRenderer(Graphics.renderer);
|
|
||||||
sdl.DestroyWindow(Graphics.window);
|
sdl.DestroyWindow(Graphics.window);
|
||||||
|
|
||||||
sdl.ReleaseGPUGraphicsPipeline(Graphics.device, Graphics.pipeline);
|
sdl.ReleaseGPUGraphicsPipeline(Graphics.device, Graphics.pipeline);
|
||||||
sdl.ReleaseGPUTexture(Graphics.device, Graphics.msaa_resolve);
|
sdl.ReleaseGPUTexture(Graphics.device, Graphics.aa_target);
|
||||||
sdl.ReleaseGPUTexture(Graphics.device, Graphics.depth_texture);
|
sdl.ReleaseGPUTexture(Graphics.device, Graphics.depth_texture);
|
||||||
sdl.ReleaseGPUBuffer(Graphics.device, Graphics.vertex_buffer);
|
|
||||||
sdl.ReleaseGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer);
|
|
||||||
|
|
||||||
sdl.ReleaseGPUShader(Graphics.device, Graphics.shader_vert);
|
sdl.ReleaseGPUShader(Graphics.device, Graphics.shader_vert);
|
||||||
sdl.ReleaseGPUShader(Graphics.device, Graphics.shader_frag);
|
sdl.ReleaseGPUShader(Graphics.device, Graphics.shader_frag);
|
||||||
@@ -187,190 +233,37 @@ pub fn destroy() void {
|
|||||||
Graphics.command_buffer = null;
|
Graphics.command_buffer = null;
|
||||||
}
|
}
|
||||||
sdl.DestroyGPUDevice(Graphics.device);
|
sdl.DestroyGPUDevice(Graphics.device);
|
||||||
}
|
Graphics.batches.clearAndFree(Game.alloc);
|
||||||
|
|
||||||
pub fn loadTexture(width: u32, height: u32, texture_bytes: []const u8) struct { *sdl.GPUTexture, *sdl.GPUSampler } {
|
|
||||||
// const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window);
|
|
||||||
const target_format = sdl.GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
|
||||||
|
|
||||||
const texture = sdl.CreateGPUTexture(Graphics.device, &sdl.GPUTextureCreateInfo{
|
|
||||||
.format = target_format,
|
|
||||||
.layer_count_or_depth = 1,
|
|
||||||
.width = width,
|
|
||||||
.height = height,
|
|
||||||
.num_levels = 1,
|
|
||||||
.sample_count = sdl.GPU_SAMPLECOUNT_1,
|
|
||||||
.usage = sdl.GPU_TEXTUREUSAGE_SAMPLER,
|
|
||||||
}) orelse err.sdl();
|
|
||||||
|
|
||||||
const temp_command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl();
|
|
||||||
{
|
|
||||||
const copy_pass = sdl.BeginGPUCopyPass(temp_command_buffer) orelse err.sdl();
|
|
||||||
defer sdl.EndGPUCopyPass(copy_pass);
|
|
||||||
|
|
||||||
const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer, false) orelse err.sdl());
|
|
||||||
@memcpy(map, texture_bytes);
|
|
||||||
sdl.UnmapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer);
|
|
||||||
|
|
||||||
sdl.UploadToGPUTexture(copy_pass, &sdl.GPUTextureTransferInfo{
|
|
||||||
.offset = 0,
|
|
||||||
.pixels_per_row = width,
|
|
||||||
.rows_per_layer = height,
|
|
||||||
.transfer_buffer = Graphics.transfer_buffer,
|
|
||||||
}, &sdl.GPUTextureRegion{
|
|
||||||
.texture = texture,
|
|
||||||
.mip_level = 0,
|
|
||||||
.layer = 0,
|
|
||||||
.x = 0,
|
|
||||||
.y = 0,
|
|
||||||
.z = 0,
|
|
||||||
.w = width,
|
|
||||||
.h = height,
|
|
||||||
.d = 1,
|
|
||||||
}, false);
|
|
||||||
}
|
|
||||||
if (!sdl.SubmitGPUCommandBuffer(temp_command_buffer)) err.sdl();
|
|
||||||
|
|
||||||
const sampler = sdl.CreateGPUSampler(Graphics.device, &sdl.GPUSamplerCreateInfo{
|
|
||||||
.address_mode_u = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
|
|
||||||
.address_mode_v = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
|
|
||||||
.address_mode_w = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
|
|
||||||
.mag_filter = sdl.GPU_FILTER_NEAREST,
|
|
||||||
.min_filter = sdl.GPU_FILTER_LINEAR,
|
|
||||||
}) orelse err.sdl();
|
|
||||||
|
|
||||||
return .{
|
|
||||||
texture,
|
|
||||||
sampler,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unloadTexture(texture: *sdl.GPUTexture, sampler: *sdl.GPUSampler) void {
|
|
||||||
sdl.ReleaseGPUSampler(Graphics.device, sampler);
|
|
||||||
sdl.ReleaseGPUTexture(Graphics.device, texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn loadMesh(mesh_bytes: []const u8) Mesh {
|
|
||||||
std.debug.assert(mesh_bytes.len < Graphics.transfer_buffer_capacity);
|
|
||||||
|
|
||||||
var size_mult: usize = 1;
|
|
||||||
while (Graphics.vertex_buffer_used + mesh_bytes.len > Graphics.vertex_buffer_capacity * size_mult) {
|
|
||||||
size_mult *= VERTEX_BUFFER_GROWTH_MULTIPLIER;
|
|
||||||
}
|
|
||||||
if (size_mult > 1) {
|
|
||||||
Graphics.growVertexBuffer(Graphics.vertex_buffer_capacity * size_mult);
|
|
||||||
}
|
|
||||||
|
|
||||||
const map = sdl.MapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer, false) orelse err.sdl();
|
|
||||||
@memcpy(@as([*]u8, @ptrCast(map)), mesh_bytes);
|
|
||||||
sdl.UnmapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer);
|
|
||||||
|
|
||||||
const temp_command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl();
|
|
||||||
const fence = blk: {
|
|
||||||
const copy_pass = sdl.BeginGPUCopyPass(temp_command_buffer) orelse err.sdl();
|
|
||||||
sdl.UploadToGPUBuffer(copy_pass, &.{
|
|
||||||
.transfer_buffer = Graphics.transfer_buffer,
|
|
||||||
.offset = 0,
|
|
||||||
}, &.{
|
|
||||||
.buffer = Graphics.vertex_buffer,
|
|
||||||
.offset = @intCast(Graphics.vertex_buffer_used),
|
|
||||||
.size = @intCast(mesh_bytes.len),
|
|
||||||
}, false);
|
|
||||||
sdl.EndGPUCopyPass(copy_pass);
|
|
||||||
|
|
||||||
break :blk sdl.SubmitGPUCommandBufferAndAcquireFence(temp_command_buffer) orelse err.sdl();
|
|
||||||
};
|
|
||||||
defer sdl.ReleaseGPUFence(Graphics.device, fence);
|
|
||||||
|
|
||||||
if (!sdl.WaitForGPUFences(Graphics.device, true, &fence, 1)) err.sdl();
|
|
||||||
|
|
||||||
const vertex_start = Graphics.vertex_buffer_used;
|
|
||||||
Graphics.vertex_buffer_used += mesh_bytes.len;
|
|
||||||
|
|
||||||
return Mesh{
|
|
||||||
.vertex_start = vertex_start / BYTES_PER_VERTEX,
|
|
||||||
.vertex_count = mesh_bytes.len / BYTES_PER_VERTEX,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unloadMesh(mesh: Mesh) void {
|
|
||||||
// TODO: free some memory
|
|
||||||
_ = &mesh;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn growVertexBuffer(new_size: usize) void {
|
|
||||||
const new_buffer = sdl.CreateGPUBuffer(Graphics.device, &.{
|
|
||||||
.size = @intCast(new_size),
|
|
||||||
.usage = sdl.GPU_BUFFERUSAGE_VERTEX,
|
|
||||||
}) orelse err.sdl();
|
|
||||||
|
|
||||||
const temp_command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl();
|
|
||||||
|
|
||||||
const fence = blk: {
|
|
||||||
const copy_pass = sdl.BeginGPUCopyPass(temp_command_buffer);
|
|
||||||
var copied: usize = 0;
|
|
||||||
while (copied < Graphics.vertex_buffer_used) {
|
|
||||||
const to_transer = @min(Graphics.vertex_buffer_used - copied, Graphics.transfer_buffer_capacity);
|
|
||||||
sdl.DownloadFromGPUBuffer(copy_pass, &.{
|
|
||||||
.buffer = Graphics.vertex_buffer,
|
|
||||||
.offset = @intCast(copied),
|
|
||||||
.size = @intCast(to_transer),
|
|
||||||
}, &.{
|
|
||||||
.transfer_buffer = Graphics.transfer_buffer,
|
|
||||||
.offset = 0,
|
|
||||||
});
|
|
||||||
sdl.UploadToGPUBuffer(copy_pass, &.{
|
|
||||||
.transfer_buffer = Graphics.transfer_buffer,
|
|
||||||
.offset = 0,
|
|
||||||
}, &.{
|
|
||||||
.buffer = new_buffer,
|
|
||||||
.offset = @intCast(copied),
|
|
||||||
.size = @intCast(to_transer),
|
|
||||||
}, false);
|
|
||||||
copied += to_transer;
|
|
||||||
}
|
|
||||||
sdl.EndGPUCopyPass(copy_pass);
|
|
||||||
|
|
||||||
break :blk sdl.SubmitGPUCommandBufferAndAcquireFence(temp_command_buffer) orelse err.sdl();
|
|
||||||
};
|
|
||||||
defer sdl.ReleaseGPUFence(Graphics.device, fence);
|
|
||||||
|
|
||||||
if (!sdl.WaitForGPUFences(Graphics.device, true, &fence, 1)) err.sdl();
|
|
||||||
|
|
||||||
sdl.ReleaseGPUBuffer(Graphics.device, Graphics.vertex_buffer);
|
|
||||||
Graphics.vertex_buffer = new_buffer;
|
|
||||||
Graphics.vertex_buffer_capacity = new_size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If window is minimized returns `false`, `render_pass` remains null
|
/// If window is minimized returns `false`, `render_pass` remains null
|
||||||
/// Otherwise `command_buffer` and `render_pass` are both set
|
/// Otherwise `command_buffer` and `render_pass` are both set
|
||||||
pub fn beginDraw() bool {
|
pub fn beginDraw() bool {
|
||||||
Graphics.command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl();
|
Graphics.command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl();
|
||||||
if (Graphics.to_resize) |new_size| {
|
|
||||||
Graphics.resetTextures(new_size[0], new_size[1]);
|
|
||||||
Graphics.camera.aspect = @as(f32, @floatFromInt(new_size[0])) / @as(f32, @floatFromInt(new_size[1]));
|
|
||||||
Graphics.window_size = new_size;
|
|
||||||
Graphics.to_resize = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var render_target: ?*sdl.GPUTexture = null;
|
|
||||||
var width: u32 = 0;
|
var width: u32 = 0;
|
||||||
var height: u32 = 0;
|
var height: u32 = 0;
|
||||||
if (!sdl.WaitAndAcquireGPUSwapchainTexture(Graphics.command_buffer, Graphics.window, &render_target, &width, &height)) err.sdl();
|
if (!sdl.WaitAndAcquireGPUSwapchainTexture(Graphics.command_buffer, Graphics.window, &Graphics.render_target, &width, &height)) err.sdl();
|
||||||
// Hidden
|
// Window is probably hidden
|
||||||
if (render_target == null) return false;
|
if (Graphics.render_target == null or width == 0 or height == 0) return false;
|
||||||
|
|
||||||
Graphics.render_pass = sdl.BeginGPURenderPass(Graphics.command_buffer, &.{
|
if (Graphics.render_width != Graphics.pixel_width or Graphics.render_height != Graphics.pixel_height) {
|
||||||
|
Graphics.render_width = Graphics.pixel_width;
|
||||||
|
Graphics.render_height = Graphics.pixel_height;
|
||||||
|
Graphics.resetTextures(Graphics.render_width, Graphics.render_height);
|
||||||
|
Graphics.camera.aspect = @as(f32, @floatFromInt(Graphics.render_width)) / @as(f32, @floatFromInt(Graphics.render_height));
|
||||||
|
}
|
||||||
|
|
||||||
|
Graphics.render_pass = sdl.BeginGPURenderPass(Graphics.command_buffer.?, &.{
|
||||||
.clear_color = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 },
|
.clear_color = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 },
|
||||||
.cycle = false,
|
.cycle = false,
|
||||||
.load_op = sdl.GPU_LOADOP_CLEAR,
|
.load_op = sdl.GPU_LOADOP_DONT_CARE,
|
||||||
.store_op = sdl.GPU_STOREOP_RESOLVE,
|
.store_op = if (Graphics.antialias == .msaa) sdl.GPU_STOREOP_RESOLVE else sdl.GPU_STOREOP_STORE,
|
||||||
// .store_op = sdl.GPU_STOREOP_STORE,
|
|
||||||
.resolve_texture = render_target,
|
|
||||||
.mip_level = 0,
|
.mip_level = 0,
|
||||||
.texture = Graphics.msaa_resolve,
|
.texture = if (Graphics.antialias == .fsaa or Graphics.antialias == .msaa) Graphics.aa_target else Graphics.render_target,
|
||||||
|
.resolve_texture = if (Graphics.antialias == .msaa) Graphics.render_target else null,
|
||||||
}, 1, &.{
|
}, 1, &.{
|
||||||
.clear_depth = 1.0,
|
.clear_depth = 0.0,
|
||||||
.load_op = sdl.GPU_LOADOP_CLEAR,
|
.load_op = sdl.GPU_LOADOP_CLEAR,
|
||||||
.store_op = sdl.GPU_STOREOP_DONT_CARE,
|
.store_op = sdl.GPU_STOREOP_DONT_CARE,
|
||||||
.stencil_load_op = sdl.GPU_STOREOP_DONT_CARE,
|
.stencil_load_op = sdl.GPU_STOREOP_DONT_CARE,
|
||||||
@@ -379,29 +272,108 @@ pub fn beginDraw() bool {
|
|||||||
}) orelse err.sdl();
|
}) orelse err.sdl();
|
||||||
|
|
||||||
sdl.BindGPUGraphicsPipeline(Graphics.render_pass, Graphics.pipeline);
|
sdl.BindGPUGraphicsPipeline(Graphics.render_pass, Graphics.pipeline);
|
||||||
sdl.BindGPUVertexBuffers(Graphics.render_pass, 0, &.{ .offset = 0, .buffer = Graphics.vertex_buffer }, 1);
|
Graphics.camera.computeMatrix();
|
||||||
sdl.PushGPUVertexUniformData(Graphics.command_buffer, 0, &Graphics.camera.matrix(), 16 * 4);
|
sdl.PushGPUVertexUniformData(Graphics.command_buffer, 0, &Graphics.camera.matrix, 16 * 4);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drawMesh(mesh: Mesh, texture: Assets.Texture, transform: Transform) void {
|
fn finishPass() void {
|
||||||
if (Graphics.render_pass == null) return;
|
std.sort.block(Batch, Graphics.batches.items, {}, Batch.orderLessThan);
|
||||||
const asset_texture = Assets.get(texture) orelse return;
|
|
||||||
|
|
||||||
sdl.PushGPUVertexUniformData(Graphics.command_buffer, 1, &transform.matrix(), 16 * 4);
|
for (Graphics.batches.items) |*batch| {
|
||||||
|
const asset_object = batch.object.get() orelse continue;
|
||||||
|
|
||||||
|
sdl.PushGPUVertexUniformData(Graphics.command_buffer, 1, &batch.transform.matrix(), 16 * 4);
|
||||||
|
for (asset_object.nodes) |node| {
|
||||||
|
const mesh = &asset_object.meshes[node.mesh];
|
||||||
|
|
||||||
|
for (mesh.primitives) |*primitive| {
|
||||||
|
const asset_texture = primitive.texture.get() orelse continue;
|
||||||
sdl.BindGPUFragmentSamplers(Graphics.render_pass, 0, &sdl.GPUTextureSamplerBinding{
|
sdl.BindGPUFragmentSamplers(Graphics.render_pass, 0, &sdl.GPUTextureSamplerBinding{
|
||||||
.texture = asset_texture.texture,
|
.texture = asset_texture.texture,
|
||||||
.sampler = asset_texture.sampler,
|
.sampler = asset_texture.sampler,
|
||||||
}, 1);
|
}, 1);
|
||||||
sdl.DrawGPUPrimitives(Graphics.render_pass, @intCast(mesh.vertex_count), 1, @intCast(mesh.vertex_start), 0);
|
sdl.BindGPUVertexBuffers(Graphics.render_pass, 0, &.{ .offset = 0, .buffer = primitive.vertex_buffer }, 1);
|
||||||
|
sdl.BindGPUIndexBuffer(Graphics.render_pass, &.{ .buffer = primitive.index_buffer }, sdl.GPU_INDEXELEMENTSIZE_16BIT);
|
||||||
|
sdl.DrawGPUIndexedPrimitives(Graphics.render_pass, primitive.indices, 1, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Graphics.batches.clearRetainingCapacity();
|
||||||
|
|
||||||
|
sdl.EndGPURenderPass(Graphics.render_pass.?);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clearDepth() void {
|
||||||
|
Graphics.finishPass();
|
||||||
|
|
||||||
|
Graphics.render_pass = sdl.BeginGPURenderPass(Graphics.command_buffer.?, &.{
|
||||||
|
.clear_color = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 },
|
||||||
|
.cycle = false,
|
||||||
|
.load_op = sdl.GPU_LOADOP_LOAD,
|
||||||
|
.store_op = if (Graphics.antialias == .msaa) sdl.GPU_STOREOP_RESOLVE else sdl.GPU_STOREOP_STORE,
|
||||||
|
.mip_level = 0,
|
||||||
|
.texture = if (Graphics.antialias == .fsaa or Graphics.antialias == .msaa) Graphics.aa_target else Graphics.render_target,
|
||||||
|
.resolve_texture = if (Graphics.antialias == .msaa) Graphics.render_target else null,
|
||||||
|
}, 1, &.{
|
||||||
|
.clear_depth = 0.0,
|
||||||
|
.load_op = sdl.GPU_LOADOP_CLEAR,
|
||||||
|
.store_op = sdl.GPU_STOREOP_DONT_CARE,
|
||||||
|
.stencil_load_op = sdl.GPU_STOREOP_DONT_CARE,
|
||||||
|
.stencil_store_op = sdl.GPU_STOREOP_DONT_CARE,
|
||||||
|
.texture = Graphics.depth_texture,
|
||||||
|
}) orelse err.sdl();
|
||||||
|
|
||||||
|
sdl.BindGPUGraphicsPipeline(Graphics.render_pass, Graphics.pipeline);
|
||||||
|
sdl.PushGPUVertexUniformData(Graphics.command_buffer, 0, &Graphics.camera.matrix, 16 * 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// `object`: pointer MUST be vaild until current render pass ends
|
||||||
|
pub fn drawObject(object: *Assets.Object, transform: Transform) void {
|
||||||
|
if (Graphics.render_pass == null) return;
|
||||||
|
|
||||||
|
@setFloatMode(.optimized);
|
||||||
|
const z = Graphics.camera.matrix[8] * transform.position[0] +
|
||||||
|
Graphics.camera.matrix[9] * transform.position[1] +
|
||||||
|
Graphics.camera.matrix[10] * transform.position[2] +
|
||||||
|
Graphics.camera.matrix[11];
|
||||||
|
var w = Graphics.camera.matrix[12] * transform.position[0] +
|
||||||
|
Graphics.camera.matrix[13] * transform.position[1] +
|
||||||
|
Graphics.camera.matrix[14] * transform.position[2] +
|
||||||
|
Graphics.camera.matrix[15];
|
||||||
|
if (w == 0) w = 1;
|
||||||
|
|
||||||
|
Graphics.batches.append(Game.alloc, .{
|
||||||
|
.object = object,
|
||||||
|
.transform = transform,
|
||||||
|
.z = z / w,
|
||||||
|
}) catch err.oom();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn endDraw() void {
|
pub fn endDraw() void {
|
||||||
defer Graphics.command_buffer = null;
|
defer Graphics.command_buffer = null;
|
||||||
defer Graphics.render_pass = null;
|
defer Graphics.render_pass = null;
|
||||||
if (Graphics.render_pass) |pass| {
|
|
||||||
sdl.EndGPURenderPass(pass);
|
Graphics.finishPass();
|
||||||
|
|
||||||
|
if (Graphics.antialias == .fsaa) {
|
||||||
|
sdl.GenerateMipmapsForGPUTexture(Graphics.command_buffer, Graphics.aa_target);
|
||||||
|
sdl.BlitGPUTexture(Graphics.command_buffer, &.{
|
||||||
|
.source = .{
|
||||||
|
.texture = Graphics.aa_target,
|
||||||
|
.w = Graphics.render_width,
|
||||||
|
.h = Graphics.render_height,
|
||||||
|
.mip_level = Graphics.antialias.getFsaaLevel() - 1,
|
||||||
|
},
|
||||||
|
.destination = .{
|
||||||
|
.texture = Graphics.render_target,
|
||||||
|
.w = Graphics.render_width,
|
||||||
|
.h = Graphics.render_height,
|
||||||
|
},
|
||||||
|
.load_op = sdl.GPU_LOADOP_DONT_CARE,
|
||||||
|
.filter = sdl.GPU_FILTER_NEAREST,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (!sdl.SubmitGPUCommandBuffer(Graphics.command_buffer)) err.sdl();
|
if (!sdl.SubmitGPUCommandBuffer(Graphics.command_buffer)) err.sdl();
|
||||||
}
|
}
|
||||||
@@ -419,44 +391,71 @@ fn loadShader(path: []const u8, info: sdl.GPUShaderCreateInfo) *sdl.GPUShader {
|
|||||||
return sdl.CreateGPUShader(device, &updated_info) orelse err.sdl();
|
return sdl.CreateGPUShader(device, &updated_info) orelse err.sdl();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn createDepthTexture(width: u32, height: u32) *sdl.GPUTexture {
|
pub fn freeTexture(texture: *sdl.GPUTexture) void {
|
||||||
return sdl.CreateGPUTexture(device, &.{
|
sdl.ReleaseGPUTexture(Graphics.device, texture);
|
||||||
.format = sdl.GPU_TEXTUREFORMAT_D16_UNORM,
|
}
|
||||||
.layer_count_or_depth = 1,
|
|
||||||
.width = width,
|
pub fn createSampler(mip_level: u32) *sdl.GPUSampler {
|
||||||
.height = height,
|
return sdl.CreateGPUSampler(Graphics.device, &sdl.GPUSamplerCreateInfo{
|
||||||
.num_levels = 1,
|
.address_mode_u = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
|
||||||
.sample_count = sdl.GPU_SAMPLECOUNT_4,
|
.address_mode_v = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
|
||||||
.usage = sdl.GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET,
|
.address_mode_w = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
|
||||||
|
.mag_filter = sdl.GPU_FILTER_NEAREST,
|
||||||
|
.min_filter = sdl.GPU_FILTER_LINEAR,
|
||||||
|
.mipmap_mode = sdl.GPU_SAMPLERMIPMAPMODE_LINEAR,
|
||||||
|
.min_lod = 0,
|
||||||
|
.max_lod = @floatFromInt(mip_level - 1),
|
||||||
|
.mip_lod_bias = -0.5,
|
||||||
}) orelse err.sdl();
|
}) orelse err.sdl();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn createTexture(width: u32, height: u32, format: c_uint) *sdl.GPUTexture {
|
pub fn freeSampler(sampler: *sdl.GPUSampler) void {
|
||||||
return sdl.CreateGPUTexture(device, &.{
|
sdl.ReleaseGPUSampler(Graphics.device, sampler);
|
||||||
.format = format,
|
|
||||||
.layer_count_or_depth = 1,
|
|
||||||
.width = width,
|
|
||||||
.height = height,
|
|
||||||
.num_levels = 1,
|
|
||||||
.sample_count = sdl.GPU_SAMPLECOUNT_4,
|
|
||||||
.usage = sdl.GPU_TEXTUREUSAGE_COLOR_TARGET,
|
|
||||||
}) orelse err.sdl();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resetTextures(width: u32, height: u32) void {
|
fn resetTextures(width: u32, height: u32) void {
|
||||||
sdl.ReleaseGPUTexture(Graphics.device, Graphics.depth_texture);
|
sdl.ReleaseGPUTexture(Graphics.device, Graphics.depth_texture);
|
||||||
Graphics.depth_texture = createDepthTexture(width, height);
|
Graphics.depth_texture = sdl.CreateGPUTexture(device, &.{
|
||||||
|
.width = width * Graphics.antialias.getFsaaScale(),
|
||||||
|
.height = height * Graphics.antialias.getFsaaScale(),
|
||||||
|
.layer_count_or_depth = 1,
|
||||||
|
.format = DEPTH_FORMAT,
|
||||||
|
.usage = sdl.GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET,
|
||||||
|
.num_levels = 1,
|
||||||
|
.sample_count = Graphics.antialias.getMsaaSamples(),
|
||||||
|
}) orelse err.sdl();
|
||||||
|
|
||||||
const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window);
|
const target_format = sdl.GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window);
|
||||||
|
|
||||||
sdl.ReleaseGPUTexture(Graphics.device, Graphics.msaa_resolve);
|
sdl.ReleaseGPUTexture(Graphics.device, Graphics.aa_target);
|
||||||
Graphics.msaa_resolve = createTexture(width, height, target_format);
|
Graphics.aa_target = sdl.CreateGPUTexture(device, &.{
|
||||||
}
|
.width = width * Graphics.antialias.getFsaaScale(),
|
||||||
|
.height = height * Graphics.antialias.getFsaaScale(),
|
||||||
pub fn resize(width: u32, height: u32) void {
|
.layer_count_or_depth = 1,
|
||||||
Graphics.to_resize = .{ width, height };
|
.format = target_format,
|
||||||
|
.usage = switch (Graphics.antialias) {
|
||||||
|
.none => sdl.GPU_TEXTUREUSAGE_COLOR_TARGET,
|
||||||
|
.fsaa => sdl.GPU_TEXTUREUSAGE_COLOR_TARGET | sdl.GPU_TEXTUREUSAGE_SAMPLER,
|
||||||
|
.msaa => sdl.GPU_TEXTUREUSAGE_COLOR_TARGET,
|
||||||
|
},
|
||||||
|
.num_levels = Graphics.antialias.getFsaaLevel(),
|
||||||
|
.sample_count = Graphics.antialias.getMsaaSamples(),
|
||||||
|
}) orelse err.sdl();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn windowId() sdl.WindowID {
|
pub fn windowId() sdl.WindowID {
|
||||||
return sdl.GetWindowID(Graphics.window);
|
return sdl.GetWindowID(Graphics.window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn generatePlane(x0: f32, y0: f32, x1: f32, y1: f32, w: f32, h: f32) [30]f32 {
|
||||||
|
const hw = w * 0.5;
|
||||||
|
const hh = h * 0.5;
|
||||||
|
return .{
|
||||||
|
-hw, -hh, 0, x0, y1,
|
||||||
|
hw, hh, 0, x1, y0,
|
||||||
|
-hw, hh, 0, x0, y0,
|
||||||
|
hw, hh, 0, x1, y0,
|
||||||
|
-hw, -hh, 0, x0, y1,
|
||||||
|
hw, -hh, 0, x1, y1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const sdl = @import("sdl");
|
const sdl = @import("sdl");
|
||||||
|
const math = @import("../math.zig");
|
||||||
const Transform = @import("transform.zig");
|
const Transform = @import("transform.zig");
|
||||||
const Camera = @This();
|
const Camera = @This();
|
||||||
|
|
||||||
@@ -7,21 +8,97 @@ transform: Transform,
|
|||||||
/// tangent of the half of the view angle (90 degress = 1 "lens")
|
/// tangent of the half of the view angle (90 degress = 1 "lens")
|
||||||
lens: f32,
|
lens: f32,
|
||||||
near: f32,
|
near: f32,
|
||||||
far: f32,
|
|
||||||
/// width = height * aspect
|
/// width = height * aspect
|
||||||
aspect: f32,
|
aspect: f32,
|
||||||
|
|
||||||
pub fn matrix(camera: Camera) @Vector(16, f32) {
|
matrix: Transform.TMatrix,
|
||||||
|
|
||||||
|
pub fn computeMatrix(camera: *Camera) void {
|
||||||
|
@setFloatMode(.optimized);
|
||||||
|
|
||||||
const xx = 1.0 / (camera.lens * camera.aspect);
|
const xx = 1.0 / (camera.lens * camera.aspect);
|
||||||
const yy = 1.0 / camera.lens;
|
const yy = 1.0 / camera.lens;
|
||||||
const fnmod = 1.0 / (camera.far - camera.near);
|
|
||||||
const zz = camera.far * fnmod;
|
|
||||||
const wz = -camera.near * camera.far * fnmod;
|
|
||||||
const projection = @Vector(16, f32){
|
const projection = @Vector(16, f32){
|
||||||
xx, 0, 0, 0,
|
xx, 0, 0, 0,
|
||||||
0, yy, 0, 0,
|
0, yy, 0, 0,
|
||||||
0, 0, -zz, wz,
|
0, 0, 0, camera.near,
|
||||||
0, 0, -1, 0,
|
0, 0, -1, 0,
|
||||||
};
|
};
|
||||||
return Transform.multiplyMatrix(projection, camera.transform.inverseMatrix());
|
camera.matrix = Transform.multiplyMatrix(projection, camera.transform.inverseMatrix());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_screen(camera: Camera, position: Transform.Position) @Vector(2, f32) {
|
||||||
|
@setFloatMode(.optimized);
|
||||||
|
|
||||||
|
var x: f32 = camera.matrix[3];
|
||||||
|
var y: f32 = camera.matrix[7];
|
||||||
|
var w: f32 = camera.matrix[15];
|
||||||
|
|
||||||
|
for (0..3) |i| {
|
||||||
|
x += camera.matrix[i] * position[i];
|
||||||
|
}
|
||||||
|
for (0..3) |i| {
|
||||||
|
y += camera.matrix[i + 4] * position[i];
|
||||||
|
}
|
||||||
|
for (0..3) |i| {
|
||||||
|
w += camera.matrix[i + 12] * position[i];
|
||||||
|
}
|
||||||
|
@setRuntimeSafety(false);
|
||||||
|
const wmod = 1 / w;
|
||||||
|
return .{ x * wmod, y * wmod };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mouse_in_quad(camera: Camera, mouse: @Vector(2, f32), quad_transform: Transform, width: f32, height: f32) bool {
|
||||||
|
@setFloatMode(.optimized);
|
||||||
|
|
||||||
|
const matrix = Transform.multiplyMatrix(camera.matrix, quad_transform.matrix());
|
||||||
|
|
||||||
|
const hw = width * 0.5;
|
||||||
|
const hh = height * 0.5;
|
||||||
|
const pi: [4]@Vector(2, f32) = .{
|
||||||
|
.{ -hw, -hh },
|
||||||
|
.{ -hw, hh },
|
||||||
|
.{ hw, hh },
|
||||||
|
.{ hw, -hh },
|
||||||
|
};
|
||||||
|
var po: [4]@Vector(2, f32) = undefined;
|
||||||
|
for (0..4) |i| {
|
||||||
|
const x = matrix[0] * pi[i][0] + matrix[1] * pi[i][1] + matrix[3];
|
||||||
|
const y = matrix[4] * pi[i][0] + matrix[5] * pi[i][1] + matrix[7];
|
||||||
|
const w = matrix[12] * pi[i][0] + matrix[13] * pi[i][1] + matrix[15];
|
||||||
|
@setRuntimeSafety(false);
|
||||||
|
po[i] = .{ x / w, y / w };
|
||||||
|
}
|
||||||
|
inline for (0..4) |i| {
|
||||||
|
const a = po[i];
|
||||||
|
const b = po[(i + 1) % 4];
|
||||||
|
const c = mouse;
|
||||||
|
if ((c[0] - a[0]) * (b[1] - a[1]) - (c[1] - a[1]) * (b[0] - a[0]) < 0.0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn raycast(camera: Camera, mouse: @Vector(2, f32), plane: @Vector(4, f32)) @Vector(3, f32) {
|
||||||
|
const matrix = camera.transform.matrix();
|
||||||
|
|
||||||
|
const local = @Vector(3, f32){
|
||||||
|
mouse[0] * camera.lens * camera.aspect,
|
||||||
|
mouse[1] * camera.lens,
|
||||||
|
-1,
|
||||||
|
};
|
||||||
|
var global = @Vector(3, f32){
|
||||||
|
matrix[3],
|
||||||
|
matrix[7],
|
||||||
|
matrix[11],
|
||||||
|
};
|
||||||
|
for (0..3) |i| {
|
||||||
|
for (0..3) |j| {
|
||||||
|
global[i] += local[j] * matrix[4 * i + j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return math.raycast(camera.transform.position, global, plane);
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ pub const BLEND_NORMAL = sdl.GPUColorTargetBlendState{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const DEPTH_ENABLED = sdl.GPUDepthStencilState{
|
pub const DEPTH_ENABLED = sdl.GPUDepthStencilState{
|
||||||
.compare_op = sdl.GPU_COMPAREOP_LESS,
|
.compare_op = sdl.GPU_COMPAREOP_GREATER,
|
||||||
.enable_depth_test = true,
|
.enable_depth_test = true,
|
||||||
.enable_depth_write = true,
|
.enable_depth_write = true,
|
||||||
};
|
};
|
||||||
|
@@ -1,25 +1,25 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const math = @import("../math.zig");
|
||||||
const Transform = @This();
|
const Transform = @This();
|
||||||
|
|
||||||
pub const TMatrix = @Vector(16, f32);
|
pub const TMatrix = @Vector(16, f32);
|
||||||
|
|
||||||
pub const Position = @Vector(3, f32);
|
pub const Position = @Vector(3, f32);
|
||||||
pub const Rotation = @Vector(4, f32);
|
pub const Rotation = @Vector(4, f32);
|
||||||
pub const Scale = @Vector(3, f32);
|
pub const Scale = f32;
|
||||||
|
|
||||||
position: Position = @splat(0.0),
|
position: Position = @splat(0.0),
|
||||||
rotation: Rotation = .{ 1.0, 0.0, 0.0, 0.0 },
|
rotation: Rotation = .{ 1.0, 0.0, 0.0, 0.0 },
|
||||||
scale: Scale = @splat(1.0),
|
scale: Scale = 1.0,
|
||||||
|
|
||||||
pub fn matrix(transform: Transform) TMatrix {
|
pub fn matrix(transform: Transform) TMatrix {
|
||||||
@setFloatMode(.optimized);
|
@setFloatMode(.optimized);
|
||||||
|
|
||||||
const r = rotationMatrix(transform.rotation);
|
const r = rotationMatrix(transform.rotation) * @as(@Vector(9, f32), @splat(transform.scale));
|
||||||
const sx, const sy, const sz = transform.scale;
|
|
||||||
return .{
|
return .{
|
||||||
sx * r[0], sy * r[1], sz * r[2], transform.position[0],
|
r[0], r[1], r[2], transform.position[0],
|
||||||
sx * r[3], sy * r[4], sz * r[5], transform.position[1],
|
r[3], r[4], r[5], transform.position[1],
|
||||||
sx * r[6], sy * r[7], sz * r[8], transform.position[2],
|
r[6], r[7], r[8], transform.position[2],
|
||||||
0.0, 0.0, 0.0, 1.0,
|
0.0, 0.0, 0.0, 1.0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -27,24 +27,12 @@ pub fn matrix(transform: Transform) TMatrix {
|
|||||||
pub fn inverseMatrix(transform: Transform) TMatrix {
|
pub fn inverseMatrix(transform: Transform) TMatrix {
|
||||||
@setFloatMode(.optimized);
|
@setFloatMode(.optimized);
|
||||||
|
|
||||||
const r = rotationMatrix(flipRotation(transform.rotation));
|
const r = rotationMatrix(flipRotation(transform.rotation)) * @as(@Vector(9, f32), @splat(1.0 / transform.scale));
|
||||||
const tx, const ty, const tz = transform.position;
|
const tx, const ty, const tz = transform.position;
|
||||||
const sx = 1.0 / transform.scale[0];
|
|
||||||
const sy = 1.0 / transform.scale[1];
|
|
||||||
const sz = 1.0 / transform.scale[2];
|
|
||||||
const r0 = r[0] * sx;
|
|
||||||
const r1 = r[1] * sx;
|
|
||||||
const r2 = r[2] * sx;
|
|
||||||
const r3 = r[3] * sy;
|
|
||||||
const r4 = r[4] * sy;
|
|
||||||
const r5 = r[5] * sy;
|
|
||||||
const r6 = r[6] * sz;
|
|
||||||
const r7 = r[7] * sz;
|
|
||||||
const r8 = r[8] * sz;
|
|
||||||
return .{
|
return .{
|
||||||
r0, r1, r2, -(r0 * tx + r1 * ty + r2 * tz),
|
r[0], r[1], r[2], -(r[0] * tx + r[1] * ty + r[2] * tz),
|
||||||
r3, r4, r5, -(r3 * tx + r4 * ty + r5 * tz),
|
r[3], r[4], r[5], -(r[3] * tx + r[4] * ty + r[5] * tz),
|
||||||
r6, r7, r8, -(r6 * tx + r7 * ty + r8 * tz),
|
r[6], r[7], r[8], -(r[6] * tx + r[7] * ty + r[8] * tz),
|
||||||
0.0, 0.0, 0.0, 1.0,
|
0.0, 0.0, 0.0, 1.0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -94,6 +82,7 @@ pub fn rotateVector(vector: Position, rotation: Rotation) Position {
|
|||||||
pub fn rotate(transform: *Transform, rotation: Rotation) void {
|
pub fn rotate(transform: *Transform, rotation: Rotation) void {
|
||||||
@setFloatMode(.optimized);
|
@setFloatMode(.optimized);
|
||||||
|
|
||||||
|
// Also rotate `Position` around the origin?
|
||||||
transform.rotation = normalizeRotation(combineRotations(transform.rotation, rotation));
|
transform.rotation = normalizeRotation(combineRotations(transform.rotation, rotation));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +137,8 @@ pub fn normalizeRotation(r: Rotation) Rotation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// a: Child `Rotation`
|
||||||
|
/// b: Parent `Rotation`
|
||||||
pub fn combineRotations(a: Rotation, b: Rotation) Rotation {
|
pub fn combineRotations(a: Rotation, b: Rotation) Rotation {
|
||||||
@setFloatMode(.optimized);
|
@setFloatMode(.optimized);
|
||||||
|
|
||||||
@@ -230,3 +221,31 @@ pub fn multiplyMatrix(a: TMatrix, b: TMatrix) TMatrix {
|
|||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// a: Child `Transform`
|
||||||
|
/// b: Parent `Transform`
|
||||||
|
pub fn combineTransforms(a: Transform, b: Transform) Transform {
|
||||||
|
@setFloatMode(.optimized);
|
||||||
|
|
||||||
|
return Transform{
|
||||||
|
.position = rotateVector(a.position, b.rotation) * @as(Position, @splat(b.scale)) + b.position,
|
||||||
|
.rotation = combineRotations(a.rotation, b.rotation),
|
||||||
|
.scale = a.scale * b.scale,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lerpTransformTimeLn(a: Transform, b: Transform, t: f32, lnf: f32) Transform {
|
||||||
|
return lerpTransform(b, a, @exp(lnf * t));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lerpTransform(a: Transform, b: Transform, f: f32) Transform {
|
||||||
|
@setFloatMode(.optimized);
|
||||||
|
|
||||||
|
return Transform{
|
||||||
|
.position = math.lerp(a.position, b.position, f),
|
||||||
|
.rotation = math.slerp(a.rotation, b.rotation, f),
|
||||||
|
.scale = math.lerp(a.scale, b.scale, f),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const ZERO = Transform{};
|
||||||
|
82
src/math.zig
82
src/math.zig
@@ -36,15 +36,21 @@ pub inline fn slerpTime(a: anytype, b: anytype, t: f32, comptime f: f32) @TypeOf
|
|||||||
pub fn slerpTimeLn(a: anytype, b: anytype, t: f32, lnf: f32) @TypeOf(a, b) {
|
pub fn slerpTimeLn(a: anytype, b: anytype, t: f32, lnf: f32) @TypeOf(a, b) {
|
||||||
@setFloatMode(.optimized);
|
@setFloatMode(.optimized);
|
||||||
|
|
||||||
|
return slerp(b, a, @exp(lnf * t));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slerp(a: anytype, b: anytype, f: f32) @TypeOf(a, b) {
|
||||||
|
@setFloatMode(.optimized);
|
||||||
|
|
||||||
const cos = @reduce(.Add, a * b);
|
const cos = @reduce(.Add, a * b);
|
||||||
if (cos > 0.999) {
|
if (cos > 0.999) {
|
||||||
return lerpTimeLn(a, b, t, lnf);
|
return lerp(a, b, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
const angle = std.math.acos(cos);
|
const angle = std.math.acos(cos);
|
||||||
|
|
||||||
const a_angle_factor = @exp(lnf * t);
|
const a_angle_factor = 1 - f;
|
||||||
const b_angle_factor = 1.0 - a_angle_factor;
|
const b_angle_factor = f;
|
||||||
|
|
||||||
const rev_angle_sin = 1.0 / std.math.sin(angle);
|
const rev_angle_sin = 1.0 / std.math.sin(angle);
|
||||||
const a_sin = std.math.sin(a_angle_factor * angle);
|
const a_sin = std.math.sin(a_angle_factor * angle);
|
||||||
@@ -106,3 +112,73 @@ pub fn dotInt(a: anytype, b: anytype) (@typeInfo(@TypeOf(a)).vector.child) {
|
|||||||
|
|
||||||
return @reduce(.Add, a * b);
|
return @reduce(.Add, a * b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn Sway(comptime T: type) type {
|
||||||
|
const STABILIZATION = -1;
|
||||||
|
return packed struct {
|
||||||
|
value: T = 0,
|
||||||
|
velocity: T = 0,
|
||||||
|
frequency: T,
|
||||||
|
amplitude: T,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
pub fn update(self: *Self, delta: f32) void {
|
||||||
|
@setFloatMode(.optimized);
|
||||||
|
|
||||||
|
const dist = delta * -2 * std.math.pi * self.frequency;
|
||||||
|
const sin = std.math.sin(dist);
|
||||||
|
const cos = std.math.cos(dist);
|
||||||
|
var len = length(@Vector(2, T){ self.value, self.velocity });
|
||||||
|
if (len < 0.001) {
|
||||||
|
self.value = 0.001;
|
||||||
|
self.velocity = 0;
|
||||||
|
len = 0.001;
|
||||||
|
}
|
||||||
|
const new_value = self.value * cos - self.velocity * sin;
|
||||||
|
const new_velocity = self.value * sin + self.velocity * cos;
|
||||||
|
const target_len = lerpTimeLn(len, self.amplitude, delta, STABILIZATION);
|
||||||
|
const mult = target_len / len;
|
||||||
|
self.value = new_value * mult;
|
||||||
|
self.velocity = new_velocity * mult;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn raycast(
|
||||||
|
origin: @Vector(3, f32),
|
||||||
|
target: @Vector(3, f32),
|
||||||
|
plane: @Vector(4, f32),
|
||||||
|
) @Vector(3, f32) {
|
||||||
|
@setFloatMode(.optimized);
|
||||||
|
|
||||||
|
const offset = target - origin;
|
||||||
|
const plane_dir = @Vector(3, f32){ plane[0], plane[1], plane[2] };
|
||||||
|
const dist = plane[3];
|
||||||
|
const dist_mod = dist / dot(plane_dir, plane_dir);
|
||||||
|
const num = dot(plane_dir, plane_dir * @as(@Vector(3, f32), @splat(dist_mod)) - origin);
|
||||||
|
var den = dot(offset, plane_dir);
|
||||||
|
if (@abs(den) < 0.0001) {
|
||||||
|
den = 0.0001;
|
||||||
|
}
|
||||||
|
|
||||||
|
return origin + offset * @as(@Vector(3, f32), @splat(num / den));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn limit(vector: anytype, value: f32) @TypeOf(vector) {
|
||||||
|
const max = @reduce(.Max, vector);
|
||||||
|
if (max > value)
|
||||||
|
return vector * @as(@TypeOf(vector), @splat(value / max))
|
||||||
|
else
|
||||||
|
return vector;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn norm(vector: anytype) @TypeOf(vector) {
|
||||||
|
const len = length(vector);
|
||||||
|
if (len < 1.01 and len > 0.99) return vector;
|
||||||
|
if (len < 1e-10) {
|
||||||
|
var output = @as(@TypeOf(vector), @splat(0));
|
||||||
|
output[@typeInfo(@TypeOf(vector)).vector.len - 1] = 1;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
return vector * @as(@TypeOf(vector), @splat(1.0 / len));
|
||||||
|
}
|
||||||
|
@@ -3,7 +3,15 @@ const key_store = @import("data/keystore.zig");
|
|||||||
|
|
||||||
buttons: key_store.KeyStore(@TypeOf(sdl.BUTTON_LEFT), 4, 0) = .{},
|
buttons: key_store.KeyStore(@TypeOf(sdl.BUTTON_LEFT), 4, 0) = .{},
|
||||||
|
|
||||||
x: f32 = 0,
|
x_screen: f32 = 0,
|
||||||
y: f32 = 0,
|
y_screen: f32 = 0,
|
||||||
|
x_norm: f32 = 0,
|
||||||
|
y_norm: f32 = 0,
|
||||||
dx: f32 = 0,
|
dx: f32 = 0,
|
||||||
dy: f32 = 0,
|
dy: f32 = 0,
|
||||||
|
wheel: i32 = 0,
|
||||||
|
|
||||||
|
pub fn reset(mouse: *@This()) void {
|
||||||
|
mouse.buttons.reset();
|
||||||
|
mouse.wheel = 0;
|
||||||
|
}
|
||||||
|
5
src/offline.zig
Normal file
5
src/offline.zig
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const client = @import("client.zig");
|
||||||
|
|
||||||
|
pub fn main() void {
|
||||||
|
client.main();
|
||||||
|
}
|
5
src/server.zig
Normal file
5
src/server.zig
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn main() void {
|
||||||
|
std.debug.print("Server started!\n", .{});
|
||||||
|
}
|
51
src/time.zig
51
src/time.zig
@@ -1,51 +0,0 @@
|
|||||||
const TimeType = u64;
|
|
||||||
const TIME_UNIT: TimeType = 1 << 32;
|
|
||||||
const TIME_MULT = 1.0 / @as(f32, @floatFromInt(TIME_UNIT));
|
|
||||||
const Time = @This();
|
|
||||||
|
|
||||||
pub const ZERO = Time{ .clock = 0 };
|
|
||||||
|
|
||||||
clock: TimeType,
|
|
||||||
|
|
||||||
pub fn tick(self: *Time, units: f32) void {
|
|
||||||
self.clock += durationFromUnits(units);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn past(self: *Time, goal: Time) bool {
|
|
||||||
return self.clock >= goal.clock;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn offset(self: Time, units: f32) Time {
|
|
||||||
return Time{
|
|
||||||
.clock = self.clock + durationFromUnits(units),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unitsSince(self: *Time, from: Time) f32 {
|
|
||||||
if (from.clock > self.clock) return 0;
|
|
||||||
return @as(f32, @floatFromInt(self.clock - from.clock)) * TIME_MULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn progress(self: *Time, from: Time, to: Time) f32 {
|
|
||||||
if (from.clock > to.clock) return 1.0;
|
|
||||||
if (self.clock > to.clock) return 1.0;
|
|
||||||
|
|
||||||
const duration = to.clock - from.clock;
|
|
||||||
return @as(f32, @floatFromInt(self.clock - from.clock)) / @as(f32, @floatFromInt(duration));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unitsFromDuration(duration: TimeType) f32 {
|
|
||||||
return @as(f32, @floatFromInt(duration)) * TIME_MULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn durationFromUnits(units: f32) TimeType {
|
|
||||||
return @intFromFloat(@as(f32, @floatFromInt(TIME_UNIT)) * units);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn earliest(a: Time, b: Time) Time {
|
|
||||||
return .{ .clock = @min(a.clock, b.clock) };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn plus(time: Time, ticks: TimeType) Time {
|
|
||||||
return .{ .clock = time.clock + ticks };
|
|
||||||
}
|
|
636
src/world.zig
636
src/world.zig
@@ -1,152 +1,546 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const sdl = @import("sdl");
|
||||||
|
const math = @import("math.zig");
|
||||||
|
const err = @import("error.zig");
|
||||||
|
const Game = @import("game.zig");
|
||||||
const Graphics = @import("graphics.zig");
|
const Graphics = @import("graphics.zig");
|
||||||
const Assets = @import("assets.zig");
|
const Assets = @import("assets.zig");
|
||||||
const Entity = @import("entity.zig");
|
|
||||||
const Time = @import("time.zig");
|
|
||||||
const comp = @import("components.zig");
|
|
||||||
|
|
||||||
pub var time: Time = undefined;
|
const Id = u32;
|
||||||
var next_stop: Time = undefined;
|
const Order = i32;
|
||||||
var entities: comp.Storage(Entity, .{}) = undefined;
|
|
||||||
|
|
||||||
pub var plane_mesh: Graphics.Mesh = undefined;
|
pub var object_map: std.AutoHashMapUnmanaged(Id, usize) = .{};
|
||||||
pub var cube_mesh: Graphics.Mesh = undefined;
|
pub var objects: std.ArrayListUnmanaged(Object) = .{};
|
||||||
pub var texture: Assets.Texture = undefined;
|
|
||||||
|
pub var hand: Assets.Object = undefined;
|
||||||
|
pub var table: Assets.Object = undefined;
|
||||||
|
pub var cubemap: Assets.Object = undefined;
|
||||||
|
|
||||||
|
pub var camera_position: @Vector(2, f32) = @splat(0);
|
||||||
|
pub var hand_transform: Graphics.Transform = .{};
|
||||||
|
pub var dock_transform: Graphics.Transform = .{};
|
||||||
|
pub var zoom: i32 = 0;
|
||||||
|
|
||||||
|
pub var hover: ?Id = null;
|
||||||
|
pub var panning = false;
|
||||||
|
pub var hand_objects: u32 = 0;
|
||||||
|
pub var hand_scale: f32 = 0;
|
||||||
|
pub var dock_objects: u32 = 0;
|
||||||
|
pub var dock_last_width: f32 = 0;
|
||||||
|
pub var dock_focused: bool = false;
|
||||||
|
pub var dock_spacing: f32 = 0;
|
||||||
|
pub var min_order: Order = undefined;
|
||||||
|
pub var max_order: Order = undefined;
|
||||||
|
|
||||||
|
const DOCK_TILT = 0.03;
|
||||||
|
const DOCK_TILT_SIN = std.math.sin(DOCK_TILT);
|
||||||
|
const DOCK_TILT_COS = std.math.cos(DOCK_TILT);
|
||||||
|
|
||||||
|
const Object = struct {
|
||||||
|
type: Type,
|
||||||
|
transform: Graphics.Transform = .{},
|
||||||
|
target_transform: Graphics.Transform = .{},
|
||||||
|
width: f32,
|
||||||
|
height: f32,
|
||||||
|
object: Assets.Object,
|
||||||
|
order: Order,
|
||||||
|
id: Id,
|
||||||
|
index: u32,
|
||||||
|
z: u32 = 0,
|
||||||
|
parent: Parent = .none,
|
||||||
|
parent_index: u32 = 0,
|
||||||
|
child_last_id: u32 = 0,
|
||||||
|
influence: f32 = 0,
|
||||||
|
|
||||||
|
const Type = enum {
|
||||||
|
card,
|
||||||
|
deck,
|
||||||
|
};
|
||||||
|
const Parent = union(enum) {
|
||||||
|
none,
|
||||||
|
hand,
|
||||||
|
dock,
|
||||||
|
deck: Id,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn reparent(self: *@This(), new_parent: Parent) void {
|
||||||
|
self.transform = self.drawingTransform();
|
||||||
|
self.influence = 0;
|
||||||
|
self.parent = new_parent;
|
||||||
|
}
|
||||||
|
pub fn drawingTransform(self: @This()) Graphics.Transform {
|
||||||
|
const transform = self.transform;
|
||||||
|
const parent_transform = switch (self.parent) {
|
||||||
|
.hand => World.hand_transform,
|
||||||
|
.dock => World.dock_transform,
|
||||||
|
.deck => |deck| if (World.getObject(deck)) |object| object.drawingTransform() else Graphics.Transform{},
|
||||||
|
.none => return transform,
|
||||||
|
};
|
||||||
|
return Graphics.Transform.combineTransforms(
|
||||||
|
transform,
|
||||||
|
Graphics.Transform.lerpTransform(
|
||||||
|
.{},
|
||||||
|
parent_transform,
|
||||||
|
self.influence,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const World = @This();
|
const World = @This();
|
||||||
pub fn initDebug() void {
|
pub fn initDebug() void {
|
||||||
entities = comp.Storage(Entity, .{}).init();
|
for (0..70) |i| {
|
||||||
_ = entities.add(.{
|
World.objects.append(Game.alloc, .{
|
||||||
.position = .{ 0, 0 },
|
.type = .card,
|
||||||
.player = true,
|
.width = 0.5,
|
||||||
});
|
.height = 0.5,
|
||||||
_ = entities.add(.{
|
.object = Assets.load(.gltf, "data/yakuza/card.gltf"),
|
||||||
.position = .{ 2, 0 },
|
.order = @intCast(i),
|
||||||
.enemy = true,
|
.id = @intCast(i),
|
||||||
.controller = .{
|
.index = @intCast(i),
|
||||||
.move_units = 0.25,
|
.parent = .{ .deck = if (i < 60) @as(Id, 70) else @as(Id, 71) },
|
||||||
},
|
}) catch err.oom();
|
||||||
});
|
World.object_map.put(Game.alloc, @intCast(i), i) catch err.oom();
|
||||||
_ = entities.add(.{
|
}
|
||||||
.position = .{ 3, 0 },
|
World.objects.append(Game.alloc, .{
|
||||||
.enemy = true,
|
.target_transform = .{ .position = .{ -3, 0, 0 } },
|
||||||
.controller = .{
|
.type = .deck,
|
||||||
.move_units = 0.25,
|
.width = 1,
|
||||||
},
|
.height = 1,
|
||||||
});
|
.object = Assets.load(.gltf, "data/yakuza/pad.gltf"),
|
||||||
time = Time.ZERO;
|
.order = 70,
|
||||||
World.plane_mesh = Graphics.loadMesh(@ptrCast(&PLANE_MESH_DATA));
|
.id = 70,
|
||||||
World.cube_mesh = Graphics.loadMesh(@ptrCast(&CUBE_MESH_DATA));
|
.index = 70,
|
||||||
World.texture = Assets.load(.texture, "data/wawa.png");
|
}) catch err.oom();
|
||||||
|
World.object_map.put(Game.alloc, 60, 60) catch err.oom();
|
||||||
|
World.objects.append(Game.alloc, .{
|
||||||
|
.target_transform = .{ .position = .{ 3, 0, 0 } },
|
||||||
|
.type = .deck,
|
||||||
|
.width = 1,
|
||||||
|
.height = 1,
|
||||||
|
.object = Assets.load(.gltf, "data/yakuza/pad.gltf"),
|
||||||
|
.order = 71,
|
||||||
|
.id = 71,
|
||||||
|
.index = 71,
|
||||||
|
}) catch err.oom();
|
||||||
|
World.object_map.put(Game.alloc, 71, 71) catch err.oom();
|
||||||
|
|
||||||
|
World.hand = Assets.load(.gltf, "data/hand.gltf");
|
||||||
|
World.table = Assets.load(.gltf, "data/yakuza/table.gltf");
|
||||||
|
World.cubemap = Assets.load(.gltf, "data/cubemap.gltf");
|
||||||
|
|
||||||
|
World.camera_position = @splat(0);
|
||||||
|
World.hand_transform = .{};
|
||||||
|
World.hand_scale = 0.5;
|
||||||
|
World.dock_transform = .{
|
||||||
|
.position = .{ 0, 0, 4 },
|
||||||
|
};
|
||||||
|
World.dock_spacing = 0.2;
|
||||||
|
World.zoom = 0;
|
||||||
|
|
||||||
|
World.panning = false;
|
||||||
|
World.dock_focused = false;
|
||||||
|
World.min_order = 0;
|
||||||
|
World.max_order = 71;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit() void {
|
pub fn deinit() void {
|
||||||
Graphics.unloadMesh(World.plane_mesh);
|
Assets.free(World.hand);
|
||||||
Graphics.unloadMesh(World.cube_mesh);
|
Assets.free(World.table);
|
||||||
Assets.free(World.texture);
|
Assets.free(World.cubemap);
|
||||||
World.entities.deinit();
|
for (World.objects.items) |*object| {
|
||||||
|
Assets.free(object.object);
|
||||||
|
}
|
||||||
|
World.objects.clearAndFree(Game.alloc);
|
||||||
|
World.object_map.clearAndFree(Game.alloc);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn updateReal(delta: f32) void {
|
pub fn update(delta: f32) void {
|
||||||
const update_until = World.time.plus(Time.durationFromUnits(delta));
|
World.updateCamera(delta);
|
||||||
while (!World.time.past(update_until)) {
|
{
|
||||||
const current = Time.earliest(World.next_stop, update_until);
|
World.dock_transform = Graphics.Transform.lerpTransformTimeLn(
|
||||||
defer World.time = current;
|
World.dock_transform,
|
||||||
|
Graphics.Transform.combineTransforms(.{ .position = .{
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
-1 / Graphics.camera.lens,
|
||||||
|
} }, Graphics.camera.transform),
|
||||||
|
delta,
|
||||||
|
-128,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const hand_target = Graphics.camera.raycast(.{ Game.mouse.x_norm, Game.mouse.y_norm }, .{ 0, 0, 1, 0 });
|
||||||
|
World.hand_transform.position = math.lerpTimeLn(
|
||||||
|
World.hand_transform.position,
|
||||||
|
hand_target + @Vector(3, f32){ 0, 0, 0.2 },
|
||||||
|
delta,
|
||||||
|
-24,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
var iter = World.entities.iter();
|
World.updateOrder();
|
||||||
while (iter.next()) |entity| {
|
|
||||||
entity.update();
|
World.hover = null;
|
||||||
|
World.hand_objects = 0;
|
||||||
|
World.dock_objects = 0;
|
||||||
|
for (World.objects.items) |*object| {
|
||||||
|
updateHover(object);
|
||||||
|
}
|
||||||
|
for (World.objects.items) |*object| {
|
||||||
|
updateObject(object, delta);
|
||||||
|
}
|
||||||
|
World.updateControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updateControls() void {
|
||||||
|
if (Game.keyboard.keys.is_pressed(sdl.SDL_SCANCODE_LSHIFT)) {
|
||||||
|
World.scroll(Game.mouse.wheel);
|
||||||
|
} else {
|
||||||
|
World.zoom = std.math.clamp(World.zoom + Game.mouse.wheel, -4, 8);
|
||||||
|
}
|
||||||
|
if (Game.mouse.buttons.is_just_pressed(sdl.BUTTON_LEFT)) {
|
||||||
|
World.panning = !World.tryPick();
|
||||||
|
}
|
||||||
|
if (Game.mouse.buttons.is_just_pressed(sdl.BUTTON_RIGHT)) {
|
||||||
|
_ = World.tryRelease();
|
||||||
|
}
|
||||||
|
if (Game.mouse.y_norm <= -0.8) {
|
||||||
|
World.dock_focused = true;
|
||||||
|
}
|
||||||
|
if (Game.mouse.y_norm >= -0.6) {
|
||||||
|
World.dock_focused = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll(delta: i32) void {
|
||||||
|
if (World.getHover()) |hover_object| {
|
||||||
|
if (hover_object.type == .deck) {
|
||||||
|
if (delta > 0) {
|
||||||
|
var left_to_put = delta;
|
||||||
|
var i = World.objects.items.len - 1;
|
||||||
|
while (left_to_put > 0) {
|
||||||
|
const object = &World.objects.items[i];
|
||||||
|
if (object.parent != .hand) {
|
||||||
|
if (i == 0) break;
|
||||||
|
i -= 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
World.bringToTop(object);
|
||||||
|
object.reparent(.{ .deck = hover_object.id });
|
||||||
|
if (i == 0) break;
|
||||||
|
i -= 1;
|
||||||
|
left_to_put -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (delta < 0) {
|
||||||
|
var left_to_take = -delta;
|
||||||
|
var i = World.objects.items.len - 1;
|
||||||
|
while (left_to_take > 0) {
|
||||||
|
const object = &World.objects.items[i];
|
||||||
|
if (object.parent != .deck or object.parent.deck != hover_object.id) {
|
||||||
|
if (i == 0) break;
|
||||||
|
i -= 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
World.bringToTop(object);
|
||||||
|
object.reparent(.hand);
|
||||||
|
if (i == 0) break;
|
||||||
|
i -= 1;
|
||||||
|
left_to_take -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (delta > 0 and World.hand_objects > 0) {
|
||||||
|
var left_to_scroll = @rem(delta, @as(i32, @intCast(World.hand_objects)));
|
||||||
|
var i = World.objects.items.len - 1;
|
||||||
|
while (left_to_scroll > 0) : (i -= 1) {
|
||||||
|
const object = &World.objects.items[i];
|
||||||
|
if (object.parent != .hand) continue;
|
||||||
|
|
||||||
|
World.bringToBottom(object);
|
||||||
|
left_to_scroll -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (delta < 0 and World.hand_objects > 0) {
|
||||||
|
var left_to_scroll = @rem(-delta, @as(i32, @intCast(World.hand_objects)));
|
||||||
|
var i: usize = 0;
|
||||||
|
while (left_to_scroll > 0) : (i += 1) {
|
||||||
|
const object = &World.objects.items[i];
|
||||||
|
if (object.parent != .hand) continue;
|
||||||
|
|
||||||
|
World.bringToTop(object);
|
||||||
|
left_to_scroll -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(delta: f32) void {
|
pub fn tryPick() bool {
|
||||||
Graphics.drawMesh(World.plane_mesh, World.texture, .{ .scale = @splat(5) });
|
var object = World.getHover() orelse return false;
|
||||||
var iter = World.entities.iter();
|
switch (object.type) {
|
||||||
while (iter.next()) |entity| {
|
.card => {},
|
||||||
entity.draw(delta);
|
.deck => {
|
||||||
|
if (!Game.keyboard.keys.is_pressed(sdl.SDL_SCANCODE_LSHIFT)) {
|
||||||
|
for (World.objects.items) |*child| {
|
||||||
|
if (child.parent == .deck and child.parent.deck == object.id and child.id == object.child_last_id) {
|
||||||
|
object = child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
World.panning = false;
|
||||||
|
object.reparent(.hand);
|
||||||
|
World.bringToTop(object);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pub fn tryRelease() bool {
|
||||||
|
const object = blk: {
|
||||||
|
var i = World.objects.items.len - 1;
|
||||||
|
while (true) {
|
||||||
|
const object = &World.objects.items[i];
|
||||||
|
if (object.parent == .hand) {
|
||||||
|
break :blk object;
|
||||||
|
}
|
||||||
|
if (i > 0)
|
||||||
|
i -= 1
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
object.target_transform.position = World.hand_transform.position;
|
||||||
|
World.bringToTop(object);
|
||||||
|
if (object.type == .card and !Game.keyboard.keys.is_pressed(sdl.SDL_SCANCODE_LSHIFT)) {
|
||||||
|
if (World.getHover()) |hover_object| {
|
||||||
|
if (hover_object.type == .deck) {
|
||||||
|
object.reparent(.{ .deck = hover_object.id });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (World.dock_focused) {
|
||||||
|
object.reparent(.dock);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
object.reparent(.none);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn requestUpdate(at: Time) void {
|
pub fn updateHover(object: *Object) void {
|
||||||
World.next_stop = Time.earliest(at, World.next_stop);
|
switch (object.parent) {
|
||||||
|
.deck => |id| {
|
||||||
|
if (World.getObject(id)) |deck| {
|
||||||
|
deck.child_last_id = object.id;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.none => {
|
||||||
|
if (!World.dock_focused and Graphics.camera.mouse_in_quad(.{ Game.mouse.x_norm, Game.mouse.y_norm }, object.drawingTransform(), object.width, object.height)) {
|
||||||
|
if (World.hover == null or World.getHover().?.z < object.z) {
|
||||||
|
World.hover = object.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.hand => {
|
||||||
|
object.parent_index = World.hand_objects;
|
||||||
|
World.hand_objects += 1;
|
||||||
|
},
|
||||||
|
.dock => {
|
||||||
|
object.parent_index = World.dock_objects;
|
||||||
|
World.dock_last_width = object.width * object.target_transform.scale;
|
||||||
|
World.dock_objects += 1;
|
||||||
|
if (World.dock_focused and Graphics.camera.mouse_in_quad(.{ Game.mouse.x_norm, Game.mouse.y_norm }, object.transform.combineTransforms(World.dock_transform), object.width, object.height)) {
|
||||||
|
if (World.hover == null or World.getObject(World.hover.?).?.z < object.z) {
|
||||||
|
World.hover = object.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn entityAt(position: @Vector(2, i32)) ?*Entity {
|
pub fn updateObject(object: *Object, delta: f32) void {
|
||||||
var iter = World.entities.iter();
|
switch (object.parent) {
|
||||||
while (iter.next()) |entity| {
|
.none => {
|
||||||
if (@reduce(.And, entity.position == position))
|
object.target_transform.position[2] = @as(f32, 0.001) * @as(f32, @floatFromInt(object.index + 1));
|
||||||
return entity;
|
object.target_transform.scale = if (World.hover == object.id) @as(f32, 1.1) else @as(f32, 1);
|
||||||
|
},
|
||||||
|
.hand => {
|
||||||
|
var target_position = @as(@Vector(3, f32), @splat(0));
|
||||||
|
var target_scale: f32 = 1.0;
|
||||||
|
target_position[2] -= 0.001;
|
||||||
|
const hand_order = hand_objects - object.parent_index - 1;
|
||||||
|
switch (hand_order) {
|
||||||
|
0 => {},
|
||||||
|
else => |i| {
|
||||||
|
target_position[0] += World.hand_scale * if (i & 2 == 0) @as(f32, 1) else @as(f32, 1.5);
|
||||||
|
target_position[1] += World.hand_scale * if ((i - 1) & 2 == 0) @as(f32, -0.25) else @as(f32, -0.75);
|
||||||
|
target_position[2] -= @as(f32, @floatFromInt((hand_order - 1) / 4)) * 0.001;
|
||||||
|
target_scale = 0.5;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
object.target_transform.position = target_position;
|
||||||
|
object.target_transform.scale = target_scale;
|
||||||
|
},
|
||||||
|
.dock => {
|
||||||
|
var topleft_x = -World.dock_last_width * 0.5 * DOCK_TILT_COS + World.dock_spacing * (@as(f32, @floatFromInt(object.parent_index)) - @as(f32, @floatFromInt(World.dock_objects - 1)) * 0.5);
|
||||||
|
const total_w = @as(f32, @floatFromInt(World.dock_objects - 1)) * World.dock_spacing + World.dock_last_width * DOCK_TILT_COS;
|
||||||
|
const mouse_x = if (World.dock_focused) Game.mouse.x_norm else 0.5;
|
||||||
|
if (total_w > Graphics.camera.aspect * 2) {
|
||||||
|
topleft_x += math.lerp(0, Graphics.camera.aspect - total_w * 0.5, mouse_x);
|
||||||
|
}
|
||||||
|
const hit = World.hover == object.id;
|
||||||
|
const topleft_y = if (World.dock_focused) if (hit) @as(f32, 0.5) else @as(f32, 0.3) else @as(f32, 0.2);
|
||||||
|
object.target_transform.position = .{
|
||||||
|
topleft_x + object.width * 0.5 * object.target_transform.scale * DOCK_TILT_COS,
|
||||||
|
topleft_y - object.height * 0.5 * object.target_transform.scale,
|
||||||
|
if (hit) @as(f32, 0.02) else -object.width * 0.5 * DOCK_TILT_SIN,
|
||||||
|
};
|
||||||
|
object.target_transform.rotation = if (hit)
|
||||||
|
Graphics.Transform.ZERO.rotation
|
||||||
|
else
|
||||||
|
Graphics.Transform.rotationByAxis(.{ 0, 1, 0 }, DOCK_TILT);
|
||||||
|
},
|
||||||
|
.deck => {
|
||||||
|
object.target_transform.position = .{ 0, 0, @as(f32, 0.001) };
|
||||||
|
object.target_transform.scale = if (World.hover == object.id) @as(f32, 1.1) else @as(f32, 1);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
object.z = switch (object.parent) {
|
||||||
|
.deck => |deck_id| if (World.getObject(deck_id)) |deck| deck.z + object.index else object.index,
|
||||||
|
.none, .hand, .dock => object.index,
|
||||||
|
};
|
||||||
|
if (object.parent != .none) {
|
||||||
|
object.influence = math.lerpTimeLn(
|
||||||
|
object.influence,
|
||||||
|
1.0,
|
||||||
|
delta,
|
||||||
|
-24,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
object.transform = Graphics.Transform.lerpTransformTimeLn(
|
||||||
|
object.transform,
|
||||||
|
object.target_transform,
|
||||||
|
delta,
|
||||||
|
-24,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw() void {
|
||||||
|
Graphics.drawObject(&World.table, .{});
|
||||||
|
|
||||||
|
for (World.objects.items) |*object| {
|
||||||
|
sw: switch (object.parent) {
|
||||||
|
.none, .hand => {
|
||||||
|
Graphics.drawObject(&object.object, object.drawingTransform());
|
||||||
|
},
|
||||||
|
.dock => {},
|
||||||
|
.deck => |id| {
|
||||||
|
if (World.getObject(id)) |deck| {
|
||||||
|
if (deck.child_last_id != object.id) continue;
|
||||||
|
}
|
||||||
|
continue :sw .none;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Graphics.drawObject(
|
||||||
|
&World.hand,
|
||||||
|
Graphics.Transform.combineTransforms(
|
||||||
|
.{
|
||||||
|
.position = .{ World.hand_scale * 0.5, -World.hand_scale * 0.5, 0 },
|
||||||
|
.scale = World.hand_scale,
|
||||||
|
},
|
||||||
|
World.hand_transform,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Graphics.drawObject(&World.cubemap, .{
|
||||||
|
.scale = 1e20,
|
||||||
|
.position = Graphics.camera.transform.position,
|
||||||
|
});
|
||||||
|
Graphics.clearDepth();
|
||||||
|
for (World.objects.items) |*object| {
|
||||||
|
if (object.parent == .dock)
|
||||||
|
Graphics.drawObject(&object.object, object.drawingTransform());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updateCamera(delta: f32) void {
|
||||||
|
const zoom_factor = std.math.exp(@as(f32, @floatFromInt(zoom)) * @log(2.0) * -0.5);
|
||||||
|
|
||||||
|
if (Game.mouse.buttons.is_pressed(sdl.BUTTON_LEFT)) {
|
||||||
|
if (World.panning) {
|
||||||
|
World.camera_position[0] += zoom_factor * Game.mouse.dx / @as(f32, @floatFromInt(Graphics.window_width)) * -15;
|
||||||
|
World.camera_position[1] += zoom_factor * Game.mouse.dy / @as(f32, @floatFromInt(Graphics.window_height)) * 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset = @Vector(3, f32){ 0.0, -1.0 * zoom_factor, 4.0 * zoom_factor };
|
||||||
|
const target_position = @Vector(3, f32){ World.camera_position[0], World.camera_position[1], 0.0 };
|
||||||
|
Graphics.camera.transform.position = math.lerpTimeLn(
|
||||||
|
Graphics.camera.transform.position,
|
||||||
|
target_position + offset,
|
||||||
|
delta,
|
||||||
|
-32,
|
||||||
|
);
|
||||||
|
|
||||||
|
const ORIGIN_DIR = @Vector(3, f32){ 0.0, 0.0, -1.0 };
|
||||||
|
const INIT_ROTATION = Graphics.Transform.rotationByAxis(.{ 1.0, 0.0, 0.0 }, std.math.pi * 0.5);
|
||||||
|
|
||||||
|
const ROTATED_DIR = Graphics.Transform.rotateVector(ORIGIN_DIR, INIT_ROTATION);
|
||||||
|
|
||||||
|
const target_rotation = Graphics.Transform.combineRotations(
|
||||||
|
INIT_ROTATION,
|
||||||
|
Graphics.Transform.rotationToward(
|
||||||
|
ROTATED_DIR,
|
||||||
|
math.lerp(-offset, target_position - Graphics.camera.transform.position, 0.125),
|
||||||
|
.{ .normalize_to = true },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Graphics.camera.transform.rotation = Graphics.Transform.normalizeRotation(math.slerpTimeLn(
|
||||||
|
Graphics.camera.transform.rotation,
|
||||||
|
target_rotation,
|
||||||
|
delta,
|
||||||
|
-16,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getHover() ?*Object {
|
||||||
|
if (World.hover) |id| {
|
||||||
|
return World.getObject(id);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn isFree(position: @Vector(2, i32)) bool {
|
fn getObject(id: Id) ?*Object {
|
||||||
return World.entityAt(position) == null;
|
const index = World.object_map.get(id) orelse return null;
|
||||||
|
if (index >= World.objects.items.len) return null;
|
||||||
|
return &World.objects.items[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getPlayer() ?*Entity {
|
fn bringToTop(object: *Object) void {
|
||||||
var iter = World.entities.iter();
|
World.max_order += 1;
|
||||||
while (iter.next()) |entity| {
|
object.order = World.max_order;
|
||||||
if (entity.player)
|
}
|
||||||
return entity;
|
fn bringToBottom(object: *Object) void {
|
||||||
|
World.min_order -= 1;
|
||||||
|
object.order = World.min_order;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn updateOrder() void {
|
||||||
|
std.sort.block(Object, World.objects.items, {}, objectOrderLessThan);
|
||||||
|
World.object_map.clearRetainingCapacity();
|
||||||
|
for (0.., World.objects.items) |i, *object| {
|
||||||
|
object.index = @intCast(i);
|
||||||
|
World.object_map.putAssumeCapacityNoClobber(object.id, i);
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const CUBE_MESH_DATA = [_]f32{
|
fn objectOrderLessThan(ctx: void, lhs: Object, rhs: Object) bool {
|
||||||
-0.5, 0.5, -0.5, 0.0, 0.0,
|
_ = ctx;
|
||||||
0.5, 0.5, -0.5, 0.0, 0.0,
|
return lhs.order < rhs.order;
|
||||||
-0.5, -0.5, -0.5, 0.0, 0.0,
|
}
|
||||||
0.5, -0.5, -0.5, 0.0, 0.0,
|
|
||||||
-0.5, -0.5, -0.5, 0.0, 0.0,
|
|
||||||
0.5, 0.5, -0.5, 0.0, 0.0,
|
|
||||||
|
|
||||||
0.5, 0.5, -0.5, 0.0, 0.0,
|
|
||||||
0.5, 0.5, 0.5, 0.0, 0.0,
|
|
||||||
0.5, -0.5, -0.5, 0.0, 0.0,
|
|
||||||
0.5, -0.5, 0.5, 0.0, 0.0,
|
|
||||||
0.5, -0.5, -0.5, 0.0, 0.0,
|
|
||||||
0.5, 0.5, 0.5, 0.0, 0.0,
|
|
||||||
|
|
||||||
0.5, 0.5, 0.5, 1.0, 0.0,
|
|
||||||
-0.5, 0.5, 0.5, 0.0, 0.0,
|
|
||||||
0.5, -0.5, 0.5, 1.0, 1.0,
|
|
||||||
-0.5, -0.5, 0.5, 0.0, 1.0,
|
|
||||||
0.5, -0.5, 0.5, 1.0, 1.0,
|
|
||||||
-0.5, 0.5, 0.5, 0.0, 0.0,
|
|
||||||
|
|
||||||
-0.5, 0.5, 0.5, 0.0, 0.0,
|
|
||||||
-0.5, 0.5, -0.5, 0.0, 0.0,
|
|
||||||
-0.5, -0.5, 0.5, 0.0, 0.0,
|
|
||||||
-0.5, -0.5, -0.5, 0.0, 0.0,
|
|
||||||
-0.5, -0.5, 0.5, 0.0, 0.0,
|
|
||||||
-0.5, 0.5, -0.5, 0.0, 0.0,
|
|
||||||
|
|
||||||
-0.5, 0.5, 0.5, 0.0, 0.0,
|
|
||||||
0.5, 0.5, 0.5, 0.0, 0.0,
|
|
||||||
-0.5, 0.5, -0.5, 0.0, 0.0,
|
|
||||||
0.5, 0.5, -0.5, 0.0, 0.0,
|
|
||||||
-0.5, 0.5, -0.5, 0.0, 0.0,
|
|
||||||
0.5, 0.5, 0.5, 0.0, 0.0,
|
|
||||||
|
|
||||||
-0.5, -0.5, -0.5, 0.0, 0.0,
|
|
||||||
0.5, -0.5, -0.5, 0.0, 0.0,
|
|
||||||
-0.5, -0.5, 0.5, 0.0, 0.0,
|
|
||||||
0.5, -0.5, 0.5, 0.0, 0.0,
|
|
||||||
-0.5, -0.5, 0.5, 0.0, 0.0,
|
|
||||||
0.5, -0.5, -0.5, 0.0, 0.0,
|
|
||||||
};
|
|
||||||
const PLANE_MESH_DATA = [_]f32{
|
|
||||||
-0.5, -0.5, 0, 0.0, 1.0,
|
|
||||||
0.5, 0.5, 0, 1.0, 0.0,
|
|
||||||
-0.5, 0.5, 0, 0.0, 0.0,
|
|
||||||
0.5, 0.5, 0, 1.0, 0.0,
|
|
||||||
-0.5, -0.5, 0, 0.0, 1.0,
|
|
||||||
0.5, -0.5, 0, 1.0, 1.0,
|
|
||||||
};
|
|
||||||
const TEXTURE_DATA = [_]u8{
|
|
||||||
255, 64, 64, 255,
|
|
||||||
64, 255, 64, 255,
|
|
||||||
64, 64, 255, 255,
|
|
||||||
64, 64, 64, 255,
|
|
||||||
};
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const sdl = @cImport({
|
const sdl = @cImport({
|
||||||
@cInclude("SDL3/SDL.h");
|
@cInclude("SDL3/SDL.h");
|
||||||
|
@cInclude("SDL3_image/SDL_image.h");
|
||||||
});
|
});
|
||||||
|
|
||||||
const PREFIX = "SDL_";
|
const PREFIX = "SDL_";
|
||||||
@@ -23,12 +24,8 @@ pub fn main() !void {
|
|||||||
try std.fs.cwd().createFile(output, .{});
|
try std.fs.cwd().createFile(output, .{});
|
||||||
defer file.close();
|
defer file.close();
|
||||||
|
|
||||||
const writer = file.writer();
|
var writer_buffer: [4096]u8 = undefined;
|
||||||
|
var writer = file.writer(&writer_buffer);
|
||||||
const stdout = std.io.getStdOut();
|
|
||||||
defer stdout.close();
|
|
||||||
|
|
||||||
const out = stdout.writer();
|
|
||||||
|
|
||||||
var renamed_count: usize = 0;
|
var renamed_count: usize = 0;
|
||||||
for (@typeInfo(sdl).@"struct".decls) |decl| {
|
for (@typeInfo(sdl).@"struct".decls) |decl| {
|
||||||
@@ -36,7 +33,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const new_name: []const u8 = decl.name[PREFIX.len..];
|
const new_name: []const u8 = decl.name[PREFIX.len..];
|
||||||
|
|
||||||
try writer.print(
|
try writer.interface.print(
|
||||||
\\#define {1s} {0s}
|
\\#define {1s} {0s}
|
||||||
\\
|
\\
|
||||||
, .{ decl.name, new_name });
|
, .{ decl.name, new_name });
|
||||||
@@ -45,5 +42,4 @@ pub fn main() !void {
|
|||||||
if (renamed_count == 0) {
|
if (renamed_count == 0) {
|
||||||
@panic("No SDL definitions renamed");
|
@panic("No SDL definitions renamed");
|
||||||
}
|
}
|
||||||
try out.print("[SDL Translator] {} SDL definitions renamed\n", .{renamed_count});
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user