]> marstr Code Repo - orthographic/commitdiff
Possible initial public release. After extensive testing, code amended to incorporate...
authorMarcus Str. <marcus@marstr.online>
Tue, 24 Sep 2024 21:20:36 +0000 (23:20 +0200)
committerMarcus Str. <marcus@marstr.online>
Tue, 24 Sep 2024 21:20:36 +0000 (23:20 +0200)
defines.py
layergen.py
og.py
orthographic.py
photogen.py
tiledb.py
xp_dsfgen.py [deleted file]
xp_normalmap.py
xp_scenery.py [new file with mode: 0644]

index 824cd01bb777df0fdbb7e20f301a94410364b34f..cbf8db3deda34ddffc6d9d5f40762f0726a072b5 100644 (file)
@@ -72,27 +72,31 @@ mstr_shadow_casters = [
 # Whether or not to generate X-Plane Scenery files\r
 mstr_xp_genscenery = True\r
 \r
-# X-Plane specific\r
-mstr_xp_dsftool = "M:/Developer/Projects/orthographic/bin/DSFTool.exe"\r
-mstr_xp_folder  = "M:/Flight Sim/Simulator/11/"\r
-# Whether or not you want normal maps for certain geographical elements\r
-# to make them appear more realistic.\r
-mstr_xp_generate_normal_maps = True\r
+# Generate normal maps for X-Plane scenery?\r
+# Strong recommendation: yes\r
+mstr_xp_scn_normalmaps = True\r
 \r
-# If you set the above to true, you can\r
-# 1) define for which features to generate normal maps for, and\r
-# 2) the specularity for each feature.\r
-# A specularity value of 100 is fully specular, useful for water\r
-# A specularity value of 10 is more useful for inland features.\r
-# Sand may also reflect some of its surface, so possibly 25 is good there.\r
+# Paths to required X-Plane scenery tools\r
+mstr_xp_meshtool = "/home/marcus/Developer/Projects/orthographic/bin/MeshTool"\r
+mstr_xp_ddstool = "/home/marcus/Developer/Projects/orthographic/bin/DDSTool"\r
+mstr_xp_xessrc = "https://dev.x-plane.com/update/misc/MeshTool/"\r
+\r
+# If you set the above to true, you can define for which features you\r
+# want to generate normal maps for. The below is my recommendation for\r
+# good-looking orthos in the simulator.\r
 mstr_xp_normal_maps = [\r
-    ("landuse", "farmland", 10),\r
-    ("landuse", "forest", 10),\r
-    ("leisure", "nature_reserve", 10),\r
-    ("natural", "water", 100),\r
-    ("water", "pond", 100),\r
-    ("water", "river", 100),\r
-    ("water", "lake", 100)\r
+    ("landuse", "farmland"),\r
+    ("landuse", "meadow"),\r
+    ("landuse", "orchard"),\r
+    ("landuse", "forest"),\r
+    ("natural", "wetland"),\r
+    ("natural", "bare_rock"),\r
+    ("natural", "scrub"),\r
+    ("natural", "heath"),\r
+    ("natural", "sand"),\r
+    ("natural", "desert"),\r
+    ("leisure", "nature_reserve"),\r
+    ("building", "*")\r
 ]\r
 \r
 # How much of a tile we need for each zoom level. The higher\r
index 9b47552ac374c034a710f1c1f3dfd42ae6055346..e04bbce90c1175b994cdac4ca795fa2e60d1daff 100644 (file)
@@ -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\r
             # need to make them at this exact point\r
             if mstr_xp_genscenery == True:\r
-                if mstr_xp_generate_normal_maps == True:\r
+                if mstr_xp_scn_normalmaps == True:\r
                     nm = False\r
                     for n in mstr_xp_normal_maps:\r
-                        if n[0] == self._tag and n[1] == self._value:\r
+                        if n[0] == self._tag and (n[1] == self._value or n[1] == "*"):\r
                             nm = True\r
                             break\r
                     if nm == True:\r
@@ -533,8 +533,27 @@ class mstr_layergen:
 \r
                 self._tiledb.commit_query()\r
                 self._tiledb.close_db()\r
+\r
+            # Create a water mask we need to remove from the DDS later\r
+            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"):\r
+                mstr_msg("layergen", "Generating inland water mask")\r
+                inl_mask = Image.new("RGBA", (self._imgsize, self._imgsize), (255,255,255,255))\r
+                lyr_pix = layer_comp.load()\r
+                inl_pix = inl_mask.load()\r
+                for y in range(self._imgsize):\r
+                    for x in range(self._imgsize):\r
+                        l = lyr_pix[x,y]\r
+                        if l[3] > 0:\r
+                            b = 255 - l[3]\r
+                            inl_pix[x,y] = (b,b,b,255)\r
+                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")\r
+                mstr_msg("layergen", "Inland water mask generated and saved")\r
         \r
 \r
+        # ---------------------------------------------------------------------------------------\r
+        # ---------------------------------------------------------------------------------------\r
+        # ---------------------------------------------------------------------------------------\r
+\r
         # If we encounter one of these road-specific tags, we need to proceed differently.\r
 \r
         if self._isline == True or self._tag == "building":\r
@@ -651,7 +670,7 @@ class mstr_layergen:
                             layer_comp_pix[x, y] = ( r,g,b,a[3] )\r
 \r
             # We will do some super magic here to let houses look more realistic\r
-            if self._tag == "building":\r
+            if self._tag == "building" or self._value == "cemetery":\r
                 vls = [ "detached", "hotel", "farm", "semidetached_house", "apartments", "civic", "office", "retail", "industrial", "house", "school", "yes" ]\r
                 if self._value in vls:\r
                     # Generate a new image\r
@@ -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\r
             # need to make them at this exact point\r
             if mstr_xp_genscenery == True:\r
-                if mstr_xp_generate_normal_maps == True:\r
+                if mstr_xp_scn_normalmaps == True:\r
                     nm = False\r
                     for n in mstr_xp_normal_maps:\r
-                        if n[0] == self._tag and n[1] == self._value:\r
+                        if n[0] == self._tag and (n[1] == self._value or n[1] == "*"):\r
                             nm = True\r
                             break\r
                     if nm == True:\r
diff --git a/og.py b/og.py
index a115f01721e493cf16bb494f623860bb2058fa7e..3050a040b8ba4e12059c654fa728c97dd910a041 100644 (file)
--- a/og.py
+++ b/og.py
@@ -1,10 +1,4 @@
 \r
-import sys\r
-import os\r
-from orthographic import *\r
-from log import *\r
-from defines import *\r
-\r
 # -------------------------------------------------------------------\r
 # ORTHOGRAPHIC\r
 # Your personal aerial satellite. Always on. At any altitude.*\r
@@ -17,6 +11,13 @@ from defines import *
 # -------------------------------------------------------------------\r
 \r
 \r
+import sys\r
+import os\r
+from orthographic import *\r
+from log import *\r
+from defines import *\r
+\r
+\r
 # Print a welcome message\r
 print(" ")\r
 print(" ---------------------------------------------------------------- ")\r
index 8dc2090ab9f99198dc108b8adba76d777498768e..3080ca78978d2c202b6266cc8471854792713af0 100644 (file)
@@ -15,12 +15,11 @@ import os
 import glob\r
 from defines import *\r
 from log import *\r
+from osmxml import *\r
 from maskgen import *\r
 from layergen import *\r
 from photogen import *\r
-from osmxml import *\r
-from tilegen import *\r
-from xp_dsfgen import *\r
+from xp_scenery import *\r
 \r
 \r
 # The main class which handles the rest\r
@@ -91,30 +90,45 @@ class mstr_orthographic:
             os.makedirs(self._output + "/_cache")\r
             mstr_msg("orthographic", "Created _cache folder.")\r
         \r
-        # Generate the Tiles folder for the finished products\r
-        if not os.path.exists(self._output + "/Tiles"):\r
-            os.makedirs(self._output + "/Tiles")\r
-            mstr_msg("orthographic", "Created Tiles folder.")\r
-        \r
         # Generate the Tiles/lat-lng folder for the finished tile\r
-        if not os.path.exists(self._output + "/Tiles/z_orthographic_" + self._latlngfld):\r
-            os.makedirs(self._output + "/Tiles/z_orthographic_"+ self._latlngfld)\r
-            mstr_msg("orthographic", "Created Tiles sub folder: " + self._latlngfld)\r
+        if not os.path.exists(self._output + "/z_orthographic"):\r
+            os.makedirs(self._output + "/z_orthographic")\r
+            mstr_msg("orthographic", "Created z_orthographic folder")\r
 \r
         # Generate the orthos folder\r
-        if not os.path.exists(self._output + "/Tiles/z_orthographic_" + self._latlngfld + "/orthos"):\r
-            os.makedirs(self._output + "/Tiles/z_orthographic_" + self._latlngfld +"/orthos")\r
+        if not os.path.exists(self._output + "/z_orthographic/orthos"):\r
+            os.makedirs(self._output + "/z_orthographic/orthos")\r
             mstr_msg("orthographic", "Created tile orthos folder")\r
+        if not os.path.exists(self._output + "/z_orthographic/orthos" + self._latlngfld):\r
+            os.makedirs(self._output + "/z_orthographic/orthos/" + self._latlngfld)\r
 \r
+        # Generate the database folder\r
+        if not os.path.exists(self._output + "/z_orthographic/data"):\r
+            os.makedirs(self._output + "/z_orthographic/data")\r
+            mstr_msg("orthographic", "Created tile database folder")\r
+        if not os.path.exists(self._output + "/z_orthographic/data/" + self._latlngfld):\r
+            os.makedirs(self._output + "/z_orthographic/data/" + self._latlngfld)\r
+\r
+        # X-Plane specific\r
         if mstr_xp_genscenery == True:\r
-            if not os.path.exists(self._output + "/Tiles/z_orthographic_" + self._latlngfld + "/terrain"):\r
-                os.makedirs(self._output + "/Tiles/z_orthographic_" + self._latlngfld + "/terrain")\r
-                mstr_msg("orthographic", "Created X-Plane tile terrain folder")\r
-\r
-            if mstr_xp_generate_normal_maps == True:\r
-                if not os.path.exists(self._output + "/Tiles/z_orthographic_" + self._latlngfld + "/normals"):\r
-                    os.makedirs(self._output + "/Tiles/z_orthographic_" + self._latlngfld + "/normals")\r
-                    mstr_msg("orthographic", "Created X-Plane tile normals folder")\r
+            btnum = self.find_earthnavdata_number()\r
+            btstr = self.latlng_folder(btnum)\r
+            if not os.path.exists(self._output + "/z_orthographic/terrain"):\r
+                os.makedirs(self._output + "/z_orthographic/terrain")\r
+                mstr_msg("orthographic", "[X-Plane] Created terrain files folder")\r
+            if not os.path.exists(self._output + "/z_orthographic/terrain/" + self._latlngfld):\r
+                os.makedirs(self._output + "/z_orthographic/terrain/" + self._latlngfld)\r
+            if not os.path.exists(self._output + "/z_orthographic/Earth nav data"):\r
+                os.makedirs(self._output + "/z_orthographic/Earth nav data")\r
+                mstr_msg("orthographic", "[X-Plane] Created Earth nav folder")\r
+            if not os.path.exists(self._output + "/z_orthographic/Earth nav data/" + btstr):\r
+                os.makedirs(self._output + "/z_orthographic/Earth nav data/" + btstr)\r
+            if mstr_xp_scn_normalmaps == True:\r
+                if not os.path.exists(self._output + "/z_orthographic/normals"):\r
+                    os.makedirs(self._output + "/z_orthographic/normals")\r
+                    mstr_msg("orthographic", "[X-Plane] created tile normal maps folder")\r
+                if not os.path.exists(self._output + "/z_orthographic/normals/" + self._latlngfld):\r
+                    os.makedirs(self._output + "/z_orthographic/normals/" + self._latlngfld)\r
         \r
         # The tile is constructed of many smaller parts. We walk through the\r
         # smallest possible, from which the bigger ones are later built.\r
@@ -162,7 +176,7 @@ class mstr_orthographic:
                 mstr_msg("orthographic", "Adjusted bounding box for XML object")\r
 \r
                 # Determine what to do... maybe work was interrupted\r
-                if os.path.isfile(mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/orthos/" + str(cur_tile_y) + "_" + str(cur_tile_x) + ".dds") == False:\r
+                if os.path.isfile(mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(cur_tile_y) + "_" + str(cur_tile_x) + ".dds") == False:\r
 \r
                     # Let the user know\r
                     mstr_msg("orthographic", "Generating missing orthophoto " + str(cur_tile_y) + "-" + str(cur_tile_x))\r
@@ -248,12 +262,15 @@ class mstr_orthographic:
 \r
         # Complete scenery\r
         if mstr_xp_genscenery == True:\r
-            dsf = mstr_xp_dsfgen(self._lat, self._long, mlat, mlng, self._vstep)\r
-            dsf.build_dsf_for_tile()\r
-            mstr_msg("orthographic", "X-Plane scenery completed")\r
+            scn = mstr_xp_scenery(self._lat, self._long, mlat, mlng, self._vstep, self._latlngfld)\r
+            scn.acquire_elevation_data()\r
+            scn.acquire_xes_data()\r
+            scn.build_mesh_script()\r
+            scn.build_mesh()\r
+            mstr_msg("orthographic", "[X-Plane] Mesh built, and scenery completed")\r
         \r
         mstr_msg("orthographic", "Final step completed.")\r
-        mstr_msg("orthographic", "Tile data in: " + self._output + "/Tiles/z_orthographic_" + self._latlngfld)\r
+        mstr_msg("orthographic", "Tile data in: " + self._output + "z_orthographic/" + self._latlngfld)\r
         print("")\r
         mstr_msg("orthographic", "Thanks for using Orthographic! -- Best, Marcus")\r
         print("")\r
@@ -335,3 +352,12 @@ class mstr_orthographic:
 \r
         return fstr\r
 \r
+\r
+    # Find the next "by-ten" numbers for the current latitude and longitude\r
+    def find_earthnavdata_number(self):\r
+        earthnavdata = []\r
+        lat = abs(int(self._lat / 10) * 10)\r
+        lng = abs(int(self._long / 10) * 10)\r
+        earthnavdata.append(lat)\r
+        earthnavdata.append(lng)\r
+        return earthnavdata
\ No newline at end of file
index 5703755f36a9f2af1718ba6d10e150ee77496dc3..ed5e41501b27146df4c3ed8dc7b344da5ec2d704 100644 (file)
@@ -4,7 +4,6 @@ from PIL import Image, ImageFilter
 from defines import *\r
 from layergen import *\r
 from log import *\r
-from wand import image\r
 \r
 # -------------------------------------------------------------------\r
 # ORTHOGRAPHIC\r
@@ -111,6 +110,25 @@ class mstr_photogen:
                 if p[0] == 255 and p[1] == 0 and p[2] == 255:\r
                     t = (0,0,0,0)\r
                     ocean_pix[x,y] = t\r
+\r
+        # Now cut out inland water\r
+        water_layers = (\r
+            ["natural", "water"],\r
+            ["water", "lake"],\r
+            ["water", "pond"],\r
+            ["water", "river"]\r
+        )\r
+        for l in water_layers:\r
+            fn = mstr_datafolder + "_cache/" + str(self._lat) + "-" + str(self._ty) + "_" + str(self._lng) + "-" + str(self._tx) + "_" + l[0] + "-" + l[1] + "_layer_mask.png"\r
+            if os.path.isfile(fn) == True:\r
+                wtr = Image.open(fn)\r
+                wtr_pix = wtr.load()\r
+                tilepix = self._tile.load()\r
+                for y in range(wtr.height):\r
+                    for x in range(wtr.width):\r
+                        wp = wtr_pix[x,y]\r
+                        if wp[0] == 0:\r
+                            tilepix[x,y] = (0,0,0,0)\r
         \r
         # We are now in posession of the final image.\r
 \r
@@ -118,15 +136,13 @@ class mstr_photogen:
         self._tile = self._tile.resize((mstr_photores, mstr_photores), Image.Resampling.BILINEAR)\r
         \r
         # This we can save accordingly.\r
-        self._tile.convert('RGB').save(mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/orthos/" + str(self._ty) + "_" + str(self._tx) + ".png")\r
-        \r
+        self._tile.save(mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._ty) + "_" + str(self._tx) + ".png")\r
 \r
         # Now we convert this into a DDS\r
-        with image.Image(filename=mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/orthos/" + str(self._ty) + "_" + str(self._tx) + ".png") as img:\r
-            img.compression = "dxt1"\r
-            img.save(filename=mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/orthos/" + str(self._ty) + "_" + str(self._tx) + ".dds")\r
-            # TODO: CUT OUT OCEANS AND SEAS IN DDS\r
-        os.remove(mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/orthos/" + str(self._ty) + "_" + str(self._tx) + ".png")\r
+        _tmpfn = mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._ty) + "_" + str(self._tx)\r
+        os.system(mstr_xp_ddstool + " --png2dxt1 " + _tmpfn + ".png " + _tmpfn + ".dds" )\r
+        \r
+        os.remove(mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._ty) + "_" + str(self._tx) + ".png")\r
 \r
 \r
 \r
index 69e24d9dbc741f87c49d908cab7150f728a8b5c0..c43185cd846f06dc48c4f69e34471074f8ad58e6 100644 (file)
--- a/tiledb.py
+++ b/tiledb.py
@@ -25,7 +25,7 @@ class mstr_tiledb:
         self._latlngfld = latlngfld\r
 \r
         # The db file will be created, should it not exist\r
-        self._conn = sqlite3.connect(mstr_datafolder + "Tiles/z_orthographic_" + latlngfld + "/data.db")\r
+        self._conn = sqlite3.connect(mstr_datafolder + "z_orthographic/data/" + latlngfld + "/data.db")\r
         self._crs = self._conn.cursor()\r
         #mstr_msg("tiledb", "Database object initiated")\r
 \r
diff --git a/xp_dsfgen.py b/xp_dsfgen.py
deleted file mode 100644 (file)
index b05483f..0000000
+++ /dev/null
@@ -1,211 +0,0 @@
-\r
-# -------------------------------------------------------------------\r
-# ORTHOGRAPHIC\r
-# Your personal aerial satellite. Always on. At any altitude.*\r
-# Developed by MarStrMind\r
-# License: Open Software License 3.0\r
-# Up to date version always on marstr.online\r
-# -------------------------------------------------------------------\r
-# xp_dsfgen.py\r
-# This class is coming into play at the very end of the tile\r
-# generation process, and builds the DSF (Distributable Scenery\r
-# Format) file for X-Plane.\r
-#\r
-# For this, you will need DSFTool which I cannot re-distribute.\r
-#\r
-# You can download it for free from X-Plane's website.\r
-# Place them somewhere convenient, and point to them in the\r
-# xp_ variables in defines.py\r
-# -------------------------------------------------------------------\r
-\r
-import os\r
-import glob\r
-import math\r
-from random import randrange\r
-from log import *\r
-\r
-class mstr_xp_dsfgen:\r
-    # Instantiate with Lat/Lng, as usual\r
-    def __init__(self, lat, lng, mlat, mlng, vstep):\r
-        self._latitude = lat\r
-        self._longitude = lng\r
-        self._maxlat = mlat\r
-        self._maxlng = mlng\r
-        self._tmpdsf = mstr_datafolder + "_cache/tiledsf.txt"\r
-        self._vstep = vstep\r
-        self._dsfstring = ""\r
-        mstr_msg("xp_dsfgen", "[X-Plane] DSFgen initialized")\r
-        self.build_header()\r
-\r
-    \r
-    # Construct header of DSF txt\r
-    def build_header(self):\r
-        self._dsfstring = self._dsfstring + "PROPERTY sim/west " + str(int(self._longitude)) + "\n"\r
-        self._dsfstring = self._dsfstring + "PROPERTY sim/east " + str(int(self._longitude + 1)) + "\n"\r
-        self._dsfstring = self._dsfstring + "PROPERTY sim/south " + str(int(self._latitude)) + "\n"\r
-        self._dsfstring = self._dsfstring + "PROPERTY sim/north " + str(int(self._latitude+1)) + "\n"\r
-        self._dsfstring = self._dsfstring + "PROPERTY sim/planet earth\n"\r
-        self._dsfstring = self._dsfstring + "PROPERTY sim/creation_agent Orthographic\n"\r
-        self._dsfstring = self._dsfstring + "PROPERTY sim/overlay 1\n" # <- DO NOT REMOVE THIS!!\r
-        self._dsfstring = self._dsfstring + "PROPERTY sim/require_facade 6/0\n"\r
-        mstr_msg("xp_dsfgen", "[X-Plane] DSF header built")\r
-\r
-\r
-    # Write the text file\r
-    def write_dsf_txt(self):\r
-        mstr_msg("xp_dsfgen", "[X-Plane] Writing DSF txt file")\r
-        with open(self._tmpdsf, 'w') as textfile:\r
-            textfile.write(self._dsfstring)                \r
-\r
-\r
-    # Convert the DSF into actual, usable data for X-Plane\r
-    def convert_dsf_text(self):\r
-        mstr_msg("xp_dsfgen", "[X-Plane] Converting DSF information into X-Plane DSF file")\r
-        # Find separator\r
-        sep = ""\r
-        if os.name == "nt":\r
-            sep = "\\"\r
-        if os.name == "posix":\r
-            sep = "/"\r
-        \r
-        datafolder = mstr_datafolder.replace("/", sep)\r
-\r
-        # First, create the Earth nav data folder should it not exist\r
-        end_base = datafolder + "Tiles/z_orthographic_" + self.xplane_latlng_folder([self._latitude, self._longitude]) + sep + "Earth nav data"\r
-\r
-        # Create the appropriate rounded folder\r
-        end_round = self.xplane_latlng_folder(self.find_earthnavdata_number())\r
-        if not os.path.exists(end_base):\r
-            os.makedirs(end_base)\r
-        if not os.path.exists(end_base + sep + end_round):\r
-            os.makedirs(end_base + sep + end_round)\r
-\r
-        # Get the file name for the DSF\r
-        end_latlng = self.xplane_latlng_folder([self._latitude, self._longitude])\r
-\r
-        # Perform conversion\r
-        os.system(mstr_xp_dsftool + " --text2dsf " + datafolder + "_cache" + sep + "tiledsf.txt \"" + end_base + sep + end_round + sep + end_latlng + ".dsf\"")\r
-        mstr_msg("xp_dsfgen", "[X-Plane] DSF conversion complete")\r
-\r
-\r
-    # Find the next "by-ten" numbers for the current latitude and longitude\r
-    def find_earthnavdata_number(self):\r
-        earthnavdata = []\r
-        lat = abs(int(self._latitude / 10) * 10)\r
-        lng = abs(int(self._longitude / 10) * 10)\r
-        earthnavdata.append(lat)\r
-        earthnavdata.append(lng)\r
-        return earthnavdata\r
-    \r
-    # Find with of a longitude (needed for DSF and .pol files)\r
-    def _findWidthOfLongitude(self, lat):\r
-        dm = math.cos(math.radians(lat)) * 111.321 # <- 1 deg width at equator in km\r
-        return round(dm * 1000, 3)\r
-\r
-    # Find diameter of an ortho\r
-    def find_ortho_diameter(self, side):\r
-        sq = side * side\r
-        sqsum = sq + sq\r
-        dm = math.sqrt(sqsum)\r
-    \r
-\r
-    # Construct an X-Plane compatible folder name for latitude and longitude\r
-    def xplane_latlng_folder(self, numbers):\r
-        fstr = ""\r
-        if numbers[0] >= 0: fstr = "+"\r
-        if numbers[0] <  0: fstr = "-"\r
-        if abs(numbers[0]) < 10: fstr = fstr + "0" + str(numbers[0])\r
-        if abs(numbers[0]) >= 10 and numbers[0] <= 90: fstr = fstr + str(numbers[0])\r
-\r
-        if numbers[1] >= 0: fstr = fstr + "+"\r
-        if numbers[1] <  0: fstr = fstr + "-"\r
-        if abs(numbers[1]) < 10: fstr = fstr + "00" + str(numbers[1])\r
-        if abs(numbers[1]) >= 10 and numbers[0] <= 99: fstr = fstr + "0" + str(numbers[1])\r
-        if abs(numbers[1]) >= 100 : fstr = fstr + str(numbers[1])\r
-\r
-        return fstr\r
-\r
-\r
-    # Build the complete DSF.\r
-    # This is the main function to call.\r
-    def build_dsf_for_tile(self):\r
-        mstr_msg("xp_dsfgen", "[X-Plane] Building DSF file")\r
-        # Add the polygon definition file entries\r
-        for v in range(1, self._maxlat+1):\r
-            for h in range(1, self._maxlng+1):\r
-                self._dsfstring = self._dsfstring + "POLYGON_DEF terrain/"+str(v)+"_"+str(h)+".pol\n"\r
-\r
-        # Add the definitions for each ortho tile\r
-        curpol = 0\r
-        cur_lat = self._latitude\r
-        cur_lng = self._longitude\r
-        for v in range(1, self._maxlat+1):\r
-            for h in range(1, self._maxlng+1):\r
-                bbox = [ cur_lat, cur_lng, cur_lat+self._vstep-0.00001, cur_lng + mstr_zl_18-0.00001 ]\r
-                self._dsfstring = self._dsfstring + "BEGIN_POLYGON "+str(curpol)+" 65535 4\n"\r
-                self._dsfstring = self._dsfstring + "BEGIN_WINDING\n"\r
-\r
-                self._dsfstring = self._dsfstring + "POLYGON_POINT " + str(bbox[1]) + " " + str(bbox[0]) + " 0.000000000 0.000000000\n"\r
-                self._dsfstring = self._dsfstring + "POLYGON_POINT " + str(bbox[3]) + " " + str(bbox[0]) + " 1.000000000 0.000000000\n"\r
-                self._dsfstring = self._dsfstring + "POLYGON_POINT " + str(bbox[3]) + " " + str(bbox[2]) + " 1.000000000 1.000000000\n"\r
-                self._dsfstring = self._dsfstring + "POLYGON_POINT " + str(bbox[1]) + " " + str(bbox[2]) + " 0.000000000 1.000000000\n"\r
-\r
-                self._dsfstring = self._dsfstring + "END_WINDING\n"\r
-                self._dsfstring = self._dsfstring + "END_POLYGON\n"\r
-\r
-                # Adjust forward\r
-                cur_lng = cur_lng + mstr_zl_18\r
-                curpol = curpol + 1\r
-            \r
-            # Adjust up, reset longitude\r
-            cur_lng = self._longitude\r
-            cur_lat = cur_lat + self._vstep\r
-\r
-        # OK... we can now save this\r
-        self.write_dsf_txt()\r
-\r
-        # Generate the single .pol files now\r
-        mstr_msg("xp_dsfgen", "[X-Plane] Beginning generation of terrain/*.pol files")\r
-        cur_lat = self._latitude\r
-        cur_lng = self._longitude\r
-        lclat = cur_lat + (self._vstep/2)\r
-        lclng = cur_lng + (mstr_zl_18/2)\r
-        for v in range(1, self._maxlat+1):\r
-            for h in range(1, self._maxlng+1):\r
-                dm = self._findWidthOfLongitude(cur_lng) * mstr_zl_18\r
-                dg = self.find_ortho_diameter(dm)\r
-                polstr = ""\r
-                polstr = polstr + "A\n"\r
-                polstr = polstr + "850\n"\r
-                polstr = polstr + "DRAPED_POLYGON\n"\r
-                polstr = polstr + "\n"\r
-                polstr = polstr + "TEXTURE_NOWRAP ../orthos/"+str(v)+"_"+str(h)+".dds\n"\r
-\r
-                # Check for existence of a normal map\r
-                # If there is one, we will add that too\r
-                end_latlng = self.xplane_latlng_folder([self._latitude, self._longitude])\r
-                if os.path.isfile(mstr_datafolder + "Tiles/z_orthographic_"+end_latlng+"/normals/" + str(v) + "_" + str(h) + ".png") == True:\r
-                    polstr = polstr + "TEXTURE_NORMAL 1 ../normals/"+str(v)+"_"+str(h)+".png\n"\r
-\r
-                polstr = polstr + "SCALE "+str(dm)+" "+str(dm)+"\n"\r
-                polstr = polstr + "LOAD_CENTER "+str(lclat)+" " +str(lclng)+" " +str(dg)+ " " +str(mstr_photores)+"\n"\r
-                polstr = polstr + "LAYER_GROUP TERRAIN 1\n"\r
-\r
-                # Save this content\r
-                with open(mstr_datafolder + "Tiles/z_orthographic_"+end_latlng+"/terrain/"+str(v)+"_"+str(h)+".pol", 'w') as textfile:\r
-                    textfile.write(polstr)   \r
-\r
-                # Adjust forward\r
-                cur_lng = cur_lng + mstr_zl_18\r
-                lclng = cur_lng + (mstr_zl_18/2)\r
-\r
-            cur_lng = self._longitude\r
-            lclng = cur_lng + (mstr_zl_18/2)\r
-\r
-            cur_lat = cur_lat + self._vstep\r
-            lclat = cur_lat + (self._vstep/2)\r
-\r
-        mstr_msg("xp_dsfgen", "[X-Plane] Converting DSF txt")\r
-        self.convert_dsf_text()\r
-\r
-        mstr_msg("xp_dsfgen", "[X-Plane] DSF for tile completed")\r
index d38ffb5a21a66afdce4eb8a1620d67bba1d44258..1394defed83e2c8467e4c48cd7284b0017981369 100644 (file)
@@ -84,20 +84,12 @@ class mstr_xp_normalmap:
     # The Big Mac. Generate the normal map\r
     def generate_normal_map_for_layer(self, image):\r
         mstr_msg("xp_normalmap", "[X-Plane] Beginning normal map generation")\r
-        nmp = Image.new("RGBA", (image.width, image.height), (128,128,255,0)) # no specularity, but default color\r
-        #nmp = Image.new("RGBA", (image.width, image.height), (0,0,0,0))\r
+        # No specularity, no reflectivity - but standard color\r
+        # Blue (reflectivity) and alpha (specularity) need to be 1 - but can be adjusted as needed\r
+        nmp = Image.new("RGBA", (image.width, image.height), (128,128,1,1))\r
         org = image.load()\r
         nmp_pix = nmp.load()\r
 \r
-        # Find out which alpha value to use\r
-        alpha = 0\r
-        for n in mstr_xp_normal_maps:\r
-            if n[0] == self._tag and n[1] == self._value:\r
-                v = n[2] / 100\r
-                a = v * 255.0\r
-                alpha = int(a)\r
-\r
-\r
         # Let's try some shenanigans\r
         w = image.width\r
         h = image.height\r
@@ -132,16 +124,13 @@ class mstr_xp_normalmap:
                     v = (dx, dy, dz)\r
                     nrm = self.normalize_vector(v)\r
                     \r
-                    # Invert height for our Orthos - but only in some circumstances\r
-                    # We want water to go "down"\r
-                    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"):\r
-                        if nrm[1] > 0:\r
-                            nrm[1] = 0 - (abs(nrm[1]))\r
-                        else:\r
-                            nrm[1] = abs(nrm[1])\r
+                    if nrm[1] > 0:\r
+                        nrm[1] = 0 - (abs(nrm[1]))\r
+                    else:\r
+                        nrm[1] = abs(nrm[1])\r
 \r
                     # Set pixel\r
-                    nmp_pix[x,y] = (int(self.map_component(nrm[0])), int(self.map_component(nrm[1])), int(self.map_component(nrm[2])), alpha)\r
+                    nmp_pix[x,y] = (int(self.map_component(nrm[0])), int(self.map_component(nrm[1])), int(self.map_component(nrm[2])), 1)\r
 \r
         mstr_msg("xp_normalmap", "[X-Plane] Normal map generated")\r
         return nmp\r
@@ -157,7 +146,7 @@ class mstr_xp_normalmap:
         nrm = self.generate_normal_map_for_layer(lyr)\r
 \r
         # Normal map final file name\r
-        nrmfln = mstr_datafolder + "Tiles/z_orthographic_" + self._latlngfld + "/normals/" + str(self._tv) + "_" + str(self._th) + ".png"\r
+        nrmfln = mstr_datafolder + "z_orthographic/normals/" + self._latlngfld + "/" + str(self._tv) + "_" + str(self._th) + ".png"\r
 \r
         # Check for existence of normal map file\r
         ex = os.path.isfile(nrmfln)\r
@@ -170,5 +159,13 @@ class mstr_xp_normalmap:
         if ex == True:\r
             nrmmap = Image.open(nrmfln)\r
             nrmmap.alpha_composite(nrm)\r
+\r
+            # Specularity blending correction\r
+            nrmmap_pix = nrmmap.load()\r
+            for y in range(nrmmap.height):\r
+                for x in range(nrmmap.width):\r
+                    c = nrmmap_pix[x,y]\r
+                    nrmmap_pix[x,y] = (c[0], c[1], c[2], 1)\r
             nrmmap.save(nrmfln)\r
+            \r
         mstr_msg("xp_normalmap", "[X-Plane] Normal map saved")\r
diff --git a/xp_scenery.py b/xp_scenery.py
new file mode 100644 (file)
index 0000000..269b8aa
--- /dev/null
@@ -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()
\ No newline at end of file