<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title><![CDATA[Infinity Code Forum — Tips & Tricks]]></title>
		<link>https://forum.infinity-code.com/index.php</link>
		<atom:link href="https://forum.infinity-code.com/extern.php?action=feed&amp;fid=41&amp;type=rss" rel="self" type="application/rss+xml" />
		<description><![CDATA[The most recent topics at Infinity Code Forum.]]></description>
		<lastBuildDate>Fri, 25 Aug 2023 17:48:08 +0000</lastBuildDate>
		<generator>PunBB</generator>
		<item>
			<title><![CDATA[Using Google Elevation API to generate Terrains]]></title>
			<link>https://forum.infinity-code.com/viewtopic.php?id=2158&amp;action=new</link>
			<description><![CDATA[<p>When none of the pre-installed providers have sufficient data quality for your area, you can use the Google Elevation API.</p><p>Important: Google Elevation API is not very suitable for generating areas because it cannot return elevations for an area (only for a point or path), which requires many requests. But in some cases it can be a lifesaver.<br /><strong>Don&#039;t use large values for row and columns because you might get a big bill to pay.</strong></p><p><strong>How to use it:</strong><br />1. Create a script in the Editor folder, with the contents below.<br />2. From the menu, select RWT/Using Google Elevation API.<br />3. Enter your Google API Key. Maps Elevation API must be enabled in google developer console.<br />4. Enter or get the coordinates of your area from Real World Terrain.<br />5. Specify the desired number of rows and columns of data.<br />A single request to the Elevation API can return a maximum of 512 values.<br />The total number of requests will be Col * Row / 512.<br />6. Click Download elevations. The requests are made synchronously, so it may seem like the script is hanging, but it is not and you just need to wait.<br />7. Click Intercept Getting Elevations.<br />8. Start the generation in the Real World Terrain window.<br />9. When the generation is complete, click Release Getting Elevations.</p><p>Enjoy.</p><div class="codebox"><pre><code>/*         INFINITY CODE         */
/*   https://infinity-code.com   */

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using System.Xml;
using InfinityCode.RealWorldTerrain;
using InfinityCode.RealWorldTerrain.Generators;
using InfinityCode.RealWorldTerrain.Windows;
using UnityEditor;
using UnityEngine;

namespace InfinityCode.RealWorldTerrainExamples
{
    public class UsingGoogleElevationsExample : EditorWindow
    {
        public string googleAPIKey = &quot;&quot;;
        public int rows = 22;
        public int cols = 22;

        public double leftLongitude = 37.6188;
        public double topLatitude = 55.7517;
        public double rightLongitude = 37.6528;
        public double bottomLatitude = 55.7291;
        
        private double mx1;
        private double my1;
        private double mx2;
        private double my2;

        private static double[,] elevations;

        private void DownloadElevations()
        {
            RealWorldTerrainUtils.LatLongToMercat(leftLongitude, topLatitude, out mx1, out my1);
            RealWorldTerrainUtils.LatLongToMercat(rightLongitude, bottomLatitude, out mx2, out my2);
            
            double dx = (mx2 - mx1) / (cols - 1);
            double dy = (my2 - my1) / (rows - 1);
            
            elevations = new double[rows, cols];
            List&lt;Tuple&lt;int, double, double&gt;&gt; points = new List&lt;Tuple&lt;int, double, double&gt;&gt;(512);

            for (int y = 0; y &lt; rows; y++)
            {
                double my = my1 + dy * y;
                for (int x = 0; x &lt; cols; x++)
                {
                    double mx = mx1 + dx * x;
                    double lng, lat;
                    RealWorldTerrainUtils.MercatToLatLong(mx, my, out lng, out lat);
                    int index = y * cols + x;
                    points.Add(new Tuple&lt;int, double, double&gt; (index, lng, lat));
                    if (points.Count == 512)
                    {
                        if (!DownloadElevations(points))
                        {
                            return;
                        }
                        
                        points.Clear();
                    }
                }
            }

            if (points.Count &gt; 0)
            {
                if (!DownloadElevations(points))
                {
                    return;
                }
            }
            
            byte[] bytes = new byte[rows * cols * 8];
            int i = 0;
            foreach (double el in elevations)
            {
                byte[] elBytes = BitConverter.GetBytes(el);
                Array.Copy(elBytes, 0, bytes, i, 8);
                i += 8;
            }
            
            File.WriteAllBytes(GetFilename(), bytes);
            
            EditorUtility.DisplayDialog(&quot;Success&quot;, &quot;Elevations downloaded&quot;, &quot;OK&quot;);
        }

        private bool DownloadElevations(List&lt;Tuple&lt;int, double, double&gt;&gt; points)
        {
            string polyline = EncodePolyline(points);
            string url = $&quot;https://maps.googleapis.com/maps/api/elevation/xml?locations=enc:{polyline}&amp;key={googleAPIKey}&quot;;
            
            WebClient client = new WebClient();
            string response = client.DownloadString(url);
            
            XmlDocument xml = new XmlDocument();
            xml.LoadXml(response);
            
            // Check for error
            XmlNode statusNode = xml.SelectSingleNode(&quot;//status&quot;);
            if (statusNode != null &amp;&amp; statusNode.InnerText != &quot;OK&quot;)
            {
                XmlNode errorNode = xml.SelectSingleNode(&quot;//error_message&quot;);
                EditorUtility.DisplayDialog(&quot;Error&quot;, errorNode.InnerText, &quot;OK&quot;);
                return false;
            }

            int index = 0;
            XmlNodeList results = xml.SelectNodes(&quot;//result&quot;);
            foreach (XmlNode result in results)
            {
                string text = result.SelectSingleNode(&quot;elevation&quot;).InnerText;
                double elevation = double.Parse(text, CultureInfo.InvariantCulture);
                int i = points[index].Item1;
                elevations[i / cols, i % cols] = elevation;
                index++;
            }

            return true;
        }
        
        public static string EncodePolyline(List&lt;Tuple&lt;int, double, double&gt;&gt; points)
        {
            List&lt;int&gt; encodedPoints = new List&lt;int&gt;();

            int prevLat = 0, prevLng = 0;

            foreach (var point in points)
            {
                int lat = (int)Math.Round(point.Item3 * 1e5);
                int lng = (int)Math.Round(point.Item2 * 1e5);

                int dLat = lat - prevLat;
                int dLng = lng - prevLng;

                prevLat = lat;
                prevLng = lng;

                encodedPoints.Add(EncodeValue(dLat));
                encodedPoints.Add(EncodeValue(dLng));
            }

            StringBuilder encodedPolyline = new StringBuilder();

            foreach (int value in encodedPoints)
            {
                encodedPolyline.Append(EncodeSignedNumber(value));
            }

            return encodedPolyline.ToString();
        }

        private static string EncodeSignedNumber(int num)
        {
            StringBuilder encoded = new StringBuilder();

            while (num &gt;= 0x20)
            {
                encoded.Append((char)((0x20 | (num &amp; 0x1f)) + 63));
                num &gt;&gt;= 5;
            }

            encoded.Append((char)(num + 63));

            return encoded.ToString();
        }

        private static int EncodeValue(int value)
        {
            value = value &lt; 0 ? ~(value &lt;&lt; 1) : (value &lt;&lt; 1);
            return value;
        }

        private string GetFilename()
        {
            return Path.Combine(RealWorldTerrainEditorUtils.heightmapCacheFolder, $&quot;google_{leftLongitude}_{topLatitude}_{rightLongitude}_{bottomLatitude}_{rows}_{cols}.raw&quot;);
        }

        private bool HasElevations()
        {
            return File.Exists(GetFilename());
        }

        private void LoadElevations()
        {
            if (!HasElevations()) return;
            byte[] bytes = File.ReadAllBytes(GetFilename());
            elevations = new double[rows, cols];
            int i = 0;
            for (int y = 0; y &lt; rows; y++)
            {
                for (int x = 0; x &lt; cols; x++)
                {
                    elevations[y, x] = BitConverter.ToDouble(bytes, i);
                    i += 8;
                }
            }
            
            RealWorldTerrainUtils.LatLongToMercat(leftLongitude, topLatitude, out mx1, out my1);
            RealWorldTerrainUtils.LatLongToMercat(rightLongitude, bottomLatitude, out mx2, out my2);
        }
        
        private void OnDestroy()
        {
            RealWorldTerrainElevationGenerator.OnGetElevation -= OnGetElevation;
            RealWorldTerrainElevationGenerator.OnGetElevationRange -= OnGetElevationRange;
        }

        private void OnGUI()
        {
            googleAPIKey = EditorGUILayout.TextField(&quot;Google API Key: &quot;, googleAPIKey);
            leftLongitude = EditorGUILayout.DoubleField(&quot;Left Longitude: &quot;, leftLongitude);
            topLatitude = EditorGUILayout.DoubleField(&quot;Top Latitude: &quot;, topLatitude);
            rightLongitude = EditorGUILayout.DoubleField(&quot;Right Longitude: &quot;, rightLongitude);
            bottomLatitude = EditorGUILayout.DoubleField(&quot;Bottom Latitude: &quot;, bottomLatitude);
            rows = EditorGUILayout.IntField(&quot;Rows: &quot;, rows);
            cols = EditorGUILayout.IntField(&quot;Cols: &quot;, cols);
            
            EditorGUILayout.HelpBox($&quot;Number of requests: {Mathf.CeilToInt(rows * cols / 512f)}&quot;, MessageType.Info);

            if (GUILayout.Button(&quot;1. Get Area From Real World Terrain&quot;))
            {
                if (RealWorldTerrainWindow.wnd == null) RealWorldTerrainWindow.OpenWindow(RealWorldTerrainGenerateType.full);
                
                RealWorldTerrainPrefs prefs = RealWorldTerrainWindow.prefs;
                leftLongitude = prefs.leftLongitude;
                topLatitude = prefs.topLatitude;
                rightLongitude = prefs.rightLongitude;
                bottomLatitude = prefs.bottomLatitude;
            }
            
            if (GUILayout.Button(&quot;2. Download elevations&quot;))
            {
                DownloadElevations();
            }
            
            if (GUILayout.Button(&quot;3. Intercept Getting Elevations&quot;))
            {
                if (!HasElevations())
                {
                    EditorUtility.DisplayDialog(&quot;Error&quot;, &quot;Elevations not downloaded&quot;, &quot;OK&quot;);
                }
                else
                {
                    LoadElevations();
                    RealWorldTerrainElevationGenerator.OnGetElevation -= OnGetElevation;
                    RealWorldTerrainElevationGenerator.OnGetElevation += OnGetElevation;
                    RealWorldTerrainElevationGenerator.OnGetElevationRange -= OnGetElevationRange;
                    RealWorldTerrainElevationGenerator.OnGetElevationRange += OnGetElevationRange;
                }
            }
            
            EditorGUILayout.HelpBox(&quot;4. Start Generation Using Real World Terrain Window&quot;, MessageType.Info);
            
            if (GUILayout.Button(&quot;5. Release Getting Elevations&quot;))
            {
                RealWorldTerrainElevationGenerator.OnGetElevation -= OnGetElevation;
                RealWorldTerrainElevationGenerator.OnGetElevationRange -= OnGetElevationRange;
            }
        }

        private double? OnGetElevation(double mx, double my)
        {
            if (mx &lt; mx1 || mx &gt; mx2 || my &lt; my1 || my &gt; my2) return null;
            double x = (mx - mx1) / (mx2 - mx1) * (elevations.GetLength(1) - 1);
            double y = (my - my1) / (my2 - my1) * (elevations.GetLength(0) - 1);
            int x1 = (int) x;
            int y1 = (int) y;
            int x2 = x1 + 1;
            int y2 = y1 + 1;
            
            if (x2 &gt;= elevations.GetLength(1)) x2 = elevations.GetLength(1) - 1;
            if (y2 &gt;= elevations.GetLength(0)) y2 = elevations.GetLength(0) - 1;
            
            double dx = x - x1;
            double dy = y - y1;
            double el1 = elevations[y1, x1];
            double el2 = elevations[y1, x2];
            double el3 = elevations[y2, x1];
            double el4 = elevations[y2, x2];
            double el = el1 * (1 - dx) * (1 - dy) + el2 * dx * (1 - dy) + el3 * (1 - dx) * dy + el4 * dx * dy;
            return el;
        }

        private void OnGetElevationRange(out double minElevation, out double maxElevation)
        {
            double min = double.MaxValue;
            double max = double.MinValue;
            
            for (int y = 0; y &lt; rows; y++)
            {
                for (int x = 0; x &lt; cols; x++) 
                {
                    double el = elevations[y, x];
                    if (el &lt; min) min = el;
                    if (el &gt; max) max = el;
                }
            }
            
            minElevation = min;
            maxElevation = max;
        }

        [MenuItem(&quot;RWT/Using Google Elevation API&quot;)]
        public static void OpenWindow()
        {
            GetWindow&lt;UsingGoogleElevationsExample&gt;(true, &quot;Google Elevation API Example&quot;);
        }
    }
}</code></pre></div>]]></description>
			<author><![CDATA[null@example.com (Alex Vertax)]]></author>
			<pubDate>Fri, 25 Aug 2023 17:48:08 +0000</pubDate>
			<guid>https://forum.infinity-code.com/viewtopic.php?id=2158&amp;action=new</guid>
		</item>
		<item>
			<title><![CDATA[Do not generate buildings by conditions]]></title>
			<link>https://forum.infinity-code.com/viewtopic.php?id=2118&amp;action=new</link>
			<description><![CDATA[<p>If you want to not generate buildings by some condition, here&#039;s an example of how to do it.<br />In this example, it is forbidden to generate buildings with a height less than 30 meters.</p><p>How to use this:<br />Place the script in Editor folder.<br />If you want to change the conditions, you can do this in the OnGenerateBuilding method.<br />Select RWT/Do Not Generate Buildings By Conditions, click Active and generate buildings.</p><div class="codebox"><pre><code>using System.Collections.Generic;
using System.Globalization;
using InfinityCode.RealWorldTerrain;
using InfinityCode.RealWorldTerrain.Generators;
using InfinityCode.RealWorldTerrain.OSM;
using InfinityCode.RealWorldTerrain.Windows;
using UnityEditor;
using UnityEngine;

namespace InfinityCode.RealWorldTerrainExamples
{
    /// &lt;summary&gt;
    /// Example of using the Real World Terrain API to not generate buildings under certain conditions.
    /// &lt;/summary&gt;
    public class DoNotGenerateBuildingsByConditions : EditorWindow
    {
        /// &lt;summary&gt;
        /// Will the conditions from the script be used?
        /// &lt;/summary&gt;
        private static bool active = false;
        
        /// &lt;summary&gt;
        /// Minimum building height.
        /// &lt;/summary&gt;
        private static float minHeight = 30;
        
        /// &lt;summary&gt;
        /// Determines whether to generate building based on provided conditions.
        /// &lt;/summary&gt;
        /// &lt;param name=&quot;points&quot;&gt;List of points for the building base.&lt;/param&gt;
        /// &lt;param name=&quot;way&quot;&gt;The OSM Way data for the building.&lt;/param&gt;
        /// &lt;param name=&quot;nodes&quot;&gt;The OSM Node data associated with the building.&lt;/param&gt;
        /// &lt;returns&gt;True - the building should not be generated, false - it should be generated.&lt;/returns&gt;
        private bool OnGenerateBuilding(List&lt;Vector3&gt; points, RealWorldTerrainOSMWay way, Dictionary&lt;string, RealWorldTerrainOSMNode&gt; nodes)
        {
            // Default building height.
            float baseHeight = 15;
            
            // Get the height of the building from the OSM data.
            string heightStr = way.GetTagValue(&quot;height&quot;);
            
            // If the height is not specified, try to get the number of floors.
            string levelsStr = way.GetTagValue(&quot;building:levels&quot;);
            
            // Parse the height from the string.
            RealWorldTerrainBuildingGenerator.GetHeightFromString(heightStr, ref baseHeight);
            
            // If the height is not specified, but the number of floors is specified, calculate the height based on the number of floors.
            if (string.IsNullOrEmpty(heightStr))
            {
                if (!string.IsNullOrEmpty(levelsStr))
                {
                    float h;
                    if (float.TryParse(levelsStr, NumberStyles.AllowDecimalPoint, RealWorldTerrainCultureInfo.cultureInfo, out h)) baseHeight = h * RealWorldTerrainWindow.prefs.buildingFloorHeight;
                }
                else baseHeight = RealWorldTerrainWindow.prefs.buildingFloorLimits.Random() * RealWorldTerrainWindow.prefs.buildingFloorHeight;
            }
            
            // If the height is less than the minimum, do not generate the building.
            if (baseHeight &lt; minHeight) return true;

            // Otherwise, generate the building.
            return false;
        }

        /// &lt;summary&gt;
        /// Displays the GUI controls for the editor window.
        /// &lt;/summary&gt;
        private void OnGUI()
        {
            EditorGUILayout.HelpBox(&quot;If active, buildings will not be generated under the conditions specified in the OnGenerateBuilding method.&quot;, MessageType.Info);
            
            EditorGUI.BeginChangeCheck();
            active = EditorGUILayout.Toggle(&quot;Active&quot;, active);
            if (EditorGUI.EndChangeCheck())
            {
                if (active)
                {
                    // Subscribe to the OnGenerateBuilding event.
                    RealWorldTerrainBuildingGenerator.OnGenerateBuilding -= OnGenerateBuilding;
                    RealWorldTerrainBuildingGenerator.OnGenerateBuilding += OnGenerateBuilding;
                }
                else
                {
                    // Unsubscribe from the OnGenerateBuilding event.
                    RealWorldTerrainBuildingGenerator.OnGenerateBuilding -= OnGenerateBuilding;
                }
            }
            
            minHeight = EditorGUILayout.FloatField(&quot;Min Height&quot;, minHeight);
        
            if (GUILayout.Button(&quot;Open Real World Terrain&quot;))
            {
                RealWorldTerrainWindow.OpenWindow(RealWorldTerrainGenerateType.full);
            }
        }

        /// &lt;summary&gt;
        /// Opens the window.
        /// &lt;/summary&gt;
        [MenuItem(&quot;RWT/Do Not Generate Buildings By Conditions&quot;)]
        private static void OpenWindow()
        {
            GetWindow&lt;DoNotGenerateBuildingsByConditions&gt;(false, &quot;Do Not Generate Buildings By Conditions&quot;, true);
        }
    }
}</code></pre></div>]]></description>
			<author><![CDATA[null@example.com (Alex Vertax)]]></author>
			<pubDate>Tue, 16 May 2023 10:35:17 +0000</pubDate>
			<guid>https://forum.infinity-code.com/viewtopic.php?id=2118&amp;action=new</guid>
		</item>
		<item>
			<title><![CDATA[Interception receiving elevations]]></title>
			<link>https://forum.infinity-code.com/viewtopic.php?id=1538&amp;action=new</link>
			<description><![CDATA[<p>Sometimes you want to use elevation data obtained from your own sources.<br />In this example, I will show you how to make Real World Terrain use your own elevation data.</p><p>How to use this:<br />Place the script in Editor folder.<br />Modify OnGetElevation and OnGetElevationRange methods for your source.<br />To start intercepting values, select RWT / Intercept Elevation and click Intercept.<br />After generating the terrains, click Release.</p><div class="codebox"><pre><code>using InfinityCode.RealWorldTerrain.Generators;
using UnityEditor;
using UnityEngine;

public class InterceptGetElevationExample : EditorWindow
{
    public static bool isIntercepted = false;

    private void OnGUI()
    {
        EditorGUI.BeginDisabledGroup(isIntercepted);

        if (GUILayout.Button(&quot;Intercept&quot;))
        {
            // Subscribe to OnGetElevation event, to intercept receiving elevation from the provider.
            RealWorldTerrainElevationGenerator.OnGetElevation += OnGetElevation;

            // If you want to use the range of values from the selected elevation provider, just do not subscribe to RealWorldTerrainElevationGenerator.OnGetElevationRange.
            RealWorldTerrainElevationGenerator.OnGetElevationRange += OnGetElevationRange;
            isIntercepted = true;
        }

        EditorGUI.EndDisabledGroup();

        EditorGUI.BeginDisabledGroup(!isIntercepted);

        if (GUILayout.Button(&quot;Release&quot;))
        {
            RealWorldTerrainElevationGenerator.OnGetElevation -= OnGetElevation;
            RealWorldTerrainElevationGenerator.OnGetElevationRange -= OnGetElevationRange;
            isIntercepted = false;
        }

        EditorGUI.EndDisabledGroup();
    }

    /// &lt;summary&gt;
    /// This method will be called when the RWT wants to get the range of elevation values.
    /// If you want to use the range of values from the selected elevation provider, just do not subscribe to RealWorldTerrainElevationGenerator.OnGetElevationRange.
    /// &lt;/summary&gt;
    /// &lt;param name=&quot;minEl&quot;&gt;Minimum elevation for the area (meters)&lt;/param&gt;
    /// &lt;param name=&quot;maxEl&quot;&gt;Maximum elevation for the area (meters)&lt;/param&gt;
    private void OnGetElevationRange(out double minEl, out double maxEl)
    {
        minEl = maxEl = 0;

        // Here you calculate the maximum and minimum elevation value,
        // or return the pre-calculated values.
    }

    /// &lt;summary&gt;
    /// This method will be called when the RWT wants to get the elevation value from the provider
    /// &lt;/summary&gt;
    /// &lt;param name=&quot;mx&quot;&gt;Mercator position X (0-1)&lt;/param&gt;
    /// &lt;param name=&quot;my&quot;&gt;Mercator position Y (0-1)&lt;/param&gt;
    /// &lt;returns&gt;Elevation value in meters or null (if you need to get elevation value from the provider)&lt;/returns&gt;
    private double? OnGetElevation(double mx, double my)
    {
        // Mercator position is a tile position for zoom 0.
        // https://www.maptiler.com/google-maps-coordinates-tile-bounds-projection/
        // https://docs.microsoft.com/en-us/bingmaps/articles/bing-maps-tile-system?redirectedfrom=MSDN

        // Here you calculate your elevation value in meters, and return that value.
        // Or return null if you need to get elevation value from the provider.

        // Points of interest:
        // Use RealWorldTerrainUtils.MercatToLatLong if you need to convert Mercator position to geographic coordinates.

        return null;
    }

    [MenuItem(&quot;RWT/Intercept Elevation&quot;)]
    private static void OpenWindow()
    {
        GetWindow&lt;InterceptGetElevationExample&gt;(&quot;Intercept Elevation&quot;);
    }
}</code></pre></div>]]></description>
			<author><![CDATA[null@example.com (Alex Vertax)]]></author>
			<pubDate>Tue, 09 Jun 2020 21:29:10 +0000</pubDate>
			<guid>https://forum.infinity-code.com/viewtopic.php?id=1538&amp;action=new</guid>
		</item>
		<item>
			<title><![CDATA[Tip: Convert generated EasyRoads3D from asphalt to underlying type]]></title>
			<link>https://forum.infinity-code.com/viewtopic.php?id=1449&amp;action=new</link>
			<description><![CDATA[<p>If you&#039;ve generated your road network and the dirt tracks look like asphalt then feel free to modify this script to automatically convert to the road type based on the data as described from RWT</p><p><a href="https://pauliom.com/2020/02/15/auto-convert-roads-types-from-real-world-terrain/">https://pauliom.com/2020/02/15/auto-con … d-terrain/</a></p>]]></description>
			<author><![CDATA[null@example.com (paulio)]]></author>
			<pubDate>Sat, 15 Feb 2020 10:26:50 +0000</pubDate>
			<guid>https://forum.infinity-code.com/viewtopic.php?id=1449&amp;action=new</guid>
		</item>
		<item>
			<title><![CDATA[Regeneration Automation]]></title>
			<link>https://forum.infinity-code.com/viewtopic.php?id=1447&amp;action=new</link>
			<description><![CDATA[<p><span class="postimg"><img src="https://infinity-code.com/sites/default/files/styles/full_post/public/images/news/2020.02.10%20-%20Real%20World%20Terrain%20Regeneration%20Automation.jpg?itok=RduetKZ0" alt="https://infinity-code.com/sites/default/files/styles/full_post/public/images/news/2020.02.10%20-%20Real%20World%20Terrain%20Regeneration%20Automation.jpg?itok=RduetKZ0" /></span></p><p>One of the amazing things that sets Real World Terrain apart from similar assets is the ability to automate the generation and regeneration of terrains, textures, and objects using Real World Terrain API.<br />In this example, I will show how to automate rule-based texture regeneration.<br />Place the script in the Editor folder, and run Menu / RWT / Automator.</p><div class="codebox"><pre><code>using System;
using System.Collections.Generic;
using InfinityCode.RealWorldTerrain;
using InfinityCode.RealWorldTerrain.Windows;
using UnityEditor;
using UnityEngine;

public class AutomatorExample : EditorWindow
{
    private RealWorldTerrainContainer container;
    private List&lt;Rule&gt; rules = new List&lt;Rule&gt;();
    private Vector2 scrollPosition;
    private int status = 0; // 0 - idle, 1 - execute, 2 - finalize
    private int executeIndex = -1;
    private bool finished = false;

    private void Execute()
    {
        if (container == null) return;
        if (container.terrains == null) return;
        if (rules.Count == 0) return;

        status = 1;
        executeIndex = -1;
        ExecuteNextTerrain();
    }

    private void ExecuteNextTerrain()
    {
        int ruleIndex = -1;
        Rule activeRule = null;
        RealWorldTerrainItem item = null;

        do
        {
            executeIndex++;
            if (executeIndex == container.terrainCount)
            {
                status = 2;
                Repaint();
                return;
            }

            item = container.terrains[executeIndex];

            for (int i = 0; i &lt; rules.Count; i++)
            {
                Rule rule = rules[i];
                if (rule.Contains(item) &amp;&amp; (rule.textureResolution != item.prefs.textureSize.x || !item.generateTextures))
                {
                    activeRule = rule;
                    ruleIndex = i;
                    break;
                }
            }
        } while (activeRule == null);

        finished = false;

        Debug.Log(item + &quot;, Rule - &quot; + ruleIndex);

        RealWorldTerrainWindow.OpenWindow(RealWorldTerrainGenerateType.texture, item);

        RealWorldTerrainWindow.prefs.generateTextures = true;
        RealWorldTerrainWindow.prefs.textureSize = new RealWorldTerrainVector2i(activeRule.textureResolution, activeRule.textureResolution);
        RealWorldTerrainWindow.OnCaptureCompleted += OnCaptureCompleted;
        RealWorldTerrainWindow.OnCaptureCanceled += OnCaptureCanceled;

        RealWorldTerrainWindow.StartCapture();
    }

    private void OnCaptureCanceled()
    {
        UnsubscribeFromEvents();

        status = 2;
    }

    private void OnCaptureCompleted()
    {
        UnsubscribeFromEvents();

        finished = true;
        Repaint();
    }

    private void OnGUI()
    {
        if (status == 0) OnIdleGUI();
        else if (status == 1) OnWaitGUI();
        else if (Event.current.type == EventType.Repaint)
        {
            status = 0;
            Repaint();
        }
    }

    private void OnIdleGUI()
    {
        int rulesCount = rules.Count;
        int moveUp = -1;
        int moveDown = -1;

        container = EditorGUILayout.ObjectField(&quot;RWT Container&quot;, container, typeof(RealWorldTerrainContainer), true) as RealWorldTerrainContainer;

        try
        {
            scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
        }
        catch
        {
            Debug.Log(Event.current.type);
        }
        

        for (int i = 0; i &lt; rulesCount; i++)
        {
            Rule rule = rules[i];

            EditorGUILayout.BeginHorizontal();

            EditorGUILayout.LabelField(&quot;From X&quot;, GUILayout.Width(50));
            rule.fromX = EditorGUILayout.IntField(rule.fromX);
            EditorGUILayout.LabelField(&quot;Y&quot;, GUILayout.Width(20));
            rule.fromY = EditorGUILayout.IntField(rule.fromY);

            EditorGUILayout.LabelField(&quot; To X&quot;, GUILayout.Width(40));
            rule.toX = EditorGUILayout.IntField(rule.toX);
            EditorGUILayout.LabelField(&quot;Y&quot;, GUILayout.Width(20));
            rule.toY = EditorGUILayout.IntField(rule.toY);

            EditorGUILayout.LabelField(&quot; Resolution&quot;, GUILayout.Width(80));
            rule.textureResolution = EditorGUILayout.IntField(rule.textureResolution);

            if (GUILayout.Button(&quot;Up&quot;, GUILayout.ExpandWidth(false))) moveUp = i;
            if (GUILayout.Button(&quot;Down&quot;, GUILayout.ExpandWidth(false))) moveDown = i;
            if (GUILayout.Button(&quot;X&quot;, GUILayout.ExpandWidth(false)))
            {
                rules.RemoveAt(i);
                rulesCount--;
            }

            EditorGUILayout.EndHorizontal();
        }

        EditorGUILayout.EndScrollView();

        if (moveUp &gt; 0)
        {
            Rule t = rules[moveUp - 1];
            rules[moveUp - 1] = rules[moveUp];
            rules[moveUp] = t;
        }

        if (moveDown != -1 &amp;&amp; moveDown &lt; rulesCount - 1)
        {
            Rule t = rules[moveDown + 1];
            rules[moveDown + 1] = rules[moveDown];
            rules[moveDown] = t;
        }

        if (GUILayout.Button(&quot;Add&quot;)) rules.Add(new Rule());
        if (GUILayout.Button(&quot;Execute&quot;)) Execute();
    }

    private void OnWaitGUI()
    {
        if (finished) ExecuteNextTerrain();

        Rect rect = GUILayoutUtility.GetRect(GUIContent.none, GUI.skin.box, GUILayout.Height(20), GUILayout.ExpandWidth(true));
        float progress = (float)executeIndex / (container.terrainCount - 1);
        EditorGUI.ProgressBar(rect, progress, Mathf.FloorToInt(progress * 100) + &quot; %&quot;);

        if (GUILayout.Button(&quot;Cancel&quot;))
        {
            status = 2;
            RealWorldTerrainWindow.CancelCapture();
        }
    }

    [MenuItem(&quot;RWT/Automator&quot;)]
    private static void OpenWindow()
    {
        GetWindow&lt;AutomatorExample&gt;();
    }

    private void UnsubscribeFromEvents()
    {
        RealWorldTerrainWindow.OnCaptureCompleted -= OnCaptureCompleted;
        RealWorldTerrainWindow.OnCaptureCanceled -= OnCaptureCanceled;
    }

    internal class Rule
    {
        public int fromX;
        public int fromY;
        public int toX;
        public int toY;
        public int textureResolution = 256;

        public bool Contains(RealWorldTerrainItem item)
        {
            return fromX &lt;= item.x &amp;&amp; toX &gt;= item.x &amp;&amp; fromY &lt;= item.y &amp;&amp; toY &gt;= item.y;
        }
    }
}</code></pre></div>]]></description>
			<author><![CDATA[null@example.com (Alex Vertax)]]></author>
			<pubDate>Mon, 10 Feb 2020 10:34:54 +0000</pubDate>
			<guid>https://forum.infinity-code.com/viewtopic.php?id=1447&amp;action=new</guid>
		</item>
		<item>
			<title><![CDATA[Extend features of Real World Terrain using uContext]]></title>
			<link>https://forum.infinity-code.com/viewtopic.php?id=1330&amp;action=new</link>
			<description><![CDATA[<p><div class="fancy_video_tag_player"><iframe class="youtube-player" type="text/html" width="640" height="385" src="https://www.youtube.com/embed/Fe6RwxA3dAM" frameborder="0"></iframe></div></p>]]></description>
			<author><![CDATA[null@example.com (Alex Vertax)]]></author>
			<pubDate>Fri, 09 Aug 2019 08:51:46 +0000</pubDate>
			<guid>https://forum.infinity-code.com/viewtopic.php?id=1330&amp;action=new</guid>
		</item>
		<item>
			<title><![CDATA[Sort Terrains for Landscape Builder]]></title>
			<link>https://forum.infinity-code.com/viewtopic.php?id=1315&amp;action=new</link>
			<description><![CDATA[<p><span class="postimg"><img src="http://support.infinity-code.com/uploads/976/T15XREQXQZH9.png" alt="http://support.infinity-code.com/uploads/976/T15XREQXQZH9.png" /></span></p><p>Based on a support request:<br /></p><div class="quotebox"><blockquote><p>I use Landscape Builder to post process RWT generated terrains. Not sure if you are familiar with LB but its a bit like photoshop for terrains. I can import a bunch of terrains and add additional layers like some noise, lower, raise, smooth etc.</p><p>LB needs terrains like this<br />[3][6][9]<br />[2][5][8]<br />[1][4][7]</p><p>but RWT generates them like this:<br />[7][8][9]<br />[4][5][6]<br />[1][2][3]</p><p>if its not too hard to change would it be possible to generate the terrains in that order?</p></blockquote></div><p>Maybe this will be useful for someone, so I will post the solution here (attached).<br />Import the script into Editor folder, and select &quot;Window / Infinity Code / Real World Terrain / Tools / Sort Terrains For Landscape Builder&quot;.</p>]]></description>
			<author><![CDATA[null@example.com (Alex Vertax)]]></author>
			<pubDate>Thu, 01 Aug 2019 10:53:23 +0000</pubDate>
			<guid>https://forum.infinity-code.com/viewtopic.php?id=1315&amp;action=new</guid>
		</item>
		<item>
			<title><![CDATA[How to find a route on terrains using Online Maps API]]></title>
			<link>https://forum.infinity-code.com/viewtopic.php?id=928&amp;action=new</link>
			<description><![CDATA[<p>Example of how to find a route on terrains created in Real World Terrain using Online Maps API.</p><p><div class="fancy_video_tag_player"><iframe class="youtube-player" type="text/html" width="640" height="385" src="https://www.youtube.com/embed/4i8wCejcdnA" frameborder="0"></iframe></div></p><p>Script:<br /></p><div class="codebox"><pre><code>/*     INFINITY CODE 2013-2018      */
/*   http://www.infinity-code.com   */

using InfinityCode.RealWorldTerrain;
using UnityEngine;

namespace InfinityCode.OnlineMapsExamples
{
    public class FindRouteOnTerrainUsingOnlineMaps : MonoBehaviour
    {
        /// &lt;summary&gt;
        /// Real World Terrain Container
        /// &lt;/summary&gt;
        public RealWorldTerrainContainer container;

        /// &lt;summary&gt;
        /// Material for the route, and points on terrainsMat
        /// &lt;/summary&gt;
        public Material lineRendererMaterial;

        /// &lt;summary&gt;
        /// Index of point (click)
        /// &lt;/summary&gt;
        private int pointIndex = 0;

        /// &lt;summary&gt;
        /// Coordinates of points
        /// &lt;/summary&gt;
        private Vector2[] points;

        /// &lt;summary&gt;
        /// Instances of points
        /// &lt;/summary&gt;
        private GameObject[] pointInstances;

        /// &lt;summary&gt;
        /// Instance of LineRenderer
        /// &lt;/summary&gt;
        private LineRenderer lineRenderer;

        /// &lt;summary&gt;
        /// Screen position of last press
        /// &lt;/summary&gt;
        private Vector2 pressPoint;

        /// &lt;summary&gt;
        /// This method is called when responding from the Google Directions API
        /// &lt;/summary&gt;
        /// &lt;param name=&quot;response&quot;&gt;Response string&lt;/param&gt;
        private void OnGoogleDirectionsComplete(string response)
        {
            OnlineMapsGoogleDirectionsResult result = OnlineMapsGoogleDirections.GetResult(response);
            if (result == null || result.routes.Length == 0) return;

            Vector2[] polyline = result.routes[0].overview_polyline;

            GameObject go = new GameObject(&quot;LineRenderer&quot;);
            lineRenderer = go.AddComponent&lt;LineRenderer&gt;();
            lineRenderer.sharedMaterial = lineRendererMaterial;
            lineRenderer.widthCurve = AnimationCurve.Constant(0, 1, 2);
            lineRenderer.startColor = lineRenderer.endColor = Color.blue;
            lineRenderer.positionCount = polyline.Length;

            for (int i = 0; i &lt; polyline.Length; i++)
            {
                Vector3 p;
                container.GetWorldPosition(polyline[i], out p);
                lineRenderer.SetPosition(i, p + new Vector3(0, 2, 0));
            }
        }

        /// &lt;summary&gt;
        /// Process click on terrain
        /// &lt;/summary&gt;
        private void ProcessClick()
        {
            bool success = container.GetCoordinatesUnderCursor(out points[pointIndex]);
            if (!success) return;

            if (pointIndex == 0)
            {
                if (lineRenderer != null)
                {
                    DestroyImmediate(lineRenderer.gameObject);
                    DestroyImmediate(pointInstances[0]);
                    DestroyImmediate(pointInstances[1]);
                    lineRenderer = null;
                }
            }

            GameObject go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            Vector3 p;
            container.GetWorldPosition(points[pointIndex], out p);
            go.transform.position = p;
            go.transform.localScale = new Vector3(3, 3, 3);
            go.GetComponent&lt;Renderer&gt;().sharedMaterial = lineRendererMaterial;
            pointInstances[pointIndex] = go;

            pointIndex++;
            if (pointIndex &lt; 2) return;

            pointIndex = 0;

            OnlineMapsGoogleDirections.Find(points[0], points[1]).OnComplete += OnGoogleDirectionsComplete;
        }

        private void Start()
        {
            points = new Vector2[2];
            pointInstances = new GameObject[2];
        }

        private void Update()
        {
            if (Input.GetMouseButtonDown(0)) pressPoint = Input.mousePosition;
            else if (Input.GetMouseButtonUp(0))
            {
                if ((pressPoint - (Vector2) Input.mousePosition).magnitude &lt; 10) ProcessClick();
            }
        }
    }
}</code></pre></div>]]></description>
			<author><![CDATA[null@example.com (Alex Vertax)]]></author>
			<pubDate>Wed, 31 Oct 2018 08:28:36 +0000</pubDate>
			<guid>https://forum.infinity-code.com/viewtopic.php?id=928&amp;action=new</guid>
		</item>
		<item>
			<title><![CDATA[Creating baseboards for Real World Terrain]]></title>
			<link>https://forum.infinity-code.com/viewtopic.php?id=815&amp;action=new</link>
			<description><![CDATA[<p>Example of how to generate highly detailed Gaia Stamps from the real world using Real World Terrain.</p><p><div class="fancy_video_tag_player"><iframe class="youtube-player" type="text/html" width="640" height="385" src="https://www.youtube.com/embed/fyzIXRTX8uo" frameborder="0"></iframe></div></p><p>Place the script in Editor folder and open Window / Infinity Code / Real World Terrain / Extensions / Create RWT Terrains Baseboards...</p>]]></description>
			<author><![CDATA[null@example.com (Alex Vertax)]]></author>
			<pubDate>Tue, 03 Apr 2018 20:05:30 +0000</pubDate>
			<guid>https://forum.infinity-code.com/viewtopic.php?id=815&amp;action=new</guid>
		</item>
		<item>
			<title><![CDATA[Generating Gaia stamps using Real World Terrain]]></title>
			<link>https://forum.infinity-code.com/viewtopic.php?id=807&amp;action=new</link>
			<description><![CDATA[<p><div class="fancy_video_tag_player"><iframe class="youtube-player" type="text/html" width="640" height="385" src="https://www.youtube.com/embed/yDQcbNiK8l8" frameborder="0"></iframe></div></p>]]></description>
			<author><![CDATA[null@example.com (Alex Vertax)]]></author>
			<pubDate>Sun, 25 Mar 2018 11:37:27 +0000</pubDate>
			<guid>https://forum.infinity-code.com/viewtopic.php?id=807&amp;action=new</guid>
		</item>
	</channel>
</rss>
