refactor loading model from blender
This commit is contained in:
parent
4015f9fb98
commit
f42d2be83a
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,4 +1,4 @@
|
|||||||
zig-cache
|
zig-cache
|
||||||
zig-out
|
zig-out
|
||||||
*.blend1
|
*.blend1
|
||||||
src/assets/models/emulator
|
src/assets/models/emulator.glb
|
||||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +1,6 @@
|
|||||||
[submodule "libs/raylib"]
|
[submodule "libs/raylib"]
|
||||||
path = libs/raylib
|
path = libs/raylib
|
||||||
url = https://github.com/ryupold/raylib.zig
|
url = https://github.com/ryupold/raylib.zig
|
||||||
|
[submodule "libs/zgltf"]
|
||||||
|
path = libs/zgltf
|
||||||
|
url = https://github.com/kooparse/zgltf.git
|
||||||
|
@ -15,6 +15,10 @@ pub fn build(b: *std.Build) !void {
|
|||||||
.target = target
|
.target = target
|
||||||
});
|
});
|
||||||
|
|
||||||
|
exe.addModule("zgltf", b.createModule(.{
|
||||||
|
.source_file = .{ .path = "libs/zgltf/src/main.zig" },
|
||||||
|
}));
|
||||||
|
|
||||||
// Provide filenames of all files in 'src/ROMs' to program as options
|
// Provide filenames of all files in 'src/ROMs' to program as options
|
||||||
{
|
{
|
||||||
var files = std.ArrayList([]const u8).init(b.allocator);
|
var files = std.ArrayList([]const u8).init(b.allocator);
|
||||||
@ -41,9 +45,9 @@ pub fn build(b: *std.Build) !void {
|
|||||||
build_models.addFileArg(.{ .path = "src/assets/models/emulator.blend" });
|
build_models.addFileArg(.{ .path = "src/assets/models/emulator.blend" });
|
||||||
build_models.addArg("--background");
|
build_models.addArg("--background");
|
||||||
build_models.addArg("--python");
|
build_models.addArg("--python");
|
||||||
build_models.addFileArg(.{ .path = "src/assets/models/export_obj.py" });
|
build_models.addFileArg(.{ .path = "src/assets/models/export.py" });
|
||||||
build_models.addArg("--");
|
build_models.addArg("--");
|
||||||
build_models.addArg("src/assets/models/emulator");
|
build_models.addArg("src/assets/models/emulator.glb");
|
||||||
|
|
||||||
build_models_step.dependOn(&build_models.step);
|
build_models_step.dependOn(&build_models.step);
|
||||||
exe.step.dependOn(build_models_step);
|
exe.step.dependOn(build_models_step);
|
||||||
|
@ -6,5 +6,9 @@
|
|||||||
.url = "https://github.com/raysan5/raylib/archive/6094869e3e845e90e1e8ae41b98e889fb3e13e78.tar.gz",
|
.url = "https://github.com/raysan5/raylib/archive/6094869e3e845e90e1e8ae41b98e889fb3e13e78.tar.gz",
|
||||||
.hash = "12203b7a16bcf8d7fe4c9990a46d92b6f2e35531a4b82eb3bdf8ba4a0dbcc5f21415",
|
.hash = "12203b7a16bcf8d7fe4c9990a46d92b6f2e35531a4b82eb3bdf8ba4a0dbcc5f21415",
|
||||||
},
|
},
|
||||||
|
.zgltf = .{
|
||||||
|
.url = "https://github.com/kooparse/zgltf/archive/f9ed05023db75484333b6c7125a8c02a99cf3a14.tar.gz",
|
||||||
|
.hash = "12207cef14c513e160a039ddd470437ed0cd663e37a5710f9cc36334c3d21dc19ffe",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
1
libs/zgltf
Submodule
1
libs/zgltf
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit f9ed05023db75484333b6c7125a8c02a99cf3a14
|
Binary file not shown.
13
src/assets/models/export.py
Normal file
13
src/assets/models/export.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import bpy
|
||||||
|
import sys
|
||||||
|
|
||||||
|
argv = sys.argv
|
||||||
|
argv = argv[argv.index("--") + 1:]
|
||||||
|
assert len(argv) >= 1
|
||||||
|
|
||||||
|
output_path = argv[0]
|
||||||
|
assert output_path.endswith(".gltf") or output_path.endswith(".glb")
|
||||||
|
bpy.ops.export_scene.gltf(
|
||||||
|
filepath=output_path,
|
||||||
|
use_visible=True,
|
||||||
|
)
|
@ -1,128 +0,0 @@
|
|||||||
import bpy
|
|
||||||
from os import path
|
|
||||||
import os
|
|
||||||
from dataclasses import dataclass
|
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class OBJMetadata:
|
|
||||||
materials: list[str]
|
|
||||||
objects: list[str]
|
|
||||||
|
|
||||||
D = bpy.data
|
|
||||||
C = bpy.context
|
|
||||||
|
|
||||||
def select_one_object(obj):
|
|
||||||
bpy.ops.object.select_all(action='DESELECT')
|
|
||||||
bpy.context.view_layer.objects.active = obj # type: ignore
|
|
||||||
obj.select_set(True)
|
|
||||||
|
|
||||||
def iter_buttons():
|
|
||||||
object_names = D.objects.keys()
|
|
||||||
assert object_names
|
|
||||||
for name in object_names:
|
|
||||||
if name.startswith("Buttons "):
|
|
||||||
yield D.objects[name]
|
|
||||||
|
|
||||||
def extract_metadata(obj_path):
|
|
||||||
mtl_filename = None
|
|
||||||
objects = []
|
|
||||||
with open(obj_path, "r") as f:
|
|
||||||
for line in f.readlines():
|
|
||||||
if line.startswith("mtllib"):
|
|
||||||
mtl_filename = line.removeprefix("mtllib ")
|
|
||||||
elif line.startswith("o"):
|
|
||||||
object_name = line.strip().removeprefix("o ")
|
|
||||||
objects.append(object_name)
|
|
||||||
|
|
||||||
materials = []
|
|
||||||
if mtl_filename:
|
|
||||||
mtl_path = path.join(path.dirname(obj_path), mtl_filename).strip()
|
|
||||||
with open(mtl_path, "r") as f:
|
|
||||||
for line in f.readlines():
|
|
||||||
if not line.startswith("newmtl"): continue
|
|
||||||
material_name = line.strip().removeprefix("newmtl ")
|
|
||||||
materials.append(material_name)
|
|
||||||
|
|
||||||
return OBJMetadata(materials, objects)
|
|
||||||
|
|
||||||
def write_list_to_file(filename, values):
|
|
||||||
with open(filename, "w") as f:
|
|
||||||
for i in range(len(values)):
|
|
||||||
if i > 0: f.write("\n")
|
|
||||||
f.writelines(values[i])
|
|
||||||
|
|
||||||
def main(output_folder):
|
|
||||||
if not path.exists(output_folder):
|
|
||||||
os.mkdir(output_folder)
|
|
||||||
|
|
||||||
export_options = {
|
|
||||||
"use_triangles": True,
|
|
||||||
"use_materials": True,
|
|
||||||
"use_normals": True,
|
|
||||||
"use_vertex_groups": True,
|
|
||||||
"path_mode": "RELATIVE",
|
|
||||||
}
|
|
||||||
|
|
||||||
bpy.ops.object.select_all(action="SELECT")
|
|
||||||
for btn in iter_buttons():
|
|
||||||
btn.select_set(False)
|
|
||||||
|
|
||||||
bpy.ops.export_scene.obj(
|
|
||||||
filepath=path.join(output_folder, "static_model.obj"),
|
|
||||||
use_selection=True,
|
|
||||||
**export_options
|
|
||||||
)
|
|
||||||
|
|
||||||
metadata = extract_metadata(path.join(output_folder, "static_model.obj"))
|
|
||||||
write_list_to_file(path.join(output_folder, "static_model.mtls.txt"), metadata.materials)
|
|
||||||
# write_list_to_file("static_model.objs.txt", metadata.objects)
|
|
||||||
|
|
||||||
if not path.exists(path.join(output_folder, "buttons")):
|
|
||||||
os.mkdir(path.join(output_folder, "buttons"))
|
|
||||||
|
|
||||||
object_names = D.objects.keys()
|
|
||||||
assert object_names
|
|
||||||
for name in object_names:
|
|
||||||
if name.startswith("Buttons "):
|
|
||||||
button_name = name.removeprefix("Buttons ")
|
|
||||||
button_obj_path = path.join(output_folder, f"buttons/Button {button_name}.obj")
|
|
||||||
button_mtl_path = path.join(output_folder, f"buttons/Button {button_name}.mtl")
|
|
||||||
select_one_object(D.objects[name])
|
|
||||||
bpy.ops.export_scene.obj(
|
|
||||||
filepath=button_obj_path,
|
|
||||||
use_selection=True,
|
|
||||||
**export_options
|
|
||||||
)
|
|
||||||
|
|
||||||
tmp_file = "/tmp/blender_export"
|
|
||||||
with open(button_obj_path, "r") as src:
|
|
||||||
with open(tmp_file, "w") as dst:
|
|
||||||
for line in src.readlines():
|
|
||||||
if line.startswith("mtllib"):
|
|
||||||
dst.write("mtllib Button.mtl\n")
|
|
||||||
else:
|
|
||||||
dst.write(line)
|
|
||||||
os.remove(button_obj_path)
|
|
||||||
shutil.move(tmp_file, button_obj_path)
|
|
||||||
|
|
||||||
if button_name == "0":
|
|
||||||
os.rename(path.join(output_folder, "buttons/Button 0.mtl"), path.join(output_folder, "buttons/Button.mtl"))
|
|
||||||
else:
|
|
||||||
os.remove(button_mtl_path)
|
|
||||||
|
|
||||||
power_switch = D.objects["Power switch"]
|
|
||||||
select_one_object(power_switch)
|
|
||||||
bpy.ops.export_scene.obj(
|
|
||||||
filepath=path.join(output_folder, "power-switch.obj"),
|
|
||||||
use_selection=True,
|
|
||||||
**export_options
|
|
||||||
)
|
|
||||||
|
|
||||||
argv = sys.argv
|
|
||||||
argv = argv[argv.index("--") + 1:]
|
|
||||||
assert len(argv) >= 1
|
|
||||||
main(*argv)
|
|
||||||
|
|
||||||
|
|
319
src/emulator-model.zig
Normal file
319
src/emulator-model.zig
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
const Self = @This();
|
||||||
|
const Gltf = @import("zgltf");
|
||||||
|
const rl = @import("raylib");
|
||||||
|
const std = @import("std");
|
||||||
|
const RaylibChip = @import("raylib-chip.zig");
|
||||||
|
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
allocator: Allocator,
|
||||||
|
|
||||||
|
materials: []rl.Material,
|
||||||
|
|
||||||
|
models: std.ArrayList(rl.Model),
|
||||||
|
buttons: [16]rl.Model,
|
||||||
|
power_switch: rl.Model,
|
||||||
|
|
||||||
|
bbox: rl.BoundingBox,
|
||||||
|
position: rl.Vector3,
|
||||||
|
screen_texture: rl.RenderTexture2D,
|
||||||
|
|
||||||
|
rl_chip: *const RaylibChip,
|
||||||
|
|
||||||
|
fn getExtensionByMimeType(mime_type: []const u8) ?[:0]const u8 {
|
||||||
|
if (std.mem.eql(u8, mime_type, "image/png")) {
|
||||||
|
return ".png\x00";
|
||||||
|
} else if (std.mem.eql(u8, mime_type, "image/jpg")) {
|
||||||
|
return ".jpg\x00";
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loadGltfImage(image: Gltf.Image) rl.Image {
|
||||||
|
assert(image.uri == null);
|
||||||
|
assert(image.data != null);
|
||||||
|
assert(image.mime_type != null);
|
||||||
|
|
||||||
|
const image_data = image.data.?;
|
||||||
|
const mime_type = image.mime_type.?;
|
||||||
|
|
||||||
|
const file_type = getExtensionByMimeType(mime_type);
|
||||||
|
assert(file_type != null);
|
||||||
|
|
||||||
|
return rl.LoadImageFromMemory(file_type.?, @ptrCast(image_data.ptr), @intCast(image_data.len));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loadGltfMaterial(data: Gltf.Data, material: Gltf.Material) !rl.Material {
|
||||||
|
var rl_material = rl.LoadMaterialDefault();
|
||||||
|
var albedo_map: *rl.MaterialMap = &rl_material.maps.?[@intFromEnum(rl.MaterialMapIndex.MATERIAL_MAP_ALBEDO)];
|
||||||
|
|
||||||
|
if (material.metallic_roughness.base_color_texture) |base_color_texture| {
|
||||||
|
const texture = data.textures.items[base_color_texture.index];
|
||||||
|
const image = data.images.items[texture.source.?];
|
||||||
|
|
||||||
|
const rl_image = loadGltfImage(image);
|
||||||
|
defer rl.UnloadImage(rl_image);
|
||||||
|
albedo_map.texture = rl.LoadTextureFromImage(rl_image);
|
||||||
|
}
|
||||||
|
|
||||||
|
albedo_map.color.r = @intFromFloat(material.metallic_roughness.base_color_factor[0]*255);
|
||||||
|
albedo_map.color.g = @intFromFloat(material.metallic_roughness.base_color_factor[1]*255);
|
||||||
|
albedo_map.color.b = @intFromFloat(material.metallic_roughness.base_color_factor[2]*255);
|
||||||
|
albedo_map.color.a = @intFromFloat(material.metallic_roughness.base_color_factor[3]*255);
|
||||||
|
|
||||||
|
return rl_material;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loadGltfPrimitive(allocator: Allocator, gltf: Gltf, primitive: Gltf.Primitive) !rl.Mesh {
|
||||||
|
assert(primitive.mode == .triangles);
|
||||||
|
var rl_mesh = std.mem.zeroes(rl.Mesh);
|
||||||
|
|
||||||
|
var bin = gltf.glb_binary.?;
|
||||||
|
var f32_buffer = std.ArrayList(f32).init(allocator);
|
||||||
|
defer f32_buffer.deinit();
|
||||||
|
|
||||||
|
for (primitive.attributes.items) |attribute| {
|
||||||
|
switch (attribute) {
|
||||||
|
.position => |accessor_index| {
|
||||||
|
const accessor = gltf.data.accessors.items[accessor_index];
|
||||||
|
assert(accessor.component_type == .float);
|
||||||
|
assert(accessor.type == .vec3);
|
||||||
|
f32_buffer.clearAndFree();
|
||||||
|
gltf.getDataFromBufferView(f32, &f32_buffer, accessor, bin);
|
||||||
|
|
||||||
|
var vertices = try allocator.dupe(f32, f32_buffer.items);
|
||||||
|
rl_mesh.vertexCount = @intCast(accessor.count);
|
||||||
|
rl_mesh.vertices = @ptrCast(vertices);
|
||||||
|
},
|
||||||
|
.normal => |accessor_index| {
|
||||||
|
const accessor = gltf.data.accessors.items[accessor_index];
|
||||||
|
assert(accessor.component_type == .float);
|
||||||
|
assert(accessor.type == .vec3);
|
||||||
|
f32_buffer.clearRetainingCapacity();
|
||||||
|
gltf.getDataFromBufferView(f32, &f32_buffer, accessor, bin);
|
||||||
|
|
||||||
|
var normals = try allocator.dupe(f32, f32_buffer.items);
|
||||||
|
rl_mesh.normals = @ptrCast(normals);
|
||||||
|
},
|
||||||
|
.tangent => |accessor_index| {
|
||||||
|
const accessor = gltf.data.accessors.items[accessor_index];
|
||||||
|
assert(accessor.component_type == .float);
|
||||||
|
assert(accessor.type == .vec4);
|
||||||
|
f32_buffer.clearRetainingCapacity();
|
||||||
|
gltf.getDataFromBufferView(f32, &f32_buffer, accessor, bin);
|
||||||
|
|
||||||
|
var tangents = try allocator.dupe(f32, f32_buffer.items);
|
||||||
|
rl_mesh.tangents = @ptrCast(tangents);
|
||||||
|
},
|
||||||
|
.texcoord => |accessor_index| {
|
||||||
|
const accessor = gltf.data.accessors.items[accessor_index];
|
||||||
|
assert(accessor.component_type == .float);
|
||||||
|
assert(accessor.type == .vec2);
|
||||||
|
f32_buffer.clearRetainingCapacity();
|
||||||
|
gltf.getDataFromBufferView(f32, &f32_buffer, accessor, bin);
|
||||||
|
|
||||||
|
var texcoords = try allocator.dupe(f32, f32_buffer.items);
|
||||||
|
rl_mesh.texcoords = @ptrCast(texcoords);
|
||||||
|
},
|
||||||
|
else => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (primitive.indices) |accessor_index| {
|
||||||
|
const accessor = gltf.data.accessors.items[accessor_index];
|
||||||
|
rl_mesh.triangleCount = @divExact(accessor.count, 3);
|
||||||
|
const accessor_count: usize = @intCast(accessor.count);
|
||||||
|
|
||||||
|
var indices = try allocator.alloc(u16, accessor_count);
|
||||||
|
rl_mesh.indices = @ptrCast(indices);
|
||||||
|
|
||||||
|
if (accessor.component_type == Gltf.ComponentType.unsigned_short) {
|
||||||
|
var u16_buffer = std.ArrayList(u16).init(allocator);
|
||||||
|
defer u16_buffer.deinit();
|
||||||
|
gltf.getDataFromBufferView(u16, &u16_buffer, accessor, bin);
|
||||||
|
|
||||||
|
@memcpy(indices, u16_buffer.items);
|
||||||
|
} else if (accessor.component_type == Gltf.ComponentType.unsigned_integer) {
|
||||||
|
var u32_buffer = std.ArrayList(u32).init(allocator);
|
||||||
|
defer u32_buffer.deinit();
|
||||||
|
gltf.getDataFromBufferView(u32, &u32_buffer, accessor, bin);
|
||||||
|
|
||||||
|
for (0..accessor_count) |i| {
|
||||||
|
indices[i] = @truncate(u32_buffer.items[i]);
|
||||||
|
}
|
||||||
|
rl.TraceLog(@intFromEnum(rl.TraceLogLevel.LOG_WARNING), "MODEL: Indices data converted from u32 to u16, possible loss of data");
|
||||||
|
} else {
|
||||||
|
@panic("Unknown GLTF primitives indices component type. Use u16 or u32");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rl_mesh.triangleCount = @divExact(rl_mesh.vertexCount, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.UploadMesh(@ptrCast(&rl_mesh), false);
|
||||||
|
return rl_mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(allocator: Allocator, rl_chip: *const RaylibChip) !Self {
|
||||||
|
var gltf = Gltf.init(allocator);
|
||||||
|
defer gltf.deinit();
|
||||||
|
try gltf.parse(@embedFile("assets/models/emulator.glb"));
|
||||||
|
|
||||||
|
const scene = gltf.data.scenes.items[gltf.data.scene.?];
|
||||||
|
const scene_nodes: std.ArrayList(Gltf.Index) = scene.nodes.?;
|
||||||
|
|
||||||
|
const material_count: usize = @intCast(gltf.data.materials.items.len);
|
||||||
|
var materials = try allocator.alloc(rl.Material, material_count + 1);
|
||||||
|
materials[0] = rl.LoadMaterialDefault();
|
||||||
|
for (0..material_count, gltf.data.materials.items) |i, material| {
|
||||||
|
materials[i+1] = try loadGltfMaterial(gltf.data, material);
|
||||||
|
}
|
||||||
|
|
||||||
|
const screen_texture = rl.LoadRenderTexture(rl_chip.chip.display_width, rl_chip.chip.display_height);
|
||||||
|
|
||||||
|
var models = try std.ArrayList(rl.Model).initCapacity(allocator, scene_nodes.items.len);
|
||||||
|
errdefer models.deinit();
|
||||||
|
|
||||||
|
var buttons: [16]rl.Model = undefined;
|
||||||
|
var power_switch: rl.Model = undefined;
|
||||||
|
|
||||||
|
for (scene_nodes.items) |node_index| {
|
||||||
|
const node = gltf.data.nodes.items[node_index];
|
||||||
|
if (node.mesh == null) continue;
|
||||||
|
|
||||||
|
const transform = Gltf.getGlobalTransform(&gltf.data, node);
|
||||||
|
|
||||||
|
var model = std.mem.zeroes(rl.Model);
|
||||||
|
model.transform = rl.Matrix{
|
||||||
|
.m0 = transform[0][0],
|
||||||
|
.m4 = transform[1][0],
|
||||||
|
.m8 = transform[2][0],
|
||||||
|
.m12 = transform[3][0],
|
||||||
|
|
||||||
|
.m1 = transform[0][1],
|
||||||
|
.m5 = transform[1][1],
|
||||||
|
.m9 = transform[2][1],
|
||||||
|
.m13 = transform[3][1],
|
||||||
|
|
||||||
|
.m2 = transform[0][2],
|
||||||
|
.m6 = transform[1][2],
|
||||||
|
.m10 = transform[2][2],
|
||||||
|
.m14 = transform[3][2],
|
||||||
|
|
||||||
|
.m3 = transform[0][3],
|
||||||
|
.m7 = transform[1][3],
|
||||||
|
.m11 = transform[2][3],
|
||||||
|
.m15 = transform[3][3]
|
||||||
|
};
|
||||||
|
model.materials = @ptrCast(materials.ptr);
|
||||||
|
model.materialCount = @intCast(materials.len);
|
||||||
|
|
||||||
|
const mesh = gltf.data.meshes.items[node.mesh.?];
|
||||||
|
const primitives: []Gltf.Primitive = mesh.primitives.items;
|
||||||
|
|
||||||
|
var meshes = try allocator.alloc(rl.Mesh, primitives.len);
|
||||||
|
var mesh_material = try allocator.alloc(i32, primitives.len);
|
||||||
|
model.meshes = @ptrCast(meshes.ptr);
|
||||||
|
model.meshCount = @intCast(primitives.len);
|
||||||
|
model.meshMaterial = @ptrCast(mesh_material.ptr);
|
||||||
|
@memset(mesh_material, 0);
|
||||||
|
|
||||||
|
for (0.., primitives) |j, primitive| {
|
||||||
|
meshes[j] = try loadGltfPrimitive(allocator, gltf, primitive);
|
||||||
|
|
||||||
|
if (primitive.material) |mtl| {
|
||||||
|
mesh_material[j] = @intCast(mtl+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, node.name, "Power switch")) {
|
||||||
|
power_switch = model;
|
||||||
|
} else if (std.mem.startsWith(u8, node.name, "Buttons ")) {
|
||||||
|
var space = std.mem.indexOfScalar(u8, node.name, ' ').?;
|
||||||
|
const button_idx = try std.fmt.parseInt(usize, node.name[space+1..], 16);
|
||||||
|
buttons[button_idx] = model;
|
||||||
|
} else {
|
||||||
|
models.appendAssumeCapacity(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Link screen render target to shader
|
||||||
|
var screen_mtl_idx: ?usize = null;
|
||||||
|
for (1.., gltf.data.materials.items) |idx, mtl| {
|
||||||
|
if (std.mem.eql(u8, mtl.name, "Screen")) {
|
||||||
|
screen_mtl_idx = idx;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rl.SetMaterialTexture(@ptrCast(&materials[screen_mtl_idx.?]), rl.MATERIAL_MAP_DIFFUSE, screen_texture.texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Self{
|
||||||
|
.allocator = allocator,
|
||||||
|
.materials = materials,
|
||||||
|
|
||||||
|
.models = models,
|
||||||
|
.buttons = buttons,
|
||||||
|
.power_switch = power_switch,
|
||||||
|
|
||||||
|
.bbox = rl.GetModelBoundingBox(models.items[0]),
|
||||||
|
.screen_texture = screen_texture,
|
||||||
|
.position = rl.Vector3{ .x = 0, .y = 0, .z = 0 },
|
||||||
|
.rl_chip = rl_chip,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setShader(self: *Self, shader: rl.Shader) void {
|
||||||
|
for (self.materials) |*mtl| {
|
||||||
|
mtl.shader = shader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.allocator.free(self.materials);
|
||||||
|
|
||||||
|
// rl.UnloadModel(self.static_model);
|
||||||
|
// for (self.button_models) |btn_model| {
|
||||||
|
// rl.UnloadModel(btn_model);
|
||||||
|
// }
|
||||||
|
rl.UnloadRenderTexture(self.screen_texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updateDisplay(self: *Self) void {
|
||||||
|
rl.BeginTextureMode(self.screen_texture);
|
||||||
|
self.rl_chip.render();
|
||||||
|
rl.EndTextureMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isMouseOverPowerSwitch(self: *const Self) bool {
|
||||||
|
_ = self;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setPowerSwitch(self: *const Self, enabled: bool) void {
|
||||||
|
_ = enabled;
|
||||||
|
_ = self;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getPowerSwitch(self: *const Self) bool {
|
||||||
|
_ = self;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(self: *Self) void {
|
||||||
|
for (self.buttons, 0..) |button, i| {
|
||||||
|
var position = self.position;
|
||||||
|
if (self.rl_chip.chip.is_input_pressed(@intCast(i))) {
|
||||||
|
position.z += 0.035;
|
||||||
|
}
|
||||||
|
rl.DrawModel(button, position, 1.0, rl.WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (self.models.items) |model| {
|
||||||
|
rl.DrawModel(model, self.position, 1.0, rl.WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.DrawModel(self.power_switch, self.position, 1.0, rl.WHITE);
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
const Self = @This();
|
const Self = @This();
|
||||||
const rl = @import("raylib");
|
const rl = @import("raylib");
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Gltf = @import("zgltf");
|
||||||
const GlobalContext = @import("./global-context.zig");
|
const GlobalContext = @import("./global-context.zig");
|
||||||
|
const EmulatorModel = @import("./emulator-model.zig");
|
||||||
|
|
||||||
const ChipContext = @import("chip.zig");
|
const ChipContext = @import("chip.zig");
|
||||||
const RaylibChip = @import("raylib-chip.zig");
|
const RaylibChip = @import("raylib-chip.zig");
|
||||||
@ -13,11 +15,6 @@ const StringList = std.ArrayList([]const u8);
|
|||||||
ctx: *GlobalContext,
|
ctx: *GlobalContext,
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
|
||||||
// model: rl.Model,
|
|
||||||
// model_bbox: rl.BoundingBox,
|
|
||||||
// model_position: rl.Vector3,
|
|
||||||
// model_buttons: [16]rl.Model,
|
|
||||||
|
|
||||||
emulator: EmulatorModel,
|
emulator: EmulatorModel,
|
||||||
|
|
||||||
camera_turn_vel: rl.Vector3 = rl.Vector3{ .x = 0, .y = 0, .z = 0 },
|
camera_turn_vel: rl.Vector3 = rl.Vector3{ .x = 0, .y = 0, .z = 0 },
|
||||||
@ -31,9 +28,7 @@ chip: *ChipContext,
|
|||||||
raylib_chip: *RaylibChip,
|
raylib_chip: *RaylibChip,
|
||||||
chip_sound: rl.Sound,
|
chip_sound: rl.Sound,
|
||||||
|
|
||||||
// screen_texture: rl.RenderTexture2D,
|
pub fn genSinWave(wave: *rl.Wave, frequency: f32) void {
|
||||||
|
|
||||||
pub fn gen_sin_wave(wave: *rl.Wave, frequency: f32) void {
|
|
||||||
assert(wave.sampleSize == 16); // Only 16 bits are supported
|
assert(wave.sampleSize == 16); // Only 16 bits are supported
|
||||||
|
|
||||||
const sample_rate: f32 = @floatFromInt(wave.sampleRate);
|
const sample_rate: f32 = @floatFromInt(wave.sampleRate);
|
||||||
@ -115,7 +110,7 @@ const Light = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fn get_camera_projection(camera: *const rl.Camera3D) rl.Matrix {
|
fn getCameraProjection(camera: *const rl.Camera3D) rl.Matrix {
|
||||||
const screen_width: f32 = @floatFromInt(rl.GetScreenWidth());
|
const screen_width: f32 = @floatFromInt(rl.GetScreenWidth());
|
||||||
const screen_height: f32 = @floatFromInt(rl.GetScreenHeight());
|
const screen_height: f32 = @floatFromInt(rl.GetScreenHeight());
|
||||||
|
|
||||||
@ -132,7 +127,7 @@ fn get_camera_projection(camera: *const rl.Camera3D) rl.Matrix {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_screen_direction_from_camera(mat_proj: *const rl.Matrix, mat_view: *const rl.Matrix, point: rl.Vector2) rl.Vector3 {
|
fn getScreenDirectionFromCamera(mat_proj: *const rl.Matrix, mat_view: *const rl.Matrix, point: rl.Vector2) rl.Vector3 {
|
||||||
const screen_width: f32 = @floatFromInt(rl.GetScreenWidth());
|
const screen_width: f32 = @floatFromInt(rl.GetScreenWidth());
|
||||||
const screen_height: f32 = @floatFromInt(rl.GetScreenHeight());
|
const screen_height: f32 = @floatFromInt(rl.GetScreenHeight());
|
||||||
|
|
||||||
@ -145,7 +140,7 @@ fn get_screen_direction_from_camera(mat_proj: *const rl.Matrix, mat_view: *const
|
|||||||
return rl.Vector3Subtract(far_point, near_point).normalize();
|
return rl.Vector3Subtract(far_point, near_point).normalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_preffered_distance_to_box(camera: *const rl.Camera3D, box: rl.BoundingBox) f32 {
|
fn getPrefferedDistanceToBox(camera: *const rl.Camera3D, box: rl.BoundingBox) f32 {
|
||||||
const screen_width: f32 = @floatFromInt(rl.GetScreenWidth());
|
const screen_width: f32 = @floatFromInt(rl.GetScreenWidth());
|
||||||
const screen_height: f32 = @floatFromInt(rl.GetScreenHeight());
|
const screen_height: f32 = @floatFromInt(rl.GetScreenHeight());
|
||||||
const margin = @min(screen_width, screen_height)*0.1;
|
const margin = @min(screen_width, screen_height)*0.1;
|
||||||
@ -156,106 +151,19 @@ fn get_preffered_distance_to_box(camera: *const rl.Camera3D, box: rl.BoundingBox
|
|||||||
// const model_screen_width = box_size.x * max_model_scale;
|
// const model_screen_width = box_size.x * max_model_scale;
|
||||||
const model_screen_height = box_size.y * max_model_scale;
|
const model_screen_height = box_size.y * max_model_scale;
|
||||||
|
|
||||||
const mat_proj = get_camera_projection(camera);
|
const mat_proj = getCameraProjection(camera);
|
||||||
const mat_view = rl.MatrixIdentity(); // rl.MatrixLookAt(camera.position, camera.target, camera.up);
|
const mat_view = rl.MatrixIdentity(); // rl.MatrixLookAt(camera.position, camera.target, camera.up);
|
||||||
|
|
||||||
const screen_middle = rl.Vector2{ .x = screen_width/2, .y = screen_height/2 };
|
const screen_middle = rl.Vector2{ .x = screen_width/2, .y = screen_height/2 };
|
||||||
const box_top_middle = screen_middle.add(.{ .y = -model_screen_height/2 });
|
const box_top_middle = screen_middle.add(.{ .y = -model_screen_height/2 });
|
||||||
|
|
||||||
const middle_dir = get_screen_direction_from_camera(&mat_proj, &mat_view, screen_middle);
|
const middle_dir = getScreenDirectionFromCamera(&mat_proj, &mat_view, screen_middle);
|
||||||
const top_middle_dir = get_screen_direction_from_camera(&mat_proj, &mat_view, box_top_middle);
|
const top_middle_dir = getScreenDirectionFromCamera(&mat_proj, &mat_view, box_top_middle);
|
||||||
const angle = top_middle_dir.angleBetween(middle_dir);
|
const angle = top_middle_dir.angleBetween(middle_dir);
|
||||||
const distance = 1/@tan(angle) * (box_size.y/2) + box_size.z/4;
|
const distance = 1/@tan(angle) * (box_size.y/2) + box_size.z/4;
|
||||||
return distance;
|
return distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_line_in_file(filename: []const u8, target: []const u8) !?usize {
|
|
||||||
const file = try std.fs.cwd().openFile(filename, .{ .mode = .read_only });
|
|
||||||
defer file.close();
|
|
||||||
|
|
||||||
const reader = file.reader();
|
|
||||||
var buf: [512]u8 = undefined;
|
|
||||||
var i: usize = 0;
|
|
||||||
while (true) {
|
|
||||||
const line = try reader.readUntilDelimiterOrEof(&buf, '\n');
|
|
||||||
if (line == null) { continue; }
|
|
||||||
|
|
||||||
if (std.mem.eql(u8, line.?, target)) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EmulatorModel = struct {
|
|
||||||
static_model: rl.Model,
|
|
||||||
bbox: rl.BoundingBox,
|
|
||||||
button_models: [16]rl.Model,
|
|
||||||
|
|
||||||
position: rl.Vector3,
|
|
||||||
screen_texture: rl.RenderTexture2D,
|
|
||||||
|
|
||||||
rl_chip: *const RaylibChip,
|
|
||||||
|
|
||||||
pub fn init(comptime location: []const u8, rl_chip: *const RaylibChip, shader: rl.Shader) !EmulatorModel {
|
|
||||||
var button_models = [1]rl.Model{ undefined } ** 16;
|
|
||||||
inline for ([_][]const u8{ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" }, 0..) |letter, i| {
|
|
||||||
button_models[i] = rl.LoadModel(location ++ "/buttons/Button " ++ letter ++ ".obj");
|
|
||||||
}
|
|
||||||
|
|
||||||
var static_model = rl.LoadModel(location ++ "/static_model.obj");
|
|
||||||
|
|
||||||
for (0..@intCast(static_model.materialCount)) |i| {
|
|
||||||
static_model.materials.?[i].shader = shader;
|
|
||||||
}
|
|
||||||
for (button_models) |btn_model| {
|
|
||||||
for (0..@intCast(btn_model.materialCount)) |i| {
|
|
||||||
btn_model.materials.?[i].shader = shader;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const screen_texture = rl.LoadRenderTexture(rl_chip.chip.display_width, rl_chip.chip.display_height);
|
|
||||||
const screen_mtl_idx = try find_line_in_file(location ++ "/static_model.mtls.txt", "Screen");
|
|
||||||
rl.SetMaterialTexture(@ptrCast(&static_model.materials.?[screen_mtl_idx.?]), rl.MATERIAL_MAP_DIFFUSE, screen_texture.texture);
|
|
||||||
|
|
||||||
return EmulatorModel{
|
|
||||||
.static_model = static_model,
|
|
||||||
.bbox = rl.GetModelBoundingBox(static_model),
|
|
||||||
.button_models = button_models,
|
|
||||||
.screen_texture = screen_texture,
|
|
||||||
.position = rl.Vector3{ .x = 0, .y = 0, .z = 0 },
|
|
||||||
.rl_chip = rl_chip
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *EmulatorModel) void {
|
|
||||||
rl.UnloadModel(self.static_model);
|
|
||||||
for (self.button_models) |btn_model| {
|
|
||||||
rl.UnloadModel(btn_model);
|
|
||||||
}
|
|
||||||
rl.UnloadRenderTexture(self.screen_texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_display(self: *EmulatorModel) void {
|
|
||||||
rl.BeginTextureMode(self.screen_texture);
|
|
||||||
self.rl_chip.render();
|
|
||||||
rl.EndTextureMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw(self: *EmulatorModel) void {
|
|
||||||
rl.DrawModel(self.static_model, self.position, 1.0, rl.WHITE);
|
|
||||||
for (self.button_models, 0..) |btn_model, i| {
|
|
||||||
var position = self.position;
|
|
||||||
if (self.rl_chip.chip.is_input_pressed(@intCast(i))) {
|
|
||||||
position.z += 0.035;
|
|
||||||
}
|
|
||||||
rl.DrawModel(btn_model, position, 1.0, rl.WHITE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, ctx: *GlobalContext) !Self {
|
pub fn init(allocator: Allocator, ctx: *GlobalContext) !Self {
|
||||||
const shader = rl.LoadShader("src/shaders/main.vs", "src/shaders/main.fs");
|
const shader = rl.LoadShader("src/shaders/main.vs", "src/shaders/main.fs");
|
||||||
shader.locs.?[@intFromEnum(rl.ShaderLocationIndex.SHADER_LOC_VECTOR_VIEW)] = rl.GetShaderLocation(shader, "viewPos");
|
shader.locs.?[@intFromEnum(rl.ShaderLocationIndex.SHADER_LOC_VECTOR_VIEW)] = rl.GetShaderLocation(shader, "viewPos");
|
||||||
@ -280,17 +188,20 @@ pub fn init(allocator: Allocator, ctx: *GlobalContext) !Self {
|
|||||||
.data = @ptrCast(data.ptr),
|
.data = @ptrCast(data.ptr),
|
||||||
};
|
};
|
||||||
|
|
||||||
gen_sin_wave(&chip_wave, 440);
|
genSinWave(&chip_wave, 440);
|
||||||
var chip_sound = rl.LoadSoundFromWave(chip_wave);
|
var chip_sound = rl.LoadSoundFromWave(chip_wave);
|
||||||
rl.SetSoundVolume(chip_sound, 0.2);
|
rl.SetSoundVolume(chip_sound, 0.2);
|
||||||
|
|
||||||
var raylib_chip = try allocator.create(RaylibChip);
|
var raylib_chip = try allocator.create(RaylibChip);
|
||||||
raylib_chip.* = RaylibChip.init(chip, chip_sound);
|
raylib_chip.* = RaylibChip.init(chip, chip_sound);
|
||||||
|
|
||||||
|
var emulator = try EmulatorModel.init(allocator, raylib_chip);
|
||||||
|
emulator.setShader(shader);
|
||||||
|
|
||||||
return Self {
|
return Self {
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.ctx = ctx,
|
.ctx = ctx,
|
||||||
.emulator = try EmulatorModel.init("src/assets/models/emulator", raylib_chip, shader),
|
.emulator = emulator,
|
||||||
.shader = shader,
|
.shader = shader,
|
||||||
.lights = .{light1, light2},
|
.lights = .{light1, light2},
|
||||||
|
|
||||||
@ -308,19 +219,19 @@ pub fn deinit(self: *Self) void {
|
|||||||
self.allocator.destroy(self.chip);
|
self.allocator.destroy(self.chip);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_camera(self: *Self, dt: f32) void {
|
fn updateCamera(self: *Self, dt: f32) void {
|
||||||
const mouse_delta = rl.GetMouseDelta();
|
const mouse_delta = rl.GetMouseDelta();
|
||||||
const camera = &self.ctx.camera;
|
const camera = &self.ctx.camera;
|
||||||
const emulator = &self.emulator;
|
const emulator = &self.emulator;
|
||||||
|
|
||||||
if (rl.IsWindowResized()) {
|
if (rl.IsWindowResized()) {
|
||||||
const distance = get_preffered_distance_to_box(camera, emulator.bbox);
|
const distance = getPrefferedDistanceToBox(camera, emulator.bbox);
|
||||||
const direction = camera.position.sub(emulator.position).normalize();
|
const direction = camera.position.sub(emulator.position).normalize();
|
||||||
camera.position = emulator.position.add(direction.scale(distance));
|
camera.position = emulator.position.add(direction.scale(distance));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rl.Vector3Equals(camera.position, rl.Vector3Zero()) == 1) {
|
if (rl.Vector3Equals(camera.position, rl.Vector3Zero()) == 1) {
|
||||||
const distance = get_preffered_distance_to_box(camera, self.emulator.bbox);
|
const distance = getPrefferedDistanceToBox(camera, self.emulator.bbox);
|
||||||
camera.target = emulator.position;
|
camera.target = emulator.position;
|
||||||
camera.position = emulator.position.sub(rl.Vector3.new(0, 0, 1).scale(distance));
|
camera.position = emulator.position.sub(rl.Vector3.new(0, 0, 1).scale(distance));
|
||||||
}
|
}
|
||||||
@ -383,7 +294,7 @@ fn update_camera(self: *Self, dt: f32) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(self: *Self, dt: f32) void {
|
pub fn update(self: *Self, dt: f32) void {
|
||||||
self.update_camera(dt);
|
self.updateCamera(dt);
|
||||||
|
|
||||||
const camera = &self.ctx.camera;
|
const camera = &self.ctx.camera;
|
||||||
const cameraPos = [3]f32{ camera.position.x, camera.position.y, camera.position.z };
|
const cameraPos = [3]f32{ camera.position.x, camera.position.y, camera.position.z };
|
||||||
@ -392,7 +303,7 @@ pub fn update(self: *Self, dt: f32) void {
|
|||||||
light.update_values(self.shader);
|
light.update_values(self.shader);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.emulator.update_display();
|
self.emulator.updateDisplay();
|
||||||
|
|
||||||
// {
|
// {
|
||||||
// var matProj = rl.MatrixIdentity();
|
// var matProj = rl.MatrixIdentity();
|
||||||
@ -444,5 +355,9 @@ pub fn update(self: *Self, dt: f32) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(self: *Self) void {
|
pub fn draw(self: *Self) void {
|
||||||
self.emulator.draw();
|
rl.BeginShaderMode(self.shader);
|
||||||
|
{
|
||||||
|
self.emulator.draw();
|
||||||
|
}
|
||||||
|
rl.EndShaderMode();
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,11 @@ const ROM = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() anyerror!void {
|
pub fn main() anyerror!void {
|
||||||
const memory = try std.heap.page_allocator.alloc(u8, megabytes(5));
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
var fba = std.heap.FixedBufferAllocator.init(memory);
|
const allocator = gpa.allocator();
|
||||||
const allocator = fba.allocator();
|
defer {
|
||||||
|
// if (gpa.deinit() == .leak) @panic("Leaked memory");
|
||||||
|
}
|
||||||
|
|
||||||
rl.SetConfigFlags(rl.ConfigFlags{ .FLAG_WINDOW_RESIZABLE = true });
|
rl.SetConfigFlags(rl.ConfigFlags{ .FLAG_WINDOW_RESIZABLE = true });
|
||||||
rl.InitWindow(1024, 720, "CHIP-8");
|
rl.InitWindow(1024, 720, "CHIP-8");
|
||||||
|
Loading…
Reference in New Issue
Block a user