Merge hands/cultmath-release-0.4.0: org.gamecult.cultmath 0.4.0
Cut cultmath-release (campaign cultlib-gaps). Soul passes 1-4; the last docs edit is the one pass 4 prescribed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
252e226c51
16 changed files with 448 additions and 12 deletions
|
|
@ -111,7 +111,7 @@ swap under "Changed" rather than "Breaking".
|
|||
|
||||
## Pre-1.0 packages
|
||||
|
||||
A package at `0.y.z` (currently `org.gamecult.cultmath`, at `0.3.x`) uses the
|
||||
A package at `0.y.z` (currently `org.gamecult.cultmath`, at `0.4.x`) uses the
|
||||
standard pre-1.0 convention: `MAJOR` stays `0` during initial development, so
|
||||
`MINOR` carries what `MAJOR` means once the package reaches `1.0.0`. Concretely:
|
||||
|
||||
|
|
|
|||
|
|
@ -36,14 +36,21 @@ shader semantics.
|
|||
It includes `shaders/CultMath.Phacelle.hlsl` (MPL-2.0) and
|
||||
`shaders/CultMath.Interval.hlsl` and `shaders/CultMath.Affine.hlsl` from the same
|
||||
directory, so ship all four.
|
||||
- interval arithmetic for culling empty space with a proof: `iv_*` functions
|
||||
- interval arithmetic for culling empty space: `iv_*` functions
|
||||
over `float2(lo, hi)` (`iv_add`, `iv_mul`, `iv_exp`, `iv_smoothstep`, ...)
|
||||
and `iv_snoise_ball`/`iv_fbm_ball`, which bound `snoise` and `fbm_grad` over a
|
||||
ball, and `iv_frustum_ball`, the ball around a screen tile's rays over a depth
|
||||
segment, so one probe serves the whole tile (its radius carries its own
|
||||
float32 rounding bound). Every one returns an interval
|
||||
containing the function's value at every point of its input, so a raymarch
|
||||
float32 rounding bound). The arithmetic functions return an interval built to
|
||||
contain the function's value over their operand intervals, so a raymarch
|
||||
can skip a segment whose `hi` is below its cutoff; `cultmath_iv_*` in HLSL.
|
||||
The noise functions rest on empirical constants with a margin, not a proof:
|
||||
`iv_snoise_ball(float3)` on `SNOISE_LIPSCHITZ` (`iv_fbm_ball` through it),
|
||||
`iv_snoise_ball(float2)` on `SNOISE2_LIPSCHITZ`, and `af_snoise` on
|
||||
`SNOISE_HESSIAN` and `SNOISE_LIPSCHITZ` (`af_fbm` through it); the enclosure
|
||||
tests sample them. `iv_frustum_ball` and `af_frustum_ball` read no noise
|
||||
constant: the ball is geometry plus a rounding widening, and the noise call
|
||||
it feeds carries the constant.
|
||||
The bounds compose with a consumer's own analytic envelope (a height fog, a
|
||||
carved sphere): its interval over the segment proves empty space in one probe,
|
||||
and the noise ball is consulted only near the surface. See `docs/design.md`,
|
||||
|
|
@ -122,6 +129,12 @@ The repository root is not a Unity package. Build the tracked package with
|
|||
assembly and rejects stale binaries, missing metadata, unexpected assemblies,
|
||||
or leaked C# source.
|
||||
|
||||
Before tagging `cultmath-unity-v*`, run the import and dispatch smoke on a Windows machine with a GPU,
|
||||
alone: `packages/cultmath/scripts/unity-smoke/run-unity-smoke.ps1 -UnityExe <path to Unity.exe>`. It
|
||||
builds a scratch project outside the repository that references the package by a `file:` path,
|
||||
dispatches a compute shader that includes `CultMath.Interval.hlsl` and `CultMath.Affine.hlsl`, requires
|
||||
a deliberately broken control shader to be rejected, and deletes the project afterwards.
|
||||
|
||||
## Shader Tooling
|
||||
|
||||
CultMath keeps portable DXC outside git under `packages/cultmath/.tools/`:
|
||||
|
|
|
|||
|
|
@ -371,9 +371,11 @@ together, laid out as `float4(gradient.xyz, value.w)`, with no value-only twin
|
|||
pins the continuity. Ties in the offset `x0` resolve in Gustavson's
|
||||
`simplexnoise1234` order (x over y, y over z, x over z), a total order, so the
|
||||
four corners are always a simplex. Upstream's `step` compares are cyclic: where
|
||||
the three components are equal, which includes the origin and every
|
||||
`(t, t, t)`, the corners repeat the origin vertex and the value jumps by about
|
||||
0.77 near the origin and up to 1.56 at magnitudes of 64 and above. `NoiseGradTests.SnoiseIsContinuousOnTheSimplexDiagonal` pins the order.
|
||||
the three components are equal, which is every input whose differences `y - x`
|
||||
and `z - x` are whole numbers (every integer point, the origin and every
|
||||
`(t, t, t)` among them), the corners repeat the origin vertex and the value
|
||||
jumps by about 0.77 near the origin and up to 1.56 at magnitudes of 64 and
|
||||
above. `SnoiseTieScopeTests` pins the set against the 0.3.0 values. `NoiseGradTests.SnoiseIsContinuousOnTheSimplexDiagonal` pins the order.
|
||||
- `snoise_grad(float3 p)` carries `snoise(float3)` to value-and-gradient form,
|
||||
following the differentiation in webgl-noise `src/noise3Dgrad.glsl` (Ashima
|
||||
Arts / Ian McEwan, MIT), which uses the same radius, permutation and scale as
|
||||
|
|
|
|||
11
packages/cultmath/scripts/unity-smoke/Broken.compute
Normal file
11
packages/cultmath/scripts/unity-smoke/Broken.compute
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#pragma kernel CSMain
|
||||
#include "Packages/org.gamecult.cultmath/Shaders/CultMath.hlsl"
|
||||
#include "Packages/org.gamecult.cultmath/Shaders/CultMath.Interval.hlsl"
|
||||
#include "Packages/org.gamecult.cultmath/Shaders/CultMath.Affine.hlsl"
|
||||
RWStructuredBuffer<float4> Out;
|
||||
[numthreads(1,1,1)] void CSMain(uint3 id : SV_DispatchThreadID)
|
||||
{
|
||||
float2 r = cultmath_iv_add_missing(float2(1, 2), float2(3, 5));
|
||||
float2 a = cultmath_af_range(cultmath_af_add(cultmath_af_point(3), cultmath_af_point(5)));
|
||||
Out[0] = float4(r, a);
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "CultMath.Smoke.Tests",
|
||||
"rootNamespace": "",
|
||||
"references": [ "UnityEngine.TestRunner", "UnityEditor.TestRunner" ],
|
||||
"includePlatforms": [ "Editor" ],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [ "nunit.framework.dll", "CultMath.dll" ],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [ "UNITY_INCLUDE_TESTS" ],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
11
packages/cultmath/scripts/unity-smoke/Good.compute
Normal file
11
packages/cultmath/scripts/unity-smoke/Good.compute
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#pragma kernel CSMain
|
||||
#include "Packages/org.gamecult.cultmath/Shaders/CultMath.hlsl"
|
||||
#include "Packages/org.gamecult.cultmath/Shaders/CultMath.Interval.hlsl"
|
||||
#include "Packages/org.gamecult.cultmath/Shaders/CultMath.Affine.hlsl"
|
||||
RWStructuredBuffer<float4> Out;
|
||||
[numthreads(1,1,1)] void CSMain(uint3 id : SV_DispatchThreadID)
|
||||
{
|
||||
float2 r = cultmath_iv_add(float2(1, 2), float2(3, 5));
|
||||
float2 a = cultmath_af_range(cultmath_af_add(cultmath_af_point(3), cultmath_af_point(5)));
|
||||
Out[0] = float4(r, a);
|
||||
}
|
||||
44
packages/cultmath/scripts/unity-smoke/SmokeTests.cs
Normal file
44
packages/cultmath/scripts/unity-smoke/SmokeTests.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using System;
|
||||
using CultMath;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
public class SmokeTests
|
||||
{
|
||||
// Compiles by FindKernel and dispatches on the device; false when the shader does not compile or the output is wrong.
|
||||
static bool CompilesAndRuns(string path)
|
||||
{
|
||||
var cs = AssetDatabase.LoadAssetAtPath<ComputeShader>(path);
|
||||
Assert.IsNotNull(cs, path);
|
||||
int k;
|
||||
try { k = cs.FindKernel("CSMain"); } catch (Exception e) { Debug.Log("SMOKE " + path + " FindKernel threw: " + e.Message); return false; }
|
||||
if (!cs.HasKernel("CSMain") || !cs.IsSupported(k)) { Debug.Log("SMOKE " + path + " kernel unusable"); return false; }
|
||||
var buf = new ComputeBuffer(1, 16);
|
||||
try
|
||||
{
|
||||
cs.SetBuffer(k, "Out", buf);
|
||||
cs.Dispatch(k, 1, 1, 1);
|
||||
var o = new Vector4[1]; buf.GetData(o);
|
||||
Debug.Log($"SMOKE {path} out=({o[0].x},{o[0].y},{o[0].z},{o[0].w}) gpu={SystemInfo.graphicsDeviceType}");
|
||||
return o[0].x == 4f && o[0].y == 7f && o[0].z <= 8f && o[0].w >= 8f && o[0].w - o[0].z < 1e-3f;
|
||||
}
|
||||
finally { buf.Release(); }
|
||||
}
|
||||
|
||||
[Test] public void CSharpIntervalAndAffineWork()
|
||||
{
|
||||
var r = math.iv_add(new float2(1, 2), new float2(3, 5));
|
||||
Assert.AreEqual(4f, r.x); Assert.AreEqual(7f, r.y);
|
||||
Assert.AreEqual(0f, math.snoise(new float3(0, 0, 0)));
|
||||
}
|
||||
|
||||
[Test] public void RealShaderCompilesAndDispatches() { Assert.IsTrue(CompilesAndRuns("Assets/Shaders/Good.compute")); }
|
||||
|
||||
[Test] public void BrokenShaderIsDetected()
|
||||
{
|
||||
LogAssert.ignoreFailingMessages = true;
|
||||
Assert.IsFalse(CompilesAndRuns("Assets/Shaders/Broken.compute"));
|
||||
}
|
||||
}
|
||||
94
packages/cultmath/scripts/unity-smoke/run-unity-smoke.ps1
Normal file
94
packages/cultmath/scripts/unity-smoke/run-unity-smoke.ps1
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
The release smoke for the tracked org.gamecult.cultmath Unity package: imports it into a
|
||||
scratch project, compiles and dispatches a compute shader that includes the package's
|
||||
CultMath.Interval.hlsl and CultMath.Affine.hlsl on a real graphics device, and proves a
|
||||
deliberately broken shader is rejected.
|
||||
|
||||
.DESCRIPTION
|
||||
Run it on a Windows machine with a GPU, alone, before tagging cultmath-unity-v*. The scratch
|
||||
project is built outside the repository under -WorkRoot, references the package by a file: path,
|
||||
and is deleted afterwards (-Keep leaves it). Exit 0 only when Unity exited 0, this run wrote the
|
||||
results file (earlier ones are deleted before launch), and all three tests of SmokeTests.cs
|
||||
passed: the C# calls, the real shader dispatched with the right output, and the broken control
|
||||
shader detected.
|
||||
|
||||
Never add -nographics. Without a device nothing compiles, and
|
||||
ShaderUtil.GetComputeShaderMessageCount reports 0 messages for a compute shader that calls an
|
||||
undeclared function until FindKernel or Dispatch compiles it, so a message-count check passes a
|
||||
broken shader. SmokeTests.cs compiles by FindKernel, dispatches, and reads the output back.
|
||||
|
||||
For a long run start it detached and poll the log, for example:
|
||||
Start-Process powershell -WindowStyle Hidden -ArgumentList '-File', '<this script>', '-UnityExe', '<Unity.exe>', '-WorkRoot', '<dir>'
|
||||
The script writes unity.pid and unity.log under the scratch root's logs folder (kept after the run).
|
||||
|
||||
.PARAMETER UnityExe
|
||||
Path to Unity.exe of the editor to test with (the cut that released 0.4.0 used 6000.3.24f1).
|
||||
.PARAMETER WorkRoot
|
||||
Directory outside the repository for the scratch project and logs. Default: a new folder under TEMP.
|
||||
.PARAMETER Keep
|
||||
Keep the scratch project instead of deleting it.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)] [string] $UnityExe,
|
||||
[string] $WorkRoot = (Join-Path $env:TEMP ("cultmath-unity-smoke-" + [guid]::NewGuid().ToString('N').Substring(0, 8))),
|
||||
[switch] $Keep
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if (-not (Test-Path -LiteralPath $UnityExe -PathType Leaf)) { throw "UnityExe not found: $UnityExe" }
|
||||
|
||||
$smoke = $PSScriptRoot
|
||||
$package = (Resolve-Path (Join-Path $smoke '..\..\unity\org.gamecult.cultmath')).Path
|
||||
$repoRoot = (git -C $smoke rev-parse --show-toplevel).Trim()
|
||||
$work = [IO.Path]::GetFullPath($WorkRoot)
|
||||
if ($work.StartsWith([IO.Path]::GetFullPath($repoRoot), [StringComparison]::OrdinalIgnoreCase)) {
|
||||
throw "WorkRoot must be outside the repository: $work"
|
||||
}
|
||||
$project = Join-Path $work 'project'
|
||||
$logs = Join-Path $work 'logs'
|
||||
New-Item -ItemType Directory -Force -Path $logs | Out-Null
|
||||
foreach ($d in 'Packages', 'Assets\Shaders', 'Assets\Tests') { New-Item -ItemType Directory -Force -Path (Join-Path $project $d) | Out-Null }
|
||||
|
||||
Copy-Item -LiteralPath (Join-Path $smoke 'Good.compute'), (Join-Path $smoke 'Broken.compute') -Destination (Join-Path $project 'Assets\Shaders')
|
||||
Copy-Item -LiteralPath (Join-Path $smoke 'SmokeTests.cs'), (Join-Path $smoke 'CultMath.Smoke.Tests.asmdef') -Destination (Join-Path $project 'Assets\Tests')
|
||||
|
||||
$packageUri = 'file:' + $package.Replace([string][char]92, '/')
|
||||
$manifest = [ordered]@{
|
||||
dependencies = [ordered]@{
|
||||
'com.unity.test-framework' = '1.6.0'
|
||||
'org.gamecult.cultmath' = $packageUri
|
||||
}
|
||||
}
|
||||
[IO.File]::WriteAllText((Join-Path $project 'Packages\manifest.json'), ($manifest | ConvertTo-Json -Depth 4), (New-Object Text.UTF8Encoding($false)))
|
||||
|
||||
$log = Join-Path $logs 'unity.log'
|
||||
$results = Join-Path $logs 'results.xml'
|
||||
# A kept logs folder may hold an earlier run's evidence: nothing from a previous run may decide this one.
|
||||
foreach ($stale in $log, $results, (Join-Path $logs 'unity.pid')) { Remove-Item -LiteralPath $stale -Force -ErrorAction SilentlyContinue }
|
||||
if (Test-Path -LiteralPath $results) { throw "could not clear the previous results file: $results" }
|
||||
$unityArgs = @('-batchmode', '-projectPath', $project, '-runTests', '-testPlatform', 'EditMode',
|
||||
'-testResults', $results, '-logFile', $log)
|
||||
$launched = Get-Date
|
||||
$p = Start-Process -PassThru -FilePath $UnityExe -ArgumentList $unityArgs -WindowStyle Hidden
|
||||
$p.Id | Out-File (Join-Path $logs 'unity.pid') -Encoding ascii
|
||||
"unity pid=$($p.Id) log=$log"
|
||||
$p.WaitForExit()
|
||||
"unity exit=$($p.ExitCode)"
|
||||
|
||||
# Pass needs all of: Unity exited 0; a results file written by this run; the real shader test passed and
|
||||
# the broken control test passed (it asserts that the broken shader is rejected).
|
||||
$ok = $false
|
||||
if ($p.ExitCode -ne 0) { "unity exited non-zero: $($p.ExitCode)" }
|
||||
elseif (-not (Test-Path -LiteralPath $results)) { 'no results file from this run' }
|
||||
elseif ((Get-Item -LiteralPath $results).LastWriteTime -lt $launched) { 'results file predates this run' }
|
||||
else {
|
||||
$cases = @(([xml](Get-Content -LiteralPath $results -Raw)).SelectNodes('//test-case'))
|
||||
$cases | ForEach-Object { "{0} {1}" -f $_.result, $_.name }
|
||||
$want = 'CSharpIntervalAndAffineWork', 'RealShaderCompilesAndDispatches', 'BrokenShaderIsDetected'
|
||||
$passed = @($cases | Where-Object { $_.result -eq 'Passed' } | ForEach-Object { ($_.name -split '\.')[-1] })
|
||||
$ok = (@($want | Where-Object { $passed -contains $_ }).Count -eq $want.Count)
|
||||
}
|
||||
Get-Content -LiteralPath $log -ErrorAction SilentlyContinue | Select-String -CaseSensitive -Pattern '^SMOKE ' | ForEach-Object { $_.Line }
|
||||
|
||||
if (-not $Keep) { Remove-Item -LiteralPath $project -Recurse -Force }
|
||||
if ($ok) { 'SMOKE PASSED'; exit 0 } else { 'SMOKE FAILED'; exit 1 }
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
<PackageId>GameCult.Math</PackageId>
|
||||
<!-- MIT overall; math.Phacelle.cs and CultPhasor.cs (and the two Phacelle HLSL files) are MPL-2.0. -->
|
||||
<PackageLicenseExpression>MIT AND MPL-2.0</PackageLicenseExpression>
|
||||
<Version>0.3.0</Version>
|
||||
<Version>0.4.0</Version>
|
||||
<Description>Clean-room C# math shaped for HLSL-style renderer code.</Description>
|
||||
<Authors>GameCult</Authors>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
|
|
@ -25,6 +25,7 @@
|
|||
<None Include="..\..\shaders\CultMath.hlsl" Pack="true" PackagePath="contentFiles\any\any\shaders\CultMath.hlsl" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Include="..\..\shaders\CultMath.Phacelle.hlsl" Pack="true" PackagePath="contentFiles\any\any\shaders\CultMath.Phacelle.hlsl" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Include="..\..\shaders\CultMath.Interval.hlsl" Pack="true" PackagePath="contentFiles\any\any\shaders\CultMath.Interval.hlsl" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Include="..\..\shaders\CultMath.Affine.hlsl" Pack="true" PackagePath="contentFiles\any\any\shaders\CultMath.Affine.hlsl" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Include="..\..\THIRD-PARTY-NOTICES.md" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -192,8 +192,9 @@ public sealed class NoiseGradTests
|
|||
|
||||
// The simplex diagonals are where x0's three components compare equal. The tie rule must be a total
|
||||
// order there (math.snoise): with a cyclic or all-strict compare the four corners stop being a
|
||||
// simplex and the value jumps on the line (t, t, t), lattice diagonals included: about 0.77 near the
|
||||
// origin, up to 1.56 at magnitudes of 64 and above.
|
||||
// simplex and the value jumps wherever x0 has three equal components (inputs whose differences y - x and
|
||||
// z - x are whole numbers, the line (t, t, t) among them): about 0.77 near the origin, up to 1.56 at
|
||||
// magnitudes of 64 and above. SnoiseTieScopeTests pins the set against the 0.3.0 values.
|
||||
[Fact]
|
||||
public void SnoiseIsContinuousOnTheSimplexDiagonal()
|
||||
{
|
||||
|
|
|
|||
177
packages/cultmath/tests/CultMath.Tests/SnoiseTieScopeTests.cs
Normal file
177
packages/cultmath/tests/CultMath.Tests/SnoiseTieScopeTests.cs
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
using System;
|
||||
using CultMath;
|
||||
using Xunit;
|
||||
|
||||
namespace CultMath.Tests;
|
||||
|
||||
// The 0.4.0 tie rule (math.snoise(float3), math.snoise_grad) changes values exactly where the cell
|
||||
// offset x0 has three equal components, which is every input whose coordinate differences y - x and
|
||||
// z - x are whole numbers (every integer point, and the whole (1, 1, 1) line through each). Off that
|
||||
// set and its float32 band (below) the results are bit-identical to 0.3.0. The goldens below were computed by the
|
||||
// cultmath-unity-v0.3.0 tag's CultMath.dll (Runtime/Plugins), not by this code. Float32 rounding
|
||||
// decides whether a given integer-difference input ties, because x0 is computed in float32: an
|
||||
// input whose differences round onto whole numbers can tie too and move, within a few float32
|
||||
// ulps of the largest coordinate (a probe found up to about two; the band widens with the magnitude), and some inputs on
|
||||
// the set keep their value (about 3% of random integer points). Every input below ties and moves
|
||||
// all five outputs (value and the four gradient words).
|
||||
// TieSetNow pins what this code returns on the same inputs, so that each of the two tie-rule sites
|
||||
// (the step in snoise, the step in snoise_grad) is pinned on its own.
|
||||
public sealed class SnoiseTieScopeTests
|
||||
{
|
||||
// (input, snoise bits, snoise_grad x y z w bits) from 0.3.0, each on the tie set and moved by the new rule.
|
||||
private static readonly (float3 P, uint Value, uint[] Grad)[] TieSet =
|
||||
{
|
||||
(new float3(8f, -9f, 1f), 0xBDD82D0D, new uint[] { 0x3FA7D14B, 0x40A68968, 0xC0EE5B16, 0xBDD82D0D }),
|
||||
(new float3(-15f, 1f, -3f), 0xBF29AEDB, new uint[] { 0x3ECABF0C, 0x40482713, 0x3F444FED, 0xBF29AEDB }),
|
||||
(new float3(9f, -14f, 7f), 0x3F3F28F4, new uint[] { 0x3F5FB228, 0x3F471A21, 0x40454A1F, 0x3F3F28F4 }),
|
||||
(new float3(15f, 8f, 17f), 0xBDEE4FE2, new uint[] { 0x3F1F3C06, 0x3FFA0CCF, 0xC00C8395, 0xBDEE4FE2 }),
|
||||
(new float3(3f, -12f, 10f), 0xBF3A1373, new uint[] { 0x3FC00B62, 0x3E28F20F, 0x403F02DA, 0xBF3A1373 }),
|
||||
(new float3(2f, -8f, 15f), 0x3F180CA2, new uint[] { 0x3F567351, 0xC0340E87, 0x40E74458, 0x3F180CA2 }),
|
||||
(new float3(-6.1645937f, -6.1645937f, -6.1645937f), 0xBF1C7CA6, new uint[] { 0xC04341A7, 0xBFE7241A, 0x3EE7511A, 0xBF1C7CA6 }),
|
||||
(new float3(22.580343f, 22.580343f, 22.580343f), 0x3F5D9075, new uint[] { 0x400096F0, 0xC0590215, 0x3FAAC79D, 0x3F5D9075 }),
|
||||
(new float3(-25.630196f, -25.630196f, -25.630196f), 0x3F4C7BDB, new uint[] { 0xBFCE54B7, 0x3FCD63EC, 0x403492AC, 0x3F4C7BDB }),
|
||||
(new float3(-26.818033f, -26.818033f, -26.818033f), 0x3DE34495, new uint[] { 0xBEF368D6, 0xBFD14FED, 0x3FE503E7, 0x3DE34495 }),
|
||||
(new float3(8.4097805f, -2.5902195f, -0.5902195f), 0xBF85F631, new uint[] { 0xBFE0D093, 0x3F8E6022, 0x3ECA4F57, 0xBF85F631 }),
|
||||
(new float3(8.618587f, 1.6185865f, 0.61858654f), 0xBE8FB513, new uint[] { 0xC02F9DDD, 0x4063CEA5, 0xBDF0BFBB, 0xBE8FB513 }),
|
||||
(new float3(-13.692368f, -9.692368f, -20.692368f), 0xBF28A1E3, new uint[] { 0xC012A224, 0xC0187F57, 0xBF57F2CB, 0xBF28A1E3 }),
|
||||
(new float3(9.495648f, 11.495648f, 15.495648f), 0x3E4FC9B5, new uint[] { 0x3EA26E55, 0x403CE620, 0xBF46F425, 0x3E4FC9B5 }),
|
||||
(new float3(-12.66157f, -19.66157f, -9.66157f), 0xBF179BF9, new uint[] { 0x3FD03786, 0x403A1C1E, 0xBEAD7581, 0xBF179BF9 }),
|
||||
(new float3(-8.801376f, -1.8013763f, -19.801376f), 0xBE82F2EE, new uint[] { 0xC0DCFC55, 0x405501DB, 0x401E6CF8, 0xBE82F2EE }),
|
||||
};
|
||||
|
||||
// The values this code returns on TieSet's inputs, in the same order (the value, then the four
|
||||
// snoise_grad components).
|
||||
private static readonly (uint Value, uint[] Grad)[] TieSetNow =
|
||||
{
|
||||
(0xB37C25C6, new uint[] { 0x3F281943, 0x40521F92, 0xC0A8193D, 0xB37C25C6 }),
|
||||
(0xBF214750, new uint[] { 0xBD931F34, 0x40260272, 0x3E94E53D, 0xBF214750 }),
|
||||
(0x3F35B75A, new uint[] { 0x3EAD85CB, 0x3E78AB7B, 0x401F83B4, 0x3F35B75A }),
|
||||
(0xBDE33E78, new uint[] { 0x3F0705E4, 0x3FE9CB38, 0xC00DE5C4, 0xBDE33E78 }),
|
||||
(0xBF30DF87, new uint[] { 0x3F76B426, 0xBEAE857A, 0x401A3325, 0xBF30DF87 }),
|
||||
(0x00000000, new uint[] { 0x3FEC7798, 0xBF1DA4FF, 0x40C50E50, 0x00000000 }),
|
||||
(0xBF13F436, new uint[] { 0xC0219F00, 0xBFA829D9, 0x3F629666, 0xBF13F436 }),
|
||||
(0x3F0C1A1C, new uint[] { 0x404F76CC, 0xBFA6560B, 0x402B2291, 0x3F0C1A1C }),
|
||||
(0x3F2E5150, new uint[] { 0xC01F3830, 0x3F007F73, 0x3FD11DF1, 0x3F2E5150 }),
|
||||
(0x3DDE3B0E, new uint[] { 0xBED7A81C, 0xBFC89166, 0x3FE7E3CC, 0x3DDE3B0E }),
|
||||
(0xBF23BB4A, new uint[] { 0xC057D547, 0xBF7B66AE, 0xBFCA313F, 0xBF23BB4A }),
|
||||
(0xBE68D15B, new uint[] { 0xC0391D00, 0x40366C69, 0xBEFFC8AD, 0xBE68D15B }),
|
||||
(0xBF2659BE, new uint[] { 0xC0048291, 0xC00A5FC3, 0xBF21B992, 0xBF2659BE }),
|
||||
(0x3E4630E4, new uint[] { 0x3E35FE51, 0x40306ACC, 0xBF655794, 0x3E4630E4 }),
|
||||
(0xBF117B1D, new uint[] { 0x3F9D6F53, 0x401F35B7, 0xBF31BAFE, 0xBF117B1D }),
|
||||
(0xBDA5A39E, new uint[] { 0xC0B4E453, 0x3FF84A5D, 0x3FA74D5F, 0xBDA5A39E }),
|
||||
};
|
||||
|
||||
// The same, off the tie set: coordinate differences that are not whole numbers.
|
||||
private static readonly (float3 P, uint Value, uint[] Grad)[] OffSet =
|
||||
{
|
||||
(new float3(-3.7841978f, -12.899288f, 1.5972604f), 0x3EA04649, new uint[] { 0xBF74444B, 0x3F87A8A7, 0xC021229C, 0x3EA04649 }),
|
||||
(new float3(8.898657f, 12.611845f, 4.790286f), 0xBEC97F79, new uint[] { 0xBD25992C, 0xC02EF328, 0xBF17ECAB, 0xBEC97F79 }),
|
||||
(new float3(0.7057237f, 11.097378f, 13.493794f), 0xBE00B70D, new uint[] { 0xBEDBFFF7, 0xBFDA79F7, 0xBF07EA57, 0xBE00B70D }),
|
||||
(new float3(-15.702414f, 3.5919724f, -7.0502086f), 0xBDC6607C, new uint[] { 0xBF113459, 0x3EB74352, 0xBF4D2C56, 0xBDC6607C }),
|
||||
(new float3(-13.623555f, -10.574037f, -15.697628f), 0x3E823717, new uint[] { 0xBF4926AE, 0x4076E757, 0x40710AFC, 0x3E823717 }),
|
||||
(new float3(-10.028356f, 5.4002185f, -13.579105f), 0x3EF1651D, new uint[] { 0x3FF0CA03, 0xC004B90C, 0xBFAF91DA, 0x3EF1651D }),
|
||||
(new float3(12.788626f, 14.914132f, -0.39256755f), 0x3DB39ABE, new uint[] { 0xC06D6427, 0xBF941974, 0x3E8B556D, 0x3DB39ABE }),
|
||||
(new float3(-2.4998372f, 10.696207f, 15.391607f), 0xBF0D1821, new uint[] { 0x3FAB00CF, 0x4044DAE4, 0x3F1E2E21, 0xBF0D1821 }),
|
||||
(new float3(10.437524f, -3.4692447f, 15.652484f), 0x3EFCC588, new uint[] { 0xC083735E, 0x3FA4A10F, 0xBE7D19D2, 0x3EFCC588 }),
|
||||
(new float3(-4.4040494f, 14.969201f, -2.2152362f), 0xBD4192BF, new uint[] { 0xBF3F69AE, 0xBF843396, 0xBFF88079, 0xBD4192BF }),
|
||||
(new float3(2.7095387f, 13.3163395f, -0.28273603f), 0x3EF0704A, new uint[] { 0xBF75C892, 0x3F0C95B0, 0x3F7401B5, 0x3EF0704A }),
|
||||
(new float3(-10.564334f, -5.6527104f, -6.6908674f), 0x3EA35AEA, new uint[] { 0xC05CB149, 0x3F928B08, 0x3EB68FCB, 0x3EA35AEA }),
|
||||
(new float3(-13.572381f, -2.2560256f, -12.702176f), 0x3D2013BE, new uint[] { 0xC0236256, 0x3D02D65C, 0x3DB57CBC, 0x3D2013BE }),
|
||||
(new float3(0.11813756f, -7.2100754f, 7.144552f), 0xBF244AB4, new uint[] { 0xBF6B4A5E, 0xBFFC1BFA, 0xBE868270, 0xBF244AB4 }),
|
||||
(new float3(9.668022f, -0.32686335f, 2.333893f), 0xBDE9567A, new uint[] { 0x3F359CBB, 0x3F2B104D, 0x3FEFAB81, 0xBDE9567A }),
|
||||
(new float3(-6.9417686f, -6.2886133f, -13.874224f), 0x3CC28786, new uint[] { 0xC0291AB6, 0x3E57B89E, 0xBE884AF7, 0x3CC28786 }),
|
||||
(new float3(9.9228945f, 13.366786f, 1.9626095f), 0x3DDCC7CA, new uint[] { 0xBFA57791, 0x40277CF0, 0xBF816FCF, 0x3DDCC7CA }),
|
||||
(new float3(1.3210233f, -5.5900016f, 9.691286f), 0x3D140957, new uint[] { 0x4003DBED, 0x3E35DCDD, 0xBF9CFAAE, 0x3D140957 }),
|
||||
(new float3(2.5023124f, 14.715639f, -7.5954947f), 0x3EB522F9, new uint[] { 0x3FB54F05, 0x4003D90A, 0x3F4CCE6D, 0x3EB522F9 }),
|
||||
(new float3(2.2056534f, 14.461132f, 0.081089176f), 0xBE0FBF83, new uint[] { 0xBF2E2FDB, 0x4014B7C5, 0x3F8C8EC2, 0xBE0FBF83 }),
|
||||
(new float3(5.137802f, -10.890227f, 12.128177f), 0xBEAEF52F, new uint[] { 0xBFBC5CA4, 0xBFD159C2, 0x402EBCD4, 0xBEAEF52F }),
|
||||
(new float3(-0.290969f, -2.4119518f, 6.2756324f), 0x3EE30F7E, new uint[] { 0x3E94AD2B, 0x3F8DCBA0, 0x4055B8BB, 0x3EE30F7E }),
|
||||
(new float3(9.232528f, 12.940779f, 11.078674f), 0x3E380DF3, new uint[] { 0x3F8B7DBE, 0x3F1596ED, 0xC0527328, 0x3E380DF3 }),
|
||||
(new float3(6.9932394f, -12.455975f, -8.343756f), 0x3E929F5A, new uint[] { 0x3E5564B6, 0x3FF1BE81, 0xBF77638A, 0x3E929F5A }),
|
||||
(new float3(15.123071f, 12.623071f, 20.12307f), 0x3E9527A3, new uint[] { 0x3D084582, 0xC07DE925, 0xBECA2112, 0x3E9527A3 }),
|
||||
(new float3(8.46288f, 11.96288f, 4.46288f), 0x3E8C0F92, new uint[] { 0x3FD2DFD3, 0x3FB49817, 0x3F023648, 0x3E8C0F92 }),
|
||||
(new float3(-2.9422863f, -1.4422863f, -12.9422865f), 0xBEBAEE39, new uint[] { 0x3FECB31B, 0xBFD33138, 0x3F9A17A8, 0xBEBAEE39 }),
|
||||
(new float3(13.526707f, 15.026707f, 2.5267067f), 0x3EF22701, new uint[] { 0x3F1D389B, 0x3FCABBBE, 0xBF66EE93, 0x3EF22701 }),
|
||||
(new float3(11.822154f, 1.322154f, 13.822154f), 0x3E85ACC7, new uint[] { 0x3FA7C0C7, 0xC067C262, 0x3FF35925, 0x3E85ACC7 }),
|
||||
(new float3(6.025978f, 8.525978f, 3.025978f), 0x3E9964B0, new uint[] { 0xBEE7F346, 0x3F1918E9, 0x3EF3DA8A, 0x3E9964B0 }),
|
||||
(new float3(-15.095373f, -14.595373f, -16.095373f), 0xBE6EF82D, new uint[] { 0xC01F9511, 0x3FEDB679, 0xBFDA5058, 0xBE6EF82D }),
|
||||
(new float3(3.2167344f, 12.716734f, -6.7832656f), 0x3EA3C041, new uint[] { 0xBFFA5E11, 0xBF41778D, 0xBFCFD96A, 0x3EA3C041 }),
|
||||
(new float3(12.25f, -0.25f, -3.25f), 0x3EB8C88C, new uint[] { 0xBFA0E2A2, 0x40101407, 0x402D2F5B, 0x3EB8C88C }),
|
||||
(new float3(-9f, -13.25f, 10f), 0x3F032C5D, new uint[] { 0x3F9FFBCB, 0x3E6ED505, 0x401D75F4, 0x3F032C5D }),
|
||||
(new float3(-5.75f, 2.25f, 13.75f), 0x3E62496B, new uint[] { 0x3FC789CA, 0x3EE63ACD, 0xBEE57812, 0x3E62496B }),
|
||||
(new float3(-12.5f, 10f, -5.25f), 0x3EEA194F, new uint[] { 0x3F182206, 0xC0282BDE, 0xBFA51540, 0x3EEA194F }),
|
||||
(new float3(13.5f, -11f, -3f), 0x3E6B5E9A, new uint[] { 0xBF2F5213, 0x3FE97BC2, 0x3F072FB4, 0x3E6B5E9A }),
|
||||
(new float3(-2.75f, 7.75f, 3.5f), 0x3E91F33E, new uint[] { 0x3E2546E0, 0xBF63D038, 0xBFC3C500, 0x3E91F33E }),
|
||||
(new float3(10.5f, 4f, -3.75f), 0x3EBCDE1F, new uint[] { 0xBFDC4426, 0x400DCD14, 0x3F7CACF6, 0x3EBCDE1F }),
|
||||
(new float3(-12.75f, -13f, -6f), 0x3D5F6FB0, new uint[] { 0xBFBF0ED8, 0xC0109EED, 0xC007C568, 0x3D5F6FB0 }),
|
||||
(new float3(-0.069121994f, 0.76107657f, 0.41525742f), 0x3DA72363, new uint[] { 0x3E5DF1FF, 0x3F7E17BE, 0x3FA89CB3, 0x3DA72363 }),
|
||||
(new float3(1.0466753f, -0.51170796f, 0.21126905f), 0xBDD1FE81, new uint[] { 0x3DFD066D, 0x3E5E0061, 0x3FDBADAF, 0xBDD1FE81 }),
|
||||
(new float3(0.30550694f, 1.6808131f, -0.76585627f), 0x3F0F3AB5, new uint[] { 0x3FF9EAC8, 0xC001ADBF, 0x3FD97B97, 0x3F0F3AB5 }),
|
||||
(new float3(-0.8827523f, 0.12493165f, -1.8058014f), 0x3EC3BE7E, new uint[] { 0xC02395D6, 0x3F00EEB9, 0x3F976926, 0x3EC3BE7E }),
|
||||
};
|
||||
|
||||
private static bool OnTieSet(float3 p)
|
||||
{
|
||||
var a = (double)p.y - p.x;
|
||||
var b = (double)p.z - p.x;
|
||||
return a == Math.Floor(a) && b == Math.Floor(b);
|
||||
}
|
||||
|
||||
// Each output is asserted on its own: a tie rule reverted at one site must fail here by itself,
|
||||
// whatever the other site returns. A given input moves some outputs and not others, so each
|
||||
// output must differ from 0.3.0 on at least one tie-set input.
|
||||
[Fact]
|
||||
public void EachOutputDiffersFromZeroPointThreeSomewhereOnTheTieSet()
|
||||
{
|
||||
Assert.NotEmpty(TieSet);
|
||||
var moved = new bool[5];
|
||||
foreach (var (p, value, grad) in TieSet)
|
||||
{
|
||||
Assert.True(OnTieSet(p));
|
||||
var g = math.snoise_grad(p);
|
||||
moved[0] |= BitConverter.SingleToUInt32Bits(math.snoise(p)) != value;
|
||||
moved[1] |= BitConverter.SingleToUInt32Bits(g.x) != grad[0];
|
||||
moved[2] |= BitConverter.SingleToUInt32Bits(g.y) != grad[1];
|
||||
moved[3] |= BitConverter.SingleToUInt32Bits(g.z) != grad[2];
|
||||
moved[4] |= BitConverter.SingleToUInt32Bits(g.w) != grad[3];
|
||||
}
|
||||
Assert.True(moved[0], "snoise(float3) still reads its 0.3.0 value on the tie set");
|
||||
Assert.True(moved[1], "snoise_grad.x still reads its 0.3.0 value on the tie set");
|
||||
Assert.True(moved[2], "snoise_grad.y still reads its 0.3.0 value on the tie set");
|
||||
Assert.True(moved[3], "snoise_grad.z still reads its 0.3.0 value on the tie set");
|
||||
Assert.True(moved[4], "snoise_grad.w still reads its 0.3.0 value on the tie set");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TieSetInputsReturnTheirZeroPointFourGoldens()
|
||||
{
|
||||
Assert.Equal(TieSet.Length, TieSetNow.Length);
|
||||
for (var k = 0; k < TieSet.Length; k++)
|
||||
{
|
||||
var p = TieSet[k].P;
|
||||
var (value, grad) = TieSetNow[k];
|
||||
var g = math.snoise_grad(p);
|
||||
Assert.Equal(value, BitConverter.SingleToUInt32Bits(math.snoise(p)));
|
||||
Assert.Equal(grad[0], BitConverter.SingleToUInt32Bits(g.x));
|
||||
Assert.Equal(grad[1], BitConverter.SingleToUInt32Bits(g.y));
|
||||
Assert.Equal(grad[2], BitConverter.SingleToUInt32Bits(g.z));
|
||||
Assert.Equal(grad[3], BitConverter.SingleToUInt32Bits(g.w));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InputsOffTheTieSetKeepTheirZeroPointThreeBits()
|
||||
{
|
||||
Assert.NotEmpty(OffSet);
|
||||
foreach (var (p, value, grad) in OffSet)
|
||||
{
|
||||
Assert.False(OnTieSet(p));
|
||||
var g = math.snoise_grad(p);
|
||||
Assert.Equal(value, BitConverter.SingleToUInt32Bits(math.snoise(p)));
|
||||
Assert.Equal(grad[0], BitConverter.SingleToUInt32Bits(g.x));
|
||||
Assert.Equal(grad[1], BitConverter.SingleToUInt32Bits(g.y));
|
||||
Assert.Equal(grad[2], BitConverter.SingleToUInt32Bits(g.z));
|
||||
Assert.Equal(grad[3], BitConverter.SingleToUInt32Bits(g.w));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,74 @@ All notable changes to this package are documented in this file.
|
|||
Earlier releases (0.2.0-0.2.3) predate this package's changelog and are not
|
||||
backfilled here; see `docs/semver-policy.md` for why.
|
||||
|
||||
## [0.4.0]
|
||||
|
||||
### Breaking
|
||||
|
||||
- `math.snoise(float3)` and `math.snoise_grad`, and their HLSL mirrors in
|
||||
`Shaders/CultMath.hlsl`, now resolve ties among the components of the cell
|
||||
offset in a fixed order: x over y, y over z, x over z. The cyclic rule they
|
||||
used before read a different function wherever the three components of the
|
||||
cell offset are equal. That is every input whose coordinate differences
|
||||
`y - x` and `z - x` are whole numbers: every integer point (the origin
|
||||
included), and the whole `(1, 1, 1)` line through each of them (`(t, t, t)`
|
||||
among them). On that set values change by up to 0.776; for example
|
||||
`snoise(0, 0, 0)` was -0.4358730 and is 0, `snoise(5, 0, 0)` was 0.7076616 and
|
||||
is 0.671713, and `snoise(1, 2, 3)` was 0.22423841 and is 0. Float32 rounding
|
||||
decides whether a given input ties, and an input that ties may still keep its
|
||||
value; measured over random integer points, about 97% change. The cell offset
|
||||
is computed in float32, so an input whose differences `y - x` and `z - x`
|
||||
round, in float32, onto whole numbers can tie and change too. How far from the set that
|
||||
reaches scales with the coordinates' magnitude: a few float32 ulps of the largest
|
||||
coordinate (a probe over random inputs found changed inputs up to about two ulps
|
||||
away; measured, not proved). Computed coordinates such as `i * 0.1f`
|
||||
can land there, so near large coordinates the band is wide. Elsewhere the results are
|
||||
bit-identical to 0.3.0 (`SnoiseTieScopeTests`, goldens computed by the 0.3.0
|
||||
DLL). `snoise` is now
|
||||
continuous across the diagonals to within 1e-4 over two ulps either side
|
||||
(`NoiseGradTests`, `SnoiseIsContinuousOnTheSimplexDiagonal`). Anything keyed
|
||||
on integer lattice coordinates (voxel or tile seeds, cell centres, generated
|
||||
content sampled on a whole-number grid) changes; regenerate it when you take
|
||||
this version.
|
||||
|
||||
### Added
|
||||
|
||||
- Interval arithmetic on `float2(lo, hi)`: `math.iv_point`, `iv_add`, `iv_sub`,
|
||||
`iv_mul`, `iv_neg`, `iv_scale`, `iv_abs`, `iv_min`, `iv_max`, `iv_clamp`,
|
||||
`iv_saturate`, `iv_lerp`, `iv_sqr`, `iv_sqrt`, `iv_exp`, `iv_smoothstep`,
|
||||
`iv_snoise_ball` (`float3` and `float2` centres), `iv_fbm_ball` and
|
||||
`iv_frustum_ball`, with the HLSL mirrors in the new
|
||||
`Shaders/CultMath.Interval.hlsl`. Each function is built to return an interval that
|
||||
contains the pointwise float32 result for the operand intervals, but the noise
|
||||
functions rest on empirical constants with a margin, not on a proof:
|
||||
`iv_snoise_ball(float3)` on `SNOISE_LIPSCHITZ`, `iv_fbm_ball` on the same constant through
|
||||
`iv_snoise_ball(float3)`, `iv_snoise_ball(float2)` on `SNOISE2_LIPSCHITZ`, and
|
||||
`af_snoise` on `SNOISE_HESSIAN` and `SNOISE_LIPSCHITZ`, which `af_fbm` reaches through
|
||||
`af_snoise`. `iv_frustum_ball` and `af_frustum_ball` read no noise constant: they are
|
||||
geometry plus a 2^-20 rounding widening, and they return a ball (`float4`) to hand to a noise
|
||||
call, which carries the constant. The enclosure tests sample
|
||||
points and check containment without a tolerance; they do not prove it for
|
||||
every input.
|
||||
- Reduced affine forms on `float3(x0, a, e)` that share one symbol over a
|
||||
region: `math.af_point`, `af_symbol`, `af_range`, `af_from_iv`, `af_add`,
|
||||
`af_add_iv`, `af_sub`, `af_neg`, `af_scale`, `af_mul`, `af_snoise`, `af_fbm`,
|
||||
`af_frustum_axis` and `af_frustum_ball`, with the HLSL mirrors in the new
|
||||
`Shaders/CultMath.Affine.hlsl`.
|
||||
- Noise bound constants `math.SNOISE_LIPSCHITZ`, `SNOISE2_LIPSCHITZ` and
|
||||
`SNOISE_HESSIAN`: empirical bounds with a margin, re-measured and pinned by
|
||||
`NoiseBoundTests`.
|
||||
- `math.asfloat(uint)`, the inverse of `math.asuint`.
|
||||
- `BoundedLeastSquares.Solve` overload taking `kktRelativeTolerance`, and the
|
||||
constant `BoundedLeastSquares.DefaultKktRelativeTolerance` (1e-6). The
|
||||
tolerance must be finite and greater than zero, else the result is
|
||||
`InvalidInput`. The overload without it passes the default and returns the same
|
||||
result as before.
|
||||
|
||||
### Fixed
|
||||
|
||||
- The NuGet package `GameCult.Math` now carries `CultMath.Affine.hlsl` under
|
||||
`contentFiles/any/any/shaders`, as the README says it does.
|
||||
|
||||
## [0.3.0]
|
||||
|
||||
### Breaking
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "org.gamecult.cultmath",
|
||||
"displayName": "CultMath",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"unity": "2021.3",
|
||||
"description": "Precompiled CultMath numeric primitives and matching HLSL semantics for GameCult Unity runtimes.",
|
||||
"license": "MIT AND MPL-2.0",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"version": "1.0.60",
|
||||
"unity": "2021.3",
|
||||
"dependencies": {
|
||||
"org.gamecult.cultmath": "0.3.0"
|
||||
"org.gamecult.cultmath": "0.4.0"
|
||||
},
|
||||
"description": "CultCache, CultNet, and CultMesh managed runtime assemblies for Unity.",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
Loading…
Reference in a new issue