Merge branch 'master' of github.com:highfidelity/hifi into possible-fix-for-2068
|
@ -474,24 +474,18 @@ void Agent::processAgentAvatar() {
|
|||
nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer);
|
||||
}
|
||||
}
|
||||
void Agent::flushEncoder() {
|
||||
void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) {
|
||||
_flushEncoder = false;
|
||||
static QByteArray zeros(AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL, 0);
|
||||
static QByteArray encodedZeros;
|
||||
static const QByteArray zeros(AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL, 0);
|
||||
if (_encoder) {
|
||||
_encoder->encode(zeros, encodedZeros);
|
||||
} else {
|
||||
encodedZeros = zeros;
|
||||
}
|
||||
}
|
||||
|
||||
void Agent::processAgentAvatarAudio() {
|
||||
if (_isAvatar && (_isListeningToAudioStream || _avatarSound)) {
|
||||
// after sound is done playing, encoder has a bit of state in it,
|
||||
// and needs some 0s to forget or you get a little click next time
|
||||
// you play something
|
||||
if (_flushEncoder) {
|
||||
flushEncoder();
|
||||
}
|
||||
|
||||
// if we have an avatar audio stream then send it out to our audio-mixer
|
||||
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
|
||||
bool silentFrame = true;
|
||||
|
@ -528,7 +522,7 @@ void Agent::processAgentAvatarAudio() {
|
|||
}
|
||||
}
|
||||
|
||||
auto audioPacket = NLPacket::create(silentFrame
|
||||
auto audioPacket = NLPacket::create(silentFrame && !_flushEncoder
|
||||
? PacketType::SilentAudioFrame
|
||||
: PacketType::MicrophoneAudioNoEcho);
|
||||
|
||||
|
@ -564,13 +558,17 @@ void Agent::processAgentAvatarAudio() {
|
|||
glm::quat headOrientation = scriptedAvatar->getHeadOrientation();
|
||||
audioPacket->writePrimitive(headOrientation);
|
||||
|
||||
QByteArray decodedBuffer(reinterpret_cast<const char*>(nextSoundOutput), numAvailableSamples*sizeof(int16_t));
|
||||
QByteArray encodedBuffer;
|
||||
// encode it
|
||||
if(_encoder) {
|
||||
_encoder->encode(decodedBuffer, encodedBuffer);
|
||||
if (_flushEncoder) {
|
||||
encodeFrameOfZeros(encodedBuffer);
|
||||
} else {
|
||||
encodedBuffer = decodedBuffer;
|
||||
QByteArray decodedBuffer(reinterpret_cast<const char*>(nextSoundOutput), numAvailableSamples*sizeof(int16_t));
|
||||
if (_encoder) {
|
||||
// encode it
|
||||
_encoder->encode(decodedBuffer, encodedBuffer);
|
||||
} else {
|
||||
encodedBuffer = decodedBuffer;
|
||||
}
|
||||
}
|
||||
audioPacket->write(encodedBuffer.constData(), encodedBuffer.size());
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ signals:
|
|||
private:
|
||||
void negotiateAudioFormat();
|
||||
void selectAudioFormat(const QString& selectedCodecName);
|
||||
void flushEncoder();
|
||||
void encodeFrameOfZeros(QByteArray& encodedZeros);
|
||||
|
||||
std::unique_ptr<ScriptEngine> _scriptEngine;
|
||||
EntityEditPacketSender _entityEditSender;
|
||||
|
|
|
@ -15,19 +15,23 @@
|
|||
// this should send a signal every 10ms, with pretty good precision. Hardcoding
|
||||
// to 10ms since that's what you'd want for audio.
|
||||
void AvatarAudioTimer::start() {
|
||||
qDebug() << "AvatarAudioTimer::start called";
|
||||
qDebug() << __FUNCTION__;
|
||||
auto startTime = usecTimestampNow();
|
||||
quint64 frameCounter = 0;
|
||||
const int TARGET_INTERVAL_USEC = 10000; // 10ms
|
||||
while (!_quit) {
|
||||
frameCounter++;
|
||||
// simplest possible timer
|
||||
++frameCounter;
|
||||
|
||||
// tick every 10ms from startTime
|
||||
quint64 targetTime = startTime + frameCounter * TARGET_INTERVAL_USEC;
|
||||
quint64 interval = std::max((quint64)0, targetTime - usecTimestampNow());
|
||||
usleep(interval);
|
||||
quint64 now = usecTimestampNow();
|
||||
|
||||
// avoid quint64 underflow
|
||||
if (now < targetTime) {
|
||||
usleep(targetTime - now);
|
||||
}
|
||||
|
||||
emit avatarTick();
|
||||
}
|
||||
qDebug() << "AvatarAudioTimer is finished";
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -90,8 +90,8 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
|
|||
PacketType::InjectAudio, PacketType::SilentAudioFrame,
|
||||
PacketType::AudioStreamStats },
|
||||
this, "handleNodeAudioPacket");
|
||||
packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket");
|
||||
packetReceiver.registerListener(PacketType::NegotiateAudioFormat, this, "handleNegotiateAudioFormat");
|
||||
packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket");
|
||||
packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket");
|
||||
|
||||
connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled);
|
||||
|
@ -481,6 +481,7 @@ void AudioMixer::sendAudioEnvironmentPacket(SharedNodePointer node) {
|
|||
}
|
||||
|
||||
void AudioMixer::handleNodeAudioPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
getOrCreateClientData(sendingNode.data());
|
||||
DependencyManager::get<NodeList>()->updateNodeWithDataFromPacket(message, sendingNode);
|
||||
}
|
||||
|
||||
|
@ -579,18 +580,8 @@ void AudioMixer::handleNegotiateAudioFormat(QSharedPointer<ReceivedMessage> mess
|
|||
}
|
||||
}
|
||||
|
||||
auto clientData = dynamic_cast<AudioMixerClientData*>(sendingNode->getLinkedData());
|
||||
|
||||
// FIXME - why would we not have client data at this point??
|
||||
if (!clientData) {
|
||||
qDebug() << "UNEXPECTED -- didn't have node linked data in " << __FUNCTION__;
|
||||
sendingNode->setLinkedData(std::unique_ptr<NodeData> { new AudioMixerClientData(sendingNode->getUUID()) });
|
||||
clientData = dynamic_cast<AudioMixerClientData*>(sendingNode->getLinkedData());
|
||||
connect(clientData, &AudioMixerClientData::injectorStreamFinished, this, &AudioMixer::removeHRTFsForFinishedInjector);
|
||||
}
|
||||
|
||||
auto clientData = getOrCreateClientData(sendingNode.data());
|
||||
clientData->setupCodec(selectedCodec, selectedCodecName);
|
||||
|
||||
qDebug() << "selectedCodecName:" << selectedCodecName;
|
||||
clientData->sendSelectAudioFormat(sendingNode, selectedCodecName);
|
||||
}
|
||||
|
@ -646,7 +637,8 @@ void AudioMixer::sendStatsPacket() {
|
|||
statsObject["trailing_sleep_percentage"] = _trailingSleepRatio * 100.0f;
|
||||
statsObject["performance_throttling_ratio"] = _performanceThrottlingRatio;
|
||||
|
||||
statsObject["avg_listeners_per_frame"] = (float) _sumListeners / (float) _numStatFrames;
|
||||
statsObject["avg_streams_per_frame"] = (float)_sumStreams / (float)_numStatFrames;
|
||||
statsObject["avg_listeners_per_frame"] = (float)_sumListeners / (float)_numStatFrames;
|
||||
|
||||
QJsonObject mixStats;
|
||||
mixStats["%_hrtf_mixes"] = percentageForMixStats(_hrtfRenders);
|
||||
|
@ -660,6 +652,7 @@ void AudioMixer::sendStatsPacket() {
|
|||
|
||||
statsObject["mix_stats"] = mixStats;
|
||||
|
||||
_sumStreams = 0;
|
||||
_sumListeners = 0;
|
||||
_hrtfRenders = 0;
|
||||
_hrtfSilentRenders = 0;
|
||||
|
@ -707,17 +700,24 @@ void AudioMixer::run() {
|
|||
ThreadedAssignment::commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NodeType::AudioMixer);
|
||||
}
|
||||
|
||||
AudioMixerClientData* AudioMixer::getOrCreateClientData(Node* node) {
|
||||
auto clientData = dynamic_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
|
||||
if (!clientData) {
|
||||
node->setLinkedData(std::unique_ptr<NodeData> { new AudioMixerClientData(node->getUUID()) });
|
||||
clientData = dynamic_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
connect(clientData, &AudioMixerClientData::injectorStreamFinished, this, &AudioMixer::removeHRTFsForFinishedInjector);
|
||||
}
|
||||
|
||||
return clientData;
|
||||
}
|
||||
|
||||
void AudioMixer::domainSettingsRequestComplete() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
|
||||
|
||||
nodeList->linkedDataCreateCallback = [&](Node* node) {
|
||||
node->setLinkedData(std::unique_ptr<NodeData> { new AudioMixerClientData(node->getUUID()) });
|
||||
auto clientData = dynamic_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
|
||||
connect(clientData, &AudioMixerClientData::injectorStreamFinished, this, &AudioMixer::removeHRTFsForFinishedInjector);
|
||||
};
|
||||
nodeList->linkedDataCreateCallback = [&](Node* node) { getOrCreateClientData(node); };
|
||||
|
||||
DomainHandler& domainHandler = nodeList->getDomainHandler();
|
||||
const QJsonObject& settingsObject = domainHandler.getSettingsObject();
|
||||
|
@ -730,79 +730,71 @@ void AudioMixer::domainSettingsRequestComplete() {
|
|||
}
|
||||
|
||||
void AudioMixer::broadcastMixes() {
|
||||
const int TRAILING_AVERAGE_FRAMES = 100;
|
||||
const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES;
|
||||
const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO;
|
||||
|
||||
const float STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.10f;
|
||||
const float BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.20f;
|
||||
|
||||
const float RATIO_BACK_OFF = 0.02f;
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
auto nextFrameTimestamp = p_high_resolution_clock::now();
|
||||
auto timeToSleep = std::chrono::microseconds(0);
|
||||
|
||||
const int TRAILING_AVERAGE_FRAMES = 100;
|
||||
int currentFrame = 1;
|
||||
int numFramesPerSecond = (int) ceil(AudioConstants::NETWORK_FRAMES_PER_SEC);
|
||||
int framesSinceCutoffEvent = TRAILING_AVERAGE_FRAMES;
|
||||
|
||||
int currentFrame { 1 };
|
||||
int numFramesPerSecond { (int) ceil(AudioConstants::NETWORK_FRAMES_PER_SEC) };
|
||||
|
||||
while (!_isFinished) {
|
||||
const float STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.10f;
|
||||
const float BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.20f;
|
||||
// manage mixer load
|
||||
{
|
||||
_trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio) +
|
||||
// ratio of frame spent sleeping / total frame time
|
||||
((CURRENT_FRAME_RATIO * timeToSleep.count()) / (float) AudioConstants::NETWORK_FRAME_USECS);
|
||||
|
||||
const float RATIO_BACK_OFF = 0.02f;
|
||||
bool hasRatioChanged = false;
|
||||
|
||||
const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES;
|
||||
const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO;
|
||||
|
||||
if (timeToSleep.count() < 0) {
|
||||
timeToSleep = std::chrono::microseconds(0);
|
||||
}
|
||||
|
||||
_trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio)
|
||||
+ (timeToSleep.count() * CURRENT_FRAME_RATIO / (float) AudioConstants::NETWORK_FRAME_USECS);
|
||||
|
||||
float lastCutoffRatio = _performanceThrottlingRatio;
|
||||
bool hasRatioChanged = false;
|
||||
|
||||
if (framesSinceCutoffEvent >= TRAILING_AVERAGE_FRAMES) {
|
||||
if (_trailingSleepRatio <= STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD) {
|
||||
// we're struggling - change our min required loudness to reduce some load
|
||||
_performanceThrottlingRatio = _performanceThrottlingRatio + (0.5f * (1.0f - _performanceThrottlingRatio));
|
||||
|
||||
qDebug() << "Mixer is struggling, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was"
|
||||
<< lastCutoffRatio << "and is now" << _performanceThrottlingRatio;
|
||||
hasRatioChanged = true;
|
||||
} else if (_trailingSleepRatio >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && _performanceThrottlingRatio != 0) {
|
||||
// we've recovered and can back off the required loudness
|
||||
_performanceThrottlingRatio = _performanceThrottlingRatio - RATIO_BACK_OFF;
|
||||
|
||||
if (_performanceThrottlingRatio < 0) {
|
||||
_performanceThrottlingRatio = 0;
|
||||
if (framesSinceCutoffEvent >= TRAILING_AVERAGE_FRAMES) {
|
||||
if (_trailingSleepRatio <= STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD) {
|
||||
qDebug() << "Mixer is struggling";
|
||||
// change our min required loudness to reduce some load
|
||||
_performanceThrottlingRatio = _performanceThrottlingRatio + (0.5f * (1.0f - _performanceThrottlingRatio));
|
||||
hasRatioChanged = true;
|
||||
} else if (_trailingSleepRatio >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && _performanceThrottlingRatio != 0) {
|
||||
qDebug() << "Mixer is recovering";
|
||||
// back off the required loudness
|
||||
_performanceThrottlingRatio = std::max(0.0f, _performanceThrottlingRatio - RATIO_BACK_OFF);
|
||||
hasRatioChanged = true;
|
||||
}
|
||||
|
||||
qDebug() << "Mixer is recovering, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was"
|
||||
<< lastCutoffRatio << "and is now" << _performanceThrottlingRatio;
|
||||
hasRatioChanged = true;
|
||||
if (hasRatioChanged) {
|
||||
// set out min audability threshold from the new ratio
|
||||
_minAudibilityThreshold = LOUDNESS_TO_DISTANCE_RATIO / (2.0f * (1.0f - _performanceThrottlingRatio));
|
||||
framesSinceCutoffEvent = 0;
|
||||
|
||||
qDebug() << "Sleeping" << _trailingSleepRatio << "of frame";
|
||||
qDebug() << "Cutoff is" << _performanceThrottlingRatio;
|
||||
qDebug() << "Minimum audibility to be mixed is" << _minAudibilityThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasRatioChanged) {
|
||||
// set out min audability threshold from the new ratio
|
||||
_minAudibilityThreshold = LOUDNESS_TO_DISTANCE_RATIO / (2.0f * (1.0f - _performanceThrottlingRatio));
|
||||
qDebug() << "Minimum audability required to be mixed is now" << _minAudibilityThreshold;
|
||||
|
||||
framesSinceCutoffEvent = 0;
|
||||
if (!hasRatioChanged) {
|
||||
++framesSinceCutoffEvent;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasRatioChanged) {
|
||||
++framesSinceCutoffEvent;
|
||||
}
|
||||
|
||||
// mix
|
||||
nodeList->eachNode([&](const SharedNodePointer& node) {
|
||||
|
||||
if (node->getLinkedData()) {
|
||||
AudioMixerClientData* nodeData = (AudioMixerClientData*)node->getLinkedData();
|
||||
|
||||
// this function will attempt to pop a frame from each audio stream.
|
||||
// a pointer to the popped data is stored as a member in InboundAudioStream.
|
||||
// That's how the popped audio data will be read for mixing (but only if the pop was successful)
|
||||
nodeData->checkBuffersBeforeFrameSend();
|
||||
_sumStreams += nodeData->checkBuffersBeforeFrameSend();
|
||||
|
||||
// if the stream should be muted, send mute packet
|
||||
if (nodeData->getAvatarAudioStream()
|
||||
|
@ -818,7 +810,8 @@ void AudioMixer::broadcastMixes() {
|
|||
|
||||
std::unique_ptr<NLPacket> mixPacket;
|
||||
|
||||
if (mixHasAudio) {
|
||||
if (mixHasAudio || nodeData->shouldFlushEncoder()) {
|
||||
|
||||
int mixPacketBytes = sizeof(quint16) + AudioConstants::MAX_CODEC_NAME_LENGTH_ON_WIRE
|
||||
+ AudioConstants::NETWORK_FRAME_BYTES_STEREO;
|
||||
mixPacket = NLPacket::create(PacketType::MixedAudio, mixPacketBytes);
|
||||
|
@ -831,12 +824,17 @@ void AudioMixer::broadcastMixes() {
|
|||
QString codecInPacket = nodeData->getCodecName();
|
||||
mixPacket->writeString(codecInPacket);
|
||||
|
||||
QByteArray decodedBuffer(reinterpret_cast<char*>(_clampedSamples), AudioConstants::NETWORK_FRAME_BYTES_STEREO);
|
||||
QByteArray encodedBuffer;
|
||||
nodeData->encode(decodedBuffer, encodedBuffer);
|
||||
|
||||
if (mixHasAudio) {
|
||||
QByteArray decodedBuffer(reinterpret_cast<char*>(_clampedSamples), AudioConstants::NETWORK_FRAME_BYTES_STEREO);
|
||||
nodeData->encode(decodedBuffer, encodedBuffer);
|
||||
} else {
|
||||
// time to flush, which resets the shouldFlush until next time we encode something
|
||||
nodeData->encodeFrameOfZeros(encodedBuffer);
|
||||
}
|
||||
// pack mixed audio samples
|
||||
mixPacket->write(encodedBuffer.constData(), encodedBuffer.size());
|
||||
|
||||
} else {
|
||||
int silentPacketBytes = sizeof(quint16) + sizeof(quint16) + AudioConstants::MAX_CODEC_NAME_LENGTH_ON_WIRE;
|
||||
mixPacket = NLPacket::create(PacketType::SilentAudioFrame, silentPacketBytes);
|
||||
|
@ -876,24 +874,32 @@ void AudioMixer::broadcastMixes() {
|
|||
|
||||
++_numStatFrames;
|
||||
|
||||
// since we're a while loop we need to help Qt's event processing
|
||||
QCoreApplication::processEvents();
|
||||
// play nice with qt event-looping
|
||||
{
|
||||
// since we're a while loop we need to help qt's event processing
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
if (_isFinished) {
|
||||
// at this point the audio-mixer is done
|
||||
// check if we have a deferred delete event to process (which we should once finished)
|
||||
QCoreApplication::sendPostedEvents(this, QEvent::DeferredDelete);
|
||||
break;
|
||||
if (_isFinished) {
|
||||
// alert qt that this is finished
|
||||
QCoreApplication::sendPostedEvents(this, QEvent::DeferredDelete);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// push the next frame timestamp to when we should send the next
|
||||
nextFrameTimestamp += std::chrono::microseconds(AudioConstants::NETWORK_FRAME_USECS);
|
||||
// sleep until the next frame, if necessary
|
||||
{
|
||||
nextFrameTimestamp += std::chrono::microseconds(AudioConstants::NETWORK_FRAME_USECS);
|
||||
|
||||
// sleep as long as we need until next frame, if we can
|
||||
auto now = p_high_resolution_clock::now();
|
||||
timeToSleep = std::chrono::duration_cast<std::chrono::microseconds>(nextFrameTimestamp - now);
|
||||
auto now = p_high_resolution_clock::now();
|
||||
timeToSleep = std::chrono::duration_cast<std::chrono::microseconds>(nextFrameTimestamp - now);
|
||||
|
||||
std::this_thread::sleep_for(timeToSleep);
|
||||
if (timeToSleep.count() < 0) {
|
||||
nextFrameTimestamp = now;
|
||||
timeToSleep = std::chrono::microseconds(0);
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(timeToSleep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ private slots:
|
|||
void removeHRTFsForFinishedInjector(const QUuid& streamID);
|
||||
|
||||
private:
|
||||
AudioMixerClientData* getOrCreateClientData(Node* node);
|
||||
void domainSettingsRequestComplete();
|
||||
|
||||
/// adds one stream to the mix for a listening node
|
||||
|
@ -85,6 +86,7 @@ private:
|
|||
float _attenuationPerDoublingInDistance;
|
||||
float _noiseMutingThreshold;
|
||||
int _numStatFrames { 0 };
|
||||
int _sumStreams { 0 };
|
||||
int _sumListeners { 0 };
|
||||
int _hrtfRenders { 0 };
|
||||
int _hrtfSilentRenders { 0 };
|
||||
|
|
|
@ -180,7 +180,7 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
void AudioMixerClientData::checkBuffersBeforeFrameSend() {
|
||||
int AudioMixerClientData::checkBuffersBeforeFrameSend() {
|
||||
QWriteLocker writeLocker { &_streamsLock };
|
||||
|
||||
auto it = _audioStreams.begin();
|
||||
|
@ -208,6 +208,8 @@ void AudioMixerClientData::checkBuffersBeforeFrameSend() {
|
|||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
return (int)_audioStreams.size();
|
||||
}
|
||||
|
||||
bool AudioMixerClientData::shouldSendStats(int frameNumber) {
|
||||
|
@ -355,7 +357,10 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() {
|
|||
}
|
||||
|
||||
void AudioMixerClientData::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) {
|
||||
qDebug() << __FUNCTION__ << "sendingNode:" << *node << "currentCodec:" << currentCodec << "recievedCodec:" << recievedCodec;
|
||||
qDebug() << __FUNCTION__ <<
|
||||
"sendingNode:" << *node <<
|
||||
"currentCodec:" << currentCodec <<
|
||||
"receivedCodec:" << recievedCodec;
|
||||
sendSelectAudioFormat(node, currentCodec);
|
||||
}
|
||||
|
||||
|
@ -366,6 +371,17 @@ void AudioMixerClientData::sendSelectAudioFormat(SharedNodePointer node, const Q
|
|||
nodeList->sendPacket(std::move(replyPacket), *node);
|
||||
}
|
||||
|
||||
void AudioMixerClientData::encodeFrameOfZeros(QByteArray& encodedZeros) {
|
||||
static QByteArray zeros(AudioConstants::NETWORK_FRAME_BYTES_STEREO, 0);
|
||||
if (_shouldFlushEncoder) {
|
||||
if (_encoder) {
|
||||
_encoder->encode(zeros, encodedZeros);
|
||||
} else {
|
||||
encodedZeros = zeros;
|
||||
}
|
||||
}
|
||||
_shouldFlushEncoder = false;
|
||||
}
|
||||
|
||||
void AudioMixerClientData::setupCodec(CodecPluginPointer codec, const QString& codecName) {
|
||||
cleanupCodec(); // cleanup any previously allocated coders first
|
||||
|
|
|
@ -52,7 +52,8 @@ public:
|
|||
|
||||
int parseData(ReceivedMessage& message) override;
|
||||
|
||||
void checkBuffersBeforeFrameSend();
|
||||
// attempt to pop a frame from each audio stream, and return the number of streams from this client
|
||||
int checkBuffersBeforeFrameSend();
|
||||
|
||||
void removeDeadInjectedStreams();
|
||||
|
||||
|
@ -76,7 +77,11 @@ public:
|
|||
} else {
|
||||
encodedBuffer = decodedBuffer;
|
||||
}
|
||||
// once you have encoded, you need to flush eventually.
|
||||
_shouldFlushEncoder = true;
|
||||
}
|
||||
void encodeFrameOfZeros(QByteArray& encodedZeros);
|
||||
bool shouldFlushEncoder() { return _shouldFlushEncoder; }
|
||||
|
||||
QString getCodecName() { return _selectedCodecName; }
|
||||
|
||||
|
@ -105,6 +110,8 @@ private:
|
|||
QString _selectedCodecName;
|
||||
Encoder* _encoder{ nullptr }; // for outbound mixed stream
|
||||
Decoder* _decoder{ nullptr }; // for mic stream
|
||||
|
||||
bool _shouldFlushEncoder { false };
|
||||
};
|
||||
|
||||
#endif // hifi_AudioMixerClientData_h
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
{ "from": "Vive.RSCenter", "to": "Standard.RightPrimaryThumb" },
|
||||
{ "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" },
|
||||
|
||||
{ "from": "Vive.LeftHand", "to": "Standard.LeftHand" },
|
||||
{ "from": "Vive.RightHand", "to": "Standard.RightHand" }
|
||||
{ "from": "Vive.LeftHand", "to": "Standard.LeftHand", "when": [ "Application.InHMD" ] },
|
||||
{ "from": "Vive.RightHand", "to": "Standard.RightHand", "when": [ "Application.InHMD" ] }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 246 KiB After Width: | Height: | Size: 106 KiB |
BIN
interface/resources/images/steam-min-spec-failed.png
Normal file
After Width: | Height: | Size: 80 KiB |
|
@ -27,6 +27,7 @@ Item {
|
|||
|
||||
WebEngineView {
|
||||
id: root
|
||||
objectName: "webEngineView"
|
||||
x: 0
|
||||
y: 0
|
||||
width: parent.width
|
||||
|
|
|
@ -534,6 +534,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_maxOctreePPS(maxOctreePacketsPerSecond.get()),
|
||||
_lastFaceTrackerUpdate(0)
|
||||
{
|
||||
setProperty("com.highfidelity.launchedFromSteam", SteamClient::isRunning());
|
||||
|
||||
_runningMarker.startRunningMarker();
|
||||
|
||||
PluginContainer* pluginContainer = dynamic_cast<PluginContainer*>(this); // set the container for any plugins that care
|
||||
|
@ -569,6 +571,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_deadlockWatchdogThread = new DeadlockWatchdogThread();
|
||||
_deadlockWatchdogThread->start();
|
||||
|
||||
qCDebug(interfaceapp) << "[VERSION] SteamVR buildID:" << SteamClient::getSteamVRBuildID();
|
||||
qCDebug(interfaceapp) << "[VERSION] Build sequence:" << qPrintable(applicationVersion());
|
||||
qCDebug(interfaceapp) << "[VERSION] MODIFIED_ORGANIZATION:" << BuildInfo::MODIFIED_ORGANIZATION;
|
||||
qCDebug(interfaceapp) << "[VERSION] VERSION:" << BuildInfo::VERSION;
|
||||
|
@ -1191,6 +1194,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
properties["dropped_frame_rate"] = displayPlugin->droppedFrameRate();
|
||||
properties["sim_rate"] = getAverageSimsPerSecond();
|
||||
properties["avatar_sim_rate"] = getAvatarSimrate();
|
||||
properties["has_async_reprojection"] = displayPlugin->hasAsyncReprojection();
|
||||
|
||||
auto bandwidthRecorder = DependencyManager::get<BandwidthRecorder>();
|
||||
properties["packet_rate_in"] = bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond();
|
||||
|
@ -1234,6 +1238,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
auto glInfo = getGLContextData();
|
||||
properties["gl_info"] = glInfo;
|
||||
properties["gpu_free_memory"] = (int)BYTES_TO_MB(gpu::Context::getFreeGPUMemory());
|
||||
properties["ideal_thread_count"] = QThread::idealThreadCount();
|
||||
|
||||
auto hmdHeadPose = getHMDSensorPose();
|
||||
properties["hmd_head_pose_changed"] = isHMDMode() && (hmdHeadPose != lastHMDHeadPose);
|
||||
|
|
|
@ -620,6 +620,14 @@ Menu::Menu() {
|
|||
// Developer > Audio >>>
|
||||
MenuWrapper* audioDebugMenu = developerMenu->addMenu("Audio");
|
||||
|
||||
action = addActionToQMenuAndActionHash(audioDebugMenu, "Stats...");
|
||||
connect(action, &QAction::triggered, [] {
|
||||
auto scriptEngines = DependencyManager::get<ScriptEngines>();
|
||||
QUrl defaultScriptsLoc = defaultScriptsLocation();
|
||||
defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/audio/stats.js");
|
||||
scriptEngines->loadScript(defaultScriptsLoc.toString());
|
||||
});
|
||||
|
||||
action = addActionToQMenuAndActionHash(audioDebugMenu, "Buffers...");
|
||||
connect(action, &QAction::triggered, [] {
|
||||
DependencyManager::get<OffscreenUi>()->toggle(QString("hifi/dialogs/AudioPreferencesDialog.qml"), "AudioPreferencesDialog");
|
||||
|
|
|
@ -37,7 +37,11 @@
|
|||
#include <CrashReporter.h>
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
extern "C" {
|
||||
typedef int(__stdcall * CHECKMINSPECPROC) ();
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
#if HAS_BUGSPLAT
|
||||
|
@ -155,15 +159,33 @@ int main(int argc, const char* argv[]) {
|
|||
|
||||
SteamClient::init();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// If we're running in steam mode, we need to do an explicit check to ensure we're up to the required min spec
|
||||
if (SteamClient::isRunning()) {
|
||||
QString appPath;
|
||||
{
|
||||
char filename[MAX_PATH];
|
||||
GetModuleFileName(NULL, filename, MAX_PATH);
|
||||
QFileInfo appInfo(filename);
|
||||
appPath = appInfo.absolutePath();
|
||||
}
|
||||
QString openvrDllPath = appPath + "/plugins/openvr.dll";
|
||||
HMODULE openvrDll;
|
||||
CHECKMINSPECPROC checkMinSpecPtr;
|
||||
if ((openvrDll = LoadLibrary(openvrDllPath.toLocal8Bit().data())) &&
|
||||
(checkMinSpecPtr = (CHECKMINSPECPROC)GetProcAddress(openvrDll, "CheckMinSpec"))) {
|
||||
if (!checkMinSpecPtr()) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int exitCode;
|
||||
{
|
||||
QSettings::setDefaultFormat(QSettings::IniFormat);
|
||||
Application app(argc, const_cast<char**>(argv), startupTime, runServer, serverContentPathOptionValue);
|
||||
|
||||
bool launchedFromSteam = SteamClient::isRunning();
|
||||
app.setProperty("com.highfidelity.launchedFromSteam", launchedFromSteam);
|
||||
|
||||
// If we failed the OpenGLVersion check, log it.
|
||||
if (override) {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
|
|
|
@ -328,7 +328,17 @@ void RenderableWebEntityItem::handlePointerEvent(const PointerEvent& event) {
|
|||
void RenderableWebEntityItem::destroyWebSurface() {
|
||||
if (_webSurface) {
|
||||
--_currentWebCount;
|
||||
|
||||
QQuickItem* rootItem = _webSurface->getRootItem();
|
||||
if (rootItem) {
|
||||
QObject* obj = rootItem->findChild<QObject*>("webEngineView");
|
||||
if (obj) {
|
||||
QMetaObject::invokeMethod(obj, "stop");
|
||||
}
|
||||
}
|
||||
|
||||
_webSurface->pause();
|
||||
|
||||
_webSurface->disconnect(_connection);
|
||||
QObject::disconnect(_mousePressConnection);
|
||||
_mousePressConnection = QMetaObject::Connection();
|
||||
|
|
|
@ -344,7 +344,6 @@ bool OffscreenQmlSurface::allowNewFrame(uint8_t fps) {
|
|||
OffscreenQmlSurface::OffscreenQmlSurface() {
|
||||
}
|
||||
|
||||
static const uint64_t MAX_SHUTDOWN_WAIT_SECS = 2;
|
||||
OffscreenQmlSurface::~OffscreenQmlSurface() {
|
||||
QObject::disconnect(&_updateTimer);
|
||||
QObject::disconnect(qApp);
|
||||
|
|
|
@ -306,6 +306,13 @@ void Socket::checkForReadyReadBackup() {
|
|||
if (_udpSocket.hasPendingDatagrams()) {
|
||||
qCDebug(networking) << "Socket::checkForReadyReadBackup() detected blocked readyRead signal. Flushing pending datagrams.";
|
||||
|
||||
// so that birarda can possibly figure out how the heck we get into this state in the first place
|
||||
// output the sequence number and socket address of the last processed packet
|
||||
qCDebug(networking) << "Socket::checkForReadyReadyBackup() last sequence number"
|
||||
<< (uint32_t) _lastReceivedSequenceNumber << "from" << _lastPacketSockAddr << "-"
|
||||
<< _lastPacketSizeRead << "bytes";
|
||||
|
||||
|
||||
// drop all of the pending datagrams on the floor
|
||||
while (_udpSocket.hasPendingDatagrams()) {
|
||||
_udpSocket.readDatagram(nullptr, 0);
|
||||
|
@ -334,6 +341,10 @@ void Socket::readPendingDatagrams() {
|
|||
auto sizeRead = _udpSocket.readDatagram(buffer.get(), packetSizeWithHeader,
|
||||
senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
|
||||
|
||||
// save information for this packet, in case it is the one that sticks readyRead
|
||||
_lastPacketSizeRead = sizeRead;
|
||||
_lastPacketSockAddr = senderSockAddr;
|
||||
|
||||
if (sizeRead <= 0) {
|
||||
// we either didn't pull anything for this packet or there was an error reading (this seems to trigger
|
||||
// on windows even if there's not a packet available)
|
||||
|
@ -373,6 +384,9 @@ void Socket::readPendingDatagrams() {
|
|||
auto packet = Packet::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr);
|
||||
packet->setReceiveTime(receiveTime);
|
||||
|
||||
// save the sequence number in case this is the packet that sticks readyRead
|
||||
_lastReceivedSequenceNumber = packet->getSequenceNumber();
|
||||
|
||||
// call our verification operator to see if this packet is verified
|
||||
if (!_packetFilterOperator || _packetFilterOperator(*packet)) {
|
||||
if (packet->isReliable()) {
|
||||
|
|
|
@ -144,6 +144,10 @@ private:
|
|||
std::unique_ptr<CongestionControlVirtualFactory> _ccFactory { new CongestionControlFactory<TCPVegasCC>() };
|
||||
|
||||
bool _shouldChangeSocketOptions { true };
|
||||
|
||||
int _lastPacketSizeRead { 0 };
|
||||
SequenceNumber _lastReceivedSequenceNumber;
|
||||
HifiSockAddr _lastPacketSockAddr;
|
||||
|
||||
friend UDTTest;
|
||||
};
|
||||
|
|
|
@ -139,6 +139,7 @@ public:
|
|||
virtual bool isStereo() const { return isHmd(); }
|
||||
virtual bool isThrottled() const { return false; }
|
||||
virtual float getTargetFrameRate() const { return 0.0f; }
|
||||
virtual bool hasAsyncReprojection() const { return false; }
|
||||
|
||||
/// Returns a boolean value indicating whether the display is currently visible
|
||||
/// to the user. For monitor displays, false might indicate that a screensaver,
|
||||
|
|
|
@ -44,33 +44,40 @@ void BatchLoader::start() {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
for (const auto& rawURL : _urls) {
|
||||
QUrl url = expandScriptUrl(normalizeScriptURL(rawURL));
|
||||
|
||||
qCDebug(scriptengine) << "Loading script at " << url;
|
||||
|
||||
QPointer<BatchLoader> self = this;
|
||||
DependencyManager::get<ScriptCache>()->getScriptContents(url.toString(), [this, self](const QString& url, const QString& contents, bool isURL, bool success) {
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
auto scriptCache = DependencyManager::get<ScriptCache>();
|
||||
|
||||
// Because the ScriptCache may call this callback from differents threads,
|
||||
// we need to make sure this is thread-safe.
|
||||
std::lock_guard<std::mutex> lock(_dataLock);
|
||||
// Use a proxy callback to handle the call and emit the signal in a thread-safe way.
|
||||
// If BatchLoader is deleted before the callback is called, the subsequent "emit" call will not do
|
||||
// anything.
|
||||
ScriptCacheSignalProxy* proxy = new ScriptCacheSignalProxy(scriptCache.data());
|
||||
scriptCache->getScriptContents(url.toString(), [proxy](const QString& url, const QString& contents, bool isURL, bool success) {
|
||||
proxy->receivedContent(url, contents, isURL, success);
|
||||
proxy->deleteLater();
|
||||
}, false);
|
||||
|
||||
connect(proxy, &ScriptCacheSignalProxy::contentAvailable, this, [this](const QString& url, const QString& contents, bool isURL, bool success) {
|
||||
if (isURL && success) {
|
||||
_data.insert(url, contents);
|
||||
qCDebug(scriptengine) << "Loaded: " << url;
|
||||
} else {
|
||||
_data.insert(url, QString());
|
||||
qCDebug(scriptengine) << "Could not load" << url;
|
||||
qCDebug(scriptengine) << "Could not load: " << url;
|
||||
}
|
||||
|
||||
if (!_finished && _urls.size() == _data.size()) {
|
||||
_finished = true;
|
||||
emit finished(_data);
|
||||
}
|
||||
}, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptCacheSignalProxy::receivedContent(const QString& url, const QString& contents, bool isURL, bool success) {
|
||||
emit contentAvailable(url, contents, isURL, success);
|
||||
}
|
||||
|
|
|
@ -21,10 +21,20 @@
|
|||
|
||||
#include <mutex>
|
||||
|
||||
class ScriptCacheSignalProxy : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ScriptCacheSignalProxy(QObject* parent) : QObject(parent) { }
|
||||
void receivedContent(const QString& url, const QString& contents, bool isURL, bool success);
|
||||
|
||||
signals:
|
||||
void contentAvailable(const QString& url, const QString& contents, bool isURL, bool success);
|
||||
};
|
||||
|
||||
class BatchLoader : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
BatchLoader(const QList<QUrl>& urls) ;
|
||||
BatchLoader(const QList<QUrl>& urls);
|
||||
|
||||
void start();
|
||||
bool isFinished() const { return _finished; };
|
||||
|
@ -39,7 +49,6 @@ private:
|
|||
bool _finished;
|
||||
QSet<QUrl> _urls;
|
||||
QMap<QUrl, QString> _data;
|
||||
std::mutex _dataLock;
|
||||
};
|
||||
|
||||
#endif // hifi_BatchLoader_h
|
||||
|
|
|
@ -245,6 +245,32 @@ void SteamClient::shutdown() {
|
|||
steamCallbackManager.getTicketRequests().stopAll();
|
||||
}
|
||||
|
||||
int SteamClient::getSteamVRBuildID() {
|
||||
if (initialized) {
|
||||
static const int MAX_PATH_SIZE = 512;
|
||||
static const int STEAMVR_APPID = 250820;
|
||||
char rawPath[MAX_PATH_SIZE];
|
||||
SteamApps()->GetAppInstallDir(STEAMVR_APPID, rawPath, MAX_PATH_SIZE);
|
||||
|
||||
QString path(rawPath);
|
||||
path += "\\bin\\version.txt";
|
||||
qDebug() << "SteamVR version file path:" << path;
|
||||
|
||||
QFile file(path);
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
QString buildIDString = file.readLine();
|
||||
|
||||
bool ok = false;
|
||||
int buildID = buildIDString.toInt(&ok);
|
||||
if (ok) {
|
||||
return buildID;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void SteamClient::runCallbacks() {
|
||||
if (!initialized) {
|
||||
return;
|
||||
|
|
|
@ -37,6 +37,7 @@ public:
|
|||
static void openInviteOverlay();
|
||||
static void joinLobby(QString lobbyId);
|
||||
|
||||
static int getSteamVRBuildID();
|
||||
};
|
||||
|
||||
class SteamScriptingInterface : public QObject {
|
||||
|
|
|
@ -19,6 +19,9 @@ public:
|
|||
~OculusBaseDisplayPlugin();
|
||||
bool isSupported() const override;
|
||||
|
||||
bool hasAsyncReprojection() const override { return true; }
|
||||
|
||||
|
||||
// Stereo specific methods
|
||||
void resetSensors() override final;
|
||||
bool beginFrameRender(uint32_t frameIndex) override;
|
||||
|
|
|
@ -43,7 +43,6 @@ PoseData _nextSimPoseData;
|
|||
#define MIN_CORES_FOR_NORMAL_RENDER 5
|
||||
bool forceInterleavedReprojection = (QThread::idealThreadCount() < MIN_CORES_FOR_NORMAL_RENDER);
|
||||
|
||||
|
||||
static std::array<vr::Hmd_Eye, 2> VR_EYES { { vr::Eye_Left, vr::Eye_Right } };
|
||||
bool _openVrDisplayActive { false };
|
||||
// Flip y-axis since GL UV coords are backwards.
|
||||
|
@ -354,6 +353,13 @@ bool OpenVrDisplayPlugin::isSupported() const {
|
|||
return openVrSupported();
|
||||
}
|
||||
|
||||
float OpenVrDisplayPlugin::getTargetFrameRate() const {
|
||||
if (forceInterleavedReprojection && !_asyncReprojectionActive) {
|
||||
return TARGET_RATE_OpenVr / 2.0f;
|
||||
}
|
||||
return TARGET_RATE_OpenVr;
|
||||
}
|
||||
|
||||
void OpenVrDisplayPlugin::init() {
|
||||
Plugin::init();
|
||||
|
||||
|
@ -373,9 +379,6 @@ void OpenVrDisplayPlugin::init() {
|
|||
emit deviceConnected(getName());
|
||||
}
|
||||
|
||||
// FIXME remove once OpenVR header is updated
|
||||
#define VRCompositor_ReprojectionAsync 0x04
|
||||
|
||||
bool OpenVrDisplayPlugin::internalActivate() {
|
||||
if (!_system) {
|
||||
_system = acquireOpenVrSystem();
|
||||
|
@ -398,9 +401,10 @@ bool OpenVrDisplayPlugin::internalActivate() {
|
|||
memset(&timing, 0, sizeof(timing));
|
||||
timing.m_nSize = sizeof(vr::Compositor_FrameTiming);
|
||||
vr::VRCompositor()->GetFrameTiming(&timing);
|
||||
bool asyncReprojectionActive = timing.m_nReprojectionFlags & VRCompositor_ReprojectionAsync;
|
||||
_asyncReprojectionActive = timing.m_nReprojectionFlags & VRCompositor_ReprojectionAsync;
|
||||
|
||||
_threadedSubmit = !asyncReprojectionActive;
|
||||
_threadedSubmit = !_asyncReprojectionActive;
|
||||
qDebug() << "OpenVR Async Reprojection active: " << _asyncReprojectionActive;
|
||||
qDebug() << "OpenVR Threaded submit enabled: " << _threadedSubmit;
|
||||
|
||||
_openVrDisplayActive = true;
|
||||
|
@ -697,4 +701,4 @@ bool OpenVrDisplayPlugin::isKeyboardVisible() {
|
|||
|
||||
int OpenVrDisplayPlugin::getRequiredThreadCount() const {
|
||||
return Parent::getRequiredThreadCount() + (_threadedSubmit ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,8 @@ public:
|
|||
|
||||
void init() override;
|
||||
|
||||
float getTargetFrameRate() const override { return TARGET_RATE_OpenVr; }
|
||||
float getTargetFrameRate() const override;
|
||||
bool hasAsyncReprojection() const override { return _asyncReprojectionActive; }
|
||||
|
||||
void customizeContext() override;
|
||||
void uncustomizeContext() override;
|
||||
|
@ -82,4 +83,6 @@ private:
|
|||
std::shared_ptr<OpenVrSubmitThread> _submitThread;
|
||||
std::shared_ptr<gl::OffscreenContext> _submitCanvas;
|
||||
friend class OpenVrSubmitThread;
|
||||
|
||||
bool _asyncReprojectionActive { false };
|
||||
};
|
||||
|
|
|
@ -12,11 +12,13 @@
|
|||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QLoggingCategory>
|
||||
#include <QtCore/QProcessEnvironment>
|
||||
#include <QtGui/QInputMethodEvent>
|
||||
#include <QtQuick/QQuickWindow>
|
||||
|
||||
#include <PathUtils.h>
|
||||
#include <Windows.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <controllers/Pose.h>
|
||||
|
@ -324,3 +326,107 @@ controller::Pose openVrControllerPoseToHandPose(bool isLeftHand, const mat4& mat
|
|||
result.angularVelocity = angularVelocity;
|
||||
return result;
|
||||
}
|
||||
|
||||
#define FAILED_MIN_SPEC_OVERLAY_NAME "FailedMinSpecOverlay"
|
||||
#define FAILED_MIN_SPEC_OVERLAY_FRIENDLY_NAME "Minimum specifications for SteamVR not met"
|
||||
#define FAILED_MIN_SPEC_UPDATE_INTERVAL_MS 10
|
||||
#define FAILED_MIN_SPEC_AUTO_QUIT_INTERVAL_MS (MSECS_PER_SECOND * 30)
|
||||
#define MIN_CORES_SPEC 5
|
||||
|
||||
void showMinSpecWarning() {
|
||||
auto vrSystem = acquireOpenVrSystem();
|
||||
auto vrOverlay = vr::VROverlay();
|
||||
if (!vrOverlay) {
|
||||
qFatal("Unable to initialize SteamVR overlay manager");
|
||||
}
|
||||
|
||||
vr::VROverlayHandle_t minSpecFailedOverlay = 0;
|
||||
if (vr::VROverlayError_None != vrOverlay->CreateOverlay(FAILED_MIN_SPEC_OVERLAY_NAME, FAILED_MIN_SPEC_OVERLAY_FRIENDLY_NAME, &minSpecFailedOverlay)) {
|
||||
qFatal("Unable to create overlay");
|
||||
}
|
||||
|
||||
// Needed here for PathUtils
|
||||
QCoreApplication miniApp(__argc, __argv);
|
||||
|
||||
vrSystem->ResetSeatedZeroPose();
|
||||
QString imagePath = PathUtils::resourcesPath() + "/images/steam-min-spec-failed.png";
|
||||
vrOverlay->SetOverlayFromFile(minSpecFailedOverlay, imagePath.toLocal8Bit().toStdString().c_str());
|
||||
vrOverlay->SetHighQualityOverlay(minSpecFailedOverlay);
|
||||
vrOverlay->SetOverlayWidthInMeters(minSpecFailedOverlay, 1.4f);
|
||||
vrOverlay->SetOverlayInputMethod(minSpecFailedOverlay, vr::VROverlayInputMethod_Mouse);
|
||||
vrOverlay->ShowOverlay(minSpecFailedOverlay);
|
||||
|
||||
QTimer* timer = new QTimer(&miniApp);
|
||||
timer->setInterval(FAILED_MIN_SPEC_UPDATE_INTERVAL_MS);
|
||||
QObject::connect(timer, &QTimer::timeout, [&] {
|
||||
vr::TrackedDevicePose_t vrPoses[vr::k_unMaxTrackedDeviceCount];
|
||||
vrSystem->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseSeated, 0, vrPoses, vr::k_unMaxTrackedDeviceCount);
|
||||
auto headPose = toGlm(vrPoses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking);
|
||||
auto overlayPose = toOpenVr(headPose * glm::translate(glm::mat4(), vec3(0, 0, -1)));
|
||||
vrOverlay->SetOverlayTransformAbsolute(minSpecFailedOverlay, vr::TrackingUniverseSeated, &overlayPose);
|
||||
|
||||
vr::VREvent_t event;
|
||||
while (vrSystem->PollNextEvent(&event, sizeof(event))) {
|
||||
switch (event.eventType) {
|
||||
case vr::VREvent_Quit:
|
||||
vrSystem->AcknowledgeQuit_Exiting();
|
||||
QCoreApplication::quit();
|
||||
break;
|
||||
|
||||
case vr::VREvent_ButtonPress:
|
||||
// Quit on any button press except for 'putting on the headset'
|
||||
if (event.data.controller.button != vr::k_EButton_ProximitySensor) {
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
timer->start();
|
||||
|
||||
QTimer::singleShot(FAILED_MIN_SPEC_AUTO_QUIT_INTERVAL_MS, &miniApp, &QCoreApplication::quit);
|
||||
miniApp.exec();
|
||||
}
|
||||
|
||||
|
||||
bool checkMinSpecImpl() {
|
||||
// If OpenVR isn't supported, we have no min spec, so pass
|
||||
if (!openVrSupported()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we have at least 5 cores, pass
|
||||
auto coreCount = QThread::idealThreadCount();
|
||||
if (coreCount >= MIN_CORES_SPEC) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Even if we have too few cores... if the compositor is using async reprojection, pass
|
||||
auto system = acquireOpenVrSystem();
|
||||
auto compositor = vr::VRCompositor();
|
||||
if (system && compositor) {
|
||||
vr::Compositor_FrameTiming timing;
|
||||
memset(&timing, 0, sizeof(timing));
|
||||
timing.m_nSize = sizeof(vr::Compositor_FrameTiming);
|
||||
compositor->GetFrameTiming(&timing);
|
||||
releaseOpenVrSystem();
|
||||
if (timing.m_nReprojectionFlags & VRCompositor_ReprojectionAsync) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We're using OpenVR and we don't have enough cores...
|
||||
showMinSpecWarning();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
__declspec(dllexport) int __stdcall CheckMinSpec() {
|
||||
return checkMinSpecImpl() ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,5 +82,7 @@ struct PoseData {
|
|||
}
|
||||
};
|
||||
|
||||
// FIXME remove once OpenVR header is updated
|
||||
#define VRCompositor_ReprojectionAsync 0x04
|
||||
|
||||
controller::Pose openVrControllerPoseToHandPose(bool isLeftHand, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity);
|
||||
|
|
26
scripts/system/assets/images/progress-bar-2k.svg
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 2240 3" style="enable-background:new 0 0 2240 3;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:url(#SVGID_1_);}
|
||||
</style>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="0.1399" y1="-1.9217" x2="2239.7764" y2="-1.9217" gradientTransform="matrix(-1 0 0 0.5607 2240.1399 2.812)">
|
||||
<stop offset="0" style="stop-color:#0FE8CD"/>
|
||||
<stop offset="0.13" style="stop-color:#020202;stop-opacity:0"/>
|
||||
<stop offset="0.1515" style="stop-color:#0FE8CD"/>
|
||||
<stop offset="0.28" style="stop-color:#000000;stop-opacity:0"/>
|
||||
<stop offset="0.3" style="stop-color:#0FE8CD"/>
|
||||
<stop offset="0.42" style="stop-color:#000000;stop-opacity:0"/>
|
||||
<stop offset="0.44" style="stop-color:#0FE8CD"/>
|
||||
<stop offset="0.57" style="stop-color:#000000;stop-opacity:0"/>
|
||||
<stop offset="0.5885" style="stop-color:#0FE8CD"/>
|
||||
<stop offset="0.71" style="stop-color:#000000;stop-opacity:0"/>
|
||||
<stop offset="0.73" style="stop-color:#0FE8CD"/>
|
||||
<stop offset="0.85" style="stop-color:#000000;stop-opacity:0"/>
|
||||
<stop offset="0.869" style="stop-color:#0FE8CD"/>
|
||||
<stop offset="0.9831" style="stop-color:#000000;stop-opacity:0"/>
|
||||
<stop offset="1" style="stop-color:#0FE8CD"/>
|
||||
</linearGradient>
|
||||
<rect x="0.4" y="0.5" class="st0" width="2239.6" height="2.5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
26
scripts/system/assets/images/progress-bar-4k.svg
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 4480 6" style="enable-background:new 0 0 4480 6;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:url(#SVGID_1_);}
|
||||
</style>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="0.607" y1="-0.4217" x2="4479.3096" y2="-0.4217" gradientTransform="matrix(-1 0 0 0.5607 4480.1401 3.471)">
|
||||
<stop offset="0" style="stop-color:#0FE8CD"/>
|
||||
<stop offset="0.13" style="stop-color:#020202;stop-opacity:0"/>
|
||||
<stop offset="0.1515" style="stop-color:#0FE8CD"/>
|
||||
<stop offset="0.28" style="stop-color:#000000;stop-opacity:0"/>
|
||||
<stop offset="0.3" style="stop-color:#0FE8CD"/>
|
||||
<stop offset="0.42" style="stop-color:#000000;stop-opacity:0"/>
|
||||
<stop offset="0.44" style="stop-color:#0FE8CD"/>
|
||||
<stop offset="0.57" style="stop-color:#000000;stop-opacity:0"/>
|
||||
<stop offset="0.5885" style="stop-color:#0FE8CD"/>
|
||||
<stop offset="0.71" style="stop-color:#000000;stop-opacity:0"/>
|
||||
<stop offset="0.73" style="stop-color:#0FE8CD"/>
|
||||
<stop offset="0.85" style="stop-color:#000000;stop-opacity:0"/>
|
||||
<stop offset="0.869" style="stop-color:#0FE8CD"/>
|
||||
<stop offset="0.9831" style="stop-color:#000000;stop-opacity:0"/>
|
||||
<stop offset="1" style="stop-color:#0FE8CD"/>
|
||||
</linearGradient>
|
||||
<rect x="0.8" y="0.7" class="st0" width="4478.7" height="5.1"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="520" height="50" viewBox="0 0 520.00 50.00" enable-background="new 0 0 540.00 50.00" xml:space="preserve">
|
||||
<rect x="0" y="0" rx="8" ry="8" width="520" height="50" style="fille:black;opacity:0.6" />
|
||||
</svg>
|
103
scripts/system/assets/images/progress-bar-text.svg
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 256 32" style="enable-background:new 0 0 256 32;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#383838;stroke:#1E1E1E;stroke-width:0.5;stroke-miterlimit:10;}
|
||||
.st1{fill:#FFFFFF;stroke:#BABABA;stroke-width:0.25;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M5.5,8v14.8h7.8l-0.3,2H3.2V8H5.5z"/>
|
||||
<path class="st0" d="M16.7,15.3c0-1.2,0.1-2.2,0.3-3.2c0.2-0.9,0.5-1.8,1.1-2.4c0.5-0.7,1.2-1.2,2-1.6c0.8-0.4,1.9-0.5,3.2-0.5
|
||||
c1.3,0,2.4,0.2,3.2,0.5c0.8,0.4,1.5,0.9,2,1.5c0.5,0.7,0.8,1.5,1,2.4c0.2,1,0.3,2,0.3,3.2v2.1c0,1.2-0.1,2.2-0.3,3.2
|
||||
c-0.2,0.9-0.5,1.8-1.1,2.4c-0.5,0.7-1.2,1.2-2,1.6c-0.8,0.4-1.9,0.5-3.2,0.5c-1.3,0-2.4-0.2-3.2-0.5c-0.9-0.4-1.5-0.9-2-1.5
|
||||
c-0.5-0.7-0.8-1.5-1-2.4c-0.2-1-0.3-2-0.3-3.2V15.3z M27.4,15.3c0-1.2-0.1-2.1-0.2-2.9c-0.2-0.7-0.4-1.3-0.8-1.7
|
||||
c-0.3-0.4-0.8-0.7-1.3-0.9c-0.5-0.2-1.1-0.2-1.8-0.2S22,9.7,21.5,9.8c-0.5,0.2-1,0.5-1.3,0.9c-0.3,0.4-0.6,1-0.8,1.8
|
||||
c-0.2,0.7-0.3,1.7-0.3,2.8v2.1c0,1.2,0.1,2.1,0.2,2.9s0.4,1.3,0.8,1.7c0.3,0.4,0.8,0.7,1.3,0.9c0.5,0.2,1.1,0.2,1.8,0.2
|
||||
s1.3-0.1,1.8-0.2c0.5-0.2,1-0.5,1.3-0.9c0.3-0.4,0.6-1,0.8-1.7c0.2-0.7,0.3-1.7,0.3-2.9V15.3z"/>
|
||||
<path class="st0" d="M43.6,20h-6.4l-1.6,4.7h-2.4L39.1,8h2.6l5.9,16.8h-2.5L43.6,20z M37.8,18.1H43l-2.6-7.8L37.8,18.1z"/>
|
||||
<path class="st0" d="M52,8h5.5c1.4,0,2.5,0.2,3.4,0.6c0.9,0.4,1.6,0.9,2.2,1.6c0.5,0.7,0.9,1.4,1.1,2.4c0.2,0.9,0.3,1.9,0.3,3v1.9
|
||||
c0,1.1-0.1,2.1-0.3,3c-0.2,0.9-0.6,1.7-1.2,2.4c-0.6,0.7-1.3,1.2-2.2,1.5c-0.9,0.4-2,0.5-3.4,0.5H52V8z M54.4,22.8h3
|
||||
c0.8,0,1.5-0.1,2.1-0.3c0.6-0.2,1.1-0.5,1.5-0.9c0.4-0.4,0.7-1,0.9-1.7c0.2-0.7,0.3-1.6,0.3-2.7v-1.6c0-1.1-0.1-2-0.3-2.7
|
||||
c-0.2-0.7-0.5-1.3-0.9-1.7c-0.4-0.4-0.9-0.8-1.5-0.9c-0.6-0.2-1.3-0.3-2.1-0.3h-3V22.8z"/>
|
||||
<path class="st0" d="M72.3,24.8h-2.4V8h2.4V24.8z"/>
|
||||
<path class="st0" d="M80.2,8l7.9,13V8h2.2v16.8h-2.1l-7.9-13v13H78V8H80.2z"/>
|
||||
<path class="st0" d="M103.3,9.7c-1,0-1.8,0.1-2.4,0.3s-1.2,0.5-1.6,0.9c-0.4,0.4-0.7,1.1-0.9,1.8c-0.2,0.8-0.3,1.7-0.3,2.9v1.6
|
||||
c0,1.2,0.1,2.1,0.3,2.9c0.2,0.7,0.5,1.3,0.8,1.8c0.4,0.4,0.9,0.7,1.5,0.9c0.6,0.2,1.4,0.3,2.3,0.3c0.8,0,1.5-0.1,2.2-0.2v-5.2h-2.8
|
||||
l0.3-1.9h4.8v8.6c-0.3,0.1-0.6,0.2-1,0.3s-0.8,0.2-1.2,0.2c-0.4,0.1-0.8,0.1-1.3,0.1c-0.4,0-0.8,0-1.2,0c-1.5,0-2.7-0.2-3.6-0.6
|
||||
c-0.9-0.4-1.6-0.9-2.1-1.6c-0.5-0.7-0.9-1.5-1-2.4c-0.2-0.9-0.3-2-0.3-3.1v-1.7c0-1.2,0.1-2.3,0.3-3.2c0.2-1,0.6-1.8,1.2-2.5
|
||||
c0.6-0.7,1.3-1.2,2.3-1.6c1-0.4,2.2-0.6,3.7-0.6c0.7,0,1.5,0.1,2.2,0.2c0.7,0.1,1.3,0.3,1.7,0.4l-0.4,1.8c-0.4-0.1-0.9-0.2-1.5-0.3
|
||||
C104.5,9.8,103.9,9.7,103.3,9.7z"/>
|
||||
<path class="st0" d="M127.1,25c-1.4,0-2.6-0.2-3.5-0.5c-0.9-0.3-1.6-0.8-2.1-1.5c-0.5-0.6-0.9-1.4-1.1-2.4c-0.2-1-0.3-2.1-0.3-3.3
|
||||
v-1.7c0-1.2,0.1-2.3,0.3-3.3c0.2-1,0.6-1.8,1.2-2.5c0.6-0.7,1.3-1.2,2.2-1.5c0.9-0.4,2.1-0.5,3.5-0.5c0.7,0,1.4,0.1,2,0.2
|
||||
c0.6,0.1,1.1,0.2,1.5,0.4l-0.4,1.8c-0.4-0.1-0.8-0.2-1.3-0.3c-0.5-0.1-1-0.1-1.5-0.1c-0.9,0-1.7,0.1-2.3,0.3
|
||||
c-0.6,0.2-1.1,0.5-1.5,0.9c-0.4,0.4-0.7,1.1-0.9,1.8c-0.2,0.8-0.3,1.7-0.3,2.9v1.6c0,1.2,0.1,2.1,0.2,2.9c0.2,0.7,0.4,1.3,0.8,1.8
|
||||
c0.4,0.4,0.9,0.7,1.5,0.9c0.6,0.2,1.4,0.3,2.4,0.3c0.5,0,1,0,1.6-0.1c0.6-0.1,1.1-0.1,1.6-0.2l-0.4,1.9c-0.5,0.1-1,0.2-1.6,0.3
|
||||
C128.1,25,127.6,25,127.1,25z"/>
|
||||
<path class="st0" d="M134.6,15.3c0-1.2,0.1-2.2,0.3-3.2c0.2-0.9,0.5-1.8,1.1-2.4c0.5-0.7,1.2-1.2,2-1.6c0.8-0.4,1.9-0.5,3.2-0.5
|
||||
c1.3,0,2.4,0.2,3.2,0.5c0.8,0.4,1.5,0.9,2,1.5c0.5,0.7,0.8,1.5,1,2.4c0.2,1,0.3,2,0.3,3.2v2.1c0,1.2-0.1,2.2-0.3,3.2
|
||||
c-0.2,0.9-0.5,1.8-1.1,2.4c-0.5,0.7-1.2,1.2-2,1.6c-0.8,0.4-1.9,0.5-3.2,0.5c-1.3,0-2.4-0.2-3.2-0.5c-0.9-0.4-1.5-0.9-2-1.5
|
||||
c-0.5-0.7-0.8-1.5-1-2.4c-0.2-1-0.3-2-0.3-3.2V15.3z M145.3,15.3c0-1.2-0.1-2.1-0.2-2.9c-0.2-0.7-0.4-1.3-0.8-1.7
|
||||
c-0.3-0.4-0.8-0.7-1.3-0.9c-0.5-0.2-1.1-0.2-1.8-0.2s-1.3,0.1-1.8,0.2c-0.5,0.2-1,0.5-1.3,0.9c-0.3,0.4-0.6,1-0.8,1.8
|
||||
c-0.2,0.7-0.3,1.7-0.3,2.8v2.1c0,1.2,0.1,2.1,0.2,2.9c0.2,0.7,0.4,1.3,0.8,1.7c0.3,0.4,0.8,0.7,1.3,0.9c0.5,0.2,1.1,0.2,1.8,0.2
|
||||
s1.3-0.1,1.8-0.2c0.5-0.2,1-0.5,1.3-0.9c0.3-0.4,0.6-1,0.8-1.7c0.2-0.7,0.3-1.7,0.3-2.9V15.3z"/>
|
||||
<path class="st0" d="M155.2,8l7.9,13V8h2.2v16.8h-2.1l-7.9-13v13h-2.2V8H155.2z"/>
|
||||
<path class="st0" d="M182.8,8l-0.3,2h-5.2v14.8H175V9.9h-5.4l0.3-2H182.8z"/>
|
||||
<path class="st0" d="M197.6,8l-0.3,1.9h-7.9v5.3h7.4l-0.3,1.9h-7.1v5.7h8.2l-0.3,1.9H187V8H197.6z"/>
|
||||
<path class="st0" d="M204.7,8l7.9,13V8h2.2v16.8h-2.1l-7.9-13v13h-2.2V8H204.7z"/>
|
||||
<path class="st0" d="M232.3,8l-0.3,2h-5.2v14.8h-2.3V9.9h-5.4l0.3-2H232.3z"/>
|
||||
<path class="st0" d="M235.2,24.9c-0.5,0-0.9-0.1-1.1-0.4c-0.2-0.3-0.4-0.6-0.4-1.1c0-0.5,0.1-0.8,0.4-1.1c0.2-0.3,0.6-0.4,1.1-0.4
|
||||
c0.5,0,0.9,0.1,1.1,0.4c0.2,0.3,0.4,0.6,0.4,1.1c0,0.5-0.1,0.8-0.4,1.1C236.1,24.8,235.7,24.9,235.2,24.9z"/>
|
||||
<path class="st0" d="M243.3,24.9c-0.5,0-0.9-0.1-1.1-0.4c-0.2-0.3-0.4-0.6-0.4-1.1c0-0.5,0.1-0.8,0.4-1.1c0.2-0.3,0.6-0.4,1.1-0.4
|
||||
s0.9,0.1,1.1,0.4c0.2,0.3,0.4,0.6,0.4,1.1c0,0.5-0.1,0.8-0.4,1.1C244.2,24.8,243.8,24.9,243.3,24.9z"/>
|
||||
<path class="st0" d="M251.3,24.9c-0.5,0-0.9-0.1-1.1-0.4c-0.2-0.3-0.4-0.6-0.4-1.1c0-0.5,0.1-0.8,0.4-1.1c0.2-0.3,0.6-0.4,1.1-0.4
|
||||
s0.9,0.1,1.1,0.4c0.2,0.3,0.4,0.6,0.4,1.1c0,0.5-0.1,0.8-0.4,1.1C252.2,24.8,251.8,24.9,251.3,24.9z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M6.6,6.8v14.8h7.8l-0.3,2H4.3V6.8H6.6z"/>
|
||||
<path class="st1" d="M17.8,14.2c0-1.2,0.1-2.2,0.3-3.2c0.2-0.9,0.5-1.8,1.1-2.4c0.5-0.7,1.2-1.2,2-1.6c0.8-0.4,1.9-0.5,3.2-0.5
|
||||
c1.3,0,2.4,0.2,3.2,0.5c0.8,0.4,1.5,0.9,2,1.5c0.5,0.7,0.8,1.5,1,2.4c0.2,1,0.3,2,0.3,3.2v2.1c0,1.2-0.1,2.2-0.3,3.2
|
||||
c-0.2,0.9-0.5,1.8-1.1,2.4c-0.5,0.7-1.2,1.2-2,1.6S25.7,24,24.4,24c-1.3,0-2.4-0.2-3.2-0.5c-0.9-0.4-1.5-0.9-2-1.5
|
||||
c-0.5-0.7-0.8-1.5-1-2.4c-0.2-1-0.3-2-0.3-3.2V14.2z M28.6,14.2c0-1.2-0.1-2.1-0.2-2.9c-0.2-0.7-0.4-1.3-0.8-1.7
|
||||
c-0.3-0.4-0.8-0.7-1.3-0.9c-0.5-0.2-1.1-0.2-1.8-0.2c-0.7,0-1.3,0.1-1.8,0.2c-0.5,0.2-1,0.5-1.3,0.9c-0.3,0.4-0.6,1-0.8,1.8
|
||||
c-0.2,0.7-0.3,1.7-0.3,2.8v2.1c0,1.2,0.1,2.1,0.2,2.9c0.2,0.7,0.4,1.3,0.8,1.7c0.3,0.4,0.8,0.7,1.3,0.9c0.5,0.2,1.1,0.2,1.8,0.2
|
||||
c0.7,0,1.3-0.1,1.8-0.2c0.5-0.2,1-0.5,1.3-0.9c0.3-0.4,0.6-1,0.8-1.7c0.2-0.7,0.3-1.7,0.3-2.9V14.2z"/>
|
||||
<path class="st1" d="M44.8,18.9h-6.4l-1.6,4.7h-2.4l5.9-16.8h2.6l5.9,16.8h-2.5L44.8,18.9z M38.9,17h5.2l-2.6-7.8L38.9,17z"/>
|
||||
<path class="st1" d="M53.1,6.8h5.5c1.4,0,2.5,0.2,3.4,0.6C63,7.8,63.7,8.3,64.3,9c0.5,0.7,0.9,1.4,1.1,2.4c0.2,0.9,0.3,1.9,0.3,3
|
||||
v1.9c0,1.1-0.1,2.1-0.3,3c-0.2,0.9-0.6,1.7-1.2,2.4c-0.6,0.7-1.3,1.2-2.2,1.5c-0.9,0.4-2,0.5-3.4,0.5h-5.5V6.8z M55.5,21.7h3
|
||||
c0.8,0,1.5-0.1,2.1-0.3c0.6-0.2,1.1-0.5,1.5-0.9c0.4-0.4,0.7-1,0.9-1.7c0.2-0.7,0.3-1.6,0.3-2.7v-1.6c0-1.1-0.1-2-0.3-2.7
|
||||
c-0.2-0.7-0.5-1.3-0.9-1.7c-0.4-0.4-0.9-0.8-1.5-0.9c-0.6-0.2-1.3-0.3-2.1-0.3h-3V21.7z"/>
|
||||
<path class="st1" d="M73.4,23.6h-2.4V6.8h2.4V23.6z"/>
|
||||
<path class="st1" d="M81.3,6.8l7.9,13v-13h2.2v16.8h-2.1l-7.9-13v13h-2.2V6.8H81.3z"/>
|
||||
<path class="st1" d="M104.4,8.6c-1,0-1.8,0.1-2.4,0.3c-0.7,0.2-1.2,0.5-1.6,0.9c-0.4,0.4-0.7,1.1-0.9,1.8c-0.2,0.8-0.3,1.7-0.3,2.9
|
||||
v1.6c0,1.2,0.1,2.1,0.3,2.9c0.2,0.7,0.5,1.3,0.8,1.8c0.4,0.4,0.9,0.7,1.5,0.9c0.6,0.2,1.4,0.3,2.3,0.3c0.8,0,1.5-0.1,2.2-0.2v-5.2
|
||||
h-2.8l0.3-1.9h4.8v8.6c-0.3,0.1-0.6,0.2-1,0.3c-0.4,0.1-0.8,0.2-1.2,0.2s-0.8,0.1-1.3,0.1c-0.4,0-0.8,0-1.2,0
|
||||
c-1.5,0-2.7-0.2-3.6-0.6c-0.9-0.4-1.6-0.9-2.1-1.6c-0.5-0.7-0.9-1.5-1-2.4c-0.2-0.9-0.3-2-0.3-3.1v-1.7c0-1.2,0.1-2.3,0.3-3.2
|
||||
c0.2-1,0.6-1.8,1.2-2.5c0.6-0.7,1.3-1.2,2.3-1.6c1-0.4,2.2-0.6,3.7-0.6c0.7,0,1.5,0.1,2.2,0.2c0.7,0.1,1.3,0.3,1.7,0.4l-0.4,1.8
|
||||
c-0.4-0.1-0.9-0.2-1.5-0.3C105.7,8.6,105.1,8.6,104.4,8.6z"/>
|
||||
<path class="st1" d="M128.2,23.9c-1.4,0-2.6-0.2-3.5-0.5c-0.9-0.3-1.6-0.8-2.1-1.5c-0.5-0.6-0.9-1.4-1.1-2.4
|
||||
c-0.2-1-0.3-2.1-0.3-3.3v-1.7c0-1.2,0.1-2.3,0.3-3.3c0.2-1,0.6-1.8,1.2-2.5c0.6-0.7,1.3-1.2,2.2-1.5c0.9-0.4,2.1-0.5,3.5-0.5
|
||||
c0.7,0,1.4,0.1,2,0.2s1.1,0.2,1.5,0.4L131.4,9c-0.4-0.1-0.8-0.2-1.3-0.3c-0.5-0.1-1-0.1-1.5-0.1c-0.9,0-1.7,0.1-2.3,0.3
|
||||
c-0.6,0.2-1.1,0.5-1.5,0.9c-0.4,0.4-0.7,1.1-0.9,1.8c-0.2,0.8-0.3,1.7-0.3,2.9v1.6c0,1.2,0.1,2.1,0.2,2.9c0.2,0.7,0.4,1.3,0.8,1.8
|
||||
c0.4,0.4,0.9,0.7,1.5,0.9c0.6,0.2,1.4,0.3,2.4,0.3c0.5,0,1,0,1.6-0.1c0.6-0.1,1.1-0.1,1.6-0.2l-0.4,1.9c-0.5,0.1-1,0.2-1.6,0.3
|
||||
C129.2,23.8,128.7,23.9,128.2,23.9z"/>
|
||||
<path class="st1" d="M135.7,14.2c0-1.2,0.1-2.2,0.3-3.2c0.2-0.9,0.5-1.8,1.1-2.4c0.5-0.7,1.2-1.2,2-1.6c0.8-0.4,1.9-0.5,3.2-0.5
|
||||
c1.3,0,2.4,0.2,3.2,0.5c0.8,0.4,1.5,0.9,2,1.5c0.5,0.7,0.8,1.5,1,2.4c0.2,1,0.3,2,0.3,3.2v2.1c0,1.2-0.1,2.2-0.3,3.2
|
||||
c-0.2,0.9-0.5,1.8-1.1,2.4c-0.5,0.7-1.2,1.2-2,1.6s-1.9,0.5-3.2,0.5c-1.3,0-2.4-0.2-3.2-0.5c-0.9-0.4-1.5-0.9-2-1.5
|
||||
c-0.5-0.7-0.8-1.5-1-2.4c-0.2-1-0.3-2-0.3-3.2V14.2z M146.4,14.2c0-1.2-0.1-2.1-0.2-2.9c-0.2-0.7-0.4-1.3-0.8-1.7
|
||||
c-0.3-0.4-0.8-0.7-1.3-0.9c-0.5-0.2-1.1-0.2-1.8-0.2c-0.7,0-1.3,0.1-1.8,0.2c-0.5,0.2-1,0.5-1.3,0.9c-0.3,0.4-0.6,1-0.8,1.8
|
||||
s-0.3,1.7-0.3,2.8v2.1c0,1.2,0.1,2.1,0.2,2.9c0.2,0.7,0.4,1.3,0.8,1.7c0.3,0.4,0.8,0.7,1.3,0.9c0.5,0.2,1.1,0.2,1.8,0.2
|
||||
c0.7,0,1.3-0.1,1.8-0.2c0.5-0.2,1-0.5,1.3-0.9c0.3-0.4,0.6-1,0.8-1.7c0.2-0.7,0.3-1.7,0.3-2.9V14.2z"/>
|
||||
<path class="st1" d="M156.3,6.8l7.9,13v-13h2.2v16.8h-2.1l-7.9-13v13h-2.2V6.8H156.3z"/>
|
||||
<path class="st1" d="M183.9,6.8l-0.3,2h-5.2v14.8h-2.3V8.8h-5.4l0.3-2H183.9z"/>
|
||||
<path class="st1" d="M198.7,6.8l-0.3,1.9h-7.9V14h7.4l-0.3,1.9h-7.1v5.7h8.2l-0.3,1.9h-10.3V6.8H198.7z"/>
|
||||
<path class="st1" d="M205.9,6.8l7.9,13v-13h2.2v16.8h-2.1l-7.9-13v13h-2.2V6.8H205.9z"/>
|
||||
<path class="st1" d="M233.4,6.8l-0.3,2H228v14.8h-2.3V8.8h-5.4l0.3-2H233.4z"/>
|
||||
<path class="st1" d="M236.3,23.8c-0.5,0-0.9-0.1-1.1-0.4c-0.2-0.3-0.4-0.6-0.4-1.1c0-0.5,0.1-0.8,0.4-1.1c0.2-0.3,0.6-0.4,1.1-0.4
|
||||
c0.5,0,0.9,0.1,1.1,0.4c0.2,0.3,0.4,0.6,0.4,1.1c0,0.5-0.1,0.8-0.4,1.1C237.2,23.7,236.9,23.8,236.3,23.8z"/>
|
||||
<path class="st1" d="M244.4,23.8c-0.5,0-0.9-0.1-1.1-0.4c-0.2-0.3-0.4-0.6-0.4-1.1c0-0.5,0.1-0.8,0.4-1.1c0.2-0.3,0.6-0.4,1.1-0.4
|
||||
c0.5,0,0.9,0.1,1.1,0.4c0.2,0.3,0.4,0.6,0.4,1.1c0,0.5-0.1,0.8-0.4,1.1C245.3,23.7,244.9,23.8,244.4,23.8z"/>
|
||||
<path class="st1" d="M252.4,23.8c-0.5,0-0.9-0.1-1.1-0.4c-0.2-0.3-0.4-0.6-0.4-1.1c0-0.5,0.1-0.8,0.4-1.1c0.2-0.3,0.6-0.4,1.1-0.4
|
||||
c0.5,0,0.9,0.1,1.1,0.4c0.2,0.3,0.4,0.6,0.4,1.1c0,0.5-0.1,0.8-0.4,1.1C253.3,23.7,253,23.8,252.4,23.8z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 10 KiB |
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 960 10" enable-background="new -159 536 960 30" xml:space="preserve">
|
||||
<defs>
|
||||
<linearGradient id="loadingGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:rgb(16,128,184);stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:rgb(0,180,239);stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="0" y="0" fill="url(#loadingGrad)" width="480" height="10"/>
|
||||
<rect x="480" y="0" fill="#000000" fill-opacity="0.8039" width="480" height="10"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 671 B |
|
@ -304,7 +304,7 @@ function setEnabled(value) {
|
|||
|
||||
var CHANNEL_AWAY_ENABLE = "Hifi-Away-Enable";
|
||||
var handleMessage = function(channel, message, sender) {
|
||||
if (channel === CHANNEL_AWAY_ENABLE) {
|
||||
if (channel === CHANNEL_AWAY_ENABLE && sender === MyAvatar.sessionUUID) {
|
||||
print("away.js | Got message on Hifi-Away-Enable: ", message);
|
||||
setEnabled(message === 'enable');
|
||||
}
|
||||
|
@ -344,6 +344,7 @@ Script.scriptEnding.connect(function () {
|
|||
Controller.mousePressEvent.disconnect(goActive);
|
||||
Controller.keyPressEvent.disconnect(maybeGoActive);
|
||||
Messages.messageReceived.disconnect(handleMessage);
|
||||
Messages.unsubscribe(CHANNEL_AWAY_ENABLE);
|
||||
});
|
||||
|
||||
if (HMD.active && !HMD.mounted) {
|
||||
|
|
|
@ -31,14 +31,24 @@ function resolveHardware(path) {
|
|||
return resolveInner(Controller.Hardware, parts, 0);
|
||||
}
|
||||
|
||||
var DEBUG = true;
|
||||
function debug() {
|
||||
if (DEBUG) {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.unshift("controllerDisplay.js | ");
|
||||
print.apply(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
createControllerDisplay = function(config) {
|
||||
var controllerDisplay = {
|
||||
overlays: [],
|
||||
partOverlays: {},
|
||||
parts: {},
|
||||
mappingName: "mapping-display",
|
||||
mappingName: "mapping-display-" + Math.random(),
|
||||
|
||||
setVisible: function(visible) {
|
||||
debug("Setting visible", this.overlays.length);
|
||||
for (var i = 0; i < this.overlays.length; ++i) {
|
||||
Overlays.editOverlay(this.overlays[i], {
|
||||
visible: visible
|
||||
|
@ -166,7 +176,7 @@ createControllerDisplay = function(config) {
|
|||
} else if (part.type === "static") {
|
||||
// do nothing
|
||||
} else {
|
||||
print("TYPE NOT SUPPORTED: ", part.type);
|
||||
debug("TYPE NOT SUPPORTED: ", part.type);
|
||||
}
|
||||
|
||||
controllerDisplay.overlays.push(overlayID);
|
||||
|
|
|
@ -69,7 +69,6 @@ ControllerDisplayManager = function() {
|
|||
}
|
||||
}
|
||||
|
||||
Messages.subscribe('Controller-Display');
|
||||
var handleMessages = function(channel, message, sender) {
|
||||
var i, data, name, visible;
|
||||
if (!controllerLeft && !controllerRight) {
|
||||
|
|
|
@ -62,6 +62,7 @@ var TIP_TEXTURE_BASE_URL = BASE_URL + "meshes/controller/vive_tips.fbm/";
|
|||
|
||||
var viveModelURL = BASE_URL + "meshes/controller/vive_body.fbx";
|
||||
var viveTipsModelURL = BASE_URL + "meshes/controller/vive_tips.fbx";
|
||||
var viveTriggerModelURL = "meshes/controller/vive_trigger.fbx"
|
||||
|
||||
VIVE_CONTROLLER_CONFIGURATION_LEFT = {
|
||||
name: "Vive",
|
||||
|
@ -86,7 +87,7 @@ VIVE_CONTROLLER_CONFIGURATION_LEFT = {
|
|||
defaultTextureLayer: "blank",
|
||||
textureLayers: {
|
||||
blank: {
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "Blank.png"
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Blank.png"
|
||||
},
|
||||
trigger: {
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Trigger.png"
|
||||
|
@ -141,7 +142,18 @@ VIVE_CONTROLLER_CONFIGURATION_LEFT = {
|
|||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
axis: { x: -1, y: 0, z: 0 },
|
||||
maxAngle: 20
|
||||
maxAngle: 25,
|
||||
|
||||
textureName: "Tex.black-trigger",
|
||||
defaultTextureLayer: "normal",
|
||||
textureLayers: {
|
||||
normal: {
|
||||
defaultTextureURL: BASE_URL + viveTriggerModelURL + "/Trigger.fbm/black.jpg",
|
||||
},
|
||||
highlight: {
|
||||
defaultTextureURL: BASE_URL + viveTriggerModelURL + "/Trigger.fbm/yellow.jpg",
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
l_grip: {
|
||||
|
@ -262,7 +274,18 @@ VIVE_CONTROLLER_CONFIGURATION_RIGHT = {
|
|||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
axis: { x: -1, y: 0, z: 0 },
|
||||
maxAngle: 25
|
||||
maxAngle: 25,
|
||||
|
||||
textureName: "Tex.black-trigger",
|
||||
defaultTextureLayer: "normal",
|
||||
textureLayers: {
|
||||
normal: {
|
||||
defaultTextureURL: BASE_URL + viveTriggerModelURL + "/Trigger.fbm/black.jpg",
|
||||
},
|
||||
highlight: {
|
||||
defaultTextureURL: BASE_URL + viveTriggerModelURL + "/Trigger.fbm/yellow.jpg",
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
l_grip: {
|
||||
|
|
|
@ -13,18 +13,14 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
(function () { // BEGIN LOCAL_SCOPE
|
||||
|
||||
function debug() {
|
||||
return;
|
||||
print.apply(null, arguments);
|
||||
//print.apply(null, arguments);
|
||||
}
|
||||
|
||||
var rawProgress = 100, // % raw value.
|
||||
displayProgress = 100, // % smoothed value to display.
|
||||
DISPLAY_PROGRESS_MINOR_MAXIMUM = 8, // % displayed progress bar goes up to while 0% raw progress.
|
||||
DISPLAY_PROGRESS_MINOR_INCREMENT = 0.1, // % amount to increment display value each update when 0% raw progress.
|
||||
DISPLAY_PROGRESS_MAJOR_INCREMENT = 5, // % maximum amount to increment display value when >0% raw progress.
|
||||
alpha = 0.0,
|
||||
alphaDelta = 0.0, // > 0 if fading in; < 0 if fading out.
|
||||
ALPHA_DELTA_IN = 0.15,
|
||||
|
@ -34,19 +30,60 @@
|
|||
fadeWaitTimer = null,
|
||||
FADE_OUT_WAIT = 1000, // Wait before starting to fade out after progress 100%.
|
||||
visible = false,
|
||||
BAR_WIDTH = 480, // Dimension of SVG in pixels of visible portion (half) of the bar.
|
||||
BAR_HEIGHT = 10,
|
||||
BAR_Y_OFFSET_2D = -10, // Offset of progress bar while in desktop mode
|
||||
BAR_Y_OFFSET_HMD = -300, // Offset of progress bar while in HMD
|
||||
BAR_URL = Script.resolvePath("assets/images/progress-bar.svg"),
|
||||
BACKGROUND_WIDTH = 520,
|
||||
BACKGROUND_HEIGHT = 50,
|
||||
BACKGROUND_URL = Script.resolvePath("assets/images/progress-bar-background.svg"),
|
||||
|
||||
BAR_DESKTOP_2K_WIDTH = 2240, // Width of SVG image in pixels. Sized for 1920 x 1080 display with 6 visible repeats.
|
||||
BAR_DESKTOP_2K_REPEAT = 320, // Length of repeat in bar = 2240 / 7.
|
||||
BAR_DESKTOP_2K_HEIGHT = 3, // Display height of SVG
|
||||
BAR_DESKTOP_2K_URL = Script.resolvePath("assets/images/progress-bar-2k.svg"),
|
||||
|
||||
BAR_DESKTOP_4K_WIDTH = 4480, // Width of SVG image in pixels. Sized for 1920 x 1080 display with 6 visible repeats.
|
||||
BAR_DESKTOP_4K_REPEAT = 640, // Length of repeat in bar = 2240 / 7.
|
||||
BAR_DESKTOP_4K_HEIGHT = 6, // Display height of SVG
|
||||
BAR_DESKTOP_4K_URL = Script.resolvePath("assets/images/progress-bar-4k.svg"),
|
||||
|
||||
BAR_HMD_WIDTH = 2240, // Desktop image works with HMD well.
|
||||
BAR_HMD_REPEAT = 320,
|
||||
BAR_HMD_HEIGHT = 3,
|
||||
BAR_HMD_URL = Script.resolvePath("assets/images/progress-bar-2k.svg"),
|
||||
|
||||
BAR_Y_OFFSET_DESKTOP = 0, // Offset of progress bar while in desktop mode
|
||||
BAR_Y_OFFSET_HMD = -100, // Offset of progress bar while in HMD
|
||||
|
||||
ANIMATION_SECONDS_PER_REPEAT = 4, // Speed of bar animation
|
||||
|
||||
TEXT_HEIGHT = 32,
|
||||
TEXT_WIDTH = 256,
|
||||
TEXT_URL = Script.resolvePath("assets/images/progress-bar-text.svg"),
|
||||
windowWidth = 0,
|
||||
windowHeight = 0,
|
||||
background2D = {},
|
||||
bar2D = {},
|
||||
SCALE_2D = 0.35; // Scale the SVGs for 2D display.
|
||||
barDesktop = {},
|
||||
barHMD = {},
|
||||
textDesktop = {}, // Separate desktop and HMD overlays because can't change text size after overlay created.
|
||||
textHMD = {},
|
||||
SCALE_TEXT_DESKTOP = 0.6,
|
||||
SCALE_TEXT_HMD = 1.0,
|
||||
isHMD = false,
|
||||
|
||||
// Max seen since downloads started. This is reset when all downloads have completed.
|
||||
maxSeen = 0,
|
||||
|
||||
// Progress is defined as: (pending_downloads + active_downloads) / max_seen
|
||||
// We keep track of both the current progress (rawProgress) and the
|
||||
// best progress we've seen (bestRawProgress). As you are downloading, you may
|
||||
// encounter new assets that require downloads, increasing the number of
|
||||
// pending downloads and thus decreasing your overall progress.
|
||||
bestRawProgress = 0,
|
||||
|
||||
// True if we have known active downloads
|
||||
isDownloading = false,
|
||||
|
||||
// Entities are streamed to users, so you don't receive them all at once; instead, you
|
||||
// receive them over a period of time. In many cases we end up in a situation where
|
||||
//
|
||||
// The initial delay cooldown keeps us from tracking progress before the allotted time
|
||||
// has passed.
|
||||
INITIAL_DELAY_COOLDOWN_TIME = 1000,
|
||||
initialDelayCooldown = 0;
|
||||
|
||||
function fade() {
|
||||
|
||||
|
@ -67,45 +104,32 @@
|
|||
visible = false;
|
||||
}
|
||||
|
||||
Overlays.editOverlay(background2D.overlay, {
|
||||
Overlays.editOverlay(barDesktop.overlay, {
|
||||
alpha: alpha,
|
||||
visible: visible
|
||||
visible: visible && !isHMD
|
||||
});
|
||||
Overlays.editOverlay(bar2D.overlay, {
|
||||
Overlays.editOverlay(barHMD.overlay, {
|
||||
alpha: alpha,
|
||||
visible: visible
|
||||
visible: visible && isHMD
|
||||
});
|
||||
Overlays.editOverlay(textDesktop.overlay, {
|
||||
alpha: alpha,
|
||||
visible: visible && !isHMD
|
||||
});
|
||||
Overlays.editOverlay(textHMD.overlay, {
|
||||
alpha: alpha,
|
||||
visible: visible && isHMD
|
||||
});
|
||||
}
|
||||
|
||||
Window.domainChanged.connect(function() {
|
||||
Window.domainChanged.connect(function () {
|
||||
isDownloading = false;
|
||||
bestRawProgress = 100;
|
||||
rawProgress = 100;
|
||||
displayProgress = 100;
|
||||
});
|
||||
|
||||
// Max seen since downloads started. This is reset when all downloads have completed.
|
||||
var maxSeen = 0;
|
||||
|
||||
// Progress is defined as: (pending_downloads + active_downloads) / max_seen
|
||||
// We keep track of both the current progress (rawProgress) and the
|
||||
// best progress we've seen (bestRawProgress). As you are downloading, you may
|
||||
// encounter new assets that require downloads, increasing the number of
|
||||
// pending downloads and thus decreasing your overall progress.
|
||||
var bestRawProgress = 0;
|
||||
|
||||
// True if we have known active downloads
|
||||
var isDownloading = false;
|
||||
|
||||
// Entities are streamed to users, so you don't receive them all at once; instead, you
|
||||
// receive them over a period of time. In many cases we end up in a situation where
|
||||
//
|
||||
// The initial delay cooldown keeps us from tracking progress before the allotted time
|
||||
// has passed.
|
||||
var INITIAL_DELAY_COOLDOWN_TIME = 1000;
|
||||
var initialDelayCooldown = 0;
|
||||
function onDownloadInfoChanged(info) {
|
||||
var i;
|
||||
|
||||
debug("PROGRESS: Download info changed ", info.downloading.length, info.pending, maxSeen);
|
||||
|
||||
|
@ -140,43 +164,96 @@
|
|||
}
|
||||
|
||||
function createOverlays() {
|
||||
background2D.overlay = Overlays.addOverlay("image", {
|
||||
imageURL: BACKGROUND_URL,
|
||||
width: background2D.width,
|
||||
height: background2D.height,
|
||||
visible: false,
|
||||
alpha: 0.0
|
||||
});
|
||||
bar2D.overlay = Overlays.addOverlay("image", {
|
||||
imageURL: BAR_URL,
|
||||
barDesktop.overlay = Overlays.addOverlay("image", {
|
||||
imageURL: barDesktop.url,
|
||||
subImage: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: BAR_WIDTH,
|
||||
height: BAR_HEIGHT
|
||||
width: barDesktop.width - barDesktop.repeat,
|
||||
height: barDesktop.height
|
||||
},
|
||||
width: bar2D.width,
|
||||
height: bar2D.height,
|
||||
width: barDesktop.width,
|
||||
height: barDesktop.height,
|
||||
visible: false,
|
||||
alpha: 0.0
|
||||
});
|
||||
barHMD.overlay = Overlays.addOverlay("image", {
|
||||
imageURL: BAR_HMD_URL,
|
||||
subImage: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: BAR_HMD_WIDTH - BAR_HMD_REPEAT,
|
||||
height: BAR_HMD_HEIGHT
|
||||
},
|
||||
width: barHMD.width,
|
||||
height: barHMD.height,
|
||||
visible: false,
|
||||
alpha: 0.0
|
||||
});
|
||||
textDesktop.overlay = Overlays.addOverlay("image", {
|
||||
imageURL: TEXT_URL,
|
||||
width: textDesktop.width,
|
||||
height: textDesktop.height,
|
||||
visible: false,
|
||||
alpha: 0.0
|
||||
});
|
||||
textHMD.overlay = Overlays.addOverlay("image", {
|
||||
imageURL: TEXT_URL,
|
||||
width: textHMD.width,
|
||||
height: textHMD.height,
|
||||
visible: false,
|
||||
alpha: 0.0
|
||||
});
|
||||
}
|
||||
|
||||
function deleteOverlays() {
|
||||
Overlays.deleteOverlay(background2D.overlay);
|
||||
Overlays.deleteOverlay(bar2D.overlay);
|
||||
Overlays.deleteOverlay(barDesktop.overlay);
|
||||
Overlays.deleteOverlay(barHMD.overlay);
|
||||
Overlays.deleteOverlay(textDesktop.overlay);
|
||||
Overlays.deleteOverlay(textHMD.overlay);
|
||||
}
|
||||
|
||||
function updateProgressBarLocation() {
|
||||
var viewport = Controller.getViewportDimensions();
|
||||
|
||||
windowWidth = viewport.x;
|
||||
windowHeight = viewport.y;
|
||||
isHMD = HMD.active;
|
||||
|
||||
if (isHMD) {
|
||||
|
||||
Overlays.editOverlay(barHMD.overlay, {
|
||||
x: windowWidth / 2 - barHMD.width / 2,
|
||||
y: windowHeight - 2 * barHMD.height + BAR_Y_OFFSET_HMD
|
||||
});
|
||||
|
||||
Overlays.editOverlay(textHMD.overlay, {
|
||||
x: windowWidth / 2 - textHMD.width / 2,
|
||||
y: windowHeight - 2 * barHMD.height - textHMD.height + BAR_Y_OFFSET_HMD
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
Overlays.editOverlay(barDesktop.overlay, {
|
||||
x: windowWidth / 2 - barDesktop.width / 2,
|
||||
y: windowHeight - 2 * barDesktop.height + BAR_Y_OFFSET_DESKTOP,
|
||||
width: barDesktop.width
|
||||
});
|
||||
|
||||
Overlays.editOverlay(textDesktop.overlay, {
|
||||
x: windowWidth / 2 - textDesktop.width / 2,
|
||||
y: windowHeight - 2 * barDesktop.height - textDesktop.height + BAR_Y_OFFSET_DESKTOP
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var b = 0;
|
||||
var currentOrientation = null;
|
||||
function update() {
|
||||
var viewport, diff, x;
|
||||
|
||||
initialDelayCooldown -= 30;
|
||||
var viewport,
|
||||
eyePosition,
|
||||
avatarOrientation;
|
||||
|
||||
if (displayProgress < rawProgress) {
|
||||
var diff = rawProgress - displayProgress;
|
||||
diff = rawProgress - displayProgress;
|
||||
if (diff < 0.5) {
|
||||
displayProgress = rawProgress;
|
||||
} else {
|
||||
|
@ -204,7 +281,7 @@
|
|||
} else { // Fully visible because downloading or recently so
|
||||
if (fadeWaitTimer === null) {
|
||||
if (rawProgress === 100) { // Was downloading but have finished so fade out soon
|
||||
fadeWaitTimer = Script.setTimeout(function() {
|
||||
fadeWaitTimer = Script.setTimeout(function () {
|
||||
alphaDelta = ALPHA_DELTA_OUT;
|
||||
fadeTimer = Script.setInterval(fade, FADE_INTERVAL);
|
||||
fadeWaitTimer = null;
|
||||
|
@ -219,59 +296,67 @@
|
|||
}
|
||||
|
||||
if (visible) {
|
||||
x = ((Date.now() / 1000) % ANIMATION_SECONDS_PER_REPEAT) / ANIMATION_SECONDS_PER_REPEAT;
|
||||
if (isHMD) {
|
||||
x = x * barDesktop.repeat;
|
||||
} else {
|
||||
x = x * BAR_HMD_REPEAT;
|
||||
}
|
||||
|
||||
// Update progress bar
|
||||
Overlays.editOverlay(bar2D.overlay, {
|
||||
visible: true,
|
||||
Overlays.editOverlay(barDesktop.overlay, {
|
||||
visible: !isHMD,
|
||||
subImage: {
|
||||
x: BAR_WIDTH * (1 - displayProgress / 100),
|
||||
x: barDesktop.repeat - x,
|
||||
y: 0,
|
||||
width: BAR_WIDTH,
|
||||
height: BAR_HEIGHT
|
||||
},
|
||||
width: barDesktop.width - barDesktop.repeat,
|
||||
height: barDesktop.height
|
||||
}
|
||||
});
|
||||
|
||||
Overlays.editOverlay(background2D.overlay, {
|
||||
visible: true,
|
||||
Overlays.editOverlay(barHMD.overlay, {
|
||||
visible: isHMD,
|
||||
subImage: {
|
||||
x: BAR_HMD_REPEAT - x,
|
||||
y: 0,
|
||||
width: BAR_HMD_WIDTH - BAR_HMD_REPEAT,
|
||||
height: BAR_HMD_HEIGHT
|
||||
}
|
||||
});
|
||||
|
||||
Overlays.editOverlay(textDesktop.overlay, {
|
||||
visible: !isHMD
|
||||
});
|
||||
|
||||
Overlays.editOverlay(textHMD.overlay, {
|
||||
visible: isHMD
|
||||
});
|
||||
|
||||
// Update 2D overlays to maintain positions at bottom middle of window
|
||||
viewport = Controller.getViewportDimensions();
|
||||
|
||||
if (viewport.x !== windowWidth || viewport.y !== windowHeight) {
|
||||
if (viewport.x !== windowWidth || viewport.y !== windowHeight || isHMD !== HMD.active) {
|
||||
updateProgressBarLocation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateProgressBarLocation() {
|
||||
var viewport = Controller.getViewportDimensions();
|
||||
windowWidth = viewport.x;
|
||||
windowHeight = viewport.y;
|
||||
|
||||
var yOffset = HMD.active ? BAR_Y_OFFSET_HMD : BAR_Y_OFFSET_2D;
|
||||
|
||||
background2D.width = SCALE_2D * BACKGROUND_WIDTH;
|
||||
background2D.height = SCALE_2D * BACKGROUND_HEIGHT;
|
||||
bar2D.width = SCALE_2D * BAR_WIDTH;
|
||||
bar2D.height = SCALE_2D * BAR_HEIGHT;
|
||||
|
||||
Overlays.editOverlay(background2D.overlay, {
|
||||
x: windowWidth / 2 - background2D.width / 2,
|
||||
y: windowHeight - background2D.height - bar2D.height + yOffset
|
||||
});
|
||||
|
||||
Overlays.editOverlay(bar2D.overlay, {
|
||||
x: windowWidth / 2 - bar2D.width / 2,
|
||||
y: windowHeight - background2D.height - bar2D.height + (background2D.height - bar2D.height) / 2 + yOffset
|
||||
});
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
background2D.width = SCALE_2D * BACKGROUND_WIDTH;
|
||||
background2D.height = SCALE_2D * BACKGROUND_HEIGHT;
|
||||
bar2D.width = SCALE_2D * BAR_WIDTH;
|
||||
bar2D.height = SCALE_2D * BAR_HEIGHT;
|
||||
var is4k = Window.innerWidth > 3000;
|
||||
|
||||
isHMD = HMD.active;
|
||||
|
||||
barDesktop.width = is4k ? BAR_DESKTOP_4K_WIDTH - BAR_DESKTOP_4K_REPEAT : BAR_DESKTOP_2K_WIDTH - BAR_DESKTOP_2K_REPEAT;
|
||||
barDesktop.height = is4k ? BAR_DESKTOP_4K_HEIGHT : BAR_DESKTOP_2K_HEIGHT;
|
||||
barDesktop.repeat = is4k ? BAR_DESKTOP_4K_REPEAT : BAR_DESKTOP_2K_REPEAT;
|
||||
barDesktop.url = is4k ? BAR_DESKTOP_4K_URL : BAR_DESKTOP_2K_URL;
|
||||
barHMD.width = BAR_HMD_WIDTH - BAR_HMD_REPEAT;
|
||||
barHMD.height = BAR_HMD_HEIGHT;
|
||||
|
||||
textDesktop.width = SCALE_TEXT_DESKTOP * TEXT_WIDTH;
|
||||
textDesktop.height = SCALE_TEXT_DESKTOP * TEXT_HEIGHT;
|
||||
textHMD.width = SCALE_TEXT_HMD * TEXT_WIDTH;
|
||||
textHMD.height = SCALE_TEXT_HMD * TEXT_HEIGHT;
|
||||
|
||||
createOverlays();
|
||||
}
|
||||
|
@ -283,7 +368,7 @@
|
|||
setUp();
|
||||
GlobalServices.downloadInfoChanged.connect(onDownloadInfoChanged);
|
||||
GlobalServices.updateDownloadInfo();
|
||||
Script.setInterval(update, 1000/60);
|
||||
Script.setInterval(update, 1000 / 60);
|
||||
Script.scriptEnding.connect(tearDown);
|
||||
|
||||
}()); // END LOCAL_SCOPE
|
||||
|
|
|
@ -812,7 +812,8 @@ for (var key in trayIcons) {
|
|||
const notificationIcon = path.join(__dirname, '../resources/console-notification.png');
|
||||
|
||||
function onContentLoaded() {
|
||||
maybeShowSplash();
|
||||
// Disable splash window for now.
|
||||
// maybeShowSplash();
|
||||
|
||||
if (buildInfo.releaseType == 'PRODUCTION') {
|
||||
var currentVersion = null;
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
birdFirework1 = {
|
||||
"clientOnly": 0,
|
||||
"collisionsWillMove": 1,
|
||||
"created": "2016-09-13T23:05:08Z",
|
||||
"dimensions": {
|
||||
"x": 0.10120716691017151,
|
||||
"y": 0.12002291530370712,
|
||||
"z": 0.18833979964256287
|
||||
},
|
||||
fireworkURLs = [
|
||||
"atp:/tutorial_models/bomb1.fbx",
|
||||
"atp:/tutorial_models/bomb2.fbx",
|
||||
"atp:/tutorial_models/bomb3.fbx",
|
||||
"atp:/tutorial_models/bomb4.fbx",
|
||||
"atp:/tutorial_models/bomb5.fbx",
|
||||
"atp:/tutorial_models/bomb6.fbx",
|
||||
];
|
||||
|
||||
fireworkBaseProps = {
|
||||
"collisionsWillMove": 1,
|
||||
velocity: {
|
||||
x: 0,
|
||||
|
@ -20,7 +21,7 @@ birdFirework1 = {
|
|||
"z": 0
|
||||
},
|
||||
"id": "{1c4061bc-b2e7-4435-bc47-3fcc39ae6624}",
|
||||
"modelURL": "atp:/tutorial_models/birdStatue15.fbx",
|
||||
"modelURL": "atp:/tutorial_models/bomb1.fbx",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"position": {
|
||||
"x": 0.11612319946289062,
|
||||
|
@ -44,15 +45,8 @@ birdFirework1 = {
|
|||
"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,
|
||||
|
@ -66,7 +60,7 @@ birdFirework2 = {
|
|||
"z": 0
|
||||
},
|
||||
"id": "{ba067084-8d0f-4eeb-a8a1-c6814527c1bb}",
|
||||
"modelURL": "atp:/tutorial_models/statuebird4.fbx",
|
||||
"modelURL": "atp:/tutorial_models/bomb2.fbx",
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"position": {
|
||||
"x": 0,
|
||||
|
|
|
@ -4,6 +4,12 @@
|
|||
|
||||
(function() {
|
||||
|
||||
function debug() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.unshift("fire.js | ");
|
||||
print.apply(this, args);
|
||||
}
|
||||
|
||||
var _this = this;
|
||||
|
||||
function Fire() {
|
||||
|
@ -54,30 +60,45 @@
|
|||
|
||||
var colors = [RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET];
|
||||
|
||||
var firePitSoundURL = Script.resolvePath("fire_burst.wav");
|
||||
debug("Firepit burst sound url is: ", firePitSoundURL);
|
||||
|
||||
var explodeTextureURL = Script.resolvePath("explode.png");
|
||||
debug("Firepit explode texture url is: ", explodeTextureURL);
|
||||
|
||||
Fire.prototype = {
|
||||
preload: function(entityID) {
|
||||
debug("Preload");
|
||||
this.entityID = entityID;
|
||||
this.EXPLOSION_SOUND = SoundCache.getSound("atp:/firepit/fire_burst.wav");
|
||||
this.EXPLOSION_SOUND = SoundCache.getSound(firePitSoundURL);
|
||||
},
|
||||
collisionWithEntity: function(myID, otherID, collisionInfo) {
|
||||
debug("Collided with entity: ", myID, otherID);
|
||||
var otherProps = Entities.getEntityProperties(otherID);
|
||||
var data = null;
|
||||
try {
|
||||
data = JSON.parse(otherProps.userData)
|
||||
data = JSON.parse(otherProps.userData);
|
||||
} catch (err) {
|
||||
print('ERROR GETTING USERDATA!');
|
||||
debug('ERROR GETTING USERDATA!');
|
||||
}
|
||||
if (data === null || "") {
|
||||
debug("Data is null or empty", data);
|
||||
return;
|
||||
} else {
|
||||
debug("Got data", data);
|
||||
if (data.hasOwnProperty('hifiHomeKey')) {
|
||||
debug("Has hifiHomeKey");
|
||||
if (data.hifiHomeKey.reset === true) {
|
||||
debug("Reset is true");
|
||||
_this.playSoundAtCurrentPosition();
|
||||
_this.explodeWithColor();
|
||||
Entities.deleteEntity(otherID)
|
||||
debug("Sending local message");
|
||||
Messages.sendLocalMessage('Entity-Exploded', JSON.stringify({
|
||||
entityID: otherID,
|
||||
position: Entities.getEntityProperties(this.entityID).position
|
||||
}));
|
||||
debug("Done sending local message");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -137,7 +158,7 @@
|
|||
"alphaStart": -0.2,
|
||||
"alphaFinish": 0.5,
|
||||
"emitterShouldTrail": 0,
|
||||
"textures": "atp:/firepit/explode.png",
|
||||
"textures": explodeTextureURL,
|
||||
"type": "ParticleEffect",
|
||||
lifetime: 1,
|
||||
position: myProps.position
|
||||
|
|
|
@ -11,13 +11,17 @@
|
|||
(function() {
|
||||
Script.include('utils.js');
|
||||
|
||||
var DEBUG = false;
|
||||
var DEBUG = true;
|
||||
function debug() {
|
||||
if (DEBUG) {
|
||||
print.apply(self, arguments);
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.unshift("fuse.js | ");
|
||||
print.apply(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
var active = false;
|
||||
|
||||
var fuseSound = SoundCache.getSound("atp:/tutorial_sounds/fuse.wav");
|
||||
function getChildProperties(entityID, propertyNames) {
|
||||
var childEntityIDs = Entities.getChildrenIDs(entityID);
|
||||
|
@ -33,12 +37,20 @@
|
|||
};
|
||||
Fuse.prototype = {
|
||||
light: function() {
|
||||
debug("LIT", this.entityID);
|
||||
var anim = Entities.getEntityProperties(this.entityID, ['animation']).animation;
|
||||
debug("Received light()", this.entityID);
|
||||
|
||||
if (anim.currentFrame < 140) {
|
||||
var visible = Entities.getEntityProperties(this.entityID, ['visible']).visible;
|
||||
if (!visible) {
|
||||
debug("Fuse is not visible, returning");
|
||||
return;
|
||||
}
|
||||
|
||||
if (active) {
|
||||
debug("Fuse is active, returning");
|
||||
return;
|
||||
}
|
||||
active = true;
|
||||
|
||||
Entities.editEntity(this.entityID, {
|
||||
animation: {
|
||||
currentFrame: 1,
|
||||
|
@ -56,6 +68,7 @@
|
|||
|
||||
var childrenProps = getChildProperties(this.entityID, ['type']);
|
||||
for (var childEntityID in childrenProps) {
|
||||
debug("Updating: ", childEntityID);
|
||||
var props = childrenProps[childEntityID];
|
||||
if (props.type == "ParticleEffect") {
|
||||
Entities.editEntity(childEntityID, {
|
||||
|
@ -70,13 +83,14 @@
|
|||
|
||||
var self = this;
|
||||
Script.setTimeout(function() {
|
||||
debug("BLOW UP");
|
||||
var spinnerID = Utils.findEntity({ name: "tutorial/equip/spinner" }, 20);
|
||||
debug("Setting off fireworks");
|
||||
var spinnerID = "{dd13fcd5-616f-4749-ab28-2e1e8bc512e9}";
|
||||
Entities.callEntityMethod(spinnerID, "onLit");
|
||||
injector.stop();
|
||||
|
||||
var childrenProps = getChildProperties(self.entityID, ['type']);
|
||||
for (var childEntityID in childrenProps) {
|
||||
debug("Updating: ", childEntityID);
|
||||
var props = childrenProps[childEntityID];
|
||||
if (props.type == "ParticleEffect") {
|
||||
Entities.editEntity(childEntityID, {
|
||||
|
@ -90,8 +104,14 @@
|
|||
}
|
||||
|
||||
}, 4900);
|
||||
|
||||
Script.setTimeout(function() {
|
||||
debug("Setting fuse to inactive");
|
||||
active = false;
|
||||
}, 14000);
|
||||
},
|
||||
preload: function(entityID) {
|
||||
debug("Preload");
|
||||
this.entityID = entityID;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -5,11 +5,12 @@
|
|||
};
|
||||
Fuse.prototype = {
|
||||
onLit: function() {
|
||||
print("LIT", this.entityID);
|
||||
var fuseID = Utils.findEntity({ name: "tutorial/equip/fuse" }, 20);
|
||||
print("fuseCollider.js | Lit", this.entityID);
|
||||
var fuseID = "{c8944a13-9acb-4d77-b1ee-851845e98357}"
|
||||
Entities.callEntityMethod(fuseID, "light");
|
||||
},
|
||||
preload: function(entityID) {
|
||||
print("fuseCollider.js | preload");
|
||||
this.entityID = entityID;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -53,33 +53,39 @@ createButaneLighter = function(transform) {
|
|||
shapeType: 'simple-compound',
|
||||
type: 'Model',
|
||||
userData: JSON.stringify({
|
||||
tag: "equip-temporary",
|
||||
grabbableKey: {
|
||||
invertSolidWhileHeld: true
|
||||
"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
|
||||
}]
|
||||
}
|
||||
"wearable": {
|
||||
"joints": {
|
||||
"RightHand": [
|
||||
{
|
||||
"x": 0.049671292304992676,
|
||||
"y": 0.09825992584228516,
|
||||
"z": 0.03760027885437012
|
||||
},
|
||||
{
|
||||
"x": 0.6562752723693848,
|
||||
"y": 0.27598991990089417,
|
||||
"z": 0.6638742685317993,
|
||||
"w": -0.22890058159828186
|
||||
}
|
||||
],
|
||||
"LeftHand": [
|
||||
{
|
||||
"x": -0.028073370456695557,
|
||||
"y": 0.09609812498092651,
|
||||
"z": 0.039550721645355225
|
||||
},
|
||||
{
|
||||
"x": -0.6697965264320374,
|
||||
"y": 0.22050897777080536,
|
||||
"z": 0.6544681191444397,
|
||||
"w": 0.27283111214637756
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}),
|
||||
script: SCRIPT_URL
|
||||
|
|
|
@ -81,7 +81,6 @@ 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
|
||||
|
@ -160,7 +159,7 @@ OwnershipToken.prototype = {
|
|||
var ownerID = getOwnershipTokenID(this.parentEntityID);
|
||||
if (ownerID !== null) {
|
||||
// Already owned, return
|
||||
debug(this.name, "Token already owned by another client, return");
|
||||
debug(this.name, "Token already owned by another client, returning. Owner: " + owenerID + ", Us: " + this.name);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -185,3 +184,5 @@ OwnershipToken.prototype = {
|
|||
Script.setTimeout(checkOwnershipRequest.bind(this), 2000);
|
||||
},
|
||||
};
|
||||
|
||||
debug("Returning from ownershipToken");
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
//
|
||||
|
||||
(function() {
|
||||
var DEBUG = false;
|
||||
var DEBUG = true;
|
||||
function debug() {
|
||||
if (DEBUG) {
|
||||
print.apply(self, arguments);
|
||||
print.apply(this, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@
|
|||
}
|
||||
Spinner.prototype = {
|
||||
onLit: function() {
|
||||
debug("LIT SPINNER", this.entityID);
|
||||
debug("spinner.js | Spinner lit");
|
||||
Entities.editEntity(this.entityID, {
|
||||
"angularDamping": 0.1,
|
||||
"angularVelocity": {
|
||||
|
@ -50,6 +50,7 @@
|
|||
for (var childEntityID in childrenProps) {
|
||||
var props = childrenProps[childEntityID];
|
||||
if (props.type == "ParticleEffect") {
|
||||
debug("spinner.js | Modifying: ", childEntityID);
|
||||
Entities.editEntity(childEntityID, {
|
||||
emitRate: 35,
|
||||
});
|
||||
|
@ -59,13 +60,14 @@
|
|||
|
||||
var self = this;
|
||||
Script.setTimeout(function() {
|
||||
debug("BLOW UP");
|
||||
debug("spinner.js | Finishing spinner");
|
||||
injector.stop();
|
||||
|
||||
var childrenProps = getChildProperties(self.entityID, ['type']);
|
||||
for (var childEntityID in childrenProps) {
|
||||
var props = childrenProps[childEntityID];
|
||||
if (props.type == "ParticleEffect") {
|
||||
debug("spinner.js | Modifying: ", childEntityID);
|
||||
Entities.editEntity(childEntityID, {
|
||||
emitRate: 0,
|
||||
});
|
||||
|
@ -74,6 +76,7 @@
|
|||
}, 4900);
|
||||
},
|
||||
preload: function(entityID) {
|
||||
debug("spinner.js | Preload");
|
||||
this.entityID = entityID;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -32,7 +32,7 @@ if (!Function.prototype.bind) {
|
|||
|
||||
if (this.prototype) {
|
||||
// Function.prototype doesn't have a prototype property
|
||||
fNOP.prototype = this.prototype;
|
||||
fNOP.prototype = this.prototype;
|
||||
}
|
||||
fBound.prototype = new fNOP();
|
||||
|
||||
|
@ -40,26 +40,36 @@ if (!Function.prototype.bind) {
|
|||
};
|
||||
}
|
||||
|
||||
var DEBUG = false;
|
||||
var DEBUG = true;
|
||||
function debug() {
|
||||
if (DEBUG) {
|
||||
print.apply(this, arguments);
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.unshift("tutorial.js | ");
|
||||
print.apply(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
var INFO = true;
|
||||
function info() {
|
||||
if (INFO) {
|
||||
print.apply(this, arguments);
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.unshift("tutorial.js | ");
|
||||
print.apply(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
// Return a number between min (inclusive) and max (exclusive)
|
||||
function randomInt(min, max) {
|
||||
return min + Math.floor(Math.random() * (max - min))
|
||||
}
|
||||
|
||||
var NEAR_BOX_SPAWN_NAME = "tutorial/nearGrab/box_spawn";
|
||||
var FAR_BOX_SPAWN_NAME = "tutorial/farGrab/box_spawn";
|
||||
var GUN_SPAWN_NAME = "tutorial/gun_spawn";
|
||||
var TELEPORT_PAD_NAME = "tutorial/teleport/pad"
|
||||
|
||||
var successSound = SoundCache.getSound("atp:/tutorial_sounds/good_one.L.wav");
|
||||
var firecrackerSound = SoundCache.getSound("atp:/tutorial_sounds/Pops_Firecracker.wav");
|
||||
|
||||
|
||||
var CHANNEL_AWAY_ENABLE = "Hifi-Away-Enable";
|
||||
|
@ -106,14 +116,6 @@ findEntities = function(properties, searchRadius, filterFn) {
|
|||
return matchedEntities;
|
||||
}
|
||||
|
||||
function setControllerVisible(name, visible) {
|
||||
return;
|
||||
Messages.sendLocalMessage('Controller-Display', JSON.stringify({
|
||||
name: name,
|
||||
visible: visible,
|
||||
}));
|
||||
}
|
||||
|
||||
function setControllerPartsVisible(parts) {
|
||||
Messages.sendLocalMessage('Controller-Display-Parts', JSON.stringify(parts));
|
||||
}
|
||||
|
@ -191,12 +193,17 @@ function deleteEntitiesWithTag(tag) {
|
|||
}
|
||||
}
|
||||
function editEntitiesWithTag(tag, propertiesOrFn) {
|
||||
var entityIDs = findEntitiesWithTag(tag);
|
||||
for (var i = 0; i < entityIDs.length; ++i) {
|
||||
if (isFunction(propertiesOrFn)) {
|
||||
Entities.editEntity(entityIDs[i], propertiesOrFn(entityIDs[i]));
|
||||
} else {
|
||||
Entities.editEntity(entityIDs[i], propertiesOrFn);
|
||||
var entities = TUTORIAL_TAG_TO_ENTITY_IDS_MAP[tag];
|
||||
|
||||
debug("Editing tag: ", tag);
|
||||
if (entities) {
|
||||
for (entityID in entities) {
|
||||
debug("Editing: ", entityID, ", ", propertiesOrFn, ", Is in local tree: ", isEntityInLocalTree(entityID));
|
||||
if (isFunction(propertiesOrFn)) {
|
||||
Entities.editEntity(entityID, propertiesOrFn(entityIDs[i]));
|
||||
} else {
|
||||
Entities.editEntity(entityID, propertiesOrFn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -205,7 +212,7 @@ function findEntitiesWithTag(tag) {
|
|||
return findEntities({ userData: "" }, 10000, function(properties, key, value) {
|
||||
data = parseJSON(value);
|
||||
return data.tag == tag;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// From http://stackoverflow.com/questions/5999998/how-can-i-check-if-a-javascript-variable-is-function-type
|
||||
|
@ -222,21 +229,30 @@ function playSuccessSound() {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
function playFirecrackerSound(position) {
|
||||
Audio.playSound(firecrackerSound, {
|
||||
position: position,
|
||||
volume: 0.7,
|
||||
loop: false
|
||||
});
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// STEP: DISABLE CONTROLLERS //
|
||||
// //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
var stepDisableControllers = function(name) {
|
||||
var stepStart = function(name) {
|
||||
this.tag = name;
|
||||
this.shouldLog = false;
|
||||
}
|
||||
stepDisableControllers.prototype = {
|
||||
stepStart.prototype = {
|
||||
start: function(onFinish) {
|
||||
HMD.requestShowHandControllers();
|
||||
disableEverything();
|
||||
|
||||
HMD.requestShowHandControllers();
|
||||
|
||||
onFinish();
|
||||
},
|
||||
cleanup: function() {
|
||||
|
@ -258,6 +274,7 @@ function disableEverything() {
|
|||
setControllerPartLayer('tips', 'blank');
|
||||
|
||||
hideEntitiesWithTag('finish');
|
||||
|
||||
setAwayEnabled(false);
|
||||
}
|
||||
|
||||
|
@ -275,7 +292,6 @@ function reenableEverything() {
|
|||
setControllerPartLayer('touchpad', 'blank');
|
||||
setControllerPartLayer('tips', 'blank');
|
||||
MyAvatar.shouldRenderLocally = true;
|
||||
HMD.requestHideHandControllers();
|
||||
setAwayEnabled(true);
|
||||
}
|
||||
|
||||
|
@ -293,6 +309,7 @@ var stepEnableControllers = function(name) {
|
|||
stepEnableControllers.prototype = {
|
||||
start: function(onFinish) {
|
||||
reenableEverything();
|
||||
HMD.requestHideHandControllers();
|
||||
onFinish();
|
||||
},
|
||||
cleanup: function() {
|
||||
|
@ -340,13 +357,11 @@ stepOrient.prototype = {
|
|||
var tag = this.tag;
|
||||
|
||||
// Spawn content set
|
||||
debug("raise hands...", this.tag);
|
||||
editEntitiesWithTag(this.tag, { visible: true });
|
||||
|
||||
|
||||
this.checkIntervalID = null;
|
||||
function checkForHandsAboveHead() {
|
||||
debug("Orient: Checking for hands above head...");
|
||||
debug("Orient | Checking for hands above head");
|
||||
if (MyAvatar.getLeftPalmPosition().y > (MyAvatar.getHeadPosition().y + 0.1)) {
|
||||
Script.clearInterval(this.checkIntervalID);
|
||||
this.checkIntervalID = null;
|
||||
|
@ -359,6 +374,7 @@ stepOrient.prototype = {
|
|||
this.checkIntervalID = Script.setInterval(checkForHandsAboveHead.bind(this), 500);
|
||||
},
|
||||
cleanup: function() {
|
||||
debug("Orient | Cleanup");
|
||||
if (this.active) {
|
||||
this.active = false;
|
||||
}
|
||||
|
@ -394,13 +410,12 @@ stepRaiseAboveHead.prototype = {
|
|||
var STATE_HANDS_UP = 2;
|
||||
this.state = STATE_START;
|
||||
|
||||
debug("raise hands...", this.tag);
|
||||
editEntitiesWithTag(this.tag, { visible: true });
|
||||
|
||||
// Wait 2 seconds before starting to check for hands
|
||||
this.checkIntervalID = null;
|
||||
function checkForHandsAboveHead() {
|
||||
debug("Raise above head: Checking hands...");
|
||||
debug("RaiseAboveHead | Checking hands");
|
||||
if (this.state == STATE_START) {
|
||||
if (MyAvatar.getLeftPalmPosition().y < (MyAvatar.getHeadPosition().y - 0.1)) {
|
||||
this.state = STATE_HANDS_DOWN;
|
||||
|
@ -418,6 +433,7 @@ stepRaiseAboveHead.prototype = {
|
|||
this.checkIntervalID = Script.setInterval(checkForHandsAboveHead.bind(this), 500);
|
||||
},
|
||||
cleanup: function() {
|
||||
debug("RaiseAboveHead | Cleanup");
|
||||
if (this.checkIntervalID) {
|
||||
Script.clearInterval(this.checkIntervalID);
|
||||
this.checkIntervalID = null
|
||||
|
@ -451,30 +467,26 @@ stepNearGrab.prototype = {
|
|||
this.finished = false;
|
||||
this.onFinish = onFinish;
|
||||
|
||||
setControllerVisible("trigger", true);
|
||||
setControllerPartLayer('tips', 'trigger');
|
||||
setControllerPartLayer('trigger', 'highlight');
|
||||
var tag = this.tag;
|
||||
|
||||
// Spawn content set
|
||||
showEntitiesWithTag(this.tag, { visible: true });
|
||||
showEntitiesWithTag('bothGrab', { visible: true });
|
||||
|
||||
var boxSpawnID = findEntity({ name: NEAR_BOX_SPAWN_NAME }, 10000);
|
||||
if (!boxSpawnID) {
|
||||
info("Error creating block, cannot find spawn");
|
||||
return null;
|
||||
}
|
||||
var boxSpawnPosition = Entities.getEntityProperties(boxSpawnID, 'position').position;
|
||||
function createBlock() {
|
||||
//Step1BlockData.position = boxSpawnPosition;
|
||||
birdFirework1.position = boxSpawnPosition;
|
||||
return spawnWithTag([birdFirework1], null, this.tempTag)[0];
|
||||
var boxSpawnPosition = getEntityWithName(NEAR_BOX_SPAWN_NAME).position;
|
||||
function createBlock(fireworkNumber) {
|
||||
fireworkBaseProps.position = boxSpawnPosition;
|
||||
fireworkBaseProps.modelURL = fireworkURLs[fireworkNumber % fireworkURLs.length];
|
||||
debug("Creating firework with url: ", fireworkBaseProps.modelURL);
|
||||
return spawnWithTag([fireworkBaseProps], null, this.tempTag)[0];
|
||||
}
|
||||
|
||||
this.birdIDs = [];
|
||||
this.birdIDs.push(createBlock.bind(this)());
|
||||
this.birdIDs.push(createBlock.bind(this)());
|
||||
this.birdIDs.push(createBlock.bind(this)());
|
||||
this.birdIDs.push(createBlock.bind(this)(0));
|
||||
this.birdIDs.push(createBlock.bind(this)(1));
|
||||
this.birdIDs.push(createBlock.bind(this)(2));
|
||||
this.positionWatcher = new PositionWatcher(this.birdIDs, boxSpawnPosition, -0.4, 4);
|
||||
},
|
||||
onMessage: function(channel, message, seneder) {
|
||||
|
@ -482,10 +494,12 @@ stepNearGrab.prototype = {
|
|||
return;
|
||||
}
|
||||
if (channel == "Entity-Exploded") {
|
||||
debug("TUTORIAL: Got entity-exploded message");
|
||||
debug("NearGrab | Got entity-exploded message: ", message);
|
||||
|
||||
var data = parseJSON(message);
|
||||
if (this.birdIDs.indexOf(data.entityID) >= 0) {
|
||||
debug("NearGrab | It's one of the firecrackers");
|
||||
playFirecrackerSound(data.position);
|
||||
playSuccessSound();
|
||||
this.finished = true;
|
||||
this.onFinish();
|
||||
|
@ -493,10 +507,10 @@ stepNearGrab.prototype = {
|
|||
}
|
||||
},
|
||||
cleanup: function() {
|
||||
debug("cleaning up near grab");
|
||||
debug("NearGrab | Cleanup");
|
||||
this.finished = true;
|
||||
setControllerVisible("trigger", false);
|
||||
setControllerPartLayer('tips', 'blank');
|
||||
setControllerPartLayer('trigger', 'normal');
|
||||
hideEntitiesWithTag(this.tag, { visible: false});
|
||||
deleteEntitiesWithTag(this.tempTag);
|
||||
if (this.positionWatcher) {
|
||||
|
@ -530,8 +544,8 @@ stepFarGrab.prototype = {
|
|||
|
||||
showEntitiesWithTag('bothGrab', { visible: true });
|
||||
|
||||
setControllerVisible("trigger", true);
|
||||
setControllerPartLayer('tips', 'trigger');
|
||||
setControllerPartLayer('trigger', 'highlight');
|
||||
Messages.sendLocalMessage('Hifi-Grab-Disable', JSON.stringify({
|
||||
farGrabEnabled: true,
|
||||
}));
|
||||
|
@ -540,21 +554,18 @@ stepFarGrab.prototype = {
|
|||
// Spawn content set
|
||||
showEntitiesWithTag(this.tag);
|
||||
|
||||
var boxSpawnID = findEntity({ name: FAR_BOX_SPAWN_NAME }, 10000);
|
||||
if (!boxSpawnID) {
|
||||
debug("Error creating block, cannot find spawn");
|
||||
return null;
|
||||
}
|
||||
var boxSpawnPosition = Entities.getEntityProperties(boxSpawnID, 'position').position;
|
||||
function createBlock() {
|
||||
birdFirework1.position = boxSpawnPosition;
|
||||
return spawnWithTag([birdFirework1], null, this.tempTag)[0];
|
||||
var boxSpawnPosition = getEntityWithName(FAR_BOX_SPAWN_NAME).position;
|
||||
function createBlock(fireworkNumber) {
|
||||
fireworkBaseProps.position = boxSpawnPosition;
|
||||
fireworkBaseProps.modelURL = fireworkURLs[fireworkNumber % fireworkURLs.length];
|
||||
debug("Creating firework with url: ", fireworkBaseProps.modelURL);
|
||||
return spawnWithTag([fireworkBaseProps], null, this.tempTag)[0];
|
||||
}
|
||||
|
||||
this.birdIDs = [];
|
||||
this.birdIDs.push(createBlock.bind(this)());
|
||||
this.birdIDs.push(createBlock.bind(this)());
|
||||
this.birdIDs.push(createBlock.bind(this)());
|
||||
this.birdIDs.push(createBlock.bind(this)(3));
|
||||
this.birdIDs.push(createBlock.bind(this)(4));
|
||||
this.birdIDs.push(createBlock.bind(this)(5));
|
||||
this.positionWatcher = new PositionWatcher(this.birdIDs, boxSpawnPosition, -0.4, 4);
|
||||
},
|
||||
onMessage: function(channel, message, seneder) {
|
||||
|
@ -562,9 +573,11 @@ stepFarGrab.prototype = {
|
|||
return;
|
||||
}
|
||||
if (channel == "Entity-Exploded") {
|
||||
debug("TUTORIAL: Got entity-exploded message");
|
||||
debug("FarGrab | Got entity-exploded message: ", message);
|
||||
var data = parseJSON(message);
|
||||
if (this.birdIDs.indexOf(data.entityID) >= 0) {
|
||||
debug("FarGrab | It's one of the firecrackers");
|
||||
playFirecrackerSound(data.position);
|
||||
playSuccessSound();
|
||||
this.finished = true;
|
||||
this.onFinish();
|
||||
|
@ -572,9 +585,10 @@ stepFarGrab.prototype = {
|
|||
}
|
||||
},
|
||||
cleanup: function() {
|
||||
debug("FarGrab | Cleanup");
|
||||
this.finished = true;
|
||||
setControllerVisible("trigger", false);
|
||||
setControllerPartLayer('tips', 'blank');
|
||||
setControllerPartLayer('trigger', 'normal');
|
||||
hideEntitiesWithTag(this.tag, { visible: false});
|
||||
hideEntitiesWithTag('bothGrab', { visible: false});
|
||||
deleteEntitiesWithTag(this.tempTag);
|
||||
|
@ -586,12 +600,13 @@ stepFarGrab.prototype = {
|
|||
};
|
||||
|
||||
function PositionWatcher(entityIDs, originalPosition, minY, maxDistance) {
|
||||
debug("Creating position watcher");
|
||||
this.watcherIntervalID = Script.setInterval(function() {
|
||||
for (var i = 0; i < entityIDs.length; ++i) {
|
||||
var entityID = entityIDs[i];
|
||||
var props = Entities.getEntityProperties(entityID, ['position']);
|
||||
if (props.position.y < minY || Vec3.distance(originalPosition, props.position) > maxDistance) {
|
||||
Entities.editEntity(entityID, {
|
||||
Entities.editEntity(entityID, {
|
||||
position: originalPosition,
|
||||
velocity: { x: 0, y: -0.01, z: 0 },
|
||||
angularVelocity: { x: 0, y: 0, z: 0 }
|
||||
|
@ -603,6 +618,7 @@ function PositionWatcher(entityIDs, originalPosition, minY, maxDistance) {
|
|||
|
||||
PositionWatcher.prototype = {
|
||||
destroy: function() {
|
||||
debug("Destroying position watcher");
|
||||
Script.clearInterval(this.watcherIntervalID);
|
||||
}
|
||||
};
|
||||
|
@ -630,8 +646,8 @@ var stepEquip = function(name) {
|
|||
}
|
||||
stepEquip.prototype = {
|
||||
start: function(onFinish) {
|
||||
setControllerVisible("trigger", true);
|
||||
setControllerPartLayer('tips', 'trigger');
|
||||
setControllerPartLayer('trigger', 'highlight');
|
||||
Messages.sendLocalMessage('Hifi-Grab-Disable', JSON.stringify({
|
||||
holdEnabled: true,
|
||||
}));
|
||||
|
@ -644,40 +660,41 @@ stepEquip.prototype = {
|
|||
|
||||
this.currentPart = this.PART1;
|
||||
|
||||
function createGun() {
|
||||
var boxSpawnID = findEntity({ name: GUN_SPAWN_NAME }, 10000);
|
||||
if (!boxSpawnID) {
|
||||
info("Error creating block, cannot find spawn");
|
||||
return null;
|
||||
}
|
||||
|
||||
function createLighter() {
|
||||
var transform = {};
|
||||
|
||||
transform.position = Entities.getEntityProperties(boxSpawnID, 'position').position;
|
||||
transform.rotation = Entities.getEntityProperties(boxSpawnID, 'rotation').rotation;
|
||||
var boxSpawnProps = getEntityWithName(GUN_SPAWN_NAME);
|
||||
transform.position = boxSpawnProps.position;
|
||||
transform.rotation = boxSpawnProps.rotation;
|
||||
transform.velocity = { x: 0, y: -0.01, z: 0 };
|
||||
transform.angularVelocity = { x: 0, y: 0, z: 0 };
|
||||
this.spawnTransform = transform;
|
||||
return doCreateButaneLighter(transform).id;
|
||||
}
|
||||
|
||||
|
||||
this.gunID = createGun.bind(this)();
|
||||
this.startWatchingGun();
|
||||
debug("Created", this.gunID);
|
||||
this.lighterID = createLighter.bind(this)();
|
||||
this.startWatchingLighter();
|
||||
debug("Created lighter", this.lighterID);
|
||||
this.onFinish = onFinish;
|
||||
},
|
||||
startWatchingGun: function() {
|
||||
startWatchingLighter: function() {
|
||||
if (!this.watcherIntervalID) {
|
||||
debug("Starting to watch lighter position");
|
||||
this.watcherIntervalID = Script.setInterval(function() {
|
||||
var props = Entities.getEntityProperties(this.gunID, ['position']);
|
||||
if (props.position.y < -0.4
|
||||
debug("Checking lighter position");
|
||||
var props = Entities.getEntityProperties(this.lighterID, ['position']);
|
||||
if (props.position.y < -0.4
|
||||
|| Vec3.distance(this.spawnTransform.position, props.position) > 4) {
|
||||
Entities.editEntity(this.gunID, this.spawnTransform);
|
||||
debug("Moving lighter back to table");
|
||||
Entities.editEntity(this.lighterID, this.spawnTransform);
|
||||
}
|
||||
}.bind(this), 1000);
|
||||
}
|
||||
},
|
||||
stopWatchingGun: function() {
|
||||
if (this.watcherIntervalID) {
|
||||
debug("Stopping watch of lighter position");
|
||||
Script.clearInterval(this.watcherIntervalID);
|
||||
this.watcherIntervalID = null;
|
||||
}
|
||||
|
@ -687,24 +704,28 @@ stepEquip.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
debug("Got message", channel, message, sender, MyAvatar.sessionUUID);
|
||||
debug("Equip | Got message", channel, message, sender, MyAvatar.sessionUUID);
|
||||
|
||||
if (channel == "Tutorial-Spinner") {
|
||||
if (this.currentPart == this.PART1 && message == "wasLit") {
|
||||
this.currentPart = this.PART2;
|
||||
debug("Equip | Starting part 2");
|
||||
Script.setTimeout(function() {
|
||||
debug("Equip | Starting part 3");
|
||||
this.currentPart = this.PART3;
|
||||
hideEntitiesWithTag(this.tagPart1);
|
||||
showEntitiesWithTag(this.tagPart2);
|
||||
setControllerPartLayer('trigger', 'normal');
|
||||
setControllerPartLayer('tips', 'grip');
|
||||
Messages.subscribe('Hifi-Object-Manipulation');
|
||||
debug("Equip | Finished starting part 3");
|
||||
}.bind(this), 9000);
|
||||
}
|
||||
} else if (channel == "Hifi-Object-Manipulation") {
|
||||
if (this.currentPart == this.PART3) {
|
||||
var data = parseJSON(message);
|
||||
if (data.action == 'release' && data.grabbedEntity == this.gunID) {
|
||||
info("got release");
|
||||
if (data.action == 'release' && data.grabbedEntity == this.lighterID) {
|
||||
debug("Equip | Got release, finishing step");
|
||||
this.stopWatchingGun();
|
||||
this.currentPart = this.COMPLETE;
|
||||
playSuccessSound();
|
||||
|
@ -714,13 +735,14 @@ stepEquip.prototype = {
|
|||
}
|
||||
},
|
||||
cleanup: function() {
|
||||
debug("Equip | Got yaw action");
|
||||
if (this.watcherIntervalID) {
|
||||
Script.clearInterval(this.watcherIntervalID);
|
||||
this.watcherIntervalID = null;
|
||||
}
|
||||
|
||||
setControllerVisible("trigger", false);
|
||||
setControllerPartLayer('tips', 'blank');
|
||||
setControllerPartLayer('trigger', 'normal');
|
||||
this.stopWatchingGun();
|
||||
this.currentPart = this.COMPLETE;
|
||||
|
||||
|
@ -752,9 +774,6 @@ var stepTurnAround = function(name) {
|
|||
}
|
||||
stepTurnAround.prototype = {
|
||||
start: function(onFinish) {
|
||||
setControllerVisible("left", true);
|
||||
setControllerVisible("right", true);
|
||||
|
||||
setControllerPartLayer('touchpad', 'arrows');
|
||||
setControllerPartLayer('tips', 'arrows');
|
||||
|
||||
|
@ -764,8 +783,9 @@ stepTurnAround.prototype = {
|
|||
Controller.actionEvent.connect(this.onActionBound);
|
||||
|
||||
this.interval = Script.setInterval(function() {
|
||||
var FORWARD_THRESHOLD = 30;
|
||||
var REQ_NUM_TIMES_PRESSED = 6;
|
||||
debug("TurnAround | Checking if finished", this.numTimesTurnPressed);
|
||||
var FORWARD_THRESHOLD = 90;
|
||||
var REQ_NUM_TIMES_PRESSED = 3;
|
||||
|
||||
var dir = Quat.getFront(MyAvatar.orientation);
|
||||
var angle = Math.atan2(dir.z, dir.x);
|
||||
|
@ -782,18 +802,17 @@ stepTurnAround.prototype = {
|
|||
onAction: function(action, value) {
|
||||
var STEP_YAW_ACTION = 6;
|
||||
if (action == STEP_YAW_ACTION && value != 0) {
|
||||
debug("TurnAround | Got yaw action");
|
||||
this.numTimesTurnPressed += 1;
|
||||
}
|
||||
},
|
||||
cleanup: function() {
|
||||
debug("TurnAround | Cleanup");
|
||||
try {
|
||||
Controller.actionEvent.disconnect(this.onActionBound);
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
setControllerVisible("left", false);
|
||||
setControllerVisible("right", false);
|
||||
|
||||
setControllerPartLayer('touchpad', 'blank');
|
||||
setControllerPartLayer('tips', 'blank');
|
||||
|
||||
|
@ -826,22 +845,21 @@ stepTeleport.prototype = {
|
|||
Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'none');
|
||||
|
||||
// Wait until touching teleport pad...
|
||||
var padID = findEntity({ name: TELEPORT_PAD_NAME }, 100);
|
||||
var padProps = Entities.getEntityProperties(padID, ["position", "dimensions"]);
|
||||
var padProps = getEntityWithName(TELEPORT_PAD_NAME);
|
||||
var xMin = padProps.position.x - padProps.dimensions.x / 2;
|
||||
var xMax = padProps.position.x + padProps.dimensions.x / 2;
|
||||
var zMin = padProps.position.z - padProps.dimensions.z / 2;
|
||||
var zMax = padProps.position.z + padProps.dimensions.z / 2;
|
||||
function checkCollides() {
|
||||
debug("Checking if on pad...");
|
||||
debug("Teleport | Checking if on pad...");
|
||||
|
||||
var pos = MyAvatar.position;
|
||||
|
||||
debug('x', pos.x, xMin, xMax);
|
||||
debug('z', pos.z, zMin, zMax);
|
||||
debug('Teleport | x', pos.x, xMin, xMax);
|
||||
debug('Teleport | z', pos.z, zMin, zMax);
|
||||
|
||||
if (pos.x > xMin && pos.x < xMax && pos.z > zMin && pos.z < zMax) {
|
||||
debug("On teleport pad");
|
||||
debug("Teleport | On teleport pad");
|
||||
Script.clearInterval(this.checkCollidesTimer);
|
||||
this.checkCollidesTimer = null;
|
||||
playSuccessSound();
|
||||
|
@ -853,6 +871,7 @@ stepTeleport.prototype = {
|
|||
showEntitiesWithTag(this.tag);
|
||||
},
|
||||
cleanup: function() {
|
||||
debug("Teleport | Cleanup");
|
||||
setControllerPartLayer('touchpad', 'blank');
|
||||
setControllerPartLayer('tips', 'blank');
|
||||
|
||||
|
@ -903,6 +922,10 @@ stepCleanupFinish.prototype = {
|
|||
|
||||
|
||||
|
||||
function isEntityInLocalTree(entityID) {
|
||||
return Entities.getEntityProperties(entityID, 'visible').visible !== undefined;
|
||||
}
|
||||
|
||||
function showEntitiesWithTag(tag) {
|
||||
var entities = TUTORIAL_TAG_TO_ENTITY_IDS_MAP[tag];
|
||||
if (entities) {
|
||||
|
@ -921,6 +944,7 @@ function showEntitiesWithTag(tag) {
|
|||
collisionless: collisionless,
|
||||
userData: JSON.stringify(data),
|
||||
};
|
||||
debug("Showing: ", entityID, ", Is in local tree: ", isEntityInLocalTree(entityID));
|
||||
Entities.editEntity(entityID, newProperties);
|
||||
}
|
||||
}
|
||||
|
@ -945,6 +969,7 @@ function showEntitiesWithTag(tag) {
|
|||
Entities.editEntity(entityID, newProperties);
|
||||
});
|
||||
}
|
||||
|
||||
function hideEntitiesWithTag(tag) {
|
||||
var entities = TUTORIAL_TAG_TO_ENTITY_IDS_MAP[tag];
|
||||
if (entities) {
|
||||
|
@ -960,6 +985,8 @@ function hideEntitiesWithTag(tag) {
|
|||
ignoreForCollisions: 1,
|
||||
userData: JSON.stringify(data),
|
||||
};
|
||||
|
||||
debug("Hiding: ", entityID, ", Is in local tree: ", isEntityInLocalTree(entityID));
|
||||
Entities.editEntity(entityID, newProperties);
|
||||
}
|
||||
}
|
||||
|
@ -982,6 +1009,15 @@ function hideEntitiesWithTag(tag) {
|
|||
});
|
||||
}
|
||||
|
||||
// Return the entity properties for an entity with a given name if it is in our
|
||||
// cached list of entities. Otherwise, return undefined.
|
||||
function getEntityWithName(name) {
|
||||
debug("Getting entity with name:", name);
|
||||
var entityID = TUTORIAL_NAME_TO_ENTITY_PROPERTIES_MAP[name];
|
||||
debug("Entity id: ", entityID, ", Is in local tree: ", isEntityInLocalTree(entityID));
|
||||
return entityID;
|
||||
}
|
||||
|
||||
|
||||
TutorialManager = function() {
|
||||
var STEPS;
|
||||
|
@ -998,9 +1034,9 @@ TutorialManager = function() {
|
|||
currentStep = null;
|
||||
startedTutorialAt = Date.now();
|
||||
STEPS = [
|
||||
new stepDisableControllers("step0"),
|
||||
new stepStart("start"),
|
||||
new stepOrient("orient"),
|
||||
new stepRaiseAboveHead("raiseHands"),
|
||||
//new stepRaiseAboveHead("raiseHands"),
|
||||
new stepNearGrab("nearGrab"),
|
||||
new stepFarGrab("farGrab"),
|
||||
new stepEquip("equip"),
|
||||
|
@ -1017,6 +1053,7 @@ TutorialManager = function() {
|
|||
}
|
||||
|
||||
this.onFinish = function() {
|
||||
debug("onFinish", currentStepNum);
|
||||
if (currentStep && currentStep.shouldLog !== false) {
|
||||
var timeToFinishStep = (Date.now() - startedLastStepAt) / 1000;
|
||||
var tutorialTimeElapsed = (Date.now() - startedTutorialAt) / 1000;
|
||||
|
@ -1059,6 +1096,7 @@ TutorialManager = function() {
|
|||
this.stopTutorial = function() {
|
||||
if (currentStep) {
|
||||
currentStep.cleanup();
|
||||
HMD.requestHideHandControllers();
|
||||
}
|
||||
reenableEverything();
|
||||
currentStepNum = -1;
|
||||
|
|
|
@ -83,7 +83,7 @@ TUTORIAL_TAG_TO_ENTITY_IDS_MAP = {
|
|||
}
|
||||
},
|
||||
"equip-part2": {
|
||||
"{8b92eec5-aeed-4368-bce0-432cc9ad4c51}": {
|
||||
"{b5d17eda-90ab-40cf-b973-efcecb2e992e}": {
|
||||
"tag": "equip-part2"
|
||||
},
|
||||
"{6307cd16-dd1d-4988-a339-578178436b45}": {
|
||||
|
@ -148,4 +148,168 @@ TUTORIAL_TAG_TO_ENTITY_IDS_MAP = {
|
|||
"tag": "orient"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TUTORIAL_NAME_TO_ENTITY_PROPERTIES_MAP = {
|
||||
"tutorial/gun_spawn": {
|
||||
"userData": "{\"tag\":\"equip\",\"visible\":false}",
|
||||
"dimensions": {
|
||||
"y": 0.0649842768907547,
|
||||
"x": 0.0649842768907547,
|
||||
"z": 0.0649842768907547
|
||||
},
|
||||
"collisionless": 1,
|
||||
"created": "2016-09-08T18:38:24Z",
|
||||
"color": {
|
||||
"blue": 0,
|
||||
"green": 0,
|
||||
"red": 255
|
||||
},
|
||||
"queryAACube": {
|
||||
"y": 0.6283726096153259,
|
||||
"x": 0.6865367293357849,
|
||||
"scale": 0.11255607008934021,
|
||||
"z": 0.3359576463699341
|
||||
},
|
||||
"visible": 0,
|
||||
"shape": "Cube",
|
||||
"clientOnly": 0,
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"position": {
|
||||
"y": 0.6846506595611572,
|
||||
"x": 0.7428147792816162,
|
||||
"z": 0.3922356963157654
|
||||
},
|
||||
"rotation": {
|
||||
"y": 0.7066605091094971,
|
||||
"x": 0.7066605091094971,
|
||||
"z": -0.025131583213806152,
|
||||
"w": -0.025101065635681152
|
||||
},
|
||||
"ignoreForCollisions": 1,
|
||||
"type": "Box",
|
||||
"id": "{9df518da-9e65-4b76-8a79-eeefdb0b7310}",
|
||||
"name": "tutorial/gun_spawn"
|
||||
},
|
||||
"tutorial/nearGrab/box_spawn": {
|
||||
"userData": "{\"tag\":\"nearGrab\",\"visible\":false}",
|
||||
"dimensions": {
|
||||
"y": 0.08225371688604355,
|
||||
"x": 0.08225371688604355,
|
||||
"z": 0.08225371688604355
|
||||
},
|
||||
"collisionless": 1,
|
||||
"created": "2016-09-08T18:38:24Z",
|
||||
"color": {
|
||||
"blue": 255,
|
||||
"green": 0,
|
||||
"red": 255
|
||||
},
|
||||
"queryAACube": {
|
||||
"y": 0.738319456577301,
|
||||
"x": 0.8985498547554016,
|
||||
"scale": 0.14246761798858643,
|
||||
"z": 0.29067665338516235
|
||||
},
|
||||
"visible": 0,
|
||||
"shape": "Cube",
|
||||
"clientOnly": 0,
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"position": {
|
||||
"y": 0.8095532655715942,
|
||||
"x": 0.9697836637496948,
|
||||
"z": 0.36191046237945557
|
||||
},
|
||||
"rotation": {
|
||||
"y": -1.52587890625e-05,
|
||||
"x": -1.52587890625e-05,
|
||||
"z": -1.52587890625e-05,
|
||||
"w": 1
|
||||
},
|
||||
"ignoreForCollisions": 1,
|
||||
"type": "Box",
|
||||
"id": "{5cf22b9c-fb22-4854-8821-554422980b24}",
|
||||
"name": "tutorial/nearGrab/box_spawn"
|
||||
},
|
||||
"tutorial/farGrab/box_spawn": {
|
||||
"userData": "{\"tag\":\"farGrab\",\"visible\":false}",
|
||||
"dimensions": {
|
||||
"y": 0.37358683347702026,
|
||||
"x": 0.37358683347702026,
|
||||
"z": 0.37358683347702026
|
||||
},
|
||||
"collisionless": 1,
|
||||
"created": "2016-09-08T18:38:24Z",
|
||||
"color": {
|
||||
"blue": 255,
|
||||
"green": 0,
|
||||
"red": 255
|
||||
},
|
||||
"queryAACube": {
|
||||
"y": 0.3304251432418823,
|
||||
"x": 3.0951309204101562,
|
||||
"scale": 0.647071361541748,
|
||||
"z": 0.18027013540267944
|
||||
},
|
||||
"visible": 0,
|
||||
"shape": "Cube",
|
||||
"clientOnly": 0,
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"position": {
|
||||
x: 3.4866,
|
||||
y: 0.6716,
|
||||
z: 0.4789
|
||||
},
|
||||
"rotation": {
|
||||
"y": -1.52587890625e-05,
|
||||
"x": -1.52587890625e-05,
|
||||
"z": -1.52587890625e-05,
|
||||
"w": 1
|
||||
},
|
||||
"ignoreForCollisions": 1,
|
||||
"type": "Box",
|
||||
"id": "{70fcd96c-cd59-4f23-9ca5-a167f2f85680}",
|
||||
"name": "tutorial/farGrab/box_spawn"
|
||||
},
|
||||
"tutorial/teleport/pad": {
|
||||
"userData": "{\"tag\":\"teleport\"}",
|
||||
"rotation": {
|
||||
"y": -0.9702650308609009,
|
||||
"x": -2.1246911273919977e-05,
|
||||
"z": -4.222852112434339e-06,
|
||||
"w": 0.2420452982187271
|
||||
},
|
||||
"dimensions": {
|
||||
"y": 0.4365682601928711,
|
||||
"x": 2.1751723289489746,
|
||||
"z": 2.175173044204712
|
||||
},
|
||||
"collisionless": 1,
|
||||
"created": "2016-09-08T18:38:24Z",
|
||||
"queryAACube": {
|
||||
"y": -1.7979401350021362,
|
||||
"x": 7.5136213302612305,
|
||||
"scale": 3.106983184814453,
|
||||
"z": -1.4602710008621216
|
||||
},
|
||||
"visible": 0,
|
||||
"angularVelocity": {
|
||||
"y": -0.5235987901687622,
|
||||
"x": 0,
|
||||
"z": 0
|
||||
},
|
||||
"clientOnly": 0,
|
||||
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"angularDamping": 0,
|
||||
"position": {
|
||||
"y": -0.2444484978914261,
|
||||
"x": 9.067112922668457,
|
||||
"z": 0.09322060644626617
|
||||
},
|
||||
"modelURL": "atp:/alan/dev/Teleport-Pad.fbx",
|
||||
"ignoreForCollisions": 1,
|
||||
"type": "Model",
|
||||
"id": "{4478f7b5-d3ac-4213-9a7b-ad8cd69575b8}",
|
||||
"name": "tutorial/teleport/pad"
|
||||
}
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
print("TutorialStartZone | Parent ID is: ", parentID);
|
||||
if (parentID) {
|
||||
print("TutorialStartZone | Sending start");
|
||||
Entities.callEntityMethod(parentID, 'start');
|
||||
Entities.callEntityMethod(parentID, 'onEnteredStartZone');
|
||||
} else {
|
||||
print("TutorialStartZone | ERROR: No parent id found on tutorial start zone");
|
||||
}
|
||||
|
@ -29,7 +29,7 @@
|
|||
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.");
|
||||
Window.alert("To proceed with this tutorial, please connect your Vive headset and hand controllers.");
|
||||
location = "/";
|
||||
}
|
||||
},
|
||||
|
@ -38,6 +38,12 @@
|
|||
if (this.sendStartIntervalID) {
|
||||
Script.clearInterval(this.sendStartIntervalID);
|
||||
}
|
||||
var parentID = Entities.getEntityProperties(this.entityID, 'parentID').parentID;
|
||||
print("TutorialStartZone | Parent ID is: ", parentID);
|
||||
if (parentID) {
|
||||
print("TutorialStartZone | Sending onLeftStartZone");
|
||||
Entities.callEntityMethod(parentID, 'on');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -27,10 +27,14 @@ if (!Function.prototype.bind) {
|
|||
}
|
||||
|
||||
(function() {
|
||||
var ownershipTokenPath = Script.resolvePath("ownershipToken.js");
|
||||
var tutorialPath = Script.resolvePath("tutorial.js");
|
||||
Script.include(ownershipTokenPath);
|
||||
Script.include(tutorialPath);
|
||||
Script.include("ownershipToken.js");
|
||||
Script.include("tutorial.js");
|
||||
|
||||
var CHANNEL_AWAY_ENABLE = "Hifi-Away-Enable";
|
||||
function setAwayEnabled(value) {
|
||||
var message = value ? 'enable' : 'disable';
|
||||
Messages.sendLocalMessage(CHANNEL_AWAY_ENABLE, message);
|
||||
}
|
||||
|
||||
var TutorialZone = function() {
|
||||
print("TutorialZone | Creating");
|
||||
|
@ -59,11 +63,16 @@ if (!Function.prototype.bind) {
|
|||
print("TutorialZone | Preload");
|
||||
this.entityID = entityID;
|
||||
},
|
||||
start: function() {
|
||||
print("TutorialZone | Got start");
|
||||
onEnteredStartZone: function() {
|
||||
print("TutorialZone | Got onEnteredStartZone");
|
||||
var self = this;
|
||||
if (!this.token) {
|
||||
print("TutorialZone | Creating token");
|
||||
// The start zone has been entered, hide the overlays immediately
|
||||
setAwayEnabled(false);
|
||||
Menu.setIsOptionChecked("Overlays", false);
|
||||
MyAvatar.shouldRenderLocally = false;
|
||||
Toolbars.getToolbar("com.highfidelity.interface.toolbar.system").writeProperty("visible", false);
|
||||
this.token = new OwnershipToken(Math.random() * 100000, this.entityID, {
|
||||
onGainedOwnership: function(token) {
|
||||
print("TutorialZone | GOT OWNERSHIP");
|
||||
|
@ -91,6 +100,18 @@ if (!Function.prototype.bind) {
|
|||
});
|
||||
}
|
||||
},
|
||||
onLeftStartZone: function() {
|
||||
print("TutorialZone | Got onLeftStartZone");
|
||||
|
||||
// If the start zone was exited, and the tutorial hasn't started, go ahead and
|
||||
// re-enable the HUD/Overlays
|
||||
if (!this.tutorialManager) {
|
||||
Menu.setIsOptionChecked("Overlays", true);
|
||||
MyAvatar.shouldRenderLocally = true;
|
||||
setAwayEnabled(true);
|
||||
Toolbars.getToolbar("com.highfidelity.interface.toolbar.system").writeProperty("visible", true);
|
||||
}
|
||||
},
|
||||
|
||||
enterEntity: function() {
|
||||
print("TutorialZone | ENTERED THE TUTORIAL AREA");
|
||||
|
@ -102,6 +123,10 @@ if (!Function.prototype.bind) {
|
|||
this.token.destroy();
|
||||
this.token = null;
|
||||
}
|
||||
if (this.tutorialManager) {
|
||||
this.tutorialManager.stopTutorial();
|
||||
this.tutorialManager = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,40 +1,47 @@
|
|||
(function(){
|
||||
var teleport;
|
||||
var portalDestination;
|
||||
var thisEntityID;
|
||||
|
||||
function playSound() {
|
||||
Audio.playSound(teleport, { volume: 0.40, localOnly: true });
|
||||
var properties = Entities.getEntityProperties(thisEntityID, 'position');
|
||||
if (properties) {
|
||||
Audio.playSound(teleport, { position: properties.position, volume: 0.40, localOnly: true });
|
||||
}
|
||||
};
|
||||
|
||||
this.preload = function(entityID) {
|
||||
thisEntityID = entityID;
|
||||
teleport = SoundCache.getSound("atp:/sounds/teleport.raw");
|
||||
|
||||
var properties = Entities.getEntityProperties(entityID);
|
||||
portalDestination = properties.userData;
|
||||
|
||||
print("portal.js | The portal destination is " + portalDestination);
|
||||
var properties = Entities.getEntityProperties(entityID, 'userData');
|
||||
if (properties) {
|
||||
portalDestination = properties.userData;
|
||||
print("portal.js | The portal destination is " + portalDestination);
|
||||
}
|
||||
}
|
||||
|
||||
this.enterEntity = function(entityID) {
|
||||
print("portal.js | enterEntity");
|
||||
|
||||
var properties = Entities.getEntityProperties(entityID); // in case the userData/portalURL has changed
|
||||
portalDestination = properties.userData;
|
||||
var properties = Entities.getEntityProperties(entityID, 'userData'); // in case the userData/portalURL has changed
|
||||
if (properties) {
|
||||
portalDestination = properties.userData;
|
||||
|
||||
print("portal.js | enterEntity() .... The portal destination is " + portalDestination);
|
||||
print("portal.js | enterEntity() .... The portal destination is " + portalDestination);
|
||||
|
||||
if (portalDestination.length > 0) {
|
||||
if (portalDestination[0] == '/') {
|
||||
print("Teleporting to " + portalDestination);
|
||||
Window.location = portalDestination;
|
||||
if (portalDestination.length > 0) {
|
||||
if (portalDestination[0] == '/') {
|
||||
print("Teleporting to " + portalDestination);
|
||||
Window.location = portalDestination;
|
||||
} else {
|
||||
print("Teleporting to hifi://" + portalDestination);
|
||||
Window.location = "hifi://" + portalDestination;
|
||||
}
|
||||
} else {
|
||||
print("Teleporting to hifi://" + portalDestination);
|
||||
Window.location = "hifi://" + portalDestination;
|
||||
location.goToEntry(); // going forward: no data means go to appropriate entry point
|
||||
}
|
||||
} else {
|
||||
location.goToEntry(); // going forward: no data means go to appropriate entry point
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
this.leaveEntity = function(entityID) {
|
||||
|
|