The way we made it, the app opens 2 windows: one displayed on a touchscreen, showing the controls; the other shows the actual result, i. e. the model. The control panel allows the user to specify the 4 parameters of the model (the whorl expansion rate, the umbilicus opening, the shape of the opening and the rate of translation), the orientation of the model (on a single window display the user would be able to rotate the model in any direction desired but since it is here shown in a separate window we simplified it, and the user can only rotate around the x axis) or select predefined forms (such as nautilus, ammonite, snail, mussel, etc).
The code is available in its repository. Currently I only put the code for the app as displayed in the exhibit but I'll add soon a simplified, one-window version, in english, as a skeleton app so that people can build more complex apps from it. It was my first experience with shiny, and I'm frankly impressed at how simple and versatile it is.
The part of the code creating the actual mollusc form is surprisingly simple (which I guess is the beauty of Raup's concept). The only difficulty (so to speak) is working in cylindrical coordinates instead of cartesian ones.
make_elliptic_generating_shape <- function(D,S,res=100){
#Let's define the original ray as 1
a <- 1
rc <- (D+1)*a/(1-D) #D is the ratio between the distance from the origin to the distal part of the shape and that from the origin to the proximal part.
t<-seq(0,2*pi,by=pi/res)
b <- a/S # Here S is the long axis v short axis ratio of the ellipsis (as we only modelled elliptical/circular opening shapes here)
circle_0 <- cbind(r=rc + a*cos(t), y= b*sin(t), phi=0) #This is the equation for the shape in cylindrical coordinates.
return(circle_0)
}
#Function to coil the shape around the axis
coiling <- function(RT,W,generating_shape, turns,steps,dir="dextral"){
PHI <- seq(0,2*pi*turns,length=steps)
far_end <- generating_shape[1,1]
closest_end <- approx(generating_shape[generating_shape[,1]<far_end-1,2],
generating_shape[generating_shape[,1]<far_end-1,1],0)$y
D <- closest_end/far_end
rc <- (D+1)/(1-D)
rho <- function(theta, W, r0) r0 * W^(theta/(2*pi)) #See Raup 1966 for the equations.
y <- function(y0,W,theta,rc,T) y0 * W^(theta/(2*pi)) + rc*T*(W^(theta/(2*pi))-1)
circle <- apply(generating_shape,1,
function(x)lapply(PHI,
function(theta)cbind(r=rho(theta, W, x['r']),
y=y(x['y'],W,theta, rc,RT),
phi=theta)))
circle <- do.call(rbind,lapply(circle,function(x)do.call(rbind,x)))
#To cartesian coordinates
if(dir=="dextral"){ #For a dextral shell
XYZ <- list(X = circle[,1] * sin(circle[,3]),
Y = circle[,1] * cos(circle[,3]),
Z = circle[,2])
}else{ #For a sinistral shell
XYZ <- list(X = circle[,1] * cos(circle[,3]),
Y = circle[,1] * sin(circle[,3]),
Z = circle[,2])
}
XYZ
}
# [...]
# With D, S, RT and W defined by the user;
# res and steps and the resolution of the opening shape and the resolution of the coiling
# And turns the number of turns desired (which can be also a parameters for the user to defined if wanted)
circle <- make_elliptic_generating_shape(D,S,res)
ce <- coiling(RT,W,circle,turns,steps,"dextral")
References:
Raup, D. (1966). Geometric Analysis of Shell Coiling: General Problems. Journal of Paleontology, 40(5), 1178-1190.