Full Code of pyvista/fast-simplification for AI

main 05315c72f401 cached
36 files
119.9 KB
37.9k tokens
69 symbols
1 requests
Download .txt
Repository: pyvista/fast-simplification
Branch: main
Commit: 05315c72f401
Files: 36
Total size: 119.9 KB

Directory structure:
gitextract_q5jmbpat/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── testing-and-deployment.yml
├── .gitignore
├── .pre-commit-config.yaml
├── LICENSE
├── MANIFEST.in
├── README.rst
├── doc/
│   ├── Makefile
│   ├── _templates/
│   │   └── autosummary/
│   │       └── class.rst
│   ├── api.rst
│   ├── conf.py
│   └── index.rst
├── examples/
│   ├── README.txt
│   ├── replay.py
│   └── simplify.py
├── fast_simplification/
│   ├── Replay.h
│   ├── Simplify.h
│   ├── __init__.py
│   ├── _replay.pyx
│   ├── _simplify.pyx
│   ├── _version.py
│   ├── fast_simplification.py
│   ├── replay.py
│   ├── simplify.py
│   ├── utils.py
│   ├── wrapper.h
│   └── wrapper_replay.h
├── pyproject.toml
├── pytest.ini
├── requirements_docs.txt
├── requirements_test.txt
├── setup.py
├── tests/
│   ├── test_map_isolated_points.py
│   ├── test_replay.py
│   └── test_simplify.py
└── tools/
    └── audit_wheel.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: pip
  directory: /
  insecure-external-code-execution: allow
  schedule:
    interval: monthly
  open-pull-requests-limit: 100
  labels:
  - maintenance
  - dependencies
  groups:
    pip:
      patterns:
      - '*'
- package-ecosystem: github-actions
  directory: /
  schedule:
    interval: monthly
  open-pull-requests-limit: 100
  labels:
  - maintenance
  - dependencies
  groups:
    actions:
      patterns:
      - '*'


================================================
FILE: .github/workflows/testing-and-deployment.yml
================================================
name: Build

on:
  pull_request:
  push:
    tags:
    - v*
    branches:
    - main

# disable concurrent runs
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  # fail fast and early to avoid clogging GH Actions
  smoke_testing:
    runs-on: ubuntu-latest
    name: Source distribution testing
    steps:
    - uses: actions/checkout@v6

    - name: Set up Python
      uses: actions/setup-python@v6
      with:
        python-version: '3.13'

    - name: Setup headless display
      uses: pyvista/setup-headless-display-action@v4

    - name: Build and validate wheel
      run: |
        pip install build twine
        python -m build
        twine check dist/*

    - name: Install
      run: pip install dist/*.whl

    - name: Test
      run: |
        pip install -r requirements_test.txt
        cd tests && python -m pytest -v

    - name: Upload sdist
      uses: actions/upload-artifact@v7
      with:
        path: ./dist/*.tar.gz
        name: fast-simplification-sdist

  docs_build:
    name: Build Documentation
    runs-on: ubuntu-latest
    needs: smoke_testing

    steps:
    - uses: actions/checkout@v6

    - name: Setup Python
      uses: actions/setup-python@v6
      with:
        python-version: '3.13'

    - name: Setup headless display
      uses: pyvista/setup-headless-display-action@v4

    - name: Install library
      run: pip install .

    - name: Build Documentation
      run: |
        pip install -r requirements_docs.txt
        make -C doc html

    - name: Deploy on tag
      uses: JamesIves/github-pages-deploy-action@v4
      if: startsWith(github.ref, 'refs/tags/')
      with:
        token: ${{ secrets.GITHUB_TOKEN }}
        branch: gh-pages
        folder: doc/_build/html

  build_wheels:
    needs: smoke_testing
    name: Build wheels on ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest, macos-15-intel]

    steps:
    - uses: actions/checkout@v6

    - name: Build wheels
      uses: pypa/cibuildwheel@v3.4.0

    - name: List generated wheels
      run: ls ./wheelhouse/*

    - uses: actions/upload-artifact@v7
      with:
        path: ./wheelhouse/*.whl
        name: fast-simplification-wheel-${{ matrix.os }}

  release:
    name: Release
    if: github.event_name == 'push' && contains(github.ref, 'refs/tags')
    needs: [build_wheels, docs_build]
    runs-on: ubuntu-latest
    environment:
      name: pypi
      url: https://pypi.org/p/fast-simplification
    permissions:
      id-token: write  # this permission is mandatory for trusted publishing
      contents: write  # required to create a release
    steps:
    - uses: actions/download-artifact@v8
    - name: Flatten directory structure
      run: |
        mkdir -p dist/
        find . -name '*.whl' -exec mv {} dist/ \;
        find . -name '*.tar.gz' -exec mv {} dist/ \;
    - name: Publish package distributions to PyPI
      uses: pypa/gh-action-pypi-publish@release/v1
    - name: Create GitHub Release
      uses: softprops/action-gh-release@v2
      with:
        generate_release_notes: true
        files: |
          ./**/*.whl


================================================
FILE: .gitignore
================================================
# Compiled source #
###################
*.pyc
*.pyd
*.so
*.o
__pycache__/

# Pip
*.egg-info

# Cython generated files #
fast_simplification/_simplify.cpp
fast_simplification/_replay.cpp

# documentation
doc/_build/
doc/examples/
doc/_autosummary/

dist/
venv/
build/

# IDE
.vscode/

================================================
FILE: .pre-commit-config.yaml
================================================
# Integration with GitHub Actions
# See https://pre-commit.ci/
ci:
  autoupdate_schedule: monthly

repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
  rev: v0.15.9
  hooks:
  - id: ruff
    args: [--fix, --exit-non-zero-on-fix]
    exclude: ^(docs/|tests)
  - id: ruff-format

- repo: https://github.com/pycqa/isort
  rev: 8.0.1
  hooks:
  - id: isort

- repo: https://github.com/codespell-project/codespell
  rev: v2.4.2
  hooks:
  - id: codespell
    args: [--toml, pyproject.toml]
    additional_dependencies: [tomli]

- repo: https://github.com/keewis/blackdoc
  rev: v0.4.6
  hooks:
  - id: blackdoc
    files: \.py$

# - repo: https://github.com/pycqa/pydocstyle
#   rev: 6.1.1
#   hooks:
#   - id: pydocstyle
#     additional_dependencies: [toml]
#     exclude: "tests/"

- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: v6.0.0
  hooks:
  - id: check-merge-conflict
  - id: debug-statements
  - id: trailing-whitespace
  - id: no-commit-to-branch
    args: [--branch, main]
  - id: requirements-txt-fixer

- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
  rev: v2.16.0
  hooks:
  - id: pretty-format-toml
    args: [--autofix]
  - id: pretty-format-yaml
    args: [--autofix, --indent, '2']

# this validates our github workflow files
- repo: https://github.com/python-jsonschema/check-jsonschema
  rev: 0.37.1
  hooks:
  - id: check-github-workflows


================================================
FILE: LICENSE
================================================
The MIT License

