Normal map maker for X-Plane now working correctly, and properly implemented in layer generation process. Tile folder now has a proper "z_orthographic_" naming convention, data is stored in this folder. Orthos are now stored in DDS format, as opposed to jpg, which was twice as large in file size. Tile generation process completes by generating proper DSF data files for X-Plane.

This commit is contained in:
marstr 2024-09-10 19:38:48 +02:00
parent a5e850feec
commit 7326984762
7 changed files with 133 additions and 74 deletions

View File

@ -150,6 +150,8 @@ mstr_ortho_layers = [
("natural", "wetland", "natural", "wetland"), ("natural", "wetland", "natural", "wetland"),
("natural", "scrub", "natural", "scrub"), ("natural", "scrub", "natural", "scrub"),
("natural", "heath", "natural", "heath"), ("natural", "heath", "natural", "heath"),
("natural", "sand", "natural", "sand"),
("natural", "desert", "natural", "desert"),
# Z-Order 3 # Z-Order 3
("natural", "water", "natural", "water"), ("natural", "water", "natural", "water"),
("natural", "bay", "natural", "beach"), ("natural", "bay", "natural", "beach"),
@ -180,7 +182,9 @@ mstr_ortho_layers = [
("building", "terrace", "building", "industrial"), ("building", "terrace", "building", "industrial"),
("building", "hangar", "building", "industrial"), ("building", "hangar", "building", "industrial"),
("building", "school", "building", "common"), ("building", "school", "building", "common"),
("building", "yes", "building", "common") ("building", "yes", "building", "common"),
("place", "sea", "natural", "sea"),
("place", "ocean", "natural", "sea")
] ]
@ -257,5 +261,7 @@ mstr_mask_blur = [
("building", "terrace", 1), ("building", "terrace", 1),
("building", "hangar", 1), ("building", "hangar", 1),
("building", "school", 1), ("building", "school", 1),
("building", "yes", 1) ("building", "yes", 1),
("place", "sea", 1),
("place", "ocean", 1)
] ]

View File

