知乎上的帖子,说MATLAB画的shit都比Python好看:

我想可以翻译成R来画一下,先照着写一个产生数据的函数:

shitshape <- function(points = 400, circle_pts = 40) {
  linspace <- function(a, b, n) seq(a, b, length.out = n)
 
  cross3 <- function(a, b) {
    c(
      a[2] * b[3] - a[3] * b[2],
      a[3] * b[1] - a[1] * b[3],
      a[1] * b[2] - a[2] * b[1]
    )
  }
 
  normalize <- function(v) {
    v / sqrt(sum(v * v))
  }
 
  z <- linspace(0, 1, points)
  z[1:50] <- z[1:50] + z[50] * linspace(1, 0, 50)
  z[201:400] <- z[201:400] - 0.12 * linspace(0, 1, 200)
 
  s <- linspace(0, 1, points)
 
  path_radius <- linspace(0.7, 0.02, points) * (cos(linspace(0, pi / 2, points)) + 1.5) / 3.5
 
  tube_radius <- 0.18 * (1 - s^2.5) + 0.001
  tube_radius[1:50] <- sin(linspace(0, pi / 2, 50)) * tube_radius[50]
 
  theta <- linspace(0, 2 * pi * 4, points)
 
  circle_theta <- linspace(0, 2 * pi, circle_pts)
 
  X <- matrix(0, nrow = circle_pts, ncol = points)
  Y <- matrix(0, nrow = circle_pts, ncol = points)
  Z <- matrix(0, nrow = circle_pts, ncol = points)
 
  for (i in 1:(points - 1)) {
    R <- path_radius[i]
    center_x <- R * cos(theta[i])
    center_y <- R * sin(theta[i])
    center_z <- z[i]
 
    dx <- path_radius[i + 1] * cos(theta[i + 1]) - center_x
    dy <- path_radius[i + 1] * sin(theta[i + 1]) - center_y
    dz <- z[i + 1] - center_z
    tangent <- normalize(c(dx, dy, dz))
 
    ref <- c(0, 0, 1)
    if (abs(sum(ref * tangent)) > 0.99) ref <- c(1, 0, 0)
 
    normal1 <- normalize(cross3(tangent, ref))
    normal2 <- cross3(tangent, normal1)
 
    r_tube <- tube_radius[i]
    for (j in 1:circle_pts) {
      offset <- r_tube * (cos(circle_theta[j]) * normal1 + sin(circle_theta[j]) * normal2)
      X[j, i] <- center_x + offset[1]
      Y[j, i] <- center_y + offset[2]
      Z[j, i] <- center_z + offset[3]
    }
  }
 
  Z[, points] <- Z[, points - 1]
 
  list(X = X, Y = Y, Z = Z)
}

然后就是3D画图了:

library(rgl)
 
xyz <- shitshape()
 
open3d()
bg3d("white")
surface3d(xyz$X, xyz$Y, xyz$Z, color = rgb(0.4, 0.2, 0), lit = TRUE)
aspect3d(1, 1, 1)
light3d(theta = -60, phi = 10)
view3d(theta = -60, phi = 10)

确实是比Python的好看。

评论想看进阶版本,我翻一下,这不就是MATLAB的原创作者嘛,必须安排上。

把他/她的MATLAB 版本给翻译成R,结构与参数基本一一对应,MATLAB我还是用过的,虽然经历比较短

library(rgl)
 
rot_x <- function(theta) {
  matrix(
    c(
      1, 0, 0,
      0, cos(theta), -sin(theta),
      0, sin(theta), cos(theta)
    ),
    nrow = 3,
    byrow = TRUE
  )
}
 
rot_y <- function(theta) {
  matrix(
    c(
      cos(theta), 0, -sin(theta),
      0, 1, 0,
      sin(theta), 0, cos(theta)
    ),
    nrow = 3,
    byrow = TRUE
  )
}
 
