#include "lib/shared/point.hlsl"
#include "lib/shared/quat-functions.hlsl"

cbuffer Params : register(b0)
{
    float3 SourceExtend;
    float Range;

    float3 SourcePivot;
    float Offset;
    float Scale;
    float Spin;
    float Twist;
}

cbuffer Params : register(b1)
{
    int SourceAlignmentAxis;
    int RepeatMode;
}

StructuredBuffer<Point> SourcePoints : t0;
StructuredBuffer<Point> Points : t1;
RWStructuredBuffer<Point> ResultPoints : u0;

static float Fraction;
static float3 PosA;
static float3 PosB;
static float4x4 OrientationA;
static float4x4 OrientationB;

static const float3 axisScaleFactors[] = {float3(0, 1, 1), float3(1, 0, 1), float3(1, 1, 0)};

inline float3 TransformVector(float3 v)
{
    float3 v2 = v * axisScaleFactors[SourceAlignmentAxis] * Scale + lerp(PosA, PosB, Fraction);
    v2 = lerp(mul(float4(v2 - PosA, 1), OrientationA).xyz + PosA,
              mul(float4(v2 - PosB, 1), OrientationB).xyz + PosB,
              Fraction);
    return v2;
}

inline float3 TransformDirection(float3 v)
{
    return lerp(mul(float4(v, 0), OrientationA).xyz,
                mul(float4(v, 0), OrientationA).xyz,
                Fraction);
}

inline float4 TransformOrientation(float4 r)
{
    return qSlerp(mul(r, OrientationA),
                  mul(r, OrientationB),
                  Fraction);
}

[numthreads(64, 1, 1)] void main(uint3 i : SV_DispatchThreadID)
{
    uint vertexIndex = i.x;

    uint vertexCount, stride;
    SourcePoints.GetDimensions(vertexCount, stride);
    if (vertexIndex > vertexCount)
    {
        return;
    }

    uint pointCount;
    Points.GetDimensions(pointCount, stride);

    float weight = 1;
    float3 offset;

    Point p = SourcePoints[vertexIndex];
    float3 pos = (p.Position + SourcePivot) / SourceExtend;
    float f = pos[SourceAlignmentAxis];

    f = f * Range * Scale + Offset + 0.5;
    if (RepeatMode == 1)
    {
        f = mod(f, 1);
    }
    else if (RepeatMode == 2)
    {
        f = saturate(f);
    }

    float floatIndex = f * pointCount + 0.00001;

    uint aIndex = (int)clamp(floatIndex, 0, pointCount - 2);
    uint bIndex = aIndex + 1;
    Fraction = floatIndex - aIndex;

    float4 rotA = Points[aIndex].Rotation;
    float4 rotB = Points[bIndex].Rotation;

    if (SourceAlignmentAxis == 0)
    {
        float4 rotY = qFromAngleAxis(3.1415 / 2, float3(0, 1, 0));
        rotA = qMul(rotA, rotY);
        rotB = qMul(rotB, rotY);
    }
    else if (SourceAlignmentAxis == 1)
    {
        float4 rotY = qFromAngleAxis(3.1415 / 2, float3(1, 0, 0));
        rotA = qMul(rotA, rotY);
        rotB = qMul(rotB, rotY);
    }

    OrientationA = transpose(qToMatrix(rotA));
    OrientationB = transpose(qToMatrix(rotB));
    PosA = Points[aIndex].Position;
    PosB = Points[bIndex].Position;

    p.Position = TransformVector(pos);
    float4 r = p.Rotation;
    p.Rotation = qSlerp(qMul(rotA, r), qMul(rotB, r), Fraction);
    ResultPoints[vertexIndex] = p;
}
