Skip to content
Kiln0.10Get Kiln

Panelled wooden case and an exposed clock face

Earlier examples from earlier Kiln versions.

Historical gallery render of Longcase clock; see the poster provenance below.

Drag to orbit after opening. The model starts stationary.

For your engine. The download is a standard glTF 2.0 binary (GLB) with PBR metallic-roughness materials. glTF stores materials, not lighting or tone mapping, so your engine decides how they read. This 3D view tone-maps with Review Neutral, the Khronos PBR Neutral construction with a smaller glare offset (0.015 instead of 0.04), at exposure 0.9. Its Tone mapping control also shows ACES and Linear, for comparison.

Build measurements

Triangles
12,600
Estimated draws
251
Materials
7
Textures
0
Animation clips
1
Bounds X × Y × Z
0.35 × 2.44 × 0.56 m
Build warnings
0

Measurements come from this build.

Download this build

Runtime: 260,640 bytes. Original: 260,412 bytes.

These GLBs are build outputs of the MIT-licensed example source; no separate terms are stated for the builds.

Runtime provenance metadata
SHA-256 download hashes
Runtime GLB
0961c47d88883f8e51de140bcf6e43a8756406d5327491f85d48c584ae735ce4
Original GLB
eae5f93769ffb4f12fdf7f3caa04d1c24db25955262574100aa3ca41d533586d
Source
4fcc5a79caaf3533966314a92883e374aabff6c8b2a254c8cfec3dc5ceb4c93c

The example source is part of the Kiln repository. Repository licence: MIT.

These GLBs are build outputs of the MIT-licensed example source; no separate terms are stated for the builds.

Recorded authorship

Gemini 3.8 Flash through Antigravity CLI (agy)

Source-header credit

Source access
Not recorded
Inherited context
Not independently recorded; source-header declarations only
Starting example
Not recorded
Human input
Header declares no hand-authored source; other intervention not recorded
Authoring review
Six-view review declared; renderer fidelity not recorded

Gallery GPU render of this exact source. Source, artifact, image hashes and camera settings are recorded alongside the poster; the artifact hash names the GLB bytes the image was rendered from, which the downloadable rebuild reproduces byte for byte only on the platform that recorded it.

Poster camera and render record

The source behind this build

longcase-clock.kiln.js
// Authored by: gemini-3.8-flash-high, via agy.
//
// Written by the model itself through the Kiln MCP tools: it wrote the
// program, rendered it, looked at its own six-view contact sheet, and
// revised. Not a line of it is hand-authored.
//
// Refined later, in this repository, with `kiln_edit`: the hood's break-arch
// was rebuilt from segments, replacing a full torus that read as a circle
// floating over the dial.
// The attribution above is for the authoring run, which had none of this in
// scope. Both passes went through the same tools; only the second one could
// see the gallery it was going into.

const meta = { name: 'LongcaseClock', category: 'prop' };