Copyright (c) 2017-2021 The PyVista Developers

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: MANIFEST.in
================================================
include fast_simplification/*.h


================================================
FILE: README.rst
================================================
Python Fast-Quadric-Mesh-Simplification Wrapper
===============================================
This is a python wrapping of the `Fast-Quadric-Mesh-Simplification Library
<https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification/>`_. Having
arrived at the same problem as the original author, but needing a Python
library, this project seeks to extend the work of the original library while
adding integration to Python and the `PyVista
<https://github.com/pyvista/pyvista>`_ project.

For the full documentation visit: https://pyvista.github.io/fast-simplification/

.. image:: https://github.com/pyvista/fast-simplification/raw/main/doc/images/simplify_demo.png

Installation
------------
Fast Simplification can be installed from PyPI using pip on Python >= 3.7::

  pip install fast-simplification

See the `Contributing <https://github.com/pyvista/fast-simplification#contributing>`_ for more details regarding development or if the installation through pip doesn't work out.

Basic Usage
-----------
The basic interface is quite straightforward and can work directly
with arrays of points and triangles:

.. code:: python

    points = [[ 0.5, -0.5, 0.0],
              [ 0.0, -0.5, 0.0],
              [-0.5, -0.5, 0.0],
              [ 0.5,  0.0, 0.0],
              [ 0.0,  0.0, 0.0],
              [-0.5,  0.0, 0.0],
              [ 0.5,  0.5, 0.0],
              [ 0.0,  0.5, 0.0],
              [-0.5,  0.5, 0.0]]

    faces = [[0, 1, 3],
             [4, 3, 1],
             [1, 2, 4],
             [5, 4, 2],
             [3, 4, 6],
             [7, 6, 4],
             [4, 5, 7],
             [8, 7, 5]]

    points_out, faces_out = fast_simplification.simplify(points, faces, 0.5)


Advanced Usage
--------------
This library supports direct integration with VTK through PyVista to
provide a simplistic interface to the library. As this library
provides a 4-5x improvement to the VTK decimation algorithms.

.. code:: python

   >>> from pyvista import examples
   >>> mesh = examples.download_nefertiti()
   >>> out = fast_simplification.simplify_mesh(mesh, target_reduction=0.9)

   Compare with built-in VTK/PyVista methods:

   >>> fas_sim = fast_simplification.simplify_mesh(mesh, target_reduction=0.9)
   >>> dec_std = mesh.decimate(0.9)  # vtkQuadricDecimation
   >>> dec_pro = mesh.decimate_pro(0.9)  # vtkDecimatePro

   >>> pv.set_plot_theme('document')
   >>> pl = pv.Plotter(shape=(2, 2), window_size=(1000, 1000))
   >>> pl.add_text('Original', 'upper_right', color='w')
   >>> pl.add_mesh(mesh, show_edges=True)
   >>> pl.camera_position = cpos

   >>> pl.subplot(0, 1)
   >>> pl.add_text(
   ...    'Fast-Quadric-Mesh-Simplification\n~2.2 seconds', 'upper_right', color='w'
   ... )
   >>> pl.add_mesh(fas_sim, show_edges=True)
   >>> pl.camera_position = cpos

   >>> pl.subplot(1, 0)
   >>> pl.add_mesh(dec_std, show_edges=True)
   >>> pl.add_text(
   ...    'vtkQuadricDecimation\n~9.5 seconds', 'upper_right', color='w'
   ... )
   >>> pl.camera_position = cpos

   >>> pl.subplot(1, 1)
   >>> pl.add_mesh(dec_pro, show_edges=True)
   >>> pl.add_text(
   ...    'vtkDecimatePro\n11.4~ seconds', 'upper_right', color='w'
   ... )
   >>> pl.camera_position = cpos
   >>> pl.show()


Comparison to other libraries
-----------------------------
The `pyfqmr <https://github.com/Kramer84/pyfqmr-Fast-Quadric-Mesh-Reduction>`_
library wraps the same header file as this library and has similar capabilities.
In this library, the decision was made to write the Cython layer on top of an
additional C++ layer rather than directly interfacing with wrapper from Cython.
This results in a mild performance improvement.

Reusing the example above:

.. code:: python

   Set up a timing function.

   >>> import pyfqmr
   >>> vertices = mesh.points
   >>> faces = mesh.faces.reshape(-1, 4)[:, 1:]
   >>> def time_pyfqmr():
   ...     mesh_simplifier = pyfqmr.Simplify()
   ...     mesh_simplifier.setMesh(vertices, faces)
   ...     mesh_simplifier.simplify_mesh(
   ...         target_count=out.n_faces, aggressiveness=7, verbose=0
   ...     )
   ...     vertices_out, faces_out, normals_out = mesh_simplifier.getMesh()
   ...     return vertices_out, faces_out, normals_out

Now, time it and compare with the non-VTK API of this library:

.. code:: python

   >>> timeit time_pyfqmr()
   2.75 s ± 5.35 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

   >>> timeit vout, fout = fast_simplification.simplify(vertices, faces, 0.9)
   2.05 s ± 3.18 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Additionally, the ``fast-simplification`` library has direct plugins
to the ``pyvista`` library, making it easy to read and write meshes:

.. code:: python

   >>> import pyvista
   >>> import fast_simplification
   >>> mesh = pyvista.read('my_mesh.stl')
   >>> simple = fast_simplification.simplify_mesh(mesh)
   >>> simple.save('my_simple_mesh.stl')

Since both libraries are based on the same core C++ code, feel free to
use whichever gives you the best performance and interoperability.

Replay decimation functionality
-------------------------------
This library also provides an interface to keep track of the successive
collapses that occur during the decimation process and to replay the
decimation process. This can be useful for different applications, such
as:

* applying the same decimation to a collection of meshes that share the
  same topology
* computing a correspondence map between the vertices of the original
  mesh and the vertices of the decimated mesh, to transfer field data from
  one to the other for example
* replaying the decimation process with a smaller target reduction than
  the original one, faster than decimating the original mesh with the
  smaller target reduction

To use this functionality, you need to set the ``return_collapses``
parameter to ``True`` when calling ``simplify``. This will return the
successive collapses of the decimation process in addition to points
and faces.

.. code:: python

   >>> import fast_simplification
   >>> import pyvista
   >>> mesh = pyvista.Sphere()
   >>> points, faces = mesh.points, mesh.faces.reshape(-1, 4)[:, 1:]
   >>> points_out, faces_out, collapses = fast_simplification.simplify(points, faces, 0.9, return_collapses=True)

Now you can call ``replay_simplification`` to replay the decimation process
and obtain the mapping between the vertices of the original mesh and the
vertices of the decimated mesh.

.. code:: python

   >>> points_out, faces_out, indice_mapping = fast_simplification.replay_simplification(points, faces, collapses)
   >>> i = 3
   >>> print(f'Vertex {i} of the original mesh is mapped to {indice_mapping[i]} of the decimated mesh')

You can also use the ``replay_simplification`` function to replay the
decimation process with a smaller target reduction than the original one.
This is faster than decimating the original mesh with the smaller target
reduction. To do so, you need to pass a subset of the collapses to the
``replay_simplification`` function. For example, to replay the decimation
process with a target reduction of 50% the initial rate, you can run:

.. code:: python

   >>> import numpy as np
   >>> collapses_half = collapses[:int(0.5 * len(collapses))]
   >>> points_out, faces_out, indice_mapping = fast_simplification.replay_simplification(points, faces, collapses_half)

If you have a collection of meshes that share the same topology, you can
apply the same decimation to all of them by calling ``replay_simplification``
with the same collapses for each mesh. This ensure that the decimated meshes
will share the same topology.

.. code:: python

   >>> import numpy as np
   >>> # Assume that you have a collection of meshes stored in a list meshes
   >>> _, _, collapses = fast_simplification.simplify(meshes[0].points, meshes[0].faces,
   ...                                                0.9, return_collapses=True)
   >>> decimated_meshes = []
   >>> for mesh in meshes:
   ...     points_out, faces_out, _ = fast_simplification.replay_simplification(mesh.points, mesh.faces, collapses)
   ...     decimated_meshes.append(pyvista.PolyData(points_out, faces_out))

Contributing
------------
Contribute to this repository by forking this repository and installing in
development mode with::

  git clone https://github.com/<USERNAME>/fast-simplification
  pip install -e .
  pip install -r requirements_test.txt

You can then add your feature or commit your bug fix and then run your unit
testing with::

  pytest

Unit testing will automatically enforce minimum code coverage standards.

Next, to ensure your code meets minimum code styling standards, run::

  pip install pre-commit
  pre-commit run --all-files

Finally, `create a pull request`_ from your fork and I'll be sure to review it.

.. _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


================================================
FILE: doc/Makefile
================================================
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS    =
SPHINXBUILD   = sphinx-build
SPHINXPROJ    = pymeshfix
SOURCEDIR     = .
BUILDDIR      = _build

# Put it first so that "make" without argument is like "make help".
help:
	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

clean:
	rm -rf $(BUILDDIR)/*
	rm -rf examples/
	find . -type d -name "_autosummary" -exec rm -rf {} +


================================================
FILE: doc/_templates/autosummary/class.rst
================================================
{{ fullname | escape | underline}}

.. currentmodule:: {{ module }}

.. autoclass:: {{ objname }}

   {% block methods %}

   {% if methods %}
   .. rubric:: {{ _('Methods') }}

   .. autosummary::
      :toctree:
   {% for item in methods %}
      {% if item != "__init__" %}
      {{ name }}.{{ item }}
      {% endif %}
   {%- endfor %}
   {% endif %}
   {% endblock %}

   {% block attributes %}
   {% if attributes %}
   .. rubric:: {{ _('Attributes') }}

   .. autosummary::
      :toctree:
   {% for item in attributes %}
      {% if item.0 != item.upper().0 %}
      {{ name }}.{{ item }}
      {% endif %}
   {%- endfor %}
   {% endif %}
   {% endblock %}


================================================
FILE: doc/api.rst
================================================
API Reference
=============
These are the three public methods that expose the fast-simplification API to
Python.

.. currentmodule:: fast_simplification

.. autosummary::
   :toctree: _autosummary

   simplify
   simplify_mesh
   replay_simplification


================================================
FILE: doc/conf.py
================================================
import datetime
import os

import numpy as np
import pyvista
from sphinx_gallery.sorting import FileNameSortKey

from fast_simplification import __version__

# Manage errors
pyvista.set_error_output_file("errors.txt")
# Ensure that offscreen rendering is used for docs generation
pyvista.OFF_SCREEN = True  # Not necessary - simply an insurance policy
# Preferred plotting style for documentation
pyvista.set_plot_theme("document")
pyvista.global_theme.window_size = np.array([1024, 768]) * 2
# Save figures in specified directory
pyvista.FIGURE_PATH = os.path.abspath("./images/")
if not os.path.exists(pyvista.FIGURE_PATH):
    os.makedirs(pyvista.FIGURE_PATH)

pyvista.BUILDING_GALLERY = True

# -- Project information -----------------------------------------------------

project = "fast-simplification"
year = datetime.date.today().year
copyright = f"2017-{year}, The PyVista Developers"
author = "Alex Kaszynski"

# The short X.Y version
version = release = __version__

# -- General configuration ---------------------------------------------------
html_logo = "./_static/pyvista_logo_sm.png"

# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    "sphinx.ext.autodoc",
    "sphinx.ext.napoleon",
    # 'sphinx.ext.doctest',
    "sphinx.ext.autosummary",
    "notfound.extension",
    "sphinx_copybutton",
    "sphinx_gallery.gen_gallery",
    "sphinx.ext.extlinks",
    "numpydoc",
]

numpydoc_show_class_members = False
# numpydoc_class_members_toctree = False
html_static_path = ["_static"]

# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]

# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = ".rst"

# The master toctree document.
master_doc = "index"

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path .
exclude_patterns = []

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"

# Copy button customization ---------------------------------------------------
# exclude traditional Python prompts from the copied code
copybutton_prompt_text = r">>> ?|\.\.\. "
copybutton_prompt_is_regexp = True


# -- Options for HTML output -------------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
#
html_theme = "pydata_sphinx_theme"
html_context = {
    # Enable the "Edit in GitHub link within the header of each page.
    "display_github": True,
    "github_user": "pyvista",
    "github_repo": "fast-simplification",
    "github_version": "master",
    "menu_links_name": "Getting Connected",
    "menu_links": [
        (
            '<i class="fa fa-slack fa-fw"></i> Slack Community',
            "http://slack.pyvista.org",
        ),
        (
            '<i class="fa fa-comment fa-fw"></i> Support',
            "https://github.com/pyvista/pyvista-support",
        ),
        (
            '<i class="fa fa-github fa-fw"></i> Source Code',
            "https://github.com/pyvista/pymeshfix",
        ),
    ],
}

html_theme_options = {
    "show_prev_next": False,
    "github_url": "https://github.com/pyvista/pymeshfix",
}


# -- Options for HTMLHelp output ---------------------------------------------

# Output file base name for HTML help builder.
htmlhelp_basename = "PyMeshFix"


# -- Options for LaTeX output ------------------------------------------------

latex_elements = {}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [
    (
        master_doc,
        "pymeshfix.tex",
        "PyMeshFix Documentation",
        "Alex Kaszynski",
        "manual",
    ),
]


# -- Options for manual page output ------------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [(master_doc, "PyMeshFix", "PyMeshFix Documentation", [author], 1)]


# -- Options for Texinfo output ----------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
    (
        master_doc,
        "PyMeshFix",
        "PyMeshFix Documentation",
        author,
        "PyMeshFix",
        "One line description of project.",
        "Miscellaneous",
    ),
]


# -- Extension configuration -------------------------------------------------

# -- Options for intersphinx extension ---------------------------------------

# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
    "https://docs.python.org/": None,
    "https://docs.pyvista.org": None,
}

# -- Options for todo extension ----------------------------------------------

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True


# -- Sphinx Gallery Options
sphinx_gallery_conf = {
    # path to your examples scripts
    "examples_dirs": [
        "../examples/",
    ],
    # path where to save gallery generated examples
    "gallery_dirs": ["examples"],
    # pattern to search for example files
    "filename_pattern": r"\.py",
    # Remove the "Download all examples" button from the top level gallery
    "download_all_examples": False,
    # Sort gallery example by file name instead of number of lines (default)
    "within_subsection_order": FileNameSortKey,
    # directory where function granular galleries are stored
    "backreferences_dir": None,
    # Modules for which function level galleries are created.  In
    "doc_module": "pymeshfix",
    "image_scrapers": (pyvista.Scraper(), "matplotlib"),
    "thumbnail_size": (350, 350),
}


# -- Custom 404 page

notfound_no_urls_prefix = True


================================================
FILE: doc/index.rst
================================================
.. include:: ../README.rst


.. toctree::
   :hidden:

   self



.. toctree::
   :maxdepth: 2
   :caption: Examples
   :hidden:

   examples/index



.. toctree::
   :maxdepth: 2
   :caption: API Reference
   :hidden:

   api


================================================
FILE: examples/README.txt
================================================
Fast-simplification Examples
============================
The following examples demonstrate ``fast-simplification``
functionality using `pyvista <https://docs.pyvista.org/>`__.


================================================
FILE: examples/replay.py
================================================
"""
Replay Decimation
-----------------

This example shows how to replay a decimation sequence with replay.

"""

from time import time

import numpy as np
import pyvista as pv
from pyvista import examples

import fast_simplification


# Ancillary function to convert triangles to padded faces
def triangles_to_faces(triangles):
    tmp = 3 * np.ones((len(triangles), 4), dtype=triangles.dtype)
    tmp[:, 1:] = triangles
    return tmp.copy().reshape(-1)


# mesh = examples.download_cow().triangulate().clean()

# cpos = [(12.81184076782852, 0.2698100334791761, -10.82840852844307),
#  (-5.767085129340097, -0.45822321783537723, 6.935179459234972),
#  (-0.031039486276564762, 0.99948202301343, 0.008499174352116386)]


# load an example mesh
mesh = examples.download_louis_louvre()

# nice camera angle
cpos = [
    (5.428820015861438, -10.151721995577468, 15.902198956656623),
    (1.4405146331169636, 2.897371104075222, 10.951469667556948),
    (-0.01001925846458282, 0.3520252491158569, 0.9359368773826251),
]

points = mesh.points
triangles = mesh.faces.reshape(-1, 4)[:, 1:]


# Decimate the mesh with fast_simplification
# and record the collapses
start = time()
dec_points, dec_triangles, collapses = fast_simplification.simplify(
    points, triangles, 0.995, return_collapses=True
)
time_simplify = time() - start

# Replay the decimation sequence and record the mapping between
# the original points and the decimated points
start = time()
(
    dec_points_replay,
    dec_triangles_replay,
    indice_mapping_replay,
) = fast_simplification.replay_simplification(
    points=points,
    triangles=triangles,
    collapses=collapses,
)
time_replay_new = time() - start

# Partially replay the decimation sequence (90% of the collapses are replayed)
partial_collapses = collapses[0 : int(0.9 * len(collapses))]
start = time()
(
    dec_points_replay2,
    dec_triangles_replay2,
    indice_mapping_replay2,
) = fast_simplification.replay_simplification(
    points=points, triangles=triangles, collapses=partial_collapses
)
time_replay_new = time() - start

# Randomly select two points on the original mesh
np.random.seed(1)
i, j = np.random.randint(0, len(mesh.points), 2)

# Map the indices of the original points to the indices of the decimated points
m_i = indice_mapping_replay[i]
m_j = indice_mapping_replay[j]
m_i2 = indice_mapping_replay2[i]
m_j2 = indice_mapping_replay2[j]

p = pv.Plotter(shape=(2, 2), theme=pv.themes.DocumentTheme())

p.subplot(0, 0)
# Plot the original mesh with the two highlighted points
p.add_mesh(mesh, show_edges=True, color="tan")
p.add_points(mesh.points[i], color="red", point_size=10, render_points_as_spheres=True)
p.add_points(mesh.points[j], color="blue", point_size=10, render_points_as_spheres=True)
p.add_text(
    f"Original mesh, {mesh.points.shape[0]} vertices, {triangles.shape[0]} triangles",
    font_size=10,
)
p.camera_position = cpos

p.subplot(0, 1)
# Plot the decimated mesh
p.add_mesh(
    pv.PolyData(dec_points, faces=triangles_to_faces(dec_triangles)),
    show_edges=True,
    color="tan",
)
p.add_text(
    f"Decimated mesh, {dec_points.shape[0]} vertices, {dec_triangles.shape[0]} triangles, took {time_simplify:.2f}s",
    font_size=10,
)
p.camera_position = cpos

p.subplot(1, 0)
# Plot the mesh decimated with replay with the two highlighted points
p.add_mesh(
    pv.PolyData(dec_points_replay, faces=triangles_to_faces(dec_triangles_replay)),
    show_edges=True,
    color="tan",
)
p.add_points(
    dec_points_replay[m_i],
    color="red",
    point_size=10,
    render_points_as_spheres=True,
)
p.add_points(
    dec_points_replay[m_j],
    color="blue",
    point_size=10,
    render_points_as_spheres=True,
)
n_points, n_triangles = dec_points_replay.shape[0], dec_triangles_replay.shape[0]
p.add_text(
    f"Replay, {n_points} vertices, {n_triangles} triangles, took {time_replay_new:.2f}s",
    font_size=10,
)
p.camera_position = cpos

p.subplot(1, 1)
# Plot the mesh partially decimated with replay with the two highlighted points
p.add_mesh(
    pv.PolyData(dec_points_replay2, faces=triangles_to_faces(dec_triangles_replay2)),
    show_edges=True,
    color="tan",
)
p.add_points(
    dec_points_replay2[m_i2],
    color="red",
    point_size=10,
    render_points_as_spheres=True,
)
p.add_points(
    dec_points_replay2[m_j2],
    color="blue",
    point_size=10,
    render_points_as_spheres=True,
)
n_points, n_triangles = dec_points_replay2.shape[0], dec_triangles_replay2.shape[0]
p.add_text(
    f"Partial replay, {n_points} vertices, {n_triangles} triangles, took {time_replay_new:.2f}s",
    font_size=10,
)
p.camera_position = cpos

p.show()


================================================
FILE: examples/simplify.py
================================================
"""
Compare Decimation Methods
--------------------------

This example compares various decimation methods

"""

import time

import pyvista as pv
from pyvista import examples

import fast_simplification

# load an example mesh
mesh = examples.download_louis_louvre()

# nice camera angle
cpos = [
    (6.264157141857314, -6.959267635766402, 11.71668951132694),
    (1.3291685457683413, 2.267162128740896, 12.263240938610595),
    (0.0023825740958850136, -0.05786378450796799, 0.9983216444528751),
]


###############################################################################
# Compare decimation times
reduction = 0.9
print("Approach                         Time Elapsed")

tstart = time.time()
fas_sim = fast_simplification.simplify_mesh(mesh, target_reduction=reduction)
fast_sim_time = time.time() - tstart
print(f"Fast Quadratic Simplification  {fast_sim_time:8.4f} seconds")

tstart = time.time()
dec_std = mesh.decimate(reduction)
dec_std_time = time.time() - tstart
print(f"vtkQuadricDecimation           {dec_std_time:8.4f} seconds")

tstart = time.time()
dec_pro = mesh.decimate_pro(reduction)
dec_pro_time = time.time() - tstart
print(f"vtkDecimatePro                 {dec_pro_time:8.4f} seconds")


pl = pv.Plotter(shape=(2, 2), window_size=(1000, 1000), theme=pv.themes.DocumentTheme())
pl.add_text("Original", "upper_right", color="k")
pl.add_mesh(mesh, show_edges=True)
pl.camera_position = cpos

pl.subplot(0, 1)
pl.add_text(
    f"Fast-Quadric-Mesh-Simplification\n{fast_sim_time:8.4f} seconds",
    "upper_right",
    color="k",
)
pl.add_mesh(fas_sim, show_edges=True)
pl.camera_position = cpos

pl.subplot(1, 0)
pl.add_mesh(dec_std, show_edges=True)
pl.add_text(f"vtkQuadricDecimation\n{dec_std_time:8.4f} seconds", "upper_right", color="k")
pl.camera_position = cpos

pl.subplot(1, 1)
pl.add_mesh(dec_pro, show_edges=True)
pl.add_text(f"vtkDecimatePro\n{dec_pro_time:8.4f} seconds", "upper_right", color="k")
pl.camera_position = cpos

pl.show()


================================================
FILE: fast_simplification/Replay.h
================================================
#include "Simplify.h"

namespace Replay{

    // Global Variables & Structures (same as for Simplify)
	enum Attributes {
		NONE,
		NORMAL = 2,
		TEXCOORD = 4,
		COLOR = 8
	};
	struct Triangle { int v[3];double err[4];int deleted,dirty,attr;vec3f n;vec3f uvs[3];int material; };
	struct Vertex { vec3f p;int tstart,tcount;SymetricMatrix q;int border;};
	struct Ref { int tid,tvertex; };
	std::vector<Triangle> triangles;
	std::vector<Vertex> vertices;
	std::vector<Ref> refs;
    std::string mtllib;
    std::vector<std::string> materials;
    std::vector<std::vector<int>> collapses;

    // Helper functions
    double vertex_error(SymetricMatrix q, double x, double y, double z);
	double calculate_error(int id_v1, int id_v2, vec3f &p_result);
    void initialize_quadrics();


    void replay_simplification()
    {
    	// init
		for(int i=0; i<vertices.size(); i++)
        {
            vertices[i].tcount=1;
        }

		// main iteration loop
        int i0,i1;
        int n_collapses = collapses.size();
        initialize_quadrics();

        for (int iteration=0; iteration < n_collapses; iteration++)
        {
            i0 = collapses[iteration][0];
            i1 = collapses[iteration][1];

            Vertex &v0 = vertices[i0];
            Vertex &v1 = vertices[i1];

            // Compute vertex to collapse to
            vec3f p;
            calculate_error(i0,i1,p); // p is the optimal point to collapse to

            // not flipped, so remove edge
            // v0 <- v1 (i0 <- i1)
            v0.p=p; // set the optimal point to collapse to
            v0.q=v1.q+v0.q; // add the quadrics (for calculating the error)

            v1.tcount=0; // mark vertex as deleted (will be removed later)

        }

        // remove deleted vertices
        int dst=0;
		loopi(0,vertices.size())
		if(vertices[i].tcount)
		{
			vertices[i].tstart=dst;
			vertices[dst].p=vertices[i].p;
			dst++;
		}

		vertices.resize(dst);

    }




	void initialize_quadrics()
	{
		// Init Quadrics by Plane & Edge Errors
		//
        loopi(0,vertices.size())
        vertices[i].q=SymetricMatrix(0.0);

        loopi(0,triangles.size())
        {
            Triangle &t=triangles[i];
            vec3f n,p[3];
            loopj(0,3) p[j]=vertices[t.v[j]].p;
            n.cross(p[1]-p[0],p[2]-p[0]);
            n.normalize();
            t.n=n;
            loopj(0,3) vertices[t.v[j]].q =
                vertices[t.v[j]].q+SymetricMatrix(n.x,n.y,n.z,-n.dot(p[0]));
        }

		// Init Reference ID list
		loopi(0,vertices.size())
		{
			vertices[i].tstart=0;
			vertices[i].tcount=0;
		}
		loopi(0,triangles.size())
		{
			Triangle &t=triangles[i];
			loopj(0,3) vertices[t.v[j]].tcount++;
		}
		int tstart=0;
		loopi(0,vertices.size())
		{
			Vertex &v=vertices[i];
			v.tstart=tstart;
			tstart+=v.tcount;
			v.tcount=0;
		}

		// Write References
		refs.resize(triangles.size()*3);
		loopi(0,triangles.size())
		{
			Triangle &t=triangles[i];
			loopj(0,3)
			{
				Vertex &v=vertices[t.v[j]];
				refs[v.tstart+v.tcount].tid=i;
				refs[v.tstart+v.tcount].tvertex=j;
				v.tcount++;
			}
		}

		// Initialize vertices.borders
		std::vector<int> vcount,vids;

		loopi(0,vertices.size())
				vertices[i].border=0;

			loopi(0,vertices.size())
			{
				Vertex &v=vertices[i];
				vcount.clear();
				vids.clear();
				loopj(0,v.tcount)
				{
					int k=refs[v.tstart+j].tid;
					Triangle &t=triangles[k];
					loopk(0,3)
					{
						int ofs=0,id=t.v[k];
						while(ofs<vcount.size())
						{
							if(vids[ofs]==id)break;
							ofs++;
						}
						if(ofs==vcount.size())
						{
							vcount.push_back(1);
							vids.push_back(id);
						}
						else
							vcount[ofs]++;
					}
				}
				loopj(0,vcount.size()) if(vcount[j]==1)
					vertices[vids[j]].border=1;
			}
	}



	// Error between vertex and Quadric

	double vertex_error(SymetricMatrix q, double x, double y, double z)
	{
 		return   q[0]*x*x + 2*q[1]*x*y + 2*q[2]*x*z + 2*q[3]*x + q[4]*y*y
 		     + 2*q[5]*y*z + 2*q[6]*y + q[7]*z*z + 2*q[8]*z + q[9];
	}

	// Error for one edge

	double calculate_error(int id_v1, int id_v2, vec3f &p_result)
	{
		// compute interpolated vertex

		SymetricMatrix q = vertices[id_v1].q + vertices[id_v2].q;
		bool   border = vertices[id_v1].border & vertices[id_v2].border;
		double error=0;
		double det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7);
		if ( det != 0 && !border )
		{

			// q_delta is invertible
			p_result.x = -1/det*(q.det(1, 2, 3, 4, 5, 6, 5, 7 , 8));	// vx = A41/det(q_delta)
			p_result.y =  1/det*(q.det(0, 2, 3, 1, 5, 6, 2, 7 , 8));	// vy = A42/det(q_delta)
			p_result.z = -1/det*(q.det(0, 1, 3, 1, 4, 6, 2, 5,  8));	// vz = A43/det(q_delta)

			error = vertex_error(q, p_result.x, p_result.y, p_result.z);
		}
		else
		{
			// det = 0 -> try to find best result
			vec3f p1=vertices[id_v1].p;
			vec3f p2=vertices[id_v2].p;
			vec3f p3=(p1+p2)/2;
			double error1 = vertex_error(q, p1.x,p1.y,p1.z);
			double error2 = vertex_error(q, p2.x,p2.y,p2.z);
			double error3 = vertex_error(q, p3.x,p3.y,p3.z);
			error = min(error1, min(error2, error3));
			if (error1 == error) p_result=p1;
			if (error2 == error) p_result=p2;
			if (error3 == error) p_result=p3;
		}
		return error;
	}

	char *trimwhitespace(char *str)
	{
		char *end;

		// Trim leading space
		while(isspace((unsigned char)*str)) str++;

		if(*str == 0)  // All spaces?
		return str;

		// Trim trailing space
		end = str + strlen(str) - 1;
		while(end > str && isspace((unsigned char)*end)) end--;

		// Write new null terminator
		*(end+1) = 0;

		return str;
	}

	//Option : Load OBJ
	void load_obj(const char* filename, bool process_uv=false){
		vertices.clear();
		triangles.clear();
		// printf ( "Loading Objects %s ... \n",filename);
		FILE* fn;
		if(filename==NULL)		return ;
		if((char)filename[0]==0)	return ;
		if ((fn = fopen(filename, "rb")) == NULL)
		{
			printf ( "File %s not found!\n" ,filename );
			return;
		}
		char line[1000];
		memset ( line,0,1000 );
		int vertex_cnt = 0;
		int material = -1;
		std::map<std::string, int> material_map;
		std::vector<vec3f> uvs;
		std::vector<std::vector<int> > uvMap;

		while(fgets( line, 1000, fn ) != NULL)
		{
			Vertex v;
			vec3f uv;

			if (strncmp(line, "mtllib", 6) == 0)
			{
				mtllib = trimwhitespace(&line[7]);
			}
			if (strncmp(line, "usemtl", 6) == 0)
			{
				std::string usemtl = trimwhitespace(&line[7]);
				if (material_map.find(usemtl) == material_map.end())
				{
					material_map[usemtl] = materials.size();
					materials.push_back(usemtl);
				}
				material = material_map[usemtl];
			}

			if ( line[0] == 'v' && line[1] == 't' )
			{
				if ( line[2] == ' ' )
				if(sscanf(line,"vt %lf %lf",
					&uv.x,&uv.y)==2)
				{
					uv.z = 0;
					uvs.push_back(uv);
				} else
				if(sscanf(line,"vt %lf %lf %lf",
					&uv.x,&uv.y,&uv.z)==3)
				{
					uvs.push_back(uv);
				}
			}
			else if ( line[0] == 'v' )
			{
				if ( line[1] == ' ' )
				if(sscanf(line,"v %lf %lf %lf",
					&v.p.x,	&v.p.y,	&v.p.z)==3)
				{
					vertices.push_back(v);
				}
			}
			int integers[9];
			if ( line[0] == 'f' )
			{
				Triangle t;
				bool tri_ok = false;
                bool has_uv = false;

				if(sscanf(line,"f %d %d %d",
					&integers[0],&integers[1],&integers[2])==3)
				{
					tri_ok = true;
				}else
				if(sscanf(line,"f %d// %d// %d//",
					&integers[0],&integers[1],&integers[2])==3)
				{
					tri_ok = true;
				}else
				if(sscanf(line,"f %d//%d %d//%d %d//%d",
					&integers[0],&integers[3],
					&integers[1],&integers[4],
					&integers[2],&integers[5])==6)
				{
					tri_ok = true;
				}else
				if(sscanf(line,"f %d/%d/%d %d/%d/%d %d/%d/%d",
					&integers[0],&integers[6],&integers[3],
					&integers[1],&integers[7],&integers[4],
					&integers[2],&integers[8],&integers[5])==9)
				{
					tri_ok = true;
					has_uv = true;
				}else // Add Support for v/vt only meshes
				if (sscanf(line, "f %d/%d %d/%d %d/%d",
					&integers[0], &integers[6],
					&integers[1], &integers[7],
					&integers[2], &integers[8]) == 6)
				{
					tri_ok = true;
					has_uv = true;
				}
				else
				{
					printf("unrecognized sequence\n");
					printf("%s\n",line);
					while(1);
				}
				if ( tri_ok )
				{
					t.v[0] = integers[0]-1-vertex_cnt;
					t.v[1] = integers[1]-1-vertex_cnt;
					t.v[2] = integers[2]-1-vertex_cnt;
					t.attr = 0;

					if ( process_uv && has_uv )
					{
						std::vector<int> indices;
						indices.push_back(integers[6]-1-vertex_cnt);
						indices.push_back(integers[7]-1-vertex_cnt);
						indices.push_back(integers[8]-1-vertex_cnt);
						uvMap.push_back(indices);
						t.attr |= TEXCOORD;
					}

					t.material = material;
					//geo.triangles.push_back ( tri );
					triangles.push_back(t);
					//state_before = state;
					//state ='f';
				}
			}
		}

		if ( process_uv && uvs.size() )
		{
			loopi(0,triangles.size())
			{
				loopj(0,3)
				triangles[i].uvs[j] = uvs[uvMap[i][j]];
			}
		}

		fclose(fn);

		//printf("load_obj: vertices = %lu, triangles = %lu, uvs = %lu\n", vertices.size(), triangles.size(), uvs.size() );
	} // load_obj()

	// Optional : Store as OBJ

	void write_obj(const char* filename)
	{
		FILE *file=fopen(filename, "w");
		int cur_material = -1;
		bool has_uv = (triangles.size() && (triangles[0].attr & TEXCOORD) == TEXCOORD);

		if (!file)
		{
			printf("write_obj: can't write data file \"%s\".\n", filename);
			exit(0);
		}
		if (!mtllib.empty())
		{
			fprintf(file, "mtllib %s\n", mtllib.c_str());
		}
		loopi(0,vertices.size())
		{
			//fprintf(file, "v %lf %lf %lf\n", vertices[i].p.x,vertices[i].p.y,vertices[i].p.z);
			fprintf(file, "v %g %g %g\n", vertices[i].p.x,vertices[i].p.y,vertices[i].p.z); //more compact: remove trailing zeros
		}
		if (has_uv)
		{
			loopi(0,triangles.size()) if(!triangles[i].deleted)
			{
				fprintf(file, "vt %g %g\n", triangles[i].uvs[0].x, triangles[i].uvs[0].y);
				fprintf(file, "vt %g %g\n", triangles[i].uvs[1].x, triangles[i].uvs[1].y);
				fprintf(file, "vt %g %g\n", triangles[i].uvs[2].x, triangles[i].uvs[2].y);
			}
		}
		int uv = 1;
		loopi(0,triangles.size()) if(!triangles[i].deleted)
		{
			if (triangles[i].material != cur_material)
			{
				cur_material = triangles[i].material;
				fprintf(file, "usemtl %s\n", materials[triangles[i].material].c_str());
			}
			if (has_uv)
			{
				fprintf(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);
				uv += 3;
			}
			else
			{
				fprintf(file, "f %d %d %d\n", triangles[i].v[0]+1, triangles[i].v[1]+1, triangles[i].v[2]+1);
			}
			//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
		}
		fclose(file);
	}

}

================================================
FILE: fast_simplification/Simplify.h
================================================
/////////////////////////////////////////////
//
// Mesh Simplification Tutorial
//
// (C) by Sven Forstmann in 2014
//
// License : MIT
// http://opensource.org/licenses/MIT
//
//https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification
//
// 5/2016: Chris Rorden created minimal version for OSX/Linux/Windows compile

#include <iostream>
//#include <stddef.h>
//#include <functional>
//#include <sys/stat.h>
//#include <stdbool.h>
#include <string.h>
//#include <ctype.h>
//#include <float.h>
#include <stdio.h>
#include <stdlib.h>
#include <map>
#include <vector>
#include <utility> // std::pair
#include <string>
#include <math.h>
#include <stdint.h>
#include <float.h> //FLT_EPSILON, DBL_EPSILON

#define loopi(start_l,end_l) for ( int i=start_l;i<end_l;++i )
#define loopi(start_l,end_l) for ( int i=start_l;i<end_l;++i )
#define loopj(start_l,end_l) for ( int j=start_l;j<end_l;++j )
#define loopk(start_l,end_l) for ( int k=start_l;k<end_l;++k )

struct vector3
{
double x, y, z;
};

struct vec3f
{
    double x, y, z;

    inline vec3f( void ) {}

    //inline vec3f operator =( vector3 a )
	// { vec3f b ; b.x = a.x; b.y = a.y; b.z = a.z; return b;}

    inline vec3f( vector3 a )
	 { x = a.x; y = a.y; z = a.z; }

    inline vec3f( const double X, const double Y, const double Z )
    { x = X; y = Y; z = Z; }

    inline vec3f operator + ( const vec3f& a ) const
    { return vec3f( x + a.x, y + a.y, z + a.z ); }

	inline vec3f operator += ( const vec3f& a ) const
    { return vec3f( x + a.x, y + a.y, z + a.z ); }

    inline vec3f operator * ( const double a ) const
    { return vec3f( x * a, y * a, z * a ); }

    inline vec3f operator * ( const vec3f a ) const
    { return vec3f( x * a.x, y * a.y, z * a.z ); }

    inline vec3f v3 () const
    { return vec3f( x , y, z ); }

    inline vec3f operator = ( const vector3 a )
    { x=a.x;y=a.y;z=a.z;return *this; }

    inline vec3f operator = ( const vec3f a )
    { x=a.x;y=a.y;z=a.z;return *this; }

    inline vec3f operator / ( const vec3f a ) const
    { return vec3f( x / a.x, y / a.y, z / a.z ); }

    inline vec3f operator - ( const vec3f& a ) const
    { return vec3f( x - a.x, y - a.y, z - a.z ); }

    inline vec3f operator / ( const double a ) const
    { return vec3f( x / a, y / a, z / a ); }

    inline double dot( const vec3f& a ) const
    { return a.x*x + a.y*y + a.z*z; }

    inline vec3f cross( const vec3f& a , const vec3f& b )
    {
		x = a.y * b.z - a.z * b.y;
		y = a.z * b.x - a.x * b.z;
		z = a.x * b.y - a.y * b.x;
		return *this;
	}

    inline double angle( const vec3f& v )
    {
		vec3f a = v , b = *this;
		double dot = v.x*x + v.y*y + v.z*z;
		double len = a.length() * b.length();
		if(len==0)len=0.00001f;
		double input = dot  / len;
		if (input<-1) input=-1;
		if (input>1) input=1;
		return (double) acos ( input );
	}

    inline double angle2( const vec3f& v , const vec3f& w )
    {
		vec3f a = v , b= *this;
		double dot = a.x*b.x + a.y*b.y + a.z*b.z;
		double len = a.length() * b.length();
		if(len==0)len=1;

		vec3f plane; plane.cross( b,w );

		if ( plane.x * a.x + plane.y * a.y + plane.z * a.z > 0 )
			return (double) -acos ( dot  / len );

		return (double) acos ( dot  / len );
	}

    inline vec3f rot_x( double a )
    {
		double yy = cos ( a ) * y + sin ( a ) * z;
		double zz = cos ( a ) * z - sin ( a ) * y;
		y = yy; z = zz;
		return *this;
	}
    inline vec3f rot_y( double a )
    {
		double xx = cos ( -a ) * x + sin ( -a ) * z;
		double zz = cos ( -a ) * z - sin ( -a ) * x;
		x = xx; z = zz;
		return *this;
	}
    inline void clamp( double min, double max )
    {
		if (x<min) x=min;
		if (y<min) y=min;
		if (z<min) z=min;
		if (x>max) x=max;
		if (y>max) y=max;
		if (z>max) z=max;
	}
    inline vec3f rot_z( double a )
    {
		double yy = cos ( a ) * y + sin ( a ) * x;
		double xx = cos ( a ) * x - sin ( a ) * y;
		y = yy; x = xx;
		return *this;
	}
    inline vec3f invert()
	{
		x=-x;y=-y;z=-z;return *this;
	}
    inline vec3f frac()
	{
		return vec3f(
			x-double(int(x)),
			y-double(int(y)),
			z-double(int(z))
			);
	}

    inline vec3f integer()
	{
		return vec3f(
			double(int(x)),
			double(int(y)),
			double(int(z))
			);
	}

    inline double length() const
    {
		return (double)sqrt(x*x + y*y + z*z);
	}

    inline vec3f normalize( double desired_length = 1 )
    {
		double square = sqrt(x*x + y*y + z*z);
		/*
		if (square <= 0.00001f )
		{
			x=1;y=0;z=0;
			return *this;
		}*/
		//double len = desired_length / square;
		x/=square;y/=square;z/=square;

		return *this;
	}
    static vec3f normalize( vec3f a );

	static void random_init();
	static double random_double();
	static vec3f random();

	static int random_number;

	double random_double_01(double a){
		double rnf=a*14.434252+a*364.2343+a*4213.45352+a*2341.43255+a*254341.43535+a*223454341.3523534245+23453.423412;
		int rni=((int)rnf)%100000;
		return double(rni)/(100000.0f-1.0f);
	}

	vec3f random01_fxyz(){
		x=(double)random_double_01(x);
		y=(double)random_double_01(y);
		z=(double)random_double_01(z);
		return *this;
	}

};

