473 lines
17 KiB
C#
473 lines
17 KiB
C#
//////////////////////////////////////////////////////
|
|
// Terain To Mesh
|
|
// Copyright (c) Jason Booth
|
|
//////////////////////////////////////////////////////
|
|
|
|
|
|
using UnityEngine;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEditor;
|
|
using JBooth.MicroSplat;
|
|
|
|
namespace JBooth.TerrainToMesh
|
|
{
|
|
#if __MICROSPLAT__ && __MICROSPLAT_MESHTERRAIN__
|
|
public class TerrainToMesh : EditorWindow
|
|
{
|
|
[MenuItem ("Window/MicroSplat/Terrain To Mesh")]
|
|
public static void ShowWindow ()
|
|
{
|
|
var window = GetWindow<JBooth.TerrainToMesh.TerrainToMesh> ();
|
|
if (window != null)
|
|
{
|
|
window.Show ();
|
|
window.Init ();
|
|
}
|
|
}
|
|
|
|
public List<Terrain> selectedTerrains = new List<Terrain> ();
|
|
|
|
void Init ()
|
|
{
|
|
Object [] objs = Selection.GetFiltered (typeof (Terrain), SelectionMode.Editable | SelectionMode.Deep);
|
|
selectedTerrains.Clear ();
|
|
for (int i = 0; i < objs.Length; ++i)
|
|
{
|
|
Terrain t = objs [i] as Terrain;
|
|
if (t == null)
|
|
continue;
|
|
|
|
selectedTerrains.Add (t);
|
|
}
|
|
}
|
|
|
|
void OnSelectionChange ()
|
|
{
|
|
Init ();
|
|
}
|
|
|
|
public class Settings
|
|
{
|
|
public int chunkDiv = 1;
|
|
public int subDiv = 64;
|
|
public bool convertMaterial = true;
|
|
public bool deleteTerrains = false;
|
|
public bool generateLowResShader = true;
|
|
public bool addColliders = false;
|
|
public string prefix = "";
|
|
public string postfix = "";
|
|
}
|
|
|
|
public Settings settings = new Settings ();
|
|
|
|
static GUIContent CChunkDiv = new GUIContent ("Chunks", "Number of objects to break terrains into per side");
|
|
static GUIContent CSubDiv = new GUIContent ("Sub Divisions", "Sub divisions per terrain chunk per side");
|
|
static GUIContent CAddColliders = new GUIContent ("Add Colliders", "Add colliders to terrain chunks. This is required for terrain blending");
|
|
|
|
void OnGUI ()
|
|
{
|
|
if (selectedTerrains.Count == 0)
|
|
{
|
|
EditorGUILayout.HelpBox ("Select terrains with MicroSplat on them to continue", MessageType.Info);
|
|
return;
|
|
}
|
|
for (int i = 0; i < selectedTerrains.Count; ++i)
|
|
{
|
|
if (selectedTerrains [0] == null || selectedTerrains [0].GetComponent<MicroSplatTerrain> () == null)
|
|
{
|
|
EditorGUILayout.HelpBox ("Terrain must be setup with MicroSplat to convert", MessageType.Error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
settings.chunkDiv = EditorGUILayout.IntSlider (CChunkDiv, settings.chunkDiv, 1, 32);
|
|
settings.subDiv = EditorGUILayout.IntSlider (CSubDiv, settings.subDiv, 16, 170);
|
|
settings.addColliders = EditorGUILayout.Toggle (CAddColliders, settings.addColliders);
|
|
|
|
settings.convertMaterial = EditorGUILayout.Toggle ("Convert Material", settings.convertMaterial);
|
|
settings.generateLowResShader = EditorGUILayout.Toggle ("Generate Separate Shader", settings.generateLowResShader);
|
|
settings.deleteTerrains = EditorGUILayout.Toggle ("Delete Terrains", settings.deleteTerrains);
|
|
|
|
settings.prefix = EditorGUILayout.TextField ("Naming Prefix", settings.prefix);
|
|
settings.postfix = EditorGUILayout.TextField ("Naming Postfix", settings.postfix);
|
|
|
|
if (GUILayout.Button ("Convert"))
|
|
{
|
|
if (selectedTerrains [0] == null)
|
|
{
|
|
Debug.LogError ("Select a terrain with MicroSplat on it.");
|
|
return;
|
|
}
|
|
var mst = selectedTerrains [0].GetComponent<MicroSplatTerrain> ();
|
|
if (mst == null || mst.templateMaterial == null)
|
|
{
|
|
Debug.Log ("Did not find MicroSplatTerrain component and material template on terrain");
|
|
}
|
|
else
|
|
{
|
|
|
|
string baseDir = JBooth.MicroSplat.MicroSplatUtilities.RelativePathFromAsset (mst.templateMaterial);
|
|
if (settings.generateLowResShader)
|
|
{
|
|
baseDir += "/MeshTerrain/MicroSplatData";
|
|
if (!System.IO.Directory.Exists (baseDir))
|
|
{
|
|
System.IO.Directory.CreateDirectory (baseDir);
|
|
}
|
|
}
|
|
|
|
|
|
for (int i = 0; i < selectedTerrains.Count; ++i)
|
|
{
|
|
var meshPath = baseDir + "/" + selectedTerrains [i].name + "_meshes.asset";
|
|
Mesh root = new Mesh ();
|
|
root.name = "Meshes";
|
|
|
|
AssetDatabase.CreateAsset (root, meshPath);
|
|
Convert (selectedTerrains [i], baseDir, root, settings);
|
|
}
|
|
AssetDatabase.SaveAssets ();
|
|
AssetDatabase.Refresh();
|
|
MicroSplatObject.SyncAll();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
static Mesh CreateSubChunk (Terrain t, Vector3 worldStart, Vector2 uvStart, int dx, int dy, int div, Settings s)
|
|
{
|
|
int chunkDiv = s.chunkDiv;
|
|
int subDiv = s.subDiv;
|
|
|
|
float size = t.terrainData.size.x;
|
|
float pSize = size * 0.5f;
|
|
pSize /= chunkDiv;
|
|
|
|
string meshName = div > 0 ? t.name + "_" + dx + "_" + dy : t.name;
|
|
Mesh mesh = new Mesh ();
|
|
mesh.name = meshName;
|
|
Vector3 [] vertices = new Vector3 [subDiv * subDiv];
|
|
Vector3 [] normals = new Vector3 [vertices.Length];
|
|
for (int z = 0; z < subDiv; z++)
|
|
{
|
|
float zPos = ((float)z / (subDiv - 1) - .5f) * pSize * 2;
|
|
for (int x = 0; x < subDiv; x++)
|
|
{
|
|
float xPos = ((float)x / (subDiv - 1) - .5f) * pSize * 2;
|
|
|
|
float uvX = (float)x / (float)(subDiv - 1);
|
|
float uvY = (float)z / (float)(subDiv - 1);
|
|
uvX /= (float)chunkDiv;
|
|
uvY /= (float)chunkDiv;
|
|
uvX += uvStart.x;
|
|
uvY += uvStart.y;
|
|
|
|
Vector3 worldPos = new Vector3 (xPos + pSize + worldStart.x, 0, zPos + pSize + worldStart.y);
|
|
worldPos += t.GetPosition ();
|
|
float yPos = t.SampleHeight (worldPos);
|
|
Vector3 nm = t.terrainData.GetInterpolatedNormal (xPos + pSize, zPos + pSize);
|
|
vertices [x + z * subDiv] = new Vector3 (xPos + pSize + worldStart.x, yPos, zPos + pSize + worldStart.y);
|
|
normals [x + z * subDiv] = new Vector3 (nm.x, nm.y, nm.z);
|
|
}
|
|
}
|
|
|
|
|
|
Vector2 [] uvs = new Vector2 [vertices.Length];
|
|
for (int v = 0; v < subDiv; v++)
|
|
{
|
|
for (int u = 0; u < subDiv; u++)
|
|
{
|
|
Vector2 uv = new Vector2 ((float)u / (subDiv - 1), (float)v / (subDiv - 1));
|
|
uv.x /= chunkDiv;
|
|
uv.y /= chunkDiv;
|
|
uv.x += uvStart.x;
|
|
uv.y += uvStart.y;
|
|
uvs [u + v * subDiv] = uv;
|
|
}
|
|
}
|
|
int nbFaces = (subDiv - 1) * (subDiv - 1);
|
|
int [] triangles = new int [nbFaces * 6];
|
|
int tidx = 0;
|
|
for (int face = 0; face < nbFaces; face++)
|
|
{
|
|
// Retrieve lower left corner from face ind
|
|
int i = face % (subDiv - 1) + (face / (subDiv - 1) * subDiv);
|
|
|
|
triangles [tidx++] = i + subDiv;
|
|
triangles [tidx++] = i + 1;
|
|
triangles [tidx++] = i;
|
|
|
|
triangles [tidx++] = i + subDiv;
|
|
triangles [tidx++] = i + subDiv + 1;
|
|
triangles [tidx++] = i + 1;
|
|
}
|
|
|
|
mesh.vertices = vertices;
|
|
mesh.normals = normals;
|
|
mesh.uv = uvs;
|
|
mesh.triangles = triangles;
|
|
|
|
mesh.RecalculateNormals ();
|
|
mesh.RecalculateTangents ();
|
|
mesh.RecalculateBounds ();
|
|
|
|
|
|
return mesh;
|
|
}
|
|
|
|
static Texture2D SaveHeightTexture (RenderTexture rt, string basePath)
|
|
{
|
|
Texture2D tex = new Texture2D (rt.width, rt.height, TextureFormat.R16, true, true);
|
|
var old = RenderTexture.active;
|
|
|
|
RenderTexture.active = rt;
|
|
tex.ReadPixels (new Rect (0, 0, tex.width, tex.height), 0, 0);
|
|
tex.Apply ();
|
|
RenderTexture.active = old;
|
|
|
|
JBooth.MicroSplat.HDTextureImporter.Write (tex, basePath + "_height", true, true);
|
|
AssetDatabase.Refresh ();
|
|
return AssetDatabase.LoadAssetAtPath<Texture2D> (basePath + "_height.hdtexture");
|
|
|
|
}
|
|
|
|
public static void Convert (Terrain t, string baseDir, Mesh root, Settings s)
|
|
{
|
|
int chunkDiv = s.chunkDiv;
|
|
|
|
var mst = t.GetComponent<MicroSplatTerrain> ();
|
|
if (mst == null)
|
|
{
|
|
Debug.LogError ("MicroSplat Terrain missing");
|
|
return;
|
|
}
|
|
|
|
var oldInstance = mst.terrain.drawInstanced;
|
|
mst.terrain.drawInstanced = false;
|
|
|
|
float chunkSkip = t.terrainData.size.x / (float)chunkDiv;
|
|
|
|
GameObject meshes = new GameObject (s.prefix + t.name + s.postfix);
|
|
meshes.transform.position = t.transform.position;
|
|
|
|
#if __MICROSPLAT_STREAMS__
|
|
var origst = mst.GetComponent<StreamManager> ();
|
|
if (origst != null)
|
|
{
|
|
var newst = meshes.AddComponent<StreamManager> ();
|
|
EditorUtility.CopySerialized (origst, newst);
|
|
}
|
|
#endif
|
|
|
|
MicroSplatMeshTerrain msm = meshes.AddComponent<MicroSplatMeshTerrain> ();
|
|
|
|
|
|
|
|
msm.perPixelNormal = mst.perPixelNormal;
|
|
msm.blendMat = mst.blendMat;
|
|
msm.propData = mst.propData;
|
|
msm.streamTexture = mst.streamTexture;
|
|
|
|
|
|
|
|
if (mst.keywordSO.IsKeywordEnabled("_DYNAMICFLOWS") || mst.keywordSO.IsKeywordEnabled("_TERRAINBLENDING"))
|
|
{
|
|
msm.terrainDescriptor.heightMap = SaveHeightTexture(mst.terrain.terrainData.heightmapTexture, baseDir + "/" + mst.name);
|
|
}
|
|
if (mst.keywordSO.IsKeywordEnabled("_PERPIXNORMAL") || mst.keywordSO.IsKeywordEnabled("_TERRAINBLENDING") || (mst.terrain != null && oldInstance))
|
|
{
|
|
msm.perPixelNormal = MicroSplatTerrainEditor.GenerateTerrainNormalMap(mst);
|
|
msm.terrainDescriptor.normalMap = msm.perPixelNormal;
|
|
}
|
|
#if (VEGETATION_STUDIO || VEGETATION_STUDIO_PRO)
|
|
msm.vsGrassMap = mst.vsGrassMap;
|
|
msm.vsShadowMap = mst.vsShadowMap;
|
|
#endif
|
|
|
|
#if __MICROSPLAT_ALPHAHOLE__
|
|
msm.clipMap = mst.clipMap;
|
|
#endif
|
|
|
|
#if __MICROSPLAT_PROCTEX__
|
|
msm.procBiomeMask = mst.procBiomeMask;
|
|
msm.procBiomeMask2 = mst.procBiomeMask2;
|
|
msm.procTexCfg = mst.procTexCfg;
|
|
msm.cavityMap = mst.cavityMap;
|
|
#endif
|
|
|
|
#if __MICROSPLAT_SCATTER__
|
|
msm.scatterMapOverride = mst.scatterMapOverride;
|
|
#endif
|
|
|
|
#if __MICROSPLAT_GLOBALTEXTURE__
|
|
msm.tintMapOverride = mst.tintMapOverride;
|
|
msm.geoTextureOverride = mst.geoTextureOverride;
|
|
msm.globalNormalOverride = mst.globalNormalOverride;
|
|
msm.globalEmisOverride = mst.globalEmisOverride;
|
|
msm.globalSAOMOverride = mst.globalSAOMOverride;
|
|
#endif
|
|
|
|
#if __MICROSPLAT_SNOW__
|
|
msm.snowMaskOverride = mst.snowMaskOverride;
|
|
#endif
|
|
|
|
|
|
if (s.convertMaterial)
|
|
{
|
|
if (s.generateLowResShader)
|
|
{
|
|
if (!System.IO.File.Exists (baseDir + "/MicroSplat.mat"))
|
|
{
|
|
System.IO.File.Copy(AssetDatabase.GetAssetPath(mst.templateMaterial), baseDir + "/MicroSplat.mat");
|
|
}
|
|
if (!System.IO.File.Exists (baseDir + "/MicroSplat_keywords.asset"))
|
|
{
|
|
System.IO.File.Copy(AssetDatabase.GetAssetPath (mst.keywordSO), baseDir + "/MicroSplat_keywords.asset");
|
|
}
|
|
if (!System.IO.File.Exists (baseDir + "/MicroSplat.shader"))
|
|
{
|
|
System.IO.File.Copy(AssetDatabase.GetAssetPath (mst.templateMaterial.shader), baseDir + "/MicroSplat.shader");
|
|
}
|
|
|
|
AssetDatabase.Refresh ();
|
|
|
|
msm.templateMaterial = AssetDatabase.LoadAssetAtPath<Material> (baseDir + "/MicroSplat.mat");
|
|
msm.templateMaterial.shader = AssetDatabase.LoadAssetAtPath<Shader> (baseDir + "/MicroSplat.shader");
|
|
msm.keywordSO = AssetDatabase.LoadAssetAtPath<MicroSplatKeywords> (baseDir + "/MicroSplat_keywords.asset");
|
|
msm.templateMaterial.CopyPropertiesFromMaterial (mst.templateMaterial);
|
|
if (!msm.keywordSO.IsKeywordEnabled ("_MICROMESHTERRAIN"))
|
|
{
|
|
msm.keywordSO.EnableKeyword ("_MICROMESHTERRAIN");
|
|
}
|
|
if (msm.keywordSO.IsKeywordEnabled ("_OUTPUTDIGGER"))
|
|
{
|
|
msm.keywordSO.DisableKeyword ("_OUTPUTDIGGER");
|
|
}
|
|
if (msm.keywordSO.IsKeywordEnabled ("_MICRODIGGERMESH"))
|
|
{
|
|
msm.keywordSO.DisableKeyword ("_MICRODIGGERMESH");
|
|
}
|
|
if (oldInstance)
|
|
{
|
|
msm.keywordSO.EnableKeyword ("_PERPIXNORMAL");
|
|
}
|
|
if (msm.keywordSO.IsKeywordEnabled("_MICROTERRAIN"))
|
|
{
|
|
msm.keywordSO.DisableKeyword("_MICROTERRAIN");
|
|
}
|
|
|
|
MicroSplatShaderGUI.MicroSplatCompiler c = new MicroSplatShaderGUI.MicroSplatCompiler ();
|
|
c.Compile (msm.templateMaterial);
|
|
}
|
|
}
|
|
|
|
|
|
// export splat textures
|
|
var textures = t.terrainData.alphamapTextures;
|
|
for (int i = 0; i < textures.Length; ++i)
|
|
{
|
|
var path = baseDir + "/splat_" + t.name + i.ToString ();
|
|
var bytes = textures [i].EncodeToTGA ();
|
|
System.IO.File.WriteAllBytes (path + ".tga", bytes);
|
|
AssetDatabase.Refresh ();
|
|
var ai = AssetImporter.GetAtPath (MicroSplat.MicroSplatUtilities.MakeRelativePath (path + ".tga"));
|
|
|
|
|
|
var ti = ai as TextureImporter;
|
|
if (ti != null)
|
|
{
|
|
var ps = ti.GetDefaultPlatformTextureSettings ();
|
|
|
|
if (ti.isReadable == true ||
|
|
ti.wrapMode != TextureWrapMode.Clamp || ps.format != TextureImporterFormat.RGBA32 ||
|
|
ps.textureCompression != TextureImporterCompression.Compressed ||
|
|
ps.overridden != true ||
|
|
ti.filterMode != FilterMode.Bilinear ||
|
|
ti.sRGBTexture != false)
|
|
{
|
|
ti.sRGBTexture = false;
|
|
ti.filterMode = FilterMode.Bilinear;
|
|
ti.mipmapEnabled = true;
|
|
ti.wrapMode = TextureWrapMode.Clamp;
|
|
ti.isReadable = false;
|
|
ps.format = TextureImporterFormat.Automatic;
|
|
ps.textureCompression = TextureImporterCompression.Compressed;
|
|
ps.overridden = false;
|
|
ti.SetPlatformTextureSettings (ps);
|
|
ti.SaveAndReimport ();
|
|
}
|
|
}
|
|
}
|
|
|
|
msm.controlTextures = new Texture2D [textures.Length];
|
|
for (int i = 0; i < textures.Length; ++i)
|
|
{
|
|
var p = MicroSplat.MicroSplatUtilities.MakeRelativePath (baseDir + "/splat_" + t.gameObject.name + i + ".tga");
|
|
|
|
Texture2D tex = AssetDatabase.LoadAssetAtPath<Texture2D> (p);
|
|
msm.controlTextures [i] = tex;
|
|
}
|
|
|
|
MeshRenderer [] rends = new MeshRenderer [chunkDiv * chunkDiv];
|
|
int chunkIdx = 0;
|
|
for (int chunkX = 0; chunkX < chunkDiv; ++chunkX)
|
|
{
|
|
for (int chunkY = 0; chunkY < chunkDiv; ++chunkY)
|
|
{
|
|
Vector2 worldStart = new Vector2 (chunkX * chunkSkip, chunkY * chunkSkip);
|
|
Vector2 uvStart = new Vector2 (worldStart.x / t.terrainData.size.x, worldStart.y / t.terrainData.size.x);
|
|
Mesh mesh = CreateSubChunk (t, worldStart, uvStart, chunkX, chunkY, chunkDiv, s);
|
|
AssetDatabase.AddObjectToAsset (mesh, root);
|
|
GameObject go = new GameObject (t.name + "_" + chunkX + "_" + chunkY);
|
|
MeshRenderer rend = go.AddComponent<MeshRenderer> ();
|
|
var filter = go.AddComponent<MeshFilter> ();
|
|
filter.sharedMesh = mesh;
|
|
if (s.addColliders)
|
|
{
|
|
var mc = go.AddComponent<MeshCollider> ();
|
|
mc.sharedMesh = mesh;
|
|
}
|
|
|
|
go.transform.position = t.GetPosition ();
|
|
go.transform.SetParent (meshes.transform);
|
|
|
|
rends [chunkIdx] = rend;
|
|
chunkIdx++;
|
|
|
|
}
|
|
}
|
|
msm.meshTerrains = rends;
|
|
|
|
|
|
if (chunkDiv == 1)
|
|
{
|
|
// remove parent object
|
|
var child = msm.transform.GetChild (0).gameObject;
|
|
var nm = child.AddComponent<MicroSplatMeshTerrain> ();
|
|
EditorUtility.CopySerialized (msm, nm);
|
|
child.transform.SetParent (null, true);
|
|
DestroyImmediate (meshes);
|
|
nm.Sync ();
|
|
}
|
|
else
|
|
{
|
|
MicroSplatObject.SyncAll();
|
|
}
|
|
|
|
mst.terrain.drawInstanced = oldInstance;
|
|
|
|
if (s.deleteTerrains)
|
|
{
|
|
DestroyImmediate (t);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
}
|
|
#endif
|
|
}
|
|
|