diff --git a/examples/timer.js b/examples/timer.js new file mode 100644 index 0000000000..c7ad0290ab --- /dev/null +++ b/examples/timer.js @@ -0,0 +1,8 @@ +var one_timer = Script.setTimeout(function() { print("One time timer fired!"); }, 10000); +var multiple_timer = Script.setInterval(function() { print("Repeating timer fired!"); }, 1000); + +// this would stop a scheduled single shot timer +Script.clearTimeout(one_timer); + +// this stops the repeating timer +Script.clearInterval(multiple_timer); \ No newline at end of file diff --git a/interface/src/AbstractLoggerInterface.h b/interface/src/AbstractLoggerInterface.h index d8a48fc2fd..cedab1fad2 100644 --- a/interface/src/AbstractLoggerInterface.h +++ b/interface/src/AbstractLoggerInterface.h @@ -17,6 +17,7 @@ class AbstractLoggerInterface : public QObject { Q_OBJECT public: + AbstractLoggerInterface(QObject* parent = NULL) : QObject(parent) {}; inline bool extraDebugging() { return _extraDebugging; }; inline void setExtraDebugging(bool debugging) { _extraDebugging = debugging; }; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a8e5a540fd..a1bbe68efa 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -152,7 +152,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _resetRecentMaxPacketsSoon(true), _swatch(NULL), _pasteMode(false), - _logger(new FileLogger()), + _logger(new FileLogger(this)), _persistThread(NULL) { _myAvatar = _avatarManager.getMyAvatar(); @@ -319,7 +319,7 @@ Application::~Application() { _persistThread->deleteLater(); _persistThread = NULL; } - + storeSizeAndPosition(); saveScripts(); _sharedVoxelSystem.changeTree(new VoxelTree); @@ -328,9 +328,7 @@ Application::~Application() { Menu::getInstance()->deleteLater(); _myAvatar = NULL; - - delete _logger; - delete _settings; + delete _glWidget; } @@ -4038,33 +4036,33 @@ void Application::packetSent(quint64 length) { _bandwidthMeter.outputStream(BandwidthMeter::VOXELS).updateValue(length); } -void Application::loadScripts(){ - // loads all saved scripts - QSettings* settings = new QSettings(this); - int size = settings->beginReadArray("Settings"); - for(int i=0; isetArrayIndex(i); - QString string = settings->value("script").toString(); - loadScript(string); - } - settings->endArray(); - +void Application::loadScripts() { + // loads all saved scripts + QSettings* settings = new QSettings(this); + int size = settings->beginReadArray("Settings"); + + for (int i = 0; i < size; ++i){ + settings->setArrayIndex(i); + QString string = settings->value("script").toString(); + loadScript(string); + } + + settings->endArray(); } -void Application::saveScripts(){ - // saves all current running scripts - QSettings* settings = new QSettings(this); - settings->beginWriteArray("Settings"); - for(int i=0; i<_activeScripts.size(); ++i){ - settings->setArrayIndex(i); - settings->setValue("script", _activeScripts.at(i)); - } - settings->endArray(); - +void Application::saveScripts() { + // saves all current running scripts + QSettings* settings = new QSettings(this); + settings->beginWriteArray("Settings"); + for (int i = 0; i < _activeScripts.size(); ++i){ + settings->setArrayIndex(i); + settings->setValue("script", _activeScripts.at(i)); + } + + settings->endArray(); } -void Application::removeScriptName(const QString& fileNameString) -{ +void Application::removeScriptName(const QString& fileNameString) { _activeScripts.removeOne(fileNameString); } @@ -4096,7 +4094,8 @@ void Application::loadScript(const QString& fileNameString) { // start the script on a new thread... bool wantMenuItems = true; // tells the ScriptEngine object to add menu items for itself - ScriptEngine* scriptEngine = new ScriptEngine(script, wantMenuItems, fileName, Menu::getInstance(), &_controllerScriptingInterface); + ScriptEngine* scriptEngine = new ScriptEngine(script, wantMenuItems, fileName, Menu::getInstance(), + &_controllerScriptingInterface); scriptEngine->setupMenuItems(); // setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so diff --git a/interface/src/FileLogger.cpp b/interface/src/FileLogger.cpp index 3c98b285a3..81c626a46e 100644 --- a/interface/src/FileLogger.cpp +++ b/interface/src/FileLogger.cpp @@ -18,7 +18,10 @@ const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt"; const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss"; const QString LOGS_DIRECTORY = "Logs"; -FileLogger::FileLogger() : _logData(NULL) { +FileLogger::FileLogger(QObject* parent) : + AbstractLoggerInterface(parent), + _logData(NULL) +{ setExtraDebugging(false); _fileName = FileUtils::standardPath(LOGS_DIRECTORY); diff --git a/interface/src/FileLogger.h b/interface/src/FileLogger.h index 6a17032ae2..35cafa4db7 100644 --- a/interface/src/FileLogger.h +++ b/interface/src/FileLogger.h @@ -16,7 +16,7 @@ class FileLogger : public AbstractLoggerInterface { Q_OBJECT public: - FileLogger(); + FileLogger(QObject* parent = NULL); virtual void addMessage(QString); virtual QStringList getLogData() { return _logData; }; diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index d128206c80..e2c3bfafdd 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -738,6 +738,22 @@ ExtractedMesh extractMesh(const FBXNode& object) { return data.extracted; } +FBXBlendshape extractBlendshape(const FBXNode& object) { + FBXBlendshape blendshape; + foreach (const FBXNode& data, object.children) { + if (data.name == "Indexes") { + blendshape.indices = getIntVector(data.properties, 0); + + } else if (data.name == "Vertices") { + blendshape.vertices = createVec3Vector(getDoubleVector(data.properties, 0)); + + } else if (data.name == "Normals") { + blendshape.normals = createVec3Vector(getDoubleVector(data.properties, 0)); + } + } + return blendshape; +} + void setTangents(FBXMesh& mesh, int firstIndex, int secondIndex) { glm::vec3 normal = glm::normalize(mesh.normals.at(firstIndex)); glm::vec3 bitangent = glm::cross(normal, mesh.vertices.at(secondIndex) - mesh.vertices.at(firstIndex)); @@ -760,6 +776,49 @@ QVector getIndices(const QVector ids, QVector modelIDs) { return indices; } +typedef QPair WeightedIndex; + +void addBlendshapes(const ExtractedBlendshape& extracted, const QList& indices, ExtractedMesh& extractedMesh) { + foreach (const WeightedIndex& index, indices) { + extractedMesh.mesh.blendshapes.resize(max(extractedMesh.mesh.blendshapes.size(), index.first + 1)); + extractedMesh.blendshapeIndexMaps.resize(extractedMesh.mesh.blendshapes.size()); + FBXBlendshape& blendshape = extractedMesh.mesh.blendshapes[index.first]; + QHash& blendshapeIndexMap = extractedMesh.blendshapeIndexMaps[index.first]; + for (int i = 0; i < extracted.blendshape.indices.size(); i++) { + int oldIndex = extracted.blendshape.indices.at(i); + for (QMultiHash::const_iterator it = extractedMesh.newIndices.constFind(oldIndex); + it != extractedMesh.newIndices.constEnd() && it.key() == oldIndex; it++) { + QHash::iterator blendshapeIndex = blendshapeIndexMap.find(it.value()); + if (blendshapeIndex == blendshapeIndexMap.end()) { + blendshapeIndexMap.insert(it.value(), blendshape.indices.size()); + blendshape.indices.append(it.value()); + blendshape.vertices.append(extracted.blendshape.vertices.at(i) * index.second); + blendshape.normals.append(extracted.blendshape.normals.at(i) * index.second); + } else { + blendshape.vertices[*blendshapeIndex] += extracted.blendshape.vertices.at(i) * index.second; + blendshape.normals[*blendshapeIndex] += extracted.blendshape.normals.at(i) * index.second; + } + } + } + } +} + +QString getTopModelID(const QMultiHash& parentMap, + const QHash& models, const QString& modelID) { + QString topID = modelID; + forever { + foreach (const QString& parentID, parentMap.values(topID)) { + if (models.contains(parentID)) { + topID = parentID; + goto outerContinue; + } + } + return topID; + + outerContinue: ; + } +} + FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) { QHash meshes; QVector blendshapes; @@ -799,7 +858,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QVector jointRightFingertipIDs(jointRightFingertipNames.size()); QVariantHash blendshapeMappings = mapping.value("bs").toHash(); - typedef QPair WeightedIndex; QMultiHash blendshapeIndices; for (int i = 0;; i++) { QByteArray blendshapeName = FACESHIFT_BLENDSHAPES[i]; @@ -827,22 +885,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) meshes.insert(getID(object.properties), extractMesh(object)); } else { // object.properties.at(2) == "Shape" - ExtractedBlendshape extracted = { getID(object.properties) }; - - foreach (const FBXNode& data, object.children) { - if (data.name == "Indexes") { - extracted.blendshape.indices = getIntVector(data.properties, 0); - - } else if (data.name == "Vertices") { - extracted.blendshape.vertices = createVec3Vector( - getDoubleVector(data.properties, 0)); - - } else if (data.name == "Normals") { - extracted.blendshape.normals = createVec3Vector( - getDoubleVector(data.properties, 0)); - } - } - + ExtractedBlendshape extracted = { getID(object.properties), extractBlendshape(object) }; blendshapes.append(extracted); } } else if (object.name == "Model") { @@ -900,6 +943,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) bool rotationMaxX = false, rotationMaxY = false, rotationMaxZ = false; glm::vec3 rotationMin, rotationMax; FBXModel model = { name, -1 }; + ExtractedMesh* mesh = NULL; + QVector blendshapes; foreach (const FBXNode& subobject, object.children) { bool properties = false; QByteArray propertyName; @@ -969,9 +1014,23 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } } else if (subobject.name == "Vertices") { // it's a mesh as well as a model - meshes.insert(getID(object.properties), extractMesh(object)); + mesh = &meshes[getID(object.properties)]; + *mesh = extractMesh(object); + + } else if (subobject.name == "Shape") { + ExtractedBlendshape blendshape = { subobject.properties.at(0).toString(), + extractBlendshape(subobject) }; + blendshapes.append(blendshape); } } + + // add the blendshapes included in the model, if any + if (mesh) { + foreach (const ExtractedBlendshape& extracted, blendshapes) { + addBlendshapes(extracted, blendshapeIndices.values(extracted.id.toLatin1()), *mesh); + } + } + // see FBX documentation, http://download.autodesk.com/us/fbx/20112/FBX_SDK_HELP/index.html model.translation = translation; model.preTransform = glm::translate(rotationOffset) * glm::translate(rotationPivot); @@ -1084,29 +1143,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QString blendshapeChannelID = parentMap.value(extracted.id); QString blendshapeID = parentMap.value(blendshapeChannelID); QString meshID = parentMap.value(blendshapeID); - ExtractedMesh& extractedMesh = meshes[meshID]; - foreach (const WeightedIndex& index, blendshapeChannelIndices.values(blendshapeChannelID)) { - extractedMesh.mesh.blendshapes.resize(max(extractedMesh.mesh.blendshapes.size(), index.first + 1)); - extractedMesh.blendshapeIndexMaps.resize(extractedMesh.mesh.blendshapes.size()); - FBXBlendshape& blendshape = extractedMesh.mesh.blendshapes[index.first]; - QHash& blendshapeIndexMap = extractedMesh.blendshapeIndexMaps[index.first]; - for (int i = 0; i < extracted.blendshape.indices.size(); i++) { - int oldIndex = extracted.blendshape.indices.at(i); - for (QMultiHash::const_iterator it = extractedMesh.newIndices.constFind(oldIndex); - it != extractedMesh.newIndices.constEnd() && it.key() == oldIndex; it++) { - QHash::iterator blendshapeIndex = blendshapeIndexMap.find(it.value()); - if (blendshapeIndex == blendshapeIndexMap.end()) { - blendshapeIndexMap.insert(it.value(), blendshape.indices.size()); - blendshape.indices.append(it.value()); - blendshape.vertices.append(extracted.blendshape.vertices.at(i) * index.second); - blendshape.normals.append(extracted.blendshape.normals.at(i) * index.second); - } else { - blendshape.vertices[*blendshapeIndex] += extracted.blendshape.vertices.at(i) * index.second; - blendshape.normals[*blendshapeIndex] += extracted.blendshape.normals.at(i) * index.second; - } - } - } - } + addBlendshapes(extracted, blendshapeChannelIndices.values(blendshapeChannelID), meshes[meshID]); } // get offset transform from mapping @@ -1121,6 +1158,20 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QVector modelIDs; QSet remainingModels; for (QHash::const_iterator model = models.constBegin(); model != models.constEnd(); model++) { + // models with clusters must be parented to the cluster top + foreach (const QString& deformerID, childMap.values(model.key())) { + foreach (const QString& clusterID, childMap.values(deformerID)) { + if (!clusters.contains(clusterID)) { + continue; + } + QString topID = getTopModelID(parentMap, models, childMap.value(clusterID)); + childMap.remove(parentMap.take(model.key()), model.key()); + parentMap.insert(model.key(), topID); + goto outerBreak; + } + } + outerBreak: + // make sure the parent is in the child map QString parent = parentMap.value(model.key()); if (!childMap.contains(parent, model.key())) { @@ -1129,20 +1180,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) remainingModels.insert(model.key()); } while (!remainingModels.isEmpty()) { - QString top = *remainingModels.constBegin(); - forever { - foreach (const QString& name, parentMap.values(top)) { - if (models.contains(name)) { - top = name; - goto outerContinue; - } - } - top = parentMap.value(top); - break; - - outerContinue: ; - } - appendModelIDs(top, childMap, models, remainingModels, modelIDs); + QString topID = getTopModelID(parentMap, models, *remainingModels.constBegin()); + appendModelIDs(parentMap.value(topID), childMap, models, remainingModels, modelIDs); } // convert the models to joints diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 656402e291..3b5cedb3ad 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -40,7 +40,8 @@ static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* eng } -ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, const QString& fileNameString, AbstractMenuInterface* menu, +ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, const QString& fileNameString, + AbstractMenuInterface* menu, AbstractControllerScriptingInterface* controllerScriptingInterface) : _isAvatar(false), _dataServerScriptingInterface(), @@ -104,8 +105,6 @@ bool ScriptEngine::setScriptContents(const QString& scriptContents) { return true; } -Q_SCRIPT_DECLARE_QMETAOBJECT(AudioInjectorOptions, QObject*) - void ScriptEngine::init() { if (_isInitialized) { return; // only initialize once @@ -297,4 +296,51 @@ void ScriptEngine::stop() { _isFinished = true; } +void ScriptEngine::timerFired() { + QTimer* callingTimer = reinterpret_cast(sender()); + + // call the associated JS function, if it exists + QScriptValue timerFunction = _timerFunctionMap.value(callingTimer); + if (timerFunction.isValid()) { + timerFunction.call(); + } + + if (!callingTimer->isActive()) { + // this timer is done, we can kill it + qDebug() << "Deleting a single shot timer"; + delete callingTimer; + } +} + +QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot) { + // create the timer, add it to the map, and start it + QTimer* newTimer = new QTimer(this); + newTimer->setSingleShot(isSingleShot); + + connect(newTimer, &QTimer::timeout, this, &ScriptEngine::timerFired); + + // make sure the timer stops when the script does + connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop); + + _timerFunctionMap.insert(newTimer, function); + + newTimer->start(intervalMS); + return newTimer; +} + +QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) { + return setupTimerWithInterval(function, intervalMS, false); +} + +QObject* ScriptEngine::setTimeout(const QScriptValue& function, int timeoutMS) { + return setupTimerWithInterval(function, timeoutMS, true); +} + +void ScriptEngine::stopTimer(QTimer *timer) { + if (_timerFunctionMap.contains(timer)) { + timer->stop(); + _timerFunctionMap.remove(timer); + delete timer; + } +} diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 8f29379266..12909a16eb 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -33,8 +33,8 @@ class ScriptEngine : public QObject { Q_OBJECT public: ScriptEngine(const QString& scriptContents = NO_SCRIPT, bool wantMenuItems = false, - const QString& scriptMenuName = QString(""), AbstractMenuInterface* menu = NULL, - AbstractControllerScriptingInterface* controllerScriptingInterface = NULL); + const QString& scriptMenuName = QString(""), AbstractMenuInterface* menu = NULL, + AbstractControllerScriptingInterface* controllerScriptingInterface = NULL); ~ScriptEngine(); @@ -59,13 +59,20 @@ public: bool isAvatar() const { return _isAvatar; } void setAvatarData(AvatarData* avatarData, const QString& objectName); + + void timerFired(); public slots: void init(); void run(); /// runs continuously until Agent.stop() is called void stop(); void evaluate(); /// initializes the engine, and evaluates the script, but then returns control to caller - + + QObject* setInterval(const QScriptValue& function, int intervalMS); + QObject* setTimeout(const QScriptValue& function, int timeoutMS); + void clearInterval(QObject* timer) { stopTimer(reinterpret_cast(timer)); } + void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast(timer)); } + signals: void willSendAudioDataCallback(); void willSendVisualDataCallback(); @@ -73,15 +80,18 @@ signals: void finished(const QString& fileNameString); protected: - QString _scriptContents; bool _isFinished; bool _isRunning; bool _isInitialized; QScriptEngine _engine; bool _isAvatar; + QHash _timerFunctionMap; private: + QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot); + void stopTimer(QTimer* timer); + static VoxelsScriptingInterface _voxelsScriptingInterface; static ParticlesScriptingInterface _particlesScriptingInterface; AbstractControllerScriptingInterface* _controllerScriptingInterface;