One zone node at the massive roots' centre of mass owns the only global term (SubzoneReach is the one place the subzone shape is decided: isolated-subzones). WellNode gains Extent and Anchor; a belt orbit's extent is its outermost asteroid. GravityField.SeriesFrom = 0.4 is emitted into the shader template and Gravity.cginc. Tests: the spec's WellLawTests; the old seam test is replaced by TheBandPotentialIsExactInSinglePrecision. GravityFieldParityVerify runs the multi-root fixture (second sun, moon inside the planet's Roche limit, far window): PASS, largest drawn tau 1.228, max error 0.01028 u. The dotnet suite has not been run at this commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
128 lines
5.2 KiB
C#
128 lines
5.2 KiB
C#
using System.Globalization;
|
|
using System.Linq;
|
|
|
|
/// <summary>
|
|
/// The single author of Assets/Shaders/Gravity.cginc, the GPU lowering of the zone's potential. The law is
|
|
/// GravityField's (its constants and smoothstep coefficients are read here, not respelled) and the height is
|
|
/// Zone.GetHeight: each term's potential minus its reference, summed over the terms whose edge the point is inside,
|
|
/// divided by the exaggeration. Unity does not run C# when it imports a shader, so the committed cginc is this output;
|
|
/// GravityShaderTests fails unless it equals Source() exactly.
|
|
/// To change the shader, change this file and regenerate: AETHERIA_REGENERATE_GRAVITY_CGINC=1 dotnet test --filter GravityShaderTests.
|
|
/// </summary>
|
|
public static class GravityShader
|
|
{
|
|
public const string Path = "Assets/Shaders/Gravity.cginc";
|
|
|
|
/// <summary>The whole file, with LF line ends.</summary>
|
|
public static string Source() => Template
|
|
.Replace("@ORDER@", GravityField.Order.ToString(CultureInfo.InvariantCulture))
|
|
.Replace("@N@", GravityField.S.Length.ToString(CultureInfo.InvariantCulture))
|
|
.Replace("@Q@", (GravityField.S.Length - 1).ToString(CultureInfo.InvariantCulture))
|
|
.Replace("@QLAST@", (GravityField.S.Length - 2).ToString(CultureInfo.InvariantCulture))
|
|
.Replace("@SERIESFROM@", GravityField.SeriesFrom.ToString("R", CultureInfo.InvariantCulture))
|
|
.Replace("@SERIES@", GravityField.SeriesTerms.ToString(CultureInfo.InvariantCulture))
|
|
.Replace("@ONEMINUSS@", Coefficients(GravityField.OneMinusS))
|
|
.Replace("@S@", Coefficients(GravityField.S))
|
|
.Replace("\r\n", "\n");
|
|
|
|
private static string Coefficients(float[] c) =>
|
|
string.Join(", ", c.Select(x => x.ToString("R", CultureInfo.InvariantCulture)));
|
|
|
|
private const string Template = @"#ifndef AETHERIA_GRAVITY_INCLUDED
|
|
#define AETHERIA_GRAVITY_INCLUDED
|
|
|
|
// GENERATED by ServerShared/GravityShader.cs from GravityField; do not edit (GravityShaderTests fails on any difference).
|
|
// The lowering of the zone's potential (aetheria-gravity, cut grid-lowering). The law is ServerShared's GravityField
|
|
// and the height is Zone.GetHeight; this file decides nothing. GravityLowering.Lower fills the buffer and the globals
|
|
// from the zone's WellTree (positions from the nodes' rails at the drawn time) each frame.
|
|
// GravityFieldParityVerify renders it and compares every texel with -Zone.GetHeight.
|
|
|
|
struct GravityTerm
|
|
{
|
|
float2 position;
|
|
float strength;
|
|
float softening;
|
|
float inner;
|
|
float outer;
|
|
float reference;
|
|
};
|
|
|
|
StructuredBuffer<GravityTerm> _GravityWells;
|
|
int _GravityWellCount;
|
|
float _HeightExaggeration;
|
|
|
|
// GravityField.OneMinusS and GravityField.S: the band profile B(t) = 1 - S(t) and the order-@ORDER@ smoothstep, lowest first.
|
|
static const float GravityOneMinusS[@N@] = { @ONEMINUSS@ };
|
|
static const float GravityS[@N@] = { @S@ };
|
|
|
|
// GravityField.BandClosed: the band's potential at t for tau <= @SERIESFROM@, per unit strength.
|
|
float gravityBandClosed(float tau, float t)
|
|
{
|
|
float q[@Q@];
|
|
q[@QLAST@] = GravityOneMinusS[@Q@];
|
|
[unroll] for (int i = @QLAST@; i > 0; i--) q[i - 1] = GravityOneMinusS[i] - tau * q[i];
|
|
float remainder = GravityOneMinusS[0] - tau * q[0];
|
|
float whole = 0;
|
|
float part = 0;
|
|
[unroll] for (int j = @QLAST@; j >= 0; j--)
|
|
{
|
|
whole = whole + q[j] / (j + 1);
|
|
part = (part + q[j] / (j + 1)) * t;
|
|
}
|
|
return 0.5 * (remainder * log((t + tau) / (1 + tau)) - (whole - part));
|
|
}
|
|
|
|
// GravityField.BandSeries: the band's potential at t for tau > @SERIESFROM@, a series in (1 - t) / (1 + tau).
|
|
float gravityBandSeries(float tau, float t)
|
|
{
|
|
float y = 1 - t;
|
|
float yj[@N@];
|
|
yj[0] = 1;
|
|
[unroll] for (int j = 1; j < @N@; j++) yj[j] = yj[j - 1] * y;
|
|
float w = 1 / (1 + tau);
|
|
float wk = w;
|
|
float yk = y;
|
|
float acc = 0;
|
|
for (int k = 0; k < @SERIES@; k++)
|
|
{
|
|
float inner = 0;
|
|
[unroll] for (int m = @ORDER@; m < @N@; m++) inner += GravityS[m] * yj[m] / (m + k + 1);
|
|
acc += wk * yk * inner;
|
|
wk *= w;
|
|
yk *= y;
|
|
}
|
|
return -0.5 * acc;
|
|
}
|
|
|
|
// GravityField.Potential: one band term, per unit strength; the global term when b is infinite.
|
|
float gravityTermPotential(float softening, float a, float b, float r)
|
|
{
|
|
float s = r * r;
|
|
float e2 = softening * softening;
|
|
if (isinf(b)) return 0.5 * log(s + e2);
|
|
float a2 = a * a;
|
|
float b2 = b * b;
|
|
if (s >= b2) return 0;
|
|
float tau = (a2 + e2) / (b2 - a2);
|
|
if (s <= a2) return 0.5 * log((s + e2) / (a2 + e2)) + (tau <= @SERIESFROM@ ? gravityBandClosed(tau, 0) : gravityBandSeries(tau, 0));
|
|
float t = (s - a2) / (b2 - a2);
|
|
return tau <= @SERIESFROM@ ? gravityBandClosed(tau, t) : gravityBandSeries(tau, t);
|
|
}
|
|
|
|
// Zone.GetHeight at world xz: each term's potential minus its own reference, summed, divided by the exaggeration.
|
|
float gravityHeight(float2 p)
|
|
{
|
|
float result = 0;
|
|
for (int i = 0; i < _GravityWellCount; i++)
|
|
{
|
|
GravityTerm term = _GravityWells[i];
|
|
float r = length(p - term.position);
|
|
if (r < term.outer)
|
|
result += term.strength * gravityTermPotential(term.softening, term.inner, term.outer, r) - term.reference;
|
|
}
|
|
return result / _HeightExaggeration;
|
|
}
|
|
|
|
#endif
|
|
";
|
|
}
|