vispy 0.15.0__cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of vispy might be problematic. Click here for more details.
- vispy/__init__.py +33 -0
- vispy/app/__init__.py +15 -0
- vispy/app/_default_app.py +76 -0
- vispy/app/_detect_eventloop.py +148 -0
- vispy/app/application.py +263 -0
- vispy/app/backends/__init__.py +52 -0
- vispy/app/backends/_egl.py +264 -0
- vispy/app/backends/_glfw.py +513 -0
- vispy/app/backends/_jupyter_rfb.py +278 -0
- vispy/app/backends/_offscreen_util.py +121 -0
- vispy/app/backends/_osmesa.py +235 -0
- vispy/app/backends/_pyglet.py +451 -0
- vispy/app/backends/_pyqt4.py +36 -0
- vispy/app/backends/_pyqt5.py +36 -0
- vispy/app/backends/_pyqt6.py +40 -0
- vispy/app/backends/_pyside.py +37 -0
- vispy/app/backends/_pyside2.py +52 -0
- vispy/app/backends/_pyside6.py +53 -0
- vispy/app/backends/_qt.py +1003 -0
- vispy/app/backends/_sdl2.py +444 -0
- vispy/app/backends/_template.py +244 -0
- vispy/app/backends/_test.py +8 -0
- vispy/app/backends/_tk.py +800 -0
- vispy/app/backends/_wx.py +476 -0
- vispy/app/backends/tests/__init__.py +0 -0
- vispy/app/backends/tests/test_offscreen_util.py +52 -0
- vispy/app/backends/tests/test_rfb.py +77 -0
- vispy/app/base.py +294 -0
- vispy/app/canvas.py +828 -0
- vispy/app/qt.py +92 -0
- vispy/app/tests/__init__.py +0 -0
- vispy/app/tests/qt-designer.ui +58 -0
- vispy/app/tests/test_app.py +442 -0
- vispy/app/tests/test_backends.py +164 -0
- vispy/app/tests/test_canvas.py +122 -0
- vispy/app/tests/test_context.py +92 -0
- vispy/app/tests/test_qt.py +47 -0
- vispy/app/tests/test_simultaneous.py +134 -0
- vispy/app/timer.py +174 -0
- vispy/color/__init__.py +17 -0
- vispy/color/_color_dict.py +193 -0
- vispy/color/color_array.py +447 -0
- vispy/color/color_space.py +181 -0
- vispy/color/colormap.py +1213 -0
- vispy/color/tests/__init__.py +0 -0
- vispy/color/tests/test_color.py +378 -0
- vispy/conftest.py +12 -0
- vispy/ext/__init__.py +0 -0
- vispy/ext/cocoapy.py +1522 -0
- vispy/ext/cubehelix.py +138 -0
- vispy/ext/egl.py +375 -0
- vispy/ext/fontconfig.py +118 -0
- vispy/ext/gdi32plus.py +206 -0
- vispy/ext/osmesa.py +105 -0
- vispy/geometry/__init__.py +23 -0
- vispy/geometry/_triangulation_debugger.py +171 -0
- vispy/geometry/calculations.py +162 -0
- vispy/geometry/curves.py +399 -0
- vispy/geometry/generation.py +643 -0
- vispy/geometry/isocurve.py +175 -0
- vispy/geometry/isosurface.py +465 -0
- vispy/geometry/meshdata.py +700 -0
- vispy/geometry/normals.py +78 -0
- vispy/geometry/parametric.py +56 -0
- vispy/geometry/polygon.py +137 -0
- vispy/geometry/rect.py +210 -0
- vispy/geometry/tests/__init__.py +0 -0
- vispy/geometry/tests/test_calculations.py +23 -0
- vispy/geometry/tests/test_generation.py +56 -0
- vispy/geometry/tests/test_meshdata.py +106 -0
- vispy/geometry/tests/test_triangulation.py +594 -0
- vispy/geometry/torusknot.py +142 -0
- vispy/geometry/triangulation.py +876 -0
- vispy/gloo/__init__.py +56 -0
- vispy/gloo/buffer.py +505 -0
- vispy/gloo/context.py +272 -0
- vispy/gloo/framebuffer.py +257 -0
- vispy/gloo/gl/__init__.py +234 -0
- vispy/gloo/gl/_constants.py +332 -0
- vispy/gloo/gl/_es2.py +986 -0
- vispy/gloo/gl/_gl2.py +1365 -0
- vispy/gloo/gl/_proxy.py +499 -0
- vispy/gloo/gl/_pyopengl2.py +362 -0
- vispy/gloo/gl/dummy.py +24 -0
- vispy/gloo/gl/es2.py +62 -0
- vispy/gloo/gl/gl2.py +98 -0
- vispy/gloo/gl/glplus.py +168 -0
- vispy/gloo/gl/pyopengl2.py +97 -0
- vispy/gloo/gl/tests/__init__.py +0 -0
- vispy/gloo/gl/tests/test_basics.py +282 -0
- vispy/gloo/gl/tests/test_functionality.py +568 -0
- vispy/gloo/gl/tests/test_names.py +246 -0
- vispy/gloo/gl/tests/test_use.py +71 -0
- vispy/gloo/glir.py +1824 -0
- vispy/gloo/globject.py +101 -0
- vispy/gloo/preprocessor.py +67 -0
- vispy/gloo/program.py +543 -0
- vispy/gloo/tests/__init__.py +0 -0
- vispy/gloo/tests/test_buffer.py +558 -0
- vispy/gloo/tests/test_context.py +119 -0
- vispy/gloo/tests/test_framebuffer.py +195 -0
- vispy/gloo/tests/test_glir.py +307 -0
- vispy/gloo/tests/test_globject.py +35 -0
- vispy/gloo/tests/test_program.py +302 -0
- vispy/gloo/tests/test_texture.py +732 -0
- vispy/gloo/tests/test_use_gloo.py +187 -0
- vispy/gloo/tests/test_util.py +60 -0
- vispy/gloo/tests/test_wrappers.py +261 -0
- vispy/gloo/texture.py +1046 -0
- vispy/gloo/util.py +129 -0
- vispy/gloo/wrappers.py +762 -0
- vispy/glsl/__init__.py +42 -0
- vispy/glsl/antialias/antialias.glsl +7 -0
- vispy/glsl/antialias/cap-butt.glsl +31 -0
- vispy/glsl/antialias/cap-round.glsl +29 -0
- vispy/glsl/antialias/cap-square.glsl +30 -0
- vispy/glsl/antialias/cap-triangle-in.glsl +30 -0
- vispy/glsl/antialias/cap-triangle-out.glsl +30 -0
- vispy/glsl/antialias/cap.glsl +67 -0
- vispy/glsl/antialias/caps.glsl +67 -0
- vispy/glsl/antialias/filled.glsl +50 -0
- vispy/glsl/antialias/outline.glsl +40 -0
- vispy/glsl/antialias/stroke.glsl +43 -0
- vispy/glsl/arrowheads/angle.glsl +99 -0
- vispy/glsl/arrowheads/arrowheads.frag +60 -0
- vispy/glsl/arrowheads/arrowheads.glsl +12 -0
- vispy/glsl/arrowheads/arrowheads.vert +83 -0
- vispy/glsl/arrowheads/curved.glsl +48 -0
- vispy/glsl/arrowheads/inhibitor.glsl +26 -0
- vispy/glsl/arrowheads/stealth.glsl +46 -0
- vispy/glsl/arrowheads/triangle.glsl +97 -0
- vispy/glsl/arrowheads/util.glsl +13 -0
- vispy/glsl/arrows/angle-30.glsl +12 -0
- vispy/glsl/arrows/angle-60.glsl +12 -0
- vispy/glsl/arrows/angle-90.glsl +12 -0
- vispy/glsl/arrows/arrow.frag +39 -0
- vispy/glsl/arrows/arrow.vert +49 -0
- vispy/glsl/arrows/arrows.glsl +17 -0
- vispy/glsl/arrows/common.glsl +187 -0
- vispy/glsl/arrows/curved.glsl +63 -0
- vispy/glsl/arrows/stealth.glsl +50 -0
- vispy/glsl/arrows/triangle-30.glsl +12 -0
- vispy/glsl/arrows/triangle-60.glsl +12 -0
- vispy/glsl/arrows/triangle-90.glsl +12 -0
- vispy/glsl/arrows/util.glsl +98 -0
- vispy/glsl/build_spatial_filters.py +660 -0
- vispy/glsl/collections/agg-fast-path.frag +20 -0
- vispy/glsl/collections/agg-fast-path.vert +78 -0
- vispy/glsl/collections/agg-glyph.frag +60 -0
- vispy/glsl/collections/agg-glyph.vert +33 -0
- vispy/glsl/collections/agg-marker.frag +35 -0
- vispy/glsl/collections/agg-marker.vert +48 -0
- vispy/glsl/collections/agg-path.frag +55 -0
- vispy/glsl/collections/agg-path.vert +166 -0
- vispy/glsl/collections/agg-point.frag +21 -0
- vispy/glsl/collections/agg-point.vert +35 -0
- vispy/glsl/collections/agg-segment.frag +32 -0
- vispy/glsl/collections/agg-segment.vert +75 -0
- vispy/glsl/collections/marker.frag +38 -0
- vispy/glsl/collections/marker.vert +48 -0
- vispy/glsl/collections/raw-path.frag +15 -0
- vispy/glsl/collections/raw-path.vert +24 -0
- vispy/glsl/collections/raw-point.frag +14 -0
- vispy/glsl/collections/raw-point.vert +31 -0
- vispy/glsl/collections/raw-segment.frag +18 -0
- vispy/glsl/collections/raw-segment.vert +26 -0
- vispy/glsl/collections/raw-triangle.frag +13 -0
- vispy/glsl/collections/raw-triangle.vert +26 -0
- vispy/glsl/collections/sdf-glyph-ticks.vert +69 -0
- vispy/glsl/collections/sdf-glyph.frag +80 -0
- vispy/glsl/collections/sdf-glyph.vert +59 -0
- vispy/glsl/collections/tick-labels.vert +71 -0
- vispy/glsl/colormaps/autumn.glsl +20 -0
- vispy/glsl/colormaps/blues.glsl +20 -0
- vispy/glsl/colormaps/color-space.glsl +17 -0
- vispy/glsl/colormaps/colormaps.glsl +24 -0
- vispy/glsl/colormaps/cool.glsl +20 -0
- vispy/glsl/colormaps/fire.glsl +21 -0
- vispy/glsl/colormaps/gray.glsl +20 -0
- vispy/glsl/colormaps/greens.glsl +20 -0
- vispy/glsl/colormaps/hot.glsl +22 -0
- vispy/glsl/colormaps/ice.glsl +20 -0
- vispy/glsl/colormaps/icefire.glsl +23 -0
- vispy/glsl/colormaps/parse.py +40 -0
- vispy/glsl/colormaps/reds.glsl +20 -0
- vispy/glsl/colormaps/spring.glsl +20 -0
- vispy/glsl/colormaps/summer.glsl +20 -0
- vispy/glsl/colormaps/user.glsl +22 -0
- vispy/glsl/colormaps/util.glsl +41 -0
- vispy/glsl/colormaps/wheel.glsl +21 -0
- vispy/glsl/colormaps/winter.glsl +20 -0
- vispy/glsl/lines/agg.frag +320 -0
- vispy/glsl/lines/agg.vert +241 -0
- vispy/glsl/markers/arrow.glsl +12 -0
- vispy/glsl/markers/asterisk.glsl +16 -0
- vispy/glsl/markers/chevron.glsl +14 -0
- vispy/glsl/markers/clover.glsl +20 -0
- vispy/glsl/markers/club.glsl +31 -0
- vispy/glsl/markers/cross.glsl +17 -0
- vispy/glsl/markers/diamond.glsl +12 -0
- vispy/glsl/markers/disc.glsl +9 -0
- vispy/glsl/markers/ellipse.glsl +67 -0
- vispy/glsl/markers/hbar.glsl +9 -0
- vispy/glsl/markers/heart.glsl +15 -0
- vispy/glsl/markers/infinity.glsl +15 -0
- vispy/glsl/markers/marker-sdf.frag +74 -0
- vispy/glsl/markers/marker-sdf.vert +41 -0
- vispy/glsl/markers/marker.frag +36 -0
- vispy/glsl/markers/marker.vert +46 -0
- vispy/glsl/markers/markers.glsl +24 -0
- vispy/glsl/markers/pin.glsl +18 -0
- vispy/glsl/markers/ring.glsl +11 -0
- vispy/glsl/markers/spade.glsl +28 -0
- vispy/glsl/markers/square.glsl +10 -0
- vispy/glsl/markers/tag.glsl +11 -0
- vispy/glsl/markers/triangle.glsl +14 -0
- vispy/glsl/markers/vbar.glsl +9 -0
- vispy/glsl/math/circle-through-2-points.glsl +30 -0
- vispy/glsl/math/constants.glsl +48 -0
- vispy/glsl/math/double.glsl +114 -0
- vispy/glsl/math/functions.glsl +20 -0
- vispy/glsl/math/point-to-line-distance.glsl +31 -0
- vispy/glsl/math/point-to-line-projection.glsl +29 -0
- vispy/glsl/math/signed-line-distance.glsl +27 -0
- vispy/glsl/math/signed-segment-distance.glsl +30 -0
- vispy/glsl/misc/regular-grid.frag +244 -0
- vispy/glsl/misc/spatial-filters.frag +1407 -0
- vispy/glsl/misc/viewport-NDC.glsl +20 -0
- vispy/glsl/transforms/azimuthal-equal-area.glsl +32 -0
- vispy/glsl/transforms/azimuthal-equidistant.glsl +38 -0
- vispy/glsl/transforms/hammer.glsl +44 -0
- vispy/glsl/transforms/identity.glsl +6 -0
- vispy/glsl/transforms/identity_forward.glsl +23 -0
- vispy/glsl/transforms/identity_inverse.glsl +23 -0
- vispy/glsl/transforms/linear-scale.glsl +127 -0
- vispy/glsl/transforms/log-scale.glsl +126 -0
- vispy/glsl/transforms/mercator-transverse-forward.glsl +40 -0
- vispy/glsl/transforms/mercator-transverse-inverse.glsl +40 -0
- vispy/glsl/transforms/panzoom.glsl +10 -0
- vispy/glsl/transforms/polar.glsl +41 -0
- vispy/glsl/transforms/position.glsl +44 -0
- vispy/glsl/transforms/power-scale.glsl +139 -0
- vispy/glsl/transforms/projection.glsl +7 -0
- vispy/glsl/transforms/pvm.glsl +13 -0
- vispy/glsl/transforms/rotate.glsl +45 -0
- vispy/glsl/transforms/trackball.glsl +15 -0
- vispy/glsl/transforms/translate.glsl +35 -0
- vispy/glsl/transforms/transverse_mercator.glsl +38 -0
- vispy/glsl/transforms/viewport-clipping.glsl +14 -0
- vispy/glsl/transforms/viewport-transform.glsl +16 -0
- vispy/glsl/transforms/viewport.glsl +50 -0
- vispy/glsl/transforms/x.glsl +24 -0
- vispy/glsl/transforms/y.glsl +19 -0
- vispy/glsl/transforms/z.glsl +14 -0
- vispy/io/__init__.py +20 -0
- vispy/io/_data/spatial-filters.npy +0 -0
- vispy/io/datasets.py +94 -0
- vispy/io/image.py +231 -0
- vispy/io/mesh.py +122 -0
- vispy/io/stl.py +167 -0
- vispy/io/tests/__init__.py +0 -0
- vispy/io/tests/test_image.py +47 -0
- vispy/io/tests/test_io.py +121 -0
- vispy/io/wavefront.py +350 -0
- vispy/plot/__init__.py +36 -0
- vispy/plot/fig.py +58 -0
- vispy/plot/plotwidget.py +522 -0
- vispy/plot/tests/__init__.py +0 -0
- vispy/plot/tests/test_plot.py +46 -0
- vispy/scene/__init__.py +43 -0
- vispy/scene/cameras/__init__.py +27 -0
- vispy/scene/cameras/_base.py +38 -0
- vispy/scene/cameras/arcball.py +105 -0
- vispy/scene/cameras/base_camera.py +551 -0
- vispy/scene/cameras/fly.py +474 -0
- vispy/scene/cameras/magnify.py +163 -0
- vispy/scene/cameras/panzoom.py +311 -0
- vispy/scene/cameras/perspective.py +338 -0
- vispy/scene/cameras/tests/__init__.py +0 -0
- vispy/scene/cameras/tests/test_cameras.py +27 -0
- vispy/scene/cameras/tests/test_link.py +53 -0
- vispy/scene/cameras/tests/test_perspective.py +122 -0
- vispy/scene/cameras/turntable.py +183 -0
- vispy/scene/canvas.py +639 -0
- vispy/scene/events.py +85 -0
- vispy/scene/node.py +644 -0
- vispy/scene/subscene.py +20 -0
- vispy/scene/tests/__init__.py +0 -0
- vispy/scene/tests/test_canvas.py +119 -0
- vispy/scene/tests/test_node.py +142 -0
- vispy/scene/tests/test_visuals.py +141 -0
- vispy/scene/visuals.py +276 -0
- vispy/scene/widgets/__init__.py +18 -0
- vispy/scene/widgets/anchor.py +25 -0
- vispy/scene/widgets/axis.py +88 -0
- vispy/scene/widgets/colorbar.py +176 -0
- vispy/scene/widgets/console.py +351 -0
- vispy/scene/widgets/grid.py +509 -0
- vispy/scene/widgets/label.py +50 -0
- vispy/scene/widgets/tests/__init__.py +0 -0
- vispy/scene/widgets/tests/test_colorbar.py +47 -0
- vispy/scene/widgets/viewbox.py +199 -0
- vispy/scene/widgets/widget.py +478 -0
- vispy/testing/__init__.py +51 -0
- vispy/testing/_runners.py +448 -0
- vispy/testing/_testing.py +416 -0
- vispy/testing/image_tester.py +494 -0
- vispy/testing/rendered_array_tester.py +85 -0
- vispy/testing/tests/__init__.py +0 -0
- vispy/testing/tests/test_testing.py +20 -0
- vispy/util/__init__.py +32 -0
- vispy/util/bunch.py +15 -0
- vispy/util/check_environment.py +57 -0
- vispy/util/config.py +490 -0
- vispy/util/dpi/__init__.py +19 -0
- vispy/util/dpi/_linux.py +69 -0
- vispy/util/dpi/_quartz.py +26 -0
- vispy/util/dpi/_win32.py +34 -0
- vispy/util/dpi/tests/__init__.py +0 -0
- vispy/util/dpi/tests/test_dpi.py +16 -0
- vispy/util/eq.py +41 -0
- vispy/util/event.py +774 -0
- vispy/util/fetching.py +276 -0
- vispy/util/filter.py +44 -0
- vispy/util/fonts/__init__.py +14 -0
- vispy/util/fonts/_freetype.py +73 -0
- vispy/util/fonts/_quartz.py +192 -0
- vispy/util/fonts/_triage.py +36 -0
- vispy/util/fonts/_vispy_fonts.py +20 -0
- vispy/util/fonts/_win32.py +105 -0
- vispy/util/fonts/data/OpenSans-Bold.ttf +0 -0
- vispy/util/fonts/data/OpenSans-BoldItalic.ttf +0 -0
- vispy/util/fonts/data/OpenSans-Italic.ttf +0 -0
- vispy/util/fonts/data/OpenSans-Regular.ttf +0 -0
- vispy/util/fonts/tests/__init__.py +0 -0
- vispy/util/fonts/tests/test_font.py +45 -0
- vispy/util/fourier.py +69 -0
- vispy/util/frozen.py +25 -0
- vispy/util/gallery_scraper.py +268 -0
- vispy/util/keys.py +91 -0
- vispy/util/logs.py +358 -0
- vispy/util/osmesa_gl.py +17 -0
- vispy/util/profiler.py +135 -0
- vispy/util/ptime.py +16 -0
- vispy/util/quaternion.py +229 -0
- vispy/util/svg/__init__.py +18 -0
- vispy/util/svg/base.py +20 -0
- vispy/util/svg/color.py +219 -0
- vispy/util/svg/element.py +51 -0
- vispy/util/svg/geometry.py +478 -0
- vispy/util/svg/group.py +66 -0
- vispy/util/svg/length.py +81 -0
- vispy/util/svg/number.py +25 -0
- vispy/util/svg/path.py +332 -0
- vispy/util/svg/shapes.py +57 -0
- vispy/util/svg/style.py +59 -0
- vispy/util/svg/svg.py +40 -0
- vispy/util/svg/transform.py +223 -0
- vispy/util/svg/transformable.py +28 -0
- vispy/util/svg/viewport.py +73 -0
- vispy/util/tests/__init__.py +0 -0
- vispy/util/tests/test_config.py +58 -0
- vispy/util/tests/test_docstring_parameters.py +123 -0
- vispy/util/tests/test_emitter_group.py +262 -0
- vispy/util/tests/test_event_emitter.py +743 -0
- vispy/util/tests/test_fourier.py +35 -0
- vispy/util/tests/test_gallery_scraper.py +112 -0
- vispy/util/tests/test_import.py +127 -0
- vispy/util/tests/test_key.py +22 -0
- vispy/util/tests/test_logging.py +45 -0
- vispy/util/tests/test_run.py +14 -0
- vispy/util/tests/test_transforms.py +42 -0
- vispy/util/tests/test_vispy.py +48 -0
- vispy/util/transforms.py +201 -0
- vispy/util/wrappers.py +155 -0
- vispy/version.py +21 -0
- vispy/visuals/__init__.py +50 -0
- vispy/visuals/_scalable_textures.py +487 -0
- vispy/visuals/axis.py +678 -0
- vispy/visuals/border.py +208 -0
- vispy/visuals/box.py +79 -0
- vispy/visuals/collections/__init__.py +30 -0
- vispy/visuals/collections/agg_fast_path_collection.py +219 -0
- vispy/visuals/collections/agg_path_collection.py +197 -0
- vispy/visuals/collections/agg_point_collection.py +52 -0
- vispy/visuals/collections/agg_segment_collection.py +142 -0
- vispy/visuals/collections/array_list.py +401 -0
- vispy/visuals/collections/base_collection.py +482 -0
- vispy/visuals/collections/collection.py +253 -0
- vispy/visuals/collections/path_collection.py +23 -0
- vispy/visuals/collections/point_collection.py +19 -0
- vispy/visuals/collections/polygon_collection.py +25 -0
- vispy/visuals/collections/raw_path_collection.py +119 -0
- vispy/visuals/collections/raw_point_collection.py +113 -0
- vispy/visuals/collections/raw_polygon_collection.py +77 -0
- vispy/visuals/collections/raw_segment_collection.py +112 -0
- vispy/visuals/collections/raw_triangle_collection.py +78 -0
- vispy/visuals/collections/segment_collection.py +19 -0
- vispy/visuals/collections/triangle_collection.py +16 -0
- vispy/visuals/collections/util.py +168 -0
- vispy/visuals/colorbar.py +699 -0
- vispy/visuals/cube.py +41 -0
- vispy/visuals/ellipse.py +162 -0
- vispy/visuals/filters/__init__.py +10 -0
- vispy/visuals/filters/base_filter.py +242 -0
- vispy/visuals/filters/clipper.py +60 -0
- vispy/visuals/filters/clipping_planes.py +122 -0
- vispy/visuals/filters/color.py +181 -0
- vispy/visuals/filters/markers.py +28 -0
- vispy/visuals/filters/mesh.py +801 -0
- vispy/visuals/filters/picking.py +60 -0
- vispy/visuals/filters/tests/__init__.py +3 -0
- vispy/visuals/filters/tests/test_primitive_picking_filters.py +70 -0
- vispy/visuals/filters/tests/test_wireframe_filter.py +16 -0
- vispy/visuals/glsl/__init__.py +1 -0
- vispy/visuals/glsl/antialiasing.py +133 -0
- vispy/visuals/glsl/color.py +63 -0
- vispy/visuals/graphs/__init__.py +1 -0
- vispy/visuals/graphs/graph.py +240 -0
- vispy/visuals/graphs/layouts/__init__.py +55 -0
- vispy/visuals/graphs/layouts/circular.py +49 -0
- vispy/visuals/graphs/layouts/force_directed.py +211 -0
- vispy/visuals/graphs/layouts/networkx_layout.py +87 -0
- vispy/visuals/graphs/layouts/random.py +52 -0
- vispy/visuals/graphs/tests/__init__.py +1 -0
- vispy/visuals/graphs/tests/test_layouts.py +139 -0
- vispy/visuals/graphs/tests/test_networkx_layout.py +47 -0
- vispy/visuals/graphs/util.py +120 -0
- vispy/visuals/gridlines.py +161 -0
- vispy/visuals/gridmesh.py +98 -0
- vispy/visuals/histogram.py +58 -0
- vispy/visuals/image.py +701 -0
- vispy/visuals/image_complex.py +130 -0
- vispy/visuals/infinite_line.py +199 -0
- vispy/visuals/instanced_mesh.py +152 -0
- vispy/visuals/isocurve.py +213 -0
- vispy/visuals/isoline.py +241 -0
- vispy/visuals/isosurface.py +113 -0
- vispy/visuals/line/__init__.py +6 -0
- vispy/visuals/line/arrow.py +289 -0
- vispy/visuals/line/dash_atlas.py +90 -0
- vispy/visuals/line/line.py +545 -0
- vispy/visuals/line_plot.py +135 -0
- vispy/visuals/linear_region.py +199 -0
- vispy/visuals/markers.py +819 -0
- vispy/visuals/mesh.py +373 -0
- vispy/visuals/mesh_normals.py +159 -0
- vispy/visuals/plane.py +54 -0
- vispy/visuals/polygon.py +145 -0
- vispy/visuals/rectangle.py +196 -0
- vispy/visuals/regular_polygon.py +56 -0
- vispy/visuals/scrolling_lines.py +197 -0
- vispy/visuals/shaders/__init__.py +17 -0
- vispy/visuals/shaders/compiler.py +206 -0
- vispy/visuals/shaders/expression.py +99 -0
- vispy/visuals/shaders/function.py +788 -0
- vispy/visuals/shaders/multiprogram.py +145 -0
- vispy/visuals/shaders/parsing.py +140 -0
- vispy/visuals/shaders/program.py +161 -0
- vispy/visuals/shaders/shader_object.py +162 -0
- vispy/visuals/shaders/tests/__init__.py +0 -0
- vispy/visuals/shaders/tests/test_function.py +486 -0
- vispy/visuals/shaders/tests/test_multiprogram.py +78 -0
- vispy/visuals/shaders/tests/test_parsing.py +57 -0
- vispy/visuals/shaders/variable.py +272 -0
- vispy/visuals/spectrogram.py +169 -0
- vispy/visuals/sphere.py +80 -0
- vispy/visuals/surface_plot.py +192 -0
- vispy/visuals/tests/__init__.py +0 -0
- vispy/visuals/tests/test_arrows.py +109 -0
- vispy/visuals/tests/test_axis.py +120 -0
- vispy/visuals/tests/test_collections.py +15 -0
- vispy/visuals/tests/test_colorbar.py +179 -0
- vispy/visuals/tests/test_colormap.py +97 -0
- vispy/visuals/tests/test_ellipse.py +122 -0
- vispy/visuals/tests/test_gridlines.py +30 -0
- vispy/visuals/tests/test_histogram.py +24 -0
- vispy/visuals/tests/test_image.py +392 -0
- vispy/visuals/tests/test_image_complex.py +36 -0
- vispy/visuals/tests/test_infinite_line.py +53 -0
- vispy/visuals/tests/test_instanced_mesh.py +50 -0
- vispy/visuals/tests/test_isosurface.py +22 -0
- vispy/visuals/tests/test_linear_region.py +152 -0
- vispy/visuals/tests/test_markers.py +54 -0
- vispy/visuals/tests/test_mesh.py +261 -0
- vispy/visuals/tests/test_mesh_normals.py +218 -0
- vispy/visuals/tests/test_polygon.py +112 -0
- vispy/visuals/tests/test_rectangle.py +163 -0
- vispy/visuals/tests/test_regular_polygon.py +111 -0
- vispy/visuals/tests/test_scalable_textures.py +196 -0
- vispy/visuals/tests/test_sdf.py +73 -0
- vispy/visuals/tests/test_spectrogram.py +42 -0
- vispy/visuals/tests/test_surface_plot.py +57 -0
- vispy/visuals/tests/test_text.py +95 -0
- vispy/visuals/tests/test_volume.py +542 -0
- vispy/visuals/tests/test_windbarb.py +33 -0
- vispy/visuals/text/__init__.py +7 -0
- vispy/visuals/text/_sdf_cpu.cpython-313-aarch64-linux-gnu.so +0 -0
- vispy/visuals/text/_sdf_cpu.pyx +112 -0
- vispy/visuals/text/_sdf_gpu.py +316 -0
- vispy/visuals/text/text.py +675 -0
- vispy/visuals/transforms/__init__.py +34 -0
- vispy/visuals/transforms/_util.py +191 -0
- vispy/visuals/transforms/base_transform.py +233 -0
- vispy/visuals/transforms/chain.py +300 -0
- vispy/visuals/transforms/interactive.py +98 -0
- vispy/visuals/transforms/linear.py +564 -0
- vispy/visuals/transforms/nonlinear.py +398 -0
- vispy/visuals/transforms/tests/__init__.py +0 -0
- vispy/visuals/transforms/tests/test_transforms.py +243 -0
- vispy/visuals/transforms/transform_system.py +339 -0
- vispy/visuals/tube.py +173 -0
- vispy/visuals/visual.py +923 -0
- vispy/visuals/volume.py +1366 -0
- vispy/visuals/windbarb.py +291 -0
- vispy/visuals/xyz_axis.py +34 -0
- vispy-0.15.0.dist-info/METADATA +243 -0
- vispy-0.15.0.dist-info/RECORD +521 -0
- vispy-0.15.0.dist-info/WHEEL +6 -0
- vispy-0.15.0.dist-info/licenses/LICENSE.txt +36 -0
- vispy-0.15.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,876 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from __future__ import division, print_function
|
|
4
|
+
|
|
5
|
+
from itertools import permutations
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from collections import OrderedDict
|
|
9
|
+
|
|
10
|
+
from .calculations import _cross_2d
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Triangulation(object):
|
|
14
|
+
"""Constrained delaunay triangulation
|
|
15
|
+
|
|
16
|
+
Implementation based on [1]_.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
pts : array
|
|
21
|
+
Nx2 array of points.
|
|
22
|
+
edges : array
|
|
23
|
+
Nx2 array of edges (dtype=int).
|
|
24
|
+
|
|
25
|
+
Notes
|
|
26
|
+
-----
|
|
27
|
+
* Delaunay legalization is not yet implemented. This produces a proper
|
|
28
|
+
triangulation, but adding legalisation would produce fewer thin
|
|
29
|
+
triangles.
|
|
30
|
+
* The pts and edges arrays may be modified.
|
|
31
|
+
|
|
32
|
+
References
|
|
33
|
+
----------
|
|
34
|
+
.. [1] Domiter, V. and Žalik, B. Sweep‐line algorithm for constrained
|
|
35
|
+
Delaunay triangulation
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, pts, edges):
|
|
39
|
+
self.pts = pts[:, :2].astype(np.float32)
|
|
40
|
+
self.edges = edges
|
|
41
|
+
if self.pts.ndim != 2 or self.pts.shape[1] != 2:
|
|
42
|
+
raise TypeError('pts argument must be ndarray of shape (N, 2).')
|
|
43
|
+
if self.edges.ndim != 2 or self.edges.shape[1] != 2:
|
|
44
|
+
raise TypeError('edges argument must be ndarray of shape (N, 2).')
|
|
45
|
+
|
|
46
|
+
# described in initialize()
|
|
47
|
+
self._front = None
|
|
48
|
+
self.tris = OrderedDict()
|
|
49
|
+
self._edges_lookup = {}
|
|
50
|
+
|
|
51
|
+
def _normalize(self):
|
|
52
|
+
# Clean up data (not discussed in original publication)
|
|
53
|
+
|
|
54
|
+
# (i) Split intersecting edges. Every edge that intersects another
|
|
55
|
+
# edge or point is split. This extends self.pts and self.edges.
|
|
56
|
+
self._split_intersecting_edges()
|
|
57
|
+
|
|
58
|
+
# (ii) Merge identical points. If any two points are found to be equal,
|
|
59
|
+
# the second is removed and the edge table is updated accordingly.
|
|
60
|
+
self._merge_duplicate_points()
|
|
61
|
+
|
|
62
|
+
# (iii) Remove duplicate edges
|
|
63
|
+
# TODO
|
|
64
|
+
|
|
65
|
+
def _initialize(self):
|
|
66
|
+
self._normalize()
|
|
67
|
+
# Initialization (sec. 3.3)
|
|
68
|
+
|
|
69
|
+
# sort points by y, then x
|
|
70
|
+
flat_shape = self.pts.shape[0] * self.pts.shape[1]
|
|
71
|
+
pts = self.pts.reshape(flat_shape).view([('x', np.float32),
|
|
72
|
+
('y', np.float32)])
|
|
73
|
+
order = np.argsort(pts, order=('y', 'x'))
|
|
74
|
+
pts = pts[order]
|
|
75
|
+
# update edges to match new point order
|
|
76
|
+
invorder = np.argsort(order)
|
|
77
|
+
self.edges = invorder[self.edges]
|
|
78
|
+
self.pts = pts.view(np.float32).reshape(len(pts), 2)
|
|
79
|
+
|
|
80
|
+
# make artificial points P-1 and P-2
|
|
81
|
+
xmax = self.pts[:, 0].max()
|
|
82
|
+
xmin = self.pts[:, 0].min()
|
|
83
|
+
ymax = self.pts[:, 1].max()
|
|
84
|
+
ymin = self.pts[:, 1].min()
|
|
85
|
+
xa = (xmax-xmin) * 0.3
|
|
86
|
+
ya = (ymax-ymin) * 0.3
|
|
87
|
+
p1 = (xmin - xa, ymin - ya)
|
|
88
|
+
p2 = (xmax + xa, ymin - ya)
|
|
89
|
+
|
|
90
|
+
# prepend artificial points to point list
|
|
91
|
+
newpts = np.empty((self.pts.shape[0]+2, 2), dtype=float)
|
|
92
|
+
newpts[0] = p1
|
|
93
|
+
newpts[1] = p2
|
|
94
|
+
newpts[2:] = self.pts
|
|
95
|
+
self.pts = newpts
|
|
96
|
+
self.edges += 2
|
|
97
|
+
|
|
98
|
+
# find topmost point in each edge
|
|
99
|
+
self._tops = self.edges.max(axis=1)
|
|
100
|
+
self._bottoms = self.edges.min(axis=1)
|
|
101
|
+
|
|
102
|
+
# initialize sweep front
|
|
103
|
+
# values in this list are indexes into self.pts
|
|
104
|
+
self._front = [0, 2, 1]
|
|
105
|
+
|
|
106
|
+
# empty triangle list.
|
|
107
|
+
# This will contain [(a, b, c), ...] where a,b,c are indexes into
|
|
108
|
+
# self.pts
|
|
109
|
+
self.tris = OrderedDict()
|
|
110
|
+
|
|
111
|
+
# For each triangle, maps (a, b): c
|
|
112
|
+
# This is used to look up the thrid point in a triangle, given any
|
|
113
|
+
# edge. Since each edge has two triangles, they are independently
|
|
114
|
+
# stored as (a, b): c and (b, a): d
|
|
115
|
+
self._edges_lookup = {}
|
|
116
|
+
|
|
117
|
+
def triangulate(self):
|
|
118
|
+
"""Do the triangulation."""
|
|
119
|
+
self._initialize()
|
|
120
|
+
|
|
121
|
+
pts = self.pts
|
|
122
|
+
front = self._front
|
|
123
|
+
|
|
124
|
+
# Begin sweep (sec. 3.4)
|
|
125
|
+
for i in range(3, pts.shape[0]):
|
|
126
|
+
pi = pts[i]
|
|
127
|
+
|
|
128
|
+
# First, triangulate from front to new point
|
|
129
|
+
# This applies to both "point events" (3.4.1)
|
|
130
|
+
# and "edge events" (3.4.2).
|
|
131
|
+
|
|
132
|
+
# get index along front that intersects pts[i]
|
|
133
|
+
idx = 0
|
|
134
|
+
while pts[front[idx+1], 0] <= pi[0]:
|
|
135
|
+
idx += 1
|
|
136
|
+
pl = pts[front[idx]]
|
|
137
|
+
|
|
138
|
+
# "(i) middle case"
|
|
139
|
+
if pi[0] > pl[0]:
|
|
140
|
+
# Add a single triangle connecting pi,pl,pr
|
|
141
|
+
self._add_tri(front[idx], front[idx+1], i)
|
|
142
|
+
front.insert(idx+1, i)
|
|
143
|
+
# "(ii) left case"
|
|
144
|
+
else:
|
|
145
|
+
# Add triangles connecting pi,pl,ps and pi,pl,pr
|
|
146
|
+
self._add_tri(front[idx], front[idx+1], i)
|
|
147
|
+
self._add_tri(front[idx-1], front[idx], i)
|
|
148
|
+
front[idx] = i
|
|
149
|
+
|
|
150
|
+
# Continue adding triangles to smooth out front
|
|
151
|
+
# (heuristics shown in figs. 9, 10)
|
|
152
|
+
for direction in -1, 1:
|
|
153
|
+
while True:
|
|
154
|
+
# Find point connected to pi
|
|
155
|
+
ind0 = front.index(i)
|
|
156
|
+
ind1 = ind0 + direction
|
|
157
|
+
ind2 = ind1 + direction
|
|
158
|
+
if ind2 < 0 or ind2 >= len(front):
|
|
159
|
+
break
|
|
160
|
+
|
|
161
|
+
# measure angle made with front
|
|
162
|
+
p1 = pts[front[ind1]]
|
|
163
|
+
p2 = pts[front[ind2]]
|
|
164
|
+
err = np.geterr()
|
|
165
|
+
np.seterr(invalid='ignore')
|
|
166
|
+
try:
|
|
167
|
+
angle = np.arccos(self._cosine(pi, p1, p2))
|
|
168
|
+
finally:
|
|
169
|
+
np.seterr(**err)
|
|
170
|
+
|
|
171
|
+
# if angle is < pi/2, make new triangle
|
|
172
|
+
if angle > np.pi/2. or np.isnan(angle):
|
|
173
|
+
break
|
|
174
|
+
|
|
175
|
+
assert (i != front[ind1] and
|
|
176
|
+
front[ind1] != front[ind2] and
|
|
177
|
+
front[ind2] != i)
|
|
178
|
+
self._add_tri(i, front[ind1], front[ind2])
|
|
179
|
+
front.pop(ind1)
|
|
180
|
+
|
|
181
|
+
# "edge event" (sec. 3.4.2)
|
|
182
|
+
# remove any triangles cut by completed edges and re-fill
|
|
183
|
+
# the holes.
|
|
184
|
+
if i in self._tops:
|
|
185
|
+
for j in self._bottoms[self._tops == i]:
|
|
186
|
+
# Make sure edge (j, i) is present in mesh
|
|
187
|
+
# because edge event may have created a new front list
|
|
188
|
+
self._edge_event(i, int(j))
|
|
189
|
+
front = self._front
|
|
190
|
+
|
|
191
|
+
self._finalize()
|
|
192
|
+
|
|
193
|
+
self.tris = np.array(list(self.tris.keys()), dtype=int)
|
|
194
|
+
|
|
195
|
+
def _finalize(self):
|
|
196
|
+
# Finalize (sec. 3.5)
|
|
197
|
+
|
|
198
|
+
# (i) Add bordering triangles to fill hull
|
|
199
|
+
front = list(OrderedDict.fromkeys(self._front))
|
|
200
|
+
|
|
201
|
+
idx = len(front) - 2
|
|
202
|
+
k = 1
|
|
203
|
+
while k < idx-1:
|
|
204
|
+
# if edges lie in counterclockwise direction, then signed area
|
|
205
|
+
# is positive
|
|
206
|
+
if self._orientation((front[k], front[k+1]), front[k+2]) < 0:
|
|
207
|
+
self._add_tri(front[k], front[k+1], front[k+2])
|
|
208
|
+
front.pop(k+1)
|
|
209
|
+
idx -= 1
|
|
210
|
+
continue
|
|
211
|
+
k += 1
|
|
212
|
+
|
|
213
|
+
# (ii) Remove all triangles not inside the hull
|
|
214
|
+
# (not described in article)
|
|
215
|
+
|
|
216
|
+
tris = [] # triangles to check
|
|
217
|
+
tri_state = {} # 0 for outside, 1 for inside
|
|
218
|
+
|
|
219
|
+
# find a starting triangle
|
|
220
|
+
for t in self.tris:
|
|
221
|
+
if 0 in t or 1 in t:
|
|
222
|
+
tri_state[t] = 0
|
|
223
|
+
tris.append(t)
|
|
224
|
+
break
|
|
225
|
+
|
|
226
|
+
while tris:
|
|
227
|
+
next_tris = []
|
|
228
|
+
for t in tris:
|
|
229
|
+
v = tri_state[t]
|
|
230
|
+
for i in (0, 1, 2):
|
|
231
|
+
edge = (t[i], t[(i + 1) % 3])
|
|
232
|
+
pt = t[(i + 2) % 3]
|
|
233
|
+
t2 = self._adjacent_tri(edge, pt)
|
|
234
|
+
if t2 is None:
|
|
235
|
+
continue
|
|
236
|
+
t2a = t2[1:3] + t2[0:1]
|
|
237
|
+
t2b = t2[2:3] + t2[0:2]
|
|
238
|
+
if t2 in tri_state or t2a in tri_state or t2b in tri_state:
|
|
239
|
+
continue
|
|
240
|
+
if self._is_constraining_edge(edge):
|
|
241
|
+
tri_state[t2] = 1 - v
|
|
242
|
+
else:
|
|
243
|
+
tri_state[t2] = v
|
|
244
|
+
next_tris.append(t2)
|
|
245
|
+
tris = next_tris
|
|
246
|
+
|
|
247
|
+
for t, v in tri_state.items():
|
|
248
|
+
if v == 0:
|
|
249
|
+
self._remove_tri(*t)
|
|
250
|
+
|
|
251
|
+
def _edge_event(self, i, j):
|
|
252
|
+
"""Force edge (i, j) to be present in mesh.
|
|
253
|
+
|
|
254
|
+
This works by removing intersected triangles and filling holes up to
|
|
255
|
+
the cutting edge.
|
|
256
|
+
"""
|
|
257
|
+
front_index = self._front.index(i)
|
|
258
|
+
|
|
259
|
+
front = self._front
|
|
260
|
+
|
|
261
|
+
# First just see whether this edge is already present
|
|
262
|
+
# (this is not in the published algorithm)
|
|
263
|
+
if (i, j) in self._edges_lookup or (j, i) in self._edges_lookup:
|
|
264
|
+
return
|
|
265
|
+
|
|
266
|
+
# traverse in two different modes:
|
|
267
|
+
# 1. If cutting edge is below front, traverse through triangles. These
|
|
268
|
+
# must be removed and the resulting hole re-filled. (fig. 12)
|
|
269
|
+
# 2. If cutting edge is above the front, then follow the front until
|
|
270
|
+
# crossing under again. (fig. 13)
|
|
271
|
+
# We must be able to switch back and forth between these
|
|
272
|
+
# modes (fig. 14)
|
|
273
|
+
|
|
274
|
+
# Collect points that draw the open polygons on either side of the
|
|
275
|
+
# cutting edge. Note that our use of 'upper' and 'lower' is not strict;
|
|
276
|
+
# in some cases the two may be swapped.
|
|
277
|
+
upper_polygon = [i]
|
|
278
|
+
lower_polygon = [i]
|
|
279
|
+
|
|
280
|
+
# Keep track of which section of the front must be replaced
|
|
281
|
+
# and with what it should be replaced
|
|
282
|
+
front_holes = [] # contains indexes for sections of front to remove
|
|
283
|
+
|
|
284
|
+
next_tri = None # next triangle to cut (already set if in mode 1)
|
|
285
|
+
last_edge = None # or last triangle edge crossed (if in mode 1)
|
|
286
|
+
|
|
287
|
+
# Which direction to traverse front
|
|
288
|
+
front_dir = 1 if self.pts[j][0] > self.pts[i][0] else -1
|
|
289
|
+
|
|
290
|
+
# Initialize search state
|
|
291
|
+
if self._edge_below_front((i, j), front_index):
|
|
292
|
+
mode = 1 # follow triangles
|
|
293
|
+
tri = self._find_cut_triangle((i, j))
|
|
294
|
+
last_edge = self._edge_opposite_point(tri, i)
|
|
295
|
+
next_tri = self._adjacent_tri(last_edge, i)
|
|
296
|
+
assert next_tri is not None
|
|
297
|
+
self._remove_tri(*tri)
|
|
298
|
+
# todo: does this work? can we count on last_edge to be clockwise
|
|
299
|
+
# around point i?
|
|
300
|
+
lower_polygon.append(last_edge[1])
|
|
301
|
+
upper_polygon.append(last_edge[0])
|
|
302
|
+
else:
|
|
303
|
+
mode = 2 # follow front
|
|
304
|
+
|
|
305
|
+
# Loop until we reach point j
|
|
306
|
+
while True:
|
|
307
|
+
if mode == 1:
|
|
308
|
+
# crossing from one triangle into another
|
|
309
|
+
if j in next_tri:
|
|
310
|
+
# reached endpoint!
|
|
311
|
+
# update front / polygons
|
|
312
|
+
upper_polygon.append(j)
|
|
313
|
+
lower_polygon.append(j)
|
|
314
|
+
self._remove_tri(*next_tri)
|
|
315
|
+
break
|
|
316
|
+
else:
|
|
317
|
+
# next triangle does not contain the end point; we will
|
|
318
|
+
# cut one of the two far edges.
|
|
319
|
+
tri_edges = self._edges_in_tri_except(next_tri, last_edge)
|
|
320
|
+
|
|
321
|
+
# select the edge that is cut
|
|
322
|
+
last_edge = self._intersected_edge(tri_edges, (i, j))
|
|
323
|
+
last_tri = next_tri
|
|
324
|
+
next_tri = self._adjacent_tri(last_edge, last_tri)
|
|
325
|
+
self._remove_tri(*last_tri)
|
|
326
|
+
|
|
327
|
+
# Crossing an edge adds one point to one of the polygons
|
|
328
|
+
if lower_polygon[-1] == last_edge[0]:
|
|
329
|
+
upper_polygon.append(last_edge[1])
|
|
330
|
+
elif lower_polygon[-1] == last_edge[1]:
|
|
331
|
+
upper_polygon.append(last_edge[0])
|
|
332
|
+
elif upper_polygon[-1] == last_edge[0]:
|
|
333
|
+
lower_polygon.append(last_edge[1])
|
|
334
|
+
elif upper_polygon[-1] == last_edge[1]:
|
|
335
|
+
lower_polygon.append(last_edge[0])
|
|
336
|
+
else:
|
|
337
|
+
raise RuntimeError("Something went wrong..")
|
|
338
|
+
|
|
339
|
+
# If we crossed the front, go to mode 2
|
|
340
|
+
x = self._edge_in_front(last_edge)
|
|
341
|
+
if x >= 0: # crossing over front
|
|
342
|
+
mode = 2
|
|
343
|
+
next_tri = None
|
|
344
|
+
|
|
345
|
+
# where did we cross the front?
|
|
346
|
+
# nearest to new point
|
|
347
|
+
front_index = x + (1 if front_dir == -1 else 0)
|
|
348
|
+
|
|
349
|
+
# Select the correct polygon to be lower_polygon
|
|
350
|
+
# (because mode 2 requires this).
|
|
351
|
+
# We know that last_edge is in the front, and
|
|
352
|
+
# front[front_index] is the point _above_ the front.
|
|
353
|
+
# So if this point is currently the last element in
|
|
354
|
+
# lower_polygon, then the polys must be swapped.
|
|
355
|
+
if lower_polygon[-1] == front[front_index]:
|
|
356
|
+
tmp = lower_polygon, upper_polygon
|
|
357
|
+
upper_polygon, lower_polygon = tmp
|
|
358
|
+
else:
|
|
359
|
+
assert upper_polygon[-1] == front[front_index]
|
|
360
|
+
|
|
361
|
+
else:
|
|
362
|
+
assert next_tri is not None
|
|
363
|
+
|
|
364
|
+
else: # mode == 2
|
|
365
|
+
# At each iteration, we require:
|
|
366
|
+
# * front_index is the starting index of the edge _preceding_
|
|
367
|
+
# the edge that will be handled in this iteration
|
|
368
|
+
# * lower_polygon is the polygon to which points should be
|
|
369
|
+
# added while traversing the front
|
|
370
|
+
|
|
371
|
+
front_index += front_dir
|
|
372
|
+
next_edge = (front[front_index], front[front_index+front_dir])
|
|
373
|
+
|
|
374
|
+
assert front_index >= 0
|
|
375
|
+
if front[front_index] == j:
|
|
376
|
+
# found endpoint!
|
|
377
|
+
lower_polygon.append(j)
|
|
378
|
+
upper_polygon.append(j)
|
|
379
|
+
break
|
|
380
|
+
|
|
381
|
+
# Add point to lower_polygon.
|
|
382
|
+
# The conditional is because there are cases where the
|
|
383
|
+
# point was already added if we just crossed from mode 1.
|
|
384
|
+
if lower_polygon[-1] != front[front_index]:
|
|
385
|
+
lower_polygon.append(front[front_index])
|
|
386
|
+
|
|
387
|
+
front_holes.append(front_index)
|
|
388
|
+
|
|
389
|
+
if self._edges_intersect((i, j), next_edge):
|
|
390
|
+
# crossing over front into triangle
|
|
391
|
+
mode = 1
|
|
392
|
+
|
|
393
|
+
last_edge = next_edge
|
|
394
|
+
|
|
395
|
+
# we are crossing the front, so this edge only has one
|
|
396
|
+
# triangle.
|
|
397
|
+
next_tri = self._tri_from_edge(last_edge)
|
|
398
|
+
|
|
399
|
+
upper_polygon.append(front[front_index+front_dir])
|
|
400
|
+
|
|
401
|
+
# (iii) triangulate empty areas
|
|
402
|
+
|
|
403
|
+
for polygon in [lower_polygon, upper_polygon]:
|
|
404
|
+
dist = self._distances_from_line((i, j), polygon)
|
|
405
|
+
while len(polygon) > 2:
|
|
406
|
+
ind = np.argmax(dist)
|
|
407
|
+
self._add_tri(polygon[ind], polygon[ind-1],
|
|
408
|
+
polygon[ind+1])
|
|
409
|
+
polygon.pop(ind)
|
|
410
|
+
dist.pop(ind)
|
|
411
|
+
|
|
412
|
+
# update front by removing points in the holes (places where front
|
|
413
|
+
# passes below the cut edge)
|
|
414
|
+
front_holes.sort(reverse=True)
|
|
415
|
+
for i in front_holes:
|
|
416
|
+
front.pop(i)
|
|
417
|
+
|
|
418
|
+
def _find_cut_triangle(self, edge):
|
|
419
|
+
"""
|
|
420
|
+
Return the triangle that has edge[0] as one of its vertices and is
|
|
421
|
+
bisected by edge.
|
|
422
|
+
|
|
423
|
+
Return None if no triangle is found.
|
|
424
|
+
"""
|
|
425
|
+
edges = [] # opposite edge for each triangle attached to edge[0]
|
|
426
|
+
for tri in self.tris:
|
|
427
|
+
if edge[0] in tri:
|
|
428
|
+
edges.append(self._edge_opposite_point(tri, edge[0]))
|
|
429
|
+
|
|
430
|
+
for oedge in edges:
|
|
431
|
+
o1 = self._orientation(edge, oedge[0])
|
|
432
|
+
o2 = self._orientation(edge, oedge[1])
|
|
433
|
+
if o1 != o2:
|
|
434
|
+
return (edge[0], oedge[0], oedge[1])
|
|
435
|
+
|
|
436
|
+
return None
|
|
437
|
+
|
|
438
|
+
def _edge_in_front(self, edge):
|
|
439
|
+
"""Return the index where *edge* appears in the current front.
|
|
440
|
+
|
|
441
|
+
If the edge is not in the front, return -1
|
|
442
|
+
"""
|
|
443
|
+
e = (list(edge), list(edge)[::-1])
|
|
444
|
+
for i in range(len(self._front)-1):
|
|
445
|
+
if self._front[i:i+2] in e:
|
|
446
|
+
return i
|
|
447
|
+
return -1
|
|
448
|
+
|
|
449
|
+
def _edge_opposite_point(self, tri, i):
|
|
450
|
+
"""Given a triangle, return the edge that is opposite point i.
|
|
451
|
+
|
|
452
|
+
Vertexes are returned in the same orientation as in tri.
|
|
453
|
+
"""
|
|
454
|
+
ind = tri.index(i)
|
|
455
|
+
return (tri[(ind+1) % 3], tri[(ind+2) % 3])
|
|
456
|
+
|
|
457
|
+
def _adjacent_tri(self, edge, i):
|
|
458
|
+
"""Given a triangle formed by edge and i, return the triangle that shares
|
|
459
|
+
edge. *i* may be either a point or the entire triangle.
|
|
460
|
+
"""
|
|
461
|
+
if not np.isscalar(i):
|
|
462
|
+
i = [x for x in i if x not in edge][0]
|
|
463
|
+
|
|
464
|
+
try:
|
|
465
|
+
pt1 = self._edges_lookup[edge]
|
|
466
|
+
pt2 = self._edges_lookup[(edge[1], edge[0])]
|
|
467
|
+
except KeyError:
|
|
468
|
+
return None
|
|
469
|
+
|
|
470
|
+
if pt1 == i:
|
|
471
|
+
return (edge[1], edge[0], pt2)
|
|
472
|
+
elif pt2 == i:
|
|
473
|
+
return (edge[1], edge[0], pt1)
|
|
474
|
+
else:
|
|
475
|
+
raise RuntimeError("Edge %s and point %d do not form a triangle "
|
|
476
|
+
"in this mesh." % (edge, i))
|
|
477
|
+
|
|
478
|
+
def _tri_from_edge(self, edge):
|
|
479
|
+
"""Return the only tri that contains *edge*.
|
|
480
|
+
|
|
481
|
+
If two tris share this edge, raise an exception.
|
|
482
|
+
"""
|
|
483
|
+
edge = tuple(edge)
|
|
484
|
+
p1 = self._edges_lookup.get(edge, None)
|
|
485
|
+
p2 = self._edges_lookup.get(edge[::-1], None)
|
|
486
|
+
if p1 is None:
|
|
487
|
+
if p2 is None:
|
|
488
|
+
raise RuntimeError("No tris connected to edge %r" % (edge,))
|
|
489
|
+
return edge + (p2,)
|
|
490
|
+
elif p2 is None:
|
|
491
|
+
return edge + (p1,)
|
|
492
|
+
else:
|
|
493
|
+
raise RuntimeError("Two triangles connected to edge %r" % (edge,))
|
|
494
|
+
|
|
495
|
+
def _edges_in_tri_except(self, tri, edge):
|
|
496
|
+
"""Return the edges in *tri*, excluding *edge*."""
|
|
497
|
+
edges = [(tri[i], tri[(i+1) % 3]) for i in range(3)]
|
|
498
|
+
try:
|
|
499
|
+
edges.remove(tuple(edge))
|
|
500
|
+
except ValueError:
|
|
501
|
+
edges.remove(tuple(edge[::-1]))
|
|
502
|
+
return edges
|
|
503
|
+
|
|
504
|
+
def _edge_below_front(self, edge, front_index):
|
|
505
|
+
"""Return True if *edge* is below the current front.
|
|
506
|
+
|
|
507
|
+
One of the points in *edge* must be _on_ the front, at *front_index*.
|
|
508
|
+
"""
|
|
509
|
+
f0 = self._front[front_index-1]
|
|
510
|
+
f1 = self._front[front_index+1]
|
|
511
|
+
return (self._orientation(edge, f0) > 0 and
|
|
512
|
+
self._orientation(edge, f1) < 0)
|
|
513
|
+
|
|
514
|
+
def _is_constraining_edge(self, edge):
|
|
515
|
+
mask1 = self.edges == edge[0]
|
|
516
|
+
mask2 = self.edges == edge[1]
|
|
517
|
+
return (np.any(mask1[:, 0] & mask2[:, 1]) or
|
|
518
|
+
np.any(mask2[:, 0] & mask1[:, 1]))
|
|
519
|
+
|
|
520
|
+
def _intersected_edge(self, edges, cut_edge):
|
|
521
|
+
"""Given a list of *edges*, return the first that is intersected by
|
|
522
|
+
*cut_edge*.
|
|
523
|
+
"""
|
|
524
|
+
for edge in edges:
|
|
525
|
+
if self._edges_intersect(edge, cut_edge):
|
|
526
|
+
return edge
|
|
527
|
+
|
|
528
|
+
def _find_edge_intersections(self):
|
|
529
|
+
"""Return a dictionary containing, for each edge in self.edges, a list
|
|
530
|
+
of the positions at which the edge should be split.
|
|
531
|
+
"""
|
|
532
|
+
edges = self.pts[self.edges]
|
|
533
|
+
cuts = {} # { edge: [(intercept, point), ...], ... }
|
|
534
|
+
for i in range(edges.shape[0]-1):
|
|
535
|
+
# intersection of edge i onto all others
|
|
536
|
+
int1 = self._intersect_edge_arrays(edges[i:i+1], edges[i+1:])
|
|
537
|
+
# intersection of all edges onto edge i
|
|
538
|
+
int2 = self._intersect_edge_arrays(edges[i+1:], edges[i:i+1])
|
|
539
|
+
|
|
540
|
+
# select for pairs that intersect
|
|
541
|
+
err = np.geterr()
|
|
542
|
+
np.seterr(divide='ignore', invalid='ignore')
|
|
543
|
+
try:
|
|
544
|
+
mask1 = (int1 >= 0) & (int1 <= 1)
|
|
545
|
+
mask2 = (int2 >= 0) & (int2 <= 1)
|
|
546
|
+
mask3 = mask1 & mask2 # all intersections
|
|
547
|
+
finally:
|
|
548
|
+
np.seterr(**err)
|
|
549
|
+
|
|
550
|
+
# compute points of intersection
|
|
551
|
+
inds = np.argwhere(mask3)[:, 0]
|
|
552
|
+
if len(inds) == 0:
|
|
553
|
+
continue
|
|
554
|
+
h = int2[inds][:, np.newaxis]
|
|
555
|
+
pts = (edges[i, 0][np.newaxis, :] * (1.0 - h) +
|
|
556
|
+
edges[i, 1][np.newaxis, :] * h)
|
|
557
|
+
|
|
558
|
+
# record for all edges the location of cut points
|
|
559
|
+
edge_cuts = cuts.setdefault(i, [])
|
|
560
|
+
for j, ind in enumerate(inds):
|
|
561
|
+
if 0 < int2[ind] < 1:
|
|
562
|
+
edge_cuts.append((int2[ind], pts[j]))
|
|
563
|
+
if 0 < int1[ind] < 1:
|
|
564
|
+
other_cuts = cuts.setdefault(ind+i+1, [])
|
|
565
|
+
other_cuts.append((int1[ind], pts[j]))
|
|
566
|
+
|
|
567
|
+
# sort all cut lists by intercept, remove duplicates
|
|
568
|
+
for k, v in cuts.items():
|
|
569
|
+
v.sort(key=lambda x: x[0])
|
|
570
|
+
for i in range(len(v)-2, -1, -1):
|
|
571
|
+
if v[i][0] == v[i+1][0]:
|
|
572
|
+
v.pop(i+1)
|
|
573
|
+
return cuts
|
|
574
|
+
|
|
575
|
+
def _split_intersecting_edges(self):
|
|
576
|
+
# we can do all intersections at once, but this has excessive memory
|
|
577
|
+
# overhead.
|
|
578
|
+
|
|
579
|
+
# measure intersection point between all pairs of edges
|
|
580
|
+
all_cuts = self._find_edge_intersections()
|
|
581
|
+
|
|
582
|
+
# cut edges at each intersection
|
|
583
|
+
add_pts = []
|
|
584
|
+
add_edges = []
|
|
585
|
+
for edge, cuts in all_cuts.items():
|
|
586
|
+
if len(cuts) == 0:
|
|
587
|
+
continue
|
|
588
|
+
|
|
589
|
+
# add new points
|
|
590
|
+
pt_offset = self.pts.shape[0] + len(add_pts)
|
|
591
|
+
new_pts = [x[1] for x in cuts]
|
|
592
|
+
add_pts.extend(new_pts)
|
|
593
|
+
|
|
594
|
+
# list of point indexes for all new edges
|
|
595
|
+
pt_indexes = list(range(pt_offset, pt_offset + len(cuts)))
|
|
596
|
+
pt_indexes.append(self.edges[edge, 1])
|
|
597
|
+
|
|
598
|
+
# modify original edge
|
|
599
|
+
self.edges[edge, 1] = pt_indexes[0]
|
|
600
|
+
|
|
601
|
+
# add new edges
|
|
602
|
+
new_edges = [[pt_indexes[i-1], pt_indexes[i]]
|
|
603
|
+
for i in range(1, len(pt_indexes))]
|
|
604
|
+
add_edges.extend(new_edges)
|
|
605
|
+
|
|
606
|
+
if add_pts:
|
|
607
|
+
add_pts = np.array(add_pts, dtype=self.pts.dtype)
|
|
608
|
+
self.pts = np.append(self.pts, add_pts, axis=0)
|
|
609
|
+
if add_edges:
|
|
610
|
+
add_edges = np.array(add_edges, dtype=self.edges.dtype)
|
|
611
|
+
self.edges = np.append(self.edges, add_edges, axis=0)
|
|
612
|
+
|
|
613
|
+
def _merge_duplicate_points(self):
|
|
614
|
+
# generate a list of all pairs (i,j) of identical points
|
|
615
|
+
dups = []
|
|
616
|
+
for i in range(self.pts.shape[0]-1):
|
|
617
|
+
test_pt = self.pts[i:i+1]
|
|
618
|
+
comp_pts = self.pts[i+1:]
|
|
619
|
+
eq = test_pt == comp_pts
|
|
620
|
+
eq = eq[:, 0] & eq[:, 1]
|
|
621
|
+
for j in np.argwhere(eq)[:, 0]:
|
|
622
|
+
dups.append((i, i+1+j))
|
|
623
|
+
|
|
624
|
+
dups_arr = np.array(dups)
|
|
625
|
+
# remove duplicate points
|
|
626
|
+
pt_mask = np.ones(self.pts.shape[0], dtype=bool)
|
|
627
|
+
for i, inds in enumerate(dups_arr):
|
|
628
|
+
# remove j from points
|
|
629
|
+
# (note we pull the index from the original dups instead of
|
|
630
|
+
# dups_arr because the indexes in pt_mask do not change)
|
|
631
|
+
pt_mask[dups[i][1]] = False
|
|
632
|
+
|
|
633
|
+
i, j = inds
|
|
634
|
+
|
|
635
|
+
# rewrite edges to use i instead of j
|
|
636
|
+
self.edges[self.edges == j] = i
|
|
637
|
+
|
|
638
|
+
# decrement all point indexes > j
|
|
639
|
+
self.edges[self.edges > j] -= 1
|
|
640
|
+
dups_arr[dups_arr > j] -= 1
|
|
641
|
+
|
|
642
|
+
self.pts = self.pts[pt_mask]
|
|
643
|
+
|
|
644
|
+
# remove zero-length edges
|
|
645
|
+
mask = self.edges[:, 0] != self.edges[:, 1]
|
|
646
|
+
self.edges = self.edges[mask]
|
|
647
|
+
|
|
648
|
+
def _distances_from_line(self, edge, points):
|
|
649
|
+
# Distance of a set of points from a given line
|
|
650
|
+
e1 = self.pts[edge[0]]
|
|
651
|
+
e2 = self.pts[edge[1]]
|
|
652
|
+
distances = []
|
|
653
|
+
for i in points:
|
|
654
|
+
p = self.pts[i]
|
|
655
|
+
proj = self._projection(e1, p, e2)
|
|
656
|
+
distances.append(((p - proj)**2).sum()**0.5)
|
|
657
|
+
assert distances[0] == 0 and distances[-1] == 0
|
|
658
|
+
return distances
|
|
659
|
+
|
|
660
|
+
def _projection(self, a, b, c):
|
|
661
|
+
"""Return projection of (a,b) onto (a,c)
|
|
662
|
+
Arguments are point locations, not indexes.
|
|
663
|
+
"""
|
|
664
|
+
ab = b - a
|
|
665
|
+
ac = c - a
|
|
666
|
+
return a + ((ab*ac).sum() / (ac*ac).sum()) * ac
|
|
667
|
+
|
|
668
|
+
def _cosine(self, A, B, C):
|
|
669
|
+
# Cosine of angle ABC
|
|
670
|
+
a = ((C - B)**2).sum()
|
|
671
|
+
b = ((C - A)**2).sum()
|
|
672
|
+
c = ((B - A)**2).sum()
|
|
673
|
+
d = (a + c - b) / ((4 * a * c)**0.5)
|
|
674
|
+
return d
|
|
675
|
+
|
|
676
|
+
def _edges_intersect(self, edge1, edge2):
|
|
677
|
+
"""Return 1 if edges intersect completely (endpoints excluded)"""
|
|
678
|
+
h12 = self._intersect_edge_arrays(self.pts[np.array(edge1)],
|
|
679
|
+
self.pts[np.array(edge2)])
|
|
680
|
+
h21 = self._intersect_edge_arrays(self.pts[np.array(edge2)],
|
|
681
|
+
self.pts[np.array(edge1)])
|
|
682
|
+
err = np.geterr()
|
|
683
|
+
np.seterr(divide='ignore', invalid='ignore')
|
|
684
|
+
try:
|
|
685
|
+
out = (0 < h12 < 1) and (0 < h21 < 1)
|
|
686
|
+
finally:
|
|
687
|
+
np.seterr(**err)
|
|
688
|
+
return out
|
|
689
|
+
|
|
690
|
+
def _intersect_edge_arrays(self, lines1, lines2):
|
|
691
|
+
"""Return the intercepts of all lines defined in *lines1* as they
|
|
692
|
+
intersect all lines in *lines2*.
|
|
693
|
+
|
|
694
|
+
Arguments are of shape (..., 2, 2), where axes are:
|
|
695
|
+
|
|
696
|
+
0: number of lines
|
|
697
|
+
1: two points per line
|
|
698
|
+
2: x,y pair per point
|
|
699
|
+
|
|
700
|
+
Lines are compared elementwise across the arrays (lines1[i] is compared
|
|
701
|
+
against lines2[i]). If one of the arrays has N=1, then that line is
|
|
702
|
+
compared against all lines in the other array.
|
|
703
|
+
|
|
704
|
+
Returns an array of shape (N,) where each value indicates the intercept
|
|
705
|
+
relative to the defined line segment. A value of 0 indicates
|
|
706
|
+
intersection at the first endpoint, and a value of 1 indicates
|
|
707
|
+
intersection at the second endpoint. Values between 1 and 0 are on the
|
|
708
|
+
segment, whereas values outside 1 and 0 are off of the segment.
|
|
709
|
+
"""
|
|
710
|
+
# vector for each line in lines1
|
|
711
|
+
l1 = lines1[..., 1, :] - lines1[..., 0, :]
|
|
712
|
+
# vector for each line in lines2
|
|
713
|
+
l2 = lines2[..., 1, :] - lines2[..., 0, :]
|
|
714
|
+
# vector between first point of each line
|
|
715
|
+
diff = lines1[..., 0, :] - lines2[..., 0, :]
|
|
716
|
+
|
|
717
|
+
p = l1.copy()[..., ::-1] # vectors perpendicular to l1
|
|
718
|
+
p[..., 0] *= -1
|
|
719
|
+
|
|
720
|
+
f = (l2 * p).sum(axis=-1) # l2 dot p
|
|
721
|
+
# tempting, but bad idea!
|
|
722
|
+
err = np.geterr()
|
|
723
|
+
np.seterr(divide='ignore', invalid='ignore')
|
|
724
|
+
try:
|
|
725
|
+
h = (diff * p).sum(axis=-1) / f # diff dot p / f
|
|
726
|
+
finally:
|
|
727
|
+
np.seterr(**err)
|
|
728
|
+
|
|
729
|
+
return h
|
|
730
|
+
|
|
731
|
+
def _orientation(self, edge, point):
|
|
732
|
+
"""Returns +1 if edge[0]->point is clockwise from edge[0]->edge[1],
|
|
733
|
+
-1 if counterclockwise, and 0 if parallel.
|
|
734
|
+
"""
|
|
735
|
+
v1 = self.pts[point] - self.pts[edge[0]]
|
|
736
|
+
v2 = self.pts[edge[1]] - self.pts[edge[0]]
|
|
737
|
+
c = _cross_2d(v1, v2) # positive if v1 is CW from v2
|
|
738
|
+
return 1 if c > 0 else (-1 if c < 0 else 0)
|
|
739
|
+
|
|
740
|
+
def _add_tri(self, a, b, c):
|
|
741
|
+
# sanity check
|
|
742
|
+
assert a != b and b != c and c != a
|
|
743
|
+
|
|
744
|
+
# ignore tris with duplicate points
|
|
745
|
+
pa = self.pts[a]
|
|
746
|
+
pb = self.pts[b]
|
|
747
|
+
pc = self.pts[c]
|
|
748
|
+
if np.all(pa == pb) or np.all(pb == pc) or np.all(pc == pa):
|
|
749
|
+
return
|
|
750
|
+
|
|
751
|
+
# check this tri is unique
|
|
752
|
+
for t in permutations((a, b, c)):
|
|
753
|
+
if t in self.tris:
|
|
754
|
+
raise Exception("Cannot add %s; already have %s" %
|
|
755
|
+
((a, b, c), t))
|
|
756
|
+
|
|
757
|
+
# ignore lines
|
|
758
|
+
orientation = self._orientation((a, b), c)
|
|
759
|
+
if orientation == 0:
|
|
760
|
+
return
|
|
761
|
+
|
|
762
|
+
# TODO: should add to edges_lookup after legalization??
|
|
763
|
+
if orientation < 0:
|
|
764
|
+
assert (a, b) not in self._edges_lookup
|
|
765
|
+
assert (b, c) not in self._edges_lookup
|
|
766
|
+
assert (c, a) not in self._edges_lookup
|
|
767
|
+
self._edges_lookup[(a, b)] = c
|
|
768
|
+
self._edges_lookup[(b, c)] = a
|
|
769
|
+
self._edges_lookup[(c, a)] = b
|
|
770
|
+
else:
|
|
771
|
+
assert (b, a) not in self._edges_lookup
|
|
772
|
+
assert (c, b) not in self._edges_lookup
|
|
773
|
+
assert (a, c) not in self._edges_lookup
|
|
774
|
+
self._edges_lookup[(b, a)] = c
|
|
775
|
+
self._edges_lookup[(c, b)] = a
|
|
776
|
+
self._edges_lookup[(a, c)] = b
|
|
777
|
+
|
|
778
|
+
tri = (a, b, c)
|
|
779
|
+
|
|
780
|
+
self.tris[tri] = None
|
|
781
|
+
|
|
782
|
+
def _remove_tri(self, a, b, c):
|
|
783
|
+
for k in permutations((a, b, c)):
|
|
784
|
+
if k in self.tris:
|
|
785
|
+
break
|
|
786
|
+
del self.tris[k]
|
|
787
|
+
(a, b, c) = k
|
|
788
|
+
|
|
789
|
+
if self._edges_lookup.get((a, b), -1) == c:
|
|
790
|
+
del self._edges_lookup[(a, b)]
|
|
791
|
+
del self._edges_lookup[(b, c)]
|
|
792
|
+
del self._edges_lookup[(c, a)]
|
|
793
|
+
elif self._edges_lookup.get((b, a), -1) == c:
|
|
794
|
+
del self._edges_lookup[(b, a)]
|
|
795
|
+
del self._edges_lookup[(a, c)]
|
|
796
|
+
del self._edges_lookup[(c, b)]
|
|
797
|
+
else:
|
|
798
|
+
raise RuntimeError("Lost edges_lookup for tri (%d, %d, %d)" %
|
|
799
|
+
(a, b, c))
|
|
800
|
+
|
|
801
|
+
return k
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
def _triangulate_python(vertices_2d, segments):
|
|
805
|
+
segments = segments.reshape(len(segments) // 2, 2)
|
|
806
|
+
T = Triangulation(vertices_2d, segments)
|
|
807
|
+
T.triangulate()
|
|
808
|
+
vertices_2d = T.pts
|
|
809
|
+
triangles = T.tris.ravel()
|
|
810
|
+
return vertices_2d, triangles
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
def _triangulate_cpp(vertices_2d, segments):
|
|
814
|
+
import triangle
|
|
815
|
+
T = triangle.triangulate({'vertices': vertices_2d,
|
|
816
|
+
'segments': segments}, "p")
|
|
817
|
+
vertices_2d = T["vertices"]
|
|
818
|
+
triangles = T["triangles"]
|
|
819
|
+
return vertices_2d, triangles
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
def triangulate(vertices):
|
|
823
|
+
"""Triangulate a set of vertices.
|
|
824
|
+
|
|
825
|
+
This uses a pure Python implementation based on [1]_.
|
|
826
|
+
|
|
827
|
+
If `Triangle` by Jonathan R. Shewchuk [2]_ and the Python bindings `triangle` [3]_
|
|
828
|
+
are installed, this will be used instead. Users need to acknowledge and adhere to
|
|
829
|
+
the licensing terms of these packages.
|
|
830
|
+
|
|
831
|
+
In the VisPy `PolygonCollection Example` [4]_ a speedup of 97% using
|
|
832
|
+
`Triangle`/`triangle` can be achieved compared to the pure Python implementation.
|
|
833
|
+
|
|
834
|
+
Parameters
|
|
835
|
+
----------
|
|
836
|
+
vertices : array-like
|
|
837
|
+
The vertices.
|
|
838
|
+
|
|
839
|
+
Returns
|
|
840
|
+
-------
|
|
841
|
+
vertices : array-like
|
|
842
|
+
The vertices.
|
|
843
|
+
triangles : array-like
|
|
844
|
+
The triangles.
|
|
845
|
+
|
|
846
|
+
References
|
|
847
|
+
----------
|
|
848
|
+
.. [1] Domiter, V. and Žalik, B. Sweep‐line algorithm for constrained
|
|
849
|
+
Delaunay triangulation
|
|
850
|
+
.. [2] Shewchuk J.R. (1996) Triangle: Engineering a 2D quality mesh generator and
|
|
851
|
+
Delaunay triangulator. In: Lin M.C., Manocha D. (eds) Applied Computational
|
|
852
|
+
Geometry Towards Geometric Engineering. WACG 1996. Lecture Notes in Computer
|
|
853
|
+
Science, vol 1148. Springer, Berlin, Heidelberg.
|
|
854
|
+
https://doi.org/10.1007/BFb0014497
|
|
855
|
+
.. [3] https://rufat.be/triangle/
|
|
856
|
+
.. [4] https://github.com/vispy/vispy/blob/main/examples/collections/polygon_collection.py
|
|
857
|
+
"""
|
|
858
|
+
n = len(vertices)
|
|
859
|
+
vertices = np.asarray(vertices)
|
|
860
|
+
zmean = vertices[:, 2].mean()
|
|
861
|
+
vertices_2d = vertices[:, :2]
|
|
862
|
+
segments = np.repeat(np.arange(n + 1), 2)[1:-1]
|
|
863
|
+
segments[-2:] = n - 1, 0
|
|
864
|
+
|
|
865
|
+
try:
|
|
866
|
+
import triangle # noqa: F401
|
|
867
|
+
except (ImportError, AssertionError):
|
|
868
|
+
vertices_2d, triangles = _triangulate_python(vertices_2d, segments)
|
|
869
|
+
else:
|
|
870
|
+
segments_2d = segments.reshape((-1, 2))
|
|
871
|
+
vertices_2d, triangles = _triangulate_cpp(vertices_2d, segments_2d)
|
|
872
|
+
|
|
873
|
+
vertices = np.empty((len(vertices_2d), 3))
|
|
874
|
+
vertices[:, :2] = vertices_2d
|
|
875
|
+
vertices[:, 2] = zmean
|
|
876
|
+
return vertices, triangles
|