HSDollyCam/HS_CamEngineCore.lsl

992 lines
29 KiB
Plaintext

/*
HS_DollyCam - CamCore (tour split, memory-safe)
- Permissions, base camera params, MoveTo, Follow/Lock, Config, GetState
- Tour playback is offloaded to HS_CamTourEngine.lsl via internal link messages:
CE_INT_TOUR_BEGIN / CE_INT_SET_CAM / CE_INT_TOUR_END / CE_INT_TOUR_STOP
*/
string CFG_CARD = "HS_CamEngine.properties";
// Link message protocol (Controller -> Core)
integer CE_CMD_INIT = 1000;
integer CE_CMD_RELEASE = 1001;
integer CE_CMD_MOVE = 1010; // payload: moveId|durMs|<pos>|<focus>
integer CE_CMD_TOUR = 1011; // handled by HS_CamTourEngine (Core ignores)
integer CE_CMD_STOP = 1012;
integer CE_CMD_LOCK = 1020; // payload: 0|<focus> or 1|<focus> or 1|<key>
integer CE_CMD_FOLLOW = 1030; // payload: 0|<key>|<posOffset>|<focusOffset> or 1|...
integer CE_CMD_CFG_RELOAD = 1050;
integer CE_CMD_CFG_DUMP = 1051;
integer CE_CMD_GET_STATE = 1060; // payload: reqId
// Link message protocol (Core -> Controller)
integer CE_EVT_READY = 2000; // payload: CAM_OK
integer CE_EVT_DENIED = 2001; // payload: CAM_DENIED
integer CE_EVT_MOVE_DONE = 2010; // payload: moveId
integer CE_EVT_CFG_DUMP = 2051; // payload: key=val|...
integer CE_EVT_STATE = 2060; // payload: reqId|<pos>|<focus>|<rot>|fovRad
// Internal protocol (TourEngine -> Core)
integer CE_INT_SET_CAM = 3000; // payload: <pos>|<focus>
integer CE_INT_TOUR_BEGIN = 3001; // payload: moveId
integer CE_INT_TOUR_END = 3002; // payload: moveId
integer CE_INT_TOUR_STOP = 3003; // payload: (unused)
integer FOLLOW_YAW = 0;
integer FOLLOW_LOCAL = 1;
integer FOLLOW_WORLD = 2;
// --- Defaults (Notecard can override) ---
float gMOVE_STEP = 0.025;
float gFOLLOW_STEP = 0.05;
integer gDEFAULT_MOVE_MS = 3000;
integer gTOUR_MAX_POINTS = 20;
float gDEFAULT_FOCUS_DIST = 10.0;
float gTOUR_CAM_MIN_INTERVAL = 0.033;
float gTOUR_POS_EPS = 0.002;
float gTOUR_FOCUS_EPS = 0.002;
float gMOVE_POS_LAG = 2.0;
float gMOVE_FOCUS_LAG = 2.0;
float gFOLLOW_POS_LAG = 1.6;
float gFOLLOW_FOCUS_LAG= 1.6;
float gPOS_THRESHOLD = 0.02;
float gFOCUS_THRESHOLD = 0.02;
float gFOLLOW_PREDICT = 0.10;
// Runtime state
key gOwner;
integer gHasPerm = FALSE;
integer gCamActive = FALSE;
integer gMoving = FALSE;
integer gMoveId = 0;
float gMoveStart = 0.0;
float gMoveDur = 1.0;
integer gMovePending = FALSE;
integer gMovePendId;
integer gMovePendDurMs;
vector gMovePendPos;
vector gMovePendFoc;
vector gStartPos;
vector gStartFocus;
vector gTargetPos;
vector gTargetFocus;
vector gCurPos;
vector gCurFocus;
rotation gCurRot;
// Follow/Lock
integer gFollowMode = FOLLOW_WORLD;
vector gCamOffW; vector gFocusOffW;
vector gCamOffL; vector gFocusOffL;
vector gCamOffYaw; vector gFocusOffYaw;
integer gFollowBlendOn = FALSE;
float gFollowBlendDur = 0.0;
float gFollowBlendStart = 0.0;
vector gFollowBlendStartCam;
vector gFollowBlendStartFocus;
integer gLockOn = FALSE;
vector gLockFocus;
key gLockTarget = NULL_KEY;
vector gLockTargetOffset = <0,0,1.0>;
integer gFollowOn = FALSE;
key gFollowTarget;
// Config
key gCfgQuery;
integer gCfgLine = 0;
integer gPendingBaseApply = FALSE;
// Tour external drive flag
integer gExternDrive = FALSE;
// Cached camera param lists
list gBaseMoveParams;
list gBaseFollowParams;
integer gBaseParamsDirty = TRUE;
// ---------- helpers ----------
float clampf(float v, float lo, float hi)
{
if (v < lo) return lo;
if (v > hi) return hi;
return v;
}
float smootherstep(float x)
{
if (x <= 0.0) return 0.0;
if (x >= 1.0) return 1.0;
return x*x*x*(x*(x*6.0 - 15.0) + 10.0);
}
rotation rotFromPosFocus(vector pos, vector focus)
{
vector fwd = llVecNorm(focus - pos);
vector up = <0,0,1>;
vector left = up % fwd;
if (llVecMag(left) < 0.0001) left = <1,0,0>;
else left = llVecNorm(left);
vector up2 = llVecNorm(fwd % left);
return llAxes2Rot(left, up2, fwd);
}
rotation yawOnlyRot(rotation r, vector vel)
{
vector fwd = llRot2Fwd(r);
if (r == ZERO_ROTATION && vel != ZERO_VECTOR) fwd = llVecNorm(vel);
fwd.z = 0.0;
if (fwd == ZERO_VECTOR) return ZERO_ROTATION;
fwd = llVecNorm(fwd);
return llRotBetween(<1,0,0>, fwd);
}
integer camControlOk()
{
return ((llGetPermissions() & PERMISSION_CONTROL_CAMERA) && (llGetPermissionsKey() == gOwner));
}
integer camTrackOk()
{
return ((llGetPermissions() & PERMISSION_TRACK_CAMERA) && (llGetPermissionsKey() == gOwner));
}
buildBaseParams()
{
gBaseMoveParams = [
CAMERA_ACTIVE, TRUE,
CAMERA_POSITION_LOCKED, TRUE,
CAMERA_FOCUS_LOCKED, TRUE,
CAMERA_POSITION_LAG, gMOVE_POS_LAG,
CAMERA_FOCUS_LAG, gMOVE_FOCUS_LAG,
CAMERA_POSITION_THRESHOLD, gPOS_THRESHOLD,
CAMERA_FOCUS_THRESHOLD, gFOCUS_THRESHOLD
];
gBaseFollowParams = [
CAMERA_ACTIVE, TRUE,
CAMERA_POSITION_LOCKED, TRUE,
CAMERA_FOCUS_LOCKED, TRUE,
CAMERA_POSITION_LAG, gFOLLOW_POS_LAG,
CAMERA_FOCUS_LAG, gFOLLOW_FOCUS_LAG,
CAMERA_POSITION_THRESHOLD, gPOS_THRESHOLD,
CAMERA_FOCUS_THRESHOLD, gFOCUS_THRESHOLD
];
gBaseParamsDirty = FALSE;
}
applyBaseMove()
{
if (!gHasPerm) return;
if (gBaseParamsDirty || llGetListLength(gBaseMoveParams) == 0) buildBaseParams();
llSetCameraParams(gBaseMoveParams);
gCamActive = TRUE;
}
applyBaseFollow()
{
if (!gHasPerm) return;
if (gBaseParamsDirty || llGetListLength(gBaseFollowParams) == 0) buildBaseParams();
llSetCameraParams(gBaseFollowParams);
gCamActive = TRUE;
}
applyBaseForMode()
{
if (!gHasPerm) return;
if (gMoving) applyBaseMove();
else applyBaseFollow();
}
setTimerForMode()
{
if (gExternDrive) { llSetTimerEvent(0.0); return; }
if (gMoving) { llSetTimerEvent(gMOVE_STEP); return; }
if (gFollowOn || gLockOn) { llSetTimerEvent(gFOLLOW_STEP); return; }
llSetTimerEvent(0.0);
}
requestCamPerm()
{
if (gOwner == NULL_KEY) gOwner = llGetOwner();
if ((llGetPermissions() & PERMISSION_CONTROL_CAMERA) && (llGetPermissionsKey() == gOwner)) {
gHasPerm = TRUE;
applyBaseForMode();
llMessageLinked(LINK_SET, CE_EVT_READY, "CAM_OK", gOwner);
return;
}
llRequestPermissions(gOwner, PERMISSION_CONTROL_CAMERA | PERMISSION_TRACK_CAMERA);
}
releaseCam()
{
// Idempotenz: verhindert doppelte Release-Sequenzen (attach(NULL_KEY) + CE_CMD_RELEASE)
if (!gCamActive && !gHasPerm && !gExternDrive && !gMoving && !gFollowOn && !gLockOn) return;
llMessageLinked(LINK_SET, CE_INT_TOUR_STOP, "", gOwner);
gExternDrive = FALSE;
gMoving = FALSE;
gFollowOn = FALSE;
gLockOn = FALSE;
gCamActive = FALSE;
// reset permission flags (avoid stale state)
gHasPerm = FALSE;
if (camControlOk())
llClearCameraParams();
llSetTimerEvent(0.0);
}
integer followCapture(key target, integer mode, integer transitionMs)
{
list d = llGetObjectDetails(target, [OBJECT_POS, OBJECT_ROT, OBJECT_VELOCITY]);
if (llGetListLength(d) < 1) return FALSE;
vector p = llList2Vector(d, 0);
rotation r = ZERO_ROTATION;
vector v = ZERO_VECTOR;
if (llGetListLength(d) >= 2) r = llList2Rot(d, 1);
if (llGetListLength(d) >= 3) v = llList2Vector(d, 2);
p += v * gFOLLOW_PREDICT;
vector camPos;
rotation camRot;
if (camTrackOk()) {
camPos = llGetCameraPos();
camRot = llGetCameraRot();
} else {
camPos = gCurPos;
camRot = rotFromPosFocus(gCurPos, gCurFocus);
}
if (camPos == ZERO_VECTOR) {
rotation rr = r;
if (mode == FOLLOW_YAW) rr = yawOnlyRot(r, v);
camPos = p + (<-4.0, 0.0, 2.0> * rr);
camRot = rr;
}
vector focusNow = camPos + (llRot2Fwd(camRot) * gDEFAULT_FOCUS_DIST);
gFollowTarget = target;
gFollowMode = mode;
if (mode == FOLLOW_WORLD) {
gCamOffW = camPos - p;
gFocusOffW = focusNow - p;
}
else if (mode == FOLLOW_LOCAL) {
gCamOffL = (camPos - p) / r;
gFocusOffL = (focusNow - p) / r;
}
else {
rotation yawR = yawOnlyRot(r, v);
gCamOffYaw = (camPos - p) / yawR;
gFocusOffYaw = (focusNow - p) / yawR;
gFollowMode = FOLLOW_YAW;
}
gFollowBlendOn = (transitionMs > 0);
if (gFollowBlendOn) {
gFollowBlendDur = (float)transitionMs / 1000.0;
if (gFollowBlendDur < 0.01) gFollowBlendDur = 0.01;
gFollowBlendStart = llGetTime();
gFollowBlendStartCam = camPos;
gFollowBlendStartFocus = focusNow;
}
return TRUE;
}
applyFollowOnce()
{
if (!gHasPerm) return;
if (!gFollowOn || gFollowTarget == NULL_KEY) return;
list d = llGetObjectDetails(gFollowTarget, [OBJECT_POS, OBJECT_ROT, OBJECT_VELOCITY]);
integer len = llGetListLength(d);
if (len < 1) return;
vector p = llList2Vector(d, 0);
rotation r = ZERO_ROTATION;
vector v = ZERO_VECTOR;
if (len >= 2) r = llList2Rot(d, 1);
if (len >= 3) v = llList2Vector(d, 2);
p += v * gFOLLOW_PREDICT;
vector pos;
vector foc;
if (gFollowMode == FOLLOW_WORLD) {
pos = p + gCamOffW;
foc = p + gFocusOffW;
}
else if (gFollowMode == FOLLOW_LOCAL) {
pos = p + (gCamOffL * r);
foc = p + (gFocusOffL * r);
}
else {
rotation yawR = yawOnlyRot(r, v);
pos = p + (gCamOffYaw * yawR);
foc = p + (gFocusOffYaw * yawR);
}
if (gFollowBlendOn) {
float b = (llGetTime() - gFollowBlendStart) / gFollowBlendDur;
if (b >= 1.0) { b = 1.0; gFollowBlendOn = FALSE; }
pos = gFollowBlendStartCam + (pos - gFollowBlendStartCam) * b;
foc = gFollowBlendStartFocus + (foc - gFollowBlendStartFocus) * b;
}
if (gLockOn) {
if (gLockTarget != NULL_KEY) {
list det2 = llGetObjectDetails(gLockTarget, [OBJECT_POS]);
if (llGetListLength(det2) >= 1) {
vector tpos = llList2Vector(det2, 0);
foc = tpos + gLockTargetOffset;
}
} else {
foc = gLockFocus;
}
}
gCurPos = pos;
gCurFocus = foc;
llSetCameraParams([CAMERA_POSITION, pos, CAMERA_FOCUS, foc]);
}
applyLockOnce()
{
if (!gHasPerm) return;
if (!gLockOn) return;
if (gMoving || gExternDrive) return;
if (gCurPos == ZERO_VECTOR && camTrackOk()) {
vector camPos = llGetCameraPos();
rotation camRot = llGetCameraRot();
gCurPos = camPos;
gCurFocus = camPos + (llRot2Fwd(camRot) * gDEFAULT_FOCUS_DIST);
}
if (gCurPos == ZERO_VECTOR) return;
vector foc = gCurFocus;
if (gLockTarget != NULL_KEY) {
list det = llGetObjectDetails(gLockTarget, [OBJECT_POS]);
if (llGetListLength(det) >= 1) {
vector tpos = llList2Vector(det, 0);
foc = tpos + gLockTargetOffset;
}
} else {
foc = gLockFocus;
}
gCurFocus = foc;
llSetCameraParams([CAMERA_POSITION, gCurPos, CAMERA_FOCUS, foc]);
}
startMove(integer moveId, integer durMs, vector pos, vector focus)
{
// stop external tour playback (a move overrides)
gExternDrive = FALSE;
llMessageLinked(LINK_SET, CE_INT_TOUR_STOP, "", gOwner);
if (!gHasPerm) return;
applyBaseMove();
if (gCurPos == ZERO_VECTOR && gCurFocus == ZERO_VECTOR) {
vector camPos = llGetCameraPos();
rotation camRot = llGetCameraRot();
vector camFocus = camPos + (llRot2Fwd(camRot) * gDEFAULT_FOCUS_DIST);
gCurPos = camPos;
gCurFocus = camFocus;
}
gMoveId = moveId;
gStartPos = gCurPos;
gStartFocus = gCurFocus;
gTargetPos = pos;
gTargetFocus = focus;
float d = (float)durMs / 1000.0;
if (durMs <= 0 || d <= gMOVE_STEP) {
gMoving = FALSE;
gCurPos = gTargetPos;
gCurFocus = gTargetFocus;
gCurRot = rotFromPosFocus(gCurPos, gCurFocus);
llSetCameraParams([CAMERA_POSITION, gCurPos, CAMERA_FOCUS, gCurFocus]);
llMessageLinked(LINK_SET, CE_EVT_MOVE_DONE, (string)gMoveId, gOwner);
if (gPendingBaseApply) {
gPendingBaseApply = FALSE;
applyBaseForMode();
}
setTimerForMode();
return;
}
gMoveStart = llGetTime();
gMoveDur = d;
gMoving = TRUE;
llSetCameraParams([CAMERA_POSITION, gStartPos, CAMERA_FOCUS, gStartFocus]);
setTimerForMode();
}
stopMove()
{
// stop external tour too
llMessageLinked(LINK_SET, CE_INT_TOUR_STOP, "", gOwner);
gExternDrive = FALSE;
gMoving = FALSE;
setTimerForMode();
}
// ---------- config loading ----------
cfgResetDefaults()
{
gMOVE_STEP = 0.025;
gFOLLOW_STEP = 0.05;
gDEFAULT_MOVE_MS = 3000;
gTOUR_MAX_POINTS = 20;
gDEFAULT_FOCUS_DIST = 10.0;
gTOUR_CAM_MIN_INTERVAL = 0.033;
gTOUR_POS_EPS = 0.002;
gTOUR_FOCUS_EPS = 0.002;
gMOVE_POS_LAG = 2.0;
gMOVE_FOCUS_LAG = 2.0;
gFOLLOW_POS_LAG = 1.6;
gFOLLOW_FOCUS_LAG = 1.6;
gPOS_THRESHOLD = 0.02;
gFOCUS_THRESHOLD = 0.02;
gFOLLOW_PREDICT = 0.10;
gBaseParamsDirty = TRUE;
}
cfgStart()
{
gCfgLine = 0;
gCfgQuery = NULL_KEY;
cfgResetDefaults();
if (llGetInventoryType(CFG_CARD) != INVENTORY_NOTECARD) {
llMessageLinked(LINK_SET, CE_EVT_CFG_DUMP, cfgDump(), gOwner);
if (gCamActive) {
if (gMoving || gExternDrive) gPendingBaseApply = TRUE;
else applyBaseForMode();
}
return;
}
gCfgQuery = llGetNotecardLine(CFG_CARD, gCfgLine);
}
cfgParseLine(string line)
{
line = llStringTrim(line, STRING_TRIM);
if (line == "") return;
if (llGetSubString(line, 0, 0) == "#") return;
if (llGetSubString(line, 0, 1) == "//") return;
integer eq = llSubStringIndex(line, "=");
if (eq < 1) return;
string k = llToLower(llStringTrim(llGetSubString(line, 0, eq - 1), STRING_TRIM));
string v = llStringTrim(llGetSubString(line, eq + 1, -1), STRING_TRIM);
if (k == "move_step") gMOVE_STEP = (float)v;
else if (k == "follow_step") gFOLLOW_STEP = (float)v;
else if (k == "default_move_ms") gDEFAULT_MOVE_MS = (integer)v;
else if (k == "default_focus_dist") gDEFAULT_FOCUS_DIST = (float)v;
else if (k == "move_pos_lag") gMOVE_POS_LAG = (float)v;
else if (k == "move_focus_lag") gMOVE_FOCUS_LAG = (float)v;
else if (k == "follow_pos_lag") gFOLLOW_POS_LAG = (float)v;
else if (k == "follow_focus_lag") gFOLLOW_FOCUS_LAG = (float)v;
else if (k == "pos_threshold") gPOS_THRESHOLD = (float)v;
else if (k == "focus_threshold") gFOCUS_THRESHOLD = (float)v;
else if (k == "follow_predict") gFOLLOW_PREDICT = (float)v;
else if (k == "tour_cam_min_interval") {
gTOUR_CAM_MIN_INTERVAL = clampf((float)v, 0.0, 0.25);
}
else if (k == "tour_pos_epsilon") {
gTOUR_POS_EPS = clampf((float)v, 0.0, 1.0);
}
else if (k == "tour_focus_epsilon") {
gTOUR_FOCUS_EPS = clampf((float)v, 0.0, 1.0);
}
else if (k == "tour_max_points") {
integer n = (integer)v;
if (n < 2) n = 2;
if (n > 40) n = 40; // safety cap
gTOUR_MAX_POINTS = n;
}
// Backward-compatible aliases
else if (k == "camera_position_lag" || k == "poslag") {
float f = (float)v;
gMOVE_POS_LAG = f; gFOLLOW_POS_LAG = f;
}
else if (k == "camera_focus_lag" || k == "focuslag") {
float f2 = (float)v;
gMOVE_FOCUS_LAG = f2; gFOLLOW_FOCUS_LAG = f2;
}
gBaseParamsDirty = TRUE;
}
string cfgDump()
{
return
"move_step=" + (string)gMOVE_STEP + "|" +
"follow_step=" + (string)gFOLLOW_STEP + "|" +
"default_move_ms=" + (string)gDEFAULT_MOVE_MS + "|" +
"default_focus_dist=" + (string)gDEFAULT_FOCUS_DIST + "|" +
"move_pos_lag=" + (string)gMOVE_POS_LAG + "|" +
"move_focus_lag=" + (string)gMOVE_FOCUS_LAG + "|" +
"follow_pos_lag=" + (string)gFOLLOW_POS_LAG + "|" +
"follow_focus_lag=" + (string)gFOLLOW_FOCUS_LAG + "|" +
"pos_threshold=" + (string)gPOS_THRESHOLD + "|" +
"focus_threshold=" + (string)gFOCUS_THRESHOLD + "|" +
"follow_predict=" + (string)gFOLLOW_PREDICT + "|" +
"tour_cam_min_interval=" + (string)gTOUR_CAM_MIN_INTERVAL + "|" +
"tour_pos_epsilon=" + (string)gTOUR_POS_EPS + "|" +
"tour_focus_epsilon=" + (string)gTOUR_FOCUS_EPS + "|" +
"tour_max_points=" + (string)gTOUR_MAX_POINTS;
}
// ---------- LSL events ----------
default
{
state_entry()
{
gOwner = llGetOwner();
cfgStart();
}
on_rez(integer sp)
{
gOwner = llGetOwner();
}
attach(key id)
{
gOwner = llGetOwner();
if (id == NULL_KEY) {
releaseCam();
}
}
run_time_permissions(integer perm)
{
gHasPerm = ((perm & PERMISSION_CONTROL_CAMERA) != 0);
if (gHasPerm) {
applyBaseForMode();
llMessageLinked(LINK_SET, CE_EVT_READY, "CAM_OK", gOwner);
if (gMovePending) {
gMovePending = FALSE;
startMove(gMovePendId, gMovePendDurMs, gMovePendPos, gMovePendFoc);
}
} else {
gHasPerm = FALSE;
llMessageLinked(LINK_SET, CE_EVT_DENIED, "CAM_DENIED", gOwner);
}
}
dataserver(key qid, string data)
{
if (qid != gCfgQuery) return;
if (data == EOF) {
if (gCamActive) {
if (gMoving || gExternDrive) gPendingBaseApply = TRUE;
else applyBaseForMode();
}
llMessageLinked(LINK_SET, CE_EVT_CFG_DUMP, cfgDump(), gOwner);
return;
}
cfgParseLine(data);
gCfgLine++;
gCfgQuery = llGetNotecardLine(CFG_CARD, gCfgLine);
}
timer()
{
if (!camControlOk()) {
// verhindert Spam-Warnungen und "tote" Moves
gHasPerm = FALSE;
gMoving = FALSE;
gExternDrive = FALSE;
llSetTimerEvent(0.0);
return;
}
if (gExternDrive) return; // TourEngine drives the camera
if (gMoving) {
float now = llGetTime();
float t = (now - gMoveStart) / gMoveDur;
if (t >= 1.0) {
gMoving = FALSE;
gCurPos = gTargetPos;
gCurFocus = gTargetFocus;
gCurRot = rotFromPosFocus(gCurPos, gCurFocus);
llSetCameraParams([CAMERA_POSITION, gCurPos, CAMERA_FOCUS, gCurFocus]);
llMessageLinked(LINK_SET, CE_EVT_MOVE_DONE, (string)gMoveId, gOwner);
if (gPendingBaseApply) {
gPendingBaseApply = FALSE;
applyBaseForMode();
} else {
applyBaseForMode();
}
setTimerForMode();
return;
}
float e = smootherstep(t);
vector pos = gStartPos + (gTargetPos - gStartPos) * e;
vector foc = gStartFocus + (gTargetFocus - gStartFocus) * e;
gCurPos = pos;
gCurFocus = foc;
llSetCameraParams([CAMERA_POSITION, pos, CAMERA_FOCUS, foc]);
return;
}
// Secondary modes (only when not moving)
vector pos = gCurPos;
vector foc = gCurFocus;
integer did = FALSE;
if (gFollowOn && gFollowTarget != NULL_KEY) {
list d = llGetObjectDetails(gFollowTarget, [OBJECT_POS, OBJECT_ROT, OBJECT_VELOCITY]);
integer len = llGetListLength(d);
if (len < 1) {
gFollowOn = FALSE;
} else {
vector p = llList2Vector(d, 0);
rotation r = ZERO_ROTATION;
vector v = ZERO_VECTOR;
if (len >= 2) r = llList2Rot(d, 1);
if (len >= 3) v = llList2Vector(d, 2);
p += v * gFOLLOW_PREDICT;
if (gFollowMode == FOLLOW_WORLD) {
pos = p + gCamOffW;
foc = p + gFocusOffW;
}
else if (gFollowMode == FOLLOW_LOCAL) {
pos = p + (gCamOffL * r);
foc = p + (gFocusOffL * r);
}
else {
rotation yawR = yawOnlyRot(r, v);
pos = p + (gCamOffYaw * yawR);
foc = p + (gFocusOffYaw * yawR);
}
if (gFollowBlendOn) {
float b = (llGetTime() - gFollowBlendStart) / gFollowBlendDur;
if (b >= 1.0) { b = 1.0; gFollowBlendOn = FALSE; }
pos = gFollowBlendStartCam + (pos - gFollowBlendStartCam) * b;
foc = gFollowBlendStartFocus + (foc - gFollowBlendStartFocus) * b;
}
did = TRUE;
}
}
if (gLockOn) {
if (gLockTarget != NULL_KEY) {
list det2 = llGetObjectDetails(gLockTarget, [OBJECT_POS]);
if (llGetListLength(det2) >= 1) {
vector tpos = llList2Vector(det2, 0);
foc = tpos + gLockTargetOffset;
did = TRUE;
}
} else {
foc = gLockFocus;
did = TRUE;
}
}
if (did && gHasPerm) {
gCurPos = pos;
gCurFocus = foc;
llSetCameraParams([CAMERA_POSITION, pos, CAMERA_FOCUS, foc]);
}
}
link_message(integer sender, integer num, string str, key id)
{
// ---- internal tour drive ----
if (num == CE_INT_TOUR_BEGIN) {
gExternDrive = TRUE;
gMoving = TRUE;
gMoveId = (integer)str;
if (gHasPerm) {
applyBaseMove();
gCamActive = TRUE;
}
llSetTimerEvent(0.0);
return;
}
if (num == CE_INT_SET_CAM) {
if (!gHasPerm || !gExternDrive) return;
integer sep = llSubStringIndex(str, "|");
if (sep < 1) return;
vector pos = (vector)llGetSubString(str, 0, sep - 1);
vector foc = (vector)llGetSubString(str, sep + 1, -1);
gCurPos = pos;
gCurFocus = foc;
llSetCameraParams([CAMERA_POSITION, pos, CAMERA_FOCUS, foc]);
return;
}
if (num == CE_INT_TOUR_END) {
gExternDrive = FALSE;
gMoving = FALSE;
if (gPendingBaseApply) { gPendingBaseApply = FALSE; }
applyBaseForMode();
setTimerForMode();
return;
}
if (num == CE_INT_TOUR_STOP) {
// IMPORTANT:
// Core broadcastet CE_INT_TOUR_STOP um TourEngine zu stoppen.
// Ohne Guard würde Core damit JEDE normale MOVE sofort wieder beenden.
if (!gExternDrive) return; // ignorieren, wenn wir nicht extern gefahren werden
gExternDrive = FALSE;
gMoving = FALSE;
if (gPendingBaseApply) { gPendingBaseApply = FALSE; }
applyBaseForMode();
setTimerForMode();
return;
}
// ---- controller protocol ----
if (num == CE_CMD_INIT) { requestCamPerm(); return; }
if (num == CE_CMD_RELEASE) { releaseCam(); return; }
if (num == CE_CMD_CFG_RELOAD) { cfgStart(); return; }
if (num == CE_CMD_CFG_DUMP) { llMessageLinked(LINK_SET, CE_EVT_CFG_DUMP, cfgDump(), gOwner); return; }
if (num == CE_CMD_STOP) { stopMove(); return; }
if (num == CE_CMD_TOUR) {
// handled by HS_CamTourEngine.lsl
return;
}
if (num == CE_CMD_MOVE) {
list p = llParseString2List(str, ["|"], []);
if (llGetListLength(p) < 4) return;
integer mid = (integer)llList2String(p, 0);
integer dms = (integer)llList2String(p, 1);
vector pos = (vector)llList2String(p, 2);
vector foc = (vector)llList2String(p, 3);
// Permission wirklich prüfen (nicht nur gHasPerm vertrauen)
if (!camControlOk()) {
gHasPerm = FALSE;
gMovePending = TRUE;
gMovePendId = mid;
gMovePendDurMs = dms;
gMovePendPos = pos;
gMovePendFoc = foc;
requestCamPerm(); // hol Permission nach
return;
}
gHasPerm = TRUE;
startMove(mid, dms, pos, foc);
return;
}
if (num == CE_CMD_LOCK) {
list p = llParseString2List(str, ["|"], []);
if (llGetListLength(p) < 2) return;
gLockOn = (integer)llList2String(p, 0);
if (!gLockOn) {
gLockTarget = NULL_KEY;
if (!gMoving && !gExternDrive) setTimerForMode();
return;
}
// Lock wins
gFollowOn = FALSE;
gFollowTarget = NULL_KEY;
gFollowBlendOn = FALSE;
string arg = llList2String(p, 1);
if (llGetSubString(arg,0,0) == "<") {
gLockTarget = NULL_KEY;
gLockFocus = (vector)arg;
} else {
key k = (key)arg;
if (k != NULL_KEY) gLockTarget = k;
else { gLockTarget = NULL_KEY; gLockFocus = (vector)arg; }
}
if (gHasPerm && !gCamActive) applyBaseForMode();
applyLockOnce();
if (!gMoving && !gExternDrive) setTimerForMode();
return;
}
if (num == CE_CMD_FOLLOW) {
list p = llParseString2List(str, ["|"], []);
integer len = llGetListLength(p);
if (len < 2) return;
gFollowOn = (integer)llList2String(p, 0);
gFollowTarget = (key)llList2String(p, 1);
if (gFollowOn) {
gLockOn = FALSE;
gLockTarget = NULL_KEY;
}
vector posOff = ZERO_VECTOR;
vector focOff = ZERO_VECTOR;
if (len >= 4) {
posOff = (vector)llList2String(p, 2);
focOff = (vector)llList2String(p, 3);
}
integer mode = FOLLOW_WORLD;
integer trans = 0;
if (len >= 5) mode = (integer)llList2String(p, 4);
if (len >= 6) trans = (integer)llList2String(p, 5);
if (!gFollowOn) {
gFollowTarget = NULL_KEY;
gFollowBlendOn = FALSE;
if (!gExternDrive) setTimerForMode();
return;
}
if (gFollowTarget == NULL_KEY) gFollowTarget = gOwner;
if (gHasPerm && !gCamActive) applyBaseForMode();
integer ok = FALSE;
if (posOff != ZERO_VECTOR || focOff != ZERO_VECTOR) {
gFollowMode = FOLLOW_WORLD;
gCamOffW = posOff;
gFocusOffW = focOff;
gFollowBlendOn = (trans > 0);
if (gFollowBlendOn) {
gFollowBlendDur = (float)trans / 1000.0;
if (gFollowBlendDur < 0.01) gFollowBlendDur = 0.01;
gFollowBlendStart = llGetTime();
if (camTrackOk()) {
vector camPos = llGetCameraPos();
rotation camRot = llGetCameraRot();
gFollowBlendStartCam = camPos;
gFollowBlendStartFocus = camPos + (llRot2Fwd(camRot) * gDEFAULT_FOCUS_DIST);
} else {
gFollowBlendStartCam = gCurPos;
gFollowBlendStartFocus = gCurFocus;
}
}
ok = TRUE;
} else {
ok = followCapture(gFollowTarget, mode, trans);
}
if (!ok) {
gFollowOn = FALSE;
gFollowTarget = NULL_KEY;
gFollowBlendOn = FALSE;
if (!gExternDrive) setTimerForMode();
return;
}
applyFollowOnce();
if (!gMoving && !gExternDrive) setTimerForMode();
return;
}
float fov = 1.04719755; // default 60 Grad
if (num == CE_CMD_GET_STATE) {
string reqId = str;
if (camTrackOk()) {
vector camPos = llGetCameraPos();
rotation camRot = llGetCameraRot();
gCurPos = camPos;
gCurRot = camRot;
gCurFocus = camPos + (llRot2Fwd(camRot) * gDEFAULT_FOCUS_DIST);
fov = llGetCameraFOV(); // radians, viewer-reported
} else {
if (gCurRot == ZERO_ROTATION) gCurRot = rotFromPosFocus(gCurPos, gCurFocus);
}
llMessageLinked(LINK_SET, CE_EVT_STATE,
reqId + "|" + (string)gCurPos + "|" + (string)gCurFocus + "|" + (string)gCurRot + "|" + (string)fov,
gOwner
);
return;
}
}
}