rot_z <- function(theta) {
  matrix(
    c(
      cos(theta), -sin(theta), 0,
      sin(theta), cos(theta), 0,
      0, 0, 1
    ),
    nrow = 3,
    byrow = TRUE
  )
}
 
rotate_xyz <- function(X, Y, Z, R) {
  pts <- cbind(as.vector(X), as.vector(Y), as.vector(Z))
  npts <- pts %*% R
  list(
    X = matrix(npts[, 1], nrow = nrow(X), ncol = ncol(X)),
    Y = matrix(npts[, 2], nrow = nrow(Y), ncol = ncol(Y)),
    Z = matrix(npts[, 3], nrow = nrow(Z), ncol = ncol(Z))
  )
}
 
rotate_xyz_col <- function(X, Y, Z, R) {
  pts <- cbind(as.vector(X), as.vector(Y), as.vector(Z))
  npts <- t(R %*% t(pts))
  list(
    X = matrix(npts[, 1], nrow = nrow(X), ncol = ncol(X)),
    Y = matrix(npts[, 2], nrow = nrow(Y), ncol = ncol(Y)),
    Z = matrix(npts[, 3], nrow = nrow(Z), ncol = ncol(Z))
  )
}
 
bezier_curve <- function(ctrl, n = 50) {
  t <- seq(0, 1, length.out = n)
  p <- nrow(ctrl) - 1
  basis <- vapply(
    0:p,
    function(k) choose(p, k) * (t^k) * ((1 - t)^(p - k)),
    numeric(length(t))
  )
  basis %*% ctrl
}
 
draw_straw <- function(X, Y, Z, color = rgb(88, 130, 126, maxColorValue = 255), lwd = 2) {
  idx <- which(Z == min(Z, na.rm = TRUE), arr.ind = TRUE)[1, ]
  x1 <- X[idx[1], idx[2]]
  y1 <- Y[idx[1], idx[2]]
  z1 <- Z[idx[1], idx[2]] + 0.03
 
  ctrl <- rbind(
    c(x1, y1, z1),
    c(0, 0, -0.7),
    c((x1 * cos(pi / 3) - y1 * sin(pi / 3)) / 3, (y1 * cos(pi / 3) + x1 * sin(pi / 3)) / 3, -1.5)
  )
 
  p <- bezier_curve(ctrl, n = 50)
  lines3d(p[, 1], p[, 2], p[, 3], color = color, lwd = lwd)
}
 
set_color_by_h <- function(H, c_list, n = 256) {
  hmin <- min(H, na.rm = TRUE)
  hmax <- max(H, na.rm = TRUE)
 
  base_cols <- grDevices::rgb(c_list[, 1], c_list[, 2], c_list[, 3])
  pal <- grDevices::colorRampPalette(base_cols)(n)
 
  if (!is.finite(hmin) || !is.finite(hmax) || hmax <= hmin) {
    return(matrix(pal[n], nrow = nrow(H), ncol = ncol(H)))
  }
 
  x <- (H - hmin) / (hmax - hmin)
  idx <- pmin(n, pmax(1, floor(x * (n - 1)) + 1))
  matrix(pal[as.vector(idx)], nrow = nrow(H), ncol = ncol(H))
}
 
