您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
352 行
12 KiB
352 行
12 KiB
using System;
|
|
using System.Text;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.IO.IsolatedStorage;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using UnityEditor.Graphing;
|
|
using UnityEditor.Graphing.Util;
|
|
using UnityEditorInternal;
|
|
using Debug = UnityEngine.Debug;
|
|
using System.Reflection;
|
|
using System.Runtime.Remoting.Metadata.W3cXsd2001;
|
|
using Data.Util;
|
|
using UnityEditor.ProjectWindowCallback;
|
|
using UnityEditor.ShaderGraph.Internal;
|
|
using UnityEngine;
|
|
using Object = System.Object;
|
|
|
|
namespace UnityEditor.ShaderGraph
|
|
{
|
|
// a structure used to track active variable dependencies in the shader code
|
|
// (i.e. the use of uv0 in the pixel shader means we need a uv0 interpolator, etc.)
|
|
struct Dependency
|
|
{
|
|
public string name; // the name of the thing
|
|
public string dependsOn; // the thing above depends on this -- it reads it / calls it / requires it to be defined
|
|
|
|
public Dependency(string name, string dependsOn)
|
|
{
|
|
this.name = name;
|
|
this.dependsOn = dependsOn;
|
|
}
|
|
};
|
|
|
|
[System.AttributeUsage(System.AttributeTargets.Struct)]
|
|
class InterpolatorPack : System.Attribute
|
|
{
|
|
public InterpolatorPack()
|
|
{
|
|
}
|
|
}
|
|
|
|
// attribute used to flag a field as needing an HLSL semantic applied
|
|
// i.e. float3 position : POSITION;
|
|
// ^ semantic
|
|
[System.AttributeUsage(System.AttributeTargets.Field)]
|
|
class Semantic : System.Attribute
|
|
{
|
|
public string semantic;
|
|
|
|
public Semantic(string semantic)
|
|
{
|
|
this.semantic = semantic;
|
|
}
|
|
}
|
|
|
|
// attribute used to flag a field as being optional
|
|
// i.e. if it is not active, then we can omit it from the struct
|
|
[System.AttributeUsage(System.AttributeTargets.Field)]
|
|
class Optional : System.Attribute
|
|
{
|
|
public Optional()
|
|
{
|
|
}
|
|
}
|
|
|
|
// attribute used to override the HLSL type of a field with a custom type string
|
|
[System.AttributeUsage(System.AttributeTargets.Field)]
|
|
class OverrideType : System.Attribute
|
|
{
|
|
public string typeName;
|
|
|
|
public OverrideType(string typeName)
|
|
{
|
|
this.typeName = typeName;
|
|
}
|
|
}
|
|
|
|
// attribute used to force system generated fields to bottom of structs
|
|
[System.AttributeUsage(System.AttributeTargets.Field)]
|
|
class SystemGenerated : System.Attribute
|
|
{
|
|
public SystemGenerated()
|
|
{
|
|
}
|
|
}
|
|
|
|
// attribute used to disable a field using a preprocessor #if
|
|
[System.AttributeUsage(System.AttributeTargets.Field)]
|
|
class PreprocessorIf : System.Attribute
|
|
{
|
|
public string conditional;
|
|
|
|
public PreprocessorIf(string conditional)
|
|
{
|
|
this.conditional = conditional;
|
|
}
|
|
}
|
|
|
|
class NewGraphAction : EndNameEditAction
|
|
{
|
|
AbstractMaterialNode m_Node;
|
|
public AbstractMaterialNode node
|
|
{
|
|
get { return m_Node; }
|
|
set { m_Node = value; }
|
|
}
|
|
|
|
public override void Action(int instanceId, string pathName, string resourceFile)
|
|
{
|
|
var graph = new GraphData();
|
|
graph.AddNode(node);
|
|
graph.path = "Shader Graphs";
|
|
FileUtilities.WriteShaderGraphToDisk(pathName, graph);
|
|
AssetDatabase.Refresh();
|
|
|
|
UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath<Shader>(pathName);
|
|
Selection.activeObject = obj;
|
|
}
|
|
}
|
|
|
|
static class GraphUtil
|
|
{
|
|
internal static string ConvertCamelCase(string text, bool preserveAcronyms)
|
|
{
|
|
if (string.IsNullOrEmpty(text))
|
|
return string.Empty;
|
|
StringBuilder newText = new StringBuilder(text.Length * 2);
|
|
newText.Append(text[0]);
|
|
for (int i = 1; i < text.Length; i++)
|
|
{
|
|
if (char.IsUpper(text[i]))
|
|
if ((text[i - 1] != ' ' && !char.IsUpper(text[i - 1])) ||
|
|
(preserveAcronyms && char.IsUpper(text[i - 1]) &&
|
|
i < text.Length - 1 && !char.IsUpper(text[i + 1])))
|
|
newText.Append(' ');
|
|
newText.Append(text[i]);
|
|
}
|
|
return newText.ToString();
|
|
}
|
|
|
|
public static void CreateNewGraph(AbstractMaterialNode node)
|
|
{
|
|
var graphItem = ScriptableObject.CreateInstance<NewGraphAction>();
|
|
graphItem.node = node;
|
|
ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, graphItem,
|
|
string.Format("New Shader Graph.{0}", ShaderGraphImporter.Extension), null, null);
|
|
}
|
|
|
|
public static Type GetOutputNodeType(string path)
|
|
{
|
|
ShaderGraphMetadata metadata = null;
|
|
foreach (var asset in AssetDatabase.LoadAllAssetsAtPath(path))
|
|
{
|
|
if (asset is ShaderGraphMetadata metadataAsset)
|
|
{
|
|
metadata = metadataAsset;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (metadata == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var outputNodeTypeName = metadata.outputNodeTypeName;
|
|
foreach (var type in TypeCache.GetTypesDerivedFrom<IMasterNode>())
|
|
{
|
|
if (type.FullName == outputNodeTypeName)
|
|
{
|
|
return type;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static bool IsShaderGraph(this Shader shader)
|
|
{
|
|
var path = AssetDatabase.GetAssetPath(shader);
|
|
var importer = AssetImporter.GetAtPath(path);
|
|
return importer is ShaderGraphImporter;
|
|
}
|
|
|
|
static void Visit(List<AbstractMaterialNode> outputList, Dictionary<Guid, AbstractMaterialNode> unmarkedNodes, AbstractMaterialNode node)
|
|
{
|
|
if (!unmarkedNodes.ContainsKey(node.guid))
|
|
return;
|
|
foreach (var slot in node.GetInputSlots<ISlot>())
|
|
{
|
|
foreach (var edge in node.owner.GetEdges(slot.slotReference))
|
|
{
|
|
var inputNode = node.owner.GetNodeFromGuid(edge.outputSlot.nodeGuid);
|
|
Visit(outputList, unmarkedNodes, inputNode);
|
|
}
|
|
}
|
|
unmarkedNodes.Remove(node.guid);
|
|
outputList.Add(node);
|
|
}
|
|
|
|
static Dictionary<SerializationHelper.TypeSerializationInfo, SerializationHelper.TypeSerializationInfo> s_LegacyTypeRemapping;
|
|
|
|
public static Dictionary<SerializationHelper.TypeSerializationInfo, SerializationHelper.TypeSerializationInfo> GetLegacyTypeRemapping()
|
|
{
|
|
if (s_LegacyTypeRemapping == null)
|
|
{
|
|
s_LegacyTypeRemapping = new Dictionary<SerializationHelper.TypeSerializationInfo, SerializationHelper.TypeSerializationInfo>();
|
|
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
|
{
|
|
foreach (var type in assembly.GetTypesOrNothing())
|
|
{
|
|
if (type.IsAbstract)
|
|
continue;
|
|
foreach (var attribute in type.GetCustomAttributes(typeof(FormerNameAttribute), false))
|
|
{
|
|
var legacyAttribute = (FormerNameAttribute)attribute;
|
|
var serializationInfo = new SerializationHelper.TypeSerializationInfo { fullName = legacyAttribute.fullName };
|
|
s_LegacyTypeRemapping[serializationInfo] = SerializationHelper.GetTypeSerializableAsString(type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return s_LegacyTypeRemapping;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sanitizes a supplied string such that it does not collide
|
|
/// with any other name in a collection.
|
|
/// </summary>
|
|
/// <param name="existingNames">
|
|
/// A collection of names that the new name should not collide with.
|
|
/// </param>
|
|
/// <param name="duplicateFormat">
|
|
/// The format applied to the name if a duplicate exists.
|
|
/// This must be a format string that contains `{0}` and `{1}`
|
|
/// once each. An example could be `{0} ({1})`, which will append ` (n)`
|
|
/// to the name for the n`th duplicate.
|
|
/// </param>
|
|
/// <param name="name">
|
|
/// The name to be sanitized.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A name that is distinct form any name in `existingNames`.
|
|
/// </returns>
|
|
internal static string SanitizeName(IEnumerable<string> existingNames, string duplicateFormat, string name)
|
|
{
|
|
if (!existingNames.Contains(name))
|
|
return name;
|
|
|
|
string escapedDuplicateFormat = Regex.Escape(duplicateFormat);
|
|
|
|
// Escaped format will escape string interpolation, so the escape caracters must be removed for these.
|
|
escapedDuplicateFormat = escapedDuplicateFormat.Replace(@"\{0}", @"{0}");
|
|
escapedDuplicateFormat = escapedDuplicateFormat.Replace(@"\{1}", @"{1}");
|
|
|
|
var baseRegex = new Regex(string.Format(escapedDuplicateFormat, @"^(.*)", @"(\d+)"));
|
|
|
|
var baseMatch = baseRegex.Match(name);
|
|
if (baseMatch.Success)
|
|
name = baseMatch.Groups[1].Value;
|
|
|
|
string baseNameExpression = string.Format(@"^{0}", Regex.Escape(name));
|
|
var regex = new Regex(string.Format(escapedDuplicateFormat, baseNameExpression, @"(\d+)") + "$");
|
|
|
|
var existingDuplicateNumbers = existingNames.Select(existingName => regex.Match(existingName)).Where(m => m.Success).Select(m => int.Parse(m.Groups[1].Value)).Where(n => n > 0).Distinct().ToList();
|
|
|
|
var duplicateNumber = 1;
|
|
existingDuplicateNumbers.Sort();
|
|
if (existingDuplicateNumbers.Any() && existingDuplicateNumbers.First() == 1)
|
|
{
|
|
duplicateNumber = existingDuplicateNumbers.Last() + 1;
|
|
for (var i = 1; i < existingDuplicateNumbers.Count; i++)
|
|
{
|
|
if (existingDuplicateNumbers[i - 1] != existingDuplicateNumbers[i] - 1)
|
|
{
|
|
duplicateNumber = existingDuplicateNumbers[i - 1] + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return string.Format(duplicateFormat, name, duplicateNumber);
|
|
}
|
|
|
|
public static bool WriteToFile(string path, string content)
|
|
{
|
|
try
|
|
{
|
|
File.WriteAllText(path, content);
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError(e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static ProcessStartInfo CreateProcessStartInfo(string filePath)
|
|
{
|
|
string externalScriptEditor = ScriptEditorUtility.GetExternalScriptEditor();
|
|
|
|
ProcessStartInfo psi = new ProcessStartInfo();
|
|
psi.UseShellExecute = false;
|
|
|
|
|
|
#if UNITY_EDITOR_OSX
|
|
string arg = string.Format("-a \"{0}\" -n --args \"{1}\"", externalScriptEditor, Path.GetFullPath(filePath));
|
|
psi.FileName = "open";
|
|
psi.Arguments = arg;
|
|
#else
|
|
psi.Arguments = Path.GetFileName(filePath);
|
|
psi.WorkingDirectory = Path.GetDirectoryName(filePath);
|
|
psi.FileName = externalScriptEditor;
|
|
#endif
|
|
return psi;
|
|
}
|
|
|
|
public static void OpenFile(string path)
|
|
{
|
|
string filePath = Path.GetFullPath(path);
|
|
if (!File.Exists(filePath))
|
|
{
|
|
Debug.LogError(string.Format("Path {0} doesn't exists", path));
|
|
return;
|
|
}
|
|
|
|
string externalScriptEditor = ScriptEditorUtility.GetExternalScriptEditor();
|
|
if (externalScriptEditor != "internal")
|
|
{
|
|
ProcessStartInfo psi = CreateProcessStartInfo(filePath);
|
|
Process.Start(psi);
|
|
}
|
|
else
|
|
{
|
|
Process p = new Process();
|
|
p.StartInfo.FileName = filePath;
|
|
p.EnableRaisingEvents = true;
|
|
p.Exited += (Object obj, EventArgs args) =>
|
|
{
|
|
if(p.ExitCode != 0)
|
|
Debug.LogWarningFormat("Unable to open {0}: Check external editor in preferences", filePath);
|
|
};
|
|
p.Start();
|
|
}
|
|
}
|
|
}
|
|
}
|