Newer
Older
CGTrack / Assets / Oculus / Spatializer / editor / ONSPPropagationMaterialEditor.cs
@Pascal Syma Pascal Syma on 25 Jul 2021 13 KB Initial Commit
/************************************************************************************
Filename    :   ONSPPropagationMaterialEditor.cs
Content     :   Propagation material editor class
                Attach to geometry to define material properties
Copyright   :   Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.

Licensed under the Oculus SDK Version 3.5 (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/sdk-3.5/

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 UnityEditor;
using UnityEngine;

using Spectrum = ONSPPropagationMaterial.Spectrum;
using Point = ONSPPropagationMaterial.Point;

[CustomEditor(typeof(ONSPPropagationMaterial))]
internal sealed class ONSPPropagationMaterialEditor : Editor{
  
  private enum AxisScale{ Lin, Log, Sqr }

  private sealed class SpectrumDrawer{

    private const float cutoff = 20000f;

    private static readonly Texture2D texture = EditorGUIUtility.whiteTexture;
    
    private static readonly GUIStyle textStyle = new GUIStyle{

      alignment = TextAnchor.MiddleLeft,
      clipping = TextClipping.Overflow,
      fontSize = 8,
      fontStyle = FontStyle.Bold,
      wordWrap = false,
      normal = new GUIStyleState{ textColor = Color.grey },
      focused = new GUIStyleState{ textColor = Color.grey }

    };

    private static int focus;

    private readonly string label;

    private readonly AxisScale scale;
    private readonly Spectrum spectrum;
    
    private bool dragInitiated;
    private bool isDragging;

    private bool displaySpectrum;
    private bool displayPoints;

    internal bool IsFocus{

      get{

        return focus == spectrum.GetHashCode();

      }

    }

    internal SpectrumDrawer(string label, Spectrum spectrum, AxisScale scale){
      
      this.label = label;
      this.spectrum = spectrum;
      this.scale = scale;

    }

    internal void Draw(Event e){

      displaySpectrum = EditorGUILayout.Foldout(displaySpectrum, label);
      
      if(displaySpectrum){

        EditorGUI.indentLevel++;
        DrawSpectrum(e);

        displayPoints = EditorGUILayout.Foldout(displayPoints, "Points");

        if(displayPoints){

          EditorGUI.indentLevel++;
          DrawPoints();
          EditorGUI.indentLevel--;

        }

        EditorGUI.indentLevel--;

      }

    }

    internal void LoadFoldoutState(){

      displaySpectrum = EditorPrefs.GetBool(label + "Spectrum", true);
      displayPoints = EditorPrefs.GetBool(label + "Points", false);

    }

    internal void SaveFoldoutState(){

      EditorPrefs.SetBool(label + "Spectrum", displaySpectrum);
      EditorPrefs.SetBool(label + "Points", displayPoints);

    }

    private void DrawSpectrum(Event e){

      float height = 10 * EditorGUIUtility.singleLineHeight;
      Rect r = EditorGUILayout.GetControlRect(true, height);

      r.width -= rightMargin;
      DrawDataTicks(r);
      r = AudioCurveRendering.BeginCurveFrame(r);

      AudioCurveRendering.DrawFilledCurve(r, EvaluateCurve, AudioCurveRendering.kAudioOrange);

      DrawFrequencyTicks(r);

      HandleEvent(r, e);
      if(IsFocus) DrawSelected(r);

      AudioCurveRendering.EndCurveFrame();

    }
        
    private void DrawPoints(){
      
      var points = spectrum.points;
      int lines = points.Count > 0 ? points.Count + 2 : 1;
      float height = EditorGUIUtility.singleLineHeight * lines;
      Rect r1 = EditorGUILayout.GetControlRect(true, height);
      r1.width -= rightMargin;
      r1.height = EditorGUIUtility.singleLineHeight;

      {

        int oldCount = points.Count;
        int newCount = EditorGUI.DelayedIntField(r1, "Size", oldCount);
        r1.y += r1.height;

        if(newCount < points.Count){

          points.RemoveRange(newCount, oldCount - newCount);
          Undo.SetCurrentGroupName("Points Removed");
          GUI.changed = true;

        } else if(newCount > oldCount){

          if(newCount > points.Capacity)
            points.Capacity = newCount;

          for(int i = oldCount; i < newCount; i++)
            points.Add(new Point(125 * (1 << i)));

          Undo.SetCurrentGroupName("Points Added");
          GUI.changed = true;

        }

      }

      if(points.Count > 0){

        Rect r2 = new Rect(r1.xMax + 9, r1.y + r1.height * 1.125f, 24, r1.height * .75f);
        
        r1.width /= 2;
        EditorGUI.LabelField(r1, "Frequency");
        r1.x += r1.width;
        EditorGUI.LabelField(r1, "Data");
        r1.x -= r1.width;
        r1.y += r1.height;

        for(int i = 0; i < points.Count; i++){

          points[i].frequency = EditorGUI.FloatField(r1, points[i].frequency);
          points[i].frequency = Mathf.Clamp(points[i].frequency, 0f, cutoff);
          r1.x += r1.width;
          points[i].data = EditorGUI.FloatField(r1, points[i].data);
          points[i].data = Mathf.Clamp01(points[i].data);
          r1.x -= r1.width;
          r1.y += r1.height;

          if(GUI.Button(r2, "–")){

            RemovePointAt(i);
            break;

          }

          r2.y += r1.height;

        }

      }

    }

    private void DrawDataTicks(Rect r){

      const int ticks = 10;
      Rect label = new Rect(r.xMax + 9, r.y - r.height / (2 * ticks), 24, r.height / ticks);
      Rect tick = new Rect(r.xMax + 2, r.y - 1, 4.5f, 2);

      for(int i = 0; i <= ticks; i++){
        
        float value = MapData(1 - (float)i / ticks, false);
        
        EditorGUI.DrawRect(tick, textStyle.normal.textColor);
        GUI.Label(label, value.ToString("0.000"), textStyle);
        tick.y += label.height;
        label.y += label.height;

      }

    }

    private void DrawFrequencyTicks(Rect r){
      
      Rect tick = new Rect(r.x, r.y, 1, r.height);
      Rect label = new Rect(r.x, r.yMax - 1.5f * EditorGUIUtility.singleLineHeight, 32, EditorGUIUtility.singleLineHeight);

      for(int i = 1; i < 30; i++){

        float frequency;

        if(MapFrequencyTick(i, out frequency)){

          tick.x = MapFrequency(frequency) * r.width;
          tick.height = label.y - r.y;
          tick.width = 2;
          EditorGUI.DrawRect(tick, textStyle.normal.textColor);

          tick.y = label.yMax;
          tick.height = r.yMax - label.yMax;
          EditorGUI.DrawRect(tick, textStyle.normal.textColor);

          label.x = tick.x - 2;
          GUI.Label(label, FrequencyToString(frequency), textStyle);

          tick.y = r.y;
          tick.height = r.height;
          tick.width = 1;

        } else{

          tick.x = MapFrequency(frequency) * r.width;
          EditorGUI.DrawRect(tick, textStyle.normal.textColor);

        }

      }

    }

    private void DrawSelected(Rect r){
      
      if(spectrum.points.Count > spectrum.selection){

        const float radius = 12;
        Vector2 position = MapPointPosition(r, spectrum.points[spectrum.selection]);
        Vector2 size = new Vector2(radius, radius);
        r = new Rect(position - size / 2, size);

#if UNITY_5
        GUI.DrawTexture(r, texture, ScaleMode.StretchToFill, false, 0);
        GUI.DrawTexture(r, texture, ScaleMode.StretchToFill, false, 0);
#else
        GUI.DrawTexture(r, texture, ScaleMode.StretchToFill, false, 0, Color.white, 0, radius);
        GUI.DrawTexture(r, texture, ScaleMode.StretchToFill, false, 0, Color.black, 2, radius);
#endif

        }
    }

    private void HandleEvent(Rect r, Event e){

      Vector2 position = e.mousePosition;

      switch(e.type){

        case EventType.MouseDown:

          if(r.Contains(position)){

            if(e.clickCount == 2){

              spectrum.selection = spectrum.points.Count;
              spectrum.points.Add(MapMouseEvent(r, position));
              Undo.SetCurrentGroupName("Point Added");
              GUI.changed = true;

            } else{

              int selection = spectrum.selection;
              float minDistance = float.MaxValue;

              for(int i = 0; i < spectrum.points.Count; i++){

                float distance = Vector2.Distance(MapPointPosition(r, spectrum.points[i]), position);

                if(distance < minDistance){

                  selection = i;
                  minDistance = distance;

                }

              }

              if(selection != spectrum.selection){

                spectrum.selection = selection;
                Undo.SetCurrentGroupName("Point Selected");
                GUI.changed = true;

              }

            }

            focus = spectrum.GetHashCode();
            dragInitiated = true;

          } else{

            isDragging = false;
            focus = 0;

          }

          e.Use();

          break;

        case EventType.MouseDrag:

          if(dragInitiated){

            dragInitiated = false;
            isDragging = true;

          }

          if(isDragging && spectrum.selection < spectrum.points.Count){

            spectrum.points[spectrum.selection] = MapMouseEvent(r, position);
            e.Use();

          }

          break;

        case EventType.Ignore:
        case EventType.MouseUp:

          dragInitiated = false;

          if(isDragging){

            isDragging = false;
            Undo.SetCurrentGroupName("Point Moved");
            GUI.changed = true;
            e.Use();

          }

          break;

        case EventType.KeyDown:

          switch(e.keyCode){

            case KeyCode.Delete:
            case KeyCode.Backspace:

              if(spectrum.selection < spectrum.points.Count){

                RemovePointAt(spectrum.selection);
                e.Use();

              }

              break;

          }

          break;

      }

    }

    private void RemovePointAt(int index){

      spectrum.points.RemoveAt(index);

      if(spectrum.selection == index)
        spectrum.selection = spectrum.points.Count;

      Undo.SetCurrentGroupName("Point Removed");
      GUI.changed = true;

    }

    private float EvaluateCurve(float f){

      return 2 * MapData(spectrum[MapFrequency(f, false)]) - 1;

    }

    private Vector2 MapPointPosition(Rect r, Point point){

      return new Vector2{

        x = r.x + r.width * MapFrequency(point.frequency),
        y = r.y + r.height * (1 - MapData(point.data))

      };

    }

    private Point MapMouseEvent(Rect r, Vector2 v){

      return new Point{

        frequency = v.x < r.xMin ? 0 : v.x > r.xMax ? cutoff : MapFrequency((v.x - r.x) / r.width, false),
        data = v.y < r.yMin ? 1 : v.y > r.yMax ? 0 : MapData(1 - (v.y - r.y) / r.height, false)

      };

    }

    private float MapData(float f, bool forward = true){

      switch(scale){

        case AxisScale.Log:
          return forward ? f < 1e-3f ? 0 : 1 + Mathf.Log10(f) / 3 : Mathf.Pow(10, -3 * (1 - f));

        case AxisScale.Sqr:
          return forward ? Mathf.Sqrt(f) : f * f;

        default:
        case AxisScale.Lin:
          return f;

      }

    }
    
    public static bool MapFrequencyTick(int i, out float frequency){

      int power = i / 9 + 1;
      int multiplier = i % 9 + 1;

      frequency = multiplier * Mathf.Pow(10, power);

      return multiplier == 1;

    } 

    public static float MapFrequency(float f, bool forward = true){

      return forward ? f < 10 ? 0 : Mathf.Log(f / 10, cutoff / 10) : 10 * Mathf.Pow(cutoff / 10, f);

    }

    private static string FrequencyToString(float frequency){

      if(frequency < 1000)
        return string.Format("{0:F0} Hz", frequency);
      else
        return string.Format("{0:F0} kHz", frequency * .001f);

    }

  }

  private const float rightMargin = 36;

  private SpectrumDrawer absorption, scattering, transmission;

  private void OnEnable(){

    GetSpectra(out absorption, out scattering, out transmission);

    absorption.LoadFoldoutState();
    scattering.LoadFoldoutState();
    transmission.LoadFoldoutState();
    
  }

  private void OnDisable(){
    
    absorption.SaveFoldoutState();
    scattering.SaveFoldoutState();
    transmission.SaveFoldoutState();

  }

  public override void OnInspectorGUI(){

    ONSPPropagationMaterial material = target as ONSPPropagationMaterial;

    EditorGUI.BeginChangeCheck();

    Rect r = EditorGUILayout.GetControlRect();
    r.width -= rightMargin;

       ONSPPropagationMaterial.Preset newPreset =
      (ONSPPropagationMaterial.Preset)EditorGUI.EnumPopup(r, "Preset", material.preset);

    Event e = Event.current;
    EventType type = e.type;
    absorption.Draw(e);
    scattering.Draw(e);
    transmission.Draw(e);
    
    if(EditorGUI.EndChangeCheck()){

      string groupName = Undo.GetCurrentGroupName();

      Undo.RegisterCompleteObjectUndo(material, groupName);

      if(groupName == "Point Added")
        Undo.CollapseUndoOperations(Undo.GetCurrentGroup() - 1);

      if(material.preset != newPreset)
        material.preset = newPreset;
      else
        material.preset = ONSPPropagationMaterial.Preset.Custom;

      if(Application.isPlaying)
        material.UploadMaterial();

    }

  }

  private void GetSpectra(out SpectrumDrawer absorption,
                          out SpectrumDrawer scattering,
                          out SpectrumDrawer transmission){

    ONSPPropagationMaterial material = target as ONSPPropagationMaterial;

    absorption = new SpectrumDrawer("Absorption", material.absorption, AxisScale.Sqr);
    scattering = new SpectrumDrawer("Scattering", material.scattering, AxisScale.Lin);
    transmission = new SpectrumDrawer("Transmission", material.transmission, AxisScale.Sqr);

  }

}