220 lines
6.4 KiB
Plaintext
220 lines
6.4 KiB
Plaintext
/*
|
|
HS_DollyCam - FOV Helper
|
|
- Central place that actually sends RLVa @setcam_fov
|
|
- Receives CE_CMD_FOV link messages: "rad|quiet" or "rad|quiet|flags"
|
|
- Dedupe + optional force flag (and optional sync hook via CE_CMD_GET_STATE if you want later)
|
|
|
|
Put this script into the HUD linkset.
|
|
*/
|
|
|
|
integer CE_CMD_FOV = 1040; // payload: rad|quiet|flags(optional)
|
|
integer CE_CMD_RELEASE = 1001; // for cleanup
|
|
integer CE_CMD_GET_STATE = 1060; // optional sync (not required for basic)
|
|
integer CE_EVT_STATE = 2060; // optional sync response
|
|
|
|
// --- RLVa ---
|
|
string RLV_FOV_CMD = "setcam_fov"; // if your viewer uses another name, change here
|
|
float RLV_FOV_MIN_DEG = 10.0;
|
|
float RLV_FOV_MAX_DEG = 179.0; // allow wide values (e.g. rad ~ 3.0)
|
|
|
|
// flags (optional third field)
|
|
integer FOVF_SYNC = 1; // request state before deciding (optional)
|
|
integer FOVF_FORCE = 2; // bypass dedupe
|
|
|
|
// runtime
|
|
key gOwner;
|
|
|
|
float gLastSentRad = -1.0;
|
|
float gEpsRad = 0.0005; // ~0.03deg
|
|
float gMinInterval = 0.02; // small safety throttle (senders already throttle)
|
|
float gLastSendT = 0.0;
|
|
|
|
// optional sync
|
|
integer gSyncPend = FALSE;
|
|
string gSyncReq = "";
|
|
float gSyncRad = 0.0;
|
|
integer gSyncQuiet= 1;
|
|
integer gSyncFlags= 0;
|
|
|
|
// debug (keep OFF in production)
|
|
integer DEBUG_FOV = FALSE;
|
|
dbg(string s){ if (DEBUG_FOV) llOwnerSay("[FOV] " + s); }
|
|
|
|
float clampf(float v, float lo, float hi){ if (v<lo) return lo; if (v>hi) return hi; return v; }
|
|
float deg2rad(float d){ return d * PI / 180.0; }
|
|
float rad2deg(float r){ return r * 180.0 / PI; }
|
|
|
|
float clampFovRad(float rad)
|
|
{
|
|
float deg = rad2deg(rad);
|
|
deg = clampf(deg, RLV_FOV_MIN_DEG, RLV_FOV_MAX_DEG);
|
|
return deg2rad(deg);
|
|
}
|
|
|
|
integer camTrackOk()
|
|
{
|
|
return ((llGetPermissions() & PERMISSION_TRACK_CAMERA) && (llGetPermissionsKey() == gOwner));
|
|
}
|
|
|
|
float getViewerFovRadOrNeg()
|
|
{
|
|
if (!camTrackOk()) return -1.0;
|
|
float f = llGetCameraFOV();
|
|
if (f <= 0.0001) return -1.0;
|
|
return clampFovRad(f);
|
|
}
|
|
|
|
sendRlvaFov(float rad, integer quiet, integer flags)
|
|
{
|
|
rad = clampFovRad(rad);
|
|
|
|
float now = llGetTime();
|
|
if ((now - gLastSendT) < gMinInterval) {
|
|
// tiny global throttle (prevents double-sends from multiple sources same frame)
|
|
if (!(flags & FOVF_FORCE)) return;
|
|
}
|
|
|
|
if (!(flags & FOVF_FORCE)) {
|
|
// Prefer dedupe against ACTUAL viewer FOV (resists manual viewer changes)
|
|
float cur = getViewerFovRadOrNeg();
|
|
if (cur > 0.0) {
|
|
if (llFabs(rad - cur) < gEpsRad) return; // viewer already at this FOV
|
|
} else {
|
|
// fallback: dedupe against last value we sent
|
|
if (gLastSentRad > 0.0 && llFabs(rad - gLastSentRad) < gEpsRad) return;
|
|
}
|
|
}
|
|
|
|
gLastSendT = now;
|
|
gLastSentRad = rad;
|
|
|
|
// RLVa commands are sent via owner chat from attachment scripts.
|
|
// Many viewers hide "@..." automatically.
|
|
string cmd = "@" + RLV_FOV_CMD + ":" + (string)rad + "=force";
|
|
|
|
if (!quiet) {
|
|
dbg("send " + cmd + " (" + (string)rad2deg(rad) + " deg)");
|
|
}
|
|
|
|
llOwnerSay(cmd);
|
|
}
|
|
|
|
string newReqId()
|
|
{
|
|
// unique enough
|
|
return "FOVSYNC:" + (string)llGetUnixTime() + ":" + (string)((integer)llFrand(1000000.0));
|
|
}
|
|
|
|
requestSyncThenMaybeSend(float rad, integer quiet, integer flags)
|
|
{
|
|
// optional path: ask Core for state (which can include fov if you patched it),
|
|
// then decide whether to send. Good for "manual viewer change" corrections
|
|
gSyncPend = TRUE;
|
|
gSyncRad = rad;
|
|
gSyncQuiet = quiet;
|
|
gSyncFlags = flags;
|
|
gSyncReq = newReqId();
|
|
|
|
llMessageLinked(LINK_SET, CE_CMD_GET_STATE, gSyncReq, gOwner);
|
|
}
|
|
|
|
default
|
|
{
|
|
state_entry()
|
|
{
|
|
gOwner = llGetOwner();
|
|
}
|
|
|
|
on_rez(integer sp)
|
|
{
|
|
gOwner = llGetOwner();
|
|
gLastSentRad = -1.0;
|
|
gLastSendT = 0.0;
|
|
gSyncPend = FALSE;
|
|
gSyncReq = "";
|
|
}
|
|
|
|
attach(key id)
|
|
{
|
|
gOwner = llGetOwner();
|
|
if (id == NULL_KEY) {
|
|
// detach cleanup
|
|
gLastSentRad = -1.0;
|
|
gLastSendT = 0.0;
|
|
gSyncPend = FALSE;
|
|
gSyncReq = "";
|
|
}
|
|
}
|
|
|
|
link_message(integer sender, integer num, string str, key id)
|
|
{
|
|
if (num == CE_CMD_RELEASE) {
|
|
// reset internal state; do NOT spam RLVa on release
|
|
gLastSentRad = -1.0;
|
|
gLastSendT = 0.0;
|
|
gSyncPend = FALSE;
|
|
gSyncReq = "";
|
|
return;
|
|
}
|
|
|
|
if (num == CE_CMD_FOV) {
|
|
if (id != gOwner) return;
|
|
|
|
list p = llParseString2List(str, ["|"], []);
|
|
integer L = llGetListLength(p);
|
|
if (L < 1) return;
|
|
|
|
float rad = (float)llList2String(p, 0);
|
|
integer quiet = 1;
|
|
if (L >= 2) quiet = (integer)llList2String(p, 1);
|
|
|
|
integer flags = 0;
|
|
if (L >= 3) flags = (integer)llList2String(p, 2);
|
|
|
|
// If you don't want sync at all, just ignore FOVF_SYNC and always send:
|
|
if (flags & FOVF_SYNC) {
|
|
requestSyncThenMaybeSend(rad, quiet, flags);
|
|
} else {
|
|
sendRlvaFov(rad, quiet, flags);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// optional sync response (only if you patched Core to append fov to CE_EVT_STATE)
|
|
if (num == CE_EVT_STATE) {
|
|
if (!gSyncPend) return;
|
|
|
|
list p2 = llParseString2List(str, ["|"], []);
|
|
if (llGetListLength(p2) < 1) return;
|
|
|
|
string reqId = llList2String(p2, 0);
|
|
if (reqId != gSyncReq) return;
|
|
|
|
// field 4 (index 4) expected to be fov rad if available
|
|
float curFov = -1.0;
|
|
if (llGetListLength(p2) >= 5) {
|
|
curFov = (float)llList2String(p2, 4);
|
|
if (curFov > 0.0001) curFov = clampFovRad(curFov);
|
|
else curFov = -1.0;
|
|
}
|
|
|
|
// If Core did not provide a usable FOV, force-send to avoid stale cache dedupe
|
|
if (curFov < 0.0 && !(gSyncFlags & FOVF_FORCE)) {
|
|
gSyncFlags = gSyncFlags | FOVF_FORCE;
|
|
}
|
|
|
|
// clear pending first
|
|
gSyncPend = FALSE;
|
|
gSyncReq = "";
|
|
|
|
// if we have current fov, use it for dedupe decision
|
|
if (curFov > 0.0 && !(gSyncFlags & FOVF_FORCE)) {
|
|
float want = clampFovRad(gSyncRad);
|
|
if (llFabs(want - curFov) < gEpsRad) return;
|
|
}
|
|
|
|
sendRlvaFov(gSyncRad, gSyncQuiet, gSyncFlags);
|
|
return;
|
|
}
|
|
}
|
|
} |