const std = @import("std"); const zphysics = @import("zphysics"); const core = @import("core"); const zm = core.zm; const physicsCollider = @import("physicsCollider.zig"); pub const PhysicsCollider = physicsCollider.PhysicsCollider; const physicsCharacter = @import("physicsCharacter.zig"); pub const PhysicsCharacter = physicsCharacter.PhysicsCharacter; pub const ObjectLayers = struct { pub const non_moving: zphysics.ObjectLayer = 0; pub const moving: zphysics.ObjectLayer = 1; pub const len: u32 = 2; }; pub const BroadPhaseLayers = struct { pub const non_moving: zphysics.BroadPhaseLayer = 0; pub const moving: zphysics.BroadPhaseLayer = 1; pub const len: u32 = 2; }; pub const IgnoreFixedBodiesFilter = extern struct { __v: *const zphysics.BodyFilter.VTable = &vtable, filtered: [16]BodyId = undefined, len: usize = 0, const vtable = zphysics.BodyFilter.VTable{ .shouldCollide = _shouldCollide, .shouldCollideLocked = _shouldCollideLocked, }; pub fn addIgnoredBody(self: *@This(), bodyId: BodyId) !void { if (self.len == 16) { return error.OutOfMemory; } self.filtered[self.len] = bodyId; self.len += 1; } pub fn _shouldCollide(p: *const zphysics.BodyFilter, bodyId: *const BodyId) callconv(.c) bool { const self: *const @This() = @ptrCast(p); for (0..self.len) |i| { if (self.filtered[i] == bodyId.*) { return false; } } return true; } pub fn _shouldCollideLocked(p: *const zphysics.BodyFilter, body: *const Body) callconv(.c) bool { const self: *const @This() = @ptrCast(p); _ = self; _ = body; return true; } }; const BroadPhaseLayerInterface = extern struct { broad_phase_layer_interface: zphysics.BroadPhaseLayerInterface = .init(@This()), object_to_broad_phase: [ObjectLayers.len]zphysics.BroadPhaseLayer = undefined, fn init() BroadPhaseLayerInterface { var layer_interface: BroadPhaseLayerInterface = .{}; layer_interface.object_to_broad_phase[ObjectLayers.non_moving] = BroadPhaseLayers.non_moving; layer_interface.object_to_broad_phase[ObjectLayers.moving] = BroadPhaseLayers.moving; return layer_interface; } pub fn getNumBroadPhaseLayers(_: *const zphysics.BroadPhaseLayerInterface) callconv(.c) u32 { return BroadPhaseLayers.len; } pub fn getBroadPhaseLayer( broad_phase_layer_interface: *const zphysics.BroadPhaseLayerInterface, object_layer: zphysics.ObjectLayer, ) callconv(.c) zphysics.BroadPhaseLayer { const self: *const BroadPhaseLayerInterface = @alignCast(@fieldParentPtr("broad_phase_layer_interface", broad_phase_layer_interface)); return self.object_to_broad_phase[object_layer]; } }; const ObjectVsBroadPhaseLayerFilter = extern struct { object_vs_broad_phase_layer_filter: zphysics.ObjectVsBroadPhaseLayerFilter = .init(@This()), pub fn shouldCollide( _: *const zphysics.ObjectVsBroadPhaseLayerFilter, object_layer: zphysics.ObjectLayer, broad_phase_layer: zphysics.BroadPhaseLayer, ) callconv(.c) bool { return switch (object_layer) { ObjectLayers.non_moving => broad_phase_layer == BroadPhaseLayers.moving, ObjectLayers.moving => true, else => unreachable, }; } }; const ObjectLayerPairFilter = extern struct { object_layer_pair_filter: zphysics.ObjectLayerPairFilter = .init(@This()), pub fn shouldCollide( _: *const zphysics.ObjectLayerPairFilter, a: zphysics.ObjectLayer, b: zphysics.ObjectLayer, ) callconv(.c) bool { return switch (a) { ObjectLayers.non_moving => b == ObjectLayers.moving, ObjectLayers.moving => true, else => unreachable, }; } }; pub const PhysicsRuntime = struct { allocator: std.mem.Allocator, bpli: BroadPhaseLayerInterface, ovbplf: ObjectVsBroadPhaseLayerFilter = .{}, olpf: ObjectLayerPairFilter = .{}, max_bodies: u32 = 8192, system: *zphysics.PhysicsSystem = undefined, shapes: std.AutoHashMapUnmanaged(u32, ShapeRef) = .{}, updatePeriod: f64 = 1.0 / 60.0, timeSinceUpdate: f64 = 0.0, idToEntity: std.AutoHashMapUnmanaged(zphysics.BodyId, core.Entity) = .{}, bodyIdsToDestroy: std.ArrayListUnmanaged(BodyId) = .{}, pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "physics.Runtime"); pub fn registerBodyEntity(self: *@This(), bodyId: zphysics.BodyId, entity: core.Entity) !void { try self.idToEntity.put(self.allocator, bodyId, entity); } pub fn init(self: *@This(), allocator: std.mem.Allocator, first: bool) !void { if (!first) { return; } try zphysics.init(std.heap.smp_allocator, .{}); //try zphysics.init(allocator, .{}); self.* = .{ .allocator = allocator, .bpli = BroadPhaseLayerInterface.init(), }; const system = try zphysics.PhysicsSystem.create( @as(*const zphysics.BroadPhaseLayerInterface, @ptrCast(&self.bpli)), @as(*const zphysics.ObjectVsBroadPhaseLayerFilter, @ptrCast(&self.ovbplf)), @as(*const zphysics.ObjectLayerPairFilter, @ptrCast(&self.olpf)), .{ .max_bodies = self.max_bodies, .max_body_pairs = self.max_bodies * 2, .max_contact_constraints = self.max_bodies * 2, .num_body_mutexes = 0, }, ); self.system = system; } pub fn tick(self: *@This(), dt: f64) void { // self.timeSinceUpdate += dt; // while (self.timeSinceUpdate > self.updatePeriod) { // self.timeSinceUpdate -= self.updatePeriod; // self.system.update(@floatCast(self.updatePeriod), .{}) catch unreachable; // } var z = core.tracy.ZoneN(@src(), "physics system update "); self.system.update(@floatCast(dt), .{}) catch unreachable; defer z.End(); // todo.. interpolation kinda easy here. self.updateScenes(dt); } fn updateScenes(self: *@This(), dt: f64) void { const lockInterface = self.system.getBodyLockInterface(); const interface = self.system.getBodyInterfaceMut(); for (self.bodyIdsToDestroy.items) |toDestroy| { interface.removeAndDestroyBody(toDestroy); } self.bodyIdsToDestroy.clearRetainingCapacity(); // update character controllers for (PhysicsCharacter.BaseContainer.list.items) |physChar| { physChar.update(dt); } var z = core.tracy.ZoneN(@src(), "Updating physics transform"); defer z.End(); // update physics colliders for (PhysicsCollider.BaseContainer.list.items) |collider| { if (collider.mobile == false) { continue; } if (collider.bodyId) |bodyId| { var readLock: zphysics.BodyLockRead = .{}; readLock.lock(lockInterface, bodyId); defer readLock.unlock(); if (readLock.body) |body| { const scene = collider.entity.fetch(core.Scene).?; scene.setPosition(core.Vectorf.fromArray(body.position)); scene.setRotation(.{ .quat = body.rotation }); } } } } pub fn pushDestroyBodyId(self: *@This(), bodyId: BodyId) void { self.bodyIdsToDestroy.append(self.allocator, bodyId) catch unreachable; } pub fn removeShape(self: *@This(), name: *core.Name) void { self.shapes.get(name.handle()).?.shape.release(); } pub fn deinit(self: *@This()) void { self.system.optimizeBroadPhase(); for (PhysicsCharacter.BaseContainer.list.items) |physChar| { physChar.deinit(); } self.system.optimizeBroadPhase(); // self.system.update(0.01, .{}) catch {}; { const interface = self.system.getBodyInterfaceMut(); _ = interface; var iterator = self.idToEntity.keyIterator(); while (iterator.next()) |bodyId| { _ = bodyId; // interface.removeAndDestroyBody(bodyId.*); } } self.system.optimizeBroadPhase(); { var iterator = self.shapes.iterator(); while (iterator.next()) |n| { n.value_ptr.shape.release(); } } self.bodyIdsToDestroy.deinit(self.allocator); // self.system.update(0.01, .{}) catch {}; self.idToEntity.deinit(self.allocator); // core.undefineComponent(PhysicsCharacter); // core.undefineComponent(PhysicsCollider); self.shapes.deinit(self.allocator); self.system.destroy(); zphysics.deinit(); } pub fn createShape(self: *@This(), name: *core.Name, settings: ShapeSettings) !void { const ref: ShapeRef = .{ .settings = settings, .shape = try settings.createShape(), }; try self.shapes.put(self.allocator, name.handle(), ref); } }; pub const ShapeSettings = union(enum(u8)) { convex: *zphysics.ConvexShapeSettings, box: *zphysics.BoxShapeSettings, sphere: *zphysics.SphereShapeSettings, triangle: *zphysics.TriangleShapeSettings, capsule: *zphysics.CapsuleShapeSettings, taperedCapsule: *zphysics.TaperedCapsuleShapeSettings, cylinder: *zphysics.CylinderShapeSettings, convexHull: *zphysics.ConvexHullShapeSettings, heightField: *zphysics.HeightFieldShapeSettings, mesh: *zphysics.MeshShapeSettings, decorated: *zphysics.DecoratedShapeSettings, compound: *zphysics.CompoundShapeSettings, pub fn release(self: @This()) void { switch (self) { .convex => |x| { x.asShapeSettings().release(); }, .box => |x| { x.asShapeSettings().release(); }, .sphere => |x| { x.asShapeSettings().release(); }, .triangle => |x| { x.asShapeSettings().release(); }, .capsule => |x| { x.asShapeSettings().release(); }, .taperedCapsule => |x| { x.asShapeSettings().release(); }, .cylinder => |x| { x.asShapeSettings().release(); }, .convexHull => |x| { x.asShapeSettings().release(); }, .heightField => |x| { x.asShapeSettings().release(); }, .mesh => |x| { x.asShapeSettings().release(); }, .decorated => |x| { x.asShapeSettings().release(); }, .compound => |x| { x.asShapeSettings().release(); }, } } pub fn createShape(self: @This()) !*zphysics.Shape { switch (self) { .convex => |x| { return try x.asShapeSettings().createShape(); }, .box => |x| { return try x.asShapeSettings().createShape(); }, .sphere => |x| { return try x.asShapeSettings().createShape(); }, .triangle => |x| { return try x.asShapeSettings().createShape(); }, .capsule => |x| { return try x.asShapeSettings().createShape(); }, .taperedCapsule => |x| { return try x.asShapeSettings().createShape(); }, .cylinder => |x| { return try x.asShapeSettings().createShape(); }, .convexHull => |x| { return try x.asShapeSettings().createShape(); }, .heightField => |x| { return try x.asShapeSettings().createShape(); }, .mesh => |x| { return try x.asShapeSettings().createShape(); }, .decorated => |x| { return try x.asShapeSettings().createShape(); }, .compound => |x| { return try x.asShapeSettings().createShape(); }, } } }; pub const ShapeRef = struct { settings: ShapeSettings = undefined, shape: *zphysics.Shape = undefined, }; const BodyId = zphysics.BodyId; const Body = zphysics.Body;