// // Created by Triplelexx on 16/05/26 // Copyright 2016 High Fidelity, Inc. // // An entity that can be sat apon // // Sitting animations adapted by Triplelexx from version obtained from Mixamo // Links provided to copies of original animations created by High Fidelity, Inc // This is due to issues requiring use of overrideRoleAnimation to chain animations and not knowing a better way // to reference them cross-platform. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html (function() { var _this; const SIT_MAPPING_NAME = "chair.sit.override"; const HAND_MAPPING_NAME = "chair.hand.override"; const IK_TYPES = { RotationAndPosition: 0, RotationOnly: 1, HmdHead: 2, HipsRelativeRotationAndPosition: 3, Off: 4 }; //http://hifi-content.s3.amazonaws.com/Examples%20Content/production/sitsystem/idle.fbx const STAND_IMAGE_URL = "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/sitsystem/stand.png"; const IDLE_ANIM = { url: "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/sitsystem/idle.fbx", playbackRate: 30.0, loopFlag: true, startFrame: 1.0, endFrame: 300.0 }; const IDLE_TALK_ANIM = { url: "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/sitsystem/talk.fbx", playbackRate: 30.0, loopFlag: true, startFrame: 1.0, endFrame: 800.0 }; const SIT_DOWN_ANIM = { url: "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/sitsystem/sit_down.fbx", playbackRate: 30.0, loopFlag: false, startFrame: 1.0, endFrame: 120.0 }; const SIT_IDLE_ANIM = { url: "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/sitsystem/sit_idle_inplace.fbx", playbackRate: 30.0, loopFlag: true, startFrame: 1.0, endFrame: 80.0 }; const SIT_IDLE_TALK_ANIM = { url: "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/sitsystem/sit_idle_talk_inplace.fbx", playbackRate: 30.0, loopFlag: true, startFrame: 1.0, endFrame: 80.0 }; const HALF = 0.5; const HALF_CIRCLE = 180.0; const SMALL_DIST = 0.1; const VERY_SMALL_DIST = 0.01; const SIT_INTERVAL = 5000; // in milliseconds const INTERACTION_CHECK_INTERVAL = 1000; // in milliseconds const SCALING_COMPLETE_INTERVAL = 1000; // in milliseconds const NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; const MIN_USERANGLE = 0.9; const MAX_USERDIST = 2.5; const SCALE_SPEED_FACTOR = 0.001; const STANDBUTTON_SIZE = 96; const SPHERE_TOUCH_DIST = 0.5; const SPHERE_OFFSET_UP = 0.75; const SPHERE_OFFSET_RIGHT = 0.75; const SPHERE_ALPHA = 0.75; const SPHERE_SIZE = 0.25; const SPHERE_COLOR = { red: 0, green: 200, blue: 50 }; ChairEntity = function() { _this = this; this.chairProperties = null; this.sphereOverlay = null; this.standButton = null; this.hasButton = false; this.wantsToSit = false; this.isChangingScale = false; this.isAngleCorrectForSitting = false; this.sitAnimationHandlerId = null; this.orientationMix = 0.0; this.allowScaling = true; this.allowSwivelling = false; this.seatPositionFront = Vec3.ZERO; this.seatPositionCenter = Vec3.ZERO; this.sphereOverlayPosition = Vec3.ZERO; this.originalDimensions = Vec3.ZERO; this.manualSitTarget = Vec3.ZERO; this.currentUser = NULL_UUID; this.sitDownAnimation = AnimationCache.prefetch(SIT_DOWN_ANIM.url); this.sitIdleAnimation = AnimationCache.prefetch(SIT_IDLE_ANIM.url); this.sitIdleTalkAnimation = AnimationCache.prefetch(SIT_IDLE_TALK_ANIM.url); this.idleAnimation = AnimationCache.prefetch(IDLE_ANIM.url); this.talkAnimation = AnimationCache.prefetch(IDLE_TALK_ANIM.url); this.sitEventMapping = null; this.handEventMapping = null; }; ChairEntity.prototype = { getUserData: function() { this.chairProperties = Entities.getEntityProperties(this.entityId, ["position", "rotation", "dimensions", "userData"]); try { var userData = JSON.parse(this.chairProperties.userData); if(userData.allowScaling == true || userData.allowScaling == false) { this.allowScaling = userData.allowScaling; } if(userData.allowSwivelling == true || userData.allowSwivelling == false) { this.allowSwivelling = userData.allowSwivelling; } if(userData.manualSitTarget.x != 0.0 || userData.manualSitTarget.y != 0.0 || userData.manualSitTarget.z != 0.0) { this.manualSitTarget = userData.manualSitTarget; } } catch(errorState) { print("userData error: " + errorState); } }, getChairSurface: function() { if(this.manualSitTarget.x != 0.0 && this.manualSitTarget.y != 0.0 && this.manualSitTarget.z != 0.0) { this.seatPositionCenter = this.manualSitTarget; return; } const DIST_INFRONT = 1.5; const DIST_ABOVE = 4.0; var chairPosition = this.chairProperties.position; // raycast down from above the chair chairPosition.y += DIST_ABOVE; var intersectionDown = Entities.findRayIntersection({direction: Vec3.UNIT_NEG_Y, origin: chairPosition}, true); if(intersectionDown.intersects) { if(intersectionDown.entityID === this.entityId) { // we found the highest point in middle this.seatPositionCenter = chairPosition; // set y slightly lower so next ray to find the front will hit chairPosition.y = intersectionDown.intersection.y - VERY_SMALL_DIST; // we cast a ray in front of the chair going backward to find the front of the geometry var chairFront = Quat.multiply(this.chairProperties.rotation, Quat.fromPitchYawRollDegrees(0.0, HALF_CIRCLE, 0.0)); chairPosition = Vec3.sum(chairPosition, Vec3.multiply(Quat.getFront(chairFront), DIST_INFRONT)); var intersectionFront = Entities.findRayIntersection({direction: Quat.getFront(this.chairProperties.rotation), origin: chairPosition}, true); if(intersectionFront.intersects) { if(intersectionFront.entityID === this.entityId) { this.seatPositionFront = Vec3.sum(intersectionFront.intersection, Vec3.multiply(Quat.getFront(chairFront), SMALL_DIST)); } } else { this.seatPositionFront = this.chairProperties.position; } } } else { this.seatPositionFront = this.chairProperties.position; } }, scaleChair: function() { if(!this.allowScaling) { return; } var currentDimensions = this.chairProperties.dimensions; this.originalDimensions = this.chairProperties.dimensions; var difference = Vec3.ZERO; if(MyAvatar.scale > 1.0) { difference.x = (currentDimensions.x * MyAvatar.scale) - currentDimensions.x; difference.y = (currentDimensions.y * MyAvatar.scale) - currentDimensions.y; difference.z = (currentDimensions.z * MyAvatar.scale) - currentDimensions.z; if(HMD.active){ Entities.editEntity(this.entityId, { dimensions: { x: currentDimensions.x + difference.x, y: currentDimensions.y + difference.y, z: currentDimensions.z + difference.z } }); } else { for(var i = 0.0; i < 1.0; i += SCALE_SPEED_FACTOR) { Entities.editEntity(this.entityId, { dimensions: { x: currentDimensions.x + (difference.x * i), y: currentDimensions.y + (difference.y * i), z: currentDimensions.z + (difference.z * i) } }); } } } else { difference.x = currentDimensions.x - (currentDimensions.x * MyAvatar.scale); difference.y = currentDimensions.y - (currentDimensions.y * MyAvatar.scale); difference.z = currentDimensions.z - (currentDimensions.z * MyAvatar.scale); if(HMD.active){ Entities.editEntity(this.entityId, { dimensions: { x: currentDimensions.x - difference.x, y: currentDimensions.y - difference.y, z: currentDimensions.z - difference.z } }); } else { for(var i = 0.0; i < 1.0; i += SCALE_SPEED_FACTOR) { Entities.editEntity(this.entityId, { dimensions: { x: currentDimensions.x - (difference.x * i), y: currentDimensions.y - (difference.y * i), z: currentDimensions.z - (difference.z * i) } }); } } } }, resetChairScale: function() { if(!this.allowScaling) { return; } // check again for valid data this.getUserData(); var currentDimensions = this.chairProperties.dimensions; var difference = Vec3.ZERO; if(currentDimensions.y > this.originalDimensions.y) { difference.x = currentDimensions.x - this.originalDimensions.x; difference.y = currentDimensions.y - this.originalDimensions.y; difference.z = currentDimensions.z - this.originalDimensions.z; if(HMD.active) { Entities.editEntity(this.entityId, { dimensions: { x: currentDimensions.x - difference.x, y: currentDimensions.y - difference.y, z: currentDimensions.z - difference.z } }); } else { for(var i = 0.0; i < 1.0; i += SCALE_SPEED_FACTOR) { Entities.editEntity(this.entityId, { dimensions: { x: currentDimensions.x - (difference.x * i), y: currentDimensions.y - (difference.y * i), z: currentDimensions.z - (difference.z * i) } }); } } } else { difference.x = this.originalDimensions.x - currentDimensions.x; difference.y = this.originalDimensions.y - currentDimensions.y; difference.z = this.originalDimensions.z - currentDimensions.z; if(HMD.active) { Entities.editEntity(this.entityId, { dimensions: { x: currentDimensions.x + difference.x, y: currentDimensions.y + difference.y, z: currentDimensions.z + difference.z } }); } else { for(var i = 0.0; i < 1.0; i += SCALE_SPEED_FACTOR) { Entities.editEntity(this.entityId, { dimensions: { x: currentDimensions.x + (difference.x * i), y: currentDimensions.y + (difference.y * i), z: currentDimensions.z + (difference.z * i) } }); } } } }, moveAvatarToChairCenter: function() { //disable collisions MyAvatar.characterControllerEnabled = false; const SEAT_OFFSET = 0.2; if(this.allowScaling) { this.seatPositionCenter.y += SEAT_OFFSET * MyAvatar.scale; } else { this.seatPositionCenter.y += SEAT_OFFSET; } var frontVec = Quat.getFront(this.chairProperties.rotation); this.seatPositionCenter = Vec3.sum(this.seatPositionCenter, Vec3.multiply(-SMALL_DIST, frontVec)); MyAvatar.position = this.seatPositionCenter; }, moveAvatarToChairFront: function() { //enable collisions MyAvatar.characterControllerEnabled = true; MyAvatar.position = this.seatPositionFront; }, maybeSit: function() { const MAX_ROT = 5.0; // block unacceptable attempts including the chair being tipped if(this.wantsToSit || this.isChangingScale || this.currentUser !== NULL_UUID || this.chairProperties.rotation.x > MAX_ROT || this.chairProperties.rotation.x < -MAX_ROT || this.chairProperties.rotation.z > MAX_ROT || this.chairProperties.rotation.z < -MAX_ROT) { return; } this.scaleChair(); this.isChangingScale = true; // sit instantly with HMD if(HMD.active) { //get the post-scaled chair surface this.getChairSurface(); this.wantsToSit = true; this.isAngleCorrectForSitting = false; this.orientationMix = 0.0; this.isChangingScale = false; } else { // allow time for scaling then proceed Script.setTimeout(function() { //get the post-scaled chair surface _this.getChairSurface(); _this.wantsToSit = true; _this.isAngleCorrectForSitting = false; _this.orientationMix = 0.0; _this.isChangingScale = false; }, SCALING_COMPLETE_INTERVAL); } Entities.clickReleaseOnEntity.disconnect(this.clickReleaseOnEntity); }, sitDown: function() { MyAvatar.overrideRoleAnimation("idleStand", SIT_DOWN_ANIM.url, SIT_DOWN_ANIM.playbackRate, SIT_DOWN_ANIM.loopFlag, SIT_DOWN_ANIM.startFrame, SIT_DOWN_ANIM.endFrame); function animateSit() { return { leftFootType: IK_TYPES.Off, rightFootType: IK_TYPES.Off, rightHandType: IK_TYPES.Off, leftHandType: IK_TYPES.Off, neckType: IK_TYPES.Off, headType: IK_TYPES.Off, isTalking: false, isFlying: false, isNotMoving: true, ikOverlayAlpha: 0.0, isMovingForward: false, isMovingBackward: false, isMovingLeft: false, isMovingRight: false, isNotTurning: true, isTurningLeft: false, isTurningRight: false, inAirAlpha: 0.0 }; } sitAnimationHandlerId = MyAvatar.addAnimationStateHandler(animateSit, [ "leftFootType", "rightFootType", "rightHandType", "leftHandType", "neckType", "headType", "isTalking", "isFlying", "isNotMoving", "ikOverlayAlpha", "isMovingForward", "isMovingBackward", "isMovingLeft", "isMovingRight", "isNotTurning", "isTurningLeft", "isTurningRight", "inAirAlpha" ] ); this.setSitControllerMapping(); MyAvatar.setParentID(this.entityId); Script.setTimeout(function() { _this.sitFinished(); }, SIT_INTERVAL); }, sitFinished: function() { if(HMD.active) { this.moveSphereOverlay(); MyAvatar.hmdLeanRecenterEnabled = false; } else { this.createStandButton(); MyAvatar.removeAnimationStateHandler(sitAnimationHandlerId); } function animateSit() { return { leftFootType: IK_TYPES.Off, rightFootType: IK_TYPES.Off, rightHandType: IK_TYPES.HipsRelativeRotationAndPosition, leftHandType: IK_TYPES.HipsRelativeRotationAndPosition, neckType: IK_TYPES.HipsRelativeRotationAndPosition, headType: IK_TYPES.HipsRelativeRotationAndPosition, isFlying: false, isNotMoving: true, ikOverlayAlpha: 1.0, isMovingForward: false, isMovingBackward: false, isMovingLeft: false, isMovingRight: false, isNotTurning: true, isTurningLeft: false, isTurningRight: false, inAirAlpha: 0.0 }; } sitAnimationHandlerId = MyAvatar.addAnimationStateHandler(animateSit, [ "leftFootType", "rightFootType", "rightHandType", "leftHandType", "neckType", "headType", "isFlying", "isNotMoving", "ikOverlayAlpha", "isMovingForward", "isMovingBackward", "isMovingLeft", "isMovingRight", "isNotTurning", "isTurningLeft", "isTurningRight", "inAirAlpha" ] ); MyAvatar.overrideRoleAnimation("idleStand", SIT_IDLE_ANIM.url, SIT_IDLE_ANIM.playbackRate, SIT_IDLE_ANIM.loopFlag, SIT_IDLE_ANIM.startFrame, SIT_IDLE_ANIM.endFrame); MyAvatar.overrideRoleAnimation("idleTalk", SIT_IDLE_TALK_ANIM.url, SIT_IDLE_TALK_ANIM.playbackRate, SIT_IDLE_TALK_ANIM.loopFlag, SIT_IDLE_TALK_ANIM.startFrame, SIT_IDLE_TALK_ANIM.endFrame); this.currentUser = MyAvatar.sessionUUID; // we move the avatar itself rather than using motion from the animation this.moveAvatarToChairCenter(); }, standUp: function() { print("STANDING"); MyAvatar.removeAnimationStateHandler(sitAnimationHandlerId); MyAvatar.overrideRoleAnimation("idleStand", IDLE_ANIM.url, IDLE_ANIM.playbackRate, IDLE_ANIM.loopFlag, IDLE_ANIM.startFrame, IDLE_ANIM.endFrame); MyAvatar.overrideRoleAnimation("idleTalk", IDLE_TALK_ANIM.url, IDLE_TALK_ANIM.playbackRate, IDLE_TALK_ANIM.loopFlag, IDLE_TALK_ANIM.startFrame, IDLE_TALK_ANIM.endFrame); if(HMD.active) { MyAvatar.hmdLeanRecenterEnabled = true; this.moveSphereOverlayBack(); } else { this.removeStandButton(); } MyAvatar.setParentID(NULL_UUID); this.currentUser = NULL_UUID; this.moveAvatarToChairFront(); this.resetChairScale(); this.removeSitControllerMapping(); Entities.clickReleaseOnEntity.connect(this.clickReleaseOnEntity); }, createSphereOverlay: function() { // check again for valid data this.getUserData(); this.setHandControllerMapping(); this.sphereOverlayPosition = this.chairProperties.position; this.sphereOverlayPosition.y += SPHERE_OFFSET_UP; this.sphereOverlay = Overlays.addOverlay("sphere", { size: SPHERE_SIZE, color: SPHERE_COLOR, position: this.sphereOverlayPosition, alpha: SPHERE_ALPHA, visible: true, solid: true, drawInFront: false }); }, moveSphereOverlay: function() { var rightVec = Quat.getRight(this.chairProperties.rotation); this.sphereOverlayPosition = Vec3.sum(this.sphereOverlayPosition, Vec3.multiply(-SPHERE_OFFSET_RIGHT, rightVec)); Overlays.editOverlay(this.sphereOverlay, { position: this.sphereOverlayPosition }); }, moveSphereOverlayBack: function() { var rightVec = Quat.getRight(this.chairProperties.rotation); this.sphereOverlayPosition = Vec3.sum(this.sphereOverlayPosition, Vec3.multiply(SPHERE_OFFSET_RIGHT, rightVec)); Overlays.editOverlay(this.sphereOverlay, { position: this.sphereOverlayPosition }); }, removeSphereOverlay: function() { Overlays.deleteOverlay(this.sphereOverlay); this.removeHandControllerMapping(); this.sphereOverlay = null; }, createStandButton: function() { if (!this.hasButton) { var windowDimensions = Controller.getViewportDimensions(); var buttonWidth = STANDBUTTON_SIZE; var buttonHeight = STANDBUTTON_SIZE; var buttonPadding = STANDBUTTON_SIZE * HALF; var buttonPositionX = (buttonWidth + buttonPadding) + (windowDimensions.x * HALF) - (buttonWidth + buttonPadding); var buttonPositionY = (buttonHeight * HALF) + buttonPadding; this.standButton = Overlays.addOverlay("image", { x: buttonPositionX, y: buttonPositionY, width: buttonWidth, height: buttonHeight, imageURL: STAND_IMAGE_URL, visible: true, alpha: 1.0 }); Controller.mousePressEvent.connect(this.mousePressEvent); this.hasButton = true; } }, removeStandButton: function() { Overlays.deleteOverlay(this.standButton); Controller.mousePressEvent.disconnect(this.mousePressEvent); this.hasButton = false; }, setSitControllerMapping: function () { this.sitEventMapping = Controller.newMapping(SIT_MAPPING_NAME); // I am trying to disable inputs to keep the avatar in the chair, is there a neater way to do this? // keyboard seems to map directly to actions if(!this.allowSwivelling) { this.sitEventMapping.from(Controller.Hardware.Keyboard.MouseMoveLeft).to(function () { }); this.sitEventMapping.from(Controller.Hardware.Keyboard.MouseMoveRight).to(function () { }); } this.sitEventMapping.from(Controller.Standard.LX).to(function () { }); this.sitEventMapping.from(Controller.Standard.LY).to(function () { }); this.sitEventMapping.from(Controller.Standard.RX).to(function () { }); this.sitEventMapping.from(Controller.Hardware.Keyboard.W).to(function () { }); this.sitEventMapping.from(Controller.Hardware.Keyboard.S).to(function () { }); this.sitEventMapping.from(Controller.Hardware.Keyboard.A).to(function () { }); this.sitEventMapping.from(Controller.Hardware.Keyboard.D).to(function () { }); this.sitEventMapping.from(Controller.Hardware.Keyboard.Left).to(function () { }); this.sitEventMapping.from(Controller.Hardware.Keyboard.Right).to(function () { }); this.sitEventMapping.from(Controller.Hardware.Keyboard.Up).to(function () { }); this.sitEventMapping.from(Controller.Hardware.Keyboard.Down).to(function () { }); this.sitEventMapping.from(Controller.Hardware.Keyboard.E).to(function () { }); this.sitEventMapping.from(Controller.Hardware.Keyboard.C).to(function () { }); Controller.enableMapping(SIT_MAPPING_NAME, true); }, removeSitControllerMapping: function () { Controller.disableMapping(SIT_MAPPING_NAME); }, setHandControllerMapping: function () { this.handEventMapping = Controller.newMapping(HAND_MAPPING_NAME); this.handEventMapping.from(Controller.Standard.RT).peek().to(function () { if(_this.currentUser === MyAvatar.sessionUUID && _this.isTouchingSphereOverlay()) { _this.standUp(); } else if(_this.currentUser === NULL_UUID && _this.isTouchingSphereOverlay()) { _this.maybeSit(); } }); this.handEventMapping.from(Controller.Standard.LT).peek().to(function () { if(_this.currentUser === MyAvatar.sessionUUID && _this.isTouchingSphereOverlay()) { _this.standUp(); } else if(_this.currentUser === NULL_UUID && _this.isTouchingSphereOverlay()) { _this.maybeSit(); } }); Controller.enableMapping(HAND_MAPPING_NAME, true); }, removeHandControllerMapping: function () { Controller.disableMapping(HAND_MAPPING_NAME); }, isTouchingSphereOverlay: function () { var leftHandPosition = MyAvatar.getLeftPalmPosition(); var rightHandPosition = MyAvatar.getRightPalmPosition(); var sphereDistL = Vec3.distance(leftHandPosition, this.sphereOverlayPosition); var sphereDistR = Vec3.distance(rightHandPosition, this.sphereOverlayPosition); return (sphereDistL < SPHERE_TOUCH_DIST || sphereDistR < SPHERE_TOUCH_DIST); }, isCloseToChair: function() { // check again for valid data this.getUserData(); var distanceFromChair = Vec3.distance(MyAvatar.position, this.chairProperties.position); return (distanceFromChair < MAX_USERDIST) ? true : false; }, isFacingChair: function() { // check again for valid data this.getUserData(); var userAngle = Quat.dot(Quat.getFront(MyAvatar.orientation), Quat.getFront(this.chairProperties.rotation)); return (userAngle > MIN_USERANGLE) ? true : false; }, maybeToggleHMDButton: function() { if(!HMD.active) { if(this.sphereOverlay !== null) { this.removeSphereOverlay(); } return; } if(this.isCloseToChair() && this.sphereOverlay === null) { this.createSphereOverlay(); } else if (!this.isCloseToChair() && this.sphereOverlay !== null) { this.removeSphereOverlay(); } Script.setTimeout(function() { _this.maybeToggleHMDButton(); }, INTERACTION_CHECK_INTERVAL); }, mousePressEvent: function(event) { var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); if(clickedOverlay === _this.standButton) { if(_this.currentUser === MyAvatar.sessionUUID) { _this.standUp(); } } }, clickReleaseOnEntity: function(entityId, clickEvent) { if(entityId === this.entityId) { if(this.isFacingChair() && this.isCloseToChair()) { this.maybeSit(); } } }, onUpdate: function(deltaTime) { if(_this.wantsToSit) { // the angle we want to face is rotated 180 deg in y var chairFront = Quat.multiply(_this.chairProperties.rotation, Quat.fromPitchYawRollDegrees(0.0, HALF_CIRCLE, 0.0)); if(!HMD.active) { if(!_this.isAngleCorrectForSitting) { // rotate smoothly using deltaTime of the loop then sit _this.orientationMix += deltaTime; var increment = _this.orientationMix * SMALL_DIST; if(_this.orientationMix < 1.0) { MyAvatar.orientation = Quat.mix(MyAvatar.orientation, chairFront, increment); } else { MyAvatar.orientation = Quat.mix(MyAvatar.orientation, chairFront, 1.0); MyAvatar.position = _this.seatPositionFront; _this.isAngleCorrectForSitting = true; _this.sitDown(); _this.wantsToSit = false; } } } else { MyAvatar.orientation = Quat.mix(MyAvatar.orientation, chairFront, 1.0); MyAvatar.position = _this.seatPositionFront; _this.isAngleCorrectForSitting = true; // go straight to idle loop _this.sitFinished(); _this.wantsToSit = false; } } }, preload: function(entityId) { this.entityId = entityId; this.getUserData(); this.getChairSurface(); this.maybeToggleHMDButton(); Entities.clickReleaseOnEntity.connect(this.clickReleaseOnEntity); Script.update.connect(this.onUpdate); }, unload: function() { print("UNLOADING"); if(this.currentUser !== NULL_UUID) { print("USER IS NOT NULL"); this.standUp(); } if(this.sphereOverlay !== null) { this.removeSphereOverlay(); } if(this.handEventMapping !== null) { this.removeHandControllerMapping(); } Script.update.disconnect(this.onUpdate); Entities.clickReleaseOnEntity.disconnect(this.clickReleaseOnEntity); } }; return new ChairEntity(); });