├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── examples └── spheres.py ├── images ├── binary.jpg ├── comparison.jpeg ├── mc_colors.jpg ├── mc_super_sampling.jpg ├── smooth.jpg └── smoothing_overview.png ├── marching_cubes ├── __init__.py ├── exporter.py ├── numpy_smoothing.py ├── smoothing.py └── src │ ├── _mcubes.pyx │ ├── marchingcubes.cpp │ ├── marchingcubes.h │ ├── pyarray_symbol.h │ ├── pyarraymodule.h │ ├── pywrapper.cpp │ └── pywrapper.h ├── requirements.txt ├── setup.py ├── test_mcubes.py └── test_smoothing.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.dae 3 | _mcubes.cpp 4 | *.py[cod] 5 | .vscode 6 | .mypy_cache 7 | 8 | .vscode 9 | 10 | # Created by https://www.gitignore.io/api/osx,linux,python 11 | # Edit at https://www.gitignore.io/?templates=osx,linux,python 12 | 13 | ### Linux ### 14 | *~ 15 | 16 | # temporary files which can be created if a process still has a handle open of a deleted file 17 | .fuse_hidden* 18 | 19 | # KDE directory preferences 20 | .directory 21 | 22 | # Linux trash folder which might appear on any partition or disk 23 | .Trash-* 24 | 25 | # .nfs files are created when an open file is removed but is still being accessed 26 | .nfs* 27 | 28 | ### OSX ### 29 | # General 30 | .DS_Store 31 | .AppleDouble 32 | .LSOverride 33 | 34 | # Icon must end with two \r 35 | Icon 36 | 37 | # Thumbnails 38 | ._* 39 | 40 | # Files that might appear in the root of a volume 41 | .DocumentRevisions-V100 42 | .fseventsd 43 | .Spotlight-V100 44 | .TemporaryItems 45 | .Trashes 46 | .VolumeIcon.icns 47 | .com.apple.timemachine.donotpresent 48 | 49 | # Directories potentially created on remote AFP share 50 | .AppleDB 51 | .AppleDesktop 52 | Network Trash Folder 53 | Temporary Items 54 | .apdisk 55 | 56 | ### Python ### 57 | # Byte-compiled / optimized / DLL files 58 | __pycache__/ 59 | *.py[cod] 60 | *$py.class 61 | 62 | # C extensions 63 | *.so 64 | 65 | # Distribution / packaging 66 | .Python 67 | build/ 68 | develop-eggs/ 69 | dist/ 70 | downloads/ 71 | eggs/ 72 | .eggs/ 73 | lib/ 74 | lib64/ 75 | parts/ 76 | sdist/ 77 | var/ 78 | wheels/ 79 | pip-wheel-metadata/ 80 | share/python-wheels/ 81 | *.egg-info/ 82 | .installed.cfg 83 | *.egg 84 | MANIFEST 85 | 86 | # PyInstaller 87 | # Usually these files are written by a python script from a template 88 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 89 | *.manifest 90 | *.spec 91 | 92 | # Installer logs 93 | pip-log.txt 94 | pip-delete-this-directory.txt 95 | 96 | # Unit test / coverage reports 97 | htmlcov/ 98 | .tox/ 99 | .nox/ 100 | .coverage 101 | .coverage.* 102 | .cache 103 | nosetests.xml 104 | coverage.xml 105 | *.cover 106 | .hypothesis/ 107 | .pytest_cache/ 108 | 109 | # Translations 110 | *.mo 111 | *.pot 112 | 113 | # Scrapy stuff: 114 | .scrapy 115 | 116 | # Sphinx documentation 117 | docs/_build/ 118 | 119 | # PyBuilder 120 | target/ 121 | 122 | # pyenv 123 | .python-version 124 | 125 | # pipenv 126 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 127 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 128 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 129 | # install all needed dependencies. 130 | #Pipfile.lock 131 | 132 | # celery beat schedule file 133 | celerybeat-schedule 134 | 135 | # SageMath parsed files 136 | *.sage.py 137 | 138 | # Spyder project settings 139 | .spyderproject 140 | .spyproject 141 | 142 | # Rope project settings 143 | .ropeproject 144 | 145 | # Mr Developer 146 | .mr.developer.cfg 147 | .project 148 | .pydevproject 149 | 150 | # mkdocs documentation 151 | /site 152 | 153 | # mypy 154 | .mypy_cache/ 155 | .dmypy.json 156 | dmypy.json 157 | 158 | # Pyre type checker 159 | .pyre/ 160 | 161 | # End of https://www.gitignore.io/api/osx,linux,python 162 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # vim ft=yaml 2 | 3 | # After changing this file, check it on: 4 | # http://yaml-online-parser.appspot.com/ 5 | 6 | # See doc/travis_notes.txt for some guidelines 7 | 8 | language: python 9 | 10 | matrix: 11 | include: 12 | - os: linux 13 | dist: trusty 14 | sudo: false 15 | python: '3.6' 16 | - os: linux 17 | dist: xenial 18 | sudo: required 19 | python: '3.7' 20 | 21 | # See http://docs.travis-ci.com/user/caching/#pip-cache 22 | cache: pip 23 | 24 | before_install: 25 | - sudo apt-get update 26 | - pip install --upgrade pip setuptools 27 | 28 | install: 29 | - pip install -r requirements.txt 30 | - pip install nose>=1.3.7 coverage codecov pytest>=3.0.5 pycollada 31 | - python setup.py build 32 | - python setup.py build_ext --inplace 33 | 34 | before_script: 35 | - mkdir output 36 | 37 | script: 38 | - nosetests -v --exe --with-xunit --with-coverage --cover-package=mcubes 39 | 40 | after_success: 41 | - python setup.py install 42 | - codecov # PUBLIC project 43 | # - coverage report && coverage xml 44 | # - python-codacy-coverage -r coverage.xml 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2015, P. M. Neila (https://github.com/pmneila/PyMCubes) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include marching_cubes/*.py 2 | exclude marching_cubes/numpy_smoothing.py 3 | 4 | include marching_cubes/src/*.h 5 | include marching_cubes/src/*.cpp 6 | include marching_cubes/src/_mcubes.pyx 7 | exclude marching_cubes/src/_mcubes.cpp 8 | 9 | include examples/*.py 10 | include images/binary.jpg 11 | include images/smooth.jpg 12 | include images/smoothing.png 13 | 14 | include setup.py 15 | include README.rst 16 | include LICENSE 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyMarchingCubes 2 | 3 | `PyMarchingCubes` is a fork from `PyMCubes`, with a different implementation of the actual marching function (see 'marching_cubes/src/marchingcubes.h'). 4 | It fixes some issues of the original implementation that let to wrong triangulation (visible in triangles that are larger than the actual 'marching cell'). 5 | 6 | ![Mesh of PyMCubes and PyMarchingCubes](images/comparison.jpeg "Marching cubes using PyMCubes and PyMarchingCubes") 7 | 8 | ### Color Interpolation 9 | The module also includes a marching cubes with color interpolation: 10 | ``` marching_cubes_color ``` and ``` marching_cubes_color_func ```. 11 | Have a look at the 'examples/spheres.py' file. 12 | Basically, it is called with ``` marching_cubes_color(sdf_volume, rgb_volume, iso_level) ``` assuming a 3D grid for the sdf values (dim_x,dim_y,dim_z) and a 4D grid for the colors (dim_x,dim_y,dim_z,3). 13 | 14 | ![PyMarchingCubes Color Interpolation](images/mc_colors.jpg "Marching cubes with color interpolation.") 15 | 16 | The export functions for obj and off files are adapted accordingly to handle the vertex colors. 17 | 18 | 19 | ### Super sampling along the edges 20 | In case you are using a truncated signed distance function, you might miss the correct zero-crossing because of undersampling. Increasing the sample volume grows cubically, instead one can also subsample along the edges of a coarser volume to find a better approximation of the zero-crossing. 21 | This subsampling of the edges is achieved by sampling along the x,y,z axis independently with higher resolution (e.g., you sample (dim_x + (dim_x-1)*edge_sampling, dim_y, dim_z) for the edges along the x axis). 22 | The computational cost grow linear with the number of subsamples (e.g. 10 subsamples result in ~30 times more samples that you have to provide (since you need 10 times more samples per axis)). 23 | A modified marching cubes implementation can be called via ``` marching_cubes_super_sampling(sdf_x, sdf_y, sdf_z, iso_level) ```. 24 | Have a look at the sphere example. 25 | 26 | ![PyMarchingCubes TSDF Sphere](images/mc_super_sampling.jpg "Marching cubes on a TSDF.") 27 | 28 | 29 | Other than that, this repository is the same as the original (https://github.com/pmneila/PyMCubes). 30 | 31 | ## Installation 32 | 33 | ``` 34 | git clone https://github.com/JustusThies/PyMarchingCubes.git 35 | cd PyMarchingCubes 36 | git clone https://gitlab.com/libeigen/eigen.git 37 | python setup.py install 38 | ``` 39 | 40 | 41 | 42 | ## Example from the original `PyMCubes` 43 | 44 | The following example creates a `NumPy` volume with spherical iso-surfaces and 45 | extracts one of them (i.e., a sphere) with `mcubes.marching_cubes`. The result 46 | is exported to `sphere.dae`: 47 | 48 | ```Python 49 | >>> import numpy as np 50 | >>> import marching_cubes as mcubes 51 | 52 | # Create a data volume (30 x 30 x 30) 53 | >>> X, Y, Z = np.mgrid[:30, :30, :30] 54 | >>> u = (X-15)**2 + (Y-15)**2 + (Z-15)**2 - 8**2 55 | 56 | # Extract the 0-isosurface 57 | >>> vertices, triangles = mcubes.marching_cubes(u, 0) 58 | 59 | # Export the result to sphere.dae 60 | >>> mcubes.export_mesh(vertices, triangles, "sphere.dae", "MySphere") 61 | ``` 62 | 63 | Alternatively, you can use a Python function to represent the volume instead of 64 | a `NumPy` array: 65 | 66 | ```Python 67 | >>> import numpy as np 68 | >>> import marching_cubes as mcubes 69 | 70 | # Create the volume 71 | >>> f = lambda x, y, z: x**2 + y**2 + z**2 72 | 73 | # Extract the 16-isosurface 74 | >>> vertices, triangles = mcubes.marching_cubes_func((-10,-10,-10), (10,10,10), 75 | ... 100, 100, 100, f, 16) 76 | 77 | # Export the result to sphere.dae (requires PyCollada) 78 | >>> mcubes.export_mesh(vertices, triangles, "sphere.dae", "MySphere") 79 | 80 | # Or export to an OBJ file 81 | >>> mcubes.export_obj(vertices, triangles, 'sphere.obj') 82 | ``` 83 | 84 | Note that using a function to represent the volumetric data is **much** slower 85 | than using a `NumPy` array. 86 | 87 | ## Smoothing binary arrays 88 | 89 | ![Overview](images/smoothing_overview.png "Overview of mcubes.smooth") 90 | 91 | Many segmentation methods build binary masks to separate _inside_ and _outside_ 92 | areas of the segmented object. When passing these binary mask to the marching 93 | cubes algorithm the resulting mesh looks jagged. The following code shows an 94 | example with a binary array embedding a sphere. 95 | ```Python 96 | x, y, z = np.mgrid[:100, :100, :100] 97 | binary_sphere = (x - 50)**2 + (y - 50)**2 + (z - 50)**2 - 25**2 < 0 98 | 99 | # Extract the 0.5-levelset since the array is binary 100 | vertices, triangles = mcubes.marching_cubes(binary_sphere, 0.5) 101 | ``` 102 | ![Mesh of a binary embedding](images/binary.jpg "Marching cubes with a binary embedding") 103 | 104 | `PyMCubes` provides the function `mcubes.smooth` that takes a 2D or 3D binary 105 | embedding function and produces a smooth version of it. 106 | ```Python 107 | smoothed_sphere = mcubes.smooth(binary_sphere) 108 | 109 | # Extract the 0-levelset (the 0-levelset of the output of mcubes.smooth is the 110 | # smoothed version of the 0.5-levelset of the binary array). 111 | vertices, triangles = mcubes.marching_cubes(smoothed_sphere, 0) 112 | ``` 113 | ![Mesh of a smoothed embedding](images/smooth.jpg "Marching cubes after smoothing the binary embedding") 114 | 115 | `mcubes.smooth` builds a smooth embedding array with negative values in the 116 | areas where the binary embedding array is 0, and positive values in the areas 117 | where it is 1. In this way, `mcubes.smooth` keeps all the information from the 118 | original embedding function, including fine details and thin structures that 119 | are commonly eroded by other standard smoothing methods. 120 | -------------------------------------------------------------------------------- /examples/spheres.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import marching_cubes as mcubes 4 | 5 | print("Example 1: Isosurface in NumPy volume...") 6 | #print(mcubes.__dir__()) 7 | 8 | # Create a data volume (100 x 100 x 100) 9 | X, Y, Z = np.mgrid[:100, :100, :100] 10 | sdf = (X-50)**2 + (Y-50)**2 + (Z-50)**2 - 25**2 11 | 12 | # Extract the 0-isosurface 13 | vertices, triangles = mcubes.marching_cubes(sdf, 0) 14 | mcubes.export_obj(vertices, triangles, "sphere.obj") 15 | 16 | 17 | 18 | 19 | print("Example 2: Isosurface and color in NumPy volume...") 20 | 21 | # Extract isosurface and color 22 | #color = 0.01 * np.concatenate((X[:,:,:,None],X[:,:,:,None],X[:,:,:,None]), axis=3) # color array (grayscale gradient in this example) 23 | color = 0.01 * np.concatenate((X[:,:,:,None],Y[:,:,:,None],Z[:,:,:,None]), axis=3) # color array (positions as color) 24 | vertices_color, triangles_color = mcubes.marching_cubes_color(sdf, color, 0) 25 | mcubes.export_obj(vertices_color, triangles_color, "sphere_color.obj") 26 | mcubes.export_off(vertices_color, triangles_color, "sphere_color.off") 27 | 28 | 29 | 30 | 31 | print("Example 3: TSDF isosurface with super resolution...") 32 | 33 | dim = 100 34 | 35 | # Create a data volume (100 x 100 x 100) 36 | def sphere_tsdf(xyz, x_scale, y_scale, z_scale): 37 | X, Y, Z = xyz 38 | X = X*x_scale 39 | Y = Y*y_scale 40 | Z = Z*z_scale 41 | sdf = (X-0.5)**2 + (Y-0.5)**2 + (Z-0.5)**2 - 0.25**2 42 | truncation = 0.001 # relatively small truncation 43 | return np.clip(sdf, -truncation, truncation) 44 | 45 | # Extract the 0-isosurface 46 | sdf = sphere_tsdf(np.mgrid[:dim, :dim, :dim], 1.0/(dim-1.0),1.0/(dim-1.0),1.0/(dim-1.0)) 47 | vertices, triangles = mcubes.marching_cubes(sdf, 0) 48 | mcubes.export_off(vertices, triangles, "sphere_tsdf_without_super_res.off") 49 | 50 | # Extract the 0-isosurface with super res 51 | 52 | edges = dim-1 53 | n_edge_samples = 10 54 | supersamples = dim + edges * n_edge_samples 55 | sdf_x = sphere_tsdf(np.mgrid[:supersamples, :dim, :dim], 1.0/(supersamples-1.0), 1.0/(dim-1.0), 1.0/(dim-1.0)) ## generates 10x more samples in x 56 | sdf_y = sphere_tsdf(np.mgrid[:dim, :supersamples, :dim], 1.0/(dim-1.0), 1.0/(supersamples-1.0), 1.0/(dim-1.0)) ## generates 10x more samples in y 57 | sdf_z = sphere_tsdf(np.mgrid[:dim, :dim, :supersamples], 1.0/(dim-1.0), 1.0/(dim-1.0), 1.0/(supersamples-1.0)) ## generates 10x more samples in z 58 | vertices, triangles = mcubes.marching_cubes_super_sampling(sdf_x, sdf_y, sdf_z, 0) 59 | mcubes.export_off(vertices, triangles, "sphere_tsdf_super_res.off") 60 | 61 | 62 | 63 | 64 | # old examples 65 | 66 | # Export the result to sphere.dae 67 | #mcubes.export_mesh(vertices1, triangles1, "sphere1.dae", "MySphere") 68 | 69 | # print("Done. Result saved in 'sphere1.dae'.") 70 | 71 | # print("Example 2: Isosurface in Python function...") 72 | # print("(this might take a while...)") 73 | 74 | # # Create the volume 75 | # def f(x, y, z): 76 | # return x**2 + y**2 + z**2 77 | 78 | # # Extract the 16-isosurface 79 | # vertices2, triangles2 = mcubes.marching_cubes_func( 80 | # (-10,-10,-10), (10,10,10), # Bounds 81 | # 100, 100, 100, # Number of samples in each dimension 82 | # f, # Implicit function 83 | # 16) # Isosurface value 84 | 85 | # # Export the result to sphere2.dae 86 | # mcubes.export_mesh(vertices2, triangles2, "sphere2.dae", "MySphere") 87 | # print("Done. Result saved in 'sphere2.dae'.") 88 | 89 | # try: 90 | # print("Plotting mesh...") 91 | # from mayavi import mlab 92 | # mlab.triangular_mesh( 93 | # vertices1[:, 0], vertices1[:, 1], vertices1[:, 2], 94 | # triangles1) 95 | # print("Done.") 96 | # mlab.show() 97 | # except ImportError: 98 | # print("Could not import mayavi. Interactive demo not available.") 99 | -------------------------------------------------------------------------------- /images/binary.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustusThies/PyMarchingCubes/3285cd56a2849b943c20d5da56fccab166832bb4/images/binary.jpg -------------------------------------------------------------------------------- /images/comparison.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustusThies/PyMarchingCubes/3285cd56a2849b943c20d5da56fccab166832bb4/images/comparison.jpeg -------------------------------------------------------------------------------- /images/mc_colors.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustusThies/PyMarchingCubes/3285cd56a2849b943c20d5da56fccab166832bb4/images/mc_colors.jpg -------------------------------------------------------------------------------- /images/mc_super_sampling.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustusThies/PyMarchingCubes/3285cd56a2849b943c20d5da56fccab166832bb4/images/mc_super_sampling.jpg -------------------------------------------------------------------------------- /images/smooth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustusThies/PyMarchingCubes/3285cd56a2849b943c20d5da56fccab166832bb4/images/smooth.jpg -------------------------------------------------------------------------------- /images/smoothing_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustusThies/PyMarchingCubes/3285cd56a2849b943c20d5da56fccab166832bb4/images/smoothing_overview.png -------------------------------------------------------------------------------- /marching_cubes/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from ._mcubes import marching_cubes, marching_cubes_func, marching_cubes_color, marching_cubes_color_func, marching_cubes_super_sampling 3 | from .exporter import export_mesh, export_obj, export_off 4 | from .smoothing import * 5 | # from .numpy_smoothing import * 6 | -------------------------------------------------------------------------------- /marching_cubes/exporter.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | 4 | 5 | def export_obj(vertices, triangles, filename): 6 | """ 7 | Exports a mesh in the (.obj) format. 8 | """ 9 | print('export mesh: ', vertices.shape) 10 | 11 | with open(filename, 'w') as fh: 12 | if (vertices.shape[1]==6): 13 | for v in vertices: 14 | fh.write("v {} {} {} {} {} {}\n".format(*v)) 15 | else: 16 | for v in vertices: 17 | fh.write("v {} {} {}\n".format(*v)) 18 | 19 | for f in triangles: 20 | fh.write("f {} {} {}\n".format(*(f + 1))) 21 | 22 | 23 | def export_off(vertices, triangles, filename): 24 | """ 25 | Exports a mesh in the (.off) format. 26 | """ 27 | 28 | with open(filename, 'w') as fh: 29 | if (vertices.shape[1]==6): 30 | fh.write('COFF\n') 31 | else: 32 | fh.write('OFF\n') 33 | fh.write('{} {} 0\n'.format(len(vertices), len(triangles))) 34 | 35 | if (vertices.shape[1]==6): 36 | for v in vertices: 37 | fh.write("{} {} {} {} {} {}\n".format(*v)) 38 | else: 39 | for v in vertices: 40 | fh.write("{} {} {}\n".format(*v)) 41 | 42 | for f in triangles: 43 | fh.write("3 {} {} {}\n".format(*f)) 44 | 45 | 46 | def export_mesh(vertices, triangles, filename, mesh_name="mcubes_mesh"): 47 | """ 48 | Exports a mesh in the COLLADA (.dae) format. 49 | 50 | Needs PyCollada (https://github.com/pycollada/pycollada). 51 | """ 52 | 53 | import collada 54 | 55 | mesh = collada.Collada() 56 | 57 | vert_src = collada.source.FloatSource("verts-array", vertices[:,0:3], ('X','Y','Z')) 58 | geom = collada.geometry.Geometry(mesh, "geometry0", mesh_name, [vert_src]) 59 | 60 | input_list = collada.source.InputList() 61 | input_list.addInput(0, 'VERTEX', "#verts-array") 62 | 63 | triset = geom.createTriangleSet(np.copy(triangles), input_list, "") 64 | geom.primitives.append(triset) 65 | mesh.geometries.append(geom) 66 | 67 | geomnode = collada.scene.GeometryNode(geom, []) 68 | node = collada.scene.Node(mesh_name, children=[geomnode]) 69 | 70 | myscene = collada.scene.Scene("mcubes_scene", [node]) 71 | mesh.scenes.append(myscene) 72 | mesh.scene = myscene 73 | 74 | mesh.write(filename) 75 | -------------------------------------------------------------------------------- /marching_cubes/numpy_smoothing.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | from scipy import ndimage as ndi 4 | 5 | __all__ = [ 6 | 'numpy_smooth', 7 | ] 8 | 9 | FILTER = np.array([1, -2, 1], dtype=np.float_) 10 | 11 | JACOBI_R_2D = np.array([ 12 | [0, 0, 1, 0, 0], 13 | [0, 0, -4, 0, 0], 14 | [1, -4, 0, -4, 1], 15 | [0, 0, -4, 0, 0], 16 | [0, 0, 1, 0, 0] 17 | ], dtype=np.float_) 18 | JACOBI_D_2D = 1/12 19 | 20 | JACOBI_R_3D = np.array([ 21 | [[0, 0, 0, 0, 0], 22 | [0, 0, 0, 0, 0], 23 | [0, 0, 1, 0, 0], 24 | [0, 0, 0, 0, 0], 25 | [0, 0, 0, 0, 0]], 26 | [[0, 0, 0, 0, 0], 27 | [0, 0, 0, 0, 0], 28 | [0, 0, -4, 0, 0], 29 | [0, 0, 0, 0, 0], 30 | [0, 0, 0, 0, 0]], 31 | [[0, 0, 1, 0, 0], 32 | [0, 0, -4, 0, 0], 33 | [1, -4, 0, -4, 1], 34 | [0, 0, -4, 0, 0], 35 | [0, 0, 1, 0, 0]], 36 | [[0, 0, 0, 0, 0], 37 | [0, 0, 0, 0, 0], 38 | [0, 0, -4, 0, 0], 39 | [0, 0, 0, 0, 0], 40 | [0, 0, 0, 0, 0]], 41 | [[0, 0, 0, 0, 0], 42 | [0, 0, 0, 0, 0], 43 | [0, 0, 1, 0, 0], 44 | [0, 0, 0, 0, 0], 45 | [0, 0, 0, 0, 0]] 46 | ], dtype=np.float_) 47 | 48 | JACOBI_D_3D = 1/18 49 | 50 | 51 | def signed_distance_function(binary_arr: np.ndarray) -> np.ndarray: 52 | 53 | arr = np.where(binary_arr > 0, 1.0, 0.0) 54 | dist_func = ndi.distance_transform_edt 55 | distance = np.where( 56 | binary_arr, 57 | dist_func(arr) - 0.5, 58 | -dist_func(1 - arr) + 0.5 59 | ) 60 | return distance 61 | 62 | 63 | def energy(arr: np.ndarray) -> np.ndarray: 64 | 65 | darr2 = [ndi.convolve1d(arr, FILTER, axis=i)**2 for i in range(arr.ndim)] 66 | return np.sum(darr2) / 2 67 | 68 | 69 | def solve_jacobi( 70 | arr: np.ndarray, 71 | lower_bound: np.ndarray, 72 | upper_bound: np.ndarray, 73 | max_iters: int = 500, 74 | jacobi_weight: float = 0.5 75 | ) -> np.ndarray: 76 | 77 | jacobi_d = JACOBI_D_2D if arr.ndim == 2 else JACOBI_D_3D 78 | jacobi_r = JACOBI_R_2D if arr.ndim == 2 else JACOBI_R_3D 79 | 80 | for it in range(max_iters): 81 | # energy_it = torch.sum(diff_energy(arr) * arr[2:-2, 2:-2, 2:-2]) / 2 82 | energy_it = energy(arr) 83 | print("Energy in iteration {}: {:.4g}".format(it, energy_it)) 84 | 85 | r_arr = ndi.convolve(arr, jacobi_r, mode='nearest') 86 | arr_1 = - jacobi_d * r_arr 87 | arr = jacobi_weight * arr_1 + (1 - jacobi_weight) * arr 88 | 89 | arr = np.maximum(arr, lower_bound) 90 | arr = np.minimum(arr, upper_bound) 91 | 92 | return arr 93 | 94 | 95 | def numpy_smooth(binary_array: np.ndarray, max_iters: int = 500) -> np.ndarray: 96 | 97 | arr = signed_distance_function(binary_array) 98 | 99 | upper_bound = np.where(arr < 0, arr, np.inf) 100 | lower_bound = np.where(arr > 0, arr, -np.inf) 101 | 102 | upper_bound[np.abs(upper_bound) < 1] = 0 103 | lower_bound[np.abs(lower_bound) < 1] = 0 104 | 105 | return solve_jacobi(arr, lower_bound, upper_bound, max_iters) 106 | -------------------------------------------------------------------------------- /marching_cubes/smoothing.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Utilities for smoothing the 0.5 level-set of binary arrays. 5 | """ 6 | 7 | import logging 8 | from typing import Tuple 9 | 10 | import numpy as np 11 | from scipy import sparse 12 | from scipy import ndimage as ndi 13 | 14 | __all__ = [ 15 | 'smooth', 16 | 'smooth_constrained', 17 | 'smooth_gaussian', 18 | 'signed_distance_function' 19 | ] 20 | 21 | 22 | def _build_variable_indices(band: np.ndarray) -> np.ndarray: 23 | num_variables = np.count_nonzero(band) 24 | variable_indices = np.full(band.shape, -1, dtype=np.int_) 25 | variable_indices[band] = np.arange(num_variables) 26 | return variable_indices 27 | 28 | 29 | def _buildq3d(variable_indices: np.ndarray): 30 | """ 31 | Builds the filterq matrix for the given variables. 32 | """ 33 | 34 | num_variables = variable_indices.max() + 1 35 | filterq = sparse.lil_matrix((3*num_variables, num_variables)) 36 | 37 | # Pad variable_indices to simplify out-of-bounds accesses 38 | variable_indices = np.pad( 39 | variable_indices, 40 | [(0, 1), (0, 1), (0, 1)], 41 | mode='constant', 42 | constant_values=-1 43 | ) 44 | 45 | coords = np.nonzero(variable_indices >= 0) 46 | for count, (i, j, k) in enumerate(zip(*coords)): 47 | 48 | assert(variable_indices[i, j, k] == count) 49 | 50 | filterq[3*count, count] = -2 51 | neighbor = variable_indices[i-1, j, k] 52 | if neighbor >= 0: 53 | filterq[3*count, neighbor] = 1 54 | else: 55 | filterq[3*count, count] += 1 56 | 57 | neighbor = variable_indices[i+1, j, k] 58 | if neighbor >= 0: 59 | filterq[3*count, neighbor] = 1 60 | else: 61 | filterq[3*count, count] += 1 62 | 63 | filterq[3*count+1, count] = -2 64 | neighbor = variable_indices[i, j-1, k] 65 | if neighbor >= 0: 66 | filterq[3*count+1, neighbor] = 1 67 | else: 68 | filterq[3*count+1, count] += 1 69 | 70 | neighbor = variable_indices[i, j+1, k] 71 | if neighbor >= 0: 72 | filterq[3*count+1, neighbor] = 1 73 | else: 74 | filterq[3*count+1, count] += 1 75 | 76 | filterq[3*count+2, count] = -2 77 | neighbor = variable_indices[i, j, k-1] 78 | if neighbor >= 0: 79 | filterq[3*count+2, neighbor] = 1 80 | else: 81 | filterq[3*count+2, count] += 1 82 | 83 | neighbor = variable_indices[i, j, k+1] 84 | if neighbor >= 0: 85 | filterq[3*count+2, neighbor] = 1 86 | else: 87 | filterq[3*count+2, count] += 1 88 | 89 | filterq = filterq.tocsr() 90 | return filterq.T.dot(filterq) 91 | 92 | 93 | def _buildq2d(variable_indices: np.ndarray): 94 | """ 95 | Builds the filterq matrix for the given variables. 96 | 97 | Version for 2 dimensions. 98 | """ 99 | 100 | num_variables = variable_indices.max() + 1 101 | filterq = sparse.lil_matrix((3*num_variables, num_variables)) 102 | 103 | # Pad variable_indices to simplify out-of-bounds accesses 104 | variable_indices = np.pad( 105 | variable_indices, 106 | [(0, 1), (0, 1)], 107 | mode='constant', 108 | constant_values=-1 109 | ) 110 | 111 | coords = np.nonzero(variable_indices >= 0) 112 | for count, (i, j) in enumerate(zip(*coords)): 113 | assert(variable_indices[i, j] == count) 114 | 115 | filterq[2*count, count] = -2 116 | neighbor = variable_indices[i-1, j] 117 | if neighbor >= 0: 118 | filterq[2*count, neighbor] = 1 119 | else: 120 | filterq[2*count, count] += 1 121 | 122 | neighbor = variable_indices[i+1, j] 123 | if neighbor >= 0: 124 | filterq[2*count, neighbor] = 1 125 | else: 126 | filterq[2*count, count] += 1 127 | 128 | filterq[2*count+1, count] = -2 129 | neighbor = variable_indices[i, j-1] 130 | if neighbor >= 0: 131 | filterq[2*count+1, neighbor] = 1 132 | else: 133 | filterq[2*count+1, count] += 1 134 | 135 | neighbor = variable_indices[i, j+1] 136 | if neighbor >= 0: 137 | filterq[2*count+1, neighbor] = 1 138 | else: 139 | filterq[2*count+1, count] += 1 140 | 141 | filterq = filterq.tocsr() 142 | return filterq.T.dot(filterq) 143 | 144 | 145 | def _jacobi( 146 | filterq, 147 | x0: np.ndarray, 148 | lower_bound: np.ndarray, 149 | upper_bound: np.ndarray, 150 | max_iters: int = 10, 151 | rel_tol: float = 1e-6, 152 | weight: float = 0.5): 153 | """Jacobi method with constraints.""" 154 | 155 | jacobi_r = sparse.lil_matrix(filterq) 156 | shp = jacobi_r.shape 157 | jacobi_d = 1.0 / filterq.diagonal() 158 | jacobi_r.setdiag((0,) * shp[0]) 159 | jacobi_r = jacobi_r.tocsr() 160 | 161 | x = x0 162 | 163 | # We check the stopping criterion each 10 iterations 164 | check_each = 10 165 | cum_rel_tol = 1 - (1 - rel_tol)**check_each 166 | 167 | energy_now = np.dot(x, filterq.dot(x)) / 2 168 | logging.info("Energy at iter %d: %.6g", 0, energy_now) 169 | for i in range(max_iters): 170 | 171 | x_1 = - jacobi_d * jacobi_r.dot(x) 172 | x = weight * x_1 + (1 - weight) * x 173 | 174 | # Constraints. 175 | x = np.maximum(x, lower_bound) 176 | x = np.minimum(x, upper_bound) 177 | 178 | # Stopping criterion 179 | if (i + 1) % check_each == 0: 180 | # Update energy 181 | energy_before = energy_now 182 | energy_now = np.dot(x, filterq.dot(x)) / 2 183 | 184 | logging.info("Energy at iter %d: %.6g", i + 1, energy_now) 185 | 186 | # Check stopping criterion 187 | cum_rel_improvement = (energy_before - energy_now) / energy_before 188 | if cum_rel_improvement < cum_rel_tol: 189 | break 190 | 191 | return x 192 | 193 | 194 | def signed_distance_function( 195 | levelset: np.ndarray, 196 | band_radius: int 197 | ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: 198 | """ 199 | Return the distance to the 0.5 levelset of a function, the mask of the 200 | border (i.e., the nearest cells to the 0.5 level-set) and the mask of the 201 | band (i.e., the cells of the function whose distance to the 0.5 level-set 202 | is less of equal to `band_radius`). 203 | """ 204 | 205 | binary_array = np.where(levelset > 0, True, False) 206 | 207 | # Compute the band and the border. 208 | dist_func = ndi.distance_transform_edt 209 | distance = np.where( 210 | binary_array, 211 | dist_func(binary_array) - 0.5, 212 | -dist_func(~binary_array) + 0.5 213 | ) 214 | border = np.abs(distance) < 1 215 | band = np.abs(distance) <= band_radius 216 | 217 | return distance, border, band 218 | 219 | 220 | def smooth_constrained( 221 | binary_array: np.ndarray, 222 | band_radius: int = 4, 223 | max_iters: int = 250, 224 | rel_tol: float = 1e-6 225 | ) -> np.ndarray: 226 | """ 227 | Implementation of the smoothing method from 228 | 229 | "Surface Extraction from Binary Volumes with Higher-Order Smoothness" 230 | Victor Lempitsky, CVPR10 231 | """ 232 | 233 | # # Compute the distance map, the border and the band. 234 | logging.info("Computing distance transform...") 235 | distance, _, band = signed_distance_function(binary_array, band_radius) 236 | 237 | variable_indices = _build_variable_indices(band) 238 | 239 | # Compute filterq. 240 | logging.info("Building matrix filterq...") 241 | if binary_array.ndim == 3: 242 | filterq = _buildq3d(variable_indices) 243 | elif binary_array.ndim == 2: 244 | filterq = _buildq2d(variable_indices) 245 | else: 246 | raise ValueError("binary_array.ndim not in [2, 3]") 247 | 248 | # Initialize the variables. 249 | res = np.asarray(distance, dtype=np.double) 250 | x = res[band] 251 | upper_bound = np.where(x < 0, x, np.inf) 252 | lower_bound = np.where(x > 0, x, -np.inf) 253 | 254 | upper_bound[np.abs(upper_bound) < 1] = 0 255 | lower_bound[np.abs(lower_bound) < 1] = 0 256 | 257 | # Solve. 258 | logging.info("Minimizing energy...") 259 | x = _jacobi( 260 | filterq=filterq, 261 | x0=x, 262 | lower_bound=lower_bound, 263 | upper_bound=upper_bound, 264 | max_iters=max_iters, 265 | rel_tol=rel_tol 266 | ) 267 | 268 | res[band] = x 269 | return res 270 | 271 | 272 | def smooth_gaussian(binary_array: np.ndarray, sigma: float = 3) -> np.ndarray: 273 | vol = np.float_(binary_array) - 0.5 274 | return ndi.gaussian_filter(vol, sigma=sigma) 275 | 276 | 277 | def smooth( 278 | binary_array: np.ndarray, 279 | method: str = 'auto', 280 | **kwargs 281 | ) -> np.ndarray: 282 | """ 283 | Smooths the 0.5 level-set of a binary array. Returns a floating-point 284 | array with a smoothed version of the original level-set in the 0 isovalue. 285 | 286 | This function can apply two different methods: 287 | 288 | - A constrained smoothing method which preserves details and fine 289 | structures, but it is slow and requires a large amount of memory. This 290 | method is recommended when the input array is small (smaller than 291 | (500, 500, 500)). 292 | - A Gaussian filter applied over the binary array. This method is fast, but 293 | not very precise, as it can destroy fine details. It is only recommended 294 | when the input array is large and the 0.5 level-set does not contain 295 | thin structures. 296 | 297 | Parameters 298 | ---------- 299 | binary_array : ndarray 300 | Input binary array with the 0.5 level-set to smooth. 301 | method : str, one of ['auto', 'gaussian', 'constrained'] 302 | Smoothing method. If 'auto' is given, the method will be automatically 303 | chosen based on the size of `binary_array`. 304 | 305 | Parameters for 'gaussian' 306 | ------------------------- 307 | sigma : float 308 | Size of the Gaussian filter (default 3). 309 | 310 | Parameters for 'constrained' 311 | ---------------------------- 312 | max_iters : positive integer 313 | Number of iterations of the constrained optimization method 314 | (default 250). 315 | rel_tol: float 316 | Relative tolerance as a stopping criterion (default 1e-6). 317 | 318 | Output 319 | ------ 320 | res : ndarray 321 | Floating-point array with a smoothed 0 level-set. 322 | """ 323 | 324 | binary_array = np.asarray(binary_array) 325 | 326 | if method == 'auto': 327 | if binary_array.size > 500**3: 328 | method = 'gaussian' 329 | else: 330 | method = 'constrained' 331 | 332 | if method == 'gaussian': 333 | return smooth_gaussian(binary_array, **kwargs) 334 | 335 | if method == 'constrained': 336 | return smooth_constrained(binary_array, **kwargs) 337 | 338 | raise ValueError("Unknown method '{}'".format(method)) 339 | -------------------------------------------------------------------------------- /marching_cubes/src/_mcubes.pyx: -------------------------------------------------------------------------------- 1 | 2 | # distutils: language = c++ 3 | # cython: embedsignature = True 4 | 5 | # from libcpp.vector cimport vector 6 | import numpy as np 7 | 8 | # Define PY_ARRAY_UNIQUE_SYMBOL 9 | cdef extern from "pyarray_symbol.h": 10 | pass 11 | 12 | cimport numpy as np 13 | 14 | np.import_array() 15 | 16 | cdef extern from "pywrapper.h": 17 | cdef object c_marching_cubes "marching_cubes"(np.ndarray, double) except + 18 | cdef object c_marching_cubes_func "marching_cubes_func"(tuple, tuple, int, int, int, object, double) except + 19 | cdef object c_marching_cubes_color "marching_cubes_color"(np.ndarray, np.ndarray, double) except + 20 | cdef object c_marching_cubes_color_func "marching_cubes_color_func"(tuple, tuple, int, int, int, object, object, object, object, double) except + 21 | cdef object c_marching_cubes_super_sampling "marching_cubes_super_sampling"(np.ndarray, np.ndarray, np.ndarray, double) except + 22 | 23 | def marching_cubes(np.ndarray volume, float isovalue): 24 | 25 | verts, faces = c_marching_cubes(volume, isovalue) 26 | verts.shape = (-1, 3) 27 | faces.shape = (-1, 3) 28 | return verts, faces 29 | 30 | def marching_cubes_super_sampling(np.ndarray volumeX, np.ndarray volumeY, np.ndarray volumeZ, float isovalue): 31 | 32 | verts, faces = c_marching_cubes_super_sampling(volumeX, volumeY, volumeZ, isovalue) 33 | verts.shape = (-1, 3) 34 | faces.shape = (-1, 3) 35 | return verts, faces 36 | 37 | def marching_cubes_func(tuple lower, tuple upper, int numx, int numy, int numz, object f, double isovalue): 38 | 39 | if any(l_i >= u_i for l_i, u_i in zip(lower, upper)): 40 | raise ValueError("lower coordinates cannot be larger than upper coordinates") 41 | 42 | if numx < 2 or numy < 2 or numz < 2: 43 | raise ValueError("numx, numy, numz cannot be smaller than 2") 44 | 45 | verts, faces = c_marching_cubes_func(lower, upper, numx, numy, numz, f, isovalue) 46 | verts.shape = (-1, 3) 47 | faces.shape = (-1, 3) 48 | return verts, faces 49 | 50 | 51 | 52 | def marching_cubes_color(np.ndarray volume_sdf, np.ndarray volume_color, float isovalue): 53 | 54 | verts, faces = c_marching_cubes_color(volume_sdf, volume_color, isovalue) 55 | verts.shape = (-1, 6) 56 | faces.shape = (-1, 3) 57 | return verts, faces 58 | 59 | def marching_cubes_color_func(tuple lower, tuple upper, int numx, int numy, int numz, object f_sdf, object f_color_r, object f_color_g, object f_color_b, double isovalue): 60 | 61 | if any(l_i >= u_i for l_i, u_i in zip(lower, upper)): 62 | raise ValueError("lower coordinates cannot be larger than upper coordinates") 63 | 64 | if numx < 2 or numy < 2 or numz < 2: 65 | raise ValueError("numx, numy, numz cannot be smaller than 2") 66 | 67 | verts, faces = c_marching_cubes_color_func(lower, upper, numx, numy, numz, f_sdf, f_color_r, f_color_g, f_color_b, isovalue) 68 | verts.shape = (-1, 6) 69 | faces.shape = (-1, 3) 70 | return verts, faces 71 | 72 | 73 | -------------------------------------------------------------------------------- /marching_cubes/src/marchingcubes.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "marchingcubes.h" 3 | 4 | namespace mc 5 | { 6 | 7 | int edge_table[256] = 8 | { 9 | 0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, 10 | 0x190, 0x099, 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, 11 | 0x230, 0x339, 0x033, 0x13a, 0x636, 0x73f, 0x435, 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, 12 | 0x3a0, 0x2a9, 0x1a3, 0x0aa, 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, 13 | 0x460, 0x569, 0x663, 0x76a, 0x066, 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, 14 | 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0x0ff, 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, 15 | 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x055, 0x15c, 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, 16 | 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0x0cc, 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, 17 | 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0x0cc, 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, 18 | 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x055, 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, 19 | 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc, 0x3f5, 0x0ff, 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, 20 | 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x066, 0x76a, 0x663, 0x569, 0x460, 21 | 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0x0aa, 0x1a3, 0x2a9, 0x3a0, 22 | 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x033, 0x339, 0x230, 23 | 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x099, 0x190, 24 | 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x000 25 | }; 26 | 27 | int triangle_table[256][16] = 28 | { 29 | {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 30 | {0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 31 | {0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 32 | {1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 33 | {1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 34 | {0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 35 | {9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 36 | {2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1}, 37 | {3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 38 | {0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 39 | {1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 40 | {1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1}, 41 | {3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 42 | {0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1}, 43 | {3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1}, 44 | {9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 45 | {4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 46 | {4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 47 | {0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 48 | {4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1}, 49 | {1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 50 | {3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1}, 51 | {9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, 52 | {2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1}, 53 | {8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 54 | {11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1}, 55 | {9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, 56 | {4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1}, 57 | {3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1}, 58 | {1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1}, 59 | {4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1}, 60 | {4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1}, 61 | {9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 62 | {9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 63 | {0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 64 | {8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1}, 65 | {1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 66 | {3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, 67 | {5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1}, 68 | {2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1}, 69 | {9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 70 | {0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, 71 | {0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, 72 | {2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1}, 73 | {10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1}, 74 | {4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1}, 75 | {5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1}, 76 | {5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1}, 77 | {9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 78 | {9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1}, 79 | {0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1}, 80 | {1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 81 | {9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1}, 82 | {10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1}, 83 | {8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1}, 84 | {2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1}, 85 | {7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1}, 86 | {9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1}, 87 | {2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1}, 88 | {11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1}, 89 | {9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1}, 90 | {5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1}, 91 | {11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1}, 92 | {11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 93 | {10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 94 | {0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 95 | {9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 96 | {1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, 97 | {1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 98 | {1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1}, 99 | {9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1}, 100 | {5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1}, 101 | {2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 102 | {11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, 103 | {0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, 104 | {5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1}, 105 | {6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1}, 106 | {0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1}, 107 | {3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1}, 108 | {6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1}, 109 | {5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 110 | {4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1}, 111 | {1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, 112 | {10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1}, 113 | {6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1}, 114 | {1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1}, 115 | {8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1}, 116 | {7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1}, 117 | {3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, 118 | {5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1}, 119 | {0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1}, 120 | {9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1}, 121 | {8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1}, 122 | {5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1}, 123 | {0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1}, 124 | {6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1}, 125 | {10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 126 | {4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1}, 127 | {10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1}, 128 | {8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1}, 129 | {1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1}, 130 | {3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1}, 131 | {0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 132 | {8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1}, 133 | {10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1}, 134 | {0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1}, 135 | {3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1}, 136 | {6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1}, 137 | {9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1}, 138 | {8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1}, 139 | {3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1}, 140 | {6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 141 | {7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1}, 142 | {0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1}, 143 | {10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1}, 144 | {10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1}, 145 | {1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1}, 146 | {2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1}, 147 | {7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1}, 148 | {7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 149 | {2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1}, 150 | {2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1}, 151 | {1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1}, 152 | {11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1}, 153 | {8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1}, 154 | {0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 155 | {7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1}, 156 | {7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 157 | {7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 158 | {3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 159 | {0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 160 | {8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, 161 | {10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 162 | {1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, 163 | {2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, 164 | {6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1}, 165 | {7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 166 | {7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1}, 167 | {2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1}, 168 | {1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1}, 169 | {10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1}, 170 | {10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1}, 171 | {0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1}, 172 | {7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1}, 173 | {6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 174 | {3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1}, 175 | {8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1}, 176 | {9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1}, 177 | {6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1}, 178 | {1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1}, 179 | {4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1}, 180 | {10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1}, 181 | {8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1}, 182 | {0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 183 | {1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1}, 184 | {1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1}, 185 | {8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1}, 186 | {10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1}, 187 | {4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1}, 188 | {10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 189 | {4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 190 | {0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, 191 | {5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, 192 | {11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1}, 193 | {9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, 194 | {6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1}, 195 | {7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1}, 196 | {3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1}, 197 | {7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1}, 198 | {9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1}, 199 | {3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1}, 200 | {6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1}, 201 | {9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1}, 202 | {1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1}, 203 | {4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1}, 204 | {7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1}, 205 | {6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1}, 206 | {3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1}, 207 | {0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1}, 208 | {6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1}, 209 | {1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1}, 210 | {0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1}, 211 | {11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1}, 212 | {6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1}, 213 | {5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1}, 214 | {9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1}, 215 | {1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1}, 216 | {1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 217 | {1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1}, 218 | {10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1}, 219 | {0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 220 | {10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 221 | {11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 222 | {11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1}, 223 | {5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1}, 224 | {10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1}, 225 | {11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1}, 226 | {0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1}, 227 | {9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1}, 228 | {7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1}, 229 | {2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1}, 230 | {8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1}, 231 | {9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1}, 232 | {9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1}, 233 | {1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 234 | {0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1}, 235 | {9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1}, 236 | {9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 237 | {5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1}, 238 | {5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1}, 239 | {0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1}, 240 | {10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1}, 241 | {2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1}, 242 | {0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1}, 243 | {0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1}, 244 | {9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 245 | {2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1}, 246 | {5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1}, 247 | {3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1}, 248 | {5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1}, 249 | {8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1}, 250 | {0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 251 | {8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1}, 252 | {9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 253 | {4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1}, 254 | {0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1}, 255 | {1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1}, 256 | {3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1}, 257 | {4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1}, 258 | {9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1}, 259 | {11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1}, 260 | {11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1}, 261 | {2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1}, 262 | {9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1}, 263 | {3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1}, 264 | {1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 265 | {4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1}, 266 | {4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1}, 267 | {4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 268 | {4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 269 | {9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 270 | {3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1}, 271 | {0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1}, 272 | {3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 273 | {1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1}, 274 | {3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1}, 275 | {0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 276 | {3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 277 | {2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1}, 278 | {9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 279 | {2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1}, 280 | {1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 281 | {1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 282 | {0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 283 | {0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, 284 | {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1} 285 | }; 286 | 287 | namespace private_ 288 | { 289 | 290 | double mc_isovalue_interpolation(double isovalue, double f1, double f2, 291 | double x1, double x2) 292 | { 293 | if(f2==f1) 294 | return (x2+x1)/2; 295 | 296 | return (x2-x1)*(isovalue-f1)/(f2-f1) + x1; 297 | } 298 | 299 | void mc_add_vertex(double x1, double y1, double z1, double c2, 300 | int axis, double f1, double f2, double isovalue, std::vector* vertices) 301 | { 302 | if(axis == 0) 303 | { 304 | double x = mc_isovalue_interpolation(isovalue, f1, f2, x1, c2); 305 | vertices->push_back(x); 306 | vertices->push_back(y1); 307 | vertices->push_back(z1); 308 | return; 309 | } 310 | if(axis == 1) 311 | { 312 | double y = mc_isovalue_interpolation(isovalue, f1, f2, y1, c2); 313 | vertices->push_back(x1); 314 | vertices->push_back(y); 315 | vertices->push_back(z1); 316 | return; 317 | } 318 | if(axis == 2) 319 | { 320 | double z = mc_isovalue_interpolation(isovalue, f1, f2, z1, c2); 321 | vertices->push_back(x1); 322 | vertices->push_back(y1); 323 | vertices->push_back(z); 324 | return; 325 | } 326 | } 327 | 328 | } 329 | 330 | } 331 | -------------------------------------------------------------------------------- /marching_cubes/src/marchingcubes.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _MARCHING_CUBES_H 3 | #define _MARCHING_CUBES_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "../../eigen/Eigen/Eigen" // git clone https://gitlab.com/libeigen/eigen.git 11 | 12 | typedef Eigen::Matrix Vector6d; 13 | 14 | namespace mc 15 | { 16 | 17 | extern int edge_table[256]; 18 | extern int triangle_table[256][16]; 19 | 20 | namespace private_ 21 | { 22 | 23 | double mc_isovalue_interpolation(double isovalue, double f1, double f2, 24 | double x1, double x2); 25 | void mc_add_vertex(double x1, double y1, double z1, double c2, 26 | int axis, double f1, double f2, double isovalue, std::vector* vertices); 27 | 28 | 29 | 30 | struct MC_Triangle { 31 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 32 | int p[3]; 33 | }; 34 | 35 | struct MC_Gridcell { 36 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 37 | Eigen::Vector3d p[8]; 38 | double val[8]; 39 | }; 40 | 41 | struct MC_Gridcell_Color { 42 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 43 | Vector6d p[8]; 44 | double val[8]; 45 | }; 46 | 47 | } 48 | 49 | 50 | 51 | template 52 | void marching_cubes(const vector3& lower, const vector3& upper, 53 | int numx, int numy, int numz, formula f, double isovalue, 54 | std::vector& vertices, std::vector& polygons) 55 | { 56 | using coord_type = typename vector3::value_type; 57 | using size_type = typename vector3::size_type; 58 | using namespace private_; 59 | 60 | // Some initial checks 61 | if(numx < 2 || numy < 2 || numz < 2) 62 | return; 63 | 64 | if(!std::equal(std::begin(lower), std::end(lower), std::begin(upper), 65 | [](double a, double b)->bool {return a <= b;})) 66 | return; 67 | 68 | 69 | #define OWN_IMPL 70 | 71 | #ifdef OWN_IMPL 72 | 73 | double dx = (upper[0] - lower[0]) / (numx - 1.0); 74 | double dy = (upper[1] - lower[1]) / (numy - 1.0); 75 | double dz = (upper[2] - lower[2]) / (numz - 1.0); 76 | 77 | auto coord_mapper = [&](int x, int y, int z) { return Eigen::Vector3d( lower[0] + x * dx, lower[1] + y * dy, lower[2] + z * dz ); }; 78 | auto push_vertex = [&] (Eigen::Vector3d xyz) {int id = vertices.size()/3; vertices.push_back(xyz.x()); vertices.push_back(xyz.y()); vertices.push_back(xyz.z()); return id;}; 79 | 80 | // vertex zero crossing interpolation 81 | // f(p2) = valp2 82 | // x 83 | // / 84 | // x f(p) = isolevel 85 | // / 86 | // / 87 | // / 88 | // x 89 | // f(p1) = valp1 90 | auto VertexInterp = [&](double isolevel, const Eigen::Vector3d& p1, const Eigen::Vector3d& p2, double valp1, double valp2) -> Eigen::Vector3d 91 | { 92 | double alpha = (valp2 - isolevel) / (valp2 - valp1); 93 | return alpha*p1 + (1 - alpha)*p2; 94 | }; 95 | 96 | // store intersections of old z plane to avoid duplicated vertices 97 | int* edge_intersections_old_x = new int[(numx-1) * numy]; 98 | int* edge_intersections_old_y = new int[numx * (numy-1)]; 99 | int* edge_intersections_current_x = new int[(numx-1) * numy]; 100 | int* edge_intersections_current_y = new int[numx * (numy-1)]; 101 | 102 | // store intersections within the z-planes to avoid duplicated vertices 103 | int* edge_intersections_current_z = new int[numx * numy]; 104 | 105 | for (int z = 0; z < numz - 1; z++) 106 | { 107 | // swap index storage 108 | std::swap(edge_intersections_old_x, edge_intersections_current_x); // old = current 109 | std::swap(edge_intersections_old_y, edge_intersections_current_y); 110 | std::fill_n(edge_intersections_current_x, (numx-1) * numy, -1); // invalidate 111 | std::fill_n(edge_intersections_current_y, (numy-1) * numx, -1); // invalidate 112 | 113 | std::fill_n(edge_intersections_current_z, numy * numx, -1); // invalidate 114 | 115 | for (int y = 0; y < numy - 1; y++) 116 | { 117 | for (int x = 0; x < numx - 1; x++) 118 | { 119 | // Process Volume Cell 120 | MC_Gridcell cell; 121 | // 122 | // 4---5 123 | // / /| 124 | // 0---1 6 125 | // | |/ 126 | // 3---2 127 | // cell corners 128 | cell.p[0] = coord_mapper(x + 1, y, z); 129 | cell.p[1] = coord_mapper(x, y, z); 130 | cell.p[2] = coord_mapper(x, y + 1, z); 131 | cell.p[3] = coord_mapper(x + 1, y + 1, z); 132 | cell.p[4] = coord_mapper(x + 1, y, z + 1); 133 | cell.p[5] = coord_mapper(x, y, z + 1); 134 | cell.p[6] = coord_mapper(x, y + 1, z + 1); 135 | cell.p[7] = coord_mapper(x + 1, y + 1, z + 1); 136 | 137 | // cell corner values 138 | cell.val[0] = (double)f(x + 1, y, z); 139 | cell.val[1] = (double)f(x, y, z); 140 | cell.val[2] = (double)f(x, y + 1, z); 141 | cell.val[3] = (double)f(x + 1, y + 1, z); 142 | cell.val[4] = (double)f(x + 1, y, z + 1); 143 | cell.val[5] = (double)f(x, y, z + 1); 144 | cell.val[6] = (double)f(x, y + 1, z + 1); 145 | cell.val[7] = (double)f(x + 1, y + 1, z + 1); 146 | 147 | // triangulation code 148 | int cubeindex = 0; 149 | if (cell.val[0] < isovalue) cubeindex |= 1; 150 | if (cell.val[1] < isovalue) cubeindex |= 2; 151 | if (cell.val[2] < isovalue) cubeindex |= 4; 152 | if (cell.val[3] < isovalue) cubeindex |= 8; 153 | if (cell.val[4] < isovalue) cubeindex |= 16; 154 | if (cell.val[5] < isovalue) cubeindex |= 32; 155 | if (cell.val[6] < isovalue) cubeindex |= 64; 156 | if (cell.val[7] < isovalue) cubeindex |= 128; 157 | 158 | // Cube is entirely in/out of the surface 159 | if (edge_table[cubeindex] == 0) continue; 160 | 161 | 162 | /* Find the vertices where the surface intersects the cube */ 163 | int vertlist[12]; 164 | { // edges on the old z plane 165 | if (edge_table[cubeindex] & 1) // edge in x at y 166 | { 167 | if(z==0) vertlist[0] = push_vertex(VertexInterp(isovalue, cell.p[0], cell.p[1], cell.val[0], cell.val[1])); 168 | else vertlist[0] = edge_intersections_old_x[y * (numx-1) + x]; 169 | } 170 | if (edge_table[cubeindex] & 2) // edge in y at x 171 | { 172 | if(z==0) vertlist[1] = push_vertex(VertexInterp(isovalue, cell.p[1], cell.p[2], cell.val[1], cell.val[2])); 173 | else vertlist[1] = edge_intersections_old_y[x * (numy-1) + y]; 174 | } 175 | if (edge_table[cubeindex] & 4) // edge in x at y+1 176 | { 177 | if(z==0) vertlist[2] = push_vertex(VertexInterp(isovalue, cell.p[2], cell.p[3], cell.val[2], cell.val[3])); 178 | else vertlist[2] = edge_intersections_old_x[(y+1) * (numx-1) + x]; 179 | } 180 | if (edge_table[cubeindex] & 8) // edge in y at x+1 181 | { 182 | if(z==0) vertlist[3] = push_vertex(VertexInterp(isovalue, cell.p[3], cell.p[0], cell.val[3], cell.val[0])); 183 | else vertlist[3] = edge_intersections_old_y[(x+1) * (numy-1) + y]; 184 | } 185 | } 186 | 187 | { // edges on the new z plane 188 | if (edge_table[cubeindex] & 16) // edge in x at y 189 | { 190 | if (edge_intersections_current_x[y * (numx-1) + x] == -1) // check if already assigned 191 | { 192 | vertlist[4] = push_vertex(VertexInterp(isovalue, cell.p[4], cell.p[5], cell.val[4], cell.val[5])); 193 | edge_intersections_current_x[y * (numx-1) + x] = vertlist[4]; 194 | } 195 | else 196 | { 197 | vertlist[4] = edge_intersections_current_x[y * (numx-1) + x]; 198 | } 199 | } 200 | if (edge_table[cubeindex] & 32) // edge in y at x 201 | { 202 | if(edge_intersections_current_y[x * (numy-1) + y] == -1) 203 | { 204 | vertlist[5] = push_vertex(VertexInterp(isovalue, cell.p[5], cell.p[6], cell.val[5], cell.val[6])); 205 | edge_intersections_current_y[x * (numy-1) + y] = vertlist[5]; 206 | } 207 | else 208 | { 209 | vertlist[5] = edge_intersections_current_y[x * (numy-1) + y]; 210 | } 211 | } 212 | if (edge_table[cubeindex] & 64) // edge in x at y+1 213 | { 214 | if (edge_intersections_current_x[(y+1) * (numx-1) + x] == -1) 215 | { 216 | vertlist[6] = push_vertex(VertexInterp(isovalue, cell.p[6], cell.p[7], cell.val[6], cell.val[7])); 217 | edge_intersections_current_x[(y+1) * (numx-1) + x] = vertlist[6]; 218 | } 219 | else 220 | { 221 | vertlist[6] = edge_intersections_current_x[(y+1) * (numx-1) + x]; 222 | } 223 | } 224 | if (edge_table[cubeindex] & 128) // edge in y at x+1 225 | { 226 | if (edge_intersections_current_y[(x+1) * (numy-1) + y] == -1) 227 | { 228 | vertlist[7] = push_vertex(VertexInterp(isovalue, cell.p[7], cell.p[4], cell.val[7], cell.val[4])); 229 | edge_intersections_current_y[(x+1) * (numy-1) + y] = vertlist[7]; 230 | } 231 | else 232 | { 233 | vertlist[7] = edge_intersections_current_y[(x+1) * (numy-1) + y]; 234 | } 235 | } 236 | } 237 | 238 | { // between the z planes 239 | if (edge_table[cubeindex] & 256) // 0 -- 4, x + 1, y 240 | { 241 | if (edge_intersections_current_z[y * numx + (x+1)] == -1) 242 | { 243 | vertlist[8] = push_vertex(VertexInterp(isovalue, cell.p[0], cell.p[4], cell.val[0], cell.val[4])); 244 | edge_intersections_current_z[y * numx + (x+1)] = vertlist[8]; 245 | } 246 | else 247 | { 248 | vertlist[8] = edge_intersections_current_z[y * numx + (x+1)]; 249 | } 250 | } 251 | if (edge_table[cubeindex] & 512) // 1 -- 5, x, y 252 | { 253 | if (edge_intersections_current_z[y * numx + x] == -1) 254 | { 255 | vertlist[9] = push_vertex(VertexInterp(isovalue, cell.p[1], cell.p[5], cell.val[1], cell.val[5])); 256 | edge_intersections_current_z[y * numx + x] = vertlist[9]; 257 | } 258 | else 259 | { 260 | vertlist[9] = edge_intersections_current_z[y * numx + x]; 261 | } 262 | } 263 | if (edge_table[cubeindex] & 1024) // 2 -- 6, x, y + 1 264 | { 265 | if (edge_intersections_current_z[(y+1) * numx + x] == -1) 266 | { 267 | vertlist[10] = push_vertex(VertexInterp(isovalue, cell.p[2], cell.p[6], cell.val[2], cell.val[6])); 268 | edge_intersections_current_z[(y+1) * numx + x] = vertlist[10]; 269 | } 270 | else 271 | { 272 | vertlist[10] = edge_intersections_current_z[(y+1) * numx + x]; 273 | } 274 | } 275 | if (edge_table[cubeindex] & 2048) // 3 -- 7, x + 1, y + 1 276 | { 277 | if (edge_intersections_current_z[(y+1) * numx + (x+1)] == -1) 278 | { 279 | vertlist[11] = push_vertex(VertexInterp(isovalue, cell.p[3], cell.p[7], cell.val[3], cell.val[7])); 280 | edge_intersections_current_z[(y+1) * numx + (x+1)] = vertlist[11]; 281 | } 282 | else 283 | { 284 | vertlist[11] = edge_intersections_current_z[(y+1) * numx + (x+1)]; 285 | } 286 | } 287 | } 288 | 289 | // push face indices 290 | for (int i = 0; triangle_table[cubeindex][i] != -1; ++i) 291 | polygons.push_back(vertlist[triangle_table[cubeindex][i]]); 292 | 293 | } 294 | } 295 | } 296 | 297 | 298 | delete[] edge_intersections_old_x; 299 | delete[] edge_intersections_old_y; 300 | delete[] edge_intersections_current_x; 301 | delete[] edge_intersections_current_y; 302 | delete[] edge_intersections_current_z; 303 | 304 | #else 305 | 306 | // numx, numy and numz are the numbers of evaluations in each direction 307 | --numx; --numy; --numz; 308 | 309 | coord_type dx = (upper[0] - lower[0]) / static_cast(numx); 310 | coord_type dy = (upper[1] - lower[1]) / static_cast(numy); 311 | coord_type dz = (upper[2] - lower[2]) / static_cast(numz); 312 | 313 | const int num_shared_indices = 2 * (numy + 1) * (numz + 1); 314 | std::vector shared_indices_x(num_shared_indices); 315 | std::vector shared_indices_y(num_shared_indices); 316 | std::vector shared_indices_z(num_shared_indices); 317 | 318 | // const int numz = numz*3; 319 | const int numyz = numy*numz; 320 | 321 | for(int i=0; i indices; 356 | // for three edges we have to compute it for sure (if zerocrossing) 357 | if(edges & 0x040) 358 | { 359 | indices[6] = vertices.size() / 3; 360 | shared_indices_x[i_mod_2_inv*numyz + (j+1)*numz + (k+1)] = indices[6]; 361 | mc_add_vertex(x_dx, y_dy, z_dz, x, 0, v[6], v[7], isovalue, &vertices); 362 | } 363 | if(edges & 0x020) 364 | { 365 | indices[5] = vertices.size() / 3; 366 | shared_indices_y[i_mod_2_inv*numyz + (j+1)*numz + (k+1)] = indices[5]; 367 | mc_add_vertex(x_dx, y, z_dz, y_dy, 1, v[5], v[6], isovalue, &vertices); 368 | } 369 | if(edges & 0x400) 370 | { 371 | indices[10] = vertices.size() / 3; 372 | shared_indices_z[i_mod_2_inv*numyz + (j+1)*numz + (k+1)] = indices[10]; 373 | mc_add_vertex(x_dx, y+dx, z, z_dz, 2, v[2], v[6], isovalue, &vertices); 374 | } 375 | 376 | if(edges & 0x001) 377 | { 378 | if(j == 0 && k == 0) 379 | { 380 | indices[0] = vertices.size() / 3; 381 | mc_add_vertex(x, y, z, x_dx, 0, v[0], v[1], isovalue, &vertices); 382 | } 383 | else 384 | indices[0] = shared_indices_x[i_mod_2_inv*numyz + j*numz + k]; 385 | } 386 | if(edges & 0x002) 387 | { 388 | if(k == 0) 389 | { 390 | indices[1] = vertices.size() / 3; 391 | shared_indices_y[i_mod_2_inv*numyz + (j+1)*numz + k] = indices[1]; 392 | mc_add_vertex(x_dx, y, z, y_dy, 1, v[1], v[2], isovalue, &vertices); 393 | } 394 | else 395 | indices[1] = shared_indices_y[i_mod_2_inv*numyz + (j+1)*numz + k]; 396 | } 397 | if(edges & 0x004) 398 | { 399 | if(k == 0) 400 | { 401 | indices[2] = vertices.size() / 3; 402 | shared_indices_x[i_mod_2_inv*numyz + (j+1)*numz + k] = indices[2]; 403 | mc_add_vertex(x_dx, y_dy, z, x, 0, v[2], v[3], isovalue, &vertices); 404 | } 405 | else 406 | indices[2] = shared_indices_x[i_mod_2_inv*numyz + (j+1)*numz + k]; 407 | } 408 | if(edges & 0x008) 409 | { 410 | if(i == 0 && k == 0) 411 | { 412 | indices[3] = vertices.size() / 3; 413 | mc_add_vertex(x, y_dy, z, y, 1, v[3], v[0], isovalue, &vertices); 414 | } 415 | else 416 | indices[3] = shared_indices_y[i_mod_2*numyz + (j+1)*numz + k]; 417 | } 418 | if(edges & 0x010) 419 | { 420 | if(j == 0) 421 | { 422 | indices[4] = vertices.size() / 3; 423 | shared_indices_x[i_mod_2_inv*numyz + j*numz + (k+1)] = indices[4]; 424 | mc_add_vertex(x, y, z_dz, x_dx, 0, v[4], v[5], isovalue, &vertices); 425 | } 426 | else 427 | indices[4] = shared_indices_x[i_mod_2_inv*numyz + j*numz + (k+1)]; 428 | } 429 | if(edges & 0x080) 430 | { 431 | if(i == 0) 432 | { 433 | indices[7] = vertices.size() / 3; 434 | shared_indices_y[i_mod_2*numyz + (j+1)*numz + (k+1)] = indices[7]; 435 | mc_add_vertex(x, y_dy, z_dz, y, 1, v[7], v[4], isovalue, &vertices); 436 | } 437 | else 438 | indices[7] = shared_indices_y[i_mod_2*numyz + (j+1)*numz + (k+1)]; 439 | } 440 | if(edges & 0x100) 441 | { 442 | if(i == 0 && j == 0) 443 | { 444 | indices[8] = vertices.size() / 3; 445 | mc_add_vertex(x, y, z, z_dz, 2, v[0], v[4], isovalue, &vertices); 446 | } 447 | else 448 | indices[8] = shared_indices_z[i_mod_2*numyz + j*numz + (k+1)]; 449 | } 450 | if(edges & 0x200) 451 | { 452 | if(j == 0) 453 | { 454 | indices[9] = vertices.size() / 3; 455 | shared_indices_z[i_mod_2_inv*numyz + j*numz + (k+1)] = indices[9]; 456 | mc_add_vertex(x_dx, y, z, z_dz, 2, v[1], v[5], isovalue, &vertices); 457 | } 458 | else 459 | indices[9] = shared_indices_z[i_mod_2_inv*numyz + j*numz + (k+1)]; 460 | } 461 | if(edges & 0x800) 462 | { 463 | if(i == 0) 464 | { 465 | indices[11] = vertices.size() / 3; 466 | shared_indices_z[i_mod_2*numyz + (j+1)*numz + (k+1)] = indices[11]; 467 | mc_add_vertex(x, y_dy, z, z_dz, 2, v[3], v[7], isovalue, &vertices); 468 | } 469 | else 470 | indices[11] = shared_indices_z[i_mod_2*numyz + (j+1)*numz + (k+1)]; 471 | } 472 | 473 | int tri; 474 | int* triangle_table_ptr = triangle_table[cubeindex]; 475 | for(int m=0; tri = triangle_table_ptr[m], tri != -1; ++m) 476 | polygons.push_back(indices[tri]); 477 | 478 | 479 | /*if(triangle_table_ptr[0] == -1) continue; 480 | Eigen::Vector3d last(vertices[3*indices[triangle_table_ptr[0] ]+0], vertices[3*indices[triangle_table_ptr[0] ]+1], vertices[3*indices[triangle_table_ptr[0] ]+2]); 481 | for(int m=0; tri = triangle_table_ptr[m], tri != -1; ++m) 482 | { 483 | Eigen::Vector3d current(vertices[3*indices[tri]+0], vertices[3*indices[tri]+1], vertices[3*indices[tri]+2]); 484 | if ((current-last).norm() > std::sqrt(dx*dx + dy*dy + dz*dz)) 485 | { 486 | std::cout << "error: " << (current-last).norm() << std::endl; 487 | std::cout << "cubeindex: " << cubeindex << std::endl; 488 | std::cout << "i: " << i << std::endl; 489 | std::cout << "j: " << j << std::endl; 490 | std::cout << "k: " << k << std::endl; 491 | std::cout << "m: " << m << std::endl; 492 | } 493 | last = current; 494 | polygons.push_back(indices[tri]); 495 | }*/ 496 | } 497 | } 498 | } 499 | #endif 500 | 501 | } 502 | 503 | 504 | 505 | 506 | ///////////////////////////// 507 | ///////////////////////////// 508 | ////////// COLOR ////////// 509 | ///////////////////////////// 510 | ///////////////////////////// 511 | 512 | 513 | 514 | template 515 | void marching_cubes_color(const vector3& lower, const vector3& upper, 516 | int numx, int numy, int numz, formula_sdf f_sdf, formula_color f_color, double isovalue, 517 | std::vector& vertices, std::vector& polygons) 518 | { 519 | using coord_type = typename vector3::value_type; 520 | using size_type = typename vector3::size_type; 521 | using namespace private_; 522 | 523 | // Some initial checks 524 | if(numx < 2 || numy < 2 || numz < 2) 525 | return; 526 | 527 | if(!std::equal(std::begin(lower), std::end(lower), std::begin(upper), 528 | [](double a, double b)->bool {return a <= b;})) 529 | return; 530 | 531 | 532 | double dx = (upper[0] - lower[0]) / (numx - 1.0); 533 | double dy = (upper[1] - lower[1]) / (numy - 1.0); 534 | double dz = (upper[2] - lower[2]) / (numz - 1.0); 535 | 536 | auto coord_mapper = [&](int x, int y, int z) { return Eigen::Vector3d( lower[0] + x * dx, lower[1] + y * dy, lower[2] + z * dz ); }; 537 | //auto push_vertex = [&] (Eigen::Vector3d xyz) {int id = vertices.size()/3; vertices.push_back(xyz.x()); vertices.push_back(xyz.y()); vertices.push_back(xyz.z()); return id;}; 538 | auto push_vertex = [&] (const Vector6d& xyz_rgb) 539 | { 540 | int id = vertices.size()/6; 541 | for(unsigned int i=0; i<6; ++i) vertices.push_back(xyz_rgb[i]); 542 | return id; 543 | }; 544 | 545 | // vertex zero crossing interpolation 546 | // f(p2) = valp2 547 | // x 548 | // / 549 | // x f(p) = isolevel 550 | // / 551 | // / 552 | // / 553 | // x 554 | // f(p1) = valp1 555 | auto VertexInterp = [&](double isolevel, const Vector6d& p1, const Vector6d& p2, double valp1, double valp2) -> Vector6d 556 | { 557 | double alpha = (valp2 - isolevel) / (valp2 - valp1); 558 | return alpha*p1 + (1 - alpha)*p2; 559 | }; 560 | 561 | // store intersections of old z plane to avoid duplicated vertices 562 | int* edge_intersections_old_x = new int[(numx-1) * numy]; 563 | int* edge_intersections_old_y = new int[numx * (numy-1)]; 564 | int* edge_intersections_current_x = new int[(numx-1) * numy]; 565 | int* edge_intersections_current_y = new int[numx * (numy-1)]; 566 | 567 | // store intersections within the z-planes to avoid duplicated vertices 568 | int* edge_intersections_current_z = new int[numx * numy]; 569 | 570 | for (int z = 0; z < numz - 1; z++) 571 | { 572 | // swap index storage 573 | std::swap(edge_intersections_old_x, edge_intersections_current_x); // old = current 574 | std::swap(edge_intersections_old_y, edge_intersections_current_y); 575 | std::fill_n(edge_intersections_current_x, (numx-1) * numy, -1); // invalidate 576 | std::fill_n(edge_intersections_current_y, (numy-1) * numx, -1); // invalidate 577 | 578 | std::fill_n(edge_intersections_current_z, numy * numx, -1); // invalidate 579 | 580 | for (int y = 0; y < numy - 1; y++) 581 | { 582 | for (int x = 0; x < numx - 1; x++) 583 | { 584 | // Process Volume Cell 585 | MC_Gridcell_Color cell; 586 | // 587 | // 4---5 588 | // / /| 589 | // 0---1 6 590 | // | |/ 591 | // 3---2 592 | // cell corners 593 | cell.p[0].block<3,1>(0,0) = coord_mapper(x + 1, y, z); 594 | cell.p[1].block<3,1>(0,0) = coord_mapper(x, y, z); 595 | cell.p[2].block<3,1>(0,0) = coord_mapper(x, y + 1, z); 596 | cell.p[3].block<3,1>(0,0) = coord_mapper(x + 1, y + 1, z); 597 | cell.p[4].block<3,1>(0,0) = coord_mapper(x + 1, y, z + 1); 598 | cell.p[5].block<3,1>(0,0) = coord_mapper(x, y, z + 1); 599 | cell.p[6].block<3,1>(0,0) = coord_mapper(x, y + 1, z + 1); 600 | cell.p[7].block<3,1>(0,0) = coord_mapper(x + 1, y + 1, z + 1); 601 | 602 | // cell colors 603 | cell.p[0].block<3,1>(3,0) = f_color(x + 1, y, z); 604 | cell.p[1].block<3,1>(3,0) = f_color(x, y, z); 605 | cell.p[2].block<3,1>(3,0) = f_color(x, y + 1, z); 606 | cell.p[3].block<3,1>(3,0) = f_color(x + 1, y + 1, z); 607 | cell.p[4].block<3,1>(3,0) = f_color(x + 1, y, z + 1); 608 | cell.p[5].block<3,1>(3,0) = f_color(x, y, z + 1); 609 | cell.p[6].block<3,1>(3,0) = f_color(x, y + 1, z + 1); 610 | cell.p[7].block<3,1>(3,0) = f_color(x + 1, y + 1, z + 1); 611 | 612 | // cell corner values 613 | cell.val[0] = (double)f_sdf(x + 1, y, z); 614 | cell.val[1] = (double)f_sdf(x, y, z); 615 | cell.val[2] = (double)f_sdf(x, y + 1, z); 616 | cell.val[3] = (double)f_sdf(x + 1, y + 1, z); 617 | cell.val[4] = (double)f_sdf(x + 1, y, z + 1); 618 | cell.val[5] = (double)f_sdf(x, y, z + 1); 619 | cell.val[6] = (double)f_sdf(x, y + 1, z + 1); 620 | cell.val[7] = (double)f_sdf(x + 1, y + 1, z + 1); 621 | 622 | // triangulation code 623 | int cubeindex = 0; 624 | if (cell.val[0] < isovalue) cubeindex |= 1; 625 | if (cell.val[1] < isovalue) cubeindex |= 2; 626 | if (cell.val[2] < isovalue) cubeindex |= 4; 627 | if (cell.val[3] < isovalue) cubeindex |= 8; 628 | if (cell.val[4] < isovalue) cubeindex |= 16; 629 | if (cell.val[5] < isovalue) cubeindex |= 32; 630 | if (cell.val[6] < isovalue) cubeindex |= 64; 631 | if (cell.val[7] < isovalue) cubeindex |= 128; 632 | 633 | // Cube is entirely in/out of the surface 634 | if (edge_table[cubeindex] == 0) continue; 635 | 636 | 637 | /* Find the vertices where the surface intersects the cube */ 638 | int vertlist[12]; 639 | { // edges on the old z plane 640 | if (edge_table[cubeindex] & 1) // edge in x at y 641 | { 642 | if(z==0) vertlist[0] = push_vertex(VertexInterp(isovalue, cell.p[0], cell.p[1], cell.val[0], cell.val[1])); 643 | else vertlist[0] = edge_intersections_old_x[y * (numx-1) + x]; 644 | } 645 | if (edge_table[cubeindex] & 2) // edge in y at x 646 | { 647 | if(z==0) vertlist[1] = push_vertex(VertexInterp(isovalue, cell.p[1], cell.p[2], cell.val[1], cell.val[2])); 648 | else vertlist[1] = edge_intersections_old_y[x * (numy-1) + y]; 649 | } 650 | if (edge_table[cubeindex] & 4) // edge in x at y+1 651 | { 652 | if(z==0) vertlist[2] = push_vertex(VertexInterp(isovalue, cell.p[2], cell.p[3], cell.val[2], cell.val[3])); 653 | else vertlist[2] = edge_intersections_old_x[(y+1) * (numx-1) + x]; 654 | } 655 | if (edge_table[cubeindex] & 8) // edge in y at x+1 656 | { 657 | if(z==0) vertlist[3] = push_vertex(VertexInterp(isovalue, cell.p[3], cell.p[0], cell.val[3], cell.val[0])); 658 | else vertlist[3] = edge_intersections_old_y[(x+1) * (numy-1) + y]; 659 | } 660 | } 661 | 662 | { // edges on the new z plane 663 | if (edge_table[cubeindex] & 16) // edge in x at y 664 | { 665 | if (edge_intersections_current_x[y * (numx-1) + x] == -1) // check if already assigned 666 | { 667 | vertlist[4] = push_vertex(VertexInterp(isovalue, cell.p[4], cell.p[5], cell.val[4], cell.val[5])); 668 | edge_intersections_current_x[y * (numx-1) + x] = vertlist[4]; 669 | } 670 | else 671 | { 672 | vertlist[4] = edge_intersections_current_x[y * (numx-1) + x]; 673 | } 674 | } 675 | if (edge_table[cubeindex] & 32) // edge in y at x 676 | { 677 | if(edge_intersections_current_y[x * (numy-1) + y] == -1) 678 | { 679 | vertlist[5] = push_vertex(VertexInterp(isovalue, cell.p[5], cell.p[6], cell.val[5], cell.val[6])); 680 | edge_intersections_current_y[x * (numy-1) + y] = vertlist[5]; 681 | } 682 | else 683 | { 684 | vertlist[5] = edge_intersections_current_y[x * (numy-1) + y]; 685 | } 686 | } 687 | if (edge_table[cubeindex] & 64) // edge in x at y+1 688 | { 689 | if (edge_intersections_current_x[(y+1) * (numx-1) + x] == -1) 690 | { 691 | vertlist[6] = push_vertex(VertexInterp(isovalue, cell.p[6], cell.p[7], cell.val[6], cell.val[7])); 692 | edge_intersections_current_x[(y+1) * (numx-1) + x] = vertlist[6]; 693 | } 694 | else 695 | { 696 | vertlist[6] = edge_intersections_current_x[(y+1) * (numx-1) + x]; 697 | } 698 | } 699 | if (edge_table[cubeindex] & 128) // edge in y at x+1 700 | { 701 | if (edge_intersections_current_y[(x+1) * (numy-1) + y] == -1) 702 | { 703 | vertlist[7] = push_vertex(VertexInterp(isovalue, cell.p[7], cell.p[4], cell.val[7], cell.val[4])); 704 | edge_intersections_current_y[(x+1) * (numy-1) + y] = vertlist[7]; 705 | } 706 | else 707 | { 708 | vertlist[7] = edge_intersections_current_y[(x+1) * (numy-1) + y]; 709 | } 710 | } 711 | } 712 | 713 | { // between the z planes 714 | if (edge_table[cubeindex] & 256) // 0 -- 4, x + 1, y 715 | { 716 | if (edge_intersections_current_z[y * numx + (x+1)] == -1) 717 | { 718 | vertlist[8] = push_vertex(VertexInterp(isovalue, cell.p[0], cell.p[4], cell.val[0], cell.val[4])); 719 | edge_intersections_current_z[y * numx + (x+1)] = vertlist[8]; 720 | } 721 | else 722 | { 723 | vertlist[8] = edge_intersections_current_z[y * numx + (x+1)]; 724 | } 725 | } 726 | if (edge_table[cubeindex] & 512) // 1 -- 5, x, y 727 | { 728 | if (edge_intersections_current_z[y * numx + x] == -1) 729 | { 730 | vertlist[9] = push_vertex(VertexInterp(isovalue, cell.p[1], cell.p[5], cell.val[1], cell.val[5])); 731 | edge_intersections_current_z[y * numx + x] = vertlist[9]; 732 | } 733 | else 734 | { 735 | vertlist[9] = edge_intersections_current_z[y * numx + x]; 736 | } 737 | } 738 | if (edge_table[cubeindex] & 1024) // 2 -- 6, x, y + 1 739 | { 740 | if (edge_intersections_current_z[(y+1) * numx + x] == -1) 741 | { 742 | vertlist[10] = push_vertex(VertexInterp(isovalue, cell.p[2], cell.p[6], cell.val[2], cell.val[6])); 743 | edge_intersections_current_z[(y+1) * numx + x] = vertlist[10]; 744 | } 745 | else 746 | { 747 | vertlist[10] = edge_intersections_current_z[(y+1) * numx + x]; 748 | } 749 | } 750 | if (edge_table[cubeindex] & 2048) // 3 -- 7, x + 1, y + 1 751 | { 752 | if (edge_intersections_current_z[(y+1) * numx + (x+1)] == -1) 753 | { 754 | vertlist[11] = push_vertex(VertexInterp(isovalue, cell.p[3], cell.p[7], cell.val[3], cell.val[7])); 755 | edge_intersections_current_z[(y+1) * numx + (x+1)] = vertlist[11]; 756 | } 757 | else 758 | { 759 | vertlist[11] = edge_intersections_current_z[(y+1) * numx + (x+1)]; 760 | } 761 | } 762 | } 763 | 764 | // push face indices 765 | for (int i = 0; triangle_table[cubeindex][i] != -1; ++i) 766 | polygons.push_back(vertlist[triangle_table[cubeindex][i]]); 767 | 768 | } 769 | } 770 | } 771 | 772 | 773 | delete[] edge_intersections_old_x; 774 | delete[] edge_intersections_old_y; 775 | delete[] edge_intersections_current_x; 776 | delete[] edge_intersections_current_y; 777 | delete[] edge_intersections_current_z; 778 | 779 | } 780 | 781 | 782 | 783 | 784 | ////////////////////////////////////// 785 | ////////////////////////////////////// 786 | ////////// SUPER SAMPLING ////////// 787 | ////////////////////////////////////// 788 | ////////////////////////////////////// 789 | 790 | 791 | template 792 | void marching_cubes_super_sampling(const vector3& lower, const vector3& upper, 793 | int numx, int numy, int numz, 794 | int superx, int supery, int superz, 795 | formula f, 796 | formulaX f_superX, formulaY f_superY, formulaZ f_superZ, 797 | double isovalue, 798 | std::vector& vertices, std::vector& polygons) 799 | { 800 | using coord_type = typename vector3::value_type; 801 | using size_type = typename vector3::size_type; 802 | using namespace private_; 803 | 804 | // Some initial checks 805 | if(numx < 2 || numy < 2 || numz < 2) 806 | return; 807 | 808 | if(!std::equal(std::begin(lower), std::end(lower), std::begin(upper), 809 | [](double a, double b)->bool {return a <= b;})) 810 | return; 811 | 812 | 813 | double dx = (upper[0] - lower[0]) / (numx - 1.0); 814 | double dy = (upper[1] - lower[1]) / (numy - 1.0); 815 | double dz = (upper[2] - lower[2]) / (numz - 1.0); 816 | 817 | 818 | 819 | double scale_x = (numx - 1.0) / ( numx + (numx - 1) * superx - 1.0 ); // map super sampling coordinates back to actual sampling coordinates 820 | double scale_y = (numy - 1.0) / ( numy + (numy - 1) * supery - 1.0 ); 821 | double scale_z = (numz - 1.0) / ( numz + (numz - 1) * superz - 1.0 ); 822 | 823 | //auto coord_mapper = [&](int x, int y, int z) { return Eigen::Vector3d( lower[0] + x * dx, lower[1] + y * dy, lower[2] + z * dz ); }; 824 | auto coord_mapper = [&](int x, int y, int z) { return Eigen::Vector3d( x,y,z ); }; 825 | auto push_vertex = [&] (Eigen::Vector3d xyz) {int id = vertices.size()/3; vertices.push_back(xyz.x()); vertices.push_back(xyz.y()); vertices.push_back(xyz.z()); return id;}; 826 | 827 | auto sign = [](double x) { return x > 0.0; }; 828 | 829 | // vertex zero crossing interpolation 830 | // f(p2) = valp2 831 | // x 832 | // / 833 | // x f(p) = isolevel 834 | // / 835 | // / 836 | // / 837 | // x 838 | // f(p1) = valp1 839 | auto VertexInterp = [&](double isolevel, const Eigen::Vector3d& p1, const Eigen::Vector3d& p2, double valp1, double valp2) -> Eigen::Vector3d 840 | { 841 | bool edge_in_x_dir = p1.x() != p2.x() && p1.y() == p2.y() && p1.z() == p2.z(); 842 | bool edge_in_y_dir = p1.x() == p2.x() && p1.y() != p2.y() && p1.z() == p2.z(); 843 | bool edge_in_z_dir = p1.x() == p2.x() && p1.y() == p2.y() && p1.z() != p2.z(); 844 | //if (edge_in_x_dir == true && edge_in_y_dir==true) std::cout << "ERROR XY" << std::endl; 845 | //if (edge_in_x_dir == true && edge_in_z_dir==true) std::cout << "ERROR XZ" << std::endl; 846 | //if (edge_in_y_dir == true && edge_in_z_dir==true) std::cout << "ERROR YZ" << std::endl; 847 | //if (edge_in_x_dir == false && edge_in_y_dir == false && edge_in_z_dir==false) std::cout << "ERROR XYZ=FALSE" << std::endl; 848 | Eigen::Vector3d p1_ss = p1; 849 | Eigen::Vector3d p2_ss = p2; 850 | double valp1_ss = valp1; 851 | double valp2_ss = valp2; 852 | 853 | //if (edge_in_x_dir) std::cout << "edge_in_x_dir" << std::endl; 854 | //if (edge_in_y_dir) std::cout << "edge_in_y_dir" << std::endl; 855 | //if (edge_in_z_dir) std::cout << "edge_in_z_dir" << std::endl; 856 | 857 | //if (fabs(valp1-isolevel)!=0.0 && fabs(valp2-isolevel)==0.0) 858 | { 859 | if (edge_in_x_dir) 860 | { 861 | // [ 0.0, 0.0, 0.0, 1.0, 1.0 ] -->valp1 = 0.0, valp2=1.0 --> val_prev=0.0 862 | // [ 1.0, 1.0, 0.0, 0.0, 0.0 ] -->valp1 = 1.0, valp2=0.0 --> val_prev=1.0 863 | int min_x = std::min(p1.x(), p2.x()); 864 | double val_prev = valp1; 865 | if(p2.x() == min_x) val_prev=valp2; 866 | int y = p1.y(); 867 | int z = p1.z(); 868 | for(int i=1; i<=superx+1; ++i) // could do interval halfing / binary search 869 | { 870 | // find isolevel point 871 | int x = min_x*(superx+1) + i; 872 | double val = (double)f_superX(x, y, z); 873 | if (sign(val-isolevel) != sign(val_prev-isolevel) || (fabs(val-isolevel)==0.0 && fabs(val_prev-isolevel)!=0.0 )) // zero crossing 874 | { 875 | valp1_ss = val_prev; 876 | valp2_ss = val; 877 | p1_ss.x() = (x - 1) * scale_x; 878 | p2_ss.x() = x * scale_x; 879 | break; 880 | } 881 | val_prev = val; 882 | } 883 | } 884 | 885 | if (edge_in_y_dir) 886 | { 887 | int min_y = std::min(p1.y(), p2.y()); 888 | double val_prev = valp1; 889 | if(p2.y() == min_y) val_prev=valp2; 890 | int x = p1.x(); 891 | int z = p1.z(); 892 | for(int i=1; i<=supery+1; ++i) // could do interval halfing / binary search 893 | { 894 | // find isolevel point 895 | int y = min_y*(supery+1) + i; 896 | double val = (double)f_superY(x, y, z); 897 | if (sign(val-isolevel) != sign(val_prev-isolevel) || (fabs(val-isolevel)==0.0 && fabs(val_prev-isolevel)!=0.0 )) // zero crossing 898 | { 899 | valp1_ss = val_prev; 900 | valp2_ss = val; 901 | p1_ss.y() = (y - 1) * scale_y; 902 | p2_ss.y() = y * scale_y; 903 | break; 904 | } 905 | val_prev = val; 906 | } 907 | } 908 | 909 | if (edge_in_z_dir) 910 | { 911 | int min_z = std::min(p1.z(), p2.z()); 912 | double val_prev = valp1; 913 | if(p2.z() == min_z) val_prev=valp2; 914 | int x = p1.x(); 915 | int y = p1.y(); 916 | for(int i=1; i<=superz+1; ++i) // could do interval halfing / binary search 917 | { 918 | // find isolevel point 919 | int z = min_z*(superz+1) + i; 920 | double val = (double)f_superZ(x, y, z); 921 | if (sign(val-isolevel) != sign(val_prev-isolevel) || (fabs(val-isolevel)==0.0 && fabs(val_prev-isolevel)!=0.0 )) // zero crossing 922 | { 923 | valp1_ss = val_prev; 924 | valp2_ss = val; 925 | p1_ss.z() = (z - 1) * scale_z; 926 | p2_ss.z() = z * scale_z; 927 | break; 928 | } 929 | val_prev = val; 930 | } 931 | } 932 | } 933 | 934 | // coord mapper 935 | p1_ss = Eigen::Vector3d( lower[0] + p1_ss.x() * dx, lower[1] + p1_ss.y() * dy, lower[2] + p1_ss.z() * dz); 936 | p2_ss = Eigen::Vector3d( lower[0] + p2_ss.x() * dx, lower[1] + p2_ss.y() * dy, lower[2] + p2_ss.z() * dz); 937 | 938 | // actual interpolation 939 | double alpha = (valp2_ss - isolevel) / (valp2_ss - valp1_ss); 940 | if (valp2_ss - valp1_ss == 0.0) 941 | { 942 | alpha = 0.5; 943 | } 944 | return alpha*p1_ss + (1 - alpha)*p2_ss; 945 | 946 | }; 947 | 948 | // store intersections of old z plane to avoid duplicated vertices 949 | int* edge_intersections_old_x = new int[(numx-1) * numy]; 950 | int* edge_intersections_old_y = new int[numx * (numy-1)]; 951 | int* edge_intersections_current_x = new int[(numx-1) * numy]; 952 | int* edge_intersections_current_y = new int[numx * (numy-1)]; 953 | 954 | // store intersections within the z-planes to avoid duplicated vertices 955 | int* edge_intersections_current_z = new int[numx * numy]; 956 | 957 | for (int z = 0; z < numz - 1; z++) 958 | { 959 | // swap index storage 960 | std::swap(edge_intersections_old_x, edge_intersections_current_x); // old = current 961 | std::swap(edge_intersections_old_y, edge_intersections_current_y); 962 | std::fill_n(edge_intersections_current_x, (numx-1) * numy, -1); // invalidate 963 | std::fill_n(edge_intersections_current_y, (numy-1) * numx, -1); // invalidate 964 | 965 | std::fill_n(edge_intersections_current_z, numy * numx, -1); // invalidate 966 | 967 | for (int y = 0; y < numy - 1; y++) 968 | { 969 | for (int x = 0; x < numx - 1; x++) 970 | { 971 | // Process Volume Cell 972 | MC_Gridcell cell; 973 | // 974 | // 4---5 975 | // / /| 976 | // 0---1 6 977 | // | |/ 978 | // 3---2 979 | // cell corners 980 | cell.p[0] = coord_mapper(x + 1, y, z); 981 | cell.p[1] = coord_mapper(x, y, z); 982 | cell.p[2] = coord_mapper(x, y + 1, z); 983 | cell.p[3] = coord_mapper(x + 1, y + 1, z); 984 | cell.p[4] = coord_mapper(x + 1, y, z + 1); 985 | cell.p[5] = coord_mapper(x, y, z + 1); 986 | cell.p[6] = coord_mapper(x, y + 1, z + 1); 987 | cell.p[7] = coord_mapper(x + 1, y + 1, z + 1); 988 | 989 | // cell corner values 990 | cell.val[0] = (double)f(x + 1, y, z); 991 | cell.val[1] = (double)f(x, y, z); 992 | cell.val[2] = (double)f(x, y + 1, z); 993 | cell.val[3] = (double)f(x + 1, y + 1, z); 994 | cell.val[4] = (double)f(x + 1, y, z + 1); 995 | cell.val[5] = (double)f(x, y, z + 1); 996 | cell.val[6] = (double)f(x, y + 1, z + 1); 997 | cell.val[7] = (double)f(x + 1, y + 1, z + 1); 998 | 999 | // triangulation code 1000 | int cubeindex = 0; 1001 | if (cell.val[0] < isovalue) cubeindex |= 1; 1002 | if (cell.val[1] < isovalue) cubeindex |= 2; 1003 | if (cell.val[2] < isovalue) cubeindex |= 4; 1004 | if (cell.val[3] < isovalue) cubeindex |= 8; 1005 | if (cell.val[4] < isovalue) cubeindex |= 16; 1006 | if (cell.val[5] < isovalue) cubeindex |= 32; 1007 | if (cell.val[6] < isovalue) cubeindex |= 64; 1008 | if (cell.val[7] < isovalue) cubeindex |= 128; 1009 | 1010 | // Cube is entirely in/out of the surface 1011 | if (edge_table[cubeindex] == 0) continue; 1012 | 1013 | 1014 | /* Find the vertices where the surface intersects the cube */ 1015 | int vertlist[12]; 1016 | { // edges on the old z plane 1017 | if (edge_table[cubeindex] & 1) // edge in x at y 1018 | { 1019 | if(z==0) vertlist[0] = push_vertex(VertexInterp(isovalue, cell.p[0], cell.p[1], cell.val[0], cell.val[1])); 1020 | else vertlist[0] = edge_intersections_old_x[y * (numx-1) + x]; 1021 | } 1022 | if (edge_table[cubeindex] & 2) // edge in y at x 1023 | { 1024 | if(z==0) vertlist[1] = push_vertex(VertexInterp(isovalue, cell.p[1], cell.p[2], cell.val[1], cell.val[2])); 1025 | else vertlist[1] = edge_intersections_old_y[x * (numy-1) + y]; 1026 | } 1027 | if (edge_table[cubeindex] & 4) // edge in x at y+1 1028 | { 1029 | if(z==0) vertlist[2] = push_vertex(VertexInterp(isovalue, cell.p[2], cell.p[3], cell.val[2], cell.val[3])); 1030 | else vertlist[2] = edge_intersections_old_x[(y+1) * (numx-1) + x]; 1031 | } 1032 | if (edge_table[cubeindex] & 8) // edge in y at x+1 1033 | { 1034 | if(z==0) vertlist[3] = push_vertex(VertexInterp(isovalue, cell.p[3], cell.p[0], cell.val[3], cell.val[0])); 1035 | else vertlist[3] = edge_intersections_old_y[(x+1) * (numy-1) + y]; 1036 | } 1037 | } 1038 | 1039 | { // edges on the new z plane 1040 | if (edge_table[cubeindex] & 16) // edge in x at y 1041 | { 1042 | if (edge_intersections_current_x[y * (numx-1) + x] == -1) // check if already assigned 1043 | { 1044 | vertlist[4] = push_vertex(VertexInterp(isovalue, cell.p[4], cell.p[5], cell.val[4], cell.val[5])); 1045 | edge_intersections_current_x[y * (numx-1) + x] = vertlist[4]; 1046 | } 1047 | else 1048 | { 1049 | vertlist[4] = edge_intersections_current_x[y * (numx-1) + x]; 1050 | } 1051 | } 1052 | if (edge_table[cubeindex] & 32) // edge in y at x 1053 | { 1054 | if(edge_intersections_current_y[x * (numy-1) + y] == -1) 1055 | { 1056 | vertlist[5] = push_vertex(VertexInterp(isovalue, cell.p[5], cell.p[6], cell.val[5], cell.val[6])); 1057 | edge_intersections_current_y[x * (numy-1) + y] = vertlist[5]; 1058 | } 1059 | else 1060 | { 1061 | vertlist[5] = edge_intersections_current_y[x * (numy-1) + y]; 1062 | } 1063 | } 1064 | if (edge_table[cubeindex] & 64) // edge in x at y+1 1065 | { 1066 | if (edge_intersections_current_x[(y+1) * (numx-1) + x] == -1) 1067 | { 1068 | vertlist[6] = push_vertex(VertexInterp(isovalue, cell.p[6], cell.p[7], cell.val[6], cell.val[7])); 1069 | edge_intersections_current_x[(y+1) * (numx-1) + x] = vertlist[6]; 1070 | } 1071 | else 1072 | { 1073 | vertlist[6] = edge_intersections_current_x[(y+1) * (numx-1) + x]; 1074 | } 1075 | } 1076 | if (edge_table[cubeindex] & 128) // edge in y at x+1 1077 | { 1078 | if (edge_intersections_current_y[(x+1) * (numy-1) + y] == -1) 1079 | { 1080 | vertlist[7] = push_vertex(VertexInterp(isovalue, cell.p[7], cell.p[4], cell.val[7], cell.val[4])); 1081 | edge_intersections_current_y[(x+1) * (numy-1) + y] = vertlist[7]; 1082 | } 1083 | else 1084 | { 1085 | vertlist[7] = edge_intersections_current_y[(x+1) * (numy-1) + y]; 1086 | } 1087 | } 1088 | } 1089 | 1090 | { // between the z planes 1091 | if (edge_table[cubeindex] & 256) // 0 -- 4, x + 1, y 1092 | { 1093 | if (edge_intersections_current_z[y * numx + (x+1)] == -1) 1094 | { 1095 | vertlist[8] = push_vertex(VertexInterp(isovalue, cell.p[0], cell.p[4], cell.val[0], cell.val[4])); 1096 | edge_intersections_current_z[y * numx + (x+1)] = vertlist[8]; 1097 | } 1098 | else 1099 | { 1100 | vertlist[8] = edge_intersections_current_z[y * numx + (x+1)]; 1101 | } 1102 | } 1103 | if (edge_table[cubeindex] & 512) // 1 -- 5, x, y 1104 | { 1105 | if (edge_intersections_current_z[y * numx + x] == -1) 1106 | { 1107 | vertlist[9] = push_vertex(VertexInterp(isovalue, cell.p[1], cell.p[5], cell.val[1], cell.val[5])); 1108 | edge_intersections_current_z[y * numx + x] = vertlist[9]; 1109 | } 1110 | else 1111 | { 1112 | vertlist[9] = edge_intersections_current_z[y * numx + x]; 1113 | } 1114 | } 1115 | if (edge_table[cubeindex] & 1024) // 2 -- 6, x, y + 1 1116 | { 1117 | if (edge_intersections_current_z[(y+1) * numx + x] == -1) 1118 | { 1119 | vertlist[10] = push_vertex(VertexInterp(isovalue, cell.p[2], cell.p[6], cell.val[2], cell.val[6])); 1120 | edge_intersections_current_z[(y+1) * numx + x] = vertlist[10]; 1121 | } 1122 | else 1123 | { 1124 | vertlist[10] = edge_intersections_current_z[(y+1) * numx + x]; 1125 | } 1126 | } 1127 | if (edge_table[cubeindex] & 2048) // 3 -- 7, x + 1, y + 1 1128 | { 1129 | if (edge_intersections_current_z[(y+1) * numx + (x+1)] == -1) 1130 | { 1131 | vertlist[11] = push_vertex(VertexInterp(isovalue, cell.p[3], cell.p[7], cell.val[3], cell.val[7])); 1132 | edge_intersections_current_z[(y+1) * numx + (x+1)] = vertlist[11]; 1133 | } 1134 | else 1135 | { 1136 | vertlist[11] = edge_intersections_current_z[(y+1) * numx + (x+1)]; 1137 | } 1138 | } 1139 | } 1140 | 1141 | // push face indices 1142 | for (int i = 0; triangle_table[cubeindex][i] != -1; ++i) 1143 | polygons.push_back(vertlist[triangle_table[cubeindex][i]]); 1144 | 1145 | } 1146 | } 1147 | } 1148 | 1149 | 1150 | delete[] edge_intersections_old_x; 1151 | delete[] edge_intersections_old_y; 1152 | delete[] edge_intersections_current_x; 1153 | delete[] edge_intersections_current_y; 1154 | delete[] edge_intersections_current_z; 1155 | } 1156 | 1157 | 1158 | 1159 | 1160 | 1161 | } 1162 | 1163 | #endif // _MARCHING_CUBES_H 1164 | -------------------------------------------------------------------------------- /marching_cubes/src/pyarray_symbol.h: -------------------------------------------------------------------------------- 1 | 2 | #define PY_ARRAY_UNIQUE_SYMBOL mcubes_PyArray_API 3 | -------------------------------------------------------------------------------- /marching_cubes/src/pyarraymodule.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _EXTMODULE_H 3 | #define _EXTMODULE_H 4 | 5 | #include 6 | #include 7 | 8 | // #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION 9 | #define PY_ARRAY_UNIQUE_SYMBOL mcubes_PyArray_API 10 | #define NO_IMPORT_ARRAY 11 | #include "numpy/arrayobject.h" 12 | 13 | #include 14 | 15 | template 16 | struct numpy_typemap; 17 | 18 | #define define_numpy_type(ctype, dtype) \ 19 | template<> \ 20 | struct numpy_typemap \ 21 | {static const int type = dtype;}; 22 | 23 | define_numpy_type(bool, NPY_BOOL); 24 | define_numpy_type(char, NPY_BYTE); 25 | define_numpy_type(short, NPY_SHORT); 26 | define_numpy_type(int, NPY_INT); 27 | define_numpy_type(long, NPY_LONG); 28 | define_numpy_type(long long, NPY_LONGLONG); 29 | define_numpy_type(unsigned char, NPY_UBYTE); 30 | define_numpy_type(unsigned short, NPY_USHORT); 31 | define_numpy_type(unsigned int, NPY_UINT); 32 | define_numpy_type(unsigned long, NPY_ULONG); 33 | define_numpy_type(unsigned long long, NPY_ULONGLONG); 34 | define_numpy_type(float, NPY_FLOAT); 35 | define_numpy_type(double, NPY_DOUBLE); 36 | define_numpy_type(long double, NPY_LONGDOUBLE); 37 | define_numpy_type(std::complex, NPY_CFLOAT); 38 | define_numpy_type(std::complex, NPY_CDOUBLE); 39 | define_numpy_type(std::complex, NPY_CLONGDOUBLE); 40 | 41 | template 42 | T PyArray_SafeGet(const PyArrayObject* aobj, const npy_intp* indaux) 43 | { 44 | // HORROR. 45 | npy_intp* ind = const_cast(indaux); 46 | void* ptr = PyArray_GetPtr(const_cast(aobj), ind); 47 | switch(PyArray_TYPE(aobj)) 48 | { 49 | case NPY_BOOL: 50 | return static_cast(*reinterpret_cast(ptr)); 51 | case NPY_BYTE: 52 | return static_cast(*reinterpret_cast(ptr)); 53 | case NPY_SHORT: 54 | return static_cast(*reinterpret_cast(ptr)); 55 | case NPY_INT: 56 | return static_cast(*reinterpret_cast(ptr)); 57 | case NPY_LONG: 58 | return static_cast(*reinterpret_cast(ptr)); 59 | case NPY_LONGLONG: 60 | return static_cast(*reinterpret_cast(ptr)); 61 | case NPY_UBYTE: 62 | return static_cast(*reinterpret_cast(ptr)); 63 | case NPY_USHORT: 64 | return static_cast(*reinterpret_cast(ptr)); 65 | case NPY_UINT: 66 | return static_cast(*reinterpret_cast(ptr)); 67 | case NPY_ULONG: 68 | return static_cast(*reinterpret_cast(ptr)); 69 | case NPY_ULONGLONG: 70 | return static_cast(*reinterpret_cast(ptr)); 71 | case NPY_FLOAT: 72 | return static_cast(*reinterpret_cast(ptr)); 73 | case NPY_DOUBLE: 74 | return static_cast(*reinterpret_cast(ptr)); 75 | case NPY_LONGDOUBLE: 76 | return static_cast(*reinterpret_cast(ptr)); 77 | default: 78 | throw std::runtime_error("data type not supported"); 79 | } 80 | } 81 | 82 | template 83 | T PyArray_SafeSet(PyArrayObject* aobj, const npy_intp* indaux, const T& value) 84 | { 85 | // HORROR. 86 | npy_intp* ind = const_cast(indaux); 87 | void* ptr = PyArray_GetPtr(aobj, ind); 88 | switch(PyArray_TYPE(aobj)) 89 | { 90 | case NPY_BOOL: 91 | *reinterpret_cast(ptr) = static_cast(value); 92 | break; 93 | case NPY_BYTE: 94 | *reinterpret_cast(ptr) = static_cast(value); 95 | break; 96 | case NPY_SHORT: 97 | *reinterpret_cast(ptr) = static_cast(value); 98 | break; 99 | case NPY_INT: 100 | *reinterpret_cast(ptr) = static_cast(value); 101 | break; 102 | case NPY_LONG: 103 | *reinterpret_cast(ptr) = static_cast(value); 104 | break; 105 | case NPY_LONGLONG: 106 | *reinterpret_cast(ptr) = static_cast(value); 107 | break; 108 | case NPY_UBYTE: 109 | *reinterpret_cast(ptr) = static_cast(value); 110 | break; 111 | case NPY_USHORT: 112 | *reinterpret_cast(ptr) = static_cast(value); 113 | break; 114 | case NPY_UINT: 115 | *reinterpret_cast(ptr) = static_cast(value); 116 | break; 117 | case NPY_ULONG: 118 | *reinterpret_cast(ptr) = static_cast(value); 119 | break; 120 | case NPY_ULONGLONG: 121 | *reinterpret_cast(ptr) = static_cast(value); 122 | break; 123 | case NPY_FLOAT: 124 | *reinterpret_cast(ptr) = static_cast(value); 125 | break; 126 | case NPY_DOUBLE: 127 | *reinterpret_cast(ptr) = static_cast(value); 128 | break; 129 | case NPY_LONGDOUBLE: 130 | *reinterpret_cast(ptr) = static_cast(value); 131 | break; 132 | default: 133 | throw std::runtime_error("data type not supported"); 134 | } 135 | } 136 | 137 | #endif 138 | -------------------------------------------------------------------------------- /marching_cubes/src/pywrapper.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "pywrapper.h" 3 | 4 | #include "marchingcubes.h" 5 | 6 | #include 7 | #include 8 | 9 | 10 | PyObject* marching_cubes_func(PyObject* lower, PyObject* upper, 11 | int numx, int numy, int numz, PyObject* pyfunc, double isovalue) 12 | { 13 | std::vector vertices; 14 | std::vector polygons; 15 | 16 | // Copy the lower and upper coordinates to a C array. 17 | std::array lower_; 18 | std::array upper_; 19 | for(int i=0; i<3; ++i) 20 | { 21 | PyObject* l = PySequence_GetItem(lower, i); 22 | if(l == NULL) 23 | throw std::runtime_error("len(lower) < 3"); 24 | PyObject* u = PySequence_GetItem(upper, i); 25 | if(u == NULL) 26 | { 27 | Py_DECREF(l); 28 | throw std::runtime_error("len(upper) < 3"); 29 | } 30 | 31 | lower_[i] = PyFloat_AsDouble(l); 32 | upper_[i] = PyFloat_AsDouble(u); 33 | 34 | Py_DECREF(l); 35 | Py_DECREF(u); 36 | if(lower_[i]==-1.0 || upper_[i]==-1.0) 37 | { 38 | if(PyErr_Occurred()) 39 | throw std::runtime_error("unknown error"); 40 | } 41 | } 42 | 43 | auto pyfunc_to_cfunc = [&](double x, double y, double z) -> double { 44 | PyObject* res = PyObject_CallFunction(pyfunc, "(d,d,d)", x, y, z); 45 | if(res == NULL) 46 | return 0.0; 47 | 48 | double result = PyFloat_AsDouble(res); 49 | Py_DECREF(res); 50 | return result; 51 | }; 52 | 53 | // Marching cubes. 54 | mc::marching_cubes(lower_, upper_, numx, numy, numz, pyfunc_to_cfunc, isovalue, vertices, polygons); 55 | 56 | // Copy the result to two Python ndarrays. 57 | npy_intp size_vertices = vertices.size(); 58 | npy_intp size_polygons = polygons.size(); 59 | PyArrayObject* verticesarr = reinterpret_cast(PyArray_SimpleNew(1, &size_vertices, PyArray_DOUBLE)); 60 | PyArrayObject* polygonsarr = reinterpret_cast(PyArray_SimpleNew(1, &size_polygons, PyArray_ULONG)); 61 | 62 | std::vector::const_iterator it = vertices.begin(); 63 | for(int i=0; it!=vertices.end(); ++i, ++it) 64 | *reinterpret_cast(PyArray_GETPTR1(verticesarr, i)) = *it; 65 | std::vector::const_iterator it2 = polygons.begin(); 66 | for(int i=0; it2!=polygons.end(); ++i, ++it2) 67 | *reinterpret_cast(PyArray_GETPTR1(polygonsarr, i)) = *it2; 68 | 69 | PyObject* res = Py_BuildValue("(O,O)", verticesarr, polygonsarr); 70 | Py_XDECREF(verticesarr); 71 | Py_XDECREF(polygonsarr); 72 | return res; 73 | } 74 | 75 | 76 | PyObject* marching_cubes(PyArrayObject* arr, double isovalue) 77 | { 78 | if(PyArray_NDIM(arr) != 3) 79 | throw std::runtime_error("Only three-dimensional arrays are supported."); 80 | 81 | // Prepare data. 82 | npy_intp* shape = PyArray_DIMS(arr); 83 | std::array lower{0, 0, 0}; 84 | std::array upper{shape[0]-1, shape[1]-1, shape[2]-1}; 85 | long numx = upper[0] - lower[0] + 1; 86 | long numy = upper[1] - lower[1] + 1; 87 | long numz = upper[2] - lower[2] + 1; 88 | std::vector vertices; 89 | std::vector polygons; 90 | 91 | auto pyarray_to_cfunc = [&](long x, long y, long z) -> double { 92 | const npy_intp c[3] = {x, y, z}; 93 | return PyArray_SafeGet(arr, c); 94 | }; 95 | 96 | // Marching cubes. 97 | mc::marching_cubes(lower, upper, numx, numy, numz, pyarray_to_cfunc, isovalue, 98 | vertices, polygons); 99 | 100 | // Copy the result to two Python ndarrays. 101 | npy_intp size_vertices = vertices.size(); 102 | npy_intp size_polygons = polygons.size(); 103 | PyArrayObject* verticesarr = reinterpret_cast(PyArray_SimpleNew(1, &size_vertices, PyArray_DOUBLE)); 104 | PyArrayObject* polygonsarr = reinterpret_cast(PyArray_SimpleNew(1, &size_polygons, PyArray_ULONG)); 105 | 106 | std::vector::const_iterator it = vertices.begin(); 107 | for(int i=0; it!=vertices.end(); ++i, ++it) 108 | *reinterpret_cast(PyArray_GETPTR1(verticesarr, i)) = *it; 109 | std::vector::const_iterator it2 = polygons.begin(); 110 | for(int i=0; it2!=polygons.end(); ++i, ++it2) 111 | *reinterpret_cast(PyArray_GETPTR1(polygonsarr, i)) = *it2; 112 | 113 | PyObject* res = Py_BuildValue("(O,O)", verticesarr, polygonsarr); 114 | Py_XDECREF(verticesarr); 115 | Py_XDECREF(polygonsarr); 116 | 117 | return res; 118 | } 119 | 120 | 121 | 122 | 123 | 124 | 125 | PyObject* marching_cubes_color_func(PyObject* lower, PyObject* upper, 126 | int numx, int numy, int numz, PyObject* pyfunc_sdf, PyObject* pyfunc_r, PyObject* pyfunc_g, PyObject* pyfunc_b, double isovalue) 127 | { 128 | std::vector vertices; 129 | std::vector polygons; 130 | 131 | // Copy the lower and upper coordinates to a C array. 132 | std::array lower_; 133 | std::array upper_; 134 | for(int i=0; i<3; ++i) 135 | { 136 | PyObject* l = PySequence_GetItem(lower, i); 137 | if(l == NULL) 138 | throw std::runtime_error("len(lower) < 3"); 139 | PyObject* u = PySequence_GetItem(upper, i); 140 | if(u == NULL) 141 | { 142 | Py_DECREF(l); 143 | throw std::runtime_error("len(upper) < 3"); 144 | } 145 | 146 | lower_[i] = PyFloat_AsDouble(l); 147 | upper_[i] = PyFloat_AsDouble(u); 148 | 149 | Py_DECREF(l); 150 | Py_DECREF(u); 151 | if(lower_[i]==-1.0 || upper_[i]==-1.0) 152 | { 153 | if(PyErr_Occurred()) 154 | throw std::runtime_error("unknown error"); 155 | } 156 | } 157 | 158 | auto pyfunc_to_cfunc_sdf = [&](double x, double y, double z) -> double { 159 | PyObject* res = PyObject_CallFunction(pyfunc_sdf, "(d,d,d)", x, y, z); 160 | if(res == NULL) 161 | return 0.0; 162 | 163 | double result = PyFloat_AsDouble(res); 164 | Py_DECREF(res); 165 | return result; 166 | }; 167 | 168 | auto pyfunc_to_cfunc_color = [&](double x, double y, double z) -> Eigen::Vector3d { 169 | Eigen::Vector3d col; 170 | { // red 171 | PyObject* res = PyObject_CallFunction(pyfunc_r, "(d,d,d)", x, y, z); 172 | if(res == NULL) return Eigen::Vector3d::Zero(); 173 | col.x() = PyFloat_AsDouble(res); 174 | Py_DECREF(res); 175 | } 176 | { // green 177 | PyObject* res = PyObject_CallFunction(pyfunc_g, "(d,d,d)", x, y, z); 178 | if(res == NULL) return Eigen::Vector3d::Zero(); 179 | col.y() = PyFloat_AsDouble(res); 180 | Py_DECREF(res); 181 | } 182 | { // blue 183 | PyObject* res = PyObject_CallFunction(pyfunc_b, "(d,d,d)", x, y, z); 184 | if(res == NULL) return Eigen::Vector3d::Zero(); 185 | col.z() = PyFloat_AsDouble(res); 186 | Py_DECREF(res); 187 | } 188 | 189 | 190 | return col; 191 | }; 192 | 193 | 194 | // Marching cubes. 195 | mc::marching_cubes_color(lower_, upper_, numx, numy, numz, pyfunc_to_cfunc_sdf, pyfunc_to_cfunc_color, isovalue, vertices, polygons); 196 | 197 | // Copy the result to two Python ndarrays. 198 | npy_intp size_vertices = vertices.size(); 199 | npy_intp size_polygons = polygons.size(); 200 | PyArrayObject* verticesarr = reinterpret_cast(PyArray_SimpleNew(1, &size_vertices, PyArray_DOUBLE)); 201 | PyArrayObject* polygonsarr = reinterpret_cast(PyArray_SimpleNew(1, &size_polygons, PyArray_ULONG)); 202 | 203 | std::vector::const_iterator it = vertices.begin(); 204 | for(int i=0; it!=vertices.end(); ++i, ++it) 205 | *reinterpret_cast(PyArray_GETPTR1(verticesarr, i)) = *it; 206 | std::vector::const_iterator it2 = polygons.begin(); 207 | for(int i=0; it2!=polygons.end(); ++i, ++it2) 208 | *reinterpret_cast(PyArray_GETPTR1(polygonsarr, i)) = *it2; 209 | 210 | PyObject* res = Py_BuildValue("(O,O)", verticesarr, polygonsarr); 211 | Py_XDECREF(verticesarr); 212 | Py_XDECREF(polygonsarr); 213 | return res; 214 | } 215 | 216 | 217 | PyObject* marching_cubes_color(PyArrayObject* arr_sdf, PyArrayObject* arr_color, double isovalue) 218 | { 219 | if(PyArray_NDIM(arr_sdf) != 3) 220 | throw std::runtime_error("Only three-dimensional arrays are supported (SDF)."); 221 | if(PyArray_NDIM(arr_color) != 4) 222 | throw std::runtime_error("Only four-dimensional arrays are supported (RGB)."); 223 | 224 | // Prepare data. 225 | npy_intp* shape = PyArray_DIMS(arr_sdf); 226 | npy_intp* shape_color = PyArray_DIMS(arr_color); 227 | 228 | if(shape[0] != shape_color[0] || shape[1] != shape_color[1] || shape[2] != shape_color[2]) 229 | throw std::runtime_error("SDF and RGB volumes do not match in size."); 230 | 231 | if(shape_color[3] != 3) 232 | throw std::runtime_error("Only RGB colors are supported."); 233 | 234 | std::array lower{0, 0, 0}; 235 | std::array upper{shape[0]-1, shape[1]-1, shape[2]-1}; 236 | long numx = upper[0] - lower[0] + 1; 237 | long numy = upper[1] - lower[1] + 1; 238 | long numz = upper[2] - lower[2] + 1; 239 | std::vector vertices; 240 | std::vector polygons; 241 | 242 | auto pyarray_to_cfunc_sdf = [&](long x, long y, long z) -> double { 243 | const npy_intp c[3] = {x, y, z}; 244 | return PyArray_SafeGet(arr_sdf, c); 245 | }; 246 | 247 | auto pyarray_to_cfunc_color = [&](long x, long y, long z) -> Eigen::Vector3d { 248 | Eigen::Vector3d color; 249 | npy_intp c_r[4] = {x, y, z, 0}; 250 | npy_intp c_g[4] = {x, y, z, 1}; 251 | npy_intp c_b[4] = {x, y, z, 2}; 252 | color.x() = PyArray_SafeGet(arr_color, c_r); 253 | color.y() = PyArray_SafeGet(arr_color, c_g); 254 | color.z() = PyArray_SafeGet(arr_color, c_b); 255 | return color; 256 | }; 257 | 258 | // Marching cubes. 259 | mc::marching_cubes_color(lower, upper, numx, numy, numz, pyarray_to_cfunc_sdf, pyarray_to_cfunc_color, isovalue, vertices, polygons); 260 | 261 | // Copy the result to two Python ndarrays. 262 | npy_intp size_vertices = vertices.size(); 263 | npy_intp size_polygons = polygons.size(); 264 | PyArrayObject* verticesarr = reinterpret_cast(PyArray_SimpleNew(1, &size_vertices, PyArray_DOUBLE)); 265 | PyArrayObject* polygonsarr = reinterpret_cast(PyArray_SimpleNew(1, &size_polygons, PyArray_ULONG)); 266 | 267 | std::vector::const_iterator it = vertices.begin(); 268 | for(int i=0; it!=vertices.end(); ++i, ++it) 269 | *reinterpret_cast(PyArray_GETPTR1(verticesarr, i)) = *it; 270 | std::vector::const_iterator it2 = polygons.begin(); 271 | for(int i=0; it2!=polygons.end(); ++i, ++it2) 272 | *reinterpret_cast(PyArray_GETPTR1(polygonsarr, i)) = *it2; 273 | 274 | PyObject* res = Py_BuildValue("(O,O)", verticesarr, polygonsarr); 275 | Py_XDECREF(verticesarr); 276 | Py_XDECREF(polygonsarr); 277 | 278 | return res; 279 | } 280 | 281 | 282 | 283 | 284 | PyObject* marching_cubes_super_sampling(PyArrayObject* arrX, PyArrayObject* arrY, PyArrayObject* arrZ, double isovalue) 285 | { 286 | if(PyArray_NDIM(arrX) != 3) 287 | throw std::runtime_error("Only three-dimensional arrays are supported."); 288 | if(PyArray_NDIM(arrY) != 3) 289 | throw std::runtime_error("Only three-dimensional arrays are supported."); 290 | if(PyArray_NDIM(arrZ) != 3) 291 | throw std::runtime_error("Only three-dimensional arrays are supported."); 292 | 293 | // Prepare data. 294 | npy_intp* shapeX = PyArray_DIMS(arrX); 295 | npy_intp* shapeY = PyArray_DIMS(arrY); 296 | npy_intp* shapeZ = PyArray_DIMS(arrZ); 297 | 298 | npy_intp shape[3] = {shapeY[0], shapeX[1], shapeX[2]}; 299 | npy_intp supersamples[3] = { (shapeX[0]-shape[0]) / (shape[0]-1), // samples along the edge (between two voxel nodes) 300 | (shapeY[1]-shape[1]) / (shape[1]-1), 301 | (shapeZ[2]-shape[2]) / (shape[2]-1)}; 302 | 303 | if(shapeX[2] != shapeY[2] || shapeX[1] != shapeZ[1] || shapeY[0] != shapeZ[0]) 304 | throw std::runtime_error("X,Y,Z supersampled sdf arrays need to be compatible."); 305 | 306 | if( shapeX[0] != shape[0] + (shape[0]-1)*supersamples[0] 307 | || shapeY[1] != shape[1] + (shape[1]-1)*supersamples[1] 308 | || shapeZ[2] != shape[2] + (shape[2]-1)*supersamples[2]) 309 | throw std::runtime_error("X,Y,Z supersampled sdf arrays need to be compatible (must be dim + supersamples*(dim-1) !)."); 310 | 311 | std::array lower{0, 0, 0}; 312 | std::array upper{shape[0]-1, shape[1]-1, shape[2]-1}; 313 | long numx = upper[0] - lower[0] + 1; 314 | long numy = upper[1] - lower[1] + 1; 315 | long numz = upper[2] - lower[2] + 1; 316 | std::vector vertices; 317 | std::vector polygons; 318 | 319 | auto pyarray_to_cfunc = [&](long x, long y, long z) -> double { 320 | const npy_intp c[3] = {x * (supersamples[0]+1), y, z}; 321 | return PyArray_SafeGet(arrX, c); 322 | }; 323 | 324 | auto pyarray_to_cfuncX = [&](long x, long y, long z) -> double { 325 | const npy_intp c[3] = {x, y, z}; 326 | return PyArray_SafeGet(arrX, c); 327 | }; 328 | 329 | auto pyarray_to_cfuncY = [&](long x, long y, long z) -> double { 330 | const npy_intp c[3] = {x, y, z}; 331 | return PyArray_SafeGet(arrY, c); 332 | }; 333 | 334 | auto pyarray_to_cfuncZ = [&](long x, long y, long z) -> double { 335 | const npy_intp c[3] = {x, y, z}; 336 | return PyArray_SafeGet(arrZ, c); 337 | }; 338 | 339 | // Marching cubes. 340 | mc::marching_cubes_super_sampling(lower, upper, numx, numy, numz, supersamples[0], supersamples[1], supersamples[2], pyarray_to_cfunc, pyarray_to_cfuncX, pyarray_to_cfuncY, pyarray_to_cfuncZ, isovalue, 341 | vertices, polygons); 342 | 343 | // Copy the result to two Python ndarrays. 344 | npy_intp size_vertices = vertices.size(); 345 | npy_intp size_polygons = polygons.size(); 346 | PyArrayObject* verticesarr = reinterpret_cast(PyArray_SimpleNew(1, &size_vertices, PyArray_DOUBLE)); 347 | PyArrayObject* polygonsarr = reinterpret_cast(PyArray_SimpleNew(1, &size_polygons, PyArray_ULONG)); 348 | 349 | std::vector::const_iterator it = vertices.begin(); 350 | for(int i=0; it!=vertices.end(); ++i, ++it) 351 | *reinterpret_cast(PyArray_GETPTR1(verticesarr, i)) = *it; 352 | std::vector::const_iterator it2 = polygons.begin(); 353 | for(int i=0; it2!=polygons.end(); ++i, ++it2) 354 | *reinterpret_cast(PyArray_GETPTR1(polygonsarr, i)) = *it2; 355 | 356 | PyObject* res = Py_BuildValue("(O,O)", verticesarr, polygonsarr); 357 | Py_XDECREF(verticesarr); 358 | Py_XDECREF(polygonsarr); 359 | 360 | return res; 361 | } -------------------------------------------------------------------------------- /marching_cubes/src/pywrapper.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _PYWRAPPER_H 3 | #define _PYWRAPPER_H 4 | 5 | #include 6 | #include "pyarraymodule.h" 7 | 8 | #include 9 | 10 | PyObject* marching_cubes(PyArrayObject* arr, double isovalue); 11 | PyObject* marching_cubes_func(PyObject* lower, PyObject* upper, 12 | int numx, int numy, int numz, PyObject* f, double isovalue); 13 | 14 | 15 | PyObject* marching_cubes_color(PyArrayObject* arr_sdf, PyArrayObject* arr_color, double isovalue); 16 | PyObject* marching_cubes_color_func(PyObject* lower, PyObject* upper, 17 | int numx, int numy, int numz, PyObject* f_sdf, PyObject* f_color_r, PyObject* f_color_g, PyObject* f_color_b, double isovalue); 18 | 19 | 20 | PyObject* marching_cubes_super_sampling(PyArrayObject* arrX, PyArrayObject* arrY, PyArrayObject* arrZ, double isovalue); 21 | 22 | #endif // _PYWRAPPER_H 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.11.3 2 | scipy>=1.0.0 3 | cython>=0.25.0 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from setuptools import setup 4 | 5 | from setuptools.extension import Extension 6 | 7 | 8 | class lazy_cythonize(list): 9 | """ 10 | Lazy evaluate extension definition, to allow correct requirements install. 11 | """ 12 | 13 | def __init__(self, callback): 14 | super(lazy_cythonize, self).__init__() 15 | self._list, self.callback = None, callback 16 | 17 | def c_list(self): 18 | if self._list is None: 19 | self._list = self.callback() 20 | 21 | return self._list 22 | 23 | def __iter__(self): 24 | for e in self.c_list(): 25 | yield e 26 | 27 | def __getitem__(self, ii): 28 | return self.c_list()[ii] 29 | 30 | def __len__(self): 31 | return len(self.c_list()) 32 | 33 | 34 | def extensions(): 35 | 36 | from Cython.Build import cythonize 37 | import numpy 38 | 39 | numpy_include_dir = numpy.get_include() 40 | 41 | marching_cubes_module = Extension( 42 | "marching_cubes._mcubes", 43 | [ 44 | "marching_cubes/src/_mcubes.pyx", 45 | "marching_cubes/src/pywrapper.cpp", 46 | "marching_cubes/src/marchingcubes.cpp" 47 | ], 48 | language="c++", 49 | extra_compile_args=['-std=c++11', '-Wall'], 50 | include_dirs=[numpy_include_dir], 51 | depends=[ 52 | "marching_cubes/src/marchingcubes.h", 53 | "marching_cubes/src/pyarray_symbol.h", 54 | "marching_cubes/src/pyarraymodule.h", 55 | "marching_cubes/src/pywrapper.h" 56 | ], 57 | ) 58 | 59 | return cythonize([marching_cubes_module]) 60 | 61 | setup( 62 | name="PyMarchingCubes", 63 | version="0.0.2", 64 | description="Marching cubes for Python", 65 | author="Justus Thies (PyMCubes: Pablo Márquez Neila)", 66 | url="https://github.com/JustusThies/PyMarchingCubes", 67 | license="BSD 3-clause", 68 | long_description=""" 69 | Marching cubes for Python 70 | """, 71 | classifiers=[ 72 | "Development Status :: 5 - Production/Stable", 73 | "Environment :: Console", 74 | "Intended Audience :: Developers", 75 | "Intended Audience :: Science/Research", 76 | "License :: OSI Approved :: BSD License", 77 | "Natural Language :: English", 78 | "Operating System :: OS Independent", 79 | "Programming Language :: C++", 80 | "Programming Language :: Python", 81 | "Topic :: Multimedia :: Graphics :: 3D Modeling", 82 | "Topic :: Scientific/Engineering :: Image Recognition", 83 | ], 84 | packages=["marching_cubes"], 85 | ext_modules=lazy_cythonize(extensions), 86 | requires=['numpy', 'Cython', 'PyCollada'], 87 | setup_requires=['numpy', 'Cython'] 88 | ) 89 | -------------------------------------------------------------------------------- /test_mcubes.py: -------------------------------------------------------------------------------- 1 | 2 | import pytest 3 | 4 | import numpy as np 5 | from numpy.testing import assert_array_equal, assert_allclose 6 | 7 | import marching_cubes as mcubes 8 | 9 | 10 | def test_empty(): 11 | 12 | levelset = np.zeros((50, 50, 50)) 13 | vertices, triangles = mcubes.marching_cubes(levelset, 0.5) 14 | 15 | assert len(vertices) == len(triangles) == 0 16 | 17 | 18 | def test_sphere(): 19 | x, y, z = np.mgrid[:100, :100, :100] 20 | u = (x - 50)**2 + (y - 50)**2 + (z - 50)**2 - 25**2 21 | 22 | def func(x, y, z): 23 | return (x - 50)**2 + (y - 50)**2 + (z - 50)**2 - 25**2 24 | 25 | vertices1, triangles1 = mcubes.marching_cubes(u, 0.0) 26 | vertices2, triangles2 = mcubes.marching_cubes_func( 27 | (0, 0, 0), 28 | (99, 99, 99), 29 | 100, 100, 100, 30 | func, 0.0 31 | ) 32 | 33 | assert_allclose(vertices1, vertices2) 34 | assert_array_equal(triangles1, triangles2) 35 | 36 | 37 | def test_no_duplicates(): 38 | def sphere(x, y, z): 39 | return np.sqrt((x - 4)**2 + (y - 4)**2 + (z - 4)**2) - 4 40 | 41 | vertices, _ = mcubes.marching_cubes_func((2, 2, 2), (9, 9, 9), 20, 20, 20, sphere, 0) 42 | 43 | assert len(vertices) == len(np.unique(vertices, axis=0)) 44 | 45 | 46 | def test_export(): 47 | 48 | u = np.zeros((10, 10, 10)) 49 | u[2:-2, 2:-2, 2:-2] = 1.0 50 | vertices, triangles = mcubes.marching_cubes(u, 0.5) 51 | 52 | mcubes.export_obj(vertices, triangles, "output/test.obj") 53 | mcubes.export_off(vertices, triangles, "output/test.off") 54 | mcubes.export_mesh(vertices, triangles, "output/test.dae") 55 | 56 | 57 | def test_invalid_input(): 58 | 59 | def func(x, y, z): 60 | return x**2 + y**2 + z**2 - 1 61 | 62 | mcubes.marching_cubes_func((-1.5, -1.5, -1.5), (1.5, 1.5, 1.5), 10, 10, 10, func, 0) 63 | 64 | with pytest.raises(ValueError): 65 | mcubes.marching_cubes_func((0, 0, 0), (0, 0, 0), 10, 10, 10, func, 0) 66 | 67 | with pytest.raises(ValueError): 68 | mcubes.marching_cubes_func((-1.5, -1.5, -1.5), (1.5, 1.5, 1.5), 1, 10, 10, func, 0) 69 | 70 | with pytest.raises(Exception): 71 | mcubes.marching_cubes_func((-1.5, -1.5), (1.5, 1.5, 1.5), 10, 10, 10, func, 0) 72 | 73 | with pytest.raises(Exception): 74 | mcubes.marching_cubes_func((-1.5, -1.5, -1.5), (1.5, 1.5), 10, 10, 10, func, 0) 75 | -------------------------------------------------------------------------------- /test_smoothing.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import pytest 4 | 5 | import numpy as np 6 | 7 | import marching_cubes as mcubes 8 | 9 | 10 | def test_sphere(): 11 | 12 | # Create sphere with radius 25 centered at (50, 50, 50) 13 | x, y, z = np.mgrid[:100, :100, :100] 14 | levelset = np.sqrt((x - 50)**2 + (y - 50)**2 + (z - 50)**2) - 25 15 | 16 | # vertices, triangles = mcubes.marching_cubes(levelset, 0) 17 | # mcubes.export_obj(vertices, triangles, 'sphere1.obj') 18 | 19 | binary_levelset = levelset > 0 20 | smoothed_levelset = mcubes.smooth( 21 | binary_levelset, 22 | method='constrained', 23 | max_iters=500, 24 | rel_tol=1e-4 25 | ) 26 | 27 | vertices, _ = mcubes.marching_cubes(smoothed_levelset, 0.0) 28 | 29 | # Check all vertices have same distance to (50, 50, 50) 30 | dist = np.sqrt(np.sum((vertices - [50, 50, 50])**2, axis=1)) 31 | 32 | assert dist.min() > 24.5 and dist.max() < 25.5 33 | assert np.all(np.abs(smoothed_levelset - levelset) < 1) 34 | assert np.all((smoothed_levelset > 0) == binary_levelset) 35 | 36 | 37 | def test_gaussian_smoothing(): 38 | 39 | # Create sphere with radius 25 centered at (50, 50, 50) 40 | x, y, z = np.mgrid[:100, :100, :100] 41 | levelset = np.sqrt((x - 50)**2 + (y - 50)**2 + (z - 50)**2) - 25 42 | 43 | binary_levelset = levelset > 0 44 | smoothed_levelset = mcubes.smooth( 45 | binary_levelset, 46 | method='gaussian', 47 | sigma=3 48 | ) 49 | 50 | vertices, _ = mcubes.marching_cubes(smoothed_levelset, 0.0) 51 | 52 | # Check all vertices have same distance to (50, 50, 50) 53 | dist = np.sqrt(np.sum((vertices - [50, 50, 50])**2, axis=1)) 54 | assert dist.min() > 24 and dist.max() < 25 55 | 56 | 57 | def test_wrong_ndim(): 58 | 59 | binary_levelset = np.random.uniform(size=(10)) < 0.5 60 | 61 | with pytest.raises(ValueError): 62 | mcubes.smooth( 63 | binary_levelset, 64 | method='constrained', 65 | max_iters=500, 66 | rel_tol=1e-4 67 | ) 68 | 69 | binary_levelset = np.random.uniform(size=(10, 10, 10, 10)) < 0.5 70 | 71 | with pytest.raises(ValueError): 72 | mcubes.smooth( 73 | binary_levelset, 74 | method='constrained', 75 | max_iters=500, 76 | rel_tol=1e-4 77 | ) 78 | 79 | 80 | def test_wrong_method(): 81 | 82 | with pytest.raises(ValueError): 83 | mcubes.smooth( 84 | np.zeros((10, 10), dtype=np.bool_), 85 | method='wrong', 86 | max_iters=500, 87 | rel_tol=1e-4 88 | ) 89 | 90 | 91 | def test_circle(): 92 | 93 | x, y = np.mgrid[:100, :100] 94 | levelset = np.sqrt((x - 50)**2 + (y - 50)**2) - 25 95 | binary_levelset = levelset > 0 96 | 97 | smoothed_levelset = mcubes.smooth( 98 | binary_levelset, 99 | max_iters=500, 100 | rel_tol=1e-4 101 | ) 102 | 103 | assert np.all(np.abs(smoothed_levelset - levelset) < 1) 104 | assert np.all((smoothed_levelset > 0) == binary_levelset) 105 | 106 | 107 | # if __name__ == '__main__': 108 | # # logging.basicConfig(level=logging.DEBUG) 109 | # test_circle() 110 | # test_sphere() 111 | # test_large_sphere() 112 | --------------------------------------------------------------------------------