You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
516 lines
22 KiB
516 lines
22 KiB
using System;
|
|
using System.Text;
|
|
using System.Linq;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
|
|
using UnityEngine;
|
|
using UnityEngine.VFX;
|
|
|
|
namespace UnityEditor.VFX.Block
|
|
{
|
|
class AttributeVariantProvider : VariantProvider
|
|
{
|
|
private readonly string m_Attribute;
|
|
|
|
public AttributeVariantProvider(string attribute)
|
|
{
|
|
m_Attribute = attribute;
|
|
}
|
|
|
|
public override IEnumerable<Variant> GetVariants()
|
|
{
|
|
var randoms = new[] { RandomMode.Off, RandomMode.Uniform, RandomMode.PerComponent };
|
|
var sources = new[] { SetAttribute.ValueSource.Slot, SetAttribute.ValueSource.Source };
|
|
var compositions = new[] { AttributeCompositionMode.Overwrite, AttributeCompositionMode.Add, AttributeCompositionMode.Multiply, AttributeCompositionMode.Blend };
|
|
|
|
var attributeRefSize = VFXExpressionHelper.GetSizeOfType(VFXAttributesManager.FindBuiltInOnly(m_Attribute).type);;
|
|
foreach (var random in randoms)
|
|
{
|
|
foreach (var source in sources)
|
|
{
|
|
foreach (var composition in compositions)
|
|
{
|
|
if (random != RandomMode.Off && source == SetAttribute.ValueSource.Source)
|
|
continue;
|
|
|
|
if (composition != AttributeCompositionMode.Overwrite && source == SetAttribute.ValueSource.Source)
|
|
continue;
|
|
|
|
if (composition != AttributeCompositionMode.Overwrite && m_Attribute == VFXAttribute.Alive.name)
|
|
continue;
|
|
|
|
if (random == RandomMode.PerComponent && attributeRefSize == 1)
|
|
continue;
|
|
|
|
// This is the main variant settings
|
|
if (composition == AttributeCompositionMode.Overwrite && source == SetAttribute.ValueSource.Slot && random == RandomMode.Off)
|
|
continue;
|
|
|
|
var compositionString = $"{VFXBlockUtility.GetNameString(composition)}";
|
|
yield return new Variant(
|
|
source != SetAttribute.ValueSource.Source ? $"{compositionString} {m_Attribute} | Random {random}" : $"Inherit source {m_Attribute}",
|
|
m_Attribute != VFXAttribute.Alive.name ? compositionString : null,
|
|
typeof(SetAttribute),
|
|
new[]
|
|
{
|
|
new KeyValuePair<string, object>("attribute", m_Attribute),
|
|
new KeyValuePair<string, object>("Random", random),
|
|
new KeyValuePair<string, object>("Source", source),
|
|
new KeyValuePair<string, object>("Composition", composition)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class SetAttributeVariantReadWritable : VariantProvider
|
|
{
|
|
public override IEnumerable<Variant> GetVariants()
|
|
{
|
|
var attributes = VFXAttributesManager.GetBuiltInNamesOrCombination(true, false, false, true);
|
|
|
|
foreach (var attribute in attributes)
|
|
{
|
|
yield return new Variant(
|
|
$"Set {attribute}",
|
|
"Attribute",
|
|
typeof(SetAttribute),
|
|
new[]
|
|
{
|
|
new KeyValuePair<string, object>("attribute", attribute),
|
|
new KeyValuePair<string, object>("Random", RandomMode.Off),
|
|
new KeyValuePair<string, object>("Source", SetAttribute.ValueSource.Slot),
|
|
new KeyValuePair<string, object>("Composition", AttributeCompositionMode.Overwrite)
|
|
},
|
|
() => new AttributeVariantProvider(attribute));
|
|
}
|
|
}
|
|
}
|
|
|
|
[VFXHelpURL("Block-SetAttribute")]
|
|
[VFXInfo(variantProvider = typeof(SetAttributeVariantReadWritable))]
|
|
class SetAttribute : VFXBlock
|
|
{
|
|
public enum ValueSource
|
|
{
|
|
Slot,
|
|
Source
|
|
}
|
|
|
|
[VFXSetting(VFXSettingAttribute.VisibleFlags.InInspector), StringProvider(typeof(ReadWritableAttributeProvider))]
|
|
public string attribute;
|
|
|
|
[VFXSetting(VFXSettingAttribute.VisibleFlags.InInspector), Tooltip("Specifies what operation to perform on the chosen attribute. The input value can overwrite, add to, multiply with, or blend with the existing attribute value.")]
|
|
public AttributeCompositionMode Composition = AttributeCompositionMode.Overwrite;
|
|
|
|
[VFXSetting(VFXSettingAttribute.VisibleFlags.InInspector), Tooltip("Specifies the source of the attribute data. 'Slot' enables a user input to modify the value, while a 'Source' attribute derives its value from a Spawn event attribute or inherits it from a parent system via a GPU event.")]
|
|
public ValueSource Source = ValueSource.Slot;
|
|
|
|
[VFXSetting(VFXSettingAttribute.VisibleFlags.InInspector), Tooltip("Specifies whether random values can be derived from this block. Random values can be turned off, derived per component, or be uniform.")]
|
|
public RandomMode Random = RandomMode.Off;
|
|
|
|
[VFXSetting, Tooltip("Specifies which channels to use in this block. This is useful for only storing relevant data if not all channels are used.")]
|
|
public VariadicChannelOptions channels = VariadicChannelOptions.XYZ;
|
|
private static readonly char[] channelNames = new char[] { 'x', 'y', 'z' };
|
|
|
|
public override string name => ComputeName();
|
|
|
|
private bool TryGetAttribute(out VFXAttribute vfxAttribute)
|
|
{
|
|
vfxAttribute = currentAttribute;
|
|
return !string.IsNullOrEmpty(vfxAttribute.name);
|
|
}
|
|
|
|
private string ComputeName()
|
|
{
|
|
if (!TryGetAttribute(out var vfxAttribute))
|
|
{
|
|
vfxAttribute = new VFXAttribute { name = attribute };
|
|
}
|
|
|
|
if (Source != ValueSource.Slot && Source != ValueSource.Source)
|
|
throw new NotImplementedException(Source.ToString());
|
|
|
|
var builder = new StringBuilder(24);
|
|
if (Source == ValueSource.Slot)
|
|
builder.AppendFormat("{0} ", VFXBlockUtility.GetNameString(Composition));
|
|
else
|
|
builder.Append("Inherit Source ");
|
|
|
|
builder.Append(ObjectNames.NicifyVariableName(attribute));
|
|
if (vfxAttribute.variadic == VFXVariadic.True)
|
|
builder.AppendFormat(".{0}", channels.ToString());
|
|
|
|
if (Source == ValueSource.Slot)
|
|
{
|
|
if (Random != RandomMode.Off)
|
|
builder.AppendFormat(" {0}", VFXBlockUtility.GetNameString(Random));
|
|
}
|
|
else
|
|
builder.AppendFormat(" ({0})", VFXBlockUtility.GetNameString(Composition));
|
|
|
|
return builder.ToString();
|
|
}
|
|
|
|
public override VFXContextType compatibleContexts => VFXContextType.InitAndUpdateAndOutput;
|
|
public override VFXDataType compatibleData => VFXDataType.Particle;
|
|
|
|
public override void Sanitize(int version)
|
|
{
|
|
if (VFXBlockUtility.SanitizeAttribute(GetGraph(), ref attribute, ref channels, version))
|
|
Invalidate(InvalidationCause.kSettingChanged);
|
|
|
|
base.Sanitize(version);
|
|
if (version <= 1 && inputSlots.Any(o => o.spaceable))
|
|
{
|
|
//Space has been added with on a few specific attributes, automatically copying space from context
|
|
var contextSpace = GetParent().space;
|
|
foreach (var slot in inputSlots.Where(o => o.spaceable))
|
|
{
|
|
slot.space = contextSpace;
|
|
}
|
|
Debug.Log($"Sanitizing attribute {attribute} : settings space to {contextSpace} (retrieved from context)");
|
|
}
|
|
}
|
|
|
|
public override void CheckGraphBeforeImport()
|
|
{
|
|
base.CheckGraphBeforeImport();
|
|
SyncCustomAttributeIfNeeded();
|
|
}
|
|
|
|
protected override IEnumerable<string> filteredOutSettings
|
|
{
|
|
get
|
|
{
|
|
if (Source != ValueSource.Slot)
|
|
yield return "Random";
|
|
|
|
if (!TryGetAttribute(out var vfxAttribute) || vfxAttribute.variadic == VFXVariadic.False)
|
|
yield return "channels";
|
|
|
|
foreach (var setting in base.filteredOutSettings)
|
|
yield return setting;
|
|
}
|
|
}
|
|
|
|
public override IEnumerable<VFXAttributeInfo> attributes
|
|
{
|
|
get
|
|
{
|
|
if (TryGetAttribute(out var attrib))
|
|
{
|
|
var attributeMode = Composition == AttributeCompositionMode.Overwrite ? VFXAttributeMode.Write : VFXAttributeMode.ReadWrite;
|
|
if (attrib.variadic == VFXVariadic.True)
|
|
{
|
|
var channelsString = channels.ToString();
|
|
foreach (var channel in channelsString)
|
|
yield return new VFXAttributeInfo(VFXAttributesManager.FindBuiltInOnly(attrib.name + channel), attributeMode);
|
|
}
|
|
else
|
|
{
|
|
yield return new VFXAttributeInfo(attrib, attributeMode);
|
|
}
|
|
if (Random != RandomMode.Off && Source == ValueSource.Slot)
|
|
yield return new VFXAttributeInfo(VFXAttribute.Seed, VFXAttributeMode.ReadWrite);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static string GenerateLocalAttributeName(string name)
|
|
{
|
|
return "_" + name[0].ToString().ToUpper(CultureInfo.InvariantCulture) + name.Substring(1);
|
|
}
|
|
|
|
public override string source
|
|
{
|
|
get
|
|
{
|
|
if (!TryGetAttribute(out var attrib))
|
|
return string.Empty;
|
|
|
|
string source = "";
|
|
|
|
int attributeSize = VFXExpression.TypeToSize(attrib.type);
|
|
int loopCount = 1;
|
|
if (attrib.variadic == VFXVariadic.True)
|
|
{
|
|
attributeSize = 1;
|
|
loopCount = channels.ToString().Length;
|
|
}
|
|
|
|
for (int i = 0; i < loopCount; i++)
|
|
{
|
|
string paramPostfix = (attrib.variadic == VFXVariadic.True) ? "." + channelNames[i] : "";
|
|
string attributePostfix = (attrib.variadic == VFXVariadic.True) ? char.ToUpper(channels.ToString()[i]).ToString() : "";
|
|
|
|
string channelSource = "";
|
|
if (Source == ValueSource.Slot)
|
|
{
|
|
if (Random == RandomMode.Off)
|
|
channelSource = VFXBlockUtility.GetRandomMacroString(Random, attributeSize, paramPostfix, GenerateLocalAttributeName(attrib.name));
|
|
else
|
|
channelSource = VFXBlockUtility.GetRandomMacroString(Random, attributeSize, paramPostfix, "A", "B");
|
|
}
|
|
else
|
|
{
|
|
channelSource = VFXBlockUtility.GetRandomMacroString(RandomMode.Off, attributeSize, paramPostfix, "Value");
|
|
}
|
|
|
|
if (Composition == AttributeCompositionMode.Blend)
|
|
channelSource = VFXBlockUtility.GetComposeString(Composition, attrib.name + attributePostfix, channelSource, "Blend");
|
|
else
|
|
channelSource = VFXBlockUtility.GetComposeString(Composition, attrib.name + attributePostfix, channelSource);
|
|
|
|
if (i < loopCount - 1)
|
|
channelSource += "\n";
|
|
|
|
source += channelSource;
|
|
}
|
|
|
|
return source;
|
|
}
|
|
}
|
|
|
|
public override IEnumerable<VFXNamedExpression> parameters
|
|
{
|
|
get
|
|
{
|
|
if (TryGetAttribute(out var attrib))
|
|
{
|
|
foreach (var param in base.parameters)
|
|
{
|
|
if (param.name is "Value" or "A" or "B" && Source == ValueSource.Source)
|
|
continue;
|
|
yield return param;
|
|
}
|
|
|
|
if (Source == ValueSource.Source)
|
|
{
|
|
VFXExpression sourceExpression = null;
|
|
if (attrib.variadic == VFXVariadic.True)
|
|
{
|
|
var currentChannels = channels.ToString().Select(c => char.ToUpper(c));
|
|
var currentChannelsExpression = currentChannels.Select(o =>
|
|
{
|
|
var subAttrib = VFXAttributesManager.FindBuiltInOnly(attribute + o);
|
|
return new VFXAttributeExpression(subAttrib, VFXAttributeLocation.Source);
|
|
}).ToArray();
|
|
|
|
if (currentChannelsExpression.Length == 1)
|
|
sourceExpression = currentChannelsExpression[0];
|
|
else
|
|
sourceExpression = new VFXExpressionCombine(currentChannelsExpression);
|
|
}
|
|
else
|
|
{
|
|
sourceExpression = new VFXAttributeExpression(attrib, VFXAttributeLocation.Source);
|
|
}
|
|
yield return new VFXNamedExpression(sourceExpression, "Value");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private int ChannelToIndex(char channel)
|
|
{
|
|
switch (channel)
|
|
{
|
|
default:
|
|
case 'X': return 0;
|
|
case 'Y': return 1;
|
|
case 'Z': return 2;
|
|
case 'W': return 3;
|
|
}
|
|
}
|
|
|
|
protected override IEnumerable<VFXPropertyWithValue> inputProperties
|
|
{
|
|
get
|
|
{
|
|
if (TryGetAttribute(out var attrib))
|
|
{
|
|
if (Source != ValueSource.Source)
|
|
{
|
|
TooltipAttribute tooltip = new TooltipAttribute(attrib.description);
|
|
var attr = attrib.Equals(VFXAttribute.Color)
|
|
? new VFXPropertyAttributes(new ShowAsColorAttribute(), tooltip)
|
|
: new VFXPropertyAttributes(tooltip);
|
|
|
|
|
|
Type slotType = VFXExpression.TypeToType(attrib.type);
|
|
object content = attrib.value.GetContent();
|
|
|
|
if (attrib.space != SpaceableType.None)
|
|
{
|
|
var contentAsVector3 = (Vector3)content;
|
|
switch (attrib.space)
|
|
{
|
|
case SpaceableType.Position: content = (Position)contentAsVector3; break;
|
|
case SpaceableType.Direction: content = (DirectionType)contentAsVector3; break;
|
|
case SpaceableType.Vector: content = (Vector)contentAsVector3; break;
|
|
default: throw new InvalidOperationException("Space is not handled for attribute : " + attrib.name + " space : " + attrib.space);
|
|
}
|
|
slotType = content.GetType();
|
|
}
|
|
|
|
if (attrib.variadic == VFXVariadic.True)
|
|
{
|
|
string channelsString = channels.ToString();
|
|
|
|
int length = channelsString.Length;
|
|
switch (length)
|
|
{
|
|
case 1:
|
|
slotType = typeof(float);
|
|
content = ((Vector3)content)[ChannelToIndex(channelsString[0])];
|
|
break;
|
|
case 2:
|
|
slotType = typeof(Vector2);
|
|
Vector2 v = (Vector2)(Vector3)content;
|
|
for (int i = 0; i < 2; i++)
|
|
v[i] = ((Vector3)content)[ChannelToIndex(channelsString[i])];
|
|
content = v;
|
|
break;
|
|
case 3:
|
|
slotType = typeof(Vector3);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Random == RandomMode.Off)
|
|
{
|
|
yield return new VFXPropertyWithValue(new VFXProperty(slotType, GenerateLocalAttributeName(attrib.name), attr), content);
|
|
}
|
|
else
|
|
{
|
|
yield return new VFXPropertyWithValue(new VFXProperty(slotType, "A", attr), content);
|
|
yield return new VFXPropertyWithValue(new VFXProperty(slotType, "B", attr), content);
|
|
}
|
|
}
|
|
|
|
if (Composition == AttributeCompositionMode.Blend)
|
|
yield return new VFXPropertyWithValue(new VFXProperty(typeof(float), "Blend", new RangeAttribute(0.0f, 1.0f)));
|
|
}
|
|
}
|
|
}
|
|
|
|
private VFXAttribute currentAttribute
|
|
{
|
|
get
|
|
{
|
|
if (GetGraph() is { } graph)
|
|
{
|
|
if (graph.attributesManager.TryFind(attribute, out var vfxAttribute))
|
|
{
|
|
return vfxAttribute;
|
|
}
|
|
}
|
|
else // Happens when the node is not yet added to the graph, but should be ok as soon as it's added (see OnAdded)
|
|
{
|
|
var attr = VFXAttributesManager.FindBuiltInOnly(attribute);
|
|
if (string.Compare(attribute, attr.name, StringComparison.OrdinalIgnoreCase) == 0)
|
|
{
|
|
return attr;
|
|
}
|
|
}
|
|
|
|
// Temporary attribute
|
|
return default;
|
|
}
|
|
}
|
|
|
|
public override void Rename(string oldName, string newName)
|
|
{
|
|
if (GetGraph() is {} graph && graph.attributesManager.IsCustom(newName))
|
|
{
|
|
attribute = newName;
|
|
SyncSlots(VFXSlot.Direction.kInput, true);
|
|
}
|
|
}
|
|
|
|
internal sealed override void GenerateErrors(VFXErrorReporter report)
|
|
{
|
|
base.GenerateErrors(report);
|
|
|
|
if (!CustomAttributeUtility.IsShaderCompilableName(attribute))
|
|
{
|
|
report.RegisterError("InvalidCustomAttributeName", VFXErrorType.Error, $"Custom attribute name '{attribute}' is not valid.\n -The name must not contain spaces or any special character\n -The name must not start with a digit character", this);
|
|
}
|
|
|
|
if (Source == ValueSource.Source && GetParent() is { } parentContext && (parentContext.contextType & VFXContextType.UpdateAndOutput) != 0)
|
|
{
|
|
report.RegisterError("SourceNotAllowed", VFXErrorType.Warning, "Inherit attribute is not supported in Update and Output contexts", this);
|
|
}
|
|
|
|
if (GetNbInputSlots() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (attribute == VFXAttribute.Lifetime.name)
|
|
{
|
|
var context = new VFXExpression.Context(VFXExpressionContextOption.CPUEvaluation | VFXExpressionContextOption.ConstantFolding);
|
|
var expression = GetInputSlot(0).GetExpression();
|
|
context.RegisterExpression(expression);
|
|
context.Compile();
|
|
|
|
if (context.GetReduced(expression) is var lifeTimeExpression &&
|
|
lifeTimeExpression.Is(VFXExpression.Flags.Constant) &&
|
|
lifeTimeExpression.Get<float>() is var lifeTime &&
|
|
lifeTime > 1e4)
|
|
{
|
|
report.RegisterError("TooLongLifeTime", VFXErrorType.Warning, $"The lifetime is pretty high: {TimeSpan.FromSeconds(lifeTime):hh\\hmm\\m}.\nYou might prefer to make immortal particles by removing the Set Lifetime block.", this);
|
|
}
|
|
}
|
|
|
|
if (attribute == VFXAttribute.scale.name)
|
|
{
|
|
var context = new VFXExpression.Context(VFXExpressionContextOption.CPUEvaluation | VFXExpressionContextOption.ConstantFolding);
|
|
var expression = GetInputSlot(0).GetExpression();
|
|
context.RegisterExpression(expression);
|
|
context.Compile();
|
|
|
|
if (context.GetReduced(expression) is var scaleExpression &&
|
|
scaleExpression.Is(VFXExpression.Flags.Constant))
|
|
{
|
|
if (scaleExpression.valueType == VFXValueType.Float && Mathf.Approximately(scaleExpression.Get<float>(), 0f) ||
|
|
scaleExpression.valueType == VFXValueType.Float2 && scaleExpression.Get<Vector2>() is var vec2 && Mathf.Approximately(vec2.x * vec2.y, 0f) ||
|
|
scaleExpression.valueType == VFXValueType.Float3 && scaleExpression.Get<Vector3>() is var vec3 && Mathf.Approximately(vec3.x * vec3.y * vec3.z, 0f) )
|
|
{
|
|
report.RegisterError("ZeroScaleValue", VFXErrorType.Warning, "Scale is set to zero", this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnAdded()
|
|
{
|
|
base.OnAdded();
|
|
SyncCustomAttributeIfNeeded();
|
|
}
|
|
|
|
private void SyncCustomAttributeIfNeeded()
|
|
{
|
|
var graph = GetGraph();
|
|
if (graph != null)
|
|
{
|
|
if (graph.attributesManager.IsCustom(attribute))
|
|
{
|
|
Invalidate(InvalidationCause.kUIChangedTransient);
|
|
SyncSlots(VFXSlot.Direction.kInput, true);
|
|
}
|
|
else if (!string.IsNullOrEmpty(attribute) && !graph.attributesManager.TryFind(attribute, out _))
|
|
{
|
|
graph.TryAddCustomAttribute(attribute, VFXValueType.Float, string.Empty, false, out _);
|
|
graph.SetCustomAttributeDirty();
|
|
Invalidate(InvalidationCause.kUIChangedTransient);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|