2017-03-17 10:45:13 -07:00
//
// sit.js
//
// Created by Clement Brisset on 3/3/17
// Copyright 2017 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
//
2017-02-15 15:15:17 -08:00
( function ( ) {
2017-02-17 11:43:09 -08:00
Script . include ( "/~/system/libraries/utils.js" ) ;
2017-03-17 17:16:08 -07:00
if ( ! String . prototype . startsWith ) {
String . prototype . startsWith = function ( searchString , position ) {
2018-03-12 13:17:23 -07:00
position = position || 0 ;
return this . substr ( position , searchString . length ) === searchString ;
} ;
2017-03-17 17:16:08 -07:00
}
2017-02-27 16:32:34 -08:00
var SETTING _KEY = "com.highfidelity.avatar.isSitting" ;
2020-09-22 19:24:58 -04:00
var ANIMATION _URL = Script . getExternalPath ( Script . ExternalPaths . HF _Content , "/clement/production/animations/sitting_idle.fbx" ) ;
2017-02-17 11:43:09 -08:00
var ANIMATION _FPS = 30 ;
var ANIMATION _FIRST _FRAME = 1 ;
var ANIMATION _LAST _FRAME = 10 ;
var RELEASE _TIME = 500 ; // ms
var RELEASE _DISTANCE = 0.2 ; // meters
2017-03-13 17:22:06 -08:00
var MAX _IK _ERROR = 30 ;
2017-03-15 17:39:38 -07:00
var IK _SETTLE _TIME = 250 ; // ms
2017-03-13 17:22:06 -08:00
var DESKTOP _UI _CHECK _INTERVAL = 100 ;
2017-02-17 11:43:09 -08:00
var DESKTOP _MAX _DISTANCE = 5 ;
2017-03-15 17:39:38 -07:00
var SIT _DELAY = 25 ;
var MAX _RESET _DISTANCE = 0.5 ; // meters
2017-03-16 18:37:58 -07:00
var OVERRIDEN _DRIVE _KEYS = [
DriveKeys . TRANSLATE _X ,
DriveKeys . TRANSLATE _Y ,
DriveKeys . TRANSLATE _Z ,
DriveKeys . STEP _TRANSLATE _X ,
DriveKeys . STEP _TRANSLATE _Y ,
DriveKeys . STEP _TRANSLATE _Z ,
] ;
2017-02-17 11:43:09 -08:00
this . entityID = null ;
this . animStateHandlerID = null ;
2017-03-13 17:22:06 -08:00
this . interval = null ;
2017-03-16 14:30:33 -07:00
this . sitDownSettlePeriod = null ;
2017-03-15 17:39:38 -07:00
this . lastTimeNoDriveKeys = null ;
2017-03-17 10:45:13 -07:00
this . sittingDown = false ;
2017-02-17 11:43:09 -08:00
2017-03-17 17:17:05 -07:00
// Preload the animation file
this . animation = AnimationCache . prefetch ( ANIMATION _URL ) ;
2017-02-17 11:43:09 -08:00
this . preload = function ( entityID ) {
this . entityID = entityID ;
2017-02-15 18:10:33 -08:00
}
2017-02-17 11:43:09 -08:00
this . unload = function ( ) {
2017-03-13 17:22:06 -08:00
if ( Settings . getValue ( SETTING _KEY ) === this . entityID ) {
2017-03-15 17:39:38 -07:00
this . standUp ( ) ;
2017-02-17 11:43:09 -08:00
}
2017-03-13 17:22:06 -08:00
if ( this . interval !== null ) {
2017-02-17 11:43:09 -08:00
Script . clearInterval ( this . interval ) ;
this . interval = null ;
}
this . cleanupOverlay ( ) ;
}
2017-02-22 11:32:07 -08:00
this . setSeatUser = function ( user ) {
2017-03-13 17:22:06 -08:00
try {
var userData = Entities . getEntityProperties ( this . entityID , [ "userData" ] ) . userData ;
userData = JSON . parse ( userData ) ;
if ( user !== null ) {
userData . seat . user = user ;
} else {
delete userData . seat . user ;
}
2017-02-22 11:32:07 -08:00
2017-03-13 17:22:06 -08:00
Entities . editEntity ( this . entityID , {
userData : JSON . stringify ( userData )
} ) ;
} catch ( e ) {
// Do Nothing
2017-02-22 11:32:07 -08:00
}
}
this . getSeatUser = function ( ) {
2017-03-13 17:22:06 -08:00
try {
var properties = Entities . getEntityProperties ( this . entityID , [ "userData" , "position" ] ) ;
var userData = JSON . parse ( properties . userData ) ;
// If MyAvatar return my uuid
if ( userData . seat . user === MyAvatar . sessionUUID ) {
return userData . seat . user ;
}
2017-02-22 11:32:07 -08:00
2017-03-13 17:22:06 -08:00
// If Avatar appears to be sitting
if ( userData . seat . user ) {
var avatar = AvatarList . getAvatar ( userData . seat . user ) ;
if ( avatar && ( Vec3 . distance ( avatar . position , properties . position ) < RELEASE _DISTANCE ) ) {
return userData . seat . user ;
}
2017-02-22 11:32:07 -08:00
}
2017-03-13 17:22:06 -08:00
} catch ( e ) {
// Do nothing
2017-02-22 11:32:07 -08:00
}
2017-03-13 17:22:06 -08:00
// Nobody on the seat
return null ;
2017-02-22 11:32:07 -08:00
}
2017-03-13 17:22:06 -08:00
// Is the seat used
2017-02-17 11:43:09 -08:00
this . checkSeatForAvatar = function ( ) {
2017-02-22 11:32:07 -08:00
var seatUser = this . getSeatUser ( ) ;
2017-03-13 17:22:06 -08:00
// If MyAvatar appears to be sitting
if ( seatUser === MyAvatar . sessionUUID ) {
var properties = Entities . getEntityProperties ( this . entityID , [ "position" ] ) ;
return Vec3 . distance ( MyAvatar . position , properties . position ) < RELEASE _DISTANCE ;
2017-02-17 11:43:09 -08:00
}
2017-03-13 17:22:06 -08:00
return seatUser !== null ;
2017-02-16 10:59:37 -08:00
}
2017-03-17 17:16:08 -07:00
this . rolesToOverride = function ( ) {
return MyAvatar . getAnimationRoles ( ) . filter ( function ( role ) {
2018-03-12 13:17:23 -07:00
return ! ( role . startsWith ( "right" ) || role . startsWith ( "left" ) ) ;
2017-03-17 17:16:08 -07:00
} ) ;
}
2018-03-12 13:17:23 -07:00
// Handler for user changing the avatar model while sitting. There's currently an issue with changing avatar models while override role animations are applied,
// so to avoid that problem, re-apply the role overrides once the model has finished changing.
this . modelURLChangeFinished = function ( ) {
print ( "Sitter's model has FINISHED changing. Reapply anim role overrides." ) ;
var roles = this . rolesToOverride ( ) ;
for ( i in roles ) {
MyAvatar . overrideRoleAnimation ( roles [ i ] , ANIMATION _URL , ANIMATION _FPS , true , ANIMATION _FIRST _FRAME , ANIMATION _LAST _FRAME ) ;
}
}
2017-02-17 11:43:09 -08:00
this . sitDown = function ( ) {
if ( this . checkSeatForAvatar ( ) ) {
print ( "Someone is already sitting in that chair." ) ;
return ;
}
2017-03-15 17:39:38 -07:00
print ( "Sitting down (" + this . entityID + ")" ) ;
2017-03-17 10:45:13 -07:00
this . sittingDown = true ;
2017-03-15 17:39:38 -07:00
2017-03-16 14:30:33 -07:00
var now = Date . now ( ) ;
this . sitDownSettlePeriod = now + IK _SETTLE _TIME ;
this . lastTimeNoDriveKeys = now ;
2017-02-17 11:43:09 -08:00
2017-02-27 16:32:34 -08:00
var previousValue = Settings . getValue ( SETTING _KEY ) ;
Settings . setValue ( SETTING _KEY , this . entityID ) ;
2017-03-13 17:22:06 -08:00
this . setSeatUser ( MyAvatar . sessionUUID ) ;
2017-02-27 16:32:34 -08:00
if ( previousValue === "" ) {
MyAvatar . characterControllerEnabled = false ;
MyAvatar . hmdLeanRecenterEnabled = false ;
2017-03-17 17:16:08 -07:00
var roles = this . rolesToOverride ( ) ;
2018-03-21 15:35:10 -07:00
for ( var i = 0 ; i < roles . length ; i ++ ) {
2017-03-17 17:16:08 -07:00
MyAvatar . overrideRoleAnimation ( roles [ i ] , ANIMATION _URL , ANIMATION _FPS , true , ANIMATION _FIRST _FRAME , ANIMATION _LAST _FRAME ) ;
2017-03-01 17:49:52 -08:00
}
2017-03-17 10:45:13 -07:00
2018-03-21 15:35:10 -07:00
for ( i = 0 ; i < OVERRIDEN _DRIVE _KEYS . length ; i ++ ) {
2017-03-17 10:45:13 -07:00
MyAvatar . disableDriveKey ( OVERRIDEN _DRIVE _KEYS [ i ] ) ;
}
2017-02-27 17:33:26 -08:00
MyAvatar . resetSensorsAndBody ( ) ;
2017-02-27 16:32:34 -08:00
}
2017-02-27 15:03:15 -08:00
2017-03-13 17:22:06 -08:00
var properties = Entities . getEntityProperties ( this . entityID , [ "position" , "rotation" ] ) ;
var index = MyAvatar . getJointIndex ( "Hips" ) ;
MyAvatar . pinJoint ( index , properties . position , properties . rotation ) ;
this . animStateHandlerID = MyAvatar . addAnimationStateHandler ( function ( properties ) {
return { headType : 0 } ;
} , [ "headType" ] ) ;
Script . update . connect ( this , this . update ) ;
2018-03-12 13:17:23 -07:00
MyAvatar . onLoadComplete . connect ( this , this . modelURLChangeFinished ) ;
2017-02-15 15:15:17 -08:00
}
2017-02-17 11:43:09 -08:00
2017-03-15 17:39:38 -07:00
this . standUp = function ( ) {
print ( "Standing up (" + this . entityID + ")" ) ;
2017-03-13 17:22:06 -08:00
MyAvatar . removeAnimationStateHandler ( this . animStateHandlerID ) ;
Script . update . disconnect ( this , this . update ) ;
2018-03-12 13:17:23 -07:00
MyAvatar . onLoadComplete . disconnect ( this , this . modelURLChangeFinished ) ;
2017-02-17 11:43:09 -08:00
2017-03-17 16:11:17 -07:00
if ( MyAvatar . sessionUUID === this . getSeatUser ( ) ) {
this . setSeatUser ( null ) ;
}
2017-03-06 16:10:11 -08:00
if ( Settings . getValue ( SETTING _KEY ) === this . entityID ) {
2017-03-13 17:22:06 -08:00
Settings . setValue ( SETTING _KEY , "" ) ;
2017-03-17 10:45:13 -07:00
2018-03-21 15:35:10 -07:00
for ( var i = 0 ; i < OVERRIDEN _DRIVE _KEYS . length ; i ++ ) {
2017-03-17 10:45:13 -07:00
MyAvatar . enableDriveKey ( OVERRIDEN _DRIVE _KEYS [ i ] ) ;
}
2017-03-17 17:16:08 -07:00
var roles = this . rolesToOverride ( ) ;
2018-03-21 15:35:10 -07:00
for ( i = 0 ; i < roles . length ; i ++ ) {
2017-03-17 17:16:08 -07:00
MyAvatar . restoreRoleAnimation ( roles [ i ] ) ;
2017-03-01 17:49:52 -08:00
}
2017-02-27 16:32:34 -08:00
MyAvatar . characterControllerEnabled = true ;
MyAvatar . hmdLeanRecenterEnabled = true ;
2017-02-17 11:43:09 -08:00
2017-02-27 16:32:34 -08:00
var index = MyAvatar . getJointIndex ( "Hips" ) ;
MyAvatar . clearPinOnJoint ( index ) ;
2017-02-17 11:43:09 -08:00
2017-02-22 12:13:22 -08:00
MyAvatar . resetSensorsAndBody ( ) ;
2017-02-21 14:01:51 -08:00
2017-02-27 16:32:34 -08:00
Script . setTimeout ( function ( ) {
MyAvatar . bodyPitch = 0.0 ;
MyAvatar . bodyRoll = 0.0 ;
} , SIT _DELAY ) ;
2017-02-17 11:43:09 -08:00
}
2017-03-17 10:45:13 -07:00
this . sittingDown = false ;
2017-02-15 15:15:17 -08:00
}
2017-02-17 11:43:09 -08:00
2017-03-15 17:39:38 -07:00
// function called by teleport.js if it detects the appropriate userData
2017-02-17 11:43:09 -08:00
this . sit = function ( ) {
this . sitDown ( ) ;
2017-02-16 10:59:37 -08:00
}
2017-02-17 11:43:09 -08:00
this . createOverlay = function ( ) {
var text = "Click to sit" ;
var textMargin = 0.05 ;
var lineHeight = 0.15 ;
2017-02-15 15:15:17 -08:00
2017-02-17 11:43:09 -08:00
this . overlay = Overlays . addOverlay ( "text3d" , {
position : { x : 0.0 , y : 0.0 , z : 0.0 } ,
dimensions : { x : 0.1 , y : 0.1 } ,
backgroundColor : { red : 0 , green : 0 , blue : 0 } ,
color : { red : 255 , green : 255 , blue : 255 } ,
topMargin : textMargin ,
leftMargin : textMargin ,
bottomMargin : textMargin ,
rightMargin : textMargin ,
text : text ,
lineHeight : lineHeight ,
alpha : 0.9 ,
backgroundAlpha : 0.9 ,
ignoreRayIntersection : true ,
visible : true ,
isFacingAvatar : true
} ) ;
var textSize = Overlays . textSize ( this . overlay , text ) ;
var overlayDimensions = {
x : textSize . width + 2 * textMargin ,
y : textSize . height + 2 * textMargin
}
2017-02-22 11:32:07 -08:00
var properties = Entities . getEntityProperties ( this . entityID , [ "position" , "registrationPoint" , "dimensions" ] ) ;
var yOffset = ( 1.0 - properties . registrationPoint . y ) * properties . dimensions . y + ( overlayDimensions . y / 2.0 ) ;
var overlayPosition = Vec3 . sum ( properties . position , { x : 0 , y : yOffset , z : 0 } ) ;
2017-02-17 11:43:09 -08:00
Overlays . editOverlay ( this . overlay , {
position : overlayPosition ,
dimensions : overlayDimensions
} ) ;
}
this . cleanupOverlay = function ( ) {
if ( this . overlay !== null ) {
Overlays . deleteOverlay ( this . overlay ) ;
this . overlay = null ;
}
}
2017-02-15 19:41:06 -08:00
2017-02-17 11:43:09 -08:00
this . update = function ( dt ) {
2017-03-17 10:45:13 -07:00
if ( this . sittingDown === true ) {
2017-03-06 16:10:11 -08:00
var properties = Entities . getEntityProperties ( this . entityID ) ;
2017-02-17 11:43:09 -08:00
var avatarDistance = Vec3 . distance ( MyAvatar . position , properties . position ) ;
var ikError = MyAvatar . getIKErrorOnLastSolve ( ) ;
2017-03-15 17:39:38 -07:00
var now = Date . now ( ) ;
var shouldStandUp = false ;
// Check if a drive key is pressed
var hasActiveDriveKey = false ;
for ( var i in OVERRIDEN _DRIVE _KEYS ) {
2018-03-21 15:35:10 -07:00
if ( MyAvatar . getRawDriveKey ( OVERRIDEN _DRIVE _KEYS [ i ] ) !== 0.0 ) {
2017-03-15 17:39:38 -07:00
hasActiveDriveKey = true ;
break ;
2017-03-06 16:10:11 -08:00
}
2017-03-15 17:39:38 -07:00
}
2017-03-06 16:10:11 -08:00
2017-03-15 17:39:38 -07:00
// Only standup if user has been pushing a drive key for RELEASE_TIME
if ( hasActiveDriveKey ) {
var elapsed = now - this . lastTimeNoDriveKeys ;
shouldStandUp = elapsed > RELEASE _TIME ;
} else {
this . lastTimeNoDriveKeys = Date . now ( ) ;
}
// Allow some time for the IK to settle
2017-03-16 14:30:33 -07:00
if ( ikError > MAX _IK _ERROR && now > this . sitDownSettlePeriod ) {
shouldStandUp = true ;
2017-02-17 11:43:09 -08:00
}
2017-02-15 15:15:17 -08:00
2017-03-17 16:11:17 -07:00
if ( MyAvatar . sessionUUID !== this . getSeatUser ( ) ) {
shouldStandUp = true ;
}
2017-03-13 17:22:06 -08:00
2017-03-15 17:39:38 -07:00
if ( shouldStandUp || avatarDistance > RELEASE _DISTANCE ) {
print ( "IK error: " + ikError + ", distance from chair: " + avatarDistance ) ;
2017-03-06 16:10:11 -08:00
// Move avatar in front of the chair to avoid getting stuck in collision hulls
if ( avatarDistance < MAX _RESET _DISTANCE ) {
var offset = { x : 0 , y : 1.0 , z : - 0.5 - properties . dimensions . z * properties . registrationPoint . z } ;
var position = Vec3 . sum ( properties . position , Vec3 . multiplyQbyV ( properties . rotation , offset ) ) ;
MyAvatar . position = position ;
2017-03-17 16:11:47 -07:00
print ( "Moving Avatar in front of the chair." ) ;
// Delay standing up by 1 cycle.
// This leaves times for the avatar to actually move since a lot
// of the stand up operations are threaded
return ;
2017-03-06 16:10:11 -08:00
}
2017-03-15 17:39:38 -07:00
this . standUp ( ) ;
2017-02-17 11:43:09 -08:00
}
}
}
this . canSitDesktop = function ( ) {
2017-02-22 11:32:07 -08:00
var properties = Entities . getEntityProperties ( this . entityID , [ "position" ] ) ;
var distanceFromSeat = Vec3 . distance ( MyAvatar . position , properties . position ) ;
2017-02-17 11:43:09 -08:00
return distanceFromSeat < DESKTOP _MAX _DISTANCE && ! this . checkSeatForAvatar ( ) ;
}
2017-02-15 19:41:06 -08:00
2017-02-17 11:43:09 -08:00
this . hoverEnterEntity = function ( event ) {
2017-03-13 17:22:06 -08:00
if ( isInEditMode ( ) || this . interval !== null ) {
2017-02-17 11:43:09 -08:00
return ;
}
2017-02-15 18:10:33 -08:00
2017-02-17 11:43:09 -08:00
var that = this ;
this . interval = Script . setInterval ( function ( ) {
if ( that . overlay === null ) {
if ( that . canSitDesktop ( ) ) {
that . createOverlay ( ) ;
}
} else if ( ! that . canSitDesktop ( ) ) {
that . cleanupOverlay ( ) ;
}
} , DESKTOP _UI _CHECK _INTERVAL ) ;
}
this . hoverLeaveEntity = function ( event ) {
2017-03-13 17:22:06 -08:00
if ( this . interval !== null ) {
2017-02-17 11:43:09 -08:00
Script . clearInterval ( this . interval ) ;
this . interval = null ;
}
this . cleanupOverlay ( ) ;
2017-02-15 18:10:33 -08:00
}
2018-03-12 13:17:23 -07:00
2017-03-06 16:09:31 -08:00
this . clickDownOnEntity = function ( id , event ) {
2017-03-13 17:22:06 -08:00
if ( isInEditMode ( ) ) {
2017-02-17 11:43:09 -08:00
return ;
}
2017-03-06 16:09:31 -08:00
if ( event . isPrimaryButton && this . canSitDesktop ( ) ) {
2017-02-17 11:43:09 -08:00
this . sitDown ( ) ;
}
2017-02-15 15:15:17 -08:00
}
2018-03-12 13:17:23 -07:00
} ) ;