Normal map moved to end of ortho pipeline. Water is no longer rendered underneath, but makes use of normal maps. Physics model in orthos fixed. Testing of normals outstanding.

This commit is contained in:
Marcus Str. 2024-11-30 22:12:36 +01:00
parent 43d00df062
commit 8a9dfd05f4
4 changed files with 83 additions and 234 deletions

View File

@ -22,7 +22,6 @@ from log import *
from tileinfo import *
from osmxml import *
from functions import *
from xp_normalmap import *
class mstr_layergen:
@ -409,6 +408,7 @@ class mstr_layergen:
# 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_scn_normalmaps == True and self._is_completion == False:
nm = False
@ -419,6 +419,7 @@ class mstr_layergen:
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(layer_comp)
"""
# Let's try our hand at pseudo shadows
@ -886,6 +887,7 @@ class mstr_layergen:
# 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_scn_normalmaps == True and self._is_completion == False:
nm = False
@ -896,6 +898,7 @@ class mstr_layergen:
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(layer_comp)
"""
# Return image

View File

@ -5,6 +5,7 @@ from defines import *
from layergen import *
from log import *
from functions import *
from xp_normalmap import *
# -------------------------------------------------------------------
# ORTHOGRAPHIC
@ -29,9 +30,7 @@ class mstr_photogen:
self._tx = tx
self._maxlatlng = [ maxlat, maxlng ]
# Define layer size depending on what is wanted
self._imgsize = 0
if mstr_photores == 2048: self._imgsize = 2048
if mstr_photores == 4096: self._imgsize = 6000
self._imgsize = mstr_photores
# Empty image where everything goes into
self._tile = Image.new("RGBA", (self._imgsize, self._imgsize))
self._latlngfld = self.latlng_folder([lat,lng])
@ -148,29 +147,6 @@ class mstr_photogen:
corrpix[x,y] = nc
if c[3] == 0:
corrpix[x,y] = (0,0,0,0)
# Now cut out inland water
for w in waterlayers:
wtr_pix = w.load()
for y in range(w.height):
for x in range(w.width):
v = wtr_pix[x,y]
if v[3] >= 50:
c = (0,0,0,0)
corrpix[x,y] = c
"""
ddsf_water = mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._ty) + "_" + str(self._tx) + "_water.png"
if os.path.isfile(ddsf_water) == True:
wtr = Image.open(ddsf_water)
wtr_pix = wtr.load()
for y in range(wtr.height):
for x in range(wtr.width):
v = wtr_pix[x,y]
if v <= 50:
c = (0,0,0,0)
corrpix[x,y] = c
"""
# We are now in posession of the final image.
@ -187,6 +163,29 @@ class mstr_photogen:
os.remove(mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._ty) + "_" + str(self._tx) + ".png")
# Now generate the normal map for this ortho.
# But only if this is enabled.
if mstr_xp_genscenery and mstr_xp_scn_normalmaps:
# Generate the normal normal map first (hah)
nrm = mstr_xp_normalmap()
nrmimg = nrm.generate_normal_map_for_layer(self._tile, False)
# Now we need to walk through the water layers and generate a combined normal map
wtrlyr = Image.new("RGBA", (self._imgsize, self._imgsize))
for w in waterlayers:
wtrlyr.alpha_composite(w)
wtrlyr = wtrlyr.resize((int(mstr_photores/4), int(mstr_photores/4)), Image.Resampling.BILINEAR)
wtrimg = nrm.generate_normal_map_for_layer(wtrlyr, True)
# Blend
nrmimg.alpha_composite(wtrimg)
# Save
nrmfln = mstr_datafolder + "z_orthographic/normals/" + self._latlngfld + "/" + str(self._ty) + "_" + str(
self._tx) + ".png"
nrmimg.save(nrmfln)
# This checks the final image for empty patches. Should one be

View File

