Implement initial Unity-style asset library cache

This commit is contained in:
2026-04-02 03:03:36 +08:00
parent 619856ab22
commit 4c167bec0e
29 changed files with 4818 additions and 73 deletions

View File

@@ -0,0 +1,677 @@
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Core/Math/Bounds.h>
#include <XCEngine/Core/Math/Vector2.h>
#include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <algorithm>
#include <cmath>
#include <vector>
namespace XCEngine {
namespace Resources {
namespace {
constexpr const char* kBuiltinPrefix = "builtin://";
constexpr const char* kBuiltinMeshPrefix = "builtin://meshes/";
constexpr const char* kBuiltinMaterialPrefix = "builtin://materials/";
constexpr const char* kBuiltinTexturePrefix = "builtin://textures/";
constexpr const char* kBuiltinDefaultPrimitiveMaterialPath = "builtin://materials/default-primitive";
constexpr const char* kBuiltinDefaultPrimitiveTexturePath = "builtin://textures/default-primitive-albedo";
constexpr float kPi = 3.14159265358979323846f;
struct MeshBuffers {
std::vector<StaticMeshVertex> vertices;
std::vector<Core::uint32> indices;
};
Math::Bounds ComputeBounds(const std::vector<StaticMeshVertex>& vertices) {
if (vertices.empty()) {
return Math::Bounds();
}
Math::Vector3 min = vertices.front().position;
Math::Vector3 max = vertices.front().position;
for (const StaticMeshVertex& vertex : vertices) {
min.x = std::min(min.x, vertex.position.x);
min.y = std::min(min.y, vertex.position.y);
min.z = std::min(min.z, vertex.position.z);
max.x = std::max(max.x, vertex.position.x);
max.y = std::max(max.y, vertex.position.y);
max.z = std::max(max.z, vertex.position.z);
}
Math::Bounds bounds;
bounds.SetMinMax(min, max);
return bounds;
}
StaticMeshVertex MakeVertex(
const Math::Vector3& position,
const Math::Vector3& normal,
const Math::Vector3& tangent,
const Math::Vector2& uv) {
StaticMeshVertex vertex;
vertex.position = position;
vertex.normal = normal.Normalized();
vertex.tangent = tangent.Normalized();
vertex.bitangent = Math::Vector3::Cross(vertex.normal, vertex.tangent).Normalized();
vertex.uv0 = uv;
return vertex;
}
void AppendQuad(
MeshBuffers& buffers,
const Math::Vector3& bottomLeft,
const Math::Vector3& bottomRight,
const Math::Vector3& topRight,
const Math::Vector3& topLeft,
const Math::Vector3& normal,
const Math::Vector3& tangent) {
const Core::uint32 baseIndex = static_cast<Core::uint32>(buffers.vertices.size());
buffers.vertices.push_back(MakeVertex(bottomLeft, normal, tangent, Math::Vector2(0.0f, 0.0f)));
buffers.vertices.push_back(MakeVertex(bottomRight, normal, tangent, Math::Vector2(1.0f, 0.0f)));
buffers.vertices.push_back(MakeVertex(topRight, normal, tangent, Math::Vector2(1.0f, 1.0f)));
buffers.vertices.push_back(MakeVertex(topLeft, normal, tangent, Math::Vector2(0.0f, 1.0f)));
buffers.indices.push_back(baseIndex + 0);
buffers.indices.push_back(baseIndex + 1);
buffers.indices.push_back(baseIndex + 2);
buffers.indices.push_back(baseIndex + 0);
buffers.indices.push_back(baseIndex + 2);
buffers.indices.push_back(baseIndex + 3);
}
void FlipTriangleWinding(MeshBuffers& buffers) {
for (size_t index = 0; index + 2 < buffers.indices.size(); index += 3) {
std::swap(buffers.indices[index + 1], buffers.indices[index + 2]);
}
}
MeshBuffers CreateCubeMeshBuffers() {
MeshBuffers buffers;
const float half = 0.5f;
AppendQuad(
buffers,
Math::Vector3(-half, -half, half),
Math::Vector3(half, -half, half),
Math::Vector3(half, half, half),
Math::Vector3(-half, half, half),
Math::Vector3::Forward(),
Math::Vector3::Right());
AppendQuad(
buffers,
Math::Vector3(half, -half, -half),
Math::Vector3(-half, -half, -half),
Math::Vector3(-half, half, -half),
Math::Vector3(half, half, -half),
Math::Vector3::Back(),
Math::Vector3::Left());
AppendQuad(
buffers,
Math::Vector3(-half, -half, -half),
Math::Vector3(-half, -half, half),
Math::Vector3(-half, half, half),
Math::Vector3(-half, half, -half),
Math::Vector3::Left(),
Math::Vector3::Forward());
AppendQuad(
buffers,
Math::Vector3(half, -half, half),
Math::Vector3(half, -half, -half),
Math::Vector3(half, half, -half),
Math::Vector3(half, half, half),
Math::Vector3::Right(),
Math::Vector3::Back());
AppendQuad(
buffers,
Math::Vector3(-half, half, half),
Math::Vector3(half, half, half),
Math::Vector3(half, half, -half),
Math::Vector3(-half, half, -half),
Math::Vector3::Up(),
Math::Vector3::Right());
AppendQuad(
buffers,
Math::Vector3(-half, -half, -half),
Math::Vector3(half, -half, -half),
Math::Vector3(half, -half, half),
Math::Vector3(-half, -half, half),
Math::Vector3::Down(),
Math::Vector3::Right());
return buffers;
}
MeshBuffers CreateQuadMeshBuffers() {
MeshBuffers buffers;
AppendQuad(
buffers,
Math::Vector3(-0.5f, -0.5f, 0.0f),
Math::Vector3(0.5f, -0.5f, 0.0f),
Math::Vector3(0.5f, 0.5f, 0.0f),
Math::Vector3(-0.5f, 0.5f, 0.0f),
Math::Vector3::Forward(),
Math::Vector3::Right());
return buffers;
}
MeshBuffers CreatePlaneMeshBuffers() {
MeshBuffers buffers;
constexpr int kSegments = 10;
constexpr float kSize = 10.0f;
const float halfSize = kSize * 0.5f;
const float step = kSize / static_cast<float>(kSegments);
for (int z = 0; z <= kSegments; ++z) {
for (int x = 0; x <= kSegments; ++x) {
const float px = -halfSize + static_cast<float>(x) * step;
const float pz = -halfSize + static_cast<float>(z) * step;
const float u = static_cast<float>(x) / static_cast<float>(kSegments);
const float v = static_cast<float>(z) / static_cast<float>(kSegments);
buffers.vertices.push_back(MakeVertex(
Math::Vector3(px, 0.0f, pz),
Math::Vector3::Up(),
Math::Vector3::Right(),
Math::Vector2(u, v)));
}
}
const int rowStride = kSegments + 1;
for (int z = 0; z < kSegments; ++z) {
for (int x = 0; x < kSegments; ++x) {
const Core::uint32 i0 = static_cast<Core::uint32>(z * rowStride + x);
const Core::uint32 i1 = i0 + 1;
const Core::uint32 i2 = i0 + static_cast<Core::uint32>(rowStride);
const Core::uint32 i3 = i2 + 1;
buffers.indices.push_back(i0);
buffers.indices.push_back(i3);
buffers.indices.push_back(i1);
buffers.indices.push_back(i0);
buffers.indices.push_back(i2);
buffers.indices.push_back(i3);
}
}
return buffers;
}
MeshBuffers CreateUvSphereMeshBuffers() {
MeshBuffers buffers;
constexpr int kLongitudeSegments = 24;
constexpr int kLatitudeSegments = 16;
constexpr float kRadius = 0.5f;
for (int latitude = 0; latitude <= kLatitudeSegments; ++latitude) {
const float v = static_cast<float>(latitude) / static_cast<float>(kLatitudeSegments);
const float theta = v * kPi;
const float sinTheta = std::sin(theta);
const float cosTheta = std::cos(theta);
for (int longitude = 0; longitude <= kLongitudeSegments; ++longitude) {
const float u = static_cast<float>(longitude) / static_cast<float>(kLongitudeSegments);
const float phi = u * (2.0f * kPi);
const float sinPhi = std::sin(phi);
const float cosPhi = std::cos(phi);
const Math::Vector3 normal(cosPhi * sinTheta, cosTheta, sinPhi * sinTheta);
Math::Vector3 tangent(-sinPhi, 0.0f, cosPhi);
if (tangent.SqrMagnitude() <= 0.000001f) {
tangent = Math::Vector3::Right();
}
buffers.vertices.push_back(MakeVertex(
normal * kRadius,
normal,
tangent,
Math::Vector2(u, 1.0f - v)));
}
}
const int stride = kLongitudeSegments + 1;
for (int latitude = 0; latitude < kLatitudeSegments; ++latitude) {
for (int longitude = 0; longitude < kLongitudeSegments; ++longitude) {
const Core::uint32 i0 = static_cast<Core::uint32>(latitude * stride + longitude);
const Core::uint32 i1 = i0 + 1;
const Core::uint32 i2 = i0 + static_cast<Core::uint32>(stride);
const Core::uint32 i3 = i2 + 1;
buffers.indices.push_back(i0);
buffers.indices.push_back(i2);
buffers.indices.push_back(i1);
buffers.indices.push_back(i1);
buffers.indices.push_back(i2);
buffers.indices.push_back(i3);
}
}
return buffers;
}
MeshBuffers CreateCylinderMeshBuffers() {
MeshBuffers buffers;
constexpr int kRadialSegments = 24;
constexpr float kRadius = 0.5f;
constexpr float kHalfHeight = 1.0f;
for (int ring = 0; ring <= 1; ++ring) {
const float y = ring == 0 ? -kHalfHeight : kHalfHeight;
const float v = static_cast<float>(ring);
for (int segment = 0; segment <= kRadialSegments; ++segment) {
const float u = static_cast<float>(segment) / static_cast<float>(kRadialSegments);
const float angle = u * (2.0f * kPi);
const float cosAngle = std::cos(angle);
const float sinAngle = std::sin(angle);
const Math::Vector3 normal(cosAngle, 0.0f, sinAngle);
const Math::Vector3 tangent(-sinAngle, 0.0f, cosAngle);
buffers.vertices.push_back(MakeVertex(
Math::Vector3(cosAngle * kRadius, y, sinAngle * kRadius),
normal,
tangent,
Math::Vector2(u, v)));
}
}
const int sideStride = kRadialSegments + 1;
for (int segment = 0; segment < kRadialSegments; ++segment) {
const Core::uint32 i0 = static_cast<Core::uint32>(segment);
const Core::uint32 i1 = i0 + 1;
const Core::uint32 i2 = i0 + static_cast<Core::uint32>(sideStride);
const Core::uint32 i3 = i2 + 1;
buffers.indices.push_back(i0);
buffers.indices.push_back(i2);
buffers.indices.push_back(i1);
buffers.indices.push_back(i1);
buffers.indices.push_back(i2);
buffers.indices.push_back(i3);
}
const auto appendCap = [&](bool topCap) {
const float y = topCap ? kHalfHeight : -kHalfHeight;
const Math::Vector3 normal = topCap ? Math::Vector3::Up() : Math::Vector3::Down();
const Core::uint32 centerIndex = static_cast<Core::uint32>(buffers.vertices.size());
buffers.vertices.push_back(MakeVertex(
Math::Vector3(0.0f, y, 0.0f),
normal,
Math::Vector3::Right(),
Math::Vector2(0.5f, 0.5f)));
for (int segment = 0; segment <= kRadialSegments; ++segment) {
const float u = static_cast<float>(segment) / static_cast<float>(kRadialSegments);
const float angle = u * (2.0f * kPi);
const float cosAngle = std::cos(angle);
const float sinAngle = std::sin(angle);
buffers.vertices.push_back(MakeVertex(
Math::Vector3(cosAngle * kRadius, y, sinAngle * kRadius),
normal,
Math::Vector3::Right(),
Math::Vector2(cosAngle * 0.5f + 0.5f, sinAngle * 0.5f + 0.5f)));
}
for (int segment = 0; segment < kRadialSegments; ++segment) {
const Core::uint32 rim0 = centerIndex + 1 + static_cast<Core::uint32>(segment);
const Core::uint32 rim1 = rim0 + 1;
if (topCap) {
buffers.indices.push_back(centerIndex);
buffers.indices.push_back(rim0);
buffers.indices.push_back(rim1);
} else {
buffers.indices.push_back(centerIndex);
buffers.indices.push_back(rim1);
buffers.indices.push_back(rim0);
}
}
};
appendCap(true);
appendCap(false);
return buffers;
}
MeshBuffers CreateCapsuleMeshBuffers() {
MeshBuffers buffers;
constexpr int kRadialSegments = 24;
constexpr int kHemisphereSegments = 8;
constexpr float kRadius = 0.5f;
constexpr float kHalfCylinderHeight = 0.5f;
struct RingDefinition {
float y = 0.0f;
float radius = 0.0f;
Math::Vector3 normalBase = Math::Vector3::Zero();
};
std::vector<RingDefinition> rings;
rings.reserve(static_cast<size_t>(kHemisphereSegments * 2 + 2));
for (int step = 0; step <= kHemisphereSegments; ++step) {
const float t = static_cast<float>(step) / static_cast<float>(kHemisphereSegments);
const float angle = -0.5f * kPi + t * (0.5f * kPi);
const float ringRadius = std::cos(angle) * kRadius;
const float y = std::sin(angle) * kRadius - kHalfCylinderHeight;
const Math::Vector3 normalBase(0.0f, std::sin(angle), 0.0f);
rings.push_back({ y, ringRadius, normalBase });
}
for (int step = 0; step <= kHemisphereSegments; ++step) {
const float t = static_cast<float>(step) / static_cast<float>(kHemisphereSegments);
const float angle = t * (0.5f * kPi);
const float ringRadius = std::cos(angle) * kRadius;
const float y = std::sin(angle) * kRadius + kHalfCylinderHeight;
const Math::Vector3 normalBase(0.0f, std::sin(angle), 0.0f);
rings.push_back({ y, ringRadius, normalBase });
}
for (size_t ringIndex = 0; ringIndex < rings.size(); ++ringIndex) {
const float v = rings.size() > 1
? static_cast<float>(ringIndex) / static_cast<float>(rings.size() - 1)
: 0.0f;
for (int segment = 0; segment <= kRadialSegments; ++segment) {
const float u = static_cast<float>(segment) / static_cast<float>(kRadialSegments);
const float angle = u * (2.0f * kPi);
const float cosAngle = std::cos(angle);
const float sinAngle = std::sin(angle);
const Math::Vector3 radial(cosAngle, 0.0f, sinAngle);
Math::Vector3 normal(
radial.x * rings[ringIndex].radius,
rings[ringIndex].normalBase.y * kRadius,
radial.z * rings[ringIndex].radius);
normal = normal.Normalized();
if (normal.SqrMagnitude() <= 0.000001f) {
normal = rings[ringIndex].y >= 0.0f ? Math::Vector3::Up() : Math::Vector3::Down();
}
const Math::Vector3 tangent(-sinAngle, 0.0f, cosAngle);
buffers.vertices.push_back(MakeVertex(
Math::Vector3(radial.x * rings[ringIndex].radius, rings[ringIndex].y, radial.z * rings[ringIndex].radius),
normal,
tangent,
Math::Vector2(u, 1.0f - v)));
}
}
const int stride = kRadialSegments + 1;
for (size_t ringIndex = 0; ringIndex + 1 < rings.size(); ++ringIndex) {
for (int segment = 0; segment < kRadialSegments; ++segment) {
const Core::uint32 i0 = static_cast<Core::uint32>(ringIndex * stride + static_cast<size_t>(segment));
const Core::uint32 i1 = i0 + 1;
const Core::uint32 i2 = i0 + static_cast<Core::uint32>(stride);
const Core::uint32 i3 = i2 + 1;
buffers.indices.push_back(i0);
buffers.indices.push_back(i2);
buffers.indices.push_back(i1);
buffers.indices.push_back(i1);
buffers.indices.push_back(i2);
buffers.indices.push_back(i3);
}
}
return buffers;
}
Mesh* BuildMeshResource(
const Containers::String& path,
const char* displayName,
MeshBuffers&& buffers) {
if (buffers.vertices.empty() || buffers.indices.empty()) {
return nullptr;
}
auto* mesh = new Mesh();
IResource::ConstructParams params;
params.name = Containers::String(displayName);
params.path = path;
params.guid = ResourceGUID::Generate(path);
params.memorySize = 0;
mesh->Initialize(params);
mesh->SetVertexData(
buffers.vertices.data(),
buffers.vertices.size() * sizeof(StaticMeshVertex),
static_cast<Core::uint32>(buffers.vertices.size()),
sizeof(StaticMeshVertex),
VertexAttribute::Position |
VertexAttribute::Normal |
VertexAttribute::Tangent |
VertexAttribute::Bitangent |
VertexAttribute::UV0);
if (buffers.vertices.size() > 65535u) {
mesh->SetIndexData(
buffers.indices.data(),
buffers.indices.size() * sizeof(Core::uint32),
static_cast<Core::uint32>(buffers.indices.size()),
true);
} else {
std::vector<Core::uint16> compactIndices;
compactIndices.reserve(buffers.indices.size());
for (Core::uint32 index : buffers.indices) {
compactIndices.push_back(static_cast<Core::uint16>(index));
}
mesh->SetIndexData(
compactIndices.data(),
compactIndices.size() * sizeof(Core::uint16),
static_cast<Core::uint32>(compactIndices.size()),
false);
}
const Math::Bounds bounds = ComputeBounds(buffers.vertices);
mesh->SetBounds(bounds);
MeshSection section = {};
section.baseVertex = 0;
section.vertexCount = static_cast<Core::uint32>(buffers.vertices.size());
section.startIndex = 0;
section.indexCount = static_cast<Core::uint32>(buffers.indices.size());
section.materialID = 0;
section.bounds = bounds;
mesh->AddSection(section);
return mesh;
}
Material* BuildDefaultPrimitiveMaterial(const Containers::String& path) {
auto* material = new Material();
IResource::ConstructParams params;
params.name = Containers::String("Default Primitive Material");
params.path = path;
params.guid = ResourceGUID::Generate(path);
params.memorySize = 0;
material->Initialize(params);
MaterialRenderState renderState = {};
renderState.cullMode = MaterialCullMode::Back;
material->SetRenderState(renderState);
material->SetRenderQueue(MaterialRenderQueue::Geometry);
material->SetTexture(
Containers::String("baseColorTexture"),
ResourceManager::Get().Load<Texture>(GetBuiltinDefaultPrimitiveTexturePath()));
material->RecalculateMemorySize();
return material;
}
Texture* BuildDefaultPrimitiveTexture(const Containers::String& path) {
static const unsigned char kTexturePixels[4] = { 214, 214, 214, 255 };
auto* texture = new Texture();
IResource::ConstructParams params;
params.name = Containers::String("Default Primitive Albedo");
params.path = path;
params.guid = ResourceGUID::Generate(path);
params.memorySize = 0;
texture->Initialize(params);
if (!texture->Create(
1,
1,
1,
1,
TextureType::Texture2D,
TextureFormat::RGBA8_UNORM,
kTexturePixels,
sizeof(kTexturePixels))) {
delete texture;
return nullptr;
}
return texture;
}
} // namespace
bool IsBuiltinResourcePath(const Containers::String& path) {
return path.StartsWith(kBuiltinPrefix);
}
bool IsBuiltinMeshPath(const Containers::String& path) {
return path.StartsWith(kBuiltinMeshPrefix);
}
bool IsBuiltinMaterialPath(const Containers::String& path) {
return path.StartsWith(kBuiltinMaterialPrefix);
}
bool IsBuiltinTexturePath(const Containers::String& path) {
return path.StartsWith(kBuiltinTexturePrefix);
}
const char* GetBuiltinPrimitiveDisplayName(BuiltinPrimitiveType primitiveType) {
switch (primitiveType) {
case BuiltinPrimitiveType::Cube: return "Cube";
case BuiltinPrimitiveType::Sphere: return "Sphere";
case BuiltinPrimitiveType::Capsule: return "Capsule";
case BuiltinPrimitiveType::Cylinder: return "Cylinder";
case BuiltinPrimitiveType::Plane: return "Plane";
case BuiltinPrimitiveType::Quad: return "Quad";
default: return "Primitive";
}
}
Containers::String GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType primitiveType) {
switch (primitiveType) {
case BuiltinPrimitiveType::Cube: return Containers::String("builtin://meshes/cube");
case BuiltinPrimitiveType::Sphere: return Containers::String("builtin://meshes/sphere");
case BuiltinPrimitiveType::Capsule: return Containers::String("builtin://meshes/capsule");
case BuiltinPrimitiveType::Cylinder: return Containers::String("builtin://meshes/cylinder");
case BuiltinPrimitiveType::Plane: return Containers::String("builtin://meshes/plane");
case BuiltinPrimitiveType::Quad: return Containers::String("builtin://meshes/quad");
default: return Containers::String();
}
}
Containers::String GetBuiltinDefaultPrimitiveMaterialPath() {
return Containers::String(kBuiltinDefaultPrimitiveMaterialPath);
}
Containers::String GetBuiltinDefaultPrimitiveTexturePath() {
return Containers::String(kBuiltinDefaultPrimitiveTexturePath);
}
bool TryParseBuiltinPrimitiveType(const Containers::String& path, BuiltinPrimitiveType& outPrimitiveType) {
if (path == GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Cube)) {
outPrimitiveType = BuiltinPrimitiveType::Cube;
return true;
}
if (path == GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Sphere)) {
outPrimitiveType = BuiltinPrimitiveType::Sphere;
return true;
}
if (path == GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Capsule)) {
outPrimitiveType = BuiltinPrimitiveType::Capsule;
return true;
}
if (path == GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Cylinder)) {
outPrimitiveType = BuiltinPrimitiveType::Cylinder;
return true;
}
if (path == GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Plane)) {
outPrimitiveType = BuiltinPrimitiveType::Plane;
return true;
}
if (path == GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Quad)) {
outPrimitiveType = BuiltinPrimitiveType::Quad;
return true;
}
return false;
}
LoadResult CreateBuiltinMeshResource(const Containers::String& path) {
BuiltinPrimitiveType primitiveType = BuiltinPrimitiveType::Cube;
if (!TryParseBuiltinPrimitiveType(path, primitiveType)) {
return LoadResult(Containers::String("Unknown builtin mesh: ") + path);
}
MeshBuffers buffers;
switch (primitiveType) {
case BuiltinPrimitiveType::Cube:
buffers = CreateCubeMeshBuffers();
break;
case BuiltinPrimitiveType::Sphere:
buffers = CreateUvSphereMeshBuffers();
break;
case BuiltinPrimitiveType::Capsule:
buffers = CreateCapsuleMeshBuffers();
break;
case BuiltinPrimitiveType::Cylinder:
buffers = CreateCylinderMeshBuffers();
break;
case BuiltinPrimitiveType::Plane:
buffers = CreatePlaneMeshBuffers();
break;
case BuiltinPrimitiveType::Quad:
buffers = CreateQuadMeshBuffers();
break;
default:
return LoadResult(Containers::String("Unsupported builtin mesh: ") + path);
}
FlipTriangleWinding(buffers);
Mesh* mesh = BuildMeshResource(path, GetBuiltinPrimitiveDisplayName(primitiveType), std::move(buffers));
if (mesh == nullptr) {
return LoadResult(Containers::String("Failed to create builtin mesh: ") + path);
}
return LoadResult(mesh);
}
LoadResult CreateBuiltinMaterialResource(const Containers::String& path) {
if (path != GetBuiltinDefaultPrimitiveMaterialPath()) {
return LoadResult(Containers::String("Unknown builtin material: ") + path);
}
Material* material = BuildDefaultPrimitiveMaterial(path);
if (material == nullptr) {
return LoadResult(Containers::String("Failed to create builtin material: ") + path);
}
return LoadResult(material);
}
LoadResult CreateBuiltinTextureResource(const Containers::String& path) {
if (path != GetBuiltinDefaultPrimitiveTexturePath()) {
return LoadResult(Containers::String("Unknown builtin texture: ") + path);
}
Texture* texture = BuildDefaultPrimitiveTexture(path);
if (texture == nullptr) {
return LoadResult(Containers::String("Failed to create builtin texture: ") + path);
}
return LoadResult(texture);
}
} // namespace Resources
} // namespace XCEngine

