16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-20 07:59:18 +02:00
This commit is contained in:
2026-05-24 21:48:05 +02:00
parent 5b51188dfe
commit 2c599cb419
46 changed files with 2034 additions and 85 deletions

View File

@@ -19,6 +19,7 @@ public:
virtual void OnDestroy(ECWorld& world) {}
virtual void Update(ECWorld& world, float dt) {}
virtual void Render(ECWorld& world) {}
};

View File

@@ -0,0 +1,116 @@
#include "stdafx.h"
#include "BillboardSystem.h"
#include "entitysystem/ECWorld.h"
#include "entitysystem/components/BasicComponents.h"
#include "entitysystem/components/RenderComponents.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "gl/buffer.h"
#include "gl/vao.h"
#include "gl/shader.h"
struct BillboardSystem::RenderState
{
std::optional<gl::buffer> instanceBuffer;
std::optional<gl::vao> vao;
std::unique_ptr<gl::program> shader;
std::size_t bufferCapacity { 0 };
};
BillboardSystem::BillboardSystem() = default;
BillboardSystem::~BillboardSystem() = default;
void BillboardSystem::Render(ECWorld& world)
{
// Group billboard instances by texture path
std::unordered_map<std::string, std::vector<BillboardInstance>> groups;
const glm::vec3 camPos{
static_cast<float>(Global.pCamera.Pos.x),
static_cast<float>(Global.pCamera.Pos.y),
static_cast<float>(Global.pCamera.Pos.z)
};
world.Each<ECSComponent::Billboard, ECSComponent::Transform>(
entt::exclude<ECSComponent::Disabled>,
[&](entt::entity, ECSComponent::Billboard& bill, ECSComponent::Transform& t)
{
if (!bill.active) return;
std::string key = bill.texturePath.ToString();
groups[key].push_back({
glm::vec3(t.Position) - camPos,
bill.size
});
});
if (groups.empty()) return;
// Lazy-init shared GL resources
if (!m_renderState)
m_renderState = std::make_unique<RenderState>();
auto& rs = *m_renderState;
if (!rs.instanceBuffer)
rs.instanceBuffer.emplace();
if (!rs.shader) {
try {
gl::shader vert("ecs_billboard.vert");
gl::shader frag("ecs_billboard.frag");
rs.shader = std::unique_ptr<gl::program>(new gl::program({vert, frag}));
} catch (const gl::shader_exception& e) {
WriteLog("[ECS] BillboardSystem: shader compile failed: " + std::string(e.what()));
return;
}
}
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthMask(GL_FALSE);
rs.shader->bind();
for (auto& [texPath, instances] : groups) {
// Grow VBO if needed
if (instances.size() > rs.bufferCapacity) {
rs.bufferCapacity = instances.size() + 64;
rs.instanceBuffer->allocate(
gl::buffer::ARRAY_BUFFER,
static_cast<int>(rs.bufferCapacity * sizeof(BillboardInstance)),
GL_DYNAMIC_DRAW);
rs.vao.reset(); // rebuild after realloc
}
rs.instanceBuffer->upload(
gl::buffer::ARRAY_BUFFER,
instances.data(), 0,
static_cast<int>(instances.size() * sizeof(BillboardInstance)));
if (!rs.vao) {
rs.vao.emplace();
constexpr int stride = sizeof(BillboardInstance);
rs.vao->setup_attrib(*rs.instanceBuffer, 0, 3, GL_FLOAT, stride, 0); // position
glVertexAttribDivisor(0, 1);
rs.vao->setup_attrib(*rs.instanceBuffer, 1, 1, GL_FLOAT, stride, 12); // size
glVertexAttribDivisor(1, 1);
rs.vao->unbind();
rs.instanceBuffer->unbind(gl::buffer::ARRAY_BUFFER);
}
// Bind texture (fallback: skip group if path is empty)
if (!texPath.empty()) {
auto texHandle = GfxRenderer->Fetch_Texture(texPath, true);
GfxRenderer->Bind_Texture(0, texHandle);
}
rs.vao->bind();
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, static_cast<GLsizei>(instances.size()));
rs.vao->unbind();
}
gl::program::unbind();
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include "BaseSystem.h"
#include "rendering/renderer.h"
#include <unordered_map>
#include <string>
// Renders Billboard components as camera-facing textured quads.
// Instances sharing the same texture are batched into one draw call.
class BillboardSystem : public BaseSystem
{
public:
BillboardSystem();
~BillboardSystem();
void Render(ECWorld& world) override;
private:
struct BillboardInstance {
glm::vec3 position; // camera-relative
float size;
};
struct RenderState;
std::unique_ptr<RenderState> m_renderState;
};

