]> marstr Code Repo - orthographic/commitdiff
Milestone commit: xp_scenery class able to generate pixel-perfect and vertex-perfect...
authorMarcus Str. <marcus@marstr.online>
Thu, 28 Nov 2024 21:28:39 +0000 (22:28 +0100)
committerMarcus Str. <marcus@marstr.online>
Thu, 28 Nov 2024 21:28:39 +0000 (22:28 +0100)
defines.py
og.py
orthographic.py
photogen.py
xp_scenery.py

index dea842cb9d0ed7a2c56d2a15c384167bb8425887..b437c17a39fa43bdb9496cb33ef0ea61dcf659a0 100644 (file)
@@ -74,8 +74,10 @@ mstr_xp_scn_normalmaps = True
 # 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_dsftool = "/home/marcus/Developer/Projects/orthographic/bin/DSFTool"\r
 mstr_xp_xessrc = "https://dev.x-plane.com/update/misc/MeshTool/"\r
 mstr_xp_floor_height = 2.8 # 2.5m ceiling height + 30cm concrete per floor\r
+mstr_xp_ortho_location = "/home/marcus/Data/Sim/Simulator/orthographic/"\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
diff --git a/og.py b/og.py
index 01f7d2496303d3e85855d18ca4b13d2cc7855d07..8b0f4e4606af97e1a68c8186bf170e7b684ce103 100644 (file)
--- a/og.py
+++ b/og.py
@@ -54,7 +54,12 @@ if cli == True:
         og._prepareTile()\r
 \r
     if prep == False:\r
-        og._generateOrthos_mt(int(sys.argv[3]))\r
+        if sys.argv[3] != "xpscenery":\r
+            og._generateOrthos_mt(int(sys.argv[3]))\r
+        \r
+        # Build the terrain mesh and assign ground textures\r
+        if sys.argv[3] == "xpscenery":\r
+            og.generate_xp_scenery()\r
 \r
 \r
 # Only if we find enough arguments, proceed.\r
index e5b515fd0537585b2fff6e017783f38f7194c32a..4be989ab1a21fe237510a98aa40ab6b58751627c 100644 (file)
@@ -208,64 +208,66 @@ class mstr_orthographic:
         maxlatlng = [ mlat, mlng ]\r
 \r
         while grid_lat <= maxlatlng[0]:\r
-            # Reset these two\r
-            bb_lat = self._lat + ((grid_lat-1)*self._vstep)\r
-            bb_lng = self._long + ((grid_lng-1)*mstr_zl_18)\r
-            bb_lat_edge = self._lat + ((grid_lat-1)*self._vstep) + self._vstep\r
-            bb_lng_edge = self._long + ((grid_lng-1)*mstr_zl_18) + mstr_zl_18\r
+            ddsf = mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(grid_lat) + "_" + str(grid_lng) + ".dds"\r
+            if os.path.isfile(ddsf) == False:\r
+                # Reset these two\r
+                bb_lat = self._lat + ((grid_lat-1)*self._vstep)\r
+                bb_lng = self._long + ((grid_lng-1)*mstr_zl_18)\r
+                bb_lat_edge = self._lat + ((grid_lat-1)*self._vstep) + self._vstep\r
+                bb_lng_edge = self._long + ((grid_lng-1)*mstr_zl_18) + mstr_zl_18\r
 \r
-            osmxml = mstr_osmxml()\r
-            osmxml.adjust_bbox(bb_lat, bb_lng, bb_lat_edge, bb_lng_edge)\r
-            osmxml.acquire_osm(grid_lat, grid_lng)\r
+                osmxml = mstr_osmxml()\r
+                osmxml.adjust_bbox(bb_lat, bb_lng, bb_lat_edge, bb_lng_edge)\r
+                osmxml.acquire_osm(grid_lat, grid_lng)\r
 \r
-            # Let the user know\r
-            mstr_msg("orthographic", "Generating orthophoto " + str(grid_lat) + "-" + str(grid_lng))\r
+                # Let the user know\r
+                mstr_msg("orthographic", "Generating missing orthophoto " + str(grid_lat) + "-" + str(grid_lng))\r
 \r
-            # Check for work to be done\r
-            layers = self.determineLayerWork(osmxml)\r
+                # Check for work to be done\r
+                layers = self.determineLayerWork(osmxml)\r
 \r
-            # We need to walk through the array of layers,\r
-            # in their z-order.\r
-            # For each layer, we will generate the mask, the layer image\r
-            # itself, and finally, compose the ortho photo.\r
-            mstr_msg("orthographic", "Beginning generation of layers")\r
+                # We need to walk through the array of layers,\r
+                # in their z-order.\r
+                # For each layer, we will generate the mask, the layer image\r
+                # itself, and finally, compose the ortho photo.\r
+                mstr_msg("orthographic", "Beginning generation of layers")\r
 \r
-            # In here we store the layers\r
-            photolayers = []\r
+                # In here we store the layers\r
+                photolayers = []\r
 \r
-            # The masks are handed to layergen in sequence. The layers are then\r
-            # in turn handed to photogen.\r
+                # The masks are handed to layergen in sequence. The layers are then\r
+                # in turn handed to photogen.\r
 \r
