using System; using System.Linq; using System.Collections.Generic; using UnityEngine; using UnityEngine.VFX; using UnityEngine.Rendering; namespace UnityEditor.VFX.Operator { class SampleMeshProvider : VariantProvider { protected virtual string nameTemplate { get; } = "Sample {0}"; protected virtual Type operatorType { get; } = typeof(SampleMesh); public override IEnumerable GetVariants() { yield return new Variant( string.Format(nameTemplate, "Mesh"), "Sampling", operatorType, new[] { new KeyValuePair("source", SampleMesh.SourceType.Mesh) }); yield return new Variant( string.Format(nameTemplate, "Skinned Mesh"), "Sampling", operatorType, new[] { new KeyValuePair("source", SampleMesh.SourceType.SkinnedMeshRenderer) }); } } [VFXHelpURL("Operator-SampleMesh")] [VFXInfo(variantProvider = typeof(SampleMeshProvider))] class SampleMesh : VFXOperator { public override string name { get { if (source == SourceType.Mesh) return "Sample Mesh"; else return "Sample Skinned Mesh"; } } public enum PlacementMode { Vertex, Edge, Surface }; public class InputPropertiesMesh { [Tooltip("Specifies the Mesh to sample from.")] public Mesh mesh = VFXResources.defaultResources.mesh; } public class InputPropertiesSkinnedMeshRenderer { [Tooltip("Specifies the Skinned Mesh Renderer component to sample from. The Skinned Mesh Renderer has to be an exposed entry.")] public SkinnedMeshRenderer skinnedMesh = null; } public class InputPropertiesPlacementVertex { [Tooltip("Sets the vertex index to read from.")] public uint vertex = 0u; } public enum SurfaceCoordinates { Barycentric, Uniform, } public class InputPropertiesEdge { [Tooltip("Sets the start index of the edge. The Block uses this index and the next index to render the line.")] public uint index = 0u; [Range(0, 1), Tooltip("Controls the percentage along the edge, from the start position to the end position, that the sample position is taken.")] public float edge; } public class InputPropertiesPlacementSurfaceBarycentricCoordinates { [Tooltip("The triangle index to read from.")] public uint triangle = 0u; [Tooltip("Barycentric coordinate (z is computed from x & y).")] public Vector2 barycentric; } public class InputPropertiesPlacementSurfaceLowDistorsionMapping { [Tooltip("The triangle index to read from.")] public uint triangle = 0u; [Tooltip("The uniform coordinate to sample the triangle at. The Block takes this value and maps it from a square coordinate to triangle space.")] public Vector2 square; } public class TransformProperties { [Tooltip("Sets the transform of the sampled Mesh. If used with a Skinned Mesh, it is applied after the transform of the root bone.")] public Transform transform = Transform.defaultValue; } [Flags] public enum VertexAttributeFlag { None = 0, Position = 1 << 0, Normal = 1 << 1, Tangent = 1 << 2, Bitangent = 1 << 3, BitangentSign = 1 << 4, Color = 1 << 5, TexCoord0 = 1 << 6, TexCoord1 = 1 << 7, TexCoord2 = 1 << 8, TexCoord3 = 1 << 9, TexCoord4 = 1 << 10, TexCoord5 = 1 << 11, TexCoord6 = 1 << 12, TexCoord7 = 1 << 13, BlendWeight = 1 << 14, BlendIndices = 1 << 15, Transform = 1 << 16, PreviousPosition = 1 << 17, PreviousNormal = 1 << 18, PreviousTangent = 1 << 19, PreviousBitangent = 1 << 20, Velocity = 1 << 21, PreviousTransform = 1 << 22 } public enum SourceType { Mesh, SkinnedMeshRenderer } [VFXSetting(VFXSettingAttribute.VisibleFlags.InInspector), SerializeField, Tooltip("Outputs the result of the Mesh sampling operation.")] private VertexAttributeFlag output = VertexAttributeFlag.Position | VertexAttributeFlag.Color | VertexAttributeFlag.TexCoord0; [VFXSetting, SerializeField, Tooltip("Specifies how Unity handles the sample when the custom vertex index is out the out of bounds of the vertex array.")] private VFXOperatorUtility.SequentialAddressingMode mode = VFXOperatorUtility.SequentialAddressingMode.Clamp; [VFXSetting, SerializeField, Tooltip("Specifies which primitive part of the mesh to sample from.")] private PlacementMode placementMode = PlacementMode.Vertex; [VFXSetting, SerializeField, Tooltip("Specifies how to sample the surface of a triangle.")] private SurfaceCoordinates surfaceCoordinates = SurfaceCoordinates.Uniform; [VFXSetting(VFXSettingAttribute.VisibleFlags.InInspector), SerializeField, Tooltip("Specifies the kind of geometry to sample from.")] private SourceType source = SourceType.Mesh; public enum SkinnedRootTransform { None, ApplyLocalRootTransform, ApplyWorldRootTransform } [VFXSetting, SerializeField, Tooltip("Specifies the transform to apply to the root bone retrieved from the Skinned Mesh Renderer.")] public SkinnedRootTransform skinnedTransform = SkinnedRootTransform.ApplyLocalRootTransform; public override void Sanitize(int version) { if (version < 10) { SanitizeHelper.MigrateSampleMeshFrom9To10(this); } else { base.Sanitize(version); } } private static IEnumerable GetAttributesFromFlags(VertexAttributeFlag flags) { var vertexAttributes = Enum.GetValues(typeof(VertexAttributeFlag)).Cast(); foreach (var vertexAttribute in vertexAttributes) if (vertexAttribute != VertexAttributeFlag.None && flags.HasFlag(vertexAttribute)) yield return vertexAttribute; } private IEnumerable GetOutputVertexAttributes() { return GetAttributesFromFlags(output); } public static readonly string kMixingSMRWorldAndLocalPostTransformMsg = @"Mixing World Root Bone transform with an input transform in Local space can yield unexpected results. To avoid this, change the input Transform space from Local to World or None."; internal override void GenerateErrors(VFXErrorReporter report) { base.GenerateErrors(report); var transformSlot = inputSlots.Last(); if (actualSkinnedTransform == SkinnedRootTransform.ApplyWorldRootTransform && transformSlot.space == VFXSpace.Local) { report.RegisterError("MixingSMRWorldAndLocalPostTransformOperator", VFXErrorType.Warning, kMixingSMRWorldAndLocalPostTransformMsg, this); } var previousFlag = VertexAttributeFlag.PreviousNormal | VertexAttributeFlag.PreviousTangent | VertexAttributeFlag.PreviousBitangent | VertexAttributeFlag.PreviousPosition | VertexAttributeFlag.Velocity | VertexAttributeFlag.PreviousTransform; if (source == SourceType.Mesh && (output & previousFlag) != 0) { report.RegisterError("PreviousOutputUsageOnMesh", VFXErrorType.Warning, "Sampling previous data is only available with SkinnedMeshRenderer sources.\nWhen using a Mesh source, previous outputs return the same values as current ones.", this); } } protected internal override void Invalidate(VFXModel model, InvalidationCause cause) { base.Invalidate(model, cause); //Called from VFXSlot.InvalidateExpressionTree, can be triggered from a space change, need to refresh block warning if (cause == InvalidationCause.kExpressionInvalidated) { model.RefreshErrors(); } } private VFXSpace GetWantedOutputSpace() { //If we are applying the skinned mesh in world, using a local transform afterwards looks unexpected, forcing conversion of inputs to world. if (actualSkinnedTransform == SkinnedRootTransform.ApplyWorldRootTransform) return VFXSpace.World; //Otherwise, the input slot transform control the output space. var transformSlot = inputSlots.Last(); if (!transformSlot.spaceable) throw new InvalidOperationException("Unexpected mesh slot: " + transformSlot); return transformSlot.space; } public override VFXSpace GetOutputSpaceFromSlot(VFXSlot outputSlot) { if (outputSlot.spaceable) { return GetWantedOutputSpace(); } return VFXSpace.None; } private static string GetTooltip(VertexAttributeFlag attribute) { switch (attribute) { case VertexAttributeFlag.Position: return "Returns the position of the sampled vertex, edge, or surface coordinate."; case VertexAttributeFlag.Normal: return "Returns the normalized Normal vector of the sampled vertex, edge, or surface coordinate."; case VertexAttributeFlag.Tangent: return "Returns the non-normalized Tangent vector of the sampled vertex, edge, or surface coordinate."; case VertexAttributeFlag.Bitangent: return "Returns the Bitangent vector, which is a cross product of the Normal and Tangent vector multiplied by the Tangent sign."; case VertexAttributeFlag.Color: return "Returns the Vertex color. Sampling both Color and Color32 structures are supported."; case VertexAttributeFlag.TexCoord0: case VertexAttributeFlag.TexCoord1: case VertexAttributeFlag.TexCoord2: case VertexAttributeFlag.TexCoord3: case VertexAttributeFlag.TexCoord4: case VertexAttributeFlag.TexCoord5: case VertexAttributeFlag.TexCoord6: case VertexAttributeFlag.TexCoord7: return "Returns the vertex data stored in this TexCoord. TexCoord0 typically stores the UV data of the sampled mesh."; case VertexAttributeFlag.BlendWeight: return "Returns the bone blend weights for the mesh. This maps to the BLENDWEIGHTS semantic in the HLSL shading language."; case VertexAttributeFlag.BlendIndices: return "Returns the bone blend indices. This maps to the BLENDINDICES semantic in the HLSL shading language."; case VertexAttributeFlag.Transform: return "Returns the computed Transform after normalization and correction from the Normal, Tangent, Bitangent, and Position inputs."; case VertexAttributeFlag.PreviousPosition: return "Returns Previous Position considering previous root bone transform."; case VertexAttributeFlag.PreviousNormal: return "Returns Previous Normal considering previous root bone transform."; case VertexAttributeFlag.PreviousBitangent: return "Returns Previous Bitangent computed with cross(previousNormal, previousTangent.xyz) * previousTangent.w"; case VertexAttributeFlag.Velocity: return "Returns Velocity vector, returns the difference between current and previous position divided by game time."; case VertexAttributeFlag.PreviousTransform: return "Returns the computed Transform after normalization and correction from the Previous Normal, Tangent, Bitangent, and Previous Position inputs."; } return string.Empty; } private static Type GetOutputType(VertexAttributeFlag attribute) { switch (attribute) { case VertexAttributeFlag.PreviousPosition: case VertexAttributeFlag.Position: return typeof(Position); case VertexAttributeFlag.PreviousNormal: case VertexAttributeFlag.Normal: return typeof(DirectionType); case VertexAttributeFlag.PreviousTangent: case VertexAttributeFlag.PreviousBitangent: case VertexAttributeFlag.Tangent: case VertexAttributeFlag.Bitangent: case VertexAttributeFlag.Velocity: return typeof(Vector); case VertexAttributeFlag.Color: return typeof(Vector4); case VertexAttributeFlag.TexCoord0: case VertexAttributeFlag.TexCoord1: case VertexAttributeFlag.TexCoord2: case VertexAttributeFlag.TexCoord3: case VertexAttributeFlag.TexCoord4: case VertexAttributeFlag.TexCoord5: case VertexAttributeFlag.TexCoord6: case VertexAttributeFlag.TexCoord7: return typeof(Vector4); case VertexAttributeFlag.BlendWeight: return typeof(Vector4); case VertexAttributeFlag.BlendIndices: return typeof(Vector4); case VertexAttributeFlag.BitangentSign: return typeof(float); case VertexAttributeFlag.Transform: case VertexAttributeFlag.PreviousTransform: return typeof(Transform); default: throw new InvalidOperationException("Unexpected attribute : " + attribute); } } private static VFXValueType GetSampledType(VertexAttribute attribute) { switch (attribute) { case VertexAttribute.Position: return VFXValueType.Float3; case VertexAttribute.Normal: return VFXValueType.Float3; case VertexAttribute.Tangent: return VFXValueType.Float4; case VertexAttribute.Color: return VFXValueType.Float4; case VertexAttribute.TexCoord0: case VertexAttribute.TexCoord1: case VertexAttribute.TexCoord2: case VertexAttribute.TexCoord3: case VertexAttribute.TexCoord4: case VertexAttribute.TexCoord5: case VertexAttribute.TexCoord6: case VertexAttribute.TexCoord7: return VFXValueType.Float4; case VertexAttribute.BlendWeight: case VertexAttribute.BlendIndices: return VFXValueType.Float4; default: throw new InvalidOperationException("Unexpected attribute: " + attribute); } } private static VertexAttribute GetActualVertexAttribute(VertexAttributeFlag attribute) { switch (attribute) { case VertexAttributeFlag.Position: return VertexAttribute.Position; case VertexAttributeFlag.Normal: return VertexAttribute.Normal; case VertexAttributeFlag.BitangentSign: case VertexAttributeFlag.Tangent: return VertexAttribute.Tangent; case VertexAttributeFlag.Color: return VertexAttribute.Color; case VertexAttributeFlag.TexCoord0: return VertexAttribute.TexCoord0; case VertexAttributeFlag.TexCoord1: return VertexAttribute.TexCoord1; case VertexAttributeFlag.TexCoord2: return VertexAttribute.TexCoord2; case VertexAttributeFlag.TexCoord3: return VertexAttribute.TexCoord3; case VertexAttributeFlag.TexCoord4: return VertexAttribute.TexCoord4; case VertexAttributeFlag.TexCoord5: return VertexAttribute.TexCoord5; case VertexAttributeFlag.TexCoord6: return VertexAttribute.TexCoord6; case VertexAttributeFlag.TexCoord7: return VertexAttribute.TexCoord7; case VertexAttributeFlag.BlendWeight: return VertexAttribute.BlendWeight; case VertexAttributeFlag.BlendIndices: return VertexAttribute.BlendIndices; default: throw new InvalidOperationException("Unexpected attribute : " + attribute); } } protected override IEnumerable filteredOutSettings { get { foreach (var settings in base.filteredOutSettings) yield return settings; if (placementMode != PlacementMode.Surface) yield return nameof(surfaceCoordinates); if (source != SourceType.SkinnedMeshRenderer) yield return nameof(skinnedTransform); } } protected sealed override IEnumerable inputProperties { get { var props = base.inputProperties; if (source == SourceType.Mesh) props = props.Concat(PropertiesFromType(nameof(InputPropertiesMesh))); else if (source == SourceType.SkinnedMeshRenderer) props = props.Concat(PropertiesFromType(nameof(InputPropertiesSkinnedMeshRenderer))); else throw new InvalidOperationException("Unexpected source type : " + source); if (placementMode == PlacementMode.Vertex) props = props.Concat(PropertiesFromType(nameof(InputPropertiesPlacementVertex))); else if (placementMode == PlacementMode.Surface) { if (surfaceCoordinates == SurfaceCoordinates.Barycentric) props = props.Concat(PropertiesFromType(nameof(InputPropertiesPlacementSurfaceBarycentricCoordinates))); else if (surfaceCoordinates == SurfaceCoordinates.Uniform) props = props.Concat(PropertiesFromType(nameof(InputPropertiesPlacementSurfaceLowDistorsionMapping))); else throw new InvalidOperationException("Unexpected surfaceCoordinates : " + surfaceCoordinates); } else if (placementMode == PlacementMode.Edge) props = props.Concat(PropertiesFromType(nameof(InputPropertiesEdge))); else throw new InvalidOperationException("Unexpected placementMode : " + placementMode); props = props.Concat(PropertiesFromType(nameof(TransformProperties))); return props; } } private SkinnedRootTransform actualSkinnedTransform { get { if (source == SourceType.SkinnedMeshRenderer) return skinnedTransform; return SkinnedRootTransform.None; } } protected override IEnumerable outputProperties { get { foreach (var vertexAttribute in GetOutputVertexAttributes()) { var outputType = GetOutputType(vertexAttribute); var tooltip = GetTooltip(vertexAttribute); yield return new VFXPropertyWithValue( new VFXProperty(outputType, ObjectNames.NicifyVariableName(vertexAttribute.ToString()), new TooltipAttribute(tooltip))); } } } private static VFXExpression SampleVertexAttribute(VFXExpression source, VFXExpression vertexIndex, VertexAttribute vertexAttribute, VFXSkinnedMeshFrame frame = VFXSkinnedMeshFrame.Current) { bool skinnedMesh = source.valueType == VFXValueType.SkinnedMeshRenderer; var mesh = !skinnedMesh ? source : new VFXExpressionMeshFromSkinnedMeshRenderer(source); var channelIndex = VFXValue.Constant((uint)vertexAttribute); var meshVertexStride = new VFXExpressionMeshVertexStride(mesh, channelIndex); var meshChannelOffset = new VFXExpressionMeshChannelOffset(mesh, channelIndex); var outputType = GetSampledType(vertexAttribute); VFXExpression sampled = null; var meshChannelFormatAndDimension = new VFXExpressionMeshChannelInfos(mesh, channelIndex); var vertexOffset = vertexIndex * meshVertexStride + meshChannelOffset; if (!skinnedMesh) { if (vertexAttribute == VertexAttribute.Color) sampled = new VFXExpressionSampleMeshColor(source, vertexOffset, meshChannelFormatAndDimension); else if (outputType == VFXValueType.Float) sampled = new VFXExpressionSampleMeshFloat(source, vertexOffset, meshChannelFormatAndDimension); else if (outputType == VFXValueType.Float2) sampled = new VFXExpressionSampleMeshFloat2(source, vertexOffset, meshChannelFormatAndDimension); else if (outputType == VFXValueType.Float3) sampled = new VFXExpressionSampleMeshFloat3(source, vertexOffset, meshChannelFormatAndDimension); else if (outputType == VFXValueType.Float4) sampled = new VFXExpressionSampleMeshFloat4(source, vertexOffset, meshChannelFormatAndDimension); } else { if (frame == VFXSkinnedMeshFrame.Previous && outputType != VFXValueType.Float3 && outputType != VFXValueType.Float4) throw new InvalidOperationException("Unexpected type to sample previous frame : " + outputType); if (vertexAttribute == VertexAttribute.Color) sampled = new VFXExpressionSampleSkinnedMeshRendererColor(source, vertexOffset, meshChannelFormatAndDimension); else if (outputType == VFXValueType.Float) sampled = new VFXExpressionSampleSkinnedMeshRendererFloat(source, vertexOffset, meshChannelFormatAndDimension); else if (outputType == VFXValueType.Float2) sampled = new VFXExpressionSampleSkinnedMeshRendererFloat2(source, vertexOffset, meshChannelFormatAndDimension); else if (outputType == VFXValueType.Float3) sampled = new VFXExpressionSampleSkinnedMeshRendererFloat3(source, vertexOffset, meshChannelFormatAndDimension, frame); else if (outputType == VFXValueType.Float4) sampled = new VFXExpressionSampleSkinnedMeshRendererFloat4(source, vertexOffset, meshChannelFormatAndDimension, frame); } if (sampled == null) throw new InvalidOperationException("Unexpected Mesh Sampling type."); return sampled; } private static SpaceableType GetSpaceableFromVertexAttribute(VertexAttributeFlag currentAttribute) { if ((currentAttribute & (currentAttribute - 1)) != 0) throw new InvalidOperationException("Unexpected not single bit current attribute: " + currentAttribute); switch (currentAttribute) { case VertexAttributeFlag.Position: case VertexAttributeFlag.PreviousPosition: return SpaceableType.Position; case VertexAttributeFlag.Normal: case VertexAttributeFlag.Tangent: case VertexAttributeFlag.Bitangent: case VertexAttributeFlag.PreviousNormal: case VertexAttributeFlag.PreviousTangent: case VertexAttributeFlag.PreviousBitangent: return SpaceableType.Vector; case VertexAttributeFlag.PreviousTransform: case VertexAttributeFlag.Transform: return SpaceableType.Matrix; } return SpaceableType.None; } private static bool ShouldUsePreviousMatrix(VertexAttributeFlag flag) { switch (flag) { case VertexAttributeFlag.PreviousNormal: case VertexAttributeFlag.PreviousTangent: case VertexAttributeFlag.PreviousBitangent: case VertexAttributeFlag.PreviousPosition: case VertexAttributeFlag.PreviousTransform: return true; } return false; } public struct VFXMeshTransform { public VFXExpression current; public VFXExpression previous; } private static VFXExpression ComputeVertexAttribute(IEnumerable sampledVertexAttribute, VertexAttributeFlag currentAttribute, VFXMeshTransform postTransform) { if (postTransform.current == null || postTransform.previous == null) throw new InvalidOperationException("Unexpected null transform"); if ((currentAttribute & (currentAttribute - 1)) != 0) throw new InvalidOperationException("Unexpected not single bit current attribute: " + currentAttribute); //Compute expected attribute VFXExpression sampled; if (currentAttribute == VertexAttributeFlag.Tangent || currentAttribute == VertexAttributeFlag.PreviousTangent) { sampled = sampledVertexAttribute.First().xyz; } else if (currentAttribute == VertexAttributeFlag.BitangentSign) { sampled = sampledVertexAttribute.First().w; } else if (currentAttribute == VertexAttributeFlag.Bitangent || currentAttribute == VertexAttributeFlag.PreviousBitangent) { var sampledNormal = sampledVertexAttribute.First(); var sampledTangent = sampledVertexAttribute.Last(); if (sampledNormal == sampledTangent) throw new InvalidOperationException("Unexpected tangent/normal equality"); sampled = VFXOperatorUtility.Cross(sampledNormal, sampledTangent.xyz) * sampledTangent.www; } else if (currentAttribute == VertexAttributeFlag.Normal || currentAttribute == VertexAttributeFlag.PreviousNormal) { //N.B.: Only normal is actually normalized var sampledNormal = sampledVertexAttribute.First(); sampled = VFXOperatorUtility.Normalize(sampledNormal); } else if (currentAttribute == VertexAttributeFlag.Transform || currentAttribute == VertexAttributeFlag.PreviousTransform) { var position = sampledVertexAttribute.ElementAt(0); var normal = sampledVertexAttribute.ElementAt(1); var tangent = sampledVertexAttribute.ElementAt(2); //insure normal/tangent aren't zero var sqrLengthNormal = VFXOperatorUtility.Dot(normal, normal); var sqrLengthTangent = VFXOperatorUtility.Dot(tangent, tangent); var srqEpsilon = VFXOperatorUtility.EpsilonSqrExpression[VFXValueType.Float]; var nullNormal = new VFXExpressionCondition(VFXValueType.Float, VFXCondition.Less, sqrLengthNormal, srqEpsilon); var nullTangent = new VFXExpressionCondition(VFXValueType.Float, VFXCondition.Less, sqrLengthTangent, srqEpsilon); normal = new VFXExpressionBranch(nullNormal, VFXValue.Constant(Vector3.up), normal); tangent = new VFXExpressionBranch(nullTangent, VFXValue.Constant(Vector3.forward), tangent.xyz); //compute initial basis normal = VFXOperatorUtility.Normalize(normal); var bitangent = VFXOperatorUtility.Cross(normal, tangent); bitangent = VFXOperatorUtility.Normalize(bitangent); //insure tangent orthonormal with normal (cross of normalized input, not need to renormalize) tangent = VFXOperatorUtility.Cross(bitangent, normal); sampled = new VFXExpressionAxisToMatrix(bitangent, normal, tangent, position); } else if (currentAttribute == VertexAttributeFlag.Velocity) { var currentPosition = sampledVertexAttribute.ElementAt(0); var previousPosition = sampledVertexAttribute.ElementAt(1); currentPosition = TransformExpression(currentPosition, SpaceableType.Position, postTransform.current); previousPosition = TransformExpression(previousPosition, SpaceableType.Position, postTransform.previous); //Warning: This is not VFX deltaTime here, the skin mesh renderer isn't using the vfx time step. var deltaTime = VFXBuiltInExpression.GameDeltaTime; deltaTime = new VFXExpressionMax(deltaTime, VFXOperatorUtility.EpsilonExpression[VFXValueType.Float]); deltaTime = VFXOperatorUtility.CastFloat(deltaTime, VFXValueType.Float3); sampled = (currentPosition - previousPosition) / deltaTime; //Cancel following transform which has already been done postTransform.current = postTransform.previous = null; } else { //Default: 1:1 between flag & actual vertex attribute sampled = sampledVertexAttribute.First(); } if (postTransform.current != null && postTransform.previous != null) { var previous = ShouldUsePreviousMatrix(currentAttribute); var spaceableType = GetSpaceableFromVertexAttribute(currentAttribute); sampled = TransformExpression(sampled, spaceableType, previous ? postTransform.previous : postTransform.current); } return sampled; } private static IEnumerable SampleNeededVertexAttribute(VFXExpression source, VFXExpression vertexIndex, VertexAttributeFlag currentAttribute) { if ((currentAttribute & (currentAttribute - 1)) != 0) throw new InvalidOperationException("Unexpected not single bit current attribute: " + currentAttribute); if (currentAttribute == VertexAttributeFlag.Tangent || currentAttribute == VertexAttributeFlag.BitangentSign) { yield return SampleVertexAttribute(source, vertexIndex, VertexAttribute.Tangent); } else if (currentAttribute == VertexAttributeFlag.Bitangent) { yield return SampleVertexAttribute(source, vertexIndex, VertexAttribute.Normal); yield return SampleVertexAttribute(source, vertexIndex, VertexAttribute.Tangent); } else if (currentAttribute == VertexAttributeFlag.PreviousBitangent) { yield return SampleVertexAttribute(source, vertexIndex, VertexAttribute.Normal, VFXSkinnedMeshFrame.Previous); yield return SampleVertexAttribute(source, vertexIndex, VertexAttribute.Tangent, VFXSkinnedMeshFrame.Previous); } else if (currentAttribute == VertexAttributeFlag.Transform) { yield return SampleVertexAttribute(source, vertexIndex, VertexAttribute.Position); yield return SampleVertexAttribute(source, vertexIndex, VertexAttribute.Normal); yield return SampleVertexAttribute(source, vertexIndex, VertexAttribute.Tangent); } else if (currentAttribute == VertexAttributeFlag.PreviousTransform) { yield return SampleVertexAttribute(source, vertexIndex, VertexAttribute.Position, VFXSkinnedMeshFrame.Previous); yield return SampleVertexAttribute(source, vertexIndex, VertexAttribute.Normal, VFXSkinnedMeshFrame.Previous); yield return SampleVertexAttribute(source, vertexIndex, VertexAttribute.Tangent, VFXSkinnedMeshFrame.Previous); } else if (currentAttribute == VertexAttributeFlag.PreviousNormal) { yield return SampleVertexAttribute(source, vertexIndex, VertexAttribute.Normal, VFXSkinnedMeshFrame.Previous); } else if (currentAttribute == VertexAttributeFlag.PreviousTangent) { yield return SampleVertexAttribute(source, vertexIndex, VertexAttribute.Tangent, VFXSkinnedMeshFrame.Previous); } else if (currentAttribute == VertexAttributeFlag.PreviousPosition) { yield return SampleVertexAttribute(source, vertexIndex, VertexAttribute.Position, VFXSkinnedMeshFrame.Previous); } else if (currentAttribute == VertexAttributeFlag.Velocity) { yield return SampleVertexAttribute(source, vertexIndex, VertexAttribute.Position, VFXSkinnedMeshFrame.Current); yield return SampleVertexAttribute(source, vertexIndex, VertexAttribute.Position, VFXSkinnedMeshFrame.Previous); } else { //Default: 1:1 between flag & actual vertex attribute var vertexAttribute = GetActualVertexAttribute(currentAttribute); yield return SampleVertexAttribute(source, vertexIndex, vertexAttribute); } } public static IEnumerable SampleVertexAttribute(VFXExpression source, VFXExpression vertexIndex, IEnumerable vertexAttributes, VFXMeshTransform postTransform) { foreach (var currentAttribute in vertexAttributes) { var neededAttribute = SampleNeededVertexAttribute(source, vertexIndex, currentAttribute); var computedAttribute = ComputeVertexAttribute(neededAttribute, currentAttribute, postTransform); yield return computedAttribute; } } private static IEnumerable SampleVertexAttribute(VFXExpression source, VFXExpression vertexIndex, VFXOperatorUtility.SequentialAddressingMode mode, IEnumerable vertexAttributes, VFXMeshTransform postTransform) { bool skinnedMesh = source.valueType == VFXValueType.SkinnedMeshRenderer; var mesh = !skinnedMesh ? source : new VFXExpressionMeshFromSkinnedMeshRenderer(source); var meshVertexCount = new VFXExpressionMeshVertexCount(mesh); vertexIndex = VFXOperatorUtility.ApplyAddressingMode(vertexIndex, meshVertexCount, mode); return SampleVertexAttribute(source, vertexIndex, vertexAttributes, postTransform); } public static IEnumerable SampleEdgeAttribute(VFXExpression source, VFXExpression index, VFXExpression lerp, IEnumerable vertexAttributes, VFXMeshTransform postTransform) { bool skinnedMesh = source.valueType == VFXValueType.SkinnedMeshRenderer; var mesh = !skinnedMesh ? source : new VFXExpressionMeshFromSkinnedMeshRenderer(source); var meshIndexFormat = new VFXExpressionMeshIndexFormat(mesh); var oneUint = VFXOperatorUtility.OneExpression[VFXValueType.Uint32]; var threeUint = VFXOperatorUtility.ThreeExpression[VFXValueType.Uint32]; var nextIndex = index + oneUint; //Loop triangle var loop = VFXOperatorUtility.Modulo(nextIndex, threeUint); var predicat = new VFXExpressionCondition(VFXValueType.Uint32, VFXCondition.NotEqual, loop, VFXOperatorUtility.ZeroExpression[VFXValueType.Uint32]); nextIndex = new VFXExpressionBranch(predicat, nextIndex, nextIndex - threeUint); var sampledIndex_A = new VFXExpressionSampleIndex(mesh, index, meshIndexFormat); var sampledIndex_B = new VFXExpressionSampleIndex(mesh, nextIndex, meshIndexFormat); foreach (var attribute in vertexAttributes) { var neededAttribute_A = SampleNeededVertexAttribute(source, sampledIndex_A, attribute); var neededAttribute_B = SampleNeededVertexAttribute(source, sampledIndex_B, attribute); var interpolatedAttribute = Enumerable.Zip(neededAttribute_A, neededAttribute_B, (a, b) => { var outputValueType = a.valueType; var s = VFXOperatorUtility.CastFloat(lerp, outputValueType); var r = VFXOperatorUtility.Lerp(a, b, s); return r; }); yield return ComputeVertexAttribute(interpolatedAttribute, attribute, postTransform); } } private static IEnumerable SampleEdgeAttribute(VFXExpression source, VFXExpression index, VFXExpression x, VFXOperatorUtility.SequentialAddressingMode mode, IEnumerable vertexAttributes, VFXMeshTransform postTransform) { bool skinnedMesh = source.valueType == VFXValueType.SkinnedMeshRenderer; var mesh = !skinnedMesh ? source : new VFXExpressionMeshFromSkinnedMeshRenderer(source); var meshIndexCount = new VFXExpressionMeshIndexCount(mesh); index = VFXOperatorUtility.ApplyAddressingMode(index, meshIndexCount, mode); return SampleEdgeAttribute(source, index, x, vertexAttributes, postTransform); } static IEnumerable Zip3(IEnumerable first, IEnumerable second, IEnumerable third, Func func) { using (var itFirst = first.GetEnumerator()) using (var itSecond = second.GetEnumerator()) using (var itThird = third.GetEnumerator()) { while (itFirst.MoveNext() && itSecond.MoveNext() && itThird.MoveNext()) yield return func(itFirst.Current, itSecond.Current, itThird.Current); } } public static IEnumerable SampleTriangleAttribute(VFXExpression source, VFXExpression triangleIndex, VFXExpression coord, SurfaceCoordinates coordMode, IEnumerable vertexAttributes, VFXMeshTransform postTranform) { bool skinnedMesh = source.valueType == VFXValueType.SkinnedMeshRenderer; var mesh = !skinnedMesh ? source : new VFXExpressionMeshFromSkinnedMeshRenderer(source); var meshIndexFormat = new VFXExpressionMeshIndexFormat(mesh); var threeUint = VFXOperatorUtility.ThreeExpression[VFXValueType.Uint32]; var baseIndex = triangleIndex * threeUint; var sampledIndex_A = new VFXExpressionSampleIndex(mesh, baseIndex, meshIndexFormat); var sampledIndex_B = new VFXExpressionSampleIndex(mesh, baseIndex + VFXValue.Constant(1u), meshIndexFormat); var sampledIndex_C = new VFXExpressionSampleIndex(mesh, baseIndex + VFXValue.Constant(2u), meshIndexFormat); var allInputValues = new List(); VFXExpression barycentricCoordinates = null; var one = VFXOperatorUtility.OneExpression[VFXValueType.Float]; if (coordMode == SurfaceCoordinates.Barycentric) { var barycentricCoordinateInput = coord; barycentricCoordinates = new VFXExpressionCombine(barycentricCoordinateInput.x, barycentricCoordinateInput.y, one - barycentricCoordinateInput.x - barycentricCoordinateInput.y); } else if (coordMode == SurfaceCoordinates.Uniform) { //https://hal.archives-ouvertes.fr/hal-02073696v2/document var input = coord; var half2 = VFXOperatorUtility.HalfExpression[VFXValueType.Float2]; var zero = VFXOperatorUtility.ZeroExpression[VFXValueType.Float]; var t = input * half2; var offset = t.y - t.x; var pred = new VFXExpressionCondition(VFXValueType.Float, VFXCondition.Greater, offset, zero); var t2 = new VFXExpressionBranch(pred, t.y + offset, t.y); var t1 = new VFXExpressionBranch(pred, t.x, t.x - offset); var t3 = one - t2 - t1; barycentricCoordinates = new VFXExpressionCombine(t1, t2, t3); /* Possible variant See http://inis.jinr.ru/sl/vol1/CMC/Graphics_Gems_1,ed_A.Glassner.pdf (p24) uniform distribution from two numbers in triangle generating barycentric coordinate var input = VFXOperatorUtility.Saturate(inputExpression[2]); var s = input.x; var t = VFXOperatorUtility.Sqrt(input.y); var a = one - t; var b = (one - s) * t; var c = s * t; barycentricCoordinates = new VFXExpressionCombine(a, b, c); */ } else { throw new InvalidOperationException("No supported surfaceCoordinates : " + coord); } foreach (var attribute in vertexAttributes) { var neededAttribute_A = SampleNeededVertexAttribute(source, sampledIndex_A, attribute); var neededAttribute_B = SampleNeededVertexAttribute(source, sampledIndex_B, attribute); var neededAttribute_C = SampleNeededVertexAttribute(source, sampledIndex_C, attribute); var interpolatedAttribute = Zip3(neededAttribute_A, neededAttribute_B, neededAttribute_C, (a, b, c) => { var outputValueType = a.valueType; var barycentricCoordinateX = VFXOperatorUtility.CastFloat(barycentricCoordinates.x, outputValueType); var barycentricCoordinateY = VFXOperatorUtility.CastFloat(barycentricCoordinates.y, outputValueType); var barycentricCoordinateZ = VFXOperatorUtility.CastFloat(barycentricCoordinates.z, outputValueType); var r = a * barycentricCoordinateX + b * barycentricCoordinateY + c * barycentricCoordinateZ; return r; }); yield return ComputeVertexAttribute(interpolatedAttribute, attribute, postTranform); } } private static IEnumerable SampleTriangleAttribute(VFXExpression source, VFXExpression triangleIndex, VFXExpression coord, VFXOperatorUtility.SequentialAddressingMode mode, SurfaceCoordinates coordMode, IEnumerable vertexAttributes, VFXMeshTransform postTranform) { bool skinnedMesh = source.valueType == VFXValueType.SkinnedMeshRenderer; var mesh = !skinnedMesh ? source : new VFXExpressionMeshFromSkinnedMeshRenderer(source); var UintThree = VFXOperatorUtility.ThreeExpression[VFXValueType.Uint32]; var meshIndexCount = new VFXExpressionMeshIndexCount(mesh); var triangleCount = meshIndexCount / UintThree; triangleIndex = VFXOperatorUtility.ApplyAddressingMode(triangleIndex, triangleCount, mode); return SampleTriangleAttribute(source, triangleIndex, coord, coordMode, vertexAttributes, postTranform); } public static VFXMeshTransform ComputeTransformMatrix(VFXExpression source, SkinnedRootTransform smrTransform, VFXExpression postTransform) { var transfom = new VFXMeshTransform() { current = postTransform, previous = postTransform }; if (smrTransform != SkinnedRootTransform.None) { VFXExpression transformRootBoneCurrent; VFXExpression transformRootBonePrevious; if (source.valueType != VFXValueType.SkinnedMeshRenderer) throw new InvalidOperationException(); if (smrTransform == SkinnedRootTransform.ApplyLocalRootTransform) { transformRootBoneCurrent = new VFXExpressionRootBoneTransformFromSkinnedMeshRenderer(source, VFXSkinnedTransform.LocalRootBoneTransform, VFXSkinnedMeshFrame.Current); transformRootBonePrevious = new VFXExpressionRootBoneTransformFromSkinnedMeshRenderer(source, VFXSkinnedTransform.LocalRootBoneTransform, VFXSkinnedMeshFrame.Previous); } else if (smrTransform == SkinnedRootTransform.ApplyWorldRootTransform) { transformRootBoneCurrent = new VFXExpressionRootBoneTransformFromSkinnedMeshRenderer(source, VFXSkinnedTransform.WorldRootBoneTransform, VFXSkinnedMeshFrame.Current); transformRootBonePrevious = new VFXExpressionRootBoneTransformFromSkinnedMeshRenderer(source, VFXSkinnedTransform.WorldRootBoneTransform, VFXSkinnedMeshFrame.Previous); } else { throw new InvalidOperationException("Unexpected SMR Transform" + smrTransform); } transfom.current = new VFXExpressionTransformMatrix(postTransform, transformRootBoneCurrent); transfom.previous = new VFXExpressionTransformMatrix(postTransform, transformRootBonePrevious); } return transfom; } protected sealed override VFXExpression[] BuildExpression(VFXExpression[] inputExpression) { var source = inputExpression[0]; var matrix = ComputeTransformMatrix(source, actualSkinnedTransform, inputExpression.Last()); VFXExpression[] outputExpressions = null; if (placementMode == PlacementMode.Vertex) { var sampled = SampleVertexAttribute(inputExpression[0], inputExpression[1], mode, GetOutputVertexAttributes(), matrix); outputExpressions = sampled.ToArray(); } else if (placementMode == PlacementMode.Edge) { var sampled = SampleEdgeAttribute(inputExpression[0], inputExpression[1], inputExpression[2], mode, GetOutputVertexAttributes(), matrix); outputExpressions = sampled.ToArray(); } else if (placementMode == PlacementMode.Surface) { var sampled = SampleTriangleAttribute(inputExpression[0], inputExpression[1], inputExpression[2], mode, surfaceCoordinates, GetOutputVertexAttributes(), matrix); outputExpressions = sampled.ToArray(); } else { throw new InvalidOperationException("Not supported placement mode " + placementMode); } return outputExpressions; } } }