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", "scrub", "natural", "scrub"),
("natural", "heath", "natural", "heath"),
("natural", "sand", "natural", "sand"),
("natural", "desert", "natural", "desert"),
# Z-Order 3
("natural", "water", "natural", "water"),
("natural", "bay", "natural", "beach"),
@ -180,7 +182,9 @@ mstr_ortho_layers = [
("building", "terrace", "building", "industrial"),
("building", "hangar", "building", "industrial"),
("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", "hangar", 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._lng_number = lngnum
self._layerborder = -1
self._tiledb = mstr_tiledb(lat, lng)
self._tiledb.create_tables()
self._is_completion = is_completion
# Define layer size depending on what is wanted
self._imgsize = 0
@ -58,6 +56,11 @@ class mstr_layergen:
def set_latlng_folder(self, 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
# before the actual crop of farm field itself. This generates this "border" layer,
# and returns it.
@ -355,6 +358,7 @@ class mstr_layergen:
# on what they are.
for i in mstr_mask_blur:
if i[0] == self._tag and i[1] == self._value:
if self._tag != "place" and (self._value != "sea" or self._value != "ocean"):
osm_mask = osm_mask.filter(ImageFilter.BoxBlur(radius=i[2]))
break
@ -414,13 +418,6 @@ class mstr_layergen:
layer_border = self.genborder(osm_edge, "landuse", "meadow")
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
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" )
@ -430,6 +427,20 @@ class mstr_layergen:
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
if mstr_shadow_enabled == True:
if mstr_shadow_shift >= 2:
@ -759,6 +770,12 @@ class mstr_layergen:
# 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()

View File

@ -20,6 +20,7 @@ from layergen import *
from photogen import *
from osmxml import *
from tilegen import *
from xp_dsfgen import *
# The main class which handles the rest
@ -110,18 +111,18 @@ class mstr_orthographic:
mstr_msg("orthographic", "Created Tiles sub folder: " + self._latlngfld)
# Generate the orthos folder
if not os.path.exists(self._output + "/Tiles/z_orthograpic_" + self._latlngfld + "/orthos"):
os.makedirs(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_orthographic_" + self._latlngfld +"/orthos")
mstr_msg("orthographic", "Created tile orthos folder")
if mstr_xp_genscenery == True:
if not os.path.exists(self._output + "/Tiles/z_orthograpic_" + self._latlngfld + "/terrain"):
os.makedirs(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_orthographic_" + self._latlngfld + "/terrain")
mstr_msg("orthographic", "Created X-Plane tile terrain folder")
if mstr_xp_generate_normal_maps == True:
if not os.path.exists(self._output + "/Tiles/z_orthograpic_" + self._latlngfld + "/normals"):
os.makedirs(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_orthographic_" + self._latlngfld + "/normals")
mstr_msg("orthographic", "Created X-Plane tile normals folder")
# 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")
# 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
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.set_max_latlng_tile(maxlatlng)
lg.set_latlng_folder(self._latlngfld)
lg.open_db()
lg.genlayer()
curlyr = curlyr+1
mstr_msg("orthographic", "All layers created")
@ -213,8 +215,6 @@ class mstr_orthographic:
pg = mstr_photogen(self._lat, self._long, cur_tile_y, cur_tile_x, maxlatlng[0], maxlatlng[1])
pg.genphoto()
mstr_msg("orthographic", " -- Ortho photo generated -- ")
if mstr_xp_genscenery == True:
xp_datagroup = xp_datagroup + 1
print("")
print("")
@ -254,6 +254,21 @@ class mstr_orthographic:
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")
tg = mstr_tilegen(self._lat, self._lng, self._vstep, top_lat, top_lng)
tg.genTiles()
@ -264,6 +279,7 @@ class mstr_orthographic:
print("")
mstr_msg("orthographic", "Thanks for using Orthographic! -- Best, Marcus")
print("")
"""

View File

@ -88,6 +88,8 @@ class mstr_photogen:
# 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.set_max_latlng_tile(self._maxlatlng)
lg.set_latlng_folder(self._latlngfld)
lg.open_db()
lg.genlayer()
# Load the image
@ -100,6 +102,16 @@ class mstr_photogen:
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.
# Scale to correct size.
@ -108,14 +120,17 @@ class mstr_photogen:
# 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")
# 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:
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
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
# 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,

View File

@ -18,13 +18,14 @@ from functions import *
from log import *
class mstr_tiledb:
def __init__(self, lat, lng):
def __init__(self, lat, lng, latlngfld):
# Note coords
self._latitude = lat
self._longitude = lng
self._latlngfld = latlngfld
# 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()
#mstr_msg("tiledb", "Database object initiated")

View File

@ -209,8 +209,3 @@ class mstr_xp_dsfgen:
self.convert_dsf_text()
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
def load_layer(self):
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)
mstr_msg("xp_normalmap", "[X-Plane] Layer image loaded")
return image
@ -50,13 +50,16 @@ class mstr_xp_normalmap:
# A few mathematical calls we need
# --------------------------------------------------------
def intensity(pixel):
def intensity(self, pixel):
avg = (pixel[0] + pixel[1] + pixel[2]) / 3
if avg > 0:
pavg = 255.0 / avg
else:
pavg = 0
return pavg
def clamp(px, mpx):
def clamp(self, px, mpx):
if px > mpx-1:
return mpx-1
else:
@ -66,11 +69,11 @@ class mstr_xp_normalmap:
return px
def map_component(px):
def map_component(self, px):
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]])
norm = np.linalg.norm(vc)
nv = vc / norm
@ -81,7 +84,8 @@ class mstr_xp_normalmap:
# The Big Mac. Generate the normal map
def generate_normal_map_for_layer(self, image):
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()
nmp_pix = nmp.load()
@ -93,18 +97,23 @@ class mstr_xp_normalmap:
a = v * 255.0
alpha = int(a)
# Let's try some shenanigans
for y in range(image.width):
for x in range(image.height):
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, image.width), self.clamp(y+1, image.height) ]
px_tr = org[ self.clamp(x+1, image.width), self.clamp(y+1, image.height) ]
px_r = org[ self.clamp(x+1, image.width), self.clamp(y, image.height) ]
px_br = org[ self.clamp(x+1, image.width), self.clamp(y+1, image.height) ]
px_b = org[ self.clamp(x, image.width), self.clamp(y-1, image.height) ]
px_bl = org[ self.clamp(x-1, image.width), self.clamp(y-1, image.height) ]
px_l = org[ self.clamp(x-1, image.width), self.clamp(y, image.height) ]
px_tl = org[ self.clamp(x-1, image.width), self.clamp(y+1, image.height) ]
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)