├── LICENSE ├── README.md ├── ecosim-logo.png ├── screenshot.png ├── screenshot_logger.png └── src ├── .ycm_extra_conf.py ├── Makefile ├── agents.c ├── agents.h ├── ambient.wav ├── config.h ├── ecosim_with_log.sh ├── graphics.c ├── graphics.h ├── input.c ├── input.h ├── logger.c ├── logger.h ├── logger_plot.py ├── main.c ├── quadtree.c ├── quadtree.h ├── utils.c └── utils.h /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Ecosim Logo](ecosim-logo.png) 2 | 3 | ![Screenshot of ecosim](screenshot.png) 4 | 5 | ## Requirements 6 | * GNU/Linux 7 | * make 8 | * gcc 9 | * libglfw3 10 | * libglew2.0 11 | * libglfw3-dev 12 | * libglew-dev 13 | * ffplay (optional) 14 | * python3 (optional) 15 | * matplotlib (optional) 16 | ## How to use 17 | Install dependencies 18 | 19 | `$ sudo apt-get install libglfw3 libglew2.0 libglfw3-dev libglew-dev ffmpeg` 20 | 21 | Grab the code: 22 | 23 | `$ git clone https://github.com/connor-brooks/ecosim.git` 24 | 25 | Enter source directory: 26 | 27 | `$ cd ecosim/src` 28 | 29 | Build: 30 | 31 | `$ make` 32 | 33 | Run the simulation: 34 | 35 | `$ ./ecosim` 36 | ## Controls 37 | * Zoom: `ctrl` + `scroll wheel` 38 | * Pan: `scroll wheel` 39 | * Pause: `space` 40 | * Quit: `q` 41 | * Insert agent: `left click` 42 | * Cycle agents: `left click` (hold) 43 | 44 | ## Behaviour of agents 45 | Agents within the simulation constantly loose energy over time, as moving around in the environment causes energy to be burned. The only way energy levels can be increased is via consumption. 46 | 47 | Dietary preferences of agents vary, but fall into two main categories: 48 | * Primary consumers - Agents which only take energy from non-living entities. 49 | * Secondary consumers - Agents which only take energy from living agents, killing them in the process. 50 | 51 | If an agents energy drops below a threshold, death occurs. In contrast, passing above a another threshold causes the agent to split into two copies of itself (asexual reproduction). 52 | 53 | When an agent splits, it doesn't always make an exact copy of itself. Sometimes mutation occurs. This means that the genetic code (DNA) that dictates the agents behaviours is slightly modified. 54 | 55 | These genetic traits are as follows: 56 | * Metabolic rate 57 | * Vision range 58 | * Rebirth rate 59 | * Dietary preference 60 | * Flocking strength 61 | * Wobble frequency 62 | 63 | Over time, only the most successful agents are able to pass on their DNA, causing the population of agents to slowly evolve. 64 | 65 | However, it is important to note that there is no one-size-fits-all perfect agent. For a well functioning ecosystem to exist, balance is essential. For example: the existence of carnivores is essential to the existence of the herbivores in order to prevent overpopulation, which would result in mass-starvation and extinction of the herbivores. 66 | 67 | ## Traits in detail 68 | ### Metabolic rate 69 | The metabolic rate is the rate which an agent transforms stored energy into kinetic energy. This has both pros and cons. Being able to move around the environment faster allows agents to consume food faster, but also causes the agent to eat more frequently. 70 | ### Vision range 71 | This trait dictates how far an agent can sense other entities. A larger vision range is generally beneficial. However, this trait has some minor downfalls, especially for herbivorous agents. For example: hypersensitivity to other agents causes fleeing from non-dangerous situations, which in turn results in reduced ability to consume. 72 | ### Rebirth rate 73 | Rebirth rate controls how much energy is stored within an agent before splitting (asexual reproduction) occurs. This is especially useful for agents with low metabolism, as it allows them to navigate the environment for a longer time without depleting their energy. 74 | ### Dietary preference 75 | Agent's dietary preference influences what food source an agent will pursue and consume: living or non-living. For an ecosystem to be stable, the need for agents of both these groups is required. 76 | ### Flocking strength 77 | Flocking is a behaviour in which agents form groups, or clusters, whilst navigating the map, forming "multicellular life". Paradoxically this offers both safety and vulnerability. By sharing information whilst flocking, a group increases it's members knowledge of other potential dangers or food. In contrast, if the group fails to avoid a potential danger the whole group suffers. 78 | ### Wobble frequency 79 | Whilst moving around the screen, agents speed up and down in a sinusoidal pattern, creating a "crawling" effect. The frequency of this movement is dictated by the wobble trait. Wobbling has both advantages and disadvantages, with a lower frequency resulting in longer periods of increased speed but also longer resting periods. 80 | 81 | 82 | ## Tweaking the simulation 83 | Ecosim provides a way to configure the mechanics of the simulation via editing the `config.h` file. The process of doing so is as follows: 84 | 85 | * Enter the `src` directory 86 | * Edit the `config.h` file with the text editor of your choice 87 | * Run `make clean` 88 | * Run `make` 89 | 90 | ### What to change 91 | This is entirely down to personal preference, however, most people will only be concerned with two sections of the configuration: The general agent settings, and the agent DNA settings. 92 | 93 | ### Enable logging 94 | In order to use the population/trait logger, change the `LOGGER_ENABLE` setting to `1`, recompile, and run: 95 | 96 | `./ecosim_with_log.sh` 97 | 98 | Note, python3 and matplotlib is required in order to plot logged data. 99 | 100 | ![Screenshot of ecosim in logger mode](screenshot_logger.png) 101 | 102 | 103 | ### List of config settings 104 | #### Main world settings 105 | * `DEV_AGENT_COUNT` - The amount of agents to spawn when the simulation runs 106 | * `DEV_GAME_FPS` - Frames per second (FPS) of the simulation 107 | * `DEV_GAME_FOOD_SPAWN_FREQ` - How often (in seconds) to spawn food 108 | * `DEV_GAME_FOOD_SPAWN_INIT` - How many items of food to spawn when the simulation runs 109 | * `DEV_GAME_FOOD_SPAWN_MAX`/`DEV_GAME_FOOD_SPAWN_MIN` - The maximum and minimum amount of food that is spawned each food spawn 110 | * `DEV_GAME_FOOD_ENERGY` - The amount of energy a piece of food provides when consumed 111 | #### Agent general settings 112 | * `AGENT_RGB_ALPHA` - Transparency of agent's inner cell 113 | * `AGENT_VIS_ALPHA` - Transparaney of agent's vision field 114 | * `AGENT_MAX_VELOCITY`/`AGENT_MIN_VELOCITY` - Agent velocity maximum / minimum 115 | * `AGENT_ENERGY_DEFAULT` - Default energy of agents spawned at startup 116 | * `AGENT_METAB_ENERGY_SCALE(x)` - The rate of which energy is burned over time, with respect to the metabolic rate of an agent (where x is metabolism) 117 | * `AGENT_ENERGY_SIZE_SCALE(x)` - How large an agent is, with respect to their energy (where x is energy) 118 | * `AGENT_MAX_SPEED` - The maximum speed any agent can move 119 | * `AGENT_ENERGY_DEAD` - The energy level which an agent dies 120 | * `AGENT_TIME_FACTOR` - How fast the process of ageing occurs 121 | * `AGENT_DIET_BOUNDARY` - Herbivore / Carnivore boundary 122 | #### Agent DNA settings 123 | * `AGENT_DNA_MUTATE_RATE` - The maximum amount a trait can change if mutation occurs 124 | * `AGENT_METAB_MAX`/`AGENT_METAB_MIN` - The maximum and minimum metabolic rates allowed 125 | * `AGENT_VISION_MAX`/`AGENT_VISION_MIN` - The maximum and minimum vision field sizes allowed 126 | * `AGENT_REBIRTH_MAX`/`AGENT_REBIRTH_MIN` - The maximum and minimum amount of energy stored in an agent before splitting occurs 127 | * `AGENT_DIET_MAX`/`AGENT_DIET_MIN` - The maximum and minimum diet values allowed 128 | * `AGENT_FLOCK_MAX`/`AGENT_FLOCK_MIN` - The maximum and minimum influence flocking has on agents 129 | * `AGENT_WOBBLE_MAX`/`AGENT_WOBBLE_MIN` - The maximum and minimum amount an agent can wobble per second 130 | #### Input settings 131 | * `INPUT_SPAWN_DELAY` - How often to respawn agents when the mouse is held down 132 | * `INPUT_SCROLL_AMT` - Input sensitivity (Effects scroll and zoom) 133 | #### Log settings 134 | * `LOGGER_ENABLE` - Enable / disable logging 135 | * `LOGGER_FILE` - Filename to log to 136 | * `LOGGER_FREQ` - Logging sample rate 137 | 138 | ## Notes 139 | * If you'd like some background noise to play whilst running the simulation, install ffmpeg, otherwise it's not required 140 | * Please report any bugs or issues encountered whilst trying to run. Ideally please mention your distribution and graphics driver. 141 | * Windows and Mac users: Don't worry, once Ecosim is ported to SDL2 it will be made officially multiplatform 142 | -------------------------------------------------------------------------------- /ecosim-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/connor-brooks/ecosim/e615c48fda0c8107f6a79d28736c2643f73d6211/ecosim-logo.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/connor-brooks/ecosim/e615c48fda0c8107f6a79d28736c2643f73d6211/screenshot.png -------------------------------------------------------------------------------- /screenshot_logger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/connor-brooks/ecosim/e615c48fda0c8107f6a79d28736c2643f73d6211/screenshot_logger.png -------------------------------------------------------------------------------- /src/.ycm_extra_conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import ycm_core 3 | 4 | flags = [ 5 | '-Wall', 6 | '-Wextra', 7 | '-Werror', 8 | '-Wno-long-long', 9 | '-Wno-variadic-macros', 10 | '-fexceptions', 11 | '-ferror-limit=10000', 12 | '-DNDEBUG', 13 | '-std=c99', 14 | '-xc', 15 | '-isystem/usr/include/', 16 | ] 17 | 18 | SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', ] 19 | 20 | def FlagsForFile( filename, **kwargs ): 21 | return { 22 | 'flags': flags, 23 | 'do_cache': True 24 | } 25 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | 3 | all: ecosim 4 | 5 | CFLAGS=-g3 -pedantic -Wall -Wextra 6 | LIBS=-lGL -lm -lglfw -lGLEW 7 | 8 | 9 | ecosim: logger.o quadtree.o graphics.o utils.o agents.o input.o main.o 10 | $(CC) $(CFLAGS) logger.o quadtree.o graphics.o utils.o agents.o input.o main.o -o ecosim $(LIBS) 11 | 12 | main.o: main.c 13 | $(CC) ${CFLAGS} -c main.c 14 | 15 | input.o: input.c 16 | $(CC) ${CFLAGS} -c input.c 17 | 18 | agents.o: agents.c 19 | $(CC) ${CFLAGS} -c agents.c 20 | 21 | utils.o: utils.c 22 | $(CC) ${CFLAGS} -c utils.c 23 | 24 | graphics.o: graphics.c 25 | $(CC) ${CFLAGS} -c graphics.c 26 | 27 | quadtree.o: quadtree.c 28 | $(CC) ${CFLAGS} -c quadtree.c 29 | 30 | logger.o: logger.c 31 | $(CC) ${CFLAGS} -c logger.c 32 | 33 | clean: 34 | rm *.o 35 | rm ecosim 36 | -------------------------------------------------------------------------------- /src/agents.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "config.h" 9 | #include "utils.h" 10 | #include "agents.h" 11 | #include "quadtree.h" 12 | 13 | 14 | /* Create empty agent array */ 15 | Agent_array* 16 | agent_array_create() 17 | { 18 | Agent_array* temp = malloc(sizeof(Agent_array)); 19 | temp->count = 0; 20 | temp->capacity = AGENT_ARRAY_DEFAULT_SIZE; 21 | temp->size = sizeof(Agent*) * temp->capacity; 22 | temp->agents = malloc(temp->size); 23 | temp->count_change = 0; 24 | temp->clock = 0; 25 | 26 | return temp; 27 | } 28 | 29 | /* To insert agent to agent array */ 30 | void 31 | agent_array_insert(Agent_array* aa, Agent* a) 32 | { 33 | /* resize of needed */ 34 | if(aa->count + 1 >= aa->capacity){ 35 | aa->capacity *= 2; 36 | aa->size = sizeof(Agent*) * aa->capacity; 37 | aa->agents = realloc(aa->agents, aa->size); 38 | } 39 | /* save the agent to array */ 40 | aa->agents[aa->count] = a; 41 | aa->count++; 42 | } 43 | 44 | /* Remove non-visable agents from the array */ 45 | Agent_array* 46 | agent_array_prune(Agent_array* aa) 47 | { 48 | unsigned int i; 49 | Agent_array* tmp_aa; 50 | tmp_aa = agent_array_create(); 51 | tmp_aa->clock = aa->clock; 52 | for(i = 0; i < aa->count; i++) { 53 | if(aa->agents[i]->state != AGENT_STATE_PRUNE) { 54 | agent_array_insert(tmp_aa, aa->agents[i]); 55 | } 56 | } 57 | 58 | free(aa); 59 | return tmp_aa; 60 | } 61 | 62 | /* Create random agent */ 63 | Agent* 64 | agent_create_random() 65 | { 66 | Agent* tmp_agent = malloc(sizeof(Agent)); 67 | /* random DNA */ 68 | tmp_agent->dna.metabolism = RANDF_MIN(AGENT_METAB_MIN, AGENT_METAB_MAX); 69 | tmp_agent->dna.vision = RANDF_MIN(AGENT_VISION_MIN, AGENT_VISION_MAX); 70 | tmp_agent->dna.rebirth = RANDF_MIN(AGENT_REBIRTH_MIN, AGENT_REBIRTH_MAX); 71 | tmp_agent->dna.diet = RANDF_MIN(AGENT_DIET_MIN, AGENT_DIET_MAX); 72 | tmp_agent->dna.flock = RANDF_MIN(AGENT_FLOCK_MIN, AGENT_FLOCK_MAX); 73 | tmp_agent->dna.wobble= RANDF_MIN(AGENT_WOBBLE_MIN, AGENT_WOBBLE_MAX); 74 | 75 | /* random pos / directional info */ 76 | tmp_agent->x = RANDF_MIN(WORLD_MIN_COORD, WORLD_MAX_COORD); 77 | tmp_agent->y = RANDF_MIN(WORLD_MIN_COORD, WORLD_MAX_COORD); 78 | tmp_agent->velocity.x = RANDF_MIN(AGENT_MIN_VELOCITY, AGENT_MAX_VELOCITY); 79 | tmp_agent->velocity.y = RANDF_MIN(AGENT_MIN_VELOCITY, AGENT_MAX_VELOCITY); 80 | 81 | /* set colors based on DNA */ 82 | agent_setup_colors(tmp_agent); 83 | agent_setup_vision_quad(tmp_agent); 84 | 85 | // default state 86 | tmp_agent->state = AGENT_STATE_LIVING; 87 | tmp_agent->energy = AGENT_ENERGY_DEFAULT; 88 | 89 | return tmp_agent; 90 | } 91 | 92 | /* rules for colors */ 93 | void 94 | agent_setup_colors(Agent* a_ptr) 95 | { 96 | float *diet = &a_ptr->dna.diet; 97 | float *flocking = &a_ptr->dna.flock; 98 | 99 | a_ptr->rgb.r = (agent_diet(a_ptr) == AGENT_DIET_LIVING)? 100 | *diet: 101 | 0.0f; 102 | a_ptr->rgb.g = *flocking; 103 | a_ptr->rgb.b = (agent_diet(a_ptr))? 104 | 1.0 - (*diet) : 105 | 0.0f; 106 | 107 | /* Normalise color vector */ 108 | float mag = sqrt( 109 | (a_ptr->rgb.r * a_ptr->rgb.r) + 110 | (a_ptr->rgb.g * a_ptr->rgb.g) + 111 | (a_ptr->rgb.b * a_ptr->rgb.b) 112 | ); 113 | 114 | a_ptr->rgb.r /= mag; 115 | a_ptr->rgb.g /= mag; 116 | a_ptr->rgb.b /= mag; 117 | } 118 | 119 | void 120 | agent_array_to_quadtree(Agent_array* aa, Quadtree* q) 121 | { 122 | unsigned int i; 123 | Agent* tmp_agent; 124 | for(i = 0; i < aa->count; i++) { 125 | tmp_agent = (aa->agents[i]); 126 | float tmp_pos[] = {tmp_agent->x, tmp_agent->y}; 127 | if(tmp_agent->state != AGENT_STATE_PRUNE) 128 | quadtree_insert(q, tmp_agent, tmp_pos); 129 | } 130 | } 131 | 132 | 133 | int 134 | agents_food_drop(Agent_array* aa, float time, float last) 135 | { 136 | int ret = 0; 137 | int food_insert_amount = (int) RANDF_MIN(DEV_GAME_FOOD_SPAWN_MIN, 138 | DEV_GAME_FOOD_SPAWN_MAX); 139 | 140 | if(time > last + DEV_GAME_FOOD_SPAWN_FREQ) { 141 | agents_insert_dead(aa, food_insert_amount); 142 | printf("Food added \n"); 143 | ret = 1; 144 | } 145 | return ret; 146 | } 147 | 148 | 149 | /* Insert "dead" agents into the array so food */ 150 | void 151 | agents_insert_dead(Agent_array* aa, int count) 152 | { 153 | int i; 154 | Agent* tmp_agent; 155 | for(i = 0; i < count; i++) { 156 | tmp_agent = agent_create_random(); 157 | tmp_agent->state = AGENT_STATE_DEAD; 158 | tmp_agent->velocity.x = 0.00f; 159 | tmp_agent->velocity.y = 0.00f; 160 | tmp_agent->dna.diet = -1.0f; 161 | tmp_agent->energy *= DEV_GAME_FOOD_ENERGY; 162 | 163 | tmp_agent->rgb.r = 0.2; 164 | tmp_agent->rgb.g = 0.2; 165 | tmp_agent->rgb.b = 0.2; 166 | agent_array_insert(aa, tmp_agent); 167 | } 168 | } 169 | 170 | /* Calculate vision quad area */ 171 | void 172 | agent_setup_vision_quad(Agent* a_ptr) 173 | { 174 | float half_rad = a_ptr->dna.vision * 0.5; 175 | 176 | /* radius sized box around agent */ 177 | a_ptr->vis_quad.bot_left[0] = a_ptr->x - half_rad; 178 | a_ptr->vis_quad.bot_left[1] = a_ptr->y - half_rad; 179 | 180 | a_ptr->vis_quad.top_right[0] = a_ptr->x + half_rad; 181 | a_ptr->vis_quad.top_right[1] = a_ptr->y + half_rad; 182 | } 183 | 184 | /* Create a empty array and fill it with agents */ 185 | Agent_array* 186 | agent_array_setup_random(int count) 187 | { 188 | int i; 189 | Agent_array* tmp_aa = agent_array_create(); 190 | Agent* tmp_a = agent_create_random(); 191 | 192 | for(i = 0; i < count; i++) 193 | { 194 | tmp_a = agent_create_random(); 195 | agent_array_insert(tmp_aa, tmp_a); 196 | } 197 | return tmp_aa; 198 | } 199 | 200 | /* Destroy agent array */ 201 | void 202 | agent_array_free(Agent_array* aa) 203 | { 204 | unsigned int i; 205 | /* free each agent */ 206 | for(i = 0; i < aa->count; i++) 207 | free(aa->agents[i]); 208 | 209 | /* free everything else */ 210 | free(aa->agents); 211 | free(aa); 212 | } 213 | 214 | /* update agents from agent array */ 215 | void 216 | agents_update(Agent_array* aa, Quadtree* quad) 217 | { 218 | unsigned int i, j; 219 | Agent* a_ptr; 220 | Agent_array* local_agents; 221 | 222 | // printf("time %f clock %f\n", glfwGetTime(), aa->clock); 223 | aa->clock += 0.05; 224 | 225 | for(i = 0; i < aa->count; i++) 226 | { 227 | /* temp pointer for agent */ 228 | a_ptr = aa->agents[i]; 229 | 230 | // If the agent isn't living, don't update anything 231 | if(a_ptr->state != AGENT_STATE_LIVING) continue; 232 | 233 | /* get local agents */ 234 | local_agents = agents_get_local(a_ptr, quad, a_ptr->dna.vision); 235 | 236 | /* flock */ 237 | agent_mv_flock(a_ptr, local_agents); 238 | agent_normalize_velocity(a_ptr); 239 | 240 | /* avoid or attrack */ 241 | for(j = 0; j < local_agents->count; j++){ 242 | agent_update_mv_avoid(a_ptr, local_agents->agents[j]); 243 | agent_item_collision(a_ptr, local_agents->agents[j]); 244 | } 245 | 246 | /* updates */ 247 | agents_update_location(a_ptr, aa->clock); 248 | agents_update_energy(a_ptr); 249 | agent_split(a_ptr, aa); 250 | 251 | /* radius sized box around agent */ 252 | agent_setup_vision_quad(a_ptr); 253 | float *bot_left = (a_ptr->vis_quad.bot_left); 254 | float *top_right = (a_ptr->vis_quad.top_right); 255 | /* debug */ 256 | if(AGENT_DEBUG_SHOW_VISION){ 257 | //quadtree_query_dump(query); 258 | glColor4f(a_ptr->rgb.r, a_ptr->rgb.g, a_ptr->rgb.b, 0.5); 259 | glBegin(GL_LINE_LOOP); 260 | glVertex3f(bot_left[0], bot_left[1], 0.0); 261 | glVertex3f(top_right[0], bot_left[1], 0.0); 262 | glVertex3f(top_right[0], top_right[1], 0.0); 263 | glVertex3f(bot_left[0], top_right[1], 0.0); 264 | glEnd(); 265 | } 266 | } 267 | } 268 | 269 | /* Query the quadtree for local agents, 270 | * Check they are in line of sight 271 | * Ignore irrelevent agents */ 272 | Agent_array* 273 | agents_get_local(Agent* a_ptr, Quadtree* quad, float radius) 274 | { 275 | unsigned int i; 276 | int ignore; 277 | 278 | Quadtree_query* query = quadtree_query_setup(); 279 | Agent_array* agent_array = agent_array_create(); 280 | Agent* tmp_a; 281 | 282 | float pos[] = {a_ptr->x, a_ptr->y}; 283 | float *bot_left = (a_ptr->vis_quad.bot_left); 284 | float *top_right = (a_ptr->vis_quad.top_right); 285 | 286 | quadtree_query(quad, query, pos, radius); 287 | 288 | /* Loop through agents */ 289 | for(i = 0; i < query->ptr_count; i++) 290 | { 291 | tmp_a = query->ptrs[i]; 292 | ignore = 0; 293 | 294 | /* Ignore null pointers and self */ 295 | if(tmp_a == NULL) 296 | ignore = 1; 297 | if(a_ptr == tmp_a) 298 | ignore = 1; 299 | 300 | /* Ignore not within Agents line of sight? */ 301 | if(tmp_a->x > top_right[0] || 302 | tmp_a->x < bot_left[0] || 303 | tmp_a->y > top_right[1] || 304 | tmp_a->y < bot_left[1]) 305 | ignore = 1; 306 | 307 | /* Ignore pruneable agents */ 308 | if(tmp_a->state == AGENT_STATE_PRUNE) 309 | ignore = 1; 310 | 311 | /* Insert if valid agent */ 312 | if(!ignore) agent_array_insert(agent_array, tmp_a); 313 | } 314 | 315 | quadtree_query_free(query); 316 | return agent_array; 317 | } 318 | 319 | /* Move the agent based on their velocity */ 320 | void 321 | agents_update_location(Agent* a_ptr, float clock) 322 | { 323 | float mv_amt = agents_update_mv_amt(a_ptr); 324 | float wobble[] = {0.0, 0.0}; 325 | 326 | /* add a wobble to their step */ 327 | wobble[0] =(2.0 + sin(a_ptr->dna.wobble + a_ptr->dna.wobble * clock)) * 0.5; 328 | wobble[1] =(2.0 + sin(a_ptr->dna.wobble + a_ptr->dna.wobble * clock)) * 0.5; 329 | a_ptr->x += a_ptr->velocity.x * mv_amt * fabs(wobble[0]); 330 | a_ptr->y += a_ptr->velocity.y * mv_amt * fabs(wobble[1]); 331 | 332 | /* Wrap if needed */ 333 | agents_update_mv_wrap(a_ptr); 334 | } 335 | 336 | /* Calculate how much the agent can move */ 337 | float 338 | agents_update_mv_amt(Agent* a_ptr) 339 | { 340 | return AGENT_MAX_SPEED * a_ptr->dna.metabolism; 341 | } 342 | 343 | /* Wraps agents to the other side of the screen if they leave the visible 344 | * area. 345 | * This function is stupid. 346 | * Put X and y for agents into an arrary so this type 347 | * of thing doesn't exist. */ 348 | void 349 | agents_update_mv_wrap(Agent* a_ptr) 350 | { 351 | float* tmp_ptr; 352 | /* loop through x and y and put the agents at the opposite side 353 | * of the screen if they're past the ends */ 354 | for(tmp_ptr = &a_ptr->x; tmp_ptr <= &a_ptr->y; tmp_ptr++){ 355 | *tmp_ptr = ((*tmp_ptr > WORLD_MAX_COORD) || 356 | (*tmp_ptr <= WORLD_MIN_COORD)) ? 357 | -(*tmp_ptr)* 0.99: 358 | *tmp_ptr; 359 | } 360 | } 361 | 362 | /* Calculate the attitude of one agent about another*/ 363 | float 364 | agent_item_attraction(Agent* a_ptr, Agent* t_ptr) 365 | { 366 | float attraction = AGENT_NEUTRAL; 367 | 368 | int a_diet = agent_diet(a_ptr); 369 | 370 | int t_diet = agent_diet(t_ptr); 371 | 372 | int t_state = t_ptr->state; 373 | 374 | if(t_state == AGENT_STATE_PRUNE) return AGENT_NEUTRAL; 375 | 376 | /* meat eater vs other meat eater */ 377 | else if(a_diet == AGENT_DIET_LIVING && t_diet == AGENT_DIET_LIVING) 378 | attraction = AGENT_NEUTRAL; 379 | 380 | /* meat eater vs any living */ 381 | else if(a_diet == AGENT_DIET_LIVING && t_state == AGENT_STATE_LIVING) 382 | attraction = AGENT_ATTRACT; 383 | 384 | /* meat eater vs dead */ 385 | else if(a_diet == AGENT_DIET_LIVING && t_state == AGENT_STATE_DEAD) 386 | attraction = AGENT_NEUTRAL; 387 | 388 | /* plant eater vs dead */ 389 | else if(a_diet == AGENT_DIET_DEAD && t_state == AGENT_STATE_DEAD) 390 | attraction = AGENT_ATTRACT; 391 | 392 | /* plant eater vs platn eater */ 393 | else if(a_diet == AGENT_DIET_DEAD && t_diet == AGENT_DIET_DEAD) 394 | attraction = AGENT_NEUTRAL; 395 | 396 | /* plant eater vs meat eater*/ 397 | else if(a_diet == AGENT_DIET_DEAD && t_diet == AGENT_DIET_LIVING) 398 | attraction = AGENT_AVOID; 399 | 400 | /* plant eater vs living */ 401 | else if(a_diet == AGENT_DIET_DEAD && t_state == AGENT_STATE_LIVING) 402 | attraction = AGENT_NEUTRAL; 403 | 404 | return attraction; 405 | } 406 | 407 | /* Normalise velocity */ 408 | void 409 | agent_normalize_velocity(Agent* a_ptr) 410 | { 411 | float mag = sqrt((a_ptr->velocity.x * a_ptr->velocity.x) 412 | + (a_ptr->velocity.y * a_ptr->velocity.y)); 413 | a_ptr->velocity.x = a_ptr->velocity.x / mag; 414 | a_ptr->velocity.y = a_ptr->velocity.y / mag; 415 | } 416 | 417 | /* Move agent with respect to other entities */ 418 | void 419 | agent_update_mv_avoid(Agent* a_ptr, Agent* t_ptr) 420 | { 421 | float diff[] = {0.0f, 0.0f}; 422 | float new_vel[] = {0.0f, 0.0f}; 423 | float mag = 0.0f; 424 | float attraction; 425 | 426 | /* calc difference */ 427 | diff[0] = a_ptr->x - t_ptr->x; 428 | diff[1] = a_ptr->y - t_ptr->y; 429 | 430 | /* Noramlise and create new vector */ 431 | mag = sqrt((diff[0] * diff[0]) + (diff[1] * diff[1])); 432 | new_vel[0] = (diff[0] / mag); 433 | new_vel[1] = (diff[1] / mag); 434 | 435 | /* Is agent attracted or repeled? */ 436 | attraction = agent_item_attraction(a_ptr, t_ptr); 437 | 438 | /* sub from existing vector */ 439 | a_ptr->velocity.x -= new_vel[0] * attraction; 440 | a_ptr->velocity.y -= new_vel[1] * attraction; 441 | 442 | /* lines */ 443 | glColor4f(a_ptr->rgb.r, a_ptr->rgb.g, a_ptr->rgb.b, 0.25); 444 | glLineWidth(2.0f); 445 | glBegin(GL_LINES); 446 | glVertex3f(t_ptr->x, t_ptr->y, 0.0); 447 | glVertex3f(a_ptr->x, a_ptr->y, 0.0); 448 | glEnd(); 449 | glLineWidth(1.0); 450 | 451 | agent_normalize_velocity(a_ptr); 452 | } 453 | 454 | /* Flock agents */ 455 | void agent_mv_flock(Agent* a_ptr, Agent_array* aa) 456 | { 457 | float final_vel[] = {0.0f, 0.0f}; 458 | float* align_vel = agent_mv_flock_align(a_ptr, aa); 459 | float* cohes_vel = agent_mv_flock_cohesion(a_ptr, aa); 460 | float* serpa_vel = agent_mv_flock_seperation(a_ptr, aa); 461 | 462 | /* Velocity alignment */ 463 | final_vel[0] += align_vel[0]; 464 | final_vel[1] += align_vel[1]; 465 | 466 | /* Cohesion: Go to center of mass */ 467 | final_vel[0] += cohes_vel[0]; 468 | final_vel[1] += cohes_vel[1]; 469 | 470 | /* Seperation: Avoid otherss */ 471 | final_vel[0] += serpa_vel[0]; 472 | final_vel[1] += serpa_vel[1]; 473 | 474 | free(align_vel); 475 | free(cohes_vel); 476 | free(serpa_vel); 477 | 478 | a_ptr->velocity.x += final_vel[0] * a_ptr->dna.flock; 479 | a_ptr->velocity.y += final_vel[1] * a_ptr->dna.flock; 480 | // agent_normalize_velocity(a_ptr); 481 | 482 | } 483 | 484 | /* Align with neutral agents */ 485 | float* 486 | agent_mv_flock_align(Agent* a_ptr, Agent_array* aa) 487 | { 488 | unsigned int i; 489 | int count = 0; 490 | float* return_vel = malloc(sizeof(float) * 2); 491 | float total[] = {0.0f, 0.0f}; 492 | float avg[] = {0.0f, 0.0f}; 493 | float mag; 494 | float attraction; 495 | 496 | return_vel[0] = 0.0f; 497 | return_vel[1] = 0.0f; 498 | 499 | /* Get total */ 500 | for(i = 0; i < aa->count; i++) { 501 | attraction = agent_item_attraction(a_ptr, aa->agents[i]); 502 | if(aa->agents[i]->state == AGENT_STATE_LIVING && attraction == 0) 503 | { 504 | total[0] += aa->agents[i]->velocity.x; 505 | total[1] += aa->agents[i]->velocity.y; 506 | count++; 507 | } 508 | } 509 | if(count == 0) return return_vel; 510 | 511 | /* Get average */ 512 | avg[0] = total[0] / (float) count; 513 | avg[1] = total[1] / (float) count; 514 | 515 | /* Normalise */ 516 | mag = sqrt((avg[0] * avg[0]) + (avg[1] * avg[1])); 517 | if(mag == 0) return return_vel; 518 | return_vel[0] = avg[0] / mag; 519 | return_vel[1] = avg[1] / mag; 520 | 521 | return return_vel; 522 | } 523 | 524 | /* Go towards center of mass */ 525 | float* 526 | agent_mv_flock_cohesion(Agent* a_ptr, Agent_array* aa) 527 | { 528 | unsigned int i; 529 | int count = 0; 530 | float* return_vel = malloc(sizeof(float) * 2); 531 | float total[] = {0.0f, 0.0f}; 532 | float avg[] = {0.0f, 0.0f}; 533 | float mass_dir[] = {0.0f, 0.0f}; 534 | float mag; 535 | float attraction; 536 | 537 | return_vel[0] = 0.0f; 538 | return_vel[1] = 0.0f; 539 | 540 | /* Get total */ 541 | for(i = 0; i < aa->count; i++) { 542 | attraction = agent_item_attraction(a_ptr, aa->agents[i]); 543 | if(aa->agents[i]->state == AGENT_STATE_LIVING && 544 | attraction == AGENT_NEUTRAL) 545 | { 546 | total[0] += aa->agents[i]->x; 547 | total[1] += aa->agents[i]->y; 548 | count++; 549 | } 550 | } 551 | if(count == 0) return return_vel; 552 | 553 | /* Get average */ 554 | avg[0] = total[0] / (float) count; 555 | avg[1] = total[1] / (float) count; 556 | 557 | /* Direction to center of mass */ 558 | mass_dir[0] = avg[0] - a_ptr->x; 559 | mass_dir[1] = avg[1] - a_ptr->y; 560 | 561 | /* Normalise */ 562 | mag = sqrt((mass_dir[0] * mass_dir[0]) + (mass_dir[1] * mass_dir[1])); 563 | if(mag == 0) return return_vel; 564 | return_vel[0] = mass_dir[0] / mag; 565 | return_vel[1] = mass_dir[1] / mag; 566 | 567 | return return_vel; 568 | 569 | } 570 | 571 | /* Seperate from each other */ 572 | float* 573 | agent_mv_flock_seperation(Agent* a_ptr, Agent_array* aa) 574 | { 575 | unsigned int i; 576 | int count = 0; 577 | float* return_vel = malloc(sizeof(float) * 2); 578 | float total[] = {0.0f, 0.0f}; 579 | float avg[] = {0.0f, 0.0f}; 580 | float mag; 581 | float attraction; 582 | 583 | return_vel[0] = 0.0f; 584 | return_vel[1] = 0.0f; 585 | 586 | /* Get total */ 587 | for(i = 0; i < aa->count; i++) { 588 | attraction = agent_item_attraction(a_ptr, aa->agents[i]); 589 | if(aa->agents[i]->state == AGENT_STATE_LIVING && 590 | attraction == AGENT_NEUTRAL) 591 | { 592 | total[0] += aa->agents[i]->x - a_ptr->x; 593 | total[1] += aa->agents[i]->y - a_ptr->y; 594 | count++; 595 | } 596 | } 597 | if(count == 0) return return_vel; 598 | 599 | /* Get average */ 600 | avg[0] = total[0] / (float) count; 601 | avg[1] = total[1] / (float) count; 602 | 603 | avg[0] *= -1; 604 | avg[1] *= -1; 605 | 606 | /* Normalise */ 607 | mag = sqrt((avg[0] * avg[0]) + (avg[1] * avg[1])); 608 | if(mag == 0) return return_vel; 609 | return_vel[0] = avg[0] / mag; 610 | return_vel[1] = avg[1] / mag; 611 | 612 | return return_vel; 613 | } 614 | 615 | /* Act on collision of two agents */ 616 | void 617 | agent_item_collision(Agent* a_ptr, Agent* t_ptr) 618 | { 619 | float close = 0.02; 620 | 621 | int a_diet = agent_diet(a_ptr); 622 | int t_diet = agent_diet(t_ptr); 623 | 624 | 625 | /* Is the agent close enough to the target? */ 626 | if(!((a_ptr->x - close < t_ptr->x) & (a_ptr->x + close > t_ptr->x) && 627 | (a_ptr->y - close < t_ptr->y) & (a_ptr->y + close > t_ptr->y))) { 628 | return; 629 | } 630 | 631 | switch(t_ptr->state){ 632 | case AGENT_STATE_LIVING: 633 | if(a_diet == AGENT_DIET_LIVING && 634 | (t_diet == AGENT_DIET_DEAD)) 635 | { 636 | t_ptr->state = AGENT_STATE_PRUNE; 637 | a_ptr->energy += t_ptr->energy; 638 | } 639 | break; 640 | case AGENT_STATE_DEAD: 641 | if(a_diet == AGENT_DIET_DEAD) { 642 | t_ptr->state = AGENT_STATE_PRUNE; 643 | a_ptr->energy += t_ptr->energy; 644 | } 645 | break; 646 | } 647 | } 648 | 649 | /* Create a copy of self */ 650 | void 651 | agent_split(Agent* a_ptr, Agent_array* aa) 652 | { 653 | Agent* tmp_agent; 654 | /* if energy is higher than rebirth threshold */ 655 | if(a_ptr->energy > a_ptr->dna.rebirth) 656 | { 657 | a_ptr->energy *= 0.5; 658 | tmp_agent = agent_create_random(); 659 | 660 | tmp_agent->dna = a_ptr->dna; 661 | agent_dna_mutate(tmp_agent); 662 | 663 | tmp_agent->x = a_ptr->x + 0.06; 664 | tmp_agent->y = a_ptr->y + 0.06; 665 | 666 | tmp_agent->velocity.x = a_ptr->velocity.x; 667 | tmp_agent->velocity.y = a_ptr->velocity.y; 668 | 669 | agent_array_insert(aa, tmp_agent); 670 | } 671 | } 672 | 673 | void 674 | agent_dna_mutate_trait(float* trait, float rate, float probability) 675 | { 676 | *trait += 677 | (RANDF(1) > probability)? 678 | RANDF_MIN(-1.0, 1.0) * rate : 679 | 0; 680 | } 681 | 682 | void 683 | agent_dna_trait_constrain(float* trait, float min, float max) 684 | { 685 | if(*trait < min) *trait = min; 686 | else if(*trait > max) *trait = max; 687 | } 688 | 689 | void 690 | agent_dna_mutate(Agent* a_ptr) 691 | { 692 | float rate = AGENT_DNA_MUTATE_RATE; 693 | 694 | /* Mutate traits */ 695 | agent_dna_mutate_trait(&(a_ptr->dna.metabolism), rate, 0.3); 696 | agent_dna_mutate_trait(&(a_ptr->dna.vision), rate, 0.3); 697 | agent_dna_mutate_trait(&(a_ptr->dna.rebirth), rate, 0.3); 698 | agent_dna_mutate_trait(&(a_ptr->dna.diet), rate, 0.3); 699 | agent_dna_mutate_trait(&(a_ptr->dna.flock), rate, 0.3); 700 | agent_dna_mutate_trait(&(a_ptr->dna.wobble), rate, 0.3); 701 | 702 | /* Stop going over max or under min */ 703 | agent_dna_trait_constrain(&(a_ptr->dna.vision), 704 | AGENT_VISION_MIN, AGENT_VISION_MAX); 705 | agent_dna_trait_constrain(&(a_ptr->dna.metabolism), 706 | AGENT_METAB_MIN, AGENT_METAB_MAX); 707 | agent_dna_trait_constrain(&(a_ptr->dna.rebirth), 708 | AGENT_REBIRTH_MIN, AGENT_REBIRTH_MAX); 709 | agent_dna_trait_constrain(&(a_ptr->dna.diet), 710 | AGENT_DIET_MIN, AGENT_DIET_MAX); 711 | agent_dna_trait_constrain(&(a_ptr->dna.flock), 712 | AGENT_FLOCK_MIN, AGENT_FLOCK_MAX); 713 | agent_dna_trait_constrain(&(a_ptr->dna.wobble), 714 | AGENT_WOBBLE_MIN, AGENT_WOBBLE_MAX); 715 | 716 | agent_setup_colors(a_ptr); 717 | } 718 | 719 | void 720 | agents_update_energy(Agent* a_ptr) 721 | { 722 | a_ptr->energy -= AGENT_METAB_ENERGY_SCALE(a_ptr->dna.metabolism) * 723 | AGENT_TIME_FACTOR; 724 | if(a_ptr->energy < AGENT_ENERGY_DEAD) { 725 | a_ptr->state = AGENT_STATE_DEAD; 726 | a_ptr->velocity.x = 0.0f; 727 | a_ptr->velocity.y = 0.0f; 728 | a_ptr->dna.diet = -1.0f; 729 | a_ptr->rgb.r = 0.2; 730 | a_ptr->rgb.g = 0.2; 731 | a_ptr->rgb.b = 0.2; 732 | } 733 | } 734 | 735 | Agent_verts* 736 | agent_verts_create() 737 | { 738 | Agent_verts* tmp = malloc(sizeof(Agent_verts)); 739 | tmp->capacity = 16; 740 | tmp->size = sizeof(float) * 4 * tmp->capacity;; 741 | tmp->verts_pos = malloc(tmp->size); 742 | tmp->verts_col = malloc(tmp->size); 743 | tmp->a_count = 0; 744 | tmp->end = 0; 745 | return tmp; 746 | } 747 | 748 | void 749 | agent_verts_free(Agent_verts* av) 750 | { 751 | free(av->verts_pos); 752 | free(av->verts_col); 753 | free(av); 754 | } 755 | 756 | void 757 | agents_to_verts(Agent_array* aa, Agent_verts* av) 758 | { 759 | unsigned int i; 760 | 761 | av->capacity = aa->count; 762 | av->size = sizeof(float) * 4 * av->capacity; 763 | av->verts_pos = realloc(av->verts_pos, av->size); 764 | av->verts_col = realloc(av->verts_col, av->size); 765 | 766 | av->end = 0; 767 | av->a_count= 0; 768 | 769 | for(i = 0; i < aa->count ; i++) { 770 | Agent* agent = aa->agents[i]; 771 | /* agent drawing */ 772 | float* pos = av->verts_pos; 773 | float* col = av->verts_col; 774 | 775 | /* rgb, x y and z*/ 776 | pos[av->end] = agent->x; 777 | col[av->end] = agent->rgb.r; 778 | av->end++; 779 | 780 | pos[av->end] = agent->y; 781 | col[av->end] = agent->rgb.g; 782 | av->end++; 783 | 784 | pos[av->end] = AGENT_ENERGY_SIZE_SCALE(agent->energy); 785 | col[av->end] = agent->rgb.b; 786 | av->end++; 787 | 788 | /* special vert data */ 789 | pos[av->end] = agent->dna.vision * 400; 790 | col[av->end] = agent->state; 791 | 792 | av->end++; 793 | av->a_count++; 794 | } 795 | } 796 | 797 | int 798 | agent_diet(Agent* tmp_agent) 799 | { 800 | int diet = (tmp_agent->dna.diet >= AGENT_DIET_BOUNDARY) ? 801 | AGENT_DIET_LIVING : 802 | AGENT_DIET_DEAD; 803 | return diet; 804 | 805 | } 806 | -------------------------------------------------------------------------------- /src/agents.h: -------------------------------------------------------------------------------- 1 | #ifndef AGENTS_H 2 | #define AGENTS_H 3 | #include 4 | #include 5 | #include "utils.h" 6 | #include "quadtree.h" 7 | 8 | typedef struct _Agent Agent; 9 | typedef struct _Agent_array Agent_array; 10 | typedef struct _Agent_verts Agent_verts; 11 | typedef struct _DNA DNA; 12 | 13 | enum agent_states { 14 | AGENT_STATE_PRUNE = -1, 15 | AGENT_STATE_DEAD = 0, 16 | AGENT_STATE_LIVING = 1 17 | }; 18 | 19 | enum agent_diet { 20 | AGENT_DIET_LIVING, 21 | AGENT_DIET_DEAD 22 | }; 23 | 24 | enum agent_attraction { 25 | AGENT_AVOID = -1, 26 | AGENT_NEUTRAL = 0, 27 | AGENT_ATTRACT = 1 28 | }; 29 | 30 | struct _DNA { 31 | float metabolism; 32 | float vision; 33 | float rebirth; 34 | float diet; /* -1 for dead, 1 for living */ 35 | float flock; 36 | float wobble; 37 | 38 | }; 39 | 40 | struct _Agent { 41 | float x; 42 | float y; 43 | 44 | RGB rgb; 45 | 46 | struct { 47 | float x; 48 | float y; 49 | } velocity; 50 | 51 | int state; 52 | float energy; 53 | DNA dna; 54 | 55 | struct { 56 | float bot_left[2]; 57 | float top_right[2]; 58 | } vis_quad; 59 | 60 | 61 | }; 62 | 63 | struct _Agent_array { 64 | Agent** agents; 65 | size_t count; 66 | size_t capacity; 67 | size_t size; 68 | int count_change; 69 | float clock; 70 | }; 71 | 72 | 73 | struct _Agent_verts { 74 | float* verts_pos, *verts_col; 75 | size_t capacity; 76 | size_t size; 77 | int a_count; 78 | ptrdiff_t end; 79 | }; 80 | 81 | 82 | Agent_array* agent_array_create(); 83 | Agent_array* agent_array_setup_random(int count); 84 | void agent_array_insert(Agent_array* aa, Agent* a); 85 | void agent_array_free(Agent_array* aa); 86 | Agent_array* agent_array_prune(Agent_array* aa); 87 | void agents_update(Agent_array* aa, Quadtree* quad); 88 | 89 | void agent_array_to_quadtree(Agent_array* aa, Quadtree* q); 90 | 91 | 92 | Agent_array* agents_get_local(Agent* a_ptr, Quadtree* quad, float radius); 93 | 94 | Agent* agent_create_random(); 95 | void agent_setup_colors(Agent* a_ptr); 96 | void agent_setup_vision_quad(Agent* a_ptr); 97 | void agents_update_location(Agent* a_ptr, float clock); 98 | void agents_update_energy(Agent* a_ptr); 99 | float agents_update_mv_amt(Agent* a_ptr); 100 | void agents_update_mv_wrap(Agent* a_ptr); 101 | void agents_insert_dead(Agent_array* aa, int count); 102 | 103 | int agents_food_drop(Agent_array* aa, float time, float last); 104 | 105 | void agent_normalize_velocity(Agent* a_ptr); 106 | void agent_update_mv_avoid(Agent* a_ptr, Agent* t_ptr); 107 | float agent_item_attraction(Agent* a_ptr, Agent* t_ptr); 108 | 109 | void agent_mv_flock(Agent* a_ptr, Agent_array* aa); 110 | float* agent_mv_flock_align(Agent* a_ptr, Agent_array* aa); 111 | float* agent_mv_flock_cohesion(Agent* a_ptr, Agent_array* aa); 112 | float* agent_mv_flock_seperation(Agent* a_ptr, Agent_array* aa); 113 | 114 | void agent_item_collision(Agent* a_ptr, Agent* t_ptr); 115 | 116 | void agent_split(Agent* a_ptr, Agent_array* aa); 117 | void agent_dna_mutate(Agent* a_ptr); 118 | void agent_dna_mutate_trait(float* trait, float rate, float probability); 119 | void agent_dna_trait_constrain(float* trait, float min, float max); 120 | 121 | /* verts */ 122 | Agent_verts* agent_verts_create(); 123 | void agent_verts_free(Agent_verts* av); 124 | void agents_to_verts(Agent_array* aa, Agent_verts* av); 125 | 126 | int agent_diet(Agent* tmp_agent); 127 | 128 | #endif 129 | 130 | -------------------------------------------------------------------------------- /src/ambient.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/connor-brooks/ecosim/e615c48fda0c8107f6a79d28736c2643f73d6211/src/ambient.wav -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | /* Main World config */ 5 | /* How many agents to initaly spawn */ 6 | #define DEV_AGENT_COUNT (90) 7 | /* Frames per second */ 8 | #define DEV_GAME_FPS (60) 9 | /* How often (in seconds) to spawn food */ 10 | #define DEV_GAME_FOOD_SPAWN_FREQ (4) 11 | #define DEV_GAME_FOOD_SPAWN_INIT (15) 12 | /* Max/min: How many foods to spawn each feeding time */ 13 | #define DEV_GAME_FOOD_SPAWN_MIN (5) 14 | #define DEV_GAME_FOOD_SPAWN_MAX (7) 15 | #define DEV_GAME_FOOD_ENERGY (0.5) 16 | /* End world config */ 17 | 18 | /* Input config */ 19 | /* The delay between re-spawn of agents when the mouse is held down */ 20 | #define INPUT_SPAWN_DELAY (0.5) 21 | /* Scroll sensitivity (also used for zoom */ 22 | #define INPUT_SCROLL_AMT (0.02) 23 | /* End input config */ 24 | 25 | /* Agent general config */ 26 | /* Agent transparency */ 27 | #define AGENT_RGB_ALPHA (0.9) 28 | /* Agent vision field transparency */ 29 | #define AGENT_VIS_ALPHA (0.2) 30 | /* Maximum agent velocity */ 31 | #define AGENT_MAX_VELOCITY (1.0) 32 | #define AGENT_MIN_VELOCITY (-1.0) 33 | /* Default energy for new-spawned agents */ 34 | #define AGENT_ENERGY_DEFAULT (1.0) 35 | /* The rate energy is burned over time, with respect to metabolism rates 36 | * (where x is agents metabolism */ 37 | #define AGENT_METAB_ENERGY_SCALE(x) (0.0015 * x) 38 | /* How large agents are, with respect to their energy (Where x is energy) */ 39 | #define AGENT_ENERGY_SIZE_SCALE(x) ((10 * x) + 4) 40 | /* The maximum speed any agents can move at */ 41 | #define AGENT_MAX_SPEED (0.0015) 42 | /* The energy level at which an agent dies */ 43 | #define AGENT_ENERGY_DEAD (0.3) 44 | /* How quickly ageing effects the agents */ 45 | #define AGENT_TIME_FACTOR (0.3) 46 | 47 | #define AGENT_DIET_BOUNDARY (0.5) 48 | /* End agent general config */ 49 | 50 | /* Agent DNA config */ 51 | /* The amount a DNA trait changes if mutation occurs */ 52 | #define AGENT_DNA_MUTATE_RATE (0.1) 53 | /* Metabolism trait max/min 54 | * How quickly an agent can move. Faster moving agents burn energy a lot 55 | * quicker */ 56 | #define AGENT_METAB_MAX (0.5) 57 | #define AGENT_METAB_MIN (0.05) 58 | /* Vision trait max/min 59 | * How wide the agents field of view is */ 60 | #define AGENT_VISION_MAX (0.1) 61 | #define AGENT_VISION_MIN (0.2) 62 | /* Rebirth trait max/min 63 | * How much energy is stored within an agent until it splits, creating a 64 | * possibly mutated clone of itself, halving it's energy */ 65 | #define AGENT_REBIRTH_MAX (3.00) 66 | #define AGENT_REBIRTH_MIN (1.00) 67 | /* Diet trait max/min 68 | * If greater or equal to zero, the agent eats other agents, if less than 69 | * zero, the agent eats only dead agents */ 70 | #define AGENT_DIET_MAX (1.00) 71 | #define AGENT_DIET_MIN (0.00) 72 | /* Flock max/min 73 | * How strong flocking behaviours influence the movement of an agent */ 74 | #define AGENT_FLOCK_MAX (1.00) 75 | #define AGENT_FLOCK_MIN (0.00) 76 | /* Wobble max/min 77 | * How many times per second the agent "wobbles" This is a movement based on 78 | * a sin wav, which gives a temporary boost of speed, followed by a equal 79 | * period of slower movement */ 80 | #define AGENT_WOBBLE_MAX (3.0) 81 | #define AGENT_WOBBLE_MIN (1.5) 82 | 83 | /*Log config */ 84 | #define LOGGER_ENABLE (0) 85 | #define LOGGER_FILE "logger_data.py" 86 | #define LOGGER_FREQ (2) 87 | 88 | /* Engine config: 89 | * DO NOT CHANGE UNLESS YOU KNOW WHAT YOU ARE DOING! */ 90 | #define QUADTREE_MAX_PER_CELL (8) 91 | #define AGENT_VERT_DIMS (4) 92 | #define AGENT_DIMENTIONS (4) 93 | #define AGENT_ARRAY_DEFAULT_SIZE (16) 94 | #define AGENT_DEBUG_SHOW_VISION (0) 95 | #define AGENT_VIS_VERTS_DEFAULT (16) 96 | #define AGENT_DRAWABLE_FEATURES (2) 97 | #define QUADTREE_VERT_LEN (3 * 4) 98 | #define QUADTREE_QUERY_SIZE (16) 99 | #define QUAD_COUNT (4) 100 | #define QUADTREE_DIMS (2) 101 | #define WORLD_MIN_COORD (-1.0) 102 | #define WORLD_MAX_COORD (1.0) 103 | #define LOG_DEFAULT_SIZE (12) 104 | #define QUAD_HEAD_POS (-1.0f) 105 | #define QUAD_HEAD_SIZE (2.0f) 106 | #define WINDOW_DEFAULT_X (1600) 107 | #define WINDOW_DEFAULT_Y (900) 108 | #define AGENT_ARRAY_PRUNE_TIME (2) 109 | /* End engine config */ 110 | #endif 111 | -------------------------------------------------------------------------------- /src/ecosim_with_log.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Starting Ecosim..." 3 | ./ecosim& 4 | sleep 1; 5 | echo "Starting logger plot" 6 | ./logger_plot.py 7 | -------------------------------------------------------------------------------- /src/graphics.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "graphics.h" 7 | #include "agents.h" 8 | #include "quadtree.h" 9 | #include "utils.h" 10 | 11 | Framebuffer* 12 | gfx_framebuffer_create(int width, int height) 13 | { 14 | Framebuffer* tmp_fb = malloc(sizeof(Framebuffer)); 15 | GLuint framebuffer; 16 | GLuint texBuffer; 17 | 18 | /* Make framebuffer */ 19 | glGenFramebuffers(1, &framebuffer); 20 | glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); 21 | 22 | glGenTextures(1, &texBuffer); 23 | glBindTexture(GL_TEXTURE_2D, texBuffer); 24 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, 25 | GL_UNSIGNED_BYTE, NULL); 26 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); 27 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 28 | glBindTexture(GL_TEXTURE_2D, 0); 29 | 30 | /* attach to framebuffer */ 31 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 32 | GL_TEXTURE_2D, texBuffer, 0); 33 | 34 | /* bind to default fb */ 35 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 36 | 37 | tmp_fb->framebuffer = framebuffer; 38 | tmp_fb->texBuffer = texBuffer; 39 | 40 | return tmp_fb; 41 | } 42 | 43 | void 44 | gfx_framebuffer_begin(Framebuffer* fb, World_view* wv) 45 | { 46 | glBindFramebuffer(GL_FRAMEBUFFER, fb->framebuffer); 47 | glBindTexture(GL_TEXTURE_2D, fb->texBuffer); 48 | glClearColor(0.0f, 0.0f, 0.0f, 1.0f); 49 | glClear(GL_COLOR_BUFFER_BIT); 50 | 51 | /* Main drawing begin */ 52 | glPushMatrix(); 53 | glScalef(wv->zoom, wv->zoom, 1.); 54 | glTranslatef(wv->pos_offsets[0], wv->pos_offsets[1], 1.0); 55 | } 56 | 57 | void 58 | gfx_framebuffer_end() 59 | { 60 | glPopMatrix(); 61 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 62 | } 63 | 64 | void 65 | gfx_framebuffer_draw(Framebuffer* fb, World_view* wv, GLuint shader) 66 | { 67 | glBindFramebuffer(GL_FRAMEBUFFER, 0); // back to default 68 | //glViewport(0, 0, width, height); 69 | glClearColor(1.0f, 1.0f, 1.0f, 1.0f); 70 | glClear(GL_COLOR_BUFFER_BIT); 71 | 72 | glBindTexture(GL_TEXTURE_2D, fb->texBuffer); 73 | 74 | glUseProgram(shader); 75 | GLuint zoom_u = glGetUniformLocation(shader, "zoom"); 76 | glUniform1f(zoom_u, wv->zoom); 77 | GLuint pos_u = glGetUniformLocation(shader, "pos_offset"); 78 | glUniform2f(pos_u, wv->pos_offsets[0], wv->pos_offsets[1]); 79 | 80 | /* Draw framebuffer*/ 81 | glPushMatrix(); 82 | glScalef(1.025, 1.025, 1.0); //hack 83 | glBegin(GL_QUADS); 84 | glVertex2f(-1, -1); 85 | glVertex2f(1, -1); 86 | glVertex2f(1, 1); 87 | glVertex2f(-1, 1); 88 | glEnd(); 89 | glPopMatrix(); 90 | 91 | glUseProgram(0); 92 | } 93 | 94 | GLuint 95 | gfx_setup_shader(const char* vs_raw, const char* fs_raw) 96 | { 97 | GLuint vs = 0; 98 | GLuint fs = 0; 99 | GLuint shader = 0; 100 | vs = glCreateShader(GL_VERTEX_SHADER); 101 | glShaderSource(vs, 1, &(vs_raw), NULL); 102 | glCompileShader(vs); 103 | 104 | fs = glCreateShader(GL_FRAGMENT_SHADER); 105 | glShaderSource(fs, 1, &(fs_raw), NULL); 106 | glCompileShader(fs); 107 | 108 | shader = glCreateProgram(); 109 | glAttachShader(shader, fs); 110 | glAttachShader(shader, vs); 111 | glLinkProgram(shader); 112 | return shader; 113 | } 114 | 115 | GLuint 116 | gfx_framebuffer_shader(){ 117 | const char* test_vs = 118 | "#version 130\n" 119 | "in vec4 position;" 120 | "in vec4 color_in;" 121 | "out vec4 color_out;" 122 | "out vec4 pos_out;" 123 | "void main() {" 124 | "pos_out = position;" 125 | "color_out = color_in;" 126 | "gl_Position = gl_ModelViewProjectionMatrix * position;" 127 | "}"; 128 | 129 | const char* test_fs = 130 | "#version 130\n" 131 | "in vec4 color_out;" 132 | "in vec4 pos_out;" 133 | "uniform sampler2D fbo_texture;" 134 | "uniform vec2 pos_offset;" 135 | "uniform float zoom;" 136 | 137 | "vec2 offset_tex(vec2 t_pos, vec2 wobble, float off_x, float off_y)" 138 | "{" 139 | "vec2 offset = vec2(0.0, 0.0);" 140 | "offset += t_pos;" 141 | "offset += wobble;" 142 | "offset += vec2(off_x, off_y);" 143 | "return offset;" 144 | "}" 145 | 146 | "vec2 calc_wobble(vec2 cam_offset, vec2 pos, float zoom)" 147 | "{" 148 | "vec2 wobble = vec2(0.0, 0.0);" 149 | "wobble.x = max(0, sin((cam_offset.y - pos.y / zoom)*16)) * 0.008;" 150 | "wobble.y = max(0, sin((cam_offset.x - pos.x / zoom)*24)) * 0.008;" 151 | "wobble *= zoom;" 152 | "return wobble;" 153 | "}" 154 | 155 | "void main()" 156 | "{" 157 | "vec4 t_sum = vec4(0.0);" 158 | "vec2 wobble;" 159 | "vec2 offset = vec2(0.0035, 0.0035);" 160 | "float blur_amt = 0.5 / 8;" 161 | "vec2 t_pos = vec2(0.5, 0.5);" 162 | "t_pos *= pos_out.xy;" 163 | "t_pos += vec2(0.5, 0.5);" 164 | 165 | "/* Create wobble for warping agents */" 166 | "wobble = calc_wobble(pos_offset.xy, pos_out.xy, zoom);" 167 | 168 | "/* Add offset textures, clearing wobble and blur effect */" 169 | "t_sum += texture2D(fbo_texture," 170 | "offset_tex(t_pos, wobble, offset.x, offset.y)) * blur_amt;" 171 | "t_sum += texture2D(fbo_texture," 172 | "offset_tex(t_pos, wobble, -offset.x, offset.y)) * blur_amt;" 173 | "t_sum += texture2D(fbo_texture," 174 | "offset_tex(t_pos, wobble, -offset.x, -offset.y)) * blur_amt;" 175 | "t_sum += texture2D(fbo_texture," 176 | "offset_tex(t_pos, wobble, offset.x, -offset.y)) * blur_amt;" 177 | 178 | "offset *= 2.0;" 179 | "blur_amt *= 0.75;" 180 | 181 | "t_sum += texture2D(fbo_texture," 182 | "offset_tex(t_pos, wobble, offset.x, offset.y)) * blur_amt;" 183 | "t_sum += texture2D(fbo_texture," 184 | "offset_tex(t_pos, wobble, -offset.x, offset.y)) * blur_amt;" 185 | "t_sum += texture2D(fbo_texture," 186 | "offset_tex(t_pos, wobble, -offset.x, -offset.y)) * blur_amt;" 187 | "t_sum += texture2D(fbo_texture," 188 | "offset_tex(t_pos, wobble, offset.x, -offset.y)) * blur_amt;" 189 | 190 | "t_sum += texture2D(fbo_texture," 191 | "offset_tex(t_pos, wobble, 0.0, 0.0)) * 1.0;" 192 | //"if(t_sum.x > 0.8) t_sum *= 1.25;" 193 | //"if(t_sum.y > 0.8) t_sum *= 1.25;" 194 | //"if(t_sum.z > 0.8) t_sum *= 1.25;" 195 | " gl_FragColor = t_sum;" 196 | "}"; 197 | 198 | return gfx_setup_shader(test_vs, test_fs); 199 | } 200 | 201 | GLuint 202 | gfx_bg_shader() 203 | { 204 | const char* bg_vs = 205 | "#version 130\n" 206 | "in vec4 position;" 207 | "in vec4 color_in;" 208 | "uniform float time;" 209 | "out vec4 color_out;" 210 | "out vec4 pos_out;" 211 | 212 | "void main() {" 213 | "pos_out = position;" 214 | "color_out = color_in;" 215 | "gl_Position = gl_ModelViewProjectionMatrix * position;" 216 | "}"; 217 | 218 | const char* bg_fs = 219 | "#version 130\n" 220 | "in vec4 color_out;" 221 | "in vec4 pos_out;" 222 | "uniform float time;" 223 | 224 | "/* Thanks for the random function!" 225 | "https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83 */" 226 | "float rand(float n)" 227 | "{" 228 | "return fract(sin(n + fract(time)) * 43758.5453123);" 229 | "}" 230 | 231 | "void main() {" 232 | "vec2 pos = pos_out.xy;" 233 | "vec4 new_col = vec4(1.0, 1.0, 1.0, 1.0);" 234 | "float noise = rand(pos.x * pos.y) * 0.02;" 235 | "new_col.a = noise;" 236 | "gl_FragColor = new_col;" 237 | "}"; 238 | 239 | return gfx_setup_shader(bg_vs, bg_fs); 240 | } 241 | 242 | GLuint 243 | gfx_agent_shader() 244 | { 245 | const char* agents_vs = 246 | "#version 130\n" 247 | "attribute vec4 position;" 248 | "attribute vec4 color;" 249 | "uniform vec2 window;" 250 | "uniform float zoom;" 251 | "out vec4 color_out;" 252 | "" 253 | "void main() {" 254 | "color_out = color;" 255 | "gl_Position = gl_ModelViewProjectionMatrix *" 256 | "vec4(position.x, position.y, 0.0, 1.0);" 257 | "gl_PointSize = position.z * window.x * zoom * 1.0;" 258 | "}"; 259 | 260 | const char* agents_fs = 261 | "#version 130\n" 262 | "out vec4 frag_colour;" 263 | "in vec4 color_out;" 264 | 265 | "float rand(float n)" 266 | "{" 267 | "return fract(sin(n) * 43758.5453123);" 268 | "}" 269 | 270 | "void main() {" 271 | "float agent_state = color_out.w;" 272 | "if(agent_state == -1) {" 273 | "gl_FragColor = vec4(0.0);" 274 | "return;" 275 | "}" 276 | " vec4 color = color_out;" 277 | "color.w = 0.9;" 278 | " vec2 pos = gl_PointCoord - vec2(0.5);" 279 | "/* Gen pattern */" 280 | " float pat_r = length(pos)*1.0;" 281 | " float pat_a = atan(pos.y,pos.x);" 282 | " float pat_f = cos(pat_a * 80);" 283 | " float alpha = smoothstep(pat_f,pat_f+0.02,pat_r) * 1.0;" 284 | " float cut_r = 1.0;" 285 | 286 | "/* Gen circle */" 287 | " float cutoff = 1.0 - smoothstep(cut_r - (cut_r * 0.02)," 288 | " cut_r + (cut_r * 0.02)," 289 | " dot(pos, pos) * 4.0);" 290 | // " if(color_out.a == 0) color.a = 0;" 291 | " gl_FragColor = alpha * cutoff * color " 292 | "* ((1.0 - (length(pos) )) * 1.5) ;" 293 | "}"; 294 | 295 | return gfx_setup_shader(agents_vs, agents_fs); 296 | } 297 | 298 | GLuint 299 | gfx_agent_vis_shader() 300 | { 301 | const char* vis_vs = 302 | "#version 130\n" 303 | "attribute vec4 position;" 304 | "attribute vec4 color;" 305 | "uniform vec2 window;" 306 | "out vec4 color_out;" 307 | "uniform float zoom;" 308 | "void main() {" 309 | 310 | "color_out = color;" 311 | "gl_Position = gl_ModelViewProjectionMatrix *" 312 | "vec4(position.x, position.y, 0.0, 1.0);" 313 | " gl_PointSize = position.w * window.x * zoom;" 314 | "}"; 315 | 316 | const char* vis_fs = 317 | "#version 130\n" 318 | "out vec4 frag_colour;" 319 | "in vec4 color_out;" 320 | "uniform float zoom;" 321 | 322 | "float rand(float n){return fract(sin(n) * 43758.5453123);}" 323 | "float mini_rand(float n){return fract(sin(n) * 150);}" 324 | 325 | "void main() {" 326 | "float agent_state = color_out.w;" 327 | "if(agent_state != 1) {" 328 | "gl_FragColor = vec4(0.0);" 329 | "return;" 330 | "}" 331 | "vec4 color = color_out;" 332 | "color.w = 0.2;" 333 | " vec2 pos = gl_PointCoord - vec2(0.5);" 334 | "float noise = rand(pos.x * pos.y) * 0.07;" 335 | "color.w = color.w + noise;" 336 | 337 | " float radius = 1.0;" //0.25;;" 338 | " float cutoff = 1.0 - smoothstep(radius - (radius * 0.01)," 339 | " radius + (radius * 0.01)," 340 | " dot(pos, pos) * 4.0);" 341 | 342 | "float noise_pat = mini_rand((pos.x + 0.5) * (pos.y + 0.5));" 343 | "float ripple = fract((length(pos) - 0.4) * 4.0);" 344 | "color += vec4(noise_pat * 0.15);" 345 | 346 | " gl_FragColor = cutoff * color * ripple + 0.02;" 347 | " if(color_out.w == 0) gl_FragColor.w = 0;" 348 | "}"; 349 | 350 | return gfx_setup_shader(vis_vs, vis_fs); 351 | } 352 | 353 | /* Draw agents from agent verts */ 354 | void 355 | gfx_agents_draw_cell(Agent_verts* av, GLuint shader, float scale, float zoom) 356 | { 357 | glEnableClientState(GL_VERTEX_ARRAY); 358 | glVertexAttribPointer(0, 4, GL_FLOAT, 0, 0, av->verts_pos); 359 | glVertexAttribPointer(1, 4, GL_FLOAT, 0, 0, av->verts_col); 360 | glUseProgram(shader); 361 | 362 | GLuint loc = glGetUniformLocation(shader, "window"); 363 | glUniform2f(loc, scale, scale); 364 | 365 | GLuint loc2 = glGetUniformLocation(shader, "zoom"); 366 | glUniform1f(loc2, zoom); 367 | 368 | glEnableVertexAttribArray(0); 369 | glEnableVertexAttribArray(1); 370 | glDrawArrays(GL_POINTS, 0, av->a_count); 371 | glDrawElements(GL_POINTS, av->a_count, GL_UNSIGNED_SHORT, (void*)0); 372 | glDisableVertexAttribArray(0); 373 | glDisableVertexAttribArray(1); 374 | glUseProgram(0); 375 | glDisableClientState(GL_VERTEX_ARRAY); 376 | } 377 | 378 | /* Draw vision fields from agent vision vertex*/ 379 | void 380 | gfx_agents_draw_vis(Agent_verts* av, GLuint shader, float scale, float zoom) 381 | { 382 | 383 | glEnableClientState(GL_VERTEX_ARRAY); 384 | glVertexAttribPointer(0, 4, GL_FLOAT, 0, 0, av->verts_pos); 385 | glVertexAttribPointer(1, 4, GL_FLOAT, 0, 0, av->verts_col); 386 | 387 | glUseProgram(shader); 388 | 389 | GLuint loc = glGetUniformLocation(shader, "window"); 390 | glUniform2f(loc, scale, scale); 391 | 392 | GLuint loc2 = glGetUniformLocation(shader, "zoom"); 393 | glUniform1f(loc2, zoom); 394 | 395 | glEnableVertexAttribArray(0); 396 | glEnableVertexAttribArray(1); 397 | glDrawArrays(GL_POINTS, 0, av->a_count); 398 | glDrawElements(GL_POINTS, av->a_count, GL_UNSIGNED_SHORT, (void*)0); 399 | glDisableVertexAttribArray(0); 400 | glDisableVertexAttribArray(1); 401 | glUseProgram(0); 402 | glDisableClientState(GL_VERTEX_ARRAY); 403 | } 404 | 405 | /* get the scale. Since the development was done in half-screen 406 | * a 1.0 width is considered 1/2 of the screen. Fullscreen scaling would 407 | * be done by a factor of 2. Hacky, but just the way things were done*/ 408 | float 409 | gfx_get_scale(GLFWwindow* window) 410 | { 411 | const GLFWvidmode * mode = glfwGetVideoMode(glfwGetPrimaryMonitor()); 412 | int w_screen = mode->width; 413 | int h_screen = mode->height; 414 | int w_window, h_window; 415 | float h_scale, w_scale, scale; 416 | 417 | glfwGetWindowSize(window, &w_window, &h_window); 418 | 419 | w_scale = (float)w_window / ((float)w_screen / 2.0); 420 | h_scale = (float)h_window / ((float)h_screen / 2.0); 421 | scale = (h_scale + w_scale) / 2.0f; 422 | return scale; 423 | } 424 | 425 | World_view* 426 | gfx_world_view_create() 427 | { 428 | World_view* tmp = malloc(sizeof(World_view)); 429 | tmp->zoom = 1.0f; 430 | tmp->pos_offsets[0] = 0.0f; 431 | tmp->pos_offsets[1] = 0.0f; 432 | return tmp; 433 | } 434 | 435 | /* Zoom in the world */ 436 | void 437 | gfx_world_view_zoom(World_view *wv, float xoffset, float yoffset) 438 | { 439 | xoffset *= INPUT_SCROLL_AMT; 440 | yoffset *= INPUT_SCROLL_AMT; 441 | 442 | wv->zoom += yoffset; 443 | 444 | wv->zoom = MAX(1.0, wv->zoom); 445 | wv->zoom = MIN(2.0, wv->zoom); 446 | 447 | /* keep in frame */ 448 | gfx_world_view_constrain(wv); 449 | 450 | } 451 | 452 | /* Scroll in the world */ 453 | void 454 | gfx_world_view_scroll(World_view *wv, float xoffset, float yoffset) 455 | { 456 | xoffset *= INPUT_SCROLL_AMT; 457 | yoffset *= INPUT_SCROLL_AMT; 458 | 459 | wv->pos_offsets[0] += xoffset; 460 | wv->pos_offsets[1] += -yoffset; 461 | 462 | gfx_world_view_constrain(wv); 463 | 464 | } 465 | 466 | /* Prevent scrolling off the edge */ 467 | void 468 | gfx_world_view_constrain(World_view *wv) 469 | { 470 | float max; 471 | max = (1 / wv->zoom) - 1; 472 | wv->pos_offsets[0] = MAX(max, wv->pos_offsets[0]); 473 | wv->pos_offsets[0] = MIN(-max, wv->pos_offsets[0]); 474 | wv->pos_offsets[1] = MAX(max, wv->pos_offsets[1]); 475 | wv->pos_offsets[1] = MIN(-max, wv->pos_offsets[1]); 476 | } 477 | 478 | /* Get reletive position of a point in the window, taking into account the 479 | * zoom and offset position of the simulation */ 480 | float* 481 | gfx_world_view_relpos(World_view* wv, GLFWwindow* window, float x, float y) 482 | { 483 | float xRel, yRel; 484 | int win_width, win_height; 485 | float* tmp = malloc(sizeof(float) * 2); 486 | glfwGetWindowSize(window, &win_width, &win_height); 487 | 488 | xRel = x / (float) win_width; 489 | yRel = y / (float) win_height; 490 | xRel *= 2; 491 | yRel *= 2; 492 | xRel -= 1; 493 | yRel -= 1; 494 | yRel = -yRel; 495 | xRel = (xRel - wv->pos_offsets[0] * wv->zoom) / wv->zoom; 496 | yRel = (yRel - wv->pos_offsets[1]* wv->zoom) / wv->zoom; 497 | 498 | tmp[0] = xRel; 499 | tmp[1] = yRel; 500 | 501 | return tmp; 502 | } 503 | 504 | /* Draw textured background */ 505 | void gfx_bg_draw(GLuint shader, float time) 506 | { 507 | glColor3f(0.0, 1.0, 0.0); 508 | glUseProgram(shader); 509 | 510 | GLuint loc = glGetUniformLocation(shader, "time"); 511 | glUniform1f(loc, time); 512 | 513 | glBegin(GL_QUADS); 514 | glVertex3f(-1.0f, -1.0f, 0.0f); 515 | glVertex3f(1.0f, -1.0f, 0.0f); 516 | glVertex3f(1.0f, 1.0f, 0.0f); 517 | glVertex3f(-1.0f, 1.0f, 0.0f); 518 | glEnd(); 519 | glUseProgram(0); 520 | } 521 | -------------------------------------------------------------------------------- /src/graphics.h: -------------------------------------------------------------------------------- 1 | #ifndef GRAPHICS_H 2 | #define GRAPHICS_H 3 | #include 4 | #include 5 | #include "agents.h" 6 | #include "utils.h" 7 | 8 | typedef struct RGB_ RGB; 9 | typedef struct _World_view World_view; 10 | typedef struct _Framebuffer Framebuffer; 11 | 12 | struct _World_view { 13 | float zoom; 14 | float pos_offsets[2]; 15 | }; 16 | 17 | struct _Framebuffer { 18 | GLuint framebuffer; 19 | GLuint texBuffer; 20 | }; 21 | 22 | GLuint gfx_setup_shader(const char* vs_raw, const char* fs_raw); 23 | GLuint gfx_agent_shader(); 24 | GLuint gfx_agent_vis_shader(); 25 | 26 | GLuint gfx_bg_shader(); 27 | 28 | Framebuffer* gfx_framebuffer_create(int width, int height); 29 | GLuint gfx_framebuffer_shader(); 30 | void gfx_framebuffer_begin(Framebuffer* fb, World_view* wv); 31 | void gfx_framebuffer_end(); 32 | void gfx_framebuffer_draw(Framebuffer* fb, World_view* wv, GLuint shader); 33 | 34 | void gfx_agents_draw_cell(Agent_verts* av, GLuint shader, float scale, float zoom); 35 | void gfx_agents_draw_vis(Agent_verts* av, GLuint shader, float scale, float zoom); 36 | 37 | 38 | void gfx_bg_draw(GLuint shader, float time); 39 | 40 | World_view* gfx_world_view_create(); 41 | void gfx_world_view_constrain(World_view *wv); 42 | void gfx_world_view_zoom(World_view *wv, float xoffset, float yoffset); 43 | void gfx_world_view_scroll(World_view *wv, float xoffset, float yoffset); 44 | float* gfx_world_view_relpos(World_view* wv, GLFWwindow* window, float x, float y); 45 | float gfx_get_scale(GLFWwindow* window); 46 | 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/input.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "input.h" 5 | #include "config.h" 6 | #include "agents.h" 7 | 8 | Input* 9 | input_create() 10 | { 11 | Input* tmp; 12 | tmp = malloc(sizeof(Input)); 13 | tmp->btn_left.is_down = 0; 14 | tmp->btn_right.is_down = 0; 15 | 16 | tmp->spawner.last_ptr = NULL; 17 | tmp->spawner.last_time = 0; 18 | tmp->spawner.pos[0] = 0; 19 | tmp->spawner.pos[1] = 0; 20 | 21 | return tmp; 22 | } 23 | 24 | void 25 | input_spawn_begin(Input* input, float pos[]) 26 | { 27 | input->btn_left.is_down = 1; 28 | input->spawner.last_time = glfwGetTime() - INPUT_SPAWN_DELAY; 29 | input->spawner.pos[0] = pos[0]; 30 | input->spawner.pos[1] = pos[1]; 31 | } 32 | 33 | void 34 | input_spawn_end(Input* input) 35 | { 36 | input->btn_left.is_down = 0; 37 | input->spawner.last_time = 0; 38 | input->spawner.last_ptr = NULL; 39 | } 40 | 41 | void 42 | input_spawn_cycle(Input* input, Agent_array* aa) 43 | { 44 | float time_cur = glfwGetTime(); 45 | float* time_last = &(input->spawner.last_time); 46 | float* spawn_pos = input->spawner.pos; 47 | Agent* agent_last = input->spawner.last_ptr; 48 | Agent* agent_tmp; 49 | 50 | /* Spawn a new agent once the mouse has been held 51 | * for X amount of seconds */ 52 | if(time_cur - *time_last >= INPUT_SPAWN_DELAY) 53 | { 54 | /* Remove the old agent if one exits */ 55 | if(agent_last != NULL) 56 | { 57 | agent_last->state = AGENT_STATE_PRUNE; 58 | } 59 | 60 | /* Create and insert the new agent */ 61 | agent_tmp = agent_create_random(); 62 | agent_tmp->x = spawn_pos[0]; 63 | agent_tmp->y = spawn_pos[1]; 64 | agent_array_insert(aa, agent_tmp); 65 | input->spawner.last_ptr = agent_tmp; 66 | 67 | /* Set the lsat spanwed time */ 68 | input->spawner.last_time = time_cur; 69 | } 70 | } 71 | 72 | void 73 | input_free(Input* input) 74 | { 75 | free(input); 76 | } 77 | -------------------------------------------------------------------------------- /src/input.h: -------------------------------------------------------------------------------- 1 | #ifndef INPUT_H 2 | #define INPUT_H 3 | #include "agents.h" 4 | #include "config.h" 5 | 6 | typedef struct _Input Input; 7 | 8 | struct _Input { 9 | struct _btn { 10 | int is_down; 11 | } btn_left, btn_right; 12 | 13 | struct _spawner { 14 | void* last_ptr; 15 | float last_time; 16 | float pos[2]; 17 | } spawner; 18 | }; 19 | 20 | Input* input_create(); 21 | void input_free(Input* input); 22 | void input_spawn_begin(Input* input, float pos[]); 23 | void input_spawn_end(Input* input); 24 | void input_spawn_cycle(Input* input, Agent_array* aa); 25 | #endif 26 | -------------------------------------------------------------------------------- /src/logger.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "config.h" 5 | #include "logger.h" 6 | #include "agents.h" 7 | #include "math.h" 8 | #include 9 | 10 | Logger* 11 | logger_create(float time, int freq) 12 | { 13 | int i; 14 | Logger* tmp = malloc(sizeof(Logger)); 15 | tmp->count = 11; 16 | for(i = 0; i < tmp->count; i++){ 17 | tmp->log[i] = log_create(); 18 | } 19 | tmp->freq = freq; 20 | tmp->last_log = time ; 21 | return tmp; 22 | } 23 | 24 | void logger_free(Logger* logger) 25 | { 26 | int i; 27 | for(i = 0; i < logger->count; i++){ 28 | log_free(logger->log[i]); 29 | } 30 | free(logger); 31 | } 32 | 33 | void 34 | logger_write(Logger* logger) 35 | { 36 | FILE* fp; 37 | fp = fopen(LOGGER_FILE, "w+"); 38 | log_write(logger->log[LOG_TIME], fp, "x_time"); 39 | log_write(logger->log[LOG_POPULATION], fp, "y_pop"); 40 | log_write(logger->log[LOG_FOOD], fp, "y_food"); 41 | log_write(logger->log[LOG_HERBIVOUR], fp, "y_herb"); 42 | log_write(logger->log[LOG_CARNIVOUR], fp, "y_meat"); 43 | log_write(logger->log[LOG_METABOLISM_AVG], fp, "y_metab_a"); 44 | log_write(logger->log[LOG_VISION_AVG], fp, "y_vision_a"); 45 | log_write(logger->log[LOG_REBIRTH_AVG], fp, "y_rebirth_a"); 46 | log_write(logger->log[LOG_DIET_AVG], fp, "y_diet_a"); 47 | log_write(logger->log[LOG_FLOCK_AVG], fp, "y_flock_a"); 48 | log_write(logger->log[LOG_WOBBLE_AVG], fp, "y_wobble_a"); 49 | fclose(fp); 50 | } 51 | 52 | void 53 | log_write(Log* log, FILE* fp, char label[]) 54 | { 55 | fprintf(fp, "%s = [", label); 56 | fprintf(fp, "%s",log->data); 57 | fprintf(fp, "]\n"); 58 | } 59 | 60 | Log* log_create() 61 | { 62 | Log* tmp = malloc(sizeof(Log)); 63 | tmp->capacity = LOG_DEFAULT_SIZE; 64 | tmp->data = calloc(tmp->capacity + 1, sizeof(char)); 65 | tmp->end = 0; 66 | return tmp; 67 | } 68 | 69 | void 70 | logger_record(Logger* logger, Agent_array* aa, float time) 71 | { 72 | int i; 73 | int i_time = (int) time; 74 | int total[logger->count]; 75 | Agent* tmp_agent; 76 | 77 | if(logger->last_log + logger->freq >= time) return; 78 | 79 | 80 | logger->last_log = time; 81 | printf("Logged at %ds\n", i_time); 82 | 83 | /* Null out totals array */ 84 | for(i = 0; i < logger->count; i++) 85 | total[i] = 0; 86 | 87 | /* save the time */ 88 | total[LOG_TIME] = (int) time; 89 | 90 | /* return if time is zero */ 91 | if(total[LOG_TIME] == 0) return; 92 | 93 | for(i = 0; (unsigned int) i < aa->count; i++) { 94 | tmp_agent = aa->agents[i]; 95 | /* Log living agents */ 96 | if(tmp_agent->state == AGENT_STATE_LIVING) { 97 | total[LOG_POPULATION]++; 98 | 99 | /* log trait */ 100 | total[LOG_METABOLISM_AVG] += tmp_agent->dna.metabolism * 100; 101 | total[LOG_VISION_AVG] += tmp_agent->dna.vision * 100; 102 | total[LOG_REBIRTH_AVG] += tmp_agent->dna.rebirth * 100; 103 | total[LOG_DIET_AVG] += tmp_agent->dna.diet * 100; 104 | total[LOG_FLOCK_AVG] += tmp_agent->dna.flock * 100; 105 | total[LOG_WOBBLE_AVG] += tmp_agent->dna.wobble * 100; 106 | 107 | /* Carnivour or plant */ 108 | if(agent_diet(tmp_agent) == AGENT_DIET_LIVING) 109 | total[LOG_CARNIVOUR]++; 110 | else 111 | total[LOG_HERBIVOUR]++; 112 | } 113 | /* Dead agents are food */ 114 | else if(tmp_agent->state == AGENT_STATE_DEAD) 115 | total[LOG_FOOD]++; 116 | } 117 | 118 | /* Get trait averages */ 119 | total[LOG_METABOLISM_AVG] /= total[LOG_POPULATION]; 120 | total[LOG_VISION_AVG] /= total[LOG_POPULATION]; 121 | total[LOG_REBIRTH_AVG] /= total[LOG_POPULATION]; 122 | total[LOG_DIET_AVG] /= total[LOG_POPULATION]; 123 | total[LOG_FLOCK_AVG] /= total[LOG_POPULATION]; 124 | total[LOG_WOBBLE_AVG] /= total[LOG_POPULATION]; 125 | 126 | /* change five to count */ 127 | for(i = 0; i < logger->count; i++) { 128 | logger_log_val(logger, i, total[i]); 129 | } 130 | logger_write(logger); 131 | } 132 | 133 | void 134 | logger_log_val(Logger* logger, int log_num, int n) 135 | { 136 | Log_string* str = log_int_to_str(n); 137 | log_cat(logger->log[log_num], str); 138 | log_string_free(str); 139 | } 140 | 141 | void 142 | log_cat(Log* log, Log_string* ls) 143 | { 144 | int new_length = ls->length + log->end; 145 | size_t new_size; 146 | if(new_length >= log->capacity - 1) 147 | { 148 | log->capacity *= 2; 149 | new_size = sizeof(float) * log->capacity; 150 | log->data = realloc(log->data, new_size); 151 | } 152 | strcat(log->data + log->end, ls->string); 153 | log->end += ls->length - 1; 154 | log->data[log->end] = '\0'; 155 | } 156 | 157 | Log_string* 158 | log_int_to_str(int n) 159 | { 160 | int i; 161 | int int_char_len; 162 | Log_string* log_str = malloc(sizeof(Log_string)); 163 | 164 | /* How many digits in the int */ 165 | if(n != 0) 166 | int_char_len = log10(n) + 1; 167 | else 168 | int_char_len = 1; 169 | 170 | /*One for comma, one for space, one for nill*/ 171 | int term_len = 3; 172 | 173 | log_str->length = int_char_len + term_len; 174 | log_str->string = calloc(term_len, sizeof(char)); 175 | 176 | /* Go to end and add terminators */ 177 | i = int_char_len; 178 | log_str->string[i] = ','; 179 | i++; 180 | log_str->string[i] = ' '; 181 | 182 | /* If the value was zero, don't do math, 183 | * just add the char '0' to array and return*/ 184 | log_str->string[int_char_len - 1] = 48; 185 | if(n == 0) return log_str; 186 | 187 | /* otherwise convert to char array */ 188 | for(i = int_char_len; i > 0; --i) { 189 | log_str->string[i - 1] = 48 + (n % 10); //48 - int ascii offfset 190 | n /= 10; 191 | } 192 | 193 | return log_str; 194 | } 195 | 196 | void 197 | log_string_free(Log_string* ls) 198 | { 199 | free(ls->string); 200 | free(ls); 201 | } 202 | 203 | void 204 | log_free(Log* log) 205 | { 206 | free(log->data); 207 | free(log); 208 | } 209 | 210 | void log_print(Log* log); 211 | -------------------------------------------------------------------------------- /src/logger.h: -------------------------------------------------------------------------------- 1 | #ifndef LOG_H 2 | #define LOG_H 3 | #include 4 | #include "agents.h" 5 | /* For debug / graphing */ 6 | 7 | typedef struct _Log Log; 8 | typedef struct _Logger Logger; 9 | typedef struct _Log_axis Log_axis; 10 | typedef struct _Log_string Log_string; 11 | 12 | enum logger_logs { 13 | LOG_TIME, 14 | LOG_POPULATION, 15 | LOG_FOOD, 16 | LOG_HERBIVOUR, 17 | LOG_CARNIVOUR, 18 | LOG_METABOLISM_AVG, 19 | LOG_VISION_AVG, 20 | LOG_REBIRTH_AVG, 21 | LOG_DIET_AVG, 22 | LOG_FLOCK_AVG, 23 | LOG_WOBBLE_AVG 24 | }; 25 | 26 | struct _Logger { 27 | Log* log[11]; 28 | int count; 29 | float last_log; 30 | int freq; 31 | }; 32 | 33 | struct _Log_string { 34 | char* string; 35 | size_t length; 36 | }; 37 | 38 | struct _Log { 39 | char* data; 40 | int capacity; 41 | ptrdiff_t end; 42 | }; 43 | 44 | 45 | Logger* logger_create(float time, int freq); 46 | void logger_free(Logger* logger); 47 | void logger_record(Logger* logger, Agent_array* aa, float time); 48 | void logger_log_val(Logger* logger, int log_num, int x); 49 | void logger_write(Logger* logger); 50 | 51 | Log* log_create(); 52 | void log_free(Log* log); 53 | void log_cat(Log* log, Log_string* ls); 54 | void log_write(Log* log, FILE* fp, char label[]); 55 | void log_print(Log* log); 56 | 57 | Log_string* log_int_to_str(int n); 58 | void log_string_free(Log_string* ls); 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /src/logger_plot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import matplotlib 3 | import matplotlib.pyplot as plt 4 | import numpy as np 5 | import sys 6 | import importlib 7 | import logger_data 8 | sys.tracebacklimit = 0 9 | 10 | matplotlib.rcParams['toolbar'] = 'None' 11 | plt.ion() 12 | 13 | while True: 14 | # Try to import the datafile ecosim generated 15 | importlib.reload(logger_data) 16 | try: 17 | from logger_data import * 18 | except ImportError: 19 | print("No data, please ensure LOGGER_ENABLED is set to (1)!") 20 | exit() 21 | 22 | # Grab int values for population 23 | x_time = np.asarray(x_time) 24 | y_pop = np.asarray(y_pop) 25 | y_food = np.asarray(y_food) 26 | y_herb = np.asarray(y_herb) 27 | y_meat = np.asarray(y_meat) 28 | 29 | # Grab values for traits, scale from int to float 30 | y_metab_a = np.asarray(y_metab_a) / 100 31 | y_vision_a = np.asarray(y_vision_a) / 100 32 | y_rebirth_a = np.asarray(y_rebirth_a) / 100 33 | y_diet_a = np.asarray(y_diet_a) / 100 34 | y_flock_a = np.asarray(y_flock_a) / 100 35 | y_wobble_a = np.asarray(y_wobble_a) / 100 36 | 37 | # Plot population 38 | ax1 = plt.subplot(211) 39 | plt.plot(x_time, y_pop, label='Living agents') 40 | plt.plot(x_time, y_food, label='Food') 41 | plt.plot(x_time, y_herb, label='Herbivours') 42 | plt.plot(x_time, y_meat, label='Carnivours') 43 | plt.title("Ecosim Population") 44 | plt.ylabel("Population") 45 | plt.legend(loc='upper right') 46 | plt.setp(ax1.get_xticklabels(), visible=False) 47 | 48 | # Plot traits 49 | ax2 = plt.subplot(212) 50 | plt.plot(x_time, y_metab_a, label='Metabolism') 51 | plt.plot(x_time, y_vision_a, label='Vision') 52 | plt.plot(x_time, y_rebirth_a, label='Rebirth') 53 | plt.plot(x_time, y_diet_a, label='Diet') 54 | plt.plot(x_time, y_flock_a, label='Flocking') 55 | plt.plot(x_time, y_wobble_a, label='Wobble') 56 | plt.title("Average Genetics") 57 | plt.xlabel("Time (s)") 58 | plt.ylabel("Average Trait Value") 59 | plt.legend(loc='upper right') 60 | 61 | # Draw every second 62 | plt.draw() 63 | plt.pause(1) 64 | plt.clf() 65 | 66 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "config.h" 10 | #include "agents.h" 11 | #include "utils.h" 12 | #include "input.h" 13 | #include "graphics.h" 14 | #include "quadtree.h" 15 | #include "logger.h" 16 | 17 | #define UNUSED __attribute__((unused)) 18 | 19 | int game_run = 1; 20 | 21 | /* For passing pointers to callbacks */ 22 | struct Callback_ptrs{ 23 | Agent_array* aa; 24 | Input* inp; 25 | World_view* wv; 26 | Framebuffer* fb; 27 | Framebuffer** fb_ptr; 28 | }; 29 | 30 | /* Keyboard callback */ 31 | void 32 | key_callback(GLFWwindow* window, int key, UNUSED int scancode, int action, UNUSED int mods) 33 | { 34 | if(action == GLFW_PRESS || action == GLFW_REPEAT) 35 | { 36 | /* Pause the simulation */ 37 | if(key == GLFW_KEY_SPACE) 38 | game_run = !game_run; 39 | 40 | /* Close the simulation */ 41 | else if(key == GLFW_KEY_Q) 42 | glfwSetWindowShouldClose(window, GLFW_TRUE); 43 | } 44 | } 45 | 46 | void mouse_button_callback(GLFWwindow* window, int button, int action, UNUSED int mods) 47 | { 48 | struct Callback_ptrs* callb_ptrs; 49 | Input* input; 50 | World_view* wv ; 51 | double xPos, yPos; 52 | 53 | /* Grab pointers of structs*/ 54 | callb_ptrs = glfwGetWindowUserPointer(window); 55 | input = callb_ptrs->inp; 56 | wv = callb_ptrs->wv; 57 | 58 | /* Begin a spawn cycle if mouse is pressed */ 59 | if(button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) 60 | { 61 | glfwGetCursorPos(window, &xPos, &yPos); 62 | float* relPos = gfx_world_view_relpos(wv, window, xPos, yPos); 63 | input_spawn_begin(input, relPos); 64 | free(relPos); 65 | } 66 | /* End spawn cycle when mouse is release */ 67 | else if(button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_RELEASE) 68 | input_spawn_end(input); 69 | 70 | } 71 | 72 | void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) 73 | { 74 | struct Callback_ptrs* callb_ptrs; 75 | int key; 76 | World_view* wv; 77 | 78 | /* grab pointers */ 79 | callb_ptrs = glfwGetWindowUserPointer(window); 80 | wv = callb_ptrs->wv; 81 | 82 | /* is ctrl clicked */ 83 | key = glfwGetKey(window, GLFW_KEY_LEFT_CONTROL); 84 | 85 | /* Zoom if ctl is pressed, scroll otherwise */ 86 | if(key){ 87 | gfx_world_view_zoom(wv, xoffset, yoffset); 88 | 89 | } else { 90 | gfx_world_view_scroll(wv, xoffset, yoffset); 91 | } 92 | } 93 | 94 | void window_size_callback(GLFWwindow* window, int width, int height) 95 | { 96 | struct Callback_ptrs* callb_ptrs; 97 | Framebuffer** fb_ptr; 98 | 99 | /* Grab pointers*/ 100 | callb_ptrs = glfwGetWindowUserPointer(window); 101 | fb_ptr = callb_ptrs->fb_ptr; 102 | /* Rebuild framebuffer to width x height */ 103 | *fb_ptr = gfx_framebuffer_create(width, height); 104 | } 105 | 106 | int 107 | main(UNUSED int argc, UNUSED char **argv) 108 | { 109 | GLFWwindow* window; 110 | Framebuffer* framebuffer; 111 | World_view* world_view; 112 | Input* input; 113 | Agent_array* agent_array; 114 | Agent_verts* agent_verts; 115 | Quadtree* quad; 116 | Logger* logger; 117 | float quad_head_pos[] = {QUAD_HEAD_POS, 118 | QUAD_HEAD_POS}; 119 | float quad_head_size = QUAD_HEAD_SIZE; 120 | float last_update_time = glfwGetTime(); 121 | float last_food_time = last_update_time; 122 | float last_prune_time = last_food_time; 123 | // For passing structs between callbacks in glfw 124 | struct Callback_ptrs callb_ptrs; 125 | 126 | /* Initalize glfw and glut*/ 127 | if (!glfwInit()) 128 | return -1; //exit 129 | 130 | /* Create window */ 131 | window = glfwCreateWindow(WINDOW_DEFAULT_X, 132 | WINDOW_DEFAULT_Y, 133 | "Ecosim", NULL, NULL); 134 | if (!window) 135 | { 136 | glfwTerminate(); 137 | return -1; 138 | } 139 | 140 | /* Set the windows context */ 141 | glfwMakeContextCurrent(window); 142 | 143 | /* initalize glew and do various gl setup */ 144 | srand((unsigned int) time(NULL)); 145 | glewInit(); 146 | glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); 147 | glEnable(GL_BLEND); 148 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 149 | 150 | /* Setup world view */ 151 | framebuffer = gfx_framebuffer_create(WINDOW_DEFAULT_X, 152 | WINDOW_DEFAULT_Y); 153 | GLuint fb_shader = gfx_framebuffer_shader(); 154 | world_view = gfx_world_view_create(); 155 | input = input_create(); 156 | 157 | logger= logger_create(glfwGetTime(), LOGGER_FREQ); 158 | 159 | /* Setup shaders, agents and verts */ 160 | agent_array = agent_array_setup_random(DEV_AGENT_COUNT); 161 | agents_insert_dead(agent_array, DEV_GAME_FOOD_SPAWN_INIT); 162 | agent_verts = agent_verts_create(); 163 | GLuint agent_shader = gfx_agent_shader(); 164 | GLuint agent_vis_shader = gfx_agent_vis_shader(); 165 | GLuint bg_shader = gfx_bg_shader(); 166 | 167 | /* Setup callbacks */ 168 | callb_ptrs.inp = input; 169 | callb_ptrs.aa = agent_array; 170 | callb_ptrs.wv = world_view; 171 | callb_ptrs.fb = framebuffer; 172 | callb_ptrs.fb_ptr = &framebuffer; 173 | glfwSetWindowUserPointer(window, &callb_ptrs); 174 | glfwSetKeyCallback(window, key_callback); 175 | glfwSetMouseButtonCallback(window, mouse_button_callback); 176 | glfwSetScrollCallback(window, scroll_callback); 177 | glfwSetWindowSizeCallback(window, window_size_callback); 178 | 179 | /* Play background noise */ 180 | system("ffplay ambient.wav -loop 0 -nodisp -loglevel quiet &"); 181 | 182 | /* Main loop */ 183 | while(!glfwWindowShouldClose(window)) 184 | { 185 | /* Set the viewport & grab scale*/ 186 | int width, height; 187 | float scale; 188 | float time; 189 | glfwGetFramebufferSize(window, &width, &height); 190 | glViewport(0, 0, width, height); 191 | scale = gfx_get_scale(window); 192 | 193 | /* Deal with user input */ 194 | if(input->btn_left.is_down) { 195 | input_spawn_cycle(input, agent_array); 196 | agents_to_verts(agent_array, agent_verts); 197 | } 198 | 199 | /* Log if needed */ 200 | #if LOGGER_ENABLE == 1 201 | logger_record(logger, agent_array, glfwGetTime()); 202 | #endif 203 | 204 | 205 | /* Grab time once to user through whole update cycle */ 206 | time = glfwGetTime(); 207 | 208 | /* Main update cycle */ 209 | if(game_run && time > last_update_time + (1.0 / DEV_GAME_FPS)) 210 | { 211 | last_update_time = time; 212 | 213 | /* Recreate quadtree and insert agents */ 214 | quad = quadtree_create(quad_head_pos, quad_head_size); 215 | agent_array_to_quadtree(agent_array, quad); 216 | /* Update agents position using quadtree, convert 217 | * them into their verts*/ 218 | agents_update(agent_array, quad); 219 | agents_to_verts(agent_array, agent_verts); 220 | 221 | /* Insert dead agents for herbivours */ 222 | if(agents_food_drop(agent_array, time, last_food_time)) 223 | last_food_time = time; 224 | 225 | /* Prune the array and update pointers if needed */ 226 | if(glfwGetTime() > last_prune_time + AGENT_ARRAY_PRUNE_TIME) { 227 | agent_array = agent_array_prune(agent_array); 228 | callb_ptrs.aa = agent_array; 229 | last_prune_time = time; 230 | printf("Pruned\n"); 231 | } 232 | 233 | quadtree_free(quad); 234 | } 235 | /* Draw all the elments to off-screen framebuffer */ 236 | glClear(GL_COLOR_BUFFER_BIT); 237 | gfx_framebuffer_begin(framebuffer, world_view); 238 | gfx_bg_draw(bg_shader, glfwGetTime()); 239 | gfx_agents_draw_cell(agent_verts, agent_shader, 240 | scale, world_view->zoom); 241 | gfx_agents_draw_vis(agent_verts, agent_vis_shader, 242 | scale, world_view->zoom); 243 | gfx_framebuffer_end(); 244 | 245 | /* Draw a shaded version of the framebuffer */ 246 | gfx_framebuffer_draw(framebuffer, world_view, fb_shader); 247 | 248 | /* swap */ 249 | glfwSwapBuffers(window); 250 | /* Poll for events */ 251 | glfwPollEvents(); 252 | } 253 | 254 | /* Agent verts can be persistant, so free at end, not each frame*/ 255 | logger_free(logger); 256 | agent_array_free(agent_array); 257 | agent_verts_free(agent_verts); 258 | free(framebuffer); 259 | input_free(input); 260 | system("pkill ffplay"); 261 | printf("KILLED\n"); 262 | glfwTerminate(); 263 | return 0; 264 | } 265 | -------------------------------------------------------------------------------- /src/quadtree.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "quadtree.h" 6 | 7 | 8 | 9 | Quadtree* 10 | quadtree_create(float pos[], float size) 11 | { 12 | int i; 13 | Quadtree* tmp = malloc(sizeof(Quadtree)); 14 | 15 | /* Copy over dimentional info and size*/ 16 | memcpy(&tmp->pos, pos, sizeof(float) * QUADTREE_DIMS); 17 | memcpy(&tmp->size, &size, sizeof(float)); 18 | 19 | /* NULL all sub-quad pointers */ 20 | for(i = 0; i < QUAD_COUNT; i++) tmp->sect[i] = NULL; 21 | 22 | /* NULL all quad's pointers */ 23 | for(i = 0; i < QUADTREE_MAX_PER_CELL; i++) tmp->ptrs[i] = NULL; 24 | 25 | tmp->ptr_count = 0; 26 | tmp->has_child = 0; 27 | 28 | return tmp; 29 | } 30 | 31 | /* free a quad and all it's children */ 32 | void 33 | quadtree_free(Quadtree* q) 34 | { 35 | int i; 36 | if(q->has_child){ 37 | for(i = 0; i < QUAD_COUNT; i++){ 38 | quadtree_free(q->sect[i]); 39 | } 40 | } 41 | free(q); 42 | } 43 | 44 | void 45 | quadtree_split(Quadtree *q) 46 | { 47 | int i, j; 48 | int add_half; 49 | float child_size = q->size * 0.5f; 50 | float new_pos[QUAD_COUNT][QUADTREE_DIMS]; 51 | 52 | // This is stupid do this in a standard way don't do this: 53 | /* Loop through each new quad 54 | * Loop through each dimention 55 | * use anding n bitshifting to see if we should offset this new quads dims 56 | * Should create the new quads in clockwise manner... */ 57 | for(i = 0; i < QUAD_COUNT; i++){ 58 | for(j = 0; j < QUADTREE_DIMS; j++){ 59 | add_half = ((1 << j) & i) ? 1 : 0; 60 | new_pos[i][j] = q->pos[j] + ((add_half) ? child_size : 0); 61 | } 62 | } 63 | for(i = 0; i < QUAD_COUNT; i++) 64 | q->sect[i] = quadtree_create(new_pos[i], child_size); 65 | 66 | q->has_child = 1; 67 | } 68 | 69 | /* insert a pointer to the quadtree a pos */ 70 | void 71 | quadtree_insert(Quadtree* q, void* ptr, float pos[]) 72 | { 73 | int i; 74 | ptrdiff_t search_pos; 75 | /* ignore null ptr */ 76 | if(ptr == NULL) return; 77 | /* go through dims, forget if outsifde */ 78 | 79 | /* exit if out of bounds */ 80 | for(int i = 0; i < QUADTREE_DIMS; i++){ 81 | if(pos[i] < q->pos[i]) return; 82 | if(pos[i] > q->pos[i] + q->size) return; 83 | } 84 | 85 | /* if quad is full */ 86 | if(q->ptr_count == QUADTREE_MAX_PER_CELL){ 87 | // if not split, split quad 88 | if(!q->has_child) quadtree_split(q); 89 | // then insert into splits 90 | for(i = 0; i < QUAD_COUNT; i++) 91 | quadtree_insert(q->sect[i], ptr, pos); 92 | } 93 | /* if quad has space */ 94 | else { 95 | /* find empty pos and insert*/ 96 | search_pos = 0; 97 | while(q->ptrs[search_pos] != NULL) search_pos++; 98 | q->ptrs[search_pos] = ptr; 99 | q->ptr_count++; 100 | } 101 | } 102 | 103 | 104 | 105 | /* check if a point intersects with a quad */ 106 | int quadtree_intersect(Quadtree *q, float pos[], float size) 107 | { 108 | float half_size = size * 0.5; 109 | 110 | if ((pos[0] - half_size <= q->pos[0] + q->size) && 111 | (pos[1] - half_size <= q->pos[1] + q->size) && 112 | (q->pos[0] <= pos[0] + half_size) && 113 | (q->pos[1] <= pos[1] + half_size)) 114 | return 1; 115 | 116 | else return 0; 117 | } 118 | 119 | Quadtree_query* 120 | quadtree_query_setup() 121 | { 122 | Quadtree_query* tmp = malloc(sizeof(Quadtree_query)); 123 | 124 | tmp->capacity = sizeof(void*) * QUADTREE_QUERY_SIZE; 125 | tmp->ptrs = malloc(tmp->capacity); 126 | tmp->size = 0; 127 | tmp->ptr_count = 0; 128 | 129 | return tmp; 130 | } 131 | 132 | 133 | void 134 | quadtree_query_free(Quadtree_query* qq) 135 | { 136 | free(qq->ptrs); 137 | free(qq); 138 | } 139 | 140 | void 141 | quadtree_query(Quadtree *q, Quadtree_query* query, float pos[], float size) 142 | { 143 | /* if no intersection ,ignore */ 144 | if(!quadtree_intersect(q, pos, size)) return; 145 | 146 | /* If quad has children, recusivly go through and add them to query*/ 147 | if(q->has_child){ 148 | for(int i = 0; i < QUAD_COUNT; i++) { 149 | quadtree_query(q->sect[i], query, pos, size); 150 | } 151 | } 152 | /* add pointers to query from this quad */ 153 | quadtree_query_add_ptr(q, query); 154 | } 155 | 156 | void 157 | quadtree_query_add_ptr(Quadtree *quad, Quadtree_query* query) 158 | { 159 | /* predict for the worst, the quad is full */ 160 | size_t new_size = query->size + (sizeof(void*) * QUADTREE_MAX_PER_CELL); 161 | 162 | /* resize if needed */ 163 | if(new_size > query->capacity){ 164 | query->capacity *= 2; 165 | query->ptrs = realloc(query->ptrs, query->capacity); 166 | } 167 | 168 | for(int i = 0; i < QUADTREE_MAX_PER_CELL; i++) { 169 | if(quad->ptrs[i] != NULL) { 170 | /* Add the pointer to end of query pointer array */ 171 | query->ptrs[query->ptr_count] = quad->ptrs[i]; 172 | query->size += sizeof(void*); 173 | query->ptr_count += 1; 174 | } else 175 | { 176 | //printf("ERR: got null\n"); 177 | } 178 | } 179 | } 180 | 181 | /* For debug */ 182 | void 183 | quadtree_query_dump(Quadtree_query* qq) 184 | { 185 | printf("DUMP: Got %ld pointers\n", (unsigned long) qq->ptr_count); 186 | for(unsigned int i =0; i < qq->ptr_count; i++) 187 | printf("DUMP: Got pointer %p at loc %d\n", (void*) qq->ptrs[i], i); 188 | } 189 | -------------------------------------------------------------------------------- /src/quadtree.h: -------------------------------------------------------------------------------- 1 | #include 2 | #ifndef QUADTREE_H 3 | #define QUADTREE_H 4 | #include "config.h" 5 | 6 | typedef struct Quadtree_ Quadtree; 7 | typedef struct Quadtree_query_ Quadtree_query; 8 | 9 | struct Quadtree_ { 10 | Quadtree* sect[QUAD_COUNT]; //see quadtree_sects 11 | float pos[QUADTREE_DIMS]; 12 | float size; 13 | void* ptrs[QUADTREE_MAX_PER_CELL]; 14 | size_t ptr_count; 15 | int has_child; 16 | }; 17 | 18 | struct Quadtree_query_ { 19 | void** ptrs; 20 | size_t ptr_count; 21 | size_t capacity; 22 | size_t size; 23 | }; 24 | 25 | Quadtree* quadtree_create(float pos[], float size); 26 | void quadtree_free(Quadtree* q); 27 | void quadtree_insert(Quadtree* q, void* ptr, float pos[]); 28 | void quadtree_split(Quadtree *q); 29 | 30 | Quadtree_query* quadtree_query_setup(); 31 | void quadtree_query_free(Quadtree_query* qq); 32 | void quadtree_query(Quadtree *q, Quadtree_query* query, 33 | float pos[], float size); 34 | int quadtree_intersect(Quadtree *q, float pos[], float size); 35 | void quadtree_query_add_ptr(Quadtree *quad, Quadtree_query* query); 36 | void quadtree_query_dump(Quadtree_query* qq); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "utils.h" 5 | 6 | /* empty atm */ 7 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H 2 | #define UTILS_H 3 | #define RANDF(x) (((float)rand()/(float)(RAND_MAX)) * x) 4 | #define RANDF_MIN(min, max) ((((float) rand() / (float) RAND_MAX) * (max - min)) + min) 5 | #define MIN(a,b) (((a)<(b))?(a):(b)) 6 | #define MAX(a,b) (((a)>(b))?(a):(b)) 7 | 8 | 9 | typedef struct { 10 | float x; 11 | float y; 12 | } Point; 13 | 14 | typedef struct RGB_ { 15 | float r; 16 | float g; 17 | float b; 18 | } RGB; 19 | 20 | 21 | #endif 22 | --------------------------------------------------------------------------------