make_waffle_cone <- function(N = 500, n = 30) {
  xs <- seq(-1, 1, length.out = N)
  ys <- seq(-1, 1, length.out = N)
  X <- matrix(rep(xs, each = N), nrow = N, ncol = N)
  Y <- matrix(rep(ys, times = N), nrow = N, ncol = N)
  Z <- X * 0
 
  for (k in 1:2) {
    Z[seq(k, N, by = n), ] <- 0.01
    Z[, seq(k, N, by = n)] <- 0.01
  }
 
  Z[X^2 + Y^2 > 1] <- NA_real_
 
  R_x <- rot_x(-pi / 2.9)
  nXYZ <- cbind(as.vector(X), as.vector(Y), as.vector(Z)) %*% R_x
 
  nX <- nXYZ[, 1]
  nY <- nXYZ[, 2]
  nZ <- nXYZ[, 3]
 
  nZ <- nZ - min(nZ, na.rm = TRUE)
  nY <- nY - min(nY, na.rm = TRUE)
 
  denom <- pmax(nZ / 2.5, 1e-6)
  nT <- nX / denom
  nR <- nY
 
  nX2 <- cos(nT) * nR
  nY2 <- sin(nT) * nR
 
  list(
    X = matrix(nX2, nrow = N, ncol = N),
    Y = matrix(nY2, nrow = N, ncol = N),
    Z = matrix(nZ, nrow = N, ncol = N)
  )
}
 
make_cylinder <- function(radius = 0.04, segments = 100, height = 1) {
  theta <- seq(0, 2 * pi, length.out = segments + 1)
  X <- outer(cos(theta), c(radius, radius))
  Y <- outer(sin(theta), c(radius, radius))
  Z <- outer(rep(1, length(theta)), c(0, height))
  list(X = X, Y = Y, Z = Z)
}
 
poop2 <- function() {
  open3d()
  bg3d("white")
  material3d(specular = "black")
 
  poop <- shitshape()
  surface3d(poop$X + 0.1, poop$Y, poop$Z + 1.06, color = rgb(0.4, 0.2, 0), lit = TRUE)
  surface3d(poop$X / 1.3, poop$Y / 1.3, -poop$Z + 1.1, color = rgb(0.4, 0.2, 0), lit = TRUE)
 
  cone <- make_waffle_cone(N = 500, n = 30)
  surface3d(cone$X, cone$Y, cone$Z, color = rgb(228, 200, 142, maxColorValue = 255), lit = TRUE)
 
  cyl <- make_cylinder(radius = 0.04, segments = 100, height = 1)
 
  r1 <- rotate_xyz(cyl$X, cyl$Y, cyl$Z, rot_x(pi / 5))
  surface3d(r1$X, r1$Y, r1$Z + 1.3, color = rgb(0.8, 0.6, 0.4), lit = TRUE)
 
  r2 <- rotate_xyz(cyl$X, cyl$Y, cyl$Z, rot_x(pi / 6))
  r2 <- rotate_xyz(r2$X, r2$Y, r2$Z, rot_y(pi / 7))
  surface3d(r2$X, r2$Y, r2$Z + 1.2, color = rgb(0.8, 0.6, 0.4), lit = TRUE)
 
  aspect3d(1, 1, 1)
  light3d(theta = -60, phi = 10)
  view3d(theta = -60, phi = 10)
}
 
