462 lines
14 KiB
Python

# -*- 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
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')