Эпитрохоида, построение функций на pygame в python

Проблема:

  1. У меня linux open suse leap ~15 + idle3 + pygame. Работаю с рядами Фурье и эпициклами, эпитрохоидами и в том же духе и так далее. Но все что я нашел это формулу построения.
  2. Мне нужно как-то реализовать на питоне параметрическое уравнение тригонометрического ряда Фурье с 3 и более окружностями. У меня пока 2.
  3. Все что я сделал, так это реализация на python с либой pygame. Управление: cтрелочки,enter, w,s,d,a,f. Для увеличени или уменьшения на 0.01 Все параметры выстроены так, чтобы не равны были 0. Иначе сбой.

Полезно:

Эпитрохоида и подобное

Тригонометрический ряд Фурье

Гомер Симпсон Эпициклы

Будет не лишним:

До нормализации добавить выравнивание по Безье или Лагранжу я это пока не успел.

Вот мой код:

#authors: Arthur Alunts
#object:Trochoid, epicycles.



#Если запустить без En раскладки клавы то не будет работать.

# Нужно:     
# №1) Cделать экстраполирование по Лагранжу или Безье.   

#Короче: Все что нужно это менять константы.
#Со строчки 130 в цикле: Здесь изменяем переменные K0, R0, r0,  в цикле, если хочешь инактивируй и напиши чтобы они изменяются согасно частотам**.

import pygame 
import math 
import time
import random
import sys

#sys.path.append("./My_Lagranzh_Interpolation")
#from L import A
#print("from Lib: ", A.Z1)
# Вот параметры которые нужно менять но менять их нкужно в цикле.
Timee= 33.0    #Задержка в милисекундах (влияет на скорость).
N= 400      #Число Эйлера.
I= 0        #Номер итерации.
Size0=800       #Размер фигуры0.
Maxim0= 8.1    #Пик изменения в цикле.(влияет на скорость).
Minim0= 1.1

A0 = 1.741
A1 = 1.001  #Радиус 1.
R0 = 1.001  #Радиус 1.   
r0 = 2.001  #Радиус 1.
h0 = 3.001

dd=0.01 #Шаг0 изменения. 


Sadness=2 #Толщина линии. 


  

delta1= 22.0   #Положение на экране для первой фигуры.
delta0= 22.0   #Положение на экране для второй фигуры.

game_over = False #Создаём переменную, которая поможет нам контролировать выход.
count=0   #Просто некчемная переменная.
F0000=1   #Просто некчемная переменная.
Const0= 3.14 #Просто число пи.

#Зададим размер массива числом N.
#Заполним массивы размером N нулевыми значениями.
x=  [0.0]*(N) #x для первой фигуры.
y=  [0.0]*(N) #y для первой фигуры.
X=  [0.0]*N #X уже нормализованной фигуры.
Y=  [0.0]*N #Y уже нормализованной фигуры.
XL= [0.0]*N #x уже для нормализованной фигуры.
YL= [0.0]*N #y уже нормализованной фигуры.

#Заполним массивы X и Y, а также массивы XL и YL. 

#tt=[0.0]*(N0000+1)#Угол.
#Главный рассчет переменных: Переменные для контроля которые ты должен изменять я добавил в конце ***.
#Некоторые переменные не должны быть равны нулю, или будет сбой.
    
pygame.init()# Это нужно просто для запуска графики скорее всего нативная фигня.
YY=1012
XX=1012
dis=pygame.display.set_mode((YY, XX))#`од.
#w, h = pygame.display.get_surface().get_size()
#print((w,h))

#pygame.draw.line(dis, ChangeColor(), [(11, 12), (122, 22)], 20) .
pygame.display.set_caption("Arthuricus's interface" ) #Добавляем название игры.

font1= pygame.font.SysFont("None", 66)
#font1 = pygame.font.Font(None, 50)#Задаем шрифт.
#pygame.font.init()

game_over = False
pygame.display.update()
coin0=0



XXX=600
YYY=200
#Это графическая часть рисуем все, что нужно и обновляем методом . 
'''Рисуем Кртинку .png'''

#$image_path = '0.png' #Имя картинки в папке с проектом.
#$image = pygame.image.load(image_path) #Загружаем картинку в переменную image.
#$def drawImage0000(image,x,y):
#$   dis.blit(image, (x, y))
    




