412 lines
14 KiB
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
|