-            curlyr = 1\r
-            for layer in layers:\r
-                # Let the user know\r
-                mstr_msg("orthographic", "Processing layer " + str(curlyr) + " of " + str(len(layers)))\r
-\r
-                # Generate the mask\r
-                mg = mstr_maskgen( [self._lat, grid_lat, self._long, grid_lng], self._vstep, layer[0], layer[1], layer[2])\r
-                if layer[0] == "building":\r
-                    mg.set_tile_width(self._findWidthOfLongitude(bb_lat))\r
-                    mg.set_latlng_numbers(self._lat, grid_lat, self._long, grid_lng)\r
-                mask = mg._build_mask(osmxml)\r
-                \r
-                # Generate the layer\r
-                lg = mstr_layergen(layer[0], layer[1], self._lat, grid_lat, self._long, grid_lng, layer[2])\r
-                lg.set_max_latlng_tile(maxlatlng)\r
-                lg.set_latlng_folder(self._latlngfld)\r
-                #lg.open_db()\r
-                lg.open_tile_info()\r
-                photolayers.append(lg.genlayer(mask, osmxml))\r
-                curlyr = curlyr+1\r
-            mstr_msg("orthographic", "All layers created")\r
-\r
-            # We should have all layers now.\r
-            # Snap a photo with our satellite :)\r
-            mstr_msg("orthographic", "Generating ortho photo")\r
-            pg = mstr_photogen(self._lat, self._long, grid_lat, grid_lng,  maxlatlng[0], maxlatlng[1])\r
-            pg.genphoto(photolayers)\r
-            mstr_msg("orthographic", " -- Ortho photo generated -- ")\r
-            print("")\r
-            print("")\r
+                curlyr = 1\r
+                for layer in layers:\r
+                    # Let the user know\r
+                    mstr_msg("orthographic", "Processing layer " + str(curlyr) + " of " + str(len(layers)))\r
+\r
+                    # Generate the mask\r
+                    mg = mstr_maskgen( [self._lat, grid_lat, self._long, grid_lng], self._vstep, layer[0], layer[1], layer[2])\r
+                    if layer[0] == "building":\r
+                        mg.set_tile_width(self._findWidthOfLongitude(bb_lat))\r
+                        mg.set_latlng_numbers(self._lat, grid_lat, self._long, grid_lng)\r
+                    mask = mg._build_mask(osmxml)\r
+                    \r
+                    # Generate the layer\r
+                    lg = mstr_layergen(layer[0], layer[1], self._lat, grid_lat, self._long, grid_lng, layer[2])\r
+                    lg.set_max_latlng_tile(maxlatlng)\r
+                    lg.set_latlng_folder(self._latlngfld)\r
+                    #lg.open_db()\r
+                    lg.open_tile_info()\r
+                    photolayers.append(lg.genlayer(mask, osmxml))\r
+                    curlyr = curlyr+1\r
+                mstr_msg("orthographic", "All layers created")\r
+\r
+                # We should have all layers now.\r
+                # Snap a photo with our satellite :)\r
+                mstr_msg("orthographic", "Generating ortho photo")\r
+                pg = mstr_photogen(self._lat, self._long, grid_lat, grid_lng,  maxlatlng[0], maxlatlng[1])\r
+                pg.genphoto(photolayers)\r
+                mstr_msg("orthographic", " -- Ortho photo generated -- ")\r
+                print("")\r
+                print("")\r
 \r
             # Perform adjustment of grid position\r
             n_lng = grid_lng + step\r
@@ -418,7 +420,62 @@ class mstr_orthographic:
 \r
 \r
 \r
+    # Generates X-Plane 11/12 scenery with\r
+    # - the finished orthos\r
+    # - a current LIDAR scan of the terrain\r
+    def generate_xp_scenery(self):\r
+        mstr_msg("orthographic", "[X-Plane] Generation of scenery started")\r
 \r