@ -38,8 +38,6 @@ class mstr_layergen:
self._longitude = lng self._longitude = lng
self._lng_number = lngnum self._lng_number = lngnum
self._layerborder = -1 self._layerborder = -1
self._tiledb = mstr_tiledb(lat, lng)
self._tiledb.create_tables()
self._is_completion = is_completion self._is_completion = is_completion
# Define layer size depending on what is wanted # Define layer size depending on what is wanted
self._imgsize = 0 self._imgsize = 0
@ -58,6 +56,11 @@ class mstr_layergen:
def set_latlng_folder(self, latlngfld): def set_latlng_folder(self, latlngfld):
self._latlngfld = latlngfld self._latlngfld = latlngfld
# Open DB
def open_db(self):
self._tiledb = mstr_tiledb(self._latitude, self._longitude, self._latlngfld)
self._tiledb.create_tables()
# This generates a "border" image, for example farmland usually has a small space of grass # This generates a "border" image, for example farmland usually has a small space of grass
# before the actual crop of farm field itself. This generates this "border" layer, # before the actual crop of farm field itself. This generates this "border" layer,
# and returns it. # and returns it.
@ -355,8 +358,9 @@ class mstr_layergen:
# on what they are. # on what they are.
for i in mstr_mask_blur: for i in mstr_mask_blur:
if i[0] == self._tag and i[1] == self._value: if i[0] == self._tag and i[1] == self._value:
osm_mask = osm_mask.filter(ImageFilter.BoxBlur(radius=i[2])) if self._tag != "place" and (self._value != "sea" or self._value != "ocean"):
break osm_mask = osm_mask.filter(ImageFilter.BoxBlur(radius=i[2]))
break
# Begin producing a largely random image # Begin producing a largely random image
samples = 250 # <- We need this in a moment samples = 250 # <- We need this in a moment
@ -414,13 +418,6 @@ class mstr_layergen:
layer_border = self.genborder(osm_edge, "landuse", "meadow") layer_border = self.genborder(osm_edge, "landuse", "meadow")
layer_comp.alpha_composite(layer_border) layer_comp.alpha_composite(layer_border)
# Edges for waters
if self._tag == "natural" and self._value == "water":
osm_edge = osm_mask.filter(ImageFilter.FIND_EDGES)
osm_edge = osm_edge.filter(ImageFilter.MaxFilter)
osm_edge = osm_edge.filter(ImageFilter.GaussianBlur(radius=2))
layer_comp.alpha_composite(osm_edge)
# Store layer # Store layer
if self._is_completion == False: if self._is_completion == False:
layer_comp.save( mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_layer.png" ) layer_comp.save( mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_layer.png" )
@ -430,6 +427,20 @@ class mstr_layergen:
mstr_msg("layergen", "Layer image finalized and saved.") mstr_msg("layergen", "Layer image finalized and saved.")
# Depending on if scenery for XP should be made, AND if normal maps should be made, we would
# need to make them at this exact point
if mstr_xp_genscenery == True:
if mstr_xp_generate_normal_maps == True:
nm = False
for n in mstr_xp_normal_maps:
if n[0] == self._tag and n[1] == self._value:
nm = True
break
if nm == True:
nrm = mstr_xp_normalmap(self._latitude, self._longitude, self._tag, self._value, self._lat_number, self._lng_number, self._latlngfld)
nrm.build_normalmap()
# Let's try our hand at pseudo shadows # Let's try our hand at pseudo shadows
if mstr_shadow_enabled == True: if mstr_shadow_enabled == True:
if mstr_shadow_shift >= 2: if mstr_shadow_shift >= 2:
@ -759,6 +770,12 @@ class mstr_layergen:
# need to make them at this exact point # need to make them at this exact point
if mstr_xp_genscenery == True: if mstr_xp_genscenery == True:
if mstr_xp_generate_normal_maps == True: if mstr_xp_generate_normal_maps == True:
nrm = mstr_xp_normalmap(self._latitude, self._longitude, self._tag, self._value, self._lat_number, self._lng_number, self._latlngfld) nm = False
nrm.build_normalmap() for n in mstr_xp_normal_maps:
if n[0] == self._tag and n[1] == self._value:
nm = True
break
if nm == True:
nrm = mstr_xp_normalmap(self._latitude, self._longitude, self._tag, self._value, self._lat_number, self._lng_number, self._latlngfld)
nrm.build_normalmap()

View File

