mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-01-02 09:41:13 -06:00
1004 lines
40 KiB
C++
1004 lines
40 KiB
C++
/*****************************************************************************************
|
|
* *
|
|
* OpenSpace *
|
|
* *
|
|
* Copyright (c) 2014-2022 *
|
|
* *
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
|
|
* software and associated documentation files (the "Software"), to deal in the Software *
|
|
* without restriction, including without limitation the rights to use, copy, modify, *
|
|
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
|
|
* permit persons to whom the Software is furnished to do so, subject to the following *
|
|
* conditions: *
|
|
* *
|
|
* The above copyright notice and this permission notice shall be included in all copies *
|
|
* or substantial portions of the Software. *
|
|
* *
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
|
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
|
|
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
|
|
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
|
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
|
****************************************************************************************/
|
|
|
|
#include <modules/softwareintegration/pointdatamessagehandler.h>
|
|
#include <modules/softwareintegration/softwareintegrationmodule.h>
|
|
#include <modules/softwareintegration/utils.h>
|
|
|
|
#include <openspace/navigation/navigationhandler.h>
|
|
#include <openspace/engine/globals.h>
|
|
#include <openspace/engine/moduleengine.h>
|
|
#include <openspace/query/query.h>
|
|
#include <openspace/rendering/renderable.h>
|
|
#include <openspace/rendering/renderengine.h>
|
|
#include <openspace/scene/scene.h>
|
|
#include <openspace/util/distanceconstants.h>
|
|
#include <openspace/scripting/scriptengine.h>
|
|
#include <ghoul/logging/logmanager.h>
|
|
#include <ghoul/misc/dictionaryluaformatter.h>
|
|
|
|
#include <iomanip>
|
|
|
|
namespace {
|
|
constexpr const char* _loggerCat = "PDatMessHand";
|
|
} // namespace
|
|
|
|
namespace openspace {
|
|
|
|
using namespace softwareintegration;
|
|
|
|
void PointDataMessageHandler::handlePointDataMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection) {
|
|
size_t messageOffset = 0;
|
|
std::string identifier;
|
|
|
|
checkRenderable(message, messageOffset, connection, identifier);
|
|
|
|
size_t nPoints;
|
|
size_t dimensionality;
|
|
std::vector<float> points;
|
|
|
|
try {
|
|
// The following order of creating variables is the exact order they are received
|
|
// in the message. If the order is not the same, the global variable
|
|
// 'message offset' will be wrong
|
|
nPoints = static_cast<size_t>(simp::readIntValue(message, messageOffset));
|
|
dimensionality = static_cast<size_t>(simp::readIntValue(message, messageOffset));
|
|
simp::readPointData(message, messageOffset, nPoints, dimensionality, points);
|
|
}
|
|
catch (const simp::SimpError& err) {
|
|
LERROR(fmt::format("Error when reading point data message: {}", err.message));
|
|
return;
|
|
}
|
|
|
|
// Use the renderable identifier as the data key
|
|
auto module = global::moduleEngine->module<SoftwareIntegrationModule>();
|
|
module->storeData(identifier, storage::Key::DataPoints, std::move(points));
|
|
|
|
auto reanchorCallback = [identifier] {
|
|
global::scriptEngine->queueScript(
|
|
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.RetargetAnchor', nil)"
|
|
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Anchor', '" + identifier + "')"
|
|
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Aim', '')",
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
};
|
|
addCallback(identifier, { reanchorCallback, { storage::Key::DataPoints }, "reanchorCallback" });
|
|
}
|
|
|
|
void PointDataMessageHandler::handleVelocityDataMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection) {
|
|
LWARNING(fmt::format("PointDataMessageHandler::handleVelocityDataMessage()"));
|
|
size_t messageOffset = 0;
|
|
std::string identifier;
|
|
|
|
checkRenderable(message, messageOffset, connection, identifier);
|
|
|
|
simp::LengthUnit length_unit;
|
|
simp::NaNRenderMode velNaNMode;
|
|
glm::vec4 velNaNColor;
|
|
size_t nVelocities;
|
|
size_t dimensionality;
|
|
std::vector<float> velocities;
|
|
|
|
try {
|
|
// The following order of creating variables is the exact order they are received
|
|
// in the message. If the order is not the same, the global variable
|
|
// 'message offset' will be wrong
|
|
length_unit = simp::getLengthUnit(simp::readString(message, messageOffset));
|
|
if (length_unit == simp::LengthUnit::Unknown) {
|
|
// TODO: Write message
|
|
throw simp::SimpError("");
|
|
}
|
|
velNaNMode = simp::getNaNRenderMode(simp::readString(message, messageOffset));
|
|
switch (velNaNMode) {
|
|
case simp::NaNRenderMode::Color:
|
|
velNaNColor = simp::readColor(message, messageOffset);
|
|
break;
|
|
case simp::NaNRenderMode::Hide: // Nothing to read
|
|
break;
|
|
default: // simp::NaNRenderMode::Unknown
|
|
// TODO: Write message
|
|
throw simp::SimpError("");
|
|
break;
|
|
}
|
|
nVelocities = static_cast<size_t>(simp::readIntValue(message, messageOffset));
|
|
dimensionality = static_cast<size_t>(simp::readIntValue(message, messageOffset));
|
|
simp::readPointData(message, messageOffset, nVelocities, dimensionality, velocities);
|
|
}
|
|
catch (const simp::SimpError& err) {
|
|
LERROR(fmt::format("Error when reading point data message: {}", err.message));
|
|
return;
|
|
}
|
|
|
|
// Convert data to m/s (assuming time unit is seconds for now)
|
|
if (length_unit != simp::LengthUnit::m) {
|
|
convertToMeterPerSecond(length_unit, velocities);
|
|
}
|
|
|
|
// Use the renderable identifier as the data key
|
|
auto module = global::moduleEngine->module<SoftwareIntegrationModule>();
|
|
module->storeData(identifier, storage::Key::VelocityData, std::move(velocities));
|
|
|
|
auto velNaNModeCallback = [this, identifier, velNaNMode, velNaNColor, connection] {
|
|
if (velNaNMode == simp::NaNRenderMode::Color) {
|
|
// Get renderable
|
|
auto r = getRenderable(identifier);
|
|
|
|
// Get velNaNColor of renderable
|
|
properties::Property* velNaNColorProperty = r->property("VelNaNColor");
|
|
glm::vec4 propertyVelNaNColor = std::any_cast<glm::vec4>(velNaNColorProperty->get());
|
|
|
|
// Update velNaNColor of renderable
|
|
if (propertyVelNaNColor != velNaNColor) {
|
|
global::scriptEngine->queueScript(
|
|
fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.VelNaNColor', {});",
|
|
identifier, ghoul::to_string(velNaNColor)
|
|
),
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
}
|
|
}
|
|
|
|
global::scriptEngine->queueScript(
|
|
fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.VelNaNMode', {});",
|
|
identifier, static_cast<int>(velNaNMode)
|
|
),
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
};
|
|
addCallback(identifier, { velNaNModeCallback, {}, "velNaNModeCallback" });
|
|
|
|
auto enableMotionCallback = [identifier] {
|
|
global::scriptEngine->queueScript(
|
|
fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.MotionEnabled', {});",
|
|
identifier, "true"
|
|
),
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
};
|
|
addCallback(identifier, { enableMotionCallback, { storage::Key::VelocityData }, "Enable motion mode, wait for VelocityData" });
|
|
}
|
|
|
|
void PointDataMessageHandler::handleFixedColorMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection) {
|
|
size_t messageOffset = 0;
|
|
std::string identifier;
|
|
|
|
checkRenderable(message, messageOffset, connection, identifier);
|
|
|
|
glm::vec4 color;
|
|
try {
|
|
color = simp::readColor(message, messageOffset);
|
|
}
|
|
catch (const simp::SimpError& err) {
|
|
LERROR(fmt::format("Error when reading fixed color message: {}", err.message));
|
|
return;
|
|
}
|
|
|
|
// Get renderable
|
|
auto r = getRenderable(identifier);
|
|
if (!r) return;
|
|
|
|
// Get color of renderable
|
|
properties::Property* colorProperty = r->property("Color");
|
|
|
|
// Create weak_ptr, safer than shared_ptr for lambdas
|
|
std::weak_ptr<SoftwareConnection> connWeakPtr{ connection };
|
|
|
|
auto setFixedColorCallback = [this, identifier, color, colorProperty, connWeakPtr] {
|
|
if (!colorProperty || connWeakPtr.expired()) return;
|
|
// auto conn = connWeakPtr.lock()
|
|
|
|
// auto propertySub = connection->getPropertySubscription(identifier, colorProperty->identifier());
|
|
// if (propertySub) {
|
|
// propertySub->shouldSendMessage = false;
|
|
// }
|
|
|
|
// Update color of renderable
|
|
glm::vec4 currentColor = std::any_cast<glm::vec4>(colorProperty->get());
|
|
if (currentColor != color) {
|
|
global::scriptEngine->queueScript(
|
|
fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Color', {});",
|
|
identifier, ghoul::to_string(color)
|
|
),
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
}
|
|
|
|
global::scriptEngine->queueScript(
|
|
fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapEnabled', {});",
|
|
identifier, "false"
|
|
),
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
};
|
|
addCallback(identifier, { setFixedColorCallback, {}, "handleFixedColorMessage" });
|
|
|
|
// Create and set onChange for color
|
|
auto updateColor = [this, colorProperty, identifier, connWeakPtr] {
|
|
if (!colorProperty || connWeakPtr.expired()) return;
|
|
onFixedColorChange(colorProperty, identifier, connWeakPtr.lock());
|
|
};
|
|
connection->addPropertySubscription(colorProperty->identifier(), identifier, updateColor);
|
|
}
|
|
|
|
void PointDataMessageHandler::handleColormapMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection) {
|
|
size_t messageOffset = 0;
|
|
std::string identifier;
|
|
|
|
checkRenderable(message, messageOffset, connection, identifier);
|
|
|
|
float min;
|
|
float max;
|
|
simp::NaNRenderMode cmapNaNMode;
|
|
glm::vec4 cmapNaNColor;
|
|
size_t nColors;
|
|
std::vector<float> colorMap;
|
|
try {
|
|
min = simp::readFloatValue(message, messageOffset);
|
|
max = simp::readFloatValue(message, messageOffset);
|
|
cmapNaNMode = simp::getNaNRenderMode(simp::readString(message, messageOffset));
|
|
switch (cmapNaNMode) {
|
|
case simp::NaNRenderMode::Color:
|
|
cmapNaNColor = simp::readColor(message, messageOffset);
|
|
break;
|
|
case simp::NaNRenderMode::Hide: // Nothing to read
|
|
break;
|
|
default: // simp::NaNRenderMode::Unknown
|
|
// TODO: Write message
|
|
throw simp::SimpError("");
|
|
break;
|
|
}
|
|
nColors = static_cast<size_t>(simp::readIntValue(message, messageOffset));
|
|
simp::readColormap(message, messageOffset, nColors, colorMap);
|
|
}
|
|
catch (const simp::SimpError& err) {
|
|
LERROR(fmt::format("Error when reading color map message: {}", err.message));
|
|
return;
|
|
}
|
|
|
|
// Use the renderable identifier as the data key
|
|
auto module = global::moduleEngine->module<SoftwareIntegrationModule>();
|
|
module->storeData(identifier, storage::Key::Colormap, std::move(colorMap));
|
|
|
|
auto colormapLimitsCallback = [this, identifier, min, max, connection] {
|
|
// Get renderable
|
|
auto r = getRenderable(identifier);
|
|
|
|
properties::Property* colormapMinProperty = r->property("ColormapMin");
|
|
// auto minPropertySub = connection->getPropertySubscription(identifier, colormapMinProperty->identifier());
|
|
// if (minPropertySub) {
|
|
// minPropertySub->shouldSendMessage = false;
|
|
// }
|
|
|
|
float colormapMin = std::any_cast<float>(colormapMinProperty->get());
|
|
if (min != colormapMin) {
|
|
global::scriptEngine->queueScript(
|
|
fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapMin', {});",
|
|
identifier, ghoul::to_string(min)
|
|
),
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
}
|
|
|
|
properties::Property* colormapMaxProperty = r->property("ColormapMax");
|
|
// auto maxPropertySub = connection->getPropertySubscription(identifier, colormapMaxProperty->identifier());
|
|
// if (maxPropertySub) {
|
|
// maxPropertySub->shouldSendMessage = false;
|
|
// }
|
|
float colormapMax = std::any_cast<float>(colormapMaxProperty->get());
|
|
if (max != colormapMax) {
|
|
global::scriptEngine->queueScript(
|
|
fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapMax', {});",
|
|
identifier, ghoul::to_string(max)
|
|
),
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
}
|
|
};
|
|
addCallback(identifier, { colormapLimitsCallback, {}, "colormapLimitsCallback" });
|
|
|
|
auto cmapNaNModeCallback = [this, identifier, cmapNaNMode, cmapNaNColor, connection] {
|
|
if (cmapNaNMode == simp::NaNRenderMode::Color) {
|
|
// Get renderable
|
|
auto r = getRenderable(identifier);
|
|
|
|
// Get cmapNaNColor of renderable
|
|
properties::Property* cmapNaNColorProperty = r->property("CmapNaNColor");
|
|
glm::vec4 propertyCmapNaNColor = std::any_cast<glm::vec4>(cmapNaNColorProperty->get());
|
|
|
|
// Update cmapNaNColor of renderable
|
|
if (propertyCmapNaNColor != cmapNaNColor) {
|
|
global::scriptEngine->queueScript(
|
|
fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.CmapNaNColor', {});",
|
|
identifier, ghoul::to_string(cmapNaNColor)
|
|
),
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
}
|
|
}
|
|
|
|
global::scriptEngine->queueScript(
|
|
fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.CmapNaNMode', {});",
|
|
identifier, static_cast<int>(cmapNaNMode)
|
|
),
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
};
|
|
addCallback(identifier, { cmapNaNModeCallback, {}, "cmapNaNModeCallback" });
|
|
|
|
auto enableColormapCallback = [this, identifier] {
|
|
global::scriptEngine->queueScript(
|
|
fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapEnabled', {});",
|
|
identifier, "true"
|
|
),
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
};
|
|
// Callback
|
|
std::vector<storage::Key> dataToWaitFor{ storage::Key::Colormap, storage::Key::ColormapAttrData };
|
|
addCallback(identifier, { enableColormapCallback, std::move(dataToWaitFor), "enableColormapCallback" });
|
|
}
|
|
|
|
void PointDataMessageHandler::handleAttributeDataMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection) {
|
|
size_t messageOffset = 0;
|
|
std::string identifier;
|
|
|
|
checkRenderable(message, messageOffset, connection, identifier);
|
|
|
|
std::string usedFor;
|
|
size_t nValues;
|
|
std::vector<float> attributeData;
|
|
try {
|
|
usedFor = simp::readString(message, messageOffset);
|
|
nValues = static_cast<size_t>(simp::readIntValue(message, messageOffset));
|
|
attributeData.reserve(nValues);
|
|
for (size_t i = 0; i < nValues; ++i)
|
|
attributeData.push_back(simp::readFloatValue(message, messageOffset));
|
|
}
|
|
catch (const simp::SimpError& err) {
|
|
LERROR(fmt::format("Error when reading message with scalars for color map: {}", err.message));
|
|
return;
|
|
}
|
|
|
|
auto module = global::moduleEngine->module<SoftwareIntegrationModule>();
|
|
storage::Key key = storage::Key::Unknown;
|
|
|
|
if (storage::hasStorageKey(usedFor)) {
|
|
key = storage::getStorageKey(usedFor);
|
|
}
|
|
else {
|
|
LERROR(fmt::format(
|
|
"The received attribute data had the \"usedFor\" value {}, which is unrecognized.",
|
|
usedFor
|
|
));
|
|
return;
|
|
}
|
|
|
|
module->storeData(identifier, key, std::move(attributeData));
|
|
|
|
std::string callbackDescription = "handleAttributeDataMessage, key=" + storage::getStorageKeyString(key);
|
|
switch (key) {
|
|
case storage::Key::ColormapAttrData : {
|
|
auto callback = [this, identifier] {
|
|
global::scriptEngine->queueScript(
|
|
fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapEnabled', {});",
|
|
identifier, "true"
|
|
),
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
};
|
|
addCallback(identifier, { callback, { key, storage::Key::Colormap }, callbackDescription });
|
|
break;
|
|
}
|
|
case storage::Key::LinearSizeAttrData: {
|
|
auto callback = [this, identifier] {
|
|
global::scriptEngine->queueScript(
|
|
fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.LinearSizeEnabled', {});",
|
|
identifier, "true"
|
|
),
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
};
|
|
addCallback(identifier, { callback, { key }, callbackDescription });
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void PointDataMessageHandler::handleOpacityMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection) {
|
|
size_t messageOffset = 0;
|
|
std::string identifier;
|
|
|
|
checkRenderable(message, messageOffset, connection, identifier);
|
|
|
|
float opacity;
|
|
try {
|
|
opacity = simp::readFloatValue(message, messageOffset);
|
|
}
|
|
catch (const simp::SimpError& err) {
|
|
LERROR(fmt::format("Error when reading opacity message: {}", err.message));
|
|
return;
|
|
}
|
|
|
|
// Get renderable
|
|
auto r = getRenderable(identifier);
|
|
if (!r) return;
|
|
|
|
// Get opacity of renderable
|
|
properties::Property* opacityProperty = r->property("Opacity");
|
|
|
|
// Create weak_ptr, safer than shared_ptr for lambdas
|
|
std::weak_ptr<SoftwareConnection> connWeakPtr{ connection };
|
|
|
|
auto callback = [this, identifier, opacity, opacityProperty, connWeakPtr] {
|
|
if (!opacityProperty || connWeakPtr.expired()) return;
|
|
// auto conn = connWeakPtr.lock()
|
|
|
|
|
|
// auto propertySub = connection->getPropertySubscription(identifier, opacityProperty->identifier());
|
|
// if (propertySub) {
|
|
// propertySub->shouldSendMessage = false;
|
|
// }
|
|
|
|
// Update opacity of renderable
|
|
float currentOpacity = std::any_cast<float>(opacityProperty->get());
|
|
if (currentOpacity != opacity) {
|
|
std::string script = fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Opacity', {});",
|
|
identifier, ghoul::to_string(opacity)
|
|
);
|
|
global::scriptEngine->queueScript(
|
|
script,
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
}
|
|
};
|
|
addCallback(identifier, { callback, {}, "handleOpacityMessage" });
|
|
|
|
// Create and set onChange for opacity
|
|
auto updateOpacity = [this, opacityProperty, identifier, connWeakPtr] {
|
|
if (!opacityProperty || connWeakPtr.expired()) return;
|
|
onOpacityChange(opacityProperty, identifier, connWeakPtr.lock());
|
|
};
|
|
connection->addPropertySubscription(opacityProperty->identifier(), identifier, updateOpacity);
|
|
}
|
|
|
|
void PointDataMessageHandler::handleFixedPointSizeMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection) {
|
|
size_t messageOffset = 0;
|
|
std::string identifier;
|
|
|
|
checkRenderable(message, messageOffset, connection, identifier);
|
|
|
|
float size;
|
|
try {
|
|
size = simp::readFloatValue(message, messageOffset);
|
|
}
|
|
catch (const simp::SimpError& err) {
|
|
LERROR(fmt::format("Error when reading fixed point size message: {}", err.message));
|
|
return;
|
|
}
|
|
|
|
// Get renderable
|
|
auto r = getRenderable(identifier);
|
|
if (!r) return;
|
|
|
|
// Get size of renderable
|
|
properties::Property* sizeProperty = r->property("Size");
|
|
|
|
// Create weak_ptr, safer than shared_ptr for lambdas
|
|
std::weak_ptr<SoftwareConnection> connWeakPtr{ connection };
|
|
|
|
auto callback = [this, identifier, size, sizeProperty, connWeakPtr] {
|
|
if (!sizeProperty || connWeakPtr.expired()) return;
|
|
// auto conn = connWeakPtr.lock()
|
|
|
|
// auto propertySub = connection->getPropertySubscription(identifier, sizeProperty->identifier());
|
|
// if (propertySub) {
|
|
// propertySub->shouldSendMessage = false;
|
|
// }
|
|
|
|
// Update size of renderable
|
|
float currentSize = std::any_cast<float>(sizeProperty->get());
|
|
if (currentSize != size) {
|
|
// TODO: Add interpolation to script, but do not send back
|
|
// updates to external software until the interpolation is done
|
|
// Use this: "openspace.setPropertyValueSingle('Scene.{}.Renderable.Size', {}, 1);",
|
|
global::scriptEngine->queueScript(
|
|
fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Size', {});",
|
|
identifier, ghoul::to_string(size)
|
|
),
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
}
|
|
|
|
global::scriptEngine->queueScript(
|
|
fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.LinearSizeEnabled', {});",
|
|
identifier, "false"
|
|
),
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
};
|
|
addCallback(identifier, { callback, {}, "handleFixedPointSizeMessage" });
|
|
|
|
auto updateSize = [this, sizeProperty, identifier, connWeakPtr] {
|
|
if (!sizeProperty || connWeakPtr.expired()) return;
|
|
onFixedPointSizeChange(sizeProperty, identifier, connWeakPtr.lock());
|
|
};
|
|
connection->addPropertySubscription(sizeProperty->identifier(), identifier, updateSize);
|
|
}
|
|
|
|
void PointDataMessageHandler::handleLinearPointSizeMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection) {
|
|
size_t messageOffset = 0;
|
|
std::string identifier;
|
|
|
|
checkRenderable(message, messageOffset, connection, identifier);
|
|
|
|
float size;
|
|
float min;
|
|
float max;
|
|
try {
|
|
size = simp::readFloatValue(message, messageOffset);
|
|
min = simp::readFloatValue(message, messageOffset);
|
|
max = simp::readFloatValue(message, messageOffset);
|
|
}
|
|
catch (const simp::SimpError& err) {
|
|
LERROR(fmt::format("Error when reading linear point size message: {}", err.message));
|
|
return;
|
|
}
|
|
|
|
auto linearSizeCallback = [this, identifier, size, min, max] {
|
|
// Get renderable
|
|
auto r = getRenderable(identifier);
|
|
|
|
// Get size from renderable
|
|
properties::Property* sizeProperty = r->property("Size");
|
|
auto propertyAny = sizeProperty->get();
|
|
float propertySize = std::any_cast<float>(propertyAny);
|
|
|
|
// Update size of renderable
|
|
if (propertySize != size) {
|
|
global::scriptEngine->queueScript(
|
|
fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Size', {});",
|
|
identifier, ghoul::to_string(size)
|
|
),
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
}
|
|
|
|
properties::Property* linearSizeMinProperty = r->property("LinearSizeMin");
|
|
float linearSizeMin = std::any_cast<float>(linearSizeMinProperty->get());
|
|
if (min != linearSizeMin) {
|
|
global::scriptEngine->queueScript(
|
|
fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.LinearSizeMin', {});",
|
|
identifier, ghoul::to_string(min)
|
|
),
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
}
|
|
|
|
properties::Property* linearSizeMaxProperty = r->property("LinearSizeMax");
|
|
float linearSizeMax = std::any_cast<float>(linearSizeMaxProperty->get());
|
|
if (max != linearSizeMax) {
|
|
global::scriptEngine->queueScript(
|
|
fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.LinearSizeMax', {});",
|
|
identifier, ghoul::to_string(max)
|
|
),
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
}
|
|
};
|
|
addCallback(identifier, { linearSizeCallback, {}, "linearSizeCallback" });
|
|
|
|
auto enableLinearSizeCallback = [this, identifier] {
|
|
global::scriptEngine->queueScript(
|
|
fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.LinearSizeEnabled', {});",
|
|
identifier, "true"
|
|
),
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
};
|
|
addCallback(
|
|
identifier,
|
|
{
|
|
enableLinearSizeCallback,
|
|
{ storage::Key::LinearSizeAttrData },
|
|
"enableLinearSizeCallback"
|
|
}
|
|
);
|
|
}
|
|
|
|
void PointDataMessageHandler::handleVisibilityMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection) {
|
|
size_t messageOffset = 0;
|
|
std::string identifier;
|
|
|
|
checkRenderable(message, messageOffset, connection, identifier);
|
|
|
|
std::string visibilityMessage;
|
|
try {
|
|
visibilityMessage = simp::readString(message, messageOffset);
|
|
}
|
|
catch (const simp::SimpError& err) {
|
|
LERROR(fmt::format("Error when reading visibility message: {}", err.message));
|
|
return;
|
|
}
|
|
|
|
// Get renderable
|
|
auto r = getRenderable(identifier);
|
|
if (!r) return;
|
|
|
|
// Get visibility of renderable
|
|
properties::Property* visibilityProperty = r->property("Enabled");
|
|
|
|
// Create weak_ptr, safer than shared_ptr for lambdas
|
|
std::weak_ptr<SoftwareConnection> connWeakPtr{ connection };
|
|
|
|
const bool visibility = visibilityMessage == "T";
|
|
|
|
auto callback = [this, identifier, visibility, visibilityProperty, connWeakPtr] {
|
|
if (!visibilityProperty || connWeakPtr.expired()) return;
|
|
// auto conn = connWeakPtr.lock()
|
|
|
|
// Get visibility from renderable
|
|
// properties::Property* enabledProperty = r->property("Enabled");
|
|
// auto propertySub = connection->getPropertySubscription(identifier, enabledProperty->identifier());
|
|
// if (propertySub) {
|
|
// propertySub->shouldSendMessage = false;
|
|
// }
|
|
|
|
// Toggle visibility of renderable
|
|
bool currentVisibility = std::any_cast<bool>(visibilityProperty->get());
|
|
if (currentVisibility != visibility) {
|
|
const std::string vis = visibility ? "true" : "false";
|
|
std::string script = fmt::format(
|
|
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Enabled', {});",
|
|
identifier, vis
|
|
);
|
|
global::scriptEngine->queueScript(
|
|
script,
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
}
|
|
};
|
|
addCallback(identifier, { callback, {}, "handleVisibilityMessage" });
|
|
|
|
// Create and set onChange for visibility
|
|
auto toggleVisibility = [this, visibilityProperty, identifier, connWeakPtr] {
|
|
if (!visibilityProperty || connWeakPtr.expired()) return;
|
|
onVisibilityChange(visibilityProperty, identifier, connWeakPtr.lock());
|
|
};
|
|
connection->addPropertySubscription(visibilityProperty->identifier(), identifier, toggleVisibility);
|
|
}
|
|
|
|
void PointDataMessageHandler::handleRemoveSGNMessage(const std::vector<char>& message,std::shared_ptr<SoftwareConnection> connection) {
|
|
size_t messageOffset = 0;
|
|
std::string identifier;
|
|
|
|
try {
|
|
identifier = simp::readString(message, messageOffset);
|
|
}
|
|
catch (const simp::SimpError& err) {
|
|
LERROR(fmt::format("Error when reading message: {}", err.message));
|
|
return;
|
|
}
|
|
|
|
const std::string currentAnchor =
|
|
global::navigationHandler->orbitalNavigator().anchorNode()->identifier();
|
|
|
|
if (currentAnchor == identifier) {
|
|
// If the deleted node is the current anchor, first change focus to the Sun
|
|
global::scriptEngine->queueScript(
|
|
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Anchor', 'Sun')"
|
|
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Aim', '')",
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
}
|
|
global::scriptEngine->queueScript(
|
|
"openspace.removeSceneGraphNode('" + identifier + "');",
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
|
|
connection->removeSceneGraphNode(identifier);
|
|
|
|
LDEBUG(fmt::format("Scene graph node '{}' removed.", identifier));
|
|
}
|
|
|
|
void PointDataMessageHandler::postSync() {
|
|
std::lock_guard guard(_onceNodeExistsCallbacksMutex);
|
|
// Check if the scene graph node has been created.
|
|
// If so, call the corresponding callback functions to set up any subscriptions
|
|
auto callbackMapIt = _onceNodeExistsCallbacks.begin();
|
|
while (callbackMapIt != _onceNodeExistsCallbacks.end()) {
|
|
auto& [identifier, callbackList] = *callbackMapIt;
|
|
|
|
try {
|
|
const SceneGraphNode* sgn = global::renderEngine->scene()->sceneGraphNode(identifier);
|
|
if (!sgn) throw std::exception{};
|
|
|
|
auto r = getRenderable(identifier);
|
|
if (!r) throw std::exception{};
|
|
|
|
auto softwareIntegrationModule = global::moduleEngine->module<SoftwareIntegrationModule>();
|
|
|
|
auto callbacksIt = callbackList.begin();
|
|
while (callbacksIt != callbackList.end()) {
|
|
auto& [callback, waitForData, description] = *callbacksIt;
|
|
|
|
try {
|
|
for (auto& waitFor : waitForData) {
|
|
if (softwareIntegrationModule->isSyncDataDirty(identifier, waitFor)) {
|
|
throw std::exception{};
|
|
}
|
|
}
|
|
|
|
callback();
|
|
callbacksIt = callbackList.erase(callbacksIt);
|
|
}
|
|
catch (std::exception&) {
|
|
++callbacksIt;
|
|
}
|
|
}
|
|
|
|
if (callbackList.empty()) {
|
|
callbackMapIt = _onceNodeExistsCallbacks.erase(callbackMapIt);
|
|
_onceNodeExistsCallbacksRetries = 0;
|
|
} else {
|
|
callbackMapIt++;
|
|
}
|
|
}
|
|
catch(std::exception &err) {
|
|
++_onceNodeExistsCallbacksRetries;
|
|
ghoul_assert(_onceNodeExistsCallbacksRetries < 10, "Too many callback retries");
|
|
LDEBUG(fmt::format("Error when trying to run callback: {}", err.what()));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const Renderable* PointDataMessageHandler::getRenderable(const std::string& identifier) {
|
|
return renderable(identifier);
|
|
}
|
|
|
|
void PointDataMessageHandler::checkRenderable(
|
|
const std::vector<char>& message, size_t& messageOffset,
|
|
std::shared_ptr<SoftwareConnection> connection, std::string& identifier
|
|
) {
|
|
std::string guiName;
|
|
|
|
try {
|
|
// The following order of creating variables is the exact order they are received
|
|
// in the message. If the order is not the same, the global variable
|
|
// 'message offset' will be wrong
|
|
identifier = simp::readString(message, messageOffset);
|
|
guiName = simp::readString(message, messageOffset);
|
|
}
|
|
catch (const simp::SimpError& err) {
|
|
LERROR(fmt::format("Error when reading identifier and guiName from message: {}", err.message));
|
|
return;
|
|
}
|
|
|
|
connection->addSceneGraphNode(identifier);
|
|
|
|
const Renderable* r = renderable(identifier);
|
|
bool hasCallbacks = false;
|
|
{
|
|
std::lock_guard guard(_onceNodeExistsCallbacksMutex);
|
|
hasCallbacks = _onceNodeExistsCallbacks.count(identifier) > 0;
|
|
}
|
|
if (!r && !hasCallbacks) {
|
|
LDEBUG(fmt::format("No renderable with identifier '{}' was found. Creating it.", identifier));
|
|
|
|
// Create a renderable, since it didn't exist
|
|
using namespace std::string_literals;
|
|
ghoul::Dictionary renderablePointsCloud;
|
|
renderablePointsCloud.setValue("Type", "RenderablePointsCloud"s);
|
|
renderablePointsCloud.setValue("Identifier", identifier);
|
|
renderablePointsCloud.setValue("Name", guiName);
|
|
|
|
ghoul::Dictionary gui;
|
|
gui.setValue("Name", guiName);
|
|
gui.setValue("Path", "/Software Integration"s);
|
|
|
|
ghoul::Dictionary node;
|
|
node.setValue("Identifier", identifier);
|
|
node.setValue("Renderable", renderablePointsCloud);
|
|
node.setValue("GUI", gui);
|
|
|
|
global::scriptEngine->queueScript(
|
|
"openspace.addSceneGraphNode(" + ghoul::formatLua(node) + ")"
|
|
"openspace.setPropertyValueSingle('Modules.CefWebGui.Reload', nil)", // Reload WebGUI so that SoftwareIntegration GUI appears
|
|
scripting::ScriptEngine::RemoteScripting::Yes
|
|
);
|
|
}
|
|
}
|
|
|
|
void PointDataMessageHandler::addCallback(
|
|
const std::string& identifier,
|
|
const Callback& newCallback
|
|
) {
|
|
std::lock_guard guard(_onceNodeExistsCallbacksMutex);
|
|
auto it = _onceNodeExistsCallbacks.find(identifier);
|
|
if (it == _onceNodeExistsCallbacks.end()) {
|
|
CallbackList newCallbackList{ newCallback };
|
|
_onceNodeExistsCallbacks.emplace(identifier, newCallbackList);
|
|
}
|
|
else {
|
|
it->second.push_back(newCallback);
|
|
}
|
|
}
|
|
|
|
void PointDataMessageHandler::onFixedColorChange(
|
|
properties::Property* property,
|
|
const std::string& identifier,
|
|
std::shared_ptr<SoftwareConnection> connection
|
|
) {
|
|
if (!connection->isConnected()) {
|
|
SoftwareConnection::PointDataMessageHandlerFriends::removePropertySubscription(
|
|
connection, property->identifier(), identifier
|
|
);
|
|
return;
|
|
}
|
|
|
|
// auto propertySubscription = connection->getPropertySubscription(identifier, property->identifier());
|
|
// if (!propertySubscription) return;
|
|
// if (!propertySubscription->shouldSendMessage) {
|
|
// propertySubscription->shouldSendMessage = true;
|
|
// return;
|
|
// }
|
|
|
|
glm::vec4 color = std::any_cast<glm::vec4>(property->get());
|
|
|
|
const std::string message = simp::formatColorMessage(identifier, color);
|
|
connection->sendMessage(message);
|
|
}
|
|
|
|
void PointDataMessageHandler::onOpacityChange(
|
|
properties::Property* property,
|
|
const std::string& identifier,
|
|
std::shared_ptr<SoftwareConnection> connection
|
|
) {
|
|
if (!connection->isConnected()) {
|
|
SoftwareConnection::PointDataMessageHandlerFriends::removePropertySubscription(
|
|
connection, property->identifier(), identifier
|
|
);
|
|
return;
|
|
}
|
|
|
|
// auto propertySubscription = connection->getPropertySubscription(identifier, property->identifier());
|
|
// if (!propertySubscription) return;
|
|
// if (!propertySubscription->shouldSendMessage) {
|
|
// propertySubscription->shouldSendMessage = true;
|
|
// return;
|
|
// }
|
|
|
|
float value = std::any_cast<float>(property->get());
|
|
std::string hex_value = simp::floatToHex(value);
|
|
|
|
const std::string message = simp::formatUpdateMessage(simp::MessageType::Opacity, identifier, hex_value);
|
|
connection->sendMessage(message);
|
|
}
|
|
|
|
void PointDataMessageHandler::onFixedPointSizeChange(
|
|
properties::Property* property,
|
|
const std::string& identifier,
|
|
std::shared_ptr<SoftwareConnection> connection
|
|
) {
|
|
if (!connection->isConnected()) {
|
|
SoftwareConnection::PointDataMessageHandlerFriends::removePropertySubscription(
|
|
connection, property->identifier(), identifier
|
|
);
|
|
return;
|
|
}
|
|
|
|
// auto propertySubscription = connection->getPropertySubscription(identifier, property->identifier());
|
|
// if (!propertySubscription) return;
|
|
// if (!propertySubscription->shouldSendMessage) {
|
|
// propertySubscription->shouldSendMessage = true;
|
|
// return;
|
|
// }
|
|
|
|
float value = std::any_cast<float>(property->get());
|
|
std::string hex_value = simp::floatToHex(value);
|
|
|
|
const std::string message = simp::formatUpdateMessage(simp::MessageType::FixedSize, identifier, hex_value);
|
|
connection->sendMessage(message);
|
|
}
|
|
|
|
void PointDataMessageHandler::onVisibilityChange(
|
|
properties::Property* property,
|
|
const std::string& identifier,
|
|
std::shared_ptr<SoftwareConnection> connection
|
|
) {
|
|
if (!connection->isConnected()) {
|
|
SoftwareConnection::PointDataMessageHandlerFriends::removePropertySubscription(
|
|
connection, property->identifier(), identifier
|
|
);
|
|
return;
|
|
}
|
|
|
|
// auto propertySubscription = connection->getPropertySubscription(identifier, property->identifier());
|
|
// if (!propertySubscription) return;
|
|
// if (!propertySubscription->shouldSendMessage) {
|
|
// propertySubscription->shouldSendMessage = true;
|
|
// return;
|
|
// }
|
|
|
|
bool isVisible = std::any_cast<bool>(property->get());
|
|
std::string_view visibilityFlag = isVisible ? "T" : "F";
|
|
|
|
const std::string message = simp::formatUpdateMessage(simp::MessageType::Visibility, identifier, visibilityFlag);
|
|
connection->sendMessage(message);
|
|
}
|
|
|
|
// TODO: Move to SIMP / use distanceconversion
|
|
void PointDataMessageHandler::convertToMeterPerSecond(simp::LengthUnit currLengthUnit, std::vector<float>& data) {
|
|
// distanceconversion::convertDistance
|
|
float multiplier = 1.0;
|
|
switch (currLengthUnit) {
|
|
case simp::LengthUnit::km:
|
|
multiplier = 1000.0;
|
|
break;
|
|
case simp::LengthUnit::AU:
|
|
multiplier = static_cast<float>(distanceconstants::AstronomicalUnit);
|
|
break;
|
|
case simp::LengthUnit::lyr:
|
|
multiplier = static_cast<float>(distanceconstants::LightYear);
|
|
break;
|
|
case simp::LengthUnit::pc:
|
|
multiplier = static_cast<float>(distanceconstants::Parsec);
|
|
break;
|
|
case simp::LengthUnit::kpc:
|
|
multiplier = static_cast<float>(distanceconstants::Parsec * 1.0e3);
|
|
break;
|
|
case simp::LengthUnit::Mpc:
|
|
multiplier = static_cast<float>(distanceconstants::Parsec * 1.0e6);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
std::transform(std::begin(data), std::end(data), std::begin(data),
|
|
[multiplier] (float f) {return f * multiplier;});
|
|
}
|
|
|
|
} // namespace openspace
|