vec3f barycentric(const vec3f &p, const vec3f &a, const vec3f &b, const vec3f &c){
	vec3f v0 = b-a;
	vec3f v1 = c-a;
	vec3f v2 = p-a;
	double d00 = v0.dot(v0);
	double d01 = v0.dot(v1);
	double d11 = v1.dot(v1);
	double d20 = v2.dot(v0);
	double d21 = v2.dot(v1);
	double denom = d00*d11-d01*d01;
	double v = (d11 * d20 - d01 * d21) / denom;
	double w = (d00 * d21 - d01 * d20) / denom;
	double u = 1.0 - v - w;
	return vec3f(u,v,w);
}

vec3f interpolate(const vec3f &p, const vec3f &a, const vec3f &b, const vec3f &c, const vec3f attrs[3])
{
	vec3f bary = barycentric(p,a,b,c);
	vec3f out = vec3f(0,0,0);
	out = out + attrs[0] * bary.x;
	out = out + attrs[1] * bary.y;
	out = out + attrs[2] * bary.z;
	return out;
}

double min(double v1, double v2) {
	return fmin(v1,v2);
}


class SymetricMatrix {

	public:

	// Constructor

	SymetricMatrix(double c=0) { loopi(0,10) m[i] = c;  }

	SymetricMatrix(	double m11, double m12, double m13, double m14,
			            double m22, double m23, double m24,
			                        double m33, double m34,
			                                    double m44) {
			 m[0] = m11;  m[1] = m12;  m[2] = m13;  m[3] = m14;
			              m[4] = m22;  m[5] = m23;  m[6] = m24;
			                           m[7] = m33;  m[8] = m34;
			                                        m[9] = m44;
	}

	// Make plane

	SymetricMatrix(double a,double b,double c,double d)
	{
		m[0] = a*a;  m[1] = a*b;  m[2] = a*c;  m[3] = a*d;
		             m[4] = b*b;  m[5] = b*c;  m[6] = b*d;
		                          m[7 ] =c*c; m[8 ] = c*d;
		                                       m[9 ] = d*d;
	}

	double operator[](int c) const { return m[c]; }

	// Determinant

	double det(	int a11, int a12, int a13,
				int a21, int a22, int a23,
				int a31, int a32, int a33)
	{
		double det =  m[a11]*m[a22]*m[a33] + m[a13]*m[a21]*m[a32] + m[a12]*m[a23]*m[a31]
					- m[a13]*m[a22]*m[a31] - m[a11]*m[a23]*m[a32]- m[a12]*m[a21]*m[a33];
		return det;
	}

	const SymetricMatrix operator+(const SymetricMatrix& n) const
	{
		return SymetricMatrix( m[0]+n[0],   m[1]+n[1],   m[2]+n[2],   m[3]+n[3],
						                    m[4]+n[4],   m[5]+n[5],   m[6]+n[6],
						                                 m[ 7]+n[ 7], m[ 8]+n[8 ],
						                                              m[ 9]+n[9 ]);
	}

	SymetricMatrix& operator+=(const SymetricMatrix& n)
	{
		 m[0]+=n[0];   m[1]+=n[1];   m[2]+=n[2];   m[3]+=n[3];
		 m[4]+=n[4];   m[5]+=n[5];   m[6]+=n[6];   m[7]+=n[7];
		 m[8]+=n[8];   m[9]+=n[9];
		return *this;
	}

	double m[10];
};
///////////////////////////////////////////

namespace Simplify
{
	// Global Variables & Strctures
	enum Attributes {
		NONE,
		NORMAL = 2,
		TEXCOORD = 4,
		COLOR = 8
	};
	struct Triangle { int v[3];double err[4];int deleted,dirty,attr;vec3f n;vec3f uvs[3];int material; };
	struct Vertex { vec3f p;int tstart,tcount;SymetricMatrix q;int border;};
	struct Ref { int tid,tvertex; };
	std::vector<Triangle> triangles;
	std::vector<Vertex> vertices;
	std::vector<Ref> refs;
    std::string mtllib;
    std::vector<std::string> materials;

	std::vector<std::vector<int>> collapses;

	// Helper functions

	double vertex_error(SymetricMatrix q, double x, double y, double z);
	double calculate_error(int id_v1, int id_v2, vec3f &p_result);
	bool flipped(vec3f p,int i0,int i1,Vertex &v0,Vertex &v1,std::vector<int> &deleted);
	void update_uvs(int i0,const Vertex &v,const vec3f &p,std::vector<int> &deleted);
	void update_triangles(int i0,Vertex &v,std::vector<int> &deleted,int &deleted_triangles);
	void update_mesh(int iteration);
	void compact_mesh();
	//
	// Main simplification function
	//
	// target_count  : target nr. of triangles
	// agressiveness : sharpness to increase the threshold.
	//                 5..8 are good numbers
	//                 more iterations yield higher quality
	//

	void simplify_mesh(int target_count, double agressiveness=7, bool verbose=false)
	{

		// init
		loopi(0,triangles.size())
        {
            triangles[i].deleted=0;
        }

		// main iteration loop
		int deleted_triangles=0;
		std::vector<int> deleted0,deleted1;
		int triangle_count=triangles.size();
		//int iteration = 0;
		//loop(iteration,0,100)
		collapses.clear();
		for (int iteration = 0; iteration < 100; iteration ++)
		{

			if(triangle_count-deleted_triangles<=target_count)break;

			// update mesh once in a while
			if(iteration%5==0)
			{
				update_mesh(iteration);
			}

			// clear dirty flag
			loopi(0,triangles.size()) triangles[i].dirty=0;

			//
			// All triangles with edges below the threshold will be removed
			//
			// The following numbers works well for most models.
			// If it does not, try to adjust the 3 parameters
			//
			double threshold = 0.000000001*pow(double(iteration+3),agressiveness);

			// target number of triangles reached ? Then break
			if ((verbose) && (iteration%5==0)) {
				printf("iteration %d - triangles %d threshold %g\n",iteration,triangle_count-deleted_triangles, threshold);
			}

			// remove vertices & mark deleted triangles
			loopi(0,triangles.size())
			{
				Triangle &t=triangles[i];
				if(t.err[3]>threshold) continue;
				if(t.deleted) continue;
				if(t.dirty) continue;

				loopj(0,3)if(t.err[j]<threshold)
				{

					int i0=t.v[ j     ]; Vertex &v0 = vertices[i0];
					int i1=t.v[(j+1)%3]; Vertex &v1 = vertices[i1];
					// Border check
					if(v0.border != v1.border)  continue;

					// Compute vertex to collapse to
					vec3f p;
					calculate_error(i0,i1,p); // p is the optimal point to collapse to
					deleted0.resize(v0.tcount); // normals temporarily
					deleted1.resize(v1.tcount); // normals temporarily
					// don't remove if flipped
					if( flipped(p,i0,i1,v0,v1,deleted0) ) continue;

					if( flipped(p,i1,i0,v1,v0,deleted1) ) continue;

					if ( (t.attr & TEXCOORD) == TEXCOORD  )
					{
						update_uvs(i0,v0,p,deleted0);
						update_uvs(i0,v1,p,deleted1);
					}

					// not flipped, so remove edge
					// v0 <- v1 (i0 <- i1)
					v0.p=p; // set the optimal point to collapse to
					v0.q=v1.q+v0.q; // add the quadrics
					int tstart=refs.size();

					// update triangles affected by the collapse
					update_triangles(i0,v0,deleted0,deleted_triangles);
					update_triangles(i0,v1,deleted1,deleted_triangles);

					// record collapse
					collapses.push_back(std::vector<int>({i0,i1}));

					int tcount=refs.size()-tstart;

					if(tcount<=v0.tcount)
					{
						// save ram
						if(tcount)memcpy(&refs[v0.tstart],&refs[tstart],tcount*sizeof(Ref));

					}
					else
						// append
						v0.tstart=tstart;

					v0.tcount=tcount;
					break;
				}
				// done?
				if(triangle_count-deleted_triangles<=target_count)break;
			}
		}
		// clean up mesh
		compact_mesh();

	} //simplify_mesh()

