Files
LightlessClient/LightlessSync/ThirdParty/Nanomesh/Base/FfxivVertexAttribute.cs
2026-01-19 09:50:54 +09:00

348 lines
11 KiB
C#

using System;
using System.Runtime.InteropServices;
namespace Nanomesh
{
[Flags]
public enum FfxivAttributeFlags : uint
{
None = 0,
Normal = 1u << 0,
Tangent1 = 1u << 1,
Tangent2 = 1u << 2,
Color = 1u << 3,
BoneWeights = 1u << 4,
PositionW = 1u << 5,
NormalW = 1u << 6,
Uv0 = 1u << 7,
Uv1 = 1u << 8,
Uv2 = 1u << 9,
Uv3 = 1u << 10,
}
[StructLayout(LayoutKind.Sequential)]
public readonly struct FfxivVertexAttribute : IEquatable<FfxivVertexAttribute>, IInterpolable<FfxivVertexAttribute>
{
public readonly Vector3F normal;
public readonly Vector4F tangent1;
public readonly Vector4F tangent2;
public readonly Vector2F uv0;
public readonly Vector2F uv1;
public readonly Vector2F uv2;
public readonly Vector2F uv3;
public readonly Vector4F color;
public readonly BoneWeight boneWeight;
public readonly float positionW;
public readonly float normalW;
public readonly FfxivAttributeFlags flags;
public FfxivVertexAttribute(
FfxivAttributeFlags flags,
Vector3F normal,
Vector4F tangent1,
Vector4F tangent2,
Vector2F uv0,
Vector2F uv1,
Vector2F uv2,
Vector2F uv3,
Vector4F color,
BoneWeight boneWeight,
float positionW,
float normalW)
{
this.flags = flags;
this.normal = normal;
this.tangent1 = tangent1;
this.tangent2 = tangent2;
this.uv0 = uv0;
this.uv1 = uv1;
this.uv2 = uv2;
this.uv3 = uv3;
this.color = color;
this.boneWeight = boneWeight;
this.positionW = positionW;
this.normalW = normalW;
}
public FfxivVertexAttribute Interpolate(FfxivVertexAttribute other, double ratio)
{
var t = (float)ratio;
var inv = 1f - t;
var combinedFlags = flags | other.flags;
var normal = (combinedFlags & FfxivAttributeFlags.Normal) != 0
? NormalizeVector3(new Vector3F(
(this.normal.x * inv) + (other.normal.x * t),
(this.normal.y * inv) + (other.normal.y * t),
(this.normal.z * inv) + (other.normal.z * t)))
: default;
var tangent1 = (combinedFlags & FfxivAttributeFlags.Tangent1) != 0
? BlendTangent(this.tangent1, other.tangent1, t)
: default;
var tangent2 = (combinedFlags & FfxivAttributeFlags.Tangent2) != 0
? BlendTangent(this.tangent2, other.tangent2, t)
: default;
var uv0 = (combinedFlags & FfxivAttributeFlags.Uv0) != 0
? Vector2F.LerpUnclamped(this.uv0, other.uv0, t)
: default;
var uv1 = (combinedFlags & FfxivAttributeFlags.Uv1) != 0
? Vector2F.LerpUnclamped(this.uv1, other.uv1, t)
: default;
var uv2 = (combinedFlags & FfxivAttributeFlags.Uv2) != 0
? Vector2F.LerpUnclamped(this.uv2, other.uv2, t)
: default;
var uv3 = (combinedFlags & FfxivAttributeFlags.Uv3) != 0
? Vector2F.LerpUnclamped(this.uv3, other.uv3, t)
: default;
var color = (combinedFlags & FfxivAttributeFlags.Color) != 0
? new Vector4F(
(this.color.x * inv) + (other.color.x * t),
(this.color.y * inv) + (other.color.y * t),
(this.color.z * inv) + (other.color.z * t),
(this.color.w * inv) + (other.color.w * t))
: default;
var boneWeight = (combinedFlags & FfxivAttributeFlags.BoneWeights) != 0
? BlendBoneWeights(this.boneWeight, other.boneWeight, t)
: default;
var positionW = (combinedFlags & FfxivAttributeFlags.PositionW) != 0
? (this.positionW * inv) + (other.positionW * t)
: 0f;
var normalW = (combinedFlags & FfxivAttributeFlags.NormalW) != 0
? (this.normalW * inv) + (other.normalW * t)
: 0f;
return new FfxivVertexAttribute(
combinedFlags,
normal,
tangent1,
tangent2,
uv0,
uv1,
uv2,
uv3,
color,
boneWeight,
positionW,
normalW);
}
public bool Equals(FfxivVertexAttribute other)
{
if (flags != other.flags)
{
return false;
}
if ((flags & FfxivAttributeFlags.Normal) != 0 && !normal.Equals(other.normal))
{
return false;
}
if ((flags & FfxivAttributeFlags.Tangent1) != 0 && !tangent1.Equals(other.tangent1))
{
return false;
}
if ((flags & FfxivAttributeFlags.Tangent2) != 0 && !tangent2.Equals(other.tangent2))
{
return false;
}
if ((flags & FfxivAttributeFlags.Uv0) != 0 && !uv0.Equals(other.uv0))
{
return false;
}
if ((flags & FfxivAttributeFlags.Uv1) != 0 && !uv1.Equals(other.uv1))
{
return false;
}
if ((flags & FfxivAttributeFlags.Uv2) != 0 && !uv2.Equals(other.uv2))
{
return false;
}
if ((flags & FfxivAttributeFlags.Uv3) != 0 && !uv3.Equals(other.uv3))
{
return false;
}
if ((flags & FfxivAttributeFlags.Color) != 0 && !color.Equals(other.color))
{
return false;
}
if ((flags & FfxivAttributeFlags.BoneWeights) != 0 && !boneWeight.Equals(other.boneWeight))
{
return false;
}
if ((flags & FfxivAttributeFlags.PositionW) != 0 && positionW != other.positionW)
{
return false;
}
if ((flags & FfxivAttributeFlags.NormalW) != 0 && normalW != other.normalW)
{
return false;
}
return true;
}
public override bool Equals(object? obj)
=> obj is FfxivVertexAttribute other && Equals(other);
public override int GetHashCode()
{
var hash = new HashCode();
hash.Add(normal);
hash.Add(tangent1);
hash.Add(tangent2);
hash.Add(uv0);
hash.Add(uv1);
hash.Add(uv2);
hash.Add(uv3);
hash.Add(color);
hash.Add(boneWeight);
hash.Add(positionW);
hash.Add(normalW);
hash.Add(flags);
return hash.ToHashCode();
}
private static Vector3F NormalizeVector3(in Vector3F value)
{
var length = Vector3F.Magnitude(value);
return length > 0f ? value / length : value;
}
private static Vector4F BlendTangent(in Vector4F a, in Vector4F b, float t)
{
var inv = 1f - t;
var blended = new Vector3F(
(a.x * inv) + (b.x * t),
(a.y * inv) + (b.y * t),
(a.z * inv) + (b.z * t));
blended = NormalizeVector3(blended);
var w = t >= 0.5f ? b.w : a.w;
if (w != 0f)
{
w = w >= 0f ? 1f : -1f;
}
return new Vector4F(blended.x, blended.y, blended.z, w);
}
private static BoneWeight BlendBoneWeights(in BoneWeight a, in BoneWeight b, float ratio)
{
Span<int> indices = stackalloc int[8];
Span<float> weights = stackalloc float[8];
var count = 0;
static void AddWeight(Span<int> indices, Span<float> weights, ref int count, int index, float weight)
{
if (weight <= 0f)
{
return;
}
for (var i = 0; i < count; i++)
{
if (indices[i] == index)
{
weights[i] += weight;
return;
}
}
if (count < indices.Length)
{
indices[count] = index;
weights[count] = weight;
count++;
}
}
var inv = 1f - ratio;
var sumA = a.weight0 + a.weight1 + a.weight2 + a.weight3;
var sumB = b.weight0 + b.weight1 + b.weight2 + b.weight3;
var targetSum = (sumA * inv) + (sumB * ratio);
AddWeight(indices, weights, ref count, a.index0, a.weight0 * inv);
AddWeight(indices, weights, ref count, a.index1, a.weight1 * inv);
AddWeight(indices, weights, ref count, a.index2, a.weight2 * inv);
AddWeight(indices, weights, ref count, a.index3, a.weight3 * inv);
AddWeight(indices, weights, ref count, b.index0, b.weight0 * ratio);
AddWeight(indices, weights, ref count, b.index1, b.weight1 * ratio);
AddWeight(indices, weights, ref count, b.index2, b.weight2 * ratio);
AddWeight(indices, weights, ref count, b.index3, b.weight3 * ratio);
if (count == 0)
{
return a;
}
Span<int> topIndices = stackalloc int[4];
Span<float> topWeights = stackalloc float[4];
for (var i = 0; i < 4; i++)
{
topIndices[i] = -1;
topWeights[i] = 0f;
}
for (var i = 0; i < count; i++)
{
var weight = weights[i];
var index = indices[i];
for (var slot = 0; slot < 4; slot++)
{
if (weight > topWeights[slot])
{
for (var shift = 3; shift > slot; shift--)
{
topWeights[shift] = topWeights[shift - 1];
topIndices[shift] = topIndices[shift - 1];
}
topWeights[slot] = weight;
topIndices[slot] = index;
break;
}
}
}
var sum = topWeights[0] + topWeights[1] + topWeights[2] + topWeights[3];
if (sum > 0f)
{
var scale = targetSum > 0f ? targetSum / sum : 0f;
for (var i = 0; i < 4; i++)
{
topWeights[i] *= scale;
}
}
return new BoneWeight(
topIndices[0] < 0 ? 0 : topIndices[0],
topIndices[1] < 0 ? 0 : topIndices[1],
topIndices[2] < 0 ? 0 : topIndices[2],
topIndices[3] < 0 ? 0 : topIndices[3],
topWeights[0],
topWeights[1],
topWeights[2],
topWeights[3]);
}
}
}