medieval-kit/my-registry/models/core/tool.ts
CRT 50c7fc60b9 Translate the whole repository to English
The kit is going public, so everything a reader sees is now English:
27 model sources, the core vocabulary, meta.ts (titles, descriptions and
every slider label), the viewer UI, all script output, REFERENCE.md and
the READMEs.

Rules applied: only comments, doc comments and human-facing strings were
touched. Identifiers, numbers, logic, registry item ids, part names and
material slot names are unchanged — those are a published contract and
were already English.

Verified rather than assumed: for all 53 changed .ts files the code
skeleton (source with comments stripped and string contents replaced by a
placeholder) is byte-identical before and after. Full gate is green —
typecheck, registry conformance, ~510 model checks, GLB round trip and a
production build.

Also fixed here:
- index.html and viewer.html were missed by the translation pass because
  the file list only covered .ts/.md/.css. Both had lang="tr" and Turkish
  titles.
- The viewer formatted triangle counts with toLocaleString('tr-TR'), which
  prints 15.425 rather than 15,425.
- REFERENCE.md's model table header did not match what
  scripts/catalog-table.ts emits, so the next regeneration would have
  silently changed it. Aligned to the generator.
- Removed scripts/probe.ts and scripts/zdiag.ts; they were throwaway
  diagnostics that got committed by accident.
2026-08-21 14:35:13 +07:00

131 lines
5.1 KiB
TypeScript

import { Color, type BufferGeometry } from 'three'
import { latheGeometry, mergeColoured, type Level } from './geometry.ts'
import { jitter } from './random.ts'
import { MEDIEVAL_PALETTE } from './materials.ts'
/**
* The shared language of hand tools.
*
* In the first attempt all three tools were just a straight prismatic shaft +
* a boxy head, and they looked like toys. The reasons were small, individual
* things:
*
* - The shaft was the same thickness end to end. A real shaft has a swell
* at its base (the grip) — so the hand does not slip off. That is the one
* detail that makes the silhouette readable.
* - The head was stuck directly onto the shaft. A real tool has a conical
* socket; the shaft goes inside it.
* - There was no geometric variation anywhere, only the colour changed. A
* forged tool is not perfectly symmetric.
*
* This module supplies all three from one place, so adding a new tool to the
* kit is now just a matter of answering "what is the head".
*/
export interface ShaftOptions {
readonly length: number
readonly radius: number
/** How many sides. 6 is enough: on a hand-held stick 8 adds nothing to the silhouette. */
readonly segments?: number
readonly random: () => number
}
export interface ToolShaft {
readonly geometry: BufferGeometry
/** Y position of the shaft's top end — the head sits here. */
readonly top: number
/** The shaft's radius at the top end. */
readonly topRadius: number
}
/**
* Tool shaft: grip swell at the bottom, a long straight body in the middle, a
* slight taper towards the top. Produced as a single lathe — stacking prisms
* on top of each other would leave coplanar face pairs between them.
*/
export function toolShaft(options: ShaftOptions): ToolShaft {
const { length, radius, random } = options
const segments = options.segments ?? 6
const bottom = -length / 2
const top = length / 2
const r = (scale: number): number => radius * scale * (1 + jitter(random, 0.02))
const profile: Level[] = [
{ y: bottom, radius: r(0.78) }, // bottom: rounded end
{ y: bottom + length * 0.012, radius: r(1.18) }, // underside of the grip swell
{ y: bottom + length * 0.075, radius: r(1.1) }, // top of the grip
{ y: bottom + length * 0.14, radius: r(0.94) }, // waist between grip and body
{ y: bottom + length * 0.55, radius: r(1) }, // body
{ y: top, radius: r(0.9) }, // taper towards the head
]
const tint = new Color(MEDIEVAL_PALETTE.oak)
tint.offsetHSL(jitter(random, 0.012), jitter(random, 0.05), 0.05 + jitter(random, 0.05))
const tintTop = new Color(MEDIEVAL_PALETTE.oak)
// The head end passes through the hand less often, so it stays a bit darker.
tintTop.offsetHSL(jitter(random, 0.01), jitter(random, 0.04), -0.02 + jitter(random, 0.04))
return {
geometry: latheGeometry(profile, segments, [0, 0, 0], tint, { colourTop: tintTop }),
top,
topRadius: profile.at(-1)!.radius,
}
}
export interface SocketOptions {
/** Y position where the socket sits (the shaft's top end). */
readonly y: number
/** The shaft's radius at that point. */
readonly shaftRadius: number
/** Socket length. */
readonly length: number
readonly segments?: number
readonly random: () => number
}
/**
* Forged socket: a cone that wraps the shaft and widens upwards, with a collar
* on top.
*
* It is extended DOWN INTO the shaft — because its lower end stays inside the
* shaft's body, no surface ends up coplanar with the shaft's surface.
*/
export function toolSocket(options: SocketOptions): BufferGeometry {
const { y, shaftRadius, length, random } = options
const segments = options.segments ?? 6
const tint = new Color(MEDIEVAL_PALETTE.iron)
tint.offsetHSL(0, jitter(random, 0.02), jitter(random, 0.05))
const collar = new Color(MEDIEVAL_PALETTE.iron)
collar.offsetHSL(0, jitter(random, 0.02), 0.04 + jitter(random, 0.04))
const profile: Level[] = [
{ y: y - length * 0.9, radius: shaftRadius * 1.12 },
{ y: y - length * 0.45, radius: shaftRadius * 1.34 },
{ y: y + length * 0.1, radius: shaftRadius * 1.5 },
{ y: y + length * 0.22, radius: shaftRadius * 1.72 }, // collar
{ y: y + length * 0.34, radius: shaftRadius * 1.46 },
]
return mergeColoured([latheGeometry(profile, segments, [0, 0, 0], tint, { colourTop: collar })])
}
/** Iron tone with a small deviation. So all three tools look like one hand made them. */
export function ironTint(random: () => number, lift = 0): Color {
const tint = new Color(MEDIEVAL_PALETTE.iron)
tint.offsetHSL(0, jitter(random, 0.02), lift + jitter(random, 0.05))
return tint
}
/**
* Burnished steel tone — for the `steel` slot.
*
* The deviation is kept narrower than in `ironTint`: what determines the
* colour of a polished surface is not its own pigment but the environment it
* reflects. Shifting hue in the vertex colour looks dirty here, so there is
* only a tiny wobble in lightness.
*/
export function steelTint(random: () => number, lift = 0): Color {
const tint = new Color(MEDIEVAL_PALETTE.steel)
tint.offsetHSL(0, jitter(random, 0.008), lift + jitter(random, 0.025))
return tint
}