@ -20,6 +20,7 @@ from layergen import *
from photogen import * from photogen import *
from osmxml import * from osmxml import *
from tilegen import * from tilegen import *
from xp_dsfgen import *
# The main class which handles the rest # The main class which handles the rest
@ -110,18 +111,18 @@ class mstr_orthographic:
mstr_msg("orthographic", "Created Tiles sub folder: " + self._latlngfld) mstr_msg("orthographic", "Created Tiles sub folder: " + self._latlngfld)
# Generate the orthos folder # Generate the orthos folder
if not os.path.exists(self._output + "/Tiles/z_orthograpic_" + self._latlngfld + "/orthos"): if not os.path.exists(self._output + "/Tiles/z_orthographic_" + self._latlngfld + "/orthos"):
os.makedirs(self._output + "/Tiles/z_orthograpic_" + self._latlngfld +"/orthos") os.makedirs(self._output + "/Tiles/z_orthographic_" + self._latlngfld +"/orthos")
mstr_msg("orthographic", "Created tile orthos folder") mstr_msg("orthographic", "Created tile orthos folder")
if mstr_xp_genscenery == True: if mstr_xp_genscenery == True:
if not os.path.exists(self._output + "/Tiles/z_orthograpic_" + self._latlngfld + "/terrain"): if not os.path.exists(self._output + "/Tiles/z_orthographic_" + self._latlngfld + "/terrain"):
os.makedirs(self._output + "/Tiles/z_orthograpic_" + self._latlngfld + "/terrain") os.makedirs(self._output + "/Tiles/z_orthographic_" + self._latlngfld + "/terrain")
mstr_msg("orthographic", "Created X-Plane tile terrain folder") mstr_msg("orthographic", "Created X-Plane tile terrain folder")
if mstr_xp_generate_normal_maps == True: if mstr_xp_generate_normal_maps == True:
if not os.path.exists(self._output + "/Tiles/z_orthograpic_" + self._latlngfld + "/normals"): if not os.path.exists(self._output + "/Tiles/z_orthographic_" + self._latlngfld + "/normals"):
os.makedirs(self._output + "/Tiles/z_orthograpic_" + self._latlngfld + "/normals") os.makedirs(self._output + "/Tiles/z_orthographic_" + self._latlngfld + "/normals")
mstr_msg("orthographic", "Created X-Plane tile normals folder") mstr_msg("orthographic", "Created X-Plane tile normals folder")
# The tile is constructed of many smaller parts. We walk through the # The tile is constructed of many smaller parts. We walk through the
@ -170,7 +171,7 @@ class mstr_orthographic:
mstr_msg("orthographic", "Adjusted bounding box for XML object") mstr_msg("orthographic", "Adjusted bounding box for XML object")
# Determine what to do... maybe work was interrupted # Determine what to do... maybe work was interrupted
if os.path.isfile(mstr_datafolder + "Tiles/" + self._latlngfld + "/orthos/" + str(cur_tile_y) + "_" + str(cur_tile_x) + ".jpg") == False: if os.path.isfile(mstr_datafolder + "Tiles/" + self._latlngfld + "/orthos/" + str(cur_tile_y) + "_" + str(cur_tile_x) + ".dds") == False:
# Let the user know # Let the user know
mstr_msg("orthographic", "Generating missing orthophoto " + str(cur_tile_y) + "-" + str(cur_tile_x)) mstr_msg("orthographic", "Generating missing orthophoto " + str(cur_tile_y) + "-" + str(cur_tile_x))
@ -203,6 +204,7 @@ class mstr_orthographic:
lg = mstr_layergen(layer[0], layer[1], self._lat, cur_tile_y, self._long, cur_tile_x, layer[2]) lg = mstr_layergen(layer[0], layer[1], self._lat, cur_tile_y, self._long, cur_tile_x, layer[2])
lg.set_max_latlng_tile(maxlatlng) lg.set_max_latlng_tile(maxlatlng)
lg.set_latlng_folder(self._latlngfld) lg.set_latlng_folder(self._latlngfld)
lg.open_db()
lg.genlayer() lg.genlayer()
curlyr = curlyr+1 curlyr = curlyr+1
mstr_msg("orthographic", "All layers created") mstr_msg("orthographic", "All layers created")
@ -210,11 +212,9 @@ class mstr_orthographic:
# We should have all layers now. # We should have all layers now.
# Snap a photo with our satellite :) # Snap a photo with our satellite :)
mstr_msg("orthographic", "Generating ortho photo") mstr_msg("orthographic", "Generating ortho photo")
pg = mstr_photogen(self._lat, self._long, cur_tile_y, cur_tile_x, maxlatlng[0], maxlatlng[1]) pg = mstr_photogen(self._lat, self._long, cur_tile_y, cur_tile_x, maxlatlng[0], maxlatlng[1])
pg.genphoto() pg.genphoto()
mstr_msg("orthographic", " -- Ortho photo generated -- ") mstr_msg("orthographic", " -- Ortho photo generated -- ")
if mstr_xp_genscenery == True:
xp_datagroup = xp_datagroup + 1
print("") print("")
print("") print("")
@ -254,6 +254,21 @@ class mstr_orthographic:
mstr_msg("orthographic", "Generation of all tiles completed!") mstr_msg("orthographic", "Generation of all tiles completed!")
# Complete scenery
if mstr_xp_genscenery == True:
dsf = mstr_xp_dsfgen(self._lat, self._long, mlat, mlng, self._vstep)
dsf.build_dsf_for_tile()
mstr_msg("orthographic", "X-Plane scenery completed")
mstr_msg("orthographic", "Final step completed.")
mstr_msg("orthographic", "Tile data in: " + self._output + "/Tiles/z_orthographic_" + self._latlngfld)
print("")
mstr_msg("orthographic", "Thanks for using Orthographic! -- Best, Marcus")
print("")
# Let's leave this out for the moment
"""
mstr_msg("orthographic", "Generating ZL16 tiles and keeping airport tiles") mstr_msg("orthographic", "Generating ZL16 tiles and keeping airport tiles")
tg = mstr_tilegen(self._lat, self._lng, self._vstep, top_lat, top_lng) tg = mstr_tilegen(self._lat, self._lng, self._vstep, top_lat, top_lng)
tg.genTiles() tg.genTiles()
@ -264,6 +279,7 @@ class mstr_orthographic:
print("") print("")
mstr_msg("orthographic", "Thanks for using Orthographic! -- Best, Marcus") mstr_msg("orthographic", "Thanks for using Orthographic! -- Best, Marcus")
print("") print("")
"""

