708 lines
No EOL
26 KiB
JavaScript
708 lines
No EOL
26 KiB
JavaScript
//
|
|
// sitClient.js
|
|
//
|
|
// Created by Robin Wilson 5/7/2019
|
|
//
|
|
// Original sit script created by Clement Brisset on 3/3/17
|
|
// Previous script modified by Robin Wilson, Rebecca Stankus, and Alexia Mandeville June 2018
|
|
//
|
|
// Copyright 2019 High Fidelity, Inc.
|
|
//
|
|
// Distributed under the Apache License, Version 2.0.
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
//
|
|
// Allows avatars to sit in High Fidelity after clicking on a local entity or on the chair. Works with sitServer.js.
|
|
//
|
|
|
|
/* global DriveKeys */
|
|
|
|
(function () {
|
|
var DEBUG = false;
|
|
// #region UTILITIES
|
|
|
|
// Returns entity properties for an overlay in front of user's camera in desktop and VR
|
|
function getEntityPropertiesForImageInFrontOfCamera(positionInFront, dimensions, url) {
|
|
return {
|
|
type: "Image",
|
|
grab: { grabbable: false },
|
|
dynamic: false,
|
|
parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"),
|
|
imageURL: url,
|
|
position: Vec3.sum(Camera.position, Vec3.multiplyQbyV(Camera.orientation, positionInFront)),
|
|
dimensions: dimensions,
|
|
rotation: Camera.rotation,
|
|
parentID: MyAvatar.sessionUUID,
|
|
ignoreRayIntersection: false,
|
|
visible: true,
|
|
emissive: true,
|
|
renderLayer: "front"
|
|
};
|
|
}
|
|
|
|
// Checks for Avatar Spine Error in VR to avoid uncomfortable looking avatar position
|
|
var NEG_ONE = -1;
|
|
var HALF = 0.5;
|
|
function setHeadToHipsDistance() {
|
|
// get hips world pos while sitting
|
|
if (MyAvatar.getJointIndex("Head") === NEG_ONE) {
|
|
// this can probably be adjusted to be more accurate
|
|
_this.headToHipsDistance = MyAvatar.getHeight() * HALF;
|
|
} else {
|
|
var headTranslation = MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(MyAvatar.getJointIndex("Head"));
|
|
var hipsTranslation = MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(MyAvatar.getJointIndex("Hips"));
|
|
_this.headToHipsDistance = Math.abs(headTranslation.y - hipsTranslation.y);
|
|
}
|
|
}
|
|
|
|
|
|
// Calculates where the avatar's hips will seat
|
|
// Used to calculate pin hip position. Adds CHAIR_OFFSET_RATIO * chair's y dimension to the y center of the seat.
|
|
// Avatar sit position y = CHAIR_OFFSET_RATIO * height of chair
|
|
var CHAIR_OFFSET_RATIO = 0.1;
|
|
function calculateSeatCenterPositionForPinningAvatarHips() {
|
|
var properties = Entities.getEntityProperties(_this.entityID, ["position", "dimensions"]);
|
|
|
|
var yOffset = properties.dimensions.y * CHAIR_OFFSET_RATIO;
|
|
_this.seatCenterPosition = properties.position;
|
|
_this.seatCenterPosition.y = properties.position.y + yOffset;
|
|
}
|
|
|
|
// #endregion UTILITIES
|
|
|
|
// #region SIT DOWN / STAND UP SEQUENCE
|
|
|
|
// 1st of sit down sequence
|
|
// Called from entity server script to begin sitting down sequence
|
|
var SETTING_KEY_AVATAR_SITTING = "Avatar/isSitting";
|
|
var SIT_SETTLE_TIME_MS = 350; // Do not pop avatar out of chair immediately if there's an issue
|
|
function checkBeforeSitDown() {
|
|
if (DEBUG) {
|
|
console.log("sitClient.js: checkBeforeSitDown() for seat ID: ", _this.entityID);
|
|
}
|
|
|
|
var currentSeatEntityID = Settings.getValue(SETTING_KEY_AVATAR_SITTING, false);
|
|
if (currentSeatEntityID && currentSeatEntityID !== _this.entityID) {
|
|
if (DEBUG) {
|
|
console.log("sitClient.js: calling standUp with callback for seat ID: " + _this.entityID);
|
|
}
|
|
Entities.callEntityMethod(currentSeatEntityID, "standUp", [_this.entityID]);
|
|
} else {
|
|
startSitDown();
|
|
}
|
|
}
|
|
|
|
function startSitDown() {
|
|
if (DEBUG) {
|
|
console.log("sitClient.js: startSitDown() for seat ID: ", _this.entityID);
|
|
}
|
|
|
|
if (HMD.active) {
|
|
showVRPresitInstructions();
|
|
Script.setTimeout(function () {
|
|
prepareSitThenSit();
|
|
}, PRESIT_FRAME_DURATION * _this.preSitLoadedImages.length);
|
|
} else {
|
|
prepareSitThenSit();
|
|
}
|
|
}
|
|
|
|
// 3rd of sit down sequence (callback for inside 2nd)
|
|
// Used before pinning hips, calculate the math required and lock the chair
|
|
function prepareSitThenSit() {
|
|
// Set the value of head to hips distance
|
|
// If avatar deviates outside of the minimum and maximum, the avatar will pop out of the chair
|
|
setHeadToHipsDistance();
|
|
calculateSeatCenterPositionForPinningAvatarHips();
|
|
|
|
// Lock chair and save old lock property
|
|
_this.locked = Entities.getEntityProperties(_this.entityID, 'locked').locked;
|
|
Entities.editEntity(_this.entityID, { locked: true });
|
|
|
|
sitDownAndPinAvatar();
|
|
}
|
|
|
|
// Listen to action events. If jump is pressed, the user will stand up
|
|
var JUMP_ACTION_ID = 50;
|
|
function onActionEvent(actionID, value) {
|
|
if (actionID === JUMP_ACTION_ID) {
|
|
standUp();
|
|
}
|
|
}
|
|
|
|
var UPDATE_INTERVAL_MS = 400;
|
|
var isActionEventConnected = false;
|
|
var isSittingInThisChair = false;
|
|
function sitDownAndPinAvatar() {
|
|
if (HMD.active) {
|
|
deletePresit();
|
|
}
|
|
if (!isActionEventConnected) {
|
|
Controller.actionEvent.connect(onActionEvent);
|
|
isActionEventConnected = true;
|
|
}
|
|
|
|
var properties = Entities.getEntityProperties(_this.entityID, ["rotation"]);
|
|
MyAvatar.beginSit(_this.seatCenterPosition, properties.rotation);
|
|
|
|
_this.sitDownSettlePeriod = Date.now() + SIT_SETTLE_TIME_MS;
|
|
stopUpdateInterval();
|
|
_this.whileSittingUpdateIntervalID = Script.setInterval(_this.whileSittingUpdate, UPDATE_INTERVAL_MS);
|
|
|
|
// Set sit value in Settings `Interface.json`
|
|
Settings.setValue(SETTING_KEY_AVATAR_SITTING, _this.entityID);
|
|
isSittingInThisChair = true;
|
|
|
|
maybeClearRequestSitDataTimeoutTimer();
|
|
maybeClearRequestSitDataTimer();
|
|
|
|
Entities.callEntityServerMethod(
|
|
_this.entityID,
|
|
"removeThisSittableOverlayForEveryoneElse",
|
|
AvatarList.getAvatarIdentifiers()
|
|
);
|
|
deleteAllClickToSitOverlays();
|
|
|
|
if (!_this.connectedStandUpSignals) {
|
|
MyAvatar.scaleChanged.connect(_this.standUp);
|
|
MyAvatar.onLoadComplete.connect(_this.standUp);
|
|
location.hostChanged.connect(_this.standUp);
|
|
Script.scriptEnding.connect(_this.standUp);
|
|
MyAvatar.wentAway.connect(_this.standUp);
|
|
HMD.displayModeChanged.connect(_this.standUp);
|
|
_this.connectedStandUpSignals = true;
|
|
}
|
|
}
|
|
|
|
// 5th of sit down sequence
|
|
// Handles stand up conditions
|
|
// Update interval while the avatar is sitting
|
|
// Listens for drive keys held, spine error (in VR), and if avatar teleported away from chair
|
|
var AVATAR_MOVED_TOO_FAR_DISTANCE_M = 0.5;
|
|
var MAX_HEAD_DEVIATION_RATIO = 1.2;
|
|
var MIN_HEAD_DEVIATION_RATIO = 0.8;
|
|
var HEAD_DEVIATION_MAX_TIME_TO_STAND_MS = 1000;
|
|
function whileSittingUpdate() {
|
|
var now = Date.now();
|
|
|
|
if (_this.sitDownSettlePeriod && now < _this.sitDownSettlePeriod) {
|
|
// below considerations only apply if sit down settle period has passed
|
|
return;
|
|
} else {
|
|
_this.sitDownSettlePeriod = false;
|
|
}
|
|
|
|
// AVATAR DISTANCE
|
|
if (Vec3.distance(MyAvatar.position, _this.seatCenterPosition) > AVATAR_MOVED_TOO_FAR_DISTANCE_M) {
|
|
_this.standUp();
|
|
if (DEBUG) {
|
|
console.log("sitClient.js: avatar distance caused standup");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// AVATAR SPINE ERROR
|
|
if (HMD.active) {
|
|
// If head is more than certain distance from hips or less than certain distance, stand up
|
|
var headPoseValue = Controller.getPoseValue(Controller.Standard.Head);
|
|
var headWorldPosition = Vec3.sum(
|
|
Vec3.multiplyQbyV(MyAvatar.orientation, headPoseValue.translation),
|
|
MyAvatar.position
|
|
);
|
|
var headDeviation = Vec3.distance(headWorldPosition, _this.seatCenterPosition);
|
|
var headDeviationRatio = headDeviation / _this.headToHipsDistance;
|
|
|
|
if (headDeviationRatio < MIN_HEAD_DEVIATION_RATIO || headDeviationRatio > MAX_HEAD_DEVIATION_RATIO) {
|
|
if (!_this.deviationTimeStart) {
|
|
_this.deviationTimeStart = now;
|
|
}
|
|
if (now - _this.deviationTimeStart > HEAD_DEVIATION_MAX_TIME_TO_STAND_MS) {
|
|
_this.deviationTimeStart = false;
|
|
_this.standUp();
|
|
if (DEBUG) {
|
|
console.log("sitClient.js: avatar spine error caused standup");
|
|
}
|
|
}
|
|
} else {
|
|
_this.deviationTimeStart = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear whileSitting update interval
|
|
function stopUpdateInterval() {
|
|
if (_this.whileSittingUpdateIntervalID) {
|
|
Script.clearInterval(_this.whileSittingUpdateIntervalID);
|
|
_this.whileSittingUpdateIntervalID = false;
|
|
}
|
|
}
|
|
|
|
|
|
function maybeClearRequestSitDataTimer() {
|
|
if (requestSitDataTimer) {
|
|
Script.clearTimeout(requestSitDataTimer);
|
|
requestSitDataTimer = false;
|
|
}
|
|
}
|
|
|
|
|
|
var requestSitDataTimer = false;
|
|
var REQUEST_SIT_DATA_TIMEOUT_MS = 12000;
|
|
var MAX_REQUEST_DISTANCE_M = 40;
|
|
function requestSitDataTimerCallback() {
|
|
if (DEBUG) {
|
|
console.log("sitClient.js: We're inside `requestSitDataTimerCallback()` for Entity ID: " + _this.entityID + "...");
|
|
}
|
|
|
|
requestSitDataTimer = false;
|
|
|
|
var entityPosition = Entities.getEntityProperties(_this.entityID, ["position"]).position;
|
|
var distance = Vec3.distance(entityPosition, Camera.position);
|
|
if (distance <= MAX_REQUEST_DISTANCE_M) {
|
|
if (DEBUG) {
|
|
console.log("sitClient.js: The camera is close enough to this seat! Requesting sit data from sitServer...");
|
|
}
|
|
requestSitData(_this.entityID);
|
|
} else {
|
|
if (DEBUG) {
|
|
console.log("sitClient.js: The camera is too far away from this seat! Waiting " + REQUEST_SIT_DATA_TIMEOUT_MS + "ms before trying again...");
|
|
}
|
|
requestSitDataTimer = Script.setTimeout(requestSitDataTimerCallback, REQUEST_SIT_DATA_TIMEOUT_MS);
|
|
}
|
|
}
|
|
|
|
|
|
function maybeClearRequestSitDataTimeoutTimer() {
|
|
currentRequestAttempts = 0;
|
|
if (requestSitDataTimeoutTimer) {
|
|
Script.clearTimeout(requestSitDataTimeoutTimer);
|
|
requestSitDataTimeoutTimer = false;
|
|
}
|
|
}
|
|
|
|
|
|
var currentRequestAttempts = 0;
|
|
var MAX_REQUEST_ATTEMPTS = 3;
|
|
function requestSitDataTimeoutTimerCallback() {
|
|
requestSitDataTimeoutTimer = false;
|
|
if (currentRequestAttempts < MAX_REQUEST_ATTEMPTS) {
|
|
console.log("sitClient.js: Request for sit data timed out for Entity ID: " + _this.entityID +
|
|
". Trying again. This will be attempt " + currentRequestAttempts + "/" + MAX_REQUEST_ATTEMPTS);
|
|
requestSitData(_this.entityID);
|
|
} else {
|
|
currentRequestAttempts = 0;
|
|
console.log("sitClient.js: Request for sit data timed out for Entity ID: " + _this.entityID +
|
|
". " + MAX_REQUEST_ATTEMPTS + " requests for data failed. Not trying again.");
|
|
}
|
|
}
|
|
|
|
|
|
var requestSitDataTimeoutTimer = false;
|
|
var REQUEST_SIT_DATA_TIMEOUT_MS = 6000;
|
|
function requestSitData(id) {
|
|
if (DEBUG) {
|
|
console.log("sitClient.js: Requesting sit data...");
|
|
}
|
|
|
|
currentRequestAttempts++;
|
|
requestSitDataTimeoutTimer = Script.setTimeout(requestSitDataTimeoutTimerCallback, REQUEST_SIT_DATA_TIMEOUT_MS);
|
|
Entities.callEntityServerMethod(id, "requestSitData", [MyAvatar.sessionUUID]);
|
|
}
|
|
|
|
|
|
function requestSitDataReply(id, args) {
|
|
maybeClearRequestSitDataTimeoutTimer();
|
|
maybeClearRequestSitDataTimer();
|
|
|
|
var data;
|
|
try {
|
|
data = JSON.parse(args[0]);
|
|
} catch (e) {
|
|
console.log("sitClient.js: Error when parsing sit data reply! "+ e);
|
|
return;
|
|
}
|
|
var isOccupied = data.isOccupied;
|
|
|
|
if (DEBUG) {
|
|
console.log("sitClient.js: Got sit data reply! `isOccupied`: " + isOccupied);
|
|
}
|
|
|
|
// If someone is sitting in the seat...
|
|
if (isOccupied) {
|
|
deleteAllClickToSitOverlays();
|
|
// Else if someone is NOT sitting in the seat (as reported by the server async) and we haven't started sitting
|
|
// in this seat (i.e. isSittingInThisChair hasn't turned `true` since the server sent us data)
|
|
} else if (!isSittingInThisChair) {
|
|
createClickToSitOverlay();
|
|
}
|
|
|
|
requestSitDataTimer = Script.setTimeout(requestSitDataTimerCallback, REQUEST_SIT_DATA_TIMEOUT_MS);
|
|
}
|
|
|
|
|
|
// Standup functionality
|
|
var alreadyCalledStandUp = false;
|
|
function standUp(id, args) {
|
|
if (DEBUG) {
|
|
console.log("sitClient.js: standup from seat ID: " + _this.entityID + "\nCall has args: " + (args && args[0]));
|
|
}
|
|
|
|
if (isSittingInThisChair) {
|
|
if (DEBUG) {
|
|
console.log("sitClient.js: we are indeed sitting in this chair.");
|
|
}
|
|
|
|
if (alreadyCalledStandUp) {
|
|
if (DEBUG) {
|
|
console.log("sitClient.js: we already called standup for: " + _this.entityID + "; no action taken");
|
|
}
|
|
return;
|
|
}
|
|
|
|
alreadyCalledStandUp = true;
|
|
|
|
stopUpdateInterval();
|
|
_this.sitDownSettlePeriod = false;
|
|
|
|
if (isActionEventConnected) {
|
|
Controller.actionEvent.disconnect(onActionEvent);
|
|
isActionEventConnected = false;
|
|
}
|
|
MyAvatar.endSit(MyAvatar.position, MyAvatar.orientation);
|
|
|
|
Settings.setValue(SETTING_KEY_AVATAR_SITTING, null);
|
|
isSittingInThisChair = false;
|
|
|
|
// Reset locked state for this chair
|
|
if (!_this.locked) {
|
|
Entities.editEntity(_this.entityID, { locked: false });
|
|
}
|
|
|
|
Entities.callEntityServerMethod(_this.entityID, "onStandUp", AvatarList.getAvatarIdentifiers());
|
|
createClickToSitOverlay();
|
|
requestSitDataTimeoutTimer = Script.setTimeout(requestSitDataTimeoutTimerCallback, REQUEST_SIT_DATA_TIMEOUT_MS);
|
|
|
|
if (_this.connectedStandUpSignals) {
|
|
MyAvatar.scaleChanged.disconnect(_this.standUp);
|
|
MyAvatar.onLoadComplete.disconnect(_this.standUp);
|
|
location.hostChanged.disconnect(_this.standUp);
|
|
Script.scriptEnding.disconnect(_this.standUp);
|
|
MyAvatar.wentAway.disconnect(_this.standUp);
|
|
HMD.displayModeChanged.disconnect(_this.standUp);
|
|
_this.connectedStandUpSignals = false;
|
|
}
|
|
|
|
alreadyCalledStandUp = false;
|
|
}
|
|
|
|
if (args) {
|
|
if (DEBUG) {
|
|
console.log("sitClient.js: standUp callback requested. starting sit down for: " + args[0]);
|
|
}
|
|
|
|
Entities.callEntityMethod(args[0], "startSitDown");
|
|
}
|
|
}
|
|
|
|
// #region PRESIT - LOCAL ENTITY shown in HMD before sitting and after clicking sittable overlay
|
|
// Has the sitting animation and "Please Face Forward"
|
|
|
|
// Prefetch all presit overlay images into user's client
|
|
var PRESIT_FRAME_DURATION = 160; // ms time duration for HMD presit overlay
|
|
var PRESIT_URL_ROOT = Script.resolvePath("./resources/images/presit/sitConfirm");
|
|
var PRESIT_URL_POSTFIX = ".png";
|
|
var PRESIT_URL_NUM = 12;
|
|
var PRESIT_URL_TEXT = Script.resolvePath("./resources/images/presit/pleaseFaceForward.png");
|
|
function prefetchPresitImages() {
|
|
var str;
|
|
for (var i = 0; i < PRESIT_URL_NUM; i++) {
|
|
str = i + 1;
|
|
_this.preSitLoadedImages[i] = TextureCache.prefetch(Script.resolvePath(PRESIT_URL_ROOT + str + PRESIT_URL_POSTFIX));
|
|
}
|
|
_this.preSitTextLoadedImage = TextureCache.prefetch(PRESIT_URL_TEXT);
|
|
}
|
|
|
|
// Create the VR presit animation local entity in front of user's screen
|
|
var SIT_ANIMATION_POSITION_IN_FRONT = { x: 0, y: 0.1, z: -1 };
|
|
var SIT_ANIMATION_DIMENSIONS = { x: 0.2, y: 0.2 };
|
|
var PLEASE_FACE_FORWARD_POSITION_IN_FRONT = { x: 0, y: -0.05, z: -1 };
|
|
var PLEASE_FACE_FORWARD_DIMENSIONS = { x: 0.425, y: 0.425 };
|
|
function showVRPresitInstructions() {
|
|
var currentPresitAnimationFrame = 0;
|
|
_this.presitAnimationImageID = Entities.addEntity(
|
|
getEntityPropertiesForImageInFrontOfCamera(
|
|
SIT_ANIMATION_POSITION_IN_FRONT,
|
|
SIT_ANIMATION_DIMENSIONS,
|
|
_this.preSitLoadedImages[currentPresitAnimationFrame].url
|
|
),
|
|
"local"
|
|
);
|
|
|
|
_this.presitTextID = Entities.addEntity(
|
|
getEntityPropertiesForImageInFrontOfCamera(
|
|
PLEASE_FACE_FORWARD_POSITION_IN_FRONT,
|
|
PLEASE_FACE_FORWARD_DIMENSIONS,
|
|
_this.preSitTextLoadedImage.url
|
|
),
|
|
"local"
|
|
);
|
|
|
|
// Flash through the presit animation images via overlay for a smooth avatar sitting animation
|
|
_this.presitIntervalID = Script.setInterval(function () {
|
|
if (_this.presitAnimationImageID) {
|
|
currentPresitAnimationFrame = currentPresitAnimationFrame + 1;
|
|
if (currentPresitAnimationFrame >= _this.preSitLoadedImages.length - 1) {
|
|
deletePresit();
|
|
}
|
|
Entities.editEntity(_this.presitAnimationImageID, { imageURL:
|
|
_this.preSitLoadedImages[currentPresitAnimationFrame].url });
|
|
}
|
|
}, PRESIT_FRAME_DURATION);
|
|
}
|
|
|
|
// Delete presit local entity in user's screen
|
|
function deletePresit() {
|
|
if (_this.presitAnimationImageID) {
|
|
Entities.deleteEntity(_this.presitAnimationImageID);
|
|
_this.presitAnimationImageID = false;
|
|
}
|
|
if (_this.presitTextID) {
|
|
Entities.deleteEntity(_this.presitTextID);
|
|
_this.presitTextID = false;
|
|
}
|
|
if (_this.presitIntervalID) {
|
|
Script.clearInterval(_this.presitIntervalID);
|
|
_this.presitIntervalID = false;
|
|
}
|
|
}
|
|
|
|
// #endregion PRESIT
|
|
|
|
// #region SITTABLE LOCAL ENTITY
|
|
|
|
// Create "Click to Sit" overlay on the chair
|
|
var SITTABLE_START_ALPHA = 0.7;
|
|
var SITTABLE_DIMENSIONS = { x: 0.3, y: 0.3 };
|
|
var SITTABLE_IMAGE_URL_HMD = Script.resolvePath("./resources/images/triggerToSit.png");
|
|
var SITTABLE_IMAGE_URL_DESKTOP = Script.resolvePath("./resources/images/clickToSit.png");
|
|
var SITTABLE_Y_OFFSET = 0.01;
|
|
function createClickToSitOverlay() {
|
|
if (_this.clickToSitOverlay) {
|
|
// already created; delete all other sit overlays
|
|
deleteAllOtherClickToSitOverlays();
|
|
return;
|
|
}
|
|
|
|
if (DEBUG) {
|
|
console.log("sitClient.js: createClickToSitOverlay()");
|
|
}
|
|
var properties = Entities.getEntityProperties(_this.entityID);
|
|
|
|
// calculate
|
|
var localOffset = { x: 0, y: SITTABLE_Y_OFFSET, z: 0 };
|
|
var worldOffset = Vec3.multiplyQbyV(properties.rotation, localOffset);
|
|
var sittablePosition = Vec3.sum(properties.position, worldOffset);
|
|
|
|
_this.clickToSitOverlay = Entities.addEntity({
|
|
type: "Image",
|
|
name: "Click to Sit Local Entity",
|
|
grab: {
|
|
grabbable: false
|
|
},
|
|
dynamic: false,
|
|
position: sittablePosition,
|
|
rotation: Quat.multiply(
|
|
properties.rotation,
|
|
Quat.fromVec3Degrees({ x: -90, y: 180, z: 0 })
|
|
),
|
|
parentID: _this.entityID,
|
|
dimensions: SITTABLE_DIMENSIONS,
|
|
imageURL: HMD.active ? SITTABLE_IMAGE_URL_HMD : SITTABLE_IMAGE_URL_DESKTOP,
|
|
ignoreRayIntersection: false,
|
|
alpha: SITTABLE_START_ALPHA,
|
|
script: Script.resolvePath("./resources/sittableUIClient.js"),
|
|
visible: true,
|
|
emissive: true
|
|
}, "local");
|
|
}
|
|
|
|
|
|
// Remove all sittable local entities except the one defined as `_this.clickToSitOverlay`.
|
|
function deleteAllOtherClickToSitOverlays() {
|
|
if (DEBUG) {
|
|
print("deleteAllOtherClickToSitOverlays()");
|
|
}
|
|
|
|
var children = Entities.getChildrenIDs(_this.entityID);
|
|
for (var i = 0; i < children.length; i++) {
|
|
var entityName = Entities.getEntityProperties(children[i], ["name"]).name;
|
|
if (entityName && entityName.indexOf("Click to Sit") > -1 &&
|
|
children[i] !== _this.clickToSitOverlay) {
|
|
Entities.deleteEntity(children[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove all sittable local entities
|
|
// In some cases that we do not yet understand, it is possible for multiple "Click to Sit Overlays" to appear
|
|
// above a sit point, with one overlay not respecting the orientation of the seat entity.
|
|
// Ensuring that _all_ "Click to Sit Overlays" are deleted is an attempt at alleviating that issue.
|
|
function deleteAllClickToSitOverlays() {
|
|
if (DEBUG) {
|
|
print("deleteAllClickToSitOverlays()");
|
|
}
|
|
|
|
var children = Entities.getChildrenIDs(_this.entityID);
|
|
for (var i = 0; i < children.length; i++) {
|
|
var entityName = Entities.getEntityProperties(children[i], ["name"]).name;
|
|
if (entityName && entityName.indexOf("Click to Sit") > -1) {
|
|
Entities.deleteEntity(children[i]);
|
|
}
|
|
}
|
|
_this.clickToSitOverlay = false;
|
|
}
|
|
// #endregion SITTABLE
|
|
|
|
// #region ENTITY LIFETIME FUNCTIONS
|
|
|
|
// Check userData for configurable settings
|
|
// If userData is not configured with the right keys update with defaults
|
|
var DEFAULT_SIT_USER_DATA_WITH_CUSTOM_SETTINGS = {
|
|
canClickOnModelToSit: false
|
|
};
|
|
function updateUserData() {
|
|
var userData = Entities.getEntityProperties(_this.entityID, ["userData"]).userData;
|
|
try {
|
|
userData = JSON.parse(userData);
|
|
} catch (e) {
|
|
console.log("sitClient.js: No userData present on seat entity or other error parsing userData; assigning default values...");
|
|
userData = false;
|
|
}
|
|
|
|
if (!userData || userData.canClickOnModelToSit === undefined) {
|
|
Entities.editEntity(_this.entityID, { "userData": JSON.stringify(DEFAULT_SIT_USER_DATA_WITH_CUSTOM_SETTINGS) });
|
|
_this.userData = DEFAULT_SIT_USER_DATA_WITH_CUSTOM_SETTINGS;
|
|
return;
|
|
}
|
|
|
|
if (userData.canClickOnModelToSit !== undefined && (_this.userData.canClickOnModelToSit !== userData.canClickOnModelToSit)) {
|
|
_this.userData = userData;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Preload entity method
|
|
function preload(id) {
|
|
_this.entityID = id;
|
|
deleteAllClickToSitOverlays();
|
|
prefetchPresitImages();
|
|
updateUserData();
|
|
requestSitData(_this.entityID);
|
|
}
|
|
|
|
// Unload entity method
|
|
function unload() {
|
|
if (DEBUG) {
|
|
console.log("sitClient.js: Unloading Sit client script...");
|
|
}
|
|
|
|
maybeClearRequestSitDataTimeoutTimer();
|
|
maybeClearRequestSitDataTimer();
|
|
|
|
deleteAllClickToSitOverlays();
|
|
deletePresit();
|
|
standUp();
|
|
|
|
if (_this.connectedStandUpSignals) {
|
|
MyAvatar.scaleChanged.disconnect(_this.standUp);
|
|
MyAvatar.onLoadComplete.disconnect(_this.standUp);
|
|
location.hostChanged.disconnect(_this.standUp);
|
|
Script.scriptEnding.disconnect(_this.standUp);
|
|
MyAvatar.wentAway.disconnect(_this.standUp);
|
|
HMD.displayModeChanged.disconnect(_this.standUp);
|
|
_this.connectedStandUpSignals = false;
|
|
}
|
|
}
|
|
|
|
|
|
// Called by server script in heartbeat to ensure avatar is still sitting in chair
|
|
function heartbeatRequest() {
|
|
Entities.callEntityServerMethod(_this.entityID, "heartbeatResponse");
|
|
}
|
|
|
|
|
|
// Can sit when clicking on chair when enabled via userData
|
|
var MAX_SIT_DISTANCE_M = 5;
|
|
var EDIT_SETTING = "io.highfidelity.isEditing";
|
|
function mousePressOnEntity(id, event) {
|
|
if (event.isPrimaryButton && !Settings.getValue(EDIT_SETTING, false)) {
|
|
updateUserData();
|
|
if (_this.userData && _this.userData.canClickOnModelToSit &&
|
|
Vec3.distance(MyAvatar.position, Entities.getEntityProperties(id, ["position"]).position) <= MAX_SIT_DISTANCE_M) {
|
|
Entities.callEntityServerMethod(_this.entityID, "onMousePressOnEntity", [MyAvatar.sessionUUID]);
|
|
}
|
|
}
|
|
}
|
|
// #endregion ENTITY LIFETIME FUNCTIONS
|
|
|
|
// Constructor
|
|
var _this = null;
|
|
function SitClient() {
|
|
_this = this;
|
|
this.sittableUIID = false;
|
|
|
|
// for presit local entity and animation
|
|
this.presitIntervalID = false;
|
|
this.presitTextID = false;
|
|
this.presitAnimationImageID = false;
|
|
|
|
// for prefetch on presit
|
|
this.preSitLoadedImages = [];
|
|
this.preSitTextLoadedImage = null;
|
|
|
|
this.locked = null;
|
|
|
|
this.seatCenterPosition = null;
|
|
this.headToHipsDistance = null;
|
|
this.sitDownSettlePeriod = false;
|
|
|
|
this.whileSittingUpdateIntervalID = false;
|
|
|
|
this.standUpID = false;
|
|
|
|
this.deviationTimeStart = false;
|
|
|
|
this.connectedStandUpSignals = false;
|
|
|
|
// CUSTOMIZATIONS
|
|
this.userData = {};
|
|
this.canClickOnModelToSit = false;
|
|
}
|
|
|
|
// Entity methods
|
|
SitClient.prototype = {
|
|
remotelyCallable: [
|
|
"createClickToSitOverlay",
|
|
"requestSitDataReply",
|
|
"deleteAllClickToSitOverlays",
|
|
"checkBeforeSitDown",
|
|
"heartbeatRequest",
|
|
"startSitDown"
|
|
],
|
|
createClickToSitOverlay: createClickToSitOverlay,
|
|
requestSitData: requestSitData,
|
|
requestSitDataReply: requestSitDataReply,
|
|
deleteAllClickToSitOverlays: deleteAllClickToSitOverlays,
|
|
// Entity liftime methods
|
|
preload: preload,
|
|
unload: unload,
|
|
// Sitting lifetime methods
|
|
mousePressOnEntity: mousePressOnEntity,
|
|
checkBeforeSitDown: checkBeforeSitDown,
|
|
whileSittingUpdate: whileSittingUpdate,
|
|
heartbeatRequest: heartbeatRequest,
|
|
standUp: standUp,
|
|
startSitDown: startSitDown
|
|
};
|
|
|
|
return new SitClient();
|
|
}); |