mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-01-05 11:09:37 -06:00
Full support of colormaps. Revamp of entire softwareintegration module. Update to SIMP version 1.8.
Co-authored-by: Jacob Molin <jacobmolin@users.noreply.github.com>
This commit is contained in:
@@ -26,23 +26,25 @@ include(${OPENSPACE_CMAKE_EXT_DIR}/module_definition.cmake)
|
||||
|
||||
set(HEADER_FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/pointdatamessagehandler.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/simp.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/softwareintegrationmodule.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/syncabledatastorage.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/network/softwareconnection.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/network/networkengine.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/rendering/renderablepointscloud.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/simp.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/syncablefloatdatastorage.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/interruptibleconcurrentqueue.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/interruptibleconcurrentqueue.inl
|
||||
)
|
||||
source_group("Header Files" FILES ${HEADER_FILES})
|
||||
|
||||
set(SOURCE_FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/pointdatamessagehandler.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/simp.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/softwareintegrationmodule.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/syncabledatastorage.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/network/softwareconnection.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/network/networkengine.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/rendering/renderablepointscloud.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/simp.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/syncablefloatdatastorage.cpp
|
||||
)
|
||||
source_group("Source Files" FILES ${SOURCE_FILES})
|
||||
|
||||
|
||||
54
modules/softwareintegration/interruptibleconcurrentqueue.h
Normal file
54
modules/softwareintegration/interruptibleconcurrentqueue.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* 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. *
|
||||
****************************************************************************************/
|
||||
|
||||
#ifndef __OPENSPACE_MODULE_SOFTWAREINTEGRATION___INTERRUPTABLECONCURRENTQUEUE___H__
|
||||
#define __OPENSPACE_MODULE_SOFTWAREINTEGRATION___INTERRUPTABLECONCURRENTQUEUE___H__
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
namespace openspace {
|
||||
|
||||
template <typename T>
|
||||
class InterruptibleConcurrentQueue {
|
||||
public:
|
||||
T pop();
|
||||
void interrupt();
|
||||
void push(const T& item);
|
||||
void push(T&& item);
|
||||
|
||||
private:
|
||||
std::atomic_bool _interrupted{ false };
|
||||
std::queue<T> _queue;
|
||||
mutable std::mutex _mutex;
|
||||
mutable std::condition_variable _notifyForPop;
|
||||
};
|
||||
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
#include "interruptibleconcurrentqueue.inl"
|
||||
|
||||
#endif // __OPENSPACE_MODULE_SOFTWAREINTEGRATION___INTERRUPTABLECONCURRENTQUEUE___H__
|
||||
73
modules/softwareintegration/interruptibleconcurrentqueue.inl
Normal file
73
modules/softwareintegration/interruptibleconcurrentqueue.inl
Normal file
@@ -0,0 +1,73 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* 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. *
|
||||
****************************************************************************************/
|
||||
|
||||
namespace openspace {
|
||||
|
||||
template <typename T>
|
||||
T InterruptibleConcurrentQueue<T>::pop() {
|
||||
auto isInterruptedOrNotEmpty = [this]() {
|
||||
if (_interrupted) return true;
|
||||
return !_queue.empty();
|
||||
};
|
||||
|
||||
std::unique_lock lock(_mutex);
|
||||
|
||||
// Block execution until interrupted or queue not empty
|
||||
if (!isInterruptedOrNotEmpty()) {
|
||||
_notifyForPop.wait(lock, isInterruptedOrNotEmpty);
|
||||
}
|
||||
|
||||
if (_interrupted) throw ghoul::RuntimeError{""};
|
||||
|
||||
auto item = _queue.front();
|
||||
_queue.pop();
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void InterruptibleConcurrentQueue<T>::interrupt() {
|
||||
_interrupted = true;
|
||||
_notifyForPop.notify_all();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void InterruptibleConcurrentQueue<T>::push(const T& item) {
|
||||
if (_interrupted) return;
|
||||
std::unique_lock<std::mutex> mlock(_mutex);
|
||||
_queue.push(item);
|
||||
mlock.unlock();
|
||||
_notifyForPop.notify_one();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void InterruptibleConcurrentQueue<T>::push(T&& item) {
|
||||
if (_interrupted) return;
|
||||
std::unique_lock<std::mutex> mlock(_mutex);
|
||||
_queue.push(std::move(item));
|
||||
mlock.unlock();
|
||||
_notifyForPop.notify_one();
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
@@ -24,16 +24,11 @@
|
||||
|
||||
#include <modules/softwareintegration/network/networkengine.h>
|
||||
|
||||
#include <modules/softwareintegration/simp.h>
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <openspace/engine/globalscallbacks.h>
|
||||
#include <openspace/navigation/navigationhandler.h>
|
||||
#include <openspace/scene/scene.h>
|
||||
#include <openspace/scripting/scriptengine.h>
|
||||
// #include <openspace/engine/syncengine.h>
|
||||
// #include <openspace/engine/windowdelegate.h>
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
// #include <openspace/util/timemanager.h>
|
||||
|
||||
|
||||
namespace {
|
||||
@@ -41,241 +36,196 @@ namespace {
|
||||
} // namespace
|
||||
|
||||
namespace openspace {
|
||||
|
||||
NetworkEngine::NetworkEngine(const int port)
|
||||
: _port{port}
|
||||
{}
|
||||
|
||||
NetworkEngine::~NetworkEngine() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void NetworkEngine::start() {
|
||||
_socketServer.listen(_port);
|
||||
|
||||
_serverThread = std::thread([this]() { handleNewPeers(); });
|
||||
_serverThread = std::thread([this]() { handleNewSoftwareConnections(); });
|
||||
_eventLoopThread = std::thread([this]() { eventLoop(); });
|
||||
}
|
||||
|
||||
void NetworkEngine::stop() {
|
||||
for (auto [id, peer] : _peers) {
|
||||
disconnect(peer);
|
||||
_shouldStopServerThread = true;
|
||||
|
||||
{
|
||||
std::lock_guard guardSoftwareConnections(_softwareConnectionsMutex);
|
||||
for (auto& [id, connectionPtr] : _softwareConnections) {
|
||||
SoftwareConnection::NetworkEngineFriends::stopThread(connectionPtr);
|
||||
}
|
||||
}
|
||||
|
||||
_shouldStop = true;
|
||||
|
||||
_incomingMessages.interrupt();
|
||||
|
||||
_shouldStopEventThread = true;
|
||||
_socketServer.close();
|
||||
_softwareConnections.clear();
|
||||
|
||||
if (_serverThread.joinable()) {
|
||||
_serverThread.join();
|
||||
}
|
||||
}
|
||||
if (_eventLoopThread.joinable()) {
|
||||
_eventLoopThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkEngine::update() {
|
||||
_pointDataMessageHandler.preSyncUpdate();
|
||||
void NetworkEngine::postSync() {
|
||||
_pointDataMessageHandler.postSync();
|
||||
}
|
||||
|
||||
bool NetworkEngine::isConnected(const std::shared_ptr<Peer> peer) const {
|
||||
return peer->status == Peer::Status::Connected;
|
||||
}
|
||||
|
||||
std::shared_ptr<NetworkEngine::Peer> NetworkEngine::peer(size_t id) {
|
||||
// std::lock_guard<std::mutex> lock(_peerListMutex);
|
||||
auto it = _peers.find(id);
|
||||
if (it == _peers.end()) return nullptr;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void NetworkEngine::disconnect(std::shared_ptr<Peer> peer) {
|
||||
if (peer == nullptr) return;
|
||||
if (!isConnected(peer)) return; // Already disconnected
|
||||
|
||||
--_nConnections;
|
||||
peer->status = Peer::Status::Disconnected;
|
||||
peer->connection.disconnect();
|
||||
|
||||
// auto sgnIterator = peer->sceneGraphNodes.begin();
|
||||
// while (sgnIterator != peer->sceneGraphNodes.end()) {
|
||||
// LDEBUG(fmt::format("t{}: disconnect() - removing SGN '{}'", std::this_thread::get_id(), *sgnIterator));
|
||||
// removeSceneGraphNode(*sgnIterator);
|
||||
// peer->sceneGraphNodes.erase(sgnIterator);
|
||||
// ++sgnIterator;
|
||||
// }
|
||||
|
||||
if (peer->thread.joinable()) peer->thread.join();
|
||||
_peers.erase(peer->id);
|
||||
}
|
||||
|
||||
void NetworkEngine::eventLoop() {
|
||||
while (!_shouldStop) {
|
||||
if (!_incomingMessages.empty()) {
|
||||
auto pm = _incomingMessages.pop();
|
||||
handlePeerMessage(std::move(pm));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkEngine::handleNewPeers() {
|
||||
while (!_shouldStop) {
|
||||
std::unique_ptr<ghoul::io::TcpSocket> socket =
|
||||
_socketServer.awaitPendingTcpSocket();
|
||||
void NetworkEngine::handleNewSoftwareConnections() {
|
||||
while (!_shouldStopServerThread) {
|
||||
std::unique_ptr<ghoul::io::TcpSocket> socket = _socketServer.awaitPendingTcpSocket();
|
||||
|
||||
if (!socket) return;
|
||||
|
||||
socket->startStreams();
|
||||
|
||||
std::shared_ptr<Peer> p = std::make_shared<Peer>(Peer{
|
||||
_nextConnectionId++,
|
||||
"",
|
||||
{},
|
||||
SoftwareConnection(std::move(socket)),
|
||||
{},
|
||||
Peer::Status::Connected
|
||||
});
|
||||
auto [it, peerInserted] = _peers.emplace(p->id, p);
|
||||
if (peerInserted){
|
||||
// The thread 'it.first->second->thread' will run 'peerEventLoop()' as fast as it can until stopped
|
||||
it->second->thread = std::thread(
|
||||
[this, &p] () {
|
||||
peerEventLoop(p->id);
|
||||
});
|
||||
auto p = std::make_shared<SoftwareConnection>(SoftwareConnection{ std::move(socket) });
|
||||
std::lock_guard guard(_softwareConnectionsMutex);
|
||||
auto [it, peerInserted] = _softwareConnections.emplace(p->id(), p);
|
||||
|
||||
if (peerInserted) {
|
||||
auto& connectionPtr = it->second;
|
||||
auto thread = std::thread{
|
||||
[this, &connectionPtr] {
|
||||
peerEventLoop(connectionPtr->id());
|
||||
}
|
||||
};
|
||||
connectionPtr->setThread(thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkEngine::peerEventLoop(size_t id) {
|
||||
void NetworkEngine::peerEventLoop(size_t connection_id) {
|
||||
using namespace std::literals::chrono_literals;
|
||||
auto connectionPtr = getSoftwareConnection(connection_id);
|
||||
|
||||
while (!_shouldStop) {
|
||||
std::shared_ptr<Peer> p = peer(id);
|
||||
|
||||
if (!p || !p->connection.isConnectedOrConnecting()
|
||||
|| p->status == Peer::Status::Disconnected) {
|
||||
break;
|
||||
}
|
||||
|
||||
while (!connectionPtr->shouldStopThread()) {
|
||||
try {
|
||||
SoftwareConnection::Message m = p->connection.receiveMessageFromSoftware();
|
||||
|
||||
_incomingMessages.push({ id, m });
|
||||
SoftwareConnection::Message m = connectionPtr->receiveMessageFromSoftware();
|
||||
_incomingMessages.push({ connection_id, m });
|
||||
}
|
||||
catch (const SoftwareConnection::SoftwareConnectionLostError& err) {
|
||||
if (p->status == Peer::Status::Connected) {
|
||||
LERROR(fmt::format("Connection lost to {}: {}", p->id, err.message));
|
||||
disconnect(p);
|
||||
if (connectionPtr->shouldStopThread()) break;
|
||||
|
||||
if (connectionPtr && (!connectionPtr->shouldStopThread() || !connectionPtr->isConnectedOrConnecting())) {
|
||||
LDEBUG(fmt::format("Connection lost to {}: {}", connection_id, err.message));
|
||||
_incomingMessages.push({
|
||||
connection_id,
|
||||
SoftwareConnection::Message{ simp::MessageType::Disconnection }
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkEngine::handlePeerMessage(PeerMessage peerMessage) {
|
||||
const size_t peerId = peerMessage.peerId;
|
||||
std::shared_ptr<Peer> peerPtr = peer(peerId);
|
||||
if(peerPtr == nullptr) {
|
||||
LERROR(fmt::format("No peer with peerId {} could be found", peerId));
|
||||
void NetworkEngine::eventLoop() {
|
||||
while (!_shouldStopEventThread) {
|
||||
// The call to "pop" below will block execution
|
||||
// on this thread until interrupt is called
|
||||
try {
|
||||
auto pm = _incomingMessages.pop();
|
||||
handleIncomingMessage(pm);
|
||||
}
|
||||
catch (const ghoul::RuntimeError&) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<SoftwareConnection> NetworkEngine::getSoftwareConnection(size_t id) {
|
||||
std::lock_guard guard(_softwareConnectionsMutex);
|
||||
auto it = _softwareConnections.find(id);
|
||||
if (it == _softwareConnections.end()) return nullptr;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void NetworkEngine::handleIncomingMessage(IncomingMessage incomingMessage) {
|
||||
auto connectionPtr = getSoftwareConnection(incomingMessage.connection_id);
|
||||
|
||||
if(!connectionPtr) {
|
||||
LDEBUG(fmt::format("Trying to handle message from disconnected peer. Aborting."));
|
||||
return;
|
||||
}
|
||||
|
||||
const simp::MessageType messageType = peerMessage.message.type;
|
||||
std::vector<char>& message = peerMessage.message.content;
|
||||
const simp::MessageType messageType = incomingMessage.message.type;
|
||||
std::vector<char>& message = incomingMessage.message.content;
|
||||
|
||||
switch (messageType) {
|
||||
case simp::MessageType::Connection:
|
||||
{
|
||||
const std::string software{ message.begin(), message.end() };
|
||||
LINFO(fmt::format("OpenSpace has connected with {} through socket", software));
|
||||
// Send back message to software to complete handshake
|
||||
peerPtr->connection.sendMessage(simp::formatConnectionMessage(software));
|
||||
break;
|
||||
}
|
||||
case simp::MessageType::ReadPointData: {
|
||||
LDEBUG("Message recieved.. Point Data");
|
||||
_pointDataMessageHandler.handlePointDataMessage(message, peerPtr->connection, peerPtr->sceneGraphNodes);
|
||||
break;
|
||||
}
|
||||
case simp::MessageType::RemoveSceneGraphNode: {
|
||||
const std::string identifier(message.begin(), message.end());
|
||||
LDEBUG(fmt::format("Message recieved.. Delete SGN: {}", identifier));
|
||||
removeSceneGraphNode(identifier);
|
||||
case simp::MessageType::Connection: {
|
||||
size_t offset = 0;
|
||||
const std::string software = simp::readString(message, offset);
|
||||
|
||||
auto sgnIterator = peerPtr->sceneGraphNodes.begin();
|
||||
while (sgnIterator != peerPtr->sceneGraphNodes.end()) {
|
||||
if (*sgnIterator == identifier) {
|
||||
peerPtr->sceneGraphNodes.erase(sgnIterator);
|
||||
break;
|
||||
}
|
||||
++sgnIterator;
|
||||
// Send back message to software to complete handshake
|
||||
connectionPtr->sendMessage(simp::formatConnectionMessage(software));
|
||||
LINFO(fmt::format("OpenSpace has connected with {} through socket", software));
|
||||
break;
|
||||
}
|
||||
case simp::MessageType::PointData: {
|
||||
LDEBUG("Message recieved.. Point Data");
|
||||
_pointDataMessageHandler.handlePointDataMessage(message, connectionPtr);
|
||||
break;
|
||||
}
|
||||
case simp::MessageType::RemoveSceneGraphNode: {
|
||||
LDEBUG(fmt::format("Message recieved.. Remove SGN"));
|
||||
_pointDataMessageHandler.handleRemoveSGNMessage(message, connectionPtr);
|
||||
break;
|
||||
}
|
||||
case simp::MessageType::Color: {
|
||||
LDEBUG(fmt::format("Message recieved.. New Color"));
|
||||
_pointDataMessageHandler.handleFixedColorMessage(message, connectionPtr);
|
||||
break;
|
||||
}
|
||||
case simp::MessageType::Colormap: {
|
||||
LDEBUG(fmt::format("Message recieved.. New Colormap"));
|
||||
_pointDataMessageHandler.handleColormapMessage(message, connectionPtr);
|
||||
break;
|
||||
}
|
||||
case simp::MessageType::AttributeData: {
|
||||
LDEBUG(fmt::format("Message recieved.. New Colormap scalar values"));
|
||||
_pointDataMessageHandler.handleAttributeDataMessage(message, connectionPtr);
|
||||
break;
|
||||
}
|
||||
case simp::MessageType::Opacity: {
|
||||
LDEBUG(fmt::format("Message recieved.. New Opacity"));
|
||||
_pointDataMessageHandler.handleOpacityMessage(message, connectionPtr);
|
||||
break;
|
||||
}
|
||||
case simp::MessageType::Size: {
|
||||
LDEBUG(fmt::format("Message recieved.. New Size"));
|
||||
_pointDataMessageHandler.handleFixedPointSizeMessage(message, connectionPtr);
|
||||
break;
|
||||
}
|
||||
case simp::MessageType::Visibility: {
|
||||
LDEBUG(fmt::format("Message recieved.. New Visibility"));
|
||||
_pointDataMessageHandler.handleVisiblityMessage(message, connectionPtr);
|
||||
break;
|
||||
}
|
||||
case simp::MessageType::Disconnection: {
|
||||
LDEBUG(fmt::format("Message recieved.. Disconnect software connection: {}", incomingMessage.connection_id));
|
||||
std::lock_guard guard(_softwareConnectionsMutex);
|
||||
if (_softwareConnections.count(incomingMessage.connection_id)) {
|
||||
_softwareConnections.erase(incomingMessage.connection_id);
|
||||
}
|
||||
SoftwareConnection::NetworkEngineFriends::stopThread(connectionPtr);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LERROR(fmt::format(
|
||||
"Unsupported message type: {}", incomingMessage.message.rawMessageType
|
||||
));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case simp::MessageType::Color: {
|
||||
const std::string colorMessage(message.begin(), message.end());
|
||||
LDEBUG(fmt::format("Message recieved.. New Color: {}", colorMessage));
|
||||
|
||||
_pointDataMessageHandler.handleColorMessage(message);
|
||||
break;
|
||||
}
|
||||
case simp::MessageType::ColorMap: {
|
||||
const std::string colorMapMessage(message.begin(), message.end());
|
||||
LDEBUG(fmt::format("Message recieved.. New ColorMap"));
|
||||
|
||||
_pointDataMessageHandler.handleColorMapMessage(message);
|
||||
break;
|
||||
}
|
||||
case simp::MessageType::Opacity: {
|
||||
const std::string opacityMessage(message.begin(), message.end());
|
||||
LDEBUG(fmt::format("Message recieved.. New Opacity: {}", opacityMessage));
|
||||
|
||||
_pointDataMessageHandler.handleOpacityMessage(message);
|
||||
break;
|
||||
}
|
||||
case simp::MessageType::Size: {
|
||||
const std::string sizeMessage(message.begin(), message.end());
|
||||
LDEBUG(fmt::format("Message recieved.. New Size: {}", sizeMessage));
|
||||
|
||||
_pointDataMessageHandler.handlePointSizeMessage(message);
|
||||
break;
|
||||
}
|
||||
case simp::MessageType::Visibility: {
|
||||
const std::string visibilityMessage(message.begin(), message.end());
|
||||
LDEBUG(fmt::format("Message recieved.. New Visibility: {}", visibilityMessage));
|
||||
|
||||
_pointDataMessageHandler.handleVisiblityMessage(message);
|
||||
break;
|
||||
}
|
||||
case simp::MessageType::Disconnection: {
|
||||
LDEBUG(fmt::format("Message recieved.. Disconnect peer: {}", peerPtr->id));
|
||||
disconnect(peerPtr);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LERROR(fmt::format(
|
||||
"Unsupported message type: {}", static_cast<int>(messageType)
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkEngine::removeSceneGraphNode(const std::string &identifier) {
|
||||
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
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Anchor', 'Sun')"
|
||||
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Aim', '')",
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
"openspace.removeSceneGraphNode('" + identifier + "');",
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
|
||||
// TODO: remove from sceneGraphNodes
|
||||
// peer->sceneGraphNodes.erase(sgnIterator);
|
||||
|
||||
LDEBUG(fmt::format("Scene graph node '{}' removed.", identifier));
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
|
||||
#include <modules/softwareintegration/network/softwareconnection.h>
|
||||
#include <modules/softwareintegration/pointdatamessagehandler.h>
|
||||
#include <openspace/util/concurrentqueue.h>
|
||||
#include <modules/softwareintegration/interruptibleconcurrentqueue.h>
|
||||
#include <modules/softwareintegration/simp.h>
|
||||
#include <ghoul/io/socket/tcpsocketserver.h>
|
||||
|
||||
namespace openspace {
|
||||
@@ -35,61 +36,42 @@ namespace openspace {
|
||||
class NetworkEngine {
|
||||
public:
|
||||
NetworkEngine(const int port = 4700);
|
||||
~NetworkEngine();
|
||||
|
||||
struct Peer {
|
||||
enum class Status : uint32_t {
|
||||
Disconnected = 0,
|
||||
Connected
|
||||
};
|
||||
|
||||
size_t id;
|
||||
std::string name;
|
||||
std::thread thread;
|
||||
|
||||
SoftwareConnection connection;
|
||||
std::list<std::string> sceneGraphNodes; // TODO: Change to std::unordered_set?
|
||||
Status status;
|
||||
};
|
||||
|
||||
struct PeerMessage {
|
||||
size_t peerId;
|
||||
SoftwareConnection::Message message;
|
||||
struct IncomingMessage {
|
||||
size_t connection_id{1};
|
||||
SoftwareConnection::Message message{ simp::MessageType::Unknown };
|
||||
};
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
void update();
|
||||
void postSync();
|
||||
|
||||
private:
|
||||
void disconnect(std::shared_ptr<Peer> peer);
|
||||
void handleNewPeers();
|
||||
void peerEventLoop(size_t id);
|
||||
void handlePeerMessage(PeerMessage peerMessage);
|
||||
void handleNewSoftwareConnections();
|
||||
void handleIncomingMessage(IncomingMessage incomingMessage);
|
||||
void peerEventLoop(size_t connection_id);
|
||||
void eventLoop();
|
||||
|
||||
bool isConnected(const std::shared_ptr<Peer> peer) const;
|
||||
// The destuction of the object a shared_ptr is pointing to, occurs when the pointer no longer has any owners
|
||||
std::shared_ptr<SoftwareConnection> getSoftwareConnection(size_t id);
|
||||
|
||||
void removeSceneGraphNode(const std::string &identifier);
|
||||
|
||||
std::shared_ptr<Peer> peer(size_t id);
|
||||
|
||||
std::unordered_map<size_t, std::shared_ptr<Peer>> _peers;
|
||||
mutable std::mutex _peerListMutex;
|
||||
std::unordered_map<size_t, std::shared_ptr<SoftwareConnection>> _softwareConnections;
|
||||
std::mutex _softwareConnectionsMutex;
|
||||
|
||||
ghoul::io::TcpSocketServer _socketServer;
|
||||
size_t _nextConnectionId = 1;
|
||||
std::atomic_size_t _nConnections = 0;
|
||||
std::thread _serverThread;
|
||||
std::atomic_bool _shouldStopServerThread = false;
|
||||
std::thread _eventLoopThread;
|
||||
std::atomic_bool _shouldStopEventThread = false;
|
||||
|
||||
std::atomic_bool _shouldStop = false;
|
||||
|
||||
const int _port;
|
||||
|
||||
// Message handlers
|
||||
PointDataMessageHandler _pointDataMessageHandler;
|
||||
|
||||
ConcurrentQueue<PeerMessage> _incomingMessages;
|
||||
InterruptibleConcurrentQueue<IncomingMessage> _incomingMessages;
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -24,11 +24,12 @@
|
||||
|
||||
#include <modules/softwareintegration/network/softwareconnection.h>
|
||||
|
||||
#include <modules/softwareintegration/simp.h>
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <openspace/engine/syncengine.h>
|
||||
#include <openspace/engine/windowdelegate.h>
|
||||
#include <openspace/query/query.h>
|
||||
#include <openspace/rendering/renderable.h>
|
||||
|
||||
namespace {
|
||||
constexpr const char* _loggerCat = "SoftwareConnection";
|
||||
@@ -36,52 +37,172 @@ namespace {
|
||||
|
||||
namespace openspace {
|
||||
|
||||
SoftwareConnection::Message::Message(simp::MessageType type, std::vector<char> content)
|
||||
: type{ type }, content{ std::move(content) }
|
||||
{}
|
||||
std::atomic_size_t SoftwareConnection::_nextConnectionId = 1;
|
||||
|
||||
SoftwareConnection::SoftwareConnectionLostError::SoftwareConnectionLostError(const std::string& msg)
|
||||
: ghoul::RuntimeError(fmt::format("{}{}", "Software connection lost", msg), "SoftwareConnection")
|
||||
{}
|
||||
|
||||
SoftwareConnection::SoftwareConnection(std::unique_ptr<ghoul::io::TcpSocket> socket)
|
||||
: _socket(std::move(socket))
|
||||
: _id{ _nextConnectionId++ }, _socket{ std::move(socket) }, _sceneGraphNodes{},
|
||||
_thread{}, _shouldStopThread{ false }
|
||||
{}
|
||||
|
||||
bool SoftwareConnection::isConnected() const {
|
||||
return _socket->isConnected();
|
||||
}
|
||||
SoftwareConnection::SoftwareConnection(SoftwareConnection&& sc)
|
||||
: _id{ std::move(sc._id) }, _socket{ std::move(sc._socket) },
|
||||
_isConnected{ sc._isConnected }, _sceneGraphNodes{ std::move(sc._sceneGraphNodes) },
|
||||
_thread{}, _shouldStopThread{ false }
|
||||
{}
|
||||
|
||||
bool SoftwareConnection::isConnectedOrConnecting() const {
|
||||
return _socket->isConnected() || _socket->isConnecting();
|
||||
}
|
||||
SoftwareConnection::~SoftwareConnection() {
|
||||
LINFO(fmt::format("Remove software connection {}", _id));
|
||||
|
||||
bool SoftwareConnection::sendMessage(std::string message) {
|
||||
LDEBUG(fmt::format("In SoftwareConnection::sendMessage()", 0));
|
||||
if (!_isConnected) return;
|
||||
_isConnected = false;
|
||||
|
||||
if (_isListening) {
|
||||
if (!_socket->put<char>(message.data(), message.size())) {
|
||||
return false;
|
||||
}
|
||||
LDEBUG(fmt::format("Message sent: {}", message));
|
||||
}
|
||||
else {
|
||||
_isListening = true;
|
||||
return false;
|
||||
for (auto& identifier : _sceneGraphNodes) {
|
||||
removePropertySubscriptions(identifier);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SoftwareConnection::disconnect() {
|
||||
if (_socket) {
|
||||
sendMessage(simp::formatDisconnectionMessage());
|
||||
_socket->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
ghoul::io::TcpSocket* SoftwareConnection::socket() {
|
||||
return _socket.get();
|
||||
void SoftwareConnection::addPropertySubscription(
|
||||
const std::string propertyName,
|
||||
const std::string& identifier,
|
||||
std::function<void()> newHandler
|
||||
) {
|
||||
// Get renderable
|
||||
auto r = renderable(identifier);
|
||||
if (!r) {
|
||||
LWARNING(fmt::format(
|
||||
"Couldn't add property subscription. Renderable \"{}\" doesn't exist",
|
||||
identifier
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!r->hasProperty(propertyName)) {
|
||||
LWARNING(fmt::format(
|
||||
"Couldn't add property subscription. Property \"{}\" doesn't exist on \"{}\"",
|
||||
propertyName, identifier
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
auto property = r->property(propertyName);
|
||||
OnChangeHandle onChangeHandle = property->onChange(newHandler);
|
||||
|
||||
auto propertySubscriptions = _subscribedProperties.find(identifier);
|
||||
if (propertySubscriptions != _subscribedProperties.end()) {
|
||||
// At least one property have been subscribed to on this SGN
|
||||
auto propertySubscription = propertySubscriptions->second.find(propertyName);
|
||||
if (propertySubscription != propertySubscriptions->second.end()) {
|
||||
// Property subscription already exists
|
||||
removeExistingPropertySubscription(identifier, property, onChangeHandle);
|
||||
propertySubscription->second = onChangeHandle;
|
||||
}
|
||||
else {
|
||||
// Property subscription doesn't exist
|
||||
propertySubscriptions->second.emplace(propertyName, onChangeHandle);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No properties have been subscribed to on this SGN
|
||||
PropertySubscriptions newPropertySubscriptionMap{ { propertyName, onChangeHandle } };
|
||||
_subscribedProperties.emplace(identifier, std::move(newPropertySubscriptionMap));
|
||||
}
|
||||
}
|
||||
|
||||
void SoftwareConnection::removePropertySubscriptions(const std::string& identifier) {
|
||||
// Get renderable
|
||||
auto r = renderable(identifier);
|
||||
if (!r) {
|
||||
LWARNING(fmt::format(
|
||||
"Couldn't remove property subscriptions, renderable {} doesn't exist",
|
||||
identifier
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
auto propertySubscriptions = _subscribedProperties.find(identifier);
|
||||
|
||||
if (propertySubscriptions == _subscribedProperties.end()) return;
|
||||
|
||||
for (auto& [propertyName, onChangeHandle] : propertySubscriptions->second) {
|
||||
if (!r->hasProperty(propertyName)) {
|
||||
LWARNING(fmt::format(
|
||||
"Couldn't remove property subscription. Property \"{}\" doesn't exist on \"{}\"",
|
||||
propertyName, identifier
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto propertySubscription = propertySubscriptions->second.find(propertyName);
|
||||
if (propertySubscription == propertySubscriptions->second.end()) continue;
|
||||
|
||||
auto property = r->property(propertyName);
|
||||
removeExistingPropertySubscription(identifier, property, onChangeHandle);
|
||||
}
|
||||
|
||||
_subscribedProperties.erase(propertySubscriptions);
|
||||
}
|
||||
|
||||
void SoftwareConnection::removeExistingPropertySubscription(
|
||||
const std::string& identifier,
|
||||
properties::Property *property,
|
||||
OnChangeHandle onChangeHandle
|
||||
) {
|
||||
property->removeOnChange(onChangeHandle);
|
||||
|
||||
auto propertySubscriptions = _subscribedProperties.find(identifier);
|
||||
propertySubscriptions->second.erase(property->identifier());
|
||||
}
|
||||
|
||||
void SoftwareConnection::disconnect() {
|
||||
_socket->disconnect();
|
||||
}
|
||||
|
||||
bool SoftwareConnection::isConnected() const {
|
||||
return _isConnected && _socket && _socket->isConnected();
|
||||
}
|
||||
|
||||
bool SoftwareConnection::isConnectedOrConnecting() const {
|
||||
return _isConnected && _socket && (_socket->isConnected() || _socket->isConnecting());
|
||||
}
|
||||
|
||||
bool SoftwareConnection::sendMessage(const std::string& message) {
|
||||
try {
|
||||
if (!_socket || !isConnected()) {
|
||||
throw SoftwareConnectionLostError("Connection lost...");
|
||||
}
|
||||
if (_socket->put<char>(message.data(), message.size())) {
|
||||
LDEBUG(fmt::format("Message sent: {}", message));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (const SoftwareConnectionLostError& err) {
|
||||
LERROR(fmt::format("Couldn't send message: \"{}\", due to: {}", message, err.message));
|
||||
}
|
||||
catch (const std::exception& err) {
|
||||
LERROR(fmt::format("Couldn't send message: \"{}\", due to: {}", message, err.what()));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SoftwareConnection::addSceneGraphNode(const std::string& identifier) {
|
||||
_sceneGraphNodes.insert(identifier);
|
||||
}
|
||||
|
||||
void SoftwareConnection::removeSceneGraphNode(const std::string& identifier) {
|
||||
size_t amountRemoved = _sceneGraphNodes.erase(identifier);
|
||||
|
||||
if (amountRemoved > 0) {
|
||||
removePropertySubscriptions(identifier);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,15 +223,14 @@ SoftwareConnection::Message SoftwareConnection::receiveMessageFromSoftware() {
|
||||
throw SoftwareConnectionLostError("Failed to read header from socket. Disconnecting.");
|
||||
}
|
||||
|
||||
// Read and convert version number: Byte 0-2
|
||||
std::string version;
|
||||
// Read the protocol version number: Byte 0-2
|
||||
std::string protocolVersionIn;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
version.push_back(headerBuffer[i]);
|
||||
protocolVersionIn.push_back(headerBuffer[i]);
|
||||
}
|
||||
const float protocolVersionIn = std::stof(version);
|
||||
|
||||
// Make sure that header matches the protocol version
|
||||
if (abs(protocolVersionIn - simp::ProtocolVersion) >= FLT_EPSILON) {
|
||||
if (protocolVersionIn != simp::ProtocolVersion) {
|
||||
throw SoftwareConnectionLostError(fmt::format(
|
||||
"Protocol versions do not match. Remote version: {}, Local version: {}",
|
||||
protocolVersionIn,
|
||||
@@ -131,36 +251,44 @@ SoftwareConnection::Message SoftwareConnection::receiveMessageFromSoftware() {
|
||||
}
|
||||
const size_t subjectSize = std::stoi(subjectSizeIn);
|
||||
|
||||
std::string rawHeader;
|
||||
for (int i = 0; i < 22; i++) {
|
||||
rawHeader.push_back(headerBuffer[i]);
|
||||
}
|
||||
LDEBUG(fmt::format("Message received with header: {}", rawHeader));
|
||||
// TODO: Remove this
|
||||
// std::string rawHeader;
|
||||
// for (int i = 0; i < 22; i++) {
|
||||
// rawHeader.push_back(headerBuffer[i]);
|
||||
// }
|
||||
// LDEBUG(fmt::format("Message received with header: {}", rawHeader));
|
||||
|
||||
auto typeEnum = simp::getMessageType(type);
|
||||
|
||||
// Receive the message data
|
||||
if (typeEnum != simp::MessageType::Disconnection) {
|
||||
if (typeEnum != simp::MessageType::Disconnection && typeEnum != simp::MessageType::Unknown) {
|
||||
subjectBuffer.resize(subjectSize);
|
||||
if (!_socket->get(subjectBuffer.data(), subjectSize)) {
|
||||
throw SoftwareConnectionLostError("Failed to read message from socket. Disconnecting.");
|
||||
}
|
||||
}
|
||||
|
||||
// Delegate decoding depending on message type
|
||||
if (typeEnum == simp::MessageType::Color
|
||||
|| typeEnum == simp::MessageType::Opacity
|
||||
|| typeEnum == simp::MessageType::Size
|
||||
|| typeEnum == simp::MessageType::Visibility
|
||||
) {
|
||||
_isListening = false;
|
||||
}
|
||||
return Message{ typeEnum, subjectBuffer, type };
|
||||
}
|
||||
|
||||
if (typeEnum == simp::MessageType::Unknown) {
|
||||
LERROR(fmt::format("Unsupported message type: {}. Disconnecting...", type));
|
||||
}
|
||||
bool SoftwareConnection::shouldStopThread() {
|
||||
return _shouldStopThread;
|
||||
}
|
||||
|
||||
return Message(typeEnum, subjectBuffer);
|
||||
size_t SoftwareConnection::id() {
|
||||
return _id;
|
||||
}
|
||||
|
||||
void SoftwareConnection::setThread(std::thread& t) {
|
||||
_thread = std::move(t);
|
||||
}
|
||||
|
||||
void SoftwareConnection::NetworkEngineFriends::stopThread(std::shared_ptr<SoftwareConnection> connectionPtr) {
|
||||
connectionPtr->_shouldStopThread = true;
|
||||
connectionPtr->disconnect();
|
||||
if (connectionPtr->_thread.joinable()) {
|
||||
connectionPtr->_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -28,38 +28,85 @@
|
||||
#include <openspace/network/messagestructures.h>
|
||||
#include <modules/softwareintegration/simp.h>
|
||||
#include <ghoul/io/socket/tcpsocket.h>
|
||||
#include <openspace/properties/property.h>
|
||||
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace openspace {
|
||||
|
||||
class Renderable;
|
||||
|
||||
class SoftwareConnection {
|
||||
public:
|
||||
using OnChangeHandle = properties::Property::OnChangeHandle;
|
||||
using PropertySubscriptions = std::unordered_map<std::string, properties::Property::OnChangeHandle>;
|
||||
using SubscribedProperties = std::unordered_map<std::string, PropertySubscriptions>;
|
||||
|
||||
struct Message {
|
||||
Message() = default;
|
||||
Message(simp::MessageType type, std::vector<char> content);
|
||||
|
||||
simp::MessageType type;
|
||||
std::vector<char> content;
|
||||
std::vector<char> content{};
|
||||
std::string rawMessageType{ "" };
|
||||
};
|
||||
|
||||
class SoftwareConnectionLostError : public ghoul::RuntimeError {
|
||||
public:
|
||||
explicit SoftwareConnectionLostError(const std::string& msg);
|
||||
};
|
||||
class SoftwareConnectionLostError;
|
||||
|
||||
SoftwareConnection(std::unique_ptr<ghoul::io::TcpSocket> socket);
|
||||
explicit SoftwareConnection(std::unique_ptr<ghoul::io::TcpSocket> socket);
|
||||
SoftwareConnection(SoftwareConnection&& p);
|
||||
~SoftwareConnection();
|
||||
|
||||
void disconnect();
|
||||
bool isConnected() const;
|
||||
bool isConnectedOrConnecting() const;
|
||||
bool sendMessage(std::string message);
|
||||
void disconnect();
|
||||
bool sendMessage(const std::string& message);
|
||||
|
||||
ghoul::io::TcpSocket* socket();
|
||||
void addPropertySubscription(
|
||||
const std::string propertyName,
|
||||
const std::string& identifier,
|
||||
std::function<void()> newHandler
|
||||
);
|
||||
|
||||
SoftwareConnection::Message receiveMessageFromSoftware();
|
||||
|
||||
void addSceneGraphNode(const std::string& identifier);
|
||||
void removeSceneGraphNode(const std::string& identifier);
|
||||
|
||||
size_t id();
|
||||
size_t nConnections();
|
||||
void setThread(std::thread& t);
|
||||
bool shouldStopThread();
|
||||
|
||||
class NetworkEngineFriends {
|
||||
private:
|
||||
static void stopThread(std::shared_ptr<SoftwareConnection> connectionPtr);
|
||||
friend class NetworkEngine;
|
||||
};
|
||||
|
||||
private:
|
||||
bool _isListening = true;
|
||||
void removePropertySubscriptions(const std::string& identifier);
|
||||
void removeExistingPropertySubscription(
|
||||
const std::string& identifier,
|
||||
properties::Property *property,
|
||||
OnChangeHandle onChangeHandle
|
||||
);
|
||||
|
||||
SubscribedProperties _subscribedProperties;
|
||||
|
||||
std::unordered_set<std::string> _sceneGraphNodes;
|
||||
|
||||
bool _isConnected = true;
|
||||
std::unique_ptr<ghoul::io::TcpSocket> _socket;
|
||||
|
||||
size_t _id;
|
||||
std::thread _thread;
|
||||
std::atomic_bool _shouldStopThread;
|
||||
|
||||
static std::atomic_size_t _nextConnectionId;
|
||||
};
|
||||
|
||||
class SoftwareConnection::SoftwareConnectionLostError : public ghoul::RuntimeError {
|
||||
public:
|
||||
explicit SoftwareConnectionLostError(const std::string& msg);
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -23,9 +23,10 @@
|
||||
****************************************************************************************/
|
||||
|
||||
#include <modules/softwareintegration/pointdatamessagehandler.h>
|
||||
|
||||
#include <modules/softwareintegration/softwareintegrationmodule.h>
|
||||
#include <modules/softwareintegration/simp.h>
|
||||
|
||||
#include <openspace/navigation/navigationhandler.h>
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <openspace/engine/moduleengine.h>
|
||||
#include <openspace/query/query.h>
|
||||
@@ -44,272 +45,462 @@ namespace {
|
||||
|
||||
namespace openspace {
|
||||
|
||||
void PointDataMessageHandler::handlePointDataMessage(const std::vector<char>& message,
|
||||
SoftwareConnection& connection,
|
||||
std::list<std::string>& sceneGraphNodes)
|
||||
{
|
||||
void PointDataMessageHandler::handlePointDataMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection) {
|
||||
size_t messageOffset = 0;
|
||||
|
||||
std::string identifier;
|
||||
glm::vec4 color;
|
||||
float opacity;
|
||||
float size;
|
||||
std::string guiName;
|
||||
int nPoints;
|
||||
int dimensionality;
|
||||
|
||||
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
|
||||
identifier = readString(message, messageOffset);
|
||||
sceneGraphNodes.push_back(identifier);
|
||||
color = readColor(message, messageOffset);
|
||||
opacity = readFloatValue(message, messageOffset);
|
||||
size = readFloatValue(message, messageOffset);
|
||||
guiName = readString(message, messageOffset);
|
||||
nPoints = readIntValue(message, messageOffset);
|
||||
dimensionality = readIntValue(message, messageOffset);
|
||||
points.reserve(nPoints * dimensionality);
|
||||
points = readPointData(message, messageOffset, nPoints, dimensionality);
|
||||
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;
|
||||
}
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
// Create a renderable
|
||||
ghoul::Dictionary renderable;
|
||||
renderable.setValue("Type", "RenderablePointsCloud"s);
|
||||
renderable.setValue("Color", static_cast<glm::dvec3>(glm::vec3{color.r, color.g, color.b}));
|
||||
renderable.setValue("Opacity", static_cast<double>(opacity));
|
||||
renderable.setValue("Size", static_cast<double>(size));
|
||||
renderable.setValue("Identifier", identifier);
|
||||
|
||||
// Use the renderable identifier as the data key
|
||||
const std::string key = identifier + "-DataPoints";
|
||||
auto module = global::moduleEngine->module<SoftwareIntegrationModule>();
|
||||
module->storeData(key, std::move(points));
|
||||
|
||||
renderable.setValue("DataStorageKey", key);
|
||||
|
||||
ghoul::Dictionary gui;
|
||||
gui.setValue("Name", guiName);
|
||||
gui.setValue("Path", "/Software Integration"s);
|
||||
|
||||
ghoul::Dictionary node;
|
||||
node.setValue("Identifier", identifier);
|
||||
node.setValue("Renderable", renderable);
|
||||
node.setValue("GUI", gui);
|
||||
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
"openspace.addSceneGraphNode(" + ghoul::formatLua(node) + ")",
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.RetargetAnchor', nil)"
|
||||
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Anchor', '" + identifier + "')"
|
||||
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Aim', '')"
|
||||
"openspace.setPropertyValueSingle('Modules.CefWebGui.Reload', nil)", // Reload WebGUI so that SoftwareIntegration GUI appears
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
|
||||
// We have to wait until the renderable exists before we can subscribe to
|
||||
// changes in its properties
|
||||
auto callback = [this, identifier, &connection]() {
|
||||
subscribeToRenderableUpdates(identifier, connection);
|
||||
};
|
||||
_onceNodeExistsCallbacks.emplace(identifier, callback);
|
||||
}
|
||||
|
||||
void PointDataMessageHandler::handleColorMessage(const std::vector<char>& message) {
|
||||
void PointDataMessageHandler::handleFixedColorMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection) {
|
||||
size_t messageOffset = 0;
|
||||
const std::string identifier = readString(message, messageOffset);
|
||||
const glm::vec3 color = readColor(message, messageOffset);
|
||||
std::string identifier;
|
||||
|
||||
// Get renderable
|
||||
auto r = getRenderable(identifier);
|
||||
if (!r) return;
|
||||
checkRenderable(message, messageOffset, connection, identifier);
|
||||
|
||||
// Get color of renderable
|
||||
properties::Property* colorProperty = r->property("Color");
|
||||
std::any propertyAny = colorProperty->get();
|
||||
glm::vec3 propertyColor = std::any_cast<glm::vec3>(propertyAny);
|
||||
|
||||
if (propertyColor != glm::vec3(0, 0, 0)) {
|
||||
LWARNING(fmt::format("propertyColor '{}'", propertyColor));
|
||||
glm::vec4 color;
|
||||
try {
|
||||
color = simp::readColor(message, messageOffset);
|
||||
}
|
||||
|
||||
// Update color of renderable
|
||||
if (propertyColor != color) {
|
||||
std::string colorScript = fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Color', {});",
|
||||
identifier, ghoul::to_string(color)
|
||||
);
|
||||
|
||||
std::string disableColorMapScript = fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Renderable.ColorMapEnabled', {});",
|
||||
identifier, false
|
||||
);
|
||||
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
colorScript,
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
disableColorMapScript,
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void PointDataMessageHandler::handleColorMapMessage(const std::vector<char>& message) {
|
||||
size_t messageOffset = 0;
|
||||
const std::string identifier = readString(message, messageOffset);
|
||||
const std::vector<float> colorMap = readColorMap(message, messageOffset);
|
||||
|
||||
// Use the renderable identifier as the data key
|
||||
const std::string key = identifier + "-ColorMap";
|
||||
auto module = global::moduleEngine->module<SoftwareIntegrationModule>();
|
||||
module->storeData(key, std::move(colorMap));
|
||||
|
||||
// Get renderable
|
||||
auto r = getRenderable(identifier);
|
||||
if (!r) return;
|
||||
|
||||
std::string script = fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Renderable.LoadNewColorMap', {});",
|
||||
identifier, true
|
||||
);
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
script,
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
|
||||
void PointDataMessageHandler::handleOpacityMessage(const std::vector<char>& message) {
|
||||
size_t messageOffset = 0;
|
||||
const std::string identifier = readString(message, messageOffset);
|
||||
const float opacity = readFloatValue(message, messageOffset);
|
||||
|
||||
// Get renderable
|
||||
auto r = getRenderable(identifier);
|
||||
if (!r) return;
|
||||
|
||||
// Get opacity of renderable
|
||||
properties::Property* opacityProperty = r->property("Opacity");
|
||||
auto propertyAny = opacityProperty->get();
|
||||
float propertyOpacity = std::any_cast<float>(propertyAny);
|
||||
|
||||
// Update opacity of renderable
|
||||
if (propertyOpacity != opacity) {
|
||||
std::string script = fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Opacity', {});",
|
||||
identifier, ghoul::to_string(opacity)
|
||||
);
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
script,
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void PointDataMessageHandler::handlePointSizeMessage(const std::vector<char>& message) {
|
||||
size_t messageOffset = 0;
|
||||
const std::string identifier = readString(message, messageOffset);
|
||||
float size = readFloatValue(message, messageOffset);
|
||||
|
||||
// Get renderable
|
||||
auto r = getRenderable(identifier);
|
||||
if (!r) return;
|
||||
|
||||
// Get size of 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) {
|
||||
// TODO: Add to script when the "send back to glue" stuff is done
|
||||
// "openspace.setPropertyValueSingle('Scene.{}.Renderable.Size', {}, 1);",
|
||||
std::string script = fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Size', {});",
|
||||
identifier, ghoul::to_string(size)
|
||||
);
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
script,
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void PointDataMessageHandler::handleVisiblityMessage(const std::vector<char>& message) {
|
||||
size_t messageOffset = 0;
|
||||
const std::string identifier = readString(message, messageOffset);
|
||||
std::string visibilityMessage;
|
||||
visibilityMessage.push_back(message[messageOffset]);
|
||||
|
||||
// Get renderable
|
||||
auto r = getRenderable(identifier);
|
||||
if (!r) return;
|
||||
|
||||
// Toggle visibility of renderable
|
||||
const std::string visability = visibilityMessage == "T" ? "true" : "false";
|
||||
std::string script = fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Enabled', {});",
|
||||
identifier, visability
|
||||
);
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
script,
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
|
||||
void PointDataMessageHandler::preSyncUpdate() {
|
||||
if (_onceNodeExistsCallbacks.empty()) {
|
||||
catch (const simp::SimpError& err) {
|
||||
LERROR(fmt::format("Error when reading fixed color message: {}", err.message));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the scene graph node has been created. If so, call the corresponding
|
||||
// callback function to set up any subscriptions
|
||||
auto it = _onceNodeExistsCallbacks.begin();
|
||||
while (it != _onceNodeExistsCallbacks.end()) {
|
||||
const std::string& identifier = it->first;
|
||||
const std::function<void()>& callback = it->second;
|
||||
const SceneGraphNode* sgn =
|
||||
global::renderEngine->scene()->sceneGraphNode(identifier);
|
||||
auto callback = [this, identifier, color] {
|
||||
// Get renderable
|
||||
auto r = getRenderable(identifier);
|
||||
|
||||
if (sgn) {
|
||||
callback();
|
||||
it = _onceNodeExistsCallbacks.erase(it);
|
||||
continue;
|
||||
// Get color of renderable
|
||||
properties::Property* colorProperty = r->property("Color");
|
||||
glm::vec4 propertyColor = std::any_cast<glm::vec4>(colorProperty->get());
|
||||
|
||||
// Update color of renderable
|
||||
if (propertyColor != color) {
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Color', {});",
|
||||
identifier, ghoul::to_string(color)
|
||||
),
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapEnabled', {});",
|
||||
identifier, "false"
|
||||
),
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
};
|
||||
addCallback(identifier, callback);
|
||||
}
|
||||
|
||||
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;
|
||||
size_t nColors;
|
||||
std::vector<float> colorMap;
|
||||
try {
|
||||
min = simp::readFloatValue(message, messageOffset);
|
||||
max = simp::readFloatValue(message, messageOffset);
|
||||
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
|
||||
const std::string key = identifier + "-Colormap";
|
||||
auto module = global::moduleEngine->module<SoftwareIntegrationModule>();
|
||||
module->storeData(key, std::move(colorMap));
|
||||
|
||||
auto callback = [this, identifier, min, max] {
|
||||
// Get renderable
|
||||
auto r = getRenderable(identifier);
|
||||
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapEnabled', {});",
|
||||
identifier, "true"
|
||||
),
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
|
||||
properties::Property* colormapMinProperty = r->property("ColormapMin");
|
||||
float colormapMin = std::any_cast<float>(colormapMinProperty->get());
|
||||
if (min != colormapMin) {
|
||||
openspace::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");
|
||||
float colormapMax = std::any_cast<float>(colormapMaxProperty->get());
|
||||
if (max != colormapMax) {
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapMax', {});",
|
||||
identifier, ghoul::to_string(max)
|
||||
),
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
};
|
||||
addCallback(identifier, callback);
|
||||
}
|
||||
|
||||
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>();
|
||||
// Use the renderable identifier as the data key
|
||||
std::string key = identifier;
|
||||
|
||||
if (usedFor == "ColormapAttributeData") {
|
||||
key += "-CmapAttributeData";
|
||||
}
|
||||
else {
|
||||
LERROR(fmt::format(
|
||||
"The received attribute data had the \"usedFor\" value {}, which is unrecognized.",
|
||||
usedFor
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
module->storeData(key, std::move(attributeData));
|
||||
|
||||
auto callback = [this, identifier, usedFor] {
|
||||
if (usedFor == "ColormapAttributeData") {
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapEnabled', {});",
|
||||
identifier, "true"
|
||||
),
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
};
|
||||
addCallback(identifier, callback);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
auto callback = [this, identifier, opacity] {
|
||||
// Get renderable
|
||||
auto r = getRenderable(identifier);
|
||||
|
||||
// Get opacity from renderable
|
||||
properties::Property* opacityProperty = r->property("Opacity");
|
||||
auto propertyAny = opacityProperty->get();
|
||||
float propertyOpacity = std::any_cast<float>(propertyAny);
|
||||
|
||||
// Update opacity of renderable
|
||||
if (propertyOpacity != opacity) {
|
||||
std::string script = fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Opacity', {});",
|
||||
identifier, ghoul::to_string(opacity)
|
||||
);
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
script,
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
};
|
||||
addCallback(identifier, callback);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
auto callback = [this, identifier, size] {
|
||||
// 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) {
|
||||
// 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);",
|
||||
std::string script = fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Size', {});",
|
||||
identifier, ghoul::to_string(size)
|
||||
);
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
script,
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
};
|
||||
addCallback(identifier, callback);
|
||||
}
|
||||
|
||||
void PointDataMessageHandler::handleVisiblityMessage(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;
|
||||
}
|
||||
|
||||
auto callback = [this, identifier, visibilityMessage] {
|
||||
// Toggle visibility from renderable
|
||||
const std::string visability = visibilityMessage == "T" ? "true" : "false";
|
||||
std::string script = fmt::format(
|
||||
"openspace.setPropertyValueSingle('Scene.{}.Renderable.Enabled', {});",
|
||||
identifier, visability
|
||||
);
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
script,
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
};
|
||||
addCallback(identifier, callback);
|
||||
}
|
||||
|
||||
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
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Anchor', 'Sun')"
|
||||
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Aim', '')",
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
}
|
||||
openspace::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 callbacksIt = callbackList.begin();
|
||||
while (callbacksIt != callbackList.end()) {
|
||||
auto& callback = *callbacksIt;
|
||||
try {
|
||||
callback();
|
||||
callbacksIt = callbackList.erase(callbacksIt);
|
||||
}
|
||||
catch (std::exception&) {
|
||||
++callbacksIt;
|
||||
}
|
||||
}
|
||||
|
||||
callbackMapIt = _onceNodeExistsCallbacks.erase(callbackMapIt);
|
||||
_onceNodeExistsCallbacksRetries = 0;
|
||||
}
|
||||
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;
|
||||
}
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
const Renderable* PointDataMessageHandler::getRenderable(const std::string& identifier) const {
|
||||
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);
|
||||
if (!r) {
|
||||
LERROR(fmt::format("No renderable with identifier '{}' was found", identifier));
|
||||
return nullptr;
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
openspace::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
|
||||
);
|
||||
auto subscriptionCallback = [this, identifier, connection] {
|
||||
subscribeToRenderableUpdates(identifier, connection);
|
||||
};
|
||||
addCallback(identifier, subscriptionCallback);
|
||||
}
|
||||
else {
|
||||
subscribeToRenderableUpdates(identifier, connection);
|
||||
}
|
||||
|
||||
auto reanchorCallback = [identifier] {
|
||||
openspace::global::scriptEngine->queueScript(
|
||||
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.RetargetAnchor', nil)"
|
||||
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Anchor', '" + identifier + "')"
|
||||
"openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Aim', '')",
|
||||
scripting::ScriptEngine::RemoteScripting::Yes
|
||||
);
|
||||
};
|
||||
return r;
|
||||
addCallback(identifier, reanchorCallback);
|
||||
}
|
||||
|
||||
void PointDataMessageHandler::subscribeToRenderableUpdates(
|
||||
const std::string& identifier,
|
||||
SoftwareConnection& connection
|
||||
std::shared_ptr<SoftwareConnection> connection
|
||||
) {
|
||||
// Get renderable
|
||||
auto aRenderable = getRenderable(identifier);
|
||||
if (!aRenderable) return;
|
||||
|
||||
if (!connection.isConnected()) {
|
||||
if (!connection->isConnected()) {
|
||||
LERROR(fmt::format(
|
||||
"Could not subscribe to updates for renderable '{}' due to lost connection",
|
||||
identifier
|
||||
@@ -320,206 +511,67 @@ void PointDataMessageHandler::subscribeToRenderableUpdates(
|
||||
properties::Property* colorProperty = aRenderable->property("Color");
|
||||
properties::Property* opacityProperty = aRenderable->property("Opacity");
|
||||
properties::Property* sizeProperty = aRenderable->property("Size");
|
||||
properties::Property* visibilityProperty = aRenderable->property("ToggleVisibility");
|
||||
properties::Property* visibilityProperty = aRenderable->property("Enabled");
|
||||
|
||||
// Update color of renderable
|
||||
auto updateColor = [colorProperty, identifier, &connection]() {
|
||||
const std::string value = colorProperty->getStringValue();
|
||||
const std::string message = simp::formatUpdateMessage(simp::MessageType::Color, identifier, value);
|
||||
connection.sendMessage(message);
|
||||
auto updateColor = [colorProperty, identifier, connection]() {
|
||||
glm::vec4 color = std::any_cast<glm::vec4>(colorProperty->get());
|
||||
|
||||
const std::string message = simp::formatColorMessage(identifier, color);
|
||||
connection->sendMessage(message);
|
||||
};
|
||||
if (colorProperty) {
|
||||
colorProperty->onChange(updateColor);
|
||||
connection->addPropertySubscription("Color", identifier, updateColor);
|
||||
}
|
||||
|
||||
// Update opacity of renderable
|
||||
auto updateOpacity = [opacityProperty, identifier, &connection]() {
|
||||
const std::string value = opacityProperty->getStringValue();
|
||||
const std::string message = simp::formatUpdateMessage(simp::MessageType::Opacity, identifier, value);
|
||||
connection.sendMessage(message);
|
||||
auto updateOpacity = [opacityProperty, identifier, connection]() {
|
||||
float value = std::any_cast<float>(opacityProperty->get());
|
||||
std::string hex_value = simp::floatToHex(value);
|
||||
|
||||
const std::string message = simp::formatUpdateMessage(simp::MessageType::Opacity, identifier, hex_value);
|
||||
connection->sendMessage(message);
|
||||
};
|
||||
if (opacityProperty) {
|
||||
opacityProperty->onChange(updateOpacity);
|
||||
connection->addPropertySubscription("Opacity", identifier, updateOpacity);
|
||||
}
|
||||
|
||||
// Update size of renderable
|
||||
auto updateSize = [sizeProperty, identifier, &connection]() {
|
||||
const std::string value = sizeProperty->getStringValue();
|
||||
const std::string message = simp::formatUpdateMessage(simp::MessageType::Size, identifier, value);
|
||||
connection.sendMessage(message);
|
||||
auto updateSize = [sizeProperty, identifier, connection]() {
|
||||
float value = std::any_cast<float>(sizeProperty->get());
|
||||
std::string hex_value = simp::floatToHex(value);
|
||||
|
||||
const std::string message = simp::formatUpdateMessage(simp::MessageType::Size, identifier, hex_value);
|
||||
connection->sendMessage(message);
|
||||
};
|
||||
if (sizeProperty) {
|
||||
sizeProperty->onChange(updateSize);
|
||||
connection->addPropertySubscription("Size", identifier, updateSize);
|
||||
}
|
||||
|
||||
// Toggle visibility of renderable
|
||||
auto toggleVisibility = [visibilityProperty, identifier, &connection]() {
|
||||
bool isVisible = visibilityProperty->getStringValue() == "true";
|
||||
auto toggleVisibility = [visibilityProperty, identifier, connection]() {
|
||||
bool isVisible = std::any_cast<bool>(visibilityProperty->get());
|
||||
std::string_view visibilityFlag = isVisible ? "T" : "F";
|
||||
|
||||
const std::string message = simp::formatUpdateMessage(simp::MessageType::Visibility, identifier, visibilityFlag);
|
||||
connection.sendMessage(message);
|
||||
connection->sendMessage(message);
|
||||
};
|
||||
if (visibilityProperty) {
|
||||
visibilityProperty->onChange(toggleVisibility);
|
||||
connection->addPropertySubscription("Enabled", identifier, toggleVisibility);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int PointDataMessageHandler::readIntValue(const std::vector<char>& message, size_t& offset) {
|
||||
std::string string_value;
|
||||
int value;
|
||||
bool isHex = false;
|
||||
|
||||
while (!simp::isEndOfCurrentValue(message, offset)) {
|
||||
char c = message[offset];
|
||||
if (c == 'x' || c == 'X') isHex = true;
|
||||
string_value.push_back(c);
|
||||
offset++;
|
||||
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 callbacks{ std::move(newCallback) };
|
||||
_onceNodeExistsCallbacks.emplace(std::move(identifier), std::move(callbacks) );
|
||||
}
|
||||
|
||||
try {
|
||||
value = std::stoi(string_value, nullptr, isHex ? 16 : 10);
|
||||
else {
|
||||
it->second.push_back(std::move(newCallback));
|
||||
}
|
||||
catch(std::exception &err) {
|
||||
throw simp::SimpError(
|
||||
simp::ErrorCode::Generic,
|
||||
fmt::format("Error when trying to parse the integer {}: {}", string_value, err.what())
|
||||
);
|
||||
}
|
||||
|
||||
++offset;
|
||||
return value;
|
||||
}
|
||||
|
||||
float PointDataMessageHandler::readFloatValue(const std::vector<char>& message, size_t& offset) {
|
||||
std::string string_value;
|
||||
float value;
|
||||
|
||||
while (!simp::isEndOfCurrentValue(message, offset)) {
|
||||
string_value.push_back(message[offset]);
|
||||
offset++;
|
||||
}
|
||||
|
||||
try {
|
||||
// long l;
|
||||
|
||||
// l = std::strtol(string_value.data(), (char**)NULL, 16);
|
||||
// value = (float)l;
|
||||
value = std::stof(string_value);
|
||||
|
||||
// if ((*s == '0') && ((*s == 'X') || (*s == 'x'))) {
|
||||
// unsigned long ul = strtoul(d, NULL, 16);
|
||||
// return (float) ul;
|
||||
// }
|
||||
// double d = atof(s, NULL);
|
||||
// return (float) d;
|
||||
}
|
||||
catch(std::exception &err) {
|
||||
throw simp::SimpError(
|
||||
simp::ErrorCode::Generic,
|
||||
fmt::format("Error when trying to parse the float {}: {}", string_value, err.what())
|
||||
);
|
||||
}
|
||||
|
||||
++offset;
|
||||
return value;
|
||||
}
|
||||
|
||||
std::vector<float> PointDataMessageHandler::readColorMap(const std::vector<char>& message,
|
||||
size_t& offset) {
|
||||
std::vector<float> colorMap;
|
||||
while (message[offset] != simp::SEP) {
|
||||
glm::vec4 color = readSingleColor(message, offset);
|
||||
|
||||
// ColorMap should be stored in a sequential vector
|
||||
// of floats for syncing between nodes and when
|
||||
// loaded to as a texture in the shader.
|
||||
colorMap.push_back(color[0]);
|
||||
colorMap.push_back(color[1]);
|
||||
colorMap.push_back(color[2]);
|
||||
colorMap.push_back(color[3]);
|
||||
}
|
||||
|
||||
offset++;
|
||||
return colorMap;
|
||||
}
|
||||
|
||||
glm::vec4 PointDataMessageHandler::readSingleColor(const std::vector<char>& message, size_t& offset) {
|
||||
if (message[offset] != '[') {
|
||||
throw simp::SimpError(
|
||||
simp::ErrorCode::Generic,
|
||||
fmt::format("Expected to read '[', got {} in 'readColor'", message[offset])
|
||||
);
|
||||
}
|
||||
++offset;
|
||||
|
||||
float r = readFloatValue(message, offset);
|
||||
float g = readFloatValue(message, offset);
|
||||
float b = readFloatValue(message, offset);
|
||||
float a = readFloatValue(message, offset);
|
||||
|
||||
if (message[offset] != ']') {
|
||||
throw simp::SimpError(
|
||||
simp::ErrorCode::Generic,
|
||||
fmt::format("Expected to read ']', got {} in 'readColor'", message[offset])
|
||||
);
|
||||
}
|
||||
++offset;
|
||||
|
||||
return { r, g, b, a };
|
||||
}
|
||||
|
||||
glm::vec4 PointDataMessageHandler::readColor(const std::vector<char>& message, size_t& offset) {
|
||||
glm::vec4 color = readSingleColor(message, offset);
|
||||
|
||||
++offset;
|
||||
return color;
|
||||
}
|
||||
|
||||
std::string PointDataMessageHandler::readString(const std::vector<char>& message, size_t& offset) {
|
||||
std::string value;
|
||||
while (!simp::isEndOfCurrentValue(message, offset)) {
|
||||
value.push_back(message[offset]);
|
||||
++offset;
|
||||
}
|
||||
|
||||
++offset;
|
||||
return value;
|
||||
}
|
||||
|
||||
std::vector<float> PointDataMessageHandler::readPointData(
|
||||
const std::vector<char>& message,
|
||||
size_t& offset,
|
||||
int nPoints,
|
||||
int dimensionality
|
||||
) {
|
||||
std::vector<float> result;
|
||||
result.reserve(nPoints * dimensionality);
|
||||
|
||||
while (!simp::isEndOfCurrentValue(message, offset)) {
|
||||
if (message[offset] != '[') {
|
||||
throw simp::SimpError(
|
||||
simp::ErrorCode::Generic,
|
||||
fmt::format("Expected to read '[', got {} in 'readPointData'", message[offset])
|
||||
);
|
||||
}
|
||||
++offset;
|
||||
|
||||
for (int i = 0; i < dimensionality; ++i) {
|
||||
result.push_back(readFloatValue(message, offset));
|
||||
}
|
||||
|
||||
if (message[offset] != ']') {
|
||||
throw simp::SimpError(
|
||||
simp::ErrorCode::Generic,
|
||||
fmt::format("Expected to read ']', got {} in 'readPointData'", message[offset])
|
||||
);
|
||||
}
|
||||
++offset;
|
||||
}
|
||||
|
||||
++offset;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#ifndef __OPENSPACE_MODULE_SOFTWAREINTEGRATION___POINTDATAMESSAGEHANDLER___H__
|
||||
#define __OPENSPACE_MODULE_SOFTWAREINTEGRATION___POINTDATAMESSAGEHANDLER___H__
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include <openspace/properties/propertyowner.h>
|
||||
|
||||
#include <modules/softwareintegration/network/softwareconnection.h>
|
||||
@@ -34,34 +36,36 @@ namespace openspace {
|
||||
class Renderable;
|
||||
|
||||
class PointDataMessageHandler {
|
||||
public:
|
||||
void handlePointDataMessage(const std::vector<char>& message,
|
||||
SoftwareConnection& connection, std::list<std::string>& sceneGraphNodes);
|
||||
void handleColorMessage(const std::vector<char>& message);
|
||||
void handleColorMapMessage(const std::vector<char>& message);
|
||||
void handleOpacityMessage(const std::vector<char>& message);
|
||||
void handlePointSizeMessage(const std::vector<char>& message);
|
||||
void handleVisiblityMessage(const std::vector<char>& message);
|
||||
using Callback = std::function<void()>;
|
||||
using CallbackList = std::vector<std::function<void()>>;
|
||||
using CallbackMap = std::unordered_map<std::string, CallbackList>;
|
||||
|
||||
void preSyncUpdate();
|
||||
public:
|
||||
void handlePointDataMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection);
|
||||
void handleFixedColorMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection);
|
||||
void handleColormapMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection);
|
||||
void handleAttributeDataMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection);
|
||||
void handleOpacityMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection);
|
||||
void handleFixedPointSizeMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection);
|
||||
void handleVisiblityMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection);
|
||||
void handleRemoveSGNMessage(const std::vector<char>& message, std::shared_ptr<SoftwareConnection> connection);
|
||||
|
||||
void postSync();
|
||||
|
||||
private:
|
||||
const Renderable* getRenderable(const std::string& identifier) const;
|
||||
|
||||
void subscribeToRenderableUpdates(const std::string& identifier,
|
||||
SoftwareConnection& connection);
|
||||
|
||||
float readFloatValue(const std::vector<char>& message, size_t& offset);
|
||||
int readIntValue(const std::vector<char>& message, size_t& offset);
|
||||
glm::vec4 readSingleColor(const std::vector<char>& message, size_t& offset);
|
||||
glm::vec4 readColor(const std::vector<char>& message, size_t& offset);
|
||||
std::string readString(const std::vector<char>& message, size_t& offset);
|
||||
std::vector<float> readPointData(
|
||||
const std::vector<char>& message, size_t& offset, int nPoints, int dimensionality
|
||||
const Renderable* getRenderable(const std::string& identifier);
|
||||
void checkRenderable(
|
||||
const std::vector<char>& message, size_t& messageOffset,
|
||||
std::shared_ptr<SoftwareConnection> connection, std::string& identifier
|
||||
);
|
||||
std::vector<float> readColorMap(const std::vector<char>& message, size_t& offset);
|
||||
|
||||
std::unordered_map<std::string, std::function<void()>> _onceNodeExistsCallbacks;
|
||||
void subscribeToRenderableUpdates(const std::string& identifier, std::shared_ptr<SoftwareConnection> connection);
|
||||
|
||||
void addCallback(const std::string& identifier, const Callback& newCallback);
|
||||
|
||||
CallbackMap _onceNodeExistsCallbacks;
|
||||
std::mutex _onceNodeExistsCallbacksMutex;
|
||||
size_t _onceNodeExistsCallbacksRetries{0};
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -36,17 +36,17 @@
|
||||
#include <ghoul/opengl/openglstatecache.h>
|
||||
#include <ghoul/opengl/programobject.h>
|
||||
#include <ghoul/opengl/texture.h>
|
||||
#include <ghoul/opengl/textureunit.h>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
|
||||
namespace {
|
||||
constexpr const char* _loggerCat = "PointsCloud";
|
||||
|
||||
constexpr const std::array<const char*, 8> UniformNames = {
|
||||
"color", "opacity", "size", "modelMatrix",
|
||||
"cameraUp", "cameraViewProjectionMatrix", "eyePosition", "sizeOption"
|
||||
constexpr const std::array<const char*, 12> UniformNames = {
|
||||
"color", "opacity", "size", "modelMatrix", "cameraUp", "cameraViewProjectionMatrix",
|
||||
"eyePosition", "sizeOption", "colormapTexture", "colormapMin", "colormapMax",
|
||||
"colormapEnabled"
|
||||
};
|
||||
//"colorMapTexture",
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo ColorInfo = {
|
||||
"Color",
|
||||
@@ -60,28 +60,16 @@ namespace {
|
||||
"The size of the points."
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo ToggleVisibilityInfo = {
|
||||
"ToggleVisibility",
|
||||
"Toggle Visibility",
|
||||
"Enables/Disables the drawing of points."
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo DataInfo = {
|
||||
"Data",
|
||||
"Data",
|
||||
"Data to use for the positions of the points, given in Parsec."
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo DataStorageKeyInfo = {
|
||||
"DataStorageKey",
|
||||
"Data Storage Key",
|
||||
"Key used to access a dataset in the module's centralized storage, which is synced to all nodes."
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo IdentifierInfo = {
|
||||
"Identifier",
|
||||
"Identifier",
|
||||
"Identifier used as part of key to access data in syncable central storage."
|
||||
"Identifier used as part of key to access data in centralized central storage."
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo SizeOptionInfo = {
|
||||
@@ -90,44 +78,47 @@ namespace {
|
||||
"This value determines how the size of the data points are rendered."
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo ColorMapEnabledInfo = {
|
||||
"ColorMapEnabled",
|
||||
"ColorMap Enabled",
|
||||
constexpr openspace::properties::Property::PropertyInfo ColormapEnabledInfo = {
|
||||
"ColormapEnabled",
|
||||
"Colormap Enabled",
|
||||
"Boolean to determine whether to use colormap or not."
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo LoadNewColorMapInfo = {
|
||||
"LoadNewColorMap",
|
||||
"Load New ColorMap",
|
||||
"Boolean to determine whether to load new colormap or not."
|
||||
constexpr openspace::properties::Property::PropertyInfo ColormapMinInfo = {
|
||||
"ColormapMin",
|
||||
"Colormap min",
|
||||
"Minimum value to sample from color map."
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo ColormapMaxInfo = {
|
||||
"ColormapMax",
|
||||
"Colormap max",
|
||||
"Maximum value to sample from color map."
|
||||
};
|
||||
|
||||
struct [[codegen::Dictionary(RenderablePointsCloud)]] Parameters {
|
||||
// [[codegen::verbatim(ColorInfo.description)]]
|
||||
std::optional<glm::vec3> color;
|
||||
std::optional<glm::vec4> color;
|
||||
|
||||
// [[codegen::verbatim(SizeInfo.description)]]
|
||||
std::optional<float> size;
|
||||
|
||||
// [[codegen::verbatim(ToggleVisibilityInfo.description)]]
|
||||
std::optional<bool> toggleVisiblity;
|
||||
|
||||
// [[codegen::verbatim(DataInfo.description)]]
|
||||
std::optional<std::vector<glm::vec3>> data;
|
||||
|
||||
// [[codegen::verbatim(DataStorageKeyInfo.description)]]
|
||||
std::optional<std::string> dataStorageKey;
|
||||
|
||||
// [[codegen::verbatim(IdentifierInfo.description)]]
|
||||
std::optional<std::string> identifier;
|
||||
|
||||
// [[codegen::verbatim(ColorMapEnabledInfo.description)]]
|
||||
std::optional<std::string> colorMapEnabled;
|
||||
// [[codegen::verbatim(ColormapMinInfo.description)]]
|
||||
std::optional<float> colormapMin;
|
||||
|
||||
// [[codegen::verbatim(LoadNewColorMapInfo.description)]]
|
||||
std::optional<std::string> loadNewColorMap;
|
||||
// [[codegen::verbatim(ColormapMaxInfo.description)]]
|
||||
std::optional<float> colormapMax;
|
||||
|
||||
enum class SizeOption {
|
||||
// [[codegen::verbatim(ColormapEnabledInfo.description)]]
|
||||
std::optional<bool> colormapEnabled;
|
||||
|
||||
enum class SizeOption : uint32_t {
|
||||
Uniform,
|
||||
NonUniform
|
||||
};
|
||||
@@ -145,12 +136,12 @@ documentation::Documentation RenderablePointsCloud::Documentation() {
|
||||
|
||||
RenderablePointsCloud::RenderablePointsCloud(const ghoul::Dictionary& dictionary)
|
||||
: Renderable(dictionary)
|
||||
, _color(ColorInfo, glm::vec3(0.5f), glm::vec3(0.f), glm::vec3(1.f))
|
||||
, _size(SizeInfo, 1.f, 0.f, 150.f)
|
||||
, _isVisible(ToggleVisibilityInfo, true)
|
||||
, _color(ColorInfo, glm::vec4(glm::vec3(0.5f), 1.f), glm::vec4(0.f), glm::vec4(1.f), glm::vec4(.001f))
|
||||
, _size(SizeInfo, 1.f, 0.f, 30.f, .001f)
|
||||
, _sizeOption(SizeOptionInfo, properties::OptionProperty::DisplayType::Dropdown)
|
||||
, _colorMapEnabled(ColorMapEnabledInfo, false)
|
||||
, _loadNewColorMap(LoadNewColorMapInfo, false)
|
||||
, _colormapEnabled(ColormapEnabledInfo, false)
|
||||
, _colormapMin(ColormapMinInfo)
|
||||
, _colormapMax(ColormapMaxInfo)
|
||||
{
|
||||
const Parameters p = codegen::bake<Parameters>(dictionary);
|
||||
|
||||
@@ -158,27 +149,37 @@ RenderablePointsCloud::RenderablePointsCloud(const ghoul::Dictionary& dictionary
|
||||
_color.setViewOption(properties::Property::ViewOptions::Color);
|
||||
addProperty(_color);
|
||||
|
||||
// Check if a key to data stored in the module's centralized memory was included
|
||||
_nValuesPerPoint = 3;
|
||||
_dataStorageKey = p.dataStorageKey.value();
|
||||
|
||||
_identifier = p.identifier.value();
|
||||
|
||||
_size = p.size.value_or(_size);
|
||||
addProperty(_size);
|
||||
|
||||
_isVisible = p.toggleVisiblity.value_or(_isVisible);
|
||||
addProperty(_isVisible);
|
||||
|
||||
addProperty(_opacity);
|
||||
|
||||
addProperty(_colorMapEnabled);
|
||||
auto colormapMinMaxChecker = [this] {
|
||||
if (_colormapMin.value() > _colormapMax.value()) {
|
||||
auto temp = _colormapMin.value();
|
||||
_colormapMin = _colormapMax.value();
|
||||
_colormapMax = temp;
|
||||
}
|
||||
};
|
||||
|
||||
addProperty(_loadNewColorMap);
|
||||
_colormapMin = p.colormapMin.value_or(_colormapMin);
|
||||
_colormapMin.setVisibility(properties::Property::Visibility::Hidden);
|
||||
_colormapMin.onChange(colormapMinMaxChecker);
|
||||
addProperty(_colormapMin);
|
||||
|
||||
_colormapMax = p.colormapMax.value_or(_colormapMax);
|
||||
_colormapMax.setVisibility(properties::Property::Visibility::Hidden);
|
||||
_colormapMax.onChange(colormapMinMaxChecker);
|
||||
addProperty(_colormapMax);
|
||||
|
||||
_colormapEnabled = p.colormapEnabled.value_or(_colormapEnabled);
|
||||
addProperty(_colormapEnabled);
|
||||
|
||||
_sizeOption.addOptions({
|
||||
{ SizeOption::Uniform, "Uniform" },
|
||||
{ SizeOption::NonUniform, "NonUniform" }
|
||||
{ SizeOption::NonUniform, "Non Uniform" }
|
||||
});
|
||||
if (p.sizeOption.has_value()) {
|
||||
switch (*p.sizeOption) {
|
||||
@@ -190,16 +191,11 @@ RenderablePointsCloud::RenderablePointsCloud(const ghoul::Dictionary& dictionary
|
||||
break;
|
||||
}
|
||||
}
|
||||
_sizeOption.onChange([&] { _isDirty = true; });
|
||||
addProperty(_sizeOption);
|
||||
}
|
||||
|
||||
bool RenderablePointsCloud::isReady() const {
|
||||
return _shaderProgram && (!_fullData.empty());
|
||||
}
|
||||
|
||||
void RenderablePointsCloud::initialize() {
|
||||
loadData();
|
||||
return _shaderProgram && _identifier.has_value() && _identifier.value() != "";
|
||||
}
|
||||
|
||||
void RenderablePointsCloud::initializeGL() {
|
||||
@@ -214,11 +210,12 @@ void RenderablePointsCloud::initializeGL() {
|
||||
}
|
||||
|
||||
void RenderablePointsCloud::deinitializeGL() {
|
||||
glDeleteVertexArrays(1, &_vertexArrayObjectID);
|
||||
_vertexArrayObjectID = 0;
|
||||
glDeleteBuffers(1, &_vbo);
|
||||
_vbo = 0;
|
||||
glDeleteVertexArrays(1, &_vao);
|
||||
_vao = 0;
|
||||
|
||||
glDeleteBuffers(1, &_vertexBufferObjectID);
|
||||
_vertexBufferObjectID = 0;
|
||||
_colormapTexture.reset();
|
||||
|
||||
if (_shaderProgram) {
|
||||
global::renderEngine->removeRenderProgram(_shaderProgram.get());
|
||||
@@ -227,13 +224,8 @@ void RenderablePointsCloud::deinitializeGL() {
|
||||
}
|
||||
|
||||
void RenderablePointsCloud::render(const RenderData& data, RendererTasks&) {
|
||||
if (_fullData.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_isVisible) {
|
||||
return;
|
||||
}
|
||||
auto pointDataSlice = getDataSlice(DataSliceKey::Points);
|
||||
if (pointDataSlice->empty()) return;
|
||||
|
||||
_shaderProgram->activate();
|
||||
|
||||
@@ -259,17 +251,28 @@ void RenderablePointsCloud::render(const RenderData& data, RendererTasks&) {
|
||||
cameraViewProjectionMatrix
|
||||
);
|
||||
|
||||
if (_loadNewColorMap) loadColorMap();
|
||||
|
||||
if (_colorMapEnabled && _colorMapTexture) {
|
||||
// TODO: Set _colorMapTexture in shader. A trasnfer function similar to
|
||||
ghoul::opengl::TextureUnit colorUnit;
|
||||
if (_colormapTexture) {
|
||||
// _colormapAttributeData
|
||||
// TODO: Set _colormapTextre in shader. A trasnfer function similar to
|
||||
// 'bv2rgb' in C:\OpenSpace\SoftwareIntegration\modules\space\shaders\star_fs.glsl
|
||||
// should probably be used.
|
||||
|
||||
colorUnit.activate();
|
||||
_colormapTexture->bind();
|
||||
_shaderProgram->setUniform(_uniformCache.colormapTexture, colorUnit);
|
||||
}
|
||||
else {
|
||||
_shaderProgram->setUniform(_uniformCache.color, _color);
|
||||
// We need to set the uniform to something, or the shader doesn't work
|
||||
_shaderProgram->setUniform(_uniformCache.colormapTexture, colorUnit);
|
||||
}
|
||||
|
||||
_shaderProgram->setUniform(_uniformCache.colormapMin, _colormapMin);
|
||||
_shaderProgram->setUniform(_uniformCache.colormapMax, _colormapMax);
|
||||
_shaderProgram->setUniform(_uniformCache.colormapEnabled, _colormapEnabled);
|
||||
|
||||
_shaderProgram->setUniform(_uniformCache.color, _color);
|
||||
|
||||
_shaderProgram->setUniform(_uniformCache.opacity, _opacity);
|
||||
_shaderProgram->setUniform(_uniformCache.size, _size);
|
||||
_shaderProgram->setUniform(_uniformCache.sizeOption, _sizeOption);
|
||||
@@ -279,8 +282,8 @@ void RenderablePointsCloud::render(const RenderData& data, RendererTasks&) {
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glDepthMask(false);
|
||||
|
||||
glBindVertexArray(_vertexArrayObjectID);
|
||||
const GLsizei nPoints = static_cast<GLsizei>(_fullData.size() / _nValuesPerPoint);
|
||||
glBindVertexArray(_vao);
|
||||
const GLsizei nPoints = static_cast<GLsizei>(pointDataSlice->size() / 3);
|
||||
glDrawArrays(GL_POINTS, 0, nPoints);
|
||||
|
||||
glBindVertexArray(0);
|
||||
@@ -297,130 +300,228 @@ void RenderablePointsCloud::update(const UpdateData&) {
|
||||
ghoul::opengl::updateUniformLocations(*_shaderProgram, _uniformCache, UniformNames);
|
||||
}
|
||||
|
||||
if (!_isDirty) {
|
||||
bool updatedDataSlices = checkDataStorage();
|
||||
|
||||
if (updatedDataSlices) {
|
||||
if (_vao == 0) {
|
||||
glGenVertexArrays(1, &_vao);
|
||||
LDEBUG(fmt::format("Generating Vertex Array id '{}'", _vao));
|
||||
}
|
||||
if (_vbo == 0) {
|
||||
glGenBuffers(1, &_vbo);
|
||||
LDEBUG(fmt::format("Generating Vertex Buffer Object id '{}'", _vbo));
|
||||
}
|
||||
|
||||
glBindVertexArray(_vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
|
||||
|
||||
auto pointDataSlice = getDataSlice(DataSliceKey::Points);
|
||||
auto colormapAttrDataSlice = getDataSlice(DataSliceKey::ColormapAttributes);
|
||||
|
||||
if (pointDataSlice->empty()) return;
|
||||
|
||||
// ========================== Create resulting data slice and buffer it ==========================
|
||||
std::vector<float> bufferData;
|
||||
bufferData.reserve(pointDataSlice->size() / 3);
|
||||
|
||||
for(size_t i = 0, j = 0; j < pointDataSlice->size(); ++i, j += 3) {
|
||||
bufferData.push_back(pointDataSlice->at(j));
|
||||
bufferData.push_back(pointDataSlice->at(j + 1));
|
||||
bufferData.push_back(pointDataSlice->at(j + 2));
|
||||
|
||||
if (colormapAttrDataSlice->size() > i) {
|
||||
bufferData.push_back(colormapAttrDataSlice->at(i));
|
||||
}
|
||||
else {
|
||||
bufferData.push_back(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
glBufferData(
|
||||
GL_ARRAY_BUFFER,
|
||||
bufferData.size() * sizeof(GLfloat),
|
||||
bufferData.data(),
|
||||
GL_STATIC_DRAW
|
||||
);
|
||||
// ==============================================================================================
|
||||
|
||||
// ========================================= VAO stuff =========================================
|
||||
GLsizei stride = static_cast<GLsizei>(sizeof(GLfloat) * 4);
|
||||
GLint positionAttribute = _shaderProgram->attributeLocation("in_position");
|
||||
glEnableVertexAttribArray(positionAttribute);
|
||||
glVertexAttribPointer(
|
||||
positionAttribute,
|
||||
3,
|
||||
GL_FLOAT,
|
||||
GL_FALSE,
|
||||
stride,
|
||||
nullptr
|
||||
);
|
||||
|
||||
if (_hasLoadedColormapAttributeData) {
|
||||
GLint colormapScalarsAttribute = _shaderProgram->attributeLocation("in_colormapAttributeScalar");
|
||||
glEnableVertexAttribArray(colormapScalarsAttribute);
|
||||
glVertexAttribPointer(
|
||||
colormapScalarsAttribute,
|
||||
1,
|
||||
GL_FLOAT,
|
||||
GL_FALSE,
|
||||
stride,
|
||||
reinterpret_cast<void*>(sizeof(GLfloat) * 3)
|
||||
);
|
||||
}
|
||||
// ==============================================================================================
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
// This can happen if the user checks the "_colormapEnabled" checkbox in the GUI
|
||||
auto colormapAttributeData = getDataSlice(DataSliceKey::ColormapAttributes);
|
||||
if (_colormapEnabled.value() && (!_colormapTexture || colormapAttributeData->empty())) {
|
||||
if (!_colormapTexture) {
|
||||
LINFO("Color map not loaded. Has it been sent from external software?");
|
||||
}
|
||||
else {
|
||||
LINFO("Color map attribute data not loaded. Has it been sent from external software?");
|
||||
}
|
||||
_colormapEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderablePointsCloud::checkDataStorage() {
|
||||
if (!_identifier.has_value()) {
|
||||
LERROR("No identifier found in renderable");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool updatedDataSlices = false;
|
||||
auto softwareIntegrationModule = global::moduleEngine->module<SoftwareIntegrationModule>();
|
||||
std::string dataPointsKey = _identifier.value() + "-DataPoints";
|
||||
std::string colorMapKey = _identifier.value() + "-Colormap";
|
||||
std::string colorMapScalarsKey = _identifier.value() + "-CmapAttributeData";
|
||||
|
||||
if (softwareIntegrationModule->isDataDirty(dataPointsKey)) {
|
||||
loadData(dataPointsKey, softwareIntegrationModule);
|
||||
updatedDataSlices = true;
|
||||
}
|
||||
|
||||
if (softwareIntegrationModule->isDataDirty(colorMapKey)) {
|
||||
loadColormap(colorMapKey, softwareIntegrationModule);
|
||||
}
|
||||
|
||||
if (softwareIntegrationModule->isDataDirty(colorMapScalarsKey)) {
|
||||
loadCmapAttributeData(colorMapScalarsKey, softwareIntegrationModule);
|
||||
updatedDataSlices = true;
|
||||
}
|
||||
|
||||
return updatedDataSlices;
|
||||
}
|
||||
|
||||
void RenderablePointsCloud::loadData(
|
||||
const std::string& storageKey, SoftwareIntegrationModule* softwareIntegrationModule
|
||||
) {
|
||||
// Fetch data from module's centralized storage
|
||||
auto fullPointData = softwareIntegrationModule->fetchData(storageKey);
|
||||
|
||||
if (fullPointData.empty()) {
|
||||
LWARNING("There was an issue trying to fetch the point data from the centralized storage.");
|
||||
return;
|
||||
}
|
||||
|
||||
LDEBUG("Regenerating data");
|
||||
|
||||
// @TODO (emmbr26 2021-02-11) This 'createDataSlice'step doesn't really seem
|
||||
// necessary, but could rather be combined with the loadData() step?
|
||||
createDataSlice();
|
||||
|
||||
int size = static_cast<int>(_slicedData.size());
|
||||
|
||||
if (_vertexArrayObjectID == 0) {
|
||||
glGenVertexArrays(1, &_vertexArrayObjectID);
|
||||
LDEBUG(fmt::format("Generating Vertex Array id '{}'", _vertexArrayObjectID));
|
||||
}
|
||||
if (_vertexBufferObjectID == 0) {
|
||||
glGenBuffers(1, &_vertexBufferObjectID);
|
||||
LDEBUG(fmt::format("Generating Vertex Buffer Object id '{}'", _vertexBufferObjectID));
|
||||
}
|
||||
|
||||
glBindVertexArray(_vertexArrayObjectID);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferObjectID);
|
||||
glBufferData(
|
||||
GL_ARRAY_BUFFER,
|
||||
size * sizeof(float),
|
||||
_slicedData.data(),
|
||||
GL_STATIC_DRAW
|
||||
);
|
||||
|
||||
GLint positionAttribute = _shaderProgram->attributeLocation("in_position");
|
||||
|
||||
glEnableVertexAttribArray(positionAttribute);
|
||||
glVertexAttribPointer(
|
||||
positionAttribute,
|
||||
4,
|
||||
GL_FLOAT,
|
||||
GL_FALSE,
|
||||
0,
|
||||
nullptr
|
||||
);
|
||||
|
||||
glBindVertexArray(0);
|
||||
|
||||
_isDirty = false;
|
||||
}
|
||||
|
||||
void RenderablePointsCloud::createDataSlice() {
|
||||
_slicedData.clear();
|
||||
_slicedData.reserve(4 * (_fullData.size() / _nValuesPerPoint));
|
||||
auto pointDataSlice = getDataSlice(DataSliceKey::Points);
|
||||
pointDataSlice->clear();
|
||||
pointDataSlice->reserve(fullPointData.size() * 3);
|
||||
|
||||
// Create data slice
|
||||
auto addPosition = [&](const glm::vec4& pos) {
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
_slicedData.push_back(pos[j]);
|
||||
for (glm::vec4::length_type j = 0; j < glm::vec4::length() - 1; ++j) {
|
||||
pointDataSlice->push_back(pos[j]);
|
||||
}
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < _fullData.size(); i += _nValuesPerPoint) {
|
||||
glm::dvec4 transformedPos = glm::dvec4(
|
||||
_fullData[i + 0],
|
||||
_fullData[i + 1],
|
||||
_fullData[i + 2],
|
||||
for (size_t i = 0; i < fullPointData.size(); i += 3) {
|
||||
glm::dvec4 transformedPos = {
|
||||
fullPointData[i + 0],
|
||||
fullPointData[i + 1],
|
||||
fullPointData[i + 2],
|
||||
1.0
|
||||
);
|
||||
};
|
||||
// W-normalization
|
||||
transformedPos /= transformedPos.w;
|
||||
transformedPos *= openspace::distanceconstants::Parsec;
|
||||
transformedPos *= distanceconstants::Parsec;
|
||||
|
||||
addPosition(transformedPos);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderablePointsCloud::loadData() {
|
||||
if (!_dataStorageKey.has_value()) {
|
||||
LWARNING("No point data found");
|
||||
return;
|
||||
}
|
||||
|
||||
// @TODO: potentially combine point data with additional data about the points,
|
||||
// such as luminosity
|
||||
|
||||
// Fetch data from module's centralized storage
|
||||
auto module = global::moduleEngine->module<SoftwareIntegrationModule>();
|
||||
_fullData = module->fetchData(_dataStorageKey.value());
|
||||
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
void RenderablePointsCloud::loadColorMap() {
|
||||
if (!_identifier.has_value()) {
|
||||
LWARNING("No data storage identifier found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch data from module's centralized storage
|
||||
auto module = global::moduleEngine->module<SoftwareIntegrationModule>();
|
||||
std::vector<float> colorMap = module->fetchData(_identifier.value() + "-ColorMap");
|
||||
void RenderablePointsCloud::loadColormap(
|
||||
const std::string& storageKey, SoftwareIntegrationModule* softwareIntegrationModule
|
||||
) {
|
||||
std::vector<float> colorMap = softwareIntegrationModule->fetchData(storageKey);
|
||||
|
||||
if (colorMap.empty()) {
|
||||
LWARNING("There was an issue trying to fetch the colormap data from the syncable storage.");
|
||||
LWARNING("There was an issue trying to fetch the colormap data from the centralized storage.");
|
||||
return;
|
||||
}
|
||||
|
||||
int width = colorMap.size();
|
||||
uint8_t* values = new uint8_t[width];
|
||||
size_t nValues = colorMap.size();
|
||||
uint8_t* values = new uint8_t[nValues];
|
||||
|
||||
int i = 0;
|
||||
while (i < width) {
|
||||
values[i++] = static_cast<uint8_t>(colorMap[i] * 255);
|
||||
values[i++] = static_cast<uint8_t>(colorMap[i] * 255);
|
||||
values[i++] = static_cast<uint8_t>(colorMap[i] * 255);
|
||||
values[i++] = static_cast<uint8_t>(colorMap[i] * 255);
|
||||
for (size_t i = 0; i < nValues; ++i) {
|
||||
values[i] = static_cast<uint8_t>(colorMap[i] * 255);
|
||||
}
|
||||
|
||||
GLenum type = GL_TEXTURE_1D;
|
||||
_colorMapTexture = std::make_unique<ghoul::opengl::Texture>(
|
||||
_colormapTexture.reset();
|
||||
_colormapTexture = std::make_unique<ghoul::opengl::Texture>(
|
||||
values,
|
||||
glm::uvec3(width, 1, 1),
|
||||
type,
|
||||
glm::size3_t(nValues / 4, 1, 1),
|
||||
GL_TEXTURE_1D,
|
||||
ghoul::opengl::Texture::Format::RGBA
|
||||
);
|
||||
_colormapTexture->uploadTexture();
|
||||
|
||||
_isDirty = true;
|
||||
_loadNewColorMap = false;
|
||||
_colorMapEnabled = true;
|
||||
_hasLoadedColormap = true;
|
||||
}
|
||||
|
||||
void RenderablePointsCloud::loadCmapAttributeData(
|
||||
const std::string& storageKey, SoftwareIntegrationModule* softwareIntegrationModule
|
||||
) {
|
||||
auto colormapAttributeData = softwareIntegrationModule->fetchData(storageKey);
|
||||
|
||||
if (colormapAttributeData.empty()) {
|
||||
LWARNING("There was an issue trying to fetch the colormap data from the centralized storage.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto pointDataSlice = getDataSlice(DataSliceKey::Points);
|
||||
|
||||
if (pointDataSlice->size() / 3 != colormapAttributeData.size()) {
|
||||
LWARNING(fmt::format(
|
||||
"There is a mismatch in the amount of colormap attribute scalars ({}) and the amount of points ({})",
|
||||
colormapAttributeData.size(), pointDataSlice->size() / 3
|
||||
));
|
||||
_colormapEnabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
auto colormapAttributeDataSlice = getDataSlice(DataSliceKey::ColormapAttributes);
|
||||
colormapAttributeDataSlice->clear();
|
||||
colormapAttributeDataSlice->reserve(colormapAttributeData.size());
|
||||
|
||||
for (size_t i = 0; i < (pointDataSlice->size() / 3); ++i) {
|
||||
colormapAttributeDataSlice->push_back(colormapAttributeData[i]);
|
||||
}
|
||||
|
||||
_hasLoadedColormapAttributeData = true;
|
||||
LDEBUG("Rerendering colormap attribute data");
|
||||
}
|
||||
|
||||
std::shared_ptr<RenderablePointsCloud::DataSlice> RenderablePointsCloud::getDataSlice(DataSliceKey key) {
|
||||
if (!_dataSlices.count(key)) {
|
||||
_dataSlices.insert({ key, std::make_shared<DataSlice>() });
|
||||
}
|
||||
return _dataSlices.at(key);
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -25,15 +25,17 @@
|
||||
#ifndef __OPENSPACE_MODULE_SOFTWAREINTEGRATION___RENDERABLEPOINTSCLOUD___H__
|
||||
#define __OPENSPACE_MODULE_SOFTWAREINTEGRATION___RENDERABLEPOINTSCLOUD___H__
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <openspace/rendering/renderable.h>
|
||||
|
||||
#include <openspace/properties/scalar/boolproperty.h>
|
||||
#include <openspace/properties/scalar/floatproperty.h>
|
||||
#include <openspace/properties/vector/vec3property.h>
|
||||
#include <openspace/properties/stringproperty.h>
|
||||
#include <openspace/properties/vector/vec4property.h>
|
||||
#include <openspace/properties/optionproperty.h>
|
||||
#include <ghoul/opengl/ghoul_gl.h>
|
||||
#include <ghoul/opengl/uniformcache.h>
|
||||
#include <optional>
|
||||
|
||||
namespace openspace::documentation { struct Documentation; }
|
||||
|
||||
@@ -45,56 +47,69 @@ namespace ghoul::opengl {
|
||||
|
||||
namespace openspace {
|
||||
|
||||
class SoftwareIntegrationModule;
|
||||
|
||||
class RenderablePointsCloud : public Renderable {
|
||||
public:
|
||||
RenderablePointsCloud(const ghoul::Dictionary& dictionary);
|
||||
|
||||
void initialize() override;
|
||||
void initializeGL() override;
|
||||
void deinitializeGL() override;
|
||||
|
||||
bool isReady() const override;
|
||||
|
||||
void render(const RenderData& data, RendererTasks& rendererTask) override;
|
||||
void render(const RenderData& data, RendererTasks&) override;
|
||||
void update(const UpdateData& data) override;
|
||||
|
||||
static documentation::Documentation Documentation();
|
||||
|
||||
protected:
|
||||
void createDataSlice();
|
||||
void loadData();
|
||||
void loadColorMap();
|
||||
void loadData(const std::string& storageKey, SoftwareIntegrationModule* softwareIntegrationModule);
|
||||
void loadColormap(const std::string& storageKey, SoftwareIntegrationModule* softwareIntegrationModule);
|
||||
void loadCmapAttributeData(const std::string& storageKey, SoftwareIntegrationModule* softwareIntegrationModule);
|
||||
|
||||
bool _hasPointData = false;
|
||||
bool _isDirty = true;
|
||||
bool checkDataStorage();
|
||||
|
||||
std::unique_ptr<ghoul::opengl::ProgramObject> _shaderProgram = nullptr;
|
||||
UniformCache(color, opacity, size, modelMatrix, cameraUp,
|
||||
cameraViewProjectionMatrix, eyePosition, sizeOption) _uniformCache;
|
||||
UniformCache(
|
||||
color, opacity, size, modelMatrix, cameraUp,
|
||||
cameraViewProjectionMatrix, eyePosition, sizeOption,
|
||||
colormapTexture, colormapMin, colormapMax, colormapEnabled
|
||||
) _uniformCache;
|
||||
|
||||
properties::BoolProperty _isVisible;
|
||||
properties::FloatProperty _size;
|
||||
properties::Vec3Property _color;
|
||||
properties::Vec4Property _color;
|
||||
properties::OptionProperty _sizeOption;
|
||||
|
||||
std::vector<float> _fullData;
|
||||
std::vector<float> _slicedData;
|
||||
std::unique_ptr<ghoul::opengl::Texture> _colorMapTexture;
|
||||
properties::FloatProperty _colormapMin;
|
||||
properties::FloatProperty _colormapMax;
|
||||
|
||||
std::unique_ptr<ghoul::opengl::Texture> _colormapTexture;
|
||||
properties::BoolProperty _colormapEnabled;
|
||||
|
||||
std::optional<std::string> _dataStorageKey = std::nullopt;
|
||||
std::optional<std::string> _identifier = std::nullopt;
|
||||
properties::BoolProperty _colorMapEnabled;
|
||||
properties::BoolProperty _loadNewColorMap;
|
||||
|
||||
int _nValuesPerPoint = 0;
|
||||
bool _hasLoadedColormapAttributeData = false;
|
||||
bool _hasLoadedColormap = false;
|
||||
|
||||
GLuint _vertexArrayObjectID = 0;
|
||||
GLuint _vertexBufferObjectID = 0;
|
||||
enum class DataSliceKey : size_t {
|
||||
Points = 0,
|
||||
ColormapAttributes
|
||||
};
|
||||
using DataSlice = std::vector<float>;
|
||||
std::unordered_map<DataSliceKey, std::shared_ptr<DataSlice>> _dataSlices;
|
||||
std::shared_ptr<DataSlice> getDataSlice(DataSliceKey key);
|
||||
|
||||
enum SizeOption {
|
||||
Uniform = 0,
|
||||
NonUniform = 1
|
||||
};
|
||||
|
||||
struct VBOLayout {
|
||||
glm::vec3 position;
|
||||
float colormapAttributeData;
|
||||
};
|
||||
GLuint _vbo = 0;
|
||||
GLuint _vao = 0;
|
||||
};
|
||||
|
||||
}// namespace openspace
|
||||
|
||||
@@ -25,25 +25,43 @@
|
||||
#include "fragment.glsl"
|
||||
#include "PowerScaling/powerScaling_fs.hglsl"
|
||||
|
||||
flat in float ge_colormapAttributeScalar;
|
||||
in vec2 coords;
|
||||
flat in float ge_screenSpaceDepth;
|
||||
in vec4 ge_positionViewSpace;
|
||||
|
||||
uniform vec3 color;
|
||||
uniform vec4 color;
|
||||
uniform float opacity;
|
||||
|
||||
Fragment getFragment() {
|
||||
if (opacity < 0.01) discard;
|
||||
uniform float colormapMin;
|
||||
uniform float colormapMax;
|
||||
uniform bool colormapEnabled;
|
||||
uniform sampler1D colormapTexture;
|
||||
|
||||
vec4 attributeScalarToRgb(float attributeData) {
|
||||
// Linear interpolation
|
||||
float t = (attributeData - colormapMin) / (colormapMax - colormapMin);
|
||||
|
||||
// Sample texture with interpolated value
|
||||
return texture(colormapTexture, t);
|
||||
}
|
||||
|
||||
Fragment getFragment() {
|
||||
const float radius = 0.5;
|
||||
float d = length(coords - radius);
|
||||
if (d > 0.5) discard;
|
||||
float distance = length(coords - radius);
|
||||
if (distance > 0.6) discard;
|
||||
|
||||
// calculate distance from the origin point
|
||||
float circle = smoothstep(radius, radius - (radius * 0.3), d);
|
||||
float circle = smoothstep(radius, radius - (radius * 0.2), distance);
|
||||
|
||||
vec4 outputColor = vec4(color.rgb, color.a * opacity);
|
||||
if (colormapEnabled) {
|
||||
vec4 colorFromColormap = attributeScalarToRgb(ge_colormapAttributeScalar);
|
||||
outputColor = vec4(colorFromColormap.rgb, colorFromColormap.a * opacity);
|
||||
}
|
||||
|
||||
Fragment frag;
|
||||
frag.color = vec4(color, opacity) * vec4(circle);
|
||||
frag.color = outputColor * vec4(circle);
|
||||
frag.depth = ge_screenSpaceDepth;
|
||||
frag.gPosition = ge_positionViewSpace;
|
||||
frag.gNormal = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
|
||||
@@ -29,10 +29,13 @@
|
||||
layout(points) in;
|
||||
layout(triangle_strip, max_vertices = 4) out;
|
||||
|
||||
in float vs_colormapAttributeScalar[];
|
||||
|
||||
flat out float ge_screenSpaceDepth;
|
||||
out vec4 ge_positionViewSpace;
|
||||
|
||||
out vec2 coords;
|
||||
out float ge_colormapAttributeScalar;
|
||||
|
||||
uniform dvec3 eyePosition;
|
||||
uniform dvec3 cameraUp;
|
||||
@@ -59,6 +62,8 @@ double scaleForUniform(vec3 pos) {
|
||||
}
|
||||
|
||||
void main() {
|
||||
ge_colormapAttributeScalar = vs_colormapAttributeScalar[0];
|
||||
|
||||
vec3 pos = gl_in[0].gl_Position.xyz;
|
||||
|
||||
double scaleMultiply = 1.0;
|
||||
|
||||
@@ -27,7 +27,12 @@
|
||||
#include "PowerScaling/powerScaling_vs.hglsl"
|
||||
|
||||
layout(location = 0) in vec3 in_position;
|
||||
in float in_colormapAttributeScalar;
|
||||
|
||||
out float vs_colormapAttributeScalar;
|
||||
|
||||
void main() {
|
||||
vs_colormapAttributeScalar = in_colormapAttributeScalar;
|
||||
|
||||
gl_Position = vec4(in_position, 1.0);
|
||||
}
|
||||
|
||||
@@ -37,21 +37,21 @@ namespace openspace {
|
||||
|
||||
namespace simp {
|
||||
|
||||
SimpError::SimpError(const ErrorCode _errorCode, const std::string& msg)
|
||||
SimpError::SimpError(const utils::ErrorCode _errorCode, const std::string& msg)
|
||||
: errorCode{errorCode}, ghoul::RuntimeError(fmt::format("{}: Error Code: {} - {}", "SIMP error", static_cast<uint32_t>(_errorCode), msg), "Software Integration Messaging Protocol error")
|
||||
{}
|
||||
|
||||
bool isEndOfCurrentValue(const std::vector<char>& message, size_t offset) {
|
||||
bool utils::isEndOfCurrentValue(const std::vector<char>& message, size_t offset) {
|
||||
if (offset >= message.size()) {
|
||||
throw SimpError(
|
||||
ErrorCode::OffsetLargerThanMessageSize,
|
||||
utils::ErrorCode::OffsetLargerThanMessageSize,
|
||||
"Unexpectedly reached the end of the message..."
|
||||
);
|
||||
}
|
||||
|
||||
if (message.size() > 0 && offset == message.size() - 1 && message[offset] != SEP) {
|
||||
throw SimpError(
|
||||
ErrorCode::ReachedEndBeforeSeparator,
|
||||
utils::ErrorCode::ReachedEndBeforeSeparator,
|
||||
"Reached end of message before reading separator character..."
|
||||
);
|
||||
}
|
||||
@@ -60,15 +60,20 @@ bool isEndOfCurrentValue(const std::vector<char>& message, size_t offset) {
|
||||
}
|
||||
|
||||
MessageType getMessageType(const std::string& type) {
|
||||
if (_messageTypeFromSIMPType.count(type) == 0) return MessageType::Unknown;
|
||||
return _messageTypeFromSIMPType.at(type);
|
||||
if (utils::_messageTypeFromSIMPType.count(type) == 0) return MessageType::Unknown;
|
||||
return utils::_messageTypeFromSIMPType.at(type);
|
||||
}
|
||||
|
||||
std::string getSIMPType(const MessageType& type){
|
||||
for (auto [key, messageType] : _messageTypeFromSIMPType) {
|
||||
if (messageType == type) return key;
|
||||
}
|
||||
return "";
|
||||
std::string getSIMPType(const MessageType& type) {
|
||||
auto it = std::find_if(
|
||||
utils::_messageTypeFromSIMPType.begin(),
|
||||
utils::_messageTypeFromSIMPType.end(),
|
||||
[type](const std::pair<const std::string, MessageType>& p) {
|
||||
return type == p.second;
|
||||
}
|
||||
);
|
||||
if (it == utils::_messageTypeFromSIMPType.end()) return "UNKN";
|
||||
return it->first;
|
||||
}
|
||||
|
||||
std::string formatLengthOfSubject(size_t lengthOfSubject) {
|
||||
@@ -82,10 +87,8 @@ std::string formatUpdateMessage(MessageType messageType,
|
||||
std::string_view identifier,
|
||||
std::string_view value)
|
||||
{
|
||||
const int lengthOfIdentifier = static_cast<int>(identifier.length());
|
||||
const int lengthOfValue = static_cast<int>(value.length());
|
||||
std::string subject = fmt::format(
|
||||
"{}{}{}{}", lengthOfIdentifier, identifier, lengthOfValue, value
|
||||
"{}{}{}{}", identifier, SEP, value, SEP
|
||||
);
|
||||
|
||||
const std::string lengthOfSubject = formatLengthOfSubject(subject.length());
|
||||
@@ -95,19 +98,191 @@ std::string formatUpdateMessage(MessageType messageType,
|
||||
|
||||
std::string formatConnectionMessage(std::string_view software) {
|
||||
std::string subject = fmt::format(
|
||||
"{}", software
|
||||
"{}{}", software, SEP
|
||||
);
|
||||
|
||||
const std::string lengthOfSubject = formatLengthOfSubject(subject.length());
|
||||
|
||||
return fmt::format("{}{}{}{}", ProtocolVersion, "CONN", lengthOfSubject, subject);
|
||||
return fmt::format("{}{}{}{}", ProtocolVersion, getSIMPType(MessageType::Connection), lengthOfSubject, subject);
|
||||
}
|
||||
|
||||
std::string formatDisconnectionMessage() {
|
||||
const std::string lengthOfSubject = formatLengthOfSubject(0);
|
||||
return fmt::format("{}{}{}", ProtocolVersion, "DISC", lengthOfSubject);
|
||||
std::string formatColorMessage(std::string_view identifier, glm::vec4 color) {
|
||||
std::ostringstream value_stream;
|
||||
value_stream << "[" << floatToHex(color.r) << SEP << floatToHex(color.g) << SEP
|
||||
<< floatToHex(color.b) << SEP << floatToHex(color.a) << SEP << "]";
|
||||
|
||||
return formatUpdateMessage(MessageType::Color, identifier, value_stream.str());
|
||||
}
|
||||
|
||||
std::string formatPointDataCallbackMessage(std::string_view identifier) {
|
||||
std::string subject = fmt::format(
|
||||
"{}{}", identifier, SEP
|
||||
);
|
||||
|
||||
const std::string lengthOfSubject = formatLengthOfSubject(subject.length());
|
||||
|
||||
return fmt::format("{}{}{}{}", ProtocolVersion, getSIMPType(MessageType::PointData), lengthOfSubject, subject);
|
||||
}
|
||||
|
||||
std::string floatToHex(const float f) {
|
||||
const uint32_t *int_representation = reinterpret_cast<const uint32_t *>(&f);
|
||||
std::ostringstream stream;
|
||||
stream << "0x" << std::setfill ('0') << std::setw(sizeof(float)*2) << std::hex << *int_representation;
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
float hexToFloat(const std::string& f) {
|
||||
uint32_t int_value = static_cast<uint32_t>(std::stoul(f, nullptr, 16));
|
||||
std::ostringstream stream;
|
||||
stream << std::dec << int_value;
|
||||
return *reinterpret_cast<float*>(&int_value);
|
||||
}
|
||||
|
||||
int readIntValue(const std::vector<char>& message, size_t& offset) {
|
||||
std::string string_value;
|
||||
int value;
|
||||
bool isHex = false;
|
||||
|
||||
while (!utils::isEndOfCurrentValue(message, offset)) {
|
||||
char c = message[offset];
|
||||
if (c == 'x' || c == 'X') isHex = true;
|
||||
string_value.push_back(c);
|
||||
offset++;
|
||||
}
|
||||
|
||||
try {
|
||||
value = std::stoi(string_value, nullptr, isHex ? 16 : 10);
|
||||
}
|
||||
catch(std::exception &err) {
|
||||
throw SimpError(
|
||||
utils::ErrorCode::Generic,
|
||||
fmt::format("Error when trying to parse the integer {}: {}", string_value, err.what())
|
||||
);
|
||||
}
|
||||
|
||||
++offset;
|
||||
return value;
|
||||
}
|
||||
|
||||
float readFloatValue(const std::vector<char>& message, size_t& offset) {
|
||||
std::string string_value;
|
||||
float value;
|
||||
|
||||
while (!utils::isEndOfCurrentValue(message, offset)) {
|
||||
string_value.push_back(message[offset]);
|
||||
offset++;
|
||||
}
|
||||
|
||||
try {
|
||||
value = hexToFloat(string_value);
|
||||
}
|
||||
catch(std::exception &err) {
|
||||
throw SimpError(
|
||||
utils::ErrorCode::Generic,
|
||||
fmt::format("Error when trying to parse the float {}: {}", string_value, err.what())
|
||||
);
|
||||
}
|
||||
|
||||
++offset;
|
||||
return value;
|
||||
}
|
||||
|
||||
void readColormap(
|
||||
const std::vector<char>& message, size_t& offset, size_t nColors, std::vector<float>& colorMap
|
||||
) {
|
||||
colorMap.reserve(nColors * 4);
|
||||
while (message[offset] != SEP) {
|
||||
glm::vec4 color = utils::readSingleColor(message, offset);
|
||||
|
||||
// Colormap should be stored in a sequential vector
|
||||
// of floats for syncing between nodes and when
|
||||
// loaded to as a texture in the shader.
|
||||
colorMap.push_back(color[0]);
|
||||
colorMap.push_back(color[1]);
|
||||
colorMap.push_back(color[2]);
|
||||
colorMap.push_back(color[3]);
|
||||
}
|
||||
|
||||
offset++;
|
||||
}
|
||||
|
||||
glm::vec4 utils::readSingleColor(const std::vector<char>& message, size_t& offset) {
|
||||
if (message[offset] != '[') {
|
||||
throw SimpError(
|
||||
utils::ErrorCode::Generic,
|
||||
fmt::format("Expected to read '[', got {} in 'readColor'", message[offset])
|
||||
);
|
||||
}
|
||||
++offset;
|
||||
|
||||
float r = readFloatValue(message, offset);
|
||||
float g = readFloatValue(message, offset);
|
||||
float b = readFloatValue(message, offset);
|
||||
float a = readFloatValue(message, offset);
|
||||
|
||||
if (message[offset] != ']') {
|
||||
throw SimpError(
|
||||
utils::ErrorCode::Generic,
|
||||
fmt::format("Expected to read ']', got {} in 'readColor'", message[offset])
|
||||
);
|
||||
}
|
||||
++offset;
|
||||
|
||||
return { r, g, b, a };
|
||||
}
|
||||
|
||||
glm::vec4 readColor(const std::vector<char>& message, size_t& offset) {
|
||||
glm::vec4 color = utils::readSingleColor(message, offset);
|
||||
++offset;
|
||||
return color;
|
||||
}
|
||||
|
||||
std::string readString(const std::vector<char>& message, size_t& offset) {
|
||||
std::string value;
|
||||
while (!utils::isEndOfCurrentValue(message, offset)) {
|
||||
value.push_back(message[offset]);
|
||||
++offset;
|
||||
}
|
||||
|
||||
++offset;
|
||||
return value;
|
||||
}
|
||||
|
||||
void readPointData(
|
||||
const std::vector<char>& message,
|
||||
size_t& offset,
|
||||
size_t nPoints,
|
||||
size_t dimensionality,
|
||||
std::vector<float>& pointData
|
||||
) {
|
||||
pointData.reserve(nPoints * dimensionality);
|
||||
|
||||
while (!utils::isEndOfCurrentValue(message, offset)) {
|
||||
if (message[offset] != '[') {
|
||||
throw SimpError(
|
||||
utils::ErrorCode::Generic,
|
||||
fmt::format("Expected to read '[', got {} in 'readPointData'", message[offset])
|
||||
);
|
||||
}
|
||||
++offset;
|
||||
|
||||
for (int i = 0; i < dimensionality; ++i) {
|
||||
pointData.push_back(readFloatValue(message, offset));
|
||||
}
|
||||
|
||||
if (message[offset] != ']') {
|
||||
throw SimpError(
|
||||
utils::ErrorCode::Generic,
|
||||
fmt::format("Expected to read ']', got {} in 'readPointData'", message[offset])
|
||||
);
|
||||
}
|
||||
++offset;
|
||||
}
|
||||
|
||||
++offset;
|
||||
}
|
||||
|
||||
} // namespace simp
|
||||
|
||||
} // namespace openspace
|
||||
} // namespace openspace
|
||||
|
||||
@@ -25,14 +25,32 @@
|
||||
#ifndef __OPENSPACE_MODULE_SOFTWAREINTEGRATION___SIMP___H__
|
||||
#define __OPENSPACE_MODULE_SOFTWAREINTEGRATION___SIMP___H__
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace openspace {
|
||||
|
||||
namespace simp {
|
||||
|
||||
const float ProtocolVersion = 1.6;
|
||||
const std::string ProtocolVersion = "1.8";
|
||||
|
||||
const char SEP = ';';
|
||||
|
||||
enum class MessageType : uint32_t {
|
||||
Connection = 0,
|
||||
PointData,
|
||||
RemoveSceneGraphNode,
|
||||
Color,
|
||||
Colormap,
|
||||
AttributeData,
|
||||
Opacity,
|
||||
Size,
|
||||
Visibility,
|
||||
Disconnection,
|
||||
Unknown
|
||||
};
|
||||
|
||||
namespace utils {
|
||||
|
||||
enum class ErrorCode : uint32_t {
|
||||
ReachedEndBeforeSeparator = 0,
|
||||
OffsetLargerThanMessageSize,
|
||||
@@ -40,39 +58,32 @@ enum class ErrorCode : uint32_t {
|
||||
Generic,
|
||||
};
|
||||
|
||||
enum class MessageType : uint32_t {
|
||||
Connection = 0,
|
||||
ReadPointData,
|
||||
RemoveSceneGraphNode,
|
||||
Color,
|
||||
ColorMap,
|
||||
Opacity,
|
||||
Size,
|
||||
Visibility,
|
||||
Disconnection,
|
||||
Unknown
|
||||
};
|
||||
|
||||
const std::map<std::string, MessageType> _messageTypeFromSIMPType {
|
||||
const std::unordered_map<std::string, MessageType> _messageTypeFromSIMPType {
|
||||
{"CONN", MessageType::Connection},
|
||||
{"PDAT", MessageType::ReadPointData},
|
||||
{"PDAT", MessageType::PointData},
|
||||
{"RSGN", MessageType::RemoveSceneGraphNode},
|
||||
{"UPCO", MessageType::Color},
|
||||
{"LCOL", MessageType::ColorMap},
|
||||
{"UPOP", MessageType::Opacity},
|
||||
{"UPSI", MessageType::Size},
|
||||
{"FCOL", MessageType::Color},
|
||||
{"LCOL", MessageType::Colormap},
|
||||
{"ATDA", MessageType::AttributeData},
|
||||
{"FOPA", MessageType::Opacity},
|
||||
{"FPSI", MessageType::Size},
|
||||
{"TOVI", MessageType::Visibility},
|
||||
{"DISC", MessageType::Disconnection},
|
||||
};
|
||||
|
||||
class SimpError : public ghoul::RuntimeError {
|
||||
public:
|
||||
ErrorCode errorCode;
|
||||
explicit SimpError(const ErrorCode _errorCode, const std::string& msg);
|
||||
};
|
||||
glm::vec4 readSingleColor(const std::vector<char>& message, size_t& offset);
|
||||
|
||||
bool isEndOfCurrentValue(const std::vector<char>& message, size_t offset);
|
||||
|
||||
} // namespace utils
|
||||
|
||||
class SimpError : public ghoul::RuntimeError {
|
||||
public:
|
||||
utils::ErrorCode errorCode;
|
||||
explicit SimpError(const utils::ErrorCode _errorCode, const std::string& msg);
|
||||
};
|
||||
|
||||
|
||||
MessageType getMessageType(const std::string& type);
|
||||
|
||||
std::string getSIMPType(const MessageType& type);
|
||||
@@ -85,8 +96,32 @@ std::string formatUpdateMessage(MessageType messageType,
|
||||
std::string_view value);
|
||||
|
||||
std::string formatConnectionMessage(std::string_view value);
|
||||
|
||||
std::string formatColorMessage(std::string_view identifier, glm::vec4 color);
|
||||
|
||||
std::string formatDisconnectionMessage();
|
||||
std::string formatPointDataCallbackMessage(std::string_view identifier);
|
||||
|
||||
std::string floatToHex(const float f);
|
||||
|
||||
float hexToFloat(const std::string& f);
|
||||
|
||||
float readFloatValue(const std::vector<char>& message, size_t& offset);
|
||||
|
||||
int readIntValue(const std::vector<char>& message, size_t& offset);
|
||||
|
||||
std::string readString(const std::vector<char>& message, size_t& offset);
|
||||
|
||||
glm::vec4 readColor(const std::vector<char>& message, size_t& offset);
|
||||
|
||||
void readPointData(
|
||||
const std::vector<char>& message, size_t& offset, size_t nPoints,
|
||||
size_t dimensionality, std::vector<float>& pointData
|
||||
);
|
||||
|
||||
void readColormap(
|
||||
const std::vector<char>& message, size_t& offset, size_t nColors,
|
||||
std::vector<float>& colorMap
|
||||
);
|
||||
|
||||
} // namespace simp
|
||||
|
||||
|
||||
@@ -45,34 +45,24 @@ SoftwareIntegrationModule::SoftwareIntegrationModule() : OpenSpaceModule(Name) {
|
||||
// The Master node will handle all communication with the external software
|
||||
// and forward it to the Client nodes
|
||||
// 4700, is the defualt port where the tcp socket will be opened to the ext. software
|
||||
_server = new NetworkEngine();
|
||||
_networkEngine = std::make_unique<NetworkEngine>();
|
||||
}
|
||||
}
|
||||
|
||||
SoftwareIntegrationModule::~SoftwareIntegrationModule() {
|
||||
if (global::windowDelegate->isMaster()) {
|
||||
delete _server;
|
||||
}
|
||||
internalDeinitialize();
|
||||
}
|
||||
|
||||
void SoftwareIntegrationModule::storeData(const std::string& key,
|
||||
const std::vector<float> data) {
|
||||
_syncableFloatDataStorage.emplace(key, std::move(data));
|
||||
void SoftwareIntegrationModule::storeData(const std::string& key, const std::vector<float>& data) {
|
||||
_syncableFloatDataStorage.store(key, data);
|
||||
}
|
||||
|
||||
std::vector<float> SoftwareIntegrationModule::fetchData(const std::string& key) {
|
||||
auto it = _syncableFloatDataStorage.find(key);
|
||||
if (it == _syncableFloatDataStorage.end()) {
|
||||
LERROR(fmt::format(
|
||||
"Could not find data with key '{}' in the temporary data storage", key
|
||||
));
|
||||
return std::vector<float>();
|
||||
}
|
||||
const std::vector<float>& SoftwareIntegrationModule::fetchData(const std::string& key) {
|
||||
return _syncableFloatDataStorage.fetch(key);
|
||||
}
|
||||
|
||||
std::vector<float> data = it->second;
|
||||
_syncableFloatDataStorage.erase(it);
|
||||
|
||||
return std::move(data);
|
||||
bool SoftwareIntegrationModule::isDataDirty(const std::string& key) {
|
||||
return _syncableFloatDataStorage.isDirty(key);
|
||||
}
|
||||
|
||||
void SoftwareIntegrationModule::internalInitialize(const ghoul::Dictionary&) {
|
||||
@@ -83,17 +73,17 @@ void SoftwareIntegrationModule::internalInitialize(const ghoul::Dictionary&) {
|
||||
fRenderable->registerClass<RenderablePointsCloud>("RenderablePointsCloud");
|
||||
|
||||
if (global::windowDelegate->isMaster()) {
|
||||
_server->start();
|
||||
_networkEngine->start();
|
||||
|
||||
global::callback::preSync->emplace_back([this]() { _server->update(); });
|
||||
global::callback::postSyncPreDraw->emplace_back([this]() {
|
||||
if (!_networkEngine) return;
|
||||
_networkEngine->postSync();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void SoftwareIntegrationModule::internalDeinitialize() {
|
||||
global::syncEngine->removeSyncables(getSyncables());
|
||||
if (global::windowDelegate->isMaster()) {
|
||||
_server->stop();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<documentation::Documentation>
|
||||
|
||||
@@ -40,8 +40,9 @@ public:
|
||||
SoftwareIntegrationModule();
|
||||
~SoftwareIntegrationModule();
|
||||
|
||||
void storeData(const std::string& key, const std::vector<float> data);
|
||||
std::vector<float> fetchData(const std::string& key);
|
||||
void storeData(const std::string& key, const std::vector<float>& data);
|
||||
const std::vector<float>& fetchData(const std::string& key);
|
||||
bool isDataDirty(const std::string& key);
|
||||
|
||||
std::vector<documentation::Documentation> documentations() const override;
|
||||
|
||||
@@ -51,10 +52,12 @@ private:
|
||||
|
||||
std::vector<Syncable*> getSyncables();
|
||||
|
||||
NetworkEngine* _server;
|
||||
|
||||
// Centralized storage for datasets
|
||||
SyncableFloatDataStorage _syncableFloatDataStorage;
|
||||
|
||||
// Network engine
|
||||
std::unique_ptr<NetworkEngine> _networkEngine;
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -41,15 +41,20 @@ void SyncableFloatDataStorage::encode(SyncBuffer* syncBuffer) {
|
||||
size_t nDataEntries = _storage.size();
|
||||
syncBuffer->encode(nDataEntries);
|
||||
|
||||
for (const auto& [key, dataEntry] : _storage) {
|
||||
for (auto& [key, value] : _storage) {
|
||||
syncBuffer->encode(key);
|
||||
|
||||
// Go trough all data in data entry.
|
||||
// Sequentially structured (ex: x1, y1, z1, x2, y2, z2...)
|
||||
size_t nItemsInDataEntry = dataEntry.size();
|
||||
syncBuffer->encode(nItemsInDataEntry);
|
||||
for (auto value : dataEntry) {
|
||||
syncBuffer->encode(value);
|
||||
syncBuffer->encode(value.dirty);
|
||||
|
||||
// Only encode data if it is dirty, to save bandwidth
|
||||
if (value.dirty) {
|
||||
// Go trough all data in data entry.
|
||||
// Sequentially structured as: x1, y1, z1, x2, y2, z2...
|
||||
size_t nItemsInDataEntry = value.data.size();
|
||||
syncBuffer->encode(nItemsInDataEntry);
|
||||
for (auto val : value.data) {
|
||||
syncBuffer->encode(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,60 +69,112 @@ void SyncableFloatDataStorage::decode(SyncBuffer* syncBuffer) {
|
||||
std::string key;
|
||||
syncBuffer->decode(key);
|
||||
|
||||
size_t nItemsInDataEntry;
|
||||
syncBuffer->decode(nItemsInDataEntry);
|
||||
|
||||
// TODO: Change to a glm::fvec3 so we can use an overload
|
||||
// of decode(glm::fvec3) instead of using a for-loop over floats?
|
||||
std::vector<float> dataEntry;
|
||||
dataEntry.reserve(nItemsInDataEntry);
|
||||
for (size_t j = 0; j < nItemsInDataEntry; ++j) {
|
||||
float value;
|
||||
syncBuffer->decode(value);
|
||||
dataEntry.push_back(value);
|
||||
}
|
||||
bool dirty;
|
||||
syncBuffer->decode(dirty);
|
||||
|
||||
_storage[key] = dataEntry;
|
||||
if (dirty) {
|
||||
size_t nItemsInDataEntry;
|
||||
syncBuffer->decode(nItemsInDataEntry);
|
||||
|
||||
std::vector<float> dataEntry;
|
||||
dataEntry.reserve(nItemsInDataEntry);
|
||||
for (size_t j = 0; j < nItemsInDataEntry; ++j) {
|
||||
float value;
|
||||
syncBuffer->decode(value);
|
||||
dataEntry.push_back(value);
|
||||
}
|
||||
|
||||
insertAssign(key, Value{ dataEntry, dirty });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SyncableFloatDataStorage::postSync(bool isMaster) {
|
||||
if (isMaster) {
|
||||
if (_storage.size() > 0) {
|
||||
// LWARNING(fmt::format("SyncableFloatDataStorage.size() (MASTER): {}", _storage.size()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (_storage.size() > 0) {
|
||||
// LWARNING(fmt::format("SyncableFloatDataStorage.size() (CLIENT): {}", _storage.size()));
|
||||
for (auto& [key, value] : _storage) {
|
||||
if (value.dirty) {
|
||||
value.dirty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* ================================================== */
|
||||
|
||||
const SyncableFloatDataStorage::ValueData& SyncableFloatDataStorage::fetch(const Key& key) {
|
||||
LDEBUG(fmt::format("Loading data from float data storage: {}", key));
|
||||
auto it = find(key);
|
||||
if (it == end()) {
|
||||
LERROR(fmt::format(
|
||||
"Could not find data with key '{}' in the centralized data storage", key
|
||||
));
|
||||
return std::move(ValueData{});
|
||||
}
|
||||
|
||||
it->second.localDirty = false;
|
||||
|
||||
return it->second.data;
|
||||
}
|
||||
|
||||
bool SyncableFloatDataStorage::isDirty(const Key& key) {
|
||||
auto it = find(key);
|
||||
if (it == end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return it->second.localDirty;
|
||||
}
|
||||
|
||||
void SyncableFloatDataStorage::store(const Key& key, const ValueData& data) {
|
||||
LDEBUG(fmt::format("Storing data in float data storage: {}", key));
|
||||
Value value{ data, true, true };
|
||||
|
||||
auto old = find(key);
|
||||
if (old != end()) {
|
||||
glm::vec3 firstValueOld{};
|
||||
for (glm::vec3::length_type i = 0; i < glm::vec3::length(); ++i) {
|
||||
firstValueOld[i] = old->second.data[i];
|
||||
}
|
||||
|
||||
LDEBUG(fmt::format(
|
||||
"First data point: old: {}", firstValueOld
|
||||
));
|
||||
}
|
||||
|
||||
insertAssign(key, std::move(value));
|
||||
|
||||
auto newVal = find(key);
|
||||
if (newVal != end()) {
|
||||
glm::vec3 firstValueNew{};
|
||||
for (glm::vec3::length_type i = 0; i < glm::vec3::length(); ++i) {
|
||||
firstValueNew[i] = newVal->second.data[i];
|
||||
}
|
||||
|
||||
LDEBUG(fmt::format(
|
||||
"First data point: new {}", firstValueNew
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/* =============== Utility functions ================ */
|
||||
SyncableFloatDataStorage::Iterator SyncableFloatDataStorage::erase(SyncableFloatDataStorage::Iterator pos) {
|
||||
return _storage.erase(pos);
|
||||
}
|
||||
SyncableFloatDataStorage::Iterator SyncableFloatDataStorage::erase(const SyncableFloatDataStorage::Iterator first, const SyncableFloatDataStorage::Iterator last) {
|
||||
return _storage.erase(first, last);
|
||||
}
|
||||
size_t SyncableFloatDataStorage::erase(const SyncableFloatDataStorage::Key& key) {
|
||||
return _storage.erase(key);
|
||||
}
|
||||
|
||||
std::pair<SyncableFloatDataStorage::Iterator, bool> SyncableFloatDataStorage::emplace(SyncableFloatDataStorage::Key key, SyncableFloatDataStorage::Value value) {
|
||||
return _storage.emplace(key, value);
|
||||
void SyncableFloatDataStorage::insertAssign(Key key, const Value& value) {
|
||||
auto it = find(key);
|
||||
if (it == end()) {
|
||||
_storage.emplace(key, value);
|
||||
}
|
||||
else {
|
||||
it->second = value;
|
||||
}
|
||||
}
|
||||
|
||||
SyncableFloatDataStorage::Value& SyncableFloatDataStorage::at(const SyncableFloatDataStorage::Key& key) {
|
||||
return _storage.at(key);
|
||||
}
|
||||
const SyncableFloatDataStorage::Value& SyncableFloatDataStorage::at(const SyncableFloatDataStorage::Key& key) const {
|
||||
SyncableFloatDataStorage::Value& SyncableFloatDataStorage::at(const Key& key) {
|
||||
return _storage.at(key);
|
||||
}
|
||||
|
||||
SyncableFloatDataStorage::Iterator SyncableFloatDataStorage::find(const SyncableFloatDataStorage::Key& key) {
|
||||
SyncableFloatDataStorage::Iterator SyncableFloatDataStorage::find(const Key& key) {
|
||||
return _storage.find(key);
|
||||
}
|
||||
/* ================================================== */
|
||||
@@ -132,10 +189,4 @@ SyncableFloatDataStorage::Iterator SyncableFloatDataStorage::begin() noexcept {
|
||||
}
|
||||
/* ================================================== */
|
||||
|
||||
/* =============== Operator overloads =============== */
|
||||
SyncableFloatDataStorage::Value& SyncableFloatDataStorage::operator[](SyncableFloatDataStorage::Key&& key) {
|
||||
return _storage[key];
|
||||
}
|
||||
/* ================================================== */
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -25,48 +25,45 @@
|
||||
#ifndef __OPENSPACE_MODULE_SOFTWAREINTEGRATION___SYNCABLEFLOATDATASTORAGE___H__
|
||||
#define __OPENSPACE_MODULE_SOFTWAREINTEGRATION___SYNCABLEFLOATDATASTORAGE___H__
|
||||
|
||||
#include <openspace/util/syncable.h>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <openspace/util/syncable.h>
|
||||
|
||||
namespace openspace {
|
||||
|
||||
/**
|
||||
* A double buffered implementation of the Syncable interface.
|
||||
* Users are encouraged to used this class as a default way to synchronize different
|
||||
* C++ data types using the SyncEngine.
|
||||
*
|
||||
* This class aims to handle the synchronization parts and yet act like a regular
|
||||
* instance of T. Implicit casts are supported, however, when accessing member functions
|
||||
* or variables, user may have to do explicit casts.
|
||||
*
|
||||
* ((T&) t).method();
|
||||
*
|
||||
*/
|
||||
class SyncableFloatDataStorage : public Syncable {
|
||||
public:
|
||||
/* ====================== Types ===================== */
|
||||
typedef std::string Key;
|
||||
typedef std::vector<float> Value; // a dataset stored like x1, y1, z1, x2, y2 ....
|
||||
typedef std::map<Key, Value> Storage;
|
||||
typedef Storage::iterator Iterator;
|
||||
struct Value {
|
||||
// a dataset stored like x1, y1, z1, x2, y2 ....
|
||||
std::vector<float> data;
|
||||
bool dirty;
|
||||
bool localDirty;
|
||||
};
|
||||
using ValueData = decltype(Value::data);
|
||||
using Key = std::string;
|
||||
using Storage = std::unordered_map<Key, Value>;
|
||||
using Iterator = Storage::iterator;
|
||||
/* ================================================== */
|
||||
|
||||
/* ============== SyncEngine functions ============== */
|
||||
// virtual void preSync(bool isMaster) override;
|
||||
virtual void encode(SyncBuffer* syncBuffer) override;
|
||||
virtual void decode(SyncBuffer* syncBuffer) override;
|
||||
virtual void postSync(bool isMaster) override;
|
||||
/* ================================================== */
|
||||
|
||||
const ValueData& fetch(const Key& key);
|
||||
bool isDirty(const Key& key);
|
||||
void store(const Key& key, const ValueData& data);
|
||||
|
||||
private:
|
||||
/* =============== Utility functions ================ */
|
||||
Iterator erase(Iterator pos);
|
||||
Iterator erase(const Iterator first, const Iterator last);
|
||||
size_t erase(const Key& key);
|
||||
|
||||
std::pair<Iterator, bool> emplace(Key key, Value value);
|
||||
void insertAssign(Key key, const Value& value);
|
||||
|
||||
Value& at(const Key& key);
|
||||
const Value& at(const Key& key) const;
|
||||
|
||||
Iterator find(const Key& key);
|
||||
/* ================================================== */
|
||||
@@ -77,16 +74,8 @@ public:
|
||||
Iterator begin() noexcept;
|
||||
/* ================================================== */
|
||||
|
||||
/* =============== Operator overloads =============== */
|
||||
Value& operator[](Key&& key);
|
||||
/* ================================================== */
|
||||
|
||||
private:
|
||||
std::mutex _mutex;
|
||||
Storage _storage;
|
||||
|
||||
bool showMessageEncode = true;
|
||||
bool showMessageDecode = true;
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
Reference in New Issue
Block a user