mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-08-08 02:00:17 -04:00
Add camera operator API to CameraService
This commit is contained in:
@@ -625,6 +625,9 @@ if (svc_camera->get_camera(mod_ctx, game_view, &camera) == MOD_OK) {
|
||||
first in-game frame. Projection matrices match the renderer's WebGPU clip convention and renderer depth convention
|
||||
(reversed-Z by default).
|
||||
|
||||
Camera operators allow overriding the main camera. When an operator callback returns true, its values replace the camera
|
||||
state for the current frame. Register and unregister using `register_camera_operator` / `unregister_camera_operator`.
|
||||
|
||||
---
|
||||
|
||||
## Hooking Game Functions
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#define CAMERA_SERVICE_ID "dev.twilitrealm.dusklight.camera"
|
||||
#define CAMERA_SERVICE_MAJOR 1u
|
||||
#define CAMERA_SERVICE_MINOR 0u
|
||||
#define CAMERA_SERVICE_MINOR 1u
|
||||
|
||||
/*
|
||||
* Snapshot of a game camera for the frame currently being recorded.
|
||||
@@ -46,6 +46,37 @@ typedef struct CameraInfo {
|
||||
|
||||
#define CAMERA_INFO_INIT {sizeof(CameraInfo)}
|
||||
|
||||
/* 0 is never a valid handle. */
|
||||
typedef uint64_t CameraOperatorHandle;
|
||||
|
||||
typedef struct CameraOperatorState {
|
||||
uint32_t struct_size;
|
||||
|
||||
/* Host inputs. */
|
||||
uint64_t frame_counter;
|
||||
uint64_t ticks;
|
||||
float aspect;
|
||||
|
||||
/* Initial camera state and callback output. */
|
||||
float eye[3];
|
||||
float center[3];
|
||||
float fovy;
|
||||
float bank_degrees;
|
||||
} CameraOperatorState;
|
||||
|
||||
/* Return true to use state for the current frame. Game thread only. */
|
||||
typedef bool (*CameraOperateFn)(ModContext* ctx, CameraOperatorState* state, void* user_data);
|
||||
|
||||
typedef struct CameraOperatorDesc {
|
||||
uint32_t struct_size;
|
||||
const char* debug_name;
|
||||
int32_t priority;
|
||||
CameraOperateFn operate;
|
||||
void* user_data;
|
||||
} CameraOperatorDesc;
|
||||
|
||||
#define CAMERA_OPERATOR_DESC_INIT {sizeof(CameraOperatorDesc), NULL, 0, NULL, NULL}
|
||||
|
||||
typedef struct CameraService {
|
||||
ServiceHeader header;
|
||||
|
||||
@@ -55,6 +86,17 @@ typedef struct CameraService {
|
||||
* perspective camera.
|
||||
*/
|
||||
ModResult (*get_camera)(ModContext* ctx, const void* game_view, CameraInfo* out_info);
|
||||
|
||||
/* Minor version 1 */
|
||||
|
||||
/*
|
||||
* Register an operator for the main camera. Operators run by descending priority, then
|
||||
* registration order, until one returns true. debug_name and operate must be set; debug_name
|
||||
* is copied. out_handle must not be NULL.
|
||||
*/
|
||||
ModResult (*register_camera_operator)(
|
||||
ModContext* ctx, const CameraOperatorDesc* desc, CameraOperatorHandle* out_handle);
|
||||
ModResult (*unregister_camera_operator)(ModContext* ctx, CameraOperatorHandle handle);
|
||||
} CameraService;
|
||||
|
||||
MOD_DECLARE_SERVICE(
|
||||
|
||||
+2
-1
@@ -29,6 +29,7 @@
|
||||
#endif
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/camera_operators.hpp"
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/logging.h"
|
||||
#include "dusk/action_bindings.h"
|
||||
@@ -1053,7 +1054,7 @@ void dCamera_c::debugDrawInit() {
|
||||
bool dCamera_c::Run() {
|
||||
#if TARGET_PC
|
||||
ResetView();
|
||||
if (executeDebugFlyCam()) {
|
||||
if (executeDebugFlyCam() || dusk::mods::camera_run_operators(this)) {
|
||||
mFrameCounter++;
|
||||
mTicks++;
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
class dCamera_c;
|
||||
|
||||
namespace dusk::mods {
|
||||
|
||||
bool camera_run_operators(dCamera_c* camera);
|
||||
|
||||
} // namespace dusk::mods
|
||||
@@ -1,13 +1,24 @@
|
||||
#include "registry.hpp"
|
||||
#include "slot_map.hpp"
|
||||
|
||||
#include "dusk/camera_operators.hpp"
|
||||
#include "dusk/mods/loader/loader.hpp"
|
||||
#include "mods/svc/camera.h"
|
||||
|
||||
#include "SSystem/SComponent/c_angle.h"
|
||||
#include "d/d_camera.h"
|
||||
#include "d/d_com_inf_game.h"
|
||||
#include "f_op/f_op_camera_mng.h"
|
||||
#include "f_op/f_op_view.h"
|
||||
#include "m_Do/m_Do_mtx.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <aurora/gfx.hpp>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <fmt/format.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace dusk::mods::svc {
|
||||
namespace {
|
||||
@@ -117,9 +128,125 @@ ModResult camera_get_camera_from_view(
|
||||
return snapshot_view(static_cast<const view_class*>(gameView), outInfo);
|
||||
}
|
||||
|
||||
struct CameraOperator {
|
||||
std::string debugName;
|
||||
int32_t priority = 0;
|
||||
uint64_t sequence = 0;
|
||||
CameraOperateFn operate = nullptr;
|
||||
void* userData = nullptr;
|
||||
};
|
||||
|
||||
SlotMap<CameraOperator> sOperators;
|
||||
uint64_t sNextOperatorSequence = 0;
|
||||
|
||||
ModResult camera_register_operator(
|
||||
ModContext* context, const CameraOperatorDesc* desc, CameraOperatorHandle* outHandle) {
|
||||
if (outHandle != nullptr) {
|
||||
*outHandle = 0;
|
||||
}
|
||||
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || desc == nullptr || desc->struct_size < sizeof(CameraOperatorDesc) ||
|
||||
desc->debug_name == nullptr || desc->debug_name[0] == '\0' || desc->operate == nullptr ||
|
||||
outHandle == nullptr)
|
||||
{
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
*outHandle = sOperators.emplace(*mod, CameraOperator{
|
||||
.debugName = desc->debug_name,
|
||||
.priority = desc->priority,
|
||||
.sequence = sNextOperatorSequence++,
|
||||
.operate = desc->operate,
|
||||
.userData = desc->user_data,
|
||||
});
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult camera_unregister_operator(ModContext* context, CameraOperatorHandle handle) {
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || handle == 0) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
return sOperators.erase_owned(handle, *mod) ? MOD_OK : MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
CameraOperatorState make_operator_state(const dCamera_c& camera) {
|
||||
return CameraOperatorState{
|
||||
.struct_size = sizeof(CameraOperatorState),
|
||||
.frame_counter = camera.mFrameCounter,
|
||||
.ticks = camera.mTicks,
|
||||
.aspect = mDoGph_gInf_c::getAspect(),
|
||||
.eye = {camera.mEye.x, camera.mEye.y, camera.mEye.z},
|
||||
.center = {camera.mCenter.x, camera.mCenter.y, camera.mCenter.z},
|
||||
.fovy = camera.mFovy,
|
||||
.bank_degrees = camera.mBank.Degree(),
|
||||
};
|
||||
}
|
||||
|
||||
bool run_operators(dCamera_c* camera) {
|
||||
auto* mainCamera = dComIfGp_getCamera(0);
|
||||
if (camera == nullptr || mainCamera == nullptr || &mainCamera->mCamera != camera) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct PendingOperator {
|
||||
CameraOperatorHandle handle;
|
||||
CameraOperator value;
|
||||
LoadedMod* owner;
|
||||
};
|
||||
std::vector<PendingOperator> operators;
|
||||
sOperators.for_each([&](CameraOperatorHandle handle, const auto& entry) {
|
||||
operators.push_back({.handle = handle, .value = entry.value, .owner = entry.owner});
|
||||
});
|
||||
std::ranges::sort(operators, [](const PendingOperator& lhs, const PendingOperator& rhs) {
|
||||
if (lhs.value.priority != rhs.value.priority) {
|
||||
return lhs.value.priority > rhs.value.priority;
|
||||
}
|
||||
return lhs.value.sequence < rhs.value.sequence;
|
||||
});
|
||||
|
||||
const auto initialState = make_operator_state(*camera);
|
||||
for (const auto& entry : operators) {
|
||||
if (!entry.owner->active || sOperators.find(entry.handle) == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto state = initialState;
|
||||
bool useState = false;
|
||||
try {
|
||||
useState =
|
||||
entry.value.operate(entry.owner->context.get(), &state, entry.value.userData);
|
||||
} catch (const std::exception& e) {
|
||||
fail_mod(*entry.owner, MOD_ERROR,
|
||||
fmt::format(
|
||||
"exception in camera operator '{}': {}", entry.value.debugName, e.what()));
|
||||
} catch (...) {
|
||||
fail_mod(*entry.owner, MOD_ERROR,
|
||||
fmt::format("unknown exception in camera operator '{}'", entry.value.debugName));
|
||||
}
|
||||
|
||||
if (!useState) {
|
||||
continue;
|
||||
}
|
||||
|
||||
camera->Reset(cXyz{state.center[0], state.center[1], state.center[2]},
|
||||
cXyz{state.eye[0], state.eye[1], state.eye[2]}, state.fovy,
|
||||
cAngle::Degree_to_SAngle(state.bank_degrees));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void camera_mod_detached(LoadedMod& mod) {
|
||||
sOperators.erase_all(mod);
|
||||
}
|
||||
|
||||
constexpr CameraService s_cameraService{
|
||||
.header = SERVICE_HEADER(CameraService, CAMERA_SERVICE_MAJOR, CAMERA_SERVICE_MINOR),
|
||||
.get_camera = camera_get_camera_from_view,
|
||||
.register_camera_operator = camera_register_operator,
|
||||
.unregister_camera_operator = camera_unregister_operator,
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -129,6 +256,11 @@ constinit const ServiceModule g_cameraModule{
|
||||
.majorVersion = CAMERA_SERVICE_MAJOR,
|
||||
.minorVersion = CAMERA_SERVICE_MINOR,
|
||||
.service = &s_cameraService,
|
||||
.modDetached = camera_mod_detached,
|
||||
};
|
||||
|
||||
} // namespace dusk::mods::svc
|
||||
|
||||
bool dusk::mods::camera_run_operators(dCamera_c* camera) {
|
||||
return svc::run_operators(camera);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user