Генератор роста снежинок (ГРСН)

кстате говоря… малина же это арм… а пк другая архитектура… может вам на малине ваш код попытаться запустить ?
и тогда генерация будет одинаковая ? или все таки сама по себе esp более аналоговая))) а пк больше цифровые…

Код работает и на УНО и на RP2040, просто генерация на МК интересней, сами подходы. Сначала узор по сектору, потом узоры в ковёр…и т.д. Можно и на мониторе ПК выводить силами ESP, но цветов будет мало и разрешения тоже.

…в плане практических реализаций кроме картинки-картонки интересен вариант настольный предложенный

…ещё конечно интересен проектор из дисплея, но для этого нужен вариант переделки 200 рублёвого дисплея на внешнюю подсветку. С монитором от ПК такое делают и подсвечивают от окна :slight_smile: (Иван Алтай вроде фотки выкладывал).

lilik а esp с модулем звука i2s тоже звуки лучше воспроизводит чем пк ? даже кажется лучше чем телефон… но я басы пробовал, попсу еще не проверял, что бы узнать как голос будет звучать… кажется в моем пк совсем плохая звуковая карта)))

эээх если бы придумали бы как на esp 32 так коротко написать программу, что бы бассы создавала)))… а то нейросети вымогают 25к за 100 треков удачных….

Непосредственно звуки воспроизводит громкоговоритель. Качество звука при этом зависит от двух вещей: от самого громкоговорителя и от качества подаваемого на него сигнала.
Сигнал в настоящее время, как правило, берется с DAC (Цифро-Аналогового Преобразователя).
А качество сигнала с DAC, как обычно, зависит от двух вещей: от качества самого DAC (в основном от качества обвязки) и от исходного цифрового сигнала.

Если Вы наблюдательны, то должны были заметить, что в перечислении сущностей, от которых зависит качество звука, отсутствуют как i2s, так и ПК.

Есть такая функция - синус называется. Если адекватно подобрать для нее частоту, то будут как раз “бассы”.
На ESP эта функция тоже есть.

andriano я просто беру max39857 понятно что не ардуино воспроизводит… она только перенаправляет данные с sd карты, и бассы получаются четкие, даже попса кажется звучит как то живо))) а может я просто внушился, но мне кажется разница существенна…
не могу так сказать если воспроизвожу через шим, но если поместить короткий трек в микроконтроллер, и опять воспроизвести через усилитель, то все равно кажется совершенно другой звук…

мне бы динамичные, с плавными переходами, и т.д. что бы программа автоматически их генерировала, мало ли lilik что то придумает)))
не верю конечно, нереально сложно… но а вдруг?))) данные только придумать надо как генерировать))) а через 1 пин воспроизводить это не то…

кстате попробовал на плеерах, например vs1053 и опять же звучание как то больше нравится, не могу такое получить на пк, и на телефоне, если с пк понятно, у меня нет звуковой карты вообще))) то с телефоном не все так однозначно, и в целом я только это заметил, и приятно удивлен открытием, может кому то тоже понравится именно так что то слушать…

Я с таким дела не имел.

Очень даже может быть.
Есть огромная масса способов испортить звук. И экономия на деталях - один из самых эффективных.

Это, кстати, еще более эффективный способ испортить звук. Тут простая арифметика: произведение динамического диапазона на частоту дискретизации не может превышать входную частоту таймера-счетчика. Для AVR примерно соответствует 7-разрядному звуку.

Начать нужно с того, чтобы сформулировать, что именно должна генерировать программа.

Это DSP.
Кстати, из DSP мне понравился ADAU1701. Но, увы, сейчас документация от разработчика для него без VPN недоступна.

Я люблю глазами, а не ушами как всю жизнь мне твердит жена :slight_smile:

это не любовь, а потребность раскрасить серые и унылые будни разными способами!

Не знаю, я жду летнее заявление Трампа :slight_smile:
https://rg.ru/2026/02/24/glava-pentagona-hegset-dopustil-sushchestvovanie-inoplanetian.html?utm_referrer=https%3A%2F%2Fyandex.ru%2F

…надо Ангар 18 пересмотреть.

lilik а что он скажет ? они уже тут ?))) но мы не можем это доказать…
шутки шутками, а когда вы в массовом городе(например калуга) увидите круги прямо в городе ночью, не высоко и без всяких звуков, при этом не одни а массово, смешно так не будет)))
а кому выгодно вообще поддерживать слухи о других цивилизациях ? и зачем ?)))

Наверное про падение корабля в 1947 году и гибель двух пилотов-инопланетян. Наверное покажет корабль и тела.

Добавил градиента в локусы


