# -*- coding: utf-8 -*- """ Created on Fri Mar 8 15:13:12 2019 Ce module contient de nombreuses implémentations de Noise, permetant en les assemblant de créer des mondes @author: mysaa """ from data import ArrayedWorldChunk,Noise import random as r import numpy as np import sys import matplotlib.pyplot as pp import matplotlib.image as img from mpl_toolkits.mplot3d import Axes3D from math import sqrt,floor,ceil,pi ##### Paramètres ##### G = 4.5 F = 5.2 class RandNoise(Noise): """ Ce bruit renvoie une carte de vecteurs complexes (2d) du cercle trigonométrique (de module 1) """ seed = None f = None def __init__(self,seed,f=lambda r : r.random()): self.seed = seed self.f = f def getRandomly(self,xg,yg): """ Cette fonction renvoie un nombre complexe aléatoire du cercle trigonométrique, uniformément distribué selon l'argument. Cette fonction est déterministe pour un même seed demandé. """ s = ((self.seed & 0xFFFFFFFFFFFFFFFF) << 64) | ((int(xg) & 0xFFFFFFFF) << 32) | (int(yg) & 0xFFFFFFFF) r.seed(s) return self.f(r) def getChunk(self,x,y,n): """ x,y sont les coordonées du chunk à considérer (boucle au bout de 4294967296=2^32) java:int n est un couple ou une liste d'aumoins deux éléments contenant la précision suivant x et y du chunk """ randomizer = lambda i,j : self.getRandomly(x+i,y+j) return np.fromfunction(np.vectorize(randomizer),(n[0],n[1])) class RandLinNoise(RandNoise): y0 = 0 y1 = 1 def __init__(self,seed,y0,y1): super().__init__(seed,lambda r : (y1-y0)*r.random() + y0) class RandTrigNoise(RandNoise): def __init__(self,seed): super().__init__(seed,lambda r : np.exp(1j*2*pi*r.random())) class DroiteNoise(Noise): seed = None F,D = 0,0 def __init__(self,seed,F,D): self.seed = seed self.F,self.D = F,D def getChunk(self,x,y,n=None): return self.getLoadedDroites(x,y) # randomizer = lambda i,j : self.getRandomGradient(x+i,y+j) # # return np.fromfunction(np.vectorize(randomizer),(n,)) def getLoadedDroites(self,x,y): """ Cette fonction renvoie la liste des droites devant être considérées dans la génération du chunk x,y. Cela permet d'effectuer la génération procédurale. """ def dst(x0,x1,y0,y1): """ Cette fonction renvoie la ditance eucildienne 2D entre les points (x0,y0) et (x1,y1) """ return sqrt( (x1-x0)**2 + (y1-y0)**2 ) F = self.F x0 = floor(x-F) x1 = floor(x+F+1) y0 = floor(y-F) y1 = floor(y+F+1) # print(x0,x1,y0,y1) drts = [] for i in range(x0,x1+1): for j in range(y0,y1+1): for d in self.getDroitesOnChunk(i,j): # Tester si la droite sera utile dx = d[0]+i dy = d[1]+j if (x <= dx <= x+1 and y-F <= dy <= y+F+1) or (y <= dy <= y+1 and x-F <= dx <= x+F+1) or (min(dst(x,dx,y,dy),dst(x+1,dx,y,dy),dst(x+1,dx,y+1,dy),dst(x,dx,y+1,dy)) <= F): drts.append((dx,dy,d[2])) # print(len(drts)) return drts def getDroitesOnChunk(self,xg,yg): s = ((self.seed & 0xFFFFFFFFFFFFFFFF) << 64) | ((int(xg) & 0xFFFFFFFF) << 32) | (int(yg) & 0xFFFFFFFF) r.seed(s) L = [] for i in range(self.D): lx = r.random() ly = r.random() theta = r.random()*2*pi L.append((lx,ly,theta)) return L class PerlinNoise(Noise): G = None randomizer = None interpol = None wrapper = None def __init__(self,G,randomizer,interpol=lambda a,b,w : (b-a)*w**2*6*(1/2-w/3)+a,wrapper = lambda x : np.tanh(x*3.8622)): # Par défaut, un banale interpolation linéaire self.G = G if type(randomizer) == int: randomizer = RandTrigNoise(randomizer) self.randomizer = randomizer self.interpol = interpol self.wrapper = wrapper def getChunkGradients(self,x,y): G=self.G # Python de merde ! x0 = floor(x/G) x1 = ceil((x+1)/G) y0 = floor(y/G) y1 = ceil((y+1)/G) nx = x1-x0+1 ny = y1-y0+1 # grads = np.fromfunction(np.vectorize(lambda x,y : self.getPerlinGradient(x+x0,y+y0)),(nx,ny)) grads = self.randomizer.getChunk(x0,y0,(nx,ny)) return grads,x0,y0 def getChunk(self,x,y,n): G = self.G chunk = np.zeros(n) # Initialise la sortie gradients,x0,y0 = self.getChunkGradients(x,y) def dotGridGradient(ix, iy, tx, ty): dx = tx - ix dy = ty - iy return (np.conj(gradients[ix-x0][iy-y0])*(dx+1j*dy)).real for i in range(n[0]): for j in range(n[1]): #C------------D# #| |# #| |# #| |# #| x M |# #| |# #A------------B# posx = x + i/n[0] posy = y + j/n[1] xx = posx / G yy = posy / G xx0 = floor(xx) yy0 = floor(yy) xx1 = xx0 + 1 yy1 = yy0 + 1 gA = dotGridGradient(xx0, yy0, xx, yy); gB = dotGridGradient(xx1, yy0, xx, yy); gC = dotGridGradient(xx0, yy1, xx, yy); gD = dotGridGradient(xx1, yy1, xx, yy); haut = self.interpol(gA, gB, xx - xx0); bas = self.interpol(gC, gD, xx - xx0); valeur = self.interpol(haut, bas, yy - yy0); chunk[i,j] = valeur return self.wrapper(chunk) class FractalNoise(Noise): F = None D = None epsilon = None interpol = None droiteMaker = None def interpolizer(n,F): """ Retourne une fonction polynomiale réelle sur [-F,F] et nulle autre part, s'annule en F et -F, vaut 1 en 0 et a comme dérivée 0 en -F,0 et F. n+1 est le degré de la racine 0. """ return lambda x : 0 if abs(x)>F else 1 + (2*n**2+6*n+4)/(F**(2*n+4)) * ((x**2)/(2*n+4)-(F**2)/(2*n+2))*abs(x)**(2*n+2) def __init__(self,F,D,epsilon,droiteMaker,n = 1): self.F = F self.D = D self.epsilon = epsilon if type(droiteMaker) == int: droiteMaker = DroiteNoise(droiteMaker,F,D) self.droiteMaker = droiteMaker self.interpol = FractalNoise.interpolizer(n,F) def getChunk(self,x,y,n): drts = self.droiteMaker.getChunk(x,y) chunk = np.zeros(n) epsilon = self.epsilon interpol = self.interpol def dst(x0,x1,y0,y1): return sqrt( (x1-x0)**2 + (y1-y0)**2 ) def kelkote(drt,x,y): dx = drt[0] dy = drt[1] #print(x,y,dx,dy) return 1 if (np.exp(1j*(drt[2]+pi/2)) * ((x-dx)+(dy-y)*1j)).real >= 0 else -1 # FractalNoise(0.7,511,0.01,42).getChunk(3,3,(16,16)) #33.8 s ± 72.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) for d in drts: drteffect = lambda i,j : interpol(dst(d[0],i/n[0] + x,d[1],j/n[1] + y))*kelkote(d,i/n[0] + x,j/n[1] + y)*epsilon chunk += np.fromfunction(np.vectorize(drteffect),n) # FractalNoise(0.7,511,0.01,42).getChunk(3,3,(16,16)) #28.9 s ± 344 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) # for i in range(n[0]): # for j in range(n[1]): # posx = i/n[0] + x # posy = j/n[1] + y # value = 0 ## print(i,j) # for d in drts: # value += interpol(dst(d[0],posx,d[1],posy))*kelkote(d,posx,posy)*epsilon # chunk[i,j] = value return chunk class TestNoise(Noise): nn = PerlinNoise def getChunk(self,x,y,n): indexes = np.array([i for i in range(n[0]*n[1]+1)]) data = np.ones((n[0]*n[1])) return WorldChunk(n,indexes,data) class CavernedNoise(Noise): perlinSurface = None perlinGrotte = None perlinFond = None def __init__(self): self.perlinSurface = PerlinNoise(.5,64) self.perlinGrotte = PerlinNoise(7 ,77) self.perlinFond = PerlinNoise(.3 ,23) def getChunk(self,x,y,n): chk = self.perlinSurface.getChunk(x,y,n) fond = self.perlinFond.getChunk(x,y,n) grotte=self.perlinGrotte.getChunk(x,y,n) out = [] for i in range(n[0]): lig = [] for j in range(n[1]): if(grotte[i,j]>.2): # Pas de grotte lig.append([chk[i,j]]) elif(grotte[i,j]>0): lig.append([fond[i,j]]) else: lig.append([fond[i,j],chk[i,j]-0.1*abs(grotte[i,j]),chk[i,j]]) out.append(lig) return out class CavernedNoise2(Noise): perlinCielH = None perlinGHaut = None perlinGH = None perlinGHp = None perlinGBas = None #-x^(4)+4x^(3)-6x^(2)+4x def __init__(self,seed): self.perlinCielH = PerlinNoise(7 ,seed) self.perlinGHaut = PerlinNoise(5 ,seed) self.perlinGH = PerlinNoise(25 ,seed) self.perlinGHp = PerlinNoise(1 ,seed) self.perlinGBas = PerlinNoise(5 ,seed) def getChunk(self,x,y,n): gtTransform = np.vectorize(lambda x : 0 if x<0 else sqrt(2*x-x**2)**1.5) transform=lambda M,a,b : M*b+a cielH = transform(self.perlinCielH.getChunk(x,y,n),28,28) gHaut = transform(self.perlinGHaut.getChunk(x,y,n),60,20) ghp = transform(self.perlinGHp.getChunk(x,y,n) ,0.005,0.005) ghh = transform(self.perlinGHp.getChunk(x,y,n) ,0.5,0.5) gBas = transform(self.perlinGBas.getChunk(x,y,n),10,10) gh = gtTransform(ghp+ghh) toit = 128 out = [] for i in range(n[0]): lig = [] for j in range(n[1]): ch = cielH[i,j] # la hauteur entre la surface et le ciel ght = gHaut[i,j] # haut limite de la grotte gbs = gBas[i,j] # bas limite de la grotte hauteur=gh[i,j] # pourcentage de hauteur de la grotte grh = (ght+gbs +hauteur*(ght-gbs))/2 # vrai plafond de la grotte grb = (ght+gbs -hauteur*(ght-gbs))/2 # vrai sol de la grotte if(ght<=40):print(ght) if hauteur==0: # Pas de grotte lig.append([toit-ch]) elif(grh+ch>=toit): # La grotte est ouverte sur la surface lig.append([grb]) else: # Grotte souterraine et surface lig.append([grb,grh,toit-ch]) out.append(lig) return ArrayedWorldChunk.fromList(out) #for c in cmaps: # pp.figure() # print(c) # pp.imshow(I, cmap=c) #sys.exit() ####### Fenêtre graphique ####### #from PyQt5.QtWidgets import QVBoxLayout,QHBoxLayout,QPushButton,QWidget,QApplication,QFormLayout,QLabel,QTextEdit,QDial # #app = QApplication([]) # #class ExplorerWidget(QWidget): # # def __init__(): # print('wow') # ##### Control Panel #### #seedSelector = QTextEdit() #ndroitesSelector = QDial() # #cPanel = QFormLayout() #cPanel.addWidget(QLabel("Seed : ")) #cPanel.addWidget(seedSelector) #cPanel.addWidget(QLabel("Nombre de droites :")) #cPanel.addWidget(ndroitesSelector) # #globalL = QHBoxLayout() #globalL.addStretch(1) #globalL.addLayout(cPanel) # #window = QWidget() #window.setLayout(globalL) #window.show() # #app.exec_() # #(x0,x1,y0,y1) = (0,4,0,4) #n = 100 # # #x = np.linspace(x0,x1,n) #y = np.linspace(y0,y1,n) #x00 = int(x0)-1 #y00 = int(y0)-1 #x11 = int(x1)+1 #y11 = int(y1)+1 #gradient = np.exp(np.random.rand(x11-x00+1,y11-y00+1)*2*np.pi*1j) #X,Y = np.meshgrid(x,y) ##print(gradient) # #def lerp(a0, a1, w): # return a0 + (a1-a0)*(-2*w*w*w+3*w*w) # #def dotGridGradient(ix, iy, x, y): # dx = x - ix # dy = y - iy # return (np.conj(gradient[iy-y00][ix-x00])*(dx+1j*dy)).real # #def bruit(x,y): # (x0,y0) = (int(x),int(y)) # (x1,y1) = (x0+1,y0+1) # # sx = x - x0; # sy = y - y0; # # n0 = dotGridGradient(x0, y0, x, y); # n1 = dotGridGradient(x1, y0, x, y); # ix0 = lerp(n0, n1, sx); # n0 = dotGridGradient(x0, y1, x, y); # n1 = dotGridGradient(x1, y1, x, y); # ix1 = lerp(n0, n1, sx); # return lerp(ix0, ix1, sy); # # # #Z = np.zeros((n,n)) #for i in range(n): # for j in range(n): # Z[i,j] = bruit(x[i],y[j]) # #pp.imshow(Z,cmap='autumn') # #fig = pp.figure() #ax = pp.axes(projection='3d') # #ax.view_init(80, 42) #ax.plot_surface(X,Y,Z, rstride=1, cstride=1, # cmap='autumn', edgecolor='none')