pub const Settings = struct { mapName: []const u8 = "map_", vertexScale: f32 = 1.0 / 32.0, rotation: core.Rotation = .{}, }; pub const ColliderSpec = struct { name: core.Name, }; pub const TBMap = struct { root: core.Entity, shapes: std.ArrayListUnmanaged(core.Name) = .{}, meshes: std.ArrayListUnmanaged(core.Entity) = .{}, // each material has its own entity colliders: std.ArrayListUnmanaged(core.Entity) = .{}, // each solid in worldspawn has it's own convex collider colliderSpecs: std.ArrayListUnmanaged(ColliderSpec) = .{}, allocator: std.mem.Allocator, pub fn create(allocator: std.mem.Allocator) !*@This() { const self = try allocator.create(@This()); core.engine_logs("creating map root"); self.* = .{ .allocator = allocator, .root = try core.createEntity(), }; _ = self.root.addComponent(core.Scene).?; return self; } var fmtBuf: [256]u8 = undefined; var fmtBuf2: [256]u8 = undefined; pub fn addMeshScene(self: *@This(), builder: *MeshBuilder) !void { const newEntity = try core.createEntity(); const scene = newEntity.addComponent(core.Scene).?; scene.setParent(self.root); const mesh = newEntity.addComponent(rend.MeshComponent).?; mesh.setMesh(builder.meshName.utf8()); const textureName = try std.fmt.bufPrint(&fmtBuf, "t_map/{s}", .{builder.materialName.utf8()}); mesh.setTexture(textureName); if (!rend.textureExists(textureName)) { var textureRef = builder.materialName.utf8(); if (std.mem.eql(u8, textureRef, "__TB_empty")) { textureRef = "texture_sample"; } try assets.load( assets.MakeImportRefOptions( "Texture", textureName, // TODO don't hardcode the root path .{ .path = try std.fmt.bufPrint(&fmtBuf2, "textures/{s}.png", .{textureRef}) }, ), ); } // if the texture Does not exist issue an asset load request try self.meshes.append(self.allocator, newEntity); } pub fn addPhysicsShape(self: *@This(), solid: QuakeMap.Solid, buildSettings: Settings) !void { var floatList: std.ArrayListUnmanaged(f32) = .{}; defer floatList.deinit(self.allocator); var vertexCount: u32 = 0; for (solid.faces.items) |face| { for (face.vertices) |vert| { // core.engine_log("adding vertex {any}", .{vert}); const position = core.Vectorf{ .x = @floatCast(-vert.data[0]), .y = @floatCast(vert.data[1]), .z = @floatCast(vert.data[2]), }; const final = buildSettings.rotation.rotateVector( position.fmul(buildSettings.vertexScale), ); try floatList.append(self.allocator, @floatCast(final.x)); try floatList.append(self.allocator, @floatCast(final.y)); try floatList.append(self.allocator, @floatCast(final.z)); vertexCount += 1; } } const settings = try physics.ConvexHullShapeSettings.create( @ptrCast(floatList.items.ptr), vertexCount, 12, ); defer settings.release(); // settings.setMaxConvexRadius(0.1); var nameBuffer: [256]u8 = undefined; const shapeName = try std.fmt.bufPrint(&nameBuffer, "t_map/worldspawn_shape_{d}", .{self.colliderSpecs.items.len}); try physics.addShape(shapeName, .{ .convexHull = settings }); try self.shapes.append(self.allocator, core.MakeName(shapeName)); try self.colliderSpecs.append(self.allocator, .{ .name = core.MakeName(shapeName), }); { const entity = try core.createEntity(); const scene = entity.addComponent(core.Scene).?; scene.setPosition(.{}); // scene.setParent(self.root); const collider = entity.addComponent(physics.PhysicsCollider).?; try collider.setupByShapeName(core.MakeName(shapeName), .{ .motion_type = .static, .object_layer = physics.ObjectLayers.non_moving, .friction = 0.8, }); try self.colliders.append(self.allocator, entity); } } pub fn destroy(self: *@This()) void { for (self.meshes.items) |*m| { m.destroy(); } self.meshes.deinit(self.allocator); for (self.colliders.items) |*c| { c.destroy(); } self.colliders.deinit(self.allocator); for (self.colliderSpecs.items) |spec| { var name = spec.name; physics.context().shapes.get(name.handle()).?.shape.release(); } self.colliderSpecs.deinit(self.allocator); self.shapes.deinit(self.allocator); self.root.destroy(); self.allocator.destroy(self); } }; // assumed to be consumed by submitMesh(). no deinit provided pub const MeshBuilder = struct { materialName: core.Name, meshName: core.Name, vertexOffset: u32 = 0, // these three use MeshAlloc meshAlloc: std.mem.Allocator, vertexList: std.ArrayListUnmanaged(rend.MeshVertex) = .{}, indexList: std.ArrayListUnmanaged(u32) = .{}, jointNames: std.ArrayListUnmanaged(JointNameEntry) = .{}, // jointNames intentionally left empty, needed for the vk_meshpool interface. // if i refactor it, this doesnt need to exist anymore, but that takes work... pub fn submitMesh(self: *@This()) !void { const newMesh: MeshUpdate = .{ .new = .{ .vertices = try self.vertexList.toOwnedSlice(self.meshAlloc), .indices = try self.indexList.toOwnedSlice(self.meshAlloc), .name = self.meshName, .jointNames = try self.jointNames.toOwnedSlice(self.meshAlloc), .skeletonName = null, }, }; try rend.renderer.pushMeshUpdate(newMesh); } pub fn addFace(self: *@This(), face: QuakeMap.Face, settings: Settings) !void { const indexStart = self.vertexOffset; const count = face.vertices.len; const uAxis: Vec3 = face.u_axis; const vAxis: Vec3 = face.v_axis; const texSizeX: f32 = 64; const texSizeY: f32 = 64; const d10 = face.vertices[1].sub(face.vertices[0]); const d21 = face.vertices[2].sub(face.vertices[1]); const n = d10.cross(d21); var normal = core.Vectorf{ .x = @floatCast(-n.data[0]), .y = @floatCast(n.data[1]), .z = @floatCast(n.data[2]), }; normal = normal.normalize(); // normal = .{ .y = 1.0 }; normal = settings.rotation.rotateVector(normal); if (normal.dot(.{ .y = 1.0 }) < 0.5) { // return; } // append all vertices to the vertex list for (face.vertices) |vert| { // uv_up axis const position = core.Vectorf{ .x = @floatCast(-vert.data[0]), .y = @floatCast(vert.data[1]), .z = @floatCast(vert.data[2]), }; const uAxisV = core.Vectorf.fromArray(uAxis.data); const vAxisV = core.Vectorf.fromArray(vAxis.data); try self.vertexList.append(self.meshAlloc, .{ .position = settings.rotation.rotateVector( position.fmul(settings.vertexScale), ), // .normal: Vectorf = .{}, .normal = normal, .uv = .{ .x = (uAxisV.dot(position) + face.shift_x) / texSizeX, .y = (vAxisV.dot(position) + face.shift_y) / texSizeY, }, }); self.vertexOffset += 1; } // generate ngons try self.indexList.append(self.meshAlloc, indexStart + 2); try self.indexList.append(self.meshAlloc, indexStart + 1); try self.indexList.append(self.meshAlloc, indexStart); for (3..count) |j| { const i: u32 = @intCast(j); try self.indexList.append(self.meshAlloc, indexStart + i); try self.indexList.append(self.meshAlloc, indexStart + i - 1); try self.indexList.append(self.meshAlloc, indexStart); } } }; var tbMapDefaultName: core.Name = core.DefineName("__TB_empty"); pub fn LoadTrenchbroomMap(baseAllocator: std.mem.Allocator, settings: Settings) !*TBMap { var err: QuakeMap.ErrorInfo = undefined; const fileMapping = try core.fs().loadFile(settings.mapName); defer core.fs().unmap(fileMapping); core.engine_log("loading map :{s}", .{settings.mapName}); var arena = std.heap.ArenaAllocator.init(baseAllocator); defer arena.deinit(); const alloc = arena.allocator(); const map = try QuakeMap.read(arena.allocator(), fileMapping.bytesNoEnd(), &err); var builders: std.AutoHashMapUnmanaged(u32, MeshBuilder) = .{}; core.engine_log("worldspawn solids count: {d}", .{map.worldspawn.solids.items.len}); const meshAlloc = rend.getAllocator(); for (map.worldspawn.solids.items) |solid| { for (solid.faces.items) |face| { var name = core.MakeName(face.texture_name); if (!builders.contains(name.handle())) { var meshName: []u8 = undefined; meshName = try std.fmt.allocPrint(alloc, "map/m_{s}", .{face.texture_name}); try builders.put(alloc, name.handle(), .{ .materialName = name, .meshName = core.MakeName(meshName), .meshAlloc = meshAlloc, }); core.engine_log("new material found, {s}", .{face.texture_name}); } } } // loop over all solids and list all materials. // creating a unique mesh for each one for (map.worldspawn.solids.items) |solid| { for (solid.faces.items) |face| { var name = core.MakeName(face.texture_name); const builder = builders.getPtr(name.handle()).?; try builder.addFace(face, settings); } } // loop over every vertex in quakeMap and push vertices and indices. const tbMap = try TBMap.create(baseAllocator); var iterator = builders.valueIterator(); while (iterator.next()) |builder| { try tbMap.addMeshScene(builder); try builder.submitMesh(); } // for (map.worldspawn.solids.items) |solid| { try tbMap.addPhysicsShape(solid, settings); } // physics.context().system.update(0.001, .{}) catch {}; // loop over all point entities look for info_player_start return tbMap; } const MeshUpdate = rend.MeshUpdate; const JointNameEntry = rend.JointNameEntry; const za = QuakeMap.za; const Vec3 = za.Vec3; const Vec3d = za.Vec3_f64; const QuakeMap = @import("QuakeMap.zig"); const std = @import("std"); const backlog = @import("Backlog"); const rend = backlog.rend; const physics = backlog.physics; const core = backlog.core; const assets = backlog.assets;