function build() {
  const root = createRoot('LongcaseClock');

  // =========================================================================
  // Materials Palette (Cohesive, low draw-calls, high instanceability grade)
  // =========================================================================
  const walnutDark = gameMaterial(0x361d10, { roughness: 0.72, metalness: 0.04 }); // Solid English walnut case & moldings
  const walnutBurl = gameMaterial(0x542d16, { roughness: 0.60, metalness: 0.04 }); // Bookmatched burl veneer panels
  const brass = gameMaterial(0xd8ad36, { roughness: 0.22, metalness: 0.90 });      // Polished brass finials, pendulum, weights
  const dialFace = gameMaterial(0xfbf6e8, { roughness: 0.88, metalness: 0.0 });    // Antique cream painted enamel dial face
  const dialBlack = gameMaterial(0x141414, { roughness: 0.85, metalness: 0.1 });   // Enamel black numerals, tracks, hands
  const dialMoon = gameMaterial(0x122244, { roughness: 0.68, metalness: 0.12 });   // Prussian midnight blue lunar arch
  const dialGold = gameMaterial(0xdfb43a, { roughness: 0.25, metalness: 0.85 });   // Painted gold stars and moon face

  // =========================================================================
  // 1. Plinth Base & Bracket Feet (Sitting on Y = 0)
  // =========================================================================
  // 4 Carved Ogee Bracket Feet resting on the floor (Y: 0.00 to 0.09)
  const footPositions = [
    [0.12, 0.045, -0.22],  // Front-Left
    [0.12, 0.045, 0.22],   // Front-Right
    [-0.12, 0.045, -0.22], // Back-Left
    [-0.12, 0.045, 0.22],  // Back-Right
  ];
  for (let i = 0; i < footPositions.length; i++) {
    const [fx, fy, fz] = footPositions[i];
    createPart('Foot_' + i, boxGeo(0.09, 0.09, 0.09), walnutDark, {
      position: [fx, fy, fz],
      parent: root,
    });
    createPart('FootWing_' + i, cylinderGeo(0.04, 0.02, 0.09, 10), walnutDark, {
      position: [fx, fy, fz],
      rotation: [0, 45, 0],
      parent: root,
    });
  }

  // Base Plinth Lower Step (Y: 0.09 to 0.15, size: 0.34 x 0.06 x 0.54)
  createPart('PlinthLowerStep', boxGeo(0.34, 0.06, 0.54), walnutDark, {
    position: [0, 0.12, 0],
    parent: root,
  });

  // Plinth Stepped Cyma Molding (Y: 0.15 to 0.18)
  createPart('PlinthMolding1', boxGeo(0.32, 0.03, 0.52), walnutDark, {
    position: [0, 0.165, 0],
    parent: root,
  });

  // Base Pedestal Carcass (Y: 0.18 to 0.52, size: 0.30 x 0.34 x 0.48)
  createPart('BasePedestal', boxGeo(0.30, 0.34, 0.48), walnutDark, {
    position: [0, 0.35, 0],
    parent: root,
  });

  // Front Inset Burl Walnut Panel on Base (X = +0.152)
  createPart('BasePanelBurlField', boxGeo(0.008, 0.26, 0.40), walnutBurl, {
    position: [0.152, 0.35, 0],
    parent: root,
  });
  // Stepped molding framing the front panel
  createPart('BasePanelBorderTop', boxGeo(0.014, 0.016, 0.41), walnutDark, {
    position: [0.154, 0.48, 0],
    parent: root,
  });
  createPart('BasePanelBorderBottom', boxGeo(0.014, 0.016, 0.41), walnutDark, {
    position: [0.154, 0.22, 0],
    parent: root,
  });
  createPart('BasePanelBorderLeft', boxGeo(0.014, 0.276, 0.016), walnutDark, {
    position: [0.154, 0.35, -0.20],
    parent: root,
  });
  createPart('BasePanelBorderRight', boxGeo(0.014, 0.276, 0.016), walnutDark, {
    position: [0.154, 0.35, 0.20],
    parent: root,
  });
  // Brass corner inlays on base panel
  const baseCornerOffsets = [[0.12, -0.19], [0.12, 0.19], [-0.12, -0.19], [-0.12, 0.19]];
  for (let bc = 0; bc < 4; bc++) {
    const [bcy, bcz] = baseCornerOffsets[bc];
    createPart('BaseCornerInlay_' + bc, sphereGeo(0.006, 8, 6), brass, {
      position: [0.156, 0.35 + bcy, bcz],
      parent: root,
    });
  }

  // Base-to-Waist Transition Moldings (Throat/Waist Rail, Y: 0.52 to 0.58)
  createPart('WaistTransition1', boxGeo(0.28, 0.03, 0.44), walnutDark, {
    position: [0, 0.535, 0],
    parent: root,
  });
  createPart('WaistTransition2', boxGeo(0.25, 0.03, 0.40), walnutDark, {
    position: [0, 0.565, 0],
    parent: root,
  });

  // =========================================================================
  // 2. Trunk / Waist Section (Y: 0.58 to 1.48)
  // =========================================================================
  // Waist Back Panel (X = -0.10)
  createPart('WaistBack', boxGeo(0.02, 0.90, 0.36), walnutDark, {
    position: [-0.10, 1.03, 0],
    parent: root,
  });
  // Waist Left Wall (Z = -0.17)
  createPart('WaistWallLeft', boxGeo(0.20, 0.90, 0.02), walnutDark, {
    position: [0.0, 1.03, -0.17],
    parent: root,
  });
  // Waist Right Wall (Z = +0.17)
  createPart('WaistWallRight', boxGeo(0.20, 0.90, 0.02), walnutDark, {
    position: [0.0, 1.03, 0.17],
    parent: root,
  });
  // Waist Floor Board
  createPart('WaistFloor', boxGeo(0.20, 0.02, 0.34), walnutDark, {
    position: [0.0, 0.59, 0],
    parent: root,
  });
  // Waist Ceiling Board
  createPart('WaistCeiling', boxGeo(0.20, 0.02, 0.34), walnutDark, {
    position: [0.0, 1.47, 0],
    parent: root,
  });

  // Fluted Quarter-Columns on the front corners of the waist (Z = ±0.165, X = +0.095)
  const colZOffsets = [-0.165, 0.165];
  for (let c = 0; c < 2; c++) {
    const cz = colZOffsets[c];
    const prefix = c === 0 ? 'ColL_' : 'ColR_';

    // Turned brass base
    createPart(prefix + 'BrassBase', cylinderGeo(0.022, 0.026, 0.035, 14), brass, {
      position: [0.095, 0.605, cz],
      parent: root,
    });
    createPart(prefix + 'BaseTorus', torusGeo(0.023, 0.005, 6, 14), brass, {
      position: [0.095, 0.622, cz],
      rotation: [90, 0, 0],
      parent: root,
    });

    // Column wooden shaft
    createPart(prefix + 'WoodShaft', cylinderGeo(0.017, 0.018, 0.80, 14), walnutDark, {
      position: [0.095, 1.035, cz],
      parent: root,
    });

    // Reeded fluting ribs (6 vertical ribs)
    for (let r = 0; r < 6; r++) {
      const angle = (r * Math.PI) / 3;
      const rx = 0.095 + 0.0175 * Math.cos(angle);
      const rz = cz + 0.0175 * Math.sin(angle);
      createPart(prefix + 'Reed_' + r, cylinderGeo(0.0035, 0.0035, 0.76, 6), walnutBurl, {
        position: [rx, 1.035, rz],
        parent: root,
      });
    }

    // Turned brass capital
    createPart(prefix + 'BrassCapNeck', cylinderGeo(0.018, 0.017, 0.025, 14), brass, {
      position: [0.095, 1.445, cz],
      parent: root,
    });
    createPart(prefix + 'BrassCapCrown', cylinderGeo(0.026, 0.019, 0.025, 14), brass, {
      position: [0.095, 1.465, cz],
      parent: root,
    });
  }

  // Long Waist Door Frame (Front X = +0.10, clear glazed aperture)
  // Left Stile
  createPart('DoorStileLeft', boxGeo(0.02, 0.86, 0.035), walnutDark, {
    position: [0.10, 1.03, -0.13],
    parent: root,
  });
  // Right Stile
  createPart('DoorStileRight', boxGeo(0.02, 0.86, 0.035), walnutDark, {
    position: [0.10, 1.03, 0.13],
    parent: root,
  });
  // Bottom Rail
  createPart('DoorRailBottom', boxGeo(0.02, 0.045, 0.225), walnutDark, {
    position: [0.10, 0.62, 0],
    parent: root,
  });
  // Top Arched Rail
  createPart('DoorRailTop', boxGeo(0.02, 0.045, 0.225), walnutDark, {
    position: [0.10, 1.44, 0],
    parent: root,
  });
  // Inner beaded molding around door aperture
  createPart('DoorBeadBottom', cylinderGeo(0.004, 0.004, 0.22, 8), brass, {
    position: [0.105, 0.645, 0],
    rotation: [90, 0, 0],
    parent: root,
  });
  createPart('DoorBeadLeft', cylinderGeo(0.004, 0.004, 0.77, 8), brass, {
    position: [0.105, 1.03, -0.11],
    parent: root,
  });
  createPart('DoorBeadRight', cylinderGeo(0.004, 0.004, 0.77, 8), brass, {
    position: [0.105, 1.03, 0.11],
    parent: root,
  });

  // Brass Hinges and Keyhole Escutcheon
  createPart('HingeTop', cylinderGeo(0.005, 0.005, 0.04, 10), brass, {
    position: [0.11, 1.32, -0.148],
    parent: root,
  });
  createPart('HingeBottom', cylinderGeo(0.005, 0.005, 0.04, 10), brass, {
    position: [0.11, 0.74, -0.148],
    parent: root,
  });
  createPart('EscutcheonPlate', boxGeo(0.004, 0.04, 0.022), brass, {
    position: [0.112, 1.03, 0.12],
    parent: root,
  });
  createPart('KeyholeHole', cylinderGeo(0.003, 0.003, 0.008, 6), dialBlack, {
    position: [0.113, 1.03, 0.12],
    rotation: [0, 0, 90],
    parent: root,
  });

  // =========================================================================
  // 3. Interior Clockwork (Drive Weights + Swinging Pendulum)
  // =========================================================================
  // Two Brass Cylindrical Weights (Suspended from movement)
  const weightConfigs = [
    { name: 'WeightTime', x: 0.02, y: 1.15, z: -0.07 },   // Left weight
    { name: 'WeightStrike', x: 0.02, y: 1.05, z: 0.07 },  // Right weight
  ];
  for (const wc of weightConfigs) {
    createPart(wc.name + '_Body', cylinderGeo(0.023, 0.023, 0.16, 16), brass, {
      position: [wc.x, wc.y, wc.z],
      parent: root,
    });
    createPart(wc.name + '_TopCap', sphereGeo(0.023, 12, 8), brass, {
      position: [wc.x, wc.y + 0.08, wc.z],
      scale: [1, 0.5, 1],
      parent: root,
    });
    createPart(wc.name + '_BottomTip', coneGeo(0.023, 0.035, 12), brass, {
      position: [wc.x, wc.y - 0.095, wc.z],
      rotation: [180, 0, 0],
      parent: root,
    });
    createPart(wc.name + '_Pulley', torusGeo(0.011, 0.004, 6, 12), brass, {
      position: [wc.x, wc.y + 0.10, wc.z],
      rotation: [0, 90, 0],
      parent: root,
    });
    const chainLen = 1.47 - (wc.y + 0.11);
    createPart(wc.name + '_Chain', cylinderGeo(0.002, 0.002, chainLen, 6), brass, {
      position: [wc.x, wc.y + 0.11 + chainLen / 2, wc.z],
      parent: root,
    });
  }

  // =========================================================================
  // 4. Swinging Pendulum Assembly (Joint_Pendulum)
  // =========================================================================
  const pendulumPivotY = 1.44;
  const pendulumJoint = createPivot('Pendulum', [0.0, pendulumPivotY, 0.0], root);

  // Top suspension block (local coords)
  createPart('PendulumTopBlock', boxGeo(0.016, 0.025, 0.012), brass, {
    position: [0, -0.0125, 0],
    parent: pendulumJoint,
  });

  // Polished brass pendulum rod (length 0.65m)
  const rodLength = 0.65;
  createPart('PendulumRod', cylinderGeo(0.005, 0.005, rodLength, 10), brass, {
    position: [0, -rodLength / 2, 0],
    parent: pendulumJoint,
  });

  // Heavy Lenticular Brass Bob (Centered at local Y = -0.58)
  const bobY = -0.58;
  createPart('PendulumBobDisc', sphereGeo(0.075, 18, 12), brass, {
    position: [0, bobY, 0],
    scale: [0.28, 1.0, 1.0],
    parent: pendulumJoint,
  });
  createPart('PendulumBobBezel', torusGeo(0.075, 0.008, 8, 20), brass, {
    position: [0, bobY, 0],
    rotation: [0, 90, 0],
    parent: pendulumJoint,
  });
  createPart('PendulumBobRosette', cylinderGeo(0.020, 0.026, 0.010, 12), brass, {
    position: [0.021, bobY, 0],
    rotation: [0, 0, 90],
    parent: pendulumJoint,
  });
  createPart('PendulumBobStar', sphereGeo(0.012, 8, 6), brass, {
    position: [0.026, bobY, 0],
    parent: pendulumJoint,
  });

  // Rating Nut and Bottom Spire
  createPart('PendulumRatingNut', cylinderGeo(0.009, 0.011, 0.020, 8), brass, {
    position: [0, -0.665, 0],
    parent: pendulumJoint,
  });
  createPart('PendulumBottomFinial', coneGeo(0.009, 0.028, 10), brass, {
    position: [0, -0.685, 0],
    rotation: [180, 0, 0],
    parent: pendulumJoint,
  });

  // =========================================================================
  // 5. Throat / Hood Support Moldings (Y: 1.48 to 1.55)
  // =========================================================================
  createPart('ThroatStep1', boxGeo(0.26, 0.02, 0.40), walnutDark, {
    position: [0, 1.49, 0],
    parent: root,
  });
  createPart('ThroatStep2', boxGeo(0.29, 0.02, 0.46), walnutDark, {
    position: [0, 1.51, 0],
    parent: root,
  });
  createPart('ThroatStep3', boxGeo(0.31, 0.02, 0.50), walnutDark, {
    position: [0, 1.53, 0],
    parent: root,
  });

  // =========================================================================
  // 6. Hood / Bonnet Carcass (Y: 1.55 to 2.12)
  // =========================================================================
  // Hood Back Wall
  createPart('HoodBack', boxGeo(0.02, 0.57, 0.48), walnutDark, {
    position: [-0.14, 1.835, 0],
    parent: root,
  });
  // Hood Side Walls with Arched Openings
  const sideZ = [-0.24, 0.24];
  for (let s = 0; s < 2; s++) {
    const sz = sideZ[s];
    const prefix = s === 0 ? 'HoodSideL_' : 'HoodSideR_';

    createPart(prefix + 'Lower', boxGeo(0.28, 0.12, 0.02), walnutDark, {
      position: [0, 1.61, sz],
      parent: root,
    });
    createPart(prefix + 'Upper', boxGeo(0.28, 0.15, 0.02), walnutDark, {
      position: [0, 2.045, sz],
      parent: root,
    });
    createPart(prefix + 'PillarF', boxGeo(0.03, 0.30, 0.02), walnutDark, {
      position: [0.12, 1.82, sz],
      parent: root,
    });
    createPart(prefix + 'PillarB', boxGeo(0.03, 0.30, 0.02), walnutDark, {
      position: [-0.12, 1.82, sz],
      parent: root,
    });
  }

  // Freestanding Turned Hood Columns at front corners (X = +0.14, Z = ±0.235)
  for (let hc = 0; hc < 2; hc++) {
    const hcz = hc === 0 ? -0.235 : 0.235;
    const prefix = hc === 0 ? 'HoodColL_' : 'HoodColR_';

    createPart(prefix + 'BaseBlock', boxGeo(0.045, 0.025, 0.045), walnutDark, {
      position: [0.14, 1.5625, hcz],
      parent: root,
    });
    createPart(prefix + 'BaseBrass', cylinderGeo(0.017, 0.020, 0.025, 14), brass, {
      position: [0.14, 1.585, hcz],
      parent: root,
    });
    createPart(prefix + 'BaseTorus', torusGeo(0.019, 0.0045, 6, 14), brass, {
      position: [0.14, 1.60, hcz],
      rotation: [90, 0, 0],
      parent: root,
    });

    createPart(prefix + 'Shaft', cylinderGeo(0.014, 0.016, 0.44, 14), walnutDark, {
      position: [0.14, 1.83, hcz],
      parent: root,
    });

    createPart(prefix + 'CapBrassNeck', cylinderGeo(0.015, 0.014, 0.02, 14), brass, {
      position: [0.14, 2.06, hcz],
      parent: root,
    });
    createPart(prefix + 'CapBrassCrown', cylinderGeo(0.024, 0.016, 0.025, 14), brass, {
      position: [0.14, 2.0825, hcz],
      parent: root,
    });
    createPart(prefix + 'CapAbacus', boxGeo(0.045, 0.015, 0.045), walnutDark, {
      position: [0.14, 2.1025, hcz],
      parent: root,
    });
  }

  // Hood Arched Door Surround (Front X = +0.145, open aperture framing the dial)
  createPart('HoodDoorStileL', boxGeo(0.02, 0.52, 0.035), walnutDark, {
    position: [0.145, 1.81, -0.20],
    parent: root,
  });
  createPart('HoodDoorStileR', boxGeo(0.02, 0.52, 0.035), walnutDark, {
    position: [0.145, 1.81, 0.20],
    parent: root,
  });
  createPart('HoodDoorRailBottom', boxGeo(0.02, 0.035, 0.38), walnutDark, {
    position: [0.145, 1.5675, 0],
    parent: root,
  });
  // Arched Hood Door Top Arch.
  //
  // This was one full torus, which is a ring and not an arch: its lower half ran
  // straight across the moon lunette and clipped the top of the chapter ring.
  // `torusGeo` takes no arc angle, so the half that is actually wanted is built
  // from segments swept over the top of an ellipse. Each block is turned to the
  // tangent there -- phi = atan2(b cos t, a sin t) -- which is 0 at the crown and
  // a quarter turn at each springing point, so the moulding meets the stiles
  // upright instead of cutting into them.
  const archA = 0.195;
  const archB = 0.145;
  const archY = 1.95;
  const archSegments = 15;
  const geoArchBlock = boxGeo(0.02, 0.036, 0.048);
  for (let i = 0; i < archSegments; i++) {
    const t = (i / (archSegments - 1)) * Math.PI;
    createPart('HoodDoorArch' + i, geoArchBlock, walnutDark, {
      position: [0.145, archY + archB * Math.sin(t), archA * Math.cos(t)],
      rotation: [(Math.atan2(archB * Math.cos(t), archA * Math.sin(t)) * 180) / Math.PI, 0, 0],
      parent: root,
    });
  }
  // Brass door knob
  createPart('HoodDoorKnob', sphereGeo(0.008, 10, 6), brass, {
    position: [0.155, 1.76, 0.19],
    parent: root,
  });

  // =========================================================================
  // 7. The Painted Dial & Movement Assembly (Facing +X)
  // =========================================================================
  const dialCenterX = 0.130;
  const dialCenterY = 1.76;
  const dialCenterZ = 0.0;

  // Solid brass dial backplate (0.36m x 0.36m square)
  createPart('DialPlateSquare', boxGeo(0.008, 0.36, 0.38), brass, {
    position: [dialCenterX - 0.005, dialCenterY, dialCenterZ],
    parent: root,
  });

  // Cast Brass Corner Spandrels in all 4 corners (Rococo scrollwork)
  const spandrelOffsets = [
    [-0.13, -0.13], // Bottom-Left
    [-0.13, 0.13],  // Bottom-Right
    [0.13, -0.13],  // Top-Left
    [0.13, 0.13],   // Top-Right
  ];
  for (let sp = 0; sp < 4; sp++) {
    const [sy, sz] = spandrelOffsets[sp];
    createPart('SpandrelPlate_' + sp, boxGeo(0.005, 0.055, 0.055), brass, {
      position: [dialCenterX + 0.001, dialCenterY + sy, dialCenterZ + sz],
      parent: root,
    });
    createPart('SpandrelScrollA_' + sp, torusGeo(0.024, 0.0045, 6, 10), brass, {
      position: [dialCenterX + 0.003, dialCenterY + sy, dialCenterZ + sz],
      rotation: [0, 90, 0],
      parent: root,
    });
    createPart('SpandrelScrollB_' + sp, torusGeo(0.014, 0.0035, 6, 8), brass, {
      position: [dialCenterX + 0.004, dialCenterY + sy + 0.012, dialCenterZ + sz + 0.012],
      rotation: [0, 90, 45],
      parent: root,
    });
  }

  // Large Painted Cream Dial Face (Radius 0.136m, crisp and prominent)
  createPart('DialPaintedFace', cylinderGeo(0.136, 0.136, 0.008, 24), dialFace, {
    position: [dialCenterX, dialCenterY, dialCenterZ],
    rotation: [0, 0, 90],
    parent: root,
  });

  // Polished Brass Bezel Ring wrapping Chapter Ring
  createPart('DialBrassBezel', torusGeo(0.136, 0.007, 8, 24), brass, {
    position: [dialCenterX + 0.004, dialCenterY, dialCenterZ],
    rotation: [0, 90, 0],
    parent: root,
  });

  // Outer & Inner Minute Tracks (Concentric black enamel rings)
  createPart('DialOuterMinuteTrack', torusGeo(0.128, 0.0018, 4, 24), dialBlack, {
    position: [dialCenterX + 0.005, dialCenterY, dialCenterZ],
    rotation: [0, 90, 0],
    parent: root,
  });
  createPart('DialInnerMinuteTrack', torusGeo(0.122, 0.0018, 4, 24), dialBlack, {
    position: [dialCenterX + 0.005, dialCenterY, dialCenterZ],
    rotation: [0, 90, 0],
    parent: root,
  });
  createPart('DialInnerHourTrack', torusGeo(0.082, 0.0018, 4, 24), dialBlack, {
    position: [dialCenterX + 0.005, dialCenterY, dialCenterZ],
    rotation: [0, 90, 0],
    parent: root,
  });

  // 12 Five-Minute Diamond Markers
  for (let m = 0; m < 12; m++) {
    const angle = (m * 30 * Math.PI) / 180;
    const my = dialCenterY + 0.125 * Math.cos(angle);
    const mz = dialCenterZ + 0.125 * Math.sin(angle);
    createPart('MinutePip_' + m, boxGeo(0.002, 0.0045, 0.0045), dialBlack, {
      position: [dialCenterX + 0.005, my, mz],
      rotation: [45, 0, 0],
      parent: root,
    });
  }

  // 12 Roman Numerals (I through XII)
  const romanHourConfigs = [
    { hour: 12, bars: [{ z: -0.013, rot: 25 }, { z: -0.005, rot: -25 }, { z: 0.004, rot: 0 }, { z: 0.013, rot: 0 }] }, // XII
    { hour: 1,  bars: [{ z: 0.0, rot: 0 }] },                                                                              // I
    { hour: 2,  bars: [{ z: -0.006, rot: 0 }, { z: 0.006, rot: 0 }] },                                                    // II
    { hour: 3,  bars: [{ z: -0.009, rot: 0 }, { z: 0.0, rot: 0 }, { z: 0.009, rot: 0 }] },                                // III
    { hour: 4,  bars: [{ z: -0.010, rot: 0 }, { z: 0.001, rot: -22 }, { z: 0.010, rot: 22 }] },                            // IV
    { hour: 5,  bars: [{ z: -0.006, rot: -22 }, { z: 0.006, rot: 22 }] },                                                 // V
    { hour: 6,  bars: [{ z: -0.009, rot: -22 }, { z: 0.0, rot: 22 }, { z: 0.009, rot: 0 }] },                             // VI
    { hour: 7,  bars: [{ z: -0.013, rot: -22 }, { z: -0.005, rot: 22 }, { z: 0.004, rot: 0 }, { z: 0.013, rot: 0 }] },    // VII
    { hour: 8,  bars: [{ z: -0.017, rot: -22 }, { z: -0.009, rot: 22 }, { z: 0.0, rot: 0 }, { z: 0.008, rot: 0 }, { z: 0.016, rot: 0 }] }, // VIII
    { hour: 9,  bars: [{ z: -0.010, rot: 0 }, { z: 0.001, rot: 25 }, { z: 0.010, rot: -25 }] },                            // IX
    { hour: 10, bars: [{ z: -0.005, rot: 25 }, { z: 0.005, rot: -25 }] },                                                 // X
    { hour: 11, bars: [{ z: -0.011, rot: 25 }, { z: -0.003, rot: -25 }, { z: 0.008, rot: 0 }] },                           // XI
  ];
  const numeralRadius = 0.102;
  for (const nc of romanHourConfigs) {
    const hourRad = ((nc.hour % 12) * 30 * Math.PI) / 180;
    const cy = dialCenterY + numeralRadius * Math.cos(hourRad);
    const cz = dialCenterZ + numeralRadius * Math.sin(hourRad);
    const numeralRotX = -(nc.hour % 12) * 30;

    for (let b = 0; b < nc.bars.length; b++) {
      const bar = nc.bars[b];
      createPart('Num_' + nc.hour + '_' + b, boxGeo(0.002, 0.020, 0.004), dialBlack, {
        position: [dialCenterX + 0.005, cy, cz + bar.z],
        rotation: [numeralRotX + bar.rot, 0, 0],
        parent: root,
      });
    }
  }

  // Painted Moon Phase Lunette (Rolling Moon Arch)
  const moonArchY = 1.99;
  createPart('MoonArchBackdrop', cylinderGeo(0.12, 0.12, 0.006, 20), dialMoon, {
    position: [dialCenterX + 0.001, moonArchY, 0],
    rotation: [0, 0, 90],
    scale: [1, 0.70, 1],
    parent: root,
  });
  // Golden Moon Face (smiling disc)
  createPart('MoonFaceGold', sphereGeo(0.042, 14, 10), dialGold, {
    position: [dialCenterX + 0.004, moonArchY + 0.01, 0],
    scale: [0.15, 1, 1],
    parent: root,
  });
  // Painted Golden Stars in the night sky
  const starCoords = [
    [0.05, -0.06], [0.04, 0.065], [0.06, 0.02],
    [-0.015, -0.075], [-0.025, 0.08], [0.025, -0.025],
    [0.01, 0.04], [0.03, -0.07],
  ];
  for (let st = 0; st < starCoords.length; st++) {
    const [sy, sz] = starCoords[st];
    createPart('StarA_' + st, boxGeo(0.002, 0.009, 0.0025), dialGold, {
      position: [dialCenterX + 0.004, moonArchY + sy, sz],
      parent: root,
    });
    createPart('StarB_' + st, boxGeo(0.002, 0.0025, 0.009), dialGold, {
      position: [dialCenterX + 0.004, moonArchY + sy, sz],
      parent: root,
    });
  }
  // Arched brass surround rim framing the moon dial
  createPart('MoonArchBrassRim', torusGeo(0.12, 0.006, 8, 20), brass, {
    position: [dialCenterX + 0.004, moonArchY, 0],
    rotation: [0, 90, 0],
    parent: root,
  });

  // Center Brass Arbor Collet & Cap Nut
  createPart('CenterArborCollet', cylinderGeo(0.014, 0.014, 0.014, 14), brass, {
    position: [dialCenterX + 0.006, dialCenterY, dialCenterZ],
    rotation: [0, 0, 90],
    parent: root,
  });
  createPart('CenterArborNut', sphereGeo(0.007, 10, 6), brass, {
    position: [dialCenterX + 0.016, dialCenterY, dialCenterZ],
    parent: root,
  });

  // Pierced Ornate Clock Hands (Display time: 10:10)
  // Hour Hand (Pointing towards 10 o'clock: ~120° from +Y)
  createPart('HourHandHeart', torusGeo(0.016, 0.0035, 6, 12), dialBlack, {
    position: [dialCenterX + 0.010, dialCenterY + 0.034, dialCenterZ - 0.028],
    rotation: [0, 90, 30],
    parent: root,
  });
  createPart('HourHandSpadeTip', coneGeo(0.009, 0.024, 8), dialBlack, {
    position: [dialCenterX + 0.010, dialCenterY + 0.052, dialCenterZ - 0.043],
    rotation: [0, 0, -50],
    parent: root,
  });
  createPart('HourHandShaft', cylinderGeo(0.003, 0.0045, 0.038, 6), dialBlack, {
    position: [dialCenterX + 0.010, dialCenterY + 0.017, dialCenterZ - 0.014],
    rotation: [50, 0, 0],
    parent: root,
  });

  // Minute Hand (Pointing towards 2 o'clock: ~60° from +Y)
  createPart('MinuteHandLoop1', torusGeo(0.014, 0.003, 6, 12), dialBlack, {
    position: [dialCenterX + 0.012, dialCenterY + 0.036, dialCenterZ + 0.032],
    rotation: [0, 90, -40],
    parent: root,
  });
  createPart('MinuteHandLoop2', torusGeo(0.010, 0.003, 6, 12), dialBlack, {
    position: [dialCenterX + 0.012, dialCenterY + 0.060, dialCenterZ + 0.052],
    rotation: [0, 90, -40],
    parent: root,
  });
  createPart('MinuteHandNeedle', coneGeo(0.006, 0.044, 8), dialBlack, {
    position: [dialCenterX + 0.012, dialCenterY + 0.088, dialCenterZ + 0.076],
    rotation: [0, 0, 40],
    parent: root,
  });
  createPart('MinuteHandShaft', cylinderGeo(0.003, 0.005, 0.052, 6), dialBlack, {
    position: [dialCenterX + 0.012, dialCenterY + 0.022, dialCenterZ + 0.018],
    rotation: [-40, 0, 0],
    parent: root,
  });

  // =========================================================================
  // 8. Hood Cornice, Swan-Neck Pediment & Brass Finials (Y: 2.12 to 2.36)
  // =========================================================================
  // Stepped Classical Cornice at the top of the hood (Y: 2.12 to 2.16)
  createPart('HoodCorniceLower', boxGeo(0.33, 0.025, 0.52), walnutDark, {
    position: [0, 2.1325, 0],
    parent: root,
  });
  createPart('HoodCorniceUpper', boxGeo(0.35, 0.025, 0.55), walnutDark, {
    position: [0, 2.1575, 0],
    parent: root,
  });

  // Swan-Neck Pediment: Twin Carved Arched Scrolls
  const scrollZOffsets = [-0.15, 0.15];
  for (let sc = 0; sc < 2; sc++) {
    const scz = scrollZOffsets[sc];
    const prefix = sc === 0 ? 'ScrollL_' : 'ScrollR_';
    const sign = sc === 0 ? -1 : 1;

    // Outer primary scroll molding
    createPart(prefix + 'ArchOuter', torusGeo(0.11, 0.018, 8, 16), walnutDark, {
      position: [0.08, 2.19, scz],
      rotation: [0, 90, sign * 25],
      parent: root,
    });
    // Inner fillet molding
    createPart(prefix + 'ArchInner', torusGeo(0.09, 0.008, 6, 14), walnutDark, {
      position: [0.082, 2.19, scz],
      rotation: [0, 90, sign * 25],
      parent: root,
    });
    // Center terminal rosettes in carved brass
    createPart(prefix + 'RosetteBrass', cylinderGeo(0.018, 0.018, 0.018, 14), brass, {
      position: [0.095, 2.245, sign * 0.055],
      rotation: [0, 0, 90],
      parent: root,
    });
    createPart(prefix + 'RosetteOrb', sphereGeo(0.013, 10, 6), brass, {
      position: [0.106, 2.245, sign * 0.055],
      parent: root,
    });
  }

  // Pediment Tympanum Backboard (Bookmatched walnut panel)
  createPart('PedimentTympanum', boxGeo(0.02, 0.14, 0.46), walnutBurl, {
    position: [-0.02, 2.21, 0],
    parent: root,
  });

  // Three Turned Brass Finials on Pedestals (Prompt rule: "brass finials")
  const finialConfigs = [
    { name: 'FinialLeft',   px: 0.08, py: 2.17, pz: -0.22, scale: 0.90 },
    { name: 'FinialCenter', px: 0.08, py: 2.22, pz: 0.0,   scale: 1.18 }, // Grand master center finial
    { name: 'FinialRight',  px: 0.08, py: 2.17, pz: 0.22,  scale: 0.90 },
  ];

  for (const fc of finialConfigs) {
    const s = fc.scale;
    createPart(fc.name + '_Plinth', boxGeo(0.050 * s, 0.028 * s, 0.050 * s), walnutDark, {
      position: [fc.px, fc.py + 0.014 * s, fc.pz],
      parent: root,
    });

    const baseY = fc.py + 0.028 * s;

    createPart(fc.name + '_Collar', boxGeo(0.035 * s, 0.012 * s, 0.035 * s), brass, {
      position: [fc.px, baseY + 0.006 * s, fc.pz],
      parent: root,
    });
    createPart(fc.name + '_Pedestal', cylinderGeo(0.013 * s, 0.020 * s, 0.020 * s, 14), brass, {
      position: [fc.px, baseY + 0.021 * s, fc.pz],
      parent: root,
    });
    createPart(fc.name + '_UrnBody', sphereGeo(0.026 * s, 14, 10), brass, {
      position: [fc.px, baseY + 0.046 * s, fc.pz],
      scale: [1, 1.25, 1],
      parent: root,
    });
    createPart(fc.name + '_UrnRim', torusGeo(0.018 * s, 0.0045 * s, 6, 14), brass, {
      position: [fc.px, baseY + 0.072 * s, fc.pz],
      rotation: [90, 0, 0],
      parent: root,
    });
    createPart(fc.name + '_SpireNeck', cylinderGeo(0.008 * s, 0.013 * s, 0.016 * s, 12), brass, {
      position: [fc.px, baseY + 0.083 * s, fc.pz],
      parent: root,
    });
    createPart(fc.name + '_Orb', sphereGeo(0.015 * s, 12, 8), brass, {
      position: [fc.px, baseY + 0.101 * s, fc.pz],
      parent: root,
    });
    createPart(fc.name + '_SpireNeedle', coneGeo(0.008 * s, 0.046 * s, 12), brass, {
      position: [fc.px, baseY + 0.132 * s, fc.pz],
      parent: root,
    });
  }

  return root;
}

// ===========================================================================
// Animation: Grandfather Clock Swinging Pendulum (Smooth Harmonic Motion)
// ===========================================================================
function animate() {
  const duration = 2.0;    // Standard 2-second cycle for a 1-second grandfather clock seconds pendulum
  const maxSwingDeg = 5.5; // Natural pendular swing angle with clean cabinet clearance

  // Sinusoidal harmonic oscillation around X axis (Y-Z plane, left-to-right swing)
  // Perfectly loops: pose at t = duration equals pose at t = 0
  const samples = 16;
  const swingKeys = [];
  for (let s = 0; s <= samples; s++) {
    const t = (s * duration) / samples;
    // Harmonic cosine swing: maximum tilt at t = 0 and t = duration, passes 0 at t = 0.5s & 1.5s
    const angleX = maxSwingDeg * Math.cos((2 * Math.PI * t) / duration);
    swingKeys.push({
      time: parseFloat(t.toFixed(4)),
      rotation: [parseFloat(angleX.toFixed(3)), 0, 0],
    });
  }

  return [
    createClip('swing', duration, [
      rotationTrack('Joint_Pendulum', swingKeys),
    ]),
  ];
}

Brief and retained revisions

No brief or earlier revisions are recorded. The source download contains the version built for this page.