Add patterngen-oss: pattern animations, blender pipeline, deterministic generation
This commit is contained in:
1 parent
2b42a76d6d
commit
c7f0be809b
4 files changed
+726
-3
No files matched your search
+8
-3
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Premium Design Wiki — Overview
|
title: Premium Design Wiki — Overview
|
||||||
description: Central index for all 79 design patterns, LLM prompts, and assets.
|
description: Central index for all 82 design patterns, LLM prompts, and assets.
|
||||||
version: 2.1.0
|
version: 2.2.0
|
||||||
date: 2026-06-20
|
date: 2026-06-20
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -22,7 +22,9 @@ Encode premium web design taste into reusable patterns so LLMs can generate prof
|
|||||||
| 🎨 Systems | 6 patterns | ✅ Complete |
|
| 🎨 Systems | 6 patterns | ✅ Complete |
|
||||||
| 🌐 Mobile | 21 token systems | ✅ Complete |
|
| 🌐 Mobile | 21 token systems | ✅ Complete |
|
||||||
| 🎯 Landing | 25 patterns | ✅ Complete |
|
| 🎯 Landing | 25 patterns | ✅ Complete |
|
||||||
| **Total** | **79 patterns** | **✅ Complete** |
|
| 🎬 Blender Pipeline | 1 pipeline spec | ✅ Complete |
|
||||||
|
| ⚙️ Deterministic Gen | 1 methodology | ✅ Complete |
|
||||||
|
| **Total** | **82 patterns + assets** | **✅ Complete** |
|
||||||
|
|
||||||
## 📁 Structure
|
## 📁 Structure
|
||||||
|
|
||||||
@@ -48,6 +50,7 @@ wow-design-wiki/
|
|||||||
│ ├── magnetic-button.md
|
│ ├── magnetic-button.md
|
||||||
│ ├── page-transitions.md
|
│ ├── page-transitions.md
|
||||||
│ ├── parallax-scroll.md
|
│ ├── parallax-scroll.md
|
||||||
|
│ ├── pattern-animations.md
|
||||||
│ ├── procedural-patterns.md
|
│ ├── procedural-patterns.md
|
||||||
│ └── staggered-reveal.md
|
│ └── staggered-reveal.md
|
||||||
├── typography/
|
├── typography/
|
||||||
@@ -56,6 +59,7 @@ wow-design-wiki/
|
|||||||
│ └── kinetic-text.md
|
│ └── kinetic-text.md
|
||||||
├── media/
|
├── media/
|
||||||
│ ├── animated-illustration.md
|
│ ├── animated-illustration.md
|
||||||
|
│ ├── blender-pipeline.md
|
||||||
│ ├── image-parallax.md
|
│ ├── image-parallax.md
|
||||||
│ ├── svg-animation.md
|
│ ├── svg-animation.md
|
||||||
│ └── video-mask.md
|
│ └── video-mask.md
|
||||||
@@ -68,6 +72,7 @@ wow-design-wiki/
|
|||||||
│ ├── component-patterns.md
|
│ ├── component-patterns.md
|
||||||
│ ├── css-architecture.md
|
│ ├── css-architecture.md
|
||||||
│ ├── design-tokens.md
|
│ ├── design-tokens.md
|
||||||
|
│ ├── deterministic-generation.md
|
||||||
│ ├── glassmorphism.md
|
│ ├── glassmorphism.md
|
||||||
│ ├── noise-texture.md
|
│ ├── noise-texture.md
|
||||||
│ └── responsive-patterns.md
|
│ └── responsive-patterns.md
|
||||||
|
|||||||
@@ -0,0 +1,222 @@
|
|||||||
|
# Blender Pipeline — Figma → PatternGen → 3D Compositing
|
||||||
|
|
||||||
|
> Полный конвейер от Figma-дизайна до 3D-compositing в Blender.
|
||||||
|
> Основано на [patterngen-oss](https://github.com/halfof8/patterngen-oss) workflow.
|
||||||
|
|
||||||
|
## Обзор
|
||||||
|
|
||||||
|
```
|
||||||
|
Figma (artboard 1920×1080)
|
||||||
|
↓ Plugin "PatternGen Scene Export"
|
||||||
|
Scene JSON (titles as 2× PNG data URLs + colors + seed)
|
||||||
|
↓ Load в PatternGen web app
|
||||||
|
Procedural pattern generation (Canvas 2D)
|
||||||
|
↓ EXPORT (JSZip + FileSaver)
|
||||||
|
ZIP: scene_titles/*.png, scene_patterns/*.png, scene_dots/*.png
|
||||||
|
↓ Blender addon "PatternGen Importer"
|
||||||
|
3 emissive planes с alpha-blending → Cycles/Eevee render
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Этап 1: Figma → Scene JSON
|
||||||
|
|
||||||
|
### Подготовка артборда
|
||||||
|
|
||||||
|
- Frame размер: **1920×1080 px** (Full HD)
|
||||||
|
- Grid: **20 px** (Layout Grid → Grid → 20 px)
|
||||||
|
- Каждый title — top-level child frame, размеры кратны 20
|
||||||
|
- Название фрейма = `sceneName` (префикс для экспорта)
|
||||||
|
- Цвета: любые (плагин сканирует fills/strokes)
|
||||||
|
|
||||||
|
### Экспорт
|
||||||
|
|
||||||
|
1. Выбрать один или несколько фреймов 1920×1080
|
||||||
|
2. **Plugins → Development → PatternGen Scene Export**
|
||||||
|
3. **Export Selected Frames**
|
||||||
|
4. Результат: `<frameName>_patterngen.json`
|
||||||
|
|
||||||
|
### Структура JSON
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sceneName": "My Title",
|
||||||
|
"seed": 123456789, // 31-bit integer, determinism
|
||||||
|
"colors": ["#FF0000", "#00FF00", "#0000FF"], // detected colors
|
||||||
|
"titles": [
|
||||||
|
{
|
||||||
|
"id": "title_1",
|
||||||
|
"x": 100, // snapped to 20px grid
|
||||||
|
"y": 200,
|
||||||
|
"w": 400,
|
||||||
|
"h": 100,
|
||||||
|
"img": "data:image/png;base64,..." // 2× retina PNG
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"defaults": {
|
||||||
|
"durationMs": 6000,
|
||||||
|
"density": 10,
|
||||||
|
"proximity": 2,
|
||||||
|
"stagger": 3,
|
||||||
|
"theme": "dark"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Что экспортируется
|
||||||
|
|
||||||
|
- **Titles**: 2× PNG (retina), data URL, позиции snapped к сетке
|
||||||
|
- **Color set**: уникальные цвета fills/strokes (кроме #000 и #FFF)
|
||||||
|
- **Seed**: 31-bit integer — детерминированная генерация паттернов
|
||||||
|
- **Scene defaults**: продолжительность, плотность, proximity, stagger, тема
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Этап 2: PatternGen Web App
|
||||||
|
|
||||||
|
### Загрузка
|
||||||
|
|
||||||
|
1. Открыть PatternGen (localhost:5173)
|
||||||
|
2. Кнопка **LOAD** → выбрать `*_patterngen.json`
|
||||||
|
3. Паттерны генерируются **детерминированно по seed**
|
||||||
|
4. Сцена сразу готова к **PLAY**
|
||||||
|
|
||||||
|
### Ручное редактирование
|
||||||
|
|
||||||
|
- **Drag & Drop** PNG/JPG изображений на canvas (snap to 20 px grid)
|
||||||
|
- **Drag** для перемещения, drag off canvas для удаления
|
||||||
|
- **+ ADD COLOR** — до 3 кастомных цветов (color picker)
|
||||||
|
- Клик по swatch — toggle on/off
|
||||||
|
|
||||||
|
### Параметры
|
||||||
|
|
||||||
|
| Параметр | Диапазон | Описание |
|
||||||
|
|-------------|---------|----------|
|
||||||
|
| **Density** | 1–20 | Количество паттерн-элементов |
|
||||||
|
| **Proximity** | 1–20 | Минимальное расстояние от titles |
|
||||||
|
| **Stagger** | 0–5 | Разброс animation delay |
|
||||||
|
| **Grid** | toggle | 20 px reference grid |
|
||||||
|
| **Theme** | dark/light | UI theme |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Этап 3: Экспорт PNG-секвенций
|
||||||
|
|
||||||
|
### Формат ZIP
|
||||||
|
|
||||||
|
```
|
||||||
|
scene_pattern_gen.zip
|
||||||
|
├── titles/
|
||||||
|
│ scene_titles_00000.png
|
||||||
|
│ scene_titles_000001.png
|
||||||
|
│ ...
|
||||||
|
├── patterns/
|
||||||
|
│ scene_patterns_00000.png
|
||||||
|
│ ...
|
||||||
|
└── dots/
|
||||||
|
scene_dots_00000.png
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Параметры экспорта
|
||||||
|
|
||||||
|
- **Scale**: 4× (EXPORT_SCALE = 4 → 7680×4320px)
|
||||||
|
- **FPS**: 24
|
||||||
|
- **Фон**: прозрачный (RGBA)
|
||||||
|
- **Слои**: 3 отдельных (titles, patterns, dots)
|
||||||
|
|
||||||
|
### Фазы рендера
|
||||||
|
|
||||||
|
```
|
||||||
|
titles: durationMs (1×) — plays once
|
||||||
|
patterns: durationMs (1×) — plays once
|
||||||
|
dots: 4000ms loop — infinite loop
|
||||||
|
```
|
||||||
|
|
||||||
|
### Easing при экспорте
|
||||||
|
|
||||||
|
Выбирается один из 7 типов:
|
||||||
|
`linear`, `easeIn`, `easeOut`, `easeInOut`, `easeInCubic`, `easeOutCubic`, `easeInOutCubic`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Этап 4: Blender Import
|
||||||
|
|
||||||
|
### Установка
|
||||||
|
|
||||||
|
1. Blender → **Edit → Preferences → Add-ons → Install…**
|
||||||
|
2. Выбрать `patterngen_importer.py`
|
||||||
|
3. Поставить галочку для активации
|
||||||
|
4. Открыть N-panel → вкладка **PatternGen**
|
||||||
|
|
||||||
|
### Импорт сцены
|
||||||
|
|
||||||
|
1. Установить **Scene Folder** на распакованную папку
|
||||||
|
2. Кнопка **Import Scene Folders**
|
||||||
|
3. Три planes создаются в origin
|
||||||
|
|
||||||
|
### Конфигурация слоёв
|
||||||
|
|
||||||
|
| Layer | Cyclic | Location (m) | Rotation | Scale |
|
||||||
|
|-------|--------|-------------|----------|-------|
|
||||||
|
| titles | off | (0, 0, 0) | 90° на X | 6×6×6 |
|
||||||
|
| patterns | off | (0, 0.1, 0) | 90° на X | 6×6×6 |
|
||||||
|
| dots | **on** | (0, 0.2, 0) | 90° на X | 6×6×6 |
|
||||||
|
|
||||||
|
### Node Tree материалов
|
||||||
|
|
||||||
|
```
|
||||||
|
Image Texture (SEQUENCE, auto-refresh ON)
|
||||||
|
Color ─┬─► Principled BSDF › Base Color
|
||||||
|
└─► Principled BSDF › Emission Color (strength = 1)
|
||||||
|
Alpha ───► Principled BSDF › Alpha
|
||||||
|
```
|
||||||
|
|
||||||
|
### Image Sequence настройки
|
||||||
|
|
||||||
|
- **Start Frame**: 0
|
||||||
|
- **Frames**: количество PNG в папке
|
||||||
|
- **Offset**: 0
|
||||||
|
- **Auto Refresh**: ON
|
||||||
|
- **Cyclic**: ON для `dots`, OFF для `titles`/`patterns`
|
||||||
|
|
||||||
|
### Sync Start Frame
|
||||||
|
|
||||||
|
1. Выбрать plane(s) (Shift+click или A для всех)
|
||||||
|
2. В секции **Sync Start Frame** ввести новое значение
|
||||||
|
3. **Apply to Selected** — все image-sequence текстуры обновлены
|
||||||
|
|
||||||
|
### Замечания
|
||||||
|
|
||||||
|
- **Blend Method**: `BLEND` (alpha renders clean в Eevee/Cycles)
|
||||||
|
- При артефактах сортировки переключить на `HASHED`
|
||||||
|
- Повторный импорт создаёт дубликаты (`.001`, `.002`) — удалить вручную
|
||||||
|
- Отступ 0.1m между слоями — только для предотвращения z-fighting
|
||||||
|
- Plane scale настроен по aspect ratio изображения (16:9)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Полный конвейер в цифрах
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
|----------|---------|
|
||||||
|
| Canvas | 1920×1080 |
|
||||||
|
| Export Scale | 4× (7680×4320) |
|
||||||
|
| FPS | 24 |
|
||||||
|
| Grid | 20 px |
|
||||||
|
| Duration | 6000ms (default) |
|
||||||
|
| Dots Loop | 4000ms |
|
||||||
|
| Cycle | 4× duration = 24s |
|
||||||
|
| Patterns | 7 animation types |
|
||||||
|
| Colors | 2 fixed + 3 custom max |
|
||||||
|
| Export Layers | 3 (titles, patterns, dots) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Референсы кода
|
||||||
|
|
||||||
|
- `figma-plugin/README.md` — Figma plugin setup, export format
|
||||||
|
- `blender-addon/README.md` — Blender addon installation, node tree
|
||||||
|
- `src/export/exporter.ts` — PNG sequence export, JSZip, FileSaver
|
||||||
|
- `src/engine/timeline.ts` — Phase 1/2/3 timing, dots loop
|
||||||
|
- `src/types.ts` — CANVAS_W/H, EXPORT_SCALE, FPS, GRID_SIZE
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
# Pattern Animations — 7 типов процедурных анимаций
|
||||||
|
|
||||||
|
> Основано на реализациях из [patterngen-oss](https://github.com/halfof8/patterngen-oss).
|
||||||
|
> 7 анимационных типов для SVG-паттернов, рендерящихся через Canvas 2D с таймлайном.
|
||||||
|
|
||||||
|
## Обзор
|
||||||
|
|
||||||
|
Каждый паттерн — это SVG-файл 40×40 px, который анимируется одним из 7 типов.
|
||||||
|
Рендер происходит на Canvas 2D с 4× масштабированием (EXPORT_SCALE = 4).
|
||||||
|
Базовый кадр 1920×1080, сетка 20 px, FPS = 24.
|
||||||
|
|
||||||
|
## Таймлайн
|
||||||
|
|
||||||
|
```
|
||||||
|
Фаза 1 (0 — durationMs): Title reveal (half-reveal с easing)
|
||||||
|
Фаза 2 (durationMs — 2×): Patterns reveal + squares reveal (staggered)
|
||||||
|
Фаза 3 (2× — 4× durationMs): Patterns hold + dots pulsation (loop 4s)
|
||||||
|
Цикл: repeat cycle = 4 × durationMs
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Dots — fade-in с stagger
|
||||||
|
|
||||||
|
Каждый SVG-элемент проявляется индивидуально с рандомной задержкой.
|
||||||
|
|
||||||
|
**Механика:**
|
||||||
|
```
|
||||||
|
dotP = max(0, min(1, (progress - stagger * 0.6) / 0.4))
|
||||||
|
globalAlpha = dotP
|
||||||
|
```
|
||||||
|
|
||||||
|
**Использование:**
|
||||||
|
- Мягкое проявление множества мелких элементов
|
||||||
|
- Звёздные/точечные паттерны, частицы, текстуры
|
||||||
|
- Элементы с `stagger` из SVG (случайный при парсинге)
|
||||||
|
|
||||||
|
**Параметры:**
|
||||||
|
- `stagger` определяет задержку: 0 = мгновенно, 5 = максимальный разброс
|
||||||
|
- Визуально: элементы появляются поочерёдно, создавая «волну»
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Capsule — рост из центра по длинной оси
|
||||||
|
|
||||||
|
Каждый элемент масштабируется от центра вдоль своей более длинной оси.
|
||||||
|
|
||||||
|
**Механика:**
|
||||||
|
```
|
||||||
|
elP = max(0, min(1, (progress - stagger * 0.3) / 0.7))
|
||||||
|
|
||||||
|
if (w >= h) {
|
||||||
|
// горизонтальный: ширина растёт, высота стоит
|
||||||
|
animW = w * elP
|
||||||
|
animX = cx - animW / 2
|
||||||
|
} else {
|
||||||
|
// вертикальный: высота растёт, ширина стоит
|
||||||
|
animH = h * elP
|
||||||
|
animY = cy - animH / 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Использование:**
|
||||||
|
- Анимация линий, полос, разделителей
|
||||||
|
- Эффект «заполнения» вдоль направления
|
||||||
|
- Элементы с rotation поддерживаются (через rotate transform)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Circle — pie-fill clockwise reveal
|
||||||
|
|
||||||
|
Полное SVG-изображение проявляется секторально, как таймер.
|
||||||
|
|
||||||
|
**Механика:**
|
||||||
|
```
|
||||||
|
cx = size / 2
|
||||||
|
cy = size / 2
|
||||||
|
startAngle = -PI / 2
|
||||||
|
endAngle = startAngle + PI * 2 * progress
|
||||||
|
|
||||||
|
ctx.arc(cx, cy, size, startAngle, endAngle)
|
||||||
|
ctx.drawImage(img, 0, 0, size, size)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Использование:**
|
||||||
|
- Круговые паттерны, диаграммы, радары
|
||||||
|
- Прогресс-индикаторы, loading-анимации
|
||||||
|
- Полное изображение, не по-элементно
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Arrow — slide up снизу
|
||||||
|
|
||||||
|
Полное изображение физически сдвигается вверх из-за нижнего края.
|
||||||
|
|
||||||
|
**Механика:**
|
||||||
|
```
|
||||||
|
offset = size * (1 - progress)
|
||||||
|
ctx.drawImage(img, 0, offset, size, size)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Использование:**
|
||||||
|
- Элементы «влетают» снизу
|
||||||
|
- Эффект подъёма, выдвижения
|
||||||
|
- Подходит для контрастных паттернов на тёмном фоне
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Square — scale down 1.6× → 1×
|
||||||
|
|
||||||
|
Изображение масштабируется от 1.6× до 1× (на 60% больше до нормального).
|
||||||
|
|
||||||
|
**Механика:**
|
||||||
|
```
|
||||||
|
s = 1 + (1 - progress) * 0.6
|
||||||
|
cx = size / 2
|
||||||
|
cy = size / 2
|
||||||
|
ctx.translate(cx, cy)
|
||||||
|
ctx.scale(s, s)
|
||||||
|
ctx.translate(-cx, -cy)
|
||||||
|
ctx.drawImage(img, 0, 0, size, size)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Использование:**
|
||||||
|
- Резкий акцент, «взрывное» проявление
|
||||||
|
- Привлечение внимания к паттерну
|
||||||
|
- Квадратные/крестообразные элементы
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Stripes — slide-in слева направо
|
||||||
|
|
||||||
|
Каждая полоса (элемент) сдвигается сверху вниз, сортировка по X.
|
||||||
|
|
||||||
|
**Механика:**
|
||||||
|
```
|
||||||
|
sorted = shapes.sort((a, b) => a.x - b.x)
|
||||||
|
stripeStart = (i / n) * 0.5
|
||||||
|
stripeP = max(0, min(1, (progress - stripeStart) / 0.5))
|
||||||
|
slideOffset = (1 - stripeP) * -size
|
||||||
|
ctx.translate(0, slideOffset)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Использование:**
|
||||||
|
- Полосатые паттерны, штрих-код текстуры
|
||||||
|
- Горизонтальные линии, решётки
|
||||||
|
- Эффект «сканирования» слева направо
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Cross — scale down 1.6× → 1×
|
||||||
|
|
||||||
|
Тот же алгоритм, что и `square`, но для крестообразных элементов.
|
||||||
|
Разница исключительно в семантике и визуальном контексте.
|
||||||
|
|
||||||
|
**Механика:**
|
||||||
|
```
|
||||||
|
// identical to square
|
||||||
|
s = 1 + (1 - progress) * 0.6
|
||||||
|
ctx.scale(s, s)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Easing функции
|
||||||
|
|
||||||
|
| Название | Формула |
|
||||||
|
|---------------|---------|
|
||||||
|
| `linear` | `t` |
|
||||||
|
| `easeIn` | `t²` |
|
||||||
|
| `easeOut` | `t(2-t)` |
|
||||||
|
| `easeInOut` | `2t²` при t<0.5, `-1+(4-2t)t` при t≥0.5 |
|
||||||
|
| `easeInCubic` | `t³` |
|
||||||
|
| `easeOutCubic`| `1-(1-t)³` |
|
||||||
|
| `easeInOutCubic` | `4t³` при t<0.5, `1-(-2t+2)³/2` при t≥0.5 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SVG Spec
|
||||||
|
|
||||||
|
Каждый SVG-паттерн должен соответствовать формату:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<svg width="40" height="40" viewBox="0 0 40 40" fill="none">
|
||||||
|
<rect width="40" height="40" fill="white"/>
|
||||||
|
<!-- foreground shapes -->
|
||||||
|
<circle cx="20" cy="20" r="14" fill="black"/>
|
||||||
|
<rect x="10" y="10" width="20" height="20" rx="4" fill="black" stroke="black"/>
|
||||||
|
</svg>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Правила:**
|
||||||
|
1. Размер: 40×40 px (viewBox 0 0 40 40)
|
||||||
|
2. Фон: `<rect width="40" height="40" fill="white"/>` (переокрашивается в runtime)
|
||||||
|
3.Foreground: `fill="black"` и/или `stroke="black"` (переокрашивается)
|
||||||
|
4. Тег `<defs>` игнорируется при парсинге
|
||||||
|
5. Вложенные `<g>` обрабатываются рекурсивно
|
||||||
|
6. Анимация определяется типом анимации, не структурой SVG
|
||||||
|
|
||||||
|
**Поддерживаемые теги:** `<rect>`, `<circle>`, `<path>`
|
||||||
|
|
||||||
|
**Transform:** `rotate(angle, ox, oy)` — угол поворота и точка вращения.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Референсы кода
|
||||||
|
|
||||||
|
- `src/engine/animations.ts` — 7 switch-касов для каждого animType
|
||||||
|
- `src/engine/timeline.ts` — фаза titles → patterns → dots (цикл 4× duration)
|
||||||
|
- `src/engine/easing.ts` — 7 easing функций
|
||||||
|
- `src/core/patterns.ts` — парсинг SVG, colorizeSvg, svgToImage
|
||||||
|
- `src/export/exporter.ts` — экспорт PNG-секвенций (titles, patterns, dots)
|
||||||
|
- `src/types.ts` — PatternAnimType, SvgShapeInfo, PatternDef
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
# Deterministic Generation — Seed-Based Layout Methodology
|
||||||
|
|
||||||
|
> Детерминированная генерация паттернов с использованием seeded PRNG.
|
||||||
|
> Основано на [patterngen-oss](https://github.com/halfof8/patterngen-oss).
|
||||||
|
|
||||||
|
## Обзор
|
||||||
|
|
||||||
|
Каждая генерация паттернов **детерминирована** — одинаковый seed всегда даёт
|
||||||
|
идентичный layout. Это позволяет:
|
||||||
|
- Повторно воспроизводить удачные комбинации
|
||||||
|
- Синхронизировать дизайн между Figma и PatternGen
|
||||||
|
- Кэшировать результаты генерации
|
||||||
|
- Делиться сценами через JSON с seed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Seeded PRNG
|
||||||
|
|
||||||
|
### Алгоритм
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class SeededRNG {
|
||||||
|
private seed: number;
|
||||||
|
|
||||||
|
constructor(seed: number) {
|
||||||
|
this.seed = seed & 0x7FFFFFFF; // 31-bit positive
|
||||||
|
}
|
||||||
|
|
||||||
|
next(): number {
|
||||||
|
// LCG: Linear Congruential Generator
|
||||||
|
this.seed = (this.seed * 1103515245 + 12345) & 0x7FFFFFFF;
|
||||||
|
return this.seed / 0x7FFFFFFF; // range [0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextInt(min: number, max: number): number {
|
||||||
|
return Math.floor(this.next() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
pick<T>(array: T[]): T {
|
||||||
|
return array[this.nextInt(0, array.length - 1)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Seed из Figma
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"seed": 123456789
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
31-bit integer генерируется плагином Figma. Значение зашито в JSON
|
||||||
|
и передается в PatternGen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Placement Engine
|
||||||
|
|
||||||
|
### Input parameters
|
||||||
|
|
||||||
|
| Параметр | Тип | Диапазон | Описание |
|
||||||
|
|----------|-----|----------|----------|
|
||||||
|
| `seed` | int | 0–2³¹-1 | Seed для PRNG |
|
||||||
|
| `density` | int | 1–20 | Количество элементов |
|
||||||
|
| `proximity` | int | 1–20 | Мин. расстояние до title (20 px grid units) |
|
||||||
|
|
||||||
|
### Placement algorithm
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Создать RNG из seed
|
||||||
|
2. Для каждого элемента:
|
||||||
|
a. Сгенерировать случайную позицию (x, y)
|
||||||
|
b. Проверить proximity к существующим элементам и title
|
||||||
|
c. Если too close → пересадить с тем же RNG state
|
||||||
|
d. Иначе → записать позицию
|
||||||
|
3. Для каждого элемента:
|
||||||
|
a. Выбрать случайный pattern из доступных
|
||||||
|
b. Выбрать случайную clipSide (top/bottom/left/right)
|
||||||
|
c. Выбрать случайную анимацию
|
||||||
|
d. Сгенерировать stagger delay
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stagger calculation
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const staggerWindow = Math.max(0, Math.min(0.85, (stagger / 5) * 0.8));
|
||||||
|
const staggeredStart = element.animDelay * staggerWindow * durationMs;
|
||||||
|
const elemDur = durationMs * (1 - staggerWindow);
|
||||||
|
const localMs = cycleMs - staggeredStart;
|
||||||
|
const p = Math.max(0, Math.min(1, localMs / elemDur));
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Animation Timeline
|
||||||
|
|
||||||
|
### Фаза 1: Title Reveal (0 — durationMs)
|
||||||
|
|
||||||
|
Title проявляется из центра по горизонтали (если wider) или вертикали (if taller).
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const titleT = min(1, max(0, elapsedMs / durationMs));
|
||||||
|
const titleEased = applyEasing(titleT, easing);
|
||||||
|
|
||||||
|
if (title.w >= title.h) {
|
||||||
|
// wide title: half-reveal horizontally
|
||||||
|
halfReveal = (title.w / 2) * titleEased;
|
||||||
|
revealBox = {
|
||||||
|
x: cx - halfReveal,
|
||||||
|
y: title.y,
|
||||||
|
w: halfReveal * 2,
|
||||||
|
h: title.h
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// tall title: half-reveal vertically
|
||||||
|
halfReveal = (title.h / 2) * titleEased;
|
||||||
|
revealBox = {
|
||||||
|
x: title.x,
|
||||||
|
y: cy - halfReveal,
|
||||||
|
w: title.w,
|
||||||
|
h: halfReveal * 2
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Фаза 2: Pattern Reveal (durationMs — 2× durationMs)
|
||||||
|
|
||||||
|
Паттерны проявляются с stagger-задержкой.
|
||||||
|
|
||||||
|
### Фаза 3: Dots Loop (2× durationMs — 4× durationMs)
|
||||||
|
|
||||||
|
Точки пульсируют синусоидой 4000ms цикл.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const fadeIn = min(1, loopMs / 800);
|
||||||
|
const wave = 0.5 + 0.5 * sin(2 * PI * dot.blinkSpeed * (loopMs / 1000) + dot.blinkPhase * 2 * PI);
|
||||||
|
const pulse = 0.15 + 0.85 * wave;
|
||||||
|
opacity = pulse * fadeIn;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Color System
|
||||||
|
|
||||||
|
### Fixed Colors
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const FIXED_COLORS = ['#000000', '#FFFFFF'];
|
||||||
|
```
|
||||||
|
|
||||||
|
Черный и белый всегда доступны.
|
||||||
|
|
||||||
|
### Custom Colors
|
||||||
|
|
||||||
|
До 3 кастомных цветов добавляются через color picker.
|
||||||
|
Color pairs для паттернов генерируются алгоритмически:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Для каждого pair custom_color:
|
||||||
|
bg = customColor;
|
||||||
|
fg = generateComplementary(customColor);
|
||||||
|
```
|
||||||
|
|
||||||
|
### SVG Colorization
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function colorizeSvg(svgText: string, colors: ColorPair): string {
|
||||||
|
return svgText
|
||||||
|
.replace(/fill="white"/g, `fill="${colors.bg}"`)
|
||||||
|
.replace(/fill="black"/g, `fill="${colors.fg}"`)
|
||||||
|
.replace(/stroke="black"/g, `stroke="${colors.fg}"`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
SVG-паттерны хранят только `white` (фон) и `black` (foreground).
|
||||||
|
Окрашивание происходит в runtime.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Image Caching
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const imageCache = new Map<string, HTMLImageElement>();
|
||||||
|
|
||||||
|
export function svgToImage(svgText: string): Promise<HTMLImageElement> {
|
||||||
|
if (imageCache.has(svgText)) return Promise.resolve(imageCache.get(svgText)!);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
const blob = new Blob([svgText], { type: 'image/svg+xml;charset=utf-8' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
img.onload = () => {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
imageCache.set(svgText, img);
|
||||||
|
resolve(img);
|
||||||
|
};
|
||||||
|
img.onerror = () => {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
reject(new Error('Failed to load SVG image'));
|
||||||
|
};
|
||||||
|
img.src = url;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearImageCache() {
|
||||||
|
imageCache.clear();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Export Pipeline
|
||||||
|
|
||||||
|
### Layer structure
|
||||||
|
|
||||||
|
```
|
||||||
|
titles: durationMs (1×) — plays once
|
||||||
|
patterns: durationMs (1×) — plays once
|
||||||
|
dots: 4000ms loop — infinite loop
|
||||||
|
```
|
||||||
|
|
||||||
|
### Export scale
|
||||||
|
|
||||||
|
- Canvas: 1920×1080
|
||||||
|
- Export Scale: 4× → 7680×4320
|
||||||
|
- FPS: 24
|
||||||
|
- Format: PNG (RGBA, transparent background)
|
||||||
|
|
||||||
|
### Frame calculation
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const totalFrames = Math.ceil((layerDuration / 1000) * FPS);
|
||||||
|
const frameRatio = totalFrames > 1 ? frame / (totalFrames - 1) : 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Naming convention
|
||||||
|
|
||||||
|
```
|
||||||
|
<sceneName>_<layer>_<frame>.png
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
MyTitle_titles_00000.png
|
||||||
|
MyTitle_titles_00001.png
|
||||||
|
MyTitle_patterns_00000.png
|
||||||
|
MyTitle_dots_00000.png
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Использование determinism в дизайне
|
||||||
|
|
||||||
|
### Scenario 1: Figma → PatternGen sync
|
||||||
|
|
||||||
|
1. Экспортировать сцену из Figma (содержит seed)
|
||||||
|
2. Загрузить в PatternGen
|
||||||
|
3. Паттерны генерируются точно так же каждый раз
|
||||||
|
4. Можно итерировать параметры и снова генерировать тот же layout
|
||||||
|
|
||||||
|
### Scenario 2: A/B testing
|
||||||
|
|
||||||
|
1. Создать базовую сцену с seed
|
||||||
|
2. Экспортировать 10 раз с разными параметрами density/stagger
|
||||||
|
3. Каждый раз layout идентичен, но timing разный
|
||||||
|
4. Сравнить конверсию/визуальное впечатление
|
||||||
|
|
||||||
|
### Scenario 3: Team collaboration
|
||||||
|
|
||||||
|
1. Дизайнер делится scene JSON
|
||||||
|
2. Коллега загружает и видит ту же генерацию
|
||||||
|
3. Может менять цвета, паттерны, параметры
|
||||||
|
4. Seed остаётся — layout predictable
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Референсы кода
|
||||||
|
|
||||||
|
- `src/core/rng.ts` — SeededRNG класс, LCG algorithm
|
||||||
|
- `src/core/placement.ts` — Placement engine, proximity check
|
||||||
|
- `src/engine/timeline.ts` — Phase 1/2/3, dots loop
|
||||||
|
- `src/export/exporter.ts` — PNG export, frame calculation
|
||||||
|
- `src/export/renderer.ts` — Canvas rendering per frame
|
||||||
|
- `src/types.ts` — AnimationState, PatternAnimType
|
||||||
Reference in new issue
Block a user