Skip to content

Commit

Permalink
Do some general cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
nikigawlik committed Feb 16, 2021
1 parent 2dacedf commit 6c6c605
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 99 deletions.
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,3 @@ Scripts:
**`example.py`**: Demonstrates all of the basic gdmc-http functionality by building a very simple village. It uses the utility functions implemented in `worldLoader.py` and `mapUtils.py`.

**`visualizeMap.py`**: Displays a map of the minecraft world, using OpenCV

### License

License is MIT, except for the bitarray class which is ported from the Minecraft source, so copyright might be in a gray area for now.
45 changes: 29 additions & 16 deletions example.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
# x position, z position, x size, z size
area = (0, 0, 128, 128) # default build area

# Do we send blocks in batches to speed up the generation process?
USE_BATCHING = True

# see if a build area has been specified
# you can set a build area in minecraft using the /setbuildarea command
buildArea = interfaceUtils.requestBuildArea()
Expand All @@ -23,7 +26,10 @@
# load the world data
# this uses the /chunks endpoint in the background
worldSlice = WorldSlice(area)
# heightmap = worldSlice.heightmaps["WORLD_SURFACE"] # simple heightmap, includes trees
heightmap = worldSlice.heightmaps["MOTION_BLOCKING"]
heightmap = worldSlice.heightmaps["MOTION_BLOCKING_NO_LEAVES"]
heightmap = worldSlice.heightmaps["OCEAN_FLOOR"]
heightmap = worldSlice.heightmaps["WORLD_SURFACE"]
# caclulate a heightmap that ignores trees:
heightmap = mapUtils.calcGoodHeightmap(worldSlice)

Expand All @@ -36,13 +42,34 @@ def heightAt(x, z):
return heightmap[(x - area[0], z - area[1])]

# a wrapper function for setting blocks
USE_BATCHING = True
def setBlock(x, y, z, block):
if USE_BATCHING:
interfaceUtils.placeBlockBatched(x, y, z, block, 100)
else:
interfaceUtils.setBlock(x, y, z, block)

# build a fence around the perimeter
for x in range(area[0], area[0] + area[2]):
z = area[1]
y = heightAt(x, z)
setBlock(x, y-1, z, "cobblestone")
setBlock(x, y, z, "oak_fence")
for z in range(area[1], area[1] + area[3]):
x = area[0]
y = heightAt(x, z)
setBlock(x, y-1, z, "cobblestone")
setBlock(x, y , z, "oak_fence")
for x in range(area[0], area[0] + area[2]):
z = area[1] + area[3] - 1
y = heightAt(x, z)
setBlock(x, y-1, z, "cobblestone")
setBlock(x, y, z, "oak_fence")
for z in range(area[1], area[1] + area[3]):
x = area[0] + area[2] - 1
y = heightAt(x, z)
setBlock(x, y-1, z, "cobblestone")
setBlock(x, y , z, "oak_fence")

# function that builds a small house
def buildHouse(x1, y1, z1, x2, y2, z2):
# floor
Expand Down Expand Up @@ -84,20 +111,6 @@ def buildHouse(x1, y1, z1, x2, y2, z2):
for x in range(x1, x2):
setBlock(x, y2 + halfI, z, "bricks")

# build a fence around the perimeter
for i in range(2):
for x in range(area[0], area[0] + area[2]):
z = area[1] + i * (area[3] - 1)
y = heightAt(x, z)
setBlock(x, y-1, z, "cobblestone")
setBlock(x, y, z, "oak_fence")
for z in range(area[1], area[1] + area[3]):
x = area[0] + i * (area[2] - 1)
y = heightAt(x, z)
setBlock(x, y-1, z, "cobblestone")
setBlock(x, y , z, "oak_fence")


# build up to a hundred random houses and remember them to avoid overlaps
def rectanglesOverlap(r1, r2):
if (r1[0]>=r2[0]+r2[2]) or (r1[0]+r1[2]<=r2[0]) or (r1[1]+r1[3]<=r2[1]) or (r1[1]>=r2[1]+r2[3]):
Expand Down
15 changes: 12 additions & 3 deletions interfaceUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@
def setBlock(x, y, z, str):
url = 'http://localhost:9000/blocks?x=%i&y=%i&z=%i' % (x, y, z)
# print('setting block %s at %i %i %i' % (str, x, y, z))
response = requests.put(url, str)
try:
response = requests.put(url, str)
except ConnectionError:
return "0"
return response.text
# print("%i, %i, %i: %s - %s" % (x, y, z, response.status_code, response.text))

def getBlock(x, y, z):
url = 'http://localhost:9000/blocks?x=%i&y=%i&z=%i' % (x, y, z)
# print(url)
response = requests.get(url)
try:
response = requests.get(url)
except ConnectionError:
return "minecraft:void_air"
return response.text
# print("%i, %i, %i: %s - %s" % (x, y, z, response.status_code, response.text))