View File

@ -88,6 +88,8 @@ class mstr_photogen:
# Generate the layer as if it were part of the OSM data # Generate the layer as if it were part of the OSM data
lg = mstr_layergen(tag, value, self._lat, self._ty, self._lng, self._tx, False, is_completion=True) lg = mstr_layergen(tag, value, self._lat, self._ty, self._lng, self._tx, False, is_completion=True)
lg.set_max_latlng_tile(self._maxlatlng) lg.set_max_latlng_tile(self._maxlatlng)
lg.set_latlng_folder(self._latlngfld)
lg.open_db()
lg.genlayer() lg.genlayer()
# Load the image # Load the image
@ -100,6 +102,16 @@ class mstr_photogen:
self._tile = completion self._tile = completion
# There may be some tiles that have a larger sea or even an ocean in them - these need to be
# removed from the final tile
ocean_pix = self._tile.load()
for y in range(self._tile.width):
for x in range(self._tile.height):
p = ocean_pix[x,y]
if p[0] == 255 and p[1] == 0 and p[2] == 255:
t = (0,0,0,0)
ocean_pix[x,y] = t
# We are now in posession of the final image. # We are now in posession of the final image.
# Scale to correct size. # Scale to correct size.
@ -108,14 +120,17 @@ class mstr_photogen:
# This we can save accordingly. # This we can save accordingly.
self._tile.convert('RGB').save(mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/orthos/" + str(self._ty) + "_" + str(self._tx) + ".png") self._tile.convert('RGB').save(mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/orthos/" + str(self._ty) + "_" + str(self._tx) + ".png")
# Now we convert this into a DDS # Now we convert this into a DDS
with image.Image(filename=mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/orthos/" + str(self._ty) + "_" + str(self._tx) + ".png") as img: with image.Image(filename=mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/orthos/" + str(self._ty) + "_" + str(self._tx) + ".png") as img:
img.compression = "dxt1" img.compression = "dxt1"
img.save(mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/orthos/" + str(self._ty) + "_" + str(self._tx) + ".dds") img.save(filename=mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/orthos/" + str(self._ty) + "_" + str(self._tx) + ".dds")
# TODO: CUT OUT OCEANS AND SEAS IN DDS # TODO: CUT OUT OCEANS AND SEAS IN DDS
os.remove(mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/orthos/" + str(self._ty) + "_" + str(self._tx) + ".png") os.remove(mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/orthos/" + str(self._ty) + "_" + str(self._tx) + ".png")
# This checks the final image for empty patches. Should one be # This checks the final image for empty patches. Should one be
# found, we will generate something to fill the gap. If this is # found, we will generate something to fill the gap. If this is
# the case, we will also note this in the database for the tile, # the case, we will also note this in the database for the tile,