poop3 <- function() {
  open3d()
  bg3d("white")
  material3d(specular = "black")
 
  a <- shitshape()
  Xa <- a$X
  Ya <- a$Y
  Za <- a$Z
 
  rb <- seq(0, 1, by = 0.01)
  tb <- seq(0, 2, length.out = 151)
  f <- (abs(1 - ((tb * 5) %% 2)) / 2) + 0.3
  wb <- outer(rb, f, "*")
 
  xb <- wb * matrix(cos(pi * tb), nrow = length(rb), ncol = length(tb), byrow = TRUE)
  yb <- wb * matrix(sin(pi * tb), nrow = length(rb), ncol = length(tb), byrow = TRUE)
  Zb <- (-cos(pi * wb * 1.2) + 1)^0.2
 
  color_list <- rbind(
    c(1.00, 0.92, 0.96),
    c(1.00, 0.76, 0.90),
    c(1.00, 0.60, 0.84),
    c(0.98, 0.44, 0.76),
    c(0.90, 0.26, 0.62),
    c(0.78, 0.12, 0.48)
  )
 
  col_b <- set_color_by_h(Zb, color_list)
 
  yaw_z <- 72 * pi / 180
  roll_x_1 <- pi / 8
  roll_x_2 <- pi / 9
 
  R_z_2 <- rot_z(yaw_z)
  R_z_1 <- rot_z(yaw_z / 2)
  R_z_3 <- rot_z(yaw_z / 3)
  R_x_1 <- rot_x(roll_x_1)
  R_x_2 <- rot_x(roll_x_2)
 
  surface3d(Xa, Ya, Za + 0.7, color = rgb(0.4, 0.2, 0), lit = TRUE)
 
  r <- rotate_xyz_col(Xa, Ya, Za + 0.7, R_x_1)
  r$Y <- r$Y - 0.4
  surface3d(r$X, r$Y, r$Z - 0.1, color = rgb(0.4, 0.2, 0), lit = TRUE)
  draw_straw(r$X, r$Y, r$Z - 0.1)
 
  for (k in 1:4) {
    r <- rotate_xyz_col(r$X, r$Y, r$Z, R_z_2)
    surface3d(r$X, r$Y, r$Z - 0.1, color = rgb(0.4, 0.2, 0), lit = TRUE)
    draw_straw(r$X, r$Y, r$Z - 0.1)
  }
 
  b <- rotate_xyz_col(xb / 2.5, yb / 2.5, Zb / 2.5 + 0.32, R_x_2)
  b$Y <- b$Y - 1.35
  for (k in 1:5) {
    b <- rotate_xyz_col(b$X, b$Y, b$Z, R_z_2)
    surface3d(b$X, b$Y, b$Z, color = as.vector(col_b), lit = TRUE)
    draw_straw(b$X, b$Y, b$Z)
  }
 
  b <- rotate_xyz_col(xb / 2.5, yb / 2.5, Zb / 2.5 + 0.32, R_x_2)
  b$Y <- b$Y - 1.15
  b <- rotate_xyz_col(b$X, b$Y, b$Z, R_z_1)
  for (k in 1:5) {
    b <- rotate_xyz_col(b$X, b$Y, b$Z, R_z_2)
    surface3d(b$X, b$Y, b$Z, color = as.vector(col_b), lit = TRUE)
    draw_straw(b$X, b$Y, b$Z)
  }
 
  b <- rotate_xyz_col(xb / 2.5, yb / 2.5, Zb / 2.5 + 0.32, R_x_2)
  b$Y <- b$Y - 1.25
  b <- rotate_xyz_col(b$X, b$Y, b$Z, R_z_3)
  for (k in 1:5) {
    b <- rotate_xyz_col(b$X, b$Y, b$Z, R_z_2)
    surface3d(b$X, b$Y, b$Z, color = as.vector(col_b), lit = TRUE)
    draw_straw(b$X, b$Y, b$Z)
  }
 
  b <- rotate_xyz_col(xb / 2.5, yb / 2.5, Zb / 2.5 + 0.32, R_x_2)
  b$Y <- b$Y - 1.25
  b <- rotate_xyz_col(b$X, b$Y, b$Z, R_z_3)
  b <- rotate_xyz_col(b$X, b$Y, b$Z, R_z_3)
  for (k in 1:5) {
    b <- rotate_xyz_col(b$X, b$Y, b$Z, R_z_2)
    surface3d(b$X, b$Y, b$Z, color = as.vector(col_b), lit = TRUE)
    draw_straw(b$X, b$Y, b$Z)
  }
 
  aspect3d(1, 1, 1)
  light3d(theta = -15, phi = 35)
  light3d(theta = 120, phi = 20)
  view3d(theta = -15, phi = 35)
}

我把shit这个词改成用poop,毕竟shit是脏话,poop就不是了,是幼儿友好的,等同于中文的粑粑

我们只要运行一个poop2()就可以出来巧克力味的冰激凌了,还带两饼干🍪:

然后运行poop3(),就能够得到花朵簇拥的一堆粑粑:

参考资料