Reshape the sack so it stops being an amphora

The profile put its widest point a quarter of the way up and narrowed from
there all the way to the neck, which is the silhouette of a vase. A filled
sack does close to the opposite: it SPREADS where it meets the ground,
because the grain settles and cloth has no stiffness of its own, then runs
nearly parallel up most of its height, and gathers in sharply only where
the cord is tied. Almost the entire difference between the two shapes
lives in the bottom level and in where the taper starts.

With the body right, the collar was what still read as a vessel: it opened
out to 0.46 of the radius over a fifth of the sack's height, which is the
mouth of a bottle. Above the tie there should be a hand's width of gathered
cloth and no flare to speak of.

27 models, 18481 triangles, all checks pass, 0 floating cases.
This commit is contained in:
CRTkafa 2026-08-22 14:05:31 +07:00
parent 6bae10f593
commit 82844abc3e
3 changed files with 76 additions and 50 deletions

View file

@ -67,7 +67,7 @@
"address": "@medieval-kit/wooden-barrel", "address": "@medieval-kit/wooden-barrel",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.787Z", "installedAt": "2026-08-22T07:04:36.921Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\wooden-barrel\\model.ts", "path": "src\\models\\medieval-kit\\wooden-barrel\\model.ts",
@ -82,7 +82,7 @@
"address": "@medieval-kit/core", "address": "@medieval-kit/core",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.769Z", "installedAt": "2026-08-22T07:04:36.903Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\core\\geometry.ts", "path": "src\\models\\medieval-kit\\core\\geometry.ts",
@ -129,7 +129,7 @@
"address": "@medieval-kit/wooden-crate", "address": "@medieval-kit/wooden-crate",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.791Z", "installedAt": "2026-08-22T07:04:36.925Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\wooden-crate\\model.ts", "path": "src\\models\\medieval-kit\\wooden-crate\\model.ts",
@ -144,7 +144,7 @@
"address": "@medieval-kit/kit", "address": "@medieval-kit/kit",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.797Z", "installedAt": "2026-08-22T07:04:36.931Z",
"files": [], "files": [],
"dependencies": [] "dependencies": []
}, },
@ -152,7 +152,7 @@
"address": "@medieval-kit/iron-anvil", "address": "@medieval-kit/iron-anvil",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.776Z", "installedAt": "2026-08-22T07:04:36.910Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\iron-anvil\\model.ts", "path": "src\\models\\medieval-kit\\iron-anvil\\model.ts",
@ -167,7 +167,7 @@
"address": "@medieval-kit/wooden-bucket", "address": "@medieval-kit/wooden-bucket",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.789Z", "installedAt": "2026-08-22T07:04:36.924Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\wooden-bucket\\model.ts", "path": "src\\models\\medieval-kit\\wooden-bucket\\model.ts",
@ -182,7 +182,7 @@
"address": "@medieval-kit/wooden-fence", "address": "@medieval-kit/wooden-fence",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.792Z", "installedAt": "2026-08-22T07:04:36.926Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\wooden-fence\\model.ts", "path": "src\\models\\medieval-kit\\wooden-fence\\model.ts",
@ -197,7 +197,7 @@
"address": "@medieval-kit/wooden-hoe", "address": "@medieval-kit/wooden-hoe",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.793Z", "installedAt": "2026-08-22T07:04:36.928Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\wooden-hoe\\model.ts", "path": "src\\models\\medieval-kit\\wooden-hoe\\model.ts",
@ -212,7 +212,7 @@
"address": "@medieval-kit/wooden-ladder", "address": "@medieval-kit/wooden-ladder",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.794Z", "installedAt": "2026-08-22T07:04:36.929Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\wooden-ladder\\model.ts", "path": "src\\models\\medieval-kit\\wooden-ladder\\model.ts",
@ -227,7 +227,7 @@
"address": "@medieval-kit/wooden-pitchfork", "address": "@medieval-kit/wooden-pitchfork",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.795Z", "installedAt": "2026-08-22T07:04:36.930Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\wooden-pitchfork\\model.ts", "path": "src\\models\\medieval-kit\\wooden-pitchfork\\model.ts",
@ -242,7 +242,7 @@
"address": "@medieval-kit/wooden-shovel", "address": "@medieval-kit/wooden-shovel",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.796Z", "installedAt": "2026-08-22T07:04:36.930Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\wooden-shovel\\model.ts", "path": "src\\models\\medieval-kit\\wooden-shovel\\model.ts",
@ -257,7 +257,7 @@
"address": "@medieval-kit/wooden-stool", "address": "@medieval-kit/wooden-stool",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.797Z", "installedAt": "2026-08-22T07:04:36.931Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\wooden-stool\\model.ts", "path": "src\\models\\medieval-kit\\wooden-stool\\model.ts",
@ -272,7 +272,7 @@
"address": "@medieval-kit/cart-wheel", "address": "@medieval-kit/cart-wheel",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.772Z", "installedAt": "2026-08-22T07:04:36.906Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\cart-wheel\\model.ts", "path": "src\\models\\medieval-kit\\cart-wheel\\model.ts",
@ -287,7 +287,7 @@
"address": "@medieval-kit/log-pile", "address": "@medieval-kit/log-pile",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.779Z", "installedAt": "2026-08-22T07:04:36.915Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\log-pile\\model.ts", "path": "src\\models\\medieval-kit\\log-pile\\model.ts",
@ -302,7 +302,7 @@
"address": "@medieval-kit/trestle-table", "address": "@medieval-kit/trestle-table",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.784Z", "installedAt": "2026-08-22T07:04:36.919Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\trestle-table\\model.ts", "path": "src\\models\\medieval-kit\\trestle-table\\model.ts",
@ -317,7 +317,7 @@
"address": "@medieval-kit/wooden-chest", "address": "@medieval-kit/wooden-chest",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.790Z", "installedAt": "2026-08-22T07:04:36.924Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\wooden-chest\\model.ts", "path": "src\\models\\medieval-kit\\wooden-chest\\model.ts",
@ -332,7 +332,7 @@
"address": "@medieval-kit/hay-bale", "address": "@medieval-kit/hay-bale",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.775Z", "installedAt": "2026-08-22T07:04:36.909Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\hay-bale\\model.ts", "path": "src\\models\\medieval-kit\\hay-bale\\model.ts",
@ -347,7 +347,7 @@
"address": "@medieval-kit/pitch-torch", "address": "@medieval-kit/pitch-torch",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.782Z", "installedAt": "2026-08-22T07:04:36.917Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\pitch-torch\\model.ts", "path": "src\\models\\medieval-kit\\pitch-torch\\model.ts",
@ -362,7 +362,7 @@
"address": "@medieval-kit/wooden-bench", "address": "@medieval-kit/wooden-bench",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.788Z", "installedAt": "2026-08-22T07:04:36.923Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\wooden-bench\\model.ts", "path": "src\\models\\medieval-kit\\wooden-bench\\model.ts",
@ -377,11 +377,11 @@
"address": "@medieval-kit/linen-sack", "address": "@medieval-kit/linen-sack",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.779Z", "installedAt": "2026-08-22T07:04:36.914Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\linen-sack\\model.ts", "path": "src\\models\\medieval-kit\\linen-sack\\model.ts",
"sourceHash": "f35b564de100d3038cc8c26ca9a8c08bc40c0fa2bab823006b0a0f72e647ffd7" "sourceHash": "b92a9b075c2b702598a6c8cb5eff76941356cd5f38c7ffd01de621aea10c368a"
} }
], ],
"dependencies": [ "dependencies": [
@ -392,7 +392,7 @@
"address": "@medieval-kit/oak-tankard", "address": "@medieval-kit/oak-tankard",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.781Z", "installedAt": "2026-08-22T07:04:36.915Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\oak-tankard\\model.ts", "path": "src\\models\\medieval-kit\\oak-tankard\\model.ts",
@ -407,7 +407,7 @@
"address": "@medieval-kit/straw-broom", "address": "@medieval-kit/straw-broom",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.783Z", "installedAt": "2026-08-22T07:04:36.918Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\straw-broom\\model.ts", "path": "src\\models\\medieval-kit\\straw-broom\\model.ts",
@ -422,7 +422,7 @@
"address": "@medieval-kit/bronze-bell", "address": "@medieval-kit/bronze-bell",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.771Z", "installedAt": "2026-08-22T07:04:36.905Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\bronze-bell\\model.ts", "path": "src\\models\\medieval-kit\\bronze-bell\\model.ts",
@ -437,7 +437,7 @@
"address": "@medieval-kit/iron-lantern", "address": "@medieval-kit/iron-lantern",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.777Z", "installedAt": "2026-08-22T07:04:36.912Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\iron-lantern\\model.ts", "path": "src\\models\\medieval-kit\\iron-lantern\\model.ts",
@ -452,7 +452,7 @@
"address": "@medieval-kit/tavern-sign", "address": "@medieval-kit/tavern-sign",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.784Z", "installedAt": "2026-08-22T07:04:36.919Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\tavern-sign\\model.ts", "path": "src\\models\\medieval-kit\\tavern-sign\\model.ts",
@ -467,7 +467,7 @@
"address": "@medieval-kit/coin-pouch", "address": "@medieval-kit/coin-pouch",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.773Z", "installedAt": "2026-08-22T07:04:36.907Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\coin-pouch\\model.ts", "path": "src\\models\\medieval-kit\\coin-pouch\\model.ts",
@ -482,7 +482,7 @@
"address": "@medieval-kit/glass-phial", "address": "@medieval-kit/glass-phial",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.774Z", "installedAt": "2026-08-22T07:04:36.908Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\glass-phial\\model.ts", "path": "src\\models\\medieval-kit\\glass-phial\\model.ts",
@ -497,7 +497,7 @@
"address": "@medieval-kit/leather-book", "address": "@medieval-kit/leather-book",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.778Z", "installedAt": "2026-08-22T07:04:36.913Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\leather-book\\model.ts", "path": "src\\models\\medieval-kit\\leather-book\\model.ts",
@ -512,7 +512,7 @@
"address": "@medieval-kit/wicker-basket", "address": "@medieval-kit/wicker-basket",
"source": "file:my-registry/dist/registry.json", "source": "file:my-registry/dist/registry.json",
"version": "workspace", "version": "workspace",
"installedAt": "2026-08-22T07:01:18.785Z", "installedAt": "2026-08-22T07:04:36.920Z",
"files": [ "files": [
{ {
"path": "src\\models\\medieval-kit\\wicker-basket\\model.ts", "path": "src\\models\\medieval-kit\\wicker-basket\\model.ts",

View file

@ -46,7 +46,8 @@ export const linenSackDefaults: LinenSackConfig = {
height: 0.52, height: 0.52,
radius: 0.16, radius: 0.16,
fill: 0.85, fill: 0.85,
collar: 0.14, // A hand's width of gathered cloth above the cord, not a fifth of the sack.
collar: 0.1,
ears: 4, ears: 4,
seed: 53, seed: 53,
} }
@ -72,15 +73,24 @@ export function createModel(overrides: Partial<LinenSackConfig> = {}) {
// silhouette means one model can produce both a full and a half-empty // silhouette means one model can produce both a full and a half-empty
// sack. // sack.
const wide = config.radius * (0.72 + fill * 0.36) const wide = config.radius * (0.72 + fill * 0.36)
// A sack, not an amphora.
//
// The old profile put its widest point a quarter of the way up and then
// narrowed all the way to the neck, which is the silhouette of a vase.
// A filled sack does the opposite: it SPREADS where it meets the ground,
// because the grain settles and the cloth has no stiffness, then runs
// close to parallel up most of its height, and only gathers in sharply
// where the cord is tied. Nearly the whole difference between the two
// shapes is in the bottom level and in where the taper begins.
const profile: Level[] = [ const profile: Level[] = [
{ y: -half, radius: wide * 0.72 }, { y: -half, radius: wide * 0.93 },
{ y: -half + config.height * 0.05, radius: wide * 0.95 }, { y: -half + config.height * 0.07, radius: wide },
{ y: -half + config.height * 0.26 * fill, radius: wide }, { y: -half + config.height * 0.44 * fill, radius: wide },
{ y: -half + config.height * 0.52 * fill, radius: wide * 0.93 }, { y: -half + config.height * 0.72 * fill, radius: wide * 0.9 },
{ y: bodyTop - config.height * 0.16, radius: wide * 0.7 }, { y: bodyTop - config.height * 0.09, radius: wide * 0.52 },
{ y: bodyTop, radius: config.radius * 0.34 }, { y: bodyTop, radius: config.radius * 0.25 },
] ]
const body = latheGeometry(profile, 9, [0, 0, 0], tint('cloth', -0.06, 1.3), { const body = latheGeometry(profile, 11, [0, 0, 0], tint('cloth', -0.06, 1.3), {
colourTop: tint('cloth', 0.05, 1.3), colourTop: tint('cloth', 0.05, 1.3),
}) })
// Cloth is not rigid: here the surface break-up is the texture itself. // Cloth is not rigid: here the surface break-up is the texture itself.
@ -123,8 +133,11 @@ export function createModel(overrides: Partial<LinenSackConfig> = {}) {
{ y: bodyTop - config.height * 0.03, radius: config.radius * 0.36 }, { y: bodyTop - config.height * 0.03, radius: config.radius * 0.36 },
{ y: neckY - config.height * 0.02, radius: config.radius * 0.3 }, { y: neckY - config.height * 0.02, radius: config.radius * 0.3 },
{ y: neckY + config.height * 0.03, radius: config.radius * 0.27 }, { y: neckY + config.height * 0.03, radius: config.radius * 0.27 },
{ y: half - config.height * 0.02, radius: config.radius * 0.46 }, // The tuft above the tie opens only a little. Flaring to 0.46 of the
{ y: half, radius: config.radius * 0.4 }, // radius turned the top of a sack into the mouth of a bottle, which is
// the last thing left reading as a vessel once the body was fixed.
{ y: half - config.height * 0.02, radius: config.radius * 0.33 },
{ y: half, radius: config.radius * 0.27 },
] ]
const collar = latheGeometry(flare, 9, [0, 0, 0], tint('cloth', 0.02, 1.3), { const collar = latheGeometry(flare, 9, [0, 0, 0], tint('cloth', 0.02, 1.3), {
colourTop: tint('cloth', 0.1, 1.3), colourTop: tint('cloth', 0.1, 1.3),

View file

@ -46,7 +46,8 @@ export const linenSackDefaults: LinenSackConfig = {
height: 0.52, height: 0.52,
radius: 0.16, radius: 0.16,
fill: 0.85, fill: 0.85,
collar: 0.14, // A hand's width of gathered cloth above the cord, not a fifth of the sack.
collar: 0.1,
ears: 4, ears: 4,
seed: 53, seed: 53,
} }
@ -72,15 +73,24 @@ export function createModel(overrides: Partial<LinenSackConfig> = {}) {
// silhouette means one model can produce both a full and a half-empty // silhouette means one model can produce both a full and a half-empty
// sack. // sack.
const wide = config.radius * (0.72 + fill * 0.36) const wide = config.radius * (0.72 + fill * 0.36)
// A sack, not an amphora.
//
// The old profile put its widest point a quarter of the way up and then
// narrowed all the way to the neck, which is the silhouette of a vase.
// A filled sack does the opposite: it SPREADS where it meets the ground,
// because the grain settles and the cloth has no stiffness, then runs
// close to parallel up most of its height, and only gathers in sharply
// where the cord is tied. Nearly the whole difference between the two
// shapes is in the bottom level and in where the taper begins.
const profile: Level[] = [ const profile: Level[] = [
{ y: -half, radius: wide * 0.72 }, { y: -half, radius: wide * 0.93 },
{ y: -half + config.height * 0.05, radius: wide * 0.95 }, { y: -half + config.height * 0.07, radius: wide },
{ y: -half + config.height * 0.26 * fill, radius: wide }, { y: -half + config.height * 0.44 * fill, radius: wide },
{ y: -half + config.height * 0.52 * fill, radius: wide * 0.93 }, { y: -half + config.height * 0.72 * fill, radius: wide * 0.9 },
{ y: bodyTop - config.height * 0.16, radius: wide * 0.7 }, { y: bodyTop - config.height * 0.09, radius: wide * 0.52 },
{ y: bodyTop, radius: config.radius * 0.34 }, { y: bodyTop, radius: config.radius * 0.25 },
] ]
const body = latheGeometry(profile, 9, [0, 0, 0], tint('cloth', -0.06, 1.3), { const body = latheGeometry(profile, 11, [0, 0, 0], tint('cloth', -0.06, 1.3), {
colourTop: tint('cloth', 0.05, 1.3), colourTop: tint('cloth', 0.05, 1.3),
}) })
// Cloth is not rigid: here the surface break-up is the texture itself. // Cloth is not rigid: here the surface break-up is the texture itself.
@ -123,8 +133,11 @@ export function createModel(overrides: Partial<LinenSackConfig> = {}) {
{ y: bodyTop - config.height * 0.03, radius: config.radius * 0.36 }, { y: bodyTop - config.height * 0.03, radius: config.radius * 0.36 },
{ y: neckY - config.height * 0.02, radius: config.radius * 0.3 }, { y: neckY - config.height * 0.02, radius: config.radius * 0.3 },
{ y: neckY + config.height * 0.03, radius: config.radius * 0.27 }, { y: neckY + config.height * 0.03, radius: config.radius * 0.27 },
{ y: half - config.height * 0.02, radius: config.radius * 0.46 }, // The tuft above the tie opens only a little. Flaring to 0.46 of the
{ y: half, radius: config.radius * 0.4 }, // radius turned the top of a sack into the mouth of a bottle, which is
// the last thing left reading as a vessel once the body was fixed.
{ y: half - config.height * 0.02, radius: config.radius * 0.33 },
{ y: half, radius: config.radius * 0.27 },
] ]
const collar = latheGeometry(flare, 9, [0, 0, 0], tint('cloth', 0.02, 1.3), { const collar = latheGeometry(flare, 9, [0, 0, 0], tint('cloth', 0.02, 1.3), {
colourTop: tint('cloth', 0.1, 1.3), colourTop: tint('cloth', 0.1, 1.3),