#include "camera/CameraController.h" #include "common/CameraObstruction.h" #include "common/Capability.h" #include "common/Json.h" #include "graphics/RenderSystem3D.h" #include "platform_event/PlatformEvent.h" #include "scene/SceneNodeRef.h" #include #include #include #include #include namespace eve::camera { namespace { std::string jsonString(const std::string& value) { std::ostringstream out; out << '"'; for (const unsigned char c : value) { switch (c) { case '"': out << "\\\""; break; case '\\': out << "\\\\"; break; case '\b': out << "\\b"; break; case '\f': out << "\\f"; break; case '\n': out << "\\n"; break; case '\r': out << "\\r"; break; case '\t': out << "\\t"; break; default: if (c < 0x20) out << "\\u00" << "0123456789abcdef"[c >> 4] << "0123456789abcdef"[c & 0xf]; else out << static_cast(c); } } out << '"'; return out.str(); } glm::vec3 jsonVec3(eve::json::Value value, glm::vec3 fallback) { if (!value.isArray() || value.size() != 3) return fallback; return {value.at(0).asFloat(fallback.x), value.at(1).asFloat(fallback.y), value.at(2).asFloat(fallback.z)}; } } // namespace Module_IMPL(Camera, new Camera()); CameraController::CameraController() = default; void CameraController::setCamera(graphics::Camera3D* cam) { cam_ = cam; } graphics::Camera3D* CameraController::getCamera() const { return cam_; } void CameraController::setTarget(float x, float y, float z) { targetNodeHost_.clear(); targetNodeId_.clear(); target_ = glm::vec3(x, y, z); } void CameraController::setTargetNode(scene::SceneNodeRef* node) { targetNodeHost_ = node ? node->getHostName() : std::string(); targetNodeId_ = node ? node->getNodeId() : std::string(); } float CameraController::getTargetX() const { return target_.x; } float CameraController::getTargetY() const { return target_.y; } float CameraController::getTargetZ() const { return target_.z; } void CameraController::setOffset(float x, float y, float z) { offset_ = glm::vec3(x, y, z); } void CameraController::setLookAhead(float x, float y, float z) { lookAhead_ = glm::vec3(x, y, z); } bool CameraController::validMode(const std::string& mode) { return mode == "follow" || mode == "orbit" || mode == "topdown" || mode == "firstperson" || mode == "cinematic"; } void CameraController::setMode(const std::string& mode) { if (validMode(mode)) mode_ = mode; } std::string CameraController::getMode() const { return mode_; } void CameraController::setRadius(float r) { radius_ = std::max(0.01f, r); } void CameraController::setAzimuth(float deg) { azimuthDeg_ = deg; } void CameraController::setElevation(float deg) { elevationDeg_ = glm::clamp(deg, -89.f, 89.f); } void CameraController::setOrbitSpeed(float degPerSec) { orbitSpeedDeg_ = degPerSec; } void CameraController::setYaw(float deg) { yawDeg_ = deg; } void CameraController::setPitch(float deg) { pitchDeg_ = glm::clamp(deg, -89.f, 89.f); } void CameraController::addInput(float yawDeltaDeg, float pitchDeltaDeg, float zoomDelta) { yawDeg_ += yawDeltaDeg; azimuthDeg_ += yawDeltaDeg; setPitch(pitchDeg_ + pitchDeltaDeg); setElevation(elevationDeg_ + pitchDeltaDeg); setRadius(radius_ + zoomDelta); } void CameraController::setComposition(float screenX, float screenY) { composition_ = glm::clamp(glm::vec2(screenX, screenY), glm::vec2(-0.9f), glm::vec2(0.9f)); } void CameraController::setDeadZone(float radius) { deadZone_ = std::max(0.f, radius); } void CameraController::setFov(float degrees) { fovDeg_ = glm::clamp(degrees, 1.f, 179.f); } float CameraController::getFov() const { return fovDeg_; } void CameraController::setSmooth(float damping) { smooth_ = std::max(0.f, damping); positionSmooth_ = smooth_; targetSmooth_ = smooth_; } void CameraController::setPositionSmooth(float damping) { positionSmooth_ = std::max(0.f, damping); } void CameraController::setTargetSmooth(float damping) { targetSmooth_ = std::max(0.f, damping); } void CameraController::setMaxSpeed(float unitsPerSec) { maxSpeed_ = std::max(0.f, unitsPerSec); } void CameraController::setCollisionEnabled(bool enabled) { collisionEnabled_ = enabled; } void CameraController::setCollisionRadius(float radius) { collisionRadius_ = std::max(0.f, radius); } void CameraController::setCollisionRecovery(float damping) { collisionRecovery_ = std::max(0.f, damping); } void CameraController::setCollisionMask(uint64_t maskBits) { collisionMask_ = maskBits; } void CameraController::setCollisionIgnoredBody(int bodyId) { collisionIgnoredBody_ = bodyId; } void CameraController::clearCollisionBoxes() { collisionBoxes_.clear(); } void CameraController::addCollisionBox(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) { CollisionBox b; b.min = glm::min(glm::vec3(minX, minY, minZ), glm::vec3(maxX, maxY, maxZ)); b.max = glm::max(glm::vec3(minX, minY, minZ), glm::vec3(maxX, maxY, maxZ)); collisionBoxes_.push_back(b); } bool CameraController::isObstructed() const { return obstructed_; } int CameraController::getCollisionBodyId() const { return collisionBodyId_; } bool CameraController::addRig(const std::string& name, const std::string& mode, int priority) { if (name.empty() || !validMode(mode)) return false; for (const auto& rig : rigs_) if (rig.name == name) return false; Rig rig; rig.name = name; rig.mode = mode; rig.priority = priority; rigs_.push_back(rig); saveRigState(name); directorEnabled_ = true; return true; } bool CameraController::removeRig(const std::string& name) { auto it = std::find_if(rigs_.begin(), rigs_.end(), [&](const Rig& r) { return r.name == name; }); if (it == rigs_.end()) return false; rigs_.erase(it); if (activeRig_ == name) activeRig_.clear(); return true; } bool CameraController::setRigPriority(const std::string& name, int priority) { for (auto& rig : rigs_) if (rig.name == name) { rig.priority = priority; return true; } return false; } bool CameraController::setRigEnabled(const std::string& name, bool enabled) { for (auto& rig : rigs_) if (rig.name == name) { rig.enabled = enabled; return true; } return false; } bool CameraController::saveRigState(const std::string& name) { for (auto& rig : rigs_) { if (rig.name != name) continue; rig.target = target_; rig.offset = offset_; rig.lookAhead = lookAhead_; rig.composition = composition_; rig.radius = radius_; rig.azimuth = azimuthDeg_; rig.elevation = elevationDeg_; rig.yaw = yawDeg_; rig.pitch = pitchDeg_; rig.fov = fovDeg_; rig.smooth = smooth_; rig.maxSpeed = maxSpeed_; return true; } return false; } bool CameraController::activateRig(const std::string& name, float blendTime) { auto it = std::find_if(rigs_.begin(), rigs_.end(), [&](const Rig& r) { return r.name == name && r.enabled; }); if (it == rigs_.end()) return false; const View from = initialized_ ? cur_ : desired(); activeRig_ = name; mode_ = it->mode; target_ = it->target; offset_ = it->offset; lookAhead_ = it->lookAhead; composition_ = it->composition; radius_ = it->radius; azimuthDeg_ = it->azimuth; elevationDeg_ = it->elevation; yawDeg_ = it->yaw; pitchDeg_ = it->pitch; fovDeg_ = it->fov; setSmooth(it->smooth); maxSpeed_ = it->maxSpeed; const bool wasBlending = blending_; blending_ = false; const View to = desired(); blending_ = wasBlending; if (blendTime > 0.f) { blendFrom_ = from; blendTo_ = to; blendDur_ = blendTime; blendT_ = 0.f; blending_ = true; } else { blending_ = false; } return true; } std::string CameraController::getActiveRig() const { return activeRig_; } void CameraController::addImpulse(float positionAmplitude, float rotationAmplitude, float duration, unsigned int seed) { if (duration <= 0.f || (positionAmplitude <= 0.f && rotationAmplitude <= 0.f)) return; impulses_.push_back({std::max(0.f, positionAmplitude), std::max(0.f, rotationAmplitude), duration, 0.f, seed, 0.f}); } void CameraController::addFovImpulse(float degrees, float duration) { if (duration <= 0.f || degrees == 0.f) return; impulses_.push_back({0.f, 0.f, duration, 0.f, 0u, degrees}); } void CameraController::clearImpulses() { impulses_.clear(); } void CameraController::addView(const std::string& name, float ex, float ey, float ez, float tx, float ty, float tz) { View v; v.name = name; v.eye = glm::vec3(ex, ey, ez); v.target = glm::vec3(tx, ty, tz); v.fov = fovDeg_; views_.push_back(v); } bool CameraController::switchTo(const std::string& name, float blendTime) { int idx = -1; for (int i = 0; i < static_cast(views_.size()); ++i) { if (views_[i].name == name) { idx = i; break; } } if (idx < 0) return false; if (viewIndex_ >= 0 && viewIndex_ < static_cast(views_.size())) { blendFrom_ = views_[viewIndex_]; } else { blendFrom_ = cur_; } blendTo_ = views_[idx]; viewIndex_ = idx; if (blendTime > 0.f) { blending_ = true; blendDur_ = blendTime; blendT_ = 0.f; } else { blending_ = false; blendDur_ = 1.f; blendT_ = 0.f; } return true; } void CameraController::playSequence(float stepTime) { playing_ = true; stepTime_ = stepTime > 0.f ? stepTime : 1.f; timer_ = 0.f; if (!blending_ && views_.empty()) return; if (viewIndex_ < 0 || viewIndex_ >= static_cast(views_.size())) { switchTo(views_.front().name, 0.f); } } void CameraController::stopSequence() { playing_ = false; } bool CameraController::isPlaying() const { return playing_; } void CameraController::clearTimeline() { timelineCuts_.clear(); timelineEvents_.clear(); timelineFloats_.clear(); pendingTimelineEvents_.clear(); timelineDuration_ = 0.f; stopTimeline(); } bool CameraController::addTimelineFloat(float time, const std::string& property, float value) { if (time < 0.f || (property != "fov" && property != "radius" && property != "smooth" && property != "compositionX" && property != "compositionY")) return false; timelineFloats_.push_back({time, property, value}); std::stable_sort(timelineFloats_.begin(), timelineFloats_.end(), [](const auto& a, const auto& b) { return a.time < b.time; }); timelineDuration_ = std::max(timelineDuration_, time); return true; } bool CameraController::addTimelineCut(float time, const std::string& rigName, float blendTime) { if (time < 0.f || blendTime < 0.f) return false; const bool known = std::any_of(rigs_.begin(), rigs_.end(), [&](const Rig& r) { return r.name == rigName; }); if (!known) return false; timelineCuts_.push_back({time, rigName, blendTime, false}); std::stable_sort(timelineCuts_.begin(), timelineCuts_.end(), [](const TimelineCut& a, const TimelineCut& b) { return a.time < b.time; }); timelineDuration_ = std::max(timelineDuration_, time); return true; } bool CameraController::addTimelineEvent(float time, const std::string& name, const std::string& data) { if (time < 0.f || name.empty()) return false; timelineEvents_.push_back({time, name, data, false}); std::stable_sort(timelineEvents_.begin(), timelineEvents_.end(), [](const TimelineEvent& a, const TimelineEvent& b) { return a.time < b.time; }); timelineDuration_ = std::max(timelineDuration_, time); return true; } void CameraController::setEventSink(platform_event::PlatformEvent* sink) { eventSink_ = sink; } void CameraController::playTimeline(bool loop) { timelineLoop_ = loop; timelinePlaying_ = !timelineCuts_.empty() || !timelineEvents_.empty(); } void CameraController::pauseTimeline() { timelinePlaying_ = false; } void CameraController::stopTimeline() { timelinePlaying_ = false; timelineTime_ = 0.f; for (auto& cut : timelineCuts_) cut.fired = false; for (auto& event : timelineEvents_) event.fired = false; } void CameraController::seekTimeline(float time, bool fireEvents) { timelineTime_ = glm::clamp(time, 0.f, std::max(0.f, timelineDuration_)); for (auto& cut : timelineCuts_) cut.fired = cut.time <= timelineTime_; const TimelineCut* latest = nullptr; for (const auto& cut : timelineCuts_) if (cut.time <= timelineTime_) latest = &cut; if (latest) activateRig(latest->rig, 0.f); for (auto& marker : timelineEvents_) { marker.fired = marker.time <= timelineTime_; if (fireEvents && marker.fired) emitTimelineEvent(marker); } evaluateTimelineFloats(); } bool CameraController::isTimelinePlaying() const { return timelinePlaying_; } float CameraController::getTimelineTime() const { return timelineTime_; } std::string CameraController::consumeTimelineEvent() { if (pendingTimelineEvents_.empty()) return {}; auto [out, data] = std::move(pendingTimelineEvents_.front()); pendingTimelineEvents_.pop_front(); consumedTimelineEventData_ = std::move(data); return out; } std::string CameraController::getTimelineEventData() const { return consumedTimelineEventData_; } int CameraController::getPendingTimelineEventCount() const { return static_cast(pendingTimelineEvents_.size()); } float CameraController::getTimelineDuration() const { return timelineDuration_; } int CameraController::getRigCount() const { return static_cast(rigs_.size()); } std::string CameraController::serializeAsset() const { std::ostringstream out; out << "{\"version\":1,\"rigs\":["; bool first = true; for (const auto& rig : rigs_) { if (!first) out << ','; first = false; out << "{\"name\":" << jsonString(rig.name) << ",\"mode\":" << jsonString(rig.mode) << ",\"priority\":" << rig.priority << ",\"enabled\":" << (rig.enabled ? "true" : "false") << ",\"target\":[" << rig.target.x << ',' << rig.target.y << ',' << rig.target.z << "],\"offset\":[" << rig.offset.x << ',' << rig.offset.y << ',' << rig.offset.z << "],\"lookAhead\":[" << rig.lookAhead.x << ',' << rig.lookAhead.y << ',' << rig.lookAhead.z << "],\"composition\":[" << rig.composition.x << ',' << rig.composition.y << "]" << ",\"radius\":" << rig.radius << ",\"azimuth\":" << rig.azimuth << ",\"elevation\":" << rig.elevation << ",\"yaw\":" << rig.yaw << ",\"pitch\":" << rig.pitch << ",\"fov\":" << rig.fov << ",\"smooth\":" << rig.smooth << ",\"maxSpeed\":" << rig.maxSpeed << '}'; } out << "],\"cuts\":["; first = true; for (const auto& cut : timelineCuts_) { if (!first) out << ','; first = false; out << "{\"time\":" << cut.time << ",\"rig\":" << jsonString(cut.rig) << ",\"blend\":" << cut.blend << '}'; } out << "],\"events\":["; first = true; for (const auto& marker : timelineEvents_) { if (!first) out << ','; first = false; out << "{\"time\":" << marker.time << ",\"name\":" << jsonString(marker.name) << ",\"data\":" << jsonString(marker.data) << '}'; } out << "],\"floats\":["; first = true; for (const auto& key : timelineFloats_) { if (!first) out << ','; first = false; out << "{\"time\":" << key.time << ",\"property\":" << jsonString(key.property) << ",\"value\":" << key.value << '}'; } out << "]}"; return out.str(); } bool CameraController::deserializeAsset(const std::string& text) { const auto document = eve::json::Document::parse(text); const auto root = document.root(); if (!root.isObject() || root.getInt("version", 0) != 1) return false; std::vector newRigs; const auto rigs = root.get("rigs"); if (rigs && !rigs.isArray()) return false; for (size_t i = 0; i < rigs.size(); ++i) { const auto item = rigs.at(i); if (!item.isObject()) return false; Rig rig; rig.name = item.getString("name"); rig.mode = item.getString("mode"); if (rig.name.empty() || !validMode(rig.mode)) return false; rig.priority = item.getInt("priority"); rig.enabled = item.getBool("enabled", true); rig.target = jsonVec3(item.get("target"), rig.target); rig.offset = jsonVec3(item.get("offset"), rig.offset); rig.lookAhead = jsonVec3(item.get("lookAhead"), rig.lookAhead); const auto composition = item.get("composition"); if (composition.isArray() && composition.size() == 2) rig.composition = {composition.at(0).asFloat(), composition.at(1).asFloat()}; rig.radius = item.getFloat("radius", rig.radius); rig.azimuth = item.getFloat("azimuth", rig.azimuth); rig.elevation = item.getFloat("elevation", rig.elevation); rig.yaw = item.getFloat("yaw", rig.yaw); rig.pitch = item.getFloat("pitch", rig.pitch); rig.fov = item.getFloat("fov", rig.fov); rig.smooth = item.getFloat("smooth", rig.smooth); rig.maxSpeed = item.getFloat("maxSpeed", rig.maxSpeed); newRigs.push_back(std::move(rig)); } clearTimeline(); rigs_ = std::move(newRigs); activeRig_.clear(); directorEnabled_ = !rigs_.empty(); const auto cuts = root.get("cuts"); if (cuts && !cuts.isArray()) return false; for (size_t i = 0; i < cuts.size(); ++i) { const auto item = cuts.at(i); if (!item.isObject() || !addTimelineCut(item.getFloat("time", -1.f), item.getString("rig"), item.getFloat("blend", 0.f))) return false; } const auto events = root.get("events"); if (events && !events.isArray()) return false; for (size_t i = 0; i < events.size(); ++i) { const auto item = events.at(i); if (!item.isObject() || !addTimelineEvent(item.getFloat("time", -1.f), item.getString("name"), item.getString("data"))) return false; } const auto floats = root.get("floats"); if (floats && !floats.isArray()) return false; for (size_t i = 0; i < floats.size(); ++i) { const auto item = floats.at(i); if (!item.isObject() || !addTimelineFloat(item.getFloat("time", -1.f), item.getString("property"), item.getFloat("value"))) return false; } return true; } void CameraController::emitTimelineEvent(const TimelineEvent& marker) { pendingTimelineEvents_.emplace_back(marker.name, marker.data); if (eventSink_) eventSink_->pushData(marker.name, marker.data); } void CameraController::evaluateTimelineFloats() { for (const std::string property : {"fov", "radius", "smooth", "compositionX", "compositionY"}) { const TimelineFloat* before = nullptr; const TimelineFloat* after = nullptr; for (const auto& key : timelineFloats_) { if (key.property != property) continue; if (key.time <= timelineTime_) before = &key; if (key.time >= timelineTime_) { after = &key; break; } } if (!before && !after) continue; if (!before) before = after; if (!after) after = before; const float span = after->time - before->time; const float t = span > 1e-6f ? (timelineTime_ - before->time) / span : 0.f; const float value = glm::mix(before->value, after->value, glm::clamp(t, 0.f, 1.f)); if (property == "fov") setFov(value); else if (property == "radius") setRadius(value); else if (property == "smooth") setSmooth(value); else if (property == "compositionX") setComposition(value, composition_.y); else setComposition(composition_.x, value); } } void CameraController::updateTrackedTarget() { if (targetNodeHost_.empty() || targetNodeId_.empty()) return; const scene::SceneNodeRef node(targetNodeHost_, targetNodeId_); if (!node.isValid()) return; const glm::vec3 observed(node.getWorldPositionX(), node.getWorldPositionY(), node.getWorldPositionZ()); if (!deadZoneInitialized_ || deadZone_ <= 0.f) { deadZoneTarget_ = observed; deadZoneInitialized_ = true; } else { const glm::vec3 delta = observed - deadZoneTarget_; const float length = glm::length(delta); if (length > deadZone_ && length > 1e-6f) deadZoneTarget_ += delta * ((length - deadZone_) / length); } target_ = deadZoneTarget_; } void CameraController::updateDirector() { if (!directorEnabled_ || timelinePlaying_) return; const Rig* best = nullptr; const Rig* active = nullptr; for (const auto& rig : rigs_) { if (rig.name == activeRig_ && rig.enabled) active = &rig; if (rig.enabled && (!best || rig.priority > best->priority)) best = &rig; } if (active && best && active->priority >= best->priority) best = active; if (best && best->name != activeRig_) activateRig(best->name, initialized_ ? 0.25f : 0.f); } void CameraController::updateTimeline(float dt) { if (!timelinePlaying_) return; float remaining = std::max(0.f, dt); if (timelineDuration_ <= 0.f) remaining = 0.f; timelineTime_ += remaining; for (auto& cut : timelineCuts_) { if (!cut.fired && cut.time <= timelineTime_) { cut.fired = true; activateRig(cut.rig, cut.blend); } } for (auto& marker : timelineEvents_) { if (!marker.fired && marker.time <= timelineTime_) { marker.fired = true; emitTimelineEvent(marker); } } evaluateTimelineFloats(); if (timelineTime_ < timelineDuration_) return; if (!timelineLoop_) { timelinePlaying_ = false; return; } timelineTime_ = timelineDuration_ > 0.f ? std::fmod(timelineTime_, timelineDuration_) : 0.f; for (auto& cut : timelineCuts_) cut.fired = false; for (auto& marker : timelineEvents_) marker.fired = false; for (auto& cut : timelineCuts_) if (cut.time <= timelineTime_) { cut.fired = true; activateRig(cut.rig, cut.blend); } for (auto& marker : timelineEvents_) if (marker.time <= timelineTime_) { marker.fired = true; emitTimelineEvent(marker); } evaluateTimelineFloats(); } void CameraController::applyCollision(View& v, float dt) { obstructed_ = false; collisionBodyId_ = -1; const glm::vec3 ray = v.eye - v.target; const float desiredDistance = glm::length(ray); if (!collisionEnabled_ || desiredDistance < 1e-5f) { collisionDistance_ = -1.f; return; } const glm::vec3 dir = ray / desiredDistance; float nearest = desiredDistance; for (const auto& box : collisionBoxes_) { const glm::vec3 bmin = box.min - glm::vec3(collisionRadius_); const glm::vec3 bmax = box.max + glm::vec3(collisionRadius_); float tmin = 0.f; float tmax = desiredDistance; bool hit = true; for (int axis = 0; axis < 3; ++axis) { if (std::abs(dir[axis]) < 1e-6f) { if (v.target[axis] < bmin[axis] || v.target[axis] > bmax[axis]) { hit = false; break; } } else { float a = (bmin[axis] - v.target[axis]) / dir[axis]; float b = (bmax[axis] - v.target[axis]) / dir[axis]; if (a > b) std::swap(a, b); tmin = std::max(tmin, a); tmax = std::min(tmax, b); if (tmin > tmax) { hit = false; break; } } } if (hit && tmin >= 0.f && tmin < nearest) nearest = tmin; } if (auto* query = eve::cap::query()) { eve::CameraObstructionHit hit; if (query->sphereCast(v.target.x, v.target.y, v.target.z, v.eye.x, v.eye.y, v.eye.z, collisionRadius_, collisionMask_, collisionIgnoredBody_, &hit) && hit.hit) { const float distance = desiredDistance * glm::clamp(hit.fraction, 0.f, 1.f); if (distance < nearest) { nearest = distance; collisionBodyId_ = hit.bodyId; } } } obstructed_ = nearest < desiredDistance; const float safeDistance = obstructed_ ? std::max(0.05f, nearest - 0.02f) : desiredDistance; if (collisionDistance_ < 0.f || safeDistance < collisionDistance_) collisionDistance_ = safeDistance; else { const float k = 1.f - std::exp(-collisionRecovery_ * std::max(0.f, dt)); collisionDistance_ += (safeDistance - collisionDistance_) * k; } collisionDistance_ = std::min(collisionDistance_, desiredDistance); v.eye = v.target + dir * collisionDistance_; } void CameraController::applyModifiers(View& v, float dt) { for (auto& impulse : impulses_) { impulse.age += std::max(0.f, dt); const float life = glm::clamp(1.f - impulse.age / impulse.duration, 0.f, 1.f); const float phase = impulse.age * 37.f + static_cast(impulse.seed % 997u); const glm::vec3 noise(std::sin(phase * 1.13f), std::sin(phase * 1.71f + 2.f), std::sin(phase * 2.07f + 4.f)); v.eye += noise * (impulse.positionAmplitude * life); const glm::vec3 forward = glm::normalize(v.target - v.eye); glm::vec3 right = glm::cross(forward, glm::vec3(0.f, 1.f, 0.f)); if (glm::length(right) > 1e-5f) right = glm::normalize(right); else right = glm::vec3(1.f, 0.f, 0.f); const glm::vec3 up = glm::normalize(glm::cross(right, forward)); const float angular = std::tan(glm::radians(impulse.rotationAmplitude * life)); v.target += (right * noise.x + up * noise.y) * angular * glm::length(v.target - v.eye); v.fov = glm::clamp(v.fov + impulse.fovAmplitude * life, 1.f, 179.f); } impulses_.erase( std::remove_if(impulses_.begin(), impulses_.end(), [](const Impulse& i) { return i.age >= i.duration; }), impulses_.end()); } CameraController::View CameraController::desired() const { View v; v.fov = fovDeg_; if (blending_) { float t = blendDur_ > 0.f ? glm::clamp(blendT_ / blendDur_, 0.f, 1.f) : 1.f; t = t * t * (3.f - 2.f * t); v.eye = glm::mix(blendFrom_.eye, blendTo_.eye, t); v.target = glm::mix(blendFrom_.target, blendTo_.target, t); v.fov = glm::mix(blendFrom_.fov, blendTo_.fov, t); } else if (mode_ == "orbit") { const float az = glm::radians(azimuthDeg_); const float el = glm::radians(elevationDeg_); glm::vec3 dir(std::cos(el) * std::sin(az), std::sin(el), std::cos(el) * std::cos(az)); v.eye = target_ + dir * radius_; v.target = target_; } else if (mode_ == "topdown") { v.eye = target_ + glm::vec3(0.f, radius_, 0.f); v.target = target_; } else if (mode_ == "firstperson") { v.eye = target_; const float yaw = glm::radians(yawDeg_); const float pitch = glm::radians(pitchDeg_); glm::vec3 dir(std::cos(pitch) * std::sin(yaw), std::sin(pitch), std::cos(pitch) * std::cos(yaw)); v.target = v.eye + dir * 100.f; } else if (mode_ == "cinematic") { if (viewIndex_ >= 0 && viewIndex_ < static_cast(views_.size())) { v = views_[viewIndex_]; } else { v.target = target_ + lookAhead_; v.eye = v.target + offset_; } } else { // follow (默认) v.target = target_ + lookAhead_; v.eye = v.target + offset_; } const glm::vec3 forward = glm::normalize(v.target - v.eye); glm::vec3 right = glm::cross(forward, glm::vec3(0.f, 1.f, 0.f)); if (glm::length(right) < 1e-5f) right = glm::vec3(1.f, 0.f, 0.f); else right = glm::normalize(right); glm::vec3 up = glm::normalize(glm::cross(right, forward)); const float distance = glm::length(v.target - v.eye); v.target += (right * composition_.x + up * composition_.y) * distance; return v; } void CameraController::applyView(const View& v) { if (!cam_) return; cam_->setEye(v.eye.x, v.eye.y, v.eye.z); cam_->setTarget(v.target.x, v.target.y, v.target.z); const glm::vec3 forward = glm::normalize(v.target - v.eye); glm::vec3 up(0.f, 1.f, 0.f); if (std::abs(glm::dot(forward, up)) > 0.999f) up = glm::vec3(0.f, 0.f, -1.f); cam_->setUp(up.x, up.y, up.z); cam_->setFov(v.fov); } void CameraController::easeToward(const View& v, float dt) { const float eyeK = 1.f - std::exp(-positionSmooth_ * dt); const float targetK = 1.f - std::exp(-targetSmooth_ * dt); glm::vec3 deye = (v.eye - cur_.eye) * eyeK; glm::vec3 dtgt = (v.target - cur_.target) * targetK; if (maxSpeed_ > 0.f && dt > 0.f) { const float maxStep = maxSpeed_ * dt; const float le = glm::length(deye); if (le > maxStep && le > 1e-6f) deye *= maxStep / le; const float lt = glm::length(dtgt); if (lt > maxStep && lt > 1e-6f) dtgt *= maxStep / lt; } cur_.eye += deye; cur_.target += dtgt; cur_.fov += (v.fov - cur_.fov) * eyeK; } void CameraController::snap() { cur_ = desired(); initialized_ = true; applyView(cur_); } void CameraController::update(float dt) { if (!cam_) return; dt = std::max(0.f, dt); updateTrackedTarget(); updateTimeline(dt); updateDirector(); if (mode_ == "cinematic" && playing_ && !views_.empty()) { timer_ += dt; if (!blending_ && timer_ >= stepTime_) { const int next = (viewIndex_ + 1) % static_cast(views_.size()); switchTo(views_[next].name, blendDur_); timer_ = 0.f; } } if (blending_) { blendT_ += dt; if (blendT_ >= blendDur_) { blending_ = false; blendT_ = blendDur_; } } // orbit 自动盘旋:不断累积方位角 if (mode_ == "orbit") { azimuthDeg_ += orbitSpeedDeg_ * dt; } View d = desired(); applyCollision(d, dt); applyModifiers(d, dt); if (!initialized_) { cur_ = d; initialized_ = true; } easeToward(d, dt); applyView(cur_); } void Camera::expose(ssq::Class& cls) { // 模块级方法(如需要可在此添加);当前只通过 eve.CameraController 暴露控制器。 } void Camera::expose(ssq::Table& table) { auto cls = table.addClass(name, Camera::create, false); expose(cls); auto cc = table.addClass( "CameraController", std::function([]() { return new CameraController(); }), true); cc.addFunc("setCamera", &CameraController::setCamera); cc.addFunc("getCamera", &CameraController::getCamera); cc.addFunc("setTarget", &CameraController::setTarget); cc.addFunc("setTargetNode", &CameraController::setTargetNode); cc.addFunc("getTargetX", &CameraController::getTargetX); cc.addFunc("getTargetY", &CameraController::getTargetY); cc.addFunc("getTargetZ", &CameraController::getTargetZ); cc.addFunc("setOffset", &CameraController::setOffset); cc.addFunc("setLookAhead", &CameraController::setLookAhead); cc.addFunc("setMode", &CameraController::setMode); cc.addFunc("getMode", &CameraController::getMode); cc.addFunc("setRadius", &CameraController::setRadius); cc.addFunc("setAzimuth", &CameraController::setAzimuth); cc.addFunc("setElevation", &CameraController::setElevation); cc.addFunc("setOrbitSpeed", &CameraController::setOrbitSpeed); cc.addFunc("setYaw", &CameraController::setYaw); cc.addFunc("setPitch", &CameraController::setPitch); cc.addFunc("addInput", &CameraController::addInput); cc.addFunc("setComposition", &CameraController::setComposition); cc.addFunc("setDeadZone", &CameraController::setDeadZone); cc.addFunc("setFov", &CameraController::setFov); cc.addFunc("getFov", &CameraController::getFov); cc.addFunc("setSmooth", &CameraController::setSmooth); cc.addFunc("setPositionSmooth", &CameraController::setPositionSmooth); cc.addFunc("setTargetSmooth", &CameraController::setTargetSmooth); cc.addFunc("setMaxSpeed", &CameraController::setMaxSpeed); cc.addFunc("snap", &CameraController::snap); cc.addFunc("setCollisionEnabled", &CameraController::setCollisionEnabled); cc.addFunc("setCollisionRadius", &CameraController::setCollisionRadius); cc.addFunc("setCollisionRecovery", &CameraController::setCollisionRecovery); cc.addFunc("setCollisionMask", &CameraController::setCollisionMask); cc.addFunc("setCollisionIgnoredBody", &CameraController::setCollisionIgnoredBody); cc.addFunc("clearCollisionBoxes", &CameraController::clearCollisionBoxes); cc.addFunc("addCollisionBox", &CameraController::addCollisionBox); cc.addFunc("getCollisionBodyId", &CameraController::getCollisionBodyId); cc.addFunc("isObstructed", &CameraController::isObstructed); cc.addFunc("addRig", &CameraController::addRig); cc.addFunc("removeRig", &CameraController::removeRig); cc.addFunc("setRigPriority", &CameraController::setRigPriority); cc.addFunc("setRigEnabled", &CameraController::setRigEnabled); cc.addFunc("saveRigState", &CameraController::saveRigState); cc.addFunc("activateRig", &CameraController::activateRig); cc.addFunc("getActiveRig", &CameraController::getActiveRig); cc.addFunc("addImpulse", &CameraController::addImpulse); cc.addFunc("addFovImpulse", &CameraController::addFovImpulse); cc.addFunc("clearImpulses", &CameraController::clearImpulses); cc.addFunc("addView", &CameraController::addView); cc.addFunc("switchTo", &CameraController::switchTo); cc.addFunc("playSequence", &CameraController::playSequence); cc.addFunc("stopSequence", &CameraController::stopSequence); cc.addFunc("isPlaying", &CameraController::isPlaying); cc.addFunc("clearTimeline", &CameraController::clearTimeline); cc.addFunc("addTimelineCut", &CameraController::addTimelineCut); cc.addFunc("addTimelineEvent", &CameraController::addTimelineEvent); cc.addFunc("addTimelineFloat", &CameraController::addTimelineFloat); cc.addFunc("setEventSink", &CameraController::setEventSink); cc.addFunc("playTimeline", &CameraController::playTimeline); cc.addFunc("pauseTimeline", &CameraController::pauseTimeline); cc.addFunc("stopTimeline", &CameraController::stopTimeline); cc.addFunc("seekTimeline", &CameraController::seekTimeline); cc.addFunc("isTimelinePlaying", &CameraController::isTimelinePlaying); cc.addFunc("getTimelineTime", &CameraController::getTimelineTime); cc.addFunc("consumeTimelineEvent", &CameraController::consumeTimelineEvent); cc.addFunc("getTimelineEventData", &CameraController::getTimelineEventData); cc.addFunc("getPendingTimelineEventCount", &CameraController::getPendingTimelineEventCount); cc.addFunc("getTimelineDuration", &CameraController::getTimelineDuration); cc.addFunc("getRigCount", &CameraController::getRigCount); cc.addFunc("serializeAsset", &CameraController::serializeAsset); cc.addFunc("deserializeAsset", &CameraController::deserializeAsset); cc.addFunc("update", &CameraController::update); } } // namespace eve::camera