Files
chat/frontend/src/utils/leafGenerator.ts
T
2026-06-13 17:57:43 +08:00

90 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 程序化叶子生成器
* 由确定性种子(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 pathviewBox 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)
}