def Calculating():
    global x
    global y
    global X
    global Y
    global k        
    global img0
    global img1
    global img2
    global img3
    global img4
    global img5
    global delta1 #Положение на экране для по X фигуры.
    global delta0 #Положение на экране для по Y фигуры.
    global Size0  #Размер графика.
    
    global Const0 #Просто число пи.
    global d0     #Шаг изменения.       
    global d1     #Шаг изменения.    
    global d2     #Шаг изменения.          
    global d3     #Шаг изменения.   
    
    global A0     #Шаг для фигуры  ***. 
    global A1     #Шаг для фигуры  ***.
    global P0     #Специальный Шаг.
    global P1     #Специальный Шаг.
    global R1     #Радиус1 ***.
    global R0     #Радиус1 ***.
    global r0     #Радиус2 ***.
    global h0     #Радиус3 ***.
    global Maxim0
    
    global font1 #Шрифт
    global alpha #Параметр фигуры
 
    MyString0= "Timee: "+ str(round(Timee,4)) #округляем до 4 знаков и преобразуем число в строку и выводим на экран.   
    MyString1= "A1: "+ str(round(A1,4)) #округляем до 4 знаков и преобразуем число в строку и выводим на экран.
    MyString2= "A0: "+ str(round(A0,4)) #округляем до 4 знаков и преобразуем число в строку и выводим на экран.
    MyString3= "R0: "+ str(round(R0,4)) #округляем до 4 знаков и преобразуем число в строку и выводим на экран.
    MyString4= "r0: "+ str(round(r0,4)) #округляем до 4 знаков и преобразуем число в строку и выводим на экран.
    MyString5= "h0: "+ str(round(h0,4)) #округляем до 4 знаков и преобразуем число в строку и выводим на экран.
    

    img0 = font1.render(MyString0, True, (0,122,122)) #рисуем текст 1.
    img1 = font1.render(MyString1, True, (0,122,122)) #рисуем текст 2.
    img2 = font1.render(MyString2, True, (0,122,122)) #рисуем текст 3.
    img3 = font1.render(MyString3, True, (0,122,122)) #рисуем текст 3.
    img4 = font1.render(MyString4, True, (0,122,122)) #рисуем текст 3.      
    img5 = font1.render(MyString5, True, (0,122,122)) #рисуем текст 3.      
    
        


#Flag0...
#Здесь управляем переменными т.к. метод вызывается в цикле то соостветственно R0, K0, r0.
#=> нарастают на d0, d1, d2, до тех пор пока не достигнут Maxim0, а затем сбрасываются до 1.
    '''
    h0=h0+dd
    if h0+dd>Maxim0: 
       h0=Minim0
       r0=r0+dd
    if r0+dd>Maxim0:
       r0=Minim0
       R0=R0+dd
    if R0+dd>Maxim0:
       R0=Minim0
       A0=A0+dd
    if A1+dd>Maxim0:
       A1=Minim0
       A0=A0+dd
    if A0+dd>Maxim0:   
       A0=Minim0 
    '''     
        #Рассчет x[], y[] перемнных после изменных параметров.
    #Имена группа: X[i] + + + , Y[i] + - + 
    #            : X[i] + - + , Y[i] + - +   
    for i in range(0,N,1):       
        if not (A0==0 and r0==0):
            P0=(A1*3.14)/A0  #Формула рассчета угла.
        else:
            A0=1.1
            r0=1.1
        x[i] = ((R0+r0)*(math.sin(i*P0)-h0*math.sin((i*P0)*(R0+r0)/r0))) #Формула рассчета переменной x.
        y[i] = ((R0+r0)*(math.cos(i*P0)-h0*math.cos((i*P0)*(R0+r0)/r0)))  #Формула рассчета переменной y.

        '''
        Это вторая поэзия, пока - не написана.
        A=k(i+b)
        xx[i] = R*sin(k*a)
        yy[i] = R*cos(k*a)
        ''' 
   
    
       
def ChangeColor(A,O,U): #изменяем цвет рандомно и возвращает рандомный цвет.
    
    if A==1:
        A= random.randint(0, 255)
    if O==1: 
        O= random.randint(0, 255)    
    if U==1:   
        U= random.randint(0, 255)
    return (A,O,U)

