X-Plane DSF file generator, X-Plane normal map generator. Normal maps can be turned on or off in defines.py . Normal map generation implemented in layergen. Nearing initial public release. Added sand and desert source material. Changed layer order for some parts.
BIN
Textures/building/common/brd/b5.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
Textures/building/common/ptc/b5_p1.png
Normal file
After Width: | Height: | Size: 672 KiB |
BIN
Textures/building/common/ptc/b5_p2.png
Normal file
After Width: | Height: | Size: 637 KiB |
BIN
Textures/natural/desert/brd/b1.png
Normal file
After Width: | Height: | Size: 3.3 MiB |
BIN
Textures/natural/desert/ptc/b1_p1.png
Normal file
After Width: | Height: | Size: 1.9 MiB |
BIN
Textures/natural/desert/ptc/b1_p2.png
Normal file
After Width: | Height: | Size: 1.9 MiB |
BIN
Textures/natural/sand/brd/b1.png
Normal file
After Width: | Height: | Size: 3.9 MiB |
BIN
Textures/natural/sand/ptc/b1_p1.png
Normal file
After Width: | Height: | Size: 2.0 MiB |
BIN
Textures/natural/sand/ptc/b1_p2.png
Normal file
After Width: | Height: | Size: 1.7 MiB |
BIN
Textures/natural/water/normal_template.png
Normal file
After Width: | Height: | Size: 705 KiB |
26
defines.py
@ -71,8 +71,26 @@ mstr_xp_genscenery = True
|
|||||||
|
|
||||||
# X-Plane specific
|
# X-Plane specific
|
||||||
mstr_xp_dsftool = "M:/Developer/Projects/orthographic/bin/DSFTool.exe"
|
mstr_xp_dsftool = "M:/Developer/Projects/orthographic/bin/DSFTool.exe"
|
||||||
mstr_xp_ddstool = "M:/Developer/Projects/orthographic/bin/DDSTool.exe"
|
|
||||||
mstr_xp_folder = "M:/Flight Sim/Simulator/11/"
|
mstr_xp_folder = "M:/Flight Sim/Simulator/11/"
|
||||||
|
# Whether or not you want normal maps for certain geographical elements
|
||||||
|
# to make them appear more realistic.
|
||||||
|
mstr_xp_generate_normal_maps = True
|
||||||
|
|
||||||
|
# If you set the above to true, you can
|
||||||
|
# 1) define for which features to generate normal maps for, and
|
||||||
|
# 2) the specularity for each feature.
|
||||||
|
# A specularity value of 100 is fully specular, useful for water
|
||||||
|
# A specularity value of 10 is more useful for inland features.
|
||||||
|
# Sand may also reflect some of its surface, so possibly 25 is good there.
|
||||||
|
mstr_xp_normal_maps = [
|
||||||
|
("landuse", "farmland", 10),
|
||||||
|
("landuse", "forest", 10),
|
||||||
|
("leisure", "nature_reserve", 10),
|
||||||
|
("natural", "water", 100),
|
||||||
|
("water", "pond", 100),
|
||||||
|
("water", "river", 100),
|
||||||
|
("water", "lake", 100)
|
||||||
|
]
|
||||||
|
|
||||||
# How much of a tile we need for each zoom level. The higher
|
# How much of a tile we need for each zoom level. The higher
|
||||||
# the zoom level, the smaller the area to generate a mask of - but also
|
# the zoom level, the smaller the area to generate a mask of - but also
|
||||||
@ -108,9 +126,6 @@ mstr_ortho_layers = [
|
|||||||
("highway", "path", 3),
|
("highway", "path", 3),
|
||||||
("natural", "bare_rock", "natural", "bare_rock"),
|
("natural", "bare_rock", "natural", "bare_rock"),
|
||||||
("natural", "grassland", "landuse", "meadow"),
|
("natural", "grassland", "landuse", "meadow"),
|
||||||
("natural", "wetland", "natural", "wetland"),
|
|
||||||
("natural", "scrub", "natural", "scrub"),
|
|
||||||
("natural", "heath", "natural", "heath"),
|
|
||||||
("leisure", "park", "leisure", "green"),
|
("leisure", "park", "leisure", "green"),
|
||||||
("leisure", "dog_park", "leisure", "green"),
|
("leisure", "dog_park", "leisure", "green"),
|
||||||
("leisure", "garden", "leisure", "green"),
|
("leisure", "garden", "leisure", "green"),
|
||||||
@ -132,6 +147,9 @@ mstr_ortho_layers = [
|
|||||||
("waterway", "stream", 10),
|
("waterway", "stream", 10),
|
||||||
("leisure", "nature_reserve", "landuse", "forest"),
|
("leisure", "nature_reserve", "landuse", "forest"),
|
||||||
("landuse", "forest", "landuse", "forest"),
|
("landuse", "forest", "landuse", "forest"),
|
||||||
|
("natural", "wetland", "natural", "wetland"),
|
||||||
|
("natural", "scrub", "natural", "scrub"),
|
||||||
|
("natural", "heath", "natural", "heath"),
|
||||||
# Z-Order 3
|
# Z-Order 3
|
||||||
("natural", "water", "natural", "water"),
|
("natural", "water", "natural", "water"),
|
||||||
("natural", "bay", "natural", "beach"),
|
("natural", "bay", "natural", "beach"),
|
||||||
|
13
layergen.py
@ -22,6 +22,7 @@ from log import *
|
|||||||
from tiledb import *
|
from tiledb import *
|
||||||
from osmxml import *
|
from osmxml import *
|
||||||
from functions import *
|
from functions import *
|
||||||
|
from xp_normalmap import *
|
||||||
|
|
||||||
class mstr_layergen:
|
class mstr_layergen:
|
||||||
|
|
||||||
@ -53,6 +54,10 @@ class mstr_layergen:
|
|||||||
self._maxlng = maxlatlng[1]
|
self._maxlng = maxlatlng[1]
|
||||||
mstr_msg("layergen", "Maximum latitude and longitude tile numbers received")
|
mstr_msg("layergen", "Maximum latitude and longitude tile numbers received")
|
||||||
|
|
||||||
|
# Set latlng folder
|
||||||
|
def set_latlng_folder(self, latlngfld):
|
||||||
|
self._latlngfld = latlngfld
|
||||||
|
|
||||||
# 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.
|
||||||
@ -749,3 +754,11 @@ class mstr_layergen:
|
|||||||
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" )
|
||||||
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:
|
||||||
|
nrm = mstr_xp_normalmap(self._latitude, self._longitude, self._tag, self._value, self._lat_number, self._lng_number, self._latlngfld)
|
||||||
|
nrm.build_normalmap()
|
||||||
|
|
||||||
|
@ -25,6 +25,17 @@ from tilegen import *
|
|||||||
# The main class which handles the rest
|
# The main class which handles the rest
|
||||||
class mstr_orthographic:
|
class mstr_orthographic:
|
||||||
|
|
||||||
|
# Constructor of class. Takes longitude and latitude.
|
||||||
|
def __init__(self, lat, lng, outfolder, pwd):
|
||||||
|
self._lat = lat
|
||||||
|
self._long = lng
|
||||||
|
self._output = outfolder
|
||||||
|
self._pwd = pwd
|
||||||
|
self._vstep = self._findVerticalStepping()
|
||||||
|
self._latlngfld = self.latlng_folder([lat,lng])
|
||||||
|
mstr_msg("orthographic", "Initiated with LAT: " + str(lat) + ", LNG: " + str(lng))
|
||||||
|
|
||||||
|
|
||||||
# It did happen that the generation of photos crashed as, for some reason,
|
# It did happen that the generation of photos crashed as, for some reason,
|
||||||
# a file in _cache was apparently used by another process (hint: it was
|
# a file in _cache was apparently used by another process (hint: it was
|
||||||
# not). I therefore need this test before deleting a file in _cache, so
|
# not). I therefore need this test before deleting a file in _cache, so
|
||||||
@ -94,9 +105,24 @@ class mstr_orthographic:
|
|||||||
mstr_msg("orthographic", "Created Tiles folder.")
|
mstr_msg("orthographic", "Created Tiles folder.")
|
||||||
|
|
||||||
# Generate the Tiles/lat-lng folder for the finished tile
|
# Generate the Tiles/lat-lng folder for the finished tile
|
||||||
if not os.path.exists(self._output + "/Tiles/"+str(self._lat)+"_"+str(self._long)):
|
if not os.path.exists(self._output + "/Tiles/z_orthographic_" + self._latlngfld):
|
||||||
os.makedirs(self._output + "/Tiles/"+str(self._lat)+"_"+str(self._long))
|
os.makedirs(self._output + "/Tiles/z_orthographic_"+ self._latlngfld)
|
||||||
mstr_msg("orthographic", "Created Tiles sub folder: " +str(self._lat)+"_"+str(self._long))
|
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")
|
||||||
|
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")
|
||||||
|
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")
|
||||||
|
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
|
||||||
# smallest possible, from which the bigger ones are later built.
|
# smallest possible, from which the bigger ones are later built.
|
||||||
@ -144,7 +170,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/" + str(self._lat) + "_" + str(self._long) + "/textures/" + 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) + ".jpg") == 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))
|
||||||
@ -162,16 +188,6 @@ class mstr_orthographic:
|
|||||||
# itself, and finally, compose the ortho photo.
|
# itself, and finally, compose the ortho photo.
|
||||||
mstr_msg("orthographic", "Beginning generation of layers")
|
mstr_msg("orthographic", "Beginning generation of layers")
|
||||||
|
|
||||||
# Generate the Tiles/lat-lng folder for the finished tile
|
|
||||||
if not os.path.exists(self._output + "/Tiles/"+str(self._lat)+"_"+str(self._long) + "/Textures"):
|
|
||||||
os.makedirs(self._output + "/Tiles/"+str(self._lat)+"_"+str(self._long)+"/Textures")
|
|
||||||
mstr_msg("orthographic", "Created tile textures folder")
|
|
||||||
|
|
||||||
# Generate the Tiles/terrain folder for the finished tile
|
|
||||||
if not os.path.exists(self._output + "/Tiles/"+str(self._lat)+"_"+str(self._long) + "/terrain"):
|
|
||||||
os.makedirs(self._output + "/Tiles/"+str(self._lat)+"_"+str(self._long)+"/terrain")
|
|
||||||
mstr_msg("orthographic", "Created tile terrain folder")
|
|
||||||
|
|
||||||
curlyr = 1
|
curlyr = 1
|
||||||
for layer in layers:
|
for layer in layers:
|
||||||
# Let the user know
|
# Let the user know
|
||||||
@ -186,6 +202,7 @@ class mstr_orthographic:
|
|||||||
# Generate the layer
|
# Generate the layer
|
||||||
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.genlayer()
|
lg.genlayer()
|
||||||
curlyr = curlyr+1
|
curlyr = curlyr+1
|
||||||
mstr_msg("orthographic", "All layers created")
|
mstr_msg("orthographic", "All layers created")
|
||||||
@ -295,12 +312,19 @@ class mstr_orthographic:
|
|||||||
return layers
|
return layers
|
||||||
|
|
||||||
|
|
||||||
|
# Construct a folder name for latitude and longitude
|
||||||
|
def latlng_folder(self, numbers):
|
||||||
|
fstr = ""
|
||||||
|
if numbers[0] >= 0: fstr = "+"
|
||||||
|
if numbers[0] < 0: fstr = "-"
|
||||||
|
if abs(numbers[0]) < 10: fstr = fstr + "0" + str(numbers[0])
|
||||||
|
if abs(numbers[0]) >= 10 and numbers[0] <= 90: fstr = fstr + str(numbers[0])
|
||||||
|
|
||||||
|
if numbers[1] >= 0: fstr = fstr + "+"
|
||||||
|
if numbers[1] < 0: fstr = fstr + "-"
|
||||||
|
if abs(numbers[1]) < 10: fstr = fstr + "00" + str(numbers[1])
|
||||||
|
if abs(numbers[1]) >= 10 and numbers[0] <= 99: fstr = fstr + "0" + str(numbers[1])
|
||||||
|
if abs(numbers[1]) >= 100 : fstr = fstr + str(numbers[1])
|
||||||
|
|
||||||
|
return fstr
|
||||||
|
|
||||||
# Constructor of class. Takes longitude and latitude.
|
|
||||||
def __init__(self, lat, lng, outfolder, pwd):
|
|
||||||
self._lat = lat
|
|
||||||
self._long = lng
|
|
||||||
self._output = outfolder
|
|
||||||
self._pwd = pwd
|
|
||||||
self._vstep = self._findVerticalStepping()
|
|
||||||
mstr_msg("orthographic", "Initiated with LAT: " + str(lat) + ", LNG: " + str(lng))
|
|
28
photogen.py
@ -4,6 +4,7 @@ from PIL import Image, ImageFilter
|
|||||||
from defines import *
|
from defines import *
|
||||||
from layergen import *
|
from layergen import *
|
||||||
from log import *
|
from log import *
|
||||||
|
from wand import image
|
||||||
|
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
# ORTHOGRAPHIC
|
# ORTHOGRAPHIC
|
||||||
@ -33,6 +34,7 @@ class mstr_photogen:
|
|||||||
if mstr_photores == 4096: self._imgsize = 6000
|
if mstr_photores == 4096: self._imgsize = 6000
|
||||||
# Empty image where everything goes into
|
# Empty image where everything goes into
|
||||||
self._tile = Image.new("RGBA", (self._imgsize, self._imgsize))
|
self._tile = Image.new("RGBA", (self._imgsize, self._imgsize))
|
||||||
|
self._latlngfld = self.latlng_folder([lat,lng])
|
||||||
mstr_msg("photogen", "Photogen initialized")
|
mstr_msg("photogen", "Photogen initialized")
|
||||||
|
|
||||||
|
|
||||||
@ -104,7 +106,14 @@ class mstr_photogen:
|
|||||||
self._tile = self._tile.resize((mstr_photores, mstr_photores), Image.Resampling.BILINEAR)
|
self._tile = self._tile.resize((mstr_photores, mstr_photores), Image.Resampling.BILINEAR)
|
||||||
|
|
||||||
# This we can save accordingly.
|
# This we can save accordingly.
|
||||||
self._tile.convert('RGB').save(mstr_datafolder + "Tiles/" + str(self._lat) + "_" + str(self._lng) + "/textures/" + str(self._ty) + "_" + str(self._tx) + ".jpg", format='JPEG', subsampling=0, quality=100)
|
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")
|
||||||
|
# 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
|
# This checks the final image for empty patches. Should one be
|
||||||
@ -149,3 +158,20 @@ class mstr_photogen:
|
|||||||
|
|
||||||
mask.save( mstr_datafolder + "_cache/" + str(self._lat) + "-" + str(self._ty) + "_" + str(self._lng) + "-" + str(self._tx) + "_tile-completion.png" )
|
mask.save( mstr_datafolder + "_cache/" + str(self._lat) + "-" + str(self._ty) + "_" + str(self._lng) + "-" + str(self._tx) + "_tile-completion.png" )
|
||||||
mstr_msg("photogen", "Generated and saved empty space mask")
|
mstr_msg("photogen", "Generated and saved empty space mask")
|
||||||
|
|
||||||
|
|
||||||
|
# Construct a folder name for latitude and longitude
|
||||||
|
def latlng_folder(self, numbers):
|
||||||
|
fstr = ""
|
||||||
|
if numbers[0] >= 0: fstr = "+"
|
||||||
|
if numbers[0] < 0: fstr = "-"
|
||||||
|
if abs(numbers[0]) < 10: fstr = fstr + "0" + str(numbers[0])
|
||||||
|
if abs(numbers[0]) >= 10 and numbers[0] <= 90: fstr = fstr + str(numbers[0])
|
||||||
|
|
||||||
|
if numbers[1] >= 0: fstr = fstr + "+"
|
||||||
|
if numbers[1] < 0: fstr = fstr + "-"
|
||||||
|
if abs(numbers[1]) < 10: fstr = fstr + "00" + str(numbers[1])
|
||||||
|
if abs(numbers[1]) >= 10 and numbers[0] <= 99: fstr = fstr + "0" + str(numbers[1])
|
||||||
|
if abs(numbers[1]) >= 100 : fstr = fstr + str(numbers[1])
|
||||||
|
|
||||||
|
return fstr
|
61
wedgen.py
@ -1,61 +0,0 @@
|
|||||||
|
|
||||||
# -------------------------------------------------------------------
|
|
||||||
# ORTHOGRAPHIC
|
|
||||||
# Your personal aerial satellite. Always on. At any altitude.*
|
|
||||||
# Developed by MarStrMind
|
|
||||||
# License: Open Software License 3.0
|
|
||||||
# Up to date version always on marstr.online
|
|
||||||
# -------------------------------------------------------------------
|
|
||||||
# wedgen.py
|
|
||||||
# Generates the XML for X-Plane's WED, which you need to use the
|
|
||||||
# orthos in the flight simulator.
|
|
||||||
# -------------------------------------------------------------------
|
|
||||||
|
|
||||||
import xml.dom.minidom
|
|
||||||
from defines import *
|
|
||||||
from log import *
|
|
||||||
from tiledb import *
|
|
||||||
|
|
||||||
|
|
||||||
class mstr_wedgen:
|
|
||||||
def __init__(self, lat, lng):
|
|
||||||
self._lat = lat
|
|
||||||
self._lng = lng
|
|
||||||
self._curID = 2
|
|
||||||
self._tiledb = mstr_tiledb(lat, lng)
|
|
||||||
|
|
||||||
|
|
||||||
# Creates the XML file for WED in one go
|
|
||||||
def create_WED_XML(self):
|
|
||||||
|
|
||||||
# Generates a very basic skeleton for the World Editor
|
|
||||||
root = xml.dom.minidom.Document()
|
|
||||||
xmlobj = root.createElement("doc")
|
|
||||||
root.appendChild(xmlobj)
|
|
||||||
|
|
||||||
objs = root.createElement("objects")
|
|
||||||
xmlobj.appendChild(objs)
|
|
||||||
|
|
||||||
rtobj = root.createElement("object")
|
|
||||||
rtobj.setAttribute("class", "WED_Root")
|
|
||||||
rtobj.setAttribute("id", "1")
|
|
||||||
rtobj.setAttribute("parent_id", "0")
|
|
||||||
|
|
||||||
chdobj = root.createElement("children")
|
|
||||||
rtobj.appendChild(chdobj)
|
|
||||||
|
|
||||||
hiera = root.createElement("hierarchy")
|
|
||||||
hiera.setAttribute("name", "root")
|
|
||||||
rtobj.appendChild(hiera)
|
|
||||||
|
|
||||||
# We now open the DB and check for everything that needs to be added,
|
|
||||||
# predominantely forests
|
|
||||||
|
|
||||||
|
|
||||||
# I believe this needs to be in
|
|
||||||
prefs = root.createElement("prefs")
|
|
||||||
xmlobj.appendChild(prefs)
|
|
||||||
|
|
||||||
file_handle = open(mstr_datafolder + "Tiles/" + str(self._lat) + "_" + str(self._lng) + "/earth.wed.xml","w")
|
|
||||||
root.writexml(file_handle)
|
|
||||||
file_handle.close()
|
|
164
xp_dsfgen.py
@ -11,11 +11,9 @@
|
|||||||
# generation process, and builds the DSF (Distributable Scenery
|
# generation process, and builds the DSF (Distributable Scenery
|
||||||
# Format) file for X-Plane.
|
# Format) file for X-Plane.
|
||||||
#
|
#
|
||||||
# For this, you will need two tools which I cannot re-distribute:
|
# For this, you will need DSFTool which I cannot re-distribute.
|
||||||
# - DSFTool
|
|
||||||
# - DDSTool
|
|
||||||
#
|
#
|
||||||
# You can download both of these for free from X-Plane's website.
|
# You can download it for free from X-Plane's website.
|
||||||
# Place them somewhere convenient, and point to them in the
|
# Place them somewhere convenient, and point to them in the
|
||||||
# xp_ variables in defines.py
|
# xp_ variables in defines.py
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
@ -25,18 +23,18 @@ import glob
|
|||||||
import math
|
import math
|
||||||
from random import randrange
|
from random import randrange
|
||||||
from log import *
|
from log import *
|
||||||
from tiledb import *
|
|
||||||
|
|
||||||
class mstr_xp_dsfgen:
|
class mstr_xp_dsfgen:
|
||||||
# Instantiate with Lat/Lng, as usual
|
# Instantiate with Lat/Lng, as usual
|
||||||
def __init__(self, lat, lng, amtdg):
|
def __init__(self, lat, lng, mlat, mlng, vstep):
|
||||||
self._latitude = lat
|
self._latitude = lat
|
||||||
self._longitude = lng
|
self._longitude = lng
|
||||||
self._tiledb = mstr_tiledb(lat, lng)
|
self._maxlat = mlat
|
||||||
|
self._maxlng = mlng
|
||||||
self._tmpdsf = mstr_datafolder + "_cache/tiledsf.txt"
|
self._tmpdsf = mstr_datafolder + "_cache/tiledsf.txt"
|
||||||
|
self._vstep = vstep
|
||||||
self._dsfstring = ""
|
self._dsfstring = ""
|
||||||
self._amtdg = amtdg
|
mstr_msg("xp_dsfgen", "[X-Plane] DSFgen initialized")
|
||||||
mstr_msg("xp_dsfgen", "DSFgen initialized")
|
|
||||||
self.build_header()
|
self.build_header()
|
||||||
|
|
||||||
|
|
||||||
@ -48,42 +46,21 @@ class mstr_xp_dsfgen:
|
|||||||
self._dsfstring = self._dsfstring + "PROPERTY sim/north " + str(int(self._latitude+1)) + "\n"
|
self._dsfstring = self._dsfstring + "PROPERTY sim/north " + str(int(self._latitude+1)) + "\n"
|
||||||
self._dsfstring = self._dsfstring + "PROPERTY sim/planet earth\n"
|
self._dsfstring = self._dsfstring + "PROPERTY sim/planet earth\n"
|
||||||
self._dsfstring = self._dsfstring + "PROPERTY sim/creation_agent Orthographic\n"
|
self._dsfstring = self._dsfstring + "PROPERTY sim/creation_agent Orthographic\n"
|
||||||
mstr_msg("xp_dsfgen", "Header built")
|
self._dsfstring = self._dsfstring + "PROPERTY sim/overlay 1\n" # <- DO NOT REMOVE THIS!!
|
||||||
|
self._dsfstring = self._dsfstring + "PROPERTY sim/require_facade 6/0\n"
|
||||||
|
mstr_msg("xp_dsfgen", "[X-Plane] DSF header built")
|
||||||
# Let's now walk through the single polygon definitions
|
|
||||||
def build_polygon_defs(self):
|
|
||||||
mstr_msg("xp_dsfgen", "Walking through forest polygons")
|
|
||||||
|
|
||||||
# Pick the kind of forest we work with
|
|
||||||
for f in range(1, self._amtdg+1):
|
|
||||||
#rws = self._tiledb.perform_query("SELECT * FROM xp_scenery WHERE datagroup="+str(f)+";")
|
|
||||||
frs = self.pick_forest_type()
|
|
||||||
self._dsfstring = self._dsfstring + "POLYGON_DEF lib/g8/"+frs+"\n"
|
|
||||||
|
|
||||||
# Put in the data
|
|
||||||
curpoly=0
|
|
||||||
for f in range(1, self._amtdg+1):
|
|
||||||
rws = self._tiledb.perform_query("SELECT * FROM xpscenery WHERE datagroup="+str(f)+";")
|
|
||||||
self._dsfstring = self._dsfstring + "BEGIN_POLYGON "+str(curpoly)+" 255 2\n"
|
|
||||||
self._dsfstring = self._dsfstring + "BEGIN_WINDING\n"
|
|
||||||
for r in rws:
|
|
||||||
self._dsfstring = self._dsfstring + "POLYGON_POINT " + str(r[2]) + " " + str(r[1]) + "\n"
|
|
||||||
self._dsfstring = self._dsfstring + "END_WINDING\n"
|
|
||||||
self._dsfstring = self._dsfstring + "END_POLYGON\n"
|
|
||||||
curpoly = curpoly + 1
|
|
||||||
|
|
||||||
mstr_msg("xp_dsfgen", "Forest definitions complete")
|
|
||||||
|
|
||||||
|
|
||||||
# Write the text file
|
# Write the text file
|
||||||
def write_dsf_txt(self):
|
def write_dsf_txt(self):
|
||||||
with open(mstr_datafolder + "_cache/dsf.txt", 'w') as textfile:
|
mstr_msg("xp_dsfgen", "[X-Plane] Writing DSF txt file")
|
||||||
|
with open(self._tmpdsf, 'w') as textfile:
|
||||||
textfile.write(self._dsfstring)
|
textfile.write(self._dsfstring)
|
||||||
|
|
||||||
|
|
||||||
# Convert the DSF into actual, usable data for X-Plane
|
# Convert the DSF into actual, usable data for X-Plane
|
||||||
def convert_dsf_text(self):
|
def convert_dsf_text(self):
|
||||||
|
mstr_msg("xp_dsfgen", "[X-Plane] Converting DSF information into X-Plane DSF file")
|
||||||
# Find separator
|
# Find separator
|
||||||
sep = ""
|
sep = ""
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
@ -94,7 +71,7 @@ class mstr_xp_dsfgen:
|
|||||||
datafolder = mstr_datafolder.replace("/", sep)
|
datafolder = mstr_datafolder.replace("/", sep)
|
||||||
|
|
||||||
# First, create the Earth nav data folder should it not exist
|
# First, create the Earth nav data folder should it not exist
|
||||||
end_base = datafolder + "Tiles" + sep + str(self._latitude) + "_" + str(self._longitude) + sep + "Earth nav data"
|
end_base = datafolder + "Tiles/z_orthographic_" + self.xplane_latlng_folder([self._latitude, self._longitude]) + sep + "Earth nav data"
|
||||||
|
|
||||||
# Create the appropriate rounded folder
|
# Create the appropriate rounded folder
|
||||||
end_round = self.xplane_latlng_folder(self.find_earthnavdata_number())
|
end_round = self.xplane_latlng_folder(self.find_earthnavdata_number())
|
||||||
@ -107,7 +84,8 @@ class mstr_xp_dsfgen:
|
|||||||
end_latlng = self.xplane_latlng_folder([self._latitude, self._longitude])
|
end_latlng = self.xplane_latlng_folder([self._latitude, self._longitude])
|
||||||
|
|
||||||
# Perform conversion
|
# Perform conversion
|
||||||
os.system(mstr_xp_dsftool + " --text2dsf " + datafolder + "_cache" + sep + "dsf.txt \"" + end_base + sep + end_round + sep + end_latlng + ".dsf\"")
|
os.system(mstr_xp_dsftool + " --text2dsf " + datafolder + "_cache" + sep + "tiledsf.txt \"" + end_base + sep + end_round + sep + end_latlng + ".dsf\"")
|
||||||
|
mstr_msg("xp_dsfgen", "[X-Plane] DSF conversion complete")
|
||||||
|
|
||||||
|
|
||||||
# Find the next "by-ten" numbers for the current latitude and longitude
|
# Find the next "by-ten" numbers for the current latitude and longitude
|
||||||
@ -119,6 +97,17 @@ class mstr_xp_dsfgen:
|
|||||||
earthnavdata.append(lng)
|
earthnavdata.append(lng)
|
||||||
return earthnavdata
|
return earthnavdata
|
||||||
|
|
||||||
|
# Find with of a longitude (needed for DSF and .pol files)
|
||||||
|
def _findWidthOfLongitude(self, lat):
|
||||||
|
dm = math.cos(math.radians(lat)) * 111.321 # <- 1 deg width at equator in km
|
||||||
|
return round(dm * 1000, 3)
|
||||||
|
|
||||||
|
# Find diameter of an ortho
|
||||||
|
def find_ortho_diameter(self, side):
|
||||||
|
sq = side * side
|
||||||
|
sqsum = sq + sq
|
||||||
|
dm = math.sqrt(sqsum)
|
||||||
|
|
||||||
|
|
||||||
# Construct an X-Plane compatible folder name for latitude and longitude
|
# Construct an X-Plane compatible folder name for latitude and longitude
|
||||||
def xplane_latlng_folder(self, numbers):
|
def xplane_latlng_folder(self, numbers):
|
||||||
@ -137,22 +126,91 @@ class mstr_xp_dsfgen:
|
|||||||
return fstr
|
return fstr
|
||||||
|
|
||||||
|
|
||||||
# Pick some forest type from X-Plane
|
# Build the complete DSF.
|
||||||
def pick_forest_type(self):
|
# This is the main function to call.
|
||||||
ftype = 0
|
def build_dsf_for_tile(self):
|
||||||
|
mstr_msg("xp_dsfgen", "[X-Plane] Building DSF file")
|
||||||
|
# Add the polygon definition file entries
|
||||||
|
for v in range(1, self._maxlat+1):
|
||||||
|
for h in range(1, self._maxlng+1):
|
||||||
|
self._dsfstring = self._dsfstring + "POLYGON_DEF terrain/"+str(v)+"_"+str(h)+".pol\n"
|
||||||
|
|
||||||
# Where forests live in X-Plane.
|
# Add the definitions for each ortho tile
|
||||||
rootfolder = mstr_xp_folder + "Resources/default scenery/1000 forests/"
|
curpol = 0
|
||||||
forests = glob.glob(rootfolder + "mixed_*.for")
|
cur_lat = self._latitude
|
||||||
ftype = randrange(1, len(forests)-1)
|
cur_lng = self._longitude
|
||||||
fstring = forests[ftype]
|
for v in range(1, self._maxlat+1):
|
||||||
fstring = fstring.replace(mstr_xp_folder + "Resources/default scenery/1000 forests\\", "")
|
for h in range(1, self._maxlng+1):
|
||||||
|
bbox = [ cur_lat, cur_lng, cur_lat+self._vstep-0.00001, cur_lng + mstr_zl_18-0.00001 ]
|
||||||
|
self._dsfstring = self._dsfstring + "BEGIN_POLYGON "+str(curpol)+" 65535 4\n"
|
||||||
|
self._dsfstring = self._dsfstring + "BEGIN_WINDING\n"
|
||||||
|
|
||||||
return fstring
|
self._dsfstring = self._dsfstring + "POLYGON_POINT " + str(bbox[1]) + " " + str(bbox[0]) + " 0.000000000 0.000000000\n"
|
||||||
|
self._dsfstring = self._dsfstring + "POLYGON_POINT " + str(bbox[3]) + " " + str(bbox[0]) + " 1.000000000 0.000000000\n"
|
||||||
|
self._dsfstring = self._dsfstring + "POLYGON_POINT " + str(bbox[3]) + " " + str(bbox[2]) + " 1.000000000 1.000000000\n"
|
||||||
|
self._dsfstring = self._dsfstring + "POLYGON_POINT " + str(bbox[1]) + " " + str(bbox[2]) + " 0.000000000 1.000000000\n"
|
||||||
|
|
||||||
|
self._dsfstring = self._dsfstring + "END_WINDING\n"
|
||||||
|
self._dsfstring = self._dsfstring + "END_POLYGON\n"
|
||||||
|
|
||||||
|
# Adjust forward
|
||||||
|
cur_lng = cur_lng + mstr_zl_18
|
||||||
|
curpol = curpol + 1
|
||||||
|
|
||||||
|
# Adjust up, reset longitude
|
||||||
|
cur_lng = self._longitude
|
||||||
|
cur_lat = cur_lat + self._vstep
|
||||||
|
|
||||||
|
# OK... we can now save this
|
||||||
|
self.write_dsf_txt()
|
||||||
|
|
||||||
|
# Generate the single .pol files now
|
||||||
|
mstr_msg("xp_dsfgen", "[X-Plane] Beginning generation of terrain/*.pol files")
|
||||||
|
cur_lat = self._latitude
|
||||||
|
cur_lng = self._longitude
|
||||||
|
lclat = cur_lat + (self._vstep/2)
|
||||||
|
lclng = cur_lng + (mstr_zl_18/2)
|
||||||
|
for v in range(1, self._maxlat+1):
|
||||||
|
for h in range(1, self._maxlng+1):
|
||||||
|
dm = self._findWidthOfLongitude(cur_lng) * mstr_zl_18
|
||||||
|
dg = self.find_ortho_diameter(dm)
|
||||||
|
polstr = ""
|
||||||
|
polstr = polstr + "A\n"
|
||||||
|
polstr = polstr + "850\n"
|
||||||
|
polstr = polstr + "DRAPED_POLYGON\n"
|
||||||
|
polstr = polstr + "\n"
|
||||||
|
polstr = polstr + "TEXTURE_NOWRAP ../orthos/"+str(v)+"_"+str(h)+".dds\n"
|
||||||
|
|
||||||
|
# Check for existence of a normal map
|
||||||
|
# If there is one, we will add that too
|
||||||
|
end_latlng = self.xplane_latlng_folder([self._latitude, self._longitude])
|
||||||
|
if os.path.isfile(mstr_datafolder + "Tiles/z_orthographic_"+end_latlng+"/normals/" + str(v) + "_" + str(h) + ".png") == True:
|
||||||
|
polstr = polstr + "TEXTURE_NORMAL 1 ../normals/"+str(v)+"_"+str(h)+".png\n"
|
||||||
|
|
||||||
|
polstr = polstr + "SCALE "+str(dm)+" "+str(dm)+"\n"
|
||||||
|
polstr = polstr + "LOAD_CENTER "+str(lclat)+" " +str(lclng)+" " +str(dg)+ " " +str(mstr_photores)+"\n"
|
||||||
|
polstr = polstr + "LAYER_GROUP TERRAIN 1\n"
|
||||||
|
|
||||||
|
# Save this content
|
||||||
|
with open(mstr_datafolder + "Tiles/z_orthographic_"+end_latlng+"/terrain/"+str(v)+"_"+str(h)+".pol", 'w') as textfile:
|
||||||
|
textfile.write(polstr)
|
||||||
|
|
||||||
|
# Adjust forward
|
||||||
|
cur_lng = cur_lng + mstr_zl_18
|
||||||
|
lclng = cur_lng + (mstr_zl_18/2)
|
||||||
|
|
||||||
|
cur_lng = self._longitude
|
||||||
|
lclng = cur_lng + (mstr_zl_18/2)
|
||||||
|
|
||||||
|
cur_lat = cur_lat + self._vstep
|
||||||
|
lclat = cur_lat + (self._vstep/2)
|
||||||
|
|
||||||
|
mstr_msg("xp_dsfgen", "[X-Plane] Converting DSF txt")
|
||||||
|
self.convert_dsf_text()
|
||||||
|
|
||||||
|
mstr_msg("xp_dsfgen", "[X-Plane] DSF for tile completed")
|
||||||
|
|
||||||
|
|
||||||
|
# Testing
|
||||||
dsf = mstr_xp_dsfgen(51, 7, 1)
|
dsf = mstr_xp_dsfgen(51, 7, 101, 63, 0.01010)
|
||||||
dsf.build_polygon_defs()
|
dsf.build_dsf_for_tile()
|
||||||
dsf.write_dsf_txt()
|
|
||||||
dsf.convert_dsf_text()
|
|
163
xp_normalmap.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
# ORTHOGRAPHIC
|
||||||
|
# Your personal aerial satellite. Always on. At any altitude.*
|
||||||
|
# Developed by MarStrMind
|
||||||
|
# License: Open Software License 3.0
|
||||||
|
# Up to date version always on marstr.online
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
# xp_normalmap.py
|
||||||
|
# For some geographical features, we will add a normal map for
|
||||||
|
# additional realism in the orthos, without using additional meshes
|
||||||
|
# or geometry.
|
||||||
|
#
|
||||||
|
# Source converted to Python from this C++ StackOverflow:
|
||||||
|
# https://stackoverflow.com/a/2368794
|
||||||
|
# -------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
from PIL import Image, ImageFilter
|
||||||
|
from defines import *
|
||||||
|
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
|
||||||
|
mstr_msg("xp_normalmap", "[X-Plane] Normal Map generator initialized")
|
||||||
|
|
||||||
|
|
||||||
|
# Load the layer image and resize it to 1/4th its size -
|
||||||
|
# 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.resize((qtr,qtr), Image.Resampling.LANCZOS)
|
||||||
|
mstr_msg("xp_normalmap", "[X-Plane] Layer image loaded")
|
||||||
|
return image
|
||||||
|
|
||||||
|
|
||||||
|
# A few mathematical calls we need
|
||||||
|
# --------------------------------------------------------
|
||||||
|
def intensity(pixel):
|
||||||
|
avg = (pixel[0] + pixel[1] + pixel[2]) / 3
|
||||||
|
pavg = 255.0 / avg
|
||||||
|
return pavg
|
||||||
|
|
||||||
|
|
||||||
|
def clamp(px, mpx):
|
||||||
|
if px > mpx-1:
|
||||||
|
return mpx-1
|
||||||
|
else:
|
||||||
|
if px < 0:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return px
|
||||||
|
|
||||||
|
|
||||||
|
def map_component(px):
|
||||||
|
return (px + 1.0) * (255.0 / 2.0)
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_vector(v):
|
||||||
|
vc = np.array([v[0], v[1], v[2]])
|
||||||
|
norm = np.linalg.norm(vc)
|
||||||
|
nv = vc / norm
|
||||||
|
return nv
|
||||||
|
# --------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# 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))
|
||||||
|
org = image.load()
|
||||||
|
nmp_pix = nmp.load()
|
||||||
|
|
||||||
|
# Find out which alpha value to use
|
||||||
|
alpha = 0
|
||||||
|
for n in mstr_xp_normal_maps:
|
||||||
|
if n[0] == self._tag and n[1] == self._value:
|
||||||
|
v = n[2] / 100
|
||||||
|
a = v * 255.0
|
||||||
|
alpha = int(a)
|
||||||
|
|
||||||
|
# Let's try some shenanigans
|
||||||
|
for y in range(image.width):
|
||||||
|
for x in range(image.height):
|
||||||
|
# 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) ]
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# Invert height for our Orthos
|
||||||
|
if nrm[1] > 0:
|
||||||
|
nrm[1] = 0 - (abs(nrm[1]))
|
||||||
|
else:
|
||||||
|
nrm[1] = abs(nrm[1])
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
mstr_msg("xp_normalmap", "[X-Plane] Normal map generated")
|
||||||
|
return nmp
|
||||||
|
|
||||||
|
|
||||||
|
# The funnction to call. Blends with the existing map, or creates a new one
|
||||||
|
def build_normalmap(self):
|
||||||
|
mstr_msg("xp_normalmap", "[X-Plane] Building normal map")
|
||||||
|
# The layer image
|
||||||
|
lyr = self.load_layer()
|
||||||
|
|
||||||
|
# Make the normal map for the layer
|
||||||
|
nrm = self.generate_normal_map_for_layer(lyr)
|
||||||
|
|
||||||
|
# Normal map final file name
|
||||||
|
nrmfln = mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/normals/" + 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)
|
||||||
|
nrmmap.save(nrmfln)
|
||||||
|
mstr_msg("xp_normalmap", "[X-Plane] Normal map saved")
|