├── .flake8 ├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── demo ├── README.md ├── __init__.py ├── example_window.py ├── shaders │ ├── demo.ComputeShader.comp │ ├── demo.FieldFragmentShader.frag │ ├── demo.QuiverFragmentShader.frag │ ├── demo.VertexShader.vert │ ├── shader.AddParticle.comp │ ├── shader.AdvectParticle.comp │ └── varying.def.sc ├── simulation_demo.py ├── smooth_particles_area.py └── utils │ ├── __init__.py │ ├── imgui_utils.py │ └── matrix_utils.py ├── media └── screenshot.png ├── natrix ├── __init__.py └── core │ ├── __init__.py │ ├── common │ ├── __init__.py │ └── constants.py │ ├── fluid_simulator.py │ ├── shaders │ └── originals │ │ ├── common.sh │ │ ├── constants.sh │ │ ├── shader.AddCircleObstacle.comp │ │ ├── shader.AddTriangleObstacle.comp │ │ ├── shader.AddVelocity.comp │ │ ├── shader.AdvectVelocity.comp │ │ ├── shader.ApplyVorticity.comp │ │ ├── shader.CalcVorticity.comp │ │ ├── shader.ClearBuffer.comp │ │ ├── shader.Divergence.comp │ │ ├── shader.InitBoundaries.comp │ │ ├── shader.Poisson.comp │ │ ├── shader.SubtractGradient.comp │ │ └── shader.Viscosity.comp │ └── utils │ ├── __init__.py │ ├── bgfx_utils.py │ └── shaders_utils.py ├── noxfile.py ├── poetry.lock ├── pyproject.toml ├── setup.py └── tests └── __init__.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | select = B,B9,BLK,C,E,F,I,S,W 3 | ignore = E203,E501,W503,E731,F405,F403,F401 4 | max-line-length = 128 5 | per-file-ignores = tests/*:S101 6 | import-order-style = google 7 | application-import-names = src,tests -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | # PyCharm 107 | .idea/ 108 | 109 | */shaders_cache 110 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | - "3.7" 5 | - "3.8" 6 | 7 | before_install: 8 | - pip3 install poetry 9 | 10 | install: 11 | - poetry install 12 | 13 | script: 14 | - true -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Federico Bertola 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE README.md CONTRIBUTING.md CODE_OF_CONDUCT.md CHANGELOG.rst NOTICES HISTORY.txt 2 | exclude .gitmodules 3 | exclude .editorconfig .travis.yml .env appveyor.yml tox.ini pytest.ini 4 | exclude Pipfile* CHANGELOG.draft.rst 5 | 6 | recursive-include docs Makefile *.rst *.py *.bat 7 | recursive-include docs/_templates *.html 8 | recursive-include docs/_static *.js *.css *.png 9 | recursive-include natrix/core/shaders *.comp 10 | recursive-exclude docs requirements*.txt 11 | recursive-exclude natrix *.pyi 12 | recursive-exclude natrix *.typed 13 | 14 | prune .github 15 | prune docs/build 16 | prune news 17 | prune tasks 18 | prune tests 19 | prune demo -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | Natrix 4 |
5 |

6 | 7 |

Fast fluid simultation in Python.

8 | 9 |

10 | 11 | 12 | 13 | 14 | 15 | 16 |

17 | 18 |

19 | Key Features • 20 | How To Use • 21 | Credits • 22 | License 23 |

24 | 25 |

26 | 27 |

