fogón

← Nature3D assetfogon/cactus-saguaro

Cactus saguaro

by fogonCC05943 blocks
Cactus saguaroDrag to turn it
// «Cactus saguaro» · gpt-6-sol (medium)
// A tall saguaro cactus: a thick green column with vertical ribs and two arms bending up, little white spines dotted along it, standing on a patch of sand.

// A sunlit patch of sand.
const sand = ["#e7b96d", "#f1c980", "#f7d897", "#dbab60"];
for (let x = 7; x <= 41; x++) {
  for (let y = 10; y <= 38; y++) {
    const dx = (x - 24) / 16.5;
    const dy = (y - 24) / 13.5;
    const edge = dx * dx + dy * dy + 0.055 * Math.sin(x * 1.8 + y * 0.7);
    if (edge < 1) {
      set(x, y, 0, sand[rand() < 0.16 ? 3 : rand() < 0.45 ? 0 : 1]);
      if (edge < 0.72 && rand() > 0.16) {
        set(x, y, 1, sand[rand() < 0.12 ? 0 : 2]);
      }
      if (edge < 0.52 && rand() < 0.035) set(x, y, 2, "#f9e1ab");
    }
  }
}

// Green shades are shared by the trunk and its two arms.
const green = ["#218b49", "#2ca855", "#35bd5d", "#49cd6b", "#61d778"];
function cactusColor(angle, z) {
  const rib = Math.cos(angle * 8);
  if (rib < -0.67) return green[0];        // Recess between ribs
  if (rib > 0.72) return z > 29 ? green[4] : green[3];
  if (Math.sin(angle) < -0.35) return green[2]; // Sunlit front
  return green[1];
}

// Thick main column with a gently domed crown and eight vertical ribs.
for (let z = 2; z <= 43; z++) {
  let radius = 5.4;
  if (z > 38) radius *= Math.sqrt(Math.max(0, 1 - ((z - 38) / 5.4) ** 2));
  for (let x = 17; x <= 31; x++) {
    for (let y = 17; y <= 31; y++) {
      const dx = x - 24, dy = y - 24;
      const angle = Math.atan2(dy, dx);
      const ribbedRadius = radius + 0.28 * Math.cos(angle * 8);
      if (dx * dx + dy * dy <= ribbedRadius * ribbedRadius) {
        set(x, y, z, cactusColor(angle, z));
      }
    }
  }
}

// Round, solid arms: each grows outward, elbows upward, and ends in a dome.
function arm(points, radius) {
  for (let i = 0; i < points.length - 1; i++) {
    const a = points[i], b = points[i + 1];
    const vx = b[0] - a[0], vy = b[1] - a[1], vz = b[2] - a[2];
    const length2 = vx * vx + vy * vy + vz * vz;
    for (let x = Math.floor(Math.min(a[0], b[0]) - radius); x <= Math.ceil(Math.max(a[0], b[0]) + radius); x++) {
      for (let y = Math.floor(Math.min(a[1], b[1]) - radius); y <= Math.ceil(Math.max(a[1], b[1]) + radius); y++) {
        for (let z = Math.floor(Math.min(a[2], b[2]) - radius); z <= Math.ceil(Math.max(a[2], b[2]) + radius); z++) {
          const t = Math.max(0, Math.min(1,
            ((x - a[0]) * vx + (y - a[1]) * vy + (z - a[2]) * vz) / length2));
          const px = a[0] + t * vx, py = a[1] + t * vy, pz = a[2] + t * vz;
          if ((x - px) ** 2 + (y - py) ** 2 + (z - pz) ** 2 <= radius * radius) {
            const angle = Math.atan2(y - py, x - px);
            set(x, y, z, cactusColor(angle, z));
          }
        }
      }
    }
  }
}
arm([[21, 24, 25], [17, 24, 25], [14, 24, 27], [13, 24, 30], [13, 24, 35]], 2.9);
arm([[27, 24, 18], [31, 24, 18], [35, 24, 21], [36, 24, 25], [36, 24, 31]], 3.0);

// Tiny ivory spines on exposed cactus surfaces, especially its visible front.
const spineSites = [];
forEach((x, y, z, color) => {
  if (!green.includes(color) || z < 4) return;
  const outward = [
    [0, -1, 0], [-1, 0, 0], [1, 0, 0],
    [0, 1, 0], [0, 0, 1]
  ].filter(([dx, dy, dz]) => get(x + dx, y + dy, z + dz) === undefined);
  if (outward.length && rand() < (y <= 24 ? 0.105 : 0.055)) {
    spineSites.push([x, y, z, outward[Math.floor(rand() * outward.length)]]);
  }
});
for (const [x, y, z, [dx, dy, dz]] of spineSites) {
  if (get(x + dx, y + dy, z + dz) === undefined) {
    set(x + dx, y + dy, z + dz, rand() < 0.28 ? "#fff9da" : "#f5ebc6");
  }
}

// A few rounded pebbles and a cheerful little desert bloom.
ellipsoid(14, 17, 2, 2, 1, 1, "#ada897");
set(14, 16, 2, "#cfcbc0");
ellipsoid(34, 31, 2, 2, 2, 1, "#a6a899");
set(34, 30, 3, "#d1d0bd");
set(29, 15, 2, "#378f45");
set(29, 15, 3, "#43b655");
set(29, 15, 4, "#f39147");
set(28, 15, 4, "#ffc15a");
set(30, 15, 4, "#ffc15a");
set(29, 14, 4, "#ffe077");