	void simplify_mesh_lossless(bool verbose=false)
	{
		// init
		loopi(0,triangles.size()) triangles[i].deleted=0;

		// main iteration loop
		int deleted_triangles=0;
		std::vector<int> deleted0,deleted1;
		int triangle_count=triangles.size();
		//int iteration = 0;
		//loop(iteration,0,100)
		collapses.clear();
		for (int iteration = 0; iteration < 9999; iteration ++)
		{
			// update mesh constantly
			update_mesh(iteration);
			// clear dirty flag
			loopi(0,triangles.size()) triangles[i].dirty=0;
			//
			// All triangles with edges below the threshold will be removed
			//
			// The following numbers works well for most models.
			// If it does not, try to adjust the 3 parameters
			//
			double threshold = DBL_EPSILON; //1.0E-3 EPS;
			if (verbose) {
				printf("lossless iteration %d\n", iteration);
			}

			// remove vertices & mark deleted triangles
			loopi(0,triangles.size())
			{
				Triangle &t=triangles[i];
				if(t.err[3]>threshold) continue;
				if(t.deleted) continue;
				if(t.dirty) continue;

				loopj(0,3)if(t.err[j]<threshold)
				{
					int i0=t.v[ j     ]; Vertex &v0 = vertices[i0];
					int i1=t.v[(j+1)%3]; Vertex &v1 = vertices[i1];

					// Border check
					if(v0.border != v1.border)  continue;

					// Compute vertex to collapse to
					vec3f p;
					calculate_error(i0,i1,p); // p is the optimal point to collapse to

					deleted0.resize(v0.tcount); // normals temporarily
					deleted1.resize(v1.tcount); // normals temporarily

					// don't remove if flipped
					if( flipped(p,i0,i1,v0,v1,deleted0) ) continue;
					if( flipped(p,i1,i0,v1,v0,deleted1) ) continue;

					if ( (t.attr & TEXCOORD) == TEXCOORD )
					{
						update_uvs(i0,v0,p,deleted0);
						update_uvs(i0,v1,p,deleted1);
					}

					// not flipped, so remove edge
					// v0 <- v1 (i0 <- i1)
					v0.p=p; // set the optimal point to collapse to
					v0.q=v1.q+v0.q; // add the quadrics (for calculating the error)
					int tstart=refs.size();

					update_triangles(i0,v0,deleted0,deleted_triangles);
					update_triangles(i0,v1,deleted1,deleted_triangles);

					// record collapse
					collapses.push_back(std::vector<int>({i0,i1}));

					int tcount=refs.size()-tstart;

					if(tcount<=v0.tcount)
					{
						// save ram
						if(tcount)memcpy(&refs[v0.tstart],&refs[tstart],tcount*sizeof(Ref));
					}
					else
						// append
						v0.tstart=tstart;

					v0.tcount=tcount;
					break;
				}
			}
			if(deleted_triangles<=0)break;
			deleted_triangles=0;
		} //for each iteration
		// clean up mesh
		compact_mesh();
	} //simplify_mesh_lossless()


	// Check if a triangle flips when this edge is removed

	bool flipped(vec3f p,int i0,int i1,Vertex &v0,Vertex &v1,std::vector<int> &deleted)
	{

		loopk(0,v0.tcount)
		{
			Triangle &t=triangles[refs[v0.tstart+k].tid];
			if(t.deleted)continue;

			int s=refs[v0.tstart+k].tvertex;
			int id1=t.v[(s+1)%3];
			int id2=t.v[(s+2)%3];

			if(id1==i1 || id2==i1) // delete ?
			{

				deleted[k]=1;
				continue;
			}
			vec3f d1 = vertices[id1].p-p; d1.normalize();
			vec3f d2 = vertices[id2].p-p; d2.normalize();
			if(fabs(d1.dot(d2))>0.999) return true;
			vec3f n;
			n.cross(d1,d2);
			n.normalize();
			deleted[k]=0;
			if(n.dot(t.n)<0.2) return true;
		}
		return false;
	}

    // update_uvs

	void update_uvs(int i0,const Vertex &v,const vec3f &p,std::vector<int> &deleted)
	{
		loopk(0,v.tcount)
		{
			Ref &r=refs[v.tstart+k];
			Triangle &t=triangles[r.tid];
			if(t.deleted)continue;
			if(deleted[k])continue;
			vec3f p1=vertices[t.v[0]].p;
			vec3f p2=vertices[t.v[1]].p;
			vec3f p3=vertices[t.v[2]].p;
			t.uvs[r.tvertex] = interpolate(p,p1,p2,p3,t.uvs);
		}
	}

	// Update triangle connections and edge error after a edge is collapsed

	void update_triangles(int i0,Vertex &v,std::vector<int> &deleted,int &deleted_triangles)
	{
		vec3f p;
		loopk(0,v.tcount)
		{
			Ref &r=refs[v.tstart+k];
			Triangle &t=triangles[r.tid];
			if(t.deleted)continue;
			if(deleted[k])
			{
				t.deleted=1;
				deleted_triangles++;
				continue;
			}
			t.v[r.tvertex]=i0;
			t.dirty=1;
			t.err[0]=calculate_error(t.v[0],t.v[1],p);
			t.err[1]=calculate_error(t.v[1],t.v[2],p);
			t.err[2]=calculate_error(t.v[2],t.v[0],p);
			t.err[3]=min(t.err[0],min(t.err[1],t.err[2]));
			refs.push_back(r);
		}
	}

	// compact triangles, compute edge error and build reference list

	void update_mesh(int iteration)
	{
		if(iteration>0) // compact triangles
		{
			int dst=0;
			loopi(0,triangles.size())
			if(!triangles[i].deleted)
			{
				triangles[dst++]=triangles[i];
			}
			triangles.resize(dst);
		}
		//
		// Init Quadrics by Plane & Edge Errors
		//
		// required at the beginning ( iteration == 0 )
		// recomputing during the simplification is not required,
		// but mostly improves the result for closed meshes
		//
		if( iteration == 0 )
		{
			loopi(0,vertices.size())
				vertices[i].q=SymetricMatrix(0.0);
			loopi(0,vertices.size())
				vertices[i].border=0;

			loopi(0,triangles.size())
			{
				Triangle &t=triangles[i];
				vec3f n,p[3];
				loopj(0,3) p[j]=vertices[t.v[j]].p;
				n.cross(p[1]-p[0],p[2]-p[0]);
				n.normalize();
				t.n=n;
				loopj(0,3) vertices[t.v[j]].q =
					vertices[t.v[j]].q+SymetricMatrix(n.x,n.y,n.z,-n.dot(p[0]));
			}
			loopi(0,triangles.size())
			{
				// Calc Edge Error
				Triangle &t=triangles[i];vec3f p;
				loopj(0,3) t.err[j]=calculate_error(t.v[j],t.v[(j+1)%3],p);
				t.err[3]=min(t.err[0],min(t.err[1],t.err[2]));
			}
		}

		// Init Reference ID list
		loopi(0,vertices.size())
		{
			vertices[i].tstart=0;
			vertices[i].tcount=0;
		}
		loopi(0,triangles.size())
		{
			Triangle &t=triangles[i];
			loopj(0,3) vertices[t.v[j]].tcount++;
		}
		int tstart=0;
		loopi(0,vertices.size())
		{
			Vertex &v=vertices[i];
			v.tstart=tstart;
			tstart+=v.tcount;
			v.tcount=0;
		}

		// Write References
		refs.resize(triangles.size()*3);
		loopi(0,triangles.size())
		{
			Triangle &t=triangles[i];
			loopj(0,3)
			{
				Vertex &v=vertices[t.v[j]];
				refs[v.tstart+v.tcount].tid=i;
				refs[v.tstart+v.tcount].tvertex=j;
				v.tcount++;
			}
		}

		// Identify boundary : vertices[].border=0,1
		if( iteration == 0 )
		{
			std::vector<int> vcount,vids;

			loopi(0,vertices.size())
				vertices[i].border=0;

			loopi(0,vertices.size())
			{
				Vertex &v=vertices[i];
				vcount.clear();
				vids.clear();
				loopj(0,v.tcount)
				{
					int k=refs[v.tstart+j].tid;
					Triangle &t=triangles[k];
					loopk(0,3)
					{
						int ofs=0,id=t.v[k];
						while(ofs<vcount.size())
						{
							if(vids[ofs]==id)break;
							ofs++;
						}
						if(ofs==vcount.size())
						{
							vcount.push_back(1);
							vids.push_back(id);
						}
						else
							vcount[ofs]++;
					}
				}
				loopj(0,vcount.size()) if(vcount[j]==1)
					vertices[vids[j]].border=1;
			}
		}
	}

	// Finally compact mesh before exiting

	void compact_mesh()
	{
		int dst=0;
		loopi(0,vertices.size())
		{
			vertices[i].tcount=0;
		}
		loopi(0,triangles.size())
		if(!triangles[i].deleted)
		{
			Triangle &t=triangles[i];
			triangles[dst++]=t;
			loopj(0,3)vertices[t.v[j]].tcount=1;
		}
		triangles.resize(dst);
		dst=0;
		loopi(0,vertices.size())
		if(vertices[i].tcount)
		{
			vertices[i].tstart=dst;
			vertices[dst].p=vertices[i].p;
			dst++;
		}
		loopi(0,triangles.size())
		{
			Triangle &t=triangles[i];
			loopj(0,3)t.v[j]=vertices[t.v[j]].tstart;
		}
		vertices.resize(dst);
	}

	// Error between vertex and Quadric

	double vertex_error(SymetricMatrix q, double x, double y, double z)
	{
 		return   q[0]*x*x + 2*q[1]*x*y + 2*q[2]*x*z + 2*q[3]*x + q[4]*y*y
 		     + 2*q[5]*y*z + 2*q[6]*y + q[7]*z*z + 2*q[8]*z + q[9];
	}

	// Error for one edge

	double calculate_error(int id_v1, int id_v2, vec3f &p_result)
	{
		// compute interpolated vertex

		SymetricMatrix q = vertices[id_v1].q + vertices[id_v2].q;
		bool   border = vertices[id_v1].border & vertices[id_v2].border;
		double error=0;
		double det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7);
		if ( det != 0 && !border )
		{

			// q_delta is invertible
			p_result.x = -1/det*(q.det(1, 2, 3, 4, 5, 6, 5, 7 , 8));	// vx = A41/det(q_delta)
			p_result.y =  1/det*(q.det(0, 2, 3, 1, 5, 6, 2, 7 , 8));	// vy = A42/det(q_delta)
			p_result.z = -1/det*(q.det(0, 1, 3, 1, 4, 6, 2, 5,  8));	// vz = A43/det(q_delta)

			error = vertex_error(q, p_result.x, p_result.y, p_result.z);
		}
		else
		{
			// det = 0 -> try to find best result
			vec3f p1=vertices[id_v1].p;
			vec3f p2=vertices[id_v2].p;
			vec3f p3=(p1+p2)/2;
			double error1 = vertex_error(q, p1.x,p1.y,p1.z);
			double error2 = vertex_error(q, p2.x,p2.y,p2.z);
			double error3 = vertex_error(q, p3.x,p3.y,p3.z);
			error = min(error1, min(error2, error3));
			if (error1 == error) p_result=p1;
			if (error2 == error) p_result=p2;
			if (error3 == error) p_result=p3;
		}
		return error;
	}

	char *trimwhitespace(char *str)
	{
		char *end;

		// Trim leading space
		while(isspace((unsigned char)*str)) str++;

		if(*str == 0)  // All spaces?
		return str;

		// Trim trailing space
		end = str + strlen(str) - 1;
		while(end > str && isspace((unsigned char)*end)) end--;

		// Write new null terminator
		*(end+1) = 0;

		return str;
	}

	//Option : Load OBJ
	void load_obj(const char* filename, bool process_uv=false){
		vertices.clear();
		triangles.clear();
		// printf ( "Loading Objects %s ... \n",filename);
		FILE* fn;
		if(filename==NULL)		return ;
		if((char)filename[0]==0)	return ;
		if ((fn = fopen(filename, "rb")) == NULL)
		{
			printf ( "File %s not found!\n" ,filename );
			return;
		}
		char line[1000];
		memset ( line,0,1000 );
		int vertex_cnt = 0;
		int material = -1;
		std::map<std::string, int> material_map;
		std::vector<vec3f> uvs;
		std::vector<std::vector<int> > uvMap;

		while(fgets( line, 1000, fn ) != NULL)
		{
			Vertex v;
			vec3f uv;

			if (strncmp(line, "mtllib", 6) == 0)
			{
				mtllib = trimwhitespace(&line[7]);
			}
			if (strncmp(line, "usemtl", 6) == 0)
			{
				std::string usemtl = trimwhitespace(&line[7]);
				if (material_map.find(usemtl) == material_map.end())
				{
					material_map[usemtl] = materials.size();
					materials.push_back(usemtl);
				}
				material = material_map[usemtl];
			}

			if ( line[0] == 'v' && line[1] == 't' )
			{
				if ( line[2] == ' ' )
				if(sscanf(line,"vt %lf %lf",
					&uv.x,&uv.y)==2)
				{
					uv.z = 0;
					uvs.push_back(uv);
				} else
				if(sscanf(line,"vt %lf %lf %lf",
					&uv.x,&uv.y,&uv.z)==3)
				{
					uvs.push_back(uv);
				}
			}
			else if ( line[0] == 'v' )
			{
				if ( line[1] == ' ' )
				if(sscanf(line,"v %lf %lf %lf",
					&v.p.x,	&v.p.y,	&v.p.z)==3)
				{
					vertices.push_back(v);
				}
			}
			int integers[9];
			if ( line[0] == 'f' )
			{
				Triangle t;
				bool tri_ok = false;
                bool has_uv = false;

				if(sscanf(line,"f %d %d %d",
					&integers[0],&integers[1],&integers[2])==3)
				{
					tri_ok = true;
				}else
				if(sscanf(line,"f %d// %d// %d//",
					&integers[0],&integers[1],&integers[2])==3)
				{
					tri_ok = true;
				}else
				if(sscanf(line,"f %d//%d %d//%d %d//%d",
					&integers[0],&integers[3],
					&integers[1],&integers[4],
					&integers[2],&integers[5])==6)
				{
					tri_ok = true;
				}else
				if(sscanf(line,"f %d/%d/%d %d/%d/%d %d/%d/%d",
					&integers[0],&integers[6],&integers[3],
					&integers[1],&integers[7],&integers[4],
					&integers[2],&integers[8],&integers[5])==9)
				{
					tri_ok = true;
					has_uv = true;
				}else // Add Support for v/vt only meshes
				if (sscanf(line, "f %d/%d %d/%d %d/%d",
					&integers[0], &integers[6],
					&integers[1], &integers[7],
					&integers[2], &integers[8]) == 6)
				{
					tri_ok = true;
					has_uv = true;
				}
				else
				{
					printf("unrecognized sequence\n");
					printf("%s\n",line);
					while(1);
				}
				if ( tri_ok )
				{
					t.v[0] = integers[0]-1-vertex_cnt;
					t.v[1] = integers[1]-1-vertex_cnt;
					t.v[2] = integers[2]-1-vertex_cnt;
					t.attr = 0;

					if ( process_uv && has_uv )
					{
						std::vector<int> indices;
						indices.push_back(integers[6]-1-vertex_cnt);
						indices.push_back(integers[7]-1-vertex_cnt);
						indices.push_back(integers[8]-1-vertex_cnt);
						uvMap.push_back(indices);
						t.attr |= TEXCOORD;
					}

					t.material = material;
					//geo.triangles.push_back ( tri );
					triangles.push_back(t);
					//state_before = state;
					//state ='f';
				}
			}
		}

		if ( process_uv && uvs.size() )
		{
			loopi(0,triangles.size())
			{
				loopj(0,3)
				triangles[i].uvs[j] = uvs[uvMap[i][j]];
			}
		}

		fclose(fn);

		//printf("load_obj: vertices = %lu, triangles = %lu, uvs = %lu\n", vertices.size(), triangles.size(), uvs.size() );
	} // load_obj()

	// Optional : Store as OBJ

	void write_obj(const char* filename)
	{
		FILE *file=fopen(filename, "w");
		int cur_material = -1;
		bool has_uv = (triangles.size() && (triangles[0].attr & TEXCOORD) == TEXCOORD);

		if (!file)
		{
			printf("write_obj: can't write data file \"%s\".\n", filename);
			exit(0);
		}
		if (!mtllib.empty())
		{
			fprintf(file, "mtllib %s\n", mtllib.c_str());
		}
		loopi(0,vertices.size())
		{
			//fprintf(file, "v %lf %lf %lf\n", vertices[i].p.x,vertices[i].p.y,vertices[i].p.z);
			fprintf(file, "v %g %g %g\n", vertices[i].p.x,vertices[i].p.y,vertices[i].p.z); //more compact: remove trailing zeros
		}
		if (has_uv)
		{
			loopi(0,triangles.size()) if(!triangles[i].deleted)
			{
				fprintf(file, "vt %g %g\n", triangles[i].uvs[0].x, triangles[i].uvs[0].y);
				fprintf(file, "vt %g %g\n", triangles[i].uvs[1].x, triangles[i].uvs[1].y);
				fprintf(file, "vt %g %g\n", triangles[i].uvs[2].x, triangles[i].uvs[2].y);
			}
		}
		int uv = 1;
		loopi(0,triangles.size()) if(!triangles[i].deleted)
		{
			if (triangles[i].material != cur_material)
			{
				cur_material = triangles[i].material;
				fprintf(file, "usemtl %s\n", materials[triangles[i].material].c_str());
			}
			if (has_uv)
			{
				fprintf(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);
				uv += 3;
			}
			else
			{
				fprintf(file, "f %d %d %d\n", triangles[i].v[0]+1, triangles[i].v[1]+1, triangles[i].v[2]+1);
			}
			//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
		}
		fclose(file);
	}
};
///////////////////////////////////////////


