Backlog/engine/core/src/scene.zig

564 lines
20 KiB
Zig

const std = @import("std");
const core = @import("core.zig");
const ArrayListUnmanaged = std.ArrayListUnmanaged;
const tracy = core.tracy;
pub const Transform = core.Mat;
const SceneObjectSet = core.SparseMultiSet(SceneObject);
const SceneSet = core.SparseSet(Scene);
pub const SceneAttachMode = enum {
none, // default, parent attachment is irrelevant and not used
relativePositionOnly, // only position is relative to parent transform
relativeRotationOnly, // only rotation is relative to parent transform
relative, // position and rotation are relative to parent transform
snapToParentPositionOnly, // snaps to the parent's position, maintaining independent rotation (not implemented)
snapToParentRotationOnly, // copys the parent's rotation, maintaining independent rotation (not implemented)
snapToParent, // snaps to parent rotation and position
};
pub const SceneMobilityMode = enum {
static, // scene is updated once and never again
moveable, // sceneobject is moveable and has it's final transform updated
};
pub const ScenePosRot = struct {
position: core.Vectorf = .{ .x = 0, .y = 0, .z = 0 },
rotation: core.Rotation = core.Rotation.init(),
scale: core.Vectorf = core.Vectorf.new(1.0, 1.0, 1.0),
pub inline fn toTransform(self: @This()) core.Transform {
var transform = core.zm.mul(
core.zm.matFromQuat(self.rotation.quat),
core.zm.scalingV(self.scale.toZm()),
);
transform = core.zm.mul(
transform,
core.zm.translationV(self.position.toZm()),
);
return transform;
}
};
pub const SceneObjectSettings = struct {
attachmentMode: SceneAttachMode = .none,
sceneMode: SceneMobilityMode = .static,
};
pub const SceneObjectRepr = struct {
// fields intended to be internally used, don't touch them
// unless you know what you're doing
transform: core.Mat = core.zm.identity(), // relative transform against parent, if no parent, then this is world transform
parent: ?core.ObjectHandle = null,
attachmentMode: SceneAttachMode = .relative, // doesn't do anything yet, only support relative right now
transformOverride: ?*core.Transform = null,
lastUpdate: u32 = 0,
merge: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), // set to true if we've merged already, false if we havent
};
pub const SceneObject = struct {
_repr: SceneObjectRepr = .{}, // not public
posRot: ScenePosRot = .{}, // position and rotation
settings: SceneObjectSettings = .{}, //
children: ArrayListUnmanaged(core.ObjectHandle) = .{},
pub fn init(params: SceneObjectInitParams) @This() {
// Hmm thinking in the future we could have scene objects be f64s then crunch them down to f32s when we are submiting to gpu
var self = @This(){
.posRot = .{},
._repr = .{},
.settings = .{},
.children = .{},
};
const shouldUpdate: bool = false; // should mutate
switch (params) {
.transform => {
self._repr.transform = params.transform;
self.posRot.position = core.Vectorf.fromZm(core.zm.mul(params.transform, core.Vectorf.zero().toZm()));
self.posRot.rotation = .{ .quat = core.zm.matToQuat(params.transform) };
},
.position => {
@panic("todo: implement position only initialization");
},
.rotation => {
@panic("todo: implement rotation only initialization");
},
.positionRotAngles => {
@panic("todo: implement position + rotation initialization (angles) ");
},
.positionRot => {
@panic("todo: implement position + rotation initialization");
},
}
if (shouldUpdate) {
self.update();
}
return self;
}
};
pub const Scene = struct {
handle: core.ObjectHandle = .{ .generation = 0, .index = 0, .alive = false },
pub var BaseContainer: *SceneSet = undefined;
pub var SceneObjectContainer: *SceneObjectSet = undefined;
pub const ComponentName = "Scene";
pub const ScriptExports: []const []const u8 = &.{
"setPosition",
"setRotation",
"setScale",
"setScaleV",
"getPosition",
"getRotation",
"getParent",
"printHandleIndex",
// "getTransform", not implemented yet
// "setMobility", gonna need special setup for this one
};
pub fn initECS(self: *@This(), handle: core.ObjectHandle) void {
self.handle = handle;
_ = SceneObjectContainer.createWithHandleECS(handle);
}
pub fn deinitECS(self: *@This(), handle: core.ObjectHandle) void {
_ = self;
_ = SceneObjectContainer.destroyObject(handle);
}
pub fn printHandleIndex(self: @This()) void {
core.engine_log("handle.index = 0x{x} generation = {d} alive={any}", .{ self.handle.index, self.handle.generation, self.handle.alive });
}
pub fn setPosRot(self: @This(), newPosRot: ScenePosRot) void {
if (SceneObjectContainer.get(self.handle, .posRot)) |posRot| {
posRot.* = newPosRot;
} else {
core.engine_log("setposRot failed handle.index = 0x{x} generation = {d} alive={any}", .{ self.handle.index, self.handle.generation, self.handle.alive });
}
}
pub fn setPosition(self: @This(), position: core.Vectorf) void {
if (SceneObjectContainer.get(self.handle, .posRot)) |posRot| {
posRot.*.position = position;
} else {
core.engine_log("setposition failed handle.index = 0x{x} generation = {d} alive={any}", .{ self.handle.index, self.handle.generation, self.handle.alive });
}
}
pub fn getPosRot(self: *@This()) *ScenePosRot {
return SceneObjectContainer.get(self.handle, .posRot).?;
}
pub fn setRotation(self: @This(), rotation: core.Rotation) void {
SceneObjectContainer.get(self.handle, .posRot).?.*.rotation = rotation;
}
pub fn setScale(self: @This(), x: f32, y: f32, z: f32) void {
SceneObjectContainer.get(self.handle, .posRot).?.*.scale = .{ .x = x, .y = y, .z = z };
}
pub fn setScaleV(self: @This(), scale: core.Vectorf) void {
SceneObjectContainer.get(self.handle, .posRot).?.*.scale = scale;
}
pub fn getPosition(self: @This()) core.Vectorf {
// core.engine_logs("getPosition called");
return SceneObjectContainer.get(self.handle, .posRot).?.position;
}
pub fn getRotation(self: @This()) core.Rotation {
return SceneObjectContainer.get(self.handle, .posRot).?.rotation;
}
pub fn getScaleV(self: @This()) core.Vectorf {
return SceneObjectContainer.get(self.handle, .posRot).?.scale;
}
pub fn getParent(self: @This()) core.Entity {
return core.Entity{ .handle = SceneObjectContainer.get(self.handle, ._repr).?.parent orelse .{} };
}
pub fn setParent(self: @This(), newParent: core.Entity) void {
core.engine_log("parenting entity {d} -> {d}", .{ self.handle.index, newParent.handle.index });
const repr: *SceneObjectRepr = SceneObjectContainer.get(self.handle, ._repr).?;
const thisParent = repr.parent;
if (thisParent) |p| {
const parentRef = @This(){ .handle = p };
parentRef.removeChild(self.handle);
}
repr.parent = newParent.handle;
const children = SceneObjectContainer.get(newParent.handle, .children).?;
children.append(childAllocator(), newParent.handle) catch unreachable;
}
pub fn clearParent(self: @This()) void {
const repr = SceneObjectContainer.get(self.handle, ._repr).?;
const thisParent = &repr.parent;
if (thisParent) |p| {
const parentRef = @This(){ .handle = p };
parentRef.removeChild(self.handle);
}
repr.parent = null;
}
pub fn removeChild(self: @This(), child: core.ObjectHandle) void {
const children = SceneObjectContainer.get(self.handle, .children).?;
for (children.items, 0..) |search, i| {
if (child.eql(search)) {
_ = children.swapRemove(i);
break;
}
}
}
pub fn getTickCount() u32 {}
pub fn getAndResolveTransform(self: @This()) core.Transform {
const repr: *SceneObjectRepr = SceneObjectContainer.get(self.handle, ._repr).?;
if (repr.lastUpdate != getSceneSystem().tickCount) {
getSceneSystem().updateTransform(repr, SceneObjectContainer.get(self.handle, .posRot).?);
}
return repr.transform;
}
pub fn getTransform(self: @This()) core.Transform {
return SceneObjectContainer.get(self.handle, ._repr).?.transform;
}
pub fn updateTransform(self: @This()) void {
const repr = SceneObjectContainer.get(self.handle, ._repr).?;
const posRot = SceneObjectContainer.get(self.handle, .posRot).?;
getSceneSystem().updateTransform(repr, posRot);
}
// you MUST clearTransfomRefUnsafe() before destroying this transform
pub fn setTransformRefUnsafe(self: @This(), ref: *core.Transform) void {
SceneObjectContainer.get(self.handle, ._repr).?.transformOverride = ref;
}
pub fn clearTransformRefUnsafe(self: @This(), ref: *core.Transform) void {
SceneObjectContainer.get(self.handle, ._repr).?.transformOverride = ref;
}
pub fn setMobility(self: @This(), mobility: SceneMobilityMode) void {
const settings = Scene.SceneObjectContainer.get(self.handle, .settings).?;
if (settings.sceneMode == .static) {
if (mobility == .moveable) {
const gScene = getSceneSystem();
gScene.dynamicObjects.append(gScene.allocator, self.handle) catch unreachable;
}
}
if (settings.sceneMode == .moveable) {
if (mobility == .static) {
@panic("todo unable to change scene mobility back to static");
}
}
settings.*.sceneMode = mobility;
}
};
pub const SceneObjectInitParams = union(enum) {
transform: core.Transform,
position: core.Vectorf,
rotation: core.Quat,
positionRotAngles: struct {
position: core.Vectorf = .{ .x = 0.0, .y = 0.0, .z = 0.0 },
angles: core.Vectorf = .{ .x = 0.0, .y = 0.0, .z = 0.0 },
},
positionRot: struct {
position: core.Vectorf = .{ .x = 0.0, .y = 0.0, .z = 0.0 },
angles: core.Quat = core.zm.qidentity(),
},
};
pub const getSceneSystem = core.EngineObject(SceneSystem).get;
fn childAllocator() std.mem.Allocator {
return getSceneSystem().childrenArena.allocator();
}
pub const SceneSystem = struct {
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "core.SceneSystem");
allocator: std.mem.Allocator,
dynamicObjects: ArrayListUnmanaged(core.ObjectHandle) = .{},
childrenArena: std.heap.ArenaAllocator,
tickCount: u32 = 0,
sceneObjectContainer: *SceneObjectSet = undefined,
cachedOutputs: std.ArrayList(std.ArrayList(core.Transform)) = .{},
writeOutList: std.ArrayList(std.ArrayList(usize)) = .{},
lastUpdateTransformTime: f64 = 0.0,
pub const Field = SceneObjectSet.Field;
pub const FieldType = SceneObjectSet.FieldType;
// internal update transform function
fn updateTransform(self: *@This(), repr: *SceneObjectRepr, posRot: *const ScenePosRot) void {
if (repr.lastUpdate == self.tickCount) {
return;
}
if (repr.transformOverride) |override| {
repr.lastUpdate = self.tickCount;
repr.transform = override.*;
return;
}
var final: core.Transform = core.zm.identity();
if (repr.parent) |parent| {
if (Scene.SceneObjectContainer.get(parent, ._repr)) |parentRepr| {
const parentPosRot = Scene.SceneObjectContainer.get(parent, .posRot).?;
self.updateTransform(parentRepr, parentPosRot);
const parentTransform = parentRepr.transform;
final = core.zm.mul(parentTransform, final);
} else {}
}
repr.transform = core.zm.mul(
core.zm.mul(
core.zm.mul(
core.zm.scalingV(posRot.scale.toZm()),
core.zm.matFromQuat(posRot.rotation.quat),
),
core.zm.translationV(posRot.position.toZm()),
),
final,
);
repr.lastUpdate = self.tickCount;
}
const useParallelJob = true;
pub fn updateTransforms(self: *@This()) void {
// todo. calculate a running load factor for the number of movable objects
// vs static objects
// if we have a small amount of movable vs static AND if we have > 1000 objects,
// then iterate over dynamicObjects array instead
if (useParallelJob) {
// we use 6 workers if the last timing scope ran > 2ms
// otherwise use 1 worker
var scope = core.engineTime.takeProfilingStamp();
var workerCount: u32 = 1;
if (self.lastUpdateTransformTime > 0.001) // 1ms
{
workerCount = 6;
}
core.parallelJob(UpdateWorldTransformsJob{}, false, workerCount) catch unreachable;
scope.end();
if (scope.duration()) |duration| {
self.lastUpdateTransformTime = duration;
}
} else {
for (Scene.SceneObjectContainer.denseItems(._repr), 0..) |*repr, i| {
const settings = Scene.SceneObjectContainer.readDense(i, .settings);
if (settings.sceneMode == .moveable or repr.lastUpdate == 0) {
const posRot = Scene.SceneObjectContainer.readDense(i, .posRot);
self.updateTransform(repr, posRot);
}
}
}
}
pub const MaxWorkerCount = 24;
// ----- NeonObject interace ----
pub fn init(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.allocator = allocator,
.childrenArena = std.heap.ArenaAllocator.init(allocator),
};
core.EngineObject(@This()).gInstance = self;
// try core.defineComponent(Scene, allocator);
Scene.SceneObjectContainer = try SceneObjectSet.create(allocator);
self.sceneObjectContainer = Scene.SceneObjectContainer;
for (0..MaxWorkerCount) |i| {
_ = i;
try self.cachedOutputs.append(self.allocator, .{});
try self.writeOutList.append(self.allocator, .{});
}
return self;
}
pub fn getOutputList(self: *@This(), threadId: u32) !*std.ArrayList(usize) {
const outputLen = self.sceneObjectContainer.denseItems(._repr).len;
try self.writeOutList.items[threadId].ensureTotalCapacity(self.allocator, outputLen);
self.writeOutList.items[threadId].shrinkRetainingCapacity(0);
return &self.writeOutList.items[threadId];
}
pub fn getOutputForWorker(self: *@This(), threadId: u32) ![]core.Transform {
const outputLen = self.sceneObjectContainer.denseItems(._repr).len;
try self.cachedOutputs.items[threadId].resize(self.allocator, outputLen);
return self.cachedOutputs.items[threadId].items;
}
pub fn preTick(self: *@This(), dt: f64) !void {
_ = dt;
self.tickCount +%= 1;
if (self.tickCount == 0) {
self.tickCount += 1;
}
}
pub fn tick(self: *@This(), deltaTime: f64) void {
var z = tracy.ZoneNC(@src(), "Scene System Tick", 0xAABBDD);
defer z.End();
self.updateTransforms();
_ = deltaTime;
}
pub fn deinit(self: *@This()) void {
for (self.cachedOutputs.items) |*i| {
i.deinit(self.allocator);
}
for (self.writeOutList.items) |*i| {
i.deinit(self.allocator);
}
self.dynamicObjects.deinit(self.allocator);
self.childrenArena.deinit();
Scene.SceneObjectContainer.destroy();
self.cachedOutputs.deinit(self.allocator);
self.writeOutList.deinit(self.allocator);
self.allocator.destroy(self);
}
};
const UpdateWorldTransformsJob = struct {
world: ?*anyopaque = null,
pub fn func(self: @This(), thread: *core.ThreadContext) void {
const z = core.tracy.ZoneN(@src(), "Transform Hierarchy - wide");
defer z.End();
thread.barrier(@src()) catch unreachable;
const outputs = core.get(core.SceneSystem).getOutputForWorker(thread.threadId) catch return;
const outputList = core.get(core.SceneSystem).getOutputList(thread.threadId) catch return;
self.updateTransformsHierarchy(thread, outputs, outputList) catch |err| switch (err) {
error.OutOfMemory => {
unreachable;
},
// else => {
// thread.abort(@src(), "unknown error", err);
// return;
// },
};
const denseRepr = core.Scene.SceneObjectContainer.denseItems(._repr);
const z3 = core.tracy.ZoneN(@src(), "Merge Outputs");
// merge outputs
for (outputList.items) |outIndex| {
if (denseRepr[outIndex].merge.cmpxchgStrong(false, true, .seq_cst, .acquire) == null) {
denseRepr[outIndex].transform = outputs[outIndex];
}
}
z3.End();
thread.barrier(@src()) catch unreachable;
}
fn updateTransform(
self: @This(),
thread: *core.ThreadContext,
index: usize,
densePosRot: []core.scene.ScenePosRot,
denseRepr: []core.scene.SceneObjectRepr,
outputs: []core.math.Transform,
outputList: *std.ArrayList(usize),
) void {
var final: core.Transform = core.zm.identity();
const repr = denseRepr[index];
if (repr.parent) |parent| {
if (core.Scene.SceneObjectContainer.sparseToDense(parent)) |parentIndex| {
self.updateTransform(thread, parentIndex, densePosRot, denseRepr, outputs, outputList);
const parentTransform = outputs[parentIndex];
final = parentTransform;
} else {}
}
const posRot = densePosRot[index];
outputs[index] = core.zm.mul(
core.zm.mul(
core.zm.mul(
core.zm.scalingV(posRot.scale.toZm()),
core.zm.matFromQuat(posRot.rotation.quat),
),
core.zm.translationV(posRot.position.toZm()),
),
final,
);
outputList.appendAssumeCapacity(index);
}
pub fn updateTransformsHierarchy(self: @This(), thread: *core.ThreadContext, outputs: []core.math.Transform, outputList: *std.ArrayList(usize)) !void {
const z = core.tracy.ZoneN(@src(), "updateTransformsHierarchy");
defer z.End();
//const dense = self.world.denseScenes();
const densePosRot = core.Scene.SceneObjectContainer.denseItems(.posRot);
const denseRepr = core.Scene.SceneObjectContainer.denseItems(._repr);
// everything allocated with thread.scratch is blown away when the thread is complete
// try outputs.resize(thread.scratch(), densePosRot.len);
// scan and mark all root nodes for update
const split = thread.splitSlice(core.scene.SceneObjectRepr, denseRepr);
// const locals:[]core.math.Transform = try thread.scratch().alloc(core.math.Transform, split.slice.len);
const z2 = core.tracy.ZoneN(@src(), "WalkAndResolve");
for (split.slice, 0..) |*repr, i| {
repr.merge.store(false, .seq_cst); // reset the merge big
const index = split.startIndex + i;
self.updateTransform(thread, index, densePosRot, denseRepr, outputs, outputList);
}
z2.End();
}
};
// LUA_BEGIN
// because scene objects are a special sparse-multiset type,
// they do not have a fixed representation in the sparse set.
// as a result this type requires a special implementation to operate properly.
// multi-set systems should only ever modify values via functions
// we really need a way to deal with multi-set handles.
// idea - in the component registration. if the container type is a sparse multiset
// then the pointer type shall be a pointer to the set handle.
// and the component acquisition shall do absolutely nothing but grab the sparse index of the set handle
//
// man... that shit sounds like so much work...
// LUA_END