mirror of
https://github.com/overte-org/overte.git
synced 2025-07-22 22:54:07 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into tablet-ui-edit-js
This commit is contained in:
commit
c2085ebb6e
61 changed files with 4509 additions and 655 deletions
|
@ -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];
|
||||
|
|
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.
|
@ -3104,23 +3104,18 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
|
|||
|
||||
if (compositor.getReticleVisible() || !isHMDMode() || !compositor.getReticleOverDesktop() ||
|
||||
getOverlays().getOverlayAtPoint(glm::vec2(transformedPos.x(), transformedPos.y())) != UNKNOWN_OVERLAY_ID) {
|
||||
if (_mouseToOverlays) {
|
||||
getOverlays().mouseMoveEvent(&mappedEvent);
|
||||
} else {
|
||||
getEntities()->mouseMoveEvent(&mappedEvent);
|
||||
}
|
||||
getOverlays().mouseMoveEvent(&mappedEvent);
|
||||
getEntities()->mouseMoveEvent(&mappedEvent);
|
||||
}
|
||||
|
||||
if (!_mouseToOverlays) {
|
||||
_controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts
|
||||
}
|
||||
_controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts
|
||||
|
||||
// if one of our scripts have asked to capture this event, then stop processing it
|
||||
if (_controllerScriptingInterface->isMouseCaptured()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_mouseToOverlays && _keyboardMouseDevice->isActive()) {
|
||||
if (_keyboardMouseDevice->isActive()) {
|
||||
_keyboardMouseDevice->mouseMoveEvent(event);
|
||||
}
|
||||
}
|
||||
|
@ -3128,7 +3123,6 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
|
|||
void Application::mousePressEvent(QMouseEvent* event) {
|
||||
// Inhibit the menu if the user is using alt-mouse dragging
|
||||
_altPressed = false;
|
||||
_mouseToOverlays = false;
|
||||
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
// If we get a mouse press event it means it wasn't consumed by the offscreen UI,
|
||||
|
@ -3145,23 +3139,20 @@ void Application::mousePressEvent(QMouseEvent* event) {
|
|||
event->buttons(), event->modifiers());
|
||||
|
||||
if (!_aboutToQuit) {
|
||||
if (getOverlays().mousePressEvent(&mappedEvent)) {
|
||||
_mouseToOverlays = true;
|
||||
} else if (!_controllerScriptingInterface->areEntityClicksCaptured()) {
|
||||
getOverlays().mousePressEvent(&mappedEvent);
|
||||
if (!_controllerScriptingInterface->areEntityClicksCaptured()) {
|
||||
getEntities()->mousePressEvent(&mappedEvent);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_mouseToOverlays) {
|
||||
_controllerScriptingInterface->emitMousePressEvent(&mappedEvent); // send events to any registered scripts
|
||||
}
|
||||
_controllerScriptingInterface->emitMousePressEvent(&mappedEvent); // send events to any registered scripts
|
||||
|
||||
// if one of our scripts have asked to capture this event, then stop processing it
|
||||
if (_controllerScriptingInterface->isMouseCaptured()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_mouseToOverlays && hasFocus()) {
|
||||
if (hasFocus()) {
|
||||
if (_keyboardMouseDevice->isActive()) {
|
||||
_keyboardMouseDevice->mousePressEvent(event);
|
||||
}
|
||||
|
@ -3195,23 +3186,18 @@ void Application::mouseReleaseEvent(QMouseEvent* event) {
|
|||
event->buttons(), event->modifiers());
|
||||
|
||||
if (!_aboutToQuit) {
|
||||
if (_mouseToOverlays) {
|
||||
getOverlays().mouseReleaseEvent(&mappedEvent);
|
||||
} else {
|
||||
getEntities()->mouseReleaseEvent(&mappedEvent);
|
||||
}
|
||||
getOverlays().mouseReleaseEvent(&mappedEvent);
|
||||
getEntities()->mouseReleaseEvent(&mappedEvent);
|
||||
}
|
||||
|
||||
if (!_mouseToOverlays) {
|
||||
_controllerScriptingInterface->emitMouseReleaseEvent(&mappedEvent); // send events to any registered scripts
|
||||
}
|
||||
_controllerScriptingInterface->emitMouseReleaseEvent(&mappedEvent); // send events to any registered scripts
|
||||
|
||||
// if one of our scripts have asked to capture this event, then stop processing it
|
||||
if (_controllerScriptingInterface->isMouseCaptured()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_mouseToOverlays && hasFocus()) {
|
||||
if (hasFocus()) {
|
||||
if (_keyboardMouseDevice->isActive()) {
|
||||
_keyboardMouseDevice->mouseReleaseEvent(event);
|
||||
}
|
||||
|
@ -3457,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;
|
||||
|
|
|
@ -676,8 +676,6 @@ private:
|
|||
void addAssetToWorldInfoDone(QString modelName);
|
||||
void addAssetToWorldError(QString modelName, QString errorText);
|
||||
|
||||
bool _mouseToOverlays { false };
|
||||
|
||||
QQuickItem* _addAssetToWorldMessageBox{ nullptr };
|
||||
QStringList _addAssetToWorldInfoKeys; // Model name
|
||||
QStringList _addAssetToWorldInfoMessages; // Info message
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
@ -2483,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.
|
||||
//
|
||||
|
|
|
@ -345,23 +345,14 @@ OverlayID Overlays::getOverlayAtPoint(const glm::vec2& point) {
|
|||
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.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)) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -336,8 +336,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);
|
||||
|
@ -377,21 +376,19 @@ 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 formatGPU = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA);
|
||||
gpu::Element formatMip = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA);
|
||||
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);
|
||||
theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits());
|
||||
generateMips(theTexture, image, formatMip, true);
|
||||
}
|
||||
|
||||
return theTexture;
|
||||
|
|
BIN
scripts/system/assets/models/teleport-seat.fbx
Normal file
BIN
scripts/system/assets/models/teleport-seat.fbx
Normal file
Binary file not shown.
|
@ -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) {
|
||||
|
|
|
@ -167,12 +167,12 @@ WebTablet = function (url, width, dpi, hand, clientOnly) {
|
|||
isAA: HMD.active
|
||||
});
|
||||
|
||||
var HOME_BUTTON_Y_OFFSET = (this.height / 2) - 0.035;
|
||||
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,
|
||||
|
|
|
@ -26,9 +26,15 @@
|
|||
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.homeButtonID;
|
||||
|
|
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 Width: | Height: | 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.
BIN
unpublishedScripts/marketplace/shortbow/sounds/gameOn.wav
Normal file
BIN
unpublishedScripts/marketplace/shortbow/sounds/gameOn.wav
Normal file
Binary file not shown.
BIN
unpublishedScripts/marketplace/shortbow/sounds/gameOver.wav
Normal file
BIN
unpublishedScripts/marketplace/shortbow/sounds/gameOver.wav
Normal file
Binary file not shown.
Binary file not shown.
BIN
unpublishedScripts/marketplace/shortbow/sounds/spawn.wav
Normal file
BIN
unpublishedScripts/marketplace/shortbow/sounds/spawn.wav
Normal file
Binary file not shown.
BIN
unpublishedScripts/marketplace/shortbow/sounds/targetHit.wav
Normal file
BIN
unpublishedScripts/marketplace/shortbow/sounds/targetHit.wav
Normal file
Binary file not shown.
Binary file not shown.
BIN
unpublishedScripts/marketplace/shortbow/sounds/waveComplete.wav
Normal file
BIN
unpublishedScripts/marketplace/shortbow/sounds/waveComplete.wav
Normal file
Binary file not shown.
210
unpublishedScripts/marketplace/shortbow/spawnShortbow.js
Normal file
210
unpublishedScripts/marketplace/shortbow/spawnShortbow.js
Normal file
|
@ -0,0 +1,210 @@
|
|||
//
|
||||
// Created by Ryan Huffman on 1/10/2017
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
/* globals utils, SHORTBOW_ENTITIES, TEMPLATES:true */
|
||||
|
||||
Script.include('utils.js');
|
||||
Script.include('shortbow.js');
|
||||
Script.include('shortbowGameManager.js');
|
||||
TEMPLATES = SHORTBOW_ENTITIES.Entities;
|
||||
|
||||
// Merge two objects into a new object. If a key name appears in both a and b,
|
||||
// the value in a will be used.
|
||||
//
|
||||
// @param {object} a
|
||||
// @param {object} b
|
||||
// @returns {object} The new object
|
||||
function mergeObjects(a, b) {
|
||||
var obj = {};
|
||||
var key;
|
||||
for (key in b) {
|
||||
obj[key] = b[key];
|
||||
}
|
||||
for (key in a) {
|
||||
obj[key] = a[key];
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Spawn an entity from a template.
|
||||
//
|
||||
// The overrides can be used to override or add properties in the template. For instance,
|
||||
// it's common to override the `position` property so that you can set the position
|
||||
// of the entity to be spawned.
|
||||
//
|
||||
// @param {string} templateName The name of the template to spawn
|
||||
// @param {object} overrides An object containing properties that will override
|
||||
// any properties set in the template.
|
||||
function spawnTemplate(templateName, overrides) {
|
||||
var template = getTemplate(templateName);
|
||||
if (template === null) {
|
||||
print("ERROR, unknown template name:", templateName);
|
||||
return null;
|
||||
}
|
||||
print("Spawning: ", templateName);
|
||||
var properties = mergeObjects(overrides, template);
|
||||
return Entities.addEntity(properties);
|
||||
}
|
||||
|
||||
function spawnTemplates(templateName, overrides) {
|
||||
var templates = getTemplates(templateName);
|
||||
if (template.length === 0) {
|
||||
print("ERROR, unknown template name:", templateName);
|
||||
return [];
|
||||
}
|
||||
|
||||
var spawnedEntities = [];
|
||||
for (var i = 0; i < templates.length; ++i) {
|
||||
print("Spawning: ", templateName);
|
||||
var properties = mergeObjects(overrides, templates[i]);
|
||||
spawnedEntities.push(Entities.addEntity(properties));
|
||||
}
|
||||
return spawnedEntities;
|
||||
}
|
||||
|
||||
// TEMPLATES contains a dictionary of different named entity templates. An entity
|
||||
// template is just a list of properties.
|
||||
//
|
||||
// @param name Name of the template to get
|
||||
// @return {object} The matching template, or null if not found
|
||||
function getTemplate(name) {
|
||||
for (var i = 0; i < TEMPLATES.length; ++i) {
|
||||
if (TEMPLATES[i].name === name) {
|
||||
return TEMPLATES[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function getTemplates(name) {
|
||||
var templates = [];
|
||||
for (var i = 0; i < TEMPLATES.length; ++i) {
|
||||
if (TEMPLATES[i].name === name) {
|
||||
templates.push(TEMPLATES[i]);
|
||||
}
|
||||
}
|
||||
return templates;
|
||||
}
|
||||
|
||||
|
||||
// Cleanup Shortbow template data
|
||||
for (var i = 0; i < TEMPLATES.length; ++i) {
|
||||
var template = TEMPLATES[i];
|
||||
|
||||
// Fixup model url
|
||||
if (template.type === "Model") {
|
||||
var urlParts = template.modelURL.split("/");
|
||||
var filename = urlParts[urlParts.length - 1];
|
||||
var newURL = Script.resolvePath("models/" + filename);
|
||||
print("Updated url", template.modelURL, "to", newURL);
|
||||
template.modelURL = newURL;
|
||||
}
|
||||
}
|
||||
|
||||
var entityIDs = [];
|
||||
|
||||
var scoreboardID = null;
|
||||
var buttonID = null;
|
||||
var waveDisplayID = null;
|
||||
var scoreDisplayID = null;
|
||||
var highScoreDisplayID = null;
|
||||
var livesDisplayID = null;
|
||||
var platformID = null;
|
||||
function createLocalGame() {
|
||||
var rootPosition = utils.findSurfaceBelowPosition(MyAvatar.position);
|
||||
rootPosition.y += 6.11;
|
||||
|
||||
scoreboardID = spawnTemplate("SB.Scoreboard", {
|
||||
position: rootPosition
|
||||
});
|
||||
entityIDs.push(scoreboardID);
|
||||
|
||||
// Create start button
|
||||
buttonID = spawnTemplate("SB.StartButton", {
|
||||
parentID: scoreboardID,
|
||||
script: Script.resolvePath("startGameButtonClientEntity.js"),
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
wantsTrigger: true
|
||||
}
|
||||
})
|
||||
});
|
||||
entityIDs.push(buttonID);
|
||||
|
||||
|
||||
waveDisplayID = spawnTemplate("SB.DisplayWave", {
|
||||
parentID: scoreboardID,
|
||||
userData: JSON.stringify({
|
||||
displayType: "wave"
|
||||
})
|
||||
});
|
||||
entityIDs.push(waveDisplayID);
|
||||
|
||||
scoreDisplayID = spawnTemplate("SB.DisplayScore", {
|
||||
parentID: scoreboardID,
|
||||
userData: JSON.stringify({
|
||||
displayType: "score"
|
||||
})
|
||||
});
|
||||
entityIDs.push(scoreDisplayID);
|
||||
|
||||
livesDisplayID = spawnTemplate("SB.DisplayLives", {
|
||||
parentID: scoreboardID,
|
||||
userData: JSON.stringify({
|
||||
displayType: "lives"
|
||||
})
|
||||
});
|
||||
entityIDs.push(livesDisplayID);
|
||||
|
||||
highScoreDisplayID = spawnTemplate("SB.DisplayHighScore", {
|
||||
parentID: scoreboardID,
|
||||
userData: JSON.stringify({
|
||||
displayType: "highscore"
|
||||
})
|
||||
});
|
||||
entityIDs.push(highScoreDisplayID);
|
||||
|
||||
platformID = spawnTemplate("SB.Platform", {
|
||||
parentID: scoreboardID
|
||||
});
|
||||
entityIDs.push(platformID);
|
||||
|
||||
spawnTemplate("SB.GateCollider", {
|
||||
parentID: scoreboardID,
|
||||
visible: false
|
||||
});
|
||||
entityIDs.push(platformID);
|
||||
|
||||
Entities.editEntity(scoreboardID, {
|
||||
serverScripts: Script.resolvePath('shortbowServerEntity.js')
|
||||
});
|
||||
|
||||
spawnTemplates("SB.BowSpawn", {
|
||||
parentID: scoreboardID,
|
||||
visible: false
|
||||
});
|
||||
spawnTemplates("SB.EnemySpawn", {
|
||||
parentID: scoreboardID,
|
||||
visible: false
|
||||
});
|
||||
|
||||
var bowPositions = [];
|
||||
var spawnPositions = [];
|
||||
for (var i = 0; i < TEMPLATES.length; ++i) {
|
||||
var template = TEMPLATES[i];
|
||||
|
||||
if (template.name === "SB.BowSpawn") {
|
||||
bowPositions.push(Vec3.sum(rootPosition, template.localPosition));
|
||||
Vec3.print("Pushing bow position", Vec3.sum(rootPosition, template.localPosition));
|
||||
} else if (template.name === "SB.EnemySpawn") {
|
||||
spawnPositions.push(Vec3.sum(rootPosition, template.localPosition));
|
||||
Vec3.print("Pushing spawnposition", Vec3.sum(rootPosition, template.localPosition));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createLocalGame();
|
||||
Script.stop();
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// Created by Ryan Huffman on 1/10/2017
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
/* globals utils */
|
||||
|
||||
(function() {
|
||||
Script.include('utils.js');
|
||||
|
||||
function StartButton() {
|
||||
}
|
||||
StartButton.prototype = {
|
||||
preload: function(entityID) {
|
||||
this.entityID = entityID;
|
||||
this.commChannel = "shortbow-" + Entities.getEntityProperties(entityID, 'parentID').parentID;
|
||||
Script.addEventHandler(entityID, "collisionWithEntity", this.onCollide.bind(this));
|
||||
},
|
||||
signalAC: function() {
|
||||
Messages.sendMessage(this.commChannel, JSON.stringify({
|
||||
type: 'start-game'
|
||||
}));
|
||||
},
|
||||
onCollide: function(entityA, entityB, collision) {
|
||||
var colliderName = Entities.getEntityProperties(entityB, 'name').name;
|
||||
|
||||
if (colliderName.indexOf("projectile") > -1) {
|
||||
this.signalAC();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
StartButton.prototype.startNearTrigger = StartButton.prototype.signalAC;
|
||||
StartButton.prototype.startFarTrigger = StartButton.prototype.signalAC;
|
||||
StartButton.prototype.clickDownOnEntity = StartButton.prototype.signalAC;
|
||||
|
||||
return new StartButton();
|
||||
});
|
57
unpublishedScripts/marketplace/shortbow/utils.js
Normal file
57
unpublishedScripts/marketplace/shortbow/utils.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
//
|
||||
// Created by Ryan Huffman on 1/10/2017
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
/* globals utils:true */
|
||||
|
||||
if (!Function.prototype.bind) {
|
||||
Function.prototype.bind = function(oThis) {
|
||||
if (typeof this !== 'function') {
|
||||
// closest thing possible to the ECMAScript 5
|
||||
// internal IsCallable function
|
||||
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
|
||||
}
|
||||
|
||||
var aArgs = Array.prototype.slice.call(arguments, 1),
|
||||
fToBind = this,
|
||||
NOP = function() {},
|
||||
fBound = function() {
|
||||
return fToBind.apply(this instanceof NOP
|
||||
? this
|
||||
: oThis,
|
||||
aArgs.concat(Array.prototype.slice.call(arguments)));
|
||||
};
|
||||
|
||||
if (this.prototype) {
|
||||
// Function.prototype doesn't have a prototype property
|
||||
NOP.prototype = this.prototype;
|
||||
}
|
||||
fBound.prototype = new NOP();
|
||||
|
||||
return fBound;
|
||||
};
|
||||
}
|
||||
|
||||
utils = {
|
||||
parseJSON: function(json) {
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
findSurfaceBelowPosition: function(pos) {
|
||||
var result = Entities.findRayIntersection({
|
||||
origin: pos,
|
||||
direction: { x: 0.0, y: -1.0, z: 0.0 }
|
||||
}, true);
|
||||
if (result.intersects) {
|
||||
return result.intersection;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
};
|
Loading…
Reference in a new issue