Merge branch 'master' of github.com:highfidelity/hifi into voxel-paint-opti

This commit is contained in:
Seth Alves 2017-02-27 20:56:16 -08:00
commit 3b669089e4
59 changed files with 4227 additions and 191 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -14,7 +14,7 @@
/* global getEntityCustomData, flatten, Xform, Script, Quat, Vec3, MyAvatar, Entities, Overlays, Settings,
Reticle, Controller, Camera, Messages, Mat4, getControllerWorldLocation, getGrabPointSphereOffset, setGrabCommunications,
Menu, HMD */
Menu, HMD, isInEditMode */
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
(function() { // BEGIN LOCAL_SCOPE
@ -399,7 +399,7 @@ function entityHasActions(entityID) {
function findRayIntersection(pickRay, precise, include, exclude) {
var entities = Entities.findRayIntersection(pickRay, precise, include, exclude, true);
var overlays = Overlays.findRayIntersection(pickRay);
var overlays = Overlays.findRayIntersection(pickRay, precise, [], [HMD.tabletID]);
if (!overlays.intersects || (entities.intersects && (entities.distance <= overlays.distance))) {
return entities;
}
@ -644,6 +644,7 @@ EquipHotspotBuddy.prototype.updateHotspot = function(hotspot, timestamp) {
// override default sphere with a user specified model, if it exists.
overlayInfoSet.overlays.push(Overlays.addOverlay("model", {
name: "hotspot overlay",
url: hotspot.modelURL ? hotspot.modelURL : DEFAULT_SPHERE_MODEL_URL,
position: hotspot.worldPosition,
rotation: {
@ -776,7 +777,7 @@ function MyController(hand) {
};
this.actionID = null; // action this script created...
this.grabbedEntity = null; // on this entity.
this.grabbedThingID = null; // on this entity.
this.grabbedOverlay = null;
this.state = STATE_OFF;
this.pointer = null; // entity-id of line object
@ -853,14 +854,19 @@ function MyController(hand) {
};
this.callEntityMethodOnGrabbed = function(entityMethodName) {
if (this.grabbedIsOverlay) {
return;
}
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
Entities.callEntityMethod(this.grabbedEntity, entityMethodName, args);
Entities.callEntityMethod(this.grabbedThingID, entityMethodName, args);
};
this.setState = function(newState, reason) {
if ((isInEditMode() && this.grabbedEntity !== HMD.tabletID )&& (newState !== STATE_OFF &&
newState !== STATE_SEARCHING &&
newState !== STATE_OVERLAY_STYLUS_TOUCHING)) {
if ((isInEditMode() && this.grabbedThingID !== HMD.tabletID) &&
(newState !== STATE_OFF &&
newState !== STATE_SEARCHING &&
newState !== STATE_OVERLAY_STYLUS_TOUCHING &&
newState !== STATE_OVERLAY_LASER_TOUCHING)) {
return;
}
setGrabCommunications((newState === STATE_DISTANCE_HOLDING) || (newState === STATE_NEAR_GRABBING));
@ -903,6 +909,7 @@ function MyController(hand) {
if (!this.grabPointSphere) {
this.grabPointSphere = Overlays.addOverlay("sphere", {
name: "grabPointSphere",
localPosition: getGrabPointSphereOffset(this.handToController()),
localRotation: { x: 0, y: 0, z: 0, w: 1 },
dimensions: GRAB_POINT_SPHERE_RADIUS * 2,
@ -933,6 +940,7 @@ function MyController(hand) {
var brightColor = colorPow(color, 0.06);
if (this.searchSphere === null) {
var sphereProperties = {
name: "searchSphere",
position: location,
rotation: rotation,
outerRadius: size * 1.2,
@ -955,7 +963,8 @@ function MyController(hand) {
innerAlpha: 1.0,
outerAlpha: 0.0,
outerRadius: size * 1.2,
visible: true
visible: true,
ignoreRayIntersection: true
});
}
};
@ -966,6 +975,7 @@ function MyController(hand) {
}
var stylusProperties = {
name: "stylus",
url: Script.resourcesPath() + "meshes/tablet-stylus-fat.fbx",
localPosition: Vec3.sum({ x: 0.0,
y: WEB_TOUCH_Y_OFFSET,
@ -1000,6 +1010,7 @@ function MyController(hand) {
this.overlayLineOn = function(closePoint, farPoint, color) {
if (this.overlayLine === null) {
var lineProperties = {
name: "line",
glow: 1.0,
start: closePoint,
end: farPoint,
@ -1175,6 +1186,13 @@ function MyController(hand) {
}
}
var candidateOverlays = Overlays.findOverlays(worldHandPosition, WEB_DISPLAY_STYLUS_DISTANCE);
for (var j = 0; j < candidateOverlays.length; j++) {
if (this.isTablet(candidateOverlays[j])) {
nearWeb = true;
}
}
if (nearWeb) {
this.showStylus();
var rayPickInfo = this.calcRayPickInfo(this.hand);
@ -1423,7 +1441,7 @@ function MyController(hand) {
var okToEquipFromOtherHand = ((this.getOtherHandController().state == STATE_NEAR_GRABBING ||
this.getOtherHandController().state == STATE_DISTANCE_HOLDING) &&
this.getOtherHandController().grabbedEntity == hotspot.entityID);
this.getOtherHandController().grabbedThingID == hotspot.entityID);
var hasParent = true;
if (props.parentID === NULL_UUID) {
hasParent = false;
@ -1581,7 +1599,7 @@ function MyController(hand) {
var farSearching = this.triggerSmoothedSqueezed() && (Date.now() - this.searchStartTime > FAR_SEARCH_DELAY);
this.grabbedEntity = null;
this.grabbedThingID = null;
this.grabbedOverlay = null;
this.isInitialGrab = false;
this.preparingHoldRelease = false;
@ -1589,7 +1607,7 @@ function MyController(hand) {
this.checkForUnexpectedChildren();
if ((this.triggerSmoothedReleased() && this.secondaryReleased())) {
this.grabbedEntity = null;
this.grabbedThingID = null;
this.setState(STATE_OFF, "trigger released");
return;
}
@ -1610,8 +1628,9 @@ function MyController(hand) {
if (potentialEquipHotspot) {
if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && holdEnabled) {
this.grabbedHotspot = potentialEquipHotspot;
this.grabbedEntity = potentialEquipHotspot.entityID;
this.setState(STATE_HOLD, "equipping '" + entityPropertiesCache.getProps(this.grabbedEntity).name + "'");
this.grabbedThingID = potentialEquipHotspot.entityID;
this.grabbedIsOverlay = false;
this.setState(STATE_HOLD, "equipping '" + entityPropertiesCache.getProps(this.grabbedThingID).name + "'");
return;
}
@ -1622,6 +1641,11 @@ function MyController(hand) {
return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE);
});
var candidateOverlays = Overlays.findOverlays(handPosition, NEAR_GRAB_RADIUS);
var grabbableOverlays = candidateOverlays.filter(function(overlayID) {
return Overlays.getProperty(overlayID, "grabbable");
});
if (rayPickInfo.entityID) {
this.intersectionDistance = rayPickInfo.distance;
if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) {
@ -1633,6 +1657,23 @@ function MyController(hand) {
this.intersectionDistance = 0;
}
if (grabbableOverlays.length > 0) {
grabbableOverlays.sort(function(a, b) {
var aPosition = Overlays.getProperty(a, "position");
var aDistance = Vec3.distance(aPosition, handPosition);
var bPosition = Overlays.getProperty(b, "position");
var bDistance = Vec3.distance(bPosition, handPosition);
return aDistance - bDistance;
});
this.grabbedThingID = grabbableOverlays[0];
this.grabbedIsOverlay = true;
if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && nearGrabEnabled) {
this.setState(STATE_NEAR_GRABBING, "near grab overlay '" +
Overlays.getProperty(this.grabbedThingID, "name") + "'");
return;
}
}
var entity;
if (grabbableEntities.length > 0) {
// sort by distance
@ -1643,7 +1684,8 @@ function MyController(hand) {
});
entity = grabbableEntities[0];
name = entityPropertiesCache.getProps(entity).name;
this.grabbedEntity = entity;
this.grabbedThingID = entity;
this.grabbedIsOverlay = false;
if (this.entityWantsTrigger(entity)) {
if (this.triggerSmoothedGrab()) {
this.setState(STATE_NEAR_TRIGGER, "near trigger '" + name + "'");
@ -1654,7 +1696,7 @@ function MyController(hand) {
} else {
// If near something grabbable, grab it!
if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && nearGrabEnabled) {
this.setState(STATE_NEAR_GRABBING, "near grab '" + name + "'");
this.setState(STATE_NEAR_GRABBING, "near grab entity '" + name + "'");
return;
} else {
// potentialNearGrabEntity = entity;
@ -1677,7 +1719,8 @@ function MyController(hand) {
name = entityPropertiesCache.getProps(entity).name;
if (this.entityWantsTrigger(entity)) {
if (this.triggerSmoothedGrab()) {
this.grabbedEntity = entity;
this.grabbedThingID = entity;
this.grabbedIsOverlay = false;
this.setState(STATE_FAR_TRIGGER, "far trigger '" + name + "'");
return;
} else {
@ -1685,7 +1728,8 @@ function MyController(hand) {
}
} else if (this.entityIsDistanceGrabbable(rayPickInfo.entityID, handPosition)) {
if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled && farSearching) {
this.grabbedEntity = entity;
this.grabbedThingID = entity;
this.grabbedIsOverlay = false;
this.grabbedDistance = rayPickInfo.distance;
this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'");
return;
@ -1765,7 +1809,8 @@ function MyController(hand) {
Entities.sendHoverOverEntity(entity, pointerEvent);
}
this.grabbedEntity = entity;
this.grabbedThingID = entity;
this.grabbedIsOverlay = false;
this.setState(STATE_ENTITY_STYLUS_TOUCHING, "begin touching entity '" + name + "'");
return true;
@ -1893,7 +1938,8 @@ function MyController(hand) {
}
if (this.triggerSmoothedGrab() && (!isEditing() || this.isTablet(entity))) {
this.grabbedEntity = entity;
this.grabbedThingID = entity;
this.grabbedIsOverlay = false;
this.setState(STATE_ENTITY_LASER_TOUCHING, "begin touching entity '" + name + "'");
return true;
}
@ -2004,7 +2050,7 @@ function MyController(hand) {
var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix());
var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition);
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES);
var now = Date.now();
// add the action and initialize some variables
@ -2034,7 +2080,7 @@ function MyController(hand) {
var timeScale = this.distanceGrabTimescale(this.mass, distanceToObject);
this.actionID = NULL_UUID;
this.actionID = Entities.addAction("spring", this.grabbedEntity, {
this.actionID = Entities.addAction("spring", this.grabbedThingID, {
targetPosition: this.currentObjectPosition,
linearTimeScale: timeScale,
targetRotation: this.currentObjectRotation,
@ -2059,12 +2105,12 @@ function MyController(hand) {
this.ensureDynamic = function() {
// if we distance hold something and keep it very still before releasing it, it ends up
// non-dynamic in bullet. If it's too still, give it a little bounce so it will fall.
var props = Entities.getEntityProperties(this.grabbedEntity, ["velocity", "dynamic", "parentID"]);
var props = Entities.getEntityProperties(this.grabbedThingID, ["velocity", "dynamic", "parentID"]);
if (props.dynamic && props.parentID == NULL_UUID) {
var velocity = props.velocity;
if (Vec3.length(velocity) < 0.05) { // see EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD
velocity = { x: 0.0, y: 0.2, z:0.0 };
Entities.editEntity(this.grabbedEntity, { velocity: velocity });
Entities.editEntity(this.grabbedThingID, { velocity: velocity });
}
}
};
@ -2086,7 +2132,7 @@ function MyController(hand) {
var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix());
var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition);
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES);
var now = Date.now();
var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds
@ -2141,7 +2187,7 @@ function MyController(hand) {
newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition);
var objectToAvatar = Vec3.subtract(this.currentObjectPosition, MyAvatar.position);
var handControllerData = getEntityCustomData('handControllerKey', this.grabbedEntity, defaultMoveWithHeadData);
var handControllerData = getEntityCustomData('handControllerKey', this.grabbedThingID, defaultMoveWithHeadData);
if (handControllerData.disableMoveWithHead !== true) {
// mix in head motion
if (MOVE_WITH_HEAD) {
@ -2170,7 +2216,7 @@ function MyController(hand) {
this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition), COLORS_GRAB_DISTANCE_HOLD);
var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition));
var success = Entities.updateAction(this.grabbedEntity, this.actionID, {
var success = Entities.updateAction(this.grabbedThingID, this.actionID, {
targetPosition: newTargetPosition,
linearTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject),
targetRotation: this.currentObjectRotation,
@ -2187,7 +2233,7 @@ function MyController(hand) {
};
this.setupHoldAction = function() {
this.actionID = Entities.addAction("hold", this.grabbedEntity, {
this.actionID = Entities.addAction("hold", this.grabbedThingID, {
hand: this.hand === RIGHT_HAND ? "right" : "left",
timeScale: NEAR_GRABBING_ACTION_TIMEFRAME,
relativePosition: this.offsetPosition,
@ -2277,17 +2323,30 @@ function MyController(hand) {
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
if (this.entityActivated) {
var saveGrabbedID = this.grabbedEntity;
var saveGrabbedID = this.grabbedThingID;
this.release();
this.grabbedEntity = saveGrabbedID;
this.grabbedThingID = saveGrabbedID;
}
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
if (FORCE_IGNORE_IK) {
var grabbedProperties;
if (this.grabbedIsOverlay) {
grabbedProperties = {
position: Overlays.getProperty(this.grabbedThingID, "position"),
rotation: Overlays.getProperty(this.grabbedThingID, "rotation"),
parentID: Overlays.getProperty(this.grabbedThingID, "parentID"),
parentJointIndex: Overlays.getProperty(this.grabbedThingID, "parentJointIndex"),
dynamic: false,
shapeType: "none"
};
this.ignoreIK = true;
} else {
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true;
grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES);
if (FORCE_IGNORE_IK) {
this.ignoreIK = true;
} else {
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedThingID, DEFAULT_GRABBABLE_DATA);
this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true;
}
}
var handRotation;
@ -2326,7 +2385,8 @@ function MyController(hand) {
this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset);
}
var isPhysical = propsArePhysical(grabbedProperties) || entityHasActions(this.grabbedEntity);
var isPhysical = propsArePhysical(grabbedProperties) ||
(!this.grabbedIsOverlay && entityHasActions(this.grabbedThingID));
if (isPhysical && this.state == STATE_NEAR_GRABBING && grabbedProperties.parentID === NULL_UUID) {
// grab entity via action
if (!this.setupHoldAction()) {
@ -2334,7 +2394,7 @@ function MyController(hand) {
}
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
action: 'grab',
grabbedEntity: this.grabbedEntity,
grabbedEntity: this.grabbedThingID,
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
}));
} else {
@ -2359,29 +2419,36 @@ function MyController(hand) {
reparentProps.localPosition = this.offsetPosition;
reparentProps.localRotation = this.offsetRotation;
}
Entities.editEntity(this.grabbedEntity, reparentProps);
if (this.grabbedIsOverlay) {
Overlays.editOverlay(this.grabbedThingID, reparentProps);
} else {
Entities.editEntity(this.grabbedThingID, reparentProps);
}
if (this.thisHandIsParent(grabbedProperties)) {
// this should never happen, but if it does, don't set previous parent to be this hand.
// this.previousParentID[this.grabbedEntity] = NULL;
// this.previousParentJointIndex[this.grabbedEntity] = -1;
// this.previousParentID[this.grabbedThingID] = NULL;
// this.previousParentJointIndex[this.grabbedThingID] = -1;
} else {
this.previousParentID[this.grabbedEntity] = grabbedProperties.parentID;
this.previousParentJointIndex[this.grabbedEntity] = grabbedProperties.parentJointIndex;
this.previousParentID[this.grabbedThingID] = grabbedProperties.parentID;
this.previousParentJointIndex[this.grabbedThingID] = grabbedProperties.parentJointIndex;
}
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
action: 'equip',
grabbedEntity: this.grabbedEntity,
grabbedEntity: this.grabbedThingID,
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
}));
}
Entities.editEntity(this.grabbedEntity, {
velocity: { x: 0, y: 0, z: 0 },
angularVelocity: { x: 0, y: 0, z: 0 },
// dynamic: false
});
if (!this.grabbedIsOverlay) {
Entities.editEntity(this.grabbedThingID, {
velocity: { x: 0, y: 0, z: 0 },
angularVelocity: { x: 0, y: 0, z: 0 },
// dynamic: false
});
}
if (this.state == STATE_NEAR_GRABBING) {
this.callEntityMethodOnGrabbed("startNearGrab");
@ -2447,26 +2514,39 @@ function MyController(hand) {
if (dropDetected && !this.waitForTriggerRelease && this.triggerSmoothedGrab()) {
// store the offset attach points into preferences.
if (USE_ATTACH_POINT_SETTINGS && this.grabbedHotspot && this.grabbedEntity) {
var prefprops = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]);
if (USE_ATTACH_POINT_SETTINGS && this.grabbedHotspot && this.grabbedThingID) {
var prefprops = Entities.getEntityProperties(this.grabbedThingID, ["localPosition", "localRotation"]);
if (prefprops && prefprops.localPosition && prefprops.localRotation) {
storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand,
prefprops.localPosition, prefprops.localRotation);
}
}
var grabbedEntity = this.grabbedEntity;
var grabbedEntity = this.grabbedThingID;
this.release();
this.grabbedEntity = grabbedEntity;
this.grabbedThingID = grabbedEntity;
this.setState(STATE_NEAR_GRABBING, "drop gesture detected");
return;
}
this.prevDropDetected = dropDetected;
}
var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID", "parentJointIndex",
var props;
if (this.grabbedIsOverlay) {
props = {
localPosition: Overlays.getProperty(this.grabbedThingID, "localPosition"),
parentID: Overlays.getProperty(this.grabbedThingID, "parentID"),
parentJointIndex: Overlays.getProperty(this.grabbedThingID, "parentJointIndex"),
position: Overlays.getProperty(this.grabbedThingID, "position"),
rotation: Overlays.getProperty(this.grabbedThingID, "rotation"),
dimensions: Overlays.getProperty(this.grabbedThingID, "dimensions"),
registrationPoint: { x: 0.5, y: 0.5, z: 0.5 }
};
} else {
props = Entities.getEntityProperties(this.grabbedThingID, ["localPosition", "parentID", "parentJointIndex",
"position", "rotation", "dimensions",
"registrationPoint"]);
}
if (!props.position) {
// server may have reset, taking our equipped entity with it. move back to "off" state
this.callEntityMethodOnGrabbed("releaseGrab");
@ -2478,7 +2558,7 @@ function MyController(hand) {
// someone took it from us or otherwise edited the parentID. end the grab. We don't do this
// for equipped things so that they can be adjusted while equipped.
this.callEntityMethodOnGrabbed("releaseGrab");
this.grabbedEntity = null;
this.grabbedThingID = null;
this.setState(STATE_OFF, "someone took it");
return;
}
@ -2560,7 +2640,7 @@ function MyController(hand) {
if (this.actionID && this.actionTimeout - now < ACTION_TTL_REFRESH * MSECS_PER_SEC) {
// if less than a 5 seconds left, refresh the actions ttl
var success = Entities.updateAction(this.grabbedEntity, this.actionID, {
var success = Entities.updateAction(this.grabbedThingID, this.actionID, {
hand: this.hand === RIGHT_HAND ? "right" : "left",
timeScale: NEAR_GRABBING_ACTION_TIMEFRAME,
relativePosition: this.offsetPosition,
@ -2574,14 +2654,14 @@ function MyController(hand) {
this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC);
} else {
print("continueNearGrabbing -- updateAction failed");
Entities.deleteAction(this.grabbedEntity, this.actionID);
Entities.deleteAction(this.grabbedThingID, this.actionID);
this.setupHoldAction();
}
}
};
this.maybeScale = function(props) {
if (!objectScalingEnabled || this.isTablet(this.grabbedEntity)) {
if (!objectScalingEnabled || this.isTablet(this.grabbedThingID) || this.grabbedIsOverlay) {
return;
}
@ -2603,7 +2683,7 @@ function MyController(hand) {
this.getOtherHandController().getHandPosition()));
var currentRescale = scalingCurrentDistance / this.scalingStartDistance;
var newDimensions = Vec3.multiply(currentRescale, this.scalingStartDimensions);
Entities.editEntity(this.grabbedEntity, { dimensions: newDimensions });
Entities.editEntity(this.grabbedThingID, { dimensions: newDimensions });
}
};
@ -2654,7 +2734,7 @@ function MyController(hand) {
this.nearTrigger = function(deltaTime, timestamp) {
if (this.triggerSmoothedReleased()) {
this.callEntityMethodOnGrabbed("stopNearTrigger");
this.grabbedEntity = null;
this.grabbedThingID = null;
this.setState(STATE_OFF, "trigger released");
return;
}
@ -2664,7 +2744,7 @@ function MyController(hand) {
this.farTrigger = function(deltaTime, timestamp) {
if (this.triggerSmoothedReleased()) {
this.callEntityMethodOnGrabbed("stopFarTrigger");
this.grabbedEntity = null;
this.grabbedThingID = null;
this.setState(STATE_OFF, "trigger released");
return;
}
@ -2679,9 +2759,9 @@ function MyController(hand) {
var intersection = findRayIntersection(pickRay, true, [], [], true);
if (intersection.accurate || intersection.overlayID) {
this.lastPickTime = now;
if (intersection.entityID != this.grabbedEntity) {
if (intersection.entityID != this.grabbedThingID) {
this.callEntityMethodOnGrabbed("stopFarTrigger");
this.grabbedEntity = null;
this.grabbedThingID = null;
this.setState(STATE_OFF, "laser moved off of entity");
return;
}
@ -2703,13 +2783,13 @@ function MyController(hand) {
this.entityTouchingEnter = function() {
// test for intersection between controller laser and web entity plane.
var intersectInfo = handLaserIntersectEntity(this.grabbedEntity,
var intersectInfo = handLaserIntersectEntity(this.grabbedThingID,
getControllerWorldLocation(this.handToController(), true));
if (intersectInfo) {
var pointerEvent = {
type: "Press",
id: this.hand + 1, // 0 is reserved for hardware mouse
pos2D: projectOntoEntityXYPlane(this.grabbedEntity, intersectInfo.point),
pos2D: projectOntoEntityXYPlane(this.grabbedThingID, intersectInfo.point),
pos3D: intersectInfo.point,
normal: intersectInfo.normal,
direction: intersectInfo.searchRay.direction,
@ -2717,8 +2797,8 @@ function MyController(hand) {
isPrimaryHeld: true
};
Entities.sendMousePressOnEntity(this.grabbedEntity, pointerEvent);
Entities.sendClickDownOnEntity(this.grabbedEntity, pointerEvent);
Entities.sendMousePressOnEntity(this.grabbedThingID, pointerEvent);
Entities.sendClickDownOnEntity(this.grabbedThingID, pointerEvent);
this.touchingEnterTimer = 0;
this.touchingEnterPointerEvent = pointerEvent;
@ -2740,7 +2820,7 @@ function MyController(hand) {
this.entityTouchingExit = function() {
// test for intersection between controller laser and web entity plane.
var intersectInfo = handLaserIntersectEntity(this.grabbedEntity,
var intersectInfo = handLaserIntersectEntity(this.grabbedThingID,
getControllerWorldLocation(this.handToController(), true));
if (intersectInfo) {
var pointerEvent;
@ -2748,7 +2828,7 @@ function MyController(hand) {
pointerEvent = {
type: "Release",
id: this.hand + 1, // 0 is reserved for hardware mouse
pos2D: projectOntoEntityXYPlane(this.grabbedEntity, intersectInfo.point),
pos2D: projectOntoEntityXYPlane(this.grabbedThingID, intersectInfo.point),
pos3D: intersectInfo.point,
normal: intersectInfo.normal,
direction: intersectInfo.searchRay.direction,
@ -2761,11 +2841,11 @@ function MyController(hand) {
pointerEvent.isPrimaryHeld = false;
}
Entities.sendMouseReleaseOnEntity(this.grabbedEntity, pointerEvent);
Entities.sendClickReleaseOnEntity(this.grabbedEntity, pointerEvent);
Entities.sendHoverLeaveEntity(this.grabbedEntity, pointerEvent);
Entities.sendMouseReleaseOnEntity(this.grabbedThingID, pointerEvent);
Entities.sendClickReleaseOnEntity(this.grabbedThingID, pointerEvent);
Entities.sendHoverLeaveEntity(this.grabbedThingID, pointerEvent);
}
this.grabbedEntity = null;
this.grabbedThingID = null;
this.grabbedOverlay = null;
};
@ -2773,7 +2853,7 @@ function MyController(hand) {
this.touchingEnterTimer += dt;
entityPropertiesCache.addEntity(this.grabbedEntity);
entityPropertiesCache.addEntity(this.grabbedThingID);
if (this.state == STATE_ENTITY_LASER_TOUCHING && !this.triggerSmoothedGrab()) {
this.setState(STATE_OFF, "released trigger");
@ -2781,7 +2861,7 @@ function MyController(hand) {
}
// test for intersection between controller laser and web entity plane.
var intersectInfo = handLaserIntersectEntity(this.grabbedEntity,
var intersectInfo = handLaserIntersectEntity(this.grabbedThingID,
getControllerWorldLocation(this.handToController(), true));
if (intersectInfo) {
@ -2791,15 +2871,15 @@ function MyController(hand) {
return;
}
if (Entities.keyboardFocusEntity != this.grabbedEntity) {
if (Entities.keyboardFocusEntity != this.grabbedThingID) {
Overlays.keyboardFocusOverlay = 0;
Entities.keyboardFocusEntity = this.grabbedEntity;
Entities.keyboardFocusEntity = this.grabbedThingID;
}
var pointerEvent = {
type: "Move",
id: this.hand + 1, // 0 is reserved for hardware mouse
pos2D: projectOntoEntityXYPlane(this.grabbedEntity, intersectInfo.point),
pos2D: projectOntoEntityXYPlane(this.grabbedThingID, intersectInfo.point),
pos3D: intersectInfo.point,
normal: intersectInfo.normal,
direction: intersectInfo.searchRay.direction,
@ -2810,8 +2890,8 @@ function MyController(hand) {
var POINTER_PRESS_TO_MOVE_DELAY = 0.25; // seconds
if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY ||
Vec3.distance(intersectInfo.point, this.touchingEnterPointerEvent.pos3D) > this.deadspotRadius) {
Entities.sendMouseMoveOnEntity(this.grabbedEntity, pointerEvent);
Entities.sendHoldingClickOnEntity(this.grabbedEntity, pointerEvent);
Entities.sendMouseMoveOnEntity(this.grabbedThingID, pointerEvent);
Entities.sendHoldingClickOnEntity(this.grabbedThingID, pointerEvent);
this.deadspotExpired = true;
}
@ -2821,7 +2901,7 @@ function MyController(hand) {
}
Reticle.setVisible(false);
} else {
this.grabbedEntity = null;
this.grabbedThingID = null;
this.setState(STATE_OFF, "grabbed entity was destroyed");
return;
}
@ -2906,7 +2986,7 @@ function MyController(hand) {
Overlays.sendMouseReleaseOnOverlay(this.grabbedOverlay, pointerEvent);
Overlays.sendHoverLeaveOverlay(this.grabbedOverlay, pointerEvent);
}
this.grabbedEntity = null;
this.grabbedThingID = null;
this.grabbedOverlay = null;
};
@ -2929,7 +3009,7 @@ function MyController(hand) {
if (this.state == STATE_OVERLAY_STYLUS_TOUCHING &&
intersectInfo.distance > WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET + WEB_TOUCH_Y_TOUCH_DEADZONE_SIZE) {
this.grabbedEntity = null;
this.grabbedThingID = null;
this.setState(STATE_OFF, "pulled away from overlay");
return;
}
@ -2986,7 +3066,7 @@ function MyController(hand) {
}
Reticle.setVisible(false);
} else {
this.grabbedEntity = null;
this.grabbedThingID = null;
this.setState(STATE_OFF, "grabbed overlay was destroyed");
return;
}
@ -2995,7 +3075,7 @@ function MyController(hand) {
this.release = function() {
this.turnOffVisualizations();
if (this.grabbedEntity !== null) {
if (this.grabbedThingID !== null) {
if (this.state === STATE_HOLD) {
this.callEntityMethodOnGrabbed("releaseEquip");
}
@ -3003,35 +3083,49 @@ function MyController(hand) {
// Make a small release haptic pulse if we really were holding something
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
if (this.actionID !== null) {
Entities.deleteAction(this.grabbedEntity, this.actionID);
Entities.deleteAction(this.grabbedThingID, this.actionID);
} else {
// no action, so it's a parenting grab
if (this.previousParentID[this.grabbedEntity] === NULL_UUID) {
Entities.editEntity(this.grabbedEntity, {
parentID: this.previousParentID[this.grabbedEntity],
parentJointIndex: this.previousParentJointIndex[this.grabbedEntity]
});
this.ensureDynamic();
if (this.previousParentID[this.grabbedThingID] === NULL_UUID) {
if (this.grabbedIsOverlay) {
Overlays.editOverlay(this.grabbedThingID, {
parentID: NULL_UUID,
parentJointIndex: -1
});
} else {
Entities.editEntity(this.grabbedThingID, {
parentID: this.previousParentID[this.grabbedThingID],
parentJointIndex: this.previousParentJointIndex[this.grabbedThingID]
});
this.ensureDynamic();
}
} else {
// we're putting this back as a child of some other parent, so zero its velocity
Entities.editEntity(this.grabbedEntity, {
parentID: this.previousParentID[this.grabbedEntity],
parentJointIndex: this.previousParentJointIndex[this.grabbedEntity],
velocity: {x: 0, y: 0, z: 0},
angularVelocity: {x: 0, y: 0, z: 0}
});
if (this.grabbedIsOverlay) {
Overlays.editOverlay(this.grabbedThingID, {
parentID: this.previousParentID[this.grabbedThingID],
parentJointIndex: this.previousParentJointIndex[this.grabbedThingID],
});
} else {
// we're putting this back as a child of some other parent, so zero its velocity
Entities.editEntity(this.grabbedThingID, {
parentID: this.previousParentID[this.grabbedThingID],
parentJointIndex: this.previousParentJointIndex[this.grabbedThingID],
velocity: {x: 0, y: 0, z: 0},
angularVelocity: {x: 0, y: 0, z: 0}
});
}
}
}
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
action: 'release',
grabbedEntity: this.grabbedEntity,
grabbedEntity: this.grabbedThingID,
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
}));
}
this.actionID = null;
this.grabbedEntity = null;
this.grabbedThingID = null;
this.grabbedOverlay = null;
this.grabbedHotspot = null;
@ -3119,9 +3213,13 @@ function MyController(hand) {
}
_this.previouslyUnhooked[childID] = now;
// we don't know if it's an entity or an overlay
Entities.editEntity(childID, { parentID: previousParentID, parentJointIndex: previousParentJointIndex });
Overlays.editOverlay(childID, { parentID: previousParentID, parentJointIndex: previousParentJointIndex });
} else {
Entities.editEntity(childID, { parentID: NULL_UUID });
Overlays.editOverlay(childID, { parentID: NULL_UUID });
}
}
});
@ -3240,8 +3338,8 @@ var handleHandMessages = function(channel, message, sender) {
selectedController.release();
var wearableEntity = data.entityID;
entityPropertiesCache.addEntity(wearableEntity);
selectedController.grabbedEntity = wearableEntity;
var hotspots = selectedController.collectEquipHotspots(selectedController.grabbedEntity);
selectedController.grabbedThingID = wearableEntity;
var hotspots = selectedController.collectEquipHotspots(selectedController.grabbedThingID);
if (hotspots.length > 0) {
if (hotspotIndex >= hotspots.length) {
hotspotIndex = 0;

View file

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

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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