Entity now can be linked to a mesh.

Only support cube generated from code no light no nothing
This commit is contained in:
2026-08-09 17:20:38 -04:00
parent 30ea435b87
commit 73aa4a846b
11 changed files with 177 additions and 89 deletions
+1
View File
@@ -23,6 +23,7 @@ struct Entity final
const Juliet::Class* Kind;
DerivedType Derived;
float X, Y;
index_t MeshInstance = indexMax;
};
template <typename EntityType>
+15
View File
@@ -15,6 +15,7 @@
#include <Entity/Entity.h>
#include <Entity/EntityManager.h>
#include <Graphics/Camera.h>
#include <Graphics/MeshRenderer.h>
#include <imgui.h>
GameState* gGameState = nullptr;
@@ -92,6 +93,10 @@ extern "C" JULIET_API void __cdecl GameUpdate(Juliet::GameData* params, [[maybe_
rock->Health = 100;
Assert(door->Base != rock->Base);
MeshAssetID cubeAsset = GetCubePrimitiveMeshAssetID();
MeshInstanceID meshInst = CreateMeshInstance(cubeAsset, 0, MatrixIdentity());
rock->Base->MeshInstance = meshInst;
printf("Door is %s\n", door->IsOpened ? "Opened" : "Closed");
printf("Rock has %d health points\n", rock->Health);
}
@@ -106,6 +111,16 @@ extern "C" JULIET_API void __cdecl GameUpdate(Juliet::GameData* params, [[maybe_
{
// update game
}
// Sync entity transforms to meshes
auto& manager = GetEntityManager();
for (Entity& ent : manager.Entities)
{
if (ent.MeshInstance != static_cast<index_t>(-1))
{
SetMeshInstanceTransform(ent.MeshInstance, MatrixTranslation(ent.X, ent.Y, 0.0f));
}
}
if (gGameState->Mode == GameMode::Debug)
{
+2
View File
@@ -41,5 +41,7 @@ constexpr int16 int16Max = MaxValueOf<int16>();
constexpr int32 int32Max = MaxValueOf<int32>();
constexpr int64 int64Max = MaxValueOf<int64>();
constexpr index_t indexMax = MaxValueOf<index_t>();
#define Kilobytes(value) value * 1024
#define Megabytes(value) Kilobytes(value) * 1024
+1 -1
View File
@@ -5,5 +5,5 @@
namespace Juliet
{
JULIET_API extern MeshID LoadMesh(String filename);
JULIET_API extern MeshAssetID LoadMesh(String filename);
}
+1
View File
@@ -10,6 +10,7 @@ namespace Juliet
{
IApplication* Application = nullptr;
Arena* PlatformArena = nullptr;
Arena* AssetArena = nullptr;
};
void InitializeEngine(JulietInit_Flags flags);
+19 -2
View File
@@ -2,7 +2,9 @@
#include <Core/Common/CoreTypes.h>
#include <Core/Common/NonNullPtr.h>
#include <Core/Common/String.h>
#include <Core/Math/Matrix.h>
#include <Core/Math/Vector.h>
#include <Juliet.h>
namespace Juliet
@@ -10,14 +12,29 @@ namespace Juliet
struct Arena;
struct Vertex;
struct Mesh
using MeshAssetID = index_t;
using MaterialAssetID = index_t;
using MeshInstanceID = index_t;
struct MeshAsset
{
String Name;
size_t VertexCount;
size_t IndexCount;
index_t VertexOffset;
index_t IndexOffset;
};
Matrix Transform = MatrixIdentity();
struct MaterialAsset
{
Vector4 AlbedoColor = {1.0f, 1.0f, 1.0f, 1.0f};
};
struct MeshInstance
{
MeshAssetID MeshAsset;
MaterialAssetID MaterialAsset;
Matrix Transform = MatrixIdentity();
};
} // namespace Juliet
+10 -29
View File
@@ -6,6 +6,7 @@
#include <Graphics/Lighting.h>
#include <Graphics/PushConstants.h>
#include <Graphics/VertexData.h>
#include <Graphics/Mesh.h>
#include <Juliet.h>
namespace Juliet
@@ -17,9 +18,6 @@ namespace Juliet
struct Window;
struct GraphicsPipeline;
struct GraphicsDevice;
struct Mesh;
using MeshID = index_t;
using LightID = index_t;
constexpr size_t kGeometryPage = Megabytes(64);
@@ -29,26 +27,7 @@ namespace Juliet
constexpr size_t kDefaultIndexCount = 16'000'000; // Fit less than one index page
constexpr size_t kDefaultLightCount = 1024;
struct MeshRenderer
{
VectorArena<Mesh, kDefaultMeshNumber> Meshes;
VectorArena<Vertex, kDefaultVertexCount> Vertices;
VectorArena<Index, kDefaultIndexCount> Indices;
VectorArena<PointLight, kDefaultLightCount> PointLights;
GraphicsBuffer* VertexBuffer;
GraphicsBuffer* IndexBuffer;
GraphicsTransferBuffer* StreamCopyBuffer;
GraphicsTransferBuffer* LoadCopyBuffer;
GraphicsBuffer* LightsBuffer;
PointLight* MappedLights;
GraphicsDevice* Device;
GraphicsPipeline* Pipeline;
};
JULIET_API void InitializeMeshRenderer(NonNullPtr<Arena> arena);
JULIET_API void InitializeMeshRenderer(NonNullPtr<Arena> assetArena, NonNullPtr<Arena> instanceArena);
[[nodiscard]] JULIET_API bool InitializeMeshRendererGraphics(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window);
JULIET_API void ShutdownMeshRendererGraphics();
JULIET_API void ShutdownMeshRenderer();
@@ -63,13 +42,15 @@ namespace Juliet
JULIET_API void SetPointLightIntensity(LightID id, float intensity);
JULIET_API void ClearPointLights();
// Primitives
JULIET_API MeshID GetCubePrimitiveMeshID();
// Assets & Instances
[[nodiscard]] JULIET_API MeshAssetID GetOrCreateMeshAsset(String name);
[[nodiscard]] JULIET_API MeshInstanceID CreateMeshInstance(MeshAssetID meshAsset, MaterialAssetID materialAsset, const Matrix& transform);
JULIET_API void SetMeshInstanceTransform(MeshInstanceID id, const Matrix& transform);
// Utils
[[nodiscard]] JULIET_API MeshID CreateCubePrimitive();
[[nodiscard]] JULIET_API MeshID CreateQuadPrimitive();
JULIET_API void SetMeshTransform(MeshID id, const Matrix& transform);
// Primitives
JULIET_API MeshAssetID GetCubePrimitiveMeshAssetID();
JULIET_API MeshAssetID GetQuadPrimitiveMeshAssetID();
JULIET_API MeshAssetID GetSpherePrimitiveMeshAssetID();
#if ALLOW_SHADER_HOT_RELOAD
JULIET_API void ReloadMeshRendererShaders();
+1 -1
View File
@@ -2,5 +2,5 @@
namespace Juliet
{
MeshID LoadMesh(String filename) {}
MeshAssetID LoadMesh([[maybe_unused]] String filename) { return 0; }
} // namespace Juliet
+4 -1
View File
@@ -153,6 +153,8 @@ namespace Juliet
{
EngineInstance.PlatformArena =
ArenaAllocate({ .ReserveSize = Megabytes(128) } JULIET_DEBUG_PARAM("Platform Arena"));
EngineInstance.AssetArena =
ArenaAllocate({ .ReserveSize = Megabytes(256) } JULIET_DEBUG_PARAM("Asset Arena"));
InitializeLogManager();
@@ -172,6 +174,7 @@ namespace Juliet
ShutdownFilesystem();
ShutdownLogManager();
ArenaRelease(EngineInstance.AssetArena);
ArenaRelease(EngineInstance.PlatformArena);
}
@@ -179,7 +182,7 @@ namespace Juliet
{
EngineInstance.Application = &app;
InitializeMeshRenderer(EngineInstance.PlatformArena);
InitializeMeshRenderer(EngineInstance.AssetArena, EngineInstance.PlatformArena);
EngineInstance.Application->Init(EngineInstance.PlatformArena);
+121 -53
View File
@@ -4,6 +4,7 @@
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
#include <Core/Math/Matrix.h>
#include <Engine/Asset.h>
#include <Graphics/GraphicsDevice.h>
#include <Graphics/Mesh.h>
#include <Graphics/VertexData.h>
@@ -22,7 +23,10 @@ namespace Juliet
GraphicsBuffer* TransformsBuffer = nullptr;
GraphicsTransferBuffer* LoadCopyBuffer = nullptr;
VectorArena<Mesh, kDefaultMeshNumber> Meshes;
VectorArena<MeshAsset, kDefaultMeshNumber> MeshAssets;
VectorArena<MaterialAsset, kDefaultMeshNumber> MaterialAssets;
VectorArena<MeshInstance, kDefaultMeshNumber> MeshInstances;
VectorArena<Vertex, kDefaultVertexCount> Vertices;
VectorArena<Index, kDefaultIndexCount> Indices;
VectorArena<PointLight, kDefaultLightCount> PointLights;
@@ -33,18 +37,28 @@ namespace Juliet
MeshRendererState g_MeshRenderer;
MeshID CubePrimitiveMeshID = 0;
MeshAssetID CubePrimitiveMeshAssetID = static_cast<MeshAssetID>(-1);
MeshAssetID QuadPrimitiveMeshAssetID = static_cast<MeshAssetID>(-1);
MeshAssetID SpherePrimitiveMeshAssetID = static_cast<MeshAssetID>(-1);
} // namespace
void InitializeMeshRenderer(NonNullPtr<Arena> arena)
MeshAssetID CreateCubePrimitive();
MeshAssetID CreateQuadPrimitive();
MeshAssetID CreateSpherePrimitive();
void InitializeMeshRenderer(NonNullPtr<Arena> assetArena, NonNullPtr<Arena> instanceArena)
{
g_MeshRenderer.Meshes.Create(arena JULIET_DEBUG_PARAM("Meshes"));
g_MeshRenderer.Vertices.Create(arena JULIET_DEBUG_PARAM("Vertices"));
g_MeshRenderer.Indices.Create(arena JULIET_DEBUG_PARAM("Indices"));
g_MeshRenderer.PointLights.Create(arena JULIET_DEBUG_PARAM("PointLights"));
g_MeshRenderer.MeshAssets.Create(assetArena JULIET_DEBUG_PARAM("MeshAssets"));
g_MeshRenderer.MaterialAssets.Create(assetArena JULIET_DEBUG_PARAM("MaterialAssets"));
g_MeshRenderer.MeshInstances.Create(instanceArena JULIET_DEBUG_PARAM("MeshInstances"));
g_MeshRenderer.Vertices.Create(assetArena JULIET_DEBUG_PARAM("Vertices"));
g_MeshRenderer.Indices.Create(assetArena JULIET_DEBUG_PARAM("Indices"));
g_MeshRenderer.PointLights.Create(instanceArena JULIET_DEBUG_PARAM("PointLights"));
// Create primitives
CubePrimitiveMeshID = AddCube();
CubePrimitiveMeshAssetID = CreateCubePrimitive();
QuadPrimitiveMeshAssetID = CreateQuadPrimitive();
// SpherePrimitiveMeshAssetID = CreateSpherePrimitive(); // Will implement later
}
bool InitializeMeshRendererGraphics(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window)
@@ -181,13 +195,15 @@ namespace Juliet
{
g_MeshRenderer.Indices.Destroy();
g_MeshRenderer.Vertices.Destroy();
g_MeshRenderer.Meshes.Destroy();
g_MeshRenderer.MeshInstances.Destroy();
g_MeshRenderer.MaterialAssets.Destroy();
g_MeshRenderer.MeshAssets.Destroy();
g_MeshRenderer.PointLights.Destroy();
}
void LoadMeshesOnGPU(NonNullPtr<CommandList> cmdList)
{
if (g_MeshRenderer.Meshes.IsEmpty())
if (g_MeshRenderer.MeshAssets.IsEmpty())
{
return;
}
@@ -273,24 +289,29 @@ namespace Juliet
SetIndexBuffer(cmdList, g_MeshRenderer.IndexBuffer, IndexFormat::UInt16, g_MeshRenderer.Indices.Count, 0);
uint32 meshIndex = 0;
for (Mesh& mesh : g_MeshRenderer.Meshes)
for (MeshInstance& instance : g_MeshRenderer.MeshInstances)
{
if (g_MeshRenderer.MappedTransforms)
{
g_MeshRenderer.MappedTransforms[meshIndex] = mesh.Transform;
g_MeshRenderer.MappedTransforms[meshIndex] = instance.Transform;
}
MeshAsset& meshAsset = g_MeshRenderer.MeshAssets.Data[instance.MeshAsset];
// If we used MaterialAsset, we would pass properties here.
// For now, the shader doesn't read material color, or maybe we can pass it if PushData supports it.
// pushData has GlobalLightColor, but no individual mesh color yet. We can just keep it simple.
pushData.MeshIndex = meshIndex;
pushData.TextureIndex = 0;
pushData.VertexOffset = static_cast<uint32>(mesh.VertexOffset);
pushData.TextureIndex = 0; // Would be instance.MaterialAsset.BaseColorTexture later
pushData.VertexOffset = static_cast<uint32>(meshAsset.VertexOffset);
pushData.Padding = 0;
pushData.Scale[0] = 1.0f;
pushData.Scale[1] = 1.0f;
pushData.Translate[0] = 0.0f;
pushData.Translate[1] = 0.0f;
SetPushConstants(cmdList, ShaderStage::Vertex, 0, sizeof(pushData) / 4, &pushData);
DrawIndexedPrimitives(pass, static_cast<uint32>(mesh.IndexCount), 1, static_cast<uint32>(mesh.IndexOffset),
static_cast<uint32>(mesh.VertexOffset), 0);
DrawIndexedPrimitives(pass, static_cast<uint32>(meshAsset.IndexCount), 1,
static_cast<uint32>(meshAsset.IndexOffset), static_cast<uint32>(meshAsset.VertexOffset), 0);
meshIndex++;
}
}
@@ -352,14 +373,58 @@ namespace Juliet
g_MeshRenderer.PointLights.Clear();
}
MeshID GetCubePrimitiveMeshID()
MeshAssetID GetCubePrimitiveMeshAssetID()
{
return CubePrimitiveMeshID;
Assert(CubePrimitiveMeshAssetID != static_cast<MeshAssetID>(-1));
return CubePrimitiveMeshAssetID;
}
MeshID CreateCubePrimitive()
MeshAssetID GetQuadPrimitiveMeshAssetID()
{
Mesh result = {};
Assert(QuadPrimitiveMeshAssetID != static_cast<MeshAssetID>(-1));
return QuadPrimitiveMeshAssetID;
}
MeshAssetID GetSpherePrimitiveMeshAssetID()
{
Assert(SpherePrimitiveMeshAssetID != static_cast<MeshAssetID>(-1));
return SpherePrimitiveMeshAssetID;
}
MeshAssetID GetOrCreateMeshAsset(String name)
{
for (index_t i = 0; i < g_MeshRenderer.MeshAssets.Count; ++i)
{
if (StringCompare(g_MeshRenderer.MeshAssets.Data[i].Name, name) == 0)
{
return i;
}
}
return LoadMesh(name);
}
MeshInstanceID CreateMeshInstance(MeshAssetID meshAsset, MaterialAssetID materialAsset, const Matrix& transform)
{
MeshInstance instance = {};
instance.MeshAsset = meshAsset;
instance.MaterialAsset = materialAsset;
instance.Transform = transform;
MeshInstanceID id = g_MeshRenderer.MeshInstances.Count;
g_MeshRenderer.MeshInstances.PushBack(instance);
return id;
}
void SetMeshInstanceTransform(MeshInstanceID id, const Matrix& transform)
{
Assert(id < static_cast<MeshInstanceID>(g_MeshRenderer.MeshInstances.Count));
g_MeshRenderer.MeshInstances.Data[id].Transform = transform;
}
MeshAssetID CreateCubePrimitive()
{
MeshAsset result = {};
result.Name = WrapString("Cube");
constexpr Vertex vertexData[] = { // Front Face (Z = -0.5f) — Red
{ { -0.5f, 0.5f, -0.5f }, { 0.0f, 0.0f, -1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
@@ -413,47 +478,50 @@ namespace Juliet
result.IndexOffset = g_MeshRenderer.Indices.Count;
g_MeshRenderer.Indices.PushBack(indices, indexCubeCount);
MeshID id = g_MeshRenderer.Meshes.Count;
g_MeshRenderer.Meshes.PushBack(result);
MeshAssetID id = g_MeshRenderer.MeshAssets.Count;
g_MeshRenderer.MeshAssets.PushBack(result);
return id;
}
MeshID CreateQuadPrimitive()
MeshAssetID CreateQuadPrimitive()
{
// Mesh result = {};
MeshAsset result = {};
result.Name = WrapString("Quad");
// // Using the exact 6 vertices from the working triangles!
// constexpr Vertex vertexData[] = {
// // Triangle 1 (Clockwise)
// { { -0.5f, -0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f } }, // 0: Red
// { { 0.0f, 0.5f, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } }, // 1: Green
// { { 0.5f, -0.5f, 0.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } }, // 2: Blue
//
// // Triangle 2 (Clockwise)
// { { -0.5f, 0.5f, 0.0f }, { 1.0f, 1.0f, 0.0f, 1.0f } }, // 3: Yellow
// { { 0.0f, 0.8f, 0.0f }, { 0.0f, 1.0f, 1.0f, 1.0f } }, // 4: Cyan
// { { 0.5f, 0.5f, 0.0f }, { 1.0f, 0.0f, 1.0f, 1.0f } } // 5: Magenta
// };
// constexpr size_t triVertexCount = ArraySize(vertexData);
// result.VertexCount = triVertexCount;
// result.Vertices = ArenaPushArray<Vertex>(arena.Get(), triVertexCount);
// MemCopy(result.Vertices, vertexData, sizeof(Vertex) * triVertexCount);
//
// // Just the 6 indices for the two triangles
// constexpr uint16 indices[] = { 0, 1, 2, 3, 4, 5 };
// constexpr size_t triIndexCount = ArraySize(indices);
// result.IndexCount = triIndexCount;
// result.Indices = ArenaPushArray<uint16>(arena.Get(), triIndexCount JULIET_DEBUG_PARAM("Indices"));
// MemCopy(result.Indices, indices, sizeof(uint16) * triIndexCount);
//
// g_MeshRenderer.Meshes.PushBack(std::move(result));
return g_MeshRenderer.Meshes.Count - 1;
constexpr Vertex vertexData[] = {
// Triangle 1 (Clockwise)
{ { -0.5f, -0.5f, 0.0f }, { 0.0f, 0.0f, -1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } }, // 0
{ { 0.0f, 0.5f, 0.0f }, { 0.0f, 0.0f, -1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } }, // 1
{ { 0.5f, -0.5f, 0.0f }, { 0.0f, 0.0f, -1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } }, // 2
// Triangle 2 (Clockwise)
{ { -0.5f, 0.5f, 0.0f }, { 0.0f, 0.0f, -1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } }, // 3
{ { 0.0f, 0.8f, 0.0f }, { 0.0f, 0.0f, -1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } }, // 4
{ { 0.5f, 0.5f, 0.0f }, { 0.0f, 0.0f, -1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } } // 5
};
constexpr size_t triVertexCount = ArraySize(vertexData);
result.VertexCount = triVertexCount;
result.VertexOffset = g_MeshRenderer.Vertices.Count;
g_MeshRenderer.Vertices.PushBack(vertexData, triVertexCount);
constexpr uint16 indices[] = { 0, 1, 2, 3, 4, 5 };
constexpr size_t triIndexCount = ArraySize(indices);
result.IndexCount = triIndexCount;
result.IndexOffset = g_MeshRenderer.Indices.Count;
g_MeshRenderer.Indices.PushBack(indices, triIndexCount);
MeshAssetID id = g_MeshRenderer.MeshAssets.Count;
g_MeshRenderer.MeshAssets.PushBack(result);
return id;
}
void SetMeshTransform(MeshID id, const Matrix& transform)
// Sphere primitive implementation omitted for brevity, returning Cube for now if called.
MeshAssetID CreateSpherePrimitive()
{
Assert(id < static_cast<MeshID>(g_MeshRenderer.Meshes.Count));
g_MeshRenderer.Meshes.Data[id].Transform = transform;
// Just returning cube for now until proper sphere generation is written
MeshAssetID id = CreateCubePrimitive();
g_MeshRenderer.MeshAssets.Data[id].Name = WrapString("Sphere"); // Rename just to pass cache
return id;
}
#if ALLOW_SHADER_HOT_RELOAD
+2 -2
View File
@@ -152,13 +152,13 @@ void JulietApplication::Init(NonNullPtr<Arena>)
{
for (int col = 0; col < kGridSize; ++col)
{
MeshID cube = AddCube();
MeshAssetID cubeAsset = GetCubePrimitiveMeshAssetID();
float x = static_cast<float>(col) * kSpacing - kOffset;
float y = static_cast<float>(row) * kSpacing - kOffset;
float seed = static_cast<float>(row * kGridSize + col);
Matrix rotation = MatrixRotation(seed * 0.73f, seed * 1.17f, seed * 0.53f);
SetMeshTransform(cube, MatrixTranslation(x, y, 0.0f) * rotation);
CreateMeshInstance(cubeAsset, 0, MatrixTranslation(x, y, 0.0f) * rotation);
}
}