В узоре появляется “остекление”-витражность


если я ищу то чего не понимаю, и не могу подкорректировать ии
В текущем коде точки генерируются функцией ran_Sait(3):

if (variant == 1 || variant == 3) {
  points[i].x = random(CENTER_X, CENTER_X + 120); // x от 120 до 240
  points[i].y = random(CENTER_Y, CENTER_Y + 60);  // y от 120 до 180
}

максимум что я могу получить
Чтобы найти координаты сайтов по уже нарисованной диаграмме Вороного, нужно:

cpp

// Функция для определения координат сайтов по пикселям диаграммы Вороного
void findSitesFromVoronoi() {
  const int SEARCH_RADIUS = 5; // Радиус поиска ядра локуса
  
  // Сканируем область, где могут быть сайты
  for (int y = CENTER_Y; y < CENTER_Y + 60; y++) {
    for (int x = CENTER_X; x < CENTER_X + 120; x++) {
      
      // Анализируем окрестность пикселя
      boolean isPotentialSite = true;
      uint16_t centerColor = tft.readPixel(x, y);
      
      // Проверяем, все ли пиксели в окрестности одного цвета
      for (int dy = -SEARCH_RADIUS; dy <= SEARCH_RADIUS; dy++) {
        for (int dx = -SEARCH_RADIUS; dx <= SEARCH_RADIUS; dx++) {
          if (dx == 0 && dy == 0) continue;
          
          int nx = x + dx;
          int ny = y + dy;
          
          // Проверяем границы
          if (nx < CENTER_X || nx > CENTER_X + 120 || 
              ny < CENTER_Y || ny > CENTER_Y + 60) {
            isPotentialSite = false;
            break;
          }
          
          uint16_t pixelColor = tft.readPixel(nx, ny);
          if (pixelColor != centerColor) {
            isPotentialSite = false;
            break;
          }
        }
        if (!isPotentialSite) break;
      }
      
      // Если нашли потенциальный сайт
      if (isPotentialSite && centerColor != GC9A01A_BLACK) {
        // Здесь можно сохранить координаты
        Serial.printf("Site found at: (%d, %d), Color: %04X\n", 
                     x, y, centerColor);
      }
    }
  }
}

Альтернативный подход - поиск центров масс

Более точный метод - найти центры масс каждого локуса:

struct Site {
  float x, y;
  uint16_t color;
  long pixelCount;
};

void findSitesByCentroid() {
  const int MAX_SITES = 30;
  Site sites[MAX_SITES];
  int siteCount = 0;
  
  // Проходим по всей области поиска
  for (int y = CENTER_Y; y < CENTER_Y + 60; y++) {
    for (int x = CENTER_X; x < CENTER_X + 120; x++) {
      uint16_t currentColor = tft.readPixel(x, y);
      
      if (currentColor == GC9A01A_BLACK) continue;
      
      // Ищем, есть ли уже такой цвет в найденных сайтах
      int siteIndex = -1;
      for (int i = 0; i < siteCount; i++) {
        if (sites[i].color == currentColor) {
          siteIndex = i;
          break;
        }
      }
      
      // Если новый цвет - создаем новый сайт
      if (siteIndex == -1 && siteCount < MAX_SITES) {
        siteIndex = siteCount;
        sites[siteIndex].color = currentColor;
        sites[siteIndex].x = 0;
        sites[siteIndex].y = 0;
        sites[siteIndex].pixelCount = 0;
        siteCount++;
      }
      
      // Добавляем координаты к центру масс
      if (siteIndex != -1) {
        sites[siteIndex].x += x;
        sites[siteIndex].y += y;
        sites[siteIndex].pixelCount++;
      }
    }
  }
  
  // Вычисляем центры масс
  for (int i = 0; i < siteCount; i++) {
    if (sites[i].pixelCount > 0) {
      sites[i].x /= sites[i].pixelCount;
      sites[i].y /= sites[i].pixelCount;
      
      Serial.printf("Site %d: (%.1f, %.1f), Color: %04X, Pixels: %ld\n",
                   i, sites[i].x, sites[i].y, 
                   sites[i].color, sites[i].pixelCount);
    }
  }
}

как писал ранее, что бы использовать ии, надо понимать что ты делаешь))) это скорее инструмент для быстрой печати, но иногда он справляется и сам!

Кажется ИИ правильно уловил суть. Единственное, что он должен был спросить - а где брать
tft.readPixel(x, y); :slight_smile:
…Вот как догадаешься так и всё станет на свои места…так ИИ и передай.
…вообще не торопись, решение обратной задачи это лишь первый шаг.

