mirror of
https://github.com/lubosz/overte.git
synced 2025-04-27 17:55:27 +02:00
Merge remote-tracking branch 'upstream/master' into smarter_textures
This commit is contained in:
commit
fa5b315e09
110 changed files with 5696 additions and 1043 deletions
assignment-client/src
domain-server/resources/web/settings/js
interface
resources
avatar
animations
sitting.fbxsitting_idle.fbxtouch_point_closed_left.fbxtouch_point_closed_right.fbxtouch_point_open_left.fbxtouch_point_open_right.fbxtouch_thumb_closed_left.fbxtouch_thumb_closed_right.fbxtouch_thumb_open_left.fbxtouch_thumb_open_right.fbxtouch_thumb_point_closed_left.fbxtouch_thumb_point_closed_right.fbxtouch_thumb_point_open_left.fbxtouch_thumb_point_open_right.fbx
avatar-animation.jsonqml/hifi
src
libraries
animation/src
avatars/src
entities-renderer/src
entities/src
model-networking/src/model-networking
model/src/model
networking/src
NodeList.cppNodeList.hUserActivityLoggerScriptingInterface.cppUserActivityLoggerScriptingInterface.h
render-utils/src
script-engine/src
shared/src
plugins/openvr/src
scripts
defaultScripts.js
system
tutorials/entity_scripts
unpublishedScripts/marketplace/shortbow
|
@ -168,7 +168,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
|
|||
QList<AvatarSharedPointer> avatarList;
|
||||
std::unordered_map<AvatarSharedPointer, SharedNodePointer> avatarDataToNodes;
|
||||
|
||||
int listItem = 0;
|
||||
std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) {
|
||||
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData());
|
||||
|
||||
|
@ -176,7 +175,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
|
|||
// but not have yet sent data that's linked to the node. Check for that case and don't
|
||||
// consider those nodes.
|
||||
if (otherNodeData) {
|
||||
listItem++;
|
||||
AvatarSharedPointer otherAvatar = otherNodeData->getAvatarSharedPointer();
|
||||
avatarList << otherAvatar;
|
||||
avatarDataToNodes[otherAvatar] = otherNode;
|
||||
|
@ -185,8 +183,8 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
|
|||
|
||||
AvatarSharedPointer thisAvatar = nodeData->getAvatarSharedPointer();
|
||||
ViewFrustum cameraView = nodeData->getViewFrustom();
|
||||
std::priority_queue<AvatarPriority> sortedAvatars = AvatarData::sortAvatars(
|
||||
avatarList, cameraView,
|
||||
std::priority_queue<AvatarPriority> sortedAvatars;
|
||||
AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars,
|
||||
|
||||
[&](AvatarSharedPointer avatar)->uint64_t{
|
||||
auto avatarNode = avatarDataToNodes[avatar];
|
||||
|
@ -384,18 +382,20 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
|
|||
if (includeThisAvatar) {
|
||||
numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122());
|
||||
numAvatarDataBytes += avatarPacketList->write(bytes);
|
||||
_stats.numOthersIncluded++;
|
||||
|
||||
// increment the number of avatars sent to this reciever
|
||||
nodeData->incrementNumAvatarsSentLastFrame();
|
||||
if (detail != AvatarData::NoData) {
|
||||
_stats.numOthersIncluded++;
|
||||
|
||||
// set the last sent sequence number for this sender on the receiver
|
||||
nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(),
|
||||
otherNodeData->getLastReceivedSequenceNumber());
|
||||
// increment the number of avatars sent to this reciever
|
||||
nodeData->incrementNumAvatarsSentLastFrame();
|
||||
|
||||
// remember the last time we sent details about this other node to the receiver
|
||||
nodeData->setLastBroadcastTime(otherNode->getUUID(), start);
|
||||
// set the last sent sequence number for this sender on the receiver
|
||||
nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(),
|
||||
otherNodeData->getLastReceivedSequenceNumber());
|
||||
|
||||
// remember the last time we sent details about this other node to the receiver
|
||||
nodeData->setLastBroadcastTime(otherNode->getUUID(), start);
|
||||
}
|
||||
}
|
||||
|
||||
avatarPacketList->endSegment();
|
||||
|
|
|
@ -324,16 +324,8 @@ void EntityScriptServer::nodeActivated(SharedNodePointer activatedNode) {
|
|||
void EntityScriptServer::nodeKilled(SharedNodePointer killedNode) {
|
||||
switch (killedNode->getType()) {
|
||||
case NodeType::EntityServer: {
|
||||
if (!_shuttingDown) {
|
||||
if (_entitiesScriptEngine) {
|
||||
_entitiesScriptEngine->unloadAllEntityScripts();
|
||||
_entitiesScriptEngine->stop();
|
||||
}
|
||||
|
||||
resetEntitiesScriptEngine();
|
||||
|
||||
_entityViewer.clear();
|
||||
}
|
||||
clear();
|
||||
|
||||
break;
|
||||
}
|
||||
case NodeType::Agent: {
|
||||
|
@ -440,12 +432,12 @@ void EntityScriptServer::clear() {
|
|||
_entitiesScriptEngine->stop();
|
||||
}
|
||||
|
||||
_entityViewer.clear();
|
||||
|
||||
// reset the engine
|
||||
if (!_shuttingDown) {
|
||||
resetEntitiesScriptEngine();
|
||||
}
|
||||
|
||||
_entityViewer.clear();
|
||||
}
|
||||
|
||||
void EntityScriptServer::shutdownScriptEngine() {
|
||||
|
|
|
@ -996,6 +996,10 @@ function saveSettings() {
|
|||
if (password && password.length > 0) {
|
||||
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
|
||||
|
@ -1010,7 +1014,6 @@ function saveSettings() {
|
|||
bootbox.alert({"message": "Passwords must match!", "title":"Password Error"});
|
||||
canPost = false;
|
||||
} else {
|
||||
formJSON["security"]["http_password"] = sha256_digest(password);
|
||||
delete formJSON["security"]["verify_http_password"];
|
||||
}
|
||||
}
|
||||
|
|
BIN
interface/resources/avatar/animations/sitting.fbx
Normal file
BIN
interface/resources/avatar/animations/sitting.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/sitting_idle.fbx
Normal file
BIN
interface/resources/avatar/animations/sitting_idle.fbx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
interface/resources/avatar/animations/touch_point_open_left.fbx
Normal file
BIN
interface/resources/avatar/animations/touch_point_open_left.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/touch_point_open_right.fbx
Normal file
BIN
interface/resources/avatar/animations/touch_point_open_right.fbx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
interface/resources/avatar/animations/touch_thumb_open_left.fbx
Normal file
BIN
interface/resources/avatar/animations/touch_thumb_open_left.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/touch_thumb_open_right.fbx
Normal file
BIN
interface/resources/avatar/animations/touch_thumb_open_right.fbx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -128,7 +128,41 @@
|
|||
"id": "rightHandGrasp",
|
||||
"interpTarget": 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": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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": {
|
||||
"alpha": 0.0,
|
||||
"boneSet": "leftHand",
|
||||
"alphaVar" : "leftHandOverlayAlpha"
|
||||
"alphaVar": "leftHandOverlayAlpha"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
|
@ -188,7 +324,41 @@
|
|||
"id": "leftHandGrasp",
|
||||
"interpTarget": 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": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -335,7 +335,7 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
// Per-Avatar Gain Slider
|
||||
// Per-Avatar Gain Slider
|
||||
Slider {
|
||||
id: gainSlider
|
||||
// Size
|
||||
|
@ -345,7 +345,7 @@ Item {
|
|||
anchors.verticalCenter: nameCardVUMeter.verticalCenter
|
||||
// Properties
|
||||
visible: !isMyCard && selected
|
||||
value: pal.gainSliderValueDB[uuid] ? pal.gainSliderValueDB[uuid] : 0.0
|
||||
value: Users.getAvatarGain(uuid)
|
||||
minimumValue: -60.0
|
||||
maximumValue: 20.0
|
||||
stepSize: 5
|
||||
|
@ -369,7 +369,7 @@ Item {
|
|||
mouse.accepted = false
|
||||
}
|
||||
onReleased: {
|
||||
// the above mouse.accepted seems to make this
|
||||
// the above mouse.accepted seems to make this
|
||||
// never get called, nonetheless...
|
||||
mouse.accepted = false
|
||||
}
|
||||
|
@ -393,14 +393,9 @@ Item {
|
|||
}
|
||||
|
||||
function updateGainFromQML(avatarUuid, sliderValue, isReleased) {
|
||||
if (isReleased || pal.gainSliderValueDB[avatarUuid] !== sliderValue) {
|
||||
pal.gainSliderValueDB[avatarUuid] = sliderValue;
|
||||
var data = {
|
||||
sessionId: avatarUuid,
|
||||
gain: sliderValue,
|
||||
isReleased: isReleased
|
||||
};
|
||||
pal.sendToScript({method: 'updateGain', params: data});
|
||||
Users.setAvatarGain(avatarUuid, sliderValue);
|
||||
if (isReleased) {
|
||||
UserActivityLogger.palAction("avatar_gain_changed", avatarUuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Pal.qml
|
||||
// qml/hifi
|
||||
//
|
||||
// People Action List
|
||||
// People Action List
|
||||
//
|
||||
// Created by Howard Stearns on 12/12/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
|
@ -13,6 +13,7 @@
|
|||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import Qt.labs.settings 1.0
|
||||
import "../styles-uit"
|
||||
import "../controls-uit" as HifiControls
|
||||
|
||||
|
@ -29,14 +30,13 @@ Rectangle {
|
|||
property int myCardHeight: 90
|
||||
property int rowHeight: 70
|
||||
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 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 bool iAmAdmin: false
|
||||
// Keep a local list of per-avatar gainSliderValueDBs. Far faster than fetching this data from the server.
|
||||
// NOTE: if another script modifies the per-avatar gain, this value won't be accurate!
|
||||
property var gainSliderValueDB: ({});
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
|
@ -52,6 +52,16 @@ Rectangle {
|
|||
letterboxMessage.visible = true
|
||||
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
|
||||
Rectangle {
|
||||
|
@ -88,11 +98,32 @@ Rectangle {
|
|||
audioLevel: myData.audioLevel
|
||||
isMyCard: true
|
||||
// Size
|
||||
width: nameCardWidth
|
||||
width: minNameCardWidth
|
||||
height: parent.height
|
||||
// Anchors
|
||||
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
|
||||
Rectangle {
|
||||
|
@ -236,7 +267,7 @@ Rectangle {
|
|||
// Anchors
|
||||
anchors.left: parent.left
|
||||
}
|
||||
|
||||
|
||||
// This CheckBox belongs in the columns that contain the stateful action buttons ("Mute" & "Ignore" for now)
|
||||
// KNOWN BUG with the Checkboxes: When clicking in the center of the sorting header, the checkbox
|
||||
// will appear in the "hovered" state. Hovering over the checkbox will fix it.
|
||||
|
@ -272,7 +303,7 @@ Rectangle {
|
|||
checked = Qt.binding(function() { return (model[styleData.role])})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// This Button belongs in the columns that contain the stateless action buttons ("Silence" & "Ban" for now)
|
||||
HifiControls.Button {
|
||||
id: actionButton
|
||||
|
@ -306,45 +337,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
|
||||
Rectangle {
|
||||
// Size
|
||||
|
@ -501,7 +494,7 @@ Rectangle {
|
|||
if (alreadyRefreshed === true) {
|
||||
letterbox('', '', 'The last editor of this object is either you or not among this list of users.');
|
||||
} else {
|
||||
pal.sendToScript({method: 'refresh', params: message.params});
|
||||
pal.sendToScript({method: 'refresh', params: {selected: message.params}});
|
||||
}
|
||||
} else {
|
||||
// If we've already refreshed the PAL and found the avatar in the model
|
||||
|
@ -542,7 +535,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
break;
|
||||
case 'updateAudioLevel':
|
||||
case 'updateAudioLevel':
|
||||
for (var userId in message.params) {
|
||||
var audioLevel = message.params[userId];
|
||||
// If the userId is 0, we're updating "myData".
|
||||
|
@ -558,9 +551,8 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
break;
|
||||
case 'clearLocalQMLData':
|
||||
case 'clearLocalQMLData':
|
||||
ignored = {};
|
||||
gainSliderValueDB = {};
|
||||
break;
|
||||
case 'avatarDisconnected':
|
||||
var sessionID = message.params[0];
|
||||
|
|
|
@ -548,6 +548,7 @@ const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 100.0f;
|
|||
const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f;
|
||||
const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true;
|
||||
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) :
|
||||
QApplication(argc, argv),
|
||||
|
@ -570,6 +571,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_desktopTabletScale("desktopTabletScale", DEFAULT_DESKTOP_TABLET_SCALE_PERCENT),
|
||||
_desktopTabletBecomesToolbarSetting("desktopTabletBecomesToolbar", DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR),
|
||||
_hmdTabletBecomesToolbarSetting("hmdTabletBecomesToolbar", DEFAULT_HMD_TABLET_BECOMES_TOOLBAR),
|
||||
_tabletVisibleToOthersSetting("tabletVisibleToOthers", DEFAULT_TABLET_VISIBLE_TO_OTHERS),
|
||||
_constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true),
|
||||
_scaleMirror(1.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(disconnectedFromDomain()), SLOT(updateWindowTitle()));
|
||||
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);
|
||||
|
||||
// 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()) {
|
||||
setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID);
|
||||
setKeyboardFocusEntity(entityItemID);
|
||||
} else {
|
||||
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -2348,6 +2357,11 @@ void Application::setHmdTabletBecomesToolbarSetting(bool value) {
|
|||
updateSystemTabletMode();
|
||||
}
|
||||
|
||||
void Application::setTabletVisibleToOthersSetting(bool value) {
|
||||
_tabletVisibleToOthersSetting.set(value);
|
||||
updateSystemTabletMode();
|
||||
}
|
||||
|
||||
void Application::setSettingConstrainToolbarPosition(bool setting) {
|
||||
_constrainToolbarPosition.set(setting);
|
||||
DependencyManager::get<OffscreenUi>()->setConstrainToolbarToCenterX(setting);
|
||||
|
@ -3093,6 +3107,7 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
|
|||
getOverlays().mouseMoveEvent(&mappedEvent);
|
||||
getEntities()->mouseMoveEvent(&mappedEvent);
|
||||
}
|
||||
|
||||
_controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts
|
||||
|
||||
// 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) {
|
||||
getOverlays().mousePressEvent(&mappedEvent);
|
||||
|
||||
if (!_controllerScriptingInterface->areEntityClicksCaptured()) {
|
||||
getEntities()->mousePressEvent(&mappedEvent);
|
||||
}
|
||||
|
@ -3429,7 +3443,7 @@ void Application::idle(float nsecsElapsed) {
|
|||
#ifdef Q_OS_WIN
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [] {
|
||||
initCpuUsage();
|
||||
initCpuUsage();
|
||||
});
|
||||
|
||||
vec3 kernelUserAndSystem;
|
||||
|
@ -6902,5 +6916,10 @@ OverlayID Application::getTabletScreenID() const {
|
|||
|
||||
OverlayID Application::getTabletHomeButtonID() const {
|
||||
auto HMD = DependencyManager::get<HMDScriptingInterface>();
|
||||
return HMD->getCurrentHomeButtonUUID();
|
||||
return HMD->getCurrentHomeButtonID();
|
||||
}
|
||||
|
||||
QUuid Application::getTabletFrameID() const {
|
||||
auto HMD = DependencyManager::get<HMDScriptingInterface>();
|
||||
return HMD->getCurrentTabletFrameID();
|
||||
}
|
||||
|
|
|
@ -218,6 +218,8 @@ public:
|
|||
void setDesktopTabletBecomesToolbarSetting(bool value);
|
||||
bool getHmdTabletBecomesToolbarSetting() { return _hmdTabletBecomesToolbarSetting.get(); }
|
||||
void setHmdTabletBecomesToolbarSetting(bool value);
|
||||
bool getTabletVisibleToOthersSetting() { return _tabletVisibleToOthersSetting.get(); }
|
||||
void setTabletVisibleToOthersSetting(bool value);
|
||||
|
||||
float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); }
|
||||
void setSettingConstrainToolbarPosition(bool setting);
|
||||
|
@ -300,6 +302,7 @@ public:
|
|||
|
||||
OverlayID getTabletScreenID() const;
|
||||
OverlayID getTabletHomeButtonID() const;
|
||||
QUuid getTabletFrameID() const; // may be an entity or an overlay
|
||||
|
||||
signals:
|
||||
void svoImportRequested(const QString& url);
|
||||
|
@ -561,6 +564,7 @@ private:
|
|||
Setting::Handle<float> _desktopTabletScale;
|
||||
Setting::Handle<bool> _desktopTabletBecomesToolbarSetting;
|
||||
Setting::Handle<bool> _hmdTabletBecomesToolbarSetting;
|
||||
Setting::Handle<bool> _tabletVisibleToOthersSetting;
|
||||
Setting::Handle<bool> _constrainToolbarPosition;
|
||||
|
||||
float _scaleMirror;
|
||||
|
|
|
@ -192,6 +192,8 @@ QVariantMap Camera::getViewFrustum() {
|
|||
result["orientation"].setValue(frustum.getOrientation());
|
||||
result["projection"].setValue(frustum.getProjection());
|
||||
result["centerRadius"].setValue(frustum.getCenterRadius());
|
||||
result["fieldOfView"].setValue(frustum.getFieldOfView());
|
||||
result["aspectRatio"].setValue(frustum.getAspectRatio());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -351,7 +351,6 @@ void Avatar::simulate(float deltaTime, bool inView) {
|
|||
_jointDataSimulationRate.increment();
|
||||
|
||||
_skeletonModel->simulate(deltaTime, true);
|
||||
_skeletonModelSimulationRate.increment();
|
||||
|
||||
locationChanged(); // joints changed, so if there are any children, update them.
|
||||
_hasNewJointData = false;
|
||||
|
@ -367,8 +366,8 @@ void Avatar::simulate(float deltaTime, bool inView) {
|
|||
} else {
|
||||
// a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated.
|
||||
_skeletonModel->simulate(deltaTime, false);
|
||||
_skeletonModelSimulationRate.increment();
|
||||
}
|
||||
_skeletonModelSimulationRate.increment();
|
||||
}
|
||||
|
||||
// update animation for display name fade in/out
|
||||
|
@ -933,6 +932,10 @@ glm::vec3 Avatar::getDefaultJointTranslation(int index) const {
|
|||
}
|
||||
|
||||
glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const {
|
||||
if (index < 0) {
|
||||
index += numeric_limits<unsigned short>::max() + 1; // 65536
|
||||
}
|
||||
|
||||
switch(index) {
|
||||
case SENSOR_TO_WORLD_MATRIX_INDEX: {
|
||||
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
|
||||
|
@ -969,6 +972,10 @@ glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const {
|
|||
}
|
||||
|
||||
glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
|
||||
if (index < 0) {
|
||||
index += numeric_limits<unsigned short>::max() + 1; // 65536
|
||||
}
|
||||
|
||||
switch(index) {
|
||||
case SENSOR_TO_WORLD_MATRIX_INDEX: {
|
||||
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
|
||||
|
|
|
@ -85,7 +85,7 @@ AvatarManager::AvatarManager(QObject* parent) :
|
|||
// 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) {
|
||||
if (enabled) {
|
||||
removeAvatar(nodeID);
|
||||
removeAvatar(nodeID, KillAvatarReason::AvatarIgnored);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -157,15 +157,14 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
lock.unlock();
|
||||
|
||||
PerformanceTimer perfTimer("otherAvatars");
|
||||
uint64_t startTime = usecTimestampNow();
|
||||
|
||||
auto avatarMap = getHashCopy();
|
||||
QList<AvatarSharedPointer> avatarList = avatarMap.values();
|
||||
ViewFrustum cameraView;
|
||||
qApp->copyDisplayViewFrustum(cameraView);
|
||||
|
||||
std::priority_queue<AvatarPriority> sortedAvatars = AvatarData::sortAvatars(
|
||||
avatarList, cameraView,
|
||||
std::priority_queue<AvatarPriority> sortedAvatars;
|
||||
AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars,
|
||||
|
||||
[](AvatarSharedPointer avatar)->uint64_t{
|
||||
return std::static_pointer_cast<Avatar>(avatar)->getLastRenderUpdateTime();
|
||||
|
@ -194,10 +193,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
});
|
||||
|
||||
render::PendingChanges pendingChanges;
|
||||
const uint64_t RENDER_UPDATE_BUDGET = 1500; // usec
|
||||
const uint64_t MAX_UPDATE_BUDGET = 2000; // usec
|
||||
uint64_t renderExpiry = startTime + RENDER_UPDATE_BUDGET;
|
||||
uint64_t maxExpiry = startTime + MAX_UPDATE_BUDGET;
|
||||
uint64_t startTime = usecTimestampNow();
|
||||
const uint64_t UPDATE_BUDGET = 2000; // usec
|
||||
uint64_t updateExpiry = startTime + UPDATE_BUDGET;
|
||||
|
||||
int numAvatarsUpdated = 0;
|
||||
int numAVatarsNotUpdated = 0;
|
||||
|
@ -223,7 +221,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
|
||||
const float OUT_OF_VIEW_THRESHOLD = 0.5f * AvatarData::OUT_OF_VIEW_PENALTY;
|
||||
uint64_t now = usecTimestampNow();
|
||||
if (now < renderExpiry) {
|
||||
if (now < updateExpiry) {
|
||||
// we're within budget
|
||||
bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD;
|
||||
if (inView && avatar->hasNewJointData()) {
|
||||
|
@ -232,21 +230,13 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
avatar->simulate(deltaTime, inView);
|
||||
avatar->updateRenderItem(pendingChanges);
|
||||
avatar->setLastRenderUpdateTime(startTime);
|
||||
} else if (now < maxExpiry) {
|
||||
// we've spent most of our time budget, but we still simulate() the avatar as it if were out of view
|
||||
// --> some avatars may freeze until their priority trickles up
|
||||
bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD;
|
||||
if (inView && avatar->hasNewJointData()) {
|
||||
numAVatarsNotUpdated++;
|
||||
}
|
||||
avatar->simulate(deltaTime, false);
|
||||
} else {
|
||||
// we've spent ALL of our time budget --> bail on the rest of the avatar updates
|
||||
// we've spent our full time budget --> bail on the rest of the avatar updates
|
||||
// --> more avatars may freeze until their priority trickles up
|
||||
// --> some scale or fade animations may glitch
|
||||
// --> some avatar velocity measurements may be a little off
|
||||
|
||||
// HACK: no time simulate, but we will take the time to count how many were tragically missed
|
||||
// no time simulate, but we take the time to count how many were tragically missed
|
||||
bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD;
|
||||
if (!inView) {
|
||||
break;
|
||||
|
|
|
@ -95,12 +95,6 @@ void CauterizedModel::createCollisionRenderItemSet() {
|
|||
Model::createCollisionRenderItemSet();
|
||||
}
|
||||
|
||||
// Called within Model::simulate call, below.
|
||||
void CauterizedModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||
Model::updateRig(deltaTime, parentTransform);
|
||||
_needsUpdateClusterMatrices = true;
|
||||
}
|
||||
|
||||
void CauterizedModel::updateClusterMatrices() {
|
||||
PerformanceTimer perfTimer("CauterizedModel::updateClusterMatrices");
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@ public:
|
|||
void createVisibleRenderItemSet() override;
|
||||
void createCollisionRenderItemSet() override;
|
||||
|
||||
virtual void updateRig(float deltaTime, glm::mat4 parentTransform) override;
|
||||
virtual void updateClusterMatrices() override;
|
||||
void updateRenderItems() override;
|
||||
|
||||
|
|
|
@ -260,6 +260,11 @@ QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) {
|
|||
return AvatarData::toByteArrayStateful(dataDetail);
|
||||
}
|
||||
|
||||
void MyAvatar::resetSensorsAndBody() {
|
||||
qApp->getActiveDisplayPlugin()->resetSensors();
|
||||
reset(true, false, true);
|
||||
}
|
||||
|
||||
void MyAvatar::centerBody() {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "centerBody");
|
||||
|
@ -823,7 +828,7 @@ void MyAvatar::saveData() {
|
|||
auto hmdInterface = DependencyManager::get<HMDScriptingInterface>();
|
||||
_avatarEntitiesLock.withReadLock([&] {
|
||||
for (auto entityID : _avatarEntityData.keys()) {
|
||||
if (hmdInterface->getCurrentTabletUIID() == entityID) {
|
||||
if (hmdInterface->getCurrentTabletFrameID() == entityID) {
|
||||
// don't persist the tablet between domains / sessions
|
||||
continue;
|
||||
}
|
||||
|
@ -2410,6 +2415,10 @@ glm::mat4 MyAvatar::computeCameraRelativeHandControllerMatrix(const glm::mat4& c
|
|||
}
|
||||
|
||||
glm::quat MyAvatar::getAbsoluteJointRotationInObjectFrame(int index) const {
|
||||
if (index < 0) {
|
||||
index += numeric_limits<unsigned short>::max() + 1; // 65536
|
||||
}
|
||||
|
||||
switch (index) {
|
||||
case CONTROLLER_LEFTHAND_INDEX: {
|
||||
return getLeftHandControllerPoseInAvatarFrame().getRotation();
|
||||
|
@ -2443,6 +2452,10 @@ glm::quat MyAvatar::getAbsoluteJointRotationInObjectFrame(int index) const {
|
|||
}
|
||||
|
||||
glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
|
||||
if (index < 0) {
|
||||
index += numeric_limits<unsigned short>::max() + 1; // 65536
|
||||
}
|
||||
|
||||
switch (index) {
|
||||
case CONTROLLER_LEFTHAND_INDEX: {
|
||||
return getLeftHandControllerPoseInAvatarFrame().getTranslation();
|
||||
|
@ -2475,6 +2488,45 @@ glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
|
|||
}
|
||||
}
|
||||
|
||||
bool MyAvatar::pinJoint(int index, const glm::vec3& position, const glm::quat& orientation) {
|
||||
auto hipsIndex = getJointIndex("Hips");
|
||||
if (index != hipsIndex) {
|
||||
qWarning() << "Pinning is only supported for the hips joint at the moment.";
|
||||
return false;
|
||||
}
|
||||
|
||||
setPosition(position);
|
||||
setOrientation(orientation);
|
||||
|
||||
_rig->setMaxHipsOffsetLength(0.05f);
|
||||
|
||||
auto it = std::find(_pinnedJoints.begin(), _pinnedJoints.end(), index);
|
||||
if (it == _pinnedJoints.end()) {
|
||||
_pinnedJoints.push_back(index);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MyAvatar::clearPinOnJoint(int index) {
|
||||
auto it = std::find(_pinnedJoints.begin(), _pinnedJoints.end(), index);
|
||||
if (it != _pinnedJoints.end()) {
|
||||
_pinnedJoints.erase(it);
|
||||
|
||||
auto hipsIndex = getJointIndex("Hips");
|
||||
if (index == hipsIndex) {
|
||||
_rig->setMaxHipsOffsetLength(FLT_MAX);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
float MyAvatar::getIKErrorOnLastSolve() const {
|
||||
return _rig->getIKErrorOnLastSolve();
|
||||
}
|
||||
|
||||
// thread-safe
|
||||
void MyAvatar::addHoldAction(AvatarActionHold* holdAction) {
|
||||
std::lock_guard<std::mutex> guard(_holdActionsMutex);
|
||||
|
|
|
@ -99,6 +99,7 @@ public:
|
|||
|
||||
void reset(bool andRecenter = false, bool andReload = true, bool andHead = true);
|
||||
|
||||
Q_INVOKABLE void resetSensorsAndBody();
|
||||
Q_INVOKABLE void centerBody(); // thread-safe
|
||||
Q_INVOKABLE void clearIKJointLimitHistory(); // thread-safe
|
||||
|
||||
|
@ -216,6 +217,11 @@ public:
|
|||
virtual void clearJointData(int index) override;
|
||||
virtual void clearJointsData() override;
|
||||
|
||||
Q_INVOKABLE bool pinJoint(int index, const glm::vec3& position, const glm::quat& orientation);
|
||||
Q_INVOKABLE bool clearPinOnJoint(int index);
|
||||
|
||||
Q_INVOKABLE float getIKErrorOnLastSolve() const;
|
||||
|
||||
Q_INVOKABLE void useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName = QString());
|
||||
Q_INVOKABLE QUrl getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; }
|
||||
Q_INVOKABLE QString getFullAvatarModelName() const { return _fullAvatarModelName; }
|
||||
|
@ -527,6 +533,8 @@ private:
|
|||
bool didTeleport();
|
||||
bool getIsAway() const { return _isAway; }
|
||||
void setAway(bool value);
|
||||
|
||||
std::vector<int> _pinnedJoints;
|
||||
};
|
||||
|
||||
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);
|
||||
|
|
|
@ -166,7 +166,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
_rig->computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState);
|
||||
|
||||
// evaluate AnimGraph animation and update jointStates.
|
||||
CauterizedModel::updateRig(deltaTime, parentTransform);
|
||||
Model::updateRig(deltaTime, parentTransform);
|
||||
|
||||
Rig::EyeParameters eyeParams;
|
||||
eyeParams.worldHeadOrientation = headParams.worldHeadOrientation;
|
||||
|
@ -179,7 +179,9 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
|
||||
_rig->updateFromEyeParameters(eyeParams);
|
||||
} else {
|
||||
CauterizedModel::updateRig(deltaTime, parentTransform);
|
||||
// no need to call Model::updateRig() because otherAvatars get their joint state
|
||||
// copied directly from AvtarData::_jointData (there are no Rig animations to blend)
|
||||
_needsUpdateClusterMatrices = true;
|
||||
|
||||
// This is a little more work than we really want.
|
||||
//
|
||||
|
|
|
@ -29,8 +29,8 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen
|
|||
Q_PROPERTY(glm::quat orientation READ getOrientation)
|
||||
Q_PROPERTY(bool mounted READ isMounted)
|
||||
Q_PROPERTY(bool showTablet READ getShouldShowTablet)
|
||||
Q_PROPERTY(QUuid tabletID READ getCurrentTabletUIID WRITE setCurrentTabletUIID)
|
||||
Q_PROPERTY(QUuid homeButtonID READ getCurrentHomeButtonUUID WRITE setCurrentHomeButtonUUID)
|
||||
Q_PROPERTY(QUuid tabletID READ getCurrentTabletFrameID WRITE setCurrentTabletFrameID)
|
||||
Q_PROPERTY(QUuid homeButtonID READ getCurrentHomeButtonID WRITE setCurrentHomeButtonID)
|
||||
Q_PROPERTY(QUuid tabletScreenID READ getCurrentTabletScreenID WRITE setCurrentTabletScreenID)
|
||||
|
||||
public:
|
||||
|
@ -90,11 +90,11 @@ public:
|
|||
void setShouldShowTablet(bool value) { _showTablet = value; }
|
||||
bool getShouldShowTablet() const { return _showTablet; }
|
||||
|
||||
void setCurrentTabletUIID(QUuid tabletID) { _tabletUIID = tabletID; }
|
||||
QUuid getCurrentTabletUIID() const { return _tabletUIID; }
|
||||
void setCurrentTabletFrameID(QUuid tabletID) { _tabletUIID = tabletID; }
|
||||
QUuid getCurrentTabletFrameID() const { return _tabletUIID; }
|
||||
|
||||
void setCurrentHomeButtonUUID(QUuid homeButtonID) { _homeButtonID = homeButtonID; }
|
||||
QUuid getCurrentHomeButtonUUID() const { return _homeButtonID; }
|
||||
void setCurrentHomeButtonID(QUuid homeButtonID) { _homeButtonID = homeButtonID; }
|
||||
QUuid getCurrentHomeButtonID() const { return _homeButtonID; }
|
||||
|
||||
void setCurrentTabletScreenID(QUuid tabletID) { _tabletScreenID = tabletID; }
|
||||
QUuid getCurrentTabletScreenID() const { return _tabletScreenID; }
|
||||
|
|
|
@ -102,7 +102,11 @@ void setupPreferences() {
|
|||
auto setter = [](bool value) { qApp->setHmdTabletBecomesToolbarSetting(value); };
|
||||
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
|
||||
static const QString SNAPSHOTS { "Snapshots" };
|
||||
{
|
||||
|
|
|
@ -39,7 +39,8 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) :
|
|||
_isDashedLine(base3DOverlay->_isDashedLine),
|
||||
_ignoreRayIntersection(base3DOverlay->_ignoreRayIntersection),
|
||||
_drawInFront(base3DOverlay->_drawInFront),
|
||||
_isAA(base3DOverlay->_isAA)
|
||||
_isAA(base3DOverlay->_isAA),
|
||||
_isGrabbable(base3DOverlay->_isGrabbable)
|
||||
{
|
||||
setTransform(base3DOverlay->getTransform());
|
||||
}
|
||||
|
@ -59,15 +60,19 @@ QVariantMap convertOverlayLocationFromScriptSemantics(const QVariantMap& propert
|
|||
} else if (result["position"].isValid()) {
|
||||
glm::vec3 localPosition = SpatiallyNestable::worldToLocal(vec3FromVariant(result["position"]),
|
||||
parentID, parentJointIndex, success);
|
||||
result["position"] = vec3toVariant(localPosition);
|
||||
if (success) {
|
||||
result["position"] = vec3toVariant(localPosition);
|
||||
}
|
||||
}
|
||||
|
||||
if (result["localOrientation"].isValid()) {
|
||||
result["orientation"] = result["localOrientation"];
|
||||
} else if (result["orientation"].isValid()) {
|
||||
glm::quat localOrientation = SpatiallyNestable::worldToLocal(quatFromVariant(result["orientation"]),
|
||||
parentID, parentJointIndex, success);
|
||||
result["orientation"] = quatToVariant(localOrientation);
|
||||
parentID, parentJointIndex, success);
|
||||
if (success) {
|
||||
result["orientation"] = quatToVariant(localOrientation);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -125,6 +130,11 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
|
|||
needRenderItemUpdate = true;
|
||||
}
|
||||
|
||||
auto isGrabbable = properties["grabbable"];
|
||||
if (isGrabbable.isValid()) {
|
||||
setIsGrabbable(isGrabbable.toBool());
|
||||
}
|
||||
|
||||
if (properties["position"].isValid()) {
|
||||
setLocalPosition(vec3FromVariant(properties["position"]));
|
||||
needRenderItemUpdate = true;
|
||||
|
@ -227,6 +237,9 @@ QVariant Base3DOverlay::getProperty(const QString& property) {
|
|||
if (property == "drawInFront") {
|
||||
return _drawInFront;
|
||||
}
|
||||
if (property == "grabbable") {
|
||||
return _isGrabbable;
|
||||
}
|
||||
if (property == "parentID") {
|
||||
return getParentID();
|
||||
}
|
||||
|
@ -246,6 +259,8 @@ bool Base3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3
|
|||
}
|
||||
|
||||
void Base3DOverlay::locationChanged(bool tellPhysics) {
|
||||
SpatiallyNestable::locationChanged(tellPhysics);
|
||||
|
||||
auto itemID = getRenderItemID();
|
||||
if (render::Item::isValidID(itemID)) {
|
||||
render::ScenePointer scene = qApp->getMain3DScene();
|
||||
|
@ -253,8 +268,6 @@ void Base3DOverlay::locationChanged(bool tellPhysics) {
|
|||
pendingChanges.updateItem(itemID);
|
||||
scene->enqueuePendingChanges(pendingChanges);
|
||||
}
|
||||
// Overlays can't currently have children.
|
||||
// SpatiallyNestable::locationChanged(tellPhysics); // tell all the children, also
|
||||
}
|
||||
|
||||
void Base3DOverlay::parentDeleted() {
|
||||
|
|
|
@ -38,6 +38,7 @@ public:
|
|||
bool getIsSolidLine() const { return !_isDashedLine; }
|
||||
bool getIgnoreRayIntersection() const { return _ignoreRayIntersection; }
|
||||
bool getDrawInFront() const { return _drawInFront; }
|
||||
bool getIsGrabbable() const { return _isGrabbable; }
|
||||
|
||||
virtual bool isAA() const { return _isAA; }
|
||||
|
||||
|
@ -47,6 +48,7 @@ public:
|
|||
void setIgnoreRayIntersection(bool value) { _ignoreRayIntersection = value; }
|
||||
void setDrawInFront(bool value) { _drawInFront = value; }
|
||||
void setIsAA(bool value) { _isAA = value; }
|
||||
void setIsGrabbable(bool value) { _isGrabbable = value; }
|
||||
|
||||
virtual AABox getBounds() const override = 0;
|
||||
|
||||
|
@ -71,6 +73,7 @@ protected:
|
|||
bool _ignoreRayIntersection;
|
||||
bool _drawInFront;
|
||||
bool _isAA;
|
||||
bool _isGrabbable { false };
|
||||
};
|
||||
|
||||
#endif // hifi_Base3DOverlay_h
|
||||
|
|
|
@ -341,28 +341,18 @@ OverlayID Overlays::getOverlayAtPoint(const glm::vec2& point) {
|
|||
return UNKNOWN_OVERLAY_ID;
|
||||
}
|
||||
QMapIterator<OverlayID, Overlay::Pointer> i(_overlaysHUD);
|
||||
i.toBack();
|
||||
|
||||
const float LARGE_NEGATIVE_FLOAT = -9999999;
|
||||
glm::vec3 origin(pointCopy.x, pointCopy.y, LARGE_NEGATIVE_FLOAT);
|
||||
glm::vec3 direction(0, 0, 1);
|
||||
BoxFace thisFace;
|
||||
glm::vec3 thisSurfaceNormal;
|
||||
float distance;
|
||||
unsigned int bestStackOrder = 0;
|
||||
OverlayID bestOverlayID = UNKNOWN_OVERLAY_ID;
|
||||
|
||||
while (i.hasPrevious()) {
|
||||
i.previous();
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
OverlayID thisID = i.key();
|
||||
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 {
|
||||
if (!i.value()->is3D()) {
|
||||
auto thisOverlay = std::dynamic_pointer_cast<Overlay2D>(i.value());
|
||||
if (thisOverlay && thisOverlay->getVisible() && thisOverlay->isLoaded() &&
|
||||
thisOverlay->getBoundingRect().contains(pointCopy.x, pointCopy.y, false)) {
|
||||
|
@ -406,16 +396,25 @@ RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray,
|
|||
const QScriptValue& overlayIDsToInclude,
|
||||
const QScriptValue& overlayIDsToDiscard,
|
||||
bool visibleOnly, bool collidableOnly) {
|
||||
float bestDistance = std::numeric_limits<float>::max();
|
||||
bool bestIsFront = false;
|
||||
const QVector<OverlayID> overlaysToInclude = qVectorOverlayIDFromScriptValue(overlayIDsToInclude);
|
||||
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;
|
||||
QMapIterator<OverlayID, Overlay::Pointer> i(_overlaysWorld);
|
||||
i.toBack();
|
||||
while (i.hasPrevious()) {
|
||||
i.previous();
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
OverlayID thisID = i.key();
|
||||
auto thisOverlay = std::dynamic_pointer_cast<Base3DOverlay>(i.value());
|
||||
|
||||
|
@ -700,8 +699,9 @@ static PointerEvent::Button toPointerButton(const QMouseEvent& event) {
|
|||
}
|
||||
}
|
||||
|
||||
PointerEvent Overlays::calculatePointerEvent(Overlay::Pointer overlay, PickRay ray,
|
||||
RayToOverlayIntersectionResult rayPickResult, QMouseEvent* event, PointerEvent::EventType eventType) {
|
||||
PointerEvent Overlays::calculatePointerEvent(Overlay::Pointer overlay, PickRay ray,
|
||||
RayToOverlayIntersectionResult rayPickResult, QMouseEvent* event,
|
||||
PointerEvent::EventType eventType) {
|
||||
|
||||
auto thisOverlay = std::dynamic_pointer_cast<Web3DOverlay>(overlay);
|
||||
|
||||
|
@ -719,11 +719,41 @@ PointerEvent Overlays::calculatePointerEvent(Overlay::Pointer overlay, PickRay r
|
|||
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");
|
||||
|
||||
PickRay ray = qApp->computePickRay(event->x(), event->y());
|
||||
RayToOverlayIntersectionResult rayPickResult = findRayIntersection(ray);
|
||||
RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray);
|
||||
if (rayPickResult.intersects) {
|
||||
_currentClickingOnOverlayID = rayPickResult.overlayID;
|
||||
|
||||
|
@ -732,19 +762,18 @@ void Overlays::mousePressEvent(QMouseEvent* event) {
|
|||
if (thisOverlay) {
|
||||
auto pointerEvent = calculatePointerEvent(thisOverlay, ray, rayPickResult, event, PointerEvent::Press);
|
||||
emit mousePressOnOverlay(_currentClickingOnOverlayID, pointerEvent);
|
||||
} else {
|
||||
emit mousePressOffOverlay();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
emit mousePressOffOverlay();
|
||||
}
|
||||
emit mousePressOffOverlay();
|
||||
return false;
|
||||
}
|
||||
|
||||
void Overlays::mouseReleaseEvent(QMouseEvent* event) {
|
||||
bool Overlays::mouseReleaseEvent(QMouseEvent* event) {
|
||||
PerformanceTimer perfTimer("Overlays::mouseReleaseEvent");
|
||||
|
||||
PickRay ray = qApp->computePickRay(event->x(), event->y());
|
||||
RayToOverlayIntersectionResult rayPickResult = findRayIntersection(ray);
|
||||
RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray);
|
||||
if (rayPickResult.intersects) {
|
||||
|
||||
// Only Web overlays can have focus.
|
||||
|
@ -756,13 +785,14 @@ void Overlays::mouseReleaseEvent(QMouseEvent* event) {
|
|||
}
|
||||
|
||||
_currentClickingOnOverlayID = UNKNOWN_OVERLAY_ID;
|
||||
return false;
|
||||
}
|
||||
|
||||
void Overlays::mouseMoveEvent(QMouseEvent* event) {
|
||||
bool Overlays::mouseMoveEvent(QMouseEvent* event) {
|
||||
PerformanceTimer perfTimer("Overlays::mouseMoveEvent");
|
||||
|
||||
PickRay ray = qApp->computePickRay(event->x(), event->y());
|
||||
RayToOverlayIntersectionResult rayPickResult = findRayIntersection(ray);
|
||||
RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray);
|
||||
if (rayPickResult.intersects) {
|
||||
|
||||
// Only Web overlays can have focus.
|
||||
|
@ -802,4 +832,34 @@ void Overlays::mouseMoveEvent(QMouseEvent* event) {
|
|||
_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;
|
||||
}
|
||||
|
|
|
@ -100,9 +100,9 @@ public:
|
|||
OverlayID addOverlay(Overlay* overlay) { return addOverlay(Overlay::Pointer(overlay)); }
|
||||
OverlayID addOverlay(Overlay::Pointer overlay);
|
||||
|
||||
void mousePressEvent(QMouseEvent* event);
|
||||
void mouseReleaseEvent(QMouseEvent* event);
|
||||
void mouseMoveEvent(QMouseEvent* event);
|
||||
bool mousePressEvent(QMouseEvent* event);
|
||||
bool mouseReleaseEvent(QMouseEvent* event);
|
||||
bool mouseMoveEvent(QMouseEvent* event);
|
||||
|
||||
void cleanupAllOverlays();
|
||||
|
||||
|
@ -206,6 +206,16 @@ public slots:
|
|||
bool visibleOnly = 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
|
||||
* 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
|
||||
|
@ -317,6 +327,12 @@ private:
|
|||
|
||||
OverlayID _currentClickingOnOverlayID { 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
|
||||
|
|
|
@ -53,7 +53,7 @@ namespace render {
|
|||
return overlay->getBounds();
|
||||
}
|
||||
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_2D = 2;
|
||||
const int LAYER_3D_FRONT = 1;
|
||||
|
|
|
@ -189,6 +189,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
|
|||
}
|
||||
}
|
||||
}
|
||||
_maxErrorOnLastSolve = maxError;
|
||||
|
||||
// finally set the relative rotation of each tip to agree with absolute target rotation
|
||||
for (auto& target: targets) {
|
||||
|
@ -268,13 +269,13 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
|
|||
|
||||
glm::quat deltaRotation;
|
||||
if (targetType == IKTarget::Type::RotationAndPosition ||
|
||||
targetType == IKTarget::Type::HipsRelativeRotationAndPosition) {
|
||||
targetType == IKTarget::Type::HipsRelativeRotationAndPosition) {
|
||||
// compute the swing that would get get tip closer
|
||||
glm::vec3 targetLine = target.getTranslation() - jointPosition;
|
||||
|
||||
const float MIN_AXIS_LENGTH = 1.0e-4f;
|
||||
RotationConstraint* constraint = getConstraint(pivotIndex);
|
||||
if (constraint && constraint->isLowerSpine()) {
|
||||
if (constraint && constraint->isLowerSpine() && tipIndex != _headIndex) {
|
||||
// for these types of targets we only allow twist at the lower-spine
|
||||
// (this prevents the hand targets from bending the spine too much and thereby driving the hips too far)
|
||||
glm::vec3 twistAxis = absolutePoses[pivotIndex].trans() - absolutePoses[pivotsParentIndex].trans();
|
||||
|
@ -300,8 +301,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
|
|||
const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f;
|
||||
if (angle > MIN_ADJUSTMENT_ANGLE) {
|
||||
// reduce angle by a fraction (for stability)
|
||||
const float FRACTION = 0.5f;
|
||||
angle *= FRACTION;
|
||||
const float STABILITY_FRACTION = 0.5f;
|
||||
angle *= STABILITY_FRACTION;
|
||||
deltaRotation = glm::angleAxis(angle, axis);
|
||||
|
||||
// The swing will re-orient the tip but there will tend to be be a non-zero delta between the tip's
|
||||
|
@ -323,7 +324,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
|
|||
glm::vec3 axis = glm::normalize(deltaRotation * leverArm);
|
||||
swingTwistDecomposition(missingRotation, axis, swingPart, twistPart);
|
||||
float dotSign = copysignf(1.0f, twistPart.w);
|
||||
deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * twistPart, FRACTION)) * deltaRotation;
|
||||
const float LIMIT_LEAK_FRACTION = 0.1f;
|
||||
deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * twistPart, LIMIT_LEAK_FRACTION)) * deltaRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -486,7 +488,13 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
|||
// measure new _hipsOffset for next frame
|
||||
// by looking for discrepancies between where a targeted endEffector is
|
||||
// and where it wants to be (after IK solutions are done)
|
||||
glm::vec3 newHipsOffset = Vectors::ZERO;
|
||||
|
||||
// use weighted average between HMD and other targets
|
||||
float HMD_WEIGHT = 10.0f;
|
||||
float OTHER_WEIGHT = 1.0f;
|
||||
float totalWeight = 0.0f;
|
||||
|
||||
glm::vec3 additionalHipsOffset = Vectors::ZERO;
|
||||
for (auto& target: targets) {
|
||||
int targetIndex = target.getIndex();
|
||||
if (targetIndex == _headIndex && _headIndex != -1) {
|
||||
|
@ -497,32 +505,61 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
|||
glm::vec3 under = _skeleton->getAbsolutePose(_headIndex, underPoses).trans();
|
||||
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans();
|
||||
const float HEAD_OFFSET_SLAVE_FACTOR = 0.65f;
|
||||
newHipsOffset += HEAD_OFFSET_SLAVE_FACTOR * (actual - under);
|
||||
additionalHipsOffset += (OTHER_WEIGHT * HEAD_OFFSET_SLAVE_FACTOR) * (under- actual);
|
||||
totalWeight += OTHER_WEIGHT;
|
||||
} else if (target.getType() == IKTarget::Type::HmdHead) {
|
||||
// we want to shift the hips to bring the head to its designated position
|
||||
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans();
|
||||
_hipsOffset += target.getTranslation() - actual;
|
||||
// and ignore all other targets
|
||||
newHipsOffset = _hipsOffset;
|
||||
break;
|
||||
glm::vec3 thisOffset = target.getTranslation() - actual;
|
||||
glm::vec3 futureHipsOffset = _hipsOffset + thisOffset;
|
||||
if (glm::length(glm::vec2(futureHipsOffset.x, futureHipsOffset.z)) < _maxHipsOffsetLength) {
|
||||
// it is imperative to shift the hips and bring the head to its designated position
|
||||
// so we slam newHipsOffset here and ignore all other targets
|
||||
additionalHipsOffset = futureHipsOffset - _hipsOffset;
|
||||
totalWeight = 0.0f;
|
||||
break;
|
||||
} else {
|
||||
additionalHipsOffset += HMD_WEIGHT * (target.getTranslation() - actual);
|
||||
totalWeight += HMD_WEIGHT;
|
||||
}
|
||||
}
|
||||
} else if (target.getType() == IKTarget::Type::RotationAndPosition) {
|
||||
glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans();
|
||||
glm::vec3 targetPosition = target.getTranslation();
|
||||
newHipsOffset += targetPosition - actualPosition;
|
||||
additionalHipsOffset += OTHER_WEIGHT * (targetPosition - actualPosition);
|
||||
totalWeight += OTHER_WEIGHT;
|
||||
}
|
||||
}
|
||||
if (totalWeight > 1.0f) {
|
||||
additionalHipsOffset /= totalWeight;
|
||||
}
|
||||
|
||||
// Add downward pressure on the hips
|
||||
additionalHipsOffset *= 0.95f;
|
||||
additionalHipsOffset -= 1.0f;
|
||||
|
||||
// smooth transitions by relaxing _hipsOffset toward the new value
|
||||
const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.15f;
|
||||
const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.10f;
|
||||
float tau = dt < HIPS_OFFSET_SLAVE_TIMESCALE ? dt / HIPS_OFFSET_SLAVE_TIMESCALE : 1.0f;
|
||||
_hipsOffset += (newHipsOffset - _hipsOffset) * tau;
|
||||
_hipsOffset += additionalHipsOffset * tau;
|
||||
|
||||
// clamp the hips offset
|
||||
float hipsOffsetLength = glm::length(_hipsOffset);
|
||||
if (hipsOffsetLength > _maxHipsOffsetLength) {
|
||||
_hipsOffset *= _maxHipsOffsetLength / hipsOffsetLength;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return _relativePoses;
|
||||
}
|
||||
|
||||
void AnimInverseKinematics::setMaxHipsOffsetLength(float maxLength) {
|
||||
// manually adjust scale here
|
||||
const float METERS_TO_CENTIMETERS = 100.0f;
|
||||
_maxHipsOffsetLength = METERS_TO_CENTIMETERS * maxLength;
|
||||
}
|
||||
|
||||
void AnimInverseKinematics::clearIKJointLimitHistory() {
|
||||
for (auto& pair : _constraints) {
|
||||
pair.second->clearHistory();
|
||||
|
@ -740,7 +777,7 @@ void AnimInverseKinematics::initConstraints() {
|
|||
stConstraint->setTwistLimits(-MAX_SPINE_TWIST, MAX_SPINE_TWIST);
|
||||
|
||||
std::vector<float> minDots;
|
||||
const float MAX_SPINE_SWING = PI / 14.0f;
|
||||
const float MAX_SPINE_SWING = PI / 10.0f;
|
||||
minDots.push_back(cosf(MAX_SPINE_SWING));
|
||||
stConstraint->setSwingLimits(minDots);
|
||||
if (0 == baseName.compare("Spine1", Qt::CaseSensitive)
|
||||
|
@ -776,11 +813,11 @@ void AnimInverseKinematics::initConstraints() {
|
|||
} else if (0 == baseName.compare("Head", Qt::CaseSensitive)) {
|
||||
SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
|
||||
stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot());
|
||||
const float MAX_HEAD_TWIST = PI / 9.0f;
|
||||
const float MAX_HEAD_TWIST = PI / 6.0f;
|
||||
stConstraint->setTwistLimits(-MAX_HEAD_TWIST, MAX_HEAD_TWIST);
|
||||
|
||||
std::vector<float> minDots;
|
||||
const float MAX_HEAD_SWING = PI / 10.0f;
|
||||
const float MAX_HEAD_SWING = PI / 6.0f;
|
||||
minDots.push_back(cosf(MAX_HEAD_SWING));
|
||||
stConstraint->setSwingLimits(minDots);
|
||||
|
||||
|
|
|
@ -39,6 +39,10 @@ public:
|
|||
|
||||
void clearIKJointLimitHistory();
|
||||
|
||||
void setMaxHipsOffsetLength(float maxLength);
|
||||
|
||||
float getMaxErrorOnLastSolve() { return _maxErrorOnLastSolve; }
|
||||
|
||||
protected:
|
||||
void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses);
|
||||
void solveWithCyclicCoordinateDescent(const std::vector<IKTarget>& targets);
|
||||
|
@ -83,6 +87,7 @@ protected:
|
|||
|
||||
// experimental data for moving hips during IK
|
||||
glm::vec3 _hipsOffset { Vectors::ZERO };
|
||||
float _maxHipsOffsetLength{ FLT_MAX };
|
||||
int _headIndex { -1 };
|
||||
int _hipsIndex { -1 };
|
||||
int _hipsParentIndex { -1 };
|
||||
|
@ -90,6 +95,8 @@ protected:
|
|||
// _maxTargetIndex is tracked to help optimize the recalculation of absolute poses
|
||||
// during the the cyclic coordinate descent algorithm
|
||||
int _maxTargetIndex { 0 };
|
||||
|
||||
float _maxErrorOnLastSolve { FLT_MAX };
|
||||
};
|
||||
|
||||
#endif // hifi_AnimInverseKinematics_h
|
||||
|
|
|
@ -319,6 +319,39 @@ void Rig::clearIKJointLimitHistory() {
|
|||
}
|
||||
}
|
||||
|
||||
void Rig::setMaxHipsOffsetLength(float maxLength) {
|
||||
_maxHipsOffsetLength = maxLength;
|
||||
|
||||
if (_animNode) {
|
||||
_animNode->traverse([&](AnimNode::Pointer node) {
|
||||
auto ikNode = std::dynamic_pointer_cast<AnimInverseKinematics>(node);
|
||||
if (ikNode) {
|
||||
ikNode->setMaxHipsOffsetLength(_maxHipsOffsetLength);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
float Rig::getMaxHipsOffsetLength() const {
|
||||
return _maxHipsOffsetLength;
|
||||
}
|
||||
|
||||
float Rig::getIKErrorOnLastSolve() const {
|
||||
float result = 0.0f;
|
||||
|
||||
if (_animNode) {
|
||||
_animNode->traverse([&](AnimNode::Pointer node) {
|
||||
auto ikNode = std::dynamic_pointer_cast<AnimInverseKinematics>(node);
|
||||
if (ikNode) {
|
||||
result = ikNode->getMaxErrorOnLastSolve();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int Rig::getJointParentIndex(int childIndex) const {
|
||||
if (_animSkeleton && isIndexValid(childIndex)) {
|
||||
return _animSkeleton->getParentIndex(childIndex);
|
||||
|
@ -1274,39 +1307,50 @@ void Rig::copyJointsIntoJointData(QVector<JointData>& jointDataVec) const {
|
|||
void Rig::copyJointsFromJointData(const QVector<JointData>& jointDataVec) {
|
||||
PerformanceTimer perfTimer("copyJoints");
|
||||
PROFILE_RANGE(simulation_animation_detail, "copyJoints");
|
||||
if (_animSkeleton && jointDataVec.size() == (int)_internalPoseSet._relativePoses.size()) {
|
||||
// make a vector of rotations in absolute-geometry-frame
|
||||
const AnimPoseVec& absoluteDefaultPoses = _animSkeleton->getAbsoluteDefaultPoses();
|
||||
std::vector<glm::quat> rotations;
|
||||
rotations.reserve(absoluteDefaultPoses.size());
|
||||
const glm::quat rigToGeometryRot(glmExtractRotation(_rigToGeometryTransform));
|
||||
for (int i = 0; i < jointDataVec.size(); i++) {
|
||||
const JointData& data = jointDataVec.at(i);
|
||||
if (data.rotationSet) {
|
||||
// JointData rotations are in absolute rig-frame so we rotate them to absolute geometry-frame
|
||||
rotations.push_back(rigToGeometryRot * data.rotation);
|
||||
} else {
|
||||
rotations.push_back(absoluteDefaultPoses[i].rot());
|
||||
}
|
||||
}
|
||||
if (!_animSkeleton) {
|
||||
return;
|
||||
}
|
||||
if (jointDataVec.size() != (int)_internalPoseSet._relativePoses.size()) {
|
||||
// animations haven't fully loaded yet.
|
||||
_internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
}
|
||||
|
||||
// convert rotations from absolute to parent relative.
|
||||
_animSkeleton->convertAbsoluteRotationsToRelative(rotations);
|
||||
|
||||
// store new relative poses
|
||||
const AnimPoseVec& relativeDefaultPoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
for (int i = 0; i < jointDataVec.size(); i++) {
|
||||
const JointData& data = jointDataVec.at(i);
|
||||
_internalPoseSet._relativePoses[i].scale() = Vectors::ONE;
|
||||
_internalPoseSet._relativePoses[i].rot() = rotations[i];
|
||||
if (data.translationSet) {
|
||||
// JointData translations are in scaled relative-frame so we scale back to regular relative-frame
|
||||
_internalPoseSet._relativePoses[i].trans() = _invGeometryOffset.scale() * data.translation;
|
||||
} else {
|
||||
_internalPoseSet._relativePoses[i].trans() = relativeDefaultPoses[i].trans();
|
||||
}
|
||||
// make a vector of rotations in absolute-geometry-frame
|
||||
const AnimPoseVec& absoluteDefaultPoses = _animSkeleton->getAbsoluteDefaultPoses();
|
||||
std::vector<glm::quat> rotations;
|
||||
rotations.reserve(absoluteDefaultPoses.size());
|
||||
const glm::quat rigToGeometryRot(glmExtractRotation(_rigToGeometryTransform));
|
||||
for (int i = 0; i < jointDataVec.size(); i++) {
|
||||
const JointData& data = jointDataVec.at(i);
|
||||
if (data.rotationSet) {
|
||||
// JointData rotations are in absolute rig-frame so we rotate them to absolute geometry-frame
|
||||
rotations.push_back(rigToGeometryRot * data.rotation);
|
||||
} else {
|
||||
rotations.push_back(absoluteDefaultPoses[i].rot());
|
||||
}
|
||||
}
|
||||
|
||||
// convert rotations from absolute to parent relative.
|
||||
_animSkeleton->convertAbsoluteRotationsToRelative(rotations);
|
||||
|
||||
// store new relative poses
|
||||
const AnimPoseVec& relativeDefaultPoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
for (int i = 0; i < jointDataVec.size(); i++) {
|
||||
const JointData& data = jointDataVec.at(i);
|
||||
_internalPoseSet._relativePoses[i].scale() = Vectors::ONE;
|
||||
_internalPoseSet._relativePoses[i].rot() = rotations[i];
|
||||
if (data.translationSet) {
|
||||
// JointData translations are in scaled relative-frame so we scale back to regular relative-frame
|
||||
_internalPoseSet._relativePoses[i].trans() = _invGeometryOffset.scale() * data.translation;
|
||||
} else {
|
||||
_internalPoseSet._relativePoses[i].trans() = relativeDefaultPoses[i].trans();
|
||||
}
|
||||
}
|
||||
|
||||
// build absolute poses and copy to externalPoseSet
|
||||
buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses);
|
||||
QWriteLocker writeLock(&_externalPoseSetLock);
|
||||
_externalPoseSet = _internalPoseSet;
|
||||
}
|
||||
|
||||
void Rig::computeAvatarBoundingCapsule(
|
||||
|
|
|
@ -104,6 +104,10 @@ public:
|
|||
void clearJointAnimationPriority(int index);
|
||||
|
||||
void clearIKJointLimitHistory();
|
||||
void setMaxHipsOffsetLength(float maxLength);
|
||||
float getMaxHipsOffsetLength() const;
|
||||
|
||||
float getIKErrorOnLastSolve() const;
|
||||
|
||||
int getJointParentIndex(int childIndex) const;
|
||||
|
||||
|
@ -318,6 +322,8 @@ protected:
|
|||
bool _enabledAnimations { true };
|
||||
|
||||
mutable uint32_t _jointNameWarningCount { 0 };
|
||||
float _maxHipsOffsetLength { 1.0f };
|
||||
float _maxErrorOnLastSolve { 0.0f };
|
||||
|
||||
private:
|
||||
QMap<int, StateHandler> _stateHandlers;
|
||||
|
|
|
@ -2324,61 +2324,57 @@ float AvatarData::_avatarSortCoefficientSize { 0.5f };
|
|||
float AvatarData::_avatarSortCoefficientCenter { 0.25 };
|
||||
float AvatarData::_avatarSortCoefficientAge { 1.0f };
|
||||
|
||||
std::priority_queue<AvatarPriority> AvatarData::sortAvatars(
|
||||
QList<AvatarSharedPointer> avatarList,
|
||||
const ViewFrustum& cameraView,
|
||||
std::function<uint64_t(AvatarSharedPointer)> getLastUpdated,
|
||||
std::function<float(AvatarSharedPointer)> getBoundingRadius,
|
||||
std::function<bool(AvatarSharedPointer)> shouldIgnore) {
|
||||
void AvatarData::sortAvatars(
|
||||
QList<AvatarSharedPointer> avatarList,
|
||||
const ViewFrustum& cameraView,
|
||||
std::priority_queue<AvatarPriority>& sortedAvatarsOut,
|
||||
std::function<uint64_t(AvatarSharedPointer)> getLastUpdated,
|
||||
std::function<float(AvatarSharedPointer)> getBoundingRadius,
|
||||
std::function<bool(AvatarSharedPointer)> shouldIgnore) {
|
||||
|
||||
uint64_t startTime = usecTimestampNow();
|
||||
PROFILE_RANGE(simulation, "sort");
|
||||
uint64_t now = usecTimestampNow();
|
||||
|
||||
glm::vec3 frustumCenter = cameraView.getPosition();
|
||||
const glm::vec3& forward = cameraView.getDirection();
|
||||
for (int32_t i = 0; i < avatarList.size(); ++i) {
|
||||
const auto& avatar = avatarList.at(i);
|
||||
|
||||
std::priority_queue<AvatarPriority> sortedAvatars;
|
||||
{
|
||||
PROFILE_RANGE(simulation, "sort");
|
||||
for (int32_t i = 0; i < avatarList.size(); ++i) {
|
||||
const auto& avatar = avatarList.at(i);
|
||||
|
||||
if (shouldIgnore(avatar)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// priority = weighted linear combination of:
|
||||
// (a) apparentSize
|
||||
// (b) proximity to center of view
|
||||
// (c) time since last update
|
||||
glm::vec3 avatarPosition = avatar->getPosition();
|
||||
glm::vec3 offset = avatarPosition - frustumCenter;
|
||||
float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero
|
||||
|
||||
// FIXME - AvatarData has something equivolent to this
|
||||
float radius = getBoundingRadius(avatar);
|
||||
|
||||
const glm::vec3& forward = cameraView.getDirection();
|
||||
float apparentSize = 2.0f * radius / distance;
|
||||
float cosineAngle = glm::length(glm::dot(offset, forward) * forward) / distance;
|
||||
float age = (float)(startTime - getLastUpdated(avatar)) / (float)(USECS_PER_SECOND);
|
||||
|
||||
// NOTE: we are adding values of different units to get a single measure of "priority".
|
||||
// Thus we multiply each component by a conversion "weight" that scales its units relative to the others.
|
||||
// These weights are pure magic tuning and should be hard coded in the relation below,
|
||||
// but are currently exposed for anyone who would like to explore fine tuning:
|
||||
float priority = _avatarSortCoefficientSize * apparentSize
|
||||
+ _avatarSortCoefficientCenter * cosineAngle
|
||||
+ _avatarSortCoefficientAge * age;
|
||||
|
||||
// decrement priority of avatars outside keyhole
|
||||
if (distance > cameraView.getCenterRadius()) {
|
||||
if (!cameraView.sphereIntersectsFrustum(avatarPosition, radius)) {
|
||||
priority += OUT_OF_VIEW_PENALTY;
|
||||
}
|
||||
}
|
||||
sortedAvatars.push(AvatarPriority(avatar, priority));
|
||||
if (shouldIgnore(avatar)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// priority = weighted linear combination of:
|
||||
// (a) apparentSize
|
||||
// (b) proximity to center of view
|
||||
// (c) time since last update
|
||||
glm::vec3 avatarPosition = avatar->getPosition();
|
||||
glm::vec3 offset = avatarPosition - frustumCenter;
|
||||
float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero
|
||||
|
||||
// FIXME - AvatarData has something equivolent to this
|
||||
float radius = getBoundingRadius(avatar);
|
||||
|
||||
float apparentSize = 2.0f * radius / distance;
|
||||
float cosineAngle = glm::dot(offset, forward) / distance;
|
||||
float age = (float)(now - getLastUpdated(avatar)) / (float)(USECS_PER_SECOND);
|
||||
|
||||
// NOTE: we are adding values of different units to get a single measure of "priority".
|
||||
// Thus we multiply each component by a conversion "weight" that scales its units relative to the others.
|
||||
// These weights are pure magic tuning and should be hard coded in the relation below,
|
||||
// but are currently exposed for anyone who would like to explore fine tuning:
|
||||
float priority = _avatarSortCoefficientSize * apparentSize
|
||||
+ _avatarSortCoefficientCenter * cosineAngle
|
||||
+ _avatarSortCoefficientAge * age;
|
||||
|
||||
// decrement priority of avatars outside keyhole
|
||||
if (distance > cameraView.getCenterRadius()) {
|
||||
if (!cameraView.sphereIntersectsFrustum(avatarPosition, radius)) {
|
||||
priority += OUT_OF_VIEW_PENALTY;
|
||||
}
|
||||
}
|
||||
sortedAvatarsOut.push(AvatarPriority(avatar, priority));
|
||||
}
|
||||
return sortedAvatars;
|
||||
}
|
||||
|
||||
QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value) {
|
||||
|
|
|
@ -597,9 +597,10 @@ public:
|
|||
|
||||
static const float OUT_OF_VIEW_PENALTY;
|
||||
|
||||
static std::priority_queue<AvatarPriority> sortAvatars(
|
||||
static void sortAvatars(
|
||||
QList<AvatarSharedPointer> avatarList,
|
||||
const ViewFrustum& cameraView,
|
||||
std::priority_queue<AvatarPriority>& sortedAvatarsOut,
|
||||
std::function<uint64_t(AvatarSharedPointer)> getLastUpdated,
|
||||
std::function<float(AvatarSharedPointer)> getBoundingRadius,
|
||||
std::function<bool(AvatarSharedPointer)> shouldIgnore);
|
||||
|
|
|
@ -182,7 +182,7 @@ void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason remo
|
|||
|
||||
void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
|
||||
qCDebug(avatars) << "Removed avatar with UUID" << uuidStringWithoutCurlyBraces(removedAvatar->getSessionUUID())
|
||||
<< "from AvatarHashMap";
|
||||
<< "from AvatarHashMap" << removalReason;
|
||||
emit avatarRemovedEvent(removedAvatar->getSessionUUID());
|
||||
}
|
||||
|
||||
|
|
|
@ -944,7 +944,10 @@ void EntityTreeRenderer::entityScriptChanging(const EntityItemID& entityID, cons
|
|||
void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const bool reload, const bool unloadFirst) {
|
||||
if (_tree && !_shuttingDown) {
|
||||
EntityItemPointer entity = getTree()->findEntityByEntityItemID(entityID);
|
||||
bool shouldLoad = entity && entity->shouldPreloadScript() && _entitiesScriptEngine;
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
bool shouldLoad = entity->shouldPreloadScript() && _entitiesScriptEngine;
|
||||
QString scriptUrl = entity->getScript();
|
||||
if ((unloadFirst && shouldLoad) || scriptUrl.isEmpty()) {
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||
|
|
|
@ -266,6 +266,35 @@ void RenderablePolyVoxEntityItem::forEachVoxelValue(quint16 voxelXSize, quint16
|
|||
});
|
||||
}
|
||||
|
||||
QByteArray RenderablePolyVoxEntityItem::volDataToArray(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize) const {
|
||||
int totalSize = voxelXSize * voxelYSize * voxelZSize;
|
||||
QByteArray result = QByteArray(totalSize, '\0');
|
||||
int index = 0;
|
||||
int lowX = 0;
|
||||
int lowY = 0;
|
||||
int lowZ = 0;
|
||||
|
||||
withReadLock([&] {
|
||||
if (isEdged(_voxelSurfaceStyle)) {
|
||||
lowX++;
|
||||
lowY++;
|
||||
lowZ++;
|
||||
voxelXSize++;
|
||||
voxelYSize++;
|
||||
voxelZSize++;
|
||||
}
|
||||
|
||||
for (int z = lowZ; z < voxelZSize; z++) {
|
||||
for (int y = lowY; y < voxelYSize; y++) {
|
||||
for (int x = lowX; x < voxelXSize; x++) {
|
||||
result[index++] = _volData->getVoxelAt(x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) {
|
||||
bool result = false;
|
||||
|
@ -365,12 +394,28 @@ bool RenderablePolyVoxEntityItem::setSphere(glm::vec3 centerWorldCoords, float r
|
|||
}
|
||||
|
||||
glm::mat4 vtwMatrix = voxelToWorldMatrix();
|
||||
glm::mat4 wtvMatrix = glm::inverse(vtwMatrix);
|
||||
|
||||
// This three-level for loop iterates over every voxel in the volume
|
||||
glm::vec3 dimensions = getDimensions();
|
||||
glm::vec3 voxelSize = dimensions / _voxelVolumeSize;
|
||||
float smallestDimensionSize = voxelSize.x;
|
||||
smallestDimensionSize = glm::min(smallestDimensionSize, voxelSize.y);
|
||||
smallestDimensionSize = glm::min(smallestDimensionSize, voxelSize.z);
|
||||
|
||||
glm::vec3 maxRadiusInVoxelCoords = glm::vec3(radiusWorldCoords / smallestDimensionSize);
|
||||
glm::vec3 centerInVoxelCoords = wtvMatrix * glm::vec4(centerWorldCoords, 1.0f);
|
||||
|
||||
glm::vec3 low = glm::floor(centerInVoxelCoords - maxRadiusInVoxelCoords);
|
||||
glm::vec3 high = glm::ceil(centerInVoxelCoords + maxRadiusInVoxelCoords);
|
||||
|
||||
glm::ivec3 lowI = glm::clamp(low, glm::vec3(0.0f), _voxelVolumeSize);
|
||||
glm::ivec3 highI = glm::clamp(high, glm::vec3(0.0f), _voxelVolumeSize);
|
||||
|
||||
// This three-level for loop iterates over every voxel in the volume that might be in the sphere
|
||||
withWriteLock([&] {
|
||||
for (int z = 0; z < _voxelVolumeSize.z; z++) {
|
||||
for (int y = 0; y < _voxelVolumeSize.y; y++) {
|
||||
for (int x = 0; x < _voxelVolumeSize.x; x++) {
|
||||
for (int z = lowI.z; z < highI.z; z++) {
|
||||
for (int y = lowI.y; y < highI.y; y++) {
|
||||
for (int x = lowI.x; x < highI.x; x++) {
|
||||
// Store our current position as a vector...
|
||||
glm::vec4 pos(x + 0.5f, y + 0.5f, z + 0.5f, 1.0); // consider voxels cenetered on their coordinates
|
||||
// convert to world coordinates
|
||||
|
@ -392,6 +437,59 @@ bool RenderablePolyVoxEntityItem::setSphere(glm::vec3 centerWorldCoords, float r
|
|||
return result;
|
||||
}
|
||||
|
||||
bool RenderablePolyVoxEntityItem::setCapsule(glm::vec3 startWorldCoords, glm::vec3 endWorldCoords,
|
||||
float radiusWorldCoords, uint8_t toValue) {
|
||||
bool result = false;
|
||||
if (_locked) {
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::mat4 vtwMatrix = voxelToWorldMatrix();
|
||||
glm::mat4 wtvMatrix = glm::inverse(vtwMatrix);
|
||||
|
||||
glm::vec3 dimensions = getDimensions();
|
||||
glm::vec3 voxelSize = dimensions / _voxelVolumeSize;
|
||||
float smallestDimensionSize = voxelSize.x;
|
||||
smallestDimensionSize = glm::min(smallestDimensionSize, voxelSize.y);
|
||||
smallestDimensionSize = glm::min(smallestDimensionSize, voxelSize.z);
|
||||
|
||||
glm::vec3 maxRadiusInVoxelCoords = glm::vec3(radiusWorldCoords / smallestDimensionSize);
|
||||
|
||||
glm::vec3 startInVoxelCoords = wtvMatrix * glm::vec4(startWorldCoords, 1.0f);
|
||||
glm::vec3 endInVoxelCoords = wtvMatrix * glm::vec4(endWorldCoords, 1.0f);
|
||||
|
||||
glm::vec3 low = glm::min(glm::floor(startInVoxelCoords - maxRadiusInVoxelCoords),
|
||||
glm::floor(endInVoxelCoords - maxRadiusInVoxelCoords));
|
||||
glm::vec3 high = glm::max(glm::ceil(startInVoxelCoords + maxRadiusInVoxelCoords),
|
||||
glm::ceil(endInVoxelCoords + maxRadiusInVoxelCoords));
|
||||
|
||||
glm::ivec3 lowI = glm::clamp(low, glm::vec3(0.0f), _voxelVolumeSize);
|
||||
glm::ivec3 highI = glm::clamp(high, glm::vec3(0.0f), _voxelVolumeSize);
|
||||
|
||||
// This three-level for loop iterates over every voxel in the volume that might be in the capsule
|
||||
withWriteLock([&] {
|
||||
for (int z = lowI.z; z < highI.z; z++) {
|
||||
for (int y = lowI.y; y < highI.y; y++) {
|
||||
for (int x = lowI.x; x < highI.x; x++) {
|
||||
// Store our current position as a vector...
|
||||
glm::vec4 pos(x + 0.5f, y + 0.5f, z + 0.5f, 1.0); // consider voxels cenetered on their coordinates
|
||||
// convert to world coordinates
|
||||
glm::vec3 worldPos = glm::vec3(vtwMatrix * pos);
|
||||
if (pointInCapsule(worldPos, startWorldCoords, endWorldCoords, radiusWorldCoords)) {
|
||||
result |= setVoxelInternal(x, y, z, toValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (result) {
|
||||
compressVolumeDataAndSendEditPacket();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
class RaycastFunctor
|
||||
{
|
||||
public:
|
||||
|
@ -501,6 +599,9 @@ PolyVox::RaycastResult RenderablePolyVoxEntityItem::doRayCast(glm::vec4 originIn
|
|||
|
||||
// virtual
|
||||
ShapeType RenderablePolyVoxEntityItem::getShapeType() const {
|
||||
if (_collisionless) {
|
||||
return SHAPE_TYPE_NONE;
|
||||
}
|
||||
return SHAPE_TYPE_COMPOUND;
|
||||
}
|
||||
|
||||
|
@ -512,6 +613,11 @@ void RenderablePolyVoxEntityItem::updateRegistrationPoint(const glm::vec3& value
|
|||
}
|
||||
|
||||
bool RenderablePolyVoxEntityItem::isReadyToComputeShape() {
|
||||
ShapeType shapeType = getShapeType();
|
||||
if (shapeType == SHAPE_TYPE_NONE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// we determine if we are ready to compute the physics shape by actually doing so.
|
||||
// if _voxelDataDirty or _volDataDirty is set, don't do this yet -- wait for their
|
||||
// threads to finish before creating the collision shape.
|
||||
|
@ -524,6 +630,12 @@ bool RenderablePolyVoxEntityItem::isReadyToComputeShape() {
|
|||
}
|
||||
|
||||
void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) {
|
||||
ShapeType shapeType = getShapeType();
|
||||
if (shapeType == SHAPE_TYPE_NONE) {
|
||||
info.setParams(getShapeType(), 0.5f * getDimensions());
|
||||
return;
|
||||
}
|
||||
|
||||
// the shape was actually computed in isReadyToComputeShape. Just hand it off, here.
|
||||
withWriteLock([&] {
|
||||
info = _shapeInfo;
|
||||
|
@ -736,7 +848,7 @@ glm::vec3 RenderablePolyVoxEntityItem::localCoordsToVoxelCoords(glm::vec3& local
|
|||
|
||||
void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) {
|
||||
// This controls how many individual voxels are in the entity. This is unrelated to
|
||||
// the dimentions of the entity -- it defines the size of the arrays that hold voxel values.
|
||||
// the dimentions of the entity -- it defines the sizes of the arrays that hold voxel values.
|
||||
// In addition to setting the number of voxels, this is used in a few places for its
|
||||
// side-effect of allocating _volData to be the correct size.
|
||||
withWriteLock([&] {
|
||||
|
@ -807,7 +919,7 @@ uint8_t RenderablePolyVoxEntityItem::getVoxel(int x, int y, int z) {
|
|||
}
|
||||
|
||||
|
||||
uint8_t RenderablePolyVoxEntityItem::getVoxelInternal(int x, int y, int z) {
|
||||
uint8_t RenderablePolyVoxEntityItem::getVoxelInternal(int x, int y, int z) const {
|
||||
if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -949,17 +1061,8 @@ void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() {
|
|||
EntityTreePointer tree = element ? element->getTree() : nullptr;
|
||||
|
||||
QtConcurrent::run([voxelXSize, voxelYSize, voxelZSize, entity, tree] {
|
||||
int rawSize = voxelXSize * voxelYSize * voxelZSize;
|
||||
QByteArray uncompressedData = QByteArray(rawSize, '\0');
|
||||
|
||||
auto polyVoxEntity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity);
|
||||
polyVoxEntity->forEachVoxelValue(voxelXSize, voxelYSize, voxelZSize, [&] (int x, int y, int z, uint8_t uVoxelValue) {
|
||||
int uncompressedIndex =
|
||||
z * voxelYSize * voxelXSize +
|
||||
y * voxelXSize +
|
||||
x;
|
||||
uncompressedData[uncompressedIndex] = uVoxelValue;
|
||||
});
|
||||
QByteArray uncompressedData = polyVoxEntity->volDataToArray(voxelXSize, voxelYSize, voxelZSize);
|
||||
|
||||
QByteArray newVoxelData;
|
||||
QDataStream writer(&newVoxelData, QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
|
@ -1174,7 +1277,9 @@ void RenderablePolyVoxEntityItem::setMesh(model::MeshPointer mesh) {
|
|||
// this catches the payload from getMesh
|
||||
bool neighborsNeedUpdate;
|
||||
withWriteLock([&] {
|
||||
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
|
||||
if (!_collisionless) {
|
||||
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
|
||||
}
|
||||
_mesh = mesh;
|
||||
_meshDirty = true;
|
||||
_meshInitialized = true;
|
||||
|
|
|
@ -94,6 +94,8 @@ public:
|
|||
|
||||
// coords are in world-space
|
||||
virtual bool setSphere(glm::vec3 center, float radius, uint8_t toValue) override;
|
||||
virtual bool setCapsule(glm::vec3 startWorldCoords, glm::vec3 endWorldCoords,
|
||||
float radiusWorldCoords, uint8_t toValue) override;
|
||||
virtual bool setAll(uint8_t toValue) override;
|
||||
virtual bool setCuboid(const glm::vec3& lowPosition, const glm::vec3& cuboidSize, int toValue) override;
|
||||
|
||||
|
@ -128,12 +130,13 @@ public:
|
|||
void setVoxelsFromData(QByteArray uncompressedData, quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize);
|
||||
void forEachVoxelValue(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize,
|
||||
std::function<void(int, int, int, uint8_t)> thunk);
|
||||
QByteArray volDataToArray(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize) const;
|
||||
|
||||
void setMesh(model::MeshPointer mesh);
|
||||
void setCollisionPoints(ShapeInfo::PointCollection points, AABox box);
|
||||
PolyVox::SimpleVolume<uint8_t>* getVolData() { return _volData; }
|
||||
|
||||
uint8_t getVoxelInternal(int x, int y, int z);
|
||||
uint8_t getVoxelInternal(int x, int y, int z) const;
|
||||
bool setVoxelInternal(int x, int y, int z, uint8_t toValue);
|
||||
|
||||
void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; }); }
|
||||
|
|
|
@ -285,7 +285,7 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit
|
|||
desiredProperties = entity->getEntityProperties(params);
|
||||
desiredProperties.setHasProperty(PROP_LOCAL_POSITION);
|
||||
desiredProperties.setHasProperty(PROP_LOCAL_ROTATION);
|
||||
}
|
||||
}
|
||||
|
||||
results = entity->getProperties(desiredProperties);
|
||||
|
||||
|
@ -825,7 +825,7 @@ bool EntityScriptingInterface::setVoxels(QUuid entityID,
|
|||
|
||||
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
|
||||
if (!entity) {
|
||||
qCDebug(entities) << "EntityScriptingInterface::setVoxelSphere no entity with ID" << entityID;
|
||||
qCDebug(entities) << "EntityScriptingInterface::setVoxels no entity with ID" << entityID;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -887,24 +887,34 @@ bool EntityScriptingInterface::setVoxelSphere(QUuid entityID, const glm::vec3& c
|
|||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
||||
return setVoxels(entityID, [center, radius, value](PolyVoxEntityItem& polyVoxEntity) {
|
||||
return polyVoxEntity.setSphere(center, radius, value);
|
||||
});
|
||||
return polyVoxEntity.setSphere(center, radius, value);
|
||||
});
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::setVoxelCapsule(QUuid entityID,
|
||||
const glm::vec3& start, const glm::vec3& end,
|
||||
float radius, int value) {
|
||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
||||
return setVoxels(entityID, [start, end, radius, value](PolyVoxEntityItem& polyVoxEntity) {
|
||||
return polyVoxEntity.setCapsule(start, end, radius, value);
|
||||
});
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::setVoxel(QUuid entityID, const glm::vec3& position, int value) {
|
||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
||||
return setVoxels(entityID, [position, value](PolyVoxEntityItem& polyVoxEntity) {
|
||||
return polyVoxEntity.setVoxelInVolume(position, value);
|
||||
});
|
||||
return polyVoxEntity.setVoxelInVolume(position, value);
|
||||
});
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::setAllVoxels(QUuid entityID, int value) {
|
||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
||||
return setVoxels(entityID, [value](PolyVoxEntityItem& polyVoxEntity) {
|
||||
return polyVoxEntity.setAll(value);
|
||||
});
|
||||
return polyVoxEntity.setAll(value);
|
||||
});
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::setVoxelsInCuboid(QUuid entityID, const glm::vec3& lowPosition,
|
||||
|
@ -912,8 +922,8 @@ bool EntityScriptingInterface::setVoxelsInCuboid(QUuid entityID, const glm::vec3
|
|||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
||||
return setVoxels(entityID, [lowPosition, cuboidSize, value](PolyVoxEntityItem& polyVoxEntity) {
|
||||
return polyVoxEntity.setCuboid(lowPosition, cuboidSize, value);
|
||||
});
|
||||
return polyVoxEntity.setCuboid(lowPosition, cuboidSize, value);
|
||||
});
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::setAllPoints(QUuid entityID, const QVector<glm::vec3>& points) {
|
||||
|
@ -1020,25 +1030,25 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString,
|
|||
auto actionFactory = DependencyManager::get<EntityActionFactoryInterface>();
|
||||
bool success = false;
|
||||
actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
|
||||
// create this action even if the entity doesn't have physics info. it will often be the
|
||||
// case that a script adds an action immediately after an object is created, and the physicsInfo
|
||||
// is computed asynchronously.
|
||||
// if (!entity->getPhysicsInfo()) {
|
||||
// return false;
|
||||
// }
|
||||
EntityActionType actionType = EntityActionInterface::actionTypeFromString(actionTypeString);
|
||||
if (actionType == ACTION_TYPE_NONE) {
|
||||
return false;
|
||||
}
|
||||
EntityActionPointer action = actionFactory->factory(actionType, actionID, entity, arguments);
|
||||
if (!action) {
|
||||
return false;
|
||||
}
|
||||
action->setIsMine(true);
|
||||
success = entity->addAction(simulation, action);
|
||||
entity->grabSimulationOwnership();
|
||||
return false; // Physics will cause a packet to be sent, so don't send from here.
|
||||
});
|
||||
// create this action even if the entity doesn't have physics info. it will often be the
|
||||
// case that a script adds an action immediately after an object is created, and the physicsInfo
|
||||
// is computed asynchronously.
|
||||
// if (!entity->getPhysicsInfo()) {
|
||||
// return false;
|
||||
// }
|
||||
EntityActionType actionType = EntityActionInterface::actionTypeFromString(actionTypeString);
|
||||
if (actionType == ACTION_TYPE_NONE) {
|
||||
return false;
|
||||
}
|
||||
EntityActionPointer action = actionFactory->factory(actionType, actionID, entity, arguments);
|
||||
if (!action) {
|
||||
return false;
|
||||
}
|
||||
action->setIsMine(true);
|
||||
success = entity->addAction(simulation, action);
|
||||
entity->grabSimulationOwnership();
|
||||
return false; // Physics will cause a packet to be sent, so don't send from here.
|
||||
});
|
||||
if (success) {
|
||||
return actionID;
|
||||
}
|
||||
|
@ -1050,12 +1060,12 @@ bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid&
|
|||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
||||
return actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
|
||||
bool success = entity->updateAction(simulation, actionID, arguments);
|
||||
if (success) {
|
||||
entity->grabSimulationOwnership();
|
||||
}
|
||||
return success;
|
||||
});
|
||||
bool success = entity->updateAction(simulation, actionID, arguments);
|
||||
if (success) {
|
||||
entity->grabSimulationOwnership();
|
||||
}
|
||||
return success;
|
||||
});
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid& actionID) {
|
||||
|
@ -1063,13 +1073,13 @@ bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid&
|
|||
|
||||
bool success = false;
|
||||
actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
|
||||
success = entity->removeAction(simulation, actionID);
|
||||
if (success) {
|
||||
// reduce from grab to poke
|
||||
entity->pokeSimulationOwnership();
|
||||
}
|
||||
return false; // Physics will cause a packet to be sent, so don't send from here.
|
||||
});
|
||||
success = entity->removeAction(simulation, actionID);
|
||||
if (success) {
|
||||
// reduce from grab to poke
|
||||
entity->pokeSimulationOwnership();
|
||||
}
|
||||
return false; // Physics will cause a packet to be sent, so don't send from here.
|
||||
});
|
||||
return success;
|
||||
}
|
||||
|
||||
|
@ -1078,10 +1088,10 @@ QVector<QUuid> EntityScriptingInterface::getActionIDs(const QUuid& entityID) {
|
|||
|
||||
QVector<QUuid> result;
|
||||
actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
|
||||
QList<QUuid> actionIDs = entity->getActionIDs();
|
||||
result = QVector<QUuid>::fromList(actionIDs);
|
||||
return false; // don't send an edit packet
|
||||
});
|
||||
QList<QUuid> actionIDs = entity->getActionIDs();
|
||||
result = QVector<QUuid>::fromList(actionIDs);
|
||||
return false; // don't send an edit packet
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1090,9 +1100,9 @@ QVariantMap EntityScriptingInterface::getActionArguments(const QUuid& entityID,
|
|||
|
||||
QVariantMap result;
|
||||
actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
|
||||
result = entity->getActionArguments(actionID);
|
||||
return false; // don't send an edit packet
|
||||
});
|
||||
result = entity->getActionArguments(actionID);
|
||||
return false; // don't send an edit packet
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1419,8 +1429,7 @@ QVector<QUuid> EntityScriptingInterface::getChildrenIDsOfJoint(const QUuid& pare
|
|||
return;
|
||||
}
|
||||
parent->forEachChild([&](SpatiallyNestablePointer child) {
|
||||
if (child->getParentJointIndex() == jointIndex &&
|
||||
child->getNestableType() != NestableType::Overlay) {
|
||||
if (child->getParentJointIndex() == jointIndex) {
|
||||
result.push_back(child->getID());
|
||||
}
|
||||
});
|
||||
|
@ -1524,3 +1533,11 @@ QObject* EntityScriptingInterface::getWebViewRoot(const QUuid& entityID) {
|
|||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO move this someplace that makes more sense...
|
||||
bool EntityScriptingInterface::AABoxIntersectsCapsule(const glm::vec3& low, const glm::vec3& dimensions,
|
||||
const glm::vec3& start, const glm::vec3& end, float radius) {
|
||||
glm::vec3 penetration;
|
||||
AABox aaBox(low, dimensions);
|
||||
return aaBox.findCapsulePenetration(start, end, radius, penetration);
|
||||
}
|
||||
|
|
|
@ -223,6 +223,8 @@ public slots:
|
|||
Q_INVOKABLE bool getDrawZoneBoundaries() const;
|
||||
|
||||
Q_INVOKABLE bool setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value);
|
||||
Q_INVOKABLE bool setVoxelCapsule(QUuid entityID, const glm::vec3& start, const glm::vec3& end, float radius, int value);
|
||||
|
||||
Q_INVOKABLE bool setVoxel(QUuid entityID, const glm::vec3& position, int value);
|
||||
Q_INVOKABLE bool setAllVoxels(QUuid entityID, int value);
|
||||
Q_INVOKABLE bool setVoxelsInCuboid(QUuid entityID, const glm::vec3& lowPosition,
|
||||
|
@ -287,6 +289,10 @@ public slots:
|
|||
|
||||
Q_INVOKABLE QObject* getWebViewRoot(const QUuid& entityID);
|
||||
|
||||
Q_INVOKABLE bool AABoxIntersectsCapsule(const glm::vec3& low, const glm::vec3& dimensions,
|
||||
const glm::vec3& start, const glm::vec3& end, float radius);
|
||||
|
||||
|
||||
signals:
|
||||
void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
|
||||
|
||||
|
|
|
@ -86,6 +86,8 @@ class PolyVoxEntityItem : public EntityItem {
|
|||
|
||||
// coords are in world-space
|
||||
virtual bool setSphere(glm::vec3 center, float radius, uint8_t toValue) { return false; }
|
||||
virtual bool setCapsule(glm::vec3 startWorldCoords, glm::vec3 endWorldCoords,
|
||||
float radiusWorldCoords, uint8_t toValue) { return false; }
|
||||
virtual bool setAll(uint8_t toValue) { return false; }
|
||||
virtual bool setCuboid(const glm::vec3& lowPosition, const glm::vec3& cuboidSize, int value) { return false; }
|
||||
|
||||
|
|
|
@ -44,6 +44,8 @@ public:
|
|||
// Mutable, but must retain structure of vector
|
||||
using NetworkMaterials = std::vector<std::shared_ptr<NetworkMaterial>>;
|
||||
|
||||
bool isGeometryLoaded() const { return (bool)_fbxGeometry; }
|
||||
|
||||
const FBXGeometry& getFBXGeometry() const { return *_fbxGeometry; }
|
||||
const GeometryMeshes& getMeshes() const { return *_meshes; }
|
||||
const std::shared_ptr<const NetworkMaterial> getShapeMaterial(int shapeID) const;
|
||||
|
|
|
@ -436,8 +436,7 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm
|
|||
const double pStrength = 2.0;
|
||||
int width = image.width();
|
||||
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_RGBA8888);
|
||||
QImage result(width, height, QImage::Format_RGB888);
|
||||
|
||||
for (int i = 0; i < width; i++) {
|
||||
const int iNextClamped = clampPixelCoordinate(i + 1, width - 1);
|
||||
|
@ -477,17 +476,15 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm
|
|||
glm::normalize(v);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
gpu::Texture* theTexture = nullptr;
|
||||
if ((image.width() > 0) && (image.height() > 0)) {
|
||||
|
||||
|
||||
gpu::Element formatMip = gpu::Element::COLOR_RGBA_32;
|
||||
gpu::Element formatGPU = gpu::Element::COLOR_RGBA_32;
|
||||
gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB);
|
||||
gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB);
|
||||
|
||||
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
|
||||
theTexture->setSource(srcImageName);
|
||||
|
@ -495,7 +492,6 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm
|
|||
theTexture->assignStoredMip(0, image.byteCount(), image.constBits());
|
||||
generateMips(theTexture, image, true);
|
||||
|
||||
theTexture->setSource(srcImageName);
|
||||
theTexture = cacheTexture(theTexture->source(), theTexture, true, false);
|
||||
}
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort)
|
|||
setCustomDeleter([](Dependency* dependency){
|
||||
static_cast<NodeList*>(dependency)->deleteLater();
|
||||
});
|
||||
|
||||
|
||||
auto addressManager = DependencyManager::get<AddressManager>();
|
||||
|
||||
// handle domain change signals from AddressManager
|
||||
|
@ -85,8 +85,8 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort)
|
|||
connect(&_domainHandler, &DomainHandler::icePeerSocketsReceived, this, &NodeList::pingPunchForDomainServer);
|
||||
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
|
||||
// assume that we may need to send a new DS check in anytime a new keypair is generated
|
||||
|
||||
// assume that we may need to send a new DS check in anytime a new keypair is generated
|
||||
connect(accountManager.data(), &AccountManager::newKeypair, this, &NodeList::sendDomainServerCheckIn);
|
||||
|
||||
// clear out NodeList when login is finished
|
||||
|
@ -101,7 +101,7 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort)
|
|||
|
||||
// anytime we get a new node we may need to re-send our set of ignored node IDs to it
|
||||
connect(this, &LimitedNodeList::nodeActivated, this, &NodeList::maybeSendIgnoreSetToNode);
|
||||
|
||||
|
||||
// setup our timer to send keepalive pings (it's started and stopped on domain connect/disconnect)
|
||||
_keepAlivePingTimer.setInterval(KEEPALIVE_PING_INTERVAL_MS); // 1s, Qt::CoarseTimer acceptable
|
||||
connect(&_keepAlivePingTimer, &QTimer::timeout, this, &NodeList::sendKeepAlivePings);
|
||||
|
@ -161,11 +161,11 @@ qint64 NodeList::sendStatsToDomainServer(QJsonObject statsObject) {
|
|||
|
||||
void NodeList::timePingReply(ReceivedMessage& message, const SharedNodePointer& sendingNode) {
|
||||
PingType_t pingType;
|
||||
|
||||
|
||||
quint64 ourOriginalTime, othersReplyTime;
|
||||
|
||||
|
||||
message.seek(0);
|
||||
|
||||
|
||||
message.readPrimitive(&pingType);
|
||||
message.readPrimitive(&ourOriginalTime);
|
||||
message.readPrimitive(&othersReplyTime);
|
||||
|
@ -199,7 +199,7 @@ void NodeList::timePingReply(ReceivedMessage& message, const SharedNodePointer&
|
|||
}
|
||||
|
||||
void NodeList::processPingPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
|
||||
|
||||
// send back a reply
|
||||
auto replyPacket = constructPingReplyPacket(*message);
|
||||
const HifiSockAddr& senderSockAddr = message->getSenderSockAddr();
|
||||
|
@ -252,6 +252,11 @@ void NodeList::reset() {
|
|||
_personalMutedNodeIDs.clear();
|
||||
_personalMutedSetLock.unlock();
|
||||
|
||||
// lock and clear out set of avatarGains
|
||||
_avatarGainMapLock.lockForWrite();
|
||||
_avatarGainMap.clear();
|
||||
_avatarGainMapLock.unlock();
|
||||
|
||||
// refresh the owner UUID to the NULL UUID
|
||||
setSessionUUID(QUuid());
|
||||
|
||||
|
@ -329,7 +334,7 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
}
|
||||
|
||||
auto domainPacket = NLPacket::create(domainPacketType);
|
||||
|
||||
|
||||
QDataStream packetStream(domainPacket.get());
|
||||
|
||||
if (domainPacketType == PacketType::DomainConnectRequest) {
|
||||
|
@ -488,7 +493,7 @@ void NodeList::processDomainServerPathResponse(QSharedPointer<ReceivedMessage> m
|
|||
qCDebug(networking) << "Could not read query path from DomainServerPathQueryResponse. Bailing.";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
QString pathQuery = QString::fromUtf8(message->getRawMessage() + message->getPosition(), numPathBytes);
|
||||
message->seek(message->getPosition() + numPathBytes);
|
||||
|
||||
|
@ -500,10 +505,10 @@ void NodeList::processDomainServerPathResponse(QSharedPointer<ReceivedMessage> m
|
|||
qCDebug(networking) << "Could not read resulting viewpoint from DomainServerPathQueryReponse. Bailing";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// pull the viewpoint from the packet
|
||||
QString viewpoint = QString::fromUtf8(message->getRawMessage() + message->getPosition(), numViewpointBytes);
|
||||
|
||||
|
||||
// Hand it off to the AddressManager so it can handle it as a relative viewpoint
|
||||
if (DependencyManager::get<AddressManager>()->goToViewpointForPath(viewpoint, pathQuery)) {
|
||||
qCDebug(networking) << "Going to viewpoint" << viewpoint << "which was the lookup result for path" << pathQuery;
|
||||
|
@ -664,16 +669,16 @@ void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) {
|
|||
}
|
||||
|
||||
void NodeList::sendAssignment(Assignment& assignment) {
|
||||
|
||||
|
||||
PacketType assignmentPacketType = assignment.getCommand() == Assignment::CreateCommand
|
||||
? PacketType::CreateAssignment
|
||||
: PacketType::RequestAssignment;
|
||||
|
||||
auto assignmentPacket = NLPacket::create(assignmentPacketType);
|
||||
|
||||
|
||||
QDataStream packetStream(assignmentPacket.get());
|
||||
packetStream << assignment;
|
||||
|
||||
|
||||
sendPacket(std::move(assignmentPacket), _assignmentServerSocket);
|
||||
}
|
||||
|
||||
|
@ -833,7 +838,7 @@ void NodeList::ignoreNodeBySessionID(const QUuid& nodeID, bool ignoreEnabled) {
|
|||
_ignoredNodeIDs.insert(nodeID);
|
||||
}
|
||||
{
|
||||
QReadLocker personalMutedSetLocker{ &_personalMutedSetLock }; // read lock for insert
|
||||
QReadLocker personalMutedSetLocker{ &_personalMutedSetLock }; // read lock for insert
|
||||
// add this nodeID to our set of personal muted IDs
|
||||
_personalMutedNodeIDs.insert(nodeID);
|
||||
}
|
||||
|
@ -896,7 +901,7 @@ void NodeList::personalMuteNodeBySessionID(const QUuid& nodeID, bool muteEnabled
|
|||
|
||||
|
||||
if (muteEnabled) {
|
||||
QReadLocker personalMutedSetLocker{ &_personalMutedSetLock }; // read lock for insert
|
||||
QReadLocker personalMutedSetLocker{ &_personalMutedSetLock }; // read lock for insert
|
||||
// add this nodeID to our set of personal muted IDs
|
||||
_personalMutedNodeIDs.insert(nodeID);
|
||||
} else {
|
||||
|
@ -981,7 +986,7 @@ void NodeList::setAvatarGain(const QUuid& nodeID, float gain) {
|
|||
if (audioMixer) {
|
||||
// setup the packet
|
||||
auto setAvatarGainPacket = NLPacket::create(PacketType::PerAvatarGainSet, NUM_BYTES_RFC4122_UUID + sizeof(float), true);
|
||||
|
||||
|
||||
// write the node ID to the packet
|
||||
setAvatarGainPacket->write(nodeID.toRfc4122());
|
||||
// We need to convert the gain in dB (from the script) to an amplitude before packing it.
|
||||
|
@ -990,6 +995,9 @@ void NodeList::setAvatarGain(const QUuid& nodeID, float gain) {
|
|||
qCDebug(networking) << "Sending Set Avatar Gain packet UUID: " << uuidStringWithoutCurlyBraces(nodeID) << "Gain:" << gain;
|
||||
|
||||
sendPacket(std::move(setAvatarGainPacket), *audioMixer);
|
||||
QWriteLocker{ &_avatarGainMapLock };
|
||||
_avatarGainMap[nodeID] = gain;
|
||||
|
||||
} else {
|
||||
qWarning() << "Couldn't find audio mixer to send set gain request";
|
||||
}
|
||||
|
@ -998,6 +1006,15 @@ void NodeList::setAvatarGain(const QUuid& nodeID, float gain) {
|
|||
}
|
||||
}
|
||||
|
||||
float NodeList::getAvatarGain(const QUuid& nodeID) {
|
||||
QReadLocker{ &_avatarGainMapLock };
|
||||
auto it = _avatarGainMap.find(nodeID);
|
||||
if (it != _avatarGainMap.cend()) {
|
||||
return it->second;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
void NodeList::kickNodeBySessionID(const QUuid& nodeID) {
|
||||
// send a request to domain-server to kick the node with the given session ID
|
||||
// the domain-server will handle the persistence of the kick (via username or IP)
|
||||
|
@ -1036,7 +1053,7 @@ void NodeList::muteNodeBySessionID(const QUuid& nodeID) {
|
|||
mutePacket->write(nodeID.toRfc4122());
|
||||
|
||||
qCDebug(networking) << "Sending packet to mute node" << uuidStringWithoutCurlyBraces(nodeID);
|
||||
|
||||
|
||||
sendPacket(std::move(mutePacket), *audioMixer);
|
||||
} else {
|
||||
qWarning() << "Couldn't find audio mixer to send node mute request";
|
||||
|
|
|
@ -68,7 +68,7 @@ public:
|
|||
|
||||
void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; }
|
||||
void sendAssignment(Assignment& assignment);
|
||||
|
||||
|
||||
void setIsShuttingDown(bool isShuttingDown) { _isShuttingDown = isShuttingDown; }
|
||||
|
||||
void ignoreNodesInRadius(bool enabled = true);
|
||||
|
@ -83,6 +83,7 @@ public:
|
|||
void personalMuteNodeBySessionID(const QUuid& nodeID, bool muteEnabled);
|
||||
bool isPersonalMutingNode(const QUuid& nodeID) const;
|
||||
void setAvatarGain(const QUuid& nodeID, float gain);
|
||||
float getAvatarGain(const QUuid& nodeID);
|
||||
|
||||
void kickNodeBySessionID(const QUuid& nodeID);
|
||||
void muteNodeBySessionID(const QUuid& nodeID);
|
||||
|
@ -103,7 +104,7 @@ public slots:
|
|||
void processDomainServerPathResponse(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
void processDomainServerConnectionTokenPacket(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
|
||||
void processPingPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
void processPingReplyPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
|
||||
|
@ -131,11 +132,11 @@ private slots:
|
|||
void handleNodePingTimeout();
|
||||
|
||||
void pingPunchForDomainServer();
|
||||
|
||||
|
||||
void sendKeepAlivePings();
|
||||
|
||||
void maybeSendIgnoreSetToNode(SharedNodePointer node);
|
||||
|
||||
|
||||
private:
|
||||
NodeList() : LimitedNodeList(INVALID_PORT, INVALID_PORT) { assert(false); } // Not implemented, needed for DependencyManager templates compile
|
||||
NodeList(char ownerType, int socketListenPort = INVALID_PORT, int dtlsListenPort = INVALID_PORT);
|
||||
|
@ -148,7 +149,7 @@ private:
|
|||
void timePingReply(ReceivedMessage& message, const SharedNodePointer& sendingNode);
|
||||
|
||||
void sendDSPathQuery(const QString& newPath);
|
||||
|
||||
|
||||
void parseNodeFromPacketStream(QDataStream& packetStream);
|
||||
|
||||
void pingPunchForInactiveNode(const SharedNodePointer& node);
|
||||
|
@ -170,6 +171,8 @@ private:
|
|||
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDs;
|
||||
mutable QReadWriteLock _personalMutedSetLock;
|
||||
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _personalMutedNodeIDs;
|
||||
mutable QReadWriteLock _avatarGainMapLock;
|
||||
tbb::concurrent_unordered_map<QUuid, float, UUIDHasher> _avatarGainMap;
|
||||
|
||||
void sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationNode);
|
||||
Setting::Handle<bool> _ignoreRadiusEnabled { "IgnoreRadiusEnabled", true };
|
||||
|
|
|
@ -16,8 +16,8 @@ void UserActivityLoggerScriptingInterface::enabledEdit() {
|
|||
logAction("enabled_edit");
|
||||
}
|
||||
|
||||
void UserActivityLoggerScriptingInterface::openedTablet() {
|
||||
logAction("opened_tablet");
|
||||
void UserActivityLoggerScriptingInterface::openedTablet(bool visibleToOthers) {
|
||||
logAction("opened_tablet", { { "visible_to_others", visibleToOthers } });
|
||||
}
|
||||
|
||||
void UserActivityLoggerScriptingInterface::closedTablet() {
|
||||
|
|
|
@ -21,7 +21,7 @@ class UserActivityLoggerScriptingInterface : public QObject, public Dependency {
|
|||
Q_OBJECT
|
||||
public:
|
||||
Q_INVOKABLE void enabledEdit();
|
||||
Q_INVOKABLE void openedTablet();
|
||||
Q_INVOKABLE void openedTablet(bool visibleToOthers);
|
||||
Q_INVOKABLE void closedTablet();
|
||||
Q_INVOKABLE void openedMarketplace();
|
||||
Q_INVOKABLE void toggledAway(bool isAway);
|
||||
|
|
|
@ -114,7 +114,7 @@ public:
|
|||
void setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry,
|
||||
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; }
|
||||
bool isWireframe() const { return _isWireframe; }
|
||||
|
|
|
@ -47,6 +47,10 @@ void UsersScriptingInterface::setAvatarGain(const QUuid& nodeID, float gain) {
|
|||
DependencyManager::get<NodeList>()->setAvatarGain(nodeID, gain);
|
||||
}
|
||||
|
||||
float UsersScriptingInterface::getAvatarGain(const QUuid& nodeID) {
|
||||
return DependencyManager::get<NodeList>()->getAvatarGain(nodeID);
|
||||
}
|
||||
|
||||
void UsersScriptingInterface::kick(const QUuid& nodeID) {
|
||||
// ask the NodeList to kick the user with the given session ID
|
||||
DependencyManager::get<NodeList>()->kickNodeBySessionID(nodeID);
|
||||
|
@ -88,4 +92,4 @@ bool UsersScriptingInterface::getRequestsDomainListData() {
|
|||
}
|
||||
void UsersScriptingInterface::setRequestsDomainListData(bool isRequesting) {
|
||||
DependencyManager::get<NodeList>()->setRequestsDomainListData(isRequesting);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,6 +70,14 @@ public slots:
|
|||
*/
|
||||
void setAvatarGain(const QUuid& nodeID, float gain);
|
||||
|
||||
/**jsdoc
|
||||
* Gets an avatar's gain for you and you only.
|
||||
* @function Users.getAvatarGain
|
||||
* @param {nodeID} nodeID The node or session ID of the user whose gain you want to get.
|
||||
* @return {float} gain (in dB)
|
||||
*/
|
||||
float getAvatarGain(const QUuid& nodeID);
|
||||
|
||||
/**jsdoc
|
||||
* Kick another user.
|
||||
* @function Users.kick
|
||||
|
@ -150,7 +158,6 @@ signals:
|
|||
private:
|
||||
bool getRequestsDomainListData();
|
||||
void setRequestsDomainListData(bool requests);
|
||||
bool _requestsDomainListData;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -205,6 +205,33 @@ bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& directi
|
|||
return true;
|
||||
}
|
||||
|
||||
bool pointInSphere(const glm::vec3& origin, const glm::vec3& center, float radius) {
|
||||
glm::vec3 relativeOrigin = origin - center;
|
||||
float c = glm::dot(relativeOrigin, relativeOrigin) - radius * radius;
|
||||
return c <= 0.0f;
|
||||
}
|
||||
|
||||
|
||||
bool pointInCapsule(const glm::vec3& origin, const glm::vec3& start, const glm::vec3& end, float radius) {
|
||||
glm::vec3 relativeOrigin = origin - start;
|
||||
glm::vec3 relativeEnd = end - start;
|
||||
float capsuleLength = glm::length(relativeEnd);
|
||||
relativeEnd /= capsuleLength;
|
||||
float originProjection = glm::dot(relativeEnd, relativeOrigin);
|
||||
glm::vec3 constant = relativeOrigin - relativeEnd * originProjection;
|
||||
float c = glm::dot(constant, constant) - radius * radius;
|
||||
if (c < 0.0f) { // starts inside cylinder
|
||||
if (originProjection < 0.0f) { // below start
|
||||
return pointInSphere(origin, start, radius);
|
||||
} else if (originProjection > capsuleLength) { // above end
|
||||
return pointInSphere(origin, end, radius);
|
||||
} else { // between start and end
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool findRayCapsuleIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& start, const glm::vec3& end, float radius, float& distance) {
|
||||
if (start == end) {
|
||||
|
|
|
@ -73,6 +73,9 @@ glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3&
|
|||
bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& center, float radius, float& distance);
|
||||
|
||||
bool pointInSphere(const glm::vec3& origin, const glm::vec3& center, float radius);
|
||||
bool pointInCapsule(const glm::vec3& origin, const glm::vec3& start, const glm::vec3& end, float radius);
|
||||
|
||||
bool findRayCapsuleIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& start, const glm::vec3& end, float radius, float& distance);
|
||||
|
||||
|
|
|
@ -545,7 +545,7 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
|
|||
// HACK: when interface is launched and steam vr is NOT running, openvr will return bad HMD poses for a few frames
|
||||
// To workaround this, filter out any hmd poses that are obviously bad, i.e. beneath the floor.
|
||||
if (isBadPose(&nextSimPoseData.vrPoses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking)) {
|
||||
qDebug() << "WARNING: ignoring bad hmd pose from openvr";
|
||||
// qDebug() << "WARNING: ignoring bad hmd pose from openvr";
|
||||
|
||||
// use the last known good HMD pose
|
||||
nextSimPoseData.vrPoses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking = _lastGoodHMDPose;
|
||||
|
|
|
@ -20,7 +20,7 @@ var DEFAULT_SCRIPTS = [
|
|||
"system/bubble.js",
|
||||
"system/snapshot.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/marketplaces/marketplaces.js",
|
||||
"system/edit.js",
|
||||
|
@ -54,9 +54,6 @@ if (previousSetting === true || previousSetting === 'true') {
|
|||
previousSetting = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (Menu.menuExists(MENU_CATEGORY) && !Menu.menuItemExists(MENU_CATEGORY, MENU_ITEM)) {
|
||||
Menu.addMenuItem({
|
||||
menuName: MENU_CATEGORY,
|
||||
|
@ -78,11 +75,11 @@ function runDefaultsSeparately() {
|
|||
Script.load(DEFAULT_SCRIPTS[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// start all scripts
|
||||
if (Menu.isOptionChecked(MENU_ITEM)) {
|
||||
// we're debugging individual default scripts
|
||||
// so we load each into its own ScriptEngine instance
|
||||
debuggingDefaultScripts = true;
|
||||
runDefaultsSeparately();
|
||||
} else {
|
||||
// include all default scripts into this ScriptEngine
|
||||
|
@ -90,32 +87,14 @@ if (Menu.isOptionChecked(MENU_ITEM)) {
|
|||
}
|
||||
|
||||
function menuItemEvent(menuItem) {
|
||||
if (menuItem == MENU_ITEM) {
|
||||
|
||||
isChecked = Menu.isOptionChecked(MENU_ITEM);
|
||||
if (menuItem === MENU_ITEM) {
|
||||
var isChecked = Menu.isOptionChecked(MENU_ITEM);
|
||||
if (isChecked === true) {
|
||||
Settings.setValue(SETTINGS_KEY, true);
|
||||
} else if (isChecked === false) {
|
||||
Settings.setValue(SETTINGS_KEY, false);
|
||||
}
|
||||
Window.alert('You must reload all scripts for this to take effect.')
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Menu.triggerOption("Reload All Scripts");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,7 +105,6 @@ function removeMenuItem() {
|
|||
}
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
stopLoadedScripts();
|
||||
removeMenuItem();
|
||||
});
|
||||
|
||||
|
|
BIN
scripts/system/assets/models/teleport-seat.fbx
Normal file
BIN
scripts/system/assets/models/teleport-seat.fbx
Normal file
Binary file not shown.
|
@ -331,6 +331,12 @@ Grabber.prototype.pressEvent = function(event) {
|
|||
}
|
||||
|
||||
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
|
||||
if (!pickResults.intersects) {
|
||||
// didn't click on anything
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
/* global getEntityCustomData, flatten, Xform, Script, Quat, Vec3, MyAvatar, Entities, Overlays, Settings,
|
||||
Reticle, Controller, Camera, Messages, Mat4, getControllerWorldLocation, getGrabPointSphereOffset, setGrabCommunications,
|
||||
Menu, HMD */
|
||||
Menu, HMD, isInEditMode */
|
||||
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
@ -399,7 +399,7 @@ function entityHasActions(entityID) {
|
|||
|
||||
function findRayIntersection(pickRay, precise, include, exclude) {
|
||||
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))) {
|
||||
return entities;
|
||||
}
|
||||
|
@ -644,6 +644,7 @@ EquipHotspotBuddy.prototype.updateHotspot = function(hotspot, timestamp) {
|
|||
|
||||
// override default sphere with a user specified model, if it exists.
|
||||
overlayInfoSet.overlays.push(Overlays.addOverlay("model", {
|
||||
name: "hotspot overlay",
|
||||
url: hotspot.modelURL ? hotspot.modelURL : DEFAULT_SPHERE_MODEL_URL,
|
||||
position: hotspot.worldPosition,
|
||||
rotation: {
|
||||
|
@ -776,7 +777,7 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.actionID = null; // action this script created...
|
||||
this.grabbedEntity = null; // on this entity.
|
||||
this.grabbedThingID = null; // on this entity.
|
||||
this.grabbedOverlay = null;
|
||||
this.state = STATE_OFF;
|
||||
this.pointer = null; // entity-id of line object
|
||||
|
@ -853,14 +854,19 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.callEntityMethodOnGrabbed = function(entityMethodName) {
|
||||
if (this.grabbedIsOverlay) {
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
if ((isInEditMode() && this.grabbedEntity !== HMD.tabletID )&& (newState !== STATE_OFF &&
|
||||
newState !== STATE_SEARCHING &&
|
||||
newState !== STATE_OVERLAY_STYLUS_TOUCHING)) {
|
||||
if ((isInEditMode() && this.grabbedThingID !== HMD.tabletID) &&
|
||||
(newState !== STATE_OFF &&
|
||||
newState !== STATE_SEARCHING &&
|
||||
newState !== STATE_OVERLAY_STYLUS_TOUCHING &&
|
||||
newState !== STATE_OVERLAY_LASER_TOUCHING)) {
|
||||
return;
|
||||
}
|
||||
setGrabCommunications((newState === STATE_DISTANCE_HOLDING) || (newState === STATE_NEAR_GRABBING));
|
||||
|
@ -903,6 +909,7 @@ function MyController(hand) {
|
|||
|
||||
if (!this.grabPointSphere) {
|
||||
this.grabPointSphere = Overlays.addOverlay("sphere", {
|
||||
name: "grabPointSphere",
|
||||
localPosition: getGrabPointSphereOffset(this.handToController()),
|
||||
localRotation: { x: 0, y: 0, z: 0, w: 1 },
|
||||
dimensions: GRAB_POINT_SPHERE_RADIUS * 2,
|
||||
|
@ -933,6 +940,7 @@ function MyController(hand) {
|
|||
var brightColor = colorPow(color, 0.06);
|
||||
if (this.searchSphere === null) {
|
||||
var sphereProperties = {
|
||||
name: "searchSphere",
|
||||
position: location,
|
||||
rotation: rotation,
|
||||
outerRadius: size * 1.2,
|
||||
|
@ -955,7 +963,8 @@ function MyController(hand) {
|
|||
innerAlpha: 1.0,
|
||||
outerAlpha: 0.0,
|
||||
outerRadius: size * 1.2,
|
||||
visible: true
|
||||
visible: true,
|
||||
ignoreRayIntersection: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -966,6 +975,7 @@ function MyController(hand) {
|
|||
}
|
||||
|
||||
var stylusProperties = {
|
||||
name: "stylus",
|
||||
url: Script.resourcesPath() + "meshes/tablet-stylus-fat.fbx",
|
||||
localPosition: Vec3.sum({ x: 0.0,
|
||||
y: WEB_TOUCH_Y_OFFSET,
|
||||
|
@ -1000,6 +1010,7 @@ function MyController(hand) {
|
|||
this.overlayLineOn = function(closePoint, farPoint, color) {
|
||||
if (this.overlayLine === null) {
|
||||
var lineProperties = {
|
||||
name: "line",
|
||||
glow: 1.0,
|
||||
start: closePoint,
|
||||
end: farPoint,
|
||||
|
@ -1175,6 +1186,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) {
|
||||
this.showStylus();
|
||||
var rayPickInfo = this.calcRayPickInfo(this.hand);
|
||||
|
@ -1423,7 +1441,7 @@ function MyController(hand) {
|
|||
|
||||
var okToEquipFromOtherHand = ((this.getOtherHandController().state == STATE_NEAR_GRABBING ||
|
||||
this.getOtherHandController().state == STATE_DISTANCE_HOLDING) &&
|
||||
this.getOtherHandController().grabbedEntity == hotspot.entityID);
|
||||
this.getOtherHandController().grabbedThingID == hotspot.entityID);
|
||||
var hasParent = true;
|
||||
if (props.parentID === NULL_UUID) {
|
||||
hasParent = false;
|
||||
|
@ -1581,7 +1599,7 @@ function MyController(hand) {
|
|||
|
||||
var farSearching = this.triggerSmoothedSqueezed() && (Date.now() - this.searchStartTime > FAR_SEARCH_DELAY);
|
||||
|
||||
this.grabbedEntity = null;
|
||||
this.grabbedThingID = null;
|
||||
this.grabbedOverlay = null;
|
||||
this.isInitialGrab = false;
|
||||
this.preparingHoldRelease = false;
|
||||
|
@ -1589,7 +1607,7 @@ function MyController(hand) {
|
|||
this.checkForUnexpectedChildren();
|
||||
|
||||
if ((this.triggerSmoothedReleased() && this.secondaryReleased())) {
|
||||
this.grabbedEntity = null;
|
||||
this.grabbedThingID = null;
|
||||
this.setState(STATE_OFF, "trigger released");
|
||||
return;
|
||||
}
|
||||
|
@ -1610,8 +1628,9 @@ function MyController(hand) {
|
|||
if (potentialEquipHotspot) {
|
||||
if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && holdEnabled) {
|
||||
this.grabbedHotspot = potentialEquipHotspot;
|
||||
this.grabbedEntity = potentialEquipHotspot.entityID;
|
||||
this.setState(STATE_HOLD, "equipping '" + entityPropertiesCache.getProps(this.grabbedEntity).name + "'");
|
||||
this.grabbedThingID = potentialEquipHotspot.entityID;
|
||||
this.grabbedIsOverlay = false;
|
||||
this.setState(STATE_HOLD, "equipping '" + entityPropertiesCache.getProps(this.grabbedThingID).name + "'");
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -1622,6 +1641,11 @@ function MyController(hand) {
|
|||
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) {
|
||||
this.intersectionDistance = rayPickInfo.distance;
|
||||
if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) {
|
||||
|
@ -1633,6 +1657,23 @@ function MyController(hand) {
|
|||
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;
|
||||
if (grabbableEntities.length > 0) {
|
||||
// sort by distance
|
||||
|
@ -1643,7 +1684,8 @@ function MyController(hand) {
|
|||
});
|
||||
entity = grabbableEntities[0];
|
||||
name = entityPropertiesCache.getProps(entity).name;
|
||||
this.grabbedEntity = entity;
|
||||
this.grabbedThingID = entity;
|
||||
this.grabbedIsOverlay = false;
|
||||
if (this.entityWantsTrigger(entity)) {
|
||||
if (this.triggerSmoothedGrab()) {
|
||||
this.setState(STATE_NEAR_TRIGGER, "near trigger '" + name + "'");
|
||||
|
@ -1654,7 +1696,7 @@ function MyController(hand) {
|
|||
} else {
|
||||
// If near something grabbable, grab it!
|
||||
if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && nearGrabEnabled) {
|
||||
this.setState(STATE_NEAR_GRABBING, "near grab '" + name + "'");
|
||||
this.setState(STATE_NEAR_GRABBING, "near grab entity '" + name + "'");
|
||||
return;
|
||||
} else {
|
||||
// potentialNearGrabEntity = entity;
|
||||
|
@ -1677,7 +1719,8 @@ function MyController(hand) {
|
|||
name = entityPropertiesCache.getProps(entity).name;
|
||||
if (this.entityWantsTrigger(entity)) {
|
||||
if (this.triggerSmoothedGrab()) {
|
||||
this.grabbedEntity = entity;
|
||||
this.grabbedThingID = entity;
|
||||
this.grabbedIsOverlay = false;
|
||||
this.setState(STATE_FAR_TRIGGER, "far trigger '" + name + "'");
|
||||
return;
|
||||
} else {
|
||||
|
@ -1685,7 +1728,8 @@ function MyController(hand) {
|
|||
}
|
||||
} else if (this.entityIsDistanceGrabbable(rayPickInfo.entityID, handPosition)) {
|
||||
if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled && farSearching) {
|
||||
this.grabbedEntity = entity;
|
||||
this.grabbedThingID = entity;
|
||||
this.grabbedIsOverlay = false;
|
||||
this.grabbedDistance = rayPickInfo.distance;
|
||||
this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'");
|
||||
return;
|
||||
|
@ -1765,7 +1809,8 @@ function MyController(hand) {
|
|||
Entities.sendHoverOverEntity(entity, pointerEvent);
|
||||
}
|
||||
|
||||
this.grabbedEntity = entity;
|
||||
this.grabbedThingID = entity;
|
||||
this.grabbedIsOverlay = false;
|
||||
this.setState(STATE_ENTITY_STYLUS_TOUCHING, "begin touching entity '" + name + "'");
|
||||
return true;
|
||||
|
||||
|
@ -1893,7 +1938,8 @@ function MyController(hand) {
|
|||
}
|
||||
|
||||
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 + "'");
|
||||
return true;
|
||||
}
|
||||
|
@ -2004,7 +2050,7 @@ function MyController(hand) {
|
|||
var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix());
|
||||
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();
|
||||
|
||||
// add the action and initialize some variables
|
||||
|
@ -2034,7 +2080,7 @@ function MyController(hand) {
|
|||
var timeScale = this.distanceGrabTimescale(this.mass, distanceToObject);
|
||||
|
||||
this.actionID = NULL_UUID;
|
||||
this.actionID = Entities.addAction("spring", this.grabbedEntity, {
|
||||
this.actionID = Entities.addAction("spring", this.grabbedThingID, {
|
||||
targetPosition: this.currentObjectPosition,
|
||||
linearTimeScale: timeScale,
|
||||
targetRotation: this.currentObjectRotation,
|
||||
|
@ -2059,12 +2105,12 @@ function MyController(hand) {
|
|||
this.ensureDynamic = function() {
|
||||
// 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.
|
||||
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) {
|
||||
var velocity = props.velocity;
|
||||
if (Vec3.length(velocity) < 0.05) { // see EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD
|
||||
velocity = { x: 0.0, y: 0.2, z:0.0 };
|
||||
Entities.editEntity(this.grabbedEntity, { velocity: velocity });
|
||||
Entities.editEntity(this.grabbedThingID, { velocity: velocity });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -2086,7 +2132,7 @@ function MyController(hand) {
|
|||
var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix());
|
||||
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 deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds
|
||||
|
@ -2141,7 +2187,7 @@ function MyController(hand) {
|
|||
newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition);
|
||||
|
||||
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) {
|
||||
// mix in head motion
|
||||
if (MOVE_WITH_HEAD) {
|
||||
|
@ -2170,7 +2216,7 @@ function MyController(hand) {
|
|||
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 success = Entities.updateAction(this.grabbedEntity, this.actionID, {
|
||||
var success = Entities.updateAction(this.grabbedThingID, this.actionID, {
|
||||
targetPosition: newTargetPosition,
|
||||
linearTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject),
|
||||
targetRotation: this.currentObjectRotation,
|
||||
|
@ -2187,7 +2233,7 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.setupHoldAction = function() {
|
||||
this.actionID = Entities.addAction("hold", this.grabbedEntity, {
|
||||
this.actionID = Entities.addAction("hold", this.grabbedThingID, {
|
||||
hand: this.hand === RIGHT_HAND ? "right" : "left",
|
||||
timeScale: NEAR_GRABBING_ACTION_TIMEFRAME,
|
||||
relativePosition: this.offsetPosition,
|
||||
|
@ -2277,17 +2323,30 @@ function MyController(hand) {
|
|||
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
|
||||
|
||||
if (this.entityActivated) {
|
||||
var saveGrabbedID = this.grabbedEntity;
|
||||
var saveGrabbedID = this.grabbedThingID;
|
||||
this.release();
|
||||
this.grabbedEntity = saveGrabbedID;
|
||||
this.grabbedThingID = saveGrabbedID;
|
||||
}
|
||||
|
||||
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
|
||||
if (FORCE_IGNORE_IK) {
|
||||
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 {
|
||||
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
|
||||
this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true;
|
||||
grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES);
|
||||
if (FORCE_IGNORE_IK) {
|
||||
this.ignoreIK = true;
|
||||
} else {
|
||||
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedThingID, DEFAULT_GRABBABLE_DATA);
|
||||
this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true;
|
||||
}
|
||||
}
|
||||
|
||||
var handRotation;
|
||||
|
@ -2326,7 +2385,8 @@ function MyController(hand) {
|
|||
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) {
|
||||
// grab entity via action
|
||||
if (!this.setupHoldAction()) {
|
||||
|
@ -2334,7 +2394,7 @@ function MyController(hand) {
|
|||
}
|
||||
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
|
||||
action: 'grab',
|
||||
grabbedEntity: this.grabbedEntity,
|
||||
grabbedEntity: this.grabbedThingID,
|
||||
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
|
||||
}));
|
||||
} else {
|
||||
|
@ -2359,29 +2419,36 @@ function MyController(hand) {
|
|||
reparentProps.localPosition = this.offsetPosition;
|
||||
reparentProps.localRotation = this.offsetRotation;
|
||||
}
|
||||
Entities.editEntity(this.grabbedEntity, reparentProps);
|
||||
|
||||
if (this.grabbedIsOverlay) {
|
||||
Overlays.editOverlay(this.grabbedThingID, reparentProps);
|
||||
} else {
|
||||
Entities.editEntity(this.grabbedThingID, reparentProps);
|
||||
}
|
||||
|
||||
if (this.thisHandIsParent(grabbedProperties)) {
|
||||
// this should never happen, but if it does, don't set previous parent to be this hand.
|
||||
// this.previousParentID[this.grabbedEntity] = NULL;
|
||||
// this.previousParentJointIndex[this.grabbedEntity] = -1;
|
||||
// this.previousParentID[this.grabbedThingID] = NULL;
|
||||
// this.previousParentJointIndex[this.grabbedThingID] = -1;
|
||||
} else {
|
||||
this.previousParentID[this.grabbedEntity] = grabbedProperties.parentID;
|
||||
this.previousParentJointIndex[this.grabbedEntity] = grabbedProperties.parentJointIndex;
|
||||
this.previousParentID[this.grabbedThingID] = grabbedProperties.parentID;
|
||||
this.previousParentJointIndex[this.grabbedThingID] = grabbedProperties.parentJointIndex;
|
||||
}
|
||||
|
||||
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
|
||||
action: 'equip',
|
||||
grabbedEntity: this.grabbedEntity,
|
||||
grabbedEntity: this.grabbedThingID,
|
||||
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
|
||||
}));
|
||||
}
|
||||
|
||||
Entities.editEntity(this.grabbedEntity, {
|
||||
velocity: { x: 0, y: 0, z: 0 },
|
||||
angularVelocity: { x: 0, y: 0, z: 0 },
|
||||
// dynamic: false
|
||||
});
|
||||
if (!this.grabbedIsOverlay) {
|
||||
Entities.editEntity(this.grabbedThingID, {
|
||||
velocity: { x: 0, y: 0, z: 0 },
|
||||
angularVelocity: { x: 0, y: 0, z: 0 },
|
||||
// dynamic: false
|
||||
});
|
||||
}
|
||||
|
||||
if (this.state == STATE_NEAR_GRABBING) {
|
||||
this.callEntityMethodOnGrabbed("startNearGrab");
|
||||
|
@ -2447,26 +2514,39 @@ function MyController(hand) {
|
|||
|
||||
if (dropDetected && !this.waitForTriggerRelease && this.triggerSmoothedGrab()) {
|
||||
// store the offset attach points into preferences.
|
||||
if (USE_ATTACH_POINT_SETTINGS && this.grabbedHotspot && this.grabbedEntity) {
|
||||
var prefprops = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]);
|
||||
if (USE_ATTACH_POINT_SETTINGS && this.grabbedHotspot && this.grabbedThingID) {
|
||||
var prefprops = Entities.getEntityProperties(this.grabbedThingID, ["localPosition", "localRotation"]);
|
||||
if (prefprops && prefprops.localPosition && prefprops.localRotation) {
|
||||
storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand,
|
||||
prefprops.localPosition, prefprops.localRotation);
|
||||
}
|
||||
}
|
||||
|
||||
var grabbedEntity = this.grabbedEntity;
|
||||
var grabbedEntity = this.grabbedThingID;
|
||||
this.release();
|
||||
this.grabbedEntity = grabbedEntity;
|
||||
this.grabbedThingID = grabbedEntity;
|
||||
this.setState(STATE_NEAR_GRABBING, "drop gesture detected");
|
||||
return;
|
||||
}
|
||||
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",
|
||||
"registrationPoint"]);
|
||||
}
|
||||
if (!props.position) {
|
||||
// server may have reset, taking our equipped entity with it. move back to "off" state
|
||||
this.callEntityMethodOnGrabbed("releaseGrab");
|
||||
|
@ -2478,7 +2558,7 @@ function MyController(hand) {
|
|||
// 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.
|
||||
this.callEntityMethodOnGrabbed("releaseGrab");
|
||||
this.grabbedEntity = null;
|
||||
this.grabbedThingID = null;
|
||||
this.setState(STATE_OFF, "someone took it");
|
||||
return;
|
||||
}
|
||||
|
@ -2560,7 +2640,7 @@ function MyController(hand) {
|
|||
|
||||
if (this.actionID && this.actionTimeout - now < ACTION_TTL_REFRESH * MSECS_PER_SEC) {
|
||||
// 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",
|
||||
timeScale: NEAR_GRABBING_ACTION_TIMEFRAME,
|
||||
relativePosition: this.offsetPosition,
|
||||
|
@ -2574,14 +2654,14 @@ function MyController(hand) {
|
|||
this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC);
|
||||
} else {
|
||||
print("continueNearGrabbing -- updateAction failed");
|
||||
Entities.deleteAction(this.grabbedEntity, this.actionID);
|
||||
Entities.deleteAction(this.grabbedThingID, this.actionID);
|
||||
this.setupHoldAction();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.maybeScale = function(props) {
|
||||
if (!objectScalingEnabled || this.isTablet(this.grabbedEntity)) {
|
||||
if (!objectScalingEnabled || this.isTablet(this.grabbedThingID) || this.grabbedIsOverlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2603,7 +2683,7 @@ function MyController(hand) {
|
|||
this.getOtherHandController().getHandPosition()));
|
||||
var currentRescale = scalingCurrentDistance / this.scalingStartDistance;
|
||||
var newDimensions = Vec3.multiply(currentRescale, this.scalingStartDimensions);
|
||||
Entities.editEntity(this.grabbedEntity, { dimensions: newDimensions });
|
||||
Entities.editEntity(this.grabbedThingID, { dimensions: newDimensions });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2654,7 +2734,7 @@ function MyController(hand) {
|
|||
this.nearTrigger = function(deltaTime, timestamp) {
|
||||
if (this.triggerSmoothedReleased()) {
|
||||
this.callEntityMethodOnGrabbed("stopNearTrigger");
|
||||
this.grabbedEntity = null;
|
||||
this.grabbedThingID = null;
|
||||
this.setState(STATE_OFF, "trigger released");
|
||||
return;
|
||||
}
|
||||
|
@ -2664,7 +2744,7 @@ function MyController(hand) {
|
|||
this.farTrigger = function(deltaTime, timestamp) {
|
||||
if (this.triggerSmoothedReleased()) {
|
||||
this.callEntityMethodOnGrabbed("stopFarTrigger");
|
||||
this.grabbedEntity = null;
|
||||
this.grabbedThingID = null;
|
||||
this.setState(STATE_OFF, "trigger released");
|
||||
return;
|
||||
}
|
||||
|
@ -2679,9 +2759,9 @@ function MyController(hand) {
|
|||
var intersection = findRayIntersection(pickRay, true, [], [], true);
|
||||
if (intersection.accurate || intersection.overlayID) {
|
||||
this.lastPickTime = now;
|
||||
if (intersection.entityID != this.grabbedEntity) {
|
||||
if (intersection.entityID != this.grabbedThingID) {
|
||||
this.callEntityMethodOnGrabbed("stopFarTrigger");
|
||||
this.grabbedEntity = null;
|
||||
this.grabbedThingID = null;
|
||||
this.setState(STATE_OFF, "laser moved off of entity");
|
||||
return;
|
||||
}
|
||||
|
@ -2703,13 +2783,13 @@ function MyController(hand) {
|
|||
|
||||
this.entityTouchingEnter = function() {
|
||||
// test for intersection between controller laser and web entity plane.
|
||||
var intersectInfo = handLaserIntersectEntity(this.grabbedEntity,
|
||||
var intersectInfo = handLaserIntersectEntity(this.grabbedThingID,
|
||||
getControllerWorldLocation(this.handToController(), true));
|
||||
if (intersectInfo) {
|
||||
var pointerEvent = {
|
||||
type: "Press",
|
||||
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,
|
||||
normal: intersectInfo.normal,
|
||||
direction: intersectInfo.searchRay.direction,
|
||||
|
@ -2717,8 +2797,8 @@ function MyController(hand) {
|
|||
isPrimaryHeld: true
|
||||
};
|
||||
|
||||
Entities.sendMousePressOnEntity(this.grabbedEntity, pointerEvent);
|
||||
Entities.sendClickDownOnEntity(this.grabbedEntity, pointerEvent);
|
||||
Entities.sendMousePressOnEntity(this.grabbedThingID, pointerEvent);
|
||||
Entities.sendClickDownOnEntity(this.grabbedThingID, pointerEvent);
|
||||
|
||||
this.touchingEnterTimer = 0;
|
||||
this.touchingEnterPointerEvent = pointerEvent;
|
||||
|
@ -2740,7 +2820,7 @@ function MyController(hand) {
|
|||
|
||||
this.entityTouchingExit = function() {
|
||||
// test for intersection between controller laser and web entity plane.
|
||||
var intersectInfo = handLaserIntersectEntity(this.grabbedEntity,
|
||||
var intersectInfo = handLaserIntersectEntity(this.grabbedThingID,
|
||||
getControllerWorldLocation(this.handToController(), true));
|
||||
if (intersectInfo) {
|
||||
var pointerEvent;
|
||||
|
@ -2748,7 +2828,7 @@ function MyController(hand) {
|
|||
pointerEvent = {
|
||||
type: "Release",
|
||||
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,
|
||||
normal: intersectInfo.normal,
|
||||
direction: intersectInfo.searchRay.direction,
|
||||
|
@ -2761,11 +2841,11 @@ function MyController(hand) {
|
|||
pointerEvent.isPrimaryHeld = false;
|
||||
}
|
||||
|
||||
Entities.sendMouseReleaseOnEntity(this.grabbedEntity, pointerEvent);
|
||||
Entities.sendClickReleaseOnEntity(this.grabbedEntity, pointerEvent);
|
||||
Entities.sendHoverLeaveEntity(this.grabbedEntity, pointerEvent);
|
||||
Entities.sendMouseReleaseOnEntity(this.grabbedThingID, pointerEvent);
|
||||
Entities.sendClickReleaseOnEntity(this.grabbedThingID, pointerEvent);
|
||||
Entities.sendHoverLeaveEntity(this.grabbedThingID, pointerEvent);
|
||||
}
|
||||
this.grabbedEntity = null;
|
||||
this.grabbedThingID = null;
|
||||
this.grabbedOverlay = null;
|
||||
};
|
||||
|
||||
|
@ -2773,7 +2853,7 @@ function MyController(hand) {
|
|||
|
||||
this.touchingEnterTimer += dt;
|
||||
|
||||
entityPropertiesCache.addEntity(this.grabbedEntity);
|
||||
entityPropertiesCache.addEntity(this.grabbedThingID);
|
||||
|
||||
if (this.state == STATE_ENTITY_LASER_TOUCHING && !this.triggerSmoothedGrab()) {
|
||||
this.setState(STATE_OFF, "released trigger");
|
||||
|
@ -2781,7 +2861,7 @@ function MyController(hand) {
|
|||
}
|
||||
|
||||
// test for intersection between controller laser and web entity plane.
|
||||
var intersectInfo = handLaserIntersectEntity(this.grabbedEntity,
|
||||
var intersectInfo = handLaserIntersectEntity(this.grabbedThingID,
|
||||
getControllerWorldLocation(this.handToController(), true));
|
||||
if (intersectInfo) {
|
||||
|
||||
|
@ -2791,15 +2871,15 @@ function MyController(hand) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (Entities.keyboardFocusEntity != this.grabbedEntity) {
|
||||
if (Entities.keyboardFocusEntity != this.grabbedThingID) {
|
||||
Overlays.keyboardFocusOverlay = 0;
|
||||
Entities.keyboardFocusEntity = this.grabbedEntity;
|
||||
Entities.keyboardFocusEntity = this.grabbedThingID;
|
||||
}
|
||||
|
||||
var pointerEvent = {
|
||||
type: "Move",
|
||||
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,
|
||||
normal: intersectInfo.normal,
|
||||
direction: intersectInfo.searchRay.direction,
|
||||
|
@ -2810,8 +2890,8 @@ function MyController(hand) {
|
|||
var POINTER_PRESS_TO_MOVE_DELAY = 0.25; // seconds
|
||||
if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY ||
|
||||
Vec3.distance(intersectInfo.point, this.touchingEnterPointerEvent.pos3D) > this.deadspotRadius) {
|
||||
Entities.sendMouseMoveOnEntity(this.grabbedEntity, pointerEvent);
|
||||
Entities.sendHoldingClickOnEntity(this.grabbedEntity, pointerEvent);
|
||||
Entities.sendMouseMoveOnEntity(this.grabbedThingID, pointerEvent);
|
||||
Entities.sendHoldingClickOnEntity(this.grabbedThingID, pointerEvent);
|
||||
this.deadspotExpired = true;
|
||||
}
|
||||
|
||||
|
@ -2821,7 +2901,7 @@ function MyController(hand) {
|
|||
}
|
||||
Reticle.setVisible(false);
|
||||
} else {
|
||||
this.grabbedEntity = null;
|
||||
this.grabbedThingID = null;
|
||||
this.setState(STATE_OFF, "grabbed entity was destroyed");
|
||||
return;
|
||||
}
|
||||
|
@ -2906,7 +2986,7 @@ function MyController(hand) {
|
|||
Overlays.sendMouseReleaseOnOverlay(this.grabbedOverlay, pointerEvent);
|
||||
Overlays.sendHoverLeaveOverlay(this.grabbedOverlay, pointerEvent);
|
||||
}
|
||||
this.grabbedEntity = null;
|
||||
this.grabbedThingID = null;
|
||||
this.grabbedOverlay = null;
|
||||
};
|
||||
|
||||
|
@ -2929,7 +3009,7 @@ function MyController(hand) {
|
|||
|
||||
if (this.state == STATE_OVERLAY_STYLUS_TOUCHING &&
|
||||
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");
|
||||
return;
|
||||
}
|
||||
|
@ -2986,7 +3066,7 @@ function MyController(hand) {
|
|||
}
|
||||
Reticle.setVisible(false);
|
||||
} else {
|
||||
this.grabbedEntity = null;
|
||||
this.grabbedThingID = null;
|
||||
this.setState(STATE_OFF, "grabbed overlay was destroyed");
|
||||
return;
|
||||
}
|
||||
|
@ -2995,7 +3075,7 @@ function MyController(hand) {
|
|||
this.release = function() {
|
||||
this.turnOffVisualizations();
|
||||
|
||||
if (this.grabbedEntity !== null) {
|
||||
if (this.grabbedThingID !== null) {
|
||||
if (this.state === STATE_HOLD) {
|
||||
this.callEntityMethodOnGrabbed("releaseEquip");
|
||||
}
|
||||
|
@ -3003,35 +3083,49 @@ function MyController(hand) {
|
|||
// Make a small release haptic pulse if we really were holding something
|
||||
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
|
||||
if (this.actionID !== null) {
|
||||
Entities.deleteAction(this.grabbedEntity, this.actionID);
|
||||
Entities.deleteAction(this.grabbedThingID, this.actionID);
|
||||
} else {
|
||||
// no action, so it's a parenting grab
|
||||
if (this.previousParentID[this.grabbedEntity] === NULL_UUID) {
|
||||
Entities.editEntity(this.grabbedEntity, {
|
||||
parentID: this.previousParentID[this.grabbedEntity],
|
||||
parentJointIndex: this.previousParentJointIndex[this.grabbedEntity]
|
||||
});
|
||||
this.ensureDynamic();
|
||||
if (this.previousParentID[this.grabbedThingID] === NULL_UUID) {
|
||||
if (this.grabbedIsOverlay) {
|
||||
Overlays.editOverlay(this.grabbedThingID, {
|
||||
parentID: NULL_UUID,
|
||||
parentJointIndex: -1
|
||||
});
|
||||
} else {
|
||||
Entities.editEntity(this.grabbedThingID, {
|
||||
parentID: this.previousParentID[this.grabbedThingID],
|
||||
parentJointIndex: this.previousParentJointIndex[this.grabbedThingID]
|
||||
});
|
||||
this.ensureDynamic();
|
||||
}
|
||||
} else {
|
||||
// we're putting this back as a child of some other parent, so zero its velocity
|
||||
Entities.editEntity(this.grabbedEntity, {
|
||||
parentID: this.previousParentID[this.grabbedEntity],
|
||||
parentJointIndex: this.previousParentJointIndex[this.grabbedEntity],
|
||||
velocity: {x: 0, y: 0, z: 0},
|
||||
angularVelocity: {x: 0, y: 0, z: 0}
|
||||
});
|
||||
if (this.grabbedIsOverlay) {
|
||||
Overlays.editOverlay(this.grabbedThingID, {
|
||||
parentID: this.previousParentID[this.grabbedThingID],
|
||||
parentJointIndex: this.previousParentJointIndex[this.grabbedThingID],
|
||||
});
|
||||
} else {
|
||||
// we're putting this back as a child of some other parent, so zero its velocity
|
||||
Entities.editEntity(this.grabbedThingID, {
|
||||
parentID: this.previousParentID[this.grabbedThingID],
|
||||
parentJointIndex: this.previousParentJointIndex[this.grabbedThingID],
|
||||
velocity: {x: 0, y: 0, z: 0},
|
||||
angularVelocity: {x: 0, y: 0, z: 0}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
|
||||
action: 'release',
|
||||
grabbedEntity: this.grabbedEntity,
|
||||
grabbedEntity: this.grabbedThingID,
|
||||
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
|
||||
}));
|
||||
}
|
||||
|
||||
this.actionID = null;
|
||||
this.grabbedEntity = null;
|
||||
this.grabbedThingID = null;
|
||||
this.grabbedOverlay = null;
|
||||
this.grabbedHotspot = null;
|
||||
|
||||
|
@ -3119,9 +3213,13 @@ function MyController(hand) {
|
|||
}
|
||||
_this.previouslyUnhooked[childID] = now;
|
||||
|
||||
// we don't know if it's an entity or an overlay
|
||||
Entities.editEntity(childID, { parentID: previousParentID, parentJointIndex: previousParentJointIndex });
|
||||
Overlays.editOverlay(childID, { parentID: previousParentID, parentJointIndex: previousParentJointIndex });
|
||||
|
||||
} else {
|
||||
Entities.editEntity(childID, { parentID: NULL_UUID });
|
||||
Overlays.editOverlay(childID, { parentID: NULL_UUID });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -3240,8 +3338,8 @@ var handleHandMessages = function(channel, message, sender) {
|
|||
selectedController.release();
|
||||
var wearableEntity = data.entityID;
|
||||
entityPropertiesCache.addEntity(wearableEntity);
|
||||
selectedController.grabbedEntity = wearableEntity;
|
||||
var hotspots = selectedController.collectEquipHotspots(selectedController.grabbedEntity);
|
||||
selectedController.grabbedThingID = wearableEntity;
|
||||
var hotspots = selectedController.collectEquipHotspots(selectedController.grabbedThingID);
|
||||
if (hotspots.length > 0) {
|
||||
if (hotspotIndex >= hotspots.length) {
|
||||
hotspotIndex = 0;
|
||||
|
|
|
@ -25,10 +25,13 @@ var OVERLAY_RAMP_RATE = 8.0;
|
|||
|
||||
var animStateHandlerID;
|
||||
|
||||
var isPointingIndex = false;
|
||||
var isBothIndexesPointing = false;
|
||||
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) {
|
||||
return Math.min(Math.max(val, min), max);
|
||||
|
@ -46,17 +49,32 @@ function init() {
|
|||
Script.update.connect(update);
|
||||
animStateHandlerID = MyAvatar.addAnimationStateHandler(
|
||||
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.messageReceived.connect(handleMessages);
|
||||
}
|
||||
|
||||
function animStateHandler(props) {
|
||||
return { leftHandOverlayAlpha: leftHandOverlayAlpha,
|
||||
leftHandGraspAlpha: lastLeftTrigger,
|
||||
rightHandOverlayAlpha: rightHandOverlayAlpha,
|
||||
rightHandGraspAlpha: lastRightTrigger };
|
||||
return {
|
||||
leftHandOverlayAlpha: leftHandOverlayAlpha,
|
||||
leftHandGraspAlpha: lastLeftTrigger,
|
||||
rightHandOverlayAlpha: rightHandOverlayAlpha,
|
||||
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) {
|
||||
|
@ -84,13 +102,11 @@ function update(dt) {
|
|||
rightHandOverlayAlpha = clamp(rightHandOverlayAlpha - OVERLAY_RAMP_RATE * dt, 0, 1);
|
||||
}
|
||||
|
||||
// Point index finger.
|
||||
if (isPointingIndex) {
|
||||
var zeroRotation = { x: 0, y: 0, z: 0, w: 1 };
|
||||
for (var i = 0; i < indexfingerJointNames.length; i++) {
|
||||
MyAvatar.setJointRotation(indexfingerJointNames[i], zeroRotation);
|
||||
}
|
||||
}
|
||||
// Pointing index fingers and raising thumbs
|
||||
isLeftIndexPointing = leftHandPose.valid && Controller.getValue(Controller.Standard.LeftIndexPoint) === 1;
|
||||
isRightIndexPointing = rightHandPose.valid && Controller.getValue(Controller.Standard.RightIndexPoint) === 1;
|
||||
isLeftThumbRaised = leftHandPose.valid && Controller.getValue(Controller.Standard.LeftThumbUp) === 1;
|
||||
isRightThumbRaised = rightHandPose.valid && Controller.getValue(Controller.Standard.RightThumbUp) === 1;
|
||||
}
|
||||
|
||||
function handleMessages(channel, message, sender) {
|
||||
|
@ -98,13 +114,7 @@ function handleMessages(channel, message, sender) {
|
|||
var data = JSON.parse(message);
|
||||
if (data.pointIndex !== undefined) {
|
||||
print("pointIndex: " + data.pointIndex);
|
||||
isPointingIndex = data.pointIndex;
|
||||
|
||||
if (!isPointingIndex) {
|
||||
for (var i = 0; i < indexfingerJointNames.length; i++) {
|
||||
MyAvatar.clearJointData(indexfingerJointNames[i]);
|
||||
}
|
||||
}
|
||||
isBothIndexesPointing = data.pointIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,12 +17,20 @@ var NUMBER_OF_STEPS = 6;
|
|||
|
||||
var TARGET_MODEL_URL = Script.resolvePath("../assets/models/teleport-destination.fbx");
|
||||
var TOO_CLOSE_MODEL_URL = Script.resolvePath("../assets/models/teleport-cancel.fbx");
|
||||
var SEAT_MODEL_URL = Script.resolvePath("../assets/models/teleport-seat.fbx");
|
||||
|
||||
var TARGET_MODEL_DIMENSIONS = {
|
||||
x: 1.15,
|
||||
y: 0.5,
|
||||
z: 1.15
|
||||
};
|
||||
|
||||
var COLORS_TELEPORT_SEAT = {
|
||||
red: 255,
|
||||
green: 0,
|
||||
blue: 170
|
||||
}
|
||||
|
||||
var COLORS_TELEPORT_CAN_TELEPORT = {
|
||||
red: 97,
|
||||
green: 247,
|
||||
|
@ -35,29 +43,30 @@ var COLORS_TELEPORT_CANNOT_TELEPORT = {
|
|||
blue: 141
|
||||
};
|
||||
|
||||
var COLORS_TELEPORT_TOO_CLOSE = {
|
||||
var COLORS_TELEPORT_CANCEL = {
|
||||
red: 255,
|
||||
green: 184,
|
||||
blue: 73
|
||||
};
|
||||
|
||||
var TELEPORT_CANCEL_RANGE = 1;
|
||||
var USE_COOL_IN = true;
|
||||
var COOL_IN_DURATION = 500;
|
||||
|
||||
const handInfo = {
|
||||
right: {
|
||||
controllerInput: Controller.Standard.RightHand
|
||||
},
|
||||
left: {
|
||||
controllerInput: Controller.Standard.LeftHand
|
||||
}
|
||||
};
|
||||
|
||||
function ThumbPad(hand) {
|
||||
this.hand = hand;
|
||||
var _thisPad = this;
|
||||
|
||||
this.buttonPress = function(value) {
|
||||
_thisPad.buttonValue = value;
|
||||
if (value === 0) {
|
||||
if (activationTimeout !== null) {
|
||||
Script.clearTimeout(activationTimeout);
|
||||
activationTimeout = null;
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -67,7 +76,6 @@ function Trigger(hand) {
|
|||
|
||||
this.buttonPress = function(value) {
|
||||
_this.buttonValue = value;
|
||||
|
||||
};
|
||||
|
||||
this.down = function() {
|
||||
|
@ -78,347 +86,224 @@ function Trigger(hand) {
|
|||
|
||||
var coolInTimeout = null;
|
||||
|
||||
var TELEPORTER_STATES = {
|
||||
IDLE: 'idle',
|
||||
COOL_IN: 'cool_in',
|
||||
TARGETTING_INVALID: 'targetting_invalid',
|
||||
}
|
||||
|
||||
var TARGET = {
|
||||
NONE: 'none', // Not currently targetting anything
|
||||
INVISIBLE: 'invisible', // The current target is an invvsible surface
|
||||
INVALID: 'invalid', // The current target is invalid (wall, ceiling, etc.)
|
||||
SURFACE: 'surface', // The current target is a valid surface
|
||||
SEAT: 'seat', // The current target is a seat
|
||||
}
|
||||
|
||||
function Teleporter() {
|
||||
var _this = this;
|
||||
this.intersection = null;
|
||||
this.rightOverlayLine = null;
|
||||
this.leftOverlayLine = null;
|
||||
this.targetOverlay = null;
|
||||
this.cancelOverlay = null;
|
||||
this.updateConnected = null;
|
||||
this.smoothArrivalInterval = null;
|
||||
this.teleportHand = null;
|
||||
this.tooClose = false;
|
||||
this.inCoolIn = false;
|
||||
this.active = false;
|
||||
this.state = TELEPORTER_STATES.IDLE;
|
||||
this.currentTarget = TARGET.INVALID;
|
||||
|
||||
this.initialize = function() {
|
||||
this.createMappings();
|
||||
this.overlayLines = {
|
||||
left: null,
|
||||
right: null,
|
||||
};
|
||||
this.updateConnected = null;
|
||||
this.activeHand = null;
|
||||
|
||||
this.createMappings = function() {
|
||||
teleporter.telporterMappingInternalName = 'Hifi-Teleporter-Internal-Dev-' + Math.random();
|
||||
teleporter.teleportMappingInternal = Controller.newMapping(teleporter.telporterMappingInternalName);
|
||||
this.telporterMappingInternalName = 'Hifi-Teleporter-Internal-Dev-' + Math.random();
|
||||
this.teleportMappingInternal = Controller.newMapping(this.telporterMappingInternalName);
|
||||
|
||||
Controller.enableMapping(teleporter.telporterMappingInternalName);
|
||||
// Setup overlays
|
||||
this.cancelOverlay = Overlays.addOverlay("model", {
|
||||
url: TOO_CLOSE_MODEL_URL,
|
||||
dimensions: TARGET_MODEL_DIMENSIONS,
|
||||
visible: false
|
||||
});
|
||||
this.targetOverlay = Overlays.addOverlay("model", {
|
||||
url: TARGET_MODEL_URL,
|
||||
dimensions: TARGET_MODEL_DIMENSIONS,
|
||||
visible: false
|
||||
});
|
||||
this.seatOverlay = Overlays.addOverlay("model", {
|
||||
url: SEAT_MODEL_URL,
|
||||
dimensions: TARGET_MODEL_DIMENSIONS,
|
||||
visible: false
|
||||
});
|
||||
|
||||
this.enableMappings = function() {
|
||||
Controller.enableMapping(this.telporterMappingInternalName);
|
||||
};
|
||||
|
||||
this.disableMappings = function() {
|
||||
Controller.disableMapping(teleporter.telporterMappingInternalName);
|
||||
};
|
||||
|
||||
this.enterTeleportMode = function(hand) {
|
||||
this.cleanup = function() {
|
||||
this.disableMappings();
|
||||
|
||||
Overlays.deleteOverlay(this.targetOverlay);
|
||||
this.targetOverlay = null;
|
||||
|
||||
Overlays.deleteOverlay(this.cancelOverlay);
|
||||
this.cancelOverlay = null;
|
||||
|
||||
Overlays.deleteOverlay(this.seatOverlay);
|
||||
this.seatOverlay = null;
|
||||
|
||||
this.deleteOverlayBeams();
|
||||
if (this.updateConnected === true) {
|
||||
Script.update.disconnect(this, this.update);
|
||||
}
|
||||
};
|
||||
|
||||
this.enterTeleportMode = function(hand) {
|
||||
if (inTeleportMode === true) {
|
||||
return;
|
||||
}
|
||||
if (isDisabled === 'both') {
|
||||
if (isDisabled === 'both' || isDisabled === hand) {
|
||||
return;
|
||||
}
|
||||
|
||||
inTeleportMode = true;
|
||||
this.inCoolIn = true;
|
||||
|
||||
if (coolInTimeout !== null) {
|
||||
Script.clearTimeout(coolInTimeout);
|
||||
|
||||
}
|
||||
|
||||
this.state = TELEPORTER_STATES.COOL_IN;
|
||||
coolInTimeout = Script.setTimeout(function() {
|
||||
_this.inCoolIn = false;
|
||||
if (_this.state === TELEPORTER_STATES.COOL_IN) {
|
||||
_this.state = TELEPORTER_STATES.TARGETTING;
|
||||
}
|
||||
}, COOL_IN_DURATION)
|
||||
|
||||
if (this.smoothArrivalInterval !== null) {
|
||||
Script.clearInterval(this.smoothArrivalInterval);
|
||||
}
|
||||
if (activationTimeout !== null) {
|
||||
Script.clearInterval(activationTimeout);
|
||||
}
|
||||
|
||||
this.teleportHand = hand;
|
||||
this.initialize();
|
||||
Script.update.connect(this.update);
|
||||
this.activeHand = hand;
|
||||
this.enableMappings();
|
||||
Script.update.connect(this, this.update);
|
||||
this.updateConnected = true;
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
this.createTargetOverlay = function(visible) {
|
||||
if (visible == undefined) {
|
||||
visible = true;
|
||||
}
|
||||
|
||||
if (_this.targetOverlay !== null) {
|
||||
return;
|
||||
}
|
||||
var targetOverlayProps = {
|
||||
url: TARGET_MODEL_URL,
|
||||
dimensions: TARGET_MODEL_DIMENSIONS,
|
||||
visible: visible
|
||||
};
|
||||
|
||||
_this.targetOverlay = Overlays.addOverlay("model", targetOverlayProps);
|
||||
|
||||
};
|
||||
|
||||
this.createCancelOverlay = function(visible) {
|
||||
if (visible == undefined) {
|
||||
visible = true;
|
||||
}
|
||||
|
||||
if (_this.cancelOverlay !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var cancelOverlayProps = {
|
||||
url: TOO_CLOSE_MODEL_URL,
|
||||
dimensions: TARGET_MODEL_DIMENSIONS,
|
||||
visible: visible
|
||||
};
|
||||
|
||||
_this.cancelOverlay = Overlays.addOverlay("model", cancelOverlayProps);
|
||||
};
|
||||
|
||||
this.deleteCancelOverlay = function() {
|
||||
if (this.cancelOverlay === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Overlays.deleteOverlay(this.cancelOverlay);
|
||||
this.cancelOverlay = null;
|
||||
}
|
||||
|
||||
this.hideCancelOverlay = function() {
|
||||
if (this.cancelOverlay === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.intersection = null;
|
||||
Overlays.editOverlay(this.cancelOverlay, { visible: false });
|
||||
}
|
||||
|
||||
this.showCancelOverlay = function() {
|
||||
if (this.cancelOverlay === null) {
|
||||
return this.createCancelOverlay();
|
||||
}
|
||||
Overlays.editOverlay(this.cancelOverlay, { visible: true });
|
||||
}
|
||||
|
||||
|
||||
this.deleteTargetOverlay = function() {
|
||||
if (this.targetOverlay === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Overlays.deleteOverlay(this.targetOverlay);
|
||||
this.intersection = null;
|
||||
this.targetOverlay = null;
|
||||
}
|
||||
|
||||
this.hideTargetOverlay = function() {
|
||||
if (this.targetOverlay === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.intersection = null;
|
||||
Overlays.editOverlay(this.targetOverlay, { visible: false });
|
||||
}
|
||||
|
||||
this.showTargetOverlay = function() {
|
||||
if (this.targetOverlay === null) {
|
||||
return this.createTargetOverlay();
|
||||
}
|
||||
Overlays.editOverlay(this.targetOverlay, { visible: true });
|
||||
}
|
||||
|
||||
this.turnOffOverlayBeams = function() {
|
||||
this.rightOverlayOff();
|
||||
this.leftOverlayOff();
|
||||
}
|
||||
|
||||
this.exitTeleportMode = function(value) {
|
||||
if (activationTimeout !== null) {
|
||||
Script.clearTimeout(activationTimeout);
|
||||
activationTimeout = null;
|
||||
}
|
||||
if (this.updateConnected === true) {
|
||||
Script.update.disconnect(this.update);
|
||||
Script.update.disconnect(this, this.update);
|
||||
}
|
||||
|
||||
this.disableMappings();
|
||||
this.turnOffOverlayBeams();
|
||||
this.deleteOverlayBeams();
|
||||
this.hideTargetOverlay();
|
||||
this.hideCancelOverlay();
|
||||
|
||||
this.updateConnected = null;
|
||||
this.inCoolIn = false;
|
||||
this.state = TELEPORTER_STATES.IDLE;
|
||||
inTeleportMode = false;
|
||||
};
|
||||
|
||||
this.update = function() {
|
||||
if (isDisabled === 'both') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (teleporter.teleportHand === 'left') {
|
||||
if (isDisabled === 'left') {
|
||||
return;
|
||||
this.deleteOverlayBeams = function() {
|
||||
for (key in this.overlayLines) {
|
||||
if (this.overlayLines[key] !== null) {
|
||||
Overlays.deleteOverlay(this.overlayLines[key]);
|
||||
this.overlayLines[key] = null;
|
||||
}
|
||||
teleporter.leftRay();
|
||||
if ((leftPad.buttonValue === 0) && inTeleportMode === true) {
|
||||
if (_this.inCoolIn === true) {
|
||||
_this.exitTeleportMode();
|
||||
_this.hideTargetOverlay();
|
||||
_this.hideCancelOverlay();
|
||||
} else {
|
||||
_this.teleport();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (isDisabled === 'right') {
|
||||
return;
|
||||
}
|
||||
teleporter.rightRay();
|
||||
if ((rightPad.buttonValue === 0) && inTeleportMode === true) {
|
||||
if (_this.inCoolIn === true) {
|
||||
_this.exitTeleportMode();
|
||||
_this.hideTargetOverlay();
|
||||
_this.hideCancelOverlay();
|
||||
} else {
|
||||
_this.teleport();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
this.rightRay = function() {
|
||||
var pose = Controller.getPoseValue(Controller.Standard.RightHand);
|
||||
var rightPosition = pose.valid ? Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position) : MyAvatar.getHeadPosition();
|
||||
var rightRotation = pose.valid ? Quat.multiply(MyAvatar.orientation, pose.rotation) :
|
||||
Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, {
|
||||
x: 1,
|
||||
y: 0,
|
||||
z: 0
|
||||
}));
|
||||
|
||||
var rightPickRay = {
|
||||
origin: rightPosition,
|
||||
direction: Quat.getUp(rightRotation),
|
||||
};
|
||||
|
||||
this.rightPickRay = rightPickRay;
|
||||
|
||||
var location = Vec3.sum(rightPickRay.origin, Vec3.multiply(rightPickRay.direction, 50));
|
||||
|
||||
|
||||
var rightIntersection = Entities.findRayIntersection(teleporter.rightPickRay, true, [], [this.targetEntity], true, true);
|
||||
|
||||
if (rightIntersection.intersects) {
|
||||
if (this.tooClose === true) {
|
||||
this.hideTargetOverlay();
|
||||
|
||||
this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE);
|
||||
if (this.cancelOverlay !== null) {
|
||||
this.updateCancelOverlay(rightIntersection);
|
||||
} else {
|
||||
this.createCancelOverlay();
|
||||
}
|
||||
} else {
|
||||
if (this.inCoolIn === true) {
|
||||
this.hideTargetOverlay();
|
||||
this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE);
|
||||
if (this.cancelOverlay !== null) {
|
||||
this.updateCancelOverlay(rightIntersection);
|
||||
} else {
|
||||
this.createCancelOverlay();
|
||||
}
|
||||
} else {
|
||||
this.hideCancelOverlay();
|
||||
|
||||
this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT);
|
||||
if (this.targetOverlay !== null) {
|
||||
this.updateTargetOverlay(rightIntersection);
|
||||
} else {
|
||||
this.createTargetOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
this.hideTargetOverlay();
|
||||
this.rightLineOn(rightPickRay.origin, location, COLORS_TELEPORT_CANNOT_TELEPORT);
|
||||
}
|
||||
}
|
||||
|
||||
this.update = function() {
|
||||
if (_this.state === TELEPORTER_STATES.IDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.leftRay = function() {
|
||||
var pose = Controller.getPoseValue(Controller.Standard.LeftHand);
|
||||
var leftPosition = pose.valid ? Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position) : MyAvatar.getHeadPosition();
|
||||
var leftRotation = pose.valid ? Quat.multiply(MyAvatar.orientation, pose.rotation) :
|
||||
// Get current hand pose information so that we can get the direction of the teleport beam
|
||||
var pose = Controller.getPoseValue(handInfo[_this.activeHand].controllerInput);
|
||||
var handPosition = pose.valid ? Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position) : MyAvatar.getHeadPosition();
|
||||
var handRotation = pose.valid ? Quat.multiply(MyAvatar.orientation, pose.rotation) :
|
||||
Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, {
|
||||
x: 1,
|
||||
y: 0,
|
||||
z: 0
|
||||
}));
|
||||
|
||||
var leftPickRay = {
|
||||
origin: leftPosition,
|
||||
direction: Quat.getUp(leftRotation),
|
||||
var pickRay = {
|
||||
origin: handPosition,
|
||||
direction: Quat.getUp(handRotation),
|
||||
};
|
||||
|
||||
this.leftPickRay = leftPickRay;
|
||||
// We do up to 2 ray picks to find a teleport location.
|
||||
// There are 2 types of teleport locations we are interested in:
|
||||
// 1. A visible floor. This can be any entity surface that points within some degree of "up"
|
||||
// 2. A seat. The seat can be visible or invisible.
|
||||
//
|
||||
// * In the first pass we pick against visible and invisible entities so that we can find invisible seats.
|
||||
// We might hit an invisible entity that is not a seat, so we need to do a second pass.
|
||||
// * In the second pass we pick against visible entities only.
|
||||
//
|
||||
var intersection = Entities.findRayIntersection(pickRay, true, [], [this.targetEntity], false, true);
|
||||
|
||||
var location = Vec3.sum(MyAvatar.position, Vec3.multiply(leftPickRay.direction, 50));
|
||||
|
||||
|
||||
var leftIntersection = Entities.findRayIntersection(teleporter.leftPickRay, true, [], [this.targetEntity], true, true);
|
||||
|
||||
if (leftIntersection.intersects) {
|
||||
|
||||
if (this.tooClose === true) {
|
||||
this.hideTargetOverlay();
|
||||
this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE);
|
||||
if (this.cancelOverlay !== null) {
|
||||
this.updateCancelOverlay(leftIntersection);
|
||||
} else {
|
||||
this.createCancelOverlay();
|
||||
}
|
||||
} else {
|
||||
if (this.inCoolIn === true) {
|
||||
this.hideTargetOverlay();
|
||||
this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE);
|
||||
if (this.cancelOverlay !== null) {
|
||||
this.updateCancelOverlay(leftIntersection);
|
||||
} else {
|
||||
this.createCancelOverlay();
|
||||
}
|
||||
} else {
|
||||
this.hideCancelOverlay();
|
||||
this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT);
|
||||
|
||||
if (this.targetOverlay !== null) {
|
||||
this.updateTargetOverlay(leftIntersection);
|
||||
} else {
|
||||
this.createTargetOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
var teleportLocationType = getTeleportTargetType(intersection);
|
||||
if (teleportLocationType === TARGET.INVISIBLE) {
|
||||
intersection = Entities.findRayIntersection(pickRay, true, [], [this.targetEntity], true, true);
|
||||
teleportLocationType = getTeleportTargetType(intersection);
|
||||
}
|
||||
|
||||
if (teleportLocationType === TARGET.NONE) {
|
||||
this.hideTargetOverlay();
|
||||
this.leftLineOn(leftPickRay.origin, location, COLORS_TELEPORT_CANNOT_TELEPORT);
|
||||
this.hideCancelOverlay();
|
||||
this.hideSeatOverlay();
|
||||
|
||||
var farPosition = Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, 50));
|
||||
this.updateLineOverlay(_this.activeHand, pickRay.origin, farPosition, COLORS_TELEPORT_CANNOT_TELEPORT);
|
||||
} else if (teleportLocationType === TARGET.INVALID || teleportLocationType === TARGET.INVISIBLE) {
|
||||
this.hideTargetOverlay();
|
||||
this.hideSeatOverlay();
|
||||
|
||||
this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_CANCEL);
|
||||
this.updateDestinationOverlay(this.cancelOverlay, intersection);
|
||||
} else if (teleportLocationType === TARGET.SURFACE) {
|
||||
if (this.state === TELEPORTER_STATES.COOL_IN) {
|
||||
this.hideTargetOverlay();
|
||||
this.hideSeatOverlay();
|
||||
|
||||
this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_CANCEL);
|
||||
this.updateDestinationOverlay(this.cancelOverlay, intersection);
|
||||
} else {
|
||||
this.hideCancelOverlay();
|
||||
this.hideSeatOverlay();
|
||||
|
||||
this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_CAN_TELEPORT);
|
||||
this.updateDestinationOverlay(this.targetOverlay, intersection);
|
||||
}
|
||||
} else if (teleportLocationType === TARGET.SEAT) {
|
||||
this.hideCancelOverlay();
|
||||
this.hideTargetOverlay();
|
||||
|
||||
this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_SEAT);
|
||||
this.updateDestinationOverlay(this.seatOverlay, intersection);
|
||||
}
|
||||
|
||||
|
||||
if (((_this.activeHand == 'left' ? leftPad : rightPad).buttonValue === 0) && inTeleportMode === true) {
|
||||
this.exitTeleportMode();
|
||||
this.hideCancelOverlay();
|
||||
this.hideTargetOverlay();
|
||||
this.hideSeatOverlay();
|
||||
|
||||
if (teleportLocationType === TARGET.NONE || teleportLocationType === TARGET.INVALID || this.state === TELEPORTER_STATES.COOL_IN) {
|
||||
// Do nothing
|
||||
} else if (teleportLocationType === TARGET.SEAT) {
|
||||
Entities.callEntityMethod(intersection.entityID, 'sit');
|
||||
} else if (teleportLocationType === TARGET.SURFACE) {
|
||||
var offset = getAvatarFootOffset();
|
||||
intersection.intersection.y += offset;
|
||||
MyAvatar.position = intersection.intersection;
|
||||
HMD.centerUI();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.rightLineOn = function(closePoint, farPoint, color) {
|
||||
if (this.rightOverlayLine === null) {
|
||||
this.updateLineOverlay = function(hand, closePoint, farPoint, color) {
|
||||
if (this.overlayLines[hand] === null) {
|
||||
var lineProperties = {
|
||||
start: closePoint,
|
||||
end: farPoint,
|
||||
|
@ -431,10 +316,10 @@ function Teleporter() {
|
|||
glow: 1.0
|
||||
};
|
||||
|
||||
this.rightOverlayLine = Overlays.addOverlay("line3d", lineProperties);
|
||||
this.overlayLines[hand] = Overlays.addOverlay("line3d", lineProperties);
|
||||
|
||||
} else {
|
||||
var success = Overlays.editOverlay(this.rightOverlayLine, {
|
||||
var success = Overlays.editOverlay(this.overlayLines[hand], {
|
||||
start: closePoint,
|
||||
end: farPoint,
|
||||
color: color
|
||||
|
@ -442,47 +327,19 @@ function Teleporter() {
|
|||
}
|
||||
};
|
||||
|
||||
this.leftLineOn = function(closePoint, farPoint, color) {
|
||||
if (this.leftOverlayLine === null) {
|
||||
var lineProperties = {
|
||||
ignoreRayIntersection: true,
|
||||
start: closePoint,
|
||||
end: farPoint,
|
||||
color: color,
|
||||
visible: true,
|
||||
alpha: 1,
|
||||
solid: true,
|
||||
glow: 1.0,
|
||||
drawInFront: true
|
||||
};
|
||||
|
||||
this.leftOverlayLine = Overlays.addOverlay("line3d", lineProperties);
|
||||
|
||||
} else {
|
||||
var success = Overlays.editOverlay(this.leftOverlayLine, {
|
||||
start: closePoint,
|
||||
end: farPoint,
|
||||
color: color
|
||||
});
|
||||
}
|
||||
};
|
||||
this.rightOverlayOff = function() {
|
||||
if (this.rightOverlayLine !== null) {
|
||||
Overlays.deleteOverlay(this.rightOverlayLine);
|
||||
this.rightOverlayLine = null;
|
||||
}
|
||||
this.hideCancelOverlay = function() {
|
||||
Overlays.editOverlay(this.cancelOverlay, { visible: false });
|
||||
};
|
||||
|
||||
this.leftOverlayOff = function() {
|
||||
if (this.leftOverlayLine !== null) {
|
||||
Overlays.deleteOverlay(this.leftOverlayLine);
|
||||
this.leftOverlayLine = null;
|
||||
}
|
||||
this.hideTargetOverlay = function() {
|
||||
Overlays.editOverlay(this.targetOverlay, { visible: false });
|
||||
};
|
||||
|
||||
this.updateTargetOverlay = function(intersection) {
|
||||
_this.intersection = intersection;
|
||||
this.hideSeatOverlay = function() {
|
||||
Overlays.editOverlay(this.seatOverlay, { visible: false });
|
||||
};
|
||||
|
||||
this.updateDestinationOverlay = function(overlayID, intersection) {
|
||||
var rotation = Quat.lookAt(intersection.intersection, MyAvatar.position, Vec3.UP);
|
||||
var euler = Quat.safeEulerAngles(rotation);
|
||||
var position = {
|
||||
|
@ -491,115 +348,15 @@ function Teleporter() {
|
|||
z: intersection.intersection.z
|
||||
};
|
||||
|
||||
this.tooClose = isValidTeleportLocation(position, intersection.surfaceNormal);
|
||||
var towardUs = Quat.fromPitchYawRollDegrees(0, euler.y, 0);
|
||||
|
||||
Overlays.editOverlay(this.targetOverlay, {
|
||||
Overlays.editOverlay(overlayID, {
|
||||
visible: true,
|
||||
position: position,
|
||||
rotation: towardUs
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
this.updateCancelOverlay = function(intersection) {
|
||||
_this.intersection = intersection;
|
||||
|
||||
var rotation = Quat.lookAt(intersection.intersection, MyAvatar.position, Vec3.UP);
|
||||
var euler = Quat.safeEulerAngles(rotation);
|
||||
var position = {
|
||||
x: intersection.intersection.x,
|
||||
y: intersection.intersection.y + TARGET_MODEL_DIMENSIONS.y / 2,
|
||||
z: intersection.intersection.z
|
||||
};
|
||||
|
||||
this.tooClose = isValidTeleportLocation(position, intersection.surfaceNormal);
|
||||
var towardUs = Quat.fromPitchYawRollDegrees(0, euler.y, 0);
|
||||
|
||||
Overlays.editOverlay(this.cancelOverlay, {
|
||||
visible: true,
|
||||
position: position,
|
||||
rotation: towardUs
|
||||
});
|
||||
};
|
||||
|
||||
this.triggerHaptics = function() {
|
||||
var hand = this.teleportHand === 'left' ? 0 : 1;
|
||||
var haptic = Controller.triggerShortHapticPulse(0.2, hand);
|
||||
};
|
||||
|
||||
this.teleport = function(value) {
|
||||
|
||||
if (value === undefined) {
|
||||
this.exitTeleportMode();
|
||||
}
|
||||
|
||||
if (this.intersection !== null) {
|
||||
if (this.tooClose === true) {
|
||||
this.exitTeleportMode();
|
||||
this.hideCancelOverlay();
|
||||
return;
|
||||
}
|
||||
var offset = getAvatarFootOffset();
|
||||
this.intersection.intersection.y += offset;
|
||||
this.exitTeleportMode();
|
||||
// Disable smooth arrival, possibly temporarily
|
||||
//this.smoothArrival();
|
||||
MyAvatar.position = _this.intersection.intersection;
|
||||
_this.hideTargetOverlay();
|
||||
_this.hideCancelOverlay();
|
||||
HMD.centerUI();
|
||||
}
|
||||
};
|
||||
|
||||
this.findMidpoint = function(start, end) {
|
||||
var xy = Vec3.sum(start, end);
|
||||
var midpoint = Vec3.multiply(0.5, xy);
|
||||
return midpoint
|
||||
};
|
||||
|
||||
this.getArrivalPoints = function(startPoint, endPoint) {
|
||||
var arrivalPoints = [];
|
||||
var i;
|
||||
var lastPoint;
|
||||
|
||||
for (i = 0; i < NUMBER_OF_STEPS; i++) {
|
||||
if (i === 0) {
|
||||
lastPoint = startPoint;
|
||||
}
|
||||
var newPoint = _this.findMidpoint(lastPoint, endPoint);
|
||||
lastPoint = newPoint;
|
||||
arrivalPoints.push(newPoint);
|
||||
}
|
||||
|
||||
arrivalPoints.push(endPoint);
|
||||
|
||||
return arrivalPoints;
|
||||
};
|
||||
|
||||
this.smoothArrival = function() {
|
||||
|
||||
_this.arrivalPoints = _this.getArrivalPoints(MyAvatar.position, _this.intersection.intersection);
|
||||
_this.smoothArrivalInterval = Script.setInterval(function() {
|
||||
if (_this.arrivalPoints.length === 0) {
|
||||
Script.clearInterval(_this.smoothArrivalInterval);
|
||||
HMD.centerUI();
|
||||
return;
|
||||
}
|
||||
var landingPoint = _this.arrivalPoints.shift();
|
||||
MyAvatar.position = landingPoint;
|
||||
|
||||
if (_this.arrivalPoints.length === 1 || _this.arrivalPoints.length === 0) {
|
||||
_this.hideTargetOverlay();
|
||||
_this.hideCancelOverlay();
|
||||
}
|
||||
|
||||
}, SMOOTH_ARRIVAL_SPACING);
|
||||
}
|
||||
|
||||
this.createTargetOverlay(false);
|
||||
this.createCancelOverlay(false);
|
||||
|
||||
}
|
||||
|
||||
//related to repositioning the avatar after you teleport
|
||||
|
@ -611,20 +368,16 @@ function getAvatarFootOffset() {
|
|||
var jointName = d.joint;
|
||||
if (jointName === "RightUpLeg") {
|
||||
upperLeg = d.translation.y;
|
||||
}
|
||||
if (jointName === "RightLeg") {
|
||||
} else if (jointName === "RightLeg") {
|
||||
lowerLeg = d.translation.y;
|
||||
}
|
||||
if (jointName === "RightFoot") {
|
||||
} else if (jointName === "RightFoot") {
|
||||
foot = d.translation.y;
|
||||
}
|
||||
if (jointName === "RightToeBase") {
|
||||
} else if (jointName === "RightToeBase") {
|
||||
toe = d.translation.y;
|
||||
}
|
||||
if (jointName === "RightToe_End") {
|
||||
} else if (jointName === "RightToe_End") {
|
||||
toeTop = d.translation.y;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
var offset = upperLeg + lowerLeg + foot + toe + toeTop;
|
||||
offset = offset / 100;
|
||||
|
@ -655,7 +408,6 @@ var rightTrigger = new Trigger('right');
|
|||
|
||||
var mappingName, teleportMapping;
|
||||
|
||||
var activationTimeout = null;
|
||||
var TELEPORT_DELAY = 0;
|
||||
|
||||
function isMoving() {
|
||||
|
@ -668,17 +420,44 @@ function isMoving() {
|
|||
}
|
||||
};
|
||||
|
||||
function parseJSON(json) {
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
// When determininig whether you can teleport to a location, the normal of the
|
||||
// point that is being intersected with is looked at. If this normal is more
|
||||
// than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from <0, 1, 0> (straight up), then
|
||||
// you can't teleport there.
|
||||
var MAX_ANGLE_FROM_UP_TO_TELEPORT = 70;
|
||||
function isValidTeleportLocation(position, surfaceNormal) {
|
||||
const MAX_ANGLE_FROM_UP_TO_TELEPORT = 70;
|
||||
function getTeleportTargetType(intersection) {
|
||||
if (!intersection.intersects) {
|
||||
return TARGET.NONE;
|
||||
}
|
||||
|
||||
var props = Entities.getEntityProperties(intersection.entityID, ['userData', 'visible']);
|
||||
var data = parseJSON(props.userData);
|
||||
if (data !== undefined && data.seat !== undefined) {
|
||||
return TARGET.SEAT;
|
||||
}
|
||||
|
||||
if (!props.visible) {
|
||||
return TARGET.INVISIBLE;
|
||||
}
|
||||
|
||||
var surfaceNormal = intersection.surfaceNormal;
|
||||
var adj = Math.sqrt(surfaceNormal.x * surfaceNormal.x + surfaceNormal.z * surfaceNormal.z);
|
||||
var angleUp = Math.atan2(surfaceNormal.y, adj) * (180 / Math.PI);
|
||||
return angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT) ||
|
||||
angleUp > (90 + MAX_ANGLE_FROM_UP_TO_TELEPORT) ||
|
||||
Vec3.distance(MyAvatar.position, position) <= TELEPORT_CANCEL_RANGE;
|
||||
|
||||
if (angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT) ||
|
||||
angleUp > (90 + MAX_ANGLE_FROM_UP_TO_TELEPORT) ||
|
||||
Vec3.distance(MyAvatar.position, intersection.intersection) <= TELEPORT_CANCEL_RANGE) {
|
||||
return TARGET.INVALID;
|
||||
} else {
|
||||
return TARGET.SURFACE;
|
||||
}
|
||||
};
|
||||
|
||||
function registerMappings() {
|
||||
|
@ -695,20 +474,13 @@ function registerMappings() {
|
|||
if (isDisabled === 'left' || isDisabled === 'both') {
|
||||
return;
|
||||
}
|
||||
if (activationTimeout !== null) {
|
||||
return
|
||||
}
|
||||
if (leftTrigger.down()) {
|
||||
return;
|
||||
}
|
||||
if (isMoving() === true) {
|
||||
return;
|
||||
}
|
||||
activationTimeout = Script.setTimeout(function() {
|
||||
Script.clearTimeout(activationTimeout);
|
||||
activationTimeout = null;
|
||||
teleporter.enterTeleportMode('left')
|
||||
}, TELEPORT_DELAY)
|
||||
teleporter.enterTeleportMode('left')
|
||||
return;
|
||||
});
|
||||
teleportMapping.from(Controller.Standard.RightPrimaryThumb)
|
||||
|
@ -716,9 +488,6 @@ function registerMappings() {
|
|||
if (isDisabled === 'right' || isDisabled === 'both') {
|
||||
return;
|
||||
}
|
||||
if (activationTimeout !== null) {
|
||||
return
|
||||
}
|
||||
if (rightTrigger.down()) {
|
||||
return;
|
||||
}
|
||||
|
@ -726,11 +495,7 @@ function registerMappings() {
|
|||
return;
|
||||
}
|
||||
|
||||
activationTimeout = Script.setTimeout(function() {
|
||||
teleporter.enterTeleportMode('right')
|
||||
Script.clearTimeout(activationTimeout);
|
||||
activationTimeout = null;
|
||||
}, TELEPORT_DELAY)
|
||||
teleporter.enterTeleportMode('right')
|
||||
return;
|
||||
});
|
||||
};
|
||||
|
@ -741,18 +506,11 @@ var teleporter = new Teleporter();
|
|||
|
||||
Controller.enableMapping(mappingName);
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
|
||||
function cleanup() {
|
||||
teleportMapping.disable();
|
||||
teleporter.disableMappings();
|
||||
teleporter.deleteTargetOverlay();
|
||||
teleporter.deleteCancelOverlay();
|
||||
teleporter.turnOffOverlayBeams();
|
||||
if (teleporter.updateConnected !== null) {
|
||||
Script.update.disconnect(teleporter.update);
|
||||
}
|
||||
teleporter.cleanup();
|
||||
}
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
|
||||
var isDisabled = false;
|
||||
var handleHandMessages = function(channel, message, sender) {
|
||||
|
|
|
@ -564,6 +564,11 @@ function findClickedEntity(event) {
|
|||
|
||||
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 lightResult = lightOverlayManager.findRayIntersection(pickRay);
|
||||
lightResult.accurate = true;
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
/* global getControllerWorldLocation, setEntityCustomData, Tablet, WebTablet:true, HMD, Settings, Script,
|
||||
Vec3, Quat, MyAvatar, Entities, Overlays, Camera, Messages, Xform, clamp */
|
||||
/* global getControllerWorldLocation, Tablet, WebTablet:true, HMD, Settings, Script,
|
||||
Vec3, Quat, MyAvatar, Entities, Overlays, Camera, Messages, Xform, clamp, Controller, Mat4 */
|
||||
|
||||
Script.include(Script.resolvePath("../libraries/utils.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 = 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 = 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:
|
||||
// * 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);
|
||||
}
|
||||
|
||||
var modelURL;
|
||||
if (Settings.getValue("tabletVisibleToOthers")) {
|
||||
modelURL = TABLET_MODEL_PATH;
|
||||
} else {
|
||||
modelURL = LOCAL_TABLET_MODEL_PATH;
|
||||
}
|
||||
|
||||
var tabletProperties = {
|
||||
name: "WebTablet Tablet",
|
||||
type: "Model",
|
||||
modelURL: TABLET_MODEL_PATH,
|
||||
modelURL: modelURL,
|
||||
url: modelURL, // for overlay
|
||||
grabbable: true, // for overlay
|
||||
userData: JSON.stringify({
|
||||
"grabbableKey": {"grabbable": true}
|
||||
}),
|
||||
|
@ -127,7 +136,14 @@ WebTablet = function (url, width, dpi, hand, clientOnly) {
|
|||
this.calculateTabletAttachmentProperties(hand, true, tabletProperties);
|
||||
|
||||
this.cleanUpOldTablets();
|
||||
this.tabletEntityID = Entities.addEntity(tabletProperties, clientOnly);
|
||||
|
||||
if (Settings.getValue("tabletVisibleToOthers")) {
|
||||
this.tabletEntityID = Entities.addEntity(tabletProperties, clientOnly);
|
||||
this.tabletIsOverlay = false;
|
||||
} else {
|
||||
this.tabletEntityID = Overlays.addOverlay("model", tabletProperties);
|
||||
this.tabletIsOverlay = true;
|
||||
}
|
||||
|
||||
if (this.webOverlayID) {
|
||||
Overlays.deleteOverlay(this.webOverlayID);
|
||||
|
@ -151,12 +167,12 @@ WebTablet = function (url, width, dpi, hand, clientOnly) {
|
|||
isAA: HMD.active
|
||||
});
|
||||
|
||||
var HOME_BUTTON_Y_OFFSET = (this.height / 2) - 0.035;
|
||||
this.homeButtonEntity = Overlays.addOverlay("sphere", {
|
||||
var HOME_BUTTON_Y_OFFSET = (this.height / 2) - 0.009;
|
||||
this.homeButtonID = Overlays.addOverlay("sphere", {
|
||||
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),
|
||||
dimensions: { x: 0.04, y: 0.04, z: 0.02},
|
||||
dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor},
|
||||
alpha: 0.0,
|
||||
visible: true,
|
||||
drawInFront: false,
|
||||
|
@ -165,7 +181,7 @@ WebTablet = function (url, width, dpi, hand, clientOnly) {
|
|||
});
|
||||
|
||||
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 onHomeScreen = tablet.onHomeScreen();
|
||||
if (onHomeScreen) {
|
||||
|
@ -184,7 +200,16 @@ WebTablet = function (url, width, dpi, hand, clientOnly) {
|
|||
};
|
||||
|
||||
this.getLocation = function() {
|
||||
return Entities.getEntityProperties(_this.tabletEntityID, ["localPosition", "localRotation"]);
|
||||
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"]);
|
||||
}
|
||||
};
|
||||
this.clicked = false;
|
||||
|
||||
|
@ -242,8 +267,12 @@ WebTablet.prototype.getOverlayObject = function () {
|
|||
|
||||
WebTablet.prototype.destroy = function () {
|
||||
Overlays.deleteOverlay(this.webOverlayID);
|
||||
Entities.deleteEntity(this.tabletEntityID);
|
||||
Overlays.deleteOverlay(this.homeButtonEntity);
|
||||
if (this.tabletIsOverlay) {
|
||||
Overlays.deleteOverlay(this.tabletEntityID);
|
||||
} else {
|
||||
Entities.deleteEntity(this.tabletEntityID);
|
||||
}
|
||||
Overlays.deleteOverlay(this.homeButtonID);
|
||||
HMD.displayModeChanged.disconnect(this.myOnHmdChanged);
|
||||
|
||||
Controller.mousePressEvent.disconnect(this.myMousePressEvent);
|
||||
|
@ -426,10 +455,16 @@ WebTablet.prototype.getPosition = function () {
|
|||
|
||||
WebTablet.prototype.mousePressEvent = function (event) {
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
var entityPickResults = Entities.findRayIntersection(pickRay, true, [this.tabletEntityID]); // non-accurate picking
|
||||
if (entityPickResults.intersects && entityPickResults.entityID === this.tabletEntityID) {
|
||||
var overlayPickResults = Overlays.findRayIntersection(pickRay);
|
||||
if (overlayPickResults.intersects && overlayPickResults.overlayID === HMD.homeButtonID) {
|
||||
var entityPickResults;
|
||||
if (this.tabletIsOverlay) {
|
||||
entityPickResults = Overlays.findRayIntersection(pickRay, true, [this.tabletEntityID]);
|
||||
} 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 onHomeScreen = tablet.onHomeScreen();
|
||||
if (onHomeScreen) {
|
||||
|
@ -438,11 +473,15 @@ WebTablet.prototype.mousePressEvent = function (event) {
|
|||
tablet.gotoHomeScreen();
|
||||
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;
|
||||
var invCameraXform = new Xform(Camera.orientation, Camera.position).inv();
|
||||
this.initialLocalIntersectionPoint = invCameraXform.xformPoint(entityPickResults.intersection);
|
||||
this.initialLocalPosition = Entities.getEntityProperties(this.tabletEntityID, ["localPosition"]).localPosition;
|
||||
if (this.tabletIsOverlay) {
|
||||
this.initialLocalPosition = Overlays.getProperty(this.tabletEntityID, "localPosition");
|
||||
} else {
|
||||
this.initialLocalPosition = Entities.getEntityProperties(this.tabletEntityID, ["localPosition"]).localPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -488,9 +527,15 @@ WebTablet.prototype.mouseMoveEvent = function (event) {
|
|||
var localIntersectionPoint = Vec3.sum(localPickRay.origin, Vec3.multiply(localPickRay.direction, result.distance));
|
||||
var localOffset = Vec3.subtract(localIntersectionPoint, this.initialLocalIntersectionPoint);
|
||||
var localPosition = Vec3.sum(this.initialLocalPosition, localOffset);
|
||||
Entities.editEntity(this.tabletEntityID, {
|
||||
localPosition: localPosition
|
||||
});
|
||||
if (this.tabletIsOverlay) {
|
||||
Overlays.editOverlay(this.tabletEntityID, {
|
||||
localPosition: localPosition
|
||||
});
|
||||
} else {
|
||||
Entities.editEntity(this.tabletEntityID, {
|
||||
localPosition: localPosition
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3866,6 +3866,12 @@ SelectionDisplay = (function() {
|
|||
var somethingClicked = false;
|
||||
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
|
||||
Overlays.editOverlay(selectionBox, {
|
||||
ignoreRayIntersection: true
|
||||
|
|
|
@ -37,6 +37,15 @@ var conserveResources = true;
|
|||
|
||||
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.
|
||||
//
|
||||
|
@ -229,20 +238,12 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
break;
|
||||
case 'refresh':
|
||||
removeOverlays();
|
||||
populateUserList(message.params);
|
||||
UserActivityLogger.palAction("refresh", "");
|
||||
break;
|
||||
case 'updateGain':
|
||||
data = message.params;
|
||||
if (data['isReleased']) {
|
||||
// isReleased=true happens once at the end of a cycle of dragging
|
||||
// the slider about, but with same gain as last isReleased=false so
|
||||
// we don't set the gain in that case, and only here do we want to
|
||||
// send an analytic event.
|
||||
UserActivityLogger.palAction("avatar_gain_changed", data['sessionId']);
|
||||
} else {
|
||||
Users.setAvatarGain(data['sessionId'], data['gain']);
|
||||
// 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", "");
|
||||
break;
|
||||
case 'displayNameUpdate':
|
||||
if (MyAvatar.displayName !== message.params) {
|
||||
|
@ -271,13 +272,42 @@ function addAvatarNode(id) {
|
|||
color: color(selected, false, 0.0),
|
||||
ignoreRayIntersection: false}, selected, !conserveResources);
|
||||
}
|
||||
// Each open/refresh will capture a stable set of avatarsOfInterest, within the specified filter.
|
||||
var avatarsOfInterest = {};
|
||||
function populateUserList(selectData) {
|
||||
var filter = Settings.getValue('pal/filtered') && {distance: Settings.getValue('pal/nearDistance')};
|
||||
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
|
||||
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 = {
|
||||
displayName: avatar.sessionDisplayName,
|
||||
displayName: name,
|
||||
userName: '',
|
||||
sessionId: id || '',
|
||||
audioLevel: 0.0,
|
||||
|
@ -289,10 +319,12 @@ function populateUserList(selectData) {
|
|||
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.
|
||||
Users.requestUsernameFromID(id);
|
||||
avatarsOfInterest[id] = true;
|
||||
}
|
||||
data.push(avatarPalDatum);
|
||||
print('PAL data:', JSON.stringify(avatarPalDatum));
|
||||
});
|
||||
conserveResources = Object.keys(avatarsOfInterest).length > 20;
|
||||
sendToQml({ method: 'users', params: data });
|
||||
if (selectData) {
|
||||
selectData[2] = true;
|
||||
|
@ -317,8 +349,8 @@ var pingPong = true;
|
|||
function updateOverlays() {
|
||||
var eye = Camera.position;
|
||||
AvatarList.getAvatarIdentifiers().forEach(function (id) {
|
||||
if (!id) {
|
||||
return; // don't update ourself
|
||||
if (!id || !avatarsOfInterest[id]) {
|
||||
return; // don't update ourself, or avatars we're not interested in
|
||||
}
|
||||
var avatar = AvatarList.getAvatar(id);
|
||||
if (!avatar) {
|
||||
|
|
|
@ -26,12 +26,19 @@
|
|||
print("show tablet-ui");
|
||||
|
||||
var DEFAULT_WIDTH = 0.4375;
|
||||
var DEFAULT_HMD_TABLET_SCALE = 100;
|
||||
var HMD_TABLET_SCALE = Settings.getValue("hmdTabletScale") || DEFAULT_HMD_TABLET_SCALE;
|
||||
UIWebTablet = new WebTablet("qml/hifi/tablet/TabletRoot.qml", DEFAULT_WIDTH * (HMD_TABLET_SCALE / 100), null, activeHand, true);
|
||||
var DEFAULT_TABLET_SCALE = 100;
|
||||
var toolbarMode = Tablet.getTablet("com.highfidelity.interface.tablet.system").toolbarMode;
|
||||
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();
|
||||
HMD.tabletID = UIWebTablet.tabletEntityID;
|
||||
HMD.homeButtonID = UIWebTablet.homeButtonEntity;
|
||||
HMD.homeButtonID = UIWebTablet.homeButtonID;
|
||||
HMD.tabletScreenID = UIWebTablet.webOverlayID;
|
||||
}
|
||||
|
||||
function hideTabletUI() {
|
||||
|
@ -48,6 +55,7 @@
|
|||
UIWebTablet = null;
|
||||
HMD.tabletID = null;
|
||||
HMD.homeButtonID = null;
|
||||
HMD.tabletScreenID = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,7 +85,7 @@
|
|||
hideTabletUI();
|
||||
HMD.closeTablet();
|
||||
} else if (HMD.showTablet && !tabletShown && !toolbarMode) {
|
||||
UserActivityLogger.openedTablet();
|
||||
UserActivityLogger.openedTablet(Settings.getValue("tabletVisibleToOthers"));
|
||||
showTabletUI();
|
||||
} else if (!HMD.showTablet && tabletShown) {
|
||||
UserActivityLogger.closedTablet();
|
||||
|
@ -126,5 +134,6 @@
|
|||
Entities.deleteEntity(HMD.tabletID);
|
||||
HMD.tabletID = null;
|
||||
HMD.homeButtonID = null;
|
||||
HMD.tabletScreenID = null;
|
||||
});
|
||||
}()); // END LOCAL_SCOPE
|
||||
|
|
257
scripts/tutorials/entity_scripts/sit.js
Normal file
257
scripts/tutorials/entity_scripts/sit.js
Normal file
|
@ -0,0 +1,257 @@
|
|||
(function() {
|
||||
Script.include("/~/system/libraries/utils.js");
|
||||
|
||||
var SETTING_KEY = "com.highfidelity.avatar.isSitting";
|
||||
var ROLE = "fly";
|
||||
var ANIMATION_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/animations/sitting_idle.fbx";
|
||||
var ANIMATION_FPS = 30;
|
||||
var ANIMATION_FIRST_FRAME = 1;
|
||||
var ANIMATION_LAST_FRAME = 10;
|
||||
var RELEASE_KEYS = ['w', 'a', 's', 'd', 'UP', 'LEFT', 'DOWN', 'RIGHT'];
|
||||
var RELEASE_TIME = 500; // ms
|
||||
var RELEASE_DISTANCE = 0.2; // meters
|
||||
var MAX_IK_ERROR = 20;
|
||||
var DESKTOP_UI_CHECK_INTERVAL = 250;
|
||||
var DESKTOP_MAX_DISTANCE = 5;
|
||||
var SIT_DELAY = 25
|
||||
|
||||
this.entityID = null;
|
||||
this.timers = {};
|
||||
this.animStateHandlerID = null;
|
||||
|
||||
this.preload = function(entityID) {
|
||||
this.entityID = entityID;
|
||||
}
|
||||
this.unload = function() {
|
||||
if (MyAvatar.sessionUUID === this.getSeatUser()) {
|
||||
this.sitUp(this.entityID);
|
||||
}
|
||||
if (this.interval) {
|
||||
Script.clearInterval(this.interval);
|
||||
this.interval = null;
|
||||
}
|
||||
this.cleanupOverlay();
|
||||
}
|
||||
|
||||
this.setSeatUser = function(user) {
|
||||
var userData = Entities.getEntityProperties(this.entityID, ["userData"]).userData;
|
||||
userData = JSON.parse(userData);
|
||||
|
||||
if (user) {
|
||||
userData.seat.user = user;
|
||||
} else {
|
||||
delete userData.seat.user;
|
||||
}
|
||||
|
||||
Entities.editEntity(this.entityID, {
|
||||
userData: JSON.stringify(userData)
|
||||
});
|
||||
}
|
||||
this.getSeatUser = function() {
|
||||
var properties = Entities.getEntityProperties(this.entityID, ["userData", "position"]);
|
||||
var userData = JSON.parse(properties.userData);
|
||||
|
||||
if (userData.seat.user && userData.seat.user !== MyAvatar.sessionUUID) {
|
||||
var avatar = AvatarList.getAvatar(userData.seat.user);
|
||||
if (avatar && Vec3.distance(avatar.position, properties.position) > RELEASE_DISTANCE) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return userData.seat.user;
|
||||
}
|
||||
|
||||
this.checkSeatForAvatar = function() {
|
||||
var seatUser = this.getSeatUser();
|
||||
var avatarIdentifiers = AvatarList.getAvatarIdentifiers();
|
||||
for (var i in avatarIdentifiers) {
|
||||
var avatar = AvatarList.getAvatar(avatarIdentifiers[i]);
|
||||
if (avatar && avatar.sessionUUID === seatUser) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this.sitDown = function() {
|
||||
if (this.checkSeatForAvatar()) {
|
||||
print("Someone is already sitting in that chair.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setSeatUser(MyAvatar.sessionUUID);
|
||||
|
||||
var previousValue = Settings.getValue(SETTING_KEY);
|
||||
Settings.setValue(SETTING_KEY, this.entityID);
|
||||
if (previousValue === "") {
|
||||
MyAvatar.characterControllerEnabled = false;
|
||||
MyAvatar.hmdLeanRecenterEnabled = false;
|
||||
MyAvatar.overrideRoleAnimation(ROLE, ANIMATION_URL, ANIMATION_FPS, true, ANIMATION_FIRST_FRAME, ANIMATION_LAST_FRAME);
|
||||
MyAvatar.resetSensorsAndBody();
|
||||
}
|
||||
|
||||
var that = this;
|
||||
Script.setTimeout(function() {
|
||||
var properties = Entities.getEntityProperties(that.entityID, ["position", "rotation"]);
|
||||
var index = MyAvatar.getJointIndex("Hips");
|
||||
MyAvatar.pinJoint(index, properties.position, properties.rotation);
|
||||
|
||||
that.animStateHandlerID = MyAvatar.addAnimationStateHandler(function(properties) {
|
||||
return { headType: 0 };
|
||||
}, ["headType"]);
|
||||
Script.update.connect(that, that.update);
|
||||
Controller.keyPressEvent.connect(that, that.keyPressed);
|
||||
Controller.keyReleaseEvent.connect(that, that.keyReleased);
|
||||
for (var i in RELEASE_KEYS) {
|
||||
Controller.captureKeyEvents({ text: RELEASE_KEYS[i] });
|
||||
}
|
||||
}, SIT_DELAY);
|
||||
}
|
||||
|
||||
this.sitUp = function() {
|
||||
this.setSeatUser(null);
|
||||
|
||||
if (Settings.getValue(SETTING_KEY) === this.entityID) {
|
||||
MyAvatar.restoreRoleAnimation(ROLE);
|
||||
MyAvatar.characterControllerEnabled = true;
|
||||
MyAvatar.hmdLeanRecenterEnabled = true;
|
||||
|
||||
var index = MyAvatar.getJointIndex("Hips");
|
||||
MyAvatar.clearPinOnJoint(index);
|
||||
|
||||
MyAvatar.resetSensorsAndBody();
|
||||
|
||||
Script.setTimeout(function() {
|
||||
MyAvatar.bodyPitch = 0.0;
|
||||
MyAvatar.bodyRoll = 0.0;
|
||||
}, SIT_DELAY);
|
||||
|
||||
Settings.setValue(SETTING_KEY, "");
|
||||
}
|
||||
|
||||
MyAvatar.removeAnimationStateHandler(this.animStateHandlerID);
|
||||
Script.update.disconnect(this, this.update);
|
||||
Controller.keyPressEvent.disconnect(this, this.keyPressed);
|
||||
Controller.keyReleaseEvent.disconnect(this, this.keyReleased);
|
||||
for (var i in RELEASE_KEYS) {
|
||||
Controller.releaseKeyEvents({ text: RELEASE_KEYS[i] });
|
||||
}
|
||||
}
|
||||
|
||||
this.sit = function () {
|
||||
this.sitDown();
|
||||
}
|
||||
|
||||
this.createOverlay = function() {
|
||||
var text = "Click to sit";
|
||||
var textMargin = 0.05;
|
||||
var lineHeight = 0.15;
|
||||
|
||||
this.overlay = Overlays.addOverlay("text3d", {
|
||||
position: { x: 0.0, y: 0.0, z: 0.0},
|
||||
dimensions: { x: 0.1, y: 0.1 },
|
||||
backgroundColor: { red: 0, green: 0, blue: 0 },
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
topMargin: textMargin,
|
||||
leftMargin: textMargin,
|
||||
bottomMargin: textMargin,
|
||||
rightMargin: textMargin,
|
||||
text: text,
|
||||
lineHeight: lineHeight,
|
||||
alpha: 0.9,
|
||||
backgroundAlpha: 0.9,
|
||||
ignoreRayIntersection: true,
|
||||
visible: true,
|
||||
isFacingAvatar: true
|
||||
});
|
||||
var textSize = Overlays.textSize(this.overlay, text);
|
||||
var overlayDimensions = {
|
||||
x: textSize.width + 2 * textMargin,
|
||||
y: textSize.height + 2 * textMargin
|
||||
}
|
||||
var properties = Entities.getEntityProperties(this.entityID, ["position", "registrationPoint", "dimensions"]);
|
||||
var yOffset = (1.0 - properties.registrationPoint.y) * properties.dimensions.y + (overlayDimensions.y / 2.0);
|
||||
var overlayPosition = Vec3.sum(properties.position, { x: 0, y: yOffset, z: 0 });
|
||||
Overlays.editOverlay(this.overlay, {
|
||||
position: overlayPosition,
|
||||
dimensions: overlayDimensions
|
||||
});
|
||||
}
|
||||
this.cleanupOverlay = function() {
|
||||
if (this.overlay !== null) {
|
||||
Overlays.deleteOverlay(this.overlay);
|
||||
this.overlay = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.update = function(dt) {
|
||||
if (MyAvatar.sessionUUID === this.getSeatUser()) {
|
||||
var properties = Entities.getEntityProperties(this.entityID, ["position"]);
|
||||
var avatarDistance = Vec3.distance(MyAvatar.position, properties.position);
|
||||
var ikError = MyAvatar.getIKErrorOnLastSolve();
|
||||
if (avatarDistance > RELEASE_DISTANCE || ikError > MAX_IK_ERROR) {
|
||||
print("IK error: " + ikError + ", distance from chair: " + avatarDistance);
|
||||
this.sitUp(this.entityID);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.keyPressed = function(event) {
|
||||
if (isInEditMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (RELEASE_KEYS.indexOf(event.text) !== -1) {
|
||||
var that = this;
|
||||
this.timers[event.text] = Script.setTimeout(function() {
|
||||
that.sitUp();
|
||||
}, RELEASE_TIME);
|
||||
}
|
||||
}
|
||||
this.keyReleased = function(event) {
|
||||
if (RELEASE_KEYS.indexOf(event.text) !== -1) {
|
||||
if (this.timers[event.text]) {
|
||||
Script.clearTimeout(this.timers[event.text]);
|
||||
delete this.timers[event.text];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.canSitDesktop = function() {
|
||||
var properties = Entities.getEntityProperties(this.entityID, ["position"]);
|
||||
var distanceFromSeat = Vec3.distance(MyAvatar.position, properties.position);
|
||||
return distanceFromSeat < DESKTOP_MAX_DISTANCE && !this.checkSeatForAvatar();
|
||||
}
|
||||
|
||||
this.hoverEnterEntity = function(event) {
|
||||
if (isInEditMode() || (MyAvatar.sessionUUID === this.getSeatUser())) {
|
||||
return;
|
||||
}
|
||||
|
||||
var that = this;
|
||||
this.interval = Script.setInterval(function() {
|
||||
if (that.overlay === null) {
|
||||
if (that.canSitDesktop()) {
|
||||
that.createOverlay();
|
||||
}
|
||||
} else if (!that.canSitDesktop()) {
|
||||
that.cleanupOverlay();
|
||||
}
|
||||
}, DESKTOP_UI_CHECK_INTERVAL);
|
||||
}
|
||||
this.hoverLeaveEntity = function(event) {
|
||||
if (this.interval) {
|
||||
Script.clearInterval(this.interval);
|
||||
this.interval = null;
|
||||
}
|
||||
this.cleanupOverlay();
|
||||
}
|
||||
|
||||
this.clickDownOnEntity = function () {
|
||||
if (isInEditMode() || (MyAvatar.sessionUUID === this.getSeatUser())) {
|
||||
return;
|
||||
}
|
||||
if (this.canSitDesktop()) {
|
||||
this.sitDown();
|
||||
}
|
||||
}
|
||||
});
|
111
unpublishedScripts/marketplace/shortbow/README.md
Normal file
111
unpublishedScripts/marketplace/shortbow/README.md
Normal 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.
|
||||
|
BIN
unpublishedScripts/marketplace/shortbow/bow/Arrow_impact1.L.wav
Normal file
BIN
unpublishedScripts/marketplace/shortbow/bow/Arrow_impact1.L.wav
Normal file
Binary file not shown.
BIN
unpublishedScripts/marketplace/shortbow/bow/Bow_draw.1.L.wav
Normal file
BIN
unpublishedScripts/marketplace/shortbow/bow/Bow_draw.1.L.wav
Normal file
Binary file not shown.
Binary file not shown.
BIN
unpublishedScripts/marketplace/shortbow/bow/arrow-sparkle.png
Normal file
BIN
unpublishedScripts/marketplace/shortbow/bow/arrow-sparkle.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 3.6 KiB |
BIN
unpublishedScripts/marketplace/shortbow/bow/arrow.fbx
Normal file
BIN
unpublishedScripts/marketplace/shortbow/bow/arrow.fbx
Normal file
Binary file not shown.
BIN
unpublishedScripts/marketplace/shortbow/bow/bow-deadly.fbx
Normal file
BIN
unpublishedScripts/marketplace/shortbow/bow/bow-deadly.fbx
Normal file
Binary file not shown.
671
unpublishedScripts/marketplace/shortbow/bow/bow.js
Normal file
671
unpublishedScripts/marketplace/shortbow/bow/bow.js
Normal 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;
|
||||
});
|
44
unpublishedScripts/marketplace/shortbow/bow/bow.json
Normal file
44
unpublishedScripts/marketplace/shortbow/bow/bow.json
Normal 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
|
||||
}
|
32
unpublishedScripts/marketplace/shortbow/bow/bow.svo.json
Normal file
32
unpublishedScripts/marketplace/shortbow/bow/bow.svo.json
Normal 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
|
||||
}
|
|
@ -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
|
BIN
unpublishedScripts/marketplace/shortbow/bow/notch.wav
Normal file
BIN
unpublishedScripts/marketplace/shortbow/bow/notch.wav
Normal file
Binary file not shown.
67
unpublishedScripts/marketplace/shortbow/bow/spawnBow.js
Normal file
67
unpublishedScripts/marketplace/shortbow/bow/spawnBow.js
Normal 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);
|
61
unpublishedScripts/marketplace/shortbow/enemyClientEntity.js
Normal file
61
unpublishedScripts/marketplace/shortbow/enemyClientEntity.js
Normal 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();
|
||||
});
|
41
unpublishedScripts/marketplace/shortbow/enemyServerEntity.js
Normal file
41
unpublishedScripts/marketplace/shortbow/enemyServerEntity.js
Normal 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();
|
||||
});
|
BIN
unpublishedScripts/marketplace/shortbow/models/Amber.fbx
Normal file
BIN
unpublishedScripts/marketplace/shortbow/models/Amber.fbx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
877
unpublishedScripts/marketplace/shortbow/shortbow.js
Normal file
877
unpublishedScripts/marketplace/shortbow/shortbow.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
840
unpublishedScripts/marketplace/shortbow/shortbow.json
Normal file
840
unpublishedScripts/marketplace/shortbow/shortbow.json
Normal 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
|
||||
}
|
621
unpublishedScripts/marketplace/shortbow/shortbowGameManager.js
Normal file
621
unpublishedScripts/marketplace/shortbow/shortbowGameManager.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
});
|
BIN
unpublishedScripts/marketplace/shortbow/sounds/escape.wav
Normal file
BIN
unpublishedScripts/marketplace/shortbow/sounds/escape.wav
Normal file
Binary file not shown.
BIN
unpublishedScripts/marketplace/shortbow/sounds/explosion.wav
Normal file
BIN
unpublishedScripts/marketplace/shortbow/sounds/explosion.wav
Normal file
Binary file not shown.
BIN
unpublishedScripts/marketplace/shortbow/sounds/fight.wav
Normal file
BIN
unpublishedScripts/marketplace/shortbow/sounds/fight.wav
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue