Skip to content

Commit

Permalink
Implement spring forces on net when importing format where positions …
Browse files Browse the repository at this point in the history
…are not defined #25
  • Loading branch information
Lecrapouille committed Jun 29, 2024
1 parent e89210d commit 2244692
Show file tree
Hide file tree
Showing 15 changed files with 417 additions and 40 deletions.
3 changes: 2 additions & 1 deletion include/TimedPetriNetEditor/PetriNet.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,8 @@ bool convertTo(Net& net, TypeOfNet const type, std::string& error, std::vector<A
//! saved. Should have the .json extension.
//! \return error message in case of failure, else return dummy string.
//-----------------------------------------------------------------------------
std::string loadFromFile(Net& net, std::string const& filepath);
// FIXME remplacer bool& springify par bool const springify
std::string loadFromFile(Net& net, std::string const& filepath, bool& springify);

//-----------------------------------------------------------------------------
//! \brief Save the Petri net in a a file.
Expand Down
25 changes: 23 additions & 2 deletions src/Editor/DearImGui/Editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,16 @@ void Editor::run(std::string const& filepath)
// Load Petri net file if passed with command line
if (!filepath.empty())
{
std::string error = loadFromFile(m_net, filepath);
bool shall_springify;
std::string error = loadFromFile(m_net, filepath, shall_springify);
if (error.empty())
{
m_messages.setInfo("Loaded with success " + filepath);
setSavePath(filepath);
if (shall_springify)
{
springify();
}
}
else
{
Expand All @@ -153,6 +158,7 @@ void Editor::onUpdate(float const dt)
}

m_simulation.step(dt);
m_spring.update();
}

//------------------------------------------------------------------------------
Expand Down Expand Up @@ -1002,7 +1008,7 @@ Transition* Editor::getTransition(ImVec2 const& position)
//------------------------------------------------------------------------------
void Editor::loadNetFile()
{
static Importer importer{"TimedPetriNetEditor", ".json", importFromJSON};
static Importer importer{"TimedPetriNetEditor", ".json", importFromJSON, false};
importNetFrom(importer);
}

Expand Down Expand Up @@ -1039,12 +1045,17 @@ void Editor::importNetFrom(Importer const& importer)
m_messages.setInfo("Loaded with success '" + filepath + "'");
setSavePath(filepath);
m_net.modified = false;
if (importer.springify)
{
springify();
}
}
else
{
m_messages.setError(error);
m_net.clear();
m_net.modified = true;
m_spring.reset();
}
}

Expand All @@ -1055,6 +1066,12 @@ void Editor::importNetFrom(Importer const& importer)
}
}

//--------------------------------------------------------------------------
void Editor::springify()
{
m_spring.reset(m_view.size().x, m_view.size().y, m_net);
}

