Drawing slices of a hypersurface with R

[This article was first published on Saturn Elephant, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Let s:I×J×KR4 be a parameterization of a hypersurface S, where I,J,KR are some intervals. I’m going to show how to draw the cross-section of S by a hyperplane with R.

For the illustration, we consider the tiger:

R1 = 2; R2 = 2; r = 0.5
s <- function(u, v, w){
  rbind(
    cos(u) * (R1 + r*cos(w)),
    sin(u) * (R1 + r*cos(w)),
    cos(v) * (R2 + r*sin(w)),
    sin(v) * (R2 + r*sin(w))
  )
}

Take a hyperplane: P:a,x=b,

let n=aa be a unit normal vector of P, and x0 be an arbitrary point in P.

a = c(1, 1, 1, 1); b = 2        # plane x+y+z+w = 2
x0 = c(b, b, b, b)/4            # a point in this plane
nrml <- a/sqrt(c(crossprod(a))) # unit normal

Compute a mesh M0 of the isosurface (s(u,v,w)x0)n=0.

library(misc3d)
f <- function(u, v, w){
  c(crossprod(s(u, v, w), nrml))
}
u_ <- v_ <- w_ <- seq(0, 2*pi, length.out = 100L)
g <- expand.grid(u = u_, v = v_, w = w_)
voxel <- array(with(g, f(u,v,w)), dim = c(100L,100L,100L))
surf <- computeContour3d(voxel, level = sum(x0*nrml), 
                         x = u_, y = v_, z = w_)
trgls <- makeTriangles(surf)
mesh0 <- misc3d:::t2ve(trgls)

Denote by VS0I×J×K the set of vertices of M0, and set VS=s(VS0)R4.

VS0 <- mesh0$vb
VS <- s(VS0[1L,], VS0[2L,], VS0[3L,]) 

Let R be a rotation in R4 which sends n=:v1 to the vector (0,0,0,1)=:v2. One can take R corresponding to the matrix 2(v1+v2)(v1+v2)(v1+v2)(v1+v2)I4.

rotationMatrix4D <- function(v1, v2){
  v1 <- v1 / sqrt(c(crossprod(v1)))
  v2 <- v2 / sqrt(c(crossprod(v2)))
  2*tcrossprod(v1+v2)/c(crossprod(v1+v2)) - diag(4L)
}
Rot <- rotationMatrix4D(nrml, c(0,0,0,1))

Now define VS=R(VS)R4. Then all points in VS are equal on their fourth coordinate (up to numerical errors in R):

VSprime <- Rot %*% VS
head(t(VSprime))
##           [,1]      [,2]      [,3]      [,4]
## [1,]  2.203740 -1.329658 -1.620365 0.9999785
## [2,] -1.324840  2.206491 -1.657871 1.0002417
## [3,] -1.320131  2.212244 -1.636339 0.9999972
## [4,] -1.417790  2.116178 -1.698381 0.9999926
## [5,]  2.219859 -1.310784 -1.651340 0.9999841
## [6,]  2.253515 -1.275147 -1.633245 1.0005005

Finally, define VSR3 as the set obtained by removing the fourth coordinates of the elements of VS, and define the mesh M whose set of vertices is VS and with the same edges as M0:

library(rgl)
mesh <- tmesh3d(
  vertices = VSprime[-4L,],
  indices = mesh0$ib,
  homogeneous = FALSE,
  normals = ?
)

What about the normals? If you have an implicit equation defining S, that is, S=ι1(0) with ι:R4R, then a normal to S at a point xR4 is given by the gradient of ι at x. For the tiger, we know an implicit equation, and it is not difficult to get the gradient:

sNormal <- function(XYZT){
  x <- XYZT[1L,]; y <- XYZT[2L,]; z <- XYZT[3L,]; t <- XYZT[4L,]
  rbind(
    x * (1 - R1/sqrt(x^2+y^2)),
    y * (1 - R1/sqrt(x^2+y^2)),
    z * (1 - R2/sqrt(z^2+t^2)),
    t * (1 - R2/sqrt(z^2+t^2))
  )
}
Normals <- sNormal(VS)

Once you get the normals:

  • project them to the hyperplane P;

  • apply the rotation R to the projected normals;

  • remove the fourth coordinates (all equal);

  • if necessary, negate the normals.

The projection of xR4 to the hyperplane P is given by xa,xba2a.

# projection onto hyperplane <a,x> = b
projection <- function(a, b, X){
  X - tcrossprod(a/c(crossprod(a)), colSums(a*X)-b)
}
mesh <- tmesh3d(
  vertices = VSprime[-4L,], 
  indices = mesh0$ib, 
  homogeneous = FALSE,
  normals = -t((Rot %*% projection(a, b, Normals))[-4L,])
)

This works:

shade3d(mesh, color = "darkmagenta")

Here is another way to get the normals. The normal at the point s(u,v,w) is su(u,v,w)×sv(u,v,w)×sw(u,v,w)

where ×× is the ternary cross-product in R4, defined by v1×v2×v3=|ijklv1xv1yv1zv1tv2xv2yv2zv2tv3xv3yv3zv3t|.

crossProd4D <- function(v1, v2, v3){
  M <- rbind(v1, v2, v3)
  c(det(M[,-1L]), -det(M[,-2L]), det(M[,-3L]), -det(M[,-4L]))
}
sNormal <- function(uvw){
  u <- uvw[1L]; v <- uvw[2L]; w <- uvw[3L]
  Du <- c((R1 + r*cos(w))*c(-sin(u),cos(u)), 0, 0)
  Dv <- c(0, 0, (R2 + r*sin(w))*c(-sin(v),cos(v)))
  Dw <- r * c(-sin(w)*c(cos(u),sin(u)), cos(w)*c(cos(v),sin(v)))
  crossProd4D(Du, Dv, Dw)
}
Normals <- apply(VS0, 2L, sNormal)

Then you can calculate the normals in this way and proceed as before:

mesh <- tmesh3d(
  vertices = VSprime[-4L,], 
  indices = mesh0$ib, 
  homogeneous = FALSE,
  normals = t((Rot %*% projection(a, b, Normals))[-4L,])
)

Here is how to do an animation:

b_ <- seq(-11.5, 11.5, length.out = 60L)
open3d(windowRect = c(100, 100, 612, 612), zoom = 0.8)
bg3d(rgb(54, 57, 64, maxColorValue = 255))
view3d(45, 40)
for(i in 1L:length(b_)){
  x0 <- rep(b_[i]/4, 4L) 
  surf <- computeContour3d(voxel, level = sum(x0*nrml), 
                           x = u_, y = v_, z = w_)
  trgls <- makeTriangles(surf)
  mesh0 <- misc3d:::t2ve(trgls)
  VS0 <- mesh0$vb
  VS <- s(VS0[1L,], VS0[2L,], VS0[3L,])
  Normals <- sNormal(VS)
  mesh <- tmesh3d(
    vertices = (Rot %*% VS)[-4L,], 
    indices = mesh0$ib, 
    homogeneous = FALSE,
    normals = -t((Rot %*% projection(a, b_[i], Normals))[-4L,])
  )
  shade3d(mesh, color = "firebrick3")
  snapshot3d(sprintf("pic%03d.png", i))
  clear3d()
}
for(i in 1L:59L){
  file.copy(sprintf("pic%03d.png", 60-i), sprintf("pic%03d.png", 60+i))
}
# run gifski
command <- "gifski --fps 12 pic*.png -o slicedTiger.gif"
system(command)
# cleaning
pngfiles <- list.files(pattern = "^pic.*png$")
file.remove(pngfiles)

Toroidal hyperboloid

Let’s give another example, a toroidal hyperboloid. This is a quadric with implicit equation x2a2+y2b2z2c2t2d2=1,

and a parameterization of this quadric is s:(0,2π)×(0,2π)×(0,+[R4(u,v,w)(acosucoshwbsinucoshwccosvsinhwdsinvsinhw).

To leave a comment for the author, please follow the link and comment on their blog: Saturn Elephant.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)