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

Эх товарищи, товарищи…

Это не ответ по теме вопроса, но замечание по алгоритму. Идет выбор ближайшей точки. При этом сравнивается расстояние с ранее выбранной точкой. Для этого не нужно извлекать корень. Оперируйте с суммами квадратов по координатам и сравнивайте квадраты гипотенуз. На результат это не влияет (а даже улучшает избавляя от точности плавучки), можно работать в целых числах, скорость поднимается существенно.

Там где надо проверять расстояния на достижения пороговых значений, контрольные значения предварительно возвести в квадрат и сравнивать с ними.

Извините, только сейчас в код вчитался. ИИ, ИИ… неужто он такое посоветовал?

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

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

При отрисовке-генерации картинки в размере экрана. На секторе разница не видна.

А почему сейчас разный? Видимо дело в типах данных byte, int, float. Именно по ним чаще ругается процессинг при адаптации скетча.

из за разницы в точности ?))) или может компилятор кода мк по своему код трактует ?)))

Попробуйте заменить на МК:

return (abs(x1 - x2) + abs(y2 - y1));

на:


float dx = x1 - x2;
float dy = y1 - y2;
if (dx < 0) dx = -dx;
if (dy < 0) dy = -dy;
return dx + dy;

Если результат станет круглым (как на ПК) — значит, дело именно в реализации abs().

у вас там одинаковые метрики ?

Не, не стал круглым результат.


… вообще процессинг не для слабонервных :slight_smile:

ran_Sait(3); //вот так ошибка

ran_Sait((byte)3);//вот так правильно
// функция генерации точек (сайтов)для локусов диаграммы Вороного
void ran_Sait(byte variant) {
  for (int i = 0; i < NUM_POINTS; i++) {
    if (variant == 1 || variant == 3) {
     xy[i] = random(CENTER_X, CENTER_X + 250 );
     xy[i+1]= random(CENTER_Y, CENTER_Y + 125);
    }
    if (variant == 2 || variant == 3) {
     R[i] = (float)(random(10,255));
     G[i] = (float)(random(10,255));
     B[i] = (float)(random(10,255));
    }
  }
}

Если в цветах-компанентах вместо float написать byte все цвета будут крайне тёмными.

Кстати в плане генерации ковра. Мне кажется надо брать один ведущий цвет и строить рисунок на основе повторяющихся уже не сегментов, а целых узоров…как то :slight_smile:

а почему не язык GO ?))) он очень похож, и думаю код и там и там будет работать…

https://ru.files.me/u/85hy676p9j

хотя нет не будет…)))
Использование float32 везде, как в ESP32 тоже мало…
https://ru.files.me/u/vfw3ksbx4s

файлы могут оказаться доступны через 10 минут!

исходный код последнего приложения

package main

import (
	"math"
	"math/rand"
	"time"

	"github.com/hajimehoshi/ebiten/v2"
)

// Константы
const (
	screenWidth   = 500
	screenHeight  = 500
	centerX       = 250
	centerY       = 250
	numPoints     = 150
	pi            = math.Pi
	redrawSeconds = 5
)

// Game структура игры
type Game struct {
	R              [numPoints]uint8
	G              [numPoints]uint8
	B              [numPoints]uint8
	xy             [2 * numPoints]float32 // Используем float32 как в ESP32
	lastTime       int64
	lastRedrawTime int64
	z              float32
	pixels         []byte
}

// Эмуляция ESP32 float вычислений
func esp32Float(x float64) float32 {
	// ESP32 использует аппаратные 32-битные float с rounding to nearest
	return float32(x)
}

// Функция вычисления метрики с float32 точностью
func dist(x1, y1, x2, y2 float32) float32 {
	// Используем float32 арифметику как на ESP32
	dx := x1 - x2
	if dx < 0 {
		dx = -dx
	}
	dy := y1 - y2
	if dy < 0 {
		dy = -dy
	}
	return dx + dy // Манхэттенская метрика
}

