Merge branch 'master' of github.com:highfidelity/hifi into motor-action

This commit is contained in:
Seth Alves 2017-05-08 11:02:42 -07:00
commit c8cdad5b4e
33 changed files with 304 additions and 224 deletions

View file

@ -140,9 +140,6 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
auto orientation = myAvatar->getLocalOrientation(); auto orientation = myAvatar->getLocalOrientation();
_rig->computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState); _rig->computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState);
// evaluate AnimGraph animation and update jointStates.
Model::updateRig(deltaTime, parentTransform);
Rig::EyeParameters eyeParams; Rig::EyeParameters eyeParams;
eyeParams.eyeLookAt = lookAt; eyeParams.eyeLookAt = lookAt;
eyeParams.eyeSaccade = head->getSaccade(); eyeParams.eyeSaccade = head->getSaccade();
@ -153,6 +150,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
_rig->updateFromEyeParameters(eyeParams); _rig->updateFromEyeParameters(eyeParams);
// evaluate AnimGraph animation and update jointStates.
Parent::updateRig(deltaTime, parentTransform); Parent::updateRig(deltaTime, parentTransform);
} }

View file

@ -28,11 +28,15 @@ const int MAX_HISTORY_SIZE = 64;
const QString COMMAND_STYLE = "color: #266a9b;"; const QString COMMAND_STYLE = "color: #266a9b;";
const QString RESULT_SUCCESS_STYLE = "color: #677373;"; const QString RESULT_SUCCESS_STYLE = "color: #677373;";
const QString RESULT_INFO_STYLE = "color: #223bd1;";
const QString RESULT_WARNING_STYLE = "color: #d13b22;";
const QString RESULT_ERROR_STYLE = "color: #d13b22;"; const QString RESULT_ERROR_STYLE = "color: #d13b22;";
const QString GUTTER_PREVIOUS_COMMAND = "<span style=\"color: #57b8bb;\">&lt;</span>"; const QString GUTTER_PREVIOUS_COMMAND = "<span style=\"color: #57b8bb;\">&lt;</span>";
const QString GUTTER_ERROR = "<span style=\"color: #d13b22;\">X</span>"; const QString GUTTER_ERROR = "<span style=\"color: #d13b22;\">X</span>";
const QString JSConsole::_consoleFileName { "about:console" };
JSConsole::JSConsole(QWidget* parent, ScriptEngine* scriptEngine) : JSConsole::JSConsole(QWidget* parent, ScriptEngine* scriptEngine) :
QWidget(parent), QWidget(parent),
_ui(new Ui::Console), _ui(new Ui::Console),
@ -77,6 +81,8 @@ void JSConsole::setScriptEngine(ScriptEngine* scriptEngine) {
} }
if (_scriptEngine != NULL) { if (_scriptEngine != NULL) {
disconnect(_scriptEngine, &ScriptEngine::printedMessage, this, &JSConsole::handlePrint); disconnect(_scriptEngine, &ScriptEngine::printedMessage, this, &JSConsole::handlePrint);
disconnect(_scriptEngine, &ScriptEngine::infoMessage, this, &JSConsole::handleInfo);
disconnect(_scriptEngine, &ScriptEngine::warningMessage, this, &JSConsole::handleWarning);
disconnect(_scriptEngine, &ScriptEngine::errorMessage, this, &JSConsole::handleError); disconnect(_scriptEngine, &ScriptEngine::errorMessage, this, &JSConsole::handleError);
if (_ownScriptEngine) { if (_ownScriptEngine) {
_scriptEngine->deleteLater(); _scriptEngine->deleteLater();
@ -84,10 +90,12 @@ void JSConsole::setScriptEngine(ScriptEngine* scriptEngine) {
} }
// if scriptEngine is NULL then create one and keep track of it using _ownScriptEngine // if scriptEngine is NULL then create one and keep track of it using _ownScriptEngine
_ownScriptEngine = scriptEngine == NULL; _ownScriptEngine = (scriptEngine == NULL);
_scriptEngine = _ownScriptEngine ? DependencyManager::get<ScriptEngines>()->loadScript(QString(), false) : scriptEngine; _scriptEngine = _ownScriptEngine ? DependencyManager::get<ScriptEngines>()->loadScript(_consoleFileName, false) : scriptEngine;
connect(_scriptEngine, &ScriptEngine::printedMessage, this, &JSConsole::handlePrint); connect(_scriptEngine, &ScriptEngine::printedMessage, this, &JSConsole::handlePrint);
connect(_scriptEngine, &ScriptEngine::infoMessage, this, &JSConsole::handleInfo);
connect(_scriptEngine, &ScriptEngine::warningMessage, this, &JSConsole::handleWarning);
connect(_scriptEngine, &ScriptEngine::errorMessage, this, &JSConsole::handleError); connect(_scriptEngine, &ScriptEngine::errorMessage, this, &JSConsole::handleError);
} }
@ -107,11 +115,10 @@ void JSConsole::executeCommand(const QString& command) {
QScriptValue JSConsole::executeCommandInWatcher(const QString& command) { QScriptValue JSConsole::executeCommandInWatcher(const QString& command) {
QScriptValue result; QScriptValue result;
static const QString filename = "JSConcole";
QMetaObject::invokeMethod(_scriptEngine, "evaluate", Qt::ConnectionType::BlockingQueuedConnection, QMetaObject::invokeMethod(_scriptEngine, "evaluate", Qt::ConnectionType::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, result), Q_RETURN_ARG(QScriptValue, result),
Q_ARG(const QString&, command), Q_ARG(const QString&, command),
Q_ARG(const QString&, filename)); Q_ARG(const QString&, _consoleFileName));
return result; return result;
} }
@ -134,16 +141,26 @@ void JSConsole::commandFinished() {
resetCurrentCommandHistory(); resetCurrentCommandHistory();
} }
void JSConsole::handleError(const QString& scriptName, const QString& message) { void JSConsole::handleError(const QString& message, const QString& scriptName) {
Q_UNUSED(scriptName); Q_UNUSED(scriptName);
appendMessage(GUTTER_ERROR, "<span style='" + RESULT_ERROR_STYLE + "'>" + message.toHtmlEscaped() + "</span>"); appendMessage(GUTTER_ERROR, "<span style='" + RESULT_ERROR_STYLE + "'>" + message.toHtmlEscaped() + "</span>");
} }
void JSConsole::handlePrint(const QString& scriptName, const QString& message) { void JSConsole::handlePrint(const QString& message, const QString& scriptName) {
Q_UNUSED(scriptName); Q_UNUSED(scriptName);
appendMessage("", message); appendMessage("", message);
} }
void JSConsole::handleInfo(const QString& message, const QString& scriptName) {
Q_UNUSED(scriptName);
appendMessage("", "<span style='" + RESULT_INFO_STYLE + "'>" + message.toHtmlEscaped() + "</span>");
}
void JSConsole::handleWarning(const QString& message, const QString& scriptName) {
Q_UNUSED(scriptName);
appendMessage("", "<span style='" + RESULT_WARNING_STYLE + "'>" + message.toHtmlEscaped() + "</span>");
}
void JSConsole::mouseReleaseEvent(QMouseEvent* event) { void JSConsole::mouseReleaseEvent(QMouseEvent* event) {
_ui->promptTextEdit->setFocus(); _ui->promptTextEdit->setFocus();
} }