я не знаю справится ли он он в этот раз, но как минимуму может получится генератор треков, а то он кажется думает что это одна и таже задача)))

#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 float Z = 300.0;

struct Point {
  float x, y;
  uint16_t color;
};

// Структура для обратной задачи (на чистом C)
struct PixelVote {
  long sumX;
  long sumY;
  int count;
  uint16_t color;
  boolean used;
};

const int NUM_POINTS = 30;
const int MAX_LOCUS = 50; // Максимальное количество различных цветов

Point points[NUM_POINTS];
PixelVote votes[MAX_LOCUS]; // Статический массив вместо map
int voteCount = 0;

long Y = 0;

void setup() {
  Serial.begin(115200);
  randomSeed(analogRead(32));
  tft.begin();
  tft.fillScreen(GC9A01A_BLACK);
  tft.setRotation(2);
  ran_Sait(3);
  
  // Инициализируем массив голосов
  for (int i = 0; i < MAX_LOCUS; i++) {
    votes[i].used = false;
  }
}

void loop() {
  if (millis() - Y > 90000L) {
    tft.fillScreen(GC9A01A_BLACK);
    Y = millis();
    ran_Sait(3);
    
    // Очищаем голоса
    for (int i = 0; i < MAX_LOCUS; i++) {
      votes[i].used = false;
    }
    voteCount = 0;
  }
  
  for (byte v = 1; v < 3; v++) {
    draw_RisSektor(v);
    delay(500);
  }
  
  // Решаем обратную задачу
  solveInverseProblem();
  delay(5000);
}

// Функция вычисления метрики
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));
  if (m == 4) return max(abs(x1 - x2), abs(y1 - y2));
  return 0;
}

// Генерация точек (сайтов)
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) {
      byte Gg = random(256);
      points[i].color = tft.color565(random(Gg), Gg, random(Gg));
    }
  }
}

// Добавление голоса для цвета
void addVote(int x, int y, uint16_t color) {
  if (color == GC9A01A_BLACK) return;
  
  // Ищем существующий голос для этого цвета
  int index = -1;
  for (int i = 0; i < voteCount; i++) {
    if (votes[i].color == color) {
      index = i;
      break;
    }
  }
  
  // Если не нашли, создаем новый
  if (index == -1 && voteCount < MAX_LOCUS) {
    index = voteCount;
    votes[index].sumX = 0;
    votes[index].sumY = 0;
    votes[index].count = 0;
    votes[index].color = color;
    votes[index].used = true;
    voteCount++;
  }
  
  // Добавляем голос
  if (index != -1) {
    votes[index].sumX += x;
    votes[index].sumY += y;
    votes[index].count++;
  }
}

// Отрисовка с сбором данных
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 > 12) {
          int x1 = CENTER_X + cos(f1 + i) * r1;
          int y1 = CENTER_Y + sin(f1 + i) * r1;
          uint16_t color = points[closestPoint].color;
          tft.drawPixel(x1, y1, color);
          
          // Собираем данные ТОЛЬКО для базового сектора
          // Проверяем, что точка в базовом секторе (0 до 30 градусов)
          float angle = atan2(y1 - CENTER_Y, x1 - CENTER_X);
          if (angle >= 0 && angle <= PI/6.0) {
            addVote(x1, y1, 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, color);
        }
      }
    }
  }
}

// Решение обратной задачи
void solveInverseProblem() {
  Serial.println("\n=== РЕШЕНИЕ ОБРАТНОЙ ЗАДАЧИ ===");
  Serial.println("Найденные сайты (центры масс локусов):");
  
  int foundSites = 0;
  
  for (int i = 0; i < voteCount; i++) {
    if (votes[i].count > 10) { // Игнорируем слишком маленькие области
      float centerX = (float)votes[i].sumX / votes[i].count;
      float centerY = (float)votes[i].sumY / votes[i].count;
      
      Serial.printf("Сайт %d: (%.1f, %.1f), цвет: %04X, пикселей: %d\n",
                   foundSites++, centerX, centerY, 
                   votes[i].color, votes[i].count);
      
      // Визуализируем
      markFoundSite(centerX, centerY, votes[i].color);
    }
  }
  
  Serial.printf("Всего найдено сайтов: %d\n", foundSites);
  
  // Сравниваем с оригинальными точками
  compareWithOriginal();
}

// Визуализация найденного сайта
void markFoundSite(float x, float y, uint16_t color) {
  int ix = round(x);
  int iy = round(y);
  
  // Рисуем желтый кружок (лучше видно)
  tft.drawCircle(ix, iy, 6, GC9A01A_YELLOW);
  tft.drawLine(ix - 4, iy, ix + 4, iy, GC9A01A_YELLOW);
  tft.drawLine(ix, iy - 4, ix, iy + 4, GC9A01A_YELLOW);
  
  // Восстанавливаем цвет в центре
  tft.drawPixel(ix, iy, color);
}

