{"id":25059945,"url":"https://github.com/stla/cxhull","last_synced_at":"2025-04-14T14:24:53.290Z","repository":{"id":56928763,"uuid":"139739675","full_name":"stla/cxhull","owner":"stla","description":"Convex hull in arbitrary dimension","archived":false,"fork":false,"pushed_at":"2023-10-24T02:46:39.000Z","size":820,"stargazers_count":14,"open_issues_count":0,"forks_count":6,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-28T03:24:31.602Z","etag":null,"topics":["computational-geometry","convex-hull","r"],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stla.png","metadata":{"files":{"readme":"README.Rmd","changelog":"NEWS.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-07-04T15:32:33.000Z","updated_at":"2025-01-31T20:18:11.000Z","dependencies_parsed_at":"2023-02-15T11:30:54.718Z","dependency_job_id":"4ed342b8-ca98-44de-a65a-0608c9ac40ca","html_url":"https://github.com/stla/cxhull","commit_stats":{"total_commits":108,"total_committers":1,"mean_commits":108.0,"dds":0.0,"last_synced_commit":"92e0d0e2b9f33d53e0dac65a2d189a061812bdd7"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stla%2Fcxhull","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stla%2Fcxhull/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stla%2Fcxhull/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stla%2Fcxhull/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stla","download_url":"https://codeload.github.com/stla/cxhull/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248895556,"owners_count":21179259,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["computational-geometry","convex-hull","r"],"created_at":"2025-02-06T15:55:58.962Z","updated_at":"2025-04-14T14:24:53.258Z","avatar_url":"https://github.com/stla.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"---\ntitle: \"cxhull\"\nauthor: \"\"\ndate: \"2022-10-25\"\noutput:\n  github_document:\n  html_document:\n    toc: yes\n    keep_md: no\neditor_options:\n  chunk_output_type: console\n---\n\n\u003c!-- badges: start --\u003e\n[![R-CMD-check](https://github.com/stla/cxhull/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/stla/cxhull/actions/workflows/R-CMD-check.yaml)\n\u003c!-- badges: end --\u003e\n\n```{r setup, include=FALSE}\nknitr::opts_chunk$set(echo = TRUE, collapse = TRUE)\n```\n\nThe purpose of the `cxhull` package is to compute the convex hull of a set of\npoints in arbitrary dimension.\nIts main function is named `cxhull`.\n\nThe output of the `cxhull` function is a list with the following fields.\n\n- `vertices`: The vertices of the convex hull. Each vertex is given with its\nneighbour vertices, its neighbour ridges and its neighbour facets.\n\n- `edges`: The edges of the convex hull, given as pairs of vertices\nidentifiers.\n\n- `ridges`: The ridges of the convex hull, i.e. the elements of the convex hull\nof dimension `dim-2`. Thus the ridges are just the vertices in dimension 2, and\nthey are the edges in dimension 3.\n\n- `facets`: The facets of the convex hull, i.e. the elements of the convex hull\nof dimension `dim-1`. Thus the facets are the edges in dimension 2, and they\nare the faces of the convex polyhedron in dimension 3.\n\n- `volume`: The volume of the convex hull\n(area in dimension 2, volume in dimension 3, hypervolume in higher dimension).\n\nLet's look at an example. The points we take are the vertices of a cube and\nthe center of this cube (in the first row):\n\n```{r}\nlibrary(cxhull)\npoints \u003c- rbind(\n  c(0.5, 0.5, 0.5),\n  c(0, 0, 0),\n  c(0, 0, 1),\n  c(0, 1, 0),\n  c(0, 1, 1),\n  c(1, 0, 0),\n  c(1, 0, 1),\n  c(1, 1, 0),\n  c(1, 1, 1)\n)\nhull \u003c- cxhull(points)\n```\n\nObviously, the convex hull of these points is the cube.\nWe can quickly see that the convex hull has 8 vertices, 12 edges, 12 ridges,\n6 facets, and its volume is 1:\n\n```{r}\nstr(hull, max = 1)\n```\n\nEach vertex, each ridge, and each facet has an identifier.\nA vertex identifier is the index of the row corresponding to\nthis vertex in the set of points passed to the `cxhull` function.\nIt is given in the field `id` of the vertex:\n\n```{r}\nhull[[\"vertices\"]][[1]]\n```\n\nAlso, the list of vertices is named with the identifiers:\n\n```{r}\nnames(hull[[\"vertices\"]])\n```\n\nEdges are given as a matrix, each row representing an edge given as a pair of\nvertices identifiers:\n\n```{r}\nhull[[\"edges\"]]\n```\n\nThe ridges are given as a list:\n\n```{r}\nhull[[\"ridges\"]][[1]]\n```\n\nThe `vertices` field provides the vertices identifiers of the ridge.\nA ridge is between two facets; the identifiers of these facets are given in\nthe field `ridgeOf`.\n\nFacets are given as a list:\n\n```{r}\nhull[[\"facets\"]][[1]]\n```\n\nThere is no `id` field for the facets: the integer `i` is the identifier of\nthe `i`-th facet of the list.\n\nThe `orientation` field has two possible values, `1` or `-1`, it indicates the \norientation of the facet. See the plotting example below.\n\nHere, the `family` field is `NA` for every facet:\n\n```{r}\nsapply(hull[[\"facets\"]], `[[`, \"family\")\n```\n\nThis field has a possibly non-missing value only when one requires the\ntriangulation of the convex hull:\n\n```{r}\nthull \u003c- cxhull(points, triangulate = TRUE)\nsapply(thull[[\"facets\"]], `[[`, \"family\")\n```\n\nThe hull is triangulated into 12 triangles: each face of the cube is\ntriangulated into two triangles.\nTherefore one gets six different families, each one consisting of two\ntriangles: two triangles belong to the same family mean that they are parts of\nthe same facet of the non-triangulated hull.\n\n\n## Ordering the vertices\n\nObserve the vertices of the first face of the cube:\n\n```{r}\nhull[[\"facets\"]][[1]][[\"vertices\"]]\n```\n\nThey are given as `8-4-6-2`.\nThey are not ordered, in the sense that `4-6` and `2-8` are not edges of this\nface:\n\n```{r}\n( face_edges \u003c- hull[[\"facets\"]][[1]][[\"edges\"]] )\n```\n\nOne can order the vertices as follows:\n\n```{r}\npolygonize \u003c- function(edges){\n  nedges \u003c- nrow(edges)\n  vs \u003c- edges[1, ]\n  v \u003c- vs[2]\n  edges \u003c- edges[-1, ]\n  for(. in 1:(nedges-2)){\n    j \u003c- which(apply(edges, 1, function(e) v %in% e))\n    v \u003c- edges[j, ][which(edges[j, ] != v)]\n    vs \u003c- c(vs, v)\n    edges \u003c- edges[-j, ]\n  }\n  vs\n}\npolygonize(face_edges)\n```\n\nInstead of using this function, use the `hullMesh` function. It returns the \nvertices of the convex hull and its faces with ordered indices. \n\n\n## The `cxhullEdges` function\n\nThe `cxhull` function returns a lot of information about the convex hull. If \nyou only want to find the edges of the convex hull, use the `cxhullEdges` \nfunction instead, for a speed gain and less memory consumption. For example, \nthe `cxhull` function fails on my laptop for the \n[E8 root polytope](https://laustep.github.io/stlahblog/posts/E8rootPolytope.html), \nwhile the `cxhullEdges` function works (but it takes a while).\n\n\n## Plotting a 3-dimensional hull\n\nThe package provides the function `plotConvexHull3d` to plot a *triangulated* 3-dimensional hull with **rgl**. Let's take an icosidodecahedron as example:\n\n```{r}\nlibrary(cxhull)\nlibrary(rgl)\n# icosidodecahedron\nphi \u003c- (1+sqrt(5))/2\nvs1 \u003c- rbind(\n  c(0, 0, 2*phi),\n  c(0, 2*phi, 0),\n  c(2*phi, 0, 0)\n)\nvs1 \u003c- rbind(vs1, -vs1)\nvs2 \u003c- rbind(\n  c( 1,  phi,  phi^2),\n  c( 1,  phi, -phi^2),\n  c( 1, -phi,  phi^2),\n  c(-1,  phi,  phi^2),\n  c( 1, -phi, -phi^2),\n  c(-1,  phi, -phi^2),\n  c(-1, -phi,  phi^2),\n  c(-1, -phi, -phi^2)\n)\nvs2 \u003c- rbind(vs2, vs2[, c(2, 3, 1)], vs2[, c(3, 1, 2)])\npoints \u003c- rbind(vs1, vs2)\n# computes the triangulated convex hull:\nhull \u003c- cxhull(points, triangulate = TRUE)\n```\n\n```{r, eval=FALSE}\n# plot:\nopen3d(windowRect = c(50, 50, 562, 562))\nview3d(10, 80, zoom = 0.7)\nplotConvexHull3d(\n  hull, facesColor = \"orangered\", edgesColor = \"yellow\",\n  tubesRadius = 0.06, spheresRadius = 0.08\n)\n```\n\n![](https://raw.githubusercontent.com/stla/cxhull/master/inst/images/icosidodecahedron.png)\n\n## Facets orientation\n\nThe `plotConvexHull3d` function calls the `TrianglesXYZ` function, which takes \ncare of the orientation of the facets. \nIndeed, with the code below, we see the whole convex hull while we hide the \nback side of the triangles:\n\n```{r, eval=FALSE}\ntriangles \u003c- TrianglesXYZ(hull)\nopen3d(windowRect = c(50, 50, 562, 562))\nview3d(10, 80, zoom = 0.7)\ntriangles3d(triangles, color = \"green\", back = \"culled\")\n```\n\n![](https://raw.githubusercontent.com/stla/cxhull/master/inst/images/icosidodecahedron_culled.png)\n\nThe `orientation` field of a facet indicates its orientation (`1` or `-1`). \n\n\n## Plotting with multiple colors\n\nThere are three possiblities for the `facesColor` argument of the \n`plotConvexHull3d` function. We have already seen the first one: a single color. \nThe second possibiity is to assign a color to each triangle of the hull. \nThere are 56 triangles:\n\n```{r}\nlength(hull[[\"facets\"]])\n```\n\nSo we specify 56 colors:\n\n```{r, eval=FALSE}\nlibrary(randomcoloR)\ncolors \u003c- distinctColorPalette(56)\nopen3d(windowRect = c(50, 50, 562, 562))\nview3d(10, 80, zoom = 0.7)\nplotConvexHull3d(\n  hull, facesColor = colors, edgesColor = \"yellow\",\n  tubesRadius = 0.06, spheresRadius = 0.08\n)\n```\n\n![](https://raw.githubusercontent.com/stla/cxhull/master/inst/images/icosidodecahedron_color_triangles.png)\n\nThe third possibility is to assign a color to each face of the convex hull. \nThere are 32 faces:\n\n```{r}\nsummary \u003c- hullSummary(hull)\nattr(summary, \"facets\")\n```\n\n```{r, eval=FALSE}\nlibrary(randomcoloR)\ncolors \u003c- distinctColorPalette(32)\nopen3d(windowRect = c(50, 50, 562, 562))\nview3d(10, 80, zoom = 0.7)\nplotConvexHull3d(\n  hull, facesColor = colors, edgesColor = \"yellow\",\n  tubesRadius = 0.06, spheresRadius = 0.08\n)\n```\n\n![](https://raw.githubusercontent.com/stla/cxhull/master/inst/images/icosidodecahedron_color_faces.png)\n\nFinally, instead of using the `facesColor` argument, you can use the `palette` \nargument, which allows to decorate the faces with a color gradient.\n\n```{r, eval=FALSE}\nopen3d(windowRect = c(50, 50, 562, 562))\nview3d(10, 80, zoom = 0.7)\nplotConvexHull3d(\n  hull, palette = hcl.colors(256, \"BuPu\"), bias = 0.25, \n  edgesColor = \"yellow\", tubesRadius = 0.06, spheresRadius = 0.08\n)\n```\n\n![](https://raw.githubusercontent.com/stla/cxhull/master/inst/images/icosidodecahedron_color_gradient.png)\n\n\n# A four-dimensional example \n\nNow, to illustrate the `cxhull` package, we deal with a four-dimensional \npolytope: the *truncated tesseract*.\n\nIt is a convex polytope whose vertices are given by all permutations of\n(\u0026pm;1, \u0026pm;(\u0026radic;2+1), \u0026pm;(\u0026radic;2+1), \u0026pm;(\u0026radic;2+1)).\n\nLet's enter these 64 vertices in a matrix `points`:\n\n```{r}\nsqr2p1 \u003c- sqrt(2) + 1\npoints \u003c- rbind(\n  c(-1, -sqr2p1, -sqr2p1, -sqr2p1),\n  c(-1, -sqr2p1, -sqr2p1, sqr2p1),\n  c(-1, -sqr2p1, sqr2p1, -sqr2p1),\n  c(-1, -sqr2p1, sqr2p1, sqr2p1),\n  c(-1, sqr2p1, -sqr2p1, -sqr2p1),\n  c(-1, sqr2p1, -sqr2p1, sqr2p1),\n  c(-1, sqr2p1, sqr2p1, -sqr2p1),\n  c(-1, sqr2p1, sqr2p1, sqr2p1),\n  c(1, -sqr2p1, -sqr2p1, -sqr2p1),\n  c(1, -sqr2p1, -sqr2p1, sqr2p1),\n  c(1, -sqr2p1, sqr2p1, -sqr2p1),\n  c(1, -sqr2p1, sqr2p1, sqr2p1),\n  c(1, sqr2p1, -sqr2p1, -sqr2p1),\n  c(1, sqr2p1, -sqr2p1, sqr2p1),\n  c(1, sqr2p1, sqr2p1, -sqr2p1),\n  c(1, sqr2p1, sqr2p1, sqr2p1),\n  c(-sqr2p1, -1, -sqr2p1, -sqr2p1),\n  c(-sqr2p1, -1, -sqr2p1, sqr2p1),\n  c(-sqr2p1, -1, sqr2p1, -sqr2p1),\n  c(-sqr2p1, -1, sqr2p1, sqr2p1),\n  c(-sqr2p1, 1, -sqr2p1, -sqr2p1),\n  c(-sqr2p1, 1, -sqr2p1, sqr2p1),\n  c(-sqr2p1, 1, sqr2p1, -sqr2p1),\n  c(-sqr2p1, 1, sqr2p1, sqr2p1),\n  c(sqr2p1, -1, -sqr2p1, -sqr2p1),\n  c(sqr2p1, -1, -sqr2p1, sqr2p1),\n  c(sqr2p1, -1, sqr2p1, -sqr2p1),\n  c(sqr2p1, -1, sqr2p1, sqr2p1),\n  c(sqr2p1, 1, -sqr2p1, -sqr2p1),\n  c(sqr2p1, 1, -sqr2p1, sqr2p1),\n  c(sqr2p1, 1, sqr2p1, -sqr2p1),\n  c(sqr2p1, 1, sqr2p1, sqr2p1),\n  c(-sqr2p1, -sqr2p1, -1, -sqr2p1),\n  c(-sqr2p1, -sqr2p1, -1, sqr2p1),\n  c(-sqr2p1, -sqr2p1, 1, -sqr2p1),\n  c(-sqr2p1, -sqr2p1, 1, sqr2p1),\n  c(-sqr2p1, sqr2p1, -1, -sqr2p1),\n  c(-sqr2p1, sqr2p1, -1, sqr2p1),\n  c(-sqr2p1, sqr2p1, 1, -sqr2p1),\n  c(-sqr2p1, sqr2p1, 1, sqr2p1),\n  c(sqr2p1, -sqr2p1, -1, -sqr2p1),\n  c(sqr2p1, -sqr2p1, -1, sqr2p1),\n  c(sqr2p1, -sqr2p1, 1, -sqr2p1),\n  c(sqr2p1, -sqr2p1, 1, sqr2p1),\n  c(sqr2p1, sqr2p1, -1, -sqr2p1),\n  c(sqr2p1, sqr2p1, -1, sqr2p1),\n  c(sqr2p1, sqr2p1, 1, -sqr2p1),\n  c(sqr2p1, sqr2p1, 1, sqr2p1),\n  c(-sqr2p1, -sqr2p1, -sqr2p1, -1),\n  c(-sqr2p1, -sqr2p1, -sqr2p1, 1),\n  c(-sqr2p1, -sqr2p1, sqr2p1, -1),\n  c(-sqr2p1, -sqr2p1, sqr2p1, 1),\n  c(-sqr2p1, sqr2p1, -sqr2p1, -1),\n  c(-sqr2p1, sqr2p1, -sqr2p1, 1),\n  c(-sqr2p1, sqr2p1, sqr2p1, -1),\n  c(-sqr2p1, sqr2p1, sqr2p1, 1),\n  c(sqr2p1, -sqr2p1, -sqr2p1, -1),\n  c(sqr2p1, -sqr2p1, -sqr2p1, 1),\n  c(sqr2p1, -sqr2p1, sqr2p1, -1),\n  c(sqr2p1, -sqr2p1, sqr2p1, 1),\n  c(sqr2p1, sqr2p1, -sqr2p1, -1),\n  c(sqr2p1, sqr2p1, -sqr2p1, 1),\n  c(sqr2p1, sqr2p1, sqr2p1, -1),\n  c(sqr2p1, sqr2p1, sqr2p1, 1)\n)\n```\n\nAs said before, the truncated tesseract is convex, therefore its convex hull \nis itself. Let's run the `cxhull` function on its vertices:\n\n```{r}\nlibrary(cxhull)\nhull \u003c- cxhull(points)\nstr(hull, max = 1)\n```\n\nWe can observe that `cxhull` has not changed the order of the points:\n\n```{r}\nall(names(hull[[\"vertices\"]]) == 1:64)\n```\n\nLet's look at the cells of the truncated tesseract:\n\n```{r}\ntable(sapply(hull[[\"facets\"]], function(cell) length(cell[[\"ridges\"]])))\n```\n\nWe see that 16 cells are made of 4 ridges; these cells are tetrahedra. \nWe will draw them later, after projecting the truncated tesseract in the \n3D-space.\n\nFor now, let's draw the projected vertices and the edges.\n\nThe vertices in the 4D-space lie on the centered sphere with radius \n\u0026radic;(1+3(\u0026radic;2+1)\u003csup\u003e2\u003c/sup\u003e).\n\nTherefore, a stereographic projection is appropriate to project the truncated \ntesseract in the 3D-space.\n\n```{r}\nsproj \u003c- function(p, r){\n  c(p[1], p[2], p[3])/(r - p[4])\n}\nppoints \u003c- t(apply(points, 1, \n                   function(point) sproj(point, sqrt(1+3*sqr2p1^2))))\n```\n\nNow we are ready to draw the projected vertices and the edges.\n\n```{r, eval=FALSE}\nedges \u003c- hull[[\"edges\"]]\nlibrary(rgl)\nopen3d(windowRect = c(100, 100, 600, 600))\nview3d(45, 45)\nspheres3d(ppoints, radius = 0.07, color = \"orange\")\nfor(i in 1:nrow(edges)){\n  shade3d(cylinder3d(rbind(ppoints[edges[i, 1], ], ppoints[edges[i, 2], ]), \n                     radius = 0.05, sides = 30), col = \"gold\")\n}\n```\n\n[![](https://i.imgur.com/YanOo2u.png)](https://laustep.github.io/stlahblog/frames/rgl_truncTesseract1.html)\n\nPretty nice.\n\nNow let's show the 16 tetrahedra. Their faces correspond to triangular ridges. \nSo we get the 64 triangles as follows:\n\n```{r}\nridgeSizes \u003c- \n  sapply(hull[[\"ridges\"]], function(ridge) length(ridge[[\"vertices\"]]))\ntriangles \u003c- t(sapply(hull[[\"ridges\"]][which(ridgeSizes == 3)], \n                      function(ridge) ridge[[\"vertices\"]]))\nhead(triangles)\n```\n\nWe finally add the triangles:\n\n```{r, eval=FALSE}\nfor(i in 1:nrow(triangles)){\n  triangles3d(rbind(\n    ppoints[triangles[i, 1], ],\n    ppoints[triangles[i, 2], ],\n    ppoints[triangles[i, 3], ]),\n    color = \"red\", alpha = 0.4)\n}\n```\n\n[![](https://i.imgur.com/LtHLYfn.png)](https://laustep.github.io/stlahblog/frames/rgl_truncTesseract2.html)\n\nWe could also use different colors for the tetrahedra:\n\n```{r, eval=FALSE}\nopen3d(windowRect = c(100, 100, 600, 600))\nview3d(45, 45)\nspheres3d(ppoints, radius= 0.07, color = \"orange\")\nfor(i in 1:nrow(edges)){\n  shade3d(cylinder3d(rbind(ppoints[edges[i, 1], ], ppoints[edges[i, 2], ]),\n                     radius = 0.05, sides = 30), col = \"gold\")\n}\ncellSizes \u003c- sapply(hull[[\"facets\"]], function(cell) length(cell[[\"ridges\"]]))\ntetrahedra \u003c- hull[[\"facets\"]][which(cellSizes == 4)]\ncolors \u003c- rainbow(16)\nfor(i in seq_along(tetrahedra)){\n  triangles \u003c- tetrahedra[[i]][[\"ridges\"]]\n  for(j in 1:4){\n    triangle \u003c- hull[[\"ridges\"]][[triangles[j]]][[\"vertices\"]]\n    triangles3d(rbind(\n      ppoints[triangle[1], ],\n      ppoints[triangle[2], ],\n      ppoints[triangle[3], ]),\n      color = colors[i], alpha = 0.4)\n  }\n}\n```\n\n[![](https://i.imgur.com/uewjY5j.png)](https://laustep.github.io/stlahblog/frames/rgl_truncTesseract3.html)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstla%2Fcxhull","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstla%2Fcxhull","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstla%2Fcxhull/lists"}