Expand Down Expand Up @@ -53,7 +59,10 @@ def placeBlockBatched(x, y, z, str, limit=50):
def runCommand(command):
# print("running cmd %s" % command)
url = 'http://localhost:9000/command'
response = requests.post(url, bytes(command, "utf-8"))
try:
response = requests.post(url, bytes(command, "utf-8"))
except ConnectionError:
return "connection error"
return response.text

def requestBuildArea():
Expand Down
96 changes: 20 additions & 76 deletions mapUtils.py
Original file line number Diff line number Diff line change
@@ -1,77 +1,22 @@
from matplotlib.pyplot import plot
from numpy import random
from numpy.core.shape_base import block
from numpy.lib.function_base import diff
import requests
from worldLoader import WorldSlice
"""
Utilities for (height)maps
"""
import cv2
import numpy as np
import matplotlib.pyplot as plt
from math import atan2, ceil, log2
import time

rng = np.random.default_rng()

minecraft_colors = ["white", "orange", "magenta", "light_blue", "yellow", "lime", "pink", "gray", "light_gray", "cyan", "purple", "blue", "brown", "green", "red", "black"]


def fractalnoise(shape, minFreq=0, maxFreq=0):
"""creates a array of size <shape> filled with perlin-noise (might not technically be perlin noise?). The noise is created by starting with a 1x1 noise, upsampling it by 2, then adding a 2x2 noise, upsampling again, then 4x4, etc. until a big enough size is reached and it's cropped down to the correct size.
Each additional noise layer has half the amplitude of the previous, so that small features aren't over-emphasized.
Args:
shape (tuple): size of the output array, must be 2d. For example (400,200)
Raises:
Raises ValueError if <shape> is not twodimensional
Returns:
ndarray of shape <shape>, of type float64 with values between 0 and 1
"""
if len(shape) != 2:
raise ValueError("Shape needs to have length 2. Only 2d noise is supported")

depth = ceil(log2(max(shape)))

noise = np.zeros((1,1), dtype = np.float64)

for i in range(depth):
noise = cv2.pyrUp(noise)
if i >= minFreq and i < (depth - maxFreq):
# noise = rng.integers(0, 128**(1/(i+1)), img.size, dtype = 'uint8')
noiseLayer = rng.random(noise.size, dtype = np.float64)
noiseLayer = noiseLayer * 2**(-(i+1))
# noise = np.random.normal(0, 1, img.size)
noiseLayer = noiseLayer.reshape(noise.shape)
noise = cv2.add(noise, noiseLayer)

# for i in range(3):
# perlin = cv2.pyrUp(perlin)

# perlin = (perlin - perlin.min()) / (perlin.max() - perlin.min())
noise = noise[0:shape[0], 0:shape[1]]
noise = noise.clip(0, 1)

return noise
# perlin = perlin * 255
# perlin = perlin.astype(np.uint8)

def distanceToCenter(shape):
if len(shape) != 2:
raise ValueError("Shape needs to have length 2. Only 2d is supported")

return np.array([[((x/shape[0]-0.5)**2 + (y/shape[1]-0.5)**2)**0.5 for x in range(shape[0])] for y in range(shape[1])])

def angleToCenter(shape):
if len(shape) != 2:
raise ValueError("Shape needs to have length 2. Only 2d is supported")

return np.array([[atan2(y/shape[1]-0.5, x/shape[0]-0.5) for x in range(shape[0])] for y in range(shape[1])])
import numpy as np

def normalize(array):
"""Normalizes the array so that the min value is 0 and the max value is 1
"""
return (array - array.min()) / (array.max() - array.min())

def visualize(*arrays, title=None, autonormalize=True):
"""Uses pyplot and OpenCV to visualize one or multiple numpy arrays
Args:
title (str, optional): display title. Defaults to None.
autonormalize (bool, optional): Normalizes the array to be between 0 (black) and 255 (white). Defaults to True.
"""
for array in arrays:
if autonormalize:
array = (normalize(array) * 255).astype(np.uint8)
Expand All @@ -83,16 +28,15 @@ def visualize(*arrays, title=None, autonormalize=True):
imgplot = plt.imshow(plt_image)
plt.show()

# def showAnimationFrame(array, title=None, autonormalize=True):
# time.sleep(0.05)
# if autonormalize:
# array = (normalize(array) * 255).astype(np.uint8)
# # frame = cv2.resize(frame, (500,500), interpolation=cv2.INTER_NEAREST)
# cv2.imshow(title, array)
# cv2.waitKey()
# cv2.destroyAllWindows()

def calcGoodHeightmap(worldSlice):
"""Calculates a heightmap that is well suited for building. It ignores any logs and leaves and treats water as ground.
Args:
worldSlice (WorldSlice): an instance of the WorldSlice class containing the raw heightmaps and block data
Returns:
any: numpy array containing the calculated heightmap
"""
hm_mbnl = np.array(worldSlice.heightmaps["MOTION_BLOCKING_NO_LEAVES"], dtype = np.uint8)
heightmapNoTrees = hm_mbnl[:]
area = worldSlice.rect
Expand Down

0 comments on commit 6c6c605

Please sign in to comment.