@ -27,14 +27,7 @@ from log import *
class mstr_xp_normalmap:
# Only a few params
def __init__(self, lat, lng, tag, value, tv, th, latlngfld):
self._lat = lat
self._lng = lng
self._tag = tag
self._value = value
self._latlngfld = latlngfld
self._tv = tv
self._th = th
def __init__(self):
mstr_msg("xp_normalmap", "[X-Plane] Normal Map generator initialized")
@ -46,7 +39,7 @@ class mstr_xp_normalmap:
pavg = 255.0 / avg
else:
pavg = 0
return pavg
return pavg * 3
def clamp(self, px, mpx):
@ -72,7 +65,7 @@ class mstr_xp_normalmap:
# The Big Mac. Generate the normal map
def generate_normal_map_for_layer(self, image):
def generate_normal_map_for_layer(self, image, water=False):
mstr_msg("xp_normalmap", "[X-Plane] Beginning normal map generation")
# No specularity, no reflectivity - but standard color
# Blue (reflectivity) and alpha (specularity) need to be 1 - but can be adjusted as needed
@ -81,6 +74,9 @@ class mstr_xp_normalmap:
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))
if water: nmp = Image.new("RGBA", (image.width, image.height), (128, 128, 255, 0))
org = image.load()
nmp_pix = nmp.load()
@ -124,7 +120,10 @@ class mstr_xp_normalmap:
nrm[1] = abs(nrm[1])
# 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)
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:
nmp_pix[x,y] = (int(self.map_component(nrm[0])), int(self.map_component(nrm[1])), 255 - int(self.map_component(nrm[2])), 1)
mstr_msg("xp_normalmap", "[X-Plane] Normal map generated")
return nmp
@ -138,26 +137,8 @@ class mstr_xp_normalmap:
nrm = self.generate_normal_map_for_layer(layer)
# Normal map final file name
nrmfln = mstr_datafolder + "z_orthographic/normals/" + self._latlngfld + "/" + str(self._tv) + "_" + str(self._th) + ".png"
# Check for existence of normal map file
ex = os.path.isfile(nrmfln)
# Does not exist? Just save
if ex == False:
nrm.save(nrmfln)
# Exists? Open it, composite both, save
if ex == True:
nrmmap = Image.open(nrmfln)
nrmmap.alpha_composite(nrm)
# Specularity blending correction
nrmmap_pix = nrmmap.load()
for y in range(nrmmap.height):
for x in range(nrmmap.width):
c = nrmmap_pix[x,y]
nrmmap_pix[x,y] = (c[0], c[1], c[2], 1)
nrmmap.save(nrmfln)
#nrmfln = mstr_datafolder + "z_orthographic/normals/" + self._latlngfld + "/" + str(self._tv) + "_" + str(self._th) + ".png"
mstr_msg("xp_normalmap", "[X-Plane] Normal map saved")
mstr_msg("xp_normalmap", "[X-Plane] Normal map generated")
return nrm

View File

