python carte procedural avec perlin

comment créer des rivieres et/ou des routes

a marqué ce sujet comme résolu.

Bonjour, avec le bruit de perlin j’arrive a créer des mondes avec des océans, des iles, avec des déserts, de la plage, des glaciers de la foret… j’ajoute ensuite de la couleur pour rendre cela plus réaliste. Voici mon code :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import random
import noise
import numpy as np
from scipy.misc import toimage
import math

class Color:

    # 0 -> 255

    r = 0.0
    g = 0.0
    b = 0.0
    a = 1.0

    def __init__(self, r=0.0, g=0.0, b=0.0):
        self.r = r
        self.g = g
        self.b = b
        self.a = 1

    def GetTuple(self):
        return int(self.r), int(self.g), int(self.b)

    def SetColor(self, r, g, b):
        self.r = r
        self.g = g
        self.b = b

    def Copy(self, color):
        self.r = color.r
        self.g = color.g
        self.b = color.b

    def SetWhite(self):
        self.SetColor(1, 1, 1)

    def SetBlack(self):
        self.SetColor(0, 0, 0)

    def SetColorFromGrayscale(self, f=0.0):
        self.SetColor(f, f, f)


class GenerateMap:

    def __init__(self, size=(50, 50), color_range=10, color_perlin_scale=0.025, scale=350, octaves=6, persistance=0.6,
                 lacunarity=2.0, x_starting_pos=0, y_starting_pos=0):
        self.scale = scale
        self.octaves = octaves
        self.persistance = persistance
        self.lacunarity = lacunarity

        self.x_starting_pos=x_starting_pos
        self.y_starting_pos=y_starting_pos

        self.mapSize = size  # size in pixels
        self.mapCenter = (self.mapSize[0] / 2, self.mapSize[1] / 2)

        self.heightMap = np.zeros(self.mapSize)
        # self.colorMap = [[Color() for j in range(self.mapSize)] for i in range(self.mapSize)]

        self.randomColorRange = color_range
        self.colorPerlinScale = color_perlin_scale

        self.lightblue = [0, 191, 255]
        self.blue = [65, 105, 225]
        self.darkblue = [0, 0, 139]
        self.green = [34, 139, 34]
        self.darkgreen = [0, 100, 0]
        self.sandy = [210, 180, 140]
        self.beach = [238, 214, 175]
        self.snow = [255, 250, 250]
        self.mountain = [139, 137, 137]

        self.threshold = -0.01

    def return_initial_blank_map(self):
        return self.heightMap

    def get_map_corners(self):
        nw = self.heightMap[0][0]
        ne = self.heightMap[0][len(self.heightMap[0])-1]
        sw = self.heightMap[len(self.heightMap)-1][0]
        se = self.heightMap[len(self.heightMap)-1][len(self.heightMap[0])-1]
        return nw, ne, sw, se

    def get_map_start_position(self, start_position):
        pass

    def generate_map(self, map_type=""):
        random_nr = random.randint(0, self.mapSize[0])
        random_nr = 3
        for i in range(self.mapSize[0]):
            for j in range(self.mapSize[1]):

                new_i=i+self.y_starting_pos
                new_j=j+self.x_starting_pos


                self.heightMap[i][j] = noise.pnoise3(new_i / self.scale, new_j / self.scale, random_nr, octaves=self.octaves,
                                                     persistence=self.persistance, lacunarity=self.lacunarity,
                                                     repeatx=10000000, repeaty=10000000, base=0)
        print("monochrome map created")
        if map_type is "island":
            gradient = self.create_circular_gradient(self.heightMap)
            color_map = self.add_color(gradient)
        else:
            color_map = self.add_color(self.heightMap)
        return color_map

    def add_color(self, world):
        color_world = np.zeros(world.shape + (3,))
        # print(color_world)
        for i in range(self.mapSize[0]):
            for j in range(self.mapSize[1]):
                if world[i][j] < self.threshold + 0.02:
                    color_world[i][j] = self.darkblue
                elif world[i][j] < self.threshold + 0.03:
                    color_world[i][j] = self.blue
                elif world[i][j] < self.threshold + 0.058:
                    color_world[i][j] = self.sandy
                elif world[i][j] < self.threshold + 0.1:
                    color_world[i][j] = self.beach
                elif world[i][j] < self.threshold + 0.25:
                    color_world[i][j] = self.green
                elif world[i][j] < self.threshold + 0.6:
                    color_world[i][j] = self.darkgreen
                elif world[i][j] < self.threshold + 0.7:
                    color_world[i][j] = self.mountain
                elif world[i][j] < self.threshold + 1.0:
                    color_world[i][j] = self.snow
        print("color map created")
        return color_world

    def create_circular_gradient(self, world):
        center_x, center_y = self.mapSize[1] // 2, self.mapSize[0] // 2
        circle_grad = np.zeros_like(world)

        for y in range(world.shape[0]):
            for x in range(world.shape[1]):
                distx = abs(x - center_x)
                disty = abs(y - center_y)
                dist = math.sqrt(distx * distx + disty * disty)
                circle_grad[y][x] = dist

        # get it between -1 and 1
        max_grad = np.max(circle_grad)
        circle_grad = circle_grad / max_grad
        circle_grad -= 0.5
        circle_grad *= 2.0
        circle_grad = -circle_grad

        # shrink gradient
        for y in range(world.shape[0]):
            for x in range(world.shape[1]):
                if circle_grad[y][x] > 0:
                    circle_grad[y][x] *= 20

        # get it between 0 and 1
        max_grad = np.max(circle_grad)
        circle_grad = circle_grad / max_grad
        grad_world = self.apply_gradient_noise(world, circle_grad)
        return grad_world

    def apply_gradient_noise(self, world, c_grad):
        world_noise = np.zeros_like(world)

        for i in range(self.mapSize[0]):
            for j in range(self.mapSize[1]):
                world_noise[i][j] = (world[i][j] * c_grad[i][j])
                if world_noise[i][j] > 0:
                    world_noise[i][j] *= 20

        # get it between 0 and 1
        max_grad = np.max(world_noise)
        world_noise = world_noise / max_grad
        return world_noise