// Генерация точек сайтов
func (g *Game) ranSait(variant byte) {
	rand.Seed(time.Now().UnixNano())
	for i := 0; i < numPoints; i++ {
		if variant == 1 || variant == 3 {
			// Генерируем точки по всему экрану с float32 точностью
			g.xy[i] = esp32Float(float64(rand.Intn(screenWidth)))
			g.xy[i+1] = esp32Float(float64(rand.Intn(screenHeight)))
		}
		if variant == 2 || variant == 3 {
			g.R[i] = uint8(rand.Intn(256))
			g.G[i] = uint8(rand.Intn(256))
			g.B[i] = uint8(rand.Intn(256))
		}
	}
}

// Очистка экрана
func (g *Game) clearScreen() {
	for i := range g.pixels {
		g.pixels[i] = 255
	}
}

// Рисование секторов с эмуляцией ESP32
func (g *Game) drawRisSektor() {
	g.clearScreen()
	
	// Используем float32 для всех вычислений как на ESP32
	var f1, r1 float32
	
	for r1 = 0; r1 <= 250; r1 += 1.0 {
		for f1 = 0; f1 <= pi/6.0; f1 += pi / g.z {
			// Вычисления с float32 точностью
			x := esp32Float(float64(centerX)) + esp32Float(math.Cos(float64(f1)))*r1
			y := esp32Float(float64(centerY)) + esp32Float(math.Sin(float64(f1)))*r1
			
			// Поиск ближайшей точки
			closestPoint := 0
			minDist := dist(x, y, g.xy[0], g.xy[1])

			for i := 2; i < 2*numPoints; i += 2 {
				d := dist(x, y, g.xy[i], g.xy[i+1])
				if d < minDist {
					minDist = d
					closestPoint = i
				}
			}

			// Условие отсечки
			if minDist > 15.0 {
				// Рисование 12 симметричных точек
				for i := float32(0.01); i <= 2*pi; i += pi / 3.0 {
					// Вычисляем координаты с float32 точностью
					cos1 := esp32Float(math.Cos(float64(f1 + i)))
					sin1 := esp32Float(math.Sin(float64(f1 + i)))
					cos2 := esp32Float(math.Cos(float64(2*pi - f1 + i)))
					sin2 := esp32Float(math.Sin(float64(2*pi - f1 + i)))
					
					x1 := esp32Float(float64(centerX)) + cos1*r1
					y1 := esp32Float(float64(centerY)) + sin1*r1
					x2 := esp32Float(float64(centerX)) + cos2*r1
					y2 := esp32Float(float64(centerY)) + sin2*r1
					
					// Рисуем точки с анти-алиасингом
					g.drawPoint(int(x1), int(y1), g.R[closestPoint], g.G[closestPoint], g.B[closestPoint])
					g.drawPoint(int(x2), int(y2), g.R[closestPoint], g.G[closestPoint], g.B[closestPoint])
				}
			}
		}
	}
}

// Улучшенное рисование точки с заполнением промежутков
func (g *Game) drawPoint(x, y int, r, gr, b uint8) {
	// Рисуем не только сам пиксель, но и соседние для заполнения пропусков
	for dy := -1; dy <= 1; dy++ {
		for dx := -1; dx <= 1; dx++ {
			nx, ny := x+dx, y+dy
			if nx >= 0 && nx < screenWidth && ny >= 0 && ny < screenHeight {
				idx := (ny*screenWidth + nx) * 4
				// Смешиваем цвета (простое среднее для заполнения пробелов)
				if g.pixels[idx] == 255 && g.pixels[idx+1] == 255 && g.pixels[idx+2] == 255 {
					// Если пиксель белый, просто ставим цвет
					g.pixels[idx] = r
					g.pixels[idx+1] = gr
					g.pixels[idx+2] = b
				} else {
					// Если уже есть цвет, смешиваем (для плавности)
					g.pixels[idx] = uint8((int(g.pixels[idx]) + int(r)) / 2)
					g.pixels[idx+1] = uint8((int(g.pixels[idx+1]) + int(gr)) / 2)
					g.pixels[idx+2] = uint8((int(g.pixels[idx+2]) + int(b)) / 2)
				}
				g.pixels[idx+3] = 255
			}
		}
	}
}