+        # This call appears quite often... surely this can be done better\r
+        mlat = 1\r
+        mlng = 1\r
+        bb_lat = self._lat\r
+        bb_lng = self._long\r
+        bb_lat_edge = self._lat+self._vstep\r
+        bb_lng_edge = self._long+mstr_zl_18\r
+        while bb_lat < self._lat + 1:\r
+            bb_lat = bb_lat + self._vstep\r
+            mlat = mlat+1\r
+        while bb_lng < self._long + 1:\r
+            bb_lng = bb_lng + mstr_zl_18\r
+            mlng = mlng+1\r
+        mstr_msg("orthographic", "Max lat tile: " + str(mlat) + " - max lng tile: " + str(mlng))\r
+        maxlatlng = [ mlat, mlng ]\r
+        \r
+        # The object that handles it all\r
+        xpscn = mstr_xp_scenery(self._lat, self._long, maxlatlng[0], maxlatlng[1], self._vstep, self._latlngfld)\r
+        mstr_msg("orthographic", "[X-Plane] Scenery object instantiated")\r
+\r
+        # Generate the script\r
+        #xpscn.build_mesh_script()\r
+        #mstr_msg("orthographic", "[X-Plane] Mesh script written")\r
+\r
+        # Download LIDAR scan from our endpoint\r
+        #xpscn.acquire_elevation_data()\r
+        #mstr_msg("orthographic", "[X-Plane] LIDAR scan acquired")\r
+\r
+        # Download required XES data\r
+        #xpscn.acquire_xes_data()\r
+        #mstr_msg("orthographic", "[X-Plane] MeshTool XES data acquired")\r
+\r
+        # Generate the .ter files\r
+        xpscn.build_ter_files()\r
+        mstr_msg("orthographic", "[X-Plane] Terrain files (.ter) generated and written")\r
+\r
+        # Build mesh\r
+        #xpscn.build_mesh()\r
+        #xpscn._dsf_test()\r
+        #xpscn.build_and_convert_dsf()\r
+\r
+        # And lastly, generate the mesh\r
+        xpscn.generate_terrain_mesh()\r
+        mstr_msg("orthographic", "[X-Plane] Scenery mesh constructed")\r
+\r
+        # Convert the DSF\r
+        xpscn.build_and_convert_dsf()\r
+        mstr_msg("orthographic", "[X-Plane] DSF generated")\r
+        \r
+    \r
     # Checks which layers need to be generated, and what kind of layer it is\r
     def determineLayerWork(self, xmlobj):\r
 \r
index a76534296631ab13394a1665a70f8a84b592548e..9099f6183964705f5ce1419936f6e0c0de7e21f6 100644 (file)
@@ -119,7 +119,7 @@ class mstr_photogen:
                 py = randrange(1, randrange(self._imgsize - ptc.height - 1))\r
 \r
                 # Add it to the completion image\r
-                cmpl.alpha_composite(ptc)\r
+                cmpl.alpha_composite(ptc, dest=(px,py))\r
 \r
             # Merge the images\r
             cmpl.alpha_composite(self._tile)\r
index fecd6c03fd7f8f58f5d4536f3203b940d5bb6048..720591fa29cfc658e2fe2749dd0f9eaff139a185 100644 (file)
 import os
 import math
 import urllib.request
+import numpy
 from defines import *
 from log import *
+from PIL import Image, ImageFilter, ImageEnhance
 
 class mstr_xp_scenery:
     # Set required variables
@@ -29,6 +31,9 @@ class mstr_xp_scenery:
         self._vstep = vstep
         self._latlngfld = latlngfld
         self._demfn = self.build_dem_filename()
+        self._dsfstring = ""
+        self._demdata = None  # To be populated when the mesh is built
+        self._demcoord = None # Also to be populated when mesh is built
 
 
     # Build the correct file name for the elevation model
@@ -60,49 +65,21 @@ class mstr_xp_scenery:
 
         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):
-                    # Write the line only if an ortho exists of course.
-                    if os.path.isfile(mstr_datafolder + "z_orthographic/" + self._latlngfld + "/orthos/" + str(lat) + "_" + str(lng) + ".dds" ) == True:
-                        # 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)
+    # Build the DSF for the ortho photo overlay
+    def build_and_convert_dsf(self):
+        end = self.find_earthnavdata_number()
+        llf = self.xplane_latlng_folder(end)
+        meshtxt = mstr_datafolder + "_cache/mesh_"+self._latlngfld+".txt"
+        scr = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/meshscript.txt"
+        cmd = mstr_xp_dsftool + " --text2dsf " + meshtxt + " '" + mstr_datafolder + "z_orthographic/Earth nav data/" + llf + "/" + self._latlngfld + ".dsf'"
+        os.system(cmd)
 
-                    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 exact with of longitude
+    def find_width_of_longitude(self, lat):
+        dm = math.cos(math.radians(lat)) * 111.321 # <- 1 deg width at equator in km
+        return round(dm * 1000, 3)
 
 
     # Find the next "by-ten" numbers for the current latitude and longitude
@@ -161,24 +138,6 @@ class mstr_xp_scenery:
         mstr_msg("xp_scenery", "[X-Plane] XES data acquired")
 
 
-    # This builds the entire mesh in one go
-    def build_mesh(self):
-        mstr_msg("xp_scenery", "[X-Plane] Building DSF mesh")
-        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)
-
-        mstr_msg("xp_scenery", "[X-Plane] Mesh construction complete")
-
 
     # This generates all .ter files
     def build_ter_files(self):
@@ -188,19 +147,504 @@ class mstr_xp_scenery:
         xp_folder = self.xplane_latlng_folder([self._lat, self._lng])
         for lat in range(1, self._mlat+1):
             for lng in range(1, self._mlng+1):
+
+                wdt = self.find_width_of_longitude(cur_lat)
+                dmt = wdt * mstr_zl_18
+
+                cnt_x = cur_lat + (self._vstep/2)
+                cnt_y = cur_lng + (mstr_zl_18/2)
+
                 terstr = ""