@ -34,6 +34,8 @@ class mstr_xp_scenery:
self._dsfstring = ""
self._demdata = None # To be populated when the mesh is built
self._demcoord = None # Also to be populated when mesh is built
self._waterdata = [] # So that we know where to implement water
#self.load_water_data()
# Build the correct file name for the elevation model
@ -66,12 +68,30 @@ class mstr_xp_scenery:
return fn
# Load the water data before we generate the mesh
def load_water_data(self):
fn = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/wtrfile"
with open(fn) as file:
for line in file:
ln = line.replace(" ", "_")
ln = ln.replace("\n", "")
ln = ln.replace("\r", "")
self._waterdata.append(ln)
# Check if ortho has water
def does_ortho_have_water(self, ortho):
wtr = False
if ortho in self._waterdata: wtr = True
return wtr
# Build the DSF for the ortho photo overlay
def build_and_convert_dsf(self):
end = self.find_earthnavdata_number()
llf = self.xplane_latlng_folder(end)
meshtxt = mstr_datafolder + "_cache/mesh_"+self._latlngfld+".txt"
scr = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/meshscript.txt"
cmd = mstr_xp_dsftool + " --text2dsf " + meshtxt + " '" + mstr_datafolder + "z_orthographic/Earth nav data/" + llf + "/" + self._latlngfld + ".dsf'"
os.system(cmd)
@ -160,9 +180,8 @@ class mstr_xp_scenery:
terstr = terstr + "TERRAIN\r\n"
terstr = terstr + "\r\n"
terstr = terstr + "LOAD_CENTER " + str(cnt_x) + " " + str(cnt_y) + " " + str(dmt) + " 2048\r\n"
#terstr = terstr + "BASE_TEX_NOWRAP ../../orthos/" + self._latlngfld + "/" + str(lat)+"_"+str(lng)+".dds\r\n"
terstr = terstr + "TEXTURE_NOWRAP ../../orthos/" + self._latlngfld + "/" + str(lat)+"_"+str(lng)+".dds\r\n"
if mstr_xp_scn_normalmaps == True:
terstr = terstr + "BASE_TEX_NOWRAP ../../orthos/" + self._latlngfld + "/" + str(lat)+"_"+str(lng)+".dds\r\n"
if mstr_xp_scn_normalmaps:
terstr = terstr + "NORMAL_TEX 1.0 ../../normals/" + self._latlngfld + "/" + str(lat)+"_"+str(lng)+".png\r\n"
terfln = mstr_datafolder + "z_orthographic/terrain/" + self._latlngfld + "/" + str(lat)+"_"+str(lng)+".ter"
@ -223,103 +242,34 @@ class mstr_xp_scenery:
with open(meshtxt, 'w') as textfile:
textfile.write(dsf_str)
dsf_str = ""
# Orthos
for lat in range(1, self._mlat+1):
for lng in range(1, self._mlng+1):
# Write the line only if an ortho exists of course.
ddsf = mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(lat) + "_" + str(lng) + ".dds"
ddsf_water = mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(lat) + "_" + str(lng) + "_water.png"
if os.path.isfile(ddsf) == True:
dsfstr = "TERRAIN_DEF terrain/" + self._latlngfld + "/" + str(lat) + "_" + str(lng) + ".ter\r\n"
# Let's check if this tile needs water beneath
if os.path.isfile(ddsf_water) == True:
dsfstr = dsfstr + "TERRAIN_DEF terrain_Water\r\n"
if os.path.isfile(ddsf):
dsf_str = dsf_str + "TERRAIN_DEF terrain/" + self._latlngfld + "/" + str(lat) + "_" + str(lng) + ".ter\r\n"
with open(meshtxt, 'a') as textfile:
textfile.write(dsfstr)
with open(meshtxt, 'a') as textfile:
textfile.write(dsf_str)
# OK. So. Let's build the mesh.
"""
# First, the ground water mesh
with open(meshtxt, 'a') as textfile:
textfile.write("BEGIN_PATCH 0 0.000000 -1.000000 1 5\r\n")
# Vertical row (Latitude Row)
for lat_r in range(0, len(self._demcoord)-2):
# Horizontal row (Longitude Column)
for lng_c in range(0, len(self._demcoord)-2):
# Lat/lng coordinate
lat_crd = self._demcoord[lat_r][lng_c][0]
lat_crd_t = self._demcoord[lat_r+1][lng_c][0]
lng_crd = self._demcoord[lat_r][lng_c][1]
lng_crd_r = self._demcoord[lat_r][lng_c+1][1]
# Coords of triangle vertices
# 0 - Longitude
# 1 - Latitude
# 2 - Height in m
t1_v1 = [ lng_crd_r, lat_crd, 0 ]
t1_v2 = [ lng_crd, lat_crd_t, 0 ]
t1_v3 = [ lng_crd_r, lat_crd_t, 0 ]
t2_v1 = [ lng_crd, lat_crd_t, 0 ]
t2_v2 = [ lng_crd_r, lat_crd, 0 ]
t2_v3 = [ lng_crd, lat_crd, 0 ]
t1_v1 = [ lng_crd_r, lat_crd, self._demcoord[lat_r][lng_c+1][2] ]
t1_v2 = [ lng_crd, lat_crd_t, self._demcoord[lat_r+1][lng_c][2] ]
t1_v3 = [ lng_crd_r, lat_crd_t, self._demcoord[lat_r+1][lng_c+1][2] ]
t2_v1 = [ lng_crd, lat_crd_t, self._demcoord[lat_r+1][lng_c][2] ]
t2_v2 = [ lng_crd_r, lat_crd, self._demcoord[lat_r][lng_c+1][2] ]
t2_v3 = [ lng_crd, lat_crd, self._demcoord[lat_r][lng_c][2] ]
# Write down the two triangles
t_str = ""
t_str = t_str + "BEGIN_PRIMITIVE 0\r\n"
t_str = t_str + "PATCH_VERTEX " + str(t1_v1[0]) + " " + str(t1_v1[1]) + " " + str(t1_v1[2]) + " 0.000015 0.000015\r\n"
t_str = t_str + "PATCH_VERTEX " + str(t1_v2[0]) + " " + str(t1_v2[1]) + " " + str(t1_v2[2]) + " 0.000015 0.000015\r\n"
t_str = t_str + "PATCH_VERTEX " + str(t1_v3[0]) + " " + str(t1_v3[1]) + " " + str(t1_v3[2]) + " 0.000015 0.000015\r\n"
t_str = t_str + "END_PRIMITIVE 0\r\n"
t_str = t_str + "BEGIN_PRIMITIVE 0\r\n"
t_str = t_str + "PATCH_VERTEX " + str(t2_v1[0]) + " " + str(t2_v1[1]) + " " + str(t2_v1[2]) + " 0.000015 0.000015\r\n"
t_str = t_str + "PATCH_VERTEX " + str(t2_v2[0]) + " " + str(t2_v2[1]) + " " + str(t2_v2[2]) + " 0.000015 0.000015\r\n"
t_str = t_str + "PATCH_VERTEX " + str(t2_v3[0]) + " " + str(t2_v3[1]) + " " + str(t2_v3[2]) + " 0.000015 0.000015\r\n"
t_str = t_str + "END_PRIMITIVE 0\r\n"
# Send to the file
with open(meshtxt, 'a') as textfile:
textfile.write(t_str)
t_str = ""
# Water mesh ends
with open(meshtxt, 'a') as textfile:
textfile.write("END PATCH\r\n")
"""
# Current patch
curpatch = 0
for lat in range(1, self._mlat+1):
for lng in range(1, self._mlng+1):
# Create the patch only if the matching ortho exists.
# This way we make sure that we hit the same order as the .ter files.
# We can also detect which lat and lng coord we are on.
#ddsf = mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/1_1.dds"
ddsf = mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(lat) + "_" + str(lng) + ".dds"
ddsf_water = mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(lat) + "_" + str(lng) + "_water.png"
if os.path.isfile(ddsf) == True:
scangrid = self.find_height_scan_start_end_points([ self._lat+((lat-1)*self._vstep), self._lng+((lng-1)*mstr_zl_18) ])
#sloped = self.build_sloped_scangrid(scangrid)
ddsf = mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(lat) + "_" + str(lng) + ".dds"
if os.path.isfile(ddsf):
# Base coords for this ortho
base_lat = self._lat + ((lat-1) * self._vstep)
@ -335,12 +285,6 @@ class mstr_xp_scenery:
latstep = self._vstep/odiv
lngstep = mstr_zl_18 /odiv
uv_step = 1 / odiv
# Height values
hgt_bl = 0
hgt_br = 0
hgt_tr = 0
hgt_tl = 0
# Generate the ortho tile
for y in range(0,odiv):
@ -408,11 +352,6 @@ class mstr_xp_scenery:
t_str = ""
# Height value:
# hgtindex = self.find_height_for_coord([lat, lng])
# height = self._demcoord[hgtindex[0]][hgtindex[1]][2]
# End this patch
with open(meshtxt, 'a') as textfile:
textfile.write("END PATCH\r\n")
@ -422,15 +361,12 @@ class mstr_xp_scenery:
# Let's check if this tile needs water beneath
needs_water = False
if os.path.isfile(ddsf_water) == True: needs_water = True
if needs_water == True:
"""
if self.does_ortho_have_water(str(lat) + "_" + str(lng)):
# Begin a new patch
with open(meshtxt, 'a') as textfile:
textfile.write("BEGIN_PATCH " + str(curpatch) + " 0.000000 -1.000000 1 5\r\n")
textfile.write("BEGIN_PATCH " + str(curpatch) + " 0.000000 -1.000000 2 5\r\n")
# Generate the ortho tile
for y in range(0,odiv):
@ -461,10 +397,10 @@ class mstr_xp_scenery:
hgt_br_idx = self.find_height_for_coord([lat_b, lng_r])
hgt_tr_idx = self.find_height_for_coord([lat_t, lng_r])
hgt_tl_idx = self.find_height_for_coord([lat_t, lng_l])
hgt_bl = round(self._demcoord[ hgt_bl_idx[0] ][ hgt_bl_idx[1] ][2] - .1, 6)
hgt_br = round(self._demcoord[ hgt_br_idx[0] ][ hgt_br_idx[1] ][2] - .1, 6)
hgt_tr = round(self._demcoord[ hgt_tr_idx[0] ][ hgt_tr_idx[1] ][2] - .1, 6)
hgt_tl = round(self._demcoord[ hgt_tl_idx[0] ][ hgt_tl_idx[1] ][2] - .1, 6)
hgt_bl = round(self._demcoord[ hgt_bl_idx[0] ][ hgt_bl_idx[1] ][2] - .01, 6)
hgt_br = round(self._demcoord[ hgt_br_idx[0] ][ hgt_br_idx[1] ][2] - .01, 6)
hgt_tr = round(self._demcoord[ hgt_tr_idx[0] ][ hgt_tr_idx[1] ][2] - .01, 6)
hgt_tl = round(self._demcoord[ hgt_tl_idx[0] ][ hgt_tl_idx[1] ][2] - .01, 6)
# Coords of triangle vertices
# 0 - Longitude
@ -500,6 +436,7 @@ class mstr_xp_scenery:
# Increase patch number
curpatch = curpatch + 1
"""
@ -561,76 +498,5 @@ class mstr_xp_scenery:
if startend[2] < 0: startend[2] = 0
if startend[3] > len(self._demdata)-1: startend[3] = startend[3] = len(self._demdata)-1
"""
t = self._lat
while t < startcoord[0]:
t = t + self._vstep
startend[0] = startend[0]+1
t = self._lat
while t < startcoord[0]+self._vstep:
t = t + self._vstep
startend[1] = startend[1]+1
t = self._lng
while t < startcoord[1]:
t = t + mstr_zl_18
startend[2] = startend[2]+1
t = self._lng
while t < startcoord[1]+mstr_zl_18:
t = t + mstr_zl_18
startend[3] = startend[3]+1
# Some corrections
startend[0] = startend[0]-1
if startend[0] < 0: startend[0] = 0
startend[1] = startend[1]+1
if startend[1] > len(self._demdata)-1: startend[1] = startend[1] = len(self._demdata)-1
startend[2] = startend[2]-1
if startend[2] < 0: startend[2] = 0
startend[3] = startend[3]+1
if startend[3] > len(self._demdata)-1: startend[3] = startend[3] = len(self._demdata)-1
"""
return startend
# Function to subdivide between two vectors
def subdivide_vectors(self, v1, v2, subdivisions):
#return np.linspace(v1, v2, subdivisions + 2, axis=0) # +2 to include endpoints
return numpy.linspace(v1, v2, subdivisions + 2, axis=0) # +2 to include endpoints
# This build a scangrid with increased resolution, extrapolated from existing points.
# With this we can accurately depict the height of a point a long the slope of the ground mesh.
def build_sloped_scangrid(self, grid):
# Contains the data as defined by the grid passed in
tmp_dem = []
# Acquire original grid data
for l in range(grid[2], grid[3]+1):
row = []
for c in range(grid[0], grid[1]+1):
row.append(self._demcoord[l][c])
tmp_dem.append(row)
# Subdivide the array
subdivisions = 10
result = []
for i in range(len(tmp_dem) - 1):
for j in range(len(tmp_dem[i]) - 1):
subdivided = self.subdivide_vectors(tmp_dem[i][j], tmp_dem[i][j + 1], subdivisions)
if i > 0: # Avoid duplicating start point
subdivided = subdivided[1:]
result.append(subdivided)
# Combine all subdivisions into one array
result = numpy.vstack(result)
return result