Possible initial public release. After extensive testing, code amended to incorporate Laminar Research tools to build usable meshes for X-Plane, along with the correct files needed for the scenery. Normal maps also generating correctly. New class xp_scenery which performs generation of the mesh. Structure of files unified so that multiple tiles can be held in one single folder. Dependency for wand removed.
This commit is contained in:
parent
49ee89ebc6
commit
747dfbdf91
42
defines.py
42
defines.py
@ -72,27 +72,31 @@ mstr_shadow_casters = [
|
||||
# Whether or not to generate X-Plane Scenery files
|
||||
mstr_xp_genscenery = True
|
||||
|
||||
# X-Plane specific
|
||||
mstr_xp_dsftool = "M:/Developer/Projects/orthographic/bin/DSFTool.exe"
|
||||
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
|
||||
# Generate normal maps for X-Plane scenery?
|
||||
# Strong recommendation: yes
|
||||
mstr_xp_scn_normalmaps = 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.
|
||||
# Paths to required X-Plane scenery tools
|
||||
mstr_xp_meshtool = "/home/marcus/Developer/Projects/orthographic/bin/MeshTool"
|
||||
mstr_xp_ddstool = "/home/marcus/Developer/Projects/orthographic/bin/DDSTool"
|
||||
mstr_xp_xessrc = "https://dev.x-plane.com/update/misc/MeshTool/"
|
||||
|
||||
# If you set the above to true, you can define for which features you
|
||||
# want to generate normal maps for. The below is my recommendation for
|
||||
# good-looking orthos in the simulator.
|
||||
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)
|
||||
("landuse", "farmland"),
|
||||
("landuse", "meadow"),
|
||||
("landuse", "orchard"),
|
||||
("landuse", "forest"),
|
||||
("natural", "wetland"),
|
||||
("natural", "bare_rock"),
|
||||
("natural", "scrub"),
|
||||
("natural", "heath"),
|
||||
("natural", "sand"),
|
||||
("natural", "desert"),
|
||||
("leisure", "nature_reserve"),
|
||||
("building", "*")
|
||||
]
|
||||
|
||||
# How much of a tile we need for each zoom level. The higher
|
||||
|
29
layergen.py
29
layergen.py
@ -430,10 +430,10 @@ 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_generate_normal_maps == True:
|
||||
if mstr_xp_scn_normalmaps == True:
|
||||
nm = False
|
||||
for n in mstr_xp_normal_maps:
|
||||
if n[0] == self._tag and n[1] == self._value:
|
||||
if n[0] == self._tag and (n[1] == self._value or n[1] == "*"):
|
||||
nm = True
|
||||
break
|
||||
if nm == True:
|
||||
@ -534,6 +534,25 @@ class mstr_layergen:
|
||||
self._tiledb.commit_query()
|
||||
self._tiledb.close_db()
|
||||
|
||||
# Create a water mask we need to remove from the DDS later
|
||||
if (self._tag == "natural" and self._value == "water") or (self._tag == "water" and self._value == "lake") or (self._tag == "water" and self._value == "pond") or (self._tag == "water" and self._value == "river"):
|
||||
mstr_msg("layergen", "Generating inland water mask")
|
||||
inl_mask = Image.new("RGBA", (self._imgsize, self._imgsize), (255,255,255,255))
|
||||
lyr_pix = layer_comp.load()
|
||||
inl_pix = inl_mask.load()
|
||||
for y in range(self._imgsize):
|
||||
for x in range(self._imgsize):
|
||||
l = lyr_pix[x,y]
|
||||
if l[3] > 0:
|
||||
b = 255 - l[3]
|
||||
inl_pix[x,y] = (b,b,b,255)
|
||||
inl_mask.save(mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_layer_mask.png")
|
||||
mstr_msg("layergen", "Inland water mask generated and saved")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------------------
|
||||
# ---------------------------------------------------------------------------------------
|
||||
# ---------------------------------------------------------------------------------------
|
||||
|
||||
# If we encounter one of these road-specific tags, we need to proceed differently.
|
||||
|
||||
@ -651,7 +670,7 @@ class mstr_layergen:
|
||||
layer_comp_pix[x, y] = ( r,g,b,a[3] )
|
||||
|
||||
# We will do some super magic here to let houses look more realistic
|
||||
if self._tag == "building":
|
||||
if self._tag == "building" or self._value == "cemetery":
|
||||
vls = [ "detached", "hotel", "farm", "semidetached_house", "apartments", "civic", "office", "retail", "industrial", "house", "school", "yes" ]
|
||||
if self._value in vls:
|
||||
# Generate a new image
|
||||
@ -769,10 +788,10 @@ 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_generate_normal_maps == True:
|
||||
if mstr_xp_scn_normalmaps == True:
|
||||
nm = False
|
||||
for n in mstr_xp_normal_maps:
|
||||
if n[0] == self._tag and n[1] == self._value:
|
||||
if n[0] == self._tag and (n[1] == self._value or n[1] == "*"):
|
||||
nm = True
|
||||
break
|
||||
if nm == True:
|
||||
|
13
og.py
13
og.py
@ -1,10 +1,4 @@
|
||||
|
||||
import sys
|
||||
import os
|
||||
from orthographic import *
|
||||
from log import *
|
||||
from defines import *
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# ORTHOGRAPHIC
|
||||
# Your personal aerial satellite. Always on. At any altitude.*
|
||||
@ -17,6 +11,13 @@ from defines import *
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
|
||||
import sys
|
||||
import os
|
||||
from orthographic import *
|
||||
from log import *
|
||||
from defines import *
|
||||
|
||||
|
||||
# Print a welcome message
|
||||
print(" ")
|
||||
print(" ---------------------------------------------------------------- ")
|
||||
|
@ -15,12 +15,11 @@ import os
|
||||
import glob
|
||||
from defines import *
|
||||
from log import *
|
||||
from osmxml import *
|
||||
from maskgen import *
|
||||
from layergen import *
|
||||
from photogen import *
|
||||
from osmxml import *
|
||||
from tilegen import *
|
||||
from xp_dsfgen import *
|
||||
from xp_scenery import *
|
||||
|
||||
|
||||
# The main class which handles the rest
|
||||
@ -91,30 +90,45 @@ class mstr_orthographic:
|
||||
os.makedirs(self._output + "/_cache")
|
||||
mstr_msg("orthographic", "Created _cache folder.")
|
||||
|
||||
# Generate the Tiles folder for the finished products
|
||||
if not os.path.exists(self._output + "/Tiles"):
|
||||
os.makedirs(self._output + "/Tiles")
|
||||
mstr_msg("orthographic", "Created Tiles folder.")
|
||||
|
||||
# Generate the Tiles/lat-lng folder for the finished tile
|
||||
if not os.path.exists(self._output + "/Tiles/z_orthographic_" + self._latlngfld):
|
||||
os.makedirs(self._output + "/Tiles/z_orthographic_"+ self._latlngfld)
|
||||
mstr_msg("orthographic", "Created Tiles sub folder: " + self._latlngfld)
|
||||
if not os.path.exists(self._output + "/z_orthographic"):
|
||||
os.makedirs(self._output + "/z_orthographic")
|
||||
mstr_msg("orthographic", "Created z_orthographic folder")
|
||||
|
||||
# Generate the orthos folder
|
||||
if not os.path.exists(self._output + "/Tiles/z_orthographic_" + self._latlngfld + "/orthos"):
|
||||
os.makedirs(self._output + "/Tiles/z_orthographic_" + self._latlngfld +"/orthos")
|
||||
if not os.path.exists(self._output + "/z_orthographic/orthos"):
|
||||
os.makedirs(self._output + "/z_orthographic/orthos")
|
||||
mstr_msg("orthographic", "Created tile orthos folder")
|
||||
if not os.path.exists(self._output + "/z_orthographic/orthos" + self._latlngfld):
|
||||
os.makedirs(self._output + "/z_orthographic/orthos/" + self._latlngfld)
|
||||
|
||||
# Generate the database folder
|
||||
if not os.path.exists(self._output + "/z_orthographic/data"):
|
||||
os.makedirs(self._output + "/z_orthographic/data")
|
||||
mstr_msg("orthographic", "Created tile database folder")
|
||||
if not os.path.exists(self._output + "/z_orthographic/data/" + self._latlngfld):
|
||||
os.makedirs(self._output + "/z_orthographic/data/" + self._latlngfld)
|
||||
|
||||
# X-Plane specific
|
||||
if mstr_xp_genscenery == True:
|
||||
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_orthographic_" + self._latlngfld + "/normals"):
|
||||
os.makedirs(self._output + "/Tiles/z_orthographic_" + self._latlngfld + "/normals")
|
||||
mstr_msg("orthographic", "Created X-Plane tile normals folder")
|
||||
btnum = self.find_earthnavdata_number()
|
||||
btstr = self.latlng_folder(btnum)
|
||||
if not os.path.exists(self._output + "/z_orthographic/terrain"):
|
||||
os.makedirs(self._output + "/z_orthographic/terrain")
|
||||
mstr_msg("orthographic", "[X-Plane] Created terrain files folder")
|
||||
if not os.path.exists(self._output + "/z_orthographic/terrain/" + self._latlngfld):
|
||||
os.makedirs(self._output + "/z_orthographic/terrain/" + self._latlngfld)
|
||||
if not os.path.exists(self._output + "/z_orthographic/Earth nav data"):
|
||||
os.makedirs(self._output + "/z_orthographic/Earth nav data")
|
||||
mstr_msg("orthographic", "[X-Plane] Created Earth nav folder")
|
||||
if not os.path.exists(self._output + "/z_orthographic/Earth nav data/" + btstr):
|
||||
os.makedirs(self._output + "/z_orthographic/Earth nav data/" + btstr)
|
||||
if mstr_xp_scn_normalmaps == True:
|
||||
if not os.path.exists(self._output + "/z_orthographic/normals"):
|
||||
os.makedirs(self._output + "/z_orthographic/normals")
|
||||
mstr_msg("orthographic", "[X-Plane] created tile normal maps folder")
|
||||
if not os.path.exists(self._output + "/z_orthographic/normals/" + self._latlngfld):
|
||||
os.makedirs(self._output + "/z_orthographic/normals/" + self._latlngfld)
|
||||
|
||||
# The tile is constructed of many smaller parts. We walk through the
|
||||
# smallest possible, from which the bigger ones are later built.
|
||||
@ -162,7 +176,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/z_orthographic_" + self._latlngfld + "/orthos/" + str(cur_tile_y) + "_" + str(cur_tile_x) + ".dds") == False:
|
||||
if os.path.isfile(mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + 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))
|
||||
@ -248,12 +262,15 @@ class mstr_orthographic:
|
||||
|
||||
# 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")
|
||||
scn = mstr_xp_scenery(self._lat, self._long, mlat, mlng, self._vstep, self._latlngfld)
|
||||
scn.acquire_elevation_data()
|
||||
scn.acquire_xes_data()
|
||||
scn.build_mesh_script()
|
||||
scn.build_mesh()
|
||||
mstr_msg("orthographic", "[X-Plane] Mesh built, and scenery completed")
|
||||
|
||||
mstr_msg("orthographic", "Final step completed.")
|
||||
mstr_msg("orthographic", "Tile data in: " + self._output + "/Tiles/z_orthographic_" + self._latlngfld)
|
||||
mstr_msg("orthographic", "Tile data in: " + self._output + "z_orthographic/" + self._latlngfld)
|
||||
print("")
|
||||
mstr_msg("orthographic", "Thanks for using Orthographic! -- Best, Marcus")
|
||||
print("")
|
||||
@ -335,3 +352,12 @@ class mstr_orthographic:
|
||||
|
||||
return fstr
|
||||
|
||||
|
||||
# Find the next "by-ten" numbers for the current latitude and longitude
|
||||
def find_earthnavdata_number(self):
|
||||
earthnavdata = []
|
||||
lat = abs(int(self._lat / 10) * 10)
|
||||
lng = abs(int(self._long / 10) * 10)
|
||||
earthnavdata.append(lat)
|
||||
earthnavdata.append(lng)
|
||||
return earthnavdata
|
32
photogen.py
32
photogen.py
@ -4,7 +4,6 @@ from PIL import Image, ImageFilter
|
||||
from defines import *
|
||||
from layergen import *
|
||||
from log import *
|
||||
from wand import image
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# ORTHOGRAPHIC
|
||||
@ -112,21 +111,38 @@ class mstr_photogen:
|
||||
t = (0,0,0,0)
|
||||
ocean_pix[x,y] = t
|
||||
|
||||
# Now cut out inland water
|
||||
water_layers = (
|
||||
["natural", "water"],
|
||||
["water", "lake"],
|
||||
["water", "pond"],
|
||||
["water", "river"]
|
||||
)
|
||||
for l in water_layers:
|
||||
fn = mstr_datafolder + "_cache/" + str(self._lat) + "-" + str(self._ty) + "_" + str(self._lng) + "-" + str(self._tx) + "_" + l[0] + "-" + l[1] + "_layer_mask.png"
|
||||
if os.path.isfile(fn) == True:
|
||||
wtr = Image.open(fn)
|
||||
wtr_pix = wtr.load()
|
||||
tilepix = self._tile.load()
|
||||
for y in range(wtr.height):
|
||||
for x in range(wtr.width):
|
||||
wp = wtr_pix[x,y]
|
||||
if wp[0] == 0:
|
||||
tilepix[x,y] = (0,0,0,0)
|
||||
|
||||
# We are now in posession of the final image.
|
||||
|
||||
# Scale to correct size.
|
||||
self._tile = self._tile.resize((mstr_photores, mstr_photores), Image.Resampling.BILINEAR)
|
||||
|
||||
# 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.save(mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + 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(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")
|
||||
_tmpfn = mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._ty) + "_" + str(self._tx)
|
||||
os.system(mstr_xp_ddstool + " --png2dxt1 " + _tmpfn + ".png " + _tmpfn + ".dds" )
|
||||
|
||||
os.remove(mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._ty) + "_" + str(self._tx) + ".png")
|
||||
|
||||
|
||||
|
||||
|
@ -25,7 +25,7 @@ class mstr_tiledb:
|
||||
self._latlngfld = latlngfld
|
||||
|
||||
# The db file will be created, should it not exist
|
||||
self._conn = sqlite3.connect(mstr_datafolder + "Tiles/z_orthographic_" + latlngfld + "/data.db")
|
||||
self._conn = sqlite3.connect(mstr_datafolder + "z_orthographic/data/" + latlngfld + "/data.db")
|
||||
self._crs = self._conn.cursor()
|
||||
#mstr_msg("tiledb", "Database object initiated")
|
||||
|
||||
|
211
xp_dsfgen.py
211
xp_dsfgen.py
@ -1,211 +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
|
||||
# -------------------------------------------------------------------
|
||||
# xp_dsfgen.py
|
||||
# This class is coming into play at the very end of the tile
|
||||
# generation process, and builds the DSF (Distributable Scenery
|
||||
# Format) file for X-Plane.
|
||||
#
|
||||
# For this, you will need DSFTool which I cannot re-distribute.
|
||||
#
|
||||
# You can download it for free from X-Plane's website.
|
||||
# Place them somewhere convenient, and point to them in the
|
||||
# xp_ variables in defines.py
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import glob
|
||||
import math
|
||||
from random import randrange
|
||||
from log import *
|
||||
|
||||
class mstr_xp_dsfgen:
|
||||
# Instantiate with Lat/Lng, as usual
|
||||
def __init__(self, lat, lng, mlat, mlng, vstep):
|
||||
self._latitude = lat
|
||||
self._longitude = lng
|
||||
self._maxlat = mlat
|
||||
self._maxlng = mlng
|
||||
self._tmpdsf = mstr_datafolder + "_cache/tiledsf.txt"
|
||||
self._vstep = vstep
|
||||
self._dsfstring = ""
|
||||
mstr_msg("xp_dsfgen", "[X-Plane] DSFgen initialized")
|
||||
self.build_header()
|
||||
|
||||
|
||||
# Construct header of DSF txt
|
||||
def build_header(self):
|
||||
self._dsfstring = self._dsfstring + "PROPERTY sim/west " + str(int(self._longitude)) + "\n"
|
||||
self._dsfstring = self._dsfstring + "PROPERTY sim/east " + str(int(self._longitude + 1)) + "\n"
|
||||
self._dsfstring = self._dsfstring + "PROPERTY sim/south " + str(int(self._latitude)) + "\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/creation_agent Orthographic\n"
|
||||
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")
|
||||
|
||||
|
||||
# Write the text file
|
||||
def write_dsf_txt(self):
|
||||
mstr_msg("xp_dsfgen", "[X-Plane] Writing DSF txt file")
|
||||
with open(self._tmpdsf, 'w') as textfile:
|
||||
textfile.write(self._dsfstring)
|
||||
|
||||
|
||||
# Convert the DSF into actual, usable data for X-Plane
|
||||
def convert_dsf_text(self):
|
||||
mstr_msg("xp_dsfgen", "[X-Plane] Converting DSF information into X-Plane DSF file")
|
||||
# Find separator
|
||||
sep = ""
|
||||
if os.name == "nt":
|
||||
sep = "\\"
|
||||
if os.name == "posix":
|
||||
sep = "/"
|
||||
|
||||
datafolder = mstr_datafolder.replace("/", sep)
|
||||
|
||||
# First, create the Earth nav data folder should it not exist
|
||||
end_base = datafolder + "Tiles/z_orthographic_" + self.xplane_latlng_folder([self._latitude, self._longitude]) + sep + "Earth nav data"
|
||||
|
||||
# Create the appropriate rounded folder
|
||||
end_round = self.xplane_latlng_folder(self.find_earthnavdata_number())
|
||||
if not os.path.exists(end_base):
|
||||
os.makedirs(end_base)
|
||||
if not os.path.exists(end_base + sep + end_round):
|
||||
os.makedirs(end_base + sep + end_round)
|
||||
|
||||
# Get the file name for the DSF
|
||||
end_latlng = self.xplane_latlng_folder([self._latitude, self._longitude])
|
||||
|
||||
# Perform conversion
|
||||
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
|
||||
def find_earthnavdata_number(self):
|
||||
earthnavdata = []
|
||||
lat = abs(int(self._latitude / 10) * 10)
|
||||
lng = abs(int(self._longitude / 10) * 10)
|
||||
earthnavdata.append(lat)
|
||||
earthnavdata.append(lng)
|
||||
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
|
||||
def xplane_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
|
||||
|
||||
|
||||
# Build the complete DSF.
|
||||
# This is the main function to call.
|
||||
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"
|
||||
|
||||
# Add the definitions for each ortho tile
|
||||
curpol = 0
|
||||
cur_lat = self._latitude
|
||||
cur_lng = self._longitude
|
||||
for v in range(1, self._maxlat+1):
|
||||
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"
|
||||
|
||||
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")
|
@ -84,20 +84,12 @@ 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,0)) # no specularity, but default color
|
||||
#nmp = Image.new("RGBA", (image.width, image.height), (0,0,0,0))
|
||||
# No specularity, no reflectivity - but standard color
|
||||
# Blue (reflectivity) and alpha (specularity) need to be 1 - but can be adjusted as needed
|
||||
nmp = Image.new("RGBA", (image.width, image.height), (128,128,1,1))
|
||||
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
|
||||
w = image.width
|
||||
h = image.height
|
||||
@ -132,16 +124,13 @@ class mstr_xp_normalmap:
|
||||
v = (dx, dy, dz)
|
||||
nrm = self.normalize_vector(v)
|
||||
|
||||
# Invert height for our Orthos - but only in some circumstances
|
||||
# We want water to go "down"
|
||||
if (self._tag != "natural" and self._value != "water") or (self._tag != "water" and self._value != "lake") or (self._tag != "water" and self._value != "pond") or (self._tag != "water" and self._value != "river"):
|
||||
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
|
||||
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])), 1)
|
||||
|
||||
mstr_msg("xp_normalmap", "[X-Plane] Normal map generated")
|
||||
return nmp
|
||||
@ -157,7 +146,7 @@ class mstr_xp_normalmap:
|
||||
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"
|
||||
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)
|
||||
@ -170,5 +159,13 @@ class mstr_xp_normalmap:
|
||||
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)
|
||||
|
||||
mstr_msg("xp_normalmap", "[X-Plane] Normal map saved")
|
||||
|
182
xp_scenery.py
Normal file
182
xp_scenery.py
Normal file
@ -0,0 +1,182 @@
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 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_scenery.py
|
||||
# This class builds an elevation mesh from provided DEM data, and
|
||||
# generates all required files that are needed to provide a usable
|
||||
# scenery package for X-Plane.
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import math
|
||||
import urllib.request
|
||||
from defines import *
|
||||
from log import *
|
||||
|
||||
class mstr_xp_scenery:
|
||||
# Set required variables
|
||||
def __init__(self, lat, lng, mlat, mlng, vstep, latlngfld):
|
||||
mstr_msg("xp_scenery", "[X-Plane] Scenery generator instantiated")
|
||||
self._lat = lat
|
||||
self._lng = lng
|
||||
self._mlat = mlat
|
||||
self._mlng = mlng
|
||||
self._vstep = vstep
|
||||
self._latlngfld = latlngfld
|
||||
self._demfn = self.build_dem_filename()
|
||||
|
||||
|
||||
# Build the correct file name for the elevation model
|
||||
def build_dem_filename(self, xes=False):
|
||||
fn = ""
|
||||
if self._lat > 0:
|
||||
fn = fn + "N"
|
||||
else:
|
||||
fn = fn + "S"
|
||||
|
||||
if abs(self._lat) < 10: fn = fn + "0" + str(self._lat)
|
||||
if abs(self._lat) >= 10 and abs(self._lat) <= 90: fn = fn + str(self._lat)
|
||||
|
||||
if self._lng > 0:
|
||||
fn = fn + "E"
|
||||
else:
|
||||
fn = fn + "W"
|
||||
|
||||
if abs(self._lng) < 10: fn = fn + "00" + str(self._lng)
|
||||
if abs(self._lng) >= 10 and abs(self._lng) <= 99: fn = fn + "0" + str(self._lng)
|
||||
if abs(self._lng) >= 100 : fn = fn + str(self._lng)
|
||||
|
||||
if xes == False:
|
||||
fn = fn + ".hgt"
|
||||
if xes == True:
|
||||
fn = fn + ".xes"
|
||||
|
||||
mstr_msg("xp_scenery", "[X-Plane] DEM file name constructed: " + fn)
|
||||
|
||||
return fn
|
||||
|
||||
# Generate the mesh script for the ortho photos
|
||||
def build_mesh_script(self):
|
||||
scr = mstr_datafolder + "z_orthographic/data/meshscript.txt"
|
||||
# Before we blast all these lines into the file, we need to make sure they do not exist already
|
||||
write_lines = True
|
||||
|
||||
if os.path.isfile(scr) == True:
|
||||
fnlines = []
|
||||
with open(scr) as textfile:
|
||||
fnlines = textfile.readlines()
|
||||
|
||||
for line in fnlines:
|
||||
l = line.split(" ")
|
||||
if l[2] == str(self._lng) and l[3] == str(self._lat):
|
||||
write_lines = False
|
||||
break
|
||||
else:
|
||||
open(scr, 'a').close()
|
||||
|
||||
# If we did not find the initial corner coordinate in the script, we can go ahead
|
||||
if write_lines == True:
|
||||
mstr_msg("xp_scenery", "[X-Plane] Writing mesh script file")
|
||||
# We basically run through all tiles and note down the position of the orthos
|
||||
# as needed by X-Plane.
|
||||
cur_lat = self._lat
|
||||
cur_lng = self._lng
|
||||
for lat in range(1, self._mlat+1):
|
||||
for lng in range(1, self._mlng+1):
|
||||
# The '1' after 'ORTHOPHOTO' defines we want water underneath transparent parts of the DDS texture/ortho.
|
||||
# This ensures that even if the mesh does not include information for there being a water body,
|
||||
# we will get 100% correct representation of the water bodies.
|
||||
scrtxt = "ORTHOPHOTO 1 " + str(cur_lng) + " " + str(cur_lat) + " " + str(round(cur_lng+mstr_zl_18, 6)) + " " + str(cur_lat) + " " + str(round(cur_lng+mstr_zl_18, 6)) + " " + str(round(cur_lat+self._vstep, 6)) + " " + str(cur_lng) + " " + str(round(cur_lat+self._vstep, 6)) + " terrain/" + self._latlngfld + "/" + str(lat) + "_" + str(lng) + ".ter\n"
|
||||
|
||||
with open(scr, 'a') as textfile:
|
||||
textfile.write(scrtxt)
|
||||
|
||||
cur_lng = round(cur_lng + mstr_zl_18, 6)
|
||||
|
||||
cur_lng = self._lng
|
||||
cur_lat = round(cur_lat + self._vstep, 6)
|
||||
mstr_msg("xp_scenery", "[X-Plane] Mesh script completed")
|
||||
|
||||
|
||||
# Find the next "by-ten" numbers for the current latitude and longitude
|
||||
def find_earthnavdata_number(self):
|
||||
earthnavdata = []
|
||||
lat = abs(int(self._lat / 10) * 10)
|
||||
lng = abs(int(self._lng / 10) * 10)
|
||||
earthnavdata.append(lat)
|
||||
earthnavdata.append(lng)
|
||||
return earthnavdata
|
||||
|
||||
|
||||
# Construct an X-Plane compatible folder name for latitude and longitude
|
||||
def xplane_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
|
||||
|
||||
|
||||
# Acquires the elevation data as needed -
|
||||
# you can either acquire the older STRM set with lower resolution,
|
||||
# or a modern LIDAR scan with higher resolution. However, the
|
||||
# LIDAR files are much larger and generates a more taxing mesh.
|
||||
# If you are testing, acquire the low resolution file.
|
||||
# Both versions are coming from my repository at marstr.online .
|
||||
def acquire_elevation_data(self):
|
||||
mstr_msg("xp_scenery", "[X-Plane] Acquiring DEM model data")
|
||||
url = "https://marstr.online/dem/"
|
||||
|
||||
url = url + self._demfn
|
||||
|
||||
urllib.request.urlretrieve(url, mstr_datafolder + "_cache/" + self._demfn)
|
||||
mstr_msg("xp_scenery", "[X-Plane] DEM data acquired")
|
||||
|
||||
|
||||
# Download the X-Plane definition file from my server
|
||||
def acquire_xes_data(self):
|
||||
mstr_msg("xp_scenery", "[X-Plane] Acquiring XES file")
|
||||
url = "https://marstr.online/xes/"
|
||||
xesfn = self.build_dem_filename(True)
|
||||
|
||||
xp_folder = self.xplane_latlng_folder([self._lat, self._lng])
|
||||
url = url + xp_folder + ".xes"
|
||||
|
||||
urllib.request.urlretrieve(url, mstr_datafolder + "_cache/" + xesfn)
|
||||
mstr_msg("xp_scenery", "[X-Plane] XES data acquired")
|
||||
|
||||
|
||||
# This builds the entire mesh in one go
|
||||
def build_mesh(self):
|
||||
end_bt = self.find_earthnavdata_number()
|
||||
btlfn = str(self.xplane_latlng_folder(end_bt))
|
||||
xp_folder = self.xplane_latlng_folder([self._lat, self._lng])
|
||||
scr = mstr_datafolder + "z_orthographic/data/meshscript.txt"
|
||||
wd = mstr_datafolder + "z_orthographic/data"
|
||||
dsf = mstr_datafolder + "z_orthographic/Earth nav data/" + btlfn + "/" + xp_folder
|
||||
xesfn = self.build_dem_filename(True)
|
||||
|
||||
# The main command to build the mesh
|
||||
cmd = mstr_xp_meshtool + " \"" + scr + "\" \"" + mstr_datafolder + "_cache/" + xesfn + "\"" + " \"" + mstr_datafolder + "_cache/" + self._demfn + "\" \"" + wd + "\" \"" + dsf + ".dsf\""
|
||||
|
||||
os.system(cmd)
|
||||
|
||||
|
||||
# Individual testing
|
||||
#scn = mstr_xp_scenery(51, 7, 101, 64, 0.010102, "+51+007")
|
||||
#scn.acquire_xes_data()
|
||||
#scn.build_mesh_script()
|
||||
#scn.build_mesh()
|
Loading…
x
Reference in New Issue
Block a user