#define VC_ENABLE_REFLECTION #include "Entities.hpp" #include #include #include "assets/Assets.hpp" #include "content/Content.hpp" #include "data/dv_util.hpp" #include "debug/Logger.hpp" #include "engine/Engine.hpp" #include "graphics/core/DrawContext.hpp" #include "graphics/core/LineBatch.hpp" #include "graphics/commons/Model.hpp" #include "graphics/render/ModelBatch.hpp" #include "logic/scripting/scripting.hpp" #include "maths/FrustumCulling.hpp" #include "maths/rays.hpp" #include "EntityDef.hpp" #include "Entity.hpp" #include "rigging.hpp" #include "physics/PhysicsSolver.hpp" #include "world/Level.hpp" static debug::Logger logger("entities"); Entities::Entities(Level& level) : level(level), sensorsTickClock(20, 3), updateTickClock(20, 3) { } std::optional Entities::get(entityid_t id) { const auto& found = entities.find(id); if (found != entities.end() && registry.valid(found->second)) { return Entity(*this, id, registry, found->second); } return std::nullopt; } entityid_t Entities::spawn( const EntityDef& def, glm::vec3 position, dv::value args, dv::value saved, entityid_t uid ) { auto skeleton = level.content.getSkeleton(def.skeletonName); if (skeleton == nullptr) { throw std::runtime_error("skeleton " + def.skeletonName + " not found"); } entityid_t id; if (uid == 0) { id = nextID++; } else { id = uid; if (auto found = get(uid)) { std::stringstream ss; ss << "UID #" << uid << " is already used by an entity "; ss << found->getDef().name; if (found->getID().destroyFlag) { ss << " marked to destroy"; } throw std::runtime_error(ss.str()); } } auto entity = registry.create(); entities[id] = entity; uids[entity] = id; registry.emplace(entity, static_cast(id), def); const auto& tsf = registry.emplace( entity, position, glm::vec3(1.0f), glm::mat3(1.0f), glm::mat4(1.0f), true ); auto& body = registry.emplace( entity, true, Hitbox {def.bodyType, position, def.hitbox * 0.5f}, std::vector {} ); body.initialize(def, id, *this); auto& scripting = registry.emplace(entity); registry.emplace(entity, skeleton->instance()); for (auto& instance : def.components) { auto component = std::make_unique( instance.component, EntityFuncsSet {}, nullptr, instance.params ); scripting.components.emplace_back(std::move(component)); } dv::value componentsMap = nullptr; if (saved != nullptr) { componentsMap = saved["comps"]; loadEntity(saved, get(id).value()); } body.hitbox.position = tsf.pos; scripting::on_entity_spawn( def, id, scripting.components, args, componentsMap ); return id; } void Entities::despawn(entityid_t id) { if (auto entity = get(id)) { auto& eid = entity->getID(); if (!eid.destroyFlag) { eid.destroyFlag = true; scripting::on_entity_despawn(*entity); } } } void Entities::loadEntity(const dv::value& map) { entityid_t uid = map["uid"].asInteger(); std::string defname = map["def"].asString(); auto& def = level.content.entities.require(defname); spawn(def, {}, nullptr, map, uid); } void Entities::loadEntity(const dv::value& map, Entity entity) { auto& transform = entity.getTransform(); auto& body = entity.getRigidbody(); auto& skeleton = entity.getSkeleton(); if (map.has(COMP_RIGIDBODY)) { body.deserialize(map[COMP_RIGIDBODY]); } if (map.has(COMP_TRANSFORM)) { transform.deserialize(map[COMP_TRANSFORM]); } std::string skeletonName = skeleton.config->getName(); map.at("skeleton").get(skeletonName); if (skeletonName != skeleton.config->getName()) { skeleton.config = level.content.getSkeleton(skeletonName); } if (auto foundSkeleton = map.at(COMP_SKELETON)) { skeleton.deserialize(*foundSkeleton); } } std::optional Entities::rayCast( glm::vec3 start, glm::vec3 dir, float maxDistance, entityid_t ignore ) { Ray ray(start, dir); auto view = registry.view(); entityid_t foundUID = 0; glm::ivec3 foundNormal; for (auto [entity, eid, transform, body] : view.each()) { if (eid.uid == ignore || !body.enabled) { continue; } auto& hitbox = body.hitbox; glm::ivec3 normal; double distance; if (ray.intersectAABB( glm::vec3(), hitbox.getAABB(), maxDistance, normal, distance ) > RayRelation::None) { foundUID = eid.uid; foundNormal = normal; maxDistance = static_cast(distance); } } if (foundUID) { return Entities::RaycastResult {foundUID, foundNormal, maxDistance}; } else { return std::nullopt; } } void Entities::loadEntities(dv::value root) { clean(); const auto& list = root["data"]; for (auto& map : list) { try { loadEntity(map); } catch (const std::runtime_error& err) { logger.error() << "could not read entity: " << err.what(); } } } void Entities::onSave(const Entity& entity) { scripting::on_entity_save(entity); } dv::value Entities::serialize(const std::vector& entities) { auto list = dv::list(); for (auto& entity : entities) { const EntityId& eid = entity.getID(); if (!entity.getDef().save.enabled || eid.destroyFlag) { continue; } level.entities->onSave(entity); if (!eid.destroyFlag) { list.add(entity.serialize()); } } return list; } void Entities::despawn(std::vector entities) { for (auto& entity : entities) { entity.destroy(); } } void Entities::clean() { for (auto it = entities.begin(); it != entities.end();) { if (!registry.get(it->second).destroyFlag) { ++it; } else { auto& rigidbody = registry.get(it->second); // todo: refactor auto physics = level.physics.get(); for (auto& sensor : rigidbody.sensors) { physics->removeSensor(&sensor); } uids.erase(it->second); registry.destroy(it->second); it = entities.erase(it); } } } void Entities::updateSensors( Rigidbody& body, const Transform& tsf, std::vector& sensors ) { for (size_t i = 0; i < body.sensors.size(); i++) { auto& sensor = body.sensors[i]; for (auto oid : sensor.prevEntered) { if (sensor.nextEntered.find(oid) == sensor.nextEntered.end()) { sensor.exitCallback(sensor.entity, i, oid); } } sensor.prevEntered = sensor.nextEntered; sensor.nextEntered.clear(); switch (sensor.type) { case SensorType::AABB: sensor.calculated.aabb = sensor.params.aabb; sensor.calculated.aabb.transform(tsf.combined); break; case SensorType::RADIUS: sensor.calculated.radial = glm::vec4( body.hitbox.position.x, body.hitbox.position.y, body.hitbox.position.z, sensor.params.radial.w * sensor.params.radial.w ); break; } sensors.push_back(&sensor); } } void Entities::preparePhysics(float delta) { if (sensorsTickClock.update(delta)) { auto part = sensorsTickClock.getPart(); auto parts = sensorsTickClock.getParts(); auto view = registry.view(); auto physics = level.physics.get(); std::vector sensors; for (auto [entity, eid, transform, rigidbody] : view.each()) { if (!rigidbody.enabled) { continue; } if ((eid.uid + part) % parts != 0) { continue; } updateSensors(rigidbody, transform, sensors); } physics->setSensors(std::move(sensors)); } } void Entities::updatePhysics(float delta) { preparePhysics(delta); auto view = registry.view(); auto physics = level.physics.get(); for (auto [entity, eid, transform, rigidbody] : view.each()) { if (!rigidbody.enabled || rigidbody.hitbox.type == BodyType::STATIC) { continue; } auto& hitbox = rigidbody.hitbox; auto prevVel = hitbox.velocity; bool grounded = hitbox.grounded; float vel = glm::length(prevVel); int substeps = static_cast(delta * vel * 20); substeps = std::min(100, std::max(2, substeps)); physics->step(*level.chunks, hitbox, delta, substeps, eid.uid); hitbox.friction = glm::abs(hitbox.gravityScale <= 1e-7f) ? 8.0f : (!grounded ? 2.0f : 10.0f); transform.setPos(hitbox.position); if (hitbox.grounded && !grounded) { scripting::on_entity_grounded( *get(eid.uid), glm::length(prevVel - hitbox.velocity) ); } if (!hitbox.grounded && grounded) { scripting::on_entity_fall(*get(eid.uid)); } } } void Entities::update(float delta) { if (updateTickClock.update(delta)) { scripting::on_entities_update( updateTickClock.getTickRate(), updateTickClock.getParts(), updateTickClock.getPart() ); } updatePhysics(delta); scripting::on_entities_physics_update(delta); } static void debug_render_skeleton( LineBatch& batch, const rigging::Bone* bone, const rigging::Skeleton& skeleton ) { size_t pindex = bone->getIndex(); for (auto& sub : bone->getSubnodes()) { size_t sindex = sub->getIndex(); const auto& matrices = skeleton.calculated.matrices; batch.line( glm::vec3(matrices[pindex] * glm::vec4(0, 0, 0, 1)), glm::vec3(matrices[sindex] * glm::vec4(0, 0, 0, 1)), glm::vec4(0, 0.5f, 0, 1) ); debug_render_skeleton(batch, sub.get(), skeleton); } } void Entities::renderDebug( LineBatch& batch, const Frustum* frustum, const DrawContext& pctx ) { { auto ctx = pctx.sub(&batch); ctx.setLineWidth(1); auto view = registry.view(); for (auto [entity, transform, rigidbody] : view.each()) { const auto& hitbox = rigidbody.hitbox; const auto& pos = transform.pos; const auto& size = transform.size; if (frustum && !frustum->isBoxVisible(pos - size, pos + size)) { continue; } batch.box(hitbox.position, hitbox.halfsize * 2.0f, glm::vec4(1.0f)); for (auto& sensor : rigidbody.sensors) { if (sensor.type != SensorType::AABB) continue; batch.box( sensor.calculated.aabb.center(), sensor.calculated.aabb.size(), glm::vec4(1.0f, 1.0f, 0.0f, 1.0f) ); } } } { auto view = registry.view(); auto ctx = pctx.sub(&batch); ctx.setDepthTest(false); ctx.setDepthMask(false); ctx.setLineWidth(2); for (auto [entity, transform, skeleton] : view.each()) { auto config = skeleton.config; const auto& pos = transform.pos; const auto& size = transform.size; if (frustum && !frustum->isBoxVisible(pos - size, pos + size)) { continue; } auto bone = config->getRoot(); debug_render_skeleton(batch, bone, skeleton); } } } void Entities::render( const Assets& assets, ModelBatch& batch, const Frustum* frustum, float delta, bool pause, entityid_t fpsEntity ) { auto view = registry.view(); for (auto [entity, eid, transform, skeleton] : view.each()) { if (eid.uid == fpsEntity) { continue; } if (transform.dirty) { transform.refresh(); } if (skeleton.interpolation.isEnabled()) { skeleton.interpolation.updateTimer(delta); } const auto& pos = transform.pos; const auto& size = transform.size; if (!frustum || frustum->isBoxVisible(pos - size, pos + size)) { const auto* rigConfig = skeleton.config; rigConfig->render(assets, batch, skeleton, transform.rot, pos); } } } bool Entities::hasBlockingInside(AABB aabb) { auto view = registry.view(); for (auto [entity, eid, body] : view.each()) { if (eid.def.blocking && aabb.intersect(body.hitbox.getAABB(), -0.05f)) { return true; } } return false; } std::vector Entities::getAllInside(AABB aabb) { std::vector collected; auto view = registry.view(); for (auto [entity, eid, transform] : view.each()) { if (!eid.destroyFlag && aabb.contains(transform.pos)) { const auto& found = uids.find(entity); if (found == uids.end()) { continue; } if (auto wrapper = get(found->second)) { collected.push_back(*wrapper); } } } return collected; } std::vector Entities::getAllInRadius(glm::vec3 center, float radius) { std::vector collected; auto view = registry.view(); for (auto [entity, transform] : view.each()) { if (glm::distance2(transform.pos, center) <= radius * radius) { const auto& found = uids.find(entity); if (found == uids.end()) { continue; } if (auto wrapper = get(found->second)) { collected.push_back(*wrapper); } } } return collected; }