28 | 29 | ## Key Features 30 | 31 | * Fast - leverages Compute Shaders to offload most of the calculations to the GPU. 32 | * Built with BGFX rendering engine, supporting OpenGL, Vulkan, Metal and DirectX backends. 33 | * Vorticity confinement. 34 | * Fluid obstacles. 35 | * Poisson kernel. 36 | 37 | ## How To Use 38 | 39 | To use this library, you'll need [Poetry](https://github.com/python-poetry/poetry): 40 | 41 | ```bash 42 | # Clone this repository 43 | $ git clone https://github.com/fbertola/Natrix . 44 | 45 | # Go into the repository 46 | $ cd Natrix 47 | 48 | # Install dependencies 49 | $ poetry install 50 | ``` 51 | 52 | ## Examples 53 | 54 | In the [demo](https://github.com/fbertola/Natrix/tree/master/demo) folder you will find a complete example, be sure to check it out. 55 | 56 | ## Credits 57 | 58 | This software uses the following open source packages: 59 | 60 | - [BGFX](https://github.com/bkaradzic/bgfx) 61 | - [BGFX Python Wrapper](https://github.com/fbertola/bgfx-python) 62 | 63 | [License (BSD 2-clause)](https://raw.githubusercontent.com/fbertola/bgfx-python/master/LICENSE) 64 | ----------------------------------------------------------------------- 65 | 66 | 67 | 68 | 69 | 70 | BSD 2-Clause License 71 | 72 | Copyright (c) 2021, Federico Bertola 73 | All rights reserved. 74 | 75 | Redistribution and use in source and binary forms, with or without 76 | modification, are permitted provided that the following conditions are met: 77 | 78 | 1. Redistributions of source code must retain the above copyright notice, this 79 | list of conditions and the following disclaimer. 80 | 81 | 2. Redistributions in binary form must reproduce the above copyright notice, 82 | this list of conditions and the following disclaimer in the documentation 83 | and/or other materials provided with the distribution. 84 | 85 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 86 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 87 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 88 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 89 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 90 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 91 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 92 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 93 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 94 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 95 | 96 | 97 | --- 98 | 99 | > GitHub [@fbertola](https://github.com/fbertola) 100 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # Natrix Demo 2 | 3 | This folder contains a complete example of how to user Natrix. 4 | 5 | It demonstrates how to: 6 | * use the velocity field to animate a simple smooth particles area. 7 | * use different resolutions for the velocity field. 8 | * use [ImGui](https://github.com/ocornut/imgui) to create a simple GUI for controlling the simulation. 9 | * support HiDPI screens. -------------------------------------------------------------------------------- /demo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbertola/Natrix/403fed55cab582563615c71ef11b40b11cdb7777/demo/__init__.py -------------------------------------------------------------------------------- /demo/example_window.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import cppyy 3 | import sys 4 | import os 5 | import time 6 | 7 | # noinspection PyPackageRequirements 8 | import glfw 9 | 10 | from pybgfx import bgfx 11 | 12 | # noinspection PyPackageRequirements,PyProtectedMember 13 | from glfw import _glfw as glfw_native 14 | from pybgfx.utils import as_void_ptr 15 | 16 | 17 | class ExampleWindow(object): 18 | def __init__(self, width, height, title): 19 | self.title = title 20 | self.height = height 21 | self.width = width 22 | self.ctx = None 23 | self.window = None 24 | self.fb_width = width 25 | self.fb_height = height 26 | self.hidpi = False 27 | 28 | def init(self, platform_data): 29 | pass 30 | 31 | def shutdown(self): 32 | pass 33 | 34 | def update(self, dt): 35 | pass 36 | 37 | def resize(self): 38 | pass 39 | 40 | def get_mouse_state(self): 41 | mouse_x, mouse_y = glfw.get_cursor_pos(self.window) 42 | 43 | if self.hidpi: 44 | mouse_x, mouse_y = mouse_x * 2, mouse_y * 2 45 | state_mbl = glfw.get_mouse_button(self.window, glfw.MOUSE_BUTTON_LEFT) 46 | state_mbm = glfw.get_mouse_button(self.window, glfw.MOUSE_BUTTON_MIDDLE) 47 | state_mbr = glfw.get_mouse_button(self.window, glfw.MOUSE_BUTTON_RIGHT) 48 | 49 | return ( 50 | mouse_x, 51 | mouse_y, 52 | 1 53 | if state_mbl == glfw.PRESS 54 | else 0 | 2 55 | if state_mbm == glfw.PRESS 56 | else 0 | 4 57 | if state_mbr == glfw.PRESS 58 | else 0, 59 | ) 60 | 61 | # noinspection PyProtectedMember 62 | def run(self): 63 | glfw_native.glfwCreateWindow.argtypes = [ 64 | ctypes.c_int, 65 | ctypes.c_int, 66 | ctypes.c_char_p, 67 | ] 68 | glfw_native.glfwCreateWindow.restype = ctypes.POINTER(glfw._GLFWwindow) 69 | glfw_native.glfwMakeContextCurrent.argtypes = [ctypes.POINTER(glfw._GLFWwindow)] 70 | glfw_native.glfwWindowShouldClose.argtypes = [ctypes.POINTER(glfw._GLFWwindow)] 71 | glfw_native.glfwWindowShouldClose.restype = ctypes.c_int 72 | 73 | glfw.init() 74 | 75 | glfw.window_hint(glfw.CLIENT_API, glfw.NO_API) 76 | glfw.window_hint(glfw.COCOA_RETINA_FRAMEBUFFER, glfw.TRUE) 77 | 78 | self.window = glfw.create_window( 79 | self.width, self.height, self.title, None, None 80 | ) 81 | 82 | self.fb_width, self.fb_height = glfw.get_framebuffer_size(self.window) 83 | 84 | self.hidpi = self.fb_width != self.width or self.fb_height != self.height 85 | 86 | glfw.set_window_size_callback(self.window, self._handle_window_resize) 87 | 88 | handle, display = None, None 89 | 90 | if sys.platform == "darwin": 91 | handle = glfw_native.glfwGetCocoaWindow(self.window) 92 | elif sys.platform == "win32": 93 | handle = glfw_native.glfwGetWin32Window(self.window) 94 | elif sys.platform == "linux" and "WAYLAND_DISPLAY" not in os.environ: 95 | handle = glfw_native.glfwGetX11Window(self.window) 96 | display = glfw_native.glfwGetX11Display() 97 | elif sys.platform == "linux" and "WAYLAND_DISPLAY" in os.environ: 98 | handle = glfw_native.glfwGetWaylandWindow(self.window) 99 | display = glfw_native.glfwGetWaylandDisplay() 100 | 101 | data = bgfx.PlatformData() 102 | data.ndt = as_void_ptr(display) if display else cppyy.nullptr 103 | data.nwh = as_void_ptr(handle) 104 | data.context = cppyy.nullptr 105 | data.backBuffer = cppyy.nullptr 106 | data.backBufferDS = cppyy.nullptr 107 | 108 | self.init(data) 109 | 110 | last_time = None 111 | 112 | while not glfw.window_should_close(self.window): 113 | glfw.poll_events() 114 | 115 | now = time.perf_counter() 116 | if not last_time: 117 | last_time = now 118 | 119 | frame_time = now - last_time 120 | last_time = now 121 | 122 | self.update(frame_time) 123 | 124 | self.shutdown() 125 | glfw.terminate() 126 | 127 | def _handle_window_resize(self, window, width, height): 128 | self.width = width 129 | self.height = height 130 | 131 | self.fb_width, self.fb_height = glfw.get_framebuffer_size(window) 132 | 133 | self.resize() 134 | 135 | def _get_normalized_mouse_coords(self, mouse_x, mouse_y): 136 | return 1.0 - mouse_x / self.fb_width, 1.0 - mouse_y / self.fb_height 137 | -------------------------------------------------------------------------------- /demo/shaders/demo.ComputeShader.comp: -------------------------------------------------------------------------------- 1 | 2 | #include "bgfx_compute.sh" 3 | 4 | IMAGE2D_WR(InputTexture, rgba8, 0); 5 | BUFFER_RO(_Particles, float, 9); 6 | 7 | uniform vec2 _ParticleSize; 8 | 9 | NUM_THREADS(16, 16, 1) 10 | void main() 11 | { 12 | if (gl_GlobalInvocationID.x >= _ParticleSize.x || gl_GlobalInvocationID.y >= _ParticleSize.y) 13 | { 14 | return; 15 | } 16 | 17 | ivec2 coord = ivec2(gl_GlobalInvocationID.xy); 18 | uint pos = gl_GlobalInvocationID.y * _ParticleSize.x + gl_GlobalInvocationID.x; 19 | float particle_value = _Particles[pos]; 20 | imageStore(InputTexture, coord, vec4(particle_value, particle_value, particle_value, particle_value)); 21 | } -------------------------------------------------------------------------------- /demo/shaders/demo.FieldFragmentShader.frag: -------------------------------------------------------------------------------- 1 | $input v_texcoord0 2 | 3 | #include 4 | 5 | SAMPLER2D(s_texColor, 0); 6 | 7 | vec3 viridis(float t) { 8 | 9 | const vec3 c0 = vec3(0.2777273272234177, 0.005407344544966578, 0.3340998053353061); 10 | const vec3 c1 = vec3(0.1050930431085774, 1.404613529898575, 1.384590162594685); 11 | const vec3 c2 = vec3(-0.3308618287255563, 0.214847559468213, 0.09509516302823659); 12 | const vec3 c3 = vec3(-4.634230498983486, -5.799100973351585, -19.33244095627987); 13 | const vec3 c4 = vec3(6.228269936347081, 14.17993336680509, 56.69055260068105); 14 | const vec3 c5 = vec3(4.776384997670288, -13.74514537774601, -65.35303263337234); 15 | const vec3 c6 = vec3(-5.435455855934631, 4.645852612178535, 26.3124352495832); 16 | 17 | return c0+t*(c1+t*(c2+t*(c3+t*(c4+t*(c5+t*c6))))); 18 | 19 | } 20 | 21 | vec3 plasma(float t) { 22 | 23 | const vec3 c0 = vec3(0.05873234392399702, 0.02333670892565664, 0.5433401826748754); 24 | const vec3 c1 = vec3(2.176514634195958, 0.2383834171260182, 0.7539604599784036); 25 | const vec3 c2 = vec3(-2.689460476458034, -7.455851135738909, 3.110799939717086); 26 | const vec3 c3 = vec3(6.130348345893603, 42.3461881477227, -28.51885465332158); 27 | const vec3 c4 = vec3(-11.10743619062271, -82.66631109428045, 60.13984767418263); 28 | const vec3 c5 = vec3(10.02306557647065, 71.41361770095349, -54.07218655560067); 29 | const vec3 c6 = vec3(-3.658713842777788, -22.93153465461149, 18.19190778539828); 30 | 31 | return c0+t*(c1+t*(c2+t*(c3+t*(c4+t*(c5+t*c6))))); 32 | 33 | } 34 | 35 | vec3 magma(float t) { 36 | 37 | const vec3 c0 = vec3(-0.002136485053939582, -0.000749655052795221, -0.005386127855323933); 38 | const vec3 c1 = vec3(0.2516605407371642, 0.6775232436837668, 2.494026599312351); 39 | const vec3 c2 = vec3(8.353717279216625, -3.577719514958484, 0.3144679030132573); 40 | const vec3 c3 = vec3(-27.66873308576866, 14.26473078096533, -13.64921318813922); 41 | const vec3 c4 = vec3(52.17613981234068, -27.94360607168351, 12.94416944238394); 42 | const vec3 c5 = vec3(-50.76852536473588, 29.04658282127291, 4.23415299384598); 43 | const vec3 c6 = vec3(18.65570506591883, -11.48977351997711, -5.601961508734096); 44 | 45 | return c0+t*(c1+t*(c2+t*(c3+t*(c4+t*(c5+t*c6))))); 46 | 47 | } 48 | 49 | vec3 inferno(float t) { 50 | 51 | const vec3 c0 = vec3(0.0002189403691192265, 0.001651004631001012, -0.01948089843709184); 52 | const vec3 c1 = vec3(0.1065134194856116, 0.5639564367884091, 3.932712388889277); 53 | const vec3 c2 = vec3(11.60249308247187, -3.972853965665698, -15.9423941062914); 54 | const vec3 c3 = vec3(-41.70399613139459, 17.43639888205313, 44.35414519872813); 55 | const vec3 c4 = vec3(77.162935699427, -33.40235894210092, -81.80730925738993); 56 | const vec3 c5 = vec3(-71.31942824499214, 32.62606426397723, 73.20951985803202); 57 | const vec3 c6 = vec3(25.13112622477341, -12.24266895238567, -23.07032500287172); 58 | 59 | return c0+t*(c1+t*(c2+t*(c3+t*(c4+t*(c5+t*c6))))); 60 | 61 | } 62 | 63 | vec3 posterize(vec3 _rgb, float _numColors) 64 | { 65 | return floor(_rgb*_numColors) / _numColors; 66 | } 67 | 68 | vec4 posterize(vec4 _rgba, float _numColors) 69 | { 70 | return vec4(posterize(_rgba.xyz, _numColors), _rgba.w); 71 | } 72 | 73 | #define NUM_OCTAVES 5 74 | 75 | float rand(float n){return fract(sin(n) * 43758.5453123);} 76 | 77 | float noise(float p){ 78 | float fl = floor(p); 79 | float fc = fract(p); 80 | return mix(rand(fl), rand(fl + 1.0), fc); 81 | } 82 | 83 | float fbm(float x) { 84 | float v = 0.0; 85 | float a = 0.5; 86 | float shift = float(100); 87 | for (int i = 0; i < NUM_OCTAVES; ++i) { 88 | v += a * noise(x); 89 | x = x * 2.0 + shift; 90 | a *= 0.5; 91 | } 92 | return v; 93 | } 94 | 95 | void main() 96 | { 97 | vec4 col = texture2D(s_texColor, v_texcoord0.xy*0.5 + 0.5); 98 | gl_FragColor = vec4(plasma(fbm(col.x)), col.x); 99 | } -------------------------------------------------------------------------------- /demo/shaders/demo.QuiverFragmentShader.frag: -------------------------------------------------------------------------------- 1 | $input v_texcoord0 2 | 3 | #include 4 | #include "bgfx_compute.sh" 5 | 6 | SAMPLER2D(s_texColor, 0); 7 | BUFFER_RO(_VelocityIn, vec2, 1); 8 | 9 | uniform float ArrowTileSize; 10 | uniform vec2 VelocitySize; 11 | uniform vec2 WindowSize; 12 | 13 | 14 | vec2 _arrow_tile_center_coord(vec2 pos) { 15 | return (floor(pos / ArrowTileSize) + 0.5) * ArrowTileSize; 16 | } 17 | 18 | float _line(vec2 p, vec2 p1, vec2 p2) { 19 | vec2 center = (p1 + p2) * 0.5; 20 | float len = length(p2 - p1); 21 | vec2 dir = (p2 - p1) / len; 22 | vec2 rel_p = p - center; 23 | float dist1 = abs(dot(rel_p, vec2(dir.y, -dir.x))); 24 | float dist2 = abs(dot(rel_p, dir)) - 0.5*len; 25 | return max(dist1, dist2); 26 | } 27 | 28 | float _vector(vec2 p, vec2 v) { 29 | p -= _arrow_tile_center_coord(p); 30 | float mag_v = length(v), mag_p = length(p); 31 | 32 | if (mag_v > 0.001) { 33 | vec2 dir_v = v / mag_v; 34 | 35 | mag_v = clamp(mag_v, 0.0, ArrowTileSize * 0.5); 36 | 37 | v = dir_v * mag_v; 38 | 39 | float shaft = _line(p, v, -v); 40 | float head = min(_line(p, v, 0.4*v + 0.2*vec2(-v.y, v.x)), 41 | _line(p, v, 0.4*v + 0.2*vec2(v.y, -v.x))); 42 | 43 | return min(shaft, head); 44 | } else { 45 | return 1.0; 46 | } 47 | } 48 | 49 | vec2 _field(vec2 pos) { 50 | ivec2 zero = ivec2(0, 0); 51 | vec2 one = vec2(1.0, 1.0); 52 | vec2 fPos = (one - (pos / WindowSize)) * VelocitySize; 53 | ivec2 size_bounds = ivec2(VelocitySize.x - 1u, VelocitySize.y - 1u); 54 | ivec2 top_right = ivec2(clamp(ceil(fPos), vec2(zero), vec2(size_bounds))); 55 | ivec2 bottom_left = ivec2(clamp(floor(fPos), vec2(zero), vec2(size_bounds))); 56 | vec2 delta = fPos - vec2(bottom_left); 57 | vec2 lt = _VelocityIn[uint(top_right.y) * VelocitySize.x + uint(bottom_left.x)]; 58 | vec2 rt = _VelocityIn[uint(top_right.y) * VelocitySize.x + uint(top_right.x)]; 59 | vec2 lb = _VelocityIn[uint(bottom_left.y) * VelocitySize.x + uint(bottom_left.x)]; 60 | vec2 rb = _VelocityIn[uint(bottom_left.y) * VelocitySize.x + uint(top_right.x)]; 61 | vec2 h1 = mix(lt, rt, vec2(delta.x, 0)); 62 | vec2 h2 = mix(lb, rb, vec2(delta.x, 0)); 63 | return -1.0 * mix(h2, h1, vec2(delta.y, 0)) * (WindowSize / VelocitySize); 64 | } 65 | 66 | void main() 67 | { 68 | float arrow_dist = _vector(gl_FragCoord.xy, _field(_arrow_tile_center_coord(gl_FragCoord.xy)) * ArrowTileSize * 0.4); 69 | gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0 - clamp(arrow_dist, 0.0, 1.0)); 70 | } -------------------------------------------------------------------------------- /demo/shaders/demo.VertexShader.vert: -------------------------------------------------------------------------------- 1 | $input a_position, a_texcoord0 2 | $output v_texcoord0 3 | 4 | #include 5 | 6 | void main() 7 | { 8 | gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0) ); 9 | v_texcoord0 = a_texcoord0; 10 | } -------------------------------------------------------------------------------- /demo/shaders/shader.AddParticle.comp: -------------------------------------------------------------------------------- 1 | 2 | #include "bgfx_compute.sh" 3 | 4 | BUFFER_RO(_ParticlesIn, float, 9); 5 | 6 | BUFFER_WR(_ParticlesOut, float, 10); 7 | 8 | BUFFER_RO(_Obstacles, vec2, 7); 9 | 10 | uniform float _Radius; 11 | 12 | uniform vec2 _Position; 13 | 14 | uniform float _Value; 15 | 16 | uniform vec2 _ParticleSize; 17 | 18 | NUM_THREADS(16, 16, 1) 19 | void main() 20 | { 21 | if (gl_GlobalInvocationID.x >= _ParticleSize.x || gl_GlobalInvocationID.y >= _ParticleSize.y) 22 | { 23 | return; 24 | } 25 | uint pos = gl_GlobalInvocationID.y * _ParticleSize.x + gl_GlobalInvocationID.x ; 26 | vec2 splat_pos = _Position * _ParticleSize; 27 | float val = _ParticlesIn[pos]; 28 | float result = val; 29 | float len = distance(splat_pos, gl_GlobalInvocationID.xy); 30 | if (len <= _Radius) 31 | { 32 | result = clamp(val + _Value * (_Radius - len) / _Radius, 0.0f, 255.0f); 33 | } 34 | _ParticlesOut[pos] = result; 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /demo/shaders/shader.AdvectParticle.comp: -------------------------------------------------------------------------------- 1 | #include "bgfx_compute.sh" 2 | 3 | BUFFER_RO(_ParticlesIn, float, 9); 4 | 5 | BUFFER_WR(_ParticlesOut, float, 10); 6 | 7 | BUFFER_RO(_Velocity, vec2, 1); 8 | 9 | BUFFER_RO(_Obstacles, vec2, 7); 10 | 11 | uniform vec2 _ParticleSize; 12 | 13 | uniform vec2 _VelocitySize; 14 | 15 | uniform float _Dissipation; 16 | 17 | uniform float _ElapsedTime; 18 | 19 | uniform float _Speed; 20 | 21 | vec2 GetVelocity(vec2 fPos) 22 | { 23 | ivec2 zero = ivec2(0, 0); 24 | ivec2 size_bounds = ivec2(_VelocitySize.x - 1u, _VelocitySize.y - 1u); 25 | ivec2 top_right = ivec2(clamp(ceil(fPos), vec2(zero), vec2(size_bounds))); 26 | ivec2 bottom_left = ivec2(clamp(floor(fPos), vec2(zero), vec2(size_bounds))); 27 | vec2 delta = fPos - vec2(bottom_left); 28 | vec2 lt = _Velocity[uint(top_right.y) * _VelocitySize.x + uint(bottom_left.x)]; 29 | vec2 rt = _Velocity[uint(top_right.y) * _VelocitySize.x + uint(top_right.x)]; 30 | vec2 lb = _Velocity[uint(bottom_left.y) * _VelocitySize.x + uint(bottom_left.x)]; 31 | vec2 rb = _Velocity[uint(bottom_left.y) * _VelocitySize.x + uint(top_right.x)]; 32 | vec2 h1 = mix(lt, rt, vec2(delta.x)); 33 | vec2 h2 = mix(lb, rb, vec2(delta.x)); 34 | return mix(h2, h1, vec2(delta.y)) * (_ParticleSize / _VelocitySize); 35 | } 36 | 37 | NUM_THREADS(16, 16, 1) 38 | void main() 39 | { 40 | if (gl_GlobalInvocationID.x >= _ParticleSize.x || gl_GlobalInvocationID.y >= _ParticleSize.y) 41 | { 42 | return; 43 | } 44 | 45 | uint particle_pos = gl_GlobalInvocationID.y * _ParticleSize.x + gl_GlobalInvocationID.x; 46 | vec2 fNormalisedPos = vec2(float(gl_GlobalInvocationID.x) / float(_ParticleSize.x), float(gl_GlobalInvocationID.y) / float(_ParticleSize.y)) * vec2(_VelocitySize); 47 | uint obstacle_pos = (uint(fNormalisedPos.y)) * _VelocitySize.x + (uint(fNormalisedPos.x)); 48 | vec2 obstacle = _Obstacles[obstacle_pos]; 49 | 50 | if (obstacle.x > 0.0f || obstacle.y > 0.0f) 51 | { 52 | _ParticlesOut[particle_pos] = 0.0f; 53 | } 54 | else 55 | { 56 | vec2 vel = GetVelocity(fNormalisedPos); 57 | vec2 final_pos = vec2(float(gl_GlobalInvocationID.x) - vel.x * _ElapsedTime * _Speed, float(gl_GlobalInvocationID.y) - vel.y * _ElapsedTime * _Speed); 58 | ivec2 zero = ivec2(0, 0); 59 | ivec2 size_bounds = ivec2(_ParticleSize.x - 1u, _ParticleSize.y - 1u); 60 | ivec2 top_right = ivec2(clamp(ceil(final_pos), vec2(zero), vec2(size_bounds))); 61 | ivec2 bottom_left = ivec2(clamp(floor(final_pos), vec2(zero), vec2(size_bounds))); 62 | vec2 delta = final_pos - vec2(bottom_left); 63 | float lt = _ParticlesIn[uint(top_right.y) * _ParticleSize.x + uint(bottom_left.x)]; 64 | float rt = _ParticlesIn[uint(top_right.y) * _ParticleSize.x + uint(top_right.x)]; 65 | float lb = _ParticlesIn[uint(bottom_left.y) * _ParticleSize.x + uint(bottom_left.x)]; 66 | float rb = _ParticlesIn[uint(bottom_left.y) * _ParticleSize.x + uint(top_right.x)]; 67 | float h1 = mix(lt, rt, vec2(delta.x)); 68 | float h2 = mix(lb, rb, vec2(delta.x)); 69 | _ParticlesOut[particle_pos] = mix(h2, h1, vec2(delta.y)) * _Dissipation; 70 | } 71 | } -------------------------------------------------------------------------------- /demo/shaders/varying.def.sc: -------------------------------------------------------------------------------- 1 | vec4 v_color0 : COLOR0; 2 | vec2 v_texcoord0 : TEXCOORD0; 3 | 4 | vec3 a_position : POSITION; 5 | vec2 a_texcoord0 : TEXCOORD0; 6 | vec4 a_color0 : COLOR0; -------------------------------------------------------------------------------- /demo/simulation_demo.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | from ctypes import c_float, sizeof, Structure 3 | from pathlib import Path 4 | 5 | import glfw 6 | import numpy as np 7 | from pybgfx import ( 8 | bgfx, 9 | ImGui, 10 | ) 11 | from pybgfx.utils.imgui_utils import ImGuiExtra 12 | from pybgfx.constants import ( 13 | BGFX_CLEAR_COLOR, 14 | BGFX_CLEAR_DEPTH, 15 | BGFX_RESET_VSYNC, 16 | BGFX_DEBUG_TEXT, 17 | BGFX_STATE_WRITE_RGB, 18 | BGFX_STATE_WRITE_A, 19 | BGFX_STATE_DEFAULT, 20 | BGFX_TEXTURE_COMPUTE_WRITE, 21 | BGFX_RESET_HIDPI, 22 | BGFX_STATE_BLEND_ALPHA, 23 | ) 24 | from pybgfx.utils import as_void_ptr 25 | from pybgfx.utils.shaders_utils import ShaderType, load_shader 26 | from loguru import logger 27 | 28 | from demo.example_window import ExampleWindow 29 | from demo.smooth_particles_area import SmoothParticlesArea 30 | # from demo.utils.imgui_utils import show_properties_dialog 31 | from demo.utils.imgui_utils import show_properties_dialog 32 | from demo.utils.matrix_utils import look_at, proj 33 | from natrix.core.fluid_simulator import FluidSimulator 34 | 35 | logger.enable("bgfx") 36 | 37 | 38 | class PosColorVertex(Structure): 39 | _fields_ = [ 40 | ("m_x", c_float), 41 | ("m_y", c_float), 42 | ("m_z", c_float), 43 | ("m_u", c_float), 44 | ("m_v", c_float), 45 | ("m_w", c_float), 46 | ] 47 | 48 | 49 | cube_vertices = (PosColorVertex * 4)( 50 | PosColorVertex(-1.0, 1.0, 0.0, -1.0, 1.0, 1.0), 51 | PosColorVertex(1.0, 1.0, 0.0, 1.0, 1.0, 1.0), 52 | PosColorVertex(-1.0, -1.0, 0.0, -1.0, -1.0, 1.0), 53 | PosColorVertex(1.0, -1.0, 0.0, 1.0, -1.0, 1.0), 54 | ) 55 | 56 | cube_indices = np.array([0, 1, 2, 1, 2, 3], dtype=np.uint16) 57 | 58 | root_path = Path(__file__).parent / "shaders" 59 | 60 | 61 | class SimulationDemo(ExampleWindow): 62 | def __init__(self, width, height, title): 63 | super().__init__(width, height, title) 64 | 65 | self.init_conf = bgfx.Init() 66 | self.init_conf.debug = True 67 | self.init_conf.resolution.width = self.fb_width 68 | self.init_conf.resolution.height = self.fb_height 69 | self.init_conf.resolution.reset = BGFX_RESET_VSYNC | BGFX_RESET_HIDPI 70 | self.old_mouse_x = -1 71 | self.old_mouse_y = -1 72 | 73 | self.particles_strength = 0.04 74 | self.particles_diameter = 250.0 75 | 76 | self.show_quiver_plot_overlay = ctypes.c_bool(False) 77 | self.quiver_plot_resolution = ctypes.c_int(2) 78 | self.quiver_plot_resolutions = (8.0, 16.0, 32.0, 64.0) 79 | 80 | def init(self, platform_data): 81 | bgfx.renderFrame() 82 | bgfx.setPlatformData(platform_data) 83 | bgfx.init(self.init_conf) 84 | bgfx.reset( 85 | self.fb_width, 86 | self.fb_height, 87 | BGFX_RESET_VSYNC | BGFX_RESET_HIDPI, 88 | self.init_conf.resolution.format, 89 | ) 90 | 91 | bgfx.setDebug(BGFX_DEBUG_TEXT) 92 | # bgfx.setViewClear(0, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0xFFFFFFFF, 1.0, 0) 93 | bgfx.setViewClear(0, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x1a0427FF, 1.0, 0) 94 | 95 | self.vertex_layout = bgfx.VertexLayout() 96 | self.vertex_layout.begin().add( 97 | bgfx.Attrib.Position, 3, bgfx.AttribType.Float 98 | ).add(bgfx.Attrib.TexCoord0, 3, bgfx.AttribType.Float).end() 99 | 100 | self.fluid_simulator = FluidSimulator( 101 | self.width // 2, self.height // 2, self.vertex_layout 102 | ) 103 | self.fluid_simulator.vorticity = 1.0 104 | self.fluid_simulator.viscosity = 0.5 105 | self.fluid_simulator.iterations = 50 106 | 107 | self.particle_area = SmoothParticlesArea( 108 | self.fb_width, self.fb_height, self.fluid_simulator, self.vertex_layout 109 | ) 110 | self.particle_area.dissipation = 0.980 111 | 112 | # Create static vertex buffer 113 | vb_memory = bgfx.copy(as_void_ptr(cube_vertices), sizeof(PosColorVertex) * 4) 114 | self.vertex_buffer = bgfx.createVertexBuffer(vb_memory, self.vertex_layout) 115 | 116 | ib_memory = bgfx.copy(as_void_ptr(cube_indices), cube_indices.nbytes) 117 | self.index_buffer = bgfx.createIndexBuffer(ib_memory) 118 | 119 | self.output_texture = bgfx.createTexture2D( 120 | self.fb_width, 121 | self.fb_height, 122 | False, 123 | 1, 124 | bgfx.TextureFormat.RGBA8, 125 | BGFX_TEXTURE_COMPUTE_WRITE, 126 | ) 127 | 128 | self.texture_uniform = bgfx.createUniform( 129 | "InputTexture", bgfx.UniformType.Sampler 130 | ) 131 | self.window_size_uniform = bgfx.createUniform( 132 | "WindowSize", bgfx.UniformType.Vec4 133 | ) 134 | self.velocity_size_uniform = bgfx.createUniform( 135 | "VelocitySize", bgfx.UniformType.Vec4 136 | ) 137 | self.arrow_tile_size_uniform = bgfx.createUniform( 138 | "ArrowTileSize", bgfx.UniformType.Vec4 139 | ) 140 | 141 | # Create program from shaders. 142 | self.main_program = bgfx.createProgram( 143 | load_shader( 144 | "demo.VertexShader.vert", ShaderType.VERTEX, root_path=root_path 145 | ), 146 | load_shader( 147 | "demo.FieldFragmentShader.frag", 148 | ShaderType.FRAGMENT, 149 | root_path=root_path, 150 | ), 151 | True, 152 | ) 153 | self.quiver_program = bgfx.createProgram( 154 | load_shader( 155 | "demo.VertexShader.vert", ShaderType.VERTEX, root_path=root_path 156 | ), 157 | load_shader( 158 | "demo.QuiverFragmentShader.frag", 159 | ShaderType.FRAGMENT, 160 | root_path=root_path, 161 | ), 162 | True, 163 | ) 164 | self.cs_program = bgfx.createProgram( 165 | load_shader( 166 | "demo.ComputeShader.comp", ShaderType.COMPUTE, root_path=root_path 167 | ), 168 | True, 169 | ) 170 | 171 | ImGuiExtra.create(36.0) 172 | 173 | def shutdown(self): 174 | self.fluid_simulator.destroy() 175 | self.particle_area.destroy() 176 | 177 | ImGuiExtra.destroy() 178 | 179 | bgfx.destroy(self.index_buffer) 180 | bgfx.destroy(self.vertex_buffer) 181 | bgfx.destroy(self.output_texture) 182 | bgfx.destroy(self.texture_uniform) 183 | bgfx.destroy(self.window_size_uniform) 184 | bgfx.destroy(self.velocity_size_uniform) 185 | bgfx.destroy(self.arrow_tile_size_uniform) 186 | bgfx.destroy(self.main_program) 187 | bgfx.destroy(self.quiver_program) 188 | bgfx.destroy(self.cs_program) 189 | 190 | bgfx.shutdown() 191 | 192 | def update(self, dt): 193 | mouse_x, mouse_y, buttons_states = self.get_mouse_state() 194 | 195 | ImGuiExtra.begin_frame( 196 | int(mouse_x), int(mouse_y), buttons_states, 0, self.fb_width, self.fb_height 197 | ) 198 | show_properties_dialog(self.fb_width, self.fb_height, self.hidpi) 199 | self._create_imgui_config_dialog() 200 | 201 | ImGuiExtra.end_frame() 202 | 203 | at = (c_float * 3)(*[0.0, 0.0, 0.0]) 204 | eye = (c_float * 3)(*[0.0, 0.0, 10.0]) 205 | up = (c_float * 3)(*[0.0, 1.0, 0.0]) 206 | 207 | view = look_at(eye, at, up) 208 | projection = proj(11.4, 1, 0.1, 100.0) 209 | 210 | bgfx.setViewRect(0, 0, 0, self.fb_width, self.fb_height) 211 | 212 | bgfx.setViewTransform(0, as_void_ptr(view), as_void_ptr(projection)) 213 | 214 | bgfx.setVertexBuffer(0, self.vertex_buffer, 0, 4) 215 | bgfx.setIndexBuffer(self.index_buffer, 0, cube_indices.size) 216 | 217 | bgfx.setState(BGFX_STATE_DEFAULT) 218 | bgfx.setImage(0, self.output_texture, 0, bgfx.Access.Write) 219 | 220 | self.fluid_simulator.add_circle_obstacle((0.5, 0.5), 40.0) 221 | 222 | self.fluid_simulator.update(dt) 223 | self.particle_area.update(dt) 224 | 225 | if glfw.get_mouse_button(self.window, glfw.MOUSE_BUTTON_LEFT) == glfw.PRESS\ 226 | and not ImGui.GetIO().WantCaptureMouse: 227 | n_mouse_x, n_mouse_y = self._get_normalized_mouse_coords(mouse_x, mouse_y) 228 | n_old_mouse_x, n_old_mouse_y = self._get_normalized_mouse_coords( 229 | self.old_mouse_x, self.old_mouse_y 230 | ) 231 | vel_x = n_mouse_x - n_old_mouse_x 232 | vel_y = n_mouse_y - n_old_mouse_y 233 | # if vel_x != 0 and vel_y != 0: 234 | self.fluid_simulator.add_velocity( 235 | (n_mouse_x, n_mouse_y), (vel_x * 10, vel_y * 10), 32.0 236 | ) 237 | self.particle_area.add_particles((n_mouse_x, n_mouse_y), self.particles_diameter, self.particles_strength) 238 | 239 | self.old_mouse_x = mouse_x 240 | self.old_mouse_y = mouse_y 241 | 242 | bgfx.setState( 243 | 0 | BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_BLEND_ALPHA 244 | ) 245 | 246 | bgfx.dispatch(0, self.cs_program, self.fb_width // 16, self.fb_height // 16) 247 | bgfx.setTexture(0, self.texture_uniform, self.output_texture) 248 | bgfx.submit(0, self.main_program, 0, False) 249 | 250 | if self.show_quiver_plot_overlay.value: 251 | bgfx.setBuffer( 252 | 1, self.fluid_simulator.get_velocity_buffer(), bgfx.Access.Read 253 | ) 254 | bgfx.setUniform( 255 | self.window_size_uniform, 256 | as_void_ptr((c_float * 2)(self.fb_width, self.fb_height)), 257 | ) 258 | bgfx.setUniform( 259 | self.velocity_size_uniform, 260 | as_void_ptr( 261 | (c_float * 2)( 262 | self.fluid_simulator.width, self.fluid_simulator.height 263 | ) 264 | ), 265 | ) 266 | bgfx.setUniform( 267 | self.arrow_tile_size_uniform, 268 | as_void_ptr( 269 | (c_float * 1)( 270 | self.quiver_plot_resolutions[self.quiver_plot_resolution.value] 271 | ) 272 | ), 273 | ) 274 | bgfx.submit(0, self.quiver_program, 0, False) 275 | 276 | bgfx.frame() 277 | 278 | def resize(self): 279 | bgfx.reset( 280 | self.fb_width, 281 | self.fb_height, 282 | BGFX_RESET_VSYNC | BGFX_RESET_HIDPI, 283 | self.init_conf.resolution.format, 284 | ) 285 | 286 | def _create_imgui_config_dialog(self): 287 | res_multiplier = 2 if self.hidpi else 1 288 | 289 | ImGui.SetNextWindowPos( 290 | ImGui.ImVec2( 291 | self.fb_width - self.fb_width / 4.1 - 20.0 * res_multiplier, 292 | 40.0 * res_multiplier, 293 | ), 294 | ImGui.ImGuiCond_FirstUseEver, 295 | ) 296 | ImGui.SetNextWindowSize( 297 | ImGui.ImVec2(self.fb_width / 4.1, self.fb_height / 2.1), 298 | ImGui.ImGuiCond_FirstUseEver, 299 | ) 300 | 301 | ImGui.Begin("\uf013 Settings") 302 | 303 | vorticity = ctypes.c_float(self.fluid_simulator.vorticity) 304 | viscosity = ctypes.c_float(self.fluid_simulator.viscosity) 305 | speed = ctypes.c_float(self.fluid_simulator.speed) 306 | iterations = ctypes.c_int(self.fluid_simulator.iterations) 307 | borders = ctypes.c_bool(self.fluid_simulator.has_borders) 308 | fluid_dissipation = ctypes.c_float(self.fluid_simulator.dissipation) 309 | part_strength = ctypes.c_float(self.particles_strength) 310 | part_diameter = ctypes.c_float(self.particles_diameter) 311 | 312 | ImGui.Text("Fluid simulation parameters") 313 | 314 | if ImGui.SliderFloat("Vorticity", vorticity, 0.0, 10.0): 315 | self.fluid_simulator.vorticity = vorticity.value 316 | 317 | if ImGui.SliderFloat("Viscosity", viscosity, 0.0, 10.0): 318 | self.fluid_simulator.viscosity = viscosity.value 319 | 320 | if ImGui.SliderFloat("Speed", speed, 1.0, 1000.0): 321 | self.fluid_simulator.speed = speed.value 322 | self.particle_area.speed = speed.value 323 | 324 | if ImGui.SliderFloat("V_Dissipation", fluid_dissipation, 0.500, 1.0): 325 | self.fluid_simulator.dissipation = fluid_dissipation.value 326 | 327 | if ImGui.SliderInt("Iteration", iterations, 10, 100): 328 | self.fluid_simulator.iterations = iterations.value 329 | 330 | if ImGui.Checkbox("Borders", borders): 331 | self.fluid_simulator.has_borders = borders.value 332 | 333 | ImGui.Separator() 334 | 335 | particles_dissipation = ctypes.c_float(self.particle_area.dissipation) 336 | 337 | ImGui.Text("Particles area parameters") 338 | 339 | if ImGui.SliderFloat("P_Dissipation", particles_dissipation, 0.900, 1.0): 340 | self.particle_area.dissipation = particles_dissipation.value 341 | 342 | if ImGui.SliderFloat("Strength", part_strength, 0.0, 1.0): 343 | self.particles_strength = part_strength.value 344 | 345 | if ImGui.SliderFloat("Diameter", part_diameter, 100.0, 1000.0): 346 | self.particles_diameter = part_diameter.value 347 | 348 | ImGui.Separator() 349 | 350 | stop = ctypes.c_bool(not self.fluid_simulator.simulate) 351 | 352 | if ImGui.Checkbox("Stop simulation", stop): 353 | self.fluid_simulator.simulate = not stop.value 354 | self.particle_area.simulate = not stop.value 355 | 356 | ImGui.Checkbox("Show velocity field", self.show_quiver_plot_overlay) 357 | ImGui.SameLine() 358 | ImGui.PushItemWidth(200.0) 359 | ImGui.Combo( 360 | "", self.quiver_plot_resolution, ["Very Small", "Small", "Medium", "Large"], 4, 361 | ) 362 | ImGui.PopItemWidth() 363 | ImGui.End() 364 | 365 | 366 | if __name__ == "__main__": 367 | demo = SimulationDemo(1280, 720, "Fluid Simulation Demo") 368 | demo.run() 369 | -------------------------------------------------------------------------------- /demo/smooth_particles_area.py: -------------------------------------------------------------------------------- 1 | from ctypes import c_float 2 | from math import ceil 3 | from pathlib import Path 4 | 5 | from pybgfx import bgfx 6 | from pybgfx.utils import as_void_ptr 7 | from pybgfx.utils.shaders_utils import ShaderType, load_shader 8 | from natrix.core.common.constants import TemplateConstants 9 | from natrix.core.fluid_simulator import FluidSimulator 10 | from natrix.core.utils.shaders_utils import create_buffer 11 | 12 | root_path = Path(__file__).parent / "shaders" 13 | 14 | 15 | class SmoothParticlesArea: 16 | PARTICLES_IN = 0 17 | PARTICLES_OUT = 1 18 | 19 | _width = 512 20 | _height = 512 21 | _particles_buffer = None 22 | 23 | _speed = 500.0 24 | _dissipation = 1.0 25 | 26 | simulate = True 27 | 28 | def __init__( 29 | self, 30 | width: int, 31 | height: int, 32 | fluid_simulation: FluidSimulator, 33 | vertex_layout: bgfx.VertexLayout, 34 | ): 35 | self.fluid_simulation = fluid_simulation 36 | self.vertex_layout = vertex_layout 37 | 38 | self._width = width 39 | self._height = height 40 | 41 | self._create_uniforms() 42 | 43 | self._load_compute_kernels() 44 | self._set_size() 45 | self._create_buffers() 46 | self._init_compute_kernels() 47 | 48 | @property 49 | def speed(self): 50 | return self._speed 51 | 52 | @speed.setter 53 | def speed(self, value): 54 | if value > 0: 55 | self._speed = value 56 | else: 57 | raise ValueError("'Speed' should be greater than zero") 58 | 59 | @property 60 | def dissipation(self): 61 | return self._dissipation 62 | 63 | @dissipation.setter 64 | def dissipation(self, value): 65 | if value > 0: 66 | self._dissipation = value 67 | else: 68 | raise ValueError("'Dissipation' should be grater than zero") 69 | 70 | def add_particles(self, position: tuple, radius: float, strength: float): 71 | if self.simulate: 72 | self._init_compute_kernels() 73 | bgfx.setUniform( 74 | self.position_uniform, 75 | as_void_ptr((c_float * 2)(position[0], position[1])), 76 | ) 77 | bgfx.setUniform(self.value_uniform, as_void_ptr((c_float * 1)(strength))) 78 | bgfx.setUniform(self.radius_uniform, as_void_ptr((c_float * 1)(radius))) 79 | 80 | bgfx.dispatch( 81 | 0, self._add_particles_kernel, self._num_groups_x, self._num_groups_x, 1 82 | ) 83 | self._flip_buffer() 84 | 85 | def update(self, time_delta: float): 86 | self._init_compute_kernels() 87 | 88 | if self.simulate: 89 | bgfx.setUniform( 90 | self.dissipation_uniform, as_void_ptr((c_float * 1)(self.dissipation)) 91 | ) 92 | bgfx.setUniform( 93 | self.elapsed_time_uniform, as_void_ptr((c_float * 1)(time_delta)) 94 | ) 95 | bgfx.setUniform(self.speed_uniform, as_void_ptr((c_float * 1)(self.speed))) 96 | 97 | bgfx.dispatch( 98 | 0, 99 | self._advect_particles_kernel, 100 | self._num_groups_x, 101 | self._num_groups_y, 102 | 1, 103 | ) 104 | self._flip_buffer() 105 | 106 | def _set_size(self): 107 | self._particle_size = (self._width, self._height) 108 | self._velocity_size = ( 109 | self.fluid_simulation.width, 110 | self.fluid_simulation.height, 111 | ) 112 | group_size_x = TemplateConstants.NUM_THREADS.value 113 | group_size_y = TemplateConstants.NUM_THREADS.value 114 | 115 | self._num_cells = self._width * self._height 116 | self._num_groups_x = int(ceil(float(self._width) / float(group_size_x))) 117 | self._num_groups_y = int(ceil(float(self._height) / float(group_size_y))) 118 | 119 | def _create_uniforms(self): 120 | self.particle_size_uniform = bgfx.createUniform( 121 | "_ParticleSize", bgfx.UniformType.Vec4 122 | ) 123 | self.position_uniform = bgfx.createUniform("_Position", bgfx.UniformType.Vec4) 124 | self.value_uniform = bgfx.createUniform("_Value", bgfx.UniformType.Vec4) 125 | self.radius_uniform = bgfx.createUniform("_Radius", bgfx.UniformType.Vec4) 126 | self.dissipation_uniform = bgfx.createUniform( 127 | "_Dissipation", bgfx.UniformType.Vec4 128 | ) 129 | self.elapsed_time_uniform = bgfx.createUniform( 130 | "_ElapsedTime", bgfx.UniformType.Vec4 131 | ) 132 | self.speed_uniform = bgfx.createUniform("_Speed", bgfx.UniformType.Vec4) 133 | self.velocity_size_uniform = bgfx.createUniform( 134 | "_VelocitySize", bgfx.UniformType.Vec4 135 | ) 136 | 137 | def _init_compute_kernels(self): 138 | bgfx.setUniform( 139 | self.particle_size_uniform, 140 | as_void_ptr((c_float * 2)(self._particle_size[0], self._particle_size[1])), 141 | ) 142 | bgfx.setUniform( 143 | self.velocity_size_uniform, 144 | as_void_ptr((c_float * 2)(self._velocity_size[0], self._velocity_size[1])), 145 | ) 146 | 147 | bgfx.setBuffer( 148 | TemplateConstants.PARTICLES_IN.value, 149 | self._particles_buffer[self.PARTICLES_IN], 150 | bgfx.Access.Write, 151 | ) 152 | bgfx.setBuffer( 153 | TemplateConstants.PARTICLES_OUT.value, 154 | self._particles_buffer[self.PARTICLES_OUT], 155 | bgfx.Access.Write, 156 | ) 157 | 158 | def _create_buffers(self): 159 | self._particles_buffer = [ 160 | create_buffer(self._num_cells, 1, self.vertex_layout), 161 | create_buffer(self._num_cells, 1, self.vertex_layout), 162 | ] 163 | 164 | def _load_compute_kernels(self): 165 | self._add_particles_kernel = bgfx.createProgram( 166 | load_shader( 167 | "shader.AddParticle.comp", ShaderType.COMPUTE, root_path=root_path 168 | ), 169 | True, 170 | ) 171 | self._advect_particles_kernel = bgfx.createProgram( 172 | load_shader( 173 | "shader.AdvectParticle.comp", ShaderType.COMPUTE, root_path=root_path 174 | ), 175 | True, 176 | ) 177 | 178 | def _flip_buffer(self): 179 | tmp = self.PARTICLES_IN 180 | self.PARTICLES_IN = self.PARTICLES_OUT 181 | self.PARTICLES_OUT = tmp 182 | 183 | bgfx.setBuffer( 184 | TemplateConstants.PARTICLES_IN.value, 185 | self._particles_buffer[self.PARTICLES_IN], 186 | bgfx.Access.Read, 187 | ) 188 | bgfx.setBuffer( 189 | TemplateConstants.PARTICLES_OUT.value, 190 | self._particles_buffer[self.PARTICLES_OUT], 191 | bgfx.Access.Write, 192 | ) 193 | 194 | def destroy(self): 195 | # Destroy uniforms 196 | bgfx.destroy(self.particle_size_uniform) 197 | bgfx.destroy(self.position_uniform) 198 | bgfx.destroy(self.value_uniform) 199 | bgfx.destroy(self.radius_uniform) 200 | bgfx.destroy(self.dissipation_uniform) 201 | bgfx.destroy(self.elapsed_time_uniform) 202 | bgfx.destroy(self.speed_uniform) 203 | bgfx.destroy(self.velocity_size_uniform) 204 | 205 | # Destroy buffers 206 | bgfx.destroy(self._particles_buffer[0]) 207 | bgfx.destroy(self._particles_buffer[1]) 208 | 209 | # Destroy compute shaders 210 | bgfx.destroy(self._add_particles_kernel) 211 | bgfx.destroy(self._advect_particles_kernel) 212 | -------------------------------------------------------------------------------- /demo/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbertola/Natrix/403fed55cab582563615c71ef11b40b11cdb7777/demo/utils/__init__.py -------------------------------------------------------------------------------- /demo/utils/imgui_utils.py: -------------------------------------------------------------------------------- 1 | from array import array 2 | 3 | # noinspection PyUnresolvedReferences 4 | from pybgfx import bgfx, ImGui 5 | 6 | 7 | class SampleData: 8 | m_values = [] 9 | m_offset = 0 10 | m_min = 0.0 11 | m_max = 0.0 12 | m_avg = 0.0 13 | 14 | def __init__(self): 15 | self.reset() 16 | 17 | def reset(self): 18 | self.m_values = [0.0] * 100 19 | self.m_offset = 0 20 | self.m_min = 0.0 21 | self.m_max = 0.0 22 | self.m_avg = 0.0 23 | 24 | def push_sample(self, value: float): 25 | self.m_values[self.m_offset] = value 26 | self.m_offset = (self.m_offset + 1) % 100 27 | 28 | min_val = float("inf") 29 | max_val = float("-inf") 30 | avg_val = 0.0 31 | 32 | for val in self.m_values: 33 | min_val = min(min_val, val) 34 | max_val = max(max_val, val) 35 | avg_val += val 36 | 37 | self.m_min = min_val 38 | self.m_max = max_val 39 | self.m_avg = avg_val / 100.0 40 | 41 | 42 | def bar(width, max_width, height, color): 43 | style = ImGui.GetStyle() 44 | 45 | hovered_color = ImGui.ImVec4( 46 | color.x + color.x * 0.1, 47 | color.y + color.y * 0.1, 48 | color.z + color.z * 0.1, 49 | color.w + color.w * 0.1, 50 | ) 51 | 52 | ImGui.PushStyleColor(ImGui.ImGuiCol_Button, color) 53 | ImGui.PushStyleColor(ImGui.ImGuiCol_ButtonHovered, hovered_color) 54 | ImGui.PushStyleColor(ImGui.ImGuiCol_ButtonActive, color) 55 | ImGui.PushStyleVar(ImGui.ImGuiStyleVar_FrameRounding, 0.0) 56 | ImGui.PushStyleVar( 57 | ImGui.ImGuiStyleVar_ItemSpacing, ImGui.ImVec2(0.0, style.ItemSpacing.y) 58 | ) 59 | 60 | item_hovered = False 61 | 62 | ImGui.Button("", ImGui.ImVec2(width, height)) 63 | item_hovered |= ImGui.IsItemHovered(0) 64 | 65 | ImGui.SameLine() 66 | ImGui.InvisibleButton("", ImGui.ImVec2(max(1.0, max_width - width), height), 0) 67 | item_hovered |= ImGui.IsItemHovered(0) 68 | 69 | ImGui.PopStyleVar(2) 70 | ImGui.PopStyleColor(3) 71 | 72 | return item_hovered 73 | 74 | 75 | s_resourceColor = ImGui.ImVec4(0.5, 0.5, 0.5, 1.0) 76 | s_frame_time = SampleData() 77 | 78 | 79 | def resource_bar(name, tooltip, num, _max, max_width, height): 80 | item_hovered = False 81 | 82 | ImGui.Text(f"{name}: {num:4d} / {_max:4d}") 83 | item_hovered |= ImGui.IsItemHovered(0) 84 | ImGui.SameLine() 85 | 86 | percentage = float(num) / float(_max) 87 | 88 | item_hovered |= bar( 89 | max(1.0, percentage * max_width), max_width, height, s_resourceColor 90 | ) 91 | ImGui.SameLine() 92 | 93 | ImGui.Text(f"{(percentage * 100.0):5.2f}%") 94 | 95 | if item_hovered: 96 | ImGui.SetTooltip(f"{tooltip} {(percentage * 100.0):5.2f}%") 97 | 98 | 99 | def show_properties_dialog(fb_width, fb_height, hidpi: bool): 100 | res_multiplier = 2 if hidpi else 1 101 | 102 | ImGui.SetNextWindowPos(ImGui.ImVec2( 103 | 20.0 * res_multiplier, fb_height - fb_height / 1.7 - 20.0 * res_multiplier 104 | ), ImGui.ImGuiCond_FirstUseEver) 105 | ImGui.SetNextWindowSize(ImGui.ImVec2( 106 | fb_width / 4.3, fb_height / 1.7 107 | ), ImGui.ImGuiCond_FirstUseEver) 108 | 109 | ImGui.Begin("\uf080 Statistics") 110 | renderer_name = bgfx.getRendererName(bgfx.getRendererType()) 111 | ImGui.TextWrapped("Current renderer: {}".format(renderer_name)) 112 | ImGui.Separator() 113 | 114 | stats = bgfx.getStats() 115 | to_ms_cpu = 1000.0 / stats.cpuTimerFreq 116 | to_ms_gpu = 1000.0 / stats.gpuTimerFreq 117 | frame_ms = float(stats.cpuTimeFrame) * to_ms_cpu 118 | 119 | s_frame_time.push_sample(frame_ms) 120 | fps = 1000.0 / s_frame_time.m_avg 121 | 122 | frame_text_overlay = f"\uf063{s_frame_time.m_min:7.3f}ms, \uf062{s_frame_time.m_max:7.3f}ms\nAvg: {s_frame_time.m_avg:7.3f}ms, {fps:6.2f} FPS" 123 | ImGui.PushStyleColor( 124 | ImGui.ImGuiCol_PlotHistogram, ImGui.ImVec4(0.0, 0.5, 0.15, 1.0) 125 | ) 126 | ImGui.PushItemWidth(-1) 127 | ImGui.PlotHistogram( 128 | "", 129 | array("f", s_frame_time.m_values), 130 | 100, 131 | s_frame_time.m_offset, 132 | frame_text_overlay, 133 | 0.0, 134 | 60.0, 135 | ImGui.ImVec2(0.0, 45.0 * res_multiplier), 136 | ) 137 | ImGui.PopItemWidth() 138 | ImGui.PopStyleColor() 139 | 140 | ImGui.Text( 141 | f"Submit CPU {(stats.cpuTimeEnd - stats.cpuTimeBegin) * to_ms_cpu:.3f}, GPU {(stats.gpuTimeEnd - stats.gpuTimeBegin) * to_ms_gpu:.3f} (L: {stats.maxGpuLatency})" 142 | ) 143 | 144 | if stats.gpuMemoryMax > 0: 145 | ImGui.Text(f"GPU mem: {stats.gpuMemoryUsed} / {stats.gpuMemoryMax}") 146 | 147 | if ImGui.CollapsingHeader("\uf12e Resources", ImGui.ImGuiTreeNodeFlags_DefaultOpen): 148 | caps = bgfx.getCaps() 149 | item_height = ImGui.GetTextLineHeightWithSpacing() 150 | max_width = 90.0 151 | ImGui.PushFont(ImGui.Font.Mono) 152 | ImGui.Text("Res: Num / Max") 153 | resource_bar( 154 | "DIB", 155 | "Dynamic index buffers", 156 | stats.numDynamicIndexBuffers, 157 | caps.limits.maxDynamicIndexBuffers, 158 | max_width, 159 | item_height, 160 | ) 161 | resource_bar( 162 | "DVB", 163 | "Dynamic vertex buffers", 164 | stats.numDynamicVertexBuffers, 165 | caps.limits.maxDynamicVertexBuffers, 166 | max_width, 167 | item_height, 168 | ) 169 | resource_bar( 170 | " FB", 171 | "Frame buffers", 172 | stats.numFrameBuffers, 173 | caps.limits.maxFrameBuffers, 174 | max_width, 175 | item_height, 176 | ) 177 | resource_bar( 178 | " IB", 179 | "Index buffers", 180 | stats.numIndexBuffers, 181 | caps.limits.maxIndexBuffers, 182 | max_width, 183 | item_height, 184 | ) 185 | resource_bar( 186 | " OQ", 187 | "Occlusion queries", 188 | stats.numOcclusionQueries, 189 | caps.limits.maxOcclusionQueries, 190 | max_width, 191 | item_height, 192 | ) 193 | resource_bar( 194 | " P", 195 | "Programs", 196 | stats.numPrograms, 197 | caps.limits.maxPrograms, 198 | max_width, 199 | item_height, 200 | ) 201 | resource_bar( 202 | " S", 203 | "Shaders", 204 | stats.numShaders, 205 | caps.limits.maxShaders, 206 | max_width, 207 | item_height, 208 | ) 209 | resource_bar( 210 | " T", 211 | "Textures", 212 | stats.numTextures, 213 | caps.limits.maxTextures, 214 | max_width, 215 | item_height, 216 | ) 217 | resource_bar( 218 | " U", 219 | "Uniforms", 220 | stats.numUniforms, 221 | caps.limits.maxUniforms, 222 | max_width, 223 | item_height, 224 | ) 225 | resource_bar( 226 | " VB", 227 | "Vertex buffers", 228 | stats.numVertexBuffers, 229 | caps.limits.maxVertexBuffers, 230 | max_width, 231 | item_height, 232 | ) 233 | resource_bar( 234 | " VL", 235 | "Vertex layouts", 236 | stats.numVertexLayouts, 237 | caps.limits.maxVertexLayouts, 238 | max_width, 239 | item_height, 240 | ) 241 | ImGui.PopFont() 242 | 243 | ImGui.End() 244 | -------------------------------------------------------------------------------- /demo/utils/matrix_utils.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import math 3 | 4 | import numpy as np 5 | 6 | 7 | def sub(v1, v2): 8 | return [v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2]] 9 | 10 | 11 | def cross(v1, v2): 12 | v1x = v1[0] 13 | v1y = v1[1] 14 | v1z = v1[2] 15 | v2x = v2[0] 16 | v2y = v2[1] 17 | v2z = v2[2] 18 | return [ 19 | (v1y * v2z) - (v1z * v2y), 20 | (v1z * v2x) - (v1x * v2z), 21 | (v1x * v2y) - (v1y * v2x), 22 | ] 23 | 24 | 25 | def length(vec3): 26 | return math.sqrt(vec3[0] * vec3[0] + vec3[1] * vec3[1] + vec3[2] * vec3[2]) 27 | 28 | 29 | def normalize(vec3): 30 | l = length(vec3) 31 | return [vec3[0] / l, vec3[1] / l, vec3[2] / l] 32 | 33 | 34 | def look_at(eye, at, up): 35 | """Constructs look-at matrix for right handed coordinate system.""" 36 | view = normalize(sub(at, eye)) 37 | right = normalize(np.cross(up, view)) 38 | 39 | up = np.cross(view, right) 40 | 41 | view = (ctypes.c_float * 16)( 42 | *[ 43 | right[0], 44 | up[0], 45 | view[0], 46 | 0.0, 47 | right[1], 48 | up[1], 49 | view[1], 50 | 0.0, 51 | right[2], 52 | up[2], 53 | view[2], 54 | 0.0, 55 | -np.dot(right, eye), 56 | -np.dot(up, eye), 57 | -np.dot(view, eye), 58 | 1.0, 59 | ] 60 | ) 61 | 62 | return view 63 | 64 | 65 | def proj(fov_y, aspect, near, far): 66 | height = 1.0 / math.tan(math.radians(fov_y) * 0.5) 67 | width = height * 1.0 / aspect 68 | diff = far - near 69 | aa = far / diff 70 | bb = near * aa 71 | 72 | return (ctypes.c_float * 16)( 73 | *[ 74 | width, 75 | 0.0, 76 | 0.0, 77 | 0.0, 78 | 0.0, 79 | height, 80 | 0.0, 81 | 0.0, 82 | 0.0, 83 | 0.0, 84 | aa, 85 | 1.0, 86 | 0.0, 87 | 0.0, 88 | -bb, 89 | 0.0, 90 | ] 91 | ) 92 | 93 | 94 | def ortho(left, right, bottom, top, near, far): 95 | aa = 2.0 / (right - left) 96 | bb = 2.0 / (top - bottom) 97 | cc = 1.0 / (far - near) 98 | dd = (left + right) / (left - right) 99 | ee = (top + bottom) / (bottom - top) 100 | ff = near / (near - far) 101 | 102 | return (ctypes.c_float * 16)( 103 | *[aa, 0.0, 0.0, 0.0, 0.0, bb, 0.0, 0.0, 0.0, 0.0, cc, 0.0, dd, ee, ff, 1.0] 104 | ) 105 | 106 | 107 | def rotate_xy(rot_x, rot_y): 108 | sx, cx = np.sin(rot_x), np.cos(rot_x) 109 | sy, cy = np.sin(rot_y), np.cos(rot_y) 110 | 111 | return np.array( 112 | ( 113 | (cy, 0.0, sy, 0.0), 114 | (sx * sy, cx, -sx * cy, 0.0), 115 | (-cx * sy, sx, cx * cy, 0.0), 116 | (0.0, 0.0, 0.0, 1.0), 117 | ), 118 | dtype=np.float32, 119 | ) 120 | -------------------------------------------------------------------------------- /media/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbertola/Natrix/403fed55cab582563615c71ef11b40b11cdb7777/media/screenshot.png -------------------------------------------------------------------------------- /natrix/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import * 2 | -------------------------------------------------------------------------------- /natrix/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbertola/Natrix/403fed55cab582563615c71ef11b40b11cdb7777/natrix/core/__init__.py -------------------------------------------------------------------------------- /natrix/core/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbertola/Natrix/403fed55cab582563615c71ef11b40b11cdb7777/natrix/core/common/__init__.py -------------------------------------------------------------------------------- /natrix/core/common/constants.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | 3 | from decouple import config 4 | 5 | 6 | class TemplateConstants(IntEnum): 7 | NUM_THREADS = config("NATRIX__NUM_THREADS", cast=int, default=16) 8 | VELOCITY_IN = 1 9 | VELOCITY_OUT = 2 10 | PRESSURE_IN = 3 11 | PRESSURE_OUT = 4 12 | VORTICITY = 5 13 | DIVERGENCE = 6 14 | OBSTACLES = 7 15 | GENERIC = 8 16 | PARTICLES_IN = 9 17 | PARTICLES_OUT = 10 18 | -------------------------------------------------------------------------------- /natrix/core/fluid_simulator.py: -------------------------------------------------------------------------------- 1 | from ctypes import c_float 2 | from math import ceil 3 | from pathlib import Path 4 | 5 | from pybgfx import bgfx 6 | from pybgfx.utils import as_void_ptr 7 | from pybgfx.utils.shaders_utils import ShaderType, load_shader 8 | 9 | from natrix.core.common.constants import TemplateConstants 10 | from natrix.core.utils.shaders_utils import create_buffer 11 | 12 | root_path = Path(__file__).parent / "shaders" / "originals" 13 | 14 | 15 | class FluidSimulator: 16 | VELOCITY_READ = 0 17 | VELOCITY_WRITE = 1 18 | 19 | PRESSURE_READ = 0 20 | PRESSURE_WRITE = 1 21 | 22 | _num_cells = 0 23 | _num_groups_x = 0 24 | _num_groups_y = 0 25 | _width = 512 26 | _height = 512 27 | 28 | _speed = 500.0 29 | _iterations = 50 30 | _dissipation = 1.0 31 | _vorticity = 0.0 32 | _viscosity = 0.1 33 | 34 | has_borders = True 35 | simulate = True 36 | 37 | def __init__(self, width: int, height: int, vertex_layout: bgfx.VertexLayout): 38 | self._width = width 39 | self._height = height 40 | 41 | self.vertex_layout = vertex_layout 42 | 43 | self._create_uniforms() 44 | 45 | self._load_compute_kernels() 46 | self._set_size(width, height) 47 | self._create_buffers() 48 | self._init_compute_kernels() 49 | 50 | @property 51 | def width(self): 52 | return self._width 53 | 54 | @property 55 | def height(self): 56 | return self._height 57 | 58 | @property 59 | def speed(self): 60 | return self._speed 61 | 62 | @speed.setter 63 | def speed(self, value): 64 | if value > 0: 65 | self._speed = value 66 | else: 67 | raise ValueError("'Speed' should be greater than zero") 68 | 69 | @property 70 | def iterations(self): 71 | return self._iterations 72 | 73 | @iterations.setter 74 | def iterations(self, value): 75 | if value > 0: 76 | self._iterations = value 77 | else: 78 | raise ValueError("'Iterations' should be grater than zero") 79 | 80 | @property 81 | def dissipation(self): 82 | return self._dissipation 83 | 84 | @dissipation.setter 85 | def dissipation(self, value): 86 | if value > 0: 87 | self._dissipation = value 88 | else: 89 | raise ValueError("'Dissipation' should be grater than zero") 90 | 91 | @property 92 | def vorticity(self): 93 | return self._vorticity 94 | 95 | @vorticity.setter 96 | def vorticity(self, value): 97 | if value >= 0: 98 | self._vorticity = value 99 | else: 100 | raise ValueError("'Vorticity' should be grater or equal than zero") 101 | 102 | @property 103 | def viscosity(self): 104 | return self._viscosity 105 | 106 | @viscosity.setter 107 | def viscosity(self, value): 108 | if value >= 0.0: 109 | self._viscosity = value 110 | else: 111 | raise ValueError("'Viscosity' should be greater or equal than zero") 112 | 113 | def get_velocity_buffer(self): 114 | return self._velocity_buffer[self.VELOCITY_READ] 115 | 116 | def add_velocity(self, position: tuple, velocity: tuple, radius: float): 117 | if self.simulate: 118 | self._init_compute_kernels() 119 | bgfx.setUniform( 120 | self.position_uniform, 121 | as_void_ptr((c_float * 2)(position[0], position[1])), 122 | ) 123 | bgfx.setUniform( 124 | self.value_uniform, as_void_ptr((c_float * 2)(velocity[0], velocity[1])) 125 | ) 126 | bgfx.setUniform(self.radius_uniform, as_void_ptr((c_float * 1)(radius))) 127 | 128 | bgfx.dispatch( 129 | 0, self._add_velocity_kernel, self._num_groups_x, self._num_groups_y, 1 130 | ) 131 | self._flip_velocity_buffer() 132 | 133 | # position in normalised local space 134 | # radius in world space 135 | def add_circle_obstacle(self, position: tuple, radius: float, static=False): 136 | if self.simulate: 137 | self._init_compute_kernels() 138 | bgfx.setUniform( 139 | self.position_uniform, 140 | as_void_ptr((c_float * 2)(position[0], position[1])), 141 | ) 142 | bgfx.setUniform(self.radius_uniform, as_void_ptr((c_float * 1)(radius))) 143 | bgfx.setUniform( 144 | self.static_uniform, as_void_ptr((c_float * 1)(1.0 if static else 0.0)) 145 | ) 146 | 147 | bgfx.dispatch( 148 | 0, 149 | self._add_circle_obstacle_kernel, 150 | self._num_groups_x, 151 | self._num_groups_y, 152 | 1, 153 | ) 154 | 155 | # points in normalised local space 156 | def add_triangle_obstacle(self, p1: tuple, p2: tuple, p3: tuple, static=False): 157 | if self.simulate: 158 | self._init_compute_kernels() 159 | bgfx.setUniform(self.p1_uniform, as_void_ptr((c_float * 2)(p1[0], p1[1]))) 160 | bgfx.setUniform(self.p2_uniform, as_void_ptr((c_float * 2)(p2[0], p2[1]))) 161 | bgfx.setUniform(self.p3_uniform, as_void_ptr((c_float * 2)(p3[0], p3[1]))) 162 | bgfx.setUniform( 163 | self.static_uniform, as_void_ptr((c_float * 1)(1.0 if static else 0.0)) 164 | ) 165 | 166 | bgfx.dispatch( 167 | 0, 168 | self._add_triangle_obstacle_kernel, 169 | self._num_groups_x, 170 | self._num_groups_y, 171 | 1, 172 | ) 173 | 174 | def update(self, time_delta: float): 175 | if self.simulate: 176 | 177 | self._init_compute_kernels() 178 | self._update_params(time_delta) 179 | 180 | # Init boundaries 181 | if self.has_borders: 182 | bgfx.dispatch( 183 | 0, 184 | self._init_boundaries_kernel, 185 | self._num_groups_x, 186 | self._num_groups_y, 187 | 1, 188 | ) 189 | 190 | # Advect 191 | bgfx.dispatch( 192 | 0, 193 | self._advect_velocity_kernel, 194 | self._num_groups_x, 195 | self._num_groups_y, 196 | 1, 197 | ) 198 | self._flip_velocity_buffer() 199 | 200 | # Vorticity confinement 1 - Calculate vorticity 201 | bgfx.dispatch( 202 | 0, 203 | self._calc_vorticity_kernel, 204 | self._num_groups_x, 205 | self._num_groups_y, 206 | 1, 207 | ) 208 | 209 | # Vorticity confinement 2 - Apply vorticity force 210 | bgfx.dispatch( 211 | 0, 212 | self._apply_vorticity_kernel, 213 | self._num_groups_x, 214 | self._num_groups_y, 215 | 1, 216 | ) 217 | self._flip_velocity_buffer() 218 | 219 | # Viscosity 220 | if self.viscosity > 0.0: 221 | bgfx.dispatch( 222 | 0, 223 | self._viscosity_kernel, 224 | self._num_groups_x, 225 | self._num_groups_y, 226 | 1, 227 | ) 228 | self._flip_velocity_buffer() 229 | 230 | # Divergence 231 | bgfx.dispatch( 232 | 0, self._divergence_kernel, self._num_groups_x, self._num_groups_y, 1 233 | ) 234 | 235 | # Clear pressure 236 | bgfx.setBuffer( 237 | TemplateConstants.GENERIC.value, 238 | self._pressure_buffer[self.PRESSURE_READ], 239 | bgfx.Access.ReadWrite, 240 | ) 241 | bgfx.dispatch( 242 | 0, self._clear_buffer_kernel, self._num_groups_x, self._num_groups_y, 1 243 | ) 244 | bgfx.setBuffer( 245 | TemplateConstants.PRESSURE_IN.value, 246 | self._pressure_buffer[self.PRESSURE_READ], 247 | bgfx.Access.Read, 248 | ) 249 | 250 | # Poisson 251 | for _ in range(self.iterations): 252 | bgfx.dispatch( 253 | 0, self._poisson_kernel, self._num_groups_x, self._num_groups_y, 1 254 | ) 255 | self._flip_pressure_buffer() 256 | 257 | # Subtract gradient 258 | bgfx.dispatch( 259 | 0, 260 | self._subtract_gradient_kernel, 261 | self._num_groups_x, 262 | self._num_groups_y, 263 | 1, 264 | ) 265 | self._flip_velocity_buffer() 266 | 267 | # Clear obstacles 268 | bgfx.setBuffer( 269 | TemplateConstants.GENERIC.value, 270 | self._obstacles_buffer, 271 | bgfx.Access.ReadWrite, 272 | ) 273 | bgfx.dispatch( 274 | 0, self._clear_buffer_kernel, self._num_groups_x, self._num_groups_y, 1 275 | ) 276 | bgfx.setBuffer( 277 | TemplateConstants.OBSTACLES.value, 278 | self._obstacles_buffer, 279 | bgfx.Access.ReadWrite, 280 | ) 281 | 282 | def _set_size(self, width: int, height: int): 283 | group_size_x = TemplateConstants.NUM_THREADS.value 284 | group_size_y = TemplateConstants.NUM_THREADS.value 285 | 286 | self._width = width 287 | self._height = height 288 | self._num_cells = width * height 289 | self._num_groups_x = int(ceil(float(width) / float(group_size_x))) 290 | self._num_groups_y = int(ceil(float(height) / float(group_size_y))) 291 | 292 | def _create_uniforms(self): 293 | self.size_uniform = bgfx.createUniform("_Size", bgfx.UniformType.Vec4) 294 | self.position_uniform = bgfx.createUniform("_Position", bgfx.UniformType.Vec4) 295 | self.radius_uniform = bgfx.createUniform("_Radius", bgfx.UniformType.Vec4) 296 | self.value_uniform = bgfx.createUniform("_Value", bgfx.UniformType.Vec4) 297 | self.static_uniform = bgfx.createUniform("_Static", bgfx.UniformType.Vec4) 298 | self.p1_uniform = bgfx.createUniform("_P1", bgfx.UniformType.Vec4) 299 | self.p2_uniform = bgfx.createUniform("_P2", bgfx.UniformType.Vec4) 300 | self.p3_uniform = bgfx.createUniform("_P3", bgfx.UniformType.Vec4) 301 | self.elapsed_time_uniform = bgfx.createUniform( 302 | "_ElapsedTime", bgfx.UniformType.Vec4 303 | ) 304 | self.speed_uniform = bgfx.createUniform("_Speed", bgfx.UniformType.Vec4) 305 | self.dissipation_uniform = bgfx.createUniform( 306 | "_Dissipation", bgfx.UniformType.Vec4 307 | ) 308 | self.velocity_uniform = bgfx.createUniform("_Velocity", bgfx.UniformType.Vec4) 309 | self.vorticity_scale_uniform = bgfx.createUniform( 310 | "_VorticityScale", bgfx.UniformType.Vec4 311 | ) 312 | self.alpha_uniform = bgfx.createUniform("_Alpha", bgfx.UniformType.Vec4) 313 | self.rbeta_uniform = bgfx.createUniform("_rBeta", bgfx.UniformType.Vec4) 314 | 315 | def _update_params(self, time_delta: float): 316 | bgfx.setUniform( 317 | self.elapsed_time_uniform, as_void_ptr((c_float * 1)(time_delta)) 318 | ) 319 | bgfx.setUniform(self.speed_uniform, as_void_ptr((c_float * 1)(self.speed))) 320 | bgfx.setUniform( 321 | self.dissipation_uniform, as_void_ptr((c_float * 1)(self.dissipation)) 322 | ) 323 | bgfx.setUniform( 324 | self.vorticity_scale_uniform, as_void_ptr((c_float * 1)(self.vorticity)) 325 | ) 326 | 327 | if self._viscosity > 0.0: 328 | centre_factor = 1.0 / self.viscosity 329 | stencil_factor = 1.0 / (4.0 + centre_factor) 330 | 331 | bgfx.setUniform( 332 | self.alpha_uniform, as_void_ptr((c_float * 1)(centre_factor)) 333 | ) 334 | bgfx.setUniform( 335 | self.rbeta_uniform, as_void_ptr((c_float * 1)(stencil_factor)) 336 | ) 337 | 338 | def _init_compute_kernels(self): 339 | bgfx.setUniform( 340 | self.size_uniform, as_void_ptr((c_float * 2)(self._width, self._height)) 341 | ) 342 | 343 | bgfx.setBuffer(1, self._velocity_buffer[self.VELOCITY_READ], bgfx.Access.Read) 344 | bgfx.setBuffer( 345 | 2, self._velocity_buffer[self.VELOCITY_WRITE], bgfx.Access.Write 346 | ) 347 | 348 | bgfx.setBuffer(3, self._pressure_buffer[self.PRESSURE_READ], bgfx.Access.Read) 349 | bgfx.setBuffer( 350 | 4, self._pressure_buffer[self.PRESSURE_WRITE], bgfx.Access.Write 351 | ) 352 | 353 | bgfx.setBuffer(5, self._divergence_buffer, bgfx.Access.ReadWrite) 354 | bgfx.setBuffer(6, self._vorticity_buffer, bgfx.Access.ReadWrite) 355 | bgfx.setBuffer(7, self._obstacles_buffer, bgfx.Access.ReadWrite) 356 | 357 | def _create_buffers(self): 358 | self._velocity_buffer = [ 359 | create_buffer(self._num_cells, 2, self.vertex_layout), 360 | create_buffer(self._num_cells, 2, self.vertex_layout), 361 | ] 362 | self._pressure_buffer = [ 363 | create_buffer(self._num_cells, 1, self.vertex_layout), 364 | create_buffer(self._num_cells, 1, self.vertex_layout), 365 | ] 366 | self._divergence_buffer = create_buffer(self._num_cells, 1, self.vertex_layout) 367 | self._vorticity_buffer = create_buffer(self._num_cells, 1, self.vertex_layout) 368 | self._obstacles_buffer = create_buffer(self._num_cells, 2, self.vertex_layout) 369 | 370 | def _load_compute_kernels(self): 371 | self._add_velocity_kernel = bgfx.createProgram( 372 | load_shader( 373 | "shader.AddVelocity.comp", ShaderType.COMPUTE, root_path=root_path 374 | ), 375 | True, 376 | ) 377 | self._init_boundaries_kernel = bgfx.createProgram( 378 | load_shader( 379 | "shader.InitBoundaries.comp", ShaderType.COMPUTE, root_path=root_path 380 | ), 381 | True, 382 | ) 383 | self._advect_velocity_kernel = bgfx.createProgram( 384 | load_shader( 385 | "shader.AdvectVelocity.comp", ShaderType.COMPUTE, root_path=root_path 386 | ), 387 | True, 388 | ) 389 | self._divergence_kernel = bgfx.createProgram( 390 | load_shader( 391 | "shader.Divergence.comp", ShaderType.COMPUTE, root_path=root_path 392 | ), 393 | True, 394 | ) 395 | self._poisson_kernel = bgfx.createProgram( 396 | load_shader("shader.Poisson.comp", ShaderType.COMPUTE, root_path=root_path), 397 | True, 398 | ) 399 | self._subtract_gradient_kernel = bgfx.createProgram( 400 | load_shader( 401 | "shader.SubtractGradient.comp", ShaderType.COMPUTE, root_path=root_path 402 | ), 403 | True, 404 | ) 405 | self._calc_vorticity_kernel = bgfx.createProgram( 406 | load_shader( 407 | "shader.CalcVorticity.comp", ShaderType.COMPUTE, root_path=root_path 408 | ), 409 | True, 410 | ) 411 | self._apply_vorticity_kernel = bgfx.createProgram( 412 | load_shader( 413 | "shader.ApplyVorticity.comp", ShaderType.COMPUTE, root_path=root_path 414 | ), 415 | True, 416 | ) 417 | self._add_circle_obstacle_kernel = bgfx.createProgram( 418 | load_shader( 419 | "shader.AddCircleObstacle.comp", ShaderType.COMPUTE, root_path=root_path 420 | ), 421 | True, 422 | ) 423 | self._add_triangle_obstacle_kernel = bgfx.createProgram( 424 | load_shader( 425 | "shader.AddTriangleObstacle.comp", 426 | ShaderType.COMPUTE, 427 | root_path=root_path, 428 | ), 429 | True, 430 | ) 431 | self._clear_buffer_kernel = bgfx.createProgram( 432 | load_shader( 433 | "shader.ClearBuffer.comp", ShaderType.COMPUTE, root_path=root_path 434 | ), 435 | True, 436 | ) 437 | self._viscosity_kernel = bgfx.createProgram( 438 | load_shader( 439 | "shader.Viscosity.comp", ShaderType.COMPUTE, root_path=root_path 440 | ), 441 | True, 442 | ) 443 | 444 | def _flip_velocity_buffer(self): 445 | tmp = self.VELOCITY_READ 446 | self.VELOCITY_READ = self.VELOCITY_WRITE 447 | self.VELOCITY_WRITE = tmp 448 | 449 | bgfx.setBuffer( 450 | TemplateConstants.VELOCITY_IN.value, 451 | self._velocity_buffer[self.VELOCITY_READ], 452 | bgfx.Access.Read, 453 | ) 454 | bgfx.setBuffer( 455 | TemplateConstants.VELOCITY_OUT.value, 456 | self._velocity_buffer[self.VELOCITY_WRITE], 457 | bgfx.Access.Write, 458 | ) 459 | 460 | def _flip_pressure_buffer(self): 461 | tmp = self.PRESSURE_READ 462 | self.PRESSURE_READ = self.PRESSURE_WRITE 463 | self.PRESSURE_WRITE = tmp 464 | 465 | bgfx.setBuffer( 466 | TemplateConstants.PRESSURE_IN.value, 467 | self._pressure_buffer[self.PRESSURE_READ], 468 | bgfx.Access.Read, 469 | ) 470 | bgfx.setBuffer( 471 | TemplateConstants.PRESSURE_OUT.value, 472 | self._pressure_buffer[self.PRESSURE_WRITE], 473 | bgfx.Access.Write, 474 | ) 475 | 476 | def destroy(self): 477 | # Destroy uniforms 478 | bgfx.destroy(self.size_uniform) 479 | bgfx.destroy(self.position_uniform) 480 | bgfx.destroy(self.radius_uniform) 481 | bgfx.destroy(self.value_uniform) 482 | bgfx.destroy(self.static_uniform) 483 | bgfx.destroy(self.velocity_uniform) 484 | bgfx.destroy(self.p1_uniform) 485 | bgfx.destroy(self.p2_uniform) 486 | bgfx.destroy(self.p3_uniform) 487 | bgfx.destroy(self.elapsed_time_uniform) 488 | bgfx.destroy(self.speed_uniform) 489 | bgfx.destroy(self.dissipation_uniform) 490 | bgfx.destroy(self.vorticity_scale_uniform) 491 | bgfx.destroy(self.alpha_uniform) 492 | bgfx.destroy(self.rbeta_uniform) 493 | 494 | # Destroy buffers 495 | bgfx.destroy(self._velocity_buffer[0]) 496 | bgfx.destroy(self._velocity_buffer[1]) 497 | bgfx.destroy(self._pressure_buffer[0]) 498 | bgfx.destroy(self._pressure_buffer[1]) 499 | bgfx.destroy(self._divergence_buffer) 500 | bgfx.destroy(self._vorticity_buffer) 501 | bgfx.destroy(self._obstacles_buffer) 502 | 503 | # Destroy compute shaders 504 | bgfx.destroy(self._add_velocity_kernel) 505 | bgfx.destroy(self._init_boundaries_kernel) 506 | bgfx.destroy(self._advect_velocity_kernel) 507 | bgfx.destroy(self._divergence_kernel) 508 | bgfx.destroy(self._poisson_kernel) 509 | bgfx.destroy(self._subtract_gradient_kernel) 510 | bgfx.destroy(self._calc_vorticity_kernel) 511 | bgfx.destroy(self._apply_vorticity_kernel) 512 | bgfx.destroy(self._add_circle_obstacle_kernel) 513 | bgfx.destroy(self._add_triangle_obstacle_kernel) 514 | bgfx.destroy(self._clear_buffer_kernel) 515 | bgfx.destroy(self._viscosity_kernel) 516 | -------------------------------------------------------------------------------- /natrix/core/shaders/originals/common.sh: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_SH_HEADER_GUARD 2 | #define COMMON_SH_HEADER_GUARD 3 | 4 | #if BGFX_SHADER_LANGUAGE_SPIRV 5 | #undef BGFX_SHADER_LANGUAGE_HLSL 6 | #define BGFX_SHADER_LANGUAGE_HLSL 5 7 | #endif 8 | 9 | uvec4 GetNeighbours(ivec2 pos, ivec2 size) 10 | { 11 | uvec4 result; 12 | int maxX = size.x - 1; 13 | int maxY = size.y - 1; 14 | result.x = uint(pos.y) * _Size.x + uint(clamp(pos.x - 1, 0, maxX)); 15 | result.y = uint(pos.y) * _Size.x + uint(clamp(pos.x + 1, 0, maxX)); 16 | result.z = uint(clamp(pos.y - 1, 0, maxY) * size.x + pos.x); 17 | result.w = uint(clamp(pos.y + 1, 0, maxY) * size.x + pos.x); 18 | return result; 19 | } 20 | 21 | #endif // COMMON_SH_HEADER_GUARD -------------------------------------------------------------------------------- /natrix/core/shaders/originals/constants.sh: -------------------------------------------------------------------------------- 1 | #ifndef CONSTANTS_SH_HEADER_GUARD 2 | #define CONSTANTS_SH_HEADER_GUARD 3 | 4 | // workgroup size of the culling compute shader 5 | // D3D compute shaders only allow up to 1024 threads per workgroup 6 | // GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS also only guarantees 1024 7 | #define GROUP_SIZE 16 8 | 9 | #define VELOCITY_IN 1 10 | #define VELOCITY_OUT 2 11 | #define PRESSURE_IN 3 12 | #define PRESSURE_OUT 4 13 | #define VORTICITY 5 14 | #define DIVERGENCE 6 15 | #define OBSTACLES 7 16 | #define GENERIC 8 17 | #define PARTICLES_IN 9 18 | #define PARTICLES_OUT 10 19 | 20 | #endif // CONSTANTS_SH_HEADER_GUARD -------------------------------------------------------------------------------- /natrix/core/shaders/originals/shader.AddCircleObstacle.comp: -------------------------------------------------------------------------------- 1 | 2 | #include "bgfx_compute.sh" 3 | #include "constants.sh" 4 | 5 | uniform vec2 _Size; 6 | 7 | BUFFER_WR(_Obstacles, vec2, 7); 8 | 9 | uniform float _Radius; 10 | 11 | uniform vec2 _Position; 12 | 13 | uniform float _Static; 14 | 15 | #include "common.sh" 16 | 17 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1) 18 | void main() 19 | { 20 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y) 21 | { 22 | return; 23 | } 24 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x; 25 | vec2 splat_pos = _Position * vec2(_Size); 26 | if (distance(splat_pos, vec2(gl_GlobalInvocationID)) <= _Radius) 27 | { 28 | if (_Static > 0) 29 | { 30 | _Obstacles[pos] = vec2(1.0f, 0); 31 | } 32 | else 33 | { 34 | _Obstacles[pos] = vec2(1.0f, 0); 35 | } 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /natrix/core/shaders/originals/shader.AddTriangleObstacle.comp: -------------------------------------------------------------------------------- 1 | 2 | #include "bgfx_compute.sh" 3 | #include "constants.sh" 4 | 5 | uniform vec2 _Size; 6 | 7 | BUFFER_WR(_Obstacles, vec2, 7); 8 | 9 | uniform float _Static; 10 | 11 | uniform vec2 _P1; 12 | 13 | uniform vec2 _P2; 14 | 15 | uniform vec2 _P3; 16 | 17 | #include "common.sh" 18 | 19 | float Sign(vec2 p1, vec2 p2, vec2 p3) 20 | { 21 | return ((p1.x - p3.x) * (p2.y - p3.y)) - ((p2.x - p3.x) * (p1.y - p3.y)); 22 | } 23 | 24 | bool IsPointInTriangle(vec2 pt, vec2 v1, vec2 v2, vec2 v3) 25 | { 26 | bool b1 = Sign(pt, v1, v2) < 0.0f; 27 | bool b2 = Sign(pt, v2, v3) < 0.0f; 28 | bool b3 = Sign(pt, v3, v1) < 0.0f; 29 | return (b1 == b2) && (b2 == b3); 30 | } 31 | 32 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1) 33 | void main() 34 | { 35 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y) 36 | { 37 | return; 38 | } 39 | vec2 pt = vec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y) / vec2(_Size); 40 | if (IsPointInTriangle(pt, _P1, _P2, _P3)) 41 | { 42 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x; 43 | if (_Static > 0) 44 | { 45 | _Obstacles[pos] = vec2(0, 1.0f); 46 | } 47 | else 48 | { 49 | _Obstacles[pos] = vec2(1.0f, 0); 50 | } 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /natrix/core/shaders/originals/shader.AddVelocity.comp: -------------------------------------------------------------------------------- 1 | 2 | #include "bgfx_compute.sh" 3 | #include "constants.sh" 4 | 5 | uniform vec2 _Size; 6 | 7 | BUFFER_RO(_VelocityIn, vec2, 1); 8 | 9 | BUFFER_WR(_VelocityOut, vec2, 2); 10 | 11 | uniform float _Radius; 12 | 13 | uniform vec2 _Position; 14 | 15 | uniform vec2 _Value; 16 | 17 | #include "common.sh" 18 | 19 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1) 20 | void main() 21 | { 22 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y) 23 | { 24 | return; 25 | } 26 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x; 27 | vec2 splat_pos = _Position * vec2(_Size); 28 | vec2 val = _VelocityIn[pos]; 29 | vec2 result = val; 30 | float len = distance(splat_pos, vec2(gl_GlobalInvocationID.xy)); 31 | if (len <= _Radius) 32 | { 33 | result = val + _Value * (_Radius - len) / _Radius; 34 | } 35 | _VelocityOut[pos] = clamp(result, vec2(-1.0, -1.0), vec2(1.0, 1.0)); 36 | } 37 | 38 | -------------------------------------------------------------------------------- /natrix/core/shaders/originals/shader.AdvectVelocity.comp: -------------------------------------------------------------------------------- 1 | 2 | #include "bgfx_compute.sh" 3 | #include "constants.sh" 4 | 5 | uniform vec2 _Size; 6 | 7 | BUFFER_RO(_VelocityIn, vec2, 1); 8 | 9 | BUFFER_WR(_VelocityOut, vec2, 2); 10 | 11 | BUFFER_RO(_Obstacles, vec2, 7); 12 | 13 | uniform float _ElapsedTime; 14 | 15 | uniform float _Speed; 16 | 17 | uniform float _Dissipation; 18 | 19 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1) 20 | void main() 21 | { 22 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y) 23 | { 24 | return; 25 | } 26 | 27 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x; 28 | vec2 obstacle = _Obstacles[pos]; 29 | 30 | if (obstacle.x > 0.0f || obstacle.y > 0.0f) 31 | { 32 | _VelocityOut[pos] = vec2(0, 0); 33 | } 34 | else 35 | { 36 | vec2 vel = _VelocityIn[pos]; 37 | vec2 final_pos = vec2(float(gl_GlobalInvocationID.x) - vel.x * _ElapsedTime * _Speed, float(gl_GlobalInvocationID.y) - vel.y * _ElapsedTime * _Speed); 38 | ivec2 zero = ivec2(0, 0); 39 | ivec2 size_bounds = ivec2(_Size.x - 1u, _Size.y - 1u); 40 | ivec2 top_right = ivec2(clamp(ceil(final_pos), vec2(zero), vec2(size_bounds))); 41 | ivec2 bottom_left = ivec2(clamp(floor(final_pos), vec2(zero), vec2(size_bounds))); 42 | vec2 delta = final_pos - vec2(bottom_left); 43 | vec2 lt = _VelocityIn[uint(top_right.y) * _Size.x + uint(bottom_left.x)]; 44 | vec2 rt = _VelocityIn[uint(top_right.y) * _Size.x + uint(top_right.x)]; 45 | vec2 lb = _VelocityIn[uint(bottom_left.y) * _Size.x + uint(bottom_left.x)]; 46 | vec2 rb = _VelocityIn[uint(bottom_left.y) * _Size.x + uint(top_right.x)]; 47 | vec2 h1 = mix(lt, rt, vec2(delta.x)); 48 | vec2 h2 = mix(lb, rb, vec2(delta.x)); 49 | _VelocityOut[pos] = clamp(mix(h2, h1, vec2(delta.y)) * _Dissipation, vec2(-1.0, -1.0), vec2(1.0, 1.0)); 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /natrix/core/shaders/originals/shader.ApplyVorticity.comp: -------------------------------------------------------------------------------- 1 | 2 | #include "bgfx_compute.sh" 3 | #include "constants.sh" 4 | 5 | uniform vec2 _Size; 6 | 7 | BUFFER_RO(_VelocityIn, vec2, 1); 8 | 9 | BUFFER_WR(_VelocityOut, vec2, 2); 10 | 11 | BUFFER_RO(_Vorticity, float, 5); 12 | 13 | uniform float _ElapsedTime; 14 | 15 | uniform float _VorticityScale; 16 | 17 | #include "common.sh" 18 | 19 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1) 20 | void main() 21 | { 22 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y) 23 | { 24 | return; 25 | } 26 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x; 27 | uvec4 n = GetNeighbours(ivec2(gl_GlobalInvocationID.xy), ivec2(_Size)); 28 | float vL = _Vorticity[n.x]; 29 | float vR = _Vorticity[n.y]; 30 | float vB = _Vorticity[n.z]; 31 | float vT = _Vorticity[n.w]; 32 | float vC = _Vorticity[pos]; 33 | vec2 force = 0.5f * vec2(abs(vT) - abs(vB), abs(vR) - abs(vL)); 34 | float EPSILON = 2.4414e-4f; 35 | float magSqr = max(EPSILON, dot(force, force)); 36 | force = force * inversesqrt(magSqr); 37 | force *= _VorticityScale * vC * vec2(1, -1); 38 | vec2 final_force = force * _ElapsedTime; 39 | _VelocityOut[pos] = _VelocityIn[pos] + vec2(final_force.x, final_force.y); 40 | } 41 | 42 | -------------------------------------------------------------------------------- /natrix/core/shaders/originals/shader.CalcVorticity.comp: -------------------------------------------------------------------------------- 1 | 2 | #include "bgfx_compute.sh" 3 | #include "constants.sh" 4 | 5 | uniform vec2 _Size; 6 | 7 | BUFFER_RO(_VelocityIn, vec2, 1); 8 | 9 | BUFFER_WR(_Vorticity, float, 5); 10 | 11 | #include "common.sh" 12 | 13 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1) 14 | void main() 15 | { 16 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y) 17 | { 18 | return; 19 | } 20 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x; 21 | uvec4 n = GetNeighbours(ivec2(gl_GlobalInvocationID.xy), ivec2(_Size)); 22 | vec2 vL = _VelocityIn[n.x]; 23 | vec2 vR = _VelocityIn[n.y]; 24 | vec2 vB = _VelocityIn[n.z]; 25 | vec2 vT = _VelocityIn[n.w]; 26 | _Vorticity[pos] = 0.5f * ((vR.y - vL.y) - (vT.x - vB.x)); 27 | } 28 | 29 | -------------------------------------------------------------------------------- /natrix/core/shaders/originals/shader.ClearBuffer.comp: -------------------------------------------------------------------------------- 1 | 2 | #include "bgfx_compute.sh" 3 | #include "constants.sh" 4 | 5 | uniform vec2 _Size; 6 | 7 | BUFFER_WR(_Buffer, vec2, 8); 8 | 9 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1) 10 | void main() 11 | { 12 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y) 13 | { 14 | return; 15 | } 16 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x; 17 | _Buffer[pos] = 0.0f; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /natrix/core/shaders/originals/shader.Divergence.comp: -------------------------------------------------------------------------------- 1 | 2 | #include "bgfx_compute.sh" 3 | #include "constants.sh" 4 | 5 | uniform vec2 _Size; 6 | 7 | BUFFER_RO(_VelocityIn, vec2, 1); 8 | 9 | BUFFER_RO(_Obstacles, vec2, 7); 10 | 11 | BUFFER_WR(_Divergence, float, 6); 12 | 13 | #include "common.sh" 14 | 15 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1) 16 | void main() 17 | { 18 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y) 19 | { 20 | return; 21 | } 22 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x; 23 | uvec4 n = GetNeighbours(ivec2(gl_GlobalInvocationID.xy), ivec2(_Size)); 24 | float x1 = _VelocityIn[n.x].x; 25 | float x2 = _VelocityIn[n.y].x; 26 | float y1 = _VelocityIn[n.z].y; 27 | float y2 = _VelocityIn[n.w].y; 28 | vec2 obsL = _Obstacles[n.x]; 29 | vec2 obsR = _Obstacles[n.y]; 30 | vec2 obsB = _Obstacles[n.z]; 31 | vec2 obsT = _Obstacles[n.w]; 32 | if (obsL.x > 0.0f || obsL.y > 0.0f) 33 | x1 = 0.0f; 34 | if (obsR.x > 0.0f || obsR.y > 0.0f) 35 | x2 = 0.0f; 36 | if (obsB.x > 0.0f || obsB.y > 0.0f) 37 | y1 = 0.0f; 38 | if (obsT.x > 0.0f || obsT.y > 0.0f) 39 | y2 = 0.0f; 40 | _Divergence[pos] = 0.5f * ((x2 - x1) + (y2 - y1)); 41 | } 42 | 43 | -------------------------------------------------------------------------------- /natrix/core/shaders/originals/shader.InitBoundaries.comp: -------------------------------------------------------------------------------- 1 | 2 | #include "bgfx_compute.sh" 3 | #include "constants.sh" 4 | 5 | uniform vec2 _Size; 6 | 7 | BUFFER_WR(_VelocityIn, vec2, 1); 8 | 9 | #include "common.sh" 10 | 11 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1) 12 | void main() 13 | { 14 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y) 15 | { 16 | return; 17 | } 18 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x; 19 | if (gl_GlobalInvocationID.x == 0u) 20 | { 21 | _VelocityIn[pos] = vec2(0.0, 0.0); 22 | } 23 | else if (gl_GlobalInvocationID.x == _Size.x - 1u) 24 | { 25 | _VelocityIn[pos] = vec2(0.0, 0.0); 26 | } 27 | else if (gl_GlobalInvocationID.y == 0u) 28 | { 29 | _VelocityIn[pos] = vec2(0.0, 0.0); 30 | } 31 | else if (gl_GlobalInvocationID.y == _Size.y - 1u) 32 | { 33 | _VelocityIn[pos] = vec2(0.0, 0.0); 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /natrix/core/shaders/originals/shader.Poisson.comp: -------------------------------------------------------------------------------- 1 | 2 | #include "bgfx_compute.sh" 3 | #include "constants.sh" 4 | 5 | uniform vec2 _Size; 6 | 7 | BUFFER_RO(_Obstacles, vec2, 7); 8 | 9 | BUFFER_RO(_Divergence, float, 6); 10 | 11 | BUFFER_RO(_PressureIn, vec2, 3); 12 | 13 | BUFFER_WR(_PressureOut, vec2, 4); 14 | 15 | #include "common.sh" 16 | 17 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1) 18 | void main() 19 | { 20 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y) 21 | { 22 | return; 23 | } 24 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x; 25 | float rbeta = 0.25f; 26 | uvec4 n = GetNeighbours(ivec2(gl_GlobalInvocationID.xy), ivec2(_Size)); 27 | float p = _PressureIn[pos]; 28 | vec2 obsL = _Obstacles[n.x]; 29 | vec2 obsR = _Obstacles[n.y]; 30 | vec2 obsB = _Obstacles[n.z]; 31 | vec2 obsT = _Obstacles[n.w]; 32 | float x1 = (obsL.x > 0.0f || obsL.y > 0.0f) ? p : _PressureIn[n.x]; 33 | float x2 = (obsR.x > 0.0f || obsR.y > 0.0f) ? p : _PressureIn[n.y]; 34 | float y1 = (obsB.x > 0.0f || obsB.y > 0.0f) ? p : _PressureIn[n.z]; 35 | float y2 = (obsT.x > 0.0f || obsT.y > 0.0f) ? p : _PressureIn[n.w]; 36 | float b = _Divergence[pos]; 37 | _PressureOut[pos] = (x1 + x2 + y1 + y2 - b) * rbeta; 38 | } 39 | 40 | -------------------------------------------------------------------------------- /natrix/core/shaders/originals/shader.SubtractGradient.comp: -------------------------------------------------------------------------------- 1 | 2 | #include "bgfx_compute.sh" 3 | #include "constants.sh" 4 | 5 | uniform vec2 _Size; 6 | 7 | BUFFER_RO(_VelocityIn, vec2, 1); 8 | 9 | BUFFER_WR(_VelocityOut, vec2, 2); 10 | 11 | BUFFER_RO(_Obstacles, vec2, 7); 12 | 13 | BUFFER_RO(_PressureIn, vec2, 3); 14 | 15 | #include "common.sh" 16 | 17 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1) 18 | void main() 19 | { 20 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y) 21 | { 22 | return; 23 | } 24 | uvec4 n = GetNeighbours(ivec2(gl_GlobalInvocationID.xy), ivec2(_Size)); 25 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x; 26 | float x1 = _PressureIn[n.x]; 27 | float x2 = _PressureIn[n.y]; 28 | float y1 = _PressureIn[n.z]; 29 | float y2 = _PressureIn[n.w]; 30 | float p = _PressureIn[pos]; 31 | vec2 obsL = _Obstacles[n.x]; 32 | vec2 obsR = _Obstacles[n.y]; 33 | vec2 obsB = _Obstacles[n.z]; 34 | vec2 obsT = _Obstacles[n.w]; 35 | if (obsL.x > 0.0f || obsL.y > 0.0f) 36 | x1 = p; 37 | if (obsR.x > 0.0f || obsR.y > 0.0f) 38 | x2 = p; 39 | if (obsB.x > 0.0f || obsB.y > 0.0f) 40 | y1 = p; 41 | if (obsT.x > 0.0f || obsT.y > 0.0f) 42 | y2 = p; 43 | vec2 velocity = _VelocityIn[pos]; 44 | velocity.x -= 0.5f * (x2 - x1); 45 | velocity.y -= 0.5f * (y2 - y1); 46 | _VelocityOut[pos] = velocity; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /natrix/core/shaders/originals/shader.Viscosity.comp: -------------------------------------------------------------------------------- 1 | 2 | #include "bgfx_compute.sh" 3 | #include "constants.sh" 4 | 5 | uniform vec2 _Size; 6 | 7 | BUFFER_RO(_VelocityIn, vec2, 1); 8 | 9 | BUFFER_WR(_VelocityOut, vec2, 2); 10 | 11 | uniform float _Alpha; 12 | 13 | uniform float _rBeta; 14 | 15 | #include "common.sh" 16 | 17 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1) 18 | void main() 19 | { 20 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y) 21 | { 22 | return; 23 | } 24 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x; 25 | uvec4 n = GetNeighbours(ivec2(gl_GlobalInvocationID.xy), ivec2(_Size)); 26 | vec2 x1 = _VelocityIn[n.x]; 27 | vec2 x2 = _VelocityIn[n.y]; 28 | vec2 y1 = _VelocityIn[n.z]; 29 | vec2 y2 = _VelocityIn[n.w]; 30 | vec2 b = _VelocityIn[pos]; 31 | _VelocityOut[pos] = (x1 + x2 + y1 + y2 + b * _Alpha) * _rBeta; 32 | } 33 | 34 | -------------------------------------------------------------------------------- /natrix/core/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbertola/Natrix/403fed55cab582563615c71ef11b40b11cdb7777/natrix/core/utils/__init__.py -------------------------------------------------------------------------------- /natrix/core/utils/bgfx_utils.py: -------------------------------------------------------------------------------- 1 | import platform 2 | from _ctypes import Structure 3 | from ctypes import c_float, c_uint32 4 | 5 | 6 | class PosVertex(Structure): 7 | _fields_ = [ 8 | ("m_x", c_float), 9 | ("m_y", c_float), 10 | ("m_z", c_float), 11 | ("m_abgr", c_uint32), 12 | ("m_u", c_float), 13 | ("m_v", c_float), 14 | ] 15 | 16 | 17 | def screen_space_quad( 18 | texture_width: int, 19 | texture_height: int, 20 | origin_bottom_left=False, 21 | width=1.0, 22 | height=1.0, 23 | ): 24 | if platform.system() == "Windows": 25 | s_texel_half = 0.5 26 | else: 27 | s_texel_half = 0.0 28 | 29 | zz = 0.0 30 | minx = -width 31 | maxx = width 32 | miny = 0.0 33 | maxy = height * 2.0 34 | texel_half_w = s_texel_half / texture_width 35 | texel_half_h = s_texel_half / texture_height 36 | minu = -1.0 + texel_half_w 37 | maxu = 1.0 + texel_half_w 38 | 39 | minv = texel_half_h 40 | maxv = 2.0 + texel_half_h 41 | 42 | if origin_bottom_left: 43 | temp = minv 44 | minv = maxv 45 | maxv = temp 46 | 47 | minv -= 1.0 48 | maxv -= 1.0 49 | 50 | vertices = (PosVertex * 3)( 51 | PosVertex(minx, miny, zz, 0xFFFFFFFF, minu, minv), 52 | PosVertex(maxx, miny, zz, 0xFFFFFFFF, maxu, minv), 53 | PosVertex(maxx, maxy, zz, 0xFFFFFFFF, maxu, maxv), 54 | ) 55 | 56 | return vertices 57 | -------------------------------------------------------------------------------- /natrix/core/utils/shaders_utils.py: -------------------------------------------------------------------------------- 1 | from ctypes import sizeof, c_float 2 | 3 | from pybgfx import bgfx 4 | from pybgfx.constants import BGFX_BUFFER_COMPUTE_READ_WRITE 5 | 6 | 7 | def create_buffer(length: int, dimensions: int, vertex_layout: bgfx.VertexLayout): 8 | return bgfx.createDynamicVertexBuffer( 9 | sizeof(c_float) * length * dimensions, 10 | vertex_layout, 11 | BGFX_BUFFER_COMPUTE_READ_WRITE, 12 | ) 13 | -------------------------------------------------------------------------------- /noxfile.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | 3 | import nox 4 | 5 | locations = "demo", "natrix", "tests", "noxfile.py" 6 | 7 | 8 | def install_with_constraints(session, *args, **kwargs): 9 | with tempfile.NamedTemporaryFile() as requirements: 10 | session.run( 11 | "poetry", 12 | "export", 13 | "--dev", 14 | "--format=requirements.txt", 15 | "--without-hashes", 16 | f"--output={requirements.name}", 17 | external=True, 18 | ) 19 | session.install(f"--constraint={requirements.name}", *args, **kwargs) 20 | 21 | 22 | @nox.session(python="3.7") 23 | def black(session): 24 | args = session.posargs or locations 25 | install_with_constraints(session, "black") 26 | session.run("black", *args) 27 | 28 | 29 | @nox.session(python="3.7") 30 | def lint(session): 31 | args = session.posargs or locations 32 | install_with_constraints( 33 | session, 34 | "flake8", 35 | "flake8-bandit", 36 | "flake8-black", 37 | "flake8-bugbear", 38 | "flake8-import-order", 39 | ) 40 | session.run("flake8", *args) 41 | 42 | 43 | @nox.session(python="3.7") 44 | def safety(session): 45 | with tempfile.NamedTemporaryFile() as requirements: 46 | session.run( 47 | "poetry", 48 | "export", 49 | "--dev", 50 | "--format=requirements.txt", 51 | "--without-hashes", 52 | f"--output={requirements.name}", 53 | external=True, 54 | ) 55 | install_with_constraints(session, "safety") 56 | session.run("safety", "check", f"--file={requirements.name}", "--full-report") 57 | 58 | 59 | @nox.session(python="3.7") 60 | def tests(session): 61 | args = session.posargs or ["--cov", "-m", "not e2e"] 62 | session.run("poetry", "install", "--no-dev", external=True) 63 | install_with_constraints(session, "pytest", "pytest-mock") 64 | session.run("pytest", *args) 65 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "appdirs" 3 | version = "1.4.4" 4 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 5 | category = "dev" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [[package]] 10 | name = "argcomplete" 11 | version = "1.12.3" 12 | description = "Bash tab completion for argparse" 13 | category = "dev" 14 | optional = false 15 | python-versions = "*" 16 | 17 | [package.dependencies] 18 | importlib-metadata = {version = ">=0.23,<5", markers = "python_version == \"3.7\""} 19 | 20 | [package.extras] 21 | test = ["coverage", "flake8", "pexpect", "wheel"] 22 | 23 | [[package]] 24 | name = "atomicwrites" 25 | version = "1.4.0" 26 | description = "Atomic file writes." 27 | category = "dev" 28 | optional = false 29 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 30 | 31 | [[package]] 32 | name = "attrs" 33 | version = "21.2.0" 34 | description = "Classes Without Boilerplate" 35 | category = "dev" 36 | optional = false 37 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 38 | 39 | [package.extras] 40 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] 41 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] 42 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] 43 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] 44 | 45 | [[package]] 46 | name = "backports.entry-points-selectable" 47 | version = "1.1.0" 48 | description = "Compatibility shim providing selectable entry points for older implementations" 49 | category = "dev" 50 | optional = false 51 | python-versions = ">=2.7" 52 | 53 | [package.dependencies] 54 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 55 | 56 | [package.extras] 57 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 58 | testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"] 59 | 60 | [[package]] 61 | name = "bandit" 62 | version = "1.7.0" 63 | description = "Security oriented static analyser for python code." 64 | category = "dev" 65 | optional = false 66 | python-versions = ">=3.5" 67 | 68 | [package.dependencies] 69 | colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} 70 | GitPython = ">=1.0.1" 71 | PyYAML = ">=5.3.1" 72 | six = ">=1.10.0" 73 | stevedore = ">=1.20.0" 74 | 75 | [[package]] 76 | name = "bgfx-python" 77 | version = "2.0.1" 78 | description = "Python wrapper for BGFX Library" 79 | category = "main" 80 | optional = false 81 | python-versions = ">=3.7.0" 82 | 83 | [package.dependencies] 84 | clang = "*" 85 | cppyy = "*" 86 | loguru = "*" 87 | 88 | [[package]] 89 | name = "black" 90 | version = "20.8b1" 91 | description = "The uncompromising code formatter." 92 | category = "dev" 93 | optional = false 94 | python-versions = ">=3.6" 95 | 96 | [package.dependencies] 97 | appdirs = "*" 98 | click = ">=7.1.2" 99 | mypy-extensions = ">=0.4.3" 100 | pathspec = ">=0.6,<1" 101 | regex = ">=2020.1.8" 102 | toml = ">=0.10.1" 103 | typed-ast = ">=1.4.0" 104 | typing-extensions = ">=3.7.4" 105 | 106 | [package.extras] 107 | colorama = ["colorama (>=0.4.3)"] 108 | d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] 109 | 110 | [[package]] 111 | name = "certifi" 112 | version = "2021.10.8" 113 | description = "Python package for providing Mozilla's CA Bundle." 114 | category = "dev" 115 | optional = false 116 | python-versions = "*" 117 | 118 | [[package]] 119 | name = "charset-normalizer" 120 | version = "2.0.6" 121 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 122 | category = "dev" 123 | optional = false 124 | python-versions = ">=3.5.0" 125 | 126 | [package.extras] 127 | unicode_backport = ["unicodedata2"] 128 | 129 | [[package]] 130 | name = "clang" 131 | version = "11.0" 132 | description = "libclang python bindings" 133 | category = "main" 134 | optional = false 135 | python-versions = "*" 136 | 137 | [[package]] 138 | name = "click" 139 | version = "8.0.3" 140 | description = "Composable command line interface toolkit" 141 | category = "dev" 142 | optional = false 143 | python-versions = ">=3.6" 144 | 145 | [package.dependencies] 146 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 147 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 148 | 149 | [[package]] 150 | name = "colorama" 151 | version = "0.4.4" 152 | description = "Cross-platform colored terminal text." 153 | category = "main" 154 | optional = false 155 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 156 | 157 | [[package]] 158 | name = "colorlog" 159 | version = "4.8.0" 160 | description = "Log formatting with colors!" 161 | category = "dev" 162 | optional = false 163 | python-versions = "*" 164 | 165 | [package.dependencies] 166 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 167 | 168 | [[package]] 169 | name = "cppyy" 170 | version = "2.1.0" 171 | description = "Cling-based Python-C++ bindings" 172 | category = "main" 173 | optional = false 174 | python-versions = "*" 175 | 176 | [package.dependencies] 177 | cppyy-backend = "1.14.6" 178 | cppyy-cling = "6.25.1" 179 | CPyCppyy = "1.12.7" 180 | 181 | [[package]] 182 | name = "cppyy-backend" 183 | version = "1.14.6" 184 | description = "C/C++ wrapper for Cling" 185 | category = "main" 186 | optional = false 187 | python-versions = "*" 188 | 189 | [package.dependencies] 190 | cppyy-cling = ">=6.25.1" 191 | 192 | [[package]] 193 | name = "cppyy-cling" 194 | version = "6.25.1" 195 | description = "Re-packaged Cling, as backend for cppyy" 196 | category = "main" 197 | optional = false 198 | python-versions = "*" 199 | 200 | [[package]] 201 | name = "cpycppyy" 202 | version = "1.12.7" 203 | description = "Cling-based Python-C++ bindings for CPython" 204 | category = "main" 205 | optional = false 206 | python-versions = "*" 207 | 208 | [package.dependencies] 209 | cppyy-backend = ">=1.14.6" 210 | cppyy-cling = ">=6.25.1" 211 | 212 | [[package]] 213 | name = "distlib" 214 | version = "0.3.3" 215 | description = "Distribution utilities" 216 | category = "dev" 217 | optional = false 218 | python-versions = "*" 219 | 220 | [[package]] 221 | name = "dparse" 222 | version = "0.5.1" 223 | description = "A parser for Python dependency files" 224 | category = "dev" 225 | optional = false 226 | python-versions = ">=3.5" 227 | 228 | [package.dependencies] 229 | packaging = "*" 230 | pyyaml = "*" 231 | toml = "*" 232 | 233 | [package.extras] 234 | pipenv = ["pipenv"] 235 | 236 | [[package]] 237 | name = "filelock" 238 | version = "3.3.0" 239 | description = "A platform independent file lock." 240 | category = "dev" 241 | optional = false 242 | python-versions = ">=3.6" 243 | 244 | [package.extras] 245 | docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] 246 | testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] 247 | 248 | [[package]] 249 | name = "flake8" 250 | version = "3.9.2" 251 | description = "the modular source code checker: pep8 pyflakes and co" 252 | category = "dev" 253 | optional = false 254 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 255 | 256 | [package.dependencies] 257 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 258 | mccabe = ">=0.6.0,<0.7.0" 259 | pycodestyle = ">=2.7.0,<2.8.0" 260 | pyflakes = ">=2.3.0,<2.4.0" 261 | 262 | [[package]] 263 | name = "flake8-bandit" 264 | version = "2.1.2" 265 | description = "Automated security testing with bandit and flake8." 266 | category = "dev" 267 | optional = false 268 | python-versions = "*" 269 | 270 | [package.dependencies] 271 | bandit = "*" 272 | flake8 = "*" 273 | flake8-polyfill = "*" 274 | pycodestyle = "*" 275 | 276 | [[package]] 277 | name = "flake8-black" 278 | version = "0.2.3" 279 | description = "flake8 plugin to call black as a code style validator" 280 | category = "dev" 281 | optional = false 282 | python-versions = "*" 283 | 284 | [package.dependencies] 285 | black = "*" 286 | flake8 = ">=3.0.0" 287 | toml = "*" 288 | 289 | [[package]] 290 | name = "flake8-bugbear" 291 | version = "20.11.1" 292 | description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." 293 | category = "dev" 294 | optional = false 295 | python-versions = ">=3.6" 296 | 297 | [package.dependencies] 298 | attrs = ">=19.2.0" 299 | flake8 = ">=3.0.0" 300 | 301 | [package.extras] 302 | dev = ["coverage", "black", "hypothesis", "hypothesmith"] 303 | 304 | [[package]] 305 | name = "flake8-import-order" 306 | version = "0.18.1" 307 | description = "Flake8 and pylama plugin that checks the ordering of import statements." 308 | category = "dev" 309 | optional = false 310 | python-versions = "*" 311 | 312 | [package.dependencies] 313 | pycodestyle = "*" 314 | 315 | [[package]] 316 | name = "flake8-polyfill" 317 | version = "1.0.2" 318 | description = "Polyfill package for Flake8 plugins" 319 | category = "dev" 320 | optional = false 321 | python-versions = "*" 322 | 323 | [package.dependencies] 324 | flake8 = "*" 325 | 326 | [[package]] 327 | name = "gitdb" 328 | version = "4.0.7" 329 | description = "Git Object Database" 330 | category = "dev" 331 | optional = false 332 | python-versions = ">=3.4" 333 | 334 | [package.dependencies] 335 | smmap = ">=3.0.1,<5" 336 | 337 | [[package]] 338 | name = "gitpython" 339 | version = "3.1.24" 340 | description = "GitPython is a python library used to interact with Git repositories" 341 | category = "dev" 342 | optional = false 343 | python-versions = ">=3.7" 344 | 345 | [package.dependencies] 346 | gitdb = ">=4.0.1,<5" 347 | typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.10\""} 348 | 349 | [[package]] 350 | name = "glfw" 351 | version = "1.12.0" 352 | description = "A ctypes-based wrapper for GLFW3." 353 | category = "main" 354 | optional = false 355 | python-versions = "*" 356 | 357 | [[package]] 358 | name = "idna" 359 | version = "3.2" 360 | description = "Internationalized Domain Names in Applications (IDNA)" 361 | category = "dev" 362 | optional = false 363 | python-versions = ">=3.5" 364 | 365 | [[package]] 366 | name = "importlib-metadata" 367 | version = "4.8.1" 368 | description = "Read metadata from Python packages" 369 | category = "dev" 370 | optional = false 371 | python-versions = ">=3.6" 372 | 373 | [package.dependencies] 374 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 375 | zipp = ">=0.5" 376 | 377 | [package.extras] 378 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 379 | perf = ["ipython"] 380 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] 381 | 382 | [[package]] 383 | name = "loguru" 384 | version = "0.5.3" 385 | description = "Python logging made (stupidly) simple" 386 | category = "main" 387 | optional = false 388 | python-versions = ">=3.5" 389 | 390 | [package.dependencies] 391 | colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} 392 | win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} 393 | 394 | [package.extras] 395 | dev = ["codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "tox (>=3.9.0)", "tox-travis (>=0.12)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "Sphinx (>=2.2.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "black (>=19.10b0)", "isort (>=5.1.1)"] 396 | 397 | [[package]] 398 | name = "mccabe" 399 | version = "0.6.1" 400 | description = "McCabe checker, plugin for flake8" 401 | category = "dev" 402 | optional = false 403 | python-versions = "*" 404 | 405 | [[package]] 406 | name = "more-itertools" 407 | version = "8.10.0" 408 | description = "More routines for operating on iterables, beyond itertools" 409 | category = "dev" 410 | optional = false 411 | python-versions = ">=3.5" 412 | 413 | [[package]] 414 | name = "mypy-extensions" 415 | version = "0.4.3" 416 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 417 | category = "dev" 418 | optional = false 419 | python-versions = "*" 420 | 421 | [[package]] 422 | name = "nox" 423 | version = "2020.12.31" 424 | description = "Flexible test automation." 425 | category = "dev" 426 | optional = false 427 | python-versions = ">=3.6" 428 | 429 | [package.dependencies] 430 | argcomplete = ">=1.9.4,<2.0" 431 | colorlog = ">=2.6.1,<5.0.0" 432 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 433 | py = ">=1.4.0,<2.0.0" 434 | virtualenv = ">=14.0.0" 435 | 436 | [package.extras] 437 | tox_to_nox = ["jinja2", "tox"] 438 | 439 | [[package]] 440 | name = "numpy" 441 | version = "1.21.1" 442 | description = "NumPy is the fundamental package for array computing with Python." 443 | category = "dev" 444 | optional = false 445 | python-versions = ">=3.7" 446 | 447 | [[package]] 448 | name = "packaging" 449 | version = "21.0" 450 | description = "Core utilities for Python packages" 451 | category = "dev" 452 | optional = false 453 | python-versions = ">=3.6" 454 | 455 | [package.dependencies] 456 | pyparsing = ">=2.0.2" 457 | 458 | [[package]] 459 | name = "pathspec" 460 | version = "0.9.0" 461 | description = "Utility library for gitignore style pattern matching of file paths." 462 | category = "dev" 463 | optional = false 464 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 465 | 466 | [[package]] 467 | name = "pbr" 468 | version = "5.6.0" 469 | description = "Python Build Reasonableness" 470 | category = "dev" 471 | optional = false 472 | python-versions = ">=2.6" 473 | 474 | [[package]] 475 | name = "platformdirs" 476 | version = "2.4.0" 477 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 478 | category = "dev" 479 | optional = false 480 | python-versions = ">=3.6" 481 | 482 | [package.extras] 483 | docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] 484 | test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] 485 | 486 | [[package]] 487 | name = "pluggy" 488 | version = "0.13.1" 489 | description = "plugin and hook calling mechanisms for python" 490 | category = "dev" 491 | optional = false 492 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 493 | 494 | [package.dependencies] 495 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 496 | 497 | [package.extras] 498 | dev = ["pre-commit", "tox"] 499 | 500 | [[package]] 501 | name = "py" 502 | version = "1.10.0" 503 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 504 | category = "dev" 505 | optional = false 506 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 507 | 508 | [[package]] 509 | name = "pycodestyle" 510 | version = "2.7.0" 511 | description = "Python style guide checker" 512 | category = "dev" 513 | optional = false 514 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 515 | 516 | [[package]] 517 | name = "pyflakes" 518 | version = "2.3.1" 519 | description = "passive checker of Python programs" 520 | category = "dev" 521 | optional = false 522 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 523 | 524 | [[package]] 525 | name = "pyparsing" 526 | version = "2.4.7" 527 | description = "Python parsing module" 528 | category = "dev" 529 | optional = false 530 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 531 | 532 | [[package]] 533 | name = "pytest" 534 | version = "5.4.3" 535 | description = "pytest: simple powerful testing with Python" 536 | category = "dev" 537 | optional = false 538 | python-versions = ">=3.5" 539 | 540 | [package.dependencies] 541 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 542 | attrs = ">=17.4.0" 543 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 544 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 545 | more-itertools = ">=4.0.0" 546 | packaging = "*" 547 | pluggy = ">=0.12,<1.0" 548 | py = ">=1.5.0" 549 | wcwidth = "*" 550 | 551 | [package.extras] 552 | checkqa-mypy = ["mypy (==v0.761)"] 553 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 554 | 555 | [[package]] 556 | name = "pytest-mock" 557 | version = "1.13.0" 558 | description = "Thin-wrapper around the mock package for easier use with py.test" 559 | category = "dev" 560 | optional = false 561 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 562 | 563 | [package.dependencies] 564 | pytest = ">=2.7" 565 | 566 | [package.extras] 567 | dev = ["pre-commit", "tox"] 568 | 569 | [[package]] 570 | name = "pytest-sugar" 571 | version = "0.9.4" 572 | description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)." 573 | category = "dev" 574 | optional = false 575 | python-versions = "*" 576 | 577 | [package.dependencies] 578 | packaging = ">=14.1" 579 | pytest = ">=2.9" 580 | termcolor = ">=1.1.0" 581 | 582 | [[package]] 583 | name = "python-decouple" 584 | version = "3.5" 585 | description = "Strict separation of settings from code." 586 | category = "main" 587 | optional = false 588 | python-versions = "*" 589 | 590 | [[package]] 591 | name = "pyyaml" 592 | version = "5.4.1" 593 | description = "YAML parser and emitter for Python" 594 | category = "dev" 595 | optional = false 596 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 597 | 598 | [[package]] 599 | name = "regex" 600 | version = "2021.10.8" 601 | description = "Alternative regular expression module, to replace re." 602 | category = "dev" 603 | optional = false 604 | python-versions = "*" 605 | 606 | [[package]] 607 | name = "requests" 608 | version = "2.26.0" 609 | description = "Python HTTP for Humans." 610 | category = "dev" 611 | optional = false 612 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 613 | 614 | [package.dependencies] 615 | certifi = ">=2017.4.17" 616 | charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} 617 | idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} 618 | urllib3 = ">=1.21.1,<1.27" 619 | 620 | [package.extras] 621 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] 622 | use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] 623 | 624 | [[package]] 625 | name = "safety" 626 | version = "1.10.3" 627 | description = "Checks installed dependencies for known vulnerabilities." 628 | category = "dev" 629 | optional = false 630 | python-versions = ">=3.5" 631 | 632 | [package.dependencies] 633 | Click = ">=6.0" 634 | dparse = ">=0.5.1" 635 | packaging = "*" 636 | requests = "*" 637 | 638 | [[package]] 639 | name = "six" 640 | version = "1.16.0" 641 | description = "Python 2 and 3 compatibility utilities" 642 | category = "dev" 643 | optional = false 644 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 645 | 646 | [[package]] 647 | name = "smmap" 648 | version = "4.0.0" 649 | description = "A pure Python implementation of a sliding window memory map manager" 650 | category = "dev" 651 | optional = false 652 | python-versions = ">=3.5" 653 | 654 | [[package]] 655 | name = "stevedore" 656 | version = "3.4.0" 657 | description = "Manage dynamic plugins for Python applications" 658 | category = "dev" 659 | optional = false 660 | python-versions = ">=3.6" 661 | 662 | [package.dependencies] 663 | importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} 664 | pbr = ">=2.0.0,<2.1.0 || >2.1.0" 665 | 666 | [[package]] 667 | name = "termcolor" 668 | version = "1.1.0" 669 | description = "ANSII Color formatting for output in terminal." 670 | category = "dev" 671 | optional = false 672 | python-versions = "*" 673 | 674 | [[package]] 675 | name = "toml" 676 | version = "0.10.2" 677 | description = "Python Library for Tom's Obvious, Minimal Language" 678 | category = "dev" 679 | optional = false 680 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 681 | 682 | [[package]] 683 | name = "typed-ast" 684 | version = "1.4.3" 685 | description = "a fork of Python 2 and 3 ast modules with type comment support" 686 | category = "dev" 687 | optional = false 688 | python-versions = "*" 689 | 690 | [[package]] 691 | name = "typing-extensions" 692 | version = "3.10.0.2" 693 | description = "Backported and Experimental Type Hints for Python 3.5+" 694 | category = "dev" 695 | optional = false 696 | python-versions = "*" 697 | 698 | [[package]] 699 | name = "urllib3" 700 | version = "1.26.7" 701 | description = "HTTP library with thread-safe connection pooling, file post, and more." 702 | category = "dev" 703 | optional = false 704 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 705 | 706 | [package.extras] 707 | brotli = ["brotlipy (>=0.6.0)"] 708 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] 709 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 710 | 711 | [[package]] 712 | name = "virtualenv" 713 | version = "20.8.1" 714 | description = "Virtual Python Environment builder" 715 | category = "dev" 716 | optional = false 717 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 718 | 719 | [package.dependencies] 720 | "backports.entry-points-selectable" = ">=1.0.4" 721 | distlib = ">=0.3.1,<1" 722 | filelock = ">=3.0.0,<4" 723 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 724 | platformdirs = ">=2,<3" 725 | six = ">=1.9.0,<2" 726 | 727 | [package.extras] 728 | docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] 729 | testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] 730 | 731 | [[package]] 732 | name = "wcwidth" 733 | version = "0.2.5" 734 | description = "Measures the displayed width of unicode strings in a terminal" 735 | category = "dev" 736 | optional = false 737 | python-versions = "*" 738 | 739 | [[package]] 740 | name = "win32-setctime" 741 | version = "1.0.3" 742 | description = "A small Python utility to set file creation time on Windows" 743 | category = "main" 744 | optional = false 745 | python-versions = ">=3.5" 746 | 747 | [package.extras] 748 | dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"] 749 | 750 | [[package]] 751 | name = "zipp" 752 | version = "3.6.0" 753 | description = "Backport of pathlib-compatible object wrapper for zip files" 754 | category = "dev" 755 | optional = false 756 | python-versions = ">=3.6" 757 | 758 | [package.extras] 759 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 760 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] 761 | 762 | [metadata] 763 | lock-version = "1.1" 764 | python-versions = "^3.7" 765 | content-hash = "dea7b78c5cb774d28b386950419497b41cd7cc94f97955b560dcaf61f7941f46" 766 | 767 | [metadata.files] 768 | appdirs = [ 769 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 770 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 771 | ] 772 | argcomplete = [ 773 | {file = "argcomplete-1.12.3-py2.py3-none-any.whl", hash = "sha256:291f0beca7fd49ce285d2f10e4c1c77e9460cf823eef2de54df0c0fec88b0d81"}, 774 | {file = "argcomplete-1.12.3.tar.gz", hash = "sha256:2c7dbffd8c045ea534921e63b0be6fe65e88599990d8dc408ac8c542b72a5445"}, 775 | ] 776 | atomicwrites = [ 777 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 778 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 779 | ] 780 | attrs = [ 781 | {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, 782 | {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, 783 | ] 784 | "backports.entry-points-selectable" = [ 785 | {file = "backports.entry_points_selectable-1.1.0-py2.py3-none-any.whl", hash = "sha256:a6d9a871cde5e15b4c4a53e3d43ba890cc6861ec1332c9c2428c92f977192acc"}, 786 | {file = "backports.entry_points_selectable-1.1.0.tar.gz", hash = "sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a"}, 787 | ] 788 | bandit = [ 789 | {file = "bandit-1.7.0-py3-none-any.whl", hash = "sha256:216be4d044209fa06cf2a3e51b319769a51be8318140659719aa7a115c35ed07"}, 790 | {file = "bandit-1.7.0.tar.gz", hash = "sha256:8a4c7415254d75df8ff3c3b15cfe9042ecee628a1e40b44c15a98890fbfc2608"}, 791 | ] 792 | bgfx-python = [ 793 | {file = "bgfx-python-2.0.1.tar.gz", hash = "sha256:cfc99ab01b60c3254f580a51a945345c6c311656051230de8183c9045539b62e"}, 794 | {file = "bgfx_python-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fd400488d7ceaa77c91b9f79b804c7ac97918aa119f5f34337751bb0ada43a51"}, 795 | {file = "bgfx_python-2.0.1-cp37-cp37m-manylinux_2_24_x86_64.whl", hash = "sha256:8433b37b3a3d85f6ad30bddde7a95ff8911cbd7fd50983bbfe927917b4bd447a"}, 796 | {file = "bgfx_python-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:cd8195d46e780f4678a73d92629bb1d59b233a9cba0e0a09b9988b66b2e07fce"}, 797 | {file = "bgfx_python-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f790b874f1b9d29842920fc99902a89d732d46719d0ef4799b8f7c3dd4ed4c0e"}, 798 | {file = "bgfx_python-2.0.1-cp38-cp38-manylinux_2_24_x86_64.whl", hash = "sha256:0bcfcfbc64fe4a47c10db953ce688b6037e0ec8f4e6c240449308858b207d570"}, 799 | {file = "bgfx_python-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:80d17fd07108b9f9cbb56b4c966689485ba0c138d5845975a514e1b9d9bbbd88"}, 800 | {file = "bgfx_python-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4cc51193502bb7c8ad23b57153336a34a73e9869f11ac7901ac82af9d8f60b1b"}, 801 | {file = "bgfx_python-2.0.1-cp39-cp39-manylinux_2_24_x86_64.whl", hash = "sha256:ea66bc03875bf17cb7ecd44280a756faa816df360b4fe0862e2a35f228e44ebd"}, 802 | {file = "bgfx_python-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:31b4be4b791d28bfd75ed3ee92ddfadfa5b74f631bbf5202d325cf2bd1575d24"}, 803 | ] 804 | black = [ 805 | {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, 806 | ] 807 | certifi = [ 808 | {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, 809 | {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, 810 | ] 811 | charset-normalizer = [ 812 | {file = "charset-normalizer-2.0.6.tar.gz", hash = "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"}, 813 | {file = "charset_normalizer-2.0.6-py3-none-any.whl", hash = "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6"}, 814 | ] 815 | clang = [ 816 | {file = "clang-11.0-py3-none-any.whl", hash = "sha256:de1b1e7defe3b499e82cc9851cdc968790575c510f62c2a000c724e1943ca8bb"}, 817 | {file = "clang-11.0.tar.gz", hash = "sha256:f838e6475b1fe5c91efb97e80ae19420c39483fd5aa7ef10f03ffb51edc6f8c5"}, 818 | ] 819 | click = [ 820 | {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, 821 | {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, 822 | ] 823 | colorama = [ 824 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 825 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 826 | ] 827 | colorlog = [ 828 | {file = "colorlog-4.8.0-py2.py3-none-any.whl", hash = "sha256:3dd15cb27e8119a24c1a7b5c93f9f3b455855e0f73993b1c25921b2f646f1dcd"}, 829 | {file = "colorlog-4.8.0.tar.gz", hash = "sha256:59b53160c60902c405cdec28d38356e09d40686659048893e026ecbd589516b1"}, 830 | ] 831 | cppyy = [ 832 | {file = "cppyy-2.1.0.tar.gz", hash = "sha256:e5feb67fe6af2314da8ced934b2966ec3dd55a4649462b21607a2f51de1d0b27"}, 833 | ] 834 | cppyy-backend = [ 835 | {file = "cppyy-backend-1.14.6.tar.gz", hash = "sha256:42598fc1f578ccd389a8c5a5410f57809726dce758185585c3a2ab1d72bd4ed3"}, 836 | ] 837 | cppyy-cling = [ 838 | {file = "cppyy-cling-6.25.1.tar.gz", hash = "sha256:bedb63a5b47a2bcb482f793fbd992f4b4a9318dd0dbd24e6dd57de07c30b66fd"}, 839 | {file = "cppyy_cling-6.25.1-py2.py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b3dd95a96989d2d552f78de92feaf2b7052be6d7e6b64d617ee377874cf3fbcf"}, 840 | {file = "cppyy_cling-6.25.1-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:4a88b88fd53b91d09000f545edcdc09dee37a63635b7166077c399d769361d2c"}, 841 | {file = "cppyy_cling-6.25.1-py2.py3-none-manylinux2014_x86_64.whl", hash = "sha256:9db039a3f68eb8a62eca0e419e7a8a145120147c41cb8b45844a51828387daa7"}, 842 | {file = "cppyy_cling-6.25.1-py2.py3-none-win32.whl", hash = "sha256:6aef281e0e897e52955091a69f06ef06a0429292a70ce6500c3b981b22ae2359"}, 843 | {file = "cppyy_cling-6.25.1-py2.py3-none-win_amd64.whl", hash = "sha256:da5fd43598bf643fbfed9bbc2f8b044058c30517e556eabf418159deb86c8884"}, 844 | ] 845 | cpycppyy = [ 846 | {file = "CPyCppyy-1.12.7.tar.gz", hash = "sha256:2c55a5c0d3ce13c6c120288d78fff09447dc4cb29b337f4a90fae6fb83fd9e3c"}, 847 | ] 848 | distlib = [ 849 | {file = "distlib-0.3.3-py2.py3-none-any.whl", hash = "sha256:c8b54e8454e5bf6237cc84c20e8264c3e991e824ef27e8f1e81049867d861e31"}, 850 | {file = "distlib-0.3.3.zip", hash = "sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05"}, 851 | ] 852 | dparse = [ 853 | {file = "dparse-0.5.1-py3-none-any.whl", hash = "sha256:e953a25e44ebb60a5c6efc2add4420c177f1d8404509da88da9729202f306994"}, 854 | {file = "dparse-0.5.1.tar.gz", hash = "sha256:a1b5f169102e1c894f9a7d5ccf6f9402a836a5d24be80a986c7ce9eaed78f367"}, 855 | ] 856 | filelock = [ 857 | {file = "filelock-3.3.0-py3-none-any.whl", hash = "sha256:bbc6a0382fe8ec4744ecdf6683a2e07f65eb10ff1aff53fc02a202565446cde0"}, 858 | {file = "filelock-3.3.0.tar.gz", hash = "sha256:8c7eab13dc442dc249e95158bcc12dec724465919bdc9831fdbf0660f03d1785"}, 859 | ] 860 | flake8 = [ 861 | {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, 862 | {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, 863 | ] 864 | flake8-bandit = [ 865 | {file = "flake8_bandit-2.1.2.tar.gz", hash = "sha256:687fc8da2e4a239b206af2e54a90093572a60d0954f3054e23690739b0b0de3b"}, 866 | ] 867 | flake8-black = [ 868 | {file = "flake8-black-0.2.3.tar.gz", hash = "sha256:c199844bc1b559d91195ebe8620216f21ed67f2cc1ff6884294c91a0d2492684"}, 869 | {file = "flake8_black-0.2.3-py3-none-any.whl", hash = "sha256:cc080ba5b3773b69ba102b6617a00cc4ecbad8914109690cfda4d565ea435d96"}, 870 | ] 871 | flake8-bugbear = [ 872 | {file = "flake8-bugbear-20.11.1.tar.gz", hash = "sha256:528020129fea2dea33a466b9d64ab650aa3e5f9ffc788b70ea4bc6cf18283538"}, 873 | {file = "flake8_bugbear-20.11.1-py36.py37.py38-none-any.whl", hash = "sha256:f35b8135ece7a014bc0aee5b5d485334ac30a6da48494998cc1fabf7ec70d703"}, 874 | ] 875 | flake8-import-order = [ 876 | {file = "flake8-import-order-0.18.1.tar.gz", hash = "sha256:a28dc39545ea4606c1ac3c24e9d05c849c6e5444a50fb7e9cdd430fc94de6e92"}, 877 | {file = "flake8_import_order-0.18.1-py2.py3-none-any.whl", hash = "sha256:90a80e46886259b9c396b578d75c749801a41ee969a235e163cfe1be7afd2543"}, 878 | ] 879 | flake8-polyfill = [ 880 | {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"}, 881 | {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"}, 882 | ] 883 | gitdb = [ 884 | {file = "gitdb-4.0.7-py3-none-any.whl", hash = "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0"}, 885 | {file = "gitdb-4.0.7.tar.gz", hash = "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"}, 886 | ] 887 | gitpython = [ 888 | {file = "GitPython-3.1.24-py3-none-any.whl", hash = "sha256:dc0a7f2f697657acc8d7f89033e8b1ea94dd90356b2983bca89dc8d2ab3cc647"}, 889 | {file = "GitPython-3.1.24.tar.gz", hash = "sha256:df83fdf5e684fef7c6ee2c02fc68a5ceb7e7e759d08b694088d0cacb4eba59e5"}, 890 | ] 891 | glfw = [ 892 | {file = "glfw-1.12.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38-none-macosx_10_6_intel.whl", hash = "sha256:88bd1cd2ace036d275e9af8312993068d94b3ac19248421eedc35a4baf53bb8c"}, 893 | {file = "glfw-1.12.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38-none-manylinux2010_i686.whl", hash = "sha256:bb63f6121c40f5f17cd78328c040b40aeaca9ed748f440c40d2fcad824107a74"}, 894 | {file = "glfw-1.12.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38-none-manylinux2010_x86_64.whl", hash = "sha256:6324fed2c4fd1762ae580277c534b5dab6f360b8fb9aed3e1547cf33f63d207c"}, 895 | {file = "glfw-1.12.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38-none-manylinux2014_x86_64.whl", hash = "sha256:20c918a1ae413a009f7559be9559bc2ec1d6251888f588ffa7a174d6db69a942"}, 896 | {file = "glfw-1.12.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38-none-win32.whl", hash = "sha256:fe9622a48ec9dc436e67de0d0bb4c9443996b5bd5564df1734d3a4280b728d38"}, 897 | {file = "glfw-1.12.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38-none-win_amd64.whl", hash = "sha256:3723db5e5628b2cd8729421fb6fc5fed3e5d25c7d1631f72f13301b218ee1600"}, 898 | {file = "glfw-1.12.0.tar.gz", hash = "sha256:f195ed7a43475e4f1603903d6999f3a6b470fda88bd1749ff10adc520abe8fb1"}, 899 | ] 900 | idna = [ 901 | {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, 902 | {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, 903 | ] 904 | importlib-metadata = [ 905 | {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, 906 | {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, 907 | ] 908 | loguru = [ 909 | {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"}, 910 | {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"}, 911 | ] 912 | mccabe = [ 913 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 914 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 915 | ] 916 | more-itertools = [ 917 | {file = "more-itertools-8.10.0.tar.gz", hash = "sha256:1debcabeb1df793814859d64a81ad7cb10504c24349368ccf214c664c474f41f"}, 918 | {file = "more_itertools-8.10.0-py3-none-any.whl", hash = "sha256:56ddac45541718ba332db05f464bebfb0768110111affd27f66e0051f276fa43"}, 919 | ] 920 | mypy-extensions = [ 921 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 922 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 923 | ] 924 | nox = [ 925 | {file = "nox-2020.12.31-py3-none-any.whl", hash = "sha256:f179d6990f7a0a9cebad01b9ecea34556518b8d3340dfcafdc1d85f2c1a37ea0"}, 926 | {file = "nox-2020.12.31.tar.gz", hash = "sha256:58a662070767ed4786beb46ce3a789fca6f1e689ed3ac15c73c4d0094e4f9dc4"}, 927 | ] 928 | numpy = [ 929 | {file = "numpy-1.21.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38e8648f9449a549a7dfe8d8755a5979b45b3538520d1e735637ef28e8c2dc50"}, 930 | {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fd7d7409fa643a91d0a05c7554dd68aa9c9bb16e186f6ccfe40d6e003156e33a"}, 931 | {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a75b4498b1e93d8b700282dc8e655b8bd559c0904b3910b144646dbbbc03e062"}, 932 | {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1412aa0aec3e00bc23fbb8664d76552b4efde98fb71f60737c83efbac24112f1"}, 933 | {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e46ceaff65609b5399163de5893d8f2a82d3c77d5e56d976c8b5fb01faa6b671"}, 934 | {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c6a2324085dd52f96498419ba95b5777e40b6bcbc20088fddb9e8cbb58885e8e"}, 935 | {file = "numpy-1.21.1-cp37-cp37m-win32.whl", hash = "sha256:73101b2a1fef16602696d133db402a7e7586654682244344b8329cdcbbb82172"}, 936 | {file = "numpy-1.21.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7a708a79c9a9d26904d1cca8d383bf869edf6f8e7650d85dbc77b041e8c5a0f8"}, 937 | {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95b995d0c413f5d0428b3f880e8fe1660ff9396dcd1f9eedbc311f37b5652e16"}, 938 | {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:635e6bd31c9fb3d475c8f44a089569070d10a9ef18ed13738b03049280281267"}, 939 | {file = "numpy-1.21.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a3d5fb89bfe21be2ef47c0614b9c9c707b7362386c9a3ff1feae63e0267ccb6"}, 940 | {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a326af80e86d0e9ce92bcc1e65c8ff88297de4fa14ee936cb2293d414c9ec63"}, 941 | {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:791492091744b0fe390a6ce85cc1bf5149968ac7d5f0477288f78c89b385d9af"}, 942 | {file = "numpy-1.21.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0318c465786c1f63ac05d7c4dbcecd4d2d7e13f0959b01b534ea1e92202235c5"}, 943 | {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a513bd9c1551894ee3d31369f9b07460ef223694098cf27d399513415855b68"}, 944 | {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:91c6f5fc58df1e0a3cc0c3a717bb3308ff850abdaa6d2d802573ee2b11f674a8"}, 945 | {file = "numpy-1.21.1-cp38-cp38-win32.whl", hash = "sha256:978010b68e17150db8765355d1ccdd450f9fc916824e8c4e35ee620590e234cd"}, 946 | {file = "numpy-1.21.1-cp38-cp38-win_amd64.whl", hash = "sha256:9749a40a5b22333467f02fe11edc98f022133ee1bfa8ab99bda5e5437b831214"}, 947 | {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d7a4aeac3b94af92a9373d6e77b37691b86411f9745190d2c351f410ab3a791f"}, 948 | {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9e7912a56108aba9b31df688a4c4f5cb0d9d3787386b87d504762b6754fbb1b"}, 949 | {file = "numpy-1.21.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:25b40b98ebdd272bc3020935427a4530b7d60dfbe1ab9381a39147834e985eac"}, 950 | {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a92c5aea763d14ba9d6475803fc7904bda7decc2a0a68153f587ad82941fec1"}, 951 | {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05a0f648eb28bae4bcb204e6fd14603de2908de982e761a2fc78efe0f19e96e1"}, 952 | {file = "numpy-1.21.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f01f28075a92eede918b965e86e8f0ba7b7797a95aa8d35e1cc8821f5fc3ad6a"}, 953 | {file = "numpy-1.21.1-cp39-cp39-win32.whl", hash = "sha256:88c0b89ad1cc24a5efbb99ff9ab5db0f9a86e9cc50240177a571fbe9c2860ac2"}, 954 | {file = "numpy-1.21.1-cp39-cp39-win_amd64.whl", hash = "sha256:01721eefe70544d548425a07c80be8377096a54118070b8a62476866d5208e33"}, 955 | {file = "numpy-1.21.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d4d1de6e6fb3d28781c73fbde702ac97f03d79e4ffd6598b880b2d95d62ead4"}, 956 | {file = "numpy-1.21.1.zip", hash = "sha256:dff4af63638afcc57a3dfb9e4b26d434a7a602d225b42d746ea7fe2edf1342fd"}, 957 | ] 958 | packaging = [ 959 | {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, 960 | {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, 961 | ] 962 | pathspec = [ 963 | {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, 964 | {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, 965 | ] 966 | pbr = [ 967 | {file = "pbr-5.6.0-py2.py3-none-any.whl", hash = "sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4"}, 968 | {file = "pbr-5.6.0.tar.gz", hash = "sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd"}, 969 | ] 970 | platformdirs = [ 971 | {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, 972 | {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, 973 | ] 974 | pluggy = [ 975 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 976 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 977 | ] 978 | py = [ 979 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, 980 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, 981 | ] 982 | pycodestyle = [ 983 | {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, 984 | {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, 985 | ] 986 | pyflakes = [ 987 | {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, 988 | {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, 989 | ] 990 | pyparsing = [ 991 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 992 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 993 | ] 994 | pytest = [ 995 | {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, 996 | {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, 997 | ] 998 | pytest-mock = [ 999 | {file = "pytest-mock-1.13.0.tar.gz", hash = "sha256:e24a911ec96773022ebcc7030059b57cd3480b56d4f5d19b7c370ec635e6aed5"}, 1000 | {file = "pytest_mock-1.13.0-py2.py3-none-any.whl", hash = "sha256:67e414b3caef7bff6fc6bd83b22b5bc39147e4493f483c2679bc9d4dc485a94d"}, 1001 | ] 1002 | pytest-sugar = [ 1003 | {file = "pytest-sugar-0.9.4.tar.gz", hash = "sha256:b1b2186b0a72aada6859bea2a5764145e3aaa2c1cfbb23c3a19b5f7b697563d3"}, 1004 | ] 1005 | python-decouple = [ 1006 | {file = "python-decouple-3.5.tar.gz", hash = "sha256:68e4b3fcc97e24bc90eecc514852d0bf970f4ff031f5f7a6728ddafa9afefcaf"}, 1007 | ] 1008 | pyyaml = [ 1009 | {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, 1010 | {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, 1011 | {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, 1012 | {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, 1013 | {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, 1014 | {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, 1015 | {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, 1016 | {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, 1017 | {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, 1018 | {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, 1019 | {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, 1020 | {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, 1021 | {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, 1022 | {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, 1023 | {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, 1024 | {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, 1025 | {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, 1026 | {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, 1027 | {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, 1028 | {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, 1029 | {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, 1030 | {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, 1031 | {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, 1032 | {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, 1033 | {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, 1034 | {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, 1035 | {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, 1036 | {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, 1037 | {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, 1038 | ] 1039 | regex = [ 1040 | {file = "regex-2021.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:981c786293a3115bc14c103086ae54e5ee50ca57f4c02ce7cf1b60318d1e8072"}, 1041 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51feefd58ac38eb91a21921b047da8644155e5678e9066af7bcb30ee0dca7361"}, 1042 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea8de658d7db5987b11097445f2b1f134400e2232cb40e614e5f7b6f5428710e"}, 1043 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1ce02f420a7ec3b2480fe6746d756530f69769292eca363218c2291d0b116a01"}, 1044 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39079ebf54156be6e6902f5c70c078f453350616cfe7bfd2dd15bdb3eac20ccc"}, 1045 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ff24897f6b2001c38a805d53b6ae72267025878d35ea225aa24675fbff2dba7f"}, 1046 | {file = "regex-2021.10.8-cp310-cp310-win32.whl", hash = "sha256:c6569ba7b948c3d61d27f04e2b08ebee24fec9ff8e9ea154d8d1e975b175bfa7"}, 1047 | {file = "regex-2021.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:45cb0f7ff782ef51bc79e227a87e4e8f24bc68192f8de4f18aae60b1d60bc152"}, 1048 | {file = "regex-2021.10.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fab3ab8aedfb443abb36729410403f0fe7f60ad860c19a979d47fb3eb98ef820"}, 1049 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e55f8d66f1b41d44bc44c891bcf2c7fad252f8f323ee86fba99d71fd1ad5e3"}, 1050 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d52c5e089edbdb6083391faffbe70329b804652a53c2fdca3533e99ab0580d9"}, 1051 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1abbd95cbe9e2467cac65c77b6abd9223df717c7ae91a628502de67c73bf6838"}, 1052 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9b5c215f3870aa9b011c00daeb7be7e1ae4ecd628e9beb6d7e6107e07d81287"}, 1053 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f540f153c4f5617bc4ba6433534f8916d96366a08797cbbe4132c37b70403e92"}, 1054 | {file = "regex-2021.10.8-cp36-cp36m-win32.whl", hash = "sha256:1f51926db492440e66c89cd2be042f2396cf91e5b05383acd7372b8cb7da373f"}, 1055 | {file = "regex-2021.10.8-cp36-cp36m-win_amd64.whl", hash = "sha256:5f55c4804797ef7381518e683249310f7f9646da271b71cb6b3552416c7894ee"}, 1056 | {file = "regex-2021.10.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb2baff66b7d2267e07ef71e17d01283b55b3cc51a81b54cc385e721ae172ba4"}, 1057 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e527ab1c4c7cf2643d93406c04e1d289a9d12966529381ce8163c4d2abe4faf"}, 1058 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c98b013273e9da5790ff6002ab326e3f81072b4616fd95f06c8fa733d2745f"}, 1059 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:55ef044899706c10bc0aa052f2fc2e58551e2510694d6aae13f37c50f3f6ff61"}, 1060 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0ab3530a279a3b7f50f852f1bab41bc304f098350b03e30a3876b7dd89840e"}, 1061 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a37305eb3199d8f0d8125ec2fb143ba94ff6d6d92554c4b8d4a8435795a6eccd"}, 1062 | {file = "regex-2021.10.8-cp37-cp37m-win32.whl", hash = "sha256:2efd47704bbb016136fe34dfb74c805b1ef5c7313aef3ce6dcb5ff844299f432"}, 1063 | {file = "regex-2021.10.8-cp37-cp37m-win_amd64.whl", hash = "sha256:924079d5590979c0e961681507eb1773a142553564ccae18d36f1de7324e71ca"}, 1064 | {file = "regex-2021.10.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b09d3904bf312d11308d9a2867427479d277365b1617e48ad09696fa7dfcdf59"}, 1065 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f125fce0a0ae4fd5c3388d369d7a7d78f185f904c90dd235f7ecf8fe13fa741"}, 1066 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f199419a81c1016e0560c39773c12f0bd924c37715bffc64b97140d2c314354"}, 1067 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:09e1031e2059abd91177c302da392a7b6859ceda038be9e015b522a182c89e4f"}, 1068 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c070d5895ac6aeb665bd3cd79f673775caf8d33a0b569e98ac434617ecea57d"}, 1069 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:176796cb7f82a7098b0c436d6daac82f57b9101bb17b8e8119c36eecf06a60a3"}, 1070 | {file = "regex-2021.10.8-cp38-cp38-win32.whl", hash = "sha256:5e5796d2f36d3c48875514c5cd9e4325a1ca172fc6c78b469faa8ddd3d770593"}, 1071 | {file = "regex-2021.10.8-cp38-cp38-win_amd64.whl", hash = "sha256:e4204708fa116dd03436a337e8e84261bc8051d058221ec63535c9403a1582a1"}, 1072 | {file = "regex-2021.10.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8b6ee6555b6fbae578f1468b3f685cdfe7940a65675611365a7ea1f8d724991"}, 1073 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973499dac63625a5ef9dfa4c791aa33a502ddb7615d992bdc89cf2cc2285daa3"}, 1074 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88dc3c1acd3f0ecfde5f95c32fcb9beda709dbdf5012acdcf66acbc4794468eb"}, 1075 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4786dae85c1f0624ac77cb3813ed99267c9adb72e59fdc7297e1cf4d6036d493"}, 1076 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe6ce4f3d3c48f9f402da1ceb571548133d3322003ce01b20d960a82251695d2"}, 1077 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9e3e2cea8f1993f476a6833ef157f5d9e8c75a59a8d8b0395a9a6887a097243b"}, 1078 | {file = "regex-2021.10.8-cp39-cp39-win32.whl", hash = "sha256:82cfb97a36b1a53de32b642482c6c46b6ce80803854445e19bc49993655ebf3b"}, 1079 | {file = "regex-2021.10.8-cp39-cp39-win_amd64.whl", hash = "sha256:b04e512eb628ea82ed86eb31c0f7fc6842b46bf2601b66b1356a7008327f7700"}, 1080 | {file = "regex-2021.10.8.tar.gz", hash = "sha256:26895d7c9bbda5c52b3635ce5991caa90fbb1ddfac9c9ff1c7ce505e2282fb2a"}, 1081 | ] 1082 | requests = [ 1083 | {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, 1084 | {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, 1085 | ] 1086 | safety = [ 1087 | {file = "safety-1.10.3-py2.py3-none-any.whl", hash = "sha256:5f802ad5df5614f9622d8d71fedec2757099705c2356f862847c58c6dfe13e84"}, 1088 | {file = "safety-1.10.3.tar.gz", hash = "sha256:30e394d02a20ac49b7f65292d19d38fa927a8f9582cdfd3ad1adbbc66c641ad5"}, 1089 | ] 1090 | six = [ 1091 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 1092 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 1093 | ] 1094 | smmap = [ 1095 | {file = "smmap-4.0.0-py2.py3-none-any.whl", hash = "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"}, 1096 | {file = "smmap-4.0.0.tar.gz", hash = "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182"}, 1097 | ] 1098 | stevedore = [ 1099 | {file = "stevedore-3.4.0-py3-none-any.whl", hash = "sha256:920ce6259f0b2498aaa4545989536a27e4e4607b8318802d7ddc3a533d3d069e"}, 1100 | {file = "stevedore-3.4.0.tar.gz", hash = "sha256:59b58edb7f57b11897f150475e7bc0c39c5381f0b8e3fa9f5c20ce6c89ec4aa1"}, 1101 | ] 1102 | termcolor = [ 1103 | {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"}, 1104 | ] 1105 | toml = [ 1106 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 1107 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 1108 | ] 1109 | typed-ast = [ 1110 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, 1111 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, 1112 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, 1113 | {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, 1114 | {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, 1115 | {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, 1116 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, 1117 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, 1118 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, 1119 | {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, 1120 | {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, 1121 | {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, 1122 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, 1123 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, 1124 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, 1125 | {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, 1126 | {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, 1127 | {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, 1128 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, 1129 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, 1130 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, 1131 | {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, 1132 | {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, 1133 | {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, 1134 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, 1135 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, 1136 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, 1137 | {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, 1138 | {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, 1139 | {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, 1140 | ] 1141 | typing-extensions = [ 1142 | {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, 1143 | {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, 1144 | {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, 1145 | ] 1146 | urllib3 = [ 1147 | {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, 1148 | {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, 1149 | ] 1150 | virtualenv = [ 1151 | {file = "virtualenv-20.8.1-py2.py3-none-any.whl", hash = "sha256:10062e34c204b5e4ec5f62e6ef2473f8ba76513a9a617e873f1f8fb4a519d300"}, 1152 | {file = "virtualenv-20.8.1.tar.gz", hash = "sha256:bcc17f0b3a29670dd777d6f0755a4c04f28815395bca279cdcb213b97199a6b8"}, 1153 | ] 1154 | wcwidth = [ 1155 | {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, 1156 | {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, 1157 | ] 1158 | win32-setctime = [ 1159 | {file = "win32_setctime-1.0.3-py3-none-any.whl", hash = "sha256:dc925662de0a6eb987f0b01f599c01a8236cb8c62831c22d9cada09ad958243e"}, 1160 | {file = "win32_setctime-1.0.3.tar.gz", hash = "sha256:4e88556c32fdf47f64165a2180ba4552f8bb32c1103a2fafd05723a0bd42bd4b"}, 1161 | ] 1162 | zipp = [ 1163 | {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, 1164 | {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, 1165 | ] 1166 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "Natrix" 3 | version = "1.0.1" 4 | description = "Fast fluid simulation in Python" 5 | authors = [ 6 | "Federico Bertola" 7 | ] 8 | license = "MIT" 9 | 10 | readme = "README.md" 11 | 12 | repository = "https://github.com/fbertola/natrix" 13 | 14 | keywords = ["fluid-simulation", "navier-stokes", "bgfx"] 15 | 16 | classifiers = [ 17 | "License :: OSI Approved :: BSD License", 18 | "Intended Audience :: Developers", 19 | "Operating System :: OS Independent", 20 | "Programming Language :: Python", 21 | "Programming Language :: Python :: 3", 22 | "Programming Language :: Python :: 3.7", 23 | "Programming Language :: Python :: 3.8", 24 | "Programming Language :: Python :: Implementation :: CPython", 25 | "Programming Language :: Python :: Implementation :: PyPy", 26 | "Topic :: Multimedia :: Graphics" 27 | ] 28 | 29 | [tool.poetry.dependencies] 30 | python = "^3.7" 31 | bgfx-python = "^2.0.1" 32 | glfw = "^1.12.0" 33 | python-decouple = "^3.3" 34 | 35 | [tool.poetry.dev-dependencies] 36 | pytest = "^5.4.3" 37 | pytest-mock = "^1.9" 38 | black = "^20.8b1" 39 | pytest-sugar = "^0.9.2" 40 | flake8 = "^3.8.4" 41 | flake8-bandit = "^2.1.2" 42 | flake8-black = "^0.2.0" 43 | flake8-bugbear = "^20.1.4" 44 | flake8-import-order = "^0.18.1" 45 | safety = "^1.9.0" 46 | nox = "^2020.5.24" 47 | glfw = "^1.12.0" 48 | numpy = "^1.19.0" 49 | 50 | [tool.black] 51 | line-length = 88 52 | include = '\.pyi?$' 53 | exclude = ''' 54 | /( 55 | \.eggs 56 | | \.git 57 | | \.hg 58 | | \.mypy_cache 59 | | \.tox 60 | | \.venv 61 | | _build 62 | | buck-out 63 | | build 64 | | dist 65 | | tests/.*/setup.py 66 | )/ 67 | ''' 68 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import io 5 | import os 6 | 7 | from setuptools import find_packages, setup 8 | 9 | NAME = 'Natrix' 10 | DESCRIPTION = 'Fast fluid simulation for python.' 11 | URL = 'https://github.com/fbertola/Natrix' 12 | AUTHOR = 'Federico Bertola' 13 | REQUIRES_PYTHON = '>=3.6.0' 14 | VERSION = "1.0.1" 15 | 16 | REQUIRED = [ 17 | "bgfx-python", 18 | "numpy", 19 | "python-decouple", 20 | ] 21 | 22 | here = os.path.abspath(os.path.dirname(__file__)) 23 | 24 | try: 25 | with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f: 26 | long_description = '\n' + f.read() 27 | except FileNotFoundError: 28 | long_description = DESCRIPTION 29 | 30 | setup( 31 | name=NAME, 32 | version=VERSION, 33 | description=DESCRIPTION, 34 | long_description=long_description, 35 | long_description_content_type='text/markdown', 36 | author=AUTHOR, 37 | python_requires=REQUIRES_PYTHON, 38 | url=URL, 39 | packages=find_packages(exclude=('tests',)), 40 | install_requires=REQUIRED, 41 | include_package_data=True, 42 | license='MIT', 43 | classifiers=[ 44 | 'License :: OSI Approved :: MIT License', 45 | 'Programming Language :: Python', 46 | 'Programming Language :: Python :: 3', 47 | 'Programming Language :: Python :: 3.6', 48 | 'Programming Language :: Python :: 3.7', 49 | 'Programming Language :: Python :: Implementation :: CPython', 50 | 'Programming Language :: Python :: Implementation :: PyPy' 51 | ], 52 | ) 53 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbertola/Natrix/403fed55cab582563615c71ef11b40b11cdb7777/tests/__init__.py --------------------------------------------------------------------------------