Merge branch 'master' into 21166

This commit is contained in:
Matti Lahtinen 2017-02-28 16:34:28 +02:00 committed by GitHub
commit e21785d5ec
83 changed files with 4695 additions and 362 deletions

View file

@ -384,6 +384,8 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
if (includeThisAvatar) { if (includeThisAvatar) {
numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122());
numAvatarDataBytes += avatarPacketList->write(bytes); numAvatarDataBytes += avatarPacketList->write(bytes);
if (detail != AvatarData::NoData) {
_stats.numOthersIncluded++; _stats.numOthersIncluded++;
// increment the number of avatars sent to this reciever // increment the number of avatars sent to this reciever
@ -395,7 +397,7 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
// remember the last time we sent details about this other node to the receiver // remember the last time we sent details about this other node to the receiver
nodeData->setLastBroadcastTime(otherNode->getUUID(), start); nodeData->setLastBroadcastTime(otherNode->getUUID(), start);
}
} }
avatarPacketList->endSegment(); avatarPacketList->endSegment();

View file

@ -324,16 +324,8 @@ void EntityScriptServer::nodeActivated(SharedNodePointer activatedNode) {
void EntityScriptServer::nodeKilled(SharedNodePointer killedNode) { void EntityScriptServer::nodeKilled(SharedNodePointer killedNode) {
switch (killedNode->getType()) { switch (killedNode->getType()) {
case NodeType::EntityServer: { case NodeType::EntityServer: {
if (!_shuttingDown) { clear();
if (_entitiesScriptEngine) {
_entitiesScriptEngine->unloadAllEntityScripts();
_entitiesScriptEngine->stop();
}
resetEntitiesScriptEngine();
_entityViewer.clear();
}
break; break;
} }
case NodeType::Agent: { case NodeType::Agent: {
@ -440,12 +432,12 @@ void EntityScriptServer::clear() {
_entitiesScriptEngine->stop(); _entitiesScriptEngine->stop();
} }
_entityViewer.clear();
// reset the engine // reset the engine
if (!_shuttingDown) { if (!_shuttingDown) {
resetEntitiesScriptEngine(); resetEntitiesScriptEngine();
} }
_entityViewer.clear();
} }
void EntityScriptServer::shutdownScriptEngine() { void EntityScriptServer::shutdownScriptEngine() {

View file

@ -996,6 +996,10 @@ function saveSettings() {
if (password && password.length > 0) { if (password && password.length > 0) {
formJSON["security"]["http_password"] = sha256_digest(password); formJSON["security"]["http_password"] = sha256_digest(password);
} }
var verify_password = formJSON["security"]["verify_http_password"];
if (verify_password && verify_password.length > 0) {
formJSON["security"]["verify_http_password"] = sha256_digest(verify_password);
}
} }
// verify that the password and confirmation match before saving // verify that the password and confirmation match before saving
@ -1010,7 +1014,6 @@ function saveSettings() {
bootbox.alert({"message": "Passwords must match!", "title":"Password Error"}); bootbox.alert({"message": "Passwords must match!", "title":"Password Error"});
canPost = false; canPost = false;
} else { } else {
formJSON["security"]["http_password"] = sha256_digest(password);
delete formJSON["security"]["verify_http_password"]; delete formJSON["security"]["verify_http_password"];
} }
} }

View file

@ -128,7 +128,41 @@
"id": "rightHandGrasp", "id": "rightHandGrasp",
"interpTarget": 3, "interpTarget": 3,
"interpDuration": 3, "interpDuration": 3,
"transitions": [] "transitions": [
{ "var": "isRightIndexPoint", "state": "rightIndexPoint" },
{ "var": "isRightThumbRaise", "state": "rightThumbRaise" },
{ "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" }
]
},
{
"id": "rightIndexPoint",
"interpTarget": 15,
"interpDuration": 3,
"transitions": [
{ "var": "isRightHandGrasp", "state": "rightHandGrasp" },
{ "var": "isRightThumbRaise", "state": "rightThumbRaise" },
{ "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" }
]
},
{
"id": "rightThumbRaise",
"interpTarget": 15,
"interpDuration": 3,
"transitions": [
{ "var": "isRightHandGrasp", "state": "rightHandGrasp" },
{ "var": "isRightIndexPoint", "state": "rightIndexPoint" },
{ "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" }
]
},
{
"id": "rightIndexPointAndThumbRaise",
"interpTarget": 15,
"interpDuration": 3,
"transitions": [
{ "var": "isRightHandGrasp", "state": "rightHandGrasp" },
{ "var": "isRightIndexPoint", "state": "rightIndexPoint" },
{ "var": "isRightThumbRaise", "state": "rightThumbRaise" }
]
} }
] ]
}, },
@ -166,6 +200,108 @@
"children": [] "children": []
} }
] ]
},
{
"id": "rightIndexPoint",
"type": "blendLinear",
"data": {
"alpha": 0.0,
"alphaVar": "rightHandGraspAlpha"
},
"children": [
{
"id": "rightIndexPointOpen",
"type": "clip",
"data": {
"url": "animations/touch_point_open_right.fbx",
"startFrame": 15.0,
"endFrame": 15.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "rightIndexPointClosed",
"type": "clip",
"data": {
"url": "animations/touch_point_closed_right.fbx",
"startFrame": 15.0,
"endFrame": 15.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
}
]
},
{
"id": "rightThumbRaise",
"type": "blendLinear",
"data": {
"alpha": 0.0,
"alphaVar": "rightHandGraspAlpha"
},
"children": [
{
"id": "rightThumbRaiseOpen",
"type": "clip",
"data": {
"url": "animations/touch_thumb_open_right.fbx",
"startFrame": 15.0,
"endFrame": 15.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "rightThumbRaiseClosed",
"type": "clip",
"data": {
"url": "animations/touch_thumb_closed_right.fbx",
"startFrame": 15.0,
"endFrame": 15.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
}
]
},
{
"id": "rightIndexPointAndThumbRaise",
"type": "blendLinear",
"data": {
"alpha": 0.0,
"alphaVar": "rightHandGraspAlpha"
},
"children": [
{
"id": "rightIndexPointAndThumbRaiseOpen",
"type": "clip",
"data": {
"url": "animations/touch_thumb_point_open_right.fbx",
"startFrame": 15.0,
"endFrame": 15.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "rightIndexPointAndThumbRaiseClosed",
"type": "clip",
"data": {
"url": "animations/touch_thumb_point_closed_right.fbx",
"startFrame": 15.0,
"endFrame": 15.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
}
]
} }
] ]
}, },
@ -175,7 +311,7 @@
"data": { "data": {
"alpha": 0.0, "alpha": 0.0,
"boneSet": "leftHand", "boneSet": "leftHand",
"alphaVar" : "leftHandOverlayAlpha" "alphaVar": "leftHandOverlayAlpha"
}, },
"children": [ "children": [
{ {
@ -188,7 +324,41 @@
"id": "leftHandGrasp", "id": "leftHandGrasp",
"interpTarget": 3, "interpTarget": 3,
"interpDuration": 3, "interpDuration": 3,
"transitions": [] "transitions": [
{ "var": "isLeftIndexPoint", "state": "leftIndexPoint" },
{ "var": "isLeftThumbRaise", "state": "leftThumbRaise" },
{ "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" }
]
},
{
"id": "leftIndexPoint",
"interpTarget": 15,
"interpDuration": 3,
"transitions": [
{ "var": "isLeftHandGrasp", "state": "leftHandGrasp" },
{ "var": "isLeftThumbRaise", "state": "leftThumbRaise" },
{ "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" }
]
},
{
"id": "leftThumbRaise",
"interpTarget": 15,
"interpDuration": 3,
"transitions": [
{ "var": "isLeftHandGrasp", "state": "leftHandGrasp" },
{ "var": "isLeftIndexPoint", "state": "leftIndexPoint" },
{ "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" }
]
},
{
"id": "leftIndexPointAndThumbRaise",
"interpTarget": 15,
"interpDuration": 3,
"transitions": [
{ "var": "isLeftHandGrasp", "state": "leftHandGrasp" },
{ "var": "isLeftIndexPoint", "state": "leftIndexPoint" },
{ "var": "isLeftThumbRaise", "state": "leftThumbRaise" }
]
} }
] ]
}, },
@ -226,6 +396,108 @@
"children": [] "children": []
} }
] ]
},
{
"id": "leftIndexPoint",
"type": "blendLinear",
"data": {
"alpha": 0.0,
"alphaVar": "leftHandGraspAlpha"
},
"children": [
{
"id": "leftIndexPointOpen",
"type": "clip",
"data": {
"url": "animations/touch_point_open_left.fbx",
"startFrame": 15.0,
"endFrame": 15.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "leftIndexPointClosed",
"type": "clip",
"data": {
"url": "animations/touch_point_closed_left.fbx",
"startFrame": 15.0,
"endFrame": 15.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
}
]
},
{
"id": "leftThumbRaise",
"type": "blendLinear",
"data": {
"alpha": 0.0,
"alphaVar": "leftHandGraspAlpha"
},
"children": [
{
"id": "leftThumbRaiseOpen",
"type": "clip",
"data": {
"url": "animations/touch_thumb_open_left.fbx",
"startFrame": 15.0,
"endFrame": 15.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "leftThumbRaiseClosed",
"type": "clip",
"data": {
"url": "animations/touch_thumb_closed_left.fbx",
"startFrame": 15.0,
"endFrame": 15.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
}
]
},
{
"id": "leftIndexPointAndThumbRaise",
"type": "blendLinear",
"data": {
"alpha": 0.0,
"alphaVar": "leftHandGraspAlpha"
},
"children": [
{
"id": "leftIndexPointAndThumbRaiseOpen",
"type": "clip",
"data": {
"url": "animations/touch_thumb_point_open_left.fbx",
"startFrame": 15.0,
"endFrame": 15.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "leftIndexPointAndThumbRaiseClosed",
"type": "clip",
"data": {
"url": "animations/touch_thumb_point_closed_left.fbx",
"startFrame": 15.0,
"endFrame": 15.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
}
]
} }
] ]
}, },

View file

@ -13,6 +13,7 @@
import QtQuick 2.5 import QtQuick 2.5
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import Qt.labs.settings 1.0
import "../styles-uit" import "../styles-uit"
import "../controls-uit" as HifiControls import "../controls-uit" as HifiControls
@ -29,7 +30,9 @@ Rectangle {
property int myCardHeight: 90 property int myCardHeight: 90
property int rowHeight: 70 property int rowHeight: 70
property int actionButtonWidth: 55 property int actionButtonWidth: 55
property int nameCardWidth: palContainer.width - actionButtonWidth*(iAmAdmin ? 4 : 2) - 4 - hifi.dimensions.scrollbarBackgroundWidth property int actionButtonAllowance: actionButtonWidth * 2
property int minNameCardWidth: palContainer.width - (actionButtonAllowance * 2) - 4 - hifi.dimensions.scrollbarBackgroundWidth
property int nameCardWidth: minNameCardWidth + (iAmAdmin ? 0 : actionButtonAllowance)
property var myData: ({displayName: "", userName: "", audioLevel: 0.0, admin: true}) // valid dummy until set property var myData: ({displayName: "", userName: "", audioLevel: 0.0, admin: true}) // valid dummy until set
property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring. property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring.
property var userModelData: [] // This simple list is essentially a mirror of the userModel listModel without all the extra complexities. property var userModelData: [] // This simple list is essentially a mirror of the userModel listModel without all the extra complexities.
@ -52,6 +55,16 @@ Rectangle {
letterboxMessage.visible = true letterboxMessage.visible = true
letterboxMessage.popupRadius = 0 letterboxMessage.popupRadius = 0
} }
Settings {
id: settings
category: "pal"
property bool filtered: false
property int nearDistance: 30
}
function refreshWithFilter() {
// We should just be able to set settings.filtered to filter.checked, but see #3249, so send to .js for saving.
pal.sendToScript({method: 'refresh', params: {filter: filter.checked && {distance: settings.nearDistance}}});
}
// This is the container for the PAL // This is the container for the PAL
Rectangle { Rectangle {
@ -88,11 +101,32 @@ Rectangle {
audioLevel: myData.audioLevel audioLevel: myData.audioLevel
isMyCard: true isMyCard: true
// Size // Size
width: nameCardWidth width: minNameCardWidth
height: parent.height height: parent.height
// Anchors // Anchors
anchors.left: parent.left anchors.left: parent.left
} }
Row {
HifiControls.CheckBox {
id: filter
checked: settings.filtered
text: "in view"
boxSize: reload.height * 0.70
onCheckedChanged: refreshWithFilter()
}
HifiControls.GlyphButton {
id: reload
glyph: hifi.glyphs.reload
width: reload.height
onClicked: refreshWithFilter()
}
spacing: 50
anchors {
right: parent.right
top: parent.top
topMargin: 10
}
}
} }
// Rectangles used to cover up rounded edges on bottom of MyInfo Rectangle // Rectangles used to cover up rounded edges on bottom of MyInfo Rectangle
Rectangle { Rectangle {
@ -306,45 +340,7 @@ Rectangle {
} }
} }
} }
// Refresh button
Rectangle {
// Size
width: hifi.dimensions.tableHeaderHeight-1
height: hifi.dimensions.tableHeaderHeight-1
// Anchors
anchors.left: table.left
anchors.leftMargin: 4
anchors.top: table.top
// Style
color: hifi.colors.tableBackgroundLight
// Actual refresh icon
HiFiGlyphs {
id: reloadButton
text: hifi.glyphs.reloadSmall
// Size
size: parent.width*1.5
// Anchors
anchors.fill: parent
// Style
horizontalAlignment: Text.AlignHCenter
color: hifi.colors.darkGray
}
MouseArea {
id: reloadButtonArea
// Anchors
anchors.fill: parent
hoverEnabled: true
// Everyone likes a responsive refresh button!
// So use onPressed instead of onClicked
onPressed: {
reloadButton.color = hifi.colors.lightGrayText
pal.sendToScript({method: 'refresh'})
}
onReleased: reloadButton.color = (containsMouse ? hifi.colors.baseGrayHighlight : hifi.colors.darkGray)
onEntered: reloadButton.color = hifi.colors.baseGrayHighlight
onExited: reloadButton.color = (pressed ? hifi.colors.lightGrayText: hifi.colors.darkGray)
}
}
// Separator between user and admin functions // Separator between user and admin functions
Rectangle { Rectangle {
// Size // Size
@ -501,7 +497,7 @@ Rectangle {
if (alreadyRefreshed === true) { if (alreadyRefreshed === true) {
letterbox('', '', 'The last editor of this object is either you or not among this list of users.'); letterbox('', '', 'The last editor of this object is either you or not among this list of users.');
} else { } else {
pal.sendToScript({method: 'refresh', params: message.params}); pal.sendToScript({method: 'refresh', params: {selected: message.params}});
} }
} else { } else {
// If we've already refreshed the PAL and found the avatar in the model // If we've already refreshed the PAL and found the avatar in the model

View file

@ -548,6 +548,7 @@ const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 100.0f;
const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f; const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f;
const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true; const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true;
const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false; const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false;
const bool DEFAULT_TABLET_VISIBLE_TO_OTHERS = false;
Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runServer, QString runServerPathOption) : Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runServer, QString runServerPathOption) :
QApplication(argc, argv), QApplication(argc, argv),
@ -570,6 +571,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
_desktopTabletScale("desktopTabletScale", DEFAULT_DESKTOP_TABLET_SCALE_PERCENT), _desktopTabletScale("desktopTabletScale", DEFAULT_DESKTOP_TABLET_SCALE_PERCENT),
_desktopTabletBecomesToolbarSetting("desktopTabletBecomesToolbar", DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR), _desktopTabletBecomesToolbarSetting("desktopTabletBecomesToolbar", DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR),
_hmdTabletBecomesToolbarSetting("hmdTabletBecomesToolbar", DEFAULT_HMD_TABLET_BECOMES_TOOLBAR), _hmdTabletBecomesToolbarSetting("hmdTabletBecomesToolbar", DEFAULT_HMD_TABLET_BECOMES_TOOLBAR),
_tabletVisibleToOthersSetting("tabletVisibleToOthers", DEFAULT_TABLET_VISIBLE_TO_OTHERS),
_constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true), _constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true),
_scaleMirror(1.0f), _scaleMirror(1.0f),
_rotateMirror(0.0f), _rotateMirror(0.0f),
@ -781,6 +783,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle()));
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle()));
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails()));
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this]() {
getOverlays().deleteOverlay(getTabletScreenID());
getOverlays().deleteOverlay(getTabletHomeButtonID());
getOverlays().deleteOverlay(getTabletFrameID());
});
connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused); connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused);
// We could clear ATP assets only when changing domains, but it's possible that the domain you are connected // We could clear ATP assets only when changing domains, but it's possible that the domain you are connected
@ -1216,6 +1223,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
if (entity && entity->wantsKeyboardFocus()) { if (entity && entity->wantsKeyboardFocus()) {
setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID); setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID);
setKeyboardFocusEntity(entityItemID); setKeyboardFocusEntity(entityItemID);
} else {
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
} }
}); });
@ -2348,6 +2357,11 @@ void Application::setHmdTabletBecomesToolbarSetting(bool value) {
updateSystemTabletMode(); updateSystemTabletMode();
} }
void Application::setTabletVisibleToOthersSetting(bool value) {
_tabletVisibleToOthersSetting.set(value);
updateSystemTabletMode();
}
void Application::setSettingConstrainToolbarPosition(bool setting) { void Application::setSettingConstrainToolbarPosition(bool setting) {
_constrainToolbarPosition.set(setting); _constrainToolbarPosition.set(setting);
DependencyManager::get<OffscreenUi>()->setConstrainToolbarToCenterX(setting); DependencyManager::get<OffscreenUi>()->setConstrainToolbarToCenterX(setting);
@ -3093,6 +3107,7 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
getOverlays().mouseMoveEvent(&mappedEvent); getOverlays().mouseMoveEvent(&mappedEvent);
getEntities()->mouseMoveEvent(&mappedEvent); getEntities()->mouseMoveEvent(&mappedEvent);
} }
_controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts _controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it // if one of our scripts have asked to capture this event, then stop processing it
@ -3125,7 +3140,6 @@ void Application::mousePressEvent(QMouseEvent* event) {
if (!_aboutToQuit) { if (!_aboutToQuit) {
getOverlays().mousePressEvent(&mappedEvent); getOverlays().mousePressEvent(&mappedEvent);
if (!_controllerScriptingInterface->areEntityClicksCaptured()) { if (!_controllerScriptingInterface->areEntityClicksCaptured()) {
getEntities()->mousePressEvent(&mappedEvent); getEntities()->mousePressEvent(&mappedEvent);
} }
@ -6902,5 +6916,10 @@ OverlayID Application::getTabletScreenID() const {
OverlayID Application::getTabletHomeButtonID() const { OverlayID Application::getTabletHomeButtonID() const {
auto HMD = DependencyManager::get<HMDScriptingInterface>(); auto HMD = DependencyManager::get<HMDScriptingInterface>();
return HMD->getCurrentHomeButtonUUID(); return HMD->getCurrentHomeButtonID();
}
QUuid Application::getTabletFrameID() const {
auto HMD = DependencyManager::get<HMDScriptingInterface>();
return HMD->getCurrentTabletFrameID();
} }

View file

@ -218,6 +218,8 @@ public:
void setDesktopTabletBecomesToolbarSetting(bool value); void setDesktopTabletBecomesToolbarSetting(bool value);
bool getHmdTabletBecomesToolbarSetting() { return _hmdTabletBecomesToolbarSetting.get(); } bool getHmdTabletBecomesToolbarSetting() { return _hmdTabletBecomesToolbarSetting.get(); }
void setHmdTabletBecomesToolbarSetting(bool value); void setHmdTabletBecomesToolbarSetting(bool value);
bool getTabletVisibleToOthersSetting() { return _tabletVisibleToOthersSetting.get(); }
void setTabletVisibleToOthersSetting(bool value);
float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); } float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); }
void setSettingConstrainToolbarPosition(bool setting); void setSettingConstrainToolbarPosition(bool setting);
@ -300,6 +302,7 @@ public:
OverlayID getTabletScreenID() const; OverlayID getTabletScreenID() const;
OverlayID getTabletHomeButtonID() const; OverlayID getTabletHomeButtonID() const;
QUuid getTabletFrameID() const; // may be an entity or an overlay
signals: signals:
void svoImportRequested(const QString& url); void svoImportRequested(const QString& url);
@ -561,6 +564,7 @@ private:
Setting::Handle<float> _desktopTabletScale; Setting::Handle<float> _desktopTabletScale;
Setting::Handle<bool> _desktopTabletBecomesToolbarSetting; Setting::Handle<bool> _desktopTabletBecomesToolbarSetting;
Setting::Handle<bool> _hmdTabletBecomesToolbarSetting; Setting::Handle<bool> _hmdTabletBecomesToolbarSetting;
Setting::Handle<bool> _tabletVisibleToOthersSetting;
Setting::Handle<bool> _constrainToolbarPosition; Setting::Handle<bool> _constrainToolbarPosition;
float _scaleMirror; float _scaleMirror;

View file

@ -192,6 +192,8 @@ QVariantMap Camera::getViewFrustum() {
result["orientation"].setValue(frustum.getOrientation()); result["orientation"].setValue(frustum.getOrientation());
result["projection"].setValue(frustum.getProjection()); result["projection"].setValue(frustum.getProjection());
result["centerRadius"].setValue(frustum.getCenterRadius()); result["centerRadius"].setValue(frustum.getCenterRadius());
result["fieldOfView"].setValue(frustum.getFieldOfView());
result["aspectRatio"].setValue(frustum.getAspectRatio());
return result; return result;
} }

View file

