commit 60f73b525def48e528df0cae3f9b8a8129ee3ad1
Author: SpecialX <47072643+wangxiner55@users.noreply.github.com>
Date: Thu Mar 19 18:27:49 2026 +0800
feat: initial DX12 foundation framework
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2702aac
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,27 @@
+.vs/
+**/x64/
+**/Debug/
+**/DebugEditor/
+**/Release/
+**/ReleaseEditor/
+*.VC.db
+*.VC.opendb
+*.suo
+*.user
+*.ipch
+*.obj
+*.o
+*.iobj
+*.ipdb
+*.pdb
+*.idb
+*.ilk
+*.tlog
+*.lastbuildstate
+*.log
+*.recipe
+*.cache
+*.db
+*.db-shm
+*.db-wal
+Engine/Graphics_Beta/
diff --git a/ContentTools/ContentTools/ContentTools.vcxproj b/ContentTools/ContentTools/ContentTools.vcxproj
new file mode 100644
index 0000000..b42393a
--- /dev/null
+++ b/ContentTools/ContentTools/ContentTools.vcxproj
@@ -0,0 +1,102 @@
+
+
+
+
+ DebugEditor
+ x64
+
+
+ ReleaseEditor
+ x64
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 17.0
+ Win32Proj
+ {d98da990-9d7c-4064-ab98-6f6c405c10f0}
+ ContentTools
+ 10.0
+
+
+
+ DynamicLibrary
+ true
+ v143
+ Unicode
+
+
+ DynamicLibrary
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Level4
+ true
+ _DEBUG;CONTENTTOOLS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
+ true
+ NotUsing
+
+
+ stdcpp17
+ $(SolutionDir)Engine;$(SolutionDir)Engine\Common;C:\Program Files\Autodesk\FBX\FBX SDK\2020.3.2\include
+ /Ignore:4099 %(AdditionalOptions)
+
+
+ Windows
+ true
+ false
+
+
+
+
+ Level4
+ true
+ true
+ true
+ NDEBUG;CONTENTTOOLS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
+ true
+ NotUsing
+
+
+ stdcpp17
+ $(SolutionDir)Engine;$(SolutionDir)Engine\Common;
+ /Ignore:4099 %(AdditionalOptions)
+
+
+ Windows
+ true
+ true
+ true
+ false
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ContentTools/ContentTools/ContentTools.vcxproj.filters b/ContentTools/ContentTools/ContentTools.vcxproj.filters
new file mode 100644
index 0000000..bc34f03
--- /dev/null
+++ b/ContentTools/ContentTools/ContentTools.vcxproj.filters
@@ -0,0 +1,42 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ 头文件
+
+
+ 头文件
+
+
+ 头文件
+
+
+ 头文件
+
+
+
+
+ 源文件
+
+
+ 源文件
+
+
+ 源文件
+
+
+
\ No newline at end of file
diff --git a/ContentTools/ContentTools/FbxImporter.cpp b/ContentTools/ContentTools/FbxImporter.cpp
new file mode 100644
index 0000000..77ae6bb
--- /dev/null
+++ b/ContentTools/ContentTools/FbxImporter.cpp
@@ -0,0 +1,352 @@
+#include "FbxImporter.h"
+#include "Geometry.h"
+
+#if _DEBUG
+#pragma comment (lib, "C:\\Program Files\\Autodesk\\FBX\\FBX SDK\\2020.3.2\\lib\\vs2019\\x64\\debug\\libfbxsdk-md.lib")
+#pragma comment (lib, "C:\\Program Files\\Autodesk\\FBX\\FBX SDK\\2020.3.2\\lib\\vs2019\\x64\\debug\\libxml2-md.lib")
+#pragma comment (lib, "C:\\Program Files\\Autodesk\\FBX\\FBX SDK\\2020.3.2\\lib\\vs2019\\x64\\debug\\zlib-md.lib")
+#else
+#pragma comment (lib, "C:\\Program Files\\Autodesk\\FBX\\FBX SDK\\2020.3.2\\lib\\vs2019\\x64\\release\\libfbxsdk-md.lib")
+#pragma comment (lib, "C:\\Program Files\\Autodesk\\FBX\\FBX SDK\\2020.3.2\\lib\\vs2019\\x64\\release\\libxml2-md.lib")
+#pragma comment (lib, "C:\\Program Files\\Autodesk\\FBX\\FBX SDK\\2020.3.2\\lib\\vs2019\\x64\\release\\zlib-md.lib")
+#endif
+
+
+namespace XEngine::tools {
+namespace {
+std::mutex fbx_mutex{};
+} // namespace
+
+
+bool
+fbx_context::initialize_fbx()
+{
+ assert(!is_valid());
+
+ _fbx_manager = FbxManager::Create();
+ if (!_fbx_manager)
+ {
+ return false;
+ }
+
+ FbxIOSettings* ios{ FbxIOSettings::Create(_fbx_manager, IOSROOT) };
+ assert(ios);
+ _fbx_manager->SetIOSettings(ios);
+
+ return true;
+}
+
+
+void
+fbx_context::load_fbx_file(const char* file)
+{
+ assert(_fbx_manager && !_fbx_scene);
+ _fbx_scene = FbxScene::Create(_fbx_manager, "Importer Scene");
+ if (!_fbx_scene)
+ {
+ return;
+ }
+
+ FbxImporter* importer{ FbxImporter::Create(_fbx_manager, "Importer") };
+ if (!(importer &&
+ importer->Initialize(file, -1, _fbx_manager->GetIOSettings()) &&
+ importer->Import(_fbx_scene)))
+ {
+ return;
+ }
+
+ importer->Destroy();
+
+ _scene_scale = (f32)_fbx_scene->GetGlobalSettings().GetSystemUnit().GetConversionFactorTo(FbxSystemUnit::m);
+}
+
+void
+fbx_context::get_scene(FbxNode* root /* = nullptr*/)
+{
+ assert(is_valid());
+
+ if (!root)
+ {
+ root = _fbx_scene->GetRootNode();
+ if (!root)return;
+ }
+
+ const s32 num_nodes{ root->GetChildCount() };
+ for (s32 i{ 0 }; i < num_nodes; ++i)
+ {
+ FbxNode* node{ root->GetChild(i) };
+ if (!node) continue;
+
+
+ lod_group lod{};
+ get_meshes(node, lod.meshes, 0, -1);
+ if (lod.meshes.size())
+ {
+ lod.name = lod.meshes[0].name;
+ _scene->lod_groups.emplace_back(lod);
+ }
+
+ }
+}
+
+void
+fbx_context::get_meshes(FbxNode* node, utl::vector& meshes, u32 lod_id, f32 lod_threshold)
+{
+ assert(node && lod_id != u32_invalid_id);
+ bool is_lod_group{ false };
+
+ if (const s32 num_attributes{ node->GetNodeAttributeCount() })
+ {
+ for (s32 i{ 0 }; i < num_attributes; ++i)
+ {
+ FbxNodeAttribute* attribute{ node->GetNodeAttributeByIndex(i) };
+ const FbxNodeAttribute::EType attribute_type{ attribute->GetAttributeType() };
+ if (attribute_type == FbxNodeAttribute::eMesh)
+ {
+ get_mesh(attribute, meshes, lod_id, lod_threshold);
+ }
+ else
+ {
+ get_lod_group(attribute);
+ is_lod_group = true;
+ }
+
+ }
+ }
+
+ if (!is_lod_group)
+ {
+ if (const s32 num_children{ node->GetChildCount() })
+ {
+ for (s32 i{ 0 }; i < num_children; ++i)
+ {
+ get_meshes(node->GetChild(i), meshes, lod_id, lod_threshold);
+ }
+ }
+ }
+
+}
+
+void
+fbx_context::get_mesh(FbxNodeAttribute* attribute, utl::vector& meshes, u32 lod_id, f32 lod_threshold)
+{
+ assert(attribute);
+
+ FbxMesh * fbx_mesh{ (FbxMesh*)attribute };
+
+ if (fbx_mesh->RemoveBadPolygons() < 0) return;
+
+ FbxGeometryConverter gc{ _fbx_manager };
+ fbx_mesh = (FbxMesh*)(gc.Triangulate(fbx_mesh, true));
+ if (!fbx_mesh || fbx_mesh->RemoveBadPolygons() < 0)return;
+
+ FbxNode *const node{ fbx_mesh->GetNode() };
+
+ mesh m{};
+ m.lod_id = lod_id;
+ m.lod_threshold = lod_threshold;
+ m.name = (node->GetName()[0] != '\0') ? node->GetName() : fbx_mesh->GetName();
+
+ if (get_mesh_data(fbx_mesh, m))
+ {
+ meshes.emplace_back(m);
+ }
+}
+
+bool
+fbx_context::get_mesh_data(FbxMesh* fbx_mesh, mesh& m)
+{
+ assert(fbx_mesh);
+
+ FbxNode *const node{ fbx_mesh->GetNode() };
+ FbxAMatrix geometricTransform;
+
+ geometricTransform.SetT(node->GetGeometricTranslation(FbxNode::eSourcePivot));
+ geometricTransform.SetR(node->GetGeometricRotation(FbxNode::eSourcePivot));
+ geometricTransform.SetS(node->GetGeometricScaling(FbxNode::eSourcePivot));
+
+ FbxAMatrix transform{ node->EvaluateGlobalTransform() * geometricTransform };
+ FbxAMatrix inverse_transpose{ transform.Inverse().Transpose() };
+ const s32 num_polys{ fbx_mesh->GetPolygonCount() };
+ if (num_polys <= 0) return false;
+
+ const s32 num_vertices{ fbx_mesh->GetControlPointsCount() };
+ FbxVector4* vertices{ fbx_mesh->GetControlPoints() };
+ const s32 num_indices{ fbx_mesh->GetPolygonVertexCount() };
+ s32* indices{ fbx_mesh->GetPolygonVertices() };
+
+
+ assert(num_vertices > 0 && vertices && num_indices > 0 && indices);
+ if (!(num_vertices > 0 && vertices && num_indices > 0 && indices)) return false;
+
+ m.raw_indices.resize(num_indices);
+
+ utl::vector vertex_ref(num_vertices, u32_invalid_id);
+
+ for (s32 i{ 0 }; i < num_indices; ++i)
+ {
+ const u32 v_idx{ (u32)indices[i] };
+
+ if (vertex_ref[v_idx] != u32_invalid_id)
+ {
+ m.raw_indices[i] = vertex_ref[v_idx];
+ }
+ else
+ {
+ FbxVector4 v = transform.MultT( vertices[v_idx]) * _scene_scale;
+ m.raw_indices[i] = (u32)m.positions.size();
+ vertex_ref[v_idx] = m.raw_indices[i];
+ m.positions.emplace_back((f32)v[0], (f32)v[1], (f32)v[2]);
+ }
+ }
+
+
+ assert(m.raw_indices.size() % 3 == 0);
+ FbxLayerElementArrayTemplate* mtl_indices;
+ if (fbx_mesh->GetMaterialIndices(&mtl_indices))
+ {
+ for (s32 i{ 0 }; i < num_polys; ++i)
+ {
+ const s32 mtl_index{ mtl_indices->GetAt(i) };
+ assert(mtl_index >= 0);
+
+ m.material_indices.emplace_back((u32)mtl_index);
+ if (std::find(m.material_used.begin(), m.material_used.end(), (u32)mtl_index) == m.material_used.end())
+ {
+ m.material_used.emplace_back((u32)mtl_index);
+ }
+ }
+ }
+
+
+ // Importing normals in ON by default
+ const bool import_normals{ !_scene_data->settings.calculate_normals };
+ // Importing normals in ON by default
+ const bool import_tangents{ !_scene_data->settings.calculate_tangents };
+
+
+ if (import_normals)
+ {
+ FbxArray normals;
+ if (fbx_mesh->GenerateNormals() &&
+ fbx_mesh->GetPolygonVertexNormals(normals) && normals.Size() > 0)
+ {
+ const s32 num_normals{ normals.Size() };
+ for (s32 i{ 0 }; i < num_normals; ++i)
+ {
+ FbxVector4 n{ inverse_transpose.MultT(normals[i]) };
+ n.Normalize();
+ m.normals.emplace_back((f32)n[0], (f32)n[1], (f32)n[2]);
+ }
+ }
+ else
+ {
+ _scene_data->settings.calculate_normals = true;
+ }
+ }
+
+
+ if (import_tangents)
+ {
+ FbxLayerElementArrayTemplate* tangents{ nullptr };
+
+ if (fbx_mesh->GenerateTangentsData() &&
+ fbx_mesh->GetTangents(&tangents) &&
+ tangents && tangents->GetCount() > 0)
+ {
+ const s32 num_tangent{ tangents->GetCount() };
+ for (s32 i{ 0 }; i < num_tangent; ++i)
+ {
+ FbxVector4 t{ tangents->GetAt(i) };
+ const f32 handedness{ (f32)t[3] };
+ t[3] = 0.0;
+ t = transform.MultT(t);
+ t.Normalize();
+ m.tangents.emplace_back((f32)t[0], (f32)t[1], (f32)t[2], handedness);
+ }
+ }
+ else
+ {
+ _scene_data->settings.calculate_tangents = true;
+ }
+ }
+
+ FbxStringList uv_names;
+ fbx_mesh->GetUVSetNames(uv_names);
+ const s32 uv_set_count{ uv_names.GetCount() };
+
+ m.uv_sets.resize(uv_set_count);
+
+ for (s32 i{ 0 }; i < uv_set_count; ++i)
+ {
+ FbxArray uvs;
+ if (fbx_mesh->GetPolygonVertexUVs(uv_names.GetStringAt(i), uvs))
+ {
+ const s32 num_uvs{ uvs.Size() };
+ for (s32 j{ 0 }; j < num_uvs; ++j)
+ {
+ m.uv_sets[i].emplace_back((f32)uvs[j][0], (f32)uvs[j][1]);
+ }
+ }
+ }
+
+
+ return true;
+}
+
+void
+fbx_context::get_lod_group(FbxNodeAttribute* attribute)
+{
+ assert(attribute);
+
+ FbxLODGroup * lod_grp{ (FbxLODGroup*)attribute };
+ FbxNode *const node{ lod_grp->GetNode() };
+
+ lod_group lod{};
+ lod.name = (node->GetName()[0] != '\0') ? node->GetName() : lod_grp->GetName();
+
+
+ const s32 num_nodes{ node->GetChildCount() };
+ assert(num_nodes > 0 && lod_grp->GetNumThresholds() == (num_nodes - 1));
+
+ for (s32 i{ 0 }; i < num_nodes; ++i)
+ {
+ f32 lod_threshold{ -1.f };
+ if (i>0)
+ {
+ FbxDistance threshold;
+ lod_grp->GetThreshold(i-1, threshold);
+ lod_threshold = threshold.value() * _scene_scale;
+ }
+ get_meshes(node->GetChild(i), lod.meshes, (u32)lod.meshes.size(), lod_threshold);
+ }
+
+ if (lod.meshes.size()) _scene->lod_groups.emplace_back(lod);
+
+}
+
+EDITOR_INTERFACE void
+ImportFbx(const char* file, scene_data* data)
+{
+ assert(file && data);
+ scene scene{};
+
+ // ע: κεô˺Ҫʹõ߳
+ {
+ std::lock_guard lock{ fbx_mutex };
+ fbx_context fbx_context{ file, &scene, data };
+ if (fbx_context.is_valid())
+ {
+ fbx_context.get_scene();
+ }
+ else
+ {
+ return;
+ }
+ }
+
+ process_scene(scene, data->settings);
+ pack_data(scene, *data);
+}
+
+}
\ No newline at end of file
diff --git a/ContentTools/ContentTools/FbxImporter.h b/ContentTools/ContentTools/FbxImporter.h
new file mode 100644
index 0000000..2f9527d
--- /dev/null
+++ b/ContentTools/ContentTools/FbxImporter.h
@@ -0,0 +1,54 @@
+#pragma once
+#include "ToolsCommon.h"
+#include
+
+
+namespace XEngine::tools {
+
+struct scene_data;
+struct scene;
+struct mesh;
+struct geometry_import_settings;
+
+
+class fbx_context {
+public:
+ fbx_context(const char* file, scene* scene, scene_data* data)
+ :_scene_data{ data }, _scene{ scene }
+ {
+ assert(file && _scene && _scene_data);
+ if (initialize_fbx())
+ {
+ load_fbx_file(file);
+ assert(is_valid());
+ }
+ }
+
+ ~fbx_context()
+ {
+ _fbx_scene->Destroy();
+ _fbx_manager->Destroy();
+ ZeroMemory(this, sizeof(fbx_context));
+ }
+
+ void get_scene(FbxNode* root = nullptr);
+
+ constexpr bool is_valid() const { return _fbx_manager && _fbx_scene; }
+ constexpr f32 scene_scale() const { return _scene_scale; }
+private:
+
+ bool initialize_fbx();
+ void load_fbx_file(const char* file);
+ void get_meshes(FbxNode* node, utl::vector& meshes, u32 lod_id, f32 lod_threshold);
+ void get_mesh(FbxNodeAttribute* attribute, utl::vector& meshes, u32 lod_id, f32 lod_threshold);
+ void get_lod_group(FbxNodeAttribute* attribute);
+ bool get_mesh_data(FbxMesh* fbx_mesh, mesh& m);
+
+ scene* _scene{ nullptr };
+ scene_data* _scene_data{ nullptr };
+ FbxManager* _fbx_manager{ nullptr };
+ FbxScene* _fbx_scene{ nullptr };
+ f32 _scene_scale{ 1.0f };
+
+};
+}
diff --git a/ContentTools/ContentTools/Geometry.cpp b/ContentTools/ContentTools/Geometry.cpp
new file mode 100644
index 0000000..50c8b26
--- /dev/null
+++ b/ContentTools/ContentTools/Geometry.cpp
@@ -0,0 +1,677 @@
+#include "Geometry.h"
+#include "..\Engine\Utilities\IOStream.h"
+
+namespace XEngine::tools {
+
+namespace {
+
+using namespace math;
+using namespace DirectX;
+
+void
+recalculate_normals(mesh& m)
+{
+ const u32 num_indeices{ (u32)m.raw_indices.size() };
+ m.normals.resize(num_indeices);
+
+ for (u32 i{ 0 }; i < num_indeices; ++i)
+ {
+ const u32 i0{ m.raw_indices[i] };
+ const u32 i1{ m.raw_indices[++i] };
+ const u32 i2{ m.raw_indices[++i] };
+
+ vector v0{ XMLoadFloat3(&m.positions[i0]) };
+ vector v1{ XMLoadFloat3(&m.positions[i1]) };
+ vector v2{ XMLoadFloat3(&m.positions[i2]) };
+
+ vector e0{ v1 - v0 };
+ vector e1{ v2 - v0 };
+ vector n{ XMVector3Normalize(XMVector3Cross(e0,e1)) };
+
+ XMStoreFloat3(&m.normals[i], n);
+ m.normals[i - 1] = m.normals[i];
+ m.normals[i - 2] = m.normals[i];
+ }
+}
+
+
+/**
+ * @brief ķߣʵƽӲЧ
+ *
+ * úݸƽǶȣsmoothing_anglemeshķߡ
+ * ƽǶȽӽ 180 ȣӲ߷ߣӽ 0 ȣ߷ߣݽǶֵƽ
+ *
+ * @param m Ҫԭʼλúͷߡ
+ * @param smoothing_angle ƽǶȣλΪȡ
+ */
+void
+process_normals(mesh& m, f32 smoothing_angle)
+{
+ // ǶȵֵΪƽֵ
+ const f32 cos_alpha{ XMScalarCos(pi - smoothing_angle * pi / 180.f) };
+
+ // жǷΪӲ
+ const bool is_hard_edge{ XMScalarNearEqual(smoothing_angle, 180.f, epsilon) };
+ const bool is_soft_edge{ XMScalarNearEqual(smoothing_angle, 0.f, epsilon) };
+
+ // ȡͶ
+ const u32 num_idices{ (u32)m.raw_indices.size() };
+ const u32 num_vertices{ (u32)m.positions.size() };
+
+ // ȷͶЧ
+ assert(num_idices && num_vertices);
+
+ // ĴСڴ洢µĶ
+ m.indices.resize(num_idices);
+
+ // һñڴ洢ÿӦԭʼб
+ // ÿһverticeӦ4pointοhoudini
+ utl::vector> idx_ref(num_vertices);
+ for (u32 i{ 0 }; i < num_idices; ++i)
+ idx_ref[m.raw_indices[i]].emplace_back(i);
+
+ // ÿvertice
+ for (u32 i{ 0 }; i < num_vertices; ++i)
+ {
+ // ȡǰԭʼб
+ auto& refs{ idx_ref[i] };
+ u32 num_refs{ (u32)refs.size() };
+
+ // ǰÿԭʼ
+ for (u32 j{ 0 }; j < num_refs; ++j)
+ {
+ m.indices[refs[j]] = (u32)m.vertices.size();
+ vertex& v{ m.vertices.emplace_back() };
+ v.position = m.positions[m.raw_indices[refs[j]]];
+
+ vector n1{ XMLoadFloat3(&m.normals[refs[j]]) };
+
+ // Ӳߣƽ
+ if (!is_hard_edge)
+ {
+ // ʣԭʼ
+ for (u32 k{ j + 1 }; k < num_refs; ++k)
+ {
+ f32 cos_theta{ 0.f };
+ vector n2{ XMLoadFloat3(&m.normals[refs[k]]) };
+
+ // ߣ㷨֮ĵ
+ // n2ѾnormalizeijΪ1ûнм㳤
+ // Ķȡn1ȻҲѾnormalizeĵܻںı
+ // Ҫ㳤
+ // cos(angle) = dot(n1,n2) / (||n1||*||n2||)
+
+ if (!is_soft_edge)
+ {
+ XMStoreFloat(&cos_theta, XMVector3Dot(n1, n2) * XMVector3ReciprocalLength(n1));
+ }
+
+ if (is_soft_edge || cos_theta >= cos_alpha)
+ {
+ n1 += n2;
+
+ m.indices[refs[k]] = m.indices[refs[j]];
+ refs.erase(refs.begin() + k);
+ --num_refs;
+ --k;
+ }
+ }
+ }
+
+ // һϲķߣ洢µĶ
+ XMStoreFloat3(&v.normal, XMVector3Normalize(n1));
+ }
+ }
+}
+
+void
+process_uvs(mesh& m)
+{
+ utl::vector old_vertices;
+ old_vertices.swap(m.vertices);
+ utl::vector old_indices(m.indices.size());
+ old_indices.swap(m.indices);
+
+
+ const u32 num_vertices{ (u32)old_vertices.size() };
+ const u32 num_indices{ (u32)old_indices.size() };
+ assert(num_vertices && num_indices);
+
+ utl::vector> idx_ref(num_vertices);
+ for (u32 i{ 0 }; i < num_indices; ++i)
+ idx_ref[old_indices[i]].emplace_back(i);
+
+
+ for (u32 i{ 0 }; i < num_vertices; ++i)
+ {
+ auto& refs{ idx_ref[i] };
+ u32 num_refs{ (u32)refs.size() };
+
+ for (u32 j{ 0 }; j < num_refs; ++j)
+ {
+ m.indices[refs[j]] = (u32)m.vertices.size();
+ vertex& v{ old_vertices[old_indices[refs[j]]] };
+ v.uv = m.uv_sets[0][refs[j]];
+ m.vertices.emplace_back(v);
+
+ for (u32 k{ j + 1 }; k < num_refs; ++k)
+ {
+ v2& uv1{ m.uv_sets[0][refs[k]] };
+ if (XMScalarNearEqual(v.uv.x, uv1.x, epsilon) &&
+ XMScalarNearEqual(v.uv.y, uv1.y, epsilon))
+ {
+ m.indices[refs[k]] = m.indices[refs[j]];
+ refs.erase(refs.begin() + k);
+ --num_refs;
+ --k;
+ }
+ }
+ }
+ }
+
+}
+
+u64
+get_vertex_element_size(elements::element_type::type elements_type)
+{
+ using namespace elements;
+ switch (elements_type)
+ {
+ case element_type::static_normal: return sizeof(static_normal);
+ case element_type::static_normal_texture: return sizeof(static_normal_texture);
+ case element_type::static_color: return sizeof(static_color);
+ case element_type::skeletal: return sizeof(skeletal);
+ case element_type::skeletal_color: return sizeof(skeletal_color);
+ case element_type::skeletal_normal: return sizeof(skeletal_normal);
+ case element_type::skeletal_normal_color: return sizeof(skeletal_normal_color);
+ case element_type::skeletal_normal_texture: return sizeof(skeletal_normal_texture);
+ case element_type::skeletal_normal_texture_color: return sizeof(skeletal_normal_texture_color);
+ }
+
+ return 0;
+}
+
+void
+pack_vertices(mesh& m)
+{
+ const u32 num_vertices{ (u32)m.vertices.size() };
+ assert(num_vertices);
+
+ m.position_buffer.resize(sizeof(math::v3) * num_vertices);
+ math::v3 *const position_buffer{ (math::v3 *const)m.position_buffer.data() };
+
+ for (u32 i{ 0 }; i < num_vertices; ++i)
+ {
+ position_buffer[i] = m.vertices[i].position;
+ }
+
+ struct u16v2 { u16 x, y; };
+ struct u8v3 { u8 x, y, z; };
+
+ utl::vector t_signs(num_vertices);
+ utl::vector normals(num_vertices);
+ utl::vector tangents(num_vertices);
+ utl::vector joint_weights(num_vertices);
+
+ if (m.elements_type & elements::element_type::static_normal)
+ {
+ for (u32 i{ 0 }; i < num_vertices; ++i)
+ {
+ vertex& v{ m.vertices[i] };
+ t_signs[i] = { (u8)((v.normal.z > 0.f) << 1) };
+ normals[i] = {
+ (u16)pack_float<16>(v.normal.x, -1.f, 1.f),
+ (u16)pack_float<16>(v.normal.y, -1.f, 1.f)
+ };
+ }
+
+
+ if (m.elements_type & elements::element_type::static_normal_texture) {
+ for (u32 i{ 0 }; i < num_vertices; ++i)
+ {
+ vertex& v{ m.vertices[i] };
+ t_signs[i] |= (u8)((v.tangent.w > 0.f) && (v.normal.z > 0.f));
+ tangents[i] = {
+ (u16)pack_float<16>(v.tangent.x, -1.f, 1.f),
+ (u16)pack_float<16>(v.tangent.y, -1.f, 1.f)
+ };
+ }
+ }
+ }
+
+ if (m.elements_type & elements::element_type::skeletal) {
+ for (u32 i{ 0 }; i < num_vertices; ++i)
+ {
+ vertex& v{ m.vertices[i] };
+ //pack [0,1] to [0,255]
+ joint_weights[i] = {
+ (u8)pack_unit_float<8>(v.joint_weights.x),
+ (u8)pack_unit_float<8>(v.joint_weights.y),
+ (u8)pack_unit_float<8>(v.joint_weights.z)
+ };
+ }
+ }
+
+ m.element_buffer.resize(get_vertex_element_size(m.elements_type) * num_vertices);
+ using namespace elements;
+
+
+ switch (m.elements_type)
+ {
+
+ case element_type::static_color:
+ {
+ static_color *const element_buffer{ (static_color *const)m.element_buffer.data() };
+ for (u32 i{ 0 }; i < num_vertices; ++i)
+ {
+ vertex& v{ m.vertices[i] };
+ element_buffer[i] = { {v.red, v.gree, v.blue}, {} };
+ }
+ }
+ break;
+ case element_type::static_normal:
+ {
+ static_normal *const element_buffer{ (static_normal *const)m.element_buffer.data() };
+ for (u32 i{ 0 }; i < num_vertices; ++i)
+ {
+ vertex& v{ m.vertices[i] };
+ element_buffer[i] = { {v.red, v.gree, v.blue}, t_signs[i], {normals[i].x, normals[i].y} };
+ }
+ }
+ break;
+ case element_type::static_normal_texture:
+ {
+ static_normal_texture *const element_buffer{ (static_normal_texture *const)m.element_buffer.data() };
+ for (u32 i{ 0 }; i < num_vertices; ++i)
+ {
+ vertex& v{ m.vertices[i] };
+ element_buffer[i] = { {v.red, v.gree, v.blue}, t_signs[i],
+ {normals[i].x, normals[i].y}, {tangents[i].x, tangents[i].y},
+ v.uv };
+ }
+ }
+ break;
+ case element_type::skeletal:
+ {
+ skeletal *const element_buffer{ (skeletal *const)m.element_buffer.data() };
+ for (u32 i{ 0 }; i < num_vertices; ++i)
+ {
+ vertex& v{ m.vertices[i] };
+ const u16 indices[4]{ (u16)v.joint_indices.x, (u16)v.joint_indices.y, (u16)v.joint_indices.z,(u16)v.joint_indices.w };
+ element_buffer[i] = { {joint_weights[i].x,joint_weights[i].y,joint_weights[i].z},{},
+ {indices[0],indices[1],indices[2],indices[3]}
+ };
+ }
+ }
+ break;
+ case element_type::skeletal_color:
+ {
+ skeletal_color *const element_buffer{ (skeletal_color *const)m.element_buffer.data() };
+ for (u32 i{ 0 }; i < num_vertices; ++i)
+ {
+ vertex& v{ m.vertices[i] };
+ const u16 indices[4]{ (u16)v.joint_indices.x, (u16)v.joint_indices.y, (u16)v.joint_indices.z,(u16)v.joint_indices.w };
+ element_buffer[i] = { {joint_weights[i].x,joint_weights[i].y,joint_weights[i].z},{},
+ {indices[0],indices[1],indices[2],indices[3]},
+ {v.red, v.gree, v.blue},{}
+ };
+ }
+ }
+ break;
+ case element_type::skeletal_normal:
+ {
+ skeletal_normal *const element_buffer{ (skeletal_normal *const)m.element_buffer.data() };
+ for (u32 i{ 0 }; i < num_vertices; ++i)
+ {
+ vertex& v{ m.vertices[i] };
+ const u16 indices[4]{ (u16)v.joint_indices.x, (u16)v.joint_indices.y, (u16)v.joint_indices.z,(u16)v.joint_indices.w };
+ element_buffer[i] = { {joint_weights[i].x,joint_weights[i].y,joint_weights[i].z},t_signs[i],
+ {indices[0],indices[1],indices[2],indices[3]},
+ {normals[i].x, normals[i].y}
+ };
+ }
+ }
+ break;
+ case element_type::skeletal_normal_color:
+ {
+ skeletal_normal_color *const element_buffer{ (skeletal_normal_color *const)m.element_buffer.data() };
+ for (u32 i{ 0 }; i < num_vertices; ++i)
+ {
+ vertex& v{ m.vertices[i] };
+ const u16 indices[4]{ (u16)v.joint_indices.x, (u16)v.joint_indices.y, (u16)v.joint_indices.z,(u16)v.joint_indices.w };
+ element_buffer[i] = { {joint_weights[i].x,joint_weights[i].y,joint_weights[i].z},t_signs[i],
+ {indices[0],indices[1],indices[2],indices[3]},
+ {normals[i].x, normals[i].y},{v.red, v.gree, v.blue},{}
+ };
+ }
+ }
+ break;
+ case element_type::skeletal_normal_texture:
+ {
+ skeletal_normal_texture *const element_buffer{ (skeletal_normal_texture *const)m.element_buffer.data() };
+ for (u32 i{ 0 }; i < num_vertices; ++i)
+ {
+ vertex& v{ m.vertices[i] };
+ const u16 indices[4]{ (u16)v.joint_indices.x, (u16)v.joint_indices.y, (u16)v.joint_indices.z,(u16)v.joint_indices.w };
+ element_buffer[i] = { {joint_weights[i].x,joint_weights[i].y,joint_weights[i].z},t_signs[i],
+ {indices[0],indices[1],indices[2],indices[3]},
+ {normals[i].x, normals[i].y},{tangents[i].x, tangents[i].y},v.uv
+ };
+ }
+ }
+ break;
+ case element_type::skeletal_normal_texture_color:
+ {
+ skeletal_normal_texture_color *const element_buffer{ (skeletal_normal_texture_color *const)m.element_buffer.data() };
+ for (u32 i{ 0 }; i < num_vertices; ++i)
+ {
+ vertex& v{ m.vertices[i] };
+ const u16 indices[4]{ (u16)v.joint_indices.x, (u16)v.joint_indices.y, (u16)v.joint_indices.z,(u16)v.joint_indices.w };
+ element_buffer[i] = { {joint_weights[i].x,joint_weights[i].y,joint_weights[i].z},t_signs[i],
+ {indices[0],indices[1],indices[2],indices[3]},
+ {normals[i].x, normals[i].y},{tangents[i].x, tangents[i].y},v.uv,
+ {v.red, v.gree, v.blue},{}
+ };
+ }
+ }
+ break;
+
+ }
+}
+
+void
+determin_elements_type(mesh&m)
+{
+ using namespace elements;
+ if (m.normals.size())
+ {
+ if (m.uv_sets.size() && m.uv_sets[0].size())
+ {
+ m.elements_type = element_type::static_normal_texture;
+ }
+ else
+ {
+ m.elements_type = element_type::static_normal;
+ }
+ }
+ else if (m.colors.size())
+ {
+ m.elements_type = element_type::static_color;
+ }
+}
+
+void
+process_vertices(mesh & m, const geometry_import_settings & settings)
+{
+ assert((m.raw_indices.size() % 3) == 0);
+ if (settings.calculate_normals || m.normals.empty())
+ {
+ recalculate_normals(m);
+ }
+
+ process_normals(m, settings.smoothing_angle);
+
+ if (!m.uv_sets.empty())
+ {
+ process_uvs(m);
+ }
+
+ determin_elements_type(m);
+ pack_vertices(m);
+
+}
+
+
+u64
+get_mesh_size(const mesh & m)
+{
+ const u64 num_vertices{ m.vertices.size() };
+ const u64 position_buffer_size{ m.position_buffer.size() };
+ assert(position_buffer_size == sizeof(math::v3) * num_vertices);
+ const u64 element_buffer_size{ m.element_buffer.size() };
+ assert(element_buffer_size == get_vertex_element_size(m.elements_type) * num_vertices);
+ const u64 index_size{ (num_vertices < (1 << 16)) ? sizeof(u16) : sizeof(u32) };
+ const u64 index_buffer_size{ index_size * m.indices.size() };
+ constexpr u64 su32{ sizeof(u32) };
+ const u64 size{
+ su32 + m.name.size() + // mesh name length ad room for mesh name string
+ su32 + // lod id
+ su32 + // vertex size
+ su32 + // element type
+ su32 + // number of vertices
+ su32 + // index size( 16 or 32 )
+ su32 + // number of indices
+ sizeof(f32) + // LOD threshold
+ position_buffer_size + //room for vertices positions
+ element_buffer_size + //room for vertex elements
+ index_buffer_size // room for indices
+ };
+
+ return size;
+}
+
+u64
+get_scene_size(const scene & scene)
+{
+ constexpr u64 su32(sizeof(u32));
+ u64 size
+ {
+ su32 + // name
+ scene.name.size() + // room for scene name string
+ su32 // number of LODS
+ };
+
+ for (auto& lod : scene.lod_groups)
+ {
+ u64 lod_size
+ {
+ su32 + lod.name.size() + // LOD name length and room for LPD name string
+ su32 // number of meshes in this LOD
+ };
+
+ for (auto& m : lod.meshes)
+ {
+ lod_size += get_mesh_size(m);
+ }
+
+ size += lod_size;
+ }
+
+ return size;
+}
+
+void
+pack_mesh_data(const mesh & m, utl::blob_stream_writer& blob)
+{
+ // mesh name
+ blob.write((u32)m.name.size());
+ blob.write(m.name.c_str(), m.name.size());
+
+ // lod id
+ blob.write(m.lod_id);
+
+ // vertex element size
+ const u32 elements_size{ (u32)get_vertex_element_size(m.elements_type) };
+ blob.write(elements_size);
+
+ // elements type enumeration
+ blob.write((u32)m.elements_type);
+
+ // number of vertices
+ const u32 num_vertices{ (u32)m.vertices.size() };
+ blob.write(num_vertices);
+
+ // index size (16 or 32)
+ const u32 index_size{ (num_vertices < (1 << 16)) ? sizeof(u16) : sizeof(u32) };
+ blob.write(index_size);
+
+ // number of indices
+ const u32 num_indices{ (u32)m.indices.size() };
+ blob.write(num_indices);
+ // LOD threshold
+ blob.write(m.lod_threshold);
+
+ // position buffer
+ assert(m.position_buffer.size() == sizeof(math::v3) * num_vertices);
+ blob.write(m.position_buffer.data(), m.position_buffer.size());
+
+ // element buffer
+ assert(m.element_buffer.size() == elements_size * num_vertices);
+ blob.write(m.element_buffer.data(), m.element_buffer.size());
+ // index data
+ const u32 index_buffer_size{ index_size * num_indices };
+ const u8* data{ (const u8*)m.indices.data() };
+ utl::vector indices;
+
+ if (index_size == sizeof(u16))
+ {
+ indices.resize(num_indices);
+ for (u32 i{ 0 }; i < num_indices; ++i) indices[i] = (u16)m.indices[i];
+ data = (const u8*)indices.data();
+ }
+
+ blob.write(data, index_buffer_size);
+
+}
+
+bool
+split_meshes_by_material(u32 material_idx, const mesh & m, mesh & submesh)
+{
+ submesh.name = m.name;
+ submesh.lod_threshold = m.lod_threshold;
+ submesh.lod_id = m.lod_id;
+ submesh.material_used.emplace_back(material_idx);
+ submesh.uv_sets.resize(m.uv_sets.size());
+
+ const u32 num_polys{ (u32)m.raw_indices.size() / 3 };
+ utl::vector vertex_ref(m.positions.size(), u32_invalid_id);
+
+ for (u32 i{ 0 }; i < num_polys; ++i)
+ {
+ const u32 mtl_idx{ m.material_indices[i] };
+ if (mtl_idx != material_idx) continue;
+
+ const u32 index{ i * 3 };
+ for (u32 j = index; j < index + 3; ++j)
+ {
+ const u32 v_idx{ m.raw_indices[i] };
+ if (vertex_ref[v_idx] != u32_invalid_id)
+ {
+ submesh.raw_indices.emplace_back(vertex_ref[v_idx]);
+ }
+ else
+ {
+ submesh.raw_indices.emplace_back((u32)submesh.positions.size());
+ vertex_ref[v_idx] = submesh.raw_indices.back();
+ submesh.positions.emplace_back(m.positions[v_idx]);
+ }
+
+ if (m.normals.size())
+ {
+ submesh.normals.emplace_back(m.normals[j]);
+ }
+
+ if (m.tangents.size())
+ {
+ submesh.tangents.emplace_back(m.tangents[j]);
+ }
+
+ for (u32 k{ 0 }; k < m.uv_sets.size(); ++k)
+ {
+ if (m.uv_sets[k].size())
+ {
+ submesh.uv_sets[k].emplace_back(m.uv_sets[k][j]);
+ }
+ }
+ }
+ }
+
+
+ assert((submesh.raw_indices.size() % 3) == 0);
+ return !submesh.raw_indices.empty();
+}
+
+
+void
+split_meshes_by_material(scene & scene)
+{
+ for (auto& lod : scene.lod_groups)
+ {
+ utl::vector new_meshes;
+
+ for (auto& m : lod.meshes)
+ {
+ const u32 num_materials{ (u32)m.material_used.size() };
+ if (num_materials > 1)
+ {
+ for (u32 i{ 0 }; i < num_materials; ++i)
+ {
+ mesh submesh{};
+ if (split_meshes_by_material(m.material_used[i], m, submesh))
+ {
+ new_meshes.emplace_back(submesh);
+ }
+ }
+ }
+ else
+ {
+ new_meshes.emplace_back(m);
+ }
+ }
+
+ new_meshes.swap(lod.meshes);
+ }
+}
+
+} // namespace
+
+void
+process_scene(scene& scene, const geometry_import_settings& settings)
+{
+ split_meshes_by_material(scene);
+
+ for (auto& lod : scene.lod_groups)
+ for (auto& m : lod.meshes)
+ {
+ process_vertices(m, settings);
+ }
+}
+
+
+
+void
+pack_data(const scene& scene, scene_data& data)
+{
+ const u64 scene_size{ get_scene_size(scene) };
+ data.buffer_size = (u32)scene_size;
+ data.buffer = (u8*)CoTaskMemAlloc(scene_size);
+ assert(data.buffer);
+
+ utl::blob_stream_writer blob{ data.buffer, data.buffer_size };
+
+ // scene name
+ blob.write((u32)scene.name.size());
+ blob.write(scene.name.c_str(), scene.name.size());
+
+ // number of LODS
+ blob.write((u32)scene.lod_groups.size());
+
+ for (auto& lod : scene.lod_groups)
+ {
+ // LOD name
+ blob.write((u32)lod.name.size());
+ blob.write(lod.name.c_str(), lod.name.size());
+
+ // Number of meshes in this LOD
+ blob.write((u32)lod.meshes.size());
+
+ for (auto& m : lod.meshes)
+ {
+ pack_mesh_data(m, blob);
+ }
+ }
+
+ assert(scene_size == blob.offset());
+}
+
+}
\ No newline at end of file
diff --git a/ContentTools/ContentTools/Geometry.h b/ContentTools/ContentTools/Geometry.h
new file mode 100644
index 0000000..8424612
--- /dev/null
+++ b/ContentTools/ContentTools/Geometry.h
@@ -0,0 +1,175 @@
+#pragma once
+#include "ToolsCommon.h"
+
+
+namespace XEngine::tools {
+
+namespace elements {
+struct element_type {
+
+ enum type :u32 {
+ position_only = 0x00,
+ static_normal = 0x01,
+ static_normal_texture = 0x03,
+ static_color = 0x04,
+ skeletal = 0x08,
+ skeletal_color = skeletal | static_color,
+ skeletal_normal = skeletal | static_normal,
+ skeletal_normal_color = skeletal_normal | skeletal_color,
+ skeletal_normal_texture = skeletal | static_normal_texture,
+ skeletal_normal_texture_color = skeletal_normal_texture | static_color
+
+ };
+};
+
+struct static_color
+{
+ u8 color[3];
+ u8 pad;
+};
+
+struct static_normal
+{
+ u8 color[3];
+ u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
+ u16 normal[2];
+};
+
+struct static_normal_texture
+{
+ u8 color[3];
+ u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
+ u16 normal[2];
+ u16 tangent[2];
+ math::v2 uv;
+};
+
+struct skeletal
+{
+ u8 joint_weights[3];
+ u8 pad;
+ u16 joint_indices[4];
+};
+
+struct skeletal_color
+{
+ u8 joint_weights[3];
+ u8 pad;
+ u16 joint_indices[4];
+ u8 color[3];
+ u8 pad2;
+};
+
+struct skeletal_normal
+{
+ u8 joint_weights[3];
+ u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
+ u16 joint_indices[4];
+ u16 normal[2];
+};
+
+struct skeletal_normal_color
+{
+ u8 joint_weights[3];
+ u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
+ u16 joint_indices[4];
+ u16 normal[2];
+ u8 color[3];
+ u8 pad;
+};
+
+struct skeletal_normal_texture
+{
+ u8 joint_weights[3];
+ u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
+ u16 joint_indices[4];
+ u16 normal[2];
+ u16 tangent[2];
+ math::v2 uv;
+};
+
+struct skeletal_normal_texture_color
+{
+ u8 joint_weights[3];
+ u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
+ u16 joint_indices[4];
+ u16 normal[2];
+ u16 tangent[2];
+ math::v2 uv;
+ u8 color[3];
+ u8 pad;
+};
+
+} // namespace elements
+
+struct vertex
+{
+ math::v4 tangent{};
+ math::v4 joint_weights{};
+ math::u32v4 joint_indices{ u32_invalid_id,u32_invalid_id ,u32_invalid_id ,u32_invalid_id };
+ math::v3 position{};
+ math::v3 normal{};
+ math::v2 uv{};
+ u8 red{}, gree{}, blue{};
+ u8 pad;
+};
+
+struct mesh
+{
+ utl::vector positions;
+ utl::vector normals;
+ utl::vector colors;
+ utl::vector tangents;
+ utl::vector> uv_sets;
+ utl::vector material_indices;
+ utl::vector material_used;
+
+ utl::vector raw_indices;
+
+
+ // Intermediate data
+ utl::vector vertices;
+ utl::vector indices;
+
+ // Output data
+ std::string name;
+ elements::element_type::type elements_type;
+ utl::vector position_buffer;
+ utl::vector element_buffer;
+ f32 lod_threshold{ -1.f };
+ u32 lod_id{ u32_invalid_id };
+};
+
+struct lod_group
+{
+ std::string name;
+ utl::vector meshes;
+};
+
+struct scene
+{
+ std::string name;
+ utl::vector lod_groups;
+};
+
+struct geometry_import_settings
+{
+ f32 smoothing_angle;
+ u8 calculate_normals;
+ u8 calculate_tangents;
+ u8 reverse_handedness;
+ u8 import_embeded_textures;
+ u8 import_animations;
+};
+
+struct scene_data
+{
+ u8* buffer;
+ u32 buffer_size;
+ geometry_import_settings settings;
+};
+
+
+void process_scene(scene& scene, const geometry_import_settings& settings);
+void pack_data(const scene& scene, scene_data& data);
+}
\ No newline at end of file
diff --git a/ContentTools/ContentTools/MeshPrimitives.cpp b/ContentTools/ContentTools/MeshPrimitives.cpp
new file mode 100644
index 0000000..276084b
--- /dev/null
+++ b/ContentTools/ContentTools/MeshPrimitives.cpp
@@ -0,0 +1,341 @@
+#include "MeshPrimitives.h"
+#include "Geometry.h"
+
+
+namespace XEngine::tools
+{
+using namespace math;
+using namespace DirectX;
+
+namespace {
+using primitive_mesh_creator = void(*)(scene&, const primitive_init_info& info);
+
+void create_plane(scene& scene, const primitive_init_info& info);
+void create_cube(scene& scene, const primitive_init_info& info);
+void create_uv_sphere(scene& scene, const primitive_init_info& info);
+void create_ico_sphere(scene& scene, const primitive_init_info& info);
+void create_cylinder(scene& scene, const primitive_init_info& info);
+void create_capsule(scene& scene, const primitive_init_info& info);
+
+primitive_mesh_creator creators[]
+{
+ create_plane,
+ create_cube,
+ create_uv_sphere,
+ create_ico_sphere,
+ create_cylinder,
+ create_capsule,
+};
+
+struct axis
+{
+ enum : u32 {
+ x = 0,
+ y = 1,
+ z = 2,
+ };
+};
+
+
+/**
+ * @brief һƽmesh
+ *
+ * ƽͨߴ硢ֶΡUV 귶ΧԼ˳
+ * Ҫע⣬positionuvs¼Ƕ㣬ںindicesuv_setϸڼ¼uvķ䡣
+ *
+ * @param info ƽߴͷֶϢĽṹ塣
+ * @param horizontal_index ˮƽ (0: x, 1: y, 2: z)ĬΪ axis::x
+ * @param vertical_index ֱ (0: x, 1: y, 2: z)ĬΪ axis::z
+ * @param flip_winding Ƿת˳ĬΪ false
+ * @param offset ƽijʼλƫƣĬΪ {-0.5f, 0.f, -0.5f}
+ * @param u_range U ķΧĬΪ {0.f, 1.f}
+ * @param v_range V ķΧĬΪ {0.f, 1.f}
+ * @return ƽ
+ */
+mesh
+create_plane(const primitive_init_info& info,
+ u32 horizontal_index = axis::x, u32 vertical_index = axis::z, bool flip_winding = false,
+ v3 offset = { -0.5f,0.f,-0.5f }, v2 u_range = { 0.f,1.f }, v2 v_range = { 0.f, 1.f })
+{
+ // ϷԼ
+ assert(horizontal_index < 3 && vertical_index < 3);
+ assert(horizontal_index != vertical_index);
+
+ // ˮƽʹֱķֶںΧ
+ const u32 horizontal_count{ clamp(info.segments[horizontal_index], 1u ,10u) };
+ const u32 vertical_count{ clamp(info.segments[vertical_index], 1u ,10u) };
+
+ // ˮƽʹֱIJ
+ const f32 horizontal_step{ 1.f / horizontal_count };
+ const f32 vertical_step{ 1.f / vertical_count };
+
+ // UV IJ
+ const f32 u_step{ (u_range.y - u_range.x) / horizontal_count };
+ const f32 v_step{ (v_range.y - v_range.x) / vertical_count };
+
+ // UV
+ mesh m{};
+ m.name = "plane";
+
+ utl::vector uvs;
+
+ // ɶ UV
+ for (u32 j{ 0 }; j <= vertical_count; ++j)
+ for (u32 i{ 0 }; i <= horizontal_count; ++i)
+ {
+ // 㶥λ
+ v3 position{ offset };
+ f32* const as_array{ &position.x };
+ as_array[horizontal_index] += i * horizontal_step;
+ as_array[vertical_index] += j * vertical_step;
+
+ // Ӷλõ
+ m.positions.emplace_back(position.x * info.size.x, position.y * info.size.y, position.z * info.size.z);
+
+ // UV
+ v2 uv{ u_range.x, 1.f - v_range.x };
+ uv.x += i * u_step;
+ uv.y -= j * v_step;
+
+ // UV 굽
+ uvs.emplace_back(uv);
+ }
+
+ //
+ assert(m.positions.size() == (((u64)horizontal_count + 1) * ((u64)vertical_count + 1)));
+
+ // ÿж
+ const u32 row_length{ horizontal_count + 1 };
+
+ //
+ for (u32 j{ 0 }; j < vertical_count; ++j)
+ {
+ for (u32 i{ 0 }; i < horizontal_count; ++i)
+ {
+ // ıĸ
+ const u32 index[4]
+ {
+ i + j * row_length,
+ i + (j + 1) * row_length,
+ (i + 1) + j * row_length,
+ (i + 1) + (j + 1) * row_length,
+ };
+
+ // flip_winding ˳
+ // ǶӦpositionš
+ m.raw_indices.emplace_back(index[0]);
+ m.raw_indices.emplace_back(index[flip_winding ? 2 : 1]);
+ m.raw_indices.emplace_back(index[flip_winding ? 1 : 2]);
+
+ m.raw_indices.emplace_back(index[2]);
+ m.raw_indices.emplace_back(index[flip_winding ? 3 : 1]);
+ m.raw_indices.emplace_back(index[flip_winding ? 1 : 3]);
+ }
+ }
+
+ //
+ const u32 num_indices{ 3 * 2 * horizontal_count * vertical_count };
+ assert(m.raw_indices.size() == num_indices);
+
+ m.uv_sets.resize(1);
+
+ // UV
+ // Ӧÿ㡣
+ for (u32 i{ 0 }; i < num_indices; ++i)
+ {
+ m.uv_sets[0].emplace_back(uvs[m.raw_indices[i]]);
+ }
+
+ return m;
+}
+
+mesh
+create_uv_sphere(const primitive_init_info& info)
+{
+ const u32 phi_count{ clamp(info.segments[axis::x], 3u, 64u) };
+ const u32 theta_count{ clamp(info.segments[axis::y], 2u, 64u) };
+ const f32 theta_step{ pi / theta_count };
+ const f32 phi_step{ two_pi / phi_count };
+ const u32 num_vertices{ 2 + phi_count * (theta_count - 1) };
+ const u32 num_indices{ 2 * 3 * phi_count + 2 * 3 * phi_count * (theta_count - 2) };
+
+
+ mesh m{};
+ m.name = "uv_sphere";
+ m.positions.resize(num_vertices);
+
+ // add the top vertex
+ u32 c{ 0 };
+ m.positions[c++] = { 0.f, info.size.y, 0.f };
+
+ for (u32 j{ 1 }; j <= (theta_count - 1); ++j)
+ {
+ const f32 theta{ j * theta_step };
+ for (u32 i{ 0 }; i < phi_count; ++i)
+ {
+ const f32 phi{ i * phi_step };
+ m.positions[c++] = {
+ info.size.x * XMScalarSin(theta) * XMScalarCos(phi),
+ info.size.y * XMScalarCos(theta),
+ -info.size.z * XMScalarSin(theta) * XMScalarSin(phi)
+ };
+ }
+ }
+
+
+ // add bottom vertex
+ m.positions[c++] = { 0.f, -info.size.y,0.f };
+ assert(c == num_vertices);
+
+ c = 0;
+ m.raw_indices.resize(num_indices);
+ utl::vector uvs(num_indices);
+ const f32 inv_theta_count{ 1.f / theta_count };
+ const f32 inv_phi_count{ 1.f / phi_count };
+
+
+ // indices for the top cap
+ for (u32 i{ 0 }; i < phi_count - 1; ++i)
+ {
+ uvs[c] = { (2 * i + 1) * 0.5f * inv_phi_count, 1.f };
+ m.raw_indices[c++] = 0;
+ uvs[c] = { i * inv_phi_count, 1.f - inv_theta_count };
+ m.raw_indices[c++] = i + 1;
+ uvs[c] = { (i + 1) * inv_phi_count, 1.f - inv_theta_count };
+ m.raw_indices[c++] = i + 2;
+ }
+
+ uvs[c] = { 1.f - 0.5f * inv_phi_count, 1.f };
+ m.raw_indices[c++] = 0;
+ uvs[c] = { 1.f - inv_phi_count, 1.f - inv_theta_count };
+ m.raw_indices[c++] = phi_count;
+ uvs[c] = { 1.f, 1.f - inv_theta_count };
+ m.raw_indices[c++] = 1;
+
+ // indices for the section
+ for (u32 j{ 0 }; j < (theta_count - 2); ++j)
+ {
+ for (u32 i{ 0 }; i < (phi_count - 1); ++i)
+ {
+ const u32 index[4]{
+ 1 + i + j * phi_count,
+ 1 + i + (j + 1) * phi_count,
+ 1 + (i + 1) + (j + 1) * phi_count,
+ 1 + (i + 1) + j * phi_count,
+ };
+ uvs[c] = { (i)*inv_phi_count, 1.f - (j + 1) * inv_theta_count };
+ m.raw_indices[c++] = index[0];
+ uvs[c] = { (i)*inv_phi_count, 1.f - (j + 2) * inv_theta_count };
+ m.raw_indices[c++] = index[1];
+ uvs[c] = { (i + 1) * inv_phi_count, 1.f - (j + 2) * inv_theta_count };
+ m.raw_indices[c++] = index[2];
+
+ uvs[c] = { (i)*inv_phi_count, 1.f - (j + 1) * inv_theta_count };
+ m.raw_indices[c++] = index[0];
+ uvs[c] = { (i + 1) * inv_phi_count, 1.f - (j + 2) * inv_theta_count };
+ m.raw_indices[c++] = index[2];
+ uvs[c] = { (i + 1) * inv_phi_count, 1.f - (j + 1) * inv_theta_count };
+ m.raw_indices[c++] = index[3];
+ }
+
+ const u32 index[4]{
+ phi_count + j * phi_count,
+ phi_count + (j + 1) * phi_count,
+ 1 + (j + 1) * phi_count,
+ 1 + j * phi_count
+ };
+
+ uvs[c] = { (1.f) - inv_phi_count, 1.f - (j + 1) * inv_theta_count };
+ m.raw_indices[c++] = index[0];
+ uvs[c] = { (1.f) - inv_phi_count, 1.f - (j + 2) * inv_theta_count };
+ m.raw_indices[c++] = index[1];
+ uvs[c] = { (1.f), 1.f - (j + 2) * inv_theta_count };
+ m.raw_indices[c++] = index[2];
+
+ uvs[c] = { (1.f) - inv_phi_count, 1.f - (j + 1) * inv_theta_count };
+ m.raw_indices[c++] = index[0];
+ uvs[c] = { (1.f), 1.f - (j + 2) * inv_theta_count };
+ m.raw_indices[c++] = index[2];
+ uvs[c] = { (1.f), 1.f - (j + 1) * inv_theta_count };
+ m.raw_indices[c++] = index[3];
+ }
+
+ // indices for the bottom
+ const u32 south_pole_index{ (u32)m.positions.size() - 1 };
+ for (u32 i{ 0 }; i < (phi_count - 1); ++i)
+ {
+ uvs[c] = { (2 * i + 1) * 0.5f * inv_phi_count, 0.f };
+ m.raw_indices[c++] = south_pole_index;
+ uvs[c] = { (i + 1) * inv_phi_count, inv_theta_count };
+ m.raw_indices[c++] = south_pole_index - phi_count + i + 1;
+ uvs[c] = { (i)*inv_phi_count, inv_theta_count };
+ m.raw_indices[c++] = south_pole_index - phi_count + i;
+ }
+
+ uvs[c] = { 1.f - 0.5f * inv_phi_count, 0.f };
+ m.raw_indices[c++] = south_pole_index;
+ uvs[c] = { 1.f, inv_theta_count };
+ m.raw_indices[c++] = south_pole_index - phi_count;
+ uvs[c] = { 1.f - inv_phi_count, inv_theta_count };
+ m.raw_indices[c++] = south_pole_index - 1;
+
+ m.uv_sets.emplace_back(uvs);
+
+ return m;
+}
+
+void
+create_plane(scene& scene, const primitive_init_info& info)
+{
+ lod_group lod{};
+ lod.name = "plane";
+ lod.meshes.emplace_back(create_plane(info));
+ scene.lod_groups.emplace_back(lod);
+}
+void
+create_cube(scene& scene, const primitive_init_info& info)
+{
+
+}
+void
+create_uv_sphere(scene& scene, const primitive_init_info& info)
+{
+ lod_group lod{};
+ lod.name = "uv_sphere";
+ lod.meshes.emplace_back(create_uv_sphere(info));
+ scene.lod_groups.emplace_back(lod);
+}
+void
+create_ico_sphere(scene& scene, const primitive_init_info& info)
+{
+
+}
+void
+create_cylinder(scene& scene, const primitive_init_info& info)
+{
+}
+void
+create_capsule(scene& scene, const primitive_init_info& info)
+{
+}
+
+static_assert(_countof(creators) == primitive_mesh_type::count);
+
+} // namespace
+
+
+EDITOR_INTERFACE void
+CreatePrimitiveMesh(scene_data* data, primitive_init_info* info)
+{
+ assert(data && info);
+ assert(info->type < primitive_mesh_type::count);
+ scene scene{};
+
+ creators[info->type](scene, *info);
+
+ data->settings.calculate_normals = 1;
+ process_scene(scene, data->settings);
+ pack_data(scene, *data);
+}
+
+}
\ No newline at end of file
diff --git a/ContentTools/ContentTools/MeshPrimitives.h b/ContentTools/ContentTools/MeshPrimitives.h
new file mode 100644
index 0000000..67709e5
--- /dev/null
+++ b/ContentTools/ContentTools/MeshPrimitives.h
@@ -0,0 +1,26 @@
+#pragma once
+#include "ToolsCommon.h"
+
+namespace XEngine::tools {
+
+enum primitive_mesh_type : u32
+{
+ plane,
+ cube,
+ uv_sphere,
+ ico_sphere,
+ cylinder,
+ capsule,
+
+ count
+};
+
+struct primitive_init_info
+{
+ primitive_mesh_type type;
+ u32 segments[3]{ 1,1,1 };
+ math::v3 size{ 1,1,1 };
+ u32 lod{ 0 };
+};
+
+}
\ No newline at end of file
diff --git a/ContentTools/ContentTools/ToolsCommon.h b/ContentTools/ContentTools/ToolsCommon.h
new file mode 100644
index 0000000..de19940
--- /dev/null
+++ b/ContentTools/ContentTools/ToolsCommon.h
@@ -0,0 +1,11 @@
+#pragma once
+#include "CommonHeader.h"
+#include
+
+#ifndef EDITOR_INTERFACE
+#define EDITOR_INTERFACE extern "C" __declspec(dllexport)
+#endif
+
+
+
+
diff --git a/ContentTools/Geometry.h b/ContentTools/Geometry.h
new file mode 100644
index 0000000..8424612
--- /dev/null
+++ b/ContentTools/Geometry.h
@@ -0,0 +1,175 @@
+#pragma once
+#include "ToolsCommon.h"
+
+
+namespace XEngine::tools {
+
+namespace elements {
+struct element_type {
+
+ enum type :u32 {
+ position_only = 0x00,
+ static_normal = 0x01,
+ static_normal_texture = 0x03,
+ static_color = 0x04,
+ skeletal = 0x08,
+ skeletal_color = skeletal | static_color,
+ skeletal_normal = skeletal | static_normal,
+ skeletal_normal_color = skeletal_normal | skeletal_color,
+ skeletal_normal_texture = skeletal | static_normal_texture,
+ skeletal_normal_texture_color = skeletal_normal_texture | static_color
+
+ };
+};
+
+struct static_color
+{
+ u8 color[3];
+ u8 pad;
+};
+
+struct static_normal
+{
+ u8 color[3];
+ u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
+ u16 normal[2];
+};
+
+struct static_normal_texture
+{
+ u8 color[3];
+ u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
+ u16 normal[2];
+ u16 tangent[2];
+ math::v2 uv;
+};
+
+struct skeletal
+{
+ u8 joint_weights[3];
+ u8 pad;
+ u16 joint_indices[4];
+};
+
+struct skeletal_color
+{
+ u8 joint_weights[3];
+ u8 pad;
+ u16 joint_indices[4];
+ u8 color[3];
+ u8 pad2;
+};
+
+struct skeletal_normal
+{
+ u8 joint_weights[3];
+ u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
+ u16 joint_indices[4];
+ u16 normal[2];
+};
+
+struct skeletal_normal_color
+{
+ u8 joint_weights[3];
+ u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
+ u16 joint_indices[4];
+ u16 normal[2];
+ u8 color[3];
+ u8 pad;
+};
+
+struct skeletal_normal_texture
+{
+ u8 joint_weights[3];
+ u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
+ u16 joint_indices[4];
+ u16 normal[2];
+ u16 tangent[2];
+ math::v2 uv;
+};
+
+struct skeletal_normal_texture_color
+{
+ u8 joint_weights[3];
+ u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
+ u16 joint_indices[4];
+ u16 normal[2];
+ u16 tangent[2];
+ math::v2 uv;
+ u8 color[3];
+ u8 pad;
+};
+
+} // namespace elements
+
+struct vertex
+{
+ math::v4 tangent{};
+ math::v4 joint_weights{};
+ math::u32v4 joint_indices{ u32_invalid_id,u32_invalid_id ,u32_invalid_id ,u32_invalid_id };
+ math::v3 position{};
+ math::v3 normal{};
+ math::v2 uv{};
+ u8 red{}, gree{}, blue{};
+ u8 pad;
+};
+
+struct mesh
+{
+ utl::vector positions;
+ utl::vector normals;
+ utl::vector colors;
+ utl::vector tangents;
+ utl::vector> uv_sets;
+ utl::vector material_indices;
+ utl::vector material_used;
+
+ utl::vector raw_indices;
+
+
+ // Intermediate data
+ utl::vector vertices;
+ utl::vector indices;
+
+ // Output data
+ std::string name;
+ elements::element_type::type elements_type;
+ utl::vector position_buffer;
+ utl::vector element_buffer;
+ f32 lod_threshold{ -1.f };
+ u32 lod_id{ u32_invalid_id };
+};
+
+struct lod_group
+{
+ std::string name;
+ utl::vector meshes;
+};
+
+struct scene
+{
+ std::string name;
+ utl::vector lod_groups;
+};
+
+struct geometry_import_settings
+{
+ f32 smoothing_angle;
+ u8 calculate_normals;
+ u8 calculate_tangents;
+ u8 reverse_handedness;
+ u8 import_embeded_textures;
+ u8 import_animations;
+};
+
+struct scene_data
+{
+ u8* buffer;
+ u32 buffer_size;
+ geometry_import_settings settings;
+};
+
+
+void process_scene(scene& scene, const geometry_import_settings& settings);
+void pack_data(const scene& scene, scene_data& data);
+}
\ No newline at end of file
diff --git a/ContentTools/ToolsCommon.h b/ContentTools/ToolsCommon.h
new file mode 100644
index 0000000..de19940
--- /dev/null
+++ b/ContentTools/ToolsCommon.h
@@ -0,0 +1,11 @@
+#pragma once
+#include "CommonHeader.h"
+#include
+
+#ifndef EDITOR_INTERFACE
+#define EDITOR_INTERFACE extern "C" __declspec(dllexport)
+#endif
+
+
+
+
diff --git a/Engine/Common/CommonHeader.h b/Engine/Common/CommonHeader.h
new file mode 100644
index 0000000..e76b044
--- /dev/null
+++ b/Engine/Common/CommonHeader.h
@@ -0,0 +1,55 @@
+#pragma once
+
+#ifdef _WIN64
+#pragma warning(disable: 4530)
+#endif // _WIN64
+
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+//#define USESTDFUNC
+#if defined USESTDFUNC
+#include
+#endif
+
+
+#if defined(_WIN64)
+#include
+#endif
+
+#ifndef DISABLE_COPY
+#define DISABLE_COPY(T) \
+ explicit T(const T&) = delete; \
+ T& operator=(const T&) = delete;
+#endif
+
+#ifndef DISABLE_MOVE
+#define DISABLE_MOVE(T) \
+ explicit T(T&&) = delete; \
+ T& operator=(T&&) = delete
+#endif
+
+#ifndef DISABLE_COPY_AND_MOVE
+#define DISABLE_COPY_AND_MOVE(T) DISABLE_COPY(T) DISABLE_MOVE(T);
+#endif
+
+#ifdef _DEBUG
+#define DEBUG_OP(x) x
+#else
+#define DEBUG_OP(x)
+#endif
+
+//Common Header
+#include "XEnginType.h"
+#include "..\Utilities\Math.h"
+#include "..\Utilities\MathTypes.h"
+#include "..\Utilities\Utilities.h"
+#include "Id.h"
+
diff --git a/Engine/Common/Id.h b/Engine/Common/Id.h
new file mode 100644
index 0000000..d743d8e
--- /dev/null
+++ b/Engine/Common/Id.h
@@ -0,0 +1,143 @@
+/**
+ * @file Id.h
+ * @brief 使用“索引 + 代数”打包方案的 32 位标识符工具。
+ * @details
+ * 位布局:
+ * - 低位:索引
+ * - 高位:代数
+ *
+ * 当索引被复用时,通过递增代数降低悬挂句柄误用风险。
+ */
+#pragma once
+#include "CommonHeader.h"
+
+
+namespace XEngine::id
+{
+/**
+ * @brief 标识符相关类型与常量。
+ */
+using id_type = u32;
+
+namespace detail {
+/**
+ * @brief 高位代数字段的位宽。
+ */
+constexpr u32 generation_bits{ 8 };
+/**
+ * @brief 低位索引字段的位宽。
+ */
+constexpr u32 index_bit{ sizeof(id_type) * 8 - generation_bits };
+/**
+ * @brief 用于提取索引字段的掩码。
+ */
+constexpr id_type index_mask{ (id_type{1} << index_bit) - 1 };
+/**
+ * @brief 右移后用于提取代数字段的掩码。
+ */
+constexpr id_type generation_mask{ (id_type{1} << generation_bits) - 1 };
+}
+/**
+ * @brief 表示无效标识符的哨兵值。
+ */
+constexpr id_type invalid_id{ (id_type)-1 };
+/**
+ * @brief 延迟回收删除项时的触发阈值。
+ */
+constexpr u32 min_deleted_elements{ 1024 };
+
+/**
+ * @brief 可容纳代数字段的最小无符号整型。
+ */
+using generation_type = std::conditional_t, u32>;
+static_assert(sizeof(generation_type) * 8 >= detail::generation_bits);
+static_assert((sizeof(id_type) - sizeof(generation_type)) > 0);
+
+
+
+/**
+ * @brief 判断标识符是否有效。
+ * @param id 打包后的标识符。
+ * @return 当 @p id 不是无效哨兵值时返回 true。
+ */
+constexpr bool
+is_valid(id_type id)
+{
+ return id != invalid_id;
+}
+
+/**
+ * @brief 从打包标识符中提取索引字段。
+ * @param id 打包后的标识符。
+ * @return 低位索引字段。
+ * @pre 提取后的索引不能等于保留的全 1 索引值。
+ */
+constexpr id_type
+index(id_type id)
+{
+ id_type index{ id & detail::index_mask };
+ assert(index != detail::index_mask);
+ return index;
+}
+
+/**
+ * @brief 从打包标识符中提取代数字段。
+ * @param id 打包后的标识符。
+ * @return 高位代数字段。
+ */
+constexpr id_type
+generation(id_type id)
+{
+ return (id >> detail::index_bit) & detail::generation_mask;
+}
+
+/**
+ * @brief 为同一索引生成下一代标识符。
+ * @param id 当前打包标识符。
+ * @return 索引不变且代数递增后的标识符。
+ * @pre 代数递增后不能溢出到保留范围。
+ */
+constexpr id_type
+new_generation(id_type id)
+{
+ const id_type generation{ id::generation(id) + 1 };
+ assert(generation < (((u64)1 << detail::generation_bits) - 1));
+ return index(id) | (generation << detail::index_bit);
+}
+
+
+
+
+#if _DEBUG
+namespace detail {
+struct id_base
+{
+ constexpr explicit id_base(id_type id) : _id(id) {}
+ constexpr operator id_type() const { return _id; }
+private:
+ id_type _id;
+};
+}
+
+/**
+ * @brief 在调试构建下声明强类型标识符。
+ * @details
+ * 调试构建使用派生自 id_base 的包装类型。
+ * 发布构建退化为 id_type 别名以保持零开销抽象。
+ */
+#define DEFINE_TYPED_ID(name) \
+ struct name final : id::detail::id_base \
+ { \
+ constexpr explicit name(id::id_type id) \
+ : id_base{id}{} \
+ constexpr name() : id_base{ 0 }{} \
+ };
+#else
+/**
+ * @brief 发布模式下的零运行时开销类型别名。
+ */
+#define DEFINE_TYPED_ID(name) using name = id::id_type;
+#endif
+
+
+}
diff --git a/Engine/Common/XEnginType.h b/Engine/Common/XEnginType.h
new file mode 100644
index 0000000..6e768a2
--- /dev/null
+++ b/Engine/Common/XEnginType.h
@@ -0,0 +1,23 @@
+#pragma once
+#include
+
+using u8 = uint8_t;
+using u16 = uint16_t;
+using u32 = uint32_t;
+using u64 = uint64_t;
+
+
+using s8 = int8_t;
+using s16 = int16_t;
+using s32 = int32_t;
+using s64 = int64_t;
+
+
+
+constexpr u64 u64_invalid_id{ 0xffffffffffffffff };
+constexpr u32 u32_invalid_id{ 0xffffffff };
+constexpr u16 u16_invalid_id{ 0xffff };
+constexpr u8 u8_invalid_id { 0xff };
+
+
+using f32 = float;
\ No newline at end of file
diff --git a/Engine/Components/ComponentsCommon.h b/Engine/Components/ComponentsCommon.h
new file mode 100644
index 0000000..b5257a1
--- /dev/null
+++ b/Engine/Components/ComponentsCommon.h
@@ -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 {
+
+}
diff --git a/Engine/Components/Entity.cpp b/Engine/Components/Entity.cpp
new file mode 100644
index 0000000..dac945b
--- /dev/null
+++ b/Engine/Components/Entity.cpp
@@ -0,0 +1,108 @@
+#include "Entity.h"
+#include "Script.h"
+#include "Transform.h"
+
+namespace XEngine::game_entity {
+
+namespace {
+
+utl::vector transforms;
+utl::vector scripts;
+
+
+utl::vector generations;
+utl::deque 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];
+}
+
+}
\ No newline at end of file
diff --git a/Engine/Components/Entity.h b/Engine/Components/Entity.h
new file mode 100644
index 0000000..74fac4a
--- /dev/null
+++ b/Engine/Components/Entity.h
@@ -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);
+}
+}
diff --git a/Engine/Components/Script.cpp b/Engine/Components/Script.cpp
new file mode 100644
index 0000000..bab9a93
--- /dev/null
+++ b/Engine/Components/Script.cpp
@@ -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 entity_scripts;
+/**
+ * @brief script_id 索引到实例数组下标的映射。
+ */
+utl::vector id_mapping;
+
+/**
+ * @brief script_id 代数数组。
+ */
+utl::vector generations;
+/**
+ * @brief 可复用 script_id 队列。
+ */
+utl::deque free_ids;
+
+/**
+ * @brief 脚本对 Transform 的延迟修改缓存。
+ */
+utl::vector transform_cache;
+
+#if USE_TRANSFORM_CACHE_MAP
+std::unordered_map cache_map;
+#endif
+
+
+using script_registry = std::unordered_map;
+
+
+/**
+ * @brief 获取全局脚本工厂注册表。
+ * @return 注册表引用。
+ */
+script_registry&
+registery()
+{
+ static script_registry reg;
+ return reg;
+}
+#ifdef USE_WITH_EDITOR
+/**
+ * @brief 获取编辑器脚本名集合。
+ * @return 脚本名数组引用。
+ */
+utl::vector&
+script_names()
+{
+ static utl::vector 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
+
+/**
+ * @brief 导出脚本名列表给编辑器端。
+ * @details
+ * 返回值为 LPSAFEARRAY(SAFEARRAY*),元素类型为 BSTR。
+ * 内部流程:
+ * - 从 script_names() 读取脚本名数量;
+ * - 为空时返回 nullptr;
+ * - 使用 CComSafeArray 分配数组;
+ * - 逐项把 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 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
diff --git a/Engine/Components/Script.h b/Engine/Components/Script.h
new file mode 100644
index 0000000..8e8c134
--- /dev/null
+++ b/Engine/Components/Script.h
@@ -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);
+}
diff --git a/Engine/Components/Transform.cpp b/Engine/Components/Transform.cpp
new file mode 100644
index 0000000..6b86ec7
--- /dev/null
+++ b/Engine/Components/Transform.cpp
@@ -0,0 +1,213 @@
+#include "Transform.h"
+#include "Entity.h"
+
+namespace XEngine::transform {
+
+namespace {
+
+utl::vector to_world;
+utl::vector inv_world;
+utl::vector positions;
+utl::vector orientations;
+utl::vector rotations;
+utl::vector scales;
+utl::vector has_transform;
+utl::vector 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)];
+
+}
+
+}
\ No newline at end of file
diff --git a/Engine/Components/Transform.h b/Engine/Components/Transform.h
new file mode 100644
index 0000000..44508a8
--- /dev/null
+++ b/Engine/Components/Transform.h
@@ -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);
+
+}
diff --git a/Engine/Content/ContentLoader.cpp b/Engine/Content/ContentLoader.cpp
new file mode 100644
index 0000000..03d3f5d
--- /dev/null
+++ b/Engine/Content/ContentLoader.cpp
@@ -0,0 +1,217 @@
+/**
+ * @file ContentLoader.cpp
+ * @brief 关卡内容反序列化与实体创建流程实现。
+ * @details
+ * 主要流程为:读取二进制关卡文件 -> 解析组件块 -> 创建实体并绑定脚本/变换。
+ * 同时提供运行期清理逻辑,确保测试场景退出时能回收创建的实体与资源。
+ * 本实现仅在非 SHIPPING 且 Win64 条件下启用。
+ */
+#include "ContentLoader.h"
+#include "..\Components\Script.h"
+
+#include "..\EngineAPI\GameEntity.h"
+#include "..\EngineAPI\TransformComponent.h"
+#include "..\EngineAPI\ScriptComponent.h"
+#include "..\Components\Entity.h"
+#include "Graphics\Renderer.h"
+
+#include
+#include
+#include
+
+#if !defined(SHIPPING) && defined(_WIN64)
+
+namespace XEngine::content {
+namespace {
+
+/**
+ * @brief 关卡文件中的组件类型标签。
+ */
+enum component_type
+{
+ transform,
+ script,
+
+ count
+};
+
+/**
+ * @brief 已创建的实体缓存,用于卸载阶段统一回收。
+ */
+utl::vector entities;
+/**
+ * @brief 解析变换组件时复用的临时结构。
+ */
+transform::init_info transform_info{};
+/**
+ * @brief 解析脚本组件时复用的临时结构。
+ */
+script::init_info script_info{};
+
+
+/**
+ * @brief 从二进制流读取变换组件数据。
+ * @param data 当前读取游标,函数返回后移动到组件末尾。
+ * @param info 目标实体初始化信息。
+ * @return 读取成功返回 true。
+ */
+bool
+read_transform(const u8*& data, game_entity::entity_info& info)
+{
+ using namespace DirectX;
+ f32 rotation[3];
+
+ assert(!info.transform);
+ memcpy(&transform_info.position[0], data, sizeof(transform_info.position)); data += sizeof(transform_info.position);
+ memcpy(&rotation[0], data, sizeof(rotation)); data += sizeof(rotation);
+ memcpy(&transform_info.scale[0], data, sizeof(transform_info.scale)); data += sizeof(transform_info.scale);
+
+
+ XMFLOAT3A rot{ &rotation[0] };
+ XMVECTOR quat{ XMQuaternionRotationRollPitchYawFromVector(XMLoadFloat3A(&rot)) };
+ XMFLOAT4A rot_quat{};
+ XMStoreFloat4A(&rot_quat, quat);
+ memcpy(&transform_info.rotation[0], &rot_quat.x, sizeof(transform_info.rotation));
+
+ info.transform = &transform_info;
+ return true;
+}
+
+
+/**
+ * @brief 从二进制流读取脚本组件并查找脚本工厂。
+ * @param data 当前读取游标,函数返回后移动到组件末尾。
+ * @param info 目标实体初始化信息。
+ * @return 解析并找到脚本工厂返回 true。
+ */
+bool
+read_script(const u8*& data, game_entity::entity_info& info)
+{
+ assert(!info.script);
+ const u32 name_length{ *data }; data += sizeof(u32);
+ if (!name_length) return false;
+
+ assert(name_length < 256);
+ char script_name[256];
+ memcpy(&script_name, data, name_length); data += name_length;
+ script_name[name_length] = 0;
+ script_info.script_creator = script::detail::get_script_creator(script::detail::string_hash()(script_name));
+ info.script = &script_info;
+ return script_info.script_creator != nullptr;
+}
+
+using compoent_reader = bool(*)(const u8*&, game_entity::entity_info&);
+compoent_reader component_readers[]
+{
+ read_transform,
+ read_script,
+};
+
+static_assert(_countof(component_readers) == component_type::count);
+
+/**
+ * @brief 读取完整二进制文件到内存缓冲区。
+ * @param path 文件路径。
+ * @param data 输出缓冲区。
+ * @param size 输出字节数。
+ * @return 成功返回 true。
+ */
+bool
+read_file(std::filesystem::path path, std::unique_ptr&data, u64& size)
+{
+ if (!std::filesystem::exists(path)) return false;
+
+ size = std::filesystem::file_size(path);
+ assert(size);
+ if (!size) return false;
+ data = std::make_unique(size);
+ std::ifstream file{ path, std::ios::in | std::ios::binary };
+ if (!file || !file.read((char*)data.get(), size))
+ {
+ file.close();
+ return false;
+ }
+
+ file.close();
+ return true;
+}
+
+} // namespace
+
+
+/**
+ * @brief 加载 game.bin 并批量创建实体。
+ * @return 全部实体创建成功返回 true。
+ */
+bool
+load_games()
+{
+
+
+
+ std::unique_ptr game_data{};
+ u64 size{ 0 };
+ if (!read_file("game.bin", game_data, size)) return false;
+ assert(game_data.get());
+ const u8* at{ game_data.get() };
+ constexpr u32 su32{ sizeof(u32) };
+ const u32 num_entities{ *at }; at += su32;
+ if (!num_entities) return false;
+
+ for (u32 entity_index{ 0 }; entity_index < num_entities; ++entity_index)
+ {
+ game_entity::entity_info info{};
+
+ const u32 entity_type{ *at }; at += su32;
+ const u32 num_components{ *at }; at += su32;
+ if (!num_components)return false;
+
+ for (u32 component_index{ 0 }; component_index < num_components; ++component_index)
+ {
+ const u32 component_type{ *at }; at += su32;
+ assert(component_type < component_type::count);
+ if (!component_readers[component_type](at, info)) return false;
+ }
+
+ assert(info.transform);
+ game_entity::entity entity{ game_entity::create(info) };
+ if (!entity.is_valid()) return false;
+ entities.emplace_back(entity);
+ }
+
+ assert(at == game_data.get() + size);
+ return true;
+}
+
+
+/**
+ * @brief 回收 load_games 创建的实体对象。
+ */
+void
+unload_game()
+{
+ for (auto entity : entities)
+ {
+ game_entity::remove(entity.get_id());
+ }
+}
+
+
+
+
+
+/**
+ * @brief 加载引擎内置着色器二进制文件。
+ * @param shaders 输出字节缓冲区。
+ * @param size 输出字节大小。
+ * @return 加载成功返回 true。
+ */
+//bool
+//load_engine_shaders(std::unique_ptr&shaders, u64& size)
+//{
+// auto path = graphics::get_engine_shaders_path();
+// return read_file(path, shaders, size);
+//}
+
+}
+#endif //!defined(SHIPPING)
diff --git a/Engine/Content/ContentLoader.h b/Engine/Content/ContentLoader.h
new file mode 100644
index 0000000..48cd16f
--- /dev/null
+++ b/Engine/Content/ContentLoader.h
@@ -0,0 +1,35 @@
+/**
+ * @file ContentLoader.h
+ * @brief 编辑器/测试构建下的关卡内容加载入口声明。
+ * @details
+ * 本文件定义内容系统对外暴露的高层加载接口,负责:
+ * - 从磁盘加载测试关卡与实体描述;
+ * - 卸载已创建的实体与运行时资源;
+ * - 读取并返回引擎着色器二进制数据供渲染模块初始化。
+ */
+#pragma once
+#include "CommonHeader.h"
+
+#if !defined(SHIPPING)
+namespace XEngine::content {
+/**
+ * @brief 加载测试游戏内容并创建对应运行时对象。
+ * @return 加载成功返回 true,否则返回 false。
+ */
+bool load_games();
+/**
+ * @brief 卸载由 load_games 创建的内容与对象。
+ */
+void unload_game();
+
+
+/**
+ * @brief 从内容目录读取引擎着色器二进制文件。
+ * @param shader 输出的字节缓冲区。
+ * @param size 输出的缓冲区大小(字节)。
+ * @return 读取成功返回 true,否则返回 false。
+ */
+bool load_engine_shaders(std::unique_ptr&shader, u64 size);
+}
+
+#endif //!defined(SHIPPING)
diff --git a/Engine/Content/ContentToEngine.cpp b/Engine/Content/ContentToEngine.cpp
new file mode 100644
index 0000000..a2c0f17
--- /dev/null
+++ b/Engine/Content/ContentToEngine.cpp
@@ -0,0 +1,528 @@
+/**
+ * @file ContentToEngine.cpp
+ * @brief 内容二进制数据到渲染/资源句柄的转换实现。
+ * @details
+ * 本文件完成导入数据的运行时落地,包含:
+ * - 网格层级(LOD)结构重排与子网格 GPU 资源创建;
+ * - 着色器分组缓存与 key 索引查询;
+ * - 材质/纹理/几何资源的统一创建与释放入口。
+ */
+#include "ContentToEngine.h"
+#include "Graphics\Renderer.h"
+#include "Utilities\IOStream.h"
+
+namespace XEngine::content {
+namespace {
+
+/**
+ * @brief 几何层级缓冲区的视图封装。
+ * @details
+ * 对连续内存中的几何层级布局进行字段映射,提供阈值、LOD 偏移与 GPU ID 访问。
+ */
+class geometry_hierarchy_stream
+{
+public:
+ DISABLE_COPY_AND_MOVE(geometry_hierarchy_stream);
+
+ geometry_hierarchy_stream(u8 *const buffer, u32 lods = u32_invalid_id)
+ :_buffer{ buffer }
+ {
+ assert(buffer && lods);
+ if (lods != u32_invalid_id)
+ {
+ *((u32*)buffer) = lods;
+ }
+
+ _lod_count = *((u32*)buffer);
+ _thresholds = (f32*)(&buffer[sizeof(u32)]);
+ _lod_offsets = (lod_offset*)(&_thresholds[_lod_count]);
+ _gpu_ids = (id::id_type*)(&_lod_offsets[_lod_count]);
+ }
+
+ void gpu_ids(u32 lod, id::id_type*& ids, u32& id_count)
+ {
+ assert(lod < _lod_count);
+ ids = &_gpu_ids[_lod_offsets[lod].offset];
+ id_count = _lod_offsets[lod].count;
+ }
+
+ u32 lod_from_threshold(f32 threshold)
+ {
+ assert(threshold > 0);
+ if (_lod_count == 1) return 0;
+
+ for (u32 i{ _lod_count - 1 }; i > 0; --i)
+ {
+ if (_thresholds[i] <= threshold) return i;
+ }
+
+ assert(false);
+ return 0;
+ }
+
+ [[nodiscard]] constexpr u32 lod_count() const { return _lod_count; }
+ [[nodiscard]] constexpr f32* thresholds() const { return _thresholds; }
+ [[nodiscard]] constexpr lod_offset* lod_offsets() const { return _lod_offsets; }
+ [[nodiscard]] constexpr id::id_type* gpu_ids() const { return _gpu_ids; }
+private:
+ u8 *const _buffer;
+ f32* _thresholds;
+ lod_offset* _lod_offsets;
+ id::id_type* _gpu_ids;
+ u32 _lod_count;
+};
+
+
+/**
+ * @brief 支持 noexcept 移动的着色器组缓存容器。
+ */
+struct noexcept_map {
+ std::unordered_map> map;
+ noexcept_map() = default;
+ noexcept_map(const noexcept_map&) = default;
+ noexcept_map(noexcept_map&&) noexcept = default;
+ noexcept_map& operator=(const noexcept_map&) = default;
+ noexcept_map& operator=(noexcept_map&&) noexcept = default;
+
+};
+
+/**
+ * @brief 单子网格资源的指针标记位。
+ */
+constexpr uintptr_t single_mesh_marker{ (uintptr_t)0x01 };
+/**
+ * @brief 几何层级资源池。
+ */
+utl::free_list geometry_hierarchies;
+/**
+ * @brief 几何资源互斥锁。
+ */
+std::mutex geometry_mutex;
+
+/**
+ * @brief 着色器组资源池。
+ */
+utl::free_list shader_groups;
+/**
+ * @brief 着色器资源互斥锁。
+ */
+std::mutex shader_mutex;
+
+
+/**
+ * @brief 预估并计算几何层级缓冲区所需字节数。
+ * @param data 几何二进制输入数据。
+ * @return 所需缓冲区大小(字节)。
+ */
+u32
+get_geometry_hierarchy_buffer_size(const void *const data)
+{
+ assert(data);
+ utl::blob_stream_reader blob{ (const u8*)data };
+ const u32 lod_count{ blob.reader() };
+ assert(lod_count);
+ constexpr u32 su32{ sizeof(u32) };
+
+ u32 size{ su32 + (sizeof(f32) + sizeof(lod_offset)) * lod_count };
+
+ for (u32 lod_idx{ 0 }; lod_idx < lod_count; ++lod_idx)
+ {
+ // skip threshold
+ blob.skip(sizeof(f32));
+ // add size of gpu_ids
+ size += sizeof(id::id_type) * blob.reader();
+ // skip submesh data and goto next LOD
+ blob.skip(blob.reader());
+ }
+
+ return size;
+}
+
+/**
+ * @brief 创建多 LOD 网格层级资源。
+ * @param data 几何二进制输入数据。
+ * @return 几何内容资源 ID。
+ */
+//id::id_type
+//create_mesh_hierarchy(const void *const data)
+//{
+// assert(data);
+//
+// const u32 size{ get_geometry_hierarchy_buffer_size(data) };
+// u8 *const hierarchy_buffer{ (u8 *const)malloc(size) };
+//
+// utl::blob_stream_reader blob{ (const u8*)data };
+// const u32 lod_count{ blob.reader() };
+// assert(lod_count);
+// geometry_hierarchy_stream stream{ hierarchy_buffer, lod_count };
+// u32 submesh_index{ 0 };
+// id::id_type *const gpu_ids{ stream.gpu_ids() };
+//
+// for (u32 lod_idx{ 0 }; lod_idx < lod_count; ++lod_idx)
+// {
+// stream.thresholds()[lod_idx] = blob.reader();
+// const u32 id_count{ blob.reader() };
+// assert(id_count < (1 << 16));
+// stream.lod_offsets()[lod_idx] = { (u16)submesh_index, (u16)id_count };
+// blob.skip(sizeof(u32)); // skip sizeof submeshes
+// for (u32 id_idx{ 0 }; id_idx < id_count; ++id_idx)
+// {
+// const u8* at{ blob.position() };
+// gpu_ids[submesh_index++] = graphics::add_submesh(at);
+// blob.skip((u32)(at - blob.position()));
+// assert(submesh_index < (1 << 16));
+// }
+// }
+//
+// assert([&]()
+// {
+// f32 previous_theshold{ stream.thresholds()[0] };
+// for (u32 i{ 1 }; i < lod_count; ++i)
+// {
+// if (stream.thresholds()[i] <= previous_theshold) return false;
+// previous_theshold = stream.thresholds()[i];
+// }
+// return true;
+// }());
+//
+// static_assert(alignof(void*) > 2, "We need the least significant bit for th single mesh marker.");
+// std::lock_guard lock{ geometry_mutex };
+// return geometry_hierarchies.add(hierarchy_buffer);
+//}
+
+/**
+ * @brief 判断几何数据是否为单 LOD 单子网格。
+ * @param data 几何二进制输入数据。
+ * @return 单子网格返回 true。
+ */
+bool
+is_single_mesh(const void *const data)
+{
+ assert(data);
+ utl::blob_stream_reader blob{ (const u8*)data };
+ const u32 lod_count{ blob.reader() };
+ assert(lod_count);
+ if (lod_count > 1) return false;
+
+
+ blob.skip(sizeof(f32)); // skip threhold
+ const u32 submesh_count{ blob.reader() };
+ assert(submesh_count);
+ return submesh_count == 1;
+}
+
+/**
+ * @brief 创建单子网格资源并编码为伪指针句柄。
+ * @param data 几何二进制输入数据。
+ * @return 几何内容资源 ID。
+ */
+//id::id_type
+//create_single_submesh(const void *const data)
+//{
+// assert(data);
+// utl::blob_stream_reader blob{ (const u8*)data };
+// // skip lod_count, lod_threshold, submesh_count, and sizeof_submeshes
+// blob.skip(sizeof(u32) + sizeof(f32) + sizeof(u32) + sizeof(u32));
+// const u8* at{ blob.position() };
+// const id::id_type gpu_id{ graphics::add_submesh(at) };
+//
+// // create a fake pointer and put it in the geometry_hierarchies.
+// static_assert(sizeof(uintptr_t) > sizeof(id::id_type));
+// constexpr u8 shift_bits{ (sizeof(uintptr_t) - sizeof(id::id_type)) << 3 };
+// u8 *const fake_pointer{ (u8 *const)((((uintptr_t)gpu_id) << shift_bits) | single_mesh_marker) };
+// std::lock_guard lock{ geometry_mutex };
+// return geometry_hierarchies.add(fake_pointer);
+//}
+
+/**
+ * @brief 从单子网格伪指针解码 GPU 资源 ID。
+ * @param pointer 编码后的伪指针。
+ * @return GPU 子网格 ID。
+ */
+constexpr id::id_type
+gpu_id_from_fake_pointer(u8 *const pointer)
+{
+ assert((uintptr_t)pointer & single_mesh_marker);
+ static_assert(sizeof(uintptr_t) > sizeof(id::id_type));
+ constexpr u8 shift_bits{ (sizeof(uintptr_t) - sizeof(id::id_type)) << 3 };
+ return (((uintptr_t)pointer) >> shift_bits) & (uintptr_t)id::invalid_id;
+}
+//
+// data contain:
+//struct{
+// u32 lod_count,
+// struct{
+// f32 lod_threshold,
+// u32 submesh_count,
+// u32 sizeof_submeshes,
+// struct{
+// u32 element_size, u32 vertex_count,
+// u32 index_count, u32 elements_type, u32 primitive_topology
+// u8 positions[sizeof(f32) * 3 * vertex_count],
+// u8 elements[sizeof(element_size) * vertex_count],
+// u8 indices[index_size * index_count],
+// } submeshes[submesh_count]
+// }mesh_lods[lod_count]
+// } geometry;
+//
+// Output format
+//
+// struct{
+// u32 lod_count,
+// f32 thresholds[lod_count]
+// struct{
+// u16 offset,
+// u16 count
+// }lod_offsets[lod_count],
+// id::idtype gpu_ids[total_number_of_submeshes]
+//}geometry_hierarchy
+//
+//
+//
+//
+
+/**
+ * @brief 按输入几何形态创建对应资源。
+ * @param data 几何二进制输入数据。
+ * @return 几何内容资源 ID。
+ */
+//id::id_type
+//create_geometry_resource(const void *const data)
+//{
+// assert(data);
+// return is_single_mesh(data) ? create_single_submesh(data) : create_mesh_hierarchy(data);
+//}
+
+/**
+ * @brief 销毁几何资源并释放关联子网格。
+ * @param id 几何内容资源 ID。
+ */
+//void
+//destory_geometry_resource(id::id_type id)
+//{
+// std::lock_guard lock{ geometry_mutex };
+// u8 *const pointer{ geometry_hierarchies[id] };
+// if ((uintptr_t)pointer & single_mesh_marker)
+// {
+// graphics::remove_submesh(gpu_id_from_fake_pointer(pointer));
+// }
+// else
+// {
+// geometry_hierarchy_stream stream{ pointer };
+// const u32 lod_count{ stream.lod_count() };
+// u32 id_index{ 0 };
+// for (u32 lod{ 0 }; lod < lod_count; ++lod)
+// {
+// for (u32 i{ 0 }; i < stream.lod_offsets()[lod].count; ++i)
+// {
+// graphics::remove_submesh(stream.gpu_ids()[id_index++]);
+// }
+// }
+//
+// free(pointer);
+// }
+//
+// geometry_hierarchies.remove(id);
+//}
+
+/**
+ * @brief 创建材质资源。
+ * @param data 材质初始化数据。
+ * @return 材质资源 ID。
+ */
+//id::id_type
+//create_material_resource(const void *const data)
+//{
+// assert(data);
+// return graphics::add_material(*(const graphics::material_init_info *const)data);
+//}
+} // namespace
+
+/**
+ * @brief 销毁材质资源。
+ * @param id 材质资源 ID。
+ */
+//void
+//destory_material_resource(id::id_type id)
+//{
+// graphics::remove_material(id);
+//}
+
+/**
+ * @brief 统一资源创建入口。
+ * @param data 资源二进制数据。
+ * @param type 资源类型。
+ * @return 资源 ID。
+ */
+//id::id_type
+//create_resource(const void *const data, asset_type::type type)
+//{
+// assert(data);
+// id::id_type id{ id::invalid_id };
+//
+// switch (type)
+// {
+// case XEngine::content::asset_type::unknown: break;
+// case XEngine::content::asset_type::animation: break;
+// case XEngine::content::asset_type::audio: break;
+// case XEngine::content::asset_type::material: id = create_material_resource(data); break;
+// case XEngine::content::asset_type::mesh: id = create_geometry_resource(data); break;
+// case XEngine::content::asset_type::skeleton: break;
+// case XEngine::content::asset_type::texture: break;
+// }
+//
+// assert(id::is_valid(id));
+// return id;
+//
+//}
+
+
+/**
+ * @brief 统一资源销毁入口。
+ * @param id 资源 ID。
+ * @param type 资源类型。
+ */
+//void
+//destroy_resource(id::id_type id, asset_type::type type)
+//{
+// assert(id::is_valid(id));
+// switch (type)
+// {
+// case XEngine::content::asset_type::unknown: break;
+// case XEngine::content::asset_type::animation: break;
+// case XEngine::content::asset_type::audio: break;
+// case XEngine::content::asset_type::material: destory_material_resource(id); break;
+// case XEngine::content::asset_type::mesh: destory_geometry_resource(id); break;
+// case XEngine::content::asset_type::skeleton: break;
+// case XEngine::content::asset_type::texture: break;
+// default:
+// assert(false);
+// break;
+// }
+//}
+/**
+ * @brief 注册着色器组并复制其二进制数据。
+ * @param shaders 着色器数组。
+ * @param num_shaders 着色器数量。
+ * @param keys 查询键数组。
+ * @return 着色器组 ID。
+ */
+id::id_type
+add_shader_group(const u8 *const * shaders, u32 num_shaders, const u32 *const keys)
+{
+ assert(shaders && num_shaders && keys);
+ noexcept_map group;
+ for (u32 i{ 0 }; i < num_shaders; ++i)
+ {
+ assert(shaders[i]);
+ const compiled_shader_ptr shader_ptr{ (const compiled_shader_ptr)shaders[i] };
+ const u64 size{ shader_ptr->buffer_size()};
+ std::unique_ptr shader{ std::make_unique(size) };
+ memcpy(shader.get(), shaders[i], size);
+ group.map[keys[i]] = std::move(shader);
+ }
+ std::lock_guard lock{ shader_mutex };
+ return shader_groups.add(std::move(group));
+}
+/**
+ * @brief 删除着色器组。
+ * @param id 着色器组 ID。
+ */
+void
+remove_shader_group(id::id_type id)
+{
+ std::lock_guard lock{ shader_mutex };
+ assert(id::is_valid(id));
+
+ shader_groups[id].map.clear();
+ shader_groups.remove(id);
+}
+/**
+ * @brief 在着色器组内按键查找着色器。
+ * @param id 着色器组 ID。
+ * @param shader_key 查询键。
+ * @return 命中时返回着色器指针。
+ */
+compiled_shader_ptr
+get_shader(id::id_type id, u32 shader_key)
+{
+ std::lock_guard lock{ shader_mutex };
+ assert(id::is_valid(id));
+ for (const auto& [key, value] : shader_groups[id].map)
+ {
+ if (key == shader_key)
+ {
+ return (const compiled_shader_ptr)value.get();
+ }
+ }
+ assert(false);
+
+ return nullptr;
+}
+
+
+/**
+ * @brief 获取几何资源包含的全部子网格 GPU ID。
+ * @param geometry_content_id 几何内容资源 ID。
+ * @param id_count 输出数组长度。
+ * @param gpu_ids 输出数组。
+ */
+void
+get_submesh_gpu_id(id::id_type geometry_content_id, u32 id_count, id::id_type *const gpu_ids)
+{
+ std::lock_guard lock{ geometry_mutex };
+ u8 *const pointer{ geometry_hierarchies[geometry_content_id] };
+ if ((uintptr_t)pointer & single_mesh_marker)
+ {
+ assert(id_count == 1);
+ *gpu_ids = gpu_id_from_fake_pointer(pointer);
+ }
+ else
+ {
+ geometry_hierarchy_stream stream{ pointer };
+
+ assert([&]() {
+ const u32 lod_count{ stream.lod_count() };
+ const lod_offset lod_offset{ stream.lod_offsets()[lod_count - 1] };
+ const u32 gpu_id_count{ (u32)lod_offset.offset + (u32)lod_offset.count };
+ return gpu_id_count == id_count;
+ }());
+
+
+ memcpy(gpu_ids, stream.gpu_ids(), sizeof(id::id_type) * id_count);
+
+ }
+}
+
+/**
+ * @brief 按阈值查询各几何实例使用的 LOD 偏移。
+ * @param geometry_ids 几何资源 ID 数组。
+ * @param thresholds 每个实例的阈值数组。
+ * @param id_count 查询数量。
+ * @param offsets 输出 LOD 偏移数组。
+ */
+void
+get_lod_offset(const id::id_type *const geometry_ids, const f32 *const thresholds, u32 id_count, utl::vector& offsets)
+{
+ assert(geometry_ids && thresholds && id_count);
+ assert(offsets.empty());
+
+ std::lock_guard lock{ geometry_mutex };
+
+ for (u32 i{ 0 }; i < id_count; ++i)
+ {
+ u8 *const pointer{ geometry_hierarchies[geometry_ids[i]] };
+ if ((uintptr_t)pointer & single_mesh_marker)
+ {
+ assert(id_count == 1);
+ offsets.emplace_back(lod_offset{ 0,1 });
+ }
+ else
+ {
+ geometry_hierarchy_stream stream{ pointer };
+ const u32 lod{ stream.lod_from_threshold(thresholds[i]) };
+ offsets.emplace_back(stream.lod_offsets()[lod]);
+ }
+ }
+}
+}
diff --git a/Engine/Content/ContentToEngine.h b/Engine/Content/ContentToEngine.h
new file mode 100644
index 0000000..67e0bae
--- /dev/null
+++ b/Engine/Content/ContentToEngine.h
@@ -0,0 +1,133 @@
+/**
+ * @file ContentToEngine.h
+ * @brief 内容系统到运行时资源系统的数据桥接接口。
+ * @details
+ * 该文件描述资源导入后的统一输入格式与运行时句柄接口,核心能力包括:
+ * - 依据 asset_type 创建/销毁网格、材质、纹理等资源;
+ * - 管理变体着色器组并按 key 查询编译结果;
+ * - 提供几何 LOD 偏移与 GPU 子网格 ID 的查询能力。
+ */
+#pragma once
+#include "CommonHeader.h"
+
+
+namespace XEngine::content {
+
+/**
+ * @brief 资源类型枚举容器。
+ */
+struct asset_type {
+ /**
+ * @brief 运行时可识别的内容资源类型。
+ */
+ enum type : u32
+ {
+ unknown = 0,
+ animation,
+ audio,
+ material,
+ mesh,
+ skeleton,
+ texture,
+
+ count
+ };
+};
+
+/**
+ * @brief 指定某个 LOD 在子网格数组中的起始偏移与数量。
+ */
+struct lod_offset
+{
+ u16 offset;
+ u16 count;
+};
+
+
+typedef struct compiled_shader {
+
+ /**
+ * @brief 着色器哈希的固定字节数。
+ */
+ static constexpr u32 hash_length{ 16 };
+ /**
+ * @brief 获取字节码长度。
+ */
+ constexpr u64 byte_code_size() const { return _byte_code_size; }
+ /**
+ * @brief 获取哈希数据首地址。
+ */
+ constexpr const u8 *const hash() const { return &_hash[0]; }
+ /**
+ * @brief 获取字节码首地址。
+ */
+ constexpr const u8 *const byte_code() const { return &_byte_code; }
+ /**
+ * @brief 获取当前对象对应的总缓冲区长度。
+ */
+ constexpr const u64 buffer_size() const { return sizeof(_byte_code_size ) + hash_length + _byte_code_size; }
+ /**
+ * @brief 根据字节码大小计算总缓冲区长度。
+ */
+ constexpr static u64 buffer_size(u64 size) { return sizeof(_byte_code_size ) + hash_length + size; }
+
+private:
+ u64 _byte_code_size;
+ u8 _hash[hash_length];
+ u8 _byte_code;
+}const *compiled_shader_ptr;
+
+
+
+
+/**
+ * @brief 根据二进制内容创建对应运行时资源。
+ * @param data 编译后的资源二进制数据。
+ * @param type 资源类型。
+ * @return 新创建资源的 ID。
+ */
+id::id_type create_resource(const void *const data, asset_type::type type);
+/**
+ * @brief 销毁指定资源类型下的运行时资源。
+ * @param id 资源 ID。
+ * @param type 资源类型。
+ */
+void destroy_resource(id::id_type id, asset_type::type type);
+
+/**
+ * @brief 注册一组着色器变体。
+ * @param shaders 着色器二进制列表。
+ * @param num_shaders 着色器数量。
+ * @param keys 每个着色器对应的查询键。
+ * @return 着色器组 ID。
+ */
+id::id_type add_shader_group(const u8 *const * shaders, u32 num_shaders, const u32 *const keys);
+/**
+ * @brief 删除着色器组及其缓存数据。
+ * @param id 着色器组 ID。
+ */
+void remove_shader_group(id::id_type id);
+/**
+ * @brief 按组 ID 与键查询编译后着色器。
+ * @param id 着色器组 ID。
+ * @param shader_key 着色器键值。
+ * @return 命中时返回着色器描述,否则返回空。
+ */
+compiled_shader_ptr get_shader(id::id_type id, u32 shader_key);
+
+/**
+ * @brief 根据几何内容 ID 批量获取子网格 GPU 资源 ID。
+ * @param geometry_content_id 几何内容资源 ID。
+ * @param id_count 输出数组长度。
+ * @param gpu_ids 输出的 GPU ID 数组。
+ */
+void get_submesh_gpu_id(id::id_type geometry_content_id, u32 id_count, id::id_type *const gpu_ids);
+/**
+ * @brief 按阈值批量查询几何资源对应的 LOD 偏移信息。
+ * @param geometry_ids 几何资源 ID 数组。
+ * @param thresholds 每个几何对应的 LOD 阈值。
+ * @param id_count 查询数量。
+ * @param offsets 输出的 LOD 偏移信息。
+ */
+void get_lod_offset(const id::id_type *const geometry_ids, const f32 *const thresholds, u32 id_count, utl::vector& offsets);
+}
diff --git a/Engine/Core/EngineWin32.cpp b/Engine/Core/EngineWin32.cpp
new file mode 100644
index 0000000..cc79884
--- /dev/null
+++ b/Engine/Core/EngineWin32.cpp
@@ -0,0 +1,103 @@
+/**
+ * @file EngineWin32.cpp
+ * @brief Win32 运行模式下的引擎生命周期实现。
+ * @details
+ * 该文件负责连接平台消息循环与引擎帧循环:
+ * - 初始化阶段加载内容并创建主窗口;
+ * - 更新阶段驱动脚本系统与帧间休眠;
+ * - 关闭阶段销毁窗口并卸载内容资源。
+ */
+
+#if !defined(SHIPPING) && defined(_WIN64)
+#include "..\Content\ContentLoader.h"
+#include "..\Components\Script.h"
+#include "..\Platform\PlatformTypes.h"
+#include "..\Platform\Platform.h"
+#include "..\Graphics\Renderer.h"
+#include
+
+using namespace XEngine;
+
+namespace {
+
+/**
+ * @brief 游戏窗口渲染表面实例。
+ */
+graphics::render_surface game_window{};
+
+/**
+ * @brief 主窗口消息回调。
+ * @param hwnd 窗口句柄。
+ * @param msg 消息类型。
+ * @param wparam 消息参数。
+ * @param lparam 消息参数。
+ * @return 消息处理结果。
+ */
+LRESULT win_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+ switch (msg)
+ {
+ case WM_DESTROY:
+ {
+ if (game_window.window.is_closed())
+ {
+ PostQuitMessage(0);
+ return 0;
+ }
+ }
+ case WM_SYSCHAR:
+ if (wparam == VK_RETURN && (HIWORD(lparam) & KF_ALTDOWN))
+ {
+ game_window.window.set_fullscreen(!game_window.window.is_fullscreen());
+ return 0;
+ }
+ default:
+ break;
+ }
+
+ return DefWindowProc(hwnd, msg, wparam, lparam);
+}
+
+}
+
+/**
+ * @brief 初始化引擎运行环境。
+ * @return 初始化成功返回 true。
+ */
+bool
+engine_initialize()
+{
+ if (!XEngine::content::load_games()) return false;
+ platform::window_init_info info
+ {
+ &win_proc, nullptr, L"XGame"
+ };
+
+ game_window.window = platform::create_window(&info);
+ if (!game_window.window.is_valid()) return false;
+
+ return true;
+}
+/**
+ * @brief 执行单帧更新。
+ */
+void
+engine_update()
+{
+ XEngine::script::update(10.f);
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+}
+/**
+ * @brief 关闭引擎并释放运行时资源。
+ */
+void
+engine_shutdown()
+{
+ platform::remove_window(game_window.window.get_id());
+ XEngine::content::unload_game();
+}
+
+
+
+
+#endif //!defined(SHIPPING)
diff --git a/Engine/Core/MainWin32.cpp b/Engine/Core/MainWin32.cpp
new file mode 100644
index 0000000..3cff462
--- /dev/null
+++ b/Engine/Core/MainWin32.cpp
@@ -0,0 +1,77 @@
+/**
+ * @file MainWin32.cpp
+ * @brief Win32 可执行程序入口与消息泵实现。
+ * @details
+ * 负责设置工作目录、初始化调试内存检测,并驱动主循环:
+ * - 轮询并分发 Windows 消息;
+ * - 在消息空闲阶段调用 engine_update;
+ * - 退出时由 engine_shutdown 完成资源收尾。
+ */
+#ifdef _WIN64
+#include "CommonHeader.h"
+#include
+#ifndef WIN32_MEAN_AND_LEAN
+#define WIN32_MEAN_AND_LEAN
+#endif
+
+#include
+#include
+
+namespace {
+
+/**
+ * @brief 将当前工作目录切换到可执行文件所在目录。
+ * @return 切换后的当前目录;失败时返回空路径。
+ */
+std::filesystem::path
+set_current_directory_to_executable_path()
+{
+ wchar_t path[MAX_PATH]{};
+ const uint32_t length{ GetModuleFileName(0, &path[0], MAX_PATH) };
+ if (!length || GetLastError() == ERROR_INSUFFICIENT_BUFFER) return {};
+ std::filesystem::path p{ path };
+ std::filesystem::current_path(p.parent_path());
+ return std::filesystem::current_path();
+}
+
+}
+
+#ifndef USE_WITH_EDITOR
+
+extern bool engine_initialize();
+extern void engine_update();
+extern void engine_shutdown();
+
+/**
+ * @brief Win32 程序入口函数。
+ * @return 进程退出码。
+ */
+int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
+{
+
+#if _DEBUG
+ _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
+#endif
+
+ set_current_directory_to_executable_path();
+
+ if (engine_initialize())
+ {
+ MSG msg{};
+ bool is_running{ true };
+ while (is_running)
+ {
+ while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ is_running &= (msg.message != WM_QUIT);
+ }
+
+ engine_update();
+ }
+ }
+}
+
+#endif // USE_WITH_EDITOR
+#endif // _WIN64
diff --git a/Engine/Engine.vcxproj b/Engine/Engine.vcxproj
new file mode 100644
index 0000000..fb3374d
--- /dev/null
+++ b/Engine/Engine.vcxproj
@@ -0,0 +1,227 @@
+
+
+
+
+ DebugEditor
+ x64
+
+
+ Debug
+ x64
+
+
+ ReleaseEditor
+ x64
+
+
+ Release
+ x64
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 17.0
+ Win32Proj
+ {838f1eae-48a4-40ba-9b8f-332d37ea66cd}
+ Engine
+ 10.0
+
+
+
+ StaticLibrary
+ true
+ v143
+ Unicode
+
+
+ StaticLibrary
+ true
+ v143
+ Unicode
+
+
+ StaticLibrary
+ false
+ v143
+ true
+ Unicode
+
+
+ StaticLibrary
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Level4
+
+
+ _DEBUG;_LIB;%(PreprocessorDefinitions)
+ true
+ NotUsing
+
+
+ true
+ false
+ Fast
+ false
+ stdcpp17
+ FastCall
+ false
+ $(ProjectDir);$(ProjectDir)/Common
+
+
+
+
+ true
+
+
+
+
+ Level4
+
+
+ _DEBUG;_LIB;USE_WITH_EDITOR;%(PreprocessorDefinitions)
+ true
+ NotUsing
+
+
+ true
+ false
+ Fast
+ false
+ stdcpp17
+ FastCall
+ false
+ $(ProjectDir);$(ProjectDir)/Common
+
+
+
+
+ true
+
+
+
+
+ Level4
+ true
+ true
+
+
+ NDEBUG;_LIB;%(PreprocessorDefinitions)
+ true
+ NotUsing
+
+
+ true
+ false
+ false
+ false
+ true
+ Fast
+ false
+ stdcpp17
+ FastCall
+ $(ProjectDir);$(ProjectDir)/Common
+
+
+
+
+ true
+ true
+ true
+
+
+
+
+ Level4
+ true
+ true
+
+
+ NDEBUG;_LIB;USE_WITH_EDITOR;%(PreprocessorDefinitions)
+ true
+ NotUsing
+
+
+ true
+ false
+ false
+ false
+ true
+ Fast
+ false
+ stdcpp17
+ FastCall
+ $(ProjectDir);$(ProjectDir)/Common
+
+
+
+
+ true
+ true
+ true
+
+
+
+
+
+
diff --git a/Engine/Engine.vcxproj.filters b/Engine/Engine.vcxproj.filters
new file mode 100644
index 0000000..b5b1e52
--- /dev/null
+++ b/Engine/Engine.vcxproj.filters
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Engine/EngineAPI/Camera.h b/Engine/EngineAPI/Camera.h
new file mode 100644
index 0000000..3e568bf
--- /dev/null
+++ b/Engine/EngineAPI/Camera.h
@@ -0,0 +1,128 @@
+/**
+ * @file Camera.h
+ * @brief 相机句柄与视图/投影参数接口定义。
+ * @details
+ * camera 提供对渲染相机实例的统一访问,覆盖:
+ * - 透视/正交两类投影参数设置;
+ * - 视图矩阵、投影矩阵及其逆矩阵查询;
+ * - 近远裁剪面、宽高与绑定实体 ID 读取。
+ */
+#pragma once
+#include "CommonHeader.h"
+
+namespace XEngine::graphics {
+
+
+/**
+ * @brief 相机强类型标识符。
+ */
+DEFINE_TYPED_ID(camera_id);
+
+/**
+ * @brief 渲染相机句柄对象。
+ */
+class camera
+{
+public:
+ /**
+ * @brief 投影模式枚举。
+ */
+ enum type :u32
+ {
+ perspective,
+ orthographic,
+ };
+
+ constexpr explicit camera(camera_id id) : _id{ id } {}
+ constexpr camera() = default;
+ constexpr camera_id get_id() const { return _id; }
+ constexpr bool is_valid() const { return id::is_valid(_id); }
+
+ /**
+ * @brief 设置相机上方向。
+ */
+ void up(math::v3 up) const;
+ /**
+ * @brief 设置透视投影视场角。
+ */
+ void field_of_view(f32 fov) const;
+ /**
+ * @brief 设置视口宽高比。
+ */
+ void aspect_ratio(f32 aspect_ratio) const;
+ /**
+ * @brief 设置正交视口宽度。
+ */
+ void view_width(f32 width) const;
+ /**
+ * @brief 设置正交视口高度。
+ */
+ void view_height(f32 height) const;
+ /**
+ * @brief 设置近远裁剪面。
+ */
+ void range(f32 near_z, f32 far_z) const;
+
+ /**
+ * @brief 获取视图矩阵。
+ */
+ math::m4x4 view() const;
+ /**
+ * @brief 获取投影矩阵。
+ */
+ math::m4x4 projection() const;
+ /**
+ * @brief 获取投影矩阵逆矩阵。
+ */
+ math::m4x4 inverse_projection() const;
+ /**
+ * @brief 获取视图投影矩阵。
+ */
+ math::m4x4 view_projection() const;
+ /**
+ * @brief 获取视图投影逆矩阵。
+ */
+ math::m4x4 inverse_view_projection() const;
+ /**
+ * @brief 获取上方向向量。
+ */
+ math::v3 up() const;
+
+ /**
+ * @brief 获取近裁剪面。
+ */
+ f32 near_z() const;
+ /**
+ * @brief 获取远裁剪面。
+ */
+ f32 far_z() const;
+ /**
+ * @brief 获取视场角。
+ */
+ f32 field_of_view() const;
+ /**
+ * @brief 获取宽高比。
+ */
+ f32 aspect_ratio() const;
+ /**
+ * @brief 获取视口宽度。
+ */
+ f32 view_width() const;
+ /**
+ * @brief 获取视口高度。
+ */
+ f32 view_height() const;
+ /**
+ * @brief 获取投影类型。
+ */
+ type projection_type() const;
+ /**
+ * @brief 获取绑定实体 ID。
+ */
+ id::id_type entity_id() const;
+private:
+ camera_id _id{ id::invalid_id };
+};
+
+
+}
diff --git a/Engine/EngineAPI/GameEntity.h b/Engine/EngineAPI/GameEntity.h
new file mode 100644
index 0000000..72d68c2
--- /dev/null
+++ b/Engine/EngineAPI/GameEntity.h
@@ -0,0 +1,191 @@
+/**
+ * @file GameEntity.h
+ * @brief 实体句柄、脚本基类与脚本注册机制定义。
+ * @details
+ * 该文件是引擎 ECS 对外脚本接口的核心入口:
+ * - game_entity::entity 提供实体句柄及常用变换访问;
+ * - script::entity_script 定义脚本生命周期与变换写接口;
+ * - script::detail 提供脚本工厂注册、按名称哈希检索与宏封装。
+ */
+#pragma once
+#include "..\Components\ComponentsCommon.h"
+#include "TransformComponent.h"
+#include "ScriptComponent.h"
+
+namespace XEngine{
+namespace game_entity {
+
+/**
+ * @brief 实体强类型标识符。
+ */
+DEFINE_TYPED_ID(entity_id);
+
+/**
+ * @brief 运行时实体句柄封装。
+ */
+class entity {
+public:
+ /**
+ * @brief 由实体 ID 构造句柄。
+ */
+ constexpr explicit entity(entity_id id) : _id{ id } {}
+ /**
+ * @brief 构造无效实体句柄。
+ */
+ constexpr entity() : _id{ id::invalid_id } {}
+ /**
+ * @brief 获取实体 ID。
+ */
+ [[nodiscard]] constexpr entity_id get_id() const { return _id; }
+ /**
+ * @brief 判断实体句柄是否有效。
+ */
+ [[nodiscard]] constexpr bool is_valid() const { return id::is_valid(_id); }
+
+ /**
+ * @brief 获取实体变换组件句柄。
+ */
+ [[nodiscard]] transform::component transform() const;
+ /**
+ * @brief 获取实体脚本组件句柄。
+ */
+ [[nodiscard]] script::component script() const;
+
+ [[nodiscard]] math::v4 rotation() const { return transform().rotation(); }
+ [[nodiscard]] math::v3 orientation() const { return transform().orientation(); }
+ [[nodiscard]] math::v3 position() const { return transform().position(); }
+ [[nodiscard]] math::v3 scale() const { return transform().scale(); }
+private:
+ entity_id _id;
+};
+}
+
+
+namespace script {
+/**
+ * @brief 实体脚本基类。
+ * @details
+ * 派生类可覆盖 begin_play/update,并通过受保护接口修改实体变换。
+ */
+class entity_script : public game_entity::entity {
+public:
+ /**
+ * @brief 析构脚本实例。
+ */
+ virtual ~entity_script() = default;
+ /**
+ * @brief 脚本启动回调。
+ */
+ virtual void begin_play() {}
+ /**
+ * @brief 脚本逐帧更新回调。
+ * @param dt 帧时间步长。
+ */
+ virtual void update(float) {}
+
+protected:
+ constexpr explicit entity_script(game_entity::entity entity)
+ : game_entity::entity{ entity.get_id() }{}
+
+ void set_rotation(math::v4 rotation_quaternion) const {set_rotation(this, rotation_quaternion);}
+ void set_orientation(math::v3 orientation_vector) const {set_orientation(this, orientation_vector);}
+ void set_position(math::v3 position) const {set_position(this, position);}
+ void set_scale(math::v3 scale) const { set_scale(this, scale); }
+
+ static void set_rotation(const game_entity::entity *const entity, math::v4 rotation_quaternion);
+ static void set_orientation(const game_entity::entity *const entity, math::v3 orientation_vector);
+ static void set_position(const game_entity::entity *const entity, math::v3 position);
+ static void set_scale(const game_entity::entity *const entity, math::v3 scale);
+
+};
+
+
+namespace detail {
+
+/**
+ * @brief 脚本实例智能指针类型。
+ */
+using script_ptr = std::unique_ptr;
+#ifdef USESTDFUNC
+/**
+ * @brief 基于 std::function 的脚本工厂签名。
+ */
+using script_creator = std::function;
+#else
+/**
+ * @brief 基于函数指针的脚本工厂签名。
+ */
+using script_creator = script_ptr(*)(game_entity::entity entity);
+#endif // USESTDFUNC
+/**
+ * @brief 脚本名哈希器类型。
+ */
+using string_hash = std::hash;
+
+/**
+ * @brief 注册脚本工厂到脚本注册表。
+ * @param tag 脚本哈希标签。
+ * @param creator 脚本工厂函数。
+ * @return 注册结果码。
+ */
+u8 register_script(size_t tag, script_creator creator);
+#ifdef USE_WITH_EDITOR
+extern "C" __declspec(dllexport)
+#endif
+/**
+ * @brief 按哈希标签查询脚本工厂。
+ * @param tag 脚本名哈希。
+ * @return 对应脚本工厂函数。
+ */
+script_creator get_script_creator(size_t tag);
+
+/**
+ * @brief 默认脚本工厂模板函数。
+ * @tparam script_class 脚本类型。
+ * @param entity 绑定实体。
+ * @return 新创建脚本实例。
+ */
+template
+script_ptr create_script(game_entity::entity entity)
+{
+ assert(entity.is_valid());
+ return std::make_unique(entity);
+}
+
+
+#ifdef USE_WITH_EDITOR
+/**
+ * @brief 向编辑器导出脚本名称。
+ * @param name 脚本类名。
+ * @return 写入结果码。
+ */
+u8 add_script_name(const char* name);
+
+#define REGISTER_SCRIPT(TYPE) \
+ namespace { \
+ u8 _reg_##TYPE{ \
+ XEngine::script::detail::register_script( \
+ XEngine::script::detail::string_hash()(#TYPE), \
+ &XEngine::script::detail::create_script) }; \
+ const u8 _name_##TYPE \
+ { XEngine::script::detail::add_script_name(#TYPE) }; \
+ }
+
+
+#else
+#define REGISTER_SCRIPT(TYPE) \
+ class TYPE; \
+ namespace { \
+ u8 _reg_##TYPE{ \
+ XEngine::script::detail::register_script( \
+ XEngine::script::detail::string_hash()(#TYPE), \
+ &XEngine::script::detail::create_script) }; \
+ }
+
+#endif // USE_WITH_EDITOR
+
+
+}//namespace detail
+}//namespace script
+
+}
diff --git a/Engine/EngineAPI/Light.h b/Engine/EngineAPI/Light.h
new file mode 100644
index 0000000..67ec2e0
--- /dev/null
+++ b/Engine/EngineAPI/Light.h
@@ -0,0 +1,97 @@
+/**
+ * @file Light.h
+ * @brief 光源句柄与光照参数访问接口定义。
+ * @details
+ * light 封装渲染层光源实例的轻量句柄语义,支持:
+ * - 启停、强度、颜色等常用光照属性读写;
+ * - 查询光源类型与绑定实体;
+ * - 通过 light_set_key 区分不同光照集合。
+ */
+#pragma once
+#include "CommonHeader.h"
+
+namespace XEngine::graphics {
+
+
+/**
+ * @brief 光源强类型标识符。
+ */
+DEFINE_TYPED_ID(light_id);
+
+/**
+ * @brief 光源句柄对象。
+ */
+class light
+{
+public:
+ /**
+ * @brief 光源类型枚举。
+ */
+ enum type :u32
+ {
+ directioinal,
+ point,
+ spot,
+
+ count
+ };
+
+ constexpr explicit light(light_id id, u64 light_set_key)
+ :_light_set_key{ light_set_key }, _id { id } {}
+ constexpr light() = default;
+ constexpr light_id get_id() const { return _id; }
+ constexpr u64 get_light_set_key() const { return _light_set_key; }
+ constexpr bool is_valid() const { return id::is_valid(_id); }
+
+ /**
+ * @brief 设置光源启用状态。
+ * @param is_enabled true 表示启用。
+ */
+ void is_enabled(bool is_enabled) const;
+ /**
+ * @brief 设置光照强度。
+ * @param intensity 光照强度值。
+ */
+ void intensity(f32 intensity) const;
+ /**
+ * @brief 设置光照颜色。
+ * @param color RGB 颜色。
+ */
+ void color(math::v3 color) const;
+
+ /**
+ * @brief 查询光源启用状态。
+ * @return 启用返回 true。
+ */
+ bool is_enabled( ) const;
+ /**
+ * @brief 查询光照强度。
+ * @return 当前强度值。
+ */
+ f32 intensity( ) const;
+ /**
+ * @brief 查询光照颜色。
+ * @return 当前 RGB 颜色。
+ */
+ math::v3 color( ) const;
+
+
+
+ /**
+ * @brief 查询光源类型。
+ * @return 光源类型枚举值。
+ */
+ type light_type() const;
+ /**
+ * @brief 查询绑定实体 ID。
+ * @return 实体 ID。
+ */
+ id::id_type entity_id() const;
+
+private:
+ u64 _light_set_key{ 0 };
+ light_id _id{ id::invalid_id };
+};
+
+
+}
diff --git a/Engine/EngineAPI/ScriptComponent.h b/Engine/EngineAPI/ScriptComponent.h
new file mode 100644
index 0000000..ca37331
--- /dev/null
+++ b/Engine/EngineAPI/ScriptComponent.h
@@ -0,0 +1,51 @@
+/**
+ * @file ScriptComponent.h
+ * @brief 脚本组件句柄定义。
+ * @details
+ * 该文件提供脚本组件在 ECS 中的强类型 ID 封装,用于:
+ * - 标识实体绑定的脚本组件实例;
+ * - 与组件系统统一的有效性检查;
+ * - 在不暴露内部存储结构的前提下传递脚本组件引用。
+ */
+#pragma once
+#include "..\Components\ComponentsCommon.h"
+
+
+namespace XEngine::script {
+/**
+ * @brief 脚本组件强类型标识符。
+ */
+DEFINE_TYPED_ID(script_id);
+
+
+/**
+ * @brief 脚本组件句柄对象。
+ */
+class component final
+{
+public:
+ /**
+ * @brief 由脚本组件 ID 构造句柄。
+ * @param id 脚本组件 ID。
+ */
+ constexpr explicit component(script_id id) : _id{ id } {}
+ /**
+ * @brief 构造无效脚本组件句柄。
+ */
+ constexpr component() : _id{ id::invalid_id } {}
+ /**
+ * @brief 获取脚本组件 ID。
+ * @return 组件 ID。
+ */
+ constexpr script_id get_id() const { return _id; }
+ /**
+ * @brief 判断句柄是否有效。
+ * @return 有效返回 true。
+ */
+ constexpr bool is_valid() const { return id::is_valid(_id); }
+
+private:
+ script_id _id;
+
+};
+}
diff --git a/Engine/EngineAPI/TransformComponent.h b/Engine/EngineAPI/TransformComponent.h
new file mode 100644
index 0000000..ac89399
--- /dev/null
+++ b/Engine/EngineAPI/TransformComponent.h
@@ -0,0 +1,53 @@
+/**
+ * @file TransformComponent.h
+ * @brief 变换组件句柄与只读查询接口定义。
+ * @details
+ * component 以轻量句柄方式访问 ECS 中的变换数据,提供:
+ * - 位置(position)、朝向(orientation)、缩放(scale)查询;
+ * - 四元数旋转(rotation)读取;
+ * - 与 id 系统一致的有效性判断语义。
+ */
+#pragma once
+#include "..\Components\ComponentsCommon.h"
+
+
+namespace XEngine::transform {
+/**
+ * @brief 变换组件强类型标识符。
+ */
+DEFINE_TYPED_ID(transform_id);
+
+
+/**
+ * @brief 变换组件句柄对象。
+ */
+class component final
+{
+public:
+ constexpr explicit component(transform_id id) : _id{ id } {}
+ constexpr component() : _id{ id::invalid_id } {}
+ constexpr transform_id get_id() const { return _id; }
+ constexpr bool is_valid() const { return id::is_valid(_id); }
+
+
+ /**
+ * @brief 获取世界空间位置。
+ */
+ math::v3 position() const;
+ /**
+ * @brief 获取朝向向量。
+ */
+ math::v3 orientation() const;
+ /**
+ * @brief 获取缩放向量。
+ */
+ math::v3 scale() const;
+ /**
+ * @brief 获取旋转四元数。
+ */
+ math::v4 rotation() const;
+private:
+ transform_id _id;
+
+};
+}
diff --git a/Engine/Platform/IncludeWindowCpp.h b/Engine/Platform/IncludeWindowCpp.h
new file mode 100644
index 0000000..5ea336b
--- /dev/null
+++ b/Engine/Platform/IncludeWindowCpp.h
@@ -0,0 +1,20 @@
+/**
+ * @file IncludeWindowCpp.h
+ * @brief 以单编译单元方式引入 Window.cpp 的桥接头。
+ * @details
+ * 某些构建路径通过宏控制将 Window.cpp 以内联包含方式编译。
+ * 该头文件负责:
+ * - 定义 INCLUDE_WINDOW_CPP 触发实现体暴露;
+ * - 包含 Window.cpp;
+ * - 立即取消宏定义以避免污染后续编译单元。
+ */
+#pragma once
+/**
+ * @brief 启用 Window.cpp 头内实现编译模式。
+ */
+#define INCLUDE_WINDOW_CPP 1
+#include "Window.cpp"
+/**
+ * @brief 关闭 Window.cpp 头内实现编译模式。
+ */
+#undef INCLUDE_WINDOW_CPP
diff --git a/Engine/Platform/Platform.h b/Engine/Platform/Platform.h
new file mode 100644
index 0000000..afc1fe1
--- /dev/null
+++ b/Engine/Platform/Platform.h
@@ -0,0 +1,33 @@
+/**
+ * @file Platform.h
+ * @brief 平台窗口生命周期管理接口。
+ * @details
+ * 对外提供窗口创建与销毁能力:
+ * - create_window 根据可选初始化参数构建窗口;
+ * - remove_window 根据 window_id 释放窗口资源;
+ * - 保持与平台无关上层模块之间的最小依赖面。
+ */
+#pragma once
+#include "CommonHeader.h"
+#include "Window.h"
+
+
+namespace XEngine::platform {
+
+struct window_init_info;
+
+/**
+ * @brief 创建平台窗口。
+ * @param init_info 可选初始化参数。
+ * @return 创建后的窗口句柄对象。
+ */
+window create_window(const window_init_info *const init_info = nullptr);
+/**
+ * @brief 销毁平台窗口。
+ * @param id 目标窗口 ID。
+ */
+void remove_window(window_id id);
+
+
+
+}
diff --git a/Engine/Platform/PlatformTypes.h b/Engine/Platform/PlatformTypes.h
new file mode 100644
index 0000000..0875099
--- /dev/null
+++ b/Engine/Platform/PlatformTypes.h
@@ -0,0 +1,47 @@
+/**
+ * @file PlatformTypes.h
+ * @brief 平台窗口系统公共类型定义。
+ * @details
+ * 该文件集中声明平台窗口创建所需的数据结构,包括:
+ * - window_proc:应用层窗口消息回调签名;
+ * - window_handle:原生窗口句柄别名;
+ * - window_init_info:窗口创建参数(父窗口、标题、位置、尺寸)。
+ */
+#pragma once
+#include "CommonHeader.h"
+
+
+#ifdef _WIN64
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+#include
+
+namespace XEngine::platform {
+
+/**
+ * @brief 窗口消息回调函数签名。
+ */
+using window_proc = LRESULT(*)(HWND, UINT, WPARAM, LPARAM);
+/**
+ * @brief 原生窗口句柄类型别名。
+ */
+using window_handle = HWND;
+
+/**
+ * @brief 窗口初始化参数。
+ */
+struct window_init_info
+{
+ window_proc callback{ nullptr };
+ window_handle parent{ nullptr };
+ const wchar_t* caption{ nullptr };
+ s32 left{ 0 };
+ s32 top{ 0 };
+ s32 width{ 0 };
+ s32 height{ 0 };
+};
+}
+
+#endif // _WIN64
diff --git a/Engine/Platform/PlatformWin32.cpp b/Engine/Platform/PlatformWin32.cpp
new file mode 100644
index 0000000..701cb73
--- /dev/null
+++ b/Engine/Platform/PlatformWin32.cpp
@@ -0,0 +1,372 @@
+/**
+ * @file PlatformWin32.cpp
+ * @brief Win32 平台窗口系统实现。
+ * @details
+ * 该文件实现平台窗口后端的完整行为,包括:
+ * - Win32 窗口类注册与窗口创建;
+ * - 窗口 ID 与 HWND 的双向关联管理;
+ * - 大小变化、销毁、全屏切换等消息处理;
+ * - 对 Platform.h / Window.h 暴露接口的底层支撑。
+ */
+#ifdef _WIN64
+#include "Platform.h"
+#include "PlatformTypes.h"
+
+namespace XEngine::platform {
+
+
+
+namespace {
+/**
+ * @brief Win32 窗口实例运行时状态。
+ */
+struct window_info
+{
+ HWND hwnd{ nullptr };
+ RECT client_area{ 0,0,1920,1080 };
+ RECT fullscreen_area{};
+ POINT top_left{ 0,0 };
+ DWORD style{ WS_VISIBLE };
+ bool is_fullscreen{ false };
+ bool is_closed{ false };
+
+};
+
+/**
+ * @brief 窗口对象存储池。
+ */
+utl::free_list windows;
+
+
+
+
+/**
+ * @brief 根据窗口 ID 获取窗口状态对象。
+ * @param id 窗口 ID。
+ * @return 对应窗口状态引用。
+ */
+window_info&
+get_from_id(window_id id)
+{
+ assert(windows[id].hwnd);
+ return windows[id];
+}
+
+/**
+ * @brief 根据 HWND 获取窗口状态对象。
+ * @param handle 原生窗口句柄。
+ * @return 对应窗口状态引用。
+ */
+window_info&
+get_from_handle(window_handle handle)
+{
+ const window_id id{ (id::id_type)GetWindowLongPtr(handle, GWLP_USERDATA) };
+ return get_from_id(id);
+}
+
+/**
+ * @brief 标记窗口是否发生了可见尺寸变更。
+ */
+bool resized{ false };
+
+/**
+ * @brief 按目标客户区更新窗口尺寸与位置。
+ * @param info 窗口状态。
+ * @param area 目标客户区矩形。
+ */
+void
+resize_window(const window_info& info, const RECT& area)
+{
+ RECT window_rect{ area };
+ AdjustWindowRect(&window_rect, info.style, FALSE);
+
+ const s32 width{ window_rect.right - window_rect.left };
+ const s32 height{ window_rect.bottom - window_rect.top };
+
+ MoveWindow(info.hwnd, info.top_left.x, info.top_left.y, width, height, true);
+}
+
+///
+
+
+/**
+ * @brief 查询窗口是否处于全屏状态。
+ * @param id 窗口 ID。
+ * @return 全屏返回 true。
+ */
+bool
+is_window_fullscreen(window_id id)
+{
+ assert(id != u32_invalid_id);
+ return get_from_id(id).is_fullscreen;
+}
+
+/**
+ * @brief 查询窗口原生句柄。
+ * @param id 窗口 ID。
+ * @return HWND 句柄。
+ */
+window_handle
+get_window_handle(window_id id)
+{
+ return get_from_id(id).hwnd;
+}
+
+/**
+ * @brief 设置窗口标题文本。
+ * @param id 窗口 ID。
+ * @param caption 标题文本。
+ */
+void
+set_window_caption(window_id id, const wchar_t* caption)
+{
+ window_info& info{ get_from_id(id) };
+ SetWindowText(info.hwnd, caption);
+}
+
+/**
+ * @brief 查询窗口矩形(客户区或全屏区)。
+ * @param id 窗口 ID。
+ * @return 左上右下坐标向量。
+ */
+math::u32v4
+get_window_size(window_id id)
+{
+ window_info& info{ get_from_id(id) };
+ RECT& rect{ info.is_fullscreen ? info.fullscreen_area : info.client_area };
+ return { (u32)rect.left,(u32)rect.top,(u32)rect.right,(u32)rect.bottom };
+}
+
+/**
+ * @brief 调整窗口客户区尺寸。
+ * @param id 窗口 ID。
+ * @param width 新宽度。
+ * @param height 新高度。
+ */
+void
+resize_window(window_id id, u32 width, u32 height)
+{
+ window_info& info{ get_from_id(id) };
+ // when we host the window in the level editor we just update
+ // the internal data.
+ if (info.style & WS_CHILD)
+ {
+ GetClientRect(info.hwnd, &info.client_area);
+ }
+ else
+ {
+
+ RECT& area{ info.is_fullscreen ? info.fullscreen_area : info.client_area };
+ area.bottom = area.top + height;
+ area.right = area.left + width;
+
+ resize_window(info, area);
+ }
+
+}
+
+/**
+ * @brief 查询窗口是否已被关闭。
+ * @param id 窗口 ID。
+ * @return 已关闭返回 true。
+ */
+bool
+is_window_close(window_id id)
+{
+ return get_from_id(id).is_closed;
+}
+
+///
+
+/**
+ * @brief 平台内部默认窗口过程。
+ * @param hwnd 窗口句柄。
+ * @param msg 消息类型。
+ * @param wparam 消息参数。
+ * @param lparam 消息参数。
+ * @return 消息处理结果。
+ */
+LRESULT CALLBACK internal_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+
+ switch (msg)
+ {
+ case WM_NCCREATE:
+ {
+
+ DEBUG_OP(SetLastError(0));
+ const window_id id{ windows.add() };
+ windows[id].hwnd = hwnd;
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)id);
+ assert(GetLastError() == 0);
+ }
+ break;
+ case WM_DESTROY:
+
+ get_from_handle(hwnd).is_closed = true;
+
+ break;
+
+ case WM_SIZE:
+ resized = (wparam != SIZE_MINIMIZED);
+
+ break;
+
+ default:
+ break;
+ }
+
+ if (resized && GetAsyncKeyState(VK_LBUTTON) >= 0)
+ {
+ window_info& info{ get_from_handle(hwnd) };
+ assert(info.hwnd);
+ GetClientRect(info.hwnd, info.is_fullscreen ? &info.fullscreen_area : &info.client_area);
+ resized = false;
+ }
+
+ LONG_PTR long_ptr{ GetWindowLongPtr(hwnd, 0) };
+ return long_ptr
+ ? ((window_proc)long_ptr)(hwnd, msg, wparam, lparam)
+ : DefWindowProc(hwnd, msg, wparam, lparam);
+
+ assert(GetLastError() == 0);
+}
+
+/**
+ * @brief 切换窗口全屏状态。
+ * @param id 窗口 ID。
+ * @param is_fullscreen 目标全屏状态。
+ */
+void
+set_window_fullscreen(window_id id, bool is_fullscreen)
+{
+ window_info& info{ windows[id] };
+ if (info.is_fullscreen != is_fullscreen)
+ {
+ info.is_fullscreen = is_fullscreen;
+
+ if (is_fullscreen)
+ {
+ GetClientRect(info.hwnd, &info.client_area);
+ RECT rect;
+ GetWindowRect(info.hwnd, &rect);
+ info.top_left.x = rect.left;
+ info.top_left.y = rect.top;
+ SetWindowLongPtr(info.hwnd, GWL_STYLE, info.style);
+ ShowWindow(info.hwnd, SW_MAXIMIZE);
+ }
+ else
+ {
+ SetWindowLongPtr(info.hwnd, GWL_STYLE, info.style);
+ resize_window(info, info.client_area);
+ ShowWindow(info.hwnd, SW_SHOWNORMAL);
+ }
+ }
+}
+
+} // namespace
+
+/**
+ * @brief 创建 Win32 窗口并注册到窗口池。
+ * @param init_info 可选窗口初始化参数。
+ * @return 创建成功的窗口句柄对象。
+ */
+window
+create_window(const window_init_info* init_info /* = nullptr */)
+{
+ window_proc callback{ init_info ? init_info->callback : nullptr }; // 回调窗口过程:由初始化参数提供;未提供时为 nullptr。
+ window_handle parent{ init_info ? init_info->parent : nullptr }; // 父窗口句柄:用于子窗口挂载;未提供时创建顶层窗口。
+
+
+ WNDCLASSEX wc;
+ ZeroMemory(&wc, sizeof(WNDCLASSEX));
+
+ // 初始化 WNDCLASSEX 字段。
+ wc.cbSize = sizeof(WNDCLASSEX); // 结构体大小。
+ wc.style = CS_HREDRAW | CS_VREDRAW; // 水平/垂直尺寸变化时重绘。
+ wc.lpfnWndProc = internal_window_proc; // 默认窗口过程入口。
+ wc.cbClsExtra = 0; // 类额外字节数。
+ wc.cbWndExtra = callback ? sizeof(callback) : 0; // 实例额外字节数:有回调时用于存储回调指针。
+ wc.hInstance = 0; // 当前模块实例句柄(使用默认)。
+ wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); // 大图标。
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW); // 光标。
+ wc.hbrBackground = CreateSolidBrush(RGB(26, 48, 76)); // 背景画刷。
+ wc.lpszMenuName = NULL; // 菜单名(无)。
+ wc.lpszClassName = L"XWindow"; // 窗口类名。
+ wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); // 小图标。
+
+
+ // 注册窗口类。
+ if (!RegisterClassEx(&wc))
+ {
+ // 若类已注册或注册失败,后续创建窗口时由系统返回结果。
+ }
+
+ window_info info{};
+ info.client_area.right = (init_info && init_info->width) ? info.client_area.left + init_info->width : info.client_area.right;
+ info.client_area.bottom = (init_info && init_info->height) ? info.client_area.top + init_info->height : info.client_area.bottom;
+ info.style |= parent ? WS_CHILD : WS_OVERLAPPEDWINDOW;
+
+ RECT rect{ info.client_area };
+ AdjustWindowRect(&rect, info.style, FALSE);
+
+ const wchar_t* caption{ (init_info && init_info->caption) ? init_info->caption : L"XGame" };
+ const s32 left{ (init_info) ? init_info->left : info.top_left.x };
+ const s32 top{ (init_info) ? init_info->top : info.top_left.y };
+
+ const s32 width{ rect.right - rect.left };
+ const s32 height{ rect.bottom - rect.top };
+
+
+ // 创建窗口实例。
+
+ info.hwnd = CreateWindowEx(
+ 0, // 扩展样式。
+ wc.lpszClassName, // 窗口类名。
+ caption, // 窗口标题。
+ info.style, // 窗口样式。
+
+ // 尺寸与位置。
+ left, top, width, height,
+
+ parent, // 父窗口句柄(nullptr 表示顶层窗口)。
+ nullptr, // 菜单句柄(未使用)。
+ nullptr, // 实例句柄(未显式传入)。
+ nullptr // 创建参数(未使用)。
+ );
+
+ if (info.hwnd)
+ {
+ DEBUG_OP(SetLastError(0));
+
+ if (callback) SetWindowLongPtr(info.hwnd, 0, (LONG_PTR)callback);
+ assert(GetLastError() == 0);
+
+ ShowWindow(info.hwnd, SW_SHOWNORMAL);
+ UpdateWindow(info.hwnd);
+
+ window_id id{ (id::id_type)GetWindowLongPtr(info.hwnd, GWLP_USERDATA) };
+ windows[id] = info;
+ return window{ id };
+ }
+
+ return {}; // 创建失败时返回无效窗口句柄。
+}
+
+/**
+ * @brief 销毁窗口并从窗口池移除。
+ * @param id 窗口 ID。
+ */
+void
+remove_window(window_id id)
+{
+ window_info& info{ get_from_id(id) };
+ DestroyWindow(info.hwnd);
+ windows.remove(id);
+}
+}
+
+#include "IncludeWindowCpp.h"
+
+
+#endif // _WIN64
diff --git a/Engine/Platform/Window.cpp b/Engine/Platform/Window.cpp
new file mode 100644
index 0000000..1f9f1ae
--- /dev/null
+++ b/Engine/Platform/Window.cpp
@@ -0,0 +1,116 @@
+/**
+ * @file Window.cpp
+ * @brief window 句柄对象成员函数实现。
+ * @details
+ * 本文件将 window 类的高层接口转发到平台后端函数:
+ * - full screen、caption、resize 等操作;
+ * - 原生句柄与尺寸查询;
+ * - 窗口关闭状态查询。
+ * 通过 INCLUDE_WINDOW_CPP 宏控制其编译包含方式。
+ */
+#if INCLUDE_WINDOW_CPP
+#include "Window.h"
+
+namespace XEngine::platform {
+
+
+/**
+ * @brief 设置窗口全屏状态。
+ * @param is_fullscreen 目标全屏状态。
+ */
+void
+window::set_fullscreen(bool is_fullscreen) const
+{
+ assert(is_valid());
+ set_window_fullscreen(_id, is_fullscreen);
+}
+/**
+ * @brief 查询窗口是否处于全屏状态。
+ * @return 全屏返回 true。
+ */
+bool
+window::is_fullscreen() const
+{
+ assert(is_valid());
+ return is_window_fullscreen(_id);
+}
+/**
+ * @brief 获取原生窗口句柄。
+ * @return 平台相关窗口句柄指针。
+ */
+void*
+window::handle() const
+{
+ assert(is_valid());
+ return get_window_handle(_id);
+}
+/**
+ * @brief 设置窗口标题。
+ * @param caption 标题文本。
+ */
+void
+window::set_caption(const wchar_t* caption) const
+{
+ assert(is_valid());
+ set_window_caption(_id, caption);
+}
+
+/**
+ * @brief 获取窗口矩形坐标。
+ * @return 左上右下坐标向量。
+ */
+math::u32v4 window::size() const
+{
+ assert(is_valid());
+ return get_window_size(_id);
+}
+/**
+ * @brief 调整窗口客户区尺寸。
+ * @param width 目标宽度。
+ * @param height 目标高度。
+ */
+void
+window::resize(u32 width, u32 height)const
+{
+ assert(is_valid());
+ resize_window(_id, width, height);
+}
+
+/**
+ * @brief 获取窗口当前宽度。
+ * @return 宽度像素值。
+ */
+u32
+window::width()const
+{
+ assert(is_valid());
+ math::u32v4 s{ size() };
+ return s.z - s.x;
+}
+
+/**
+ * @brief 获取窗口当前高度。
+ * @return 高度像素值。
+ */
+u32
+window::height()const
+{
+ assert(is_valid());
+ math::u32v4 s{ size() };
+ return s.w - s.y;
+}
+/**
+ * @brief 查询窗口是否已关闭。
+ * @return 已关闭返回 true。
+ */
+bool
+window::is_closed()const
+{
+ assert(is_valid());
+ return is_window_close(_id);
+}
+
+}
+
+
+#endif //INCLUDE_WINDOW_CPP
diff --git a/Engine/Platform/Window.h b/Engine/Platform/Window.h
new file mode 100644
index 0000000..cf3ad0c
--- /dev/null
+++ b/Engine/Platform/Window.h
@@ -0,0 +1,99 @@
+/**
+ * @file Window.h
+ * @brief 跨模块窗口句柄对象与基础窗口操作接口。
+ * @details
+ * window 是平台层窗口对象的轻量包装,向上层提供:
+ * - 全屏切换、标题设置、尺寸调整;
+ * - 原生窗口句柄访问;
+ * - 关闭状态与尺寸查询。
+ * 具体平台行为由 PlatformWin32.cpp 等实现体完成。
+ */
+#pragma once
+#include "CommonHeader.h"
+
+
+namespace XEngine::platform {
+/**
+ * @brief 窗口强类型标识符。
+ */
+DEFINE_TYPED_ID(window_id);
+
+/**
+ * @brief 平台窗口句柄对象。
+ */
+class window
+{
+public:
+ /**
+ * @brief 由窗口 ID 构造句柄。
+ * @param id 窗口 ID。
+ */
+ constexpr explicit window(window_id id) : _id{ id } {}
+ /**
+ * @brief 构造无效窗口句柄。
+ */
+ constexpr window() = default;
+ /**
+ * @brief 获取窗口 ID。
+ * @return 窗口 ID。
+ */
+ constexpr window_id get_id() const { return _id; }
+ /**
+ * @brief 判断窗口句柄是否有效。
+ * @return 有效返回 true。
+ */
+ constexpr bool is_valid() const { return id::is_valid(_id); }
+
+
+ /**
+ * @brief 设置全屏状态。
+ * @param is_fullscreen true 表示全屏。
+ */
+ void set_fullscreen(bool is_fullscreen) const;
+ /**
+ * @brief 查询是否全屏。
+ * @return 全屏返回 true。
+ */
+ bool is_fullscreen() const;
+ /**
+ * @brief 获取原生窗口句柄。
+ * @return 平台句柄指针。
+ */
+ void* handle() const;
+ /**
+ * @brief 设置窗口标题文本。
+ * @param caption 标题字符串。
+ */
+ void set_caption(const wchar_t* caption) const;
+ /**
+ * @brief 获取窗口矩形坐标。
+ * @return 左上右下坐标向量。
+ */
+ math::u32v4 size() const;
+ /**
+ * @brief 调整窗口大小。
+ * @param width 目标宽度。
+ * @param height 目标高度。
+ */
+ void resize(u32 width, u32 height)const;
+ /**
+ * @brief 获取窗口宽度。
+ * @return 宽度像素值。
+ */
+ u32 width()const;
+ /**
+ * @brief 获取窗口高度。
+ * @return 高度像素值。
+ */
+ u32 height()const;
+ /**
+ * @brief 查询窗口是否已关闭。
+ * @return 已关闭返回 true。
+ */
+ bool is_closed()const;
+
+private:
+ window_id _id{ id::invalid_id };
+
+};
+}
diff --git a/Engine/Utilities/FreeList.h b/Engine/Utilities/FreeList.h
new file mode 100644
index 0000000..0a934c6
--- /dev/null
+++ b/Engine/Utilities/FreeList.h
@@ -0,0 +1,169 @@
+/**
+ * @file FreeList.h
+ * @brief 带复用槽位的稀疏对象容器模板。
+ * @details
+ * free_list 维护“已使用槽位 + 空闲链”结构,支持:
+ * - O(1) 近似开销的新增与删除;
+ * - 删除后槽位复用,降低频繁分配释放成本;
+ * - 以索引作为外部句柄,与 id 系统协同使用。
+ */
+#pragma once
+#include "CommonHeader.h"
+
+namespace XEngine::utl {
+
+#if USE_STL_VECTOR
+#pragma message("WARNING: using utl::free_list with std::vector result in duplicate calls to class constructor!");
+#endif
+
+template
+class free_list
+{
+ static_assert(sizeof(T) >= sizeof(u32));
+
+public:
+ /**
+ * @brief 构造空容器。
+ */
+ free_list() = default;
+
+ /**
+ * @brief 预留底层存储容量。
+ * @param count 预留元素数量。
+ */
+ explicit free_list(u32 count)
+ {
+ _array.reserve(count);
+ }
+
+ /**
+ * @brief 销毁容器并校验无存活元素。
+ */
+ ~free_list()
+ {
+ assert(!_size);
+
+#if USE_STL_VECTOR
+ memset(_array.data(), 0, _array.size() * sizeof(T));
+#endif
+ }
+
+ /**
+ * @brief 新增一个元素并返回槽位 ID。
+ * @tparam params 构造参数类型。
+ * @param p 构造参数。
+ * @return 新元素 ID。
+ */
+ template
+ constexpr u32 add(params&&... p)
+ {
+ u32 id{ u32_invalid_id };
+ if (_next_free_index == u32_invalid_id)
+ {
+ id = (u32)_array.size();
+ _array.emplace_back(std::forward(p)...);
+ }
+ else
+ {
+ id = _next_free_index;
+ assert(id < _array.size() && already_removed(id));
+ _next_free_index = *(const u32 *const)std::addressof(_array[id]);
+ new (std::addressof(_array[id])) T(std::forward(p)...);
+ }
+ ++_size;
+
+ return id;
+ }
+
+ /**
+ * @brief 删除指定 ID 的元素并回收到空闲链。
+ * @param id 元素 ID。
+ */
+ constexpr void remove(u32 id)
+ {
+ assert(id < _array.size() && !already_removed(id));
+ T& item{ _array[id] };
+ item.~T();
+ DEBUG_OP(memset(std::addressof(_array[id]), 0xcc, sizeof(T)));
+ *(u32 *const)std::addressof(_array[id]) = _next_free_index;
+ _next_free_index = id;
+ --_size;
+ }
+
+ /**
+ * @brief 获取当前有效元素数量。
+ * @return 元素数量。
+ */
+ constexpr u32 size() const
+ {
+ return _size;
+ }
+
+
+ /**
+ * @brief 获取当前已分配槽位总数。
+ * @return 容量值。
+ */
+ constexpr u32 capacity() const
+ {
+ return _array.size();
+ }
+
+
+ /**
+ * @brief 判断容器是否为空。
+ * @return 空返回 true。
+ */
+ constexpr bool empty() const
+ {
+ return _size == 0;
+ }
+
+ /**
+ * @brief 按 ID 访问元素。
+ * @param id 元素 ID。
+ * @return 元素引用。
+ */
+ [[nodiscard]] constexpr T& operator[](u32 id)
+ {
+ assert(id < _array.size() && !already_removed(id));
+ return _array[id];
+ }
+
+ /**
+ * @brief 按 ID 访问常量元素。
+ * @param id 元素 ID。
+ * @return 常量元素引用。
+ */
+ [[nodiscard]] constexpr const T& operator[](u32 id) const
+ {
+ assert(id < _array.size() && !already_removed(id));
+ return _array[id];
+ }
+
+private:
+ constexpr bool already_removed(u32 id) const
+ {
+ if constexpr (sizeof(T) > sizeof(u32))
+ {
+ u32 i{ sizeof(u32) };
+ const u8 *const p{ (const u8 *const)std::addressof(_array[id]) };
+ while ((p[i] == 0xcc) && (i < sizeof(T))) ++i;
+ return i == sizeof(T);
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+#if USE_STL_VECTOR
+ utl::vector _array;
+#else
+ utl::vector _array;
+#endif
+ u32 _next_free_index{ u32_invalid_id };
+ u32 _size{ 0 };
+};
+
+}
diff --git a/Engine/Utilities/IOStream.h b/Engine/Utilities/IOStream.h
new file mode 100644
index 0000000..9c07923
--- /dev/null
+++ b/Engine/Utilities/IOStream.h
@@ -0,0 +1,154 @@
+/**
+ * @file IOStream.h
+ * @brief 面向内存缓冲区的二进制流读写工具。
+ * @details
+ * 定义 reader/writer 两个轻量类,用于在连续内存上顺序序列化:
+ * - 支持算术类型按字节写入与读取;
+ * - 支持原始块拷贝与位置跳过;
+ * - 通过边界断言降低越界读写风险。
+ */
+#pragma once
+#include "CommonHeader.h"
+
+namespace XEngine::utl {
+
+/**
+ * @brief 只读内存二进制流。
+ */
+class blob_stream_reader
+{
+public:
+ DISABLE_COPY_AND_MOVE(blob_stream_reader);
+ /**
+ * @brief 绑定外部只读缓冲区。
+ * @param buffer 缓冲区首地址。
+ */
+ explicit blob_stream_reader(const u8* buffer)
+ :_buffer{buffer}, _position{buffer}
+ {
+ assert(buffer);
+ }
+
+
+ /**
+ * @brief 按类型读取一个值并推进游标。
+ * @tparam T 算术类型。
+ * @return 读取到的值。
+ */
+ template
+ [[nodiscard]] T reader()
+ {
+ static_assert(std::is_arithmetic_v, "Template argument should be a primitive type.");
+ T value{ *((T*)_position) };
+ _position += sizeof(T);
+ return value;
+ }
+
+ /**
+ * @brief 读取原始字节块并推进游标。
+ * @param buffer 输出缓冲区。
+ * @param length 读取字节数。
+ */
+ void read(u8* buffer, size_t length)
+ {
+ memcpy(buffer, _position, length);
+ _position += length;
+ }
+
+ /**
+ * @brief 跳过指定字节数。
+ * @param offset 偏移字节数。
+ */
+ void skip(size_t offset)
+ {
+ _position += offset;
+ }
+
+ [[nodiscard]] constexpr const u8 *const buffer_start() const { return _buffer; }
+ [[nodiscard]] constexpr const u8 *const position() const { return _position; }
+ [[nodiscard]] constexpr size_t offset() const { return _position - _buffer; }
+
+private:
+ const u8 *const _buffer;
+ const u8* _position;
+};
+
+
+/**
+ * @brief 可写内存二进制流。
+ */
+class blob_stream_writer
+{
+public:
+ DISABLE_COPY_AND_MOVE(blob_stream_writer);
+ /**
+ * @brief 绑定外部可写缓冲区。
+ * @param buffer 缓冲区首地址。
+ * @param buffer_size 缓冲区大小。
+ */
+ explicit blob_stream_writer(u8* buffer, size_t buffer_size)
+ :_buffer{ buffer }, _position{ buffer }, _buffer_size(buffer_size)
+ {
+ assert(buffer && buffer_size);
+ }
+
+
+ /**
+ * @brief 写入一个算术类型值并推进游标。
+ * @tparam T 算术类型。
+ * @param value 待写入值。
+ */
+ template
+ void write(T value)
+ {
+ static_assert(std::is_arithmetic_v, "Template argument should be a primitive type.");
+ assert(&_position[sizeof(T)] <= &_buffer[_buffer_size]);
+ *((T*)_position) = value;
+ _position += sizeof(T);
+ }
+
+ /**
+ * @brief 写入字符缓冲区。
+ * @param buffer 输入缓冲区。
+ * @param length 写入字节数。
+ */
+ void write(const char* buffer, size_t length)
+ {
+ assert(&_position[length] <= &_buffer[_buffer_size]);
+ memcpy(_position, buffer, length);
+ _position += length;
+ }
+
+ /**
+ * @brief 写入字节缓冲区。
+ * @param buffer 输入缓冲区。
+ * @param length 写入字节数。
+ */
+ void write(const u8* buffer, size_t length)
+ {
+ assert(&_position[length] <= &_buffer[_buffer_size]);
+ memcpy(_position, buffer, length);
+ _position += length;
+ }
+
+ /**
+ * @brief 跳过指定字节数。
+ * @param offset 偏移字节数。
+ */
+ void skip(size_t offset)
+ {
+ assert(&_position[offset] <= &_buffer[_buffer_size]);
+ _position += offset;
+ }
+
+ [[nodiscard]] constexpr const u8 *const buffer_start() const { return _buffer; }
+ [[nodiscard]] constexpr const u8 *const buffer_end() const { return &_buffer[_buffer_size]; }
+ [[nodiscard]] constexpr const u8 *const position() const { return _position; }
+ [[nodiscard]] constexpr size_t offset() const { return _position - _buffer; }
+
+private:
+ u8 *const _buffer;
+ u8* _position;
+ size_t _buffer_size;
+};
+}
diff --git a/Engine/Utilities/Math.h b/Engine/Utilities/Math.h
new file mode 100644
index 0000000..cdd69b6
--- /dev/null
+++ b/Engine/Utilities/Math.h
@@ -0,0 +1,185 @@
+/**
+ * @file Math.h
+ * @brief 数学工具函数与二进制对齐辅助函数。
+ * @details
+ * 提供模板化的数值打包/解包与内存对齐能力,包括:
+ * - clamp 与归一化浮点量化打包;
+ * - 固定/动态对齐粒度下的向上与向下对齐;
+ * - 基于 SIMD 指令的 CRC32 快速计算辅助。
+ */
+#pragma once
+#include "CommonHeader.h"
+#include "MathTypes.h"
+
+namespace XEngine::math {
+
+/**
+ * @brief 将值限制在给定区间内。
+ * @tparam T 标量类型。
+ * @param value 输入值。
+ * @param min 下界。
+ * @param max 上界。
+ * @return 限制后的值。
+ */
+template
+[[nodiscard]] constexpr T
+clamp(T value, T min, T max)
+{
+ return (value < min) ? min : (value > max) ? max : value;
+}
+
+
+/**
+ * @brief 将 [0,1] 浮点值量化到指定位宽的无符号整数。
+ * @tparam bits 量化位宽。
+ * @param i 归一化输入值。
+ * @return 量化结果。
+ */
+template
+[[nodiscard]] constexpr u32
+pack_unit_float(f32 i)
+{
+ static_assert(bits <= sizeof(u32) * 8);
+ assert(i >= 0.f && i <= 1.f);
+ constexpr f32 intervals{ (f32)(((u32)1 << bits) - 1) };
+ return (u32)(intervals * i + 0.5f);
+}
+
+/**
+ * @brief 将 [min,max] 浮点值量化到指定位宽。
+ * @tparam bits 量化位宽。
+ * @param i 输入值。
+ * @param min 下界。
+ * @param max 上界。
+ * @return 量化结果。
+ */
+template
+[[nodiscard]] constexpr u32
+pack_float(f32 i, f32 min, f32 max)
+{
+ assert(min < max);
+ assert(i <= max && i >= min);
+ const f32 distance{ (i - min) / (max - min) };
+ return pack_unit_float(distance);
+}
+
+
+
+/**
+ * @brief 将量化整数还原为 [0,1] 浮点值。
+ * @tparam bits 量化位宽。
+ * @param i 量化输入值。
+ * @return 反量化结果。
+ */
+template
+[[nodiscard]] constexpr f32
+unpack_unit_float(u32 i)
+{
+ static_assert(bits <= sizeof(u32) * 8);
+ assert(i < ((u32)i << bits));
+ constexpr f32 intervals{ (f32)(((u32)1 << bits) - 1) };
+ return (f32)i / intervals;
+}
+
+
+/**
+ * @brief 将量化值还原到 [min,max] 区间。
+ * @tparam bits 量化位宽。
+ * @param i 量化输入值。
+ * @param min 下界。
+ * @param max 上界。
+ * @return 反量化结果。
+ */
+template
+[[nodiscard]] constexpr f32
+unpack_float(f32 i, f32 min, f32 max)
+{
+ assert(min < max);
+ return unpack_unit_float(i) * (max - min) + min;
+}
+
+/**
+ * @brief 按固定对齐值向上对齐。
+ * @tparam alignment 对齐粒度(2 的幂)。
+ * @param size 原始大小。
+ * @return 向上对齐后的大小。
+ */
+template
+[[nodiscard]] constexpr u64
+align_size_up(u64 size)
+{
+ static_assert(alignment, "Alignment must be non-zero.");
+ constexpr u64 mask{ alignment - 1 };
+ static_assert(!(alignment & mask), "Alignment should be a power of 2.");
+ return ((size + mask) & ~mask);
+}
+
+/**
+ * @brief 按固定对齐值向下对齐。
+ * @tparam alignment 对齐粒度(2 的幂)。
+ * @param size 原始大小。
+ * @return 向下对齐后的大小。
+ */
+template
+[[nodiscard]] constexpr u64
+align_size_down(u64 size)
+{
+ static_assert(alignment, "Alignment must be non-zero.");
+ constexpr u64 mask{ alignment - 1 };
+ static_assert(!(alignment & mask), "Alignment should be a power of 2.");
+ return (size & ~mask);
+}
+
+/**
+ * @brief 按运行时对齐值向上对齐。
+ * @param size 原始大小。
+ * @param alignment 对齐粒度(2 的幂)。
+ * @return 向上对齐后的大小。
+ */
+[[nodiscard]] constexpr u64
+align_size_up(u64 size, u64 alignment)
+{
+ assert(alignment && "Alignment must be non-zero.");
+ const u64 mask{ alignment - 1 };
+ assert(!(alignment & mask) && "Alignment should be a power of 2.");
+ return ((size + mask) & ~mask);
+}
+
+/**
+ * @brief 按运行时对齐值向下对齐。
+ * @param size 原始大小。
+ * @param alignment 对齐粒度(2 的幂)。
+ * @return 向下对齐后的大小。
+ */
+[[nodiscard]] constexpr u64
+align_size_down(u64 size, u64 alignment)
+{
+ assert(alignment && "Alignment must be non-zero.");
+ const u64 mask{ alignment - 1 };
+ assert(!(alignment & mask) && "Alignment should be a power of 2.");
+ return (size & ~mask);
+}
+
+/**
+ * @brief 计算数据块的 64 位 CRC32 累积值。
+ * @param data 输入字节流。
+ * @param size 输入长度。
+ * @return CRC32 累积结果。
+ */
+[[nodiscard]] constexpr u64
+calcect_crc32_u64(const u8 *const data, u64 size)
+{
+ assert(size >= sizeof(u64));
+ u64 crc{ 0 };
+ const u8* at{ data };
+ const u8 *const end{ data + align_size_down(size) };
+ while (at < end)
+ {
+ crc = _mm_crc32_u64(crc, *((const u64*)at));
+ at += sizeof(u64);
+ }
+
+ return crc;
+}
+
+}
diff --git a/Engine/Utilities/MathTypes.h b/Engine/Utilities/MathTypes.h
new file mode 100644
index 0000000..3853d6e
--- /dev/null
+++ b/Engine/Utilities/MathTypes.h
@@ -0,0 +1,80 @@
+/**
+ * @file MathTypes.h
+ * @brief 数学常量与 DirectX 数学类型别名集合。
+ * @details
+ * 统一定义引擎常用数学基础元素:
+ * - 常量:pi、two_pi、epsilon;
+ * - 向量/矩阵别名:v2/v3/v4、m3x3/m4x4 等;
+ * - 与 Win64 下 DirectXMath 类型的映射与对齐变体。
+ */
+#pragma once
+#include "CommonHeader.h"
+
+namespace XEngine::math {
+
+//constexpr long double pi = 3.141592653589793238462643383279L;
+/**
+ * @brief 圆周率常量。
+ */
+constexpr float pi = 3.1415927f;
+/**
+ * @brief 两倍圆周率常量。
+ */
+constexpr float two_pi = 6.2831853f;
+
+/**
+ * @brief 浮点比较容差常量。
+ */
+constexpr float epsilon = 1e-5f;
+
+#if defined(_WIN64)
+
+/**
+ * @brief DirectXMath 向量类型别名。
+ * @details
+ * - 无 A 后缀类型使用常规布局;
+ * - A 后缀类型要求 16 字节对齐,适合 SIMD 对齐访问场景。
+ */
+using v2 = DirectX::XMFLOAT2;
+using v2a = DirectX::XMFLOAT2A;
+using v3 = DirectX::XMFLOAT3;
+using v3a = DirectX::XMFLOAT3A;
+using v4 = DirectX::XMFLOAT4;
+using v4a = DirectX::XMFLOAT4A;
+
+/**
+ * @brief DirectXMath 整型向量别名。
+ */
+using u32v2 = DirectX::XMUINT2;
+using u32v3 = DirectX::XMUINT3;
+using u32v4 = DirectX::XMUINT4;
+using s32v2 = DirectX::XMINT2;
+using s32v3 = DirectX::XMINT3;
+using s32v4 = DirectX::XMINT4;
+
+/**
+ * @brief 标量浮点向量别名。
+ */
+using f32v2 = DirectX::XMFLOAT2;
+using f32v3 = DirectX::XMFLOAT3;
+using f32v4 = DirectX::XMFLOAT4;
+
+/**
+ * @brief DirectXMath 矩阵与寄存器向量别名。
+ */
+using Vec4 = DirectX::XMVECTOR;
+using Mat4 = DirectX::XMMATRIX;
+using Mat3 = DirectX::XMFLOAT3X3;
+using Mat4f = DirectX::XMFLOAT4X4;
+using m3x3 = DirectX::XMFLOAT3X3;
+using m4x4 = DirectX::XMFLOAT4X4;
+using m4x4a = DirectX::XMFLOAT4X4A;
+using vector4 = DirectX::XMVECTOR;
+using MAT4 = DirectX::XMMATRIX;
+using vector = DirectX::XMVECTOR;
+using Quat = DirectX::XMVECTOR;
+
+#endif // (_WIN64)
+
+
+}
diff --git a/Engine/Utilities/Utilities.h b/Engine/Utilities/Utilities.h
new file mode 100644
index 0000000..16ab728
--- /dev/null
+++ b/Engine/Utilities/Utilities.h
@@ -0,0 +1,86 @@
+/**
+ * @file Utilities.h
+ * @brief 公共容器与工具模块聚合入口。
+ * @details
+ * 通过编译开关统一切换 STL 与自定义容器实现,并集中导出:
+ * - vector/deque 等基础容器别名;
+ * - erase_unordered 辅助函数;
+ * - FreeList 等常用数据结构头文件。
+ */
+#pragma once
+
+
+/**
+ * @brief 是否使用 STL vector 作为基础容器。
+ */
+#define USE_STL_VECTOR 0
+/**
+ * @brief 是否启用 STL deque 别名。
+ */
+#define USE_STL_DRQUE 1
+
+#if USE_STL_VECTOR
+#include
+#include
+namespace XEngine::utl {
+template
+using vector = std::vector;
+
+
+/**
+ * @brief 通过交换尾元素方式无序删除。
+ * @tparam T 容器类型。
+ * @param v 目标容器。
+ * @param index 删除下标。
+ */
+template
+void erase_unordered(T& v, size_t index)
+{
+ if (v.size() > 1)
+ {
+ std::iter_swap(v.begin() + index, v.end() - 1);
+ v.pop_back();
+ }
+ else
+ {
+ v.clear();
+ }
+}
+}
+#else
+
+#include "Vector.h"
+
+namespace XEngine::utl {
+/**
+ * @brief 调用自定义容器的无序删除接口。
+ * @tparam T 容器类型。
+ * @param v 目标容器。
+ * @param index 删除下标。
+ */
+template
+void erase_unordered(T& v, size_t index)
+{
+ v.erase_unordered(index);
+}
+
+}
+
+#endif
+
+
+#if USE_STL_DRQUE
+#include
+namespace XEngine::utl {
+template
+using deque = std::deque;
+}
+#endif
+
+
+
+
+namespace XEngine::utl {
+}
+
+#include "FreeList.h"
diff --git a/Engine/Utilities/Vector.h b/Engine/Utilities/Vector.h
new file mode 100644
index 0000000..0a2598d
--- /dev/null
+++ b/Engine/Utilities/Vector.h
@@ -0,0 +1,463 @@
+/**
+ * @file Vector.h
+ * @brief 自定义动态数组容器实现。
+ * @details
+ * 该容器提供接近 std::vector 的常用能力,并支持可选析构策略:
+ * - 动态扩容、插入、删除与无序删除;
+ * - 连续内存访问与迭代器接口;
+ * - 在性能敏感场景下复用底层内存分配行为。
+ */
+#pragma once
+#include "CommonHeader.h"
+
+namespace XEngine::utl {
+
+/**
+ * @brief 自定义连续内存动态数组。
+ * @tparam T 元素类型。
+ * @tparam destruct 是否在删除时调用析构。
+ */
+template
+class vector
+{
+
+public:
+ // Default constructor. Doesn~t allocate memory.
+ /**
+ * @brief 构造空容器。
+ */
+ vector() = default;
+
+ /**
+ * @brief 构造并调整到指定元素数量。
+ * @param count 目标元素数量。
+ */
+ constexpr explicit vector(u64 count)
+ {
+ resize(count);
+ }
+
+ /**
+ * @brief 构造并填充指定数量元素。
+ * @param count 目标元素数量。
+ * @param value 填充值。
+ */
+ constexpr explicit vector(u64 count, const T& value)
+ {
+ resize(count, value);
+ }
+
+ constexpr vector(const vector& o)
+ {
+ *this = o;
+ }
+
+ constexpr vector(vector&& o)
+ :_capacity{o._capacity},
+ _size{o._size},
+ _data{o._data}
+ {
+ o.reset();
+ }
+
+
+ constexpr vector& operator=(const vector& o)
+ {
+ assert(this != std::addressof(o));
+ if (this != std::addressof(o))
+ {
+ clear();
+ reserve(o._size);
+ for (auto& item : o)
+ {
+ emplace_back(item);
+ }
+ assert(_size == o._size);
+ }
+
+ return *this;
+ }
+
+ constexpr vector& operator=(vector&& o)
+ {
+ assert(this != std::addressof(o));
+ if (this != std::addressof(o))
+ {
+ destroy();
+ move(o);
+ }
+
+ return *this;
+ }
+
+ ~vector() { destroy(); }
+
+ /**
+ * @brief 追加拷贝元素。
+ * @param value 元素值。
+ */
+ constexpr void push_back(const T& value)
+ {
+ emplace_back(value);
+ }
+
+ /**
+ * @brief 追加右值元素。
+ * @param value 元素值。
+ */
+ constexpr void push_back(const T&& value)
+ {
+ emplace_back(std::move(value));
+ }
+
+ /**
+ * @brief 原位构造并追加元素。
+ * @tparam params 构造参数类型。
+ * @param p 构造参数。
+ * @return 新元素引用。
+ */
+ template
+ constexpr decltype(auto) emplace_back(params&&... p)
+ {
+ if (_size == _capacity)
+ {
+ reserve(((_capacity + 1) * 3) >> 1); // reserve 50% more
+ }
+ assert(_size < _capacity);
+
+ T *const item{ new (std::addressof(_data[_size])) T(std::forward(p)...) };
+ ++_size;
+ return *item;
+ }
+
+ /**
+ * @brief 调整元素数量,新增元素默认构造。
+ * @param new_size 新大小。
+ */
+ constexpr void resize(u64 new_size)
+ {
+ static_assert(std::is_default_constructible::value,
+ "Type must be default-constructible.");
+
+ if (new_size > _size)
+ {
+ reserve(new_size);
+ while (_size < new_size)
+ {
+ emplace_back();
+ }
+ }
+ else if (new_size < _size)
+ {
+ if constexpr (destruct)
+ {
+ destruct_range(new_size, _size);
+ }
+
+ _size = new_size;
+ }
+
+
+ assert(new_size == _size);
+ }
+
+
+ /**
+ * @brief 调整元素数量,新增元素使用给定值填充。
+ * @param new_size 新大小。
+ * @param value 填充值。
+ */
+ constexpr void resize(u64 new_size, const T& value)
+ {
+ static_assert(std::is_copy_constructible::value,
+ "Type must be copy-constructible.");
+
+ if (new_size > _size)
+ {
+ reserve(new_size);
+ while (_size < new_size)
+ {
+ emplace_back(value);
+ }
+ }
+ else if (new_size < _size)
+ {
+ if constexpr (destruct)
+ {
+ destruct_range(new_size, _size);
+ }
+
+ _size = new_size;
+ }
+
+
+ assert(new_size == _size);
+ }
+
+
+ /**
+ * @brief 预留最小容量。
+ * @param new_capacity 目标容量。
+ */
+ constexpr void reserve(u64 new_capacity)
+ {
+ if (new_capacity > _capacity)
+ {
+ // NOTE: realoc() will automatically copy the data in the buffer
+ // if a new region of memory iss allocated.
+ void* new_buffer{ realloc(_data, new_capacity * sizeof(T)) };
+ assert(new_buffer);
+ if (new_buffer)
+ {
+ _data = static_cast(new_buffer);
+ _capacity = new_capacity;
+ }
+ }
+ }
+
+ /**
+ * @brief 删除指定下标元素并保持顺序。
+ * @param index 删除下标。
+ * @return 指向删除位置的指针。
+ */
+ constexpr T *const erase(u64 index)
+ {
+ assert(_data && index < _size);
+ return erase(std::addressof(_data[index]));
+ }
+
+ /**
+ * @brief 删除指定位置元素并保持顺序。
+ * @param item 待删除元素指针。
+ * @return 指向删除位置的指针。
+ */
+ constexpr T *const erase(T *const item)
+ {
+ assert(_data && item >= std::addressof(_data[0]) &&
+ item < std::addressof(_data[_size]));
+
+ if constexpr (destruct) item->~T();
+ --_size;
+ if (item < std::addressof(_data[_size]))
+ {
+ memcpy(item, item + 1, (std::addressof(_data[_size]) - item) * sizeof(T));
+ }
+
+ return item;
+ }
+
+ /**
+ * @brief 无序删除指定下标元素。
+ * @param index 删除下标。
+ * @return 指向删除位置的指针。
+ */
+ constexpr T *const erase_unordered(u64 index)
+ {
+ assert(_data && index < _size);
+ return erase_unordered(std::addressof(_data[index]));
+ }
+
+ /**
+ * @brief 无序删除指定位置元素。
+ * @param item 待删除元素指针。
+ * @return 指向删除位置的指针。
+ */
+ constexpr T *const erase_unordered(T *const item)
+ {
+ assert(_data && item >= std::addressof(_data[0]) &&
+ item < std::addressof(_data[_size]));
+
+ if constexpr (destruct) item->~T();
+ --_size;
+
+ if (item < std::addressof(_data[_size]))
+ {
+ memcpy(item, std::addressof(_data[_size]), sizeof(T));
+ }
+
+ return item;
+ }
+
+ /**
+ * @brief 清空容器中的有效元素。
+ */
+ constexpr void clear()
+ {
+ if constexpr (destruct)
+ {
+ destruct_range(0, _size);
+ }
+ _size = 0;
+ }
+
+
+ /**
+ * @brief 与另一个容器交换内容。
+ * @param o 目标容器。
+ */
+ constexpr void swap(vector& o)
+ {
+ if (this != std::addressof(o))
+ {
+ auto temp(std::move(o));
+ o.move(*this);
+ move(temp);
+ }
+ }
+
+
+ /**
+ * @brief 返回底层数据指针。
+ * @return 数据首地址。
+ */
+ [[nodiscard]] constexpr T* data()
+ {
+ return _data;
+ }
+
+
+ /**
+ * @brief 返回只读底层数据指针。
+ * @return 数据首地址。
+ */
+ [[nodiscard]] constexpr T *const data() const
+ {
+ return _data;
+ }
+
+ /**
+ * @brief 判断容器是否为空。
+ * @return 空返回 true。
+ */
+ [[nodiscard]] constexpr bool empty() const
+ {
+ return _size == 0;
+ }
+
+ /**
+ * @brief 获取元素个数。
+ * @return 元素数量。
+ */
+ [[nodiscard]] constexpr u64 size() const
+ {
+ return _size;
+ }
+
+
+ /**
+ * @brief 获取当前容量。
+ * @return 容量值。
+ */
+ [[nodiscard]] constexpr u64 capacity() const
+ {
+ return _capacity;
+ }
+
+
+ [[nodiscard]] constexpr T& operator [](u64 index)
+ {
+ assert(_data && index < _size);
+ return _data[index];
+ }
+
+ [[nodiscard]] constexpr const T& operator [](u64 index) const
+ {
+ assert(_data && index < _size);
+ return _data[index];
+ }
+
+ [[nodiscard]] constexpr T& front()
+ {
+ assert(_data && _size);
+ return _data[0];
+ }
+
+
+ [[nodiscard]] constexpr const T& front() const
+ {
+ assert(_data && _size);
+ return _data[0];
+ }
+
+ [[nodiscard]] constexpr T& back()
+ {
+ assert(_data && _size);
+ return _data[_size -1];
+ }
+
+ [[nodiscard]] constexpr const T& back() const
+ {
+ assert(_data && _size);
+ return _data[_size - 1];
+ }
+
+
+ [[nodiscard]] constexpr T* begin()
+ {
+ return std::addressof(_data[0]);
+ }
+
+
+ [[nodiscard]] constexpr const T* begin() const
+ {
+ return std::addressof(_data[0]);
+ }
+
+ [[nodiscard]] constexpr T* end()
+ {
+ assert(!(_data == nullptr && _size > 0));
+ return std::addressof(_data[_size]);
+ }
+
+ [[nodiscard]] constexpr const T* end() const
+ {
+ assert(!(_data == nullptr && _size > 0));
+ return std::addressof(_data[_size]);
+ }
+private:
+
+ constexpr void move(vector& o)
+ {
+ _capacity = o._capacity;
+ _size = o._size;
+ _data = o._data;
+ o.reset();
+ }
+
+ constexpr void reset()
+ {
+ _capacity = 0;
+ _size = 0;
+ _data = nullptr;
+ }
+
+ constexpr void destruct_range(u64 first, u64 last)
+ {
+ assert(destruct);
+ assert(first <= _size && last <= _size && first <= last);
+ if (_data)
+ {
+ for (; first != last; ++first)
+ {
+ _data[first].~T();
+ }
+ }
+ }
+
+ constexpr void destroy()
+ {
+ assert([&] {return _capacity ? _data != nullptr : _data == nullptr; }());
+ clear();
+ _capacity = 0;
+ if (_data) free(_data);
+ _data = nullptr;
+ }
+
+ u64 _capacity{ 0 };
+ u64 _size{ 0 };
+ T* _data{ nullptr };
+};
+
+
+}
+
diff --git a/EngineTest/EngineTest.vcxproj b/EngineTest/EngineTest.vcxproj
new file mode 100644
index 0000000..bf5a935
--- /dev/null
+++ b/EngineTest/EngineTest.vcxproj
@@ -0,0 +1,194 @@
+
+
+
+
+ DebugEditor
+ x64
+
+
+ ReleaseEditor
+ x64
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 17.0
+ Win32Proj
+ {d1fb8bbd-6836-431b-a43d-f6ea248b8965}
+ EngineTest
+ 10.0
+
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+ false
+
+
+ false
+
+
+ false
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ $(SolutionDir)Engine\Common;$(SolutionDir)Engine
+ stdcpp17
+
+
+ Windows
+ true
+ $(OutDir)
+
+
+ xcopy /Y /D "$(SolutionDir)packages\dxCompiler\dxcompiler.dll" "$(OutDir)"
+
+
+
+
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ $(SolutionDir)Engine\Common;$(SolutionDir)Engine
+ stdcpp17
+
+
+ Windows
+ true
+ $(OutDir)
+
+
+ xcopy /Y /D "$(SolutionDir)packages\dxCompiler\dxcompiler.dll" "$(OutDir)"
+
+
+
+
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ $(SolutionDir)Engine\Common;$(SolutionDir)Engine
+ stdcpp17
+
+
+ Windows
+ true
+ true
+ true
+ $(OutDir)
+
+
+ xcopy /Y /D "$(SolutionDir)packages\dxCompiler\dxcompiler.dll" "$(OutDir)"
+
+
+
+
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ $(SolutionDir)Engine\Common;$(SolutionDir)Engine
+ stdcpp17
+
+
+ Windows
+ true
+ true
+ true
+ $(OutDir)
+
+
+ xcopy /Y /D "$(SolutionDir)packages\dxCompiler\dxcompiler.dll" "$(OutDir)"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/EngineTest/EngineTest.vcxproj.filters b/EngineTest/EngineTest.vcxproj.filters
new file mode 100644
index 0000000..848a439
--- /dev/null
+++ b/EngineTest/EngineTest.vcxproj.filters
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/EngineTest/Lights.cpp b/EngineTest/Lights.cpp
new file mode 100644
index 0000000..c9cc6b4
--- /dev/null
+++ b/EngineTest/Lights.cpp
@@ -0,0 +1,84 @@
+/**
+ * @file Lights.cpp
+ * @brief 渲染测试场景灯光创建与销毁逻辑。
+ */
+#include "EngineAPI/GameEntity.h"
+#include "EngineAPI/Light.h"
+#include "EngineAPI/TransformComponent.h"
+#include "Graphics_Beta/Renderer.h"
+#include "Test.h"
+
+#if TEST_RENDERER
+
+using namespace XEngine;
+
+game_entity::entity create_one_game_entity(math::v3 position, math::v3 rotation, const char* script_name);
+void remove_game_entity(game_entity::entity_id id);
+
+const u64 left_set{ 0 };
+const u64 right_set{ 1 };
+
+utl::vector lights;
+
+constexpr math::v3
+rgb_to_color(u8 r, u8 g, u8 b) { return { r / 255.f, g / 255.f, b / 255.f }; }
+
+void
+generate_lights()
+{
+ // LEFT_SET
+ graphics::light_init_info info{};
+ info.entity_id = create_one_game_entity({}, {}, nullptr).get_id();
+ info.type = graphics::light::directioinal;
+ info.light_set_key = left_set;
+ info.intensity = 1.f;
+ info.color = rgb_to_color(174, 174, 174);
+
+ lights.emplace_back(graphics::create_light(info));
+
+ info.entity_id = create_one_game_entity({}, { math::pi * 0.5f,0,0 }, nullptr).get_id();
+ info.color = rgb_to_color(17, 27, 28);
+ lights.emplace_back(graphics::create_light(info));
+
+
+ info.entity_id = create_one_game_entity({}, { -math::pi * 0.5f,0,0 }, nullptr).get_id();
+ info.color = rgb_to_color(63, 47, 38);
+ lights.emplace_back(graphics::create_light(info));
+
+
+ //// RIGHT_SET
+ //info.entity_id = create_one_game_entity({}, {}, nullptr).get_id();
+ //info.type = graphics::light::directioinal;
+ //info.light_set_key = right_set;
+ //info.intensity = 1.f;
+ //info.color = rgb_to_color(174, 174, 174);
+
+ //lights.emplace_back(graphics::create_light(info));
+
+ //info.entity_id = create_one_game_entity({}, { math::pi * 0.5f,0,0 }, nullptr).get_id();
+ //info.color = rgb_to_color(17, 27, 28);
+ //lights.emplace_back(graphics::create_light(info));
+
+
+ //info.entity_id = create_one_game_entity({}, { -math::pi * 0.5f,0,0 }, nullptr).get_id();
+ //info.color = rgb_to_color(63, 47, 38);
+ //lights.emplace_back(graphics::create_light(info));
+}
+
+
+void
+remove_lights()
+{
+ for (auto& light : lights)
+ {
+ const game_entity::entity_id id{ light.entity_id() };
+ graphics::remove_light(light.get_id(), light.get_light_set_key());
+ remove_game_entity(id);
+ }
+
+ lights.clear();
+}
+
+#endif
+
+
diff --git a/EngineTest/Main.cpp b/EngineTest/Main.cpp
new file mode 100644
index 0000000..cf84d47
--- /dev/null
+++ b/EngineTest/Main.cpp
@@ -0,0 +1,85 @@
+/**
+ * @file Main.cpp
+ * @brief 测试程序入口与主循环。
+ */
+#include "Test.h"
+
+#pragma comment(lib, "engine.lib")
+
+#if TEST_ENTITY_COMPONENTS
+#include "TestEntityComponent.h"
+#elif TEST_WINDOW_COMPONENTS
+#include "TestWindow.h"
+#elif TEST_RENDERER
+#include "TestRenderer.h"
+#endif
+
+#ifdef _WIN64
+#include
+#include
+
+// TODO: duplicate
+std::filesystem::path
+set_current_directory_to_executable_path()
+{
+ wchar_t path[MAX_PATH]{};
+ const u32 length{ GetModuleFileName(0, &path[0], MAX_PATH) };
+ if (!length || GetLastError() == ERROR_INSUFFICIENT_BUFFER) return {};
+ std::filesystem::path p{ path };
+ std::filesystem::current_path(p.parent_path());
+ return std::filesystem::current_path();
+}
+
+
+int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
+{
+
+#if _DEBUG
+ _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
+#endif
+
+ set_current_directory_to_executable_path();
+
+ engine_test test{};
+
+
+ if (test.initialize())
+ {
+ MSG msg{};
+ bool is_running{ true };
+ while (is_running)
+ {
+ while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ is_running &= (msg.message != WM_QUIT);
+ }
+
+ test.run();
+ }
+ }
+ test.shutdown();
+
+
+}
+
+
+#else
+int main()
+{
+#if _DEBUG
+ _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
+#endif
+ engine_test test{};
+
+ if (test.initialize())
+ {
+ test.run();
+ }
+
+ test.shutdown();
+
+}
+
+#endif
diff --git a/EngineTest/RenderItem.cpp b/EngineTest/RenderItem.cpp
new file mode 100644
index 0000000..211f7dc
--- /dev/null
+++ b/EngineTest/RenderItem.cpp
@@ -0,0 +1,141 @@
+/**
+ * @file RenderItem.cpp
+ * @brief 渲染项创建与销毁测试逻辑。
+ */
+#include "CommonHeader.h"
+#include
+#include "Content/ContentToEngine.h"
+#include "ShaderComponents.h"
+#include "Components/Entity.h"
+#include "Graphics_Beta/Renderer.h"
+#include "../ContentTools/Geometry.h"
+#include "Test.h"
+#if TEST_RENDERER
+using namespace XEngine;
+bool read_file(std::filesystem::path, std::unique_ptr&, u64&);
+
+namespace {
+id::id_type model_id{ id::invalid_id };
+id::id_type vs_id{ id::invalid_id };
+id::id_type ps_id{ id::invalid_id };
+id::id_type mtl_id{ id::invalid_id };
+
+std::unordered_map render_item_entity_map;
+
+void
+load_model()
+{
+ std::unique_ptr model;
+ u64 size{ 0 };
+ read_file("..\\..\\enginetest\\model.model", model, size);
+ model_id = content::create_resource(model.get(), content::asset_type::mesh);
+ assert(id::is_valid(model_id));
+}
+
+void
+load_shaders()
+{
+ shader_file_info info{};
+ info.file_name = "TestShader.hlsl";
+ info.function = "TestShaderVS";
+ info.type = shader_type::vertex;
+
+ const char* shader_path{ "..\\..\\enginetest\\" };
+
+ std::wstring defines[]{ L"ELEMENTS_TYPE=1", L"ELEMENTS_TYPE=3" };
+ utl::vector keys;
+ keys.emplace_back(tools::elements::element_type::static_normal);
+ keys.emplace_back(tools::elements::element_type::static_normal_texture);
+
+ utl::vector extra_args{};
+ utl::vector> vertex_shaders;
+ utl::vector vertex_shaders_pointers;
+ for (u32 i{ 0 }; i < _countof(defines); ++i)
+ {
+ extra_args.clear();
+ extra_args.emplace_back(L"-D");
+ extra_args.emplace_back(defines[i]);
+ vertex_shaders.emplace_back(std::move(compile_shader(info, shader_path, extra_args)));
+ assert(vertex_shaders.back().get());
+ vertex_shaders_pointers.emplace_back(vertex_shaders.back().get());
+ }
+
+
+ extra_args.clear();
+ info.function = "TestShaderPS";
+ info.type = shader_type::pixel;
+
+ auto pixel_shader = compile_shader(info, shader_path, extra_args);
+ assert(pixel_shader.get());
+
+
+ vs_id = content::add_shader_group(vertex_shaders_pointers.data(), (u32)vertex_shaders_pointers.size(), keys.data());
+
+ const u8* pixel_shaders[]{ pixel_shader.get() };
+ ps_id = content::add_shader_group(&pixel_shaders[0], 1, &u32_invalid_id);
+
+}
+
+void
+create_material()
+{
+ graphics::material_init_info info{};
+ info.shader_ids[graphics::shader_type::vertex] = vs_id;
+ info.shader_ids[graphics::shader_type::pixel] = ps_id;
+ info.type = graphics::material_type::opaque;
+ mtl_id = content::create_resource(&info, content::asset_type::material);
+
+}
+}//namespace
+
+id::id_type
+create_render_item(id::id_type entity_id)
+{
+ auto _1 = std::thread{ [] {load_model(); } };
+ auto _2 = std::thread{ [] {load_shaders(); } };
+
+ _1.join();
+ _2.join();
+
+ create_material();
+ id::id_type materials[]{ mtl_id };
+
+ id::id_type item_id{ graphics::add_render_item(entity_id, model_id, _countof(materials), &materials[0]) };
+
+ render_item_entity_map[item_id] = entity_id;
+
+ return item_id;
+}
+
+void
+destroy_render_item(id::id_type item_id)
+{
+ if (id::is_valid(item_id))
+ {
+ graphics::remove_render_item(item_id);
+ auto pair = render_item_entity_map.find(item_id);
+ if (pair != render_item_entity_map.end())
+ {
+ game_entity::remove(game_entity::entity_id{ pair->second });
+ }
+ }
+
+ if (id::is_valid(mtl_id))
+ {
+ content::destroy_resource(mtl_id, content::asset_type::material);
+ }
+
+ if (id::is_valid(vs_id))
+ {
+ content::remove_shader_group(vs_id);
+ }
+ if (id::is_valid(ps_id))
+ {
+ content::remove_shader_group(ps_id);
+ }
+ if (id::is_valid(model_id))
+ {
+ content::destroy_resource(model_id, content::asset_type::mesh);
+ }
+}
+#endif
diff --git a/EngineTest/ShaderComponents.cpp b/EngineTest/ShaderComponents.cpp
new file mode 100644
index 0000000..119e9d9
--- /dev/null
+++ b/EngineTest/ShaderComponents.cpp
@@ -0,0 +1,326 @@
+/**
+ * @file ShaderComponents.cpp
+ * @brief 测试工程着色器编译与缓存实现。
+ */
+#include
+#include
+#include
+#include "CommonHeader.h"
+#include "Graphics_Beta\Direct3D12\D3D12Shaders.h"
+#include "Graphics_Beta\Direct3D12\D3D12CommonHeaders.h"
+#include "Graphics_Beta\Direct3D12\D3D12Core.h"
+#include "ShaderComponents.h"
+
+#include "Content/ContentToEngine.h"
+#include "Utilities/IOStream.h"
+#include
+
+
+using namespace XEngine;
+using namespace XEngine::graphics::d3d12::shaders;
+using namespace Microsoft::WRL;
+
+
+namespace {
+constexpr const char* shaders_source_path{ "../../Engine/Graphics_Beta/Direct3D12/Shaders/" };
+
+struct engine_shader_info
+{
+ engine_shader::id id;
+ shader_file_info info;
+};
+
+constexpr engine_shader_info engine_shader_files[]
+{
+ engine_shader::fullscreen_triangle_vs,{"FullScreenTriangle.hlsl", "FullScreenTriangleVS",shader_type::vertex},
+ engine_shader::fill_color_ps,{"FillColor.hlsl", "FillColorPS", shader_type::pixel},
+ engine_shader::post_process_ps,{"PostProcess.hlsl", "PostProcessPS", shader_type::pixel},
+};
+
+static_assert(_countof(engine_shader_files) == engine_shader::count);
+
+struct dxc_compiled_shader
+{
+ ComPtr byte_code;
+ ComPtr disassembly;
+ DxcShaderHash hash;
+};
+std::wstring
+to_wstring(const char* c)
+{
+ std::string s{ c };
+ return { s.begin(), s.end() };
+}
+
+class shader_compiler
+{
+public:
+ shader_compiler()
+ {
+ HRESULT hr{ S_OK };
+ DXCall(hr = DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&_compiler)));
+ if (FAILED(hr)) return;
+ DXCall(hr = DxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(&_utils)));
+ if (FAILED(hr)) return;
+ DXCall(hr = _utils->CreateDefaultIncludeHandler(&_include_handler));
+ if (FAILED(hr)) return;
+
+ }
+
+ DISABLE_COPY_AND_MOVE(shader_compiler);
+
+ dxc_compiled_shader compile(shader_file_info info, std::filesystem::path full_path, XEngine::utl::vector& extra_args)
+ {
+ assert(_compiler && _utils && _include_handler);
+ HRESULT hr{ S_OK };
+
+ ComPtr source_blob{ nullptr };
+ DXCall(hr = _utils->LoadFile(full_path.c_str(), nullptr, &source_blob));
+ if (FAILED(hr)) return {};
+ assert(source_blob && source_blob->GetBufferSize());
+
+
+ OutputDebugStringA("Compiling ");
+ OutputDebugStringA(info.file_name);
+
+ return compile(source_blob.Get(), get_args(info, extra_args));
+ }
+
+ dxc_compiled_shader compile(IDxcBlobEncoding* source_blob, XEngine::utl::vector compiler_args)
+ {
+ DxcBuffer buffer{};
+ buffer.Encoding = DXC_CP_ACP;
+ buffer.Ptr = source_blob->GetBufferPointer();
+ buffer.Size = source_blob->GetBufferSize();
+
+ utl::vector args;
+ for (const auto& arg : compiler_args)
+ {
+ args.emplace_back(arg.c_str());
+ }
+
+ HRESULT hr{ S_OK };
+ ComPtr results{ nullptr };
+ DXCall(hr = _compiler->Compile(&buffer, args.data(), args.size(), _include_handler.Get(), IID_PPV_ARGS(&results)));
+ if (FAILED(hr)) return {};
+
+ ComPtr errors{ nullptr };
+ DXCall(hr = results->GetOutput(DXC_OUT_ERRORS, IID_PPV_ARGS(&errors), nullptr));
+ if (FAILED(hr)) return {};
+
+ if (errors && errors->GetStringLength())
+ {
+ OutputDebugStringA("\nShader compilation error : \n");
+ OutputDebugStringA(errors->GetStringPointer());
+ }
+ else
+ {
+ OutputDebugStringA(" [ Succeeded ] ");
+ }
+ OutputDebugStringA("\n");
+
+ HRESULT status{ S_OK };
+ DXCall(hr = results->GetStatus(&status));
+ if (FAILED(hr) || FAILED(status)) return {};
+
+ ComPtr hash{ nullptr };
+ DXCall(hr = results->GetOutput(DXC_OUT_SHADER_HASH, IID_PPV_ARGS(&hash), nullptr));
+ if (FAILED(hr)) return {};
+ DxcShaderHash *const hash_buffer{ (DxcShaderHash *const)hash->GetBufferPointer() };
+ assert(!(hash_buffer->Flags & DXC_HASHFLAG_INCLUDES_SOURCE));
+ OutputDebugStringA(" [ Shader Hash: ] ");
+ for (u32 i{ 0 }; i < _countof(hash_buffer->HashDigest); ++i)
+ {
+ char hash_bytes[3]{};
+ sprintf_s(hash_bytes, "%02x", (u32)hash_buffer->HashDigest[i]);
+ OutputDebugStringA(hash_bytes);
+ OutputDebugStringA(" ");
+ }
+ OutputDebugStringA("\n");
+
+ ComPtr shader{ nullptr };
+ DXCall(hr = results->GetOutput(DXC_OUT_OBJECT, IID_PPV_ARGS(&shader), nullptr));
+ if (FAILED(hr)) return {};
+ buffer.Ptr = shader->GetBufferPointer();
+ buffer.Size = shader->GetBufferSize();
+
+ ComPtr disasm_results{ nullptr };
+ DXCall(hr = _compiler->Disassemble(&buffer, IID_PPV_ARGS(&disasm_results)));
+
+ ComPtr disassembly{ nullptr };
+ DXCall(hr = disasm_results->GetOutput(DXC_OUT_DISASSEMBLY, IID_PPV_ARGS(&disassembly), nullptr));
+
+ dxc_compiled_shader result{ shader.Detach(), disassembly.Detach() };
+ memcpy(&result.hash.HashDigest[0], &hash_buffer->HashDigest[0], _countof(hash_buffer->HashDigest));
+
+
+ return result;
+ }
+private:
+
+ utl::vector
+ get_args(const shader_file_info& info, utl::vector& extra_args)
+ {
+
+ utl::vector args{};
+ args.emplace_back(to_wstring(info.file_name));
+
+ args.emplace_back(L"-E");
+ args.emplace_back(to_wstring(info.function));
+ args.emplace_back(L"-T");
+ args.emplace_back(to_wstring(_profile_strings[(u32)info.type]));
+ args.emplace_back(L"-I");
+ args.emplace_back(to_wstring(shaders_source_path));
+ args.emplace_back(L"-enable-16bit-types");
+ args.emplace_back(DXC_ARG_ALL_RESOURCES_BOUND);
+
+#if _DEBUG
+ args.emplace_back(DXC_ARG_DEBUG);
+ args.emplace_back(DXC_ARG_SKIP_OPTIMIZATIONS);
+#else
+ args.emplace_back(DXC_ARG_OPTIMIZATION_LEVEL3);
+#endif // _DEBUG
+ args.emplace_back(DXC_ARG_WARNINGS_ARE_ERRORS);
+ args.emplace_back(L"-Qstrip_reflect");
+ args.emplace_back(L"-Qstrip_debug");
+
+
+ for (const auto& arg : extra_args)
+ {
+ args.emplace_back(arg.c_str());
+ }
+
+ return args;
+
+ }
+
+ constexpr static const char* _profile_strings[]{ "vs_6_6", "hs_6_6", "ds_6_6", "gs_6_6", "ps_6_6", "cs_6_6", "as_6_6", "ms_6_6" };
+ static_assert(_countof(_profile_strings) == shader_type::count);
+
+ ComPtr _compiler{ nullptr };
+ ComPtr _utils{ nullptr };
+ ComPtr _include_handler{ nullptr };
+};
+
+decltype(auto)
+get_engine_shaders_path()
+{
+ return std::filesystem::absolute(graphics::get_engine_shaders_path(graphics::graphics_platform::direct3d12));
+}
+
+bool
+compiled_shaders_are_up_to_date()
+{
+ auto engine_shaders_path = get_engine_shaders_path();
+ if (!std::filesystem::exists(engine_shaders_path))return false;
+ auto shaders_compilation_time = std::filesystem::last_write_time(engine_shaders_path);
+
+ std::filesystem::path full_path{};
+
+ for (u32 i{ 0 }; i < engine_shader::count; ++i) {
+ auto& info = engine_shader_files[i];
+
+ full_path = shaders_source_path;
+ full_path += info.info.file_name;
+ if (!std::filesystem::exists(full_path)) return false;
+
+ auto shader_file_time = std::filesystem::last_write_time(full_path);
+ if (shader_file_time > shaders_compilation_time)
+ {
+ return false;
+ }
+
+
+ }
+
+ return true;
+}
+
+bool
+save_compiled_shaders(utl::vector& shaders)
+{
+ auto engine_shaders_path = get_engine_shaders_path();
+ std::filesystem::create_directories(engine_shaders_path.parent_path());
+ std::ofstream file(engine_shaders_path, std::ios::out | std::ios::binary);
+ if (!file || !std::filesystem::exists(engine_shaders_path))
+ {
+ file.close();
+ return false;
+ }
+
+ for (const auto& shader : shaders)
+ {
+ const D3D12_SHADER_BYTECODE byte_code{ shader.byte_code->GetBufferPointer(), shader.byte_code->GetBufferSize() };
+ file.write((char*)&byte_code.BytecodeLength, sizeof(byte_code.BytecodeLength));
+ file.write((char*)&shader.hash.HashDigest[0], _countof(shader.hash.HashDigest));
+ file.write((char*)byte_code.pShaderBytecode, byte_code.BytecodeLength);
+ }
+
+ file.close();
+
+ return true;
+}
+
+} // namespace
+
+std::unique_ptr
+compile_shader(shader_file_info info, const char* file_path, utl::vector& extra_args)
+{
+ std::filesystem::path full_path{ file_path };
+ full_path += info.file_name;
+ if (!std::filesystem::exists(full_path)) return {};
+
+
+ shader_compiler compiler{};
+ dxc_compiled_shader compiled_shader{ compiler.compile(info, full_path, extra_args) };
+
+ if (compiled_shader.byte_code != nullptr && compiled_shader.byte_code->GetBufferPointer() && compiled_shader.byte_code->GetBufferSize())
+ {
+ static_assert(content::compiled_shader::hash_length == _countof(DxcShaderHash::HashDigest));
+ const u64 buffer_size{ sizeof(u64) + content::compiled_shader::hash_length + compiled_shader.byte_code->GetBufferSize() };
+ std::unique_ptr buffer{ std::make_unique(buffer_size) };
+ utl::blob_stream_writer blob{ buffer.get(), buffer_size };
+ blob.write(compiled_shader.byte_code->GetBufferSize());
+ blob.write(compiled_shader.hash.HashDigest, content::compiled_shader::hash_length);
+ blob.write((u8*)compiled_shader.byte_code->GetBufferPointer(), compiled_shader.byte_code->GetBufferSize());
+
+ assert(blob.offset() == buffer_size);
+ return buffer;
+ }
+
+ return {};
+}
+
+
+bool
+compile_shaders()
+{
+ if (compiled_shaders_are_up_to_date()) return true;
+
+ shader_compiler compiler{};
+ utl::vector shaders;
+ std::filesystem::path full_path{};
+
+
+ for (u32 i{ 0 }; i < engine_shader::count; ++i)
+ {
+ auto& file = engine_shader_files[i];
+
+ full_path = shaders_source_path;
+ full_path += file.info.file_name;
+ if (!std::filesystem::exists(full_path)) return false;
+ utl::vector extra_args{};
+
+ dxc_compiled_shader compiled_shader{ compiler.compile(file.info, full_path, extra_args) };
+ if (compiled_shader.byte_code != nullptr && compiled_shader.byte_code->GetBufferPointer() && compiled_shader.byte_code->GetBufferSize())
+ {
+ shaders.emplace_back(std::move(compiled_shader));
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ return save_compiled_shaders(shaders);
+}
diff --git a/EngineTest/ShaderComponents.h b/EngineTest/ShaderComponents.h
new file mode 100644
index 0000000..6199af7
--- /dev/null
+++ b/EngineTest/ShaderComponents.h
@@ -0,0 +1,35 @@
+/**
+ * @file ShaderComponents.h
+ * @brief 测试工程着色器编译接口定义。
+ */
+#pragma once
+#include "CommonHeader.h"
+#pragma comment(lib, "dxcompiler.lib")
+
+struct shader_type {
+ enum type :u32 {
+ vertex = 0,
+ hull,
+ domain,
+ geometry,
+ pixel,
+ compute,
+ amplification,
+ mesh,
+
+ count
+ };
+
+
+};
+
+
+struct shader_file_info
+{
+ const char* file_name;
+ const char* function;
+ shader_type::type type;
+};
+
+std::unique_ptr compile_shader(shader_file_info info, const char* file_path, XEngine::utl::vector& extra_args);
+bool compile_shaders();
diff --git a/EngineTest/Test.h b/EngineTest/Test.h
new file mode 100644
index 0000000..e33bb0e
--- /dev/null
+++ b/EngineTest/Test.h
@@ -0,0 +1,72 @@
+/**
+ * @file Test.h
+ * @brief 测试基类与计时工具定义。
+ */
+#pragma once
+#include
+#include
+#include
+#include
+#include "Id.h"
+
+
+#define TEST_ENTITY_COMPONENTS 0
+#define TEST_WINDOW_COMPONENTS 0
+#define TEST_RENDERER 1
+
+class Test
+{
+public:
+ virtual bool initialize() = 0;
+ virtual void run() = 0;
+ virtual bool shutdown() = 0;
+};
+
+#if _WIN64
+#include
+class time_it
+{
+public:
+ using clock = std::chrono::high_resolution_clock;
+ using time_stamp = std::chrono::steady_clock::time_point;
+
+ constexpr float dt_avg() const { return _dt_avg * 1e-3f; }
+ void begin()
+ {
+ _start = clock::now();
+ }
+
+ void end()
+ {
+
+ auto dt = clock::now() - _start;
+ _msg_avg += ((float)std::chrono::duration_cast(dt).count() - _msg_avg) / (float)_counter;
+ ++_counter;
+ _dt_avg = _msg_avg;
+
+ if (std::chrono::duration_cast(clock::now() - _seconds).count() >= 1)
+ {
+ OutputDebugStringA("Avg. frame (ms): ");
+ OutputDebugStringA(std::to_string(_msg_avg).c_str());
+ OutputDebugStringA((" " + std::to_string(_counter)).c_str());
+ OutputDebugStringA(" fps");
+ OutputDebugStringA("\n");
+ _msg_avg = 0.f;
+ _counter = 1;
+ _seconds = clock::now();
+ }
+
+ }
+
+
+
+private:
+ float _dt_avg{ 16.7f };
+ float _msg_avg{ 0.f };
+ int _counter{ 1 };
+ time_stamp _start;
+ time_stamp _seconds{ clock::now() };
+};
+
+
+#endif
diff --git a/EngineTest/TestEntityComponent.h b/EngineTest/TestEntityComponent.h
new file mode 100644
index 0000000..093e033
--- /dev/null
+++ b/EngineTest/TestEntityComponent.h
@@ -0,0 +1,98 @@
+/**
+ * @file TestEntityComponent.h
+ * @brief 实体与组件系统测试用例定义。
+ */
+#pragma once
+#include "Test.h"
+#include "..\Engine\Components\Entity.h"
+#include "..\Engine\Components\Transform.h"
+
+#include
+#include
+
+using namespace XEngine;
+
+class engine_test : public Test
+{
+public:
+ bool initialize() override
+ {
+ srand((u32)time(nullptr));
+ return true;
+ }
+ void run() override
+ {
+ //do {
+ for (u32 i{ 0 }; i < 10; ++i)
+ {
+ create_random();
+ remove_random();
+ _num_entities = (u32)_entities.size();
+ }
+ print_results();
+ //} while (getchar() != 'q');
+ }
+ bool shutdown() override
+ {
+ return true;
+ }
+
+
+private:
+ utl::vector _entities;
+
+ u32 _added{ 0 };
+ u32 _removed{ 0 };
+ u32 _num_entities{ 0 };
+
+
+
+ void create_random()
+ {
+ u32 count = rand() % 20;
+ if (_entities.empty()) count = 10;
+ transform::init_info transform_info{};
+ game_entity::entity_info entity_info
+ {
+ &transform_info,
+ nullptr,
+ };
+
+ while (count > 0)
+ {
+ ++_added;
+ game_entity::entity entity{ game_entity::create(entity_info) };
+ assert(entity.is_valid() && id::is_valid(entity.get_id()));
+ _entities.push_back(entity);
+ assert(game_entity::is_alive(entity.get_id()));
+ --count;
+ }
+ }
+
+ void print_results()
+ {
+ std::cout << "Entities created:" << _added << std::endl;
+ std::cout << "Entities deleted:" << _removed << std::endl;
+ }
+
+ void remove_random()
+ {
+ u32 count = rand() % 20;
+ if (_entities.size() < 1000)return;
+ while (count > 0)
+ {
+ ++_removed;
+ const u32 index{ (u32)rand() % (u32)_entities.size() };
+ auto entity = _entities[index];
+ assert(entity.is_valid() && id::is_valid(entity.get_id()));
+ if (entity.is_valid())
+ {
+ game_entity::remove(entity.get_id());
+ _entities.erase(_entities.begin() + index);
+ assert(!game_entity::is_alive(entity.get_id()));
+ }
+ --count;
+ }
+ }
+
+};
diff --git a/EngineTest/TestRenderer_Beta.cpp b/EngineTest/TestRenderer_Beta.cpp
new file mode 100644
index 0000000..f9fcccf
--- /dev/null
+++ b/EngineTest/TestRenderer_Beta.cpp
@@ -0,0 +1,355 @@
+/**
+ * @file TestRenderer.cpp
+ * @brief 渲染功能综合测试实现。
+ */
+
+#include "TestRenderer.h"
+#include "Platform/Platform.h"
+#include "Platform/PlatformTypes.h"
+#include "Graphics_Beta/Renderer.h"
+#include "ShaderComponents.h"
+#include "Content/ContentToEngine.h"
+#include "Graphics_Beta/Direct3D12/D3D12Core.h"
+#include "Components/Script.h"
+#include "Components/Entity.h"
+#include "Components/Transform.h"
+#include
+#include
+
+
+#if TEST_RENDERER
+using namespace XEngine;
+
+class rotator_script;
+REGISTER_SCRIPT(rotator_script);
+class rotator_script :public script::entity_script
+{
+public:
+ constexpr explicit rotator_script(game_entity::entity entity)
+ : script::entity_script{ entity }{}
+ void begin_play() override {}
+ void update(float dt) override
+ {
+ _angle += 0.25f * dt * math::two_pi;
+ if (_angle > math::two_pi) _angle -= math::two_pi;
+ math::v3a rot{ 0.f, _angle, 0.f };
+ DirectX::XMVECTOR quat{ DirectX::XMQuaternionRotationRollPitchYawFromVector(DirectX::XMLoadFloat3A(&rot)) };
+ math::v4 rot_quat{};
+ DirectX::XMStoreFloat4(&rot_quat, quat);
+ set_rotation(rot_quat);
+ }
+
+private:
+ f32 _angle{ 0.f };
+};
+
+// Multithreading test worker spawn code ///////////////////
+
+#define ENABLE_TEST_WORKERS 0
+
+constexpr u32 num_threads{ 8 };
+bool wshutdown{ false };
+std::thread workers[num_threads];
+
+utl::vector buffer(1024 * 1024, 0);
+
+void buffer_test_worker()
+{
+ while (!wshutdown)
+ {
+ auto* resource = graphics::d3d12::d3dx::create_buffer(buffer.data(), (u32)buffer.size());
+
+ graphics::d3d12::core::deferred_release(resource);
+ }
+}
+
+template
+void init_test_workers(FnPtr&& fnPtr, Args&&... args)
+{
+#if ENABLE_TEST_WORKERS
+ wshutdown = false;
+ for (auto& w : workers)
+ w = std::thread(std::forward(fnPtr), std::forward(args)...);
+#endif
+}
+
+void joint_test_workers()
+{
+#if ENABLE_TEST_WORKERS
+ wshutdown = true;
+ for (auto&w : workers) w.join();
+#endif
+}
+//////////////////////
+struct camera_surface {
+ game_entity::entity entity{};
+ graphics::camera camera{};
+ graphics::render_surface surface{};
+};
+id::id_type item_id{ id::invalid_id };
+id::id_type model_id{ id::invalid_id };
+
+camera_surface _surfaces[3]{};
+time_it timer{};
+
+bool resized{ false };
+bool is_restarting{ false };
+void destroy_camera_surface(camera_surface& surface);
+game_entity::entity create_one_game_entity(math::v3 position, math::v3 rotation, const char* script_name);
+bool test_initialize();
+void test_shutdown();
+id::id_type create_render_item(id::id_type entity_id);
+void destroy_render_item(id::id_type item_id);
+
+void generate_lights();
+void remove_lights();
+
+LRESULT win_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+ bool toggle_fullscreen{ false };
+ switch (msg)
+ {
+ case WM_DESTROY:
+ {
+ bool all_closed{ true };
+ for (u32 i{ 0 }; i < _countof(_surfaces); ++i)
+ {
+ if (_surfaces[i].surface.window.is_valid())
+ {
+
+ if (_surfaces[i].surface.window.is_closed())
+ {
+ destroy_camera_surface(_surfaces[i]);
+ }
+ else
+ {
+ all_closed = false;
+ }
+ }
+ }
+ if (all_closed && !is_restarting)
+ {
+ PostQuitMessage(0);
+ return 0;
+ }
+ }
+ case WM_SIZE:
+ resized = (wparam != SIZE_MINIMIZED);
+ break;
+ case WM_SYSCHAR:
+ toggle_fullscreen = (wparam == VK_RETURN && (HIWORD(lparam) & KF_ALTDOWN));
+ break;
+ case WM_KEYDOWN:
+ if (wparam == VK_ESCAPE)
+ {
+ PostMessage(hwnd, WM_CLOSE, 0, 0);
+ return 0;
+ }
+ else if (wparam == VK_F11)
+ {
+ is_restarting = true;
+ test_shutdown();
+ test_initialize();
+ }
+ }
+
+ if ((resized & (GetAsyncKeyState(VK_LBUTTON) >= 0)) || toggle_fullscreen)
+ {
+ platform::window win{ platform::window_id{(id::id_type)GetWindowLongPtr(hwnd, GWLP_USERDATA)} };
+ for (u32 i{ 0 }; i < _countof(_surfaces); ++i)
+ {
+ if (win.get_id() == _surfaces[i].surface.window.get_id())
+ {
+ if (toggle_fullscreen)
+ {
+ win.set_fullscreen(!win.is_fullscreen());
+ return 0;
+ }
+ else
+ {
+ _surfaces[i].surface.surface.resize(win.width(), win.height());
+ _surfaces[i].camera.aspect_ratio((f32)win.width() / win.height());
+ resized = false;
+ }
+ break;
+ }
+ }
+ }
+
+ return DefWindowProc(hwnd, msg, wparam, lparam);
+}
+
+void
+create_camera_surface(camera_surface& surface, platform::window_init_info info)
+{
+ surface.surface.window = platform::create_window(&info);
+ surface.surface.surface = graphics::create_surface(surface.surface.window);
+ surface.entity = create_one_game_entity({ 0.0f,0.f,6.0f }, { 0.f, 3.14f, 0.f }, nullptr);
+ surface.camera = graphics::create_camera(graphics::perspective_camera_init_info{ surface.entity.get_id() });
+ surface.camera.aspect_ratio((f32)surface.surface.window.width() / surface.surface.window.height());
+}
+
+
+void
+destroy_camera_surface(camera_surface& surface)
+{
+ camera_surface temp{ surface };
+ surface = {};
+ if (temp.surface.surface.is_valid()) graphics::remove_surface(temp.surface.surface.get_id());
+ if (temp.surface.window.is_valid()) platform::remove_window(temp.surface.window.get_id());
+ if (temp.camera.is_valid()) graphics::remove_camera(temp.camera.get_id());
+ if (temp.entity.is_valid()) game_entity::remove(temp.entity.get_id());
+}
+
+game_entity::entity
+create_one_game_entity(math::v3 position, math::v3 rotation, const char* script_name)
+{
+ transform::init_info transform_info{};
+ DirectX::XMVECTOR quat{ DirectX::XMQuaternionRotationRollPitchYawFromVector(DirectX::XMLoadFloat3(&rotation)) };
+ math::v4a rot_quat;
+ DirectX::XMStoreFloat4A(&rot_quat, quat);
+ memcpy(&transform_info.rotation[0], &rot_quat.x, sizeof(transform_info.rotation));
+ memcpy(&transform_info.position[0], &position.x, sizeof(transform_info.position));
+
+ script::init_info script_info{};
+ if (script_name)
+ {
+ script_info.script_creator = script::detail::get_script_creator(script::detail::string_hash()(script_name));
+ assert(script_info.script_creator);
+ }
+
+
+ game_entity::entity_info entity_info{};
+ entity_info.transform = &transform_info;
+ entity_info.script = &script_info;
+
+ game_entity::entity ntt{ game_entity::create(entity_info) };
+ assert(ntt.is_valid());
+ return ntt;
+}
+
+void
+remove_game_entity(game_entity::entity_id id)
+{
+ game_entity::remove(id);
+}
+
+bool
+read_file(std::filesystem::path path, std::unique_ptr&data, u64& size)
+{
+ if (!std::filesystem::exists(path)) return false;
+
+ size = std::filesystem::file_size(path);
+ assert(size);
+ if (!size) return false;
+ data = std::make_unique(size);
+ std::ifstream file{ path, std::ios::in | std::ios::binary };
+ if (!file || !file.read((char*)data.get(), size))
+ {
+ file.close();
+ return false;
+ }
+
+ file.close();
+ return true;
+}
+
+
+bool
+test_initialize()
+{
+ while (!compile_shaders())
+ {
+ if (MessageBox(nullptr, L"Failed to compile engine shaders.", L"sahder Compilation Error", MB_RETRYCANCEL) != IDRETRY)
+ return false;
+ }
+
+ if (!graphics::initialize(graphics::graphics_platform::direct3d12))
+ return false;
+
+ platform::window_init_info info[]
+ {
+ {&win_proc, nullptr, L"Test Window 1", 200, 100,400,400},
+ {&win_proc, nullptr, L"Test Window 2", 700, 100,400,400},
+ {&win_proc, nullptr, L"Test Window 3", 1200,100,400,400},/*
+ {&win_proc, nullptr, L"Test Window 4", 200, 600,400,400},
+ {&win_proc, nullptr, L"Test Window 5", 700, 600,400,400},
+ {&win_proc, nullptr, L"Test Window 6", 1200,600,400,400},*/
+ };
+ static_assert(_countof(info) == _countof(_surfaces));
+ for (u32 i{ 0 }; i < _countof(_surfaces); ++i)
+ create_camera_surface(_surfaces[i], info[i]);
+
+
+ //load test model
+ std::unique_ptr model;
+ u64 size{ 0 };
+ if (!read_file("..\\..\\enginetest\\model.model", model, size)) return false;
+ model_id = content::create_resource(model.get(), content::asset_type::mesh);
+ if (!id::is_valid(model_id))return false;
+ init_test_workers(buffer_test_worker);
+
+ item_id = create_render_item(create_one_game_entity({ }, { }, nullptr).get_id());
+
+ generate_lights();
+
+ is_restarting = false;
+ return true;
+}
+
+void
+test_shutdown()
+{
+ remove_lights();
+ destroy_render_item(item_id);
+ joint_test_workers();
+
+ if (id::is_valid(model_id))
+ {
+ content::destroy_resource(model_id, content::asset_type::mesh);
+ }
+ for (u32 i{ 0 }; i < _countof(_surfaces); ++i)
+ destroy_camera_surface(_surfaces[i]);
+
+ graphics::shutdown();
+}
+
+bool
+engine_test::initialize()
+{
+ return test_initialize();
+}
+
+void
+engine_test::run()
+{
+ timer.begin();
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ script::update(timer.dt_avg());
+ for (u32 i{ 0 }; i < _countof(_surfaces); ++i)
+ {
+ if (_surfaces[i].surface.surface.is_valid())
+ {
+ f32 threshold{ 10 };
+
+ graphics::frame_info info{};
+ info.render_item_ids = &item_id;
+ info.render_item_count = 1;
+ info.thresholds = &threshold;
+ info.light_set_key = 0;
+ info.average_frame_time = timer.dt_avg();
+ info.camera_id = _surfaces[i].camera.get_id();
+
+ _surfaces[i].surface.surface.render(info);
+ }
+ }
+ timer.end();
+}
+bool
+engine_test::shutdown()
+{
+ test_shutdown();
+ return true;
+}
+
+#endif
diff --git a/EngineTest/TestShader.hlsl b/EngineTest/TestShader.hlsl
new file mode 100644
index 0000000..b773e9a
--- /dev/null
+++ b/EngineTest/TestShader.hlsl
@@ -0,0 +1,154 @@
+//#include "../Engine/Graphics/Direct3D12/Shaders/Common.hlsli"
+#include "D:\AllWX\AllC\XEngine\Engine\Graphics\Direct3D12\Shaders\Common.hlsli"
+
+struct VertexOut
+{
+ float4 HomogeneousPosition : SV_Position;
+ float3 WorldPosition : POSITIONT;
+ float3 WorldNormal : NORMAL;
+ float3 WorldTangent : TANGENT;
+ float2 uv : TEXTURE;
+};
+
+struct PixelOut
+{
+ float4 Color : SV_TARGET0;
+};
+
+#define ElementsTypeStaticNormal 0x01
+#define ElementsTypeStaticNormalTexture 0x03
+#define ElementsTypeStaticColor 0x04
+#define ElementsTypeSkeletal 0x08
+#define ElementsTypeSkeletalColor ElementsTypeSkeletal | ElementsTypeStaticColor
+#define ElementsTypeSkeletalNormal ElementsTypeSkeletal | ElementsTypeStaticNormal
+#define ElementsTypeSkeletalNormalColor ElementsTypeSkeletalNormal | ElementsTypeSkeletalColor
+#define ElementsTypeSkeletalNormalTexture ElementsTypeSkeletal | ElementsTypeStaticNormalTexture
+#define ElementsTypeSkeletalNormalTextureColor ElementsTypeSkeletalNormalTexture | ElementsTypeStaticColor
+
+struct VertexElement
+{
+#if ELEMENTS_TYPE == ElementsTypeStaticNormal
+ uint ColorTSign;
+ uint16_t2 Normal;
+#elif ELEMENTS_TYPE == ElementsTypeStaticNormalTexture
+ uint ColorTSign;
+ uint16_t2 Normal;
+ uint16_t2 Tangent;
+ float2 UV;
+#elif ELEMENTS_TYPE == ElementsTypeStaticColor
+#elif ELEMENTS_TYPE == ElementsTypeSkeletal
+#elif ELEMENTS_TYPE == ElementsTypeSkeletalColor
+#elif ELEMENTS_TYPE == ElementsTypeSkeletalNormal
+#elif ELEMENTS_TYPE == ElementsTypeSkeletalNormalColor
+#elif ELEMENTS_TYPE == ElementsTypeSkeletalNormalTexture
+#elif ELEMENTS_TYPE == ElementsTypeSkeletalNormalTextureColor
+
+#endif
+};
+
+
+const static float InvIntervals = 2.f / ((1 << 16) - 1);
+
+ConstantBuffer GlobalData : register(b0, space0);
+ConstantBuffer PerObjectBuffer : register(b1, space0);
+StructuredBuffer VertexPositions : register(t0, space0);
+StructuredBuffer Elements : register(t1, space0);
+
+StructuredBuffer DirectionLights : register(t3, space0);
+
+VertexOut TestShaderVS(in uint VertexIdx : SV_VertexID)
+{
+ VertexOut vsOut;
+ float4x4 IdentityMatrix =
+ {
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ };
+
+ float4 position = float4(VertexPositions[VertexIdx], 1.f);
+ float4 worldPosition = mul(PerObjectBuffer.World, position);
+
+#if ELEMENTS_TYPE == ElementsTypeStaticNormal
+ VertexElement element = Elements[VertexIdx];
+ float2 nXY = element.Normal * InvIntervals - 1.f;
+ uint signs = (element.ColorTSign >> 24) & 0xff;
+
+ float nSign = float(signs & 0x02) - 1;
+ float3 normal = float3(nXY.x, nXY.y, sqrt(saturate(1.f - dot(normal.xy, normal.xy))) * nSign);
+
+ vsOut.HomogeneousPosition = mul(PerObjectBuffer.WorldViewProjection, position);
+ vsOut.WorldPosition = worldPosition.xyz;
+ vsOut.WorldNormal = 0;
+ vsOut.WorldTangent = 0.f;
+ vsOut.uv = 0.f;
+#elif ELEMENTS_TYPE == ElementsTypeStaticNormalTexture
+ VertexElement element = Elements[VertexIdx];
+ float2 nXY = element.Normal * InvIntervals - 1.f;
+ uint signs = (element.ColorTSign >> 24) & 0xff;
+
+ float nSign = float(signs & 0x02) - 1;
+ float3 normal = float3(nXY.x, nXY.y, sqrt(saturate(1.f - dot(normal.xy, normal.xy))) * nSign);
+
+ vsOut.HomogeneousPosition = mul(PerObjectBuffer.WorldViewProjection, position);
+ vsOut.WorldPosition = worldPosition.xyz;
+ vsOut.WorldNormal = mul(float4(normal, 0.f), PerObjectBuffer.InvWorld).xyz;
+ vsOut.WorldNormal = mul(PerObjectBuffer.World, float4(normal, 0.f)).xyz;
+ //vsOut.WorldNormal = sqrt(saturate(1.f - dot(normal.xy, normal.xy))) * nSign;
+ vsOut.WorldTangent = 0.f;
+ vsOut.uv = 0.f;
+#else
+#undef ELEMENTS_TYPE
+ vsOut.HomogeneousPosition = mul(PerObjectBuffer.WorldViewProjection, position);
+ vsOut.WorldPosition = worldPosition.xyz;
+ vsOut.WorldNormal = 0.f;
+ vsOut.WorldTangent = 0.f;
+ vsOut.uv = 0.f;
+#endif
+
+ return vsOut;
+}
+
+[earlydepthstencil]
+PixelOut TestShaderPS(in VertexOut psIn)
+{
+ PixelOut psOut;
+ psOut.Color = float4(psIn.WorldNormal, 1);
+ float4x4 IdentityMatrix =
+ {
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ };
+ float3 normal = normalize(psIn.WorldNormal);
+ float3 viewDir = normalize(GlobalData.CameraPosition - psIn.WorldPosition);
+
+ float3 color = 0;
+
+ for (uint i = 0; i < GlobalData.NumIdrectionalLights; ++i)
+ {
+ DirectionalLightParameters light = DirectionLights[i];
+
+ float3 lr = normalize(light.Direction);
+ float3 lightDirection = mul(IdentityMatrix, float4(lr, 0)).xyz;
+
+ //float3 lightDirection = normalize(float3(0, 1, 0));
+ float diffuse = max(dot(normal, -lightDirection), 0.f);
+ float3 h = (viewDir + lightDirection);
+ float specular = pow(max(dot(normal, h), 0.f), 16) * 0.5f;
+
+
+ //float3 lightColor = float3(1, 1, 1);
+ float3 lightColor = light.Color * light.Intensity;
+ //color = dot(normal, -lr);
+ color = normal;
+ }
+
+
+ float3 ambient = 10 / 255.f;
+ psOut.Color = saturate(float4(color + ambient, 1.f));
+
+ return psOut;
+}
\ No newline at end of file
diff --git a/EngineTest/TestWindow.h b/EngineTest/TestWindow.h
new file mode 100644
index 0000000..07e6539
--- /dev/null
+++ b/EngineTest/TestWindow.h
@@ -0,0 +1,78 @@
+/**
+ * @file TestWindow.h
+ * @brief 窗口系统测试用例实现。
+ */
+#pragma once
+#include "Test.h"
+#include "..\Platform\Platform.h"
+#include "..\Platform\PlatformTypes.h"
+
+using namespace XEngine;
+
+platform::window _windows[4];
+
+LRESULT win_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+ switch (msg)
+ {
+ case WM_DESTROY:
+ {
+ bool all_closed{ true };
+ for (u32 i{ 0 }; i < _countof(_windows); ++i)
+ {
+ if (!_windows[i].is_closed())
+ {
+ all_closed = false;
+ }
+ }
+ if (all_closed)
+ {
+ PostQuitMessage(0);
+ return 0;
+ }
+ }
+ case WM_SYSCHAR:
+ if (wparam == VK_RETURN && (HIWORD(lparam) & KF_ALTDOWN))
+ {
+ platform::window win{ platform::window_id{(id::id_type)GetWindowLongPtr(hwnd, GWLP_USERDATA)} };
+ win.set_fullscreen(!win.is_fullscreen());
+ return 0;
+ }
+ default:
+ break;
+ }
+
+ return DefWindowProc(hwnd, msg, wparam, lparam);
+}
+
+class engine_test :public Test
+{
+public:
+ bool initialize() override
+ {
+ platform::window_init_info info[]
+ {
+ {&win_proc, nullptr, L"Test Window 1", 100,100,400,800},
+ {&win_proc, nullptr, L"Test Window 2", 150,200,400,700},
+ {&win_proc, nullptr, L"Test Window 3", 200,300,400,600},
+ {&win_proc, nullptr, L"Test Window 4", 150,400,400,500},
+ };
+ static_assert(_countof(info) == _countof(_windows));
+ for (u32 i{ 0 }; i < _countof(_windows); ++i)
+ _windows[i] = platform::create_window(&info[i]);
+ return true;
+ }
+ void run() override
+ {
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ }
+ bool shutdown() override
+ {
+ for (u32 i{ 0 }; i < _countof(_windows); ++i)
+ platform::remove_window(_windows[i].get_id());
+ return true;
+ }
+
+
+
+};
diff --git a/EngineTest/model.model b/EngineTest/model.model
new file mode 100644
index 0000000..f5985db
Binary files /dev/null and b/EngineTest/model.model differ
diff --git a/FeatureExtractDemo.sln b/FeatureExtractDemo.sln
new file mode 100644
index 0000000..f780efc
--- /dev/null
+++ b/FeatureExtractDemo.sln
@@ -0,0 +1,38 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.12.35527.113
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Engine", "Engine\Engine.vcxproj", "{838F1EAE-48A4-40BA-9B8F-332D37EA66CD}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EngineTest", "EngineTest\EngineTest.vcxproj", "{D1FB8BBD-6836-431B-A43D-F6EA248B8965}"
+ ProjectSection(ProjectDependencies) = postProject
+ {838F1EAE-48A4-40BA-9B8F-332D37EA66CD} = {838F1EAE-48A4-40BA-9B8F-332D37EA66CD}
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ DebugEditor|x64 = DebugEditor|x64
+ Release|x64 = Release|x64
+ ReleaseEditor|x64 = ReleaseEditor|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {838F1EAE-48A4-40BA-9B8F-332D37EA66CD}.Debug|x64.ActiveCfg = Debug|x64
+ {838F1EAE-48A4-40BA-9B8F-332D37EA66CD}.Debug|x64.Build.0 = Debug|x64
+ {838F1EAE-48A4-40BA-9B8F-332D37EA66CD}.DebugEditor|x64.ActiveCfg = DebugEditor|x64
+ {838F1EAE-48A4-40BA-9B8F-332D37EA66CD}.DebugEditor|x64.Build.0 = DebugEditor|x64
+ {838F1EAE-48A4-40BA-9B8F-332D37EA66CD}.Release|x64.ActiveCfg = Release|x64
+ {838F1EAE-48A4-40BA-9B8F-332D37EA66CD}.Release|x64.Build.0 = Release|x64
+ {838F1EAE-48A4-40BA-9B8F-332D37EA66CD}.ReleaseEditor|x64.ActiveCfg = ReleaseEditor|x64
+ {838F1EAE-48A4-40BA-9B8F-332D37EA66CD}.ReleaseEditor|x64.Build.0 = ReleaseEditor|x64
+ {D1FB8BBD-6836-431B-A43D-F6EA248B8965}.Debug|x64.ActiveCfg = Debug|x64
+ {D1FB8BBD-6836-431B-A43D-F6EA248B8965}.Debug|x64.Build.0 = Debug|x64
+ {D1FB8BBD-6836-431B-A43D-F6EA248B8965}.DebugEditor|x64.ActiveCfg = DebugEditor|x64
+ {D1FB8BBD-6836-431B-A43D-F6EA248B8965}.Release|x64.ActiveCfg = Release|x64
+ {D1FB8BBD-6836-431B-A43D-F6EA248B8965}.Release|x64.Build.0 = Release|x64
+ {D1FB8BBD-6836-431B-A43D-F6EA248B8965}.ReleaseEditor|x64.ActiveCfg = ReleaseEditor|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a46d44d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,110 @@
+# FeatureExtractDemo(DX12 基础框架)
+
+FeatureExtractDemo 是一个面向 Windows 平台的 C++17 引擎学习工程,采用 **Engine 静态库 + EngineTest 可执行程序** 的结构,用于搭建和演进 DirectX 12 图形程序的基础工程能力。
+
+---
+
+## 1. 项目目标
+
+- 提供清晰的引擎分层结构,便于学习与迭代
+- 用最小可运行框架承载窗口、组件、内容与测试系统
+- 为后续 DX12 功能扩展提供稳定入口
+
+---
+
+## 2. 目录与工程结构
+
+- `FeatureExtractDemo.sln`:Visual Studio 解决方案
+- `Engine/`:核心引擎静态库工程
+- `EngineTest/`:测试程序与主循环
+- `ContentTools/`:内容工具工程(模型/几何处理相关)
+- `packages/dxCompiler/`:DXC 依赖文件
+- `docs/`:架构分析文档
+
+---
+
+## 3. 模块说明
+
+### Engine
+
+- `Common/`:公共类型、基础定义与通用头
+- `Components/`:Entity / Transform / Script 组件系统
+- `Content/`:内容加载与运行时转换
+- `Platform/`:Win32 窗口与平台层封装
+- `Graphics/`:图形接口入口
+- `Utilities/`:数学、容器、IO 等工具模块
+- `Core/`:引擎核心流程代码
+
+### EngineTest
+
+- `Main.cpp`:应用入口与 Win32 消息循环
+- `Test.h`:测试基类与基础计时工具
+- `Lights.cpp`、`RenderItem.cpp`、`ShaderComponents.*`:测试样例逻辑
+- `Test*.h`、`Test*.hlsl`:测试配置与着色器样例
+
+### ContentTools
+
+- 几何生成、模型导入相关工具模块
+
+---
+
+## 4. 构建关系
+
+- `Engine` 为静态库项目(`ConfigurationType = StaticLibrary`)
+- `EngineTest` 依赖 `Engine` 并链接 `engine.lib`
+- 主要配置:`Debug|x64`、`Release|x64`、`DebugEditor|x64`、`ReleaseEditor|x64`
+
+---
+
+## 5. 开发环境
+
+- Windows 10/11 x64
+- Visual Studio 2022(MSVC v143)
+- C++17
+- Windows SDK 10.x
+
+---
+
+## 6. 快速开始
+
+1. 使用 Visual Studio 2022 打开 `FeatureExtractDemo.sln`
+2. 选择 `x64 + Debug` 配置
+3. 构建解决方案
+4. 将 `EngineTest` 设为启动项目并运行
+
+运行时说明:
+
+- `EngineTest` 的预构建事件会将 `packages/dxCompiler/dxcompiler.dll` 复制到输出目录
+- 程序启动时会自动切换工作目录到可执行文件路径
+
+---
+
+## 7. 测试开关
+
+在 `EngineTest/Test.h` 中可通过宏切换测试入口:
+
+- `TEST_ENTITY_COMPONENTS`
+- `TEST_WINDOW_COMPONENTS`
+- `TEST_RENDERER`
+
+---
+
+## 8. 代码约定
+
+- 平台相关实现集中在 `Platform/`
+- 业务逻辑尽量通过组件与 API 层解耦
+- 图形接口通过 `Graphics` 入口统一抽象
+- 建议按模块拆分提交,便于回溯与调试
+
+---
+
+## 9. 后续扩展建议
+
+1. 完善 `Graphics` 接口与资源生命周期管理
+2. 逐步补齐渲染通道、资源系统与 shader 管线
+3. 增加渲染诊断与调试辅助能力
+4. 细化测试场景并整理测试入口
+
+---
+
+这个仓库定位为 DX12 学习和工程化演进的基础骨架,适合按功能模块逐步扩展。
diff --git a/docs/架构分析/Entity组件分析.md b/docs/架构分析/Entity组件分析.md
new file mode 100644
index 0000000..2768405
--- /dev/null
+++ b/docs/架构分析/Entity组件分析.md
@@ -0,0 +1,58 @@
+# Entity 组件分析
+
+## 组件职责
+
+`Entity.*` 负责:
+
+- 分配与回收 `entity_id`;
+- 维护实体到 `transform/script` 句柄的绑定;
+- 提供 `is_alive` 存活判定。
+
+## 核心存储
+
+位于 `Entity.cpp` 的静态容器:
+
+- `transforms`:实体索引 -> 变换组件句柄
+- `scripts`:实体索引 -> 脚本组件句柄
+- `generations`:实体索引 -> 当前代数
+- `free_ids`:可复用实体 ID 队列
+
+## 创建流程
+
+入口:`game_entity::create(entity_info info)`。
+
+主要步骤:
+
+1. 校验 `info.transform`,Transform 为必需组件;
+2. 分配 `entity_id`:
+ - 回收路径:从 `free_ids` 取旧 id,`new_generation`;
+ - 新建路径:追加新索引并扩展并行数组;
+3. 创建 Transform 组件(强制);
+4. 若传入脚本工厂,则创建 Script 组件(可选);
+5. 返回 `entity` 轻句柄。
+
+## 删除流程
+
+入口:`game_entity::remove(entity_id id)`。
+
+步骤:
+
+1. 校验实体存活;
+2. 若存在脚本组件,先删脚本并清空句柄槽;
+3. 删除 Transform 并清空句柄槽;
+4. 把 id 放入 `free_ids`,等待复用。
+
+## 存活判定规则
+
+`is_alive(id)` 依赖两项:
+
+- `generations[index] == id::generation(id)`
+- `transforms[index].is_valid()`
+
+这让“旧代数句柄”天然失效,避免悬挂句柄误访问。
+
+## 设计特点
+
+- Entity 层是组件索引空间的“主时钟”;
+- Transform 与实体强 1:1 绑定;
+- Script 按需挂载,存在即可访问,不存在则为空句柄。
diff --git a/docs/架构分析/Id与Components运行机制设计分析.md b/docs/架构分析/Id与Components运行机制设计分析.md
new file mode 100644
index 0000000..fafc2c7
--- /dev/null
+++ b/docs/架构分析/Id与Components运行机制设计分析.md
@@ -0,0 +1,18 @@
+# Id.h 与 Components 运行机制设计索引
+
+本文档改为总览索引。按你的要求,每个组件拆分到独立页面。
+
+## 总览
+
+- ID 机制分析:`Engine/Common/Id.h`
+- Entity 组件分析:`Engine/Components/Entity.cpp/.h`
+- Transform 组件分析:`Engine/Components/Transform.cpp/.h`
+- Script 组件分析:`Engine/Components/Script.cpp/.h`
+
+## 分页文档
+
+- [Id机制分析.md](./Id机制分析.md)
+- [Entity组件分析.md](./Entity组件分析.md)
+- [Transform组件分析.md](./Transform组件分析.md)
+- [Script组件分析.md](./Script组件分析.md)
+- [项目约定规范.md](./项目约定规范.md)
diff --git a/docs/架构分析/Id机制分析.md b/docs/架构分析/Id机制分析.md
new file mode 100644
index 0000000..c26f701
--- /dev/null
+++ b/docs/架构分析/Id机制分析.md
@@ -0,0 +1,46 @@
+# Id 机制分析
+
+## 作用定位
+
+`Engine/Common/Id.h` 是整个运行时句柄系统的基础。
+核心目标是用一个 `u32` 同时表达:
+
+- 资源在数组中的索引;
+- 该索引的生命周期代数(generation)。
+
+## 位布局
+
+- `id_type = u32`
+- 高位:generation(`generation_bits = 8`)
+- 低位:index(`index_bit = 24`)
+
+对应关键常量与工具:
+
+- `index_mask`:提取低位索引
+- `generation_mask`:提取高位代数
+- `invalid_id`:无效句柄哨兵值
+
+## 核心函数语义
+
+- `is_valid(id)`:判断是否不是无效值
+- `index(id)`:提取索引位
+- `generation(id)`:提取代数位
+- `new_generation(id)`:索引不变,代数递增
+
+## 运行时价值
+
+它主要解决“删除后旧句柄仍被外部持有”的问题:
+
+1. 对象删除后,槽位可复用;
+2. 复用时代数 +1;
+3. 旧句柄的 generation 与当前 generation 不一致;
+4. 存活校验失败,避免误访问新对象。
+
+## 强类型策略
+
+`DEFINE_TYPED_ID(name)`:
+
+- Debug:生成包装类型,增强类型隔离;
+- Release:退化为 `id::id_type`,零额外开销。
+
+这是“开发期安全 + 发布期性能”的组合设计。
diff --git a/docs/架构分析/Script组件分析.md b/docs/架构分析/Script组件分析.md
new file mode 100644
index 0000000..65ccc4d
--- /dev/null
+++ b/docs/架构分析/Script组件分析.md
@@ -0,0 +1,119 @@
+# Script 组件分析
+
+## 组件职责
+
+`Script.*` 负责:
+
+- 脚本类型注册与脚本实例创建;
+- `script_id` 生命周期管理;
+- 脚本逐帧更新;
+- 脚本对 Transform 的延迟写回。
+
+## 核心存储
+
+`Script.cpp` 的关键结构:
+
+- `entity_scripts`:活跃脚本实例池
+- `id_mapping`:`script_id` 索引到实例池下标
+- `generations + free_ids`:脚本 ID 代数与复用队列
+- `transform_cache`:脚本写变换的缓存数组
+- `cache_map`:Transform ID 到缓存下标映射
+
+## 注册机制
+
+脚本工厂注册表:
+
+- 类型:`unordered_map`
+- `register_script(tag, func)` 注册工厂
+- `get_script_creator(tag)` 查询工厂
+
+通常由 `REGISTER_SCRIPT` 宏在静态初始化阶段完成注册。
+
+## 创建流程
+
+`script::create(init_info, entity)`:
+
+1. 校验实体有效与工厂函数有效;
+2. 分配 `script_id`(复用或新建);
+3. 调用工厂创建脚本实例并压入 `entity_scripts`;
+4. 写入 `id_mapping`;
+5. 返回 `script::component` 句柄。
+
+## 删除流程
+
+`script::remove(component c)`:
+
+- 校验组件存在;
+- 用无序删除移除脚本实例;
+- 修正被交换元素的 `id_mapping`;
+- 将被删 id 的映射标记为无效。
+
+## 更新与缓存提交流程
+
+`script::update(dt)` 包含两段:
+
+1. 遍历 `entity_scripts` 执行每个脚本的 `update(dt)`;
+2. 若 `transform_cache` 非空,统一调用 `transform::update(...)` 提交后清空。
+
+这使脚本阶段不直接写 Transform 主数组,减少帧内重复写。
+
+## 脚本写 Transform 的方式
+
+`entity_script::set_rotation/position/...` 的行为是:
+
+- 先通过 `get_cache_ptr(entity)` 找到该实体缓存槽;
+- 设置对应 `flags`;
+- 写入新值到 cache;
+- 等待帧末统一提交。
+
+## 设计特点
+
+- 句柄安全:`script_id` 采用 generation 机制;
+- 批处理友好:Transform 写入集中提交;
+- 解耦:脚本逻辑与 Transform 存储细节隔离。
+
+## 三条主线时序图(注册、生命周期、缓存提交)
+
+```mermaid
+sequenceDiagram
+ autonumber
+ participant Macro as REGISTER_SCRIPT
+ participant Detail as script::detail
+ participant Registry as script_registry
+ participant Entity as game_entity::entity
+ participant ScriptSys as script::system
+ participant Obj as entity_script
+ participant Cache as transform_cache
+ participant Trans as transform::system
+
+ rect rgb(236, 248, 255)
+ Note over Macro,Registry: 主线一:脚本类型注册
+ Macro->>Detail: register_script(tag, creator)
+ Detail->>Registry: insert(tag, creator)
+ Registry-->>Detail: creator 已登记
+ end
+
+ rect rgb(239, 252, 240)
+ Note over Entity,ScriptSys: 主线二:脚本实例生命周期
+ Entity->>ScriptSys: create(init_info, entity)
+ ScriptSys->>ScriptSys: 分配或复用 script_id
+ ScriptSys->>Registry: get_script_creator(tag)
+ Registry-->>ScriptSys: 返回 creator
+ ScriptSys->>Obj: creator(entity)
+ Obj-->>ScriptSys: 生成脚本实例
+ ScriptSys->>ScriptSys: 写入 entity_scripts 与 id_mapping
+ ScriptSys-->>Entity: 返回 script::component
+ Entity->>ScriptSys: remove(component)
+ ScriptSys->>ScriptSys: erase_unordered + 修正 id_mapping
+ end
+
+ rect rgb(255, 246, 236)
+ Note over Obj,Trans: 主线三:脚本写变换缓存并帧末提交
+ ScriptSys->>Obj: update(dt)
+ Obj->>Cache: set_position/rotation/scale...
+ Cache-->>Obj: flags + value 已缓存
+ ScriptSys->>Trans: transform::update(cache, count)
+ Trans-->>ScriptSys: 写入完成
+ ScriptSys->>Cache: clear()
+ end
+```
diff --git a/docs/架构分析/Transform组件分析.md b/docs/架构分析/Transform组件分析.md
new file mode 100644
index 0000000..609a4b3
--- /dev/null
+++ b/docs/架构分析/Transform组件分析.md
@@ -0,0 +1,48 @@
+# Transform 组件分析
+
+## 组件职责
+
+`Transform.*` 负责实体空间变换数据:
+
+- 位置、旋转、朝向、缩放存储;
+- 世界矩阵与逆矩阵计算;
+- 脏标记记录与批量更新入口。
+
+## 数据布局
+
+`Transform.cpp` 采用并行数组(SoA):
+
+- `positions / rotations / orientations / scales`
+- `to_world / inv_world`
+- `has_transform`:矩阵缓存有效位
+- `changes_from_previous_frame`:逐帧脏标记
+
+## 创建与绑定
+
+`transform::create(init_info, entity)` 使用 `entity_id` 的索引位定位槽位:
+
+- 若槽位已存在,覆写其数据并标记脏;
+- 若槽位不存在,扩容所有并行数组;
+- 返回 `transform::component{ transform_id{ entity.get_id() } }`。
+
+这说明 Transform 不维护独立 free list,而是复用实体索引空间。
+
+## 读写策略
+
+- 写接口(`set_rotation/position/scale`)只改数据与脏标记;
+- `get_transform_matrices` 在读取时按需计算矩阵;
+- 通过 `has_transform` 避免重复矩阵重算。
+
+## 批量更新机制
+
+`transform::update(cache, count)` 接收 `component_cache` 数组:
+
+- 每条 cache 带 `flags` 指示写哪些字段;
+- 逐条落地到内部并行数组;
+- 适配脚本系统“先缓存,后统一提交”的帧内流程。
+
+## 设计特点
+
+- 读取快:索引直达;
+- 可批处理:cache + flags;
+- 低耦合:外部通过句柄和 cache 交互,不直接暴露内部数组。
diff --git a/docs/架构分析/项目约定规范.md b/docs/架构分析/项目约定规范.md
new file mode 100644
index 0000000..e42c6de
--- /dev/null
+++ b/docs/架构分析/项目约定规范.md
@@ -0,0 +1,132 @@
+# 项目约定规范(接口层 / 实现层)
+
+what every programmer should know about memory
+
+本文档用于统一本项目在 `EngineAPI`、`Components`、`Platform`、`Utilities` 等目录下的设计与编码约定,重点规范“接口层与实现层分离”。
+
+## 1. 分层职责约定
+
+### 1.1 EngineAPI 层(对外契约层)
+
+- 只暴露稳定接口:强类型 ID、句柄类、对外可调用的 API。
+- 不暴露内部容器与存储细节,不暴露运行时池结构。
+- 允许声明行为,不实现复杂运行逻辑。
+- 头文件应可被上层模块直接 include,尽量减少内部依赖。
+
+示例:
+- `Engine/EngineAPI/ScriptComponent.h`:仅定义 `script_id` 与 `script::component` 句柄。
+
+### 1.2 Components 层(运行时实现层)
+
+- 实现组件生命周期:`create/remove/update`。
+- 管理内部存储:数组、映射、空闲队列、代数校验。
+- 对外只通过 API 层句柄交互,不向外泄露内部布局。
+- 允许高性能实现细节(如 `erase_unordered`、缓存表、延迟提交)。
+
+示例:
+- `Engine/Components/Script.h/.cpp`:实现脚本创建、删除、逐帧更新和注册表逻辑。
+
+### 1.3 Platform 层(平台适配层)
+
+- 只处理操作系统相关对象与流程(如 Win32 `HWND`、消息循环、窗口样式)。
+- 上层通过抽象类型访问,不直接依赖平台原生类型。
+- 平台特有分支必须受平台宏保护(如 `_WIN64`)。
+
+### 1.4 Utilities 层(通用基础层)
+
+- 提供与业务无关的数学、容器、IO、工具函数。
+- 不耦合具体业务组件,不反向依赖高层模块。
+
+## 2. 文件组织约定
+
+### 2.1 命名与位置
+
+- 对外契约放 `EngineAPI`,运行时实现放 `Components`。
+- 头文件与实现文件成对组织:`Xxx.h` / `Xxx.cpp`。
+- 公共依赖入口统一放在模块公共头(如 `ComponentsCommon.h`)。
+
+### 2.2 include 方向
+
+- 允许 `Components -> EngineAPI` 依赖。
+- 禁止 `EngineAPI -> Components` 反向依赖实现细节。
+- `Utilities` 不应依赖 `Components` 或 `Platform`。
+
+### 2.3 前向声明优先
+
+- 能前向声明时不直接 include 重头文件。
+- 对外头文件避免引入不必要实现依赖,控制编译扇出。
+
+## 3. 类型与句柄约定
+
+### 3.1 强类型 ID
+
+- 所有核心对象 ID 使用强类型封装(`DEFINE_TYPED_ID`)。
+- 禁止在模块边界裸传 `u32` 作为对象标识。
+- 必须通过 `is_valid` / `generation` 做有效性校验。
+
+### 3.2 句柄对象
+
+- 对外使用轻量句柄(如 `script::component`、`game_entity::entity`)。
+- 句柄仅保存 ID,不保存大对象数据。
+- 句柄方法可转发到实现层查询真实数据。
+
+## 4. 生命周期与存储约定
+
+### 4.1 创建与删除
+
+- `create` 负责分配 ID、构造实例、建立映射。
+- `remove` 必须清理映射、回收槽位、更新空闲队列。
+- 必须保证删除后旧句柄不可通过代数校验。
+
+### 4.2 映射一致性
+
+- 使用 `id_mapping` 时必须保证:
+ - `id -> 索引` 映射存在且越界受保护;
+ - 交换删除后映射及时回填;
+ - 无效 ID 显式写回 `invalid_id`。
+
+### 4.3 批处理更新
+
+- 高频写入优先使用缓存并批量提交(如脚本修改 Transform 后统一提交)。
+- 帧内缓存必须在提交后清空,避免跨帧污染。
+
+## 5. 脚本系统专项约定
+
+### 5.1 注册机制
+
+- 脚本类型通过统一宏注册(`REGISTER_SCRIPT(TYPE)`)。
+- 注册信息至少包含:类型名哈希、工厂函数指针。
+- 编辑器模式下可额外导出脚本名列表。
+
+### 5.2 工厂机制
+
+- 统一使用 `script_creator` 创建脚本实例。
+- 禁止外部直接 new 并绕过组件创建流程。
+- 工厂函数签名保持稳定,避免跨模块 ABI 漂移。
+
+## 6. 注释与文档约定
+
+### 6.1 文件头注释
+
+- 使用 `@file` + `@brief` + `@details`。
+- `@details` 需说明职责边界、数据流、关键约束。
+
+### 6.2 函数注释
+
+- 对核心函数标注参数、返回值、副作用。
+- 对平台参数表、结构体字段允许保留逐行行尾注释。
+- 禁止乱码注释;统一 UTF-8 编码保存。
+
+## 7. 可维护性与扩展性约定
+
+- 先保证接口稳定,再演进实现细节。
+- 扩展新组件时,优先复用现有 ID/句柄/生命周期模式。
+- 变更跨层关系时必须先更新本规范文档与对应模块分析文档。
+
+## 8. 变更检查清单(提交前)
+
+- 是否保持 `EngineAPI` 与 `Components` 的边界不反转。
+- 是否新增了不必要 include 或扩大了依赖扇出。
+- 是否破坏了 ID 代数校验与映射一致性。
+- 是否补齐了注释并确保无乱码。
+- 是否与现有目录结构与命名风格一致。
diff --git a/packages/dxCompiler/dxcompiler.dll b/packages/dxCompiler/dxcompiler.dll
new file mode 100644
index 0000000..fd95641
Binary files /dev/null and b/packages/dxCompiler/dxcompiler.dll differ
diff --git a/packages/dxCompiler/dxil.dll b/packages/dxCompiler/dxil.dll
new file mode 100644
index 0000000..e99de3e
Binary files /dev/null and b/packages/dxCompiler/dxil.dll differ