using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using FlatKit;
using JetBrains.Annotations;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public static class ObjectOutlineEditorUtils {
private static readonly GUIStyle RichHelpBoxStyle = new(EditorStyles.helpBox) { richText = true };
public static void SetActive(Material material, bool active) {
// Work directly with the active URP Renderer Data to avoid dependency on a Camera context.
var rendererData = GetRendererData();
if (rendererData == null) {
const string m = "ScriptableRendererData is required to manage per-object outlines.\n" +
"Please assign a URP Asset in the Graphics settings.";
EditorGUILayout.LabelField(m, RichHelpBoxStyle);
return;
}
// Find existing feature on the renderer data.
var feature = rendererData.rendererFeatures
.FirstOrDefault(f => f != null && f.GetType() == typeof(ObjectOutlineRendererFeature));
// Create feature on demand when enabling outlines.
if (feature == null && active) {
feature = ScriptableObject.CreateInstance();
feature.name = "Flat Kit Per Object Outline";
AddRendererFeature(rendererData, feature);
var addedMsg = $"[Flat Kit] Added {feature.name} Renderer " +
$"Feature to {rendererData.name}.";
Debug.Log(addedMsg, rendererData);
}
// If disabling and there's no feature, nothing to do.
if (feature == null) return;
// Register/unregister the material and check usage.
if (feature is not ObjectOutlineRendererFeature outlineFeature) {
Debug.LogError("ObjectOutlineRendererFeature not found");
return;
}
var featureIsUsed = outlineFeature.RegisterMaterial(material, active);
// Remove the feature asset if no materials are using it anymore.
if (!featureIsUsed) {
RemoveRendererFeature(rendererData, feature);
var removedMsg = $"[Flat Kit] Removed {feature.name} Renderer " +
$"Feature from {rendererData.name} because no materials are using it.";
Debug.Log(removedMsg, rendererData);
}
}
private static void AddRendererFeature(ScriptableRendererData rendererData, ScriptableRendererFeature feature) {
// Save the asset as a sub-asset.
AssetDatabase.AddObjectToAsset(feature, rendererData);
rendererData.rendererFeatures.Add(feature);
rendererData.SetDirty();
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
private static void RemoveRendererFeature(ScriptableRendererData rendererData, ScriptableRendererFeature feature) {
rendererData.rendererFeatures.Remove(feature);
rendererData.SetDirty();
// Remove the asset.
AssetDatabase.RemoveObjectFromAsset(feature);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
[CanBeNull]
private static ScriptableRenderer GetRenderer(Camera camera) {
if (!camera) {
return null;
}
var additionalCameraData = camera.GetComponent();
if (!additionalCameraData) {
return null;
}
var renderer = additionalCameraData.scriptableRenderer;
return renderer;
}
private static List GetRendererFeatures(ScriptableRenderer renderer) {
var property =
typeof(ScriptableRenderer).GetProperty("rendererFeatures", BindingFlags.NonPublic | BindingFlags.Instance);
if (property == null) return null;
var features = property.GetValue(renderer) as List;
return features;
}
internal static ScriptableRendererData GetRendererData() {
#if UNITY_6000_0_OR_NEWER
var srpAsset = GraphicsSettings.defaultRenderPipeline;
#else
var srpAsset = GraphicsSettings.renderPipelineAsset;
#endif
if (srpAsset == null) {
const string m = "Flat Kit No SRP asset found. Please assign a URP Asset in the Graphics settings " +
"to enable per-object outlines.";
Debug.LogError(m);
return null;
}
var field = typeof(UniversalRenderPipelineAsset).GetField("m_RendererDataList",
BindingFlags.NonPublic | BindingFlags.Instance);
var rendererDataList = (ScriptableRendererData[])field!.GetValue(srpAsset);
var rendererData = rendererDataList.FirstOrDefault();
if (rendererData == null) {
Debug.LogError("No ScriptableRendererData found");
return null;
}
return rendererData;
}
}