несмотря на то что круто, компактно! но мне кажется последние картинки потеряли красоту чтоль…)))
а может просто стартовая картинка не удачная… а может и вовсе кажется)))
Сколько времени уходит на такой полный цикл перебора?
Смена одного узора на другой около 3,5 сек. (именно она как анимация гораздо зрелищнее самого узора). Полного цикла перебора нет, каждый раз генерится новый узор по генерации сектора. Типа такого в полном круге:
Нет картинок, есть их генерация. Но они конечно же уступают реальным картинкам. У диаграмм подобных диаграмме Вороного в частичке-локусе цвет постоянный, а в реальном куске стекла нет ![]()
…сам вот такую формулу-генерацию выдумал
и узоры, они конечно однотипны
да, я понял, может мне просто звезды нравятся, но вот эти картинки мне кажется смотрятся лучше)))
Вот практика и показывает, что статичные нравятся одни, а динамичные совсем другие. При анимации мне нравится именно смена, сознание ( моё) не успевает отделить нарастающие границы секторов смены и узор как бы “перерождается” на глазах из одной формы в другую плавно.
Большая линза кстати помимо увеличения вида дисплея создаёт интересный эффект - дисторсию (бочкообразную).
Узор как бы натянут на сферу, а не на плоскость. Пробовал фотографировать, но на снимке его нет, только для глаз.
На палитре 256 цветов такое сложно получить.
В первом приближении - за каждым полигоном находится “свертка” из подлежащего под ним похожего узора. Попробуйте для начала сгенерировать картинку в градиентах серого (один канал), в пределах одного полигона под ним сгенерировать аналогичную картинку чуть в меньшем масштабе и размере и пропустить ее через шум перлина. Сначала по цвету (яркости). Потом попробовать составляющую шума использовать не как коэффициент яркости, а как коэффициент отклонения по координате. Если осилите, то точка в шуме по значениям соседних точек дает направление своей нормали, нормаль можно использовать как отклонение по преломлению “смотрим через воду или волнистое стекло“. Конечно, в честном RGB это уже красиво. Если добъетесь приятного ЧБ, можно подумать над эмуляцией палитры для полного цвета (будет постеризация, но тем не менее).
По сути алгоритм такой.
- Набор точек для диаграмы вороного верхнего слоя.
- Массив с шумом перлина (можно для экономии памяти сгененрировать циклический узор из повторяющейся картинки, нужна уникальность размером с локус.
- Набор точек для диаграмы вороного второго слоя.
- Взять точку верхнего слоя, базовый цвет.
- Взять под ней точку слоя шума и найти смещение по координате.
- По этому смещению относительно текущей точки взять цвет второго слоя.
- Смешать полученные цвета.
Дальше можно пробовать 5-7 делать каскадно. Можно еще размытие (шум гаусса) на нижние слои накладывать.
В фотошопе все шаги можно попробовать. Там фильтры на эти эффекты есть. Будет настроение, что-то попробую.
Чтобы не париться со слоями вороного и для экономии памяти можно один и тот же слой на каждом погружении просто брать с другим смещением (за узором не разглядишь, что смотрим сами на себя через себя).
Задачка технически интересна отсутствием памяти для хранения промежуточных результатов. На компе такое на раз делается без извращений с постоянным вычислением вглубь для каждой точки. Там сделал слой, и его используешь как фон для искажений для следующего слоя, и так сколько угодно, причем в честном RGB.
Мишутк и lilik извиняюсь за вопрос не по теме, но мне только спросить, и срооочно)) я потерял код, не знаете как это кодом реализовать ?
рисуем стрелку на бумажке стрелку ( ↑ ) переворачиваем и рисуем с другой стороны такую же, теперь мы смотрим на стрелку только с одной стороны, выводя 2 стороннюю стрелку, если изначальный квадрат в котором нарисована двухсторонняя стрелка сейчас она смотрит вверх
квадрат выглядит так
а б
в г
переворачиваем рисунок что бы квадрат стал
г б
в а
или что бы стал
а в
б г
при таких поворотах стрелка изменит направление и будет смотреть влево или вправо, как это описать кодом ?)))
кстате может и вам пригодится как то…
Не ясен вопрос. Как вращать стрелку?
#include <Adafruit_GFX.h>
#include <Adafruit_GC9A01A.h>
#include <SPI.h>
#define TFT_DC 17
#define TFT_CS 16
Adafruit_GC9A01A tft(TFT_CS, TFT_DC);
// Константы для улучшения читаемости
const int CENTER_X = 120;
const int CENTER_Y = 120;
const int STR_LENGTH = 100;
const float ANGLE_STEP = PI / 30.0;
float prevAngle = -PI/2 - ANGLE_STEP;
byte seconds = 0;
unsigned long lastUpdate = 0;
const int UPDATE_INTERVAL = 1000; // 1 секунда
void setup() {
tft.begin();
tft.fillScreen(GC9A01A_BLACK);
tft.cp437(true);
tft.setRotation(2);
// Рисуем статичные элементы
drawStaticElements();
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - lastUpdate >= UPDATE_INTERVAL) {
drawStaticElements();
lastUpdate = currentMillis;
updateTimer();
// Центральный круг
tft.fillCircle(CENTER_X, CENTER_Y, 11, GC9A01A_ORANGE);
tft.fillCircle(CENTER_X, CENTER_Y, 5, GC9A01A_BLACK);
}
}
void drawStaticElements() {
// Обод секундомера
for (int r = 107; r <= 109; r++) {
tft.drawCircle(CENTER_X, CENTER_Y, r, GC9A01A_WHITE);
}
// Заголовок
tft.setCursor(60, 85);
tft.setTextSize(2);
tft.setTextColor(GC9A01A_GREEN);
tft.println(utf8rus("СЕКУНДОМЕР"));
// Шкала
for (float i = 0.01; i < 2 * PI; i += ANGLE_STEP) {
tft.drawLine(
CENTER_X + cos(i) * STR_LENGTH * 0.90,
CENTER_Y + sin(i) * STR_LENGTH * 0.90,
CENTER_X + cos(i) * STR_LENGTH,
CENTER_Y + sin(i) * STR_LENGTH,
GC9A01A_GREEN
);
}
for (float i = 0.01; i < 2 * PI; i += PI / 6) {
tft.drawLine(
CENTER_X + cos(i) * STR_LENGTH * 0.75,
CENTER_Y + sin(i) * STR_LENGTH * 0.75,
CENTER_X + cos(i) * STR_LENGTH,
CENTER_Y + sin(i) * STR_LENGTH,
GC9A01A_ORANGE
);
}
}
void updateTimer() {
// Обновляем секунды
seconds++;
if (seconds > 59) seconds = 0;
float newAngle = seconds * ANGLE_STEP - PI/2 - ANGLE_STEP;
// Обновляем цифровое отображение
tft.fillRect(100, 150, 50, 40, GC9A01A_BLACK);
tft.setCursor(100, 150);
tft.setTextSize(4);
tft.setTextColor(GC9A01A_GREEN);
if (seconds < 10) tft.print("0");
tft.println(seconds);
// Стираем предыдущую стрелку
drawArrow(prevAngle, GC9A01A_BLACK);
// Рисуем новую стрелку
drawArrow(newAngle, GC9A01A_WHITE);
prevAngle = newAngle;
}
void drawArrow(float angle, uint16_t color) {
tft.fillTriangle(
CENTER_X + cos(angle + 1.5) * STR_LENGTH * 0.1,
CENTER_Y + sin(angle + 1.5) * STR_LENGTH * 0.1,
CENTER_X + cos(angle) * STR_LENGTH,
CENTER_Y + sin(angle) * STR_LENGTH,
CENTER_X + cos(angle - 1.5) * STR_LENGTH * 0.1,
CENTER_Y + sin(angle - 1.5) * STR_LENGTH * 0.1,
color
);
}
// Функция перекодировки русских букв из UTF-8 в Win-1251
String utf8rus(String source) {
int i, k;
String target;
unsigned char n;
char m[2] = { '0', '\0' };
k = source.length(); i = 0;
while (i < k) {
n = source[i]; i++;
if (n >= 0xC0) {
switch (n) {
case 0xD0: {
n = source[i]; i++;
if (n == 0x81) { n = 0xA8; break; }
if (n >= 0x90 && n <= 0xBF) n = n + 0x30;
break;
}
case 0xD1: {
n = source[i]; i++;
if (n == 0x91) { n = 0xB8; break; }
if (n >= 0x80 && n <= 0x8F) n = n + 0x70;
break;
}
}
}
m[0] = n; target = target + String(m);
}
return target;
}
https://ya.ru/video/preview/11033177292612752758 я вот про это, примерно…
единый объект вращаем переворачивая, и получаем разный рисунок…
помню думал как это описать кодом, часа 3-5 наверное потратил, и не знаю куда дел исходный код))))
Да и хрен с ним, вон человек за деньги помощь просит, можно переключиться ![]()
Не, я пока озадачиваюсь оттенками цвета локуса в пределах этого локуса ![]()
Вопрос на самом деле ясен: любые перемещения в пространстве, включая зеркальное отображение и масштабирование.
Делаем так: вводим однородные координаты, для всех элементарных преобрахований:
- перемещение,
- поворот,
- зеркальное отображение,
- масштабирование.
записываем матрицы.
Дальше перемножением матриц в нужном порядке достигаем любого преобразования.
я пока что не уверен что это мой клиент))) хотя похож…
а может мне код готовый надо…)))
спс, завтра попробую, может напишу
Обьект описан двумя наборами точек/линий. При угле поворота в одной полуплоскости 0..180 на отрисовку берем первый набор, при угле 180..360 второй набор. Это конкретно для моделирования данного видео. Тут вообще двумерные преобразования - повернуть точку в полярных координатах.
Вопросы по готовому коду размещаются в другом разделе.
Наконец-то мандала получилась
Жизненный цикл 90 секунд, чёрное вытесняется цветным постепенно.
Зрелая мандала меняется быстро и незаметно, не по секторам визуально(реально по секторам), а по областям: центр, середина, край - всё случайный образом и постепенно
Для этого диаграмму Вороного надо укрыть чёрными пятнами по центрам локусов
#include <Adafruit_GFX.h>
#include <Adafruit_GC9A01A.h>
#include <SPI.h>
#define TFT_DC 17
#define TFT_CS 16
/*
SCL --> 18 (SCK)
SDA --> 23 (MOSI)
RST --> EN
*/
Adafruit_GC9A01A tft(TFT_CS, TFT_DC);
// Константы координат смещения центра
const int CENTER_X = 120;
const int CENTER_Y = 120;
const float Z = 300.0; //от 100.0 до 1000.0 регулировка качества отрисовки пикселей с пропусками и без них
struct Point {
float x, y;
uint16_t color;
};
const int NUM_POINTS = 40; // Количество точек для диаграммы Вороного
Point points[NUM_POINTS];
long Y=0;//
void setup() {
// Инициализация генератора случайных чисел
randomSeed(analogRead(32));
tft.begin();
tft.fillScreen(GC9A01A_BLACK);
// tft.cp437(true);
tft.setRotation(2);
ran_Sait(3);
}
void loop() {
if (millis() - Y > 90000L) {//новый цикл роста узоров по условие каждые 90 секунд
tft.fillScreen(GC9A01A_BLACK);
Y=millis();
}
for (byte v = 2; v < 4; v++) {
draw_RisSektor(v);
delay(500);
}
}
//////////////////////////////////////
//функция вычисления метрики (как вариант расстояния между двумя точками)
float dist(float x1, float y1, float x2, float y2, byte m) {
if (m == 1) {
return sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2)); //Евклидова метрика
}
if (m == 2) {
return (abs(x1 - x2) + abs(y2 - y1)); //Манхэттенская метрика
}
if (m == 3) {
return abs(abs(x1 - x2) - abs(y1 - y2)); // авторская метрика №1
}
}
//////////////////////////////////////
// функция генерации точек (сайтов)для локусов диаграммы Вороного
void ran_Sait(byte variant) {
for (int i = 0; i < NUM_POINTS; i++) {
if (variant == 1 || variant == 3) {
points[i].x = random(CENTER_X, CENTER_X + 120 );
points[i].y = random(CENTER_Y, CENTER_Y + 60);
}
if (variant == 2 || variant == 3) {
points[i].color = tft.color565(random(256), random(256), random(256));
}
}
}
//////////////////////////////////////
//функция вывода 12 отсимметриченных секторов узора по базовому сектору картинки
void draw_RisSektor(byte variant) {
for (float f1 = 0.0; f1 <= PI / 6.0; f1 += PI / Z) {
for (int r1 = 0; r1 <= 120 ; r1 += 1) {
int x = CENTER_X + cos(f1) * r1;
int y = CENTER_Y + sin(f1) * r1;
int closestPoint = 0;
float minDist = dist(x, y, points[0].x, points[0].y, variant );
for (int i = 1; i < NUM_POINTS; i++) {
float d = dist(x, y, points[i].x, points[i].y, variant );
if (d < minDist) {
minDist = d;
closestPoint = i;
}
}
for (float i = 0.01; i <= 2 * PI; i += PI / 3.0) {
if (minDist > 15) {//условие роста степени заполнения элементов узора
int x1 = CENTER_X + cos(f1 + i) * r1;
int y1 = CENTER_Y + sin(f1 + i) * r1;
tft.drawPixel(x1, y1, points[closestPoint].color);
int x2 = CENTER_X + cos(2 * PI - f1 + i) * r1;
int y2 = CENTER_Y + sin(2 * PI - f1 + i) * r1;
tft.drawPixel(x2, y2, points[closestPoint].color );
}
}
}
}
ran_Sait(variant);
}
//////////////////////////////////////
Вот, Бабос, можно конструкцию на продажу собирать, с линзой и в футляре. Поделка 500р. Толкователь-методичка узоров - 10000р.
более того даже озу не надо выделять….
вот это правильный настрой, как сказал один человек, время делать бабки)))
только будут ли покупать ?)))
для более мелких дисплеев, но возможно там часть анимации не будет видна!
но вроде так же + проверка границ и максимальный радиус
#include <TFT_eSPI.h>
#include <SPI.h>
#include <math.h>
TFT_eSPI tft = TFT_eSPI();
// Константы
const int CENTER_X = 80;
const int CENTER_Y = 64;
const float Z = 300.0;
// Структура точки
struct Point {
float x, y;
uint16_t color;
};
// Глобальный массив точек
const int NUM_POINTS = 40;
Point points[NUM_POINTS];
long Y = 0;
// Прототипы функций
float dist(float x1, float y1, float x2, float y2, byte m);
void ran_Sait(byte variant);
void draw_RisSektor(byte variant);
bool isValidPoint(int x, int y);
void setup() {
Serial.begin(115200);
randomSeed(analogRead(32));
tft.init();
tft.setRotation(1);
tft.fillScreen(TFT_BLACK);
// Включаем подсветку
pinMode(TFT_BL, OUTPUT);
digitalWrite(TFT_BL, HIGH);
ran_Sait(3);
}
void loop() {
if (millis() - Y > 90000L) {
tft.fillScreen(TFT_BLACK);
Y = millis();
}
// Рисуем варианты 2 и 3
draw_RisSektor(2);
delay(500);
draw_RisSektor(3);
delay(500);
}
// Проверка границ экрана
bool isValidPoint(int x, int y) {
return (x >= 0 && x < 160 && y >= 0 && y < 128);
}
// Функция вычисления метрики
float dist(float x1, float y1, float x2, float y2, byte m) {
float dx = x1 - x2;
float dy = y1 - y2;
if (m == 1) {
return sqrt(dx*dx + dy*dy);
}
if (m == 2) {
return abs(dx) + abs(dy);
}
if (m == 3) {
return abs(abs(dx) - abs(dy));
}
return 0;
}
// Генерация точек
void ran_Sait(byte variant) {
for (int i = 0; i < NUM_POINTS; i++) {
if (variant == 1 || variant == 3) {
// Генерируем точки по всему экрану
points[i].x = random(20, 140);
points[i].y = random(20, 108);
}
if (variant == 2 || variant == 3) {
points[i].color = tft.color565(random(256), random(256), random(256));
}
}
}
// Основная функция отрисовки
void draw_RisSektor(byte variant) {
// Вычисляем безопасный радиус
int maxRadius = min(CENTER_X, CENTER_Y) - 5;
for (float f1 = 0.0; f1 <= PI / 6.0; f1 += PI / Z) {
for (int r1 = 0; r1 <= maxRadius; r1++) {
// Вычисляем базовую точку для определения ближайшего сайта
int x_base = CENTER_X + cos(f1) * r1;
int y_base = CENTER_Y + sin(f1) * r1;
if (!isValidPoint(x_base, y_base)) continue;
// Находим ближайшую точку для базовой координаты
int closestPoint = 0;
float minDist = dist(x_base, y_base, points[0].x, points[0].y, variant);
for (int i = 1; i < NUM_POINTS; i++) {
float d = dist(x_base, y_base, points[i].x, points[i].y, variant);
if (d < minDist) {
minDist = d;
closestPoint = i;
}
}
// Рисуем 12 секторов
for (float angleOffset = 0; angleOffset < 2 * PI; angleOffset += PI / 6.0) {
if (minDist > 8) {
// Первая точка
int x1 = CENTER_X + cos(f1 + angleOffset) * r1;
int y1 = CENTER_Y + sin(f1 + angleOffset) * r1;
// Вторая точка (зеркальная)
int x2 = CENTER_X + cos(2 * PI - f1 + angleOffset) * r1;
int y2 = CENTER_Y + sin(2 * PI - f1 + angleOffset) * r1;
// Рисуем если в пределах экрана
if (isValidPoint(x1, y1)) {
tft.drawPixel(x1, y1, points[closestPoint].color);
}
if (isValidPoint(x2, y2)) {
tft.drawPixel(x2, y2, points[closestPoint].color);
}
}
}
}
}
// Обновляем точки для следующего кадра
ran_Sait(variant);
}
а 4 дисплея подключить и синхронизировать вывод можно ?
А проверить скетч на своём дисплее?
Можно. Можно и на одном сразу 4 узора рисовать.

Вот мысль крамольная ![]()
…дисплей за 200р. - проектор! Смешно, но попробовать можно.