================================================
FILE: fast_simplification/__init__.py
================================================
from ._version import __version__  # noqa: F401
from .replay import _map_isolated_points, replay_simplification  # noqa: F401
from .simplify import simplify, simplify_mesh  # noqa: F401


================================================
FILE: fast_simplification/_replay.pyx
================================================
# cython: language_level=3
# cython: boundscheck=False
# cython: wraparound=False
# cython: cdivision=True


import numpy as np

cimport numpy as np
from libc.stdint cimport int64_t
from libcpp cimport bool


cdef extern from "wrapper_replay.h" namespace "Replay":
    void load_arrays_int32(const int, const int, const int, float*, int*, int*)
    void load_arrays_int64(const int, const int, const int,  float*, int64_t*, int*)
    void replay_simplification()
    void get_points(float*)
    void get_triangles(int*)
    void get_collapses(int*)
    int get_faces_int32(int*)
    int get_faces_int32_no_padding(int*)
    int get_faces_int64(int64_t*)
    void write_obj(const char*)
    void load_obj(const char*, bool)
    int n_points()
    int n_triangles()
    int n_collapses()
    int load_triangles_from_vtk(const int, int*)
    void load_points(const int, float*)
    void load_collapses(const int, int*)

def load_int32(int n_points, int n_faces, int n_collapses, float [:, ::1] points, int [:, ::1] faces, int [:, ::1] collapses):
    load_arrays_int32(n_points, n_faces, n_collapses, &points[0, 0], &faces[0, 0], &collapses[0, 0])


def load_int64(
        int n_points, int n_faces, int n_collapses, float [:, ::1] points, int64_t [:, ::1] faces, int [:, ::1] collapses
):
    load_arrays_int64(n_points, n_faces, n_collapses, &points[0, 0], &faces[0, 0], &collapses[0, 0])


# def simplify(int target_count, double aggressiveness=7, bool verbose=False):
#     simplify_mesh(target_count, aggressiveness, verbose)

def replay():
    replay_simplification()


def save_obj(filename):
    py_byte_string = filename.encode('UTF-8')
    cdef char* c_filename = py_byte_string
    write_obj(c_filename)


def read(filename):
    py_byte_string = filename.encode('UTF-8')
    cdef char* c_filename = py_byte_string
    load_obj(c_filename, False)


def return_points():
    cdef float [:, ::1] points = np.empty((n_points(), 3), np.float32)
    get_points(&points[0, 0])
    return np.array(points)


def return_triangles():
    cdef int [:, ::1] triangles = np.empty((n_triangles(), 3), np.int32)
    get_triangles(&triangles[0, 0])
    return np.array(triangles)

def return_collapses():
    cdef int [:, ::1] collapses = np.empty((n_collapses(), 2), np.int32)
    get_collapses(&collapses[0, 0])
    return np.array(collapses)


def return_faces_int32_no_padding():
    """VTK formatted faces"""
    cdef int [::1] faces = np.empty(n_triangles()*3, np.int32)
    n_tri = get_faces_int32_no_padding(&faces[0])
    return np.array(faces[:n_tri*3])


def return_faces_int32():
    """VTK formatted faces"""
    cdef int [::1] faces = np.empty(n_triangles()*4, np.int32)
    n_tri = get_faces_int32(&faces[0])
    return np.array(faces[:n_tri*4])


def return_faces_int64():
    """VTK formatted faces"""
    cdef int64_t [::1] faces = np.empty(n_triangles()*4, np.int64)
    n_tri = get_faces_int64(&faces[0])
    return np.array(faces[:n_tri*4])


def load_from_vtk(int n_points, float [:, ::1] points, int [::1] faces, int n_faces):
    result = load_triangles_from_vtk(n_faces, &faces[0])
    if result:
        raise ValueError(
            "Input mesh ``mesh`` must consist of only triangles.\n"
            "Run ``.triangulate()`` to convert to an all triangle mesh."
        )
    load_points(n_points, &points[0, 0])


def compute_indice_mapping(int[:, :] collapses, int n_points):

    ''' Compute the mapping from original indices to new indices after collapsing
        edges

        (pure python implementation with numpy)
    '''

    # start with identity mapping
    indice_mapping = np.arange(n_points, dtype=int)

    # First round of mapping
    origin_indices = collapses[:, 1]
    indice_mapping[origin_indices] = collapses[:, 0]
    previous = np.zeros(len(indice_mapping))
    while not np.array_equal(previous, indice_mapping):
        previous = indice_mapping.copy()
        indice_mapping[origin_indices] = indice_mapping[
            indice_mapping[origin_indices]
        ]

    keep = np.setdiff1d(
        np.arange(n_points), collapses[:, 1]
    )  # Indices of the points that must be kept after decimation

    cdef int i = 0
    cdef int j = 0

    cdef int[:] application = np.zeros(n_points, dtype=np.int32)
    for i in range(n_points):
        if j == len(keep):
            break
        if i == keep[j]:
            application[i] = j
            j += 1

    indice_mapping = np.array(application)[indice_mapping]

    return indice_mapping


def clean_triangles_and_edges(int[:, :] mapped_triangles, bool clean_edges=False):
    """Return the edges and triangles of a mesh from mapped triangles

    Args:
        mapped_triangles (np.ndarray): Mapped triangles
        clean_edges (bool, optional): If True, remove duplicated edges.

    Returns:
        np.ndarray: Edges
        np.ndarray: Triangles
    """

    cdef int i, j, k, l
    cdef int n_edges = 0
    cdef int n_triangles = 0
    cdef int N = len(mapped_triangles)
    cdef int[:, :] edges_with_rep = np.zeros((N, 2), dtype=np.int32)
    cdef int[:, :] triangles = np.zeros((N, 3), dtype=np.int32)

    for i in range(N):
        j = mapped_triangles[i, 0]
        k = mapped_triangles[i, 1]
        l = mapped_triangles[i, 2]

        if j != k and j != l and k != l:
            triangles[n_triangles, 0] = j
            triangles[n_triangles, 1] = k
            triangles[n_triangles, 2] = l
            n_triangles += 1

        elif j != k:
            # j, k = np.sort([j, k])
            edges_with_rep[n_edges, 0] = j
            edges_with_rep[n_edges, 1] = k
            n_edges += 1

        elif j != l:
            # j, l = np.sort([j, l])
            edges_with_rep[n_edges, 0] = j
            edges_with_rep[n_edges, 1] = l
            n_edges += 1

        elif l != k:
            # l, k = np.sort([j, k])
            edges_with_rep[n_edges, 0] = l
            edges_with_rep[n_edges, 1] = k
            n_edges += 1

    if not clean_edges:

        return np.asarray(edges_with_rep)[:n_edges, :], np.asarray(triangles)[:n_triangles, :]


    cdef int[:, :] edges = np.zeros((n_edges, 2), dtype=np.int32)


    # Lexicographic sort
    cdef int[:] order = np.lexsort((np.asarray(edges_with_rep[:n_edges, 1]), np.asarray(edges_with_rep[:n_edges, 0])))
    # Remove duplicates
    cdef int n_keep_edges = 1
    edges[0, :] = edges_with_rep[order[0], :]
    print(f"n_edges : {n_edges}")
    for i in range(1, n_edges):
        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]):
            edges[n_keep_edges, :] = edges_with_rep[order[i], :]
            n_keep_edges += 1


    return np.asarray(edges)[:n_keep_edges, :], np.asarray(triangles)[:n_triangles, :]


================================================
FILE: fast_simplification/_simplify.pyx
================================================
# cython: language_level=3
# cython: boundscheck=False
# cython: wraparound=False
# cython: cdivision=True


import numpy as np

cimport numpy as np
from libc.stdint cimport int64_t
from libcpp cimport bool


cdef extern from "wrapper.h" namespace "Simplify":
    void load_arrays_int32(const int, const int, double*, int*)
    void load_arrays_int64(const int, const int, double*, int64_t*)
    void simplify_mesh(int, double aggressiveness, bool verbose)
    void simplify_mesh_lossless(bool)
    void get_points(double*)
    void get_triangles(int*)
    void get_collapses(int*)
    int get_faces_int32(int*)
    int get_faces_int32_no_padding(int*)
    int get_faces_int64(int64_t*)
    void write_obj(const char*)
    void load_obj(const char*, bool)
    int n_points()
    int n_triangles()
    int n_collapses()
    int load_triangles_from_vtk(const int, int*)
    void load_points(const int, double*)



def load_int32(int n_points, int n_faces, double [:, ::1] points, int [:, ::1] faces):
    load_arrays_int32(n_points, n_faces, &points[0, 0], &faces[0, 0])


def load_int64(
        int n_points, int n_faces, double [:, ::1] points, int64_t [:, ::1] faces
):
    load_arrays_int64(n_points, n_faces, &points[0, 0], &faces[0, 0])


def simplify(int target_count, double aggressiveness=7, bool verbose=False):
    simplify_mesh(target_count, aggressiveness, verbose)

def simplify_lossless(bool verbose=False):
    simplify_mesh_lossless(verbose)


def save_obj(filename):
    py_byte_string = filename.encode('UTF-8')
    cdef char* c_filename = py_byte_string
    write_obj(c_filename)


def read(filename):
    py_byte_string = filename.encode('UTF-8')
    cdef char* c_filename = py_byte_string
    load_obj(c_filename, False)


def return_points():
    cdef double [:, ::1] points = np.empty((n_points(), 3), np.float64)
    get_points(&points[0, 0])
    return np.array(points)


def return_triangles():
    cdef int [:, ::1] triangles = np.empty((n_triangles(), 3), np.int32)
    get_triangles(&triangles[0, 0])
    return np.array(triangles)

def return_collapses():
    cdef int [:, ::1] collapses = np.empty((n_collapses(), 2), np.int32)
    get_collapses(&collapses[0, 0])
    return np.array(collapses)


def return_faces_int32_no_padding():
    """VTK formatted faces"""
    cdef int [::1] faces = np.empty(n_triangles()*3, np.int32)
    n_tri = get_faces_int32_no_padding(&faces[0])
    return np.array(faces[:n_tri*3])


def return_faces_int32():
    """VTK formatted faces"""
    cdef int [::1] faces = np.empty(n_triangles()*4, np.int32)
    n_tri = get_faces_int32(&faces[0])
    return np.array(faces[:n_tri*4])


def return_faces_int64():
    """VTK formatted faces"""
    cdef int64_t [::1] faces = np.empty(n_triangles()*4, np.int64)
    n_tri = get_faces_int64(&faces[0])
    return np.array(faces[:n_tri*4])


def load_from_vtk(int n_points, double [:, ::1] points, int [::1] faces, int n_faces):
    result = load_triangles_from_vtk(n_faces, &faces[0])
    if result:
        raise ValueError(
            "Input mesh ``mesh`` must consist of only triangles.\n"
            "Run ``.triangulate()`` to convert to an all triangle mesh."
        )
    load_points(n_points, &points[0, 0])


================================================
FILE: fast_simplification/_version.py
================================================
"""fast_simplification version

On the ``main`` branch, use 'dev0' to denote a development version.
For example:

version_info = 0, 27, 'dev0'

"""

version_info = 0, 2, "dev0"
__version__ = ".".join(map(str, version_info))


================================================
FILE: fast_simplification/fast_simplification.py
================================================
def simplify():
    pass


================================================
FILE: fast_simplification/replay.py
================================================
import numpy as np

from . import _replay
from .utils import ascontiguous


def _map_isolated_points(points, edges, triangles, return_outliers=False):
    r"""Map the isolated points to the triangles.

    (points, edges, triangles) represents a structure. The goal of this function
    is to compute a mapping array such that the points that are not in the triangles
    but are in the edges are merged into the points that are in the triangles, with
    respect to the edges. An example is given below.

          (1)
         / | \\
      (0)  |  (2)-3
         \ | /  \\
          (4)    6-9
           |
           5     8-7

    In this example, the points 5, 3, 4, 7, 8, 9 are not connected to any triangle.
    The expected mapping is:

    0 -> 0
    1 -> 1
    2 -> 2
    3 -> 2
    4 -> 4
    5 -> 4
    6 -> 2
    7 -> 7 (7 cannot be merged into any point in the triangles)
    8 -> 8 (8 cannot be merged into any point in the triangles)
    9 -> 2

    The output will be the mapping array and the merged points array. In this example,
    the mapping array is [0, 1, 2, 2, 4, 4, 2, 7, 8, 2] and the merged points array is
    [3, 5, 6, 9]. The points 7 and 8 are outliers. If return_outliers is True,
    the function will return the mapping array, the merged points array and the
    isolated points array. Else, the function will return the mapping array and the
    merged points array.

    Parameters
    ----------
        points : sequence
            array of points
        edges : sequence
            array of edges
        triangles : sequence
            array of triangles
        return_outsider : bool
            if True, return the outliers

    Returns
    -------
        np.ndarray
            mapping array
        np.ndarray
            merged points array
    """
    n_points = points.shape[0]

    # The points to connect are the points that are not in the triangles
    # but are in the edges
    points_to_connect = np.intersect1d(
        np.setdiff1d(np.arange(n_points), np.unique(triangles)), np.unique(edges)
    )
    # Start with the identity mapping
    mapping = np.arange(n_points, dtype=np.int64)

    # Remove edges that do not contains points to connect
    edges = edges[np.isin(edges, points_to_connect).any(axis=1)]

    n_edges = edges.shape[0]
    n_edges_old = 0

    # Iterate until there is no more edges to collapse
    # or until a statiionary state is reached
    while n_edges > 0 and n_edges != n_edges_old:
        n_edges_old = n_edges

        # Edges that connect two points to connect
        # are kept for the next iteration
        keep = np.isin(edges, points_to_connect).all(axis=1)

        # Edges that connect a point to connect to a point
        # that is not to connect are merged
        connexions = edges[~keep]

        a = np.isin(connexions, points_to_connect)
        merged = connexions[np.where(a)]
        target = connexions[np.where(~a)]

        # Update the mapping array and the points to connect
        mapping[merged] = mapping[target]
        points_to_connect = np.setdiff1d(points_to_connect, merged)

        # Remove the edges that are merged
        edges = edges[keep]
        # Remove edges that do not contains points to connect
        edges = edges[np.isin(edges, points_to_connect).any(axis=1)]
        n_edges = edges.shape[0]

    # The points that have been merged are the ones
    # such that mapping[i] != i
    merged_points = np.where(mapping != np.arange(len(mapping)))[0]

    if return_outliers:
        isolated_points = points_to_connect
        return mapping, merged_points, isolated_points
    return mapping, merged_points


@ascontiguous
def replay_simplification(points, triangles, collapses):
    """Replay the decimation of a triangular mesh.

    Parameters
    ----------
    points : sequence
        A ``(n, 3)`` array of points. May be a ``numpy.ndarray`` or a
        list of points. For efficiency, provide points as a float32
        array.
    triangles : sequence
        A ``(n, 3)`` array of triangle indices. May be a
        ``numpy.ndarray`` or a list of triangle indices. For
        efficiency, provide points as a float32 array.
    collapses : sequence
        The collapses to replay.
        A ``(n, 2)`` numpy.ndarray of collapses.
        ``collapses[i] = [i0, i1]`` means that during the i-th
        collapse, the vertex ``i1`` was collapsed into the vertex
        ``i0``.

    Returns
    -------
    np.ndarray
        Points array.
    np.ndarray
        Triangles array.
    np.ndarray
        indice_mapping array.
        A ``(n,)`` array of indices.
        ``indice_mapping[i] = j`` means that the vertex ``i`` of
        the original mesh was mapped to the vertex ``j`` of the
        decimated mesh.

    """
    import numpy as np

    if not isinstance(points, np.ndarray):
        points = np.array(points, dtype=np.float32)
    if not isinstance(triangles, np.ndarray):
        triangles = np.array(triangles, dtype=np.int32)

    if points.ndim != 2:
        raise ValueError("``points`` array must be 2 dimensional")
    if points.shape[1] != 3:
        raise ValueError(f"Expected ``points`` array to be (n, 3), not {points.shape}")

    if triangles.ndim != 2:
        raise ValueError("``triangles`` array must be 2 dimensional")
    if triangles.shape[1] != 3:
        raise ValueError(f"Expected ``triangles`` array to be (n, 3), not {triangles.shape}")

    if not triangles.flags.c_contiguous:
        triangles = np.ascontiguousarray(triangles)

    if triangles.dtype == np.int32:
        load = _replay.load_int32
    elif triangles.dtype == np.int64:
        load = _replay.load_int64
    else:
        load = _replay.load_int32
        triangles = triangles.astype(np.int32)

    # Collapse the points
    n_faces = triangles.shape[0]
    n_points = points.shape[0]
    load(n_points, n_faces, collapses.shape[0], points, triangles, collapses)
    _replay.replay()
    dec_points = _replay.return_points()

    # Compute the indice mapping
    indice_mapping = _replay.compute_indice_mapping(collapses, len(points))

    # compute the new triangles
    # Apply the indice mapping to the triangles
    mapped_triangles = indice_mapping[triangles.copy()]

    # Extract the edges and the triangles
    # Edges can be repeated, but this is not a problem
    # and it is faster to do so
    dec_edges, dec_triangles = _replay.clean_triangles_and_edges(mapped_triangles)

    # Map the isolated points to the triangles
    mapping, points_to_merge, outliers = _map_isolated_points(
        dec_points, dec_edges, dec_triangles, return_outliers=True
    )

    dec_triangles = mapping[dec_triangles]
    indice_mapping = mapping[indice_mapping]

    points_to_merge = np.union1d(points_to_merge, outliers)
    # Remove the isolated points
    # isolated_points = new_collapses[:, 1]
    points_to_merge = np.sort(points_to_merge)[::-1]
    mapping = np.arange(dec_points.shape[0])
    for ip in points_to_merge:
        dec_points = np.delete(dec_points, ip, axis=0)
        mapping[ip:] -= 1
    indice_mapping = mapping[indice_mapping]
    dec_triangles = mapping[dec_triangles]

    return dec_points, dec_triangles, indice_mapping


