# zonohedra User Guide

#### Glenn Davis

#### 2025-08-31

# Introduction

A zonohedron is the linear image of a high-dimensional cube \([0,1]^N\) to \(\mathbb{R}^3\). A zonohedron is a special type of convex polyhedron. The images of the standard basis of \(\mathbb{R}^N\) are called the generators of the zonohedron.

The goal of this package is to construct any zonohedron from the generators, but especially the ones in these 2 families:

- the classical zonohedra, with high symmetry
- zonohedra that arise naturally from colorimetry, which may contain hundreds of generators, but little symmetry

In the first case, 13 classical zonohedra have been taken from [9] and are built in to the package. In the second case, an object color solid is viewed as a zonohedron; this connection was discovered by Paul Centore and is explained very clearly in [1].

    library(zonohedra)
    library(rgl)

The package dependencies are:

- rgl [4] - for 3D plotting
- microbenchmark [7] - is suggested for its high-precision timer
- logger [3] - for event logging

Some of the figures below are displayed with WebGL - a JavaScript API for rendering interactive 2D and 3D graphics. Try using the left mouse button to rotate and the scroll wheel to zoom.

# Polar Zonohedra

The generators for a polar zonohedra are particularly simple - they are equally distributed on a circle that is in a plane parallel to the xy-plane and whose center is on the z-axis. Construct polar zonohedra with 5 and 25 generators and plot them.

    rgl::mfrow3d( 1, 2 )
    pz5 = polarzonohedron( 5 ) ;  plot( pz5, ewd=5 )
    rgl::next3d()
    plot( polarzonohedron( 25 ), ewd=3 )
    rgl::rglwidget( webgl=TRUE )

polar zonohedra with 5 generators (left) and 25 generators (right)    [both of these are interactive WebGL widgets]

polar zonohedra with 5 generators (left) and 25 generators (right) &emsp;&emsp; [both of these are interactive WebGL widgets]

In these 2 plots, the black dot is the origin, the 5 vertices nearest to the origin are the 5 generators, and the white dot is the point (0,0,\(\pi\)). Each of the generators is assigned a unique color, and every other edge with that color is parallel to the generator. All parallelograms with an edge of that color form the zone or belt for that generator. Each belt is a topological annulus. For more details on these polar zonohedra, see [2].

Print the generators of the first zonohedron `pz5`; they are the columns of this 3x5 matrix.

    getmatrix( pz5 )

    ##              1         2          3          4          5
    ## [1,] 0.6283185 0.1941611 -0.5083204 -0.5083204  0.1941611
    ## [2,] 0.0000000 0.5975664  0.3693164 -0.3693164 -0.5975664
    ## [3,] 0.6283185 0.6283185  0.6283185  0.6283185  0.6283185

A function similar to `polarzonohedron()` is `regularprism()`.

# Classic Zonohedra

There are 13 classic zonohedron available in the package, as a list of 3xN matrices, where N is the number of generators. The global data variable is `classics.genlist`, with S3 class `'genlist'`. The 13 matrices in the list are taken from [5].

    classics.genlist

    ##                                   fullname generators vertices edges facets       area     volume pointed
    ## C                                     cube          3        8    12      6    6.00000    1.00000    TRUE
    ## RD                    rhombic dodecahedron          4       14    24     12   33.94113   16.00000    TRUE
    ## BD                   Bilinski dodecahedron          4       14    24     12   38.83282   16.94427    TRUE
    ## RI                     rhombic icosahedron          5       22    40     20   64.72136   42.36068    TRUE
    ## RHD          rhombo-hexagonal dodecahedron          5       18    28     12   72.55309   48.00000    TRUE
    ## RT                 rhombic triacontahedron          6       32    60     30   97.08204   84.72136    TRUE
    ## TO                    truncated octahedron          6       24    36     14   53.56922   32.00000    TRUE
    ## TRD         truncated rhombic dodecahedron          7       32    48     18  110.72888   98.76537    TRUE
    ## TC                 truncated cuboctahedron          9       48    72     26  123.51034  118.22540    TRUE
    ## RE                rhombic enneacontahedron         10       92   180     90  229.70563  318.88544   FALSE
    ## RH              rhombic hectotriadiohedron         12      134   264    132  869.36961 2367.25310   FALSE
    ## TI             truncated icosidodecahedron         15      120   180     62  697.16812 1654.42719    TRUE
    ## TSR truncated small rhombicosidodecahedron         21      240   360    122 1336.66780 4497.87138   FALSE