paperColor = Color(212, 161, 104)
waterColor = Color(0, 20, 28)

map_data = GenerateMap((512, 512), x_starting_pos=0, y_starting_pos=0)
print("Generator initiallized")
mono_map = map_data.generate_map("island")
print("map generated")
toimage(mono_map).show()
print("map displayed")

map_data = GenerateMap((512, 512), x_starting_pos=512, y_starting_pos=0)
print("Generator initiallized")
mono_map = map_data.generate_map("island")
print("map generated")
toimage(mono_map).show()
print("map displayed")

voila ce que j’ai en sortie :Image utilisateur

mais je ne trouve pas le résultat encore assez réaliste pour 2 raisons : 1) la premiere c’est que je voudrais des tailles de terres variables, en modifiant les valeurs d’entré (octave princiaplement) j’arrive a obtenir des iles plus petites et plus grande mais j’aimerais obtenir un monde plus diversifié avec des petites îles et des continents. je voudrais donc diversifié la taille de mes iles au lieu d’avoir que des iles de taille moyenne ou que tres petite ou que tres grosse.

2) la 2ieme difficulté que je rencontre c’est que comme vous pouvez le voir j’ai ajouter des glaciers/montagne enneiger et des lacs dans mes mondes mais j’aimerais rajouter des rivières et des routes de maniere la aussi procédural je voudrais en gros un truc dans ce style :Image utilisateur

mais je n’ai rien trouvé dans le bruit de perlin qui permette d’obtenir ce genre de forme (des traits)

par avance merci pour votre aide

Clairement le bruit de Perlin ne t’aidera pas pour les rivières et les routes.

Par-contre pour la taille des îles, il me semble qu’il faille combiner deux bruits de Perlin de taille différentes. C’est un vieu souvenir que j’ai de l’époque où j’ai étudié la chose, pas forcément la solution miracle.

PS: Ce que tu as déjà est clairement pas mal :D

+1 -0

