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

View file

@ -28,11 +28,15 @@ const int MAX_HISTORY_SIZE = 64;
const QString COMMAND_STYLE = "color: #266a9b;";
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 GUTTER_PREVIOUS_COMMAND = "<span style=\"color: #57b8bb;\">&lt;</span>";
const QString GUTTER_ERROR = "<span style=\"color: #d13b22;\">X</span>";
const QString JSConsole::_consoleFileName { "about:console" };
JSConsole::JSConsole(QWidget* parent, ScriptEngine* scriptEngine) :
QWidget(parent),
_ui(new Ui::Console),
@ -77,6 +81,8 @@ void JSConsole::setScriptEngine(ScriptEngine* scriptEngine) {
}
if (_scriptEngine != NULL) {
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);
if (_ownScriptEngine) {
_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
_ownScriptEngine = scriptEngine == NULL;
_scriptEngine = _ownScriptEngine ? DependencyManager::get<ScriptEngines>()->loadScript(QString(), false) : scriptEngine;
_ownScriptEngine = (scriptEngine == NULL);
_scriptEngine = _ownScriptEngine ? DependencyManager::get<ScriptEngines>()->loadScript(_consoleFileName, false) : scriptEngine;
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);
}
@ -107,11 +115,10 @@ void JSConsole::executeCommand(const QString& command) {
QScriptValue JSConsole::executeCommandInWatcher(const QString& command) {
QScriptValue result;
static const QString filename = "JSConcole";
QMetaObject::invokeMethod(_scriptEngine, "evaluate", Qt::ConnectionType::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, result),
Q_ARG(const QString&, command),
Q_ARG(const QString&, filename));
Q_ARG(const QString&, _consoleFileName));
return result;
}
@ -134,16 +141,26 @@ void JSConsole::commandFinished() {
resetCurrentCommandHistory();
}
void JSConsole::handleError(const QString& scriptName, const QString& message) {
void JSConsole::handleError(const QString& message, const QString& scriptName) {
Q_UNUSED(scriptName);
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);
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) {
_ui->promptTextEdit->setFocus();
}

View file

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

View file

