Backlog/engine/core/src/scene.zig

412 lines
14 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 SceneObjectPosRot = 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,
};
pub const SceneObject = struct {
_repr: SceneObjectRepr = .{}, // not public
posRot: SceneObjectPosRot = .{}, // 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 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()) *SceneObjectPosRot {
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 getAndResolveTransform(self: @This()) core.Transform {
const repr: *SceneObjectRepr = SceneObjectContainer.get(self.handle, ._repr).?;
if (repr.lastUpdate != gSceneSystem.tickCount) {
gSceneSystem.updateTransform(repr, SceneObjectContainer.get(self.handle, .posRot).?);
}
return repr.transform;
}
pub fn getTransform(self: @This()) core.Transform {
return SceneObjectContainer.get(self.handle, ._repr).?.transform;
}
// 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) {
core.gScene.dynamicObjects.append(core.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 var gSceneSystem: *SceneSystem = undefined;
fn childAllocator() std.mem.Allocator {
return gSceneSystem.childrenArena.allocator();
}
pub const SceneSystem = struct {
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This());
allocator: std.mem.Allocator,
dynamicObjects: ArrayListUnmanaged(core.ObjectHandle) = .{},
childrenArena: std.heap.ArenaAllocator,
tickCount: u32 = 0,
pub const Field = SceneObjectSet.Field;
pub const FieldType = SceneObjectSet.FieldType;
// internal update transform function
fn updateTransform(self: *@This(), repr: *SceneObjectRepr, posRot: *const SceneObjectPosRot) 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 {}
}
// core.engine_log("final\n{d} {d} {d} {d}\n{d} {d} {d} {d}", .{
// final[0][0],
// final[0][1],
// final[0][2],
// final[0][3],
// final[1][0],
// final[1][1],
// final[1][2],
// final[1][3],
// });
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,
);
// core.engine_log("final\n{d} {d} {d} {d}\n{d} {d} {d} {d}", .{
// repr.transform[0][0],
// repr.transform[0][1],
// repr.transform[0][2],
// repr.transform[0][3],
// repr.transform[1][0],
// repr.transform[1][1],
// repr.transform[1][2],
// repr.transform[1][3],
// });
repr.lastUpdate = self.tickCount;
}
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
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);
}
}
}
// ----- 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),
};
gSceneSystem = self;
try core.defineComponent(Scene, allocator);
Scene.SceneObjectContainer = try SceneObjectSet.create(allocator);
return self;
}
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 {
self.dynamicObjects.deinit(self.allocator);
self.childrenArena.deinit();
core.undefineComponent(Scene);
Scene.SceneObjectContainer.destroy();
self.allocator.destroy(self);
}
};
// 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