-                terstr = terstr + "A\n"
-                terstr = terstr + "800\n"
-                terstr = terstr + "TERRAIN\n"
-                terstr = terstr + "\n"
-                terstr = terstr + "BASE_TEX_NOWRAP ../orthos/"+xp_folder+"/"+str(lat)+"_"+str(lng)+".dds\n"
+                terstr = terstr + "A\r\n"
+                terstr = terstr + "800\r\n"
+                terstr = terstr + "TERRAIN\r\n"
+                terstr = terstr + "\r\n"
+                terstr = terstr + "LOAD_CENTER " + str(cnt_x) + " " + str(cnt_y) + " " + str(dmt) + " 2048\r\n"
+                terstr = terstr + "TEXTURE_NOWRAP ../../orthos/" + self._latlngfld + "/" + str(lat)+"_"+str(lng)+".dds\r\n"
                 if mstr_xp_scn_normalmaps == True:
-                    terstr = terstr + "TEXTURE_NORMAL ../normals/"+xp_folder+"/"+str(lat)+"_"+str(lng)+".dds\n"
+                    terstr = terstr + "NORMAL_TEX 1.0 ../../normals/" + self._latlngfld + "/" + str(lat)+"_"+str(lng)+".png\r\n"
 
-                terfln = mstr_datafolder + "z_orthographic/terrain/"+xp_folder+"/"+str(lat)+"_"+str(lng)+".ter"
+                terfln = mstr_datafolder + "z_orthographic/terrain/" + self._latlngfld + "/" + str(lat)+"_"+str(lng)+".ter"
 
                 with open(terfln, 'w') as textfile:
                     textfile.write(terstr)
+                
+                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] Terrain files written")
 
