fogón

← Medieval3D assetfogon/torre-con-almenas

Torre con almenas

by fogonCC013328 blocks
Torre con almenasDrag to turn it
// «Torre con almenas» · gpt-6-sol (medium)
// A single tall round stone watchtower, about twice as tall as wide: warm grey stone blocks in several shades, a slightly wider base, a projecting ring of stone corbels near the top and a crown of crenellations (clearly separated square merlons with gaps between them) on a flat roof. A small arched wooden door at the bottom, three narrow dark arrow slits going up, and a small red banner with a gold stripe hanging on the front.

const cx = 24, cy = 24;
const stone = ["#817e75", "#918d82", "#a19c90", "#aea99b"];
const pale = "#bab4a5";
const dark = "#67675f";

function grain(a, b, c) {
  let n = Math.sin(a * 127.1 + b * 311.7 + c * 74.7) * 43758.5453;
  return n - Math.floor(n);
}

function wallRadius(z) {
  return z <= 4 ? 11 : 9.5;
}

function frontY(x, z) {
  const r = wallRadius(z), dx = x - cx;
  return Math.ceil(cy - Math.sqrt(Math.max(0, r * r - dx * dx)));
}

// Solid, slightly battered stone shaft. Broad patches of color read as blocks.
for (let z = 0; z <= 34; z++) {
  const r = wallRadius(z);
  for (let x = 11; x <= 37; x++) {
    for (let y = 11; y <= 37; y++) {
      const d2 = (x - cx) ** 2 + (y - cy) ** 2;
      if (d2 > r * r) continue;

      const course = Math.floor(z / 4);
      const offset = (course % 2) * 2;
      const g = grain(Math.floor((x + offset) / 4), Math.floor(y / 4), course);
      let color = stone[Math.min(3, Math.floor(g * 4))];

      // Recessed mortar lines, staggered from course to course.
      if (z % 4 === 0) color = "#77756d";
      else if ((x + offset) % 5 === 0 && d2 > (r - 1.5) ** 2)
        color = "#79776e";

      if (z < 12 && d2 > (r - 1.5) ** 2 &&
          grain(x, y, z) > 0.978)
        color = "#6e7962";
      set(x, y, z, color);
    }
  }
}

// The wider foot has a firm plinth and a light finishing course.
for (let z of [0, 1, 4]) {
  for (let x = 12; x <= 36; x++)
    for (let y = 12; y <= 36; y++) {
      const d2 = (x - cx) ** 2 + (y - cy) ** 2;
      if (d2 <= 11.4 ** 2 && d2 >= 9.2 ** 2)
        set(x, y, z, z === 4 ? "#aaa598" : "#77746c");
    }
}

// A ring of individual projecting stone corbels supports the crown.
for (let i = 0; i < 16; i++) {
  const a = 2 * Math.PI * i / 16;
  const x0 = Math.round(cx + Math.cos(a) * 10);
  const y0 = Math.round(cy + Math.sin(a) * 10);
  const x1 = Math.round(cx + Math.cos(a) * 12);
  const y1 = Math.round(cy + Math.sin(a) * 12);
  line(x0, y0, 34, x1, y1, 35, 1, i % 3 ? "#89867c" : "#a5a093");
  set(x1, y1, 35, pale);
}
for (let z = 36; z <= 38; z++)
  for (let x = 11; x <= 37; x++)
    for (let y = 11; y <= 37; y++) {
      const d2 = (x - cx) ** 2 + (y - cy) ** 2;
      if (d2 <= 12 ** 2)
        set(x, y, z, z === 37 ? "#a6a194" :
          stone[Math.floor(grain(Math.floor(x / 4), Math.floor(y / 4), z) * 4)]);
    }

// Flat paved roof, with a low continuous parapet beneath the battlements.
for (let x = 12; x <= 36; x++)
  for (let y = 12; y <= 36; y++) {
    const d = Math.hypot(x - cx, y - cy);
    if (d <= 9.8)
      set(x, y, 39, (x % 4 === 0 || y % 4 === 0) ? "#77776f" : "#99978d");
    if (d >= 9.4 && d <= 12)
      for (let z = 39; z <= 40; z++)
        set(x, y, z, z === 40 ? "#aaa597" : "#858279");
  }

// Twelve square merlons, with unmistakable open gaps between them.
for (let i = 0; i < 12; i++) {
  const a = 2 * Math.PI * i / 12;
  const mx = Math.round(cx + Math.cos(a) * 11);
  const my = Math.round(cy + Math.sin(a) * 11);
  box(mx - 1, my - 1, 41, mx + 1, my + 1, 43,
      i % 3 === 0 ? "#918d83" : "#a39e91");
  box(mx - 1, my - 1, 44, mx + 1, my + 1, 44, pale);
}

// Cut an arched opening into the front, then fit a plank door within it.
function inDoor(x, z) {
  return (z <= 6 && x >= 22 && x <= 26) ||
         (z === 7 && x >= 23 && x <= 25) ||
         (z === 8 && x === 24);
}
for (let z = 0; z <= 8; z++)
  for (let x = 22; x <= 26; x++)
    if (inDoor(x, z))
      for (let y = 10; y <= 18; y++) set(x, y, z, null);

for (let z = 0; z <= 8; z++)
  for (let x = 22; x <= 26; x++)
    if (inDoor(x, z)) {
      const y = frontY(x, z);
      const plank = x % 2 ? "#65412e" : "#785035";
      set(x, y + 1, z, "#362c28");
      set(x, y, z, plank);
      if (z === 2 || z === 5) set(x, y - 1, z, "#302f30");
    }

// Pale dressed-stone jambs and arch voussoirs.
for (let z = 0; z <= 6; z++)
  for (let x of [21, 27])
    set(x, frontY(x, z) - 1, z, z % 3 === 0 ? "#858177" : pale);
for (let x of [22, 26]) set(x, frontY(x, 7) - 1, 7, pale);
for (let x of [23, 25]) set(x, frontY(x, 8) - 1, 8, "#c3bcaa");
set(24, frontY(24, 9) - 1, 9, pale);
for (let z of [2, 5])
  for (let x of [22, 26])
    set(x, frontY(x, z) - 2, z, "#c1ad78");
set(25, frontY(25, 3) - 2, 3, "#d4ad57");

// Three deep, narrow arrow slits climb the front of the tower.
for (let bottom of [13, 21, 29]) {
  for (let z = bottom; z <= bottom + 3; z++) {
    const y = frontY(24, z);
    set(24, y, z, "#242629");
    set(24, y - 1, z, "#303035");
    for (let x of [23, 25])
      set(x, frontY(x, z) - 1, z, "#b1ab9e");
  }
  set(24, frontY(24, bottom - 1) - 1, bottom - 1, "#b7b1a3");
  set(24, frontY(24, bottom + 4) - 1, bottom + 4, "#bab4a6");
}

// A short royal-red banner hangs to the right of the slits.
for (let x = 27; x <= 32; x++) {
  const y = frontY(x, 27) - 2;
  set(x, y, 27, "#49372b"); // timber crossbar
  if (x === 27 || x === 32) set(x, y - 1, 27, "#d2ad60");
}
for (let z = 19; z <= 26; z++)
  for (let x = 28; x <= 31; x++) {
    if (z === 19 && (x === 28 || x === 31)) continue;
    const y = frontY(x, z) - 1;
    set(x, y, z, x === 28 ? "#792b32" : "#a4383c");
    if (z === 24 || x === 31)
      set(x, y - 1, z, "#d4aa58");
  }
for (let x of [29, 30])
  set(x, frontY(x, 18) - 1, 18, "#992f38");