// Layout
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
	return screenWidth, screenHeight
}

// Update
func (g *Game) Update() error {
	currentTime := time.Now().UnixMilli()
	
	if ebiten.IsKeyPressed(ebiten.KeySpace) {
		g.lastRedrawTime = currentTime
		g.ranSait(3)
	}

	if currentTime-g.lastTime > 90000 {
		g.lastTime = currentTime
		g.lastRedrawTime = currentTime
		g.ranSait(3)
	}
	
	if currentTime-g.lastRedrawTime > redrawSeconds*1000 {
		g.lastRedrawTime = currentTime
		g.ranSait(3)
	}

	g.drawRisSektor()
	return nil
}

// Draw
func (g *Game) Draw(screen *ebiten.Image) {
	screen.WritePixels(g.pixels)
}

func main() {
	rand.Seed(time.Now().UnixNano())
	
	game := &Game{
		z:              800.0,
		pixels:         make([]byte, screenWidth*screenHeight*4),
		lastTime:       time.Now().UnixMilli(),
		lastRedrawTime: time.Now().UnixMilli(),
	}
	
	game.clearScreen()
	game.ranSait(3)

	ebiten.SetWindowSize(screenWidth, screenHeight)
	ebiten.SetWindowTitle("ESP32 Pattern Emulator")
	ebiten.SetTPS(10)
	
	if err := ebiten.RunGame(game); err != nil {
		panic(err)
	}
}

а как это еще на вашем языке делать… бросайте это дело, и делайте на esp32!)))

а последние фото от куда ?))) с пк ?

Да, с ПК.

а кстати, использовать связку esp - пк можно ?)))
есп 32 будет генерировать свою стартовую картинку, а на пк будут браться данные с есп дорабатываться и выводиться….

Нет смысла. На ПК процессинг не даёт такую анимацию как МК. Он просто сколь угодно быстро перешлёпывает узоры, но их плавную трансформацию показать не может. Видимо отличие основного цикла скетчей очень существенно. Создаётся впечатление, что обновление картинки экрана и цикл void draw() работают как два независимых потока.

Ну и на дисплее не хуже, конечно разрешение меньше.

Спойлер
#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 = 30; // Количество точек для диаграммы Вороного
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 = 1; v < 3; 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
  }
  if (m == 4) {
    return max(abs(x1 - x2), abs(y1 - y2)); // авторская метрика №2
  }
}
//////////////////////////////////////
// функция генерации точек (сайтов)для локусов диаграммы Вороного
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));
     byte Gg=random(256);
     points[i].color = tft.color565(random(Gg), Gg, random(Gg));
    }
  }
}
//////////////////////////////////////
//функция вывода 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 > 12) {//условие роста степени заполнения элементов узора
          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(3);
}
//////////////////////////////////////



1 лайк

Пишется тестовый пример, без рандома, на фиксированную последовательность, вывод в порт. Сравниваются результаты в момент расхождения. Для него берутся входные данные и уже для них пишется тест. Приходит озарение и понимание.

На работе постоянно ошибки таким методом ловлю.

В плане практического использования, проще взять картинку, наклеить на картонку, вырезать круглое окно под дисплей…какие темы можно ещё задействовать?

Проще взять экранчик побольше. Заодно и “фоновую” картинку можно периодически менять.
И самое главное - вырезать ничего не нужно.

Так это надо 750х750 примерно экранчик.

Мне кажется, достаточно будет примерно такого:
https://aliexpress.ru/item/1005005702139997.html

Вроде доступен по цене, но смущает что народ не массово такими пользуется и периодически на форуме слышно помогите - нет библиотеки, как подключить и т.д.
…вот ещё тема аленький цветочек, а ещё?

2 лайка

Фишка: рядом ставите блок определения окружающей освещенности, по ее анализу меняете тональность картинок.

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

Надо подумать как это можно оформить конструктивно. Как на стенку понятно :slight_smile:

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

Нет. Просто связка картинка-картонка… и маленькая собачонка (круглый дисплей).

Интереснее двигаться дальше в направлении генерации анимации.