Files
2026-07-13 13:13:17 +08:00

199 lines
8.0 KiB
HLSL

#include "shared/hash-functions.hlsl"
#include "shared/noise-functions.hlsl"
#include "shared/point.hlsl"
#include "shared/quat-functions.hlsl"
cbuffer Params : register(b0)
{
float3 DirectionBias;
float Tolerance;
float Influence;
float MaxBendAngle; // in radians, used if AngleConstraint is enabled
}
cbuffer Params : register(b1)
{
int MaxIterations; // Usually 10-20 is enough
int Reset;
int PointsPerChain;
int AngleConstraint; // 0 = off, 1 = on
int TargetRotation;
}
// SourcePoints: the rest-pose / original chain (read-only, never changes)
StructuredBuffer<Point> SourcePoints : t0;
StructuredBuffer<Point> TargetPoints : t1;
// ResultPoints: the live stateful chain - persists across frames.
// - chain root is pinned to SourcePoints[chainStart] each solve
// - chain end is the target/effector (driven externally each frame)
// - middle joints are solved by FABRIK
RWStructuredBuffer<Point> ResultPoints : u0;
[numthreads(64, 1, 1)] void main(uint3 i : SV_DispatchThreadID)
{
uint totalPoints, stride, targetPointCount;
SourcePoints.GetDimensions(totalPoints, stride);
TargetPoints.GetDimensions(targetPointCount, stride);
uint pointsPerChain = (PointsPerChain > 0) ? (uint)PointsPerChain : totalPoints ;
uint numChains = totalPoints / pointsPerChain;
if (pointsPerChain < 2 || numChains == 0)
return;
// --- Reset: copy source into result so all chains start in rest pose ---
if (Reset == 1)
{
ResultPoints[i.x] = SourcePoints[i.x];
return;
}
// --- Solve each chain independently ---
for (uint c = 0; c < numChains; c++)
{
uint chainStart = c * pointsPerChain;
uint chainEnd = chainStart + pointsPerChain -1; // exclusive
// Read live positions into local array
float3 pos[256];
for (uint j = 0; j < pointsPerChain; j++)
pos[j] = ResultPoints[chainStart + j].Position;
// Compute rest-pose segment lengths from SourcePoints
float segLen[256];
for (uint k = 0; k < pointsPerChain - 1; k++)
segLen[k] = distance(SourcePoints[chainStart + k].Position,
SourcePoints[chainStart + k + 1].Position);
// Pin root to this chain's source rest position
float3 rootPos = SourcePoints[chainStart].Position;
// Target: use per-chain target if available, otherwise wrap around
uint targetIdx = (c < targetPointCount) ? c : (c % targetPointCount);
uint anchorIdx = (chainEnd < totalPoints) ? chainEnd : chainStart;
float3 targetPos = lerp(ResultPoints[anchorIdx].Position, TargetPoints[targetIdx].Position, Influence);
// Store original positions for blending
float3 originalPos[256];
for (uint o = 0; o < pointsPerChain; o++)
originalPos[o] = pos[o];
// --- FABRIK solve ---
float error = 1e10f;
//float3 gravity = float3(0, -0.1, 0); // Optional: add gravity effect by offsetting targetPos each iteration
float3 directionBias = DirectionBias *.01; // Optional: bias to encourage bending in a particular direction
for (int iter = 0; iter < MaxIterations && error > Tolerance; iter++)
{
// Forward pass: pull from target back toward root
pos[pointsPerChain - 1] = targetPos;
for (int f = (int)pointsPerChain - 2; f >= 0; f--)
{
float3 dir = normalize(pos[f] - pos[f + 1]);
pos[f] = pos[f + 1] + dir * segLen[f];
}
// Backward pass: push from root out toward target
pos[0] = rootPos;
for (uint b = 1; b < pointsPerChain; b++)
{
float3 dir = normalize(pos[b] - pos[b - 1]);
dir = normalize(dir + directionBias); // Optional: add a small bias to encourage bending in a particular direction
pos[b] = pos[b - 1] + dir * segLen[b - 1];
}
error = distance(pos[pointsPerChain - 1], targetPos);
}
if (AngleConstraint == 1){
float maxBendAngle = radians(MaxBendAngle); // Example max bend angle in radians
// After computing the new position in the backward pass,
// clamp the bend angle relative to the parent direction
for (uint b = 1; b < pointsPerChain; b++)
{
float3 dir = normalize(pos[b] - pos[b - 1]);
pos[b] = pos[b - 1] + dir * segLen[b - 1];
// Cone constraint: clamp angle against parent segment
if (b >= 2)
{
float3 parentDir = normalize(pos[b - 1] - pos[b - 2]);
float3 currentDir = normalize(pos[b] - pos[b - 1]);
float cosAngle = dot(parentDir, currentDir);
float maxCosAngle = cos(MaxBendAngle); // MaxBendAngle in radians
if (cosAngle < maxCosAngle)
{
// Project currentDir onto the cone surface around parentDir
float3 perp = currentDir - parentDir * dot(currentDir, parentDir);
float perpLen = length(perp);
if (perpLen > 0.0001f)
{
float3 constrained = normalize(parentDir * cos(MaxBendAngle)
+ normalize(perp) * sin(MaxBendAngle));
pos[b] = pos[b - 1] + constrained * segLen[b - 1];
}
}
}
}
}
// --- Write solved positions back with blending ---
for (uint l = 0; l < pointsPerChain; l++)
{
uint globalIdx = chainStart + l;
Point p = ResultPoints[globalIdx];
float3 solvedPos = (l == 0) ? rootPos : pos[l];
p.Position = solvedPos;
// Update rotation to face the next joint
if (l < pointsPerChain - 1)
{
float3 newDir = pos[l + 1] - pos[l];
float3 orgDir = SourcePoints[globalIdx + 1].Position
- SourcePoints[globalIdx].Position;
p.Scale.x = distance(pos[l], pos[l + 1]);
if (length(newDir) > 0.0001f && length(orgDir) > 0.0001f)
{
float4 alignRot = qFromVectors(normalize(orgDir), normalize(newDir));
p.Rotation = qMul(alignRot, SourcePoints[globalIdx].Rotation);
}
}
if (l == pointsPerChain - 1)
{
// For the end effector, optionally copy rotation from target
// or copy the last segment's rotation for a more natural look:
if (TargetRotation == 1)
{
p.Rotation = lerp(ResultPoints[globalIdx].Rotation, TargetPoints[targetIdx].Rotation, Influence);
}
else
{
// Blend between original rotation and last segment's rotation
p.Rotation = lerp(ResultPoints[globalIdx - 1].Rotation, SourcePoints[globalIdx].Rotation,1 - Influence);
}
//p.Rotation = lerp(ResultPoints[globalIdx - 1].Rotation,TargetPoints[targetIdx].Rotation,(float)TargetRotation- Influence);
p.Scale.x = distance(pos[l], pos[l - 1]); // Update scale for end segment as well
}
p.Scale.yz = SourcePoints[globalIdx].Scale.yz;
ResultPoints[globalIdx].Position = p.Position;
ResultPoints[globalIdx].Rotation = p.Rotation;
ResultPoints[globalIdx].Scale = p.Scale;
ResultPoints[globalIdx].Color = SourcePoints[globalIdx].Color;
ResultPoints[globalIdx].FX1 = SourcePoints[globalIdx].FX1;
ResultPoints[globalIdx].FX2 = p.FX2;
}
}
}