using UnityEngine; using System.Collections; using System.IO; namespace Gaia { /// /// Unity integration extension to the heightmap class /// public class UnityHeightMap : HeightMap { public Bounds m_boundsWU = new Bounds(); /// /// Create a unity heightmap /// public UnityHeightMap() : base() { } /// /// Create a unity heightmap by loading from a source file /// /// Paht of file to load public UnityHeightMap(string path) : base(path) { m_boundsWU.size = new Vector3(m_widthX, 0f, m_depthZ); m_isDirty = false; } /// /// Create a unity heightmap by loading a TextAsset /// /// The text asset to be loaded public UnityHeightMap(TextAsset source) : base(source.bytes) { m_boundsWU.size = new Vector3(m_widthX, 0f, m_depthZ); m_isDirty = false; } /// /// Create a unity heightmap by replicating a source file /// /// Source heightmap public UnityHeightMap(UnityHeightMap source) : base(source) { m_boundsWU = source.m_boundsWU; m_isDirty = false; } /// /// Create from terrain /// /// public UnityHeightMap(Terrain terrain) : base() { LoadFromTerrain(terrain); } /// /// Create a heightmap by reading in and processing an image file /// /// Bounds in world units /// Source file /// Width /// Depth public UnityHeightMap(Bounds bounds, string sourceFile) : base(sourceFile) { m_boundsWU = bounds; m_isDirty = false; } /// /// Initializes a new instance of the class from a texture. /// /// Texture. public UnityHeightMap(Texture2D texture, GaiaConstants.ImageChannel channel = GaiaConstants.ImageChannel.R) : base() { LoadFromTexture2D(texture, channel); m_isDirty = false; } /// /// Get bounds in world units /// /// Terrain bounds in world units public Bounds GetBoundsWU() { return m_boundsWU; } /// /// Get position in world units /// /// Position in world units public Vector3 GetPositionWU() { Vector3 pos = m_boundsWU.center - m_boundsWU.extents; return pos; } /// /// Set the bounds in world units /// /// public void SetBoundsWU(Bounds bounds) { m_boundsWU = bounds; m_isDirty = true; } /// /// Set the position in world units /// /// public void SetPositionWU(Vector3 position) { m_boundsWU.center = position; m_isDirty = true; } /// /// Load this height map from the supplied terrain /// /// Terrain to load public void LoadFromTerrain(Terrain terrain) { Reset(); m_boundsWU.center = terrain.transform.position; m_boundsWU.size = terrain.terrainData.size; m_boundsWU.center += m_boundsWU.extents; m_widthX = terrain.terrainData.heightmapResolution; m_depthZ = terrain.terrainData.heightmapResolution; m_widthInvX = 1f / (float)(m_widthX); m_depthInvZ = 1f / (float)(m_depthZ); m_heights = terrain.terrainData.GetHeights(0, 0, m_widthX, m_depthZ); m_isPowerOf2 = Gaia.GaiaUtils.Math_IsPowerOf2(m_widthX) && Gaia.GaiaUtils.Math_IsPowerOf2(m_depthZ); m_isDirty = false; } /// /// Update the supplied terrain with the content of this heightmap - will scale if necessary /// /// Terrain to uppdate public void SaveToTerrain(Terrain terrain) { if (terrain == null) { return; } //Get terrain stats int terWidth = terrain.terrainData.heightmapResolution; int terDepth = terrain.terrainData.heightmapResolution; //Direct one to one mapping if (m_widthX == terWidth && m_depthZ == terDepth) { terrain.terrainData.SetHeights(0, 0, m_heights); m_isDirty = false; return; } //Build new array and scale it to the size of the terrain float[,] heights = new float[terWidth, terDepth]; for (int x = 0; x < terWidth; x++) { for (int z = 0; z < terDepth; z++) { heights[x,z] = this[((float)x / (float)terWidth), ((float)z / (float)terDepth)]; } } //And apply it terrain.terrainData.SetHeights(0, 0, heights); m_isDirty = false; } public void LoadFromTexture2D(Texture2D texture, GaiaConstants.ImageChannel channel) { //Check if it is readable - if not then make it readable Gaia.GaiaUtils.MakeTextureReadable(texture); //Make sure its not compressed Gaia.GaiaUtils.MakeTextureUncompressed(texture); //And load m_widthX = texture.width; m_depthZ = texture.height; m_widthInvX = 1f / (float)(m_widthX); m_depthInvZ = 1f / (float)(m_depthZ); m_heights = new float[m_widthX, m_depthZ]; m_isPowerOf2 = Gaia.GaiaUtils.Math_IsPowerOf2(m_widthX) && Gaia.GaiaUtils.Math_IsPowerOf2(m_depthZ); if (channel == GaiaConstants.ImageChannel.R) { for (int x = 0; x < m_widthX; x++) { for (int z = 0; z < m_depthZ; z++) { m_heights[x, z] = texture.GetPixel(x, z).r; } } } else if (channel == GaiaConstants.ImageChannel.G) { for (int x = 0; x < m_widthX; x++) { for (int z = 0; z < m_depthZ; z++) { m_heights[x, z] = texture.GetPixel(x, z).g; } } } else if (channel == GaiaConstants.ImageChannel.B) { for (int x = 0; x < m_widthX; x++) { for (int z = 0; z < m_depthZ; z++) { m_heights[x, z] = texture.GetPixel(x, z).b; } } } else { for (int x = 0; x < m_widthX; x++) { for (int z = 0; z < m_depthZ; z++) { m_heights[x, z] = texture.GetPixel(x, z).a; } } } m_isDirty = false; } /// /// Read heightmap from the supplied RAW file supplied as a text asset /// /// True on success public void ReadRawFromTextAsset(TextAsset asset) { using (Stream s = new MemoryStream(asset.bytes)) { using (BinaryReader br = new BinaryReader(s)) { m_widthX = m_depthZ = Mathf.CeilToInt(Mathf.Sqrt(s.Length / 2)); m_widthInvX = 1f / (float)(m_widthX); m_depthInvZ = 1f / (float)(m_depthZ); m_heights = new float[m_widthX, m_depthZ]; m_isPowerOf2 = Gaia.GaiaUtils.Math_IsPowerOf2(m_widthX) && Gaia.GaiaUtils.Math_IsPowerOf2(m_depthZ); for (int hmX = 0; hmX < m_widthX; hmX++) { for (int hmZ = 0; hmZ < m_depthZ; hmZ++) { m_heights[hmX, hmZ] = (float)br.ReadUInt16() / 65535.0f; } } } s.Close(); } m_isDirty = false; } /// /// Returns a heightmap that only contains the single color channel info of the original map /// /// The color channel to return /// public UnityHeightMap GetColorChannelHeightMap() { UnityHeightMap returnMap = new UnityHeightMap(this); for (int hmX = 0; hmX < m_widthX; hmX++) { for (int hmZ = 0; hmZ < m_depthZ; hmZ++) { returnMap.m_heights[hmX, hmZ] = this.m_heights[hmX, hmZ] / 3f; } } return returnMap; } /// /// Calculate the normals for the heightmap /// /// Normals for the heightmap public Texture2D CalculateNormals() { float terrainHeight = m_widthX / 2f; int width = m_widthX; int height = m_depthZ; float ux = 1.0f / (width - 1.0f); float uy = 1.0f / (height - 1.0f); float scaleX = terrainHeight / (float)m_widthX; float scaleY = terrainHeight / (float)m_depthZ; float[] heights = Heights1D(); Texture2D normalMap = new Texture2D(width, height, TextureFormat.RGBAFloat, false, true); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int xp1 = (x == width - 1) ? x : x + 1; int xn1 = (x == 0) ? x : x - 1; int yp1 = (y == height - 1) ? y : y + 1; int yn1 = (y == 0) ? y : y - 1; float l = heights[xn1 + y * width] * scaleX; float r = heights[xp1 + y * width] * scaleX; float b = heights[x + yn1 * width] * scaleY; float t = heights[x + yp1 * width] * scaleY; float dx = (r - l) / (2.0f * ux); float dy = (t - b) / (2.0f * uy); Vector3 normal; normal.x = -dx; normal.y = -dy; normal.z = 1; normal.Normalize(); Color pixel; pixel.r = normal.x * 0.5f + 0.5f; pixel.g = normal.y * 0.5f + 0.5f; pixel.b = normal.z; pixel.a = 1.0f; normalMap.SetPixel(x, y, pixel); } } normalMap.Apply(); return normalMap; } } }