View File

@@ -46,7 +46,7 @@ void Material::SetShaderPass(const Containers::String& shaderPass) {
}
void Material::SetTag(const Containers::String& name, const Containers::String& value) {
for (TagEntry& tag : m_tags) {
for (MaterialTagEntry& tag : m_tags) {
if (tag.name == name) {
tag.value = value;
UpdateMemorySize();
@@ -54,7 +54,7 @@ void Material::SetTag(const Containers::String& name, const Containers::String&
}
}
TagEntry tag;
MaterialTagEntry tag;
tag.name = name;
tag.value = value;
m_tags.PushBack(tag);
@@ -62,7 +62,7 @@ void Material::SetTag(const Containers::String& name, const Containers::String&
}
Containers::String Material::GetTag(const Containers::String& name) const {
for (const TagEntry& tag : m_tags) {
for (const MaterialTagEntry& tag : m_tags) {
if (tag.name == name) {
return tag.value;
}
@@ -72,7 +72,7 @@ Containers::String Material::GetTag(const Containers::String& name) const {
}
bool Material::HasTag(const Containers::String& name) const {
for (const TagEntry& tag : m_tags) {
for (const MaterialTagEntry& tag : m_tags) {
if (tag.name == name) {
return true;
}
@@ -99,6 +99,14 @@ void Material::ClearTags() {
UpdateMemorySize();
}
Containers::String Material::GetTagName(Core::uint32 index) const {
return index < m_tags.Size() ? m_tags[index].name : Containers::String();
}
Containers::String Material::GetTagValue(Core::uint32 index) const {
return index < m_tags.Size() ? m_tags[index].value : Containers::String();
}
void Material::SetFloat(const Containers::String& name, float value) {
MaterialProperty prop;
prop.name = name;
@@ -180,7 +188,7 @@ void Material::SetTexture(const Containers::String& name, const ResourceHandle<T
}
}
TextureBinding binding;
MaterialTextureBinding binding;
binding.name = name;
binding.slot = static_cast<Core::uint32>(m_textureBindings.Size());
binding.texture = texture;
@@ -246,6 +254,24 @@ ResourceHandle<Texture> Material::GetTexture(const Containers::String& name) con
return ResourceHandle<Texture>();
}
Containers::String Material::GetTextureBindingName(Core::uint32 index) const {
return index < m_textureBindings.Size() ? m_textureBindings[index].name : Containers::String();
}
ResourceHandle<Texture> Material::GetTextureBindingTexture(Core::uint32 index) const {
return index < m_textureBindings.Size() ? m_textureBindings[index].texture : ResourceHandle<Texture>();
}
std::vector<MaterialProperty> Material::GetProperties() const {
std::vector<MaterialProperty> properties;
const auto pairs = m_properties.GetPairs();
properties.reserve(pairs.Size());
for (const auto& pair : pairs) {
properties.push_back(pair.second);
}
return properties;
}
void Material::UpdateConstantBuffer() {
m_constantBufferData.Clear();
UpdateMemorySize();
@@ -275,13 +301,13 @@ void Material::UpdateMemorySize() {
m_memorySize = m_constantBufferData.Size() +
sizeof(MaterialRenderState) +
m_shaderPass.Length() +
m_tags.Size() * sizeof(TagEntry) +
m_textureBindings.Size() * sizeof(TextureBinding) +
m_tags.Size() * sizeof(MaterialTagEntry) +
m_textureBindings.Size() * sizeof(MaterialTextureBinding) +
m_properties.Size() * sizeof(MaterialProperty) +
m_name.Length() +
m_path.Length();
for (const TagEntry& tag : m_tags) {
for (const MaterialTagEntry& tag : m_tags) {
m_memorySize += tag.name.Length();
m_memorySize += tag.value.Length();
}

View File

@@ -1,10 +1,12 @@
#include <XCEngine/Resources/Material/MaterialLoader.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Asset/ResourceTypes.h>
#include <XCEngine/Resources/Shader/ShaderLoader.h>
#include <cctype>
#include <cstdlib>
#include <filesystem>
#include <string>
namespace XCEngine {
@@ -531,6 +533,24 @@ ResourceHandle<Shader> LoadShaderHandle(const Containers::String& shaderPath) {
return ResourceHandle<Shader>(static_cast<Shader*>(shaderResult.resource));
}
bool MaterialFileExists(const Containers::String& path) {
const std::filesystem::path inputPath(path.CStr());
if (std::filesystem::exists(inputPath)) {
return true;
}
if (inputPath.is_absolute()) {
return false;
}
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
if (resourceRoot.Empty()) {
return false;
}
return std::filesystem::exists(std::filesystem::path(resourceRoot.CStr()) / inputPath);
}
} // namespace
MaterialLoader::MaterialLoader() = default;
@@ -546,6 +566,10 @@ Containers::Array<Containers::String> MaterialLoader::GetSupportedExtensions() c
}
bool MaterialLoader::CanLoad(const Containers::String& path) const {
if (IsBuiltinMaterialPath(path)) {
return true;
}
Containers::String ext = GetExtension(path);
return ext == "mat" || ext == "material" || ext == "json";
}
@@ -553,17 +577,22 @@ bool MaterialLoader::CanLoad(const Containers::String& path) const {
LoadResult MaterialLoader::Load(const Containers::String& path, const ImportSettings* settings) {
(void)settings;
Containers::Array<Core::uint8> data = ReadFileData(path);
if (data.Empty()) {
return LoadResult("Failed to read material file: " + path);
if (IsBuiltinMaterialPath(path)) {
return CreateBuiltinMaterialResource(path);
}
Containers::Array<Core::uint8> data = ReadFileData(path);
Material* material = new Material();
material->m_path = path;
material->m_name = path;
material->m_guid = ResourceGUID::Generate(path);
if (!ParseMaterialData(data, material)) {
if (data.Empty() && !MaterialFileExists(path)) {
delete material;
return LoadResult("Failed to read material file: " + path);
}
if (!data.Empty() && !ParseMaterialData(data, material)) {
delete material;
return LoadResult("Failed to parse material file: " + path);
}

View File

@@ -1,4 +1,6 @@
#include <XCEngine/Resources/Mesh/MeshLoader.h>
#include <XCEngine/Core/Asset/ArtifactFormats.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Mesh/MeshImportSettings.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Texture/Texture.h>
@@ -14,6 +16,7 @@
#include <filesystem>
#include <fstream>
#include <limits>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
@@ -507,6 +510,218 @@ void ProcessNode(const aiNode& node,
}
}
Containers::String ReadBinaryString(std::ifstream& stream) {
Core::uint32 length = 0;
stream.read(reinterpret_cast<char*>(&length), sizeof(length));
if (!stream || length == 0) {
return Containers::String();
}
std::string buffer(length, '\0');
stream.read(buffer.data(), length);
if (!stream) {
return Containers::String();
}
return Containers::String(buffer.c_str());
}
void ApplyMaterialProperty(Material& material, const MaterialProperty& property) {
switch (property.type) {
case MaterialPropertyType::Float:
material.SetFloat(property.name, property.value.floatValue[0]);
break;
case MaterialPropertyType::Float2:
material.SetFloat2(property.name,
Math::Vector2(property.value.floatValue[0], property.value.floatValue[1]));
break;
case MaterialPropertyType::Float3:
material.SetFloat3(property.name,
Math::Vector3(property.value.floatValue[0], property.value.floatValue[1], property.value.floatValue[2]));
break;
case MaterialPropertyType::Float4:
material.SetFloat4(property.name,
Math::Vector4(property.value.floatValue[0],
property.value.floatValue[1],
property.value.floatValue[2],
property.value.floatValue[3]));
break;
case MaterialPropertyType::Int:
material.SetInt(property.name, property.value.intValue[0]);
break;
case MaterialPropertyType::Bool:
material.SetBool(property.name, property.value.boolValue);
break;
default:
break;
}
}
LoadResult LoadMeshArtifact(const Containers::String& path) {
std::ifstream input(path.CStr(), std::ios::binary);
if (!input.is_open()) {
return LoadResult(Containers::String("Failed to read mesh artifact: ") + path);
}
MeshArtifactHeader header;
input.read(reinterpret_cast<char*>(&header), sizeof(header));
if (!input) {
return LoadResult(Containers::String("Failed to parse mesh artifact header: ") + path);
}
const std::string magic(header.magic, header.magic + 7);
if (magic != "XCMESH1") {
return LoadResult(Containers::String("Invalid mesh artifact magic: ") + path);
}
auto mesh = std::make_unique<Mesh>();
IResource::ConstructParams params;
params.name = GetResourceNameFromPath(path);
params.path = path;
params.guid = ResourceGUID::Generate(path);
params.memorySize = 0;
mesh->Initialize(params);
Containers::Array<MeshSection> sections;
sections.Resize(header.sectionCount);
for (Core::uint32 index = 0; index < header.sectionCount; ++index) {
input.read(reinterpret_cast<char*>(&sections[index]), sizeof(MeshSection));
if (!input) {
return LoadResult(Containers::String("Failed to read mesh sections: ") + path);
}
}
Containers::Array<Core::uint8> vertexData;
vertexData.Resize(static_cast<size_t>(header.vertexDataSize));
if (header.vertexDataSize > 0) {
input.read(reinterpret_cast<char*>(vertexData.Data()), static_cast<std::streamsize>(header.vertexDataSize));
if (!input) {
return LoadResult(Containers::String("Failed to read mesh vertex data: ") + path);
}
}
Containers::Array<Core::uint8> indexData;
indexData.Resize(static_cast<size_t>(header.indexDataSize));
if (header.indexDataSize > 0) {
input.read(reinterpret_cast<char*>(indexData.Data()), static_cast<std::streamsize>(header.indexDataSize));
if (!input) {
return LoadResult(Containers::String("Failed to read mesh index data: ") + path);
}
}
mesh->SetVertexData(vertexData.Data(),
vertexData.Size(),
header.vertexCount,
header.vertexStride,
static_cast<VertexAttribute>(header.vertexAttributes));
mesh->SetIndexData(indexData.Data(),
indexData.Size(),
header.indexCount,
header.use32BitIndex != 0);
for (const MeshSection& section : sections) {
mesh->AddSection(section);
}
Math::Bounds bounds;
bounds.SetMinMax(header.boundsMin, header.boundsMax);
mesh->SetBounds(bounds);
std::vector<Containers::String> textureFiles;
textureFiles.reserve(header.textureCount);
for (Core::uint32 textureIndex = 0; textureIndex < header.textureCount; ++textureIndex) {
textureFiles.push_back(ReadBinaryString(input));
}
std::unordered_map<std::string, Texture*> loadedTextures;
TextureLoader textureLoader;
const std::filesystem::path artifactDirectory = std::filesystem::path(path.CStr()).parent_path();
for (Core::uint32 materialIndex = 0; materialIndex < header.materialCount; ++materialIndex) {
Core::uint32 materialPresent = 0;
input.read(reinterpret_cast<char*>(&materialPresent), sizeof(materialPresent));
if (!input) {
return LoadResult(Containers::String("Failed to read mesh material flag: ") + path);
}
if (materialPresent == 0) {
mesh->AddMaterial(nullptr);
continue;
}
auto* material = new Material();
material->m_name = ReadBinaryString(input);
material->m_path = ReadBinaryString(input);
material->m_guid = ResourceGUID::Generate(material->m_path);
material->m_isValid = true;
material->SetShaderPass(ReadBinaryString(input));
MaterialArtifactHeader materialHeader;
input.read(reinterpret_cast<char*>(&materialHeader), sizeof(materialHeader));
if (!input) {
delete material;
return LoadResult(Containers::String("Failed to read material artifact header: ") + path);
}
material->SetRenderQueue(materialHeader.renderQueue);
material->SetRenderState(materialHeader.renderState);
for (Core::uint32 tagIndex = 0; tagIndex < materialHeader.tagCount; ++tagIndex) {
material->SetTag(ReadBinaryString(input), ReadBinaryString(input));
}
for (Core::uint32 propertyIndex = 0; propertyIndex < materialHeader.propertyCount; ++propertyIndex) {
MaterialProperty property;
property.name = ReadBinaryString(input);
MaterialPropertyArtifact propertyArtifact;
input.read(reinterpret_cast<char*>(&propertyArtifact), sizeof(propertyArtifact));
if (!input) {
delete material;
return LoadResult(Containers::String("Failed to read material property: ") + path);
}
property.type = static_cast<MaterialPropertyType>(propertyArtifact.propertyType);
property.value = propertyArtifact.value;
ApplyMaterialProperty(*material, property);
}
for (Core::uint32 bindingIndex = 0; bindingIndex < materialHeader.textureBindingCount; ++bindingIndex) {
const Containers::String bindingName = ReadBinaryString(input);
const Containers::String textureFile = ReadBinaryString(input);
if (textureFile.Empty()) {
continue;
}
const std::string textureKey(textureFile.CStr());
Texture* texture = nullptr;
auto textureIt = loadedTextures.find(textureKey);
if (textureIt != loadedTextures.end()) {
texture = textureIt->second;
} else {
const Containers::String texturePath =
Containers::String((artifactDirectory / textureFile.CStr()).lexically_normal().string().c_str());
LoadResult textureResult = textureLoader.Load(texturePath);
if (textureResult && textureResult.resource != nullptr) {
texture = static_cast<Texture*>(textureResult.resource);
loadedTextures.emplace(textureKey, texture);
mesh->AddTexture(texture);
}
}
if (texture != nullptr) {
material->SetTexture(bindingName, ResourceHandle<Texture>(texture));
}
}
material->RecalculateMemorySize();
mesh->AddMaterial(material);
}
return LoadResult(mesh.release());
}
} // namespace
MeshLoader::MeshLoader() = default;
@@ -520,26 +735,45 @@ Containers::Array<Containers::String> MeshLoader::GetSupportedExtensions() const
extensions.PushBack(Containers::String("glb"));
extensions.PushBack(Containers::String("dae"));
extensions.PushBack(Containers::String("stl"));
extensions.PushBack(Containers::String("xcmesh"));
return extensions;
}
bool MeshLoader::CanLoad(const Containers::String& path) const {
if (IsBuiltinMeshPath(path)) {
return true;
}
Containers::String ext = GetExtension(path).ToLower();
return ext == "fbx" || ext == "obj" || ext == "gltf" ||
ext == "glb" || ext == "dae" || ext == "stl";
ext == "glb" || ext == "dae" || ext == "stl" ||
ext == "xcmesh";
}
LoadResult MeshLoader::Load(const Containers::String& path, const ImportSettings* settings) {
if (IsBuiltinMeshPath(path)) {
return CreateBuiltinMeshResource(path);
}
const Containers::String ext = GetExtension(path).ToLower();
if (!CanLoad(path)) {
return LoadResult(Containers::String("Unsupported mesh format: ") + ext);
}
std::ifstream file(path.CStr(), std::ios::binary);
if (ext == "xcmesh") {
return LoadMeshArtifact(path);
}
Containers::String resolvedPath = path;
if (!std::filesystem::path(path.CStr()).is_absolute()) {
resolvedPath = ResourceManager::Get().ResolvePath(path);
}
std::ifstream file(resolvedPath.CStr(), std::ios::binary);
if (!file.is_open()) {
return LoadResult(Containers::String("Failed to read file: ") + path);
return LoadResult(Containers::String("Failed to read file: ") + resolvedPath);
}
MeshImportSettings defaultSettings;
@@ -549,7 +783,7 @@ LoadResult MeshLoader::Load(const Containers::String& path, const ImportSettings
}
Assimp::Importer importer;
const aiScene* scene = importer.ReadFile(path.CStr(), BuildPostProcessFlags(*resolvedSettings));
const aiScene* scene = importer.ReadFile(resolvedPath.CStr(), BuildPostProcessFlags(*resolvedSettings));
if (scene == nullptr || scene->mRootNode == nullptr || (scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) != 0) {
const char* errorText = importer.GetErrorString();
return LoadResult(Containers::String("Assimp failed to load mesh: ") +
@@ -577,7 +811,7 @@ LoadResult MeshLoader::Load(const Containers::String& path, const ImportSettings
auto* mesh = new Mesh();
IResource::ConstructParams params;
const std::string fileName = std::filesystem::path(path.CStr()).filename().string();
const std::string fileName = std::filesystem::path(resolvedPath.CStr()).filename().string();
params.name = Containers::String(fileName.c_str());
params.path = path;
params.guid = ResourceGUID::Generate(path);

View File

@@ -1,7 +1,10 @@
#include <XCEngine/Resources/Texture/TextureLoader.h>
#include <XCEngine/Core/Asset/ArtifactFormats.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <stb_image.h>
#include <filesystem>
#include <fstream>
namespace XCEngine {
namespace Resources {
@@ -29,7 +32,7 @@ LoadResult CreateTextureResource(const Containers::String& path,
params.name = GetResourceNameFromPath(path);
params.path = path;
params.guid = ResourceGUID::Generate(path);
params.memorySize = 0;
params.memorySize = pixelDataSize;
texture->Initialize(params);
if (!texture->Create(width,
@@ -47,6 +50,40 @@ LoadResult CreateTextureResource(const Containers::String& path,
return LoadResult(texture);
}
LoadResult LoadTextureArtifact(const Containers::String& path) {
std::ifstream input(path.CStr(), std::ios::binary);
if (!input.is_open()) {
return LoadResult(Containers::String("Failed to read texture artifact: ") + path);
}
TextureArtifactHeader header;
input.read(reinterpret_cast<char*>(&header), sizeof(header));
if (!input) {
return LoadResult(Containers::String("Failed to parse texture artifact header: ") + path);
}
const std::string magic(header.magic, header.magic + 7);
if (magic != "XCTEX01") {
return LoadResult(Containers::String("Invalid texture artifact magic: ") + path);
}
Containers::Array<Core::uint8> pixelData;
pixelData.Resize(static_cast<size_t>(header.pixelDataSize));
if (header.pixelDataSize > 0) {
input.read(reinterpret_cast<char*>(pixelData.Data()), static_cast<std::streamsize>(header.pixelDataSize));
if (!input) {
return LoadResult(Containers::String("Failed to read texture artifact payload: ") + path);
}
}
return CreateTextureResource(path,
static_cast<TextureFormat>(header.textureFormat),
header.width,
header.height,
pixelData.Data(),
pixelData.Size());
}
} // namespace
TextureLoader::TextureLoader() = default;
@@ -62,19 +99,28 @@ Containers::Array<Containers::String> TextureLoader::GetSupportedExtensions() co
extensions.PushBack(Containers::String("gif"));
extensions.PushBack(Containers::String("hdr"));
extensions.PushBack(Containers::String("dds"));
extensions.PushBack(Containers::String("xctex"));
return extensions;
}
bool TextureLoader::CanLoad(const Containers::String& path) const {
if (IsBuiltinTexturePath(path)) {
return true;
}
Containers::String ext = GetExtension(path).ToLower();
return ext == "png" || ext == "jpg" || ext == "jpeg" ||
ext == "tga" || ext == "bmp" || ext == "gif" ||
ext == "hdr" || ext == "dds";
ext == "hdr" || ext == "dds" || ext == "xctex";
}
LoadResult TextureLoader::Load(const Containers::String& path, const ImportSettings* settings) {
(void)settings;
if (IsBuiltinTexturePath(path)) {
return CreateBuiltinTextureResource(path);
}
Containers::String ext = GetExtension(path).ToLower();
@@ -82,6 +128,10 @@ LoadResult TextureLoader::Load(const Containers::String& path, const ImportSetti
return LoadResult(Containers::String("Unsupported texture format: ") + ext);
}
if (ext == "xctex") {
return LoadTextureArtifact(path);
}
if (ext == "dds") {
return LoadResult(Containers::String("DDS texture decoding is not implemented yet: ") + path);
}