992 lines
29 KiB
Plaintext
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;
|
|
}
|
|
}
|
|
}
|