@ -1097,28 +1097,27 @@ void AudioClient::handleRecordedAudioInput(const QByteArray& audio) {
}
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();
while (samplesNeeded > 0) {
// lock for every write to avoid locking out the device callback
// this lock is intentional - the buffer is only lock-free in its use in the device callback
RecursiveLock lock(_localAudioMutex);
// unlock between every write to allow device switching
Lock 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);
if (samplesNeeded <= 0) {
if (samplesNeeded < maxOutputSamples) {
// avoid overwriting the buffer to prevent losing frames
break;
}
@ -1168,16 +1167,18 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
memset(mixBuffer, 0, AudioConstants::NETWORK_FRAME_SAMPLES_STEREO * sizeof(float));
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;
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
memset(_localScratchBuffer, 0, bytesToRead);
if (0 < injector->getLocalBuffer()->readData((char*)_localScratchBuffer, bytesToRead)) {
if (0 < injectorBuffer->readData((char*)_localScratchBuffer, bytesToRead)) {
if (injector->isAmbisonic()) {
@ -1317,15 +1318,17 @@ void AudioClient::setIsStereoInput(bool isStereoInput) {
}
bool AudioClient::outputLocalInjector(AudioInjector* injector) {
Lock lock(_injectorsMutex);
if (injector->getLocalBuffer() && _audioInput ) {
// just add it to the vector of active local injectors, if
// not already there.
// Since this is invoked with invokeMethod, there _should_ be
// no reason to lock access to the vector of injectors.
AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer();
if (injectorBuffer) {
// local injectors are on the AudioInjectorsThread, so we must guard access
Lock lock(_injectorsMutex);
if (!_activeLocalAudioInjectors.contains(injector)) {
qCDebug(audioclient) << "adding new injector";
_activeLocalAudioInjectors.append(injector);
// move local buffer to the LocalAudioThread to avoid dataraces with AudioInjector (like stop())
injectorBuffer->setParent(nullptr);
injectorBuffer->moveToThread(&_localAudioThread);
} else {
qCDebug(audioclient) << "injector exists in active list already";
}
@ -1333,7 +1336,7 @@ bool AudioClient::outputLocalInjector(AudioInjector* injector) {
return true;
} else {
// no local buffer or audio
// no local buffer
return false;
}
}
@ -1452,7 +1455,7 @@ void AudioClient::outputNotify() {
bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) {
bool supportedFormat = false;
RecursiveLock lock(_localAudioMutex);
Lock lock(_localAudioMutex);
_localSamplesAvailable.exchange(0, std::memory_order_release);
// cleanup any previously initialized device
@ -1681,8 +1684,12 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
int injectorSamplesPopped = 0;
{
RecursiveLock lock(_audio->_localAudioMutex);
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));
if ((injectorSamplesPopped = _localInjectorsStream.appendSamples(mixBuffer, samplesRequested, append)) > 0) {
_audio->_localSamplesAvailable.fetch_sub(injectorSamplesPopped, std::memory_order_release);

View file

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

View file

@ -33,7 +33,11 @@ public:
PacketType packetType, QString codecName = QString(""));
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 shouldLoopbackInjectors() { return false; }
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 {
return (_state & state) == state;
}
@ -87,11 +91,7 @@ void AudioInjector::finish() {
emit finished();
if (_localBuffer) {
_localBuffer->stop();
_localBuffer->deleteLater();
_localBuffer = NULL;
}
deleteLocalBuffer();
if (stateHas(AudioInjectorState::PendingDelete)) {
// we've been asked to delete after finishing, trigger a deleteLater here
@ -163,7 +163,7 @@ bool AudioInjector::injectLocally() {
if (_localAudioInterface) {
if (_audioData.size() > 0) {
_localBuffer = new AudioInjectorLocalBuffer(_audioData, this);
_localBuffer = new AudioInjectorLocalBuffer(_audioData);
_localBuffer->open(QIODevice::ReadOnly);
_localBuffer->setShouldLoop(_options.loop);
@ -172,7 +172,8 @@ bool AudioInjector::injectLocally() {
_localBuffer->setCurrentOffset(_currentSendOffset);
// 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) {
qCDebug(audio) << "AudioInjector::injectLocally could not output locally via _localAudioInterface";
@ -185,6 +186,14 @@ bool AudioInjector::injectLocally() {
return success;
}
void AudioInjector::deleteLocalBuffer() {
if (_localBuffer) {
_localBuffer->stop();
_localBuffer->deleteLater();
_localBuffer = nullptr;
}
}
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_IMMEDIATELY = 0;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -19,8 +19,8 @@
#include <DependencyManager.h>
#ifdef Q_OS_WIN
#include <comdef.h>
#include <Wbemidl.h>
#include <Windows.h>
#include <winreg.h>
#endif //Q_OS_WIN
#ifdef Q_OS_MAC
@ -30,6 +30,9 @@
#endif //Q_OS_MAC
static const QString FALLBACK_FINGERPRINT_KEY = "fallbackFingerprint";
QUuid FingerprintUtils::_machineFingerprint { QUuid() };
QString FingerprintUtils::getMachineFingerprintString() {
QString uuidString;
#ifdef Q_OS_LINUX
@ -47,122 +50,32 @@ QString FingerprintUtils::getMachineFingerprintString() {
#endif //Q_OS_MAC
#ifdef Q_OS_WIN
HRESULT hres;
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;
}
HKEY cryptoKey;
// initialize WbemLocator
hres = CoCreateInstance(
CLSID_WbemLocator,
0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID *) &pLoc);
// try and open the key that contains the machine GUID
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Cryptography", 0, KEY_READ, &cryptoKey) == ERROR_SUCCESS) {
DWORD type;
DWORD guidSize;
if (FAILED(hres)) {
qCDebug(networking) << "Failed to initialize WbemLocator";
return uuidString;
}
// Connect to WMI through the IWbemLocator::ConnectServer method
IWbemServices *pSvc = NULL;
const char* MACHINE_GUID_KEY = "MachineGuid";
// Connect to the root\cimv2 namespace with
// the current user and obtain pointer pSvc
// to make IWbemServices calls.
hres = pLoc->ConnectServer(
_bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
NULL, // User name. NULL = current user
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
);
// try and retrieve the size of the GUID value
if (RegQueryValueEx(cryptoKey, MACHINE_GUID_KEY, NULL, &type, NULL, &guidSize) == ERROR_SUCCESS) {
// make sure that the value is a string
if (type == REG_SZ) {
// retrieve the machine GUID and return that as our UUID string
std::string machineGUID(guidSize / sizeof(char), '\0');
if (FAILED(hres)) {
pLoc->Release();
qCDebug(networking) << "Failed to connect to WMI";
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;
if (RegQueryValueEx(cryptoKey, MACHINE_GUID_KEY, NULL, NULL,
reinterpret_cast<LPBYTE>(&machineGUID[0]), &guidSize) == ERROR_SUCCESS) {
uuidString = QString::fromStdString(machineGUID);
}
}
}
VariantClear(&vtProp);
pclsObj->Release();
RegCloseKey(cryptoKey);
}
pEnumerator->Release();
// Cleanup
pSvc->Release();
pLoc->Release();
qCDebug(networking) << "Windows BIOS UUID: " << uuidString;
#endif //Q_OS_WIN
return uuidString;
@ -171,29 +84,36 @@ QString FingerprintUtils::getMachineFingerprintString() {
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()) {
// 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();
// 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()) {
// 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:
static QString getMachineFingerprintString();
static QUuid _machineFingerprint;
};
#endif // hifi_FingerprintUtils_h

View file

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

View file

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

View file

@ -16,9 +16,10 @@
#include <QObject>
#include <QString>
#include <QtScript/QScriptable>
/// Scriptable Mat4 object. Used exclusively in the JavaScript API
class Mat4 : public QObject {
class Mat4 : public QObject, protected QScriptable {
Q_OBJECT
public slots:
@ -43,7 +44,7 @@ public slots:
glm::vec3 getRight(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

View file

@ -15,7 +15,9 @@
#include <OctreeConstants.h>
#include <GLMHelpers.h>
#include <glm/gtx/string_cast.hpp>
#include "ScriptEngineLogging.h"
#include "ScriptEngine.h"
#include "Quat.h"
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);
}
void Quat::print(const QString& label, const glm::quat& q) {
qCDebug(scriptengine) << qPrintable(label) << q.x << "," << q.y << "," << q.z << "," << q.w;
void Quat::print(const QString& label, const glm::quat& q, bool asDegrees) {
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) {

View file

@ -18,6 +18,7 @@
#include <QObject>
#include <QString>
#include <QtScript/QScriptable>
/**jsdoc
* A Quaternion
@ -30,7 +31,7 @@
*/
/// 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
public slots:
@ -58,7 +59,7 @@ public slots:
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);
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);
glm::quat cancelOutRollAndPitch(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();
}
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?
engine->globalObject().property("Script").property("print")
.call(engine->nullValue(), QScriptValueList({ message }));
if (ScriptEngine *scriptEngine = qobject_cast<ScriptEngine*>(engine)) {
scriptEngine->print(message);
}
return QScriptValue();
}
@ -472,6 +472,11 @@ void ScriptEngine::scriptInfoMessage(const QString& message) {
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
// callAnimationStateHandler requires that the type be registered.
// 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 scriptWarningMessage(const QString& message);
void scriptInfoMessage(const QString& message);
void scriptPrintedMessage(const QString& message);
int getNumRunningEntityScripts() 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() != "https" &&
scriptFilename.scheme() != "atp" &&
scriptFilename.scheme() != "file")) {
scriptFilename.scheme() != "file" &&
scriptFilename.scheme() != "about")) {
// deal with a "url" like c:/something
scriptUrl = normalizeScriptURL(QUrl::fromLocalFile(scriptFilename.toString()));
} else {
@ -472,7 +473,7 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL
}, Qt::QueuedConnection);
if (scriptFilename.isEmpty()) {
if (scriptFilename.isEmpty() || !scriptUrl.isValid()) {
launchScriptEngine(scriptEngine);
} else {
// connect to the appropriate signals of this script engine

View file

@ -14,6 +14,7 @@
#include <QDebug>
#include "ScriptEngineLogging.h"
#include "ScriptEngine.h"
#include "ScriptUUID.h"
QUuid ScriptUUID::fromString(const QString& s) {
@ -36,6 +37,11 @@ bool ScriptUUID::isNull(const QUuid& id) {
return id.isNull();
}
void ScriptUUID::print(const QString& lable, const QUuid& id) {
qCDebug(scriptengine) << qPrintable(lable) << id.toString();
void ScriptUUID::print(const QString& label, const QUuid& id) {
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
#include <QUuid>
#include <QtScript/QScriptable>
/// 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
public slots:
@ -26,7 +27,7 @@ public slots:
QUuid generate();
bool isEqual(const QUuid& idA, const QUuid& idB);
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

View file

@ -14,20 +14,26 @@
#include <QDebug>
#include <GLMHelpers.h>
#include <glm/gtx/string_cast.hpp>
#include "ScriptEngineLogging.h"
#include "NumericalConstants.h"
#include "Vec3.h"
#include "ScriptEngine.h"
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));
return glm::degrees(radians);
}
void Vec3::print(const QString& lable, const glm::vec3& v) {
qCDebug(scriptengine) << qPrintable(lable) << v.x << "," << v.y << "," << v.z;
void Vec3::print(const QString& label, const glm::vec3& v) {
QString message = QString("%1 %2").arg(qPrintable(label));
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) {

View file

@ -17,6 +17,7 @@
#include <QtCore/QObject>
#include <QtCore/QString>
#include <QtScript/QScriptable>
#include "GLMHelpers.h"
@ -48,7 +49,7 @@
*/
/// 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_PROPERTY(glm::vec3 UNIT_X READ UNIT_X 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 ' +
errorCount + ' ' + ERROR + '.<span>');
}
if (pending.length)
if (pending.length) {
print ('<span style="color:darkorange">disabled: <br />&nbsp;&nbsp;&nbsp;'+
pending.join('<br />&nbsp;&nbsp;&nbsp;')+'</span>');
}
print('Tests completed in ' + (endTime - startTime) + 'ms.');
};
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
// 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 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 () {
// backup/restore current skeletonModelURL
beforeAll(function() {
this.oldURL = MyAvatar.skeletonModelURL;
});
afterAll(function() {
MyAvatar.skeletonModelURL = this.oldURL;
});
// reload the avatar from scratch before each test.
beforeEach(function (done) {
MyAvatar.skeletonModelURL = DEFAULT_AVATAR_URL;
@ -20,12 +31,12 @@ describe("MyAvatar", function () {
MyAvatar.position = ORIGIN;
MyAvatar.orientation = ROT_IDENT;
// give the avatar 1/2 a second to settle on the ground in the idle pose.
Script.setTimeout(function () {
Script.setTimeout(function () {
done();
}, 500);
}
}, 500);
});
}, 10000 /* timeout -- allow time to download avatar*/);
// makes the assumption that there is solid ground somewhat underneath the avatar.
it("position and orientation getters", function () {

View file

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

View file

@ -1,3 +1,5 @@
/* eslint-env jasmine */
describe('Entity', function() {
var center = Vec3.sum(
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() {
boxEntity = Entities.addEntity(boxProps);
});
@ -62,4 +72,4 @@ describe('Entity', function() {
props = Entities.getEntityProperties(boxEntity);
expect(props.lastEdited).toBeGreaterThan(prevLastEdited);
});
});
});

View file

@ -1,13 +1,30 @@
/* eslint-env jasmine */
// Include testing library
Script.include('../../libraries/jasmine/jasmine.js');
Script.include('../../libraries/jasmine/hifi-boot.js')
Script.include('../../libraries/jasmine/hifi-boot.js');
// 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('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
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