Extract the matrix of generators for the `truncated cuboctahedron`, which is abbreviated by `TC`.

    mat = classics.genlist[['TC']] ; mat

    ##      [,1]     [,2] [,3] [,4] [,5]     [,6] [,7]     [,8] [,9]
    ## [1,]    1 1.414214    1    0    1 0.000000    0 0.000000    1
    ## [2,]   -1 0.000000    0    1    1 1.414214    1 0.000000    0
    ## [3,]    0 0.000000   -1   -1    0 0.000000    1 1.414214    1
    ## attr(,"shortname")
    ## [1] "TC"
    ## attr(,"fullname")
    ## [1] "truncated cuboctahedron"

Create the truncated cuboctahedron and plot it, with filled faces.

    rgl::par3d( userMatrix = rotationMatrix( -20*pi/180, 0, 1, 1) )
    zono = zonohedron( mat )
    plot( zono, type='f' )
    rgl::rglwidget( webgl=TRUE )

truncated cuboctahedron      [This is an interactive WebGL widget]

truncated cuboctahedron &emsp;&emsp;&emsp;&emsp; [This is an interactive WebGL widget]

Some WebM video plots in the next section are made with help of the package av and this function:

    #   zono        the zonohedron
    #   id          unique ID for this animation, a positive integer
    #   fps         frames per second
    #   duration    of the animation, in seconds
    #   revolutions number of revolutions
    #   vpsize      viewport size = (width,height)
    spinit <- function( zono, index, fps=5, duration=8, revolutions=1, vpsize=c(480,480) ) {
    #  enlarge viewport
    wr = par3d( "windowRect" )
    par3d( windowRect = c( wr[1:2], wr[1:2] + vpsize ) )
    pathtemp = tempdir()   #"./figs" ;   if( ! file.exists(pathtemp) ) dir.create(pathtemp)  # make temp folder
    #  make a lot of .PNG files in pathtemp
    movie3d( spin3d( getcenter(zono), rpm=revolutions*60/duration ), duration=duration, fps=fps, startTime=1/fps,
               convert=F, movie='junk', dir=pathtemp, verbose=F, webshot=F )
    #  combine all the .PNGs into a single .webm
    pathvec = dir( pathtemp, pattern="png$", full=T )

    webm_file = sprintf( "%s/animation%g.webm", pathtemp, index )
    out = av::av_encode_video( pathvec, output=webm_file, framerate=fps, codec='libvpx-vp9', verbose=F )
    res = file.remove( pathvec )  # cleanup the .PNG files, leaving just the .webm

    return( out )
    }

Embedding videos in the HTML output is done with the help of the package base64enc and this function:

    video2html  <- function( path, attributes="controls loop autoplay muted" )
        {
        requireNamespace( "base64enc", quietly=TRUE )

        i   = regexpr( "[.][a-z]+$", path, ignore.case=T )
        if( i < 0 ) return('')
        ext = substring( path, i+1 )    # extract the extension, and skip over the '.'

        part1   = sprintf( '<video %s src="data:video/%s;base64,\n', attributes, ext )
        part2   = base64enc::base64encode( path, linewidth=120, newline='\n' )
        part3   = '"></video>'

        return( paste0( part1, part2, part3, collapse='' ) )
        }

# Colorimetry Zonohedra

In colorimetry, an object color solid is a zonohedron.

    # colorimetry.genlist[[1]] is a 3x81 matrix with the CIE 1931 CMFs at 5nm interval
    zono5 = zonohedron( colorimetry.genlist[[1]] )
    plot( zono5, type='f' )
    webm_file = spinit( zono5, 2, vpsize=c(480,480) )

object color solid at 5nm interval; this zonohedron has 81 generators

In this figure, the black dot is the black point [0,0,0]. The white dot is the white point, i.e. the column sums of the generating matrix.

# Future Work

Here are a few possible improvements and additions.

export
There should be a way to export a zonohedron as a quadrilateral mesh in some standard format(s). DONE. Version 0.5-0 adds `as.mesh3d().zonohedron()`, which is derived from `rgl::as.mesh3d()`.

vignettes
There should be more vignettes. One idea is to show ways to examine individual hyperplanes and facets of a zonohedron. Another idea is to display some interesting Minkowski sums of a few classic zonohedra.

# References

