Stereographic truncated tesseract
Posted on December 16, 2019
by Stéphane Laurent
- The stereographic truncated tesseract
- Drawing with rgl (R)
- Drawing with Asymptote
- Drawing with POV-Ray
The stereographic truncated tesseract
We show how to draw a stereographic truncated tesseract with rgl (R), Asymptote, and POV-Ray.
The truncated tesseract is a uniform polychoron. Among its cells, there are sixteen tetrahedra, and we fill the faces of these tetrahedra.
Using Asymptote and POV-Ray, we include a 4D rotation to animate the figure.
We call it stereographic because we map each edge to the 3-sphere before applying the stereographic projection.
Drawing with rgl (R)
library(rgl)
library(cxhull)
library(abind)
# vertices #####################################################################
sqr2p1 <- sqrt(2) + 1
vertices <- rbind(
c(-1, -sqr2p1, -sqr2p1, -sqr2p1),
c(-1, -sqr2p1, -sqr2p1, sqr2p1),
c(-1, -sqr2p1, sqr2p1, -sqr2p1),
c(-1, -sqr2p1, sqr2p1, sqr2p1),
c(-1, sqr2p1, -sqr2p1, -sqr2p1),
c(-1, sqr2p1, -sqr2p1, sqr2p1),
c(-1, sqr2p1, sqr2p1, -sqr2p1),
c(-1, sqr2p1, sqr2p1, sqr2p1),
c(1, -sqr2p1, -sqr2p1, -sqr2p1),
c(1, -sqr2p1, -sqr2p1, sqr2p1),
c(1, -sqr2p1, sqr2p1, -sqr2p1),
c(1, -sqr2p1, sqr2p1, sqr2p1),
c(1, sqr2p1, -sqr2p1, -sqr2p1),
c(1, sqr2p1, -sqr2p1, sqr2p1),
c(1, sqr2p1, sqr2p1, -sqr2p1),
c(1, sqr2p1, sqr2p1, sqr2p1),
c(-sqr2p1, -1, -sqr2p1, -sqr2p1),
c(-sqr2p1, -1, -sqr2p1, sqr2p1),
c(-sqr2p1, -1, sqr2p1, -sqr2p1),
c(-sqr2p1, -1, sqr2p1, sqr2p1),
c(-sqr2p1, 1, -sqr2p1, -sqr2p1),
c(-sqr2p1, 1, -sqr2p1, sqr2p1),
c(-sqr2p1, 1, sqr2p1, -sqr2p1),
c(-sqr2p1, 1, sqr2p1, sqr2p1),
c(sqr2p1, -1, -sqr2p1, -sqr2p1),
c(sqr2p1, -1, -sqr2p1, sqr2p1),
c(sqr2p1, -1, sqr2p1, -sqr2p1),
c(sqr2p1, -1, sqr2p1, sqr2p1),
c(sqr2p1, 1, -sqr2p1, -sqr2p1),
c(sqr2p1, 1, -sqr2p1, sqr2p1),
c(sqr2p1, 1, sqr2p1, -sqr2p1),
c(sqr2p1, 1, sqr2p1, sqr2p1),
c(-sqr2p1, -sqr2p1, -1, -sqr2p1),
c(-sqr2p1, -sqr2p1, -1, sqr2p1),
c(-sqr2p1, -sqr2p1, 1, -sqr2p1),
c(-sqr2p1, -sqr2p1, 1, sqr2p1),
c(-sqr2p1, sqr2p1, -1, -sqr2p1),
c(-sqr2p1, sqr2p1, -1, sqr2p1),
c(-sqr2p1, sqr2p1, 1, -sqr2p1),
c(-sqr2p1, sqr2p1, 1, sqr2p1),
c(sqr2p1, -sqr2p1, -1, -sqr2p1),
c(sqr2p1, -sqr2p1, -1, sqr2p1),
c(sqr2p1, -sqr2p1, 1, -sqr2p1),
c(sqr2p1, -sqr2p1, 1, sqr2p1),
c(sqr2p1, sqr2p1, -1, -sqr2p1),
c(sqr2p1, sqr2p1, -1, sqr2p1),
c(sqr2p1, sqr2p1, 1, -sqr2p1),
c(sqr2p1, sqr2p1, 1, sqr2p1),
c(-sqr2p1, -sqr2p1, -sqr2p1, -1),
c(-sqr2p1, -sqr2p1, -sqr2p1, 1),
c(-sqr2p1, -sqr2p1, sqr2p1, -1),
c(-sqr2p1, -sqr2p1, sqr2p1, 1),
c(-sqr2p1, sqr2p1, -sqr2p1, -1),
c(-sqr2p1, sqr2p1, -sqr2p1, 1),
c(-sqr2p1, sqr2p1, sqr2p1, -1),
c(-sqr2p1, sqr2p1, sqr2p1, 1),
c(sqr2p1, -sqr2p1, -sqr2p1, -1),
c(sqr2p1, -sqr2p1, -sqr2p1, 1),
c(sqr2p1, -sqr2p1, sqr2p1, -1),
c(sqr2p1, -sqr2p1, sqr2p1, 1),
c(sqr2p1, sqr2p1, -sqr2p1, -1),
c(sqr2p1, sqr2p1, -sqr2p1, 1),
c(sqr2p1, sqr2p1, sqr2p1, -1),
c(sqr2p1, sqr2p1, sqr2p1, 1)
)
# convex hull is the polytope itself, since it is convex #######################
hull <- cxhull(vertices)
# edges ########################################################################
edges <- hull$edges
# (triangular) faces of the tetrahedra #########################################
ridgeSizes <- sapply(hull$ridges, function(ridge) length(ridge$vertices))
triangles <- t(sapply(hull$ridges[which(ridgeSizes==3)],
function(ridge) ridge$vertices))
# stereographic projection #####################################################
sproj <- function(p, r){
c(p[1], p[2], p[3])/(r-p[4])
}
# spherical segment ############################################################
sphericalSegment <- function(P, Q, r, n){
out <- matrix(NA_real_, nrow = n+1, ncol = 4)
for(i in 0:n){
pt <- P + (i/n)*(Q-P)
out[i+1L, ] <- r/sqrt(c(crossprod(pt))) * pt
}
out
}
# stereographic edge ###########################################################
stereoEdge <- function(verts, edge, r, n){
P <- verts[edge[1], ]
Q <- verts[edge[2], ]
PQ <- sphericalSegment(P, Q, r, n)
pq <- t(apply(PQ, 1, function(M){sproj(M, r)}))
dists <- sqrt(apply(pq, 1, crossprod))
cylinder3d(pq, radius = log1p(dists/4)/4, sides = 60)
}
# stereographic subdivision (to fill the triangles) ############################
midpoint4 <- function(A, B, r){
M <- (A + B) / 2
lg <- sqrt(c(crossprod(M))) / r
M / lg
}
subdiv0 <- function(A, B, C, r){
mAB <- midpoint4(A, B, r)
mAC <- midpoint4(A, C, r)
mBC <- midpoint4(B, C, r)
out <- array(NA_real_, dim = c(4L, 3L, 4L))
out[1,,] <- rbind(A, mAB, mAC)
out[2,,] <- rbind(B, mBC, mAB)
out[3,,] <- rbind(C, mAC, mBC)
out[4,,] <- rbind(mAB, mBC, mAC)
out
}
subdiv <- function(n, A, B, C, r){
if(n == 1){
return(subdiv0(A, B, C, r))
}
triangles <- subdiv(n-1, A, B, C, r)
out <- array(NA_real_, dim = c(0L, 3L, 4L))
for(i in 1:(4^(n-1))){
trgl <- triangles[i,,]
out <- abind(out, subdiv0(trgl[1L,], trgl[2L,], trgl[3L,], r), along = 1L)
}
out
}
# mesh maker ###################################################################
stereoTriangle <- function(n, A, B, C, r){
triangles <- subdiv(n, A, B, C, r)
ntriangles <- dim(triangles)[1L]
indices <- matrix(1L:(3L*ntriangles), nrow = 3L, ncol = ntriangles)
vertices <- matrix(NA_real_, nrow = 3L, ncol = 3L*ntriangles)
for(i in 1L:ntriangles){
trgl <- triangles[i,,]
vertices[, 3L*(i-1L)+1L] <- sproj(trgl[1L,], r)
vertices[, 3L*(i-1L)+2L] <- sproj(trgl[2L,], r)
vertices[, 3L*(i-1L)+3L] <- sproj(trgl[3L,], r)
}
mesh <- tmesh3d(vertices, indices, homogeneous = FALSE)
Rvcg::vcgClean(mesh, sel = c(0,7), silent = TRUE)
}
# projected vertices ###########################################################
r <- sqrt(1+3*sqr2p1^2)
vs <- t(apply(vertices, 1L, function(M){sproj(M, r)}))
####~~~~ plot ~~~~##############################################################
open3d(windowRect = c(50, 50, 562, 562), zoom = 0.9)
bg3d(rgb(54, 57, 64, maxColorValue = 255))
## plot the edges
for(k in 1L:nrow(edges)){
edge <- stereoEdge(vertices, edges[k,], r, n = 100)
shade3d(edge, color = "gold")
}
## plot the vertices
for(i in 1L:nrow(vs)){
v <- vs[i,]
spheres3d(v, radius = log1p(sqrt(c(crossprod(v)))/4)/3, color = "gold2")
}
## plot the filled triangles
for(i in 1L:nrow(triangles)){
trgl <- triangles[i,]
A <- vertices[trgl[1L], ]
B <- vertices[trgl[2L], ]
C <- vertices[trgl[3L], ]
mesh <- stereoTriangle(6, A, B, C, r)
shade3d(mesh, color = "red")
}
Drawing with Asymptote
settings.render = 4;
settings.outformat = "eps";
import tube;
size(200,0);
currentprojection = orthographic(1, 2, 4);
currentlight = light(gray(0.85), ambient=black, specularfactor=3,
(100,100,100), specular=gray(0.9), viewport=false);
currentlight.background = rgb("363940ff");
// files to be saved -----------------------------------------------------------
string[] files = {
"TT000", "TT001", "TT002", "TT003", "TT004", "TT005",
"TT006", "TT007", "TT008", "TT009", "TT010", "TT011",
"TT012", "TT013", "TT014", "TT015", "TT016", "TT017",
"TT018", "TT019", "TT020", "TT021", "TT022", "TT023",
"TT024", "TT025", "TT026", "TT027", "TT028", "TT029",
"TT030", "TT031", "TT032", "TT033", "TT034", "TT035",
"TT036", "TT037", "TT038", "TT039", "TT040", "TT041",
"TT042", "TT043", "TT044", "TT045", "TT046", "TT047",
"TT048", "TT049", "TT050", "TT051", "TT052", "TT053",
"TT054", "TT055", "TT056", "TT057", "TT058", "TT059",
"TT060", "TT061", "TT062", "TT063", "TT064", "TT065",
"TT066", "TT067", "TT068", "TT069", "TT070", "TT071",
"TT072", "TT073", "TT074", "TT075", "TT076", "TT077",
"TT078", "TT079", "TT080", "TT081", "TT082", "TT083",
"TT084", "TT085", "TT086", "TT087", "TT088", "TT089",
"TT090", "TT091", "TT092", "TT093", "TT094", "TT095",
"TT096", "TT097", "TT098", "TT099", "TT100", "TT101",
"TT102", "TT103", "TT104", "TT105", "TT106", "TT107",
"TT108", "TT109", "TT110", "TT111", "TT112", "TT113",
"TT114", "TT115", "TT116", "TT117", "TT118", "TT119",
"TT120", "TT121", "TT122", "TT123", "TT124", "TT125",
"TT126", "TT127", "TT128", "TT129", "TT130", "TT131",
"TT132", "TT133", "TT134", "TT135", "TT136", "TT137",
"TT138", "TT139", "TT140", "TT141", "TT142", "TT143",
"TT144", "TT145", "TT146", "TT147", "TT148", "TT149",
"TT150", "TT151", "TT152", "TT153", "TT154", "TT155",
"TT156", "TT157", "TT158", "TT159", "TT160", "TT161",
"TT162", "TT163", "TT164", "TT165", "TT166", "TT167",
"TT168", "TT169", "TT170", "TT171", "TT172", "TT173",
"TT174", "TT175", "TT176", "TT177", "TT178", "TT179"};
// vertices --------------------------------------------------------------------
struct quadruple {
real x;
real y;
real z;
real t;
}
quadruple array2quadruple(real[] v){
quadruple q;
q.x = v[0]; q.y = v[1]; q.z = v[2]; q.t = v[3];
return q;
}
real x = 1 + sqrt(2);
real r = sqrt(1 + 3 * x * x);
real[][] vertices0 = {
{-1.0, -x, -x, -x},
{-1.0, -x, -x, x},
{-1.0, -x, x, -x},
{-1.0, -x, x, x},
{-1.0, x, -x, -x},
{-1.0, x, -x, x},
{-1.0, x, x, -x},
{-1.0, x, x, x},
{1.0, -x, -x, -x},
{1.0, -x, -x, x},
{1.0, -x, x, -x},
{1.0, -x, x, x},
{1.0, x, -x, -x},
{1.0, x, -x, x},
{1.0, x, x, -x},
{1.0, x, x, x},
{-x, -1.0, -x, -x},
{-x, -1.0, -x, x},
{-x, -1.0, x, -x},
{-x, -1.0, x, x},
{-x, 1.0, -x, -x},
{-x, 1.0, -x, x},
{-x, 1.0, x, -x},
{-x, 1.0, x, x},
{x, -1.0, -x, -x},
{x, -1.0, -x, x},
{x, -1.0, x, -x},
{x, -1.0, x, x},
{x, 1.0, -x, -x},
{x, 1.0, -x, x},
{x, 1.0, x, -x},
{x, 1.0, x, x},
{-x, -x, -1.0, -x},
{-x, -x, -1.0, x},
{-x, -x, 1.0, -x},
{-x, -x, 1.0, x},
{-x, x, -1.0, -x},
{-x, x, -1.0, x},
{-x, x, 1.0, -x},
{-x, x, 1.0, x},
{x, -x, -1.0, -x},
{x, -x, -1.0, x},
{x, -x, 1.0, -x},
{x, -x, 1.0, x},
{x, x, -1.0, -x},
{x, x, -1.0, x},
{x, x, 1.0, -x},
{x, x, 1.0, x},
{-x, -x, -x, -1.0},
{-x, -x, -x, 1.0},
{-x, -x, x, -1.0},
{-x, -x, x, 1.0},
{-x, x, -x, -1.0},
{-x, x, -x, 1.0},
{-x, x, x, -1.0},
{-x, x, x, 1.0},
{x, -x, -x, -1.0},
{x, -x, -x, 1.0},
{x, -x, x, -1.0},
{x, -x, x, 1.0},
{x, x, -x, -1.0},
{x, x, -x, 1.0},
{x, x, x, -1.0},
{x, x, x, 1.0} };
quadruple[] vertices = new quadruple[vertices0.length];
for(int i = 0; i < vertices0.length; ++i){
vertices[i] = array2quadruple(vertices0[i]);
}
// edges -----------------------------------------------------------------------
int[][] edges = {
{0, 8},
{0, 16},
{0, 32},
{0, 48},
{1, 9},
{1, 17},
{1, 33},
{1, 49},
{2, 10},
{2, 18},
{2, 34},
{2, 50},
{3, 11},
{3, 19},
{3, 35},
{3, 51},
{4, 12},
{4, 20},
{4, 36},
{4, 52},
{5, 13},
{5, 21},
{5, 37},
{5, 53},
{6, 14},
{6, 22},
{6, 38},
{6, 54},
{7, 15},
{7, 23},
{7, 39},
{7, 55},
{8, 24},
{8, 40},
{8, 56},
{9, 25},
{9, 41},
{9, 57},
{10, 26},
{10, 42},
{10, 58},
{11, 27},
{11, 43},
{11, 59},
{12, 28},
{12, 44},
{12, 60},
{13, 29},
{13, 45},
{13, 61},
{14, 30},
{14, 46},
{14, 62},
{15, 31},
{15, 47},
{15, 63},
{16, 20},
{16, 32},
{16, 48},
{17, 21},
{17, 33},
{17, 49},
{18, 22},
{18, 34},
{18, 50},
{19, 23},
{19, 35},
{19, 51},
{20, 36},
{20, 52},
{21, 37},
{21, 53},
{22, 38},
{22, 54},
{23, 39},
{23, 55},
{24, 28},
{24, 40},
{24, 56},
{25, 29},
{25, 41},
{25, 57},
{26, 30},
{26, 42},
{26, 58},
{27, 31},
{27, 43},
{27, 59},
{28, 44},
{28, 60},
{29, 45},
{29, 61},
{30, 46},
{30, 62},
{31, 47},
{31, 63},
{32, 34},
{32, 48},
{33, 35},
{33, 49},
{34, 50},
{35, 51},
{36, 38},
{36, 52},
{37, 39},
{37, 53},
{38, 54},
{39, 55},
{40, 42},
{40, 56},
{41, 43},
{41, 57},
{42, 58},
{43, 59},
{44, 46},
{44, 60},
{45, 47},
{45, 61},
{46, 62},
{47, 63},
{48, 49},
{50, 51},
{52, 53},
{54, 55},
{56, 57},
{58, 59},
{60, 61},
{62, 63} };
// tetrahedra ------------------------------------------------------------------
int[][] tetrahedra = {
{0, 16, 32, 48},
{11, 27, 43, 59},
{12, 28, 44, 60},
{8, 24, 40, 56},
{9, 25, 41, 57},
{15, 31, 47, 63},
{13, 29, 45, 61},
{14, 30, 46, 62},
{10, 26, 42, 58},
{3, 19, 35, 51},
{2, 18, 34, 50},
{1, 17, 33, 49},
{4, 20, 36, 52},
{5, 21, 37, 53},
{6, 22, 38, 54},
{7, 23, 39, 55} };
// rotation in 4D space (right-isoclinic) --------------------------------------
quadruple rotate4d(real alpha, real beta, real xi, quadruple vec){
real a = cos(xi);
real b = sin(alpha)*cos(beta)*sin(xi);
real c = sin(alpha)*sin(beta)*sin(xi);
real d = cos(alpha)*sin(xi);
real p = vec.x;
real q = vec.y;
real r = vec.z;
real s = vec.t;
quadruple out;
out.x = a*p - b*q - c*r - d*s;
out.y = a*q + b*p + c*s - d*r;
out.z = a*r - b*s + c*p + d*q;
out.t = a*s + b*r - c*q + d*p;
return out;
}
// stereographic projection ----------------------------------------------------
triple stereog(quadruple A, real r){
return (A.x, A.y, A.z) / (r - A.t);
}
// stereographic path ----------------------------------------------------------
path3 stereoPath(quadruple A, quadruple B, real r, int n){
path3 out;
for(int i = 0; i <= n; ++i){
real t = i/n;
quadruple M;
real x = (1-t)*A.x + t*B.x;
real y = (1-t)*A.y + t*B.y;
real z = (1-t)*A.z + t*B.z;
real t = (1-t)*A.t + t*B.t;
real lg = sqrt(x*x + y*y + z*z + t*t) / r;
M.x = x / lg; M.y = y / lg; M.z = z / lg; M.t = t / lg;
out = out .. stereog(M, r);
}
return out;
}
// section transformation ------------------------------------------------------
transform T(path3 p3, real t, int n){
triple M = relpoint(p3, t/(n/4));
return scale(length(M)/15);
}
// stereographic subdivision (to fill the tetrahedra) --------------------------
quadruple midpoint4(quadruple A, quadruple B, real r){
quadruple M;
real x = (A.x + B.x)/2;
real y = (A.y + B.y)/2;
real z = (A.z + B.z)/2;
real t = (A.t + B.t)/2;
real lg = sqrt(x*x + y*y + z*z + t*t) / r;
M.x = x / lg; M.y = y / lg; M.z = z / lg; M.t = t / lg;
return(M);
}
quadruple[][] subdiv0(quadruple A, quadruple B, quadruple C, real r){
quadruple m01 = midpoint4(A, B, r);
quadruple m02 = midpoint4(A, C, r);
quadruple m12 = midpoint4(B, C, r);
return new quadruple[][] {
{A, m01, m02},
{B, m12, m01},
{C, m02, m12},
{m01, m12, m02}
};
}
quadruple[][] subdiv(int n, quadruple A, quadruple B, quadruple C, real r){
if(n == 1){
return subdiv0(A, B, C, r);
}
quadruple[][] triangles = subdiv(n-1, A, B, C, r);
quadruple[][] out = new quadruple[0][3];
for(int i = 0; i < 4^(n-1); ++i){
quadruple[] trgl = triangles[i];
out.append(subdiv0(trgl[0], trgl[1], trgl[2], r));
}
return out;
}
// mesh maker ------------------------------------------------------------------
struct Mesh {
triple[] vertices;
int[][] indices;
}
Mesh stereoTriangle(int n, quadruple A, quadruple B, quadruple C, real r){
quadruple[][] triangles = subdiv(n, A, B, C, r);
triple[] vertices;
int[][] indices;
int faceIndex = 0;
for(int i = 0; i < triangles.length; ++i){
quadruple[] triangle = triangles[i];
vertices.push(stereog(triangle[0], r));
vertices.push(stereog(triangle[1], r));
vertices.push(stereog(triangle[2], r));
int[] x = {faceIndex, faceIndex+1, faceIndex+2};
indices.push(x);
faceIndex += 3;
}
Mesh out;
out.vertices = vertices;
out.indices = indices;
return out;
}
// "bounding box" (to clip the animation) --------------------------------------
path3 boundingbox = scale(0.5,1,1)*circle(c=O, r=4, normal=Z);
// plot ------------------------------------------------------------------------
int n = 75;
int depth = 6;
real alpha = 0, beta = 0;
for(int f = 0; f < 180; ++f){
// rotation angle
real xi = 2*f*pi/180;
// rotated vertices
quadruple[] vs = new quadruple[vertices.length];
for(int i = 0; i < vertices.length; ++i){
vs[i] = rotate4d(alpha, beta, xi, vertices[i]);
}
// new picture
picture pic;
// draw boundingbox
draw(pic, boundingbox, rgb("363940ff")+opacity(0));
// draw edges
for(int k = 0; k < edges.length; ++k){
quadruple A = vs[edges[k][0]];
quadruple B = vs[edges[k][1]];
path3 p3 = stereoPath(A, B, r, n);
transform S(real t){
return T(p3, t, n);
}
draw(pic, tube(p3, unitcircle, S), rgb(139, 0, 139),
render(compression=Low, merge=true));
}
// draw vertices
for(int i = 0; i < vertices.length; ++i){
triple Asg = stereog(vs[i], r);
draw(pic, shift(Asg)*scale3(length(Asg)/10)*unitsphere, purple);
}
// draw tetrahedra
for(int i = 0; i < tetrahedra.length; ++i){
int[] t = tetrahedra[i];
Mesh mesh = stereoTriangle(depth, vs[t[0]], vs[t[1]], vs[t[2]], r);
draw(pic, mesh.vertices, mesh.indices, m = red);
Mesh mesh = stereoTriangle(depth, vs[t[0]], vs[t[1]], vs[t[3]], r);
draw(pic, mesh.vertices, mesh.indices, m = red);
Mesh mesh = stereoTriangle(depth, vs[t[0]], vs[t[2]], vs[t[3]], r);
draw(pic, mesh.vertices, mesh.indices, m = red);
Mesh mesh = stereoTriangle(depth, vs[t[1]], vs[t[2]], vs[t[3]], r);
draw(pic, mesh.vertices, mesh.indices, m = red);
}
// add picture and save
add(pic);
shipout(files[f], bbox(rgb("363940ff"), FillDraw(rgb("363940ff"))));
erase();
}
Drawing with POV-Ray
#version 3.7;
global_settings { assumed_gamma 1 }
#include "colors.inc"
#include "textures.inc"
/* camera */
camera {
location <-11, 7,-32>
look_at 0
angle 45
}
// sun -------------------------------------------------------------------------
light_source {< 1000,3000,-6000> color rgb<1,1,1>*0.9} // sun
light_source {<-11, 7,-32> color rgb<0.9,0.9,1>*0.1 shadowless} // flash
// sky -------------------------------------------------------------------------
plane{<0,1,0>,1 hollow
texture{ pigment{ bozo turbulence 1.3
color_map { [0.00 rgb <0.24, 0.32, 1.0>*0.6]
[0.75 rgb <0.24, 0.32, 1.0>*0.6]
[0.83 rgb <1,1,1>]
[0.95 rgb <0.25,0.25,0.25>]
[1.0 rgb <0.5,0.5,0.5>]}
scale<1,1,1>*2.5 translate< 0,0,3>
}
finish {ambient 1 diffuse 0} }
scale 10000}
// fog on the ground -----------------------------------------------------------
fog { fog_type 2
distance 50
color Gray10
fog_offset 0.1
fog_alt 1.5
turbulence 1.8
}
// ground ----------------------------------------------------------------------
plane { <0,1,0>, 0
texture {
pigment { color rgb <0.95,0.9,0.73>*0.35 }
normal { bumps 2 scale <0.25,0.25,0.25>*0.5 turbulence 0.5 }
finish { phong 0.1 }
}
}
/* vertices */
#declare sqr2p1 = sqrt(2) + 1;
#declare v0 = < -1.0 , -sqr2p1 , -sqr2p1 , -sqr2p1 >;
#declare v1 = < -1.0 , -sqr2p1 , -sqr2p1 , sqr2p1 >;
#declare v2 = < -1.0 , -sqr2p1 , sqr2p1 , -sqr2p1 >;
#declare v3 = < -1.0 , -sqr2p1 , sqr2p1 , sqr2p1 >;
#declare v4 = < -1.0 , sqr2p1 , -sqr2p1 , -sqr2p1 >;
#declare v5 = < -1.0 , sqr2p1 , -sqr2p1 , sqr2p1 >;
#declare v6 = < -1.0 , sqr2p1 , sqr2p1 , -sqr2p1 >;
#declare v7 = < -1.0 , sqr2p1 , sqr2p1 , sqr2p1 >;
#declare v8 = < 1.0 , -sqr2p1 , -sqr2p1 , -sqr2p1 >;
#declare v9 = < 1.0 , -sqr2p1 , -sqr2p1 , sqr2p1 >;
#declare v10 = < 1.0 , -sqr2p1 , sqr2p1 , -sqr2p1 >;
#declare v11 = < 1.0 , -sqr2p1 , sqr2p1 , sqr2p1 >;
#declare v12 = < 1.0 , sqr2p1 , -sqr2p1 , -sqr2p1 >;
#declare v13 = < 1.0 , sqr2p1 , -sqr2p1 , sqr2p1 >;
#declare v14 = < 1.0 , sqr2p1 , sqr2p1 , -sqr2p1 >;
#declare v15 = < 1.0 , sqr2p1 , sqr2p1 , sqr2p1 >;
#declare v16 = < -sqr2p1 , -1.0 , -sqr2p1 , -sqr2p1 >;
#declare v17 = < -sqr2p1 , -1.0 , -sqr2p1 , sqr2p1 >;
#declare v18 = < -sqr2p1 , -1.0 , sqr2p1 , -sqr2p1 >;
#declare v19 = < -sqr2p1 , -1.0 , sqr2p1 , sqr2p1 >;
#declare v20 = < -sqr2p1 , 1.0 , -sqr2p1 , -sqr2p1 >;
#declare v21 = < -sqr2p1 , 1.0 , -sqr2p1 , sqr2p1 >;
#declare v22 = < -sqr2p1 , 1.0 , sqr2p1 , -sqr2p1 >;
#declare v23 = < -sqr2p1 , 1.0 , sqr2p1 , sqr2p1 >;
#declare v24 = < sqr2p1 , -1.0 , -sqr2p1 , -sqr2p1 >;
#declare v25 = < sqr2p1 , -1.0 , -sqr2p1 , sqr2p1 >;
#declare v26 = < sqr2p1 , -1.0 , sqr2p1 , -sqr2p1 >;
#declare v27 = < sqr2p1 , -1.0 , sqr2p1 , sqr2p1 >;
#declare v28 = < sqr2p1 , 1.0 , -sqr2p1 , -sqr2p1 >;
#declare v29 = < sqr2p1 , 1.0 , -sqr2p1 , sqr2p1 >;
#declare v30 = < sqr2p1 , 1.0 , sqr2p1 , -sqr2p1 >;
#declare v31 = < sqr2p1 , 1.0 , sqr2p1 , sqr2p1 >;
#declare v32 = < -sqr2p1 , -sqr2p1 , -1.0 , -sqr2p1 >;
#declare v33 = < -sqr2p1 , -sqr2p1 , -1.0 , sqr2p1 >;
#declare v34 = < -sqr2p1 , -sqr2p1 , 1.0 , -sqr2p1 >;
#declare v35 = < -sqr2p1 , -sqr2p1 , 1.0 , sqr2p1 >;
#declare v36 = < -sqr2p1 , sqr2p1 , -1.0 , -sqr2p1 >;
#declare v37 = < -sqr2p1 , sqr2p1 , -1.0 , sqr2p1 >;
#declare v38 = < -sqr2p1 , sqr2p1 , 1.0 , -sqr2p1 >;
#declare v39 = < -sqr2p1 , sqr2p1 , 1.0 , sqr2p1 >;
#declare v40 = < sqr2p1 , -sqr2p1 , -1.0 , -sqr2p1 >;
#declare v41 = < sqr2p1 , -sqr2p1 , -1.0 , sqr2p1 >;
#declare v42 = < sqr2p1 , -sqr2p1 , 1.0 , -sqr2p1 >;
#declare v43 = < sqr2p1 , -sqr2p1 , 1.0 , sqr2p1 >;
#declare v44 = < sqr2p1 , sqr2p1 , -1.0 , -sqr2p1 >;
#declare v45 = < sqr2p1 , sqr2p1 , -1.0 , sqr2p1 >;
#declare v46 = < sqr2p1 , sqr2p1 , 1.0 , -sqr2p1 >;
#declare v47 = < sqr2p1 , sqr2p1 , 1.0 , sqr2p1 >;
#declare v48 = < -sqr2p1 , -sqr2p1 , -sqr2p1 , -1.0 >;
#declare v49 = < -sqr2p1 , -sqr2p1 , -sqr2p1 , 1.0 >;
#declare v50 = < -sqr2p1 , -sqr2p1 , sqr2p1 , -1.0 >;
#declare v51 = < -sqr2p1 , -sqr2p1 , sqr2p1 , 1.0 >;
#declare v52 = < -sqr2p1 , sqr2p1 , -sqr2p1 , -1.0 >;
#declare v53 = < -sqr2p1 , sqr2p1 , -sqr2p1 , 1.0 >;
#declare v54 = < -sqr2p1 , sqr2p1 , sqr2p1 , -1.0 >;
#declare v55 = < -sqr2p1 , sqr2p1 , sqr2p1 , 1.0 >;
#declare v56 = < sqr2p1 , -sqr2p1 , -sqr2p1 , -1.0 >;
#declare v57 = < sqr2p1 , -sqr2p1 , -sqr2p1 , 1.0 >;
#declare v58 = < sqr2p1 , -sqr2p1 , sqr2p1 , -1.0 >;
#declare v59 = < sqr2p1 , -sqr2p1 , sqr2p1 , 1.0 >;
#declare v60 = < sqr2p1 , sqr2p1 , -sqr2p1 , -1.0 >;
#declare v61 = < sqr2p1 , sqr2p1 , -sqr2p1 , 1.0 >;
#declare v62 = < sqr2p1 , sqr2p1 , sqr2p1 , -1.0 >;
#declare v63 = < sqr2p1 , sqr2p1 , sqr2p1 , 1.0 >;
#declare vertices = array[64]
{v0,v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15,
v16,v17,v18,v19,v20,v21,v22,v23,v24,v25,v26,v27,v28,v29,v30,v31,
v32,v33,v34,v35,v36,v37,v38,v39,v40,v41,v42,v43,v44,v45,v46,v47,
v48,v49,v50,v51,v52,v53,v54,v55,v56,v57,v58,v59,v60,v61,v62,v63};
/* edges */
#declare edges = array[128][2]
{ { 0 , 8 }
, { 0 , 16 }
, { 0 , 32 }
, { 0 , 48 }
, { 1 , 9 }
, { 1 , 17 }
, { 1 , 33 }
, { 1 , 49 }
, { 2 , 10 }
, { 2 , 18 }
, { 2 , 34 }
, { 2 , 50 }
, { 3 , 11 }
, { 3 , 19 }
, { 3 , 35 }
, { 3 , 51 }
, { 4 , 12 }
, { 4 , 20 }
, { 4 , 36 }
, { 4 , 52 }
, { 5 , 13 }
, { 5 , 21 }
, { 5 , 37 }
, { 5 , 53 }
, { 6 , 14 }
, { 6 , 22 }
, { 6 , 38 }
, { 6 , 54 }
, { 7 , 15 }
, { 7 , 23 }
, { 7 , 39 }
, { 7 , 55 }
, { 8 , 24 }
, { 8 , 40 }
, { 8 , 56 }
, { 9 , 25 }
, { 9 , 41 }
, { 9 , 57 }
, { 10 , 26 }
, { 10 , 42 }
, { 10 , 58 }
, { 11 , 27 }
, { 11 , 43 }
, { 11 , 59 }
, { 12 , 28 }
, { 12 , 44 }
, { 12 , 60 }
, { 13 , 29 }
, { 13 , 45 }
, { 13 , 61 }
, { 14 , 30 }
, { 14 , 46 }
, { 14 , 62 }
, { 15 , 31 }
, { 15 , 47 }
, { 15 , 63 }
, { 16 , 20 }
, { 16 , 32 }
, { 16 , 48 }
, { 17 , 21 }
, { 17 , 33 }
, { 17 , 49 }
, { 18 , 22 }
, { 18 , 34 }
, { 18 , 50 }
, { 19 , 23 }
, { 19 , 35 }
, { 19 , 51 }
, { 20 , 36 }
, { 20 , 52 }
, { 21 , 37 }
, { 21 , 53 }
, { 22 , 38 }
, { 22 , 54 }
, { 23 , 39 }
, { 23 , 55 }
, { 24 , 28 }
, { 24 , 40 }
, { 24 , 56 }
, { 25 , 29 }
, { 25 , 41 }
, { 25 , 57 }
, { 26 , 30 }
, { 26 , 42 }
, { 26 , 58 }
, { 27 , 31 }
, { 27 , 43 }
, { 27 , 59 }
, { 28 , 44 }
, { 28 , 60 }
, { 29 , 45 }
, { 29 , 61 }
, { 30 , 46 }
, { 30 , 62 }
, { 31 , 47 }
, { 31 , 63 }
, { 32 , 34 }
, { 32 , 48 }
, { 33 , 35 }
, { 33 , 49 }
, { 34 , 50 }
, { 35 , 51 }
, { 36 , 38 }
, { 36 , 52 }
, { 37 , 39 }
, { 37 , 53 }
, { 38 , 54 }
, { 39 , 55 }
, { 40 , 42 }
, { 40 , 56 }
, { 41 , 43 }
, { 41 , 57 }
, { 42 , 58 }
, { 43 , 59 }
, { 44 , 46 }
, { 44 , 60 }
, { 45 , 47 }
, { 45 , 61 }
, { 46 , 62 }
, { 47 , 63 }
, { 48 , 49 }
, { 50 , 51 }
, { 52 , 53 }
, { 54 , 55 }
, { 56 , 57 }
, { 58 , 59 }
, { 60 , 61 }
, { 62 , 63 } };
/* tetrahedra */
#declare tetrahedra = array[16][4]
{ { 0 , 16 , 32 , 48 }
, { 11 , 27 , 43 , 59 }
, { 12 , 28 , 44 , 60 }
, { 8 , 24 , 40 , 56 }
, { 9 , 25 , 41 , 57 }
, { 15 , 31 , 47 , 63 }
, { 13 , 29 , 45 , 61 }
, { 14 , 30 , 46 , 62 }
, { 10 , 26 , 42 , 58 }
, { 3 , 19 , 35 , 51 }
, { 2 , 18 , 34 , 50 }
, { 1 , 17 , 33 , 49 }
, { 4 , 20 , 36 , 52 }
, { 5 , 21 , 37 , 53 }
, { 6 , 22 , 38 , 54 }
, { 7 , 23 , 39 , 55 } };
#declare faces = array[64][3];
#for(i, 0, 15)
#declare faces[4*i][0] = tetrahedra[i][0];
#declare faces[4*i][1] = tetrahedra[i][1];
#declare faces[4*i][2] = tetrahedra[i][2];
#declare faces[4*i+1][0] = tetrahedra[i][0];
#declare faces[4*i+1][1] = tetrahedra[i][1];
#declare faces[4*i+1][2] = tetrahedra[i][3];
#declare faces[4*i+2][0] = tetrahedra[i][0];
#declare faces[4*i+2][1] = tetrahedra[i][2];
#declare faces[4*i+2][2] = tetrahedra[i][3];
#declare faces[4*i+3][0] = tetrahedra[i][1];
#declare faces[4*i+3][1] = tetrahedra[i][2];
#declare faces[4*i+3][2] = tetrahedra[i][3];
#end
/* rotation in 4D space */
#macro rotate4d(theta, phi, xi, vec)
#local a = cos(xi);
#local b = sin(theta)*cos(phi)*sin(xi);
#local c = sin(theta)*sin(phi)*sin(xi);
#local d = cos(theta)*sin(xi);
#local p = vec.x;
#local q = vec.y;
#local r = vec.z;
#local s = vec.t;
< a*p - b*q - c*r - d*s
, a*q + b*p + c*s - d*r
, a*r - b*s + c*p + d*q
, a*s + b*r - c*q + d*p >
#end
/* stereographic projection */
#macro StereographicProjection(q, r)
<q.x,q.y,q.z>/(r-q.t)
#end
/* rotated and projected vertices */
#macro ProjectedVertices(theta, phi, xi, r)
#local out = array[64];
#for(i, 0, 63)
#local out[i] = StereographicProjection(
rotate4d(theta,phi,xi,vertices[i]), r
);
#end
out
#end
/* macro spherical segment */
#macro vlength4(P)
sqrt(P.x*P.x + P.y*P.y + P.z*P.z + P.t*P.t)
#end
#macro sphericalSegment(P, Q, r, n)
#local out = array[n+1];
#for(i, 0, n)
#local pt = P + (i/n)*(Q-P);
#local out[i] = r/vlength4(pt) * pt;
#end
out
#end
/* macro to draw an edge */
#macro Edge(verts, v1, v2, theta, phi, xi, r, Tex)
#local P = verts[v1];
#local Q = verts[v2];
#local PQ = sphericalSegment(P, Q, r, 100);
sphere_sweep {
b_spline 101
#for(k,0,100)
#local O =
StereographicProjection(rotate4d(theta,phi,xi,PQ[k]), r);
O log(1+vlength(O)/4)/2
#end
texture {
Tex
}
}
#end
/* stereographic subdivision (to fill the triangular faces) */
#macro midpoint4(A, B, r)
#local xmid = (A.x + B.x)/2;
#local ymid = (A.y + B.y)/2;
#local zmid = (A.z + B.z)/2;
#local tmid = (A.t + B.t)/2;
#local lg = sqrt(xmid*xmid + ymid*ymid + zmid*zmid + tmid*tmid) / r;
< xmid / lg, ymid / lg, zmid / lg, tmid / lg >
#end
#macro subdiv0(A, B, C, r)
#local mAB = midpoint4(A, B, r);
#local mAC = midpoint4(A, C, r);
#local mBC = midpoint4(B, C, r);
#local trgl1 = array[3] {A, mAB, mAC};
#local trgl2 = array[3] {B, mAB, mBC};
#local trgl3 = array[3] {C, mBC, mAC};
#local trgl4 = array[3] {mAB, mAC, mBC};
array[4] {trgl1, trgl2, trgl3, trgl4}
#end
#macro subdiv(A, B, C, r, depth)
#if(depth=1)
#local out = subdiv0(A, B, C, r);
#else
#local triangles = subdiv(A, B, C, r, depth-1);
#local out = array[pow(4,depth)];
#for(i, 0, pow(4,depth-1)-1)
#local trgl = triangles[i];
#local trgls = subdiv0(trgl[0], trgl[1], trgl[2], r);
#local out[4*i] = trgls[0];
#local out[4*i+1] = trgls[1];
#local out[4*i+2] = trgls[2];
#local out[4*i+3] = trgls[3];
#end
#end
out
#end
/*-------------------------------------------*/
/*----- draw the polychoron ------*/
/*-------------------------------------------*/
#declare theta = pi/2;
#declare phi = 0;
#declare xi = 2*frame_number*pi/180;
#declare r = sqrt(1+3*sqr2p1*sqr2p1);
#declare depth = 5;
#declare vs = ProjectedVertices(theta, phi, xi, r);
#declare stereoTriangles = array[64];
#for(i, 0, 63)
#local triangles4 =
subdiv(
vertices[faces[i][0]],
vertices[faces[i][1]],
vertices[faces[i][2]], r, depth);
#declare stereoTriangles[i] = array[dimension_size(triangles4,1)][3];
#for(j, 0, dimension_size(triangles4,1)-1)
#local trgl4 = triangles4[j];
#declare stereoTriangles[i][j][0] =
StereographicProjection(rotate4d(theta, phi, xi, trgl4[0]), r);
#declare stereoTriangles[i][j][1] =
StereographicProjection(rotate4d(theta, phi, xi, trgl4[1]), r);
#declare stereoTriangles[i][j][2] =
StereographicProjection(rotate4d(theta, phi, xi, trgl4[2]), r);
#end
#end
#declare edgeTexture = texture {
pigment { color DarkPurple }
finish {
ambient .1
diffuse .9
reflection 0
specular 1
metallic
}
};
object {
union {
/* draw edges */
#for(i, 0, 127)
Edge(vertices, edges[i][0], edges[i][1],
theta, phi, xi, r, edgeTexture)
#end
/* draw vertices */
#for(i,0,63)
sphere {
vs[i], vlength(vs[i])/15
texture { edgeTexture }
}
#end
/* fill triangles */
mesh {
#for(i, 0, 63)
#local trgl = stereoTriangles[i];
#for(j, 0, dimension_size(trgl,1)-1)
triangle {
trgl[j][0], trgl[j][1], trgl[j][2]
}
#end
#end
texture {
pigment { Red }
finish {
ambient 0.5
reflection 0
brilliance 4
}
}
}
}
scale 0.5
translate <-8, 6, -25>
}