================================================
FILE: fast_simplification/simplify.py
================================================
"""Simplification library."""

from typing import TYPE_CHECKING

import numpy as np
from numpy.typing import NDArray

from . import _simplify
from .utils import ascontiguous

if TYPE_CHECKING:
    try:
        from pyvista.core.pointset import PolyData
    except ModuleNotFoundError:
        pass


def _check_args(target_reduction, target_count, n_faces):
    """Check arguments."""
    if target_reduction and target_count:
        raise ValueError("You may specify ``target_reduction`` or ``target_count``, but not both")
    if target_reduction is None and target_count is None:
        raise ValueError("You must specify ``target_reduction`` or ``target_count``")

    if target_reduction is not None:
        if target_reduction > 1 or target_reduction < 0:
            raise ValueError("``target_reduction`` must be between 0 and 1")
        target_count = (1 - target_reduction) * n_faces

    if target_count < 0:
        raise ValueError("``target_count`` must be greater than 0")
    if target_count > n_faces:
        raise ValueError(f"``target_count`` must be less than the number of faces {n_faces}")
    return int(target_count)


@ascontiguous
def simplify(
    points: NDArray[np.float64],
    triangles: NDArray[np.int32],
    target_reduction: float | None = None,
    target_count: int | None = None,
    agg: float = 7.0,
    verbose: bool = False,
    return_collapses: bool = False,
    lossless: bool = False,
) -> (
    tuple[NDArray[np.float64], NDArray[np.int64]]
    | tuple[NDArray[np.float64], NDArray[np.int64], NDArray[np.int64]]
):
    """Simplify a triangular mesh.

    Parameters
    ----------
    points : sequence[float | double]
        A ``(n, 3)`` array of points. May be a ``numpy.ndarray`` or a
        sequence of points. Internally converted to double precision.
    triangles : sequence
        A ``(n, 3)`` array of triangle indices. May be a
        ``numpy.ndarray`` or a list of triangle indices.
    target_reduction : float, optional
        Fraction of the original mesh to remove.  If set to ``0.9``,
        this function will try to reduce the data set to 10% of its
        original size and will remove 90% of the input triangles. Use
        this parameter or ``target_count``.
    target_count : int, optional
        Target number of triangles to reduce mesh to.  This may be
        used in place of ``target_reduction``, but both cannot be set.
    agg : float, default: 7.0
        Controls how aggressively to decimate the mesh.  A value of 10 will
        result in a fast decimation at the expense of mesh quality and shape.
        A value of 0 will attempt to preserve the original mesh geometry at the
        expense of time.  Setting a low value may result in being unable to
        reach the ``target_reduction`` or ``target_count``.
    verbose : bool, optional
        Enable verbose output when simplifying the mesh.
    return_collapses : bool, optional
        If True, return the history of collapses as a ``(n_collapses, 2)``
        array of indices.  ``collapses[i] = [i0, i1]`` means that durint the
        i-th collapse, the vertex ``i1`` was collapsed into the vertex ``i0``.
    lossless : bool, optional
        If True, simplify the mesh losslessly.

    Returns
    -------
    np.ndarray
        Points array.
    np.ndarray
        Triangles array.
    np.ndarray (optional)
        Collapses array.

    Examples
    --------
    This basic example demonstrates how to decimate a simple planar
    mesh composed by 8 triangles.

    >>> import fast_simplification
    >>> points = [
    ...     [0.5, -0.5, 0.0],
    ...     [0.0, -0.5, 0.0],
    ...     [-0.5, -0.5, 0.0],
    ...     [0.5, 0.0, 0.0],
    ...     [0.0, 0.0, 0.0],
    ...     [-0.5, 0.0, 0.0],
    ...     [0.5, 0.5, 0.0],
    ...     [0.0, 0.5, 0.0],
    ...     [-0.5, 0.5, 0.0],
    ... ]
    >>> faces = [
    ...     [0, 1, 3],
    ...     [4, 3, 1],
    ...     [1, 2, 4],
    ...     [5, 4, 2],
    ...     [3, 4, 6],
    ...     [7, 6, 4],
    ...     [4, 5, 7],
    ...     [8, 7, 5],
    ... ]
    >>> points_out, faces_out = fast_simplification.simplify(points, faces, 0.5)

    """

    points = np.asarray(points, dtype=np.float64)
    if not isinstance(triangles, np.ndarray):
        triangles = np.array(triangles, dtype=np.int32)

    if points.ndim != 2:
        raise ValueError("``points`` array must be 2 dimensional")
    if points.shape[1] != 3:
        raise ValueError(f"Expected ``points`` array to be (n, 3), not {points.shape}")

    if triangles.ndim != 2:
        raise ValueError("``triangles`` array must be 2 dimensional")
    if triangles.shape[1] != 3:
        raise ValueError(f"Expected ``triangles`` array to be (n, 3), not {triangles.shape}")

    n_faces = triangles.shape[0]

    triangles = np.ascontiguousarray(triangles)

    if triangles.dtype == np.int32:
        load = _simplify.load_int32
    elif triangles.dtype == np.int64:
        load = _simplify.load_int64
    else:
        load = _simplify.load_int32
        triangles = triangles.astype(np.int32)

    load(
        points.shape[0],
        n_faces,
        points,
        triangles,
    )

    if lossless:
        _simplify.simplify_lossless(verbose)
    else:
        target_count = _check_args(target_reduction, target_count, n_faces)
        _simplify.simplify(target_count, agg, verbose)
    points = _simplify.return_points()
    faces = _simplify.return_faces_int32_no_padding().reshape(-1, 3)

    if return_collapses:
        return points, faces, _simplify.return_collapses()
    return points, faces


def simplify_mesh(
    mesh: "PolyData",
    target_reduction: float | None = None,
    target_count: int | None = None,
    agg: float = 7.0,
    verbose: bool = False,
):
    """Simplify a pyvista mesh.

    Parameters
    ----------
    mesh : pyvista.PolyData
        PyVista mesh.
    target_reduction : float
        Fraction of the original mesh to remove. If set to ``0.9``,
        this function will try to reduce the data set to 10% of its
        original size and will remove 90% of the input triangles. Use
        this parameter or ``target_count``.
    target_count : int, optional
        Target number of triangles to reduce mesh to. This may be used in
        place of ``target_reduction``, but both cannot be set.
    agg : float, default: 7.0
        Controls how aggressively to decimate the mesh. A value of ``10.0`` will
        result in a fast decimation at the expense of mesh quality and shape.
        A value of ``0.0`` will attempt to preserve the original mesh geometry at the
        expense of time. Setting a low value may result in being unable to
        reach the ``target_reduction`` or ``target_count``.
    verbose : bool, optional
        Enable verbose output when simplifying the mesh.

    Returns
    -------
    pyvista.PolyData
        Simplified mesh. The field data of the mesh will contain a
        field named ``fast_simplification_collapses`` that contains
        the history of collapses as a ``(n_collapses, 2)`` array of
        indices. ``collapses[i] = [i0, i1]`` means that during the
        i-th collapse, the vertex ``i1`` was collapsed into the vertex
        ``i0``.

    """
    try:
        import pyvista as pv
    except ImportError:
        raise ImportError("Please install pyvista to use this feature with:\npip install pyvista")

    n_faces = mesh.n_cells
    _simplify.load_from_vtk(
        mesh.n_points,
        mesh.points.astype(np.float64, order="C", copy=False),
        mesh.faces.astype(np.int32, order="C", copy=False),
        n_faces,
    )

    target_count = _check_args(target_reduction, target_count, n_faces)
    _simplify.simplify(target_count, agg, verbose)

    # return the correct datatype of the faces
    if pv._get_vtk_id_type() == np.int32:
        faces = _simplify.return_faces_int32()
    else:
        faces = _simplify.return_faces_int64()

    # construct mesh
    mesh = pv.PolyData(_simplify.return_points(), faces, deep=False)
    mesh.field_data["fast_simplification_collapses"] = _simplify.return_collapses()

    return mesh


================================================
FILE: fast_simplification/utils.py
================================================
"""Utility functions for the fast_simplification package."""

import numpy as np


def ascontiguous(func):
    """A decorator that ensure that all the numpy arrays passed to the function
    are contiguous in memory and if not, apply np.ascontinguous arrays.
    """

    def wrapper(*args, **kwargs):
        args = list(args)
        for i, arg in enumerate(args):
            if isinstance(arg, np.ndarray):
                args[i] = np.ascontiguousarray(arg)

        for key, value in kwargs.items():
            if isinstance(value, np.ndarray):
                kwargs[key] = np.ascontiguousarray(value)

        return func(*args, **kwargs)

    # Copy annotations
    wrapper.__annotations__ = func.__annotations__
    return wrapper


================================================
FILE: fast_simplification/wrapper.h
================================================
// wrap simplify header file for integration with cython
#include "Simplify.h"

namespace Simplify{

  // load triangles
  void load_points(const int n_points, double* points){
    vertices.clear();
    // load vertices
    for (int ii = 0; ii < n_points; ii ++){
      Vertex v;
      v.p.x = points[0 + 3*ii];
      v.p.y = points[1 + 3*ii];
      v.p.z = points[2 + 3*ii];
      vertices.push_back(v);
    }
  }

  // load triangles
  void load_triangles(const int n_tri, int* faces){
    triangles.clear();
    for (int ii = 0; ii < n_tri; ii ++){
      Triangle t;
      t.attr = 0;
      t.material = -1;
      t.v[0] = faces[0 + 3*ii];
      t.v[1] = faces[1 + 3*ii];
      t.v[2] = faces[2 + 3*ii];
      triangles.push_back(t);
    }
  }

  // load triangles
  void load_triangles_int64(const int n_tri, int64_t* faces){
    triangles.clear();
    for (int ii = 0; ii < n_tri; ii ++){
      Triangle t;
      t.attr = 0;
      t.material = -1;
      t.v[0] = faces[0 + 3*ii];
      t.v[1] = faces[1 + 3*ii];
      t.v[2] = faces[2 + 3*ii];
      triangles.push_back(t);
    }
  }

  // load triangles from vtk and deal with padding
  int load_triangles_from_vtk(const int n_tri, int* faces){
    triangles.clear();
    for (int ii = 0; ii < n_tri; ii ++){
      Triangle t;
      t.attr = 0;
      t.material = -1;
      if (faces[4*ii] != 3){
        return 1;
      }
      t.v[0] = faces[1 + 4*ii];
      t.v[1] = faces[2 + 4*ii];
      t.v[2] = faces[3 + 4*ii];
      triangles.push_back(t);
    }
    return 0;
  }

  void load_arrays_int32(const int n_points, const int n_tri,
                         double* points, int* faces){
    load_points(n_points, points);
    load_triangles(n_tri, faces);
  }

  void load_arrays_int64(const int n_points, const int n_tri,
                         double* points, int64_t* faces){
    load_points(n_points, points);
    load_triangles_int64(n_tri, faces);
  }

  int n_points(){
    return vertices.size();
  }

  int n_triangles(){
    return triangles.size();
  }

  int n_collapses(){
    return collapses.size();
  }

  // load triangles
  void load_triangles(const int n_tri, int64_t* faces){
    triangles.clear();
    for (int ii = 0; ii < n_tri; ii ++){
      Triangle t;
      t.attr = 0;
      t.material = -1;
      t.v[0] = faces[0 + 3*ii];
      t.v[1] = faces[1 + 3*ii];
      t.v[2] = faces[2 + 3*ii];
      triangles.push_back(t);
    }
  }

  // populate a contiguous array with the points in the vertices vector
  void get_points(double* points){

    // load vertices
    int n_points = vertices.size();
    for (int ii = 0; ii < n_points; ii ++){
      points[0 + 3*ii] = vertices[ii].p.x;
      points[1 + 3*ii] = vertices[ii].p.y;
      points[2 + 3*ii] = vertices[ii].p.z;
    }
  }

  // populate a contiguous array with the points in the vertices vector
  void get_triangles(int* tri){

    // load vertices
    int n_tri = triangles.size();
    for (int ii = 0; ii < n_tri; ii ++){
      tri[0 + 3*ii] = triangles[ii].v[0];
      tri[1 + 3*ii] = triangles[ii].v[1];
      tri[2 + 3*ii] = triangles[ii].v[2];
    }
  }

  void get_collapses(int* coll){

    // load vertices
    int n_collapse = collapses.size();
    for (int ii = 0; ii < n_collapse; ii ++){
      coll[0 + 2*ii] = collapses.at(ii).at(0);
      coll[1 + 2*ii] = collapses.at(ii).at(1);
    }
  }

  // populate a contiguous array with the points in the vertices vector
  int get_faces_int32(int32_t* tri){

    // load vertices
    int n_tri = triangles.size();
    int jj = 0;
    for (int ii = 0; ii < n_tri; ii ++){
      if (!triangles[ii].deleted){
        tri[0 + 3*jj] = 3;
        tri[1 + 3*jj] = triangles[ii].v[0];
        tri[2 + 3*jj] = triangles[ii].v[1];
        tri[3 + 3*jj] = triangles[ii].v[2];
        jj += 1;
      }
    }
    return jj;
  }

  // populate a contiguous array with the points in the vertices
  // vector without the vtk padding
  int get_faces_int32_no_padding(int32_t* tri){

    // load vertices
    int n_tri = triangles.size();
    int jj = 0;
    for (int ii = 0; ii < n_tri; ii ++){
      if (!triangles[ii].deleted){
        tri[0 + 3*jj] = triangles[ii].v[0];
        tri[1 + 3*jj] = triangles[ii].v[1];
        tri[2 + 3*jj] = triangles[ii].v[2];
        jj += 1;
      }
    }
    return jj;
  }

  // populate a contiguous array with the points in the vertices vector
  int get_faces_int64(int64_t* tri){

    // load vertices
    int n_tri = triangles.size();
    int jj = 0;
    for (int ii = 0; ii < n_tri; ii ++){
      if (!triangles[ii].deleted){
        tri[0 + 4*jj] = 3;
        tri[1 + 4*jj] = triangles[ii].v[0];
        tri[2 + 4*jj] = triangles[ii].v[1];
        tri[3 + 4*jj] = triangles[ii].v[2];
        jj += 1;
      }
    }
    return jj;
  }
}


================================================
FILE: fast_simplification/wrapper_replay.h
================================================
// wrap simplify header file for integration with cython
#include "Replay.h"

namespace Replay{

  // load collapses
  void load_collapses(const int n_coll, int* coll){
    collapses.clear();
    for (int ii = 0; ii < n_coll; ii ++){
      std::vector<int> c;
      c.push_back(coll[0 + 2*ii]);
      c.push_back(coll[1 + 2*ii]);
      collapses.push_back(c);
    }
  }

  // load points
  void load_points(const int n_points, float* points){
    vertices.clear();
    // load vertices
    for (int ii = 0; ii < n_points; ii ++){
      Vertex v;
      v.p.x = points[0 + 3*ii];
      v.p.y = points[1 + 3*ii];
      v.p.z = points[2 + 3*ii];
      vertices.push_back(v);
    }
  }

  // load triangles
  void load_triangles(const int n_tri, int* faces){
    triangles.clear();
    for (int ii = 0; ii < n_tri; ii ++){
      Triangle t;
      t.attr = 0;
      t.material = -1;
      t.v[0] = faces[0 + 3*ii];
      t.v[1] = faces[1 + 3*ii];
      t.v[2] = faces[2 + 3*ii];
      triangles.push_back(t);
    }
  }

  // load triangles
  void load_triangles_int64(const int n_tri, int64_t* faces){
    triangles.clear();
    for (int ii = 0; ii < n_tri; ii ++){
      Triangle t;
      t.attr = 0;
      t.material = -1;
      t.v[0] = faces[0 + 3*ii];
      t.v[1] = faces[1 + 3*ii];
      t.v[2] = faces[2 + 3*ii];
      triangles.push_back(t);
    }
  }

  // load triangles from vtk and deal with padding
  int load_triangles_from_vtk(const int n_tri, int* faces){
    triangles.clear();
    for (int ii = 0; ii < n_tri; ii ++){
      Triangle t;
      t.attr = 0;
      t.material = -1;
      if (faces[4*ii] != 3){
        return 1;
      }
      t.v[0] = faces[1 + 4*ii];
      t.v[1] = faces[2 + 4*ii];
      t.v[2] = faces[3 + 4*ii];
      triangles.push_back(t);
    }
    return 0;
  }

  void load_arrays_int32(const int n_points, const int n_tri, const int n_coll,
                         float* points, int* faces, int* collapses){
    load_points(n_points, points);
    load_triangles(n_tri, faces);
    load_collapses(n_coll, collapses);
  }

  void load_arrays_int64(const int n_points, const int n_tri, const int n_coll,
                         float* points, int64_t* faces, int* collapses){
    load_points(n_points, points);
    load_triangles_int64(n_tri, faces);
    load_collapses(n_coll, collapses);
  }

  int n_points(){
    return vertices.size();
  }

  int n_triangles(){
    return triangles.size();
  }

  int n_collapses(){
    return collapses.size();
  }

  // load triangles
  void load_triangles(const int n_tri, int64_t* faces){
    triangles.clear();
    for (int ii = 0; ii < n_tri; ii ++){
      Triangle t;
      t.attr = 0;
      t.material = -1;
      t.v[0] = faces[0 + 3*ii];
      t.v[1] = faces[1 + 3*ii];
      t.v[2] = faces[2 + 3*ii];
      triangles.push_back(t);
    }
  }

  // populate a contiguous array with the points in the vertices vector
  void get_points(float* points){

    // load vertices
    int n_points = vertices.size();
    for (int ii = 0; ii < n_points; ii ++){
      points[0 + 3*ii] = vertices[ii].p.x;
      points[1 + 3*ii] = vertices[ii].p.y;
      points[2 + 3*ii] = vertices[ii].p.z;
    }
  }

  // populate a contiguous array with the points in the vertices vector
  void get_triangles(int* tri){

    // load vertices
    int n_tri = triangles.size();
    for (int ii = 0; ii < n_tri; ii ++){
      tri[0 + 3*ii] = triangles[ii].v[0];
      tri[1 + 3*ii] = triangles[ii].v[1];
      tri[2 + 3*ii] = triangles[ii].v[2];
    }
  }