// Сравнение с оригинальными точками
void compareWithOriginal() {
  Serial.println("\n=== ОРИГИНАЛЬНЫЕ ТОЧКИ (из ran_Sait) ===");
  
  for (int i = 0; i < NUM_POINTS; i++) {
    Serial.printf("points[%d]: (%.1f, %.1f), цвет: %04X\n", 
                 i, points[i].x, points[i].y, points[i].color);
    
    // Отмечаем оригинальные точки синим
    int ix = round(points[i].x);
    int iy = round(points[i].y);
    tft.drawCircle(ix, iy, 3, GC9A01A_BLUE);
  }
  
  // Оценка точности
  Serial.println("\n=== ОЦЕНКА ТОЧНОСТИ ===");
  int matches = 0;
  float totalError = 0;
  
  for (int i = 0; i < NUM_POINTS; i++) {
    // Ищем соответствующий голос по цвету
    for (int j = 0; j < voteCount; j++) {
      if (votes[j].color == points[i].color && votes[j].count > 10) {
        float foundX = (float)votes[j].sumX / votes[j].count;
        float foundY = (float)votes[j].sumY / votes[j].count;
        
        float error = sqrt(pow(foundX - points[i].x, 2) + 
                          pow(foundY - points[i].y, 2));
        
        Serial.printf("Точка %d: ошибка = %.2f пикселей\n", i, error);
        
        matches++;
        totalError += error;
        break;
      }
    }
  }
  
  if (matches > 0) {
    Serial.printf("\nСредняя ошибка: %.2f пикселей\n", totalError / matches);
    Serial.printf("Найдено %d из %d точек\n", matches, NUM_POINTS);
  }
}

передал, задал пару уточняющих вопросов, я просто спросил, он извинялся и выдавал все новые, и новые кода)))

Добавил спиральность в узор.

#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;
  int R, G, B;
};
const int NUM_POINTS = 12; // Количество точек для диаграммы Вороного
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 > 90000ul) {//новый цикл роста узоров по условию каждые 90 секунд
    tft.fillScreen(GC9A01A_BLACK);
    Y = millis();
  }
  for (byte v = 1; 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 max(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));
      points[i].R = random(256); points[i].G = random(256); points[i].B = 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) {//условие роста степени заполнения элементов узора
          float df = (1.2 * r1) / 120;//завихрение узора в спираль
          int x1 = CENTER_X + cos(f1 + i + df) * r1;
          int y1 = CENTER_Y + sin(f1 + i + df) * r1;
          tft.drawPixel(x1, y1,  tft.color565(abs(points[closestPoint].R - 10 * minDist), abs(points[closestPoint].G - 10 * minDist), abs(points[closestPoint].B - 10 * minDist)));
          int x2 = CENTER_X + cos(2 * PI - f1 + i + df) * r1;
          int y2 = CENTER_Y + sin(2 * PI - f1 + i + df) * r1;
          tft.drawPixel(x2, y2, tft.color565(abs(points[closestPoint].R - 10 * minDist), abs(points[closestPoint].G - 10 * minDist), abs(points[closestPoint].B - 10 * minDist)));
        }
      }
    }
  }
  ran_Sait(3);//новая генерация точек-сайтов
}
//////////////////////////////////////
void cvet() {

}

/////////////////////////////////////

…купил матриц 4 штуки для варианта узора 10х10…итого 400.
https://aliexpress.ru/item/1005006922046351.html?

1 лайк

а когда окончательная версия готова будет ?)))
со всеми разными этими режимами….
часы в конце думаете добавлять поверх этой анимации ?
или все таки проектор будет… или с лупой… определились что в итоге делать ?

Скорее всего заброшу.

Если только стрелочные.

Режим проектора работает, но тускло, свечения светодиода(ов) дисплея не хватает для дня, только в темноте нормально видно. С линзой как увеличительным стеклом интересен вариант, но для готовой конструкции надо делать футляр-тубус.

Хотя идей для узоров на круглом дисплее полно - бабочки, сердечки, отрезки.




если курочить не жалко…
у дисплея наверное есть шлейф, и его можно отогнуть, не знаю задняя крышка дисплея легко удаляется или нет, если легко, то ее снимаем, за ней делаем освещение самим, и можно отображать и днем, не знаю сколько разломать дисплеев придется, если не все там просто)))

нашли что то интересное что можно собрать еще ?