View File

@ -18,13 +18,14 @@ from functions import *
from log import * from log import *
class mstr_tiledb: class mstr_tiledb:
def __init__(self, lat, lng): def __init__(self, lat, lng, latlngfld):
# Note coords # Note coords
self._latitude = lat self._latitude = lat
self._longitude = lng self._longitude = lng
self._latlngfld = latlngfld
# The db file will be created, should it not exist # The db file will be created, should it not exist
self._conn = sqlite3.connect(mstr_datafolder + "Tiles/" + str(self._latitude) + "_" + str(self._longitude) + "/data.db") self._conn = sqlite3.connect(mstr_datafolder + "Tiles/z_orthographic_" + latlngfld + "/data.db")
self._crs = self._conn.cursor() self._crs = self._conn.cursor()
#mstr_msg("tiledb", "Database object initiated") #mstr_msg("tiledb", "Database object initiated")

View File

@ -209,8 +209,3 @@ class mstr_xp_dsfgen:
self.convert_dsf_text() self.convert_dsf_text()
mstr_msg("xp_dsfgen", "[X-Plane] DSF for tile completed") mstr_msg("xp_dsfgen", "[X-Plane] DSF for tile completed")
# Testing
dsf = mstr_xp_dsfgen(51, 7, 101, 63, 0.01010)
dsf.build_dsf_for_tile()

View File

