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 } }