From e4501e3a8f6c29a6c7584d1e4b5aa6878bfe89eb Mon Sep 17 00:00:00 2001 From: AlessandroSigna Date: Thu, 14 Jan 2016 18:23:39 -0800 Subject: [PATCH] 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