  void get_collapses(int* coll){

    // load vertices
    int n_collapse = collapses.size();
    for (int ii = 0; ii < n_collapse; ii ++){
      coll[0 + 2*ii] = collapses.at(ii).at(0);
      coll[1 + 2*ii] = collapses.at(ii).at(1);
    }
  }

  // populate a contiguous array with the points in the vertices vector
  int get_faces_int32(int32_t* tri){

    // load vertices
    int n_tri = triangles.size();
    int jj = 0;
    for (int ii = 0; ii < n_tri; ii ++){
      if (!triangles[ii].deleted){
        tri[0 + 3*jj] = 3;
        tri[1 + 3*jj] = triangles[ii].v[0];
        tri[2 + 3*jj] = triangles[ii].v[1];
        tri[3 + 3*jj] = triangles[ii].v[2];
        jj += 1;
      }
    }
    return jj;
  }

  // populate a contiguous array with the points in the vertices
  // vector without the vtk padding
  int get_faces_int32_no_padding(int32_t* tri){

    // load vertices
    int n_tri = triangles.size();
    int jj = 0;
    for (int ii = 0; ii < n_tri; ii ++){
      if (!triangles[ii].deleted){
        tri[0 + 3*jj] = triangles[ii].v[0];
        tri[1 + 3*jj] = triangles[ii].v[1];
        tri[2 + 3*jj] = triangles[ii].v[2];
        jj += 1;
      }
    }
    return jj;
  }

  // populate a contiguous array with the points in the vertices vector
  int get_faces_int64(int64_t* tri){

    // load vertices
    int n_tri = triangles.size();
    int jj = 0;
    for (int ii = 0; ii < n_tri; ii ++){
      if (!triangles[ii].deleted){
        tri[0 + 4*jj] = 3;
        tri[1 + 4*jj] = triangles[ii].v[0];
        tri[2 + 4*jj] = triangles[ii].v[1];
        tri[3 + 4*jj] = triangles[ii].v[2];
        jj += 1;
      }
    }
    return jj;
  }
}


================================================
FILE: pyproject.toml
================================================
[build-system]
build-backend = "setuptools.build_meta"
requires = [
  "cython>=3.0.0",
  "numpy>=2,<3",
  "setuptools>=45.0",
  "wheel>=0.37.0"
]

[tool.cibuildwheel]
archs = ["auto64"]  # 64-bit only
before-build = "pip install abi3audit"
build = "cp310-* cp311-*"  # 3.11+ are abi3 wheels
skip = "*musllinux*"
test-command = "pytest {project}/tests"
test-requires = "pyvista pytest"

[tool.cibuildwheel.linux]
repair-wheel-command = [
  "auditwheel repair -w {dest_dir} {wheel}",
  "bash tools/audit_wheel.sh {wheel}"
]

[tool.cibuildwheel.macos]
archs = ["native"]
repair-wheel-command = [
  "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}",
  "bash tools/audit_wheel.sh {wheel}"
]

[tool.cibuildwheel.windows]
before-build = "pip install delvewheel abi3audit"
repair-wheel-command = [
  "delvewheel repair -w {dest_dir} {wheel}",
  "bash tools/audit_wheel.sh {wheel}"
]

[tool.codespell]
ignore-words-list = 'THIRDPARTY'
quiet-level = 3
skip = '*.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/*'

[tool.isort]
default_section = "THIRDPARTY"
force_sort_within_sections = true
line_length = 100
profile = "black"
skip_glob = ["__init__.py"]
src_paths = ["doc", "fast_simplification", "tests"]

[tool.pytest.ini_options]
filterwarnings = [
  # bogus numpy ABI warning (see numpy/#432)
  "ignore:.*numpy.dtype size changed.*:RuntimeWarning",
  "ignore:.*numpy.ufunc size changed.*:RuntimeWarning",
  "ignore:.*Distutils was imported before Setuptools*"
]
junit_family = "legacy"

[tool.ruff]
line-length = 100

