90 lines
3.1 KiB
TypeScript
90 lines
3.1 KiB
TypeScript
/**
|
||
* 程序化叶子生成器
|
||
* 由确定性种子(hash(userId+date))派生独一无二的叶子形态
|
||
*/
|
||
|
||
export interface LeafStyle {
|
||
hue: number // 色相 0-360
|
||
saturation: number // 饱和度
|
||
lightness: number // 亮度
|
||
shapeVariant: number // 形态变体 0-3(不同叶形 path)
|
||
veinCount: number // 叶脉数量 3-7
|
||
angle: number // 叶子倾斜角度
|
||
spots: number // 斑点数量
|
||
size: number // 相对大小 0.85-1.1
|
||
}
|
||
|
||
/** 从 16 进制种子派生叶子样式 */
|
||
export function generateLeafStyle(seed: string): LeafStyle {
|
||
// 将 16 位 hex 种子拆成多段用于不同属性
|
||
const n = (start: number, len: number) => parseInt(seed.slice(start, start + len), 16)
|
||
|
||
const hue = n(0, 3) % 80 + 70 // 70-150 区间:黄绿到青绿
|
||
const saturation = 40 + (n(3, 2) % 35) // 40-75
|
||
const lightness = 45 + (n(5, 2) % 20) // 45-65
|
||
const shapeVariant = n(7, 1) % 4
|
||
const veinCount = 3 + (n(8, 1) % 5)
|
||
const angle = (n(9, 2) % 30) - 15 // -15 到 +15 度
|
||
const spots = n(11, 1) % 4
|
||
const size = 0.85 + (n(12, 2) % 26) / 100
|
||
|
||
return { hue, saturation, lightness, shapeVariant, veinCount, angle, spots, size }
|
||
}
|
||
|
||
export function leafColor(style: LeafStyle, lightDelta = 0): string {
|
||
return `hsl(${style.hue}, ${style.saturation}%, ${style.lightness + lightDelta}%)`
|
||
}
|
||
|
||
/** 4 种叶形 SVG path(viewBox 0 0 100 120) */
|
||
const LEAF_SHAPES = [
|
||
// 经典椭圆叶
|
||
'M50 8 C72 18 82 45 78 78 C74 104 60 116 50 116 C40 116 26 104 22 78 C18 45 28 18 50 8 Z',
|
||
// 心形叶
|
||
'M50 12 C60 4 78 8 80 28 C82 52 64 78 50 116 C36 78 18 52 20 28 C22 8 40 4 50 12 Z',
|
||
// 长披针叶
|
||
'M50 6 C62 30 66 60 62 92 C58 110 54 118 50 118 C46 118 42 110 38 92 C34 60 38 30 50 6 Z',
|
||
// 枫叶状
|
||
'M50 8 C58 22 56 30 68 34 C78 38 72 50 66 54 C74 62 70 74 58 72 C56 90 54 108 50 118 C46 108 44 90 42 72 C30 74 26 62 34 54 C28 50 22 38 32 34 C44 30 42 22 50 8 Z',
|
||
]
|
||
|
||
export function leafPath(variant: number): string {
|
||
return LEAF_SHAPES[variant % LEAF_SHAPES.length]
|
||
}
|
||
|
||
/** 生成叶脉 path(沿中线对称分支) */
|
||
export function veinPaths(style: LeafStyle): string[] {
|
||
const paths: string[] = []
|
||
// 主脉
|
||
paths.push('M50 16 L50 112')
|
||
// 侧脉
|
||
for (let i = 0; i < style.veinCount; i++) {
|
||
const y = 28 + i * (70 / style.veinCount)
|
||
const len = 16 + (i % 2) * 6
|
||
paths.push(`M50 ${y} Q38 ${y + 8} ${50 - len} ${y + 16}`)
|
||
paths.push(`M50 ${y} Q62 ${y + 8} ${50 + len} ${y + 16}`)
|
||
}
|
||
return paths
|
||
}
|
||
|
||
/** 生成斑点坐标(装饰) */
|
||
export function spotPositions(style: LeafStyle): { x: number; y: number; r: number }[] {
|
||
const spots: { x: number; y: number; r: number }[] = []
|
||
let s = parseInt(seedHash(style), 10) || 1
|
||
const rand = () => {
|
||
s = (s * 9301 + 49297) % 233280
|
||
return s / 233280
|
||
}
|
||
for (let i = 0; i < style.spots; i++) {
|
||
spots.push({
|
||
x: 30 + rand() * 40,
|
||
y: 30 + rand() * 60,
|
||
r: 1.5 + rand() * 2,
|
||
})
|
||
}
|
||
return spots
|
||
}
|
||
|
||
function seedHash(style: LeafStyle): string {
|
||
return '' + (style.hue * 7 + style.veinCount * 13 + style.spots * 31)
|
||
}
|