diff --git a/include/flamegpu/visualiser/FLAMEGPU_Visualisation.h b/include/flamegpu/visualiser/FLAMEGPU_Visualisation.h index 5ef476a..b44595a 100644 --- a/include/flamegpu/visualiser/FLAMEGPU_Visualisation.h +++ b/include/flamegpu/visualiser/FLAMEGPU_Visualisation.h @@ -30,12 +30,23 @@ class FLAMEGPU_Visualisation { void requestBufferResizes(const std::string &agent_name, const std::string &state_name, const unsigned int buffLen, bool force) { requestBufferResizes(agent_name.c_str(), state_name.c_str(), buffLen, force); } + /** + * Main render mutex used to prevent race conditions rendering agents + */ void lockMutex(); void releaseMutex(); + /** + * Dynamic line mutex ensures graphs are fully updated before synced to render + */ + void lockDynamicLinesMutex(); + void releaseDynamicLinesMutex(); void updateAgentStateBuffer(const std::string &agent_name, const std::string &state_name, const unsigned int buffLen, const std::map& core_tex_buffers, const std::multimap& tex_buffers) { updateAgentStateBuffer(agent_name.c_str(), state_name.c_str(), buffLen, core_tex_buffers, tex_buffers); } + void updateDynamicLine(const std::string &graph_name) { + updateDynamicLine(graph_name.c_str()); + } /** * Provide an environment property, so it can be displayed */ @@ -81,8 +92,11 @@ class FLAMEGPU_Visualisation { void requestBufferResizes(const char *agent_name, const char *state_name, const unsigned int buffLen, bool force); void updateAgentStateBuffer(const char *agent_name, const char *state_name, const unsigned int buffLen, const std::map& core_tex_buffers, const std::multimap& tex_buffers); + void updateDynamicLine(const char* graph_name); + Visualiser *vis = nullptr; LockHolder *lock = nullptr; + LockHolder *dynamic_lines_lock = nullptr; /** * If non-0, limits the number of simulation steps per second, by blocking the return of releaseMutex * Blocking whilst the mutex was held, would block visualisation updates as that also uses the mutex diff --git a/include/flamegpu/visualiser/config/ModelConfig.h b/include/flamegpu/visualiser/config/ModelConfig.h index e100a05..d273ce3 100644 --- a/include/flamegpu/visualiser/config/ModelConfig.h +++ b/include/flamegpu/visualiser/config/ModelConfig.h @@ -90,6 +90,10 @@ struct ModelConfig { * @note Defaults to (0,0,0) */ float cameraTarget[3]; + /** + * The initial camera roll angle in radians + */ + float cameraRoll; /** * The movement speed of the camera in units per millisecond, and the shift key multiplier * When shift is pressed the movement speed is multiplied by this value @@ -125,6 +129,10 @@ struct ModelConfig { * Store of user defined line renderings */ std::list> lines; + /** + * Store of user defined graph renderings + */ + std::map> dynamic_lines; /** * Store of user defined UI panels */ diff --git a/src/flamegpu/visualiser/Draw.cpp b/src/flamegpu/visualiser/Draw.cpp index cb9bfb8..aca0e0f 100644 --- a/src/flamegpu/visualiser/Draw.cpp +++ b/src/flamegpu/visualiser/Draw.cpp @@ -91,6 +91,9 @@ void Draw::save(bool replaceExisting) { requiredLength += tState.count; stateDirectory.insert({ tName, std::move(tState) }); } +bool Draw::has(const std::string &name) { + return stateDirectory.find(name) != stateDirectory.end(); +} Draw::State Draw::_save(bool isTemporary) { if (tType == Type::Lines && tVertices.size() % 2 != 0) { THROW VisAssert("Draw::_save(): Line drawings require an even number of vertices.\n"); diff --git a/src/flamegpu/visualiser/Draw.h b/src/flamegpu/visualiser/Draw.h index 5de22e5..6f284f7 100644 --- a/src/flamegpu/visualiser/Draw.h +++ b/src/flamegpu/visualiser/Draw.h @@ -68,6 +68,10 @@ class Draw : Renderable { * @throw Throws a runtime exception if called whilst the draw state is not open */ void save(bool replaceExisting = false); + /** + * Check whether a drawing with the name exists. + **/ + bool has(const std::string &name); /** * Renders a saved drawstate * @param name The name of the draw state to render diff --git a/src/flamegpu/visualiser/FLAMEGPU_Visualisation.cpp b/src/flamegpu/visualiser/FLAMEGPU_Visualisation.cpp index 3009552..cfdf9bc 100644 --- a/src/flamegpu/visualiser/FLAMEGPU_Visualisation.cpp +++ b/src/flamegpu/visualiser/FLAMEGPU_Visualisation.cpp @@ -25,6 +25,8 @@ FLAMEGPU_Visualisation::~FLAMEGPU_Visualisation() { delete vis; if (lock) delete lock; + if (dynamic_lines_lock) + delete dynamic_lines_lock; } void FLAMEGPU_Visualisation::addAgentState(const char *agent_name, const char *state_name, const AgentStateConfig &vc, const std::map& core_tex_buffers, const std::multimap& tex_buffers) { @@ -40,6 +42,9 @@ void FLAMEGPU_Visualisation::updateAgentStateBuffer(const char *agent_name, cons void FLAMEGPU_Visualisation::registerEnvironmentProperty(const std::string& property_name, void* ptr, const std::type_index type, const unsigned int elements, const bool is_const) { vis->registerEnvironmentProperty(property_name, ptr, type, elements, is_const); } +void FLAMEGPU_Visualisation::updateDynamicLine(const char *graph_name) { + vis->updateDynamicLine(graph_name); +} void FLAMEGPU_Visualisation::setStepCount(const unsigned int stepCount) { vis->setStepCount(stepCount); } @@ -89,6 +94,15 @@ void FLAMEGPU_Visualisation::releaseMutex() { } } } +void FLAMEGPU_Visualisation::lockDynamicLinesMutex() { + dynamic_lines_lock = new LockHolder(vis->getDynamicLineMutex()); +} +void FLAMEGPU_Visualisation::releaseDynamicLinesMutex() { + if (dynamic_lines_lock) { + delete dynamic_lines_lock; + dynamic_lines_lock = nullptr; + } +} } // namespace visualiser } // namespace flamegpu diff --git a/src/flamegpu/visualiser/Visualiser.cpp b/src/flamegpu/visualiser/Visualiser.cpp index f3c8897..016e520 100644 --- a/src/flamegpu/visualiser/Visualiser.cpp +++ b/src/flamegpu/visualiser/Visualiser.cpp @@ -104,10 +104,28 @@ Visualiser::RenderInfo::RenderInfo(const AgentStateConfig& vc, entity->loadKeyFrameModel(vc.model_pathB); } } - +void addLine(std::shared_ptr &lines, const std::shared_ptr &line, const std::string &name, bool replace) { + // Check it's valid + if (line->lineType == LineConfig::Type::Polyline) { + if (line->vertices.size() < 6 || line->vertices.size() % 3 != 0 || line->colors.size() % 4 != 0 || (line->colors.size() / 4) * 3 != line->vertices.size()) { + THROW SketchError("Polyline sketch contains invalid number of vertices (%d/3) or colours (%d/4).\n", line->vertices.size(), line->colors.size()); + } + } else if (line->lineType == LineConfig::Type::Lines) { + if (line->vertices.size() < 6 || line->vertices.size() % 6 != 0 || line->colors.size() % 4 != 0 || (line->colors.size() / 4) * 3 != line->vertices.size()) { + THROW SketchError("Lines sketch contains invalid number of vertices (%d/3) or colours (%d/4).\n", line->vertices.size(), line->colors.size()); + } + } + // Convert to Draw + lines->begin(line->lineType == LineConfig::Type::Polyline ? Draw::Type::Polyline : Draw::Type::Lines, name); + for (size_t i = 0; i < line->vertices.size() / 3; ++i) { + lines->color(*reinterpret_cast(&line->colors[i * 4])); + lines->vertex(*reinterpret_cast(&line->vertices[i * 3])); + } + lines->save(replace); +} Visualiser::Visualiser(const ModelConfig& modelcfg) : hud(std::make_shared(modelcfg.windowDimensions[0], modelcfg.windowDimensions[1])) - , camera(std::make_shared(*reinterpret_cast(&modelcfg.cameraLocation[0]), *reinterpret_cast(&modelcfg.cameraTarget[0]))) + , camera(std::make_shared(*reinterpret_cast(&modelcfg.cameraLocation[0]), *reinterpret_cast(&modelcfg.cameraTarget[0]), modelcfg.cameraRoll)) , isInitialised(false) , continueRender(false) , buffersAllocated(false) @@ -149,9 +167,12 @@ Visualiser::Visualiser(const ModelConfig& modelcfg) imguiPanel = std::make_shared(modelcfg.panels, *this); hud->add(imguiPanel, HUD::AnchorV::North, HUD::AnchorH::West, glm::ivec2(0, 0), INT_MAX-2); } - lines = std::make_shared(); - lines->setViewMatPtr(camera->getViewMatPtr()); - lines->setProjectionMatPtr(&this->projMat); + lines_static = std::make_shared(); + lines_static->setViewMatPtr(camera->getViewMatPtr()); + lines_static->setProjectionMatPtr(&this->projMat); + lines_dynamic = std::make_shared(); + lines_dynamic->setViewMatPtr(camera->getViewMatPtr()); + lines_dynamic->setProjectionMatPtr(&this->projMat); // Process static models for (auto &sm : modelcfg.staticModels) { std::shared_ptr entity; @@ -186,23 +207,7 @@ Visualiser::Visualiser(const ModelConfig& modelcfg) } // Process lines for (auto &line : modelcfg.lines) { - // Check it's valid - if (line->lineType == LineConfig::Type::Polyline) { - if (line->vertices.size() < 6 || line->vertices.size() % 3 != 0 || line->colors.size() % 4 != 0 || (line->colors.size() / 4) * 3 != line->vertices.size()) { - THROW SketchError("Polyline sketch contains invalid number of vertices (%d/3) or colours (%d/4).\n", line->vertices.size(), line->colors.size()); - } - } else if (line->lineType == LineConfig::Type::Lines) { - if (line->vertices.size() < 6 || line->vertices.size() % 6 != 0 || line->colors.size() % 4 != 0 || (line->colors.size() / 4) * 3 != line->vertices.size()) { - THROW SketchError("Lines sketch contains invalid number of vertices (%d/3) or colours (%d/4).\n", line->vertices.size(), line->colors.size()); - } - } - // Convert to Draw - lines->begin(line->lineType == LineConfig::Type::Polyline ? Draw::Type::Polyline : Draw::Type::Lines, std::to_string(totalLines++)); - for (size_t i = 0; i < line->vertices.size() / 3; ++i) { - lines->color(*reinterpret_cast(&line->colors[i * 4])); - lines->vertex(*reinterpret_cast(&line->vertices[i * 3])); - } - lines->save(); + addLine(lines_static, line, std::to_string(totalLines++), false); } // Default lighting, single point light attached to camera // Maybe in future let user specify lights instead of this @@ -445,12 +450,23 @@ void Visualiser::render() { hud->remove(splashScreen); splashScreen.reset(); } + // Update dynamic lines if they have changed + { + auto lock = std::lock_guard(lines_dynamic_mutex); + for (auto &name : lines_dynamic_updates) + addLine(lines_dynamic, modelConfig.dynamic_lines.at(name), name, true); + lines_dynamic_updates.clear(); + } // Render lines last, as they may contain alpha if (renderLines) { GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); for (unsigned int i = 0; i < totalLines; ++i) - lines->render(std::to_string(i)); + lines_static->render(std::to_string(i)); + for (const auto &[name, _] : modelConfig.dynamic_lines) + // Dynamic lines may begin uninitialised + if (lines_dynamic->has(name)) + lines_dynamic->render(name); GL_CALL(glDisable(GL_BLEND)); } GL_CALL(glViewport(0, 0, windowDims.x, windowDims.y)); @@ -804,7 +820,8 @@ void Visualiser::deallocateGLObjects() { for (auto &as : agentStates) { as.second.entity.reset(); } - this->lines.reset(); + this->lines_static.reset(); + this->lines_dynamic.reset(); render_buffer.reset(); screenshot_buffer.reset(); @@ -896,8 +913,10 @@ void Visualiser::handleKeypress(SDL_Keycode keycode, int /*x*/, int /*y*/) { break; case SDLK_F5: // Reload all shaders - if (this->lines) - this->lines->reload(); + if (this->lines_static) + this->lines_static->reload(); + if (this->lines_dynamic) + this->lines_dynamic->reload(); for (auto& as : this->agentStates) as.second.entity->reload(); for (auto& sm : this->staticModels) @@ -1241,6 +1260,10 @@ void Visualiser::setWindowIcon() { if (surface) SDL_SetWindowIcon(window, surface.get()); } +void Visualiser::updateDynamicLine(const std::string& name) { + // Store a list of dynamic line updates, as they must occur in render thread + lines_dynamic_updates.insert(name); +} } // namespace visualiser } // namespace flamegpu diff --git a/src/flamegpu/visualiser/Visualiser.h b/src/flamegpu/visualiser/Visualiser.h index 5b4602e..b73eb16 100644 --- a/src/flamegpu/visualiser/Visualiser.h +++ b/src/flamegpu/visualiser/Visualiser.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #define GLM_FORCE_NO_CTOR_INIT #include @@ -67,7 +68,7 @@ class Visualiser : public ViewportExt { public: explicit Visualiser(const ModelConfig &modelcfg); - ~Visualiser(); + ~Visualiser() override; /** * Starts the render loop running */ @@ -128,6 +129,11 @@ class Visualiser : public ViewportExt { * Provide the env_cache ptr for the specified environment property, for visualisation */ void registerEnvironmentProperty(const std::string& property_name, void* ptr, std::type_index type, unsigned int elements, bool is_const); + /** + * Update the dynamic_lines with the corresponding name + * @note getDynamicLineMutex() should be locked before this is called + */ + void updateDynamicLine(const std::string &name); private: void run(); @@ -242,6 +248,11 @@ class Visualiser : public ViewportExt { * @see updateAgentStateBuffer(const std::string &, const std::string &, const unsigned int, float *, float *, float *, float *) */ std::mutex &getRenderBufferMutex() { return render_buffer_mutex; } + /** + * This must be locked when changes to dynamic_lines are performed + * e.g. during graph updates + */ + std::mutex &getDynamicLineMutex() { return lines_dynamic_mutex; } /** * Sets the value to be rendered to the HUD step counter (if enabled) * @param stepCount The step value to be displayed @@ -358,7 +369,9 @@ class Visualiser : public ViewportExt { /** * User defined lines to be rendered */ - std::shared_ptr lines; + std::shared_ptr lines_static, lines_dynamic; + std::set lines_dynamic_updates; + std::mutex lines_dynamic_mutex; /** * Provides a simple default lighting configuration located at the camera using the old fixed function pipeline methods */ diff --git a/src/flamegpu/visualiser/camera/NoClipCamera.cpp b/src/flamegpu/visualiser/camera/NoClipCamera.cpp index 7fc227a..7cde22d 100644 --- a/src/flamegpu/visualiser/camera/NoClipCamera.cpp +++ b/src/flamegpu/visualiser/camera/NoClipCamera.cpp @@ -16,7 +16,7 @@ NoClipCamera::NoClipCamera(const glm::vec3 &eye) : NoClipCamera(eye, glm::vec3(0, 0, 0)) {} // Initialiser list written to remove any references to member variables // Because member variables are initialised via initaliser lists in the order they are declared in the class declaration (rather than the order of the initialiser list) -NoClipCamera::NoClipCamera(const glm::vec3 &eye, const glm::vec3 &target) +NoClipCamera::NoClipCamera(const glm::vec3 &eye, const glm::vec3 &target, const float _roll) : Camera(eye) , pureUp(0.0f, 1.0f, 0.0f) , look(normalize(target - eye)) @@ -27,8 +27,7 @@ NoClipCamera::NoClipCamera(const glm::vec3 &eye, const glm::vec3 &target) // this->look = target - eye; // Look is the direction from eye to target // this->right = cross(look, pureUp); // Right is perpendicular to look and pureUp // this->up = cross(right, look); // Up is perpendicular to right and look - - this->updateViews(); + this->roll(_roll); } NoClipCamera::~NoClipCamera() { } diff --git a/src/flamegpu/visualiser/camera/NoClipCamera.h b/src/flamegpu/visualiser/camera/NoClipCamera.h index e2b540f..515215e 100644 --- a/src/flamegpu/visualiser/camera/NoClipCamera.h +++ b/src/flamegpu/visualiser/camera/NoClipCamera.h @@ -22,8 +22,9 @@ class NoClipCamera : public Camera { * Initialises the camera located at eye directed at target * @param eye The coordinates the camera is located * @param target The coordinates the camera is directed towards + * @param _roll The camera roll in radians */ - NoClipCamera(const glm::vec3 &eye, const glm::vec3 &target); + NoClipCamera(const glm::vec3 &eye, const glm::vec3 &target, float _roll = 0); /** * Default destructor */ diff --git a/src/flamegpu/visualiser/config/ModelConfig.cpp b/src/flamegpu/visualiser/config/ModelConfig.cpp index f50d641..a28d5c2 100644 --- a/src/flamegpu/visualiser/config/ModelConfig.cpp +++ b/src/flamegpu/visualiser/config/ModelConfig.cpp @@ -19,6 +19,7 @@ ModelConfig::ModelConfig(const char* _windowTitle) , fpsColor{1, 1, 1} , cameraLocation{1.5f, 1.5f, 1.5f} , cameraTarget{0, 0, 0} + , cameraRoll(0) , cameraSpeed{0.05f, 5} , nearFarClip{0.05f, 5000} , stepVisible(true) @@ -39,6 +40,7 @@ ModelConfig &ModelConfig::operator=(const ModelConfig &other) { memcpy(fpsColor, other.fpsColor, sizeof(fpsColor)); memcpy(cameraLocation, other.cameraLocation, sizeof(cameraLocation)); memcpy(cameraTarget, other.cameraTarget, sizeof(cameraTarget)); + cameraRoll = other.cameraRoll; memcpy(cameraSpeed, other.cameraSpeed, sizeof(cameraSpeed)); memcpy(nearFarClip, other.nearFarClip, sizeof(nearFarClip)); stepVisible = other.stepVisible; @@ -46,6 +48,7 @@ ModelConfig &ModelConfig::operator=(const ModelConfig &other) { isPython = other.isPython; isOrtho = other.isOrtho; orthoZoom = other.orthoZoom; + dynamic_lines = other.dynamic_lines; // Here because they are probably empty at construction, so addLine() can't be used // staticModels // lines // panels