Merge pull request #8627 from huffman/feat/tutorialv3

Tutorial
This commit is contained in:
Chris Collins 2016-10-07 15:52:25 -07:00 committed by GitHub
commit bde3a6d02a
44 changed files with 3443 additions and 47 deletions

View file

@ -87,6 +87,7 @@
#include <PhysicsEngine.h>
#include <PhysicsHelpers.h>
#include <plugins/PluginManager.h>
#include <plugins/PluginUtils.h>
#include <plugins/CodecPlugin.h>
#include <RecordingScriptingInterface.h>
#include <RenderableWebEntityItem.h>
@ -1255,8 +1256,87 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
return entityServerNode && !isPhysicsEnabled();
});
// Get sandbox content set version, if available
auto acDirPath = PathUtils::getRootDataDirectory() + BuildInfo::MODIFIED_ORGANIZATION + "/assignment-client/";
auto contentVersionPath = acDirPath + "content-version.txt";
qDebug() << "Checking " << contentVersionPath << " for content version";
auto contentVersion = 0;
QFile contentVersionFile(contentVersionPath);
if (contentVersionFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
QString line = contentVersionFile.readAll();
// toInt() returns 0 if the conversion fails, so we don't need to specifically check for failure
contentVersion = line.toInt();
}
qDebug() << "Server content version: " << contentVersion;
bool hasTutorialContent = contentVersion >= 1;
Setting::Handle<bool> firstRun { Settings::firstRun, true };
bool hasHMDAndHandControllers = PluginUtils::isHMDAvailable("OpenVR (Vive)") && PluginUtils::isHandControllerAvailable();
Setting::Handle<bool> tutorialComplete { "tutorialComplete", false };
bool shouldGoToTutorial = hasHMDAndHandControllers && hasTutorialContent && !tutorialComplete.get();
qDebug() << "Has HMD + Hand Controllers: " << hasHMDAndHandControllers << ", current plugin: " << _displayPlugin->getName();
qDebug() << "Has tutorial content: " << hasTutorialContent;
qDebug() << "Tutorial complete: " << tutorialComplete.get();
qDebug() << "Should go to tutorial: " << shouldGoToTutorial;
// when --url in command line, teleport to location
const QString HIFI_URL_COMMAND_LINE_KEY = "--url";
int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY);
QString addressLookupString;
if (urlIndex != -1) {
addressLookupString = arguments().value(urlIndex + 1);
}
const QString TUTORIAL_PATH = "/tutorial_begin";
if (shouldGoToTutorial) {
DependencyManager::get<AddressManager>()->ifLocalSandboxRunningElse([=]() {
qDebug() << "Home sandbox appears to be running, going to Home.";
DependencyManager::get<AddressManager>()->goToLocalSandbox(TUTORIAL_PATH);
}, [=]() {
qDebug() << "Home sandbox does not appear to be running, going to Entry.";
if (firstRun.get()) {
showHelp();
}
if (addressLookupString.isEmpty()) {
DependencyManager::get<AddressManager>()->goToEntry();
} else {
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
}
});
} else {
bool isFirstRun = firstRun.get();
if (isFirstRun) {
showHelp();
}
// If this is a first run we short-circuit the address passed in
if (isFirstRun) {
if (hasHMDAndHandControllers) {
DependencyManager::get<AddressManager>()->ifLocalSandboxRunningElse([=]() {
qDebug() << "Home sandbox appears to be running, going to Home.";
DependencyManager::get<AddressManager>()->goToLocalSandbox();
}, [=]() {
qDebug() << "Home sandbox does not appear to be running, going to Entry.";
DependencyManager::get<AddressManager>()->goToEntry();
});
} else {
DependencyManager::get<AddressManager>()->goToEntry();
}
} else {
qDebug() << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString);
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
}
}
// After all of the constructor is completed, then set firstRun to false.
Setting::Handle<bool> firstRun{ Settings::firstRun, true };
firstRun.set(false);
}
@ -3279,15 +3359,6 @@ void Application::init() {
_timerStart.start();
_lastTimeUpdated.start();
// when --url in command line, teleport to location
const QString HIFI_URL_COMMAND_LINE_KEY = "--url";
int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY);
QString addressLookupString;
if (urlIndex != -1) {
addressLookupString = arguments().value(urlIndex + 1);
}
// when +connect_lobby in command line, join steam lobby
const QString STEAM_LOBBY_COMMAND_LINE_KEY = "+connect_lobby";
int lobbyIndex = arguments().indexOf(STEAM_LOBBY_COMMAND_LINE_KEY);
@ -3296,21 +3367,6 @@ void Application::init() {
SteamClient::joinLobby(lobbyId);
}
Setting::Handle<bool> firstRun { Settings::firstRun, true };
if (addressLookupString.isEmpty() && firstRun.get()) {
qCDebug(interfaceapp) << "First run and no URL passed... attempting to go to Home or Entry...";
DependencyManager::get<AddressManager>()->ifLocalSandboxRunningElse([](){
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
DependencyManager::get<AddressManager>()->goToLocalSandbox();
},
[](){
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
DependencyManager::get<AddressManager>()->goToEntry();
});
} else {
qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString);
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
}
qCDebug(interfaceapp) << "Loaded settings";

View file

@ -13,10 +13,12 @@
#include <QtScript/QScriptContext>
#include <avatar/AvatarManager.h>
#include <display-plugins/DisplayPlugin.h>
#include <display-plugins/CompositorHelper.h>
#include <OffscreenUi.h>
#include <avatar/AvatarManager.h>
#include <plugins/PluginUtils.h>
#include "Application.h"
HMDScriptingInterface::HMDScriptingInterface() {
@ -47,6 +49,14 @@ glm::vec2 HMDScriptingInterface::overlayToSpherical(const glm::vec2 & position)
return qApp->getApplicationCompositor().overlayToSpherical(position);
}
bool HMDScriptingInterface::isHMDAvailable() {
return PluginUtils::isHMDAvailable();
}
bool HMDScriptingInterface::isHandControllerAvailable() {
return PluginUtils::isHandControllerAvailable();
}
QScriptValue HMDScriptingInterface::getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine) {
glm::vec3 hudIntersection;
auto instance = DependencyManager::get<HMDScriptingInterface>();

View file

@ -38,6 +38,9 @@ public:
Q_INVOKABLE QString preferredAudioInput() const;
Q_INVOKABLE QString preferredAudioOutput() const;
Q_INVOKABLE bool isHMDAvailable();
Q_INVOKABLE bool isHandControllerAvailable();
Q_INVOKABLE bool setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const;
Q_INVOKABLE void disableHandLasers(int hands) const;

View file

@ -124,6 +124,12 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) {
}
properties.remove("start"); // so that Base3DOverlay doesn't respond to it
auto localStart = properties["localStart"];
if (localStart.isValid()) {
_start = vec3FromVariant(localStart);
}
properties.remove("localStart"); // so that Base3DOverlay doesn't respond to it
auto end = properties["end"];
// if "end" property was not there, check to see if they included aliases: endPoint
if (!end.isValid()) {
@ -133,6 +139,12 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) {
setEnd(vec3FromVariant(end));
}
auto localEnd = properties["localEnd"];
if (localEnd.isValid()) {
_end = vec3FromVariant(localEnd);
}
properties.remove("localEnd"); // so that Base3DOverlay doesn't respond to it
auto glow = properties["glow"];
if (glow.isValid()) {
setGlow(glow.toFloat());

View file

@ -68,6 +68,8 @@ public:
bool isSupported() const override { return true; }
const QString& getName() const override { return NAME; }
bool isHandController() const override { return false; }
void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); }
void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override;

View file

@ -39,6 +39,8 @@ public:
virtual bool isSupported() const override;
virtual const QString& getName() const override { return NAME; }
bool isHandController() const override { return false; }
virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); }
virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override;

View file

@ -23,7 +23,7 @@
#include "AccountManager.h"
const QString HIFI_URL_SCHEME = "hifi";
const QString DEFAULT_HIFI_ADDRESS = "hifi://entry";
const QString DEFAULT_HIFI_ADDRESS = "hifi://dev-welcome";
const QString SANDBOX_HIFI_ADDRESS = "hifi://localhost";
const QString SANDBOX_STATUS_URL = "http://localhost:60332/status";
const QString INDEX_PATH = "/";
@ -92,7 +92,7 @@ public slots:
void goBack();
void goForward();
void goToLocalSandbox(LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(SANDBOX_HIFI_ADDRESS, trigger); }
void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); }
void goToEntry(LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(DEFAULT_HIFI_ADDRESS, trigger); }
void goToUser(const QString& username);

View file