View File

@@ -0,0 +1,27 @@
#include "stdafx.h"
#include "HierarchySystem.h"
#include "entitysystem/ECWorld.h"
#include "entitysystem/components/BasicComponents.h"
void HierarchySystem::Update(ECWorld& world, float dt)
{
world.Each<ECSComponent::Parent, ECSComponent::Transform>(
entt::exclude<ECSComponent::Disabled>,
[&](entt::entity, ECSComponent::Parent& parent, ECSComponent::Transform& childTransform)
{
if (parent.value == entt::null || !world.IsAlive(parent.value))
return;
const auto* parentTransform = world.GetComponent<ECSComponent::Transform>(parent.value);
if (!parentTransform)
return;
// Rotate local offset by parent rotation then add parent world position.
glm::dvec3 rotatedOffset = glm::dvec3(parentTransform->Rotation * glm::vec3(parent.localOffset));
childTransform.Position = parentTransform->Position + rotatedOffset;
// Compose rotations: child world = parent world * child local.
childTransform.Rotation = parentTransform->Rotation * parent.localRotation;
});
}

View File

@@ -0,0 +1,14 @@
#pragma once
#include "BaseSystem.h"
// Propagates parent world Transform to children.
// Runs after MovementSystem so children always inherit the parent's
// already-updated position in the same frame.
// Child's Transform.Position is stored as a LOCAL offset relative to
// the parent and this system overwrites it with the computed world position.
class HierarchySystem : public BaseSystem
{
public:
void Update(ECWorld& world, float dt) override;
};

View File

