Backlog/extras/bsp/src/maploader.zig

330 lines
11 KiB
Zig

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;