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.
 
 
 
 
 

481 lines
21 KiB

using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.VFX;
using System.Globalization;
namespace UnityEditor.VFX.Block
{
class AttributeFromCurveVariantProvider : VariantProvider
{
private readonly string m_Attribute;
public AttributeFromCurveVariantProvider(string attribute)
{
m_Attribute = attribute;
}
public override IEnumerable<Variant> GetVariants()
{
var compositions = new[] { AttributeCompositionMode.Add, AttributeCompositionMode.Overwrite, AttributeCompositionMode.Multiply, AttributeCompositionMode.Blend };
var sampleModes = new[] { AttributeFromCurve.CurveSampleMode.OverLife, AttributeFromCurve.CurveSampleMode.BySpeed, AttributeFromCurve.CurveSampleMode.Random }.ToArray();
foreach (var composition in compositions)
{
foreach (var sampleMode in sampleModes)
{
if (m_Attribute == VFXAttribute.Age.name && sampleMode == AttributeFromCurve.CurveSampleMode.OverLife)
{
continue;
}
if (m_Attribute == VFXAttribute.Velocity.name && sampleMode == AttributeFromCurve.CurveSampleMode.BySpeed)
{
continue;
}
// This is the main variant settings
if (composition == AttributeCompositionMode.Overwrite && sampleMode == AttributeFromCurve.CurveSampleMode.OverLife)
{
continue;
}
var compositionString = $"{VFXBlockUtility.GetNameString(composition)}";
yield return new Variant(
$"{compositionString} {m_Attribute} | {VFXBlockUtility.GetNameString(sampleMode)}",
compositionString,
typeof(AttributeFromCurve),
new[]
{
new KeyValuePair<string, object>("attribute", m_Attribute),
new KeyValuePair<string, object>("Composition", composition),
new KeyValuePair<string, object>("SampleMode", sampleMode)
});
}
}
}
}
class AttributeFromCurveProvider : VariantProvider
{
public override IEnumerable<Variant> GetVariants()
{
var attributes = VFXAttributesManager.GetBuiltInNamesOrCombination(true, false, false, false).Except(new[] { VFXAttribute.Alive.name }).ToArray();
foreach (var attribute in attributes)
{
var sampleMode = attribute != VFXAttribute.Age.name ? AttributeFromCurve.CurveSampleMode.OverLife : AttributeFromCurve.CurveSampleMode.BySpeed;
yield return new Variant(
$"Set {attribute} | {VFXBlockUtility.GetNameString(sampleMode)}",
"Attribute from curve",
typeof(AttributeFromCurve),
new[]
{
new KeyValuePair<string, object>("attribute", attribute),
new KeyValuePair<string, object>("Composition", AttributeCompositionMode.Overwrite),
new KeyValuePair<string, object>("SampleMode", sampleMode)
},
() => new AttributeFromCurveVariantProvider(attribute));
}
}
}
[VFXHelpURL("Block-SetAttributeFromCurve")]
[VFXInfo(variantProvider = typeof(AttributeFromCurveProvider))]
class AttributeFromCurve : VFXBlock
{
public enum CurveSampleMode
{
OverLife,
BySpeed,
Random,
RandomConstantPerParticle,
Custom
}
public enum ComputeMode
{
Uniform,
PerComponent
}
public enum ColorApplicationMode
{
Color = 1 << 0,
Alpha = 1 << 1,
ColorAndAlpha = Color | Alpha,
}
[VFXSetting(VFXSettingAttribute.VisibleFlags.InInspector), StringProvider(typeof(ReadWritableAttributeProvider))]
public string attribute = VFXAttributesManager.GetBuiltInNamesOrCombination(true, false, false, false).First();
[VFXSetting(VFXSettingAttribute.VisibleFlags.InInspector), Tooltip("Specifies what operation to perform on the chosen attribute. The value derived from this block can overwrite, add to, multiply with, or blend with the existing attribute value.")]
public AttributeCompositionMode Composition = AttributeCompositionMode.Overwrite;
[VFXSetting(VFXSettingAttribute.VisibleFlags.InInspector), Tooltip("Specifies what operation to perform on the alpha value. The value derived from this block can overwrite, add to, multiply with, or blend with the existing alpha value.")]
public AttributeCompositionMode AlphaComposition = AttributeCompositionMode.Overwrite;
[VFXSetting, Tooltip("Specifies the method by which to sample the curve. This can be over the particle's lifetime, its speed, randomly, or through a user-specified value.")]
public CurveSampleMode SampleMode = CurveSampleMode.OverLife;
[VFXSetting(VFXSettingAttribute.VisibleFlags.InInspector), Tooltip("Specifies whether the block operates per component or uniformly across all components of the chosen attribute.")]
public ComputeMode Mode = ComputeMode.PerComponent;
[VFXSetting, Tooltip("Specifies whether the color is applied to RGB, alpha, or both.")]
public ColorApplicationMode ColorMode = ColorApplicationMode.ColorAndAlpha;
[VFXSetting, Tooltip("Specifies which channels to use in this block. This is useful for only affecting the relevant data if not all channels are used.")]
public VariadicChannelOptions channels = VariadicChannelOptions.XYZ;
private static readonly char[] channelNames = new char[] { 'x', 'y', 'z' };
private string GenerateName()
{
var variadicName = currentAttribute.variadic == VFXVariadic.True ? "." + channels : string.Empty;
var n = VFXBlockUtility.GetNameString(Composition) + " " + ObjectNames.NicifyVariableName(attribute) + variadicName;
switch (SampleMode)
{
case CurveSampleMode.OverLife: n += " over Life"; break;
case CurveSampleMode.BySpeed: n += " by Speed"; break;
case CurveSampleMode.Random: n += $" Random from {(attribute == VFXAttribute.Color.name ? "Gradient" : "Curve")}"; break;
case CurveSampleMode.RandomConstantPerParticle: n += $" Random from {(attribute == VFXAttribute.Color.name ? "Gradient" : "Curve")} (Constant per-particle)"; break;
case CurveSampleMode.Custom: n += " Custom"; break;
default:
throw new NotImplementedException("Invalid CurveSampleMode");
}
return n;
}
public override string name => GenerateName();
public override VFXContextType compatibleContexts => VFXContextType.InitAndUpdateAndOutput;
public override VFXDataType compatibleData => VFXDataType.Particle;
public override IEnumerable<VFXAttributeInfo> attributes
{
get
{
VFXAttributeMode attributeMode = (Composition != AttributeCompositionMode.Overwrite) ? VFXAttributeMode.ReadWrite : VFXAttributeMode.Write;
VFXAttributeMode alphaAttributeMode = (AlphaComposition != AttributeCompositionMode.Overwrite) ? VFXAttributeMode.ReadWrite : VFXAttributeMode.Write;
var attrib = currentAttribute;
if (attrib.Equals(VFXAttribute.Color))
{
if ((ColorMode & ColorApplicationMode.Color) != 0)
yield return new VFXAttributeInfo(VFXAttribute.Color, attributeMode);
if ((ColorMode & ColorApplicationMode.Alpha) != 0)
yield return new VFXAttributeInfo(VFXAttribute.Alpha, alphaAttributeMode);
}
else
{
if (attrib.variadic == VFXVariadic.True)
{
string channelsString = channels.ToString();
for (int i = 0; i < channelsString.Length; i++)
yield return new VFXAttributeInfo(VFXAttributesManager.FindBuiltInOnly(attrib.name + channelsString[i]), attributeMode);
}
else
{
yield return new VFXAttributeInfo(attrib, attributeMode);
}
}
switch (SampleMode)
{
case CurveSampleMode.OverLife:
yield return new VFXAttributeInfo(VFXAttribute.Age, VFXAttributeMode.Read);
yield return new VFXAttributeInfo(VFXAttribute.Lifetime, VFXAttributeMode.Read);
break;
case CurveSampleMode.BySpeed:
yield return new VFXAttributeInfo(VFXAttribute.Velocity, VFXAttributeMode.Read);
break;
case CurveSampleMode.Random:
yield return new VFXAttributeInfo(VFXAttribute.Seed, VFXAttributeMode.ReadWrite);
break;
case CurveSampleMode.RandomConstantPerParticle:
yield return new VFXAttributeInfo(VFXAttribute.ParticleId, VFXAttributeMode.Read);
break;
default:
break;
}
}
}
public override void Sanitize(int version)
{
if (GetGraph() is {} graph)
{
if (VFXBlockUtility.SanitizeAttribute(graph, ref attribute, ref channels, version))
{
Invalidate(InvalidationCause.kSettingChanged);
}
}
else
{
Debug.LogError($"Trying to find attribute '{attribute}' when graph is not available");
}
base.Sanitize(version);
}
protected override IEnumerable<string> filteredOutSettings
{
get
{
var attrib = currentAttribute;
if (attrib.variadic == VFXVariadic.False)
yield return "channels";
if (VFXExpression.TypeToSize(attrib.type) == 1)
yield return "Mode";
if (!currentAttribute.Equals(VFXAttribute.Color))
{
yield return "ColorMode";
yield return "AlphaComposition";
}
foreach (var setting in base.filteredOutSettings)
yield return setting;
}
}
static private string GenerateLocalAttributeName(string name)
{
return name[0].ToString().ToUpper(CultureInfo.InvariantCulture) + name.Substring(1);
}
public override string source
{
get
{
string source = "";
var attrib = currentAttribute;
bool isColor = currentAttribute.Equals(VFXAttribute.Color);
int attributeCount = isColor ? 2 : 1;
int attributeSize = isColor ? 4 : VFXExpression.TypeToSize(attrib.type);
int loopCount = 1;
if (attrib.variadic == VFXVariadic.True)
{
attributeSize = channels.ToString().Length;
loopCount = attributeSize;
}
source += GetFetchValueString(GenerateLocalAttributeName(attrib.name), attributeSize, Mode, SampleMode);
int attributeAddedCount = 0;
for (int attribIndex = 0; attribIndex < attributeCount; attribIndex++)
{
string attribName = attrib.name;
if (isColor)
{
if (((int)ColorMode & (1 << attribIndex)) == 0)
continue;
if (attribIndex == 1)
attribName = VFXAttribute.Alpha.name;
}
string channelSource = "";
if (attributeAddedCount > 0)
channelSource += "\n";
for (int i = 0; i < loopCount; i++)
{
AttributeCompositionMode compositionMode = Composition;
string paramPostfix = (attrib.variadic == VFXVariadic.True) ? "." + channelNames[i] : "";
if (isColor)
{
if (attribIndex == 0)
{
paramPostfix = ".rgb";
}
else
{
paramPostfix = ".a";
compositionMode = AlphaComposition;
}
}
string attributePostfix = (attrib.variadic == VFXVariadic.True) ? char.ToUpper(channels.ToString()[i]).ToString() : "";
if (compositionMode == AttributeCompositionMode.Blend)
channelSource += VFXBlockUtility.GetComposeString(compositionMode, attribName + attributePostfix, "value" + paramPostfix, "Blend");
else
channelSource += VFXBlockUtility.GetComposeString(compositionMode, attribName + attributePostfix, "value" + paramPostfix);
if (i < loopCount - 1)
channelSource += "\n";
}
source += channelSource;
attributeAddedCount++;
}
return source;
}
}
private string GetFetchValueString(string localName, int size, ComputeMode computeMode, CurveSampleMode sampleMode)
{
string output;
switch (SampleMode)
{
case CurveSampleMode.OverLife: output = "float t = age / lifetime;\n"; break;
case CurveSampleMode.BySpeed: output = "float t = saturate((length(velocity) - SpeedRange.x) * SpeedRange.y);\n"; break;
case CurveSampleMode.Random: output = "float t = RAND;\n"; break;
case CurveSampleMode.RandomConstantPerParticle: output = "float t = FIXED_RAND(Seed);\n"; break;
case CurveSampleMode.Custom: output = "float t = SampleTime;\n"; break;
default:
throw new NotImplementedException("Invalid CurveSampleMode");
}
output += $"float{(size == 1 ? "" : size.ToString())} value = 0.0f;\n";
if (computeMode == ComputeMode.Uniform || size == 1)
{
output += $"value = SampleCurve({localName}, t);\n";
}
else
{
if (currentAttribute.Equals(VFXAttribute.Color))
{
output += $"value = SampleGradient({localName}, t);\n";
}
else
{
if (size > 0) output += $"value[0] = SampleCurve({localName + "_x"}, t);\n";
if (size > 1) output += $"value[1] = SampleCurve({localName + "_y"}, t);\n";
if (size > 2) output += $"value[2] = SampleCurve({localName + "_z"}, t);\n";
if (size > 3) output += $"value[3] = SampleCurve({localName + "_w"}, t);\n";
}
}
return output;
}
protected override IEnumerable<VFXPropertyWithValue> inputProperties
{
get
{
var attrib = currentAttribute;
int size = VFXExpression.TypeToSize(attrib.type);
if (attrib.variadic == VFXVariadic.True)
size = channels.ToString().Length;
string localName = GenerateLocalAttributeName(attrib.name);
if (Mode == ComputeMode.Uniform || size == 1)
{
yield return new VFXPropertyWithValue(new VFXProperty(typeof(AnimationCurve), localName), VFXResources.defaultResources.animationCurve);
}
else
{
if (attrib.Equals(VFXAttribute.Color))
{
yield return new VFXPropertyWithValue(new VFXProperty(typeof(Gradient), localName), VFXResources.defaultResources.gradient);
}
else
{
if (size > 0) yield return new VFXPropertyWithValue(new VFXProperty(typeof(AnimationCurve), localName + "_x"), VFXResources.defaultResources.animationCurve);
if (size > 1) yield return new VFXPropertyWithValue(new VFXProperty(typeof(AnimationCurve), localName + "_y"), VFXResources.defaultResources.animationCurve);
if (size > 2) yield return new VFXPropertyWithValue(new VFXProperty(typeof(AnimationCurve), localName + "_z"), VFXResources.defaultResources.animationCurve);
if (size > 3) yield return new VFXPropertyWithValue(new VFXProperty(typeof(AnimationCurve), localName + "_w"), VFXResources.defaultResources.animationCurve);
}
}
if (SampleMode == CurveSampleMode.BySpeed)
yield return new VFXPropertyWithValue(new VFXProperty(typeof(Vector2), "SpeedRange", new MinAttribute(0.0f)), new Vector2(0.0f, 1.0f));
else if (SampleMode == CurveSampleMode.Custom)
yield return new VFXPropertyWithValue(new VFXProperty(typeof(float), "SampleTime"));
else if (SampleMode == CurveSampleMode.RandomConstantPerParticle)
yield return new VFXPropertyWithValue(new VFXProperty(typeof(uint), "Seed"));
if (Composition == AttributeCompositionMode.Blend || (attrib.Equals(VFXAttribute.Color) && AlphaComposition == AttributeCompositionMode.Blend))
yield return new VFXPropertyWithValue(new VFXProperty(typeof(float), "Blend"));
}
}
public override IEnumerable<VFXNamedExpression> parameters
{
get
{
VFXExpression speedRange = null;
foreach (var p in GetExpressionsFromSlots(this))
{
if (p.name == "SpeedRange")
speedRange = p.exp;
else
yield return p;
}
if (SampleMode == CurveSampleMode.BySpeed)
{
var speedRangeComponents = VFXOperatorUtility.ExtractComponents(speedRange).ToArray();
// speedRange.y = 1 / (speedRange.y - speedRange.x)
var speedRangeDelta = speedRangeComponents[1] - speedRangeComponents[0];
speedRangeComponents[1] = VFXOperatorUtility.OneExpression[VFXValueType.Float] / speedRangeDelta;
yield return new VFXNamedExpression(new VFXExpressionCombine(speedRangeComponents), "SpeedRange");
}
if (SampleMode == CurveSampleMode.RandomConstantPerParticle)
{
yield return new VFXNamedExpression(VFXBuiltInExpression.SystemSeed, "systemSeed");
}
}
}
protected override void OnAdded()
{
base.OnAdded();
// When using custom attribute we need to access to the graph to find the custom attribute
// and the graph is only available after the node being added to it.
if (GetGraph() is {} graph && graph.attributesManager.IsCustom(attribute))
{
Invalidate(InvalidationCause.kSettingChanged);
}
}
public 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 new VFXAttribute(attribute, VFXValueType.Float, null);
}
}
public override void Rename(string oldName, string newName)
{
if (GetGraph() is {} graph && graph.attributesManager.IsCustom(newName))
{
attribute = newName;
SyncSlots(VFXSlot.Direction.kInput, true);
}
}
}
}