564 lines
20 KiB
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
|