feat: initial DX12 foundation framework
This commit is contained in:
13
Engine/Components/ComponentsCommon.h
Normal file
13
Engine/Components/ComponentsCommon.h
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @file ComponentsCommon.h
|
||||
* @brief Components 模块公共依赖头。
|
||||
*/
|
||||
#pragma once
|
||||
#include "..\Common\CommonHeader.h"
|
||||
#include "..\EngineAPI\GameEntity.h"
|
||||
#include "..\EngineAPI\TransformComponent.h"
|
||||
#include "..\EngineAPI\ScriptComponent.h"
|
||||
|
||||
namespace XEngine::game_entity {
|
||||
|
||||
}
|
||||
108
Engine/Components/Entity.cpp
Normal file
108
Engine/Components/Entity.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
#include "Entity.h"
|
||||
#include "Script.h"
|
||||
#include "Transform.h"
|
||||
|
||||
namespace XEngine::game_entity {
|
||||
|
||||
namespace {
|
||||
|
||||
utl::vector<transform::component> transforms;
|
||||
utl::vector<script::component> scripts;
|
||||
|
||||
|
||||
utl::vector<id::generation_type> generations;
|
||||
utl::deque<entity_id> free_ids;
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
|
||||
entity
|
||||
create(entity_info info)
|
||||
{
|
||||
assert(info.transform);
|
||||
if (!info.transform) return entity{ };
|
||||
|
||||
entity_id id;
|
||||
|
||||
if (free_ids.size() > id::min_deleted_elements)
|
||||
{
|
||||
id = free_ids.front();
|
||||
assert(!is_alive( id ));
|
||||
free_ids.pop_front();
|
||||
id = entity_id{ id::new_generation(id) };
|
||||
++generations[id::index(id)];
|
||||
}
|
||||
else
|
||||
{
|
||||
id = entity_id{ (id::id_type)generations.size() };
|
||||
generations.push_back(0);
|
||||
|
||||
transforms.emplace_back();
|
||||
scripts.emplace_back();
|
||||
}
|
||||
|
||||
const entity new_entity{ id };
|
||||
const id::id_type index{ id::index(id) };
|
||||
|
||||
|
||||
//Create Transform Component
|
||||
assert(!transforms[index].is_valid());
|
||||
transforms[index] = transform::create(*info.transform, new_entity);
|
||||
if (!transforms[index].is_valid()) return{ };
|
||||
|
||||
|
||||
//Create Script Component
|
||||
if (info.script && info.script->script_creator)
|
||||
{
|
||||
assert(!scripts[index].is_valid());
|
||||
scripts[index] = script::create(*info.script, new_entity);
|
||||
assert(scripts[index].is_valid());
|
||||
}
|
||||
|
||||
|
||||
return new_entity;
|
||||
|
||||
}
|
||||
void
|
||||
remove(entity_id id)
|
||||
{
|
||||
const id::id_type index{ id::index(id) };
|
||||
assert(is_alive(id));
|
||||
if (scripts[index].is_valid())
|
||||
{
|
||||
script::remove(scripts[index]);
|
||||
scripts[index] = {};
|
||||
}
|
||||
transform::remove(transforms[index]);
|
||||
transforms[index] = {};
|
||||
free_ids.push_back(id);
|
||||
|
||||
}
|
||||
|
||||
bool
|
||||
is_alive(entity_id id)
|
||||
{
|
||||
assert(id::is_valid(id));
|
||||
const id::id_type index{ id::index(id) };
|
||||
assert(index < generations.size());
|
||||
return (generations[index] == id::generation(id) && transforms[index].is_valid());
|
||||
}
|
||||
|
||||
transform::component
|
||||
entity::transform() const
|
||||
{
|
||||
assert(is_alive(_id));
|
||||
const id::id_type index{ id::index(_id) };
|
||||
return transforms[index];
|
||||
}
|
||||
|
||||
|
||||
script::component
|
||||
entity::script() const
|
||||
{
|
||||
assert(is_alive(_id));
|
||||
const id::id_type index{ id::index(_id) };
|
||||
return scripts[index];
|
||||
}
|
||||
|
||||
}
|
||||
51
Engine/Components/Entity.h
Normal file
51
Engine/Components/Entity.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @file Entity.h
|
||||
* @brief 实体组件生命周期管理接口。
|
||||
*/
|
||||
#pragma once
|
||||
#include "ComponentsCommon.h"
|
||||
#include "Transform.h"
|
||||
|
||||
|
||||
namespace XEngine {
|
||||
|
||||
/**
|
||||
* @brief 前置声明组件初始化参数结构。
|
||||
*/
|
||||
#define INIT_INFO(component) namespace component { struct init_info; }
|
||||
|
||||
INIT_INFO(transform);
|
||||
INIT_INFO(script);
|
||||
|
||||
#undef INIT_INFO
|
||||
|
||||
|
||||
namespace game_entity {
|
||||
/**
|
||||
* @brief 创建实体时可选的组件初始化信息。
|
||||
*/
|
||||
struct entity_info
|
||||
{
|
||||
transform::init_info* transform{ nullptr };
|
||||
script::init_info* script{ nullptr };
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 创建实体并按需附加组件。
|
||||
* @param info 实体初始化信息。
|
||||
* @return 新建实体句柄。
|
||||
*/
|
||||
entity create(entity_info info);
|
||||
/**
|
||||
* @brief 删除实体及其关联组件。
|
||||
* @param id 实体标识符。
|
||||
*/
|
||||
void remove(entity_id id);
|
||||
/**
|
||||
* @brief 判断实体是否仍然存活。
|
||||
* @param id 实体标识符。
|
||||
* @return 存活返回 true,否则返回 false。
|
||||
*/
|
||||
bool is_alive(entity_id id);
|
||||
}
|
||||
}
|
||||
367
Engine/Components/Script.cpp
Normal file
367
Engine/Components/Script.cpp
Normal file
@@ -0,0 +1,367 @@
|
||||
/**
|
||||
* @file Script.cpp
|
||||
* @brief 脚本组件存储、脚本注册表与逐帧更新实现。
|
||||
* @details
|
||||
* 该文件负责脚本系统运行时核心流程:
|
||||
* - 维护脚本实例数组与 script_id 到实例下标映射;
|
||||
* - 维护脚本工厂注册表,支持按哈希查询创建函数;
|
||||
* - 在脚本回调中缓存变换修改,并在帧末批量提交到 Transform 系统。
|
||||
*/
|
||||
#include "Script.h"
|
||||
#include "Entity.h"
|
||||
#include "Transform.h"
|
||||
|
||||
#define USE_TRANSFORM_CACHE_MAP 1
|
||||
|
||||
|
||||
namespace XEngine::script {
|
||||
namespace {
|
||||
/**
|
||||
* @brief 活跃脚本实例数组。
|
||||
*/
|
||||
utl::vector<detail::script_ptr> entity_scripts;
|
||||
/**
|
||||
* @brief script_id 索引到实例数组下标的映射。
|
||||
*/
|
||||
utl::vector<id::id_type> id_mapping;
|
||||
|
||||
/**
|
||||
* @brief script_id 代数数组。
|
||||
*/
|
||||
utl::vector<id::generation_type> generations;
|
||||
/**
|
||||
* @brief 可复用 script_id 队列。
|
||||
*/
|
||||
utl::deque<script_id> free_ids;
|
||||
|
||||
/**
|
||||
* @brief 脚本对 Transform 的延迟修改缓存。
|
||||
*/
|
||||
utl::vector<transform::component_cache> transform_cache;
|
||||
|
||||
#if USE_TRANSFORM_CACHE_MAP
|
||||
std::unordered_map<id::id_type, u32> cache_map;
|
||||
#endif
|
||||
|
||||
|
||||
using script_registry = std::unordered_map<size_t, detail::script_creator>;
|
||||
|
||||
|
||||
/**
|
||||
* @brief 获取全局脚本工厂注册表。
|
||||
* @return 注册表引用。
|
||||
*/
|
||||
script_registry&
|
||||
registery()
|
||||
{
|
||||
static script_registry reg;
|
||||
return reg;
|
||||
}
|
||||
#ifdef USE_WITH_EDITOR
|
||||
/**
|
||||
* @brief 获取编辑器脚本名集合。
|
||||
* @return 脚本名数组引用。
|
||||
*/
|
||||
utl::vector<std::string>&
|
||||
script_names()
|
||||
{
|
||||
static utl::vector<std::string> names;
|
||||
return names;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 判断脚本组件是否存在且有效。
|
||||
* @param id 脚本组件 ID。
|
||||
* @return 存在返回 true。
|
||||
*/
|
||||
bool
|
||||
exists(script_id id)
|
||||
{
|
||||
assert(id::is_valid(id));
|
||||
const id::id_type index{ id::index(id) };
|
||||
assert(index < generations.size() && id_mapping[index] < entity_scripts.size());
|
||||
assert(generations[index] == id::generation(id));
|
||||
return (generations[index] == id::generation(id) &&
|
||||
entity_scripts[id_mapping[index]] &&
|
||||
entity_scripts[id_mapping[index]]->is_valid());
|
||||
}
|
||||
|
||||
#if USE_TRANSFORM_CACHE_MAP
|
||||
/**
|
||||
* @brief 获取实体对应的 Transform 缓存项。
|
||||
* @param entity 目标实体。
|
||||
* @return 缓存项指针。
|
||||
*/
|
||||
transform::component_cache *const
|
||||
get_cache_ptr(const game_entity::entity *const entity)
|
||||
{
|
||||
assert(game_entity::is_alive((*entity).get_id()));
|
||||
const transform::transform_id id{ (*entity).transform().get_id() };
|
||||
|
||||
u32 index{ u32_invalid_id };
|
||||
auto pair = cache_map.try_emplace(id, id::invalid_id);
|
||||
|
||||
if (pair.second)
|
||||
{
|
||||
index = (u32)transform_cache.size();
|
||||
transform_cache.emplace_back();
|
||||
transform_cache.back().id = id;
|
||||
cache_map[id] = index;
|
||||
}
|
||||
else
|
||||
{
|
||||
index = cache_map[id];
|
||||
}
|
||||
|
||||
assert(index < transform_cache.size());
|
||||
return &transform_cache[index];
|
||||
}
|
||||
#else
|
||||
/**
|
||||
* @brief 线性查找方式获取 Transform 缓存项。
|
||||
* @param entity 目标实体。
|
||||
* @return 缓存项指针。
|
||||
*/
|
||||
transform::component_cache *const
|
||||
get_cache_ptr(const game_entity::entity *const entity)
|
||||
{
|
||||
assert(game_entity::is_alive((*entity).get_id()));
|
||||
const transform::transform_id id{ (*entity).transform().get_id() };
|
||||
|
||||
for (auto& cache : transform_cache)
|
||||
{
|
||||
if (cache.id == id)
|
||||
{
|
||||
return &cache;
|
||||
}
|
||||
}
|
||||
|
||||
transform_cache.emplace_back();
|
||||
transform_cache.back().id = id;
|
||||
|
||||
return &transform_cache.back();
|
||||
}
|
||||
#endif
|
||||
}// namespace
|
||||
|
||||
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
* @brief 注册脚本工厂函数。
|
||||
* @param tag 脚本名哈希。
|
||||
* @param func 工厂函数。
|
||||
* @return 注册成功返回 1。
|
||||
*/
|
||||
u8
|
||||
register_script(size_t tag, script_creator func)
|
||||
{
|
||||
bool result{ registery().insert(script_registry::value_type{tag, func}).second };
|
||||
assert(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按哈希获取脚本工厂函数。
|
||||
* @param tag 脚本名哈希。
|
||||
* @return 脚本工厂函数。
|
||||
*/
|
||||
script_creator
|
||||
get_script_creator(size_t tag)
|
||||
{
|
||||
auto script = XEngine::script::registery().find(tag);
|
||||
assert(script != XEngine::script::registery().end() && script->first == tag);
|
||||
return script->second;
|
||||
}
|
||||
|
||||
#ifdef USE_WITH_EDITOR
|
||||
/**
|
||||
* @brief 记录脚本名称供编辑器使用。
|
||||
* @param name 脚本类名。
|
||||
* @return 写入成功返回 1。
|
||||
*/
|
||||
u8
|
||||
add_script_name(const char* name)
|
||||
{
|
||||
script_names().emplace_back(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}// namespace detail
|
||||
|
||||
|
||||
/**
|
||||
* @brief 创建脚本组件并实例化脚本对象。
|
||||
* @param info 脚本初始化参数。
|
||||
* @param entity 绑定实体。
|
||||
* @return 脚本组件句柄。
|
||||
*/
|
||||
component
|
||||
create(init_info info, game_entity::entity entity)
|
||||
{
|
||||
assert(entity.is_valid());
|
||||
assert(info.script_creator);
|
||||
|
||||
script_id id{};
|
||||
if (free_ids.size() > id::min_deleted_elements)
|
||||
{
|
||||
id = free_ids.front();
|
||||
assert(!exists(id));
|
||||
free_ids.pop_front();
|
||||
id = script_id{ id::new_generation(id) };
|
||||
++generations[id::index(id)];
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
id = script_id{ (id::id_type)id_mapping.size() };
|
||||
id_mapping.emplace_back();
|
||||
generations.push_back(0);
|
||||
}
|
||||
assert(id::is_valid(id));
|
||||
const id::id_type index{ (id::id_type)entity_scripts.size() };
|
||||
entity_scripts.emplace_back(info.script_creator(entity));
|
||||
assert(entity_scripts.back()->get_id() == entity.get_id());
|
||||
id_mapping[id::index(id)] = index;
|
||||
|
||||
return component{ id };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief 删除脚本组件并更新映射关系。
|
||||
* @param c 脚本组件句柄。
|
||||
*/
|
||||
void
|
||||
remove(component c)
|
||||
{
|
||||
assert(c.is_valid() && exists(c.get_id()));
|
||||
const script_id id{ c.get_id() };
|
||||
const id::id_type index{ id::index(id) };
|
||||
const script_id last_id{ entity_scripts.back()->script().get_id() };
|
||||
utl::erase_unordered(entity_scripts, index);
|
||||
id_mapping[id::index(last_id)] = index;
|
||||
id_mapping[id::index(id)] = id::invalid_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 执行脚本逐帧更新并提交变换缓存。
|
||||
* @param dt 帧时间步长。
|
||||
*/
|
||||
void
|
||||
update(float dt) {
|
||||
for (auto& ptr : entity_scripts)
|
||||
{
|
||||
ptr->update(dt);
|
||||
}
|
||||
|
||||
if (transform_cache.size())
|
||||
{
|
||||
transform::update(transform_cache.data(), (u32)transform_cache.size());
|
||||
transform_cache.clear();
|
||||
|
||||
#if USE_TRANSFORM_CACHE_MAP
|
||||
cache_map.clear();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 缓存脚本写入的旋转变更。
|
||||
* @param entity 目标实体。
|
||||
* @param rotation_quaternion 旋转四元数。
|
||||
*/
|
||||
void
|
||||
entity_script::set_rotation(const game_entity::entity *const entity, math::v4 rotation_quaternion)
|
||||
{
|
||||
transform::component_cache& cache{ *get_cache_ptr(entity) };
|
||||
cache.flags |= transform::component_flags::rotation;
|
||||
cache.rotation = rotation_quaternion;
|
||||
}
|
||||
/**
|
||||
* @brief 缓存脚本写入的朝向变更。
|
||||
* @param entity 目标实体。
|
||||
* @param orientation_vector 朝向向量。
|
||||
*/
|
||||
void
|
||||
entity_script::set_orientation(const game_entity::entity *const entity, math::v3 orientation_vector)
|
||||
{
|
||||
transform::component_cache& cache{ *get_cache_ptr(entity) };
|
||||
cache.flags |= transform::component_flags::orientation;
|
||||
cache.orientation = orientation_vector;
|
||||
}
|
||||
/**
|
||||
* @brief 缓存脚本写入的位置变更。
|
||||
* @param entity 目标实体。
|
||||
* @param position 位置向量。
|
||||
*/
|
||||
void
|
||||
entity_script::set_position(const game_entity::entity *const entity, math::v3 position)
|
||||
{
|
||||
transform::component_cache& cache{ *get_cache_ptr(entity) };
|
||||
cache.flags |= transform::component_flags::position;
|
||||
cache.position = position;
|
||||
}
|
||||
/**
|
||||
* @brief 缓存脚本写入的缩放变更。
|
||||
* @param entity 目标实体。
|
||||
* @param scale 缩放向量。
|
||||
*/
|
||||
void
|
||||
entity_script::set_scale(const game_entity::entity *const entity, math::v3 scale)
|
||||
{
|
||||
transform::component_cache& cache{ *get_cache_ptr(entity) };
|
||||
cache.flags |= transform::component_flags::scale;
|
||||
cache.scale = scale;
|
||||
}
|
||||
|
||||
} // namespace script
|
||||
|
||||
#ifdef USE_WITH_EDITOR
|
||||
|
||||
/**
|
||||
* @brief ATL 的 SAFEARRAY 封装头。
|
||||
* @details
|
||||
* 该头文件提供 CComSafeArray,用于在编辑器导出路径中构造 COM 安全数组,
|
||||
* 以便把脚本名列表跨 DLL 边界返回给外部调用方。
|
||||
*/
|
||||
#include <atlsafe.h>
|
||||
|
||||
/**
|
||||
* @brief 导出脚本名列表给编辑器端。
|
||||
* @details
|
||||
* 返回值为 LPSAFEARRAY(SAFEARRAY*),元素类型为 BSTR。
|
||||
* 内部流程:
|
||||
* - 从 script_names() 读取脚本名数量;
|
||||
* - 为空时返回 nullptr;
|
||||
* - 使用 CComSafeArray<BSTR> 分配数组;
|
||||
* - 逐项把 std::string 转成 BSTR 填入数组;
|
||||
* - 通过 Detach 转移 SAFEARRAY 所有权给调用方。
|
||||
* 调用方在不再使用后应负责释放 SAFEARRAY。
|
||||
*/
|
||||
extern "C" __declspec(dllexport)
|
||||
LPSAFEARRAY
|
||||
get_script_names()
|
||||
{
|
||||
// 读取当前已注册脚本名数量。
|
||||
const u32 size{ (u32)XEngine::script::script_names().size() };
|
||||
|
||||
// 没有脚本时返回空指针,避免创建空 SAFEARRAY。
|
||||
if (!size) return nullptr;
|
||||
|
||||
// 创建固定长度的 BSTR 安全数组。
|
||||
CComSafeArray<BSTR> names(size);
|
||||
|
||||
// 将脚本名逐项转换为 BSTR 并写入数组。
|
||||
for (u32 i{ 0 }; i < size; ++i)
|
||||
{
|
||||
names.SetAt(i, A2BSTR_EX(XEngine::script::script_names()[i].c_str()), false);
|
||||
}
|
||||
// 释放 CComSafeArray 对底层 SAFEARRAY 的管理并返回给调用方。
|
||||
return names.Detach();
|
||||
}
|
||||
#endif
|
||||
35
Engine/Components/Script.h
Normal file
35
Engine/Components/Script.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @file Script.h
|
||||
* @brief 脚本组件创建、销毁与更新接口。
|
||||
*/
|
||||
#pragma once
|
||||
#include "ComponentsCommon.h"
|
||||
|
||||
namespace XEngine::script {
|
||||
|
||||
/**
|
||||
* @brief 脚本组件初始化信息。
|
||||
*/
|
||||
struct init_info
|
||||
{
|
||||
detail::script_creator script_creator;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 为实体创建脚本组件。
|
||||
* @param info 脚本初始化信息。
|
||||
* @param entity 目标实体。
|
||||
* @return 创建后的脚本组件句柄。
|
||||
*/
|
||||
component create(init_info info, game_entity::entity entity);
|
||||
/**
|
||||
* @brief 移除脚本组件。
|
||||
* @param c 脚本组件句柄。
|
||||
*/
|
||||
void remove(component c);
|
||||
/**
|
||||
* @brief 更新所有脚本组件。
|
||||
* @param dt 帧时间间隔(秒)。
|
||||
*/
|
||||
void update(float dt);
|
||||
}
|
||||
213
Engine/Components/Transform.cpp
Normal file
213
Engine/Components/Transform.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
#include "Transform.h"
|
||||
#include "Entity.h"
|
||||
|
||||
namespace XEngine::transform {
|
||||
|
||||
namespace {
|
||||
|
||||
utl::vector<math::m4x4> to_world;
|
||||
utl::vector<math::m4x4> inv_world;
|
||||
utl::vector<math::v3> positions;
|
||||
utl::vector<math::v3> orientations;
|
||||
utl::vector<math::v4> rotations;
|
||||
utl::vector<math::v3> scales;
|
||||
utl::vector<u8> has_transform;
|
||||
utl::vector<u8> changes_from_previous_frame;
|
||||
u8 read_write_flag;
|
||||
|
||||
void
|
||||
calculate_transform_matrices(id::id_type index)
|
||||
{
|
||||
assert(rotations.size() >= index);
|
||||
assert(positions.size() >= index);
|
||||
assert(scales.size() >= index);
|
||||
|
||||
using namespace DirectX;
|
||||
XMVECTOR r{ XMLoadFloat4(&rotations[index]) };
|
||||
XMVECTOR t{ XMLoadFloat3(&positions[index]) };
|
||||
XMVECTOR s{ XMLoadFloat3(&scales[index]) };
|
||||
|
||||
XMMATRIX world{ XMMatrixAffineTransformation(s, XMQuaternionIdentity(), r, t) };
|
||||
XMStoreFloat4x4(&to_world[index], world);
|
||||
|
||||
world.r[3] = XMVectorSet(0.f, 0.f, 0.f, 1.f);
|
||||
XMMATRIX inverse_world{ XMMatrixInverse(nullptr,world) };
|
||||
XMStoreFloat4x4(&inv_world[index], inverse_world);
|
||||
|
||||
has_transform[index] = 1;
|
||||
}
|
||||
|
||||
math::v3
|
||||
calculate_orientation(math::v4 rotation)
|
||||
{
|
||||
using namespace DirectX;
|
||||
XMVECTOR rotation_quat{ XMLoadFloat4(&rotation) };
|
||||
XMVECTOR front{ XMVectorSet(0.f,0.f,1.f,0.f) };
|
||||
math::v3 orientation;
|
||||
XMStoreFloat3(&orientation, XMVector3Rotate(front, rotation_quat));
|
||||
return orientation;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
set_rotation(transform_id id, const math::v4& rotation_quaternion)
|
||||
{
|
||||
const u32 index{ id::index(id) };
|
||||
rotations[index] = rotation_quaternion;
|
||||
orientations[index] = calculate_orientation(rotation_quaternion);
|
||||
has_transform[index] = 0;
|
||||
changes_from_previous_frame[index] |= component_flags::rotation;
|
||||
}
|
||||
void
|
||||
set_orientation(transform_id id, const math::v3& rotation_quaternion)
|
||||
{
|
||||
|
||||
}
|
||||
void
|
||||
set_position(transform_id id, const math::v3& position)
|
||||
{
|
||||
const u32 index{ id::index(id) };
|
||||
positions[index] = position;
|
||||
has_transform[index] = 0;
|
||||
changes_from_previous_frame[index] |= component_flags::position;
|
||||
}
|
||||
void
|
||||
set_scale(transform_id id, const math::v3& scale)
|
||||
{
|
||||
const u32 index{ id::index(id) };
|
||||
scales[index] = scale;
|
||||
has_transform[index] = 0;
|
||||
changes_from_previous_frame[index] |= component_flags::scale;
|
||||
}
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
component
|
||||
create(init_info info, game_entity::entity entity)
|
||||
{
|
||||
assert(entity.is_valid());
|
||||
const id::id_type entity_index{ id::index(entity.get_id()) };
|
||||
|
||||
if (positions.size() > entity_index)
|
||||
{
|
||||
math::v4 rotation{ info.rotation };
|
||||
rotations[entity_index] = rotation;
|
||||
orientations[entity_index] = calculate_orientation(rotation);
|
||||
positions[entity_index] = math::v3{ info.position };
|
||||
scales[entity_index] = math::v3{ info.scale };
|
||||
has_transform[entity_index] = 0;
|
||||
changes_from_previous_frame[entity_index] = (u8)component_flags::all;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(positions.size() == entity_index);
|
||||
rotations.emplace_back(info.rotation);
|
||||
orientations.emplace_back(calculate_orientation(math::v4{ info.rotation }));
|
||||
positions.emplace_back(info.position);
|
||||
scales.emplace_back(info.scale);
|
||||
has_transform.emplace_back((u8)0);
|
||||
to_world.emplace_back();
|
||||
inv_world.emplace_back();
|
||||
changes_from_previous_frame.emplace_back((u8)component_flags::all);
|
||||
}
|
||||
|
||||
return component{ transform_id{ entity.get_id() } };
|
||||
}
|
||||
void
|
||||
remove([[maybe_unused]] component c)
|
||||
{
|
||||
assert(c.is_valid());
|
||||
}
|
||||
|
||||
void
|
||||
get_transform_matrices(const game_entity::entity_id id, math::m4x4& world, math::m4x4& inverse_world)
|
||||
{
|
||||
assert(game_entity::entity{ id }.is_valid());
|
||||
|
||||
const id::id_type entity_index{ id::index(id) };
|
||||
if (!has_transform[entity_index])
|
||||
{
|
||||
calculate_transform_matrices(entity_index);
|
||||
}
|
||||
|
||||
world = to_world[entity_index];
|
||||
inverse_world = inv_world[entity_index];
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
get_update_component_flags(const game_entity::entity_id *const ids, u32 count, u8 *const flags)
|
||||
{
|
||||
assert(ids && count && flags);
|
||||
read_write_flag = 1;
|
||||
|
||||
for (u32 i{ 0 }; i < count; ++i)
|
||||
{
|
||||
assert(game_entity::entity{ ids[i] }.is_valid());
|
||||
flags[i] = changes_from_previous_frame[id::index(ids[i])];
|
||||
}
|
||||
}
|
||||
void
|
||||
update(const component_cache *const cache, u32 count)
|
||||
{
|
||||
assert(cache && count);
|
||||
|
||||
if (read_write_flag)
|
||||
{
|
||||
memset(changes_from_previous_frame.data(), 0, changes_from_previous_frame.size());
|
||||
read_write_flag = 0;
|
||||
}
|
||||
|
||||
for (u32 i{ 0 }; i < count; ++i)
|
||||
{
|
||||
const component_cache& c{ cache[i] };
|
||||
assert(component{ c.id }.is_valid());
|
||||
if (c.flags & component_flags::rotation)
|
||||
{
|
||||
set_rotation(c.id, c.rotation);
|
||||
}
|
||||
if (c.flags & component_flags::orientation)
|
||||
{
|
||||
set_orientation(c.id, c.orientation);
|
||||
}
|
||||
if (c.flags & component_flags::position)
|
||||
{
|
||||
set_position(c.id, c.position);
|
||||
}
|
||||
if (c.flags & component_flags::scale)
|
||||
{
|
||||
set_scale(c.id, c.scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
math::v3
|
||||
component::orientation() const
|
||||
{
|
||||
assert(is_valid());
|
||||
return orientations[id::index(_id)];
|
||||
}
|
||||
|
||||
math::v3
|
||||
component::position() const
|
||||
{
|
||||
assert(is_valid());
|
||||
return positions[id::index(_id)];
|
||||
}
|
||||
math::v3
|
||||
component::scale() const
|
||||
{
|
||||
assert(is_valid());
|
||||
return scales[id::index(_id)];
|
||||
|
||||
}
|
||||
math::v4
|
||||
component::rotation() const
|
||||
{
|
||||
assert(is_valid());
|
||||
return rotations[id::index(_id)];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
83
Engine/Components/Transform.h
Normal file
83
Engine/Components/Transform.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* @file Transform.h
|
||||
* @brief 变换组件数据结构与更新接口。
|
||||
*/
|
||||
#pragma once
|
||||
#include "ComponentsCommon.h"
|
||||
|
||||
namespace XEngine::transform {
|
||||
|
||||
/**
|
||||
* @brief 变换组件初始化参数。
|
||||
*/
|
||||
struct init_info
|
||||
{
|
||||
f32 position[3]{};
|
||||
f32 rotation[4]{};
|
||||
f32 scale[3]{1.f, 1.f, 1.f};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 变换组件缓存更新标记位。
|
||||
*/
|
||||
struct component_flags
|
||||
{
|
||||
enum flags :u32 {
|
||||
rotation = 0x01,
|
||||
orientation = 0x02,
|
||||
position = 0x04,
|
||||
scale = 0x08,
|
||||
|
||||
|
||||
all = rotation | orientation | position| scale
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 变换组件批量更新缓存项。
|
||||
*/
|
||||
struct component_cache
|
||||
{
|
||||
math::v4 rotation;
|
||||
math::v3 orientation;
|
||||
math::v3 position;
|
||||
math::v3 scale;
|
||||
|
||||
transform_id id;
|
||||
u32 flags;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 获取实体的世界矩阵与逆世界矩阵。
|
||||
* @param id 实体标识符。
|
||||
* @param world 输出世界矩阵。
|
||||
* @param inverse_world 输出逆世界矩阵。
|
||||
*/
|
||||
void get_transform_matrices(const game_entity::entity_id id, math::m4x4& world, math::m4x4& inverse_world);
|
||||
/**
|
||||
* @brief 为实体创建变换组件。
|
||||
* @param info 变换初始化参数。
|
||||
* @param entity 目标实体。
|
||||
* @return 创建后的变换组件句柄。
|
||||
*/
|
||||
component create( init_info info, game_entity::entity entity);
|
||||
/**
|
||||
* @brief 移除变换组件。
|
||||
* @param c 变换组件句柄。
|
||||
*/
|
||||
void remove(component c);
|
||||
/**
|
||||
* @brief 查询指定实体集合的变换更新标记。
|
||||
* @param ids 实体标识符数组。
|
||||
* @param count 实体数量。
|
||||
* @param flags 输出标记数组。
|
||||
*/
|
||||
void get_update_component_flags(const game_entity::entity_id *const ids, u32 count, u8 *const flags);
|
||||
/**
|
||||
* @brief 批量提交变换缓存更新。
|
||||
* @param cache 缓存数组首地址。
|
||||
* @param count 缓存项数量。
|
||||
*/
|
||||
void update(const component_cache *const cache, u32 count);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user