@ -933,6 +933,10 @@ glm::vec3 Avatar::getDefaultJointTranslation(int index) const {
} }
glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const { glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const {
if (index < 0) {
index += numeric_limits<unsigned short>::max() + 1; // 65536
}
switch(index) { switch(index) {
case SENSOR_TO_WORLD_MATRIX_INDEX: { case SENSOR_TO_WORLD_MATRIX_INDEX: {
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
@ -969,6 +973,10 @@ glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const {
} }
glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const { glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
if (index < 0) {
index += numeric_limits<unsigned short>::max() + 1; // 65536
}
switch(index) { switch(index) {
case SENSOR_TO_WORLD_MATRIX_INDEX: { case SENSOR_TO_WORLD_MATRIX_INDEX: {
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();

View file

@ -85,7 +85,7 @@ AvatarManager::AvatarManager(QObject* parent) :
// immediately remove that avatar instead of waiting for the absence of packets from avatar mixer // immediately remove that avatar instead of waiting for the absence of packets from avatar mixer
connect(nodeList.data(), &NodeList::ignoredNode, this, [=](const QUuid& nodeID, bool enabled) { connect(nodeList.data(), &NodeList::ignoredNode, this, [=](const QUuid& nodeID, bool enabled) {
if (enabled) { if (enabled) {
removeAvatar(nodeID); removeAvatar(nodeID, KillAvatarReason::AvatarIgnored);
} }
}); });
} }

View file

@ -823,7 +823,7 @@ void MyAvatar::saveData() {
auto hmdInterface = DependencyManager::get<HMDScriptingInterface>(); auto hmdInterface = DependencyManager::get<HMDScriptingInterface>();
_avatarEntitiesLock.withReadLock([&] { _avatarEntitiesLock.withReadLock([&] {
for (auto entityID : _avatarEntityData.keys()) { for (auto entityID : _avatarEntityData.keys()) {
if (hmdInterface->getCurrentTabletUIID() == entityID) { if (hmdInterface->getCurrentTabletFrameID() == entityID) {
// don't persist the tablet between domains / sessions // don't persist the tablet between domains / sessions
continue; continue;
} }
@ -2410,6 +2410,10 @@ glm::mat4 MyAvatar::computeCameraRelativeHandControllerMatrix(const glm::mat4& c
} }
glm::quat MyAvatar::getAbsoluteJointRotationInObjectFrame(int index) const { glm::quat MyAvatar::getAbsoluteJointRotationInObjectFrame(int index) const {
if (index < 0) {
index += numeric_limits<unsigned short>::max() + 1; // 65536
}
switch (index) { switch (index) {
case CONTROLLER_LEFTHAND_INDEX: { case CONTROLLER_LEFTHAND_INDEX: {
return getLeftHandControllerPoseInAvatarFrame().getRotation(); return getLeftHandControllerPoseInAvatarFrame().getRotation();
@ -2443,6 +2447,10 @@ glm::quat MyAvatar::getAbsoluteJointRotationInObjectFrame(int index) const {
} }
glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const { glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
if (index < 0) {
index += numeric_limits<unsigned short>::max() + 1; // 65536
}
switch (index) { switch (index) {
case CONTROLLER_LEFTHAND_INDEX: { case CONTROLLER_LEFTHAND_INDEX: {
return getLeftHandControllerPoseInAvatarFrame().getTranslation(); return getLeftHandControllerPoseInAvatarFrame().getTranslation();

View file

@ -29,8 +29,8 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen
Q_PROPERTY(glm::quat orientation READ getOrientation) Q_PROPERTY(glm::quat orientation READ getOrientation)
Q_PROPERTY(bool mounted READ isMounted) Q_PROPERTY(bool mounted READ isMounted)
Q_PROPERTY(bool showTablet READ getShouldShowTablet) Q_PROPERTY(bool showTablet READ getShouldShowTablet)
Q_PROPERTY(QUuid tabletID READ getCurrentTabletUIID WRITE setCurrentTabletUIID) Q_PROPERTY(QUuid tabletID READ getCurrentTabletFrameID WRITE setCurrentTabletFrameID)
Q_PROPERTY(QUuid homeButtonID READ getCurrentHomeButtonUUID WRITE setCurrentHomeButtonUUID) Q_PROPERTY(QUuid homeButtonID READ getCurrentHomeButtonID WRITE setCurrentHomeButtonID)
Q_PROPERTY(QUuid tabletScreenID READ getCurrentTabletScreenID WRITE setCurrentTabletScreenID) Q_PROPERTY(QUuid tabletScreenID READ getCurrentTabletScreenID WRITE setCurrentTabletScreenID)
public: public:
@ -90,11 +90,11 @@ public:
void setShouldShowTablet(bool value) { _showTablet = value; } void setShouldShowTablet(bool value) { _showTablet = value; }
bool getShouldShowTablet() const { return _showTablet; } bool getShouldShowTablet() const { return _showTablet; }
void setCurrentTabletUIID(QUuid tabletID) { _tabletUIID = tabletID; } void setCurrentTabletFrameID(QUuid tabletID) { _tabletUIID = tabletID; }
QUuid getCurrentTabletUIID() const { return _tabletUIID; } QUuid getCurrentTabletFrameID() const { return _tabletUIID; }
void setCurrentHomeButtonUUID(QUuid homeButtonID) { _homeButtonID = homeButtonID; } void setCurrentHomeButtonID(QUuid homeButtonID) { _homeButtonID = homeButtonID; }
QUuid getCurrentHomeButtonUUID() const { return _homeButtonID; } QUuid getCurrentHomeButtonID() const { return _homeButtonID; }
void setCurrentTabletScreenID(QUuid tabletID) { _tabletScreenID = tabletID; } void setCurrentTabletScreenID(QUuid tabletID) { _tabletScreenID = tabletID; }
QUuid getCurrentTabletScreenID() const { return _tabletScreenID; } QUuid getCurrentTabletScreenID() const { return _tabletScreenID; }

View file

@ -102,7 +102,11 @@ void setupPreferences() {
auto setter = [](bool value) { qApp->setHmdTabletBecomesToolbarSetting(value); }; auto setter = [](bool value) { qApp->setHmdTabletBecomesToolbarSetting(value); };
preferences->addPreference(new CheckPreference(UI_CATEGORY, "HMD Tablet Becomes Toolbar", getter, setter)); preferences->addPreference(new CheckPreference(UI_CATEGORY, "HMD Tablet Becomes Toolbar", getter, setter));
} }
{
auto getter = []()->bool { return qApp->getTabletVisibleToOthersSetting(); };
auto setter = [](bool value) { qApp->setTabletVisibleToOthersSetting(value); };
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Tablet Is Visible To Others", getter, setter));
}
// Snapshots // Snapshots
static const QString SNAPSHOTS { "Snapshots" }; static const QString SNAPSHOTS { "Snapshots" };
{ {

View file

@ -39,7 +39,8 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) :
_isDashedLine(base3DOverlay->_isDashedLine), _isDashedLine(base3DOverlay->_isDashedLine),
_ignoreRayIntersection(base3DOverlay->_ignoreRayIntersection), _ignoreRayIntersection(base3DOverlay->_ignoreRayIntersection),
_drawInFront(base3DOverlay->_drawInFront), _drawInFront(base3DOverlay->_drawInFront),
_isAA(base3DOverlay->_isAA) _isAA(base3DOverlay->_isAA),
_isGrabbable(base3DOverlay->_isGrabbable)
{ {
setTransform(base3DOverlay->getTransform()); setTransform(base3DOverlay->getTransform());
} }
@ -59,16 +60,20 @@ QVariantMap convertOverlayLocationFromScriptSemantics(const QVariantMap& propert
} else if (result["position"].isValid()) { } else if (result["position"].isValid()) {
glm::vec3 localPosition = SpatiallyNestable::worldToLocal(vec3FromVariant(result["position"]), glm::vec3 localPosition = SpatiallyNestable::worldToLocal(vec3FromVariant(result["position"]),
parentID, parentJointIndex, success); parentID, parentJointIndex, success);
if (success) {
result["position"] = vec3toVariant(localPosition); result["position"] = vec3toVariant(localPosition);
} }
}
if (result["localOrientation"].isValid()) { if (result["localOrientation"].isValid()) {
result["orientation"] = result["localOrientation"]; result["orientation"] = result["localOrientation"];
} else if (result["orientation"].isValid()) { } else if (result["orientation"].isValid()) {
glm::quat localOrientation = SpatiallyNestable::worldToLocal(quatFromVariant(result["orientation"]), glm::quat localOrientation = SpatiallyNestable::worldToLocal(quatFromVariant(result["orientation"]),
parentID, parentJointIndex, success); parentID, parentJointIndex, success);
if (success) {
result["orientation"] = quatToVariant(localOrientation); result["orientation"] = quatToVariant(localOrientation);
} }
}
return result; return result;
} }
@ -125,6 +130,11 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
needRenderItemUpdate = true; needRenderItemUpdate = true;
} }
auto isGrabbable = properties["grabbable"];
if (isGrabbable.isValid()) {
setIsGrabbable(isGrabbable.toBool());
}
if (properties["position"].isValid()) { if (properties["position"].isValid()) {
setLocalPosition(vec3FromVariant(properties["position"])); setLocalPosition(vec3FromVariant(properties["position"]));
needRenderItemUpdate = true; needRenderItemUpdate = true;
@ -227,6 +237,9 @@ QVariant Base3DOverlay::getProperty(const QString& property) {
if (property == "drawInFront") { if (property == "drawInFront") {
return _drawInFront; return _drawInFront;
} }
if (property == "grabbable") {
return _isGrabbable;
}
if (property == "parentID") { if (property == "parentID") {
return getParentID(); return getParentID();
} }
@ -246,6 +259,8 @@ bool Base3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3
} }
void Base3DOverlay::locationChanged(bool tellPhysics) { void Base3DOverlay::locationChanged(bool tellPhysics) {
SpatiallyNestable::locationChanged(tellPhysics);
auto itemID = getRenderItemID(); auto itemID = getRenderItemID();
if (render::Item::isValidID(itemID)) { if (render::Item::isValidID(itemID)) {
render::ScenePointer scene = qApp->getMain3DScene(); render::ScenePointer scene = qApp->getMain3DScene();
@ -253,8 +268,6 @@ void Base3DOverlay::locationChanged(bool tellPhysics) {
pendingChanges.updateItem(itemID); pendingChanges.updateItem(itemID);
scene->enqueuePendingChanges(pendingChanges); scene->enqueuePendingChanges(pendingChanges);
} }
// Overlays can't currently have children.
// SpatiallyNestable::locationChanged(tellPhysics); // tell all the children, also
} }
void Base3DOverlay::parentDeleted() { void Base3DOverlay::parentDeleted() {

View file

@ -38,6 +38,7 @@ public:
bool getIsSolidLine() const { return !_isDashedLine; } bool getIsSolidLine() const { return !_isDashedLine; }
bool getIgnoreRayIntersection() const { return _ignoreRayIntersection; } bool getIgnoreRayIntersection() const { return _ignoreRayIntersection; }
bool getDrawInFront() const { return _drawInFront; } bool getDrawInFront() const { return _drawInFront; }
bool getIsGrabbable() const { return _isGrabbable; }
virtual bool isAA() const { return _isAA; } virtual bool isAA() const { return _isAA; }
@ -47,6 +48,7 @@ public:
void setIgnoreRayIntersection(bool value) { _ignoreRayIntersection = value; } void setIgnoreRayIntersection(bool value) { _ignoreRayIntersection = value; }
void setDrawInFront(bool value) { _drawInFront = value; } void setDrawInFront(bool value) { _drawInFront = value; }
void setIsAA(bool value) { _isAA = value; } void setIsAA(bool value) { _isAA = value; }
void setIsGrabbable(bool value) { _isGrabbable = value; }
virtual AABox getBounds() const override = 0; virtual AABox getBounds() const override = 0;
@ -71,6 +73,7 @@ protected:
bool _ignoreRayIntersection; bool _ignoreRayIntersection;
bool _drawInFront; bool _drawInFront;
bool _isAA; bool _isAA;
bool _isGrabbable { false };
}; };
#endif // hifi_Base3DOverlay_h #endif // hifi_Base3DOverlay_h

View file

@ -341,28 +341,18 @@ OverlayID Overlays::getOverlayAtPoint(const glm::vec2& point) {
return UNKNOWN_OVERLAY_ID; return UNKNOWN_OVERLAY_ID;
} }
QMapIterator<OverlayID, Overlay::Pointer> i(_overlaysHUD); QMapIterator<OverlayID, Overlay::Pointer> i(_overlaysHUD);
i.toBack();
const float LARGE_NEGATIVE_FLOAT = -9999999; const float LARGE_NEGATIVE_FLOAT = -9999999;
glm::vec3 origin(pointCopy.x, pointCopy.y, LARGE_NEGATIVE_FLOAT); glm::vec3 origin(pointCopy.x, pointCopy.y, LARGE_NEGATIVE_FLOAT);
glm::vec3 direction(0, 0, 1); glm::vec3 direction(0, 0, 1);
BoxFace thisFace;
glm::vec3 thisSurfaceNormal; glm::vec3 thisSurfaceNormal;
float distance;
unsigned int bestStackOrder = 0; unsigned int bestStackOrder = 0;
OverlayID bestOverlayID = UNKNOWN_OVERLAY_ID; OverlayID bestOverlayID = UNKNOWN_OVERLAY_ID;
while (i.hasPrevious()) { while (i.hasNext()) {
i.previous(); i.next();
OverlayID thisID = i.key(); OverlayID thisID = i.key();
if (i.value()->is3D()) { if (!i.value()->is3D()) {
auto thisOverlay = std::dynamic_pointer_cast<Base3DOverlay>(i.value());
if (thisOverlay && !thisOverlay->getIgnoreRayIntersection()) {
if (thisOverlay->findRayIntersection(origin, direction, distance, thisFace, thisSurfaceNormal)) {
return thisID;
}
}
} else {
auto thisOverlay = std::dynamic_pointer_cast<Overlay2D>(i.value()); auto thisOverlay = std::dynamic_pointer_cast<Overlay2D>(i.value());
if (thisOverlay && thisOverlay->getVisible() && thisOverlay->isLoaded() && if (thisOverlay && thisOverlay->getVisible() && thisOverlay->isLoaded() &&
thisOverlay->getBoundingRect().contains(pointCopy.x, pointCopy.y, false)) { thisOverlay->getBoundingRect().contains(pointCopy.x, pointCopy.y, false)) {
@ -406,16 +396,25 @@ RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray,
const QScriptValue& overlayIDsToInclude, const QScriptValue& overlayIDsToInclude,
const QScriptValue& overlayIDsToDiscard, const QScriptValue& overlayIDsToDiscard,
bool visibleOnly, bool collidableOnly) { bool visibleOnly, bool collidableOnly) {
float bestDistance = std::numeric_limits<float>::max();
bool bestIsFront = false;
const QVector<OverlayID> overlaysToInclude = qVectorOverlayIDFromScriptValue(overlayIDsToInclude); const QVector<OverlayID> overlaysToInclude = qVectorOverlayIDFromScriptValue(overlayIDsToInclude);
const QVector<OverlayID> overlaysToDiscard = qVectorOverlayIDFromScriptValue(overlayIDsToDiscard); const QVector<OverlayID> overlaysToDiscard = qVectorOverlayIDFromScriptValue(overlayIDsToDiscard);
return findRayIntersectionInternal(ray, precisionPicking,
overlaysToInclude, overlaysToDiscard, visibleOnly, collidableOnly);
}
RayToOverlayIntersectionResult Overlays::findRayIntersectionInternal(const PickRay& ray, bool precisionPicking,
const QVector<OverlayID>& overlaysToInclude,
const QVector<OverlayID>& overlaysToDiscard,
bool visibleOnly, bool collidableOnly) {
float bestDistance = std::numeric_limits<float>::max();
bool bestIsFront = false;
RayToOverlayIntersectionResult result; RayToOverlayIntersectionResult result;
QMapIterator<OverlayID, Overlay::Pointer> i(_overlaysWorld); QMapIterator<OverlayID, Overlay::Pointer> i(_overlaysWorld);
i.toBack(); while (i.hasNext()) {
while (i.hasPrevious()) { i.next();
i.previous();
OverlayID thisID = i.key(); OverlayID thisID = i.key();
auto thisOverlay = std::dynamic_pointer_cast<Base3DOverlay>(i.value()); auto thisOverlay = std::dynamic_pointer_cast<Base3DOverlay>(i.value());
@ -701,7 +700,8 @@ static PointerEvent::Button toPointerButton(const QMouseEvent& event) {
} }
PointerEvent Overlays::calculatePointerEvent(Overlay::Pointer overlay, PickRay ray, PointerEvent Overlays::calculatePointerEvent(Overlay::Pointer overlay, PickRay ray,
RayToOverlayIntersectionResult rayPickResult, QMouseEvent* event, PointerEvent::EventType eventType) { RayToOverlayIntersectionResult rayPickResult, QMouseEvent* event,
PointerEvent::EventType eventType) {
auto thisOverlay = std::dynamic_pointer_cast<Web3DOverlay>(overlay); auto thisOverlay = std::dynamic_pointer_cast<Web3DOverlay>(overlay);
@ -719,11 +719,41 @@ PointerEvent Overlays::calculatePointerEvent(Overlay::Pointer overlay, PickRay r
return pointerEvent; return pointerEvent;
} }
void Overlays::mousePressEvent(QMouseEvent* event) {
RayToOverlayIntersectionResult Overlays::findRayIntersectionForMouseEvent(PickRay ray) {
QVector<OverlayID> overlaysToInclude;
QVector<OverlayID> overlaysToDiscard;
RayToOverlayIntersectionResult rayPickResult;
// first priority is tablet screen
overlaysToInclude << qApp->getTabletScreenID();
rayPickResult = findRayIntersectionInternal(ray, true, overlaysToInclude, overlaysToDiscard);
if (rayPickResult.intersects) {
return rayPickResult;
}
// then tablet home button
overlaysToInclude.clear();
overlaysToInclude << qApp->getTabletHomeButtonID();
rayPickResult = findRayIntersectionInternal(ray, true, overlaysToInclude, overlaysToDiscard);
if (rayPickResult.intersects) {
return rayPickResult;
}
// then tablet frame
overlaysToInclude.clear();
overlaysToInclude << OverlayID(qApp->getTabletFrameID());
rayPickResult = findRayIntersectionInternal(ray, true, overlaysToInclude, overlaysToDiscard);
if (rayPickResult.intersects) {
return rayPickResult;
}
// then whatever
return findRayIntersection(ray);
}
bool Overlays::mousePressEvent(QMouseEvent* event) {
PerformanceTimer perfTimer("Overlays::mousePressEvent"); PerformanceTimer perfTimer("Overlays::mousePressEvent");
PickRay ray = qApp->computePickRay(event->x(), event->y()); PickRay ray = qApp->computePickRay(event->x(), event->y());
RayToOverlayIntersectionResult rayPickResult = findRayIntersection(ray); RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray);
if (rayPickResult.intersects) { if (rayPickResult.intersects) {
_currentClickingOnOverlayID = rayPickResult.overlayID; _currentClickingOnOverlayID = rayPickResult.overlayID;
@ -732,19 +762,18 @@ void Overlays::mousePressEvent(QMouseEvent* event) {
if (thisOverlay) { if (thisOverlay) {
auto pointerEvent = calculatePointerEvent(thisOverlay, ray, rayPickResult, event, PointerEvent::Press); auto pointerEvent = calculatePointerEvent(thisOverlay, ray, rayPickResult, event, PointerEvent::Press);
emit mousePressOnOverlay(_currentClickingOnOverlayID, pointerEvent); emit mousePressOnOverlay(_currentClickingOnOverlayID, pointerEvent);
} else { return true;
emit mousePressOffOverlay();
} }
} else {
emit mousePressOffOverlay();
} }
emit mousePressOffOverlay();
return false;
} }
void Overlays::mouseReleaseEvent(QMouseEvent* event) { bool Overlays::mouseReleaseEvent(QMouseEvent* event) {
PerformanceTimer perfTimer("Overlays::mouseReleaseEvent"); PerformanceTimer perfTimer("Overlays::mouseReleaseEvent");
PickRay ray = qApp->computePickRay(event->x(), event->y()); PickRay ray = qApp->computePickRay(event->x(), event->y());
RayToOverlayIntersectionResult rayPickResult = findRayIntersection(ray); RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray);
if (rayPickResult.intersects) { if (rayPickResult.intersects) {
// Only Web overlays can have focus. // Only Web overlays can have focus.
@ -756,13 +785,14 @@ void Overlays::mouseReleaseEvent(QMouseEvent* event) {
} }
_currentClickingOnOverlayID = UNKNOWN_OVERLAY_ID; _currentClickingOnOverlayID = UNKNOWN_OVERLAY_ID;
return false;
} }
void Overlays::mouseMoveEvent(QMouseEvent* event) { bool Overlays::mouseMoveEvent(QMouseEvent* event) {
PerformanceTimer perfTimer("Overlays::mouseMoveEvent"); PerformanceTimer perfTimer("Overlays::mouseMoveEvent");
PickRay ray = qApp->computePickRay(event->x(), event->y()); PickRay ray = qApp->computePickRay(event->x(), event->y());
RayToOverlayIntersectionResult rayPickResult = findRayIntersection(ray); RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray);
if (rayPickResult.intersects) { if (rayPickResult.intersects) {
// Only Web overlays can have focus. // Only Web overlays can have focus.
@ -802,4 +832,34 @@ void Overlays::mouseMoveEvent(QMouseEvent* event) {
_currentHoverOverOverlayID = UNKNOWN_OVERLAY_ID; _currentHoverOverOverlayID = UNKNOWN_OVERLAY_ID;
} }
} }
return false;
}
QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) const {
QVector<QUuid> result;
QMapIterator<OverlayID, Overlay::Pointer> i(_overlaysWorld);
int checked = 0;
while (i.hasNext()) {
checked++;
i.next();
OverlayID thisID = i.key();
auto overlay = std::dynamic_pointer_cast<Volume3DOverlay>(i.value());
if (overlay && overlay->getVisible() && !overlay->getIgnoreRayIntersection() && overlay->isLoaded()) {
// get AABox in frame of overlay
glm::vec3 dimensions = overlay->getDimensions();
glm::vec3 low = dimensions * -0.5f;
AABox overlayFrameBox(low, dimensions);
Transform overlayToWorldMatrix = overlay->getTransform();
glm::mat4 worldToOverlayMatrix = glm::inverse(overlayToWorldMatrix.getMatrix());
glm::vec3 overlayFrameSearchPosition = glm::vec3(worldToOverlayMatrix * glm::vec4(center, 1.0f));
glm::vec3 penetration;
if (overlayFrameBox.findSpherePenetration(overlayFrameSearchPosition, radius, penetration)) {
result.append(thisID);
}
}
}
return result;
} }

View file

@ -100,9 +100,9 @@ public:
OverlayID addOverlay(Overlay* overlay) { return addOverlay(Overlay::Pointer(overlay)); } OverlayID addOverlay(Overlay* overlay) { return addOverlay(Overlay::Pointer(overlay)); }
OverlayID addOverlay(Overlay::Pointer overlay); OverlayID addOverlay(Overlay::Pointer overlay);
void mousePressEvent(QMouseEvent* event); bool mousePressEvent(QMouseEvent* event);
void mouseReleaseEvent(QMouseEvent* event); bool mouseReleaseEvent(QMouseEvent* event);
void mouseMoveEvent(QMouseEvent* event); bool mouseMoveEvent(QMouseEvent* event);
void cleanupAllOverlays(); void cleanupAllOverlays();
@ -206,6 +206,16 @@ public slots:
bool visibleOnly = false, bool visibleOnly = false,
bool collidableOnly = false); bool collidableOnly = false);
/**jsdoc
* Return a list of 3d overlays with bounding boxes that touch the given sphere
*
* @function Overlays.findOverlays
* @param {Vec3} center the point to search from.
* @param {float} radius search radius
* @return {List of Overlays.OverlayID} list of overlays withing the radius
*/
QVector<QUuid> findOverlays(const glm::vec3& center, float radius) const;
/**jsdoc /**jsdoc
* Check whether an overlay's assets have been loaded. For example, if the * Check whether an overlay's assets have been loaded. For example, if the
* overlay is an "image" overlay, this will indicate whether the its image * overlay is an "image" overlay, this will indicate whether the its image
@ -317,6 +327,12 @@ private:
OverlayID _currentClickingOnOverlayID { UNKNOWN_OVERLAY_ID }; OverlayID _currentClickingOnOverlayID { UNKNOWN_OVERLAY_ID };
OverlayID _currentHoverOverOverlayID { UNKNOWN_OVERLAY_ID }; OverlayID _currentHoverOverOverlayID { UNKNOWN_OVERLAY_ID };
RayToOverlayIntersectionResult findRayIntersectionInternal(const PickRay& ray, bool precisionPicking,
const QVector<OverlayID>& overlaysToInclude,
const QVector<OverlayID>& overlaysToDiscard,
bool visibleOnly = false, bool collidableOnly = false);
RayToOverlayIntersectionResult findRayIntersectionForMouseEvent(PickRay ray);
}; };
#endif // hifi_Overlays_h #endif // hifi_Overlays_h

View file

@ -53,7 +53,7 @@ namespace render {
return overlay->getBounds(); return overlay->getBounds();
} }
template <> int payloadGetLayer(const Overlay::Pointer& overlay) { template <> int payloadGetLayer(const Overlay::Pointer& overlay) {
// MAgic number while we are defining the layering mechanism: // Magic number while we are defining the layering mechanism:
const int LAYER_NO_AA = 3; const int LAYER_NO_AA = 3;
const int LAYER_2D = 2; const int LAYER_2D = 2;
const int LAYER_3D_FRONT = 1; const int LAYER_3D_FRONT = 1;

View file

@ -182,7 +182,7 @@ void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason remo
void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) { void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
qCDebug(avatars) << "Removed avatar with UUID" << uuidStringWithoutCurlyBraces(removedAvatar->getSessionUUID()) qCDebug(avatars) << "Removed avatar with UUID" << uuidStringWithoutCurlyBraces(removedAvatar->getSessionUUID())
<< "from AvatarHashMap"; << "from AvatarHashMap" << removalReason;
emit avatarRemovedEvent(removedAvatar->getSessionUUID()); emit avatarRemovedEvent(removedAvatar->getSessionUUID());
} }

View file

@ -1419,8 +1419,7 @@ QVector<QUuid> EntityScriptingInterface::getChildrenIDsOfJoint(const QUuid& pare
return; return;
} }
parent->forEachChild([&](SpatiallyNestablePointer child) { parent->forEachChild([&](SpatiallyNestablePointer child) {
if (child->getParentJointIndex() == jointIndex && if (child->getParentJointIndex() == jointIndex) {
child->getNestableType() != NestableType::Overlay) {
result.push_back(child->getID()); result.push_back(child->getID());
} }
}); });

View file

@ -44,6 +44,8 @@ public:
// Mutable, but must retain structure of vector // Mutable, but must retain structure of vector
using NetworkMaterials = std::vector<std::shared_ptr<NetworkMaterial>>; using NetworkMaterials = std::vector<std::shared_ptr<NetworkMaterial>>;
bool isGeometryLoaded() const { return (bool)_fbxGeometry; }
const FBXGeometry& getFBXGeometry() const { return *_fbxGeometry; } const FBXGeometry& getFBXGeometry() const { return *_fbxGeometry; }
const GeometryMeshes& getMeshes() const { return *_meshes; } const GeometryMeshes& getMeshes() const { return *_meshes; }
const std::shared_ptr<const NetworkMaterial> getShapeMaterial(int shapeID) const; const std::shared_ptr<const NetworkMaterial> getShapeMaterial(int shapeID) const;

View file

@ -336,8 +336,7 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm
const double pStrength = 2.0; const double pStrength = 2.0;
int width = image.width(); int width = image.width();
int height = image.height(); int height = image.height();
// THe end result image for normal map is RGBA32 even though the A is not used QImage result(width, height, QImage::Format_RGB888);
QImage result(width, height, QImage::Format_RGBA8888);
for (int i = 0; i < width; i++) { for (int i = 0; i < width; i++) {
const int iNextClamped = clampPixelCoordinate(i + 1, width - 1); const int iNextClamped = clampPixelCoordinate(i + 1, width - 1);
@ -377,21 +376,19 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm
glm::normalize(v); glm::normalize(v);
// convert to rgb from the value obtained computing the filter // convert to rgb from the value obtained computing the filter
QRgb qRgbValue = qRgb(mapComponent(v.x), mapComponent(v.y), mapComponent(v.z)); QRgb qRgbValue = qRgba(mapComponent(v.x), mapComponent(v.y), mapComponent(v.z), 1.0);
result.setPixel(i, j, qRgbValue); result.setPixel(i, j, qRgbValue);
} }
} }
gpu::Texture* theTexture = nullptr; gpu::Texture* theTexture = nullptr;
if ((image.width() > 0) && (image.height() > 0)) { if ((image.width() > 0) && (image.height() > 0)) {
gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB);
gpu::Element formatGPU = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA); gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB);
gpu::Element formatMip = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA);
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture->setSource(srcImageName); theTexture->setSource(srcImageName);
theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits());
generateMips(theTexture, image, formatMip, true);
} }
return theTexture; return theTexture;

View file

@ -16,8 +16,8 @@ void UserActivityLoggerScriptingInterface::enabledEdit() {
logAction("enabled_edit"); logAction("enabled_edit");
} }
void UserActivityLoggerScriptingInterface::openedTablet() { void UserActivityLoggerScriptingInterface::openedTablet(bool visibleToOthers) {
logAction("opened_tablet"); logAction("opened_tablet", { { "visible_to_others", visibleToOthers } });
} }
void UserActivityLoggerScriptingInterface::closedTablet() { void UserActivityLoggerScriptingInterface::closedTablet() {

View file

@ -21,7 +21,7 @@ class UserActivityLoggerScriptingInterface : public QObject, public Dependency {
Q_OBJECT Q_OBJECT
public: public:
Q_INVOKABLE void enabledEdit(); Q_INVOKABLE void enabledEdit();
Q_INVOKABLE void openedTablet(); Q_INVOKABLE void openedTablet(bool visibleToOthers);
Q_INVOKABLE void closedTablet(); Q_INVOKABLE void closedTablet();
Q_INVOKABLE void openedMarketplace(); Q_INVOKABLE void openedMarketplace();
Q_INVOKABLE void toggledAway(bool isAway); Q_INVOKABLE void toggledAway(bool isAway);

View file

@ -114,7 +114,7 @@ public:
void setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry, void setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry,
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals); const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
bool isLoaded() const { return (bool)_renderGeometry; } bool isLoaded() const { return (bool)_renderGeometry && _renderGeometry->isGeometryLoaded(); }
void setIsWireframe(bool isWireframe) { _isWireframe = isWireframe; } void setIsWireframe(bool isWireframe) { _isWireframe = isWireframe; }
bool isWireframe() const { return _isWireframe; } bool isWireframe() const { return _isWireframe; }

View file

@ -150,7 +150,6 @@ signals:
private: private:
bool getRequestsDomainListData(); bool getRequestsDomainListData();
void setRequestsDomainListData(bool requests); void setRequestsDomainListData(bool requests);
bool _requestsDomainListData;
}; };

View file

@ -20,7 +20,7 @@ var DEFAULT_SCRIPTS = [
"system/bubble.js", "system/bubble.js",
"system/snapshot.js", "system/snapshot.js",
"system/help.js", "system/help.js",
"system/pal.js", //"system/mod.js", // older UX, if you prefer "system/pal.js", // "system/mod.js", // older UX, if you prefer
"system/goto.js", "system/goto.js",
"system/marketplaces/marketplaces.js", "system/marketplaces/marketplaces.js",
"system/edit.js", "system/edit.js",
@ -54,9 +54,6 @@ if (previousSetting === true || previousSetting === 'true') {
previousSetting = true; previousSetting = true;
} }
if (Menu.menuExists(MENU_CATEGORY) && !Menu.menuItemExists(MENU_CATEGORY, MENU_ITEM)) { if (Menu.menuExists(MENU_CATEGORY) && !Menu.menuItemExists(MENU_CATEGORY, MENU_ITEM)) {
Menu.addMenuItem({ Menu.addMenuItem({
menuName: MENU_CATEGORY, menuName: MENU_CATEGORY,
@ -78,11 +75,11 @@ function runDefaultsSeparately() {
Script.load(DEFAULT_SCRIPTS[i]); Script.load(DEFAULT_SCRIPTS[i]);
} }
} }
// start all scripts // start all scripts
if (Menu.isOptionChecked(MENU_ITEM)) { if (Menu.isOptionChecked(MENU_ITEM)) {
// we're debugging individual default scripts // we're debugging individual default scripts
// so we load each into its own ScriptEngine instance // so we load each into its own ScriptEngine instance
debuggingDefaultScripts = true;
runDefaultsSeparately(); runDefaultsSeparately();
} else { } else {
// include all default scripts into this ScriptEngine // include all default scripts into this ScriptEngine
@ -90,32 +87,14 @@ if (Menu.isOptionChecked(MENU_ITEM)) {
} }
function menuItemEvent(menuItem) { function menuItemEvent(menuItem) {
if (menuItem == MENU_ITEM) { if (menuItem === MENU_ITEM) {
var isChecked = Menu.isOptionChecked(MENU_ITEM);
isChecked = Menu.isOptionChecked(MENU_ITEM);
if (isChecked === true) { if (isChecked === true) {
Settings.setValue(SETTINGS_KEY, true); Settings.setValue(SETTINGS_KEY, true);
} else if (isChecked === false) { } else if (isChecked === false) {
Settings.setValue(SETTINGS_KEY, false); Settings.setValue(SETTINGS_KEY, false);
} }
Window.alert('You must reload all scripts for this to take effect.') Menu.triggerOption("Reload All Scripts");
}
}
function stopLoadedScripts() {
// remove debug script loads
var runningScripts = ScriptDiscoveryService.getRunning();
for (var i in runningScripts) {
var scriptName = runningScripts[i].name;
for (var j in DEFAULT_SCRIPTS) {
if (DEFAULT_SCRIPTS[j].slice(-scriptName.length) === scriptName) {
ScriptDiscoveryService.stopScript(runningScripts[i].url);
}
}
} }
} }
@ -126,7 +105,6 @@ function removeMenuItem() {
} }
Script.scriptEnding.connect(function() { Script.scriptEnding.connect(function() {
stopLoadedScripts();
removeMenuItem(); removeMenuItem();
}); });

View file

@ -331,6 +331,12 @@ Grabber.prototype.pressEvent = function(event) {
} }
var pickRay = Camera.computePickRay(event.x, event.y); var pickRay = Camera.computePickRay(event.x, event.y);
var overlayResult = Overlays.findRayIntersection(pickRay, true, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]);
if (overlayResult.intersects) {
return;
}
var pickResults = Entities.findRayIntersection(pickRay, true); // accurate picking var pickResults = Entities.findRayIntersection(pickRay, true); // accurate picking
if (!pickResults.intersects) { if (!pickResults.intersects) {
// didn't click on anything // didn't click on anything

View file

@ -14,7 +14,7 @@
/* global getEntityCustomData, flatten, Xform, Script, Quat, Vec3, MyAvatar, Entities, Overlays, Settings, /* global getEntityCustomData, flatten, Xform, Script, Quat, Vec3, MyAvatar, Entities, Overlays, Settings,
Reticle, Controller, Camera, Messages, Mat4, getControllerWorldLocation, getGrabPointSphereOffset, setGrabCommunications, Reticle, Controller, Camera, Messages, Mat4, getControllerWorldLocation, getGrabPointSphereOffset, setGrabCommunications,
Menu, HMD */ Menu, HMD, isInEditMode */
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
(function() { // BEGIN LOCAL_SCOPE (function() { // BEGIN LOCAL_SCOPE
@ -420,7 +420,7 @@ function entityHasActions(entityID) {
function findRayIntersection(pickRay, precise, include, exclude) { function findRayIntersection(pickRay, precise, include, exclude) {
var entities = Entities.findRayIntersection(pickRay, precise, include, exclude, true); var entities = Entities.findRayIntersection(pickRay, precise, include, exclude, true);
var overlays = Overlays.findRayIntersection(pickRay); var overlays = Overlays.findRayIntersection(pickRay, precise, [], [HMD.tabletID]);
if (!overlays.intersects || (entities.intersects && (entities.distance <= overlays.distance))) { if (!overlays.intersects || (entities.intersects && (entities.distance <= overlays.distance))) {
return entities; return entities;
} }
@ -665,6 +665,7 @@ EquipHotspotBuddy.prototype.updateHotspot = function(hotspot, timestamp) {
// override default sphere with a user specified model, if it exists. // override default sphere with a user specified model, if it exists.
overlayInfoSet.overlays.push(Overlays.addOverlay("model", { overlayInfoSet.overlays.push(Overlays.addOverlay("model", {
name: "hotspot overlay",
url: hotspot.modelURL ? hotspot.modelURL : DEFAULT_SPHERE_MODEL_URL, url: hotspot.modelURL ? hotspot.modelURL : DEFAULT_SPHERE_MODEL_URL,
position: hotspot.worldPosition, position: hotspot.worldPosition,
rotation: { rotation: {
@ -797,7 +798,7 @@ function MyController(hand) {
}; };
this.actionID = null; // action this script created... this.actionID = null; // action this script created...
this.grabbedEntity = null; // on this entity. this.grabbedThingID = null; // on this entity.
this.grabbedOverlay = null; this.grabbedOverlay = null;
this.state = STATE_OFF; this.state = STATE_OFF;
this.pointer = null; // entity-id of line object this.pointer = null; // entity-id of line object
@ -874,14 +875,19 @@ function MyController(hand) {
}; };
this.callEntityMethodOnGrabbed = function(entityMethodName) { this.callEntityMethodOnGrabbed = function(entityMethodName) {
if (this.grabbedIsOverlay) {
return;
}
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
Entities.callEntityMethod(this.grabbedEntity, entityMethodName, args); Entities.callEntityMethod(this.grabbedThingID, entityMethodName, args);
}; };
this.setState = function(newState, reason) { this.setState = function(newState, reason) {
if ((isInEditMode() && this.grabbedEntity !== HMD.tabletID )&& (newState !== STATE_OFF && if ((isInEditMode() && this.grabbedThingID !== HMD.tabletID) &&
(newState !== STATE_OFF &&
newState !== STATE_SEARCHING && newState !== STATE_SEARCHING &&
newState !== STATE_OVERLAY_STYLUS_TOUCHING)) { newState !== STATE_OVERLAY_STYLUS_TOUCHING &&
newState !== STATE_OVERLAY_LASER_TOUCHING)) {
return; return;
} }
setGrabCommunications((newState === STATE_DISTANCE_HOLDING) || (newState === STATE_NEAR_GRABBING)); setGrabCommunications((newState === STATE_DISTANCE_HOLDING) || (newState === STATE_NEAR_GRABBING));
@ -924,6 +930,7 @@ function MyController(hand) {
if (!this.grabPointSphere) { if (!this.grabPointSphere) {
this.grabPointSphere = Overlays.addOverlay("sphere", { this.grabPointSphere = Overlays.addOverlay("sphere", {
name: "grabPointSphere",
localPosition: getGrabPointSphereOffset(this.handToController()), localPosition: getGrabPointSphereOffset(this.handToController()),
localRotation: { x: 0, y: 0, z: 0, w: 1 }, localRotation: { x: 0, y: 0, z: 0, w: 1 },
dimensions: GRAB_POINT_SPHERE_RADIUS * 2, dimensions: GRAB_POINT_SPHERE_RADIUS * 2,
@ -954,6 +961,7 @@ function MyController(hand) {
var brightColor = colorPow(color, 0.06); var brightColor = colorPow(color, 0.06);
if (this.searchSphere === null) { if (this.searchSphere === null) {
var sphereProperties = { var sphereProperties = {
name: "searchSphere",
position: location, position: location,
rotation: rotation, rotation: rotation,
outerRadius: size * 1.2, outerRadius: size * 1.2,
@ -976,7 +984,8 @@ function MyController(hand) {
innerAlpha: 1.0, innerAlpha: 1.0,
outerAlpha: 0.0, outerAlpha: 0.0,
outerRadius: size * 1.2, outerRadius: size * 1.2,
visible: true visible: true,
ignoreRayIntersection: true
}); });
} }
}; };
@ -987,6 +996,7 @@ function MyController(hand) {
} }
var stylusProperties = { var stylusProperties = {
name: "stylus",
url: Script.resourcesPath() + "meshes/tablet-stylus-fat.fbx", url: Script.resourcesPath() + "meshes/tablet-stylus-fat.fbx",
localPosition: Vec3.sum({ x: 0.0, localPosition: Vec3.sum({ x: 0.0,
y: WEB_TOUCH_Y_OFFSET, y: WEB_TOUCH_Y_OFFSET,
@ -1021,6 +1031,7 @@ function MyController(hand) {
this.overlayLineOn = function(closePoint, farPoint, color) { this.overlayLineOn = function(closePoint, farPoint, color) {
if (this.overlayLine === null) { if (this.overlayLine === null) {
var lineProperties = { var lineProperties = {
name: "line",
glow: 1.0, glow: 1.0,
start: closePoint, start: closePoint,
end: farPoint, end: farPoint,
@ -1196,6 +1207,13 @@ function MyController(hand) {
} }
} }
var candidateOverlays = Overlays.findOverlays(worldHandPosition, WEB_DISPLAY_STYLUS_DISTANCE);
for (var j = 0; j < candidateOverlays.length; j++) {
if (this.isTablet(candidateOverlays[j])) {
nearWeb = true;
}
}
if (nearWeb) { if (nearWeb) {
this.showStylus(); this.showStylus();
var rayPickInfo = this.calcRayPickInfo(this.hand); var rayPickInfo = this.calcRayPickInfo(this.hand);
@ -1444,7 +1462,7 @@ function MyController(hand) {
var okToEquipFromOtherHand = ((this.getOtherHandController().state == STATE_NEAR_GRABBING || var okToEquipFromOtherHand = ((this.getOtherHandController().state == STATE_NEAR_GRABBING ||
this.getOtherHandController().state == STATE_DISTANCE_HOLDING) && this.getOtherHandController().state == STATE_DISTANCE_HOLDING) &&
this.getOtherHandController().grabbedEntity == hotspot.entityID); this.getOtherHandController().grabbedThingID == hotspot.entityID);
var hasParent = true; var hasParent = true;
if (props.parentID === NULL_UUID) { if (props.parentID === NULL_UUID) {
hasParent = false; hasParent = false;
@ -1613,7 +1631,7 @@ function MyController(hand) {
var farSearching = this.triggerSmoothedSqueezed() && (Date.now() - this.searchStartTime > FAR_SEARCH_DELAY); var farSearching = this.triggerSmoothedSqueezed() && (Date.now() - this.searchStartTime > FAR_SEARCH_DELAY);
this.grabbedEntity = null; this.grabbedThingID = null;
this.grabbedOverlay = null; this.grabbedOverlay = null;
this.isInitialGrab = false; this.isInitialGrab = false;
this.preparingHoldRelease = false; this.preparingHoldRelease = false;
@ -1621,7 +1639,7 @@ function MyController(hand) {
this.checkForUnexpectedChildren(); this.checkForUnexpectedChildren();
if ((this.triggerSmoothedReleased() && this.secondaryReleased())) { if ((this.triggerSmoothedReleased() && this.secondaryReleased())) {
this.grabbedEntity = null; this.grabbedThingID = null;
this.setState(STATE_OFF, "trigger released"); this.setState(STATE_OFF, "trigger released");
return; return;
} }
@ -1642,8 +1660,9 @@ function MyController(hand) {
if (potentialEquipHotspot) { if (potentialEquipHotspot) {
if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && holdEnabled) { if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && holdEnabled) {
this.grabbedHotspot = potentialEquipHotspot; this.grabbedHotspot = potentialEquipHotspot;
this.grabbedEntity = potentialEquipHotspot.entityID; this.grabbedThingID = potentialEquipHotspot.entityID;
this.setState(STATE_HOLD, "equipping '" + entityPropertiesCache.getProps(this.grabbedEntity).name + "'"); this.grabbedIsOverlay = false;
this.setState(STATE_HOLD, "equipping '" + entityPropertiesCache.getProps(this.grabbedThingID).name + "'");
return; return;
} }
@ -1654,6 +1673,11 @@ function MyController(hand) {
return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE); return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE);
}); });
var candidateOverlays = Overlays.findOverlays(handPosition, NEAR_GRAB_RADIUS);
var grabbableOverlays = candidateOverlays.filter(function(overlayID) {
return Overlays.getProperty(overlayID, "grabbable");
});
if (rayPickInfo.entityID) { if (rayPickInfo.entityID) {
this.intersectionDistance = rayPickInfo.distance; this.intersectionDistance = rayPickInfo.distance;
if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) { if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) {
@ -1665,6 +1689,23 @@ function MyController(hand) {
this.intersectionDistance = 0; this.intersectionDistance = 0;
} }
if (grabbableOverlays.length > 0) {
grabbableOverlays.sort(function(a, b) {
var aPosition = Overlays.getProperty(a, "position");
var aDistance = Vec3.distance(aPosition, handPosition);
var bPosition = Overlays.getProperty(b, "position");
var bDistance = Vec3.distance(bPosition, handPosition);
return aDistance - bDistance;
});
this.grabbedThingID = grabbableOverlays[0];
this.grabbedIsOverlay = true;
if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && nearGrabEnabled) {
this.setState(STATE_NEAR_GRABBING, "near grab overlay '" +
Overlays.getProperty(this.grabbedThingID, "name") + "'");
return;
}
}
var entity; var entity;
if (grabbableEntities.length > 0) { if (grabbableEntities.length > 0) {
// sort by distance // sort by distance
@ -1675,7 +1716,8 @@ function MyController(hand) {
}); });
entity = grabbableEntities[0]; entity = grabbableEntities[0];
name = entityPropertiesCache.getProps(entity).name; name = entityPropertiesCache.getProps(entity).name;
this.grabbedEntity = entity; this.grabbedThingID = entity;
this.grabbedIsOverlay = false;
if (this.entityWantsTrigger(entity)) { if (this.entityWantsTrigger(entity)) {
if (this.triggerSmoothedGrab()) { if (this.triggerSmoothedGrab()) {
this.setState(STATE_NEAR_TRIGGER, "near trigger '" + name + "'"); this.setState(STATE_NEAR_TRIGGER, "near trigger '" + name + "'");
@ -1686,7 +1728,7 @@ function MyController(hand) {
} else { } else {
// If near something grabbable, grab it! // If near something grabbable, grab it!
if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && nearGrabEnabled) { if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && nearGrabEnabled) {
this.setState(STATE_NEAR_GRABBING, "near grab '" + name + "'"); this.setState(STATE_NEAR_GRABBING, "near grab entity '" + name + "'");
return; return;
} else { } else {
// potentialNearGrabEntity = entity; // potentialNearGrabEntity = entity;
@ -1709,7 +1751,8 @@ function MyController(hand) {
name = entityPropertiesCache.getProps(entity).name; name = entityPropertiesCache.getProps(entity).name;
if (this.entityWantsTrigger(entity)) { if (this.entityWantsTrigger(entity)) {
if (this.triggerSmoothedGrab()) { if (this.triggerSmoothedGrab()) {
this.grabbedEntity = entity; this.grabbedThingID = entity;
this.grabbedIsOverlay = false;
this.setState(STATE_FAR_TRIGGER, "far trigger '" + name + "'"); this.setState(STATE_FAR_TRIGGER, "far trigger '" + name + "'");
return; return;
} else { } else {
@ -1717,7 +1760,8 @@ function MyController(hand) {
} }
} else if (this.entityIsDistanceGrabbable(rayPickInfo.entityID, handPosition)) { } else if (this.entityIsDistanceGrabbable(rayPickInfo.entityID, handPosition)) {
if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled && farSearching) { if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled && farSearching) {
this.grabbedEntity = entity; this.grabbedThingID = entity;
this.grabbedIsOverlay = false;
this.grabbedDistance = rayPickInfo.distance; this.grabbedDistance = rayPickInfo.distance;
this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'"); this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'");
return; return;
@ -1797,7 +1841,8 @@ function MyController(hand) {
Entities.sendHoverOverEntity(entity, pointerEvent); Entities.sendHoverOverEntity(entity, pointerEvent);
} }
this.grabbedEntity = entity; this.grabbedThingID = entity;
this.grabbedIsOverlay = false;
this.setState(STATE_ENTITY_STYLUS_TOUCHING, "begin touching entity '" + name + "'"); this.setState(STATE_ENTITY_STYLUS_TOUCHING, "begin touching entity '" + name + "'");
return true; return true;
@ -1925,7 +1970,8 @@ function MyController(hand) {
} }
if (this.triggerSmoothedGrab() && (!isEditing() || this.isTablet(entity))) { if (this.triggerSmoothedGrab() && (!isEditing() || this.isTablet(entity))) {
this.grabbedEntity = entity; this.grabbedThingID = entity;
this.grabbedIsOverlay = false;
this.setState(STATE_ENTITY_LASER_TOUCHING, "begin touching entity '" + name + "'"); this.setState(STATE_ENTITY_LASER_TOUCHING, "begin touching entity '" + name + "'");
return true; return true;
} }
@ -2036,7 +2082,7 @@ function MyController(hand) {
var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix());
var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition);
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES);
var now = Date.now(); var now = Date.now();
// add the action and initialize some variables // add the action and initialize some variables
@ -2066,7 +2112,7 @@ function MyController(hand) {
var timeScale = this.distanceGrabTimescale(this.mass, distanceToObject); var timeScale = this.distanceGrabTimescale(this.mass, distanceToObject);
this.actionID = NULL_UUID; this.actionID = NULL_UUID;
this.actionID = Entities.addAction("spring", this.grabbedEntity, { this.actionID = Entities.addAction("spring", this.grabbedThingID, {
targetPosition: this.currentObjectPosition, targetPosition: this.currentObjectPosition,
linearTimeScale: timeScale, linearTimeScale: timeScale,
targetRotation: this.currentObjectRotation, targetRotation: this.currentObjectRotation,
@ -2091,12 +2137,12 @@ function MyController(hand) {
this.ensureDynamic = function() { this.ensureDynamic = function() {
// if we distance hold something and keep it very still before releasing it, it ends up // if we distance hold something and keep it very still before releasing it, it ends up
// non-dynamic in bullet. If it's too still, give it a little bounce so it will fall. // non-dynamic in bullet. If it's too still, give it a little bounce so it will fall.
var props = Entities.getEntityProperties(this.grabbedEntity, ["velocity", "dynamic", "parentID"]); var props = Entities.getEntityProperties(this.grabbedThingID, ["velocity", "dynamic", "parentID"]);
if (props.dynamic && props.parentID == NULL_UUID) { if (props.dynamic && props.parentID == NULL_UUID) {
var velocity = props.velocity; var velocity = props.velocity;
if (Vec3.length(velocity) < 0.05) { // see EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD if (Vec3.length(velocity) < 0.05) { // see EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD
velocity = { x: 0.0, y: 0.2, z:0.0 }; velocity = { x: 0.0, y: 0.2, z:0.0 };
Entities.editEntity(this.grabbedEntity, { velocity: velocity }); Entities.editEntity(this.grabbedThingID, { velocity: velocity });
} }
} }
}; };
@ -2118,7 +2164,7 @@ function MyController(hand) {
var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix());
var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition);
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES);
var now = Date.now(); var now = Date.now();
var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds
@ -2173,7 +2219,7 @@ function MyController(hand) {
newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition); newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition);
var objectToAvatar = Vec3.subtract(this.currentObjectPosition, MyAvatar.position); var objectToAvatar = Vec3.subtract(this.currentObjectPosition, MyAvatar.position);
var handControllerData = getEntityCustomData('handControllerKey', this.grabbedEntity, defaultMoveWithHeadData); var handControllerData = getEntityCustomData('handControllerKey', this.grabbedThingID, defaultMoveWithHeadData);
if (handControllerData.disableMoveWithHead !== true) { if (handControllerData.disableMoveWithHead !== true) {
// mix in head motion // mix in head motion
if (MOVE_WITH_HEAD) { if (MOVE_WITH_HEAD) {
@ -2202,7 +2248,7 @@ function MyController(hand) {
this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition), COLORS_GRAB_DISTANCE_HOLD); this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition), COLORS_GRAB_DISTANCE_HOLD);
var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition)); var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition));
var success = Entities.updateAction(this.grabbedEntity, this.actionID, { var success = Entities.updateAction(this.grabbedThingID, this.actionID, {
targetPosition: newTargetPosition, targetPosition: newTargetPosition,
linearTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject), linearTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject),
targetRotation: this.currentObjectRotation, targetRotation: this.currentObjectRotation,
@ -2219,7 +2265,7 @@ function MyController(hand) {
}; };
this.setupHoldAction = function() { this.setupHoldAction = function() {
this.actionID = Entities.addAction("hold", this.grabbedEntity, { this.actionID = Entities.addAction("hold", this.grabbedThingID, {
hand: this.hand === RIGHT_HAND ? "right" : "left", hand: this.hand === RIGHT_HAND ? "right" : "left",
timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, timeScale: NEAR_GRABBING_ACTION_TIMEFRAME,
relativePosition: this.offsetPosition, relativePosition: this.offsetPosition,
@ -2309,18 +2355,31 @@ function MyController(hand) {
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
if (this.entityActivated) { if (this.entityActivated) {
var saveGrabbedID = this.grabbedEntity; var saveGrabbedID = this.grabbedThingID;
this.release(); this.release();
this.grabbedEntity = saveGrabbedID; this.grabbedThingID = saveGrabbedID;
} }
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); var grabbedProperties;
if (this.grabbedIsOverlay) {
grabbedProperties = {
position: Overlays.getProperty(this.grabbedThingID, "position"),
rotation: Overlays.getProperty(this.grabbedThingID, "rotation"),
parentID: Overlays.getProperty(this.grabbedThingID, "parentID"),
parentJointIndex: Overlays.getProperty(this.grabbedThingID, "parentJointIndex"),
dynamic: false,
shapeType: "none"
};
this.ignoreIK = true;
} else {
grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES);
if (FORCE_IGNORE_IK) { if (FORCE_IGNORE_IK) {
this.ignoreIK = true; this.ignoreIK = true;
} else { } else {
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedThingID, DEFAULT_GRABBABLE_DATA);
this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true; this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true;
} }
}
var handRotation; var handRotation;
var handPosition; var handPosition;
@ -2358,7 +2417,8 @@ function MyController(hand) {
this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset);
} }
var isPhysical = propsArePhysical(grabbedProperties) || entityHasActions(this.grabbedEntity); var isPhysical = propsArePhysical(grabbedProperties) ||
(!this.grabbedIsOverlay && entityHasActions(this.grabbedThingID));
if (isPhysical && this.state == STATE_NEAR_GRABBING && grabbedProperties.parentID === NULL_UUID) { if (isPhysical && this.state == STATE_NEAR_GRABBING && grabbedProperties.parentID === NULL_UUID) {
// grab entity via action // grab entity via action
if (!this.setupHoldAction()) { if (!this.setupHoldAction()) {
@ -2366,7 +2426,7 @@ function MyController(hand) {
} }
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
action: 'grab', action: 'grab',
grabbedEntity: this.grabbedEntity, grabbedEntity: this.grabbedThingID,
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
})); }));
} else { } else {
@ -2392,6 +2452,9 @@ function MyController(hand) {
reparentProps.localRotation = this.offsetRotation; reparentProps.localRotation = this.offsetRotation;
} }
if (this.grabbedIsOverlay) {
Overlays.editOverlay(this.grabbedThingID, reparentProps);
} else {
if (grabbedProperties.userData.length > 0) { if (grabbedProperties.userData.length > 0) {
try{ try{
var userData = JSON.parse(grabbedProperties.userData); var userData = JSON.parse(grabbedProperties.userData);
@ -2433,7 +2496,7 @@ function MyController(hand) {
cUserData.grabbableKey.grabbable = true; cUserData.grabbableKey.grabbable = true;
cProperties.lifetime = lifetime; cProperties.lifetime = lifetime;
cProperties.userData = JSON.stringify(cUserData); cProperties.userData = JSON.stringify(cUserData);
this.grabbedEntity = Entities.addEntity(cProperties); this.grabbedThingID = Entities.addEntity(cProperties);
grabbedProperties = Entities.getEntityProperties(this.grabbedEntity); grabbedProperties = Entities.getEntityProperties(this.grabbedEntity);
var _this = this; var _this = this;
@ -2442,33 +2505,34 @@ function MyController(hand) {
_this.callEntityMethodOnGrabbed("startEquip"); _this.callEntityMethodOnGrabbed("startEquip");
},400); },400);
} }
}catch(e) { }catch(e) {}
print("ERROR: " + e);
} }
Entities.editEntity(this.grabbedThingID, reparentProps);
} }
Entities.editEntity(this.grabbedEntity, reparentProps);
if (this.thisHandIsParent(grabbedProperties)) { if (this.thisHandIsParent(grabbedProperties)) {
// this should never happen, but if it does, don't set previous parent to be this hand. // this should never happen, but if it does, don't set previous parent to be this hand.
// this.previousParentID[this.grabbedEntity] = NULL; // this.previousParentID[this.grabbedThingID] = NULL;
// this.previousParentJointIndex[this.grabbedEntity] = -1; // this.previousParentJointIndex[this.grabbedThingID] = -1;
} else { } else {
this.previousParentID[this.grabbedEntity] = grabbedProperties.parentID; this.previousParentID[this.grabbedThingID] = grabbedProperties.parentID;
this.previousParentJointIndex[this.grabbedEntity] = grabbedProperties.parentJointIndex; this.previousParentJointIndex[this.grabbedThingID] = grabbedProperties.parentJointIndex;
} }
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
action: 'equip', action: 'equip',
grabbedEntity: this.grabbedEntity, grabbedEntity: this.grabbedThingID,
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
})); }));
} }
Entities.editEntity(this.grabbedEntity, { if (!this.grabbedIsOverlay) {
Entities.editEntity(this.grabbedThingID, {
velocity: { x: 0, y: 0, z: 0 }, velocity: { x: 0, y: 0, z: 0 },
angularVelocity: { x: 0, y: 0, z: 0 }, angularVelocity: { x: 0, y: 0, z: 0 },
// dynamic: false // dynamic: false
}); });
}
if (this.state == STATE_NEAR_GRABBING) { if (this.state == STATE_NEAR_GRABBING) {
this.callEntityMethodOnGrabbed("startNearGrab"); this.callEntityMethodOnGrabbed("startNearGrab");
@ -2534,26 +2598,39 @@ function MyController(hand) {
if (dropDetected && !this.waitForTriggerRelease && this.triggerSmoothedGrab()) { if (dropDetected && !this.waitForTriggerRelease && this.triggerSmoothedGrab()) {
// store the offset attach points into preferences. // store the offset attach points into preferences.
if (USE_ATTACH_POINT_SETTINGS && this.grabbedHotspot && this.grabbedEntity) { if (USE_ATTACH_POINT_SETTINGS && this.grabbedHotspot && this.grabbedThingID) {
var prefprops = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); var prefprops = Entities.getEntityProperties(this.grabbedThingID, ["localPosition", "localRotation"]);
if (prefprops && prefprops.localPosition && prefprops.localRotation) { if (prefprops && prefprops.localPosition && prefprops.localRotation) {
storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand, storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand,
prefprops.localPosition, prefprops.localRotation); prefprops.localPosition, prefprops.localRotation);
} }
} }
var grabbedEntity = this.grabbedEntity; var grabbedEntity = this.grabbedThingID;
this.release(); this.release();
this.grabbedEntity = grabbedEntity; this.grabbedThingID = grabbedEntity;
this.setState(STATE_NEAR_GRABBING, "drop gesture detected"); this.setState(STATE_NEAR_GRABBING, "drop gesture detected");
return; return;
} }
this.prevDropDetected = dropDetected; this.prevDropDetected = dropDetected;
} }
var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID", "parentJointIndex", var props;
if (this.grabbedIsOverlay) {
props = {
localPosition: Overlays.getProperty(this.grabbedThingID, "localPosition"),
parentID: Overlays.getProperty(this.grabbedThingID, "parentID"),
parentJointIndex: Overlays.getProperty(this.grabbedThingID, "parentJointIndex"),
position: Overlays.getProperty(this.grabbedThingID, "position"),
rotation: Overlays.getProperty(this.grabbedThingID, "rotation"),
dimensions: Overlays.getProperty(this.grabbedThingID, "dimensions"),
registrationPoint: { x: 0.5, y: 0.5, z: 0.5 }
};
} else {
props = Entities.getEntityProperties(this.grabbedThingID, ["localPosition", "parentID", "parentJointIndex",
"position", "rotation", "dimensions", "position", "rotation", "dimensions",
"registrationPoint"]); "registrationPoint"]);
}
if (!props.position) { if (!props.position) {
// server may have reset, taking our equipped entity with it. move back to "off" state // server may have reset, taking our equipped entity with it. move back to "off" state
this.callEntityMethodOnGrabbed("releaseGrab"); this.callEntityMethodOnGrabbed("releaseGrab");
@ -2565,7 +2642,7 @@ function MyController(hand) {
// someone took it from us or otherwise edited the parentID. end the grab. We don't do this // someone took it from us or otherwise edited the parentID. end the grab. We don't do this
// for equipped things so that they can be adjusted while equipped. // for equipped things so that they can be adjusted while equipped.
this.callEntityMethodOnGrabbed("releaseGrab"); this.callEntityMethodOnGrabbed("releaseGrab");
this.grabbedEntity = null; this.grabbedThingID = null;
this.setState(STATE_OFF, "someone took it"); this.setState(STATE_OFF, "someone took it");
return; return;
} }
@ -2647,7 +2724,7 @@ function MyController(hand) {
if (this.actionID && this.actionTimeout - now < ACTION_TTL_REFRESH * MSECS_PER_SEC) { if (this.actionID && this.actionTimeout - now < ACTION_TTL_REFRESH * MSECS_PER_SEC) {
// if less than a 5 seconds left, refresh the actions ttl // if less than a 5 seconds left, refresh the actions ttl
var success = Entities.updateAction(this.grabbedEntity, this.actionID, { var success = Entities.updateAction(this.grabbedThingID, this.actionID, {
hand: this.hand === RIGHT_HAND ? "right" : "left", hand: this.hand === RIGHT_HAND ? "right" : "left",
timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, timeScale: NEAR_GRABBING_ACTION_TIMEFRAME,
relativePosition: this.offsetPosition, relativePosition: this.offsetPosition,
@ -2661,14 +2738,14 @@ function MyController(hand) {
this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC);
} else { } else {
print("continueNearGrabbing -- updateAction failed"); print("continueNearGrabbing -- updateAction failed");
Entities.deleteAction(this.grabbedEntity, this.actionID); Entities.deleteAction(this.grabbedThingID, this.actionID);
this.setupHoldAction(); this.setupHoldAction();
} }
} }
}; };
this.maybeScale = function(props) { this.maybeScale = function(props) {
if (!objectScalingEnabled || this.isTablet(this.grabbedEntity)) { if (!objectScalingEnabled || this.isTablet(this.grabbedThingID) || this.grabbedIsOverlay) {
return; return;
} }
@ -2690,7 +2767,7 @@ function MyController(hand) {
this.getOtherHandController().getHandPosition())); this.getOtherHandController().getHandPosition()));
var currentRescale = scalingCurrentDistance / this.scalingStartDistance; var currentRescale = scalingCurrentDistance / this.scalingStartDistance;
var newDimensions = Vec3.multiply(currentRescale, this.scalingStartDimensions); var newDimensions = Vec3.multiply(currentRescale, this.scalingStartDimensions);
Entities.editEntity(this.grabbedEntity, { dimensions: newDimensions }); Entities.editEntity(this.grabbedThingID, { dimensions: newDimensions });
} }
}; };
@ -2741,7 +2818,7 @@ function MyController(hand) {
this.nearTrigger = function(deltaTime, timestamp) { this.nearTrigger = function(deltaTime, timestamp) {
if (this.triggerSmoothedReleased()) { if (this.triggerSmoothedReleased()) {
this.callEntityMethodOnGrabbed("stopNearTrigger"); this.callEntityMethodOnGrabbed("stopNearTrigger");
this.grabbedEntity = null; this.grabbedThingID = null;
this.setState(STATE_OFF, "trigger released"); this.setState(STATE_OFF, "trigger released");
return; return;
} }
@ -2751,7 +2828,7 @@ function MyController(hand) {
this.farTrigger = function(deltaTime, timestamp) { this.farTrigger = function(deltaTime, timestamp) {
if (this.triggerSmoothedReleased()) { if (this.triggerSmoothedReleased()) {
this.callEntityMethodOnGrabbed("stopFarTrigger"); this.callEntityMethodOnGrabbed("stopFarTrigger");
this.grabbedEntity = null; this.grabbedThingID = null;
this.setState(STATE_OFF, "trigger released"); this.setState(STATE_OFF, "trigger released");
return; return;
} }
@ -2766,9 +2843,9 @@ function MyController(hand) {
var intersection = findRayIntersection(pickRay, true, [], [], true); var intersection = findRayIntersection(pickRay, true, [], [], true);
if (intersection.accurate || intersection.overlayID) { if (intersection.accurate || intersection.overlayID) {
this.lastPickTime = now; this.lastPickTime = now;
if (intersection.entityID != this.grabbedEntity) { if (intersection.entityID != this.grabbedThingID) {
this.callEntityMethodOnGrabbed("stopFarTrigger"); this.callEntityMethodOnGrabbed("stopFarTrigger");
this.grabbedEntity = null; this.grabbedThingID = null;
this.setState(STATE_OFF, "laser moved off of entity"); this.setState(STATE_OFF, "laser moved off of entity");
return; return;
} }
@ -2790,13 +2867,13 @@ function MyController(hand) {
this.entityTouchingEnter = function() { this.entityTouchingEnter = function() {
// test for intersection between controller laser and web entity plane. // test for intersection between controller laser and web entity plane.
var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, var intersectInfo = handLaserIntersectEntity(this.grabbedThingID,
getControllerWorldLocation(this.handToController(), true)); getControllerWorldLocation(this.handToController(), true));
if (intersectInfo) { if (intersectInfo) {
var pointerEvent = { var pointerEvent = {
type: "Press", type: "Press",
id: this.hand + 1, // 0 is reserved for hardware mouse id: this.hand + 1, // 0 is reserved for hardware mouse
pos2D: projectOntoEntityXYPlane(this.grabbedEntity, intersectInfo.point), pos2D: projectOntoEntityXYPlane(this.grabbedThingID, intersectInfo.point),
pos3D: intersectInfo.point, pos3D: intersectInfo.point,
normal: intersectInfo.normal, normal: intersectInfo.normal,
direction: intersectInfo.searchRay.direction, direction: intersectInfo.searchRay.direction,
@ -2804,8 +2881,8 @@ function MyController(hand) {
isPrimaryHeld: true isPrimaryHeld: true
}; };
Entities.sendMousePressOnEntity(this.grabbedEntity, pointerEvent); Entities.sendMousePressOnEntity(this.grabbedThingID, pointerEvent);
Entities.sendClickDownOnEntity(this.grabbedEntity, pointerEvent); Entities.sendClickDownOnEntity(this.grabbedThingID, pointerEvent);
this.touchingEnterTimer = 0; this.touchingEnterTimer = 0;
this.touchingEnterPointerEvent = pointerEvent; this.touchingEnterPointerEvent = pointerEvent;
@ -2827,7 +2904,7 @@ function MyController(hand) {
this.entityTouchingExit = function() { this.entityTouchingExit = function() {
// test for intersection between controller laser and web entity plane. // test for intersection between controller laser and web entity plane.
var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, var intersectInfo = handLaserIntersectEntity(this.grabbedThingID,
getControllerWorldLocation(this.handToController(), true)); getControllerWorldLocation(this.handToController(), true));
if (intersectInfo) { if (intersectInfo) {
var pointerEvent; var pointerEvent;
@ -2835,7 +2912,7 @@ function MyController(hand) {
pointerEvent = { pointerEvent = {
type: "Release", type: "Release",
id: this.hand + 1, // 0 is reserved for hardware mouse id: this.hand + 1, // 0 is reserved for hardware mouse
pos2D: projectOntoEntityXYPlane(this.grabbedEntity, intersectInfo.point), pos2D: projectOntoEntityXYPlane(this.grabbedThingID, intersectInfo.point),
pos3D: intersectInfo.point, pos3D: intersectInfo.point,
normal: intersectInfo.normal, normal: intersectInfo.normal,
direction: intersectInfo.searchRay.direction, direction: intersectInfo.searchRay.direction,
@ -2848,11 +2925,11 @@ function MyController(hand) {
pointerEvent.isPrimaryHeld = false; pointerEvent.isPrimaryHeld = false;
} }
Entities.sendMouseReleaseOnEntity(this.grabbedEntity, pointerEvent); Entities.sendMouseReleaseOnEntity(this.grabbedThingID, pointerEvent);
Entities.sendClickReleaseOnEntity(this.grabbedEntity, pointerEvent); Entities.sendClickReleaseOnEntity(this.grabbedThingID, pointerEvent);
Entities.sendHoverLeaveEntity(this.grabbedEntity, pointerEvent); Entities.sendHoverLeaveEntity(this.grabbedThingID, pointerEvent);
} }
this.grabbedEntity = null; this.grabbedThingID = null;
this.grabbedOverlay = null; this.grabbedOverlay = null;
}; };
@ -2860,7 +2937,7 @@ function MyController(hand) {
this.touchingEnterTimer += dt; this.touchingEnterTimer += dt;
entityPropertiesCache.addEntity(this.grabbedEntity); entityPropertiesCache.addEntity(this.grabbedThingID);
if (this.state == STATE_ENTITY_LASER_TOUCHING && !this.triggerSmoothedGrab()) { if (this.state == STATE_ENTITY_LASER_TOUCHING && !this.triggerSmoothedGrab()) {
this.setState(STATE_OFF, "released trigger"); this.setState(STATE_OFF, "released trigger");
@ -2868,7 +2945,7 @@ function MyController(hand) {
} }
// test for intersection between controller laser and web entity plane. // test for intersection between controller laser and web entity plane.
var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, var intersectInfo = handLaserIntersectEntity(this.grabbedThingID,
getControllerWorldLocation(this.handToController(), true)); getControllerWorldLocation(this.handToController(), true));
if (intersectInfo) { if (intersectInfo) {
@ -2878,15 +2955,15 @@ function MyController(hand) {
return; return;
} }
if (Entities.keyboardFocusEntity != this.grabbedEntity) { if (Entities.keyboardFocusEntity != this.grabbedThingID) {
Overlays.keyboardFocusOverlay = 0; Overlays.keyboardFocusOverlay = 0;
Entities.keyboardFocusEntity = this.grabbedEntity; Entities.keyboardFocusEntity = this.grabbedThingID;
} }
var pointerEvent = { var pointerEvent = {
type: "Move", type: "Move",
id: this.hand + 1, // 0 is reserved for hardware mouse id: this.hand + 1, // 0 is reserved for hardware mouse
pos2D: projectOntoEntityXYPlane(this.grabbedEntity, intersectInfo.point), pos2D: projectOntoEntityXYPlane(this.grabbedThingID, intersectInfo.point),
pos3D: intersectInfo.point, pos3D: intersectInfo.point,
normal: intersectInfo.normal, normal: intersectInfo.normal,
direction: intersectInfo.searchRay.direction, direction: intersectInfo.searchRay.direction,
@ -2897,8 +2974,8 @@ function MyController(hand) {
var POINTER_PRESS_TO_MOVE_DELAY = 0.25; // seconds var POINTER_PRESS_TO_MOVE_DELAY = 0.25; // seconds
if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY ||
Vec3.distance(intersectInfo.point, this.touchingEnterPointerEvent.pos3D) > this.deadspotRadius) { Vec3.distance(intersectInfo.point, this.touchingEnterPointerEvent.pos3D) > this.deadspotRadius) {
Entities.sendMouseMoveOnEntity(this.grabbedEntity, pointerEvent); Entities.sendMouseMoveOnEntity(this.grabbedThingID, pointerEvent);
Entities.sendHoldingClickOnEntity(this.grabbedEntity, pointerEvent); Entities.sendHoldingClickOnEntity(this.grabbedThingID, pointerEvent);
this.deadspotExpired = true; this.deadspotExpired = true;
} }
@ -2908,7 +2985,7 @@ function MyController(hand) {
} }
Reticle.setVisible(false); Reticle.setVisible(false);
} else { } else {
this.grabbedEntity = null; this.grabbedThingID = null;
this.setState(STATE_OFF, "grabbed entity was destroyed"); this.setState(STATE_OFF, "grabbed entity was destroyed");
return; return;
} }
@ -2993,7 +3070,7 @@ function MyController(hand) {
Overlays.sendMouseReleaseOnOverlay(this.grabbedOverlay, pointerEvent); Overlays.sendMouseReleaseOnOverlay(this.grabbedOverlay, pointerEvent);
Overlays.sendHoverLeaveOverlay(this.grabbedOverlay, pointerEvent); Overlays.sendHoverLeaveOverlay(this.grabbedOverlay, pointerEvent);
} }
this.grabbedEntity = null; this.grabbedThingID = null;
this.grabbedOverlay = null; this.grabbedOverlay = null;
}; };
@ -3016,7 +3093,7 @@ function MyController(hand) {
if (this.state == STATE_OVERLAY_STYLUS_TOUCHING && if (this.state == STATE_OVERLAY_STYLUS_TOUCHING &&
intersectInfo.distance > WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET + WEB_TOUCH_Y_TOUCH_DEADZONE_SIZE) { intersectInfo.distance > WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET + WEB_TOUCH_Y_TOUCH_DEADZONE_SIZE) {
this.grabbedEntity = null; this.grabbedThingID = null;
this.setState(STATE_OFF, "pulled away from overlay"); this.setState(STATE_OFF, "pulled away from overlay");
return; return;
} }
@ -3073,7 +3150,7 @@ function MyController(hand) {
} }
Reticle.setVisible(false); Reticle.setVisible(false);
} else { } else {
this.grabbedEntity = null; this.grabbedThingID = null;
this.setState(STATE_OFF, "grabbed overlay was destroyed"); this.setState(STATE_OFF, "grabbed overlay was destroyed");
return; return;
} }
@ -3082,7 +3159,7 @@ function MyController(hand) {
this.release = function() { this.release = function() {
this.turnOffVisualizations(); this.turnOffVisualizations();
if (this.grabbedEntity !== null) { if (this.grabbedThingID !== null) {
if (this.state === STATE_HOLD) { if (this.state === STATE_HOLD) {
this.callEntityMethodOnGrabbed("releaseEquip"); this.callEntityMethodOnGrabbed("releaseEquip");
} }
@ -3090,35 +3167,49 @@ function MyController(hand) {
// Make a small release haptic pulse if we really were holding something // Make a small release haptic pulse if we really were holding something
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
if (this.actionID !== null) { if (this.actionID !== null) {
Entities.deleteAction(this.grabbedEntity, this.actionID); Entities.deleteAction(this.grabbedThingID, this.actionID);
} else { } else {
// no action, so it's a parenting grab // no action, so it's a parenting grab
if (this.previousParentID[this.grabbedEntity] === NULL_UUID) { if (this.previousParentID[this.grabbedThingID] === NULL_UUID) {
Entities.editEntity(this.grabbedEntity, { if (this.grabbedIsOverlay) {
parentID: this.previousParentID[this.grabbedEntity], Overlays.editOverlay(this.grabbedThingID, {
parentJointIndex: this.previousParentJointIndex[this.grabbedEntity] parentID: NULL_UUID,
parentJointIndex: -1
});
} else {
Entities.editEntity(this.grabbedThingID, {
parentID: this.previousParentID[this.grabbedThingID],
parentJointIndex: this.previousParentJointIndex[this.grabbedThingID]
}); });
this.ensureDynamic(); this.ensureDynamic();
}
} else {
if (this.grabbedIsOverlay) {
Overlays.editOverlay(this.grabbedThingID, {
parentID: this.previousParentID[this.grabbedThingID],
parentJointIndex: this.previousParentJointIndex[this.grabbedThingID],
});
} else { } else {
// we're putting this back as a child of some other parent, so zero its velocity // we're putting this back as a child of some other parent, so zero its velocity
Entities.editEntity(this.grabbedEntity, { Entities.editEntity(this.grabbedThingID, {
parentID: this.previousParentID[this.grabbedEntity], parentID: this.previousParentID[this.grabbedThingID],
parentJointIndex: this.previousParentJointIndex[this.grabbedEntity], parentJointIndex: this.previousParentJointIndex[this.grabbedThingID],
velocity: {x: 0, y: 0, z: 0}, velocity: {x: 0, y: 0, z: 0},
angularVelocity: {x: 0, y: 0, z: 0} angularVelocity: {x: 0, y: 0, z: 0}
}); });
} }
} }
}
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
action: 'release', action: 'release',
grabbedEntity: this.grabbedEntity, grabbedEntity: this.grabbedThingID,
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
})); }));
} }
this.actionID = null; this.actionID = null;
this.grabbedEntity = null; this.grabbedThingID = null;
this.grabbedOverlay = null; this.grabbedOverlay = null;
this.grabbedHotspot = null; this.grabbedHotspot = null;
@ -3206,9 +3297,13 @@ function MyController(hand) {
} }
_this.previouslyUnhooked[childID] = now; _this.previouslyUnhooked[childID] = now;
// we don't know if it's an entity or an overlay
Entities.editEntity(childID, { parentID: previousParentID, parentJointIndex: previousParentJointIndex }); Entities.editEntity(childID, { parentID: previousParentID, parentJointIndex: previousParentJointIndex });
Overlays.editOverlay(childID, { parentID: previousParentID, parentJointIndex: previousParentJointIndex });
} else { } else {
Entities.editEntity(childID, { parentID: NULL_UUID }); Entities.editEntity(childID, { parentID: NULL_UUID });
Overlays.editOverlay(childID, { parentID: NULL_UUID });
} }
} }
}); });
@ -3327,8 +3422,8 @@ var handleHandMessages = function(channel, message, sender) {
selectedController.release(); selectedController.release();
var wearableEntity = data.entityID; var wearableEntity = data.entityID;
entityPropertiesCache.addEntity(wearableEntity); entityPropertiesCache.addEntity(wearableEntity);
selectedController.grabbedEntity = wearableEntity; selectedController.grabbedThingID = wearableEntity;
var hotspots = selectedController.collectEquipHotspots(selectedController.grabbedEntity); var hotspots = selectedController.collectEquipHotspots(selectedController.grabbedThingID);
if (hotspots.length > 0) { if (hotspots.length > 0) {
if (hotspotIndex >= hotspots.length) { if (hotspotIndex >= hotspots.length) {
hotspotIndex = 0; hotspotIndex = 0;

View file

@ -25,10 +25,13 @@ var OVERLAY_RAMP_RATE = 8.0;
var animStateHandlerID; var animStateHandlerID;
var isPointingIndex = false; var isBothIndexesPointing = false;
var HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index"; var HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index";
var indexfingerJointNames = ["LeftHandIndex1", "LeftHandIndex2", "LeftHandIndex3", "RightHandIndex1", "RightHandIndex2", "RightHandIndex3"]; var isLeftIndexPointing = false;
var isRightIndexPointing = false;
var isLeftThumbRaised = false;
var isRightThumbRaised = false;
function clamp(val, min, max) { function clamp(val, min, max) {
return Math.min(Math.max(val, min), max); return Math.min(Math.max(val, min), max);
@ -46,17 +49,32 @@ function init() {
Script.update.connect(update); Script.update.connect(update);
animStateHandlerID = MyAvatar.addAnimationStateHandler( animStateHandlerID = MyAvatar.addAnimationStateHandler(
animStateHandler, animStateHandler,
["leftHandOverlayAlpha", "rightHandOverlayAlpha", "leftHandGraspAlpha", "rightHandGraspAlpha"] [
"leftHandOverlayAlpha", "leftHandGraspAlpha",
"rightHandOverlayAlpha", "rightHandGraspAlpha",
"isLeftHandGrasp", "isLeftIndexPoint", "isLeftThumbRaise", "isLeftIndexPointAndThumbRaise",
"isRightHandGrasp", "isRightIndexPoint", "isRightThumbRaise", "isRightIndexPointAndThumbRaise",
]
); );
Messages.subscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); Messages.subscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL);
Messages.messageReceived.connect(handleMessages); Messages.messageReceived.connect(handleMessages);
} }
function animStateHandler(props) { function animStateHandler(props) {
return { leftHandOverlayAlpha: leftHandOverlayAlpha, return {
leftHandOverlayAlpha: leftHandOverlayAlpha,
leftHandGraspAlpha: lastLeftTrigger, leftHandGraspAlpha: lastLeftTrigger,
rightHandOverlayAlpha: rightHandOverlayAlpha, rightHandOverlayAlpha: rightHandOverlayAlpha,
rightHandGraspAlpha: lastRightTrigger }; rightHandGraspAlpha: lastRightTrigger,
isLeftHandGrasp: !isBothIndexesPointing && !isLeftIndexPointing && !isLeftThumbRaised,
isLeftIndexPoint: (isBothIndexesPointing || isLeftIndexPointing) && !isLeftThumbRaised,
isLeftThumbRaise: !isBothIndexesPointing && !isLeftIndexPointing && isLeftThumbRaised,
isLeftIndexPointAndThumbRaise: (isBothIndexesPointing || isLeftIndexPointing) && isLeftThumbRaised,
isRightHandGrasp: !isBothIndexesPointing && !isRightIndexPointing && !isRightThumbRaised,
isRightIndexPoint: (isBothIndexesPointing || isRightIndexPointing) && !isRightThumbRaised,
isRightThumbRaise: !isBothIndexesPointing && !isRightIndexPointing && isRightThumbRaised,
isRightIndexPointAndThumbRaise: (isBothIndexesPointing || isRightIndexPointing) && isRightThumbRaised
};
} }
function update(dt) { function update(dt) {
@ -84,13 +102,11 @@ function update(dt) {
rightHandOverlayAlpha = clamp(rightHandOverlayAlpha - OVERLAY_RAMP_RATE * dt, 0, 1); rightHandOverlayAlpha = clamp(rightHandOverlayAlpha - OVERLAY_RAMP_RATE * dt, 0, 1);
} }
// Point index finger. // Pointing index fingers and raising thumbs
if (isPointingIndex) { isLeftIndexPointing = leftHandPose.valid && Controller.getValue(Controller.Standard.LeftIndexPoint) === 1;
var zeroRotation = { x: 0, y: 0, z: 0, w: 1 }; isRightIndexPointing = rightHandPose.valid && Controller.getValue(Controller.Standard.RightIndexPoint) === 1;
for (var i = 0; i < indexfingerJointNames.length; i++) { isLeftThumbRaised = leftHandPose.valid && Controller.getValue(Controller.Standard.LeftThumbUp) === 1;
MyAvatar.setJointRotation(indexfingerJointNames[i], zeroRotation); isRightThumbRaised = rightHandPose.valid && Controller.getValue(Controller.Standard.RightThumbUp) === 1;
}
}
} }
function handleMessages(channel, message, sender) { function handleMessages(channel, message, sender) {
@ -98,13 +114,7 @@ function handleMessages(channel, message, sender) {
var data = JSON.parse(message); var data = JSON.parse(message);
if (data.pointIndex !== undefined) { if (data.pointIndex !== undefined) {
print("pointIndex: " + data.pointIndex); print("pointIndex: " + data.pointIndex);
isPointingIndex = data.pointIndex; isBothIndexesPointing = data.pointIndex;
if (!isPointingIndex) {
for (var i = 0; i < indexfingerJointNames.length; i++) {
MyAvatar.clearJointData(indexfingerJointNames[i]);
}
}
} }
} }
} }

View file

@ -564,6 +564,11 @@ function findClickedEntity(event) {
var pickRay = Camera.computePickRay(event.x, event.y); var pickRay = Camera.computePickRay(event.x, event.y);
var overlayResult = Overlays.findRayIntersection(pickRay, true, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]);
if (overlayResult.intersects) {
return null;
}
var entityResult = Entities.findRayIntersection(pickRay, true); // want precision picking var entityResult = Entities.findRayIntersection(pickRay, true); // want precision picking
var lightResult = lightOverlayManager.findRayIntersection(pickRay); var lightResult = lightOverlayManager.findRayIntersection(pickRay);
lightResult.accurate = true; lightResult.accurate = true;

View file

@ -7,8 +7,8 @@
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
/* global getControllerWorldLocation, setEntityCustomData, Tablet, WebTablet:true, HMD, Settings, Script, /* global getControllerWorldLocation, Tablet, WebTablet:true, HMD, Settings, Script,
Vec3, Quat, MyAvatar, Entities, Overlays, Camera, Messages, Xform, clamp */ Vec3, Quat, MyAvatar, Entities, Overlays, Camera, Messages, Xform, clamp, Controller, Mat4 */
Script.include(Script.resolvePath("../libraries/utils.js")); Script.include(Script.resolvePath("../libraries/utils.js"));
Script.include(Script.resolvePath("../libraries/controllers.js")); Script.include(Script.resolvePath("../libraries/controllers.js"));
@ -34,7 +34,7 @@ var TABLET_NATURAL_DIMENSIONS = {x: 33.797, y: 50.129, z: 2.269};
var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png"; var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png";
// var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png"; // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png";
var TABLET_MODEL_PATH = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx"; var TABLET_MODEL_PATH = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx";
// var TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx"; var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx";
// returns object with two fields: // returns object with two fields:
// * position - position in front of the user // * position - position in front of the user
@ -112,10 +112,19 @@ WebTablet = function (url, width, dpi, hand, clientOnly) {
this.dpi = DEFAULT_DPI * (DEFAULT_WIDTH / this.width); this.dpi = DEFAULT_DPI * (DEFAULT_WIDTH / this.width);
} }
var modelURL;
if (Settings.getValue("tabletVisibleToOthers")) {
modelURL = TABLET_MODEL_PATH;
} else {
modelURL = LOCAL_TABLET_MODEL_PATH;
}
var tabletProperties = { var tabletProperties = {
name: "WebTablet Tablet", name: "WebTablet Tablet",
type: "Model", type: "Model",
modelURL: TABLET_MODEL_PATH, modelURL: modelURL,
url: modelURL, // for overlay
grabbable: true, // for overlay
userData: JSON.stringify({ userData: JSON.stringify({
"grabbableKey": {"grabbable": true} "grabbableKey": {"grabbable": true}
}), }),
@ -127,7 +136,14 @@ WebTablet = function (url, width, dpi, hand, clientOnly) {
this.calculateTabletAttachmentProperties(hand, true, tabletProperties); this.calculateTabletAttachmentProperties(hand, true, tabletProperties);
this.cleanUpOldTablets(); this.cleanUpOldTablets();
if (Settings.getValue("tabletVisibleToOthers")) {
this.tabletEntityID = Entities.addEntity(tabletProperties, clientOnly); this.tabletEntityID = Entities.addEntity(tabletProperties, clientOnly);
this.tabletIsOverlay = false;
} else {
this.tabletEntityID = Overlays.addOverlay("model", tabletProperties);
this.tabletIsOverlay = true;
}
if (this.webOverlayID) { if (this.webOverlayID) {
Overlays.deleteOverlay(this.webOverlayID); Overlays.deleteOverlay(this.webOverlayID);
@ -151,12 +167,12 @@ WebTablet = function (url, width, dpi, hand, clientOnly) {
isAA: HMD.active isAA: HMD.active
}); });
var HOME_BUTTON_Y_OFFSET = (this.height / 2) - 0.035; var HOME_BUTTON_Y_OFFSET = (this.height / 2) - 0.009;
this.homeButtonEntity = Overlays.addOverlay("sphere", { this.homeButtonID = Overlays.addOverlay("sphere", {
name: "homeButton", name: "homeButton",
localPosition: {x: 0.0, y: -HOME_BUTTON_Y_OFFSET, z: -0.01}, localPosition: {x: -0.001, y: -HOME_BUTTON_Y_OFFSET, z: 0.0},
localRotation: Quat.angleAxis(0, Y_AXIS), localRotation: Quat.angleAxis(0, Y_AXIS),
dimensions: { x: 0.04, y: 0.04, z: 0.02}, dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor},
alpha: 0.0, alpha: 0.0,
visible: true, visible: true,
drawInFront: false, drawInFront: false,
@ -165,7 +181,7 @@ WebTablet = function (url, width, dpi, hand, clientOnly) {
}); });
this.receive = function (channel, senderID, senderUUID, localOnly) { this.receive = function (channel, senderID, senderUUID, localOnly) {
if (_this.homeButtonEntity == senderID) { if (_this.homeButtonID == senderID) {
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var onHomeScreen = tablet.onHomeScreen(); var onHomeScreen = tablet.onHomeScreen();
if (onHomeScreen) { if (onHomeScreen) {
@ -184,7 +200,16 @@ WebTablet = function (url, width, dpi, hand, clientOnly) {
}; };
this.getLocation = function() { this.getLocation = function() {
if (this.tabletIsOverlay) {
var location = Overlays.getProperty(this.tabletEntityID, "localPosition");
var orientation = Overlays.getProperty(this.tabletEntityID, "localOrientation");
return {
localPosition: location,
localRotation: orientation
};
} else {
return Entities.getEntityProperties(_this.tabletEntityID, ["localPosition", "localRotation"]); return Entities.getEntityProperties(_this.tabletEntityID, ["localPosition", "localRotation"]);
}
}; };
this.clicked = false; this.clicked = false;
@ -242,8 +267,12 @@ WebTablet.prototype.getOverlayObject = function () {
WebTablet.prototype.destroy = function () { WebTablet.prototype.destroy = function () {
Overlays.deleteOverlay(this.webOverlayID); Overlays.deleteOverlay(this.webOverlayID);
if (this.tabletIsOverlay) {
Overlays.deleteOverlay(this.tabletEntityID);
} else {
Entities.deleteEntity(this.tabletEntityID); Entities.deleteEntity(this.tabletEntityID);
Overlays.deleteOverlay(this.homeButtonEntity); }
Overlays.deleteOverlay(this.homeButtonID);
HMD.displayModeChanged.disconnect(this.myOnHmdChanged); HMD.displayModeChanged.disconnect(this.myOnHmdChanged);
Controller.mousePressEvent.disconnect(this.myMousePressEvent); Controller.mousePressEvent.disconnect(this.myMousePressEvent);
@ -426,10 +455,16 @@ WebTablet.prototype.getPosition = function () {
WebTablet.prototype.mousePressEvent = function (event) { WebTablet.prototype.mousePressEvent = function (event) {
var pickRay = Camera.computePickRay(event.x, event.y); var pickRay = Camera.computePickRay(event.x, event.y);
var entityPickResults = Entities.findRayIntersection(pickRay, true, [this.tabletEntityID]); // non-accurate picking var entityPickResults;
if (entityPickResults.intersects && entityPickResults.entityID === this.tabletEntityID) { if (this.tabletIsOverlay) {
var overlayPickResults = Overlays.findRayIntersection(pickRay); entityPickResults = Overlays.findRayIntersection(pickRay, true, [this.tabletEntityID]);
if (overlayPickResults.intersects && overlayPickResults.overlayID === HMD.homeButtonID) { } else {
entityPickResults = Entities.findRayIntersection(pickRay, true, [this.tabletEntityID]);
}
if (entityPickResults.intersects && (entityPickResults.entityID === this.tabletEntityID ||
entityPickResults.overlayID === this.tabletEntityID)) {
var overlayPickResults = Overlays.findRayIntersection(pickRay, true, [this.webOverlayID, this.homeButtonID], []);
if (overlayPickResults.intersects && overlayPickResults.overlayID === this.homeButtonID) {
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var onHomeScreen = tablet.onHomeScreen(); var onHomeScreen = tablet.onHomeScreen();
if (onHomeScreen) { if (onHomeScreen) {
@ -438,13 +473,17 @@ WebTablet.prototype.mousePressEvent = function (event) {
tablet.gotoHomeScreen(); tablet.gotoHomeScreen();
this.setHomeButtonTexture(); this.setHomeButtonTexture();
} }
} else if (!HMD.active && (!overlayPickResults.intersects || !overlayPickResults.overlayID === this.webOverlayID)) { } else if (!HMD.active && (!overlayPickResults.intersects || overlayPickResults.overlayID !== this.webOverlayID)) {
this.dragging = true; this.dragging = true;
var invCameraXform = new Xform(Camera.orientation, Camera.position).inv(); var invCameraXform = new Xform(Camera.orientation, Camera.position).inv();
this.initialLocalIntersectionPoint = invCameraXform.xformPoint(entityPickResults.intersection); this.initialLocalIntersectionPoint = invCameraXform.xformPoint(entityPickResults.intersection);
if (this.tabletIsOverlay) {
this.initialLocalPosition = Overlays.getProperty(this.tabletEntityID, "localPosition");
} else {
this.initialLocalPosition = Entities.getEntityProperties(this.tabletEntityID, ["localPosition"]).localPosition; this.initialLocalPosition = Entities.getEntityProperties(this.tabletEntityID, ["localPosition"]).localPosition;
} }
} }
}
}; };
WebTablet.prototype.cameraModeChanged = function (newMode) { WebTablet.prototype.cameraModeChanged = function (newMode) {
@ -488,11 +527,17 @@ WebTablet.prototype.mouseMoveEvent = function (event) {
var localIntersectionPoint = Vec3.sum(localPickRay.origin, Vec3.multiply(localPickRay.direction, result.distance)); var localIntersectionPoint = Vec3.sum(localPickRay.origin, Vec3.multiply(localPickRay.direction, result.distance));
var localOffset = Vec3.subtract(localIntersectionPoint, this.initialLocalIntersectionPoint); var localOffset = Vec3.subtract(localIntersectionPoint, this.initialLocalIntersectionPoint);
var localPosition = Vec3.sum(this.initialLocalPosition, localOffset); var localPosition = Vec3.sum(this.initialLocalPosition, localOffset);
if (this.tabletIsOverlay) {
Overlays.editOverlay(this.tabletEntityID, {
localPosition: localPosition
});
} else {
Entities.editEntity(this.tabletEntityID, { Entities.editEntity(this.tabletEntityID, {
localPosition: localPosition localPosition: localPosition
}); });
} }
} }
}
}; };
WebTablet.prototype.mouseReleaseEvent = function (event) { WebTablet.prototype.mouseReleaseEvent = function (event) {

View file

@ -3866,6 +3866,12 @@ SelectionDisplay = (function() {
var somethingClicked = false; var somethingClicked = false;
var pickRay = generalComputePickRay(event.x, event.y); var pickRay = generalComputePickRay(event.x, event.y);
var result = Overlays.findRayIntersection(pickRay, true, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]);
if (result.intersects) {
// mouse clicks on the tablet should override the edit affordances
return false;
}
// before we do a ray test for grabbers, disable the ray intersection for our selection box // before we do a ray test for grabbers, disable the ray intersection for our selection box
Overlays.editOverlay(selectionBox, { Overlays.editOverlay(selectionBox, {
ignoreRayIntersection: true ignoreRayIntersection: true

View file

@ -37,6 +37,15 @@ var conserveResources = true;
Script.include("/~/system/libraries/controllers.js"); Script.include("/~/system/libraries/controllers.js");
function projectVectorOntoPlane(normalizedVector, planeNormal) {
return Vec3.cross(planeNormal, Vec3.cross(normalizedVector, planeNormal));
}
function angleBetweenVectorsInPlane(from, to, normal) {
var projectedFrom = projectVectorOntoPlane(from, normal);
var projectedTo = projectVectorOntoPlane(to, normal);
return Vec3.orientedAngle(projectedFrom, projectedTo, normal);
}
// //
// Overlays. // Overlays.
// //
@ -229,7 +238,11 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
break; break;
case 'refresh': case 'refresh':
removeOverlays(); removeOverlays();
populateUserList(message.params); // If filter is specified from .qml instead of through settings, update the settings.
if (message.params.filter !== undefined) {
Settings.setValue('pal/filtered', !!message.params.filter);
}
populateUserList(message.params.selected);
UserActivityLogger.palAction("refresh", ""); UserActivityLogger.palAction("refresh", "");
break; break;
case 'updateGain': case 'updateGain':
@ -271,13 +284,42 @@ function addAvatarNode(id) {
color: color(selected, false, 0.0), color: color(selected, false, 0.0),
ignoreRayIntersection: false}, selected, !conserveResources); ignoreRayIntersection: false}, selected, !conserveResources);
} }
// Each open/refresh will capture a stable set of avatarsOfInterest, within the specified filter.
var avatarsOfInterest = {};
function populateUserList(selectData) { function populateUserList(selectData) {
var filter = Settings.getValue('pal/filtered') && {distance: Settings.getValue('pal/nearDistance')};
var data = [], avatars = AvatarList.getAvatarIdentifiers(); var data = [], avatars = AvatarList.getAvatarIdentifiers();
conserveResources = avatars.length > 20; avatarsOfInterest = {};
var myPosition = filter && Camera.position,
frustum = filter && Camera.frustum,
verticalHalfAngle = filter && (frustum.fieldOfView / 2),
horizontalHalfAngle = filter && (verticalHalfAngle * frustum.aspectRatio),
orientation = filter && Camera.orientation,
front = filter && Quat.getFront(orientation),
verticalAngleNormal = filter && Quat.getRight(orientation),
horizontalAngleNormal = filter && Quat.getUp(orientation);
avatars.forEach(function (id) { // sorting the identifiers is just an aid for debugging avatars.forEach(function (id) { // sorting the identifiers is just an aid for debugging
var avatar = AvatarList.getAvatar(id); var avatar = AvatarList.getAvatar(id);
var name = avatar.sessionDisplayName;
if (!name) {
// Either we got a data packet but no identity yet, or something is really messed up. In any case,
// we won't be able to do anything with this user, so don't include them.
// In normal circumstances, a refresh will bring in the new user, but if we're very heavily loaded,
// we could be losing and gaining people randomly.
print('No avatar identity data for', id);
return;
}
if (id && myPosition && (Vec3.distance(avatar.position, myPosition) > filter.distance)) {
return;
}
var normal = id && filter && Vec3.normalize(Vec3.subtract(avatar.position, myPosition));
var horizontal = normal && angleBetweenVectorsInPlane(normal, front, horizontalAngleNormal);
var vertical = normal && angleBetweenVectorsInPlane(normal, front, verticalAngleNormal);
if (id && filter && ((Math.abs(horizontal) > horizontalHalfAngle) || (Math.abs(vertical) > verticalHalfAngle))) {
return;
}
var avatarPalDatum = { var avatarPalDatum = {
displayName: avatar.sessionDisplayName, displayName: name,
userName: '', userName: '',
sessionId: id || '', sessionId: id || '',
audioLevel: 0.0, audioLevel: 0.0,
@ -289,10 +331,12 @@ function populateUserList(selectData) {
addAvatarNode(id); // No overlay for ourselves addAvatarNode(id); // No overlay for ourselves
// Everyone needs to see admin status. Username and fingerprint returns default constructor output if the requesting user isn't an admin. // Everyone needs to see admin status. Username and fingerprint returns default constructor output if the requesting user isn't an admin.
Users.requestUsernameFromID(id); Users.requestUsernameFromID(id);
avatarsOfInterest[id] = true;
} }
data.push(avatarPalDatum); data.push(avatarPalDatum);
print('PAL data:', JSON.stringify(avatarPalDatum)); print('PAL data:', JSON.stringify(avatarPalDatum));
}); });
conserveResources = Object.keys(avatarsOfInterest).length > 20;
sendToQml({ method: 'users', params: data }); sendToQml({ method: 'users', params: data });
if (selectData) { if (selectData) {
selectData[2] = true; selectData[2] = true;
@ -317,8 +361,8 @@ var pingPong = true;
function updateOverlays() { function updateOverlays() {
var eye = Camera.position; var eye = Camera.position;
AvatarList.getAvatarIdentifiers().forEach(function (id) { AvatarList.getAvatarIdentifiers().forEach(function (id) {
if (!id) { if (!id || !avatarsOfInterest[id]) {
return; // don't update ourself return; // don't update ourself, or avatars we're not interested in
} }
var avatar = AvatarList.getAvatar(id); var avatar = AvatarList.getAvatar(id);
if (!avatar) { if (!avatar) {

View file

@ -26,12 +26,19 @@
print("show tablet-ui"); print("show tablet-ui");
var DEFAULT_WIDTH = 0.4375; var DEFAULT_WIDTH = 0.4375;
var DEFAULT_HMD_TABLET_SCALE = 100; var DEFAULT_TABLET_SCALE = 100;
var HMD_TABLET_SCALE = Settings.getValue("hmdTabletScale") || DEFAULT_HMD_TABLET_SCALE; var toolbarMode = Tablet.getTablet("com.highfidelity.interface.tablet.system").toolbarMode;
UIWebTablet = new WebTablet("qml/hifi/tablet/TabletRoot.qml", DEFAULT_WIDTH * (HMD_TABLET_SCALE / 100), null, activeHand, true); var TABLET_SCALE = DEFAULT_TABLET_SCALE;
if (toolbarMode) {
TABLET_SCALE = Settings.getValue("desktopTabletScale") || DEFAULT_TABLET_SCALE;
} else {
TABLET_SCALE = Settings.getValue("hmdTabletScale") || DEFAULT_TABLET_SCALE;
}
UIWebTablet = new WebTablet("qml/hifi/tablet/TabletRoot.qml", DEFAULT_WIDTH * (TABLET_SCALE / 100), null, activeHand, true);
UIWebTablet.register(); UIWebTablet.register();
HMD.tabletID = UIWebTablet.tabletEntityID; HMD.tabletID = UIWebTablet.tabletEntityID;
HMD.homeButtonID = UIWebTablet.homeButtonEntity; HMD.homeButtonID = UIWebTablet.homeButtonID;
HMD.tabletScreenID = UIWebTablet.webOverlayID;
} }
function hideTabletUI() { function hideTabletUI() {
@ -48,6 +55,7 @@
UIWebTablet = null; UIWebTablet = null;
HMD.tabletID = null; HMD.tabletID = null;
HMD.homeButtonID = null; HMD.homeButtonID = null;
HMD.tabletScreenID = null;
} }
} }
@ -77,7 +85,7 @@
hideTabletUI(); hideTabletUI();
HMD.closeTablet(); HMD.closeTablet();
} else if (HMD.showTablet && !tabletShown && !toolbarMode) { } else if (HMD.showTablet && !tabletShown && !toolbarMode) {
UserActivityLogger.openedTablet(); UserActivityLogger.openedTablet(Settings.getValue("tabletVisibleToOthers"));
showTabletUI(); showTabletUI();
} else if (!HMD.showTablet && tabletShown) { } else if (!HMD.showTablet && tabletShown) {
UserActivityLogger.closedTablet(); UserActivityLogger.closedTablet();
@ -126,5 +134,6 @@
Entities.deleteEntity(HMD.tabletID); Entities.deleteEntity(HMD.tabletID);
HMD.tabletID = null; HMD.tabletID = null;
HMD.homeButtonID = null; HMD.homeButtonID = null;
HMD.tabletScreenID = null;
}); });
}()); // END LOCAL_SCOPE }()); // END LOCAL_SCOPE

View file

@ -0,0 +1,111 @@
# Shortbow
Shortbow is a wave-based archery game.
## Notes
There are several design decisions that are based on certain characteristics of the High Fidelity platform,
and in particular, [server entity scripts](https://wiki.highfidelity.com/wiki/Creating_Entity_Server_Scripts),
which are touched on below.
It is recommended that you understand the basics of client entity scripts and server entity scripts (and their
differences) if you plan on digging into the shortbow code.
* Client entity scripts end in `ClientEntity.js` and server entity scripts end in `ServerEntity.js`.
* Server entity scripts are not guaranteed to have access to *other* entities, and should not rely on it.
* You should not rely on using `Entities.getEntityProperties` to access the properties of entities
other than the entity that the server entity script is running on. This also applies to other
functions like `Entities.findEntities`. This means that something like the `ShortGameManager` (described below)
will not know the entity IDs of entities like the start button or scoreboard text entities, so it
has to find ways to work around that limitation.
* You can, however, use `Entities.editEntity` to edit other entities.
* *NOTE*: It is likely that this will change in the future, and server entity scripts will be able to
query the existence of other entities in some way. This will obviate the need for some of the workarounds
used in shortbow.
* The Entity Script Server (where server entity scripts) does not run a physics simulation
* Server entity scripts do not generate collision events like would be used with
`Script.addEventHandler(entityID, "collisionWithEntity", collideHandler)`.
* High Fidelity distributes its physics simulation to clients, so collisions need to be handled
there. In the case of enemies in shortbow, for instance, the collisions are handled in the
client entity script `enemeyClientEntity.js`.
* If no client is present to run the physics simulation, entities will not physically interact with other
entities.
* But, entities will still apply their basic physical properties.
## Shortbow Game Manager
This section describes both `shortbowServerEntity.js` and `shortbowGameManager.js`. The `shortbowServerEntity.js` script
exists on the main arena model entity, and is the parent of all of the other parts of the game, other than the
enemies and bows that are spawned during gameplay.
The `shortbowServerEntity.js` script is a server entity script that runs the shortbow game. The actual logic for
the game is stored inside `shortbowGameManager.js`, in the `ShortbowGameManager` prototype.
## Enemy Scripts
These scripts exist on each enemy that is spawned.
### enemyClientEntity.js
This script handles collision events on the client. There are two collisions events that it is interested in:
1. Collisions with arrows
1. Arrow entities have "projectile" in their name
1. Any other entity with "projectile" in its name could be used to destroy the enemy
1. Collisions with the gate that the enemies roll towards
1. The gate entity has "GateCollider" in its name
### enemyServerEntity.js
This script acts as a fail-safe to work around some of the limitations that are mentioned under [Notes](#notes).
In particular, if no client is running the physics simulation of an enemy entity, it may fall through the floor
of the arena or never have a collision event generated against the gate. A server entity script also
cannot access the properties of another entity, so it can't know if the entity has moved far away from
the arena.
To handle this, this script is used to periodically send heartbeats to the [ShortbowGameManager](#shortbow-game-manager)
that runs the game. The heartbeats include the position of the enemy. If the script that received the
heartbeats hasn't heard from `enemyServerEntity.js` in too long, or the entity has moved too far away, it
will remove it from it's local list of enemies that still need to be destroyed. This ensure that the game
doesn't get into a "hung" state where it's waiting for an enemy that will never escape through the gate or be
destroyed.
## Start Button
These scripts exist on the start button.
### startGameButtonClientEntity.js
This script sends a message to the [ShortbowGameManager](#shortbow-game-manager) to request that the game be started.
### startGameButtonServerEntity.js
When the shortbow game starts the start button is hidden, and when the shortbow game ends it is shown again.
As described in the [Notes](#notes), server entity scripts cannot access other entities, including their IDs.
Because of this, the [ShortbowGameManager](#shortbow-game-manager) has no way of knowing the id of the start button,
and thus being able to use `Entities.editEntity` to set its `visible` property to `true` or `false`. One way around
this, and is what is used here, is to use `Messages` on a common channel to send messages to a server entity script
on the start button to request that it be shown or hidden.
## Display (Scoreboard)
This script exists on each text entity that scoreboard is composed of. The scoreboard area is composed of a text entity for each of the following: Score, High Score, Wave, Lives.
### displayServerEntity.js
The same problem that exists for [startGameButtonServerEntity.js](#startgamebuttonserverentityjs) exists for
the text entities on the scoreboard. This script works around the problem in the same way, but instead of
receiving a message that tells it whether to hide or show itself, it receives a message that contains the
text to update the text entity with. For intance, the "lives" display entity will receive a message each
time a life is lost with the current number of lives.
## How is the "common channel" determined that is used in some of the client and server entity scripts?
Imagine that there are two instances of shortbow next to each. If the channel being used is always the same,
and not differentiated in some way between the two instances, a "start game" message sent from [startGameButtonClientEntity.js](#startgamebuttoncliententityjs)
on one game will be received and handled by both games, causing both of them to start. We need a way to create
a channel that is unique to the scripts that are running for a particular instance of shortbow.
All of the entities in shortbow, besides the enemy entities, are children of the main arena entity that
runs the game. All of the scripts on these entities can access their parentID, so they can use
a prefix plus the parentID to generate a unique channel for that instance of shortbow.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

View file

@ -0,0 +1,671 @@
//
// Created by Seth Alves on 2016-9-7
// 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
/* global MyAvatar, Vec3, Controller, Quat */
var GRAB_COMMUNICATIONS_SETTING = "io.highfidelity.isFarGrabbing";
setGrabCommunications = function setFarGrabCommunications(on) {
Settings.setValue(GRAB_COMMUNICATIONS_SETTING, on ? "on" : "");
}
getGrabCommunications = function getFarGrabCommunications() {
return !!Settings.getValue(GRAB_COMMUNICATIONS_SETTING, "");
}
// this offset needs to match the one in libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp:378
var GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }; // x = upward, y = forward, z = lateral
getGrabPointSphereOffset = function(handController) {
if (handController === Controller.Standard.RightHand) {
return GRAB_POINT_SPHERE_OFFSET;
}
return {
x: GRAB_POINT_SPHERE_OFFSET.x * -1,
y: GRAB_POINT_SPHERE_OFFSET.y,
z: GRAB_POINT_SPHERE_OFFSET.z
};
};
// controllerWorldLocation is where the controller would be, in-world, with an added offset
getControllerWorldLocation = function (handController, doOffset) {
var orientation;
var position;
var pose = Controller.getPoseValue(handController);
var valid = pose.valid;
var controllerJointIndex;
if (pose.valid) {
if (handController === Controller.Standard.RightHand) {
controllerJointIndex = MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND");
} else {
controllerJointIndex = MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_LEFTHAND");
}
orientation = Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(controllerJointIndex));
position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, MyAvatar.getAbsoluteJointTranslationInObjectFrame(controllerJointIndex)));
// add to the real position so the grab-point is out in front of the hand, a bit
if (doOffset) {
var offset = getGrabPointSphereOffset(handController);
position = Vec3.sum(position, Vec3.multiplyQbyV(orientation, offset));
}
} else if (!HMD.isHandControllerAvailable()) {
// NOTE: keep this offset in sync with scripts/system/controllers/handControllerPointer.js:493
var VERTICAL_HEAD_LASER_OFFSET = 0.1;
position = Vec3.sum(Camera.position, Vec3.multiplyQbyV(Camera.orientation, {x: 0, y: VERTICAL_HEAD_LASER_OFFSET, z: 0}));
orientation = Quat.multiply(Camera.orientation, Quat.angleAxis(-90, { x: 1, y: 0, z: 0 }));
valid = true;
}
return {position: position,
translation: position,
orientation: orientation,
rotation: orientation,
valid: valid};
};
//
//
//
//
//
//
// bow.js
//
// This script attaches to a bow that you can pick up with a hand controller.
// Created by James B. Pollack @imgntn on 10/19/2015
// Copyright 2015 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
//
/*global Script, Controller, SoundCache, Entities, getEntityCustomData, setEntityCustomData, MyAvatar, Vec3, Quat, Messages */
function getControllerLocation(controllerHand) {
var standardControllerValue =
(controllerHand === "right") ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
return getControllerWorldLocation(standardControllerValue, true);
};
(function() {
Script.include("/~/system/libraries/utils.js");
const NULL_UUID = "{00000000-0000-0000-0000-000000000000}";
const NOTCH_ARROW_SOUND_URL = Script.resolvePath('notch.wav');
const SHOOT_ARROW_SOUND_URL = Script.resolvePath('String_release2.L.wav');
const STRING_PULL_SOUND_URL = Script.resolvePath('Bow_draw.1.L.wav');
const ARROW_HIT_SOUND_URL = Script.resolvePath('Arrow_impact1.L.wav');
const ARROW_MODEL_URL = Script.resolvePath('arrow.fbx');
const ARROW_DIMENSIONS = {
x: 0.20,
y: 0.19,
z: 0.93
};
const MIN_ARROW_SPEED = 3.0;
const MAX_ARROW_SPEED = 30.0;
const ARROW_TIP_OFFSET = 0.47;
const ARROW_GRAVITY = {
x: 0,
y: -9.8,
z: 0
};
const ARROW_LIFETIME = 15; // seconds
const ARROW_PARTICLE_LIFESPAN = 2; // seconds
const TOP_NOTCH_OFFSET = 0.6;
const BOTTOM_NOTCH_OFFSET = 0.6;
const LINE_DIMENSIONS = {
x: 5.0,
y: 5.0,
z: 5.0
};
const DRAW_STRING_THRESHOLD = 0.80;
const DRAW_STRING_PULL_DELTA_HAPTIC_PULSE = 0.09;
const DRAW_STRING_MAX_DRAW = 0.7;
const MIN_ARROW_DISTANCE_FROM_BOW_REST = 0.2;
const MAX_ARROW_DISTANCE_FROM_BOW_REST = ARROW_DIMENSIONS.z - 0.2;
const MIN_HAND_DISTANCE_FROM_BOW_TO_KNOCK_ARROW = 0.25;
const MIN_ARROW_DISTANCE_FROM_BOW_REST_TO_SHOOT = 0.30;
const NOTCH_OFFSET_FORWARD = 0.08;
const NOTCH_OFFSET_UP = 0.035;
const SHOT_SCALE = {
min1: 0.0,
max1: 0.6,
min2: 1.0,
max2: 15.0
};
const USE_DEBOUNCE = false;
const TRIGGER_CONTROLS = [
Controller.Standard.LT,
Controller.Standard.RT,
];
function interval() {
var lastTime = new Date().getTime();
return function getInterval() {
var newTime = new Date().getTime();
var delta = newTime - lastTime;
lastTime = newTime;
return delta;
};
}
var checkInterval = interval();
var _this;
function Bow() {
_this = this;
return;
}
const STRING_NAME = 'Hifi-Bow-String';
const ARROW_NAME = 'Hifi-Arrow-projectile';
const STATE_IDLE = 0;
const STATE_ARROW_GRABBED = 1;
Bow.prototype = {
topString: null,
aiming: false,
arrowTipPosition: null,
preNotchString: null,
stringID: null,
arrow: null,
stringData: {
currentColor: {
red: 255,
green: 255,
blue: 255
}
},
state: STATE_IDLE,
sinceLastUpdate: 0,
preload: function(entityID) {
this.entityID = entityID;
this.stringPullSound = SoundCache.getSound(STRING_PULL_SOUND_URL);
this.shootArrowSound = SoundCache.getSound(SHOOT_ARROW_SOUND_URL);
this.arrowHitSound = SoundCache.getSound(ARROW_HIT_SOUND_URL);
this.arrowNotchSound = SoundCache.getSound(NOTCH_ARROW_SOUND_URL);
var userData = Entities.getEntityProperties(this.entityID, ["userData"]).userData;
print(userData);
this.userData = JSON.parse(userData);
this.stringID = null;
},
unload: function() {
Messages.sendLocalMessage('Hifi-Hand-Disabler', "none");
Entities.deleteEntity(this.arrow);
},
startEquip: function(entityID, args) {
this.hand = args[0];
this.bowHand = args[0];
this.stringHand = this.bowHand === "right" ? "left" : "right";
Entities.editEntity(_this.entityID, {
collidesWith: "",
});
var data = getEntityCustomData('grabbableKey', this.entityID, {});
data.grabbable = false;
setEntityCustomData('grabbableKey', this.entityID, data);
this.initString();
var self = this;
this.updateIntervalID = Script.setInterval(function() { self.update(); }, 11);
},
getStringHandPosition: function() {
return getControllerLocation(this.stringHand).position;
},
releaseEquip: function(entityID, args) {
Script.clearInterval(this.updateIntervalID);
this.updateIntervalID = null;
Messages.sendLocalMessage('Hifi-Hand-Disabler', "none");
var data = getEntityCustomData('grabbableKey', this.entityID, {});
data.grabbable = true;
setEntityCustomData('grabbableKey', this.entityID, data);
Entities.deleteEntity(this.arrow);
this.resetStringToIdlePosition();
this.destroyArrow();
Entities.editEntity(_this.entityID, {
collidesWith: "static,dynamic,kinematic,otherAvatar,myAvatar"
});
},
update: function(entityID) {
var self = this;
self.deltaTime = checkInterval();
//debounce during debugging -- maybe we're updating too fast?
if (USE_DEBOUNCE === true) {
self.sinceLastUpdate = self.sinceLastUpdate + self.deltaTime;
if (self.sinceLastUpdate > 60) {
self.sinceLastUpdate = 0;
} else {
return;
}
}
//invert the hands because our string will be held with the opposite hand of the first one we pick up the bow with
this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[(this.hand === 'left') ? 1 : 0]);
this.bowProperties = Entities.getEntityProperties(this.entityID, ['position', 'rotation']);
var notchPosition = this.getNotchPosition(this.bowProperties);
var stringHandPosition = this.getStringHandPosition();
var handToNotch = Vec3.subtract(notchPosition, stringHandPosition);
var pullBackDistance = Vec3.length(handToNotch);
if (this.state === STATE_IDLE) {
this.pullBackDistance = 0;
this.resetStringToIdlePosition();
if (this.triggerValue >= DRAW_STRING_THRESHOLD && pullBackDistance < MIN_HAND_DISTANCE_FROM_BOW_TO_KNOCK_ARROW && !this.backHandBusy) {
//the first time aiming the arrow
var handToDisable = (this.hand === 'right' ? 'left' : 'right');
this.state = STATE_ARROW_GRABBED;
}
}
if (this.state === STATE_ARROW_GRABBED) {
if (!this.arrow) {
var handToDisable = (this.hand === 'right' ? 'left' : 'right');
Messages.sendLocalMessage('Hifi-Hand-Disabler', handToDisable);
this.playArrowNotchSound();
this.arrow = this.createArrow();
this.playStringPullSound();
}
if (this.triggerValue < DRAW_STRING_THRESHOLD) {
if (pullBackDistance >= (MIN_ARROW_DISTANCE_FROM_BOW_REST_TO_SHOOT)) {
// The arrow has been pulled far enough back that we can release it
Messages.sendLocalMessage('Hifi-Hand-Disabler', "none");
this.updateArrowPositionInNotch(true, true);
this.arrow = null;
this.state = STATE_IDLE;
this.resetStringToIdlePosition();
} else {
// The arrow has not been pulled far enough back so we just remove the arrow
Messages.sendLocalMessage('Hifi-Hand-Disabler', "none");
Entities.deleteEntity(this.arrow);
this.arrow = null;
this.state = STATE_IDLE;
this.resetStringToIdlePosition();
}
} else {
this.updateArrowPositionInNotch(false, true);
this.updateString();
}
}
},
destroyArrow: function() {
var children = Entities.getChildrenIDs(this.entityID);
children.forEach(function(childID) {
var childName = Entities.getEntityProperties(childID, ["name"]).name;
if (childName == ARROW_NAME) {
Entities.deleteEntity(childID);
// Continue iterating through children in case we've ended up in
// a bad state where there are multiple arrows.
}
});
},
createArrow: function() {
this.playArrowNotchSound();
var arrow = Entities.addEntity({
name: ARROW_NAME,
type: 'Model',
modelURL: ARROW_MODEL_URL,
shapeType: 'simple-compound',
dimensions: ARROW_DIMENSIONS,
position: this.bowProperties.position,
parentID: this.entityID,
dynamic: false,
collisionless: true,
collisionSoundURL: ARROW_HIT_SOUND_URL,
damping: 0.01,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
},
creatorSessionUUID: MyAvatar.sessionUUID
})
});
var makeArrowStick = function(entityA, entityB, collision) {
Entities.editEntity(entityA, {
localAngularVelocity: {
x: 0,
y: 0,
z: 0
},
localVelocity: {
x: 0,
y: 0,
z: 0
},
gravity: {
x: 0,
y: 0,
z: 0
},
parentID: entityB,
dynamic: false,
collisionless: true,
collidesWith: ""
});
Script.removeEventHandler(arrow, "collisionWithEntity", makeArrowStick);
};
Script.addEventHandler(arrow, "collisionWithEntity", makeArrowStick);
return arrow;
},
initString: function() {
// Check for existence of string
var children = Entities.getChildrenIDs(this.entityID);
children.forEach(function(childID) {
var childName = Entities.getEntityProperties(childID, ["name"]).name;
if (childName == STRING_NAME) {
this.stringID = childID;
}
});
// If thie string wasn't found, create it
if (this.stringID === null) {
this.stringID = Entities.addEntity({
collisionless: true,
dimensions: { "x": 5, "y": 5, "z": 5 },
ignoreForCollisions: 1,
linePoints: [ { "x": 0, "y": 0, "z": 0 }, { "x": 0, "y": -1.2, "z": 0 } ],
lineWidth: 5,
color: { red: 153, green: 102, blue: 51 },
name: STRING_NAME,
parentID: this.entityID,
localPosition: { "x": 0, "y": 0.6, "z": 0.1 },
localRotation: { "w": 1, "x": 0, "y": 0, "z": 0 },
type: 'Line',
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
})
});
}
this.resetStringToIdlePosition();
},
// This resets the string to a straight line
resetStringToIdlePosition: function() {
Entities.editEntity(this.stringID, {
linePoints: [ { "x": 0, "y": 0, "z": 0 }, { "x": 0, "y": -1.2, "z": 0 } ],
lineWidth: 5,
localPosition: { "x": 0, "y": 0.6, "z": 0.1 },
localRotation: { "w": 1, "x": 0, "y": 0, "z": 0 },
});
},
updateString: function() {
var upVector = Quat.getUp(this.bowProperties.rotation);
var upOffset = Vec3.multiply(upVector, TOP_NOTCH_OFFSET);
var downVector = Vec3.multiply(-1, Quat.getUp(this.bowProperties.rotation));
var downOffset = Vec3.multiply(downVector, BOTTOM_NOTCH_OFFSET);
var backOffset = Vec3.multiply(-0.1, Quat.getFront(this.bowProperties.rotation));
var topStringPosition = Vec3.sum(this.bowProperties.position, upOffset);
this.topStringPosition = Vec3.sum(topStringPosition, backOffset);
var bottomStringPosition = Vec3.sum(this.bowProperties.position, downOffset);
this.bottomStringPosition = Vec3.sum(bottomStringPosition, backOffset);
var stringProps = Entities.getEntityProperties(this.stringID, ['position', 'rotation']);
var handPositionLocal = Vec3.subtract(this.arrowRearPosition, stringProps.position);
handPositionLocal = Vec3.multiplyQbyV(Quat.inverse(stringProps.rotation), handPositionLocal);
var linePoints = [
{ x: 0, y: 0, z: 0 },
handPositionLocal,
{ x: 0, y: -1.2, z: 0 },
];
Entities.editEntity(this.stringID, {
linePoints: linePoints,
});
},
getNotchPosition: function(bowProperties) {
var frontVector = Quat.getFront(bowProperties.rotation);
var notchVectorForward = Vec3.multiply(frontVector, NOTCH_OFFSET_FORWARD);
var upVector = Quat.getUp(bowProperties.rotation);
var notchVectorUp = Vec3.multiply(upVector, NOTCH_OFFSET_UP);
var notchPosition = Vec3.sum(bowProperties.position, notchVectorForward);
notchPosition = Vec3.sum(notchPosition, notchVectorUp);
return notchPosition;
},
updateArrowPositionInNotch: function(shouldReleaseArrow, doHapticPulses) {
//set the notch that the arrow should go through
var notchPosition = this.getNotchPosition(this.bowProperties);
//set the arrow rotation to be between the notch and other hand
var stringHandPosition = this.getStringHandPosition();
var handToNotch = Vec3.subtract(notchPosition, stringHandPosition);
var arrowRotation = Quat.rotationBetween(Vec3.FRONT, handToNotch);
var backHand = this.hand === 'left' ? 1 : 0;
var pullBackDistance = Vec3.length(handToNotch);
// pulse as arrow is drawn
if (doHapticPulses &&
Math.abs(pullBackDistance - this.pullBackDistance) > DRAW_STRING_PULL_DELTA_HAPTIC_PULSE) {
Controller.triggerHapticPulse(1, 20, backHand);
this.pullBackDistance = pullBackDistance;
}
if (pullBackDistance > DRAW_STRING_MAX_DRAW) {
pullBackDistance = DRAW_STRING_MAX_DRAW;
}
var handToNotchDistance = Vec3.length(handToNotch);
var stringToNotchDistance = Math.max(MIN_ARROW_DISTANCE_FROM_BOW_REST, Math.min(MAX_ARROW_DISTANCE_FROM_BOW_REST, handToNotchDistance));
var halfArrowVec = Vec3.multiply(Vec3.normalize(handToNotch), ARROW_DIMENSIONS.z / 2.0);
var offset = Vec3.subtract(notchPosition, Vec3.multiply(Vec3.normalize(handToNotch), stringToNotchDistance - ARROW_DIMENSIONS.z / 2.0));
var arrowPosition = offset;
// Set arrow rear position
var frontVector = Quat.getFront(arrowRotation);
var frontOffset = Vec3.multiply(frontVector, -ARROW_TIP_OFFSET);
var arrorRearPosition = Vec3.sum(arrowPosition, frontOffset);
this.arrowRearPosition = arrorRearPosition;
//if we're not shooting, we're updating the arrow's orientation
if (shouldReleaseArrow !== true) {
Entities.editEntity(this.arrow, {
position: arrowPosition,
rotation: arrowRotation
});
} else {
//shoot the arrow
var arrowAge = Entities.getEntityProperties(this.arrow, ["age"]).age;
//scale the shot strength by the distance you've pulled the arrow back and set its release velocity to be
// in the direction of the v
var arrowForce = this.scaleArrowShotStrength(stringToNotchDistance);
var handToNotchNorm = Vec3.normalize(handToNotch);
var releaseVelocity = Vec3.multiply(handToNotchNorm, arrowForce);
//make the arrow physical, give it gravity, a lifetime, and set our velocity
var arrowProperties = {
dynamic: true,
collisionless: false,
collidesWith: "static,dynamic,otherAvatar", // workaround: not with kinematic --> no collision with bow
velocity: releaseVelocity,
parentID: NULL_UUID,
gravity: ARROW_GRAVITY,
lifetime: arrowAge + ARROW_LIFETIME,
};
// add a particle effect to make the arrow easier to see as it flies
var arrowParticleProperties = {
accelerationSpread: { x: 0, y: 0, z: 0 },
alpha: 1,
alphaFinish: 0,
alphaSpread: 0,
alphaStart: 0.3,
azimuthFinish: 3.1,
azimuthStart: -3.14159,
color: { red: 255, green: 255, blue: 255 },
colorFinish: { red: 255, green: 255, blue: 255 },
colorSpread: { red: 0, green: 0, blue: 0 },
colorStart: { red: 255, green: 255, blue: 255 },
emitAcceleration: { x: 0, y: 0, z: 0 },
emitDimensions: { x: 0, y: 0, z: 0 },
emitOrientation: { x: -0.7, y: 0.0, z: 0.0, w: 0.7 },
emitRate: 0.01,
emitSpeed: 0,
emitterShouldTrail: 0,
isEmitting: 1,
lifespan: ARROW_PARTICLE_LIFESPAN,
lifetime: ARROW_PARTICLE_LIFESPAN + 1,
maxParticles: 1000,
name: 'arrow-particles',
parentID: this.arrow,
particleRadius: 0.132,
polarFinish: 0,
polarStart: 0,
radiusFinish: 0.35,
radiusSpread: 0,
radiusStart: 0.132,
speedSpread: 0,
textures: Script.resolvePath('arrow-sparkle.png'),
type: 'ParticleEffect'
};
Entities.addEntity(arrowParticleProperties);
// actually shoot the arrow
Entities.editEntity(this.arrow, arrowProperties);
// play the sound of a shooting arrow
this.playShootArrowSound();
Entities.addAction("travel-oriented", this.arrow, {
forward: { x: 0, y: 0, z: -1 },
angularTimeScale: 0.1,
tag: "arrow from hifi-bow",
ttl: ARROW_LIFETIME
});
}
},
scaleArrowShotStrength: function(value) {
var percentage = (value - MIN_ARROW_DISTANCE_FROM_BOW_REST)
/ (MAX_ARROW_DISTANCE_FROM_BOW_REST - MIN_ARROW_DISTANCE_FROM_BOW_REST);
return MIN_ARROW_SPEED + (percentage * (MAX_ARROW_SPEED - MIN_ARROW_SPEED)) ;
},
playStringPullSound: function() {
var audioProperties = {
volume: 0.10,
position: this.bowProperties.position
};
this.stringPullInjector = Audio.playSound(this.stringPullSound, audioProperties);
},
playShootArrowSound: function(sound) {
var audioProperties = {
volume: 0.15,
position: this.bowProperties.position
};
Audio.playSound(this.shootArrowSound, audioProperties);
},
playArrowNotchSound: function() {
var audioProperties = {
volume: 0.15,
position: this.bowProperties.position
};
Audio.playSound(this.arrowNotchSound, audioProperties);
},
changeStringPullSoundVolume: function(pullBackDistance) {
var audioProperties = {
volume: this.scaleSoundVolume(pullBackDistance),
position: this.bowProperties.position
};
this.stringPullInjector.options = audioProperties;
},
scaleSoundVolume: function(value) {
var min1 = SHOT_SCALE.min1;
var max1 = SHOT_SCALE.max1;
var min2 = 0;
var max2 = 0.2;
return min2 + (max2 - min2) * ((value - min1) / (max1 - min1));
},
handleMessages: function(channel, message, sender) {
if (sender !== MyAvatar.sessionUUID) {
return;
}
if (channel !== 'Hifi-Object-Manipulation') {
return;
}
try {
var data = JSON.parse(message);
var action = data.action;
var hand = data.joint;
var isBackHand = ((_this.hand == "left" && hand == "RightHand") ||
(_this.hand == "right" && hand == "LeftHand"));
if ((action == "equip" || action == "grab") && isBackHand) {
_this.backHandBusy = true;
}
if (action == "release" && isBackHand) {
_this.backHandBusy = false;
}
} catch (e) {
print("WARNING: bow.js -- error parsing Hifi-Object-Manipulation message: " + message);
}
}
};
var bow = new Bow();
Messages.subscribe('Hifi-Object-Manipulation');
Messages.messageReceived.connect(bow.handleMessages);
return bow;
});

View file

@ -0,0 +1,44 @@
{
"Entities": [
{
"clientOnly": 0,
"collisionsWillMove": 1,
"compoundShapeURL": "http://mpassets.highfidelity.com/a9221d01-95eb-4b2e-85e5-d9e0970a7f51-v1/bow_collision_hull.obj",
"created": "2017-02-14T18:54:38Z",
"dimensions": {
"x": 0.039999999105930328,
"y": 1.2999999523162842,
"z": 0.20000000298023224
},
"dynamic": 1,
"gravity": {
"x": 0,
"y": -9.8000001907348633,
"z": 0
},
"id": "{73954924-2e18-4787-91a7-092c2afb6242}",
"lastEdited": 1487098438422164,
"lastEditedBy": "{d2da5e17-9125-414d-ac4e-cd7fba6c22f8}",
"modelURL": "http://mpassets.highfidelity.com/a9221d01-95eb-4b2e-85e5-d9e0970a7f51-v1/bow-deadly.fbx",
"name": "WG.Hifi-Bow",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"queryAACube": {
"scale": 1.3159027099609375,
"x": -0.65795135498046875,
"y": -0.65795135498046875,
"z": -0.65795135498046875
},
"rotation": {
"w": 0.9717707633972168,
"x": 0.15437555313110352,
"y": -0.10472267866134644,
"z": -0.14421302080154419
},
"script": "http://mpassets.highfidelity.com/a9221d01-95eb-4b2e-85e5-d9e0970a7f51-v1/bow.js",
"shapeType": "compound",
"type": "Model",
"userData": "{\"grabbableKey\":{\"grabbable\":true},\"wearable\":{\"joints\":{\"RightHand\":[{\"x\":0.0813,\"y\":0.0452,\"z\":0.0095},{\"x\":-0.3946,\"y\":-0.6604,\"z\":0.4748,\"w\":-0.4275}],\"LeftHand\":[{\"x\":-0.0881,\"y\":0.0259,\"z\":0.0159},{\"x\":0.4427,\"y\":-0.6519,\"z\":0.4592,\"w\":0.4099}]}}}"
}
],
"Version": 67
}

View file

@ -0,0 +1,32 @@
{
"Entities": [ {
"collisionsWillMove": 1,
"compoundShapeURL": "http://hifi-content.s3.amazonaws.com/caitlyn/production/bow/bow_collision_hull.obj",
"created": "2016-09-01T23:57:55Z",
"dimensions": {
"x": 0.039999999105930328,
"y": 1.2999999523162842,
"z": 0.20000000298023224
},
"dynamic": 1,
"gravity": {
"x": 0,
"y": -1,
"z": 0
},
"modelURL": "http://hifi-content.s3.amazonaws.com/caitlyn/production/bow/bow-deadly.fbx",
"name": "Hifi-Bow",
"rotation": {
"w": 0.9718012809753418,
"x": 0.15440607070922852,
"y": -0.10469216108322144,
"z": -0.14418250322341919
},
"script": "http://hifi-content.s3.amazonaws.com/caitlyn/production/bow/bow.js",
"shapeType": "compound",
"type": "Model",
"userData": "{\"grabbableKey\":{\"grabbable\":true},\"wearable\":{\"joints\":{\"RightHand\":[{\"x\":0.0813,\"y\":0.0452,\"z\":0.0095},{\"x\":-0.3946,\"y\":-0.6604,\"z\":0.4748,\"w\":-0.4275}],\"LeftHand\":[{\"x\":-0.0881,\"y\":0.0259,\"z\":0.0159},{\"x\":0.4427,\"y\":-0.6519,\"z\":0.4592,\"w\":0.4099}]}}}"
}
],
"Version": 57
}

View file

@ -0,0 +1,21 @@
v -0.016461 -0.431491 -0.033447
v -0.007624 0.437384 -0.046243
v 0.011984 -0.424659 -0.03691
v 0.015514 0.425913 -0.028648
v -0.010788 -0.421429 0.093711
v 0.007135 -0.423115 0.098735
v -0.010208 0.425558 0.096005
v 0.006734 0.43913 0.088902
f 1 2 3
f 3 2 4
f 5 6 7
f 7 6 8
f 1 5 2
f 2 5 7
f 3 4 6
f 6 4 8
f 1 3 5
f 5 3 6
f 2 7 4
f 4 7 8

Binary file not shown.

View file

@ -0,0 +1,67 @@
//
// Created by Ryan Huffman on 1/10/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var leftHandPosition = {
"x": 0,
"y": 0.0559,
"z": 0.0159
};
var leftHandRotation = Quat.fromPitchYawRollDegrees(90, -90, 0);
var rightHandPosition = Vec3.multiplyVbyV(leftHandPosition, { x: -1, y: 0, z: 0 });
var rightHandRotation = Quat.fromPitchYawRollDegrees(90, 90, 0);
var userData = {
"grabbableKey": {
"grabbable": true
},
"wearable": {
"joints": {
"LeftHand": [
leftHandPosition,
leftHandRotation
],
"RightHand": [
rightHandPosition,
rightHandRotation
]
}
}
};
var id = Entities.addEntity({
"position": MyAvatar.position,
"collisionsWillMove": 1,
"compoundShapeURL": Script.resolvePath("bow_collision_hull.obj"),
"created": "2016-09-01T23:57:55Z",
"dimensions": {
"x": 0.039999999105930328,
"y": 1.2999999523162842,
"z": 0.20000000298023224
},
"dynamic": 1,
"gravity": {
"x": 0,
"y": -9.8,
"z": 0
},
"modelURL": Script.resolvePath("bow-deadly.fbx"),
"name": "Hifi-Bow",
"rotation": {
"w": 0.9718012809753418,
"x": 0.15440607070922852,
"y": -0.10469216108322144,
"z": -0.14418250322341919
},
"script": Script.resolvePath("bow.js"),
"shapeType": "compound",
"type": "Model",
"userData": "{\"grabbableKey\":{\"grabbable\":true},\"wearable\":{\"joints\":{\"RightHand\":[{\"x\":0.0813,\"y\":0.0452,\"z\":0.0095},{\"x\":-0.3946,\"y\":-0.6604,\"z\":0.4748,\"w\":-0.4275}],\"LeftHand\":[{\"x\":-0.0881,\"y\":0.0259,\"z\":0.0159},{\"x\":0.4427,\"y\":-0.6519,\"z\":0.4592,\"w\":0.4099}]}}}",
"lifetime": 600
});
print("Created bow:", id);

View file

@ -0,0 +1,61 @@
//
// Created by Ryan Huffman on 1/10/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* globals utils */
(function() {
Script.include('utils.js');
function Enemy() {
}
Enemy.prototype = {
preload: function(entityID) {
this.entityID = entityID;
// To avoid sending extraneous messages and checking entities that we've already
// seen, we keep track of which entities we've collided with previously.
this.entityIDsThatHaveCollidedWithMe = [];
Script.addEventHandler(entityID, "collisionWithEntity", this.onCollide.bind(this));
var userData = Entities.getEntityProperties(this.entityID, 'userData').userData;
var data = utils.parseJSON(userData);
if (data !== undefined && data.gameChannel !== undefined) {
this.gameChannel = data.gameChannel;
} else {
print("enemyEntity.js | ERROR: userData does not contain a game channel and/or team number");
}
},
onCollide: function(entityA, entityB, collision) {
if (this.entityIDsThatHaveCollidedWithMe.indexOf(entityB) > -1) {
return;
}
this.entityIDsThatHaveCollidedWithMe.push(entityB);
var colliderName = Entities.getEntityProperties(entityB, 'name').name;
if (colliderName.indexOf("projectile") > -1) {
Messages.sendMessage(this.gameChannel, JSON.stringify({
type: "enemy-killed",
entityID: this.entityID,
position: Entities.getEntityProperties(this.entityID, 'position').position
}));
Entities.deleteEntity(this.entityID);
} else if (colliderName.indexOf("GateCollider") > -1) {
Messages.sendMessage(this.gameChannel, JSON.stringify({
type: "enemy-escaped",
entityID: this.entityID,
position: Entities.getEntityProperties(this.entityID, 'position').position
}));
Entities.deleteEntity(this.entityID);
}
}
};
return new Enemy();
});

View file

@ -0,0 +1,41 @@
//
// Created by Ryan Huffman on 1/10/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* globals utils */
(function() {
Script.include('utils.js');
function Enemy() {
}
Enemy.prototype = {
preload: function(entityID) {
this.entityID = entityID;
var userData = Entities.getEntityProperties(this.entityID, 'userData').userData;
var data = utils.parseJSON(userData);
if (data !== undefined && data.gameChannel !== undefined) {
this.gameChannel = data.gameChannel;
} else {
print("enemyServerEntity.js | ERROR: userData does not contain a game channel and/or team number");
}
var self = this;
this.heartbeatTimerID = Script.setInterval(function() {
Messages.sendMessage(self.gameChannel, JSON.stringify({
type: "enemy-heartbeat",
entityID: self.entityID,
position: Entities.getEntityProperties(self.entityID, 'position').position
}));
}, 1000);
},
unload: function() {
Script.clearInterval(this.heartbeatTimerID);
}
};
return new Enemy();
});

View file

@ -0,0 +1,877 @@
//
// Created by Ryan Huffman on 1/10/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* globals SHORTBOW_ENTITIES:true */
// This is a copy of the data in shortbow.json, which is an export of the shortbow
// scene.
//
// Because .json can't be Script.include'd directly, the contents are copied over
// to here and exposed as a global variable.
//
SHORTBOW_ENTITIES =
{
"Entities": [
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{02f39515-cab4-41d5-b315-5fb41613f844}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708750446,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -5.1684012413024902,
"y": 0.54034698009490967,
"z": -11.257695198059082
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": -1.3604279756546021,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"backgroundColor": {
"blue": 65,
"green": 78,
"red": 82
},
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 2,
"y": 0.69999998807907104,
"z": 0.0099999997764825821
},
"id": "{3eae601e-3c6e-49ab-8f40-dedee32f7573}",
"lastEdited": 1487894036038423,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"lineHeight": 0.5,
"name": "SB.DisplayScore",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -8.0707607269287109,
"y": 1.5265679359436035,
"z": -9.5913219451904297
},
"queryAACube": {
"scale": 2.118985652923584,
"x": -5.1109838485717773,
"y": -1803.69189453125,
"z": -26.774648666381836
},
"rotation": {
"w": 0.70708787441253662,
"x": -1.52587890625e-05,
"y": 0.70708787441253662,
"z": -1.52587890625e-05
},
"text": "0",
"type": "Text",
"userData": "{\"displayType\":\"score\"}"
},
{
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.71920669078826904,
"y": 3.3160061836242676,
"z": 2.2217941284179688
},
"id": "{04288f77-64df-4323-ac38-9c1960a393a5}",
"lastEdited": 1487893058314990,
"lastEditedBy": "{fce8028a-4bac-43e8-96ff-4c7286ea4ab3}",
"modelURL": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/models/shortbow-button.fbx",
"name": "SB.StartButton",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -9.8358345031738281,
"y": 0.45674961805343628,
"z": -13.044205665588379
},
"queryAACube": {
"scale": 4.0558013916015625,
"x": -7.844393253326416,
"y": -1805.730224609375,
"z": -31.195960998535156
},
"rotation": {
"w": 1,
"x": 1.52587890625e-05,
"y": 1.52587890625e-05,
"z": 1.52587890625e-05
},
"script": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/startGameButtonClientEntity.js",
"shapeType": "static-mesh",
"type": "Model",
"userData": "{\"grabbableKey\":{\"wantsTrigger\":true}}"
},
{
"backgroundColor": {
"blue": 65,
"green": 78,
"red": 82
},
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 2,
"y": 0.69999998807907104,
"z": 0.0099999997764825821
},
"id": "{1196096f-bcc9-4b19-970d-605113474c1b}",
"lastEdited": 1487894037900323,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"lineHeight": 0.5,
"name": "SB.DisplayHighScore",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -8.0707607269287109,
"y": 0.26189804077148438,
"z": -9.5913219451904297
},
"queryAACube": {
"scale": 2.118985652923584,
"x": -5.11102294921875,
"y": -1804.95654296875,
"z": -26.77461051940918
},
"rotation": {
"w": 0.70708787441253662,
"x": -1.52587890625e-05,
"y": 0.70708787441253662,
"z": -1.52587890625e-05
},
"text": "0",
"type": "Text",
"userData": "{\"displayType\":\"highscore\"}"
},
{
"backgroundColor": {
"blue": 65,
"green": 78,
"red": 82
},
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 1.4120937585830688,
"y": 0.71569448709487915,
"z": 0.0099999997764825821
},
"id": "{293c294d-1df5-461e-82a3-66abee852d44}",
"lastEdited": 1487894033695485,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"lineHeight": 0.5,
"name": "SB.DisplayWave",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -8.0707607269287109,
"y": 1.5265679359436035,
"z": -7.2889409065246582
},
"queryAACube": {
"scale": 1.5831384658813477,
"x": -4.8431310653686523,
"y": -1803.4239501953125,
"z": -24.204343795776367
},
"rotation": {
"w": 0.70708787441253662,
"x": -1.52587890625e-05,
"y": 0.70708787441253662,
"z": -1.52587890625e-05
},
"text": "0",
"type": "Text",
"userData": "{\"displayType\":\"wave\"}"
},
{
"backgroundColor": {
"blue": 65,
"green": 78,
"red": 82
},
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 1.4120937585830688,
"y": 0.71569448709487915,
"z": 0.0099999997764825821
},
"id": "{379afa7b-c668-4c4e-b290-e5c37fb3440c}",
"lastEdited": 1487893055310428,
"lastEditedBy": "{fce8028a-4bac-43e8-96ff-4c7286ea4ab3}",
"lineHeight": 0.5,
"name": "SB.DisplayLives",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -8.0707607269287109,
"y": 0.26189804077148438,
"z": -7.2889409065246582
},
"queryAACube": {
"scale": 1.5831384658813477,
"x": -4.8431692123413086,
"y": -1804.6885986328125,
"z": -24.204303741455078
},
"rotation": {
"w": 0.70708787441253662,
"x": -1.52587890625e-05,
"y": 0.70708787441253662,
"z": -1.52587890625e-05
},
"text": "0",
"type": "Text",
"userData": "{\"displayType\":\"lives\"}"
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{760e81a1-a804-4f5e-9769-393d021fc8fe}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440234633,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -1.89238440990448,
"y": -5.3368110656738281,
"z": 11.512755393981934
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 1.9147146940231323,
"y": -1809.7066650390625,
"z": -4.8219971656799316
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{0a76d0ac-6353-467b-8edc-56417d5a987c}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440235124,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 3.6569130420684814,
"y": -5.3365960121154785,
"z": 10.01292610168457
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 7.4640579223632812,
"y": -1809.7066650390625,
"z": -6.3216567039489746
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{f8549c8a-e646-4feb-bbaf-70e7d5be755a}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440235339,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 8.8902750015258789,
"y": -5.3364419937133789,
"z": 10.195274353027344
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 12.697414398193359,
"y": -1809.7066650390625,
"z": -6.1391491889953613
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{f3aea4ae-4445-4a2d-8d61-e9fd72f04008}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708751269,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -2.5027251243591309,
"y": 0.54042834043502808,
"z": -11.257777214050293
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 1.3052481412887573,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{cc1ac907-124b-4372-8c4c-82d175546725}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708751135,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 2.7972855567932129,
"y": 0.54059004783630371,
"z": -11.257938385009766
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 6.6052589416503906,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{e25ce690-e267-4c51-80a0-f63a72474b8a}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708751527,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 0.17114110291004181,
"y": 0.54050993919372559,
"z": -11.257858276367188
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 3.979114294052124,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{91ee2285-38f8-4795-b6bc-7abc8dcde07c}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708750806,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 5.4656705856323242,
"y": 0.54067152738571167,
"z": -11.258020401000977
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 9.2736434936523438,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{d81e5fae-8a8d-4186-bbd2-0c3ae737b0f2}",
"ignoreForCollisions": 1,
"lastEdited": 1487892552671000,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 9.6099967956542969,
"y": 0.64012420177459717,
"z": -9.9802846908569336
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 13.417934417724609,
"y": -1803.730712890625,
"z": -26.314868927001953
},
"rotation": {
"w": 0.22495110332965851,
"x": -2.9734959753113799e-05,
"y": 0.97437006235122681,
"z": 2.9735869247815572e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{7056e21e-bce6-4c4b-bbca-36bea1dce303}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708750993,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 8.1799373626708984,
"y": 0.54075431823730469,
"z": -11.258102416992188
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 11.987911224365234,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{ed073620-e304-4b8e-b12a-5371b595bbf6}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440234415,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -2.3618791103363037,
"y": -2.0691573619842529,
"z": 11.254574775695801
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 1.4453276395797729,
"y": -1806.43896484375,
"z": -5.0802912712097168
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{32ed7820-c386-4da1-b676-7e63762861a3}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440234854,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 0.64757472276687622,
"y": -2.5217375755310059,
"z": 10.08248233795166
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 4.454803466796875,
"y": -1806.8917236328125,
"z": -6.2522788047790527
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 26.619264602661133,
"y": 14.24090576171875,
"z": 39.351066589355469
},
"id": "{d4c8f577-944d-4d50-ac85-e56387c0ef0a}",
"lastEdited": 1487892440231278,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"modelURL": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/models/shortbow-platform.fbx",
"name": "SB.Platform",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 0.097909502685070038,
"y": -1.0163799524307251,
"z": 2.0321114063262939
},
"queryAACube": {
"scale": 49.597328186035156,
"x": -20.681917190551758,
"y": -1829.9739990234375,
"z": -38.890060424804688
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shapeType": "static-mesh",
"type": "Model"
},
{
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 23.341892242431641,
"y": 12.223045349121094,
"z": 32.012016296386719
},
"friction": 1,
"id": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"lastEdited": 1487892440231832,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"modelURL": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/models/shortbow-scoreboard.fbx",
"name": "SB.Scoreboard",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"queryAACube": {
"scale": 41.461017608642578,
"x": -20.730508804321289,
"y": -20.730508804321289,
"z": -20.730508804321289
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"serverScripts": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/shortbowServerEntity.js",
"shapeType": "static-mesh",
"type": "Model"
},
{
"clientOnly": 0,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 15.710711479187012,
"y": 4.7783288955688477,
"z": 1.6129581928253174
},
"id": "{84cdff6e-a68d-4bbf-8660-2d6a8c2f1fd0}",
"lastEdited": 1487892440231522,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.GateCollider",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 0.31728419661521912,
"y": -4.3002614974975586,
"z": -12.531644821166992
},
"queryAACube": {
"scale": 16.50031852722168,
"x": -3.913693904876709,
"y": -1816.709716796875,
"z": -36.905204772949219
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
}
],
"Version": 68
};
// Add LocalPosition to entity data if parent properties are available
var entities = SHORTBOW_ENTITIES.Entities;
var entitiesByID = {};
var i, entity;
for (i = 0; i < entities.length; ++i) {
entity = entities[i];
entitiesByID[entity.id] = entity;
}
for (i = 0; i < entities.length; ++i) {
entity = entities[i];
if (entity.parentID !== undefined) {
var parent = entitiesByID[entity.parentID];
if (parent !== undefined) {
entity.localPosition = Vec3.subtract(entity.position, parent.position);
delete entity.position;
}
}
}

View file

@ -0,0 +1,840 @@
{
"Entities": [
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{02f39515-cab4-41d5-b315-5fb41613f844}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708750446,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -5.1684012413024902,
"y": 0.54034698009490967,
"z": -11.257695198059082
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": -1.3604279756546021,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"backgroundColor": {
"blue": 65,
"green": 78,
"red": 82
},
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 2,
"y": 0.69999998807907104,
"z": 0.0099999997764825821
},
"id": "{3eae601e-3c6e-49ab-8f40-dedee32f7573}",
"lastEdited": 1487894036038423,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"lineHeight": 0.5,
"name": "SB.DisplayScore",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -8.0707607269287109,
"y": 1.5265679359436035,
"z": -9.5913219451904297
},
"queryAACube": {
"scale": 2.118985652923584,
"x": -5.1109838485717773,
"y": -1803.69189453125,
"z": -26.774648666381836
},
"rotation": {
"w": 0.70708787441253662,
"x": -1.52587890625e-05,
"y": 0.70708787441253662,
"z": -1.52587890625e-05
},
"text": "0",
"type": "Text",
"userData": "{\"displayType\":\"score\"}"
},
{
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.71920669078826904,
"y": 3.3160061836242676,
"z": 2.2217941284179688
},
"id": "{04288f77-64df-4323-ac38-9c1960a393a5}",
"lastEdited": 1487893058314990,
"lastEditedBy": "{fce8028a-4bac-43e8-96ff-4c7286ea4ab3}",
"modelURL": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/models/shortbow-button.fbx",
"name": "SB.StartButton",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -9.8358345031738281,
"y": 0.45674961805343628,
"z": -13.044205665588379
},
"queryAACube": {
"scale": 4.0558013916015625,
"x": -7.844393253326416,
"y": -1805.730224609375,
"z": -31.195960998535156
},
"rotation": {
"w": 1,
"x": 1.52587890625e-05,
"y": 1.52587890625e-05,
"z": 1.52587890625e-05
},
"script": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/startGameButtonClientEntity.js",
"shapeType": "static-mesh",
"type": "Model",
"userData": "{\"grabbableKey\":{\"wantsTrigger\":true}}"
},
{
"backgroundColor": {
"blue": 65,
"green": 78,
"red": 82
},
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 2,
"y": 0.69999998807907104,
"z": 0.0099999997764825821
},
"id": "{1196096f-bcc9-4b19-970d-605113474c1b}",
"lastEdited": 1487894037900323,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"lineHeight": 0.5,
"name": "SB.DisplayHighScore",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -8.0707607269287109,
"y": 0.26189804077148438,
"z": -9.5913219451904297
},
"queryAACube": {
"scale": 2.118985652923584,
"x": -5.11102294921875,
"y": -1804.95654296875,
"z": -26.77461051940918
},
"rotation": {
"w": 0.70708787441253662,
"x": -1.52587890625e-05,
"y": 0.70708787441253662,
"z": -1.52587890625e-05
},
"text": "0",
"type": "Text",
"userData": "{\"displayType\":\"highscore\"}"
},
{
"backgroundColor": {
"blue": 65,
"green": 78,
"red": 82
},
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 1.4120937585830688,
"y": 0.71569448709487915,
"z": 0.0099999997764825821
},
"id": "{293c294d-1df5-461e-82a3-66abee852d44}",
"lastEdited": 1487894033695485,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"lineHeight": 0.5,
"name": "SB.DisplayWave",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -8.0707607269287109,
"y": 1.5265679359436035,
"z": -7.2889409065246582
},
"queryAACube": {
"scale": 1.5831384658813477,
"x": -4.8431310653686523,
"y": -1803.4239501953125,
"z": -24.204343795776367
},
"rotation": {
"w": 0.70708787441253662,
"x": -1.52587890625e-05,
"y": 0.70708787441253662,
"z": -1.52587890625e-05
},
"text": "0",
"type": "Text",
"userData": "{\"displayType\":\"wave\"}"
},
{
"backgroundColor": {
"blue": 65,
"green": 78,
"red": 82
},
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 1.4120937585830688,
"y": 0.71569448709487915,
"z": 0.0099999997764825821
},
"id": "{379afa7b-c668-4c4e-b290-e5c37fb3440c}",
"lastEdited": 1487893055310428,
"lastEditedBy": "{fce8028a-4bac-43e8-96ff-4c7286ea4ab3}",
"lineHeight": 0.5,
"name": "SB.DisplayLives",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -8.0707607269287109,
"y": 0.26189804077148438,
"z": -7.2889409065246582
},
"queryAACube": {
"scale": 1.5831384658813477,
"x": -4.8431692123413086,
"y": -1804.6885986328125,
"z": -24.204303741455078
},
"rotation": {
"w": 0.70708787441253662,
"x": -1.52587890625e-05,
"y": 0.70708787441253662,
"z": -1.52587890625e-05
},
"text": "0",
"type": "Text",
"userData": "{\"displayType\":\"lives\"}"
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{760e81a1-a804-4f5e-9769-393d021fc8fe}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440234633,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -1.89238440990448,
"y": -5.3368110656738281,
"z": 11.512755393981934
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 1.9147146940231323,
"y": -1809.7066650390625,
"z": -4.8219971656799316
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{0a76d0ac-6353-467b-8edc-56417d5a987c}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440235124,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 3.6569130420684814,
"y": -5.3365960121154785,
"z": 10.01292610168457
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 7.4640579223632812,
"y": -1809.7066650390625,
"z": -6.3216567039489746
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{f8549c8a-e646-4feb-bbaf-70e7d5be755a}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440235339,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 8.8902750015258789,
"y": -5.3364419937133789,
"z": 10.195274353027344
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 12.697414398193359,
"y": -1809.7066650390625,
"z": -6.1391491889953613
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{f3aea4ae-4445-4a2d-8d61-e9fd72f04008}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708751269,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -2.5027251243591309,
"y": 0.54042834043502808,
"z": -11.257777214050293
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 1.3052481412887573,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{cc1ac907-124b-4372-8c4c-82d175546725}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708751135,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 2.7972855567932129,
"y": 0.54059004783630371,
"z": -11.257938385009766
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 6.6052589416503906,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{e25ce690-e267-4c51-80a0-f63a72474b8a}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708751527,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 0.17114110291004181,
"y": 0.54050993919372559,
"z": -11.257858276367188
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 3.979114294052124,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{91ee2285-38f8-4795-b6bc-7abc8dcde07c}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708750806,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 5.4656705856323242,
"y": 0.54067152738571167,
"z": -11.258020401000977
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 9.2736434936523438,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{d81e5fae-8a8d-4186-bbd2-0c3ae737b0f2}",
"ignoreForCollisions": 1,
"lastEdited": 1487892552671000,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 9.6099967956542969,
"y": 0.64012420177459717,
"z": -9.9802846908569336
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 13.417934417724609,
"y": -1803.730712890625,
"z": -26.314868927001953
},
"rotation": {
"w": 0.22495110332965851,
"x": -2.9734959753113799e-05,
"y": 0.97437006235122681,
"z": 2.9735869247815572e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{7056e21e-bce6-4c4b-bbca-36bea1dce303}",
"ignoreForCollisions": 1,
"lastEdited": 1487892708750993,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.BowSpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 8.1799373626708984,
"y": 0.54075431823730469,
"z": -11.258102416992188
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 11.987911224365234,
"y": -1803.830078125,
"z": -27.592727661132812
},
"rotation": {
"w": 0.17366324365139008,
"x": 4.9033405957743526e-07,
"y": -0.98480510711669922,
"z": -2.9563087082351558e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{ed073620-e304-4b8e-b12a-5371b595bbf6}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440234415,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": -2.3618791103363037,
"y": -2.0691573619842529,
"z": 11.254574775695801
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 1.4453276395797729,
"y": -1806.43896484375,
"z": -5.0802912712097168
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"collidesWith": "",
"collisionMask": 0,
"collisionless": 1,
"color": {
"blue": 171,
"green": 50,
"red": 62
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 0.24400754272937775,
"y": 0.24400754272937775,
"z": 0.24400754272937775
},
"id": "{32ed7820-c386-4da1-b676-7e63762861a3}",
"ignoreForCollisions": 1,
"lastEdited": 1487892440234854,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.EnemySpawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 0.64757472276687622,
"y": -2.5217375755310059,
"z": 10.08248233795166
},
"queryAACube": {
"scale": 0.42263346910476685,
"x": 4.454803466796875,
"y": -1806.8917236328125,
"z": -6.2522788047790527
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
},
{
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 26.619264602661133,
"y": 14.24090576171875,
"z": 39.351066589355469
},
"id": "{d4c8f577-944d-4d50-ac85-e56387c0ef0a}",
"lastEdited": 1487892440231278,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"modelURL": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/models/shortbow-platform.fbx",
"name": "SB.Platform",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 0.097909502685070038,
"y": -1.0163799524307251,
"z": 2.0321114063262939
},
"queryAACube": {
"scale": 49.597328186035156,
"x": -20.681917190551758,
"y": -1829.9739990234375,
"z": -38.890060424804688
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shapeType": "static-mesh",
"type": "Model"
},
{
"clientOnly": 0,
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 23.341892242431641,
"y": 12.223045349121094,
"z": 32.012016296386719
},
"friction": 1,
"id": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"lastEdited": 1487892440231832,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"modelURL": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/models/shortbow-scoreboard.fbx",
"name": "SB.Scoreboard",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"queryAACube": {
"scale": 41.461017608642578,
"x": -20.730508804321289,
"y": -20.730508804321289,
"z": -20.730508804321289
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"serverScripts": "file:///c:/Users/ryanh/dev/hifi/unpublishedScripts/marketplace/shortbow/shortbowServerEntity.js",
"shapeType": "static-mesh",
"type": "Model"
},
{
"clientOnly": 0,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2017-02-23T23:28:32Z",
"dimensions": {
"x": 15.710711479187012,
"y": 4.7783288955688477,
"z": 1.6129581928253174
},
"id": "{84cdff6e-a68d-4bbf-8660-2d6a8c2f1fd0}",
"lastEdited": 1487892440231522,
"lastEditedBy": "{91f193dd-829a-4b33-ab27-e9a26160634a}",
"name": "SB.GateCollider",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"parentID": "{0cd1f1f7-53b9-4c15-bf25-42c0760d16f0}",
"position": {
"x": 0.31728419661521912,
"y": -4.3002614974975586,
"z": -12.531644821166992
},
"queryAACube": {
"scale": 16.50031852722168,
"x": -3.913693904876709,
"y": -1816.709716796875,
"z": -36.905204772949219
},
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"visible": 0
}
],
"Version": 68
}

View file

@ -0,0 +1,621 @@
//
// Created by Ryan Huffman on 1/10/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* globals ShortbowGameManager:true, utils */
Script.include('utils.js');
// +--------+ +-----------+ +-----------------+
// | | | |<-----+ |
// | IDLE +----->| PLAYING | | BETWEEN_WAVES |
// | | | +----->| |
// +--------+ +-----+-----+ +-----------------+
// ^ |
// | v
// | +-------------+
// | | |
// +---------+ GAME_OVER |
// | |
// +-------------+
var GAME_STATES = {
IDLE: 0,
PLAYING: 1,
BETWEEN_WAVES: 2,
GAME_OVER: 3
};
// Load the sounds that we will be using in the game so they are ready to be
// used when we need them.
var BEGIN_BUILDING_SOUND = SoundCache.getSound(Script.resolvePath("sounds/gameOn.wav"));
var GAME_OVER_SOUND = SoundCache.getSound(Script.resolvePath("sounds/gameOver.wav"));
var WAVE_COMPLETE_SOUND = SoundCache.getSound(Script.resolvePath("sounds/waveComplete.wav"));
var EXPLOSION_SOUND = SoundCache.getSound(Script.resolvePath("sounds/explosion.wav"));
var TARGET_HIT_SOUND = SoundCache.getSound(Script.resolvePath("sounds/targetHit.wav"));
var ESCAPE_SOUND = SoundCache.getSound(Script.resolvePath("sounds/escape.wav"));
const STARTING_NUMBER_OF_LIVES = 6;
const ENEMIES_PER_WAVE_MULTIPLIER = 2;
const POINTS_PER_KILL = 100;
const ENEMY_SPEED = 3.0;
// Encode a set of key-value pairs into a param string. Does NOT do any URL escaping.
function encodeURLParams(params) {
var paramPairs = [];
for (var key in params) {
paramPairs.push(key + "=" + params[key]);
}
return paramPairs.join("&");
}
function sendAndUpdateHighScore(entityID, score, wave, numPlayers, onResposeReceived) {
const URL = 'https://script.google.com/macros/s/AKfycbwbjCm9mGd1d5BzfAHmVT_XKmWyUYRkjCEqDOKm1368oM8nqWni/exec';
print("Sending high score");
const paramString = encodeURLParams({
entityID: entityID,
score: score,
wave: wave,
numPlayers: numPlayers
});
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
print("ready state: ", request.readyState, request.status, request.readyState === request.DONE, request.response);
if (request.readyState === request.DONE && request.status === 200) {
print("Got response for high score: ", request.response);
var response = JSON.parse(request.responseText);
if (response.highScore !== undefined) {
onResposeReceived(response.highScore);
}
}
};
request.open('GET', URL + "?" + paramString);
request.timeout = 10000;
request.send();
}
function findChildrenWithName(parentID, name) {
var childrenIDs = Entities.getChildrenIDs(parentID);
var matchingIDs = [];
for (var i = 0; i < childrenIDs.length; ++i) {
var id = childrenIDs[i];
var childName = Entities.getEntityProperties(id, 'name').name;
if (childName === name) {
matchingIDs.push(id);
}
}
return matchingIDs;
}
function getPropertiesForEntities(entityIDs, desiredProperties) {
var properties = [];
for (var i = 0; i < entityIDs.length; ++i) {
properties.push(Entities.getEntityProperties(entityIDs[i], desiredProperties));
}
return properties;
}
var baseEnemyProperties = {
"name": "SB.Enemy",
"damping": 0,
"linearDamping": 0,
"angularDamping": 0,
"acceleration": {
"x": 0,
"y": -9,
"z": 0
},
"angularVelocity": {
"x": -0.058330666273832321,
"y": -0.77943277359008789,
"z": -2.1163818836212158
},
"clientOnly": 0,
"collisionsWillMove": 1,
"dimensions": {
"x": 0.63503998517990112,
"y": 0.63503998517990112,
"z": 0.63503998517990112
},
"dynamic": 1,
"gravity": {
"x": 0,
"y": -15,
"z": 0
},
"lifetime": 30,
"id": "{ed8f7339-8bbd-4750-968e-c3ceb9d64721}",
"modelURL": Script.resolvePath("models/Amber.fbx"),
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"queryAACube": {
"scale": 1.0999215841293335,
"x": -0.54996079206466675,
"y": -0.54996079206466675,
"z": -0.54996079206466675
},
"shapeType": "sphere",
"type": "Model",
"script": Script.resolvePath('enemyClientEntity.js'),
"serverScripts": Script.resolvePath('enemyServerEntity.js')
};
function searchForChildren(parentID, names, callback, timeoutMs) {
// Map from name to entity ID for the children that have been found
var foundEntities = {};
for (var i = 0; i < names.length; ++i) {
foundEntities[names[i]] = null;
}
const CHECK_EVERY_MS = 500;
const maxChecks = Math.ceil(timeoutMs / CHECK_EVERY_MS);
var check = 0;
var intervalID = Script.setInterval(function() {
check++;
var childrenIDs = Entities.getChildrenIDs(parentID);
print("\tNumber of children:", childrenIDs.length);
for (var i = 0; i < childrenIDs.length; ++i) {
print("\t\t" + i + ".", Entities.getEntityProperties(childrenIDs[i]).name);
var id = childrenIDs[i];
var name = Entities.getEntityProperties(id, 'name').name;
var idx = names.indexOf(name);
if (idx > -1) {
foundEntities[name] = id;
print(name, id);
names.splice(idx, 1);
}
}
if (names.length === 0 || check >= maxChecks) {
Script.clearInterval(intervalID);
callback(foundEntities);
}
}, CHECK_EVERY_MS);
}
ShortbowGameManager = function(rootEntityID, bowPositions, spawnPositions) {
print("Starting game manager");
var self = this;
this.gameState = GAME_STATES.IDLE;
this.rootEntityID = rootEntityID;
this.bowPositions = bowPositions;
this.rootPosition = null;
this.spawnPositions = spawnPositions;
this.loadedChildren = false;
const START_BUTTON_NAME = 'SB.StartButton';
const WAVE_DISPLAY_NAME = 'SB.DisplayWave';
const SCORE_DISPLAY_NAME = 'SB.DisplayScore';
const LIVES_DISPLAY_NAME = 'SB.DisplayLives';
const HIGH_SCORE_DISPLAY_NAME = 'SB.DisplayHighScore';
const SEARCH_FOR_CHILDREN_TIMEOUT = 5000;
searchForChildren(rootEntityID, [
START_BUTTON_NAME,
WAVE_DISPLAY_NAME,
SCORE_DISPLAY_NAME,
LIVES_DISPLAY_NAME,
HIGH_SCORE_DISPLAY_NAME
], function(children) {
self.loadedChildren = true;
self.startButtonID = children[START_BUTTON_NAME];
self.waveDisplayID = children[WAVE_DISPLAY_NAME];
self.scoreDisplayID = children[SCORE_DISPLAY_NAME];
self.livesDisplayID = children[LIVES_DISPLAY_NAME];
self.highScoreDisplayID = children[HIGH_SCORE_DISPLAY_NAME];
sendAndUpdateHighScore(self.rootEntityID, self.score, self.waveNumber, 1, self.setHighScore.bind(self));
self.reset();
}, SEARCH_FOR_CHILDREN_TIMEOUT);
// Gameplay state
this.waveNumber = 0;
this.livesLeft = STARTING_NUMBER_OF_LIVES;
this.score = 0;
this.nextWaveTimer = null;
this.spawnEnemyTimers = [];
this.remainingEnemies = [];
this.bowIDs = [];
this.startButtonChannelName = 'button-' + this.rootEntityID;
// Entity client and server scripts will send messages to this channel
this.commChannelName = "shortbow-" + this.rootEntityID;
Messages.subscribe(this.commChannelName);
Messages.messageReceived.connect(this, this.onReceivedMessage);
print("Listening on: ", this.commChannelName);
Messages.sendMessage(this.commChannelName, 'hi');
};
ShortbowGameManager.prototype = {
reset: function() {
Entities.editEntity(this.startButtonID, { visible: true });
},
cleanup: function() {
Messages.unsubscribe(this.commChannelName);
Messages.messageReceived.disconnect(this, this.onReceivedMessage);
if (this.checkEnemiesTimer) {
Script.clearInterval(this.checkEnemiesTimer);
this.checkEnemiesTimer = null;
}
for (var i = this.bowIDs.length - 1; i >= 0; i--) {
Entities.deleteEntity(this.bowIDs[i]);
}
this.bowIDs = [];
for (i = 0; i < this.remainingEnemies.length; i++) {
Entities.deleteEntity(this.remainingEnemies[i].id);
}
this.remainingEnemies = [];
this.gameState = GAME_STATES.IDLE;
},
startGame: function() {
if (this.gameState !== GAME_STATES.IDLE) {
print("shortbowGameManagerManager.js | Error, trying to start game when not in idle state");
return;
}
if (this.loadedChildren === false) {
print('shortbowGameManager.js | Children have not loaded, not allowing game to start');
return;
}
print("Game started!!");
this.rootPosition = Entities.getEntityProperties(this.rootEntityID, 'position').position;
Entities.editEntity(this.startButtonID, { visible: false });
// Spawn bows
var bowSpawnEntityIDs = findChildrenWithName(this.rootEntityID, 'SB.BowSpawn');
var bowSpawnProperties = getPropertiesForEntities(bowSpawnEntityIDs, ['position', 'rotation']);
for (var i = 0; i < bowSpawnProperties.length; ++i) {
const props = bowSpawnProperties[i];
Vec3.print("Creating bow: " + i, props.position);
this.bowIDs.push(Entities.addEntity({
"position": props.position,
"rotation": props.rotation,
"collisionsWillMove": 1,
"compoundShapeURL": Script.resolvePath("bow/bow_collision_hull.obj"),
"created": "2016-09-01T23:57:55Z",
"dimensions": {
"x": 0.039999999105930328,
"y": 1.2999999523162842,
"z": 0.20000000298023224
},
"dynamic": 1,
"gravity": {
"x": 0,
"y": -9.8,
"z": 0
},
"modelURL": Script.resolvePath("bow/bow-deadly.fbx"),
"name": "WG.Hifi-Bow",
"script": Script.resolvePath("bow/bow.js"),
"shapeType": "compound",
"type": "Model",
"userData": "{\"grabbableKey\":{\"grabbable\":true},\"wearable\":{\"joints\":{\"RightHand\":[{\"x\":0.0813,\"y\":0.0452,\"z\":0.0095},{\"x\":-0.3946,\"y\":-0.6604,\"z\":0.4748,\"w\":-0.4275}],\"LeftHand\":[{\"x\":-0.0881,\"y\":0.0259,\"z\":0.0159},{\"x\":0.4427,\"y\":-0.6519,\"z\":0.4592,\"w\":0.4099}]}}}"
}));
}
// Initialize game state
this.waveNumber = 0;
this.setScore(0);
this.setLivesLeft(STARTING_NUMBER_OF_LIVES);
this.nextWaveTimer = Script.setTimeout(this.startNextWave.bind(this), 100);
this.spawnEnemyTimers = [];
this.checkEnemiesTimer = null;
this.remainingEnemies = [];
// SpawnQueue is a list of enemies left to spawn. Each entry looks like:
//
// { spawnAt: 1000, position: { x: 0, y: 0, z: 0 } }
//
// where spawnAt is the number of millseconds after the start of the wave
// to spawn the enemy. The list is sorted by spawnAt, ascending.
this.spawnQueue = [];
this.gameState = GAME_STATES.BETWEEN_WAVES;
Audio.playSound(BEGIN_BUILDING_SOUND, {
volume: 1.0,
position: this.rootPosition
});
},
startNextWave: function() {
if (this.gameState !== GAME_STATES.BETWEEN_WAVES) {
return;
}
print("Starting next wave");
this.gameState = GAME_STATES.PLAYING;
this.waveNumber++;
this.remainingEnemies= [];
this.spawnQueue = [];
this.spawnStartTime = Date.now();
Entities.editEntity(this.waveDisplayID, { text: this.waveNumber});
var numberOfEnemiesLeftToSpawn = this.waveNumber * ENEMIES_PER_WAVE_MULTIPLIER;
var delayBetweenSpawns = 2000 / Math.max(1, Math.log(this.waveNumber));
var currentDelay = 2000;
print("Number of enemies:", numberOfEnemiesLeftToSpawn);
this.checkEnemiesTimer = Script.setInterval(this.checkEnemies.bind(this), 100);
var enemySpawnEntityIDs = findChildrenWithName(this.rootEntityID, 'SB.EnemySpawn');
var enemySpawnProperties = getPropertiesForEntities(enemySpawnEntityIDs, ['position', 'rotation']);
for (var i = 0; i < numberOfEnemiesLeftToSpawn; ++i) {
print("Adding enemy");
var idx = Math.floor(Math.random() * enemySpawnProperties.length);
var props = enemySpawnProperties[idx];
this.spawnQueue.push({
spawnAt: currentDelay,
position: props.position,
rotation: props.rotation,
velocity: Vec3.multiply(ENEMY_SPEED, Quat.getFront(props.rotation))
});
currentDelay += delayBetweenSpawns;
}
print("Starting wave", this.waveNumber);
},
checkWaveComplete: function() {
if (this.gameState !== GAME_STATES.PLAYING) {
return;
}
if (this.spawnQueue.length === 0 && this.remainingEnemies.length === 0) {
this.gameState = GAME_STATES.BETWEEN_WAVES;
Script.setTimeout(this.startNextWave.bind(this), 5000);
Script.clearInterval(this.checkEnemiesTimer);
this.checkEnemiesTimer = null;
// Play after 1.5s to let other sounds finish playing
var self = this;
Script.setTimeout(function() {
Audio.playSound(WAVE_COMPLETE_SOUND, {
volume: 1.0,
position: self.rootPosition
});
}, 1500);
}
},
setHighScore: function(highScore) {
print("Setting high score to:", this.highScoreDisplayID, highScore);
Entities.editEntity(this.highScoreDisplayID, { text: highScore });
},
setLivesLeft: function(lives) {
lives = Math.max(0, lives);
this.livesLeft = lives;
Entities.editEntity(this.livesDisplayID, { text: this.livesLeft });
},
setScore: function(score) {
this.score = score;
Entities.editEntity(this.scoreDisplayID, { text: this.score });
},
checkEnemies: function() {
if (this.gameState !== GAME_STATES.PLAYING) {
return;
}
// Check the spawn queueu to see if there are any enemies that need to
// be spawned
var waveElapsedTime = Date.now() - this.spawnStartTime;
while (this.spawnQueue.length > 0 && waveElapsedTime > this.spawnQueue[0].spawnAt) {
baseEnemyProperties.position = this.spawnQueue[0].position;
baseEnemyProperties.rotation = this.spawnQueue[0].rotation;
baseEnemyProperties.velocity= this.spawnQueue[0].velocity;
baseEnemyProperties.userData = JSON.stringify({
gameChannel: this.commChannelName,
grabbableKey: {
grabbable: false
}
});
var entityID = Entities.addEntity(baseEnemyProperties);
this.remainingEnemies.push({
id: entityID,
lastKnownPosition: baseEnemyProperties.position,
lastHeartbeat: Date.now()
});
this.spawnQueue.splice(0, 1);
Script.setTimeout(function() {
const JUMP_SPEED = 5.0;
var velocity = Entities.getEntityProperties(entityID, 'velocity').velocity;
velocity.y += JUMP_SPEED;
Entities.editEntity(entityID, { velocity: velocity });
}, 500 + Math.random() * 4000);
}
// Check the list of remaining enemies to see if any are too far away
// or haven't been heard from in awhile - if so, delete them.
var enemiesEscaped = false;
const MAX_UNHEARD_TIME_BEFORE_DESTROYING_ENTITY_MS = 5000;
const MAX_DISTANCE_FROM_GAME_BEFORE_DESTROYING_ENTITY = 200;
for (var i = this.remainingEnemies.length - 1; i >= 0; --i) {
var enemy = this.remainingEnemies[i];
var timeSinceLastHeartbeat = Date.now() - enemy.lastHeartbeat;
var distance = Vec3.distance(enemy.lastKnownPosition, this.rootPosition);
if (timeSinceLastHeartbeat > MAX_UNHEARD_TIME_BEFORE_DESTROYING_ENTITY_MS
|| distance > MAX_DISTANCE_FROM_GAME_BEFORE_DESTROYING_ENTITY) {
print("EXPIRING: ", enemy.id);
Entities.deleteEntity(enemy.id);
this.remainingEnemies.splice(i, 1);
Audio.playSound(TARGET_HIT_SOUND, {
volume: 1.0,
position: this.rootPosition
});
this.setScore(this.score + POINTS_PER_KILL);
enemiesEscaped = true;
}
}
if (enemiesEscaped) {
this.checkWaveComplete();
}
},
endGame: function() {
if (this.gameState !== GAME_STATES.PLAYING) {
return;
}
var self = this;
Script.setTimeout(function() {
Audio.playSound(GAME_OVER_SOUND, {
volume: 1.0,
position: self.rootPosition
});
}, 1500);
this.gameState = GAME_STATES.GAME_OVER;
print("GAME OVER");
// Update high score
sendAndUpdateHighScore(this.rootEntityID, this.score, this.waveNumber, 1, this.setHighScore.bind(this));
// Cleanup
Script.clearTimeout(this.nextWaveTimer);
this.nextWaveTimer = null;
var i;
for (i = 0; i < this.spawnEnemyTimers.length; ++i) {
Script.clearTimeout(this.spawnEnemyTimers[i]);
}
this.spawnEnemyTimers = [];
Script.clearInterval(this.checkEnemiesTimer);
this.checkEnemiesTimer = null;
for (i = this.bowIDs.length - 1; i >= 0; i--) {
var id = this.bowIDs[i];
print("Checking bow: ", id);
var userData = utils.parseJSON(Entities.getEntityProperties(id, 'userData').userData);
var bowIsHeld = userData.grabKey !== undefined && userData.grabKey !== undefined && userData.grabKey.refCount > 0;
print("Held: ", bowIsHeld);
if (!bowIsHeld) {
Entities.deleteEntity(id);
this.bowIDs.splice(i, 1);
}
}
for (i = 0; i < this.remainingEnemies.length; i++) {
Entities.deleteEntity(this.remainingEnemies[i].id);
}
this.remainingEnemies = [];
// Wait a short time before showing the start button so that any current sounds
// can finish playing.
const WAIT_TO_REENABLE_GAME_TIMEOUT_MS = 3000;
Script.setTimeout(function() {
Entities.editEntity(this.startButtonID, { visible: true });
this.gameState = GAME_STATES.IDLE;
}.bind(this), WAIT_TO_REENABLE_GAME_TIMEOUT_MS);
},
onReceivedMessage: function(channel, messageJSON, senderID) {
if (channel === this.commChannelName) {
var message = utils.parseJSON(messageJSON);
if (message === undefined) {
print("shortbowGameManager.js | Received non-json message:", JSON.stringify(messageJSON));
return;
}
switch (message.type) {
case 'start-game':
this.startGame();
break;
case 'enemy-killed':
this.onEnemyKilled(message.entityID, message.position);
break;
case 'enemy-escaped':
this.onEnemyEscaped(message.entityID);
break;
case 'enemy-heartbeat':
this.onEnemyHeartbeat(message.entityID, message.position);
break;
default:
print("shortbowGameManager.js | Ignoring unknown message type: ", message.type);
break;
}
}
},
onEnemyKilled: function(entityID, position) {
if (this.gameState !== GAME_STATES.PLAYING) {
return;
}
for (var i = this.remainingEnemies.length - 1; i >= 0; --i) {
var enemy = this.remainingEnemies[i];
if (enemy.id === entityID) {
this.remainingEnemies.splice(i, 1);
Audio.playSound(TARGET_HIT_SOUND, {
volume: 1.0,
position: this.rootPosition
});
// Update score
this.setScore(this.score + POINTS_PER_KILL);
print("SCORE: ", this.score);
this.checkWaveComplete();
break;
}
}
},
onEnemyEscaped: function(entityID, position) {
if (this.gameState !== GAME_STATES.PLAYING) {
return;
}
var enemiesEscaped = false;
for (var i = this.remainingEnemies.length - 1; i >= 0; --i) {
var enemy = this.remainingEnemies[i];
if (enemy.id === entityID) {
Entities.deleteEntity(enemy.id);
this.remainingEnemies.splice(i, 1);
this.setLivesLeft(this.livesLeft - 1);
Audio.playSound(ESCAPE_SOUND, {
volume: 1.0,
position: this.rootPosition
});
enemiesEscaped = true;
}
}
if (this.livesLeft <= 0) {
this.endGame();
} else if (enemiesEscaped) {
this.checkWaveComplete();
}
},
onEnemyHeartbeat: function(entityID, position) {
for (var i = 0; i < this.remainingEnemies.length; i++) {
var enemy = this.remainingEnemies[i];
if (enemy.id === entityID) {
enemy.lastHeartbeat = Date.now();
enemy.lastKnownPosition = position;
break;
}
}
}
};

View file

@ -0,0 +1,42 @@
//
// Created by Ryan Huffman on 1/10/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* globals TEMPLATES:true, SHORTBOW_ENTITIES, ShortbowGameManager */
(function() {
Script.include('utils.js');
Script.include('shortbow.js');
Script.include('shortbowGameManager.js');
TEMPLATES = SHORTBOW_ENTITIES.Entities;
this.entityID = null;
var gameManager = null;
this.preload = function(entityID) {
this.entityID = entityID;
var bowPositions = [];
var spawnPositions = [];
for (var i = 0; i < TEMPLATES.length; ++i) {
var template = TEMPLATES[i];
if (template.name === "SB.BowSpawn") {
bowPositions.push(template.localPosition);
} else if (template.name === "SB.EnemySpawn") {
spawnPositions.push(template.localPosition);
}
}
gameManager = new ShortbowGameManager(this.entityID, bowPositions, spawnPositions);
};
this.unload = function() {
if (gameManager) {
gameManager.cleanup();
gameManager = null;
}
};
});

View file

@ -0,0 +1,210 @@
//
// Created by Ryan Huffman on 1/10/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* globals utils, SHORTBOW_ENTITIES, TEMPLATES:true */
Script.include('utils.js');
Script.include('shortbow.js');
Script.include('shortbowGameManager.js');
TEMPLATES = SHORTBOW_ENTITIES.Entities;
// Merge two objects into a new object. If a key name appears in both a and b,
// the value in a will be used.
//
// @param {object} a
// @param {object} b
// @returns {object} The new object
function mergeObjects(a, b) {
var obj = {};
var key;
for (key in b) {
obj[key] = b[key];
}
for (key in a) {
obj[key] = a[key];
}
return obj;
}
// Spawn an entity from a template.
//
// The overrides can be used to override or add properties in the template. For instance,
// it's common to override the `position` property so that you can set the position
// of the entity to be spawned.
//
// @param {string} templateName The name of the template to spawn
// @param {object} overrides An object containing properties that will override
// any properties set in the template.
function spawnTemplate(templateName, overrides) {
var template = getTemplate(templateName);
if (template === null) {
print("ERROR, unknown template name:", templateName);
return null;
}
print("Spawning: ", templateName);
var properties = mergeObjects(overrides, template);
return Entities.addEntity(properties);
}
function spawnTemplates(templateName, overrides) {
var templates = getTemplates(templateName);
if (template.length === 0) {
print("ERROR, unknown template name:", templateName);
return [];
}
var spawnedEntities = [];
for (var i = 0; i < templates.length; ++i) {
print("Spawning: ", templateName);
var properties = mergeObjects(overrides, templates[i]);
spawnedEntities.push(Entities.addEntity(properties));
}
return spawnedEntities;
}
// TEMPLATES contains a dictionary of different named entity templates. An entity
// template is just a list of properties.
//
// @param name Name of the template to get
// @return {object} The matching template, or null if not found
function getTemplate(name) {
for (var i = 0; i < TEMPLATES.length; ++i) {
if (TEMPLATES[i].name === name) {
return TEMPLATES[i];
}
}
return null;
}
function getTemplates(name) {
var templates = [];
for (var i = 0; i < TEMPLATES.length; ++i) {
if (TEMPLATES[i].name === name) {
templates.push(TEMPLATES[i]);
}
}
return templates;
}
// Cleanup Shortbow template data
for (var i = 0; i < TEMPLATES.length; ++i) {
var template = TEMPLATES[i];
// Fixup model url
if (template.type === "Model") {
var urlParts = template.modelURL.split("/");
var filename = urlParts[urlParts.length - 1];
var newURL = Script.resolvePath("models/" + filename);
print("Updated url", template.modelURL, "to", newURL);
template.modelURL = newURL;
}
}
var entityIDs = [];
var scoreboardID = null;
var buttonID = null;
var waveDisplayID = null;
var scoreDisplayID = null;
var highScoreDisplayID = null;
var livesDisplayID = null;
var platformID = null;
function createLocalGame() {
var rootPosition = utils.findSurfaceBelowPosition(MyAvatar.position);
rootPosition.y += 6.11;
scoreboardID = spawnTemplate("SB.Scoreboard", {
position: rootPosition
});
entityIDs.push(scoreboardID);
// Create start button
buttonID = spawnTemplate("SB.StartButton", {
parentID: scoreboardID,
script: Script.resolvePath("startGameButtonClientEntity.js"),
userData: JSON.stringify({
grabbableKey: {
wantsTrigger: true
}
})
});
entityIDs.push(buttonID);
waveDisplayID = spawnTemplate("SB.DisplayWave", {
parentID: scoreboardID,
userData: JSON.stringify({
displayType: "wave"
})
});
entityIDs.push(waveDisplayID);
scoreDisplayID = spawnTemplate("SB.DisplayScore", {
parentID: scoreboardID,
userData: JSON.stringify({
displayType: "score"
})
});
entityIDs.push(scoreDisplayID);
livesDisplayID = spawnTemplate("SB.DisplayLives", {
parentID: scoreboardID,
userData: JSON.stringify({
displayType: "lives"
})
});
entityIDs.push(livesDisplayID);
highScoreDisplayID = spawnTemplate("SB.DisplayHighScore", {
parentID: scoreboardID,
userData: JSON.stringify({
displayType: "highscore"
})
});
entityIDs.push(highScoreDisplayID);
platformID = spawnTemplate("SB.Platform", {
parentID: scoreboardID
});
entityIDs.push(platformID);
spawnTemplate("SB.GateCollider", {
parentID: scoreboardID,
visible: false
});
entityIDs.push(platformID);
Entities.editEntity(scoreboardID, {
serverScripts: Script.resolvePath('shortbowServerEntity.js')
});
spawnTemplates("SB.BowSpawn", {
parentID: scoreboardID,
visible: false
});
spawnTemplates("SB.EnemySpawn", {
parentID: scoreboardID,
visible: false
});
var bowPositions = [];
var spawnPositions = [];
for (var i = 0; i < TEMPLATES.length; ++i) {
var template = TEMPLATES[i];
if (template.name === "SB.BowSpawn") {
bowPositions.push(Vec3.sum(rootPosition, template.localPosition));
Vec3.print("Pushing bow position", Vec3.sum(rootPosition, template.localPosition));
} else if (template.name === "SB.EnemySpawn") {
spawnPositions.push(Vec3.sum(rootPosition, template.localPosition));
Vec3.print("Pushing spawnposition", Vec3.sum(rootPosition, template.localPosition));
}
}
}
createLocalGame();
Script.stop();

View file

@ -0,0 +1,41 @@
//
// Created by Ryan Huffman on 1/10/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* globals utils */
(function() {
Script.include('utils.js');
function StartButton() {
}
StartButton.prototype = {
preload: function(entityID) {
this.entityID = entityID;
this.commChannel = "shortbow-" + Entities.getEntityProperties(entityID, 'parentID').parentID;
Script.addEventHandler(entityID, "collisionWithEntity", this.onCollide.bind(this));
},
signalAC: function() {
Messages.sendMessage(this.commChannel, JSON.stringify({
type: 'start-game'
}));
},
onCollide: function(entityA, entityB, collision) {
var colliderName = Entities.getEntityProperties(entityB, 'name').name;
if (colliderName.indexOf("projectile") > -1) {
this.signalAC();
}
}
};
StartButton.prototype.startNearTrigger = StartButton.prototype.signalAC;
StartButton.prototype.startFarTrigger = StartButton.prototype.signalAC;
StartButton.prototype.clickDownOnEntity = StartButton.prototype.signalAC;
return new StartButton();
});

View file

@ -0,0 +1,57 @@
//
// Created by Ryan Huffman on 1/10/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* globals utils:true */
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
NOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof NOP
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
if (this.prototype) {
// Function.prototype doesn't have a prototype property
NOP.prototype = this.prototype;
}
fBound.prototype = new NOP();
return fBound;
};
}
utils = {
parseJSON: function(json) {
try {
return JSON.parse(json);
} catch (e) {
return undefined;
}
},
findSurfaceBelowPosition: function(pos) {
var result = Entities.findRayIntersection({
origin: pos,
direction: { x: 0.0, y: -1.0, z: 0.0 }
}, true);
if (result.intersects) {
return result.intersection;
}
return pos;
}
};