Oui c’est bien ce que je pensait que avec perlin se serait impossible de faire des rivières. Pour les rivière j’ai pensé que je pourrais utiliser un autre algo et ajouter mes rivière ensuite par dessus ma carte, quel bruit serait le plus adapté pour dessiner des rivières/routes/canyon (bref tous ce qui génère des traits)

Pour la taille des îles je vais regarder, si dans la théorie je suis d’accord avec toi en mélangent 2 bruit de perlin, dans la pratique je ne vois pas comment les faire fusionner ?

Pour les rivière je ne pense pas qu’un algo de type bruit soit applicable pour ça. Après je n’ai pas beaucoup de connaissance en la matière même si la génération procédurale de monde et un domaine qui me fascine, je n’ai pas encore pris le temps de me pencher dessus.

Il y a un département du laboratoire de recherche de l’université de Lyon spécialisé dans ce domaine. Tu devrais peut-être envoyé un mail à un (enseignant-)chercheur pour plus d’info. Si tu fait ça, pense à partager la réponse aussi :)

+0 -0

Pour générer des rivières, lance des points aléatoirement, s’ils sont au dessus d’une certaine hauteur de terrain, ça devient une source.

De cette source, tu suis le gradient jusqu’à arriver à la mer/ocean. Si une rivière en rejoint une autre, tu grossis cette dernière.

Tu peux aussi utiliser tes rivières comme un masque supplémentaire, et creuser le terrain.

Pour les routes, elles ont tendance à suivre les côtes et éviter les forts dénivelés. Donc tu peux en générer à une hauteur faible (pour suivre les côtes) et ensuite, si tu as des villes, tu génères un chemin de la ville à la côte la plus proche (en pondérant la longueur des chemins avec les dénivelés et les virages). Et pareil entre les villes.


Pour les rivières, les sources n’ont pas à être vraiment aléatoires, elles peuvent être issues d’un bruit.

Malheureusement, il me paraît peu probable de pouvoir avoir une cohérence pour les routes juste avec un bruit.

+0 -0

j’ai regardé un peu coté algo, il semble que le marble noise puisse faire cela (des rivières/routes…etc)

l’idée serait que je rajoute une surcouche avec les rivières et que je colle cette surcouche sur ma carte (uniquement si c’est de la terre)

pour ton idée JuDePom, mon but c’est de produire pleins de petite carte qui s’assemble, pour faire 1 carte oui cela peut marcher mais après comment j’assemble si une rivière vas sur une carte a coté ?

pr cohérence, j’entends cohérence au niveau de l’assemblage de ces petites carte, pas forcément au niveau du rendu. Si une route vas dans la neige c’est pas un probleme. Il faut par contre que la forme des routes soit "naturel", je veut pas des routes qui soit des droites, je veut des routes avec des virages, enfin une route normal quoi.

Pour le marble noise, je ne suis pas trop convaincu. Il y a des "boucles" qui se forment, et il y a de forts risque d’inconsistances. Le bruit ne va pas respecter ton terrain (En utilisant le gradient comme paramètre du bruit ?? là je ne sais pas, à tester).

Sur Minecraft il me semble que pour les rivières, c’est bien un bruit, mais comme l’eau est toujours mis au niveau de la mer, il font un "ceil" dégueulasse ce qui donne des trucs qui coupent une montagne en deux ^^.

Pour les routes, tu peux générer des points sur les bords de ta carte (sur la terre, évidemment). Et ensuite, tu appliques un algo qui prends en compte les variations de ton terrain pour avoir un truc réaliste. Comme cela, ta génération reste procédurale, vu que les routes entre les différentes cartes sont reliées, et tu n’as pas des routes qui n’ont aucun sens (comme ça pourrait être le cas avec un bruit). Si ton algorithme n’est pas stochastique, tu pourra donc régénérer la même chose à chaque fois.

ok je pense avoir compris.

reste par contre ma taille des îles, j’aimerais une taille plus variable allant d’un continent a un morceau de caillou, peut on améliorer cela avec perlin et obtenir des formes plus variables ?

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte