diff --git a/include/flamegpu/simulation/detail/CUDAEnvironmentDirectedGraphBuffers.cuh b/include/flamegpu/simulation/detail/CUDAEnvironmentDirectedGraphBuffers.cuh index e1d3766cf..f4aefc0b9 100644 --- a/include/flamegpu/simulation/detail/CUDAEnvironmentDirectedGraphBuffers.cuh +++ b/include/flamegpu/simulation/detail/CUDAEnvironmentDirectedGraphBuffers.cuh @@ -15,15 +15,18 @@ #include "flamegpu/simulation/detail/CUDAErrorChecking.cuh" #include "flamegpu/detail/type_decode.h" #include "flamegpu/util/StringPair.h" - namespace flamegpu { +#ifdef FLAMEGPU_VISUALISATION +namespace visualiser { +struct ModelVisData; +} +#endif namespace detail { class CUDAScatter; namespace curve { class HostCurve; class CurveRTCHost; } - /** * This represents the equivalent of CUDAAgent, CUDAMessage for EnvironmentDirectedGraph * As the graph cannot be modified on the device, the host buffers can be assumed to always holds the truth @@ -66,6 +69,12 @@ class CUDAEnvironmentDirectedGraphBuffers { std::map edge_buffers; std::list> curve_instances; std::list> rtc_curve_instances; +#ifdef FLAMEGPU_VISUALISATION + /** + * Empty if getVisualisation() hasn't been called + */ + mutable std::weak_ptr visualisation; +#endif size_type vertex_count; size_type edge_count; bool requires_rebuild; @@ -267,6 +276,11 @@ class CUDAEnvironmentDirectedGraphBuffers { * @throws exception::InvalidID If the ID is not in use */ unsigned int getEdgeIndex(id_t src_vertex_id, id_t dest_vertex_id) const; +#ifdef FLAMEGPU_VISUALISATION + void setVisualisation(std::shared_ptr &_visualisation) const { + this->visualisation = _visualisation; + } +#endif }; diff --git a/include/flamegpu/visualiser/EnvironmentGraphVis.h b/include/flamegpu/visualiser/EnvironmentGraphVis.h new file mode 100644 index 000000000..870126627 --- /dev/null +++ b/include/flamegpu/visualiser/EnvironmentGraphVis.h @@ -0,0 +1,73 @@ +#ifndef INCLUDE_FLAMEGPU_VISUALISER_ENVIRONMENTGRAPHVIS_H_ +#define INCLUDE_FLAMEGPU_VISUALISER_ENVIRONMENTGRAPHVIS_H_ +#ifdef FLAMEGPU_VISUALISATION + +#include +#include + +#include "flamegpu/visualiser/color/Color.h" + +namespace flamegpu { +struct EnvironmentDirectedGraphData; +namespace detail { +class CUDAEnvironmentDirectedGraphBuffers; +} // namespace detail +namespace visualiser { +struct LineConfig; +struct EnvironmentGraphVisData { + explicit EnvironmentGraphVisData(std::shared_ptr _graphData, std::shared_ptr_lines); + void constructGraph(const std::shared_ptr &graph); + std::string x_varName = ""; + std::string y_varName = ""; + std::string z_varName = ""; + std::string xy_varName = ""; + std::string xyz_varName = ""; + Color color; + const std::shared_ptr graphData; + const std::shared_ptr lines; +}; +class EnvironmentGraphVis { + public: + explicit EnvironmentGraphVis(std::shared_ptr data); + + /** + * Set the name of the variable representing the agents x/y/z location coordinates + * @param var_name Name of the agent variable + * @note unnecessary if the variables are named "x", "y", "z" respectively + * @note Implicitly calls clearXYProperty(), clearXYZProperty() + * @throws InvalidEnvProperty If the variable is not type float[1] + */ + void setXProperty(const std::string &var_name); + void setYProperty(const std::string &var_name); + void setZProperty(const std::string &var_name); + /** + * Set the name of the array variable (length 2) representing the agents x/y location coordinates + * @param var_name Name of the agent variable + * @note Implicitly calls clearXProperty(), clearYProperty(), clearZProperty(),clearXYZProperty() + * @throws InvalidEnvProperty If the variable is not type float[2] + */ + void setXYProperty(const std::string &var_name); + /** + * Set the name of the array variable (length 3) representing the agents x/y/z location coordinates + * @param var_name Name of the agent variable + * @note Implicitly calls clearXProperty(), clearYProperty(), clearZProperty(),clearXYProperty() + * @throws InvalidEnvProperty If the variable is not type float[3] + */ + void setXYZProperty(const std::string &var_name); + /** + * Set the colour the graph will be rendered + * @param cf The colour to be used, default white + */ + void setColor(const Color& cf); + + private: + /** + * Pointer to data struct + */ + std::shared_ptr data; +}; +} // namespace visualiser +} // namespace flamegpu + +#endif // FLAMEGPU_VISUALISATION +#endif // INCLUDE_FLAMEGPU_VISUALISER_ENVIRONMENTGRAPHVIS_H_ diff --git a/include/flamegpu/visualiser/LineVis.h b/include/flamegpu/visualiser/LineVis.h index 06892ed0b..c7dbe794b 100644 --- a/include/flamegpu/visualiser/LineVis.h +++ b/include/flamegpu/visualiser/LineVis.h @@ -40,6 +40,10 @@ class LineVis { * @note Y is considered the vertical axis */ void addVertex(float x, float y, float z = 0.0f); + /** + * Remove all sketch data to start drawing a replacement + */ + void clear(); private: /** diff --git a/include/flamegpu/visualiser/ModelVis.h b/include/flamegpu/visualiser/ModelVis.h index 4a5d4bd94..0defaa479 100644 --- a/include/flamegpu/visualiser/ModelVis.h +++ b/include/flamegpu/visualiser/ModelVis.h @@ -10,6 +10,7 @@ // @todo - All vis headers should live in the vis repo. #include "flamegpu/visualiser/AgentVis.h" +#include "flamegpu/visualiser/EnvironmentGraphVis.h" #include "flamegpu/visualiser/StaticModelVis.h" #include "flamegpu/visualiser/LineVis.h" #include "flamegpu/visualiser/PanelVis.h" @@ -33,6 +34,12 @@ struct ModelVisData { * > On resize, also update textures */ explicit ModelVisData(const CUDASimulation& model/*TBD*/); + /** + * Pass the vis shared pointer to directed graphs being visualised so they can trigger updates + * @param vis pointer to this + * @param map the map of environment directed graph cuda buffers + */ + void hookVis(std::shared_ptr &vis, std::unordered_map> &map); /** * Main struct of visualisation configuration options for the model */ @@ -46,6 +53,10 @@ struct ModelVisData { * Per agent, visualisation configuration options */ std::unordered_map> agents; + /** + * Per environment direct graph, visualisation configuration options + */ + std::unordered_map> graphs; /** * Reference back to the model to be visualised */ @@ -75,6 +86,14 @@ struct ModelVisData { * Random seed has changed */ void updateRandomSeed(); + /** + * Rebuild all environment graph sketches + */ + void buildEnvGraphs(); + /** + * Rebuild a specific environment graph sketch + */ + void rebuildEnvGraph(const std::string& graph_name); }; /** @@ -110,6 +129,17 @@ class ModelVis { * @see addAgent(const std::string&) */ AgentVis Agent(const std::string &agent_name); + /** + * Select a graph to be rendered + * @param graph_name The name of the environment directed graph to visualise + * @return A handle to configure the visualisation of the specified graph + */ + EnvironmentGraphVis addGraph(const std::string &graph_name); + /** + * Returns the configuration handler if the environment directed graph has been marked for visualisation + * @see addGraph(const std::string&) + */ + EnvironmentGraphVis Graph(const std::string& graph_name); /** * Set the title for the visualisation window * This value defaults to the model's name diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d7a791166..ae23febef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -394,6 +394,7 @@ SET(SRC_INCLUDE_VISUALISER ${FLAMEGPU_ROOT}/include/flamegpu/visualiser/ModelVis.h ${FLAMEGPU_ROOT}/include/flamegpu/visualiser/AgentVis.h ${FLAMEGPU_ROOT}/include/flamegpu/visualiser/AgentStateVis.h + ${FLAMEGPU_ROOT}/include/flamegpu/visualiser/EnvironmentGraphVis.h ${FLAMEGPU_ROOT}/include/flamegpu/visualiser/StaticModelVis.h ${FLAMEGPU_ROOT}/include/flamegpu/visualiser/LineVis.h ${FLAMEGPU_ROOT}/include/flamegpu/visualiser/PanelVis.h @@ -411,6 +412,7 @@ set(SRC_FLAMEGPU_VISUALISER ${FLAMEGPU_ROOT}/src/flamegpu/visualiser/ModelVis.cpp ${FLAMEGPU_ROOT}/src/flamegpu/visualiser/AgentVis.cpp ${FLAMEGPU_ROOT}/src/flamegpu/visualiser/AgentStateVis.cpp + ${FLAMEGPU_ROOT}/src/flamegpu/visualiser/EnvironmentGraphVis.cpp ${FLAMEGPU_ROOT}/src/flamegpu/visualiser/StaticModelVis.cpp ${FLAMEGPU_ROOT}/src/flamegpu/visualiser/LineVis.cpp ${FLAMEGPU_ROOT}/src/flamegpu/visualiser/PanelVis.cpp diff --git a/src/flamegpu/simulation/CUDASimulation.cu b/src/flamegpu/simulation/CUDASimulation.cu index 56baaa58a..480640ac2 100644 --- a/src/flamegpu/simulation/CUDASimulation.cu +++ b/src/flamegpu/simulation/CUDASimulation.cu @@ -1542,6 +1542,12 @@ void CUDASimulation::applyConfig_derived() { // We init Random through submodel hierarchy after singletons reseed(getSimulationConfig().random_seed); + +#ifdef FLAMEGPU_VISUALISATION + if (visualisation) { + visualisation->hookVis(visualisation, directed_graph_map); + } +#endif } void CUDASimulation::reseed(const uint64_t seed) { @@ -1709,8 +1715,9 @@ const CUDASimulation::Config &CUDASimulation::getCUDAConfig() const { } #ifdef FLAMEGPU_VISUALISATION visualiser::ModelVis CUDASimulation::getVisualisation() { - if (!visualisation) + if (!visualisation) { visualisation = std::make_shared(*this); + } return visualiser::ModelVis(visualisation, isSWIG); } #endif diff --git a/src/flamegpu/simulation/detail/CUDAEnvironmentDirectedGraphBuffers.cu b/src/flamegpu/simulation/detail/CUDAEnvironmentDirectedGraphBuffers.cu index 1d9f64b9f..73af3a0fc 100644 --- a/src/flamegpu/simulation/detail/CUDAEnvironmentDirectedGraphBuffers.cu +++ b/src/flamegpu/simulation/detail/CUDAEnvironmentDirectedGraphBuffers.cu @@ -7,6 +7,9 @@ #include "flamegpu/simulation/detail/CUDAScatter.cuh" #include "flamegpu/runtime/detail/curve/HostCurve.cuh" #include "flamegpu/detail/cuda.cuh" +#ifdef FLAMEGPU_VISUALISATION +#include "flamegpu/visualiser/ModelVis.h" +#endif #ifdef _MSC_VER #pragma warning(push, 1) #pragma warning(disable : 4706 4834) @@ -18,6 +21,8 @@ #endif // __NVCC_DIAG_PRAGMA_SUPPORT__ #include +#include "flamegpu/visualiser/FLAMEGPU_Visualisation.h" + #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ #pragma nv_diag_default 1719 #else @@ -379,6 +384,7 @@ __global__ void translateSrcDest(id_t *edgeSrcDest, unsigned int *idMap, const u } } void CUDAEnvironmentDirectedGraphBuffers::syncDevice_async(detail::CUDAScatter& scatter, const unsigned int streamID, const cudaStream_t stream) { + bool has_changed = false; // Copy variable buffers to device if (vertex_count) { for (auto& v : graph_description.vertexProperties) { @@ -386,6 +392,7 @@ void CUDAEnvironmentDirectedGraphBuffers::syncDevice_async(detail::CUDAScatter& if (vb.ready == Buffer::Host) { gpuErrchk(cudaMemcpyAsync(vb.d_ptr, vb.h_ptr, vertex_count * v.second.type_size * v.second.elements, cudaMemcpyHostToDevice, stream)); vb.ready = Buffer::Both; + has_changed = true; } } } @@ -395,6 +402,7 @@ void CUDAEnvironmentDirectedGraphBuffers::syncDevice_async(detail::CUDAScatter& if (eb.ready == Buffer::Host) { gpuErrchk(cudaMemcpyAsync(eb.d_ptr, eb.h_ptr, edge_count * e.second.type_size * e.second.elements, cudaMemcpyHostToDevice, stream)); eb.ready = Buffer::Both; + has_changed = true; } } } @@ -571,7 +579,18 @@ void CUDAEnvironmentDirectedGraphBuffers::syncDevice_async(detail::CUDAScatter& } } requires_rebuild = false; + has_changed = true; + } +#ifdef FLAMEGPU_VISUALISATION + if (has_changed) { + if (auto vis = visualisation.lock()) { + vis->visualiser->lockDynamicLinesMutex(); + vis->rebuildEnvGraph(graph_description.name); + vis->visualiser->updateDynamicLine(std::string("graph_") + graph_description.name); + vis->visualiser->releaseDynamicLinesMutex(); + } } +#endif } void CUDAEnvironmentDirectedGraphBuffers::Buffer::updateHostBuffer(size_type edge_count, cudaStream_t stream) const { diff --git a/src/flamegpu/visualiser/EnvironmentGraphVis.cpp b/src/flamegpu/visualiser/EnvironmentGraphVis.cpp new file mode 100644 index 000000000..4e2cc9743 --- /dev/null +++ b/src/flamegpu/visualiser/EnvironmentGraphVis.cpp @@ -0,0 +1,180 @@ +#include "flamegpu/visualiser/EnvironmentGraphVis.h" + + +#include "flamegpu/simulation/detail/CUDAEnvironmentDirectedGraphBuffers.cuh" +#include "flamegpu/visualiser/LineVis.h" +#include "flamegpu/visualiser/config/LineConfig.h" + +namespace flamegpu { +namespace visualiser { + +EnvironmentGraphVisData::EnvironmentGraphVisData(std::shared_ptr _graphData, std::shared_ptr_lines) + : color(Stock::Colors::WHITE) + , graphData(std::move(_graphData)) + , lines(std::move(_lines)) { + const auto &x_it = graphData->vertexProperties.find("x"); + if (x_it != graphData->vertexProperties.end() && + x_it->second.type == std::type_index(typeid(float)) && + x_it->second.elements == 1) { + x_varName = "x"; + } + const auto &y_it = graphData->vertexProperties.find("y"); + if (y_it != graphData->vertexProperties.end() && + y_it->second.type == std::type_index(typeid(float)) && + y_it->second.elements == 1) { + y_varName = "y"; + } + const auto &z_it = graphData->vertexProperties.find("z"); + if (z_it != graphData->vertexProperties.end() && + z_it->second.type == std::type_index(typeid(float)) && + z_it->second.elements == 1) { + z_varName = "z"; + } +} +void EnvironmentGraphVisData::constructGraph(const std::shared_ptr &graph) { + // Can't construct prior to initialisation + if (!graph->getVertexCount() && !graph->getEdgeCount()) + return; + // Retrieve buffer data + const float *x = nullptr; + const float *y = nullptr; + const float *z = nullptr; + int x_stride = 1; + int y_stride = 1; + int z_stride = 1; + if (!x_varName.empty() && !y_varName.empty()) { + size_type ONE = 1; + x = graph->getVertexPropertyBuffer(x_varName, ONE, nullptr); + y = graph->getVertexPropertyBuffer(y_varName, ONE, nullptr); + if (!z_varName.empty()) + z = graph->getVertexPropertyBuffer(z_varName, ONE, nullptr); + } else if (!xy_varName.empty()) { + size_type TWO = 1; + const float* xy = graph->getVertexPropertyBuffer(xy_varName, TWO, nullptr); + x = xy; + y = x+1; + x_stride = 2; + y_stride = 2; + } else if (!xyz_varName.empty()) { + size_type THREE = 1; + const float* xyz = graph->getVertexPropertyBuffer(xyz_varName, THREE, nullptr); + x = xyz; + y = x + 1; + z = y + 1; + x_stride = 3; + y_stride = 3; + z_stride = 3; + } else { + throw exception::InvalidOperation("Unable to construct graph visualisation, appropriate vertex variables not set, in EnvironmentGraphVisData::constructGraph()"); + } + // Update line sketch + auto ln = LineVis(lines, color.r, color.g, color.b, color.a); + ln.clear(); + // Iterate edges + size_type TWO = 2; + const id_t *edges = graph->getEdgePropertyBuffer(GRAPH_SOURCE_DEST_VARIABLE_NAME, TWO, nullptr); + for (unsigned int i = 0; i < graph->getEdgeCount(); ++i) { + // Sketch destination vertex + const size_type dest_i = graph->getVertexIndex(edges[i * 2]); + if (z) { + ln.addVertex(x[dest_i * x_stride], y[dest_i * y_stride], z[dest_i * z_stride]); + } else { + ln.addVertex(x[dest_i * x_stride], y[dest_i * y_stride]); + } + // Sketch source vertex + const size_type src_i = graph->getVertexIndex(edges[(i * 2) + 1]); + if (z) { + ln.addVertex(x[src_i * x_stride], y[src_i * y_stride], z[src_i * z_stride]); + } else { + ln.addVertex(x[src_i * x_stride], y[src_i * y_stride]); + } + } +} + +EnvironmentGraphVis::EnvironmentGraphVis(std::shared_ptr _data) + : data(std::move(_data)) { } + +void EnvironmentGraphVis::setXProperty(const std::string &var_name) { + auto it = data->graphData->vertexProperties.find(var_name); + if (it == data->graphData->vertexProperties.end()) { + THROW exception::InvalidEnvProperty("Property '%s' was not found within graph '%s', " + "in EnvironmentGraphVis::setXProperty()\n", + var_name.c_str(), data->graphData->name.c_str()); + } else if (it->second.type != std::type_index(typeid(float)) || it->second.elements != 1) { + THROW exception::InvalidEnvProperty("Visualisation position x property must be type float[1], graph '%s' property '%s' is type %s[%u], " + "in EnvironmentGraphVis::setXProperty()\n", + data->graphData->name.c_str(), var_name.c_str(), it->second.type.name(), it->second.elements); + } + data->xy_varName.clear(); + data->xyz_varName.clear(); + data->x_varName = var_name; +} +void EnvironmentGraphVis::setYProperty(const std::string &var_name) { + auto it = data->graphData->vertexProperties.find(var_name); + if (it == data->graphData->vertexProperties.end()) { + THROW exception::InvalidEnvProperty("Property '%s' was not found within graph '%s', " + "in EnvironmentGraphVis::setYProperty()\n", + var_name.c_str(), data->graphData->name.c_str()); + } else if (it->second.type != std::type_index(typeid(float)) || it->second.elements != 1) { + THROW exception::InvalidEnvProperty("Visualisation position y property must be type float[1], graph '%s' property '%s' is type %s[%u], " + "in EnvironmentGraphVis::setYProperty()\n", + data->graphData->name.c_str(), var_name.c_str(), it->second.type.name(), it->second.elements); + } + data->xy_varName.clear(); + data->xyz_varName.clear(); + data->y_varName = var_name; +} +void EnvironmentGraphVis::setZProperty(const std::string &var_name) { + auto it = data->graphData->vertexProperties.find(var_name); + if (it == data->graphData->vertexProperties.end()) { + THROW exception::InvalidEnvProperty("Property '%s' was not found within graph '%s', " + "in EnvironmentGraphVis::setZProperty()\n", + var_name.c_str(), data->graphData->name.c_str()); + } else if (it->second.type != std::type_index(typeid(float)) || it->second.elements != 1) { + THROW exception::InvalidEnvProperty("Visualisation position z property must be type float[1], graph '%s' property '%s' is type %s[%u], " + "in EnvironmentGraphVis::setZProperty()\n", + data->graphData->name.c_str(), var_name.c_str(), it->second.type.name(), it->second.elements); + } + data->xy_varName.clear(); + data->xyz_varName.clear(); + data->z_varName = var_name; +} +void EnvironmentGraphVis::setXYProperty(const std::string& var_name) { + auto it = data->graphData->vertexProperties.find(var_name); + if (it == data->graphData->vertexProperties.end()) { + THROW exception::InvalidEnvProperty("Property '%s' was not found within graph '%s', " + "in EnvironmentGraphVis::setXYProperty()\n", + var_name.c_str(), data->graphData->name.c_str()); + } else if (it->second.type != std::type_index(typeid(float)) || it->second.elements != 2) { + THROW exception::InvalidEnvProperty("Visualisation position x property must be type float[2], graph '%s' property '%s' is type %s[%u], " + "in EnvironmentGraphVis::setXYProperty()\n", + data->graphData->name.c_str(), var_name.c_str(), it->second.type.name(), it->second.elements); + } + data->x_varName.clear(); + data->y_varName.clear(); + data->z_varName.clear(); + data->xyz_varName.clear(); + data->xy_varName = var_name; +} +void EnvironmentGraphVis::setXYZProperty(const std::string& var_name) { + auto it = data->graphData->vertexProperties.find(var_name); + if (it == data->graphData->vertexProperties.end()) { + THROW exception::InvalidEnvProperty("Property '%s' was not found within graph '%s', " + "in EnvironmentGraphVis::setXYZProperty()\n", + var_name.c_str(), data->graphData->name.c_str()); + } else if (it->second.type != std::type_index(typeid(float)) || it->second.elements != 3) { + THROW exception::InvalidEnvProperty("Visualisation position x property must be type float[3], graph '%s' property '%s' is type %s[%u], " + "in EnvironmentGraphVis::setXYZProperty()\n", + data->graphData->name.c_str(), var_name.c_str(), it->second.type.name(), it->second.elements); + } + data->x_varName.clear(); + data->y_varName.clear(); + data->y_varName.clear(); + data->xy_varName.clear(); + data->xyz_varName = var_name; +} +void EnvironmentGraphVis::setColor(const Color& color) { + data->color = color; +} +} // namespace visualiser +} // namespace flamegpu diff --git a/src/flamegpu/visualiser/LineVis.cpp b/src/flamegpu/visualiser/LineVis.cpp index 841a507c1..94e90d5e7 100644 --- a/src/flamegpu/visualiser/LineVis.cpp +++ b/src/flamegpu/visualiser/LineVis.cpp @@ -28,5 +28,10 @@ void LineVis::addVertex(float x, float y, float z) { l->colors.push_back(currentColor[3]); } +void LineVis::clear() { + l->vertices.clear(); + l->colors.clear(); +} + } // namespace visualiser } // namespace flamegpu diff --git a/src/flamegpu/visualiser/ModelVis.cpp b/src/flamegpu/visualiser/ModelVis.cpp index 0ac9a54ca..99fe2fd8b 100644 --- a/src/flamegpu/visualiser/ModelVis.cpp +++ b/src/flamegpu/visualiser/ModelVis.cpp @@ -18,6 +18,14 @@ ModelVisData::ModelVisData(const flamegpu::CUDASimulation &_model) , model(_model) , modelData(_model.getModelDescription()) { } +void ModelVisData::hookVis(std::shared_ptr& vis, std::unordered_map> &map) { + for (auto [name, graph] : graphs) { + auto &graph_buffs = map.at(name); + graph_buffs->setVisualisation(vis); + graph->constructGraph(graph_buffs); + vis->rebuildEnvGraph(name); + } +} void ModelVisData::registerEnvProperties() { if (model.singletons && !env_registered) { char* const host_env_origin = const_cast(static_cast(model.singletons->environment->getHostBuffer())); @@ -70,6 +78,14 @@ void ModelVisData::updateRandomSeed() { visualiser->setRandomSeed(model.getSimulationConfig().random_seed); } } +void ModelVisData::buildEnvGraphs() { + for (auto [name, graph] : graphs) { + graph->constructGraph(model.directed_graph_map.at(name)); + } +} +void ModelVisData::rebuildEnvGraph(const std::string &graph_name) { + graphs.at(graph_name)->constructGraph(model.directed_graph_map.at(graph_name)); +} ModelVis::ModelVis(std::shared_ptr _data, bool _isSWIG) : isSWIG(_isSWIG) @@ -100,7 +116,7 @@ AgentVis ModelVis::addAgent(const std::string &agent_name) { AgentVis ModelVis::Agent(const std::string &agent_name) { // If agent exists if (data->modelData.agents.find(agent_name) != data->modelData.agents.end()) { - // If agent is not already in vis map + // If agent is already in vis map auto visAgent = data->agents.find(agent_name); if (visAgent != data->agents.end()) { // Create new vis agent @@ -114,7 +130,41 @@ AgentVis ModelVis::Agent(const std::string &agent_name) { "in ModelVis::Agent()\n", agent_name.c_str()); } - +EnvironmentGraphVis ModelVis::addGraph(const std::string& graph_name) { + // If graph exists + auto graph_it = data->modelData.environment->directed_graphs.find(graph_name); + if (graph_it != data->modelData.environment->directed_graphs.end()) { + // If graph is not already in vis map + auto visGraph = data->graphs.find(graph_name); + if (visGraph == data->graphs.end()) { + // Create a line config for the graph + auto m = std::make_shared(LineConfig::Type::Lines); + data->modelCfg.dynamic_lines.insert({std::string("graph_") + graph_name, m}); + // Create new vis graph + return EnvironmentGraphVis(data->graphs.emplace(graph_name, std::make_shared(graph_it->second, m)).first->second); + } + return EnvironmentGraphVis(visGraph->second); + } + THROW exception::InvalidEnvGraph("Environment direct graph name '%s' was not found within the model description hierarchy, " + "in ModelVis::addGraph()\n", + graph_name.c_str()); +} +EnvironmentGraphVis ModelVis::Graph(const std::string& graph_name) { + // If graph exists + if (data->modelData.environment->directed_graphs.find(graph_name) != data->modelData.environment->directed_graphs.end()) { + // If graph is already in vis map + auto visGraph = data->graphs.find(graph_name); + if (visGraph != data->graphs.end()) { + return EnvironmentGraphVis(visGraph->second); + } + THROW exception::InvalidEnvGraph("Environment direct graph name '%s' has not been marked for visualisation, ModelVis::addGraph() must be called first, " + "in ModelVis::Agent()\n", + graph_name.c_str()); + } + THROW exception::InvalidEnvGraph("Environment direct graph name '%s' was not found within the model description hierarchy, " + "in ModelVis::addGraph()\n", + graph_name.c_str()); +} // Below methods are related to executing the visualiser void ModelVis::activate() { // Only execute if background thread is not active diff --git a/swig/python/flamegpu.i b/swig/python/flamegpu.i index d2eae31fc..b9d99dacf 100644 --- a/swig/python/flamegpu.i +++ b/swig/python/flamegpu.i @@ -1162,6 +1162,8 @@ TEMPLATE_VARIABLE_INSTANTIATE_INTS(poisson, flamegpu::HostRandom::poisson) %ignore flamegpu::visualiser::Palette::colors; // This is protected, i've no idea why SWIG is trying to wrap it // Mark PanelVis as a class where assignment operator is not supported %feature("valuewrapper") flamegpu::visualiser::PanelVis; + // Mark EnvironmentGraphVis as a class where default construction is not supported + %feature("valuewrapper") flamegpu::visualiser::EnvironmentGraphVis; // Rename directives. These go before any %includes // ----------------- // Director features. These go before the %includes. @@ -1174,6 +1176,7 @@ TEMPLATE_VARIABLE_INSTANTIATE_INTS(poisson, flamegpu::HostRandom::poisson) %include "flamegpu/visualiser/StaticModelVis.h" %include "flamegpu/visualiser/AgentStateVis.h" %include "flamegpu/visualiser/AgentVis.h" + %include "flamegpu/visualiser/EnvironmentGraphVis.h" %include "flamegpu/visualiser/LineVis.h" %include "flamegpu/visualiser/PanelVis.h" %include "flamegpu/visualiser/ModelVis.h"