def Normalize(x,y): # Преобразует каждую точку графика: x[] y[]   в корректные точки  X[] Y[] дабы он не уменьшался и был всегда по центру. 
    
     
    global X
    global Y
    xm = max(x)
    ym = max(y)

    xn = min(x)
    yn = min(y)
       
    
    for i in range(len(x)):
        if (not ((ym-yn)==0)  and  (not(xm-xn)==0)):# Проверяем: чтобы часть формулы не была равна 0, 
            X[i]=(x[i]-xn)/(xm-xn)*(Size0-322)+delta0  # а то будет ошибка т.е. на 0 делить нельзя.
            Y[i]=(y[i]-yn)/(ym-yn)*(Size0-322)+delta1
        else:
            
            print("Not Normal ")
            X[i]=x[i]
            Y[i]=y[i]
        #gives: x y return must hav:  X Y    



#Прорисовка
 
def Draw0000():
    global Y
    global X
    pygame.draw.rect(dis, (0,0,0), (0,0,XX,YY))  
    for i in range(0,(N-1),1): 
        pygame.draw.lines(dis,ChangeColor(222,222,1),True, [(X[i], Y[i]), (X[i+1], Y[i+1])], Sadness)      



def Pause(tt):
    time.sleep(tt/1000)





YYY=46

def FullMethode():
    Calculating()   #рассчитываем переменные с учетом изменения.
    Normalize(x, y) #нормализизируем чтобы фиксировать размер и центровку.
    Draw0000()      #рисуем согласно переменным.
    #drawImage0000(image, -500,520) #Рисуем изображение по координатам x,y,.

    dis.blit(img0, (XXX, 1*YYY+50)) #Выводим на экран параметры/ 
    dis.blit(img1, (XXX, 2*YYY+50)) #Выводим на экран параметры/
    dis.blit(img2, (XXX, 3*YYY+50)) #Выводим на экран параметры/ 
    dis.blit(img3, (XXX, 4*YYY+50)) #Выводим на экран параметры/ 
    dis.blit(img4, (XXX, 5*YYY+50)) #Выводим на экран параметры/ 
    dis.blit(img5, (XXX, 6*YYY+50)) #Выводим на экран параметры/ 
    pygame.display.update() #обновляем экран 
FullMethode()  
while not game_over:
    

    
    #Pause(Timee) # пауза в коде на время Timee (милисекунды).
    #pygame.display.update() #обновляем экран 
    for event in pygame.event.get():
       if event.type == pygame.QUIT:
           pygame.quit()
           sys.exit()
           print('App Closed')
           
           
 
    keys = pygame.key.get_pressed()
    if keys[pygame.K_UP]:
        A1=A1+dd
    if keys[pygame.K_DOWN]:    
        A0=A0+dd  
    if keys[pygame.K_LEFT]:
        r0=r0+dd
    if keys[pygame.K_RIGHT]:    
        R0=R0+dd
    if keys[pygame.K_RETURN]:    
        h0=h0+dd
    if keys[pygame.K_w]:
        A1=A1-dd
    if keys[pygame.K_s]:    
        A0=A0-dd  
    if keys[pygame.K_a]:
        r0=r0-dd
    if keys[pygame.K_d]:    
        R0=R0-dd
    if keys[pygame.K_f]:    
        h0=h0-dd
    FullMethode() 
    pygame.display.update()
    Pause(Timee)    
        
    
    

          
           
          #quit()
       


#Это нужно чтобы гладкие линии были плавные переходы или по Bezier или по Lagranzh.
'''эээ'''
'''
def LagranzhInterpolation(X,Y,  N):
 
    L=[]
    L=[0.0]*100
    global XL
    global YL
    for i in range(N, 20):
        
        X= X[i]/20                       
        X= N*20               
        W0=     Y[i]*  (  (V-X[i+1])*     (V-X[i+2])*      (V-X[i+3]  ))/  ((X[i]-X[i+1])*  (X[i]-X[i+2])*   (X[i]-X[i+3]))
        W1=   Y[i+1]*  (  (V-X[i])*       (V-X[i+2])*      (V-X[i+3]  ))/  ((X[i+1]-X[i])*  (X[i+1]-X[i+2])* (X[i+1]-X[i+3]))
        W2=   Y[i+2]*  (  (V-X[i])*       (V-X[i+2])*      (V-X[i+3]  ))/  ((X[i+2]-X[i])*  (X[i+2]-X[i+1])* (X[i+2]-X[i+3]))
        W3=   Y[i+3]*  (  (V-X[i])*       (V-X[i+2])*      (V-X[i+3]  ))/  ((X[i+3]-X[i])*  (X[i+3]-X[i+1])* (X[i+3]-X[i+2]))
        
        L[i]=W0+W1+W2+W3
         
        Z=0+0+\
            +0+\
                +0
     
 '''                       
      # print("ff: " , XY)                    
                             
