dx
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4156a9b5bd1a63a4ea34c9b0d1c801d8
|
||||
folderAsset: yes
|
||||
timeCreated: 1470870903
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,169 @@
|
||||
/* INFINITY CODE 2013-2019 */
|
||||
/* http://www.infinity-code.com */
|
||||
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools.Base
|
||||
{
|
||||
public class RealWorldTerrainBaseContainerTool:EditorWindow
|
||||
{
|
||||
protected static RealWorldTerrainBaseContainerTool wnd;
|
||||
|
||||
public RealWorldTerrainMonoBase item;
|
||||
protected Texture2D image;
|
||||
protected Texture2D preview;
|
||||
protected Color[] originalColors;
|
||||
protected Color[] previewColors;
|
||||
|
||||
private Vector2 scrollPosition;
|
||||
|
||||
protected int countX
|
||||
{
|
||||
get
|
||||
{
|
||||
if (item == null) return 0;
|
||||
return item is RealWorldTerrainContainer ? (item as RealWorldTerrainContainer).terrainCount.x : 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected int countY
|
||||
{
|
||||
get
|
||||
{
|
||||
if (item == null) return 0;
|
||||
return item is RealWorldTerrainContainer ? (item as RealWorldTerrainContainer).terrainCount.y : 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected RealWorldTerrainItem[] terrains
|
||||
{
|
||||
get
|
||||
{
|
||||
if (item == null) return null;
|
||||
return item is RealWorldTerrainContainer ? (item as RealWorldTerrainContainer).terrains : new[] { item as RealWorldTerrainItem };
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Apply()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual Color ApplyFilters(Color color)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
public void GetImage()
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
image = null;
|
||||
preview = null;
|
||||
OnGetEmptyImage();
|
||||
return;
|
||||
}
|
||||
image = GetImage(item, 512);
|
||||
preview = new Texture2D(image.width, image.height);
|
||||
originalColors = image.GetPixels();
|
||||
previewColors = new Color[originalColors.Length];
|
||||
|
||||
UpdatePreview();
|
||||
OnGetImageLate();
|
||||
}
|
||||
|
||||
public static Texture2D GetImage(RealWorldTerrainMonoBase target, int imageWidth)
|
||||
{
|
||||
RealWorldTerrainContainer container = target as RealWorldTerrainContainer;
|
||||
RealWorldTerrainItem item = target as RealWorldTerrainItem;
|
||||
|
||||
int cx = container != null ? container.terrainCount.x : 1;
|
||||
int cy = container != null ? container.terrainCount.y : 1;
|
||||
|
||||
RealWorldTerrainItem[] terrains = container != null ? container.terrains : new[] { item };
|
||||
|
||||
int imageHeight = Mathf.CeilToInt((float)imageWidth / cx * cy);
|
||||
Texture2D texture = new Texture2D(imageWidth, imageHeight);
|
||||
|
||||
Color[] colors = new Color[imageWidth * imageHeight];
|
||||
|
||||
for (int y = 0; y < imageHeight; y++)
|
||||
{
|
||||
float py = (float)y / imageHeight * cy;
|
||||
int ty = (int)py;
|
||||
float fy = py - ty;
|
||||
|
||||
for (int x = 0; x < imageWidth; x++)
|
||||
{
|
||||
float px = (float)x / imageWidth * cx;
|
||||
int tx = (int)px;
|
||||
float fx = px - tx;
|
||||
|
||||
int tIndex = ty * cx + tx;
|
||||
Texture2D terrainTexture = terrains[tIndex].texture;
|
||||
if (terrainTexture == null) continue;
|
||||
colors[y * imageWidth + x] = terrainTexture.GetPixelBilinear(fx, fy);
|
||||
}
|
||||
}
|
||||
|
||||
texture.SetPixels(colors);
|
||||
texture.Apply();
|
||||
return texture;
|
||||
}
|
||||
|
||||
protected virtual void OnGetImageLate()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void OnGetEmptyImage()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void OnContentGUI()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
wnd = null;
|
||||
item = null;
|
||||
image = null;
|
||||
preview = null;
|
||||
|
||||
OnDestoyLate();
|
||||
}
|
||||
|
||||
protected virtual void OnDestoyLate()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
item = EditorGUILayout.ObjectField("Target: ", item, typeof (RealWorldTerrainMonoBase), true) as RealWorldTerrainMonoBase;
|
||||
if (EditorGUI.EndChangeCheck()) GetImage();
|
||||
if (item == null) return;
|
||||
|
||||
OnContentGUI();
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
|
||||
if (GUILayout.Button("Apply")) Apply();
|
||||
}
|
||||
|
||||
protected virtual void UpdatePreview()
|
||||
{
|
||||
for (int i = 0; i < originalColors.Length; i++) previewColors[i] = ApplyFilters(originalColors[i]);
|
||||
|
||||
preview.SetPixels(previewColors);
|
||||
preview.Apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06d65ee8979c50d46bedffea7ead4951
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,130 @@
|
||||
/* INFINITY CODE 2013-2019 */
|
||||
/* http://www.infinity-code.com */
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public class RealWorldTerrainAlphamapExporter : EditorWindow
|
||||
{
|
||||
private RealWorldTerrainMonoBase target;
|
||||
private int layer;
|
||||
private int depth = 16;
|
||||
private RealWorldTerrainByteOrder order = RealWorldTerrainByteOrder.Windows;
|
||||
private GUIContent[] depthLabels;
|
||||
private int[] depthValues;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
depthLabels = new[] { new GUIContent("8"), new GUIContent("16") };
|
||||
depthValues = new[] { 8, 16 };
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
target = EditorGUILayout.ObjectField("Target: ", target, typeof(RealWorldTerrainMonoBase), true) as RealWorldTerrainMonoBase;
|
||||
|
||||
RealWorldTerrainContainer container = target as RealWorldTerrainContainer;
|
||||
RealWorldTerrainItem item = target as RealWorldTerrainItem;
|
||||
RealWorldTerrainItem[] terrains = container != null ? container.terrains : new[] { item };
|
||||
|
||||
bool disabled = target == null || target.prefs.resultType != RealWorldTerrainResultType.terrain;
|
||||
EditorGUI.BeginDisabledGroup(disabled);
|
||||
|
||||
int maxLayer = !disabled ? terrains[0].terrainData.alphamapLayers : 0;
|
||||
int minLayer = maxLayer > 0 ? 1 : 0;
|
||||
|
||||
layer = EditorGUILayout.IntSlider("Alphamap Layer", layer, minLayer, maxLayer);
|
||||
|
||||
depth = EditorGUILayout.IntPopup(new GUIContent("Depth"), depth, depthLabels, depthValues);
|
||||
order = (RealWorldTerrainByteOrder)EditorGUILayout.EnumPopup("Byte Order", order);
|
||||
|
||||
EditorGUI.BeginDisabledGroup(maxLayer == 0);
|
||||
if (GUILayout.Button("Export")) ExportAlphamap();
|
||||
|
||||
EditorGUI.EndDisabledGroup();
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
|
||||
private void ExportAlphamap()
|
||||
{
|
||||
string filename = EditorUtility.SaveFilePanel("Export RAW Alphamap", Application.dataPath, String.Format("alphamap-layer-{0}.raw", layer), "raw");
|
||||
if (string.IsNullOrEmpty(filename)) return;
|
||||
|
||||
int alphaResolution = -1;
|
||||
|
||||
RealWorldTerrainContainer container = target as RealWorldTerrainContainer;
|
||||
RealWorldTerrainItem item = target as RealWorldTerrainItem;
|
||||
|
||||
int cx = container != null ? container.terrainCount.x : 1;
|
||||
int cy = container != null ? container.terrainCount.y : 1;
|
||||
|
||||
RealWorldTerrainItem[] terrains = container != null ? container.terrains : new[] { item };
|
||||
|
||||
foreach (RealWorldTerrainItem terrain in terrains)
|
||||
{
|
||||
if (alphaResolution == -1) alphaResolution = terrain.terrainData.alphamapResolution;
|
||||
else if (alphaResolution != terrain.terrainData.alphamapResolution)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Error", "Terrains have different alphamap resolution.", "OK");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FileStream stream = new FileStream(filename, FileMode.Create);
|
||||
BinaryWriter writer = new BinaryWriter(stream);
|
||||
|
||||
int textureWidth = cx * alphaResolution;
|
||||
int coof = depth == 8 ? 1 : 2;
|
||||
|
||||
for (int y = 0; y < cy; y++)
|
||||
{
|
||||
for (int x = 0; x < cx; x++)
|
||||
{
|
||||
int tIndex = y * cx + x;
|
||||
float[,,] alpha = terrains[tIndex].terrainData.GetAlphamaps(0, 0, alphaResolution, alphaResolution);
|
||||
|
||||
for (int dy = 0; dy < alphaResolution; dy++)
|
||||
{
|
||||
float progress = ((y * cx + x) * alphaResolution + dy) / (float)(cx * cy * alphaResolution);
|
||||
EditorUtility.DisplayProgressBar("Export RAW Alphamap", Mathf.RoundToInt(progress * 100) + "%", progress);
|
||||
|
||||
int row = y * alphaResolution + dy;
|
||||
int seek = (row * textureWidth + x * alphaResolution) * coof;
|
||||
|
||||
stream.Seek(seek, SeekOrigin.Begin);
|
||||
|
||||
for (int dx = 0; dx < alphaResolution; dx++)
|
||||
{
|
||||
if (depth == 8) writer.Write((byte)Mathf.RoundToInt(alpha[dy, dx, layer - 1] * 255));
|
||||
else
|
||||
{
|
||||
short v = (short)Mathf.RoundToInt(alpha[dy, dx, layer - 1] * 65536);
|
||||
if (order == RealWorldTerrainByteOrder.Windows) writer.Write(v);
|
||||
else
|
||||
{
|
||||
writer.Write((byte)(v / 256));
|
||||
writer.Write((byte)(v % 256));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stream.Close();
|
||||
EditorUtility.ClearProgressBar();
|
||||
|
||||
EditorUtility.RevealInFinder(filename);
|
||||
}
|
||||
|
||||
public static void OpenWindow(RealWorldTerrainMonoBase target)
|
||||
{
|
||||
RealWorldTerrainAlphamapExporter wnd = GetWindow<RealWorldTerrainAlphamapExporter>(true, "Alphamap Exporter");
|
||||
wnd.target = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dfb943c58fdbf70468ca19a55b444883
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,184 @@
|
||||
/* INFINITY CODE 2013-2019 */
|
||||
/* http://www.infinity-code.com */
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public class RealWorldTerrainAlphamapImporter : EditorWindow
|
||||
{
|
||||
private RealWorldTerrainMonoBase target;
|
||||
private int layer;
|
||||
private int depth = 16;
|
||||
private RealWorldTerrainByteOrder order = RealWorldTerrainByteOrder.Windows;
|
||||
private GUIContent[] depthLabels;
|
||||
private int[] depthValues;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
depthLabels = new[] { new GUIContent("8"), new GUIContent("16") };
|
||||
depthValues = new[] { 8, 16 };
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
target = EditorGUILayout.ObjectField("Target: ", target, typeof(RealWorldTerrainMonoBase), true) as RealWorldTerrainMonoBase;
|
||||
|
||||
RealWorldTerrainContainer container = target as RealWorldTerrainContainer;
|
||||
RealWorldTerrainItem item = target as RealWorldTerrainItem;
|
||||
RealWorldTerrainItem[] terrains = container != null ? container.terrains : new[] { item };
|
||||
|
||||
bool disabled = target == null || target.prefs.resultType != RealWorldTerrainResultType.terrain;
|
||||
EditorGUI.BeginDisabledGroup(disabled);
|
||||
int maxLayer = !disabled ? terrains[0].terrainData.alphamapLayers : 0;
|
||||
int minLayer = maxLayer > 0 ? 1 : 0;
|
||||
|
||||
layer = EditorGUILayout.IntSlider("Alphamap layer", layer, minLayer, maxLayer);
|
||||
|
||||
depth = EditorGUILayout.IntPopup(new GUIContent("Depth"), depth, depthLabels, depthValues);
|
||||
order = (RealWorldTerrainByteOrder)EditorGUILayout.EnumPopup("Byte Order", order);
|
||||
|
||||
EditorGUI.BeginDisabledGroup(maxLayer == 0);
|
||||
if (GUILayout.Button("Import"))
|
||||
{
|
||||
ImportAlphamap();
|
||||
}
|
||||
|
||||
EditorGUI.EndDisabledGroup();
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
|
||||
private void ImportAlphamap()
|
||||
{
|
||||
string filename = EditorUtility.OpenFilePanel("Import RAW Alphamap", Application.dataPath, "raw");
|
||||
if (string.IsNullOrEmpty(filename)) return;
|
||||
|
||||
RealWorldTerrainContainer container = target as RealWorldTerrainContainer;
|
||||
RealWorldTerrainItem item = target as RealWorldTerrainItem;
|
||||
|
||||
int cx = container != null ? container.terrainCount.x : 1;
|
||||
int cy = container != null ? container.terrainCount.y : 1;
|
||||
|
||||
RealWorldTerrainItem[] terrains = container != null ? container.terrains : new[] { item };
|
||||
|
||||
long fileSize = new FileInfo(filename).Length;
|
||||
|
||||
if (depth == 16) fileSize /= 2;
|
||||
fileSize /= cx * cy;
|
||||
|
||||
int alphamapResolution = (int)Mathf.Sqrt(fileSize);
|
||||
if (alphamapResolution * alphamapResolution != fileSize)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Error", "Invalid file size.", "OK");
|
||||
return;
|
||||
}
|
||||
if (Mathf.ClosestPowerOfTwo(alphamapResolution) != alphamapResolution)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Error", "Invalid file size.", "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
FileStream stream = new FileStream(filename, FileMode.Open);
|
||||
|
||||
int textureWidth = cx * alphamapResolution;
|
||||
int coof = depth == 8 ? 1 : 2;
|
||||
|
||||
for (int y = 0; y < cy; y++)
|
||||
{
|
||||
for (int x = 0; x < cx; x++)
|
||||
{
|
||||
int tIndex = y * cx + x;
|
||||
float[,,] alphamap = terrains[tIndex].terrainData.GetAlphamaps(0, 0, alphamapResolution, alphamapResolution);
|
||||
int alphaCount = terrains[tIndex].terrainData.alphamapLayers;
|
||||
|
||||
for (int dy = 0; dy < alphamapResolution; dy++)
|
||||
{
|
||||
float progress = ((y * cx + x) * alphamapResolution + dy) / (float)(cx * cy * alphamapResolution);
|
||||
EditorUtility.DisplayProgressBar("Import RAW Alphamap", Mathf.RoundToInt(progress * 100) + "%", progress);
|
||||
|
||||
int row = y * alphamapResolution + dy;
|
||||
int seek = (row * textureWidth + x * alphamapResolution) * coof;
|
||||
|
||||
stream.Seek(seek, SeekOrigin.Begin);
|
||||
|
||||
for (int dx = 0; dx < alphamapResolution; dx++)
|
||||
{
|
||||
float v;
|
||||
if (depth == 8) v = alphamap[dy, dx, layer - 1] = stream.ReadByte() / 256f;
|
||||
else
|
||||
{
|
||||
int b1 = stream.ReadByte();
|
||||
int b2 = stream.ReadByte();
|
||||
|
||||
if (order == RealWorldTerrainByteOrder.Windows) v = alphamap[dy, dx, layer - 1] = (b2 * 256 + b1) / 65536f;
|
||||
else v = alphamap[dy, dx, layer - 1] = (b1 * 256 + b2) / 65536f;
|
||||
}
|
||||
|
||||
float other = 1 - v;
|
||||
|
||||
if (Math.Abs(v - 1) < 0.0001)
|
||||
{
|
||||
for (int l = 0; l < alphaCount; l++)
|
||||
{
|
||||
if (l != layer - 1)
|
||||
{
|
||||
alphamap[dy, dx, l] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float total = 0;
|
||||
for (int l = 0; l < alphaCount; l++)
|
||||
{
|
||||
if (l == layer - 1) continue;
|
||||
total += alphamap[dy, dx, l];
|
||||
}
|
||||
|
||||
if (total > 0)
|
||||
{
|
||||
float scale = other / total;
|
||||
|
||||
for (int l = 0; l < alphaCount; l++)
|
||||
{
|
||||
if (l == layer) continue;
|
||||
alphamap[dy, dx, l] *= scale;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int l = 0; l < alphaCount; l++)
|
||||
{
|
||||
if (l == layer) continue;
|
||||
alphamap[dy, dx, l] = 1f / (alphaCount - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (terrains[tIndex].terrainData.alphamapResolution != alphamapResolution)
|
||||
{
|
||||
Vector3 size = terrains[tIndex].terrainData.size;
|
||||
terrains[tIndex].terrainData.alphamapResolution = alphamapResolution;
|
||||
terrains[tIndex].terrainData.size = size;
|
||||
}
|
||||
|
||||
terrains[tIndex].terrainData.SetAlphamaps(0, 0, alphamap);
|
||||
}
|
||||
}
|
||||
|
||||
stream.Close();
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
|
||||
public static void OpenWindow(RealWorldTerrainMonoBase target)
|
||||
{
|
||||
RealWorldTerrainAlphamapImporter wnd = GetWindow<RealWorldTerrainAlphamapImporter>(true, "Alphamap Importer");
|
||||
wnd.target = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a232986d2eb1fc41bcda08bca95c118
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,132 @@
|
||||
/* INFINITY CODE */
|
||||
/* https://infinity-code.com */
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using InfinityCode.RealWorldTerrain.OSM;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public class RealWorldTerrainBuildingManager : EditorWindow
|
||||
{
|
||||
private RealWorldTerrainOSMMeta[] buildings;
|
||||
private Vector2 scrollPosition;
|
||||
private string filter;
|
||||
private FilterType filterType = FilterType.KeyAndValue;
|
||||
private RealWorldTerrainOSMMeta[] filteredBuildings;
|
||||
private int page = 0;
|
||||
|
||||
private void FilterBuildings()
|
||||
{
|
||||
if (string.IsNullOrEmpty(filter)) filteredBuildings = buildings;
|
||||
else
|
||||
{
|
||||
string f = filter.ToUpperInvariant();
|
||||
bool searchInKey = filterType != FilterType.Value;
|
||||
bool searchInValue = filterType != FilterType.Key;
|
||||
filteredBuildings = buildings.Where(b => b.ContainKeyOrValue(f, searchInKey, searchInValue)).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
UpdateBuildings();
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
EditorGUILayout.HelpBox("Filters buildings by tag (in whole or in part).", MessageType.Info);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
filter = EditorGUILayout.TextField("Filter", filter);
|
||||
filterType = (FilterType)EditorGUILayout.EnumPopup("Search in", filterType);
|
||||
|
||||
if (EditorGUI.EndChangeCheck()) FilterBuildings();
|
||||
|
||||
int pageSize = 500;
|
||||
|
||||
bool showPaginator = filteredBuildings.Length > pageSize;
|
||||
if (!showPaginator) page = 0;
|
||||
else if (page * pageSize >= filteredBuildings.Length) page = filteredBuildings.Length / pageSize;
|
||||
|
||||
int start = page * pageSize;
|
||||
int end = Mathf.Min(start + pageSize, filteredBuildings.Length);
|
||||
|
||||
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
||||
|
||||
bool hasNullItems = false;
|
||||
|
||||
for (int i = start; i < end; i++)
|
||||
{
|
||||
RealWorldTerrainOSMMeta building = filteredBuildings[i];
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
string buildingName = "";
|
||||
if (building == null)
|
||||
{
|
||||
buildingName = "Missed";
|
||||
hasNullItems = true;
|
||||
}
|
||||
else buildingName = building.name;
|
||||
GUILayout.Label(buildingName);
|
||||
if (GUILayout.Button(new GUIContent(">", "Select"), GUILayout.ExpandWidth(false)))
|
||||
{
|
||||
Selection.activeGameObject = building.gameObject;
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
|
||||
if (showPaginator)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
EditorGUI.BeginDisabledGroup(page == 0);
|
||||
if (GUILayout.Button("<")) page--;
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
EditorGUILayout.LabelField((start + 1) + "-" + end + " from " + filteredBuildings.Length);
|
||||
|
||||
EditorGUI.BeginDisabledGroup(page == filteredBuildings.Length / pageSize);
|
||||
if (GUILayout.Button(">")) page++;
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Select All"))
|
||||
{
|
||||
Selection.objects = filteredBuildings.Select(b => b.gameObject).ToArray();
|
||||
}
|
||||
|
||||
if (hasNullItems)
|
||||
{
|
||||
UpdateBuildings();
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
[MenuItem("Window/Infinity Code/Real World Terrain/Tools/Building Manager")]
|
||||
public static void OpenWindow()
|
||||
{
|
||||
GetWindow<RealWorldTerrainBuildingManager>(true, "Building Manager");
|
||||
}
|
||||
|
||||
private void UpdateBuildings()
|
||||
{
|
||||
buildings = RealWorldTerrainUtils.FindObjectsOfType<RealWorldTerrainBuilding>().Select(b => b.GetComponent<RealWorldTerrainOSMMeta>()).OrderBy(b => b.name).ToArray();
|
||||
FilterBuildings();
|
||||
}
|
||||
|
||||
public enum FilterType
|
||||
{
|
||||
KeyAndValue,
|
||||
Key,
|
||||
Value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 837c3491b47772949a78857a0b395d79
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,164 @@
|
||||
/* INFINITY CODE 2013-2019 */
|
||||
/* http://www.infinity-code.com */
|
||||
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public class RealWorldTerrainColorBalance : Base.RealWorldTerrainBaseContainerTool
|
||||
{
|
||||
private float cyanRed;
|
||||
private float magentaGreen;
|
||||
private float yellowBlue;
|
||||
|
||||
protected override void Apply()
|
||||
{
|
||||
int tick = 0;
|
||||
|
||||
int sx = countX;
|
||||
int sy = countY;
|
||||
|
||||
for (int ty = 0; ty < sy; ty++)
|
||||
{
|
||||
for (int tx = 0; tx < sx; tx++)
|
||||
{
|
||||
int tIndex = ty * sy + tx;
|
||||
|
||||
Texture2D texture = terrains[tIndex].texture;
|
||||
if (texture == null) continue;
|
||||
|
||||
Color[] colors = texture.GetPixels();
|
||||
|
||||
int cl = colors.Length;
|
||||
for (int i = 0; i < cl; i++)
|
||||
{
|
||||
tick++;
|
||||
|
||||
if (tick >= 1000)
|
||||
{
|
||||
float progress = ((ty * sx + tx) * cl + i) / (float) (sx * sy * cl);
|
||||
EditorUtility.DisplayProgressBar("Apply Color Ballance", Mathf.RoundToInt(progress * 100) + "%", progress);
|
||||
tick = 0;
|
||||
}
|
||||
|
||||
colors[i] = ApplyFilters(colors[i]);
|
||||
}
|
||||
|
||||
Texture2D newTexture = new Texture2D(texture.width, texture.height);
|
||||
newTexture.SetPixels(colors);
|
||||
newTexture.Apply();
|
||||
|
||||
string path = AssetDatabase.GetAssetPath(texture);
|
||||
File.WriteAllBytes(path, newTexture.EncodeToPNG());
|
||||
}
|
||||
}
|
||||
|
||||
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
|
||||
Close();
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
|
||||
protected override Color ApplyFilters(Color color)
|
||||
{
|
||||
float r = 0;
|
||||
float g = 0;
|
||||
float b = 0;
|
||||
|
||||
if (cyanRed > 0) r += cyanRed / 256;
|
||||
if (magentaGreen > 0) g += magentaGreen / 256;
|
||||
if (yellowBlue > 0) b += yellowBlue / 256;
|
||||
if (cyanRed < 0)
|
||||
{
|
||||
g += cyanRed / -512;
|
||||
b += cyanRed / -512;
|
||||
}
|
||||
if (magentaGreen < 0)
|
||||
{
|
||||
r += magentaGreen / -512;
|
||||
b += magentaGreen / -512;
|
||||
}
|
||||
if (yellowBlue < 0)
|
||||
{
|
||||
r += yellowBlue / -512;
|
||||
g += yellowBlue / -512;
|
||||
}
|
||||
|
||||
color.r = Mathf.Clamp01(color.r + r);
|
||||
color.g = Mathf.Clamp01(color.g + g);
|
||||
color.b = Mathf.Clamp01(color.b + b);
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
protected override void OnContentGUI()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
bool reset = false;
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
cyanRed = EditorGUILayout.Slider("Cyan / Red", cyanRed, -100, 100);
|
||||
if (GUILayout.Button("Reset", GUILayout.ExpandWidth(false)))
|
||||
{
|
||||
cyanRed = 0;
|
||||
reset = true;
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
magentaGreen = EditorGUILayout.Slider("Magenta / Green", magentaGreen, -100, 100);
|
||||
if (GUILayout.Button("Reset", GUILayout.ExpandWidth(false)))
|
||||
{
|
||||
magentaGreen = 0;
|
||||
reset = true;
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
yellowBlue = EditorGUILayout.Slider("Yellow / Blue", yellowBlue, -100, 100);
|
||||
if (GUILayout.Button("Reset", GUILayout.ExpandWidth(false)))
|
||||
{
|
||||
yellowBlue = 0;
|
||||
reset = true;
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (EditorGUI.EndChangeCheck() || reset) UpdatePreview();
|
||||
|
||||
if (image != null && preview != null)
|
||||
{
|
||||
int width = (int)position.width;
|
||||
|
||||
GUILayout.Box("", GUILayout.Height(width / 2), GUILayout.ExpandWidth(true));
|
||||
Rect lastRect = GUILayoutUtility.GetLastRect();
|
||||
|
||||
if (Event.current.type != EventType.Layout)
|
||||
{
|
||||
float imgWidth = lastRect.width / 2 - 10;
|
||||
EditorGUI.DrawPreviewTexture(new Rect(lastRect.x + 5, lastRect.y + 5, imgWidth, lastRect.height - 10), image, null, ScaleMode.ScaleToFit);
|
||||
EditorGUI.DrawPreviewTexture(new Rect(lastRect.x + 5 + lastRect.width / 2, lastRect.y + 5, imgWidth, lastRect.height - 10), preview, null, ScaleMode.ScaleToFit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void OpenWindow(RealWorldTerrainMonoBase item)
|
||||
{
|
||||
if (wnd != null) wnd.Close();
|
||||
|
||||
wnd = GetWindow<RealWorldTerrainColorBalance>(true, "Color Balance", true);
|
||||
wnd.item = item;
|
||||
wnd.GetImage();
|
||||
}
|
||||
|
||||
protected override void UpdatePreview()
|
||||
{
|
||||
for (int i = 0; i < originalColors.Length; i++) previewColors[i] = ApplyFilters(originalColors[i]);
|
||||
|
||||
preview.SetPixels(previewColors);
|
||||
preview.Apply();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94a3fa1b581867748a154b2674770e67
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,75 @@
|
||||
/* INFINITY CODE 2013-2019 */
|
||||
/* http://www.infinity-code.com */
|
||||
|
||||
using System;
|
||||
using System.Xml;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
[Serializable]
|
||||
public class RealWorldTerrainColorItem
|
||||
{
|
||||
public bool deleted;
|
||||
|
||||
private Color32 color;
|
||||
private bool expanded = true;
|
||||
private int rangeB = 10;
|
||||
private int rangeG = 255;
|
||||
private int rangeR = 10;
|
||||
|
||||
public RealWorldTerrainColorItem()
|
||||
{
|
||||
color = Color.red;
|
||||
}
|
||||
|
||||
public bool EqualWithRange(Color32 clr)
|
||||
{
|
||||
int fr = Mathf.Abs(clr.r - color.r);
|
||||
int fg = Mathf.Abs(clr.g - color.g);
|
||||
int fb = Mathf.Abs(clr.b - color.b);
|
||||
return fr < rangeR / 2f && fg < rangeG / 2f && fb < rangeB / 2f;
|
||||
}
|
||||
|
||||
public XmlNode GetNode(XmlDocument doc)
|
||||
{
|
||||
XmlElement node = doc.CreateElement("Color");
|
||||
node.SetAttribute("color", RealWorldTerrainUtils.ColorToHex(color));
|
||||
node.SetAttribute("range", rangeR + "," + rangeG + "," + rangeB);
|
||||
return node;
|
||||
}
|
||||
|
||||
public void OnGUI(int index)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
expanded = EditorGUILayout.Foldout(expanded, "Color " + index + ": ");
|
||||
|
||||
color = EditorGUILayout.ColorField(color);
|
||||
|
||||
if (GUILayout.Button(new GUIContent(RealWorldTerrainResources.deleteIcon, "Remove color"), GUIStyle.none,
|
||||
GUILayout.Width(24), GUILayout.Height(20)))
|
||||
deleted = true;
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (expanded)
|
||||
{
|
||||
rangeR = EditorGUILayout.IntSlider("Range red: ", rangeR, 0, 255);
|
||||
rangeG = EditorGUILayout.IntSlider("Range green: ", rangeG, 0, 255);
|
||||
rangeB = EditorGUILayout.IntSlider("Range blue: ", rangeB, 0, 255);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetNode(XmlElement node)
|
||||
{
|
||||
color = RealWorldTerrainUtils.HexToColor(node.GetAttribute("color"));
|
||||
string range = node.GetAttribute("range");
|
||||
string[] rs = range.Split(',');
|
||||
rangeR = int.Parse(rs[0]);
|
||||
rangeG = int.Parse(rs[1]);
|
||||
rangeB = int.Parse(rs[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e9a0f5d1db46654fb58f0ea6b77be4a
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,177 @@
|
||||
/* INFINITY CODE 2013-2019 */
|
||||
/* http://www.infinity-code.com */
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public class RealWorldTerrainColorLevels : Base.RealWorldTerrainBaseContainerTool
|
||||
{
|
||||
private Texture2D levels;
|
||||
private float minLevel = 0;
|
||||
private float maxLevel = 1;
|
||||
private float center = 0.5f;
|
||||
private float minInput = 0;
|
||||
private float maxInput = 1;
|
||||
|
||||
protected override void Apply()
|
||||
{
|
||||
int tick = 0;
|
||||
|
||||
int sx = countX;
|
||||
int sy = countY;
|
||||
|
||||
for (int ty = 0; ty < sy; ty++)
|
||||
{
|
||||
for (int tx = 0; tx < sx; tx++)
|
||||
{
|
||||
int tIndex = ty * sx + tx;
|
||||
|
||||
Texture2D texture = terrains[tIndex].texture;
|
||||
if (texture == null) continue;
|
||||
Color[] colors = texture.GetPixels();
|
||||
|
||||
int cl = colors.Length;
|
||||
|
||||
for (int i = 0; i < cl; i++)
|
||||
{
|
||||
tick++;
|
||||
|
||||
if (tick >= 1000)
|
||||
{
|
||||
float progress = ((ty * sx + tx) * cl + i) / (float)(sx * sy * cl);
|
||||
EditorUtility.DisplayProgressBar("Apply Color Levels", Mathf.RoundToInt(progress * 100) + "%", progress);
|
||||
tick = 0;
|
||||
}
|
||||
|
||||
colors[i] = ApplyFilters(colors[i]);
|
||||
}
|
||||
|
||||
Texture2D newTexture = new Texture2D(texture.width, texture.height);
|
||||
newTexture.SetPixels(colors);
|
||||
newTexture.Apply();
|
||||
|
||||
string path = AssetDatabase.GetAssetPath(texture);
|
||||
File.WriteAllBytes(path, newTexture.EncodeToPNG());
|
||||
}
|
||||
}
|
||||
|
||||
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
|
||||
Close();
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
|
||||
protected override Color ApplyFilters(Color color)
|
||||
{
|
||||
float g = color.grayscale;
|
||||
float ng = Mathf.Clamp01((g - minLevel) / (maxLevel - minLevel));
|
||||
ng = (ng + 0.5f) * (1.5f - center) - 0.5f;
|
||||
|
||||
float scale = ng / g;
|
||||
color.r = (Mathf.Clamp01(color.r * scale) - minInput) * (maxInput - minInput);
|
||||
color.g = (Mathf.Clamp01(color.g * scale) - minInput) * (maxInput - minInput);
|
||||
color.b = (Mathf.Clamp01(color.b * scale) - minInput) * (maxInput - minInput);
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
protected override void OnDestoyLate()
|
||||
{
|
||||
levels = null;
|
||||
}
|
||||
|
||||
protected override void OnContentGUI()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
GUILayout.Box(levels, GUILayout.Height(260), GUILayout.ExpandWidth(true));
|
||||
Rect lastRect = GUILayoutUtility.GetLastRect();
|
||||
|
||||
if (Event.current.type != EventType.Layout)
|
||||
{
|
||||
EditorGUI.DrawPreviewTexture(new Rect(lastRect.x + 2, lastRect.y + 2, lastRect.width - 4, lastRect.height - 4), levels, null, ScaleMode.StretchToFill);
|
||||
}
|
||||
|
||||
EditorGUILayout.MinMaxSlider(ref minLevel, ref maxLevel, 0, 1);
|
||||
center = EditorGUILayout.Slider(center, 0.01f, 0.99f);
|
||||
|
||||
EditorGUILayout.MinMaxSlider(new GUIContent(string.Format("Output Colors ({0}-{1})", Mathf.RoundToInt(minInput * 256), Mathf.RoundToInt(maxInput * 256))), ref minInput, ref maxInput, 0, 1);
|
||||
|
||||
if (EditorGUI.EndChangeCheck()) UpdatePreview();
|
||||
|
||||
if (image != null && preview != null)
|
||||
{
|
||||
int width = (int)position.width;
|
||||
|
||||
GUILayout.Box("", GUILayout.Height(width / 2), GUILayout.ExpandWidth(true));
|
||||
lastRect = GUILayoutUtility.GetLastRect();
|
||||
|
||||
if (Event.current.type != EventType.Layout)
|
||||
{
|
||||
float imgWidth = lastRect.width / 2 - 10;
|
||||
EditorGUI.DrawPreviewTexture(new Rect(lastRect.x + 5, lastRect.y + 5, imgWidth, lastRect.height - 10), image, null, ScaleMode.ScaleToFit);
|
||||
EditorGUI.DrawPreviewTexture(new Rect(lastRect.x + 5 + lastRect.width / 2, lastRect.y + 5, imgWidth, lastRect.height - 10), preview, null, ScaleMode.ScaleToFit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnGetEmptyImage()
|
||||
{
|
||||
levels = null;
|
||||
}
|
||||
|
||||
protected override void OnGetImageLate()
|
||||
{
|
||||
UpdateLevels();
|
||||
}
|
||||
|
||||
public static void OpenWindow(RealWorldTerrainMonoBase item)
|
||||
{
|
||||
if (wnd != null) wnd.Close();
|
||||
|
||||
wnd = GetWindow<RealWorldTerrainColorLevels>(true, "Color Levels", true);
|
||||
wnd.item = item;
|
||||
wnd.GetImage();
|
||||
}
|
||||
|
||||
private void UpdateLevels()
|
||||
{
|
||||
int[] lR = new int[256];
|
||||
int[] lG = new int[256];
|
||||
int[] lB = new int[256];
|
||||
int[] lGr = new int[256];
|
||||
|
||||
levels = new Texture2D(512, 256);
|
||||
for (int i = 0; i < originalColors.Length; i++)
|
||||
{
|
||||
Color32 clr = originalColors[i];
|
||||
lR[clr.r]++;
|
||||
lG[clr.g]++;
|
||||
lB[clr.b]++;
|
||||
lGr[Mathf.RoundToInt(originalColors[i].grayscale * 255)]++;
|
||||
}
|
||||
|
||||
Color[] levelColors = new Color[512 * 256];
|
||||
for (int i = 0; i < 512 * 256; i++) levelColors[i] = Color.white;
|
||||
|
||||
int maxValue = lGr.Max();
|
||||
float scale = 256f / maxValue;
|
||||
|
||||
for (int x = 0; x < 256; x++)
|
||||
{
|
||||
int ty = Mathf.RoundToInt(lGr[x] * scale);
|
||||
for (int y = 0; y < ty; y++)
|
||||
{
|
||||
levelColors[y * 512 + x * 2] = Color.black;
|
||||
levelColors[y * 512 + x * 2 + 1] = Color.black;
|
||||
}
|
||||
}
|
||||
|
||||
levels.SetPixels(levelColors);
|
||||
levels.Apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 41cc1af0145767f49b6752a43e5e961c
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,90 @@
|
||||
/* INFINITY CODE */
|
||||
/* https://infinity-code.com */
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public class RealWorldTerrainCurrentLatLon : EditorWindow
|
||||
{
|
||||
private RealWorldTerrainContainer rwt;
|
||||
private Vector3 lastCursorPosition;
|
||||
private static RealWorldTerrainCurrentLatLon wnd;
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
EditorApplication.update -= OnUpdate;
|
||||
SceneView.duringSceneGui -= OnSceneGUI;
|
||||
wnd = null;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
OnDestroy();
|
||||
wnd = this;
|
||||
|
||||
EditorApplication.update += OnUpdate;
|
||||
SceneView.duringSceneGui += OnSceneGUI;
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
rwt = (RealWorldTerrainContainer)EditorGUILayout.ObjectField("Real World Terrain", rwt, typeof(RealWorldTerrainContainer), true);
|
||||
|
||||
if (rwt == null) return;
|
||||
|
||||
SceneView view = SceneView.lastActiveSceneView;
|
||||
if (view == null) return;
|
||||
|
||||
Vector3 cp = view.camera.transform.position;
|
||||
double longitude, latitude, altitude;
|
||||
|
||||
rwt.GetCoordinatesByWorldPosition(cp, out longitude, out latitude, out altitude);
|
||||
|
||||
EditorGUILayout.LabelField("Scene camera latitude: " + latitude);
|
||||
EditorGUILayout.LabelField("Scene camera longitude: " + longitude);
|
||||
EditorGUILayout.LabelField("Scene camera altitude: " + altitude);
|
||||
|
||||
if (lastCursorPosition == Vector3.zero) return;
|
||||
|
||||
rwt.GetCoordinatesByWorldPosition(lastCursorPosition, out longitude, out latitude, out altitude);
|
||||
|
||||
EditorGUILayout.LabelField("Scene cursor latitude: " + latitude);
|
||||
EditorGUILayout.LabelField("Scene cursor longitude: " + longitude);
|
||||
EditorGUILayout.LabelField("Scene cursor altitude: " + altitude.ToString("F2") + " meters");
|
||||
}
|
||||
|
||||
private void OnSceneGUI(SceneView view)
|
||||
{
|
||||
RaycastHit hit;
|
||||
Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
|
||||
|
||||
if (Physics.Raycast(ray.origin, ray.direction, out hit)) lastCursorPosition = hit.point;
|
||||
else lastCursorPosition = Vector3.zero;
|
||||
}
|
||||
|
||||
private void OnUpdate()
|
||||
{
|
||||
Repaint();
|
||||
}
|
||||
|
||||
[MenuItem("Window/Infinity Code/Real World Terrain/Tools/Current Position")]
|
||||
public static void OpenWindow()
|
||||
{
|
||||
if (wnd != null) wnd.Close();
|
||||
|
||||
wnd = GetWindow<RealWorldTerrainCurrentLatLon>(false, "Current Position");
|
||||
wnd.rwt = RealWorldTerrainUtils.FindObjectOfType<RealWorldTerrainContainer>();
|
||||
}
|
||||
|
||||
public static void OpenWindow(RealWorldTerrainContainer container)
|
||||
{
|
||||
if (wnd != null) wnd.Close();
|
||||
|
||||
wnd = GetWindow<RealWorldTerrainCurrentLatLon>(false, "Current Position");
|
||||
wnd.rwt = container;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b3bb777dbda3da4b9de5f056e2132eb
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,111 @@
|
||||
/* INFINITY CODE 2013-2019 */
|
||||
/* http://www.infinity-code.com */
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public class RealWorldTerrainDetailmapExporter:EditorWindow
|
||||
{
|
||||
private RealWorldTerrainMonoBase target;
|
||||
private int layer;
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
target = EditorGUILayout.ObjectField("Target: ", target, typeof(RealWorldTerrainMonoBase), true) as RealWorldTerrainMonoBase;
|
||||
|
||||
RealWorldTerrainContainer container = target as RealWorldTerrainContainer;
|
||||
RealWorldTerrainItem item = target as RealWorldTerrainItem;
|
||||
RealWorldTerrainItem[] terrains = container != null ? container.terrains : new[] { item };
|
||||
|
||||
bool disabled = target == null || target.prefs.resultType != RealWorldTerrainResultType.terrain;
|
||||
EditorGUI.BeginDisabledGroup(disabled);
|
||||
int maxLayer = !disabled ? terrains[0].terrainData.detailPrototypes.Length : 0;
|
||||
int minLayer = maxLayer > 0 ? 1 : 0;
|
||||
|
||||
layer = EditorGUILayout.IntSlider("Detail layer", layer, minLayer, maxLayer);
|
||||
|
||||
EditorGUI.BeginDisabledGroup(maxLayer == 0);
|
||||
if (GUILayout.Button("Export"))
|
||||
{
|
||||
ExportDetailmap();
|
||||
}
|
||||
|
||||
EditorGUI.EndDisabledGroup();
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
|
||||
private void ExportDetailmap()
|
||||
{
|
||||
string filename = EditorUtility.SaveFilePanel("Export RAW Detail map", Application.dataPath, String.Format("detailmap-layer-{0}.raw", layer), "raw");
|
||||
if (string.IsNullOrEmpty(filename)) return;
|
||||
|
||||
int detailResolution = -1;
|
||||
|
||||
RealWorldTerrainContainer container = target as RealWorldTerrainContainer;
|
||||
RealWorldTerrainItem item = target as RealWorldTerrainItem;
|
||||
|
||||
int cx = container != null ? container.terrainCount.x : 1;
|
||||
int cy = container != null ? container.terrainCount.y : 1;
|
||||
|
||||
RealWorldTerrainItem[] terrains = container != null ? container.terrains : new[] { item };
|
||||
|
||||
foreach (RealWorldTerrainItem terrain in terrains)
|
||||
{
|
||||
if (detailResolution == -1) detailResolution = terrain.terrainData.detailResolution;
|
||||
else if (detailResolution != terrain.terrainData.detailResolution)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Error", "Terrains have different detail map resolution.", "OK");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FileStream stream = new FileStream(filename, FileMode.Create);
|
||||
|
||||
int textureWidth = cx * detailResolution;
|
||||
int textureHeight = cy * detailResolution;
|
||||
|
||||
for (int y = 0; y < cy; y++)
|
||||
{
|
||||
for (int x = 0; x < cx; x++)
|
||||
{
|
||||
int tIndex = y * cx + x;
|
||||
int[,] detailLayer = terrains[tIndex].terrainData.GetDetailLayer(0, 0, detailResolution, detailResolution, layer - 1);
|
||||
|
||||
for (int dy = 0; dy < detailResolution; dy++)
|
||||
{
|
||||
float progress = ((y * cx + x) * detailResolution + dy) / (float) (cx * cy * detailResolution);
|
||||
EditorUtility.DisplayProgressBar("Export RAW Detail map", Mathf.RoundToInt(progress * 100) + "%", progress);
|
||||
|
||||
int row = textureHeight - y * detailResolution - dy - 1;
|
||||
int seek = (row * textureWidth + x * detailResolution) * 3;
|
||||
|
||||
stream.Seek(seek, SeekOrigin.Begin);
|
||||
|
||||
for (int dx = 0; dx < detailResolution; dx++)
|
||||
{
|
||||
byte v = (byte)detailLayer[dy, dx];
|
||||
stream.WriteByte(v);
|
||||
stream.WriteByte(v);
|
||||
stream.WriteByte(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stream.Close();
|
||||
EditorUtility.ClearProgressBar();
|
||||
|
||||
EditorUtility.RevealInFinder(filename);
|
||||
}
|
||||
|
||||
public static void OpenWindow(RealWorldTerrainMonoBase target)
|
||||
{
|
||||
RealWorldTerrainDetailmapExporter wnd = GetWindow<RealWorldTerrainDetailmapExporter>(true, "Detail map Exporter");
|
||||
wnd.target = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe1418483e7671045a84921e57299e03
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,110 @@
|
||||
/* INFINITY CODE 2013-2019 */
|
||||
/* http://www.infinity-code.com */
|
||||
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public class RealWorldTerrainDetailmapImporter:EditorWindow
|
||||
{
|
||||
private RealWorldTerrainMonoBase target;
|
||||
private int layer;
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
target = EditorGUILayout.ObjectField("Target: ", target, typeof(RealWorldTerrainMonoBase), true) as RealWorldTerrainMonoBase;
|
||||
|
||||
RealWorldTerrainContainer container = target as RealWorldTerrainContainer;
|
||||
RealWorldTerrainItem item = target as RealWorldTerrainItem;
|
||||
RealWorldTerrainItem[] terrains = container != null ? container.terrains : new[] { item };
|
||||
|
||||
bool disabled = target == null || target.prefs.resultType != RealWorldTerrainResultType.terrain;
|
||||
EditorGUI.BeginDisabledGroup(disabled);
|
||||
int maxLayer = !disabled ? terrains[0].terrainData.detailPrototypes.Length : 0;
|
||||
int minLayer = maxLayer > 0 ? 1 : 0;
|
||||
|
||||
layer = EditorGUILayout.IntSlider("Detail layer", layer, minLayer, maxLayer);
|
||||
|
||||
EditorGUI.BeginDisabledGroup(maxLayer == 0);
|
||||
if (GUILayout.Button("Import"))
|
||||
{
|
||||
ImportDetailmap();
|
||||
}
|
||||
|
||||
EditorGUI.EndDisabledGroup();
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
|
||||
private void ImportDetailmap()
|
||||
{
|
||||
string filename = EditorUtility.OpenFilePanel("Import RAW Detailmap", Application.dataPath, "raw");
|
||||
if (string.IsNullOrEmpty(filename)) return;
|
||||
|
||||
int detailResolution = -1;
|
||||
|
||||
RealWorldTerrainContainer container = target as RealWorldTerrainContainer;
|
||||
RealWorldTerrainItem item = target as RealWorldTerrainItem;
|
||||
|
||||
int cx = container != null ? container.terrainCount.x : 1;
|
||||
int cy = container != null ? container.terrainCount.y : 1;
|
||||
|
||||
RealWorldTerrainItem[] terrains = container != null ? container.terrains : new[] { item };
|
||||
|
||||
foreach (RealWorldTerrainItem terrain in terrains)
|
||||
{
|
||||
if (detailResolution == -1) detailResolution = terrain.terrainData.detailResolution;
|
||||
else if (detailResolution != terrain.terrainData.detailResolution)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Error", "Terrains have different detailmap resolution.", "OK");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FileStream stream = new FileStream(filename, FileMode.Open);
|
||||
|
||||
int textureWidth = cx * detailResolution;
|
||||
int textureHeight = cy * detailResolution;
|
||||
|
||||
for (int y = 0; y < cy; y++)
|
||||
{
|
||||
for (int x = 0; x < cx; x++)
|
||||
{
|
||||
int[,] detailLayer = new int[detailResolution, detailResolution];
|
||||
|
||||
for (int dy = 0; dy < detailResolution; dy++)
|
||||
{
|
||||
float progress = ((y * cx + x) * detailResolution + dy) / (float)(cx * cy * detailResolution);
|
||||
EditorUtility.DisplayProgressBar("Import RAW Detailmap", Mathf.RoundToInt(progress * 100) + "%", progress);
|
||||
|
||||
int row = textureHeight - y * detailResolution - dy - 1;
|
||||
int seek = (row * textureWidth + x * detailResolution) * 3;
|
||||
|
||||
stream.Seek(seek, SeekOrigin.Begin);
|
||||
|
||||
for (int dx = 0; dx < detailResolution; dx++)
|
||||
{
|
||||
int r = stream.ReadByte();
|
||||
int g = stream.ReadByte();
|
||||
int b = stream.ReadByte();
|
||||
detailLayer[dy, dx] = (r + g + b) / 3;
|
||||
}
|
||||
}
|
||||
|
||||
int tIndex = y * cx + x;
|
||||
terrains[tIndex].terrainData.SetDetailLayer(0, 0, layer - 1, detailLayer);
|
||||
}
|
||||
}
|
||||
|
||||
stream.Close();
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
|
||||
public static void OpenWindow(RealWorldTerrainMonoBase target)
|
||||
{
|
||||
RealWorldTerrainDetailmapImporter wnd = GetWindow<RealWorldTerrainDetailmapImporter>(true, "Detailmap Importer");
|
||||
wnd.target = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 174724f437ad91545981721d04ec39be
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,260 @@
|
||||
/* INFINITY CODE 2013-2019 */
|
||||
/* http://www.infinity-code.com */
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public class RealWorldTerrainErosionFilter : EditorWindow
|
||||
{
|
||||
struct Neighbour
|
||||
{
|
||||
public float decline;
|
||||
public int x;
|
||||
public int y;
|
||||
}
|
||||
|
||||
private RealWorldTerrainMonoBase target;
|
||||
private int iterations = 15;
|
||||
private float rainfall = 1.0f;
|
||||
private float coneThreshold = 2.0f;
|
||||
private int flowIterations = 100;
|
||||
private Vector2 scrollPosition;
|
||||
|
||||
private Neighbour[] neighbours = new Neighbour[8];
|
||||
private float[,] heightmap;
|
||||
private float[,] watermap;
|
||||
private float[,] sedimentmap;
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
private void AddSediment(int x, int y)
|
||||
{
|
||||
float v = sedimentmap[y, x];
|
||||
float sign = Mathf.Sign(v);
|
||||
float absVal = Mathf.Abs(v);
|
||||
float threshold = 2e-4f * coneThreshold;
|
||||
|
||||
if (absVal < threshold)
|
||||
{
|
||||
heightmap[y, x] += sedimentmap[y, x];
|
||||
return;
|
||||
}
|
||||
|
||||
float radius = absVal / threshold;
|
||||
radius = Mathf.Sqrt(radius);
|
||||
int r = Mathf.CeilToInt(radius) - 1;
|
||||
|
||||
for (int oy = -r; oy <= r; oy++)
|
||||
{
|
||||
int cy = y + oy;
|
||||
if (cy < 0 || cy >= height) continue;
|
||||
|
||||
for (int ox = -r; ox <= r; ox++)
|
||||
{
|
||||
int cx = x + ox;
|
||||
if (cx < 0 || cx >= width) continue;
|
||||
float normDiff = 1 - Mathf.Sqrt(oy * oy + ox * ox) / radius;
|
||||
|
||||
if (normDiff < 0) normDiff = 0;
|
||||
else if (normDiff > 1) normDiff = 1;
|
||||
|
||||
heightmap[cy, cx] += threshold * sign * normDiff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Apply()
|
||||
{
|
||||
RealWorldTerrainContainer container = target as RealWorldTerrainContainer;
|
||||
RealWorldTerrainItem item = target as RealWorldTerrainItem;
|
||||
|
||||
int cx = container != null ? container.terrainCount.x : 1;
|
||||
int cy = container != null ? container.terrainCount.y : 1;
|
||||
|
||||
RealWorldTerrainItem[] terrains = container != null ? container.terrains : new[] { item };
|
||||
|
||||
int heightmapResolution = -1;
|
||||
|
||||
foreach (RealWorldTerrainItem terrain in terrains)
|
||||
{
|
||||
if (heightmapResolution == -1) heightmapResolution = terrain.terrainData.heightmapResolution;
|
||||
else if (heightmapResolution != terrain.terrainData.heightmapResolution)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Error", "Terrains have different heightmap resolution.", "OK");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
width = cx * heightmapResolution;
|
||||
height = cy * heightmapResolution;
|
||||
|
||||
heightmap = new float[height, width];
|
||||
sedimentmap = new float[height, width];
|
||||
watermap = new float[height, width];
|
||||
|
||||
float[,] heights;
|
||||
|
||||
for (int y = 0; y < cy; y++)
|
||||
{
|
||||
for (int x = 0; x < cx; x++)
|
||||
{
|
||||
int tIndex = y * cx + x;
|
||||
heights = terrains[tIndex].terrainData.GetHeights(0, 0, heightmapResolution, heightmapResolution);
|
||||
|
||||
for (int dy = 0; dy < heightmapResolution; dy++)
|
||||
{
|
||||
int row = y * heightmapResolution + dy;
|
||||
|
||||
for (int dx = 0; dx < heightmapResolution; dx++)
|
||||
{
|
||||
heightmap[row, x * heightmapResolution + dx] = heights[dy, dx];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!WaterErosion()) return;
|
||||
|
||||
heights = new float[heightmapResolution, heightmapResolution];
|
||||
|
||||
for (int y = 0; y < cy; y++)
|
||||
{
|
||||
for (int x = 0; x < cx; x++)
|
||||
{
|
||||
for (int dy = 0; dy < heightmapResolution; dy++)
|
||||
{
|
||||
int row = y * heightmapResolution + dy;
|
||||
|
||||
for (int dx = 0; dx < heightmapResolution; dx++)
|
||||
{
|
||||
heights[dy, dx] = heightmap[row, x * heightmapResolution + dx];
|
||||
}
|
||||
}
|
||||
|
||||
int tIndex = y * cx + x;
|
||||
terrains[tIndex].terrainData.SetHeights(0, 0, heights);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveWater(int x, int y)
|
||||
{
|
||||
if (watermap[y, x] <= 0) return;
|
||||
|
||||
int cn = 0;
|
||||
float sd = 0;
|
||||
float v = heightmap[y, x];
|
||||
|
||||
int x1 = Mathf.Max(x - 1, 0);
|
||||
int x2 = Mathf.Min(x + 2, width);
|
||||
|
||||
for (int cy = Mathf.Max(y - 1, 0); cy < Mathf.Min(y + 2, height); cy++)
|
||||
{
|
||||
for (int cx = x1; cx < x2; cx++)
|
||||
{
|
||||
if (cy == y && cx == x) continue;
|
||||
|
||||
float decline = v - heightmap[cy, cx];
|
||||
float lenCoeff = 0.714f;
|
||||
|
||||
if (decline > 0)
|
||||
{
|
||||
float d = decline * lenCoeff;
|
||||
sd += d;
|
||||
neighbours[cn].decline = d;
|
||||
neighbours[cn].x = cx;
|
||||
neighbours[cn].y = cy;
|
||||
cn++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cn > 0)
|
||||
{
|
||||
if (sd < 0.00001f) sd = 0.00001f;
|
||||
|
||||
float waterNorm = 1 / sd;
|
||||
float waterHere = watermap[y, x];
|
||||
|
||||
for (int i = 0; i < cn; i++)
|
||||
{
|
||||
Neighbour n = neighbours[i];
|
||||
float waterFlow = waterHere * n.decline * waterNorm;
|
||||
float sandAmount = 0.05f * waterFlow * n.decline;
|
||||
watermap[n.y, n.x] += waterFlow;
|
||||
sedimentmap[y, x] -= sandAmount;
|
||||
sedimentmap[n.y, n.x] += sandAmount;
|
||||
}
|
||||
}
|
||||
watermap[y, x] = 0;
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
||||
|
||||
target = EditorGUILayout.ObjectField("Target", target, typeof(RealWorldTerrainMonoBase), true) as RealWorldTerrainMonoBase;
|
||||
|
||||
iterations = EditorGUILayout.IntField("Erosion iterations", iterations);
|
||||
rainfall = EditorGUILayout.FloatField("Rain fall", rainfall);
|
||||
coneThreshold = EditorGUILayout.FloatField("Cone threshold", coneThreshold);
|
||||
flowIterations = EditorGUILayout.IntField("Flow iterations", flowIterations);
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
|
||||
if (GUILayout.Button("Apply")) Apply();
|
||||
}
|
||||
|
||||
public static void OpenWindow(RealWorldTerrainMonoBase target)
|
||||
{
|
||||
RealWorldTerrainErosionFilter wnd = GetWindow<RealWorldTerrainErosionFilter>(true, "Erosion Filter");
|
||||
wnd.target = target;
|
||||
}
|
||||
|
||||
private bool WaterErosion()
|
||||
{
|
||||
for (int i = 0; i < iterations; i++)
|
||||
{
|
||||
if (EditorUtility.DisplayCancelableProgressBar("Applying erosion", "Please wait...", (float) i / iterations))
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
watermap[y, x] = rainfall;
|
||||
sedimentmap[y, x] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < flowIterations; j++)
|
||||
{
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
MoveWater(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
AddSediment(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorUtility.ClearProgressBar();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff771e2b6cc0d9b45949373ba16854e9
|
||||
timeCreated: 1547585956
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,111 @@
|
||||
/* INFINITY CODE */
|
||||
/* https://infinity-code.com */
|
||||
|
||||
using InfinityCode.RealWorldTerrain.Webservices;
|
||||
using InfinityCode.RealWorldTerrain.Webservices.Results;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public class RealWorldTerrainGeocodingWindow : EditorWindow
|
||||
{
|
||||
private static RealWorldTerrainGeocodingWindow wnd;
|
||||
private RealWorldTerrainMonoBase target;
|
||||
|
||||
private string response;
|
||||
private Vector2 scrollPosition;
|
||||
private string address;
|
||||
private string languageCode = "en";
|
||||
private string key;
|
||||
private GameObject resultGameObject;
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
wnd = null;
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
target = EditorGUILayout.ObjectField("Container", target, typeof(RealWorldTerrainContainer), true) as RealWorldTerrainContainer;
|
||||
|
||||
address = EditorGUILayout.TextField("Location Name", address);
|
||||
key = EditorGUILayout.TextField("Google API key", key);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
languageCode = EditorGUILayout.TextField("Language Code", languageCode);
|
||||
RealWorldTerrainWindowUI.DrawHelpButton("List of Languages", "https://developers.google.com/maps/faq?hl=en#languagesupport");
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (GUILayout.Button("Search"))
|
||||
{
|
||||
RealWorldTerrainGoogleGeocoding.Find(
|
||||
new RealWorldTerrainGoogleGeocoding.GeocodingParams(address)
|
||||
{
|
||||
language = languageCode,
|
||||
key = key
|
||||
}
|
||||
).OnComplete += OnRequestComplete;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (!string.IsNullOrEmpty(response))
|
||||
{
|
||||
GUILayout.Label("Full Response: ");
|
||||
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
||||
EditorGUILayout.TextArea(response);
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRequestComplete(string response)
|
||||
{
|
||||
this.response = response;
|
||||
try
|
||||
{
|
||||
RealWorldTerrainGoogleGeocodingResult[] result = RealWorldTerrainGoogleGeocoding.GetResults(response);
|
||||
if (result.Length > 0)
|
||||
{
|
||||
Vector3 pos;
|
||||
target.GetWorldPosition(result[0].geometry_location, out pos);
|
||||
if (pos != default(Vector3))
|
||||
{
|
||||
resultGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
resultGameObject.name = "Geocoding Result";
|
||||
resultGameObject.transform.position = pos;
|
||||
resultGameObject.GetComponent<MeshRenderer>().sharedMaterial.color = Color.red;
|
||||
RealWorldTerrainGeocodingObject geocodingObject = resultGameObject.AddComponent<RealWorldTerrainGeocodingObject>();
|
||||
geocodingObject.info = result[0];
|
||||
SceneView.duringSceneGui += OnSceneGUI;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
Repaint();
|
||||
}
|
||||
|
||||
private void OnSceneGUI(SceneView view)
|
||||
{
|
||||
view.LookAt(resultGameObject.transform.position, view.camera.transform.rotation, 100);
|
||||
SceneView.duringSceneGui -= OnSceneGUI;
|
||||
}
|
||||
|
||||
[MenuItem("Window/Infinity Code/Real World Terrain/Tools/Geocoder")]
|
||||
public static void OpenWindow()
|
||||
{
|
||||
OpenWindow(null);
|
||||
}
|
||||
|
||||
public static void OpenWindow(RealWorldTerrainMonoBase target)
|
||||
{
|
||||
if (wnd != null) wnd.Close();
|
||||
|
||||
wnd = GetWindow<RealWorldTerrainGeocodingWindow>(true, "Geocoder");
|
||||
if (target == null) wnd.target = RealWorldTerrainUtils.FindObjectOfType<RealWorldTerrainContainer>();
|
||||
else wnd.target = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d60df1f6c4e69234ba43aab3c8338a3b
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,259 @@
|
||||
/* INFINITY CODE 2013-2019 */
|
||||
/* http://www.infinity-code.com */
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public class RealWorldTerrainGrassGeneratorWindow : EditorWindow
|
||||
{
|
||||
private static RealWorldTerrainGrassGeneratorWindow wnd;
|
||||
|
||||
private List<RealWorldTerrainColorItem> colors;
|
||||
private Color dryColor = new Color32(205, 188, 26, 255);
|
||||
private Color healthyColor = new Color32(67, 249, 42, 255);
|
||||
private RealWorldTerrainItem item;
|
||||
private int maxHeight = 2;
|
||||
private int maxWidth = 2;
|
||||
private int minHeight = 1;
|
||||
private int minWidth = 1;
|
||||
private float noiseSpread = 0.1f;
|
||||
private Texture2D previewTexture;
|
||||
private Vector2 scrollPosition;
|
||||
private Texture2D texture;
|
||||
|
||||
private void DrawPreview()
|
||||
{
|
||||
TerrainData tdata = item.terrainData;
|
||||
Texture2D originalTexture = item.texture;
|
||||
float[,,] alphamap = tdata.GetAlphamaps(0, 0, tdata.alphamapWidth, tdata.alphamapHeight);
|
||||
Color[] originalColors = originalTexture.GetPixels();
|
||||
int w = originalTexture.width;
|
||||
int h = originalTexture.height;
|
||||
float sw = w / (float)tdata.alphamapWidth;
|
||||
float sh = h / (float)tdata.alphamapHeight;
|
||||
int l = alphamap.GetLength(2) - 1;
|
||||
float step = 1 / (sw * sh);
|
||||
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
int fx = Mathf.FloorToInt(x / sw);
|
||||
bool isFirstX = x % sw == 0;
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
int fy = Mathf.FloorToInt(y / sh);
|
||||
bool isFirstY = y % sh == 0;
|
||||
Color clr = originalColors[x * w + y];
|
||||
if (isFirstX && isFirstY) alphamap[fx, fy, l] = 0;
|
||||
alphamap[fx, fy, l] += colors.Any(c => c.EqualWithRange(clr)) ? step : 0;
|
||||
}
|
||||
}
|
||||
tdata.SetAlphamaps(0, 0, alphamap);
|
||||
}
|
||||
|
||||
private void ExportSettings()
|
||||
{
|
||||
string path = EditorUtility.SaveFilePanel("Export settings", Application.dataPath, "GrassGeneratorSettings.xml", "xml");
|
||||
if (string.IsNullOrEmpty(path)) return;
|
||||
|
||||
XmlDocument doc = new XmlDocument();
|
||||
XmlElement firstElement = (XmlElement)doc.AppendChild(doc.CreateElement("GrassGenerator"));
|
||||
firstElement.SetAttribute("dryColor", RealWorldTerrainUtils.ColorToHex(dryColor));
|
||||
firstElement.SetAttribute("healthyColor", RealWorldTerrainUtils.ColorToHex(healthyColor));
|
||||
firstElement.SetAttribute("minWidth", minWidth.ToString());
|
||||
firstElement.SetAttribute("maxWidth", maxWidth.ToString());
|
||||
firstElement.SetAttribute("minHeight", minHeight.ToString());
|
||||
firstElement.SetAttribute("maxHeight", maxHeight.ToString());
|
||||
firstElement.SetAttribute("noiseSpread", noiseSpread.ToString());
|
||||
firstElement.SetAttribute("textureID", (texture != null) ? texture.GetInstanceID().ToString() : "-1");
|
||||
|
||||
foreach (RealWorldTerrainColorItem color in colors) firstElement.AppendChild(color.GetNode(doc));
|
||||
|
||||
doc.Save(path);
|
||||
}
|
||||
|
||||
private void GenerateGrass()
|
||||
{
|
||||
DetailPrototype prototype = new DetailPrototype
|
||||
{
|
||||
prototypeTexture = texture,
|
||||
renderMode = DetailRenderMode.GrassBillboard,
|
||||
minWidth = minWidth,
|
||||
minHeight = minHeight,
|
||||
maxHeight = maxHeight,
|
||||
maxWidth = maxWidth,
|
||||
dryColor = dryColor,
|
||||
healthyColor = healthyColor,
|
||||
noiseSpread = noiseSpread
|
||||
};
|
||||
|
||||
TerrainData tdata = item.terrainData;
|
||||
Texture2D originalTexture = item.texture;
|
||||
int[,] detailmap = new int[tdata.detailResolution, tdata.detailResolution];
|
||||
Color[] originalColors = originalTexture.GetPixels();
|
||||
int w = originalTexture.width;
|
||||
int h = originalTexture.height;
|
||||
float sw = w / (float)tdata.detailResolution;
|
||||
float sh = h / (float)tdata.detailResolution;
|
||||
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
int fx = Mathf.FloorToInt(x / sw);
|
||||
bool isFirstX = x % sw == 0;
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
int fy = Mathf.FloorToInt(y / sh);
|
||||
bool isFirstY = y % sh == 0;
|
||||
Color clr = originalColors[x * w + y];
|
||||
if (isFirstX && isFirstY) detailmap[fx, fy] = 0;
|
||||
detailmap[fx, fy] += colors.Any(c => c.EqualWithRange(clr)) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
List<DetailPrototype> prototypes = tdata.detailPrototypes.ToList();
|
||||
prototypes.Add(prototype);
|
||||
tdata.detailPrototypes = prototypes.ToArray();
|
||||
tdata.SetDetailLayer(0, 0, tdata.detailPrototypes.Length - 1, detailmap);
|
||||
}
|
||||
|
||||
private void GeneratePreview()
|
||||
{
|
||||
TerrainData tdata = item.terrainData;
|
||||
|
||||
RealWorldTerrainEditorUtils.GeneratePreviewTexture(tdata, ref previewTexture);
|
||||
|
||||
DrawPreview();
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
private void ImportSettings()
|
||||
{
|
||||
string path = EditorUtility.OpenFilePanel("Export settings", Application.dataPath, "xml");
|
||||
if (string.IsNullOrEmpty(path)) return;
|
||||
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(path);
|
||||
|
||||
XmlElement firstElement = (XmlElement)doc.FirstChild;
|
||||
if (firstElement.Name != "GrassGenerator") return;
|
||||
|
||||
dryColor = RealWorldTerrainUtils.HexToColor(firstElement.GetAttribute("dryColor"));
|
||||
healthyColor = RealWorldTerrainUtils.HexToColor(firstElement.GetAttribute("healthyColor"));
|
||||
minWidth = int.Parse(firstElement.GetAttribute("minWidth"));
|
||||
maxWidth = int.Parse(firstElement.GetAttribute("maxWidth"));
|
||||
minHeight = int.Parse(firstElement.GetAttribute("minHeight"));
|
||||
maxHeight = int.Parse(firstElement.GetAttribute("maxHeight"));
|
||||
noiseSpread = float.Parse(firstElement.GetAttribute("noiseSpread"));
|
||||
int textureID = int.Parse(firstElement.GetAttribute("textureID"));
|
||||
if (textureID != -1) texture = (Texture2D)EditorUtility.InstanceIDToObject(textureID);
|
||||
else texture = null;
|
||||
|
||||
colors = new List<RealWorldTerrainColorItem>();
|
||||
|
||||
foreach (XmlElement node in firstElement.ChildNodes)
|
||||
{
|
||||
RealWorldTerrainColorItem color = new RealWorldTerrainColorItem();
|
||||
color.SetNode(node);
|
||||
colors.Add(color);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
OnDisable();
|
||||
wnd = null;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (previewTexture != null)
|
||||
{
|
||||
List<TerrainLayer> tls = item.terrainData.terrainLayers.ToList();
|
||||
tls.RemoveAll(l => l.diffuseTexture == previewTexture);
|
||||
item.terrainData.terrainLayers = tls.ToArray();
|
||||
previewTexture = null;
|
||||
EditorUtility.UnloadUnusedAssetsImmediate();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
OnGUIToolbar();
|
||||
|
||||
item = EditorGUILayout.ObjectField("Terrain Item: ", item, typeof(RealWorldTerrainItem), true) as RealWorldTerrainItem;
|
||||
if (item == null) return;
|
||||
|
||||
OnGUIProps();
|
||||
|
||||
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
||||
for (int i = 0; i < colors.Count; i++) colors[i].OnGUI(i + 1);
|
||||
EditorGUILayout.EndScrollView();
|
||||
|
||||
colors.RemoveAll(c => c.deleted);
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
if (GUILayout.Button("Add color")) colors.Add(new RealWorldTerrainColorItem());
|
||||
if (GUILayout.Button("Generate preview")) GeneratePreview();
|
||||
if (GUILayout.Button("Remove preview") && previewTexture != null) OnDisable();
|
||||
|
||||
if (GUILayout.Button("Generate grass"))
|
||||
{
|
||||
GenerateGrass();
|
||||
Close();
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void OnGUIProps()
|
||||
{
|
||||
if (colors == null) colors = new List<RealWorldTerrainColorItem>();
|
||||
texture = EditorGUILayout.ObjectField("Grass texture: ", texture, typeof(Texture2D), true) as Texture2D;
|
||||
|
||||
minWidth = EditorGUILayout.IntField("Min width: ", minWidth);
|
||||
maxWidth = EditorGUILayout.IntField("Max width: ", maxWidth);
|
||||
minHeight = EditorGUILayout.IntField("Min height: ", minHeight);
|
||||
maxHeight = EditorGUILayout.IntField("Max height: ", maxHeight);
|
||||
noiseSpread = EditorGUILayout.FloatField("Noise spread: ", noiseSpread);
|
||||
healthyColor = EditorGUILayout.ColorField("Healthy color: ", healthyColor);
|
||||
dryColor = EditorGUILayout.ColorField("Dry color: ", dryColor);
|
||||
}
|
||||
|
||||
private void OnGUIToolbar()
|
||||
{
|
||||
GUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
|
||||
GUIStyle toolbarButtonStyle = new GUIStyle(EditorStyles.toolbarButton) { padding = new RectOffset(5, 5, 2, 2) };
|
||||
|
||||
if (GUILayout.Button(new GUIContent(RealWorldTerrainResources.openIcon, "Import settings"), toolbarButtonStyle,
|
||||
GUILayout.ExpandWidth(false)))
|
||||
ImportSettings();
|
||||
if (GUILayout.Button(new GUIContent(RealWorldTerrainResources.saveIcon, "Export settings"), toolbarButtonStyle,
|
||||
GUILayout.ExpandWidth(false)))
|
||||
ExportSettings();
|
||||
|
||||
GUILayout.Label("", EditorStyles.toolbarButton);
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public static void OpenWindow()
|
||||
{
|
||||
if (wnd != null) wnd.Close();
|
||||
|
||||
wnd = GetWindow<RealWorldTerrainGrassGeneratorWindow>("Grass RealWorldTerrainWindow", true);
|
||||
}
|
||||
|
||||
public static void OpenWindow(RealWorldTerrainItem item)
|
||||
{
|
||||
OpenWindow();
|
||||
wnd.item = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca1806edef40ac04b9f74dd29b7996c0
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,209 @@
|
||||
/* INFINITY CODE 2013-2019 */
|
||||
/* http://www.infinity-code.com */
|
||||
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public class RealWorldTerrainHUEWindow : Base.RealWorldTerrainBaseContainerTool
|
||||
{
|
||||
private int brightness;
|
||||
private int contrast;
|
||||
private int tone;
|
||||
private int saturation;
|
||||
|
||||
protected override void Apply()
|
||||
{
|
||||
int tick = 0;
|
||||
|
||||
int sx = countX;
|
||||
int sy = countY;
|
||||
|
||||
for (int ty = 0; ty < sy; ty++)
|
||||
{
|
||||
for (int tx = 0; tx < sx; tx++)
|
||||
{
|
||||
int tIndex = ty * sx + tx;
|
||||
|
||||
Texture2D texture = terrains[tIndex].texture;
|
||||
if (texture == null) continue;
|
||||
Color[] colors = texture.GetPixels();
|
||||
|
||||
int cl = colors.Length;
|
||||
for (int i = 0; i < cl; i++)
|
||||
{
|
||||
tick++;
|
||||
|
||||
if (tick >= 1000)
|
||||
{
|
||||
float progress = ((ty * sx + tx) * cl + i) / (float)(sx * sy * cl);
|
||||
EditorUtility.DisplayProgressBar("Apply Brightness, Contrast and HUE", Mathf.RoundToInt(progress * 100) + "%", progress);
|
||||
tick = 0;
|
||||
}
|
||||
|
||||
colors[i] = ApplyFilters(colors[i]);
|
||||
}
|
||||
|
||||
Texture2D newTexture = new Texture2D(texture.width, texture.height);
|
||||
newTexture.SetPixels(colors);
|
||||
newTexture.Apply();
|
||||
|
||||
string path = AssetDatabase.GetAssetPath(texture);
|
||||
File.WriteAllBytes(path, newTexture.EncodeToPNG());
|
||||
}
|
||||
}
|
||||
|
||||
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
|
||||
Close();
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
|
||||
protected override Color ApplyFilters(Color color)
|
||||
{
|
||||
if (brightness < 0) color = Color.Lerp(color, Color.black, (float)brightness / -150);
|
||||
else if (brightness > 0) color = Color.Lerp(color, Color.white, (float)brightness / 150);
|
||||
|
||||
if (contrast != 0)
|
||||
{
|
||||
float c = Mathf.Pow((contrast + 100f) / 100, 2);
|
||||
|
||||
float r = (color.r - 0.5f) * c + 0.5f;
|
||||
float g = (color.g - 0.5f) * c + 0.5f;
|
||||
float b = (color.b - 0.5f) * c + 0.5f;
|
||||
|
||||
color.r = Mathf.Clamp01(r);
|
||||
color.g = Mathf.Clamp01(g);
|
||||
color.b = Mathf.Clamp01(b);
|
||||
}
|
||||
|
||||
if (tone != 0)
|
||||
{
|
||||
float t = tone >= 0 ? tone : tone + 360;
|
||||
float rt = t / 120;
|
||||
rt = rt - (int)rt;
|
||||
|
||||
float r = color.r;
|
||||
float g = color.g;
|
||||
float b = color.b;
|
||||
|
||||
float nr, ng, nb;
|
||||
|
||||
if (t < 120)
|
||||
{
|
||||
nr = Mathf.Lerp(r, b, rt);
|
||||
ng = Mathf.Lerp(g, r, rt);
|
||||
nb = Mathf.Lerp(b, g, rt);
|
||||
}
|
||||
else if (t < 240)
|
||||
{
|
||||
nr = Mathf.Lerp(b, g, rt);
|
||||
ng = Mathf.Lerp(r, b, rt);
|
||||
nb = Mathf.Lerp(g, r, rt);
|
||||
}
|
||||
else
|
||||
{
|
||||
nr = Mathf.Lerp(g, r, rt);
|
||||
ng = Mathf.Lerp(b, g, rt);
|
||||
nb = Mathf.Lerp(r, b, rt);
|
||||
}
|
||||
|
||||
color.r = nr;
|
||||
color.g = ng;
|
||||
color.b = nb;
|
||||
}
|
||||
|
||||
if (saturation != 0)
|
||||
{
|
||||
float s = Mathf.Pow((saturation + 100f) / 100, 2);
|
||||
float averageColor = color.grayscale;
|
||||
float difR = color.r - averageColor;
|
||||
float difG = color.g - averageColor;
|
||||
float difB = color.b - averageColor;
|
||||
color.r = averageColor + difR * s;
|
||||
color.g = averageColor + difG * s;
|
||||
color.b = averageColor + difB * s;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
protected override void OnContentGUI()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
bool reset = false;
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
brightness = EditorGUILayout.IntSlider("Brightness", brightness, -150, 150);
|
||||
if (GUILayout.Button("Reset", GUILayout.ExpandWidth(false)))
|
||||
{
|
||||
brightness = 0;
|
||||
reset = true;
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
contrast = EditorGUILayout.IntSlider("Contrast", contrast, -100, 100);
|
||||
if (GUILayout.Button("Reset", GUILayout.ExpandWidth(false)))
|
||||
{
|
||||
contrast = 0;
|
||||
reset = true;
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
tone = EditorGUILayout.IntSlider("Tone", tone, -180, 180);
|
||||
if (GUILayout.Button("Reset", GUILayout.ExpandWidth(false)))
|
||||
{
|
||||
tone = 0;
|
||||
reset = true;
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
saturation = EditorGUILayout.IntSlider("Saturation", saturation, -100, 100);
|
||||
if (GUILayout.Button("Reset", GUILayout.ExpandWidth(false)))
|
||||
{
|
||||
saturation = 0;
|
||||
reset = true;
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (EditorGUI.EndChangeCheck() || reset) UpdatePreview();
|
||||
|
||||
if (image != null && preview != null)
|
||||
{
|
||||
int width = (int)position.width;
|
||||
|
||||
GUILayout.Box("", GUILayout.Height(width / 2), GUILayout.ExpandWidth(true));
|
||||
Rect lastRect = GUILayoutUtility.GetLastRect();
|
||||
|
||||
if (Event.current.type != EventType.Layout)
|
||||
{
|
||||
float imgWidth = lastRect.width / 2 - 10;
|
||||
EditorGUI.DrawPreviewTexture(new Rect(lastRect.x + 5, lastRect.y + 5, imgWidth, lastRect.height - 10), image, null, ScaleMode.ScaleToFit);
|
||||
EditorGUI.DrawPreviewTexture(new Rect(lastRect.x + 5 + lastRect.width / 2, lastRect.y + 5, imgWidth, lastRect.height - 10), preview, null, ScaleMode.ScaleToFit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdatePreview()
|
||||
{
|
||||
for (int i = 0; i < originalColors.Length; i++) previewColors[i] = ApplyFilters(originalColors[i]);
|
||||
|
||||
preview.SetPixels(previewColors);
|
||||
preview.Apply();
|
||||
}
|
||||
|
||||
public static void OpenWindow(RealWorldTerrainMonoBase item)
|
||||
{
|
||||
if (wnd != null) wnd.Close();
|
||||
|
||||
wnd = GetWindow<RealWorldTerrainHUEWindow>(true, "Brightness, Contrast and HUE", true);
|
||||
wnd.item = item;
|
||||
wnd.GetImage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bdd9aef836e10144b820eacfa7c98d8d
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,129 @@
|
||||
/* INFINITY CODE 2013-2019 */
|
||||
/* http://www.infinity-code.com */
|
||||
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public class RealWorldTerraiHeightmapExporter : EditorWindow
|
||||
{
|
||||
private RealWorldTerrainMonoBase target;
|
||||
private int depth = 16;
|
||||
private RealWorldTerrainByteOrder order = RealWorldTerrainByteOrder.Windows;
|
||||
private GUIContent[] depthLabels;
|
||||
private int[] depthValues;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
depthLabels = new[] {new GUIContent("8"), new GUIContent("16")};
|
||||
depthValues = new[] { 8, 16 };
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
target = EditorGUILayout.ObjectField("Target", target, typeof(RealWorldTerrainMonoBase), true) as RealWorldTerrainMonoBase;
|
||||
depth = EditorGUILayout.IntPopup(new GUIContent("Depth"), depth, depthLabels, depthValues);
|
||||
order = (RealWorldTerrainByteOrder) EditorGUILayout.EnumPopup("Byte Order", order);
|
||||
bool disabled = target == null || target.prefs.resultType != RealWorldTerrainResultType.terrain;
|
||||
EditorGUI.BeginDisabledGroup(disabled);
|
||||
if (GUILayout.Button("Export")) ExportHeightmap();
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
RealWorldTerrainContainer container = target as RealWorldTerrainContainer;
|
||||
int cx = container != null ? container.terrainCount.x : 1;
|
||||
int cy = container != null ? container.terrainCount.y : 1;
|
||||
|
||||
RealWorldTerrainItem item = target as RealWorldTerrainItem;
|
||||
RealWorldTerrainItem[] terrains = container != null ? container.terrains : new[] { item };
|
||||
|
||||
int heightmapResolution = terrains[0].terrainData.heightmapResolution;
|
||||
|
||||
int textureWidth = cx * heightmapResolution;
|
||||
int textureHeight = cy * heightmapResolution;
|
||||
EditorGUILayout.HelpBox("Width: " + textureWidth + "px, height: " + textureHeight + "px, channel: 1", MessageType.Info);
|
||||
}
|
||||
}
|
||||
|
||||
private void ExportHeightmap()
|
||||
{
|
||||
string filename = EditorUtility.SaveFilePanel("Export RAW Heightmap", Application.dataPath, "heightmap.raw", "raw");
|
||||
if (string.IsNullOrEmpty(filename)) return;
|
||||
|
||||
int heightmapResolution = -1;
|
||||
|
||||
RealWorldTerrainContainer container = target as RealWorldTerrainContainer;
|
||||
RealWorldTerrainItem item = target as RealWorldTerrainItem;
|
||||
|
||||
int cx = container != null ? container.terrainCount.x : 1;
|
||||
int cy = container != null ? container.terrainCount.y : 1;
|
||||
|
||||
RealWorldTerrainItem[] terrains = container != null ? container.terrains : new[] { item };
|
||||
|
||||
foreach (RealWorldTerrainItem terrain in terrains)
|
||||
{
|
||||
if (heightmapResolution == -1) heightmapResolution = terrain.terrainData.heightmapResolution;
|
||||
else if (heightmapResolution != terrain.terrainData.heightmapResolution)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Error", "Terrains have different heightmap resolution.", "OK");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FileStream stream = new FileStream(filename, FileMode.Create);
|
||||
BinaryWriter writer = new BinaryWriter(stream);
|
||||
|
||||
int textureWidth = cx * heightmapResolution;
|
||||
int coof = depth == 8 ? 1 : 2;
|
||||
|
||||
for (int y = 0; y < cy; y++)
|
||||
{
|
||||
for (int x = 0; x < cx; x++)
|
||||
{
|
||||
int tIndex = y * cx + x;
|
||||
float[,] heightmap = terrains[tIndex].terrainData.GetHeights(0, 0, heightmapResolution, heightmapResolution);
|
||||
|
||||
for (int dy = 0; dy < heightmapResolution; dy++)
|
||||
{
|
||||
float progress = (tIndex * heightmapResolution + dy) / (float)(cx * cy * heightmapResolution);
|
||||
EditorUtility.DisplayProgressBar("Export RAW Heightmap", Mathf.RoundToInt(progress * 100) + "%", progress);
|
||||
|
||||
int row = cy * heightmapResolution - (y * heightmapResolution + dy) - 1;
|
||||
int seek = (row * textureWidth + x * heightmapResolution) * coof;
|
||||
|
||||
stream.Seek(seek, SeekOrigin.Begin);
|
||||
|
||||
for (int dx = 0; dx < heightmapResolution; dx++)
|
||||
{
|
||||
if (depth == 8) writer.Write((byte) Mathf.RoundToInt(heightmap[dy, dx] * 255));
|
||||
else
|
||||
{
|
||||
short v = (short)Mathf.RoundToInt(heightmap[dy, dx] * 65536);
|
||||
if (order == RealWorldTerrainByteOrder.Windows) writer.Write(v);
|
||||
else
|
||||
{
|
||||
writer.Write((byte)(v / 256));
|
||||
writer.Write((byte)(v % 256));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stream.Close();
|
||||
EditorUtility.ClearProgressBar();
|
||||
|
||||
EditorUtility.RevealInFinder(filename);
|
||||
}
|
||||
|
||||
public static void OpenWindow(RealWorldTerrainMonoBase target)
|
||||
{
|
||||
RealWorldTerraiHeightmapExporter wnd = GetWindow<RealWorldTerraiHeightmapExporter>(true, "Heightmap Exporter");
|
||||
wnd.target = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d284ef9aabf0c25429ec762b595c58fa
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,125 @@
|
||||
/* INFINITY CODE 2013-2019 */
|
||||
/* http://www.infinity-code.com */
|
||||
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public class RealWorldTerrainHeightmapImporter : EditorWindow
|
||||
{
|
||||
private RealWorldTerrainMonoBase target;
|
||||
private int depth = 16;
|
||||
private RealWorldTerrainByteOrder order = RealWorldTerrainByteOrder.Windows;
|
||||
private GUIContent[] depthLabels;
|
||||
private int[] depthValues;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
depthLabels = new[] { new GUIContent("8"), new GUIContent("16") };
|
||||
depthValues = new[] { 8, 16 };
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
target = EditorGUILayout.ObjectField("Target: ", target, typeof(RealWorldTerrainMonoBase), true) as RealWorldTerrainMonoBase;
|
||||
|
||||
depth = EditorGUILayout.IntPopup(new GUIContent("Depth"), depth, depthLabels, depthValues);
|
||||
order = (RealWorldTerrainByteOrder)EditorGUILayout.EnumPopup("Byte Order", order);
|
||||
|
||||
bool disabled = target == null || target.prefs.resultType != RealWorldTerrainResultType.terrain;
|
||||
|
||||
EditorGUI.BeginDisabledGroup(disabled);
|
||||
if (GUILayout.Button("Import")) ImportHeightmap();
|
||||
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
|
||||
private void ImportHeightmap()
|
||||
{
|
||||
string filename = EditorUtility.OpenFilePanel("Import RAW Heightmap", Application.dataPath, "raw");
|
||||
if (string.IsNullOrEmpty(filename)) return;
|
||||
|
||||
RealWorldTerrainContainer container = target as RealWorldTerrainContainer;
|
||||
RealWorldTerrainItem item = target as RealWorldTerrainItem;
|
||||
|
||||
int cx = container != null ? container.terrainCount.x : 1;
|
||||
int cy = container != null ? container.terrainCount.y : 1;
|
||||
|
||||
RealWorldTerrainItem[] terrains = container != null ? container.terrains : new[] { item };
|
||||
|
||||
long fileSize = new FileInfo(filename).Length;
|
||||
|
||||
if (depth == 16) fileSize /= 2;
|
||||
fileSize /= cx * cy;
|
||||
|
||||
int heightmapResolution = (int)Mathf.Sqrt(fileSize);
|
||||
if (heightmapResolution * heightmapResolution != fileSize)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Error", "Invalid file size.", "OK");
|
||||
return;
|
||||
}
|
||||
if (Mathf.ClosestPowerOfTwo(heightmapResolution) != heightmapResolution - 1)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Error", "Invalid file size.", "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
int textureWidth = cx * heightmapResolution;
|
||||
int coof = depth == 8 ? 1 : 2;
|
||||
|
||||
FileStream stream = new FileStream(filename, FileMode.Open);
|
||||
|
||||
for (int y = 0; y < cy; y++)
|
||||
{
|
||||
for (int x = 0; x < cx; x++)
|
||||
{
|
||||
float[,] heightmap = new float[heightmapResolution, heightmapResolution];
|
||||
|
||||
for (int dy = 0; dy < heightmapResolution; dy++)
|
||||
{
|
||||
float progress = ((y * cx + x) * heightmapResolution + dy) / (float)(cx * cy * heightmapResolution);
|
||||
EditorUtility.DisplayProgressBar("Import RAW Heightmap", Mathf.RoundToInt(progress * 100) + "%", progress);
|
||||
|
||||
int row = cy * heightmapResolution - (y * heightmapResolution + dy) - 1;
|
||||
int seek = (row * textureWidth + x * heightmapResolution) * coof;
|
||||
|
||||
stream.Seek(seek, SeekOrigin.Begin);
|
||||
|
||||
for (int dx = 0; dx < heightmapResolution; dx++)
|
||||
{
|
||||
if (depth == 8) heightmap[dy, dx] = stream.ReadByte() / 256f;
|
||||
else
|
||||
{
|
||||
int b1 = stream.ReadByte();
|
||||
int b2 = stream.ReadByte();
|
||||
|
||||
if (order == RealWorldTerrainByteOrder.Windows) heightmap[dy, dx] = (b2 * 256 + b1) / 65536f;
|
||||
else heightmap[dy, dx] = (b1 * 256 + b2) / 65536f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int tIndex = y * cx + x;
|
||||
if (terrains[tIndex].terrainData.heightmapResolution != heightmapResolution)
|
||||
{
|
||||
Vector3 size = terrains[tIndex].terrainData.size;
|
||||
terrains[tIndex].terrainData.heightmapResolution = heightmapResolution;
|
||||
terrains[tIndex].terrainData.size = size;
|
||||
}
|
||||
terrains[tIndex].terrainData.SetHeights(0, 0, heightmap);
|
||||
}
|
||||
}
|
||||
|
||||
stream.Close();
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
|
||||
public static void OpenWindow(RealWorldTerrainMonoBase target)
|
||||
{
|
||||
RealWorldTerrainHeightmapImporter wnd = GetWindow<RealWorldTerrainHeightmapImporter>(true, "Heightmap Importer");
|
||||
wnd.target = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 372c3c85d760fdb438fff5e09ca50ed3
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,103 @@
|
||||
/* INFINITY CODE 2013-2019 */
|
||||
/* http://www.infinity-code.com */
|
||||
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public static class RealWorldTerrainMeshOBJExporter
|
||||
{
|
||||
private static int index = 0;
|
||||
|
||||
public static void Export(RealWorldTerrainMonoBase item)
|
||||
{
|
||||
string meshName = item.gameObject.name;
|
||||
string filename = EditorUtility.SaveFilePanel("Export .obj file", "", meshName, "obj");
|
||||
if (string.IsNullOrEmpty(filename)) return;
|
||||
|
||||
StringBuilder meshString = new StringBuilder();
|
||||
|
||||
meshString.Append("#").Append(meshName).Append(".obj")
|
||||
.Append("\n#").Append(System.DateTime.Now.ToLongDateString())
|
||||
.Append("\n#").Append(System.DateTime.Now.ToLongTimeString())
|
||||
.Append("\n#-------")
|
||||
.Append("\n\n");
|
||||
|
||||
meshString.Append("g ").Append(meshName).Append("\n");
|
||||
|
||||
MeshFilter[] filters = item.GetComponentsInChildren<MeshFilter>();
|
||||
index = 0;
|
||||
|
||||
for (int i = 0; i < filters.Length; i++)
|
||||
{
|
||||
MeshFilter filter = filters[i];
|
||||
if (EditorUtility.DisplayCancelableProgressBar("Export OBJ", "Please wait", i / (float) filters.Length))
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
return;
|
||||
}
|
||||
MeshToString(filter, meshString);
|
||||
}
|
||||
|
||||
StreamWriter sw = new StreamWriter(filename);
|
||||
sw.Write(meshString.ToString());
|
||||
sw.Close();
|
||||
|
||||
EditorUtility.ClearProgressBar();
|
||||
|
||||
EditorUtility.RevealInFinder(filename);
|
||||
}
|
||||
|
||||
private static void MeshToString(MeshFilter mf, StringBuilder meshString)
|
||||
{
|
||||
Transform t = mf.transform;
|
||||
Quaternion r = t.localRotation;
|
||||
|
||||
meshString.Append("g ").Append(t.name).Append("\n");
|
||||
|
||||
int numVertices = 0;
|
||||
Mesh m = mf.sharedMesh;
|
||||
Material[] mats = mf.GetComponent<Renderer>().sharedMaterials;
|
||||
|
||||
foreach (Vector3 vv in m.vertices)
|
||||
{
|
||||
Vector3 v = t.TransformPoint(vv);
|
||||
numVertices++;
|
||||
meshString.Append("v ").Append(v.x).Append(" ").Append(v.y).Append(" ").Append(-v.z).Append("\n");
|
||||
}
|
||||
meshString.Append("\n");
|
||||
foreach (Vector3 nn in m.normals)
|
||||
{
|
||||
Vector3 v = r * nn;
|
||||
meshString.Append("vn ").Append(-v.x).Append(" ").Append(-v.y).Append(" ").Append(v.z).Append("\n");
|
||||
}
|
||||
meshString.Append("\n");
|
||||
foreach (Vector3 v in m.uv)
|
||||
{
|
||||
meshString.Append("vt ").Append(v.x).Append(" ").Append(v.y).Append("\n");
|
||||
}
|
||||
for (int material = 0; material < m.subMeshCount; material++)
|
||||
{
|
||||
meshString.Append("\n");
|
||||
meshString.Append("usemtl ").Append(mats[material].name).Append("\n");
|
||||
meshString.Append("usemap ").Append(mats[material].name).Append("\n");
|
||||
|
||||
int[] triangles = m.GetTriangles(material);
|
||||
for (int i = 0; i < triangles.Length; i += 3)
|
||||
{
|
||||
int ti1 = triangles[i] + 1 + index;
|
||||
int ti2 = triangles[i + 1] + 1 + index;
|
||||
int ti3 = triangles[i + 2] + 1 + index;
|
||||
meshString.Append("f ").Append(ti1).Append("/").Append(ti1).Append("/").Append(ti1).Append(" ").
|
||||
Append(ti2).Append("/").Append(ti2).Append("/").Append(ti2).Append(" ").
|
||||
Append(ti3).Append("/").Append(ti3).Append("/").Append(ti3).Append(" ").Append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
index += numVertices;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9164dc2fc4e1c114ba7da1b68a6c9bfa
|
||||
timeCreated: 1523353454
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,341 @@
|
||||
/* INFINITY CODE */
|
||||
/* https://infinity-code.com */
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public class RealWorldTerrainObjectPlacerWindow : EditorWindow
|
||||
{
|
||||
private static string[] gridLabels = { "Place New Object", "Update Position" };
|
||||
|
||||
private static RealWorldTerrainObjectPlacerWindow wnd;
|
||||
private int isNewGameobject = 0;
|
||||
private GameObject obj;
|
||||
private double latitude;
|
||||
private double longitude;
|
||||
private double altitude;
|
||||
private RealWorldTerrainContainer container;
|
||||
private bool selectGameObject = true;
|
||||
private PlaceMode placeMode = PlaceMode.singleLocation;
|
||||
private double[] locations;
|
||||
private string multipleLocations;
|
||||
private string multipleLocationsFile;
|
||||
|
||||
private bool hasCoordinates = false;
|
||||
private bool useAltitude = false;
|
||||
private double cursorLongitude;
|
||||
private double cursorLatitude;
|
||||
private double cursorAltitude;
|
||||
private Vector2 scrollPosition;
|
||||
|
||||
private void DrawCursorLocation()
|
||||
{
|
||||
if (!hasCoordinates) return;
|
||||
|
||||
EditorGUILayout.LabelField("Cursor Coordinates:");
|
||||
EditorGUILayout.LabelField("Latitude: ", cursorLatitude.ToString());
|
||||
EditorGUILayout.LabelField("Longitude: ", cursorLongitude.ToString());
|
||||
EditorGUILayout.LabelField("Altitude: ", cursorAltitude.ToString("F2") + " meters");
|
||||
EditorGUILayout.LabelField("Use CTRL+SHIFT to insert the coordinates.");
|
||||
|
||||
if (Event.current.control && Event.current.shift)
|
||||
{
|
||||
latitude = cursorLatitude;
|
||||
longitude = cursorLongitude;
|
||||
altitude = cursorAltitude;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMultipleLocations()
|
||||
{
|
||||
EditorGUILayout.HelpBox("Use one location in decimal per line in \"latitude;longitude\" format without quotes.\n", MessageType.None);
|
||||
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.MaxHeight(200));
|
||||
multipleLocations = EditorGUILayout.TextArea(multipleLocations, GUILayout.ExpandHeight(true));
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
private void DrawMultipleLocationsFromFile()
|
||||
{
|
||||
EditorGUILayout.HelpBox("Use one location in decimal per line in \"latitude;longitude\" format without quotes.\n", MessageType.None);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
multipleLocationsFile = EditorGUILayout.TextField("File: ", multipleLocationsFile);
|
||||
if (GUILayout.Button("...", GUILayout.ExpandWidth(false)))
|
||||
{
|
||||
multipleLocationsFile = EditorUtility.OpenFilePanel("Select Input File", multipleLocationsFile, "txt");
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void DrawNewUI()
|
||||
{
|
||||
obj = EditorGUILayout.ObjectField("Prefab: ", obj, typeof(GameObject), true) as GameObject;
|
||||
|
||||
placeMode = (PlaceMode)EditorGUILayout.EnumPopup("Place Mode", placeMode);
|
||||
if (placeMode == PlaceMode.singleLocation) DrawSingleLocation();
|
||||
else if (placeMode == PlaceMode.multipleLocations) DrawMultipleLocations();
|
||||
else if (placeMode == PlaceMode.multipleLocationsFromFile) DrawMultipleLocationsFromFile();
|
||||
|
||||
selectGameObject = EditorGUILayout.Toggle("Select GameObject(s)?", selectGameObject);
|
||||
|
||||
if (GUILayout.Button("Place") && ValidateFields()) PlaceItems();
|
||||
}
|
||||
|
||||
private void DrawSingleLocation()
|
||||
{
|
||||
latitude = EditorGUILayout.DoubleField("Latitude", latitude);
|
||||
longitude = EditorGUILayout.DoubleField("Longitude", longitude);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
useAltitude = GUILayout.Toggle(useAltitude, GUIContent.none, GUILayout.Width(16));
|
||||
EditorGUI.BeginDisabledGroup(!useAltitude);
|
||||
altitude = EditorGUILayout.DoubleField("Altitude", altitude);
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void DrawUpdateUI()
|
||||
{
|
||||
obj = EditorGUILayout.ObjectField("GameObject: ", obj, typeof(GameObject), true) as GameObject;
|
||||
|
||||
DrawSingleLocation();
|
||||
selectGameObject = EditorGUILayout.Toggle("Select GameObject?", selectGameObject);
|
||||
|
||||
if (GUILayout.Button("Update") && ValidateFields())
|
||||
{
|
||||
UpdateGameObjectPosition(obj, longitude, latitude, altitude);
|
||||
if (selectGameObject) SelectGameObject(obj);
|
||||
}
|
||||
}
|
||||
|
||||
private bool LoadMultipleLocations(string content)
|
||||
{
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
ShowError("List of locations is empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
string[] lines = content.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (lines.Length == 0)
|
||||
{
|
||||
ShowError("List of locations is empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
locations = new double[lines.Length * 2];
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
string[] coordinates = lines[i].Split(';');
|
||||
if (coordinates.Length != 2)
|
||||
{
|
||||
ShowError(string.Format("Invalid coordinates in line {0}.", i + 1));
|
||||
return false;
|
||||
}
|
||||
|
||||
double lng, lat;
|
||||
if (!double.TryParse(coordinates[0].Trim(), out lat) || !double.TryParse(coordinates[1].Trim(), out lng))
|
||||
{
|
||||
ShowError(string.Format("Invalid coordinates in line {0}.", i + 1));
|
||||
return false;
|
||||
}
|
||||
|
||||
locations[i * 2] = lng;
|
||||
locations[i * 2 + 1] = lat;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool LoadMultipleLocationsFromFile()
|
||||
{
|
||||
if (!File.Exists(multipleLocationsFile))
|
||||
{
|
||||
ShowError("Input file does not exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string content = File.ReadAllText(multipleLocationsFile);
|
||||
return LoadMultipleLocations(content);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ShowError("Exception: " + e.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
EditorApplication.update -= OnUpdate;
|
||||
SceneView.duringSceneGui -= OnSceneGUI;
|
||||
wnd = null;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
OnDestroy();
|
||||
|
||||
wnd = this;
|
||||
EditorApplication.update += OnUpdate;
|
||||
SceneView.duringSceneGui += OnSceneGUI;
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
isNewGameobject = GUILayout.SelectionGrid(isNewGameobject, gridLabels, 2);
|
||||
if (EditorGUI.EndChangeCheck()) obj = null;
|
||||
|
||||
container = EditorGUILayout.ObjectField("Container", container, typeof(RealWorldTerrainContainer), true) as RealWorldTerrainContainer;
|
||||
|
||||
if (isNewGameobject == 0) DrawNewUI();
|
||||
else DrawUpdateUI();
|
||||
|
||||
DrawCursorLocation();
|
||||
}
|
||||
|
||||
private void OnSceneGUI(SceneView view)
|
||||
{
|
||||
if (container == null) return;
|
||||
|
||||
Vector2 mp = Event.current.mousePosition;
|
||||
mp.y = view.camera.pixelHeight - mp.y;
|
||||
|
||||
Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
|
||||
RaycastHit hit;
|
||||
hasCoordinates = Physics.Raycast(ray, out hit);
|
||||
if (hasCoordinates) container.GetCoordinatesByWorldPosition(hit.point, out cursorLongitude, out cursorLatitude, out cursorAltitude);
|
||||
}
|
||||
|
||||
private void OnUpdate()
|
||||
{
|
||||
Repaint();
|
||||
}
|
||||
|
||||
[MenuItem("Window/Infinity Code/Real World Terrain/Tools/Object Placer")]
|
||||
public static void OpenWindow()
|
||||
{
|
||||
OpenWindow(null);
|
||||
}
|
||||
|
||||
public static void OpenWindow(RealWorldTerrainContainer container)
|
||||
{
|
||||
if (wnd != null) wnd.Close();
|
||||
|
||||
wnd = GetWindow<RealWorldTerrainObjectPlacerWindow>(false, "Object Placer", true);
|
||||
if (container == null)
|
||||
{
|
||||
wnd.container = RealWorldTerrainUtils.FindObjectOfType<RealWorldTerrainContainer>();
|
||||
}
|
||||
else wnd.container = container;
|
||||
}
|
||||
|
||||
public static void OpenWindow(RealWorldTerrainContainer container, double lng, double lat)
|
||||
{
|
||||
OpenWindow(container);
|
||||
wnd.latitude = lat;
|
||||
wnd.longitude = lng;
|
||||
}
|
||||
|
||||
private void PlaceItems()
|
||||
{
|
||||
if (placeMode == PlaceMode.singleLocation)
|
||||
{
|
||||
GameObject go = Instantiate(obj);
|
||||
UpdateGameObjectPosition(go, longitude, latitude, altitude);
|
||||
if (selectGameObject) SelectGameObject(go);
|
||||
}
|
||||
else
|
||||
{
|
||||
int count = locations.Length / 2;
|
||||
GameObject[] gos = new GameObject[count];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
GameObject go = Instantiate(obj);
|
||||
double lng = locations[i * 2];
|
||||
double lat = locations[i * 2 + 1];
|
||||
|
||||
UpdateGameObjectPosition(go, lng, lat);
|
||||
gos[i] = go;
|
||||
}
|
||||
|
||||
if (selectGameObject)
|
||||
{
|
||||
Selection.objects = gos;
|
||||
EditorGUIUtility.PingObject(gos[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectGameObject(GameObject go)
|
||||
{
|
||||
if (!selectGameObject) return;
|
||||
|
||||
Selection.activeGameObject = go;
|
||||
EditorGUIUtility.PingObject(go);
|
||||
}
|
||||
|
||||
private static void ShowError(string message)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Error", message, "OK");
|
||||
}
|
||||
|
||||
private void UpdateGameObjectPosition(GameObject go, double lng, double lat, double alt = 0)
|
||||
{
|
||||
Vector3 worldPosition;
|
||||
bool status = false;
|
||||
|
||||
if (useAltitude) status = container.GetWorldPosition(lng, lat, alt, out worldPosition);
|
||||
else status = container.GetWorldPosition(lng, lat, out worldPosition);
|
||||
|
||||
if (status) go.transform.position = worldPosition;
|
||||
}
|
||||
|
||||
private bool ValidateFields()
|
||||
{
|
||||
if (container == null)
|
||||
{
|
||||
ShowError("Please select Real World Terrain Container.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (obj == null)
|
||||
{
|
||||
ShowError(string.Format("Please select {0}.", isNewGameobject == 0 ? "Prefab" : "GameObject"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!container.Contains(longitude, latitude))
|
||||
{
|
||||
ShowError("These the coordinates outside terrain.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (placeMode == PlaceMode.multipleLocations)
|
||||
{
|
||||
if (!LoadMultipleLocations(multipleLocations)) return false;
|
||||
}
|
||||
else if (placeMode == PlaceMode.multipleLocationsFromFile)
|
||||
{
|
||||
if (!LoadMultipleLocationsFromFile()) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private enum PlaceMode
|
||||
{
|
||||
singleLocation,
|
||||
multipleLocations,
|
||||
multipleLocationsFromFile
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38b1145c4205f9147998fd1fa6c2baea
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,74 @@
|
||||
/* INFINITY CODE */
|
||||
/* https://infinity-code.com */
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public class RealWorldTerrainPOIManager : EditorWindow
|
||||
{
|
||||
private static RealWorldTerrainPOIManager wnd;
|
||||
private RealWorldTerrainContainer container;
|
||||
private RealWorldTerrainPOIItem[] items;
|
||||
private Vector2 scrollPosition;
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
wnd = null;
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
container = EditorGUILayout.ObjectField("Container", container, typeof(RealWorldTerrainContainer), true) as RealWorldTerrainContainer;
|
||||
|
||||
if (container == null) return;
|
||||
if (GUILayout.Button("Update POI") || items == null) UpdatePOI();
|
||||
|
||||
GUILayout.Label("POI:");
|
||||
if (items == null || items.Length == 0)
|
||||
{
|
||||
GUILayout.Label("No POI.");
|
||||
return;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
||||
foreach (RealWorldTerrainPOIItem item in items)
|
||||
{
|
||||
index++;
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
EditorGUILayout.LabelField(index + ": " + item.title);
|
||||
EditorGUILayout.LabelField("lat: " + item.y, GUILayout.Width(90));
|
||||
EditorGUILayout.LabelField("lng: " + item.x, GUILayout.Width(90));
|
||||
|
||||
if (GUILayout.Button(new GUIContent("S", "Select GameObject"), GUILayout.ExpandWidth(false))) Selection.activeGameObject = item.gameObject;
|
||||
if (GUILayout.Button(new GUIContent("P", "Open in Object Placer"), GUILayout.ExpandWidth(false))) RealWorldTerrainObjectPlacerWindow.OpenWindow(container, item.x, item.y);
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
private void UpdatePOI()
|
||||
{
|
||||
items = container.GetComponentsInChildren<RealWorldTerrainPOIItem>();
|
||||
}
|
||||
|
||||
[MenuItem("Window/Infinity Code/Real World Terrain/Tools/POI Manager")]
|
||||
public static void OpenWindow()
|
||||
{
|
||||
if (wnd != null) wnd.Close();
|
||||
|
||||
wnd = GetWindow<RealWorldTerrainPOIManager>(false, "POI Manager");
|
||||
wnd.container = RealWorldTerrainUtils.FindObjectOfType<RealWorldTerrainContainer>();
|
||||
}
|
||||
public static void OpenWindow(RealWorldTerrainContainer container)
|
||||
{
|
||||
OpenWindow();
|
||||
wnd.container = container;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f26fd24d069ba9347b8f71938f38563c
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,146 @@
|
||||
/* INFINITY CODE */
|
||||
/* https://infinity-code.com */
|
||||
|
||||
using InfinityCode.RealWorldTerrain.Webservices;
|
||||
using InfinityCode.RealWorldTerrain.Webservices.Results;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public class RealWorldTerrainReverseGeocodingWindow: EditorWindow
|
||||
{
|
||||
private static RealWorldTerrainReverseGeocodingWindow wnd;
|
||||
private RealWorldTerrainMonoBase target;
|
||||
private double cursorLatitude;
|
||||
private double cursorLongitude;
|
||||
private double cursorAltitude;
|
||||
private float lat;
|
||||
private float lng;
|
||||
private bool hasCoordinates = false;
|
||||
private string languageCode = "en";
|
||||
|
||||
private string formattedAddress;
|
||||
private string response;
|
||||
private Vector2 scrollPosition;
|
||||
private string key;
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
EditorApplication.update -= OnUpdate;
|
||||
SceneView.duringSceneGui -= OnSceneGUI;
|
||||
wnd = null;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
EditorApplication.update += OnUpdate;
|
||||
SceneView.duringSceneGui += OnSceneGUI;
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
target = EditorGUILayout.ObjectField("Container", target, typeof(RealWorldTerrainContainer), true) as RealWorldTerrainContainer;
|
||||
|
||||
lat = EditorGUILayout.FloatField("Latitude", lat);
|
||||
lng = EditorGUILayout.FloatField("Longitude", lng);
|
||||
key = EditorGUILayout.TextField("Google API key", key);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
languageCode = EditorGUILayout.TextField("Language Code", languageCode);
|
||||
RealWorldTerrainWindowUI.DrawHelpButton("List of Languages", "https://developers.google.com/maps/faq?hl=en#languagesupport");
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (GUILayout.Button("What's here?"))
|
||||
{
|
||||
RealWorldTerrainGoogleGeocoding.Find(
|
||||
new RealWorldTerrainGoogleGeocoding.ReverseGeocodingParams(lng, lat)
|
||||
{
|
||||
language = languageCode,
|
||||
key = key
|
||||
}
|
||||
).OnComplete += OnRequestComplete;
|
||||
}
|
||||
|
||||
if (hasCoordinates)
|
||||
{
|
||||
EditorGUILayout.LabelField("Cursor Coordinates:");
|
||||
EditorGUILayout.LabelField("Latitude: ", cursorLatitude.ToString());
|
||||
EditorGUILayout.LabelField("Longitude: ", cursorLongitude.ToString());
|
||||
EditorGUILayout.LabelField("Altitude: ", cursorAltitude.ToString("F2") + " meters");
|
||||
EditorGUILayout.LabelField("Use CTRL+SHIFT to insert the coordinates.");
|
||||
|
||||
if (Event.current.control && Event.current.shift)
|
||||
{
|
||||
lat = (float)cursorLatitude;
|
||||
lng = (float)cursorLongitude;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (!string.IsNullOrEmpty(formattedAddress))
|
||||
{
|
||||
GUILayout.Label("Formatted Address: " + formattedAddress, EditorStyles.wordWrappedLabel);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(response))
|
||||
{
|
||||
GUILayout.Label("Full Response: ");
|
||||
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
||||
EditorGUILayout.TextArea(response);
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRequestComplete(string response)
|
||||
{
|
||||
Debug.Log(response);
|
||||
this.response = response;
|
||||
try
|
||||
{
|
||||
RealWorldTerrainGoogleGeocodingResult[] result = RealWorldTerrainGoogleGeocoding.GetResults(response);
|
||||
if (result.Length > 0)
|
||||
{
|
||||
formattedAddress = result[0].formatted_address;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void OnSceneGUI(SceneView view)
|
||||
{
|
||||
if (target == null) return;
|
||||
|
||||
Vector2 mp = Event.current.mousePosition;
|
||||
mp.y = view.camera.pixelHeight - mp.y;
|
||||
|
||||
hasCoordinates = target.GetCoordinatesByScreenPosition(mp, out cursorLongitude, out cursorLatitude, out cursorAltitude, view.camera);
|
||||
}
|
||||
|
||||
private void OnUpdate()
|
||||
{
|
||||
Repaint();
|
||||
}
|
||||
|
||||
[MenuItem("Window/Infinity Code/Real World Terrain/Tools/Reverse Geocoder")]
|
||||
public static void OpenWindow()
|
||||
{
|
||||
OpenWindow(null);
|
||||
}
|
||||
|
||||
|
||||
public static void OpenWindow(RealWorldTerrainMonoBase target)
|
||||
{
|
||||
if (wnd != null) wnd.Close();
|
||||
|
||||
wnd = GetWindow<RealWorldTerrainReverseGeocodingWindow>(true, "Reverse Geocoder");
|
||||
if (target == null) wnd.target = RealWorldTerrainUtils.FindObjectOfType<RealWorldTerrainContainer>();
|
||||
else wnd.target = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ecda3fdeb25a758458c2726a39c0822b
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,150 @@
|
||||
/* INFINITY CODE */
|
||||
/* https://infinity-code.com */
|
||||
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public class RealWorldTerrainScalerWindow : EditorWindow
|
||||
{
|
||||
private static RealWorldTerrainScalerWindow wnd;
|
||||
private RealWorldTerrainContainer item;
|
||||
private Vector3 scale = Vector3.one;
|
||||
private Vector3 size = Vector3.one;
|
||||
private SizeType sizeType = SizeType.relative;
|
||||
#if !RTP
|
||||
private bool scaleFirstSP = true;
|
||||
#endif
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
item = (RealWorldTerrainContainer)EditorGUILayout.ObjectField("Real World Terrain", item, typeof(RealWorldTerrainContainer), true);
|
||||
if (item == null) return;
|
||||
|
||||
sizeType = (SizeType)EditorGUILayout.EnumPopup("Size Type", sizeType);
|
||||
if (sizeType == SizeType.relative) scale = EditorGUILayout.Vector3Field("Scale", scale);
|
||||
else size = EditorGUILayout.Vector3Field("Size", size);
|
||||
|
||||
if (item.prefs.resultType == RealWorldTerrainResultType.terrain)
|
||||
{
|
||||
#if !RTP
|
||||
scaleFirstSP = EditorGUILayout.Toggle("Scale first SplatPrototype", scaleFirstSP);
|
||||
#endif
|
||||
}
|
||||
if (GUILayout.Button("Apply")) Scale();
|
||||
}
|
||||
|
||||
[MenuItem("Window/Infinity Code/Real World Terrain/Tools/Scaler")]
|
||||
public static void OpenWindow()
|
||||
{
|
||||
OpenWindow(null);
|
||||
}
|
||||
|
||||
public static void OpenWindow(RealWorldTerrainContainer item)
|
||||
{
|
||||
wnd = GetWindow<RealWorldTerrainScalerWindow>("Scaler", true);
|
||||
wnd.item = item;
|
||||
if (item != null) wnd.size = item.size;
|
||||
}
|
||||
|
||||
private void Scale()
|
||||
{
|
||||
if (sizeType == SizeType.relative)
|
||||
{
|
||||
if (!ValidateValue(scale, "Scale")) return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ValidateValue(size, "Size")) return;
|
||||
}
|
||||
|
||||
Vector3 originalSize = item.bounds.size;
|
||||
Vector3 newSize = originalSize;
|
||||
Vector3 pscale;
|
||||
|
||||
if (sizeType == SizeType.relative)
|
||||
{
|
||||
pscale = scale;
|
||||
newSize.Scale(pscale);
|
||||
}
|
||||
else
|
||||
{
|
||||
newSize = size;
|
||||
pscale = new Vector3(newSize.x / originalSize.x, newSize.y / originalSize.y, newSize.z / originalSize.z);
|
||||
}
|
||||
|
||||
item.size = newSize;
|
||||
|
||||
if (item.prefs.resultType == RealWorldTerrainResultType.mesh)
|
||||
{
|
||||
Vector3 localScale = item.transform.localScale;
|
||||
localScale.Scale(pscale);
|
||||
item.transform.localScale = localScale;
|
||||
item.size = newSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (RealWorldTerrainItem terrain in item.terrains)
|
||||
{
|
||||
Vector3 p = terrain.transform.position;
|
||||
|
||||
p.Scale(pscale);
|
||||
terrain.transform.position = p;
|
||||
|
||||
Vector3 s = terrain.terrainData.size;
|
||||
s.Scale(pscale);
|
||||
terrain.terrainData.size = s;
|
||||
terrain.size = s;
|
||||
|
||||
TreeInstance[] treeInstances = terrain.terrainData.treeInstances;
|
||||
for (int i = 0; i < treeInstances.Length; i++)
|
||||
{
|
||||
TreeInstance treeInstance = treeInstances[i];
|
||||
treeInstance.heightScale *= pscale.y;
|
||||
treeInstance.widthScale *= (pscale.x + pscale.z) / 2;
|
||||
treeInstances[i] = treeInstance;
|
||||
}
|
||||
terrain.terrain.terrainData.SetTreeInstances(treeInstances, true);
|
||||
#if !RTP
|
||||
if (scaleFirstSP && terrain.terrainData.terrainLayers.Length > 0)
|
||||
{
|
||||
TerrainLayer[] tls = terrain.terrainData.terrainLayers;
|
||||
TerrainLayer l = tls[0];
|
||||
l.tileSize = new Vector2(l.tileSize.x * pscale.x, l.tileSize.y * pscale.z);
|
||||
l.tileOffset = new Vector2(l.tileOffset.x * pscale.x, l.tileOffset.y * pscale.z);
|
||||
l.diffuseTexture = l.diffuseTexture;
|
||||
terrain.terrainData.terrainLayers = tls;
|
||||
}
|
||||
#endif
|
||||
terrain.terrain.terrainData.RefreshPrototypes();
|
||||
EditorUtility.SetDirty(terrain);
|
||||
}
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
private bool ValidateValue(Vector3 value, string variableName)
|
||||
{
|
||||
if (Math.Abs(value.x) < float.Epsilon || Math.Abs(value.y) < float.Epsilon || Math.Abs(value.z) < float.Epsilon)
|
||||
{
|
||||
Debug.LogError(variableName + " failed!!! Value can not be zero.");
|
||||
return false;
|
||||
}
|
||||
if (scale.x < 0 || scale.y < 0 || scale.z < 0)
|
||||
{
|
||||
Debug.LogError(variableName + " failed!!! Value can not be lower zero.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public enum SizeType
|
||||
{
|
||||
absolute,
|
||||
relative
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8cd7ee0d27213d84a9b17621e123eddf
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,114 @@
|
||||
/* INFINITY CODE */
|
||||
/* https://infinity-code.com */
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public class RealWorldTerrainSeamsFixer : EditorWindow
|
||||
{
|
||||
private bool createUndo = true;
|
||||
private Terrain terrain1;
|
||||
private Terrain terrain2;
|
||||
private Vector2 scrollPosition;
|
||||
|
||||
[MenuItem("Window/Infinity Code/Real World Terrain/Tools/Seams Fixer")]
|
||||
public static void CreateWizard()
|
||||
{
|
||||
GetWindow<RealWorldTerrainSeamsFixer>("Seams Fixer");
|
||||
}
|
||||
|
||||
private void FixHeights(float[,] h1, float[,] h2)
|
||||
{
|
||||
for (int i = 0; i < h1.GetLength(0); i++)
|
||||
{
|
||||
for (int j = 0; j < h1.GetLength(1); j++)
|
||||
{
|
||||
h1[i, j] = (h1[i, j] + h2[i, j]) / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
||||
|
||||
EditorGUILayout.HelpBox(@"A tool for repairing seams between adjacent Terrains.
|
||||
Requirements:
|
||||
1.Terrains must be located in the correct position to be neighbors.
|
||||
2.Terrains must have the same heightmap resolution.", MessageType.Info);
|
||||
|
||||
terrain1 = EditorGUILayout.ObjectField("Terrain 1", terrain1, typeof(Terrain), true) as Terrain;
|
||||
terrain2 = EditorGUILayout.ObjectField("Terrain 2", terrain2, typeof(Terrain), true) as Terrain;
|
||||
createUndo = EditorGUILayout.ToggleLeft("Create Undo (May take a long time)", createUndo);
|
||||
EditorGUILayout.EndScrollView();
|
||||
EditorGUI.BeginDisabledGroup(terrain1 == null || terrain2 == null);
|
||||
if (GUILayout.Button("Fix"))
|
||||
{
|
||||
OnFix();
|
||||
}
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
|
||||
public void OnFix()
|
||||
{
|
||||
if (terrain1 == terrain2)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Error", "You have selected the same terrain.", "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
TerrainData td1 = terrain1.terrainData;
|
||||
TerrainData td2 = terrain2.terrainData;
|
||||
|
||||
if (td1.heightmapResolution != td2.heightmapResolution)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Error", "Terrains cannot have different heightmap resolution.", "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
if (createUndo) Undo.RecordObjects(new []{td1, td2}, "Fix seams");
|
||||
|
||||
int r = td1.heightmapResolution;
|
||||
|
||||
if (terrain1.transform.position + new Vector3(td1.size.x, 0, 0) == terrain2.transform.position) // Right
|
||||
{
|
||||
float[,] h1 = td1.GetHeights(r - 1, 0, 1, r);
|
||||
float[,] h2 = td2.GetHeights(0, 0, 1, r);
|
||||
FixHeights(h1, h2);
|
||||
td1.SetHeights(r - 1, 0, h1);
|
||||
td2.SetHeights(0, 0, h1);
|
||||
}
|
||||
else if (terrain2.transform.position + new Vector3(td2.size.x, 0, 0) == terrain1.transform.position) // Left
|
||||
{
|
||||
float[,] h1 = td2.GetHeights(r - 1, 0, 1, r);
|
||||
float[,] h2 = td1.GetHeights(0, 0, 1, r);
|
||||
FixHeights(h1, h2);
|
||||
td2.SetHeights(r - 1, 0, h1);
|
||||
td1.SetHeights(0, 0, h1);
|
||||
}
|
||||
else if (terrain1.transform.position + new Vector3(0, 0, td1.size.z) == terrain2.transform.position) // Down
|
||||
{
|
||||
float[,] h1 = td1.GetHeights(0, r - 1, r, 1);
|
||||
float[,] h2 = td2.GetHeights(0, 0, r, 1);
|
||||
FixHeights(h1, h2);
|
||||
td1.SetHeights(0, r - 1, h1);
|
||||
td2.SetHeights(0, 0, h1);
|
||||
}
|
||||
else if (terrain2.transform.position + new Vector3(0, 0, td2.size.z) == terrain1.transform.position) // Up
|
||||
{
|
||||
float[,] h1 = td2.GetHeights(0, r - 1, r, 1);
|
||||
float[,] h2 = td1.GetHeights(0, 0, r, 1);
|
||||
FixHeights(h1, h2);
|
||||
td2.SetHeights(0, r - 1, h1);
|
||||
td1.SetHeights(0, 0, h1);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorUtility.DisplayDialog("Error", "This is not neighboring terrains.", "OK");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c4de5dea82cbaa4b84d1a6cb3b2af86
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,260 @@
|
||||
/* INFINITY CODE 2013-2019 */
|
||||
/* http://www.infinity-code.com */
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain
|
||||
{
|
||||
public class RealWorldTerrainSplatPrototypeGenerator : EditorWindow
|
||||
{
|
||||
private static RealWorldTerrainSplatPrototypeGenerator wnd;
|
||||
private static Texture2D previewTexture;
|
||||
|
||||
private RealWorldTerrainSplatPrototypeItem baseTexture;
|
||||
private RealWorldTerrainItem item;
|
||||
private List<RealWorldTerrainSplatPrototypeItem> prototypes;
|
||||
private Vector2 scrollPosition;
|
||||
|
||||
private void ExportSettings()
|
||||
{
|
||||
string path = EditorUtility.SaveFilePanel("Export settings", Application.dataPath,
|
||||
"SplatGeneratorSettings.xml", "xml");
|
||||
if (string.IsNullOrEmpty(path)) return;
|
||||
|
||||
XmlDocument doc = new XmlDocument();
|
||||
XmlElement firstElement = (XmlElement)doc.AppendChild(doc.CreateElement("SplatGenerator"));
|
||||
|
||||
firstElement.AppendChild(baseTexture.GetNode(doc));
|
||||
foreach (RealWorldTerrainSplatPrototypeItem sp in prototypes) firstElement.AppendChild(sp.GetNode(doc));
|
||||
|
||||
doc.Save(path);
|
||||
}
|
||||
|
||||
public static void GeneratePreview(RealWorldTerrainSplatPrototypeItem sp)
|
||||
{
|
||||
TerrainData tdata = wnd.item.terrainData;
|
||||
|
||||
RealWorldTerrainEditorUtils.GeneratePreviewTexture(tdata, ref previewTexture);
|
||||
|
||||
Texture2D originalTexture = tdata.terrainLayers[0].diffuseTexture;
|
||||
float[,,] alphamap = tdata.GetAlphamaps(0, 0, tdata.alphamapWidth, tdata.alphamapHeight);
|
||||
Color[] originalColors = originalTexture.GetPixels();
|
||||
int w = originalTexture.width;
|
||||
int h = originalTexture.height;
|
||||
float sw = w / (float)tdata.alphamapWidth;
|
||||
float sh = h / (float)tdata.alphamapHeight;
|
||||
int l = alphamap.GetLength(2) - 1;
|
||||
float step = 1 / (sw * sh);
|
||||
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
int fx = Mathf.FloorToInt(x / sw);
|
||||
bool isFirstX = x % sw == 0;
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
int fy = Mathf.FloorToInt(y / sh);
|
||||
bool isFirstY = y % sh == 0;
|
||||
Color clr = originalColors[x * w + y];
|
||||
if (isFirstX && isFirstY) alphamap[fx, fy, l] = 0;
|
||||
alphamap[fx, fy, l] += sp.colors.Any(c => c.EqualWithRange(clr)) ? step : 0;
|
||||
}
|
||||
}
|
||||
tdata.SetAlphamaps(0, 0, alphamap);
|
||||
alphamap = null;
|
||||
originalColors = null;
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
private void GenerateSplatPrototypes()
|
||||
{
|
||||
TerrainData tdata = item.terrainData;
|
||||
List<TerrainLayer> spsList = new List<TerrainLayer>(tdata.terrainLayers);
|
||||
int startIndex = spsList.Count;
|
||||
int endIndex = startIndex + prototypes.Count + 1;
|
||||
spsList.Add(baseTexture.terrainLayer);
|
||||
|
||||
spsList.AddRange(prototypes.Select(prototype => prototype.terrainLayer));
|
||||
tdata.terrainLayers = spsList.ToArray();
|
||||
tdata.RefreshPrototypes();
|
||||
|
||||
Texture2D originalTexture = tdata.terrainLayers[0].diffuseTexture;
|
||||
float[,,] alphamap = tdata.GetAlphamaps(0, 0, tdata.alphamapWidth, tdata.alphamapHeight);
|
||||
Color[] originalColors = originalTexture.GetPixels();
|
||||
int w = originalTexture.width;
|
||||
int h = originalTexture.height;
|
||||
float sw = w / (float)tdata.alphamapWidth;
|
||||
float sh = h / (float)tdata.alphamapHeight;
|
||||
float step = 1 / (sw * sh);
|
||||
|
||||
for (int x = 0; x < alphamap.GetLength(0); x++)
|
||||
{
|
||||
for (int y = 0; y < alphamap.GetLength(1); y++)
|
||||
alphamap[x, y, startIndex] = 1;
|
||||
}
|
||||
|
||||
for (int l = startIndex + 1; l < endIndex; l++)
|
||||
{
|
||||
RealWorldTerrainSplatPrototypeItem prototype = prototypes[l - startIndex - 1];
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
int fx = Mathf.FloorToInt(x / sw);
|
||||
bool isFirstX = x % sw == 0;
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
int fy = Mathf.FloorToInt(y / sh);
|
||||
bool isFirstY = y % sh == 0;
|
||||
Color clr = originalColors[x * w + y];
|
||||
if (isFirstX && isFirstY) alphamap[fx, fy, l] = 0;
|
||||
alphamap[fx, fy, l] += prototype.colors.Any(c => c.EqualWithRange(clr)) ? step : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int x = 0; x < alphamap.GetLength(0); x++)
|
||||
{
|
||||
for (int y = 0; y < alphamap.GetLength(1); y++)
|
||||
{
|
||||
float total = 0;
|
||||
for (int l = startIndex + 1; l < endIndex; l++) total += alphamap[x, y, l];
|
||||
for (int l = 0; l < alphamap.GetLength(2); l++)
|
||||
{
|
||||
if (l < startIndex || l >= endIndex) alphamap[x, y, l] = 0;
|
||||
else if (l == startIndex)
|
||||
{
|
||||
if (total < 1) alphamap[x, y, l] = 1 - total;
|
||||
else alphamap[x, y, l] = 0;
|
||||
}
|
||||
else if (total != 0)
|
||||
{
|
||||
if (total < 1)
|
||||
alphamap[x, y, l] = total;
|
||||
else alphamap[x, y, l] /= total;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tdata.SetAlphamaps(0, 0, alphamap);
|
||||
alphamap = null;
|
||||
originalColors = null;
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
private void ImportSettings()
|
||||
{
|
||||
string path = EditorUtility.OpenFilePanel("Export settings", Application.dataPath, "xml");
|
||||
if (string.IsNullOrEmpty(path)) return;
|
||||
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(path);
|
||||
|
||||
XmlElement firstElement = (XmlElement)doc.FirstChild;
|
||||
if (firstElement.Name != "SplatGenerator") return;
|
||||
|
||||
baseTexture = new RealWorldTerrainSplatPrototypeItem(true);
|
||||
baseTexture.SetNode((XmlElement)firstElement.ChildNodes[0]);
|
||||
|
||||
prototypes = new List<RealWorldTerrainSplatPrototypeItem>();
|
||||
|
||||
for (int i = 1; i < firstElement.ChildNodes.Count; i++)
|
||||
{
|
||||
RealWorldTerrainSplatPrototypeItem sp = new RealWorldTerrainSplatPrototypeItem();
|
||||
sp.SetNode((XmlElement)firstElement.ChildNodes[i]);
|
||||
prototypes.Add(sp);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
OnDisable();
|
||||
wnd = null;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (previewTexture != null)
|
||||
{
|
||||
List<TerrainLayer> tls = item.terrainData.terrainLayers.ToList();
|
||||
tls.RemoveAll(l => l.diffuseTexture == previewTexture);
|
||||
item.terrainData.terrainLayers = tls.ToArray();
|
||||
previewTexture = null;
|
||||
EditorUtility.UnloadUnusedAssetsImmediate();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
wnd = this;
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (baseTexture == null) baseTexture = new RealWorldTerrainSplatPrototypeItem(true);
|
||||
if (prototypes == null) prototypes = new List<RealWorldTerrainSplatPrototypeItem>();
|
||||
|
||||
OnGUIToolbar();
|
||||
|
||||
item = EditorGUILayout.ObjectField("Terrain Item: ", item, typeof(RealWorldTerrainItem), true) as RealWorldTerrainItem;
|
||||
if (item == null) return;
|
||||
|
||||
baseTexture.OnGUI();
|
||||
int index = prototypes.Count;
|
||||
scrollPosition = GUILayout.BeginScrollView(scrollPosition);
|
||||
foreach (RealWorldTerrainSplatPrototypeItem prototype in prototypes) prototype.OnGUI(index--);
|
||||
GUILayout.EndScrollView();
|
||||
|
||||
prototypes.RemoveAll(p => p.deleted);
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
if (GUILayout.Button("Add SplatPrototype"))
|
||||
prototypes.Insert(0, new RealWorldTerrainSplatPrototypeItem());
|
||||
|
||||
if (GUILayout.Button("Clear preview"))
|
||||
OnDisable();
|
||||
|
||||
if (GUILayout.Button("Generate SplatPrototypes"))
|
||||
{
|
||||
GenerateSplatPrototypes();
|
||||
Close();
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void OnGUIToolbar()
|
||||
{
|
||||
GUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
|
||||
GUIStyle toolbarButtonStyle = new GUIStyle(EditorStyles.toolbarButton) { padding = new RectOffset(5, 5, 2, 2) };
|
||||
|
||||
if (GUILayout.Button(new GUIContent(RealWorldTerrainResources.openIcon, "Import settings"), toolbarButtonStyle,
|
||||
GUILayout.ExpandWidth(false)))
|
||||
ImportSettings();
|
||||
if (GUILayout.Button(new GUIContent(RealWorldTerrainResources.saveIcon, "Export settings"), toolbarButtonStyle,
|
||||
GUILayout.ExpandWidth(false)))
|
||||
ExportSettings();
|
||||
|
||||
GUILayout.Label("", EditorStyles.toolbarButton);
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public static void OpenWindow()
|
||||
{
|
||||
if (wnd != null) wnd.Close();
|
||||
|
||||
wnd = GetWindow<RealWorldTerrainSplatPrototypeGenerator>("SplatPrototype RealWorldTerrainWindow", true);
|
||||
}
|
||||
|
||||
public static void OpenWindow(RealWorldTerrainItem item)
|
||||
{
|
||||
OpenWindow();
|
||||
wnd.item = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e33f0538f18655141ae46113e4f6af95
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,117 @@
|
||||
/* INFINITY CODE 2013-2019 */
|
||||
/* http://www.infinity-code.com */
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using InfinityCode.RealWorldTerrain.Tools;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain
|
||||
{
|
||||
[Serializable]
|
||||
public class RealWorldTerrainSplatPrototypeItem
|
||||
{
|
||||
public List<RealWorldTerrainColorItem> colors;
|
||||
public bool deleted;
|
||||
private readonly bool isBase;
|
||||
private bool expanded = true;
|
||||
private Texture2D texture;
|
||||
private Vector2 tileOffset = Vector2.zero;
|
||||
private Vector2 tileSize = new Vector2(15, 15);
|
||||
|
||||
public SplatPrototype splat
|
||||
{
|
||||
get
|
||||
{
|
||||
return new SplatPrototype { texture = texture, tileSize = tileSize, tileOffset = tileOffset };
|
||||
}
|
||||
}
|
||||
|
||||
public TerrainLayer terrainLayer
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TerrainLayer { diffuseTexture = texture, tileSize = tileSize, tileOffset = tileOffset };
|
||||
}
|
||||
}
|
||||
|
||||
public RealWorldTerrainSplatPrototypeItem(bool isBase = false)
|
||||
{
|
||||
this.isBase = isBase;
|
||||
colors = new List<RealWorldTerrainColorItem>();
|
||||
}
|
||||
|
||||
public XmlNode GetNode(XmlDocument doc)
|
||||
{
|
||||
XmlElement node = doc.CreateElement("SplatPrototype");
|
||||
node.SetAttribute("tileSizeX", tileSize.x.ToString());
|
||||
node.SetAttribute("tileSizeY", tileSize.y.ToString());
|
||||
node.SetAttribute("tileOffsetX", tileOffset.x.ToString());
|
||||
node.SetAttribute("tileOffsetY", tileOffset.y.ToString());
|
||||
node.SetAttribute("textureID", (texture != null) ? texture.GetInstanceID().ToString() : "-1");
|
||||
|
||||
foreach (RealWorldTerrainColorItem color in colors) node.AppendChild(color.GetNode(doc));
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
public void OnGUI(int index = 0)
|
||||
{
|
||||
if (!isBase)
|
||||
{
|
||||
expanded = EditorGUILayout.Foldout(expanded, "SplatPrototype " + index);
|
||||
if (expanded)
|
||||
{
|
||||
OnGUIProp("Texture: ");
|
||||
int colorIndex = 1;
|
||||
foreach (RealWorldTerrainColorItem color in colors) color.OnGUI(colorIndex++);
|
||||
colors.RemoveAll(c => c.deleted);
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
if (GUILayout.Button("Add color"))
|
||||
colors.Add(new RealWorldTerrainColorItem());
|
||||
|
||||
if (GUILayout.Button("Generate preview"))
|
||||
RealWorldTerrainSplatPrototypeGenerator.GeneratePreview(this);
|
||||
|
||||
if (GUILayout.Button("Remove SplatPrototype"))
|
||||
deleted = true;
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
else
|
||||
OnGUIProp("Base texture: ");
|
||||
}
|
||||
|
||||
private void OnGUIProp(string label)
|
||||
{
|
||||
texture = (Texture2D)EditorGUILayout.ObjectField(label, texture, typeof(Texture2D), false);
|
||||
tileSize = EditorGUILayout.Vector2Field("Tile size", tileSize);
|
||||
tileOffset = EditorGUILayout.Vector2Field("Tile offset", tileOffset);
|
||||
}
|
||||
|
||||
public void SetNode(XmlElement node)
|
||||
{
|
||||
tileSize.x = float.Parse(node.GetAttribute("tileSizeX"));
|
||||
tileSize.y = float.Parse(node.GetAttribute("tileSizeY"));
|
||||
tileOffset.x = float.Parse(node.GetAttribute("tileOffsetX"));
|
||||
tileOffset.y = float.Parse(node.GetAttribute("tileOffsetY"));
|
||||
int textureID = int.Parse(node.GetAttribute("textureID"));
|
||||
if (textureID != -1) texture = (Texture2D)EditorUtility.InstanceIDToObject(textureID);
|
||||
else texture = null;
|
||||
|
||||
colors = new List<RealWorldTerrainColorItem>();
|
||||
|
||||
foreach (XmlElement cNode in node.ChildNodes)
|
||||
{
|
||||
RealWorldTerrainColorItem color = new RealWorldTerrainColorItem();
|
||||
color.SetNode(cNode);
|
||||
colors.Add(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d46b1b68d1798da479475e66ae2877fd
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,30 @@
|
||||
/* INFINITY CODE 2013-2019 */
|
||||
/* http://www.infinity-code.com */
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfinityCode.RealWorldTerrain.Tools
|
||||
{
|
||||
public static class RealWorldTerrainUpdateNeighbors
|
||||
{
|
||||
public static void Update(RealWorldTerrainContainer container)
|
||||
{
|
||||
RealWorldTerrainVector2i terrainCount = container.terrainCount;
|
||||
RealWorldTerrainItem[] terrains = container.terrains;
|
||||
for (int x = 0; x < terrainCount.x; x++)
|
||||
{
|
||||
for (int y = 0; y < terrainCount.y; y++)
|
||||
{
|
||||
int index = y * terrainCount.x + x;
|
||||
Terrain bottom = y > 0 ? terrains[index - terrainCount.x].terrain : null;
|
||||
Terrain top = y < terrainCount.y - 1 ? terrains[index + terrainCount.x].terrain : null;
|
||||
Terrain left = x > 0 ? terrains[index - 1].terrain : null;
|
||||
Terrain right = x < terrainCount.x - 1 ? terrains[index + 1].terrain : null;
|
||||
terrains[index].terrain.SetNeighbors(left, top, right, bottom);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (RealWorldTerrainItem terrain in terrains) terrain.terrain.Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 533bd5f8867ea8242aaeca44afe78c04
|
||||
timeCreated: 1523353454
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user