466 lines
14 KiB
Python
466 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
|
|
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')
|
|
|
|
|
|
|
|
|
|
|