├── .gitattributes ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── README.md ├── data ├── gifs │ └── triceratops_gif_small.gif ├── objs │ ├── bull_initmesh.obj │ ├── cylinder.obj │ ├── g_initmesh.obj │ ├── giraffe_initmesh.obj │ ├── guitar_initmesh.obj │ ├── icosahedron.obj │ ├── remeshed_elephant_hull.obj │ ├── tetrahedron.obj │ ├── tiki_initmesh.obj │ └── triceratops_initmesh.obj └── point_clouds │ ├── ankylosaurus.pwn │ ├── ankylosaurus_settings.json │ ├── bull.pwn │ ├── bull_settings.json │ ├── elephant.pwn │ ├── elephant_settings.json │ ├── g.pwn │ ├── giraffe.pwn │ ├── giraffe_settings.json │ ├── guitar.pwn │ ├── guitar_settings.json │ ├── guitar_settings_pooling.json │ ├── hand.pwn │ ├── hand_settings.json │ ├── sphere.pwn │ ├── tiki.pwn │ ├── tiki_settings.json │ ├── triceratops.pwn │ └── triceratops_settings.json ├── requirements.txt └── source ├── __init__.py ├── layers ├── __init__.py ├── composite │ ├── ConvolutionSequence.py │ ├── Decoder.py │ ├── DownConvolution.py │ ├── Encoder.py │ ├── UpConvolution.py │ ├── __init__.py │ └── __tests__ │ │ ├── TestConvolutionSequence.py │ │ ├── TestDecoder.py │ │ ├── TestDownConvolution.py │ │ ├── TestEncoder.py │ │ ├── TestUpConvolution.py │ │ └── __init__.py ├── convolution │ ├── MeshConvolution.py │ ├── __init__.py │ └── __tests__ │ │ ├── TestMeshConvolution.py │ │ └── __init__.py ├── feature.py └── pooling │ ├── CollapseSnapshot.py │ ├── MeshPool.py │ ├── MeshUnpool.py │ ├── __init__.py │ ├── __tests__ │ ├── TestCollapseEdge.py │ ├── TestMeshPool.py │ ├── TestMeshUnpool.py │ └── __init__.py │ └── collapse_edge.py ├── loss ├── ChamferLossLayer.py ├── ConvergenceDetector.py ├── __init__.py ├── __tests__ │ ├── TestChamferLoss.py │ ├── TestLoss.py │ └── __init__.py └── loss.py ├── mesh ├── EdgeConnection.py ├── Mesh.py ├── MeshChecker.py ├── Obj.py ├── __init__.py ├── __tests__ │ ├── TestMesh.py │ └── __init__.py └── remesh.py ├── model ├── PointToMeshModel.py ├── __init__.py ├── __tests__ │ ├── TestGetVertexFeatures.py │ └── __init__.py └── get_vertex_features.py ├── options └── options.py └── script_modules ├── __init__.py ├── create_convex_hull.py ├── make_c4d_point_cloud_ascii.py ├── remesh_obj.py └── train_model.py /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pwn linguist-detectable=false 2 | -------------------------------------------------------------------------------- /.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 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # pytype static type analyzer 135 | .pytype/ 136 | 137 | # Cython debug symbols 138 | cython_debug/ 139 | 140 | # MacOS 141 | .DS_Store 142 | 143 | # Temporary script outputs 144 | tmp* 145 | 146 | # Manifold software 147 | Manifold -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Create Convex Hull", 9 | "type": "python", 10 | "request": "launch", 11 | "module": "source.script_modules.create_convex_hull", 12 | "args": [ 13 | "--input", 14 | "data/point_clouds/elephant.pwn", 15 | "--output", 16 | "tmp_convex_hull.obj" 17 | ] 18 | }, 19 | { 20 | "name": "Remesh Convex Hull", 21 | "type": "python", 22 | "request": "launch", 23 | "module": "source.script_modules.remesh_obj", 24 | "args": [ 25 | "--input", 26 | "tmp_convex_hull.obj", 27 | "--output", 28 | "tmp_remeshed_convex_hull.obj" 29 | ] 30 | }, 31 | { 32 | "name": "Train Model", 33 | "type": "python", 34 | "request": "launch", 35 | "module": "source.script_modules.train_model", 36 | "args": ["data/point_clouds/elephant_settings.json"] 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "venv/bin/python3", 3 | "python.formatting.provider": "black", 4 | "files.exclude": { 5 | "**/.git": true, 6 | "**/.svn": true, 7 | "**/.hg": true, 8 | "**/CVS": true, 9 | "**/.DS_Store": true, 10 | "**/*.pyc": true, 11 | "**/__pycache__": true, 12 | "**/__init__.py": true, 13 | }, 14 | "search.exclude": { 15 | "**/node_modules": true, 16 | "**/bower_components": true, 17 | "**/*.code-search": true, 18 | "**/env": true, 19 | "Manifold": true, 20 | }, 21 | "python.languageServer": "Pylance", 22 | "python.testing.unittestArgs": [ 23 | "-v", 24 | "-s", 25 | ".", 26 | "-p", 27 | "Test*.py" 28 | ], 29 | "python.testing.pytestEnabled": false, 30 | "python.testing.nosetestsEnabled": false, 31 | "python.testing.unittestEnabled": true, 32 | "editor.rulers": [80], 33 | "editor.formatOnSave": true, 34 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Point2Mesh Reimplementation 2 | 3 | ![](data/gifs/triceratops_gif_small.gif) 4 | 5 | This is a reimplementation of [Point2Mesh: A Self-Prior for Deformable Meshes](https://ranahanocka.github.io/point2mesh/) by Rana Hanocka, Gal Metzer, Raja Giryes and Daniel Cohen-Or. It was written by David Charatan, Solon James and Grace Deng as a final project for CSCI 1470: Deep Learning at Brown University. The authors' original implementation (in PyTorch) can be found [here](https://github.com/ranahanocka/Point2Mesh/). Watch a short video we made about the project [here](https://youtu.be/k34zExyJHvo). 6 | 7 | ## Running the Code 8 | 9 | Several example point clouds can be found in the `data` folder. To fit to a point cloud, do the following: 10 | 11 | ``` 12 | python3 -m source.script_modules.train_model data/point_clouds/elephant_settings.json 13 | ``` 14 | 15 | Replace `elephant_settings.json` with a JSON settings file of your choice. For more information about valid JSON settings files, see `options.py`. 16 | 17 | ## Notes About this Implementation 18 | 19 | This implementation runs slower than the original implementation and doesn't include the PartMesh structure (splitting meshes into parts so that larger meshes can be optimized). However, we tried to add lots of comments to the code to make this implementation easier to follow than the original. 20 | 21 | ## Project Setup 22 | 23 | ### Creating a Virtual Environment 24 | 25 | To create a virtual environment, run `python3 -m venv venv`. Then, do `source venv/bin/activate` (or equivalent) and `pip3 install -r requirements.txt` to install the project's dependencies. 26 | 27 | ### Manifold Software Dependency 28 | 29 | The remeshing and simplification operations Point2Mesh depends on require [this watertight manifold software](https://github.com/hjwdzh/Manifold). To install it, `cd` into the `point2mesh-reimplementation` folder and run the following: 30 | 31 | ``` 32 | git clone --recursive -j8 git://github.com/hjwdzh/Manifold 33 | cd Manifold 34 | mkdir build 35 | cd build 36 | cmake .. -DCMAKE_BUILD_TYPE=Release 37 | make 38 | ``` 39 | 40 | ### Installing OpenEXR 41 | 42 | OpenEXR, a dependency of TensorFlow Graphics, cannot be installed directly via `pip`. Instead, follow the steps below: 43 | 44 | #### Windows 45 | 46 | Download a precompiled wheel file from `https://www.lfd.uci.edu/~gohlke/pythonlibs/`, move it to the workspace folder, then run `python -m pip install SomePackage-1.0-py2.py3-none-any.whl`. Running `pip3 install tensorflow-graphics` should then work. 47 | 48 | #### MacOS 49 | 50 | ``` 51 | brew install openexr 52 | export CFLAGS="-I/Users/USERNAME/homebrew/include/OpenEXR -std=c++11" 53 | export LDFLAGS="-L/Users/USERNAME/homebrew/lib" 54 | pip3 install tensorflow-graphics 55 | ``` 56 | 57 | #### Ubuntu 58 | 59 | ``` 60 | sudo apt-get install python3-dev 61 | sudo apt-get install libopenexr-dev 62 | sudo apt-get install openexr 63 | pip3 install tensorflow-graphics 64 | ``` 65 | 66 | ### Running Scripts 67 | 68 | The scripts in `script_modules` can be run as Python modules. To run `create_convex_hull.py`, run the command `python3 -m source.script_modules.create_convex_hull` from the project's root directory. Several VS Code run configurations are included in `.vscode/launch.json`. 69 | 70 | ### Running Unit Tests 71 | 72 | To run the unit tests in VS Code, open the command menu (⌘ Command ⇧ Shift P on MacOS) and run `Python: Discover Tests` and then `Python: Run All Tests`. I recommend discovering and running tests through Python Test Explorer for Visual Studio Code (`littlefoxteam.vscode-python-test-adapter`) if you add more tests, since it's hard to diagnose broken tests that aren't being discovered with the default test explorer. 73 | 74 | ### GCP VM Creation Steps 75 | 76 | Disclaimer: This is a bit hacky since GCP's setup precludes usage of the normal venv setup. 77 | 78 | First, make a VM: 79 | 80 | - Region: `us-west1 (Oregon)` 81 | - Series: `N1` 82 | - Machine type: `n1-standard-4 (4 vCPU, 15 GB memory)` 83 | - Under `CPU platform and GPU`: Add one `NVIDIA Tesla V100` GPU 84 | - Scroll down and check `Allow HTTP traffic` and `Allow HTTPS traffic` 85 | - Set the boot disk to `Deep Learning on Linux` and `Intel® optimized Deep Learning Image: TensorFlow 2.3 m59 (with Intel® MKL-DNN/MKL and CUDA 110)`. 86 | 87 | Now, SSH into the VM under `Open in browser window`. You might have to wait a few minutes before this is possible (before that, it might hang). Now, in the terminal: 88 | 89 | - When it prompts you for driver installation when you first log in, say yes. 90 | 91 | Now run the following commands: 92 | 93 | ``` 94 | git clone https://github.com/dcharatan/point2mesh-reimplementation.git 95 | cd point2mesh-reimplementation 96 | 97 | git clone --recursive -j8 git://github.com/hjwdzh/Manifold 98 | cd Manifold 99 | mkdir build 100 | cd build 101 | cmake .. -DCMAKE_BUILD_TYPE=Release 102 | make 103 | 104 | cd ~/point2mesh-reimplementation 105 | pip3 install trimesh 106 | rm -rf /opt/conda/lib/python3.7/site-packages/cloudpickle 107 | pip3 install cloudpickle==1.4.0 108 | sudo apt-get install libopenexr-dev 109 | sudo apt-get install openexr 110 | pip3 install tensorflow-graphics 111 | ``` 112 | 113 | Now you can run the training script via: 114 | 115 | ``` 116 | python3 -m source.script_modules.train_model 117 | ``` 118 | 119 | ### Retrieving OBJs from Google Cloud 120 | 121 | ``` 122 | mv results ~ 123 | zip -r temp.zip results 124 | ``` 125 | 126 | If using the browser-based SSH terminal, now click the cog (settings icon) and download `temp.zip`. 127 | 128 | ## Acknowledgements 129 | 130 | The files `elephant.pwn`, `hand.pwn` and `sphere.pwn` were taken from Alex Jacobson's [mesh reconstruction project](https://github.com/alecjacobson/geometry-processing-mesh-reconstruction). 131 | -------------------------------------------------------------------------------- /data/gifs/triceratops_gif_small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/data/gifs/triceratops_gif_small.gif -------------------------------------------------------------------------------- /data/objs/cylinder.obj: -------------------------------------------------------------------------------- 1 | # WaveFront *.obj file (generated by Cinema 4D) 2 | 3 | v 0 -50 0 4 | v 0 50 0 5 | v -50 -50 0 6 | v -50 50 0 7 | v -46.19397735595703 -50 19.1341724395752 8 | v -46.19397735595703 50 19.1341724395752 9 | v -35.35533905029297 -50 35.35533905029297 10 | v -35.35533905029297 50 35.35533905029297 11 | v -19.1341724395752 -50 46.19397735595703 12 | v -19.1341724395752 50 46.19397735595703 13 | v 0 -50 50 14 | v 0 50 50 15 | v 19.1341724395752 -50 46.19397735595703 16 | v 19.1341724395752 50 46.19397735595703 17 | v 35.35533905029297 -50 35.35533905029297 18 | v 35.35533905029297 50 35.35533905029297 19 | v 46.19397735595703 -50 19.1341724395752 20 | v 46.19397735595703 50 19.1341724395752 21 | v 50 -50 0.00000000000001 22 | v 50 50 0.00000000000001 23 | v 46.19397735595703 -50 -19.1341724395752 24 | v 46.19397735595703 50 -19.1341724395752 25 | v 35.35533905029297 -50 -35.35533905029297 26 | v 35.35533905029297 50 -35.35533905029297 27 | v 19.1341724395752 -50 -46.19397735595703 28 | v 19.1341724395752 50 -46.19397735595703 29 | v 0.00000000000001 -50 -50 30 | v 0.00000000000001 50 -50 31 | v -19.1341724395752 -50 -46.19397735595703 32 | v -19.1341724395752 50 -46.19397735595703 33 | v -35.35533905029297 -50 -35.35533905029297 34 | v -35.35533905029297 50 -35.35533905029297 35 | v -46.19397735595703 -50 -19.1341724395752 36 | v -46.19397735595703 50 -19.1341724395752 37 | # 34 vertices 38 | 39 | g Cylinder 40 | usemtl default 41 | f 5 3 1 42 | f 6 4 3 43 | f 6 2 4 44 | f 7 5 1 45 | f 8 6 5 46 | f 8 2 6 47 | f 9 7 1 48 | f 10 8 7 49 | f 10 2 8 50 | f 11 9 1 51 | f 12 10 9 52 | f 12 2 10 53 | f 13 11 1 54 | f 14 12 11 55 | f 14 2 12 56 | f 15 13 1 57 | f 16 14 13 58 | f 16 2 14 59 | f 17 15 1 60 | f 18 16 15 61 | f 18 2 16 62 | f 19 17 1 63 | f 20 18 17 64 | f 20 2 18 65 | f 21 19 1 66 | f 22 20 19 67 | f 22 2 20 68 | f 23 21 1 69 | f 24 22 21 70 | f 24 2 22 71 | f 25 23 1 72 | f 26 24 23 73 | f 26 2 24 74 | f 27 25 1 75 | f 28 26 25 76 | f 28 2 26 77 | f 29 27 1 78 | f 30 28 27 79 | f 30 2 28 80 | f 31 29 1 81 | f 32 30 29 82 | f 32 2 30 83 | f 33 31 1 84 | f 34 32 31 85 | f 34 2 32 86 | f 3 33 1 87 | f 4 34 33 88 | f 4 2 34 89 | f 5 6 3 90 | f 7 8 5 91 | f 9 10 7 92 | f 11 12 9 93 | f 13 14 11 94 | f 15 16 13 95 | f 17 18 15 96 | f 19 20 17 97 | f 21 22 19 98 | f 23 24 21 99 | f 25 26 23 100 | f 27 28 25 101 | f 29 30 27 102 | f 31 32 29 103 | f 33 34 31 104 | f 3 4 33 105 | 106 | -------------------------------------------------------------------------------- /data/objs/icosahedron.obj: -------------------------------------------------------------------------------- 1 | # WaveFront *.obj file (generated by Cinema 4D) 2 | 3 | v 0 52.57319641113281 -85.06521606445312 4 | v 0 -52.57319641113281 -85.06521606445312 5 | v -85.06521606445312 0 -52.57319641113281 6 | v -85.06521606445312 0 52.57319641113281 7 | v 0 52.57319641113281 85.06521606445312 8 | v 0 -52.57319641113281 85.06521606445312 9 | v 85.06521606445312 0 52.57319641113281 10 | v 85.06521606445312 0 -52.57319641113281 11 | v -52.57319641113281 85.06521606445312 0 12 | v 52.57319641113281 85.06521606445312 0 13 | v 52.57319641113281 -85.06521606445312 0 14 | v -52.57319641113281 -85.06521606445312 0 15 | # 12 vertices 16 | 17 | g Platonic 18 | usemtl default 19 | f 1 9 10 20 | f 1 3 9 21 | f 3 4 9 22 | f 4 5 9 23 | f 5 10 9 24 | f 7 10 5 25 | f 8 10 7 26 | f 8 1 10 27 | f 2 11 12 28 | f 2 12 3 29 | f 12 4 3 30 | f 12 6 4 31 | f 12 11 6 32 | f 11 7 6 33 | f 11 8 7 34 | f 11 2 8 35 | f 1 8 2 36 | f 1 2 3 37 | f 4 6 5 38 | f 6 7 5 39 | 40 | -------------------------------------------------------------------------------- /data/objs/remeshed_elephant_hull.obj: -------------------------------------------------------------------------------- 1 | # https://github.com/mikedh/trimesh 2 | v 0.20508160 -0.05028716 0.11785877 3 | v -0.27776255 0.22682678 -0.23598986 4 | v 0.03357697 0.20551651 0.27511320 5 | v -0.06796031 -0.28694344 -0.27614924 6 | v -0.27547647 0.38460647 -0.28479177 7 | v -0.23933439 0.10221652 0.38542411 8 | v -0.28715007 0.25067055 -0.02190969 9 | v 0.01899265 -0.17526894 -0.30009222 10 | v -0.09816062 -0.35870955 0.25919185 11 | v -0.26167806 0.08029003 0.24774497 12 | v 0.03270146 -0.32375876 0.22531945 13 | v -0.09148925 0.50739543 -0.03929118 14 | v -0.13853181 0.44716900 0.28839993 15 | v -0.19027965 -0.11484403 0.32599679 16 | v 0.03258883 0.47382838 0.08359488 17 | v 0.00733188 -0.49101744 0.15783122 18 | v 0.04472324 0.09478265 0.29269839 19 | v 0.03034116 0.46482769 -0.08381008 20 | v -0.26137595 0.03161169 -0.11575180 21 | v 0.03366405 0.47882629 -0.03827316 22 | v -0.12967930 0.49465585 -0.15156331 23 | v -0.20306426 -0.34324209 -0.09910689 24 | v -0.23035130 -0.16227816 0.14762657 25 | v 0.22583302 -0.06794984 -0.06283722 26 | v -0.16467962 0.20133116 0.43429715 27 | v -0.10233338 -0.50490029 -0.17038707 28 | v -0.00860154 0.45166569 0.15753814 29 | v -0.23787001 0.00225219 0.31591780 30 | v -0.23900409 0.23286909 -0.43445729 31 | v -0.22442233 0.28422918 -0.44036201 32 | v 0.10275591 0.14635722 -0.21522102 33 | v -0.22728440 -0.14510003 -0.18849529 34 | v 0.12072968 -0.22229933 0.14805650 35 | v 0.26909960 0.24616755 0.07565186 36 | v 0.01099528 0.36481007 -0.21230963 37 | v -0.02203383 0.37329259 -0.23523446 38 | v -0.17245431 0.48775835 0.21790675 39 | v -0.23731703 -0.14711129 0.11783322 40 | v 0.03949066 -0.09581628 -0.31288490 41 | v -0.24933878 0.15466629 -0.38225547 42 | v -0.23932918 -0.18063319 0.03694717 43 | v 0.11924747 -0.18798044 0.17299975 44 | v 0.00987404 -0.29549272 0.26382259 45 | v -0.28502772 0.24464058 0.09078105 46 | v -0.06527988 0.50996402 -0.04003026 47 | v -0.27288052 0.11015089 0.00865877 48 | v -0.00402164 0.02021066 -0.33223364 49 | v -0.18551284 -0.43617901 -0.00924513 50 | v -0.02484211 -0.42464942 0.21807279 51 | v -0.08022003 0.12171612 -0.36962465 52 | v 0.00125453 -0.09377062 0.34566986 53 | v -0.04179442 -0.46574239 0.20505850 54 | v 0.05430286 -0.42025369 0.12499413 55 | v 0.05264655 -0.42671764 -0.10854562 56 | v -0.23937777 -0.20436415 0.01689830 57 | v -0.08878831 -0.49960981 -0.17661811 58 | v -0.21842415 -0.09499762 -0.28765735 59 | v 0.16304657 0.03392654 -0.18882652 60 | v 0.07196177 0.00783792 -0.27369541 61 | v -0.12788463 -0.03664095 -0.36817537 62 | v -0.07935983 -0.27873610 0.29683070 63 | v -0.01047534 0.49959055 0.06420896 64 | v 0.11670290 -0.09767528 0.23627421 65 | v -0.15774190 0.48974010 0.02830380 66 | v -0.28553710 0.30607211 0.18479668 67 | v -0.19372526 -0.41193879 -0.05906914 68 | v -0.26313198 0.05600280 -0.10539380 69 | v -0.15629806 0.36995689 -0.35988090 70 | v -0.26965230 0.40879749 0.29309746 71 | v -0.23478254 0.42084560 -0.35500164 72 | v 0.03649043 -0.17765460 0.30616444 73 | v -0.09970925 -0.19765828 -0.31229638 74 | v -0.24154860 -0.00345143 -0.28108002 75 | v -0.15503852 -0.50058112 -0.15439388 76 | v -0.08184555 0.22655269 0.36377814 77 | v -0.27555819 0.24805530 -0.36443598 78 | v -0.05317611 -0.13710217 -0.34210532 79 | v -0.07961322 -0.50896659 0.17964056 80 | v 0.24480790 0.31368353 -0.03538647 81 | v 0.24302358 0.01005690 0.08175835 82 | v 0.12440342 -0.03318542 -0.23011006 83 | v -0.18863593 -0.25086316 -0.24528864 84 | v -0.08440297 -0.43978091 -0.20502060 85 | v -0.06536937 0.24265767 -0.32799898 86 | v 0.07372823 -0.28907237 0.18043719 87 | v 0.15892759 0.29652361 -0.12889311 88 | v -0.14973417 0.01441052 -0.38074543 89 | v 0.31116567 0.16414359 -0.00485926 90 | v 0.24049687 0.33146864 0.00078773 91 | v -0.26798056 0.27699920 -0.40283138 92 | v -0.24236264 -0.04806089 -0.21147874 93 | v -0.22528488 -0.00873848 -0.32788442 94 | v -0.17852904 -0.44421762 -0.14490434 95 | v 0.20368920 -0.16597816 -0.02666578 96 | v -0.13697512 -0.50938919 -0.15170801 97 | v 0.25377049 0.02663345 0.07128331 98 | v 0.18961433 0.20573881 -0.13022492 99 | v -0.07639783 0.50323739 0.10640749 100 | v -0.19669735 -0.26802464 -0.22384867 101 | v 0.00057113 0.49094426 0.08352129 102 | v -0.22551527 0.24237176 0.45774926 103 | v -0.21690342 0.18263992 -0.42347331 104 | v -0.25678822 0.31969648 -0.41393818 105 | v -0.20369126 0.25669160 -0.43483049 106 | v -0.08613862 0.18049356 0.37820764 107 | v -0.26098918 0.00877642 -0.02340972 108 | v 0.00006683 -0.23008467 0.30023525 109 | v 0.11612699 -0.10294383 -0.21766147 110 | v 0.29572379 0.20068272 -0.04064740 111 | v -0.28681445 0.30001999 -0.08502775 112 | v 0.18464283 -0.07298245 0.14255370 113 | v -0.15281125 0.20662210 -0.40782377 114 | v -0.20159204 0.38961355 0.40560940 115 | v -0.24927972 -0.09477515 -0.07320045 116 | v -0.20007395 0.45456259 0.33148763 117 | v -0.24986851 0.26916651 -0.43246977 118 | v -0.07345379 0.37905727 -0.27956855 119 | v 0.28184094 0.28221727 0.02227394 120 | v -0.28493282 0.28258579 0.21506118 121 | v -0.25669602 -0.01550795 0.03119027 122 | v -0.13595362 -0.15636246 -0.31430368 123 | v -0.11360226 0.48391875 -0.17746849 124 | v -0.07485666 -0.36151995 0.25876960 125 | v -0.20128481 0.21602432 -0.43202868 126 | v -0.21920736 0.06035442 0.38763311 127 | v 0.03597344 -0.14273383 0.32347221 128 | v 0.05651233 -0.37543526 0.15231271 129 | v -0.04362719 0.47810914 0.14722034 130 | v -0.22292191 0.44178803 0.35012635 131 | v -0.22020831 0.32952570 -0.42762796 132 | v -0.20183006 0.45785277 -0.30784278 133 | v -0.23326430 0.46827741 -0.19013017 134 | v -0.27157214 0.38493662 0.33767775 135 | v -0.26886366 0.12905191 0.16591371 136 | v -0.26217438 0.33080163 0.41769223 137 | v -0.27160029 0.30458479 0.40450323 138 | v 0.11648803 -0.31957613 0.08268407 139 | v -0.19752412 -0.38996545 0.04487847 140 | v -0.25516508 -0.01509959 -0.09810628 141 | v -0.11752156 -0.38131243 -0.22681248 142 | v -0.00804828 0.36797205 0.24368696 143 | v -0.02551515 0.06677343 -0.33844166 144 | v 0.15407397 -0.25139587 0.02752045 145 | v -0.28339419 0.33466631 -0.06904582 146 | v -0.18518490 0.11769379 0.42598705 147 | v -0.24882137 0.17768136 0.41332029 148 | v -0.27153625 0.15840484 -0.21237942 149 | v -0.22789848 -0.24026124 0.06188692 150 | v -0.19011288 0.26321589 0.44354506 151 | v -0.17228759 -0.25388647 -0.25713711 152 | v -0.25048791 0.01330632 -0.23108368 153 | v -0.25080790 -0.08594410 0.06409185 154 | v -0.24227798 0.20743830 0.43833684 155 | v 0.27689514 0.24684065 -0.05026096 156 | v 0.12881450 0.29927406 0.17085831 157 | v 0.13996074 0.11615241 -0.19232978 158 | v 0.21430571 -0.14812106 0.03463534 159 | v 0.14631211 -0.27006138 0.06041407 160 | v -0.16672725 0.49108362 0.18972086 161 | v 0.05512492 -0.23738401 0.24461244 162 | v 0.10713132 0.41687008 -0.05937420 163 | v -0.28730068 0.27097277 0.08844306 164 | v 0.04169260 0.04520277 -0.28910265 165 | v -0.28456660 0.29626778 -0.16202071 166 | v 0.21420844 0.03151262 0.13319759 167 | v -0.13289939 -0.01238065 0.39294556 168 | v 0.00545581 -0.36842204 -0.21179433 169 | v -0.04532691 0.38254377 -0.24860404 170 | v -0.26147538 0.06280567 0.18049744 171 | v 0.06043950 0.37797348 -0.14894032 172 | v -0.17970898 0.44503680 0.32994639 173 | v -0.02890962 0.02180884 -0.34771330 174 | v -0.19405522 -0.41025027 0.02735706 175 | v -0.17523153 -0.47950496 -0.00041837 176 | v -0.00481233 0.49415005 -0.06216711 177 | v -0.28052177 0.24351843 0.20860494 178 | v 0.02896944 -0.33812135 -0.20534580 179 | v -0.06996688 0.41324812 -0.24007291 180 | v -0.25075920 -0.01171217 -0.19261920 181 | v 0.26771760 0.14277619 0.09027318 182 | v -0.25001168 0.04357535 -0.27929844 183 | v 0.06270256 -0.07089799 -0.29844947 184 | v 0.03070768 -0.42422228 0.16131228 185 | v 0.19351601 0.15267925 0.15869870 186 | v 0.29511729 0.10201524 0.03260222 187 | v -0.16808143 -0.29097424 0.26046698 188 | v 0.03339313 -0.46892551 -0.10161733 189 | v 0.26999857 0.01400413 0.04232692 190 | v -0.12855758 -0.51060529 0.16446140 191 | v -0.04047565 0.50737497 -0.05324066 192 | v -0.17375181 -0.03113286 -0.35135569 193 | v 0.07921972 -0.08874182 -0.27904866 194 | v -0.24944843 0.04067244 0.29268171 195 | v -0.06522827 -0.19855082 0.33346270 196 | v -0.23224522 -0.24742815 0.00249026 197 | v -0.08608458 0.29274245 0.35235578 198 | v 0.05765174 -0.17246872 -0.26725133 199 | v -0.27309513 0.18078947 -0.26173635 200 | v -0.24341961 0.45999971 -0.24791048 201 | v -0.11620297 -0.07743115 -0.35638468 202 | v -0.21696713 -0.19518074 -0.20281991 203 | v 0.12651320 0.39325350 0.08686473 204 | v -0.08694216 -0.07882460 0.37590389 205 | v -0.12342216 0.15136288 -0.39473213 206 | v -0.14944456 0.06282969 0.41224356 207 | v 0.02055993 -0.49057191 -0.11239324 208 | v 0.01616953 -0.33234640 0.24005982 209 | v 0.28556345 0.07761136 -0.02391169 210 | v 0.07460409 0.07826106 -0.25441641 211 | v 0.19950650 0.04754929 0.16297287 212 | v -0.15611590 0.30637042 0.40550876 213 | v -0.24720582 0.30997005 -0.42741988 214 | v 0.04197947 0.35698117 0.20677879 215 | v -0.00127429 0.49625091 -0.04008691 216 | v -0.02869445 -0.47152956 -0.17928460 217 | v -0.22622149 -0.07001719 0.29902754 218 | v 0.30003259 0.16268381 -0.03424597 219 | v 0.14890300 -0.09698377 0.18536365 220 | v -0.20874692 -0.31603132 -0.09802227 221 | v 0.01807420 0.28643495 -0.25054097 222 | v -0.26155972 0.03222578 0.04361971 223 | v 0.27526717 -0.00578054 0.00043490 224 | v -0.27701955 0.23037235 0.26221277 225 | v -0.09023939 0.50465435 -0.08240233 226 | v -0.08620483 0.12607928 0.39235705 227 | v -0.05390275 0.13095304 0.36393516 228 | v 0.26790973 -0.03473216 0.00228850 229 | v 0.25110078 -0.08511839 0.00620397 230 | v -0.22468290 -0.03375539 0.32907373 231 | v -0.03067330 0.44208392 -0.17159355 232 | v -0.20694210 0.22037090 0.45175212 233 | v -0.23901378 -0.18741924 0.05188393 234 | v 0.09457982 -0.35611205 -0.03407771 235 | v -0.09404098 -0.13359442 -0.34215596 236 | v -0.22694082 -0.25335946 -0.03712315 237 | v -0.04075202 -0.13835463 0.35574850 238 | v -0.15534274 -0.36516010 0.23447974 239 | v -0.21952961 -0.20663947 0.17195993 240 | v -0.27255233 0.16670629 0.23974219 241 | v -0.28040641 0.29892217 0.36469164 242 | v -0.21541569 -0.15627392 0.26678601 243 | v 0.24781120 -0.06532295 -0.03042666 244 | v -0.24955584 -0.01380329 0.21467836 245 | v -0.06834880 -0.10404852 -0.35104880 246 | v -0.19203520 0.12579974 -0.40956518 247 | v 0.03089985 -0.35510270 0.20713954 248 | v 0.07172673 -0.40143077 0.09970727 249 | v 0.03343426 -0.38600723 0.18287257 250 | v -0.02595006 -0.30719560 -0.25690488 251 | v -0.17799102 0.25138819 -0.41739374 252 | v 0.15380546 0.08029246 0.20879557 253 | v 0.04111682 -0.45035692 0.07418937 254 | v -0.25900990 0.41060617 -0.32682072 255 | v -0.23528894 0.35176581 -0.41547009 256 | v -0.01836478 -0.03425128 -0.34102449 257 | v 0.14010179 -0.09616918 -0.18378071 258 | v -0.25890749 0.09722974 -0.28036021 259 | v 0.09774404 -0.20400913 -0.18216120 260 | v -0.11888828 0.46305310 -0.22483997 261 | v -0.19671740 -0.36742785 -0.11921560 262 | v -0.01220027 -0.50571016 -0.14841711 263 | v -0.08839098 0.05841378 -0.37605061 264 | v 0.23078214 0.00878482 -0.08208994 265 | v -0.11786682 0.09650421 0.40829137 266 | v -0.21868986 -0.28369714 -0.06305636 267 | v 0.23099206 0.33491689 0.03587271 268 | v -0.11943098 -0.49098587 0.19527959 269 | v -0.22760377 0.12208073 0.40999295 270 | v -0.20528825 0.36003803 -0.40703113 271 | v 0.19216200 0.01366067 -0.14295999 272 | v -0.28031928 0.21020465 0.06366644 273 | v 0.21382728 0.34792546 -0.01949359 274 | v 0.20993864 0.11480858 -0.13493690 275 | v -0.21251768 0.02789199 -0.35927690 276 | v -0.20593336 -0.29313457 0.15960604 277 | v 0.09882440 0.17411268 0.22970466 278 | v 0.09937993 0.42893468 -0.04567307 279 | v -0.16783287 -0.39869087 0.20945048 280 | v -0.20086198 0.19825415 0.44691045 281 | v -0.19723445 -0.37814277 0.10665198 282 | v -0.03204021 -0.50641486 -0.16060662 283 | v -0.15583157 0.03623556 0.40407554 284 | v 0.01733963 0.32141881 -0.23733244 285 | v -0.20281525 -0.21505936 0.25908394 286 | v -0.27938485 0.22181731 0.13720130 287 | v 0.03266123 -0.45330437 0.13975362 288 | v -0.19180212 -0.29157293 0.23299715 289 | v 0.05748556 -0.31965184 -0.17201441 290 | v 0.16894284 0.13121315 -0.16586327 291 | v -0.04257715 -0.29627685 -0.26687932 292 | v 0.00994381 -0.50468955 -0.05661275 293 | v 0.24010573 -0.09884697 -0.01430228 294 | v 0.09024333 0.41796884 -0.07833184 295 | v -0.12011443 0.40213385 0.31797394 296 | v 0.03544142 -0.45711962 -0.11730427 297 | v 0.12065870 -0.25431891 0.12725768 298 | v -0.10330802 0.50534885 0.08312350 299 | v -0.22963634 0.37258341 0.42067161 300 | v -0.10685362 0.11114728 -0.38712709 301 | v 0.09527887 -0.10150778 -0.24971732 302 | v -0.26708913 0.14069599 -0.28332190 303 | v -0.10069168 -0.02297724 -0.37025762 304 | v 0.10789456 -0.16107271 0.20924210 305 | v 0.14580305 0.22588513 -0.16109639 306 | v -0.27163311 0.36459796 -0.34333015 307 | v -0.21394095 0.43853166 -0.34103202 308 | v -0.18096559 -0.18485883 0.30005381 309 | v 0.00870482 0.20805645 -0.27637238 310 | v -0.27405230 0.17539603 -0.16137656 311 | v -0.20521134 -0.34498381 0.09491059 312 | v -0.22549678 -0.22940026 -0.08051646 313 | v 0.16793683 -0.17689597 0.10105447 314 | v 0.18472019 -0.11647037 0.11379369 315 | v -0.24370860 0.45952449 0.02870817 316 | v -0.27299500 0.12563352 -0.04068257 317 | v 0.07262804 -0.10334774 0.30142609 318 | v -0.25690796 0.00403317 0.12050933 319 | v 0.16584277 0.04585771 0.20286267 320 | v -0.13881259 0.48420598 0.21600199 321 | v 0.25386837 0.30206047 0.05599640 322 | v 0.22249301 -0.01357695 0.10438729 323 | v 0.06339157 0.35011921 -0.17598596 324 | v 0.25091669 0.28320016 -0.05686359 325 | v -0.23726251 -0.13333842 -0.12469573 326 | v -0.02526634 0.01382251 0.36333464 327 | v -0.11408042 0.33708654 0.35802986 328 | v -0.23849129 -0.03458501 0.27287357 329 | v 0.03287639 -0.46418245 -0.06166448 330 | v 0.26866519 0.27636170 0.06193022 331 | v -0.16320169 0.49118064 -0.16978350 332 | v -0.26964648 0.24813763 0.40476359 333 | v 0.15117912 0.32149772 0.13682647 334 | v -0.07660711 -0.14021598 -0.34395895 335 | v 0.00882009 -0.14500446 -0.31923513 336 | v -0.14607270 0.15283282 0.42440678 337 | v 0.10435418 -0.02864575 0.27105573 338 | v -0.24035750 -0.19708171 -0.00872268 339 | v 0.23172442 0.26102360 0.10161202 340 | v 0.22269274 0.07249100 -0.11921612 341 | v 0.06290522 0.31118609 -0.20384666 342 | v 0.08344411 -0.36469143 -0.10074047 343 | v -0.27296421 0.14769832 -0.14068937 344 | v -0.19362213 -0.25787723 0.25149138 345 | v -0.14902181 0.49309584 -0.08462741 346 | v 0.04641371 -0.12687084 -0.30436309 347 | v 0.00383323 -0.19623747 0.31411801 348 | v 0.03486812 0.01662222 0.31924414 349 | v -0.00042916 -0.50935990 -0.10567336 350 | v -0.22777764 0.38975061 0.40903761 351 | v -0.13859236 0.30371029 -0.37336639 352 | v -0.27687332 0.23149406 -0.26754791 353 | v -0.21475491 0.43253625 0.36652133 354 | v -0.27038627 0.40689912 -0.24571981 355 | v -0.26055075 0.14688185 0.35201619 356 | v 0.09035357 -0.24660656 0.18168271 357 | v -0.07105186 0.50990770 0.06073738 358 | v -0.21585314 -0.21760774 0.18916064 359 | v -0.11282082 0.16598509 -0.38542914 360 | v -0.21435810 -0.22750730 -0.17193588 361 | v -0.26057377 0.05194141 0.21160872 362 | v -0.10523809 0.49867397 0.14106837 363 | v -0.27166113 0.12968984 -0.08563392 364 | v -0.23188128 0.38921811 -0.39054844 365 | v -0.20158112 -0.37590041 -0.06396999 366 | v -0.10822560 -0.27572723 -0.27561738 367 | v 0.04054368 0.44023744 -0.10432248 368 | v -0.23994528 -0.16106309 0.08296084 369 | v 0.07731552 -0.36278151 0.12718010 370 | v -0.27197856 0.15703591 0.20091233 371 | v -0.23442335 -0.00868285 -0.30511428 372 | v -0.01106078 -0.08670367 -0.33425267 373 | v -0.28095390 0.22202900 -0.08020264 374 | v 0.13587042 0.30746212 -0.14286246 375 | v -0.27896990 0.21813367 -0.12903826 376 | v -0.16873630 0.07643681 0.41518937 377 | v 0.01812578 -0.49453948 0.12640637 378 | v -0.14302110 0.47258894 0.24747533 379 | v 0.13825122 0.33651555 -0.11747598 380 | v -0.05570205 0.46055468 -0.17028831 381 | v -0.24438429 0.27640471 0.45391998 382 | v -0.21840541 -0.15310156 -0.24534135 383 | v -0.26275228 0.03964220 -0.05375032 384 | v -0.21086108 -0.33596471 -0.04948515 385 | v -0.24616348 0.20197569 -0.41427085 386 | v 0.13018468 0.04834968 0.23533915 387 | v -0.28200348 0.27659907 0.33367908 388 | v 0.19824486 0.00972204 0.14986708 389 | v 0.28876646 0.15660381 0.06508770 390 | v 0.25012838 0.17048969 -0.08951422 391 | v -0.23871386 -0.09180217 0.18997093 392 | v -0.09579418 -0.25671991 0.30512948 393 | v 0.08013927 -0.11640637 0.28318636 394 | v 0.01995623 -0.00669332 0.33762336 395 | v 0.07051623 0.45155563 0.01483852 396 | v -0.28533401 0.26821321 -0.15633010 397 | v -0.22882201 0.09306370 -0.37707521 398 | v -0.14491801 0.15881416 -0.40575848 399 | v -0.07469976 0.29635160 -0.32313422 400 | v 0.17709416 0.02934497 0.18846790 401 | v -0.25858200 0.22442956 0.42224311 402 | v -0.10192536 0.33001138 -0.33342761 403 | v 0.01342824 -0.37316355 0.22016098 404 | v -0.19534232 -0.07930338 -0.32142935 405 | v -0.16380823 -0.33938518 -0.22409003 406 | v 0.26093417 -0.02573568 0.04206586 407 | v -0.17618839 0.17912893 -0.41993990 408 | v 0.05844841 -0.09471773 0.32086128 409 | v -0.25873303 0.38452437 -0.36403605 410 | v -0.11509655 -0.02563781 0.39024883 411 | v -0.16555415 0.49014186 0.14716152 412 | v -0.24470167 0.39420548 0.39391375 413 | v -0.21879958 0.36346792 0.42896026 414 | v -0.03516054 -0.35449729 -0.23773238 415 | v -0.12807318 -0.49238473 -0.17492731 416 | v 0.18721898 -0.04945866 -0.12719239 417 | v -0.03443861 -0.19972962 0.32703403 418 | v -0.21127573 0.26001679 0.45731381 419 | v 0.08627051 0.00377674 0.28178119 420 | v -0.27627924 0.37959973 0.30469870 421 | v 0.06061932 0.20331321 -0.23560470 422 | v 0.14526316 -0.01345786 0.22263096 423 | v -0.04926612 0.45286622 0.19549342 424 | v -0.16882323 -0.50375879 0.12302472 425 | v 0.28030317 0.03487359 0.02704908 426 | v -0.06236051 -0.24293687 -0.29605894 427 | v -0.23755922 -0.11740170 0.15836059 428 | v -0.08939922 -0.10409797 -0.35344634 429 | v -0.10446994 0.36755618 -0.31611068 430 | v -0.05726866 -0.13681186 0.36071756 431 | v -0.21567890 -0.31519331 0.04490899 432 | v 0.17102190 0.36564572 -0.05322747 433 | v -0.21153169 0.41635698 -0.36840972 434 | v -0.02676880 0.19880339 0.32578029 435 | v -0.12780432 0.19043916 0.41080054 436 | v 0.10500209 0.04269042 -0.23920593 437 | v -0.23784590 -0.06366437 0.23397622 438 | v -0.28566158 0.27942683 -0.19658584 439 | v 0.02962802 0.47558987 -0.06462142 440 | v -0.22767245 -0.09417104 -0.25805429 441 | v 0.30090612 0.19448333 0.05091303 442 | v 0.22346356 -0.12042201 -0.03361654 443 | v 0.26939765 -0.01143704 0.02715468 444 | v -0.26184331 0.05126643 -0.14676503 445 | v -0.28145035 0.32451553 -0.31222057 446 | v -0.10174859 -0.06044316 -0.36440674 447 | v 0.10547061 -0.33979375 -0.07219774 448 | v -0.24119435 -0.16571571 -0.04982184 449 | v -0.22133898 0.32202700 0.44888765 450 | v -0.18698634 0.21302827 0.44467026 451 | v -0.25118090 0.09754674 0.34885495 452 | v -0.27272330 0.19984493 0.29386555 453 | v -0.22740105 -0.26242507 0.03656813 454 | v -0.17885833 0.14790958 0.43349842 455 | v -0.20762249 -0.20766604 -0.23788956 456 | v 0.11100023 -0.07339989 0.25295189 457 | v 0.03958903 0.47463621 0.02299401 458 | v 0.14519159 0.39827972 0.04857316 459 | v -0.02382985 -0.14906469 -0.33011324 460 | v -0.08639368 -0.18980135 0.33705576 461 | v 0.29372792 0.23994018 -0.02770589 462 | v 0.11130232 -0.12125131 0.22982694 463 | v -0.00919944 -0.36014338 0.24221414 464 | v -0.06013831 0.01880048 -0.36155686 465 | v -0.26716039 0.20334138 -0.36148228 466 | v -0.16209543 0.42057116 -0.32444765 467 | v -0.23411591 0.31967208 -0.43087054 468 | v 0.01443450 -0.03564855 -0.32588939 469 | v -0.28628102 0.26056189 -0.09158315 470 | v -0.23772294 0.34601543 0.43432387 471 | v 0.17589370 -0.20550555 0.06869498 472 | v -0.28118918 0.21589080 -0.03993658 473 | v -0.02616709 -0.07506436 0.35776587 474 | v 0.23665275 0.12012540 -0.10703775 475 | v -0.23234181 -0.20823551 -0.06178141 476 | v 0.20812667 0.06957192 -0.13844815 477 | v 0.01045883 -0.14888658 0.33432889 478 | v 0.08878460 0.05304282 0.26694116 479 | v 0.13215100 -0.28578508 0.08616848 480 | v -0.26194307 0.03635774 0.13247478 481 | v -0.16683395 0.44093292 -0.30317467 482 | v -0.03966339 -0.21451772 -0.30433437 483 | v -0.28148867 0.32975532 0.32657464 484 | v -0.26248067 0.40266930 0.35017366 485 | v -0.09207939 0.49311148 -0.13347009 486 | v -0.06447747 0.38568681 0.28058497 487 | v -0.26403714 0.24483442 -0.40612628 488 | v 0.04861054 -0.27280677 0.23158904 489 | v -0.08688770 -0.21733815 0.32493673 490 | v -0.04144514 0.35321472 0.28771013 491 | v 0.24852351 0.10684385 0.10872374 492 | v -0.04551297 0.32766263 -0.28724639 493 | v -0.12739713 -0.22617159 -0.28838222 494 | v -0.07788160 0.09688821 -0.37143377 495 | v -0.17652608 0.07752889 -0.39636735 496 | v -0.28459392 0.26252413 0.17881786 497 | v 0.06556033 0.23610900 0.24176102 498 | v -0.25530751 0.12768274 -0.34544139 499 | v -0.24937087 0.01570613 0.25496844 500 | v -0.08758142 -0.49823616 0.19487418 501 | v -0.09268350 -0.29783523 -0.27004203 502 | v 0.06403202 -0.25490782 0.21868828 503 | v -0.28389502 0.22918207 0.03625237 504 | v -0.00143486 -0.26925683 0.28244679 505 | v 0.27469443 0.15525275 -0.06558766 506 | v -0.19830397 -0.15231974 0.30206876 507 | v 0.15104899 0.06739383 -0.19571778 508 | v 0.30901123 0.15558750 0.02577748 509 | v 0.29527939 0.08513976 0.00231445 510 | v 0.15904387 -0.24541193 -0.04617671 511 | v -0.19400829 -0.40918138 0.09322853 512 | v -0.27250556 0.15774132 0.13543040 513 | v 0.01989764 -0.24211788 -0.26655594 514 | v -0.27425404 0.34322570 0.37471749 515 | v 0.11574916 -0.31906679 -0.02232663 516 | v -0.04670331 0.50818972 0.01129169 517 | v 0.13029765 0.00002298 0.24215402 518 | v 0.26218630 -0.06066953 -0.00186262 519 | v -0.11463034 -0.01315990 -0.37614315 520 | v -0.19039866 0.47100135 -0.27275100 521 | v -0.23753850 0.44363602 0.33173948 522 | v -0.26108026 0.01447068 0.04474625 523 | v -0.23223162 -0.05131808 -0.27917328 524 | v -0.07356538 0.49596204 0.13275621 525 | v -0.19598202 -0.34400393 -0.15946341 526 | v -0.28303225 0.33458011 0.15344283 527 | v 0.20720263 0.34153518 0.06140588 528 | v 0.05855623 0.27449953 -0.22039101 529 | v 0.20475271 -0.16589014 0.04096102 530 | v -0.19696249 -0.31149372 -0.19177036 531 | v 0.06727815 0.44309045 -0.07243706 532 | v 0.12776839 -0.30209757 -0.05922358 533 | v -0.20443768 0.48104603 -0.13268899 534 | v -0.10796920 -0.33932890 0.26526442 535 | v -0.02126200 -0.37335341 -0.22322347 536 | v -0.11366264 0.27497723 0.37807672 537 | v -0.00283512 0.25606712 0.29261837 538 | v 0.26325128 0.10876642 -0.07122169 539 | v -0.17967480 0.48437693 0.09844032 540 | v 0.01313673 -0.44658963 -0.16059644 541 | v -0.20693111 -0.26830523 -0.17988644 542 | v 0.05597577 -0.42441111 0.06831314 543 | v 0.05931145 -0.42232700 0.10523981 544 | v 0.25601673 0.31538177 0.02336052 545 | v -0.21248375 0.47996229 -0.18010430 546 | v -0.22447990 0.20503428 0.44578271 547 | v 0.14263434 -0.16601950 0.14946781 548 | v -0.12402806 0.01211354 0.39899260 549 | v -0.17905014 0.48290594 0.24854421 550 | v 0.22008202 -0.06677168 0.08961825 551 | v 0.28916875 0.19264620 0.06620253 552 | v -0.08823413 -0.04461853 0.38304572 553 | v 0.04900747 0.27990492 0.24540865 554 | v 0.09375372 0.32606272 0.18501827 555 | v 0.13835660 -0.17985963 -0.13241251 556 | v 0.00802444 0.15265056 0.30857275 557 | v -0.00561524 -0.25265046 -0.27451458 558 | v -0.26116212 0.12121834 0.31085784 559 | v -0.22157940 -0.29206547 -0.02612666 560 | v -0.27241587 0.17682954 0.28153424 561 | v 0.05876109 0.32114157 0.21987532 562 | v 0.08963981 0.34441054 0.17281042 563 | v -0.04138611 -0.49236616 0.19024441 564 | v -0.18942323 -0.41151188 -0.11469823 565 | v -0.11351410 0.28574851 -0.35682714 566 | v -0.25111922 -0.08872527 -0.01876860 567 | v 0.17285007 0.01703303 -0.17602288 568 | v -0.15810417 0.31514159 -0.38544683 569 | v -0.00641904 -0.44937255 -0.17820785 570 | v 0.02176912 -0.11052620 0.33674247 571 | v -0.10988656 -0.31598229 -0.25765662 572 | v -0.26112624 0.19665510 -0.38199524 573 | v 0.20878992 0.34225496 -0.03949528 574 | v 0.21424784 0.17477235 -0.11879739 575 | v -0.17040752 -0.49413684 -0.13286714 576 | v -0.21421423 0.40323835 0.39885119 577 | v 0.07102609 -0.21860662 0.23025071 578 | v 0.26069905 0.08500226 -0.06453652 579 | v -0.23811694 0.31653800 0.44808727 580 | v -0.23350638 0.46834854 0.03445882 581 | v -0.20458094 0.28450312 -0.43119409 582 | v -0.27273582 0.19204033 0.32080354 583 | v -0.23734382 0.25372962 0.45709846 584 | v 0.05590675 -0.16251958 0.29365351 585 | v 0.21516947 -0.12289153 0.06030904 586 | v 0.19867540 -0.08922717 0.10896566 587 | v -0.12440172 0.49664114 -0.02376211 588 | v -0.22744684 -0.16829104 0.17050623 589 | v -0.18666347 -0.39291127 -0.16042036 590 | v -0.22790763 -0.08784627 0.27491437 591 | v -0.23392106 0.26822952 -0.44153867 592 | v 0.00671382 -0.50645985 0.13114499 593 | v -0.21524586 0.31436054 -0.43253423 594 | v -0.08217194 0.48179949 0.17581784 595 | v -0.25044586 -0.07782630 0.10240790 596 | v 0.24596119 -0.05496560 0.05459537 597 | v -0.25472617 0.44508099 0.27702944 598 | v -0.19883625 -0.17782162 -0.27236622 599 | v -0.26349929 0.42802107 -0.26028652 600 | v -0.04843368 -0.45262124 -0.19570858 601 | v -0.05875560 -0.18394053 -0.32324638 602 | v -0.21331559 -0.05827122 0.33428615 603 | v -0.01207655 -0.46228749 0.19339057 604 | v 0.18718105 0.10228605 0.17593668 605 | v -0.25706343 0.43115342 -0.28958149 606 | v -0.21298790 0.02808432 0.37665257 607 | v -0.28929894 0.27895543 -0.01218124 608 | v -0.20667494 -0.23899503 0.22550937 609 | v 0.10363035 0.42226201 0.07294856 610 | v -0.03923045 0.50798623 0.06365932 611 | v -0.17147493 -0.43781888 0.18286422 612 | v -0.25849445 0.07964373 0.26269177 613 | v 0.23261339 0.06385479 0.11666758 614 | v -0.14795620 -0.43749619 -0.18957022 615 | v 0.01851292 0.39863440 -0.16843894 616 | v -0.26917988 0.41101018 0.06138221 617 | v -0.09613412 0.45171480 0.24116619 618 | v -0.01450340 -0.48810600 -0.16361696 619 | v 0.17842589 -0.19949319 -0.05471841 620 | v 0.27816349 0.03505459 -0.01993685 621 | v -0.14315619 -0.44910652 0.20461478 622 | v -0.27679843 0.21055850 -0.18546731 623 | v -0.20848995 0.34390901 0.43407477 624 | v -0.06526818 -0.42799468 0.22779106 625 | v 0.22250654 -0.13561684 -0.00920246 626 | v 0.14662558 -0.23292518 0.09908207 627 | v -0.26145633 0.04920171 0.11728982 628 | v -0.14069857 0.08003327 0.41428609 629 | v -0.04915297 -0.02180912 -0.35348448 630 | v -0.00777312 -0.41128979 -0.19857553 631 | v -0.22760800 -0.22361824 0.09177078 632 | v 0.00013217 0.46223422 -0.11548684 633 | v -0.22565215 0.46800344 0.29054159 634 | v -0.20335711 0.47188523 0.29585361 635 | v -0.28574559 0.31705672 0.08333586 636 | v -0.03932767 -0.25397958 0.30285669 637 | v -0.16314462 0.11259623 -0.40729851 638 | v 0.15339682 -0.23990287 -0.06912460 639 | v -0.26275422 0.07648245 -0.22051266 640 | v -0.23642718 0.46647040 0.25279995 641 | v -0.09481054 -0.41989677 0.23118770 642 | v 0.27429026 0.10420945 0.06829268 643 | v 0.00574114 -0.28383433 -0.25382693 644 | v -0.24121182 -0.04753771 0.22610209 645 | v -0.07732317 -0.17314898 -0.32951839 646 | v -0.27237725 0.22743650 0.37387292 647 | v -0.02522205 -0.51040586 0.15532774 648 | v -0.26215261 0.01419703 0.01055387 649 | v 0.01181860 0.10819137 -0.29816618 650 | v -0.13706076 -0.51056719 -0.12964873 651 | v -0.23589467 -0.08863165 -0.19669194 652 | v 0.08136613 -0.38413599 -0.08176043 653 | v -0.23901531 0.45567822 0.30178358 654 | v -0.22562651 0.27872656 0.46040942 655 | v -0.22748763 -0.13784450 0.21153344 656 | v -0.10491739 -0.29219082 0.28625051 657 | v 0.03864741 -0.27915336 -0.22826602 658 | v -0.04382554 -0.39084098 0.24000769 659 | v -0.20342874 -0.30539178 -0.14755895 660 | v -0.21714266 -0.29628307 0.07016290 661 | v -0.21701978 -0.18882044 0.22706549 662 | v -0.11343706 -0.05679598 0.38209478 663 | v -0.20515930 -0.27582333 0.19518111 664 | v 0.01386407 -0.41246961 0.19685204 665 | v -0.20096601 0.44717281 -0.32550462 666 | v -0.12672857 0.50027782 -0.10150801 667 | v -0.04278361 -0.49767714 -0.17197794 668 | v -0.21572952 -0.27471541 0.11072091 669 | v -0.07603288 -0.10741209 0.37104844 670 | v -0.23961943 0.03813124 -0.32687694 671 | v 0.18047913 0.08537778 -0.16676768 672 | v -0.03072854 -0.51050657 -0.13449934 673 | v 0.04075178 0.33530323 -0.20833325 674 | v -0.10144598 -0.01355568 0.39061376 675 | v 0.14956714 -0.26282447 -0.03699979 676 | v 0.06507434 0.45341645 -0.05437092 677 | v -0.05745494 0.06127853 -0.36178771 678 | v -0.01088342 -0.42392330 0.21170350 679 | v 0.10380970 0.35605960 -0.12977567 680 | v 0.01353386 0.45965136 0.12481682 681 | v -0.13172053 0.02213467 -0.38494492 682 | v 0.18168491 0.30796316 -0.10094672 683 | v -0.16472405 0.39547630 -0.35010324 684 | v -0.03141460 -0.16410109 0.34310881 685 | v -0.22720340 -0.11473427 0.24735292 686 | v -0.26255444 0.27127063 0.43415220 687 | v -0.25111124 0.07580102 -0.31386587 688 | v -0.24719699 0.36505328 0.41547636 689 | v -0.00986963 -0.50469688 0.16279099 690 | v -0.10218578 -0.09556772 0.37057386 691 | v -0.10198354 0.50494018 0.02681506 692 | v 0.08483246 -0.37167545 0.10397621 693 | v -0.14584657 0.49514214 0.10121132 694 | v -0.24121169 -0.14477463 -0.08272205 695 | v -0.11144017 0.50181535 0.05142214 696 | v -0.27528017 0.38600164 0.05404691 697 | v -0.05835601 0.04222367 0.37983287 698 | v -0.28296016 0.29629412 -0.32082097 699 | v -0.01925456 0.48302198 -0.09904971 700 | v 0.26522288 0.28374351 -0.04064546 701 | v 0.01380273 -0.41564469 -0.17767734 702 | v 0.07674033 -0.05410166 0.30094691 703 | v 0.08885425 -0.19987294 0.21421752 704 | v -0.12235854 -0.45109919 -0.19438905 705 | v -0.08421710 -0.36078005 -0.24168983 706 | v -0.26166094 0.02580901 0.08939704 707 | v -0.24641101 -0.12807361 -0.02328124 708 | v 0.21393843 0.25802124 -0.09876102 709 | v -0.17399117 -0.38021292 -0.19494828 710 | v -0.26112077 0.34659639 -0.39119771 711 | v 0.01117108 -0.44440409 0.18190431 712 | v -0.23000140 0.41781607 0.37885024 713 | v 0.09268404 -0.12953977 0.25410519 714 | v -0.01056076 -0.13731959 0.34520329 715 | v 0.27069621 0.20626300 -0.06434565 716 | v -0.18915147 -0.31617419 -0.20772608 717 | v 0.09857205 -0.28686900 -0.12708648 718 | v 0.15635637 0.15000513 0.18873882 719 | v -0.21385803 -0.10910048 0.30364937 720 | v 0.01270966 -0.50025877 0.08202675 721 | v -0.25073226 -0.02811812 0.18239441 722 | v 0.00750006 -0.33152521 -0.22922477 723 | v -0.27155786 0.13382598 0.10852365 724 | v -0.26218314 0.04743744 0.17629390 725 | v -0.12501577 0.03642699 -0.38657385 726 | v 0.16613047 -0.07951466 0.16674655 727 | v 0.13288375 0.26757120 0.18030802 728 | v 0.19315128 -0.08260039 -0.10713710 729 | v -0.28182088 0.21019942 -0.00826876 730 | v 0.29825465 0.24580863 0.02836050 731 | v -0.27149894 0.12238272 0.06142629 732 | v -0.26622278 0.11142962 -0.24694930 733 | v -0.26290758 0.14993070 -0.32816010 734 | v -0.18229062 -0.46017083 0.07223278 735 | v -0.05953093 -0.34850381 -0.24642957 736 | v -0.01824325 0.28747366 -0.27941511 737 | v -0.25141573 -0.06072285 -0.11472944 738 | v -0.19879493 0.42873987 0.36732221 739 | v 0.09745339 -0.06801469 0.27638170 740 | v -0.25069380 -0.09549940 0.02440272 741 | v 0.10996924 0.32389617 -0.15445565 742 | v -0.00143172 -0.50969388 0.12607692 743 | v -0.12810307 0.20826459 -0.38734143 744 | v -0.19548483 0.16244865 0.43781813 745 | v 0.10078397 0.43189039 -0.02096315 746 | v -0.20525635 -0.16256802 0.28802728 747 | v 0.06956222 0.13968929 0.26174128 748 | v -0.21394754 0.47718695 0.27198508 749 | v -0.27470146 0.14603428 -0.01705510 750 | v 0.12002495 -0.28796839 0.10669780 751 | v -0.13355585 0.49194896 0.18524907 752 | v 0.20421823 0.30583914 0.10089726 753 | v -0.24142145 -0.12340235 0.11711505 754 | v -0.20202049 0.48413704 -0.22216340 755 | v 0.30789567 0.20528536 -0.00717007 756 | v 0.29576896 0.11800243 -0.02263976 757 | v 0.06116032 0.44461783 0.09709456 758 | v -0.25390912 0.44692336 -0.23755181 759 | v -0.15864896 -0.50211469 0.16616348 760 | v 0.28383937 0.03313021 0.00628776 761 | v -0.12378175 0.44495415 -0.25709072 762 | v 0.05670849 -0.12835357 0.31387769 763 | v -0.28271167 0.27760402 -0.31692078 764 | v -0.28073791 0.34777206 -0.27174915 765 | v -0.25047103 -0.05258559 0.14889054 766 | v -0.28567358 0.31214946 -0.11171428 767 | v 0.15933378 -0.23819288 0.07268713 768 | v 0.03826370 -0.45899034 0.10424463 769 | v -0.18126142 -0.39036253 0.19237098 770 | v -0.10752062 -0.40270447 0.23766697 771 | v -0.22713661 0.01203096 0.35348600 772 | v -0.25340988 0.30345489 0.44061447 773 | v -0.18327100 -0.45580539 -0.09033531 774 | v -0.08233276 -0.13708821 0.36104708 775 | v -0.27752508 0.27854213 0.38235174 776 | v 0.19112388 -0.17268678 0.06697264 777 | v 0.03462596 -0.28239815 0.24888117 778 | v -0.00402492 0.09005616 0.33334087 779 | v -0.27260987 0.18193013 0.23194586 780 | v -0.23510843 0.04189319 0.35545030 781 | v -0.18288699 -0.33999913 0.22190267 782 | v 0.01181894 -0.03614074 0.34458604 783 | v -0.15526714 0.06961188 -0.39681326 784 | v 0.05192390 -0.30307224 0.20602189 785 | v -0.25043770 -0.06177556 0.09984918 786 | v -0.22954297 -0.19244662 -0.10624356 787 | v 0.04313031 -0.16354582 -0.29022096 788 | v 0.08104392 -0.15596310 0.25579110 789 | v -0.13702456 0.07474584 -0.39453312 790 | v 0.15472073 0.00482325 0.21435198 791 | v -0.26176055 0.37045826 0.38963529 792 | v -0.24599863 -0.11822269 0.03996143 793 | v 0.26208457 -0.05526933 0.01857161 794 | v -0.00062006 0.28103526 0.28578744 795 | v -0.06991670 0.43245709 0.23725891 796 | v -0.20287542 -0.01749893 0.36185147 797 | v 0.25602250 0.18485454 0.09967798 798 | v -0.15202613 -0.50847755 0.15962134 799 | v 0.17966285 -0.14257243 -0.09003206 800 | v -0.28355379 0.33241448 -0.13954292 801 | v -0.04512562 -0.34444778 0.26219597 802 | v -0.27243058 0.14094708 0.06761635 803 | v 0.13652430 0.20408522 0.19199171 804 | v 0.02730703 -0.23622620 0.28243510 805 | v -0.13650481 0.47576793 -0.21685571 806 | v -0.24772400 -0.04909364 -0.16266534 807 | v 0.29375440 0.25908590 -0.00477060 808 | v 0.16470224 -0.12800507 0.13854121 809 | v -0.22588464 0.47586120 -0.23436908 810 | v -0.24822831 0.06946170 0.33279859 811 | v 0.13390801 -0.14233586 0.17889750 812 | v -0.18262319 -0.18375317 -0.28238553 813 | v 0.13927633 -0.04446689 0.21991464 814 | v 0.08821713 0.44018991 0.05235171 815 | v 0.05288401 -0.33584812 0.18316879 816 | v 0.09708242 0.27039000 0.20858021 817 | v -0.11325998 0.39799316 -0.29929160 818 | v 0.02399186 -0.47258299 0.14084965 819 | v -0.24856110 0.14075309 0.39048589 820 | v -0.17840430 0.37899757 0.39150529 821 | v 0.15124690 -0.20707318 0.10862101 822 | v 0.12613075 0.01082024 -0.22779861 823 | v -0.09987373 0.06152666 0.39811060 824 | v -0.26163501 0.17846995 0.38019756 825 | v -0.20253997 0.48344568 0.20357566 826 | v -0.20955180 -0.33559342 0.05485950 827 | v 0.18151636 0.32634486 -0.08532037 828 | v -0.05488683 -0.00799996 0.37446764 829 | v 0.30832085 0.21260775 0.01930162 830 | v -0.02968363 0.06326309 0.36160944 831 | v 0.16037077 -0.24500726 0.05166355 832 | v 0.22795989 -0.11758161 0.03794139 833 | v 0.17094869 -0.03389182 0.17537313 834 | v -0.18282026 -0.42112558 0.16019153 835 | v 0.23283414 -0.09369084 0.05040286 836 | v -0.21930794 0.47624667 0.23781472 837 | v 0.23765974 0.22459545 -0.08756708 838 | v -0.02039850 -0.49556667 0.18046113 839 | v 0.28049366 0.05729257 0.04268218 840 | v -0.26147113 0.02191590 -0.07379946 841 | v 0.24341975 -0.01981118 0.06999440 842 | v 0.02792151 0.48131025 0.06284741 843 | v 0.19996120 -0.13137990 0.07927001 844 | v -0.05497128 -0.39714867 -0.22314904 845 | v -0.05953832 0.48418617 -0.13254210 846 | v -0.28496168 0.32292197 -0.05740177 847 | v -0.26061096 0.09850476 0.28298781 848 | v 0.13826008 0.31734685 0.14971917 849 | v 0.12858329 0.11091688 0.22068787 850 | v 0.04490969 0.13291240 -0.26522522 851 | v -0.17445159 -0.49354371 -0.11175719 852 | v -0.18747175 -0.43529294 0.09699659 853 | v -0.20908071 0.38275626 -0.39724223 854 | v -0.12246566 0.49897525 0.12388852 855 | v 0.29576410 0.13948074 0.04989131 856 | v 0.12349402 -0.29533939 -0.08103790 857 | v -0.07815206 0.08968868 0.39006172 858 | v -0.25009957 0.40882843 0.36941398 859 | v 0.13410011 0.40766366 0.01107026 860 | v 0.13939420 -0.08078759 0.20754500 861 | v -0.05509412 0.32234132 0.31537394 862 | v 0.01974749 -0.00713512 -0.31958068 863 | v 0.06436317 -0.11596410 -0.29217488 864 | v -0.21660707 -0.24311398 0.15104987 865 | v 0.26446557 -0.01182854 -0.02367746 866 | v 0.10161374 -0.34810933 0.08133041 867 | v -0.22008098 -0.22983496 -0.12997545 868 | v 0.19223518 0.28108881 0.12564231 869 | v -0.21850658 0.35213914 -0.41779854 870 | v 0.01248991 -0.50341132 -0.11166817 871 | v -0.26244385 0.42920229 0.29037302 872 | v -0.07808801 0.43925836 -0.22119358 873 | v -0.26901280 0.09820824 -0.02242808 874 | v 0.27105504 0.29622522 -0.00982454 875 | v -0.24202016 0.46121810 0.26244371 876 | v -0.14631456 0.49365837 0.16742431 877 | v 0.22268384 0.08459748 0.13949064 878 | v -0.27261983 0.15763130 -0.17432476 879 | v -0.22321951 0.24458522 -0.44026786 880 | v 0.17859297 0.26084662 0.14473574 881 | v 0.11127777 0.27174918 -0.17829584 882 | v -0.02535198 -0.24216045 -0.28735761 883 | v 0.17983202 -0.20853170 0.04367466 884 | v -0.21458718 0.16768439 0.43588834 885 | v -0.21127966 0.30083274 0.45197312 886 | v -0.19355678 -0.37842182 0.13929039 887 | v -0.20568861 -0.32330522 0.12428603 888 | v 0.20947427 0.28698327 -0.09043979 889 | v -0.16858397 -0.47618456 0.16417506 890 | v 0.25109732 -0.07656556 0.02986202 891 | v -0.02736924 0.18560380 -0.31111677 892 | v -0.24806172 0.37697713 -0.38896608 893 | v -0.05467932 0.09591440 -0.35501459 894 | v -0.27224348 0.15961893 0.16851941 895 | v -0.19410972 -0.34649622 0.18068590 896 | v 0.10802148 -0.31982863 0.10527411 897 | v -0.19721499 -0.38454665 0.00054586 898 | v -0.13742107 0.03213556 0.40563537 899 | v -0.04757275 -0.05227590 0.36747183 900 | v 0.18782693 -0.00974352 0.15861589 901 | v -0.21772059 0.47420482 -0.26937736 902 | v -0.01642267 -0.18454120 -0.31158642 903 | v -0.02615066 0.34739580 -0.25989183 904 | v -0.20561434 0.48138898 0.24584759 905 | v 0.08289510 -0.32647625 0.14081920 906 | v 0.16747494 -0.08840578 -0.14470012 907 | v 0.05769447 -0.42571084 -0.08320504 908 | v -0.21484569 0.47806296 0.11936861 909 | v -0.15126553 0.26334289 0.41120448 910 | v -0.22857241 -0.11902029 -0.21767125 911 | v -0.22784025 -0.16968138 -0.14984267 912 | v -0.28343833 0.27673565 -0.26075997 913 | v -0.27224345 0.14406239 -0.11651924 914 | v -0.20008473 -0.06657665 0.34203216 915 | v 0.28238567 0.26514398 -0.02952951 916 | v 0.15163112 -0.03762202 -0.18566481 917 | v -0.04625176 0.28832112 0.32071794 918 | v 0.18380776 0.37160723 0.03396138 919 | v -0.10121240 0.02649261 0.39535897 920 | v -0.13645592 -0.50130729 0.18428872 921 | v 0.22733800 0.13725478 0.13336740 922 | v -0.17486103 -0.22771061 0.28440231 923 | v -0.21238551 -0.27530539 -0.12319397 924 | v -0.04194327 -0.29561822 0.28452224 925 | v -0.22015510 0.46006486 0.31833692 926 | v -0.25531926 -0.02557181 -0.05006997 927 | v -0.28151318 0.31399255 -0.07703474 928 | v 0.03048682 -0.20357181 -0.27897197 929 | v -0.18431254 0.21906711 -0.42508519 930 | v 0.04306923 -0.22291618 0.27386022 931 | v 0.00901867 0.34200616 0.25013361 932 | v 0.00575979 -0.49592758 -0.13958492 933 | v -0.24093204 0.08660662 -0.35649556 934 | v 0.10281708 0.31009276 -0.16952485 935 | v 0.15964909 0.38357660 -0.03908638 936 | v 0.31000046 0.17686067 0.02858093 937 | v -0.16838673 -0.50424527 -0.11351286 938 | v -0.24160017 0.43308230 -0.33009694 939 | v 0.16017903 0.26228855 -0.14158776 940 | v 0.25985519 0.06269953 0.07472941 941 | v -0.16531473 0.11008574 0.42466214 942 | v -0.19544593 -0.31156533 0.20867731 943 | v 0.06447344 -0.18815814 0.26180451 944 | v -0.23422992 0.22272050 0.44970675 945 | v -0.24471312 0.45262486 -0.27938390 946 | v -0.03809552 -0.50548371 0.17838301 947 | v -0.17340943 -0.49396959 0.10569943 948 | v 0.04244465 -0.23556094 -0.25027781 949 | v -0.12312078 0.42956737 -0.27570894 950 | v 0.05535474 -0.42592849 -0.05633489 951 | v -0.17255182 -0.49587375 0.13400388 952 | v -0.18628622 0.14767928 -0.41602688 953 | v 0.18008050 0.37563136 -0.01008920 954 | v 0.18965747 -0.18937103 0.00539218 955 | v -0.24995853 0.24141299 0.44439679 956 | v 0.13375220 0.40352405 -0.04409065 957 | v -0.08447500 -0.22478815 -0.30458551 958 | v -0.04939532 0.49350983 -0.09897918 959 | v 0.29020660 0.23122056 0.05802310 960 | v -0.08101131 -0.06290138 -0.35952129 961 | v -0.18912779 -0.23640090 0.26928378 962 | v -0.26222347 0.05187063 -0.18115638 963 | v -0.17636619 -0.13038077 -0.30811710 964 | v -0.25413642 -0.01548557 -0.16430528 965 | v 0.22314742 0.31454540 -0.05727778 966 | v 0.02332406 -0.28829832 -0.23983504 967 | v -0.20724490 -0.09402565 -0.30543386 968 | v -0.14279433 0.38171807 0.35838363 969 | v 0.13311479 0.38172808 -0.07404867 970 | v 0.10169847 -0.21031720 0.18675792 971 | v -0.19503881 0.41434250 -0.36241204 972 | v -0.17532937 -0.41913359 -0.17107880 973 | v -0.20607753 -0.35573306 0.00286034 974 | v 0.09810935 -0.27615033 0.14954424 975 | v -0.15680265 -0.50862967 -0.11856317 976 | v -0.24217608 -0.08271827 -0.16321318 977 | v 0.21594104 0.20733039 0.12688387 978 | v -0.02620935 0.49670516 0.09481066 979 | v -0.20701191 -0.35755670 0.06050973 980 | v -0.22194439 0.11895036 -0.39398349 981 | v -0.27351303 0.20403642 -0.31660671 982 | v -0.15865607 0.49246210 -0.07104189 983 | v -0.18419719 -0.44972644 0.11594350 984 | v -0.27178246 0.31718389 -0.37923177 985 | v 0.12165370 -0.31192269 0.06376813 986 | v -0.18291462 0.47467779 0.27820011 987 | v 0.05201915 -0.04154501 0.32036538 988 | v -0.08095237 0.45294083 0.22439393 989 | v 0.09015803 -0.02570419 -0.26680037 990 | v 0.00035324 0.31695929 0.27096480 991 | v -0.08868100 0.36438927 0.32253219 992 | v 0.04047612 -0.03971276 -0.31095047 993 | v 0.09694204 -0.10210074 0.26465529 994 | v -0.22206211 0.45693593 -0.30619305 995 | v -0.25016579 -0.07723123 0.03896744 996 | v -0.18328579 0.48350855 -0.22774179 997 | v -0.16623618 0.41870757 0.34590363 998 | v 0.10658814 -0.33485741 0.03984540 999 | v -0.28504833 0.25383667 0.13156662 1000 | v -0.24791809 0.42639335 0.34769729 1001 | v -0.25315073 0.04000841 0.25452732 1002 | v 0.18074367 -0.20844384 -0.03136446 1003 | v -0.22684673 -0.19435377 0.13545618 1004 | f 971 588 529 1005 | f 971 529 708 1006 | f 574 936 850 1007 | f 93 574 850 1008 | f 971 574 93 1009 | f 93 850 772 1010 | f 93 772 563 1011 | f 850 936 423 1012 | f 850 423 946 1013 | f 850 174 772 1014 | f 93 563 588 1015 | f 971 93 588 1016 | f 563 772 364 1017 | f 588 563 260 1018 | f 588 260 524 1019 | f 563 364 260 1020 | f 260 364 22 1021 | f 772 48 66 1022 | f 772 66 364 1023 | f 772 733 48 1024 | f 66 48 173 1025 | f 66 896 364 1026 | f 364 383 22 1027 | f 66 138 896 1028 | f 66 173 138 1029 | f 364 896 972 1030 | f 708 613 74 1031 | f 414 74 613 1032 | f 414 613 703 1033 | f 414 703 83 1034 | f 56 414 83 1035 | f 613 708 404 1036 | f 613 404 140 1037 | f 83 140 704 1038 | f 83 704 843 1039 | f 140 703 613 1040 | f 703 140 83 1041 | f 140 570 704 1042 | f 56 666 26 1043 | f 56 599 666 1044 | f 83 599 56 1045 | f 599 215 666 1046 | f 215 599 568 1047 | f 83 843 599 1048 | f 599 843 629 1049 | f 843 534 629 1050 | f 704 734 843 1051 | f 843 734 413 1052 | f 413 534 843 1053 | f 599 629 568 1054 | f 568 629 700 1055 | f 534 167 629 1056 | f 629 167 700 1057 | f 74 936 574 1058 | f 74 974 936 1059 | f 74 95 974 1060 | f 95 649 974 1061 | f 74 26 95 1062 | f 74 414 26 1063 | f 74 574 971 1064 | f 74 971 708 1065 | f 26 414 56 1066 | f 26 281 95 1067 | f 95 671 649 1068 | f 936 974 423 1069 | f 26 666 281 1070 | f 281 671 95 1071 | f 281 666 617 1072 | f 281 617 261 1073 | f 281 261 671 1074 | f 261 617 931 1075 | f 261 931 869 1076 | f 671 261 348 1077 | f 261 869 348 1078 | f 931 206 869 1079 | f 348 869 291 1080 | f 666 215 617 1081 | f 215 568 617 1082 | f 617 539 931 1083 | f 617 568 539 1084 | f 931 539 295 1085 | f 931 295 206 1086 | f 568 700 539 1087 | f 671 646 649 1088 | f 671 348 646 1089 | f 646 348 741 1090 | f 200 427 234 1091 | f 191 87 60 1092 | f 191 60 200 1093 | f 60 87 518 1094 | f 200 445 427 1095 | f 200 60 445 1096 | f 427 445 959 1097 | f 427 959 244 1098 | f 60 302 445 1099 | f 518 302 60 1100 | f 445 302 959 1101 | f 959 302 628 1102 | f 302 463 628 1103 | f 244 959 255 1104 | f 959 628 255 1105 | f 463 172 628 1106 | f 715 708 529 1107 | f 588 524 529 1108 | f 708 715 99 1109 | f 708 82 404 1110 | f 708 99 82 1111 | f 82 150 404 1112 | f 715 529 99 1113 | f 529 540 99 1114 | f 597 150 82 1115 | f 811 150 597 1116 | f 359 866 201 1117 | f 201 866 910 1118 | f 82 99 454 1119 | f 99 540 454 1120 | f 454 540 201 1121 | f 540 359 201 1122 | f 454 597 82 1123 | f 524 260 22 1124 | f 524 22 658 1125 | f 658 22 219 1126 | f 529 524 658 1127 | f 658 219 922 1128 | f 658 922 540 1129 | f 529 658 540 1130 | f 219 265 922 1131 | f 383 430 558 1132 | f 265 558 235 1133 | f 22 383 219 1134 | f 364 972 383 1135 | f 219 383 265 1136 | f 383 972 825 1137 | f 383 825 430 1138 | f 265 383 558 1139 | f 922 265 866 1140 | f 265 311 866 1141 | f 866 785 910 1142 | f 866 311 785 1143 | f 311 474 785 1144 | f 540 922 359 1145 | f 922 866 359 1146 | f 265 235 311 1147 | f 311 235 474 1148 | f 235 558 195 1149 | f 235 195 337 1150 | f 235 337 474 1151 | f 785 474 447 1152 | f 195 55 337 1153 | f 57 381 439 1154 | f 597 454 381 1155 | f 966 597 57 1156 | f 811 597 966 1157 | f 811 966 962 1158 | f 597 381 57 1159 | f 403 962 966 1160 | f 454 201 381 1161 | f 381 201 909 1162 | f 201 910 32 1163 | f 909 201 32 1164 | f 439 381 909 1165 | f 909 32 650 1166 | f 32 324 650 1167 | f 966 57 92 1168 | f 522 370 57 1169 | f 57 439 522 1170 | f 92 403 966 1171 | f 92 274 403 1172 | f 370 92 57 1173 | f 522 73 370 1174 | f 403 274 191 1175 | f 909 650 439 1176 | f 439 91 522 1177 | f 650 91 439 1178 | f 650 975 91 1179 | f 91 975 805 1180 | f 522 91 73 1181 | f 73 91 151 1182 | f 91 179 151 1183 | f 91 805 179 1184 | f 805 963 179 1185 | f 910 785 324 1186 | f 785 693 324 1187 | f 32 910 324 1188 | f 650 324 975 1189 | f 324 693 975 1190 | f 693 114 975 1191 | f 447 474 337 1192 | f 693 447 114 1193 | f 447 706 114 1194 | f 785 447 693 1195 | f 447 337 706 1196 | f 337 55 706 1197 | f 55 791 706 1198 | f 114 706 565 1199 | f 706 739 565 1200 | f 975 736 805 1201 | f 975 114 736 1202 | f 736 114 139 1203 | f 805 736 963 1204 | f 963 736 19 1205 | f 139 114 839 1206 | f 736 139 19 1207 | f 114 565 925 1208 | f 925 565 106 1209 | f 565 120 106 1210 | f 565 994 120 1211 | f 114 925 839 1212 | f 365 500 570 1213 | f 404 570 140 1214 | f 404 365 570 1215 | f 150 365 404 1216 | f 570 500 704 1217 | f 365 150 492 1218 | f 150 811 492 1219 | f 492 811 72 1220 | f 811 121 72 1221 | f 492 72 365 1222 | f 365 4 500 1223 | f 365 956 4 1224 | f 365 72 956 1225 | f 956 72 644 1226 | f 234 644 72 1227 | f 4 881 290 1228 | f 249 290 556 1229 | f 249 556 642 1230 | f 704 4 734 1231 | f 704 500 4 1232 | f 734 4 290 1233 | f 734 249 413 1234 | f 734 290 249 1235 | f 534 413 721 1236 | f 413 249 721 1237 | f 249 642 721 1238 | f 534 721 167 1239 | f 721 642 965 1240 | f 4 956 425 1241 | f 425 881 4 1242 | f 481 881 425 1243 | f 956 600 425 1244 | f 956 644 600 1245 | f 425 600 481 1246 | f 481 600 901 1247 | f 290 881 556 1248 | f 556 512 642 1249 | f 881 481 901 1250 | f 881 8 556 1251 | f 881 901 8 1252 | f 556 8 927 1253 | f 121 811 962 1254 | f 403 200 962 1255 | f 962 200 121 1256 | f 121 234 72 1257 | f 234 333 644 1258 | f 200 234 121 1259 | f 234 427 333 1260 | f 333 427 244 1261 | f 200 403 191 1262 | f 644 77 600 1263 | f 644 333 77 1264 | f 600 458 901 1265 | f 600 77 458 1266 | f 333 244 77 1267 | f 244 255 77 1268 | f 77 255 371 1269 | f 458 334 901 1270 | f 901 334 8 1271 | f 458 77 371 1272 | f 458 39 334 1273 | f 458 371 39 1274 | f 371 467 39 1275 | f 255 628 172 1276 | f 172 47 255 1277 | f 371 255 467 1278 | f 255 47 467 1279 | f 467 47 861 1280 | f 946 423 950 1281 | f 850 946 174 1282 | f 174 733 772 1283 | f 946 733 174 1284 | f 851 733 982 1285 | f 733 946 982 1286 | f 946 950 982 1287 | f 982 950 833 1288 | f 950 888 610 1289 | f 950 610 833 1290 | f 173 510 138 1291 | f 733 851 48 1292 | f 48 851 510 1293 | f 48 510 173 1294 | f 138 978 896 1295 | f 896 978 972 1296 | f 510 280 138 1297 | f 138 280 978 1298 | f 280 310 978 1299 | f 851 982 833 1300 | f 510 851 885 1301 | f 851 833 885 1302 | f 833 610 768 1303 | f 510 885 280 1304 | f 885 894 280 1305 | f 885 833 894 1306 | f 833 768 894 1307 | f 610 278 768 1308 | f 894 768 780 1309 | f 768 278 780 1310 | f 974 649 797 1311 | f 423 974 797 1312 | f 649 189 797 1313 | f 423 758 950 1314 | f 423 797 758 1315 | f 950 758 888 1316 | f 797 919 758 1317 | f 797 189 919 1318 | f 758 919 620 1319 | f 888 758 610 1320 | f 649 646 189 1321 | f 919 499 267 1322 | f 919 78 499 1323 | f 189 78 919 1324 | f 189 646 78 1325 | f 348 291 741 1326 | f 741 291 591 1327 | f 591 291 719 1328 | f 78 945 499 1329 | f 78 646 945 1330 | f 945 837 562 1331 | f 646 741 688 1332 | f 741 591 688 1333 | f 591 376 16 1334 | f 945 688 837 1335 | f 646 688 945 1336 | f 591 16 688 1337 | f 688 16 837 1338 | f 376 817 16 1339 | f 16 710 837 1340 | f 837 710 602 1341 | f 817 710 16 1342 | f 758 278 610 1343 | f 758 620 278 1344 | f 620 919 267 1345 | f 267 499 640 1346 | f 499 623 640 1347 | f 278 620 237 1348 | f 620 769 237 1349 | f 237 769 533 1350 | f 620 267 769 1351 | f 267 640 769 1352 | f 533 769 9 1353 | f 640 123 769 1354 | f 769 123 9 1355 | f 945 562 499 1356 | f 499 562 52 1357 | f 499 52 623 1358 | f 837 602 562 1359 | f 562 602 52 1360 | f 52 602 677 1361 | f 52 677 49 1362 | f 710 663 602 1363 | f 52 49 623 1364 | f 623 49 657 1365 | f 640 623 657 1366 | f 640 657 123 1367 | f 123 657 800 1368 | f 677 602 663 1369 | f 677 663 402 1370 | f 677 402 49 1371 | f 657 49 462 1372 | f 49 402 462 1373 | f 462 402 207 1374 | f 657 462 800 1375 | f 430 659 558 1376 | f 558 659 452 1377 | f 972 978 825 1378 | f 430 825 659 1379 | f 825 275 659 1380 | f 825 978 275 1381 | f 978 310 275 1382 | f 659 275 667 1383 | f 280 886 310 1384 | f 310 886 275 1385 | f 280 894 941 1386 | f 280 941 886 1387 | f 886 941 275 1388 | f 667 275 863 1389 | f 941 662 275 1390 | f 558 452 195 1391 | f 195 452 148 1392 | f 452 659 148 1393 | f 195 148 55 1394 | f 659 238 148 1395 | f 238 630 148 1396 | f 55 148 232 1397 | f 148 630 23 1398 | f 148 367 232 1399 | f 148 23 367 1400 | f 659 667 238 1401 | f 667 863 238 1402 | f 630 1002 23 1403 | f 630 238 1002 1404 | f 863 357 238 1405 | f 238 587 1002 1406 | f 275 357 863 1407 | f 894 780 941 1408 | f 278 186 780 1409 | f 278 237 186 1410 | f 941 287 662 1411 | f 941 780 287 1412 | f 780 960 287 1413 | f 780 186 960 1414 | f 287 960 343 1415 | f 960 186 921 1416 | f 357 660 238 1417 | f 275 607 357 1418 | f 275 662 607 1419 | f 662 287 607 1420 | f 287 343 607 1421 | f 607 343 284 1422 | f 343 960 284 1423 | f 357 607 660 1424 | f 607 241 660 1425 | f 284 745 607 1426 | f 607 745 241 1427 | f 284 505 745 1428 | f 505 960 307 1429 | f 960 505 284 1430 | f 960 921 307 1431 | f 55 41 791 1432 | f 55 367 41 1433 | f 55 232 367 1434 | f 41 367 791 1435 | f 706 791 739 1436 | f 791 367 152 1437 | f 791 152 739 1438 | f 367 752 152 1439 | f 367 38 752 1440 | f 367 23 38 1441 | f 38 23 426 1442 | f 1002 587 23 1443 | f 587 654 23 1444 | f 752 38 426 1445 | f 752 426 643 1446 | f 426 390 643 1447 | f 426 23 390 1448 | f 565 739 994 1449 | f 994 739 152 1450 | f 152 784 994 1451 | f 152 594 784 1452 | f 152 752 594 1453 | f 994 317 120 1454 | f 994 784 317 1455 | f 752 764 594 1456 | f 594 764 784 1457 | f 752 643 764 1458 | f 764 643 720 1459 | f 784 764 317 1460 | f 764 720 317 1461 | f 587 238 654 1462 | f 654 238 684 1463 | f 238 660 241 1464 | f 238 241 684 1465 | f 23 589 390 1466 | f 23 654 589 1467 | f 390 589 436 1468 | f 684 241 589 1469 | f 654 684 589 1470 | f 241 718 589 1471 | f 589 718 216 1472 | f 241 745 718 1473 | f 505 307 14 1474 | f 745 505 718 1475 | f 505 913 718 1476 | f 505 14 913 1477 | f 14 689 913 1478 | f 390 436 643 1479 | f 589 327 436 1480 | f 589 216 327 1481 | f 643 243 720 1482 | f 720 243 1000 1483 | f 436 327 643 1484 | f 243 643 498 1485 | f 718 601 216 1486 | f 216 601 229 1487 | f 327 28 643 1488 | f 327 216 28 1489 | f 216 229 28 1490 | f 28 229 770 1491 | f 229 601 770 1492 | f 601 605 770 1493 | f 601 795 605 1494 | f 718 913 601 1495 | f 913 689 661 1496 | f 913 795 601 1497 | f 237 533 186 1498 | f 533 655 186 1499 | f 9 123 61 1500 | f 9 61 533 1501 | f 533 61 655 1502 | f 655 61 391 1503 | f 186 307 921 1504 | f 186 655 391 1505 | f 186 391 307 1506 | f 391 61 488 1507 | f 307 391 488 1508 | f 307 488 459 1509 | f 307 459 773 1510 | f 488 194 459 1511 | f 800 462 43 1512 | f 462 207 43 1513 | f 800 43 923 1514 | f 923 43 503 1515 | f 123 800 61 1516 | f 800 923 61 1517 | f 923 635 61 1518 | f 61 194 488 1519 | f 61 635 194 1520 | f 194 635 416 1521 | f 194 416 683 1522 | f 923 107 635 1523 | f 923 503 107 1524 | f 635 107 346 1525 | f 503 803 107 1526 | f 635 346 416 1527 | f 307 689 14 1528 | f 307 773 689 1529 | f 773 668 689 1530 | f 661 166 913 1531 | f 459 194 429 1532 | f 194 683 429 1533 | f 459 429 773 1534 | f 429 683 236 1535 | f 773 429 668 1536 | f 668 429 472 1537 | f 429 236 472 1538 | f 236 713 472 1539 | f 416 476 683 1540 | f 683 713 236 1541 | f 416 346 476 1542 | f 683 476 713 1543 | f 713 51 472 1544 | f 713 569 51 1545 | f 713 476 569 1546 | f 668 472 898 1547 | f 898 472 827 1548 | f 827 472 325 1549 | f 472 51 781 1550 | f 51 569 781 1551 | f 781 325 472 1552 | f 913 166 795 1553 | f 795 166 605 1554 | f 668 203 689 1555 | f 661 409 166 1556 | f 689 203 661 1557 | f 661 203 551 1558 | f 203 898 551 1559 | f 661 551 409 1560 | f 409 551 673 1561 | f 409 673 166 1562 | f 166 673 547 1563 | f 551 827 673 1564 | f 203 668 898 1565 | f 551 898 827 1566 | f 92 396 274 1567 | f 92 932 396 1568 | f 396 979 274 1569 | f 274 87 191 1570 | f 274 494 87 1571 | f 494 274 979 1572 | f 396 932 40 1573 | f 497 40 932 1574 | f 384 396 40 1575 | f 384 979 396 1576 | f 979 102 245 1577 | f 40 497 571 1578 | f 979 245 494 1579 | f 494 636 782 1580 | f 494 245 636 1581 | f 245 951 636 1582 | f 245 102 951 1583 | f 951 406 636 1584 | f 464 571 497 1585 | f 571 464 486 1586 | f 486 464 76 1587 | f 464 980 76 1588 | f 76 90 486 1589 | f 90 709 103 1590 | f 90 983 709 1591 | f 90 76 697 1592 | f 90 697 983 1593 | f 983 305 709 1594 | f 878 29 590 1595 | f 878 30 104 1596 | f 590 30 878 1597 | f 384 29 102 1598 | f 40 571 384 1599 | f 102 979 384 1600 | f 29 878 102 1601 | f 102 878 124 1602 | f 384 486 116 1603 | f 384 116 29 1604 | f 384 571 486 1605 | f 116 590 29 1606 | f 878 104 124 1607 | f 102 124 951 1608 | f 951 124 406 1609 | f 124 928 406 1610 | f 406 928 112 1611 | f 124 104 928 1612 | f 104 580 250 1613 | f 928 250 112 1614 | f 928 104 250 1615 | f 116 486 90 1616 | f 116 30 590 1617 | f 116 103 212 1618 | f 116 90 103 1619 | f 116 466 30 1620 | f 116 212 466 1621 | f 30 580 104 1622 | f 30 466 592 1623 | f 30 592 580 1624 | f 103 254 212 1625 | f 212 254 466 1626 | f 103 709 891 1627 | f 103 891 254 1628 | f 466 130 592 1629 | f 466 868 130 1630 | f 466 254 868 1631 | f 130 868 592 1632 | f 868 269 592 1633 | f 580 350 250 1634 | f 580 567 350 1635 | f 580 592 567 1636 | f 567 592 269 1637 | f 87 494 782 1638 | f 87 724 680 1639 | f 87 782 724 1640 | f 87 680 518 1641 | f 680 724 518 1642 | f 782 788 724 1643 | f 518 724 262 1644 | f 518 262 463 1645 | f 302 518 463 1646 | f 463 262 676 1647 | f 724 299 262 1648 | f 788 299 724 1649 | f 782 636 788 1650 | f 636 397 788 1651 | f 788 397 299 1652 | f 636 406 397 1653 | f 397 204 299 1654 | f 204 112 358 1655 | f 397 112 204 1656 | f 262 299 493 1657 | f 299 50 493 1658 | f 299 204 50 1659 | f 204 358 50 1660 | f 463 676 172 1661 | f 262 493 676 1662 | f 676 493 892 1663 | f 493 50 892 1664 | f 406 112 397 1665 | f 112 742 358 1666 | f 112 350 564 1667 | f 112 564 742 1668 | f 358 742 84 1669 | f 112 250 350 1670 | f 567 269 68 1671 | f 567 401 350 1672 | f 567 68 401 1673 | f 350 401 564 1674 | f 257 731 301 1675 | f 497 301 732 1676 | f 301 497 257 1677 | f 497 732 464 1678 | f 257 638 731 1679 | f 638 147 731 1680 | f 638 961 147 1681 | f 961 877 147 1682 | f 961 443 877 1683 | f 301 731 198 1684 | f 198 731 147 1685 | f 877 342 309 1686 | f 669 370 73 1687 | f 73 181 669 1688 | f 92 370 669 1689 | f 92 669 932 1690 | f 73 151 181 1691 | f 669 181 686 1692 | f 932 669 686 1693 | f 686 181 257 1694 | f 179 961 151 1695 | f 179 963 961 1696 | f 181 151 638 1697 | f 181 638 257 1698 | f 151 961 638 1699 | f 932 686 497 1700 | f 686 257 497 1701 | f 443 67 342 1702 | f 19 67 443 1703 | f 19 839 67 1704 | f 382 839 106 1705 | f 839 382 67 1706 | f 67 382 362 1707 | f 382 106 872 1708 | f 106 46 872 1709 | f 106 647 46 1710 | f 877 443 342 1711 | f 342 67 912 1712 | f 67 362 912 1713 | f 342 912 309 1714 | f 382 315 362 1715 | f 382 872 315 1716 | f 872 46 315 1717 | f 362 315 372 1718 | f 315 471 372 1719 | f 315 748 471 1720 | f 315 46 748 1721 | f 963 19 443 1722 | f 963 443 961 1723 | f 139 839 19 1724 | f 839 925 106 1725 | f 120 647 106 1726 | f 464 732 980 1727 | f 732 301 980 1728 | f 301 198 980 1729 | f 980 198 351 1730 | f 76 980 762 1731 | f 980 911 762 1732 | f 980 351 911 1733 | f 198 2 351 1734 | f 198 147 2 1735 | f 2 147 621 1736 | f 877 621 147 1737 | f 877 309 621 1738 | f 621 437 2 1739 | f 351 2 911 1740 | f 621 309 395 1741 | f 621 395 437 1742 | f 76 762 697 1743 | f 697 762 763 1744 | f 697 444 983 1745 | f 983 5 305 1746 | f 983 444 5 1747 | f 444 697 763 1748 | f 2 437 911 1749 | f 911 437 762 1750 | f 762 437 763 1751 | f 437 395 164 1752 | f 437 799 763 1753 | f 437 164 799 1754 | f 309 912 374 1755 | f 912 362 372 1756 | f 374 912 468 1757 | f 912 372 468 1758 | f 309 374 395 1759 | f 374 468 395 1760 | f 471 748 728 1761 | f 471 606 468 1762 | f 471 7 606 1763 | f 372 471 468 1764 | f 7 471 728 1765 | f 728 502 7 1766 | f 395 765 164 1767 | f 468 110 395 1768 | f 395 110 765 1769 | f 110 926 765 1770 | f 164 765 799 1771 | f 765 926 144 1772 | f 765 144 799 1773 | f 468 606 110 1774 | f 110 606 845 1775 | f 606 634 845 1776 | f 110 845 926 1777 | f 926 845 144 1778 | f 676 142 172 1779 | f 676 892 142 1780 | f 172 142 47 1781 | f 47 142 648 1782 | f 358 84 50 1783 | f 892 50 890 1784 | f 142 890 648 1785 | f 142 892 890 1786 | f 890 308 648 1787 | f 742 398 84 1788 | f 742 564 398 1789 | f 564 401 398 1790 | f 398 401 491 1791 | f 50 84 890 1792 | f 890 84 735 1793 | f 890 735 308 1794 | f 308 735 220 1795 | f 398 735 84 1796 | f 398 491 735 1797 | f 401 428 491 1798 | f 428 117 491 1799 | f 117 902 491 1800 | f 735 491 283 1801 | f 491 902 283 1802 | f 220 735 283 1803 | f 283 902 672 1804 | f 902 35 672 1805 | f 709 305 408 1806 | f 408 305 253 1807 | f 254 891 363 1808 | f 254 852 868 1809 | f 868 852 269 1810 | f 254 363 852 1811 | f 891 709 408 1812 | f 363 432 852 1813 | f 891 70 363 1814 | f 891 937 70 1815 | f 891 408 937 1816 | f 363 70 432 1817 | f 70 306 432 1818 | f 432 970 852 1819 | f 269 852 682 1820 | f 852 970 682 1821 | f 269 682 68 1822 | f 432 306 970 1823 | f 682 970 465 1824 | f 305 5 253 1825 | f 444 763 5 1826 | f 253 5 598 1827 | f 253 598 604 1828 | f 5 353 598 1829 | f 763 799 5 1830 | f 408 253 937 1831 | f 253 944 937 1832 | f 253 604 944 1833 | f 937 993 306 1834 | f 70 937 306 1835 | f 306 993 664 1836 | f 604 598 944 1837 | f 970 306 664 1838 | f 970 664 465 1839 | f 465 664 480 1840 | f 598 757 944 1841 | f 993 131 664 1842 | f 937 944 993 1843 | f 944 199 900 1844 | f 993 944 900 1845 | f 993 900 131 1846 | f 664 131 480 1847 | f 131 900 519 1848 | f 131 259 480 1849 | f 944 757 199 1850 | f 199 808 900 1851 | f 199 132 808 1852 | f 132 544 808 1853 | f 900 995 519 1854 | f 900 808 753 1855 | f 900 753 995 1856 | f 808 544 753 1857 | f 753 330 995 1858 | f 753 544 330 1859 | f 544 532 330 1860 | f 5 695 353 1861 | f 799 144 5 1862 | f 695 615 353 1863 | f 353 615 598 1864 | f 144 695 5 1865 | f 598 596 757 1866 | f 757 314 199 1867 | f 132 907 544 1868 | f 532 344 330 1869 | f 544 907 532 1870 | f 532 981 344 1871 | f 314 579 199 1872 | f 199 579 132 1873 | f 132 579 907 1874 | f 532 538 64 1875 | f 532 64 981 1876 | f 532 907 538 1877 | f 68 682 428 1878 | f 428 682 816 1879 | f 68 428 401 1880 | f 682 465 816 1881 | f 465 480 948 1882 | f 480 760 948 1883 | f 465 948 816 1884 | f 428 816 117 1885 | f 816 948 178 1886 | f 816 178 117 1887 | f 948 760 871 1888 | f 178 948 871 1889 | f 480 259 760 1890 | f 131 519 259 1891 | f 519 804 259 1892 | f 519 995 804 1893 | f 995 122 804 1894 | f 995 330 122 1895 | f 760 259 871 1896 | f 804 122 259 1897 | f 871 259 379 1898 | f 259 122 379 1899 | f 117 178 168 1900 | f 117 168 902 1901 | f 902 168 36 1902 | f 168 178 36 1903 | f 36 178 230 1904 | f 178 871 230 1905 | f 871 379 230 1906 | f 36 35 902 1907 | f 36 614 35 1908 | f 36 230 614 1909 | f 330 344 665 1910 | f 122 330 21 1911 | f 330 665 21 1912 | f 21 484 122 1913 | f 122 844 379 1914 | f 122 484 844 1915 | f 21 224 484 1916 | f 21 665 224 1917 | f 981 586 344 1918 | f 981 690 586 1919 | f 981 64 690 1920 | f 344 12 665 1921 | f 344 586 12 1922 | f 665 12 224 1923 | f 224 12 45 1924 | f 586 690 12 1925 | f 614 230 366 1926 | f 230 379 631 1927 | f 631 379 698 1928 | f 379 844 698 1929 | f 844 957 698 1930 | f 844 484 957 1931 | f 484 224 957 1932 | f 230 631 366 1933 | f 698 438 631 1934 | f 224 190 957 1935 | f 224 45 190 1936 | f 957 190 175 1937 | f 45 515 190 1938 | f 190 515 62 1939 | f 698 175 438 1940 | f 175 20 438 1941 | f 175 214 20 1942 | f 957 175 698 1943 | f 190 214 175 1944 | f 190 62 214 1945 | f 647 521 221 1946 | f 521 705 221 1947 | f 647 221 46 1948 | f 221 705 626 1949 | f 221 626 722 1950 | f 626 705 479 1951 | f 479 169 626 1952 | f 169 479 723 1953 | f 46 221 730 1954 | f 221 722 730 1955 | f 46 801 748 1956 | f 46 730 801 1957 | f 748 801 728 1958 | f 730 722 801 1959 | f 722 626 134 1960 | f 626 169 134 1961 | f 511 722 893 1962 | f 801 722 511 1963 | f 893 722 134 1964 | f 893 134 369 1965 | f 120 521 647 1966 | f 120 317 521 1967 | f 317 705 521 1968 | f 317 479 705 1969 | f 317 720 360 1970 | f 317 360 723 1971 | f 317 723 479 1972 | f 169 723 360 1973 | f 360 10 169 1974 | f 169 369 134 1975 | f 169 10 369 1976 | f 611 846 10 1977 | f 10 239 369 1978 | f 369 778 893 1979 | f 369 239 778 1980 | f 10 557 239 1981 | f 10 846 557 1982 | f 239 557 559 1983 | f 557 581 559 1984 | f 581 557 823 1985 | f 557 354 823 1986 | f 720 1000 360 1987 | f 243 498 1000 1988 | f 643 193 498 1989 | f 360 1000 10 1990 | f 498 450 1000 1991 | f 498 193 450 1992 | f 1000 611 10 1993 | f 1000 846 611 1994 | f 1000 450 846 1995 | f 643 28 193 1996 | f 28 809 193 1997 | f 28 779 809 1998 | f 28 770 779 1999 | f 193 809 450 2000 | f 779 6 809 2001 | f 846 450 557 2002 | f 809 6 450 2003 | f 557 450 354 2004 | f 450 818 354 2005 | f 801 271 728 2006 | f 728 271 502 2007 | f 801 285 271 2008 | f 7 502 162 2009 | f 271 44 502 2010 | f 502 44 162 2011 | f 271 285 44 2012 | f 801 511 285 2013 | f 511 893 176 2014 | f 511 176 285 2015 | f 285 998 44 2016 | f 998 285 495 2017 | f 285 176 495 2018 | f 7 162 606 2019 | f 606 162 634 2020 | f 845 634 144 2021 | f 634 695 144 2022 | f 44 998 162 2023 | f 162 998 65 2024 | f 162 525 634 2025 | f 162 65 525 2026 | f 998 495 65 2027 | f 634 525 695 2028 | f 893 778 176 2029 | f 239 559 778 2030 | f 778 559 451 2031 | f 778 451 223 2032 | f 176 778 223 2033 | f 176 223 386 2034 | f 559 581 451 2035 | f 581 645 451 2036 | f 581 823 645 2037 | f 451 386 223 2038 | f 451 645 386 2039 | f 645 774 386 2040 | f 495 176 119 2041 | f 495 119 65 2042 | f 176 386 119 2043 | f 119 482 65 2044 | f 65 482 525 2045 | f 119 386 482 2046 | f 386 774 240 2047 | f 386 240 482 2048 | f 482 419 525 2049 | f 482 240 513 2050 | f 325 393 829 2051 | f 325 781 393 2052 | f 393 347 829 2053 | f 829 347 777 2054 | f 777 433 226 2055 | f 226 433 105 2056 | f 829 777 226 2057 | f 555 433 777 2058 | f 196 860 326 2059 | f 990 326 860 2060 | f 433 75 105 2061 | f 433 916 75 2062 | f 196 535 916 2063 | f 535 75 916 2064 | f 536 916 433 2065 | f 555 3 433 2066 | f 433 3 536 2067 | f 489 989 930 2068 | f 930 141 489 2069 | f 196 916 860 2070 | f 989 860 916 2071 | f 489 990 860 2072 | f 860 989 489 2073 | f 536 793 916 2074 | f 989 916 793 2075 | f 770 605 125 2076 | f 779 770 125 2077 | f 779 268 6 2078 | f 779 125 268 2079 | f 605 282 125 2080 | f 605 166 282 2081 | f 125 145 268 2082 | f 125 375 145 2083 | f 282 375 125 2084 | f 450 6 818 2085 | f 6 268 818 2086 | f 354 818 823 2087 | f 818 146 823 2088 | f 818 268 146 2089 | f 268 153 146 2090 | f 268 883 153 2091 | f 268 145 883 2092 | f 375 940 145 2093 | f 145 940 453 2094 | f 145 743 883 2095 | f 145 453 743 2096 | f 453 940 335 2097 | f 823 331 645 2098 | f 331 823 400 2099 | f 645 331 774 2100 | f 331 400 685 2101 | f 331 685 774 2102 | f 774 685 136 2103 | f 774 136 240 2104 | f 685 135 136 2105 | f 685 771 135 2106 | f 240 136 513 2107 | f 136 135 513 2108 | f 513 135 790 2109 | f 823 146 400 2110 | f 146 153 400 2111 | f 400 153 954 2112 | f 153 883 545 2113 | f 883 279 545 2114 | f 545 231 943 2115 | f 545 943 153 2116 | f 545 279 231 2117 | f 153 943 954 2118 | f 943 101 582 2119 | f 954 943 582 2120 | f 400 954 685 2121 | f 954 582 380 2122 | f 685 954 380 2123 | f 943 231 101 2124 | f 101 231 417 2125 | f 883 743 279 2126 | f 743 453 449 2127 | f 743 449 279 2128 | f 231 279 449 2129 | f 453 25 449 2130 | f 453 335 25 2131 | f 231 449 417 2132 | f 149 417 449 2133 | f 25 434 149 2134 | f 449 25 149 2135 | f 149 434 908 2136 | f 582 417 653 2137 | f 582 101 417 2138 | f 685 380 771 2139 | f 771 380 578 2140 | f 380 653 578 2141 | f 653 884 578 2142 | f 578 884 448 2143 | f 135 771 469 2144 | f 771 578 469 2145 | f 135 469 687 2146 | f 622 448 884 2147 | f 578 412 469 2148 | f 578 448 412 2149 | f 448 622 412 2150 | f 417 149 884 2151 | f 211 884 149 2152 | f 622 884 211 2153 | f 149 908 211 2154 | f 582 653 380 2155 | f 884 653 417 2156 | f 166 897 282 2157 | f 166 547 897 2158 | f 547 918 897 2159 | f 282 205 375 2160 | f 282 897 205 2161 | f 205 897 627 2162 | f 897 264 627 2163 | f 897 918 822 2164 | f 897 822 264 2165 | f 375 205 627 2166 | f 547 673 918 2167 | f 673 696 918 2168 | f 696 822 918 2169 | f 822 696 856 2170 | f 264 822 856 2171 | f 264 225 335 2172 | f 375 627 940 2173 | f 940 627 335 2174 | f 627 264 335 2175 | f 335 434 25 2176 | f 264 856 225 2177 | f 335 225 434 2178 | f 105 434 225 2179 | f 226 105 225 2180 | f 673 827 696 2181 | f 827 325 696 2182 | f 696 829 856 2183 | f 696 325 829 2184 | f 829 225 856 2185 | f 829 226 225 2186 | f 75 535 908 2187 | f 434 75 908 2188 | f 434 105 75 2189 | f 535 211 908 2190 | f 326 211 196 2191 | f 211 535 196 2192 | f 326 967 819 2193 | f 326 622 211 2194 | f 326 819 622 2195 | f 598 615 870 2196 | f 695 525 419 2197 | f 615 69 870 2198 | f 695 69 615 2199 | f 598 870 596 2200 | f 757 596 314 2201 | f 314 874 579 2202 | f 579 639 907 2203 | f 907 639 835 2204 | f 874 639 579 2205 | f 907 410 538 2206 | f 835 824 907 2207 | f 907 159 410 2208 | f 907 824 159 2209 | f 695 419 69 2210 | f 482 133 419 2211 | f 482 513 133 2212 | f 513 483 133 2213 | f 419 133 69 2214 | f 69 133 870 2215 | f 133 483 870 2216 | f 596 520 652 2217 | f 596 999 520 2218 | f 870 999 596 2219 | f 483 857 999 2220 | f 870 483 999 2221 | f 999 711 129 2222 | f 999 129 520 2223 | f 129 352 115 2224 | f 352 737 115 2225 | f 115 737 171 2226 | f 171 737 996 2227 | f 314 596 874 2228 | f 639 747 835 2229 | f 639 632 747 2230 | f 639 874 632 2231 | f 835 903 824 2232 | f 824 903 37 2233 | f 824 37 159 2234 | f 835 747 903 2235 | f 903 548 37 2236 | f 903 985 548 2237 | f 903 747 985 2238 | f 319 548 377 2239 | f 548 985 377 2240 | f 874 652 632 2241 | f 596 652 874 2242 | f 652 520 924 2243 | f 652 924 632 2244 | f 632 924 633 2245 | f 632 633 747 2246 | f 924 520 129 2247 | f 924 129 115 2248 | f 924 115 633 2249 | f 633 115 985 2250 | f 747 633 985 2251 | f 377 985 13 2252 | f 985 115 13 2253 | f 171 13 115 2254 | f 64 694 690 2255 | f 64 538 692 2256 | f 64 297 694 2257 | f 64 692 297 2258 | f 694 356 690 2259 | f 297 692 853 2260 | f 694 297 356 2261 | f 410 159 875 2262 | f 875 159 750 2263 | f 538 410 692 2264 | f 692 410 853 2265 | f 410 875 853 2266 | f 853 875 361 2267 | f 875 750 361 2268 | f 853 361 297 2269 | f 297 361 98 2270 | f 361 523 98 2271 | f 523 750 593 2272 | f 523 361 750 2273 | f 12 356 45 2274 | f 12 690 356 2275 | f 45 356 515 2276 | f 356 609 515 2277 | f 356 297 98 2278 | f 356 98 609 2279 | f 62 609 977 2280 | f 98 977 609 2281 | f 214 841 456 2282 | f 214 62 841 2283 | f 62 100 841 2284 | f 679 15 100 2285 | f 515 609 62 2286 | f 62 977 100 2287 | f 523 128 977 2288 | f 523 593 128 2289 | f 422 128 593 2290 | f 128 422 27 2291 | f 98 523 977 2292 | f 977 128 100 2293 | f 100 128 679 2294 | f 27 679 128 2295 | f 794 616 13 2296 | f 294 13 171 2297 | f 794 13 294 2298 | f 171 996 294 2299 | f 294 996 967 2300 | f 990 485 294 2301 | f 489 485 990 2302 | f 294 967 990 2303 | f 967 326 990 2304 | f 294 485 794 2305 | f 159 37 750 2306 | f 37 548 319 2307 | f 37 319 750 2308 | f 13 616 377 2309 | f 750 319 593 2310 | f 377 593 319 2311 | f 377 616 593 2312 | f 422 616 987 2313 | f 422 593 616 2314 | f 794 987 616 2315 | f 141 485 489 2316 | f 794 422 987 2317 | f 794 485 141 2318 | f 930 213 141 2319 | f 794 141 422 2320 | f 141 27 422 2321 | f 513 790 483 2322 | f 790 135 687 2323 | f 790 857 483 2324 | f 790 687 411 2325 | f 790 411 857 2326 | f 687 349 411 2327 | f 687 469 298 2328 | f 687 298 349 2329 | f 469 412 298 2330 | f 622 113 412 2331 | f 298 412 349 2332 | f 575 412 113 2333 | f 575 349 412 2334 | f 857 411 711 2335 | f 999 857 711 2336 | f 352 575 737 2337 | f 352 711 575 2338 | f 711 349 575 2339 | f 711 411 349 2340 | f 129 711 352 2341 | f 819 737 113 2342 | f 819 967 737 2343 | f 622 819 113 2344 | f 737 575 113 2345 | f 737 967 996 2346 | f 700 177 539 2347 | f 167 177 700 2348 | f 206 295 187 2349 | f 206 187 328 2350 | f 187 295 906 2351 | f 906 328 187 2352 | f 869 206 291 2353 | f 206 328 291 2354 | f 295 539 54 2355 | f 539 288 341 2356 | f 539 341 54 2357 | f 295 54 906 2358 | f 539 177 288 2359 | f 341 651 54 2360 | f 54 651 906 2361 | f 288 716 341 2362 | f 341 446 651 2363 | f 328 906 949 2364 | f 328 949 252 2365 | f 949 541 252 2366 | f 906 651 949 2367 | f 949 651 233 2368 | f 721 965 177 2369 | f 177 965 656 2370 | f 167 721 177 2371 | f 177 656 288 2372 | f 556 927 512 2373 | f 512 927 947 2374 | f 927 8 786 2375 | f 927 197 947 2376 | f 927 786 197 2377 | f 642 512 965 2378 | f 965 947 656 2379 | f 512 947 965 2380 | f 656 947 258 2381 | f 656 258 288 2382 | f 947 197 258 2383 | f 288 258 716 2384 | f 341 855 446 2385 | f 341 716 855 2386 | f 651 446 233 2387 | f 855 531 446 2388 | f 446 531 233 2389 | f 233 531 514 2390 | f 531 674 514 2391 | f 855 509 531 2392 | f 531 509 674 2393 | f 514 674 143 2394 | f 258 554 716 2395 | f 716 637 855 2396 | f 716 554 637 2397 | f 554 798 637 2398 | f 855 637 509 2399 | f 637 1001 509 2400 | f 674 953 143 2401 | f 637 798 618 2402 | f 637 618 1001 2403 | f 618 94 1001 2404 | f 509 1001 674 2405 | f 674 1001 953 2406 | f 8 345 786 2407 | f 8 334 345 2408 | f 786 862 197 2409 | f 786 345 862 2410 | f 334 39 345 2411 | f 345 39 862 2412 | f 39 182 862 2413 | f 862 300 197 2414 | f 862 192 300 2415 | f 258 197 256 2416 | f 197 300 108 2417 | f 39 467 991 2418 | f 39 991 182 2419 | f 467 861 991 2420 | f 861 163 991 2421 | f 991 163 59 2422 | f 862 182 192 2423 | f 192 182 988 2424 | f 192 81 300 2425 | f 182 991 988 2426 | f 991 59 988 2427 | f 192 988 81 2428 | f 988 59 435 2429 | f 197 108 256 2430 | f 256 905 258 2431 | f 108 300 915 2432 | f 300 81 915 2433 | f 988 821 81 2434 | f 81 566 915 2435 | f 256 108 415 2436 | f 108 915 415 2437 | f 258 905 554 2438 | f 905 727 554 2439 | f 554 727 798 2440 | f 798 441 618 2441 | f 256 415 905 2442 | f 415 915 270 2443 | f 618 441 94 2444 | f 1001 94 953 2445 | f 94 157 953 2446 | f 441 624 94 2447 | f 94 624 157 2448 | f 798 242 441 2449 | f 441 292 624 2450 | f 624 292 228 2451 | f 727 24 798 2452 | f 905 415 727 2453 | f 727 415 24 2454 | f 270 263 415 2455 | f 798 24 242 2456 | f 415 864 24 2457 | f 415 263 864 2458 | f 242 517 292 2459 | f 441 242 292 2460 | f 292 517 228 2461 | f 242 864 517 2462 | f 517 227 792 2463 | f 242 24 864 2464 | f 517 864 227 2465 | f 864 222 227 2466 | f 864 619 222 2467 | f 291 328 719 2468 | f 719 252 376 2469 | f 328 252 719 2470 | f 252 767 376 2471 | f 719 376 591 2472 | f 767 53 376 2473 | f 376 286 817 2474 | f 376 53 286 2475 | f 817 286 710 2476 | f 252 541 542 2477 | f 252 542 767 2478 | f 541 247 542 2479 | f 949 233 541 2480 | f 233 997 541 2481 | f 541 865 247 2482 | f 541 997 865 2483 | f 247 865 691 2484 | f 767 542 53 2485 | f 53 183 286 2486 | f 183 53 127 2487 | f 286 183 710 2488 | f 710 183 663 2489 | f 183 248 663 2490 | f 127 248 183 2491 | f 127 246 248 2492 | f 127 814 246 2493 | f 542 247 53 2494 | f 247 368 53 2495 | f 247 691 368 2496 | f 53 368 127 2497 | f 368 904 127 2498 | f 248 402 663 2499 | f 248 246 402 2500 | f 865 895 691 2501 | f 691 895 368 2502 | f 895 904 368 2503 | f 904 814 127 2504 | f 814 904 85 2505 | f 895 973 904 2506 | f 85 783 814 2507 | f 904 973 85 2508 | f 973 355 85 2509 | f 233 514 997 2510 | f 997 137 865 2511 | f 997 984 137 2512 | f 865 137 895 2513 | f 514 143 997 2514 | f 143 158 997 2515 | f 997 158 984 2516 | f 984 478 137 2517 | f 478 749 137 2518 | f 984 158 478 2519 | f 137 749 895 2520 | f 749 973 895 2521 | f 749 296 973 2522 | f 143 830 158 2523 | f 158 766 478 2524 | f 766 625 478 2525 | f 158 830 766 2526 | f 830 882 766 2527 | f 882 470 766 2528 | f 766 820 625 2529 | f 143 953 882 2530 | f 143 882 830 2531 | f 953 528 882 2532 | f 882 775 470 2533 | f 766 470 820 2534 | f 470 312 820 2535 | f 470 775 312 2536 | f 478 296 749 2537 | f 478 625 296 2538 | f 625 33 296 2539 | f 625 820 33 2540 | f 296 355 973 2541 | f 296 33 355 2542 | f 33 969 355 2543 | f 33 42 969 2544 | f 820 42 33 2545 | f 820 546 42 2546 | f 246 207 402 2547 | f 246 11 207 2548 | f 814 11 246 2549 | f 814 783 11 2550 | f 207 11 776 2551 | f 783 776 11 2552 | f 783 487 776 2553 | f 783 501 487 2554 | f 776 43 207 2555 | f 43 776 803 2556 | f 85 501 783 2557 | f 503 43 803 2558 | f 803 776 929 2559 | f 487 929 776 2560 | f 487 160 929 2561 | f 501 160 487 2562 | f 160 942 929 2563 | f 355 501 85 2564 | f 501 576 160 2565 | f 355 576 501 2566 | f 355 702 576 2567 | f 969 702 355 2568 | f 160 576 942 2569 | f 702 942 576 2570 | f 702 787 942 2571 | f 107 803 71 2572 | f 803 929 71 2573 | f 107 71 346 2574 | f 929 583 71 2575 | f 942 583 929 2576 | f 702 969 303 2577 | f 42 303 969 2578 | f 842 313 775 2579 | f 42 810 303 2580 | f 546 810 42 2581 | f 312 546 820 2582 | f 312 807 546 2583 | f 775 807 312 2584 | f 775 313 807 2585 | f 807 810 546 2586 | f 313 111 807 2587 | f 313 585 111 2588 | f 807 725 218 2589 | f 807 218 810 2590 | f 807 111 725 2591 | f 585 1 111 2592 | f 218 725 859 2593 | f 859 725 832 2594 | f 725 111 832 2595 | f 1 832 111 2596 | f 1 899 832 2597 | f 303 787 702 2598 | f 942 787 583 2599 | f 787 303 712 2600 | f 303 461 712 2601 | f 787 712 392 2602 | f 712 992 392 2603 | f 461 992 712 2604 | f 346 71 126 2605 | f 126 71 761 2606 | f 71 583 761 2607 | f 346 126 476 2608 | f 126 761 407 2609 | f 476 126 569 2610 | f 569 126 407 2611 | f 781 569 407 2612 | f 787 761 583 2613 | f 787 392 761 2614 | f 392 316 761 2615 | f 761 316 407 2616 | f 992 316 392 2617 | f 992 738 316 2618 | f 455 738 992 2619 | f 781 407 986 2620 | f 781 986 393 2621 | f 986 347 393 2622 | f 738 407 316 2623 | f 738 701 407 2624 | f 407 701 986 2625 | f 738 336 701 2626 | f 336 418 701 2627 | f 701 418 986 2628 | f 347 986 418 2629 | f 810 461 303 2630 | f 810 218 461 2631 | f 63 461 218 2632 | f 218 859 63 2633 | f 461 63 992 2634 | f 455 859 812 2635 | f 63 455 992 2636 | f 859 455 63 2637 | f 455 421 738 2638 | f 738 421 336 2639 | f 516 421 789 2640 | f 812 421 455 2641 | f 421 516 336 2642 | f 859 832 812 2643 | f 832 421 812 2644 | f 832 789 421 2645 | f 832 899 789 2646 | f 953 157 528 2647 | f 882 528 775 2648 | f 157 584 528 2649 | f 528 842 775 2650 | f 528 584 842 2651 | f 624 228 831 2652 | f 624 831 157 2653 | f 157 831 584 2654 | f 842 585 313 2655 | f 584 585 842 2656 | f 831 834 584 2657 | f 834 585 584 2658 | f 834 549 585 2659 | f 228 889 831 2660 | f 831 889 834 2661 | f 834 595 549 2662 | f 549 321 1 2663 | f 321 549 595 2664 | f 595 840 321 2665 | f 228 517 889 2666 | f 834 889 595 2667 | f 517 792 889 2668 | f 889 792 595 2669 | f 792 405 595 2670 | f 405 840 595 2671 | f 227 442 792 2672 | f 792 442 405 2673 | f 227 222 442 2674 | f 442 188 405 2675 | f 405 80 840 2676 | f 549 1 585 2677 | f 1 321 899 2678 | f 321 387 899 2679 | f 321 165 387 2680 | f 861 648 163 2681 | f 47 648 861 2682 | f 648 849 163 2683 | f 59 163 209 2684 | f 59 209 435 2685 | f 648 420 849 2686 | f 648 308 420 2687 | f 163 849 209 2688 | f 435 209 31 2689 | f 209 849 31 2690 | f 849 420 31 2691 | f 988 435 821 2692 | f 435 506 821 2693 | f 81 821 58 2694 | f 821 506 58 2695 | f 435 156 506 2696 | f 81 58 566 2697 | f 566 270 915 2698 | f 58 670 566 2699 | f 58 506 670 2700 | f 435 31 156 2701 | f 156 289 506 2702 | f 506 289 670 2703 | f 270 566 475 2704 | f 566 670 475 2705 | f 156 97 289 2706 | f 289 97 573 2707 | f 420 308 527 2708 | f 308 220 527 2709 | f 31 420 880 2710 | f 420 527 880 2711 | f 220 672 340 2712 | f 283 672 220 2713 | f 220 340 527 2714 | f 527 340 880 2715 | f 340 933 880 2716 | f 672 322 340 2717 | f 340 322 933 2718 | f 31 880 304 2719 | f 31 304 156 2720 | f 322 740 933 2721 | f 322 678 740 2722 | f 304 880 938 2723 | f 156 304 97 2724 | f 304 938 97 2725 | f 880 933 373 2726 | f 933 740 373 2727 | f 880 86 938 2728 | f 880 373 86 2729 | f 678 378 740 2730 | f 740 378 373 2731 | f 678 968 378 2732 | f 378 968 826 2733 | f 938 86 887 2734 | f 86 681 887 2735 | f 86 373 378 2736 | f 378 826 681 2737 | f 378 681 86 2738 | f 826 968 431 2739 | f 270 339 577 2740 | f 270 577 263 2741 | f 270 475 339 2742 | f 475 670 273 2743 | f 339 537 577 2744 | f 864 208 619 2745 | f 864 263 208 2746 | f 619 759 222 2747 | f 577 208 263 2748 | f 670 573 273 2749 | f 475 273 473 2750 | f 475 473 339 2751 | f 670 289 573 2752 | f 273 389 473 2753 | f 273 573 389 2754 | f 537 339 504 2755 | f 339 473 504 2756 | f 473 389 504 2757 | f 577 755 208 2758 | f 389 714 504 2759 | f 619 508 759 2760 | f 619 208 508 2761 | f 208 755 508 2762 | f 577 217 755 2763 | f 537 217 577 2764 | f 537 504 217 2765 | f 504 109 217 2766 | f 755 217 88 2767 | f 755 88 508 2768 | f 573 97 836 2769 | f 97 938 707 2770 | f 97 707 836 2771 | f 573 836 389 2772 | f 389 836 714 2773 | f 836 154 714 2774 | f 836 707 154 2775 | f 707 323 154 2776 | f 154 699 914 2777 | f 938 887 707 2778 | f 681 826 887 2779 | f 887 826 964 2780 | f 707 887 323 2781 | f 826 572 964 2782 | f 431 572 826 2783 | f 964 572 79 2784 | f 89 272 952 2785 | f 572 272 79 2786 | f 887 964 323 2787 | f 964 79 323 2788 | f 323 79 699 2789 | f 323 699 154 2790 | f 699 79 873 2791 | f 914 873 806 2792 | f 914 699 873 2793 | f 89 543 873 2794 | f 79 272 89 2795 | f 873 79 89 2796 | f 504 714 109 2797 | f 217 109 88 2798 | f 109 460 754 2799 | f 88 109 754 2800 | f 754 828 88 2801 | f 109 154 460 2802 | f 714 154 109 2803 | f 754 806 828 2804 | f 460 806 754 2805 | f 154 914 460 2806 | f 914 806 460 2807 | f 806 118 729 2808 | f 873 118 806 2809 | f 35 614 322 2810 | f 672 35 322 2811 | f 614 170 322 2812 | f 614 366 170 2813 | f 170 366 678 2814 | f 170 678 322 2815 | f 366 293 678 2816 | f 366 530 293 2817 | f 161 293 277 2818 | f 530 277 293 2819 | f 530 675 277 2820 | f 675 744 277 2821 | f 394 858 744 2822 | f 631 18 366 2823 | f 631 438 18 2824 | f 18 530 366 2825 | f 18 438 530 2826 | f 530 438 675 2827 | f 438 20 675 2828 | f 20 214 456 2829 | f 20 456 394 2830 | f 675 20 744 2831 | f 20 394 744 2832 | f 678 293 968 2833 | f 293 161 968 2834 | f 955 858 952 2835 | f 968 161 955 2836 | f 161 277 955 2837 | f 277 744 955 2838 | f 955 744 858 2839 | f 968 955 431 2840 | f 431 955 934 2841 | f 572 431 272 2842 | f 934 955 952 2843 | f 952 858 457 2844 | f 934 272 431 2845 | f 934 952 272 2846 | f 387 399 899 2847 | f 387 210 399 2848 | f 399 210 318 2849 | f 210 876 318 2850 | f 251 318 603 2851 | f 876 603 318 2852 | f 603 184 717 2853 | f 385 848 477 2854 | f 347 17 777 2855 | f 477 17 347 2856 | f 418 477 347 2857 | f 17 477 746 2858 | f 848 746 477 2859 | f 848 276 746 2860 | f 777 17 555 2861 | f 746 555 17 2862 | f 746 3 555 2863 | f 789 318 516 2864 | f 318 385 516 2865 | f 385 418 516 2866 | f 516 418 336 2867 | f 418 385 477 2868 | f 385 251 848 2869 | f 899 399 789 2870 | f 399 318 789 2871 | f 318 251 385 2872 | f 251 717 848 2873 | f 276 848 717 2874 | f 717 802 276 2875 | f 251 603 717 2876 | f 726 815 879 2877 | f 184 802 717 2878 | f 184 879 802 2879 | f 184 976 879 2880 | f 751 526 332 2881 | f 332 202 561 2882 | f 561 847 332 2883 | f 553 847 561 2884 | f 553 155 847 2885 | f 751 332 867 2886 | f 726 879 155 2887 | f 879 867 155 2888 | f 867 332 155 2889 | f 847 155 332 2890 | f 332 526 202 2891 | f 746 496 3 2892 | f 536 552 793 2893 | f 496 536 3 2894 | f 552 536 496 2895 | f 496 276 802 2896 | f 496 802 815 2897 | f 746 276 496 2898 | f 815 552 496 2899 | f 989 793 552 2900 | f 552 560 989 2901 | f 213 930 560 2902 | f 930 989 560 2903 | f 552 815 560 2904 | f 560 815 155 2905 | f 553 560 155 2906 | f 213 560 553 2907 | f 553 561 213 2908 | f 879 815 802 2909 | f 815 726 155 2910 | f 222 424 442 2911 | f 442 424 188 2912 | f 222 759 424 2913 | f 80 405 96 2914 | f 188 96 405 2915 | f 424 838 188 2916 | f 321 840 80 2917 | f 80 96 612 2918 | f 188 939 96 2919 | f 838 939 188 2920 | f 96 939 612 2921 | f 80 165 321 2922 | f 387 165 210 2923 | f 80 612 165 2924 | f 165 612 210 2925 | f 612 876 210 2926 | f 939 490 612 2927 | f 612 490 876 2928 | f 838 641 939 2929 | f 939 641 490 2930 | f 641 180 490 2931 | f 641 388 180 2932 | f 388 550 180 2933 | f 603 876 920 2934 | f 603 920 184 2935 | f 184 920 976 2936 | f 490 920 876 2937 | f 920 490 180 2938 | f 180 796 920 2939 | f 920 796 976 2940 | f 424 759 838 2941 | f 759 185 838 2942 | f 759 508 185 2943 | f 185 641 838 2944 | f 185 854 641 2945 | f 508 507 185 2946 | f 185 507 854 2947 | f 854 507 388 2948 | f 507 440 388 2949 | f 507 935 440 2950 | f 508 88 507 2951 | f 854 388 641 2952 | f 796 180 550 2953 | f 34 796 550 2954 | f 976 34 338 2955 | f 34 976 796 2956 | f 338 34 329 2957 | f 958 329 34 2958 | f 879 338 867 2959 | f 879 976 338 2960 | f 526 751 320 2961 | f 266 89 917 2962 | f 917 526 266 2963 | f 118 873 543 2964 | f 118 320 329 2965 | f 118 543 320 2966 | f 338 329 751 2967 | f 329 320 751 2968 | f 89 266 543 2969 | f 266 526 320 2970 | f 320 543 266 2971 | f 751 867 338 2972 | f 828 729 440 2973 | f 440 729 958 2974 | f 88 935 507 2975 | f 88 828 935 2976 | f 440 935 828 2977 | f 440 550 388 2978 | f 958 34 550 2979 | f 440 958 550 2980 | f 806 729 828 2981 | f 729 329 958 2982 | f 329 729 118 2983 | f 394 813 858 2984 | f 756 202 608 2985 | f 813 756 608 2986 | f 679 27 756 2987 | f 141 561 27 2988 | f 561 756 27 2989 | f 202 756 561 2990 | f 394 456 813 2991 | f 100 15 841 2992 | f 756 15 679 2993 | f 841 813 456 2994 | f 841 15 813 2995 | f 15 756 813 2996 | f 608 202 457 2997 | f 813 457 858 2998 | f 813 608 457 2999 | f 457 917 952 3000 | f 457 526 917 3001 | f 457 202 526 3002 | f 141 213 561 3003 | f 952 917 89 -------------------------------------------------------------------------------- /data/objs/tetrahedron.obj: -------------------------------------------------------------------------------- 1 | # WaveFront *.obj file (generated by Cinema 4D) 2 | 3 | v 81.649658203125 -33.33333206176758 -47.14045333862305 4 | v -81.649658203125 -33.33333206176758 -47.14045333862305 5 | v 0 -33.33333206176758 94.28090667724609 6 | v 0 100 0 7 | # 4 vertices 8 | 9 | g Sphere 10 | usemtl default 11 | f 3 2 1 12 | f 2 4 1 13 | f 3 4 2 14 | f 1 4 3 15 | 16 | -------------------------------------------------------------------------------- /data/point_clouds/ankylosaurus_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "point_cloud": "data/point_clouds/ankylosaurus.pwn" 3 | } -------------------------------------------------------------------------------- /data/point_clouds/bull_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "point_cloud": "data/point_clouds/bull.pwn" 3 | } -------------------------------------------------------------------------------- /data/point_clouds/elephant_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "point_cloud": "data/point_clouds/elephant.pwn" 3 | } -------------------------------------------------------------------------------- /data/point_clouds/giraffe_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "point_cloud": "data/point_clouds/giraffe.pwn" 3 | } -------------------------------------------------------------------------------- /data/point_clouds/guitar_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "point_cloud": "data/point_clouds/guitar.pwn" 3 | } -------------------------------------------------------------------------------- /data/point_clouds/guitar_settings_pooling.json: -------------------------------------------------------------------------------- 1 | { 2 | "point_cloud": "data/point_clouds/guitar.pwn", 3 | "pooling": [0.9, 0.8, 0.7, 0.6, 0.5, 0.4] 4 | } -------------------------------------------------------------------------------- /data/point_clouds/hand_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "point_cloud": "data/point_clouds/hand.pwn" 3 | } -------------------------------------------------------------------------------- /data/point_clouds/tiki_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "point_cloud": "data/point_clouds/tiki.pwn" 3 | } -------------------------------------------------------------------------------- /data/point_clouds/triceratops_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "point_cloud": "data/point_clouds/triceratops.pwn" 3 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | absl-py==0.11.0 2 | appdirs==1.4.4 3 | astunparse==1.6.3 4 | attrs==20.3.0 5 | black==20.8b1 6 | cachetools==4.1.1 7 | certifi==2020.11.8 8 | chardet==3.0.4 9 | click==7.1.2 10 | cloudpickle==1.6.0 11 | colorama==0.4.4 12 | cycler==0.10.0 13 | decorator==4.4.2 14 | dill==0.3.3 15 | dm-tree==0.1.5 16 | future==0.18.2 17 | gast==0.3.3 18 | google-auth==1.23.0 19 | google-auth-oauthlib==0.4.2 20 | google-pasta==0.2.0 21 | googleapis-common-protos==1.52.0 22 | grpcio==1.33.2 23 | gviz-api==1.9.0 24 | h5py==2.10.0 25 | idna==2.10 26 | importlib-resources==3.3.0 27 | Keras-Preprocessing==1.1.2 28 | kiwisolver==1.3.1 29 | Markdown==3.3.3 30 | matplotlib==3.3.3 31 | mypy-extensions==0.4.3 32 | networkx==2.5 33 | numpy==1.18.5 34 | oauthlib==3.1.0 35 | OpenEXR==1.3.2 36 | opt-einsum==3.3.0 37 | pathspec==0.8.1 38 | Pillow==8.0.1 39 | promise==2.3 40 | protobuf==3.14.0 41 | psutil==5.7.3 42 | pyasn1==0.4.8 43 | pyasn1-modules==0.2.8 44 | pyparsing==2.4.7 45 | python-dateutil==2.8.1 46 | regex==2020.11.13 47 | requests==2.25.0 48 | requests-oauthlib==1.3.0 49 | rsa==4.6 50 | scipy==1.5.4 51 | six==1.15.0 52 | tensorboard==2.4.0 53 | tensorboard-plugin-profile==2.3.0 54 | tensorboard-plugin-wit==1.7.0 55 | tensorflow==2.3.1 56 | tensorflow-datasets==4.1.0 57 | tensorflow-estimator==2.3.0 58 | tensorflow-graphics==2020.5.20 59 | tensorflow-metadata==0.25.0 60 | tensorflow-probability==0.11.1 61 | termcolor==1.1.0 62 | toml==0.10.2 63 | tqdm==4.53.0 64 | trimesh==3.8.12 65 | typed-ast==1.4.1 66 | typing-extensions==3.7.4.3 67 | urllib3==1.26.2 68 | Werkzeug==1.0.1 69 | wrapt==1.12.1 70 | -------------------------------------------------------------------------------- /source/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/__init__.py -------------------------------------------------------------------------------- /source/layers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/layers/__init__.py -------------------------------------------------------------------------------- /source/layers/composite/ConvolutionSequence.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | from typing import List 3 | from tensorflow.keras.layers import Layer 4 | from ...mesh.Mesh import Mesh 5 | from ..convolution.MeshConvolution import MeshConvolution 6 | from ..feature import features_valid 7 | 8 | 9 | class ConvolutionSequence(Layer): 10 | """A ConvolutionSequence is simply a number of sequential convolutions.""" 11 | 12 | convolutions: List[MeshConvolution] 13 | 14 | def __init__(self, out_channels: int, num_convolutions: int): 15 | Layer.__init__(self) 16 | 17 | # Create num_convolutions MeshConvolution layers. 18 | self.convolutions = [ 19 | MeshConvolution(out_channels) for _ in range(num_convolutions) 20 | ] 21 | 22 | def call(self, mesh: Mesh, features: tf.Tensor) -> tf.Tensor: 23 | assert features_valid(mesh, features) 24 | 25 | for convolution in self.convolutions: 26 | features = convolution(mesh, features) 27 | 28 | return features 29 | -------------------------------------------------------------------------------- /source/layers/composite/Decoder.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | from typing import List, Optional 3 | from tensorflow.keras.layers import Layer 4 | from .UpConvolution import UpConvolution 5 | from ...mesh.Mesh import Mesh 6 | from ..feature import features_valid 7 | from ..pooling.CollapseSnapshot import CollapseSnapshot 8 | 9 | 10 | class Decoder(Layer): 11 | """This combines several UpConvolution layers.""" 12 | 13 | up_convolutions: List[UpConvolution] 14 | 15 | def __init__( 16 | self, 17 | out_channels: List[int], 18 | convolutions_per_sequence: int, 19 | num_sequences_per_down_convolution: int, 20 | leaky_relu_alpha: float, 21 | encoder_pool_targets: List[Optional[int]], 22 | ): 23 | Layer.__init__(self) 24 | 25 | assert len(out_channels) == len(encoder_pool_targets) 26 | 27 | needs_unpool = [x != None for x in reversed(encoder_pool_targets)] 28 | self.up_convolutions = [ 29 | UpConvolution( 30 | out_channels[i], 31 | convolutions_per_sequence, 32 | num_sequences_per_down_convolution, 33 | leaky_relu_alpha, 34 | needs_unpool[i], 35 | ) 36 | for i in range(len(encoder_pool_targets)) 37 | ] 38 | 39 | def call( 40 | self, mesh: Mesh, features: tf.Tensor, snapshots: CollapseSnapshot 41 | ) -> tf.Tensor: 42 | assert features_valid(mesh, features) 43 | 44 | snapshots = reversed(snapshots) 45 | for up_convolution, snapshot in zip(self.up_convolutions, snapshots): 46 | features = up_convolution(mesh, features, snapshot) 47 | 48 | return features 49 | -------------------------------------------------------------------------------- /source/layers/composite/DownConvolution.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | from typing import List, Optional 3 | from tensorflow.keras.layers import Layer, LeakyReLU, BatchNormalization 4 | from ...mesh.Mesh import Mesh 5 | from ..feature import features_valid 6 | from ..pooling.MeshPool import MeshPool 7 | from .ConvolutionSequence import ConvolutionSequence 8 | 9 | 10 | class DownConvolution(Layer): 11 | """A DownConvolution combines several skip-connected ConvolutionSequence 12 | blocks separated by nonlinearities and batch normalization. It can 13 | optionally include a pooling step at the end. 14 | """ 15 | 16 | convolutions: List[ConvolutionSequence] 17 | mesh_pool: Optional[MeshPool] 18 | batch_normalizations: List[BatchNormalization] 19 | leaky_relu: LeakyReLU 20 | 21 | def __init__( 22 | self, 23 | out_channels: int, 24 | convolutions_per_sequence: int, 25 | num_sequences: int, 26 | leaky_relu_alpha: float, 27 | pool_target: Optional[int], 28 | ): 29 | Layer.__init__(self) 30 | self.convolutions = [ 31 | ConvolutionSequence(out_channels, convolutions_per_sequence) 32 | for _ in range(num_sequences) 33 | ] 34 | self.leaky_relu = LeakyReLU(leaky_relu_alpha) 35 | self.batch_normalizations = [BatchNormalization() for _ in range(num_sequences)] 36 | self.mesh_pool = None if pool_target is None else MeshPool(pool_target) 37 | 38 | def call(self, mesh: Mesh, features: tf.Tensor) -> tf.Tensor: 39 | assert features_valid(mesh, features) 40 | 41 | # Run through the first ConvolutionSequence. 42 | out_features = self.convolutions[0](mesh, features) 43 | out_features = self.leaky_relu(out_features) 44 | out_features = self.batch_normalizations[0](out_features, training=True) 45 | 46 | # Run the remaining ConvolutionSequences with skip connections. 47 | in_features = out_features 48 | for i in range(1, len(self.convolutions)): 49 | # Create out_features using in_features. 50 | out_features = self.convolutions[i](mesh, in_features) 51 | out_features = self.leaky_relu(out_features) 52 | out_features = self.batch_normalizations[i](out_features, training=True) 53 | 54 | # Add the skip connections. 55 | out_features += in_features 56 | in_features = out_features 57 | 58 | # Optionally run pooling. 59 | if self.mesh_pool is not None: 60 | return self.mesh_pool(mesh, out_features) 61 | return out_features, None 62 | -------------------------------------------------------------------------------- /source/layers/composite/Encoder.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | from typing import List, Optional 3 | from tensorflow.keras.layers import Layer 4 | from .DownConvolution import DownConvolution 5 | from ...mesh.Mesh import Mesh 6 | from ..feature import features_valid 7 | 8 | 9 | class Encoder(Layer): 10 | """This combines several DownConvolution layers.""" 11 | 12 | down_convolutions: List[DownConvolution] 13 | 14 | def __init__( 15 | self, 16 | out_channels: List[int], 17 | convolutions_per_sequence: int, 18 | num_sequences_per_down_convolution: int, 19 | leaky_relu_alpha: float, 20 | pool_targets: List[Optional[int]], 21 | ): 22 | Layer.__init__(self) 23 | 24 | assert len(out_channels) == len(pool_targets) 25 | for i in range(1, len(pool_targets)): 26 | assert pool_targets[i] is None or pool_targets[i] < pool_targets[i - 1] 27 | 28 | self.down_convolutions = [ 29 | DownConvolution( 30 | out_channels[i], 31 | convolutions_per_sequence, 32 | num_sequences_per_down_convolution, 33 | leaky_relu_alpha, 34 | pool_targets[i], 35 | ) 36 | for i in range(len(pool_targets)) 37 | ] 38 | 39 | def call(self, mesh: Mesh, features: tf.Tensor) -> tf.Tensor: 40 | assert features_valid(mesh, features) 41 | 42 | snapshots = [] 43 | for down_convolution in self.down_convolutions: 44 | features, snapshot = down_convolution(mesh, features) 45 | snapshots.append(snapshot) 46 | 47 | return features, snapshots 48 | -------------------------------------------------------------------------------- /source/layers/composite/UpConvolution.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | from typing import List, Optional 3 | from tensorflow.keras.layers import Layer, LeakyReLU, BatchNormalization 4 | from ...mesh.Mesh import Mesh 5 | from ..feature import features_valid 6 | from ..pooling.MeshUnpool import MeshUnpool 7 | from ..pooling.CollapseSnapshot import CollapseSnapshot 8 | from .ConvolutionSequence import ConvolutionSequence 9 | 10 | 11 | class UpConvolution(Layer): 12 | """An UpConvolution combines several skip-connected ConvolutionSequence 13 | blocks separated by nonlinearities and batch normalization. It can 14 | optionally include an unpooling step at the end. 15 | """ 16 | 17 | convolutions: List[ConvolutionSequence] 18 | mesh_unpool: Optional[MeshUnpool] 19 | batch_normalizations: List[BatchNormalization] 20 | leaky_relu: LeakyReLU 21 | 22 | def __init__( 23 | self, 24 | out_channels: int, 25 | convolutions_per_sequence: int, 26 | num_sequences: int, 27 | leaky_relu_alpha: float, 28 | unpool: bool, 29 | ): 30 | Layer.__init__(self) 31 | self.convolutions = [ 32 | ConvolutionSequence(out_channels, convolutions_per_sequence) 33 | for _ in range(num_sequences) 34 | ] 35 | self.leaky_relu = LeakyReLU(leaky_relu_alpha) 36 | self.batch_normalizations = [BatchNormalization() for _ in range(num_sequences)] 37 | self.mesh_unpool = MeshUnpool() if unpool else None 38 | 39 | def call( 40 | self, mesh: Mesh, features: tf.Tensor, snapshot: CollapseSnapshot 41 | ) -> tf.Tensor: 42 | assert features_valid(mesh, features) 43 | 44 | # Run through the first ConvolutionSequence. 45 | out_features = self.convolutions[0](mesh, features) 46 | out_features = self.leaky_relu(out_features) 47 | out_features = self.batch_normalizations[0](out_features, training=True) 48 | 49 | # Run the remaining ConvolutionSequences with skip connections. 50 | in_features = out_features 51 | for i in range(1, len(self.convolutions)): 52 | # Create out_features using in_features. 53 | out_features = self.convolutions[i](mesh, in_features) 54 | out_features = self.leaky_relu(out_features) 55 | out_features = self.batch_normalizations[i](out_features, training=True) 56 | 57 | # Add the skip connections. 58 | out_features += in_features 59 | in_features = out_features 60 | 61 | # Optionally run unpooling. 62 | if self.mesh_unpool is not None: 63 | return self.mesh_unpool(mesh, out_features, snapshot) 64 | return out_features 65 | -------------------------------------------------------------------------------- /source/layers/composite/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/layers/composite/__init__.py -------------------------------------------------------------------------------- /source/layers/composite/__tests__/TestConvolutionSequence.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import trimesh 3 | import tensorflow as tf 4 | import numpy as np 5 | from ....mesh.Mesh import Mesh 6 | from ..ConvolutionSequence import ConvolutionSequence 7 | 8 | 9 | class TestConvolutionSequence(unittest.TestCase): 10 | def setUp(self) -> None: 11 | with open("data/objs/icosahedron.obj", "r") as f: 12 | mesh = trimesh.exchange.obj.load_obj(f) 13 | self.mesh = Mesh(mesh["vertices"], mesh["faces"]) 14 | 15 | def test_output_shape(self): 16 | num_edges = self.mesh.edges.shape[0] 17 | sequence = ConvolutionSequence(11, 3) 18 | 19 | initial_feature_values = np.random.random((num_edges, 3)) 20 | features = tf.Variable(initial_feature_values, dtype=tf.float32) 21 | 22 | result = sequence(self.mesh, features) 23 | 24 | self.assertEqual(result.shape, (30, 11)) 25 | -------------------------------------------------------------------------------- /source/layers/composite/__tests__/TestDecoder.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import trimesh 3 | import tensorflow as tf 4 | import numpy as np 5 | from ....mesh.Mesh import Mesh 6 | from ..Encoder import Encoder 7 | from ..Decoder import Decoder 8 | 9 | 10 | class TestDecoder(unittest.TestCase): 11 | def setUp(self) -> None: 12 | with open("data/objs/icosahedron.obj", "r") as f: 13 | mesh = trimesh.exchange.obj.load_obj(f) 14 | self.mesh = Mesh(mesh["vertices"], mesh["faces"]) 15 | 16 | def test_output_shape(self): 17 | num_edges = self.mesh.edges.shape[0] 18 | 19 | encoder = Encoder((12, 24, 48), 3, 4, 0.1, (27, 24, None)) 20 | decoder = Decoder((24, 12, 6), 3, 4, 0.1, (27, 24, None)) 21 | initial_feature_values = np.random.random((num_edges, 6)) 22 | in_features = tf.Variable(initial_feature_values, dtype=tf.float32) 23 | 24 | encoding, snapshots = encoder(self.mesh, in_features) 25 | out_features = decoder(self.mesh, encoding, snapshots) 26 | 27 | self.assertEqual(out_features.shape, (30, 6)) 28 | -------------------------------------------------------------------------------- /source/layers/composite/__tests__/TestDownConvolution.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import trimesh 3 | import tensorflow as tf 4 | import numpy as np 5 | from ....mesh.Mesh import Mesh 6 | from ..DownConvolution import DownConvolution 7 | 8 | 9 | class TestDownConvolution(unittest.TestCase): 10 | def setUp(self) -> None: 11 | with open("data/objs/icosahedron.obj", "r") as f: 12 | mesh = trimesh.exchange.obj.load_obj(f) 13 | self.mesh = Mesh(mesh["vertices"], mesh["faces"]) 14 | 15 | def test_output_shape_with_pooling(self): 16 | num_edges = self.mesh.edges.shape[0] 17 | down = DownConvolution(17, 3, 4, 0.1, 18) 18 | initial_feature_values = np.random.random((num_edges, 6)) 19 | in_features = tf.Variable(initial_feature_values, dtype=tf.float32) 20 | 21 | out_features, _ = down(self.mesh, in_features) 22 | 23 | self.assertEqual(out_features.shape, (18, 17)) 24 | 25 | def test_output_shape_without_pooling(self): 26 | num_edges = self.mesh.edges.shape[0] 27 | down = DownConvolution(31, 3, 4, 0.1, None) 28 | initial_feature_values = np.random.random((num_edges, 2)) 29 | in_features = tf.Variable(initial_feature_values, dtype=tf.float32) 30 | 31 | out_features, _ = down(self.mesh, in_features) 32 | 33 | self.assertEqual(out_features.shape, (30, 31)) 34 | -------------------------------------------------------------------------------- /source/layers/composite/__tests__/TestEncoder.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import trimesh 3 | import tensorflow as tf 4 | import numpy as np 5 | from ....mesh.Mesh import Mesh 6 | from ..Encoder import Encoder 7 | 8 | 9 | class TestEncoder(unittest.TestCase): 10 | def setUp(self) -> None: 11 | with open("data/objs/icosahedron.obj", "r") as f: 12 | mesh = trimesh.exchange.obj.load_obj(f) 13 | self.mesh = Mesh(mesh["vertices"], mesh["faces"]) 14 | 15 | def test_output_shape(self): 16 | num_edges = self.mesh.edges.shape[0] 17 | encoder = Encoder((12, 24, 48), 3, 4, 0.1, (27, 24, None)) 18 | initial_feature_values = np.random.random((num_edges, 6)) 19 | in_features = tf.Variable(initial_feature_values, dtype=tf.float32) 20 | 21 | out_features, _ = encoder(self.mesh, in_features) 22 | 23 | self.assertEqual(out_features.shape, (24, 48)) 24 | -------------------------------------------------------------------------------- /source/layers/composite/__tests__/TestUpConvolution.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import trimesh 3 | import tensorflow as tf 4 | import numpy as np 5 | from ....mesh.Mesh import Mesh 6 | from ..DownConvolution import DownConvolution 7 | from ..UpConvolution import UpConvolution 8 | 9 | 10 | class TestUpConvolution(unittest.TestCase): 11 | def setUp(self) -> None: 12 | with open("data/objs/icosahedron.obj", "r") as f: 13 | mesh = trimesh.exchange.obj.load_obj(f) 14 | self.mesh = Mesh(mesh["vertices"], mesh["faces"]) 15 | 16 | def test_output_shape_with_pooling(self): 17 | num_edges = self.mesh.edges.shape[0] 18 | down = DownConvolution(17, 3, 4, 0.1, 18) 19 | up = UpConvolution(6, 3, 4, 0.1, True) 20 | initial_feature_values = np.random.random((num_edges, 6)) 21 | in_features = tf.Variable(initial_feature_values, dtype=tf.float32) 22 | 23 | encoding, snapshot = down(self.mesh, in_features) 24 | out_features = up(self.mesh, encoding, snapshot) 25 | 26 | self.assertEqual(out_features.shape, (30, 6)) 27 | 28 | def test_output_shape_without_pooling(self): 29 | num_edges = self.mesh.edges.shape[0] 30 | down = DownConvolution(17, 3, 4, 0.1, None) 31 | up = UpConvolution(6, 3, 4, 0.1, False) 32 | initial_feature_values = np.random.random((num_edges, 6)) 33 | in_features = tf.Variable(initial_feature_values, dtype=tf.float32) 34 | 35 | encoding, snapshot = down(self.mesh, in_features) 36 | out_features = up(self.mesh, encoding, snapshot) 37 | 38 | self.assertEqual(out_features.shape, (30, 6)) 39 | -------------------------------------------------------------------------------- /source/layers/composite/__tests__/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/layers/composite/__tests__/__init__.py -------------------------------------------------------------------------------- /source/layers/convolution/MeshConvolution.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import numpy as np 3 | from tensorflow.keras.layers import Layer 4 | from ...mesh.Mesh import Mesh 5 | from ..feature import features_valid 6 | 7 | 8 | class MeshConvolution(Layer): 9 | """MeshConvolution implements MeshCNN's mesh convolution operation by 10 | computing convolution between edges and 4 incident (1-ring) edge neighbors. 11 | """ 12 | 13 | def __init__( 14 | self, 15 | out_channels, 16 | kernel_initializer="glorot_uniform", 17 | bias_initializer="zeros", 18 | ): 19 | Layer.__init__(self) 20 | self.conv = tf.keras.layers.Conv2D( 21 | filters=out_channels, 22 | kernel_size=(1, 5), 23 | kernel_initializer=kernel_initializer, 24 | bias_initializer=bias_initializer, 25 | ) 26 | 27 | def call(self, mesh: Mesh, features: tf.Tensor) -> tf.Tensor: 28 | """Compute convolution between edges and 4 incident (1-ring) edge 29 | neighbors using standard Conv2D. 30 | """ 31 | assert features_valid(mesh, features) 32 | feature_image = self.create_feature_image(mesh, features) 33 | edge_feats = self.conv(feature_image) 34 | return edge_feats[0, :, 0, :] 35 | 36 | def create_feature_image(self, mesh: Mesh, features: tf.Tensor) -> tf.Tensor: 37 | """Using the connectivity information in mesh.edge_to_neighbor, create a 38 | an "image" that can be used by conv2d. The image's dimensions shape is 39 | (1, num_edges, 5, feature_size). Think of feature_size as in_channels. 40 | """ 41 | 42 | # Create neighborhoods of the original edge plus its neighbors. 43 | neighborhoods = np.concatenate( 44 | [np.arange(mesh.edges.shape[0])[:, None], mesh.edge_to_neighbors], axis=1 45 | ) 46 | 47 | # Gather features into the shape (num_edges, 5, feature_size). 48 | f = tf.gather(features, neighborhoods, axis=0) 49 | 50 | # Apply the symmetric functions to make an convolution equivariant. 51 | x_1 = f[:, 1, :] + f[:, 3, :] 52 | x_2 = f[:, 2, :] + f[:, 4, :] 53 | x_3 = tf.math.abs(f[:, 1, :] - f[:, 3, :]) 54 | x_4 = tf.math.abs(f[:, 2, :] - f[:, 4, :]) 55 | image = tf.stack([f[:, 0, :], x_1, x_2, x_3, x_4], axis=1) 56 | 57 | # Add a fake batch dimension. 58 | return image[None, :, :, :] 59 | -------------------------------------------------------------------------------- /source/layers/convolution/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/layers/convolution/__init__.py -------------------------------------------------------------------------------- /source/layers/convolution/__tests__/TestMeshConvolution.py: -------------------------------------------------------------------------------- 1 | from source.mesh.MeshChecker import MeshChecker 2 | import unittest 3 | import trimesh 4 | import tensorflow as tf 5 | import numpy as np 6 | from ....mesh.Mesh import Mesh 7 | from ..MeshConvolution import MeshConvolution 8 | 9 | 10 | class TestMeshConvolution(unittest.TestCase): 11 | def setUp(self) -> None: 12 | with open("data/objs/icosahedron.obj", "r") as f: 13 | mesh = trimesh.exchange.obj.load_obj(f) 14 | self.mesh = Mesh(mesh["vertices"], mesh["faces"]) 15 | 16 | def test_output_shape(self): 17 | num_edges = self.mesh.edges.shape[0] 18 | self.mesh_convolution = MeshConvolution(2) 19 | 20 | initial_feature_values = np.zeros((num_edges, 3)) 21 | initial_feature_values[:, :] = np.arange(num_edges)[:, None] 22 | 23 | features = tf.Variable(initial_feature_values, dtype=tf.float32) 24 | result = self.mesh_convolution(self.mesh, features) 25 | 26 | self.assertEqual(result.shape, (30, 2)) 27 | -------------------------------------------------------------------------------- /source/layers/convolution/__tests__/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/layers/convolution/__tests__/__init__.py -------------------------------------------------------------------------------- /source/layers/feature.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | from ..mesh.Mesh import Mesh 3 | 4 | 5 | def features_valid(mesh: Mesh, features: tf.Tensor) -> bool: 6 | """Check whether a feature tensor is compatible with the specified mesh.""" 7 | 8 | # A feature tensor's shape should be (feature_length, num_edges). 9 | return tf.rank(features) == 2 and features.shape[0] == mesh.edges.shape[0] -------------------------------------------------------------------------------- /source/layers/pooling/CollapseSnapshot.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, List, Set 2 | import numpy as np 3 | from ...mesh.Mesh import Mesh 4 | from ...mesh.EdgeConnection import EdgeConnection 5 | 6 | 7 | class CollapseSnapshot: 8 | """This holds information about which edges in a mesh were collapsed. It's 9 | used to make the pooling operation reversible. 10 | """ 11 | 12 | # This is the mesh this CollapseSnapshot references. 13 | mesh: Mesh 14 | 15 | # This square matrix with shape (num_edges, num_edges) holds information 16 | # about how the mesh's edges have been collapsed. A nonzero entry at (i, j) 17 | # indicates that the ith edge is the parent of the jth edge. Note that all 18 | # edges start parented to themselves. 19 | relationships: np.ndarray 20 | 21 | # These are copies of the mesh's fields. 22 | vertices: np.ndarray # (num_vertices, 3) 23 | edges: np.ndarray # (num_edges, 2) 24 | edge_to_neighbors: np.ndarray # (num_edges, 4) 25 | edge_lookup: np.ndarray # (num_edges, 4) 26 | vertex_to_edges: List[Optional[Set[EdgeConnection]]] 27 | 28 | def __init__(self, mesh: Mesh) -> None: 29 | # Assume we're starting with a clean mesh, i.e. no pooling/subdivision 30 | # has occurred. 31 | assert (mesh.edge_mask == True).all() 32 | assert (mesh.vertex_mask == True).all() 33 | 34 | # Save the information needed to reconstruct the mesh. 35 | # Note that this does not include the masks (see above) or the faces. 36 | # The faces are not included because collapse_edge does not modify them. 37 | self.vertices = np.copy(mesh.vertices) 38 | self.edges = np.copy(mesh.edges) 39 | self.edge_to_neighbors = np.copy(mesh.edge_to_neighbors) 40 | self.edge_lookup = np.copy(mesh.edge_lookup) 41 | self.vertex_to_edges = [set(ec) for ec in mesh.vertex_to_edges] 42 | 43 | # The identity matrix parents all edges to themselves. 44 | self.relationships = np.eye(mesh.edges.shape[0]) 45 | 46 | def reparent(self, child_edge_key: int, parent_edge_key: int) -> None: 47 | """Reparent the child edge and its children to the parent edge.""" 48 | self.relationships[parent_edge_key, :] += self.relationships[child_edge_key, :] 49 | self.relationships[child_edge_key, :] = 0 50 | 51 | def extract_relationships(self): 52 | """Once a number of edges have been reparented, relationships will be a 53 | square matrix with several zero rows. This returns relationships with 54 | its zero rows removed. 55 | """ 56 | return self.relationships[np.sum(self.relationships, axis=1) != 0] -------------------------------------------------------------------------------- /source/layers/pooling/MeshPool.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import numpy as np 3 | from tensorflow.keras.layers import Layer 4 | from ...mesh.Mesh import Mesh 5 | from ..feature import features_valid 6 | from .collapse_edge import collapse_edge 7 | from .CollapseSnapshot import CollapseSnapshot 8 | 9 | 10 | class MeshPool(Layer): 11 | """MeshPool implements MeshCNN's mesh pooling. It collapses edges until a 12 | specified number of edges is reached. Each MeshPool should be paired with 13 | a corresponding MeshUnpool. Note that MeshPool's collapse operation does not 14 | update a mesh's faces (i.e. the mesh is valid except for its faces). This is 15 | because the edge structure, which is derived from the face structure, is all 16 | that is needed for pooling and un-pooling to work. 17 | """ 18 | 19 | # Edge collapsing continues until the mesh's number of edges is edge_target. 20 | edge_target: int 21 | 22 | def __init__(self, edge_target: int) -> None: 23 | Layer.__init__(self) 24 | self.edge_target = edge_target 25 | 26 | def call(self, mesh: Mesh, features: tf.Tensor): 27 | assert features_valid(mesh, features) 28 | 29 | # Create a snapshot of the mesh's structure. 30 | # This contains all the information needed to undo the pooling (edge 31 | # collapse) operation. It also contains information about how the old 32 | # edges/features are mapped to the new ones in the relationships matrix. 33 | snapshot = CollapseSnapshot(mesh) 34 | 35 | # Sort the features by their L2 norms. 36 | values = tf.norm(features, axis=0) 37 | sorted_edge_keys = tf.argsort(values, direction="DESCENDING") 38 | 39 | # Collapse edges until edge_target is hit. 40 | # Keep track of how edges were merged in the snapshot's relationships 41 | # matrix, which will be used to determine how features will be pooled 42 | # and later unpooled. 43 | for edge_key in sorted_edge_keys: 44 | collapse_edge(mesh, edge_key.numpy(), snapshot) 45 | if mesh.num_edges <= self.edge_target: 46 | break 47 | 48 | # Rebuild the mesh to remove masked vertices and edges. 49 | mesh.collapse_masked_elements() 50 | 51 | # Return both the pooled features and the information needed to unpool. 52 | return self.pool_features(features, snapshot), snapshot 53 | 54 | def pool_features( 55 | self, features: tf.Tensor, snapshot: CollapseSnapshot 56 | ) -> tf.Tensor: 57 | """Use the snapshot's relationships to pool the features. Each new 58 | edge's feature is a weighted average of its children's features. 59 | """ 60 | relationships = snapshot.extract_relationships() 61 | weighted = relationships / np.sum(relationships, axis=1)[:, None] 62 | return weighted @ features 63 | -------------------------------------------------------------------------------- /source/layers/pooling/MeshUnpool.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import numpy as np 3 | from tensorflow.keras.layers import Layer 4 | from ...mesh.Mesh import Mesh 5 | from ..feature import features_valid 6 | from .collapse_edge import collapse_edge 7 | from .CollapseSnapshot import CollapseSnapshot 8 | 9 | 10 | class MeshUnpool(Layer): 11 | """MeshUnpool implements MeshCNN's mesh unpooling. It reverses the edge 12 | collapses carried out by MeshPool and distributes each edge's features to 13 | its children in the snapshot's relationships. 14 | """ 15 | 16 | def __init__(self) -> None: 17 | Layer.__init__(self) 18 | 19 | def call(self, mesh: Mesh, features: tf.Tensor, snapshot: CollapseSnapshot): 20 | assert features_valid(mesh, features) 21 | 22 | # Restore the mesh's pre-collapse state. 23 | mesh.vertices = snapshot.vertices 24 | mesh.edges = snapshot.edges 25 | mesh.edge_to_neighbors = snapshot.edge_to_neighbors 26 | mesh.edge_lookup = snapshot.edge_lookup 27 | mesh.vertex_to_edges = snapshot.vertex_to_edges 28 | mesh.edge_mask = np.ones((mesh.edges.shape[0]), dtype=np.bool) 29 | mesh.vertex_mask = np.ones((mesh.vertices.shape[0]), dtype=np.bool) 30 | mesh.num_edges = mesh.edges.shape[0] 31 | 32 | # Use the snapshot's relationships to unpool the features. 33 | return self.unpool_features(features, snapshot) 34 | 35 | def unpool_features( 36 | self, features: tf.Tensor, snapshot: CollapseSnapshot 37 | ) -> tf.Tensor: 38 | """Use the snapshot's relationships to unpool the features. Each new 39 | edge's feature is simply its parent's feature. 40 | """ 41 | return snapshot.extract_relationships().T @ features 42 | -------------------------------------------------------------------------------- /source/layers/pooling/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/layers/pooling/__init__.py -------------------------------------------------------------------------------- /source/layers/pooling/__tests__/TestCollapseEdge.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import trimesh 3 | import numpy as np 4 | from typing import Tuple 5 | from ....mesh.MeshChecker import MeshChecker 6 | from ....mesh.Mesh import Mesh 7 | from ..CollapseSnapshot import CollapseSnapshot 8 | from ..collapse_edge import check_collapse_manifold, collapse_edge 9 | 10 | 11 | class TestCollapseEdge(unittest.TestCase): 12 | def load_obj(_, file_name: str) -> Tuple[np.ndarray]: 13 | with open(file_name, "r") as f: 14 | mesh = trimesh.exchange.obj.load_obj(f) 15 | return mesh["vertices"], mesh["faces"] 16 | 17 | def test_check_collapse_manifold_for_invalid(self): 18 | vertices, faces = self.load_obj("data/objs/tetrahedron.obj") 19 | mesh = Mesh(vertices, faces) 20 | for edge_key in range(mesh.edges.shape[0]): 21 | self.assertFalse(check_collapse_manifold(mesh, edge_key)) 22 | 23 | def test_check_collapse_manifold_for_valid(self): 24 | vertices, faces = self.load_obj("data/objs/icosahedron.obj") 25 | mesh = Mesh(vertices, faces) 26 | for edge_key in range(mesh.edges.shape[0]): 27 | self.assertTrue(check_collapse_manifold(mesh, edge_key)) 28 | 29 | def test_collapse_individual_icosahedron_edges(self): 30 | vertices, faces = self.load_obj("data/objs/icosahedron.obj") 31 | num_edges = Mesh(vertices, faces).edges.shape[0] 32 | for edge_key in range(num_edges): 33 | mesh = Mesh(vertices, faces) 34 | snapshot = CollapseSnapshot(mesh) 35 | checker = MeshChecker(mesh) 36 | self.assertTrue(collapse_edge(mesh, edge_key, snapshot)) 37 | self.assertTrue(checker.check_validity()) 38 | 39 | def test_collapse_sequential_icosahedron_edges(self): 40 | vertices, faces = self.load_obj("data/objs/icosahedron.obj") 41 | mesh = Mesh(vertices, faces) 42 | snapshot = CollapseSnapshot(mesh) 43 | checker = MeshChecker(mesh) 44 | edge_key = 0 45 | for _ in range(mesh.edges.shape[0]): 46 | collapse_edge(mesh, edge_key, snapshot) 47 | self.assertTrue(checker.check_validity()) 48 | edge_key += 1 49 | -------------------------------------------------------------------------------- /source/layers/pooling/__tests__/TestMeshPool.py: -------------------------------------------------------------------------------- 1 | from source.mesh.MeshChecker import MeshChecker 2 | import unittest 3 | import trimesh 4 | import tensorflow as tf 5 | import numpy as np 6 | from ....mesh.Mesh import Mesh 7 | from ..MeshPool import MeshPool 8 | from ..CollapseSnapshot import CollapseSnapshot 9 | 10 | 11 | class TestMeshPool(unittest.TestCase): 12 | def setUp(self) -> None: 13 | with open("data/objs/icosahedron.obj", "r") as f: 14 | mesh = trimesh.exchange.obj.load_obj(f) 15 | self.mesh = Mesh(mesh["vertices"], mesh["faces"]) 16 | 17 | def test_mesh_remains_valid(self): 18 | num_edges = self.mesh.edges.shape[0] 19 | self.mesh_pool = MeshPool(num_edges - 1) 20 | 21 | initial_feature_values = np.zeros((num_edges, 3)) 22 | initial_feature_values[:, :] = np.arange(num_edges)[:, None] 23 | 24 | features = tf.Variable(initial_feature_values, dtype=tf.float32) 25 | self.mesh_pool(self.mesh, features) 26 | 27 | checker = MeshChecker(self.mesh) 28 | self.assertTrue(checker.check_validity()) 29 | 30 | def test_weighted_average(self): 31 | num_edges = self.mesh.edges.shape[0] 32 | self.mesh_pool = MeshPool(num_edges // 2) 33 | 34 | features = tf.Variable(np.ones((num_edges, 7)), dtype=tf.float32) 35 | new_features, _ = self.mesh_pool(self.mesh, features) 36 | 37 | self.assertTrue(np.allclose(new_features.numpy(), 1)) 38 | -------------------------------------------------------------------------------- /source/layers/pooling/__tests__/TestMeshUnpool.py: -------------------------------------------------------------------------------- 1 | from source.mesh.MeshChecker import MeshChecker 2 | import unittest 3 | import trimesh 4 | import tensorflow as tf 5 | import numpy as np 6 | from ....mesh.Mesh import Mesh 7 | from ..MeshPool import MeshPool 8 | from ..MeshUnpool import MeshUnpool 9 | from ..CollapseSnapshot import CollapseSnapshot 10 | 11 | 12 | class TestMeshUnpool(unittest.TestCase): 13 | def setUp(self) -> None: 14 | with open("data/objs/icosahedron.obj", "r") as f: 15 | mesh = trimesh.exchange.obj.load_obj(f) 16 | self.mesh = Mesh(mesh["vertices"], mesh["faces"]) 17 | 18 | def test_mesh_remains_valid(self): 19 | num_edges = self.mesh.edges.shape[0] 20 | self.mesh_pool = MeshPool(num_edges // 2) 21 | self.mesh_unpool = MeshUnpool() 22 | 23 | original_features = tf.Variable(np.ones((num_edges, 7)), dtype=tf.float32) 24 | pooled_features, snapshot = self.mesh_pool(self.mesh, original_features) 25 | self.mesh_unpool(self.mesh, pooled_features, snapshot) 26 | 27 | checker = MeshChecker(self.mesh) 28 | self.assertTrue(checker.check_validity()) 29 | 30 | def test_weighted_average(self): 31 | num_edges = self.mesh.edges.shape[0] 32 | self.mesh_pool = MeshPool(num_edges // 2) 33 | self.mesh_unpool = MeshUnpool() 34 | 35 | original_features = tf.Variable(np.ones((num_edges, 7)), dtype=tf.float32) 36 | pooled_features, snapshot = self.mesh_pool(self.mesh, original_features) 37 | unpooled_features = self.mesh_unpool(self.mesh, pooled_features, snapshot) 38 | 39 | self.assertTrue( 40 | np.allclose(original_features.numpy(), unpooled_features.numpy()) 41 | ) 42 | -------------------------------------------------------------------------------- /source/layers/pooling/__tests__/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/layers/pooling/__tests__/__init__.py -------------------------------------------------------------------------------- /source/layers/pooling/collapse_edge.py: -------------------------------------------------------------------------------- 1 | from ...mesh.Mesh import Mesh 2 | from ...mesh.EdgeConnection import EdgeConnection 3 | from .CollapseSnapshot import CollapseSnapshot 4 | 5 | 6 | def collapse_edge(mesh: Mesh, edge_key: int, snapshot: CollapseSnapshot): 7 | """Collapse an edge (two triangles) and update edge_mask to remove three 8 | edges: the one that's being collapsed and two edges that would overlap 9 | other edges as a result of the collapse. The mesh should be in a 10 | consistent state before and after this is called. Update the provided 11 | CollapseSnapshot with information about which edges have been collapsed. 12 | Note that the collapsed edge is parented to both surviving edges. 13 | 14 | Important: This edge collapse operation does not modify the faces. As a 15 | result, the mesh's faces will point to invalid vertices. This is because 16 | it's assumed that the collapse (pooling) operation will be accompanied by an 17 | unpooling operation, and that it won't be necessary to export a valid mesh 18 | created by this edge collapse. In other words, this edge collapse only 19 | updates the mesh with the information needed to pool edge-based features and 20 | determine which further edge collapses would be valid. 21 | 22 | Return True if the collapse succeeds. If the collapse would create 23 | non-manifold geometry, return False. 24 | """ 25 | 26 | # Check for edge collapse validity. 27 | if not check_collapse_manifold(mesh, edge_key): 28 | return False 29 | 30 | # Collapse both sides (triangles). 31 | kept_vertex, masked_vertex = mesh.edges[edge_key] 32 | masked_edge_a = collapse_side(mesh, edge_key, 0, snapshot) 33 | masked_edge_b = collapse_side(mesh, edge_key, 2, snapshot) 34 | 35 | # Mask off the collapsed edge. 36 | mesh.edge_mask[edge_key] = False 37 | 38 | # Mask off the vertex that's being removed. 39 | mesh.vertex_mask[masked_vertex] = False 40 | 41 | # Move the kept vertex. 42 | mesh.vertices[kept_vertex, :] = ( 43 | mesh.vertices[kept_vertex, :] + mesh.vertices[masked_vertex, :] 44 | ) * 0.5 45 | 46 | # This gets rid of references to the specified edge in vertex_to_edges. 47 | def discard_edge(discard_edge_key): 48 | vertex_a, vertex_b = mesh.edges[discard_edge_key] 49 | mesh.vertex_to_edges[vertex_a].remove(EdgeConnection(discard_edge_key, 0)) 50 | mesh.vertex_to_edges[vertex_b].remove(EdgeConnection(discard_edge_key, 1)) 51 | 52 | # Update the mesh's vertex_to_edges. 53 | masked_vertex_edges = mesh.vertex_to_edges[masked_vertex] 54 | discard_edge(edge_key) 55 | discard_edge(masked_edge_a) 56 | discard_edge(masked_edge_b) 57 | for edge_connection in masked_vertex_edges: 58 | mesh.edges[ 59 | edge_connection.edge_index, edge_connection.index_in_edge 60 | ] = kept_vertex 61 | mesh.vertex_to_edges[kept_vertex].update(masked_vertex_edges) 62 | mesh.vertex_to_edges[masked_vertex] = None 63 | 64 | # Update the mesh's num_edges. 65 | mesh.num_edges -= 3 66 | 67 | return True 68 | 69 | 70 | def check_collapse_manifold(mesh: Mesh, edge_key: int): 71 | """Return False if collapsing the specified edge would cause 72 | non-manifold geometry to be formed. There are two conditions that under 73 | which non-manifold geometry is created: 74 | 1) A vertex that neighbors one of the edges has degree 3. 75 | 2) The edge's vertices do not share exactly 2 vertex neighbors. 76 | """ 77 | 78 | # If the edge doesn't exist anymore, it can't be collapsed. 79 | if mesh.edge_mask[edge_key] == False: 80 | return False 81 | 82 | vertex_a, vertex_b = mesh.edges[edge_key] 83 | 84 | # Get the one-ring neighbors around the edge's vertices. 85 | def get_neighbor_vertices(vertex): 86 | neighbor_has_degree_3 = False 87 | neighbor_vertices = set() 88 | for edge_connection in mesh.vertex_to_edges[vertex]: 89 | other_vertex = mesh.edges[ 90 | edge_connection.edge_index, 1 - edge_connection.index_in_edge 91 | ] 92 | if len(mesh.vertex_to_edges[other_vertex]) == 3: 93 | neighbor_has_degree_3 = True 94 | break 95 | 96 | neighbor_vertices.add(other_vertex) 97 | return neighbor_vertices, neighbor_has_degree_3 98 | 99 | # The mesh will remain manifold as long as there are exactly two shared 100 | # neighbors. 101 | vertex_a_neighbors, a_neighbor_invalid = get_neighbor_vertices(vertex_a) 102 | vertex_b_neighbors, b_neighbor_invalid = get_neighbor_vertices(vertex_b) 103 | return ( 104 | len(vertex_a_neighbors & vertex_b_neighbors) == 2 105 | and not a_neighbor_invalid 106 | and not b_neighbor_invalid 107 | ) 108 | 109 | 110 | def collapse_side(mesh: Mesh, edge_key: int, side: int, snapshot: CollapseSnapshot): 111 | """Collapse the triangle to the specified side of an edge that's being 112 | collapsed. Update edge_mask to exclude one of the edges that becomes 113 | overlapping as a result of the collapse. Calling this alone will leave 114 | the mesh in an inconsistent state. 115 | 116 | Returns the key for the edge that's masked off. 117 | """ 118 | 119 | # The triangle that's being collapsed has three edges: the one that's 120 | # being collapsed, its even neighbor (side is 0 or 2) and its odd 121 | # neighbor (side is 1 or 3). After the collapse, the even neighbor 122 | # remains, while the odd neighbor is masked off. 123 | assert side == 0 or side == 2 124 | ( 125 | even_neighbor_key, 126 | odd_neighbor_key, 127 | even_lookup, 128 | _, 129 | _, 130 | odd_lookup_opposite, 131 | _, 132 | odd_neighbor_neighbors, 133 | ) = get_face_neighborhood(mesh, edge_key, side) 134 | 135 | # Define functions to get the even and odd sides within a side group. 136 | get_even_side = lambda x: x - x % 2 137 | get_odd_side = lambda x: get_even_side(x) + 1 138 | 139 | # Rewrite the even neighbor's information to make the collapse happen. 140 | # First, replace the even neighbor's even neighbor (the one within the 141 | # collapsing triangle) with the odd neighbor's even neighbor (the one 142 | # outside the collapsing triangle). 143 | redirect_edges( 144 | mesh, 145 | even_neighbor_key, 146 | get_even_side(even_lookup), 147 | odd_neighbor_neighbors[0], 148 | mesh.edge_lookup[odd_neighbor_key, odd_lookup_opposite], 149 | ) 150 | 151 | # Next, do the same for the even neighbor's odd neighbor. 152 | redirect_edges( 153 | mesh, 154 | even_neighbor_key, 155 | get_odd_side(even_lookup), 156 | odd_neighbor_neighbors[1], 157 | mesh.edge_lookup[odd_neighbor_key, odd_lookup_opposite + 1], 158 | ) 159 | 160 | # Reparent the collapsed edge and the odd neighbor to the even neighbor. 161 | snapshot.reparent(odd_neighbor_key, even_neighbor_key) 162 | snapshot.reparent(edge_key, even_neighbor_key) 163 | 164 | # Mask off the odd neighbor. 165 | mesh.edge_mask[odd_neighbor_key] = False 166 | return odd_neighbor_key 167 | 168 | 169 | def redirect_edges( 170 | mesh: Mesh, 171 | edge_key_a: int, 172 | b_side_for_a: int, 173 | edge_key_b: int, 174 | a_side_for_b: int, 175 | ): 176 | """Update edge_to_neighbors and edge_lookup so that edges A and B point 177 | towards each other. Calling this alone will leave the mesh in an 178 | inconsistent state. 179 | """ 180 | mesh.edge_to_neighbors[edge_key_a, b_side_for_a] = edge_key_b 181 | mesh.edge_to_neighbors[edge_key_b, a_side_for_b] = edge_key_a 182 | mesh.edge_lookup[edge_key_a, b_side_for_a] = a_side_for_b 183 | mesh.edge_lookup[edge_key_b, a_side_for_b] = b_side_for_a 184 | 185 | 186 | def get_face_neighborhood(mesh: Mesh, edge_key: int, side: int): 187 | """Gather information needed for edge collapse. In a watertight mesh, 188 | each triangular face has three adjacent faces. 189 | """ 190 | assert side == 0 or side == 2 191 | 192 | # Find the keys for the neighbors on the specified sides. 193 | # Together, the original edge and its neighbors form a triangle. 194 | even_neighbor_key = mesh.edge_to_neighbors[edge_key, side] 195 | odd_neighbor_key = mesh.edge_to_neighbors[edge_key, side + 1] 196 | 197 | # Use edge_lookup to determine what side the original edge is on from 198 | # its neighbors' perspectives. 199 | even_lookup = mesh.edge_lookup[edge_key, side] 200 | odd_lookup = mesh.edge_lookup[edge_key, side + 1] 201 | 202 | # Define a lambda that gets the side index for the even neighbor that's 203 | # opposite an edge. Recall that an edge's 4 neighbors have sides 0 204 | # through 3, where (0, 1) and (2, 3) are on opposite sides of the edge. 205 | get_other_side = lambda x: 0 if x >= 2 else 2 206 | even_lookup_opposite = get_other_side(even_lookup) 207 | odd_lookup_opposite = get_other_side(odd_lookup) 208 | 209 | # For the original edge's neighbors on the specified side, get the other 210 | # two neighbors. 211 | even_neighbor_neighbors = ( 212 | mesh.edge_to_neighbors[even_neighbor_key, even_lookup_opposite], 213 | mesh.edge_to_neighbors[even_neighbor_key, even_lookup_opposite + 1], 214 | ) 215 | odd_neighbor_neighbors = ( 216 | mesh.edge_to_neighbors[odd_neighbor_key, odd_lookup_opposite], 217 | mesh.edge_to_neighbors[odd_neighbor_key, odd_lookup_opposite + 1], 218 | ) 219 | 220 | return ( 221 | even_neighbor_key, 222 | odd_neighbor_key, 223 | even_lookup, 224 | odd_lookup, 225 | even_lookup_opposite, 226 | odd_lookup_opposite, 227 | even_neighbor_neighbors, 228 | odd_neighbor_neighbors, 229 | ) -------------------------------------------------------------------------------- /source/loss/ChamferLossLayer.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import tensorflow_graphics.nn.loss.chamfer_distance as chamfer_distance 3 | from tensorflow.keras.layers import Layer 4 | import tensorflow_probability as tfp 5 | 6 | 7 | class ChamferLossLayer(Layer): 8 | def __init__(self) -> None: 9 | super(ChamferLossLayer, self).__init__() 10 | 11 | def call(self, cloud1, cloud2, num_samples): 12 | """ 13 | Arguments: 14 | cloud1 is a tf tensor of shape (N,P1,D) where: 15 | N is number of clouds in batch, 16 | P1 is the max number of points amoung all of the images in the batch 17 | D is dimensionality of system 18 | cloud2 is a tf tensor of shape (M,P2,D) where: 19 | N is number of clouds in batch, 20 | P2 is the max number of points amoung all of the images in the batch 21 | D is dimensionality of system 22 | """ 23 | 24 | # Assert that cloud1 and cloud2 are tensors of the correct shape. 25 | assert tf.is_tensor(cloud1) 26 | assert tf.shape(cloud1)[-1] == 3 27 | assert tf.is_tensor(cloud2) 28 | assert tf.shape(cloud2)[-1] == 3 29 | 30 | def subsample(cloud): 31 | num_points = tf.shape(cloud)[-2] 32 | point_sample_probs = tf.ones(num_points) / tf.cast( 33 | num_points, dtype=tf.float32 34 | ) 35 | point_distribution = tfp.distributions.Categorical(probs=point_sample_probs) 36 | points_to_sample = point_distribution.sample(num_samples) 37 | return tf.gather(cloud, points_to_sample) 38 | 39 | subsampled_cloud1 = subsample(cloud1) 40 | subsampled_cloud2 = subsample(cloud2) 41 | 42 | # Compute bidirectional, average loss using built in tfg function. 43 | # Returns the sum of (the mean, minimum, squared distance from cloud 1 44 | # to 2) and vice-versa (the mean, minimum, squared distance from cloud 2 45 | # to 1). 46 | return chamfer_distance.evaluate(subsampled_cloud1, subsampled_cloud2) -------------------------------------------------------------------------------- /source/loss/ConvergenceDetector.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | from typing import Deque 3 | 4 | 5 | class ConvergenceDetector: 6 | window_size: int 7 | threshold: float 8 | loss_window: Deque[float] 9 | running_average_window: Deque[float] 10 | 11 | def __init__(self, window_size: int = 25, threshold: float = 0.99) -> None: 12 | super().__init__() 13 | self.window_size = window_size 14 | self.threshold = threshold 15 | self.loss_window = deque() 16 | self.running_average_window = deque() 17 | 18 | def step(self, loss_value: float) -> bool: 19 | # Update the loss window. 20 | self.loss_window.append(loss_value) 21 | if len(self.loss_window) > self.window_size: 22 | self.loss_window.popleft() 23 | 24 | # Calculate the average over the window. 25 | loss_sum = 0 26 | for loss in self.loss_window: 27 | loss_sum += loss 28 | loss_average = loss_sum / len(self.loss_window) 29 | 30 | # Update the running average window. 31 | self.running_average_window.append(loss_average) 32 | if len(self.running_average_window) > self.window_size: 33 | past_average = self.running_average_window.popleft() 34 | 35 | # Indicate that convergence has been detected if the running average 36 | # of the loss hasn't been decreasing. 37 | if loss_average > past_average * self.threshold: 38 | return True 39 | 40 | return False -------------------------------------------------------------------------------- /source/loss/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/loss/__init__.py -------------------------------------------------------------------------------- /source/loss/__tests__/TestChamferLoss.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from tensorflow.python.keras.regularizers import get 3 | import trimesh 4 | import numpy as np 5 | from typing import Tuple 6 | import tensorflow as tf 7 | from ..ChamferLossLayer import ChamferLossLayer 8 | 9 | 10 | class TestChamferLoss(unittest.TestCase): 11 | def load_obj_into_mesh(self, file_name: str) -> Tuple[np.ndarray]: 12 | with open(file_name, "r") as f: 13 | mesh = trimesh.exchange.obj.load_obj(f) 14 | return np.float32(mesh["vertices"]), mesh["faces"] 15 | 16 | def test_chamfer_loss_test(self): 17 | chamfer_loss_layer = ChamferLossLayer() 18 | cloud1 = tf.convert_to_tensor([[1.0, 1.0, 1.0]], dtype=tf.float32) 19 | cloud2 = tf.convert_to_tensor( 20 | [[0.0, 0.0, 0.0], [2.0, 1.0, 1.0], [2.0, 3.0, 4.0]], 21 | dtype=tf.float32, 22 | ) 23 | # This is a bidirectional average square loss. 24 | # It should be (1)^2 + (3+1^2+(1+2^2+3^2))/3 = 1 + 6 = 7 25 | loss = chamfer_loss_layer(cloud1, cloud2) 26 | self.assertTrue(loss.numpy() - 7.0 < 0.01) 27 | -------------------------------------------------------------------------------- /source/loss/__tests__/TestLoss.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from tensorflow.python.keras.regularizers import get 3 | import trimesh 4 | import numpy as np 5 | from typing import Tuple 6 | import tensorflow as tf 7 | 8 | from ..loss import ( 9 | BeamGapLossLayer, 10 | discrete_project, 11 | get_looping_points, 12 | distance_within_cone, 13 | ) 14 | from ...mesh.Mesh import Mesh 15 | 16 | 17 | class TestLoss(unittest.TestCase): 18 | def load_obj_into_mesh(self, file_name: str) -> Tuple[np.ndarray]: 19 | with open(file_name, "r") as f: 20 | mesh = trimesh.exchange.obj.load_obj(f) 21 | return np.float32(mesh["vertices"]), mesh["faces"] 22 | 23 | def test_initialize_beam_gap_loss_layer(self): 24 | init_test = BeamGapLossLayer("cpu", discrete_project) 25 | self.assertTrue(True) 26 | 27 | def test_update_point_masks(self): 28 | def testing_target_function(mesh, point_cloud, threshold): 29 | return ( 30 | tf.convert_to_tensor([[0.0, 0.0, 0.0], [1.0, 0.0, 2.0]]), 31 | tf.convert_to_tensor([True, False]), 32 | ) 33 | 34 | test_layer = BeamGapLossLayer("cpu", testing_target_function) 35 | 36 | mesh = [] 37 | target_point_cloud = [] 38 | test_layer.update_points_masks(mesh, target_point_cloud) 39 | self.assertTrue( 40 | np.prod( 41 | np.equal(test_layer.points.numpy(), [[0.0, 0.0, 0.0], [1.0, 0.0, 2.0]]) 42 | ) 43 | ) 44 | self.assertTrue(np.prod(np.equal(test_layer.mask.numpy(), [True, False]))) 45 | 46 | def test_looping_mask(self): 47 | cloud1 = np.array( 48 | [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 1.0], [0.0, 0.0, 10.0]] 49 | ) 50 | cloud2 = np.array([[1.0, 0.0, 0.0], [2.0, 0.0, 0.0], [1.0, 1.0, 1.5]]) 51 | cloud1_loop = get_looping_points(cloud1, cloud2) 52 | cloud2_loop = get_looping_points(cloud2, cloud1) 53 | self.assertTrue(np.prod(np.equal(cloud1_loop, [True, True, True, False]))) 54 | self.assertTrue(np.prod(np.equal(cloud2_loop, [True, True, True]))) 55 | 56 | def test_distance_within_cone(self): 57 | cloud1 = np.array([[0.0, 0.0, 0.0], [0.0, 5.0, 5.0], [1.0, 1.0, 1.0]]) 58 | cloud1_normals = np.array( 59 | [[1.0, 0.0, 0.0], [0.0, -0.7071, -0.7071], [1.0, 0.0, 0.0]] 60 | ) 61 | cloud2 = np.array([[1.0, 0.0, 0.0], [2.0, 0.0, 0.0]]) 62 | dist, mask = distance_within_cone(cloud1, cloud1_normals, cloud2, 0.99) 63 | test = np.sum(dist - np.array([[1.0, 2.0], [7.14, 7.35], [1.41, 1.73]])) 64 | self.assertTrue( 65 | np.sum(dist - np.array([[1.0, 2.0], [7.14, 7.35], [1.41, 1.73]])) < 0.1 66 | ) 67 | self.assertTrue( 68 | np.prod( 69 | np.equal(mask, np.array([[True, True], [True, False], [False, False]])) 70 | ) 71 | ) 72 | 73 | def test_discrete_project(self): 74 | vertices, faces = self.load_obj_into_mesh("data/objs/icosahedron.obj") 75 | mesh = Mesh(vertices, faces) 76 | test_layer = BeamGapLossLayer("cpu", discrete_project) 77 | test_point_cloud, _ = mesh.sample_surface( 78 | tf.convert_to_tensor(mesh.vertices), 3 79 | ) 80 | test_layer.update_points_masks(mesh, test_point_cloud) 81 | points = test_layer.points.numpy() 82 | mask = test_layer.mask.numpy() 83 | self.assertTrue(True) 84 | -------------------------------------------------------------------------------- /source/loss/__tests__/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/loss/__tests__/__init__.py -------------------------------------------------------------------------------- /source/loss/loss.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import tensorflow as tf 3 | import scipy 4 | from tensorflow.keras.layers import Layer 5 | from ..mesh.Mesh import Mesh 6 | 7 | 8 | class BeamGapLossLayer(Layer): 9 | def __init__(self, target_function) -> None: 10 | 11 | super(BeamGapLossLayer, self).__init__() 12 | 13 | # Sets the target function. In this case is mesh.discrete project except for debugging 14 | self.target_function = target_function 15 | 16 | # Initializes: 17 | # self.points which is used to store the points that will eventually be used to calculate beam gap loss 18 | # self.masks which is used to store the mask that will eventually be used to calculate beam gap loss 19 | self.points = None 20 | self.masks = None 21 | 22 | def update_points_masks(self, mesh, vertices, target_point_cloud): 23 | 24 | """ 25 | Updates self.points and self.mask 26 | self.points: a (num_faces, 3) tensor that repersents the XYZ coordinates for the nearest point_cloud point to 27 | a given face's midpoints. Instead coordinates will be nan if: 28 | the face midpoint is looping (see below) 29 | there is no point cloud point within the cone (see below) 30 | self.mask: a (num_faces) tensor that is True if the coordinates in a given row are not nan, alse otherwise 31 | """ 32 | self.points, self.mask = self.target_function( 33 | mesh, vertices, target_point_cloud, 0.99 34 | ) 35 | 36 | def call(self, mesh: Mesh, vertices: tf.Tensor): 37 | # Only calculate beam-gap loss for points that aren't masked off. 38 | affected_faces = mesh.faces[self.mask] 39 | affected_points = self.points[self.mask] 40 | 41 | # This loss is the average distance from each unmasked point to the 42 | # closest valid point (i.e. within the cone) in the point cloud. 43 | face_vertices = tf.gather(vertices, affected_faces) 44 | face_midpoints = tf.math.reduce_mean(face_vertices, axis=1) 45 | distances = tf.norm(affected_points - face_midpoints, axis=1) 46 | return 10.0 * tf.math.reduce_mean(distances) 47 | 48 | 49 | def discrete_project(mesh, point_cloud, vertices=None, threshold=0.9): 50 | """ 51 | ARGS: 52 | self: a Mesh Object 53 | vertices: a tf tensor of shape (num_vertices, 3) 54 | point_cloud: a tf tensor of shap (num_points_in_cloud, 3) 55 | threshold: a float representing the cosine of the cone angle when checking to see if there exist any points 56 | within the target pointcloud that are relevant for the mash 57 | 58 | returns: 59 | pc_per_face: a (num_faces, 3) tensor that repersents the XYZ coordinates for the nearest point_cloud point to 60 | a given face's midpoints. Instead coordinates will be nan if: 61 | the face midpoint is looping (see below) 62 | there is no point cloud point within the cone (see below) 63 | pc_is_not_nan: a (num_faces) tensor that is True if the coordinates in a given row are not nan, alse otherwise 64 | """ 65 | 66 | # Check that point_cloud is tensor with corrrect dimension size 67 | # Check that self is a Mesh 68 | assert tf.is_tensor(point_cloud) 69 | if not tf.is_tensor(vertices): 70 | vertices = mesh.vertices 71 | else: 72 | vertices = vertices.numpy() 73 | 74 | assert tf.shape(point_cloud)[-1] == 3 75 | assert isinstance(mesh, Mesh) 76 | 77 | # Cast to data type double 78 | # Also get numpy to stop gradient 79 | point_cloud = tf.cast(point_cloud, dtype=tf.double) # (num_points_in_cloud, 3) 80 | point_cloud = point_cloud.numpy() 81 | 82 | # Get face normals for mesh, point normal to the mesh out of the face midpoints 83 | mesh_normals, _ = mesh.generate_face_areas_normals( 84 | tf.convert_to_tensor(vertices) 85 | ) # (num_faces, 3) 86 | 87 | mesh_normals = mesh_normals.numpy() 88 | 89 | # Get set of 3 vertexes associated with each face. 90 | # Compute the midpoint of every face by taking the mean of each dimension over the 3 vertices 91 | # Also get numpy as to stop gradient 92 | mid_points = vertices[ 93 | mesh.faces 94 | ] # (num_faces, 3(3 vertexes per face), 3(3 coordinates per vertex)) 95 | mid_points = tf.math.reduce_mean( 96 | mid_points, axis=1 97 | ).numpy() # (num_faces, 3(3 avg coordiates per face)) 98 | 99 | # See function below. 100 | # looping_mesh_points_mask: numpy boolean array of shape (num_faces) that is True if any of the k nearest points cloud 101 | # points of the row index's mesh point has the original mesh point amoung its k nearest neighbors 102 | looping_mesh_points_mask = get_looping_points(mid_points, point_cloud) 103 | 104 | # We are only interested in points that DONT map back to themselves because this indicates that the mesh is not 105 | # entering a deep enough cavity. Thus we pull from mid_points and normals using ~ mask 106 | # From here on, we will call the number of non-looping points num_non_looping 107 | masked_mid_points = mid_points[~looping_mesh_points_mask, :] # (num_non_looping, 3) 108 | masked_normals = mesh_normals[~looping_mesh_points_mask, :] # (num_non_looping, 3) 109 | 110 | # See function below. 111 | # distance: numpy arrar of shape (num_non_looping, num_points) which is the l2 distance from each of the face points to each of the 112 | # point cloud points 113 | # masked_within_cone: numpy boolean array of shape (num_non_looping, num_points) that is True if the (unit displacement between 114 | # the face mid_point and the point cloud point) dotted with (the face unit normal) is absolute value greater than the 115 | # threshold 116 | distance, masked_within_cone = distance_within_cone( 117 | masked_mid_points, masked_normals, point_cloud, threshold 118 | ) 119 | 120 | # For every non looping point that doesn't have a corresponding point on the PC within cone 121 | # Set the distances between that mesh point and all the PC points = inf 122 | distance[~masked_within_cone] += float( 123 | "inf" 124 | ) # (num_non_looping, num_points_in_cloud) 125 | 126 | # compute the minumum for each point in num_non_looping of the distances to all the points in the point cloud 127 | # also compute the index of the point cloud point associated with the minimum distance 128 | min = np.amin(distance, axis=-1) # (num_non_looping) 129 | arg_min = np.argmin(distance, axis=-1) # (num_non_looping) 130 | 131 | # For each non looping point gets the XYZ coordniates of the closest point in the point cloud 132 | # Fot all points where the was no point in the point cloud within the cone, sets the XYZ coordinates to nan 133 | pc_per_masked_face = point_cloud[arg_min, :].copy() 134 | pc_per_masked_face[min == float("inf"), :] = float("nan") 135 | 136 | # creates one of the output objects as a tensor with shape (num_faces, 3) 137 | pc_per_face = np.zeros( 138 | shape=(np.shape(mid_points)[0], 3), dtype=pc_per_masked_face.dtype 139 | ) 140 | 141 | # for every non looping point, we have already computed the xyz point on the PC that is closest 142 | # so this just writes those values to be the percomputed values 143 | pc_per_face[~looping_mesh_points_mask, :] = pc_per_masked_face 144 | 145 | # for all the looping points, just set the closest poinct cloud XYZs to nan 146 | pc_per_face[looping_mesh_points_mask, :] = float("nan") 147 | 148 | # checks to see if the XYZ points are nan becasue nan=/=nan 149 | # True indicates the XYZ point is real which happens when the corresponing mesh midpoint is non-looping AND 150 | # there is a point on the point cloud within the cone 151 | pc_is_non_nan = pc_per_face[:, 0] == pc_per_face[:, 0] 152 | 153 | # return objects 154 | return tf.convert_to_tensor(pc_per_face, dtype=tf.float32), tf.convert_to_tensor( 155 | pc_is_non_nan, dtype=tf.bool 156 | ) 157 | 158 | 159 | def get_looping_points(mid_points, point_cloud, k=3): 160 | """ 161 | ARGS: 162 | mid_points: numpy array of shape (num_faces, 3) representing the XYZ locations of the center of a mesh's faces 163 | point_cloud: numpy array of shape (num_points_in_cloud, 3) representing the XYZ locations of the points in the cloud 164 | 165 | return: 166 | looping_mesh_points_mask: numpy boolean array of shape (num_faces) that is True if any of the k nearest points cloud 167 | points of the row index's mesh point has the original mesh point amoung its k nearest neighbors 168 | """ 169 | 170 | # create a scipy KDTree to represent points 171 | # ref: https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.KDTree.html 172 | KDTree_cloud = scipy.spatial.KDTree(point_cloud) 173 | KDTree_mesh = scipy.spatial.KDTree(mid_points) 174 | 175 | # querries each KDTree to find the k nearest neighbors. As an example: 176 | # k_closest_cloud_points2mesh will have the shape (np.shape(mid_points)[0], k (in our case 3)) 177 | # each row corresponds to a midpoint, the nth index in that row corresponds to the index row into point_cloud 178 | # that is nth closest to the given midpoint 179 | # The same is querried for the mesh cloud as well (can be thought of as bi directional) 180 | _, k_closest_cloud_points2mesh = KDTree_cloud.query( 181 | mid_points, k=k, eps=0 182 | ) # (num_faces, k) 183 | _, k_closest_mesh_points2cloud = KDTree_mesh.query( 184 | point_cloud, k=k, eps=0 185 | ) # (num_points_in_cloud, k) 186 | 187 | # gets, for every face mid_point in the mesh, the k nearest neighbors in the point cloud 188 | # then, for all k of those points in the point cloud, gets the k nearest points in the set of mid_points 189 | # finally, reshapes from (num_faces, 3, 3) to (num_faces, 9) 190 | 191 | # as an example if the meshes were identical (mid_points == point_cloud) 192 | # with points [0,1,2] in mid_points and [a,b,c] in point_cloud 193 | # where dist(0,1) 0: 213 | # True if a point maps back to itself at least once amoung 9 options 214 | # boolean array of shape (num_faces) 215 | 216 | looping_mesh_points_mask = ( 217 | np.sum( 218 | np.equal( 219 | neighbors_neighbors, 220 | np.expand_dims(np.arange(0, np.shape(mid_points)[0]), axis=1), 221 | ), 222 | axis=1, 223 | ) 224 | > 0 225 | ) 226 | 227 | return looping_mesh_points_mask 228 | 229 | 230 | def distance_within_cone(mid_points, normals, point_cloud, threshold): 231 | """ 232 | ARGS: 233 | mid_points: numpy array of shape (num_faces, 3) representing the XYZ locations of the center of a mesh's faces 234 | normals: numpy array of shape (num_faces, 3) representing the normal vector to the face 235 | point_cloud: numpy array of shape (num_points_in_cloud, 3) representing the XYZ locations of the points in the cloud 236 | threshold: a float representing the cosine of the cone angle when checking to see if there exist any points 237 | within the target pointcloud that are relevant for the mash 238 | 239 | return: 240 | distance: numpy arrar of shape (num_faces, num_points) which is the l2 distance from each of the face points to each of the 241 | point cloud points 242 | masked_within_cone: numpy boolean array of shape (num_faces, num_points) that is True if the (unit displacement between 243 | the face mid_point and the point cloud point) dotted with (the face unit normal) is absolute value greater than the 244 | threshold 245 | """ 246 | 247 | # This uses broadcasting to compute the displacement from every point in the mid_points 248 | # to every point in the point cloud 249 | displacement = ( 250 | mid_points[:, None, :] - point_cloud 251 | ) # (num_mid_points, num_points_in_cloud, 3) 252 | 253 | # Compute distance via L2 norm on displacements 254 | distance = np.linalg.norm( 255 | displacement, axis=-1 256 | ) # (num_mid_points, num_points_in_cloud) 257 | 258 | # Another big, multistep operation. 259 | # First, normalize the displacement by its distance to compute the unit normal in the direction of the displacement 260 | # Second, compute the dot product of this norm with the mesh surface norm: 261 | # If the dot product is close to 1 then these vectors are nearly parallel 262 | # If the dot product is close to 0 then these vectors are perpendicular 263 | # Third, create the mask by checking if the dot product is close to 1 (within threshold) 264 | # In the end, boolean array of shape (num_non_looping) 265 | # True if there is a point within the cone 266 | 267 | masked_within_cone = np.array( 268 | np.abs( 269 | np.sum( 270 | (displacement / distance[:, :, None]) * normals[:, None, :], 271 | axis=-1, 272 | ) 273 | ) 274 | > threshold 275 | ) 276 | 277 | return distance, masked_within_cone -------------------------------------------------------------------------------- /source/mesh/EdgeConnection.py: -------------------------------------------------------------------------------- 1 | class EdgeConnection: 2 | """This stores information about how a vertex is connected to an edge.""" 3 | 4 | # The index of the edge the vertex is connected to. 5 | edge_index: int 6 | 7 | # The index of the vertex within the edge (i.e. 0 or 1). 8 | index_in_edge: int 9 | 10 | def __init__(self, edge_index: int, index_in_edge: int) -> None: 11 | assert edge_index >= 0 12 | assert index_in_edge == 0 or index_in_edge == 1 13 | self.edge_index = edge_index 14 | self.index_in_edge = index_in_edge 15 | 16 | def __hash__(self) -> int: 17 | return (self.edge_index, self.index_in_edge).__hash__() 18 | 19 | def __eq__(self, o: object) -> bool: 20 | if not isinstance(o, EdgeConnection): 21 | return False 22 | return self.edge_index == o.edge_index and self.index_in_edge == o.index_in_edge -------------------------------------------------------------------------------- /source/mesh/Mesh.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import tensorflow as tf 3 | import tensorflow_probability as tfp 4 | from .EdgeConnection import EdgeConnection 5 | from typing import Optional, List, Set 6 | 7 | 8 | class Mesh: 9 | # Hold the mesh's vertices as xyz points. 10 | vertices: np.ndarray # (num_vertices, 3) 11 | 12 | # Hold the mesh's faces as triplets of vertex indices. 13 | faces: np.ndarray # (num_faces, 3) 14 | 15 | # Hold the mesh's edges as couples of vertex indices. 16 | # Note that an edge's key is its row index within this array. 17 | edges: np.ndarray # (num_edges, 2) 18 | 19 | # Map each edge's key to those of its four neighbors. 20 | edge_to_neighbors: np.ndarray # (num_edges, 4) 21 | 22 | # Maps each edge's key to that edge's index within other entries in edge_to_neighbors. 23 | # Let k be an edge's key. Let k'_n be the nth value of edge_to_neighbors[k]. 24 | # Then the nth element of edge_lookup[k] will be the index of k within edge_to_neighbors[k'_n]. 25 | # This is used for MeshCNN's pooling operation. 26 | edge_lookup: np.ndarray # (num_edges, 4) 27 | 28 | # Map vertex indices to lists of edge indices. 29 | vertex_to_edges: List[Optional[Set[EdgeConnection]]] 30 | 31 | # This indicates whether an edge is still part of the mesh. 32 | edge_mask: np.ndarray 33 | 34 | # This indicates whether a vertex is still part of the mesh. 35 | vertex_mask: np.ndarray 36 | 37 | # The number of non-masked edges in the mesh. 38 | num_edges: int 39 | 40 | # The maximum vertex degree of the initial (un-collapsed) mesh. 41 | max_vertex_degree: int 42 | 43 | # This contains the contents of vertex_to_edges. The shape is the follwoing: 44 | # (num_vertices * max_vertex_degree, 2) 45 | # The first column contains the edge index, while the second contains the 46 | # index in the edge. For vertices whose degree is less than the maximum 47 | # degree, the empty parts have index -1. This is used to convert edge 48 | # features to vertex features. 49 | vertex_to_edges_tensor: tf.Tensor 50 | 51 | # This is also used to convert edge features to vertex features. 52 | vertex_to_degree: tf.Tensor 53 | 54 | def __init__(self, vertices: np.ndarray, faces: np.ndarray) -> None: 55 | """Create a new mesh. 56 | 57 | vertices: the mesh's vertices with shape (num_vertices, 3). 58 | faces: the mesh's faces with shape (num_faces, 3). 59 | """ 60 | assert isinstance(vertices, np.ndarray) 61 | assert vertices.shape[1] == 3 62 | assert isinstance(faces, np.ndarray) 63 | assert faces.shape[1] == 3 64 | self.vertices = vertices 65 | self.faces = faces 66 | self.build_acceleration_structures() 67 | self.edge_mask = np.ones((self.edges.shape[0]), dtype=np.bool) 68 | self.vertex_mask = np.ones((self.vertices.shape[0]), dtype=np.bool) 69 | self.num_edges = self.edges.shape[0] 70 | 71 | def build_acceleration_structures(self): 72 | # Map vertex indices 73 | self.vertex_to_edges = [set() for _ in range(self.vertices.shape[0])] 74 | 75 | # Each edge has a unique key. 76 | # These map between (smaller vertex index, larger vertex index) and an edge's unique key. 77 | edge_to_key = {} 78 | key_to_edge = [] 79 | 80 | # This maps each edge's key to its neighboring edges' keys. 81 | # In a watertight mesh, each edge has four neighbors. 82 | # The neighbors are the four other edges of the two triangles an edge is connected to. 83 | edge_to_neighbors: List[List[int]] = [] 84 | 85 | # This maps each edge to the number of neighbors that have been encountered. 86 | edge_to_seen_neighbors: List[int] = [] 87 | 88 | # This maps each edge's key to that edge's index within other entries in edge_to_neighbors. 89 | # Let k be an edge's key. Let k'_n be the nth value of edge_to_neighbors[k]. 90 | # Then the nth element of edge_lookup[k] will be the index of k within edge_to_neighbors[k'_n]. 91 | edge_lookup: List[List[int]] = [] 92 | 93 | for face in self.faces: 94 | # Create a list of the face's edges. 95 | # Each entry is a sorted list of the edge's two vertex indices. 96 | face_edges = [tuple(sorted([face[i], face[(i + 1) % 3]])) for i in range(3)] 97 | 98 | # The stuff here happens once per edge (the first time it's encountered). 99 | for edge_vertices in face_edges: 100 | if edge_vertices not in edge_to_key: 101 | # The edge gets a unique key (its index). 102 | edge_key = len(edge_to_key) 103 | edge_to_key[edge_vertices] = edge_key 104 | key_to_edge.append(edge_vertices) 105 | 106 | # Set up data structures for the new edge. 107 | edge_to_neighbors.append([None, None, None, None]) 108 | edge_lookup.append([None, None, None, None]) 109 | edge_to_seen_neighbors.append(0) 110 | 111 | # Associate each vertex with the edge. 112 | v0, v1 = edge_vertices 113 | self.vertex_to_edges[v0].add(EdgeConnection(edge_key, 0)) 114 | self.vertex_to_edges[v1].add(EdgeConnection(edge_key, 1)) 115 | 116 | # Associate edges with their neighbors. 117 | # This happens in a separate loop because it requires all encountered edges to have keys. 118 | for edge_index, edge_vertices in enumerate(face_edges): 119 | # Get the edge's neighbors' keys. 120 | neighbor_0_key = edge_to_key[face_edges[(edge_index + 1) % 3]] 121 | neighbor_1_key = edge_to_key[face_edges[(edge_index + 2) % 3]] 122 | 123 | # Update edge_to_neighbors. 124 | edge_key = edge_to_key[edge_vertices] 125 | seen_neighbors = edge_to_seen_neighbors[edge_key] 126 | edge_to_neighbors[edge_key][seen_neighbors + 0] = neighbor_0_key 127 | edge_to_neighbors[edge_key][seen_neighbors + 1] = neighbor_1_key 128 | edge_to_seen_neighbors[edge_key] += 2 129 | 130 | # Create the edge lookup. 131 | # This happens in a separate loop because it requires all encountered edges' seen neighbor counts to be up to date. 132 | for edge_index, edge_vertices in enumerate(face_edges): 133 | # Get the edge's neighbors' keys. 134 | neighbor_0_key = edge_to_key[face_edges[(edge_index + 1) % 3]] 135 | neighbor_1_key = edge_to_key[face_edges[(edge_index + 2) % 3]] 136 | 137 | # Find how many neighbors the neighbors have seen. 138 | neighbor_0_seen_neighbors = edge_to_seen_neighbors[neighbor_0_key] 139 | neighbor_1_seen_neighbors = edge_to_seen_neighbors[neighbor_1_key] 140 | 141 | # Deduce the current edge's index in its neighbors' entries in edge_to_neighbors. 142 | # Note: The keys to edge_lookup here are the same as the keys to edge_to_neighbors in the previous loop. 143 | # They just look different because seen_neighbors was incremented by 2. 144 | edge_key = edge_to_key[edge_vertices] 145 | seen_neighbors = edge_to_seen_neighbors[edge_key] 146 | edge_lookup[edge_key][seen_neighbors - 2] = ( 147 | neighbor_0_seen_neighbors - 1 148 | ) 149 | edge_lookup[edge_key][seen_neighbors - 1] = ( 150 | neighbor_1_seen_neighbors - 2 151 | ) 152 | 153 | # Save the results to instance variables. 154 | self.edges = np.array(key_to_edge, dtype=np.int32) 155 | self.edge_to_neighbors = np.array(edge_to_neighbors, dtype=np.int64) 156 | self.edge_lookup = np.array(edge_lookup, dtype=np.int64) 157 | 158 | # Record the maximum vertex degree. 159 | self.max_vertex_degree = max([len(ec) for ec in self.vertex_to_edges]) 160 | 161 | # This is used to extract vertex features from edges. 162 | self.vertex_to_edges_tensor = [] 163 | self.vertex_to_degree = [] 164 | for ecs in self.vertex_to_edges: 165 | vertex_info = [(ec.edge_index, ec.index_in_edge) for ec in ecs] 166 | 167 | # For vertices whose degree is less than the maximum vertex degree 168 | # in the mesh, we specify (num_edges, 0) as the index of the edge. 169 | # This is because the edge features are assumed to be padded with 170 | # an extra zero feature at the end that can be indexed this way. 171 | # This is necessary for the CPU implementation to work because 172 | # tf.gather_nd will give an error when an invalid index is given on 173 | # CPU (instead of just returning a zero feature like on GPU). 174 | num_e = self.edges.shape[0] 175 | vertex_info.extend( 176 | [(num_e, 0) for _ in range(self.max_vertex_degree - len(ecs))] 177 | ) 178 | self.vertex_to_edges_tensor.extend(vertex_info) 179 | self.vertex_to_degree.append(len(ecs)) 180 | self.vertex_to_edges_tensor = tf.convert_to_tensor(self.vertex_to_edges_tensor) 181 | self.vertex_to_degree = tf.convert_to_tensor( 182 | self.vertex_to_degree, dtype=tf.float32 183 | ) 184 | 185 | def collapse_masked_elements(self) -> None: 186 | """Rebuild the mesh without its masked elements. This creates a smaller 187 | mesh where nothing is masked off. 188 | """ 189 | 190 | # Map each unmasked vertex to its new index. 191 | self.vertices = self.vertices[self.vertex_mask] 192 | new_vertex_indices = np.zeros_like(self.vertex_mask, dtype=np.int32) 193 | new_vertex_indices[self.vertex_mask] = np.arange(self.vertices.shape[0]) 194 | 195 | # Update the edges. This requires two changes: 196 | # 1) Masked-off edges have to be removed. 197 | # 2) The vertex pair defining an edge has to be re-indexed to account 198 | # for masked-off vertices that were removed. 199 | self.edges = new_vertex_indices[self.edges[self.edge_mask]] 200 | 201 | # Map each unmasked edge to its new index. 202 | new_edge_indices = np.zeros_like(self.edge_mask, dtype=np.int32) 203 | new_edge_indices[self.edge_mask] = np.arange(self.edges.shape[0]) 204 | 205 | # Update edge_to_neighbors. This similarly requires two changes: 206 | # 1) Masked-off edges have to be removed. 207 | # 2) The neighbors have to be re-indexed to account for masked-off edges 208 | # that were removed. 209 | self.edge_to_neighbors = new_edge_indices[ 210 | self.edge_to_neighbors[self.edge_mask] 211 | ] 212 | 213 | # Update vertex_to_edges. 214 | new_vertex_to_edges = [] 215 | for edge_connections in self.vertex_to_edges: 216 | # Remove masked-off vertices. 217 | if edge_connections is None: 218 | continue 219 | 220 | # Re-index edge keys (indices). 221 | new_edge_connections = { 222 | EdgeConnection(new_edge_indices[old.edge_index], old.index_in_edge) 223 | for old in edge_connections 224 | } 225 | new_vertex_to_edges.append(new_edge_connections) 226 | self.vertex_to_edges = new_vertex_to_edges 227 | 228 | # Update edge_lookup. This simply requires masked edges to be removed. 229 | self.edge_lookup = self.edge_lookup[self.edge_mask] 230 | 231 | # Reset the masks. 232 | self.edge_mask = np.ones((self.edges.shape[0],), dtype=np.bool) 233 | self.vertex_mask = np.ones((self.vertices.shape[0],), dtype=np.bool) 234 | 235 | def generate_face_areas_normals(self, vertices): 236 | """ 237 | ARGS: 238 | vertices: a tensor of shape (num_verticies, 3) numpy array holding the XYZ coordinates of each vertex 239 | self.faces: a (num_faces, 3) numpy array containing the row index for all three verticies 240 | that make up a face 241 | return: 242 | face_unit_normals: a (num_faces, 3) numpy array represeting the XYZ components of the face's normal vector 243 | face_areas: a (num_faces) numpy array representing the area of the face 244 | """ 245 | faces = self.faces 246 | 247 | # creates two (num_faces, 3) tensors where the rows are an XYZ vector representing one of the edge vectors 248 | edge_vectors_1 = tf.gather(vertices, faces[:, 1]) - tf.gather( 249 | vertices, faces[:, 0] 250 | ) 251 | edge_vectors_2 = tf.gather(vertices, faces[:, 2]) - tf.gather( 252 | vertices, faces[:, 1] 253 | ) 254 | 255 | # computes the cross product of the two edge arrays dim (num_faces, 3) 256 | edge_cross = tf.linalg.cross(edge_vectors_1, edge_vectors_2) 257 | 258 | # computes the magnitude of the adge_cross vector dim (numfaces) 259 | edge_cross_mag = tf.norm(edge_cross, axis=1) 260 | 261 | # the unit normal is the cross product divided by its magnitude dim (num_faces, 3) 262 | face_unit_normals = edge_cross / edge_cross_mag[:, None] 263 | 264 | # a triangle's area is equal to 1/2 * mag(edge cross product) dim (num_faces) 265 | face_areas = 0.5 * edge_cross_mag 266 | 267 | return face_unit_normals, face_areas 268 | 269 | def sample_surface(self, vertices, count): 270 | """ 271 | ARGS: 272 | verticies: tensor of shape (num_verticies, 3) XYZ coordinate associated with each vertex 273 | self.faces: np array (num_faces, 3) set of three row indexesassociated with each face 274 | self.face_areas: np array (num_faces) hlding the area of each face 275 | self.face_unit_normals: np array (num_faces, 3) representing the XYZ vector of the unti normal for the face 276 | count: number of points to sample 277 | 278 | Uses: 279 | https://mathworld.wolfram.com/TrianglePointPicking.html 280 | 281 | returns: 282 | sample_points: tf (count, 3) XYZ coordinate for randomly sampled points accross the mesh 283 | sample_normals: tf (count, 3) XYZ norms for the selected points 284 | """ 285 | 286 | faces = self.faces 287 | face_unit_normals, face_areas = self.generate_face_areas_normals(vertices) 288 | 289 | # normalize the face areas by the total area 290 | total_face_area = tf.math.reduce_sum(face_areas) 291 | face_areas = face_areas / total_face_area 292 | 293 | # Creates a probability distribution from the face areas 294 | face_distribution = tfp.distributions.Categorical(probs=face_areas) 295 | 296 | # samples the distribution count number of times, then gets the face row value for the relevant face 297 | face_to_sample = face_distribution.sample(count) 298 | face_index = face_to_sample # (count) 299 | face_to_sample = tf.gather(faces, face_to_sample) # (count, 3) 300 | 301 | # sets XYZ "origins" for each triangle as the 0th index vertex (count, 3) 302 | origin = tf.gather( 303 | vertices, 304 | face_to_sample[:, 0], 305 | ) 306 | 307 | # defines the two edges that, with the origin, define the triangle (count, 3) 308 | edge_1 = tf.gather(vertices, face_to_sample[:, 1]) - origin 309 | edge_2 = tf.gather(vertices, face_to_sample[:, 2]) - origin 310 | 311 | # stacks the two edge matricies together, dim (count, 2, 3) 312 | edges = tf.stack([edge_1, edge_2], axis=1) 313 | 314 | # computes two values between 0 and 1 that will scale the edge vectors and then summed to compute the points 315 | # dim (count, 2) 316 | edge_weights = tf.random.uniform(shape=(count, 2)) 317 | 318 | # some points would be outside the triangle. if the sum is less than one then it is insde and thus 319 | # outside_triangle is true. dim (count) 320 | outside_triange = tf.math.reduce_sum(edge_weights, axis=1) > 1.0 321 | 322 | # remaps the points that are outside the trianle inside of the triangle 323 | edge_weights = edge_weights - tf.expand_dims( 324 | tf.cast(outside_triange, dtype=tf.float32), axis=1 325 | ) 326 | edge_weights = tf.math.abs(edge_weights) 327 | 328 | # computes a sample vector as the weighted sum of the edge vectors using the random weights from above 329 | # dim (count, 3) 330 | sample_vector = tf.math.reduce_sum( 331 | edges * tf.expand_dims(edge_weights, axis=2), axis=1 332 | ) 333 | 334 | # sample points are the displacement vector plus the origin 335 | # dim (count, 3) 336 | sample_points = sample_vector + origin 337 | 338 | # gather the normal for each point from the face it was sampled from 339 | sample_normals = tf.gather(face_unit_normals, face_index) 340 | 341 | return sample_points, sample_normals 342 | -------------------------------------------------------------------------------- /source/mesh/MeshChecker.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from .Mesh import Mesh 3 | 4 | 5 | class MeshChecker: 6 | def __init__(self, mesh: Mesh) -> None: 7 | self.mesh = mesh 8 | 9 | def _check_vertices(self) -> bool: 10 | """Check the validity of the mesh's vertices field.""" 11 | 12 | # Check the shape. 13 | if self.mesh.vertices.shape[1] != 3: 14 | return False 15 | 16 | # Create a list of vertices seen in faces. 17 | vertices_seen_in_faces = set() 18 | for vertex_a, vertex_b, vertex_c in self.mesh.faces: 19 | vertices_seen_in_faces.add(vertex_a) 20 | vertices_seen_in_faces.add(vertex_b) 21 | vertices_seen_in_faces.add(vertex_c) 22 | 23 | # Create a list of vertices seen in non-masked edges. 24 | vertices_seen_in_edges = set() 25 | for edge_key, edge in enumerate(self.mesh.edges): 26 | # Skip masked edges. 27 | if not self.mesh.edge_mask[edge_key]: 28 | continue 29 | vertices_seen_in_edges.add(edge[0]) 30 | vertices_seen_in_edges.add(edge[1]) 31 | 32 | # Check each vertex's validity. 33 | for vertex_key, xyz in enumerate(self.mesh.vertices): 34 | # Skip masked vertices. 35 | if not self.mesh.vertex_mask[vertex_key]: 36 | continue 37 | 38 | # The vertex's position should not include NaN. 39 | if np.isnan(xyz).any(): 40 | return False 41 | 42 | # The vertex should appear in at least one edge. 43 | if vertex_key not in vertices_seen_in_edges: 44 | return False 45 | 46 | # The vertex should appear in at least one face. 47 | if vertex_key not in vertices_seen_in_faces: 48 | return False 49 | 50 | # The vertex should appear in vertex_to_edges. 51 | if self.mesh.vertex_to_edges[vertex_key] is None: 52 | return False 53 | 54 | return True 55 | 56 | def _check_edges(self) -> bool: 57 | """Check the validity of the mesh's edges field.""" 58 | 59 | # Check the shape. 60 | if self.mesh.edges.shape[1] != 2: 61 | return False 62 | 63 | # Non-masked edges should not include masked vertices. 64 | for edge_key, edge in enumerate(self.mesh.edges): 65 | # Skip masked edges. 66 | if not self.mesh.edge_mask[edge_key]: 67 | continue 68 | a_masked = not self.mesh.vertex_mask[edge[0]] 69 | b_masked = not self.mesh.vertex_mask[edge[1]] 70 | if a_masked or b_masked: 71 | return False 72 | 73 | return True 74 | 75 | def _check_edge_to_neighbors_and_edge_lookup(self) -> bool: 76 | """Check the validity of the mesh's edge_to_neighbors and edge_lookup 77 | fields. 78 | """ 79 | 80 | # Check the shapes. 81 | num_edges = self.mesh.edges.shape[0] 82 | if self.mesh.edge_to_neighbors.shape != (num_edges, 4): 83 | return False 84 | if self.mesh.edge_lookup.shape != (num_edges, 4): 85 | return False 86 | 87 | for edge_key, neighbors in enumerate(self.mesh.edge_to_neighbors): 88 | # Skip masked edges. 89 | if not self.mesh.edge_mask[edge_key]: 90 | continue 91 | 92 | # Confirm that edge_to_neighbors and edge_lookup agree. 93 | # Check that each neighbor's nth neighbor is the original edge, 94 | # where n = lookup[neighbor index]. 95 | lookup = self.mesh.edge_lookup[edge_key, :] 96 | for neighbor_index, neighbor in enumerate(neighbors): 97 | expected_edge_key = self.mesh.edge_to_neighbors[ 98 | neighbor, lookup[neighbor_index] 99 | ] 100 | if edge_key != expected_edge_key: 101 | return False 102 | 103 | # Make sure no neighbors are masked. 104 | neighbors = self.mesh.edge_to_neighbors[edge_key, :] 105 | for neighbor_key in neighbors: 106 | if not self.mesh.edge_mask[neighbor_key]: 107 | return False 108 | 109 | return True 110 | 111 | def _check_vertex_to_edges(self): 112 | """Check the validity of the mesh's vertex_to_edges field.""" 113 | 114 | for vertex_key, edge_connections in enumerate(self.mesh.vertex_to_edges): 115 | # A masked vertex should not have EdgeConnections. 116 | if edge_connections is None: 117 | if self.mesh.vertex_mask[vertex_key]: 118 | return False 119 | continue 120 | 121 | # Check the validity of the EdgeConnections. 122 | for edge_connection in edge_connections: 123 | edge_key = edge_connection.edge_index 124 | index_in_edge = edge_connection.index_in_edge 125 | 126 | # EdgeConnections should not include masked edges. 127 | if not self.mesh.edge_mask[edge_key]: 128 | return False 129 | 130 | # The EdgeConnection should agree with what's in edges. 131 | if self.mesh.edges[edge_key, index_in_edge] != vertex_key: 132 | return False 133 | 134 | return True 135 | 136 | def _check_edge_mask(self) -> bool: 137 | """Check the validity of the mesh's edge_mask field.""" 138 | 139 | # Check the shape. 140 | num_edges = self.mesh.edges.shape[0] 141 | if self.mesh.edge_mask.shape != (num_edges,): 142 | return False 143 | 144 | # Check that the edge mask agrees with num_edges. 145 | if self.mesh.num_edges != np.sum(self.mesh.edge_mask): 146 | return False 147 | 148 | return True 149 | 150 | def _check_vertex_mask(self) -> bool: 151 | """Check the validity of the mesh's vertex_mask field.""" 152 | 153 | num_vertices = self.mesh.vertices.shape[0] 154 | if self.mesh.vertex_mask.shape != (num_vertices,): 155 | return False 156 | 157 | return True 158 | 159 | def check_validity(self) -> bool: 160 | return ( 161 | True 162 | and self._check_vertices() 163 | and self._check_edges() 164 | and self._check_edge_to_neighbors_and_edge_lookup() 165 | and self._check_vertex_to_edges() 166 | and self._check_edge_mask() 167 | and self._check_vertex_mask() 168 | ) 169 | -------------------------------------------------------------------------------- /source/mesh/Obj.py: -------------------------------------------------------------------------------- 1 | import trimesh 2 | import numpy as np 3 | from typing import Tuple 4 | import os 5 | 6 | 7 | class Obj: 8 | """This just makes it easier to save and load obj files.""" 9 | 10 | @staticmethod 11 | def save(file_name: str, vertices: np.ndarray, faces: np.ndarray) -> None: 12 | os.makedirs(os.path.dirname(file_name), exist_ok=True) 13 | with open(file_name, "w") as f: 14 | mesh = trimesh.Trimesh(vertices=vertices, faces=faces) 15 | f.write(trimesh.exchange.obj.export_obj(mesh)) 16 | 17 | @staticmethod 18 | def load(file_name: str) -> Tuple[np.ndarray, np.ndarray]: 19 | with open(file_name, "r") as f: 20 | mesh = trimesh.exchange.obj.load_obj(f) 21 | return np.float32(mesh["vertices"]), mesh["faces"] -------------------------------------------------------------------------------- /source/mesh/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/mesh/__init__.py -------------------------------------------------------------------------------- /source/mesh/__tests__/TestMesh.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import trimesh 3 | import numpy as np 4 | import tensorflow as tf 5 | from typing import Tuple 6 | from ..MeshChecker import MeshChecker 7 | from ..Mesh import Mesh 8 | from ...layers.pooling.CollapseSnapshot import CollapseSnapshot 9 | from ...layers.pooling.collapse_edge import collapse_edge 10 | 11 | 12 | class TestMesh(unittest.TestCase): 13 | def load_obj(self, file_name: str) -> Tuple[np.ndarray]: 14 | with open(file_name, "r") as f: 15 | mesh = trimesh.exchange.obj.load_obj(f) 16 | return np.float32(mesh["vertices"]), mesh["faces"] 17 | 18 | def test_icosahedron_smoke(self): 19 | vertices, faces = self.load_obj("data/objs/icosahedron.obj") 20 | Mesh(vertices, faces) 21 | 22 | def test_icosahedron_edge_count(self): 23 | vertices, faces = self.load_obj("data/objs/icosahedron.obj") 24 | mesh = Mesh(vertices, faces) 25 | self.assertEqual(mesh.edges.shape, (30, 2)) 26 | 27 | def test_icosahedron_vertex_degree(self): 28 | vertices, faces = self.load_obj("data/objs/icosahedron.obj") 29 | mesh = Mesh(vertices, faces) 30 | 31 | vertex_degrees = {} 32 | for edge_index in range(mesh.edges.shape[0]): 33 | v0, v1 = mesh.edges[edge_index, :] 34 | vertex_degrees[v0] = vertex_degrees.get(v0, 0) + 1 35 | vertex_degrees[v1] = vertex_degrees.get(v1, 0) + 1 36 | 37 | for degree in vertex_degrees.values(): 38 | self.assertEqual(degree, 5) 39 | 40 | def test_icosahedron_validity(self): 41 | vertices, faces = self.load_obj("data/objs/icosahedron.obj") 42 | mesh = Mesh(vertices, faces) 43 | checker = MeshChecker(mesh) 44 | self.assertTrue(checker.check_validity()) 45 | 46 | def test_tetrahedron_validity(self): 47 | vertices, faces = self.load_obj("data/objs/tetrahedron.obj") 48 | mesh = Mesh(vertices, faces) 49 | checker = MeshChecker(mesh) 50 | self.assertTrue(checker.check_validity()) 51 | 52 | def test_collapse_masked_elements(self): 53 | vertices, faces = self.load_obj("data/objs/icosahedron.obj") 54 | mesh = Mesh(vertices, faces) 55 | snapshot = CollapseSnapshot(mesh) 56 | checker = MeshChecker(mesh) 57 | for edge_key in range(mesh.edges.shape[0]): 58 | collapse_edge(mesh, edge_key, snapshot) 59 | mesh.collapse_masked_elements() 60 | self.assertTrue(checker.check_validity()) 61 | 62 | def test_face_areas_normals(self): 63 | vertices, faces = self.load_obj("data/objs/icosahedron.obj") 64 | mesh = Mesh(vertices, faces) 65 | normals, areas = mesh.generate_face_areas_normals(mesh.vertices) 66 | checker = MeshChecker(mesh) 67 | self.assertTrue(np.abs(np.mean(areas.numpy()) - 4787.286) < 0.01) 68 | self.assertTrue(checker.check_validity) 69 | 70 | def test_sample(self): 71 | vertices, faces = self.load_obj("data/objs/icosahedron.obj") 72 | mesh = Mesh(vertices, faces) 73 | points, normals = mesh.sample_surface(tf.convert_to_tensor(mesh.vertices), 1001) 74 | self.assertTrue(np.abs(np.mean(points.numpy())) < 5) 75 | self.assertTrue(np.abs(np.mean(normals.numpy())) < 0.5) 76 | 77 | def test_remesh(self): 78 | vertices_old, faces_old = self.load_obj("data/objs/cylinder.obj") 79 | mesh_old = Mesh(vertices_old, faces_old) 80 | mesh_new = mesh_old.remesh() 81 | vertices_new = mesh_new.vertices 82 | faces_new = mesh_new.faces 83 | checker = MeshChecker(mesh_new) 84 | self.assertTrue(checker.check_validity()) 85 | mesh_for_saving = trimesh.Trimesh(vertices=vertices_new, faces=faces_new) 86 | with open("tmp_out_test_remesh.obj", "w") as f: 87 | f.write(trimesh.exchange.obj.export_obj(mesh_for_saving)) -------------------------------------------------------------------------------- /source/mesh/__tests__/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/mesh/__tests__/__init__.py -------------------------------------------------------------------------------- /source/mesh/remesh.py: -------------------------------------------------------------------------------- 1 | import os 2 | import uuid 3 | import trimesh 4 | import numpy as np 5 | 6 | MANIFOLD_SOFTWARE_DIR = "Manifold/build" 7 | 8 | 9 | def remesh( 10 | vertices: np.ndarray, 11 | faces: np.ndarray, 12 | num_faces=2000, 13 | ): 14 | # Write the original mesh as OBJ. 15 | original_file = random_file_name("obj") 16 | with open(original_file, "w") as f: 17 | mesh = trimesh.Trimesh(vertices=vertices, faces=faces) 18 | f.write(trimesh.exchange.obj.export_obj(mesh)) 19 | 20 | # Create a manifold of the original file. 21 | manifold_file = random_file_name("obj") 22 | manifold_script_path = os.path.join(MANIFOLD_SOFTWARE_DIR, "manifold") 23 | cmd = f"{manifold_script_path} {original_file} {manifold_file}" 24 | os.system(cmd) 25 | 26 | # Simplify the manifold. 27 | simplified_file = random_file_name("obj") 28 | simplify_script_path = os.path.join(MANIFOLD_SOFTWARE_DIR, "simplify") 29 | cmd = ( 30 | f"{simplify_script_path} -i {manifold_file} -o {simplified_file} -f {num_faces}" 31 | ) 32 | os.system(cmd) 33 | 34 | # Read the simplified manifold. 35 | with open(simplified_file, "r") as f: 36 | mesh = trimesh.exchange.obj.load_obj(f) 37 | 38 | # Prevent file spam. 39 | os.remove(original_file) 40 | os.remove(manifold_file) 41 | os.remove(simplified_file) 42 | 43 | return mesh["vertices"], mesh["faces"] 44 | 45 | 46 | def random_file_name(ext, prefix="tmp_"): 47 | return f"{prefix}{uuid.uuid4()}.{ext}" -------------------------------------------------------------------------------- /source/model/PointToMeshModel.py: -------------------------------------------------------------------------------- 1 | from tensorflow.keras import Model 2 | from ..layers.composite.Encoder import Encoder 3 | from ..layers.composite.Decoder import Decoder 4 | from ..layers.convolution.MeshConvolution import MeshConvolution 5 | from ..mesh.Mesh import Mesh 6 | from typing import List 7 | import tensorflow as tf 8 | 9 | 10 | class PointToMeshModel(Model): 11 | def __init__(self, initial_num_edges: int, pooling: List[float]) -> None: 12 | super(PointToMeshModel, self).__init__() 13 | assert len(pooling) == 6 14 | scaled_pooling = [None if p is None else initial_num_edges * p for p in pooling] 15 | self.encoder = Encoder((6, 16, 32, 64, 64, 128), 1, 3, 0.01, scaled_pooling) 16 | self.decoder = Decoder((64, 64, 32, 16, 6, 6), 1, 1, 0.01, scaled_pooling) 17 | self.batch_normalization = tf.keras.layers.BatchNormalization() 18 | initializer = tf.keras.initializers.RandomUniform(-1e-8, 1e-8) 19 | self.final_convolution = MeshConvolution(6, initializer, initializer) 20 | 21 | def call(self, mesh: Mesh, fixed_input_features: tf.Tensor): 22 | assert tf.rank(fixed_input_features) == 2 and fixed_input_features.shape[1] == 6 23 | 24 | encoding, snapshots = self.encoder(mesh, fixed_input_features) 25 | decoding = self.decoder(mesh, encoding, snapshots) 26 | normalized = self.batch_normalization(decoding, training=True) 27 | output = self.final_convolution(mesh, normalized) 28 | 29 | return output -------------------------------------------------------------------------------- /source/model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/model/__init__.py -------------------------------------------------------------------------------- /source/model/__tests__/TestGetVertexFeatures.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import trimesh 3 | import tensorflow as tf 4 | import numpy as np 5 | from ...mesh.Mesh import Mesh 6 | from ..get_vertex_features import get_vertex_features, naive_get_vertex_features 7 | 8 | 9 | class TestGetVertexFeatures(unittest.TestCase): 10 | def setUp(self) -> None: 11 | with open("data/objs/icosahedron.obj", "r") as f: 12 | mesh = trimesh.exchange.obj.load_obj(f) 13 | self.mesh = Mesh(mesh["vertices"], mesh["faces"]) 14 | 15 | def test_against_naive(self): 16 | num_edges = self.mesh.edges.shape[0] 17 | features = tf.random.uniform((num_edges, 6)) 18 | 19 | fast = get_vertex_features(self.mesh, features) 20 | naive = naive_get_vertex_features(self.mesh, features) 21 | 22 | tf.debugging.assert_near(fast, naive) 23 | -------------------------------------------------------------------------------- /source/model/__tests__/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/model/__tests__/__init__.py -------------------------------------------------------------------------------- /source/model/get_vertex_features.py: -------------------------------------------------------------------------------- 1 | from ..mesh.Mesh import Mesh 2 | from ..layers.feature import features_valid 3 | import tensorflow as tf 4 | import numpy as np 5 | 6 | 7 | def get_vertex_features(mesh: Mesh, features: tf.Tensor): 8 | """Convert edge features in shape (num_edges, 6) to vertex features in shape 9 | (num_vertices, 3). 10 | """ 11 | assert features_valid(mesh, features) 12 | assert features.shape[1] == 6 13 | 14 | # Reshape features into the following shape: 15 | # (num_edges, num_vertices_per_edge = 2, dimensions_per_vertex = 3) 16 | edge_features = tf.reshape(features, (-1, 2, 3)) 17 | 18 | # Now, pad the first two dimensions with slices of zeros. 19 | # This will be needed to handle variable vertex degrees using gather_nd. 20 | edge_features = tf.pad(edge_features, ((0, 1), (0, 1), (0, 0))) 21 | 22 | # Since TensorFlow doesn't have NumPy like indexing, it's a bit harder to do 23 | # this than in PyTorch. 24 | gathered_vertex_features = tf.gather_nd(edge_features, mesh.vertex_to_edges_tensor) 25 | gathered_vertex_features = tf.reshape( 26 | gathered_vertex_features, (mesh.vertices.shape[0], mesh.max_vertex_degree, 3) 27 | ) 28 | 29 | # Get a weighted average of the features accumulated from the edges. 30 | vertex_features = tf.math.reduce_sum(gathered_vertex_features, axis=1) 31 | vertex_features /= mesh.vertex_to_degree[:, None] 32 | 33 | return vertex_features 34 | 35 | 36 | def naive_get_vertex_features(mesh: Mesh, features: tf.Tensor): 37 | """This is a naive version of get_vertex_features used for testing.""" 38 | assert features_valid(mesh, features) 39 | assert features.shape[1] == 6 40 | 41 | # Create a list of features for each vertex. 42 | vertex_features = [[] for _ in range(mesh.vertices.shape[0])] 43 | for edge_key in range(features.shape[0]): 44 | left_vertex, right_vertex = mesh.edges[edge_key, :] 45 | vertex_features[left_vertex].append(features[edge_key, :3]) 46 | vertex_features[right_vertex].append(features[edge_key, 3:]) 47 | 48 | # Use the mean of each vertex's list of features as that vertex's feature. 49 | vertex_features = [tf.stack(vf) for vf in vertex_features] 50 | vertex_features = [tf.math.reduce_mean(vf, axis=0) for vf in vertex_features] 51 | return tf.stack(vertex_features) -------------------------------------------------------------------------------- /source/options/options.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | DEFAULTS = { 4 | # The point cloud that's fitted. 5 | "point_cloud": "data/point_clouds/elephant.pwn", 6 | # The number of times remeshing/subdivision happens. 7 | "num_subdivisions": 6, 8 | # The number of iterations between each remeshing/subdivision. 9 | "num_iterations": 1000, 10 | # Each subdivision multiplies the number of faces by this. 11 | "subdivision_multiplier": 1.5, 12 | # The maximum number of faces that subdivision is allowed to yield. 13 | "max_num_faces": 10000, 14 | # The initial number of faces used for optimization. 15 | "initial_num_faces": 1000, 16 | # An optional initial mesh. 17 | "initial_mesh": None, 18 | # The folder where the results are saved. 19 | "save_location": "results", 20 | # how often to run beamgap loss if -1 then no beam gap loss 21 | "beamgap_modulo": -1, 22 | # how often to save objs 23 | "obj_save_modulo": 5, 24 | # range to lineralyinterp between when computing samples 25 | "min_num_samples": 10000, 26 | "max_num_samples": 16000, 27 | "pooling": [None, None, None, None, None, None], 28 | } 29 | 30 | 31 | def load_options(argv: list): 32 | if len(argv) == 1: 33 | return DEFAULTS 34 | if len(argv) != 2: 35 | raise Exception( 36 | "The only (optional) argument should be the settings JSON file." 37 | ) 38 | with open(argv[1], "r") as f: 39 | options = json.load(f) 40 | 41 | for key in options: 42 | if key not in DEFAULTS: 43 | raise Exception(f'Unknown setting "{key}"') 44 | return {**DEFAULTS, **options} -------------------------------------------------------------------------------- /source/script_modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcharatan/point2mesh-reimplementation/222c477a0ed4391dcc4ee4eb36c9a6abe57e09db/source/script_modules/__init__.py -------------------------------------------------------------------------------- /source/script_modules/create_convex_hull.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import numpy as np 3 | import trimesh 4 | 5 | # Get the input file path. 6 | parser = argparse.ArgumentParser(description="Create a convex hull.") 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument( 9 | "--input", type=argparse.FileType("r", encoding="UTF-8"), required=True 10 | ) 11 | parser.add_argument( 12 | "--output", type=argparse.FileType("w", encoding="UTF-8"), required=True 13 | ) 14 | args = parser.parse_args() 15 | 16 | # Load the points into NumPy. 17 | points = np.loadtxt(args.input) 18 | convex_hull = trimesh.convex.convex_hull(points[:, :3]) 19 | 20 | # Save an OBJ file. 21 | obj = trimesh.exchange.obj.export_obj(convex_hull) 22 | args.output.write(obj) 23 | -------------------------------------------------------------------------------- /source/script_modules/make_c4d_point_cloud_ascii.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | # Load the point cloud. 4 | file = "data/point_clouds/tiki.pwn" 5 | point_cloud_np = np.loadtxt(file)[:, :3] 6 | 7 | # Randomly sample n points. 8 | max_num_points = point_cloud_np.shape[0] / 2 9 | point_cloud_np = point_cloud_np[ 10 | np.random.choice(np.arange(point_cloud_np.shape[0]), 1000, replace=False), : 11 | ] 12 | 13 | # Write to C4D's ASCII format (for C4D Structure Manager). 14 | with open("tmp_c4d.txt", "w") as f: 15 | f.write("Point X Y Z\n") 16 | for row in range(point_cloud_np.shape[0]): 17 | f.write( 18 | f"{row}\t{point_cloud_np[row, 0]} cm\t{point_cloud_np[row, 1]} cm\t{point_cloud_np[row, 2]} cm\n" 19 | ) 20 | -------------------------------------------------------------------------------- /source/script_modules/remesh_obj.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import numpy as np 3 | import trimesh 4 | from ..mesh.remesh import remesh 5 | 6 | # Get the input file path. 7 | parser = argparse.ArgumentParser(description="Remesh an obj.") 8 | parser = argparse.ArgumentParser() 9 | parser.add_argument( 10 | "--input", type=argparse.FileType("r", encoding="UTF-8"), required=True 11 | ) 12 | parser.add_argument( 13 | "--output", type=argparse.FileType("w", encoding="UTF-8"), required=True 14 | ) 15 | args = parser.parse_args() 16 | 17 | # Load the obj. 18 | mesh = trimesh.exchange.obj.load_obj(args.input) 19 | vertices = mesh["vertices"] 20 | faces = mesh["faces"] 21 | 22 | # Remesh the obj. 23 | remeshed_vertices, remeshed_faces = remesh(vertices, faces) 24 | 25 | # Save an OBJ file. 26 | mesh = trimesh.Trimesh(vertices=remeshed_vertices, faces=remeshed_faces) 27 | obj = trimesh.exchange.obj.export_obj(mesh) 28 | args.output.write(obj) 29 | -------------------------------------------------------------------------------- /source/script_modules/train_model.py: -------------------------------------------------------------------------------- 1 | import time 2 | import sys 3 | import tensorflow as tf 4 | import numpy as np 5 | from tensorflow.python.keras.backend import dtype 6 | import trimesh 7 | import colorama 8 | import os 9 | from colorama import Fore, Back, Style 10 | from ..mesh.Mesh import Mesh 11 | from ..mesh.Obj import Obj 12 | from ..model.PointToMeshModel import PointToMeshModel 13 | from ..model.get_vertex_features import get_vertex_features 14 | from ..loss.ChamferLossLayer import ChamferLossLayer 15 | from ..loss.loss import BeamGapLossLayer, discrete_project 16 | from ..loss.ConvergenceDetector import ConvergenceDetector 17 | from ..mesh.remesh import remesh 18 | from ..options.options import load_options 19 | 20 | # This makes Colorama (terminal colors) work on Windows. 21 | colorama.init() 22 | 23 | # Set up CUDA. 24 | physical_devices = tf.config.list_physical_devices("GPU") 25 | if len(physical_devices) > 0: 26 | tf.config.experimental.set_memory_growth(physical_devices[0], True) 27 | 28 | # Load the options and the point cloud. 29 | options = load_options(sys.argv) 30 | with open(options["point_cloud"], "r") as f: 31 | point_cloud_np = np.loadtxt(f)[:, :3] 32 | point_cloud_tf = tf.convert_to_tensor(point_cloud_np, dtype=tf.float32) 33 | 34 | # Create a function for saving meshes. 35 | def save_mesh(file_name, vertices, faces): 36 | Obj.save(os.path.join(options["save_location"], file_name), vertices, faces) 37 | 38 | 39 | # Create the mesh. 40 | if options["initial_mesh"]: 41 | remeshed_vertices, remeshed_faces = Obj.load(options["initial_mesh"]) 42 | else: 43 | convex_hull = trimesh.convex.convex_hull(point_cloud_np) 44 | remeshed_vertices, remeshed_faces = remesh( 45 | convex_hull.vertices, convex_hull.faces, options["initial_num_faces"] 46 | ) 47 | save_mesh("tmp_initial_mesh.obj", remeshed_vertices, remeshed_faces) 48 | 49 | # Create and train the model. 50 | chamfer_loss = ChamferLossLayer() 51 | beam_loss = BeamGapLossLayer(discrete_project) 52 | optimizer = tf.keras.optimizers.Adam(learning_rate=0.00005) 53 | num_subdivisions = options["num_subdivisions"] 54 | new_vertices = None 55 | for subdivision_level in range(num_subdivisions): 56 | # Create a new model at each subdivision level. 57 | # This is because the learned weights don't probably don't carry over to 58 | # different initial positions and resolutions. 59 | chamfer_convergence = ConvergenceDetector() 60 | beam_convergence = ConvergenceDetector() 61 | 62 | # Subdivide the mesh if beyond the first level. 63 | if subdivision_level != 0: 64 | if new_vertices is None: 65 | raise Exception("Could not find vertices to subdivide.") 66 | new_num_faces = min( 67 | options["max_num_faces"], 68 | options["subdivision_multiplier"] * remeshed_faces.shape[0], 69 | ) 70 | print( 71 | f"{Back.MAGENTA}Remeshing to {int(new_num_faces)} faces.{Style.RESET_ALL}" 72 | ) 73 | remeshed_vertices, remeshed_faces = remesh( 74 | new_vertices.numpy(), remeshed_faces, new_num_faces 75 | ) 76 | else: 77 | print( 78 | f"{Back.MAGENTA}Starting with {remeshed_faces.shape[0]} faces.{Style.RESET_ALL}" 79 | ) 80 | mesh = Mesh(remeshed_vertices, remeshed_faces) 81 | model = PointToMeshModel(mesh.edges.shape[0], options["pooling"]) 82 | 83 | # Create the random features. 84 | in_features = tf.random.uniform((mesh.edges.shape[0], 6), -0.5, 0.5) 85 | 86 | old_vertices = tf.convert_to_tensor(remeshed_vertices, dtype=tf.float32) 87 | num_iterations = options["num_iterations"] 88 | for iteration in range(num_iterations): 89 | iteration_start_time = time.time() 90 | 91 | converged = False 92 | with tf.GradientTape() as tape: 93 | # Get new vertex positions by calling the model. 94 | features = model(mesh, in_features) 95 | new_vertices = old_vertices + get_vertex_features(mesh, features) 96 | 97 | # Calculate loss. 98 | num_samples = int( 99 | options["min_num_samples"] 100 | + (iteration / options["num_iterations"]) 101 | * (options["max_num_samples"] - options["min_num_samples"]) 102 | ) 103 | surface_sample = mesh.sample_surface(new_vertices, num_samples) 104 | beamgap_modulo = options["beamgap_modulo"] 105 | if beamgap_modulo == -1: 106 | use_beamgap_loss = False 107 | else: 108 | use_beamgap_loss = iteration % beamgap_modulo == 0 109 | if use_beamgap_loss: 110 | beam_loss.update_points_masks(mesh, new_vertices, point_cloud_tf) 111 | total_loss = 0.01 * beam_loss(mesh, new_vertices) 112 | converged = beam_convergence.step(total_loss.numpy().item()) 113 | else: 114 | total_loss = chamfer_loss( 115 | surface_sample[0], point_cloud_tf, num_samples 116 | ) 117 | converged = chamfer_convergence.step(total_loss.numpy().item()) 118 | 119 | # Apply gradients. 120 | gradients = tape.gradient(total_loss, model.trainable_variables) 121 | optimizer.apply_gradients(zip(gradients, model.trainable_variables)) 122 | 123 | # Save the obj every few iterations. 124 | save_modulo = options["obj_save_modulo"] 125 | if iteration % save_modulo == 0 or converged or iteration == num_iterations - 1: 126 | save_mesh( 127 | f"tmp_{str(subdivision_level).zfill(2)}_{str(iteration).zfill(3)}.obj", 128 | new_vertices.numpy(), 129 | remeshed_faces, 130 | ) 131 | 132 | # Log a progress update. 133 | loss_type = "Beam-gap" if use_beamgap_loss else "Chamfer" 134 | message = [ 135 | f"{Back.WHITE}{Fore.BLACK}" 136 | f" {subdivision_level + 1}/{num_subdivisions} & {iteration + 1}/{num_iterations}", 137 | f"{Style.RESET_ALL}", 138 | f"{loss_type} Loss: {total_loss.numpy().item()},", 139 | f"Time: {time.time() - iteration_start_time}", 140 | ] 141 | print(" ".join(message)) 142 | if converged: 143 | print( 144 | f"{Back.MAGENTA}Converged at iteration {iteration + 1}/{num_iterations}.{Style.RESET_ALL}" 145 | ) 146 | break 147 | 148 | print("Done.") 149 | --------------------------------------------------------------------------------