View file

@ -47,8 +47,10 @@ protected:
protected slots: protected slots:
void scrollToBottom(); void scrollToBottom();
void resizeTextInput(); void resizeTextInput();
void handlePrint(const QString& scriptName, const QString& message); void handlePrint(const QString& message, const QString& scriptName);
void handleError(const QString& scriptName, const QString& message); void handleInfo(const QString& message, const QString& scriptName);
void handleWarning(const QString& message, const QString& scriptName);
void handleError(const QString& message, const QString& scriptName);
void commandFinished(); void commandFinished();
private: private:
@ -66,6 +68,7 @@ private:
bool _ownScriptEngine; bool _ownScriptEngine;
QString _rootCommand; QString _rootCommand;
ScriptEngine* _scriptEngine; ScriptEngine* _scriptEngine;
static const QString _consoleFileName;
}; };

View file

@ -1097,28 +1097,27 @@ void AudioClient::handleRecordedAudioInput(const QByteArray& audio) {
} }
void AudioClient::prepareLocalAudioInjectors() { void AudioClient::prepareLocalAudioInjectors() {
if (_outputPeriod == 0) {
return;
}
int bufferCapacity = _localInjectorsStream.getSampleCapacity();
if (_localToOutputResampler) {
// avoid overwriting the buffer,
// instead of failing on writes because the buffer is used as a lock-free pipe
bufferCapacity -=
_localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL) *
AudioConstants::STEREO;
bufferCapacity += 1;
}
int samplesNeeded = std::numeric_limits<int>::max(); int samplesNeeded = std::numeric_limits<int>::max();
while (samplesNeeded > 0) { while (samplesNeeded > 0) {
// lock for every write to avoid locking out the device callback // unlock between every write to allow device switching
// this lock is intentional - the buffer is only lock-free in its use in the device callback Lock lock(_localAudioMutex);
RecursiveLock lock(_localAudioMutex);
// in case of a device switch, consider bufferCapacity volatile across iterations
if (_outputPeriod == 0) {
return;
}
int bufferCapacity = _localInjectorsStream.getSampleCapacity();
int maxOutputSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * AudioConstants::STEREO;
if (_localToOutputResampler) {
maxOutputSamples =
_localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL) *
AudioConstants::STEREO;
}
samplesNeeded = bufferCapacity - _localSamplesAvailable.load(std::memory_order_relaxed); samplesNeeded = bufferCapacity - _localSamplesAvailable.load(std::memory_order_relaxed);
if (samplesNeeded <= 0) { if (samplesNeeded < maxOutputSamples) {
// avoid overwriting the buffer to prevent losing frames
break; break;
} }
@ -1168,16 +1167,18 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
memset(mixBuffer, 0, AudioConstants::NETWORK_FRAME_SAMPLES_STEREO * sizeof(float)); memset(mixBuffer, 0, AudioConstants::NETWORK_FRAME_SAMPLES_STEREO * sizeof(float));
for (AudioInjector* injector : _activeLocalAudioInjectors) { for (AudioInjector* injector : _activeLocalAudioInjectors) {
if (injector->getLocalBuffer()) { // the lock guarantees that injectorBuffer, if found, is invariant
AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer();
if (injectorBuffer) {
static const int HRTF_DATASET_INDEX = 1; static const int HRTF_DATASET_INDEX = 1;
int numChannels = injector->isAmbisonic() ? AudioConstants::AMBISONIC : (injector->isStereo() ? AudioConstants::STEREO : AudioConstants::MONO); int numChannels = injector->isAmbisonic() ? AudioConstants::AMBISONIC : (injector->isStereo() ? AudioConstants::STEREO : AudioConstants::MONO);
qint64 bytesToRead = numChannels * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL; size_t bytesToRead = numChannels * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
// get one frame from the injector // get one frame from the injector
memset(_localScratchBuffer, 0, bytesToRead); memset(_localScratchBuffer, 0, bytesToRead);
if (0 < injector->getLocalBuffer()->readData((char*)_localScratchBuffer, bytesToRead)) { if (0 < injectorBuffer->readData((char*)_localScratchBuffer, bytesToRead)) {
if (injector->isAmbisonic()) { if (injector->isAmbisonic()) {
@ -1317,15 +1318,17 @@ void AudioClient::setIsStereoInput(bool isStereoInput) {
} }
bool AudioClient::outputLocalInjector(AudioInjector* injector) { bool AudioClient::outputLocalInjector(AudioInjector* injector) {
Lock lock(_injectorsMutex); AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer();
if (injector->getLocalBuffer() && _audioInput ) { if (injectorBuffer) {
// just add it to the vector of active local injectors, if // local injectors are on the AudioInjectorsThread, so we must guard access
// not already there. Lock lock(_injectorsMutex);
// Since this is invoked with invokeMethod, there _should_ be
// no reason to lock access to the vector of injectors.
if (!_activeLocalAudioInjectors.contains(injector)) { if (!_activeLocalAudioInjectors.contains(injector)) {
qCDebug(audioclient) << "adding new injector"; qCDebug(audioclient) << "adding new injector";
_activeLocalAudioInjectors.append(injector); _activeLocalAudioInjectors.append(injector);
// move local buffer to the LocalAudioThread to avoid dataraces with AudioInjector (like stop())
injectorBuffer->setParent(nullptr);
injectorBuffer->moveToThread(&_localAudioThread);
} else { } else {
qCDebug(audioclient) << "injector exists in active list already"; qCDebug(audioclient) << "injector exists in active list already";
} }
@ -1333,7 +1336,7 @@ bool AudioClient::outputLocalInjector(AudioInjector* injector) {
return true; return true;
} else { } else {
// no local buffer or audio // no local buffer
return false; return false;
} }
} }
@ -1452,7 +1455,7 @@ void AudioClient::outputNotify() {
bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) { bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) {
bool supportedFormat = false; bool supportedFormat = false;
RecursiveLock lock(_localAudioMutex); Lock lock(_localAudioMutex);
_localSamplesAvailable.exchange(0, std::memory_order_release); _localSamplesAvailable.exchange(0, std::memory_order_release);
// cleanup any previously initialized device // cleanup any previously initialized device
@ -1681,8 +1684,12 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
int injectorSamplesPopped = 0; int injectorSamplesPopped = 0;
{ {
RecursiveLock lock(_audio->_localAudioMutex);
bool append = networkSamplesPopped > 0; bool append = networkSamplesPopped > 0;
// this does not require a lock as of the only two functions adding to _localSamplesAvailable (samples count):
// - prepareLocalAudioInjectors will only increase samples count
// - switchOutputToAudioDevice will zero samples count
// stop the device, so that readData will exhaust the existing buffer or see a zeroed samples count
// and start the device, which can only see a zeroed samples count
samplesRequested = std::min(samplesRequested, _audio->_localSamplesAvailable.load(std::memory_order_acquire)); samplesRequested = std::min(samplesRequested, _audio->_localSamplesAvailable.load(std::memory_order_acquire));
if ((injectorSamplesPopped = _localInjectorsStream.appendSamples(mixBuffer, samplesRequested, append)) > 0) { if ((injectorSamplesPopped = _localInjectorsStream.appendSamples(mixBuffer, samplesRequested, append)) > 0) {
_audio->_localSamplesAvailable.fetch_sub(injectorSamplesPopped, std::memory_order_release); _audio->_localSamplesAvailable.fetch_sub(injectorSamplesPopped, std::memory_order_release);

View file

@ -96,8 +96,6 @@ public:
using AudioPositionGetter = std::function<glm::vec3()>; using AudioPositionGetter = std::function<glm::vec3()>;
using AudioOrientationGetter = std::function<glm::quat()>; using AudioOrientationGetter = std::function<glm::quat()>;
using RecursiveMutex = std::recursive_mutex;
using RecursiveLock = std::unique_lock<RecursiveMutex>;
using Mutex = std::mutex; using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>; using Lock = std::unique_lock<Mutex>;
@ -345,7 +343,7 @@ private:
int16_t _localScratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC]; int16_t _localScratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC];
float* _localOutputMixBuffer { NULL }; float* _localOutputMixBuffer { NULL };
AudioInjectorsThread _localAudioThread; AudioInjectorsThread _localAudioThread;
RecursiveMutex _localAudioMutex; Mutex _localAudioMutex;
// for output audio (used by this thread) // for output audio (used by this thread)
int _outputPeriod { 0 }; int _outputPeriod { 0 };

View file

@ -33,7 +33,11 @@ public:
PacketType packetType, QString codecName = QString("")); PacketType packetType, QString codecName = QString(""));
public slots: public slots:
// threadsafe
// moves injector->getLocalBuffer() to another thread (so removes its parent)
// take care to delete it when ~AudioInjector, as parenting Qt semantics will not work
virtual bool outputLocalInjector(AudioInjector* injector) = 0; virtual bool outputLocalInjector(AudioInjector* injector) = 0;
virtual bool shouldLoopbackInjectors() { return false; } virtual bool shouldLoopbackInjectors() { return false; }
virtual void setIsStereoInput(bool stereo) = 0; virtual void setIsStereoInput(bool stereo) = 0;

View file

@ -51,6 +51,10 @@ AudioInjector::AudioInjector(const QByteArray& audioData, const AudioInjectorOpt
{ {
} }
AudioInjector::~AudioInjector() {
deleteLocalBuffer();
}
bool AudioInjector::stateHas(AudioInjectorState state) const { bool AudioInjector::stateHas(AudioInjectorState state) const {
return (_state & state) == state; return (_state & state) == state;
} }
@ -87,11 +91,7 @@ void AudioInjector::finish() {
emit finished(); emit finished();
if (_localBuffer) { deleteLocalBuffer();
_localBuffer->stop();
_localBuffer->deleteLater();
_localBuffer = NULL;
}
if (stateHas(AudioInjectorState::PendingDelete)) { if (stateHas(AudioInjectorState::PendingDelete)) {
// we've been asked to delete after finishing, trigger a deleteLater here // we've been asked to delete after finishing, trigger a deleteLater here
@ -163,7 +163,7 @@ bool AudioInjector::injectLocally() {
if (_localAudioInterface) { if (_localAudioInterface) {
if (_audioData.size() > 0) { if (_audioData.size() > 0) {
_localBuffer = new AudioInjectorLocalBuffer(_audioData, this); _localBuffer = new AudioInjectorLocalBuffer(_audioData);
_localBuffer->open(QIODevice::ReadOnly); _localBuffer->open(QIODevice::ReadOnly);
_localBuffer->setShouldLoop(_options.loop); _localBuffer->setShouldLoop(_options.loop);
@ -172,7 +172,8 @@ bool AudioInjector::injectLocally() {
_localBuffer->setCurrentOffset(_currentSendOffset); _localBuffer->setCurrentOffset(_currentSendOffset);
// call this function on the AudioClient's thread // call this function on the AudioClient's thread
success = QMetaObject::invokeMethod(_localAudioInterface, "outputLocalInjector", Q_ARG(AudioInjector*, this)); // this will move the local buffer's thread to the LocalInjectorThread
success = _localAudioInterface->outputLocalInjector(this);
if (!success) { if (!success) {
qCDebug(audio) << "AudioInjector::injectLocally could not output locally via _localAudioInterface"; qCDebug(audio) << "AudioInjector::injectLocally could not output locally via _localAudioInterface";
@ -185,6 +186,14 @@ bool AudioInjector::injectLocally() {
return success; return success;
} }
void AudioInjector::deleteLocalBuffer() {
if (_localBuffer) {
_localBuffer->stop();
_localBuffer->deleteLater();
_localBuffer = nullptr;
}
}
const uchar MAX_INJECTOR_VOLUME = packFloatGainToByte(1.0f); const uchar MAX_INJECTOR_VOLUME = packFloatGainToByte(1.0f);
static const int64_t NEXT_FRAME_DELTA_ERROR_OR_FINISHED = -1; static const int64_t NEXT_FRAME_DELTA_ERROR_OR_FINISHED = -1;
static const int64_t NEXT_FRAME_DELTA_IMMEDIATELY = 0; static const int64_t NEXT_FRAME_DELTA_IMMEDIATELY = 0;

View file

@ -52,6 +52,7 @@ class AudioInjector : public QObject {
public: public:
AudioInjector(const Sound& sound, const AudioInjectorOptions& injectorOptions); AudioInjector(const Sound& sound, const AudioInjectorOptions& injectorOptions);
AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions); AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions);
~AudioInjector();
bool isFinished() const { return (stateHas(AudioInjectorState::Finished)); } bool isFinished() const { return (stateHas(AudioInjectorState::Finished)); }
@ -99,6 +100,7 @@ private:
int64_t injectNextFrame(); int64_t injectNextFrame();
bool inject(bool(AudioInjectorManager::*injection)(AudioInjector*)); bool inject(bool(AudioInjectorManager::*injection)(AudioInjector*));
bool injectLocally(); bool injectLocally();
void deleteLocalBuffer();
static AbstractAudioInterface* _localAudioInterface; static AbstractAudioInterface* _localAudioInterface;

View file

@ -11,8 +11,7 @@
#include "AudioInjectorLocalBuffer.h" #include "AudioInjectorLocalBuffer.h"
AudioInjectorLocalBuffer::AudioInjectorLocalBuffer(const QByteArray& rawAudioArray, QObject* parent) : AudioInjectorLocalBuffer::AudioInjectorLocalBuffer(const QByteArray& rawAudioArray) :
QIODevice(parent),
_rawAudioArray(rawAudioArray), _rawAudioArray(rawAudioArray),
_shouldLoop(false), _shouldLoop(false),
_isStopped(false), _isStopped(false),

View file

@ -19,7 +19,7 @@
class AudioInjectorLocalBuffer : public QIODevice { class AudioInjectorLocalBuffer : public QIODevice {
Q_OBJECT Q_OBJECT
public: public:
AudioInjectorLocalBuffer(const QByteArray& rawAudioArray, QObject* parent); AudioInjectorLocalBuffer(const QByteArray& rawAudioArray);
void stop(); void stop();

View file

@ -120,7 +120,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
} }
// evaluate AnimGraph animation and update jointStates. // evaluate AnimGraph animation and update jointStates.
Model::updateRig(deltaTime, parentTransform); Parent::updateRig(deltaTime, parentTransform);
} }
void SkeletonModel::updateAttitude() { void SkeletonModel::updateAttitude() {
@ -136,7 +136,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
if (fullUpdate) { if (fullUpdate) {
setBlendshapeCoefficients(_owningAvatar->getHead()->getSummedBlendshapeCoefficients()); setBlendshapeCoefficients(_owningAvatar->getHead()->getSummedBlendshapeCoefficients());
Model::simulate(deltaTime, fullUpdate); Parent::simulate(deltaTime, fullUpdate);
// let rig compute the model offset // let rig compute the model offset
glm::vec3 registrationPoint; glm::vec3 registrationPoint;
@ -144,7 +144,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
setOffset(registrationPoint); setOffset(registrationPoint);
} }
} else { } else {
Model::simulate(deltaTime, fullUpdate); Parent::simulate(deltaTime, fullUpdate);
} }
if (!isActive() || !_owningAvatar->isMyAvatar()) { if (!isActive() || !_owningAvatar->isMyAvatar()) {

View file

@ -23,6 +23,7 @@ using SkeletonModelWeakPointer = std::weak_ptr<SkeletonModel>;
/// A skeleton loaded from a model. /// A skeleton loaded from a model.
class SkeletonModel : public CauterizedModel { class SkeletonModel : public CauterizedModel {
using Parent = CauterizedModel;
Q_OBJECT Q_OBJECT
public: public:

View file

@ -19,8 +19,8 @@
#include <DependencyManager.h> #include <DependencyManager.h>
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include <comdef.h> #include <Windows.h>
#include <Wbemidl.h> #include <winreg.h>
#endif //Q_OS_WIN #endif //Q_OS_WIN
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
@ -30,6 +30,9 @@
#endif //Q_OS_MAC #endif //Q_OS_MAC
static const QString FALLBACK_FINGERPRINT_KEY = "fallbackFingerprint"; static const QString FALLBACK_FINGERPRINT_KEY = "fallbackFingerprint";
QUuid FingerprintUtils::_machineFingerprint { QUuid() };
QString FingerprintUtils::getMachineFingerprintString() { QString FingerprintUtils::getMachineFingerprintString() {
QString uuidString; QString uuidString;
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
@ -47,122 +50,32 @@ QString FingerprintUtils::getMachineFingerprintString() {
#endif //Q_OS_MAC #endif //Q_OS_MAC
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
HRESULT hres; HKEY cryptoKey;
IWbemLocator *pLoc = NULL;
// initialize com. Interface already does, but other
// users of this lib don't necessarily do so.
hres = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hres)) {
qCDebug(networking) << "Failed to initialize COM library!";
return uuidString;
}
// initialize WbemLocator // try and open the key that contains the machine GUID
hres = CoCreateInstance( if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Cryptography", 0, KEY_READ, &cryptoKey) == ERROR_SUCCESS) {
CLSID_WbemLocator, DWORD type;
0, DWORD guidSize;
CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID *) &pLoc);
if (FAILED(hres)) { const char* MACHINE_GUID_KEY = "MachineGuid";
qCDebug(networking) << "Failed to initialize WbemLocator";
return uuidString;
}
// Connect to WMI through the IWbemLocator::ConnectServer method
IWbemServices *pSvc = NULL;
// Connect to the root\cimv2 namespace with // try and retrieve the size of the GUID value
// the current user and obtain pointer pSvc if (RegQueryValueEx(cryptoKey, MACHINE_GUID_KEY, NULL, &type, NULL, &guidSize) == ERROR_SUCCESS) {
// to make IWbemServices calls. // make sure that the value is a string
hres = pLoc->ConnectServer( if (type == REG_SZ) {
_bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace // retrieve the machine GUID and return that as our UUID string
NULL, // User name. NULL = current user std::string machineGUID(guidSize / sizeof(char), '\0');
NULL, // User password. NULL = current
0, // Locale. NULL indicates current
NULL, // Security flags.
0, // Authority (for example, Kerberos)
0, // Context object
&pSvc // pointer to IWbemServices proxy
);
if (FAILED(hres)) { if (RegQueryValueEx(cryptoKey, MACHINE_GUID_KEY, NULL, NULL,
pLoc->Release(); reinterpret_cast<LPBYTE>(&machineGUID[0]), &guidSize) == ERROR_SUCCESS) {
qCDebug(networking) << "Failed to connect to WMI"; uuidString = QString::fromStdString(machineGUID);
return uuidString; }
}
// Set security levels on the proxy
hres = CoSetProxyBlanket(
pSvc, // Indicates the proxy to set
RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx
RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx
NULL, // Server principal name
RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
NULL, // client identity
EOAC_NONE // proxy capabilities
);
if (FAILED(hres)) {
pSvc->Release();
pLoc->Release();
qCDebug(networking) << "Failed to set security on proxy blanket";
return uuidString;
}
// Use the IWbemServices pointer to grab the Win32_BIOS stuff
IEnumWbemClassObject* pEnumerator = NULL;
hres = pSvc->ExecQuery(
bstr_t("WQL"),
bstr_t("SELECT * FROM Win32_ComputerSystemProduct"),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator);
if (FAILED(hres)) {
pSvc->Release();
pLoc->Release();
qCDebug(networking) << "query to get Win32_ComputerSystemProduct info";
return uuidString;
}
// Get the SerialNumber from the Win32_BIOS data
IWbemClassObject *pclsObj;
ULONG uReturn = 0;
SHORT sRetStatus = -100;
while (pEnumerator) {
HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
if(0 == uReturn){
break;
}
VARIANT vtProp;
// Get the value of the Name property
hr = pclsObj->Get(L"UUID", 0, &vtProp, 0, 0);
if (!FAILED(hres)) {
switch (vtProp.vt) {
case VT_BSTR:
uuidString = QString::fromWCharArray(vtProp.bstrVal);
break;
} }
} }
VariantClear(&vtProp);
pclsObj->Release(); RegCloseKey(cryptoKey);
} }
pEnumerator->Release();
// Cleanup
pSvc->Release();
pLoc->Release();
qCDebug(networking) << "Windows BIOS UUID: " << uuidString;
#endif //Q_OS_WIN #endif //Q_OS_WIN
return uuidString; return uuidString;
@ -171,29 +84,36 @@ QString FingerprintUtils::getMachineFingerprintString() {
QUuid FingerprintUtils::getMachineFingerprint() { QUuid FingerprintUtils::getMachineFingerprint() {
QString uuidString = getMachineFingerprintString(); if (_machineFingerprint.isNull()) {
QString uuidString = getMachineFingerprintString();
// now, turn into uuid. A malformed string will
// return QUuid() ("{00000...}"), which handles
// any errors in getting the string
QUuid uuid(uuidString);
// now, turn into uuid. A malformed string will
// return QUuid() ("{00000...}"), which handles
// any errors in getting the string
QUuid uuid(uuidString);
if (uuid == QUuid()) {
// if you cannot read a fallback key cuz we aren't saving them, just generate one for
// this session and move on
if (DependencyManager::get<Setting::Manager>().isNull()) {
return QUuid::createUuid();
}
// read fallback key (if any)
Settings settings;
uuid = QUuid(settings.value(FALLBACK_FINGERPRINT_KEY).toString());
qCDebug(networking) << "read fallback maching fingerprint: " << uuid.toString();
if (uuid == QUuid()) { if (uuid == QUuid()) {
// no fallback yet, set one // if you cannot read a fallback key cuz we aren't saving them, just generate one for
uuid = QUuid::createUuid(); // this session and move on
settings.setValue(FALLBACK_FINGERPRINT_KEY, uuid.toString()); if (DependencyManager::get<Setting::Manager>().isNull()) {
qCDebug(networking) << "no fallback machine fingerprint, setting it to: " << uuid.toString(); return QUuid::createUuid();
}
// read fallback key (if any)
Settings settings;
uuid = QUuid(settings.value(FALLBACK_FINGERPRINT_KEY).toString());
qCDebug(networking) << "read fallback maching fingerprint: " << uuid.toString();
if (uuid == QUuid()) {
// no fallback yet, set one
uuid = QUuid::createUuid();
settings.setValue(FALLBACK_FINGERPRINT_KEY, uuid.toString());
qCDebug(networking) << "no fallback machine fingerprint, setting it to: " << uuid.toString();
}
} }
_machineFingerprint = uuid;
} }
return uuid;
return _machineFingerprint;
} }

View file

@ -21,6 +21,7 @@ public:
private: private:
static QString getMachineFingerprintString(); static QString getMachineFingerprintString();
static QUuid _machineFingerprint;
}; };
#endif // hifi_FingerprintUtils_h #endif // hifi_FingerprintUtils_h

View file

@ -28,10 +28,10 @@ public:
const std::unordered_set<int>& getCauterizeBoneSet() const { return _cauterizeBoneSet; } const std::unordered_set<int>& getCauterizeBoneSet() const { return _cauterizeBoneSet; }
void setCauterizeBoneSet(const std::unordered_set<int>& boneSet) { _cauterizeBoneSet = boneSet; } void setCauterizeBoneSet(const std::unordered_set<int>& boneSet) { _cauterizeBoneSet = boneSet; }
void deleteGeometry() override; void deleteGeometry() override;
bool updateGeometry() override; bool updateGeometry() override;
void createVisibleRenderItemSet() override; void createVisibleRenderItemSet() override;
void createCollisionRenderItemSet() override; void createCollisionRenderItemSet() override;
virtual void updateClusterMatrices() override; virtual void updateClusterMatrices() override;
@ -41,7 +41,7 @@ public:
protected: protected:
std::unordered_set<int> _cauterizeBoneSet; std::unordered_set<int> _cauterizeBoneSet;
QVector<Model::MeshState> _cauterizeMeshStates; QVector<Model::MeshState> _cauterizeMeshStates;
bool _isCauterized { false }; bool _isCauterized { false };
bool _enableCauterization { false }; bool _enableCauterization { false };
}; };

View file

@ -11,7 +11,9 @@
#include <QDebug> #include <QDebug>
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include <glm/gtx/string_cast.hpp>
#include "ScriptEngineLogging.h" #include "ScriptEngineLogging.h"
#include "ScriptEngine.h"
#include "Mat4.h" #include "Mat4.h"
glm::mat4 Mat4::multiply(const glm::mat4& m1, const glm::mat4& m2) const { glm::mat4 Mat4::multiply(const glm::mat4& m1, const glm::mat4& m2) const {
@ -66,10 +68,12 @@ glm::vec3 Mat4::getUp(const glm::mat4& m) const {
return glm::vec3(m[0][1], m[1][1], m[2][1]); return glm::vec3(m[0][1], m[1][1], m[2][1]);
} }
void Mat4::print(const QString& label, const glm::mat4& m) const { void Mat4::print(const QString& label, const glm::mat4& m, bool transpose) const {
qCDebug(scriptengine) << qPrintable(label) << glm::dmat4 out = transpose ? glm::transpose(m) : m;
"row0 =" << m[0][0] << "," << m[1][0] << "," << m[2][0] << "," << m[3][0] << QString message = QString("%1 %2").arg(qPrintable(label));
"row1 =" << m[0][1] << "," << m[1][1] << "," << m[2][1] << "," << m[3][1] << message = message.arg(glm::to_string(out).c_str());
"row2 =" << m[0][2] << "," << m[1][2] << "," << m[2][2] << "," << m[3][2] << qCDebug(scriptengine) << message;
"row3 =" << m[0][3] << "," << m[1][3] << "," << m[2][3] << "," << m[3][3]; if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine())) {
scriptEngine->print(message);
}
} }

View file

@ -16,9 +16,10 @@
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <QtScript/QScriptable>
/// Scriptable Mat4 object. Used exclusively in the JavaScript API /// Scriptable Mat4 object. Used exclusively in the JavaScript API
class Mat4 : public QObject { class Mat4 : public QObject, protected QScriptable {
Q_OBJECT Q_OBJECT
public slots: public slots:
@ -43,7 +44,7 @@ public slots:
glm::vec3 getRight(const glm::mat4& m) const; glm::vec3 getRight(const glm::mat4& m) const;
glm::vec3 getUp(const glm::mat4& m) const; glm::vec3 getUp(const glm::mat4& m) const;
void print(const QString& label, const glm::mat4& m) const; void print(const QString& label, const glm::mat4& m, bool transpose = false) const;
}; };
#endif // hifi_Mat4_h #endif // hifi_Mat4_h

View file

@ -15,7 +15,9 @@
#include <OctreeConstants.h> #include <OctreeConstants.h>
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include <glm/gtx/string_cast.hpp>
#include "ScriptEngineLogging.h" #include "ScriptEngineLogging.h"
#include "ScriptEngine.h"
#include "Quat.h" #include "Quat.h"
quat Quat::normalize(const glm::quat& q) { quat Quat::normalize(const glm::quat& q) {
@ -114,8 +116,17 @@ float Quat::dot(const glm::quat& q1, const glm::quat& q2) {
return glm::dot(q1, q2); return glm::dot(q1, q2);
} }
void Quat::print(const QString& label, const glm::quat& q) { void Quat::print(const QString& label, const glm::quat& q, bool asDegrees) {
qCDebug(scriptengine) << qPrintable(label) << q.x << "," << q.y << "," << q.z << "," << q.w; QString message = QString("%1 %2").arg(qPrintable(label));
if (asDegrees) {
message = message.arg(glm::to_string(glm::dvec3(safeEulerAngles(q))).c_str());
} else {
message = message.arg(glm::to_string(glm::dquat(q)).c_str());
}
qCDebug(scriptengine) << message;
if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine())) {
scriptEngine->print(message);
}
} }
bool Quat::equal(const glm::quat& q1, const glm::quat& q2) { bool Quat::equal(const glm::quat& q1, const glm::quat& q2) {

View file

@ -18,6 +18,7 @@
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <QtScript/QScriptable>
/**jsdoc /**jsdoc
* A Quaternion * A Quaternion
@ -30,7 +31,7 @@
*/ */
/// Scriptable interface a Quaternion helper class object. Used exclusively in the JavaScript API /// Scriptable interface a Quaternion helper class object. Used exclusively in the JavaScript API
class Quat : public QObject { class Quat : public QObject, protected QScriptable {
Q_OBJECT Q_OBJECT
public slots: public slots:
@ -58,7 +59,7 @@ public slots:
glm::quat slerp(const glm::quat& q1, const glm::quat& q2, float alpha); glm::quat slerp(const glm::quat& q1, const glm::quat& q2, float alpha);
glm::quat squad(const glm::quat& q1, const glm::quat& q2, const glm::quat& s1, const glm::quat& s2, float h); glm::quat squad(const glm::quat& q1, const glm::quat& q2, const glm::quat& s1, const glm::quat& s2, float h);
float dot(const glm::quat& q1, const glm::quat& q2); float dot(const glm::quat& q1, const glm::quat& q2);
void print(const QString& label, const glm::quat& q); void print(const QString& label, const glm::quat& q, bool asDegrees = false);
bool equal(const glm::quat& q1, const glm::quat& q2); bool equal(const glm::quat& q1, const glm::quat& q2);
glm::quat cancelOutRollAndPitch(const glm::quat& q); glm::quat cancelOutRollAndPitch(const glm::quat& q);
glm::quat cancelOutRoll(const glm::quat& q); glm::quat cancelOutRoll(const glm::quat& q);

View file

@ -105,11 +105,11 @@ static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine) {
} }
message += context->argument(i).toString(); message += context->argument(i).toString();
} }
qCDebug(scriptengineScript).noquote() << "script:print()<<" << message; // noquote() so that \n is treated as newline qCDebug(scriptengineScript).noquote() << message; // noquote() so that \n is treated as newline
// FIXME - this approach neeeds revisiting. print() comes here, which ends up calling Script.print? if (ScriptEngine *scriptEngine = qobject_cast<ScriptEngine*>(engine)) {
engine->globalObject().property("Script").property("print") scriptEngine->print(message);
.call(engine->nullValue(), QScriptValueList({ message })); }
return QScriptValue(); return QScriptValue();
} }
@ -472,6 +472,11 @@ void ScriptEngine::scriptInfoMessage(const QString& message) {
emit infoMessage(message, getFilename()); emit infoMessage(message, getFilename());
} }
void ScriptEngine::scriptPrintedMessage(const QString& message) {
qCDebug(scriptengine) << message;
emit printedMessage(message, getFilename());
}
// Even though we never pass AnimVariantMap directly to and from javascript, the queued invokeMethod of // Even though we never pass AnimVariantMap directly to and from javascript, the queued invokeMethod of
// callAnimationStateHandler requires that the type be registered. // callAnimationStateHandler requires that the type be registered.
// These two are meaningful, if we ever do want to use them... // These two are meaningful, if we ever do want to use them...

View file

@ -221,6 +221,7 @@ public:
void scriptErrorMessage(const QString& message); void scriptErrorMessage(const QString& message);
void scriptWarningMessage(const QString& message); void scriptWarningMessage(const QString& message);
void scriptInfoMessage(const QString& message); void scriptInfoMessage(const QString& message);
void scriptPrintedMessage(const QString& message);
int getNumRunningEntityScripts() const; int getNumRunningEntityScripts() const;
bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const; bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const;

View file

@ -453,7 +453,8 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL
(scriptFilename.scheme() != "http" && (scriptFilename.scheme() != "http" &&
scriptFilename.scheme() != "https" && scriptFilename.scheme() != "https" &&
scriptFilename.scheme() != "atp" && scriptFilename.scheme() != "atp" &&
scriptFilename.scheme() != "file")) { scriptFilename.scheme() != "file" &&
scriptFilename.scheme() != "about")) {
// deal with a "url" like c:/something // deal with a "url" like c:/something
scriptUrl = normalizeScriptURL(QUrl::fromLocalFile(scriptFilename.toString())); scriptUrl = normalizeScriptURL(QUrl::fromLocalFile(scriptFilename.toString()));
} else { } else {
@ -472,7 +473,7 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL
}, Qt::QueuedConnection); }, Qt::QueuedConnection);
if (scriptFilename.isEmpty()) { if (scriptFilename.isEmpty() || !scriptUrl.isValid()) {
launchScriptEngine(scriptEngine); launchScriptEngine(scriptEngine);
} else { } else {
// connect to the appropriate signals of this script engine // connect to the appropriate signals of this script engine

View file

@ -14,6 +14,7 @@
#include <QDebug> #include <QDebug>
#include "ScriptEngineLogging.h" #include "ScriptEngineLogging.h"
#include "ScriptEngine.h"
#include "ScriptUUID.h" #include "ScriptUUID.h"
QUuid ScriptUUID::fromString(const QString& s) { QUuid ScriptUUID::fromString(const QString& s) {
@ -36,6 +37,11 @@ bool ScriptUUID::isNull(const QUuid& id) {
return id.isNull(); return id.isNull();
} }
void ScriptUUID::print(const QString& lable, const QUuid& id) { void ScriptUUID::print(const QString& label, const QUuid& id) {
qCDebug(scriptengine) << qPrintable(lable) << id.toString(); QString message = QString("%1 %2").arg(qPrintable(label));
message = message.arg(id.toString());
qCDebug(scriptengine) << message;
if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine())) {
scriptEngine->print(message);
}
} }

View file

@ -15,9 +15,10 @@
#define hifi_ScriptUUID_h #define hifi_ScriptUUID_h
#include <QUuid> #include <QUuid>
#include <QtScript/QScriptable>
/// Scriptable interface for a UUID helper class object. Used exclusively in the JavaScript API /// Scriptable interface for a UUID helper class object. Used exclusively in the JavaScript API
class ScriptUUID : public QObject { class ScriptUUID : public QObject, protected QScriptable {
Q_OBJECT Q_OBJECT
public slots: public slots:
@ -26,7 +27,7 @@ public slots:
QUuid generate(); QUuid generate();
bool isEqual(const QUuid& idA, const QUuid& idB); bool isEqual(const QUuid& idA, const QUuid& idB);
bool isNull(const QUuid& id); bool isNull(const QUuid& id);
void print(const QString& lable, const QUuid& id); void print(const QString& label, const QUuid& id);
}; };
#endif // hifi_ScriptUUID_h #endif // hifi_ScriptUUID_h

View file

@ -14,20 +14,26 @@
#include <QDebug> #include <QDebug>
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include <glm/gtx/string_cast.hpp>
#include "ScriptEngineLogging.h" #include "ScriptEngineLogging.h"
#include "NumericalConstants.h" #include "NumericalConstants.h"
#include "Vec3.h" #include "Vec3.h"
#include "ScriptEngine.h"
float Vec3::orientedAngle(const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3) { float Vec3::orientedAngle(const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3) {
float radians = glm::orientedAngle(glm::normalize(v1), glm::normalize(v2), glm::normalize(v3)); float radians = glm::orientedAngle(glm::normalize(v1), glm::normalize(v2), glm::normalize(v3));
return glm::degrees(radians); return glm::degrees(radians);
} }
void Vec3::print(const QString& label, const glm::vec3& v) {
void Vec3::print(const QString& lable, const glm::vec3& v) { QString message = QString("%1 %2").arg(qPrintable(label));
qCDebug(scriptengine) << qPrintable(lable) << v.x << "," << v.y << "," << v.z; message = message.arg(glm::to_string(glm::dvec3(v)).c_str());
qCDebug(scriptengine) << message;
if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine())) {
scriptEngine->print(message);
}
} }
bool Vec3::withinEpsilon(const glm::vec3& v1, const glm::vec3& v2, float epsilon) { bool Vec3::withinEpsilon(const glm::vec3& v1, const glm::vec3& v2, float epsilon) {

View file

@ -17,6 +17,7 @@
#include <QtCore/QObject> #include <QtCore/QObject>
#include <QtCore/QString> #include <QtCore/QString>
#include <QtScript/QScriptable>
#include "GLMHelpers.h" #include "GLMHelpers.h"
@ -48,7 +49,7 @@
*/ */
/// Scriptable interface a Vec3ernion helper class object. Used exclusively in the JavaScript API /// Scriptable interface a Vec3ernion helper class object. Used exclusively in the JavaScript API
class Vec3 : public QObject { class Vec3 : public QObject, protected QScriptable {
Q_OBJECT Q_OBJECT
Q_PROPERTY(glm::vec3 UNIT_X READ UNIT_X CONSTANT) Q_PROPERTY(glm::vec3 UNIT_X READ UNIT_X CONSTANT)
Q_PROPERTY(glm::vec3 UNIT_Y READ UNIT_Y CONSTANT) Q_PROPERTY(glm::vec3 UNIT_Y READ UNIT_Y CONSTANT)

View file

@ -20,9 +20,10 @@
print('<span style="color:red">Tests completed with ' + print('<span style="color:red">Tests completed with ' +
errorCount + ' ' + ERROR + '.<span>'); errorCount + ' ' + ERROR + '.<span>');
} }
if (pending.length) if (pending.length) {
print ('<span style="color:darkorange">disabled: <br />&nbsp;&nbsp;&nbsp;'+ print ('<span style="color:darkorange">disabled: <br />&nbsp;&nbsp;&nbsp;'+
pending.join('<br />&nbsp;&nbsp;&nbsp;')+'</span>'); pending.join('<br />&nbsp;&nbsp;&nbsp;')+'</span>');
}
print('Tests completed in ' + (endTime - startTime) + 'ms.'); print('Tests completed in ' + (endTime - startTime) + 'ms.');
}; };
this.suiteStarted = function(obj) { this.suiteStarted = function(obj) {

View file

@ -0,0 +1,39 @@
/* eslint-env jasmine */
// this test generates sample print, Script.print, etc. output
main();
function main() {
// to match with historical behavior, Script.print(message) output only triggers
// the printedMessage signal (and therefore doesn't show up in the application log)
Script.print('[Script.print] hello world');
// the rest of these should show up in both the application log and signaled print handlers
print('[print]', 'hello', 'world');
// note: these trigger the equivalent of an emit
Script.printedMessage('[Script.printedMessage] hello world', '{filename}');
Script.infoMessage('[Script.infoMessage] hello world', '{filename}');
Script.warningMessage('[Script.warningMessage] hello world', '{filename}');
Script.errorMessage('[Script.errorMessage] hello world', '{filename}');
{
Vec3.print('[Vec3.print]', Vec3.HALF);
var q = Quat.fromPitchYawRollDegrees(45, 45, 45);
Quat.print('[Quat.print]', q);
Quat.print('[Quat.print (euler)]', q, true);
function vec4(x,y,z,w) {
return { x: x, y: y, z: z, w: w };
}
var m = Mat4.createFromColumns(
vec4(1,2,3,4), vec4(5,6,7,8), vec4(9,10,11,12), vec4(13,14,15,16)
);
Mat4.print('[Mat4.print (col major)]', m);
Mat4.print('[Mat4.print (row major)]', m, true);
Uuid.print('[Uuid.print]', Uuid.fromString(Uuid.toString(0)));
}
}

View file

@ -1,5 +1,7 @@
/* eslint-env jasmine */
// Art3mis // Art3mis
// eslint-disable-next-line max-len
var DEFAULT_AVATAR_URL = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/contents/e76946cc-c272-4adf-9bb6-02cde0a4b57d/8fd984ea6fe1495147a3303f87fa6e23.fst?1460131758"; var DEFAULT_AVATAR_URL = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/contents/e76946cc-c272-4adf-9bb6-02cde0a4b57d/8fd984ea6fe1495147a3303f87fa6e23.fst?1460131758";
var ORIGIN = {x: 0, y: 0, z: 0}; var ORIGIN = {x: 0, y: 0, z: 0};
@ -8,6 +10,15 @@ var ROT_IDENT = {x: 0, y: 0, z: 0, w: 1};
describe("MyAvatar", function () { describe("MyAvatar", function () {
// backup/restore current skeletonModelURL
beforeAll(function() {
this.oldURL = MyAvatar.skeletonModelURL;
});
afterAll(function() {
MyAvatar.skeletonModelURL = this.oldURL;
});
// reload the avatar from scratch before each test. // reload the avatar from scratch before each test.
beforeEach(function (done) { beforeEach(function (done) {
MyAvatar.skeletonModelURL = DEFAULT_AVATAR_URL; MyAvatar.skeletonModelURL = DEFAULT_AVATAR_URL;
@ -20,12 +31,12 @@ describe("MyAvatar", function () {
MyAvatar.position = ORIGIN; MyAvatar.position = ORIGIN;
MyAvatar.orientation = ROT_IDENT; MyAvatar.orientation = ROT_IDENT;
// give the avatar 1/2 a second to settle on the ground in the idle pose. // give the avatar 1/2 a second to settle on the ground in the idle pose.
Script.setTimeout(function () { Script.setTimeout(function () {
done(); done();
}, 500); }, 500);
} }
}, 500); }, 500);
}); }, 10000 /* timeout -- allow time to download avatar*/);
// makes the assumption that there is solid ground somewhat underneath the avatar. // makes the assumption that there is solid ground somewhat underneath the avatar.
it("position and orientation getters", function () { it("position and orientation getters", function () {

View file

@ -1,3 +1,5 @@
/* eslint-env jasmine */
Script.include('../../../system/libraries/utils.js'); Script.include('../../../system/libraries/utils.js');
describe('Bind', function() { describe('Bind', function() {

View file

@ -1,3 +1,5 @@
/* eslint-env jasmine */
describe('Entity', function() { describe('Entity', function() {
var center = Vec3.sum( var center = Vec3.sum(
MyAvatar.position, MyAvatar.position,
@ -19,6 +21,14 @@ describe('Entity', function() {
}, },
}; };
it('serversExist', function() {
expect(Entities.serversExist()).toBe(true);
});
it('canRezTmp', function() {
expect(Entities.canRezTmp()).toBe(true);
});
beforeEach(function() { beforeEach(function() {
boxEntity = Entities.addEntity(boxProps); boxEntity = Entities.addEntity(boxProps);
}); });
@ -62,4 +72,4 @@ describe('Entity', function() {
props = Entities.getEntityProperties(boxEntity); props = Entities.getEntityProperties(boxEntity);
expect(props.lastEdited).toBeGreaterThan(prevLastEdited); expect(props.lastEdited).toBeGreaterThan(prevLastEdited);
}); });
}); });

View file

@ -1,13 +1,30 @@
/* eslint-env jasmine */
// Include testing library // Include testing library
Script.include('../../libraries/jasmine/jasmine.js'); Script.include('../../libraries/jasmine/jasmine.js');
Script.include('../../libraries/jasmine/hifi-boot.js') Script.include('../../libraries/jasmine/hifi-boot.js');
// Include unit tests // Include unit tests
// FIXME: Figure out why jasmine done() is not working. Script.include('avatarUnitTests.js');
// Script.include('avatarUnitTests.js');
Script.include('bindUnitTest.js'); Script.include('bindUnitTest.js');
Script.include('entityUnitTests.js'); Script.include('entityUnitTests.js');
describe("jasmine internal tests", function() {
it('should support async .done()', function(done) {
var start = new Date;
Script.setTimeout(function() {
expect((new Date - start)/1000).toBeCloseTo(0.5, 1);
done();
}, 500);
});
// jasmine pending test
xit('disabled test', function() {
expect(false).toBe(true);
});
});
// invoke Script.stop (after any async tests complete)
jasmine.getEnv().addReporter({ jasmineDone: Script.stop });
// Run the tests // Run the tests
jasmine.getEnv().execute(); jasmine.getEnv().execute();
Script.stop();

3
tutorial/Changelog.md Normal file
View file

@ -0,0 +1,3 @@
* home-tutorial-34
* Update tutorial to only start if `HMD.active`
* Update builder's grid to use "Good - Sub-meshes" for collision shape type