//--------------------------------------------------------------------------
void Editor::saveNetAs()
{
Expand Down Expand Up @@ -1562,6 +1579,10 @@ void Editor::PetriView::onHandleInput()
{
handleMoveNode();
}
else if (ImGui::IsKeyPressed(KEY_SPRINGIFY_NET))
{
m_editor.springify();
}
// Run the animation of the Petri net
else if (ImGui::IsKeyPressed(KEY_RUN_SIMULATION) ||
ImGui::IsKeyPressed(KEY_RUN_SIMULATION_ALT))
Expand Down
14 changes: 11 additions & 3 deletions src/Editor/DearImGui/Editor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
# include "Net/Simulation.hpp"
# include "Net/Exports/Exports.hpp"
# include "Net/Imports/Imports.hpp"
# include "Utils/ForceDirected.hpp"
# include "Utils/History.hpp"
# include "Utils/Path.hpp"
# include <vector>
Expand Down Expand Up @@ -94,6 +95,7 @@ class Editor: public PetriNetEditor, public Application
void clearNet();
void undo();
void redo();
void springify();

private: // Error logs

Expand Down Expand Up @@ -195,9 +197,12 @@ class Editor: public PetriNetEditor, public Application
// ********************************************************************
struct Canvas
{
ImVec2 corners[2];
ImVec2 size;
ImVec2 origin;
ImVec2 corners[2] = {{0.0f, 0.0f}, {0.0f, 0.0f}};
ImVec2 size{800.0f, 600.0f}; // FIXME: size is undefined while not
// rendered once but when importing nets
// we need to know th windows size which
// is 0x0
ImVec2 origin{0.0f, 0.0f};
ImVec2 scrolling{0.0f, 0.0f};
ImDrawList* draw_list;

Expand Down Expand Up @@ -272,6 +277,9 @@ class Editor: public PetriNetEditor, public Application
//! \brief Single Petri net the editor can edit.
//! \fixme Manage several nets (like done with GEMMA).
Net m_net;
//! Apply spring positive/negative forces on arcs/nodes to unfold imported
//! Petri nets that do not have positions on their nodes.
ForceDirected m_spring;
//! \brief History of modifications of the net.
History m_history;
//! \brief Instance allowing to do timed simulation.
Expand Down
1 change: 1 addition & 0 deletions src/Editor/DearImGui/KeyBindings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
// FIXME: The backend raylib does not support other than US keyboard meaning that
// other keyboard mapping are fucked up.
# define KEY_QUIT_APPLICATION ImGuiKey_Escape
# define KEY_SPRINGIFY_NET ImGuiKey_A
# define KEY_RUN_SIMULATION ImGuiKey_Space
# define KEY_RUN_SIMULATION_ALT ImGuiKey_R
# define KEY_ROTATE_CW ImGuiKey_PageUp
Expand Down
2 changes: 1 addition & 1 deletion src/Net/Imports/ImportTimedEventGraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ std::string importFromTimedEventGraph(Net& net, std::string const& filename)
size_t x = margin, y = margin;
for (size_t id = 0u; id < transitions; ++id)
{
net.addTransition(id, Transition::to_str(id), float(x), float(y), 0);
net.addTransition(id, Transition::to_str(id), randomInt(0,800), randomInt(0,600), 0);
x += dx;
if (x > w - margin) { x = margin; y += dy; }
}
Expand Down
8 changes: 4 additions & 4 deletions src/Net/Imports/Imports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ namespace tpne {
std::vector<Importer> const& importers()
{
static const std::vector<Importer> s_importers = {
{ "JSON", ".json", importFromJSON },
{ "Petri Net Markup Language", ".pnml", importFromPNML },
{ "Flowshop", ".flowshop", importFlowshop },
{ "Timed Event Graph", ".teg", importFromTimedEventGraph }
{ "JSON", ".json", importFromJSON, false },
{ "Petri Net Markup Language", ".pnml", importFromPNML, false },
{ "Flowshop", ".flowshop", importFlowshop, false },
{ "Timed Event Graph", ".teg", importFromTimedEventGraph, true }
};

return s_importers;
Expand Down
2 changes: 2 additions & 0 deletions src/Net/Imports/Imports.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ struct Importer
std::string extensions;
//! \brief the pointer function for importing (i.e. importFromJSON)
ImportFunc importFct;
//! \brief Shall apply spring force on the net ?
bool springify;
};

//! \brief Container of file formats we can import a Petri net from.
Expand Down
6 changes: 5 additions & 1 deletion src/Net/PetriNet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,7 @@ std::string saveToFile(Net const& net, std::string const& filepath)
}

//------------------------------------------------------------------------------
std::string loadFromFile(Net& net, std::string const& filepath)
std::string loadFromFile(Net& net, std::string const& filepath, bool& springify)
{
// Search the importer
Importer const* importer = getImporter(extension(filepath));
Expand All @@ -771,6 +771,10 @@ std::string loadFromFile(Net& net, std::string const& filepath)
{
net.reset(net.type());
}
else
{
springify = importer->springify;
}

// Get a name to the net
if (net.name == "")
Expand Down
150 changes: 150 additions & 0 deletions src/Utils/ForceDirected.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/* *****************************************************************************
** MIT License
**
** Copyright (c) 2022 Quentin Quadrat
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in all
** copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
** SOFTWARE.
********************************************************************************
*/

#include "Utils/ForceDirected.hpp"

namespace tpne {

//------------------------------------------------------------------------------
void ForceDirected::reset(float width, float height, Net& net)
{
std::cout << "ForceDirected::reset " << width << " x " << height << "\n";
m_net = &net;
m_width = width;
m_height = height;

N = net.transitions().size() + net.places().size();
K = sqrtf(m_width * m_height / 2.0f / float(N));
m_temperature = m_width + m_height;
m_vertices.clear();
m_vertices.reserve(N);

// Copy Petri nodes to Graph vertices
for (auto& it: net.transitions())
{
m_vertices.emplace_back(it);
}
for (auto& it: net.places())
{
m_vertices.emplace_back(it);
}

// Lookup table: Node ID to index on the vector
std::map<std::string, size_t> lookup;
for (size_t n = 0u; n < N; ++n)
{
lookup[m_vertices[n].node->key] = n;
}

// Add edges "source node" -> "destination node".
// Since, we need undirected graph so add edges "destination node" -> "source node"
for (size_t n = 0u; n < N; ++n)
{
Vertex& v = m_vertices[n];
v.neighbors.reserve(2u * (v.node->arcsIn.size() + v.node->arcsOut.size()));

for (auto& it: v.node->arcsIn)
{
v.neighbors.emplace_back(m_vertices[lookup[it->from.key]].node);
v.neighbors.emplace_back(m_vertices[lookup[it->to.key]].node);
}

for (auto& it: v.node->arcsOut)
{
v.neighbors.emplace_back(m_vertices[lookup[it->from.key]].node);
v.neighbors.emplace_back(m_vertices[lookup[it->to.key]].node);
}
}
}

//------------------------------------------------------------------------------
void ForceDirected::update()
{
if (m_net == nullptr)
return ;

if (m_temperature < 0.1f)
return ;

step();
}

//------------------------------------------------------------------------------
void ForceDirected::step()
{
if (m_net == nullptr)
return ;

for (auto& v: m_vertices)
{
const ImVec2 v_position(v.node->x, v.node->y);

// Repulsive forces: nodes -- nodes
for (auto& u: m_vertices)
{
if (u.node->key == v.node->key)
continue ;

const ImVec2 u_position(u.node->x, u.node->y);
const ImVec2 direction(v_position - u_position);
const float dist = distance(direction);
const float rf = repulsive_force(dist);
v.displacement += direction * rf / dist;
}

// Attractive forces: edges
for (auto& u: v.neighbors)
{
if (u->key == v.node->key)
continue ;

const ImVec2 u_position(u->x, u->y);
const ImVec2 direction(v_position - u_position);
const float dist = distance(direction);
const float af = attractive_force(dist);
v.displacement -= direction * af / dist;
}
}

// Update position and constrain position to the window bounds
for (auto& v: m_vertices)
{
constexpr ImVec2 LAYOUT_BORDER(50.0f, 50.0f);
const float dist = distance(v.displacement);
ImVec2 v_position(v.node->x, v.node->y);
v_position += (dist > m_temperature)
? v.displacement * m_temperature / dist
: v.displacement;
v.node->x = std::min(m_width - LAYOUT_BORDER.x,
std::max(LAYOUT_BORDER.x, v_position.x));
v.node->y = std::min(m_height - LAYOUT_BORDER.y,
std::max(LAYOUT_BORDER.y, v_position.y));
v.displacement = { 0.0f, 0.0f };
}

cooling();
}

} // namespace tpne
Loading

0 comments on commit 2244692

Please sign in to comment.