+
+    # This generates the entire terrain mesh
+    def generate_terrain_mesh(self):
+        # Get the DEM model file name, and acquire important info about the data
+        meshfn = mstr_datafolder + "_cache/" + self.build_dem_filename()
+        siz = os.path.getsize(meshfn)
+        dim = int(math.sqrt(siz/2))
+        assert dim*dim*2 == siz, 'Invalid file size'
+        self._demdata = numpy.fromfile(meshfn, numpy.dtype('>i2'), dim*dim).reshape((dim, dim))
+        self._demdata = self._demdata[::-1] # Invert order so that we can start from bottom left
+
+        # We want to achieve perfect stepping for each data point in the DEM.
+        demstep = round( 1 / len(self._demdata), 6)
+
+        # Generate an array which contains only the coordinates
+        self._demcoord = []
+        for r in range(0, len(self._demdata)):
+            row = []
+            for c in range(0, len(self._demdata)):
+                lat = round(self._lat + r * demstep, 6)
+                lng = round(self._lng + c * demstep, 6)
+                crd = [ lat, lng, self._demdata[r][c]]
+                #crd = [ lat, lng ]
+                row.append(crd)
+            self._demcoord.append(row)
+
+        mstr_msg("xp_scenery", "[X-Plane] Populating DSF information file")
+
+        # The complete string to write into the DSF txt file
+        dsf_str = ""
+
+        dsf_str = dsf_str + "PROPERTY sim/west " + str(int(self._lng)) + "\r\n"
+        dsf_str = dsf_str + "PROPERTY sim/east " + str((int(self._lng) + 1)) + "\r\n"
+        dsf_str = dsf_str + "PROPERTY sim/south " + str(int(self._lat)) + "\r\n"
+        dsf_str = dsf_str + "PROPERTY sim/north " + str((int(self._lat) + 1)) + "\r\n"
+        dsf_str = dsf_str + "PROPERTY sim/require_object 6/0\r\n"
+        dsf_str = dsf_str + "PROPERTY planet earth\r\n"
+        dsf_str = dsf_str + "PROPERTY sim/creation_agent Orthographic\r\n"
+        #dsf_str = dsf_str + "TERRAIN_DEF terrain_Water\r\n"
+
+        # The file to be converted into DSF later
+        meshtxt = mstr_datafolder + "_cache/mesh_"+self._latlngfld+".txt"
+
+        with open(meshtxt, 'w') as textfile:
+            textfile.write(dsf_str)
+
+        for lat in range(1, self._mlat+1):
+            for lng in range(1, self._mlng+1):
+                # Write the line only if an ortho exists of course.
+                ddsf = mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(lat) + "_" + str(lng) + ".dds"
+                if os.path.isfile(ddsf) == True:
+                    dsfstr = "TERRAIN_DEF terrain/" + self._latlngfld + "/" + str(lat) + "_" + str(lng) + ".ter\r\n"
+                    
+                    # Let's check if this tile needs water beneath
+                    needs_water = False
+                    thistile = Image.open(ddsf)
+                    tile_pix = thistile.load()
+                    for y in range(thistile.height):
+                        for x in range(thistile.width):
+                            clr = tile_pix[x,y]
+                            if clr[3] == 0:
+                                needs_water = True
+                                break
+                    
+                    if needs_water == True:
+                        dsfstr = dsfstr + "TERRAIN_DEF terrain_Water\r\n"
+
+                    with open(meshtxt, 'a') as textfile:
+                        textfile.write(dsfstr)
+        
+
+        # OK. So. Let's build the mesh.
+
+        """
+        # First, the ground water mesh
+        with open(meshtxt, 'a') as textfile:
+            textfile.write("BEGIN_PATCH 0 0.000000 -1.000000 1 5\r\n")
+
+        # Vertical row (Latitude Row)
+        for lat_r in range(0, len(self._demcoord)-2):
+
+            # Horizontal row (Longitude Column)
+            for lng_c in range(0, len(self._demcoord)-2):
+
+                # Lat/lng coordinate
+                lat_crd   = self._demcoord[lat_r][lng_c][0]
+                lat_crd_t = self._demcoord[lat_r+1][lng_c][0]
+                lng_crd   = self._demcoord[lat_r][lng_c][1]
+                lng_crd_r = self._demcoord[lat_r][lng_c+1][1]
+
+                # Coords of triangle vertices
+                # 0 - Longitude
+                # 1 - Latitude
+                # 2 - Height in m
+                t1_v1 = [ lng_crd_r, lat_crd, 0 ]
+                t1_v2 = [ lng_crd, lat_crd_t, 0 ]
+                t1_v3 = [ lng_crd_r, lat_crd_t, 0 ]
+                t2_v1 = [ lng_crd, lat_crd_t, 0 ]
+                t2_v2 = [ lng_crd_r, lat_crd, 0 ]
+                t2_v3 = [ lng_crd, lat_crd, 0 ]
+
+
+                t1_v1 = [ lng_crd_r, lat_crd, self._demcoord[lat_r][lng_c+1][2] ]
+                t1_v2 = [ lng_crd, lat_crd_t, self._demcoord[lat_r+1][lng_c][2] ]
+                t1_v3 = [ lng_crd_r, lat_crd_t, self._demcoord[lat_r+1][lng_c+1][2] ]
+                t2_v1 = [ lng_crd, lat_crd_t, self._demcoord[lat_r+1][lng_c][2] ]
+                t2_v2 = [ lng_crd_r, lat_crd, self._demcoord[lat_r][lng_c+1][2] ]
+                t2_v3 = [ lng_crd, lat_crd, self._demcoord[lat_r][lng_c][2] ]
+
+
+                # Write down the two triangles
+                t_str = ""
+                t_str = t_str + "BEGIN_PRIMITIVE 0\r\n"
+                t_str = t_str + "PATCH_VERTEX " + str(t1_v1[0]) + " " + str(t1_v1[1]) + " " + str(t1_v1[2]) + " 0.000015 0.000015\r\n"
+                t_str = t_str + "PATCH_VERTEX " + str(t1_v2[0]) + " " + str(t1_v2[1]) + " " + str(t1_v2[2]) + " 0.000015 0.000015\r\n"
+                t_str = t_str + "PATCH_VERTEX " + str(t1_v3[0]) + " " + str(t1_v3[1]) + " " + str(t1_v3[2]) + " 0.000015 0.000015\r\n"
+                t_str = t_str + "END_PRIMITIVE 0\r\n"
+                t_str = t_str + "BEGIN_PRIMITIVE 0\r\n"
+                t_str = t_str + "PATCH_VERTEX " + str(t2_v1[0]) + " " + str(t2_v1[1]) + " " + str(t2_v1[2]) + " 0.000015 0.000015\r\n"
+                t_str = t_str + "PATCH_VERTEX " + str(t2_v2[0]) + " " + str(t2_v2[1]) + " " + str(t2_v2[2]) + " 0.000015 0.000015\r\n"
+                t_str = t_str + "PATCH_VERTEX " + str(t2_v3[0]) + " " + str(t2_v3[1]) + " " + str(t2_v3[2]) + " 0.000015 0.000015\r\n"
+                t_str = t_str + "END_PRIMITIVE 0\r\n"
+
+                # Send to the file
+                with open(meshtxt, 'a') as textfile:
+                    textfile.write(t_str)
+
+                t_str = ""
+
+        # Water mesh ends
+        with open(meshtxt, 'a') as textfile:
+            textfile.write("END PATCH\r\n")
+        """
+        
+        # Current patch
+        curpatch = 0
+
+        for lat in range(1, self._mlat+1):
+            for lng in range(1, self._mlng+1):
+                
+
+                # Create the patch only if the matching ortho exists.
+                # This way we make sure that we hit the same order as the .ter files.
+                # We can also detect which lat and lng coord we are on.
+
+                #ddsf = mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/1_1.dds"
+                ddsf = mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(lat) + "_" + str(lng) + ".dds"
+                if os.path.isfile(ddsf) == True:
+                
+                    scangrid = self.find_height_scan_start_end_points([ self._lat+((lat-1)*self._vstep), self._lng+((lng-1)*mstr_zl_18) ])
+                    #sloped   = self.build_sloped_scangrid(scangrid)
+
+                    # Base coords for this ortho
+                    base_lat = self._lat + ((lat-1) * self._vstep)
+                    base_lng = self._lng + ((lng-1) * mstr_zl_18)
+
+                    # Begin a new patch
+                    mstr_msg("xp_scenery", "[X-Plane] Processing ortho patch " + str(curpatch))
+                    with open(meshtxt, 'a') as textfile:
+                        textfile.write("BEGIN_PATCH " + str(curpatch) + " 0.000000 -1.000000 1 7\r\n")
+
+                    # Step for each ortho vertex
+                    odiv = 4
+                    latstep = self._vstep/odiv
+                    lngstep = mstr_zl_18 /odiv
+                    uv_step = 1 / odiv
+                    
+                    # Height values
+                    hgt_bl = 0
+                    hgt_br = 0
+                    hgt_tr = 0
+                    hgt_tl = 0
+
+                    # Generate the ortho tile
+                    for y in range(0,odiv):
+                        for x in range(0,odiv):
+                            # Coordinates
+                            lat_b = round(base_lat + (y*latstep), 6)
+                            lat_t = round(base_lat + ((y+1)*latstep), 6)
+                            lng_l = round(base_lng + (x*lngstep), 6)
+                            lng_r = round(base_lng + ((x+1)*lngstep), 6)
+
+                            # Minimal adjustment
+                            if x == 0:
+                                lng_l = base_lng
+                            if y == 0:
+                                lat_b = base_lat
+                            if y == 3:
+                                lat_t = base_lat + self._vstep
+                            if x == 3:
+                                lng_r = base_lng + mstr_zl_18
+
+                            # Corrections, just in case
+                            if lat_b > self._lat + 1: lat_b = self._lat+1
+                            if lat_t > self._lat + 1: lat_t = self._lat+1
+                            if lng_l > self._lng + 1: lng_l = self._lng+1
+                            if lng_r > self._lng + 1: lng_r = self._lng+1
+
+                            # Height indexes
+                            hgt_bl_idx = self.find_height_for_coord([lat_b, lng_l])
+                            hgt_br_idx = self.find_height_for_coord([lat_b, lng_r])
+                            hgt_tr_idx = self.find_height_for_coord([lat_t, lng_r])
+                            hgt_tl_idx = self.find_height_for_coord([lat_t, lng_l])
+                            hgt_bl = round(self._demcoord[ hgt_bl_idx[0] ][ hgt_bl_idx[1] ][2], 6)
+                            hgt_br = round(self._demcoord[ hgt_br_idx[0] ][ hgt_br_idx[1] ][2], 6)
+                            hgt_tr = round(self._demcoord[ hgt_tr_idx[0] ][ hgt_tr_idx[1] ][2], 6)
+                            hgt_tl = round(self._demcoord[ hgt_tl_idx[0] ][ hgt_tl_idx[1] ][2], 6)
+
+                            # Coords of triangle vertices
+                            # 0 - Longitude
+                            # 1 - Latitude
+                            # 2 - Height in m
+                            t1_v1 = [ lng_r, lat_b, hgt_br ]
+                            t1_v2 = [ lng_l, lat_t, hgt_tl ]
+                            t1_v3 = [ lng_r, lat_t, hgt_tr ]
+                            t2_v1 = [ lng_l, lat_t, hgt_tl ]
+                            t2_v2 = [ lng_r, lat_b, hgt_br ]
+                            t2_v3 = [ lng_l, lat_b, hgt_bl ]
+
+                            # Write down the two triangles
+                            t_str = ""
+                            t_str = t_str + "BEGIN_PRIMITIVE 0\r\n"
+                            t_str = t_str + "PATCH_VERTEX " + str(t1_v1[0]) + " " + str(t1_v1[1]) + " " + str(t1_v1[2]) + " 0.000015 0.000015 " + str((x+1) * uv_step) + " " + str(y*uv_step) + "\r\n"
+                            t_str = t_str + "PATCH_VERTEX " + str(t1_v2[0]) + " " + str(t1_v2[1]) + " " + str(t1_v2[2]) + " 0.000015 0.000015 " + str(x * uv_step) + " " + str((y+1)*uv_step) + "\r\n"
+                            t_str = t_str + "PATCH_VERTEX " + str(t1_v3[0]) + " " + str(t1_v3[1]) + " " + str(t1_v3[2]) + " 0.000015 0.000015 " + str((x+1) * uv_step) + " " + str((y+1)*uv_step) + "\r\n"
+                            t_str = t_str + "END_PRIMITIVE 0\r\n"
+                            t_str = t_str + "BEGIN_PRIMITIVE 0\r\n"
+                            t_str = t_str + "PATCH_VERTEX " + str(t2_v1[0]) + " " + str(t2_v1[1]) + " " + str(t2_v1[2]) + " 0.000015 0.000015 " + str(x * uv_step) + " " + str((y+1)*uv_step) + "\r\n"
+                            t_str = t_str + "PATCH_VERTEX " + str(t2_v2[0]) + " " + str(t2_v2[1]) + " " + str(t2_v2[2]) + " 0.000015 0.000015 " + str((x+1) * uv_step) + " " + str(y*uv_step) + "\r\n"
+                            t_str = t_str + "PATCH_VERTEX " + str(t2_v3[0]) + " " + str(t2_v3[1]) + " " + str(t2_v3[2]) + " 0.000015 0.000015 " + str(x * uv_step) + " " + str(y*uv_step) + "\r\n"
+                            t_str = t_str + "END_PRIMITIVE 0\r\n"
+
+                            # Send to the file
+                            with open(meshtxt, 'a') as textfile:
+                                textfile.write(t_str)
+
+                            t_str = ""
+
+                    
+                    # Height value:
+                    # hgtindex = self.find_height_for_coord([lat, lng])
+                    # height = self._demcoord[hgtindex[0]][hgtindex[1]][2]
+
+                    
+                    # End this patch
+                    with open(meshtxt, 'a') as textfile:
+                        textfile.write("END PATCH\r\n")
+
+                    # Increase patch number
+                    curpatch = curpatch + 1
+
+
+                    # Let's check if this tile needs water beneath
+                    needs_water = False
+                    thistile = Image.open(ddsf)
+                    tile_pix = thistile.load()
+                    for y in range(thistile.height):
+                        for x in range(thistile.width):
+                            clr = tile_pix[x,y]
+                            if clr[3] == 0:
+                                needs_water = True
+                                break
+
+
+                    if needs_water == True:
+
+                        # Begin a new patch
+                        with open(meshtxt, 'a') as textfile:
+                            textfile.write("BEGIN_PATCH " + str(curpatch) + " 0.000000 -1.000000 1 5\r\n")
+
+                        # Generate the ortho tile
+                        for y in range(0,odiv):
+                            for x in range(0,odiv):
+                                # Coordinates
+                                lat_b = round(base_lat + (y*latstep), 6)
+                                lat_t = round(base_lat + ((y+1)*latstep), 6)
+                                lng_l = round(base_lng + (x*lngstep), 6)
+                                lng_r = round(base_lng + ((x+1)*lngstep), 6)
+
+                                # Minimal adjustment
+                                if x == 0:
+                                    lng_l = base_lng
+                                if y == 0:
+                                    lat_b = base_lat
+                                if y == 3:
+                                    lat_t = base_lat + self._vstep
+                                if x == 3:
+                                    lng_r = base_lng + mstr_zl_18
+
+                                # Corrections, just in case
+                                if lat_b > self._lat + 1: lat_b = self._lat+1
+                                if lat_t > self._lat + 1: lat_t = self._lat+1
+                                if lng_l > self._lng + 1: lng_l = self._lng+1
+                                if lng_r > self._lng + 1: lng_r = self._lng+1
+
+                                hgt_bl_idx = self.find_height_for_coord([lat_b, lng_l])
+                                hgt_br_idx = self.find_height_for_coord([lat_b, lng_r])
+                                hgt_tr_idx = self.find_height_for_coord([lat_t, lng_r])
+                                hgt_tl_idx = self.find_height_for_coord([lat_t, lng_l])
+                                hgt_bl = round(self._demcoord[ hgt_bl_idx[0] ][ hgt_bl_idx[1] ][2] - .1, 6)
+                                hgt_br = round(self._demcoord[ hgt_br_idx[0] ][ hgt_br_idx[1] ][2] - .1, 6)
+                                hgt_tr = round(self._demcoord[ hgt_tr_idx[0] ][ hgt_tr_idx[1] ][2] - .1, 6)
+                                hgt_tl = round(self._demcoord[ hgt_tl_idx[0] ][ hgt_tl_idx[1] ][2] - .1, 6)
+
+                                # Coords of triangle vertices
+                                # 0 - Longitude
+                                # 1 - Latitude
+                                # 2 - Height in m
+                                t1_v1 = [ lng_r, lat_b, hgt_br ]
+                                t1_v2 = [ lng_l, lat_t, hgt_tl ]
+                                t1_v3 = [ lng_r, lat_t, hgt_tr ]
+                                t2_v1 = [ lng_l, lat_t, hgt_tl ]
+                                t2_v2 = [ lng_r, lat_b, hgt_br ]
+                                t2_v3 = [ lng_l, lat_b, hgt_bl ]
+
+                                # Write down the two triangles
+                                t_str = ""
+                                t_str = t_str + "BEGIN_PRIMITIVE 0\r\n"
+                                t_str = t_str + "PATCH_VERTEX " + str(t1_v1[0]) + " " + str(t1_v1[1]) + " " + str(t1_v1[2]) + " 0.000015 0.000015\r\n"
+                                t_str = t_str + "PATCH_VERTEX " + str(t1_v2[0]) + " " + str(t1_v2[1]) + " " + str(t1_v2[2]) + " 0.000015 0.000015\r\n"
+                                t_str = t_str + "PATCH_VERTEX " + str(t1_v3[0]) + " " + str(t1_v3[1]) + " " + str(t1_v3[2]) + " 0.000015 0.000015\r\n"
+                                t_str = t_str + "END_PRIMITIVE 0\r\n"
+                                t_str = t_str + "BEGIN_PRIMITIVE 0\r\n"
+                                t_str = t_str + "PATCH_VERTEX " + str(t2_v1[0]) + " " + str(t2_v1[1]) + " " + str(t2_v1[2]) + " 0.000015 0.000015\r\n"
+                                t_str = t_str + "PATCH_VERTEX " + str(t2_v2[0]) + " " + str(t2_v2[1]) + " " + str(t2_v2[2]) + " 0.000015 0.000015\r\n"
+                                t_str = t_str + "PATCH_VERTEX " + str(t2_v3[0]) + " " + str(t2_v3[1]) + " " + str(t2_v3[2]) + " 0.000015 0.000015\r\n"
+                                t_str = t_str + "END_PRIMITIVE 0\r\n"
+
+                                # Send to the file
+                                with open(meshtxt, 'a') as textfile:
+                                    textfile.write(t_str)
+
+                        # End this patch
+                        with open(meshtxt, 'a') as textfile:
+                            textfile.write("END PATCH\r\n")
+
+                        # Increase patch number
+                        curpatch = curpatch + 1
+
+
+
+
+    # Find the next best matching height for a point
+    def find_height_for_coord(self, coord):
+        idx = [0,0]
+        dst = 99999
+        ste = self.find_height_scan_start_end_points(coord)
+
+        for r in range(ste[0], ste[1]+1):
+            for d in range(ste[2], ste[3]+1):
+                dist = math.dist(coord, [self._demcoord[r][d][0], self._demcoord[r][d][1]])
+                if dist < dst:
+                    dst = dist
+                    idx = [r,d]
+        return idx
+        
+
+
+    # Find the starting and end points to scan for heights in the DEM grid
+    def find_height_scan_start_end_points(self, stc):
+        startend = [0,0,0,0]
+        stp = 1 / len(self._demdata)
+
+        # Bottom
+        lt = self._lat
+        while lt < stc[0]:
+            lt = lt + stp
+            startend[0] = startend[0] + 1
+
+        # Top
+        lt = self._lat
+        while lt < stc[0]+self._vstep:
+            lt = lt+stp
+            startend[1] = startend[1] + 1
+
+        # Left
+        ln = self._lng
+        while ln < stc[1]:
+            ln = ln + stp
+            startend[2] = startend[2] + 1
+        
+        # Right
+        ln = self._lng
+        while ln < stc[1]+mstr_zl_18:
+            ln = ln + stp
+            startend[3] = startend[3] + 1
+
+        # Make sure we have everything
+        startend[0] = startend[0]-1
+        startend[1] = startend[1]+1
+        startend[2] = startend[2]-1
+        startend[3] = startend[3]+1
+
+        # Some corrections
+        if startend[0] < 0: startend[0] = 0
+        if startend[1] > len(self._demdata)-1: startend[1] = startend[1] = len(self._demdata)-1
+        if startend[2] < 0: startend[2] = 0
+        if startend[3] > len(self._demdata)-1: startend[3] = startend[3] = len(self._demdata)-1
+
+
+        """
+        t = self._lat
+        while t < startcoord[0]:
+            t = t + self._vstep
+            startend[0] = startend[0]+1
+
+        t = self._lat
+        while t < startcoord[0]+self._vstep:
+            t = t + self._vstep
+            startend[1] = startend[1]+1
+
+        t = self._lng
+        while t < startcoord[1]:
+            t = t + mstr_zl_18
+            startend[2] = startend[2]+1
+
+        t = self._lng
+        while t < startcoord[1]+mstr_zl_18:
+            t = t + mstr_zl_18
+            startend[3] = startend[3]+1
+
+        # Some corrections
+        startend[0] = startend[0]-1
+        if startend[0] < 0: startend[0] = 0
+
+        startend[1] = startend[1]+1
+        if startend[1] > len(self._demdata)-1: startend[1] = startend[1] = len(self._demdata)-1
+
+        startend[2] = startend[2]-1
+        if startend[2] < 0: startend[2] = 0
+        
+        startend[3] = startend[3]+1
+        if startend[3] > len(self._demdata)-1: startend[3] = startend[3] = len(self._demdata)-1
+        """
+
+        return startend
+
+
+    # Function to subdivide between two vectors
+    def subdivide_vectors(self, v1, v2, subdivisions):
+        #return np.linspace(v1, v2, subdivisions + 2, axis=0)  # +2 to include endpoints
+        return numpy.linspace(v1, v2, subdivisions + 2, axis=0)  # +2 to include endpoints
+
+
+    # This build a scangrid with increased resolution, extrapolated from existing points.
+    # With this we can accurately depict the height of a point a long the slope of the ground mesh.
+    def build_sloped_scangrid(self, grid):
+        
+        # Contains the data as defined by the grid passed in
+        tmp_dem = []
+
+        # Acquire original grid data
+        for l in range(grid[2], grid[3]+1):
+            row = []
+            for c in range(grid[0], grid[1]+1):
+                row.append(self._demcoord[l][c])
+            tmp_dem.append(row)
+        
+        # Subdivide the array
+        subdivisions = 10
+        result = []
+        for i in range(len(tmp_dem) - 1):
+            for j in range(len(tmp_dem[i]) - 1):
+                subdivided = self.subdivide_vectors(tmp_dem[i][j], tmp_dem[i][j + 1], subdivisions)
+                if i > 0:  # Avoid duplicating start point
+                    subdivided = subdivided[1:]
+                result.append(subdivided)
+
+        # Combine all subdivisions into one array
+        result = numpy.vstack(result)
+
+        return result
\ No newline at end of file