[1]
CENTORE, Paul. A zonohedral approach to optimal colours. Color Research & Application [online]. 2013, 38(2), 110–119. Available at: doi:10.1002/col.20713
[2]
CHILTON, B. L. and COXETER, H. S. M. Polar zonohedra. The American Mathematical Monthly [online]. 1963, 70(9), 946–951 [accessed. 2022-05-27]. ISSN 00029890, 19300972. Available at: https://doi.org/10.2307/2313051
[3]
DARÓCZI, Gergely. Logger: A lightweight, modern and flexible logging utility [online]. 2021. Available at: https://cran.r-project.org/package=logger
[4]
DUNCAN MURDOCH, et. al. Rgl: 3D visualization using OpenGL [online]. 2022. Available at: https://cran.r-project.org/package=rgl
[5]
EPPSTEIN, David. Zonohedra and Zonotopes [online]. Available at: https://www.ics.uci.edu/~eppstein/junkyard/ukraine/ukraine.html. [Online; accessed 26-May-2023]
[6]
HECKBERT, Paul. An Efficient Algorithm for Generating Zonohedra. 3-D Technical Memo 11, Computer Graphics Lab, New York Institute of Technology. 24 February 1985.
[7]
OLAF MERSMANN, Rainer Hurling, Claudia Beleites. Microbenchmark: Accurate timing functions [online]. 2018. Available at: https://cran.r-project.org/package=microbenchmark
[8]
QU, Long. uniqueAtomMat: Finding unique or duplicated rows or columns for atomic matrices [online]. 2019. Available at: https://github.com/cran/uniqueAtomMat/
[9]
WIKIPEDIA CONTRIBUTORS. Zonohedron — Wikipedia, the free encyclopedia [online]. 2022. Available at: https://en.wikipedia.org/w/index.php?title=Zonohedron&oldid=1081903795. [Online; accessed 27-May-2022]

## Appendix A - Methods

The constructor `zonohedron()` uses the optimizations in Paul Heckbert’s memo [6]. The key step is sorting points that lie on a great circle on the sphere. This efficient method is \(O(N^2\log(N))\); whereas the naive method is \(O(N 2^N)\).

The central symmetry is used whenever possible, and when used this can speed things up by a factor of 2. To further speed things up, many of the methods use C/C++.

The function `grpDuplicated()` was written by Long Qu, with a small modification of the return value by myself. It is written in C/C++ and is implemented with `std::unordered_map`. The code was taken from the discontinued package uniqueAtomMat, see [8].

## Appendix B - Logging

Logging is performed using the package logger, see [3]. This is a powerful package that allows a separate configuration for logging from within zonohedra, and that is what I have done. During package loading, the logging threshold is changed from `INFO` to `WARN`. To change it back again, one can execute:
`library( logger )`
`log_threshold( INFO, namespace="zonohedra" )`

The layout callback functions is customized; it adds the name of the calling function to the message. To install your own layout function, you can execute:
`log_layout( <your function>, namespace="zonohedra" )`

The appender callback functions is also customized; it comes to an immediate stop if the message level is `ERROR` or `FATAL`. To return to the default behavior, you can execute:
`log_appender( appender_console, namespace="zonohedra" )`

The formatter callback function is forced to be `formatter_sprintf()`; this should not be changed.

# Session Information

This document was prepared Sun Aug 31, 2025 with the following configuration:

    R version 4.5.1 (2025-06-13 ucrt)
    Platform: x86_64-w64-mingw32/x64
    Running under: Windows 11 x64 (build 26100)

    Matrix products: default
      LAPACK version 3.12.1

    locale:
    [1] LC_COLLATE=C
    [2] LC_CTYPE=English_United States.utf8
    [3] LC_MONETARY=English_United States.utf8
    [4] LC_NUMERIC=C
    [5] LC_TIME=English_United States.utf8

    time zone: America/Los_Angeles
    tzcode source: internal

    attached base packages:
    [1] stats     graphics  grDevices utils     datasets  methods   base

    other attached packages:
    [1] orientlib_0.10.5 rgl_1.3.18       zonohedra_0.6-0

    loaded via a namespace (and not attached):
     [1] digest_0.6.37        R6_2.6.1             microbenchmark_1.5.0
     [4] base64enc_0.1-3      fastmap_1.2.0        xfun_0.52
     [7] magrittr_2.0.3       glue_1.8.0           cachem_1.1.0
    [10] knitr_1.50           htmltools_0.5.8.1    logger_0.4.0
    [13] rmarkdown_2.29       lifecycle_1.0.4      cli_3.6.5
    [16] sass_0.4.10          jquerylib_0.1.4      compiler_4.5.1
    [19] av_0.9.4             tools_4.5.1          evaluate_1.0.3
    [22] bslib_0.9.0          yaml_2.3.10          htmlwidgets_1.6.4
    [25] rlang_1.1.6          jsonlite_2.0.0