Files
2024-11-20 15:21:28 +01:00

348 lines
11 KiB
C#

using UnityEngine;
using System.Collections;
using System.IO;
namespace Gaia
{
/// <summary>
/// Unity integration extension to the heightmap class
/// </summary>
public class UnityHeightMap : HeightMap
{
public Bounds m_boundsWU = new Bounds();
/// <summary>
/// Create a unity heightmap
/// </summary>
public UnityHeightMap() : base()
{
}
/// <summary>
/// Create a unity heightmap by loading from a source file
/// </summary>
/// <param name="path">Paht of file to load</param>
public UnityHeightMap(string path) : base(path)
{
m_boundsWU.size = new Vector3(m_widthX, 0f, m_depthZ);
m_isDirty = false;
}
/// <summary>
/// Create a unity heightmap by loading a TextAsset
/// </summary>
/// <param name="source">The text asset to be loaded</param>
public UnityHeightMap(TextAsset source) : base(source.bytes)
{
m_boundsWU.size = new Vector3(m_widthX, 0f, m_depthZ);
m_isDirty = false;
}
/// <summary>
/// Create a unity heightmap by replicating a source file
/// </summary>
/// <param name="source">Source heightmap</param>
public UnityHeightMap(UnityHeightMap source) : base(source)
{
m_boundsWU = source.m_boundsWU;
m_isDirty = false;
}
/// <summary>
/// Create from terrain
/// </summary>
/// <param name="terrain"></param>
public UnityHeightMap(Terrain terrain) : base()
{
LoadFromTerrain(terrain);
}
/// <summary>
/// Create a heightmap by reading in and processing an image file
/// </summary>
/// <param name="bounds">Bounds in world units</param>
/// <param name="sourceFile">Source file</param>
/// <param name="width">Width</param>
/// <param name="depth">Depth</param>
public UnityHeightMap(Bounds bounds, string sourceFile) : base(sourceFile)
{
m_boundsWU = bounds;
m_isDirty = false;
}
/// <summary>
/// Initializes a new instance of the <see cref="Gaia.UnityHeightMap"/> class from a texture.
/// </summary>
/// <param name="texture">Texture.</param>
public UnityHeightMap(Texture2D texture, GaiaConstants.ImageChannel channel = GaiaConstants.ImageChannel.R) : base()
{
LoadFromTexture2D(texture, channel);
m_isDirty = false;
}
/// <summary>
/// Get bounds in world units
/// </summary>
/// <returns>Terrain bounds in world units</returns>
public Bounds GetBoundsWU()
{
return m_boundsWU;
}
/// <summary>
/// Get position in world units
/// </summary>
/// <returns>Position in world units</returns>
public Vector3 GetPositionWU()
{
Vector3 pos = m_boundsWU.center - m_boundsWU.extents;
return pos;
}
/// <summary>
/// Set the bounds in world units
/// </summary>
/// <param name="bounds"></param>
public void SetBoundsWU(Bounds bounds)
{
m_boundsWU = bounds;
m_isDirty = true;
}
/// <summary>
/// Set the position in world units
/// </summary>
/// <param name="position"></param>
public void SetPositionWU(Vector3 position)
{
m_boundsWU.center = position;
m_isDirty = true;
}
/// <summary>
/// Load this height map from the supplied terrain
/// </summary>
/// <param name="terrain">Terrain to load</param>
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;
}
/// <summary>
/// Update the supplied terrain with the content of this heightmap - will scale if necessary
/// </summary>
/// <param name="terrain">Terrain to uppdate</param>
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;
}
/// <summary>
/// Read heightmap from the supplied RAW file supplied as a text asset
/// </summary>
/// <returns>True on success</returns>
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;
}
/// <summary>
/// Returns a heightmap that only contains the single color channel info of the original map
/// </summary>
/// <param name="channel">The color channel to return</param>
/// <returns></returns>
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;
}
/// <summary>
/// Calculate the normals for the heightmap
/// </summary>
/// <returns>Normals for the heightmap</returns>
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;
}
}
}