--- name: motion/procedural-patterns title: Procedural Pattern Generator description: Canvas-based procedural generation of animated patterns around title elements. Uses occupancy grid, BFS proximity map, and seeded RNG for deterministic layout. tags: [motion, procedural, pattern, canvas, generation, animation, title-card] source: github.com/halfof8/patterngen-oss --- # Procedural Pattern Generator ## Что это Инструментарий для автоматического создания анимированных заставок с процедурно размещёнными паттернами вокруг заголовков. Состоит из трёх компонентов: 1. **Web App** (React + Canvas 2D) — основной генератор 2. **Figma Plugin** — импорт дизайна из Figma 3. **Blender Add-on** — импорт PNG-последовательностей как emissive planes Пайплайн: Figma → Web App (генерация) → PNG-последовательности → Blender (композиция). ## Алгоритм размещения ### 1. Occupancy Grid Сетка 20px на 1920×1080 (96×54 ячеек). Title-элементы занимают ячейки, помечая их как занятые. ```typescript class GridOccupancy { private cells: boolean[][]; // GRID_ROWS × GRID_COLS markRect(x, y, w, h) { /* occupy cells */ } isBlockFree(col, row, spanCols, spanRows) { /* collision check */ } findFreeBlocks(spanCols, spanRows, align) { /* find valid positions */ } } ``` ### 2. BFS Proximity Map BFS от всех title-элементов к каждой ячейке сетки. Определяет «зону влияния» — где паттерны могут появляться относительно заголовка. ```typescript function buildProximityMap(titles): number[][] { const dist: number[][] = fill(Infinity); // BFS from all title edges while (queue.length > 0) { const [cx, cy] = queue.shift(); for (const [dx, dy] of neighbors) { const nd = dist[cy+dy][cx+dx]; if (nd > dist[cy][cx] + 1) { dist[cy+dy][cx+dx] = dist[cy][cx] + 1; queue.push([cx+dx, cy+dy]); } } } } ``` ### 3. Seeded Placement Deterministic RNG (mulberry32). Same seed = identical layout. ```typescript function mulberry32(seed) { let state = seed >>> 0; return () => { state = (state + 0x6D2B79F5) >>> 0; let t = state; t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } ``` ## 7 типов анимации паттернов | Тип | Эффект | CSS/Canvas | |-----|--------|------------| | `dots` | Поэлементный fade-in со stagger | `globalAlpha` per shape | | `capsule` | Рост от центра по длинной оси | Clip rect grow | | `circle` | Pie-fill по часовой | Arc + clip | | `arrow` | Slide up снизу | `translateY` offset | | `square` | Scale 1.6× → 1× | `scale()` transform | | `stripes` | Полосы slide-in staggered | Y-offset per stripe | | `cross` | Scale 1.6× → 1× | `scale()` transform | ## Timeline: 3 фазы ``` 0──────────→ durationMs ──────────→ 2×durationMs ──────────→ 4×durationMs (loop) │ ФАЗА 1 │ ФАЗА 2 │ ФАЗА 3 │ │ Title reveal │ Pattern reveal │ Dots pulse │ │ half-reveal из центра │ clip-side reveal │ sin wave │ ``` ```typescript // Фазовая логика timeline function evaluate(elapsedMs, durationMs, titles, patterns, dots, easing) { // Phase 1: Title reveal (0→1×) const titleT = Math.min(1, elapsedMs / durationMs); const titleEased = applyEasing(titleT, easing); // half-reveal // Phase 2: Pattern reveal (1×→2×) const cycleMs = afterTitle % (4 * durationMs); // Phase 3: Dots pulse (2×→4×) const holdMs = cycleMs - durationMs; const pulse = 0.15 + 0.85 * (0.5 + 0.5 * Math.sin(2π * speed * t + phase)); } ``` ## Color System ```typescript function getPairsForColor(primary: string): ColorPair[] { const lum = luminance(r, g, b); const dark = mixRGB(primary, #000000, 0.45); const light = mixRGB(primary, #FFFFFF, 0.35); if (lum > 0.5) { return [ { bg: primary, fg: '#000000' }, { bg: dark, fg: primary }, { bg: primary, fg: dark }, ]; } // inverted for dark colors } ``` Конвенция SVG-паттернов (40×40): `fill="white"` = фон (перекрашивается runtime), `fill="black"` / `stroke="black"` = foreground. ## Figma Plugin Экспортирует фреймы 1920×1080 в JSON. Каждый слой экспортируется как 2× PNG, цвета автодетектятся. ```javascript // code.js — экспорт слоя bytes = await child.exportAsync({ format: 'PNG', constraint: { type: 'SCALE', value: 2 }, }); // Автодетект цветов collectUsedColors(node, usedColors); // BFS по иерархии ``` ## Blender Add-on Импорт ZIP с 3 PNG-последовательностями (titles, patterns, dots) как emissive planes: ```python # Создаёт plane с aspect-ratio из разрешения PNG plane.scale = (6.0, 6.0, 6.0) plane.rotation_euler = (90°, 0, 0) # Emissive shader node graph tex.image.source = 'SEQUENCE' tex.image_user.use_cyclic = cyclic # dots = True nt.links.new(tex.outputs['Color'], bsdf.inputs['Emission']) nt.links.new(tex.outputs['Alpha'], bsdf.inputs['Alpha']) ``` ## Ключевые параметры | Параметр | Диапазон | Описание | |----------|----------|----------| | density | 1–20 | Количество паттернов (формула: `3 + 40 × (d/10)²`) | | proximity | 1–20 | Зона влияния от title (maxRadius = `2 + proximity × 2.5`) | | stagger | 0–5 | Stagger window: `(stagger/5) × 0.8` | | durationMs | 100–20000 | Длительность фазы | | EXPORT_SCALE | 4 | Рендер в 4× разрешении | | FPS | 24 | Частота кадров | ## Anti-patterns | ❌ Плохо | ✅ Хорошо | |----------|-----------| | Паттерны накладываются на title | BFS proximity map ограничивает placement | | Без collision detection | Occupancy grid предотвращает наложения | | Рандомная расстановка | Seeded RNG — детерминизм для итераций | | Паттерны без анимации | 7 типов анимации с clip-side | | Фиксированные цвета | Алгоритмическая генерация color pairs | | Рендер без bg/fg разделения | white→bg, black→fg runtime colorize | | 1 кадр = 1 экспорт | Layer export: titles/patterns/dots separately | ## Где используется - **patterngen-oss** (github.com/halfof8/patterngen-oss) — open-source референс - Title cards в motion design - Анимированные фоны лендингов - Procedural patterns для социальных сетей - Генерация фоновых текстур для 3D-композиций в Blender ## CV Service Validation Процедурные паттерны рендерятся на Canvas 2D — это клиентская генерация, не серверная. Для валидации дизайна страницы с pattern-генератором: ```bash curl -X POST http://localhost:8001/design/full-analysis \ -H "Content-Type: application/json" \ -d '{"html": "", "full_analysis": true}' ``` **Особенности:** Canvas-элементы не оцениваются по semantic HTML — паттерны — это визуальный слой. Оценивается обёртка (header, sidebar, controls). ## Технические детали ``` Структура проекта: ├── src/store.ts # Zustand store — 373 строки, вся бизнес-логика ├── src/core/ │ ├── patterns.ts # Load SVG, colorize (white→bg, black→fg) │ ├── colors.ts # getPairsForColor, luminance-based │ ├── grid.ts # GridOccupancy + snapToGrid(20px) │ ├── placement.ts # generatePlacement — BFS + occupancy │ └── rng.ts # mulberry32 seeded RNG ├── src/engine/ │ ├── animations.ts # 7 анимаций (drawAnimatedPattern) │ ├── easing.ts # 7 easing функций │ └── timeline.ts # evaluate() — фазовая логика ├── src/export/ │ ├── renderer.ts # renderFrame() — canvas layer compositing │ └── exporter.ts # exportAll() → ZIP with 3 layers × 24fps ├── figma-plugin/ │ ├── code.js # Figma sandbox export │ └── ui.html # Plugin UI └── blender-addon/ └── patterngen_importer.py # Blender 3.4+ operator ``` **Зависимости:** React 18, Zustand 5, JSZip, FileSaver, Vite