using UnityEngine;
using System.IO;
using System;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Gaia
{
public enum ScannerObjectType { Raw, Texture, Mesh, Terrain, Unkown }
///
/// Scanning system - creates stamps
///
public class Scanner : MonoBehaviour
{
//Public
public string m_exportFolder;
public string m_exportFileName = "/Scan " + string.Format("{0:YYYYMMDD-hhmmss}", DateTime.Now);
//public string m_featureName = string.Format("{0}",DateTime.Now);
public float m_baseLevel = 0f;
public float m_scanResolution = 0.1f; //Every 10 cm
public float m_lastScanResolution = 0.1f; //the scan resolution that was used last
public Material m_previewMaterial;
public bool m_exportTextureAlso = false;
public bool m_exportBytesData = false;
public HeightMap m_scanMap;
public GaiaConstants.GaiaProWaterReflectionsQuality m_textureExportResolution = GaiaConstants.GaiaProWaterReflectionsQuality.Resolution1024;
public bool m_boundsSet = false;
public bool m_normalize = true;
public bool m_objectScanned = false;
public ScannerObjectType m_scannerObjectType = ScannerObjectType.Unkown;
public GameObject m_lastScannedMesh;
//Private
private Bounds m_scanBounds;
private int m_scanWidth = 1;
private int m_scanDepth = 1;
private int m_scanHeight = 500;
private Vector3 m_groundOffset = Vector3.zero;
private Vector3 m_groundSize = Vector3.zero;
[SerializeField] private MeshFilter m_meshFilter;
[SerializeField] private MeshRenderer m_meshRenderer;
#region Unity Functions
///
/// Draw gizmos, and make updates / overrides
///
private void OnDrawGizmosSelected()
{
//Housekeep
UpdateScanner();
if (m_scanMap == null)
{
return;
}
//Draw a border to show what we are working on
Gizmos.color = Color.blue;
Gizmos.DrawWireCube(m_scanBounds.center, m_scanBounds.size);
//Draw the ground plane
if (m_baseLevel > 0)
{
m_groundOffset = m_scanBounds.center;
m_groundOffset.y = m_scanBounds.min.y + (m_scanBounds.max.y - m_scanBounds.min.y) * m_baseLevel;
m_groundSize = m_scanBounds.size;
m_groundSize.y = 0.01f;
Gizmos.color = Color.yellow;
Gizmos.DrawCube(m_groundOffset, m_groundSize);
}
}
///
/// Loads on enable
///
private void OnEnable()
{
if (String.IsNullOrEmpty(m_exportFolder))
{
m_exportFolder = GaiaDirectories.GetScannerExportDirectory();
}
SetOrCreateMeshComponents();
}
///
/// Knock ourselves out if we happen to be left on in play mode
///
private void Awake()
{
gameObject.SetActive(false);
}
#endregion
#region Clean Up/Save Functions
///
/// Reset the scanner
///
public void ResetData()
{
m_exportFolder = GaiaDirectories.GetScannerExportDirectory();
m_exportFileName = "/Scan " + string.Format("{0:YYYYMMDD-hhmmss}", DateTime.Now);
m_boundsSet = false;
m_scanBounds = new Bounds(GetPosition(gameObject, null), Vector3.one * 10f);
m_scanWidth = m_scanDepth = m_scanHeight = 0;
m_baseLevel = 0f;
transform.localRotation = Quaternion.identity;
transform.localScale = Vector3.one;
}
///
/// Clears all the data and generated mesh
///
public void Clear()
{
ResetData();
m_scanMap = null;
if (m_meshFilter != null)
{
m_meshFilter.sharedMesh = null;
}
}
///
/// Save the stamp
///
/// Path of saved stamp
public string SaveScan()
{
if (m_scanMap == null || !m_scanMap.HasData())
{
Debug.LogWarning("Cant save scan as none has been loaded");
return null;
}
#if UNITY_EDITOR
EditorUtility.DisplayProgressBar("Generating Texture", "Generating texture", 0.25f);
#endif
//work with a copy for the export - don't want to normalize etc. source data
HeightMap heightmapCopy = new HeightMap(m_scanMap);
heightmapCopy.AddClamped(0f, m_baseLevel, 1f);
if (m_normalize)
{
heightmapCopy.Normalise();
}
//Save preview
string fullpath = m_exportFolder + "/" + m_exportFileName;
GaiaUtils.CompressToMultiChannelFileImage(fullpath, heightmapCopy, heightmapCopy, heightmapCopy, null, TextureFormat.RGBAFloat, GaiaConstants.ImageFileType.Exr, m_baseLevel);
GaiaUtils.SetDefaultStampImportSettings(fullpath + ".exr");
#if UNITY_EDITOR
EditorUtility.DisplayProgressBar("Generating Texture", "Compressing texture", 0.5f);
#endif
GaiaUtils.CompressToSingleChannelFileImage(heightmapCopy.Heights(), m_exportFolder, GaiaConstants.fmtHmTextureFormat, m_exportTextureAlso, false);
//Save stamp
if (m_exportBytesData)
{
#if UNITY_EDITOR
EditorUtility.DisplayProgressBar("Generating Texture", "Generating bytes data", 0.75f);
#endif
m_exportFolder += ".bytes";
float [] metaData = new float[5];
metaData[0] = m_scanWidth;
metaData[1] = m_scanDepth;
metaData[2] = m_scanHeight;
metaData[3] = m_scanResolution;
metaData[4] = m_baseLevel;
byte[] byteData = new byte[metaData.Length * 4];
Buffer.BlockCopy(metaData, 0, byteData, 0, byteData.Length);
heightmapCopy.SetMetaData(byteData);
heightmapCopy.SaveToBinaryFile(m_exportFolder);
}
#if UNITY_EDITOR
EditorUtility.ClearProgressBar();
#endif
return fullpath;
}
#endregion
#region Load Data Functions
///
/// Load the raw file at the path given
///
/// Full path of the raw file
public void LoadRawFile(string path, GaiaConstants.RawByteOrder byteOrder, ref GaiaConstants.RawBitDepth bitDepth, ref int resolution)
{
if (string.IsNullOrEmpty(path))
{
Debug.LogError("Must supply a valid path. Raw load Aborted!");
}
//Clear out the old
ResetData();
//Load up the new
m_scanMap = new HeightMap();
m_scanMap.LoadFromRawFile(path, byteOrder, ref bitDepth, ref resolution);
if (m_scanMap.HasData() == false)
{
Debug.LogError("Unable to load raw file. Raw load aborted.");
return;
}
m_scanWidth = m_scanMap.Width();
m_scanDepth = m_scanMap.Depth();
m_scanHeight = m_scanWidth / 2;
m_scanResolution = 0.1f;
m_scanBounds = new Bounds(GetPosition(gameObject, null), new Vector3(m_scanWidth * m_scanResolution, m_scanWidth * m_scanResolution, m_scanDepth * m_scanResolution));
//m_baseLevel = m_scanMap.GetBaseLevel();
SetOrCreateMeshComponents();
m_meshFilter.sharedMesh = GaiaUtils.CreateMesh(m_scanMap.Heights(), m_scanBounds.size);
if (m_previewMaterial != null)
{
m_previewMaterial.hideFlags = HideFlags.HideInInspector;
m_meshRenderer.sharedMaterial = m_previewMaterial;
}
gameObject.transform.position = m_scanBounds.center;
m_exportFileName = path.Substring(path.LastIndexOf('/'));
m_boundsSet = true;
}
///
/// Load the texture file provided
///
/// Texture file to load
public void LoadTextureFile(Texture2D texture)
{
//Check not null
if (texture == null)
{
Debug.LogError("Must supply a valid texture! Texture load aborted.");
return;
}
//Clear out the old
ResetData();
m_scanMap = new UnityHeightMap(texture);
if (m_scanMap.HasData() == false)
{
Debug.LogError("Unable to load Texture file. Texture load aborted.");
return;
}
m_scanWidth = m_scanMap.Width();
m_scanDepth = m_scanMap.Depth();
m_scanHeight = m_scanWidth / 2;
m_scanResolution = 0.1f;
m_scanBounds = new Bounds(GetPosition(gameObject, null), new Vector3(texture.width / 2, m_scanWidth * m_scanResolution, texture.height / 2));
//m_baseLevel = m_scanMap.GetBaseLevel();
SetOrCreateMeshComponents();
m_meshFilter.sharedMesh = GaiaUtils.CreateMesh(m_scanMap.Heights(), m_scanBounds.size);
if (m_previewMaterial != null)
{
m_previewMaterial.hideFlags = HideFlags.HideInInspector;
m_meshRenderer.sharedMaterial = m_previewMaterial;
}
gameObject.transform.position = m_scanBounds.center;
m_exportFileName = texture.name;
m_boundsSet = true;
}
///
/// Load the terrain provided
///
/// Terrain to load
public void LoadTerain(Terrain terrain)
{
//Check not null
if (terrain == null)
{
Debug.LogError("Must supply a valid terrain! Terrain load aborted.");
return;
}
//Clear out the old
ResetData();
m_scanMap = new UnityHeightMap(terrain);
if (m_scanMap.HasData() == false)
{
Debug.LogError("Unable to load terrain file. Terrain load aborted.");
return;
}
m_scanMap.Flip(); //Undo unity terrain shenannigans
m_scanWidth = m_scanMap.Width();
m_scanDepth = m_scanMap.Depth();
m_scanHeight = (int)terrain.terrainData.size.y;
m_scanResolution = 0.1f;
//m_scanBounds = new Bounds(GetPosition(gameObject), new Vector3(m_scanWidth * m_scanResolution, m_scanWidth * m_scanResolution, m_scanDepth * m_scanResolution));
m_scanBounds = new Bounds(GetPosition(gameObject, terrain, true), new Vector3(terrain.terrainData.size.x, terrain.terrainData.size.y, terrain.terrainData.size.z));
//m_baseLevel = m_scanMap.GetBaseLevel();
SetOrCreateMeshComponents();
m_meshFilter.sharedMesh = GaiaUtils.CreateMesh(m_scanMap.Heights(), m_scanBounds.size);
if (m_previewMaterial != null)
{
m_previewMaterial.hideFlags = HideFlags.HideInInspector;
m_meshRenderer.sharedMaterial = m_previewMaterial;
}
gameObject.transform.position = m_scanBounds.center;
m_exportFileName = terrain.name;
m_boundsSet = true;
}
///
/// Load the object provided
///
/// Terrain to load
public void LoadGameObject(GameObject go)
{
//Check not null
if (go == null)
{
Debug.LogError("Must supply a valid game object! GameObject load aborted.");
return;
}
//Clear out the old
ResetData();
//Duplicate the object
GameObject workingGo = GameObject.Instantiate(go);
workingGo.transform.position = transform.position;
workingGo.transform.localRotation = Quaternion.identity;
workingGo.transform.localScale = Vector3.one;
//Delete any old colliders
Collider[] colliders = workingGo.GetComponentsInChildren();
foreach (Collider c in colliders)
{
DestroyImmediate(c);
}
//Now add mesh colliders to all active game objects for the most accurate possible scanning
Transform[] transforms = workingGo.GetComponentsInChildren();
foreach (Transform child in transforms)
{
if (child.gameObject.activeSelf)
{
child.gameObject.AddComponent();
}
}
//Calculate bounds
m_scanBounds.center = workingGo.transform.position;
m_scanBounds.size = Vector3.zero;
foreach (MeshCollider c in workingGo.GetComponentsInChildren())
{
m_scanBounds.Encapsulate(c.bounds);
}
//Update scan array details - dont need to allocate mem until we scan
m_scanWidth = (int)(Mathf.Ceil(m_scanBounds.size.x * (1f / m_scanResolution)));
m_scanHeight = (int)(Mathf.Ceil(m_scanBounds.size.y * (1f / m_scanResolution)));
m_scanDepth = (int)(Mathf.Ceil(m_scanBounds.size.z * (1f / m_scanResolution)));
//Now scan the object
m_scanMap = new HeightMap(m_scanWidth, m_scanDepth);
Vector3 scanMin = m_scanBounds.min;
Vector3 scanPos = scanMin;
scanPos.y = m_scanBounds.max.y + 1;
RaycastHit scanHit;
#if UNITY_EDITOR
EditorUtility.DisplayProgressBar("Processing Mesh", "Processing mesh data", 0f);
#endif
int count = 0;
int totalNumberOfRays = m_scanWidth * m_scanDepth;
//Perform the scan - only need to store hits as float arrays inherently zero
for (int x = 0; x < m_scanWidth; x++)
{
scanPos.x = scanMin.x + (m_scanResolution * (float)x);
for (int z = 0; z < m_scanDepth; z++)
{
scanPos.z = scanMin.z + (m_scanResolution * (float)z);
if (Physics.Raycast(scanPos, Vector3.down, out scanHit, m_scanBounds.size.y + 1))
{
m_scanMap[x, z] = 1f - ((scanHit.distance -1f) / m_scanBounds.size.y);
}
count++;
}
#if UNITY_EDITOR
EditorUtility.DisplayProgressBar("Processing Mesh", "Processing mesh data", (float)count / (float)totalNumberOfRays);
#endif
}
//Now delete the scanned clone
DestroyImmediate(workingGo);
//Nad make sure we had some data
if (m_scanMap.HasData() == false)
{
Debug.LogError("Unable to scan GameObject. GameObject load aborted.");
return;
}
m_scanBounds = new Bounds(GetPosition(gameObject, null), new Vector3(m_scanWidth * m_scanResolution, m_scanBounds.size.y, m_scanDepth * m_scanResolution));//m_scanWidth * m_scanResolution * 0.4f, m_scanDepth * m_scanResolution));
//m_baseLevel = m_scanMap.GetBaseLevel();
SetOrCreateMeshComponents();
#if UNITY_EDITOR
EditorUtility.DisplayProgressBar("Processing Mesh", "Creating mesh from data", 0.95f);
#endif
m_meshFilter.sharedMesh = GaiaUtils.CreateMesh(m_scanMap.Heights(), m_scanBounds.size);
if (m_previewMaterial != null)
{
m_previewMaterial.hideFlags = HideFlags.HideInInspector;
m_meshRenderer.sharedMaterial = m_previewMaterial;
}
gameObject.transform.position = m_scanBounds.center;
m_boundsSet = true;
m_lastScanResolution = m_scanResolution;
m_exportFileName = go.name;
#if UNITY_EDITOR
EditorUtility.ClearProgressBar();
#endif
}
#endregion
#region Utils
///
/// Creates or gets the mesh filter and renderer
///
private void SetOrCreateMeshComponents()
{
//Mesh filter
if (m_meshFilter == null)
{
m_meshFilter = GetComponent();
if (m_meshFilter == null)
{
m_meshFilter = gameObject.AddComponent();
}
}
m_meshFilter.hideFlags = HideFlags.HideInInspector;
//Mesh renderer
if (m_meshRenderer == null)
{
m_meshRenderer = GetComponent();
if (m_meshRenderer == null)
{
m_meshRenderer = gameObject.AddComponent();
}
}
m_meshRenderer.hideFlags = HideFlags.HideInInspector;
}
private Vector3 GetPosition(GameObject scannerObj, Terrain terrain, bool terrainMode = false)
{
Vector3 position = Vector3.zero;
if (terrainMode)
{
if (terrain != null)
{
position.y = terrain.terrainData.size.x / 2f;
}
}
else
{
GaiaSceneInfo sceneInfo = null;
if (Terrain.activeTerrains.Length > 0)
{
sceneInfo = GaiaSceneInfo.GetSceneInfo();
}
if (sceneInfo != null)
{
position.y = sceneInfo.m_seaLevel;
scannerObj.transform.position = position;
}
else
{
scannerObj.transform.position = new Vector3(0f, 50f, 0f);
}
}
return position;
}
///
/// Update the scanner settings, fix any location and rotation, and perform any other housekeeping
///
private void UpdateScanner()
{
//Reset rotation and scaling on scanner
transform.localRotation = Quaternion.identity;
transform.localScale = Vector3.one;
if (m_boundsSet)
{
m_scanBounds.center = transform.position;
}
}
///
/// Select or create a scanner
///
public static GameObject CreateScanner()
{
GameObject gaiaObj = GaiaUtils.GetGaiaGameObject();
GameObject scannerObj = GameObject.Find("Scanner");
if (scannerObj == null)
{
scannerObj = new GameObject("Scanner");
scannerObj.transform.parent = gaiaObj.transform;
GaiaSceneInfo sceneInfo = null;
if (Terrain.activeTerrains.Length > 0)
{
sceneInfo = GaiaSceneInfo.GetSceneInfo();
}
if (sceneInfo != null)
{
Vector3 position = scannerObj.transform.position;
position.y = sceneInfo.m_seaLevel;
scannerObj.transform.position = position;
}
else
{
scannerObj.transform.position = new Vector3(0f, 50f, 0f);
}
Scanner scanner = scannerObj.AddComponent();
#if UNITY_EDITOR
//Load the material to draw it
string matPath = GaiaUtils.GetAssetPath("GaiaScannerMaterial.mat");
if (!string.IsNullOrEmpty(matPath))
{
scanner.m_previewMaterial = AssetDatabase.LoadAssetAtPath(matPath);
}
#endif
}
return scannerObj;
}
#endregion
}
}