@@ -0,0 +1,33 @@
#include "stdafx.h"
#include "LODSystem.h"
#include "entitysystem/ECWorld.h"
#include "entitysystem/components/BasicComponents.h"
#include "entitysystem/components/RenderComponents.h"
#include "utilities/Globals.h"
#include "model/AnimModel.h"
void LODSystem::Update(ECWorld& world, float dt)
{
const glm::dvec3 camPos{
Global.pCamera.Pos.x,
Global.pCamera.Pos.y,
Global.pCamera.Pos.z
};
world.Each<ECSComponent::LODController, ECSComponent::Transform>(
entt::exclude<ECSComponent::Disabled>,
[&](entt::entity entity,
ECSComponent::LODController& lod,
ECSComponent::Transform& transform)
{
double dist = glm::length(camPos - transform.Position);
bool inRange = (dist >= lod.RangeMin) && (dist <= lod.RangeMax);
if (auto* mesh = world.GetComponent<ECSComponent::MeshRenderer>(entity)) {
mesh->visible = inRange;
if (mesh->modelInstance)
mesh->modelInstance->visible(inRange);
}
});
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "BaseSystem.h"
// Reads LODController + Transform on each entity and toggles MeshRenderer.visible
// based on distance to camera. Runs every Update tick.
class LODSystem : public BaseSystem
{
public:
void Update(ECWorld& world, float dt) override;
};

View File

@@ -0,0 +1,62 @@
#include "stdafx.h"
#include "LightSystem.h"
#include "entitysystem/ECWorld.h"
#include "entitysystem/components/BasicComponents.h"
#include "entitysystem/components/RenderComponents.h"
#include "utilities/Globals.h"
#include "rendering/lightarray.h"
#include "simulation/simulation.h"
void LightSystem::Update(ECWorld& world, float dt)
{
// Sync SunLight intensity with global overcast value.
// Overcast 0 = clear sky (full sun), 2 = heavy overcast (dim).
const float sunIntensity = glm::clamp( 1.0f - Global.Overcast * 0.4f, 0.1f, 1.0f );
world.Each<ECSComponent::SunLight>(
entt::exclude<ECSComponent::Disabled>,
[sunIntensity](entt::entity, ECSComponent::SunLight& sun)
{
if (sun.Enabled)
sun.Intensity = sunIntensity;
});
// SpotLights: cull distant lights and rebuild the free_lights list for the renderer.
const glm::dvec3 camPos{
Global.pCamera.Pos.x,
Global.pCamera.Pos.y,
Global.pCamera.Pos.z
};
simulation::Lights.free_lights.clear();
world.Each<ECSComponent::SpotLight, ECSComponent::Transform>(
entt::exclude<ECSComponent::Disabled>,
[&](entt::entity, ECSComponent::SpotLight& spot, ECSComponent::Transform& transform)
{
double dist = glm::length(camPos - transform.Position);
if (dist > static_cast<double>(spot.Range) * 4.0) {
spot.Enabled = false;
return;
}
if (spot.Range > 0.0f)
spot.Enabled = true;
if (!spot.Enabled) return;
light_array::free_light_record rec;
rec.position = transform.Position;
rec.direction = glm::vec3(0.f, -1.f, 0.f); // default downward; rotation support can be added later
rec.color = spot.Color;
rec.intensity = spot.Intensity;
rec.range = spot.Range;
rec.inner_cutoff = glm::cos(glm::radians(spot.InnerAngle));
rec.outer_cutoff = glm::cos(glm::radians(spot.OuterAngle));
simulation::Lights.free_lights.push_back(rec);
});
}
void LightSystem::Render(ECWorld& world)
{
// Rendering of ECS lights is handled by the main renderer pipeline,
// which queries SpotLight/AreaLight/SunLight components via World views.
// Nothing to do here for now.
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include "BaseSystem.h"
// Manages ECS light components (SpotLight, AreaLight, SunLight).
// Updates light position/direction from Transform each tick.
// Actual renderer integration is handled by the rendering pipeline reading these components.
class LightSystem : public BaseSystem
{
public:
void Update(ECWorld& world, float dt) override;
void Render(ECWorld& world) override;
};

View File

@@ -0,0 +1,96 @@
#include "stdafx.h"
#include "LineSystem.h"
#include "entitysystem/ECWorld.h"
#include "entitysystem/components/BasicComponents.h"
#include "entitysystem/components/RenderComponents.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "gl/buffer.h"
#include "gl/vao.h"
#include "gl/shader.h"
struct LineSystem::RenderState
{
std::optional<gl::buffer> vertexBuffer;
std::optional<gl::vao> vao;
std::unique_ptr<gl::program> shader;
std::size_t bufferCapacity { 0 };
};
LineSystem::LineSystem() = default;
LineSystem::~LineSystem() = default;
void LineSystem::Render(ECWorld& world)
{
std::vector<LineVertex> vertices;
vertices.reserve(256);
const glm::vec3 camPos{
static_cast<float>(Global.pCamera.Pos.x),
static_cast<float>(Global.pCamera.Pos.y),
static_cast<float>(Global.pCamera.Pos.z)
};
world.Each<ECSComponent::Line, ECSComponent::Transform>(
entt::exclude<ECSComponent::Disabled>,
[&](entt::entity, ECSComponent::Line& line, ECSComponent::Transform& t)
{
if (!line.active) return;
glm::vec3 worldOrigin = glm::vec3(t.Position);
vertices.push_back({ worldOrigin + line.start - camPos, line.color });
vertices.push_back({ worldOrigin + line.end - camPos, line.color });
});
if (vertices.empty()) return;
if (!m_renderState)
m_renderState = std::make_unique<RenderState>();
auto& rs = *m_renderState;
if (!rs.vertexBuffer)
rs.vertexBuffer.emplace();
if (vertices.size() > rs.bufferCapacity) {
rs.bufferCapacity = vertices.size() + 64;
rs.vertexBuffer->allocate(
gl::buffer::ARRAY_BUFFER,
static_cast<int>(rs.bufferCapacity * sizeof(LineVertex)),
GL_DYNAMIC_DRAW);
rs.vao.reset();
}
rs.vertexBuffer->upload(
gl::buffer::ARRAY_BUFFER,
vertices.data(), 0,
static_cast<int>(vertices.size() * sizeof(LineVertex)));
if (!rs.vao) {
rs.vao.emplace();
constexpr int stride = sizeof(LineVertex);
rs.vao->setup_attrib(*rs.vertexBuffer, 0, 3, GL_FLOAT, stride, 0); // position
rs.vao->setup_attrib(*rs.vertexBuffer, 1, 3, GL_FLOAT, stride, 12); // color
rs.vao->unbind();
rs.vertexBuffer->unbind(gl::buffer::ARRAY_BUFFER);
}
if (!rs.shader) {
try {
gl::shader vert("vbocolor.vert");
gl::shader frag("ecs_line.frag");
rs.shader = std::unique_ptr<gl::program>(new gl::program({vert, frag}));
} catch (const gl::shader_exception& e) {
WriteLog("[ECS] LineSystem: shader compile failed: " + std::string(e.what()));
return;
}
}
rs.shader->bind();
rs.vao->bind();
glDrawArrays(GL_LINES, 0, static_cast<GLsizei>(vertices.size()));
rs.vao->unbind();
gl::program::unbind();
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include "BaseSystem.h"
// Renders Line components as GL_LINES using camera-relative world positions.
// Uses vbocolor.vert + ecs_line.frag.
class LineSystem : public BaseSystem
{
public:
LineSystem();
~LineSystem();
void Render(ECWorld& world) override;
private:
struct LineVertex {
glm::vec3 position; // camera-relative world position
glm::vec3 color;
};
struct RenderState;
std::unique_ptr<RenderState> m_renderState;
};

View File

@@ -0,0 +1,16 @@
#include "stdafx.h"
#include "MeshRenderSystem.h"
#include "entitysystem/ECWorld.h"
#include "entitysystem/components/BasicComponents.h"
#include "entitysystem/components/RenderComponents.h"
// gfx_renderer does not expose per-handle draw calls — geometry is drawn
// by the legacy pipeline (simulation::Region → GfxRenderer->Render()).
// MeshRenderer.visible is the authoritative visibility flag; LODSystem writes
// it and the legacy scene node reads it when the renderer traverses the scene.
// When a custom ECS draw path is wired into the renderer this system will
// issue the actual draw calls.
void MeshRenderSystem::Render(ECWorld& world)
{
}

View File

@@ -0,0 +1,9 @@
#pragma once
#include "BaseSystem.h"
class MeshRenderSystem : public BaseSystem
{
public:
void Render(ECWorld& world) override;
};

View File

@@ -13,6 +13,7 @@ void MovementSystem::Update(ECWorld& world, float dt)
constexpr double damping = 0.90; // 0.0 = instant stop, 1.0 = without damping
world.Each<ECSComponent::Transform, ECSComponent::Velocity>(
entt::exclude<ECSComponent::Disabled>,
[dt](auto entity, auto& transform, auto& velocity)
{
@@ -22,7 +23,7 @@ void MovementSystem::Update(ECWorld& world, float dt)
if (glm::length(velocity.Value) < 0.001)
{
velocity.Value = glm::dvec3(0.0);
velocity.Value = glm::vec3(0.0f);
}
}
);

View File

@@ -0,0 +1,196 @@
//
// Created by Daniu
//
#include "stdafx.h"
#include "ParticlesSystem.h"
#include "entitysystem/components/BasicComponents.h"
#include "entitysystem/components/RenderComponents.h"
#include "utilities/Globals.h"
#include "gl/buffer.h"
#include "gl/vao.h"
#include "gl/shader.h"
#include "utilities/Logs.h"
#include <algorithm>
#include <random>
// ---- OpenGL resource bundle (pimpl) ----------------------------------------
struct ParticlesSystem::RenderState
{
std::optional<gl::buffer> instanceBuffer;
std::optional<gl::vao> vao;
std::unique_ptr<gl::program> shader;
std::size_t bufferCapacity { 0 };
};
ParticlesSystem::ParticlesSystem() = default;
ParticlesSystem::~ParticlesSystem() = default;
// ---- Update ----------------------------------------------------------------
void ParticlesSystem::Update(ECWorld &world, float dt)
{
world.Each<ECSComponent::ParticleEmitter>(
entt::exclude<ECSComponent::Disabled>,
[&](entt::entity entity, ECSComponent::ParticleEmitter& emitter)
{
if (!emitter.isActive && emitter.particles.empty()) return;
for (size_t i = 0; i < emitter.particles.size(); )
{
auto& p = emitter.particles[i];
p.age += dt;
if (p.age >= p.maxAge || p.color.a <= 0.0f) {
p = emitter.particles.back();
emitter.particles.pop_back();
continue;
}
p.velocity += emitter.gravity * dt;
p.velocity *= (1.0f - emitter.airResistance * dt);
glm::vec3 nextPos = p.position + p.velocity * dt;
if (emitter.hasCollision && nextPos.y < 0.0f) {
p.velocity.y *= -emitter.bounceFactor;
p.velocity.x *= 0.8f;
nextPos.y = 0.01f;
}
p.position = nextPos;
p.size += emitter.sizeGrowth * dt;
p.color += emitter.colorFade * dt;
++i;
}
if (emitter.isActive) {
emitter.spawnAccumulator += dt;
float spawnInterval = 1.0f / emitter.spawnRate;
while (emitter.spawnAccumulator >= spawnInterval) {
if (emitter.particles.size() < emitter.maxParticles)
SpawnParticle(emitter);
emitter.spawnAccumulator -= spawnInterval;
}
}
});
}
void ParticlesSystem::SpawnParticle(ECSComponent::ParticleEmitter& emitter)
{
static thread_local std::mt19937 gen(std::random_device{}());
std::uniform_real_distribution<float> dist(0.0f, 1.0f);
ECSComponent::Particle p{};
p.position = emitter.emitterLocation;
p.velocity.x = emitter.minStartVelocity.x + dist(gen) * (emitter.maxStartVelocity.x - emitter.minStartVelocity.x);
p.velocity.y = emitter.minStartVelocity.y + dist(gen) * (emitter.maxStartVelocity.y - emitter.minStartVelocity.y);
p.velocity.z = emitter.minStartVelocity.z + dist(gen) * (emitter.maxStartVelocity.z - emitter.minStartVelocity.z);
p.color = glm::vec4(1.0f);
p.size = 0.1f;
p.age = 0.0f;
p.maxAge = emitter.particleLifetime;
emitter.particles.push_back(p);
}
// ---- Render ----------------------------------------------------------------
void ParticlesSystem::Render(ECWorld& world)
{
// Collect instance data for every live particle across all emitters
std::vector<GPUInstanceData> instances;
instances.reserve(4096);
const glm::vec3 camPos{
static_cast<float>(Global.pCamera.Pos.x),
static_cast<float>(Global.pCamera.Pos.y),
static_cast<float>(Global.pCamera.Pos.z)
};
world.Each<ECSComponent::ParticleEmitter>(
entt::exclude<ECSComponent::Disabled>,
[&](entt::entity, ECSComponent::ParticleEmitter& emitter)
{
for (const auto& p : emitter.particles) {
if (p.color.a <= 0.0f) continue;
instances.push_back({
p.position - camPos, // camera-relative world position
std::max(p.size, 0.01f),
p.color
});
}
});
if (instances.empty()) return;
// Lazy-init OpenGL resources
if (!m_renderState)
m_renderState = std::make_unique<RenderState>();
auto& rs = *m_renderState;
// Grow VBO if needed
if (!rs.instanceBuffer)
rs.instanceBuffer.emplace();
if (instances.size() > rs.bufferCapacity) {
rs.bufferCapacity = instances.size() + 1024; // headroom
rs.instanceBuffer->allocate(
gl::buffer::ARRAY_BUFFER,
static_cast<int>(rs.bufferCapacity * sizeof(GPUInstanceData)),
GL_DYNAMIC_DRAW);
// VAO needs rebuild after buffer reallocation
rs.vao.reset();
}
rs.instanceBuffer->upload(
gl::buffer::ARRAY_BUFFER,
instances.data(), 0,
static_cast<int>(instances.size() * sizeof(GPUInstanceData)));
// Build VAO once (or after buffer realloc)
if (!rs.vao) {
rs.vao.emplace();
constexpr int stride = sizeof(GPUInstanceData);
rs.vao->setup_attrib(*rs.instanceBuffer, 0, 3, GL_FLOAT, stride, 0); // position
glVertexAttribDivisor(0, 1);
rs.vao->setup_attrib(*rs.instanceBuffer, 1, 1, GL_FLOAT, stride, 12); // size
glVertexAttribDivisor(1, 1);
rs.vao->setup_attrib(*rs.instanceBuffer, 2, 4, GL_FLOAT, stride, 16); // color
glVertexAttribDivisor(2, 1);
rs.vao->unbind();
rs.instanceBuffer->unbind(gl::buffer::ARRAY_BUFFER);
}
// Load shader once
if (!rs.shader) {
try {
gl::shader vert("ecs_particle.vert");
gl::shader frag("ecs_particle.frag");
rs.shader = std::unique_ptr<gl::program>(new gl::program({vert, frag}));
} catch (const gl::shader_exception& e) {
WriteLog("[ECS] ParticlesSystem: shader compile failed: " + std::string(e.what()));
return;
}
}
// Render
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthMask(GL_FALSE);
rs.shader->bind();
rs.vao->bind();
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, static_cast<GLsizei>(instances.size()));
rs.vao->unbind();
gl::program::unbind();
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
}

View File

@@ -0,0 +1,35 @@
//
// Created by Daniu
//
#ifndef EU07_PARTICLESSYSTEM_H
#define EU07_PARTICLESSYSTEM_H
#include "BaseSystem.h"
#include "entitysystem/ECWorld.h"
#include "entitysystem/components/RenderComponents.h"
class ParticlesSystem : public BaseSystem
{
public:
ParticlesSystem();
~ParticlesSystem();
void Update(ECWorld& world, float dt) override;
void Render(ECWorld& world) override;
private:
void SpawnParticle(ECSComponent::ParticleEmitter& emitter);
// Per-instance data uploaded to GPU (one entry per live particle)
struct GPUInstanceData {
glm::vec3 position; // camera-relative world position
float size;
glm::vec4 color;
};
// OpenGL resources — allocated lazily on first Render() call
struct RenderState;
std::unique_ptr<RenderState> m_renderState;
};
#endif // EU07_PARTICLESSYSTEM_H

View File

@@ -0,0 +1,26 @@
#include "stdafx.h"
#include "SoundSystem.h"
#include "entitysystem/ECWorld.h"
#include "entitysystem/components/BasicComponents.h"
void SoundSystem::Update(ECWorld& world, float dt)
{
world.Each<ECSComponent::SoundComponent, ECSComponent::Transform>(
entt::exclude<ECSComponent::Disabled>,
[](entt::entity,
ECSComponent::SoundComponent& sc,
ECSComponent::Transform& transform)
{
sc.sound.offset( glm::vec3( transform.Position ) );
sc.sound.gain( sc.volume );
sc.sound.pitch( sc.pitch );
if (sc.isPlaying && !sc.sound.is_playing()) {
int flags = sc.loop ? sound_flags::looping : 0;
sc.sound.play( flags );
} else if (!sc.isPlaying && sc.sound.is_playing()) {
sc.sound.stop();
}
});
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "BaseSystem.h"
// Drives SoundComponent: starts/stops sound_source playback and keeps
// position, volume and pitch in sync with the entity's Transform.
class SoundSystem : public BaseSystem
{
public:
void Update(ECWorld& world, float dt) override;
};

View File

@@ -27,7 +27,12 @@ void SystemManager::Destroy(ECWorld& world)
void SystemManager::Update(ECWorld& world, float dt)
{
for (auto& system : m_systems)
system->Update(world, dt);
}
void SystemManager::Render(ECWorld& world)
{
for (auto& system : m_systems)
system->Render(world);
}

View File

@@ -36,6 +36,7 @@ public:
void Destroy(ECWorld& world);
void Update(ECWorld& world, float dt);
void Render(ECWorld& world);
private:
std::vector<std::unique_ptr<BaseSystem>> m_systems;

View File

@@ -0,0 +1,30 @@
#include "stdafx.h"
#include "VehicleSyncSystem.h"
#include "entitysystem/ECWorld.h"
#include "entitysystem/components/BasicComponents.h"
#include "vehicle/DynObj.h"
void VehicleSyncSystem::Update(ECWorld& world, float dt)
{
world.Each<ECSComponent::VehicleRef, ECSComponent::Transform>(
entt::exclude<ECSComponent::Disabled>,
[](entt::entity,
ECSComponent::VehicleRef& ref,
ECSComponent::Transform& transform)
{
if (!ref.vehicle) return;
auto const &pos = ref.vehicle->GetPosition();
transform.Position = glm::dvec3(pos.x, pos.y, pos.z);
// Extract rotation from the vehicle's render matrix (column vectors = basis)
auto const *m = ref.vehicle->mMatrix.getArray();
// mMatrix is column-major 4x4; extract upper-left 3x3 into glm
glm::mat3 rot(
m[0], m[1], m[2],
m[4], m[5], m[6],
m[8], m[9], m[10]);
transform.Rotation = glm::quat_cast(rot);
});
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "BaseSystem.h"
// Keeps ECS Transform in sync with the vehicle's actual world position each frame.
// Must run before all other systems that read Transform (e.g. LOD, Sound, Hierarchy).
class VehicleSyncSystem : public BaseSystem
{
public:
void Update(ECWorld& world, float dt) override;
};