# Ray Tracing the Zonohedron Boundary and the 2-Transition Surface

#### Glenn Davis

#### 2025-08-31

# Introduction

The focus of this vignette are the two functions `raytrace()` and `raytrace2trans()`. The former is for the boundary of the zonohedron and the latter is for the associated 2-transition surface. We revisit the example at the end of section 6 in Scott Burns’ paper [1], which is also illustrated in the 1nm plot from Figure 8. His example is from colorimetry, where the boundary of the zonohedron is the set of optimal colors and the 2-transition surface is the set of Schrödinger colors (both for Illuminant E). The correspondence for the optimal colors was discovered by Paul Centore, see [2].

Other featured functions are `invertboundary()`, `inside()` and `inside2trans()`.

    library(zonohedra)

# A Ray Tracing Example

In Burns’ example, the base of the ray is the center of the zonohedron \(Z\):

    matgen = colorimetry.genlist[["xyz1931.1nm"]]   # the CIE 1931 CMFs at 1nm step
    matgen = 100 * matgen / sum( matgen[2, ] )   # it is traditional to scale so the center has Y=50, recall we use Illuminant E
    zono =  zonohedron( matgen )
    base = getcenter(zono) ; base

    ##        x        y        z
    ## 50.00400 50.00000 50.01653

The vector `base` corresponds to Burns’ vector \(XYZ_{\text{50%}}\).

The direction of the ray is given by spherical angles, which define a unit vector `u`:

    theta = 1.478858 ; phi = 0.371322
    u = c( sin(phi)*cos(theta), sin(phi)*sin(theta), cos(phi) ) ; u

    ## [1] 0.03331263 0.36131522 0.93184848

Calculate the intersection of the ray with the boundary of \(Z\).

    df_opt = raytrace( zono, base, u ) ; df_opt

    ##     base.1   base.2   base.3 direction.1 direction.2 direction.3 facetidx sign     tmax  point.1  point.2  point.3 timetrace
    ## 1 50.00400 50.00000 50.01653  0.03331263  0.36131522  0.93184848    49283   -1 53.63393 51.79069 69.37875 99.99523  0.001034

    XYZ_opt = df_opt$point[1, ] ; XYZ_opt

    ## [1] 51.79069 69.37875 99.99523

This matches Burns’ value of \(XYZ_{\text{LPsoln}}\). From Figure 8 of [1] we see that this point (and every point in the same parallelogram) comes from a reflectance spectrum with 4 transitions. This can be verified by inverting:

    invertboundary( zono, XYZ_opt )$transitions

    ## [1] 4

Now calculate the intersection of the ray with the 2-transition surface associated with \(Z\).

    df_2trans = raytrace2trans( zono, base, u ) ; df_2trans

    ##     base.1   base.2   base.3 direction.1 direction.2 direction.3 gndpair.1 gndpair.2   alpha.1   alpha.2     tmax  point.1  point.2  point.3
    ## 1 50.00400 50.00000 50.01653  0.03331263  0.36131522  0.93184848       629       575 0.2246808 0.4459951 53.63263 51.79065 69.37829 99.99402
    ##    iters timetrace
    ## 1 107564 0.0018986

    XYZ_2trans = df_2trans$point[1, ] ; XYZ_2trans

    ## [1] 51.79065 69.37829 99.99402

This matches Burns’ value of \(XYZ_{\text{two-trans}}\) to 4 decimal places. The transition wavelengths 629 and 575nm, and the parallelogram coordinates 0.2246808 and 0.4459951 (these are the corresponding reflectances), are clearly visible in Figure 8.

Now consider the distance between these 2 points \(XYZ_{\text{LPsoln}}\) and \(XYZ_{\text{two-trans}}\). The parameter `tmax` in both data frames is the parameter on the ray where it intersects the boundary or the surface. Since `u` is a unit vector, the difference between the parameters is this distance.

    df_opt$tmax - df_2trans$tmax

    ## [1] 0.001292229

This matches Burns’ value of \(1.29 \times 10^{-3}\), which is very tiny especially compared to the two \(XYZ\)s.

What is the maximum that this distance can be over the entire \(\partial Z\) ? To get a rough estimate, a search was made over the rays passing though the centers of all the 21900 deficient parallelograms, and with the same basepoint as before. The largest distance over these rays was \(2.47 \times 10^{-3}\). This distance is for the parallelogram with generators corresponding to 592 and 608 nm; the generating ‘spectrum’ has 8 transitions. The actual maximum distance between the boundary of the color solid and the 2-transition surface is not much larger than this sampling. This confirms Burns’ statement from [1] that the distance between these surfaces has “… no practical impact on typical colorimetric calculations”.

If the zonohedron \(Z\) is called the Object Color Solid (OCS), and the inside of the 2-transition surface is called the Schrödinger Color Solid (SCS), we see that the OCS is obtained by adding a very thin “skin” on some regions of the SCS.

# Inside or Outside ?

Consider the midpoint of \(XYZ_{\text{LPsoln}}\) and \(XYZ_{\text{two-trans}}\). It lies on the same ray as these 2 points, so it must be inside the zonohedron, but outside the 2-transition surface. We can verify this easily:

    XYZ_mid = (XYZ_opt + XYZ_2trans) / 2
    inside( zono, XYZ_mid )

    ##        p.1      p.2      p.3 idxhyper      distance inside
    ## 1 51.79067 69.37852 99.99462    49283 -0.0006013682   TRUE

    inside2trans( zono, XYZ_mid )

    ##        p.1      p.2      p.3     distance linkingnumber inside  timecalc
    ## 1 51.79067 69.37852 99.99462 0.0006013897             0  FALSE 0.0090238

# References

[1]
BURNS, Scott A. The location of optimal object colors with more than two transitions. Color Research & Application [online]. 2021, 46(6), 1180–1193. Available at: doi:https://doi.org/10.1002/col.22693
[2]
CENTORE, Paul. A zonohedral approach to optimal colours. Color Research & Application [online]. 2013, 38(2), 110–119. Available at: doi:10.1002/col.20713

# 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] zonohedra_0.6-0

    loaded via a namespace (and not attached):
     [1] rgl_1.3.18           digest_0.6.37        R6_2.6.1
     [4] microbenchmark_1.5.0 base64enc_0.1-3      fastmap_1.2.0
     [7] xfun_0.52            magrittr_2.0.3       glue_1.8.0
    [10] cachem_1.1.0         knitr_1.50           htmltools_0.5.8.1
    [13] logger_0.4.0         rmarkdown_2.29       lifecycle_1.0.4
    [16] cli_3.6.5            sass_0.4.10          jquerylib_0.1.4
    [19] compiler_4.5.1       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