VR4RoboticArm2/VR4RoboticArm/Library/PackageCache/com.meta.xr.sdk.audio/editor/MetaXRAcousticMapEditor.cs
IonutMocanu d7aba243a2 Main
2025-09-08 11:04:02 +03:00

760 lines
33 KiB
C#

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Meta.XR.Acoustics;
using System;
using System.IO;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.SceneManagement;
[InitializeOnLoad] // required to make playModeStateChanged capture all state changes
[CustomEditor(typeof(MetaXRAcousticMap))]
internal class MetaXRAcousticMapEditor : Editor
{
private bool showAdvancedControls = false;
private bool showMappingConfig = false;
static MetaXRAcousticMapEditor()
{
#if META_XR_ACOUSTIC_STALE_CHECK
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
SceneManager.sceneLoaded += OnSceneLoaded;
#endif
EditorSceneManager.sceneOpened += OnSceneOpened;
}
private MetaXRAcousticMapEditor()
{
Selection.selectionChanged += OnSelectionChanged;
}
private static void OnPlayModeStateChanged(PlayModeStateChange state)
{
#if META_XR_ACOUSTIC_INFO
Debug.LogWarning($"OnPlayModeStateChanged {state}");
#endif
if (state == PlayModeStateChange.ExitingEditMode)
{
#if META_XR_ACOUSTIC_INFO
Debug.Log($"Validating AcousticMap hashes for {SceneManager.sceneCount} scenes");
#endif
for (int i = 0; i < SceneManager.sceneCount; ++i)
{
Scene scene = SceneManager.GetSceneAt(i);
GameObject[] rootObjects = scene.GetRootGameObjects();
foreach (GameObject go in rootObjects)
{
MetaXRAcousticMap[] acousticMaps = go.GetComponentsInChildren<MetaXRAcousticMap>();
foreach (MetaXRAcousticMap acousticMap in acousticMaps)
{
if (acousticMap.Status != AcousticMapStatus.READY)
Debug.LogWarning($"AcousticMap not baked: {acousticMap.gameObject.name} in scene {acousticMap.gameObject.scene.name}", acousticMap);
if (acousticMap.IsDirty())
Debug.LogError($"AcousticMap out of date: {acousticMap.gameObject.name} in scene {acousticMap.gameObject.scene.name}", acousticMap);
}
}
}
}
}
private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
#if META_XR_ACOUSTIC_INFO
Debug.LogWarning($"SceneLoaded {scene.name}");
#endif
GameObject[] rootObjects = scene.GetRootGameObjects();
foreach (GameObject go in rootObjects)
{
MetaXRAcousticMap[] acousticMaps = go.GetComponentsInChildren<MetaXRAcousticMap>();
foreach (MetaXRAcousticMap acousticMap in acousticMaps)
{
if (acousticMap.IsDirty())
Debug.LogError($"AcousticMap out of date: {acousticMap.gameObject.name} in scene {acousticMap.gameObject.scene.name}", acousticMap);
}
}
}
private static void OnSceneOpened(Scene scene, OpenSceneMode mode)
{
#if META_XR_ACOUSTIC_INFO
Debug.Log($"Open scene {scene.name}");
#endif
GameObject[] rootObjects = scene.GetRootGameObjects();
foreach (GameObject go in rootObjects)
{
MetaXRAcousticMap[] acousticMaps = go.GetComponentsInChildren<MetaXRAcousticMap>();
foreach (MetaXRAcousticMap acousticMap in acousticMaps)
acousticMap.StartInternal();
}
}
public override void OnInspectorGUI()
{
acousticMap = target as MetaXRAcousticMap;
string newFilePath = acousticMap.AbsoluteFilePath;
bool pathChanged = false;
bool compute = false;
bool mapScene = false;
bool addPoint = false;
bool removePoint = false;
bool staticOnly = acousticMap.StaticOnly;
bool noFloating = acousticMap.NoFloating;
bool diffraction = acousticMap.Diffraction;
bool customPointsEnabled = acousticMap.customPointsEnabled;
bool newCustomPointsEnabled = acousticMap.customPointsEnabled;
float minSpacing = acousticMap.MinSpacing;
float maxSpacing = acousticMap.MaxSpacing;
float headHeight = acousticMap.HeadHeight;
float maxHeight = acousticMap.MaxHeight;
int reflectionCount = (int)acousticMap.ReflectionCount;
MetaXRAcousticSceneGroup sceneGroup = acousticMap.SceneGroup;
selectedPoint = acousticMap.SelectedPoint;
bool changed = false;
using (new EditorGUI.DisabledScope(Application.isPlaying))
{
using (new EditorGUI.DisabledScope(acousticMap.Computing))
{
EditorGUI.BeginChangeCheck();
showAdvancedControls = EditorGUILayout.Foldout(showAdvancedControls, "Advanced Controls", true);
if (showAdvancedControls)
{
EditorGUI.indentLevel++;
//*****************************************************************************
// Scene Group
sceneGroup = EditorGUILayout.ObjectField(new GUIContent("Scene Group", "A collection of scenes to support additive loading, if not set will use parent scene"), acousticMap.SceneGroup, typeof(MetaXRAcousticSceneGroup), false) as MetaXRAcousticSceneGroup;
using (new EditorGUI.DisabledScope(true))
{
if (sceneGroup != null)
{
for (int i = 0; i < sceneGroup.sceneGuids.Length; ++i)
{
SceneAsset oldScene = null;
if (!string.IsNullOrEmpty(sceneGroup.sceneGuids[i]))
{
string path = AssetDatabase.GUIDToAssetPath(sceneGroup.sceneGuids[i]);
oldScene = AssetDatabase.LoadAssetAtPath<SceneAsset>(path);
}
var newScene = EditorGUILayout.ObjectField("", oldScene, typeof(SceneAsset), false) as SceneAsset;
}
}
else
{
SceneAsset oldScene = AssetDatabase.LoadAssetAtPath<SceneAsset>(acousticMap.gameObject.scene.path);
var newScene = EditorGUILayout.ObjectField("", oldScene, typeof(SceneAsset), false) as SceneAsset;
}
}
staticOnly = EditorGUILayout.Toggle(new GUIContent("Static Only", "Only bake data for game objects marked as static"), staticOnly);
diffraction = EditorGUILayout.Toggle(new GUIContent("Edge Diffraction", "Precompute edge diffraction data for smooth occlusion. If disabled, a lower-quality but faster fallback diffraction will be used."), acousticMap.Diffraction);
reflectionCount = EditorGUILayout.IntSlider(new GUIContent("Reflections", "The number of reflections generated for each data point"), reflectionCount, 0, 12);
MetaXRAcousticGeometryEditor.Separator();
//*****************************************************************************
// Serialized File Path
bool showFilePicker = false;
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.PrefixLabel(new GUIContent("File Path:", "The path to the serialized Acoustic Map, relative to the Assets directory"));
int indentLevelPrev = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
EditorGUILayout.LabelField(acousticMap.RelativeFilePath, EditorStyles.wordWrappedLabel);
// defer displaying dialog to avoid layout error
if (GUILayout.Button("...", GUILayout.ExpandWidth(false)))
showFilePicker = true;
EditorGUI.indentLevel = indentLevelPrev;
}
if (showFilePicker)
{
if (!System.IO.Directory.Exists(Application.streamingAssetsPath))
System.IO.Directory.CreateDirectory(Application.streamingAssetsPath);
string directory = Application.streamingAssetsPath;
string fileName = acousticMap.gameObject.scene.name + "." + MetaXRAcousticMap.FILE_EXTENSION;
if (newFilePath != "")
{
string suggestedDirectory = System.IO.Path.GetDirectoryName(newFilePath);
while (!System.IO.Directory.Exists(suggestedDirectory))
{
suggestedDirectory = System.IO.Path.GetFullPath(suggestedDirectory + "/..");
Debug.Log($"suggest: {suggestedDirectory}");
}
directory = suggestedDirectory;
fileName = System.IO.Path.GetFileName(newFilePath);
}
newFilePath = EditorUtility.SaveFilePanel(
"Save baked Acoustic Map to file", System.IO.Path.GetDirectoryName(acousticMap.AbsoluteFilePath), fileName, MetaXRAcousticMap.FILE_EXTENSION);
// If the user canceled, use the old path.
if (string.IsNullOrEmpty(newFilePath))
newFilePath = acousticMap.AbsoluteFilePath;
else
pathChanged = true;
}
EditorGUI.indentLevel--;
} // Advanced Controls
showMappingConfig = EditorGUILayout.Foldout(showMappingConfig, "Mapping Configuration", true);
if (showMappingConfig)
{
EditorGUI.indentLevel++;
//*****************************************************************************
// Mapping Parameters
minSpacing = EditorGUILayout.FloatField(new GUIContent("Min Spacing", "The size in meters of the smallest space that will be precomputed"), minSpacing);
minSpacing = Mathf.Clamp(minSpacing, 0, maxSpacing);
maxSpacing = EditorGUILayout.FloatField(new GUIContent("Max Spacing", "The maximum distance in meters between precomputed data points"), maxSpacing);
maxSpacing = Mathf.Clamp(maxSpacing, minSpacing, MetaXRAcousticMap.DISTANCE_PARAMETER_MAX);
noFloating = EditorGUILayout.Toggle(new GUIContent("No Floating", "Don't automatically place data for points far above the floor"), noFloating);
headHeight = EditorGUILayout.FloatField(new GUIContent("Head Height", "The distance above the floor where data points are placed"), headHeight);
headHeight = Mathf.Clamp(headHeight, 0, MetaXRAcousticMap.DISTANCE_PARAMETER_MAX);
maxHeight = EditorGUILayout.FloatField(new GUIContent("Max Height", "The maximum height above the floor where data points are placed"), maxHeight);
maxHeight = Mathf.Clamp(maxHeight, 0, MetaXRAcousticMap.DISTANCE_PARAMETER_MAX);
newCustomPointsEnabled = EditorGUILayout.Toggle(new GUIContent("Custom Points", "Custom points allow you to move and place points"), customPointsEnabled);
if (customPointsEnabled && !newCustomPointsEnabled && acousticMap.HasCustomPoints)
mapScene = true;
using (new EditorGUI.DisabledGroupScope(!customPointsEnabled))
{
// Exclude "Map Scene" and "Enable Editing" from change check
changed = EditorGUI.EndChangeCheck();
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.PrefixLabel(" ");
string mapLabel = acousticMap.HasCustomPoints ? "Remap Scene" : "Map Scene";
if (GUILayout.Button(new GUIContent(mapLabel, "Automatically place points in the scene.\nNOTE: this will clear any previously baked data or custom points")))
mapScene = true;
}
//*****************************************************************************
// Point editing
EditorGUI.BeginChangeCheck();
using (new EditorGUI.DisabledGroupScope(acousticMap.Computing))
{
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.PrefixLabel(" ");
if (GUILayout.Button(new GUIContent("Add Point", "Add a new point at the center of the current view.\nTo quickly create points by left mouse button, enable editing in the viewport.")))
addPoint = true;
using (new EditorGUI.DisabledScope(!acousticMap.HasSelectedPoint))
{
if (GUILayout.Button(new GUIContent("Remove Point", "Remove the currently-selected point.\nTo quickly delete points by pressing backspace, enable editing in the viewport")))
removePoint = true;
}
}
}
}
EditorGUI.indentLevel--;
MetaXRAcousticGeometryEditor.Separator();
} // Mapping Configuration
}
//*****************************************************************************
// Baking controls
// Exclude baking from change check
changed |= EditorGUI.EndChangeCheck();
bool cancel = false;
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.PrefixLabel(" ");
if (acousticMap.Computing && !acousticMap.ComputeCanceled)
cancel = GUILayout.Button("Cancel");
else
compute = GUILayout.Button(new GUIContent("Bake Acoustics", "Simulate the scene, and also map it if custom points are not provided."));
}
if (cancel)
acousticMap.CancelCompute();
//*****************************************************************************
// Statistics
EditorGUILayout.LabelField(new GUIContent("Status:", "The current state of the precomputed data"), new GUIContent(acousticMap.Computing ? "COMPUTING" : (acousticMap.Status).ToString()));
EditorGUILayout.LabelField(new GUIContent("Points:", "The number of precomputed data points"), new GUIContent((acousticMap.PointCount).ToString() + (acousticMap.HasCustomPoints ? " (Custom)" : "")));
if (newFilePath != null)
{
FileInfo fileInfo = new FileInfo(newFilePath);
long fileSize = fileInfo.Exists ? fileInfo.Length : 0;
EditorGUILayout.LabelField(new GUIContent("Data Size:", "The total size of the serialized data"), new GUIContent(GetSizeString(fileSize)));
}
//*****************************************************************************
// Progress
// Show the progress bar if we are currently computing the IR asynchronously
if (acousticMap.Computing)
{
double timeRemaining = acousticMap.ComputeProgress <= 0.0f ? 0.0 : acousticMap.ComputeTime / acousticMap.ComputeProgress - acousticMap.ComputeTime;
EditorUtility.DisplayProgressBar(acousticMap.ComputeDescription, acousticMap.ComputeDescription + " ETA: " + getTimeString(timeRemaining), acousticMap.ComputeProgress);
}
else
{
EditorUtility.ClearProgressBar();
}
EditorGUI.BeginChangeCheck();
}
changed |= EditorGUI.EndChangeCheck();
// Ask the user to confirm overwrite of previous data.
bool overwritePoints = true;
if (mapScene)
{
if (acousticMap.HasCustomPoints)
{
overwritePoints = EditorUtility.DisplayDialog("Overwrite Custom Points?",
"Are you sure you want to overwrite the custom points and re-map the scene?", "Overwrite", "Cancel");
}
else if (acousticMap.Status == AcousticMapStatus.READY)
{
overwritePoints = EditorUtility.DisplayDialog("Overwrite Baked Data?",
"Mapping the scene will clear previously baked data.\nDo you want to proceed?", "Overwrite", "Cancel");
}
// cancel toggle
if (!overwritePoints && !newCustomPointsEnabled && customPointsEnabled)
newCustomPointsEnabled = customPointsEnabled;
}
//*****************************************************************************
// Apply changes
if (changed)
{
Undo.RecordObject(acousticMap, "Edited MetaXRAcousticMap");
acousticMap.StaticOnly = staticOnly;
acousticMap.NoFloating = noFloating;
acousticMap.Diffraction = diffraction;
acousticMap.customPointsEnabled = newCustomPointsEnabled;
acousticMap.MinSpacing = minSpacing;
acousticMap.MaxSpacing = maxSpacing;
acousticMap.HeadHeight = headHeight;
acousticMap.MaxHeight = maxHeight;
acousticMap.ReflectionCount = (uint)reflectionCount;
acousticMap.SceneGroup = sceneGroup;
if (pathChanged)
{
string prevPath = acousticMap.AbsoluteFilePath;
string newRelativeFilePath = newFilePath.Substring(Application.dataPath.Length + 1).Replace('\\', '/');
if (File.Exists(prevPath) && string.Compare(newRelativeFilePath, acousticMap.RelativeFilePath, true) != 0)
{
Debug.Log($"Move/rename Acoustic Map file\n to:\t{newRelativeFilePath} from:\t{acousticMap.RelativeFilePath}\n");
if (UnityEditor.VersionControl.Provider.isActive)
{
UnityEditor.VersionControl.Provider.Move(prevPath, newFilePath);
}
else
{
File.Move(prevPath, newFilePath);
if (File.Exists(prevPath + ".meta"))
File.Move(prevPath + ".meta", newFilePath + ".meta");
}
}
acousticMap.AbsoluteFilePath = newFilePath;
}
}
if (compute || mapScene)
{
if (compute)
{
StartComputing(false);
}
else if (mapScene && overwritePoints)
{
// Set dirty so that the custom points are actually overwritten in the serialized data.
// Without this, the custom points will be erroneously restored when loading project again.
EditorUtility.SetDirty(acousticMap);
StartComputing(true);
}
}
else if (addPoint)
{
Undo.RecordObject(acousticMap, "Added Acoustic Map point");
// Add a new point to the center of the view.
// Trace a ray from the center into the scene to find the distance from camera.
Camera camera = SceneView.lastActiveSceneView.camera;
Ray ray = camera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0.0f));
selectedPoint = acousticMap.AddPoint(GetNewPointForRay(acousticMap, ray, camera.farClipPlane));
acousticMap.SelectedPoint = selectedPoint;
SceneView.RepaintAll();
}
else if (removePoint)
{
Undo.RecordObject(acousticMap, "Deleted Acoustic Map point");
acousticMap.RemovePoint(acousticMap.SelectedPoint);
selectedPoint = INVALID_POINT;
SceneView.RepaintAll();
}
}
//***********************************************************************
// Point Editing
private MetaXRAcousticMap acousticMap;
private const int INVALID_POINT = -1;
private int selectedPoint = INVALID_POINT;
private bool customMouseHandlingEnabled = false;
private static float pointRadius = 0.2f;
private bool freeEditEnabled = false;
// Define a custom gizmo drawing function for the MetaXRAcousticMap component
[DrawGizmo(GizmoType.Active | GizmoType.Selected | GizmoType.NotInSelectionHierarchy | GizmoType.Pickable)]
internal static void DrawGizmos(MetaXRAcousticMap acousticMap, GizmoType gizmoType)
{
// Don't draw if the Acoustic Map inspector is collapsed.
if (!InternalEditorUtility.GetIsInspectorExpanded(acousticMap))
return;
int pointCount = acousticMap.PointCount;
for (int i = 0; i < pointCount; i++)
{
Gizmos.color = (i == acousticMap.SelectedPoint) ? Color.red : Color.yellow;
Gizmos.DrawSphere(acousticMap.GetPoint(i), pointRadius);
}
}
protected void OnSceneGUI()
{
MetaXRAcousticMap acousticMap = target as MetaXRAcousticMap;
if (acousticMap.customPointsEnabled)
{
// Transform or remove the selected point.
if (selectedPoint >= 0 && selectedPoint < acousticMap.PointCount)
{
EditorGUI.BeginChangeCheck();
Vector3 oldPosition = acousticMap.GetPoint(selectedPoint);
Vector3 newPosition = Handles.PositionHandle(oldPosition, Quaternion.identity);
bool selectedPointMoved = EditorGUI.EndChangeCheck();
if (selectedPointMoved)
{
Undo.RecordObject(acousticMap, "Moved Acoustic Map point");
acousticMap.SetPoint(selectedPoint, newPosition);
return; // to not mess up with other mouse handling logic below
}
if (freeEditEnabled && Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Backspace)
{
// Remove selected point.
Undo.RecordObject(acousticMap, "Deleted Acoustic Map point");
acousticMap.RemovePoint(selectedPoint);
selectedPoint = INVALID_POINT;
}
}
if (Event.current.type == EventType.MouseDown && Event.current.button == 0)
{
bool shouldEnableCustomMouseHandling = false;
selectedPoint = INVALID_POINT;
if (PickPoint(Event.current.mousePosition, out selectedPoint))
{
shouldEnableCustomMouseHandling = true;
}
else if (freeEditEnabled)
{
// Create a new point in the scene at the mouse position.
Undo.RecordObject(acousticMap, "Added Acoustic Map point");
Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
acousticMap.AddPoint(GetNewPointForRay(acousticMap, ray, SceneView.lastActiveSceneView.camera.farClipPlane));
shouldEnableCustomMouseHandling = true;
}
if (shouldEnableCustomMouseHandling)
{
// Eat the event to avoid the editor selecting objects behind the point.
Event.current.Use();
customMouseHandlingEnabled = true;
// This makes sure we receive mouse up events.
GUIUtility.hotControl = GUIUtility.GetControlID(GetHashCode(), FocusType.Passive);
}
// Inform the Acoustic Map which point is selected so that it can draw visual feedback.
// This is ugly, but there doesn't seem to be a way to draw gizmos
// from the editor and access non-static editor instance variables.
acousticMap.SelectedPoint = selectedPoint;
}
else if (Event.current.type == EventType.MouseUp && Event.current.button == 0)
{
if (customMouseHandlingEnabled)
{
// Eat the event to avoid the editor selecting objects behind the point.
Event.current.Use();
customMouseHandlingEnabled = false;
// Mouse up cleanup
GUIUtility.hotControl = 0;
}
}
}
}
/// Find the point that is under the specified mouse position.
private bool PickPoint(Vector2 mousePosition, out int closestPoint)
{
// Convert from screen to world space.
Ray ray = HandleUtility.GUIPointToWorldRay(mousePosition);
MetaXRAcousticMap acousticMap = target as MetaXRAcousticMap;
float minDistance = Single.MaxValue;
closestPoint = INVALID_POINT;
int pointCount = acousticMap.PointCount;
for (int i = 0; i < pointCount; i++)
{
float distance = 0.0f;
if (RayIntersectsSphere(ray, acousticMap.GetPoint(i), pointRadius, out distance) && distance < minDistance)
{
minDistance = distance;
closestPoint = i;
}
}
return closestPoint != INVALID_POINT;
}
private void OnSelectionChanged()
{
if (acousticMap != null && acousticMap.gameObject != Selection.activeGameObject)
acousticMap.SelectedPoint = INVALID_POINT;
}
/// Determine whether or not a ray intersects a sphere.
private bool RayIntersectsSphere(Ray ray, Vector3 center, float radius, out float distance)
{
Vector3 d = center - ray.origin;
float dSquared = d.sqrMagnitude;
float rSquared = radius * radius;
// Find the closest point on the ray to the sphere's center.
float t1 = Vector3.Dot(d, ray.direction);
// If the ray starts inside the sphere there is an intersection.
if (dSquared < rSquared)
{
// Find the distance from the closest point to the sphere's surface.
float t2Squared = rSquared - dSquared + t1 * t1;
// Compute the distance along the ray of the intersection.
distance = t1 + Mathf.Sqrt(t2Squared);
return true;
}
else
{
// Check to see if the ray is outside and points away from the sphere so there is no intersection.
if (t1 < 0.0f)
{
distance = Single.MaxValue;
return false;
}
// Find the distance from the closest point to the sphere's surface.
// If the descriminant is negative, there is no intersection.
float t2Squared = rSquared - dSquared + t1 * t1;
if (t2Squared < 0.0f)
{
distance = Single.MaxValue;
return false;
}
// Compute the distance along the ray of the intersection.
distance = t1 - Mathf.Sqrt(t2Squared);
return true;
}
}
private Vector3 GetNewPointForRay(MetaXRAcousticMap acousticMap, Ray ray, float maxDistance, float defaultDistance = 2.0f)
{
RaycastHit hit;
if (Physics.Raycast(ray, out hit, maxDistance, Physics.DefaultRaycastLayers, QueryTriggerInteraction.Ignore))
{
// Place the point slightly away from the hit point to avoid placing points on walls.
float bias = 1.0f;
Vector3 newPoint = ray.origin + ray.direction * Mathf.Max(hit.distance - bias, 0.0f);
if (acousticMap.NoFloating == false)
{
return newPoint;
}
// Trace two rays down and up to place the point at the right height.
Ray downRay = new Ray(newPoint, acousticMap.GravityVector.normalized);
if (Physics.Raycast(downRay, out hit, Mathf.Infinity, Physics.DefaultRaycastLayers, QueryTriggerInteraction.Ignore))
{
float downDistance = hit.distance;
float targetHeight = newPoint.y;
if (downDistance > acousticMap.MaxHeight)
{
targetHeight = acousticMap.HeadHeight;
}
else if (downDistance < acousticMap.HeadHeight)
{
Ray upRay = new Ray(newPoint, -downRay.direction);
if (Physics.Raycast(upRay, out hit, Mathf.Infinity, Physics.DefaultRaycastLayers, QueryTriggerInteraction.Ignore))
{
// Move point so that it is either at head height or is at the floor/ceiling midpoint if that is less.
float midHeight = 0.5f * (downDistance + hit.distance);
targetHeight = Mathf.Min(acousticMap.HeadHeight, midHeight);
}
}
// height adjustment
newPoint += downRay.direction * (downDistance - targetHeight);
}
return newPoint;
}
else
{
// Camera is pointing at empty space.
// Place the point a short default distance from the camera.
return ray.origin + ray.direction * defaultDistance;
}
}
Action<bool> onFinish;
//***********************************************************************
// Computation / Progress Bar
internal bool StartComputing(bool mapOnly, Action<bool> OnFinish = null)
{
// Disable editing of custom points.
freeEditEnabled = false;
onFinish = OnFinish;
// Subscribe to constant update ticks from the application.
// This is the only way to always get updates on the main thread.
EditorApplication.update += this.Update;
MetaXRAcousticMap map = (MetaXRAcousticMap)target;
// Trigger the computation.
bool result = map.Compute(mapOnly);
// Cause the editor to be redrawn to update the progress bar.
Repaint();
return result;
}
private void Update()
{
if (target == null)
EditorApplication.update -= this.Update;
MetaXRAcousticMap acousticMap = (MetaXRAcousticMap)target;
if (acousticMap.Computing)
{
// Poll to see if the async compute was finished.
if (acousticMap.ComputeFinished)
{
bool wasCancelled = acousticMap.ComputeCanceled;
// Do final cleanup on the main thread after computing the IR.
acousticMap.FinishCompute();
// Unsubcribe from constant updates.
EditorApplication.update -= this.Update;
if (acousticMap.ComputeSucceeded)
acousticMap.UpdateHash();
if (wasCancelled)
{
Debug.Log($"Attempting to load previous bake {acousticMap.RelativeFilePath}", acousticMap);
acousticMap.DestroyInternal();
acousticMap.StartInternal();
}
if (onFinish != null)
onFinish(acousticMap.ComputeSucceeded);
onFinish = null;
}
// Cause the editor to be redrawn to update the progress bar.
Repaint();
}
}
//***********************************************************************
// Helpers
/// Return a string suitable for display in a GUI for the specified data size in bytes.
internal static string GetSizeString(long size)
{
if (size <= 0L)
return "0 B";
else if (size < 1024L)
return $"{size} B";
else if (size < 1024L * 1024L)
return $"{size / (1024f):G3} KB";
else if (size < 1024L * 1024L * 1024L)
return $"{size / (1024f * 1024f):G3} MB";
else if (size < 1024L * 1024L * 1024L * 1024L)
return $"{size / (1024f * 1024f * 1024f):G3} GB";
else
return $"{size / (1024f * 1024f * 1024f * 1024f):0.##} TB";
}
/// Return a string suitable for display in a GUI for the specified time interval in seconds.
private string getTimeString(double time)
{
return new TimeSpan((Int64)(time * 1.0e7)).ToString(@"hh\:mm\:ss");
}
}