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/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 cf9b582e2c..3b5cedb3ad 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -105,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 @@ -298,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 a07f18f5f7..12909a16eb 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -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;