Item, Review, Shop and Vendor scripts.

This commit is contained in:
EdgarPironti 2016-01-14 18:20:40 -08:00
parent c5556b1c29
commit 40279c8fc5
6 changed files with 1980 additions and 0 deletions

View file

@ -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();
})

View file

@ -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);

View file

@ -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();
});

View file

@ -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);

View file

@ -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();
});

View file

@ -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: [
]
}
})
});
}