#LagranzhInterpolation(1,3) 
        



Ответы (1 шт):

Автор решения: Pak Uula

Не знаю, то ли это, что вам надо, но вопрос я понял так: нужно представить замкнутую кривую на плоскости в виде набора эпициклов. Под эпициклами мы понимаем вращающиеся круги, где центр одного круга движется по окружности другого, верно?

Что-то вроде такого? Приближение пятилучевой звезды эпициклами: Разложение звезды на эпициклы

Решение элементарно: нужно представить кривую как кривую на комплексной плоскости z(t) и разложить её в ряд Фурье. Последовательные элементы ряда будут эпициклами этой кривой.

Функция zepicycles раскладывает комплексную кривую на набор эпициклов:

import numpy as np
import numpy.fft as fft

def zepicycles(z):
    N = len(z)
    F = fft.fft(z)
    f = N*fft.fftfreq(N)
    t = np.linspace(0, 2j*np.pi, N)
    center = F[0]/N
    cycles = [ (F[i]*np.exp(t*f[i]) + F[-i]*np.exp(t*f[-i]))/N for i in range(1,N//2)]
    return center, cycles

Эта функция предназначена для разложения замкнутой кривой, представленной набором точек на комплексной плоскости (входной параметр z), на набор эпициклов.

Вот пошаговое объяснение:

  • F = fft.fft(z): Собсна, вот оно! fft.fft - это функция из модуля numpy.fft (импортированного как fft), которая выполняет быстрое преобразование Фурье. Результат F - это комплексный массив, содержащий комплексные амплитуды по частотам. Самих частот в этом массиве нет.

  • f = N*fft.fftfreq(N): Эта строка вычисляет частоты, соответствующие каждому компоненту в F. fft.fftfreq генерирует массив частот на основе количества точек данных (N).

  • t = np.linspace(0, 2j*np.pi, N): заготовка для массива отсечек времени для движения по кругу. При проходе по массиву t первый эпицикл делает один оборот, а i-й делает i оборотов.

  • center = F[0]/N: Эта строка определяет центральную точку системы эпициклов. Она вычисляется как первый элемент преобразования Фурье (F[0]), деленный на общее количество точек (N).

  • cycles = [(F[i]*np.exp(t*f[i]) + F[-i]*np.exp(t*f[-i]))/N for i in range(1,N//2)]: Это ядро вычисления эпициклов. Здесь используется списковое включение для создания списка эпициклов. Для каждого частотного компонента в F он вычисляет соответствующий эпицикл. Здесь частотные компоненты из преобразования Фурье переводятся в соответствующие им круговые движения.

    При вычислении эпициклов нужно учитывать, что функция fft возвращает в разложении одну и ту же частоту дважды: один раз f[i] положительное, второй раз f[-i] отрицательное. Поэтому для вычисления эпициклов нужно суммировать оба слагаемых.

  • return center, cycles: функция возвращает вычисленную центральную точку и список эпициклов.

Для рисования эпициклов добавим функцию, которая вычисляет n-е приближение орбиты

def zorbit(center, epicycles, n):
    return center + sum(epicycles[:n])

Она принимает центральную точку (center), список комплексных эпициклов epicycles и степень приближения n в качестве входных данных. Затем она вычисляет положение на кривой, суммируя вклады первых n эпициклов и добавляя это к center.

В блокноте jupyter есть примеры разложения на эпициклы для квадрата и пятилучевой звезды.

Разложение квадратной орбиты на эпициклы

последовательные приближения к звезде

→ Ссылка