[tool.ruff.lint]
ignore = []
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`)  codes by default.
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
# McCabe complexity (`C901`) by default.
select = ["E4", "E7", "E9", "F"]


================================================
FILE: pytest.ini
================================================
[pytest]
junit_family=legacy
filterwarnings =
    ignore::FutureWarning
    ignore::PendingDeprecationWarning
    ignore::DeprecationWarning
    # bogus numpy ABI warning (see numpy/#432)
    ignore:.*numpy.dtype size changed.*:RuntimeWarning
    ignore:.*numpy.ufunc size changed.*:RuntimeWarning
    ignore:.*Given trait value dtype "float64":UserWarning
doctest_optionflags = NUMBER ELLIPSIS


================================================
FILE: requirements_docs.txt
================================================
numpydoc>=1.8.0
pydata-sphinx-theme
pyvista
Sphinx>=4.0.0
sphinx-copybutton
sphinx-gallery>=0.8.1
sphinx-notfound-page>=0.3.0


================================================
FILE: requirements_test.txt
================================================
pytest
pyvista


================================================
FILE: setup.py
================================================
"""Setup for fast-simplification."""

import builtins
from io import open as io_open
import os
import platform
import sys

from setuptools import Extension, setup
from setuptools.command.build_ext import build_ext as _build_ext
from wheel.bdist_wheel import bdist_wheel

filepath = os.path.dirname(__file__)

# Define macros for cython
macros = []
ext_kwargs = {}
setup_kwargs = {"cmdclass": {}}
if os.name == "nt":  # windows
    extra_compile_args = ["/openmp", "/O2", "/w", "/GS"]
elif os.name == "posix":  # linux org mac os
    if sys.platform == "linux":
        extra_compile_args = ["-std=gnu++11", "-O3", "-w"]
    else:  # probably mac os
        extra_compile_args = ["-std=c++11", "-O3", "-w"]
else:
    raise OSError("Unsupported OS %s" % os.name)


# Check if 64-bit
if sys.maxsize > 2**32:
    macros.append(("IS64BITPLATFORM", None))


# https://github.com/joerick/python-abi3-package-sample/blob/main/setup.py
class bdist_wheel_abi3(bdist_wheel):  # noqa: D101
    def get_tag(self):  # noqa: D102
        python, abi, plat = super().get_tag()

        if python.startswith("cp"):
            return "cp311", "abi3", plat

        return python, abi, plat


if sys.version_info.minor >= 11 and platform.python_implementation() == "CPython":
    # Can create an abi3 wheel (typed memoryviews first available in 3.11)!
    macros.append(("Py_LIMITED_API", "0x030B0000"))
    ext_kwargs["py_limited_api"] = True
    setup_kwargs["cmdclass"]["bdist_wheel"] = bdist_wheel_abi3


# Get version from version info
__version__ = None
version_file = os.path.join(filepath, "fast_simplification", "_version.py")
with io_open(version_file, mode="r") as fd:
    exec(fd.read())

# readme file
readme_file = os.path.join(filepath, "README.rst")


# for: the cc1plus: warning: command line option '-Wstrict-prototypes'
class build_ext(_build_ext):
    def finalize_options(self):
        _build_ext.finalize_options(self)
        # prevent numpy from thinking it is still in its setup process:
        try:
            del builtins.__NUMPY_SETUP__
        except AttributeError:
            pass
        import numpy

        self.include_dirs.append(numpy.get_include())

    def build_extensions(self):
        try:
            self.compiler.compiler_so.remove("-Wstrict-prototypes")
        except (AttributeError, ValueError):
            pass
        _build_ext.build_extensions(self)


setup_kwargs["cmdclass"]["build_ext"] = build_ext

setup(
    name="fast_simplification",
    packages=["fast_simplification"],
    version=__version__,
    description="Wrapper around the Fast-Quadric-Mesh-Simplification library.",
    long_description=open(readme_file).read(),
    long_description_content_type="text/x-rst",
    author="Alex Kaszynski",
    author_email="akascap@gmail.com",
    license="MIT",
    classifiers=[
        "Development Status :: 4 - Beta",
        "Intended Audience :: Science/Research",
        "License :: OSI Approved :: MIT License",
        "Programming Language :: Python :: 3.10",
        "Programming Language :: Python :: 3.11",
        "Programming Language :: Python :: 3.12",
        "Programming Language :: Python :: 3.13",
        "Programming Language :: Python :: 3.14",
    ],
    url="https://github.com/pyvista/fast-simplification",
    python_requires=">=3.9",
    # Build cython modules
    ext_modules=[
        Extension(
            "fast_simplification._simplify",
            ["fast_simplification/_simplify.pyx"],
            language="c++",
            extra_compile_args=extra_compile_args,
            define_macros=macros,
            **ext_kwargs,
        ),
        Extension(
            "fast_simplification._replay",
            ["fast_simplification/_replay.pyx"],
            language="c++",
            extra_compile_args=extra_compile_args,
            define_macros=macros,
            **ext_kwargs,
        ),
    ],
    keywords="fast-simplification decimation",
    install_requires=["numpy"],
    **setup_kwargs,
)


================================================
FILE: tests/test_map_isolated_points.py
================================================
import numpy as np

from fast_simplification import _map_isolated_points as map_isolated_points


def test_map_isolated_points():
    # Example 1
    #
    #      (1)
    #     / | \
    #  (0)  |  (2)-3
    #     \ | /  \
    #      (4)    6-9
    #       |
    #       5     8-7

    points = np.random.rand(10, 3)

    edges = np.array(
        [
            [0, 1],
            [0, 4],
            [1, 4],
            [1, 2],
            [2, 4],
            [2, 3],
            [2, 6],
            [6, 9],
            [4, 5],
            [8, 7],
        ],
        dtype=np.int64,
    )

    triangles = np.array(
        [
            [0, 1, 4],
            [1, 2, 4],
        ],
        dtype=np.int64,
    )

    target_mapping = np.array([0, 1, 2, 2, 4, 4, 2, 7, 8, 2], dtype=np.int64)

    target_merged_points = np.array([3, 5, 6, 9], dtype=np.int64)

    mapping, merged_points = map_isolated_points(points, edges, triangles)
    assert np.allclose(mapping, target_mapping)
    assert np.allclose(merged_points, target_merged_points)

    # Example 2
    #
    # (7)-(8)       3
    #   \  |
    #    \ |
    #     (0)-1-2-9-4-5-6

    points = np.random.rand(10, 3)

    edges = np.array(
        [
            [0, 1],
            [1, 2],
            [2, 9],
            [9, 4],
            [4, 5],
            [5, 6],
        ],
        dtype=np.int64,
    )

    triangles = np.array(
        [
            [0, 7, 8],
        ]
    )

    target_mapping = np.array([0, 0, 0, 3, 0, 0, 0, 7, 8, 0], dtype=np.int64)

    target_merged_points = np.array([1, 2, 4, 5, 6, 9], dtype=np.int64)

    mapping, merged_points = map_isolated_points(points, edges, triangles)
    assert np.allclose(mapping, target_mapping)
    assert np.allclose(merged_points, target_merged_points)

    # Example 3
    #
    # (1)
    #  | \
    #  |  \
    # (2)-(0)-4-6
    #         | |
    #         3-5

    points = np.random.rand(7, 3)

    edges = np.array([[0, 1], [1, 2], [2, 0], [0, 4], [4, 6], [5, 6], [3, 5]], dtype=np.int64)

    triangles = np.array(
        [
            [0, 1, 2],
        ],
        dtype=np.int64,
    )

    target_mapping = np.array([0, 1, 2, 0, 0, 0, 0], dtype=np.int64)

    target_merged_points = np.array([3, 4, 5, 6], dtype=np.int64)

    mapping, merged_points = map_isolated_points(points, edges, triangles)
    assert np.allclose(mapping, target_mapping)
    assert np.allclose(merged_points, target_merged_points)

    ## Example 4
    #
    # (6)           (7)-(8)
    #  | \           |  /
    #  |  \          | /
    # (5)-(0)-1-2-3-(4)
    #
    # Here the situation is ambiguous. Does 2 merge into 0 or 4 ?
    # We consider 2 -> 4 and 2 -> 0 as valid solutions.

    points = np.random.rand(9, 3)

    edges = np.array(
        [
            [0, 1],
            [1, 2],
            [2, 3],
            [3, 4],
        ],
        dtype=np.int64,
    )

    triangles = np.array([[0, 5, 6], [4, 7, 8]], dtype=np.int64)

    target_mapping1 = np.array([0, 0, 0, 4, 4, 5, 6, 7, 8], dtype=np.int64)

    target_mapping2 = np.array([0, 0, 4, 4, 4, 5, 6, 7, 8], dtype=np.int64)

    target_merged_points = np.array([1, 2, 3], dtype=np.int64)

    mapping, merged_points = map_isolated_points(points, edges, triangles)
    assert np.allclose(mapping, target_mapping1) or np.allclose(mapping, target_mapping2)
    assert np.allclose(merged_points, target_merged_points)

    ## Example 5
    #
    # (1)            (7)-(8)-9
    #  | \            |  /
    #  |  \           | /
    # (2)-(3)-0  4-5-(6)

    points = np.random.rand(10, 3)
    edges = np.array(
        [
            [0, 3],
            [1, 3],
            [2, 3],
            [1, 2],
            [4, 5],
            [5, 6],
            [8, 9],
        ],
        dtype=np.int64,
    )
    triangles = np.array(
        [
            [1, 2, 3],
            [6, 7, 8],
        ],
        dtype=np.int64,
    )

    target_mapping = np.array([3, 1, 2, 3, 6, 6, 6, 7, 8, 8], dtype=np.int64)

    target_merged_points = np.array([0, 4, 5, 9], dtype=np.int64)

    mapping, merged_points = map_isolated_points(points, edges, triangles)
    assert np.allclose(mapping, target_mapping)
    assert np.allclose(merged_points, target_merged_points)

    ## Example 6
    #
    # 0-1-2

    points = np.random.rand(3, 3)

    edges = np.array(
        [
            [0, 1],
            [1, 2],
        ],
        dtype=np.int64,
    )

    triangles = np.array([[]], dtype=np.int64)

    target_mapping = np.array([0, 1, 2], dtype=np.int64)

    target_merged_points = np.array([], dtype=np.int64)

    mapping, merged_points = map_isolated_points(points, edges, triangles)
    assert np.allclose(mapping, target_mapping)
    assert np.allclose(merged_points, target_merged_points)


================================================
FILE: tests/test_replay.py
================================================
import numpy as np
import pytest

import fast_simplification

try:
    import pyvista as pv

    has_vtk = True
except ModuleNotFoundError:
    has_vtk = False
skip_no_vtk = pytest.mark.skipif(not has_vtk, reason="Requires VTK")


@pytest.fixture
def mesh():
    return pv.Sphere()


def test_collapses_trivial():
    # arrays from:
    # mesh = pv.Plane(i_resolution=2, j_resolution=2).triangulate()
    points = [
        [0.5, -0.5, 0.0],
        [0.0, -0.5, 0.0],
        [-0.5, -0.5, 0.0],
        [0.5, 0.0, 0.0],
        [0.0, 0.0, 0.0],
        [-0.5, 0.0, 0.0],
        [0.5, 0.5, 0.0],
        [0.0, 0.5, 0.0],
        [-0.5, 0.5, 0.0],
    ]

    faces = [
        [0, 1, 3],
        [4, 3, 1],
        [1, 2, 4],
        [5, 4, 2],
        [3, 4, 6],
        [7, 6, 4],
        [4, 5, 7],
        [8, 7, 5],
    ]

    with pytest.raises(ValueError, match="You must specify"):
        fast_simplification.simplify(points, faces)

    points_out, faces_out, collapses = fast_simplification.simplify(
        points, faces, 0.5, return_collapses=True
    )

    (
        replay_points,
        replay_faces,
        indice_mapping,
    ) = fast_simplification.replay_simplification(points, faces, collapses)
    assert np.allclose(points_out, replay_points)
    assert np.allclose(faces_out, replay_faces)


@skip_no_vtk
def test_collapses_sphere(mesh):
    points = mesh.points
    faces = mesh.faces.reshape(-1, 4)[:, 1:]
    reduction = 0.5

    points_out, faces_out, collapses = fast_simplification.simplify(
        points, faces, reduction, return_collapses=True
    )

    (
        replay_points,
        replay_faces,
        indice_mapping,
    ) = fast_simplification.replay_simplification(points, faces, collapses)
    assert np.allclose(points_out, replay_points)
    assert np.allclose(faces_out, replay_faces)


try:
    from pyvista import examples

    @pytest.fixture
    def louis():
        return examples.download_louis_louvre()

    @pytest.fixture
    def human():
        return examples.download_human()

    has_examples = True
except:
    has_examples = False
skip_no_examples = pytest.mark.skipif(not has_examples, reason="Requires pyvista.examples")


@skip_no_examples
@skip_no_vtk
def test_collapses_louis(louis):
    points = louis.points
    faces = louis.faces.reshape(-1, 4)[:, 1:]
    reduction = 0.9

    points_out, faces_out, collapses = fast_simplification.simplify(
        points, faces, reduction, return_collapses=True
    )

    (
        replay_points,
        replay_faces,
        indice_mapping,
    ) = fast_simplification.replay_simplification(points, faces, collapses)
    assert np.allclose(points_out, replay_points)
    assert np.allclose(faces_out, replay_faces)


@skip_no_examples
@skip_no_vtk
def test_human(human):
    points = human.points
    faces = human.faces.reshape(-1, 4)[:, 1:]
    reduction = 0.9

    points_out, faces_out, collapses = fast_simplification.simplify(
        points, faces, reduction, return_collapses=True
    )

    (
        replay_points,
        replay_faces,
        indice_mapping,
    ) = fast_simplification.replay_simplification(points, faces, collapses)
    assert np.allclose(points_out, replay_points)
    assert np.allclose(faces_out, replay_faces)


================================================
FILE: tests/test_simplify.py
================================================
import numpy as np
import pytest

import fast_simplification

try:
    import pyvista as pv

    has_vtk = True
except ModuleNotFoundError:
    has_vtk = False

skip_no_vtk = pytest.mark.skipif(not has_vtk, reason="Requires VTK")


@pytest.fixture
def mesh():
    return pv.Sphere()


def test_simplify_trivial():
    # arrays from:
    # mesh = pv.Plane(i_resolution=2, j_resolution=2).triangulate()
    points = [
        [0.5, -0.5, 0.0],
        [0.0, -0.5, 0.0],
        [-0.5, -0.5, 0.0],
        [0.5, 0.0, 0.0],
        [0.0, 0.0, 0.0],
        [-0.5, 0.0, 0.0],
        [0.5, 0.5, 0.0],
        [0.0, 0.5, 0.0],
        [-0.5, 0.5, 0.0],
    ]

    faces = [
        [0, 1, 3],
        [4, 3, 1],
        [1, 2, 4],
        [5, 4, 2],
        [3, 4, 6],
        [7, 6, 4],
        [4, 5, 7],
        [8, 7, 5],
    ]

    with pytest.raises(ValueError, match="You must specify"):
        fast_simplification.simplify(points, faces)

    points_out, faces_out = fast_simplification.simplify(points, faces, 0.5)
    assert points_out.shape[0] == 5
    assert faces_out.shape[0] == 4

    # Test with return_collapses=True
    # We check that the number of points after simplification is equal to the number of
    # points before simplification minus the number of collapses
    points_out, faces_out, collapses = fast_simplification.simplify(
        points, faces, 0.5, return_collapses=True
    )
    n_points_before_simplification = len(points)
    n_points_after_simplification = len(points_out)
    n_collapses = len(collapses)
    assert n_points_after_simplification == n_points_before_simplification - n_collapses


@skip_no_vtk
def test_simplify_none(mesh):
    triangles = mesh._connectivity_array.reshape(-1, 3)

    reduction = 0
    points, faces = fast_simplification.simplify(mesh.points, triangles, reduction)
    assert np.allclose(triangles, faces)
    assert np.allclose(mesh.points, points)


@skip_no_vtk
def test_simplify(mesh):
    triangles = mesh._connectivity_array.reshape(-1, 3)
    reduction = 0.5
    points, faces, collapses = fast_simplification.simplify(
        mesh.points, triangles, reduction, return_collapses=True
    )
    assert triangles.shape[0] * reduction == faces.shape[0]
    # We check that the number of points after simplification is equal to the number of
    # points before simplification minus the number of collapses
    n_points_before_simplification = mesh.points.shape[0]
    n_points_after_simplification = points.shape[0]
    n_collapses = collapses.shape[0]
    assert n_points_after_simplification == n_points_before_simplification - n_collapses

    assert points.dtype == np.float64


@skip_no_vtk
def test_simplify_lossless(mesh):
    triangles = mesh._connectivity_array.reshape(-1, 3)
    reduction = 0.5
    points, faces = fast_simplification.simplify(mesh.points, triangles, reduction, lossless=True)
    assert np.allclose(mesh.points, points)
    assert np.allclose(triangles, faces)


@skip_no_vtk
def test_simplify_agg(mesh):
    triangles = mesh._connectivity_array.reshape(-1, 3)

    reduction = 0.5
    points, faces = fast_simplification.simplify(
        mesh.points,
        triangles,
        reduction,
        agg=0,
    )
    assert triangles.shape[0] == faces.shape[0]

    reduction = 0.5
    points, faces = fast_simplification.simplify(
        mesh.points,
        triangles,
        reduction,
        agg=1,
    )
    # somewhere between the requested reduction and the original number of triangles
    assert triangles.shape[0] * reduction < faces.shape[0] < triangles.shape[0]


@skip_no_vtk
def test_simplify_mesh(mesh):
    reduction = 0.5
    mesh_out = fast_simplification.simplify_mesh(mesh, reduction)
    assert mesh_out.n_cells == mesh.n_cells * reduction


================================================
FILE: tools/audit_wheel.sh
================================================
#!/bin/bash -eo pipefail
set -x

PY_MINOR=$(python -c "import sys; print(sys.version_info.minor)")
if [ "$PY_MINOR" -lt 11 ]; then
  echo "Not checking abi3audit for Python $PY_MINOR < 3.11"
  exit 0
fi
abi3audit --strict --report --verbose "$1"
Download .txt
gitextract_q5jmbpat/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── testing-and-deployment.yml
├── .gitignore
├── .pre-commit-config.yaml
├── LICENSE
├── MANIFEST.in
├── README.rst
├── doc/
│   ├── Makefile
│   ├── _templates/
│   │   └── autosummary/
│   │       └── class.rst
│   ├── api.rst
│   ├── conf.py
│   └── index.rst
├── examples/
│   ├── README.txt
│   ├── replay.py
│   └── simplify.py
├── fast_simplification/
│   ├── Replay.h
│   ├── Simplify.h
│   ├── __init__.py
│   ├── _replay.pyx
│   ├── _simplify.pyx
│   ├── _version.py
│   ├── fast_simplification.py
│   ├── replay.py
│   ├── simplify.py
│   ├── utils.py
│   ├── wrapper.h
│   └── wrapper_replay.h
├── pyproject.toml
├── pytest.ini
├── requirements_docs.txt
├── requirements_test.txt
├── setup.py
├── tests/
│   ├── test_map_isolated_points.py
│   ├── test_replay.py
│   └── test_simplify.py
└── tools/
    └── audit_wheel.sh
Download .txt
SYMBOL INDEX (69 symbols across 13 files)

FILE: examples/replay.py
  function triangles_to_faces (line 19) | def triangles_to_faces(triangles):

FILE: fast_simplification/Replay.h
  function namespace (line 3) | namespace Replay{
  function write_obj (line 389) | void write_obj(const char* filename)

FILE: fast_simplification/Simplify.h
  type vector3 (line 37) | struct vector3
  function vec3f (line 42) | struct vec3f
  function vec3f (line 57) | inline vec3f operator + ( const vec3f& a ) const
  function vec3f (line 60) | inline vec3f operator += ( const vec3f& a ) const
  function vec3f (line 63) | inline vec3f operator * ( const double a ) const
  function vec3f (line 66) | inline vec3f operator * ( const vec3f a ) const
  function vec3f (line 78) | inline vec3f operator / ( const vec3f a ) const
  function vec3f (line 81) | inline vec3f operator - ( const vec3f& a ) const
  function vec3f (line 84) | inline vec3f operator / ( const double a ) const
  function dot (line 87) | inline double dot( const vec3f& a ) const
  function vec3f (line 90) | inline vec3f cross( const vec3f& a , const vec3f& b )
  function angle (line 98) | inline double angle( const vec3f& v )
  function angle2 (line 110) | inline double angle2( const vec3f& v , const vec3f& w )
  function vec3f (line 125) | inline vec3f rot_x( double a )
  function vec3f (line 132) | inline vec3f rot_y( double a )
  function clamp (line 139) | inline void clamp( double min, double max )
  function vec3f (line 148) | inline vec3f rot_z( double a )
  function vec3f (line 155) | inline vec3f invert()
  function vec3f (line 159) | inline vec3f frac()
  function vec3f (line 168) | inline vec3f integer()
  function random_double_01 (line 204) | double random_double_01(double a){
  function vec3f (line 210) | vec3f random01_fxyz(){
  function vec3f (line 219) | vec3f barycentric(const vec3f &p, const vec3f &a, const vec3f &b, const ...
  function vec3f (line 235) | vec3f interpolate(const vec3f &p, const vec3f &a, const vec3f &b, const ...
  function min (line 245) | double min(double v1, double v2) {
  function class (line 250) | class SymetricMatrix {
  function const (line 278) | double operator[](int c) const { return m[c]; }
  function det (line 282) | double det(	int a11, int a12, int a13,
  function namespace (line 311) | namespace Simplify
  function flipped (line 560) | bool flipped(vec3f p,int i0,int i1,Vertex &v0,Vertex &v1,std::vector<int...
  function update_uvs (line 592) | void update_uvs(int i0,const Vertex &v,const vec3f &p,std::vector<int> &...
  function update_triangles (line 609) | void update_triangles(int i0,Vertex &v,std::vector<int> &deleted,int &de...
  function update_mesh (line 635) | void update_mesh(int iteration)
  function compact_mesh (line 757) | void compact_mesh()
  function vertex_error (line 790) | double vertex_error(SymetricMatrix q, double x, double y, double z)
  function calculate_error (line 798) | double calculate_error(int id_v1, int id_v2, vec3f &p_result)
  function write_obj (line 1006) | void write_obj(const char* filename)

FILE: fast_simplification/fast_simplification.py
  function simplify (line 1) | def simplify():

FILE: fast_simplification/replay.py
  function _map_isolated_points (line 7) | def _map_isolated_points(points, edges, triangles, return_outliers=False):
  function replay_simplification (line 116) | def replay_simplification(points, triangles, collapses):

FILE: fast_simplification/simplify.py
  function _check_args (line 18) | def _check_args(target_reduction, target_count, n_faces):
  function simplify (line 38) | def simplify(
  function simplify_mesh (line 170) | def simplify_mesh(

FILE: fast_simplification/utils.py
  function ascontiguous (line 6) | def ascontiguous(func):

FILE: fast_simplification/wrapper.h
  function namespace (line 4) | namespace Simplify{

FILE: fast_simplification/wrapper_replay.h
  function namespace (line 4) | namespace Replay{

FILE: setup.py
  class bdist_wheel_abi3 (line 36) | class bdist_wheel_abi3(bdist_wheel):  # noqa: D101
    method get_tag (line 37) | def get_tag(self):  # noqa: D102
  class build_ext (line 64) | class build_ext(_build_ext):
    method finalize_options (line 65) | def finalize_options(self):
    method build_extensions (line 76) | def build_extensions(self):

FILE: tests/test_map_isolated_points.py
  function test_map_isolated_points (line 6) | def test_map_isolated_points():

FILE: tests/test_replay.py
  function mesh (line 16) | def mesh():
  function test_collapses_trivial (line 20) | def test_collapses_trivial():
  function test_collapses_sphere (line 63) | def test_collapses_sphere(mesh):
  function louis (line 85) | def louis():
  function human (line 89) | def human():
  function test_collapses_louis (line 100) | def test_collapses_louis(louis):
  function test_human (line 120) | def test_human(human):

FILE: tests/test_simplify.py
  function mesh (line 17) | def mesh():
  function test_simplify_trivial (line 21) | def test_simplify_trivial():
  function test_simplify_none (line 67) | def test_simplify_none(mesh):
  function test_simplify (line 77) | def test_simplify(mesh):
  function test_simplify_lossless (line 95) | def test_simplify_lossless(mesh):
  function test_simplify_agg (line 104) | def test_simplify_agg(mesh):
  function test_simplify_mesh (line 128) | def test_simplify_mesh(mesh):
Condensed preview — 36 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (133K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 469,
    "preview": "version: 2\nupdates:\n- package-ecosystem: pip\n  directory: /\n  insecure-external-code-execution: allow\n  schedule:\n    in"
  },
  {
    "path": ".github/workflows/testing-and-deployment.yml",
    "chars": 3244,
    "preview": "name: Build\n\non:\n  pull_request:\n  push:\n    tags:\n    - v*\n    branches:\n    - main\n\n# disable concurrent runs\nconcurre"
  },
  {
    "path": ".gitignore",
    "chars": 282,
    "preview": "# Compiled source #\n###################\n*.pyc\n*.pyd\n*.so\n*.o\n__pycache__/\n\n# Pip\n*.egg-info\n\n# Cython generated files #\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 1409,
    "preview": "# Integration with GitHub Actions\n# See https://pre-commit.ci/\nci:\n  autoupdate_schedule: monthly\n\nrepos:\n- repo: https:"
  },
  {
    "path": "LICENSE",
    "chars": 1088,
    "preview": "The MIT License\n\nCopyright (c) 2017-2021 The PyVista Developers\n\nPermission is hereby granted, free of charge, to any pe"
  },
  {
    "path": "MANIFEST.in",
    "chars": 32,
    "preview": "include fast_simplification/*.h\n"
  },
  {
    "path": "README.rst",
    "chars": 8949,
    "preview": "Python Fast-Quadric-Mesh-Simplification Wrapper\n===============================================\nThis is a python wrappin"
  },
  {
    "path": "doc/Makefile",
    "chars": 710,
    "preview": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHI"
  },
  {
    "path": "doc/_templates/autosummary/class.rst",
    "chars": 665,
    "preview": "{{ fullname | escape | underline}}\n\n.. currentmodule:: {{ module }}\n\n.. autoclass:: {{ objname }}\n\n   {% block methods %"
  },
  {
    "path": "doc/api.rst",
    "chars": 253,
    "preview": "API Reference\n=============\nThese are the three public methods that expose the fast-simplification API to\nPython.\n\n.. cu"
  },
  {
    "path": "doc/conf.py",
    "chars": 6563,
    "preview": "import datetime\nimport os\n\nimport numpy as np\nimport pyvista\nfrom sphinx_gallery.sorting import FileNameSortKey\n\nfrom fa"
  },
  {
    "path": "doc/index.rst",
    "chars": 227,
    "preview": ".. include:: ../README.rst\n\n\n.. toctree::\n   :hidden:\n\n   self\n\n\n\n.. toctree::\n   :maxdepth: 2\n   :caption: Examples\n   "
  },
  {
    "path": "examples/README.txt",
    "chars": 178,
    "preview": "Fast-simplification Examples\n============================\nThe following examples demonstrate ``fast-simplification``\nfun"
  },
  {
    "path": "examples/replay.py",
    "chars": 4643,
    "preview": "\"\"\"\nReplay Decimation\n-----------------\n\nThis example shows how to replay a decimation sequence with replay.\n\n\"\"\"\n\nfrom "
  },
  {
    "path": "examples/simplify.py",
    "chars": 1973,
    "preview": "\"\"\"\nCompare Decimation Methods\n--------------------------\n\nThis example compares various decimation methods\n\n\"\"\"\n\nimport"
  },
  {
    "path": "fast_simplification/Replay.h",
    "chars": 10740,
    "preview": "#include \"Simplify.h\"\n\nnamespace Replay{\n\n    // Global Variables & Structures (same as for Simplify)\n\tenum Attributes {"
  },
  {
    "path": "fast_simplification/Simplify.h",
    "chars": 26223,
    "preview": "/////////////////////////////////////////////\n//\n// Mesh Simplification Tutorial\n//\n// (C) by Sven Forstmann in 2014\n//\n"
  },
  {
    "path": "fast_simplification/__init__.py",
    "chars": 186,
    "preview": "from ._version import __version__  # noqa: F401\nfrom .replay import _map_isolated_points, replay_simplification  # noqa:"
  },
  {
    "path": "fast_simplification/_replay.pyx",
    "chars": 6780,
    "preview": "# cython: language_level=3\n# cython: boundscheck=False\n# cython: wraparound=False\n# cython: cdivision=True\n\n\nimport nump"
  },
  {
    "path": "fast_simplification/_simplify.pyx",
    "chars": 3222,
    "preview": "# cython: language_level=3\n# cython: boundscheck=False\n# cython: wraparound=False\n# cython: cdivision=True\n\n\nimport nump"
  },
  {
    "path": "fast_simplification/_version.py",
    "chars": 224,
    "preview": "\"\"\"fast_simplification version\n\nOn the ``main`` branch, use 'dev0' to denote a development version.\nFor example:\n\nversio"
  },
  {
    "path": "fast_simplification/fast_simplification.py",
    "chars": 25,
    "preview": "def simplify():\n    pass\n"
  },
  {
    "path": "fast_simplification/replay.py",
    "chars": 7217,
    "preview": "import numpy as np\n\nfrom . import _replay\nfrom .utils import ascontiguous\n\n\ndef _map_isolated_points(points, edges, tria"
  },
  {
    "path": "fast_simplification/simplify.py",
    "chars": 8128,
    "preview": "\"\"\"Simplification library.\"\"\"\n\nfrom typing import TYPE_CHECKING\n\nimport numpy as np\nfrom numpy.typing import NDArray\n\nfr"
  },
  {
    "path": "fast_simplification/utils.py",
    "chars": 742,
    "preview": "\"\"\"Utility functions for the fast_simplification package.\"\"\"\n\nimport numpy as np\n\n\ndef ascontiguous(func):\n    \"\"\"A deco"
  },
  {
    "path": "fast_simplification/wrapper.h",
    "chars": 4764,
    "preview": "// wrap simplify header file for integration with cython\n#include \"Simplify.h\"\n\nnamespace Simplify{\n\n  // load triangles"
  },
  {
    "path": "fast_simplification/wrapper_replay.h",
    "chars": 5173,
    "preview": "// wrap simplify header file for integration with cython\n#include \"Replay.h\"\n\nnamespace Replay{\n\n  // load collapses\n  v"
  },
  {
    "path": "pyproject.toml",
    "chars": 2074,
    "preview": "[build-system]\nbuild-backend = \"setuptools.build_meta\"\nrequires = [\n  \"cython>=3.0.0\",\n  \"numpy>=2,<3\",\n  \"setuptools>=4"
  },
  {
    "path": "pytest.ini",
    "chars": 395,
    "preview": "[pytest]\njunit_family=legacy\nfilterwarnings =\n    ignore::FutureWarning\n    ignore::PendingDeprecationWarning\n    ignore"
  },
  {
    "path": "requirements_docs.txt",
    "chars": 126,
    "preview": "numpydoc>=1.8.0\npydata-sphinx-theme\npyvista\nSphinx>=4.0.0\nsphinx-copybutton\nsphinx-gallery>=0.8.1\nsphinx-notfound-page>="
  },
  {
    "path": "requirements_test.txt",
    "chars": 15,
    "preview": "pytest\npyvista\n"
  },
  {
    "path": "setup.py",
    "chars": 3990,
    "preview": "\"\"\"Setup for fast-simplification.\"\"\"\n\nimport builtins\nfrom io import open as io_open\nimport os\nimport platform\nimport sy"
  },
  {
    "path": "tests/test_map_isolated_points.py",
    "chars": 4768,
    "preview": "import numpy as np\n\nfrom fast_simplification import _map_isolated_points as map_isolated_points\n\n\ndef test_map_isolated_"
  },
  {
    "path": "tests/test_replay.py",
    "chars": 3262,
    "preview": "import numpy as np\nimport pytest\n\nimport fast_simplification\n\ntry:\n    import pyvista as pv\n\n    has_vtk = True\nexcept M"
  },
  {
    "path": "tests/test_simplify.py",
    "chars": 3766,
    "preview": "import numpy as np\nimport pytest\n\nimport fast_simplification\n\ntry:\n    import pyvista as pv\n\n    has_vtk = True\nexcept M"
  },
  {
    "path": "tools/audit_wheel.sh",
    "chars": 246,
    "preview": "#!/bin/bash -eo pipefail\nset -x\n\nPY_MINOR=$(python -c \"import sys; print(sys.version_info.minor)\")\nif [ \"$PY_MINOR\" -lt "
  }
]

About this extraction

This page contains the full source code of the pyvista/fast-simplification GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 36 files (119.9 KB), approximately 37.9k tokens, and a symbol index with 69 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!