From 40279c8fc5ac1483c466f4bae7b11a46bdc39a2b Mon Sep 17 00:00:00 2001 From: EdgarPironti Date: Thu, 14 Jan 2016 18:20:40 -0800 Subject: [PATCH 1/4] Item, Review, Shop and Vendor scripts. --- examples/vrShop/item/shopItemEntityScript.js | 389 ++++++++ examples/vrShop/item/shopItemGrab.js | 862 ++++++++++++++++++ .../vrShop/review/shopReviewEntityScript.js | 477 ++++++++++ examples/vrShop/review/shopReviewerAC.js | 96 ++ .../shop/shopGrabSwapperEntityScript.js | 55 ++ examples/vrShop/vendor/shopVendorAddItem.js | 101 ++ 6 files changed, 1980 insertions(+) create mode 100644 examples/vrShop/item/shopItemEntityScript.js create mode 100644 examples/vrShop/item/shopItemGrab.js create mode 100644 examples/vrShop/review/shopReviewEntityScript.js create mode 100644 examples/vrShop/review/shopReviewerAC.js create mode 100644 examples/vrShop/shop/shopGrabSwapperEntityScript.js create mode 100644 examples/vrShop/vendor/shopVendorAddItem.js diff --git a/examples/vrShop/item/shopItemEntityScript.js b/examples/vrShop/item/shopItemEntityScript.js new file mode 100644 index 0000000000..0b54df0f90 --- /dev/null +++ b/examples/vrShop/item/shopItemEntityScript.js @@ -0,0 +1,389 @@ +// shopItemEntityScript.js +// +// This script makes the shop items react properly to the grab (see shopItemGrab.js) and gives it the capability to interact with the shop environment (inspection entity and cart) +// The first time the item is grabbed a copy of itself is created on the shelf. +// If the item isn't held by the user it can be inInspect or inCart, otherwise it's destroyed +// The start and release grab methods handle the creation of the item copy, UI, inspection entity +// If the item is released into a valid zone, the doSomething() method of that zone is called and the item sends its ID + +// Created by Alessandro Signa and Edgar Pironti on 01/13/2016 +// Copyright 2016 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 +// + +(function() { + var utilitiesScript = Script.resolvePath("../../libraries/utils.js"); + var overlayManagerScript = Script.resolvePath("../../libraries/overlayManager.js"); + var inspectEntityScript = Script.resolvePath("../inspect/shopInspectEntityScript.js"); + + Script.include(utilitiesScript); + Script.include(overlayManagerScript); + + + var RED_IMAGE_URL = "https://dl.dropboxusercontent.com/u/14127429/FBX/VRshop/inspRED.png"; + var GREEN_IMAGE_URL = "https://dl.dropboxusercontent.com/u/14127429/FBX/VRshop/inspGREEN.png"; + var MIN_DIMENSION_THRESHOLD = null; + var MAX_DIMENSION_THRESHOLD = null; + var PENETRATION_THRESHOLD = 0.2; + var MAPPING_NAME = "controllerMapping_Inspection"; + var SHOPPING_CART_NAME = "Shopping cart"; + + var _this; + var onShelf = true; + var inspecting = false; + var inCart = false; + var overlayInspectRed = true; + var zoneID = null; + var newPosition; + var originalDimensions = null; + var deltaLX = 0; + var deltaLY = 0; + var deltaRX = 0; + var deltaRY = 0; + var inspectingEntity = null; + var inspectPanel = null; + var background = null; + var textCart = null; + var cartID = null; + + // this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember + // our this object, so we can access it in cases where we're called without a this (like in the case of various global signals) + ItemEntity = function() { + _this = this; + }; + + function update(deltaTime) { + if(inspecting){ + _this.orientationPositionUpdate(); + } + }; + + + ItemEntity.prototype = { + + // preload() will be called when the entity has become visible (or known) to the interface + // it gives us a chance to set our local JavaScript object up. In this case it means: + // * remembering our entityID, so we can access it in cases where we're called without an entityID + // * connecting to the update signal so we can check our grabbed state + preload: function(entityID) { + this.entityID = entityID; + print("PRELOAD: " + Entities.getEntityProperties(this.entityID).name + " " + entityID + " at:"); + Vec3.print(Entities.getEntityProperties(this.entityID).name, Entities.getEntityProperties(this.entityID).position); + + Script.update.connect(update); + + MIN_DIMENSION_THRESHOLD = Vec3.length(Entities.getEntityProperties(this.entityID).dimensions)/2; + MAX_DIMENSION_THRESHOLD = Vec3.length(Entities.getEntityProperties(this.entityID).dimensions)*2; + + }, + + //create a rectangle red or green do guide the user in putting the item in the inspection area + createInspectOverlay: function (entityBindID) { + inspectPanel = new OverlayPanel({ + anchorPositionBinding: { entity: entityBindID }, + anchorRotationBinding: { entity: entityBindID }, + isFacingAvatar: false + }); + + background = new Image3DOverlay({ + url: RED_IMAGE_URL, + dimensions: { + x: 0.6, + y: 0.6, + }, + isFacingAvatar: false, + alpha: 1, + ignoreRayIntersection: false, + offsetPosition: { + x: 0, + y: 0, + z: 0 + }, + emissive: true, + }); + + inspectPanel.addChild(background); + }, + + //add an overlay on the item grabbed by the customer to give him the feedback if the item is inside the bounding box of the cart + createCartOverlay: function (entityBindID) { + cartPanel = new OverlayPanel({ + anchorPositionBinding: { entity: entityBindID }, + offsetPosition: { x: 0, y: 0.2, z: 0.1 }, + isFacingAvatar: true, + + }); + var entityProperties = Entities.getEntityProperties(entityBindID); + var userDataObj = JSON.parse(entityProperties.userData); + var availabilityNumber = userDataObj.infoKey.availability; + + textCart = new Text3DOverlay({ + text: availabilityNumber > 0 ? "Store the item!" : "Not available!", + isFacingAvatar: false, + alpha: 1.0, + ignoreRayIntersection: true, + dimensions: { x: 0, y: 0 }, + backgroundColor: { red: 255, green: 255, blue: 255 }, + color: { red: 0, green: 0, blue: 0 }, + topMargin: 0.00625, + leftMargin: 0.00625, + bottomMargin: 0.1, + rightMargin: 0.00625, + lineHeight: 0.02, + alpha: 1, + backgroundAlpha: 0.3, + visible: false + }); + + cartPanel.addChild(textCart); + + }, + + + + setCartOverlayVisible: function () { + textCart.visible = true; + }, + + setCartOverlayNotVisible: function () { + textCart.visible = false; + }, + + changeOverlayColor: function () { + if (overlayInspectRed) { + //print ("Change color of overlay to green"); + overlayInspectRed = false; + background.url = GREEN_IMAGE_URL; + } else { + //print ("Change color of overlay to red"); + overlayInspectRed = true; + background.url = RED_IMAGE_URL; + } + }, + + //What is done by this methos changes acoordingly to the previous state of the item - onShelf , inCart, inInspect + startNearGrab: function () { + + //we have to distinguish if the user who is grabbing has a cart or not + //if he does it is a buyer, otherwise he probably want to do a review + var thisItemPosition = Entities.getEntityProperties(_this.entityID).position; + + // if onShelf is true, this is the first grab + if (onShelf === true) { + + var foundEntities = Entities.findEntities(thisItemPosition, 5); + foundEntities.forEach( function (foundEntityID) { + var entityName = Entities.getEntityProperties(foundEntityID).name; + if (entityName == SHOPPING_CART_NAME) { + var cartOwnerID = getEntityCustomData('ownerKey', foundEntityID, null).ownerID; + if (cartOwnerID == MyAvatar.sessionUUID) { + cartID = foundEntityID; + } + } + }); + + // Create a copy of this entity if it is the first grab + print("creating a copy of the grabbed dentity"); + var entityProperties = Entities.getEntityProperties(_this.entityID); + + var entityOnShelf = Entities.addEntity({ + type: entityProperties.type, + name: entityProperties.name, + position: thisItemPosition, + dimensions: entityProperties.dimensions, + rotation: entityProperties.rotation, + collisionsWillMove: false, + ignoreForCollisions: true, + modelURL: entityProperties.modelURL, + shapeType: entityProperties.shapeType, + originalTextures: entityProperties.originalTextures, + script: entityProperties.script, + userData: entityProperties.userData + }); + + var tempUserDataObj = JSON.parse(entityProperties.userData); + var availabilityNumber = tempUserDataObj.infoKey.availability; + + if (availabilityNumber > 0 && cartID) { + tempUserDataObj.infoKey.availability = tempUserDataObj.infoKey.availability - 1; + setEntityCustomData('infoKey', entityOnShelf, tempUserDataObj.infoKey); + } + + setEntityCustomData('statusKey', _this.entityID, { + status: "inHand" + }); + + onShelf = false; + setEntityCustomData('ownerKey', _this.entityID, { + ownerID: MyAvatar.sessionUUID + }); + originalDimensions = entityProperties.dimensions; + + } + + //if cartID is defined the user is a buyer + if (cartID) { + // Everytime we grab, we create the inspectEntity and the inspectAreaOverlay in front of the avatar + if(!inspecting) { + inspectingEntity = Entities.addEntity({ + type: "Box", + name: "inspectionEntity", + dimensions: {x: 0.5, y: 0.5, z: 0.5}, + collisionsWillMove: false, + ignoreForCollisions: false, + visible: false, + script: inspectEntityScript, + userData: JSON.stringify({ + ownerKey: { + ownerID: MyAvatar.sessionUUID + }, + itemKey: { + itemID: _this.entityID + }, + grabbableKey: { + grabbable: false + } + }) + }); + } + + Entities.editEntity(_this.entityID, { ignoreForCollisions: false }); + _this.createInspectOverlay(inspectingEntity); + _this.createCartOverlay(_this.entityID); + + if (inspecting === true) { + inspecting = false; + Entities.editEntity(_this.entityID, { dimensions: originalDimensions }); + Controller.disableMapping(MAPPING_NAME); + setEntityCustomData('statusKey', _this.entityID, { + status: "inHand" + }); + } else if (inCart === true) { + inCart = false; + Entities.editEntity(_this.entityID, { dimensions: originalDimensions }); + setEntityCustomData('statusKey', _this.entityID, { + status: "inHand" + }); + var dataJSON = { + id: _this.entityID + }; + var dataArray = [JSON.stringify(dataJSON)]; + Entities.callEntityMethod(zoneID, 'refreshCartContent', dataArray); + } + } + }, + + continueNearGrab: function () { + }, + + //Every time the item is released I have to check what's the role of the user and where the release happens + releaseGrab: function () { + + //if cart ID is not defined destroy the item whatever the zone is because the user is a reviewer + if (!cartID) { + Entities.deleteEntity(this.entityID); + return; + } + + Entities.editEntity(_this.entityID, { ignoreForCollisions: true }); + // Destroy overlay + inspectPanel.destroy(); + cartPanel.destroy(); + inspectPanel = cartPanel = null; + + if (zoneID !== null) { + var dataJSON = { + id: _this.entityID + }; + var dataArray = [JSON.stringify(dataJSON)]; + Entities.callEntityMethod(zoneID, 'doSomething', dataArray); + + var statusObj = getEntityCustomData('statusKey', _this.entityID, null); + + //There are two known zones where the relase can happen: inspecting area and cart + if (statusObj.status == "inInspect") { + inspecting = true; + + var mapping = Controller.newMapping(MAPPING_NAME); + mapping.from(Controller.Standard.LX).to(function (value) { + deltaLX = value; + }); + mapping.from(Controller.Standard.LY).to(function (value) { + deltaLY = value; + }); + mapping.from(Controller.Standard.RX).to(function (value) { + deltaRX = value; + }); + mapping.from(Controller.Standard.RY).to(function (value) { + deltaRY = value; + }); + Controller.enableMapping(MAPPING_NAME); + } else if (statusObj.status == "inCart") { + Entities.deleteEntity(inspectingEntity); + inspectingEntity = null; + + var entityProperties = Entities.getEntityProperties(this.entityID); + var userDataObj = JSON.parse(entityProperties.userData); + var availabilityNumber = userDataObj.infoKey.availability; + //if the item is no more available, destroy it + if (availabilityNumber == 0) { + Entities.deleteEntity(this.entityID); + } + print("inCart is TRUE"); + inCart = true; + } else { // any other zone + //print("------------zoneID is something"); + Entities.deleteEntity(inspectingEntity); + inspectingEntity = null; + } + + } else { // ZoneID is null, released somewhere that is not a zone. + //print("------------zoneID is null"); + Entities.deleteEntity(inspectingEntity); + inspectingEntity = null; + Entities.deleteEntity(this.entityID); + } + + }, + + //Analysing the collisions we can define the value of zoneID + collisionWithEntity: function(myID, otherID, collisionInfo) { + var penetrationValue = Vec3.length(collisionInfo.penetration); + if (penetrationValue > PENETRATION_THRESHOLD && zoneID === null) { + zoneID = otherID; + print("Item IN: " + Entities.getEntityProperties(zoneID).name); + } else if (penetrationValue < PENETRATION_THRESHOLD && zoneID !== null && otherID == zoneID) { + print("Item OUT: " + Entities.getEntityProperties(zoneID).name); + zoneID = null; + } + }, + + //this method handles the rotation and scale of the item while in inspect and also guarantees to keep the item in the proper position. It's done every update + orientationPositionUpdate: function() { + //position + var inspectingEntityPosition = Entities.getEntityProperties(inspectingEntity).position; //at this time inspectingEntity is a valid entity + var inspectingEntityRotation = Entities.getEntityProperties(inspectingEntity).rotation; + newPosition = Vec3.sum(inspectingEntityPosition, Vec3.multiply(Quat.getFront(inspectingEntityRotation), -0.2)); //put the item near to the face of the user + Entities.editEntity(_this.entityID, { position: newPosition }); + //orientation + var newRotation = Quat.multiply(Entities.getEntityProperties(_this.entityID).rotation, Quat.fromPitchYawRollDegrees(deltaRY*10, deltaRX*10, 0)) + Entities.editEntity(_this.entityID, { rotation: newRotation }); + //zoom + var oldDimension = Entities.getEntityProperties(_this.entityID).dimensions; + var scaleFactor = (deltaLY * 0.1) + 1; + if (!((Vec3.length(oldDimension) < MIN_DIMENSION_THRESHOLD && scaleFactor < 1) || (Vec3.length(oldDimension) > MAX_DIMENSION_THRESHOLD && scaleFactor > 1))) { + var newDimension = Vec3.multiply(oldDimension, scaleFactor); + Entities.editEntity(_this.entityID, { dimensions: newDimension }); + } + }, + + unload: function (entityID) { + Script.update.disconnect(update); + } + }; + + // entity scripts always need to return a newly constructed object of our type + return new ItemEntity(); +}) \ No newline at end of file diff --git a/examples/vrShop/item/shopItemGrab.js b/examples/vrShop/item/shopItemGrab.js new file mode 100644 index 0000000000..a7226675eb --- /dev/null +++ b/examples/vrShop/item/shopItemGrab.js @@ -0,0 +1,862 @@ +// shopItemGrab.js +// +// Semplified and coarse version of handControllerGrab.js with the addition of the ownerID concept. +// This grab is the only one which should run in the vrShop. It allows only near grab and add the feature of checking the ownerID. (See shopGrapSwapperEntityScript.js) +// + +Script.include("../../libraries/utils.js"); + +var SHOP_GRAB_CHANNEL = "Hifi-vrShop-Grab"; +Messages.sendMessage('Hifi-Hand-Disabler', "both"); //disable both the hands from handControlledGrab + +// +// add lines where the hand ray picking is happening +// +var WANT_DEBUG = false; + +// +// these tune time-averaging and "on" value for analog trigger +// + +var TRIGGER_SMOOTH_RATIO = 0.1; // 0.0 disables smoothing of trigger value +var TRIGGER_ON_VALUE = 0.4; +var TRIGGER_OFF_VALUE = 0.15; +// +// distant manipulation +// + +var DISTANCE_HOLDING_RADIUS_FACTOR = 5; // multiplied by distance between hand and object +var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position +var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did + +var NO_INTERSECT_COLOR = { + red: 10, + green: 10, + blue: 255 +}; // line color when pick misses +var INTERSECT_COLOR = { + red: 250, + green: 10, + blue: 10 +}; // line color when pick hits +var LINE_ENTITY_DIMENSIONS = { + x: 1000, + y: 1000, + z: 1000 +}; +var LINE_LENGTH = 500; +var PICK_MAX_DISTANCE = 500; // max length of pick-ray + +// +// near grabbing +// + +var GRAB_RADIUS = 0.03; // if the ray misses but an object is this close, it will still be selected +var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position +var NEAR_GRABBING_VELOCITY_SMOOTH_RATIO = 1.0; // adjust time-averaging of held object's velocity. 1.0 to disable. +var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected +var RELEASE_VELOCITY_MULTIPLIER = 1.5; // affects throwing things +var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object +var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed + +// +// equip +// + +var EQUIP_SPRING_SHUTOFF_DISTANCE = 0.05; +var EQUIP_SPRING_TIMEFRAME = 0.4; // how quickly objects move to their new position + +// +// other constants +// + +var RIGHT_HAND = 1; +var LEFT_HAND = 0; + +var ZERO_VEC = { + x: 0, + y: 0, + z: 0 +}; + +var NULL_ACTION_ID = "{00000000-0000-0000-000000000000}"; +var MSEC_PER_SEC = 1000.0; + +// these control how long an abandoned pointer line or action will hang around +var LIFETIME = 10; +var ACTION_TTL = 15; // seconds +var ACTION_TTL_REFRESH = 5; +var PICKS_PER_SECOND_PER_HAND = 5; +var MSECS_PER_SEC = 1000.0; +var GRABBABLE_PROPERTIES = [ + "position", + "rotation", + "gravity", + "ignoreForCollisions", + "collisionsWillMove", + "locked", + "name" +]; + +var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with grab.js +var GRAB_USER_DATA_KEY = "grabKey"; // shared with grab.js + +var DEFAULT_GRABBABLE_DATA = { + grabbable: true, + invertSolidWhileHeld: false +}; + + +// states for the state machine +var STATE_OFF = 0; +var STATE_SEARCHING = 1; +var STATE_NEAR_GRABBING = 4; +var STATE_CONTINUE_NEAR_GRABBING = 5; +var STATE_NEAR_TRIGGER = 6; +var STATE_CONTINUE_NEAR_TRIGGER = 7; +var STATE_FAR_TRIGGER = 8; +var STATE_CONTINUE_FAR_TRIGGER = 9; +var STATE_RELEASE = 10; +var STATE_EQUIP_SEARCHING = 11; +var STATE_EQUIP = 12 +var STATE_CONTINUE_EQUIP_BD = 13; // equip while bumper is still held down +var STATE_CONTINUE_EQUIP = 14; +var STATE_EQUIP_SPRING = 16; + + +function stateToName(state) { + switch (state) { + case STATE_OFF: + return "off"; + case STATE_SEARCHING: + return "searching"; + case STATE_NEAR_GRABBING: + return "near_grabbing"; + case STATE_CONTINUE_NEAR_GRABBING: + return "continue_near_grabbing"; + case STATE_NEAR_TRIGGER: + return "near_trigger"; + case STATE_CONTINUE_NEAR_TRIGGER: + return "continue_near_trigger"; + case STATE_FAR_TRIGGER: + return "far_trigger"; + case STATE_CONTINUE_FAR_TRIGGER: + return "continue_far_trigger"; + case STATE_RELEASE: + return "release"; + case STATE_EQUIP_SEARCHING: + return "equip_searching"; + case STATE_EQUIP: + return "equip"; + case STATE_CONTINUE_EQUIP_BD: + return "continue_equip_bd"; + case STATE_CONTINUE_EQUIP: + return "continue_equip"; + case STATE_EQUIP_SPRING: + return "state_equip_spring"; + } + + return "unknown"; +} + + + +function MyController(hand) { + this.hand = hand; + if (this.hand === RIGHT_HAND) { + this.getHandPosition = MyAvatar.getRightPalmPosition; + this.getHandRotation = MyAvatar.getRightPalmRotation; + } else { + this.getHandPosition = MyAvatar.getLeftPalmPosition; + this.getHandRotation = MyAvatar.getLeftPalmRotation; + } + + var SPATIAL_CONTROLLERS_PER_PALM = 2; + var TIP_CONTROLLER_OFFSET = 1; + this.palm = SPATIAL_CONTROLLERS_PER_PALM * hand; + this.tip = SPATIAL_CONTROLLERS_PER_PALM * hand + TIP_CONTROLLER_OFFSET; + + this.actionID = null; // action this script created... + this.grabbedEntity = null; // on this entity. + this.state = STATE_OFF; + this.pointer = null; // entity-id of line object + this.triggerValue = 0; // rolling average of trigger value + this.rawTriggerValue = 0; + + this.offsetPosition = { + x: 0.0, + y: 0.0, + z: 0.0 + }; + this.offsetRotation = { + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0 + }; + + var _this = this; + + this.update = function() { + + this.updateSmoothedTrigger(); + + switch (this.state) { + case STATE_OFF: + this.off(); + this.touchTest(); + break; + case STATE_SEARCHING: + this.search(); + break; + case STATE_EQUIP_SEARCHING: + this.search(); + break; + case STATE_NEAR_GRABBING: + case STATE_EQUIP: + this.nearGrabbing(); + break; + case STATE_EQUIP_SPRING: + this.pullTowardEquipPosition() + break; + case STATE_CONTINUE_NEAR_GRABBING: + case STATE_CONTINUE_EQUIP_BD: + case STATE_CONTINUE_EQUIP: + this.continueNearGrabbing(); + break; + case STATE_NEAR_TRIGGER: + this.nearTrigger(); + break; + case STATE_CONTINUE_NEAR_TRIGGER: + this.continueNearTrigger(); + break; + case STATE_FAR_TRIGGER: + this.farTrigger(); + break; + case STATE_CONTINUE_FAR_TRIGGER: + this.continueFarTrigger(); + break; + case STATE_RELEASE: + this.release(); + break; + } + }; + + this.setState = function(newState) { + if (WANT_DEBUG) { + print("STATE: " + stateToName(this.state) + " --> " + stateToName(newState) + ", hand: " + this.hand); + } + this.state = newState; + } + + this.debugLine = function(closePoint, farPoint, color) { + Entities.addEntity({ + type: "Line", + name: "Grab Debug Entity", + dimensions: LINE_ENTITY_DIMENSIONS, + visible: true, + position: closePoint, + linePoints: [ZERO_VEC, farPoint], + color: color, + lifetime: 0.1, + collisionsWillMove: false, + ignoreForCollisions: true, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + }); + } + + this.lineOn = function(closePoint, farPoint, color) { + // draw a line + if (this.pointer === null) { + this.pointer = Entities.addEntity({ + type: "Line", + name: "grab pointer", + dimensions: LINE_ENTITY_DIMENSIONS, + visible: false, + position: closePoint, + linePoints: [ZERO_VEC, farPoint], + color: color, + lifetime: LIFETIME, + collisionsWillMove: false, + ignoreForCollisions: true, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + }); + } else { + var age = Entities.getEntityProperties(this.pointer, "age").age; + this.pointer = Entities.editEntity(this.pointer, { + position: closePoint, + linePoints: [ZERO_VEC, farPoint], + color: color, + lifetime: age + LIFETIME + }); + } + }; + + this.lineOff = function() { + if (this.pointer !== null) { + Entities.deleteEntity(this.pointer); + } + this.pointer = null; + }; + + this.triggerPress = function(value) { + _this.rawTriggerValue = value; + }; + + + this.updateSmoothedTrigger = function() { + var triggerValue = this.rawTriggerValue; + // smooth out trigger value + this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) + + (triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO)); + }; + + this.triggerSmoothedSqueezed = function() { + return this.triggerValue > TRIGGER_ON_VALUE; + }; + + this.triggerSmoothedReleased = function() { + return this.triggerValue < TRIGGER_OFF_VALUE; + }; + + this.triggerSqueezed = function() { + var triggerValue = this.rawTriggerValue; + return triggerValue > TRIGGER_ON_VALUE; + }; + + this.off = function() { + if (this.triggerSmoothedSqueezed()) { + this.lastPickTime = 0; + this.setState(STATE_SEARCHING); + return; + } + } + + this.search = function() { + this.grabbedEntity = null; + + if (this.state == STATE_SEARCHING && this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + return; + } + + // the trigger is being pressed, do a ray test + var handPosition = this.getHandPosition(); + var distantPickRay = { + origin: handPosition, + direction: Quat.getUp(this.getHandRotation()), + length: PICK_MAX_DISTANCE + }; + + // don't pick 60x per second. + var pickRays = []; + var now = Date.now(); + if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { + pickRays = [distantPickRay]; + this.lastPickTime = now; + } + + for (var index = 0; index < pickRays.length; ++index) { + var pickRay = pickRays[index]; + var directionNormalized = Vec3.normalize(pickRay.direction); + var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE); + var pickRayBacked = { + origin: Vec3.subtract(pickRay.origin, directionBacked), + direction: pickRay.direction + }; + + + var intersection = Entities.findRayIntersection(pickRayBacked, true); + + if (intersection.intersects) { + // the ray is intersecting something we can move. + var intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); + + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, intersection.entityID, DEFAULT_GRABBABLE_DATA); + + if (intersection.properties.name == "Grab Debug Entity") { + continue; + } + + if (typeof grabbableData.grabbable !== 'undefined' && !grabbableData.grabbable) { + continue; + } + if (intersectionDistance > pickRay.length) { + // too far away for this ray. + continue; + } + if (intersectionDistance <= NEAR_PICK_MAX_DISTANCE) { + // the hand is very close to the intersected object. go into close-grabbing mode. + if (grabbableData.wantsTrigger) { + this.grabbedEntity = intersection.entityID; + this.setState(STATE_NEAR_TRIGGER); + return; + } else if (!intersection.properties.locked) { + var ownerObj = getEntityCustomData('ownerKey', intersection.entityID, null); + + if (ownerObj == null || ownerObj.ownerID === MyAvatar.sessionUUID) { //I can only grab new or already mine items + this.grabbedEntity = intersection.entityID; + if (this.state == STATE_SEARCHING) { + this.setState(STATE_NEAR_GRABBING); + } else { // equipping + if (typeof grabbableData.spatialKey !== 'undefined') { + this.setState(STATE_EQUIP_SPRING); + } else { + this.setState(STATE_EQUIP); + } + } + return; + } + } + } + } + } + + this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + }; + + this.nearGrabbing = function() { + var now = Date.now(); + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + //Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + return; + } + + this.lineOff(); + + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + if (grabbedProperties.collisionsWillMove && NEAR_GRABBING_KINEMATIC) { + Entities.editEntity(this.grabbedEntity, { + collisionsWillMove: false + }); + } + + var handRotation = this.getHandRotation(); + var handPosition = this.getHandPosition(); + + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) { + // if an object is "equipped" and has a spatialKey, use it. + if (grabbableData.spatialKey.relativePosition) { + this.offsetPosition = grabbableData.spatialKey.relativePosition; + } + if (grabbableData.spatialKey.relativeRotation) { + this.offsetRotation = grabbableData.spatialKey.relativeRotation; + } + } else { + var objectRotation = grabbedProperties.rotation; + this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); + + var currentObjectPosition = grabbedProperties.position; + var offset = Vec3.subtract(currentObjectPosition, handPosition); + this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); + } + + this.actionID = NULL_ACTION_ID; + this.actionID = Entities.addAction("hold", this.grabbedEntity, { + hand: this.hand === RIGHT_HAND ? "right" : "left", + timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, + relativePosition: this.offsetPosition, + relativeRotation: this.offsetRotation, + ttl: ACTION_TTL, + kinematic: NEAR_GRABBING_KINEMATIC, + kinematicSetVelocity: true + }); + if (this.actionID === NULL_ACTION_ID) { + this.actionID = null; + } else { + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + if (this.state == STATE_NEAR_GRABBING) { + this.setState(STATE_CONTINUE_NEAR_GRABBING); + } else { + // equipping + Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); + this.startHandGrasp(); + this.setState(STATE_CONTINUE_EQUIP_BD); + } + + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + + Entities.callEntityMethod(this.grabbedEntity, "startNearGrab"); + + } + + this.currentHandControllerTipPosition = + (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; + + this.currentObjectTime = Date.now(); + }; + + this.continueNearGrabbing = function() { + if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + //Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + return; + } + + // Keep track of the fingertip velocity to impart when we release the object. + // Note that the idea of using a constant 'tip' velocity regardless of the + // object's actual held offset is an idea intended to make it easier to throw things: + // Because we might catch something or transfer it between hands without a good idea + // of it's actual offset, let's try imparting a velocity which is at a fixed radius + // from the palm. + + var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; + var now = Date.now(); + + var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerTipPosition); // meters + var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds + + this.currentHandControllerTipPosition = handControllerPosition; + this.currentObjectTime = now; + Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab"); + + if (this.state === STATE_CONTINUE_EQUIP_BD) { + Entities.callEntityMethod(this.grabbedEntity, "continueEquip"); + } + + if (this.actionTimeout - now < ACTION_TTL_REFRESH * MSEC_PER_SEC) { + // if less than a 5 seconds left, refresh the actions ttl + Entities.updateAction(this.grabbedEntity, this.actionID, { + hand: this.hand === RIGHT_HAND ? "right" : "left", + timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, + relativePosition: this.offsetPosition, + relativeRotation: this.offsetRotation, + ttl: ACTION_TTL, + kinematic: NEAR_GRABBING_KINEMATIC, + kinematicSetVelocity: true + }); + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + } + }; + + + this.pullTowardEquipPosition = function() { + this.lineOff(); + + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + // use a spring to pull the object to where it will be when equipped + var relativeRotation = { + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0 + }; + var relativePosition = { + x: 0.0, + y: 0.0, + z: 0.0 + }; + if (grabbableData.spatialKey.relativePosition) { + relativePosition = grabbableData.spatialKey.relativePosition; + } + if (grabbableData.spatialKey.relativeRotation) { + relativeRotation = grabbableData.spatialKey.relativeRotation; + } + var handRotation = this.getHandRotation(); + var handPosition = this.getHandPosition(); + var targetRotation = Quat.multiply(handRotation, relativeRotation); + var offset = Vec3.multiplyQbyV(targetRotation, relativePosition); + var targetPosition = Vec3.sum(handPosition, offset); + + if (typeof this.equipSpringID === 'undefined' || + this.equipSpringID === null || + this.equipSpringID === NULL_ACTION_ID) { + this.equipSpringID = Entities.addAction("spring", this.grabbedEntity, { + targetPosition: targetPosition, + linearTimeScale: EQUIP_SPRING_TIMEFRAME, + targetRotation: targetRotation, + angularTimeScale: EQUIP_SPRING_TIMEFRAME, + ttl: ACTION_TTL + }); + if (this.equipSpringID === NULL_ACTION_ID) { + this.equipSpringID = null; + this.setState(STATE_OFF); + return; + } + } else { + Entities.updateAction(this.grabbedEntity, this.equipSpringID, { + targetPosition: targetPosition, + linearTimeScale: EQUIP_SPRING_TIMEFRAME, + targetRotation: targetRotation, + angularTimeScale: EQUIP_SPRING_TIMEFRAME, + ttl: ACTION_TTL + }); + } + + if (Vec3.distance(grabbedProperties.position, targetPosition) < EQUIP_SPRING_SHUTOFF_DISTANCE) { + Entities.deleteAction(this.grabbedEntity, this.equipSpringID); + this.equipSpringID = null; + this.setState(STATE_EQUIP); + } + }; + + this.nearTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); + return; + } + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + + Entities.callEntityMethod(this.grabbedEntity, "startNearTrigger"); + this.setState(STATE_CONTINUE_NEAR_TRIGGER); + }; + + this.farTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); + return; + } + + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + Entities.callEntityMethod(this.grabbedEntity, "startFarTrigger"); + this.setState(STATE_CONTINUE_FAR_TRIGGER); + }; + + this.continueNearTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); + return; + } + + Entities.callEntityMethod(this.grabbedEntity, "continueNearTrigger"); + }; + + this.continueFarTrigger = function() { + if (this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopNearTrigger"); + return; + } + + var handPosition = this.getHandPosition(); + var pickRay = { + origin: handPosition, + direction: Quat.getUp(this.getHandRotation()) + }; + + var now = Date.now(); + if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { + var intersection = Entities.findRayIntersection(pickRay, true); + this.lastPickTime = now; + if (intersection.entityID != this.grabbedEntity) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "stopFarTrigger"); + return; + } + } + + this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + Entities.callEntityMethod(this.grabbedEntity, "continueFarTrigger"); + }; + + _this.allTouchedIDs = {}; + this.touchTest = function() { + var maxDistance = 0.05; + var leftHandPosition = MyAvatar.getLeftPalmPosition(); + var rightHandPosition = MyAvatar.getRightPalmPosition(); + var leftEntities = Entities.findEntities(leftHandPosition, maxDistance); + var rightEntities = Entities.findEntities(rightHandPosition, maxDistance); + var ids = []; + + if (leftEntities.length !== 0) { + leftEntities.forEach(function(entity) { + ids.push(entity); + }); + + } + + if (rightEntities.length !== 0) { + rightEntities.forEach(function(entity) { + ids.push(entity); + }); + } + + ids.forEach(function(id) { + + var props = Entities.getEntityProperties(id, ["boundingBox", "name"]); + if (props.name === 'pointer') { + return; + } else { + var entityMinPoint = props.boundingBox.brn; + var entityMaxPoint = props.boundingBox.tfl; + var leftIsTouching = pointInExtents(leftHandPosition, entityMinPoint, entityMaxPoint); + var rightIsTouching = pointInExtents(rightHandPosition, entityMinPoint, entityMaxPoint); + + if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id] === undefined) { + // we haven't been touched before, but either right or left is touching us now + _this.allTouchedIDs[id] = true; + _this.startTouch(id); + } else if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id]) { + // we have been touched before and are still being touched + // continue touch + _this.continueTouch(id); + } else if (_this.allTouchedIDs[id]) { + delete _this.allTouchedIDs[id]; + _this.stopTouch(id); + + } else { + //we are in another state + return; + } + } + + }); + + }; + + this.startTouch = function(entityID) { + Entities.callEntityMethod(entityID, "startTouch"); + }; + + this.continueTouch = function(entityID) { + Entities.callEntityMethod(entityID, "continueTouch"); + }; + + this.stopTouch = function(entityID) { + Entities.callEntityMethod(entityID, "stopTouch"); + }; + + this.release = function() { + + this.lineOff(); + + if (this.grabbedEntity !== null) { + if (this.actionID !== null) { + Entities.deleteAction(this.grabbedEntity, this.actionID); + Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + print("CALLING releaseGrab"); + } + } + + + this.grabbedEntity = null; + this.actionID = null; + this.setState(STATE_OFF); + + }; + + this.cleanup = function() { + this.release(); + this.endHandGrasp(); + }; + + + + + //this is our handler, where we do the actual work of changing animation settings + this.graspHand = function(animationProperties) { + var result = {}; + //full alpha on overlay for this hand + //set grab to true + //set idle to false + //full alpha on the blend btw open and grab + if (_this.hand === RIGHT_HAND) { + result['rightHandOverlayAlpha'] = 1.0; + result['isRightHandGrab'] = true; + result['isRightHandIdle'] = false; + result['rightHandGrabBlend'] = 1.0; + } else if (_this.hand === LEFT_HAND) { + result['leftHandOverlayAlpha'] = 1.0; + result['isLeftHandGrab'] = true; + result['isLeftHandIdle'] = false; + result['leftHandGrabBlend'] = 1.0; + } + //return an object with our updated settings + return result; + } + + this.graspHandler = null + this.startHandGrasp = function() { + if (this.hand === RIGHT_HAND) { + this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isRightHandGrab']); + } else if (this.hand === LEFT_HAND) { + this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isLeftHandGrab']); + } + } + + this.endHandGrasp = function() { + // Tell the animation system we don't need any more callbacks. + MyAvatar.removeAnimationStateHandler(this.graspHandler); + } + +} + +var rightController = new MyController(RIGHT_HAND); +var leftController = new MyController(LEFT_HAND); + +var MAPPING_NAME = "com.highfidelity.handControllerGrab"; + +var mapping = Controller.newMapping(MAPPING_NAME); +mapping.from([Controller.Standard.RT]).peek().to(rightController.triggerPress); +mapping.from([Controller.Standard.LT]).peek().to(leftController.triggerPress); + +Controller.enableMapping(MAPPING_NAME); + +var handToDisable = 'none'; + +function update() { + if (handToDisable !== LEFT_HAND) { + leftController.update(); + } + if (handToDisable !== RIGHT_HAND) { + rightController.update(); + } +} + +Messages.subscribe(SHOP_GRAB_CHANNEL); + +stopScriptMessage = function(channel, message, sender) { + if (channel == SHOP_GRAB_CHANNEL && sender === MyAvatar.sessionUUID) { + //stop this script and enable the handControllerGrab + Messages.sendMessage('Hifi-Hand-Disabler', "none"); + Messages.unsubscribe(SHOP_GRAB_CHANNEL); + Messages.messageReceived.disconnect(stopScriptMessage); + Script.stop(); + } +} + +Messages.messageReceived.connect(stopScriptMessage); + +function cleanup() { + rightController.cleanup(); + leftController.cleanup(); + Controller.disableMapping(MAPPING_NAME); +} + +Script.scriptEnding.connect(cleanup); +Script.update.connect(update); \ No newline at end of file diff --git a/examples/vrShop/review/shopReviewEntityScript.js b/examples/vrShop/review/shopReviewEntityScript.js new file mode 100644 index 0000000000..49f0a49dea --- /dev/null +++ b/examples/vrShop/review/shopReviewEntityScript.js @@ -0,0 +1,477 @@ +// shopReviewEntityScript.js +// +// This script handles the review phase in the vrShop. It starts entering into the entity holding in hand the item to review. +// Then the user can rate the item and record a review for that item. +// Finally the recording is stored into the asset and a link to that file is stored into the DB entity of that item. +// During the whole reviewing experience an (interactive) UI drives the user. + +// Created by Alessandro Signa and Edgar Pironti on 01/13/2016 +// Copyright 2016 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 +// + +(function () { + var utilitiesScript = Script.resolvePath("../../libraries/utils.js"); + var overlayManagerScript = Script.resolvePath("../../libraries/overlayManager.js"); + Script.include(utilitiesScript); + Script.include(overlayManagerScript); + + var POINTER_ICON_URL = "https://dl.dropboxusercontent.com/u/14127429/FBX/VRshop/Pointer.png"; + var STAR_ON_URL = "https://dl.dropboxusercontent.com/u/14127429/FBX/VRshop/SingleStar_Yellow.png"; + var STAR_OFF_URL = "https://dl.dropboxusercontent.com/u/14127429/FBX/VRshop/SingleStar_Black.png"; + var RECORDING_ON_ICON_URL = "https://dl.dropboxusercontent.com/u/14127429/FBX/VRshop/REC.png"; + var ANCHOR_ENTITY_FOR_UI_NAME = "anchorEntityForReviewUI"; + var START_RECORDING_TEXT = "Press the bumper to start recording"; + var STOP_RECORDING_TEXT = "Press the bumper to stop recording"; + var SHOPPING_CART_NAME = "Shopping cart"; + var CAMERA_NAME = "CameraReview"; + + + var RIGHT_HAND = 1; + var LEFT_HAND = 0; + + var LINE_LENGTH = 100; + var COLOR = { + red: 165, + green: 199, + blue: 218 + }; + + + + var _this; + var dataBaseID = null; + var cameraEntity = null; + var scoreAssigned = null; + var hoveredButton = null; + var hoveredButtonIndex = -1; + var recording = false; + var workDone = false; + + var PENETRATION_THRESHOLD = 0.2; + + + var isUIWorking = false; + var wantToStopTrying = false; + var rightController = null; + var leftController = null; + var workingHand = null; + + var mainPanel = null; + var cameraPanel = null; + var buttons = []; + var onAirOverlay = null; + var instructionsOverlay = null; + + + var pointer = new Image3DOverlay({ + url: POINTER_ICON_URL, + dimensions: { + x: 0.015, + y: 0.015 + }, + alpha: 1, + emissive: true, + isFacingAvatar: false, + ignoreRayIntersection: true, + }) + + function ReviewZone() { + _this = this; + return; + }; + + function MyController(hand) { + this.hand = hand; + if (this.hand === RIGHT_HAND) { + this.getHandPosition = MyAvatar.getRightPalmPosition; + this.getHandRotation = MyAvatar.getRightPalmRotation; + this.bumper = Controller.Standard.RB; + } else { + this.getHandPosition = MyAvatar.getLeftPalmPosition; + this.getHandRotation = MyAvatar.getLeftPalmRotation; + this.bumper = Controller.Standard.LB; + } + + this.pickRay = null; // ray object + this.overlayLine = null; // id of line overlay + this.waitingForBumpReleased = false; + + this.overlayLineOn = function(closePoint, farPoint, color) { + if (this.overlayLine == null) { + var lineProperties = { + lineWidth: 5, + start: closePoint, + end: farPoint, + color: color, + ignoreRayIntersection: true, + visible: true, + alpha: 1 + }; + this.overlayLine = new Line3DOverlay(lineProperties); + } else { + this.overlayLine.start = closePoint; + this.overlayLine.end = farPoint; + } + }, + + this.updateRay = function() { + //update the ray object + this.pickRay = { + origin: this.getHandPosition(), + direction: Quat.getUp(this.getHandRotation()) + }; + //update the ray overlay and the pointer + + var rayPickResult = OverlayManager.findRayIntersection(this.pickRay); + if (rayPickResult.intersects) { + var normal = Vec3.multiply(Quat.getFront(Camera.getOrientation()), -1); + var offset = Vec3.multiply(normal, 0.001); + pointer.position = Vec3.sum(rayPickResult.intersection, offset); //pointer is a global Image3DOverlay + pointer.rotation = Camera.getOrientation(); + pointer.visible = true; + } else { + pointer.visible = false; + } + + var farPoint = rayPickResult.intersects ? rayPickResult.intersection : Vec3.sum(this.pickRay.origin, Vec3.multiply(this.pickRay.direction, LINE_LENGTH)); + this.overlayLineOn(this.pickRay.origin, farPoint, COLOR); + + }, + //the update of each hand has to update the ray belonging to that hand and handle the bumper event + this.updateHand = function() { + //detect the bumper event + var bumperPressed = Controller.getValue(this.bumper); + if (bumperPressed && this != workingHand) { + //mantain active one ray at a time + workingHand.clean(); + workingHand = this; + } else if (this != workingHand) { + return; + } + + if (!scoreAssigned) { + this.updateRay(); + + //manage event on UI + var lastHoveredButton = hoveredButton; + hoveredButton = OverlayManager.findOnRay(this.pickRay); + //print("hovered button: " + hoveredButton); + if (lastHoveredButton != hoveredButton) { + hoveredButtonIndex = -1; + if (hoveredButton) { + for (var i = 0; i < buttons.length; i++) { + if (buttons[i] == hoveredButton) { + //print("Adapting overlay rendering"); + hoveredButtonIndex = i; + } + } + } + adaptOverlayOnHover(hoveredButtonIndex); + } + } else if (!instructionsOverlay.visible) { + workingHand.clean(); + for (var i = 0; i < buttons.length; i++) { + buttons[i].destroy(); + } + buttons = []; + instructionsOverlay.visible = true; + } + + + if (bumperPressed && !this.waitingForBumpReleased) { + this.waitingForBumpReleased = true; + + if (hoveredButton) { + scoreAssigned = hoveredButtonIndex + 1; + hoveredButton = null; + } else if (scoreAssigned && !recording) { + instructionsOverlay.text = STOP_RECORDING_TEXT; + Recording.startRecording(); + onAirOverlay.visible = true; + recording = true; + } else if (scoreAssigned && recording) { + Recording.stopRecording(); + Recording.saveRecordingToAsset(saveDataIntoDB); + onAirOverlay.visible = false; + recording = false; + workDone = true; + _this.cleanUI(); + } + } else if (!bumperPressed && this.waitingForBumpReleased) { + this.waitingForBumpReleased = false; + } + }, + + this.clean = function() { + this.pickRay = null; + if (this.overlayLine) { + this.overlayLine.destroy(); + } + this.overlayLine = null; + pointer.visible = false; + } + }; + + function update(deltaTime) { + + if (!workDone) { + leftController.updateHand(); + rightController.updateHand(); + } else { + _this.cleanUI(); + Script.update.disconnect(update); + } + + + if (!insideZone && isUIWorking) { + // Destroy rays + _this.cleanUI(); + } + + }; + + //This method is the callback called once the recording is loaded into the asset + function saveDataIntoDB(url) { + // Feed the database + var dbObj = getEntityCustomData('infoKey', dataBaseID, null); + if(dbObj) { + var myName = MyAvatar.displayName ? MyAvatar.displayName : "Anonymous"; + dbObj.dbKey[dbObj.dbKey.length] = {name: myName, score: scoreAssigned, clip_url: url}; + setEntityCustomData('infoKey', dataBaseID, dbObj); + print("Feeded DB: " + url"); + } + }; + + // Find items in the zone. It returns a not null value if an item belonging to the user is found AND if a cart belonging to him is not found + function findItemToReview(searchingPointEntityID) { + var foundItemToReviewID = null; + var entitiesInZone = Entities.findEntities(Entities.getEntityProperties(searchingPointEntityID).position, 5); + for (var i = 0; i < entitiesInZone.length; i++) { + + var ownerObj = getEntityCustomData('ownerKey', entitiesInZone[i], null); + + if (ownerObj) { + print("Not sure if review. Check " + MyAvatar.sessionUUID); + if (ownerObj.ownerID === MyAvatar.sessionUUID) { + if (Entities.getEntityProperties(entitiesInZone[i]).name == SHOPPING_CART_NAME) { + //the user has a cart, he's not a reviewer + print("the user has a cart, it's not a reviewer"); + return null; + } else { + foundItemToReviewID = entitiesInZone[i]; + } + } + } + } + var res = null; + if (foundItemToReviewID) { + res = Entities.getEntityProperties(foundItemToReviewID).name; + print("Found an item to review: " + res); + //delete the item + Entities.deleteEntity(foundItemToReviewID); + } + return res; + }; + + function findAnchorEntityForUI(searchingPointEntityID) { + + var entitiesInZone = Entities.findEntities(Entities.getEntityProperties(searchingPointEntityID).position, 2); + for (var i = 0; i < entitiesInZone.length; i++) { + + if (Entities.getEntityProperties(entitiesInZone[i]).name == ANCHOR_ENTITY_FOR_UI_NAME) { + print("Anchor entity found " + entitiesInZone[i]); + return entitiesInZone[i]; + } + } + return null; + }; + + function findItemByName(searchingPointEntityID, itemName) { + // find the database entity + print("Looking for item: " + itemName); + var entitiesInZone = Entities.findEntities(Entities.getEntityProperties(searchingPointEntityID).position, (Entities.getEntityProperties(searchingPointEntityID).dimensions.x)*10); + + for (var i = 0; i < entitiesInZone.length; i++) { + if (Entities.getEntityProperties(entitiesInZone[i]).name == itemName) { + print(itemName + " found! " + entitiesInZone[i]); + return entitiesInZone[i]; + } + } + print("Item " + itemName + " not found"); + return null; + }; + + function adaptOverlayOnHover(hoveredButtonIndex) { + for (var i = buttons.length - 1; i >= 0; i--) { + if (i <= hoveredButtonIndex) { + buttons[i].url = STAR_ON_URL; + } else { + buttons[i].url = STAR_OFF_URL; + } + } + }; + + + ReviewZone.prototype = { + + preload: function (entityID) { + }, + + //When the avatar comes into the review zone the script looks for the actual item to review, the proper DB for that item and the entity camera. If all goes well the UI is created + enterEntity: function (entityID) { + print("entering in the review area"); + insideZone = true; + + var itemToReview = findItemToReview(entityID); //return the name or null + cameraEntity = findItemByName(entityID, CAMERA_NAME); + if (itemToReview && cameraEntity) { + dataBaseID = findItemByName(entityID, itemToReview + "DB"); //return the ID of the DB or null + if (dataBaseID) { + var anchorEntityForUI = findAnchorEntityForUI(entityID); + if (anchorEntityForUI) { + _this.createReviewUI(anchorEntityForUI); + rightController = new MyController(RIGHT_HAND); //rightController and leftController are two objects + leftController = new MyController(LEFT_HAND); + workingHand = rightController; + Script.update.connect(update); + } + } + } + }, + + leaveEntity: function (entityID) { + print("leaving the review area"); + if (!workDone) { + Script.update.disconnect(update); + } + if (recording) { + Recording.stopRecording(); + recording = false; + } + + if (cameraPanel) { + cameraPanel.destroy(); + cameraPanel = null; + } + workDone = false; + cameraEntity = null; + dataBaseID = null; + scoreAssigned = null; + hoveredButton = null; + hoveredButtonIndex = -1; + insideZone = false; + _this.cleanUI(); + }, + + createReviewUI : function(anchorEntityID) { + Entities.editEntity(anchorEntityID, { locked: false }); + var anchorEntityPosition = Entities.getEntityProperties(anchorEntityID).position; + anchorEntityPosition.y = MyAvatar.getHeadPosition().y; + Entities.editEntity(anchorEntityID, { position: anchorEntityPosition }); + Entities.editEntity(anchorEntityID, { locked: true }); + + //set the main panel to follow the inspect entity + mainPanel = new OverlayPanel({ + anchorPositionBinding: { entity: anchorEntityID }, + anchorRotationBinding: { entity: anchorEntityID }, + + isFacingAvatar: false + }); + + var offsetPositionY = 0.0; + var offsetPositionX = -0.3; + + for (var i = 0; i < 3; i++) { + buttons[i] = new Image3DOverlay({ + url: STAR_OFF_URL, + dimensions: { + x: 0.15, + y: 0.15 + }, + isFacingAvatar: false, + alpha: 0.8, + ignoreRayIntersection: false, + offsetPosition: { + x: offsetPositionX - (i * offsetPositionX), + y: offsetPositionY, + z: 0 + }, + emissive: true, + }); + mainPanel.addChild(buttons[i]); + } + + instructionsOverlay= new Text3DOverlay({ + text: START_RECORDING_TEXT, + isFacingAvatar: false, + alpha: 1.0, + ignoreRayIntersection: true, + offsetPosition: { + x: -0.5, + y: 0, + z: 0 + }, + dimensions: { x: 0, y: 0 }, + backgroundColor: { red: 0, green: 255, blue: 0 }, + color: { red: 0, green: 0, blue: 0 }, + topMargin: 0.00625, + leftMargin: 0.00625, + bottomMargin: 0.1, + rightMargin: 0.00625, + lineHeight: 0.06, + alpha: 1, + backgroundAlpha: 0.3, + visible: false + }); + + mainPanel.addChild(instructionsOverlay); + + cameraPanel = new OverlayPanel({ + anchorPositionBinding: { entity: cameraEntity }, + isFacingAvatar: false + }); + + onAirOverlay = new Image3DOverlay({ + url: RECORDING_ON_ICON_URL, + dimensions: { + x: 0.2, + y: 0.2 + }, + isFacingAvatar: false, + alpha: 0.8, + ignoreRayIntersection: true, + offsetPosition: { + x: 0, + y: 0.7, + z: 0 + }, + emissive: true, + visible: false, + }); + cameraPanel.addChild(onAirOverlay); + + isUIWorking = true; + }, + + cleanUI: function () { + workingHand.clean(); + if (mainPanel) { + mainPanel.destroy(); + } + mainPanel = null; + isUIWorking = false; + }, + + unload: function (entityID) { + this.cleanUI(); + if (cameraPanel) { + cameraPanel.destroy(); + } + } + } + + return new ReviewZone(); +}); \ No newline at end of file diff --git a/examples/vrShop/review/shopReviewerAC.js b/examples/vrShop/review/shopReviewerAC.js new file mode 100644 index 0000000000..7b7776fe98 --- /dev/null +++ b/examples/vrShop/review/shopReviewerAC.js @@ -0,0 +1,96 @@ +// shopReviewerAC.js +// + +// Created by Alessandro Signa and Edgar Pironti on 01/13/2016 +// Copyright 2016 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 + + +var command = null; +var clip_url = null; + +var REVIEW_CHANNEL = "reviewChannel"; +var playFromCurrentLocation = true; +var useDisplayName = true; +var useAttachments = true; +var useAvatarModel = true; + +var totalTime = 0; +var subscribed = false; +var WAIT_FOR_AUDIO_MIXER = 1; + +var PLAY = "Play"; +var SHOW = "Show"; +var HIDE = "Hide"; + +function getAction(channel, message, senderID) { + if(subscribed) { + print("I'm the agent and I received this: " + message); + + if (Recording.isPlaying()) { + Recording.stopPlaying(); + } + + m = JSON.parse(message); + + command = m.command; + clip_url = m.clip_url; + + switch(command) { + case PLAY: + print("Play"); + if (!Recording.isPlaying()) { + Recording.setPlayerTime(0.0); + Recording.startPlaying(); + } + break; + + case SHOW: + print("Show"); + Recording.loadRecording(clip_url); + Agent.isAvatar = true; + Recording.setPlayerTime(0.0); + Recording.startPlaying(); + Recording.stopPlaying(); + break; + + case HIDE: + print("Hide"); + Agent.isAvatar = false; + break; + + default: + print("Unknown action: " + action); + break; + } + } +} + + +function update(deltaTime) { + + totalTime += deltaTime; + + if (totalTime > WAIT_FOR_AUDIO_MIXER) { + if (!subscribed) { + Messages.subscribe(REVIEW_CHANNEL); + subscribed = true; + Recording.setPlayFromCurrentLocation(playFromCurrentLocation); + Recording.setPlayerUseDisplayName(useDisplayName); + Recording.setPlayerUseAttachments(useAttachments); + Recording.setPlayerUseHeadModel(false); + Recording.setPlayerUseSkeletonModel(useAvatarModel); + } + } + +} + +Messages.messageReceived.connect(function (channel, message, senderID) { + if (channel == REVIEW_CHANNEL) { + getAction(channel, message, senderID); + } +}); + +Script.update.connect(update); \ No newline at end of file diff --git a/examples/vrShop/shop/shopGrabSwapperEntityScript.js b/examples/vrShop/shop/shopGrabSwapperEntityScript.js new file mode 100644 index 0000000000..b6f52b2731 --- /dev/null +++ b/examples/vrShop/shop/shopGrabSwapperEntityScript.js @@ -0,0 +1,55 @@ +// shopGrapSwapperEntityScript.js +// +// This script handle the transition from handControllerGrab to shopItemGrab +// When an avatar enters the zone a message is sent to the handControllerGrab script to disable itself and the shopItemGrab is loaded. +// When exit from the zone the handControllerGrab is re-enabled. +// This mechanism may not work well with the last changes to the animations in handControllerGrab, if it's the case please load this script manually and disable that one. + +// Created by Alessandro Signa and Edgar Pironti on 01/13/2016 +// Copyright 2016 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 +// + +(function () { + var SHOP_GRAB_SCRIPT_URL = Script.resolvePath("../item/shopItemGrab.js"); + var SHOP_GRAB_CHANNEL = "Hifi-vrShop-Grab"; + var _this; + + function SwapGrabZone() { + _this = this; + return; + }; + + function isScriptRunning(script) { + script = script.toLowerCase().trim(); + var runningScripts = ScriptDiscoveryService.getRunning(); + for (i in runningScripts) { + if (runningScripts[i].url.toLowerCase().trim() == script) { + return true; + } + } + return false; + }; + + SwapGrabZone.prototype = { + + enterEntity: function (entityID) { + print("entering in the shop area"); + + if (!isScriptRunning(SHOP_GRAB_SCRIPT_URL)) { + Script.load(SHOP_GRAB_SCRIPT_URL); + } + + }, + + leaveEntity: function (entityID) { + print("leaving the shop area"); + Messages.sendMessage(SHOP_GRAB_CHANNEL, null); //signal to shopItemGrab that it has to kill itself + } + + } + + return new SwapGrabZone(); +}); \ No newline at end of file diff --git a/examples/vrShop/vendor/shopVendorAddItem.js b/examples/vrShop/vendor/shopVendorAddItem.js new file mode 100644 index 0000000000..a73fa97ba8 --- /dev/null +++ b/examples/vrShop/vendor/shopVendorAddItem.js @@ -0,0 +1,101 @@ +// shopVendorAddItem.js +// +// This shows a propt to allow the user to create a custom shop item, it also creates a DB entity for that object + +// Created by Alessandro Signa and Edgar Pironti on 01/13/2016 +// Copyright 2016 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 +// + +var TITLE = "Add Item Form"; +var DEFAULT_ITEM_NAME = "New Item"; +var DEFAULT_ROOT_DIRECTORY_URL = "https://dl.dropboxusercontent.com/u/14127429/FBX/VRshop/"; +var DEFAULT_MODEL_1_RELATIVE_PATH = "cubeRed.fbx"; // those paths are relative to the root directory url +var DEFAULT_PREVIEW_1_RELATIVE_PATH = "previewRed.png"; +var DEFAULT_MODEL_2_RELATIVE_PATH = "cubeGreen.fbx"; +var DEFAULT_PREVIEW_2_RELATIVE_PATH = "previewGreen.png"; +var DEFAULT_MODEL_3_RELATIVE_PATH = "cubeBlue.fbx"; +var DEFAULT_PREVIEW_3_RELATIVE_PATH = "previewBlue.png"; +var DEFAULT_QUANTITY = 1; +var DEFAULT_PRICE = 1.00; +var DEFAULT_DESCRIPTION = "Description empty"; + + +var scriptURL = Script.resolvePath("../item/shopItemEntityScript.js"); +var rotation = Quat.safeEulerAngles(Camera.getOrientation()); +rotation = Quat.fromPitchYawRollDegrees(0, rotation.y, 0); +var position = Vec3.sum(MyAvatar.position, Vec3.multiply(1.5, Quat.getFront(rotation))); + +var form = [ + {"label": "Item Name", "value": DEFAULT_ITEM_NAME}, + + {"label": "Root directory URL", "value": DEFAULT_ROOT_DIRECTORY_URL}, + {"label": "Model 1 relative path", "value": DEFAULT_MODEL_1_RELATIVE_PATH}, + {"label": "Preview 1 relative path", "value": DEFAULT_PREVIEW_1_RELATIVE_PATH}, + {"label": "Model 2 relative path", "value": DEFAULT_MODEL_2_RELATIVE_PATH}, + {"label": "Preview 2 relative path", "value": DEFAULT_PREVIEW_2_RELATIVE_PATH}, + {"label": "Model 3 relative path", "value": DEFAULT_MODEL_3_RELATIVE_PATH}, + {"label": "Preview 3 relative path", "value": DEFAULT_PREVIEW_3_RELATIVE_PATH}, + + {"label": "Quantity", "value": DEFAULT_QUANTITY}, + {"label": "Price", "value": DEFAULT_PRICE}, + {"label": "Description", "value": DEFAULT_DESCRIPTION}, + {"label": "Wearable", "type": "checkbox", "value": false}, + +]; + +var accepted = Window.form(TITLE, form); + +if (accepted) { + var modelURL = "" + form[1].value + form[2].value; + var myEntity = Entities.addEntity({ + type: "Model", + name: form[0].value, + position: position, + script: scriptURL, + rotation: rotation, + collisionsWillMove: false, + ignoreForCollisions: true, + collisionMask: "static,dynamic,otherAvatar", + shapeType: "box", + modelURL: modelURL, + userData: JSON.stringify({ + infoKey: { + rootURL: form[1].value, + modelURLs: [ + form[2].value, + form[4].value, + form[6].value + ], + previewURLs: [ + form[3].value, + form[5].value, + form[7].value + ], + availability: form[8].value, + price: form[9].value, + description: form[10].value, + wearable: form[11].value + } + }) + }); + + var myEntityDB = Entities.addEntity({ + type: "Box", + name: form[0].value + "DB", + position: {x: 0, y: 0, z: 0}, + rotation: rotation, + collisionsWillMove: false, + ignoreForCollisions: true, + visible: false, + shapeType: "box", + userData: JSON.stringify({ + infoKey: { + dbKey: [ + ] + } + }) + }); +} \ No newline at end of file From e4501e3a8f6c29a6c7584d1e4b5aa6878bfe89eb Mon Sep 17 00:00:00 2001 From: AlessandroSigna Date: Thu, 14 Jan 2016 18:23:39 -0800 Subject: [PATCH 2/4] Added cart, cash, inspect and readme --- examples/vrShop/README.txt | 51 ++ examples/vrShop/cart/shopCartEntityScript.js | 324 +++++++ .../vrShop/cart/shopCartSpawnEntityScript.js | 91 ++ .../vrShop/cart/shopCartZeroEntityScript.js | 39 + examples/vrShop/cash/shopCashEntityScript.js | 94 ++ .../cash/shopCashRegisterEntityScript.js | 166 ++++ examples/vrShop/cash/shopCashierAC.js | 73 ++ .../vrShop/cash/shopCreditCardEntityScript.js | 45 + .../vrShop/inspect/shopInspectEntityScript.js | 858 ++++++++++++++++++ 9 files changed, 1741 insertions(+) create mode 100644 examples/vrShop/README.txt create mode 100644 examples/vrShop/cart/shopCartEntityScript.js create mode 100644 examples/vrShop/cart/shopCartSpawnEntityScript.js create mode 100644 examples/vrShop/cart/shopCartZeroEntityScript.js create mode 100644 examples/vrShop/cash/shopCashEntityScript.js create mode 100644 examples/vrShop/cash/shopCashRegisterEntityScript.js create mode 100644 examples/vrShop/cash/shopCashierAC.js create mode 100644 examples/vrShop/cash/shopCreditCardEntityScript.js create mode 100644 examples/vrShop/inspect/shopInspectEntityScript.js diff --git a/examples/vrShop/README.txt b/examples/vrShop/README.txt new file mode 100644 index 0000000000..af20c2e23e --- /dev/null +++ b/examples/vrShop/README.txt @@ -0,0 +1,51 @@ +Instructions for experiencing vrShop +Alessandro Signa and Edgar Pironti, 13 Jan 2016 + +To avoid weird issues we suggest to run this build https://github.com/highfidelity/hifi/pull/6786 +To test the experience in the current release of interface that PR needs to be merged +and the scripts need to be updated to the new collisionMask system. + +Go to the vrShop domain. + +When you get closer to the shop (entering the Zone - GrabSwapper) you should notice that +shopItemGrab is in your running scripts. At this point the handControllerGrab should be disabled +(it will be re-enabled automatically when leaving the zone), but if you still see rays +coming out from your hands when you squeeze the triggers something went wrong so you probably +want to remove manually handControllerGrab + +Once you're here you can do two different experiences: buyer and reviewr. +Both of them are designed to be done entirely using HMD and hand controllers, +so please put your Oculus on and equip your hydra. + +BUYER +First of all you have to pass through the pick cart line, the Zone - CartSpawner is there. +Now you see a cart following you (look at your right). +Reach the shelf and grab an item (you can only near grab here). +An inspection area should now stay in front of you, drop the item inside it to inspect. Be sure +to see the overlay turning green before releasing the item. + +In inspect mode you can't move or rotate your avatar but you can scale and rotate the item. +Interact with the UI buttons using your bumpers: you can change the color of the item, +see the reviews (looking at the review cabin) and you can also try some of the items on (but +this feature at the moment works well only with the Will avatar increasing its default size once). + +If you are good with your item you can put it in the cart, otherwise just drop it around. +You can also pick again an item from your cart to inspect it or to discard it. +To empty the cart you can push the right primary button. + +Then you can go to the cashier for the "payment": once you enter the Zone - CashDesk +a credit card will appear above the register and a bot will give you some informations. +Just drag that card to the register to confirm the purchase. + +When you're ready to exit pass again through the leave cart line. + +REVIEWER +Go and grab the item you want to review, this time don't pick the cart. +Enter into the review cabin holding the item in your hand , if all goes right the item should disappear +and a UI appears allowing you to rate the item. +Now you can follow the instrucions to record a review: both the voice and the animation of your avatar +will be recorded and stored into the DB of that item. + + + + diff --git a/examples/vrShop/cart/shopCartEntityScript.js b/examples/vrShop/cart/shopCartEntityScript.js new file mode 100644 index 0000000000..b572c30bdd --- /dev/null +++ b/examples/vrShop/cart/shopCartEntityScript.js @@ -0,0 +1,324 @@ +// shopCartEntityScript.js +// +// This script makes the cart follow the avatar who picks it and manage interations with items (store and delete them) and with cash register (send the item prices) + +// Created by Alessandro Signa and Edgar Pironti on 01/13/2016 +// Copyright 2016 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 +// + + +(function() { + var utilitiesScript = Script.resolvePath("../../libraries/utils.js"); + var overlayManagerScript = Script.resolvePath("../../libraries/overlayManager.js"); + Script.include(utilitiesScript); + Script.include(overlayManagerScript); + + var COMFORT_ARM_LENGTH = 0.5; + var CART_REGISTER_CHANNEL = "Hifi-vrShop-Register"; + var PENETRATION_THRESHOLD = 0.2; + + var _this; + var cartIsMine = false; + var originalY = 0; + var itemsID = []; + var scaleFactor = 0.7; //TODO: The scale factor will dipend on the number of items in the cart. We would resize even the items already present. + var cartTargetPosition; + var singlePrices = []; + var singlePriceTagsAreShowing = false; + var collidedItemID = null; + + // this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember + // our this object, so we can access it in cases where we're called without a this (like in the case of various global signals) + ShopCart = function() { + _this = this; + }; + + function update(deltaTime) { + _this.followAvatar(); + + if (Controller.getValue(Controller.Standard.RightPrimaryThumb)) { + _this.resetCart(); + _this.computeAndSendTotalPrice(); + + } + }; + + function receivingMessage(channel, message, senderID) { + if (senderID === MyAvatar.sessionUUID && channel == CART_REGISTER_CHANNEL) { + var messageObj = JSON.parse(message); + if (messageObj.senderEntity != _this.entityID) { + print("--------------- cart received message"); + //Receiving this message means that the register wants the total price + _this.computeAndSendTotalPrice(); + } + } + + }; + + ShopCart.prototype = { + + // preload() will be called when the entity has become visible (or known) to the interface + // it gives us a chance to set our local JavaScript object up. In this case it means: + // * remembering our entityID, so we can access it in cases where we're called without an entityID + // * connecting to the update signal so we can check our grabbed state + preload: function(entityID) { + this.entityID = entityID; + //get the owner ID from user data and compare to the mine + //so the update will be connected just for the owner + var ownerObj = getEntityCustomData('ownerKey', this.entityID, null); + if (ownerObj.ownerID === MyAvatar.sessionUUID) { + cartIsMine = true; + cartTargetPosition = Entities.getEntityProperties(_this.entityID).position; //useful if the entity script is assigned manually + Script.update.connect(update); + Messages.subscribe(CART_REGISTER_CHANNEL); + Messages.messageReceived.connect(receivingMessage); + } + }, + + //update cart's target position. It will be at the right of the avatar as long as he moves + followAvatar: function() { + if (Vec3.length(MyAvatar.getVelocity()) > 0.1) { + var radius = (Entities.getEntityProperties(_this.entityID).dimensions.x) / 2 + COMFORT_ARM_LENGTH; + var properY = MyAvatar.position.y + ((MyAvatar.getHeadPosition().y - MyAvatar.position.y) / 2); + var targetPositionPrecomputing = {x: MyAvatar.position.x, y: properY, z: MyAvatar.position.z}; + cartTargetPosition = Vec3.sum(targetPositionPrecomputing, Vec3.multiply(Quat.getRight(MyAvatar.orientation), radius)); + } + + var cartPosition = Entities.getEntityProperties(_this.entityID).position; + var positionDifference = Vec3.subtract(cartTargetPosition, cartPosition); + if (Vec3.length(positionDifference) > 0.1) { + //give to the cart the proper velocity and make it ignore for collision + Entities.editEntity(_this.entityID, { velocity: positionDifference }); + Entities.editEntity(_this.entityID, { ignoreForCollisions: true }); + if (collidedItemID != null) { + Entities.callEntityMethod(collidedItemID, 'setCartOverlayNotVisible', null); + collidedItemID = null; + } + } else if (Vec3.length(positionDifference) > 0.01) { + //give to the cart the proper velocity and make it NOT ignore for collision + Entities.editEntity(_this.entityID, { velocity: positionDifference }); + Entities.editEntity(_this.entityID, { ignoreForCollisions: false }); + } else if (Vec3.length(positionDifference) > 0) { + //set the position to be at the right of MyAvatar and make it NOT ignore for collision + Entities.editEntity(_this.entityID, { position: cartTargetPosition }); + positionDifference = Vec3.subtract(cartTargetPosition, cartPosition); + Entities.editEntity(_this.entityID, { velocity: positionDifference }); + Entities.editEntity(_this.entityID, { ignoreForCollisions: false }); + } + + }, + + // delete all items stored into the cart + resetCart: function () { + + //print("RESET CART - USER DATA: " + Entities.getEntityProperties(_this.entityID).userData); + if (itemsID.length != 0) { + if (singlePriceTagsAreShowing) { + _this.singlePriceOff(); + } + for (var i=0; i < itemsID.length; i++) { + Entities.deleteEntity(itemsID[i]); + } + + // Clear the userData field for the cart + Entities.editEntity(this.entityID, { userData: ""}); + + setEntityCustomData('ownerKey', this.entityID, { + ownerID: MyAvatar.sessionUUID + }); + + setEntityCustomData('grabbableKey', this.entityID, { + grabbable: false + }); + + itemsID = []; + } + }, + + //delete the item pointed by dataArray (data.id) from the cart because it's been grabbed from there + refreshCartContent: function (entityID, dataArray) { + var data = JSON.parse(dataArray[0]); + + for (var i=0; i < itemsID.length; i++) { + if(itemsID[i] == data.id) { + itemsID.splice(i, 1); + //if the price tags are showing we have to remove also the proper tag + if (singlePriceTagsAreShowing) { + singlePrices[i].destroy(); + singlePrices.splice(i, 1); + } + } + } + + _this.computeAndSendTotalPrice(); + }, + + //show the prices on each item into the cart + singlePriceOn: function () { + //create an array of text3D which follows the structure of the itemsID array. Each text3D is like the 'Store the item!' one + var i = 0; + itemsID.forEach( function(itemID) { + singlePrices[i] = new OverlayPanel({ + anchorPositionBinding: { entity: itemID }, + offsetPosition: { x: 0, y: 0.15, z: 0 }, + isFacingAvatar: true, + + }); + + var textPrice = new Text3DOverlay({ + text: "" + getEntityCustomData('infoKey', itemID, null).price + " $", + isFacingAvatar: false, + alpha: 1.0, + ignoreRayIntersection: true, + dimensions: { x: 0, y: 0 }, + backgroundColor: { red: 255, green: 255, blue: 255 }, + color: { red: 0, green: 0, blue: 0 }, + topMargin: 0.00625, + leftMargin: 0.00625, + bottomMargin: 0.1, + rightMargin: 0.00625, + lineHeight: 0.02, + alpha: 1, + backgroundAlpha: 0.3, + visible: true + }); + + singlePrices[i].addChild(textPrice); + i++; + }); + + singlePriceTagsAreShowing = true; + }, + + singlePriceOff: function () { + //destroy or make invisible the text3D, or both + singlePrices.forEach(function(panel) { + panel.destroy(); + }); + singlePrices = []; + singlePriceTagsAreShowing = false; + }, + + //Send to the register the total price for all the items in the cart + computeAndSendTotalPrice: function () { + var totalPrice = 0; + itemsID.forEach( function(itemID) { + var infoObj = getEntityCustomData('infoKey', itemID, null); + if(infoObj != null) { + totalPrice += infoObj.price; + } + }); + + var messageObj = {senderEntity: _this.entityID, totalPrice: totalPrice}; + Messages.sendMessage(CART_REGISTER_CHANNEL, JSON.stringify(messageObj)); + }, + + //dataArray stores the ID of the item which has to be stored into the cart + //this entity method is invoked by shopItemEntityScript.js + doSomething: function (entityID, dataArray) { + collidedItemID = null; + var data = JSON.parse(dataArray[0]); + var itemOwnerObj = getEntityCustomData('ownerKey', data.id, null); + + var cartOwnerObj = getEntityCustomData('ownerKey', this.entityID, null); + + if (cartOwnerObj == null) { + //print("The cart doesn't have a owner."); + Entities.deleteEntity(data.id); + } + + if (itemOwnerObj.ownerID === cartOwnerObj.ownerID) { + // TODO if itemsQuantity == fullCart resize all the items present in the cart and change the scaleFactor for this and next insert + + print("Going to put item in the cart!"); + var itemsQuantity = itemsID.length; + + itemsID[itemsQuantity] = data.id; + + var oldDimension = Entities.getEntityProperties(data.id).dimensions; + Entities.editEntity(data.id, { dimensions: Vec3.multiply(oldDimension, scaleFactor) }); + Entities.editEntity(data.id, { velocity: {x: 0.0, y: 0.0, z: 0.0} }); + // parent item to the cart + Entities.editEntity(data.id, { parentID: this.entityID }); + + itemsQuantity = itemsID.length; + + setEntityCustomData('statusKey', data.id, { + status: "inCart" + }); + + _this.computeAndSendTotalPrice(); + + //if the single price tags are showing we have to put a tag also in the new item + if (singlePriceTagsAreShowing) { + singlePrices[itemsQuantity-1] = new OverlayPanel({ + anchorPositionBinding: { entity: data.id }, + offsetPosition: { x: 0, y: 0.15, z: 0 }, + isFacingAvatar: true, + + }); + + var textPrice = new Text3DOverlay({ + text: "" + getEntityCustomData('infoKey', data.id, null).price + " $", + isFacingAvatar: false, + alpha: 1.0, + ignoreRayIntersection: true, + dimensions: { x: 0, y: 0 }, + backgroundColor: { red: 255, green: 255, blue: 255 }, + color: { red: 0, green: 0, blue: 0 }, + topMargin: 0.00625, + leftMargin: 0.00625, + bottomMargin: 0.1, + rightMargin: 0.00625, + lineHeight: 0.02, + alpha: 1, + backgroundAlpha: 0.3, + visible: true + }); + + singlePrices[itemsQuantity-1].addChild(textPrice); + } + } else { + print("Not your cart!"); + Entities.deleteEntity(data.id); + } + }, + + //detect when the item enters or leave the cart while it's grabbed + collisionWithEntity: function(myID, otherID, collisionInfo) { + var penetrationValue = Vec3.length(collisionInfo.penetration); + + var cartOwnerObj = getEntityCustomData('ownerKey', myID, null); + var itemOwnerObj = getEntityCustomData('ownerKey', otherID, null); + + if (penetrationValue > PENETRATION_THRESHOLD && collidedItemID === null) { + if (itemOwnerObj != null && itemOwnerObj.ownerID === cartOwnerObj.ownerID) { + Entities.callEntityMethod(otherID, 'setCartOverlayVisible', null); + collidedItemID = otherID; + } + } else if (penetrationValue < PENETRATION_THRESHOLD && collidedItemID !== null) { + if (itemOwnerObj != null && itemOwnerObj.ownerID === cartOwnerObj.ownerID) { + Entities.callEntityMethod(otherID, 'setCartOverlayNotVisible', null); + collidedItemID = null; + } + } + }, + + unload: function (entityID) { + print("UNLOAD CART"); + if(cartIsMine){ + Script.update.disconnect(update); + _this.resetCart(); //useful if the script is reloaded manually + //Entities.deleteEntity(_this.entityID); //comment for manual reload + Messages.unsubscribe(CART_REGISTER_CHANNEL); + Messages.messageReceived.disconnect(receivingMessage); + } + } + }; + + // entity scripts always need to return a newly constructed object of our type + return new ShopCart(); +}) \ No newline at end of file diff --git a/examples/vrShop/cart/shopCartSpawnEntityScript.js b/examples/vrShop/cart/shopCartSpawnEntityScript.js new file mode 100644 index 0000000000..b5b678d9fb --- /dev/null +++ b/examples/vrShop/cart/shopCartSpawnEntityScript.js @@ -0,0 +1,91 @@ +// shopCartSpawnEntityScript.js +// +// If an avatar doesn't own a cart and enters the zone, a cart is added. +// Otherwise if it already has a cart, this will be destroyed + +// Created by Alessandro Signa and Edgar Pironti on 01/13/2016 +// Copyright 2016 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 +// + + +(function () { + var CART_MASTER_NAME = "ShopCartZero"; + var CART_SCRIPT_URL = Script.resolvePath("shopCartEntityScript.js"); + var SHOP_GRAB_SCRIPT_URL = Script.resolvePath("../item/shopItemGrab.js"); + var _this; + var isOwningACart = false; + var cartMasterID = null; + var myCartID = null; + + + function SpawnCartZone() { + _this = this; + return; + }; + + + + SpawnCartZone.prototype = { + + preload: function (entityID) { + this.entityID = entityID; + // Look for the ShopCartZero. Every cart created by this script is a copy of it + var ids = Entities.findEntities(Entities.getEntityProperties(this.entityID).position, 50); + ids.forEach(function(id) { + var properties = Entities.getEntityProperties(id); + if (properties.name == CART_MASTER_NAME) { + cartMasterID = id; + print("Master Cart found"); + Entities.editEntity(_this.entityID, { collisionMask: "static,dynamic,otherAvatar" }); + + return; + } + }); + }, + + enterEntity: function (entityID) { + print("entering in the spawn cart area"); + + if (myCartID) { + Entities.callEntityMethod(myCartID, "resetCart"); + Entities.deleteEntity (myCartID); + myCartID = null; + } else { + var entityProperties = Entities.getEntityProperties(cartMasterID); + myCartID = Entities.addEntity({ + type: entityProperties.type, + name: "Shopping cart", + ignoreForCollisions: false, + collisionsWillMove: false, + position: entityProperties.position, + dimensions: entityProperties.dimensions, + modelURL: entityProperties.modelURL, + shapeType: entityProperties.shapeType, + originalTextures: entityProperties.originalTextures, + script: CART_SCRIPT_URL, + userData: JSON.stringify({ + ownerKey: { + ownerID: MyAvatar.sessionUUID + }, + grabbableKey: { + grabbable: false + } + }) + }); + } + }, + + leaveEntity: function (entityID) { + + }, + + unload: function (entityID) { + + } + } + + return new SpawnCartZone(); +}); \ No newline at end of file diff --git a/examples/vrShop/cart/shopCartZeroEntityScript.js b/examples/vrShop/cart/shopCartZeroEntityScript.js new file mode 100644 index 0000000000..6b3ef96a59 --- /dev/null +++ b/examples/vrShop/cart/shopCartZeroEntityScript.js @@ -0,0 +1,39 @@ +// shopCartZeroEntityScript.js +// + +// Created by Alessandro Signa and Edgar Pironti on 01/13/2016 +// Copyright 2016 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 +// + + +(function() { + BALL_ANGULAR_VELOCITY = {x:0, y:5, z:0} + var _this; + + + // this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember + // our this object, so we can access it in cases where we're called without a this (like in the case of various global signals) + ShopCartZero = function() { + _this = this; + }; + + + ShopCartZero.prototype = { + + // preload() will be called when the entity has become visible (or known) to the interface + // it gives us a chance to set our local JavaScript object up. In this case it means: + // * remembering our entityID, so we can access it in cases where we're called without an entityID + // * connecting to the update signal so we can check our grabbed state + preload: function(entityID) { + this.entityID = entityID; + Entities.editEntity(_this.entityID, { angularVelocity: BALL_ANGULAR_VELOCITY }); + Entities.editEntity(_this.entityID, { angularDamping: 0 }); + }, + }; + + // entity scripts always need to return a newly constructed object of our type + return new ShopCartZero(); +}) \ No newline at end of file diff --git a/examples/vrShop/cash/shopCashEntityScript.js b/examples/vrShop/cash/shopCashEntityScript.js new file mode 100644 index 0000000000..9efdaf8ee2 --- /dev/null +++ b/examples/vrShop/cash/shopCashEntityScript.js @@ -0,0 +1,94 @@ +// shopCashEntityScript.js +// +// When an avatar enters the zone the cashier bot (agent) is activated and the cash register as well. +// A credit card model will be added to the scene for make the payment + +// Created by Alessandro Signa and Edgar Pironti on 01/13/2016 +// Copyright 2016 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 +// + + +(function () { + var AGENT_PLAYBACK_CHANNEL = "playbackChannel"; + var PLAY_MESSAGE = "Play"; + var REGISTER_NAME = "CashRegister"; + var CARD_ANGULAR_VELOCITY = {x: 0, y: 2, z: 0}; + var CARD_POSITION_OFFSET = {x: 0, y: 0.5, z: 0}; + var CARD_INITIAL_ORIENTATION = {x: 0, y: 0, z: 40}; + var CARD_DIMENSIONS = {x: 0.02, y: 0.09, z: 0.15}; + var SCRIPT_URL = Script.resolvePath("shopCreditCardEntityScript.js"); + + var _this; + var cashRegisterID = null; + var cardID = null; + + + function CashZone() { + _this = this; + return; + }; + + CashZone.prototype = { + + preload: function (entityID) { + }, + + enterEntity: function (entityID) { + print("entering in the cash area"); + Messages.sendMessage(AGENT_PLAYBACK_CHANNEL, PLAY_MESSAGE); + print("Play sent."); + + // Look for the register + var entitiesInZone = Entities.findEntities(Entities.getEntityProperties(entityID).position, (Entities.getEntityProperties(entityID).dimensions.x)/2); + entitiesInZone.forEach( function(e) { + if (Entities.getEntityProperties(e).name == REGISTER_NAME) { + cashRegisterID = Entities.getEntityProperties(e).id; + print(cashRegisterID); + } + }); + + // create a credit card above register position + var cardPosition = Vec3.sum(Entities.getEntityProperties(cashRegisterID).position, CARD_POSITION_OFFSET); + var cardOrientationQuat = Quat.fromVec3Degrees(CARD_INITIAL_ORIENTATION); + + cardID = Entities.addEntity({ + type: "Model", + name: "CreditCard", + position: cardPosition, + rotation: cardOrientationQuat, + dimensions: CARD_DIMENSIONS, + collisionsWillMove: false, + ignoreForCollisions: false, + angularVelocity: CARD_ANGULAR_VELOCITY, + angularDamping: 0, + script: Script.resolvePath(SCRIPT_URL), + // We have to put the ownerID in the card, and check this property when grabbing the card. Otherwise it cannot be grabbed - I can only grab my card + userData: JSON.stringify({ + ownerKey: { + ownerID: MyAvatar.sessionUUID + } + }), + modelURL: "https://dl.dropboxusercontent.com/u/14127429/FBX/VRshop/CreditCard.fbx", + shapeType: "box" + }); + + Entities.callEntityMethod(cashRegisterID, 'cashRegisterOn', null); + }, + + leaveEntity: function (entityID) { + // destroy card + Entities.deleteEntity(cardID); + cardID = null; + + Entities.callEntityMethod(cashRegisterID, 'cashRegisterOff', null); + }, + + unload: function (entityID) { + } + } + + return new CashZone(); +}); \ No newline at end of file diff --git a/examples/vrShop/cash/shopCashRegisterEntityScript.js b/examples/vrShop/cash/shopCashRegisterEntityScript.js new file mode 100644 index 0000000000..bc6dd25f75 --- /dev/null +++ b/examples/vrShop/cash/shopCashRegisterEntityScript.js @@ -0,0 +1,166 @@ +// shopCashRegisterEntityScript.js +// +// The register manages the total price overlay and the payment through collision with credit card + +// Created by Alessandro Signa and Edgar Pironti on 01/13/2016 +// Copyright 2016 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 +// + +(function () { + var overlayManagerScript = Script.resolvePath("../../libraries/overlayManager.js"); + Script.include(overlayManagerScript); + + var SHOPPING_CART_NAME = "Shopping cart"; + var CART_REGISTER_CHANNEL = "Hifi-vrShop-Register"; + var CREDIT_CARD_NAME = "CreditCard"; + + var _this; + var cartID = null; + var registerPanel = null; + var priceText = null; + var payingAvatarID = null; + var totalPrice = 0; + + function CashRegister() { + _this = this; + return; + }; + + function receivingMessage(channel, message, senderID) { + if (senderID === MyAvatar.sessionUUID && channel == CART_REGISTER_CHANNEL) { + var messageObj = JSON.parse(message); + if (messageObj.senderEntity != _this.entityID) { + //This message means that the cart sent the total price: create or update the Overlay on the register + var price = messageObj.totalPrice.toFixed(2); + _this.cashRegisterOverlayOn("" + price + " $"); + totalPrice = messageObj.totalPrice; + } + } + }; + + CashRegister.prototype = { + + preload: function (entityID) { + this.entityID = entityID; + }, + + //This method is called by the cashZone when an avatar comes in it + //It has to find the cart belonging to that avatar and ask it the total price of the items + cashRegisterOn: function() { + print("cashRegister ON"); + Messages.subscribe(CART_REGISTER_CHANNEL); + Messages.messageReceived.connect(receivingMessage); + var cashRegisterPosition = Entities.getEntityProperties(_this.entityID).position; + var foundEntities = Entities.findEntities(cashRegisterPosition, 50); + foundEntities.forEach( function (foundEntityID) { + var entityName = Entities.getEntityProperties(foundEntityID).name; + if (entityName == SHOPPING_CART_NAME) { + var cartOwnerID = getEntityCustomData('ownerKey', foundEntityID, null).ownerID; + if (cartOwnerID == MyAvatar.sessionUUID) { + cartID = foundEntityID; + + } + } + }); + if (cartID != null) { + payingAvatarID = MyAvatar.sessionUUID; + Messages.sendMessage(CART_REGISTER_CHANNEL, JSON.stringify({senderEntity: _this.entityID})); //with this message the cart know that it has to compute and send back the total price of the items + Entities.callEntityMethod(cartID, 'singlePriceOn', null); + //sometimes the cart is unable to receive the message. I think it's a message mixer problem + } else { + payingAvatarID = null; + // Show anyway the overlay with the price 0$ + _this.cashRegisterOverlayOn("0 $"); + } + }, + + cashRegisterOff: function() { + print("cashRegister OFF"); + Messages.unsubscribe(CART_REGISTER_CHANNEL); + Messages.messageReceived.disconnect(receivingMessage); + priceText.visible = false; + if (cartID != null) { + Entities.callEntityMethod(cartID, 'singlePriceOff', null); + } + }, + + cashRegisterOverlayOn: function (string) { + print("cashRegister OVERLAY ON"); + var stringOffset = string.length * 0.018; + if (priceText == null) { + + registerPanel = new OverlayPanel({ + anchorPositionBinding: { entity: _this.entityID }, + offsetPosition: { x: 0, y: 0.21, z: -0.14 }, + isFacingAvatar: false, + + }); + + priceText = new Text3DOverlay({ + text: string, + isFacingAvatar: false, + ignoreRayIntersection: true, + dimensions: { x: 0, y: 0 }, + offsetPosition: { + x: -stringOffset, + y: 0, + z: 0 + }, + backgroundColor: { red: 255, green: 255, blue: 255 }, + color: { red: 0, green: 255, blue: 0 }, + topMargin: 0.00625, + leftMargin: 0.00625, + bottomMargin: 0.1, + rightMargin: 0.00625, + lineHeight: 0.06, + alpha: 1, + backgroundAlpha: 0.3, + visible: true + }); + + registerPanel.addChild(priceText); + } else { + priceText.text = string; + priceText.visible = true; + priceText.offsetPosition = { + x: -stringOffset, + y: 0, + z: 0 + }; + } + }, + + //Manage the collision with credit card + collisionWithEntity: function (myID, otherID, collisionInfo) { + var entityName = Entities.getEntityProperties(otherID).name; + var entityOwnerID = getEntityCustomData('ownerKey', otherID, null).ownerID; + if (entityName == CREDIT_CARD_NAME && entityOwnerID == payingAvatarID) { + //The register collided with the right credit card - CHECKOUT + Entities.deleteEntity(otherID); + Entities.callEntityMethod(cartID, 'resetCart', null); + _this.cashRegisterOverlayOn("THANK YOU!"); + _this.clean(); + } + }, + + //clean all the variable related to the cart + clean: function () { + cartID = null; + payingAvatarID = null; + totalPrice = 0; + }, + + unload: function (entityID) { + _this.clean(); + if (registerPanel != null) { + registerPanel.destroy(); + } + registerPanel = priceText = null; + } + } + + return new CashRegister(); +}); \ No newline at end of file diff --git a/examples/vrShop/cash/shopCashierAC.js b/examples/vrShop/cash/shopCashierAC.js new file mode 100644 index 0000000000..1219806e8a --- /dev/null +++ b/examples/vrShop/cash/shopCashierAC.js @@ -0,0 +1,73 @@ +// shopCashierAC.js +// + +// Created by Alessandro Signa and Edgar Pironti on 01/13/2016 +// Copyright 2016 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 + +// Set the following variable to the values needed +var clip_url = "atp:6865d9c89472d58b18929aff0ac779026bc129190f7536f71d3835f7e2629c93.hfr"; // This url is working in VRshop + + +var PLAYBACK_CHANNEL = "playbackChannel"; +var playFromCurrentLocation = false; +var useDisplayName = true; +var useAttachments = true; +var useAvatarModel = true; + +var totalTime = 0; +var subscribed = false; +var WAIT_FOR_AUDIO_MIXER = 1; + +var PLAY = "Play"; + +function getAction(channel, message, senderID) { + if(subscribed) { + print("I'm the agent and I received this: " + message); + + switch(message) { + case PLAY: + print("Play"); + if (!Recording.isPlaying()) { + Recording.setPlayerTime(0.0); + Recording.startPlaying(); + } + break; + + default: + print("Unknown action: " + action); + break; + } + } +} + + +function update(deltaTime) { + + totalTime += deltaTime; + + if (totalTime > WAIT_FOR_AUDIO_MIXER) { + if (!subscribed) { + Messages.subscribe(PLAYBACK_CHANNEL); + subscribed = true; + Recording.loadRecording(clip_url); + Recording.setPlayFromCurrentLocation(playFromCurrentLocation); + Recording.setPlayerUseDisplayName(useDisplayName); + Recording.setPlayerUseAttachments(useAttachments); + Recording.setPlayerUseHeadModel(false); + Recording.setPlayerUseSkeletonModel(useAvatarModel); + Agent.isAvatar = true; + } + } + +} + +Messages.messageReceived.connect(function (channel, message, senderID) { + if (channel == PLAYBACK_CHANNEL) { + getAction(channel, message, senderID); + } +}); + +Script.update.connect(update); \ No newline at end of file diff --git a/examples/vrShop/cash/shopCreditCardEntityScript.js b/examples/vrShop/cash/shopCreditCardEntityScript.js new file mode 100644 index 0000000000..cfefebfcda --- /dev/null +++ b/examples/vrShop/cash/shopCreditCardEntityScript.js @@ -0,0 +1,45 @@ +// shopCreditCardEntityScript.js +// + +// Created by Alessandro Signa and Edgar Pironti on 01/13/2016 +// Copyright 2016 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 +// + +(function() { + + var _this; + var entityProperties = null; + + // this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember + // our this object, so we can access it in cases where we're called without a this (like in the case of various global signals) + CreditCard = function() { + _this = this; + }; + + CreditCard.prototype = { + + preload: function(entityID) { + this.entityID = entityID; + var ownerObj = getEntityCustomData('ownerKey', this.entityID, null); + if (ownerObj.ownerID === MyAvatar.sessionUUID) { + myCard = true; + entityProperties = Entities.getEntityProperties(this.entityID); + } + }, + + releaseGrab: function () { + //reset the card to its original properties (position, angular velocity, ecc) + Entities.editEntity(_this.entityID, entityProperties); + }, + + unload: function (entityID) { + Entities.deleteEntity(this.entityID); + } + }; + + // entity scripts always need to return a newly constructed object of our type + return new CreditCard(); +}) \ No newline at end of file diff --git a/examples/vrShop/inspect/shopInspectEntityScript.js b/examples/vrShop/inspect/shopInspectEntityScript.js new file mode 100644 index 0000000000..dc8537043f --- /dev/null +++ b/examples/vrShop/inspect/shopInspectEntityScript.js @@ -0,0 +1,858 @@ +// shopInspectEntityScript.js +// +// The inspection entity which runs this entity script will be in fron of the avatar while he's grabbing an item. +// This script gives some information to the avatar using interactive 3DOverlays: +// - Drive the customer to put he item in the correct zone to inspect it +// - Create the UI while inspecting with text and buttons and manages the clicks on them modifying the item and communicating with agents +// And also it creates the MyController which are in charge to render the rays from the hands during inspecting and analysing their intersection with other overlays + +// Created by Alessandro Signa and Edgar Pironti on 01/13/2016 +// Copyright 2016 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 +// + + +(function() { + + + var utilitiesScript = Script.resolvePath("../../libraries/utils.js"); + var overlayManagerScript = Script.resolvePath("../../libraries/overlayManager.js"); + Script.include(utilitiesScript); + Script.include(overlayManagerScript); + + var AGENT_REVIEW_CHANNEL = "reviewChannel"; + var NO_REVIEWS_AVAILABLE = "No reviews available"; + var SEPARATOR = "Separator"; + var CAMERA_REVIEW = "CameraReview"; + + var ZERO_STAR_URL = "https://dl.dropboxusercontent.com/u/14127429/FBX/VRshop/0Star.png"; + var ONE_STAR_URL = "https://dl.dropboxusercontent.com/u/14127429/FBX/VRshop/1Star.png"; + var TWO_STAR_URL = "https://dl.dropboxusercontent.com/u/14127429/FBX/VRshop/2Star.png"; + var THREE_STAR_URL = "https://dl.dropboxusercontent.com/u/14127429/FBX/VRshop/3Star.png"; + + var POINTER_ICON_URL = "https://dl.dropboxusercontent.com/u/14127429/FBX/VRshop/Pointer.png"; + var TRY_ON_ICON = "https://dl.dropboxusercontent.com/u/14127429/FBX/VRshop/TryOn.png" + + var MIN_DIMENSION_THRESHOLD = null; + var IN_HAND_STATUS = "inHand"; + var IN_INSPECT_STATUS = "inInspect"; + + var RIGHT_HAND = 1; + var LEFT_HAND = 0; + + var LINE_LENGTH = 100; + var COLOR = { + red: 165, + green: 199, + blue: 218 + }; + + var COMFORT_ARM_LENGTH = 0.4; + + var PENETRATION_THRESHOLD = 0.2; + + var _this; + var inspecting = false; + var inspectingMyItem = false; + var inspectedEntityID = null; + var isUIWorking = false; + var wantToStopTrying = false; + var rightController = null; + var leftController = null; + var workingHand = null; + var collidedItemID = null; + var tempTryEntity = null; + var tryingOnAvatar = false; + var itemOriginalDimensions = null; + + var mainPanel = null; + var mirrorPanel = null; + var buttons = []; + var tryOnAvatarButton = null; + var playButton = null; + var nextButton = null; + var textReviewerName = null; + var modelURLsArray = []; + var previewURLsArray = []; + var starURL = null; + + var reviewIndex = 0; + var reviewsNumber = 0; + var dbMatrix = null; + + var separator = null; + var cameraReview = null; + + + var pointer = new Image3DOverlay({ //maybe we want to use one pointer for each hand ? + url: POINTER_ICON_URL, + dimensions: { + x: 0.015, + y: 0.015 + }, + alpha: 1, + emissive: true, + isFacingAvatar: false, + ignoreRayIntersection: true, + }) + + + + // this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember + // our this object, so we can access it in cases where we're called without a this (like in the case of various global signals) + InspectEntity = function() { + _this = this; + }; + + + function MyController(hand) { + this.hand = hand; + if (this.hand === RIGHT_HAND) { + this.getHandPosition = MyAvatar.getRightPalmPosition; + this.getHandRotation = MyAvatar.getRightPalmRotation; + this.bumper = Controller.Standard.RB; + } else { + this.getHandPosition = MyAvatar.getLeftPalmPosition; + this.getHandRotation = MyAvatar.getLeftPalmRotation; + this.bumper = Controller.Standard.LB; + } + + this.pickRay = null; // ray object + this.overlayLine = null; // id of line overlay + this.waitingForBumpReleased = false; + + this.overlayLineOn = function(closePoint, farPoint, color) { + if (this.overlayLine == null) { + var lineProperties = { + lineWidth: 5, + start: closePoint, + end: farPoint, + color: color, + ignoreRayIntersection: true, + visible: true, + alpha: 1 + }; + this.overlayLine = new Line3DOverlay(lineProperties); + } else { + this.overlayLine.start = closePoint; + this.overlayLine.end = farPoint; + } + }, + + this.updateRay = function() { + //update the ray object + this.pickRay = { + origin: this.getHandPosition(), + direction: Quat.getUp(this.getHandRotation()) + }; + + //update the ray overlay and the pointer + var rayPickResult = OverlayManager.findRayIntersection(this.pickRay); + if (rayPickResult.intersects) { + var normal = Vec3.multiply(Quat.getFront(Camera.getOrientation()), -1); + var offset = Vec3.multiply(normal, 0.001); + pointer.position = Vec3.sum(rayPickResult.intersection, offset); //pointer is a global Image3DOverlay + pointer.rotation = Camera.getOrientation(); + pointer.visible = true; + } else { + pointer.visible = false; + } + + var farPoint = rayPickResult.intersects ? rayPickResult.intersection : Vec3.sum(this.pickRay.origin, Vec3.multiply(this.pickRay.direction, LINE_LENGTH)); + this.overlayLineOn(this.pickRay.origin, farPoint, COLOR); + + }, + //the update of each hand has to update the ray belonging to that hand and handle the bumper event + this.updateHand = function() { + //detect the bumper event + var bumperPressed = Controller.getValue(this.bumper); + if (bumperPressed && this != workingHand) { + workingHand.clean(); + workingHand = this; + } else if (this != workingHand) { + return; + } + + this.updateRay(); + + //manage event on UI + if (bumperPressed && !this.waitingForBumpReleased) { + this.waitingForBumpReleased = true; //to avoid looping on the button while keep pressing the bumper + var triggeredButton = OverlayManager.findOnRay(this.pickRay); + if (triggeredButton != null) { + //search the index of the UI element triggered + for (var i = 0; i < buttons.length; i++) { + if (buttons[i] == triggeredButton) { + + _this.changeModel(i); + } + } + + //the nextButton moves to the next customer review, changing the agent accordingly + if (nextButton == triggeredButton) { + + reviewIndex ++; + if (reviewIndex == reviewsNumber) { + reviewIndex = 0; + } + + var message = { + command: "Show", + clip_url: dbMatrix[reviewIndex].clip_url + }; + + Messages.sendMessage(AGENT_REVIEW_CHANNEL, JSON.stringify(message)); + print("Show sent to agent"); + + // update UI + textReviewerName.text = dbMatrix[reviewIndex].name; + reviewerScore.url = starConverter(dbMatrix[reviewIndex].score); + print("UI updated"); + } + + //the playButton sends the play command to the agent + if (playButton == triggeredButton) { + var message = { + command: "Play", + clip_url: dbMatrix[reviewIndex].clip_url + }; + + Messages.sendMessage(AGENT_REVIEW_CHANNEL, JSON.stringify(message)); + print("Play sent to agent"); + + } + + //the tryOnAvatarButton change the camera mode and puts t a copy of the inspected item in a proper position on the the avatar + if (tryOnAvatarButton == triggeredButton) { + print("tryOnAvatar pressed!"); + + var itemPositionWhileTrying = null; + //All the offset here are good just for Will avatar increasing its size by one + switch (Entities.getEntityProperties(inspectedEntityID).name) { + case "Item_Sunglasses": + itemPositionWhileTrying = {x: 0, y: 0.04, z: 0.05}; + break; + case "Item_Hat": + itemPositionWhileTrying = {x: 0, y: 0.16, z: 0.025}; + break; + default: + //there isn't any position specified for that item, use a default one + itemPositionWhileTrying = {x: 0, y: 0.16, z: 0.025}; + break; + } + + //Code for the overlay text for the mirror. + mirrorPanel = new OverlayPanel({ + anchorPositionBinding: { avatar: "MyAvatar" }, + anchorRotationBinding: { avatar: "MyAvatar" }, + offsetPosition: { + x: 0.5, + y: 0.9, + z: 0 + }, + offsetRotation: Quat.fromVec3Degrees({x: 0, y: 180, z: 0}), + + isFacingAvatar: false + }); + var mirrorText = new Text3DOverlay({ + text: "Press the bumper to go back in inspection", + isFacingAvatar: false, + ignoreRayIntersection: true, + + + dimensions: { x: 0, y: 0 }, + backgroundColor: { red: 255, green: 255, blue: 255 }, + color: { red: 200, green: 0, blue: 0 }, + topMargin: 0.00625, + leftMargin: 0.00625, + bottomMargin: 0.1, + rightMargin: 0.00625, + lineHeight: 0.05, + alpha: 1, + backgroundAlpha: 0.3, + visible: true + }); + mirrorPanel.addChild(mirrorText); + + tryingOnAvatar = true; + + //Clean inspect Overlays and related stuff + workingHand.clean(); + mainPanel.destroy(); + mainPanel = null; + isUIWorking = false; + Entities.editEntity(inspectedEntityID, { visible: false }); //the inspected item becomes invisible + + + Camera.mode = "entity"; + Camera.cameraEntity = _this.entityID; + var entityProperties = Entities.getEntityProperties(inspectedEntityID); + tempTryEntity = Entities.addEntity({ + type: entityProperties.type, + name: entityProperties.name, + localPosition: itemPositionWhileTrying, + dimensions: itemOriginalDimensions, + collisionsWillMove: false, + ignoreForCollisions: true, + modelURL: entityProperties.modelURL, + shapeType: entityProperties.shapeType, + originalTextures: entityProperties.originalTextures, + parentID: MyAvatar.sessionUUID, + parentJointIndex: MyAvatar.getJointIndex("Head") + }); + } + } + } else if (!bumperPressed && this.waitingForBumpReleased) { + this.waitingForBumpReleased = false; + } + }, + + this.clean = function() { + this.pickRay = null; + this.overlayLine.destroy(); + this.overlayLine = null; + pointer.visible = false; + } + }; + + function update(deltaTime) { + + if (tryingOnAvatar) { + // if trying the item on avatar, wait for a bumper being pressed to exit this mode + //the bumper is already pressed when we get here because we triggered the button pressing the bumper so we have to wait it's released + if(Controller.getValue(workingHand.bumper) && wantToStopTrying) { + Camera.cameraEntity = null; + Camera.mode = "first person"; + mirrorPanel.destroy(); + mirrorPanel = null; + Entities.deleteEntity(tempTryEntity); + tempTryEntity = null; + Entities.editEntity(inspectedEntityID, { visible: true }); + _this.createInspectUI(); + tryingOnAvatar = false; + wantToStopTrying = false; + } else if (!Controller.getValue(workingHand.bumper) && !wantToStopTrying) { + //no bumper is pressed + wantToStopTrying = true; + } + return; + } else if (inspecting) { + //update the rays from both hands + leftController.updateHand(); + rightController.updateHand(); + + //check the item status for consistency + var entityStatus = getEntityCustomData('statusKey', inspectedEntityID, null).status; + if (entityStatus == IN_HAND_STATUS) { + //the inspection is over + inspecting = false; + inspectedEntityID = null; + } + } else if (isUIWorking) { + //getting here when the inspect phase end, so we want to clean the UI + // Destroy rays + workingHand.clean(); + + // Destroy overlay + mainPanel.destroy(); + isUIWorking = false; + + var message = { + command: "Hide", + clip_url: "" + }; + + Messages.sendMessage(AGENT_REVIEW_CHANNEL, JSON.stringify(message)); + print("Hide sent to agent"); + + if (separator != null || cameraReview != null) { + Entities.editEntity(separator, { visible: true }); + Entities.editEntity(separator, { locked: true }); + Entities.editEntity(cameraReview, { visible: true }); + Entities.editEntity(cameraReview, { locked: true }); + } + } + + _this.positionRotationUpdate(); + }; + + function starConverter(value) { + var starURL = ZERO_STAR_URL; + + switch(value) { + case 0: + starURL = ZERO_STAR_URL; + break; + + case 1: + starURL = ONE_STAR_URL; + break; + + case 2: + starURL = TWO_STAR_URL; + break; + + case 3: + starURL = THREE_STAR_URL; + break; + + default: + starURL = ZERO_STAR_URL; + break; + } + + return starURL; + }; + + + // look for the database entity relative to the inspected item + function findItemDataBase(entityID, item) { + var dataBaseID = null; + var databaseEntityName = item + "DB"; + var entitiesInZone = Entities.findEntities(Entities.getEntityProperties(entityID).position, (Entities.getEntityProperties(entityID).dimensions.x)*100); + + for (var i = 0; i < entitiesInZone.length && dataBaseID == null; i++) { + if (Entities.getEntityProperties(entitiesInZone[i]).name == databaseEntityName) { + dataBaseID = entitiesInZone[i]; + print("Database found! " + entitiesInZone[i]); + return dataBaseID; + } + } + print("No database for this item."); + return null; + }; + + function findItemByName(searchingPointEntityID, itemName) { + print("Looking for item: " + itemName); + var entitiesInZone = Entities.findEntities(Entities.getEntityProperties(searchingPointEntityID).position, (Entities.getEntityProperties(searchingPointEntityID).dimensions.x)*100); + + for (var i = 0; i < entitiesInZone.length; i++) { + if (Entities.getEntityProperties(entitiesInZone[i]).name == itemName) { + print(itemName + " found! " + entitiesInZone[i]); + return entitiesInZone[i]; + } + } + print("Item " + itemName + " not found"); + return null; + }; + + InspectEntity.prototype = { + + preload: function(entityID) { + this.entityID = entityID; + print("PRELOAD INSPECT ENTITY"); + //get the owner ID from user data and compare to the mine + //the update will be connected just for the owner + var ownerObj = getEntityCustomData('ownerKey', this.entityID, null); + if (ownerObj.ownerID === MyAvatar.sessionUUID) { + rightController = new MyController(RIGHT_HAND); //rightController and leftController are two objects + leftController = new MyController(LEFT_HAND); + workingHand = rightController; + inspectingMyItem = true; + inspectRadius = (Entities.getEntityProperties(_this.entityID).dimensions.x) / 2 + COMFORT_ARM_LENGTH; + Script.update.connect(update); + } + }, + + //Put the item which calls this method in inspect mode if it belongs to the owner of the inspect zone + doSomething: function (entityID, dataArray) { + var data = JSON.parse(dataArray[0]); + var itemOwnerObj = getEntityCustomData('ownerKey', data.id, null); + + var inspectOwnerObj = getEntityCustomData('ownerKey', this.entityID, null); + + if (inspectOwnerObj == null) { + Entities.deleteEntity(data.id); + } + + if (itemOwnerObj.ownerID === inspectOwnerObj.ownerID) { + //setup the things for inspecting the item + inspecting = true; + inspectedEntityID = data.id; //store the ID of the inspected entity + setEntityCustomData('statusKey', data.id, { + status: IN_INSPECT_STATUS + }); + _this.createInspectUI(); + itemOriginalDimensions = Entities.getEntityProperties(inspectedEntityID).dimensions; + + // find separator and camera and hide + separator = findItemByName(inspectedEntityID, SEPARATOR); + Entities.editEntity(separator, { locked: false }); + Entities.editEntity(separator, { visible: false }); + print("Got here! Entity to hide: " + separator); + cameraReview = findItemByName(inspectedEntityID, CAMERA_REVIEW); + Entities.editEntity(cameraReview, { locked: false }); + Entities.editEntity(cameraReview, { visible: false }); + print("Got here! Entity to hide: " + cameraReview); + } else { + Entities.deleteEntity(data.id); + } + }, + + //make the inspection entity stay at the proper position with respect to the avatar and camera positions + positionRotationUpdate: function() { + var newRotation; + if (tryingOnAvatar) { + newRotation = Vec3.sum(Quat.safeEulerAngles(MyAvatar.orientation), {x:0, y: 180, z: 0}); //neccessary to set properly the camera in entity mode when trying on avatar + Entities.editEntity(_this.entityID, { rotation: Quat.fromVec3Degrees(newRotation) }); + } else { + var newPosition = Vec3.sum(Camera.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), inspectRadius)); + Entities.editEntity(_this.entityID, { position: newPosition }); + newRotation = MyAvatar.orientation; + Entities.editEntity(_this.entityID, { rotation: newRotation }); + } + + newPosition = Vec3.sum(newPosition, Vec3.multiply(Quat.getRight(newRotation), 0.34)); + + }, + + createInspectUI : function() { + + var infoObj = getEntityCustomData('infoKey', inspectedEntityID, null); + var itemDescriptionString = null; + var priceNumber = -1; + var availabilityNumber = -1; + var wearable = false; + + if(infoObj != null) { + //var modelURLsLoop = infoObj.modelURLs; ?? + var rootURLString = infoObj.rootURL; + for (var i = 0; i < infoObj.modelURLs.length; i++) { + modelURLsArray[i] = rootURLString + infoObj.modelURLs[i]; + previewURLsArray[i] = rootURLString + infoObj.previewURLs[i]; + } + + itemDescriptionString = infoObj.description; + priceNumber = infoObj.price; + availabilityNumber = infoObj.availability; + wearable = infoObj.wearable; + infoObj = null; + } + + //Looking for the item DB, this entity has in its userData the info of the customer reviews for the item + var DBID = findItemDataBase(_this.entityID, Entities.getEntityProperties(inspectedEntityID).name); + + if (DBID != null) { + infoObj = getEntityCustomData('infoKey', DBID, null); + var scoreAverage = null; + var reviewerName = null; + + if(infoObj != null) { + dbMatrix = infoObj.dbKey; + reviewsNumber = infoObj.dbKey.length; + //print("DB matrix is " + dbMatrix + " with element number: " + reviewsNumber); + var scoreSum = null; + + for (var i = 0; i < dbMatrix.length; i++) { + scoreSum += dbMatrix[i].score; + } + + if (dbMatrix.length) { + scoreAverage = Math.round(scoreSum / dbMatrix.length); + reviewerName = dbMatrix[reviewIndex].name; + + var message = { + command: "Show", + clip_url: dbMatrix[reviewIndex].clip_url + }; + + Messages.sendMessage(AGENT_REVIEW_CHANNEL, JSON.stringify(message)); + print("Show sent to agent"); + } else { + //some default value if the DB is empty + scoreAverage = 0; + reviewerName = NO_REVIEWS_AVAILABLE; + } + + } + + print ("Creating inspect UI"); + //set the main panel to follow the inspect entity + mainPanel = new OverlayPanel({ + anchorPositionBinding: { entity: _this.entityID }, + anchorRotationBinding: { entity: _this.entityID }, + isFacingAvatar: false + }); + + var offsetPositionY = 0.2; + var offsetPositionX = -0.4; + + //these buttons are the 3 previews of the item + for (var i = 0; i < previewURLsArray.length; i++) { + buttons[i] = new Image3DOverlay({ + url: previewURLsArray[i], + dimensions: { + x: 0.15, + y: 0.15 + }, + isFacingAvatar: false, + alpha: 0.8, + ignoreRayIntersection: false, + offsetPosition: { + x: offsetPositionX, + y: offsetPositionY - (i * offsetPositionY), + z: 0 + }, + emissive: true, + }); + + mainPanel.addChild(buttons[i]); + } + + //aggregateScore is the average between those given by the reviewers + var aggregateScore = new Image3DOverlay({ + url: starConverter(scoreAverage), + dimensions: { + x: 0.25, + y: 0.25 + }, + isFacingAvatar: false, + alpha: 1, + ignoreRayIntersection: true, + offsetPosition: { + x: 0, + y: 0.27, + z: 0 + }, + emissive: true, + }); + + mainPanel.addChild(aggregateScore); + + //if any review is available create buttons to manage them + if (dbMatrix.length) { + + playButton = new Image3DOverlay({ + url: "https://dl.dropboxusercontent.com/u/14127429/FBX/VRshop/Play.png", + dimensions: { + x: 0.08, + y: 0.08 + }, + isFacingAvatar: false, + alpha: 1, + ignoreRayIntersection: false, + offsetPosition: { + x: 0.42, + y: 0.27, + z: 0 + }, + emissive: true, + }); + + mainPanel.addChild(playButton); + + + reviewerScore = new Image3DOverlay({ + url: starConverter(dbMatrix[reviewIndex].score), + dimensions: { + x: 0.15, + y: 0.15 + }, + isFacingAvatar: false, + alpha: 1, + ignoreRayIntersection: true, + offsetPosition: { + x: 0.31, + y: 0.26, + z: 0 + }, + emissive: true, + }); + + mainPanel.addChild(reviewerScore); + + nextButton = new Image3DOverlay({ + url: "https://dl.dropboxusercontent.com/u/14127429/FBX/VRshop/Next.png", + dimensions: { + x: 0.2, + y: 0.2 + }, + isFacingAvatar: false, + alpha: 1, + ignoreRayIntersection: false, + offsetPosition: { + x: 0.36, + y: 0.18, + z: 0 + }, + emissive: true, + }); + + + mainPanel.addChild(nextButton); + + } + + textReviewerName = new Text3DOverlay({ + text: reviewerName, + isFacingAvatar: false, + alpha: 1.0, + ignoreRayIntersection: true, + offsetPosition: { + x: 0.23, + y: 0.31, + z: 0 + }, + dimensions: { x: 0, y: 0 }, + backgroundColor: { red: 255, green: 255, blue: 255 }, + color: { red: 0, green: 0, blue: 0 }, + topMargin: 0.00625, + leftMargin: 0.00625, + bottomMargin: 0.1, + rightMargin: 0.00625, + lineHeight: 0.02, + alpha: 1, + backgroundAlpha: 0.3 + }); + + mainPanel.addChild(textReviewerName); + + //if the item is wearable create a tryOnAvatarButton + if (wearable) { + tryOnAvatarButton = new Image3DOverlay({ + url: TRY_ON_ICON, + dimensions: { + x: 0.2, + y: 0.2 + }, + isFacingAvatar: false, + alpha: 1, + ignoreRayIntersection: false, + offsetPosition: { + x: 0.35, + y: -0.22, + z: 0 + }, + emissive: true, + }); + + mainPanel.addChild(tryOnAvatarButton); + } + + + var textQuantityString = new Text3DOverlay({ + text: "Quantity: ", + isFacingAvatar: false, + alpha: 1.0, + ignoreRayIntersection: true, + offsetPosition: { + x: 0.25, + y: -0.3, + z: 0 + }, + dimensions: { x: 0, y: 0 }, + backgroundColor: { red: 255, green: 255, blue: 255 }, + color: { red: 0, green: 0, blue: 0 }, + topMargin: 0.00625, + leftMargin: 0.00625, + bottomMargin: 0.1, + rightMargin: 0.00625, + lineHeight: 0.02, + alpha: 1, + backgroundAlpha: 0.3 + }); + + mainPanel.addChild(textQuantityString); + + var textQuantityNumber = new Text3DOverlay({ + text: availabilityNumber, + isFacingAvatar: false, + alpha: 1.0, + ignoreRayIntersection: true, + offsetPosition: { + x: 0.28, + y: -0.32, + z: 0 + }, + dimensions: { x: 0, y: 0 }, + backgroundColor: { red: 255, green: 255, blue: 255 }, + color: { red: 0, green: 0, blue: 0 }, + topMargin: 0.00625, + leftMargin: 0.00625, + bottomMargin: 0.1, + rightMargin: 0.00625, + lineHeight: 0.06, + alpha: 1, + backgroundAlpha: 0.3 + }); + + mainPanel.addChild(textQuantityNumber); + + if (itemDescriptionString != null) { + var textDescription = new Text3DOverlay({ + text: "Price: " + priceNumber + "\nAdditional information: \n" + itemDescriptionString, + isFacingAvatar: false, + alpha: 1.0, + ignoreRayIntersection: true, + offsetPosition: { + x: -0.2, + y: -0.3, + z: 0 + }, + dimensions: { x: 0, y: 0 }, + backgroundColor: { red: 255, green: 255, blue: 255 }, + color: { red: 0, green: 0, blue: 0 }, + topMargin: 0.00625, + leftMargin: 0.00625, + bottomMargin: 0.1, + rightMargin: 0.00625, + lineHeight: 0.02, + alpha: 1, + backgroundAlpha: 0.3 + }); + + mainPanel.addChild(textDescription); + } + + + + print ("GOT HERE: Descrition " + itemDescriptionString + " Availability " + availabilityNumber); + + isUIWorking = true; + } + }, + + //Manage the collisions and tell to the item if it is into the this inspection area + collisionWithEntity: function(myID, otherID, collisionInfo) { + + var itemObj = getEntityCustomData('itemKey', _this.entityID, null); + if (itemObj != null) { + if (itemObj.itemID == otherID) { //verify that the inspect area is colliding with the actual item which created it + + var penetrationValue = Vec3.length(collisionInfo.penetration); + if (penetrationValue > PENETRATION_THRESHOLD && collidedItemID === null) { + collidedItemID = otherID; + print("Start collision with: " + Entities.getEntityProperties(collidedItemID).name); + Entities.callEntityMethod(otherID, 'changeOverlayColor', null); + + } else if (penetrationValue < PENETRATION_THRESHOLD && collidedItemID !== null) { + + print("End collision with: " + Entities.getEntityProperties(collidedItemID).name); + collidedItemID = null; + Entities.callEntityMethod(otherID, 'changeOverlayColor', null); + } + } + } + }, + + changeModel: function(index) { + var entityProperties = Entities.getEntityProperties(inspectedEntityID); + if (entityProperties.modelURL != modelURLsArray[index]) { + Entities.editEntity(inspectedEntityID, { modelURL: modelURLsArray[index] }); + } + }, + + unload: function (entityID) { + if(inspectingMyItem){ + print("UNLOAD INSPECT ENTITY"); + Script.update.disconnect(update); + + // clean UI + Entities.deleteEntity(_this.entityID); + } + } + }; + return new InspectEntity(); +}) \ No newline at end of file From 41e6dd48e07b4a0cee04d23966be06c3b0fbc11a Mon Sep 17 00:00:00 2001 From: AlessandroSigna Date: Thu, 14 Jan 2016 18:27:17 -0800 Subject: [PATCH 3/4] Overlay manager exposing findRayIntersection --- examples/libraries/overlayManager.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/libraries/overlayManager.js b/examples/libraries/overlayManager.js index 0623721375..ce79214537 100644 --- a/examples/libraries/overlayManager.js +++ b/examples/libraries/overlayManager.js @@ -383,6 +383,9 @@ searchList[object._id] = object; }); return searchList; + }, + findRayIntersection: function(pickRay) { + return Overlays.findRayIntersection(pickRay); } }; From 737a00a2cb2eef2c2e8544f6d2365fc6e596ea4d Mon Sep 17 00:00:00 2001 From: AlessandroSigna Date: Mon, 18 Jan 2016 16:20:32 +0100 Subject: [PATCH 4/4] Typo fixed --- examples/vrShop/review/shopReviewEntityScript.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/vrShop/review/shopReviewEntityScript.js b/examples/vrShop/review/shopReviewEntityScript.js index 49f0a49dea..6f3aaa8186 100644 --- a/examples/vrShop/review/shopReviewEntityScript.js +++ b/examples/vrShop/review/shopReviewEntityScript.js @@ -241,7 +241,7 @@ var myName = MyAvatar.displayName ? MyAvatar.displayName : "Anonymous"; dbObj.dbKey[dbObj.dbKey.length] = {name: myName, score: scoreAssigned, clip_url: url}; setEntityCustomData('infoKey', dataBaseID, dbObj); - print("Feeded DB: " + url"); + print("Feeded DB: " + url); } };