Massive commit. Raytracer for tile preparation. Resource generator. Everything moved into RAM for processing.

This commit is contained in:
Marcus Str. 2024-12-17 17:18:05 +01:00
parent aae81c58e9
commit 2278fe3b0a
11 changed files with 934 additions and 791 deletions

View File

@ -132,6 +132,7 @@ mstr_ortho_layers = [
("natural", "bare_rock", "natural", "bare_rock"),
("highway", "track", 3),
("highway", "path", 3),
("highway", "footway", 4),
("leisure", "park", "leisure", "green"),
("leisure", "dog_park", "leisure", "green"),
("leisure", "garden", "leisure", "green"),
@ -144,7 +145,6 @@ mstr_ortho_layers = [
# Z-Order 2
("highway", "service", 6),
("highway", "residential", 12),
("highway", "footway", 4),
("highway", "primary", 25),
("highway", "secondary", 13),
("highway", "tertiary", 20),
@ -191,6 +191,7 @@ mstr_ortho_layers = [
("building", "kindergarten", "building", "kindergarten"),
("building", "public", "building", "public"),
("building", "commercial", "building", "commercial"),
("building", "warehouse", "building", "warehouse"),
("building", "yes", "building", "common"),
("water", "lake", "natural", "water"),
("water", "pond", "natural", "water"),
@ -281,9 +282,10 @@ mstr_mask_blur = [
("building", "terrace", 1),
("building", "hangar", 1),
("building", "school", 1),
("building", "kindergarten", 1),
("building", "public", 1),
("building", "commercial", 1),
("building", "kindergarten", 1),
("building", "public", 1),
("building", "commercial", 1),
("building", "warehouse", 1),
("building", "yes", 0),
("place", "sea", 1),
("place", "ocean", 1)
@ -396,7 +398,13 @@ mstr_building_base_colors = [
"#373942", "#40424a", "#363b4f", "#2c2d32", "#444651",
"#454545", "#39393b", "#4b4b4c", "#363638", "#525252"
]
)
),
("warehouse", [
"#403a33", "#4f4b46", "#413629", "#574c3f", "#3a2e21",
"#aaaeb6", "#939cac", "#8a919d", "#a0b9bf", "#8d999b",
"#49575a", "#273d43", "#313a3c", "#484f50", "#212d30"
]
),
]

View File

@ -108,3 +108,4 @@ def xplane_latlng_folder(numbers):
if abs(numbers[1]) >= 100 : fstr = fstr + str(numbers[1])
return fstr

File diff suppressed because it is too large Load Diff

8
log.py
View File

@ -11,9 +11,15 @@
# -------------------------------------------------------------------
import datetime
from colorama import init as colorama_init
from colorama import Fore
from colorama import Style
from defines import *
def mstr_msg(fnc, msg):
if mstr_show_log == True:
colorama_init()
now = datetime.datetime.now()
print(now.strftime(" %H:%M:%S" + " | ["+fnc+"] | " + msg))
print(f' {Fore.GREEN}'+now.strftime("%H:%M:%S")+f'{Style.RESET_ALL} | {Fore.YELLOW}[' + fnc + f']{Style.RESET_ALL} | {Fore.CYAN}'+ msg + f'{Style.RESET_ALL}')
#print(f"{Fore.GREEN}" + now.strftime(" %H:%M:%S" + " | ["+fnc+"] | " + msg))

View File

@ -252,6 +252,14 @@ class mstr_maskgen:
if sp[3] != 0:
bld_shadow_pix[x,y] = (0,0,0,120)
# Mark buildings in red
for y in range(mstr_photores):
for x in range(mstr_photores):
mp = mask_pix[x,y]
if mp[3] != 0:
bld_shadow_pix[x,y] = (255,0,0,255)
# Store
if os.path.isfile(fn) == True:
lyr = Image.open(fn)

32
og.py
View File

@ -35,13 +35,9 @@ prep = False
if len(sys.argv) == 4:
cli = True
if sys.argv[3] == "true": prep = True
#if len(sys.argv) == 4:
# pbf = True
# Only if we find enough arguments, proceed.
if cli == True:
if cli:
lat = int(sys.argv[1])
lng = int(sys.argv[2])
@ -50,29 +46,19 @@ if cli == True:
# Create the class and init values
og = mstr_orthographic(lat, lng, mstr_datafolder, os.getcwd(), prep)
if prep == True:
# Prepare a tile
if sys.argv[3] == "prepare":
og._prepareTile()
if prep == False:
if sys.argv[3] != "xpscenery":
og._generateOrthos_mt(int(sys.argv[3]))
# Generate orthos
if sys.argv[3] != "prepare" and sys.argv[3] != "xpscenery":
og._generateOrthos_mt(int(sys.argv[3]))
# Build the terrain mesh and assign ground textures
if sys.argv[3] == "xpscenery":
og.generate_xp_scenery()
# Build the terrain mesh and assign ground textures
if sys.argv[3] == "xpscenery":
og.generate_xp_scenery()
# Only if we find enough arguments, proceed.
if pbf == True:
lat = int(sys.argv[1])
lng = int(sys.argv[2])
pbf = sys.argv[3]
if pbf == "pbf":
# Create the class and init values
og = mstr_orthographic(lat, lng, mstr_datafolder, os.getcwd())
og._generateData()
if cli == False and pbf == False:
mstr_msg("_main", "Please provide Latitude and Longitude. Exiting.")

View File

@ -170,9 +170,13 @@ class mstr_orthographic:
mstr_msg("orthographic", "Max lat tile: " + str(mlat) + " - max lng tile: " + str(mlng))
maxlatlng = [ mlat, mlng ]
# For completion layers
numbers = list(range(1, 16))
res = random.sample(numbers, 5)
procs = []
for p in range(1, amtsmt+1):
proc = Process(target=self._buildOrtho, args=[1, p, amtsmt])
proc = Process(target=self._buildOrtho, args=[1, p, amtsmt, res])
procs.append(proc)
proc.start()
mstr_msg("orthographic", "Ortho threads started")
@ -181,7 +185,7 @@ class mstr_orthographic:
# Starts a threading loop to build orthos, with the defined starting point in
# the lat-lng grid. You will also need to provide the horizontal stepping so
# that the thread keeps running.
def _buildOrtho(self, v, h, step):
def _buildOrtho(self, v, h, step, cpl):
# Starting point
grid_lat = v
@ -256,7 +260,6 @@ class mstr_orthographic:
lg = mstr_layergen(layer[0], layer[1], self._lat, grid_lat, self._long, grid_lng, layer[2])
lg.set_max_latlng_tile(maxlatlng)
lg.set_latlng_folder(self._latlngfld)
#lg.open_db()
lg.open_tile_info()
lyr = lg.genlayer(mask, osmxml)
photolayers.append(lyr)
@ -274,7 +277,8 @@ class mstr_orthographic:
# Snap a photo with our satellite :)
mstr_msg("orthographic", "Generating ortho photo")
pg = mstr_photogen(self._lat, self._long, grid_lat, grid_lng, maxlatlng[0], maxlatlng[1])
pg.genphoto(photolayers, waterlayers)
pg.setLayerNames(layers)
pg.genphoto(photolayers, waterlayers, cpl)
mstr_msg("orthographic", " -- Ortho photo generated -- ")
print("")
print("")
@ -375,12 +379,12 @@ class mstr_orthographic:
bb_lat = self._lat
bb_lng = self._long
# We will now prepare the graphic tile generation. We do this by only generating
# the masks and determine which sources to use in the actual images.
# Previously, I downloaded all XML files in one go - but to ease the
# stress on OSM servers and my server, we will do acquire the data
# only for the current processed part of the tile.
"""
for lat_grid in range(1, maxlatlng[0]+1):
for lng_grid in range(1, maxlatlng[1]+1):
# Adjust bounding box
@ -403,7 +407,7 @@ class mstr_orthographic:
mask = mg._build_mask(osmxml, is_prep=True) # We need an object here
tp = mstr_tileprep(self._lat, self._long, lat_grid, lng_grid, layer[0], layer[1], mask, False)
tp._prepareTile()
tp._determineEdges()
curlyr = curlyr+1
@ -428,6 +432,24 @@ class mstr_orthographic:
if cur_tile_y > top_lat:
top_lat = cur_tile_y
exit(1)
"""
# We now need to "raytrace" the resources for correct placement
mstr_msg("orthographic", "Performing resource plamement tracing for tile")
for lat_grid in range(1, maxlatlng[0]+1):
for lng_grid in range(1, maxlatlng[1]+1):
mstr_msg("orthographic", "Placement tracing for " + str(lat_grid) + ":" + str(lng_grid))
df = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/" + str(lat_grid) + "_" + str(lng_grid)
fnlines = []
with open(df) as textfile:
fnlines = textfile.readlines()
for l in range(0, len(fnlines)):
lyr = fnlines[l].split(" ")
tp = mstr_tileprep(self._lat, self._long, lat_grid, lng_grid, lyr[0], lyr[1], None, False)
tp._setLatLngFold(self._latlngfld)
tp._placeTileSources(lat_grid, lng_grid)
# Generates X-Plane 11/12 scenery with

View File

@ -1,12 +1,14 @@
import os
from PIL import Image, ImageFilter, ImageEnhance
from PIL import Image, ImageFilter, ImageEnhance, ImageFile
from defines import *
from layergen import *
from log import *
from functions import *
from xp_normalmap import *
ImageFile.LOAD_TRUNCATED_IMAGES = True
# -------------------------------------------------------------------
# ORTHOGRAPHIC
# Your personal aerial satellite. Always on. At any altitude.*
@ -37,16 +39,29 @@ class mstr_photogen:
mstr_msg("photogen", "Photogen initialized")
# Defines the order of layer names that were processed
# Called by orthographic prior to starting the photo gen process
def setLayerNames(self, names):
self._lyrnames = names
# This puts it all together. Bonus: AND saves it.
def genphoto(self, layers, waterlayers):
def genphoto(self, layers, waterlayers, cpl):
# Template for the file name which is always the same
#root_filename = mstr_datafolder + "/_cache/" + str(self._lat) + "-" + str(self._ty) + "_" + str(self._lng) + "-" + str(self._tx) + "_"
# Correct layers
#mstr_msg("photogen", "Correcting layer order issues")
#layers = self.correctLayerIssues(layers)
# First, we walk through all layers and blend them on top of each other, in order
mstr_msg("photogen", "Merging layers")
lyr=0
for l in layers:
self._tile.alpha_composite(l)
if self._lyrnames[lyr] != "building":
self._tile.alpha_composite(l)
lyr=lyr+1
# When we have run through this loop, we will end up with a sandwiched
@ -65,65 +80,80 @@ class mstr_photogen:
if emptyspace == True:
mstr_msg("photogen", "Patching empty space")
mask = self.buildCompletionMask()
# Load the mask
mask_px = mask.load()
cmpl = Image.new("RGBA", (self._imgsize, self._imgsize))
cmp_px = cmpl.load()
edn = self.find_earthnavdata_number()
edns = self.latlng_folder(edn)
idx = 0
for r in mstr_completion_colors:
if r[0] == edns:
break
else:
idx = idx+1
for y in range(self._imgsize):
for x in range(self._imgsize):
p = mask_px[x,y]
if p[3] > 0:
cidx = randrange(0, len(mstr_completion_colors[idx][1])-1)
clr = mstr_completion_colors[idx][1][cidx]
cmp_px[x,y] = (clr[0], clr[1], clr[2], 255)
cplstr = ""
for c in range(0, len(cpl)):
cplstr = cplstr + str(cpl[c])
if c < len(cpl)-1:
cplstr = cplstr + "_"
# Some features
patches = glob.glob(mstr_datafolder + "textures/tile/completion/*.png")
# Should this not exist yet, we need to create it
rg = mstr_resourcegen("landuse", "meadow", cpl)
rg.setLayerContrast(randrange(1,4))
ptcimg = rg.gensource()
# Pick an amount of features to add
patch_amt = randrange(1, 7)
ptc_src = [ptcimg[0]]
samples = 250 # <- We need this in a moment
for i in range(samples):
imgid = 0
if len(ptc_src) == 1: imgid = 0
l = 0 - int(ptc_src[imgid].width / 2)
r = cmpl.width - int(ptc_src[imgid].width / 2)
t = 0 - int(ptc_src[imgid].height / 2)
b = cmpl.height - int(ptc_src[imgid].height / 2)
cmpl.alpha_composite(ptc_src[imgid], (randrange(l, r), randrange(t, b)))
# Add those somewhere
for p in range(1, patch_amt+1):
# Load some patch
ptc = Image.open(mstr_datafolder + "textures/tile/completion/p" + str(randrange(1, len(patches)+1)) + ".png")
# Rotate it
ptc = ptc.rotate(randrange(0, 360), expand=True)
brd_img = ptcimg[1]
cmpl.alpha_composite(brd_img)
# Make sure ortho generation does not crash
if ptc.width >= mstr_photores:
ptc = ptc.resize((1536, 1536), Image.Resampling.BILINEAR)
# Patches to add from other sources. If they don't exist, we also need to make them
masks = glob.glob(mstr_datafolder + "textures/tile/completion/*.png")
amt = randrange(5, 16)
for i in range(1, amt + 1):
pick = randrange(0, len(masks))
patchmask = Image.open(masks[pick])
patchpix = patchmask.load()
# Pick from possible tags and values for the patches
patchtags = [
["landuse", "meadow"],
["landuse", "grass"],
["natural", "heath"],
["natural", "scrub"]
]
# Adjust alpha on this image
ptc_p = ptc.load()
for y in range(ptc.height):
for x in range(ptc.width):
c = ptc_p[x,y]
if c[3] > 0:
na = c[3] - 160
if na < 0: na = 0
nc = (c[0], c[1], c[2], na)
ptc_p[x,y] = nc
numbers = list(range(1, 16))
src = random.sample(numbers, 5)
# Find a location INSIDE the image!
px = randrange(1, randrange(self._imgsize - ptc.width - 1))
py = randrange(1, randrange(self._imgsize - ptc.height - 1))
patchpick = randrange(0, len(patchtags))
# Add it to the completion image
cmpl.alpha_composite(ptc, dest=(px,py))
rg = mstr_resourcegen(patchtags[patchpick][0], patchtags[patchpick][1], src)
rg.setLayerContrast(randrange(1, 4))
ptch = rg.gensource()
rg_img = ptch[0]
rg_pix = rg_img.load()
# The patch to be used in the layer
layerpatch = Image.new("RGBA", (patchmask.width, patchmask.height))
lp_pix = layerpatch.load()
for y in range(0, patchmask.height):
for x in range(0, patchmask.width):
ptc_msk = patchpix[x, y]
if ptc_msk[3] > 0:
oc = rg_pix[x, y]
nc = (oc[0], oc[1], oc[2], ptc_msk[3])
lp_pix[x, y] = nc
layerpatch = layerpatch.rotate(randrange(0, 360), expand=True)
lx = randrange(self._imgsize - layerpatch.width)
ly = randrange(self._imgsize - layerpatch.height)
cmpl.alpha_composite(layerpatch, (lx, ly))
# Merge the images
cmpl.alpha_composite(self._tile)
@ -153,10 +183,21 @@ class mstr_photogen:
if c[3] == 0:
corrpix[x,y] = (0,0,0,0)
# One more thing...
mstr_msg("photogen", "Adding features to layers")
self.addTreesToFeatures(layers)
# Throw missing buildings on top
lyr = 0
for l in layers:
if self._lyrnames[lyr] == "building":
self._tile.alpha_composite(l)
lyr = lyr + 1
# We are now in posession of the final image.
# Contrast
self._tile = ImageEnhance.Contrast(self._tile).enhance(1)
#self._tile = ImageEnhance.Contrast(self._tile).enhance(0.1)
# This we can save accordingly.
self._tile.save(mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._ty) + "_" + str(self._tx) + ".png")
@ -191,6 +232,172 @@ class mstr_photogen:
nrmimg.save(nrmfln)
# Generates some random tree.
# We will now move away from using pre-made trees...
# they didn't look so great
def generate_tree(self):
sx = randrange(18, 31)
sy = randrange(18, 31)
treepoly = Image.new("RGBA", (sx, sy))
draw = ImageDraw.Draw(treepoly)
draw.ellipse((4, 4, sx - 4, sy - 4), fill="black")
tree = Image.new("RGBA", (sx, sy))
treepx = tree.load()
maskpx = treepoly.load()
# How many tree points do we want?
treepts = 75
# How many of those have been drawn?
ptsdrawn = 0
bc = [
(36, 50, 52),
(30, 41, 39),
(32, 45, 37),
(32, 39, 49),
(33, 34, 40),
(44, 50, 53),
(40, 46, 48),
(14, 31, 38),
(17, 41, 33),
(39, 56, 35),
(51, 51, 42),
(12, 27, 31),
(45, 59, 48),
(37, 54, 29),
(59, 50, 34),
(59, 59, 35),
(59, 51, 35),
(70, 72, 45),
(48, 59, 44),
(29, 47, 23),
(47, 61, 43),
(29, 68, 15),
(53, 77, 63),
(20, 68, 40)
]
bcp = randrange(0, len(bc))
treedraw = ImageDraw.Draw(tree)
while ptsdrawn < treepts + 1:
rx = randrange(0, sx)
ry = randrange(0, sy)
mp = maskpx[rx, ry]
if mp[3] > 0:
d = randrange(0, 51)
r = bc[bcp][0]
g = bc[bcp][1] + d
b = bc[bcp][2] + (d // 2)
a = randrange(170, 256)
c = (r, g, b, a)
ex = randrange(2, 5)
ey = randrange(2, 5)
treedraw.ellipse((rx - (ex // 2), ry - (ey // 2), rx + (ey // 2), ry + (ey // 2)), fill=c)
ptsdrawn = ptsdrawn + 1
for y in range(0, tree.height):
for x in range(0, tree.width):
tp = treepx[x, y]
diff = randrange(0, 31)
nc = (tp[0] - diff, tp[1] - diff, tp[2] - diff, tp[3])
treepx[x, y] = nc
tree = tree.filter(ImageFilter.GaussianBlur(radius=0.5))
for y in range(0, tree.height):
for x in range(0, tree.width):
tp = treepx[x, y]
diff = randrange(0, 51)
nc = (tp[0] - diff, tp[1] - diff, tp[2] - diff, tp[3])
treepx[x, y] = nc
return tree
# This used to be in layergen and solves the problem of roads being rendered above trees.
# It is the only solution that worked, after some research.
def addTreesToFeatures(self, layers):
# Walk through list of layers to decide where to add the trees
curlyr = 0
for lyr in self._lyrnames:
# Add trees only in some features
if (lyr[0] == "landuse" and lyr[1] == "cemetery") or (
lyr[0] == "landuse" and lyr[1] == "residential") or (
lyr[0] == "leisure" and lyr[1] == "park"):
trees = Image.new("RGBA", (self._imgsize, self._imgsize))
amt = 4000
lyrmask = layers[curlyr].load() # We can use the layer image as alpha mask
for i in range(1, amt + 1):
lx = randrange(0, self._imgsize)
ly = randrange(0, self._imgsize)
lp = lyrmask[lx,ly]
if lp[3] > 0:
tree = self.generate_tree()
trees.alpha_composite(tree, (lx, ly))
tree_shadow = Image.new("RGBA", (self._imgsize, self._imgsize))
tree_pix = trees.load()
shadow_pix = tree_shadow.load()
for y in range(self._imgsize):
for x in range(self._imgsize):
tp = tree_pix[x, y]
if tp[3] > 0:
rndshd = randrange(5, 210)
sc = (0, 0, 0, rndshd)
if x + 8 < self._imgsize and y + 5 < self._imgsize:
shadow_pix[x + 8, y + 5] = sc
tree_shadow = tree_shadow.filter(ImageFilter.GaussianBlur(radius=2))
tree_shadow.alpha_composite(trees)
self._tile.alpha_composite(tree_shadow)
curlyr = curlyr + 1
# Reset
curlyr = 0
bldg = []
tilepix = self._tile.load()
for lyr in self._lyrnames:
if lyr[0] == "building":
bldg.append(curlyr)
curlyr = curlyr + 1
for b in range(0, len(bldg)):
bldg_lyr = layers[bldg[b]].load()
shdw = Image.open(mstr_datafolder + "_cache/" + str(self._lat) + "-" + str(self._ty) + "_" + str(self._lng) + "-" + str(self._tx) + "_" + self._lyrnames[bldg[b]][0] + "-" + self._lyrnames[bldg[b]][1] + "_layer_shadow.png")
shdw_pix = shdw.load()
for y in range(0, self._imgsize):
for x in range(0, self._imgsize):
bpix = bldg_lyr[x,y]
spix = shdw_pix[x,y]
if bpix[3] > 0 and spix[0] == 255:
tilepix[x,y] = ( bpix[0], bpix[1], bpix[2], bpix[3] )
# Correct some layer issues
def correctLayerIssues(self, layers):
# First the residential/forest dilemma
residential = 0
forest = 0
curlyr = 0
for lyr in self._lyrnames:
if lyr[0] == "landuse" and lyr[1] == "residential":
residential=curlyr
if lyr[0] == "landuse" and lyr[1] == "forest":
forest = curlyr
curlyr = curlyr+1
layers[forest].alpha_composite(layers[residential])
return layers
# This checks the final image for empty patches. Should one be

View File

@ -29,7 +29,6 @@ class mstr_tileinfo:
#self._adjfile = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/adjinfo"
self._cplfile = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/cplinfo"
self.createDataFile()
self.createCompletionFile()
def createDataFile(self):
@ -37,15 +36,8 @@ class mstr_tileinfo:
open(self._adjfile, 'a').close()
def createCompletionFile(self):
if os.path.isfile(self._cplfile) == False:
open(self._cplfile, 'a').close()
def add_adjacency_data(self, tv, th, tag, value, source, adj):
def add_adjacency_data(self, tag, value, source, adj):
line = ""
line = line + str(tv) + " "
line = line + str(th) + " "
line = line + tag + " "
line = line + value + " "
line = line + str(source) + " "
@ -55,19 +47,6 @@ class mstr_tileinfo:
textfile.write(line)
def add_completion_data(self, tv, th, tag, value, source, adj):
line = ""
line = line + str(tv) + " "
line = line + str(th) + " "
line = line + tag + " "
line = line + value + " "
line = line + str(source) + " "
line = line + str(adj) + "\n"
with open(self._cplfile, 'a') as textfile:
textfile.write(line)
def get_adjacency_for_tag_and_value(self, tv, th, tag, value):
adj = []
fnlines = []
@ -78,12 +57,11 @@ class mstr_tileinfo:
for ln in fnlines:
l = ln.split(" ")
if int(l[0]) == tv and int(l[1]) == th:
if l[2] == tag and l[3] == value:
l[5] = l[5].replace("\n", "")
adj.append(int(l[4]))
adj.append(l[5])
break
if l[0] == tag and l[1] == value:
l[3] = l[3].replace("\n", "")
adj.append(l[2])
adj.append(l[3])
break
return adj
@ -243,3 +221,4 @@ class mstr_tileinfo:
if abs(numbers[1]) >= 100 : fstr = fstr + str(numbers[1])
return fstr

View File

@ -14,6 +14,7 @@
# -------------------------------------------------------------------
import glob
from os import MFD_ALLOW_SEALING
from random import randrange
from PIL import Image
from osmxml import *
@ -34,7 +35,7 @@ class mstr_tileprep:
self._tag = tag
self._value = value
self._edges = "" # To be filled by _edgeDetect call
self._source = -1
self._source = ""
self._mask = mask
latlngfld = xplane_latlng_folder([lat, lng])
self._tileinfo = mstr_tileinfo(lat, lng, v, h, latlngfld)
@ -53,8 +54,8 @@ class mstr_tileprep:
return srcfld
# Prepare the tile accordingly
def _prepareTile(self):
# Use the mask to determine the edges
def _determineEdges(self):
# Load the mask pixels
mp = self._mask.load()
imgsize = self._mask.width
@ -69,29 +70,29 @@ class mstr_tileprep:
al=False
# Top scan
for i in range(0, imgsize-1):
for i in range(0, imgsize):
p = mp[i,0]
if p[3] > 0:
at=True
break
# Right scan
for i in range(0, imgsize-1):
for i in range(0, imgsize):
p = mp[imgsize-1,i]
if p[3] > 0:
ar=True
break
# Bottom scan
for i in range(0, imgsize-1):
for i in range(0, imgsize):
p = mp[i,imgsize-1]
if p[3] > 0:
ab=True
break
# Left scan
for i in range(0, imgsize-1):
p = mp[1,i]
for i in range(0, imgsize):
p = mp[0,i]
if p[3] > 0:
al=True
break
@ -103,48 +104,187 @@ class mstr_tileprep:
if ab==True: adjstr = adjstr + "b"
if al==True: adjstr = adjstr + "l"
# Now find out of there is a source from any adjacent tile
adjtiles = findAdjacentTilesTo(self._tile_v, self._tile_h)
sat = []
sar = []
sab = []
sal = []
if self._is_completion == False:
if at == True: sat = self._tileinfo.get_adjacency_for_tag_and_value(adjtiles[0][0], adjtiles[0][1], self._tag, self._value) # Top
if ar == True: sar = self._tileinfo.get_adjacency_for_tag_and_value(adjtiles[1][0], adjtiles[1][1], self._tag, self._value) # Right
if ab == True: sab = self._tileinfo.get_adjacency_for_tag_and_value(adjtiles[2][0], adjtiles[2][1], self._tag, self._value) # Bottom
if al == True: sal = self._tileinfo.get_adjacency_for_tag_and_value(adjtiles[3][0], adjtiles[3][1], self._tag, self._value) # Left
if self._is_completion == True:
if at == True: sat = self._tileinfo.get_adjacency_for_completion(adjtiles[0][0], adjtiles[0][1]) # Top
if ar == True: sar = self._tileinfo.get_adjacency_for_completion(adjtiles[1][0], adjtiles[1][1]) # Right
if ab == True: sab = self._tileinfo.get_adjacency_for_completion(adjtiles[2][0], adjtiles[2][1]) # Bottom
if al == True: sal = self._tileinfo.get_adjacency_for_completion(adjtiles[3][0], adjtiles[3][1]) # Left
if self._source == -1 and len(sat) == 2: self._source = sat[0]
if self._source == -1 and len(sar) == 2: self._source = sar[0]
if self._source == -1 and len(sab) == 2: self._source = sab[0]
if self._source == -1 and len(sal) == 2: self._source = sal[0]
# If there was nothing in the info still, we need to select some source
if self._source == -1:
srcfld = self._findCorrectTextureFolder()
tx = mstr_datafolder + "textures/" + srcfld[0] + "/" + srcfld[1] + "/brd/b*.png"
lst = glob.glob(tx)
if len(lst) == 1: self._source = 1
if len(lst) >= 2: self._source = randrange(1, len(lst)+1)
# Store into DB - but only if there is something to store
# We will now write this down first, without a source being selected
if adjstr != "":
if self._is_completion == False:
r = self._tileinfo.get_adjacency_for_tag_and_value(self._tile_v, self._tile_h, self._tag, self._value)
if len(r) == 0:
self._tileinfo.add_adjacency_data(self._tile_v, self._tile_h, self._tag, self._value, self._source, adjstr)
mstr_msg("tileprep", "Adjacency info stored in database")
self._tileinfo.add_adjacency_data(self._tag, self._value, "0", adjstr)
if self._is_completion == True:
r = self._tileinfo.get_adjacency_for_completion(self._tile_v, self._tile_h)
if len(r) == 0:
self._tileinfo.add_completion_data(self._tile_v, self._tile_h, self._tag, self._value, self._source, adjstr)
mstr_msg("tileprep", "Adjacency info for completion stored in database")
# Set the latlng folder
def _setLatLngFold(self, latlngfld):
self._latlngfld = latlngfld
# Find out if there is already something in place for this tag of this tile
def _getResourceInfo(self, tv, th):
# This either remains 0 or a string different to "0" in the end
src = "0"
df = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/" + str(tv) + "_" + str(th)
fnlines = []
if os.path.isfile(df) == True: # It is possible that the requested file does not yet exist
with open(df) as textfile:
fnlines = textfile.readlines()
for ln in fnlines:
l = ln.split(" ")
if l[0] == self._tag and l[1] == self._value:
l[3] = l[3].replace("\n", "")
src = l[2]
return src
# Find the edge touch info
def _getResourceTouch(self, tv, th):
touch = ""
df = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/" + str(tv) + "_" + str(th)
fnlines = []
if os.path.isfile(df) == True: # It is possible that the requested file does not yet exist
with open(df) as textfile:
fnlines = textfile.readlines()
for ln in fnlines:
l = ln.split(" ")
if l[0] == self._tag and l[1] == self._value:
l[3] = l[3].replace("\n", "")
touch = l[3]
return touch
# Select a combination of resources
def _selectResources(self):
numbers = list(range(1, 16))
res = random.sample(numbers, 5)
# Construct a string of the array
resstr = ""
for r in range(len(res)):
resstr = resstr + str(res[r])
if r < len(res)-1:
resstr = resstr + ","
return resstr
# Store the required resource information into the appropriate tile
def _storeResourceInfo(self, tile_v, tile_h, res):
df = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/" + str(tile_v) + "_" + str(tile_h)
fnlines = []
contrast = 0
if os.path.isfile(df) == True: # It is possible that the requested file does not yet exist
with open(df) as textfile:
fnlines = textfile.readlines()
curline = 0
for ln in fnlines:
l = ln.split(" ")
if l[0] == self._tag and l[1] == self._value:
l[2] = res
contrast = int(l[4])
# Find contrast values for some tags
if (
(l[0] == "landuse" and l[1] == "forest") or
(l[0] == "landuse" and l[1] == "meadow") or
(l[0] == "landuse" and l[1] == "grass") or
(l[0] == "leisure" and l[1] == "nature_reserve") or
(l[0] == "natural" and l[1] == "grassland") or
(l[0] == "landuse" and l[1] == "greenfield") or
(l[0] == "natural" and l[1] == "heath") or
(l[0] == "natural" and l[1] == "wetland") or
(l[0] == "leisure" and l[1] == "park") or
(l[0] == "building")
):
if int(l[4]) == 0: contrast = randrange(1, 4)
l[3] = l[3].replace("\n", "")
fnlines[curline] = l[0] + " " + l[1] + " " + l[2] + " " + l[3] + " " + str(contrast) + "\n"
curline = curline+1
lines = ""
for l in range(len(fnlines)):
lines = lines + fnlines[l]
with open(df, 'w') as textfile:
textfile.write(lines)
# Walk through the now existing data files and make sure we always pick the correct
# sources for every tile, thus evading previous edge detection errors
def _placeTileSources(self, mlat, mlng):
# The first tile gets to choose something for itself
if self._tile_v == 1 and self._tile_h == 1:
resstr = self._selectResources()
self._storeResourceInfo(1, 1, resstr)
# Start "raytracing"
# Initial reset
tv = self._tile_v
th = self._tile_h
# Start marching north
while tv < mlat+1:
restch = self._getResourceTouch(tv, th)
if "t" in restch:
resstr = self._getResourceInfo(tv, th)
if resstr == "0":
resstr = self._selectResources()
self._storeResourceInfo(tv, th, resstr)
resd = self._getResourceInfo(tv+1, th)
if resd=="0":
self._storeResourceInfo(tv + 1, th, resstr)
else:
break
tv = tv + 1
# Start marching east
tv = self._tile_v
th = self._tile_h
while th < mlng + 1:
restch = self._getResourceTouch(tv, th)
if "r" in restch:
resstr = self._getResourceInfo(tv, th)
if resstr == "0":
resstr = self._selectResources()
self._storeResourceInfo(tv, th, resstr)
resd = self._getResourceInfo(tv, th+1)
if resd == "0":
self._storeResourceInfo(tv, th + 1, resstr)
else:
break
th = th + 1
# Start marching south
tv = self._tile_v
th = self._tile_h
while tv > 0:
restch = self._getResourceTouch(tv, th)
if "b" in restch:
resstr = self._getResourceInfo(tv, th)
if resstr == "0":
resstr = self._selectResources()
self._storeResourceInfo(tv, th, resstr)
resd = self._getResourceInfo(tv - 1, th)
if resd == "0":
self._storeResourceInfo(tv - 1, th, resstr)
else:
break
tv = tv - 1
# Start marching west
tv = self._tile_v
th = self._tile_h
while th > 0:
restch = self._getResourceTouch(tv, th)
if "l" in restch:
resstr = self._getResourceInfo(tv, th)
if resstr == "0":
resstr = self._selectResources()
self._storeResourceInfo(tv, th, resstr)
resd = self._getResourceInfo(tv, th-1)
if resd == "0":
self._storeResourceInfo(tv, th - 1, resstr)
else:
break
th = th - 1

View File

@ -1,3 +1,4 @@
from random import randrange
# -------------------------------------------------------------------
# ORTHOGRAPHIC
@ -73,7 +74,7 @@ class mstr_xp_normalmap:
# Resize original
image = image.resize((int(mstr_photores/4), int(mstr_photores/4)), Image.Resampling.BILINEAR)
nmp = Image.new("RGBA", (image.width, image.height), (128,128,1,1))
nmp = Image.new("RGBA", (image.width, image.height), (128,128,0,0))
if water: nmp = Image.new("RGBA", (image.width, image.height), (128, 128, 255, 0))
@ -83,48 +84,62 @@ class mstr_xp_normalmap:
# Let's try some shenanigans
w = image.width
h = image.height
for y in range(h):
for x in range(w):
p = org[x,y]
if p[3] > 0: # Only do something if there is something to do in layer
# Neighboring pixels
px_t = org[ self.clamp(x, w), self.clamp(y+1, h) ]
px_tr = org[ self.clamp(x+1, w), self.clamp(y+1, h) ]
px_r = org[ self.clamp(x+1, w), self.clamp(y, h) ]
px_br = org[ self.clamp(x+1, w), self.clamp(y+1, h) ]
px_b = org[ self.clamp(x, w), self.clamp(y-1, h) ]
px_bl = org[ self.clamp(x-1, w), self.clamp(y-1, h) ]
px_l = org[ self.clamp(x-1, w), self.clamp(y, h) ]
px_tl = org[ self.clamp(x-1, w), self.clamp(y+1, h) ]
if not water:
for y in range(h):
for x in range(w):
p = org[x,y]
if p[3] > 0: # Only do something if there is something to do in layer
# Neighboring pixels
px_t = org[ self.clamp(x, w), self.clamp(y+1, h) ]
px_tr = org[ self.clamp(x+1, w), self.clamp(y+1, h) ]
px_r = org[ self.clamp(x+1, w), self.clamp(y, h) ]
px_br = org[ self.clamp(x+1, w), self.clamp(y+1, h) ]
px_b = org[ self.clamp(x, w), self.clamp(y-1, h) ]
px_bl = org[ self.clamp(x-1, w), self.clamp(y-1, h) ]
px_l = org[ self.clamp(x-1, w), self.clamp(y, h) ]
px_tl = org[ self.clamp(x-1, w), self.clamp(y+1, h) ]
# Intensities of pixels
it_t = self.intensity(px_t)
it_tr = self.intensity(px_tr)
it_r = self.intensity(px_r)
it_br = self.intensity(px_br)
it_b = self.intensity(px_b)
it_bl = self.intensity(px_bl)
it_l = self.intensity(px_l)
it_tl = self.intensity(px_tl)
# Intensities of pixels
it_t = self.intensity(px_t)
it_tr = self.intensity(px_tr)
it_r = self.intensity(px_r)
it_br = self.intensity(px_br)
it_b = self.intensity(px_b)
it_bl = self.intensity(px_bl)
it_l = self.intensity(px_l)
it_tl = self.intensity(px_tl)
# Sobel filter
dx = (it_tr + 2.0 * it_r + it_br) - (it_tl + 2.0 * it_l + it_bl)
dy = (it_bl + 2.0 * it_b + it_br) - (it_tl + 2.0 * it_t + it_tr)
dz = 10 # This is usually a good value for strength
v = (dx, dy, dz)
nrm = self.normalize_vector(v)
# Sobel filter
dx = (it_tr + 2.0 * it_r + it_br) - (it_tl + 2.0 * it_l + it_bl)
dy = (it_bl + 2.0 * it_b + it_br) - (it_tl + 2.0 * it_t + it_tr)
dz = 10 # This is usually a good value for strength
v = (dx, dy, dz)
nrm = self.normalize_vector(v)
if nrm[1] > 0:
nrm[1] = 0 - (abs(nrm[1]))
else:
nrm[1] = abs(nrm[1])
if nrm[1] > 0:
nrm[1] = 0 - (abs(nrm[1]))
else:
nrm[1] = abs(nrm[1])
# Set pixel
if water:
nmp_pix[x,y] = (int(self.map_component(nrm[0])), int(self.map_component(nrm[1])), int(self.map_component(nrm[2])), int(self.map_component(nrm[2])))
if not water:
# Set pixel
nmp_pix[x,y] = (int(self.map_component(nrm[0])), int(self.map_component(nrm[1])), 255 - int(self.map_component(nrm[2])), 1)
# Previous code produced bad and eerie looking lines at the border of the image. Let's have a go with this.
if water:
for y in range(h):
for x in range(w):
p = org[x,y]
if p[3] > 0:
wr = randrange(116, 141)
wg = randrange(111, 139)
wb = 255
if x == 0 or x == w-1: wb = 0
if y == 0 or y == h-1: wb = 0
wa = p[3]
c = (wr, wg, wb, wa)
nmp_pix[x,y] = c
mstr_msg("xp_normalmap", "[X-Plane] Normal map generated")
return nmp