class HeightAndColourMap: def __init__(self): self.noise = Noise() self.cache = PagedDataCache(self._get_height_and_colour_non_cached, IsoCraftConstants.VOLUME_DEPTH, IsoCraftConstants.VOLUME_DEPTH) def get_height_and_colour(self, x, y): # Cached version of below return self.cache.get(x, y) def _get_height_and_colour_non_cached(self, x, y): # Uses noise to determine the height of a given point. # Return a tuple of this height and the colour to render noise = HeightAndColourMap._normalise_noise( self.noise.get_with_octaves(x * IsoCraftConstants.NOISE_STEP, y * IsoCraftConstants.NOISE_STEP, IsoCraftConstants.OCTAVES)) colour = HeightAndColourMap._colour_from_normalised_noise(noise) # Scale height = int(noise * IsoCraftConstants.VOLUME_HEIGHT) return height, colour @staticmethod def _normalise_noise(n): # Noise will typically be in the range of -Sqrt(N/4) to + Sqrt(N/4) where N is the number of dimension # In our case we are using 2D noise - approximately -0.7 to 0.7 # We want to discount any noise <= Water_Level, then normalise the remaining portion. water_level = max(-0.7, IsoCraftConstants.WATER_LEVEL) return max(n - water_level, 0.0) / (0.7 - water_level) @staticmethod def _colour_from_normalised_noise(noise): return IsoCraftConstants.COLOUR_WATER if noise <= IsoCraftConstants.THRESHOLD_WATER \ else IsoCraftConstants.COLOUR_BEACH if noise <= IsoCraftConstants.THRESHOLD_BEACH \ else IsoCraftConstants.COLOUR_GRASS if noise <= IsoCraftConstants.THRESHOLD_GRASS \ else IsoCraftConstants.COLOUR_ROCK if noise <= IsoCraftConstants.THRESHOLD_ROCK \ else IsoCraftConstants.COLOUR_SNOW
class NoiseDemo: STEP = 0.02 def __init__(self, width, height): self.noise = Noise(256) self.cache = dict() self.width = width self.height = height self.screen = pygame.display.set_mode((width, height)) pygame.display.set_caption('Perlin Noise Demo') pygame.font.init() self.font = pygame.font.SysFont('Arial', 30) self.background = (2, 2, 2) def run(self): running = True octaves = 1 while running: print(f"Rendering {octaves} octaves") self._display(octaves) pygame.display.flip() print(f"Rendered {octaves} octaves - waiting") waiting = True pygame.event.clear() while waiting: event = pygame.event.wait() if event.type == pygame.QUIT: waiting = False running = False elif event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: waiting = False octaves = min(10, octaves + 1) if event.key == pygame.K_DOWN: waiting = False octaves = max(1, octaves - 1) def _display(self, octaves): self.screen.fill(self.background) self.screen.blit( self.font.render( f"Number of Octaves: {octaves} - Use KEYUP and KEYDOWN to change number.", False, (255, 255, 255)), (40, 40) ) pygame.draw.line(self.screen, (255, 255, 255), (0, self.height / 2), (self.width, self.height / 2)) prev_x = 0 prev_y = self.height / 2 for i in range(1280): current = self.noise.get_with_octaves(i * NoiseDemo.STEP, 0, octaves) cur_x = i * 1280 / self.width cur_y = current * self.height * 0.4 + self.height / 2 pygame.draw.line( self.screen, (255, 255, 255), (prev_x, prev_y), (cur_x, cur_y)) prev_x = cur_x prev_y = cur_y for x in range(200): for y in range(200): current = self.noise.get_with_octaves(x * NoiseDemo.STEP, y * NoiseDemo.STEP, octaves) current += 0.7 current /= 1.4 current = int(current * 255) pygame.draw.line( self.screen, (current, current, current), (x + self.width * 0.75, y + self.height * 0.75), (x + self.width * 0.75, y + self.height * 0.75) ) def get_with_octaves(self, x, y, octaves): key = (x, y, octaves) if key not in self.cache: self.cache[key] = self.noise.get_with_octaves(x, y, octaves) return self.cache[key]