import type { FC } from 'react' import * as THREE from 'three' import { SpinningModel } from './model3d' // The Barricelli Luggage: Nils Aall Barricelli's patented suitcase-commode. Two wooden // beams that taper upward carry three suitcases slung between them as drawers, so the // whole thing reads as a rudimentary chest of drawers built from luggage. const WOOD = new THREE.MeshStandardMaterial({ color: 0x6b4a2f, roughness: 0.85, metalness: 0.04, flatShading: true }) const BRASS = new THREE.MeshStandardMaterial({ color: 0xc9a24a, roughness: 0.45, metalness: 0.6, flatShading: true }) const STRAP = new THREE.MeshStandardMaterial({ color: 0x2f2118, roughness: 0.8, flatShading: true }) const LEATHERS = [0x8a5a34, 0x6f4632, 0x9a6b3f].map(color => new THREE.MeshStandardMaterial({ color, roughness: 0.8, metalness: 0.05, flatShading: true })) // One suitcase drawer: leather body, a lid seam, two wrap-around straps, brass latches, // corner caps, and a front pull (it is a drawer now, so the handle faces the viewer). function buildSuitcase(width: number, height: number, depth: number, leather: THREE.Material) { const suitcase = new THREE.Group() const body = new THREE.Mesh(new THREE.BoxGeometry(width, height, depth), leather); suitcase.add(body) const seam = new THREE.Mesh(new THREE.BoxGeometry(width + 0.02, 0.04, depth + 0.02), STRAP); suitcase.add(seam) for (const sign of [-1, 1]) { const strap = new THREE.Mesh(new THREE.BoxGeometry(0.07, height + 0.02, depth + 0.04), STRAP) strap.position.x = sign * width * 0.3; suitcase.add(strap) const latch = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.07, 0.03), BRASS) latch.position.set(sign * width * 0.3, 0.02, depth / 2 + 0.01); suitcase.add(latch) } for (const sx of [-1, 1]) for (const sy of [-1, 1]) { // brass corner caps on the front face const cap = new THREE.Mesh(new THREE.BoxGeometry(0.07, 0.07, 0.06), BRASS) cap.position.set(sx * (width / 2 - 0.035), sy * (height / 2 - 0.035), depth / 2) suitcase.add(cap) } const pull = new THREE.Mesh(new THREE.BoxGeometry(0.26, 0.05, 0.05), BRASS) pull.position.set(0, -height * 0.16, depth / 2 + 0.05); suitcase.add(pull) for (const sign of [-1, 1]) { const stem = new THREE.Mesh(new THREE.BoxGeometry(0.03, 0.03, 0.05), BRASS); stem.position.set(sign * 0.1, -height * 0.16, depth / 2 + 0.02); suitcase.add(stem) } return suitcase } function buildBarricelliLuggage() { const frame = new THREE.Group() const postHeight = 2.1 const suitcase = { w: 1.05, h: 0.44, d: 0.62 } const levels = [0.62, 0, -0.62] // three stacked drawers for (const sign of [-1, 1]) { // two beams, each tapering upward, leaning slightly inward const post = new THREE.Mesh(new THREE.CylinderGeometry(0.085, 0.12, postHeight, 4), WOOD) post.rotation.y = Math.PI / 4 // square the prism to face front post.rotation.z = -sign * 0.05 // splay the feet, converge the tops post.position.x = sign * 0.66 frame.add(post) const foot = new THREE.Mesh(new THREE.BoxGeometry(0.24, 0.09, 0.32), WOOD) foot.position.set(sign * 0.7, -postHeight / 2 - 0.02, 0); frame.add(foot) } // Top cap and a low stretcher tie the beams into a stand. const cap = new THREE.Mesh(new THREE.BoxGeometry(1.44, 0.11, 0.5), WOOD); cap.position.y = postHeight / 2 + 0.02; frame.add(cap) const stretcher = new THREE.Mesh(new THREE.BoxGeometry(1.4, 0.08, 0.18), WOOD); stretcher.position.y = -postHeight / 2 + 0.14; frame.add(stretcher) levels.forEach((y, i) => { const drawer = buildSuitcase(suitcase.w, suitcase.h, suitcase.d, LEATHERS[i % LEATHERS.length]) drawer.position.y = y; frame.add(drawer) for (const sign of [-1, 1]) { // small wooden brackets mounting each case to the beams const bracket = new THREE.Mesh(new THREE.BoxGeometry(0.16, 0.08, 0.3), WOOD) bracket.position.set(sign * (suitcase.w / 2 + 0.05), y, 0); frame.add(bracket) } }) frame.traverse(obj => { if (obj instanceof THREE.Mesh) obj.castShadow = true }) return frame } export const BarricelliLuggageMerit: FC<{ label: string; onComplete: () => void }> = ({ label, onComplete }) => (
◆ MERIT AWARDED ◆

{label}

)