├── .gitignore ├── CMakeLists.txt ├── README.md ├── img └── tree.png ├── include ├── number_generator.hpp ├── pinned_segment.hpp ├── scaffold.hpp ├── tree.hpp ├── tree_builder.hpp ├── tree_renderer.hpp ├── utils.hpp ├── vec2.hpp └── wind.hpp ├── lib └── swarm.hpp ├── res ├── font.ttf ├── font_2.ttf └── leaf.png └── src └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | set(PROJECT_NAME Tree2D) 3 | project(${PROJECT_NAME} VERSION 1.0.0 LANGUAGES CXX) 4 | find_package(OpenGL) 5 | 6 | file(GLOB source_files 7 | "src/*.cpp" 8 | ) 9 | 10 | set(SOURCES ${source_files}) 11 | 12 | # Detect and add SFML 13 | set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake_modules" ${CMAKE_MODULE_PATH}) 14 | find_package(SFML 2 REQUIRED COMPONENTS network audio graphics window system) 15 | 16 | if(WIN32) 17 | set(WIN32_GUI WIN32) 18 | endif(WIN32) 19 | 20 | # Set build type 21 | if(NOT CMAKE_BUILD_TYPE) 22 | set(CMAKE_BUILD_TYPE Release) 23 | endif(NOT CMAKE_BUILD_TYPE) 24 | 25 | set(CMAKE_CXX_FLAGS "-Wall -Wextra") 26 | set(CMAKE_CXX_FLAGS_DEBUG "-g") 27 | set(CMAKE_CXX_FLAGS_RELEASE "-O3") 28 | 29 | add_executable(${PROJECT_NAME} ${WIN32_GUI} ${SOURCES}) 30 | target_include_directories(${PROJECT_NAME} PRIVATE "include" "lib") 31 | set(SFML_LIBS sfml-system sfml-window sfml-graphics) 32 | target_link_libraries(${PROJECT_NAME} ${SFML_LIBS}) 33 | set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11) 34 | if (UNIX) 35 | target_link_libraries(${PROJECT_NAME} pthread) 36 | endif (UNIX) 37 | 38 | # Copy res dir to the binary directory 39 | file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/res DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) 40 | 41 | if(MSVC) 42 | foreach(lib ${SFML_LIBS}) 43 | get_target_property(lib_path ${lib} LOCATION) 44 | file(COPY ${lib_path} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) 45 | endforeach() 46 | endif(MSVC) 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tree2D 2 | 3 | Library to generate 2D trees and simulate branches physic (not accurately). 4 | ![example](https://github.com/johnBuffer/Tree2D/blob/main/img/tree.png) 5 | 6 | # WIP 7 | To be updated when interface will be finished 8 | -------------------------------------------------------------------------------- /img/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnBuffer/Tree2D/168322ac0c7572f5bb00e73128c0e401cb84e1a2/img/tree.png -------------------------------------------------------------------------------- /include/number_generator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | 5 | template 6 | class NumberGenerator 7 | { 8 | private: 9 | std::random_device rd; 10 | std::uniform_real_distribution dis; 11 | std::mt19937 gen; 12 | 13 | public: 14 | NumberGenerator() 15 | : rd() 16 | , dis(0.0f, 1.0f) 17 | , gen(0) 18 | 19 | {} 20 | 21 | // random_device is not copyable 22 | NumberGenerator(const NumberGenerator& right) 23 | : rd() 24 | , dis(right.dis) 25 | , gen(rd()) 26 | 27 | {} 28 | 29 | float get() 30 | { 31 | return dis(gen); 32 | } 33 | 34 | float getUnder(float max) 35 | { 36 | return get() * max; 37 | } 38 | 39 | float getRange(float min, float max) 40 | { 41 | return min + get() * (max - min); 42 | } 43 | 44 | float getRange(float width) 45 | { 46 | return getRange(-width * 0.5f, width * 0.5f); 47 | } 48 | }; 49 | 50 | 51 | template 52 | class RNG 53 | { 54 | private: 55 | static NumberGenerator gen; 56 | 57 | public: 58 | static T get() 59 | { 60 | return gen.get(); 61 | } 62 | 63 | static float getUnder(float max) 64 | { 65 | return gen.getUnder(max); 66 | } 67 | 68 | static float getRange(float min, float max) 69 | { 70 | return gen.getRange(min, max); 71 | } 72 | 73 | static float getRange(float width) 74 | { 75 | return gen.getRange(width); 76 | } 77 | 78 | static bool rng(float proba) 79 | { 80 | return get() < proba; 81 | } 82 | }; 83 | 84 | using RNGf = RNG; 85 | 86 | template 87 | NumberGenerator RNG::gen = NumberGenerator(); 88 | -------------------------------------------------------------------------------- /include/pinned_segment.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "vec2.hpp" 3 | 4 | 5 | struct Particule 6 | { 7 | Vec2 position; 8 | Vec2 old_position; 9 | Vec2 acceleration; 10 | float mass; 11 | float inv_mass; 12 | 13 | Particule() 14 | : position() 15 | , old_position() 16 | , acceleration() 17 | , mass(1.0f) 18 | , inv_mass(1.0f) 19 | {} 20 | 21 | Particule(Vec2 pos, float m = 1.0f) 22 | : position(pos) 23 | , old_position(pos) 24 | , acceleration(0.0f, 0.0f) 25 | , mass(m) 26 | , inv_mass(1.0f / m) 27 | {} 28 | 29 | void update(float dt, float air_friction = 0.5f) 30 | { 31 | const Vec2 velocity = position - old_position; 32 | acceleration -= velocity * air_friction; 33 | const Vec2 new_pos = position + (velocity + acceleration * dt); 34 | old_position = position; 35 | position = new_pos; 36 | acceleration = Vec2(0.0f, 0.0f); 37 | } 38 | 39 | void move(Vec2 v) 40 | { 41 | position += v; 42 | } 43 | 44 | void applyForce(Vec2 v) 45 | { 46 | acceleration += v * inv_mass; 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /include/scaffold.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "tree.hpp" 3 | 4 | namespace v2 5 | { 6 | namespace scaffold 7 | { 8 | struct Node 9 | { 10 | Vec2 direction; 11 | float length; 12 | uint32_t index; 13 | uint32_t branch_index; 14 | 15 | Node() 16 | : direction() 17 | , length(0.0f) 18 | , index(0) 19 | , branch_index(0) 20 | {} 21 | 22 | Node(Vec2 dir, float l, uint32_t i, uint32_t branch) 23 | : direction(dir) 24 | , length(l) 25 | , index(i) 26 | , branch_index(branch) 27 | {} 28 | 29 | Vec2 getVec() const 30 | { 31 | return direction * length; 32 | } 33 | }; 34 | 35 | struct Branch 36 | { 37 | std::vector nodes; 38 | 39 | Branch(const Node& n) 40 | : nodes{ n } 41 | {} 42 | }; 43 | 44 | struct Tree 45 | { 46 | std::vector branches; 47 | }; 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /include/tree.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "vec2.hpp" 3 | #include "pinned_segment.hpp" 4 | #include "wind.hpp" 5 | #include "utils.hpp" 6 | 7 | 8 | namespace v2 9 | { 10 | struct PhysicSegment 11 | { 12 | Vec2 attach_point; 13 | Vec2 direction; 14 | Particule moving_point; 15 | float length; 16 | float delta_angle; 17 | float last_angle; 18 | 19 | PhysicSegment() 20 | : length(0.0f) 21 | , delta_angle(0.0f) 22 | , last_angle(0.0f) 23 | {} 24 | 25 | PhysicSegment(Vec2 attach, Vec2 moving) 26 | : attach_point(attach) 27 | , direction((moving - attach).getNormalized()) 28 | , moving_point(moving) 29 | , length((moving - attach).getLength()) 30 | , delta_angle(0.0f) 31 | , last_angle(direction.getAngle()) 32 | { 33 | } 34 | 35 | void translate(const Vec2& v) 36 | { 37 | attach_point += v; 38 | moving_point.position += v; 39 | moving_point.old_position += v; 40 | } 41 | 42 | void updateDeltaAngle() 43 | { 44 | const float new_angle = (moving_point.position - attach_point).getAngle(); 45 | delta_angle = new_angle - last_angle; 46 | last_angle = new_angle; 47 | } 48 | 49 | void solveAttach() 50 | { 51 | const Vec2 delta = moving_point.position - attach_point; 52 | const float dist = delta.getLength(); 53 | const float dist_delta = length - dist; 54 | const float inv_dist = 1.0f / dist; 55 | moving_point.move(Vec2(delta.x * inv_dist * dist_delta, delta.y * inv_dist * dist_delta)); 56 | } 57 | 58 | void update(float dt) 59 | { 60 | solveAttach(); 61 | moving_point.acceleration += direction; 62 | moving_point.update(dt); 63 | updateDeltaAngle(); 64 | } 65 | }; 66 | 67 | struct Node 68 | { 69 | Vec2 position; 70 | float width; 71 | 72 | Node() 73 | : position() 74 | , width(1.0f) 75 | {} 76 | 77 | Node(Vec2 pos, float w) 78 | : position(pos) 79 | , width(w) 80 | {} 81 | }; 82 | 83 | struct NodeRef 84 | { 85 | uint32_t branch_id; 86 | uint32_t node_id; 87 | Vec2 position; 88 | 89 | NodeRef() 90 | : branch_id(0) 91 | , node_id(0) 92 | , position() 93 | { 94 | } 95 | 96 | NodeRef(uint32_t branch, uint32_t node, Vec2 pos = Vec2()) 97 | : branch_id(branch) 98 | , node_id(node) 99 | , position(pos) 100 | { 101 | } 102 | }; 103 | 104 | struct Branch 105 | { 106 | std::vector nodes; 107 | uint32_t level; 108 | // Physics 109 | PhysicSegment segment; 110 | NodeRef root; 111 | 112 | Branch() 113 | : level(0) 114 | {} 115 | 116 | Branch(const Node& node, uint32_t lvl, const NodeRef& root_ref = NodeRef()) 117 | : nodes{node} 118 | , level(lvl) 119 | , root(root_ref) 120 | {} 121 | 122 | void update(float dt) 123 | { 124 | segment.update(dt); 125 | } 126 | 127 | void translate(Vec2 v) 128 | { 129 | segment.translate(v); 130 | for (Node& n : nodes) { 131 | n.position += v; 132 | } 133 | } 134 | 135 | void translateTo(Vec2 position) 136 | { 137 | const Vec2 delta = position - root.position; 138 | root.position = position; 139 | translate(delta); 140 | } 141 | 142 | void initializePhysics() 143 | { 144 | segment = PhysicSegment(nodes.front().position, nodes.back().position); 145 | const float joint_strength(4000.0f * std::powf(0.4f, float(level))); 146 | segment.direction = segment.direction * joint_strength; 147 | } 148 | }; 149 | 150 | struct Leaf 151 | { 152 | NodeRef attach; 153 | 154 | Particule free_particule; 155 | Particule broken_part; 156 | 157 | Vec2 target_direction; 158 | Vec2 acceleration; 159 | 160 | sf::Color color; 161 | float cut_threshold; 162 | float size; 163 | 164 | Leaf(NodeRef anchor, const Vec2& dir) 165 | : attach(anchor) 166 | , free_particule(anchor.position + dir) 167 | , target_direction(dir * RNGf::getRange(1.0f, 4.0f)) 168 | , cut_threshold(0.4f + RNGf::getUnder(1.0f)) 169 | , size(1.0f) 170 | { 171 | color = sf::Color(255, static_cast(168 + RNGf::getRange(80.0f)), 0); 172 | 173 | } 174 | 175 | void solveAttach() 176 | { 177 | const float target_length = 1.0f; 178 | Vec2 delta = free_particule.position - attach.position; 179 | const float length = delta.normalize(); 180 | const float dist_delta = target_length - length; 181 | /*if (std::abs(dist_delta) > cut_threshold) { 182 | cut(); 183 | }*/ 184 | 185 | free_particule.move(delta * dist_delta); 186 | } 187 | 188 | void solveLink() 189 | { 190 | const float length = 1.0f; 191 | const Vec2 delta = free_particule.position - broken_part.position; 192 | const float dist_delta = 1.0f - delta.getLength(); 193 | free_particule.move(delta.getNormalized() * (0.5f * dist_delta)); 194 | broken_part.move(delta.getNormalized() * (-0.5f * dist_delta)); 195 | } 196 | 197 | Vec2 getDir() const 198 | { 199 | return free_particule.position - attach.position; 200 | } 201 | 202 | Vec2 getPosition() const 203 | { 204 | return attach.position; 205 | } 206 | 207 | void moveTo(Vec2 position) 208 | { 209 | const Vec2 delta = position - attach.position; 210 | attach.position = position; 211 | free_particule.position += delta; 212 | free_particule.old_position += delta; 213 | } 214 | 215 | /*void cut() 216 | { 217 | broken_part = Particule(attach->pos); 218 | attach = nullptr; 219 | target_direction = Vec2(0.0f, 1.0f); 220 | }*/ 221 | 222 | void applyWind(const Wind& wind) 223 | { 224 | const float ratio = 1.0f; 225 | const float wind_force = wind.strength * (wind.speed ? ratio : 1.0f); 226 | free_particule.acceleration += Vec2(1.0f, RNGf::getRange(2.0f)) * wind_force; 227 | } 228 | 229 | void update(float dt) 230 | { 231 | solveAttach(); 232 | free_particule.update(dt); 233 | // Reset acceleration 234 | free_particule.acceleration = target_direction; 235 | /*broken_part.update(dt); 236 | broken_part.acceleration = target_direction * joint_strenght;*/ 237 | } 238 | }; 239 | 240 | struct Tree 241 | { 242 | std::vector branches; 243 | std::vector leaves; 244 | 245 | Tree() = default; 246 | 247 | void updateBranches(float dt) 248 | { 249 | for (Branch& b : branches) { 250 | b.update(dt); 251 | } 252 | } 253 | 254 | void updateLeaves(float dt) 255 | { 256 | for (Leaf& l : leaves) { 257 | l.update(dt); 258 | } 259 | } 260 | 261 | void updateStructure() 262 | { 263 | // Apply resulting translations 264 | rotateBranches(); 265 | translateBranches(); 266 | translateLeaves(); 267 | } 268 | 269 | void update(float dt) 270 | { 271 | // Branch physics 272 | updateBranches(dt); 273 | // Leaves physics 274 | updateLeaves(dt); 275 | // Apply resulting transformations 276 | updateStructure(); 277 | } 278 | 279 | void applyWind(const std::vector& wind) 280 | { 281 | for (const Wind& w : wind) { 282 | for (Leaf& l : leaves) { 283 | w.apply(l.free_particule); 284 | } 285 | 286 | for (Branch& b : branches) { 287 | w.apply(b.segment.moving_point); 288 | } 289 | } 290 | } 291 | 292 | void rotateBranches() 293 | { 294 | for (Branch& b : branches) { 295 | rotateBranchTarget(b); 296 | } 297 | } 298 | 299 | void rotateBranchTarget(Branch& b) 300 | { 301 | const RotMat2 mat(b.segment.delta_angle); 302 | const Vec2 origin = b.nodes.front().position; 303 | for (Node& n : b.nodes) { 304 | n.position.rotate(origin, mat); 305 | } 306 | } 307 | 308 | void translateBranches() 309 | { 310 | uint64_t branches_count = branches.size(); 311 | for (uint64_t i(1); i < branches_count; ++i) { 312 | Branch& b = branches[i]; 313 | b.translateTo(getNode(b.root).position); 314 | } 315 | } 316 | 317 | Node& getNode(const NodeRef& ref) 318 | { 319 | return branches[ref.branch_id].nodes[ref.node_id]; 320 | } 321 | 322 | void translateLeaves() 323 | { 324 | for (Leaf& l : leaves) { 325 | l.moveTo(getNode(l.attach).position); 326 | } 327 | } 328 | 329 | uint64_t getNodesCount() const 330 | { 331 | uint64_t res = 0; 332 | for (const Branch& b : branches) { 333 | res += b.nodes.size(); 334 | } 335 | return res; 336 | } 337 | 338 | void generateSkeleton() 339 | { 340 | for (Branch& b : branches) { 341 | b.initializePhysics(); 342 | } 343 | } 344 | }; 345 | } 346 | -------------------------------------------------------------------------------- /include/tree_builder.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "tree.hpp" 3 | #include "number_generator.hpp" 4 | #include "utils.hpp" 5 | #include "scaffold.hpp" 6 | 7 | 8 | namespace v2 9 | { 10 | struct TreeConf 11 | { 12 | float branch_width; 13 | float branch_width_ratio; 14 | float split_width_ratio; 15 | float branch_deviation; 16 | float branch_split_angle; 17 | float branch_split_var; 18 | float branch_length; 19 | float branch_length_ratio; 20 | float branch_split_proba; 21 | float double_split_proba; 22 | Vec2 attraction; 23 | uint32_t max_level; 24 | }; 25 | 26 | struct GrowthResult 27 | { 28 | bool split; 29 | Node node; 30 | scaffold::Node sfd_node; 31 | uint32_t level; 32 | NodeRef root; 33 | 34 | GrowthResult() 35 | : split(false) 36 | , node() 37 | , sfd_node() 38 | , level(0) 39 | , root() 40 | {} 41 | }; 42 | 43 | struct TreeBuilder 44 | { 45 | static GrowthResult grow(v2::scaffold::Branch& sfd_branch, v2::Branch& branch, const TreeConf& conf) 46 | { 47 | GrowthResult result; 48 | const scaffold::Node& sfd_node = sfd_branch.nodes.back(); 49 | Node& current_node = branch.nodes.back(); 50 | const uint32_t level = branch.level; 51 | const uint32_t index = sfd_node.index; 52 | 53 | const float width = current_node.width; 54 | const float width_threshold = 0.8f; 55 | if (width > width_threshold) { 56 | // Compute new start 57 | const Vec2 start = current_node.position + sfd_node.getVec(); 58 | // Compute new length 59 | const float new_length = sfd_node.length * conf.branch_length_ratio; 60 | const float new_width = current_node.width * conf.branch_width_ratio; 61 | // Compute new direction 62 | const float deviation = RNGf::getRange(conf.branch_deviation); 63 | Vec2 direction = sfd_node.direction; 64 | direction.rotate(deviation); 65 | const float attraction_force = 1.0f / new_length; 66 | direction = (direction + conf.attraction * attraction_force).getNormalized(); 67 | // Add new node 68 | branch.nodes.emplace_back(start, new_width); 69 | sfd_branch.nodes.emplace_back(direction, new_length, index + 1, 0); 70 | Node& new_node = branch.nodes.back(); 71 | // Check for split 72 | if (index && (index % 5 == 0) && level < conf.max_level) { 73 | result.split = true; 74 | float split_angle = conf.branch_split_angle + RNGf::getRange(conf.branch_split_var); 75 | // Determine side 76 | if (RNGf::rng(0.5f)) { 77 | split_angle = -split_angle; 78 | } 79 | result.root.node_id = index; 80 | result.root.position = new_node.position; 81 | result.node.position = start; 82 | result.sfd_node.direction = Vec2::getRotated(direction, split_angle); 83 | result.sfd_node.length = new_length * conf.branch_length_ratio; 84 | result.node.width = new_width * conf.split_width_ratio; 85 | result.level = level + 1; 86 | result.sfd_node.index = 0; 87 | // Avoid single node branches 88 | if (result.node.width < width_threshold) { 89 | result.split = false; 90 | new_node.width = 0.0f; 91 | } 92 | } 93 | } 94 | 95 | return result; 96 | } 97 | 98 | static void grow(scaffold::Tree& sfd_tree, Tree& tree, const TreeConf& conf) 99 | { 100 | std::vector to_add; 101 | uint32_t i(0); 102 | for (Branch& b : tree.branches) { 103 | scaffold::Branch& sfd_b = sfd_tree.branches[i]; 104 | GrowthResult res = TreeBuilder::grow(sfd_b, b, conf); 105 | if (res.split) { 106 | to_add.emplace_back(res); 107 | to_add.back().root.branch_id = i; 108 | } 109 | ++i; 110 | } 111 | 112 | for (const GrowthResult& res : to_add) { 113 | //tree.branches[res.root.branch_id].nodes[res.root.node_id].branch_id = static_cast(tree.branches.size()); 114 | sfd_tree.branches.emplace_back(res.sfd_node); 115 | tree.branches.emplace_back(res.node, res.level, res.root); 116 | } 117 | } 118 | 119 | static void addLeaves(Tree& tree) 120 | { 121 | uint32_t branch_id = 0; 122 | for (const Branch& b : tree.branches) { 123 | const uint64_t nodes_count = b.nodes.size() - 1; 124 | const uint32_t leafs_count = 10; 125 | int32_t node_id = static_cast(nodes_count); 126 | for (uint32_t i(0); i < leafs_count; ++i) { 127 | node_id -= i; 128 | if (node_id < 0) { 129 | break; 130 | } 131 | const float angle = RNGf::getRange(2.0f * PI); 132 | const NodeRef anchor(branch_id, node_id, b.nodes[node_id].position); 133 | tree.leaves.emplace_back(anchor, Vec2(cos(angle), sin(angle))); 134 | tree.leaves.back().size = 1.0f + (0.5f * i / float(leafs_count)); 135 | } 136 | ++branch_id; 137 | } 138 | } 139 | 140 | static Tree build(Vec2 position, const TreeConf& conf) 141 | { 142 | // Create root 143 | const Node root(position, conf.branch_width); 144 | scaffold::Tree sfd_tree; 145 | sfd_tree.branches.emplace_back(scaffold::Node(Vec2(0.0f, -1.0f), conf.branch_length, 0, 0)); 146 | Tree tree; 147 | tree.branches.emplace_back(root, 0); 148 | // Build the tree 149 | uint64_t nodes_count = 0; 150 | while (true) { 151 | grow(sfd_tree, tree, conf); 152 | if (nodes_count == tree.getNodesCount()) { 153 | break; 154 | } 155 | nodes_count = tree.getNodesCount(); 156 | } 157 | // Add physic and leaves 158 | addLeaves(tree); 159 | tree.generateSkeleton(); 160 | 161 | return tree; 162 | } 163 | }; 164 | } 165 | -------------------------------------------------------------------------------- /include/tree_renderer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "tree.hpp" 4 | 5 | 6 | class TreeRenderer 7 | { 8 | private: 9 | sf::RenderTarget& m_target; 10 | sf::Sprite leaf_sprite; 11 | sf::Texture texture; 12 | 13 | public: 14 | TreeRenderer(sf::RenderTarget& target) 15 | : m_target(target) 16 | { 17 | texture.loadFromFile("../res/leaf.png"); 18 | leaf_sprite.setTexture(texture); 19 | leaf_sprite.setOrigin(800, 1485); 20 | leaf_sprite.setScale(0.02f, 0.02f); 21 | } 22 | 23 | //static void generateRenderData(const Tree& tree, std::vector& branches_va) 24 | //{ 25 | // // Create branches 26 | // branches_va.clear(); 27 | // for (const Branch& b : tree.branches) { 28 | // branches_va.emplace_back(sf::TriangleStrip, b.nodes.size() * 2); 29 | // sf::VertexArray& va = branches_va.back(); 30 | // uint64_t i(0); 31 | // for (const Node::Ptr n : b.nodes) { 32 | // const float width = 0.5f * n->width; 33 | // const Vec2 n_vec = n->growth_direction.getNormal() * width; 34 | // va[2 * i].position = sf::Vector2f(n->pos.x + n_vec.x, n->pos.y + n_vec.y); 35 | // va[2 * i + 1].position = sf::Vector2f(n->pos.x - n_vec.x, n->pos.y - n_vec.y); 36 | // ++i; 37 | // } 38 | // } 39 | //} 40 | 41 | static void generateRenderData(const v2::Tree& tree, std::vector& branches_va, sf::VertexArray& leaves_va) 42 | { 43 | // Create branches 44 | branches_va.clear(); 45 | for (const v2::Branch& b : tree.branches) { 46 | const uint64_t nodes_count = b.nodes.size() - 1; 47 | branches_va.emplace_back(sf::TriangleStrip, nodes_count * 2); 48 | sf::VertexArray& va = branches_va.back(); 49 | for (uint64_t i(0); i < nodes_count; ++i) { 50 | const v2::Node& n = b.nodes[i]; 51 | const v2::Node& next_n = b.nodes[i+1]; 52 | const float width = 0.5f * n.width; 53 | const Vec2 n_vec = (next_n.position - n.position).getNormalized().getNormal() * width; 54 | va[2 * i].position = sf::Vector2f(n.position.x + n_vec.x, n.position.y + n_vec.y); 55 | va[2 * i + 1].position = sf::Vector2f(n.position.x - n_vec.x, n.position.y - n_vec.y); 56 | } 57 | } 58 | 59 | uint64_t i(0); 60 | const float leaf_length = 30.0f; 61 | const float leaf_width = 30.0f; 62 | leaves_va.resize(4 * tree.leaves.size()); 63 | for (const v2::Leaf& l : tree.leaves) { 64 | const Vec2 leaf_dir = l.getDir().getNormalized(); 65 | const Vec2 dir = leaf_dir * leaf_length * l.size; 66 | const Vec2 nrm = leaf_dir.getNormal() * (0.5f * leaf_width* l.size); 67 | const Vec2 attach = l.getPosition(); 68 | const Vec2 pt1 = attach + nrm; 69 | const Vec2 pt2 = attach + nrm + dir; 70 | const Vec2 pt3 = attach - nrm + dir; 71 | const Vec2 pt4 = attach - nrm; 72 | // Geometry 73 | leaves_va[4 * i + 0].position = sf::Vector2f(pt1.x, pt1.y); 74 | leaves_va[4 * i + 1].position = sf::Vector2f(pt2.x, pt2.y); 75 | leaves_va[4 * i + 2].position = sf::Vector2f(pt3.x, pt3.y); 76 | leaves_va[4 * i + 3].position = sf::Vector2f(pt4.x, pt4.y); 77 | // Texture 78 | leaves_va[4 * i + 0].texCoords = sf::Vector2f(0.0f, 0.0f); 79 | leaves_va[4 * i + 1].texCoords = sf::Vector2f(1024.0f, 0.0f); 80 | leaves_va[4 * i + 2].texCoords = sf::Vector2f(1024.0f, 1024.0f); 81 | leaves_va[4 * i + 3].texCoords = sf::Vector2f(0.0f, 1024.0f); 82 | // Color 83 | leaves_va[4 * i + 0].color = l.color; 84 | leaves_va[4 * i + 1].color = l.color; 85 | leaves_va[4 * i + 2].color = l.color; 86 | leaves_va[4 * i + 3].color = l.color; 87 | 88 | ++i; 89 | } 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /include/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "vec2.hpp" 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | const float PI = 3.141592653f; 10 | 11 | 12 | float getVec2Angle(const Vec2& v1, const Vec2& v2) 13 | { 14 | const float dot = v1.x * v2.x + v1.y * v2.y; 15 | const float det = v1.x * v2.y - v1.y * v2.x; 16 | return atan2(det, dot); 17 | } 18 | 19 | 20 | template 21 | std::string toString(T v, bool truncate = false) 22 | { 23 | std::stringstream sx; 24 | sx << v; 25 | 26 | if (truncate) { 27 | return sx.str().substr(0, 4); 28 | } 29 | return sx.str(); 30 | } 31 | 32 | 33 | float getLength(const Vec2& v) 34 | { 35 | return sqrt(v.x*v.x + v.y*v.y); 36 | } 37 | 38 | 39 | float sign(float a) 40 | { 41 | return a < 0.0f ? -1.0f : 1.0f; 42 | } 43 | -------------------------------------------------------------------------------- /include/vec2.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | 5 | struct RotMat2 6 | { 7 | float cosa, sina; 8 | 9 | RotMat2(float angle) 10 | : cosa(cos(angle)) 11 | , sina(sin(angle)) 12 | {} 13 | 14 | void apply(float x_in, float y_in, float& x_out, float& y_out) const 15 | { 16 | x_out = cosa * x_in - sina * y_in; 17 | y_out = sina * x_in + cosa * y_in; 18 | } 19 | }; 20 | 21 | 22 | struct Vec2 23 | { 24 | float x, y; 25 | 26 | Vec2() 27 | : x(0.0f) 28 | , y(0.0f) 29 | {} 30 | 31 | Vec2(float x_, float y_) 32 | : x(x_) 33 | , y(y_) 34 | {} 35 | 36 | void operator+=(const Vec2& v) 37 | { 38 | x += v.x; 39 | y += v.y; 40 | } 41 | 42 | void operator-=(const Vec2& v) 43 | { 44 | x -= v.x; 45 | y -= v.y; 46 | } 47 | 48 | float getLength() const 49 | { 50 | return sqrt(x*x + y*y); 51 | } 52 | 53 | Vec2 getNormalized() const 54 | { 55 | const float length = getLength(); 56 | return Vec2(x / length, y / length); 57 | } 58 | 59 | float normalize() 60 | { 61 | const float length = getLength(); 62 | const float inv = 1.0f / length; 63 | x *= inv; 64 | y *= inv; 65 | return length; 66 | } 67 | 68 | Vec2 getNormal() const 69 | { 70 | return Vec2(-y, x); 71 | } 72 | 73 | float dot(const Vec2& v) const 74 | { 75 | return x * v.x + y * v.y; 76 | } 77 | 78 | void rotate(float angle) 79 | { 80 | const RotMat2 mat(angle); 81 | rotate(Vec2(), mat); 82 | } 83 | 84 | void rotate(const RotMat2& mat) 85 | { 86 | rotate(Vec2(), mat); 87 | } 88 | 89 | void rotate(const Vec2& origin, const RotMat2& mat) 90 | { 91 | const Vec2 v(x - origin.x, y - origin.y); 92 | mat.apply(v.x, v.y, x, y); 93 | x += origin.x; 94 | y += origin.y; 95 | } 96 | 97 | float getAngle() const 98 | { 99 | const float angle = acos(x / getLength()); 100 | return y < 0.0f ? -angle : angle; 101 | } 102 | 103 | static float getAngle(const Vec2& v1, const Vec2& v2) 104 | { 105 | const float dot = v1.x * v2.x + v1.y * v2.y; 106 | const float det = v1.x * v2.y - v1.y * v2.x; 107 | return atan2(det, dot); 108 | } 109 | 110 | static Vec2 getRotated(const Vec2& v, float angle) 111 | { 112 | Vec2 vec = v; 113 | vec.rotate(angle); 114 | return vec; 115 | } 116 | }; 117 | 118 | Vec2 operator+(const Vec2& v1, const Vec2& v2) 119 | { 120 | return Vec2(v1.x + v2.x, v1.y + v2.y); 121 | } 122 | 123 | Vec2 operator-(const Vec2& v1, const Vec2& v2) 124 | { 125 | return Vec2(v1.x - v2.x, v1.y - v2.y); 126 | } 127 | 128 | Vec2 operator*(const Vec2& v, float f) 129 | { 130 | return Vec2(v.x * f, v.y * f); 131 | } 132 | 133 | Vec2 operator/(const Vec2& v, float f) 134 | { 135 | return Vec2(v.x / f, v.y / f); 136 | } 137 | 138 | -------------------------------------------------------------------------------- /include/wind.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "pinned_segment.hpp" 3 | #include "number_generator.hpp" 4 | 5 | 6 | struct Wind 7 | { 8 | float width; 9 | float strength; 10 | float pos_x; 11 | float speed; 12 | 13 | Wind(float w, float force, float spd, float start = 0.0f) 14 | : width(w) 15 | , strength(force) 16 | , speed(spd) 17 | , pos_x(start ? start : -w*0.5f) 18 | { 19 | 20 | } 21 | 22 | void update(float dt, float max_x) 23 | { 24 | pos_x += speed * dt; 25 | if (pos_x - width * 0.5f > max_x) { 26 | pos_x = -width * 0.5f; 27 | } 28 | } 29 | 30 | bool isOver(const Vec2& pos) const 31 | { 32 | return (pos.x > pos_x - width * 0.5f) && (pos.x < pos_x + width * 0.5f); 33 | } 34 | 35 | void apply(Particule& p) const 36 | { 37 | if (isOver(p.position)) { 38 | p.applyForce(Vec2(1.0f, RNGf::getRange(1.0f)) * strength); 39 | } 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /lib/swarm.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace swrm 14 | { 15 | 16 | class Swarm; 17 | class ExecutionGroup; 18 | 19 | using WorkerFunction = std::function; 20 | 21 | class Worker 22 | { 23 | public: 24 | Worker(Swarm* swarm); 25 | 26 | void createThread(); 27 | 28 | void lockReady(); 29 | void lockDone(); 30 | void unlockReady(); 31 | void unlockDone(); 32 | 33 | void setJob(uint32_t id, ExecutionGroup* group); 34 | void stop(); 35 | void join(); 36 | 37 | private: 38 | bool m_running; 39 | uint32_t m_id; 40 | uint32_t m_group_size; 41 | 42 | Swarm* m_swarm; 43 | ExecutionGroup* m_group; 44 | std::thread m_thread; 45 | WorkerFunction m_job; 46 | 47 | std::mutex m_ready_mutex; 48 | std::mutex m_done_mutex; 49 | 50 | void run(); 51 | void waitReady(); 52 | void waitDone(); 53 | }; 54 | 55 | class Synchronizer 56 | { 57 | public: 58 | static void lockAtReady(std::list& workers) 59 | { 60 | for (Worker* worker : workers) { 61 | worker->lockReady(); 62 | } 63 | } 64 | 65 | static void unlockAtReady(std::list& workers) 66 | { 67 | for (Worker* worker : workers) { 68 | worker->unlockReady(); 69 | } 70 | } 71 | 72 | static void lockAtDone(std::list& workers) 73 | { 74 | for (Worker* worker : workers) { 75 | worker->lockDone(); 76 | } 77 | } 78 | 79 | static void unlockAtDone(std::list& workers) 80 | { 81 | for (Worker* worker : workers) { 82 | worker->unlockDone(); 83 | } 84 | } 85 | 86 | static void stop(std::list& workers) 87 | { 88 | for (Worker* worker : workers) { 89 | worker->stop(); 90 | } 91 | } 92 | 93 | static void join(std::list& workers) 94 | { 95 | for (Worker* worker : workers) { 96 | worker->join(); 97 | } 98 | } 99 | }; 100 | 101 | class ExecutionGroup 102 | { 103 | public: 104 | ExecutionGroup(WorkerFunction job, uint32_t group_size, std::list& available_workers) 105 | : m_job(job) 106 | , m_group_size(group_size) 107 | , m_done_count(0U) 108 | , m_condition() 109 | , m_condition_mutex() 110 | { 111 | retrieveWorkers(available_workers); 112 | start(); 113 | } 114 | 115 | ~ExecutionGroup() 116 | { 117 | } 118 | 119 | void start() 120 | { 121 | m_done_count = 0U; 122 | Synchronizer::lockAtDone(m_workers); 123 | Synchronizer::unlockAtReady(m_workers); 124 | } 125 | 126 | void waitExecutionDone() 127 | { 128 | waitWorkersDone(); 129 | Synchronizer::lockAtReady(m_workers); 130 | Synchronizer::unlockAtDone(m_workers); 131 | m_workers.clear(); 132 | } 133 | 134 | private: 135 | const uint32_t m_group_size; 136 | const WorkerFunction m_job; 137 | std::list m_workers; 138 | 139 | std::atomic m_done_count; 140 | std::condition_variable m_condition; 141 | std::mutex m_condition_mutex; 142 | 143 | void notifyWorkerDone() 144 | { 145 | { 146 | std::lock_guard lg(m_condition_mutex); 147 | ++m_done_count; 148 | } 149 | m_condition.notify_one(); 150 | } 151 | 152 | void waitWorkersDone() 153 | { 154 | std::unique_lock ul(m_condition_mutex); 155 | m_condition.wait(ul, [this] { return m_done_count == m_group_size; }); 156 | } 157 | 158 | void retrieveWorkers(std::list& available_workers) 159 | { 160 | for (uint32_t i(m_group_size); i--;) { 161 | Worker* worker = available_workers.front(); 162 | available_workers.pop_front(); 163 | worker->setJob(i, this); 164 | m_workers.push_back(worker); 165 | } 166 | } 167 | 168 | friend Worker; 169 | }; 170 | 171 | class WorkGroup 172 | { 173 | public: 174 | WorkGroup() 175 | : m_group(nullptr) 176 | {} 177 | 178 | WorkGroup(std::shared_ptr execution_group) 179 | : m_group(execution_group) 180 | {} 181 | 182 | void waitExecutionDone() 183 | { 184 | if (m_group) { 185 | m_group->waitExecutionDone(); 186 | } 187 | } 188 | 189 | private: 190 | std::shared_ptr m_group; 191 | }; 192 | 193 | class Swarm 194 | { 195 | public: 196 | Swarm(uint32_t thread_count) 197 | : m_thread_count(thread_count) 198 | , m_ready_count(0U) 199 | { 200 | for (uint32_t i(thread_count); i--;) { 201 | createWorker(); 202 | } 203 | 204 | while (m_ready_count < m_thread_count) {} 205 | } 206 | 207 | ~Swarm() 208 | { 209 | Synchronizer::stop(m_workers); 210 | Synchronizer::unlockAtReady(m_workers); 211 | Synchronizer::join(m_workers); 212 | deleteWorkers(); 213 | } 214 | 215 | WorkGroup execute(WorkerFunction job, uint32_t group_size = 0) 216 | { 217 | if (!group_size) { 218 | group_size = m_thread_count; 219 | } 220 | 221 | if (group_size > m_available_workers.size()) { 222 | return WorkGroup(); 223 | } 224 | 225 | return WorkGroup(std::make_unique(job, group_size, m_available_workers)); 226 | } 227 | 228 | 229 | private: 230 | const uint32_t m_thread_count; 231 | 232 | std::atomic m_ready_count; 233 | std::list m_workers; 234 | std::list m_available_workers; 235 | std::mutex m_mutex; 236 | 237 | void createWorker() 238 | { 239 | Worker* new_worker = new Worker(this); 240 | new_worker->createThread(); 241 | m_workers.push_back(new_worker); 242 | } 243 | 244 | void deleteWorkers() 245 | { 246 | for (Worker* worker : m_workers) { 247 | delete worker; 248 | } 249 | } 250 | 251 | void notifyWorkerReady(Worker* worker) 252 | { 253 | std::lock_guard lg(m_mutex); 254 | ++m_ready_count; 255 | m_available_workers.push_back(worker); 256 | } 257 | 258 | friend Worker; 259 | }; 260 | 261 | Worker::Worker(Swarm* swarm) 262 | : m_swarm(swarm) 263 | , m_group(nullptr) 264 | , m_id(0) 265 | , m_group_size(0) 266 | , m_running(true) 267 | , m_ready_mutex() 268 | , m_done_mutex() 269 | { 270 | } 271 | 272 | void Worker::createThread() 273 | { 274 | lockReady(); 275 | m_thread = std::thread(&Worker::run, this); 276 | } 277 | 278 | void Worker::run() 279 | { 280 | while (true) { 281 | waitReady(); 282 | 283 | if (!m_running) { 284 | break; 285 | } 286 | 287 | m_job(m_id, m_group_size); 288 | 289 | waitDone(); 290 | } 291 | } 292 | 293 | void Worker::lockReady() 294 | { 295 | m_ready_mutex.lock(); 296 | } 297 | 298 | void Worker::unlockReady() 299 | { 300 | m_ready_mutex.unlock(); 301 | } 302 | 303 | void Worker::lockDone() 304 | { 305 | m_done_mutex.lock(); 306 | } 307 | 308 | void Worker::unlockDone() 309 | { 310 | m_done_mutex.unlock(); 311 | } 312 | 313 | void Worker::setJob(uint32_t id, ExecutionGroup* group) 314 | { 315 | m_id = id; 316 | m_job = group->m_job; 317 | m_group_size = group->m_group_size; 318 | m_group = group; 319 | } 320 | 321 | void Worker::stop() 322 | { 323 | m_running = false; 324 | } 325 | 326 | void Worker::join() 327 | { 328 | m_thread.join(); 329 | } 330 | 331 | void Worker::waitReady() 332 | { 333 | m_swarm->notifyWorkerReady(this); 334 | lockReady(); 335 | unlockReady(); 336 | } 337 | 338 | void Worker::waitDone() 339 | { 340 | m_group->notifyWorkerDone(); 341 | lockDone(); 342 | unlockDone(); 343 | } 344 | 345 | } 346 | -------------------------------------------------------------------------------- /res/font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnBuffer/Tree2D/168322ac0c7572f5bb00e73128c0e401cb84e1a2/res/font.ttf -------------------------------------------------------------------------------- /res/font_2.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnBuffer/Tree2D/168322ac0c7572f5bb00e73128c0e401cb84e1a2/res/font_2.ttf -------------------------------------------------------------------------------- /res/leaf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnBuffer/Tree2D/168322ac0c7572f5bb00e73128c0e401cb84e1a2/res/leaf.png -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "tree_renderer.hpp" 8 | #include "wind.hpp" 9 | 10 | #include "tree.hpp" 11 | #include "tree_builder.hpp" 12 | 13 | 14 | int main() 15 | { 16 | constexpr uint32_t WinWidth = 1920; 17 | constexpr uint32_t WinHeight = 1080; 18 | 19 | sf::ContextSettings settings; 20 | settings.antialiasingLevel = 8; 21 | 22 | sf::RenderWindow window(sf::VideoMode(WinWidth, WinHeight), "Tree", sf::Style::Default, settings); 23 | window.setFramerateLimit(60); 24 | 25 | // 9f0f269 26 | // Perf: 0.36 ms 27 | // b01e334 28 | // Perf: 0.25 ms 29 | 30 | v2::TreeConf tree_conf{ 31 | 80.0f, // branch_width 32 | 0.95f, // branch_width_ratio 33 | 0.75f, // split_width_ratio 34 | 0.5f, // deviation 35 | PI * 0.25f, // split angle 36 | 0.1f, // branch_split_var; 37 | 40.0f, // branch_length; 38 | 0.96f, // branch_length_ratio; 39 | 0.5f, // branch_split_proba; 40 | 0.0f, // double split 41 | Vec2(0.0f, -0.5f), // Attraction 42 | 8 43 | }; 44 | 45 | sf::Texture texture; 46 | texture.loadFromFile("../res/leaf.png"); 47 | 48 | sf::Font font; 49 | font.loadFromFile("../res/font.ttf"); 50 | sf::Text text_profiler; 51 | text_profiler.setFont(font); 52 | text_profiler.setFillColor(sf::Color::White); 53 | text_profiler.setCharacterSize(24); 54 | 55 | std::vector branches_va; 56 | sf::VertexArray leaves_va(sf::Quads); 57 | v2::Tree tree = v2::TreeBuilder::build(Vec2(WinWidth * 0.5f, WinHeight), tree_conf); 58 | 59 | float base_wind_force = 0.05f; 60 | float max_wind_force = 30.0f; 61 | float current_wind_force = 0.0f; 62 | 63 | const float wind_force = 1.0f; 64 | std::vector wind{ 65 | Wind(100.0f, 3.f * wind_force, 700.0f), 66 | Wind(300.0f, 2.f * wind_force, 1050.0f), 67 | Wind(400.0f, 3.f * wind_force, 1208.0f), 68 | Wind(500.0f, 4.f * wind_force, 1400.0f), 69 | }; 70 | 71 | const float dt = 0.016f; 72 | 73 | float time_sum_leaves = 0.0f; 74 | float time_sum_branches = 0.0f; 75 | float time_sum_rest = 0.0f; 76 | float img_count = 1.0f; 77 | 78 | bool boosting = false; 79 | 80 | bool draw_branches = true; 81 | bool draw_leaves = true; 82 | bool draw_debug = false; 83 | bool draw_wind_debug = false; 84 | 85 | sf::Clock clock; 86 | while (window.isOpen()) 87 | { 88 | const sf::Vector2i mouse_pos = sf::Mouse::getPosition(window); 89 | 90 | sf::Event event; 91 | while (window.pollEvent(event)) { 92 | if (event.type == sf::Event::Closed) { 93 | window.close(); 94 | } else if (event.type == sf::Event::KeyReleased) { 95 | if (event.key.code == sf::Keyboard::Space) { 96 | tree = v2::TreeBuilder::build(Vec2(WinWidth * 0.5f, WinHeight), tree_conf); 97 | } 98 | else if (event.key.code == sf::Keyboard::B) { 99 | draw_branches = !draw_branches; 100 | } 101 | else if (event.key.code == sf::Keyboard::L) { 102 | draw_leaves = !draw_leaves; 103 | } 104 | else if (event.key.code == sf::Keyboard::D) { 105 | draw_debug = !draw_debug; 106 | } 107 | else if (event.key.code == sf::Keyboard::W) { 108 | draw_wind_debug = !draw_wind_debug; 109 | } 110 | else { 111 | boosting = false; 112 | wind[0].strength = base_wind_force; 113 | } 114 | } 115 | else if (event.type == sf::Event::KeyPressed) { 116 | if (event.key.code == sf::Keyboard::Space) { 117 | 118 | } 119 | else if (event.key.code == sf::Keyboard::Escape) { 120 | window.close(); 121 | } 122 | else if (event.key.code == sf::Keyboard::Up) { 123 | for (Wind& w : wind) { 124 | w.strength *= 1.2f; 125 | } 126 | } 127 | else if (event.key.code == sf::Keyboard::Down) { 128 | for (Wind& w : wind) { 129 | w.strength /= 1.2f; 130 | } 131 | } 132 | else if (event.key.code == sf::Keyboard::W) { 133 | base_wind_force = 1.0f; 134 | } 135 | else { 136 | boosting = true; 137 | if (current_wind_force < max_wind_force) { 138 | current_wind_force += 0.1f; 139 | } 140 | wind[0].strength = current_wind_force; 141 | } 142 | } 143 | } 144 | 145 | for (Wind& w : wind) { 146 | w.update(dt, WinWidth); 147 | } 148 | 149 | tree.applyWind(wind); 150 | 151 | if (boosting) { 152 | for (v2::Branch& b : tree.branches) { 153 | b.segment.moving_point.acceleration += Vec2(1.0f, 0.0f) * wind_force; 154 | } 155 | } 156 | 157 | sf::Clock profiler_clock; 158 | tree.updateBranches(dt); 159 | const float elapsed_b = static_cast(profiler_clock.getElapsedTime().asMicroseconds()); 160 | time_sum_branches += elapsed_b; 161 | 162 | profiler_clock.restart(); 163 | tree.updateLeaves(dt); 164 | const float elapsed_l = static_cast(profiler_clock.getElapsedTime().asMicroseconds()); 165 | time_sum_leaves += elapsed_l; 166 | 167 | profiler_clock.restart(); 168 | tree.updateStructure(); 169 | const float elapsed_r = static_cast(profiler_clock.getElapsedTime().asMicroseconds()); 170 | time_sum_rest += elapsed_r; 171 | 172 | window.clear(sf::Color::Black); 173 | 174 | const float text_offset = 24.0f; 175 | float text_y = 10.0f; 176 | text_profiler.setString("Structure simulation " + toString(int(time_sum_branches / img_count)) + " us"); 177 | text_profiler.setPosition(10.0f, text_y); 178 | window.draw(text_profiler); 179 | text_y += text_offset; 180 | 181 | text_profiler.setString("Leaves simulation " + toString(int(time_sum_leaves / img_count)) + " us"); 182 | text_profiler.setPosition(10.0f, text_y); 183 | window.draw(text_profiler); 184 | text_y += text_offset; 185 | 186 | text_profiler.setString("Structure update " + toString(int(time_sum_rest / img_count)) + " us"); 187 | text_profiler.setPosition(10.0f, text_y); 188 | window.draw(text_profiler); 189 | text_y += 2.0f * text_offset; 190 | 191 | text_profiler.setString("Physic simulation time " + toString(0.001f * ((time_sum_leaves + time_sum_branches + time_sum_rest) / img_count), true) + "ms"); 192 | text_profiler.setPosition(10.0f, text_y); 193 | window.draw(text_profiler); 194 | 195 | TreeRenderer::generateRenderData(tree, branches_va, leaves_va); 196 | if (draw_branches) { 197 | for (const auto& va : branches_va) { 198 | window.draw(va); 199 | } 200 | } 201 | if (draw_leaves) { 202 | sf::RenderStates states; 203 | states.texture = &texture; 204 | window.draw(leaves_va, states); 205 | } 206 | 207 | if (draw_debug) { 208 | sf::VertexArray va_debug(sf::Lines, 2 * tree.branches.size()); 209 | uint32_t i = 0; 210 | for (const v2::Branch& b : tree.branches) { 211 | va_debug[2 * i + 0].position = sf::Vector2f(b.segment.attach_point.x, b.segment.attach_point.y); 212 | va_debug[2 * i + 1].position = sf::Vector2f(b.segment.moving_point.position.x, b.segment.moving_point.position.y); 213 | va_debug[2 * i + 0].color = sf::Color::Red; 214 | va_debug[2 * i + 1].color = sf::Color::Red; 215 | ++i; 216 | } 217 | window.draw(va_debug); 218 | 219 | i = 0; 220 | for (const v2::Branch& b : tree.branches) { 221 | const float joint_strength(4000.0f * std::powf(0.4f, float(b.level))); 222 | const float length = joint_strength * 0.03f; 223 | sf::Vector2f bot(b.segment.moving_point.position.x, b.segment.moving_point.position.y); 224 | const Vec2& dir = b.segment.direction.getNormalized(); 225 | sf::Vector2f top(bot + length * sf::Vector2f(dir.x, dir.y)); 226 | va_debug[2 * i + 0].position = bot; 227 | va_debug[2 * i + 1].position = top; 228 | va_debug[2 * i + 0].color = sf::Color::Green; 229 | va_debug[2 * i + 1].color = sf::Color::Green; 230 | ++i; 231 | } 232 | window.draw(va_debug); 233 | } 234 | 235 | if (draw_wind_debug) { 236 | for (const Wind& w : wind) { 237 | sf::RectangleShape wind_debug(sf::Vector2f(w.width, WinHeight)); 238 | wind_debug.setPosition(w.pos_x - w.width * 0.5f, 0.0f); 239 | wind_debug.setFillColor(sf::Color(255, 0, 0, 100)); 240 | window.draw(wind_debug); 241 | } 242 | } 243 | 244 | img_count += 1.0f; 245 | 246 | window.display(); 247 | } 248 | 249 | return 0; 250 | } 251 | --------------------------------------------------------------------------------