Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline (Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/* packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
188 lines
4.8 KiB
TypeScript
188 lines
4.8 KiB
TypeScript
type AvatarPoint = readonly [number, number];
|
|
|
|
let organicAvatarTemplates: readonly (readonly AvatarPoint[])[] | undefined;
|
|
|
|
export function avatarIdentitySeed(identity: string): number {
|
|
let hash = 0;
|
|
for (let index = 0; index < identity.length; index++) {
|
|
hash = (hash << 5) - hash + identity.charCodeAt(index);
|
|
hash |= 0;
|
|
}
|
|
return Math.abs(hash);
|
|
}
|
|
|
|
export function organicAvatarPath(seed: number, phaseOffset = 0): string {
|
|
const phase = ((seed % 360) * Math.PI) / 180 + phaseOffset;
|
|
const family = seed % 10;
|
|
if (!organicAvatarTemplates) {
|
|
organicAvatarTemplates = [
|
|
[
|
|
[-43, 22],
|
|
[-51, 16],
|
|
[-53, 6],
|
|
[-49, -4],
|
|
[-40, -12],
|
|
[-30, -13],
|
|
[-27, -24],
|
|
[-18, -34],
|
|
[-6, -36],
|
|
[4, -32],
|
|
[12, -42],
|
|
[25, -45],
|
|
[37, -38],
|
|
[43, -27],
|
|
[42, -17],
|
|
[51, -11],
|
|
[56, 0],
|
|
[54, 12],
|
|
[46, 20],
|
|
[30, 24],
|
|
[0, 25],
|
|
[-29, 25],
|
|
],
|
|
[
|
|
[0, -52],
|
|
[12, -35],
|
|
[28, -17],
|
|
[39, 2],
|
|
[42, 21],
|
|
[34, 38],
|
|
[19, 49],
|
|
[0, 52],
|
|
[-19, 49],
|
|
[-34, 38],
|
|
[-42, 21],
|
|
[-39, 2],
|
|
[-28, -17],
|
|
[-12, -35],
|
|
],
|
|
[
|
|
[-42, -23],
|
|
[-27, -40],
|
|
[-7, -46],
|
|
[14, -40],
|
|
[33, -27],
|
|
[46, -8],
|
|
[46, 14],
|
|
[35, 34],
|
|
[14, 46],
|
|
[-8, 46],
|
|
[-29, 38],
|
|
[-43, 21],
|
|
[-48, 0],
|
|
],
|
|
Array.from({ length: 40 }, (_, index) => {
|
|
const angle = (index / 40) * Math.PI * 2;
|
|
const radius = 44 + Math.cos(angle * 5) * 5;
|
|
return [Math.cos(angle) * radius, Math.sin(angle) * radius];
|
|
}),
|
|
Array.from({ length: 36 }, (_, index) => {
|
|
const angle = (index / 36) * Math.PI * 2;
|
|
const radius = 43 + Math.cos(angle * 6) * 7;
|
|
return [Math.cos(angle) * radius, Math.sin(angle) * radius];
|
|
}),
|
|
Array.from({ length: 32 }, (_, index) => {
|
|
const angle = (index / 32) * Math.PI * 2;
|
|
const x = Math.cos(angle);
|
|
const y = Math.sin(angle);
|
|
return [
|
|
Math.sign(x) * Math.sqrt(Math.abs(x)) * 46,
|
|
Math.sign(y) * Math.sqrt(Math.abs(y)) * 43,
|
|
];
|
|
}),
|
|
[
|
|
[0, 48],
|
|
[-14, 36],
|
|
[-32, 20],
|
|
[-45, 0],
|
|
[-43, -20],
|
|
[-29, -36],
|
|
[-10, -39],
|
|
[0, -27],
|
|
[10, -39],
|
|
[29, -36],
|
|
[43, -20],
|
|
[45, 0],
|
|
[32, 20],
|
|
[14, 36],
|
|
],
|
|
[
|
|
[-48, 35],
|
|
[-38, 22],
|
|
[-34, -8],
|
|
[-27, -31],
|
|
[-12, -44],
|
|
[0, -48],
|
|
[12, -44],
|
|
[27, -31],
|
|
[34, -8],
|
|
[38, 22],
|
|
[48, 35],
|
|
[26, 42],
|
|
[0, 44],
|
|
[-26, 42],
|
|
],
|
|
[
|
|
[-45, -19],
|
|
[-28, -38],
|
|
[-4, -46],
|
|
[22, -41],
|
|
[42, -25],
|
|
[49, -2],
|
|
[42, 23],
|
|
[22, 42],
|
|
[-4, 47],
|
|
[-29, 38],
|
|
[-46, 18],
|
|
[-50, 0],
|
|
],
|
|
[
|
|
[-50, 0],
|
|
[-45, -20],
|
|
[-30, -38],
|
|
[0, -48],
|
|
[30, -38],
|
|
[45, -20],
|
|
[50, 0],
|
|
[35, 10],
|
|
[18, 12],
|
|
[16, 38],
|
|
[0, 46],
|
|
[-16, 38],
|
|
[-18, 12],
|
|
[-35, 10],
|
|
],
|
|
] as const;
|
|
}
|
|
const templates = organicAvatarTemplates;
|
|
const xScale = 1 + Math.sin(phase) * 0.035;
|
|
const yScale = 1 + Math.cos(phase) * 0.025;
|
|
const shear = Math.sin(phase * 1.7) * 0.025;
|
|
let points = (templates[family] ?? templates[5]!).map(([x, y]) => ({
|
|
x: x * xScale + y * shear,
|
|
y: y * yScale,
|
|
}));
|
|
const smoothingPasses = family >= 3 && family <= 5 ? 1 : 2;
|
|
for (let pass = 0; pass < smoothingPasses; pass++) {
|
|
points = points.flatMap((point, index) => {
|
|
const next = points[(index + 1) % points.length]!;
|
|
return [
|
|
{ x: point.x * 0.75 + next.x * 0.25, y: point.y * 0.75 + next.y * 0.25 },
|
|
{ x: point.x * 0.25 + next.x * 0.75, y: point.y * 0.25 + next.y * 0.75 },
|
|
];
|
|
});
|
|
}
|
|
const pointCount = points.length;
|
|
const round = (value: number) => Math.round(value * 100) / 100;
|
|
const first = points[0]!;
|
|
let path = `M${round(first.x)} ${round(first.y)}`;
|
|
for (let index = 0; index < pointCount; index++) {
|
|
const before = points[(index - 1 + pointCount) % pointCount]!;
|
|
const current = points[index]!;
|
|
const next = points[(index + 1) % pointCount]!;
|
|
const after = points[(index + 2) % pointCount]!;
|
|
path += `C${round(current.x + (next.x - before.x) / 6)} ${round(current.y + (next.y - before.y) / 6)} ${round(next.x - (after.x - current.x) / 6)} ${round(next.y - (after.y - current.y) / 6)} ${round(next.x)} ${round(next.y)}`;
|
|
}
|
|
return `${path}Z`;
|
|
}
|