@ -42,7 +42,7 @@ class mstr_xp_normalmap:
# then provide it # then provide it
def load_layer(self): def load_layer(self):
qtr = int(mstr_photores / 4) qtr = int(mstr_photores / 4)
image = Image.open(mstr_datafolder + "_cache/" + str(self._lat) + "-" + str(self._lng) + "_" + self._tag + "_" + self._value + "_layer.png") image = Image.open(mstr_datafolder + "_cache/" + str(self._lat) + "-" + str(self._tv) + "_" + str(self._lng) + "-" + str(self._th) + "_" + self._tag + "-" + self._value + "_layer.png")
image = image.resize((qtr,qtr), Image.Resampling.LANCZOS) image = image.resize((qtr,qtr), Image.Resampling.LANCZOS)
mstr_msg("xp_normalmap", "[X-Plane] Layer image loaded") mstr_msg("xp_normalmap", "[X-Plane] Layer image loaded")
return image return image
@ -50,13 +50,16 @@ class mstr_xp_normalmap:
# A few mathematical calls we need # A few mathematical calls we need
# -------------------------------------------------------- # --------------------------------------------------------
def intensity(pixel): def intensity(self, pixel):
avg = (pixel[0] + pixel[1] + pixel[2]) / 3 avg = (pixel[0] + pixel[1] + pixel[2]) / 3
pavg = 255.0 / avg if avg > 0:
pavg = 255.0 / avg
else:
pavg = 0
return pavg return pavg
def clamp(px, mpx): def clamp(self, px, mpx):
if px > mpx-1: if px > mpx-1:
return mpx-1 return mpx-1
else: else:
@ -66,11 +69,11 @@ class mstr_xp_normalmap:
return px return px
def map_component(px): def map_component(self, px):
return (px + 1.0) * (255.0 / 2.0) return (px + 1.0) * (255.0 / 2.0)
def normalize_vector(v): def normalize_vector(self, v):
vc = np.array([v[0], v[1], v[2]]) vc = np.array([v[0], v[1], v[2]])
norm = np.linalg.norm(vc) norm = np.linalg.norm(vc)
nv = vc / norm nv = vc / norm
@ -81,7 +84,8 @@ class mstr_xp_normalmap:
# The Big Mac. Generate the normal map # The Big Mac. Generate the normal map
def generate_normal_map_for_layer(self, image): def generate_normal_map_for_layer(self, image):
mstr_msg("xp_normalmap", "[X-Plane] Beginning normal map generation") mstr_msg("xp_normalmap", "[X-Plane] Beginning normal map generation")
nmp = Image.new("RGBA", (image.width, image.height), (128,128,255,255)) #nmp = Image.new("RGBA", (image.width, image.height), (128,128,255,255))
nmp = Image.new("RGBA", (image.width, image.height), (0,0,0,0))
org = image.load() org = image.load()
nmp_pix = nmp.load() nmp_pix = nmp.load()
@ -93,44 +97,49 @@ class mstr_xp_normalmap:
a = v * 255.0 a = v * 255.0
alpha = int(a) alpha = int(a)
# Let's try some shenanigans # Let's try some shenanigans
for y in range(image.width): w = image.width
for x in range(image.height): h = image.height
# Neighboring pixels for y in range(h):
px_t = org[ self.clamp(x, image.width), self.clamp(y+1, image.height) ] for x in range(w):
px_tr = org[ self.clamp(x+1, image.width), self.clamp(y+1, image.height) ] p = org[x,y]
px_r = org[ self.clamp(x+1, image.width), self.clamp(y, image.height) ] if p[3] > 0: # Only do something if there is something to do in layer
px_br = org[ self.clamp(x+1, image.width), self.clamp(y+1, image.height) ] # Neighboring pixels
px_b = org[ self.clamp(x, image.width), self.clamp(y-1, image.height) ] px_t = org[ self.clamp(x, w), self.clamp(y+1, h) ]
px_bl = org[ self.clamp(x-1, image.width), self.clamp(y-1, image.height) ] px_tr = org[ self.clamp(x+1, w), self.clamp(y+1, h) ]
px_l = org[ self.clamp(x-1, image.width), self.clamp(y, image.height) ] px_r = org[ self.clamp(x+1, w), self.clamp(y, h) ]
px_tl = org[ self.clamp(x-1, image.width), self.clamp(y+1, image.height) ] 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 # Intensities of pixels
it_t = self.intensity(px_t) it_t = self.intensity(px_t)
it_tr = self.intensity(px_tr) it_tr = self.intensity(px_tr)
it_r = self.intensity(px_r) it_r = self.intensity(px_r)
it_br = self.intensity(px_br) it_br = self.intensity(px_br)
it_b = self.intensity(px_b) it_b = self.intensity(px_b)
it_bl = self.intensity(px_bl) it_bl = self.intensity(px_bl)
it_l = self.intensity(px_l) it_l = self.intensity(px_l)
it_tl = self.intensity(px_tl) it_tl = self.intensity(px_tl)
# Sobel filter # Sobel filter
dx = (it_tr + 2.0 * it_r + it_br) - (it_tl + 2.0 * it_l + it_bl) 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) 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 dz = 10 # This is usually a good value for strength
v = (dx, dy, dz) v = (dx, dy, dz)
nrm = self.normalize_vector(v) nrm = self.normalize_vector(v)
# Invert height for our Orthos # Invert height for our Orthos
if nrm[1] > 0: if nrm[1] > 0:
nrm[1] = 0 - (abs(nrm[1])) nrm[1] = 0 - (abs(nrm[1]))
else: else:
nrm[1] = abs(nrm[1]) nrm[1] = abs(nrm[1])
# Set pixel # Set pixel
nmp_pix[x,y] = (int(self.map_component(nrm[0])), int(self.map_component(nrm[1])), int(self.map_component(nrm[2])), alpha) nmp_pix[x,y] = (int(self.map_component(nrm[0])), int(self.map_component(nrm[1])), int(self.map_component(nrm[2])), alpha)
mstr_msg("xp_normalmap", "[X-Plane] Normal map generated") mstr_msg("xp_normalmap", "[X-Plane] Normal map generated")
return nmp return nmp