diff --git a/docs/index.md b/docs/index.md index 97ec5304..e22c456a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -18,44 +18,46 @@ Jump over to the [**mathplot reference section**](/mathplot/ref) These are screenshots from the many [mathplot examples](https://github.com/sebsjames/mathplot/tree/main/examples) -| ![visual](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/visual.png?raw=true) [visual](https://github.com/sebsjames/mathplot/blob/main/examples/visual.cpp)| ![vvec_rgb_gaussians](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/vvec_rgb_gaussians.png?raw=true) [vvec_rgb_gaussians](https://github.com/sebsjames/mathplot/blob/main/examples/vvec_rgb_gaussians.cpp)| ![graph_logist2](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_logist2.png?raw=true) [graph_logist2](https://github.com/sebsjames/mathplot/blob/main/examples/graph_logist2.cpp)| -| ![rectangle](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rectangle.png?raw=true) [rectangle](https://github.com/sebsjames/mathplot/blob/main/examples/rectangle.cpp)| ![graph_bar](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_bar.png?raw=true) [graph_bar](https://github.com/sebsjames/mathplot/blob/main/examples/graph_bar.cpp)| ![voronoi_fixed_xz](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/voronoi_fixed_xz.png?raw=true) [voronoi_fixed_xz](https://github.com/sebsjames/mathplot/blob/main/examples/voronoi_fixed_xz.cpp)| -| ![graph_rightaxis](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_rightaxis.png?raw=true) [graph_rightaxis](https://github.com/sebsjames/mathplot/blob/main/examples/graph_rightaxis.cpp)| ![icosahedron](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/icosahedron.png?raw=true) [icosahedron](https://github.com/sebsjames/mathplot/blob/main/examples/icosahedron.cpp)| ![hexgrid_image_rect](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/hexgrid_image_rect.png?raw=true) [hexgrid_image_rect](https://github.com/sebsjames/mathplot/blob/main/examples/hexgrid_image_rect.cpp)| -| ![voronoi_function_flat](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/voronoi_function_flat.png?raw=true) [voronoi_function_flat](https://github.com/sebsjames/mathplot/blob/main/examples/voronoi_function_flat.cpp)| ![scatter_hex_mercator](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/scatter_hex_mercator.png?raw=true) [scatter_hex_mercator](https://github.com/sebsjames/mathplot/blob/main/examples/scatter_hex_mercator.cpp)| ![cray_eye](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/cray_eye.png?raw=true) [cray_eye](https://github.com/sebsjames/mathplot/blob/main/examples/cray_eye.cpp)| -| ![hexgrid_image_onsphere](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/hexgrid_image_onsphere.png?raw=true) [hexgrid_image_onsphere](https://github.com/sebsjames/mathplot/blob/main/examples/hexgrid_image_onsphere.cpp)| ![quiver](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/quiver.png?raw=true) [quiver](https://github.com/sebsjames/mathplot/blob/main/examples/quiver.cpp)| ![vvec_convolve](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/vvec_convolve.png?raw=true) [vvec_convolve](https://github.com/sebsjames/mathplot/blob/main/examples/vvec_convolve.cpp)| -| ![geodesic](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/geodesic.png?raw=true) [geodesic](https://github.com/sebsjames/mathplot/blob/main/examples/geodesic.cpp)| ![bootstrap](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/bootstrap.png?raw=true) [bootstrap](https://github.com/sebsjames/mathplot/blob/main/examples/bootstrap.cpp)| ![cyclic_colour](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/cyclic_colour.png?raw=true) [cyclic_colour](https://github.com/sebsjames/mathplot/blob/main/examples/cyclic_colour.cpp)| -| ![voronoi_fixed_nearlyz](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/voronoi_fixed_nearlyz.png?raw=true) [voronoi_fixed_nearlyz](https://github.com/sebsjames/mathplot/blob/main/examples/voronoi_fixed_nearlyz.cpp)| ![randvec](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/randvec.png?raw=true) [randvec](https://github.com/sebsjames/mathplot/blob/main/examples/randvec.cpp)| ![linregr](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/linregr.png?raw=true) [linregr](https://github.com/sebsjames/mathplot/blob/main/examples/linregr.cpp)| -| ![graph_logist](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_logist.png?raw=true) [graph_logist](https://github.com/sebsjames/mathplot/blob/main/examples/graph_logist.cpp)| ![lighting_test](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/lighting_test.png?raw=true) [lighting_test](https://github.com/sebsjames/mathplot/blob/main/examples/lighting_test.cpp)| ![colourbar](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourbar.png?raw=true) [colourbar](https://github.com/sebsjames/mathplot/blob/main/examples/colourbar.cpp)| -| ![draw_triangles](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/draw_triangles.png?raw=true) [draw_triangles](https://github.com/sebsjames/mathplot/blob/main/examples/draw_triangles.cpp)| ![showcase](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/showcase.png?raw=true) [showcase](https://github.com/sebsjames/mathplot/blob/main/examples/showcase.cpp)| ![graph_incoming_data_rescale](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_incoming_data_rescale.png?raw=true) [graph_incoming_data_rescale](https://github.com/sebsjames/mathplot/blob/main/examples/graph_incoming_data_rescale.cpp)| -| ![izhikevich](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/izhikevich.png?raw=true) [izhikevich](https://github.com/sebsjames/mathplot/blob/main/examples/izhikevich.cpp)| ![lines](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/lines.png?raw=true) [lines](https://github.com/sebsjames/mathplot/blob/main/examples/lines.cpp)| ![graph_fouraxes](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_fouraxes.png?raw=true) [graph_fouraxes](https://github.com/sebsjames/mathplot/blob/main/examples/graph_fouraxes.cpp)| -| ![helloworld](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/helloworld.png?raw=true) [helloworld](https://github.com/sebsjames/mathplot/blob/main/examples/helloworld.cpp)| ![graph_histo](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_histo.png?raw=true) [graph_histo](https://github.com/sebsjames/mathplot/blob/main/examples/graph_histo.cpp)| ![grid_border](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/grid_border.png?raw=true) [grid_border](https://github.com/sebsjames/mathplot/blob/main/examples/grid_border.cpp)| -| ![hsvwheel](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/hsvwheel.png?raw=true) [hsvwheel](https://github.com/sebsjames/mathplot/blob/main/examples/hsvwheel.cpp)| ![scatter_ico](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/scatter_ico.png?raw=true) [scatter_ico](https://github.com/sebsjames/mathplot/blob/main/examples/scatter_ico.cpp)| ![cubetrans2](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/cubetrans2.png?raw=true) [cubetrans2](https://github.com/sebsjames/mathplot/blob/main/examples/cubetrans2.cpp)| -| ![vectorvis](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/vectorvis.png?raw=true) [vectorvis](https://github.com/sebsjames/mathplot/blob/main/examples/vectorvis.cpp)| ![colourmaps_cet](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmaps_cet.png?raw=true) [colourmaps_cet](https://github.com/sebsjames/mathplot/blob/main/examples/colourmaps_cet.cpp)| ![voronoi_random](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/voronoi_random.png?raw=true) [voronoi_random](https://github.com/sebsjames/mathplot/blob/main/examples/voronoi_random.cpp)| -| ![logisticmap](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/logisticmap.png?raw=true) [logisticmap](https://github.com/sebsjames/mathplot/blob/main/examples/logisticmap.cpp)| ![polar](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/polar.png?raw=true) [polar](https://github.com/sebsjames/mathplot/blob/main/examples/polar.cpp)| ![graph_incoming_data](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_incoming_data.png?raw=true) [graph_incoming_data](https://github.com/sebsjames/mathplot/blob/main/examples/graph_incoming_data.cpp)| -| ![scatter_instanced](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/scatter_instanced.png?raw=true) [scatter_instanced](https://github.com/sebsjames/mathplot/blob/main/examples/scatter_instanced.cpp)| ![graph_line_xcross](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_line_xcross.png?raw=true) [graph_line_xcross](https://github.com/sebsjames/mathplot/blob/main/examples/graph_line_xcross.cpp)| ![graph_dynamic_x2](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_dynamic_x2.png?raw=true) [graph_dynamic_x2](https://github.com/sebsjames/mathplot/blob/main/examples/graph_dynamic_x2.cpp)| -| ![duochrome](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/duochrome.png?raw=true) [duochrome](https://github.com/sebsjames/mathplot/blob/main/examples/duochrome.cpp)| ![fps](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/fps.png?raw=true) [fps](https://github.com/sebsjames/mathplot/blob/main/examples/fps.cpp)| ![anneal_asa](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/anneal_asa.png?raw=true) [anneal_asa](https://github.com/sebsjames/mathplot/blob/main/examples/anneal_asa.cpp)| -| ![curvytelly_pipe](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/curvytelly_pipe.png?raw=true) [curvytelly_pipe](https://github.com/sebsjames/mathplot/blob/main/examples/curvytelly_pipe.cpp)| ![grid_simple](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/grid_simple.png?raw=true) [grid_simple](https://github.com/sebsjames/mathplot/blob/main/examples/grid_simple.cpp)| ![grating](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/grating.png?raw=true) [grating](https://github.com/sebsjames/mathplot/blob/main/examples/grating.cpp)| -| ![curvytelly](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/curvytelly.png?raw=true) [curvytelly](https://github.com/sebsjames/mathplot/blob/main/examples/curvytelly.cpp)| ![coordarrows](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/coordarrows.png?raw=true) [coordarrows](https://github.com/sebsjames/mathplot/blob/main/examples/coordarrows.cpp)| ![rhombo_scene](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rhombo_scene.png?raw=true) [rhombo_scene](https://github.com/sebsjames/mathplot/blob/main/examples/rhombo_scene.cpp)| -| ![zernike_radial](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/zernike_radial.png?raw=true) [zernike_radial](https://github.com/sebsjames/mathplot/blob/main/examples/zernike_radial.cpp)| ![line](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/line.png?raw=true) [line](https://github.com/sebsjames/mathplot/blob/main/examples/line.cpp)| ![colourmap_test](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmap_test.png?raw=true) [colourmap_test](https://github.com/sebsjames/mathplot/blob/main/examples/colourmap_test.cpp)| -| ![colourmaps_crameri](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmaps_crameri.png?raw=true) [colourmaps_crameri](https://github.com/sebsjames/mathplot/blob/main/examples/colourmaps_crameri.cpp)| ![cartgrid](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/cartgrid.png?raw=true) [cartgrid](https://github.com/sebsjames/mathplot/blob/main/examples/cartgrid.cpp)| ![graph_coords](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_coords.png?raw=true) [graph_coords](https://github.com/sebsjames/mathplot/blob/main/examples/graph_coords.cpp)| -| ![hexgrid_image](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/hexgrid_image.png?raw=true) [hexgrid_image](https://github.com/sebsjames/mathplot/blob/main/examples/hexgrid_image.cpp)| ![cube](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/cube.png?raw=true) [cube](https://github.com/sebsjames/mathplot/blob/main/examples/cube.cpp)| ![rhombo](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rhombo.png?raw=true) [rhombo](https://github.com/sebsjames/mathplot/blob/main/examples/rhombo.cpp)| -| ![colourmaps_other](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmaps_other.png?raw=true) [colourmaps_other](https://github.com/sebsjames/mathplot/blob/main/examples/colourmaps_other.cpp)| ![vvec_gauss](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/vvec_gauss.png?raw=true) [vvec_gauss](https://github.com/sebsjames/mathplot/blob/main/examples/vvec_gauss.cpp)| ![rosenbrock_asa](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rosenbrock_asa.png?raw=true) [rosenbrock_asa](https://github.com/sebsjames/mathplot/blob/main/examples/rosenbrock_asa.cpp)| -| ![model_crawler](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/model_crawler.png?raw=true) [model_crawler](https://github.com/sebsjames/mathplot/blob/main/examples/model_crawler.cpp)| ![graph_twinax](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_twinax.png?raw=true) [graph_twinax](https://github.com/sebsjames/mathplot/blob/main/examples/graph_twinax.cpp)| ![colourmaps_desaturating](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmaps_desaturating.png?raw=true) [colourmaps_desaturating](https://github.com/sebsjames/mathplot/blob/main/examples/colourmaps_desaturating.cpp)| -| ![grid_flat_dynamic](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/grid_flat_dynamic.png?raw=true) [grid_flat_dynamic](https://github.com/sebsjames/mathplot/blob/main/examples/grid_flat_dynamic.cpp)| ![helloversion](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/helloversion.png?raw=true) [helloversion](https://github.com/sebsjames/mathplot/blob/main/examples/helloversion.cpp)| ![rod](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rod.png?raw=true) [rod](https://github.com/sebsjames/mathplot/blob/main/examples/rod.cpp)| -| ![rhombo_scene2](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rhombo_scene2.png?raw=true) [rhombo_scene2](https://github.com/sebsjames/mathplot/blob/main/examples/rhombo_scene2.cpp)| ![rotating_models](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rotating_models.png?raw=true) [rotating_models](https://github.com/sebsjames/mathplot/blob/main/examples/rotating_models.cpp)| ![zernike](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/zernike.png?raw=true) [zernike](https://github.com/sebsjames/mathplot/blob/main/examples/zernike.cpp)| -| ![colourmap_browser](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmap_browser.png?raw=true) [colourmap_browser](https://github.com/sebsjames/mathplot/blob/main/examples/colourmap_browser.cpp)| ![scatter_geodesic](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/scatter_geodesic.png?raw=true) [scatter_geodesic](https://github.com/sebsjames/mathplot/blob/main/examples/scatter_geodesic.cpp)| ![izhikevich_alt](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/izhikevich_alt.png?raw=true) [izhikevich_alt](https://github.com/sebsjames/mathplot/blob/main/examples/izhikevich_alt.cpp)| -| ![voronoi_vectordata](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/voronoi_vectordata.png?raw=true) [voronoi_vectordata](https://github.com/sebsjames/mathplot/blob/main/examples/voronoi_vectordata.cpp)| ![tri](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/tri.png?raw=true) [tri](https://github.com/sebsjames/mathplot/blob/main/examples/tri.cpp)| ![pointrows](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/pointrows.png?raw=true) [pointrows](https://github.com/sebsjames/mathplot/blob/main/examples/pointrows.cpp)| -| ![quads](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/quads.png?raw=true) [quads](https://github.com/sebsjames/mathplot/blob/main/examples/quads.cpp)| ![voronoi_rectangular](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/voronoi_rectangular.png?raw=true) [voronoi_rectangular](https://github.com/sebsjames/mathplot/blob/main/examples/voronoi_rectangular.cpp)| ![grid_border2](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/grid_border2.png?raw=true) [grid_border2](https://github.com/sebsjames/mathplot/blob/main/examples/grid_border2.cpp)| -| ![graph1](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph1.png?raw=true) [graph1](https://github.com/sebsjames/mathplot/blob/main/examples/graph1.cpp)| ![vonmises](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/vonmises.png?raw=true) [vonmises](https://github.com/sebsjames/mathplot/blob/main/examples/vonmises.cpp)| ![breadcrumbs](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/breadcrumbs.png?raw=true) [breadcrumbs](https://github.com/sebsjames/mathplot/blob/main/examples/breadcrumbs.cpp)| -| ![grid_image](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/grid_image.png?raw=true) [grid_image](https://github.com/sebsjames/mathplot/blob/main/examples/grid_image.cpp)| ![sphere](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/sphere.png?raw=true) [sphere](https://github.com/sebsjames/mathplot/blob/main/examples/sphere.cpp)| ![convolve_rect](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/convolve_rect.png?raw=true) [convolve_rect](https://github.com/sebsjames/mathplot/blob/main/examples/convolve_rect.cpp)| -| ![hexgrid](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/hexgrid.png?raw=true) [hexgrid](https://github.com/sebsjames/mathplot/blob/main/examples/hexgrid.cpp)| ![twowindows](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/twowindows.png?raw=true) [twowindows](https://github.com/sebsjames/mathplot/blob/main/examples/twowindows.cpp)| ![txt](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/txt.png?raw=true) [txt](https://github.com/sebsjames/mathplot/blob/main/examples/txt.cpp)| -| ![ellipsoid](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/ellipsoid.png?raw=true) [ellipsoid](https://github.com/sebsjames/mathplot/blob/main/examples/ellipsoid.cpp)| ![vvec_smoothgauss](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/vvec_smoothgauss.png?raw=true) [vvec_smoothgauss](https://github.com/sebsjames/mathplot/blob/main/examples/vvec_smoothgauss.cpp)| ![grid_simple_rand](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/grid_simple_rand.png?raw=true) [grid_simple_rand](https://github.com/sebsjames/mathplot/blob/main/examples/grid_simple_rand.cpp)| -| ![scatter](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/scatter.png?raw=true) [scatter](https://github.com/sebsjames/mathplot/blob/main/examples/scatter.cpp)| ![ring](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/ring.png?raw=true) [ring](https://github.com/sebsjames/mathplot/blob/main/examples/ring.cpp)| ![sph_to_cart](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/sph_to_cart.png?raw=true) [sph_to_cart](https://github.com/sebsjames/mathplot/blob/main/examples/sph_to_cart.cpp)| -| ![rod_pan](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rod_pan.png?raw=true) [rod_pan](https://github.com/sebsjames/mathplot/blob/main/examples/rod_pan.cpp)| ![colourbar_interactive](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourbar_interactive.png?raw=true) [colourbar_interactive](https://github.com/sebsjames/mathplot/blob/main/examples/colourbar_interactive.cpp)| ![curvytelly_chequered_pipe](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/curvytelly_chequered_pipe.png?raw=true) [curvytelly_chequered_pipe](https://github.com/sebsjames/mathplot/blob/main/examples/curvytelly_chequered_pipe.cpp)| -| ![graph_distributions](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_distributions.png?raw=true) [graph_distributions](https://github.com/sebsjames/mathplot/blob/main/examples/graph_distributions.cpp)| ![rosenbrock](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rosenbrock.png?raw=true) [rosenbrock](https://github.com/sebsjames/mathplot/blob/main/examples/rosenbrock.cpp)| ![cubetrans](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/cubetrans.png?raw=true) [cubetrans](https://github.com/sebsjames/mathplot/blob/main/examples/cubetrans.cpp)| -| ![geodesic_with_normals](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/geodesic_with_normals.png?raw=true) [geodesic_with_normals](https://github.com/sebsjames/mathplot/blob/main/examples/geodesic_with_normals.cpp)| ![voronoi_function](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/voronoi_function.png?raw=true) [voronoi_function](https://github.com/sebsjames/mathplot/blob/main/examples/voronoi_function.cpp)| ![geodesic_ce](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/geodesic_ce.png?raw=true) [geodesic_ce](https://github.com/sebsjames/mathplot/blob/main/examples/geodesic_ce.cpp)| -| ![healpix](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/healpix.png?raw=true) [healpix](https://github.com/sebsjames/mathplot/blob/main/examples/healpix.cpp)| ![convolve](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/convolve.png?raw=true) [convolve](https://github.com/sebsjames/mathplot/blob/main/examples/convolve.cpp)| ![graph_dynamic_sine_rescale](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_dynamic_sine_rescale.png?raw=true) [graph_dynamic_sine_rescale](https://github.com/sebsjames/mathplot/blob/main/examples/graph_dynamic_sine_rescale.cpp)| -| ![rod_with_normals](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rod_with_normals.png?raw=true) [rod_with_normals](https://github.com/sebsjames/mathplot/blob/main/examples/rod_with_normals.cpp)| ![colourmaps_hsv1d](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmaps_hsv1d.png?raw=true) [colourmaps_hsv1d](https://github.com/sebsjames/mathplot/blob/main/examples/colourmaps_hsv1d.cpp)| ![colourmaps_lenthe](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmaps_lenthe.png?raw=true) [colourmaps_lenthe](https://github.com/sebsjames/mathplot/blob/main/examples/colourmaps_lenthe.cpp)| -| ![scatter_dynamic](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/scatter_dynamic.png?raw=true) [scatter_dynamic](https://github.com/sebsjames/mathplot/blob/main/examples/scatter_dynamic.cpp)| ![unicode_coordaxes](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/unicode_coordaxes.png?raw=true) [unicode_coordaxes](https://github.com/sebsjames/mathplot/blob/main/examples/unicode_coordaxes.cpp)| ![graph_line](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_line.png?raw=true) [graph_line](https://github.com/sebsjames/mathplot/blob/main/examples/graph_line.cpp)| -| ![colourmaps_mono](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmaps_mono.png?raw=true) [colourmaps_mono](https://github.com/sebsjames/mathplot/blob/main/examples/colourmaps_mono.cpp)| ![voronoi_fixed](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/voronoi_fixed.png?raw=true) [voronoi_fixed](https://github.com/sebsjames/mathplot/blob/main/examples/voronoi_fixed.cpp)| ![graph_change_xaxis](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_change_xaxis.png?raw=true) [graph_change_xaxis](https://github.com/sebsjames/mathplot/blob/main/examples/graph_change_xaxis.cpp)| -| ![grid_resampled_image](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/grid_resampled_image.png?raw=true) [grid_resampled_image](https://github.com/sebsjames/mathplot/blob/main/examples/grid_resampled_image.cpp)| ![cone](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/cone.png?raw=true) [cone](https://github.com/sebsjames/mathplot/blob/main/examples/cone.cpp)| ![colourmaps_matplotlib](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmaps_matplotlib.png?raw=true) [colourmaps_matplotlib](https://github.com/sebsjames/mathplot/blob/main/examples/colourmaps_matplotlib.cpp)| +| ![icosahedron](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/icosahedron.png?raw=true) [icosahedron](https://github.com/sebsjames/mathplot/blob/main/examples/icosahedron.cpp)| ![convolve_rect](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/convolve_rect.png?raw=true) [convolve_rect](https://github.com/sebsjames/mathplot/blob/main/examples/convolve_rect.cpp)| ![graph_twinax](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_twinax.png?raw=true) [graph_twinax](https://github.com/sebsjames/mathplot/blob/main/examples/graph_twinax.cpp)| +| ![izhikevich](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/izhikevich.png?raw=true) [izhikevich](https://github.com/sebsjames/mathplot/blob/main/examples/izhikevich.cpp)| ![bootstrap](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/bootstrap.png?raw=true) [bootstrap](https://github.com/sebsjames/mathplot/blob/main/examples/bootstrap.cpp)| ![curvytelly](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/curvytelly.png?raw=true) [curvytelly](https://github.com/sebsjames/mathplot/blob/main/examples/curvytelly.cpp)| +| ![hexgrid_image_onsphere](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/hexgrid_image_onsphere.png?raw=true) [hexgrid_image_onsphere](https://github.com/sebsjames/mathplot/blob/main/examples/hexgrid_image_onsphere.cpp)| ![grid_border](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/grid_border.png?raw=true) [grid_border](https://github.com/sebsjames/mathplot/blob/main/examples/grid_border.cpp)| ![voronoi_random](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/voronoi_random.png?raw=true) [voronoi_random](https://github.com/sebsjames/mathplot/blob/main/examples/voronoi_random.cpp)| +| ![grid_resampled_image](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/grid_resampled_image.png?raw=true) [grid_resampled_image](https://github.com/sebsjames/mathplot/blob/main/examples/grid_resampled_image.cpp)| ![colourmaps_other](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmaps_other.png?raw=true) [colourmaps_other](https://github.com/sebsjames/mathplot/blob/main/examples/colourmaps_other.cpp)| ![rectangle](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rectangle.png?raw=true) [rectangle](https://github.com/sebsjames/mathplot/blob/main/examples/rectangle.cpp)| +| ![scatter_geodesic](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/scatter_geodesic.png?raw=true) [scatter_geodesic](https://github.com/sebsjames/mathplot/blob/main/examples/scatter_geodesic.cpp)| ![scatter](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/scatter.png?raw=true) [scatter](https://github.com/sebsjames/mathplot/blob/main/examples/scatter.cpp)| ![voronoi_fixed_nearlyz](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/voronoi_fixed_nearlyz.png?raw=true) [voronoi_fixed_nearlyz](https://github.com/sebsjames/mathplot/blob/main/examples/voronoi_fixed_nearlyz.cpp)| +| ![logisticmap](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/logisticmap.png?raw=true) [logisticmap](https://github.com/sebsjames/mathplot/blob/main/examples/logisticmap.cpp)| ![graph_incoming_data](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_incoming_data.png?raw=true) [graph_incoming_data](https://github.com/sebsjames/mathplot/blob/main/examples/graph_incoming_data.cpp)| ![geodesic_ce](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/geodesic_ce.png?raw=true) [geodesic_ce](https://github.com/sebsjames/mathplot/blob/main/examples/geodesic_ce.cpp)| +| ![hexgrid_image_rect](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/hexgrid_image_rect.png?raw=true) [hexgrid_image_rect](https://github.com/sebsjames/mathplot/blob/main/examples/hexgrid_image_rect.cpp)| ![helloworld](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/helloworld.png?raw=true) [helloworld](https://github.com/sebsjames/mathplot/blob/main/examples/helloworld.cpp)| ![graph_bar](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_bar.png?raw=true) [graph_bar](https://github.com/sebsjames/mathplot/blob/main/examples/graph_bar.cpp)| +| ![colourmaps_matplotlib](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmaps_matplotlib.png?raw=true) [colourmaps_matplotlib](https://github.com/sebsjames/mathplot/blob/main/examples/colourmaps_matplotlib.cpp)| ![grating](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/grating.png?raw=true) [grating](https://github.com/sebsjames/mathplot/blob/main/examples/grating.cpp)| ![graph1](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph1.png?raw=true) [graph1](https://github.com/sebsjames/mathplot/blob/main/examples/graph1.cpp)| +| ![voronoi_rectangular](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/voronoi_rectangular.png?raw=true) [voronoi_rectangular](https://github.com/sebsjames/mathplot/blob/main/examples/voronoi_rectangular.cpp)| ![grid_simple_rand](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/grid_simple_rand.png?raw=true) [grid_simple_rand](https://github.com/sebsjames/mathplot/blob/main/examples/grid_simple_rand.cpp)| ![quiver](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/quiver.png?raw=true) [quiver](https://github.com/sebsjames/mathplot/blob/main/examples/quiver.cpp)| +| ![scatter_ico](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/scatter_ico.png?raw=true) [scatter_ico](https://github.com/sebsjames/mathplot/blob/main/examples/scatter_ico.cpp)| ![healpix](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/healpix.png?raw=true) [healpix](https://github.com/sebsjames/mathplot/blob/main/examples/healpix.cpp)| ![colourmaps_mono](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmaps_mono.png?raw=true) [colourmaps_mono](https://github.com/sebsjames/mathplot/blob/main/examples/colourmaps_mono.cpp)| +| ![izhikevich_alt](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/izhikevich_alt.png?raw=true) [izhikevich_alt](https://github.com/sebsjames/mathplot/blob/main/examples/izhikevich_alt.cpp)| ![duochrome](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/duochrome.png?raw=true) [duochrome](https://github.com/sebsjames/mathplot/blob/main/examples/duochrome.cpp)| ![rhombo_scene2](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rhombo_scene2.png?raw=true) [rhombo_scene2](https://github.com/sebsjames/mathplot/blob/main/examples/rhombo_scene2.cpp)| +| ![rotating_models](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rotating_models.png?raw=true) [rotating_models](https://github.com/sebsjames/mathplot/blob/main/examples/rotating_models.cpp)| ![graph_histo](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_histo.png?raw=true) [graph_histo](https://github.com/sebsjames/mathplot/blob/main/examples/graph_histo.cpp)| ![helloversion](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/helloversion.png?raw=true) [helloversion](https://github.com/sebsjames/mathplot/blob/main/examples/helloversion.cpp)| +| ![graph_rightaxis](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_rightaxis.png?raw=true) [graph_rightaxis](https://github.com/sebsjames/mathplot/blob/main/examples/graph_rightaxis.cpp)| ![rod](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rod.png?raw=true) [rod](https://github.com/sebsjames/mathplot/blob/main/examples/rod.cpp)| ![draw_triangles_intersections](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/draw_triangles_intersections.png?raw=true) [draw_triangles_intersections](https://github.com/sebsjames/mathplot/blob/main/examples/draw_triangles_intersections.cpp)| +| ![cubetrans](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/cubetrans.png?raw=true) [cubetrans](https://github.com/sebsjames/mathplot/blob/main/examples/cubetrans.cpp)| ![scatter_dynamic](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/scatter_dynamic.png?raw=true) [scatter_dynamic](https://github.com/sebsjames/mathplot/blob/main/examples/scatter_dynamic.cpp)| ![sph_to_cart](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/sph_to_cart.png?raw=true) [sph_to_cart](https://github.com/sebsjames/mathplot/blob/main/examples/sph_to_cart.cpp)| +| ![rod_pan](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rod_pan.png?raw=true) [rod_pan](https://github.com/sebsjames/mathplot/blob/main/examples/rod_pan.cpp)| ![colourmaps_desaturating](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmaps_desaturating.png?raw=true) [colourmaps_desaturating](https://github.com/sebsjames/mathplot/blob/main/examples/colourmaps_desaturating.cpp)| ![cubetrans2](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/cubetrans2.png?raw=true) [cubetrans2](https://github.com/sebsjames/mathplot/blob/main/examples/cubetrans2.cpp)| +| ![graph_dynamic_x2](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_dynamic_x2.png?raw=true) [graph_dynamic_x2](https://github.com/sebsjames/mathplot/blob/main/examples/graph_dynamic_x2.cpp)| ![scatter_instanced](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/scatter_instanced.png?raw=true) [scatter_instanced](https://github.com/sebsjames/mathplot/blob/main/examples/scatter_instanced.cpp)| ![rhombo_scene](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rhombo_scene.png?raw=true) [rhombo_scene](https://github.com/sebsjames/mathplot/blob/main/examples/rhombo_scene.cpp)| +| ![tri](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/tri.png?raw=true) [tri](https://github.com/sebsjames/mathplot/blob/main/examples/tri.cpp)| ![graph_fouraxes](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_fouraxes.png?raw=true) [graph_fouraxes](https://github.com/sebsjames/mathplot/blob/main/examples/graph_fouraxes.cpp)| ![cone](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/cone.png?raw=true) [cone](https://github.com/sebsjames/mathplot/blob/main/examples/cone.cpp)| +| ![colourmaps_cet](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmaps_cet.png?raw=true) [colourmaps_cet](https://github.com/sebsjames/mathplot/blob/main/examples/colourmaps_cet.cpp)| ![grid_image](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/grid_image.png?raw=true) [grid_image](https://github.com/sebsjames/mathplot/blob/main/examples/grid_image.cpp)| ![curvytelly_chequered_pipe](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/curvytelly_chequered_pipe.png?raw=true) [curvytelly_chequered_pipe](https://github.com/sebsjames/mathplot/blob/main/examples/curvytelly_chequered_pipe.cpp)| +| ![anneal_asa](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/anneal_asa.png?raw=true) [anneal_asa](https://github.com/sebsjames/mathplot/blob/main/examples/anneal_asa.cpp)| ![graph_incoming_data_rescale](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_incoming_data_rescale.png?raw=true) [graph_incoming_data_rescale](https://github.com/sebsjames/mathplot/blob/main/examples/graph_incoming_data_rescale.cpp)| ![geodesic](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/geodesic.png?raw=true) [geodesic](https://github.com/sebsjames/mathplot/blob/main/examples/geodesic.cpp)| +| ![colourmap_browser](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmap_browser.png?raw=true) [colourmap_browser](https://github.com/sebsjames/mathplot/blob/main/examples/colourmap_browser.cpp)| ![scatter_hex_mercator](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/scatter_hex_mercator.png?raw=true) [scatter_hex_mercator](https://github.com/sebsjames/mathplot/blob/main/examples/scatter_hex_mercator.cpp)| ![unicode_coordaxes](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/unicode_coordaxes.png?raw=true) [unicode_coordaxes](https://github.com/sebsjames/mathplot/blob/main/examples/unicode_coordaxes.cpp)| +| ![vectorvis](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/vectorvis.png?raw=true) [vectorvis](https://github.com/sebsjames/mathplot/blob/main/examples/vectorvis.cpp)| ![colourmaps_hsv1d](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmaps_hsv1d.png?raw=true) [colourmaps_hsv1d](https://github.com/sebsjames/mathplot/blob/main/examples/colourmaps_hsv1d.cpp)| ![colourmap_test](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmap_test.png?raw=true) [colourmap_test](https://github.com/sebsjames/mathplot/blob/main/examples/colourmap_test.cpp)| +| ![grid_simple](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/grid_simple.png?raw=true) [grid_simple](https://github.com/sebsjames/mathplot/blob/main/examples/grid_simple.cpp)| ![cube](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/cube.png?raw=true) [cube](https://github.com/sebsjames/mathplot/blob/main/examples/cube.cpp)| ![vonmises](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/vonmises.png?raw=true) [vonmises](https://github.com/sebsjames/mathplot/blob/main/examples/vonmises.cpp)| +| ![graph_dynamic_sine_rescale](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_dynamic_sine_rescale.png?raw=true) [graph_dynamic_sine_rescale](https://github.com/sebsjames/mathplot/blob/main/examples/graph_dynamic_sine_rescale.cpp)| ![colourmaps_crameri](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmaps_crameri.png?raw=true) [colourmaps_crameri](https://github.com/sebsjames/mathplot/blob/main/examples/colourmaps_crameri.cpp)| ![vvec_gauss](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/vvec_gauss.png?raw=true) [vvec_gauss](https://github.com/sebsjames/mathplot/blob/main/examples/vvec_gauss.cpp)| +| ![txt](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/txt.png?raw=true) [txt](https://github.com/sebsjames/mathplot/blob/main/examples/txt.cpp)| ![ring](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/ring.png?raw=true) [ring](https://github.com/sebsjames/mathplot/blob/main/examples/ring.cpp)| ![lines](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/lines.png?raw=true) [lines](https://github.com/sebsjames/mathplot/blob/main/examples/lines.cpp)| +| ![vvec_convolve](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/vvec_convolve.png?raw=true) [vvec_convolve](https://github.com/sebsjames/mathplot/blob/main/examples/vvec_convolve.cpp)| ![hexgrid](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/hexgrid.png?raw=true) [hexgrid](https://github.com/sebsjames/mathplot/blob/main/examples/hexgrid.cpp)| ![ellipse_pca](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/ellipse_pca.png?raw=true) [ellipse_pca](https://github.com/sebsjames/mathplot/blob/main/examples/ellipse_pca.cpp)| +| ![graph_logist2](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_logist2.png?raw=true) [graph_logist2](https://github.com/sebsjames/mathplot/blob/main/examples/graph_logist2.cpp)| ![cyclic_colour](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/cyclic_colour.png?raw=true) [cyclic_colour](https://github.com/sebsjames/mathplot/blob/main/examples/cyclic_colour.cpp)| ![graph_line](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_line.png?raw=true) [graph_line](https://github.com/sebsjames/mathplot/blob/main/examples/graph_line.cpp)| +| ![voronoi_vectordata](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/voronoi_vectordata.png?raw=true) [voronoi_vectordata](https://github.com/sebsjames/mathplot/blob/main/examples/voronoi_vectordata.cpp)| ![rod_with_normals](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rod_with_normals.png?raw=true) [rod_with_normals](https://github.com/sebsjames/mathplot/blob/main/examples/rod_with_normals.cpp)| ![colourmaps_lenthe](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourmaps_lenthe.png?raw=true) [colourmaps_lenthe](https://github.com/sebsjames/mathplot/blob/main/examples/colourmaps_lenthe.cpp)| +| ![curvytelly_pipe](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/curvytelly_pipe.png?raw=true) [curvytelly_pipe](https://github.com/sebsjames/mathplot/blob/main/examples/curvytelly_pipe.cpp)| ![visual](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/visual.png?raw=true) [visual](https://github.com/sebsjames/mathplot/blob/main/examples/visual.cpp)| ![graph_coords](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_coords.png?raw=true) [graph_coords](https://github.com/sebsjames/mathplot/blob/main/examples/graph_coords.cpp)| +| ![graph_line_xcross](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_line_xcross.png?raw=true) [graph_line_xcross](https://github.com/sebsjames/mathplot/blob/main/examples/graph_line_xcross.cpp)| ![hsvwheel](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/hsvwheel.png?raw=true) [hsvwheel](https://github.com/sebsjames/mathplot/blob/main/examples/hsvwheel.cpp)| ![colourbar_interactive](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourbar_interactive.png?raw=true) [colourbar_interactive](https://github.com/sebsjames/mathplot/blob/main/examples/colourbar_interactive.cpp)| +| ![hexgrid_image](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/hexgrid_image.png?raw=true) [hexgrid_image](https://github.com/sebsjames/mathplot/blob/main/examples/hexgrid_image.cpp)| ![grid_border2](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/grid_border2.png?raw=true) [grid_border2](https://github.com/sebsjames/mathplot/blob/main/examples/grid_border2.cpp)| ![zernike_radial](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/zernike_radial.png?raw=true) [zernike_radial](https://github.com/sebsjames/mathplot/blob/main/examples/zernike_radial.cpp)| +| ![voronoi_function_flat](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/voronoi_function_flat.png?raw=true) [voronoi_function_flat](https://github.com/sebsjames/mathplot/blob/main/examples/voronoi_function_flat.cpp)| ![showcase](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/showcase.png?raw=true) [showcase](https://github.com/sebsjames/mathplot/blob/main/examples/showcase.cpp)| ![zernike](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/zernike.png?raw=true) [zernike](https://github.com/sebsjames/mathplot/blob/main/examples/zernike.cpp)| +| ![graph_change_xaxis](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_change_xaxis.png?raw=true) [graph_change_xaxis](https://github.com/sebsjames/mathplot/blob/main/examples/graph_change_xaxis.cpp)| ![graph_distributions](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_distributions.png?raw=true) [graph_distributions](https://github.com/sebsjames/mathplot/blob/main/examples/graph_distributions.cpp)| ![geodesic_with_normals](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/geodesic_with_normals.png?raw=true) [geodesic_with_normals](https://github.com/sebsjames/mathplot/blob/main/examples/geodesic_with_normals.cpp)| +| ![rosenbrock](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rosenbrock.png?raw=true) [rosenbrock](https://github.com/sebsjames/mathplot/blob/main/examples/rosenbrock.cpp)| ![breadcrumbs](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/breadcrumbs.png?raw=true) [breadcrumbs](https://github.com/sebsjames/mathplot/blob/main/examples/breadcrumbs.cpp)| ![quads](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/quads.png?raw=true) [quads](https://github.com/sebsjames/mathplot/blob/main/examples/quads.cpp)| +| ![cray_eye](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/cray_eye.png?raw=true) [cray_eye](https://github.com/sebsjames/mathplot/blob/main/examples/cray_eye.cpp)| ![graph_logist](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/graph_logist.png?raw=true) [graph_logist](https://github.com/sebsjames/mathplot/blob/main/examples/graph_logist.cpp)| ![cartgrid](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/cartgrid.png?raw=true) [cartgrid](https://github.com/sebsjames/mathplot/blob/main/examples/cartgrid.cpp)| +| ![twowindows](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/twowindows.png?raw=true) [twowindows](https://github.com/sebsjames/mathplot/blob/main/examples/twowindows.cpp)| ![voronoi_fixed_xz](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/voronoi_fixed_xz.png?raw=true) [voronoi_fixed_xz](https://github.com/sebsjames/mathplot/blob/main/examples/voronoi_fixed_xz.cpp)| ![vvec_smoothgauss](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/vvec_smoothgauss.png?raw=true) [vvec_smoothgauss](https://github.com/sebsjames/mathplot/blob/main/examples/vvec_smoothgauss.cpp)| +| ![trace_boundary](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/trace_boundary.png?raw=true) [trace_boundary](https://github.com/sebsjames/mathplot/blob/main/examples/trace_boundary.cpp)| ![polar](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/polar.png?raw=true) [polar](https://github.com/sebsjames/mathplot/blob/main/examples/polar.cpp)| ![lighting_test](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/lighting_test.png?raw=true) [lighting_test](https://github.com/sebsjames/mathplot/blob/main/examples/lighting_test.cpp)| +| ![linregr](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/linregr.png?raw=true) [linregr](https://github.com/sebsjames/mathplot/blob/main/examples/linregr.cpp)| ![voronoi_fixed](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/voronoi_fixed.png?raw=true) [voronoi_fixed](https://github.com/sebsjames/mathplot/blob/main/examples/voronoi_fixed.cpp)| ![rosenbrock_asa](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rosenbrock_asa.png?raw=true) [rosenbrock_asa](https://github.com/sebsjames/mathplot/blob/main/examples/rosenbrock_asa.cpp)| +| ![coordarrows](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/coordarrows.png?raw=true) [coordarrows](https://github.com/sebsjames/mathplot/blob/main/examples/coordarrows.cpp)| ![voronoi_function](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/voronoi_function.png?raw=true) [voronoi_function](https://github.com/sebsjames/mathplot/blob/main/examples/voronoi_function.cpp)| ![ellipsoid](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/ellipsoid.png?raw=true) [ellipsoid](https://github.com/sebsjames/mathplot/blob/main/examples/ellipsoid.cpp)| +| ![colourbar](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/colourbar.png?raw=true) [colourbar](https://github.com/sebsjames/mathplot/blob/main/examples/colourbar.cpp)| ![vvec_rgb_gaussians](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/vvec_rgb_gaussians.png?raw=true) [vvec_rgb_gaussians](https://github.com/sebsjames/mathplot/blob/main/examples/vvec_rgb_gaussians.cpp)| ![line](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/line.png?raw=true) [line](https://github.com/sebsjames/mathplot/blob/main/examples/line.cpp)| +| ![randvec](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/randvec.png?raw=true) [randvec](https://github.com/sebsjames/mathplot/blob/main/examples/randvec.cpp)| ![convolve](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/convolve.png?raw=true) [convolve](https://github.com/sebsjames/mathplot/blob/main/examples/convolve.cpp)| ![fps](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/fps.png?raw=true) [fps](https://github.com/sebsjames/mathplot/blob/main/examples/fps.cpp)| +| ![voronoi_boundary](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/voronoi_boundary.png?raw=true) [voronoi_boundary](https://github.com/sebsjames/mathplot/blob/main/examples/voronoi_boundary.cpp)| ![sphere](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/sphere.png?raw=true) [sphere](https://github.com/sebsjames/mathplot/blob/main/examples/sphere.cpp)| ![rhombo](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/rhombo.png?raw=true) [rhombo](https://github.com/sebsjames/mathplot/blob/main/examples/rhombo.cpp)| +| ![grid_flat_dynamic](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/grid_flat_dynamic.png?raw=true) [grid_flat_dynamic](https://github.com/sebsjames/mathplot/blob/main/examples/grid_flat_dynamic.cpp)| ![pointrows](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/pointrows.png?raw=true) [pointrows](https://github.com/sebsjames/mathplot/blob/main/examples/pointrows.cpp)| ![model_crawler](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/model_crawler.png?raw=true) [model_crawler](https://github.com/sebsjames/mathplot/blob/main/examples/model_crawler.cpp)| +| ![draw_triangles](https://github.com/sebsjames/mathplot/blob/main/examples/screenshots/draw_triangles.png?raw=true) [draw_triangles](https://github.com/sebsjames/mathplot/blob/main/examples/draw_triangles.cpp)| diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 2e147b34..8168f544 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -411,6 +411,9 @@ target_link_libraries(triangle_intersect OpenGL::GL glfw Freetype::Freetype) add_executable(voronoi_random voronoi_random.cpp) target_link_libraries(voronoi_random OpenGL::GL glfw Freetype::Freetype) +add_executable(voronoi_boundary voronoi_boundary.cpp) +target_link_libraries(voronoi_boundary OpenGL::GL glfw Freetype::Freetype) + add_executable(voronoi_rectangular voronoi_rectangular.cpp) target_link_libraries(voronoi_rectangular OpenGL::GL glfw Freetype::Freetype) @@ -558,3 +561,12 @@ target_link_libraries(show_boundingboxes OpenGL::GL glfw Freetype::Freetype) # if have compound-ray header add_executable(cray_eye cray_eye.cpp) target_link_libraries(cray_eye OpenGL::GL glfw Freetype::Freetype) + +if(ARMADILLO_FOUND) + # Make use of principle component analysis from arma in this example + add_executable(ellipse_pca ellipse_pca.cpp) + target_link_libraries(ellipse_pca ${ARMADILLO_LIBRARY} ${ARMADILLO_LIBRARIES} OpenGL::GL glfw Freetype::Freetype) +endif() + +add_executable(trace_boundary trace_boundary.cpp) +target_link_libraries(trace_boundary OpenGL::GL glfw Freetype::Freetype) diff --git a/examples/ellipse_pca.cpp b/examples/ellipse_pca.cpp new file mode 100644 index 00000000..ba87d025 --- /dev/null +++ b/examples/ellipse_pca.cpp @@ -0,0 +1,81 @@ +/* + * Applying principle component analysis (using arma) to some random data + */ +#include + +#include +#include +#include + +#include + +#include +#include + +int main() +{ + constexpr unsigned int n_samp = 100; + + // Create data: Draw some samples from 2D Gaussian and rotate + sm::rand_normal rn1 (0.0f, 2.0f); + sm::rand_normal rn2 (0.0f, 0.5f); + sm::vvec> _x (n_samp, {0}); + sm::mat22 rotn; + rotn.rotate (sm::mathconst::pi_over_8); + for (unsigned int i = 0; i < n_samp; ++i) { + _x[i] = rotn * sm::vec{ rn1.get(), rn2.get() }; + } + + // Graph the data + mplot::Visual v(1024, 768, "Principle component analysis with armadillo"); + // Create a GraphVisual object (obtaining a unique_ptr to the object) with a spatial offset within the scene of 0,0,0 + auto gv = std::make_unique> (sm::vec{-0.5f,-0.5f,0.0f}); + mplot::DatasetStyle ds (mplot::stylepolicy::markers); + ds.datalabel = std::string("data"); + v.bindmodel (gv); + gv->setlimits (-8, 8, -8, 8); + gv->setdata (_x, ds); + + std::cout << "\narma gives:\n"; + // Place data in arma::Mat + arma::Mat x(_x.size(), 2); + for (unsigned int i = 0; i < _x.size(); ++i) { + x(i, 0) = _x[i][0]; + x(i, 1) = _x[i][1]; + } + + // Apply arma::princomp + arma::Mat co, sc; + arma::Col lat, tsq; + arma::princomp (co, sc, lat, tsq, x); + + std::cout << "coeff: " << co << std::endl; + //std::cout << "scores: " << sc << std::endl; + std::cout << "latent: " << lat << std::endl; + + // Mat access is (r, c) + sm::vec pc1vec = { co(0, 0), co(1, 0) }; + float angle1 = pc1vec.angle(); + std::cout << "Angle of first component " << pc1vec << " is " << angle1 * sm::mathconst::rad2deg + << " and length is " << lat(0) << std::endl; + sm::vec pc2vec = { co(0, 1), co(1, 1) }; + float angle2 = pc2vec.angle(); + std::cout << "Angle of 2nd component " << pc2vec << " is " << angle2 * sm::mathconst::rad2deg + << " and length is " << lat(1) << std::endl; + + mplot::DatasetStyle ds2 (mplot::stylepolicy::lines); + // Show 3 sigma axes + ds2.setcolour (mplot::colour::crimson); + ds2.datalabel = std::string("PC1 3") + mplot::unicode::toUtf8 (mplot::unicode::sigma); + sm::vvec> pc1vv = { {0,0}, pc1vec * 3.0f * std::sqrt(lat(0))}; + gv->setdata (pc1vv, ds2); + + ds2.setcolour (mplot::colour::orange); + ds2.datalabel = std::string("PC2 3") + mplot::unicode::toUtf8 (mplot::unicode::sigma); + sm::vvec> pc2vv = { {0,0}, pc2vec * 3.0f * std::sqrt(lat(1)) }; + gv->setdata (pc2vv, ds2); + + gv->finalize(); + v.addVisualModel (gv); + v.keepOpen(); +} diff --git a/examples/screenshots/draw_triangles_intersections.png b/examples/screenshots/draw_triangles_intersections.png new file mode 100644 index 00000000..d5dff019 Binary files /dev/null and b/examples/screenshots/draw_triangles_intersections.png differ diff --git a/examples/screenshots/ellipse_pca.png b/examples/screenshots/ellipse_pca.png new file mode 100644 index 00000000..85229471 Binary files /dev/null and b/examples/screenshots/ellipse_pca.png differ diff --git a/examples/screenshots/trace_boundary.png b/examples/screenshots/trace_boundary.png new file mode 100644 index 00000000..bd761e6f Binary files /dev/null and b/examples/screenshots/trace_boundary.png differ diff --git a/examples/screenshots/voronoi_boundary.png b/examples/screenshots/voronoi_boundary.png new file mode 100644 index 00000000..b5265250 Binary files /dev/null and b/examples/screenshots/voronoi_boundary.png differ diff --git a/examples/trace_boundary.cpp b/examples/trace_boundary.cpp new file mode 100644 index 00000000..6177c7f8 --- /dev/null +++ b/examples/trace_boundary.cpp @@ -0,0 +1,47 @@ +/* + * Trace around a boundary of points in 2D. This code uses Graham's scan to compute the convex hull + * of a set of randomly generated points. + */ +#include + +#include +#include +#include +#include +#include + +#include +#include + +int main() +{ + // Get ready to graph the data + mplot::Visual v(1024, 768, "Graham's scan computes the convex hull"); + mplot::DatasetStyle ds (mplot::stylepolicy::markers); + auto gv = std::make_unique> (sm::vec{-0.5f,-0.5f,0.0f}); + v.bindmodel (gv); + + constexpr unsigned int n_points = 200; + + // Create data + sm::rand_uniform rngxy(-0.8f, 0.8f); + sm::vvec> points(n_points); + for (unsigned int i = 0; i < n_points; ++i) { points[i] = { rngxy.get(), rngxy.get() }; } + + // Trace algorithm + sm::vvec> boundary = sm::geometry::graham_scan (points); + // Close the boundary + boundary.push_back (boundary.front()); + + gv->setlimits (-1, 1, -1, 1); + + gv->setdata (points, ds); + + mplot::DatasetStyle ds2 (mplot::stylepolicy::lines); + ds2.linecolour = mplot::colour::crimson; + gv->setdata (boundary, ds2); + + gv->finalize(); + v.addVisualModel (gv); + v.keepOpen(); +} diff --git a/examples/voronoi_boundary.cpp b/examples/voronoi_boundary.cpp new file mode 100644 index 00000000..259001c8 --- /dev/null +++ b/examples/voronoi_boundary.cpp @@ -0,0 +1,75 @@ +/* + * This example generates a number (n_points) of random (but bounded) coordinates and + * uses the VoronoiVisual to display the coordinates as a map, with the order of + * random-choice being used to colourize the Voronoi cells. + * + * Test harness for drawing an arbitrary boundary around the Voronoi region + * + * Author Seb James + * Date 2026 + */ +#include +#include +#include +#include +#include + +int main (int argc, char** argv) +{ + int rtn = -1; + + float border_width = 0.2f; + unsigned int n_points = 80; + int domshape = 0; // 0 for rectangular, 1 for circ, 2 for ellipse, 3 for traced + + if (argc > 1) { domshape = std::stoi (argv[1]); } + if (argc > 2) { border_width = std::stof (argv[2]); } + if (argc > 3) { n_points = std::stoi (argv[3]); } + + mplot::Visual v(1024, 768, "VoronoiVisual"); + + sm::rand_uniform rngxy(-0.8f, 0.8f, n_points); + sm::rand_uniform rngz(0.8f, 1.0f, n_points); + + // make n_points random coordinates + std::vector> points(n_points); + std::vector data(n_points); + + for (unsigned int i = 0; i < n_points; ++i) { + points[i] = { rngxy.get(), rngxy.get(), rngz.get() }; + data[i] = static_cast(i) / n_points; + } + + mplot::ColourMapType cmap_t = mplot::ColourMapType::Plasma; + + sm::vec offset = { 0.0f }; + auto vorv = std::make_unique> (offset); + v.bindmodel (vorv); + vorv->show_voronoi2d = true; // true to show the 2D voronoi edges + vorv->debug_dataCoords = true; // true to show coordinate spheres + + // traced, circular, ellipsoid, rectangular: + if (domshape == 1) { + vorv->dom_shape = mplot::VoronoiVisual::domain_shape::circular; + } else if (domshape == 2) { + vorv->dom_shape = mplot::VoronoiVisual::domain_shape::ellipsoid; + } else if (domshape == 3) { + vorv->dom_shape = mplot::VoronoiVisual::domain_shape::traced; + } else { + vorv->dom_shape = mplot::VoronoiVisual::domain_shape::rectangular; + } + // A border to add to our domain boundary + vorv->border_width = border_width; + // Some domain shapes need to know a number of points: + vorv->num_boundary_points = 12; + + vorv->cm.setType (cmap_t); + vorv->setDataCoords (&points); + vorv->setScalarData (&data); + vorv->finalize(); + v.addVisualModel (vorv); + + v.keepOpen(); + + return rtn; +} diff --git a/examples/voronoi_function.cpp b/examples/voronoi_function.cpp index dbc2fed4..6ff9f7b8 100644 --- a/examples/voronoi_function.cpp +++ b/examples/voronoi_function.cpp @@ -44,7 +44,10 @@ int main() vorv->show_voronoi2d = false; // true to show the 2D voronoi edges vorv->debug_dataCoords = false; // true to show coordinate spheres float length_scale = 4.0f / std::sqrt (n_points); - vorv->border_width = length_scale; + vorv->border_width = length_scale; + //vorv->dom_shape = mplot::VoronoiVisual::domain_shape::traced; + //vorv->dom_shape = mplot::VoronoiVisual::domain_shape::circular; + //vorv->dom_shape = mplot::VoronoiVisual::domain_shape::rectangular; // default vorv->cm.setType (cmap_t); vorv->setDataCoords (&points); vorv->setScalarData (&data); diff --git a/examples/voronoi_function_flat.cpp b/examples/voronoi_function_flat.cpp index 6ae4e4ca..a65d9714 100644 --- a/examples/voronoi_function_flat.cpp +++ b/examples/voronoi_function_flat.cpp @@ -43,7 +43,10 @@ int main() vorv->show_voronoi2d = true; // true to show the 2D voronoi edges vorv->debug_dataCoords = false; // true to show coordinate spheres float length_scale = 4.0f / std::sqrt (n_points); - vorv->border_width = length_scale; + vorv->border_width = length_scale; + //vorv->dom_shape = mplot::VoronoiVisual::domain_shape::traced; + //vorv->dom_shape = mplot::VoronoiVisual::domain_shape::circular; + //vorv->dom_shape = mplot::VoronoiVisual::domain_shape::rectangular; // default vorv->cm.setType (cmap_t); vorv->setDataCoords (&points); vorv->setScalarData (&data); @@ -61,7 +64,7 @@ int main() if (fcount++% 600 == 0) { vorvp->cm.setType (++cmap_t); } - vorvp->reinitColours(); // Not quite working when I change the colourmap + vorvp->reinitColours(); v.waitevents(0.018); v.render(); k += 0.01f; diff --git a/maths b/maths index db69cc49..0acb70ea 160000 --- a/maths +++ b/maths @@ -1 +1 @@ -Subproject commit db69cc490c54c267c99f1220819b485566e03f14 +Subproject commit 0acb70ea154f031751f9921bc4581e65242f4261 diff --git a/mplot/VisualDataModel.h b/mplot/VisualDataModel.h index 6bc2c025..72ceadd0 100644 --- a/mplot/VisualDataModel.h +++ b/mplot/VisualDataModel.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -141,6 +142,8 @@ namespace mplot this->reinit(); } + sm::vec coordsCentroid() const { return sm::algo::centroid (*this->dataCoords); } + //! An overridable function to set the colour of rect ri std::array setColour (uint64_t ri) { diff --git a/mplot/VoronoiVisual.h b/mplot/VoronoiVisual.h index 556ed1b2..72965e28 100644 --- a/mplot/VoronoiVisual.h +++ b/mplot/VoronoiVisual.h @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include @@ -45,6 +47,18 @@ namespace mplot }; public: + + // The shape of the domain to draw around the points in the Voronoi diagram Each kind of + // boundary shape is auto-fit to the data points, although the user can set an additional + // border_width. + enum class domain_shape : uint32_t + { + rectangular, + ellipsoid, + circular, + traced // trace around the points - arbitrary polygon + }; + VoronoiVisual (const sm::vec _offset) { this->viewmatrix.translate (_offset); @@ -89,20 +103,99 @@ namespace mplot this->dcoords_ptr = this->dataCoords; } - // Use mplot::range to find the extents of dataCoords. From these create a - // rectangle to pass to diagram_generate. - sm::range rx, ry; - rx.search_init(); - ry.search_init(); - for (unsigned int i = 0; i < ncoords; ++i) { - rx.update ((*this->dcoords_ptr)[i][0]); - ry.update ((*this->dcoords_ptr)[i][1]); - } - // Generate the 2D Voronoi diagram jcv::manager vorman; + if (this->border_width == -1) { this->border_width = 4.0f / std::sqrt (ncoords); } vorman.border_width = this->border_width; - vorman.diagram_generate (*(dcoords_ptr)); + + if (this->dom_shape == domain_shape::ellipsoid || this->dom_shape == domain_shape::circular) { + + sm::vec cent = this->coordsCentroid(); + sm::vec cent2 = {cent[0], cent[1]}; + sm::vvec lengths (this->dcoords_ptr->size(), 0.0f); + for (unsigned int i = 0; i < this->dcoords_ptr->size(); ++i) { + sm::vec c = (*this->dcoords_ptr)[i].less_one_dim() - cent2; + lengths[i] = c.length(); + } + float max_len = lengths.max(); + // Points MUST be in anti-clockwise order!!!!! + this->boundary.clear(); + this->boundary.resize (this->num_boundary_points, cent2.plus_one_dim()); + + if (this->dom_shape == domain_shape::ellipsoid) { + throw std::runtime_error ("Elliptic domain shapes not supported"); + /* + * Here is an approach using arma::princomp, but I don't want the arma + * dependency, as elliptic boundaries are not as useful to me as traced + * boundaries. This code awaits sm::pca::compute(). + */ +#if 0 + // Find ellipse parameters for the data. First place data in an arma::mat, offsetting by the centroid + arma::Mat x (dcoords_ptr->size(), 2); + for (unsigned int i = 0; i < dcoords_ptr->size(); ++i) { + x(i, 0) = (*this->dcoords_ptr)[i][0] - cent2[0]; + x(i, 1) = (*this->dcoords_ptr)[i][1] - cent2[1]; + } + // From PCA determine ellipsoid axes. Angle of coefficient vector and length of + // eigen values gives the ellipse parameters + arma::Mat co, sc; + arma::Col lat, tsq; + arma::princomp (co, sc, lat, tsq, x); + // Mat access is (r, c) + sm::vec pc1vec = { co(0, 0), co(1, 0) }; + sm::mat22 el_rotn; + el_rotn.rotate (pc1vec.angle()); + float a = this->n_sigma * std::sqrt (lat(0)); + float b = this->n_sigma * std::sqrt (lat(1)); + // Create the elliptic boundary + for (unsigned int i = 0; i < this->num_boundary_points; ++i) { + float phi = i * sm::mathconst::two_pi / this->num_boundary_points; + sm::vec bp = el_rotn * sm::vec{ a * std::cos (phi), b * std::sin (phi) } + cent2; + this->boundary[i] = bp.plus_one_dim(); + } +#endif + } else { // circular boundary + float l = max_len + this->border_width; + for (unsigned int i = 0; i < this->num_boundary_points; ++i) { + float phi = i * sm::mathconst::two_pi / this->num_boundary_points; + this->boundary[i] += { l * std::cos (phi), l * std::sin (phi) }; + } + } + } else if (this->dom_shape == domain_shape::traced) { + // Copy 3D points to 2D + sm::vvec> coords2 (dcoords_ptr->size()); + for (unsigned int i = 0; i < dcoords_ptr->size(); ++i) { + coords2[i] = (*dcoords_ptr)[i].less_one_dim(); + } + auto bnd2centr = sm::algo::centroid (coords2); + // Find convex hull + sm::vvec> bnd2 = sm::geometry::graham_scan (coords2); + this->boundary.resize (bnd2.size()); + // Copy 2D to 3D boundary + for (unsigned int i = 0; i < bnd2.size(); ++i) { + this->boundary[i] = bnd2[i].plus_one_dim(); + // Add border + sm::vec brd = bnd2[i] - bnd2centr; // border vector from centroid to point + brd.renormalize(); + brd *= this->border_width; + this->boundary[i] += brd.plus_one_dim(); + } + } // else rectangular default does not populate this->boundary + + if (this->boundary.size() > 0) { + vorman.diagram_generate (*(dcoords_ptr), this->boundary); + } else { + // default rectangular box boundary + vorman.diagram_generate (*(dcoords_ptr)); + } + + if (static_cast(vorman.diagram_numsites()) == 0) { + if (this->boundary.empty()) { + throw std::runtime_error ("numsites == 0."); + } else { + throw std::runtime_error ("numsites == 0. Make sure your boundary points appear in anti-clockwise order"); + } + } // We obtain access to the Voronoi cell sites: const jcv::site* sites = vorman.diagram_get_sites(); @@ -258,7 +351,8 @@ namespace mplot } } if (static_cast(vorman.diagram_numsites()) != ncoords) { - std::cout << "WARNING: numsites != ncoords ?!?!\n"; + std::cout << "WARNING: numsites (" << vorman.diagram_numsites() + << ") != ncoords (" << ncoords << ")?!?!\n"; } // Draw optional objects @@ -321,6 +415,9 @@ namespace mplot this->computeTube ({ e->pos[0].x() * this->zoom, e->pos[0].y() * this->zoom, 0.0f }, { e->pos[1].x() * this->zoom, e->pos[1].y() * this->zoom, 0.0f }, mplot::colour::black, mplot::colour::black, this->voronoi_grid_thickness, 6); + //this->addLabel (e->pos[0].str(), + // e->pos[0].less_one_dim().plus_one_dim() * this->zoom + labelOffset, + // mplot::TextFeatures(labelSize) ); e = e->next; } } @@ -332,6 +429,12 @@ namespace mplot for (unsigned int i = 0; i < ncoords; ++i) { this->computeSphere ((*this->dataCoords)[i] * this->zoom, mplot::colour::black, this->dataCoord_sphere_size); } + // Polygonal boundary (if used) + for (auto b : this->boundary) { + this->computeSphere (b * this->zoom, mplot::colour::dodgerblue2, 3.0f * this->dataCoord_sphere_size); + // Show text of boundary vertex position like this + // this->addLabel (b.str(), b * this->zoom + labelOffset, mplot::TextFeatures(labelSize) ); + } } } @@ -426,8 +529,6 @@ namespace mplot bool debug_dataCoords = false; //! The size of the black spheres are dataCoord locations float dataCoord_sphere_size = 0.008f; - - //! What direction should be considered 'z' when converting the data into a voronoi diagram? //! The data values will be rotated before the Voronoi pass, then rotated back. sm::vec data_z_direction = sm::vec<>::uz(); @@ -435,7 +536,13 @@ namespace mplot //! You can add a little extra to the rectangle that is auto-detected from the //! datacoordinate ranges. This defaults to epsilon to give the best possible //! surface with a rectangular grid. - float border_width = std::numeric_limits::epsilon(); + float border_width = -1; + // Create a rectangular domain or use a smoother (circular) boundary? + domain_shape dom_shape = domain_shape::rectangular; + // When making the boundary, how many points? Or use some f (dcoords.size())? + unsigned int num_boundary_points = 30; + // When finding ellipsoid shape from PCA, how many sigmas to multiply the principle axes length by? + float n_sigma = 3.0f; // Do we add index labels? bool labelIndices = false; @@ -473,10 +580,11 @@ namespace mplot //! Record the data index for each Voronoi cell index sm::vvec site_indices; unsigned int triangle_count_sum = 0; - + // Polygon domain coordinates + std::vector> boundary; //! Internally owned version of dataCoords after rotation std::vector> dcoords; - //! A pointer either to dcoords or this->dataCoords + //! A pointer to dcoords const std::vector>* dcoords_ptr; }; diff --git a/mplot/compoundray/EyeVisual.h b/mplot/compoundray/EyeVisual.h index 8893a42e..63f29889 100644 --- a/mplot/compoundray/EyeVisual.h +++ b/mplot/compoundray/EyeVisual.h @@ -10,9 +10,9 @@ #include #include #include +#include #include #include - #include namespace mplot::compoundray @@ -435,7 +435,29 @@ namespace mplot::compoundray jcv::manager vorman; // we need double precision for projections, float may run into trouble vorman.border_width = this->border_width; - vorman.diagram_generate (this->omm2d); + + std::vector> boundary; + + // Copy 3D points to 2D + sm::vvec> coords2 (omm2d.size()); + for (unsigned int i = 0; i < omm2d.size(); ++i) { + coords2[i] = omm2d[i].less_one_dim(); + } + auto bnd2centr = sm::algo::centroid (coords2); + // Find convex hull + sm::vvec> bnd2 = sm::geometry::graham_scan (coords2); + boundary.resize (bnd2.size()); + // Copy 2D to 3D boundary + for (unsigned int i = 0; i < bnd2.size(); ++i) { + boundary[i] = bnd2[i].plus_one_dim(); + // Add border + sm::vec brd = bnd2[i] - bnd2centr; // border vector from centroid to point + brd.renormalize(); + brd *= this->border_width; + boundary[i] += brd.plus_one_dim(); + } + + vorman.diagram_generate (this->omm2d, boundary); int diag_nsites = vorman.diagram_numsites(); if (diag_nsites != ncoords) { diff --git a/mplot/jcvoronoi/CMakeLists.txt b/mplot/jcvoronoi/CMakeLists.txt index cd5f3abf..91e79358 100644 --- a/mplot/jcvoronoi/CMakeLists.txt +++ b/mplot/jcvoronoi/CMakeLists.txt @@ -1 +1 @@ -install(FILES jc_voronoi_clip.h jc_voronoi.h DESTINATION ${CMAKE_INSTALL_PREFIX}/include/mplot/jcvoronoi) +install(FILES jc_voronoi.h DESTINATION ${CMAKE_INSTALL_PREFIX}/include/mplot/jcvoronoi) diff --git a/mplot/jcvoronoi/jc_voronoi.h b/mplot/jcvoronoi/jc_voronoi.h index 8a6e27a6..ba317437 100644 --- a/mplot/jcvoronoi/jc_voronoi.h +++ b/mplot/jcvoronoi/jc_voronoi.h @@ -22,6 +22,8 @@ #include #include #include +#include +#include #ifndef JCV_EDGE_INTERSECT_THRESHOLD // Fix for Issue #40 @@ -111,7 +113,7 @@ namespace jcv point min; // The bounding rect min point max; // The bounding rect max - void* ctx; // User defined context + void* ctx; // User defined context function }; // Second batch of structs @@ -183,6 +185,13 @@ namespace jcv point max; }; + // Used for boundary clipping + template + struct clipping_polygon + { + std::vector> points; + }; + #pragma pack(pop) // The mananger class. Type T is what is called real in the original code @@ -224,12 +233,6 @@ namespace jcv return (pt1->y() == pt2->y()) ? (pt1->x() < pt2->x()) : pt1->y() < pt2->y(); } - [[maybe_unused]] - static int point_on_box_edge (const point* pt, const point* min, const point* max) - { - return pt->x() == min->x() || pt->y() == min->y() || pt->x() == max->x() || pt->y() == max->y(); - } - // edges and corners static const int EDGE_LEFT = 1; static const int EDGE_RIGHT = 2; @@ -885,7 +888,7 @@ namespace jcv // https://cp-algorithms.com/geometry/oriented-triangle-area.html static T determinant (const point* a, const point* b, const point* c) { - return (b->x() - a->x())*(c->y() - a->y()) - (b->y() - a->y())*(c->x() - a->x()); + return (b->x() - a->x()) * (c->y() - a->y()) - (b->y() - a->y())*(c->x() - a->x()); } static T calc_sort_metric (const site* _site, const graphedge* _edge) @@ -930,7 +933,13 @@ namespace jcv static void finishline (context_internal* internal, edge* e) { - if (!edge_clipline (internal, e)) { return; } + int er = 0; + if (!(er = edge_clipline (internal, e))) { + return; + } else if (er == 2) { + // 2 means the edge 'was removed/not added' + return; + } // Make sure the graph edges are CCW int flip = determinant (&e->sites[0]->p, &e->pos[0], &e->pos[1]) > T{0} ? 0 : 1; @@ -990,36 +999,36 @@ namespace jcv return edge; } - static void boxshape_fillgaps (const clipper* clipper, context_internal* allocator, site* site) + static void boxshape_fill (const clipper* clipper, context_internal* allocator, site* site) { // They're sorted CCW, so if the current->pos[1] != next->pos[0], then we have a gap - graphedge* current = site->edges; - if (!current) { + graphedge* curr_graphedge = site->edges; + if (!curr_graphedge) { // No edges, then it should be a single cell assert (allocator->numsites == 1); graphedge* gap = alloc_graphedge (allocator); gap->neighbor = 0; gap->pos[0] = clipper->min; - gap->pos[1][0] = clipper->max[0]; - gap->pos[1][1] = clipper->min[1]; + gap->pos[1][0] = clipper->max[0]; + gap->pos[1][1] = clipper->min[1]; gap->angle = calc_sort_metric(site, gap); gap->next = 0; - gap->edge_ = create_gap_edge (allocator, site, gap); + gap->edge_ = create_gap_edge (allocator, site, gap); - current = gap; + curr_graphedge = gap; site->edges = gap; } - graphedge* next = current->next; + graphedge* next = curr_graphedge->next; if (!next) { graphedge* gap = alloc_graphedge (allocator); - create_corner_edge(allocator, site, current, gap); + create_corner_edge(allocator, site, curr_graphedge, gap); gap->edge_ = create_gap_edge (allocator, site, gap); - gap->next = current->next; - current->next = gap; - current = gap; + gap->next = curr_graphedge->next; + curr_graphedge->next = gap; + curr_graphedge = gap; next = site->edges; } @@ -1029,10 +1038,11 @@ namespace jcv constexpr int loopcount_thresh = 1024; int loopcount = 0; - while (current && next && loopcount < loopcount_thresh) { + while (curr_graphedge && next && loopcount < loopcount_thresh) { + + int current_edge_flags = get_edge_flags(&curr_graphedge->pos[1], &clipper->min, &clipper->max); - int current_edge_flags = get_edge_flags(¤t->pos[1], &clipper->min, &clipper->max); - if (current_edge_flags && !equal(¤t->pos[1], &next->pos[0])) { + if (current_edge_flags && !equal(&curr_graphedge->pos[1], &next->pos[0])) { // Cases: // Current and Next on the same border // Current on one border, and Next on another border @@ -1044,13 +1054,13 @@ namespace jcv // Current and Next on the same border graphedge* gap = alloc_graphedge (allocator); gap->neighbor = 0; - gap->pos[0] = current->pos[1]; + gap->pos[0] = curr_graphedge->pos[1]; gap->pos[1] = next->pos[0]; gap->angle = calc_sort_metric (site, gap); gap->edge_ = create_gap_edge (allocator, site, gap); - gap->next = current->next; - current->next = gap; + gap->next = curr_graphedge->next; + curr_graphedge->next = gap; } else { // Current and Next on different borders int corner_flag = edge_flags_to_corner (current_edge_flags); @@ -1070,19 +1080,19 @@ namespace jcv graphedge* gap = alloc_graphedge (allocator); gap->neighbor = 0; - gap->pos[0] = current->pos[1]; + gap->pos[0] = curr_graphedge->pos[1]; gap->pos[1] = corner; gap->angle = calc_sort_metric(site, gap); gap->edge_ = create_gap_edge (allocator, site, gap); - gap->next = current->next; - current->next = gap; + gap->next = curr_graphedge->next; + curr_graphedge->next = gap; } - } + } // else inside border - current = current->next; - if (current) { - next = current->next; + curr_graphedge = curr_graphedge->next; + if (curr_graphedge) { + next = curr_graphedge->next; if (!next) { next = site->edges; } } ++loopcount; @@ -1100,6 +1110,7 @@ namespace jcv if (!internal->clipper_.fill_fn) { return; } for (int i = 0; i < internal->numsites; ++i) { site* site = &internal->sites[i]; + // Call fill fn for each site internal->clipper_.fill_fn (&internal->clipper_, internal, site); } } @@ -1315,11 +1326,11 @@ namespace jcv qsort (sites, (size_t)num_points, sizeof(site), point_cmp); clipper box_clipper; - if (_clipper == 0) { + if (_clipper == nullptr) { // model->get_shaderprogs = &mplot::VisualBase::get_shaderprogs; box_clipper.test_fn = &jcv::manager::boxshape_test; box_clipper.clip_fn = &jcv::manager::boxshape_clip; - box_clipper.fill_fn = &jcv::manager::boxshape_fillgaps; + box_clipper.fill_fn = &jcv::manager::boxshape_fill; _clipper = &box_clipper; } internal->clipper_ = *_clipper; @@ -1396,7 +1407,12 @@ namespace jcv */ static void diagram_generate (int num_points, const point* points, const rect* rect, const clipper* clipper, diagram* d) { - diagram_generate_useralloc(num_points, points, rect, clipper, 0, alloc_fn, free_fn, d); + diagram_generate_useralloc (num_points, points, rect, clipper, 0, alloc_fn, free_fn, d); + } + + static void diagram_generate (int num_points, const point* points, const clipper* clipper, diagram* d) + { + diagram_generate_useralloc (num_points, points, 0, clipper, 0, alloc_fn, free_fn, d); } // User API @@ -1418,8 +1434,404 @@ namespace jcv jcv::manager::diagram_generate (ncoords, centres.data(), &this->domain, 0, &this->diagram); } + // User API to generate with a polygon boundary + void diagram_generate (const std::vector>& centres, std::vector>& polygon) + { + int ncoords = static_cast(centres.size()); + + std::memset (&this->diagram, 0, sizeof(jcv::diagram)); + + jcv::clipper polygonclipper; + polygonclipper.test_fn = &jcv::manager::polygon_test; + polygonclipper.clip_fn = &jcv::manager::polygon_clip; + polygonclipper.fill_fn = &jcv::manager::polygon_fill; + polygonclipper.ctx = &polygon; + + jcv::manager::diagram_generate (ncoords, centres.data(), &polygonclipper, &this->diagram); + } + int diagram_numsites() const { return this->diagram.numsites; } + static int polygon_test (const clipper* clipper, const point p) + { + auto polygon = reinterpret_cast>*>(clipper->ctx); + int num_points = polygon->size(); + + // convex polygon + // winding CCW + // all polygon normals point outward + // if the point is in front of the plane, it is outside + + int result = 1; + for (int i = 0; i < num_points; ++i) { + point p0 = (*polygon)[i]; + point p1 = (*polygon)[(i + 1) % num_points]; + point n = {}; + n[0] = p1.y() - p0.y(); + n[1] = p0.x() - p1.x(); + point diff = p - p0; + + if (n.dot (diff) > 0) { + result = 0; + break; + } + } + return result; + } + + static int ray_intersect_polygon (const clipper* clipper, point& p0, point& p1) + { + constexpr bool debug_ray_intersect = false; + auto polygon = reinterpret_cast>*>(clipper->ctx); + int num_points = polygon->size(); + + // First wind to find out if p0 or p1 is inside clipper's boundary + sm::winder w (*polygon); // winder ignores z + int w_p0 = w.wind (p0); + int w_p1 = w.wind (p1); + + if (w_p0 == 0 && w_p1 == 0) { + if constexpr (debug_ray_intersect) { std::cout << "Both points outside boundary\n"; } + return 2; // Both outside means remove this edge. + } else if (w_p0 != 0 && w_p1 != 0) { + if constexpr (debug_ray_intersect) { std::cout << "Both points INSIDE boundary, return 1\n"; } + return 1; // Both inside + } + + if constexpr (debug_ray_intersect) { + std::cout << "p0 is " << (w_p0 == 0 ? "outside" : "inside") << " and p1 is " + << (w_p1 == 0 ? "outside" : "inside") << std::endl; + } + for (int i = 0; i < num_points; ++i) { + sm::vec v0 = (*polygon)[i].less_one_dim(); + sm::vec v1 = (*polygon)[(i + 1) % num_points].less_one_dim(); + + if constexpr (debug_ray_intersect) { + std::cout << "Test segments intersect for v0/v1: " << v0 << ", " << v1 << ", p0/p1: " + << p0.less_one_dim() << ", " << p1.less_one_dim() << std::endl; + } + + // find crossing point of v0,v1 and p0,p1 + std::bitset<2> isect = sm::geometry::segments_intersect (v0, v1, p0.less_one_dim(), p1.less_one_dim()); + if constexpr (debug_ray_intersect) { + std::cout << "Intersect? " << (isect.test(0) ? "yes" : "no") + << " colinear? " << (isect.test(1) ? "yes" : "no") << std::endl; + } + if (isect.test(0) == true) { + if (isect.test(1) == true) { + // lines co-linear. This is always an error? + return 0; + } + // lines intersect. Find intersection point + sm::vec cp = sm::geometry::crossing_point (v0, v1, p0.less_one_dim(), p1.less_one_dim()); + + if (w_p0 != 0) { // p0 inside, p1 outside + p1[0] = cp[0]; + p1[1] = cp[1]; + } else if (w_p1 != 0) { + p0[0] = cp[0]; + p0[1] = cp[1]; + } else { + // Neither p0 nor p1 were inside the polygon + return 0; + } + + } else { + if (isect.test(1) == true) { + // lines co-linear. This is always an error? + return 0; + } // else no crossing point with this section. + } + } + + return 1; + } + + static int polygon_clip (const clipper* clipper, edge* e) + { + constexpr bool debug_polyclip = false; + + // Using the box clipper to get a finite line segment + int result = manager::boxshape_clip (clipper, e); + if (!result) { return 0; } + + // Return here for a sanity check on the polygon clipping + //return 1; + + point p0 = e->pos[0]; + point p1 = e->pos[1]; + + if constexpr (debug_polyclip) { + std::cout << "**Clip for: " << p0 << " to " << p1 << std::endl; + } + + result = ray_intersect_polygon (clipper, p0, p1); + + if (result == 2) { + // Both p0 and p1 were outside boundary. + return result; + } else if (result == 0) { + e->pos[0] = e->pos[1]; + return result; + } // else result should be 1, which is ok + + e->pos[0] = p0; + e->pos[1] = p1; + + if constexpr (debug_polyclip) { + std::cout << "Clipped to: " << p0 << " to " << p1 << std::endl; + } + + return 1; + } + + // Get the polygon vertex with index vtx_idx from the clipper + static point get_polygon_vertex (const clipper* clipper, int vtx_idx) + { + point p = {}; + auto polygon = reinterpret_cast>*>(clipper->ctx); + int num_points = polygon->size(); + if (vtx_idx < num_points) { p = (*polygon)[vtx_idx]; } + return p; + } + + // Find which edge (or vertex) of the polygon in the clipper a point _p lies on + // + // In field 0, return: 0: p NOT on polygon; 1: p on polygon edge section; 2: p on polygon vertex + // In field 1, return the index of the edge or vertex referred to in field 0. + static sm::vec find_polygon_edge (const clipper* clipper, const point& _p) + { + point p = _p; + sm::vec info = {}; + if (std::isnan(p[2])) { p[2] = T{0}; } + + auto polygon = reinterpret_cast>*>(clipper->ctx); + T min_dist = std::numeric_limits::max(); + int num_points = polygon->size(); + + for (int i = 0; i < num_points; ++i) { + + point p0 = (*polygon)[i]; + if (p == p0) { + // ON vertex p0 + info[0] = 2; + info[1] = i; + break; + } + + point p1 = (*polygon)[(i + 1) % num_points]; + if (p == p1) { + // ON vertex p1 + info[0] = 2; + info[1] = (i + 1) % num_points; + break; + } + + // Now is p on the edge? + point vsegment = p1 - p0; + point vpoint = p - p0; + T t = vsegment.dot (vpoint) / vsegment.dot (vsegment); + if (t < T{0} || t > T{1}) { continue; } + point projected = p0 + vsegment * t; // projection of vpoint onto vsegment + T distsq = (p - projected).length_sq(); + + if (distsq < min_dist) { + min_dist = distsq; + info[0] = 1; + info[1] = i; + } + } + + return info; + } + + static void polygon_fill (const clipper* clipper, + context_internal* allocator, site* site) + { + constexpr bool debug_polyfill = false; + + // They're sorted CCW, so if the current->pos[1] != next->pos[0], then we have a gap + auto polygon = reinterpret_cast>*>(clipper->ctx); + int num_points = polygon->size(); + + graphedge* curr_graphedge = site->edges; + if (!curr_graphedge) { + graphedge* gap = alloc_graphedge (allocator); + gap->neighbor = 0; + // Pick the first edge of the polygon (which is also CCW) + gap->pos[0] = (*polygon)[0]; + gap->pos[1] = (*polygon)[1]; + gap->angle = calc_sort_metric (site, gap); + gap->next = 0; + gap->edge_ = create_gap_edge (allocator, site, gap); + + curr_graphedge = gap; + site->edges = gap; + } + + graphedge* next = curr_graphedge->next; + if (!next) { + graphedge* gap = alloc_graphedge (allocator); + + sm::vec polygon_info = find_polygon_edge (clipper, curr_graphedge->pos[1]); + int polygon_edge = polygon_info[1]; + if (!(curr_graphedge->pos[1] == (*polygon)[(polygon_edge + 1) % num_points])) { + gap->pos[0] = curr_graphedge->pos[1]; + gap->pos[1] = (*polygon)[(polygon_edge + 1) % num_points]; + } else { + gap->pos[0] = (*polygon)[(polygon_edge + 1) % num_points]; + gap->pos[1] = (*polygon)[(polygon_edge + 2) % num_points]; + } + + gap->neighbor = 0; + gap->angle = calc_sort_metric (site, gap); + gap->next = 0; + gap->edge_ = create_gap_edge (allocator, site, gap); + + gap->next = curr_graphedge->next; + curr_graphedge->next = gap; + curr_graphedge = gap; + next = site->edges; + } + + constexpr int loopcount_thresh = 1024; + int loopcount = 0; + while (curr_graphedge && next) { + + // Which edge of the polygon, if any, are we on? current_edge[0] indicates whether + // the point is "not edge/vertex" (value 0); "on an edge" (value 1) or "is a vertex" + // (value 2). current_edge[1] indicates the index of the edge. + sm::vec current_edge = find_polygon_edge (clipper, curr_graphedge->pos[1]); + + if (current_edge[0] > 0 && !equal (&curr_graphedge->pos[1], &next->pos[0])) { + + sm::vec next_edge = find_polygon_edge (clipper, next->pos[0]); + + if (current_edge[0] == 1 && next_edge[0] == 1 && current_edge[1] == next_edge[1]) { + + // Case: Current and Next on the same border + if constexpr (debug_polyfill) { std::cout << "Current and next on same border\n"; } + + graphedge* gap = alloc_graphedge (allocator); + gap->neighbor = 0; + gap->pos[0] = curr_graphedge->pos[1]; + gap->pos[1] = next->pos[0]; + gap->angle = calc_sort_metric (site, gap); + gap->edge_ = create_gap_edge (allocator, site, gap); + + gap->next = curr_graphedge->next; + curr_graphedge->next = gap; + + } else if (current_edge[0] == 1 && next_edge[0] == 1 && next_edge[1] != current_edge[1]) { + + // Case: Current and Next on different borders, so we need to find the + // adjacent vertex, following the borders CCW + if constexpr (debug_polyfill) { std::cout << "Current and next on different borders\n"; } + + int next_vertex = (current_edge[1] + 1) % num_points; + point vtx = get_polygon_vertex (clipper, next_vertex); + if constexpr (debug_polyfill) { std::cout << "vtx = " << vtx << std::endl; } + + graphedge* gap = alloc_graphedge (allocator); + gap->neighbor = 0; + gap->pos[0] = curr_graphedge->pos[1]; + gap->pos[1] = vtx; + gap->angle = calc_sort_metric (site, gap); + gap->edge_ = create_gap_edge (allocator, site, gap); + + gap->next = curr_graphedge->next; + curr_graphedge->next = gap; + + } else if (current_edge[0] == 1 && next_edge[0] == 2) { + + // Case: Current on border, next on a vertex + if constexpr (debug_polyfill) { std::cout << "Current on border next on vertex\n"; } + point vtx = get_polygon_vertex (clipper, next_edge[1]); + + graphedge* gap = alloc_graphedge (allocator); + gap->neighbor = 0; + gap->pos[0] = curr_graphedge->pos[1]; + gap->pos[1] = vtx; + gap->angle = calc_sort_metric (site, gap); + gap->edge_ = create_gap_edge (allocator, site, gap); + + gap->next = curr_graphedge->next; + curr_graphedge->next = gap; + + } else if (current_edge[0] == 2 && next_edge[0] == 1 && next_edge[1] == current_edge[1]) { + + if constexpr (debug_polyfill) { std::cout << "Current on vertex next on same border\n"; } + // Case: Current on vertex, next on *same* border + graphedge* gap = alloc_graphedge (allocator); + gap->neighbor = 0; + gap->pos[0] = curr_graphedge->pos[1]; + gap->pos[1] = next->pos[0]; + gap->angle = calc_sort_metric (site, gap); + gap->edge_ = create_gap_edge (allocator, site, gap); + + gap->next = curr_graphedge->next; + curr_graphedge->next = gap; + + } else if (current_edge[0] == 2 && next_edge[0] == 1 && next_edge[1] != current_edge[1]) { + + if constexpr (debug_polyfill) { std::cout << "Current on vertex next on another border\n"; } + // Case: Current on vertex, next on another border, so we need to find the adjacent + // vertex, following the borders CCW + int next_vertex = (current_edge[1] + 1) % num_points; + point vtx = get_polygon_vertex (clipper, next_vertex); + + graphedge* gap = alloc_graphedge (allocator); + gap->neighbor = 0; + gap->pos[0] = curr_graphedge->pos[1]; + gap->pos[1] = vtx; + gap->angle = calc_sort_metric (site, gap); + gap->edge_ = create_gap_edge (allocator, site, gap); + + gap->next = curr_graphedge->next; + curr_graphedge->next = gap; + + } else if (next_edge[0] == 0) { + + // Case: Current on vertex or border, but next not on polygon boundary + if constexpr (debug_polyfill) { std::cout << "Current on vertex or border, but next not on polygon boundary\n"; } + graphedge* gap = alloc_graphedge (allocator); + gap->neighbor = 0; + gap->pos[0] = curr_graphedge->pos[1]; + gap->pos[1] = next->pos[0]; + gap->angle = calc_sort_metric (site, gap); + gap->edge_ = create_gap_edge (allocator, site, gap); + + gap->next = curr_graphedge->next; + curr_graphedge->next = gap; + + } else { + // next not on polygon? + std::cout << "Whoop whoop, unhandled case: current_edge = " + << current_edge << " and next_edge = " << next_edge << "\n"; + } + } // else current_edge->pos[1] is not on the polygonal boundary + + if constexpr (debug_polyfill) { + if ((curr_graphedge->pos[0] - curr_graphedge->pos[1]).length_sq() > 25) { + std::cout << "added a long edge from " << curr_graphedge->pos[0] << " to " << curr_graphedge->pos[1] << std::endl; + } + } + + curr_graphedge = curr_graphedge->next; + if (curr_graphedge) { + next = curr_graphedge->next; + if (!next) { next = site->edges; } + } + ++loopcount; + if (loopcount >= loopcount_thresh) { throw std::runtime_error ("Kaboom (too many loops)"); } + + } + } + + /** + * End of boundary clipping code + */ + // User-configurable border width T border_width = std::numeric_limits::epsilon(); @@ -1428,7 +1840,7 @@ namespace jcv jcv::diagram diagram; // A domain for the diagram. jcv::rect domain = {}; - }; // end struct jcv + }; // end struct jcv::manager } // namespace diff --git a/mplot/jcvoronoi/jc_voronoi_clip.h b/mplot/jcvoronoi/jc_voronoi_clip.h deleted file mode 100644 index 1a2a9fa4..00000000 --- a/mplot/jcvoronoi/jc_voronoi_clip.h +++ /dev/null @@ -1,379 +0,0 @@ -// Note: This will need to be brought in-line with changes in jc_voronoi.h made as part of Seb's -// mathplot work. - -// Copyright (c) 2019 Mathias Westerdahl -// For full LICENSE (MIT) or USAGE, see bottom of file - -#ifndef JC_VORONOI_CLIP_H -#define JC_VORONOI_CLIP_H - -#include "jc_voronoi.h" - -#pragma pack(push, 1) - -typedef struct jcv_clipping_polygon_ -{ - jcv_point* points; - int num_points; -} jcv_clipping_polygon; - -#pragma pack(pop) - - -// Convex polygon clip functions -int jcv_clip_polygon_test_point(const jcv_clipper* clipper, const jcv_point p); -int jcv_clip_polygon_clip_edge(const jcv_clipper* clipper, jcv_edge* e); -void jcv_clip_polygon_fill_gaps(const jcv_clipper* clipper, jcv_context_internal* allocator, jcv_site* site); - - -#endif // JC_VORONOI_CLIP_H - -#ifdef JC_VORONOI_CLIP_IMPLEMENTATION -#undef JC_VORONOI_CLIP_IMPLEMENTATION - -// These helpers will probably end up in the main library - -static inline jcv_real jcv_cross(const jcv_point a, const jcv_point b) { - return a.x * b.y - a.y * b.x; -} - -static inline jcv_point jcv_add(jcv_point a, jcv_point b) { - jcv_point r; - r.x = a.x + b.x; - r.y = a.y + b.y; - return r; -} - -static inline jcv_point jcv_sub(jcv_point a, jcv_point b) { - jcv_point r; - r.x = a.x - b.x; - r.y = a.y - b.y; - return r; -} - -static inline jcv_point jcv_mul(jcv_point v, jcv_real s) { - jcv_point r; - r.x = v.x * s; - r.y = v.y * s; - return r; -} - -static inline jcv_point jcv_mix(jcv_point a, jcv_point b, jcv_real t) { - jcv_point r; - r.x = a.x + (b.x - a.x) * t; - r.y = a.y + (b.y - a.y) * t; - return r; -} - -static inline jcv_real jcv_dot(jcv_point a, jcv_point b) { - return a.x * b.x + a.y * b.y; -} - - -static inline jcv_real jcv_length(jcv_point v) { - return JCV_SQRT(v.x*v.x + v.y*v.y); -} - -static inline jcv_real jcv_length_sq(jcv_point v) { - return v.x*v.x + v.y*v.y; -} - -static inline jcv_real jcv_fabs(jcv_real a) { - return a < 0 ? -a : a; -} - -// if it returns [0.0, 1.0] it's on the line segment -static inline jcv_real jcv_point_to_line_segment_t(jcv_point p, jcv_point p0, jcv_point p1) { - jcv_point vpoint = jcv_sub(p, p0); - jcv_point vsegment = jcv_sub(p1, p0); - return jcv_dot(vsegment, vpoint) / jcv_dot(vsegment, vsegment); -} - -int jcv_clip_polygon_test_point(const jcv_clipper* clipper, const jcv_point p) -{ - jcv_clipping_polygon* polygon = (jcv_clipping_polygon*)clipper->ctx; - int num_points = polygon->num_points; - - // convex polygon - // winding CCW - // all polygon normals point outward - // if the point is in front of the plane, it is outside - - int result = 1; - for (int i = 0; i < num_points; ++i) - { - jcv_point p0 = polygon->points[i]; - jcv_point p1 = polygon->points[(i+1)%num_points]; - jcv_point n; - n.x = p1.y - p0.y; - n.y = p0.x - p1.x; - jcv_point diff; - diff.x = p.x - p0.x; - diff.y = p.y - p0.y; - - if (jcv_dot(n, diff) > 0) { - result = 0; - break; - } - } - return result; -} - -static int jcv_ray_intersect_polygon(const jcv_clipper* clipper, jcv_point p0, jcv_point p1, jcv_real* out_t0, jcv_real* out_t1) -{ - jcv_clipping_polygon* polygon = (jcv_clipping_polygon*)clipper->ctx; - int num_points = polygon->num_points; - - jcv_real t0 = (jcv_real)0; - jcv_real t1 = (jcv_real)1; - jcv_point dir = jcv_sub(p1, p0); - - for (int i = 0; i < num_points; ++i) - { - jcv_point v0 = polygon->points[i]; - jcv_point v1 = polygon->points[(i+1)%num_points]; - jcv_point n; - n.x = v1.y - v0.y; - n.y = -(v1.x - v0.x); - - jcv_point v0p0 = jcv_sub(p0, v0); - - jcv_real N = -jcv_dot(v0p0, n); - jcv_real D = jcv_dot(dir, n); - if (jcv_fabs(D) < 0.0001f) // parallel to the line - { - if (N < 0) - return 0; - continue; - } - - jcv_real t = N / D; - if (D < 0) // -> entering - { - t0 = t > t0 ? t : t0; - if (t0 > t1) - return 0; - } - else // D > 0 -> exiting - { - t1 = t < t1 ? t : t1; - if (t1 < t0) - return 0; - } - } - - *out_t0 = t0; - *out_t1 = t1; - return 1; -} - -int jcv_clip_polygon_clip_edge(const jcv_clipper* clipper, jcv_edge* e) -{ - // Using the box clipper to get a finite line segment - int result = jcv_boxshape_clip(clipper, e); - if (!result) - return 0; - - jcv_point p0 = e->pos[0]; - jcv_point p1 = e->pos[1]; - - jcv_real t0; - jcv_real t1; - result = jcv_ray_intersect_polygon(clipper, p0, p1, &t0, &t1); - - if (!result) { - e->pos[0] = e->pos[1]; - return 0; - } - - e->pos[0] = jcv_mix(p0, p1, t0); - e->pos[1] = jcv_mix(p0, p1, t1); - return 1; -} - -// Find the edge which the point sits on -static int jcv_find_polygon_edge(const jcv_clipper* clipper, jcv_point p) -{ - jcv_clipping_polygon* polygon = (jcv_clipping_polygon*)clipper->ctx; - - int min_edge = -1; - jcv_real min_dist = JCV_FLT_MAX; - int num_points = polygon->num_points; - for (int i = 0; i < num_points; ++i) - { - jcv_point p0 = polygon->points[i]; - if (jcv_point_eq(&p, &p0)) - return i; - - jcv_point p1 = polygon->points[(i+1)%num_points]; - jcv_point vsegment = jcv_sub(p1, p0); - jcv_point vpoint = jcv_sub(p, p0); - - jcv_real t = jcv_dot(vsegment, vpoint) / jcv_dot(vsegment,vsegment); - - if (t < (jcv_real)0.0f || t > (jcv_real)1.0f) - continue; - - jcv_point projected = jcv_add(p0, jcv_mul(vsegment, t)); - jcv_real distsq = jcv_length_sq(jcv_sub(p, projected)); - - if (distsq < min_dist) { - min_dist = distsq; - min_edge = i; - } - } - assert(min_edge >= 0); - return min_edge; -} - -void jcv_clip_polygon_fill_gaps(const jcv_clipper* clipper, jcv_context_internal* allocator, jcv_site* site) -{ - // They're sorted CCW, so if the current->pos[1] != next->pos[0], then we have a gap - jcv_clipping_polygon* polygon = (jcv_clipping_polygon*)clipper->ctx; - int num_points = polygon->num_points; - - jcv_graphedge* current = site->edges; - if( !current ) - { - jcv_graphedge* gap = jcv_alloc_graphedge(allocator); - gap->neighbor = 0; - // Pick the first edge of the polygon (which is also CCW) - gap->pos[0] = polygon->points[0]; - gap->pos[1] = polygon->points[1]; - gap->angle = jcv_calc_sort_metric(site, gap); - gap->next = 0; - gap->edge = jcv_create_gap_edge(allocator, site, gap); - - current = gap; - site->edges = gap; - } - - jcv_graphedge* next = current->next; - if( !next ) - { - jcv_graphedge* gap = jcv_alloc_graphedge(allocator); - - int polygon_edge = jcv_find_polygon_edge(clipper, current->pos[1]); - if (!jcv_point_eq(¤t->pos[1], &polygon->points[(polygon_edge+1)%num_points])) { - gap->pos[0] = current->pos[1]; - gap->pos[1] = polygon->points[(polygon_edge+1)%num_points]; - } else { - gap->pos[0] = polygon->points[(polygon_edge+1)%num_points]; - gap->pos[1] = polygon->points[(polygon_edge+2)%num_points]; - } - - gap->neighbor = 0; - gap->angle = jcv_calc_sort_metric(site, gap); - gap->next = 0; - gap->edge = jcv_create_gap_edge(allocator, site, gap); - - gap->next = current->next; - current->next = gap; - current = gap; - next = site->edges; - } - - while (current && next) - { - if (!jcv_point_eq(¤t->pos[1], &next->pos[0])) - { - int polygon_edge1 = jcv_find_polygon_edge(clipper, current->pos[1]); - int polygon_edge2 = jcv_find_polygon_edge(clipper, next->pos[0]); - - jcv_graphedge* gap = jcv_alloc_graphedge(allocator); - gap->pos[0] = current->pos[1]; - - if (polygon_edge1 != polygon_edge2) { - gap->pos[1] = polygon->points[(polygon_edge1+1)%num_points]; - } else { - gap->pos[1] = next->pos[0]; - } - - gap->neighbor = 0; - gap->angle = jcv_calc_sort_metric(site, gap); - gap->edge = jcv_create_gap_edge(allocator, site, gap); - gap->next = current->next; - current->next = gap; - } - - current = current->next; - if( current ) - { - next = current->next; - if( !next ) { - next = site->edges; - } - } - } -} - -#endif // JC_VORONOI_CLIP_IMPLEMENTATION - - -/* - -ABOUT: - - Helper functions for clipping a vosonoi diagram against a convex polygon - - -LICENSE: - - The MIT License (MIT) - - Copyright (c) 2019 Mathias Westerdahl - - 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. - - -DISCLAIMER: - - This software is supplied "AS IS" without any warranties and support - -USAGE: - -USAGE: - - The function `jcv_clipper` struct allows for supplying a set of custom clipper functions to interact with the generating of the resulting diagram. - - #define JC_VORONOI_CLIP_IMPLEMENTATION - #include "jc_voronoi_clip.h" - - jcv_clipping_polygon polygon; - // Triangle - polygon.num_points = 3; - polygon.points = (jcv_point*)malloc(sizeof(jcv_point)*(size_t)polygon.num_points); - - polygon.points[0].x = width/2; - polygon.points[1].x = width - width/5; - polygon.points[2].x = width/5; - polygon.points[0].y = height/5; - polygon.points[1].y = height - height/5; - polygon.points[2].y = height - height/5; - - jcv_clipper polygonclipper; - polygonclipper.test_fn = jcv_clip_polygon_test_point; - polygonclipper.clip_fn = jcv_clip_polygon_clip_edge; - polygonclipper.fill_fn = jcv_clip_polygon_fill_gaps; - polygonclipper.ctx = &polygon; - - jcv_diagram diagram; - memset(&diagram, 0, sizeof(jcv_diagram)); - jcv_diagram_generate(count, (const jcv_point*)points, 0, clipper, &diagram); -*/