diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index be23dcfa25..a0c80453e0 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -371,25 +371,39 @@ void Agent::executeScript() { using namespace recording; static const FrameType AUDIO_FRAME_TYPE = Frame::registerFrameType(AudioConstants::getAudioFrameName()); Frame::registerFrameHandler(AUDIO_FRAME_TYPE, [this, &scriptedAvatar](Frame::ConstPointer frame) { - const QByteArray& audio = frame->data; static quint16 audioSequenceNumber{ 0 }; - Transform audioTransform; + QByteArray audio(frame->data); + + if (_isNoiseGateEnabled) { + static int numSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; + _noiseGate.gateSamples(reinterpret_cast(audio.data()), numSamples); + } + + computeLoudness(&audio, scriptedAvatar); + + // the codec needs a flush frame before sending silent packets, so + // do not send one if the gate closed in this block (eventually this can be crossfaded). + auto packetType = PacketType::MicrophoneAudioNoEcho; + if (scriptedAvatar->getAudioLoudness() == 0.0f && !_noiseGate.closedInLastBlock()) { + packetType = PacketType::SilentAudioFrame; + } + + Transform audioTransform; auto headOrientation = scriptedAvatar->getHeadOrientation(); audioTransform.setTranslation(scriptedAvatar->getPosition()); audioTransform.setRotation(headOrientation); - computeLoudness(&audio, scriptedAvatar); - QByteArray encodedBuffer; if (_encoder) { _encoder->encode(audio, encodedBuffer); } else { encodedBuffer = audio; } + AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber, audioTransform, scriptedAvatar->getPosition(), glm::vec3(0), - PacketType::MicrophoneAudioNoEcho, _selectedCodecName); + packetType, _selectedCodecName); }); auto avatarHashMap = DependencyManager::set(); @@ -483,6 +497,14 @@ void Agent::setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; } +void Agent::setIsNoiseGateEnabled(bool isNoiseGateEnabled) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setIsNoiseGateEnabled", Q_ARG(bool, isNoiseGateEnabled)); + return; + } + _isNoiseGateEnabled = isNoiseGateEnabled; +} + void Agent::setIsAvatar(bool isAvatar) { // this must happen on Agent's main thread if (QThread::currentThread() != thread()) { diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 0ce7b71d5d..620ac8e047 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -29,6 +29,7 @@ #include +#include "AudioNoiseGate.h" #include "MixedAudioStream.h" #include "avatars/ScriptableAvatar.h" @@ -38,6 +39,7 @@ class Agent : public ThreadedAssignment { Q_PROPERTY(bool isAvatar READ isAvatar WRITE setIsAvatar) Q_PROPERTY(bool isPlayingAvatarSound READ isPlayingAvatarSound) Q_PROPERTY(bool isListeningToAudioStream READ isListeningToAudioStream WRITE setIsListeningToAudioStream) + Q_PROPERTY(bool isNoiseGateEnabled READ isNoiseGateEnabled WRITE setIsNoiseGateEnabled) Q_PROPERTY(float lastReceivedAudioLoudness READ getLastReceivedAudioLoudness) Q_PROPERTY(QUuid sessionUUID READ getSessionUUID) @@ -52,6 +54,9 @@ public: bool isListeningToAudioStream() const { return _isListeningToAudioStream; } void setIsListeningToAudioStream(bool isListeningToAudioStream); + bool isNoiseGateEnabled() const { return _isNoiseGateEnabled; } + void setIsNoiseGateEnabled(bool isNoiseGateEnabled); + float getLastReceivedAudioLoudness() const { return _lastReceivedAudioLoudness; } QUuid getSessionUUID() const; @@ -106,6 +111,9 @@ private: QTimer* _avatarIdentityTimer = nullptr; QHash _outgoingScriptAudioSequenceNumbers; + AudioNoiseGate _noiseGate; + bool _isNoiseGateEnabled { false }; + CodecPluginPointer _codec; QString _selectedCodecName; Encoder* _encoder { nullptr }; diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index 47071b10b7..f674ab006c 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -455,13 +455,13 @@ void EntityScriptServer::addingEntity(const EntityItemID& entityID) { void EntityScriptServer::deletingEntity(const EntityItemID& entityID) { if (_entityViewer.getTree() && !_shuttingDown && _entitiesScriptEngine) { - _entitiesScriptEngine->unloadEntityScript(entityID); + _entitiesScriptEngine->unloadEntityScript(entityID, true); } } void EntityScriptServer::entityServerScriptChanging(const EntityItemID& entityID, const bool reload) { if (_entityViewer.getTree() && !_shuttingDown) { - _entitiesScriptEngine->unloadEntityScript(entityID); + _entitiesScriptEngine->unloadEntityScript(entityID, true); checkAndCallPreload(entityID, reload); } } diff --git a/interface/resources/styles/log_dialog.qss b/interface/resources/styles/log_dialog.qss index 1fc4df0717..d3ae4e0a00 100644 --- a/interface/resources/styles/log_dialog.qss +++ b/interface/resources/styles/log_dialog.qss @@ -1,6 +1,6 @@ QPlainTextEdit { - font-family: Inconsolata, Lucida Console, Andale Mono, Monaco; + font-family: Inconsolata, Consolas, Courier New, monospace; font-size: 16px; padding-left: 28px; padding-top: 7px; @@ -11,7 +11,7 @@ QPlainTextEdit { } QLineEdit { - font-family: Inconsolata, Lucida Console, Andale Mono, Monaco; + font-family: Inconsolata, Consolas, Courier New, monospace; padding-left: 7px; background-color: #CCCCCC; border-width: 0; diff --git a/interface/src/avatar/CauterizedMeshPartPayload.cpp b/interface/src/avatar/CauterizedMeshPartPayload.cpp index c8ec90dcee..c11f92083b 100644 --- a/interface/src/avatar/CauterizedMeshPartPayload.cpp +++ b/interface/src/avatar/CauterizedMeshPartPayload.cpp @@ -20,55 +20,28 @@ using namespace render; CauterizedMeshPartPayload::CauterizedMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform) : ModelMeshPartPayload(model, meshIndex, partIndex, shapeIndex, transform, offsetTransform) {} -void CauterizedMeshPartPayload::updateTransformForSkinnedCauterizedMesh(const Transform& transform, - const QVector& clusterMatrices, - const QVector& cauterizedClusterMatrices) { - _transform = transform; - _cauterizedTransform = transform; - - if (clusterMatrices.size() > 0) { - _worldBound = AABox(); - for (auto& clusterMatrix : clusterMatrices) { - AABox clusterBound = _localBound; - clusterBound.transform(clusterMatrix); - _worldBound += clusterBound; - } - - _worldBound.transform(transform); - if (clusterMatrices.size() == 1) { - _transform = _transform.worldTransform(Transform(clusterMatrices[0])); - if (cauterizedClusterMatrices.size() != 0) { - _cauterizedTransform = _cauterizedTransform.worldTransform(Transform(cauterizedClusterMatrices[0])); - } else { - _cauterizedTransform = _transform; - } - } - } else { - _worldBound = _localBound; - _worldBound.transform(_drawTransform); - } +void CauterizedMeshPartPayload::updateTransformForCauterizedMesh( + const Transform& renderTransform, + const gpu::BufferPointer& buffer) { + _cauterizedTransform = renderTransform; + _cauterizedClusterBuffer = buffer; } void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const { // Still relying on the raw data from the model - const Model::MeshState& state = _model->getMeshState(_meshIndex); SkeletonModel* skeleton = static_cast(_model); bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE) && skeleton->getEnableCauterization(); - if (state.clusterBuffer) { - if (useCauterizedMesh) { - const Model::MeshState& cState = skeleton->getCauterizeMeshState(_meshIndex); - batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, cState.clusterBuffer); - } else { - batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, state.clusterBuffer); + if (useCauterizedMesh) { + if (_cauterizedClusterBuffer) { + batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, _cauterizedClusterBuffer); + } + batch.setModelTransform(_cauterizedTransform); + } else { + if (_clusterBuffer) { + batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, _clusterBuffer); } batch.setModelTransform(_transform); - } else { - if (useCauterizedMesh) { - batch.setModelTransform(_cauterizedTransform); - } else { - batch.setModelTransform(_transform); - } } } diff --git a/interface/src/avatar/CauterizedMeshPartPayload.h b/interface/src/avatar/CauterizedMeshPartPayload.h index f4319ead6f..dc88e950c1 100644 --- a/interface/src/avatar/CauterizedMeshPartPayload.h +++ b/interface/src/avatar/CauterizedMeshPartPayload.h @@ -17,12 +17,13 @@ class CauterizedMeshPartPayload : public ModelMeshPartPayload { public: CauterizedMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform); - void updateTransformForSkinnedCauterizedMesh(const Transform& transform, - const QVector& clusterMatrices, - const QVector& cauterizedClusterMatrices); + + void updateTransformForCauterizedMesh(const Transform& renderTransform, const gpu::BufferPointer& buffer); void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override; + private: + gpu::BufferPointer _cauterizedClusterBuffer; Transform _cauterizedTransform; }; diff --git a/interface/src/avatar/CauterizedModel.cpp b/interface/src/avatar/CauterizedModel.cpp index 1ca87a498a..d8db83fbb7 100644 --- a/interface/src/avatar/CauterizedModel.cpp +++ b/interface/src/avatar/CauterizedModel.cpp @@ -26,8 +26,8 @@ CauterizedModel::~CauterizedModel() { } void CauterizedModel::deleteGeometry() { - Model::deleteGeometry(); - _cauterizeMeshStates.clear(); + Model::deleteGeometry(); + _cauterizeMeshStates.clear(); } bool CauterizedModel::updateGeometry() { @@ -41,7 +41,7 @@ bool CauterizedModel::updateGeometry() { _cauterizeMeshStates.append(state); } } - return needsFullUpdate; + return needsFullUpdate; } void CauterizedModel::createVisibleRenderItemSet() { @@ -86,13 +86,13 @@ void CauterizedModel::createVisibleRenderItemSet() { } } } else { - Model::createVisibleRenderItemSet(); + Model::createVisibleRenderItemSet(); } } void CauterizedModel::createCollisionRenderItemSet() { // Temporary HACK: use base class method for now - Model::createCollisionRenderItemSet(); + Model::createCollisionRenderItemSet(); } void CauterizedModel::updateClusterMatrices() { @@ -122,8 +122,8 @@ void CauterizedModel::updateClusterMatrices() { state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4), (const gpu::Byte*) state.clusterMatrices.constData()); } - } - } + } + } // as an optimization, don't build cautrizedClusterMatrices if the boneSet is empty. if (!_cauterizeBoneSet.empty()) { @@ -191,6 +191,9 @@ void CauterizedModel::updateRenderItems() { return; } + // lazy update of cluster matrices used for rendering. We need to update them here, so we can correctly update the bounding box. + self->updateClusterMatrices(); + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); Transform modelTransform; @@ -209,15 +212,22 @@ void CauterizedModel::updateRenderItems() { if (data._model && data._model->isLoaded()) { // Ensure the model geometry was not reset between frames if (deleteGeometryCounter == data._model->getGeometryCounter()) { - // lazy update of cluster matrices used for rendering. We need to update them here, so we can correctly update the bounding box. - data._model->updateClusterMatrices(); - - // update the model transform and bounding box for this render item. + // this stuff identical to what happens in regular Model const Model::MeshState& state = data._model->getMeshState(data._meshIndex); + Transform renderTransform = modelTransform; + if (state.clusterMatrices.size() == 1) { + renderTransform = modelTransform.worldTransform(Transform(state.clusterMatrices[0])); + } + data.updateTransformForSkinnedMesh(renderTransform, modelTransform, state.clusterBuffer); + + // this stuff for cauterized mesh CauterizedModel* cModel = static_cast(data._model); - assert(data._meshIndex < cModel->_cauterizeMeshStates.size()); - const Model::MeshState& cState = cModel->_cauterizeMeshStates.at(data._meshIndex); - data.updateTransformForSkinnedCauterizedMesh(modelTransform, state.clusterMatrices, cState.clusterMatrices); + const Model::MeshState& cState = cModel->getCauterizeMeshState(data._meshIndex); + renderTransform = modelTransform; + if (cState.clusterMatrices.size() == 1) { + renderTransform = modelTransform.worldTransform(Transform(cState.clusterMatrices[0])); + } + data.updateTransformForCauterizedMesh(renderTransform, cState.clusterBuffer); } } }); diff --git a/interface/src/ui/BaseLogDialog.cpp b/interface/src/ui/BaseLogDialog.cpp index 7e0027e0a8..571d3ac403 100644 --- a/interface/src/ui/BaseLogDialog.cpp +++ b/interface/src/ui/BaseLogDialog.cpp @@ -28,17 +28,23 @@ const int SEARCH_BUTTON_LEFT = 25; const int SEARCH_BUTTON_WIDTH = 20; const int SEARCH_TOGGLE_BUTTON_WIDTH = 50; const int SEARCH_TEXT_WIDTH = 240; +const int TIME_STAMP_LENGTH = 16; +const int FONT_WEIGHT = 75; const QColor HIGHLIGHT_COLOR = QColor("#3366CC"); +const QColor BOLD_COLOR = QColor("#445c8c"); +const QString BOLD_PATTERN = "\\[\\d*\\/.*:\\d*:\\d*\\]"; -class KeywordHighlighter : public QSyntaxHighlighter { +class Highlighter : public QSyntaxHighlighter { public: - KeywordHighlighter(QTextDocument* parent = nullptr); + Highlighter(QTextDocument* parent = nullptr); + void setBold(int indexToBold); QString keyword; protected: void highlightBlock(const QString& text) override; private: + QTextCharFormat boldFormat; QTextCharFormat keywordFormat; }; @@ -89,7 +95,7 @@ void BaseLogDialog::initControls() { _leftPad += SEARCH_TOGGLE_BUTTON_WIDTH + BUTTON_MARGIN; _searchPrevButton->show(); connect(_searchPrevButton, SIGNAL(clicked()), SLOT(toggleSearchPrev())); - + _searchNextButton = new QPushButton(this); _searchNextButton->setObjectName("searchNextButton"); _searchNextButton->setGeometry(_leftPad, ELEMENT_MARGIN, SEARCH_TOGGLE_BUTTON_WIDTH, ELEMENT_HEIGHT); @@ -101,9 +107,8 @@ void BaseLogDialog::initControls() { _logTextBox = new QPlainTextEdit(this); _logTextBox->setReadOnly(true); _logTextBox->show(); - _highlighter = new KeywordHighlighter(_logTextBox->document()); + _highlighter = new Highlighter(_logTextBox->document()); connect(_logTextBox, SIGNAL(selectionChanged()), SLOT(updateSelection())); - } void BaseLogDialog::showEvent(QShowEvent* event) { @@ -116,7 +121,9 @@ void BaseLogDialog::resizeEvent(QResizeEvent* event) { void BaseLogDialog::appendLogLine(QString logLine) { if (logLine.contains(_searchTerm, Qt::CaseInsensitive)) { + int indexToBold = _logTextBox->document()->characterCount(); _logTextBox->appendPlainText(logLine.trimmed()); + _highlighter->setBold(indexToBold); } } @@ -128,7 +135,7 @@ void BaseLogDialog::handleSearchTextChanged(QString searchText) { if (searchText.isEmpty()) { return; } - + QTextCursor cursor = _logTextBox->textCursor(); if (cursor.hasSelection()) { QString selectedTerm = cursor.selectedText(); @@ -136,16 +143,16 @@ void BaseLogDialog::handleSearchTextChanged(QString searchText) { return; } } - + cursor.setPosition(0, QTextCursor::MoveAnchor); _logTextBox->setTextCursor(cursor); bool foundTerm = _logTextBox->find(searchText); - + if (!foundTerm) { cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor); _logTextBox->setTextCursor(cursor); } - + _searchTerm = searchText; _highlighter->keyword = searchText; _highlighter->rehighlight(); @@ -175,6 +182,7 @@ void BaseLogDialog::showLogData() { _logTextBox->clear(); _logTextBox->appendPlainText(getCurrentLog()); _logTextBox->ensureCursorVisible(); + _highlighter->rehighlight(); } void BaseLogDialog::updateSelection() { @@ -187,16 +195,28 @@ void BaseLogDialog::updateSelection() { } } -KeywordHighlighter::KeywordHighlighter(QTextDocument* parent) : QSyntaxHighlighter(parent) { +Highlighter::Highlighter(QTextDocument* parent) : QSyntaxHighlighter(parent) { + boldFormat.setFontWeight(FONT_WEIGHT); + boldFormat.setForeground(BOLD_COLOR); keywordFormat.setForeground(HIGHLIGHT_COLOR); } -void KeywordHighlighter::highlightBlock(const QString& text) { +void Highlighter::highlightBlock(const QString& text) { + QRegExp expression(BOLD_PATTERN); + + int index = text.indexOf(expression, 0); + + while (index >= 0) { + int length = expression.matchedLength(); + setFormat(index, length, boldFormat); + index = text.indexOf(expression, index + length); + } + if (keyword.isNull() || keyword.isEmpty()) { return; } - int index = text.indexOf(keyword, 0, Qt::CaseInsensitive); + index = text.indexOf(keyword, 0, Qt::CaseInsensitive); int length = keyword.length(); while (index >= 0) { @@ -204,3 +224,7 @@ void KeywordHighlighter::highlightBlock(const QString& text) { index = text.indexOf(keyword, index + length, Qt::CaseInsensitive); } } + +void Highlighter::setBold(int indexToBold) { + setFormat(indexToBold, TIME_STAMP_LENGTH, boldFormat); +} diff --git a/interface/src/ui/BaseLogDialog.h b/interface/src/ui/BaseLogDialog.h index d097010bae..e18d23937f 100644 --- a/interface/src/ui/BaseLogDialog.h +++ b/interface/src/ui/BaseLogDialog.h @@ -23,7 +23,7 @@ const int BUTTON_MARGIN = 8; class QPushButton; class QLineEdit; class QPlainTextEdit; -class KeywordHighlighter; +class Highlighter; class BaseLogDialog : public QDialog { Q_OBJECT @@ -56,7 +56,7 @@ private: QPushButton* _searchPrevButton { nullptr }; QPushButton* _searchNextButton { nullptr }; QString _searchTerm; - KeywordHighlighter* _highlighter { nullptr }; + Highlighter* _highlighter { nullptr }; void initControls(); void showLogData(); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 109c1b4183..8c4498edc6 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -941,7 +941,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { if (_tree && !_shuttingDown && _entitiesScriptEngine) { - _entitiesScriptEngine->unloadEntityScript(entityID); + _entitiesScriptEngine->unloadEntityScript(entityID, true); } forceRecheckEntities(); // reset our state to force checking our inside/outsideness of entities diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 5b3d285b47..41a1bb4c74 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -372,19 +372,12 @@ void ModelMeshPartPayload::notifyLocationChanged() { } -void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& transform, const QVector& clusterMatrices) { - _transform = transform; - - if (clusterMatrices.size() > 0) { - _worldBound = _adjustedLocalBound; - _worldBound.transform(_transform); - if (clusterMatrices.size() == 1) { - _transform = _transform.worldTransform(Transform(clusterMatrices[0])); - } - } else { - _worldBound = _localBound; - _worldBound.transform(_transform); - } +void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform, + const gpu::BufferPointer& buffer) { + _transform = renderTransform; + _worldBound = _adjustedLocalBound; + _worldBound.transform(boundTransform); + _clusterBuffer = buffer; } ItemKey ModelMeshPartPayload::getKey() const { @@ -532,9 +525,8 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) const { void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const { // Still relying on the raw data from the model - const Model::MeshState& state = _model->getMeshState(_meshIndex); - if (state.clusterBuffer) { - batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, state.clusterBuffer); + if (_clusterBuffer) { + batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, _clusterBuffer); } batch.setModelTransform(_transform); } @@ -590,8 +582,6 @@ void ModelMeshPartPayload::render(RenderArgs* args) const { auto locations = args->_pipeline->locations; assert(locations); - // Bind the model transform and the skinCLusterMatrices if needed - _model->updateClusterMatrices(); bindTransform(batch, locations, args->_renderMode); //Bind the index buffer and vertex buffer and Blend shapes if needed diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index c585c95025..ef74011c40 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -89,8 +89,9 @@ public: typedef Payload::DataPointer Pointer; void notifyLocationChanged() override; - void updateTransformForSkinnedMesh(const Transform& transform, - const QVector& clusterMatrices); + void updateTransformForSkinnedMesh(const Transform& renderTransform, + const Transform& boundTransform, + const gpu::BufferPointer& buffer); float computeFadeAlpha() const; @@ -108,6 +109,7 @@ public: void computeAdjustedLocalBound(const QVector& clusterMatrices); + gpu::BufferPointer _clusterBuffer; Model* _model; int _meshIndex; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 48c1d29b68..c584b0bc21 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -227,6 +227,10 @@ void Model::updateRenderItems() { return; } + // lazy update of cluster matrices used for rendering. + // We need to update them here so we can correctly update the bounding box. + self->updateClusterMatrices(); + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); uint32_t deleteGeometryCounter = self->_deleteGeometryCounter; @@ -240,12 +244,12 @@ void Model::updateRenderItems() { Transform modelTransform = data._model->getTransform(); modelTransform.setScale(glm::vec3(1.0f)); - // lazy update of cluster matrices used for rendering. We need to update them here, so we can correctly update the bounding box. - data._model->updateClusterMatrices(); - - // update the model transform and bounding box for this render item. - const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex); - data.updateTransformForSkinnedMesh(modelTransform, state.clusterMatrices); + const Model::MeshState& state = data._model->getMeshState(data._meshIndex); + Transform renderTransform = modelTransform; + if (state.clusterMatrices.size() == 1) { + renderTransform = modelTransform.worldTransform(Transform(state.clusterMatrices[0])); + } + data.updateTransformForSkinnedMesh(renderTransform, modelTransform, state.clusterBuffer); } } }); @@ -1048,7 +1052,7 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) { } void Model::computeMeshPartLocalBounds() { - for (auto& part : _modelMeshRenderItemsSet) { + for (auto& part : _modelMeshRenderItemsSet) { assert(part->_meshIndex < _modelMeshRenderItemsSet.size()); const Model::MeshState& state = _meshStates.at(part->_meshIndex); part->computeAdjustedLocalBound(state.clusterMatrices); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 0f7b7b9c4c..7c5785e524 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -583,16 +583,6 @@ void ScriptEngine::init() { auto entityScriptingInterface = DependencyManager::get(); entityScriptingInterface->init(); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) { - if (_entityScripts.contains(entityID)) { - if (isEntityScriptRunning(entityID) && !isStopping()) { - qCWarning(scriptengine) << "deletingEntity while entity script is still running" << entityID; - } - _entityScripts.remove(entityID); - emit entityScriptDetailsUpdated(); - } - }); - // register various meta-types registerMetaTypes(this); @@ -2291,7 +2281,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co processDeferredEntityLoads(entityScript, entityID); } -void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { +void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap) { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::unloadEntityScript() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " @@ -2299,7 +2289,8 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { #endif QMetaObject::invokeMethod(this, "unloadEntityScript", - Q_ARG(const EntityItemID&, entityID)); + Q_ARG(const EntityItemID&, entityID), + Q_ARG(bool, shouldRemoveFromMap)); return; } #ifdef THREAD_DEBUGGING @@ -2317,7 +2308,11 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { qCDebug(scriptengine) << "unload called while !running" << entityID << oldDetails.status; } #endif - if (oldDetails.status != EntityScriptStatus::UNLOADED) { + if (shouldRemoveFromMap) { + // this was a deleted entity, we've been asked to remove it from the map + _entityScripts.remove(entityID); + emit entityScriptDetailsUpdated(); + } else if (oldDetails.status != EntityScriptStatus::UNLOADED) { EntityScriptDetails newDetails; newDetails.status = EntityScriptStatus::UNLOADED; newDetails.lastModified = QDateTime::currentMSecsSinceEpoch(); @@ -2325,6 +2320,7 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { newDetails.scriptText = oldDetails.scriptText; setEntityScriptDetails(entityID, newDetails); } + stopAllTimersForEntityScript(entityID); { // FIXME: shouldn't have to do this here, but currently something seems to be firing unloads moments after firing initial load requests diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index a3f709eff1..95f377d92b 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -186,7 +186,7 @@ public: QVariant cloneEntityScriptDetails(const EntityItemID& entityID); QFuture getLocalEntityScriptDetails(const EntityItemID& entityID) override; Q_INVOKABLE void loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload); - Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID); // will call unload method + Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap = false); // will call unload method Q_INVOKABLE void unloadAllEntityScripts(); Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params = QStringList()) override;