[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: pip\n  directory: /\n  insecure-external-code-execution: allow\n  schedule:\n    interval: monthly\n  open-pull-requests-limit: 100\n  labels:\n  - maintenance\n  - dependencies\n  groups:\n    pip:\n      patterns:\n      - '*'\n- package-ecosystem: github-actions\n  directory: /\n  schedule:\n    interval: monthly\n  open-pull-requests-limit: 100\n  labels:\n  - maintenance\n  - dependencies\n  groups:\n    actions:\n      patterns:\n      - '*'\n"
  },
  {
    "path": ".github/workflows/testing-and-deployment.yml",
    "content": "name: Build\n\non:\n  pull_request:\n  push:\n    tags:\n    - v*\n    branches:\n    - main\n\n# disable concurrent runs\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  # fail fast and early to avoid clogging GH Actions\n  smoke_testing:\n    runs-on: ubuntu-latest\n    name: Source distribution testing\n    steps:\n    - uses: actions/checkout@v6\n\n    - name: Set up Python\n      uses: actions/setup-python@v6\n      with:\n        python-version: '3.13'\n\n    - name: Setup headless display\n      uses: pyvista/setup-headless-display-action@v4\n\n    - name: Build and validate wheel\n      run: |\n        pip install build twine\n        python -m build\n        twine check dist/*\n\n    - name: Install\n      run: pip install dist/*.whl\n\n    - name: Test\n      run: |\n        pip install -r requirements_test.txt\n        cd tests && python -m pytest -v\n\n    - name: Upload sdist\n      uses: actions/upload-artifact@v7\n      with:\n        path: ./dist/*.tar.gz\n        name: fast-simplification-sdist\n\n  docs_build:\n    name: Build Documentation\n    runs-on: ubuntu-latest\n    needs: smoke_testing\n\n    steps:\n    - uses: actions/checkout@v6\n\n    - name: Setup Python\n      uses: actions/setup-python@v6\n      with:\n        python-version: '3.13'\n\n    - name: Setup headless display\n      uses: pyvista/setup-headless-display-action@v4\n\n    - name: Install library\n      run: pip install .\n\n    - name: Build Documentation\n      run: |\n        pip install -r requirements_docs.txt\n        make -C doc html\n\n    - name: Deploy on tag\n      uses: JamesIves/github-pages-deploy-action@v4\n      if: startsWith(github.ref, 'refs/tags/')\n      with:\n        token: ${{ secrets.GITHUB_TOKEN }}\n        branch: gh-pages\n        folder: doc/_build/html\n\n  build_wheels:\n    needs: smoke_testing\n    name: Build wheels on ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest, macos-15-intel]\n\n    steps:\n    - uses: actions/checkout@v6\n\n    - name: Build wheels\n      uses: pypa/cibuildwheel@v3.4.0\n\n    - name: List generated wheels\n      run: ls ./wheelhouse/*\n\n    - uses: actions/upload-artifact@v7\n      with:\n        path: ./wheelhouse/*.whl\n        name: fast-simplification-wheel-${{ matrix.os }}\n\n  release:\n    name: Release\n    if: github.event_name == 'push' && contains(github.ref, 'refs/tags')\n    needs: [build_wheels, docs_build]\n    runs-on: ubuntu-latest\n    environment:\n      name: pypi\n      url: https://pypi.org/p/fast-simplification\n    permissions:\n      id-token: write  # this permission is mandatory for trusted publishing\n      contents: write  # required to create a release\n    steps:\n    - uses: actions/download-artifact@v8\n    - name: Flatten directory structure\n      run: |\n        mkdir -p dist/\n        find . -name '*.whl' -exec mv {} dist/ \\;\n        find . -name '*.tar.gz' -exec mv {} dist/ \\;\n    - name: Publish package distributions to PyPI\n      uses: pypa/gh-action-pypi-publish@release/v1\n    - name: Create GitHub Release\n      uses: softprops/action-gh-release@v2\n      with:\n        generate_release_notes: true\n        files: |\n          ./**/*.whl\n"
  },
  {
    "path": ".gitignore",
    "content": "# Compiled source #\n###################\n*.pyc\n*.pyd\n*.so\n*.o\n__pycache__/\n\n# Pip\n*.egg-info\n\n# Cython generated files #\nfast_simplification/_simplify.cpp\nfast_simplification/_replay.cpp\n\n# documentation\ndoc/_build/\ndoc/examples/\ndoc/_autosummary/\n\ndist/\nvenv/\nbuild/\n\n# IDE\n.vscode/"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "# Integration with GitHub Actions\n# See https://pre-commit.ci/\nci:\n  autoupdate_schedule: monthly\n\nrepos:\n- repo: https://github.com/astral-sh/ruff-pre-commit\n  rev: v0.15.9\n  hooks:\n  - id: ruff\n    args: [--fix, --exit-non-zero-on-fix]\n    exclude: ^(docs/|tests)\n  - id: ruff-format\n\n- repo: https://github.com/pycqa/isort\n  rev: 8.0.1\n  hooks:\n  - id: isort\n\n- repo: https://github.com/codespell-project/codespell\n  rev: v2.4.2\n  hooks:\n  - id: codespell\n    args: [--toml, pyproject.toml]\n    additional_dependencies: [tomli]\n\n- repo: https://github.com/keewis/blackdoc\n  rev: v0.4.6\n  hooks:\n  - id: blackdoc\n    files: \\.py$\n\n# - repo: https://github.com/pycqa/pydocstyle\n#   rev: 6.1.1\n#   hooks:\n#   - id: pydocstyle\n#     additional_dependencies: [toml]\n#     exclude: \"tests/\"\n\n- repo: https://github.com/pre-commit/pre-commit-hooks\n  rev: v6.0.0\n  hooks:\n  - id: check-merge-conflict\n  - id: debug-statements\n  - id: trailing-whitespace\n  - id: no-commit-to-branch\n    args: [--branch, main]\n  - id: requirements-txt-fixer\n\n- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks\n  rev: v2.16.0\n  hooks:\n  - id: pretty-format-toml\n    args: [--autofix]\n  - id: pretty-format-yaml\n    args: [--autofix, --indent, '2']\n\n# this validates our github workflow files\n- repo: https://github.com/python-jsonschema/check-jsonschema\n  rev: 0.37.1\n  hooks:\n  - id: check-github-workflows\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License\n\nCopyright (c) 2017-2021 The PyVista Developers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include fast_simplification/*.h\n"
  },
  {
    "path": "README.rst",
    "content": "Python Fast-Quadric-Mesh-Simplification Wrapper\n===============================================\nThis is a python wrapping of the `Fast-Quadric-Mesh-Simplification Library\n<https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification/>`_. Having\narrived at the same problem as the original author, but needing a Python\nlibrary, this project seeks to extend the work of the original library while\nadding integration to Python and the `PyVista\n<https://github.com/pyvista/pyvista>`_ project.\n\nFor the full documentation visit: https://pyvista.github.io/fast-simplification/\n\n.. image:: https://github.com/pyvista/fast-simplification/raw/main/doc/images/simplify_demo.png\n\nInstallation\n------------\nFast Simplification can be installed from PyPI using pip on Python >= 3.7::\n\n  pip install fast-simplification\n\nSee the `Contributing <https://github.com/pyvista/fast-simplification#contributing>`_ for more details regarding development or if the installation through pip doesn't work out.\n\nBasic Usage\n-----------\nThe basic interface is quite straightforward and can work directly\nwith arrays of points and triangles:\n\n.. code:: python\n\n    points = [[ 0.5, -0.5, 0.0],\n              [ 0.0, -0.5, 0.0],\n              [-0.5, -0.5, 0.0],\n              [ 0.5,  0.0, 0.0],\n              [ 0.0,  0.0, 0.0],\n              [-0.5,  0.0, 0.0],\n              [ 0.5,  0.5, 0.0],\n              [ 0.0,  0.5, 0.0],\n              [-0.5,  0.5, 0.0]]\n\n    faces = [[0, 1, 3],\n             [4, 3, 1],\n             [1, 2, 4],\n             [5, 4, 2],\n             [3, 4, 6],\n             [7, 6, 4],\n             [4, 5, 7],\n             [8, 7, 5]]\n\n    points_out, faces_out = fast_simplification.simplify(points, faces, 0.5)\n\n\nAdvanced Usage\n--------------\nThis library supports direct integration with VTK through PyVista to\nprovide a simplistic interface to the library. As this library\nprovides a 4-5x improvement to the VTK decimation algorithms.\n\n.. code:: python\n\n   >>> from pyvista import examples\n   >>> mesh = examples.download_nefertiti()\n   >>> out = fast_simplification.simplify_mesh(mesh, target_reduction=0.9)\n\n   Compare with built-in VTK/PyVista methods:\n\n   >>> fas_sim = fast_simplification.simplify_mesh(mesh, target_reduction=0.9)\n   >>> dec_std = mesh.decimate(0.9)  # vtkQuadricDecimation\n   >>> dec_pro = mesh.decimate_pro(0.9)  # vtkDecimatePro\n\n   >>> pv.set_plot_theme('document')\n   >>> pl = pv.Plotter(shape=(2, 2), window_size=(1000, 1000))\n   >>> pl.add_text('Original', 'upper_right', color='w')\n   >>> pl.add_mesh(mesh, show_edges=True)\n   >>> pl.camera_position = cpos\n\n   >>> pl.subplot(0, 1)\n   >>> pl.add_text(\n   ...    'Fast-Quadric-Mesh-Simplification\\n~2.2 seconds', 'upper_right', color='w'\n   ... )\n   >>> pl.add_mesh(fas_sim, show_edges=True)\n   >>> pl.camera_position = cpos\n\n   >>> pl.subplot(1, 0)\n   >>> pl.add_mesh(dec_std, show_edges=True)\n   >>> pl.add_text(\n   ...    'vtkQuadricDecimation\\n~9.5 seconds', 'upper_right', color='w'\n   ... )\n   >>> pl.camera_position = cpos\n\n   >>> pl.subplot(1, 1)\n   >>> pl.add_mesh(dec_pro, show_edges=True)\n   >>> pl.add_text(\n   ...    'vtkDecimatePro\\n11.4~ seconds', 'upper_right', color='w'\n   ... )\n   >>> pl.camera_position = cpos\n   >>> pl.show()\n\n\nComparison to other libraries\n-----------------------------\nThe `pyfqmr <https://github.com/Kramer84/pyfqmr-Fast-Quadric-Mesh-Reduction>`_\nlibrary wraps the same header file as this library and has similar capabilities.\nIn this library, the decision was made to write the Cython layer on top of an\nadditional C++ layer rather than directly interfacing with wrapper from Cython.\nThis results in a mild performance improvement.\n\nReusing the example above:\n\n.. code:: python\n\n   Set up a timing function.\n\n   >>> import pyfqmr\n   >>> vertices = mesh.points\n   >>> faces = mesh.faces.reshape(-1, 4)[:, 1:]\n   >>> def time_pyfqmr():\n   ...     mesh_simplifier = pyfqmr.Simplify()\n   ...     mesh_simplifier.setMesh(vertices, faces)\n   ...     mesh_simplifier.simplify_mesh(\n   ...         target_count=out.n_faces, aggressiveness=7, verbose=0\n   ...     )\n   ...     vertices_out, faces_out, normals_out = mesh_simplifier.getMesh()\n   ...     return vertices_out, faces_out, normals_out\n\nNow, time it and compare with the non-VTK API of this library:\n\n.. code:: python\n\n   >>> timeit time_pyfqmr()\n   2.75 s ± 5.35 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n\n   >>> timeit vout, fout = fast_simplification.simplify(vertices, faces, 0.9)\n   2.05 s ± 3.18 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n\nAdditionally, the ``fast-simplification`` library has direct plugins\nto the ``pyvista`` library, making it easy to read and write meshes:\n\n.. code:: python\n\n   >>> import pyvista\n   >>> import fast_simplification\n   >>> mesh = pyvista.read('my_mesh.stl')\n   >>> simple = fast_simplification.simplify_mesh(mesh)\n   >>> simple.save('my_simple_mesh.stl')\n\nSince both libraries are based on the same core C++ code, feel free to\nuse whichever gives you the best performance and interoperability.\n\nReplay decimation functionality\n-------------------------------\nThis library also provides an interface to keep track of the successive\ncollapses that occur during the decimation process and to replay the\ndecimation process. This can be useful for different applications, such\nas:\n\n* applying the same decimation to a collection of meshes that share the\n  same topology\n* computing a correspondence map between the vertices of the original\n  mesh and the vertices of the decimated mesh, to transfer field data from\n  one to the other for example\n* replaying the decimation process with a smaller target reduction than\n  the original one, faster than decimating the original mesh with the\n  smaller target reduction\n\nTo use this functionality, you need to set the ``return_collapses``\nparameter to ``True`` when calling ``simplify``. This will return the\nsuccessive collapses of the decimation process in addition to points\nand faces.\n\n.. code:: python\n\n   >>> import fast_simplification\n   >>> import pyvista\n   >>> mesh = pyvista.Sphere()\n   >>> points, faces = mesh.points, mesh.faces.reshape(-1, 4)[:, 1:]\n   >>> points_out, faces_out, collapses = fast_simplification.simplify(points, faces, 0.9, return_collapses=True)\n\nNow you can call ``replay_simplification`` to replay the decimation process\nand obtain the mapping between the vertices of the original mesh and the\nvertices of the decimated mesh.\n\n.. code:: python\n\n   >>> points_out, faces_out, indice_mapping = fast_simplification.replay_simplification(points, faces, collapses)\n   >>> i = 3\n   >>> print(f'Vertex {i} of the original mesh is mapped to {indice_mapping[i]} of the decimated mesh')\n\nYou can also use the ``replay_simplification`` function to replay the\ndecimation process with a smaller target reduction than the original one.\nThis is faster than decimating the original mesh with the smaller target\nreduction. To do so, you need to pass a subset of the collapses to the\n``replay_simplification`` function. For example, to replay the decimation\nprocess with a target reduction of 50% the initial rate, you can run:\n\n.. code:: python\n\n   >>> import numpy as np\n   >>> collapses_half = collapses[:int(0.5 * len(collapses))]\n   >>> points_out, faces_out, indice_mapping = fast_simplification.replay_simplification(points, faces, collapses_half)\n\nIf you have a collection of meshes that share the same topology, you can\napply the same decimation to all of them by calling ``replay_simplification``\nwith the same collapses for each mesh. This ensure that the decimated meshes\nwill share the same topology.\n\n.. code:: python\n\n   >>> import numpy as np\n   >>> # Assume that you have a collection of meshes stored in a list meshes\n   >>> _, _, collapses = fast_simplification.simplify(meshes[0].points, meshes[0].faces,\n   ...                                                0.9, return_collapses=True)\n   >>> decimated_meshes = []\n   >>> for mesh in meshes:\n   ...     points_out, faces_out, _ = fast_simplification.replay_simplification(mesh.points, mesh.faces, collapses)\n   ...     decimated_meshes.append(pyvista.PolyData(points_out, faces_out))\n\nContributing\n------------\nContribute to this repository by forking this repository and installing in\ndevelopment mode with::\n\n  git clone https://github.com/<USERNAME>/fast-simplification\n  pip install -e .\n  pip install -r requirements_test.txt\n\nYou can then add your feature or commit your bug fix and then run your unit\ntesting with::\n\n  pytest\n\nUnit testing will automatically enforce minimum code coverage standards.\n\nNext, to ensure your code meets minimum code styling standards, run::\n\n  pip install pre-commit\n  pre-commit run --all-files\n\nFinally, `create a pull request`_ from your fork and I'll be sure to review it.\n\n.. _create a pull request: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request\n"
  },
  {
    "path": "doc/Makefile",
    "content": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nSPHINXPROJ    = pymeshfix\nSOURCEDIR     = .\nBUILDDIR      = _build\n\n# Put it first so that \"make\" without argument is like \"make help\".\nhelp:\n\t@$(SPHINXBUILD) -M help \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\n.PHONY: help Makefile\n\n# Catch-all target: route all unknown targets to Sphinx using the new\n# \"make mode\" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).\n%: Makefile\n\t@$(SPHINXBUILD) -M $@ \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\nclean:\n\trm -rf $(BUILDDIR)/*\n\trm -rf examples/\n\tfind . -type d -name \"_autosummary\" -exec rm -rf {} +\n"
  },
  {
    "path": "doc/_templates/autosummary/class.rst",
    "content": "{{ fullname | escape | underline}}\n\n.. currentmodule:: {{ module }}\n\n.. autoclass:: {{ objname }}\n\n   {% block methods %}\n\n   {% if methods %}\n   .. rubric:: {{ _('Methods') }}\n\n   .. autosummary::\n      :toctree:\n   {% for item in methods %}\n      {% if item != \"__init__\" %}\n      {{ name }}.{{ item }}\n      {% endif %}\n   {%- endfor %}\n   {% endif %}\n   {% endblock %}\n\n   {% block attributes %}\n   {% if attributes %}\n   .. rubric:: {{ _('Attributes') }}\n\n   .. autosummary::\n      :toctree:\n   {% for item in attributes %}\n      {% if item.0 != item.upper().0 %}\n      {{ name }}.{{ item }}\n      {% endif %}\n   {%- endfor %}\n   {% endif %}\n   {% endblock %}\n"
  },
  {
    "path": "doc/api.rst",
    "content": "API Reference\n=============\nThese are the three public methods that expose the fast-simplification API to\nPython.\n\n.. currentmodule:: fast_simplification\n\n.. autosummary::\n   :toctree: _autosummary\n\n   simplify\n   simplify_mesh\n   replay_simplification\n"
  },
  {
    "path": "doc/conf.py",
    "content": "import datetime\nimport os\n\nimport numpy as np\nimport pyvista\nfrom sphinx_gallery.sorting import FileNameSortKey\n\nfrom fast_simplification import __version__\n\n# Manage errors\npyvista.set_error_output_file(\"errors.txt\")\n# Ensure that offscreen rendering is used for docs generation\npyvista.OFF_SCREEN = True  # Not necessary - simply an insurance policy\n# Preferred plotting style for documentation\npyvista.set_plot_theme(\"document\")\npyvista.global_theme.window_size = np.array([1024, 768]) * 2\n# Save figures in specified directory\npyvista.FIGURE_PATH = os.path.abspath(\"./images/\")\nif not os.path.exists(pyvista.FIGURE_PATH):\n    os.makedirs(pyvista.FIGURE_PATH)\n\npyvista.BUILDING_GALLERY = True\n\n# -- Project information -----------------------------------------------------\n\nproject = \"fast-simplification\"\nyear = datetime.date.today().year\ncopyright = f\"2017-{year}, The PyVista Developers\"\nauthor = \"Alex Kaszynski\"\n\n# The short X.Y version\nversion = release = __version__\n\n# -- General configuration ---------------------------------------------------\nhtml_logo = \"./_static/pyvista_logo_sm.png\"\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#\n# needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = [\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.napoleon\",\n    # 'sphinx.ext.doctest',\n    \"sphinx.ext.autosummary\",\n    \"notfound.extension\",\n    \"sphinx_copybutton\",\n    \"sphinx_gallery.gen_gallery\",\n    \"sphinx.ext.extlinks\",\n    \"numpydoc\",\n]\n\nnumpydoc_show_class_members = False\n# numpydoc_class_members_toctree = False\nhtml_static_path = [\"_static\"]\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = [\"_templates\"]\n\n# The suffix(es) of source filenames.\n# You can specify multiple suffix as a list of string:\n#\n# source_suffix = ['.rst', '.md']\nsource_suffix = \".rst\"\n\n# The master toctree document.\nmaster_doc = \"index\"\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#\n# This is also used if you do content translation via gettext catalogs.\n# Usually you set \"language\" from the command line for these cases.\nlanguage = None\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\n# This pattern also affects html_static_path and html_extra_path .\nexclude_patterns = []\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = \"sphinx\"\n\n# Copy button customization ---------------------------------------------------\n# exclude traditional Python prompts from the copied code\ncopybutton_prompt_text = r\">>> ?|\\.\\.\\. \"\ncopybutton_prompt_is_regexp = True\n\n\n# -- Options for HTML output -------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n#\nhtml_theme = \"pydata_sphinx_theme\"\nhtml_context = {\n    # Enable the \"Edit in GitHub link within the header of each page.\n    \"display_github\": True,\n    \"github_user\": \"pyvista\",\n    \"github_repo\": \"fast-simplification\",\n    \"github_version\": \"master\",\n    \"menu_links_name\": \"Getting Connected\",\n    \"menu_links\": [\n        (\n            '<i class=\"fa fa-slack fa-fw\"></i> Slack Community',\n            \"http://slack.pyvista.org\",\n        ),\n        (\n            '<i class=\"fa fa-comment fa-fw\"></i> Support',\n            \"https://github.com/pyvista/pyvista-support\",\n        ),\n        (\n            '<i class=\"fa fa-github fa-fw\"></i> Source Code',\n            \"https://github.com/pyvista/pymeshfix\",\n        ),\n    ],\n}\n\nhtml_theme_options = {\n    \"show_prev_next\": False,\n    \"github_url\": \"https://github.com/pyvista/pymeshfix\",\n}\n\n\n# -- Options for HTMLHelp output ---------------------------------------------\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = \"PyMeshFix\"\n\n\n# -- Options for LaTeX output ------------------------------------------------\n\nlatex_elements = {}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [\n    (\n        master_doc,\n        \"pymeshfix.tex\",\n        \"PyMeshFix Documentation\",\n        \"Alex Kaszynski\",\n        \"manual\",\n    ),\n]\n\n\n# -- Options for manual page output ------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [(master_doc, \"PyMeshFix\", \"PyMeshFix Documentation\", [author], 1)]\n\n\n# -- Options for Texinfo output ----------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n    (\n        master_doc,\n        \"PyMeshFix\",\n        \"PyMeshFix Documentation\",\n        author,\n        \"PyMeshFix\",\n        \"One line description of project.\",\n        \"Miscellaneous\",\n    ),\n]\n\n\n# -- Extension configuration -------------------------------------------------\n\n# -- Options for intersphinx extension ---------------------------------------\n\n# Example configuration for intersphinx: refer to the Python standard library.\nintersphinx_mapping = {\n    \"https://docs.python.org/\": None,\n    \"https://docs.pyvista.org\": None,\n}\n\n# -- Options for todo extension ----------------------------------------------\n\n# If true, `todo` and `todoList` produce output, else they produce nothing.\ntodo_include_todos = True\n\n\n# -- Sphinx Gallery Options\nsphinx_gallery_conf = {\n    # path to your examples scripts\n    \"examples_dirs\": [\n        \"../examples/\",\n    ],\n    # path where to save gallery generated examples\n    \"gallery_dirs\": [\"examples\"],\n    # pattern to search for example files\n    \"filename_pattern\": r\"\\.py\",\n    # Remove the \"Download all examples\" button from the top level gallery\n    \"download_all_examples\": False,\n    # Sort gallery example by file name instead of number of lines (default)\n    \"within_subsection_order\": FileNameSortKey,\n    # directory where function granular galleries are stored\n    \"backreferences_dir\": None,\n    # Modules for which function level galleries are created.  In\n    \"doc_module\": \"pymeshfix\",\n    \"image_scrapers\": (pyvista.Scraper(), \"matplotlib\"),\n    \"thumbnail_size\": (350, 350),\n}\n\n\n# -- Custom 404 page\n\nnotfound_no_urls_prefix = True\n"
  },
  {
    "path": "doc/index.rst",
    "content": ".. include:: ../README.rst\n\n\n.. toctree::\n   :hidden:\n\n   self\n\n\n\n.. toctree::\n   :maxdepth: 2\n   :caption: Examples\n   :hidden:\n\n   examples/index\n\n\n\n.. toctree::\n   :maxdepth: 2\n   :caption: API Reference\n   :hidden:\n\n   api\n"
  },
  {
    "path": "examples/README.txt",
    "content": "Fast-simplification Examples\n============================\nThe following examples demonstrate ``fast-simplification``\nfunctionality using `pyvista <https://docs.pyvista.org/>`__.\n"
  },
  {
    "path": "examples/replay.py",
    "content": "\"\"\"\nReplay Decimation\n-----------------\n\nThis example shows how to replay a decimation sequence with replay.\n\n\"\"\"\n\nfrom time import time\n\nimport numpy as np\nimport pyvista as pv\nfrom pyvista import examples\n\nimport fast_simplification\n\n\n# Ancillary function to convert triangles to padded faces\ndef triangles_to_faces(triangles):\n    tmp = 3 * np.ones((len(triangles), 4), dtype=triangles.dtype)\n    tmp[:, 1:] = triangles\n    return tmp.copy().reshape(-1)\n\n\n# mesh = examples.download_cow().triangulate().clean()\n\n# cpos = [(12.81184076782852, 0.2698100334791761, -10.82840852844307),\n#  (-5.767085129340097, -0.45822321783537723, 6.935179459234972),\n#  (-0.031039486276564762, 0.99948202301343, 0.008499174352116386)]\n\n\n# load an example mesh\nmesh = examples.download_louis_louvre()\n\n# nice camera angle\ncpos = [\n    (5.428820015861438, -10.151721995577468, 15.902198956656623),\n    (1.4405146331169636, 2.897371104075222, 10.951469667556948),\n    (-0.01001925846458282, 0.3520252491158569, 0.9359368773826251),\n]\n\npoints = mesh.points\ntriangles = mesh.faces.reshape(-1, 4)[:, 1:]\n\n\n# Decimate the mesh with fast_simplification\n# and record the collapses\nstart = time()\ndec_points, dec_triangles, collapses = fast_simplification.simplify(\n    points, triangles, 0.995, return_collapses=True\n)\ntime_simplify = time() - start\n\n# Replay the decimation sequence and record the mapping between\n# the original points and the decimated points\nstart = time()\n(\n    dec_points_replay,\n    dec_triangles_replay,\n    indice_mapping_replay,\n) = fast_simplification.replay_simplification(\n    points=points,\n    triangles=triangles,\n    collapses=collapses,\n)\ntime_replay_new = time() - start\n\n# Partially replay the decimation sequence (90% of the collapses are replayed)\npartial_collapses = collapses[0 : int(0.9 * len(collapses))]\nstart = time()\n(\n    dec_points_replay2,\n    dec_triangles_replay2,\n    indice_mapping_replay2,\n) = fast_simplification.replay_simplification(\n    points=points, triangles=triangles, collapses=partial_collapses\n)\ntime_replay_new = time() - start\n\n# Randomly select two points on the original mesh\nnp.random.seed(1)\ni, j = np.random.randint(0, len(mesh.points), 2)\n\n# Map the indices of the original points to the indices of the decimated points\nm_i = indice_mapping_replay[i]\nm_j = indice_mapping_replay[j]\nm_i2 = indice_mapping_replay2[i]\nm_j2 = indice_mapping_replay2[j]\n\np = pv.Plotter(shape=(2, 2), theme=pv.themes.DocumentTheme())\n\np.subplot(0, 0)\n# Plot the original mesh with the two highlighted points\np.add_mesh(mesh, show_edges=True, color=\"tan\")\np.add_points(mesh.points[i], color=\"red\", point_size=10, render_points_as_spheres=True)\np.add_points(mesh.points[j], color=\"blue\", point_size=10, render_points_as_spheres=True)\np.add_text(\n    f\"Original mesh, {mesh.points.shape[0]} vertices, {triangles.shape[0]} triangles\",\n    font_size=10,\n)\np.camera_position = cpos\n\np.subplot(0, 1)\n# Plot the decimated mesh\np.add_mesh(\n    pv.PolyData(dec_points, faces=triangles_to_faces(dec_triangles)),\n    show_edges=True,\n    color=\"tan\",\n)\np.add_text(\n    f\"Decimated mesh, {dec_points.shape[0]} vertices, {dec_triangles.shape[0]} triangles, took {time_simplify:.2f}s\",\n    font_size=10,\n)\np.camera_position = cpos\n\np.subplot(1, 0)\n# Plot the mesh decimated with replay with the two highlighted points\np.add_mesh(\n    pv.PolyData(dec_points_replay, faces=triangles_to_faces(dec_triangles_replay)),\n    show_edges=True,\n    color=\"tan\",\n)\np.add_points(\n    dec_points_replay[m_i],\n    color=\"red\",\n    point_size=10,\n    render_points_as_spheres=True,\n)\np.add_points(\n    dec_points_replay[m_j],\n    color=\"blue\",\n    point_size=10,\n    render_points_as_spheres=True,\n)\nn_points, n_triangles = dec_points_replay.shape[0], dec_triangles_replay.shape[0]\np.add_text(\n    f\"Replay, {n_points} vertices, {n_triangles} triangles, took {time_replay_new:.2f}s\",\n    font_size=10,\n)\np.camera_position = cpos\n\np.subplot(1, 1)\n# Plot the mesh partially decimated with replay with the two highlighted points\np.add_mesh(\n    pv.PolyData(dec_points_replay2, faces=triangles_to_faces(dec_triangles_replay2)),\n    show_edges=True,\n    color=\"tan\",\n)\np.add_points(\n    dec_points_replay2[m_i2],\n    color=\"red\",\n    point_size=10,\n    render_points_as_spheres=True,\n)\np.add_points(\n    dec_points_replay2[m_j2],\n    color=\"blue\",\n    point_size=10,\n    render_points_as_spheres=True,\n)\nn_points, n_triangles = dec_points_replay2.shape[0], dec_triangles_replay2.shape[0]\np.add_text(\n    f\"Partial replay, {n_points} vertices, {n_triangles} triangles, took {time_replay_new:.2f}s\",\n    font_size=10,\n)\np.camera_position = cpos\n\np.show()\n"
  },
  {
    "path": "examples/simplify.py",
    "content": "\"\"\"\nCompare Decimation Methods\n--------------------------\n\nThis example compares various decimation methods\n\n\"\"\"\n\nimport time\n\nimport pyvista as pv\nfrom pyvista import examples\n\nimport fast_simplification\n\n# load an example mesh\nmesh = examples.download_louis_louvre()\n\n# nice camera angle\ncpos = [\n    (6.264157141857314, -6.959267635766402, 11.71668951132694),\n    (1.3291685457683413, 2.267162128740896, 12.263240938610595),\n    (0.0023825740958850136, -0.05786378450796799, 0.9983216444528751),\n]\n\n\n###############################################################################\n# Compare decimation times\nreduction = 0.9\nprint(\"Approach                         Time Elapsed\")\n\ntstart = time.time()\nfas_sim = fast_simplification.simplify_mesh(mesh, target_reduction=reduction)\nfast_sim_time = time.time() - tstart\nprint(f\"Fast Quadratic Simplification  {fast_sim_time:8.4f} seconds\")\n\ntstart = time.time()\ndec_std = mesh.decimate(reduction)\ndec_std_time = time.time() - tstart\nprint(f\"vtkQuadricDecimation           {dec_std_time:8.4f} seconds\")\n\ntstart = time.time()\ndec_pro = mesh.decimate_pro(reduction)\ndec_pro_time = time.time() - tstart\nprint(f\"vtkDecimatePro                 {dec_pro_time:8.4f} seconds\")\n\n\npl = pv.Plotter(shape=(2, 2), window_size=(1000, 1000), theme=pv.themes.DocumentTheme())\npl.add_text(\"Original\", \"upper_right\", color=\"k\")\npl.add_mesh(mesh, show_edges=True)\npl.camera_position = cpos\n\npl.subplot(0, 1)\npl.add_text(\n    f\"Fast-Quadric-Mesh-Simplification\\n{fast_sim_time:8.4f} seconds\",\n    \"upper_right\",\n    color=\"k\",\n)\npl.add_mesh(fas_sim, show_edges=True)\npl.camera_position = cpos\n\npl.subplot(1, 0)\npl.add_mesh(dec_std, show_edges=True)\npl.add_text(f\"vtkQuadricDecimation\\n{dec_std_time:8.4f} seconds\", \"upper_right\", color=\"k\")\npl.camera_position = cpos\n\npl.subplot(1, 1)\npl.add_mesh(dec_pro, show_edges=True)\npl.add_text(f\"vtkDecimatePro\\n{dec_pro_time:8.4f} seconds\", \"upper_right\", color=\"k\")\npl.camera_position = cpos\n\npl.show()\n"
  },
  {
    "path": "fast_simplification/Replay.h",
    "content": "#include \"Simplify.h\"\n\nnamespace Replay{\n\n    // Global Variables & Structures (same as for Simplify)\n\tenum Attributes {\n\t\tNONE,\n\t\tNORMAL = 2,\n\t\tTEXCOORD = 4,\n\t\tCOLOR = 8\n\t};\n\tstruct Triangle { int v[3];double err[4];int deleted,dirty,attr;vec3f n;vec3f uvs[3];int material; };\n\tstruct Vertex { vec3f p;int tstart,tcount;SymetricMatrix q;int border;};\n\tstruct Ref { int tid,tvertex; };\n\tstd::vector<Triangle> triangles;\n\tstd::vector<Vertex> vertices;\n\tstd::vector<Ref> refs;\n    std::string mtllib;\n    std::vector<std::string> materials;\n    std::vector<std::vector<int>> collapses;\n\n    // Helper functions\n    double vertex_error(SymetricMatrix q, double x, double y, double z);\n\tdouble calculate_error(int id_v1, int id_v2, vec3f &p_result);\n    void initialize_quadrics();\n\n\n    void replay_simplification()\n    {\n    \t// init\n\t\tfor(int i=0; i<vertices.size(); i++)\n        {\n            vertices[i].tcount=1;\n        }\n\n\t\t// main iteration loop\n        int i0,i1;\n        int n_collapses = collapses.size();\n        initialize_quadrics();\n\n        for (int iteration=0; iteration < n_collapses; iteration++)\n        {\n            i0 = collapses[iteration][0];\n            i1 = collapses[iteration][1];\n\n            Vertex &v0 = vertices[i0];\n            Vertex &v1 = vertices[i1];\n\n            // Compute vertex to collapse to\n            vec3f p;\n            calculate_error(i0,i1,p); // p is the optimal point to collapse to\n\n            // not flipped, so remove edge\n            // v0 <- v1 (i0 <- i1)\n            v0.p=p; // set the optimal point to collapse to\n            v0.q=v1.q+v0.q; // add the quadrics (for calculating the error)\n\n            v1.tcount=0; // mark vertex as deleted (will be removed later)\n\n        }\n\n        // remove deleted vertices\n        int dst=0;\n\t\tloopi(0,vertices.size())\n\t\tif(vertices[i].tcount)\n\t\t{\n\t\t\tvertices[i].tstart=dst;\n\t\t\tvertices[dst].p=vertices[i].p;\n\t\t\tdst++;\n\t\t}\n\n\t\tvertices.resize(dst);\n\n    }\n\n\n\n\n\tvoid initialize_quadrics()\n\t{\n\t\t// Init Quadrics by Plane & Edge Errors\n\t\t//\n        loopi(0,vertices.size())\n        vertices[i].q=SymetricMatrix(0.0);\n\n        loopi(0,triangles.size())\n        {\n            Triangle &t=triangles[i];\n            vec3f n,p[3];\n            loopj(0,3) p[j]=vertices[t.v[j]].p;\n            n.cross(p[1]-p[0],p[2]-p[0]);\n            n.normalize();\n            t.n=n;\n            loopj(0,3) vertices[t.v[j]].q =\n                vertices[t.v[j]].q+SymetricMatrix(n.x,n.y,n.z,-n.dot(p[0]));\n        }\n\n\t\t// Init Reference ID list\n\t\tloopi(0,vertices.size())\n\t\t{\n\t\t\tvertices[i].tstart=0;\n\t\t\tvertices[i].tcount=0;\n\t\t}\n\t\tloopi(0,triangles.size())\n\t\t{\n\t\t\tTriangle &t=triangles[i];\n\t\t\tloopj(0,3) vertices[t.v[j]].tcount++;\n\t\t}\n\t\tint tstart=0;\n\t\tloopi(0,vertices.size())\n\t\t{\n\t\t\tVertex &v=vertices[i];\n\t\t\tv.tstart=tstart;\n\t\t\ttstart+=v.tcount;\n\t\t\tv.tcount=0;\n\t\t}\n\n\t\t// Write References\n\t\trefs.resize(triangles.size()*3);\n\t\tloopi(0,triangles.size())\n\t\t{\n\t\t\tTriangle &t=triangles[i];\n\t\t\tloopj(0,3)\n\t\t\t{\n\t\t\t\tVertex &v=vertices[t.v[j]];\n\t\t\t\trefs[v.tstart+v.tcount].tid=i;\n\t\t\t\trefs[v.tstart+v.tcount].tvertex=j;\n\t\t\t\tv.tcount++;\n\t\t\t}\n\t\t}\n\n\t\t// Initialize vertices.borders\n\t\tstd::vector<int> vcount,vids;\n\n\t\tloopi(0,vertices.size())\n\t\t\t\tvertices[i].border=0;\n\n\t\t\tloopi(0,vertices.size())\n\t\t\t{\n\t\t\t\tVertex &v=vertices[i];\n\t\t\t\tvcount.clear();\n\t\t\t\tvids.clear();\n\t\t\t\tloopj(0,v.tcount)\n\t\t\t\t{\n\t\t\t\t\tint k=refs[v.tstart+j].tid;\n\t\t\t\t\tTriangle &t=triangles[k];\n\t\t\t\t\tloopk(0,3)\n\t\t\t\t\t{\n\t\t\t\t\t\tint ofs=0,id=t.v[k];\n\t\t\t\t\t\twhile(ofs<vcount.size())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif(vids[ofs]==id)break;\n\t\t\t\t\t\t\tofs++;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif(ofs==vcount.size())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvcount.push_back(1);\n\t\t\t\t\t\t\tvids.push_back(id);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tvcount[ofs]++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tloopj(0,vcount.size()) if(vcount[j]==1)\n\t\t\t\t\tvertices[vids[j]].border=1;\n\t\t\t}\n\t}\n\n\n\n\t// Error between vertex and Quadric\n\n\tdouble vertex_error(SymetricMatrix q, double x, double y, double z)\n\t{\n \t\treturn   q[0]*x*x + 2*q[1]*x*y + 2*q[2]*x*z + 2*q[3]*x + q[4]*y*y\n \t\t     + 2*q[5]*y*z + 2*q[6]*y + q[7]*z*z + 2*q[8]*z + q[9];\n\t}\n\n\t// Error for one edge\n\n\tdouble calculate_error(int id_v1, int id_v2, vec3f &p_result)\n\t{\n\t\t// compute interpolated vertex\n\n\t\tSymetricMatrix q = vertices[id_v1].q + vertices[id_v2].q;\n\t\tbool   border = vertices[id_v1].border & vertices[id_v2].border;\n\t\tdouble error=0;\n\t\tdouble det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7);\n\t\tif ( det != 0 && !border )\n\t\t{\n\n\t\t\t// q_delta is invertible\n\t\t\tp_result.x = -1/det*(q.det(1, 2, 3, 4, 5, 6, 5, 7 , 8));\t// vx = A41/det(q_delta)\n\t\t\tp_result.y =  1/det*(q.det(0, 2, 3, 1, 5, 6, 2, 7 , 8));\t// vy = A42/det(q_delta)\n\t\t\tp_result.z = -1/det*(q.det(0, 1, 3, 1, 4, 6, 2, 5,  8));\t// vz = A43/det(q_delta)\n\n\t\t\terror = vertex_error(q, p_result.x, p_result.y, p_result.z);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// det = 0 -> try to find best result\n\t\t\tvec3f p1=vertices[id_v1].p;\n\t\t\tvec3f p2=vertices[id_v2].p;\n\t\t\tvec3f p3=(p1+p2)/2;\n\t\t\tdouble error1 = vertex_error(q, p1.x,p1.y,p1.z);\n\t\t\tdouble error2 = vertex_error(q, p2.x,p2.y,p2.z);\n\t\t\tdouble error3 = vertex_error(q, p3.x,p3.y,p3.z);\n\t\t\terror = min(error1, min(error2, error3));\n\t\t\tif (error1 == error) p_result=p1;\n\t\t\tif (error2 == error) p_result=p2;\n\t\t\tif (error3 == error) p_result=p3;\n\t\t}\n\t\treturn error;\n\t}\n\n\tchar *trimwhitespace(char *str)\n\t{\n\t\tchar *end;\n\n\t\t// Trim leading space\n\t\twhile(isspace((unsigned char)*str)) str++;\n\n\t\tif(*str == 0)  // All spaces?\n\t\treturn str;\n\n\t\t// Trim trailing space\n\t\tend = str + strlen(str) - 1;\n\t\twhile(end > str && isspace((unsigned char)*end)) end--;\n\n\t\t// Write new null terminator\n\t\t*(end+1) = 0;\n\n\t\treturn str;\n\t}\n\n\t//Option : Load OBJ\n\tvoid load_obj(const char* filename, bool process_uv=false){\n\t\tvertices.clear();\n\t\ttriangles.clear();\n\t\t// printf ( \"Loading Objects %s ... \\n\",filename);\n\t\tFILE* fn;\n\t\tif(filename==NULL)\t\treturn ;\n\t\tif((char)filename[0]==0)\treturn ;\n\t\tif ((fn = fopen(filename, \"rb\")) == NULL)\n\t\t{\n\t\t\tprintf ( \"File %s not found!\\n\" ,filename );\n\t\t\treturn;\n\t\t}\n\t\tchar line[1000];\n\t\tmemset ( line,0,1000 );\n\t\tint vertex_cnt = 0;\n\t\tint material = -1;\n\t\tstd::map<std::string, int> material_map;\n\t\tstd::vector<vec3f> uvs;\n\t\tstd::vector<std::vector<int> > uvMap;\n\n\t\twhile(fgets( line, 1000, fn ) != NULL)\n\t\t{\n\t\t\tVertex v;\n\t\t\tvec3f uv;\n\n\t\t\tif (strncmp(line, \"mtllib\", 6) == 0)\n\t\t\t{\n\t\t\t\tmtllib = trimwhitespace(&line[7]);\n\t\t\t}\n\t\t\tif (strncmp(line, \"usemtl\", 6) == 0)\n\t\t\t{\n\t\t\t\tstd::string usemtl = trimwhitespace(&line[7]);\n\t\t\t\tif (material_map.find(usemtl) == material_map.end())\n\t\t\t\t{\n\t\t\t\t\tmaterial_map[usemtl] = materials.size();\n\t\t\t\t\tmaterials.push_back(usemtl);\n\t\t\t\t}\n\t\t\t\tmaterial = material_map[usemtl];\n\t\t\t}\n\n\t\t\tif ( line[0] == 'v' && line[1] == 't' )\n\t\t\t{\n\t\t\t\tif ( line[2] == ' ' )\n\t\t\t\tif(sscanf(line,\"vt %lf %lf\",\n\t\t\t\t\t&uv.x,&uv.y)==2)\n\t\t\t\t{\n\t\t\t\t\tuv.z = 0;\n\t\t\t\t\tuvs.push_back(uv);\n\t\t\t\t} else\n\t\t\t\tif(sscanf(line,\"vt %lf %lf %lf\",\n\t\t\t\t\t&uv.x,&uv.y,&uv.z)==3)\n\t\t\t\t{\n\t\t\t\t\tuvs.push_back(uv);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if ( line[0] == 'v' )\n\t\t\t{\n\t\t\t\tif ( line[1] == ' ' )\n\t\t\t\tif(sscanf(line,\"v %lf %lf %lf\",\n\t\t\t\t\t&v.p.x,\t&v.p.y,\t&v.p.z)==3)\n\t\t\t\t{\n\t\t\t\t\tvertices.push_back(v);\n\t\t\t\t}\n\t\t\t}\n\t\t\tint integers[9];\n\t\t\tif ( line[0] == 'f' )\n\t\t\t{\n\t\t\t\tTriangle t;\n\t\t\t\tbool tri_ok = false;\n                bool has_uv = false;\n\n\t\t\t\tif(sscanf(line,\"f %d %d %d\",\n\t\t\t\t\t&integers[0],&integers[1],&integers[2])==3)\n\t\t\t\t{\n\t\t\t\t\ttri_ok = true;\n\t\t\t\t}else\n\t\t\t\tif(sscanf(line,\"f %d// %d// %d//\",\n\t\t\t\t\t&integers[0],&integers[1],&integers[2])==3)\n\t\t\t\t{\n\t\t\t\t\ttri_ok = true;\n\t\t\t\t}else\n\t\t\t\tif(sscanf(line,\"f %d//%d %d//%d %d//%d\",\n\t\t\t\t\t&integers[0],&integers[3],\n\t\t\t\t\t&integers[1],&integers[4],\n\t\t\t\t\t&integers[2],&integers[5])==6)\n\t\t\t\t{\n\t\t\t\t\ttri_ok = true;\n\t\t\t\t}else\n\t\t\t\tif(sscanf(line,\"f %d/%d/%d %d/%d/%d %d/%d/%d\",\n\t\t\t\t\t&integers[0],&integers[6],&integers[3],\n\t\t\t\t\t&integers[1],&integers[7],&integers[4],\n\t\t\t\t\t&integers[2],&integers[8],&integers[5])==9)\n\t\t\t\t{\n\t\t\t\t\ttri_ok = true;\n\t\t\t\t\thas_uv = true;\n\t\t\t\t}else // Add Support for v/vt only meshes\n\t\t\t\tif (sscanf(line, \"f %d/%d %d/%d %d/%d\",\n\t\t\t\t\t&integers[0], &integers[6],\n\t\t\t\t\t&integers[1], &integers[7],\n\t\t\t\t\t&integers[2], &integers[8]) == 6)\n\t\t\t\t{\n\t\t\t\t\ttri_ok = true;\n\t\t\t\t\thas_uv = true;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tprintf(\"unrecognized sequence\\n\");\n\t\t\t\t\tprintf(\"%s\\n\",line);\n\t\t\t\t\twhile(1);\n\t\t\t\t}\n\t\t\t\tif ( tri_ok )\n\t\t\t\t{\n\t\t\t\t\tt.v[0] = integers[0]-1-vertex_cnt;\n\t\t\t\t\tt.v[1] = integers[1]-1-vertex_cnt;\n\t\t\t\t\tt.v[2] = integers[2]-1-vertex_cnt;\n\t\t\t\t\tt.attr = 0;\n\n\t\t\t\t\tif ( process_uv && has_uv )\n\t\t\t\t\t{\n\t\t\t\t\t\tstd::vector<int> indices;\n\t\t\t\t\t\tindices.push_back(integers[6]-1-vertex_cnt);\n\t\t\t\t\t\tindices.push_back(integers[7]-1-vertex_cnt);\n\t\t\t\t\t\tindices.push_back(integers[8]-1-vertex_cnt);\n\t\t\t\t\t\tuvMap.push_back(indices);\n\t\t\t\t\t\tt.attr |= TEXCOORD;\n\t\t\t\t\t}\n\n\t\t\t\t\tt.material = material;\n\t\t\t\t\t//geo.triangles.push_back ( tri );\n\t\t\t\t\ttriangles.push_back(t);\n\t\t\t\t\t//state_before = state;\n\t\t\t\t\t//state ='f';\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ( process_uv && uvs.size() )\n\t\t{\n\t\t\tloopi(0,triangles.size())\n\t\t\t{\n\t\t\t\tloopj(0,3)\n\t\t\t\ttriangles[i].uvs[j] = uvs[uvMap[i][j]];\n\t\t\t}\n\t\t}\n\n\t\tfclose(fn);\n\n\t\t//printf(\"load_obj: vertices = %lu, triangles = %lu, uvs = %lu\\n\", vertices.size(), triangles.size(), uvs.size() );\n\t} // load_obj()\n\n\t// Optional : Store as OBJ\n\n\tvoid write_obj(const char* filename)\n\t{\n\t\tFILE *file=fopen(filename, \"w\");\n\t\tint cur_material = -1;\n\t\tbool has_uv = (triangles.size() && (triangles[0].attr & TEXCOORD) == TEXCOORD);\n\n\t\tif (!file)\n\t\t{\n\t\t\tprintf(\"write_obj: can't write data file \\\"%s\\\".\\n\", filename);\n\t\t\texit(0);\n\t\t}\n\t\tif (!mtllib.empty())\n\t\t{\n\t\t\tfprintf(file, \"mtllib %s\\n\", mtllib.c_str());\n\t\t}\n\t\tloopi(0,vertices.size())\n\t\t{\n\t\t\t//fprintf(file, \"v %lf %lf %lf\\n\", vertices[i].p.x,vertices[i].p.y,vertices[i].p.z);\n\t\t\tfprintf(file, \"v %g %g %g\\n\", vertices[i].p.x,vertices[i].p.y,vertices[i].p.z); //more compact: remove trailing zeros\n\t\t}\n\t\tif (has_uv)\n\t\t{\n\t\t\tloopi(0,triangles.size()) if(!triangles[i].deleted)\n\t\t\t{\n\t\t\t\tfprintf(file, \"vt %g %g\\n\", triangles[i].uvs[0].x, triangles[i].uvs[0].y);\n\t\t\t\tfprintf(file, \"vt %g %g\\n\", triangles[i].uvs[1].x, triangles[i].uvs[1].y);\n\t\t\t\tfprintf(file, \"vt %g %g\\n\", triangles[i].uvs[2].x, triangles[i].uvs[2].y);\n\t\t\t}\n\t\t}\n\t\tint uv = 1;\n\t\tloopi(0,triangles.size()) if(!triangles[i].deleted)\n\t\t{\n\t\t\tif (triangles[i].material != cur_material)\n\t\t\t{\n\t\t\t\tcur_material = triangles[i].material;\n\t\t\t\tfprintf(file, \"usemtl %s\\n\", materials[triangles[i].material].c_str());\n\t\t\t}\n\t\t\tif (has_uv)\n\t\t\t{\n\t\t\t\tfprintf(file, \"f %d/%d %d/%d %d/%d\\n\", triangles[i].v[0]+1, uv, triangles[i].v[1]+1, uv+1, triangles[i].v[2]+1, uv+2);\n\t\t\t\tuv += 3;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfprintf(file, \"f %d %d %d\\n\", triangles[i].v[0]+1, triangles[i].v[1]+1, triangles[i].v[2]+1);\n\t\t\t}\n\t\t\t//fprintf(file, \"f %d// %d// %d//\\n\", triangles[i].v[0]+1, triangles[i].v[1]+1, triangles[i].v[2]+1); //more compact: remove trailing zeros\n\t\t}\n\t\tfclose(file);\n\t}\n\n}"
  },
  {
    "path": "fast_simplification/Simplify.h",
    "content": "/////////////////////////////////////////////\n//\n// Mesh Simplification Tutorial\n//\n// (C) by Sven Forstmann in 2014\n//\n// License : MIT\n// http://opensource.org/licenses/MIT\n//\n//https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification\n//\n// 5/2016: Chris Rorden created minimal version for OSX/Linux/Windows compile\n\n#include <iostream>\n//#include <stddef.h>\n//#include <functional>\n//#include <sys/stat.h>\n//#include <stdbool.h>\n#include <string.h>\n//#include <ctype.h>\n//#include <float.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <map>\n#include <vector>\n#include <utility> // std::pair\n#include <string>\n#include <math.h>\n#include <stdint.h>\n#include <float.h> //FLT_EPSILON, DBL_EPSILON\n\n#define loopi(start_l,end_l) for ( int i=start_l;i<end_l;++i )\n#define loopi(start_l,end_l) for ( int i=start_l;i<end_l;++i )\n#define loopj(start_l,end_l) for ( int j=start_l;j<end_l;++j )\n#define loopk(start_l,end_l) for ( int k=start_l;k<end_l;++k )\n\nstruct vector3\n{\ndouble x, y, z;\n};\n\nstruct vec3f\n{\n    double x, y, z;\n\n    inline vec3f( void ) {}\n\n    //inline vec3f operator =( vector3 a )\n\t// { vec3f b ; b.x = a.x; b.y = a.y; b.z = a.z; return b;}\n\n    inline vec3f( vector3 a )\n\t { x = a.x; y = a.y; z = a.z; }\n\n    inline vec3f( const double X, const double Y, const double Z )\n    { x = X; y = Y; z = Z; }\n\n    inline vec3f operator + ( const vec3f& a ) const\n    { return vec3f( x + a.x, y + a.y, z + a.z ); }\n\n\tinline vec3f operator += ( const vec3f& a ) const\n    { return vec3f( x + a.x, y + a.y, z + a.z ); }\n\n    inline vec3f operator * ( const double a ) const\n    { return vec3f( x * a, y * a, z * a ); }\n\n    inline vec3f operator * ( const vec3f a ) const\n    { return vec3f( x * a.x, y * a.y, z * a.z ); }\n\n    inline vec3f v3 () const\n    { return vec3f( x , y, z ); }\n\n    inline vec3f operator = ( const vector3 a )\n    { x=a.x;y=a.y;z=a.z;return *this; }\n\n    inline vec3f operator = ( const vec3f a )\n    { x=a.x;y=a.y;z=a.z;return *this; }\n\n    inline vec3f operator / ( const vec3f a ) const\n    { return vec3f( x / a.x, y / a.y, z / a.z ); }\n\n    inline vec3f operator - ( const vec3f& a ) const\n    { return vec3f( x - a.x, y - a.y, z - a.z ); }\n\n    inline vec3f operator / ( const double a ) const\n    { return vec3f( x / a, y / a, z / a ); }\n\n    inline double dot( const vec3f& a ) const\n    { return a.x*x + a.y*y + a.z*z; }\n\n    inline vec3f cross( const vec3f& a , const vec3f& b )\n    {\n\t\tx = a.y * b.z - a.z * b.y;\n\t\ty = a.z * b.x - a.x * b.z;\n\t\tz = a.x * b.y - a.y * b.x;\n\t\treturn *this;\n\t}\n\n    inline double angle( const vec3f& v )\n    {\n\t\tvec3f a = v , b = *this;\n\t\tdouble dot = v.x*x + v.y*y + v.z*z;\n\t\tdouble len = a.length() * b.length();\n\t\tif(len==0)len=0.00001f;\n\t\tdouble input = dot  / len;\n\t\tif (input<-1) input=-1;\n\t\tif (input>1) input=1;\n\t\treturn (double) acos ( input );\n\t}\n\n    inline double angle2( const vec3f& v , const vec3f& w )\n    {\n\t\tvec3f a = v , b= *this;\n\t\tdouble dot = a.x*b.x + a.y*b.y + a.z*b.z;\n\t\tdouble len = a.length() * b.length();\n\t\tif(len==0)len=1;\n\n\t\tvec3f plane; plane.cross( b,w );\n\n\t\tif ( plane.x * a.x + plane.y * a.y + plane.z * a.z > 0 )\n\t\t\treturn (double) -acos ( dot  / len );\n\n\t\treturn (double) acos ( dot  / len );\n\t}\n\n    inline vec3f rot_x( double a )\n    {\n\t\tdouble yy = cos ( a ) * y + sin ( a ) * z;\n\t\tdouble zz = cos ( a ) * z - sin ( a ) * y;\n\t\ty = yy; z = zz;\n\t\treturn *this;\n\t}\n    inline vec3f rot_y( double a )\n    {\n\t\tdouble xx = cos ( -a ) * x + sin ( -a ) * z;\n\t\tdouble zz = cos ( -a ) * z - sin ( -a ) * x;\n\t\tx = xx; z = zz;\n\t\treturn *this;\n\t}\n    inline void clamp( double min, double max )\n    {\n\t\tif (x<min) x=min;\n\t\tif (y<min) y=min;\n\t\tif (z<min) z=min;\n\t\tif (x>max) x=max;\n\t\tif (y>max) y=max;\n\t\tif (z>max) z=max;\n\t}\n    inline vec3f rot_z( double a )\n    {\n\t\tdouble yy = cos ( a ) * y + sin ( a ) * x;\n\t\tdouble xx = cos ( a ) * x - sin ( a ) * y;\n\t\ty = yy; x = xx;\n\t\treturn *this;\n\t}\n    inline vec3f invert()\n\t{\n\t\tx=-x;y=-y;z=-z;return *this;\n\t}\n    inline vec3f frac()\n\t{\n\t\treturn vec3f(\n\t\t\tx-double(int(x)),\n\t\t\ty-double(int(y)),\n\t\t\tz-double(int(z))\n\t\t\t);\n\t}\n\n    inline vec3f integer()\n\t{\n\t\treturn vec3f(\n\t\t\tdouble(int(x)),\n\t\t\tdouble(int(y)),\n\t\t\tdouble(int(z))\n\t\t\t);\n\t}\n\n    inline double length() const\n    {\n\t\treturn (double)sqrt(x*x + y*y + z*z);\n\t}\n\n    inline vec3f normalize( double desired_length = 1 )\n    {\n\t\tdouble square = sqrt(x*x + y*y + z*z);\n\t\t/*\n\t\tif (square <= 0.00001f )\n\t\t{\n\t\t\tx=1;y=0;z=0;\n\t\t\treturn *this;\n\t\t}*/\n\t\t//double len = desired_length / square;\n\t\tx/=square;y/=square;z/=square;\n\n\t\treturn *this;\n\t}\n    static vec3f normalize( vec3f a );\n\n\tstatic void random_init();\n\tstatic double random_double();\n\tstatic vec3f random();\n\n\tstatic int random_number;\n\n\tdouble random_double_01(double a){\n\t\tdouble rnf=a*14.434252+a*364.2343+a*4213.45352+a*2341.43255+a*254341.43535+a*223454341.3523534245+23453.423412;\n\t\tint rni=((int)rnf)%100000;\n\t\treturn double(rni)/(100000.0f-1.0f);\n\t}\n\n\tvec3f random01_fxyz(){\n\t\tx=(double)random_double_01(x);\n\t\ty=(double)random_double_01(y);\n\t\tz=(double)random_double_01(z);\n\t\treturn *this;\n\t}\n\n};\n\nvec3f barycentric(const vec3f &p, const vec3f &a, const vec3f &b, const vec3f &c){\n\tvec3f v0 = b-a;\n\tvec3f v1 = c-a;\n\tvec3f v2 = p-a;\n\tdouble d00 = v0.dot(v0);\n\tdouble d01 = v0.dot(v1);\n\tdouble d11 = v1.dot(v1);\n\tdouble d20 = v2.dot(v0);\n\tdouble d21 = v2.dot(v1);\n\tdouble denom = d00*d11-d01*d01;\n\tdouble v = (d11 * d20 - d01 * d21) / denom;\n\tdouble w = (d00 * d21 - d01 * d20) / denom;\n\tdouble u = 1.0 - v - w;\n\treturn vec3f(u,v,w);\n}\n\nvec3f interpolate(const vec3f &p, const vec3f &a, const vec3f &b, const vec3f &c, const vec3f attrs[3])\n{\n\tvec3f bary = barycentric(p,a,b,c);\n\tvec3f out = vec3f(0,0,0);\n\tout = out + attrs[0] * bary.x;\n\tout = out + attrs[1] * bary.y;\n\tout = out + attrs[2] * bary.z;\n\treturn out;\n}\n\ndouble min(double v1, double v2) {\n\treturn fmin(v1,v2);\n}\n\n\nclass SymetricMatrix {\n\n\tpublic:\n\n\t// Constructor\n\n\tSymetricMatrix(double c=0) { loopi(0,10) m[i] = c;  }\n\n\tSymetricMatrix(\tdouble m11, double m12, double m13, double m14,\n\t\t\t            double m22, double m23, double m24,\n\t\t\t                        double m33, double m34,\n\t\t\t                                    double m44) {\n\t\t\t m[0] = m11;  m[1] = m12;  m[2] = m13;  m[3] = m14;\n\t\t\t              m[4] = m22;  m[5] = m23;  m[6] = m24;\n\t\t\t                           m[7] = m33;  m[8] = m34;\n\t\t\t                                        m[9] = m44;\n\t}\n\n\t// Make plane\n\n\tSymetricMatrix(double a,double b,double c,double d)\n\t{\n\t\tm[0] = a*a;  m[1] = a*b;  m[2] = a*c;  m[3] = a*d;\n\t\t             m[4] = b*b;  m[5] = b*c;  m[6] = b*d;\n\t\t                          m[7 ] =c*c; m[8 ] = c*d;\n\t\t                                       m[9 ] = d*d;\n\t}\n\n\tdouble operator[](int c) const { return m[c]; }\n\n\t// Determinant\n\n\tdouble det(\tint a11, int a12, int a13,\n\t\t\t\tint a21, int a22, int a23,\n\t\t\t\tint a31, int a32, int a33)\n\t{\n\t\tdouble det =  m[a11]*m[a22]*m[a33] + m[a13]*m[a21]*m[a32] + m[a12]*m[a23]*m[a31]\n\t\t\t\t\t- m[a13]*m[a22]*m[a31] - m[a11]*m[a23]*m[a32]- m[a12]*m[a21]*m[a33];\n\t\treturn det;\n\t}\n\n\tconst SymetricMatrix operator+(const SymetricMatrix& n) const\n\t{\n\t\treturn SymetricMatrix( m[0]+n[0],   m[1]+n[1],   m[2]+n[2],   m[3]+n[3],\n\t\t\t\t\t\t                    m[4]+n[4],   m[5]+n[5],   m[6]+n[6],\n\t\t\t\t\t\t                                 m[ 7]+n[ 7], m[ 8]+n[8 ],\n\t\t\t\t\t\t                                              m[ 9]+n[9 ]);\n\t}\n\n\tSymetricMatrix& operator+=(const SymetricMatrix& n)\n\t{\n\t\t m[0]+=n[0];   m[1]+=n[1];   m[2]+=n[2];   m[3]+=n[3];\n\t\t m[4]+=n[4];   m[5]+=n[5];   m[6]+=n[6];   m[7]+=n[7];\n\t\t m[8]+=n[8];   m[9]+=n[9];\n\t\treturn *this;\n\t}\n\n\tdouble m[10];\n};\n///////////////////////////////////////////\n\nnamespace Simplify\n{\n\t// Global Variables & Strctures\n\tenum Attributes {\n\t\tNONE,\n\t\tNORMAL = 2,\n\t\tTEXCOORD = 4,\n\t\tCOLOR = 8\n\t};\n\tstruct Triangle { int v[3];double err[4];int deleted,dirty,attr;vec3f n;vec3f uvs[3];int material; };\n\tstruct Vertex { vec3f p;int tstart,tcount;SymetricMatrix q;int border;};\n\tstruct Ref { int tid,tvertex; };\n\tstd::vector<Triangle> triangles;\n\tstd::vector<Vertex> vertices;\n\tstd::vector<Ref> refs;\n    std::string mtllib;\n    std::vector<std::string> materials;\n\n\tstd::vector<std::vector<int>> collapses;\n\n\t// Helper functions\n\n\tdouble vertex_error(SymetricMatrix q, double x, double y, double z);\n\tdouble calculate_error(int id_v1, int id_v2, vec3f &p_result);\n\tbool flipped(vec3f p,int i0,int i1,Vertex &v0,Vertex &v1,std::vector<int> &deleted);\n\tvoid update_uvs(int i0,const Vertex &v,const vec3f &p,std::vector<int> &deleted);\n\tvoid update_triangles(int i0,Vertex &v,std::vector<int> &deleted,int &deleted_triangles);\n\tvoid update_mesh(int iteration);\n\tvoid compact_mesh();\n\t//\n\t// Main simplification function\n\t//\n\t// target_count  : target nr. of triangles\n\t// agressiveness : sharpness to increase the threshold.\n\t//                 5..8 are good numbers\n\t//                 more iterations yield higher quality\n\t//\n\n\tvoid simplify_mesh(int target_count, double agressiveness=7, bool verbose=false)\n\t{\n\n\t\t// init\n\t\tloopi(0,triangles.size())\n        {\n            triangles[i].deleted=0;\n        }\n\n\t\t// main iteration loop\n\t\tint deleted_triangles=0;\n\t\tstd::vector<int> deleted0,deleted1;\n\t\tint triangle_count=triangles.size();\n\t\t//int iteration = 0;\n\t\t//loop(iteration,0,100)\n\t\tcollapses.clear();\n\t\tfor (int iteration = 0; iteration < 100; iteration ++)\n\t\t{\n\n\t\t\tif(triangle_count-deleted_triangles<=target_count)break;\n\n\t\t\t// update mesh once in a while\n\t\t\tif(iteration%5==0)\n\t\t\t{\n\t\t\t\tupdate_mesh(iteration);\n\t\t\t}\n\n\t\t\t// clear dirty flag\n\t\t\tloopi(0,triangles.size()) triangles[i].dirty=0;\n\n\t\t\t//\n\t\t\t// All triangles with edges below the threshold will be removed\n\t\t\t//\n\t\t\t// The following numbers works well for most models.\n\t\t\t// If it does not, try to adjust the 3 parameters\n\t\t\t//\n\t\t\tdouble threshold = 0.000000001*pow(double(iteration+3),agressiveness);\n\n\t\t\t// target number of triangles reached ? Then break\n\t\t\tif ((verbose) && (iteration%5==0)) {\n\t\t\t\tprintf(\"iteration %d - triangles %d threshold %g\\n\",iteration,triangle_count-deleted_triangles, threshold);\n\t\t\t}\n\n\t\t\t// remove vertices & mark deleted triangles\n\t\t\tloopi(0,triangles.size())\n\t\t\t{\n\t\t\t\tTriangle &t=triangles[i];\n\t\t\t\tif(t.err[3]>threshold) continue;\n\t\t\t\tif(t.deleted) continue;\n\t\t\t\tif(t.dirty) continue;\n\n\t\t\t\tloopj(0,3)if(t.err[j]<threshold)\n\t\t\t\t{\n\n\t\t\t\t\tint i0=t.v[ j     ]; Vertex &v0 = vertices[i0];\n\t\t\t\t\tint i1=t.v[(j+1)%3]; Vertex &v1 = vertices[i1];\n\t\t\t\t\t// Border check\n\t\t\t\t\tif(v0.border != v1.border)  continue;\n\n\t\t\t\t\t// Compute vertex to collapse to\n\t\t\t\t\tvec3f p;\n\t\t\t\t\tcalculate_error(i0,i1,p); // p is the optimal point to collapse to\n\t\t\t\t\tdeleted0.resize(v0.tcount); // normals temporarily\n\t\t\t\t\tdeleted1.resize(v1.tcount); // normals temporarily\n\t\t\t\t\t// don't remove if flipped\n\t\t\t\t\tif( flipped(p,i0,i1,v0,v1,deleted0) ) continue;\n\n\t\t\t\t\tif( flipped(p,i1,i0,v1,v0,deleted1) ) continue;\n\n\t\t\t\t\tif ( (t.attr & TEXCOORD) == TEXCOORD  )\n\t\t\t\t\t{\n\t\t\t\t\t\tupdate_uvs(i0,v0,p,deleted0);\n\t\t\t\t\t\tupdate_uvs(i0,v1,p,deleted1);\n\t\t\t\t\t}\n\n\t\t\t\t\t// not flipped, so remove edge\n\t\t\t\t\t// v0 <- v1 (i0 <- i1)\n\t\t\t\t\tv0.p=p; // set the optimal point to collapse to\n\t\t\t\t\tv0.q=v1.q+v0.q; // add the quadrics\n\t\t\t\t\tint tstart=refs.size();\n\n\t\t\t\t\t// update triangles affected by the collapse\n\t\t\t\t\tupdate_triangles(i0,v0,deleted0,deleted_triangles);\n\t\t\t\t\tupdate_triangles(i0,v1,deleted1,deleted_triangles);\n\n\t\t\t\t\t// record collapse\n\t\t\t\t\tcollapses.push_back(std::vector<int>({i0,i1}));\n\n\t\t\t\t\tint tcount=refs.size()-tstart;\n\n\t\t\t\t\tif(tcount<=v0.tcount)\n\t\t\t\t\t{\n\t\t\t\t\t\t// save ram\n\t\t\t\t\t\tif(tcount)memcpy(&refs[v0.tstart],&refs[tstart],tcount*sizeof(Ref));\n\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\t// append\n\t\t\t\t\t\tv0.tstart=tstart;\n\n\t\t\t\t\tv0.tcount=tcount;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// done?\n\t\t\t\tif(triangle_count-deleted_triangles<=target_count)break;\n\t\t\t}\n\t\t}\n\t\t// clean up mesh\n\t\tcompact_mesh();\n\n\t} //simplify_mesh()\n\n\tvoid simplify_mesh_lossless(bool verbose=false)\n\t{\n\t\t// init\n\t\tloopi(0,triangles.size()) triangles[i].deleted=0;\n\n\t\t// main iteration loop\n\t\tint deleted_triangles=0;\n\t\tstd::vector<int> deleted0,deleted1;\n\t\tint triangle_count=triangles.size();\n\t\t//int iteration = 0;\n\t\t//loop(iteration,0,100)\n\t\tcollapses.clear();\n\t\tfor (int iteration = 0; iteration < 9999; iteration ++)\n\t\t{\n\t\t\t// update mesh constantly\n\t\t\tupdate_mesh(iteration);\n\t\t\t// clear dirty flag\n\t\t\tloopi(0,triangles.size()) triangles[i].dirty=0;\n\t\t\t//\n\t\t\t// All triangles with edges below the threshold will be removed\n\t\t\t//\n\t\t\t// The following numbers works well for most models.\n\t\t\t// If it does not, try to adjust the 3 parameters\n\t\t\t//\n\t\t\tdouble threshold = DBL_EPSILON; //1.0E-3 EPS;\n\t\t\tif (verbose) {\n\t\t\t\tprintf(\"lossless iteration %d\\n\", iteration);\n\t\t\t}\n\n\t\t\t// remove vertices & mark deleted triangles\n\t\t\tloopi(0,triangles.size())\n\t\t\t{\n\t\t\t\tTriangle &t=triangles[i];\n\t\t\t\tif(t.err[3]>threshold) continue;\n\t\t\t\tif(t.deleted) continue;\n\t\t\t\tif(t.dirty) continue;\n\n\t\t\t\tloopj(0,3)if(t.err[j]<threshold)\n\t\t\t\t{\n\t\t\t\t\tint i0=t.v[ j     ]; Vertex &v0 = vertices[i0];\n\t\t\t\t\tint i1=t.v[(j+1)%3]; Vertex &v1 = vertices[i1];\n\n\t\t\t\t\t// Border check\n\t\t\t\t\tif(v0.border != v1.border)  continue;\n\n\t\t\t\t\t// Compute vertex to collapse to\n\t\t\t\t\tvec3f p;\n\t\t\t\t\tcalculate_error(i0,i1,p); // p is the optimal point to collapse to\n\n\t\t\t\t\tdeleted0.resize(v0.tcount); // normals temporarily\n\t\t\t\t\tdeleted1.resize(v1.tcount); // normals temporarily\n\n\t\t\t\t\t// don't remove if flipped\n\t\t\t\t\tif( flipped(p,i0,i1,v0,v1,deleted0) ) continue;\n\t\t\t\t\tif( flipped(p,i1,i0,v1,v0,deleted1) ) continue;\n\n\t\t\t\t\tif ( (t.attr & TEXCOORD) == TEXCOORD )\n\t\t\t\t\t{\n\t\t\t\t\t\tupdate_uvs(i0,v0,p,deleted0);\n\t\t\t\t\t\tupdate_uvs(i0,v1,p,deleted1);\n\t\t\t\t\t}\n\n\t\t\t\t\t// not flipped, so remove edge\n\t\t\t\t\t// v0 <- v1 (i0 <- i1)\n\t\t\t\t\tv0.p=p; // set the optimal point to collapse to\n\t\t\t\t\tv0.q=v1.q+v0.q; // add the quadrics (for calculating the error)\n\t\t\t\t\tint tstart=refs.size();\n\n\t\t\t\t\tupdate_triangles(i0,v0,deleted0,deleted_triangles);\n\t\t\t\t\tupdate_triangles(i0,v1,deleted1,deleted_triangles);\n\n\t\t\t\t\t// record collapse\n\t\t\t\t\tcollapses.push_back(std::vector<int>({i0,i1}));\n\n\t\t\t\t\tint tcount=refs.size()-tstart;\n\n\t\t\t\t\tif(tcount<=v0.tcount)\n\t\t\t\t\t{\n\t\t\t\t\t\t// save ram\n\t\t\t\t\t\tif(tcount)memcpy(&refs[v0.tstart],&refs[tstart],tcount*sizeof(Ref));\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\t// append\n\t\t\t\t\t\tv0.tstart=tstart;\n\n\t\t\t\t\tv0.tcount=tcount;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif(deleted_triangles<=0)break;\n\t\t\tdeleted_triangles=0;\n\t\t} //for each iteration\n\t\t// clean up mesh\n\t\tcompact_mesh();\n\t} //simplify_mesh_lossless()\n\n\n\t// Check if a triangle flips when this edge is removed\n\n\tbool flipped(vec3f p,int i0,int i1,Vertex &v0,Vertex &v1,std::vector<int> &deleted)\n\t{\n\n\t\tloopk(0,v0.tcount)\n\t\t{\n\t\t\tTriangle &t=triangles[refs[v0.tstart+k].tid];\n\t\t\tif(t.deleted)continue;\n\n\t\t\tint s=refs[v0.tstart+k].tvertex;\n\t\t\tint id1=t.v[(s+1)%3];\n\t\t\tint id2=t.v[(s+2)%3];\n\n\t\t\tif(id1==i1 || id2==i1) // delete ?\n\t\t\t{\n\n\t\t\t\tdeleted[k]=1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvec3f d1 = vertices[id1].p-p; d1.normalize();\n\t\t\tvec3f d2 = vertices[id2].p-p; d2.normalize();\n\t\t\tif(fabs(d1.dot(d2))>0.999) return true;\n\t\t\tvec3f n;\n\t\t\tn.cross(d1,d2);\n\t\t\tn.normalize();\n\t\t\tdeleted[k]=0;\n\t\t\tif(n.dot(t.n)<0.2) return true;\n\t\t}\n\t\treturn false;\n\t}\n\n    // update_uvs\n\n\tvoid update_uvs(int i0,const Vertex &v,const vec3f &p,std::vector<int> &deleted)\n\t{\n\t\tloopk(0,v.tcount)\n\t\t{\n\t\t\tRef &r=refs[v.tstart+k];\n\t\t\tTriangle &t=triangles[r.tid];\n\t\t\tif(t.deleted)continue;\n\t\t\tif(deleted[k])continue;\n\t\t\tvec3f p1=vertices[t.v[0]].p;\n\t\t\tvec3f p2=vertices[t.v[1]].p;\n\t\t\tvec3f p3=vertices[t.v[2]].p;\n\t\t\tt.uvs[r.tvertex] = interpolate(p,p1,p2,p3,t.uvs);\n\t\t}\n\t}\n\n\t// Update triangle connections and edge error after a edge is collapsed\n\n\tvoid update_triangles(int i0,Vertex &v,std::vector<int> &deleted,int &deleted_triangles)\n\t{\n\t\tvec3f p;\n\t\tloopk(0,v.tcount)\n\t\t{\n\t\t\tRef &r=refs[v.tstart+k];\n\t\t\tTriangle &t=triangles[r.tid];\n\t\t\tif(t.deleted)continue;\n\t\t\tif(deleted[k])\n\t\t\t{\n\t\t\t\tt.deleted=1;\n\t\t\t\tdeleted_triangles++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tt.v[r.tvertex]=i0;\n\t\t\tt.dirty=1;\n\t\t\tt.err[0]=calculate_error(t.v[0],t.v[1],p);\n\t\t\tt.err[1]=calculate_error(t.v[1],t.v[2],p);\n\t\t\tt.err[2]=calculate_error(t.v[2],t.v[0],p);\n\t\t\tt.err[3]=min(t.err[0],min(t.err[1],t.err[2]));\n\t\t\trefs.push_back(r);\n\t\t}\n\t}\n\n\t// compact triangles, compute edge error and build reference list\n\n\tvoid update_mesh(int iteration)\n\t{\n\t\tif(iteration>0) // compact triangles\n\t\t{\n\t\t\tint dst=0;\n\t\t\tloopi(0,triangles.size())\n\t\t\tif(!triangles[i].deleted)\n\t\t\t{\n\t\t\t\ttriangles[dst++]=triangles[i];\n\t\t\t}\n\t\t\ttriangles.resize(dst);\n\t\t}\n\t\t//\n\t\t// Init Quadrics by Plane & Edge Errors\n\t\t//\n\t\t// required at the beginning ( iteration == 0 )\n\t\t// recomputing during the simplification is not required,\n\t\t// but mostly improves the result for closed meshes\n\t\t//\n\t\tif( iteration == 0 )\n\t\t{\n\t\t\tloopi(0,vertices.size())\n\t\t\t\tvertices[i].q=SymetricMatrix(0.0);\n\t\t\tloopi(0,vertices.size())\n\t\t\t\tvertices[i].border=0;\n\n\t\t\tloopi(0,triangles.size())\n\t\t\t{\n\t\t\t\tTriangle &t=triangles[i];\n\t\t\t\tvec3f n,p[3];\n\t\t\t\tloopj(0,3) p[j]=vertices[t.v[j]].p;\n\t\t\t\tn.cross(p[1]-p[0],p[2]-p[0]);\n\t\t\t\tn.normalize();\n\t\t\t\tt.n=n;\n\t\t\t\tloopj(0,3) vertices[t.v[j]].q =\n\t\t\t\t\tvertices[t.v[j]].q+SymetricMatrix(n.x,n.y,n.z,-n.dot(p[0]));\n\t\t\t}\n\t\t\tloopi(0,triangles.size())\n\t\t\t{\n\t\t\t\t// Calc Edge Error\n\t\t\t\tTriangle &t=triangles[i];vec3f p;\n\t\t\t\tloopj(0,3) t.err[j]=calculate_error(t.v[j],t.v[(j+1)%3],p);\n\t\t\t\tt.err[3]=min(t.err[0],min(t.err[1],t.err[2]));\n\t\t\t}\n\t\t}\n\n\t\t// Init Reference ID list\n\t\tloopi(0,vertices.size())\n\t\t{\n\t\t\tvertices[i].tstart=0;\n\t\t\tvertices[i].tcount=0;\n\t\t}\n\t\tloopi(0,triangles.size())\n\t\t{\n\t\t\tTriangle &t=triangles[i];\n\t\t\tloopj(0,3) vertices[t.v[j]].tcount++;\n\t\t}\n\t\tint tstart=0;\n\t\tloopi(0,vertices.size())\n\t\t{\n\t\t\tVertex &v=vertices[i];\n\t\t\tv.tstart=tstart;\n\t\t\ttstart+=v.tcount;\n\t\t\tv.tcount=0;\n\t\t}\n\n\t\t// Write References\n\t\trefs.resize(triangles.size()*3);\n\t\tloopi(0,triangles.size())\n\t\t{\n\t\t\tTriangle &t=triangles[i];\n\t\t\tloopj(0,3)\n\t\t\t{\n\t\t\t\tVertex &v=vertices[t.v[j]];\n\t\t\t\trefs[v.tstart+v.tcount].tid=i;\n\t\t\t\trefs[v.tstart+v.tcount].tvertex=j;\n\t\t\t\tv.tcount++;\n\t\t\t}\n\t\t}\n\n\t\t// Identify boundary : vertices[].border=0,1\n\t\tif( iteration == 0 )\n\t\t{\n\t\t\tstd::vector<int> vcount,vids;\n\n\t\t\tloopi(0,vertices.size())\n\t\t\t\tvertices[i].border=0;\n\n\t\t\tloopi(0,vertices.size())\n\t\t\t{\n\t\t\t\tVertex &v=vertices[i];\n\t\t\t\tvcount.clear();\n\t\t\t\tvids.clear();\n\t\t\t\tloopj(0,v.tcount)\n\t\t\t\t{\n\t\t\t\t\tint k=refs[v.tstart+j].tid;\n\t\t\t\t\tTriangle &t=triangles[k];\n\t\t\t\t\tloopk(0,3)\n\t\t\t\t\t{\n\t\t\t\t\t\tint ofs=0,id=t.v[k];\n\t\t\t\t\t\twhile(ofs<vcount.size())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif(vids[ofs]==id)break;\n\t\t\t\t\t\t\tofs++;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif(ofs==vcount.size())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvcount.push_back(1);\n\t\t\t\t\t\t\tvids.push_back(id);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tvcount[ofs]++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tloopj(0,vcount.size()) if(vcount[j]==1)\n\t\t\t\t\tvertices[vids[j]].border=1;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Finally compact mesh before exiting\n\n\tvoid compact_mesh()\n\t{\n\t\tint dst=0;\n\t\tloopi(0,vertices.size())\n\t\t{\n\t\t\tvertices[i].tcount=0;\n\t\t}\n\t\tloopi(0,triangles.size())\n\t\tif(!triangles[i].deleted)\n\t\t{\n\t\t\tTriangle &t=triangles[i];\n\t\t\ttriangles[dst++]=t;\n\t\t\tloopj(0,3)vertices[t.v[j]].tcount=1;\n\t\t}\n\t\ttriangles.resize(dst);\n\t\tdst=0;\n\t\tloopi(0,vertices.size())\n\t\tif(vertices[i].tcount)\n\t\t{\n\t\t\tvertices[i].tstart=dst;\n\t\t\tvertices[dst].p=vertices[i].p;\n\t\t\tdst++;\n\t\t}\n\t\tloopi(0,triangles.size())\n\t\t{\n\t\t\tTriangle &t=triangles[i];\n\t\t\tloopj(0,3)t.v[j]=vertices[t.v[j]].tstart;\n\t\t}\n\t\tvertices.resize(dst);\n\t}\n\n\t// Error between vertex and Quadric\n\n\tdouble vertex_error(SymetricMatrix q, double x, double y, double z)\n\t{\n \t\treturn   q[0]*x*x + 2*q[1]*x*y + 2*q[2]*x*z + 2*q[3]*x + q[4]*y*y\n \t\t     + 2*q[5]*y*z + 2*q[6]*y + q[7]*z*z + 2*q[8]*z + q[9];\n\t}\n\n\t// Error for one edge\n\n\tdouble calculate_error(int id_v1, int id_v2, vec3f &p_result)\n\t{\n\t\t// compute interpolated vertex\n\n\t\tSymetricMatrix q = vertices[id_v1].q + vertices[id_v2].q;\n\t\tbool   border = vertices[id_v1].border & vertices[id_v2].border;\n\t\tdouble error=0;\n\t\tdouble det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7);\n\t\tif ( det != 0 && !border )\n\t\t{\n\n\t\t\t// q_delta is invertible\n\t\t\tp_result.x = -1/det*(q.det(1, 2, 3, 4, 5, 6, 5, 7 , 8));\t// vx = A41/det(q_delta)\n\t\t\tp_result.y =  1/det*(q.det(0, 2, 3, 1, 5, 6, 2, 7 , 8));\t// vy = A42/det(q_delta)\n\t\t\tp_result.z = -1/det*(q.det(0, 1, 3, 1, 4, 6, 2, 5,  8));\t// vz = A43/det(q_delta)\n\n\t\t\terror = vertex_error(q, p_result.x, p_result.y, p_result.z);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// det = 0 -> try to find best result\n\t\t\tvec3f p1=vertices[id_v1].p;\n\t\t\tvec3f p2=vertices[id_v2].p;\n\t\t\tvec3f p3=(p1+p2)/2;\n\t\t\tdouble error1 = vertex_error(q, p1.x,p1.y,p1.z);\n\t\t\tdouble error2 = vertex_error(q, p2.x,p2.y,p2.z);\n\t\t\tdouble error3 = vertex_error(q, p3.x,p3.y,p3.z);\n\t\t\terror = min(error1, min(error2, error3));\n\t\t\tif (error1 == error) p_result=p1;\n\t\t\tif (error2 == error) p_result=p2;\n\t\t\tif (error3 == error) p_result=p3;\n\t\t}\n\t\treturn error;\n\t}\n\n\tchar *trimwhitespace(char *str)\n\t{\n\t\tchar *end;\n\n\t\t// Trim leading space\n\t\twhile(isspace((unsigned char)*str)) str++;\n\n\t\tif(*str == 0)  // All spaces?\n\t\treturn str;\n\n\t\t// Trim trailing space\n\t\tend = str + strlen(str) - 1;\n\t\twhile(end > str && isspace((unsigned char)*end)) end--;\n\n\t\t// Write new null terminator\n\t\t*(end+1) = 0;\n\n\t\treturn str;\n\t}\n\n\t//Option : Load OBJ\n\tvoid load_obj(const char* filename, bool process_uv=false){\n\t\tvertices.clear();\n\t\ttriangles.clear();\n\t\t// printf ( \"Loading Objects %s ... \\n\",filename);\n\t\tFILE* fn;\n\t\tif(filename==NULL)\t\treturn ;\n\t\tif((char)filename[0]==0)\treturn ;\n\t\tif ((fn = fopen(filename, \"rb\")) == NULL)\n\t\t{\n\t\t\tprintf ( \"File %s not found!\\n\" ,filename );\n\t\t\treturn;\n\t\t}\n\t\tchar line[1000];\n\t\tmemset ( line,0,1000 );\n\t\tint vertex_cnt = 0;\n\t\tint material = -1;\n\t\tstd::map<std::string, int> material_map;\n\t\tstd::vector<vec3f> uvs;\n\t\tstd::vector<std::vector<int> > uvMap;\n\n\t\twhile(fgets( line, 1000, fn ) != NULL)\n\t\t{\n\t\t\tVertex v;\n\t\t\tvec3f uv;\n\n\t\t\tif (strncmp(line, \"mtllib\", 6) == 0)\n\t\t\t{\n\t\t\t\tmtllib = trimwhitespace(&line[7]);\n\t\t\t}\n\t\t\tif (strncmp(line, \"usemtl\", 6) == 0)\n\t\t\t{\n\t\t\t\tstd::string usemtl = trimwhitespace(&line[7]);\n\t\t\t\tif (material_map.find(usemtl) == material_map.end())\n\t\t\t\t{\n\t\t\t\t\tmaterial_map[usemtl] = materials.size();\n\t\t\t\t\tmaterials.push_back(usemtl);\n\t\t\t\t}\n\t\t\t\tmaterial = material_map[usemtl];\n\t\t\t}\n\n\t\t\tif ( line[0] == 'v' && line[1] == 't' )\n\t\t\t{\n\t\t\t\tif ( line[2] == ' ' )\n\t\t\t\tif(sscanf(line,\"vt %lf %lf\",\n\t\t\t\t\t&uv.x,&uv.y)==2)\n\t\t\t\t{\n\t\t\t\t\tuv.z = 0;\n\t\t\t\t\tuvs.push_back(uv);\n\t\t\t\t} else\n\t\t\t\tif(sscanf(line,\"vt %lf %lf %lf\",\n\t\t\t\t\t&uv.x,&uv.y,&uv.z)==3)\n\t\t\t\t{\n\t\t\t\t\tuvs.push_back(uv);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if ( line[0] == 'v' )\n\t\t\t{\n\t\t\t\tif ( line[1] == ' ' )\n\t\t\t\tif(sscanf(line,\"v %lf %lf %lf\",\n\t\t\t\t\t&v.p.x,\t&v.p.y,\t&v.p.z)==3)\n\t\t\t\t{\n\t\t\t\t\tvertices.push_back(v);\n\t\t\t\t}\n\t\t\t}\n\t\t\tint integers[9];\n\t\t\tif ( line[0] == 'f' )\n\t\t\t{\n\t\t\t\tTriangle t;\n\t\t\t\tbool tri_ok = false;\n                bool has_uv = false;\n\n\t\t\t\tif(sscanf(line,\"f %d %d %d\",\n\t\t\t\t\t&integers[0],&integers[1],&integers[2])==3)\n\t\t\t\t{\n\t\t\t\t\ttri_ok = true;\n\t\t\t\t}else\n\t\t\t\tif(sscanf(line,\"f %d// %d// %d//\",\n\t\t\t\t\t&integers[0],&integers[1],&integers[2])==3)\n\t\t\t\t{\n\t\t\t\t\ttri_ok = true;\n\t\t\t\t}else\n\t\t\t\tif(sscanf(line,\"f %d//%d %d//%d %d//%d\",\n\t\t\t\t\t&integers[0],&integers[3],\n\t\t\t\t\t&integers[1],&integers[4],\n\t\t\t\t\t&integers[2],&integers[5])==6)\n\t\t\t\t{\n\t\t\t\t\ttri_ok = true;\n\t\t\t\t}else\n\t\t\t\tif(sscanf(line,\"f %d/%d/%d %d/%d/%d %d/%d/%d\",\n\t\t\t\t\t&integers[0],&integers[6],&integers[3],\n\t\t\t\t\t&integers[1],&integers[7],&integers[4],\n\t\t\t\t\t&integers[2],&integers[8],&integers[5])==9)\n\t\t\t\t{\n\t\t\t\t\ttri_ok = true;\n\t\t\t\t\thas_uv = true;\n\t\t\t\t}else // Add Support for v/vt only meshes\n\t\t\t\tif (sscanf(line, \"f %d/%d %d/%d %d/%d\",\n\t\t\t\t\t&integers[0], &integers[6],\n\t\t\t\t\t&integers[1], &integers[7],\n\t\t\t\t\t&integers[2], &integers[8]) == 6)\n\t\t\t\t{\n\t\t\t\t\ttri_ok = true;\n\t\t\t\t\thas_uv = true;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tprintf(\"unrecognized sequence\\n\");\n\t\t\t\t\tprintf(\"%s\\n\",line);\n\t\t\t\t\twhile(1);\n\t\t\t\t}\n\t\t\t\tif ( tri_ok )\n\t\t\t\t{\n\t\t\t\t\tt.v[0] = integers[0]-1-vertex_cnt;\n\t\t\t\t\tt.v[1] = integers[1]-1-vertex_cnt;\n\t\t\t\t\tt.v[2] = integers[2]-1-vertex_cnt;\n\t\t\t\t\tt.attr = 0;\n\n\t\t\t\t\tif ( process_uv && has_uv )\n\t\t\t\t\t{\n\t\t\t\t\t\tstd::vector<int> indices;\n\t\t\t\t\t\tindices.push_back(integers[6]-1-vertex_cnt);\n\t\t\t\t\t\tindices.push_back(integers[7]-1-vertex_cnt);\n\t\t\t\t\t\tindices.push_back(integers[8]-1-vertex_cnt);\n\t\t\t\t\t\tuvMap.push_back(indices);\n\t\t\t\t\t\tt.attr |= TEXCOORD;\n\t\t\t\t\t}\n\n\t\t\t\t\tt.material = material;\n\t\t\t\t\t//geo.triangles.push_back ( tri );\n\t\t\t\t\ttriangles.push_back(t);\n\t\t\t\t\t//state_before = state;\n\t\t\t\t\t//state ='f';\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ( process_uv && uvs.size() )\n\t\t{\n\t\t\tloopi(0,triangles.size())\n\t\t\t{\n\t\t\t\tloopj(0,3)\n\t\t\t\ttriangles[i].uvs[j] = uvs[uvMap[i][j]];\n\t\t\t}\n\t\t}\n\n\t\tfclose(fn);\n\n\t\t//printf(\"load_obj: vertices = %lu, triangles = %lu, uvs = %lu\\n\", vertices.size(), triangles.size(), uvs.size() );\n\t} // load_obj()\n\n\t// Optional : Store as OBJ\n\n\tvoid write_obj(const char* filename)\n\t{\n\t\tFILE *file=fopen(filename, \"w\");\n\t\tint cur_material = -1;\n\t\tbool has_uv = (triangles.size() && (triangles[0].attr & TEXCOORD) == TEXCOORD);\n\n\t\tif (!file)\n\t\t{\n\t\t\tprintf(\"write_obj: can't write data file \\\"%s\\\".\\n\", filename);\n\t\t\texit(0);\n\t\t}\n\t\tif (!mtllib.empty())\n\t\t{\n\t\t\tfprintf(file, \"mtllib %s\\n\", mtllib.c_str());\n\t\t}\n\t\tloopi(0,vertices.size())\n\t\t{\n\t\t\t//fprintf(file, \"v %lf %lf %lf\\n\", vertices[i].p.x,vertices[i].p.y,vertices[i].p.z);\n\t\t\tfprintf(file, \"v %g %g %g\\n\", vertices[i].p.x,vertices[i].p.y,vertices[i].p.z); //more compact: remove trailing zeros\n\t\t}\n\t\tif (has_uv)\n\t\t{\n\t\t\tloopi(0,triangles.size()) if(!triangles[i].deleted)\n\t\t\t{\n\t\t\t\tfprintf(file, \"vt %g %g\\n\", triangles[i].uvs[0].x, triangles[i].uvs[0].y);\n\t\t\t\tfprintf(file, \"vt %g %g\\n\", triangles[i].uvs[1].x, triangles[i].uvs[1].y);\n\t\t\t\tfprintf(file, \"vt %g %g\\n\", triangles[i].uvs[2].x, triangles[i].uvs[2].y);\n\t\t\t}\n\t\t}\n\t\tint uv = 1;\n\t\tloopi(0,triangles.size()) if(!triangles[i].deleted)\n\t\t{\n\t\t\tif (triangles[i].material != cur_material)\n\t\t\t{\n\t\t\t\tcur_material = triangles[i].material;\n\t\t\t\tfprintf(file, \"usemtl %s\\n\", materials[triangles[i].material].c_str());\n\t\t\t}\n\t\t\tif (has_uv)\n\t\t\t{\n\t\t\t\tfprintf(file, \"f %d/%d %d/%d %d/%d\\n\", triangles[i].v[0]+1, uv, triangles[i].v[1]+1, uv+1, triangles[i].v[2]+1, uv+2);\n\t\t\t\tuv += 3;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfprintf(file, \"f %d %d %d\\n\", triangles[i].v[0]+1, triangles[i].v[1]+1, triangles[i].v[2]+1);\n\t\t\t}\n\t\t\t//fprintf(file, \"f %d// %d// %d//\\n\", triangles[i].v[0]+1, triangles[i].v[1]+1, triangles[i].v[2]+1); //more compact: remove trailing zeros\n\t\t}\n\t\tfclose(file);\n\t}\n};\n///////////////////////////////////////////\n"
  },
  {
    "path": "fast_simplification/__init__.py",
    "content": "from ._version import __version__  # noqa: F401\nfrom .replay import _map_isolated_points, replay_simplification  # noqa: F401\nfrom .simplify import simplify, simplify_mesh  # noqa: F401\n"
  },
  {
    "path": "fast_simplification/_replay.pyx",
    "content": "# cython: language_level=3\n# cython: boundscheck=False\n# cython: wraparound=False\n# cython: cdivision=True\n\n\nimport numpy as np\n\ncimport numpy as np\nfrom libc.stdint cimport int64_t\nfrom libcpp cimport bool\n\n\ncdef extern from \"wrapper_replay.h\" namespace \"Replay\":\n    void load_arrays_int32(const int, const int, const int, float*, int*, int*)\n    void load_arrays_int64(const int, const int, const int,  float*, int64_t*, int*)\n    void replay_simplification()\n    void get_points(float*)\n    void get_triangles(int*)\n    void get_collapses(int*)\n    int get_faces_int32(int*)\n    int get_faces_int32_no_padding(int*)\n    int get_faces_int64(int64_t*)\n    void write_obj(const char*)\n    void load_obj(const char*, bool)\n    int n_points()\n    int n_triangles()\n    int n_collapses()\n    int load_triangles_from_vtk(const int, int*)\n    void load_points(const int, float*)\n    void load_collapses(const int, int*)\n\ndef load_int32(int n_points, int n_faces, int n_collapses, float [:, ::1] points, int [:, ::1] faces, int [:, ::1] collapses):\n    load_arrays_int32(n_points, n_faces, n_collapses, &points[0, 0], &faces[0, 0], &collapses[0, 0])\n\n\ndef load_int64(\n        int n_points, int n_faces, int n_collapses, float [:, ::1] points, int64_t [:, ::1] faces, int [:, ::1] collapses\n):\n    load_arrays_int64(n_points, n_faces, n_collapses, &points[0, 0], &faces[0, 0], &collapses[0, 0])\n\n\n# def simplify(int target_count, double aggressiveness=7, bool verbose=False):\n#     simplify_mesh(target_count, aggressiveness, verbose)\n\ndef replay():\n    replay_simplification()\n\n\ndef save_obj(filename):\n    py_byte_string = filename.encode('UTF-8')\n    cdef char* c_filename = py_byte_string\n    write_obj(c_filename)\n\n\ndef read(filename):\n    py_byte_string = filename.encode('UTF-8')\n    cdef char* c_filename = py_byte_string\n    load_obj(c_filename, False)\n\n\ndef return_points():\n    cdef float [:, ::1] points = np.empty((n_points(), 3), np.float32)\n    get_points(&points[0, 0])\n    return np.array(points)\n\n\ndef return_triangles():\n    cdef int [:, ::1] triangles = np.empty((n_triangles(), 3), np.int32)\n    get_triangles(&triangles[0, 0])\n    return np.array(triangles)\n\ndef return_collapses():\n    cdef int [:, ::1] collapses = np.empty((n_collapses(), 2), np.int32)\n    get_collapses(&collapses[0, 0])\n    return np.array(collapses)\n\n\ndef return_faces_int32_no_padding():\n    \"\"\"VTK formatted faces\"\"\"\n    cdef int [::1] faces = np.empty(n_triangles()*3, np.int32)\n    n_tri = get_faces_int32_no_padding(&faces[0])\n    return np.array(faces[:n_tri*3])\n\n\ndef return_faces_int32():\n    \"\"\"VTK formatted faces\"\"\"\n    cdef int [::1] faces = np.empty(n_triangles()*4, np.int32)\n    n_tri = get_faces_int32(&faces[0])\n    return np.array(faces[:n_tri*4])\n\n\ndef return_faces_int64():\n    \"\"\"VTK formatted faces\"\"\"\n    cdef int64_t [::1] faces = np.empty(n_triangles()*4, np.int64)\n    n_tri = get_faces_int64(&faces[0])\n    return np.array(faces[:n_tri*4])\n\n\ndef load_from_vtk(int n_points, float [:, ::1] points, int [::1] faces, int n_faces):\n    result = load_triangles_from_vtk(n_faces, &faces[0])\n    if result:\n        raise ValueError(\n            \"Input mesh ``mesh`` must consist of only triangles.\\n\"\n            \"Run ``.triangulate()`` to convert to an all triangle mesh.\"\n        )\n    load_points(n_points, &points[0, 0])\n\n\ndef compute_indice_mapping(int[:, :] collapses, int n_points):\n\n    ''' Compute the mapping from original indices to new indices after collapsing\n        edges\n\n        (pure python implementation with numpy)\n    '''\n\n    # start with identity mapping\n    indice_mapping = np.arange(n_points, dtype=int)\n\n    # First round of mapping\n    origin_indices = collapses[:, 1]\n    indice_mapping[origin_indices] = collapses[:, 0]\n    previous = np.zeros(len(indice_mapping))\n    while not np.array_equal(previous, indice_mapping):\n        previous = indice_mapping.copy()\n        indice_mapping[origin_indices] = indice_mapping[\n            indice_mapping[origin_indices]\n        ]\n\n    keep = np.setdiff1d(\n        np.arange(n_points), collapses[:, 1]\n    )  # Indices of the points that must be kept after decimation\n\n    cdef int i = 0\n    cdef int j = 0\n\n    cdef int[:] application = np.zeros(n_points, dtype=np.int32)\n    for i in range(n_points):\n        if j == len(keep):\n            break\n        if i == keep[j]:\n            application[i] = j\n            j += 1\n\n    indice_mapping = np.array(application)[indice_mapping]\n\n    return indice_mapping\n\n\ndef clean_triangles_and_edges(int[:, :] mapped_triangles, bool clean_edges=False):\n    \"\"\"Return the edges and triangles of a mesh from mapped triangles\n\n    Args:\n        mapped_triangles (np.ndarray): Mapped triangles\n        clean_edges (bool, optional): If True, remove duplicated edges.\n\n    Returns:\n        np.ndarray: Edges\n        np.ndarray: Triangles\n    \"\"\"\n\n    cdef int i, j, k, l\n    cdef int n_edges = 0\n    cdef int n_triangles = 0\n    cdef int N = len(mapped_triangles)\n    cdef int[:, :] edges_with_rep = np.zeros((N, 2), dtype=np.int32)\n    cdef int[:, :] triangles = np.zeros((N, 3), dtype=np.int32)\n\n    for i in range(N):\n        j = mapped_triangles[i, 0]\n        k = mapped_triangles[i, 1]\n        l = mapped_triangles[i, 2]\n\n        if j != k and j != l and k != l:\n            triangles[n_triangles, 0] = j\n            triangles[n_triangles, 1] = k\n            triangles[n_triangles, 2] = l\n            n_triangles += 1\n\n        elif j != k:\n            # j, k = np.sort([j, k])\n            edges_with_rep[n_edges, 0] = j\n            edges_with_rep[n_edges, 1] = k\n            n_edges += 1\n\n        elif j != l:\n            # j, l = np.sort([j, l])\n            edges_with_rep[n_edges, 0] = j\n            edges_with_rep[n_edges, 1] = l\n            n_edges += 1\n\n        elif l != k:\n            # l, k = np.sort([j, k])\n            edges_with_rep[n_edges, 0] = l\n            edges_with_rep[n_edges, 1] = k\n            n_edges += 1\n\n    if not clean_edges:\n\n        return np.asarray(edges_with_rep)[:n_edges, :], np.asarray(triangles)[:n_triangles, :]\n\n\n    cdef int[:, :] edges = np.zeros((n_edges, 2), dtype=np.int32)\n\n\n    # Lexicographic sort\n    cdef int[:] order = np.lexsort((np.asarray(edges_with_rep[:n_edges, 1]), np.asarray(edges_with_rep[:n_edges, 0])))\n    # Remove duplicates\n    cdef int n_keep_edges = 1\n    edges[0, :] = edges_with_rep[order[0], :]\n    print(f\"n_edges : {n_edges}\")\n    for i in range(1, n_edges):\n        if (edges_with_rep[order[i], 0] != edges_with_rep[order[i - 1], 0]) or (edges_with_rep[order[i], 1] != edges_with_rep[order[i - 1], 1]):\n            edges[n_keep_edges, :] = edges_with_rep[order[i], :]\n            n_keep_edges += 1\n\n\n    return np.asarray(edges)[:n_keep_edges, :], np.asarray(triangles)[:n_triangles, :]\n"
  },
  {
    "path": "fast_simplification/_simplify.pyx",
    "content": "# cython: language_level=3\n# cython: boundscheck=False\n# cython: wraparound=False\n# cython: cdivision=True\n\n\nimport numpy as np\n\ncimport numpy as np\nfrom libc.stdint cimport int64_t\nfrom libcpp cimport bool\n\n\ncdef extern from \"wrapper.h\" namespace \"Simplify\":\n    void load_arrays_int32(const int, const int, double*, int*)\n    void load_arrays_int64(const int, const int, double*, int64_t*)\n    void simplify_mesh(int, double aggressiveness, bool verbose)\n    void simplify_mesh_lossless(bool)\n    void get_points(double*)\n    void get_triangles(int*)\n    void get_collapses(int*)\n    int get_faces_int32(int*)\n    int get_faces_int32_no_padding(int*)\n    int get_faces_int64(int64_t*)\n    void write_obj(const char*)\n    void load_obj(const char*, bool)\n    int n_points()\n    int n_triangles()\n    int n_collapses()\n    int load_triangles_from_vtk(const int, int*)\n    void load_points(const int, double*)\n\n\n\ndef load_int32(int n_points, int n_faces, double [:, ::1] points, int [:, ::1] faces):\n    load_arrays_int32(n_points, n_faces, &points[0, 0], &faces[0, 0])\n\n\ndef load_int64(\n        int n_points, int n_faces, double [:, ::1] points, int64_t [:, ::1] faces\n):\n    load_arrays_int64(n_points, n_faces, &points[0, 0], &faces[0, 0])\n\n\ndef simplify(int target_count, double aggressiveness=7, bool verbose=False):\n    simplify_mesh(target_count, aggressiveness, verbose)\n\ndef simplify_lossless(bool verbose=False):\n    simplify_mesh_lossless(verbose)\n\n\ndef save_obj(filename):\n    py_byte_string = filename.encode('UTF-8')\n    cdef char* c_filename = py_byte_string\n    write_obj(c_filename)\n\n\ndef read(filename):\n    py_byte_string = filename.encode('UTF-8')\n    cdef char* c_filename = py_byte_string\n    load_obj(c_filename, False)\n\n\ndef return_points():\n    cdef double [:, ::1] points = np.empty((n_points(), 3), np.float64)\n    get_points(&points[0, 0])\n    return np.array(points)\n\n\ndef return_triangles():\n    cdef int [:, ::1] triangles = np.empty((n_triangles(), 3), np.int32)\n    get_triangles(&triangles[0, 0])\n    return np.array(triangles)\n\ndef return_collapses():\n    cdef int [:, ::1] collapses = np.empty((n_collapses(), 2), np.int32)\n    get_collapses(&collapses[0, 0])\n    return np.array(collapses)\n\n\ndef return_faces_int32_no_padding():\n    \"\"\"VTK formatted faces\"\"\"\n    cdef int [::1] faces = np.empty(n_triangles()*3, np.int32)\n    n_tri = get_faces_int32_no_padding(&faces[0])\n    return np.array(faces[:n_tri*3])\n\n\ndef return_faces_int32():\n    \"\"\"VTK formatted faces\"\"\"\n    cdef int [::1] faces = np.empty(n_triangles()*4, np.int32)\n    n_tri = get_faces_int32(&faces[0])\n    return np.array(faces[:n_tri*4])\n\n\ndef return_faces_int64():\n    \"\"\"VTK formatted faces\"\"\"\n    cdef int64_t [::1] faces = np.empty(n_triangles()*4, np.int64)\n    n_tri = get_faces_int64(&faces[0])\n    return np.array(faces[:n_tri*4])\n\n\ndef load_from_vtk(int n_points, double [:, ::1] points, int [::1] faces, int n_faces):\n    result = load_triangles_from_vtk(n_faces, &faces[0])\n    if result:\n        raise ValueError(\n            \"Input mesh ``mesh`` must consist of only triangles.\\n\"\n            \"Run ``.triangulate()`` to convert to an all triangle mesh.\"\n        )\n    load_points(n_points, &points[0, 0])\n"
  },
  {
    "path": "fast_simplification/_version.py",
    "content": "\"\"\"fast_simplification version\n\nOn the ``main`` branch, use 'dev0' to denote a development version.\nFor example:\n\nversion_info = 0, 27, 'dev0'\n\n\"\"\"\n\nversion_info = 0, 2, \"dev0\"\n__version__ = \".\".join(map(str, version_info))\n"
  },
  {
    "path": "fast_simplification/fast_simplification.py",
    "content": "def simplify():\n    pass\n"
  },
  {
    "path": "fast_simplification/replay.py",
    "content": "import numpy as np\n\nfrom . import _replay\nfrom .utils import ascontiguous\n\n\ndef _map_isolated_points(points, edges, triangles, return_outliers=False):\n    r\"\"\"Map the isolated points to the triangles.\n\n    (points, edges, triangles) represents a structure. The goal of this function\n    is to compute a mapping array such that the points that are not in the triangles\n    but are in the edges are merged into the points that are in the triangles, with\n    respect to the edges. An example is given below.\n\n          (1)\n         / | \\\\\n      (0)  |  (2)-3\n         \\ | /  \\\\\n          (4)    6-9\n           |\n           5     8-7\n\n    In this example, the points 5, 3, 4, 7, 8, 9 are not connected to any triangle.\n    The expected mapping is:\n\n    0 -> 0\n    1 -> 1\n    2 -> 2\n    3 -> 2\n    4 -> 4\n    5 -> 4\n    6 -> 2\n    7 -> 7 (7 cannot be merged into any point in the triangles)\n    8 -> 8 (8 cannot be merged into any point in the triangles)\n    9 -> 2\n\n    The output will be the mapping array and the merged points array. In this example,\n    the mapping array is [0, 1, 2, 2, 4, 4, 2, 7, 8, 2] and the merged points array is\n    [3, 5, 6, 9]. The points 7 and 8 are outliers. If return_outliers is True,\n    the function will return the mapping array, the merged points array and the\n    isolated points array. Else, the function will return the mapping array and the\n    merged points array.\n\n    Parameters\n    ----------\n        points : sequence\n            array of points\n        edges : sequence\n            array of edges\n        triangles : sequence\n            array of triangles\n        return_outsider : bool\n            if True, return the outliers\n\n    Returns\n    -------\n        np.ndarray\n            mapping array\n        np.ndarray\n            merged points array\n    \"\"\"\n    n_points = points.shape[0]\n\n    # The points to connect are the points that are not in the triangles\n    # but are in the edges\n    points_to_connect = np.intersect1d(\n        np.setdiff1d(np.arange(n_points), np.unique(triangles)), np.unique(edges)\n    )\n    # Start with the identity mapping\n    mapping = np.arange(n_points, dtype=np.int64)\n\n    # Remove edges that do not contains points to connect\n    edges = edges[np.isin(edges, points_to_connect).any(axis=1)]\n\n    n_edges = edges.shape[0]\n    n_edges_old = 0\n\n    # Iterate until there is no more edges to collapse\n    # or until a statiionary state is reached\n    while n_edges > 0 and n_edges != n_edges_old:\n        n_edges_old = n_edges\n\n        # Edges that connect two points to connect\n        # are kept for the next iteration\n        keep = np.isin(edges, points_to_connect).all(axis=1)\n\n        # Edges that connect a point to connect to a point\n        # that is not to connect are merged\n        connexions = edges[~keep]\n\n        a = np.isin(connexions, points_to_connect)\n        merged = connexions[np.where(a)]\n        target = connexions[np.where(~a)]\n\n        # Update the mapping array and the points to connect\n        mapping[merged] = mapping[target]\n        points_to_connect = np.setdiff1d(points_to_connect, merged)\n\n        # Remove the edges that are merged\n        edges = edges[keep]\n        # Remove edges that do not contains points to connect\n        edges = edges[np.isin(edges, points_to_connect).any(axis=1)]\n        n_edges = edges.shape[0]\n\n    # The points that have been merged are the ones\n    # such that mapping[i] != i\n    merged_points = np.where(mapping != np.arange(len(mapping)))[0]\n\n    if return_outliers:\n        isolated_points = points_to_connect\n        return mapping, merged_points, isolated_points\n    return mapping, merged_points\n\n\n@ascontiguous\ndef replay_simplification(points, triangles, collapses):\n    \"\"\"Replay the decimation of a triangular mesh.\n\n    Parameters\n    ----------\n    points : sequence\n        A ``(n, 3)`` array of points. May be a ``numpy.ndarray`` or a\n        list of points. For efficiency, provide points as a float32\n        array.\n    triangles : sequence\n        A ``(n, 3)`` array of triangle indices. May be a\n        ``numpy.ndarray`` or a list of triangle indices. For\n        efficiency, provide points as a float32 array.\n    collapses : sequence\n        The collapses to replay.\n        A ``(n, 2)`` numpy.ndarray of collapses.\n        ``collapses[i] = [i0, i1]`` means that during the i-th\n        collapse, the vertex ``i1`` was collapsed into the vertex\n        ``i0``.\n\n    Returns\n    -------\n    np.ndarray\n        Points array.\n    np.ndarray\n        Triangles array.\n    np.ndarray\n        indice_mapping array.\n        A ``(n,)`` array of indices.\n        ``indice_mapping[i] = j`` means that the vertex ``i`` of\n        the original mesh was mapped to the vertex ``j`` of the\n        decimated mesh.\n\n    \"\"\"\n    import numpy as np\n\n    if not isinstance(points, np.ndarray):\n        points = np.array(points, dtype=np.float32)\n    if not isinstance(triangles, np.ndarray):\n        triangles = np.array(triangles, dtype=np.int32)\n\n    if points.ndim != 2:\n        raise ValueError(\"``points`` array must be 2 dimensional\")\n    if points.shape[1] != 3:\n        raise ValueError(f\"Expected ``points`` array to be (n, 3), not {points.shape}\")\n\n    if triangles.ndim != 2:\n        raise ValueError(\"``triangles`` array must be 2 dimensional\")\n    if triangles.shape[1] != 3:\n        raise ValueError(f\"Expected ``triangles`` array to be (n, 3), not {triangles.shape}\")\n\n    if not triangles.flags.c_contiguous:\n        triangles = np.ascontiguousarray(triangles)\n\n    if triangles.dtype == np.int32:\n        load = _replay.load_int32\n    elif triangles.dtype == np.int64:\n        load = _replay.load_int64\n    else:\n        load = _replay.load_int32\n        triangles = triangles.astype(np.int32)\n\n    # Collapse the points\n    n_faces = triangles.shape[0]\n    n_points = points.shape[0]\n    load(n_points, n_faces, collapses.shape[0], points, triangles, collapses)\n    _replay.replay()\n    dec_points = _replay.return_points()\n\n    # Compute the indice mapping\n    indice_mapping = _replay.compute_indice_mapping(collapses, len(points))\n\n    # compute the new triangles\n    # Apply the indice mapping to the triangles\n    mapped_triangles = indice_mapping[triangles.copy()]\n\n    # Extract the edges and the triangles\n    # Edges can be repeated, but this is not a problem\n    # and it is faster to do so\n    dec_edges, dec_triangles = _replay.clean_triangles_and_edges(mapped_triangles)\n\n    # Map the isolated points to the triangles\n    mapping, points_to_merge, outliers = _map_isolated_points(\n        dec_points, dec_edges, dec_triangles, return_outliers=True\n    )\n\n    dec_triangles = mapping[dec_triangles]\n    indice_mapping = mapping[indice_mapping]\n\n    points_to_merge = np.union1d(points_to_merge, outliers)\n    # Remove the isolated points\n    # isolated_points = new_collapses[:, 1]\n    points_to_merge = np.sort(points_to_merge)[::-1]\n    mapping = np.arange(dec_points.shape[0])\n    for ip in points_to_merge:\n        dec_points = np.delete(dec_points, ip, axis=0)\n        mapping[ip:] -= 1\n    indice_mapping = mapping[indice_mapping]\n    dec_triangles = mapping[dec_triangles]\n\n    return dec_points, dec_triangles, indice_mapping\n"
  },
  {
    "path": "fast_simplification/simplify.py",
    "content": "\"\"\"Simplification library.\"\"\"\n\nfrom typing import TYPE_CHECKING\n\nimport numpy as np\nfrom numpy.typing import NDArray\n\nfrom . import _simplify\nfrom .utils import ascontiguous\n\nif TYPE_CHECKING:\n    try:\n        from pyvista.core.pointset import PolyData\n    except ModuleNotFoundError:\n        pass\n\n\ndef _check_args(target_reduction, target_count, n_faces):\n    \"\"\"Check arguments.\"\"\"\n    if target_reduction and target_count:\n        raise ValueError(\"You may specify ``target_reduction`` or ``target_count``, but not both\")\n    if target_reduction is None and target_count is None:\n        raise ValueError(\"You must specify ``target_reduction`` or ``target_count``\")\n\n    if target_reduction is not None:\n        if target_reduction > 1 or target_reduction < 0:\n            raise ValueError(\"``target_reduction`` must be between 0 and 1\")\n        target_count = (1 - target_reduction) * n_faces\n\n    if target_count < 0:\n        raise ValueError(\"``target_count`` must be greater than 0\")\n    if target_count > n_faces:\n        raise ValueError(f\"``target_count`` must be less than the number of faces {n_faces}\")\n    return int(target_count)\n\n\n@ascontiguous\ndef simplify(\n    points: NDArray[np.float64],\n    triangles: NDArray[np.int32],\n    target_reduction: float | None = None,\n    target_count: int | None = None,\n    agg: float = 7.0,\n    verbose: bool = False,\n    return_collapses: bool = False,\n    lossless: bool = False,\n) -> (\n    tuple[NDArray[np.float64], NDArray[np.int64]]\n    | tuple[NDArray[np.float64], NDArray[np.int64], NDArray[np.int64]]\n):\n    \"\"\"Simplify a triangular mesh.\n\n    Parameters\n    ----------\n    points : sequence[float | double]\n        A ``(n, 3)`` array of points. May be a ``numpy.ndarray`` or a\n        sequence of points. Internally converted to double precision.\n    triangles : sequence\n        A ``(n, 3)`` array of triangle indices. May be a\n        ``numpy.ndarray`` or a list of triangle indices.\n    target_reduction : float, optional\n        Fraction of the original mesh to remove.  If set to ``0.9``,\n        this function will try to reduce the data set to 10% of its\n        original size and will remove 90% of the input triangles. Use\n        this parameter or ``target_count``.\n    target_count : int, optional\n        Target number of triangles to reduce mesh to.  This may be\n        used in place of ``target_reduction``, but both cannot be set.\n    agg : float, default: 7.0\n        Controls how aggressively to decimate the mesh.  A value of 10 will\n        result in a fast decimation at the expense of mesh quality and shape.\n        A value of 0 will attempt to preserve the original mesh geometry at the\n        expense of time.  Setting a low value may result in being unable to\n        reach the ``target_reduction`` or ``target_count``.\n    verbose : bool, optional\n        Enable verbose output when simplifying the mesh.\n    return_collapses : bool, optional\n        If True, return the history of collapses as a ``(n_collapses, 2)``\n        array of indices.  ``collapses[i] = [i0, i1]`` means that durint the\n        i-th collapse, the vertex ``i1`` was collapsed into the vertex ``i0``.\n    lossless : bool, optional\n        If True, simplify the mesh losslessly.\n\n    Returns\n    -------\n    np.ndarray\n        Points array.\n    np.ndarray\n        Triangles array.\n    np.ndarray (optional)\n        Collapses array.\n\n    Examples\n    --------\n    This basic example demonstrates how to decimate a simple planar\n    mesh composed by 8 triangles.\n\n    >>> import fast_simplification\n    >>> points = [\n    ...     [0.5, -0.5, 0.0],\n    ...     [0.0, -0.5, 0.0],\n    ...     [-0.5, -0.5, 0.0],\n    ...     [0.5, 0.0, 0.0],\n    ...     [0.0, 0.0, 0.0],\n    ...     [-0.5, 0.0, 0.0],\n    ...     [0.5, 0.5, 0.0],\n    ...     [0.0, 0.5, 0.0],\n    ...     [-0.5, 0.5, 0.0],\n    ... ]\n    >>> faces = [\n    ...     [0, 1, 3],\n    ...     [4, 3, 1],\n    ...     [1, 2, 4],\n    ...     [5, 4, 2],\n    ...     [3, 4, 6],\n    ...     [7, 6, 4],\n    ...     [4, 5, 7],\n    ...     [8, 7, 5],\n    ... ]\n    >>> points_out, faces_out = fast_simplification.simplify(points, faces, 0.5)\n\n    \"\"\"\n\n    points = np.asarray(points, dtype=np.float64)\n    if not isinstance(triangles, np.ndarray):\n        triangles = np.array(triangles, dtype=np.int32)\n\n    if points.ndim != 2:\n        raise ValueError(\"``points`` array must be 2 dimensional\")\n    if points.shape[1] != 3:\n        raise ValueError(f\"Expected ``points`` array to be (n, 3), not {points.shape}\")\n\n    if triangles.ndim != 2:\n        raise ValueError(\"``triangles`` array must be 2 dimensional\")\n    if triangles.shape[1] != 3:\n        raise ValueError(f\"Expected ``triangles`` array to be (n, 3), not {triangles.shape}\")\n\n    n_faces = triangles.shape[0]\n\n    triangles = np.ascontiguousarray(triangles)\n\n    if triangles.dtype == np.int32:\n        load = _simplify.load_int32\n    elif triangles.dtype == np.int64:\n        load = _simplify.load_int64\n    else:\n        load = _simplify.load_int32\n        triangles = triangles.astype(np.int32)\n\n    load(\n        points.shape[0],\n        n_faces,\n        points,\n        triangles,\n    )\n\n    if lossless:\n        _simplify.simplify_lossless(verbose)\n    else:\n        target_count = _check_args(target_reduction, target_count, n_faces)\n        _simplify.simplify(target_count, agg, verbose)\n    points = _simplify.return_points()\n    faces = _simplify.return_faces_int32_no_padding().reshape(-1, 3)\n\n    if return_collapses:\n        return points, faces, _simplify.return_collapses()\n    return points, faces\n\n\ndef simplify_mesh(\n    mesh: \"PolyData\",\n    target_reduction: float | None = None,\n    target_count: int | None = None,\n    agg: float = 7.0,\n    verbose: bool = False,\n):\n    \"\"\"Simplify a pyvista mesh.\n\n    Parameters\n    ----------\n    mesh : pyvista.PolyData\n        PyVista mesh.\n    target_reduction : float\n        Fraction of the original mesh to remove. If set to ``0.9``,\n        this function will try to reduce the data set to 10% of its\n        original size and will remove 90% of the input triangles. Use\n        this parameter or ``target_count``.\n    target_count : int, optional\n        Target number of triangles to reduce mesh to. This may be used in\n        place of ``target_reduction``, but both cannot be set.\n    agg : float, default: 7.0\n        Controls how aggressively to decimate the mesh. A value of ``10.0`` will\n        result in a fast decimation at the expense of mesh quality and shape.\n        A value of ``0.0`` will attempt to preserve the original mesh geometry at the\n        expense of time. Setting a low value may result in being unable to\n        reach the ``target_reduction`` or ``target_count``.\n    verbose : bool, optional\n        Enable verbose output when simplifying the mesh.\n\n    Returns\n    -------\n    pyvista.PolyData\n        Simplified mesh. The field data of the mesh will contain a\n        field named ``fast_simplification_collapses`` that contains\n        the history of collapses as a ``(n_collapses, 2)`` array of\n        indices. ``collapses[i] = [i0, i1]`` means that during the\n        i-th collapse, the vertex ``i1`` was collapsed into the vertex\n        ``i0``.\n\n    \"\"\"\n    try:\n        import pyvista as pv\n    except ImportError:\n        raise ImportError(\"Please install pyvista to use this feature with:\\npip install pyvista\")\n\n    n_faces = mesh.n_cells\n    _simplify.load_from_vtk(\n        mesh.n_points,\n        mesh.points.astype(np.float64, order=\"C\", copy=False),\n        mesh.faces.astype(np.int32, order=\"C\", copy=False),\n        n_faces,\n    )\n\n    target_count = _check_args(target_reduction, target_count, n_faces)\n    _simplify.simplify(target_count, agg, verbose)\n\n    # return the correct datatype of the faces\n    if pv._get_vtk_id_type() == np.int32:\n        faces = _simplify.return_faces_int32()\n    else:\n        faces = _simplify.return_faces_int64()\n\n    # construct mesh\n    mesh = pv.PolyData(_simplify.return_points(), faces, deep=False)\n    mesh.field_data[\"fast_simplification_collapses\"] = _simplify.return_collapses()\n\n    return mesh\n"
  },
  {
    "path": "fast_simplification/utils.py",
    "content": "\"\"\"Utility functions for the fast_simplification package.\"\"\"\n\nimport numpy as np\n\n\ndef ascontiguous(func):\n    \"\"\"A decorator that ensure that all the numpy arrays passed to the function\n    are contiguous in memory and if not, apply np.ascontinguous arrays.\n    \"\"\"\n\n    def wrapper(*args, **kwargs):\n        args = list(args)\n        for i, arg in enumerate(args):\n            if isinstance(arg, np.ndarray):\n                args[i] = np.ascontiguousarray(arg)\n\n        for key, value in kwargs.items():\n            if isinstance(value, np.ndarray):\n                kwargs[key] = np.ascontiguousarray(value)\n\n        return func(*args, **kwargs)\n\n    # Copy annotations\n    wrapper.__annotations__ = func.__annotations__\n    return wrapper\n"
  },
  {
    "path": "fast_simplification/wrapper.h",
    "content": "// wrap simplify header file for integration with cython\n#include \"Simplify.h\"\n\nnamespace Simplify{\n\n  // load triangles\n  void load_points(const int n_points, double* points){\n    vertices.clear();\n    // load vertices\n    for (int ii = 0; ii < n_points; ii ++){\n      Vertex v;\n      v.p.x = points[0 + 3*ii];\n      v.p.y = points[1 + 3*ii];\n      v.p.z = points[2 + 3*ii];\n      vertices.push_back(v);\n    }\n  }\n\n  // load triangles\n  void load_triangles(const int n_tri, int* faces){\n    triangles.clear();\n    for (int ii = 0; ii < n_tri; ii ++){\n      Triangle t;\n      t.attr = 0;\n      t.material = -1;\n      t.v[0] = faces[0 + 3*ii];\n      t.v[1] = faces[1 + 3*ii];\n      t.v[2] = faces[2 + 3*ii];\n      triangles.push_back(t);\n    }\n  }\n\n  // load triangles\n  void load_triangles_int64(const int n_tri, int64_t* faces){\n    triangles.clear();\n    for (int ii = 0; ii < n_tri; ii ++){\n      Triangle t;\n      t.attr = 0;\n      t.material = -1;\n      t.v[0] = faces[0 + 3*ii];\n      t.v[1] = faces[1 + 3*ii];\n      t.v[2] = faces[2 + 3*ii];\n      triangles.push_back(t);\n    }\n  }\n\n  // load triangles from vtk and deal with padding\n  int load_triangles_from_vtk(const int n_tri, int* faces){\n    triangles.clear();\n    for (int ii = 0; ii < n_tri; ii ++){\n      Triangle t;\n      t.attr = 0;\n      t.material = -1;\n      if (faces[4*ii] != 3){\n        return 1;\n      }\n      t.v[0] = faces[1 + 4*ii];\n      t.v[1] = faces[2 + 4*ii];\n      t.v[2] = faces[3 + 4*ii];\n      triangles.push_back(t);\n    }\n    return 0;\n  }\n\n  void load_arrays_int32(const int n_points, const int n_tri,\n                         double* points, int* faces){\n    load_points(n_points, points);\n    load_triangles(n_tri, faces);\n  }\n\n  void load_arrays_int64(const int n_points, const int n_tri,\n                         double* points, int64_t* faces){\n    load_points(n_points, points);\n    load_triangles_int64(n_tri, faces);\n  }\n\n  int n_points(){\n    return vertices.size();\n  }\n\n  int n_triangles(){\n    return triangles.size();\n  }\n\n  int n_collapses(){\n    return collapses.size();\n  }\n\n  // load triangles\n  void load_triangles(const int n_tri, int64_t* faces){\n    triangles.clear();\n    for (int ii = 0; ii < n_tri; ii ++){\n      Triangle t;\n      t.attr = 0;\n      t.material = -1;\n      t.v[0] = faces[0 + 3*ii];\n      t.v[1] = faces[1 + 3*ii];\n      t.v[2] = faces[2 + 3*ii];\n      triangles.push_back(t);\n    }\n  }\n\n  // populate a contiguous array with the points in the vertices vector\n  void get_points(double* points){\n\n    // load vertices\n    int n_points = vertices.size();\n    for (int ii = 0; ii < n_points; ii ++){\n      points[0 + 3*ii] = vertices[ii].p.x;\n      points[1 + 3*ii] = vertices[ii].p.y;\n      points[2 + 3*ii] = vertices[ii].p.z;\n    }\n  }\n\n  // populate a contiguous array with the points in the vertices vector\n  void get_triangles(int* tri){\n\n    // load vertices\n    int n_tri = triangles.size();\n    for (int ii = 0; ii < n_tri; ii ++){\n      tri[0 + 3*ii] = triangles[ii].v[0];\n      tri[1 + 3*ii] = triangles[ii].v[1];\n      tri[2 + 3*ii] = triangles[ii].v[2];\n    }\n  }\n\n  void get_collapses(int* coll){\n\n    // load vertices\n    int n_collapse = collapses.size();\n    for (int ii = 0; ii < n_collapse; ii ++){\n      coll[0 + 2*ii] = collapses.at(ii).at(0);\n      coll[1 + 2*ii] = collapses.at(ii).at(1);\n    }\n  }\n\n  // populate a contiguous array with the points in the vertices vector\n  int get_faces_int32(int32_t* tri){\n\n    // load vertices\n    int n_tri = triangles.size();\n    int jj = 0;\n    for (int ii = 0; ii < n_tri; ii ++){\n      if (!triangles[ii].deleted){\n        tri[0 + 3*jj] = 3;\n        tri[1 + 3*jj] = triangles[ii].v[0];\n        tri[2 + 3*jj] = triangles[ii].v[1];\n        tri[3 + 3*jj] = triangles[ii].v[2];\n        jj += 1;\n      }\n    }\n    return jj;\n  }\n\n  // populate a contiguous array with the points in the vertices\n  // vector without the vtk padding\n  int get_faces_int32_no_padding(int32_t* tri){\n\n    // load vertices\n    int n_tri = triangles.size();\n    int jj = 0;\n    for (int ii = 0; ii < n_tri; ii ++){\n      if (!triangles[ii].deleted){\n        tri[0 + 3*jj] = triangles[ii].v[0];\n        tri[1 + 3*jj] = triangles[ii].v[1];\n        tri[2 + 3*jj] = triangles[ii].v[2];\n        jj += 1;\n      }\n    }\n    return jj;\n  }\n\n  // populate a contiguous array with the points in the vertices vector\n  int get_faces_int64(int64_t* tri){\n\n    // load vertices\n    int n_tri = triangles.size();\n    int jj = 0;\n    for (int ii = 0; ii < n_tri; ii ++){\n      if (!triangles[ii].deleted){\n        tri[0 + 4*jj] = 3;\n        tri[1 + 4*jj] = triangles[ii].v[0];\n        tri[2 + 4*jj] = triangles[ii].v[1];\n        tri[3 + 4*jj] = triangles[ii].v[2];\n        jj += 1;\n      }\n    }\n    return jj;\n  }\n}\n"
  },
  {
    "path": "fast_simplification/wrapper_replay.h",
    "content": "// wrap simplify header file for integration with cython\n#include \"Replay.h\"\n\nnamespace Replay{\n\n  // load collapses\n  void load_collapses(const int n_coll, int* coll){\n    collapses.clear();\n    for (int ii = 0; ii < n_coll; ii ++){\n      std::vector<int> c;\n      c.push_back(coll[0 + 2*ii]);\n      c.push_back(coll[1 + 2*ii]);\n      collapses.push_back(c);\n    }\n  }\n\n  // load points\n  void load_points(const int n_points, float* points){\n    vertices.clear();\n    // load vertices\n    for (int ii = 0; ii < n_points; ii ++){\n      Vertex v;\n      v.p.x = points[0 + 3*ii];\n      v.p.y = points[1 + 3*ii];\n      v.p.z = points[2 + 3*ii];\n      vertices.push_back(v);\n    }\n  }\n\n  // load triangles\n  void load_triangles(const int n_tri, int* faces){\n    triangles.clear();\n    for (int ii = 0; ii < n_tri; ii ++){\n      Triangle t;\n      t.attr = 0;\n      t.material = -1;\n      t.v[0] = faces[0 + 3*ii];\n      t.v[1] = faces[1 + 3*ii];\n      t.v[2] = faces[2 + 3*ii];\n      triangles.push_back(t);\n    }\n  }\n\n  // load triangles\n  void load_triangles_int64(const int n_tri, int64_t* faces){\n    triangles.clear();\n    for (int ii = 0; ii < n_tri; ii ++){\n      Triangle t;\n      t.attr = 0;\n      t.material = -1;\n      t.v[0] = faces[0 + 3*ii];\n      t.v[1] = faces[1 + 3*ii];\n      t.v[2] = faces[2 + 3*ii];\n      triangles.push_back(t);\n    }\n  }\n\n  // load triangles from vtk and deal with padding\n  int load_triangles_from_vtk(const int n_tri, int* faces){\n    triangles.clear();\n    for (int ii = 0; ii < n_tri; ii ++){\n      Triangle t;\n      t.attr = 0;\n      t.material = -1;\n      if (faces[4*ii] != 3){\n        return 1;\n      }\n      t.v[0] = faces[1 + 4*ii];\n      t.v[1] = faces[2 + 4*ii];\n      t.v[2] = faces[3 + 4*ii];\n      triangles.push_back(t);\n    }\n    return 0;\n  }\n\n  void load_arrays_int32(const int n_points, const int n_tri, const int n_coll,\n                         float* points, int* faces, int* collapses){\n    load_points(n_points, points);\n    load_triangles(n_tri, faces);\n    load_collapses(n_coll, collapses);\n  }\n\n  void load_arrays_int64(const int n_points, const int n_tri, const int n_coll,\n                         float* points, int64_t* faces, int* collapses){\n    load_points(n_points, points);\n    load_triangles_int64(n_tri, faces);\n    load_collapses(n_coll, collapses);\n  }\n\n  int n_points(){\n    return vertices.size();\n  }\n\n  int n_triangles(){\n    return triangles.size();\n  }\n\n  int n_collapses(){\n    return collapses.size();\n  }\n\n  // load triangles\n  void load_triangles(const int n_tri, int64_t* faces){\n    triangles.clear();\n    for (int ii = 0; ii < n_tri; ii ++){\n      Triangle t;\n      t.attr = 0;\n      t.material = -1;\n      t.v[0] = faces[0 + 3*ii];\n      t.v[1] = faces[1 + 3*ii];\n      t.v[2] = faces[2 + 3*ii];\n      triangles.push_back(t);\n    }\n  }\n\n  // populate a contiguous array with the points in the vertices vector\n  void get_points(float* points){\n\n    // load vertices\n    int n_points = vertices.size();\n    for (int ii = 0; ii < n_points; ii ++){\n      points[0 + 3*ii] = vertices[ii].p.x;\n      points[1 + 3*ii] = vertices[ii].p.y;\n      points[2 + 3*ii] = vertices[ii].p.z;\n    }\n  }\n\n  // populate a contiguous array with the points in the vertices vector\n  void get_triangles(int* tri){\n\n    // load vertices\n    int n_tri = triangles.size();\n    for (int ii = 0; ii < n_tri; ii ++){\n      tri[0 + 3*ii] = triangles[ii].v[0];\n      tri[1 + 3*ii] = triangles[ii].v[1];\n      tri[2 + 3*ii] = triangles[ii].v[2];\n    }\n  }\n\n  void get_collapses(int* coll){\n\n    // load vertices\n    int n_collapse = collapses.size();\n    for (int ii = 0; ii < n_collapse; ii ++){\n      coll[0 + 2*ii] = collapses.at(ii).at(0);\n      coll[1 + 2*ii] = collapses.at(ii).at(1);\n    }\n  }\n\n  // populate a contiguous array with the points in the vertices vector\n  int get_faces_int32(int32_t* tri){\n\n    // load vertices\n    int n_tri = triangles.size();\n    int jj = 0;\n    for (int ii = 0; ii < n_tri; ii ++){\n      if (!triangles[ii].deleted){\n        tri[0 + 3*jj] = 3;\n        tri[1 + 3*jj] = triangles[ii].v[0];\n        tri[2 + 3*jj] = triangles[ii].v[1];\n        tri[3 + 3*jj] = triangles[ii].v[2];\n        jj += 1;\n      }\n    }\n    return jj;\n  }\n\n  // populate a contiguous array with the points in the vertices\n  // vector without the vtk padding\n  int get_faces_int32_no_padding(int32_t* tri){\n\n    // load vertices\n    int n_tri = triangles.size();\n    int jj = 0;\n    for (int ii = 0; ii < n_tri; ii ++){\n      if (!triangles[ii].deleted){\n        tri[0 + 3*jj] = triangles[ii].v[0];\n        tri[1 + 3*jj] = triangles[ii].v[1];\n        tri[2 + 3*jj] = triangles[ii].v[2];\n        jj += 1;\n      }\n    }\n    return jj;\n  }\n\n  // populate a contiguous array with the points in the vertices vector\n  int get_faces_int64(int64_t* tri){\n\n    // load vertices\n    int n_tri = triangles.size();\n    int jj = 0;\n    for (int ii = 0; ii < n_tri; ii ++){\n      if (!triangles[ii].deleted){\n        tri[0 + 4*jj] = 3;\n        tri[1 + 4*jj] = triangles[ii].v[0];\n        tri[2 + 4*jj] = triangles[ii].v[1];\n        tri[3 + 4*jj] = triangles[ii].v[2];\n        jj += 1;\n      }\n    }\n    return jj;\n  }\n}\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nbuild-backend = \"setuptools.build_meta\"\nrequires = [\n  \"cython>=3.0.0\",\n  \"numpy>=2,<3\",\n  \"setuptools>=45.0\",\n  \"wheel>=0.37.0\"\n]\n\n[tool.cibuildwheel]\narchs = [\"auto64\"]  # 64-bit only\nbefore-build = \"pip install abi3audit\"\nbuild = \"cp310-* cp311-*\"  # 3.11+ are abi3 wheels\nskip = \"*musllinux*\"\ntest-command = \"pytest {project}/tests\"\ntest-requires = \"pyvista pytest\"\n\n[tool.cibuildwheel.linux]\nrepair-wheel-command = [\n  \"auditwheel repair -w {dest_dir} {wheel}\",\n  \"bash tools/audit_wheel.sh {wheel}\"\n]\n\n[tool.cibuildwheel.macos]\narchs = [\"native\"]\nrepair-wheel-command = [\n  \"delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}\",\n  \"bash tools/audit_wheel.sh {wheel}\"\n]\n\n[tool.cibuildwheel.windows]\nbefore-build = \"pip install delvewheel abi3audit\"\nrepair-wheel-command = [\n  \"delvewheel repair -w {dest_dir} {wheel}\",\n  \"bash tools/audit_wheel.sh {wheel}\"\n]\n\n[tool.codespell]\nignore-words-list = 'THIRDPARTY'\nquiet-level = 3\nskip = '*.pyc,*.txt,*.gif,*.png,*.jpg,*.js,*.html,*.doctree,*.ttf,*.woff,*.woff2,*.eot,*.mp4,*.inv,*.pickle,*.ipynb,flycheck*,./.git/*,./.hypothesis/*,*.yml,./doc/build/*,./doc/images/*,./dist/*,*~,.hypothesis*,./doc/source/examples/*,*cover,*.dat,*.mac,build,fast_simplification/Simplify.h,PKG-INFO,*.mypy_cache/*,./docker/mapdl/*,./_unused/*'\n\n[tool.isort]\ndefault_section = \"THIRDPARTY\"\nforce_sort_within_sections = true\nline_length = 100\nprofile = \"black\"\nskip_glob = [\"__init__.py\"]\nsrc_paths = [\"doc\", \"fast_simplification\", \"tests\"]\n\n[tool.pytest.ini_options]\nfilterwarnings = [\n  # bogus numpy ABI warning (see numpy/#432)\n  \"ignore:.*numpy.dtype size changed.*:RuntimeWarning\",\n  \"ignore:.*numpy.ufunc size changed.*:RuntimeWarning\",\n  \"ignore:.*Distutils was imported before Setuptools*\"\n]\njunit_family = \"legacy\"\n\n[tool.ruff]\nline-length = 100\n\n[tool.ruff.lint]\nignore = []\n# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`)  codes by default.\n# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or\n# McCabe complexity (`C901`) by default.\nselect = [\"E4\", \"E7\", \"E9\", \"F\"]\n"
  },
  {
    "path": "pytest.ini",
    "content": "[pytest]\njunit_family=legacy\nfilterwarnings =\n    ignore::FutureWarning\n    ignore::PendingDeprecationWarning\n    ignore::DeprecationWarning\n    # bogus numpy ABI warning (see numpy/#432)\n    ignore:.*numpy.dtype size changed.*:RuntimeWarning\n    ignore:.*numpy.ufunc size changed.*:RuntimeWarning\n    ignore:.*Given trait value dtype \"float64\":UserWarning\ndoctest_optionflags = NUMBER ELLIPSIS\n"
  },
  {
    "path": "requirements_docs.txt",
    "content": "numpydoc>=1.8.0\npydata-sphinx-theme\npyvista\nSphinx>=4.0.0\nsphinx-copybutton\nsphinx-gallery>=0.8.1\nsphinx-notfound-page>=0.3.0\n"
  },
  {
    "path": "requirements_test.txt",
    "content": "pytest\npyvista\n"
  },
  {
    "path": "setup.py",
    "content": "\"\"\"Setup for fast-simplification.\"\"\"\n\nimport builtins\nfrom io import open as io_open\nimport os\nimport platform\nimport sys\n\nfrom setuptools import Extension, setup\nfrom setuptools.command.build_ext import build_ext as _build_ext\nfrom wheel.bdist_wheel import bdist_wheel\n\nfilepath = os.path.dirname(__file__)\n\n# Define macros for cython\nmacros = []\next_kwargs = {}\nsetup_kwargs = {\"cmdclass\": {}}\nif os.name == \"nt\":  # windows\n    extra_compile_args = [\"/openmp\", \"/O2\", \"/w\", \"/GS\"]\nelif os.name == \"posix\":  # linux org mac os\n    if sys.platform == \"linux\":\n        extra_compile_args = [\"-std=gnu++11\", \"-O3\", \"-w\"]\n    else:  # probably mac os\n        extra_compile_args = [\"-std=c++11\", \"-O3\", \"-w\"]\nelse:\n    raise OSError(\"Unsupported OS %s\" % os.name)\n\n\n# Check if 64-bit\nif sys.maxsize > 2**32:\n    macros.append((\"IS64BITPLATFORM\", None))\n\n\n# https://github.com/joerick/python-abi3-package-sample/blob/main/setup.py\nclass bdist_wheel_abi3(bdist_wheel):  # noqa: D101\n    def get_tag(self):  # noqa: D102\n        python, abi, plat = super().get_tag()\n\n        if python.startswith(\"cp\"):\n            return \"cp311\", \"abi3\", plat\n\n        return python, abi, plat\n\n\nif sys.version_info.minor >= 11 and platform.python_implementation() == \"CPython\":\n    # Can create an abi3 wheel (typed memoryviews first available in 3.11)!\n    macros.append((\"Py_LIMITED_API\", \"0x030B0000\"))\n    ext_kwargs[\"py_limited_api\"] = True\n    setup_kwargs[\"cmdclass\"][\"bdist_wheel\"] = bdist_wheel_abi3\n\n\n# Get version from version info\n__version__ = None\nversion_file = os.path.join(filepath, \"fast_simplification\", \"_version.py\")\nwith io_open(version_file, mode=\"r\") as fd:\n    exec(fd.read())\n\n# readme file\nreadme_file = os.path.join(filepath, \"README.rst\")\n\n\n# for: the cc1plus: warning: command line option '-Wstrict-prototypes'\nclass build_ext(_build_ext):\n    def finalize_options(self):\n        _build_ext.finalize_options(self)\n        # prevent numpy from thinking it is still in its setup process:\n        try:\n            del builtins.__NUMPY_SETUP__\n        except AttributeError:\n            pass\n        import numpy\n\n        self.include_dirs.append(numpy.get_include())\n\n    def build_extensions(self):\n        try:\n            self.compiler.compiler_so.remove(\"-Wstrict-prototypes\")\n        except (AttributeError, ValueError):\n            pass\n        _build_ext.build_extensions(self)\n\n\nsetup_kwargs[\"cmdclass\"][\"build_ext\"] = build_ext\n\nsetup(\n    name=\"fast_simplification\",\n    packages=[\"fast_simplification\"],\n    version=__version__,\n    description=\"Wrapper around the Fast-Quadric-Mesh-Simplification library.\",\n    long_description=open(readme_file).read(),\n    long_description_content_type=\"text/x-rst\",\n    author=\"Alex Kaszynski\",\n    author_email=\"akascap@gmail.com\",\n    license=\"MIT\",\n    classifiers=[\n        \"Development Status :: 4 - Beta\",\n        \"Intended Audience :: Science/Research\",\n        \"License :: OSI Approved :: MIT License\",\n        \"Programming Language :: Python :: 3.10\",\n        \"Programming Language :: Python :: 3.11\",\n        \"Programming Language :: Python :: 3.12\",\n        \"Programming Language :: Python :: 3.13\",\n        \"Programming Language :: Python :: 3.14\",\n    ],\n    url=\"https://github.com/pyvista/fast-simplification\",\n    python_requires=\">=3.9\",\n    # Build cython modules\n    ext_modules=[\n        Extension(\n            \"fast_simplification._simplify\",\n            [\"fast_simplification/_simplify.pyx\"],\n            language=\"c++\",\n            extra_compile_args=extra_compile_args,\n            define_macros=macros,\n            **ext_kwargs,\n        ),\n        Extension(\n            \"fast_simplification._replay\",\n            [\"fast_simplification/_replay.pyx\"],\n            language=\"c++\",\n            extra_compile_args=extra_compile_args,\n            define_macros=macros,\n            **ext_kwargs,\n        ),\n    ],\n    keywords=\"fast-simplification decimation\",\n    install_requires=[\"numpy\"],\n    **setup_kwargs,\n)\n"
  },
  {
    "path": "tests/test_map_isolated_points.py",
    "content": "import numpy as np\n\nfrom fast_simplification import _map_isolated_points as map_isolated_points\n\n\ndef test_map_isolated_points():\n    # Example 1\n    #\n    #      (1)\n    #     / | \\\n    #  (0)  |  (2)-3\n    #     \\ | /  \\\n    #      (4)    6-9\n    #       |\n    #       5     8-7\n\n    points = np.random.rand(10, 3)\n\n    edges = np.array(\n        [\n            [0, 1],\n            [0, 4],\n            [1, 4],\n            [1, 2],\n            [2, 4],\n            [2, 3],\n            [2, 6],\n            [6, 9],\n            [4, 5],\n            [8, 7],\n        ],\n        dtype=np.int64,\n    )\n\n    triangles = np.array(\n        [\n            [0, 1, 4],\n            [1, 2, 4],\n        ],\n        dtype=np.int64,\n    )\n\n    target_mapping = np.array([0, 1, 2, 2, 4, 4, 2, 7, 8, 2], dtype=np.int64)\n\n    target_merged_points = np.array([3, 5, 6, 9], dtype=np.int64)\n\n    mapping, merged_points = map_isolated_points(points, edges, triangles)\n    assert np.allclose(mapping, target_mapping)\n    assert np.allclose(merged_points, target_merged_points)\n\n    # Example 2\n    #\n    # (7)-(8)       3\n    #   \\  |\n    #    \\ |\n    #     (0)-1-2-9-4-5-6\n\n    points = np.random.rand(10, 3)\n\n    edges = np.array(\n        [\n            [0, 1],\n            [1, 2],\n            [2, 9],\n            [9, 4],\n            [4, 5],\n            [5, 6],\n        ],\n        dtype=np.int64,\n    )\n\n    triangles = np.array(\n        [\n            [0, 7, 8],\n        ]\n    )\n\n    target_mapping = np.array([0, 0, 0, 3, 0, 0, 0, 7, 8, 0], dtype=np.int64)\n\n    target_merged_points = np.array([1, 2, 4, 5, 6, 9], dtype=np.int64)\n\n    mapping, merged_points = map_isolated_points(points, edges, triangles)\n    assert np.allclose(mapping, target_mapping)\n    assert np.allclose(merged_points, target_merged_points)\n\n    # Example 3\n    #\n    # (1)\n    #  | \\\n    #  |  \\\n    # (2)-(0)-4-6\n    #         | |\n    #         3-5\n\n    points = np.random.rand(7, 3)\n\n    edges = np.array([[0, 1], [1, 2], [2, 0], [0, 4], [4, 6], [5, 6], [3, 5]], dtype=np.int64)\n\n    triangles = np.array(\n        [\n            [0, 1, 2],\n        ],\n        dtype=np.int64,\n    )\n\n    target_mapping = np.array([0, 1, 2, 0, 0, 0, 0], dtype=np.int64)\n\n    target_merged_points = np.array([3, 4, 5, 6], dtype=np.int64)\n\n    mapping, merged_points = map_isolated_points(points, edges, triangles)\n    assert np.allclose(mapping, target_mapping)\n    assert np.allclose(merged_points, target_merged_points)\n\n    ## Example 4\n    #\n    # (6)           (7)-(8)\n    #  | \\           |  /\n    #  |  \\          | /\n    # (5)-(0)-1-2-3-(4)\n    #\n    # Here the situation is ambiguous. Does 2 merge into 0 or 4 ?\n    # We consider 2 -> 4 and 2 -> 0 as valid solutions.\n\n    points = np.random.rand(9, 3)\n\n    edges = np.array(\n        [\n            [0, 1],\n            [1, 2],\n            [2, 3],\n            [3, 4],\n        ],\n        dtype=np.int64,\n    )\n\n    triangles = np.array([[0, 5, 6], [4, 7, 8]], dtype=np.int64)\n\n    target_mapping1 = np.array([0, 0, 0, 4, 4, 5, 6, 7, 8], dtype=np.int64)\n\n    target_mapping2 = np.array([0, 0, 4, 4, 4, 5, 6, 7, 8], dtype=np.int64)\n\n    target_merged_points = np.array([1, 2, 3], dtype=np.int64)\n\n    mapping, merged_points = map_isolated_points(points, edges, triangles)\n    assert np.allclose(mapping, target_mapping1) or np.allclose(mapping, target_mapping2)\n    assert np.allclose(merged_points, target_merged_points)\n\n    ## Example 5\n    #\n    # (1)            (7)-(8)-9\n    #  | \\            |  /\n    #  |  \\           | /\n    # (2)-(3)-0  4-5-(6)\n\n    points = np.random.rand(10, 3)\n    edges = np.array(\n        [\n            [0, 3],\n            [1, 3],\n            [2, 3],\n            [1, 2],\n            [4, 5],\n            [5, 6],\n            [8, 9],\n        ],\n        dtype=np.int64,\n    )\n    triangles = np.array(\n        [\n            [1, 2, 3],\n            [6, 7, 8],\n        ],\n        dtype=np.int64,\n    )\n\n    target_mapping = np.array([3, 1, 2, 3, 6, 6, 6, 7, 8, 8], dtype=np.int64)\n\n    target_merged_points = np.array([0, 4, 5, 9], dtype=np.int64)\n\n    mapping, merged_points = map_isolated_points(points, edges, triangles)\n    assert np.allclose(mapping, target_mapping)\n    assert np.allclose(merged_points, target_merged_points)\n\n    ## Example 6\n    #\n    # 0-1-2\n\n    points = np.random.rand(3, 3)\n\n    edges = np.array(\n        [\n            [0, 1],\n            [1, 2],\n        ],\n        dtype=np.int64,\n    )\n\n    triangles = np.array([[]], dtype=np.int64)\n\n    target_mapping = np.array([0, 1, 2], dtype=np.int64)\n\n    target_merged_points = np.array([], dtype=np.int64)\n\n    mapping, merged_points = map_isolated_points(points, edges, triangles)\n    assert np.allclose(mapping, target_mapping)\n    assert np.allclose(merged_points, target_merged_points)\n"
  },
  {
    "path": "tests/test_replay.py",
    "content": "import numpy as np\nimport pytest\n\nimport fast_simplification\n\ntry:\n    import pyvista as pv\n\n    has_vtk = True\nexcept ModuleNotFoundError:\n    has_vtk = False\nskip_no_vtk = pytest.mark.skipif(not has_vtk, reason=\"Requires VTK\")\n\n\n@pytest.fixture\ndef mesh():\n    return pv.Sphere()\n\n\ndef test_collapses_trivial():\n    # arrays from:\n    # mesh = pv.Plane(i_resolution=2, j_resolution=2).triangulate()\n    points = [\n        [0.5, -0.5, 0.0],\n        [0.0, -0.5, 0.0],\n        [-0.5, -0.5, 0.0],\n        [0.5, 0.0, 0.0],\n        [0.0, 0.0, 0.0],\n        [-0.5, 0.0, 0.0],\n        [0.5, 0.5, 0.0],\n        [0.0, 0.5, 0.0],\n        [-0.5, 0.5, 0.0],\n    ]\n\n    faces = [\n        [0, 1, 3],\n        [4, 3, 1],\n        [1, 2, 4],\n        [5, 4, 2],\n        [3, 4, 6],\n        [7, 6, 4],\n        [4, 5, 7],\n        [8, 7, 5],\n    ]\n\n    with pytest.raises(ValueError, match=\"You must specify\"):\n        fast_simplification.simplify(points, faces)\n\n    points_out, faces_out, collapses = fast_simplification.simplify(\n        points, faces, 0.5, return_collapses=True\n    )\n\n    (\n        replay_points,\n        replay_faces,\n        indice_mapping,\n    ) = fast_simplification.replay_simplification(points, faces, collapses)\n    assert np.allclose(points_out, replay_points)\n    assert np.allclose(faces_out, replay_faces)\n\n\n@skip_no_vtk\ndef test_collapses_sphere(mesh):\n    points = mesh.points\n    faces = mesh.faces.reshape(-1, 4)[:, 1:]\n    reduction = 0.5\n\n    points_out, faces_out, collapses = fast_simplification.simplify(\n        points, faces, reduction, return_collapses=True\n    )\n\n    (\n        replay_points,\n        replay_faces,\n        indice_mapping,\n    ) = fast_simplification.replay_simplification(points, faces, collapses)\n    assert np.allclose(points_out, replay_points)\n    assert np.allclose(faces_out, replay_faces)\n\n\ntry:\n    from pyvista import examples\n\n    @pytest.fixture\n    def louis():\n        return examples.download_louis_louvre()\n\n    @pytest.fixture\n    def human():\n        return examples.download_human()\n\n    has_examples = True\nexcept:\n    has_examples = False\nskip_no_examples = pytest.mark.skipif(not has_examples, reason=\"Requires pyvista.examples\")\n\n\n@skip_no_examples\n@skip_no_vtk\ndef test_collapses_louis(louis):\n    points = louis.points\n    faces = louis.faces.reshape(-1, 4)[:, 1:]\n    reduction = 0.9\n\n    points_out, faces_out, collapses = fast_simplification.simplify(\n        points, faces, reduction, return_collapses=True\n    )\n\n    (\n        replay_points,\n        replay_faces,\n        indice_mapping,\n    ) = fast_simplification.replay_simplification(points, faces, collapses)\n    assert np.allclose(points_out, replay_points)\n    assert np.allclose(faces_out, replay_faces)\n\n\n@skip_no_examples\n@skip_no_vtk\ndef test_human(human):\n    points = human.points\n    faces = human.faces.reshape(-1, 4)[:, 1:]\n    reduction = 0.9\n\n    points_out, faces_out, collapses = fast_simplification.simplify(\n        points, faces, reduction, return_collapses=True\n    )\n\n    (\n        replay_points,\n        replay_faces,\n        indice_mapping,\n    ) = fast_simplification.replay_simplification(points, faces, collapses)\n    assert np.allclose(points_out, replay_points)\n    assert np.allclose(faces_out, replay_faces)\n"
  },
  {
    "path": "tests/test_simplify.py",
    "content": "import numpy as np\nimport pytest\n\nimport fast_simplification\n\ntry:\n    import pyvista as pv\n\n    has_vtk = True\nexcept ModuleNotFoundError:\n    has_vtk = False\n\nskip_no_vtk = pytest.mark.skipif(not has_vtk, reason=\"Requires VTK\")\n\n\n@pytest.fixture\ndef mesh():\n    return pv.Sphere()\n\n\ndef test_simplify_trivial():\n    # arrays from:\n    # mesh = pv.Plane(i_resolution=2, j_resolution=2).triangulate()\n    points = [\n        [0.5, -0.5, 0.0],\n        [0.0, -0.5, 0.0],\n        [-0.5, -0.5, 0.0],\n        [0.5, 0.0, 0.0],\n        [0.0, 0.0, 0.0],\n        [-0.5, 0.0, 0.0],\n        [0.5, 0.5, 0.0],\n        [0.0, 0.5, 0.0],\n        [-0.5, 0.5, 0.0],\n    ]\n\n    faces = [\n        [0, 1, 3],\n        [4, 3, 1],\n        [1, 2, 4],\n        [5, 4, 2],\n        [3, 4, 6],\n        [7, 6, 4],\n        [4, 5, 7],\n        [8, 7, 5],\n    ]\n\n    with pytest.raises(ValueError, match=\"You must specify\"):\n        fast_simplification.simplify(points, faces)\n\n    points_out, faces_out = fast_simplification.simplify(points, faces, 0.5)\n    assert points_out.shape[0] == 5\n    assert faces_out.shape[0] == 4\n\n    # Test with return_collapses=True\n    # We check that the number of points after simplification is equal to the number of\n    # points before simplification minus the number of collapses\n    points_out, faces_out, collapses = fast_simplification.simplify(\n        points, faces, 0.5, return_collapses=True\n    )\n    n_points_before_simplification = len(points)\n    n_points_after_simplification = len(points_out)\n    n_collapses = len(collapses)\n    assert n_points_after_simplification == n_points_before_simplification - n_collapses\n\n\n@skip_no_vtk\ndef test_simplify_none(mesh):\n    triangles = mesh._connectivity_array.reshape(-1, 3)\n\n    reduction = 0\n    points, faces = fast_simplification.simplify(mesh.points, triangles, reduction)\n    assert np.allclose(triangles, faces)\n    assert np.allclose(mesh.points, points)\n\n\n@skip_no_vtk\ndef test_simplify(mesh):\n    triangles = mesh._connectivity_array.reshape(-1, 3)\n    reduction = 0.5\n    points, faces, collapses = fast_simplification.simplify(\n        mesh.points, triangles, reduction, return_collapses=True\n    )\n    assert triangles.shape[0] * reduction == faces.shape[0]\n    # We check that the number of points after simplification is equal to the number of\n    # points before simplification minus the number of collapses\n    n_points_before_simplification = mesh.points.shape[0]\n    n_points_after_simplification = points.shape[0]\n    n_collapses = collapses.shape[0]\n    assert n_points_after_simplification == n_points_before_simplification - n_collapses\n\n    assert points.dtype == np.float64\n\n\n@skip_no_vtk\ndef test_simplify_lossless(mesh):\n    triangles = mesh._connectivity_array.reshape(-1, 3)\n    reduction = 0.5\n    points, faces = fast_simplification.simplify(mesh.points, triangles, reduction, lossless=True)\n    assert np.allclose(mesh.points, points)\n    assert np.allclose(triangles, faces)\n\n\n@skip_no_vtk\ndef test_simplify_agg(mesh):\n    triangles = mesh._connectivity_array.reshape(-1, 3)\n\n    reduction = 0.5\n    points, faces = fast_simplification.simplify(\n        mesh.points,\n        triangles,\n        reduction,\n        agg=0,\n    )\n    assert triangles.shape[0] == faces.shape[0]\n\n    reduction = 0.5\n    points, faces = fast_simplification.simplify(\n        mesh.points,\n        triangles,\n        reduction,\n        agg=1,\n    )\n    # somewhere between the requested reduction and the original number of triangles\n    assert triangles.shape[0] * reduction < faces.shape[0] < triangles.shape[0]\n\n\n@skip_no_vtk\ndef test_simplify_mesh(mesh):\n    reduction = 0.5\n    mesh_out = fast_simplification.simplify_mesh(mesh, reduction)\n    assert mesh_out.n_cells == mesh.n_cells * reduction\n"
  },
  {
    "path": "tools/audit_wheel.sh",
    "content": "#!/bin/bash -eo pipefail\nset -x\n\nPY_MINOR=$(python -c \"import sys; print(sys.version_info.minor)\")\nif [ \"$PY_MINOR\" -lt 11 ]; then\n  echo \"Not checking abi3audit for Python $PY_MINOR < 3.11\"\n  exit 0\nfi\nabi3audit --strict --report --verbose \"$1\"\n"
  }
]