@ -24,6 +24,16 @@ void UserActivityLoggerScriptingInterface::toggledAway(bool isAway) {
logAction("toggled_away", { { "is_away", isAway } });
}
void UserActivityLoggerScriptingInterface::tutorialProgress(QString stepName, int stepNumber, float secondsToComplete, float tutorialElapsedTime) {
logAction("tutorial_progress", {
{ "step", stepName },
{ "step_number", stepNumber },
{ "seconds_to_complete", secondsToComplete },
{ "tutorial_elapsed_seconds", tutorialElapsedTime }
});
}
void UserActivityLoggerScriptingInterface::logAction(QString action, QJsonObject details) {
QMetaObject::invokeMethod(&UserActivityLogger::getInstance(), "logAction",
Q_ARG(QString, action),

View file

@ -23,6 +23,7 @@ public:
Q_INVOKABLE void enabledEdit();
Q_INVOKABLE void openedMarketplace();
Q_INVOKABLE void toggledAway(bool isAway);
Q_INVOKABLE void tutorialProgress(QString stepName, int stepNumber, float secondsToComplete, float tutorialElapsedTime);
private:
void logAction(QString action, QJsonObject details = {});

View file

@ -19,7 +19,8 @@ namespace controller {
class InputPlugin : public Plugin {
public:
virtual void pluginFocusOutEvent() = 0;
virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) = 0;
virtual bool isHandController() const = 0;
};

View file

@ -0,0 +1,34 @@
// PluginUtils.cpp
// input-plugins/src/input-plugins
//
// Created by Ryan Huffman on 9/22/16.
// 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
//
#include "PluginUtils.h"
#include "DisplayPlugin.h"
#include "InputPlugin.h"
#include "PluginManager.h"
bool PluginUtils::isHMDAvailable(const QString& pluginName) {
for (auto& displayPlugin : PluginManager::getInstance()->getDisplayPlugins()) {
// Temporarily only enable this for Vive
if (displayPlugin->isHmd() && (pluginName.isEmpty() || displayPlugin->getName() == pluginName)) {
return true;
}
}
return false;
}
bool PluginUtils::isHandControllerAvailable() {
for (auto& inputPlugin : PluginManager::getInstance()->getInputPlugins()) {
if (inputPlugin->isHandController()) {
return true;
}
}
return false;
};

View file

@ -0,0 +1,19 @@
// PluginUtils.h
// input-plugins/src/input-plugins
//
// Created by Ryan Huffman on 9/22/16.
// 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
//
#pragma once
#include <QString>
class PluginUtils {
public:
static bool isHMDAvailable(const QString& pluginName = "");
static bool isHandControllerAvailable();
};

View file

@ -1533,6 +1533,7 @@ void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, const QUrl& s
#else
operation();
#endif
hadUncaughtExceptions(*this, _fileNameString);
currentEntityIdentifier = oldIdentifier;
currentSandboxURL = oldSandboxURL;

View file

@ -17,6 +17,7 @@
#include <QDir>
#include <QUrl>
#include "PathUtils.h"
#include <QtCore/QStandardPaths>
const QString& PathUtils::resourcesPath() {
@ -29,6 +30,19 @@ const QString& PathUtils::resourcesPath() {
return staticResourcePath;
}
QString PathUtils::getRootDataDirectory() {
auto dataPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
#ifdef Q_OS_WIN
dataPath += "/AppData/Roaming/";
#elif defined(Q_OS_OSX)
dataPath += "/Library/Application Support/";
#else
dataPath += "/.local/share/";
#endif
return dataPath;
}
QString fileNameWithoutExtension(const QString& fileName, const QVector<QString> possibleExtensions) {
QString fileNameLowered = fileName.toLower();

View file

@ -22,6 +22,7 @@ class PathUtils : public QObject, public Dependency {
Q_PROPERTY(QString resources READ resourcesPath)
public:
static const QString& resourcesPath();
static QString getRootDataDirectory();
};
QString fileNameWithoutExtension(const QString& fileName, const QVector<QString> possibleExtensions);

View file

@ -15,16 +15,10 @@
#include <QtWidgets/qapplication.h>
#include <QDebug>
QString ServerPathUtils::getDataDirectory() {
auto dataPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
#include "PathUtils.h"
#ifdef Q_OS_WIN
dataPath += "/AppData/Roaming/";
#elif defined(Q_OS_OSX)
dataPath += "/Library/Application Support/";
#else
dataPath += "/.local/share/";
#endif
QString ServerPathUtils::getDataDirectory() {
auto dataPath = PathUtils::getRootDataDirectory();
dataPath += qApp->organizationName() + "/" + qApp->applicationName();

View file

@ -25,6 +25,8 @@ class NeuronPlugin : public InputPlugin {
public:
friend void FrameDataReceivedCallback(void* context, void* sender, _BvhDataHeaderEx* header, float* data);
bool isHandController() const override { return false; }
// Plugin functions
virtual bool isSupported() const override;
virtual const QString& getName() const override { return NAME; }

View file

@ -26,6 +26,8 @@ public:
bool isSupported() const override;
const QString& getName() const override { return NAME; }
bool isHandController() const override { return false; }
void init() override;
void deinit() override;

View file

@ -31,6 +31,10 @@ public:
virtual const QString& getName() const override { return NAME; }
virtual const QString& getID() const override { return HYDRA_ID_STRING; }
// Sixense always seems to initialize even if the hydras are not present. Is there
// a way we can properly detect whether the hydras are present?
bool isHandController() const override { return false; }
virtual bool activate() override;
virtual void deactivate() override;

View file

@ -26,6 +26,8 @@ public:
bool isSupported() const override;
const QString& getName() const override { return NAME; }
bool isHandController() const override { return _touch != nullptr; }
bool activate() override;
void deactivate() override;

View file

@ -35,6 +35,8 @@ public:
bool isSupported() const override;
const QString& getName() const override { return NAME; }
bool isHandController() const override { return true; }
bool activate() override;
void deactivate() override;

View file

@ -184,6 +184,10 @@ var STATE_FAR_TRIGGER = 5;
var STATE_HOLD = 6;
var STATE_ENTITY_TOUCHING = 7;
var holdEnabled = true;
var nearGrabEnabled = true;
var farGrabEnabled = true;
// "collidesWith" is specified by comma-separated list of group names
// the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatar
var COLLIDES_WITH_WHILE_GRABBED = "dynamic,otherAvatar";
@ -1440,7 +1444,7 @@ function MyController(hand) {
var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateHotSpotEntities);
if (potentialEquipHotspot) {
if (this.triggerSmoothedGrab()) {
if (this.triggerSmoothedGrab() && holdEnabled) {
this.grabbedHotspot = potentialEquipHotspot;
this.grabbedEntity = potentialEquipHotspot.entityID;
this.setState(STATE_HOLD, "equipping '" + entityPropertiesCache.getProps(this.grabbedEntity).name + "'");
@ -1483,7 +1487,7 @@ function MyController(hand) {
// potentialNearTriggerEntity = entity;
}
} else {
if (this.triggerSmoothedGrab()) {
if (this.triggerSmoothedGrab() && nearGrabEnabled) {
var props = entityPropertiesCache.getProps(entity);
var grabProps = entityPropertiesCache.getGrabProps(entity);
var refCount = grabProps.refCount ? grabProps.refCount : 0;
@ -1571,7 +1575,7 @@ function MyController(hand) {
// potentialFarTriggerEntity = entity;
}
} else if (this.entityIsDistanceGrabbable(rayPickInfo.entityID, handPosition)) {
if (this.triggerSmoothedGrab() && !isEditing()) {
if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled) {
this.grabbedEntity = entity;
this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'");
return;
@ -1589,7 +1593,9 @@ function MyController(hand) {
equipHotspotBuddy.highlightHotspot(potentialEquipHotspot);
}
this.searchIndicatorOn(rayPickInfo.searchRay);
if (farGrabEnabled) {
this.searchIndicatorOn(rayPickInfo.searchRay);
}
Reticle.setVisible(false);
};
@ -2219,7 +2225,9 @@ function MyController(hand) {
if (intersection.intersects) {
this.intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection);
}
this.searchIndicatorOn(pickRay);
if (farGrabEnabled) {
this.searchIndicatorOn(pickRay);
}
}
}
@ -2327,7 +2335,9 @@ function MyController(hand) {
}
this.intersectionDistance = intersectInfo.distance;
this.searchIndicatorOn(intersectInfo.searchRay);
if (farGrabEnabled) {
this.searchIndicatorOn(intersectInfo.searchRay);
}
Reticle.setVisible(false);
} else {
this.setState(STATE_OFF, "grabbed entity was destroyed");
@ -2681,6 +2691,7 @@ function update(deltaTime) {
entityPropertiesCache.update();
}
Messages.subscribe('Hifi-Grab-Disable');
Messages.subscribe('Hifi-Hand-Disabler');
Messages.subscribe('Hifi-Hand-Grab');
Messages.subscribe('Hifi-Hand-RayPick-Blacklist');

View file

@ -363,7 +363,7 @@ function makeToggleAction(hand) { // return a function(0|1) that makes the speci
};
}
var clickMapping = Controller.newMapping(Script.resolvePath('') + '-click');
var clickMapping = Controller.newMapping('handControllerPointer-click');
Script.scriptEnding.connect(clickMapping.disable);
// Gather the trigger data for smoothing.

View file

@ -18,6 +18,7 @@ var mappingName, basicMapping, isChecked;
var TURN_RATE = 1000;
var MENU_ITEM_NAME = "Advanced Movement For Hand Controllers";
var SETTINGS_KEY = 'advancedMovementForHandControllersIsChecked';
var isDisabled = false;
var previousSetting = Settings.getValue(SETTINGS_KEY);
if (previousSetting === '' || previousSetting === false || previousSetting === 'false') {
previousSetting = false;
@ -54,6 +55,9 @@ function registerBasicMapping() {
mappingName = 'Hifi-AdvancedMovement-Dev-' + Math.random();
basicMapping = Controller.newMapping(mappingName);
basicMapping.from(Controller.Standard.LY).to(function(value) {
if (isDisabled) {
return;
}
var stick = Controller.getValue(Controller.Standard.LS);
if (value === 1 && Controller.Hardware.OculusTouch !== undefined) {
rotate180();
@ -70,6 +74,9 @@ function registerBasicMapping() {
});
basicMapping.from(Controller.Standard.LX).to(Controller.Standard.RX);
basicMapping.from(Controller.Standard.RY).to(function(value) {
if (isDisabled) {
return;
}
var stick = Controller.getValue(Controller.Standard.RS);
if (value === 1 && Controller.Hardware.OculusTouch !== undefined) {
rotate180();
@ -144,4 +151,19 @@ HMD.displayModeChanged.connect(function(isHMDMode) {
}
});
var HIFI_ADVANCED_MOVEMENT_DISABLER_CHANNEL = 'Hifi-Advanced-Movement-Disabler';
function handleMessage(channel, message, sender) {
if (channel == HIFI_ADVANCED_MOVEMENT_DISABLER_CHANNEL) {
if (message == 'disable') {
isDisabled = true;
} else if (message == 'enable') {
isDisabled = false;
}
}
}
Messages.subscribe(HIFI_ADVANCED_MOVEMENT_DISABLER_CHANNEL);
Messages.messageReceived.connect(handleMessage);
}()); // END LOCAL_SCOPE

View file

@ -42,7 +42,7 @@ const appIcon = path.join(__dirname, '../resources/console.png');
const DELETE_LOG_FILES_OLDER_THAN_X_SECONDS = 60 * 60 * 24 * 7; // 7 Days
const LOG_FILE_REGEX = /(domain-server|ac-monitor|ac)-.*-std(out|err).txt/;
const HOME_CONTENT_URL = "http://cachefly.highfidelity.com/home.tgz";
const HOME_CONTENT_URL = "http://cachefly.highfidelity.com/home-tutorial-9.tar.gz";
function getBuildInfo() {
var buildInfoPath = null;

View file

@ -0,0 +1,195 @@
var VISIBLE_BY_DEFAULT = false;
var PARENT_ID = "{00000000-0000-0000-0000-000000000001}";
var DEBUG = false;
function debug() {
if (DEBUG) {
print.apply(self, arguments);
}
}
createControllerDisplay = function(config) {
var controllerDisplay = {
overlays: [],
partOverlays: {
},
parts: {
},
mappingName: "mapping-display",
setVisible: function(visible) {
print("CONTROLLER_DISPLAY::Setting visible", this.overlays.length);
for (var i = 0; i < this.overlays.length; ++i) {
print("i", i, this.overlays[i]);
Overlays.editOverlay(this.overlays[i], {
visible: visible
});
}
},
setPartVisible: function(partName, visible) {
return;
if (partName in this.partOverlays) {
debug("Setting part visible", partName, visible);
for (var i = 0; i < this.partOverlays[partName].length; ++i) {
Overlays.editOverlay(this.partOverlays[partName][i], {
//visible: visible
});
}
}
},
setLayerForPart: function(partName, layerName) {
if (partName in this.parts) {
debug("Setting layer...", partName, layerName);
var part = this.parts[partName];
if (part.textureLayers && layerName in part.textureLayers) {
var layer = part.textureLayers[layerName];
var textures = {};
if (layer.defaultTextureURL) {
textures[part.textureName] = layer.defaultTextureURL;
}
for (var i = 0; i < this.partOverlays[partName].length; ++i) {
Overlays.editOverlay(this.partOverlays[partName][i], {
textures: textures
});
}
}
}
}
};
var mapping = Controller.newMapping(controllerDisplay.mappingName);
for (var i = 0; i < config.controllers.length; ++i) {
var controller = config.controllers[i];
var position = controller.position;
if (controller.naturalPosition) {
position = Vec3.sum(Vec3.multiplyQbyV(
controller.rotation, controller.naturalPosition), position);
}
var overlayID = Overlays.addOverlay("model", {
url: controller.modelURL,
dimensions: controller.dimensions,
localRotation: controller.rotation,
localPosition: position,
parentID: PARENT_ID,
parentJointIndex: controller.jointIndex,
ignoreRayIntersection: true,
});
controllerDisplay.overlays.push(overlayID);
overlayID = null;
function clamp(value, min, max) {
if (value < min) {
return min;
} else if (value > max) {
return max
}
return value;
}
if (controller.parts) {
for (var partName in controller.parts) {
var part = controller.parts[partName];
var partPosition = Vec3.sum(controller.position, Vec3.multiplyQbyV(controller.rotation, part.naturalPosition));
var innerRotation = controller.rotation
controllerDisplay.parts[partName] = controller.parts[partName];
var properties = {
url: part.modelURL,
localPosition: partPosition,
localRotation: innerRotation,
parentID: PARENT_ID,
parentJointIndex: controller.jointIndex,
ignoreRayIntersection: true,
};
if (part.defaultTextureLayer) {
var textures = {};
textures[part.textureName] = part.textureLayers[part.defaultTextureLayer].defaultTextureURL;
properties['textures'] = textures;
}
var overlayID = Overlays.addOverlay("model", properties);
if (part.type == "rotational") {
var range = part.maxValue - part.minValue;
mapping.from([part.input]).peek().to(function(controller, overlayID, part) {
return function(value) {
value = clamp(value, part.minValue, part.maxValue);
var pct = (value - part.minValue) / part.maxValue;
var angle = pct * part.maxAngle;
var rotation = Quat.angleAxis(angle, part.axis);
var offset = { x: 0, y: 0, z: 0 };
if (part.origin) {
var offset = Vec3.multiplyQbyV(rotation, part.origin);
offset = Vec3.subtract(offset, part.origin);
}
var partPosition = Vec3.sum(controller.position,
Vec3.multiplyQbyV(controller.rotation, Vec3.sum(offset, part.naturalPosition)));
Overlays.editOverlay(overlayID, {
localPosition: partPosition,
localRotation: Quat.multiply(controller.rotation, rotation)
});
}
}(controller, overlayID, part));
} else if (part.type == "touchpad") {
function resolveHardware(path) {
var parts = path.split(".");
function resolveInner(base, path, i) {
if (i >= path.length) {
return base;
}
return resolveInner(base[path[i]], path, ++i);
}
return resolveInner(Controller.Hardware, parts, 0);
}
var visibleInput = resolveHardware(part.visibleInput);
var xinput = resolveHardware(part.xInput);
var yinput = resolveHardware(part.yInput);
// TODO: Touchpad inputs are currently only working for half
// of the touchpad. When that is fixed, it would be useful
// to update these to display the current finger position.
mapping.from([visibleInput]).peek().to(function(value) {
});
mapping.from([xinput]).peek().to(function(value) {
});
mapping.from([yinput]).peek().invert().to(function(value) {
});
} else if (part.type == "static") {
} else {
print("TYPE NOT SUPPORTED: ", part.type);
}
controllerDisplay.overlays.push(overlayID);
if (!(partName in controllerDisplay.partOverlays)) {
controllerDisplay.partOverlays[partName] = [];
}
controllerDisplay.partOverlays[partName].push(overlayID);
}
}
}
Controller.enableMapping(controllerDisplay.mappingName);
return controllerDisplay;
}
ControllerDisplay = function() {
};
deleteControllerDisplay = function(controllerDisplay) {
print("Deleting controller display");
for (var i = 0; i < controllerDisplay.overlays.length; ++i) {
Overlays.deleteOverlay(controllerDisplay.overlays[i]);
}
Controller.disableMapping(controllerDisplay.mappingName);
}

137
tutorial/entityData.js Normal file
View file

@ -0,0 +1,137 @@
birdFirework1 = {
"clientOnly": 0,
"collisionsWillMove": 1,
"created": "2016-09-13T23:05:08Z",
"dimensions": {
"x": 0.10120716691017151,
"y": 0.12002291530370712,
"z": 0.18833979964256287
},
"collisionsWillMove": 1,
velocity: {
x: 0,
y: -0.2,
z: 0
},
"dynamic": 1,
"gravity": {
"x": 0,
"y": -10,
"z": 0
},
"id": "{1c4061bc-b2e7-4435-bc47-3fcc39ae6624}",
"modelURL": "atp:/tutorial_models/birdStatue15.fbx",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"position": {
"x": 0.11612319946289062,
"y": 0,
"z": 0.21749019622802734
},
"queryAACube": {
"scale": 0.24519434571266174,
"x": -0.0064739733934402466,
"y": -0.12259717285633087,
"z": 0.094893023371696472
},
"rotation": {
"w": -0.083054840564727783,
"x": 0.93615627288818359,
"y": 0.34154272079467773,
"z": -0.0073701143264770508
},
"shapeType": "simple-hull",
"type": "Model",
"userData": "{\n \"hifiHomeKey\": {\n \"reset\": true\n }\n}"
} ;
birdFirework2 = {
"clientOnly": 0,
"collisionsWillMove": 1,
"created": "2016-09-12T22:56:48Z",
"dimensions": {
"x": 0.098819166421890259,
"y": 0.11143554747104645,
"z": 0.18833979964256287
},
"collisionsWillMove": 1,
velocity: {
x: 0,
y: -0.01,
z: 0
},
"dynamic": 1,
"gravity": {
"x": 0,
"y": -10,
"z": 0
},
"id": "{ba067084-8d0f-4eeb-a8a1-c6814527c1bb}",
"modelURL": "atp:/tutorial_models/statuebird4.fbx",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"position": {
"x": 0,
"y": 0.014694660902023315,
"z": 0
},
"queryAACube": {
"scale": 0.24011452496051788,
"x": -0.12005726248025894,
"y": -0.10536260157823563,
"z": -0.12005726248025894
},
"rotation": {
"w": 0.55410087108612061,
"x": 0.36000609397888184,
"y": -0.33641564846038818,
"z": -0.67092394828796387
},
"shapeType": "simple-compound",
"type": "Model",
"userData": "{\n \"hifiHomeKey\": {\n \"reset\": true\n }\n}"
};
Step1BlockData = {
"clientOnly": 0,
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"created": "2016-08-22T22:54:07Z",
"dimensions": {
"x": 0.20000000298023224,
"y": 0.20000000298023224,
"z": 0.20000000298023224
},
name: "tutorial/block",
"collisionsWillMove": 1,
velocity: {
x: 0,
y: -0.2,
z: 0
},
"dynamic": 1,
"gravity": {
"x": 0,
"y": -10,
"z": 0
},
"id": "{5c7223f8-3bc5-4cb4-913c-0e93f5994ca2}",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"queryAACube": {
"scale": 0.34641015529632568,
"x": -0.17320507764816284,
"y": -0.17320507764816284,
"z": -0.17320507764816284
},
"rotation": {
"w": 1,
"x": -0.0001373291015625,
"y": -7.62939453125e-05,
"z": -0.0003204345703125
},
"shape": "Cube",
"type": "Box",
"userData": JSON.stringify({ hifiHomeKey: { reset: true } }),
};

160
tutorial/firePit/fire.js Normal file
View file

@ -0,0 +1,160 @@
// Originally written by James Pollack, modified by Ryan Huffman for the tutorial
//
// this script turns an entity into an exploder -- anything that collides with it will be vaporized!
(function() {
var _this = this;
function Fire() {
_this = this;
}
var RED = {
red: 255,
green: 0,
blue: 0
};
var ORANGE = {
red: 255,
green: 165,
blue: 0
};
var YELLOW = {
red: 255,
green: 255,
blue: 0
};
var GREEN = {
red: 0,
green: 255,
blue: 0
};
var BLUE = {
red: 0,
green: 0,
blue: 255
};
var INDIGO = {
red: 128,
green: 0,
blue: 128
};
var VIOLET = {
red: 75,
green: 0,
blue: 130
};
var colors = [RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET];
Fire.prototype = {
preload: function(entityID) {
this.entityID = entityID;
this.EXPLOSION_SOUND = SoundCache.getSound("atp:/firepit/fire_burst.wav");
},
collisionWithEntity: function(myID, otherID, collisionInfo) {
var otherProps = Entities.getEntityProperties(otherID);
var data = null;
try {
data = JSON.parse(otherProps.userData)
} catch (err) {
print('ERROR GETTING USERDATA!');
}
if (data === null || "") {
return;
} else {
if (data.hasOwnProperty('hifiHomeKey')) {
if (data.hifiHomeKey.reset === true) {
_this.playSoundAtCurrentPosition();
_this.explodeWithColor();
Entities.deleteEntity(otherID)
Messages.sendLocalMessage('Entity-Exploded', JSON.stringify({
entityID: otherID,
}));
}
}
}
},
explodeWithColor: function() {
var myProps = Entities.getEntityProperties(this.entityID);
var color = colors[Math.floor(Math.random() * colors.length)];
var explosionParticleProperties = {
"color": color,
"isEmitting": 1,
"maxParticles": 1000,
"lifespan": 0.25,
"emitRate": 1,
"emitSpeed": 0.1,
"speedSpread": 1,
"emitOrientation": Quat.getUp(myProps.rotation),
"emitDimensions": {
"x": 0,
"y": 0,
"z": 0
},
"polarStart": 0,
"polarFinish": 0,
"azimuthStart": 0,
"azimuthFinish": 0,
"emitAcceleration": {
"x": 0,
"y": 0,
"z": 0
},
"accelerationSpread": {
"x": 0,
"y": 0,
"z": 0
},
"particleRadius": 0.829,
"radiusSpread": 0,
"radiusStart": 0.361,
"radiusFinish": 0.294,
"colorSpread": {
"red": 0,
"green": 0,
"blue": 0
},
"colorStart": {
"red": 255,
"green": 255,
"blue": 255
},
"colorFinish": {
"red": 255,
"green": 255,
"blue": 255
},
"alpha": 1,
"alphaSpread": 0,
"alphaStart": -0.2,
"alphaFinish": 0.5,
"emitterShouldTrail": 0,
"textures": "atp:/firepit/explode.png",
"type": "ParticleEffect",
lifetime: 1,
position: myProps.position
};
var explosion = Entities.addEntity(explosionParticleProperties);
},
playSoundAtCurrentPosition: function() {
var audioProperties = {
volume: 0.5,
position: Entities.getEntityProperties(this.entityID).position
};
Audio.playSound(this.EXPLOSION_SOUND, audioProperties);
},
}
return new Fire();
});

View file

@ -0,0 +1,52 @@
// Originally written for the Home content set. Pulled into the tutorial by Ryan Huffman
(function() {
var MINIMUM_LIGHT_INTENSITY = 50.0;
var MAXIMUM_LIGHT_INTENSITY = 200.0;
var LIGHT_FALLOFF_RADIUS = 0.1;
var LIGHT_INTENSITY_RANDOMNESS = 0.1;
function randFloat(low, high) {
return low + Math.random() * (high - low);
}
var _this;
function FlickeringFlame() {
_this = this;
}
var totalTime = 0;
var spacer = 2;
FlickeringFlame.prototype = {
preload: function(entityID) {
this.entityID = entityID;
Script.update.connect(this.update);
},
update: function(deltaTime) {
totalTime += deltaTime;
if (totalTime > spacer) {
var howManyAvatars = AvatarList.getAvatarIdentifiers().length;
var intensity = (MINIMUM_LIGHT_INTENSITY + (MAXIMUM_LIGHT_INTENSITY + (Math.sin(totalTime) * MAXIMUM_LIGHT_INTENSITY)));
intensity += randFloat(-LIGHT_INTENSITY_RANDOMNESS, LIGHT_INTENSITY_RANDOMNESS);
Entities.editEntity(_this.entityID, {
intensity: intensity
});
spacer = Math.random(0, 100) * (2 / howManyAvatars);
totalTime = 0;
} else {
//just keep counting
}
},
unload: function() {
Script.update.disconnect(this.update)
}
}
return new FlickeringFlame
});

99
tutorial/fuse.js Normal file
View file

@ -0,0 +1,99 @@
//
// fuse.js
//
// Created by Ryan Huffman on 9/1/16.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
Script.include('utils.js');
var DEBUG = false;
function debug() {
if (DEBUG) {
print.apply(self, arguments);
}
}
var fuseSound = SoundCache.getSound("atp:/tutorial_sounds/fuse.wav");
function getChildProperties(entityID, propertyNames) {
var childEntityIDs = Entities.getChildrenIDs(entityID);
var results = {}
for (var i = 0; i < childEntityIDs.length; ++i) {
var childEntityID = childEntityIDs[i];
var properties = Entities.getEntityProperties(childEntityID, propertyNames);
results[childEntityID] = properties;
}
return results;
}
var Fuse = function() {
};
Fuse.prototype = {
light: function() {
debug("LIT", this.entityID);
var anim = Entities.getEntityProperties(this.entityID, ['animation']).animation;
if (anim.currentFrame < 140) {
return;
}
Entities.editEntity(this.entityID, {
animation: {
currentFrame: 1,
lastFrame: 150,
running: 1,
url: "atp:/tutorial_models/fuse.fbx",
loop: 0
},
});
var injector = Audio.playSound(fuseSound, {
position: Entities.getEntityProperties(this.entityID, 'position').position,
volume: 0.7,
loop: true
});
var childrenProps = getChildProperties(this.entityID, ['type']);
for (var childEntityID in childrenProps) {
var props = childrenProps[childEntityID];
if (props.type == "ParticleEffect") {
Entities.editEntity(childEntityID, {
emitRate: 140,
});
} else if (props.type == "Light") {
Entities.editEntity(childEntityID, {
visible: true,
});
}
}
var self = this;
Script.setTimeout(function() {
debug("BLOW UP");
var spinnerID = Utils.findEntity({ name: "tutorial/equip/spinner" }, 20);
Entities.callEntityMethod(spinnerID, "onLit");
injector.stop();
var childrenProps = getChildProperties(self.entityID, ['type']);
for (var childEntityID in childrenProps) {
var props = childrenProps[childEntityID];
if (props.type == "ParticleEffect") {
Entities.editEntity(childEntityID, {
emitRate: 0,
});
} else if (props.type == "Light") {
Entities.editEntity(childEntityID, {
visible: false,
});
}
}
}, 4900);
},
preload: function(entityID) {
this.entityID = entityID;
},
};
return new Fuse();
});

17
tutorial/fuseCollider.js Normal file
View file

@ -0,0 +1,17 @@
(function() {
Script.include('utils.js');
var Fuse = function() {
};
Fuse.prototype = {
onLit: function() {
print("LIT", this.entityID);
var fuseID = Utils.findEntity({ name: "tutorial/equip/fuse" }, 20);
Entities.callEntityMethod(fuseID, "light");
},
preload: function(entityID) {
this.entityID = entityID;
},
};
return new Fuse();
});

View file

@ -0,0 +1,189 @@
//
// Created by Thijs Wenker on September 14, 2016.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
var _this;
function getResourceURL(file) {
return 'atp:/' + file;
};
const LIGHTER_ON_SOUND_URL = getResourceURL('tutorial_sounds/lighter_on.wav');
const BUTANE_SOUND_URL = getResourceURL('tutorial_sounds/butane.wav');
// TODO: fix this in the client, changing the sound volume while a sound is playing doesn't seem to work right now
const DYNAMIC_SOUND_VOLUME = false;
const BUTANE_MIN_SOUND_VOLUME = 0.05;
const FLAME_LENGTH = 0.05;
const BUTANE_SOUND_SETTINGS = {
volume: 0.4,
loop: true,
playbackGap: 1000,
playbackGapRange: 1000
};
const LIGHTER_ON_SOUND_SETTINGS = {
volume: 0.5,
loop: false
};
function RemoteLogSender(channel, identifier) {
this.channel = channel;
this.identifier = identifier;
}
RemoteLogSender.prototype = {
channel: null,
identifier: null,
debug: function(message) {
Messages.sendLocalMessage(this.channel, JSON.stringify({
message: '[DEBUG ' + this.identifier + '] ' + message
}));
}
};
var remoteLogSender = null;
function debugPrint(message) {
if (remoteLogSender !== null) {
remoteLogSender.debug(message);
}
}
ButaneLighter = function() {
_this = this;
_this.triggerValue = 0.0; // be sure to set this value in the constructor, otherwise it will be a shared value
_this.isFiring = false;
}
ButaneLighter.prototype = {
entityID: null,
lighterOnSound: null,
butaneSound: null,
lighterOnSoundInjector: null,
butaneSoundInjector: null,
butaneSoundInjectorOptions: null,
lighterParticleEntity: null,
buttonBeingPressed: null,
triggerValue: null,
isFiring: null,
getLighterParticleEntity: function() {
var result = null;
Entities.getChildrenIDs(_this.entityID).forEach(function(entity) {
var name = Entities.getEntityProperties(entity, ['name']).name;
if (name === 'lighter_particle') {
result = entity;
}
});
return result;
},
preload: function(entityID) {
_this.entityID = entityID;
_this.lighterOnSound = SoundCache.getSound(LIGHTER_ON_SOUND_URL);
_this.butaneSound = SoundCache.getSound(BUTANE_SOUND_URL);
var properties = Entities.getEntityProperties(_this.entityID, ['userData']);
try {
var userData = JSON.parse(properties.userData);
if (userData['debug'] !== undefined && userData['debug']['sessionUUID'] === MyAvatar.sessionUUID &&
userData['debug']['channel'] !== undefined)
{
remoteLogSender = new RemoteLogSender(userData['debug']['channel'], _this.entityID);
remoteLogSender.debug('Debugger initialized');
}
} catch (e) {
//failed to detect if we have a debugger
}
debugPrint(_this.getLighterParticleEntity());
},
startEquip: function(entityID, args) {
_this.lighterParticleEntity = _this.getLighterParticleEntity();
},
continueEquip: function(entityID, args) {
_this.triggerValue = Controller.getValue(args[0] === 'left' ? Controller.Standard.LT : Controller.Standard.RT);
if (_this.triggerValue > 0.2) {
if (!_this.isFiring) {
_this.startFiring();
}
_this.tryToIgnite();
_this.updateButaneSound()
return;
}
_this.stopFiring();
},
startFiring: function() {
if (_this.isFiring) {
return;
}
_this.isFiring = true;
if (_this.lighterOnSound.downloaded) {
// We don't want to override the default volume setting, so lets clone the default SETTINGS object
var lighterOnOptions = JSON.parse(JSON.stringify(LIGHTER_ON_SOUND_SETTINGS));
lighterOnOptions['position'] = Entities.getEntityProperties(_this.entityID, ['position']).position;
_this.lighterOnSoundInjector = Audio.playSound(_this.lighterOnSound, lighterOnOptions);
}
if (_this.butaneSound.downloaded) {
_this.butaneSoundInjectorOptions = JSON.parse(JSON.stringify(BUTANE_SOUND_SETTINGS));
_this.butaneSoundInjectorOptions['position'] = Entities.getEntityProperties(_this.lighterParticleEntity, ['position']).position;
if (DYNAMIC_SOUND_VOLUME) {
_this.butaneSoundInjectorOptions['volume'] = BUTANE_MIN_SOUND_VOLUME;
}
_this.butaneSoundInjector = Audio.playSound(_this.butaneSound, _this.butaneSoundInjectorOptions);
}
Entities.editEntity(_this.lighterParticleEntity, {isEmitting: _this.isFiring});
},
stopFiring: function() {
if (!_this.isFiring) {
return;
}
_this.isFiring = false;
Entities.editEntity(_this.lighterParticleEntity, {isEmitting: _this.isFiring});
_this.stopButaneSound();
},
tryToIgnite: function() {
var flameProperties = Entities.getEntityProperties(_this.lighterParticleEntity, ['position', 'rotation']);
var pickRay = {
origin: flameProperties.position,
direction: Quat.inverse(Quat.getFront(flameProperties.rotation))
}
var intersection = Entities.findRayIntersection(pickRay, true, [], [_this.entityID, _this.lighterParticleEntity]);
if (intersection.intersects && intersection.distance <= FLAME_LENGTH && intersection.properties.script !== '') {
Entities.callEntityMethod(intersection.properties.id, 'onLit', [_this.triggerValue]);
debugPrint('Light it up! found: ' + intersection.properties.id);
}
},
releaseEquip: function(entityID, args) {
_this.stopFiring();
// reset trigger value;
_this.triggerValue = 0.0;
},
updateButaneSound: function() {
if (_this.butaneSoundInjector !== null && _this.butaneSoundInjector.isPlaying()) {
_this.butaneSoundInjectorOptions = _this.butaneSoundInjector.options;
_this.butaneSoundInjectorOptions['position'] = Entities.getEntityProperties(_this.entityID, ['position']).position;
if (DYNAMIC_SOUND_VOLUME) {
_this.butaneSoundInjectorOptions['volume'] = ((BUTANE_SOUND_SETTINGS.volume - BUTANE_MIN_SOUND_VOLUME) *
_this.triggerValue) + BUTANE_MIN_SOUND_VOLUME;
}
_this.butaneSoundInjector.options = _this.butaneSoundInjectorOptions;
}
},
stopButaneSound: function() {
if (_this.butaneSoundInjector !== null && _this.butaneSoundInjector.isPlaying()) {
_this.butaneSoundInjector.stop();
}
_this.butaneSoundInjector = null;
},
unload: function() {
_this.stopButaneSound();
},
};
return new ButaneLighter();
})

View file

@ -0,0 +1,214 @@
//
// Created by Thijs Wenker on September 14, 2016.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
const TEST_MODE = false;
const SCRIPT_URL = 'atp:/tutorial/lighter/butaneLighter.js';
//Creates an entity and returns a mixed object of the creation properties and the assigned entityID
var createEntity = function(entityProperties, parent) {
if (parent.rotation !== undefined) {
if (entityProperties.rotation !== undefined) {
entityProperties.rotation = Quat.multiply(parent.rotation, entityProperties.rotation);
} else {
entityProperties.rotation = parent.rotation;
}
}
if (parent.position !== undefined) {
var localPosition = (parent.rotation !== undefined) ? Vec3.multiplyQbyV(parent.rotation, entityProperties.position) : entityProperties.position;
entityProperties.position = Vec3.sum(localPosition, parent.position)
}
if (parent.id !== undefined) {
entityProperties.parentID = parent.id;
}
entityProperties.id = Entities.addEntity(entityProperties);
return entityProperties;
};
createButaneLighter = function(transform) {
var entityProperties = {
collisionsWillMove: true,
dimensions: {
x: 0.025599999353289604,
y: 0.057399999350309372,
z: 0.37419998645782471
},
dynamic: true,
gravity: {
x: 0,
y: -9.8,
z: 0
},
velocity: {
x: 0,
y: -0.01,
z: 0
},
modelURL: 'atp:/tutorial_models/lighterIceCreamSandwich.fbx',
name: 'BrutaneLighter',
shapeType: 'simple-compound',
type: 'Model',
userData: JSON.stringify({
tag: "equip-temporary",
grabbableKey: {
invertSolidWhileHeld: true
},
wearable: {
joints: {
RightHand: [{
x: 0.029085848480463028,
y: 0.09807153046131134,
z: 0.03062543272972107
}, {
x: 0.5929139256477356,
y: 0.3207578659057617,
z: 0.7151655554771423,
w: -0.18468326330184937
}],
LeftHand: [{
x: -0.029085848480463028,
y: 0.09807153046131134,
z: 0.03062543272972107
}, {
x: -0.5929139256477356,
y: 0.3207578659057617,
z: 0.7151655554771423,
w: -0.18468326330184937
}]
}
}
}),
script: SCRIPT_URL
};
return createEntity(entityProperties, transform);
}
function createFireParticle(butaneLighter) {
var entityProperties = {
userData: JSON.stringify({ tag: "equip-temporary" }),
accelerationSpread: {
x: 0.1,
y: 0,
z: 0.1
},
alpha: 0.039999999105930328,
alphaFinish: 0.039999999105930328,
alphaStart: 0.039999999105930328,
azimuthFinish: 0.039999999105930328,
azimuthStart: 0,
dimensions: {
x: 0.49194091558456421,
y: 0.49194091558456421,
z: 0.49194091558456421
},
emitAcceleration: {
x: 0,
y: 0,
z: 0
},
emitOrientation: {
w: 1,
x: -1.52587890625e-05,
y: -1.52587890625e-05,
z: -1.52587890625e-05
},
emitRate: 770,
emitSpeed: 0.014000000432133675,
isEmitting: false,
lifespan: 0.37000000476837158,
maxParticles: 820,
name: 'lighter_particle',
particleRadius: 0.0027000000700354576,
position: {
x: -0.00044769048690795898,
y: 0.016354814171791077,
z: 0.19217036664485931
},
radiusFinish: 0.0027000000700354576,
radiusSpread: 3,
radiusStart: 0.0027000000700354576,
rotation: {
w: 1,
x: -0.0001678466796875,
y: -1.52587890625e-05,
z: -1.52587890625e-05
},
speedSpread: 0.56999999284744263,
textures: 'atp:/textures/fire3.png',
type: 'ParticleEffect',
"color": {
"red": 255,
"green": 255,
"blue": 255
},
"isEmitting": 0,
"maxParticles": 820,
"lifespan": 0.28,
"emitRate": 1100,
"emitSpeed": 0.007,
"speedSpread": 0.5699999928474426,
"emitOrientation": {
"x": -0.0000152587890625,
"y": -0.0000152587890625,
"z": -0.0000152587890625,
"w": 1
},
"emitDimensions": {
"x": 0,
"y": 0,
"z": 0
},
"polarStart": 0,
"polarFinish": 0,
"azimuthStart": 0,
"azimuthFinish": 0.03999999910593033,
"emitAcceleration": {
"x": 0,
"y": 0,
"z": 0
},
"accelerationSpread": {
"x": 0,
"y": 0,
"z": 0
},
"particleRadius": 0.0037,
"radiusSpread": 3,
"radiusStart": 0.008,
"radiusFinish": 0.0004,
"colorSpread": {
"red": 0,
"green": 0,
"blue": 0
},
"colorStart": {
"red": 255,
"green": 255,
"blue": 255
},
"colorFinish": {
"red": 255,
"green": 255,
"blue": 255
},
"alpha": 0.03999999910593033,
"alphaSpread": 0,
"alphaStart": 0.141,
"alphaFinish": 0.02,
"emitterShouldTrail": 0,
"textures": "atp:/textures/fire3.png"
};
return createEntity(entityProperties, butaneLighter);
}
doCreateButaneLighter = function(transform) {
var butaneLighter = createButaneLighter(transform);
createFireParticle(butaneLighter);
return butaneLighter;
}

187
tutorial/ownershipToken.js Normal file
View file

@ -0,0 +1,187 @@
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,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
}
function getOption(options, key, defaultValue) {
if (options.hasOwnProperty(key)) {
return options[key];
}
return defaultValue;
}
var TOKEN_NAME_PREFIX = "ownership_token-";
function getOwnershipTokenID(parentEntityID) {
var childEntityIDs = Entities.getChildrenIDs(parentEntityID);
var ownerID = null;
var ownerName = '';
for (var i = 0; i < childEntityIDs.length; ++i) {
var childID = childEntityIDs[i];
var properties = Entities.getEntityProperties(childID, ['name', 'userData', 'lifetime', 'age']);
var childName = properties.name;
if (childName.indexOf(TOKEN_NAME_PREFIX) == 0) {
if (ownerID === null || childName < ownerName) {
ownerID = childID;
ownerName = childName;
}
}
}
return ownerID;
}
function createOwnershipToken(name, parentEntityID) {
return Entities.addEntity({
type: "Box",
name: TOKEN_NAME_PREFIX + name,
visible: false,
parentID: parentEntityID,
locationPosition: { x: 0, y: 0, z: 0 },
dimensions: { x: 100, y: 100, z: 100 },
collisionless: true,
lifetime: 5
});
}
var DEBUG = true;
function debug() {
if (DEBUG) {
var args = Array.prototype.slice.call(arguments);
print.apply(this, args);
}
}
var TOKEN_STATE_DESTROYED = -1;
var TOKEN_STATE_UNOWNED = 0;
var TOKEN_STATE_REQUESTING_OWNERSHIP = 1;
var TOKEN_STATE_OWNED = 2;
OwnershipToken = function(name, parentEntityID, options) {
this.name = MyAvatar.sessionUUID + "-" + Math.floor(Math.random() * 10000000);
this.name = Math.floor(Math.random() * 10000000);
this.parentEntityID = parentEntityID;
// How often to check whether the token is available if we don't currently own it
this.checkEverySeconds = getOption(options, 'checkEverySeconds', 1000);
this.updateTokenLifetimeEvery = getOption(options, 'updateTokenLifetimeEvery', 2000);
this.onGainedOwnership = getOption(options, 'onGainedOwnership', function() { });
this.onLostOwnership = getOption(options, 'onLostOwnership', function() { });
this.ownershipTokenID = null;
this.setState(TOKEN_STATE_UNOWNED);
};
OwnershipToken.prototype = {
destroy: function() {
debug(this.name, "Destroying token");
this.setState(TOKEN_STATE_DESTROYED);
},
setState: function(newState) {
if (this.state == newState) {
debug(this.name, "Warning: Trying to set state to the current state");
return;
}
if (this.updateLifetimeID) {
debug(this.name, "Clearing update lifetime interval");
Script.clearInterval(this.updateLifetimeID);
this.updateLifetimeID = null;
}
if (this.checkOwnershipAvailableID) {
Script.clearInterval(this.checkOwnershipAvailableID);
this.checkOwnershipAvailableID = null;
}
if (this.state == TOKEN_STATE_OWNED) {
this.onLostOwnership(this);
}
if (newState == TOKEN_STATE_UNOWNED) {
this.checkOwnershipAvailableID = Script.setInterval(
this.tryRequestingOwnership.bind(this), this.checkEverySeconds);
} else if (newState == TOKEN_STATE_REQUESTING_OWNERSHIP) {
} else if (newState == TOKEN_STATE_OWNED) {
this.onGainedOwnership(this);
this.updateLifetimeID = Script.setInterval(
this.updateTokenLifetime.bind(this), this.updateTokenLifetimeEvery);
} else if (newState == TOKEN_STATE_DESTROYED) {
Entities.deleteEntity(this.ownershipTokenID);
}
debug(this.name, "Info: Switching to state:", newState);
this.state = newState;
},
updateTokenLifetime: function() {
if (this.state != TOKEN_STATE_OWNED) {
debug(this.name, "Error: Trying to update token while it is unowned");
return;
}
debug(this.name, "Updating entity lifetime");
var age = Entities.getEntityProperties(this.ownershipTokenID, 'age').age;
Entities.editEntity(this.ownershipTokenID, {
lifetime: age + 5
});
},
tryRequestingOwnership: function() {
if (this.state == TOKEN_STATE_REQUESTING_OWNERSHIP || this.state == TOKEN_STATE_OWNED) {
debug(this.name, "We already have or are requesting ownership");
return;
}
var ownerID = getOwnershipTokenID(this.parentEntityID);
if (ownerID !== null) {
// Already owned, return
debug(this.name, "Token already owned by another client, return");
return;
}
this.ownershipTokenID = createOwnershipToken(this.name, this.parentEntityID);
this.setState(TOKEN_STATE_REQUESTING_OWNERSHIP);
function checkOwnershipRequest() {
var ownerID = getOwnershipTokenID(this.parentEntityID);
if (ownerID == this.ownershipTokenID) {
debug(this.name, "Info: Obtained ownership");
this.setState(TOKEN_STATE_OWNED);
} else {
if (ownerID === null) {
debug(this.name, "Warning: Checked ownership request and no tokens existed");
}
debug(this.name, "Info: Lost ownership request")
this.ownershipTokenID = null;
this.setState(TOKEN_STATE_UNOWNED);
}
}
Script.setTimeout(checkOwnershipRequest.bind(this), 2000);
},
};

81
tutorial/spinner.js Normal file
View file

@ -0,0 +1,81 @@
//
// spinner.js
//
// Created by Ryan Huffman on 9/1/16.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
var DEBUG = false;
function debug() {
if (DEBUG) {
print.apply(self, arguments);
}
}
var spinnerSound = SoundCache.getSound("atp:/tutorial_sounds/Pinwheel.L.wav");
var Spinner = function() {
};
function getChildProperties(entityID, propertyNames) {
var childEntityIDs = Entities.getChildrenIDs(entityID);
var results = {}
for (var i = 0; i < childEntityIDs.length; ++i) {
var childEntityID = childEntityIDs[i];
var properties = Entities.getEntityProperties(childEntityID, propertyNames);
results[childEntityID] = properties;
}
return results;
}
Spinner.prototype = {
onLit: function() {
debug("LIT SPINNER", this.entityID);
Entities.editEntity(this.entityID, {
"angularDamping": 0.1,
"angularVelocity": {
"x": 20.471975326538086,
"y": 0,
"z": 0
},
});
var injector = Audio.playSound(spinnerSound, {
position: Entities.getEntityProperties(this.entityID, 'position').position,
volume: 1.0,
loop: false
});
var childrenProps = getChildProperties(this.entityID, ['type']);
for (var childEntityID in childrenProps) {
var props = childrenProps[childEntityID];
if (props.type == "ParticleEffect") {
Entities.editEntity(childEntityID, {
emitRate: 35,
});
}
}
Messages.sendLocalMessage("Tutorial-Spinner", "wasLit");
var self = this;
Script.setTimeout(function() {
debug("BLOW UP");
injector.stop();
var childrenProps = getChildProperties(self.entityID, ['type']);
for (var childEntityID in childrenProps) {
var props = childrenProps[childEntityID];
if (props.type == "ParticleEffect") {
Entities.editEntity(childEntityID, {
emitRate: 0,
});
}
}
}, 4900);
},
preload: function(entityID) {
this.entityID = entityID;
},
};
return new Spinner();
});

BIN
tutorial/success.wav Normal file

Binary file not shown.

BIN
tutorial/success48.wav Normal file

Binary file not shown.

1077
tutorial/tutorial.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,151 @@
TUTORIAL_TAG_TO_ENTITY_IDS_MAP = {
"teleport": {
"{ff064b9e-7fa4-4693-a386-a67b9f92a948}": {
"tag": "teleport"
},
"{4478f7b5-d3ac-4213-9a7b-ad8cd69575b8}": {
"tag": "teleport"
}
},
"finish": {
"{340e05b5-88df-4b2b-b43c-756dd714d6d8}": {
"tag": "finish"
}
},
"door": {
"{9c5b0fee-e695-4516-94cd-153371e3857b}": {
"tag": "door"
}
},
"farGrab": {
"{70fcd96c-cd59-4f23-9ca5-a167f2f85680}": {
"visible": false,
"tag": "farGrab"
},
"{ff7b9793-0d94-4f18-bc09-4ab589126e60}": {
"tag": "farGrab"
},
"{fdd77d2c-af36-41c1-ba57-74b7ae79d996}": {
"tag": "farGrab"
},
"{e11700f6-bc9a-411f-9ddc-bf265d4e3ccf}": {
"tag": "farGrab"
},
"{95850c56-cd1c-42b9-ab6b-a163a6f2878f}": {
"tag": "farGrab"
}
},
"nearGrab": {
"{55c861ef-60ca-4722-a6c5-9c6967966ec5}": {
"tag": "nearGrab"
},
"{644d655b-ae66-43b1-9bab-a44b9a8ad632}": {
"tag": "nearGrab"
},
"{88221a22-b710-4d35-852b-5257b0aa77dc}": {
"tag": "nearGrab"
},
"{8bf0baa1-88d0-448a-a782-100d4413bd82}": {
"tag": "nearGrab"
},
"{5cf22b9c-fb22-4854-8821-554422980b24}": {
"visible": false,
"tag": "nearGrab"
}
},
"equip-part1": {
"{d73822ca-0a34-4cf4-a530-3258ac459a14}": {
"tag": "equip-part1"
},
"{97ced5e7-fc81-40f9-a9e8-f85b4b30f24c}": {
"tag": "equip-part1"
},
"{8572d991-5777-45df-97bf-7243d7b12f81}": {
"tag": "equip-part1"
},
"{da5ea72e-54b6-41ac-b711-742b062b6968}": {
"tag": "equip-part1"
},
"{c8944a13-9acb-4d77-b1ee-851845e98357}": {
"tag": "equip-part1"
},
"{e9481c78-1a21-43f7-b54c-58f2efdf3c8f}": {
"tag": "equip-part1"
},
"{ca3c28f3-15fc-4349-a85e-eaca0fad6434}": {
"tag": "equip-part1"
},
"{09ddcb94-52a7-4f50-a5a2-db9db28fc519}": {
"tag": "equip-part1"
},
"{dd13fcd5-616f-4749-ab28-2e1e8bc512e9}": {
"tag": "equip-part1"
}
},
"equip-part2": {
"{8b92eec5-aeed-4368-bce0-432cc9ad4c51}": {
"tag": "equip-part2"
},
"{6307cd16-dd1d-4988-a339-578178436b45}": {
"tag": "equip-part2"
}
},
"turnAround": {
"{ce74b3ca-d1c7-4980-bd98-2d488095a39e}": {
"tag": "turnAround"
}
},
"bothGrab": {
"{14792a6e-dc6f-4e7a-843f-4b109b06b5a4}": {
"visible": false,
"tag": "bothGrab",
"collidable": true
},
"{215dcd14-88fc-4604-9033-cbd2a660178a}": {
"tag": "bothGrab"
},
"{fbc2e40d-0633-45ac-b1c9-97fc8465f93b}": {
"tag": "bothGrab"
},
"{6752dad6-109d-4dc5-aef7-dc8509468cf4}": {
"tag": "bothGrab"
},
"{178e2c71-dff5-4231-8d28-df47fddf4709}": {
"soundKey": {
"playbackGapRange": 0,
"url": "atp:/sounds/crackling_fire.L.wav",
"volume": 0.5,
"playbackGap": 5,
"playing": false,
"loop": true
},
"tag": "bothGrab"
},
"{52445ac5-8730-4457-827e-6c076d2c609c}": {
"tag": "bothGrab"
}
},
"raiseHands": {
"{7139e45d-25cf-470b-b133-c0fda0099d2b}": {
"tag": "raiseHands"
}
},
"equip": {
"{e7897c9c-f4fa-4989-a383-28af56c2e544}": {
"visible": false,
"tag": "equip"
},
"{9df518da-9e65-4b76-8a79-eeefdb0b7310}": {
"visible": false,
"tag": "equip"
},
"{1a77c20e-5d9b-4b54-bf20-1416141a7ca8}": {
"tag": "equip"
}
},
"orient": {
"{95d233ab-ed0a-46e1-b047-1c542688ef3f}": {
"tag": "orient"
}
}
}

View file

@ -0,0 +1,45 @@
(function() {
var TutorialStartZone = function() {
print("TutorialStartZone | Creating");
};
TutorialStartZone.prototype = {
preload: function(entityID) {
print("TutorialStartZone | Preload");
this.entityID = entityID;
this.sendStartIntervalID = null;
},
enterEntity: function() {
var self = this;
// send message to outer zone
print("TutorialStartZone | Entered the tutorial start area");
if (HMD.isHMDAvailable() && HMD.isHandControllerAvailable()) {
function sendStart() {
print("TutorialStartZone | Checking parent ID");
var parentID = Entities.getEntityProperties(self.entityID, 'parentID').parentID;
print("TutorialStartZone | Parent ID is: ", parentID);
if (parentID) {
print("TutorialStartZone | Sending start");
Entities.callEntityMethod(parentID, 'start');
} else {
print("TutorialStartZone | ERROR: No parent id found on tutorial start zone");
}
}
this.sendStartIntervalID = Script.setInterval(sendStart, 1500);
sendStart();
} else {
print("TutorialStartZone | User tried to go to tutorial with HMD and hand controllers, sending back to /");
Window.alert("To proceed with this tutorial, please connect your VR headset and hand controllers.");
location = "/";
}
},
leaveEntity: function() {
print("TutorialStartZone | Exited the tutorial start area");
if (this.sendStartIntervalID) {
Script.clearInterval(this.sendStartIntervalID);
}
}
};
return new TutorialStartZone();
});

109
tutorial/tutorialZone.js Normal file
View file

@ -0,0 +1,109 @@
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,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
}
(function() {
var ownershipTokenPath = Script.resolvePath("ownershipToken.js");
var tutorialPath = Script.resolvePath("tutorial.js");
Script.include(ownershipTokenPath);
Script.include(tutorialPath);
var TutorialZone = function() {
print("TutorialZone | Creating");
this.token = null;
};
TutorialZone.prototype = {
keyReleaseHandler: function(event) {
print(event.text);
if (event.isShifted && event.isAlt) {
if (event.text == ",") {
if (!this.tutorialManager.startNextStep()) {
this.tutorialManager.startTutorial();
}
} else if (event.text == "F11") {
this.tutorialManager.restartStep();
} else if (event.text == "F10") {
MyAvatar.shouldRenderLocally = !MyAvatar.shouldRenderLocally;
} else if (event.text == "r") {
this.tutorialManager.stopTutorial();
this.tutorialManager.startTutorial();
}
}
},
preload: function(entityID) {
print("TutorialZone | Preload");
this.entityID = entityID;
},
start: function() {
print("TutorialZone | Got start");
var self = this;
if (!this.token) {
print("TutorialZone | Creating token");
this.token = new OwnershipToken(Math.random() * 100000, this.entityID, {
onGainedOwnership: function(token) {
print("TutorialZone | GOT OWNERSHIP");
if (!self.tutorialManager) {
self.tutorialManager = new TutorialManager();
}
self.tutorialManager.startTutorial();
print("TutorialZone | making bound release handler");
self.keyReleaseHandlerBound = self.keyReleaseHandler.bind(self);
print("TutorialZone | binding");
Controller.keyReleaseEvent.connect(self.keyReleaseHandlerBound);
print("TutorialZone | done");
},
onLostOwnership: function(token) {
print("TutorialZone | LOST OWNERSHIP");
if (self.tutorialManager) {
print("TutorialZone | stopping tutorial..");
self.tutorialManager.stopTutorial();
print("TutorialZone | done");
Controller.keyReleaseEvent.disconnect(self.keyReleaseHandlerBound);
} else {
print("TutorialZone | no tutorial manager...");
}
}
});
}
},
enterEntity: function() {
print("TutorialZone | ENTERED THE TUTORIAL AREA");
},
leaveEntity: function() {
print("TutorialZone | EXITED THE TUTORIAL AREA");
if (this.token) {
print("TutorialZone | Destroying token");
this.token.destroy();
this.token = null;
}
}
};
return new TutorialZone();
});

View file

@ -0,0 +1,281 @@
var LEFT_JOINT_INDEX = MyAvatar.getJointIndex("_CONTROLLER_LEFTHAND");
var RIGHT_JOINT_INDEX = MyAvatar.getJointIndex("_CONTROLLER_RIGHTHAND");
var leftBaseRotation = Quat.multiply(
Quat.fromPitchYawRollDegrees(0, 0, 45),
Quat.multiply(
Quat.fromPitchYawRollDegrees(90, 0, 0),
Quat.fromPitchYawRollDegrees(0, 0, 90)
)
);
var rightBaseRotation = Quat.multiply(
Quat.fromPitchYawRollDegrees(0, 0, -45),
Quat.multiply(
Quat.fromPitchYawRollDegrees(90, 0, 0),
Quat.fromPitchYawRollDegrees(0, 0, -90)
)
);
var CONTROLLER_LENGTH_OFFSET = 0.0762;
var leftBasePosition = {
x: CONTROLLER_LENGTH_OFFSET / 2,
y: CONTROLLER_LENGTH_OFFSET * 2,
z: CONTROLLER_LENGTH_OFFSET / 2
};
var rightBasePosition = {
x: -CONTROLLER_LENGTH_OFFSET / 2,
y: CONTROLLER_LENGTH_OFFSET * 2,
z: CONTROLLER_LENGTH_OFFSET / 2
};
var viveNaturalDimensions = {
x: 0.1174320001155138,
y: 0.08361100335605443,
z: 0.21942697931081057
};
var viveNaturalPosition = {
x: 0,
y: -0.034076502197422087,
z: 0.06380049744620919
};
var viveModelURL = "atp:/controller/vive_body.fbx";
var viveTipsModelURL = "atp:/controller/vive_tips.fbx"
VIVE_CONTROLLER_CONFIGURATION_LEFT = {
name: "Vive",
controllers: [
{
modelURL: viveModelURL,
jointIndex: MyAvatar.getJointIndex("_CONTROLLER_LEFTHAND"),
naturalPosition: viveNaturalPosition,
rotation: leftBaseRotation,
position: Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, 0, 45), leftBasePosition),
dimensions: viveNaturalDimensions,
parts: {
tips: {
type: "static",
modelURL: viveTipsModelURL,
naturalPosition: {"x":-0.004377640783786774,"y":-0.034371938556432724,"z":0.06769277155399323},
textureName: "Tex.Blank",
defaultTextureLayer: "blank",
textureLayers: {
blank: {
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Blank.png",
},
trigger: {
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Trigger.png",
},
arrows: {
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Rotate.png",
},
grip: {
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Grip.png",
},
teleport: {
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Teleport.png",
},
}
},
// The touchpad type draws a dot indicating the current touch/thumb position
// and swaps in textures based on the thumb position.
touchpad: {
type: "touchpad",
modelURL: "atp:/controller/vive_trackpad.fbx",
visibleInput: "Vive.RSTouch",
xInput: "Vive.LX",
yInput: "Vive.LY",
naturalPosition: {"x":0,"y":0.000979491975158453,"z":0.04872849956154823},
minValue: 0.0,
maxValue: 1.0,
minPosition: { x: -0.035, y: 0.004, z: -0.005 },
maxPosition: { x: -0.035, y: 0.004, z: -0.005 },
disable_textureName: "Tex.touchpad-blank",
disable_defaultTextureLayer: "blank",
disable_textureLayers: {
blank: {
defaultTextureURL: "atp:/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-blank.jpg",
},
teleport: {
defaultTextureURL: "atp:/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-teleport-active-LG.jpg",
},
arrows: {
defaultTextureURL: "atp:/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-look-arrows.jpg",
}
}
},
trigger: {
type: "rotational",
modelURL: "atp:/controller/vive_trigger.fbx",
input: Controller.Standard.LT,
naturalPosition: {"x":0.000004500150680541992,"y":-0.027690507471561432,"z":0.04830199480056763},
origin: { x: 0, y: -0.015, z: -0.00 },
minValue: 0.0,
maxValue: 1.0,
axis: { x: -1, y: 0, z: 0 },
maxAngle: 20,
},
l_grip: {
type: "static",
modelURL: "atp:/controller/vive_l_grip.fbx",
naturalPosition: {"x":-0.01720449887216091,"y":-0.014324013143777847,"z":0.08714400231838226},
},
r_grip: {
type: "static",
modelURL: "atp:/controller/vive_r_grip.fbx",
naturalPosition: {"x":0.01720449887216091,"y":-0.014324013143777847,"z":0.08714400231838226},
},
sys_button: {
type: "static",
modelURL: "atp:/controller/vive_sys_button.fbx",
naturalPosition: {"x":0,"y":0.0020399854984134436,"z":0.08825899660587311},
},
button: {
type: "static",
modelURL: "atp:/controller/vive_button.fbx",
naturalPosition: {"x":0,"y":0.005480996798723936,"z":0.019918499514460564}
},
button2: {
type: "static",
modelURL: "atp:/controller/vive_button.fbx",
naturalPosition: {"x":0,"y":0.005480996798723936,"z":0.019918499514460564}
},
},
},
]
};
VIVE_CONTROLLER_CONFIGURATION_RIGHT = {
name: "Vive Right",
controllers: [
{
modelURL: viveModelURL,
jointIndex: MyAvatar.getJointIndex("_CONTROLLER_RIGHTHAND"),
rotation: rightBaseRotation,
position: Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, 0, -45), rightBasePosition),
dimensions: viveNaturalDimensions,
naturalPosition: {
x: 0,
y: -0.034076502197422087,
z: 0.06380049744620919
},
parts: {
tips: {
type: "static",
modelURL: viveTipsModelURL,
naturalPosition: {"x":-0.004377640783786774,"y":-0.034371938556432724,"z":0.06769277155399323},
textureName: "Tex.Blank",
defaultTextureLayer: "blank",
textureLayers: {
blank: {
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Blank.png",
},
trigger: {
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Trigger.png",
},
arrows: {
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Rotate.png",
},
grip: {
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Grip.png",
},
teleport: {
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Teleport.png",
},
}
},
// The touchpad type draws a dot indicating the current touch/thumb position
// and swaps in textures based on the thumb position.
touchpad: {
type: "touchpad",
modelURL: "atp:/controller/vive_trackpad.fbx",
visibleInput: "Vive.RSTouch",
xInput: "Vive.RX",
yInput: "Vive.RY",
naturalPosition: { x: 0, y: 0.000979491975158453, z: 0.04872849956154823 },
minValue: 0.0,
maxValue: 1.0,
minPosition: { x: -0.035, y: 0.004, z: -0.005 },
maxPosition: { x: -0.035, y: 0.004, z: -0.005 },
disable_textureName: "Tex.touchpad-blank",
disable_defaultTextureLayer: "blank",
disable_textureLayers: {
blank: {
defaultTextureURL: "atp:/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-blank.jpg",
},
teleport: {
defaultTextureURL: "atp:/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-teleport-active-LG.jpg",
},
arrows: {
defaultTextureURL: "atp:/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-look-arrows-active.jpg",
}
}
},
trigger: {
type: "rotational",
modelURL: "atp:/controller/vive_trigger.fbx",
input: Controller.Standard.RT,
naturalPosition: {"x":0.000004500150680541992,"y":-0.027690507471561432,"z":0.04830199480056763},
origin: { x: 0, y: -0.015, z: -0.00 },
minValue: 0.0,
maxValue: 1.0,
axis: { x: -1, y: 0, z: 0 },
maxAngle: 25,
},
l_grip: {
type: "static",
modelURL: "atp:/controller/vive_l_grip.fbx",
naturalPosition: {"x":-0.01720449887216091,"y":-0.014324013143777847,"z":0.08714400231838226},
},
r_grip: {
type: "static",
modelURL: "atp:/controller/vive_r_grip.fbx",
naturalPosition: {"x":0.01720449887216091,"y":-0.014324013143777847,"z":0.08714400231838226},
},
sys_button: {
type: "static",
modelURL: "atp:/controller/vive_sys_button.fbx",
naturalPosition: {"x":0,"y":0.0020399854984134436,"z":0.08825899660587311},
},
button: {
type: "static",
modelURL: "atp:/controller/vive_button.fbx",
naturalPosition: {"x":0,"y":0.005480996798723936,"z":0.019918499514460564}
},
button2: {
type: "static",
modelURL: "atp:/controller/vive_button.fbx",
naturalPosition: {"x":0,"y":0.005480996798723936,"z":0.019918499514460564}
},
},
}
]
};

194
tutorial/viveHandsv2.js Normal file
View file

@ -0,0 +1,194 @@
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,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
}
Script.setTimeout(function() { print('timeout') }, 100);
Script.include("controllerDisplay.js");
Script.include("viveControllerConfiguration.js");
function debug() {
var args = Array.prototype.slice.call(arguments);
args.unshift("CONTROLLER DEBUG:");
print.apply(this, args);
}
var zeroPosition = { x: 0, y: 0, z: 0 };
var zeroRotation = { x: 0, y: 0, z: 0, w: 1 };
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Management of controller display //
///////////////////////////////////////////////////////////////////////////////
ControllerDisplayManager = function() {
var self = this;
var controllerLeft = null;
var controllerRight = null;
var controllerCheckerIntervalID = null;
this.setLeftVisible = function(visible) {
print("settings controller display to visible");
if (controllerLeft) {
print("doign it...", visible);
controllerLeft.setVisible(visible);
}
};
this.setRightVisible = function(visible) {
print("settings controller display to visible");
if (controllerRight) {
print("doign it...", visible);
controllerRight.setVisible(visible);
}
};
function updateControllers() {
if (HMD.active) {
if ("Vive" in Controller.Hardware) {
if (!controllerLeft) {
debug("Found vive left!");
controllerLeft = createControllerDisplay(VIVE_CONTROLLER_CONFIGURATION_LEFT);
}
if (!controllerRight) {
debug("Found vive right!");
controllerRight = createControllerDisplay(VIVE_CONTROLLER_CONFIGURATION_RIGHT);
}
// We've found the controllers, we no longer need to look for active controllers
if (controllerCheckerIntervalID) {
Script.clearInterval(controllerCheckerIntervalID);
controllerCheckerIntervalID = null;
}
} else {
debug("HMD active, but no controllers found");
self.deleteControllerDisplays();
if (controllerCheckerIntervalID == null) {
controllerCheckerIntervalID = Script.setInterval(updateControllers, 1000);
}
}
} else {
debug("HMD inactive");
// We aren't in HMD mode, we no longer need to look for active controllers
if (controllerCheckerIntervalID) {
debug("Clearing controller checker interval");
Script.clearInterval(controllerCheckerIntervalID);
controllerCheckerIntervalID = null;
}
self.deleteControllerDisplays();
}
}
Messages.subscribe('Controller-Display');
var handleMessages = function(channel, message, sender) {
if (!controllerLeft && !controllerRight) {
return;
}
if (sender === MyAvatar.sessionUUID) {
if (channel === 'Controller-Display') {
var data = JSON.parse(message);
var name = data.name;
var visible = data.visible;
//c.setDisplayAnnotation(name, visible);
if (controllerLeft) {
if (name in controllerLeft.annotations) {
debug("hiding");
for (var i = 0; i < controllerLeft.annotations[name].length; ++i) {
debug("hiding", i);
Overlays.editOverlay(controllerLeft.annotations[name][i], { visible: visible });
}
}
}
if (controllerRight) {
if (name in controllerRight.annotations) {
debug("hiding");
for (var i = 0; i < controllerRight.annotations[name].length; ++i) {
debug("hiding", i);
Overlays.editOverlay(controllerRight.annotations[name][i], { visible: visible });
}
}
}
} else if (channel === 'Controller-Display-Parts') {
debug('here part');
var data = JSON.parse(message);
for (var name in data) {
var visible = data[name];
if (controllerLeft) {
controllerLeft.setPartVisible(name, visible);
}
if (controllerRight) {
controllerRight.setPartVisible(name, visible);
}
}
} else if (channel === 'Controller-Set-Part-Layer') {
var data = JSON.parse(message);
for (var name in data) {
var layer = data[name];
if (controllerLeft) {
controllerLeft.setLayerForPart(name, layer);
}
if (controllerRight) {
controllerRight.setLayerForPart(name, layer);
}
}
} else if (channel == 'Hifi-Object-Manipulation') {// && sender == MyAvatar.sessionUUID) {
//print("got manip");
var data = JSON.parse(message);
//print("post data", data);
var visible = data.action != 'equip';
//print("Calling...");
if (data.joint == "LeftHand") {
self.setLeftVisible(visible);
} else if (data.joint == "RightHand") {
self.setRightVisible(visible);
}
}
}
}
Messages.messageReceived.connect(handleMessages);
this.deleteControllerDisplays = function() {
if (controllerLeft) {
deleteControllerDisplay(controllerLeft);
controllerLeft = null;
}
if (controllerRight) {
deleteControllerDisplay(controllerRight);
controllerRight = null;
}
};
this.destroy = function() {
print("Destroying controller display");
Messages.messageReceived.disconnect(handleMessages);
self.deleteControllerDisplays();
};
HMD.displayModeChanged.connect(updateControllers);
updateControllers();
}

View file

@ -78,6 +78,9 @@
_this.explodeWithColor();
_this.smokePuff();
Entities.deleteEntity(otherID)
Messages.sendMessage('Entity-Exploded', JSON.stringify({
entityID: otherID,
}));
}
}
}
@ -162,4 +165,4 @@
}
return new Fire();
});
});