Merge CultMath HLSL parity into CultCache store routing
Some checks failed
CultMesh portability and performance / ubuntu-latest one-command multiplayer onboarding (push) Has been cancelled
CultMesh portability and performance / windows-latest one-command multiplayer onboarding (push) Has been cancelled
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (push) Has been cancelled
CultMesh portability and performance / windows-latest reactive semantics and leases (push) Has been cancelled
Some checks failed
CultMesh portability and performance / ubuntu-latest one-command multiplayer onboarding (push) Has been cancelled
CultMesh portability and performance / windows-latest one-command multiplayer onboarding (push) Has been cancelled
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (push) Has been cancelled
CultMesh portability and performance / windows-latest reactive semantics and leases (push) Has been cancelled
Brings codex/cultmath-hlsl-parity (d4c43ed) into the Cut 6 branch so Aetheria's
CultMath adoption and the CultCache Studio land on one line.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
1b95dd67ee
53 changed files with 5403 additions and 216 deletions
|
|
@ -16,7 +16,9 @@ shader semantics.
|
||||||
- `float2`, `float3`, `float4`
|
- `float2`, `float3`, `float4`
|
||||||
- component-wise arithmetic
|
- component-wise arithmetic
|
||||||
- scalar-to-vector splats
|
- scalar-to-vector splats
|
||||||
- swizzles for common lanes
|
- every HLSL swizzle (`xyzw` and `rgba`, repeats readable, non-repeating
|
||||||
|
masks writable) and every HLSL mixed constructor, generated by
|
||||||
|
`tools/generate-swizzles.ps1`
|
||||||
- conversions to and from `System.Numerics`
|
- conversions to and from `System.Numerics`
|
||||||
- `Random`, a compact deterministic XORShift stream with scalar and vector draws
|
- `Random`, a compact deterministic XORShift stream with scalar and vector draws
|
||||||
- `BatchMath` SoA kernels over `Span<float>` / `ReadOnlySpan<float>` using
|
- `BatchMath` SoA kernels over `Span<float>` / `ReadOnlySpan<float>` using
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
# Third-Party Notices
|
# Third-Party Notices
|
||||||
|
|
||||||
## 2D Simplex Noise
|
## 2D and 3D Simplex Noise
|
||||||
|
|
||||||
`math.simplex_noise` and `cultmath_simplex_noise` port the textureless 2D
|
`math.snoise` and `cultmath_snoise` port the textureless 2D and 3D
|
||||||
simplex-noise function by Ian McEwan and Ashima Arts, as distributed by Stefan
|
simplex-noise functions by Ian McEwan and Ashima Arts, as distributed by Stefan
|
||||||
Gustavson's `webgl-noise` project and Unity Mathematics.
|
Gustavson's `webgl-noise` project and Unity Mathematics.
|
||||||
|
|
||||||
- Copyright 2011 Ashima Arts.
|
- Copyright 2011 Ashima Arts.
|
||||||
|
|
|
||||||
|
|
@ -3,22 +3,136 @@
|
||||||
CultMath exists to make renderer math readable in every place it needs to live:
|
CultMath exists to make renderer math readable in every place it needs to live:
|
||||||
Rust hot kernels, C# host code, and HLSL shader code.
|
Rust hot kernels, C# host code, and HLSL shader code.
|
||||||
|
|
||||||
|
## Parity Target: HLSL
|
||||||
|
|
||||||
|
HLSL is the semantic authority for every type and intrinsic HLSL defines. The
|
||||||
|
goal is source compatibility: HLSL-shaped math compiles as C# under
|
||||||
|
`using static CultMath.math;`. C# forces float literal suffixes (`0.5f`); the
|
||||||
|
rest of the spelling should survive the move.
|
||||||
|
|
||||||
|
That makes these HLSL rules, not options:
|
||||||
|
|
||||||
|
- Every swizzle exists. `float2/3/4`, `int2/3/4`, and `bool2/3/4` expose every
|
||||||
|
read swizzle of length 2 to 4, repeats included (`v.wwxy`), in both `xyzw` and
|
||||||
|
`rgba` naming (never mixed), plus single-component `r/g/b/a`. `double2/3` get
|
||||||
|
lengths 2 and 3 only, because there is no `double4`. Components and swizzles are
|
||||||
|
writable: `v.xz = f2;`, `c.rgb *= 0.5f;`, and `v[1] = s;` work. Setters exist
|
||||||
|
only for non-repeating masks, because HLSL rejects `v.xx = ...`.
|
||||||
|
- Every HLSL mixed constructor exists, as a struct constructor and as a `math`
|
||||||
|
function: `float4(float2, float2)`, `float4(float, float2, float)`,
|
||||||
|
`float3(float, float2)`, the copy form `float3(float3)`, and the rest, for the
|
||||||
|
float, int, and bool families (double gets constructors only; a `math.double3`
|
||||||
|
function would hide the `double3` type name).
|
||||||
|
- Swizzles and mixed constructors are generated. `tools/generate-swizzles.ps1`
|
||||||
|
writes the checked-in `src/CultMath/Swizzles.g.cs` (partial structs plus a
|
||||||
|
partial `math`). Edit the generator, rerun it, and commit both; never edit the
|
||||||
|
output by hand.
|
||||||
|
- Comparison operators (`<`, `>`, `<=`, `>=`, `==`, `!=`) are component-wise and
|
||||||
|
return `bool2`/`bool3`/`bool4`. Reduce them with `any` and `all`.
|
||||||
|
`Equals`/`GetHashCode` stay value-based so vectors still work as dictionary
|
||||||
|
keys.
|
||||||
|
- `select(condition, whenTrue, whenFalse)` uses HLSL's argument order.
|
||||||
|
- Matrices are row-major: constructors take rows, `m[i]` returns a reference to
|
||||||
|
row i (so `m[1][2] = s;` writes one element), `_mRC` is row R column C, and
|
||||||
|
products go through `mul` (`mul(m, v)` treats `v` as a column, `mul(v, m)` as a
|
||||||
|
row). `*` on matrices would be component-wise in HLSL, so CultMath does not
|
||||||
|
define it.
|
||||||
|
- `m[r][c] = v` writes through locals, array elements, fields of classes, and
|
||||||
|
ref locals. Through a readonly field, an `in` parameter, a property
|
||||||
|
(auto-properties included), or a static readonly value such as
|
||||||
|
`float3x3.identity`, C# takes a defensive copy: the write compiles and is
|
||||||
|
silently lost. Copy the matrix to a local, write to it, and assign it back.
|
||||||
|
- Scalar/vector arithmetic and comparisons have explicit overloads on both
|
||||||
|
sides rather than leaning on implicit scalar splat.
|
||||||
|
- Intrinsics return HLSL's types and edge cases: `sign` returns `int`/`intN`
|
||||||
|
(NaN gives 0); `step(y, x)` is `x < y ? 0 : 1` (NaN gives 1), which is how dxc
|
||||||
|
lowers it, in C# and in the HLSL mirror; float and double `min` and `max`, and
|
||||||
|
float `clamp` and `saturate`, follow the DXIL `FMin`/`FMax`/`Saturate` operations dxc emits for
|
||||||
|
them: `min(a, b)` is `a < b ? a : b`, `max(a, b)` is `a >= b ? a : b`, a NaN
|
||||||
|
operand returns the other (so `saturate(NaN)` is 0), `clamp(x, a, b)` is
|
||||||
|
`min(max(x, a), b)`, and `saturate(x)` is `min(1, max(0, x))`, which maps -0
|
||||||
|
to +0; `any`/`all` accept numeric vectors (a component is true when it is
|
||||||
|
not 0); `min`, `max`, and `abs` on int vectors return int vectors.
|
||||||
|
|
||||||
|
dxc marks float arithmetic and compares `fast` (no NaNs) unless a value is
|
||||||
|
`precise`, so drivers may optimize NaN handling away and NaN results on real
|
||||||
|
GPUs are not guaranteed. CultMath defines the CPU result by dxc's lowering and
|
||||||
|
the DXIL operation spec (DirectXShaderCompiler `docs/DXIL.rst`).
|
||||||
|
|
||||||
|
NaN does not reliably propagate through CultMath, just as it does not on the
|
||||||
|
GPU. Because `min`, `max`, and `saturate` return the non-NaN operand, some
|
||||||
|
functions turn NaN or infinity into numbers: `smoothstep(a, a, a)` is 0;
|
||||||
|
infinite inputs to `smoothstep` and the intercept helpers give numbers; the
|
||||||
|
bezier, `smoothstep01`, and `smootherstep` functions return finite values for
|
||||||
|
NaN input; and `normalize((NaN, 2.41))` is `(NaN, 2.41e20)`, because the NaN
|
||||||
|
length is clamped to the epsilon and the other component is divided by it.
|
||||||
|
Callers that must detect bad data should check `isnan` before calling these
|
||||||
|
functions.
|
||||||
|
|
||||||
|
Deferred until a consumer needs them: `float4x4`, `transpose`, `determinant`,
|
||||||
|
and HLSL's one-based `_11`..`_33` element names.
|
||||||
|
|
||||||
|
### HLSL Target: Source Transformations
|
||||||
|
|
||||||
|
`HlslSourceCompatibilityTests` compiles the real `shaders/CultMath.hlsl` with
|
||||||
|
Roslyn against CultMath. These transformations are the complete list; if a body
|
||||||
|
needs anything else, the test fails:
|
||||||
|
|
||||||
|
1. Include guards (`#ifndef`/`#define`/`#endif`) are dropped.
|
||||||
|
2. Functions taking `Texture2D`/`SamplerState` are dropped; GPU resources have
|
||||||
|
no CultMath analog.
|
||||||
|
3. File-scope `static const` becomes `const`.
|
||||||
|
4. Floating literals gain `f` (`0.5` becomes `0.5f`).
|
||||||
|
5. The file body is wrapped in a class, since C# has no free functions; HLSL
|
||||||
|
functions become private instance methods.
|
||||||
|
|
||||||
|
HLSL constructs that stay outside the target and must not appear in the mirror:
|
||||||
|
`const` locals of vector type (C# `const` is primitives only; write a plain
|
||||||
|
local) and writes through a swizzle component (`v.xy.x = s`, which C# rejects as
|
||||||
|
modifying a temporary).
|
||||||
|
|
||||||
|
The HLSL side is checked too: `tools/get-dxc.ps1` fetches Microsoft's
|
||||||
|
DirectXShaderCompiler, and
|
||||||
|
`dxc -T lib_6_3 -HV 2021 shaders/CultMath.hlsl` must compile cleanly.
|
||||||
|
|
||||||
|
The mirror test compares bit patterns (any NaN equals any NaN; -0 differs from
|
||||||
|
0). It proves the text of `CultMath.hlsl` computes the same float32 results as
|
||||||
|
C# `math` on the CPU, not that a GPU agrees: driver `sin` precision alone makes
|
||||||
|
`cultmath_hash` and value noise differ bit for bit on hardware. The mirror is
|
||||||
|
compiled under `using static CultMath.math;`, so its intrinsics are C# `math`
|
||||||
|
itself: the test proves the composition in the text, not the intrinsic rules,
|
||||||
|
which only `HlslSemanticsTests` pins.
|
||||||
|
|
||||||
|
Where HLSL is silent, CultMath keeps its own decisions and does not defer to
|
||||||
|
Unity.Mathematics: `normalize` is safe (divides by `max(length, 1e-20)`),
|
||||||
|
`hash` returns float, and `Random` is CultMath's own xorshift32. Engine-shaped
|
||||||
|
helpers that HLSL lacks (`float2x2.Rotate`, `float3x3.Euler`, `quaternion`,
|
||||||
|
`snoise`) enter only when a consumer needs them, with their semantics stated in
|
||||||
|
code.
|
||||||
|
|
||||||
|
Known C# friction: under `using static CultMath.math;` the constructor
|
||||||
|
functions (`float3(...)`, `float3x3(...)`) hide the type names in member access,
|
||||||
|
so static members need a qualified type (`CultMath.float3x3.Euler(...)`).
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
|
|
||||||
- Keep type names HLSL-shaped: `float2`, `float3`, `float4`, and `math`.
|
- Keep type names HLSL-shaped: `float2`, `float3`, `float4`, `float3x3`, and `math`.
|
||||||
- Keep Rust as the owner of native hot-path kernels and parity fixtures.
|
- Keep Rust as the owner of native hot-path kernels and parity fixtures.
|
||||||
- Keep C#, shader, and scripting surfaces as runtime wrappers, not competing
|
- Keep C#, shader, and scripting surfaces as runtime wrappers, not competing
|
||||||
math authorities.
|
math authorities.
|
||||||
- Keep `shaders/CultMath.hlsl` as the canonical shader mirror for shared
|
- Keep `shaders/CultMath.hlsl` as the canonical shader mirror for shared
|
||||||
intrinsics. Project-local shader helpers should move here once more than one
|
CultMath functions HLSL does not already provide (HLSL intrinsics such as
|
||||||
organ needs them or once CPU/GPU parity matters.
|
`mul`, `any`, and `log` need no mirror). Project-local shader helpers should
|
||||||
|
move here once more than one organ needs them or once CPU/GPU parity matters.
|
||||||
- Implement public shader semantics directly; do not copy Unity.Mathematics.
|
- Implement public shader semantics directly; do not copy Unity.Mathematics.
|
||||||
- Prefer small immutable value types.
|
- Prefer plain mutable value types with public component fields, as HLSL does.
|
||||||
- Prefer component-wise overloads over clever generic machinery until the API
|
- Prefer component-wise overloads over clever generic machinery until the API
|
||||||
earns that complexity.
|
earns that complexity.
|
||||||
- Keep the surface boring and exact. Noise primitives are allowed when they are
|
- Keep the surface boring and exact. Fill what a consumer uses, guided by HLSL
|
||||||
source-grounded, shader-shaped, and shared by more than one project. Matrices,
|
semantics; noise primitives, matrices, quaternions, and packing helpers need an
|
||||||
quaternions, and packing helpers still need an owner before they enter.
|
owner before they enter.
|
||||||
|
- Keep the core assembly engine-free. Unity conversions live in the Unity
|
||||||
|
package's `CultMath.UnityBridge` assembly.
|
||||||
|
|
||||||
## Non-Goals
|
## Non-Goals
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,10 @@ if (Test-Path -LiteralPath $outputRoot) {
|
||||||
Remove-Item -LiteralPath $outputRoot -Recurse -Force
|
Remove-Item -LiteralPath $outputRoot -Recurse -Force
|
||||||
}
|
}
|
||||||
|
|
||||||
dotnet build $projectPath -c $Configuration -f netstandard2.1 -o $buildRoot `
|
# The tracked DLL and pdb are committed beside their source, so neither may name a commit: Source Link
|
||||||
--disable-build-servers -p:UseSharedCompilation=false -p:ContinuousIntegrationBuild=true -m:1
|
# writes the commit SHA into the pdb, and the DLL carries that pdb's content id.
|
||||||
|
dotnet build $projectPath -c $Configuration -f netstandard2.1 -o $buildRoot --no-incremental `
|
||||||
|
--disable-build-servers -p:UseSharedCompilation=false -p:ContinuousIntegrationBuild=true -p:EnableSourceLink=false -m:1
|
||||||
if ($LASTEXITCODE -ne 0) {
|
if ($LASTEXITCODE -ne 0) {
|
||||||
throw "CultMath Unity assembly build failed with exit code $LASTEXITCODE"
|
throw "CultMath Unity assembly build failed with exit code $LASTEXITCODE"
|
||||||
}
|
}
|
||||||
|
|
@ -52,6 +54,10 @@ $requiredTemplateFiles = @(
|
||||||
"Runtime\Plugins\CultMath.dll.meta",
|
"Runtime\Plugins\CultMath.dll.meta",
|
||||||
"Runtime\Plugins\CultMath.pdb",
|
"Runtime\Plugins\CultMath.pdb",
|
||||||
"Runtime\Plugins\CultMath.pdb.meta",
|
"Runtime\Plugins\CultMath.pdb.meta",
|
||||||
|
"Runtime\UnityBridge\CultMath.UnityBridge.asmdef",
|
||||||
|
"Runtime\UnityBridge\CultMath.UnityBridge.asmdef.meta",
|
||||||
|
"Runtime\UnityBridge\UnityConversions.cs",
|
||||||
|
"Runtime\UnityBridge\UnityConversions.cs.meta",
|
||||||
"Shaders\CultMath.hlsl",
|
"Shaders\CultMath.hlsl",
|
||||||
"Shaders\CultMath.hlsl.meta"
|
"Shaders\CultMath.hlsl.meta"
|
||||||
)
|
)
|
||||||
|
|
@ -62,7 +68,7 @@ foreach ($relativePath in $requiredTemplateFiles) {
|
||||||
}
|
}
|
||||||
|
|
||||||
$unityAssetFiles = @(Get-ChildItem -LiteralPath $templateRoot -Recurse -File | Where-Object {
|
$unityAssetFiles = @(Get-ChildItem -LiteralPath $templateRoot -Recurse -File | Where-Object {
|
||||||
$_.Extension -in @(".asmdef", ".dll", ".pdb", ".hlsl")
|
$_.Extension -in @(".asmdef", ".cs", ".dll", ".pdb", ".hlsl")
|
||||||
})
|
})
|
||||||
$missingAssetMetas = @($unityAssetFiles | Where-Object { -not (Test-Path -LiteralPath ($_.FullName + ".meta")) })
|
$missingAssetMetas = @($unityAssetFiles | Where-Object { -not (Test-Path -LiteralPath ($_.FullName + ".meta")) })
|
||||||
$missingDirectoryMetas = @(Get-ChildItem -LiteralPath $templateRoot -Recurse -Directory | Where-Object {
|
$missingDirectoryMetas = @(Get-ChildItem -LiteralPath $templateRoot -Recurse -Directory | Where-Object {
|
||||||
|
|
@ -78,9 +84,18 @@ $pluginMeta = Get-Content -LiteralPath ($trackedAssembly + ".meta") -Raw
|
||||||
if ($pluginMeta -notmatch '(?m)^\s*isExplicitlyReferenced:\s*0\s*$') {
|
if ($pluginMeta -notmatch '(?m)^\s*isExplicitlyReferenced:\s*0\s*$') {
|
||||||
throw "CultMath.dll must be auto-referenced; explicit-only plugins do not reach dependent Unity asmdefs"
|
throw "CultMath.dll must be auto-referenced; explicit-only plugins do not reach dependent Unity asmdefs"
|
||||||
}
|
}
|
||||||
$facadeDefinitions = @(Get-ChildItem -LiteralPath $templateRoot -Recurse -File -Filter "*.asmdef")
|
# The precompiled DLL is the only authority for CultMath's numeric types. The one
|
||||||
if ($facadeDefinitions.Count -ne 0) {
|
# compiled assembly allowed beside it is the UnityEngine conversion bridge, which
|
||||||
throw "CultMath's precompiled DLL is the Unity assembly authority; empty asmdef facades are forbidden"
|
# references that DLL and must not re-declare core sources.
|
||||||
|
$bridgeRoot = Join-Path $templateRoot "Runtime\UnityBridge"
|
||||||
|
$bridgeDefinition = Join-Path $bridgeRoot "CultMath.UnityBridge.asmdef"
|
||||||
|
$definitions = @(Get-ChildItem -LiteralPath $templateRoot -Recurse -File -Filter "*.asmdef")
|
||||||
|
if ($definitions.Count -ne 1 -or $definitions[0].FullName -ne $bridgeDefinition) {
|
||||||
|
throw "CultMath's Unity package may define only the Runtime\UnityBridge assembly; found: $($definitions.FullName -join ', ')"
|
||||||
|
}
|
||||||
|
$bridge = Get-Content -LiteralPath $bridgeDefinition -Raw | ConvertFrom-Json
|
||||||
|
if ($bridge.noEngineReferences -or -not $bridge.overrideReferences -or @($bridge.precompiledReferences) -notcontains "CultMath.dll") {
|
||||||
|
throw "CultMath.UnityBridge must reference UnityEngine and the precompiled CultMath.dll"
|
||||||
}
|
}
|
||||||
$expectedFileVersion = "$($manifest.version).0"
|
$expectedFileVersion = "$($manifest.version).0"
|
||||||
$trackedFileVersion = [Diagnostics.FileVersionInfo]::GetVersionInfo($trackedAssembly).FileVersion
|
$trackedFileVersion = [Diagnostics.FileVersionInfo]::GetVersionInfo($trackedAssembly).FileVersion
|
||||||
|
|
@ -98,22 +113,47 @@ foreach ($pair in @(@($freshAssembly, $trackedAssembly), @($freshSymbols, $track
|
||||||
}
|
}
|
||||||
|
|
||||||
$trackedSourceFiles = @(Get-ChildItem -LiteralPath $templateRoot -Recurse -File -Filter "*.cs")
|
$trackedSourceFiles = @(Get-ChildItem -LiteralPath $templateRoot -Recurse -File -Filter "*.cs")
|
||||||
if ($trackedSourceFiles.Count -ne 0) {
|
$straySources = @($trackedSourceFiles | Where-Object { $_.DirectoryName -ne $bridgeRoot })
|
||||||
throw "CultMath Unity package must not compile repository C# sources: $($trackedSourceFiles.FullName -join ', ')"
|
if ($trackedSourceFiles.Count -eq 0 -or $straySources.Count -ne 0) {
|
||||||
|
throw "CultMath Unity package may compile only bridge sources under Runtime\UnityBridge: $($straySources.FullName -join ', ')"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check what the bridge declares, not what its files are called: it may declare types only in
|
||||||
|
# CultMath.UnityBridge and never a type name the core CultMath sources declare.
|
||||||
|
$declarationPattern = '\b(?:struct|class|enum|interface|record|delegate\s+[\w<>.,\s]+?)\s+(\w+)'
|
||||||
|
function Get-CodeWithoutComments([string]$path) {
|
||||||
|
$text = Get-Content -LiteralPath $path -Raw
|
||||||
|
return [regex]::Replace($text, '(?s)/\*.*?\*/|//[^\r\n]*', '')
|
||||||
|
}
|
||||||
|
$coreTypeNames = @(Get-ChildItem -LiteralPath (Join-Path $repoRoot "src\CultMath") -File -Filter "*.cs" | ForEach-Object {
|
||||||
|
[regex]::Matches((Get-CodeWithoutComments $_.FullName), $declarationPattern) | ForEach-Object { $_.Groups[1].Value }
|
||||||
|
} | Sort-Object -Unique)
|
||||||
|
if ($coreTypeNames -notcontains "float3" -or $coreTypeNames -notcontains "math") {
|
||||||
|
throw "Could not read CultMath core type declarations; the bridge check would be vacuous"
|
||||||
|
}
|
||||||
|
foreach ($source in $trackedSourceFiles) {
|
||||||
|
$code = Get-CodeWithoutComments $source.FullName
|
||||||
|
$redeclared = @([regex]::Matches($code, $declarationPattern) | ForEach-Object { $_.Groups[1].Value } | Where-Object { $coreTypeNames -contains $_ })
|
||||||
|
$foreignNamespaces = @([regex]::Matches($code, '\bnamespace\s+([\w.]+)') | ForEach-Object { $_.Groups[1].Value } | Where-Object { $_ -ne "CultMath.UnityBridge" })
|
||||||
|
if ($redeclared.Count -ne 0 -or $foreignNamespaces.Count -ne 0) {
|
||||||
|
throw "CultMath.UnityBridge source $($source.Name) re-declares core types [$($redeclared -join ', ')] or uses namespaces [$($foreignNamespaces -join ', ')]; the precompiled DLL owns CultMath's types"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
New-Item -ItemType Directory -Force -Path $outputRoot | Out-Null
|
New-Item -ItemType Directory -Force -Path $outputRoot | Out-Null
|
||||||
Copy-Item -Path (Join-Path $templateRoot "*") -Destination $outputRoot -Recurse -Force
|
Copy-Item -Path (Join-Path $templateRoot "*") -Destination $outputRoot -Recurse -Force
|
||||||
|
|
||||||
$stagedAssemblies = @(Get-ChildItem -LiteralPath (Join-Path $outputRoot "Runtime\Plugins") -Filter "*.dll")
|
$stagedAssemblies = @(Get-ChildItem -LiteralPath (Join-Path $outputRoot "Runtime\Plugins") -Filter "*.dll")
|
||||||
|
$stagedBridgeRoot = Join-Path $outputRoot "Runtime\UnityBridge"
|
||||||
$stagedSources = @(Get-ChildItem -LiteralPath $outputRoot -Recurse -File -Filter "*.cs")
|
$stagedSources = @(Get-ChildItem -LiteralPath $outputRoot -Recurse -File -Filter "*.cs")
|
||||||
$stagedFacades = @(Get-ChildItem -LiteralPath $outputRoot -Recurse -File -Filter "*.asmdef")
|
$stagedDefinitions = @(Get-ChildItem -LiteralPath $outputRoot -Recurse -File -Filter "*.asmdef")
|
||||||
if ($stagedAssemblies.Count -ne 1 -or $stagedAssemblies[0].Name -ne "CultMath.dll" -or $stagedSources.Count -ne 0 -or $stagedFacades.Count -ne 0) {
|
$stagedStraySources = @($stagedSources | Where-Object { $_.DirectoryName -ne $stagedBridgeRoot })
|
||||||
throw "CultMath package must contain one owned auto-referenced DLL, no C# source files, and no asmdef facade"
|
if ($stagedAssemblies.Count -ne 1 -or $stagedAssemblies[0].Name -ne "CultMath.dll" -or $stagedStraySources.Count -ne 0 -or $stagedDefinitions.Count -ne 1) {
|
||||||
|
throw "CultMath package must contain one owned auto-referenced DLL and only the UnityBridge assembly as source"
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "CultMath Unity package: $outputRoot"
|
Write-Host "CultMath Unity package: $outputRoot"
|
||||||
Write-Host "Package: $($manifest.name)@$($manifest.version)"
|
Write-Host "Package: $($manifest.name)@$($manifest.version)"
|
||||||
Write-Host "Managed assemblies: $($stagedAssemblies.Count)"
|
Write-Host "Managed assemblies: $($stagedAssemblies.Count)"
|
||||||
Write-Host "C# source files: $($stagedSources.Count)"
|
Write-Host "Bridge C# source files: $($stagedSources.Count)"
|
||||||
Write-Host "Asmdef facades: $($stagedFacades.Count)"
|
Write-Host "Asmdef definitions: $($stagedDefinitions.Count)"
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,11 @@ float2 cultmath_clamp(float2 value, float2 minimum, float2 maximum) { return min
|
||||||
float3 cultmath_clamp(float3 value, float3 minimum, float3 maximum) { return min(max(value, minimum), maximum); }
|
float3 cultmath_clamp(float3 value, float3 minimum, float3 maximum) { return min(max(value, minimum), maximum); }
|
||||||
float4 cultmath_clamp(float4 value, float4 minimum, float4 maximum) { return min(max(value, minimum), maximum); }
|
float4 cultmath_clamp(float4 value, float4 minimum, float4 maximum) { return min(max(value, minimum), maximum); }
|
||||||
|
|
||||||
float cultmath_saturate(float value) { return cultmath_clamp(value, 0.0, 1.0); }
|
// DXIL Saturate is FMin(1, FMax(0, x)); dxc lowers min/max to the same FMin/FMax, NaN rules included.
|
||||||
float2 cultmath_saturate(float2 value) { return cultmath_clamp(value, float2(0.0, 0.0), float2(1.0, 1.0)); }
|
float cultmath_saturate(float value) { return min(1.0, max(0.0, value)); }
|
||||||
float3 cultmath_saturate(float3 value) { return cultmath_clamp(value, float3(0.0, 0.0, 0.0), float3(1.0, 1.0, 1.0)); }
|
float2 cultmath_saturate(float2 value) { return min(float2(1.0, 1.0), max(float2(0.0, 0.0), value)); }
|
||||||
float4 cultmath_saturate(float4 value) { return cultmath_clamp(value, float4(0.0, 0.0, 0.0, 0.0), float4(1.0, 1.0, 1.0, 1.0)); }
|
float3 cultmath_saturate(float3 value) { return min(float3(1.0, 1.0, 1.0), max(float3(0.0, 0.0, 0.0), value)); }
|
||||||
|
float4 cultmath_saturate(float4 value) { return min(float4(1.0, 1.0, 1.0, 1.0), max(float4(0.0, 0.0, 0.0, 0.0), value)); }
|
||||||
|
|
||||||
float cultmath_lerp(float start, float end, float amount) { return start + (end - start) * amount; }
|
float cultmath_lerp(float start, float end, float amount) { return start + (end - start) * amount; }
|
||||||
float2 cultmath_lerp(float2 start, float2 end, float2 amount) { return start + (end - start) * amount; }
|
float2 cultmath_lerp(float2 start, float2 end, float2 amount) { return start + (end - start) * amount; }
|
||||||
|
|
@ -73,13 +74,66 @@ float cultmath_smootherstep(float value)
|
||||||
return value * value * value * (value * (value * 6.0 - 15.0) + 10.0);
|
return value * value * value * (value * (value * 6.0 - 15.0) + 10.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
float2 cultmath_simplex_mod289(float2 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
|
float2 cultmath_snoise_mod289(float2 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
|
||||||
float3 cultmath_simplex_mod289(float3 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
|
float3 cultmath_snoise_mod289(float3 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
|
||||||
float3 cultmath_simplex_permute(float3 value) { return cultmath_simplex_mod289(((value * 34.0) + 1.0) * value); }
|
float4 cultmath_snoise_mod289(float4 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
|
||||||
|
float3 cultmath_snoise_permute(float3 value) { return cultmath_snoise_mod289(((value * 34.0) + 1.0) * value); }
|
||||||
|
float4 cultmath_snoise_permute(float4 value) { return cultmath_snoise_mod289(((value * 34.0) + 1.0) * value); }
|
||||||
|
|
||||||
float cultmath_simplex_noise(float2 value)
|
// Ashima Arts / Ian McEwan 3D simplex noise (MIT), same float32 evaluation
|
||||||
|
// order as the C# math.snoise(float3) mirror.
|
||||||
|
float cultmath_snoise(float3 value)
|
||||||
{
|
{
|
||||||
const float4 c = float4(
|
float2 c = float2(1.0 / 6.0, 1.0 / 3.0);
|
||||||
|
float3 i = floor(value + dot(value, c.yyy));
|
||||||
|
float3 x0 = value - i + dot(i, c.xxx);
|
||||||
|
float3 g = step(x0.yzx, x0.xyz);
|
||||||
|
float3 l = 1.0 - g;
|
||||||
|
float3 i1 = min(g.xyz, l.zxy);
|
||||||
|
float3 i2 = max(g.xyz, l.zxy);
|
||||||
|
float3 x1 = x0 - i1 + c.xxx;
|
||||||
|
float3 x2 = x0 - i2 + c.yyy;
|
||||||
|
float3 x3 = x0 - 0.5;
|
||||||
|
|
||||||
|
i = cultmath_snoise_mod289(i);
|
||||||
|
float4 p = cultmath_snoise_permute(cultmath_snoise_permute(cultmath_snoise_permute(
|
||||||
|
i.z + float4(0.0, i1.z, i2.z, 1.0)) + i.y + float4(0.0, i1.y, i2.y, 1.0)) + i.x + float4(0.0, i1.x, i2.x, 1.0));
|
||||||
|
|
||||||
|
const float n_ = 0.142857142857;
|
||||||
|
float3 ns = float3(2.0 * n_, 0.5 * n_ - 1.0, n_);
|
||||||
|
float4 j = p - 49.0 * floor(p * ns.z * ns.z);
|
||||||
|
float4 x_ = floor(j * ns.z);
|
||||||
|
float4 y_ = floor(j - 7.0 * x_);
|
||||||
|
float4 x = x_ * ns.x + ns.yyyy;
|
||||||
|
float4 y = y_ * ns.x + ns.yyyy;
|
||||||
|
float4 h = 1.0 - abs(x) - abs(y);
|
||||||
|
|
||||||
|
float4 b0 = float4(x.xy, y.xy);
|
||||||
|
float4 b1 = float4(x.zw, y.zw);
|
||||||
|
float4 s0 = floor(b0) * 2.0 + 1.0;
|
||||||
|
float4 s1 = floor(b1) * 2.0 + 1.0;
|
||||||
|
float4 sh = -step(h, 0.0);
|
||||||
|
float4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
|
||||||
|
float4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
|
||||||
|
|
||||||
|
float3 p0 = float3(a0.xy, h.x);
|
||||||
|
float3 p1 = float3(a0.zw, h.y);
|
||||||
|
float3 p2 = float3(a1.xy, h.z);
|
||||||
|
float3 p3 = float3(a1.zw, h.w);
|
||||||
|
float4 norm = 1.79284291400159 - 0.85373472095314 * float4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3));
|
||||||
|
p0 *= norm.x;
|
||||||
|
p1 *= norm.y;
|
||||||
|
p2 *= norm.z;
|
||||||
|
p3 *= norm.w;
|
||||||
|
|
||||||
|
float4 m = max(0.6 - float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
|
||||||
|
m = m * m;
|
||||||
|
return 42.0 * dot(m * m, float4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
|
||||||
|
}
|
||||||
|
|
||||||
|
float cultmath_snoise(float2 value)
|
||||||
|
{
|
||||||
|
float4 c = float4(
|
||||||
0.211324865405187,
|
0.211324865405187,
|
||||||
0.366025403784439,
|
0.366025403784439,
|
||||||
-0.577350269189626,
|
-0.577350269189626,
|
||||||
|
|
@ -89,9 +143,9 @@ float cultmath_simplex_noise(float2 value)
|
||||||
float2 i1 = x0.x > x0.y ? float2(1.0, 0.0) : float2(0.0, 1.0);
|
float2 i1 = x0.x > x0.y ? float2(1.0, 0.0) : float2(0.0, 1.0);
|
||||||
float4 x12 = float4(x0.x + c.x - i1.x, x0.y + c.x - i1.y, x0.x + c.z, x0.y + c.z);
|
float4 x12 = float4(x0.x + c.x - i1.x, x0.y + c.x - i1.y, x0.x + c.z, x0.y + c.z);
|
||||||
|
|
||||||
i = cultmath_simplex_mod289(i);
|
i = cultmath_snoise_mod289(i);
|
||||||
float3 p = cultmath_simplex_permute(
|
float3 p = cultmath_snoise_permute(
|
||||||
cultmath_simplex_permute(i.y + float3(0.0, i1.y, 1.0)) + i.x + float3(0.0, i1.x, 1.0));
|
cultmath_snoise_permute(i.y + float3(0.0, i1.y, 1.0)) + i.x + float3(0.0, i1.x, 1.0));
|
||||||
float3 m = max(0.5 - float3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
|
float3 m = max(0.5 - float3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
|
||||||
m *= m;
|
m *= m;
|
||||||
m *= m;
|
m *= m;
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,14 @@
|
||||||
<RootNamespace>CultMath</RootNamespace>
|
<RootNamespace>CultMath</RootNamespace>
|
||||||
<AssemblyName>CultMath</AssemblyName>
|
<AssemblyName>CultMath</AssemblyName>
|
||||||
<PackageId>GameCult.Math</PackageId>
|
<PackageId>GameCult.Math</PackageId>
|
||||||
<Version>0.1.2</Version>
|
<Version>0.2.0</Version>
|
||||||
<Description>Clean-room C# math shaped for HLSL-style renderer code.</Description>
|
<Description>Clean-room C# math shaped for HLSL-style renderer code.</Description>
|
||||||
<Authors>GameCult</Authors>
|
<Authors>GameCult</Authors>
|
||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
<NoWarn>$(NoWarn);CS8981</NoWarn>
|
<NoWarn>$(NoWarn);CS8981</NoWarn>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<!-- The Unity DLL is committed beside its source, so its bytes must not name a commit. -->
|
||||||
|
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -58,5 +58,9 @@ public struct Random
|
||||||
return new float2(math.cos(angle), math.sin(angle));
|
return new float2(math.cos(angle), math.sin(angle));
|
||||||
}
|
}
|
||||||
|
|
||||||
public float3 NextFloat3(float3 min, float3 max) => min + new float3(NextFloat(), NextFloat(), NextFloat()) * (max - min);
|
public float3 NextFloat3() => new(NextFloat(), NextFloat(), NextFloat());
|
||||||
|
|
||||||
|
public float3 NextFloat3(float3 max) => NextFloat3() * max;
|
||||||
|
|
||||||
|
public float3 NextFloat3(float3 min, float3 max) => min + NextFloat3() * (max - min);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3137
packages/cultmath/src/CultMath/Swizzles.g.cs
Normal file
3137
packages/cultmath/src/CultMath/Swizzles.g.cs
Normal file
File diff suppressed because it is too large
Load diff
2
packages/cultmath/src/CultMath/Swizzles.g.cs.meta
Normal file
2
packages/cultmath/src/CultMath/Swizzles.g.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e89d6089b354459788eb6f0faebfd6c0
|
||||||
10
packages/cultmath/src/CultMath/UnscopedRefAttribute.cs
Normal file
10
packages/cultmath/src/CultMath/UnscopedRefAttribute.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
#if !NET7_0_OR_GREATER
|
||||||
|
namespace System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
// Compiler-recognized polyfill so netstandard2.1 builds can return refs into a struct's
|
||||||
|
// own fields (the matrix row indexer). net7.0+ supplies the real attribute.
|
||||||
|
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
|
||||||
|
internal sealed class UnscopedRefAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3f204000f9854517a8dd754e2d9dc975
|
||||||
|
|
@ -2,17 +2,50 @@ using System;
|
||||||
|
|
||||||
namespace CultMath;
|
namespace CultMath;
|
||||||
|
|
||||||
public record struct bool2(bool x, bool y)
|
public partial struct bool2 : IEquatable<bool2>
|
||||||
{
|
{
|
||||||
public static readonly bool2 @false = new(false, false);
|
public static readonly bool2 @false = new(false, false);
|
||||||
public static readonly bool2 @true = new(true, true);
|
public static readonly bool2 @true = new(true, true);
|
||||||
|
|
||||||
public bool this[int index] => index switch
|
public bool x;
|
||||||
|
public bool y;
|
||||||
|
|
||||||
|
public bool2(bool x, bool y)
|
||||||
{
|
{
|
||||||
0 => x,
|
this.x = x;
|
||||||
1 => y,
|
this.y = y;
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
}
|
||||||
};
|
|
||||||
|
public bool this[int index]
|
||||||
|
{
|
||||||
|
readonly get => index switch
|
||||||
|
{
|
||||||
|
0 => x,
|
||||||
|
1 => y,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||||
|
};
|
||||||
|
set
|
||||||
|
{
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case 0: x = value; break;
|
||||||
|
case 1: y = value; break;
|
||||||
|
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static implicit operator bool2(bool value) => new(value, value);
|
public static implicit operator bool2(bool value) => new(value, value);
|
||||||
|
|
||||||
|
public static bool2 operator !(bool2 value) => new(!value.x, !value.y);
|
||||||
|
public static bool2 operator &(bool2 left, bool2 right) => new(left.x & right.x, left.y & right.y);
|
||||||
|
public static bool2 operator |(bool2 left, bool2 right) => new(left.x | right.x, left.y | right.y);
|
||||||
|
public static bool2 operator ^(bool2 left, bool2 right) => new(left.x ^ right.x, left.y ^ right.y);
|
||||||
|
public static bool2 operator ==(bool2 left, bool2 right) => new(left.x == right.x, left.y == right.y);
|
||||||
|
public static bool2 operator !=(bool2 left, bool2 right) => new(left.x != right.x, left.y != right.y);
|
||||||
|
|
||||||
|
public readonly bool Equals(bool2 other) => x == other.x && y == other.y;
|
||||||
|
public override readonly bool Equals(object? obj) => obj is bool2 other && Equals(other);
|
||||||
|
public override readonly int GetHashCode() => HashCode.Combine(x, y);
|
||||||
|
public override readonly string ToString() => $"bool2({x}, {y})";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
55
packages/cultmath/src/CultMath/bool3.cs
Normal file
55
packages/cultmath/src/CultMath/bool3.cs
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace CultMath;
|
||||||
|
|
||||||
|
public partial struct bool3 : IEquatable<bool3>
|
||||||
|
{
|
||||||
|
public static readonly bool3 @false = new(false, false, false);
|
||||||
|
public static readonly bool3 @true = new(true, true, true);
|
||||||
|
|
||||||
|
public bool x;
|
||||||
|
public bool y;
|
||||||
|
public bool z;
|
||||||
|
|
||||||
|
public bool3(bool x, bool y, bool z)
|
||||||
|
{
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.z = z;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool this[int index]
|
||||||
|
{
|
||||||
|
readonly get => index switch
|
||||||
|
{
|
||||||
|
0 => x,
|
||||||
|
1 => y,
|
||||||
|
2 => z,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||||
|
};
|
||||||
|
set
|
||||||
|
{
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case 0: x = value; break;
|
||||||
|
case 1: y = value; break;
|
||||||
|
case 2: z = value; break;
|
||||||
|
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator bool3(bool value) => new(value, value, value);
|
||||||
|
|
||||||
|
public static bool3 operator !(bool3 value) => new(!value.x, !value.y, !value.z);
|
||||||
|
public static bool3 operator &(bool3 left, bool3 right) => new(left.x & right.x, left.y & right.y, left.z & right.z);
|
||||||
|
public static bool3 operator |(bool3 left, bool3 right) => new(left.x | right.x, left.y | right.y, left.z | right.z);
|
||||||
|
public static bool3 operator ^(bool3 left, bool3 right) => new(left.x ^ right.x, left.y ^ right.y, left.z ^ right.z);
|
||||||
|
public static bool3 operator ==(bool3 left, bool3 right) => new(left.x == right.x, left.y == right.y, left.z == right.z);
|
||||||
|
public static bool3 operator !=(bool3 left, bool3 right) => new(left.x != right.x, left.y != right.y, left.z != right.z);
|
||||||
|
|
||||||
|
public readonly bool Equals(bool3 other) => x == other.x && y == other.y && z == other.z;
|
||||||
|
public override readonly bool Equals(object? obj) => obj is bool3 other && Equals(other);
|
||||||
|
public override readonly int GetHashCode() => HashCode.Combine(x, y, z);
|
||||||
|
public override readonly string ToString() => $"bool3({x}, {y}, {z})";
|
||||||
|
}
|
||||||
2
packages/cultmath/src/CultMath/bool3.cs.meta
Normal file
2
packages/cultmath/src/CultMath/bool3.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 323ec670fc374c8ba4d87b4bf4c91f34
|
||||||
59
packages/cultmath/src/CultMath/bool4.cs
Normal file
59
packages/cultmath/src/CultMath/bool4.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace CultMath;
|
||||||
|
|
||||||
|
public partial struct bool4 : IEquatable<bool4>
|
||||||
|
{
|
||||||
|
public static readonly bool4 @false = new(false, false, false, false);
|
||||||
|
public static readonly bool4 @true = new(true, true, true, true);
|
||||||
|
|
||||||
|
public bool x;
|
||||||
|
public bool y;
|
||||||
|
public bool z;
|
||||||
|
public bool w;
|
||||||
|
|
||||||
|
public bool4(bool x, bool y, bool z, bool w)
|
||||||
|
{
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.z = z;
|
||||||
|
this.w = w;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool this[int index]
|
||||||
|
{
|
||||||
|
readonly get => index switch
|
||||||
|
{
|
||||||
|
0 => x,
|
||||||
|
1 => y,
|
||||||
|
2 => z,
|
||||||
|
3 => w,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||||
|
};
|
||||||
|
set
|
||||||
|
{
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case 0: x = value; break;
|
||||||
|
case 1: y = value; break;
|
||||||
|
case 2: z = value; break;
|
||||||
|
case 3: w = value; break;
|
||||||
|
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator bool4(bool value) => new(value, value, value, value);
|
||||||
|
|
||||||
|
public static bool4 operator !(bool4 value) => new(!value.x, !value.y, !value.z, !value.w);
|
||||||
|
public static bool4 operator &(bool4 left, bool4 right) => new(left.x & right.x, left.y & right.y, left.z & right.z, left.w & right.w);
|
||||||
|
public static bool4 operator |(bool4 left, bool4 right) => new(left.x | right.x, left.y | right.y, left.z | right.z, left.w | right.w);
|
||||||
|
public static bool4 operator ^(bool4 left, bool4 right) => new(left.x ^ right.x, left.y ^ right.y, left.z ^ right.z, left.w ^ right.w);
|
||||||
|
public static bool4 operator ==(bool4 left, bool4 right) => new(left.x == right.x, left.y == right.y, left.z == right.z, left.w == right.w);
|
||||||
|
public static bool4 operator !=(bool4 left, bool4 right) => new(left.x != right.x, left.y != right.y, left.z != right.z, left.w != right.w);
|
||||||
|
|
||||||
|
public readonly bool Equals(bool4 other) => x == other.x && y == other.y && z == other.z && w == other.w;
|
||||||
|
public override readonly bool Equals(object? obj) => obj is bool4 other && Equals(other);
|
||||||
|
public override readonly int GetHashCode() => HashCode.Combine(x, y, z, w);
|
||||||
|
public override readonly string ToString() => $"bool4({x}, {y}, {z}, {w})";
|
||||||
|
}
|
||||||
2
packages/cultmath/src/CultMath/bool4.cs.meta
Normal file
2
packages/cultmath/src/CultMath/bool4.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d38b9f70d88c459584441b85ecbfd92a
|
||||||
|
|
@ -2,17 +2,38 @@ using System;
|
||||||
|
|
||||||
namespace CultMath;
|
namespace CultMath;
|
||||||
|
|
||||||
public record struct double2(double x, double y)
|
public partial struct double2 : IEquatable<double2>
|
||||||
{
|
{
|
||||||
public static readonly double2 zero = new(0.0, 0.0);
|
public static readonly double2 zero = new(0.0, 0.0);
|
||||||
public static readonly double2 one = new(1.0, 1.0);
|
public static readonly double2 one = new(1.0, 1.0);
|
||||||
|
|
||||||
public double this[int index] => index switch
|
public double x;
|
||||||
|
public double y;
|
||||||
|
|
||||||
|
public double2(double x, double y)
|
||||||
{
|
{
|
||||||
0 => x,
|
this.x = x;
|
||||||
1 => y,
|
this.y = y;
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
}
|
||||||
};
|
|
||||||
|
public double this[int index]
|
||||||
|
{
|
||||||
|
readonly get => index switch
|
||||||
|
{
|
||||||
|
0 => x,
|
||||||
|
1 => y,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||||
|
};
|
||||||
|
set
|
||||||
|
{
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case 0: x = value; break;
|
||||||
|
case 1: y = value; break;
|
||||||
|
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static implicit operator double2(double value) => new(value, value);
|
public static implicit operator double2(double value) => new(value, value);
|
||||||
|
|
||||||
|
|
@ -22,4 +43,16 @@ public record struct double2(double x, double y)
|
||||||
public static double2 operator -(double2 left, double2 right) => new(left.x - right.x, left.y - right.y);
|
public static double2 operator -(double2 left, double2 right) => new(left.x - right.x, left.y - right.y);
|
||||||
public static double2 operator *(double2 left, double2 right) => new(left.x * right.x, left.y * right.y);
|
public static double2 operator *(double2 left, double2 right) => new(left.x * right.x, left.y * right.y);
|
||||||
public static double2 operator /(double2 left, double2 right) => new(left.x / right.x, left.y / right.y);
|
public static double2 operator /(double2 left, double2 right) => new(left.x / right.x, left.y / right.y);
|
||||||
|
|
||||||
|
public static bool2 operator <(double2 left, double2 right) => new(left.x < right.x, left.y < right.y);
|
||||||
|
public static bool2 operator >(double2 left, double2 right) => new(left.x > right.x, left.y > right.y);
|
||||||
|
public static bool2 operator <=(double2 left, double2 right) => new(left.x <= right.x, left.y <= right.y);
|
||||||
|
public static bool2 operator >=(double2 left, double2 right) => new(left.x >= right.x, left.y >= right.y);
|
||||||
|
public static bool2 operator ==(double2 left, double2 right) => new(left.x == right.x, left.y == right.y);
|
||||||
|
public static bool2 operator !=(double2 left, double2 right) => new(left.x != right.x, left.y != right.y);
|
||||||
|
|
||||||
|
public readonly bool Equals(double2 other) => x.Equals(other.x) && y.Equals(other.y);
|
||||||
|
public override readonly bool Equals(object? obj) => obj is double2 other && Equals(other);
|
||||||
|
public override readonly int GetHashCode() => HashCode.Combine(x, y);
|
||||||
|
public override readonly string ToString() => FormattableString.Invariant($"double2({x:R}, {y:R})");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,28 +2,42 @@ using System;
|
||||||
|
|
||||||
namespace CultMath;
|
namespace CultMath;
|
||||||
|
|
||||||
public record struct double3(double x, double y, double z)
|
public partial struct double3 : IEquatable<double3>
|
||||||
{
|
{
|
||||||
public static readonly double3 zero = new(0.0, 0.0, 0.0);
|
public static readonly double3 zero = new(0.0, 0.0, 0.0);
|
||||||
public static readonly double3 one = new(1.0, 1.0, 1.0);
|
public static readonly double3 one = new(1.0, 1.0, 1.0);
|
||||||
|
|
||||||
public double2 xy
|
public double x;
|
||||||
|
public double y;
|
||||||
|
public double z;
|
||||||
|
|
||||||
|
public double3(double x, double y, double z)
|
||||||
{
|
{
|
||||||
get => new(x, y);
|
this.x = x;
|
||||||
set
|
this.y = y;
|
||||||
{
|
this.z = z;
|
||||||
x = value.x;
|
|
||||||
y = value.y;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public double this[int index] => index switch
|
public double this[int index]
|
||||||
{
|
{
|
||||||
0 => x,
|
readonly get => index switch
|
||||||
1 => y,
|
{
|
||||||
2 => z,
|
0 => x,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
1 => y,
|
||||||
};
|
2 => z,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||||
|
};
|
||||||
|
set
|
||||||
|
{
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case 0: x = value; break;
|
||||||
|
case 1: y = value; break;
|
||||||
|
case 2: z = value; break;
|
||||||
|
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static implicit operator double3(double value) => new(value, value, value);
|
public static implicit operator double3(double value) => new(value, value, value);
|
||||||
|
|
||||||
|
|
@ -33,4 +47,16 @@ public record struct double3(double x, double y, double z)
|
||||||
public static double3 operator -(double3 left, double3 right) => new(left.x - right.x, left.y - right.y, left.z - right.z);
|
public static double3 operator -(double3 left, double3 right) => new(left.x - right.x, left.y - right.y, left.z - right.z);
|
||||||
public static double3 operator *(double3 left, double3 right) => new(left.x * right.x, left.y * right.y, left.z * right.z);
|
public static double3 operator *(double3 left, double3 right) => new(left.x * right.x, left.y * right.y, left.z * right.z);
|
||||||
public static double3 operator /(double3 left, double3 right) => new(left.x / right.x, left.y / right.y, left.z / right.z);
|
public static double3 operator /(double3 left, double3 right) => new(left.x / right.x, left.y / right.y, left.z / right.z);
|
||||||
|
|
||||||
|
public static bool3 operator <(double3 left, double3 right) => new(left.x < right.x, left.y < right.y, left.z < right.z);
|
||||||
|
public static bool3 operator >(double3 left, double3 right) => new(left.x > right.x, left.y > right.y, left.z > right.z);
|
||||||
|
public static bool3 operator <=(double3 left, double3 right) => new(left.x <= right.x, left.y <= right.y, left.z <= right.z);
|
||||||
|
public static bool3 operator >=(double3 left, double3 right) => new(left.x >= right.x, left.y >= right.y, left.z >= right.z);
|
||||||
|
public static bool3 operator ==(double3 left, double3 right) => new(left.x == right.x, left.y == right.y, left.z == right.z);
|
||||||
|
public static bool3 operator !=(double3 left, double3 right) => new(left.x != right.x, left.y != right.y, left.z != right.z);
|
||||||
|
|
||||||
|
public readonly bool Equals(double3 other) => x.Equals(other.x) && y.Equals(other.y) && z.Equals(other.z);
|
||||||
|
public override readonly bool Equals(object? obj) => obj is double3 other && Equals(other);
|
||||||
|
public override readonly int GetHashCode() => HashCode.Combine(x, y, z);
|
||||||
|
public override readonly string ToString() => FormattableString.Invariant($"double3({x:R}, {y:R}, {z:R})");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,38 @@ using System;
|
||||||
|
|
||||||
namespace CultMath;
|
namespace CultMath;
|
||||||
|
|
||||||
public record struct float2(float x, float y)
|
public partial struct float2 : IEquatable<float2>
|
||||||
{
|
{
|
||||||
public static readonly float2 zero = new(0.0f, 0.0f);
|
public static readonly float2 zero = new(0.0f, 0.0f);
|
||||||
public static readonly float2 one = new(1.0f, 1.0f);
|
public static readonly float2 one = new(1.0f, 1.0f);
|
||||||
|
|
||||||
public float this[int index] => index switch
|
public float x;
|
||||||
|
public float y;
|
||||||
|
|
||||||
|
public float2(float x, float y)
|
||||||
{
|
{
|
||||||
0 => x,
|
this.x = x;
|
||||||
1 => y,
|
this.y = y;
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
}
|
||||||
};
|
|
||||||
|
public float this[int index]
|
||||||
|
{
|
||||||
|
readonly get => index switch
|
||||||
|
{
|
||||||
|
0 => x,
|
||||||
|
1 => y,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||||
|
};
|
||||||
|
set
|
||||||
|
{
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case 0: x = value; break;
|
||||||
|
case 1: y = value; break;
|
||||||
|
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static implicit operator float2(float value) => new(value, value);
|
public static implicit operator float2(float value) => new(value, value);
|
||||||
public static explicit operator System.Numerics.Vector2(float2 value) => new(value.x, value.y);
|
public static explicit operator System.Numerics.Vector2(float2 value) => new(value.x, value.y);
|
||||||
|
|
@ -20,9 +41,44 @@ public record struct float2(float x, float y)
|
||||||
|
|
||||||
public static float2 operator +(float2 value) => value;
|
public static float2 operator +(float2 value) => value;
|
||||||
public static float2 operator -(float2 value) => new(-value.x, -value.y);
|
public static float2 operator -(float2 value) => new(-value.x, -value.y);
|
||||||
|
|
||||||
public static float2 operator +(float2 left, float2 right) => new(left.x + right.x, left.y + right.y);
|
public static float2 operator +(float2 left, float2 right) => new(left.x + right.x, left.y + right.y);
|
||||||
|
public static float2 operator +(float2 left, float right) => new(left.x + right, left.y + right);
|
||||||
|
public static float2 operator +(float left, float2 right) => new(left + right.x, left + right.y);
|
||||||
public static float2 operator -(float2 left, float2 right) => new(left.x - right.x, left.y - right.y);
|
public static float2 operator -(float2 left, float2 right) => new(left.x - right.x, left.y - right.y);
|
||||||
|
public static float2 operator -(float2 left, float right) => new(left.x - right, left.y - right);
|
||||||
|
public static float2 operator -(float left, float2 right) => new(left - right.x, left - right.y);
|
||||||
public static float2 operator *(float2 left, float2 right) => new(left.x * right.x, left.y * right.y);
|
public static float2 operator *(float2 left, float2 right) => new(left.x * right.x, left.y * right.y);
|
||||||
|
public static float2 operator *(float2 left, float right) => new(left.x * right, left.y * right);
|
||||||
|
public static float2 operator *(float left, float2 right) => new(left * right.x, left * right.y);
|
||||||
public static float2 operator /(float2 left, float2 right) => new(left.x / right.x, left.y / right.y);
|
public static float2 operator /(float2 left, float2 right) => new(left.x / right.x, left.y / right.y);
|
||||||
|
public static float2 operator /(float2 left, float right) => new(left.x / right, left.y / right);
|
||||||
|
public static float2 operator /(float left, float2 right) => new(left / right.x, left / right.y);
|
||||||
public static float2 operator %(float2 left, float2 right) => new(left.x % right.x, left.y % right.y);
|
public static float2 operator %(float2 left, float2 right) => new(left.x % right.x, left.y % right.y);
|
||||||
|
public static float2 operator %(float2 left, float right) => new(left.x % right, left.y % right);
|
||||||
|
public static float2 operator %(float left, float2 right) => new(left % right.x, left % right.y);
|
||||||
|
|
||||||
|
public static bool2 operator <(float2 left, float2 right) => new(left.x < right.x, left.y < right.y);
|
||||||
|
public static bool2 operator <(float2 left, float right) => new(left.x < right, left.y < right);
|
||||||
|
public static bool2 operator <(float left, float2 right) => new(left < right.x, left < right.y);
|
||||||
|
public static bool2 operator >(float2 left, float2 right) => new(left.x > right.x, left.y > right.y);
|
||||||
|
public static bool2 operator >(float2 left, float right) => new(left.x > right, left.y > right);
|
||||||
|
public static bool2 operator >(float left, float2 right) => new(left > right.x, left > right.y);
|
||||||
|
public static bool2 operator <=(float2 left, float2 right) => new(left.x <= right.x, left.y <= right.y);
|
||||||
|
public static bool2 operator <=(float2 left, float right) => new(left.x <= right, left.y <= right);
|
||||||
|
public static bool2 operator <=(float left, float2 right) => new(left <= right.x, left <= right.y);
|
||||||
|
public static bool2 operator >=(float2 left, float2 right) => new(left.x >= right.x, left.y >= right.y);
|
||||||
|
public static bool2 operator >=(float2 left, float right) => new(left.x >= right, left.y >= right);
|
||||||
|
public static bool2 operator >=(float left, float2 right) => new(left >= right.x, left >= right.y);
|
||||||
|
public static bool2 operator ==(float2 left, float2 right) => new(left.x == right.x, left.y == right.y);
|
||||||
|
public static bool2 operator ==(float2 left, float right) => new(left.x == right, left.y == right);
|
||||||
|
public static bool2 operator ==(float left, float2 right) => new(left == right.x, left == right.y);
|
||||||
|
public static bool2 operator !=(float2 left, float2 right) => new(left.x != right.x, left.y != right.y);
|
||||||
|
public static bool2 operator !=(float2 left, float right) => new(left.x != right, left.y != right);
|
||||||
|
public static bool2 operator !=(float left, float2 right) => new(left != right.x, left != right.y);
|
||||||
|
|
||||||
|
public readonly bool Equals(float2 other) => x.Equals(other.x) && y.Equals(other.y);
|
||||||
|
public override readonly bool Equals(object? obj) => obj is float2 other && Equals(other);
|
||||||
|
public override readonly int GetHashCode() => HashCode.Combine(x, y);
|
||||||
|
public override readonly string ToString() => FormattableString.Invariant($"float2({x:R}, {y:R})");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
67
packages/cultmath/src/CultMath/float2x2.cs
Normal file
67
packages/cultmath/src/CultMath/float2x2.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace CultMath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HLSL float2x2: row-major. Constructors take rows, <c>m[i]</c> is a writable reference to
|
||||||
|
/// row i (so <c>m[1][0] = s</c> works), and <c>_mRC</c> names row R, column C exactly as HLSL
|
||||||
|
/// does. Use <see cref="math.mul(float2x2, float2)"/> for products; there is no <c>*</c>
|
||||||
|
/// matrix product because HLSL's <c>*</c> is component-wise.
|
||||||
|
/// </summary>
|
||||||
|
public struct float2x2 : IEquatable<float2x2>
|
||||||
|
{
|
||||||
|
public static readonly float2x2 identity = new(1.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
public static readonly float2x2 zero = new(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
|
||||||
|
private float2 _row0;
|
||||||
|
private float2 _row1;
|
||||||
|
|
||||||
|
public float2x2(float m00, float m01, float m10, float m11)
|
||||||
|
{
|
||||||
|
_row0 = new float2(m00, m01);
|
||||||
|
_row1 = new float2(m10, m11);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float2x2(float2 row0, float2 row1)
|
||||||
|
{
|
||||||
|
_row0 = row0;
|
||||||
|
_row1 = row1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float _m00 { readonly get => _row0.x; set => _row0.x = value; }
|
||||||
|
public float _m01 { readonly get => _row0.y; set => _row0.y = value; }
|
||||||
|
public float _m10 { readonly get => _row1.x; set => _row1.x = value; }
|
||||||
|
public float _m11 { readonly get => _row1.y; set => _row1.y = value; }
|
||||||
|
|
||||||
|
/// <remarks>Writes through a readonly field, <c>in</c> parameter, property, or <c>identity</c> hit a defensive copy and are lost; write to a local and assign it back.</remarks>
|
||||||
|
[UnscopedRef]
|
||||||
|
public ref float2 this[int row]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (row)
|
||||||
|
{
|
||||||
|
case 0: return ref _row0;
|
||||||
|
case 1: return ref _row1;
|
||||||
|
default: throw new ArgumentOutOfRangeException(nameof(row));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Counter-clockwise rotation for column vectors: <c>mul(Rotate(a), v)</c> equals
|
||||||
|
/// <c>math.rotate(v, a)</c>; <c>mul(v, Rotate(a))</c> rotates the other way.
|
||||||
|
/// </summary>
|
||||||
|
public static float2x2 Rotate(float radians)
|
||||||
|
{
|
||||||
|
var s = MathF.Sin(radians);
|
||||||
|
var c = MathF.Cos(radians);
|
||||||
|
return new float2x2(c, -s, s, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly bool Equals(float2x2 other) => _row0.Equals(other._row0) && _row1.Equals(other._row1);
|
||||||
|
public override readonly bool Equals(object? obj) => obj is float2x2 other && Equals(other);
|
||||||
|
public override readonly int GetHashCode() => HashCode.Combine(_row0, _row1);
|
||||||
|
public override readonly string ToString() => FormattableString.Invariant($"float2x2({_row0.x:R}, {_row0.y:R}, {_row1.x:R}, {_row1.y:R})");
|
||||||
|
}
|
||||||
2
packages/cultmath/src/CultMath/float2x2.cs.meta
Normal file
2
packages/cultmath/src/CultMath/float2x2.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c0dcec14c0b7432dbd8ba0bebb0a7c2f
|
||||||
|
|
@ -2,78 +2,87 @@ using System;
|
||||||
|
|
||||||
namespace CultMath;
|
namespace CultMath;
|
||||||
|
|
||||||
public record struct float3(float x, float y, float z)
|
public partial struct float3 : IEquatable<float3>
|
||||||
{
|
{
|
||||||
public static readonly float3 zero = new(0.0f, 0.0f, 0.0f);
|
public static readonly float3 zero = new(0.0f, 0.0f, 0.0f);
|
||||||
public static readonly float3 one = new(1.0f, 1.0f, 1.0f);
|
public static readonly float3 one = new(1.0f, 1.0f, 1.0f);
|
||||||
|
|
||||||
public float3(float2 xy, float z)
|
public float x;
|
||||||
: this(xy.x, xy.y, z)
|
public float y;
|
||||||
|
public float z;
|
||||||
|
|
||||||
|
public float3(float x, float y, float z)
|
||||||
{
|
{
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.z = z;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float2 xy
|
public float this[int index]
|
||||||
{
|
{
|
||||||
get => new(x, y);
|
readonly get => index switch
|
||||||
|
{
|
||||||
|
0 => x,
|
||||||
|
1 => y,
|
||||||
|
2 => z,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||||
|
};
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
x = value.x;
|
switch (index)
|
||||||
y = value.y;
|
{
|
||||||
|
case 0: x = value; break;
|
||||||
|
case 1: y = value; break;
|
||||||
|
case 2: z = value; break;
|
||||||
|
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public float2 xz
|
|
||||||
{
|
|
||||||
get => new(x, z);
|
|
||||||
set
|
|
||||||
{
|
|
||||||
x = value.x;
|
|
||||||
z = value.y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public float2 yz
|
|
||||||
{
|
|
||||||
get => new(y, z);
|
|
||||||
set
|
|
||||||
{
|
|
||||||
y = value.x;
|
|
||||||
z = value.y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public float2 zy
|
|
||||||
{
|
|
||||||
get => new(z, y);
|
|
||||||
set
|
|
||||||
{
|
|
||||||
z = value.x;
|
|
||||||
y = value.y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public float3 yzx => new(y, z, x);
|
|
||||||
public float3 zyx => new(z, y, x);
|
|
||||||
|
|
||||||
public float this[int index] => index switch
|
|
||||||
{
|
|
||||||
0 => x,
|
|
||||||
1 => y,
|
|
||||||
2 => z,
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
|
||||||
};
|
|
||||||
|
|
||||||
public static implicit operator float3(float value) => new(value, value, value);
|
public static implicit operator float3(float value) => new(value, value, value);
|
||||||
public static explicit operator System.Numerics.Vector3(float3 value) => new(value.x, value.y, value.z);
|
public static explicit operator System.Numerics.Vector3(float3 value) => new(value.x, value.y, value.z);
|
||||||
public static explicit operator float3(System.Numerics.Vector3 value) => new(value.X, value.Y, value.Z);
|
public static explicit operator float3(System.Numerics.Vector3 value) => new(value.X, value.Y, value.Z);
|
||||||
|
|
||||||
public override readonly string ToString() => $"float3({x:R}, {y:R}, {z:R})";
|
|
||||||
|
|
||||||
public static float3 operator +(float3 value) => value;
|
public static float3 operator +(float3 value) => value;
|
||||||
public static float3 operator -(float3 value) => new(-value.x, -value.y, -value.z);
|
public static float3 operator -(float3 value) => new(-value.x, -value.y, -value.z);
|
||||||
|
|
||||||
public static float3 operator +(float3 left, float3 right) => new(left.x + right.x, left.y + right.y, left.z + right.z);
|
public static float3 operator +(float3 left, float3 right) => new(left.x + right.x, left.y + right.y, left.z + right.z);
|
||||||
|
public static float3 operator +(float3 left, float right) => new(left.x + right, left.y + right, left.z + right);
|
||||||
|
public static float3 operator +(float left, float3 right) => new(left + right.x, left + right.y, left + right.z);
|
||||||
public static float3 operator -(float3 left, float3 right) => new(left.x - right.x, left.y - right.y, left.z - right.z);
|
public static float3 operator -(float3 left, float3 right) => new(left.x - right.x, left.y - right.y, left.z - right.z);
|
||||||
|
public static float3 operator -(float3 left, float right) => new(left.x - right, left.y - right, left.z - right);
|
||||||
|
public static float3 operator -(float left, float3 right) => new(left - right.x, left - right.y, left - right.z);
|
||||||
public static float3 operator *(float3 left, float3 right) => new(left.x * right.x, left.y * right.y, left.z * right.z);
|
public static float3 operator *(float3 left, float3 right) => new(left.x * right.x, left.y * right.y, left.z * right.z);
|
||||||
|
public static float3 operator *(float3 left, float right) => new(left.x * right, left.y * right, left.z * right);
|
||||||
|
public static float3 operator *(float left, float3 right) => new(left * right.x, left * right.y, left * right.z);
|
||||||
public static float3 operator /(float3 left, float3 right) => new(left.x / right.x, left.y / right.y, left.z / right.z);
|
public static float3 operator /(float3 left, float3 right) => new(left.x / right.x, left.y / right.y, left.z / right.z);
|
||||||
|
public static float3 operator /(float3 left, float right) => new(left.x / right, left.y / right, left.z / right);
|
||||||
|
public static float3 operator /(float left, float3 right) => new(left / right.x, left / right.y, left / right.z);
|
||||||
public static float3 operator %(float3 left, float3 right) => new(left.x % right.x, left.y % right.y, left.z % right.z);
|
public static float3 operator %(float3 left, float3 right) => new(left.x % right.x, left.y % right.y, left.z % right.z);
|
||||||
|
public static float3 operator %(float3 left, float right) => new(left.x % right, left.y % right, left.z % right);
|
||||||
|
public static float3 operator %(float left, float3 right) => new(left % right.x, left % right.y, left % right.z);
|
||||||
|
|
||||||
|
public static bool3 operator <(float3 left, float3 right) => new(left.x < right.x, left.y < right.y, left.z < right.z);
|
||||||
|
public static bool3 operator <(float3 left, float right) => new(left.x < right, left.y < right, left.z < right);
|
||||||
|
public static bool3 operator <(float left, float3 right) => new(left < right.x, left < right.y, left < right.z);
|
||||||
|
public static bool3 operator >(float3 left, float3 right) => new(left.x > right.x, left.y > right.y, left.z > right.z);
|
||||||
|
public static bool3 operator >(float3 left, float right) => new(left.x > right, left.y > right, left.z > right);
|
||||||
|
public static bool3 operator >(float left, float3 right) => new(left > right.x, left > right.y, left > right.z);
|
||||||
|
public static bool3 operator <=(float3 left, float3 right) => new(left.x <= right.x, left.y <= right.y, left.z <= right.z);
|
||||||
|
public static bool3 operator <=(float3 left, float right) => new(left.x <= right, left.y <= right, left.z <= right);
|
||||||
|
public static bool3 operator <=(float left, float3 right) => new(left <= right.x, left <= right.y, left <= right.z);
|
||||||
|
public static bool3 operator >=(float3 left, float3 right) => new(left.x >= right.x, left.y >= right.y, left.z >= right.z);
|
||||||
|
public static bool3 operator >=(float3 left, float right) => new(left.x >= right, left.y >= right, left.z >= right);
|
||||||
|
public static bool3 operator >=(float left, float3 right) => new(left >= right.x, left >= right.y, left >= right.z);
|
||||||
|
public static bool3 operator ==(float3 left, float3 right) => new(left.x == right.x, left.y == right.y, left.z == right.z);
|
||||||
|
public static bool3 operator ==(float3 left, float right) => new(left.x == right, left.y == right, left.z == right);
|
||||||
|
public static bool3 operator ==(float left, float3 right) => new(left == right.x, left == right.y, left == right.z);
|
||||||
|
public static bool3 operator !=(float3 left, float3 right) => new(left.x != right.x, left.y != right.y, left.z != right.z);
|
||||||
|
public static bool3 operator !=(float3 left, float right) => new(left.x != right, left.y != right, left.z != right);
|
||||||
|
public static bool3 operator !=(float left, float3 right) => new(left != right.x, left != right.y, left != right.z);
|
||||||
|
|
||||||
|
public readonly bool Equals(float3 other) => x.Equals(other.x) && y.Equals(other.y) && z.Equals(other.z);
|
||||||
|
public override readonly bool Equals(object? obj) => obj is float3 other && Equals(other);
|
||||||
|
public override readonly int GetHashCode() => HashCode.Combine(x, y, z);
|
||||||
|
public override readonly string ToString() => FormattableString.Invariant($"float3({x:R}, {y:R}, {z:R})");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
116
packages/cultmath/src/CultMath/float3x3.cs
Normal file
116
packages/cultmath/src/CultMath/float3x3.cs
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace CultMath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HLSL float3x3: row-major. Constructors take rows, <c>m[i]</c> is a writable reference to
|
||||||
|
/// row i (so <c>m[1][2] = s</c> works), and <c>_mRC</c> names row R, column C exactly as HLSL
|
||||||
|
/// does. Use <see cref="math.mul(float3x3, float3)"/> for products; there is no <c>*</c>
|
||||||
|
/// matrix product because HLSL's <c>*</c> is component-wise.
|
||||||
|
/// </summary>
|
||||||
|
public struct float3x3 : IEquatable<float3x3>
|
||||||
|
{
|
||||||
|
public static readonly float3x3 identity = new(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
public static readonly float3x3 zero = new(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
|
||||||
|
private float3 _row0;
|
||||||
|
private float3 _row1;
|
||||||
|
private float3 _row2;
|
||||||
|
|
||||||
|
public float3x3(float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22)
|
||||||
|
{
|
||||||
|
_row0 = new float3(m00, m01, m02);
|
||||||
|
_row1 = new float3(m10, m11, m12);
|
||||||
|
_row2 = new float3(m20, m21, m22);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float3x3(float3 row0, float3 row1, float3 row2)
|
||||||
|
{
|
||||||
|
_row0 = row0;
|
||||||
|
_row1 = row1;
|
||||||
|
_row2 = row2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float _m00 { readonly get => _row0.x; set => _row0.x = value; }
|
||||||
|
public float _m01 { readonly get => _row0.y; set => _row0.y = value; }
|
||||||
|
public float _m02 { readonly get => _row0.z; set => _row0.z = value; }
|
||||||
|
public float _m10 { readonly get => _row1.x; set => _row1.x = value; }
|
||||||
|
public float _m11 { readonly get => _row1.y; set => _row1.y = value; }
|
||||||
|
public float _m12 { readonly get => _row1.z; set => _row1.z = value; }
|
||||||
|
public float _m20 { readonly get => _row2.x; set => _row2.x = value; }
|
||||||
|
public float _m21 { readonly get => _row2.y; set => _row2.y = value; }
|
||||||
|
public float _m22 { readonly get => _row2.z; set => _row2.z = value; }
|
||||||
|
|
||||||
|
/// <remarks>Writes through a readonly field, <c>in</c> parameter, property, or <c>identity</c> hit a defensive copy and are lost; write to a local and assign it back.</remarks>
|
||||||
|
[UnscopedRef]
|
||||||
|
public ref float3 this[int row]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (row)
|
||||||
|
{
|
||||||
|
case 0: return ref _row0;
|
||||||
|
case 1: return ref _row1;
|
||||||
|
case 2: return ref _row2;
|
||||||
|
default: throw new ArgumentOutOfRangeException(nameof(row));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Rotation about +X for column vectors (<c>mul(m, v)</c>).</summary>
|
||||||
|
public static float3x3 RotateX(float radians)
|
||||||
|
{
|
||||||
|
var s = MathF.Sin(radians);
|
||||||
|
var c = MathF.Cos(radians);
|
||||||
|
return new float3x3(1.0f, 0.0f, 0.0f, 0.0f, c, -s, 0.0f, s, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Rotation about +Y for column vectors (<c>mul(m, v)</c>).</summary>
|
||||||
|
public static float3x3 RotateY(float radians)
|
||||||
|
{
|
||||||
|
var s = MathF.Sin(radians);
|
||||||
|
var c = MathF.Cos(radians);
|
||||||
|
return new float3x3(c, 0.0f, s, 0.0f, 1.0f, 0.0f, -s, 0.0f, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Rotation about +Z for column vectors (<c>mul(m, v)</c>).</summary>
|
||||||
|
public static float3x3 RotateZ(float radians)
|
||||||
|
{
|
||||||
|
var s = MathF.Sin(radians);
|
||||||
|
var c = MathF.Cos(radians);
|
||||||
|
return new float3x3(c, -s, 0.0f, s, c, 0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extrinsic Euler rotation in radians. The order names the axis applied first:
|
||||||
|
/// <see cref="math.RotationOrder.YXZ"/> rotates about Y, then X, then Z, so the
|
||||||
|
/// matrix is <c>mul(RotateZ, mul(RotateX, RotateY))</c> for column vectors.
|
||||||
|
/// </summary>
|
||||||
|
public static float3x3 Euler(float3 xyz, math.RotationOrder order = math.RotationOrder.Default)
|
||||||
|
{
|
||||||
|
var x = RotateX(xyz.x);
|
||||||
|
var y = RotateY(xyz.y);
|
||||||
|
var z = RotateZ(xyz.z);
|
||||||
|
return order switch
|
||||||
|
{
|
||||||
|
math.RotationOrder.XYZ => math.mul(z, math.mul(y, x)),
|
||||||
|
math.RotationOrder.XZY => math.mul(y, math.mul(z, x)),
|
||||||
|
math.RotationOrder.YXZ => math.mul(z, math.mul(x, y)),
|
||||||
|
math.RotationOrder.YZX => math.mul(x, math.mul(z, y)),
|
||||||
|
math.RotationOrder.ZXY => math.mul(y, math.mul(x, z)),
|
||||||
|
math.RotationOrder.ZYX => math.mul(x, math.mul(y, z)),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(order)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float3x3 Euler(float x, float y, float z, math.RotationOrder order = math.RotationOrder.Default) =>
|
||||||
|
Euler(new float3(x, y, z), order);
|
||||||
|
|
||||||
|
public readonly bool Equals(float3x3 other) => _row0.Equals(other._row0) && _row1.Equals(other._row1) && _row2.Equals(other._row2);
|
||||||
|
public override readonly bool Equals(object? obj) => obj is float3x3 other && Equals(other);
|
||||||
|
public override readonly int GetHashCode() => HashCode.Combine(_row0, _row1, _row2);
|
||||||
|
|
||||||
|
public override readonly string ToString() =>
|
||||||
|
FormattableString.Invariant($"float3x3({_row0.x:R}, {_row0.y:R}, {_row0.z:R}, {_row1.x:R}, {_row1.y:R}, {_row1.z:R}, {_row2.x:R}, {_row2.y:R}, {_row2.z:R})");
|
||||||
|
}
|
||||||
2
packages/cultmath/src/CultMath/float3x3.cs.meta
Normal file
2
packages/cultmath/src/CultMath/float3x3.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9ca36a9bfff144a9b51478a6d7b19c8e
|
||||||
|
|
@ -2,47 +2,46 @@ using System;
|
||||||
|
|
||||||
namespace CultMath;
|
namespace CultMath;
|
||||||
|
|
||||||
public record struct float4(float x, float y, float z, float w)
|
public partial struct float4 : IEquatable<float4>
|
||||||
{
|
{
|
||||||
public static readonly float4 zero = new(0.0f, 0.0f, 0.0f, 0.0f);
|
public static readonly float4 zero = new(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
public static readonly float4 one = new(1.0f, 1.0f, 1.0f, 1.0f);
|
public static readonly float4 one = new(1.0f, 1.0f, 1.0f, 1.0f);
|
||||||
|
|
||||||
public float4(float2 xy, float z, float w)
|
public float x;
|
||||||
: this(xy.x, xy.y, z, w)
|
public float y;
|
||||||
|
public float z;
|
||||||
|
public float w;
|
||||||
|
|
||||||
|
public float4(float x, float y, float z, float w)
|
||||||
{
|
{
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.z = z;
|
||||||
|
this.w = w;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float4(float3 xyz, float w)
|
public float this[int index]
|
||||||
: this(xyz.x, xyz.y, xyz.z, w)
|
|
||||||
{
|
{
|
||||||
}
|
readonly get => index switch
|
||||||
|
{
|
||||||
public float4(float x, float3 yzw)
|
0 => x,
|
||||||
: this(x, yzw.x, yzw.y, yzw.z)
|
1 => y,
|
||||||
{
|
2 => z,
|
||||||
}
|
3 => w,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||||
public float2 xy
|
};
|
||||||
{
|
|
||||||
get => new(x, y);
|
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
x = value.x;
|
switch (index)
|
||||||
y = value.y;
|
{
|
||||||
|
case 0: x = value; break;
|
||||||
|
case 1: y = value; break;
|
||||||
|
case 2: z = value; break;
|
||||||
|
case 3: w = value; break;
|
||||||
|
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public float3 xyw => new(x, y, w);
|
|
||||||
public float3 yzx => new(y, z, x);
|
|
||||||
public float3 xyz => new(x, y, z);
|
|
||||||
|
|
||||||
public float this[int index] => index switch
|
|
||||||
{
|
|
||||||
0 => x,
|
|
||||||
1 => y,
|
|
||||||
2 => z,
|
|
||||||
3 => w,
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
|
||||||
};
|
|
||||||
|
|
||||||
public static implicit operator float4(float value) => new(value, value, value, value);
|
public static implicit operator float4(float value) => new(value, value, value, value);
|
||||||
public static explicit operator System.Numerics.Vector4(float4 value) => new(value.x, value.y, value.z, value.w);
|
public static explicit operator System.Numerics.Vector4(float4 value) => new(value.x, value.y, value.z, value.w);
|
||||||
|
|
@ -50,9 +49,44 @@ public record struct float4(float x, float y, float z, float w)
|
||||||
|
|
||||||
public static float4 operator +(float4 value) => value;
|
public static float4 operator +(float4 value) => value;
|
||||||
public static float4 operator -(float4 value) => new(-value.x, -value.y, -value.z, -value.w);
|
public static float4 operator -(float4 value) => new(-value.x, -value.y, -value.z, -value.w);
|
||||||
|
|
||||||
public static float4 operator +(float4 left, float4 right) => new(left.x + right.x, left.y + right.y, left.z + right.z, left.w + right.w);
|
public static float4 operator +(float4 left, float4 right) => new(left.x + right.x, left.y + right.y, left.z + right.z, left.w + right.w);
|
||||||
|
public static float4 operator +(float4 left, float right) => new(left.x + right, left.y + right, left.z + right, left.w + right);
|
||||||
|
public static float4 operator +(float left, float4 right) => new(left + right.x, left + right.y, left + right.z, left + right.w);
|
||||||
public static float4 operator -(float4 left, float4 right) => new(left.x - right.x, left.y - right.y, left.z - right.z, left.w - right.w);
|
public static float4 operator -(float4 left, float4 right) => new(left.x - right.x, left.y - right.y, left.z - right.z, left.w - right.w);
|
||||||
|
public static float4 operator -(float4 left, float right) => new(left.x - right, left.y - right, left.z - right, left.w - right);
|
||||||
|
public static float4 operator -(float left, float4 right) => new(left - right.x, left - right.y, left - right.z, left - right.w);
|
||||||
public static float4 operator *(float4 left, float4 right) => new(left.x * right.x, left.y * right.y, left.z * right.z, left.w * right.w);
|
public static float4 operator *(float4 left, float4 right) => new(left.x * right.x, left.y * right.y, left.z * right.z, left.w * right.w);
|
||||||
|
public static float4 operator *(float4 left, float right) => new(left.x * right, left.y * right, left.z * right, left.w * right);
|
||||||
|
public static float4 operator *(float left, float4 right) => new(left * right.x, left * right.y, left * right.z, left * right.w);
|
||||||
public static float4 operator /(float4 left, float4 right) => new(left.x / right.x, left.y / right.y, left.z / right.z, left.w / right.w);
|
public static float4 operator /(float4 left, float4 right) => new(left.x / right.x, left.y / right.y, left.z / right.z, left.w / right.w);
|
||||||
|
public static float4 operator /(float4 left, float right) => new(left.x / right, left.y / right, left.z / right, left.w / right);
|
||||||
|
public static float4 operator /(float left, float4 right) => new(left / right.x, left / right.y, left / right.z, left / right.w);
|
||||||
public static float4 operator %(float4 left, float4 right) => new(left.x % right.x, left.y % right.y, left.z % right.z, left.w % right.w);
|
public static float4 operator %(float4 left, float4 right) => new(left.x % right.x, left.y % right.y, left.z % right.z, left.w % right.w);
|
||||||
|
public static float4 operator %(float4 left, float right) => new(left.x % right, left.y % right, left.z % right, left.w % right);
|
||||||
|
public static float4 operator %(float left, float4 right) => new(left % right.x, left % right.y, left % right.z, left % right.w);
|
||||||
|
|
||||||
|
public static bool4 operator <(float4 left, float4 right) => new(left.x < right.x, left.y < right.y, left.z < right.z, left.w < right.w);
|
||||||
|
public static bool4 operator <(float4 left, float right) => new(left.x < right, left.y < right, left.z < right, left.w < right);
|
||||||
|
public static bool4 operator <(float left, float4 right) => new(left < right.x, left < right.y, left < right.z, left < right.w);
|
||||||
|
public static bool4 operator >(float4 left, float4 right) => new(left.x > right.x, left.y > right.y, left.z > right.z, left.w > right.w);
|
||||||
|
public static bool4 operator >(float4 left, float right) => new(left.x > right, left.y > right, left.z > right, left.w > right);
|
||||||
|
public static bool4 operator >(float left, float4 right) => new(left > right.x, left > right.y, left > right.z, left > right.w);
|
||||||
|
public static bool4 operator <=(float4 left, float4 right) => new(left.x <= right.x, left.y <= right.y, left.z <= right.z, left.w <= right.w);
|
||||||
|
public static bool4 operator <=(float4 left, float right) => new(left.x <= right, left.y <= right, left.z <= right, left.w <= right);
|
||||||
|
public static bool4 operator <=(float left, float4 right) => new(left <= right.x, left <= right.y, left <= right.z, left <= right.w);
|
||||||
|
public static bool4 operator >=(float4 left, float4 right) => new(left.x >= right.x, left.y >= right.y, left.z >= right.z, left.w >= right.w);
|
||||||
|
public static bool4 operator >=(float4 left, float right) => new(left.x >= right, left.y >= right, left.z >= right, left.w >= right);
|
||||||
|
public static bool4 operator >=(float left, float4 right) => new(left >= right.x, left >= right.y, left >= right.z, left >= right.w);
|
||||||
|
public static bool4 operator ==(float4 left, float4 right) => new(left.x == right.x, left.y == right.y, left.z == right.z, left.w == right.w);
|
||||||
|
public static bool4 operator ==(float4 left, float right) => new(left.x == right, left.y == right, left.z == right, left.w == right);
|
||||||
|
public static bool4 operator ==(float left, float4 right) => new(left == right.x, left == right.y, left == right.z, left == right.w);
|
||||||
|
public static bool4 operator !=(float4 left, float4 right) => new(left.x != right.x, left.y != right.y, left.z != right.z, left.w != right.w);
|
||||||
|
public static bool4 operator !=(float4 left, float right) => new(left.x != right, left.y != right, left.z != right, left.w != right);
|
||||||
|
public static bool4 operator !=(float left, float4 right) => new(left != right.x, left != right.y, left != right.z, left != right.w);
|
||||||
|
|
||||||
|
public readonly bool Equals(float4 other) => x.Equals(other.x) && y.Equals(other.y) && z.Equals(other.z) && w.Equals(other.w);
|
||||||
|
public override readonly bool Equals(object? obj) => obj is float4 other && Equals(other);
|
||||||
|
public override readonly int GetHashCode() => HashCode.Combine(x, y, z, w);
|
||||||
|
public override readonly string ToString() => FormattableString.Invariant($"float4({x:R}, {y:R}, {z:R}, {w:R})");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,38 @@ using System;
|
||||||
|
|
||||||
namespace CultMath;
|
namespace CultMath;
|
||||||
|
|
||||||
public record struct int2(int x, int y)
|
public partial struct int2 : IEquatable<int2>
|
||||||
{
|
{
|
||||||
public static readonly int2 zero = new(0, 0);
|
public static readonly int2 zero = new(0, 0);
|
||||||
public static readonly int2 one = new(1, 1);
|
public static readonly int2 one = new(1, 1);
|
||||||
|
|
||||||
public int this[int index] => index switch
|
public int x;
|
||||||
|
public int y;
|
||||||
|
|
||||||
|
public int2(int x, int y)
|
||||||
{
|
{
|
||||||
0 => x,
|
this.x = x;
|
||||||
1 => y,
|
this.y = y;
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
}
|
||||||
};
|
|
||||||
|
public int this[int index]
|
||||||
|
{
|
||||||
|
readonly get => index switch
|
||||||
|
{
|
||||||
|
0 => x,
|
||||||
|
1 => y,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||||
|
};
|
||||||
|
set
|
||||||
|
{
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case 0: x = value; break;
|
||||||
|
case 1: y = value; break;
|
||||||
|
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static implicit operator int2(int value) => new(value, value);
|
public static implicit operator int2(int value) => new(value, value);
|
||||||
public static implicit operator float2(int2 value) => new(value.x, value.y);
|
public static implicit operator float2(int2 value) => new(value.x, value.y);
|
||||||
|
|
@ -26,4 +47,16 @@ public record struct int2(int x, int y)
|
||||||
public static int2 operator *(int2 left, int2 right) => new(left.x * right.x, left.y * right.y);
|
public static int2 operator *(int2 left, int2 right) => new(left.x * right.x, left.y * right.y);
|
||||||
public static int2 operator /(int2 left, int2 right) => new(left.x / right.x, left.y / right.y);
|
public static int2 operator /(int2 left, int2 right) => new(left.x / right.x, left.y / right.y);
|
||||||
public static int2 operator %(int2 left, int2 right) => new(left.x % right.x, left.y % right.y);
|
public static int2 operator %(int2 left, int2 right) => new(left.x % right.x, left.y % right.y);
|
||||||
|
|
||||||
|
public static bool2 operator <(int2 left, int2 right) => new(left.x < right.x, left.y < right.y);
|
||||||
|
public static bool2 operator >(int2 left, int2 right) => new(left.x > right.x, left.y > right.y);
|
||||||
|
public static bool2 operator <=(int2 left, int2 right) => new(left.x <= right.x, left.y <= right.y);
|
||||||
|
public static bool2 operator >=(int2 left, int2 right) => new(left.x >= right.x, left.y >= right.y);
|
||||||
|
public static bool2 operator ==(int2 left, int2 right) => new(left.x == right.x, left.y == right.y);
|
||||||
|
public static bool2 operator !=(int2 left, int2 right) => new(left.x != right.x, left.y != right.y);
|
||||||
|
|
||||||
|
public readonly bool Equals(int2 other) => x == other.x && y == other.y;
|
||||||
|
public override readonly bool Equals(object? obj) => obj is int2 other && Equals(other);
|
||||||
|
public override readonly int GetHashCode() => HashCode.Combine(x, y);
|
||||||
|
public override readonly string ToString() => FormattableString.Invariant($"int2({x}, {y})");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
64
packages/cultmath/src/CultMath/int3.cs
Normal file
64
packages/cultmath/src/CultMath/int3.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace CultMath;
|
||||||
|
|
||||||
|
public partial struct int3 : IEquatable<int3>
|
||||||
|
{
|
||||||
|
public static readonly int3 zero = new(0, 0, 0);
|
||||||
|
public static readonly int3 one = new(1, 1, 1);
|
||||||
|
|
||||||
|
public int x;
|
||||||
|
public int y;
|
||||||
|
public int z;
|
||||||
|
|
||||||
|
public int3(int x, int y, int z)
|
||||||
|
{
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.z = z;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int this[int index]
|
||||||
|
{
|
||||||
|
readonly get => index switch
|
||||||
|
{
|
||||||
|
0 => x,
|
||||||
|
1 => y,
|
||||||
|
2 => z,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||||
|
};
|
||||||
|
set
|
||||||
|
{
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case 0: x = value; break;
|
||||||
|
case 1: y = value; break;
|
||||||
|
case 2: z = value; break;
|
||||||
|
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator int3(int value) => new(value, value, value);
|
||||||
|
public static implicit operator float3(int3 value) => new(value.x, value.y, value.z);
|
||||||
|
|
||||||
|
public static int3 operator +(int3 value) => value;
|
||||||
|
public static int3 operator -(int3 value) => new(-value.x, -value.y, -value.z);
|
||||||
|
public static int3 operator +(int3 left, int3 right) => new(left.x + right.x, left.y + right.y, left.z + right.z);
|
||||||
|
public static int3 operator -(int3 left, int3 right) => new(left.x - right.x, left.y - right.y, left.z - right.z);
|
||||||
|
public static int3 operator *(int3 left, int3 right) => new(left.x * right.x, left.y * right.y, left.z * right.z);
|
||||||
|
public static int3 operator /(int3 left, int3 right) => new(left.x / right.x, left.y / right.y, left.z / right.z);
|
||||||
|
public static int3 operator %(int3 left, int3 right) => new(left.x % right.x, left.y % right.y, left.z % right.z);
|
||||||
|
|
||||||
|
public static bool3 operator <(int3 left, int3 right) => new(left.x < right.x, left.y < right.y, left.z < right.z);
|
||||||
|
public static bool3 operator >(int3 left, int3 right) => new(left.x > right.x, left.y > right.y, left.z > right.z);
|
||||||
|
public static bool3 operator <=(int3 left, int3 right) => new(left.x <= right.x, left.y <= right.y, left.z <= right.z);
|
||||||
|
public static bool3 operator >=(int3 left, int3 right) => new(left.x >= right.x, left.y >= right.y, left.z >= right.z);
|
||||||
|
public static bool3 operator ==(int3 left, int3 right) => new(left.x == right.x, left.y == right.y, left.z == right.z);
|
||||||
|
public static bool3 operator !=(int3 left, int3 right) => new(left.x != right.x, left.y != right.y, left.z != right.z);
|
||||||
|
|
||||||
|
public readonly bool Equals(int3 other) => x == other.x && y == other.y && z == other.z;
|
||||||
|
public override readonly bool Equals(object? obj) => obj is int3 other && Equals(other);
|
||||||
|
public override readonly int GetHashCode() => HashCode.Combine(x, y, z);
|
||||||
|
public override readonly string ToString() => FormattableString.Invariant($"int3({x}, {y}, {z})");
|
||||||
|
}
|
||||||
2
packages/cultmath/src/CultMath/int3.cs.meta
Normal file
2
packages/cultmath/src/CultMath/int3.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 42ee98aace354db19b3ae87bdcbc1df9
|
||||||
68
packages/cultmath/src/CultMath/int4.cs
Normal file
68
packages/cultmath/src/CultMath/int4.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace CultMath;
|
||||||
|
|
||||||
|
public partial struct int4 : IEquatable<int4>
|
||||||
|
{
|
||||||
|
public static readonly int4 zero = new(0, 0, 0, 0);
|
||||||
|
public static readonly int4 one = new(1, 1, 1, 1);
|
||||||
|
|
||||||
|
public int x;
|
||||||
|
public int y;
|
||||||
|
public int z;
|
||||||
|
public int w;
|
||||||
|
|
||||||
|
public int4(int x, int y, int z, int w)
|
||||||
|
{
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.z = z;
|
||||||
|
this.w = w;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int this[int index]
|
||||||
|
{
|
||||||
|
readonly get => index switch
|
||||||
|
{
|
||||||
|
0 => x,
|
||||||
|
1 => y,
|
||||||
|
2 => z,
|
||||||
|
3 => w,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||||
|
};
|
||||||
|
set
|
||||||
|
{
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case 0: x = value; break;
|
||||||
|
case 1: y = value; break;
|
||||||
|
case 2: z = value; break;
|
||||||
|
case 3: w = value; break;
|
||||||
|
default: throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator int4(int value) => new(value, value, value, value);
|
||||||
|
public static implicit operator float4(int4 value) => new(value.x, value.y, value.z, value.w);
|
||||||
|
|
||||||
|
public static int4 operator +(int4 value) => value;
|
||||||
|
public static int4 operator -(int4 value) => new(-value.x, -value.y, -value.z, -value.w);
|
||||||
|
public static int4 operator +(int4 left, int4 right) => new(left.x + right.x, left.y + right.y, left.z + right.z, left.w + right.w);
|
||||||
|
public static int4 operator -(int4 left, int4 right) => new(left.x - right.x, left.y - right.y, left.z - right.z, left.w - right.w);
|
||||||
|
public static int4 operator *(int4 left, int4 right) => new(left.x * right.x, left.y * right.y, left.z * right.z, left.w * right.w);
|
||||||
|
public static int4 operator /(int4 left, int4 right) => new(left.x / right.x, left.y / right.y, left.z / right.z, left.w / right.w);
|
||||||
|
public static int4 operator %(int4 left, int4 right) => new(left.x % right.x, left.y % right.y, left.z % right.z, left.w % right.w);
|
||||||
|
|
||||||
|
public static bool4 operator <(int4 left, int4 right) => new(left.x < right.x, left.y < right.y, left.z < right.z, left.w < right.w);
|
||||||
|
public static bool4 operator >(int4 left, int4 right) => new(left.x > right.x, left.y > right.y, left.z > right.z, left.w > right.w);
|
||||||
|
public static bool4 operator <=(int4 left, int4 right) => new(left.x <= right.x, left.y <= right.y, left.z <= right.z, left.w <= right.w);
|
||||||
|
public static bool4 operator >=(int4 left, int4 right) => new(left.x >= right.x, left.y >= right.y, left.z >= right.z, left.w >= right.w);
|
||||||
|
public static bool4 operator ==(int4 left, int4 right) => new(left.x == right.x, left.y == right.y, left.z == right.z, left.w == right.w);
|
||||||
|
public static bool4 operator !=(int4 left, int4 right) => new(left.x != right.x, left.y != right.y, left.z != right.z, left.w != right.w);
|
||||||
|
|
||||||
|
public readonly bool Equals(int4 other) => x == other.x && y == other.y && z == other.z && w == other.w;
|
||||||
|
public override readonly bool Equals(object? obj) => obj is int4 other && Equals(other);
|
||||||
|
public override readonly int GetHashCode() => HashCode.Combine(x, y, z, w);
|
||||||
|
public override readonly string ToString() => FormattableString.Invariant($"int4({x}, {y}, {z}, {w})");
|
||||||
|
}
|
||||||
2
packages/cultmath/src/CultMath/int4.cs.meta
Normal file
2
packages/cultmath/src/CultMath/int4.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1a313709f76a430eb83901bb2d05c74e
|
||||||
|
|
@ -2,26 +2,56 @@ using System;
|
||||||
|
|
||||||
namespace CultMath;
|
namespace CultMath;
|
||||||
|
|
||||||
public static class math
|
public static partial class math
|
||||||
{
|
{
|
||||||
public const float PI = MathF.PI;
|
public const float PI = MathF.PI;
|
||||||
public const float TAU = MathF.PI * 2.0f;
|
public const float TAU = MathF.PI * 2.0f;
|
||||||
public const float HALF_PI = MathF.PI * 0.5f;
|
public const float HALF_PI = MathF.PI * 0.5f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extrinsic Euler order for <see cref="CultMath.float3x3.Euler(CultMath.float3, RotationOrder)"/>:
|
||||||
|
/// the first letter is the axis applied first. Nested here so <c>using static CultMath.math;</c>
|
||||||
|
/// brings it into scope with the rest of the shader vocabulary.
|
||||||
|
/// </summary>
|
||||||
|
public enum RotationOrder : byte
|
||||||
|
{
|
||||||
|
XYZ,
|
||||||
|
XZY,
|
||||||
|
YXZ,
|
||||||
|
YZX,
|
||||||
|
ZXY,
|
||||||
|
ZYX,
|
||||||
|
Default = ZXY,
|
||||||
|
}
|
||||||
|
|
||||||
public static float2 float2(float x, float y) => new(x, y);
|
public static float2 float2(float x, float y) => new(x, y);
|
||||||
public static float2 float2(float value) => new(value, value);
|
public static float2 float2(float value) => new(value, value);
|
||||||
public static bool2 bool2(bool x, bool y) => new(x, y);
|
public static bool2 bool2(bool x, bool y) => new(x, y);
|
||||||
public static bool2 bool2(bool value) => new(value, value);
|
public static bool2 bool2(bool value) => new(value, value);
|
||||||
public static int2 int2(int x, int y) => new(x, y);
|
public static int2 int2(int x, int y) => new(x, y);
|
||||||
public static int2 int2(int value) => new(value, value);
|
public static int2 int2(int value) => new(value, value);
|
||||||
|
// Mixed constructor functions (float4(float2, float2), ...) are generated in Swizzles.g.cs.
|
||||||
public static float3 float3(float x, float y, float z) => new(x, y, z);
|
public static float3 float3(float x, float y, float z) => new(x, y, z);
|
||||||
public static float3 float3(float2 xy, float z) => new(xy, z);
|
|
||||||
public static float3 float3(float value) => new(value, value, value);
|
public static float3 float3(float value) => new(value, value, value);
|
||||||
public static float4 float4(float x, float y, float z, float w) => new(x, y, z, w);
|
public static float4 float4(float x, float y, float z, float w) => new(x, y, z, w);
|
||||||
public static float4 float4(float2 xy, float z, float w) => new(xy, z, w);
|
|
||||||
public static float4 float4(float3 xyz, float w) => new(xyz, w);
|
|
||||||
public static float4 float4(float x, float3 yzw) => new(x, yzw);
|
|
||||||
public static float4 float4(float value) => new(value, value, value, value);
|
public static float4 float4(float value) => new(value, value, value, value);
|
||||||
|
public static bool3 bool3(bool x, bool y, bool z) => new(x, y, z);
|
||||||
|
public static bool3 bool3(bool value) => new(value, value, value);
|
||||||
|
public static bool4 bool4(bool x, bool y, bool z, bool w) => new(x, y, z, w);
|
||||||
|
public static bool4 bool4(bool value) => new(value, value, value, value);
|
||||||
|
public static int3 int3(int x, int y, int z) => new(x, y, z);
|
||||||
|
public static int3 int3(int value) => new(value, value, value);
|
||||||
|
public static int4 int4(int x, int y, int z, int w) => new(x, y, z, w);
|
||||||
|
public static int4 int4(int value) => new(value, value, value, value);
|
||||||
|
|
||||||
|
// Matrix constructor functions take rows, as HLSL does. Under `using static CultMath.math;`
|
||||||
|
// these method groups hide the type names in member access, so static members need a
|
||||||
|
// qualified type: `CultMath.float3x3.Euler(...)`, `CultMath.float2x2.Rotate(...)`.
|
||||||
|
public static float2x2 float2x2(float m00, float m01, float m10, float m11) => new(m00, m01, m10, m11);
|
||||||
|
public static float2x2 float2x2(float2 row0, float2 row1) => new(row0, row1);
|
||||||
|
public static float3x3 float3x3(float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22) =>
|
||||||
|
new(m00, m01, m02, m10, m11, m12, m20, m21, m22);
|
||||||
|
public static float3x3 float3x3(float3 row0, float3 row1, float3 row2) => new(row0, row1, row2);
|
||||||
public static float radians(float degrees) => degrees * (PI / 180.0f);
|
public static float radians(float degrees) => degrees * (PI / 180.0f);
|
||||||
public static float degrees(float radians) => radians * (180.0f / PI);
|
public static float degrees(float radians) => radians * (180.0f / PI);
|
||||||
public static float sin(float value) => MathF.Sin(value);
|
public static float sin(float value) => MathF.Sin(value);
|
||||||
|
|
@ -46,11 +76,20 @@ public static class math
|
||||||
public static float2 abs(float2 value) => new(abs(value.x), abs(value.y));
|
public static float2 abs(float2 value) => new(abs(value.x), abs(value.y));
|
||||||
public static float3 abs(float3 value) => new(abs(value.x), abs(value.y), abs(value.z));
|
public static float3 abs(float3 value) => new(abs(value.x), abs(value.y), abs(value.z));
|
||||||
public static float4 abs(float4 value) => new(abs(value.x), abs(value.y), abs(value.z), abs(value.w));
|
public static float4 abs(float4 value) => new(abs(value.x), abs(value.y), abs(value.z), abs(value.w));
|
||||||
|
public static int abs(int value) => Math.Abs(value);
|
||||||
|
public static int2 abs(int2 value) => new(abs(value.x), abs(value.y));
|
||||||
|
public static int3 abs(int3 value) => new(abs(value.x), abs(value.y), abs(value.z));
|
||||||
|
public static int4 abs(int4 value) => new(abs(value.x), abs(value.y), abs(value.z), abs(value.w));
|
||||||
|
|
||||||
public static float sign(float value) => MathF.Sign(value);
|
// HLSL sign returns int. NaN compares false both ways, so it yields 0 instead of throwing.
|
||||||
public static float2 sign(float2 value) => new(sign(value.x), sign(value.y));
|
public static int sign(float value) => value > 0.0f ? 1 : value < 0.0f ? -1 : 0;
|
||||||
public static float3 sign(float3 value) => new(sign(value.x), sign(value.y), sign(value.z));
|
public static int2 sign(float2 value) => new(sign(value.x), sign(value.y));
|
||||||
public static float4 sign(float4 value) => new(sign(value.x), sign(value.y), sign(value.z), sign(value.w));
|
public static int3 sign(float3 value) => new(sign(value.x), sign(value.y), sign(value.z));
|
||||||
|
public static int4 sign(float4 value) => new(sign(value.x), sign(value.y), sign(value.z), sign(value.w));
|
||||||
|
public static int sign(int value) => value > 0 ? 1 : value < 0 ? -1 : 0;
|
||||||
|
public static int2 sign(int2 value) => new(sign(value.x), sign(value.y));
|
||||||
|
public static int3 sign(int3 value) => new(sign(value.x), sign(value.y), sign(value.z));
|
||||||
|
public static int4 sign(int4 value) => new(sign(value.x), sign(value.y), sign(value.z), sign(value.w));
|
||||||
|
|
||||||
public static float floor(float value) => MathF.Floor(value);
|
public static float floor(float value) => MathF.Floor(value);
|
||||||
public static float2 floor(float2 value) => new(floor(value.x), floor(value.y));
|
public static float2 floor(float2 value) => new(floor(value.x), floor(value.y));
|
||||||
|
|
@ -67,27 +106,36 @@ public static class math
|
||||||
public static float3 frac(float3 value) => value - floor(value);
|
public static float3 frac(float3 value) => value - floor(value);
|
||||||
public static float4 frac(float4 value) => value - floor(value);
|
public static float4 frac(float4 value) => value - floor(value);
|
||||||
|
|
||||||
public static float min(float left, float right) => MathF.Min(left, right);
|
// dxc lowers float min/max/clamp to DXIL FMin/FMax (saturate to Saturate, defined as FMin(1, FMax(0, x))).
|
||||||
|
// DXIL.rst: FMin(a, b) is a < b ? a : b, FMax(a, b) is a >= b ? a : b, and a NaN operand returns the other.
|
||||||
|
public static float min(float left, float right) => left < right || float.IsNaN(right) ? left : right;
|
||||||
public static int min(int left, int right) => Math.Min(left, right);
|
public static int min(int left, int right) => Math.Min(left, right);
|
||||||
public static float2 min(float2 left, float2 right) => new(min(left.x, right.x), min(left.y, right.y));
|
public static float2 min(float2 left, float2 right) => new(min(left.x, right.x), min(left.y, right.y));
|
||||||
public static float3 min(float3 left, float3 right) => new(min(left.x, right.x), min(left.y, right.y), min(left.z, right.z));
|
public static float3 min(float3 left, float3 right) => new(min(left.x, right.x), min(left.y, right.y), min(left.z, right.z));
|
||||||
public static float4 min(float4 left, float4 right) => new(min(left.x, right.x), min(left.y, right.y), min(left.z, right.z), min(left.w, right.w));
|
public static float4 min(float4 left, float4 right) => new(min(left.x, right.x), min(left.y, right.y), min(left.z, right.z), min(left.w, right.w));
|
||||||
|
public static int2 min(int2 left, int2 right) => new(min(left.x, right.x), min(left.y, right.y));
|
||||||
|
public static int3 min(int3 left, int3 right) => new(min(left.x, right.x), min(left.y, right.y), min(left.z, right.z));
|
||||||
|
public static int4 min(int4 left, int4 right) => new(min(left.x, right.x), min(left.y, right.y), min(left.z, right.z), min(left.w, right.w));
|
||||||
|
|
||||||
public static float max(float left, float right) => MathF.Max(left, right);
|
public static float max(float left, float right) => left >= right || float.IsNaN(right) ? left : right;
|
||||||
public static int max(int left, int right) => Math.Max(left, right);
|
public static int max(int left, int right) => Math.Max(left, right);
|
||||||
public static float2 max(float2 left, float2 right) => new(max(left.x, right.x), max(left.y, right.y));
|
public static float2 max(float2 left, float2 right) => new(max(left.x, right.x), max(left.y, right.y));
|
||||||
public static float3 max(float3 left, float3 right) => new(max(left.x, right.x), max(left.y, right.y), max(left.z, right.z));
|
public static float3 max(float3 left, float3 right) => new(max(left.x, right.x), max(left.y, right.y), max(left.z, right.z));
|
||||||
public static float4 max(float4 left, float4 right) => new(max(left.x, right.x), max(left.y, right.y), max(left.z, right.z), max(left.w, right.w));
|
public static float4 max(float4 left, float4 right) => new(max(left.x, right.x), max(left.y, right.y), max(left.z, right.z), max(left.w, right.w));
|
||||||
|
public static int2 max(int2 left, int2 right) => new(max(left.x, right.x), max(left.y, right.y));
|
||||||
|
public static int3 max(int3 left, int3 right) => new(max(left.x, right.x), max(left.y, right.y), max(left.z, right.z));
|
||||||
|
public static int4 max(int4 left, int4 right) => new(max(left.x, right.x), max(left.y, right.y), max(left.z, right.z), max(left.w, right.w));
|
||||||
|
|
||||||
public static float clamp(float value, float minimum, float maximum) => min(max(value, minimum), maximum);
|
public static float clamp(float value, float minimum, float maximum) => min(max(value, minimum), maximum);
|
||||||
public static float2 clamp(float2 value, float2 minimum, float2 maximum) => min(max(value, minimum), maximum);
|
public static float2 clamp(float2 value, float2 minimum, float2 maximum) => min(max(value, minimum), maximum);
|
||||||
public static float3 clamp(float3 value, float3 minimum, float3 maximum) => min(max(value, minimum), maximum);
|
public static float3 clamp(float3 value, float3 minimum, float3 maximum) => min(max(value, minimum), maximum);
|
||||||
public static float4 clamp(float4 value, float4 minimum, float4 maximum) => min(max(value, minimum), maximum);
|
public static float4 clamp(float4 value, float4 minimum, float4 maximum) => min(max(value, minimum), maximum);
|
||||||
|
|
||||||
public static float saturate(float value) => clamp(value, 0.0f, 1.0f);
|
// DXIL Saturate is FMin(1, FMax(0, x)); that operand order, unlike clamp(x, 0, 1), maps -0 to +0.
|
||||||
public static float2 saturate(float2 value) => clamp(value, 0.0f, 1.0f);
|
public static float saturate(float value) => min(1.0f, max(0.0f, value));
|
||||||
public static float3 saturate(float3 value) => clamp(value, 0.0f, 1.0f);
|
public static float2 saturate(float2 value) => min(1.0f, max(0.0f, value));
|
||||||
public static float4 saturate(float4 value) => clamp(value, 0.0f, 1.0f);
|
public static float3 saturate(float3 value) => min(1.0f, max(0.0f, value));
|
||||||
|
public static float4 saturate(float4 value) => min(1.0f, max(0.0f, value));
|
||||||
|
|
||||||
public static float lerp(float start, float end, float amount) => start + (end - start) * amount;
|
public static float lerp(float start, float end, float amount) => start + (end - start) * amount;
|
||||||
public static double lerp(double start, double end, double amount) => start + (end - start) * amount;
|
public static double lerp(double start, double end, double amount) => start + (end - start) * amount;
|
||||||
|
|
@ -105,6 +153,7 @@ public static class math
|
||||||
public static double2 unlerp(double2 start, double2 end, double2 value) => (value - start) / (end - start);
|
public static double2 unlerp(double2 start, double2 end, double2 value) => (value - start) / (end - start);
|
||||||
public static double3 unlerp(double3 start, double3 end, double3 value) => (value - start) / (end - start);
|
public static double3 unlerp(double3 start, double3 end, double3 value) => (value - start) / (end - start);
|
||||||
|
|
||||||
|
// dxc lowers step(y, x) to x < y ? 0 : 1 (fcmp olt, then select), so a NaN on either side yields 1.
|
||||||
public static float step(float edge, float value) => value < edge ? 0.0f : 1.0f;
|
public static float step(float edge, float value) => value < edge ? 0.0f : 1.0f;
|
||||||
public static float2 step(float2 edge, float2 value) => new(step(edge.x, value.x), step(edge.y, value.y));
|
public static float2 step(float2 edge, float2 value) => new(step(edge.x, value.x), step(edge.y, value.y));
|
||||||
public static float3 step(float3 edge, float3 value) => new(step(edge.x, value.x), step(edge.y, value.y), step(edge.z, value.z));
|
public static float3 step(float3 edge, float3 value) => new(step(edge.x, value.x), step(edge.y, value.y), step(edge.z, value.z));
|
||||||
|
|
@ -156,9 +205,9 @@ public static class math
|
||||||
public static float distance(float3 left, float3 right) => length(left - right);
|
public static float distance(float3 left, float3 right) => length(left - right);
|
||||||
public static float distance(float4 left, float4 right) => length(left - right);
|
public static float distance(float4 left, float4 right) => length(left - right);
|
||||||
|
|
||||||
public static float2 normalize(float2 value) => value / MathF.Max(length(value), 1.0e-20f);
|
public static float2 normalize(float2 value) => value / max(length(value), 1.0e-20f);
|
||||||
public static float3 normalize(float3 value) => value / MathF.Max(length(value), 1.0e-20f);
|
public static float3 normalize(float3 value) => value / max(length(value), 1.0e-20f);
|
||||||
public static float4 normalize(float4 value) => value / MathF.Max(length(value), 1.0e-20f);
|
public static float4 normalize(float4 value) => value / max(length(value), 1.0e-20f);
|
||||||
public static quaternion normalize(quaternion value)
|
public static quaternion normalize(quaternion value)
|
||||||
{
|
{
|
||||||
var length = MathF.Sqrt(value.x * value.x + value.y * value.y + value.z * value.z + value.w * value.w);
|
var length = MathF.Sqrt(value.x * value.x + value.y * value.y + value.z * value.z + value.w * value.w);
|
||||||
|
|
@ -324,9 +373,164 @@ public static class math
|
||||||
public static float hash(float2 value) => hash(dot(value, new float2(127.1f, 311.7f)));
|
public static float hash(float2 value) => hash(dot(value, new float2(127.1f, 311.7f)));
|
||||||
public static float hash(float3 value) => hash(dot(value, new float3(127.1f, 311.7f, 74.7f)));
|
public static float hash(float3 value) => hash(dot(value, new float3(127.1f, 311.7f, 74.7f)));
|
||||||
|
|
||||||
|
public static float log(float value) => MathF.Log(value);
|
||||||
|
public static float2 log(float2 value) => new(log(value.x), log(value.y));
|
||||||
|
public static float3 log(float3 value) => new(log(value.x), log(value.y), log(value.z));
|
||||||
|
public static float4 log(float4 value) => new(log(value.x), log(value.y), log(value.z), log(value.w));
|
||||||
|
|
||||||
|
public static float2 pow(float2 value, float2 power) => new(pow(value.x, power.x), pow(value.y, power.y));
|
||||||
|
public static float3 pow(float3 value, float3 power) => new(pow(value.x, power.x), pow(value.y, power.y), pow(value.z, power.z));
|
||||||
|
public static float4 pow(float4 value, float4 power) => new(pow(value.x, power.x), pow(value.y, power.y), pow(value.z, power.z), pow(value.w, power.w));
|
||||||
|
|
||||||
|
public static float2 sqrt(float2 value) => new(sqrt(value.x), sqrt(value.y));
|
||||||
|
public static float3 sqrt(float3 value) => new(sqrt(value.x), sqrt(value.y), sqrt(value.z));
|
||||||
|
public static float4 sqrt(float4 value) => new(sqrt(value.x), sqrt(value.y), sqrt(value.z), sqrt(value.w));
|
||||||
|
|
||||||
|
public static float2 sin(float2 value) => new(sin(value.x), sin(value.y));
|
||||||
|
public static float3 sin(float3 value) => new(sin(value.x), sin(value.y), sin(value.z));
|
||||||
|
public static float4 sin(float4 value) => new(sin(value.x), sin(value.y), sin(value.z), sin(value.w));
|
||||||
|
|
||||||
|
public static float2 cos(float2 value) => new(cos(value.x), cos(value.y));
|
||||||
|
public static float3 cos(float3 value) => new(cos(value.x), cos(value.y), cos(value.z));
|
||||||
|
public static float4 cos(float4 value) => new(cos(value.x), cos(value.y), cos(value.z), cos(value.w));
|
||||||
|
|
||||||
|
public static float2 acos(float2 value) => new(acos(value.x), acos(value.y));
|
||||||
|
public static float3 acos(float3 value) => new(acos(value.x), acos(value.y), acos(value.z));
|
||||||
|
public static float4 acos(float4 value) => new(acos(value.x), acos(value.y), acos(value.z), acos(value.w));
|
||||||
|
|
||||||
|
public static float2 atan2(float2 y, float2 x) => new(atan2(y.x, x.x), atan2(y.y, x.y));
|
||||||
|
public static float3 atan2(float3 y, float3 x) => new(atan2(y.x, x.x), atan2(y.y, x.y), atan2(y.z, x.z));
|
||||||
|
public static float4 atan2(float4 y, float4 x) => new(atan2(y.x, x.x), atan2(y.y, x.y), atan2(y.z, x.z), atan2(y.w, x.w));
|
||||||
|
|
||||||
|
// DXIL FMin/FMax take double overloads with the same rules as float (see min/max above).
|
||||||
|
public static double min(double left, double right) => left < right || double.IsNaN(right) ? left : right;
|
||||||
|
public static double max(double left, double right) => left >= right || double.IsNaN(right) ? left : right;
|
||||||
|
|
||||||
|
public static bool any(bool2 value) => value.x || value.y;
|
||||||
|
public static bool any(bool3 value) => value.x || value.y || value.z;
|
||||||
|
public static bool any(bool4 value) => value.x || value.y || value.z || value.w;
|
||||||
|
public static bool all(bool2 value) => value.x && value.y;
|
||||||
|
public static bool all(bool3 value) => value.x && value.y && value.z;
|
||||||
|
public static bool all(bool4 value) => value.x && value.y && value.z && value.w;
|
||||||
|
|
||||||
|
// HLSL any/all on numeric vectors test components against zero (NaN != 0 counts as true).
|
||||||
|
public static bool any(float2 value) => value.x != 0.0f || value.y != 0.0f;
|
||||||
|
public static bool any(float3 value) => value.x != 0.0f || value.y != 0.0f || value.z != 0.0f;
|
||||||
|
public static bool any(float4 value) => value.x != 0.0f || value.y != 0.0f || value.z != 0.0f || value.w != 0.0f;
|
||||||
|
public static bool all(float2 value) => value.x != 0.0f && value.y != 0.0f;
|
||||||
|
public static bool all(float3 value) => value.x != 0.0f && value.y != 0.0f && value.z != 0.0f;
|
||||||
|
public static bool all(float4 value) => value.x != 0.0f && value.y != 0.0f && value.z != 0.0f && value.w != 0.0f;
|
||||||
|
public static bool any(int2 value) => value.x != 0 || value.y != 0;
|
||||||
|
public static bool any(int3 value) => value.x != 0 || value.y != 0 || value.z != 0;
|
||||||
|
public static bool any(int4 value) => value.x != 0 || value.y != 0 || value.z != 0 || value.w != 0;
|
||||||
|
public static bool all(int2 value) => value.x != 0 && value.y != 0;
|
||||||
|
public static bool all(int3 value) => value.x != 0 && value.y != 0 && value.z != 0;
|
||||||
|
public static bool all(int4 value) => value.x != 0 && value.y != 0 && value.z != 0 && value.w != 0;
|
||||||
|
|
||||||
|
// HLSL 2021 argument order: select(condition, whenTrue, whenFalse).
|
||||||
|
// Unity.Mathematics' select(falseValue, trueValue, condition) is the reverse.
|
||||||
|
public static float select(bool condition, float whenTrue, float whenFalse) => condition ? whenTrue : whenFalse;
|
||||||
|
public static float2 select(bool2 condition, float2 whenTrue, float2 whenFalse) =>
|
||||||
|
new(condition.x ? whenTrue.x : whenFalse.x, condition.y ? whenTrue.y : whenFalse.y);
|
||||||
|
public static float3 select(bool3 condition, float3 whenTrue, float3 whenFalse) =>
|
||||||
|
new(condition.x ? whenTrue.x : whenFalse.x, condition.y ? whenTrue.y : whenFalse.y, condition.z ? whenTrue.z : whenFalse.z);
|
||||||
|
public static float4 select(bool4 condition, float4 whenTrue, float4 whenFalse) =>
|
||||||
|
new(condition.x ? whenTrue.x : whenFalse.x, condition.y ? whenTrue.y : whenFalse.y, condition.z ? whenTrue.z : whenFalse.z, condition.w ? whenTrue.w : whenFalse.w);
|
||||||
|
|
||||||
|
// HLSL mul: mul(m, v) treats v as a column vector, mul(v, m) as a row vector.
|
||||||
|
public static float mul(float left, float right) => left * right;
|
||||||
|
|
||||||
|
public static float2 mul(float2x2 m, float2 v) =>
|
||||||
|
new(m._m00 * v.x + m._m01 * v.y, m._m10 * v.x + m._m11 * v.y);
|
||||||
|
|
||||||
|
public static float2 mul(float2 v, float2x2 m) =>
|
||||||
|
new(v.x * m._m00 + v.y * m._m10, v.x * m._m01 + v.y * m._m11);
|
||||||
|
|
||||||
|
public static float2x2 mul(float2x2 a, float2x2 b) =>
|
||||||
|
new(
|
||||||
|
a._m00 * b._m00 + a._m01 * b._m10, a._m00 * b._m01 + a._m01 * b._m11,
|
||||||
|
a._m10 * b._m00 + a._m11 * b._m10, a._m10 * b._m01 + a._m11 * b._m11);
|
||||||
|
|
||||||
|
public static float3 mul(float3x3 m, float3 v) =>
|
||||||
|
new(
|
||||||
|
m._m00 * v.x + m._m01 * v.y + m._m02 * v.z,
|
||||||
|
m._m10 * v.x + m._m11 * v.y + m._m12 * v.z,
|
||||||
|
m._m20 * v.x + m._m21 * v.y + m._m22 * v.z);
|
||||||
|
|
||||||
|
public static float3 mul(float3 v, float3x3 m) =>
|
||||||
|
new(
|
||||||
|
v.x * m._m00 + v.y * m._m10 + v.z * m._m20,
|
||||||
|
v.x * m._m01 + v.y * m._m11 + v.z * m._m21,
|
||||||
|
v.x * m._m02 + v.y * m._m12 + v.z * m._m22);
|
||||||
|
|
||||||
|
public static float3x3 mul(float3x3 a, float3x3 b) =>
|
||||||
|
new(
|
||||||
|
a._m00 * b._m00 + a._m01 * b._m10 + a._m02 * b._m20,
|
||||||
|
a._m00 * b._m01 + a._m01 * b._m11 + a._m02 * b._m21,
|
||||||
|
a._m00 * b._m02 + a._m01 * b._m12 + a._m02 * b._m22,
|
||||||
|
a._m10 * b._m00 + a._m11 * b._m10 + a._m12 * b._m20,
|
||||||
|
a._m10 * b._m01 + a._m11 * b._m11 + a._m12 * b._m21,
|
||||||
|
a._m10 * b._m02 + a._m11 * b._m12 + a._m12 * b._m22,
|
||||||
|
a._m20 * b._m00 + a._m21 * b._m10 + a._m22 * b._m20,
|
||||||
|
a._m20 * b._m01 + a._m21 * b._m11 + a._m22 * b._m21,
|
||||||
|
a._m20 * b._m02 + a._m21 * b._m12 + a._m22 * b._m22);
|
||||||
|
|
||||||
|
// Ashima Arts / Ian McEwan 3D simplex noise (MIT), mirrored by cultmath_snoise(float3)
|
||||||
|
// in shaders/CultMath.hlsl with the same float32 evaluation order.
|
||||||
|
public static float snoise(float3 value)
|
||||||
|
{
|
||||||
|
const float cx = 1.0f / 6.0f;
|
||||||
|
const float cy = 1.0f / 3.0f;
|
||||||
|
var i = floor(value + dot(value, new float3(cy, cy, cy)));
|
||||||
|
var x0 = value - i + dot(i, new float3(cx, cx, cx));
|
||||||
|
var g = step(new float3(x0.y, x0.z, x0.x), x0);
|
||||||
|
var l = 1.0f - g;
|
||||||
|
var lzxy = new float3(l.z, l.x, l.y);
|
||||||
|
var i1 = min(g, lzxy);
|
||||||
|
var i2 = max(g, lzxy);
|
||||||
|
var x1 = x0 - i1 + cx;
|
||||||
|
var x2 = x0 - i2 + cy;
|
||||||
|
var x3 = x0 - 0.5f;
|
||||||
|
|
||||||
|
i = snoise_mod289(i);
|
||||||
|
var p = snoise_permute(snoise_permute(snoise_permute(
|
||||||
|
i.z + new float4(0.0f, i1.z, i2.z, 1.0f)) + i.y + new float4(0.0f, i1.y, i2.y, 1.0f)) + i.x + new float4(0.0f, i1.x, i2.x, 1.0f));
|
||||||
|
|
||||||
|
const float n = 0.142857142857f;
|
||||||
|
var ns = new float3(2.0f * n, 0.5f * n - 1.0f, n);
|
||||||
|
var j = p - 49.0f * floor(p * ns.z * ns.z);
|
||||||
|
var xs = floor(j * ns.z);
|
||||||
|
var ys = floor(j - 7.0f * xs);
|
||||||
|
var x = xs * ns.x + ns.y;
|
||||||
|
var y = ys * ns.x + ns.y;
|
||||||
|
var h = 1.0f - abs(x) - abs(y);
|
||||||
|
|
||||||
|
var b0 = new float4(x.x, x.y, y.x, y.y);
|
||||||
|
var b1 = new float4(x.z, x.w, y.z, y.w);
|
||||||
|
var s0 = floor(b0) * 2.0f + 1.0f;
|
||||||
|
var s1 = floor(b1) * 2.0f + 1.0f;
|
||||||
|
var sh = -step(h, 0.0f);
|
||||||
|
var a0 = new float4(b0.x, b0.z, b0.y, b0.w) + new float4(s0.x, s0.z, s0.y, s0.w) * new float4(sh.x, sh.x, sh.y, sh.y);
|
||||||
|
var a1 = new float4(b1.x, b1.z, b1.y, b1.w) + new float4(s1.x, s1.z, s1.y, s1.w) * new float4(sh.z, sh.z, sh.w, sh.w);
|
||||||
|
|
||||||
|
var p0 = new float3(a0.x, a0.y, h.x);
|
||||||
|
var p1 = new float3(a0.z, a0.w, h.y);
|
||||||
|
var p2 = new float3(a1.x, a1.y, h.z);
|
||||||
|
var p3 = new float3(a1.z, a1.w, h.w);
|
||||||
|
var norm = 1.79284291400159f - 0.85373472095314f * new float4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3));
|
||||||
|
p0 *= norm.x;
|
||||||
|
p1 *= norm.y;
|
||||||
|
p2 *= norm.z;
|
||||||
|
p3 *= norm.w;
|
||||||
|
|
||||||
|
var m = max(0.6f - new float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0f);
|
||||||
|
m = m * m;
|
||||||
|
return 42.0f * dot(m * m, new float4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
|
||||||
|
}
|
||||||
|
|
||||||
// Ashima Arts / Ian McEwan 2D simplex noise, kept component-explicit so
|
// Ashima Arts / Ian McEwan 2D simplex noise, kept component-explicit so
|
||||||
// the C# and HLSL mirrors preserve the same float32 evaluation order.
|
// the C# and HLSL mirrors preserve the same float32 evaluation order.
|
||||||
public static float simplex_noise(float2 value)
|
public static float snoise(float2 value)
|
||||||
{
|
{
|
||||||
var c = new float4(
|
var c = new float4(
|
||||||
0.211324865405187f,
|
0.211324865405187f,
|
||||||
|
|
@ -342,8 +546,8 @@ public static class math
|
||||||
x0.x + c.z,
|
x0.x + c.z,
|
||||||
x0.y + c.z);
|
x0.y + c.z);
|
||||||
|
|
||||||
i = simplex_mod289(i);
|
i = snoise_mod289(i);
|
||||||
var p = simplex_permute(simplex_permute(i.y + new float3(0.0f, i1.y, 1.0f)) + i.x + new float3(0.0f, i1.x, 1.0f));
|
var p = snoise_permute(snoise_permute(i.y + new float3(0.0f, i1.y, 1.0f)) + i.x + new float3(0.0f, i1.x, 1.0f));
|
||||||
var m = max(0.5f - new float3(
|
var m = max(0.5f - new float3(
|
||||||
dot(x0, x0),
|
dot(x0, x0),
|
||||||
x12.x * x12.x + x12.y * x12.y,
|
x12.x * x12.x + x12.y * x12.y,
|
||||||
|
|
@ -364,9 +568,11 @@ public static class math
|
||||||
return 130.0f * dot(m, g);
|
return 130.0f * dot(m, g);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static float2 simplex_mod289(float2 value) => value - floor(value * (1.0f / 289.0f)) * 289.0f;
|
private static float2 snoise_mod289(float2 value) => value - floor(value * (1.0f / 289.0f)) * 289.0f;
|
||||||
private static float3 simplex_mod289(float3 value) => value - floor(value * (1.0f / 289.0f)) * 289.0f;
|
private static float3 snoise_mod289(float3 value) => value - floor(value * (1.0f / 289.0f)) * 289.0f;
|
||||||
private static float3 simplex_permute(float3 value) => simplex_mod289(((value * 34.0f) + 1.0f) * value);
|
private static float4 snoise_mod289(float4 value) => value - floor(value * (1.0f / 289.0f)) * 289.0f;
|
||||||
|
private static float3 snoise_permute(float3 value) => snoise_mod289(((value * 34.0f) + 1.0f) * value);
|
||||||
|
private static float4 snoise_permute(float4 value) => snoise_mod289(((value * 34.0f) + 1.0f) * value);
|
||||||
|
|
||||||
public static float value_noise(float2 position)
|
public static float value_noise(float2 position)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,28 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace CultMath;
|
namespace CultMath;
|
||||||
|
|
||||||
public record struct quaternion(float x, float y, float z, float w)
|
/// <summary>
|
||||||
|
/// Not an HLSL type: shaders carry rotations as float4. Equality therefore stays
|
||||||
|
/// scalar (<c>==</c> returns bool) instead of following the component-wise vector rule.
|
||||||
|
/// </summary>
|
||||||
|
public struct quaternion : IEquatable<quaternion>
|
||||||
{
|
{
|
||||||
public static readonly quaternion identity = new(0.0f, 0.0f, 0.0f, 1.0f);
|
public static readonly quaternion identity = new(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
public float x;
|
||||||
|
public float y;
|
||||||
|
public float z;
|
||||||
|
public float w;
|
||||||
|
|
||||||
|
public quaternion(float x, float y, float z, float w)
|
||||||
|
{
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.z = z;
|
||||||
|
this.w = w;
|
||||||
|
}
|
||||||
|
|
||||||
public static quaternion LookRotation(float3 forward, float3 up)
|
public static quaternion LookRotation(float3 forward, float3 up)
|
||||||
{
|
{
|
||||||
forward = math.normalize(forward);
|
forward = math.normalize(forward);
|
||||||
|
|
@ -65,4 +84,12 @@ public record struct quaternion(float x, float y, float z, float w)
|
||||||
|
|
||||||
public static implicit operator quaternion(float4 value) => new(value.x, value.y, value.z, value.w);
|
public static implicit operator quaternion(float4 value) => new(value.x, value.y, value.z, value.w);
|
||||||
public static implicit operator float4(quaternion value) => new(value.x, value.y, value.z, value.w);
|
public static implicit operator float4(quaternion value) => new(value.x, value.y, value.z, value.w);
|
||||||
|
|
||||||
|
public static bool operator ==(quaternion left, quaternion right) => left.Equals(right);
|
||||||
|
public static bool operator !=(quaternion left, quaternion right) => !left.Equals(right);
|
||||||
|
|
||||||
|
public readonly bool Equals(quaternion other) => x.Equals(other.x) && y.Equals(other.y) && z.Equals(other.z) && w.Equals(other.w);
|
||||||
|
public override readonly bool Equals(object? obj) => obj is quaternion other && Equals(other);
|
||||||
|
public override readonly int GetHashCode() => HashCode.Combine(x, y, z, w);
|
||||||
|
public override readonly string ToString() => FormattableString.Invariant($"quaternion({x:R}, {y:R}, {z:R}, {w:R})");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.3.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
<PackageReference Include="xunit.v3" Version="3.2.0" />
|
<PackageReference Include="xunit.v3" Version="3.2.0" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" />
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,8 @@ public sealed class HlslMirrorTests
|
||||||
"cultmath_cubic_bezier",
|
"cultmath_cubic_bezier",
|
||||||
"cultmath_hash",
|
"cultmath_hash",
|
||||||
"cultmath_value_noise",
|
"cultmath_value_noise",
|
||||||
"cultmath_simplex_noise",
|
"cultmath_snoise(float2",
|
||||||
|
"cultmath_snoise(float3",
|
||||||
"cultmath_value_noise_bicubic",
|
"cultmath_value_noise_bicubic",
|
||||||
"cultmath_value_noise_texture",
|
"cultmath_value_noise_texture",
|
||||||
"cultmath_value_noise_texture_bicubic",
|
"cultmath_value_noise_texture_bicubic",
|
||||||
|
|
|
||||||
441
packages/cultmath/tests/CultMath.Tests/HlslSemanticsTests.cs
Normal file
441
packages/cultmath/tests/CultMath.Tests/HlslSemanticsTests.cs
Normal file
|
|
@ -0,0 +1,441 @@
|
||||||
|
using System.Text.Json;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace CultMath.Tests;
|
||||||
|
|
||||||
|
public sealed class HlslSemanticsTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ComponentsAndIndexersAreWritable()
|
||||||
|
{
|
||||||
|
var value = new float4(1.0f, 2.0f, 3.0f, 4.0f);
|
||||||
|
value.w = 9.0f;
|
||||||
|
value[0] = -1.0f;
|
||||||
|
|
||||||
|
Assert.Equal(new float4(-1.0f, 2.0f, 3.0f, 9.0f), value);
|
||||||
|
|
||||||
|
var cell = new int3(1, 2, 3);
|
||||||
|
cell[2] = 7;
|
||||||
|
Assert.Equal(new int3(1, 2, 7), cell);
|
||||||
|
|
||||||
|
ref var component = ref value.y;
|
||||||
|
component = 20.0f;
|
||||||
|
Assert.Equal(20.0f, value.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Float2SwizzlesReadAndWrite()
|
||||||
|
{
|
||||||
|
var value = new float2(1.0f, 2.0f);
|
||||||
|
Assert.Equal(new float2(2.0f, 1.0f), value.yx);
|
||||||
|
|
||||||
|
value.yx = new float2(5.0f, 6.0f);
|
||||||
|
Assert.Equal(new float2(6.0f, 5.0f), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Float3SwizzlesReadAndWrite()
|
||||||
|
{
|
||||||
|
var source = new float3(1.0f, 2.0f, 3.0f);
|
||||||
|
Assert.Equal(new float2(1.0f, 2.0f), source.xy);
|
||||||
|
Assert.Equal(new float2(1.0f, 3.0f), source.xz);
|
||||||
|
Assert.Equal(new float2(2.0f, 1.0f), source.yx);
|
||||||
|
Assert.Equal(new float2(2.0f, 3.0f), source.yz);
|
||||||
|
Assert.Equal(new float2(3.0f, 2.0f), source.zy);
|
||||||
|
Assert.Equal(new float3(1.0f, 2.0f, 3.0f), source.xyz);
|
||||||
|
Assert.Equal(new float3(1.0f, 3.0f, 2.0f), source.xzy);
|
||||||
|
Assert.Equal(new float3(2.0f, 3.0f, 1.0f), source.yzx);
|
||||||
|
Assert.Equal(new float3(3.0f, 2.0f, 1.0f), source.zyx);
|
||||||
|
|
||||||
|
Assert.Equal(new float3(8.0f, 9.0f, 3.0f), Write(source, (ref float3 v) => v.xy = new float2(8.0f, 9.0f)));
|
||||||
|
Assert.Equal(new float3(8.0f, 2.0f, 9.0f), Write(source, (ref float3 v) => v.xz = new float2(8.0f, 9.0f)));
|
||||||
|
Assert.Equal(new float3(9.0f, 8.0f, 3.0f), Write(source, (ref float3 v) => v.yx = new float2(8.0f, 9.0f)));
|
||||||
|
Assert.Equal(new float3(1.0f, 8.0f, 9.0f), Write(source, (ref float3 v) => v.yz = new float2(8.0f, 9.0f)));
|
||||||
|
Assert.Equal(new float3(1.0f, 9.0f, 8.0f), Write(source, (ref float3 v) => v.zy = new float2(8.0f, 9.0f)));
|
||||||
|
Assert.Equal(new float3(7.0f, 8.0f, 9.0f), Write(source, (ref float3 v) => v.xyz = new float3(7.0f, 8.0f, 9.0f)));
|
||||||
|
Assert.Equal(new float3(7.0f, 9.0f, 8.0f), Write(source, (ref float3 v) => v.xzy = new float3(7.0f, 8.0f, 9.0f)));
|
||||||
|
Assert.Equal(new float3(9.0f, 7.0f, 8.0f), Write(source, (ref float3 v) => v.yzx = new float3(7.0f, 8.0f, 9.0f)));
|
||||||
|
Assert.Equal(new float3(9.0f, 8.0f, 7.0f), Write(source, (ref float3 v) => v.zyx = new float3(7.0f, 8.0f, 9.0f)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Float4SwizzlesReadAndWrite()
|
||||||
|
{
|
||||||
|
var source = new float4(1.0f, 2.0f, 3.0f, 4.0f);
|
||||||
|
Assert.Equal(new float2(1.0f, 2.0f), source.xy);
|
||||||
|
Assert.Equal(new float3(1.0f, 2.0f, 3.0f), source.xyz);
|
||||||
|
Assert.Equal(new float3(1.0f, 2.0f, 4.0f), source.xyw);
|
||||||
|
Assert.Equal(new float3(2.0f, 3.0f, 1.0f), source.yzx);
|
||||||
|
|
||||||
|
var a = source;
|
||||||
|
a.xy = new float2(8.0f, 9.0f);
|
||||||
|
Assert.Equal(new float4(8.0f, 9.0f, 3.0f, 4.0f), a);
|
||||||
|
|
||||||
|
var b = source;
|
||||||
|
b.xyz = new float3(7.0f, 8.0f, 9.0f);
|
||||||
|
Assert.Equal(new float4(7.0f, 8.0f, 9.0f, 4.0f), b);
|
||||||
|
|
||||||
|
var c = source;
|
||||||
|
c.xyw = new float3(7.0f, 8.0f, 9.0f);
|
||||||
|
Assert.Equal(new float4(7.0f, 8.0f, 3.0f, 9.0f), c);
|
||||||
|
|
||||||
|
var d = source;
|
||||||
|
d.yzx = new float3(7.0f, 8.0f, 9.0f);
|
||||||
|
Assert.Equal(new float4(9.0f, 7.0f, 8.0f, 4.0f), d);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GeneratedSwizzlesCoverRepeatsColorNamesAndAllVectorFamilies()
|
||||||
|
{
|
||||||
|
var v = new float4(1.0f, 2.0f, 3.0f, 4.0f);
|
||||||
|
Assert.Equal(new float4(4.0f, 4.0f, 1.0f, 2.0f), v.wwxy);
|
||||||
|
Assert.Equal(new float3(3.0f, 2.0f, 1.0f), v.bgr);
|
||||||
|
Assert.Equal(4.0f, v.a);
|
||||||
|
Assert.Equal(new float4(2.0f, 2.0f, 2.0f, 2.0f), new float2(1.0f, 2.0f).yyyy);
|
||||||
|
|
||||||
|
v.wx = new float2(9.0f, 8.0f);
|
||||||
|
Assert.Equal(new float4(8.0f, 2.0f, 3.0f, 9.0f), v);
|
||||||
|
v.r = 0.5f;
|
||||||
|
v.gb += new float2(1.0f, 1.0f);
|
||||||
|
Assert.Equal(new float4(0.5f, 3.0f, 4.0f, 9.0f), v);
|
||||||
|
|
||||||
|
Assert.Equal(new int3(3, 1, 1), new int3(1, 2, 3).zxx);
|
||||||
|
Assert.Equal(new bool2(false, true), new bool4(true, false, false, true).zw);
|
||||||
|
Assert.Equal(new double2(2.0, 1.0), new double3(1.0, 2.0, 3.0).yx);
|
||||||
|
|
||||||
|
var cell = new int4(1, 2, 3, 4);
|
||||||
|
cell.xyz = new int3(7, 8, 9);
|
||||||
|
Assert.Equal(new int4(7, 8, 9, 4), cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MixedConstructorsMatchHlsl()
|
||||||
|
{
|
||||||
|
var xy = new float2(1.0f, 2.0f);
|
||||||
|
var zw = new float2(3.0f, 4.0f);
|
||||||
|
var expected = new float4(1.0f, 2.0f, 3.0f, 4.0f);
|
||||||
|
|
||||||
|
Assert.Equal(expected, math.float4(xy, zw));
|
||||||
|
Assert.Equal(expected, math.float4(1.0f, new float3(2.0f, 3.0f, 4.0f)));
|
||||||
|
Assert.Equal(expected, math.float4(new float3(1.0f, 2.0f, 3.0f), 4.0f));
|
||||||
|
Assert.Equal(expected, math.float4(xy, 3.0f, 4.0f));
|
||||||
|
Assert.Equal(expected, math.float4(1.0f, 2.0f, zw));
|
||||||
|
Assert.Equal(expected, math.float4(1.0f, new float2(2.0f, 3.0f), 4.0f));
|
||||||
|
Assert.Equal(expected, new float4(xy, zw));
|
||||||
|
Assert.Equal(new float3(1.0f, 2.0f, 3.0f), math.float3(1.0f, zw - 1.0f));
|
||||||
|
Assert.Equal(new int4(1, 2, 3, 4), math.int4(new int2(1, 2), new int2(3, 4)));
|
||||||
|
Assert.Equal(new bool3(true, false, true), math.bool3(true, new bool2(false, true)));
|
||||||
|
Assert.Equal(new double3(1.0, 2.0, 3.0), new double3(new double2(1.0, 2.0), 3.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MatrixRowElementsAreWritable()
|
||||||
|
{
|
||||||
|
var m = float3x3.identity;
|
||||||
|
m[1][2] = 5.0f;
|
||||||
|
m[2].x = 7.0f;
|
||||||
|
m._m00 = 2.0f;
|
||||||
|
|
||||||
|
Assert.Equal(5.0f, m._m12);
|
||||||
|
Assert.Equal(7.0f, m._m20);
|
||||||
|
Assert.Equal(math.float3x3(2.0f, 0.0f, 0.0f, 0.0f, 1.0f, 5.0f, 7.0f, 0.0f, 1.0f), m);
|
||||||
|
Assert.Equal(float3x3.identity, math.float3x3(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f));
|
||||||
|
|
||||||
|
var r = float2x2.identity;
|
||||||
|
r[0][1] = 3.0f;
|
||||||
|
Assert.Equal(math.float2x2(1.0f, 3.0f, 0.0f, 1.0f), r);
|
||||||
|
Assert.Equal(new float2(0.0f, 1.0f), float2x2.identity[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IntrinsicEdgeCasesFollowHlsl()
|
||||||
|
{
|
||||||
|
Assert.True(math.any(new float3(0.0f, 0.0f, -2.0f)));
|
||||||
|
Assert.False(math.any(float4.zero));
|
||||||
|
Assert.True(math.all(new int2(1, -1)));
|
||||||
|
Assert.False(math.all(new int3(1, 0, 1)));
|
||||||
|
Assert.True(math.any(new float2(float.NaN, 0.0f)));
|
||||||
|
|
||||||
|
Assert.Equal(new int3(1, 2, 3), math.min(new int3(1, 5, 3), new int3(4, 2, 6)));
|
||||||
|
Assert.Equal(new int2(4, 5), math.max(new int2(1, 5), new int2(4, 2)));
|
||||||
|
Assert.Equal(new int4(1, 2, 0, 3), math.abs(new int4(-1, 2, 0, -3)));
|
||||||
|
Assert.Equal(7, math.abs(-7));
|
||||||
|
|
||||||
|
Assert.Equal(1.0f, math.step(1.0f, float.NaN));
|
||||||
|
Assert.Equal(1.0f, math.step(float.NaN, 1.0f));
|
||||||
|
Assert.Equal(1.0f, math.step(1.0f, 1.0f));
|
||||||
|
|
||||||
|
// DXIL FMin/FMax/Saturate: a NaN operand returns the other; max(a, b) is a >= b ? a : b.
|
||||||
|
Assert.Equal(1.0f, math.min(float.NaN, 1.0f));
|
||||||
|
Assert.Equal(1.0f, math.min(1.0f, float.NaN));
|
||||||
|
Assert.Equal(1.0f, math.max(float.NaN, 1.0f));
|
||||||
|
Assert.Equal(1.0f, math.max(1.0f, float.NaN));
|
||||||
|
Assert.True(float.IsNaN(math.max(float.NaN, float.NaN)));
|
||||||
|
Assert.Equal(2.0f, math.clamp(float.NaN, 2.0f, 3.0f));
|
||||||
|
Assert.Equal(new float3(0.0f, 1.0f, 0.0f), math.saturate(new float3(float.NaN, 2.0f, -1.0f)));
|
||||||
|
Assert.Equal(new float2(-1.0f, 4.0f), math.max(new float2(float.NaN, 4.0f), new float2(-1.0f, float.NaN)));
|
||||||
|
static int Bits(float value) => BitConverter.SingleToInt32Bits(value);
|
||||||
|
Assert.Equal(Bits(-0.0f), Bits(math.max(-0.0f, 0.0f)));
|
||||||
|
Assert.Equal(Bits(0.0f), Bits(math.min(-0.0f, 0.0f)));
|
||||||
|
Assert.Equal(Bits(0.0f), Bits(math.saturate(-0.0f)));
|
||||||
|
Assert.Equal(1.0, math.min(double.NaN, 1.0));
|
||||||
|
Assert.Equal(1.0, math.min(1.0, double.NaN));
|
||||||
|
Assert.Equal(1.0, math.max(double.NaN, 1.0));
|
||||||
|
Assert.Equal(1.0, math.max(1.0, double.NaN));
|
||||||
|
Assert.True(double.IsNaN(math.max(double.NaN, double.NaN)));
|
||||||
|
static long DoubleBits(double value) => BitConverter.DoubleToInt64Bits(value);
|
||||||
|
Assert.Equal(DoubleBits(-0.0), DoubleBits(math.max(-0.0, 0.0)));
|
||||||
|
Assert.Equal(DoubleBits(0.0), DoubleBits(math.min(-0.0, 0.0)));
|
||||||
|
|
||||||
|
Assert.Equal(0, math.sign(float.NaN));
|
||||||
|
Assert.Equal(-1, math.sign(-0.5f));
|
||||||
|
Assert.Equal(new int3(-1, 0, 1), math.sign(new float3(-2.0f, 0.0f, 4.0f)));
|
||||||
|
Assert.Equal(new int2(1, -1), math.sign(new int2(9, -9)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ComparisonOperatorsReturnComponentWiseBoolVectors()
|
||||||
|
{
|
||||||
|
var a = new float3(1.0f, 2.0f, 3.0f);
|
||||||
|
var b = new float3(3.0f, 2.0f, 1.0f);
|
||||||
|
|
||||||
|
Assert.Equal(new bool3(true, false, false), a < b);
|
||||||
|
Assert.Equal(new bool3(false, false, true), a > b);
|
||||||
|
Assert.Equal(new bool3(true, true, false), a <= b);
|
||||||
|
Assert.Equal(new bool3(false, true, true), a >= b);
|
||||||
|
Assert.Equal(new bool3(false, true, false), a == b);
|
||||||
|
Assert.Equal(new bool3(true, false, true), a != b);
|
||||||
|
|
||||||
|
Assert.Equal(new bool3(true, false, false), a < 2.0f);
|
||||||
|
Assert.Equal(new bool3(false, false, true), 2.0f < a);
|
||||||
|
Assert.Equal(new bool2(true, false), new float2(0.0f, 1.0f) == 0.0f);
|
||||||
|
Assert.Equal(new bool4(false, true, true, true), new float4(0.0f, 1.0f, 2.0f, 3.0f) >= 1.0f);
|
||||||
|
Assert.Equal(new bool2(false, true), new int2(1, 2) > new int2(1, 1));
|
||||||
|
Assert.Equal(new bool3(true, false, true), new int3(1, 2, 3) != new int3(0, 2, 4));
|
||||||
|
Assert.Equal(new bool4(true, true, false, false), new int4(1, 2, 3, 4) <= new int4(1, 2, 2, 2));
|
||||||
|
Assert.Equal(new bool2(true, false), new double2(0.5, 1.0) < new double2(1.0, 1.0));
|
||||||
|
Assert.Equal(new bool3(false, false, true), new double3(1.0, 1.0, 2.0) > new double3(1.0, 1.0, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ScalarOperatorsApplyPerComponentOnBothSides()
|
||||||
|
{
|
||||||
|
var value = new float3(1.0f, 2.0f, 4.0f);
|
||||||
|
|
||||||
|
Assert.Equal(new float3(3.0f, 2.0f, 0.0f), 4.0f - value);
|
||||||
|
Assert.Equal(new float3(0.0f, 1.0f, 3.0f), value - 1.0f);
|
||||||
|
Assert.Equal(new float3(2.0f, 3.0f, 5.0f), 1.0f + value);
|
||||||
|
Assert.Equal(new float3(4.0f, 2.0f, 1.0f), 4.0f / value);
|
||||||
|
Assert.Equal(new float3(0.5f, 1.0f, 2.0f), value / 2.0f);
|
||||||
|
Assert.Equal(new float3(2.0f, 4.0f, 8.0f), 2.0f * value);
|
||||||
|
Assert.Equal(new float3(1.0f, 0.0f, 0.0f), value % 2.0f);
|
||||||
|
Assert.Equal(new float2(1.5f, 2.5f), new float2(1.0f, 2.0f) + 0.5f);
|
||||||
|
Assert.Equal(new float4(0.0f, 1.0f, 2.0f, 3.0f), new float4(1.0f, 2.0f, 3.0f, 4.0f) - 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EqualsAndHashCodeStayValueBasedForDictionaryKeys()
|
||||||
|
{
|
||||||
|
var map = new Dictionary<float3, string> { [new float3(1.0f, 2.0f, 3.0f)] = "hit" };
|
||||||
|
Assert.Equal("hit", map[new float3(1.0f, 2.0f, 3.0f)]);
|
||||||
|
Assert.False(map.ContainsKey(new float3(1.0f, 2.0f, 3.5f)));
|
||||||
|
|
||||||
|
var cells = new HashSet<int2> { new int2(3, 4) };
|
||||||
|
Assert.Contains(new int2(3, 4), cells);
|
||||||
|
Assert.Contains(new int3(1, 2, 3), new HashSet<int3> { new int3(1, 2, 3) });
|
||||||
|
|
||||||
|
var nan = new float2(float.NaN, 0.0f);
|
||||||
|
var sameNan = nan;
|
||||||
|
Assert.True(nan.Equals(sameNan));
|
||||||
|
Assert.False((nan == sameNan).x);
|
||||||
|
|
||||||
|
Assert.True(new quaternion(0.0f, 0.0f, 0.0f, 1.0f) == quaternion.identity);
|
||||||
|
Assert.Equal(float2x2.identity, math.float2x2(1.0f, 0.0f, 0.0f, 1.0f));
|
||||||
|
Assert.Equal("float3(1, 2.5, -3)", new float3(1.0f, 2.5f, -3.0f).ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AnyAllAndSelectFollowHlsl()
|
||||||
|
{
|
||||||
|
Assert.True(math.any(new bool2(false, true)));
|
||||||
|
Assert.False(math.any(bool3.@false));
|
||||||
|
Assert.True(math.all(bool4.@true));
|
||||||
|
Assert.False(math.all(new bool3(true, false, true)));
|
||||||
|
|
||||||
|
var mask = new bool3(true, false, true);
|
||||||
|
Assert.Equal(new float3(1.0f, 20.0f, 3.0f), math.select(mask, new float3(1.0f, 2.0f, 3.0f), new float3(10.0f, 20.0f, 30.0f)));
|
||||||
|
Assert.Equal(7.0f, math.select(true, 7.0f, 8.0f));
|
||||||
|
Assert.Equal(new float2(0.0f, 5.0f), math.select(new bool2(false, true), math.float2(5.0f), math.float2(0.0f)));
|
||||||
|
Assert.Equal(new float4(1.0f, 0.0f, 1.0f, 0.0f), math.select(new bool4(true, false, true, false), 1.0f, 0.0f));
|
||||||
|
|
||||||
|
Assert.Equal(new bool2(false, true), !new bool2(true, false));
|
||||||
|
Assert.Equal(new bool3(true, false, false), new bool3(true, true, false) & new bool3(true, false, true));
|
||||||
|
Assert.Equal(new bool3(true, true, true), new bool3(true, true, false) | new bool3(true, false, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddedIntrinsicsAreComponentWise()
|
||||||
|
{
|
||||||
|
Assert.Equal(MathF.Log(3.0f), math.log(3.0f));
|
||||||
|
Assert.Equal(new float3(0.0f, MathF.Log(2.0f), MathF.Log(4.0f)), math.log(new float3(1.0f, 2.0f, 4.0f)));
|
||||||
|
Assert.Equal(new float2(8.0f, 9.0f), math.pow(new float2(2.0f, 3.0f), new float2(3.0f, 2.0f)));
|
||||||
|
Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), math.sqrt(new float4(1.0f, 4.0f, 9.0f, 16.0f)));
|
||||||
|
var sines = math.sin(new float3(0.0f, math.HALF_PI, math.PI));
|
||||||
|
Assert.Equal(0.0f, sines.x, precision: 5);
|
||||||
|
Assert.Equal(1.0f, sines.y, precision: 5);
|
||||||
|
Assert.Equal(0.0f, sines.z, precision: 5);
|
||||||
|
var cosines = math.cos(new float2(0.0f, math.PI));
|
||||||
|
Assert.Equal(1.0f, cosines.x, precision: 5);
|
||||||
|
Assert.Equal(-1.0f, cosines.y, precision: 5);
|
||||||
|
var arccosines = math.acos(new float3(1.0f, 0.0f, -1.0f));
|
||||||
|
Assert.Equal(0.0f, arccosines.x, precision: 5);
|
||||||
|
Assert.Equal(math.HALF_PI, arccosines.y, precision: 5);
|
||||||
|
Assert.Equal(math.PI, arccosines.z, precision: 5);
|
||||||
|
Assert.Equal(math.atan2(1.0f, -1.0f), math.atan2(new float2(1.0f, 0.0f), new float2(-1.0f, 1.0f)).x);
|
||||||
|
Assert.Equal(0.0f, math.atan2(new float4(1.0f, 0.0f, 1.0f, 0.0f), new float4(1.0f, 1.0f, 1.0f, 1.0f)).y);
|
||||||
|
Assert.Equal(2.5, math.min(2.5, 3.5));
|
||||||
|
Assert.Equal(3.5, math.max(2.5, 3.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IntVectorsPromoteToFloatVectors()
|
||||||
|
{
|
||||||
|
float3 promoted3 = new int3(1, 2, 3);
|
||||||
|
float4 promoted4 = new int4(1, 2, 3, 4);
|
||||||
|
|
||||||
|
Assert.Equal(new float3(1.0f, 2.0f, 3.0f), promoted3);
|
||||||
|
Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), promoted4);
|
||||||
|
Assert.Equal(new int3(2, 4, 6), new int3(1, 2, 3) * 2);
|
||||||
|
Assert.Equal(new int4(1, 0, 1, 0), new int4(3, 4, 5, 6) % 2);
|
||||||
|
Assert.Equal(new int3(1, 2, 3), math.int3(1, 2, 3));
|
||||||
|
Assert.Equal(new int4(5, 5, 5, 5), math.int4(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MatrixMulFollowsHlslRowMajorConvention()
|
||||||
|
{
|
||||||
|
var m = math.float2x2(1.0f, 2.0f, 3.0f, 4.0f);
|
||||||
|
var v = new float2(5.0f, 6.0f);
|
||||||
|
|
||||||
|
Assert.Equal(new float2(1.0f, 2.0f), m[0]);
|
||||||
|
Assert.Equal(new float2(17.0f, 39.0f), math.mul(m, v));
|
||||||
|
Assert.Equal(new float2(23.0f, 34.0f), math.mul(v, m));
|
||||||
|
Assert.Equal(math.float2x2(7.0f, 10.0f, 15.0f, 22.0f), math.mul(m, m));
|
||||||
|
|
||||||
|
var m3 = math.float3x3(new float3(1.0f, 2.0f, 3.0f), new float3(4.0f, 5.0f, 6.0f), new float3(7.0f, 8.0f, 9.0f));
|
||||||
|
var v3 = new float3(1.0f, 0.0f, -1.0f);
|
||||||
|
Assert.Equal(new float3(-2.0f, -2.0f, -2.0f), math.mul(m3, v3));
|
||||||
|
Assert.Equal(new float3(-6.0f, -6.0f, -6.0f), math.mul(v3, m3));
|
||||||
|
Assert.Equal(math.float3x3(30.0f, 36.0f, 42.0f, 66.0f, 81.0f, 96.0f, 102.0f, 126.0f, 150.0f), math.mul(m3, m3));
|
||||||
|
Assert.Equal(m3, math.mul(m3, float3x3.identity));
|
||||||
|
|
||||||
|
m3[1] = new float3(0.0f, 1.0f, 0.0f);
|
||||||
|
Assert.Equal(1.0f, m3._m11);
|
||||||
|
Assert.Equal(12.0f, math.mul(3.0f, 4.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Float2x2RotateMatchesRotateAndUnityMathematics()
|
||||||
|
{
|
||||||
|
var rotation = float2x2.Rotate(0.5f);
|
||||||
|
var column = math.mul(rotation, new float2(1.0f, 0.0f));
|
||||||
|
var row = math.mul(new float2(1.0f, 0.0f), rotation);
|
||||||
|
var reference = math.rotate(new float2(1.0f, 0.0f), 0.5f);
|
||||||
|
|
||||||
|
Assert.Equal(reference.x, column.x, precision: 6);
|
||||||
|
Assert.Equal(reference.y, column.y, precision: 6);
|
||||||
|
|
||||||
|
// Unity.Mathematics 1.x reference values: mul(float2(1, 0), float2x2.Rotate(0.5f)).
|
||||||
|
Assert.Equal(0.87758255f, row.x, precision: 6);
|
||||||
|
Assert.Equal(-0.47942555f, row.y, precision: 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Float3x3EulerMatchesUnityMathematicsReference()
|
||||||
|
{
|
||||||
|
// Unity.Mathematics 1.x reference values captured from float3x3.Euler.
|
||||||
|
AssertRows(
|
||||||
|
float3x3.Euler(new float3(0.4f, -0.7f, 1.3f), math.RotationOrder.YXZ),
|
||||||
|
0.44632244f, -0.8874959f, 0.114662126f,
|
||||||
|
0.6698625f, 0.24638279f, -0.700414f,
|
||||||
|
0.59336376f, 0.38941833f, 0.70446634f);
|
||||||
|
AssertRows(
|
||||||
|
float3x3.Euler(new float3(0.4f, -0.7f, 1.3f)),
|
||||||
|
-0.037133574f, -0.8040775f, -0.59336376f,
|
||||||
|
0.8874959f, 0.24638279f, -0.38941833f,
|
||||||
|
0.45931715f, -0.5410684f, 0.70446634f);
|
||||||
|
|
||||||
|
// Aetheria ActionGameManager view direction: mul(float3(0, 0, 1), Euler(float3(yawPitch.yx, 0), YXZ)).
|
||||||
|
var yawPitch = new float2(0.3f, -1.1f);
|
||||||
|
var view = math.mul(new float3(0.0f, 0.0f, 1.0f), float3x3.Euler(new float3(yawPitch.yx, 0.0f), math.RotationOrder.YXZ));
|
||||||
|
Assert.Equal(-0.13404681f, view.x, precision: 5);
|
||||||
|
Assert.Equal(-0.8912074f, view.y, precision: 5);
|
||||||
|
Assert.Equal(0.4333369f, view.z, precision: 5);
|
||||||
|
|
||||||
|
var composed = math.mul(float3x3.RotateZ(0.2f), math.mul(float3x3.RotateY(0.3f), float3x3.RotateX(0.1f)));
|
||||||
|
AssertRows(float3x3.Euler(0.1f, 0.3f, 0.2f, math.RotationOrder.XYZ),
|
||||||
|
composed._m00, composed._m01, composed._m02,
|
||||||
|
composed._m10, composed._m11, composed._m12,
|
||||||
|
composed._m20, composed._m21, composed._m22);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(0.0f, 0.0f, 0.0f, -0.41219875f)]
|
||||||
|
[InlineData(0.25f, -0.5f, 1.75f, 0.5250269f)]
|
||||||
|
[InlineData(12.25f, -4.5f, 3.125f, 0.008141451f)]
|
||||||
|
[InlineData(-7.3f, 2.9f, 101.4f, 0.578015f)]
|
||||||
|
public void Snoise3MatchesUnityMathematicsReference(float x, float y, float z, float expected)
|
||||||
|
{
|
||||||
|
Assert.Equal(expected, math.snoise(new float3(x, y, z)), precision: 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RandomFloat3OverloadsShareOneDrawOrder()
|
||||||
|
{
|
||||||
|
var a = new Random(99);
|
||||||
|
var b = new Random(99);
|
||||||
|
var c = new Random(99);
|
||||||
|
|
||||||
|
var unit = a.NextFloat3();
|
||||||
|
Assert.Equal(new float3(b.NextFloat(), b.NextFloat(), b.NextFloat()), unit);
|
||||||
|
Assert.Equal(unit * new float3(2.0f, 4.0f, 8.0f), c.NextFloat3(new float3(2.0f, 4.0f, 8.0f)));
|
||||||
|
|
||||||
|
var d = new Random(99);
|
||||||
|
Assert.Equal(math.float3(-1.0f) + unit * 2.0f, d.NextFloat3(math.float3(-1.0f), math.float3(1.0f)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CoreAssemblyStaysEngineFree()
|
||||||
|
{
|
||||||
|
var references = typeof(math).Assembly.GetReferencedAssemblies();
|
||||||
|
Assert.DoesNotContain(references, reference => reference.Name!.StartsWith("UnityEngine", StringComparison.Ordinal));
|
||||||
|
|
||||||
|
var root = new DirectoryInfo(AppContext.BaseDirectory);
|
||||||
|
while (root is not null && !File.Exists(Path.Combine(root.FullName, "packages", "cultmath", "src", "CultMath", "CultMath.asmdef")))
|
||||||
|
{
|
||||||
|
root = root.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.NotNull(root);
|
||||||
|
var asmdefPath = Path.Combine(root!.FullName, "packages", "cultmath", "src", "CultMath", "CultMath.asmdef");
|
||||||
|
using var asmdef = JsonDocument.Parse(File.ReadAllText(asmdefPath));
|
||||||
|
Assert.True(asmdef.RootElement.GetProperty("noEngineReferences").GetBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float3 Write(float3 value, WriteAction action)
|
||||||
|
{
|
||||||
|
action(ref value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private delegate void WriteAction(ref float3 value);
|
||||||
|
|
||||||
|
private static void AssertRows(float3x3 m, params float[] expected)
|
||||||
|
{
|
||||||
|
var actual = new[] { m._m00, m._m01, m._m02, m._m10, m._m11, m._m12, m._m20, m._m21, m._m22 };
|
||||||
|
for (var index = 0; index < expected.Length; index++)
|
||||||
|
{
|
||||||
|
Assert.Equal(expected[index], actual[index], precision: 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7164d00a023e43578a31897347d8f106
|
||||||
|
|
@ -0,0 +1,203 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Xunit;
|
||||||
|
using static CultMath.math;
|
||||||
|
|
||||||
|
namespace CultMath.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The real shader mirror, <c>shaders/CultMath.hlsl</c>, compiles as C# against CultMath under
|
||||||
|
/// <c>using static CultMath.math;</c> after exactly the source transformations listed in
|
||||||
|
/// docs/design.md (HLSL Target: Source Transformations). <see cref="TransformHlslToCSharp"/> is
|
||||||
|
/// that list in code; if a body needs anything else, this test fails.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class HlslSourceCompatibilityTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ShaderMirrorBodiesCompileAsCSharp()
|
||||||
|
{
|
||||||
|
var (assembly, errors) = CompileShaderMirror();
|
||||||
|
Assert.True(assembly is not null, "HLSL mirror did not compile as C#:" + Environment.NewLine + string.Join(Environment.NewLine, errors));
|
||||||
|
|
||||||
|
var shader = assembly!.GetType("CultMathHlsl.HlslShader")!;
|
||||||
|
Assert.NotNull(ShaderFunction(shader, "cultmath_snoise", typeof(float3)));
|
||||||
|
Assert.NotNull(ShaderFunction(shader, "cultmath_snoise", typeof(float2)));
|
||||||
|
Assert.NotNull(ShaderFunction(shader, "cultmath_value_noise_bicubic", typeof(float2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// HLSL functions carry no access modifier, so they compile as private instance methods.
|
||||||
|
private static MethodInfo? ShaderFunction(Type shader, string name, params Type[] parameters) =>
|
||||||
|
shader.GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic, null, parameters, null);
|
||||||
|
|
||||||
|
// Mirror internals with no public C# math counterpart. Any other cultmath_* function must match one.
|
||||||
|
private static readonly string[] MirrorOnly = { "cultmath_snoise_mod289", "cultmath_snoise_permute" };
|
||||||
|
|
||||||
|
private static readonly float[] Specials = { 0.0f, -0.0f, 1.0f, -1.0f, 0.5f, -2.75f, 3.0f, 1.0e-7f, 1.0e4f, -1.0e4f, 1.0e7f, float.NaN };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Every mirror function with a C# math counterpart returns the same bits (any NaN equals any NaN;
|
||||||
|
/// -0 differs from 0) over random inputs, every special value in every argument (ties, zeros,
|
||||||
|
/// negatives, NaN, large values, integer lattice points), special pairs, and vectors whose first two
|
||||||
|
/// components tie (x0.x == x0.y in snoise). This proves the text of CultMath.hlsl computes the same
|
||||||
|
/// float32 results as C# math on the CPU. It does not prove GPU agreement: driver sin precision alone
|
||||||
|
/// makes cultmath_hash and value noise differ bit for bit on hardware. The mirror's intrinsics are C#
|
||||||
|
/// math itself (it compiles under using static CultMath.math), so this proves the composition in the
|
||||||
|
/// text, not the intrinsic rules; HlslSemanticsTests alone pins those.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void EveryMirrorFunctionMatchesCSharpMath()
|
||||||
|
{
|
||||||
|
var (assembly, errors) = CompileShaderMirror();
|
||||||
|
Assert.True(assembly is not null, string.Join(Environment.NewLine, errors));
|
||||||
|
var shaderType = assembly!.GetType("CultMathHlsl.HlslShader")!;
|
||||||
|
var shader = Activator.CreateInstance(shaderType);
|
||||||
|
|
||||||
|
var random = new System.Random(0x5EED);
|
||||||
|
float Next() => random.NextSingle() * 200.0f - 100.0f;
|
||||||
|
var cases = new List<Func<int, float>>();
|
||||||
|
foreach (var s in Specials) cases.Add(_ => s);
|
||||||
|
for (var k = 0; k < Specials.Length; k++) { var o = k; cases.Add(i => Specials[(i + o + 1) % Specials.Length]); }
|
||||||
|
for (var k = 0; k < 64; k++) { var values = Enumerable.Range(0, 32).Select(_ => Next()).ToArray(); cases.Add(i => values[i]); }
|
||||||
|
for (var k = 0; k < 16; k++) { var values = Enumerable.Range(0, 32).Select(_ => Next()).ToArray(); cases.Add(i => values[i & ~1]); }
|
||||||
|
|
||||||
|
var mismatches = new List<string>();
|
||||||
|
var compared = new HashSet<string>();
|
||||||
|
foreach (var mirror in shaderType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).Where(m => m.Name.StartsWith("cultmath_")))
|
||||||
|
{
|
||||||
|
var types = mirror.GetParameters().Select(p => p.ParameterType).ToArray();
|
||||||
|
var counterpart = typeof(math).GetMethod(mirror.Name["cultmath_".Length..], BindingFlags.Public | BindingFlags.Static, null, types, null);
|
||||||
|
if (counterpart is null)
|
||||||
|
{
|
||||||
|
Assert.Contains(mirror.Name, MirrorOnly);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
compared.Add(mirror.Name);
|
||||||
|
foreach (var input in cases)
|
||||||
|
{
|
||||||
|
var slot = 0;
|
||||||
|
var args = types.Select(t => t == typeof(float) ? (object)input(slot++)
|
||||||
|
: Activator.CreateInstance(t, Enumerable.Range(0, Components(t)).Select(_ => (object)input(slot++)).ToArray())!).ToArray();
|
||||||
|
var expected = Floats(counterpart.Invoke(null, args)!);
|
||||||
|
var actual = Floats(mirror.Invoke(shader, args)!);
|
||||||
|
if (expected.Length != actual.Length || expected.Zip(actual).Any(p => !(float.IsNaN(p.First) && float.IsNaN(p.Second))
|
||||||
|
&& BitConverter.SingleToInt32Bits(p.First) != BitConverter.SingleToInt32Bits(p.Second)))
|
||||||
|
{
|
||||||
|
mismatches.Add($"{mirror.Name}({string.Join(", ", args.Select(a => string.Join(" ", Floats(a))))}): C# {string.Join(" ", expected)}, mirror {string.Join(" ", actual)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.True(mismatches.Count == 0, $"{mismatches.Count} mismatches:{Environment.NewLine}{string.Join(Environment.NewLine, mismatches.Take(12))}");
|
||||||
|
Assert.Equal(26, compared.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int Components(Type t) => t.GetFields().Count(f => f.FieldType == typeof(float) && !f.IsStatic);
|
||||||
|
|
||||||
|
private static float[] Floats(object value) => value is float f ? new[] { f }
|
||||||
|
: value.GetType().GetFields().Where(field => field.FieldType == typeof(float) && !field.IsStatic).Select(field => (float)field.GetValue(value)!).ToArray();
|
||||||
|
|
||||||
|
/// <summary>The documented HLSL-to-C# transformations, and nothing else.</summary>
|
||||||
|
internal static string TransformHlslToCSharp(string hlsl)
|
||||||
|
{
|
||||||
|
// 1. Include guards (#ifndef/#define/#endif) are dropped; C# has no textual include.
|
||||||
|
var source = Regex.Replace(hlsl, @"(?m)^\s*#(ifndef|define|endif)\b.*$", string.Empty);
|
||||||
|
|
||||||
|
// 2. Functions taking Texture2D/SamplerState resources are dropped; GPU resources have
|
||||||
|
// no CultMath analog.
|
||||||
|
source = Regex.Replace(source, @"(?ms)^\w+ \w+\([^)]*\b(Texture2D|SamplerState)\b[^)]*\)\s*\{.*?^\}[ \t]*\r?$", string.Empty);
|
||||||
|
|
||||||
|
// 3. File-scope `static const` becomes `const` (C# consts are implicitly static).
|
||||||
|
source = Regex.Replace(source, @"(?m)^static const ", "const ");
|
||||||
|
|
||||||
|
// 4. Floating literals gain the `f` suffix; an unsuffixed C# literal is a double.
|
||||||
|
source = Regex.Replace(source, @"(?<![\w.])(\d+\.\d*(?:[eE][+-]?\d+)?|\d+[eE][+-]?\d+)(?![\w.])", "$1f");
|
||||||
|
|
||||||
|
// 5. The file body is wrapped in a class; C# has no free functions.
|
||||||
|
return "using CultMath;\nusing static CultMath.math;\nnamespace CultMathHlsl;\npublic class HlslShader\n{\n" + source + "\n}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (Assembly? Assembly, IReadOnlyList<string> Errors) CompileShaderMirror()
|
||||||
|
{
|
||||||
|
var hlsl = File.ReadAllText(Path.Combine(FindCultMathRoot(), "shaders", "CultMath.hlsl"));
|
||||||
|
var tree = CSharpSyntaxTree.ParseText(TransformHlslToCSharp(hlsl));
|
||||||
|
var references = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")!)
|
||||||
|
.Split(Path.PathSeparator)
|
||||||
|
.Append(typeof(math).Assembly.Location)
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Select(path => MetadataReference.CreateFromFile(path));
|
||||||
|
var compilation = CSharpCompilation.Create(
|
||||||
|
"CultMathHlslMirror",
|
||||||
|
new[] { tree },
|
||||||
|
references,
|
||||||
|
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||||
|
|
||||||
|
using var stream = new MemoryStream();
|
||||||
|
var result = compilation.Emit(stream);
|
||||||
|
var errors = result.Diagnostics
|
||||||
|
.Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
|
||||||
|
.Select(diagnostic => diagnostic.ToString())
|
||||||
|
.ToList();
|
||||||
|
return (result.Success ? Assembly.Load(stream.ToArray()) : null, errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FindCultMathRoot()
|
||||||
|
{
|
||||||
|
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
||||||
|
while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "packages", "cultmath", "shaders", "CultMath.hlsl")))
|
||||||
|
{
|
||||||
|
directory = directory.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Path.Combine(directory?.FullName ?? throw new InvalidOperationException("CultMath package root not found."), "packages", "cultmath");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Hand-written HLSL-shaped host code: swizzles, mixed constructors, matrix rows. ----
|
||||||
|
static float3 shade(float3 position, float3 normal, float2 wind, float time)
|
||||||
|
{
|
||||||
|
float3 p = position;
|
||||||
|
p.xz = p.xz + wind * time;
|
||||||
|
p.y = saturate(p.y);
|
||||||
|
|
||||||
|
float3x3 basis = float3x3(float3(1, 0, 0), float3(0, 1, 0), float3(0, 0, 1));
|
||||||
|
basis[1][1] = 1.0f;
|
||||||
|
float3 n = normalize(mul(basis, normal));
|
||||||
|
float ndotl = saturate(dot(n, normalize(float3(0.3f, 1, 0.2f))));
|
||||||
|
float4 color = float4(lerp(float3(0.1f, 0.1f, 0.2f), float3(1, 0.9f, 0.7f), ndotl), 1);
|
||||||
|
|
||||||
|
if (any(p < 0.0f))
|
||||||
|
color.rgb *= 0.5f;
|
||||||
|
if (all(abs(n) <= 1.0f))
|
||||||
|
color.bgr = color.bgr * float3(1, 1, 1);
|
||||||
|
|
||||||
|
color.rgb = select(p > 10.0f, float3(0), color.rgb);
|
||||||
|
float2 uv = frac(p.xz * 0.25f);
|
||||||
|
color.rg = lerp(color.rg, uv, 0.1f);
|
||||||
|
return float4(color.xy, color.zw).xyz;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float2 steer(float2 direction, float torque)
|
||||||
|
{
|
||||||
|
float2x2 turn = float2x2(cos(torque), -sin(torque), sin(torque), cos(torque));
|
||||||
|
float2 turned = mul(direction, turn);
|
||||||
|
return normalize(turned) * pow(length(turned), 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HandWrittenShaderStyleMathEvaluates()
|
||||||
|
{
|
||||||
|
var color = shade(float3(1.0f, 0.5f, 2.0f), float3(0.0f, 1.0f, 0.0f), float2(1.0f, 0.0f), 0.0f);
|
||||||
|
Assert.True(color.x > 0.1f && color.x <= 1.0f);
|
||||||
|
Assert.True(float.IsFinite(color.y) && float.IsFinite(color.z));
|
||||||
|
|
||||||
|
var far = shade(float3(20.0f, 20.0f, 20.0f), float3(0.0f, 1.0f, 0.0f), float2(0.0f), 0.0f);
|
||||||
|
Assert.Equal(0.0f, far.z);
|
||||||
|
|
||||||
|
var steered = steer(float2(1.0f, 0.0f), 0.5f);
|
||||||
|
var viaRotate = mul(float2(1.0f, 0.0f), CultMath.float2x2.Rotate(0.5f));
|
||||||
|
Assert.Equal(viaRotate.x, steered.x, precision: 5);
|
||||||
|
Assert.Equal(viaRotate.y, steered.y, precision: 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 942fbcb9c28a4e559e5baf435dc24cf1
|
||||||
|
|
@ -33,7 +33,7 @@ public sealed class MathTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void VectorsExposeCommonReadOnlySwizzles()
|
public void VectorsExposeCommonSwizzles()
|
||||||
{
|
{
|
||||||
Assert.Equal(new float3(2.0f, 3.0f, 1.0f), new float3(1.0f, 2.0f, 3.0f).yzx);
|
Assert.Equal(new float3(2.0f, 3.0f, 1.0f), new float3(1.0f, 2.0f, 3.0f).yzx);
|
||||||
Assert.Equal(new float3(3.0f, 2.0f, 1.0f), new float3(1.0f, 2.0f, 3.0f).zyx);
|
Assert.Equal(new float3(3.0f, 2.0f, 1.0f), new float3(1.0f, 2.0f, 3.0f).zyx);
|
||||||
|
|
@ -170,10 +170,10 @@ public sealed class MathTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SignIsComponentWiseForSimulationVectors()
|
public void SignIsComponentWiseForSimulationVectors()
|
||||||
{
|
{
|
||||||
Assert.Equal(-1.0f, math.sign(-12.0f));
|
Assert.Equal(-1, math.sign(-12.0f));
|
||||||
Assert.Equal(0.0f, math.sign(0.0f));
|
Assert.Equal(0, math.sign(0.0f));
|
||||||
Assert.Equal(1.0f, math.sign(12.0f));
|
Assert.Equal(1, math.sign(12.0f));
|
||||||
Assert.Equal(new float3(-1.0f, 0.0f, 1.0f), math.sign(new float3(-2.0f, 0.0f, 4.0f)));
|
Assert.Equal(new int3(-1, 0, 1), math.sign(new float3(-2.0f, 0.0f, 4.0f)));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -277,7 +277,7 @@ public sealed class MathTests
|
||||||
[InlineData(536.5106f, 536.5106f, -0.6901103f)]
|
[InlineData(536.5106f, 536.5106f, -0.6901103f)]
|
||||||
public void SimplexNoiseMatchesUnityMathematics(float x, float y, float expected)
|
public void SimplexNoiseMatchesUnityMathematics(float x, float y, float expected)
|
||||||
{
|
{
|
||||||
Assert.Equal(expected, math.simplex_noise(new float2(x, y)), precision: 6);
|
Assert.Equal(expected, math.snoise(new float2(x, y)), precision: 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
132
packages/cultmath/tools/generate-swizzles.ps1
Normal file
132
packages/cultmath/tools/generate-swizzles.ps1
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
param(
|
||||||
|
[string]$OutputPath = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
# Writes src\CultMath\Swizzles.g.cs: HLSL swizzles and mixed constructors for
|
||||||
|
# CultMath's vector types. The output is checked in; rerun after changing the
|
||||||
|
# family table below and commit both files together.
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
if (-not $OutputPath) {
|
||||||
|
$OutputPath = Join-Path $PSScriptRoot "..\src\CultMath\Swizzles.g.cs"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Functions: emit math.<type>(...) constructor functions. double has none because
|
||||||
|
# a math.double3 method group would hide the double3 type name under
|
||||||
|
# `using static CultMath.math;` and HLSL shader code does not construct doubles.
|
||||||
|
$families = @(
|
||||||
|
@{ Scalar = "float"; Sizes = @(2, 3, 4); Functions = $true },
|
||||||
|
@{ Scalar = "int"; Sizes = @(2, 3, 4); Functions = $true },
|
||||||
|
@{ Scalar = "bool"; Sizes = @(2, 3, 4); Functions = $true },
|
||||||
|
@{ Scalar = "double"; Sizes = @(2, 3); Functions = $false }
|
||||||
|
)
|
||||||
|
$componentFields = @("x", "y", "z", "w")
|
||||||
|
$namings = @(@("x", "y", "z", "w"), @("r", "g", "b", "a"))
|
||||||
|
|
||||||
|
function Get-Compositions([int]$total) {
|
||||||
|
# Ordered part sizes summing to $total, excluding the all-scalar form that the
|
||||||
|
# hand-written base constructor already owns.
|
||||||
|
$results = [System.Collections.Generic.List[object]]::new()
|
||||||
|
function Expand([int]$remaining, [int[]]$prefix) {
|
||||||
|
if ($remaining -eq 0) {
|
||||||
|
if (@($prefix | Where-Object { $_ -ne 1 }).Count -gt 0) {
|
||||||
|
$results.Add($prefix)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for ($part = 1; $part -le $remaining; $part++) {
|
||||||
|
Expand ($remaining - $part) ($prefix + $part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expand $total @()
|
||||||
|
return , $results
|
||||||
|
}
|
||||||
|
|
||||||
|
$builder = [System.Text.StringBuilder]::new()
|
||||||
|
function Line([string]$text = "") { [void]$builder.Append($text).Append("`n") }
|
||||||
|
|
||||||
|
Line "// <auto-generated>"
|
||||||
|
Line "// Generated by tools/generate-swizzles.ps1. Do not edit by hand."
|
||||||
|
Line "// </auto-generated>"
|
||||||
|
Line ""
|
||||||
|
Line "namespace CultMath;"
|
||||||
|
|
||||||
|
$functionLines = [System.Collections.Generic.List[string]]::new()
|
||||||
|
|
||||||
|
foreach ($family in $families) {
|
||||||
|
$scalar = $family.Scalar
|
||||||
|
$maxSize = ($family.Sizes | Measure-Object -Maximum).Maximum
|
||||||
|
foreach ($size in $family.Sizes) {
|
||||||
|
$type = "$scalar$size"
|
||||||
|
Line ""
|
||||||
|
Line "public partial struct $type"
|
||||||
|
Line "{"
|
||||||
|
|
||||||
|
foreach ($composition in (Get-Compositions $size)) {
|
||||||
|
$parameters = @()
|
||||||
|
$arguments = @()
|
||||||
|
$offset = 0
|
||||||
|
foreach ($part in $composition) {
|
||||||
|
$name = -join $componentFields[$offset..($offset + $part - 1)]
|
||||||
|
if ($part -eq 1) {
|
||||||
|
$parameters += "$scalar $name"
|
||||||
|
$arguments += $name
|
||||||
|
} else {
|
||||||
|
$parameters += "$scalar$part $name"
|
||||||
|
for ($i = 0; $i -lt $part; $i++) { $arguments += "$name.$($componentFields[$i])" }
|
||||||
|
}
|
||||||
|
$offset += $part
|
||||||
|
}
|
||||||
|
$parameterList = $parameters -join ", "
|
||||||
|
Line " public $type($parameterList) : this($($arguments -join ', ')) { }"
|
||||||
|
if ($family.Functions) {
|
||||||
|
$names = @($parameters | ForEach-Object { ($_ -split " ")[1] })
|
||||||
|
$functionLines.Add(" public static $type $type($parameterList) => new($($names -join ', '));")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Single-component color aliases; x/y/z/w are the fields themselves.
|
||||||
|
$letters = $namings[1]
|
||||||
|
for ($i = 0; $i -lt $size; $i++) {
|
||||||
|
Line " public $scalar $($letters[$i]) { readonly get => $($componentFields[$i]); set => $($componentFields[$i]) = value; }"
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($letters in $namings) {
|
||||||
|
for ($length = 2; $length -le $maxSize; $length++) {
|
||||||
|
$count = [math]::Pow($size, $length)
|
||||||
|
for ($index = 0; $index -lt $count; $index++) {
|
||||||
|
[int[]]$digits = @()
|
||||||
|
[int]$remainder = $index
|
||||||
|
for ($d = 0; $d -lt $length; $d++) {
|
||||||
|
$digits = @([int]($remainder % $size)) + $digits
|
||||||
|
$remainder = [int][math]::Floor($remainder / $size)
|
||||||
|
}
|
||||||
|
$name = -join ($digits | ForEach-Object { $letters[$_] })
|
||||||
|
$getter = ($digits | ForEach-Object { $componentFields[$_] }) -join ", "
|
||||||
|
$resultType = "$scalar$length"
|
||||||
|
$unique = @($digits | Select-Object -Unique).Count -eq $length
|
||||||
|
if ($unique) {
|
||||||
|
$assignments = for ($d = 0; $d -lt $length; $d++) { "$($componentFields[$digits[$d]]) = value.$($componentFields[$d]);" }
|
||||||
|
Line " public $resultType $name { readonly get => new($getter); set { $($assignments -join ' ') } }"
|
||||||
|
} else {
|
||||||
|
Line " public readonly $resultType $name => new($getter);"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Line "}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Line ""
|
||||||
|
Line "public static partial class math"
|
||||||
|
Line "{"
|
||||||
|
foreach ($functionLine in $functionLines) { Line $functionLine }
|
||||||
|
Line "}"
|
||||||
|
|
||||||
|
$fullPath = [System.IO.Path]::GetFullPath($OutputPath)
|
||||||
|
$content = $builder.ToString() -replace "`n", "`r`n"
|
||||||
|
[System.IO.File]::WriteAllText($fullPath, $content, [System.Text.UTF8Encoding]::new($false))
|
||||||
|
Write-Host "Wrote $fullPath ($((Get-Item -LiteralPath $fullPath).Length) bytes)"
|
||||||
7
packages/cultmath/tools/generate-swizzles.ps1.meta
Normal file
7
packages/cultmath/tools/generate-swizzles.ps1.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d7a796fd2d87489c9f055fd6e73513b5
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -8,26 +8,27 @@ $repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||||
$installPath = Join-Path $repoRoot $InstallRoot
|
$installPath = Join-Path $repoRoot $InstallRoot
|
||||||
New-Item -ItemType Directory -Force -Path $installPath | Out-Null
|
New-Item -ItemType Directory -Force -Path $installPath | Out-Null
|
||||||
|
|
||||||
$release = Invoke-RestMethod `
|
# Pinned: parity means this compiler's lowering (dxcompiler 1.9.0.5402). The hash is the SHA256
|
||||||
-Uri "https://api.github.com/repos/microsoft/DirectXShaderCompiler/releases/latest" `
|
# of dxc_2026_07_29.zip downloaded from Microsoft's GitHub release v1.9.2607, and matches the
|
||||||
-Headers @{ "User-Agent" = "CultMath-DXC-Bootstrap" }
|
# digest GitHub publishes for that asset.
|
||||||
|
$zipName = "dxc_2026_07_29.zip"
|
||||||
|
$zipUrl = "https://github.com/microsoft/DirectXShaderCompiler/releases/download/v1.9.2607/$zipName"
|
||||||
|
$zipSha256 = "A1DFB116BA3EEAE6A1582291B53A8E7BF65AD760676BD3194685C8F7367CD241"
|
||||||
|
|
||||||
$asset = $release.assets |
|
$zipPath = Join-Path $installPath $zipName
|
||||||
Where-Object { $_.name -like "dxc_*.zip" } |
|
|
||||||
Select-Object -First 1
|
|
||||||
|
|
||||||
if (-not $asset) {
|
|
||||||
throw "No Windows dxc_*.zip asset found on the latest DirectXShaderCompiler release."
|
|
||||||
}
|
|
||||||
|
|
||||||
$zipPath = Join-Path $installPath $asset.name
|
|
||||||
Remove-Item -LiteralPath $zipPath -Force -ErrorAction SilentlyContinue
|
Remove-Item -LiteralPath $zipPath -Force -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
& curl.exe -L --retry 5 --retry-delay 2 --fail -o $zipPath $asset.browser_download_url
|
& curl.exe -L --retry 5 --retry-delay 2 --fail -o $zipPath $zipUrl
|
||||||
if ($LASTEXITCODE -ne 0) {
|
if ($LASTEXITCODE -ne 0) {
|
||||||
throw "curl.exe failed with exit code $LASTEXITCODE."
|
throw "curl.exe failed with exit code $LASTEXITCODE."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$actualSha256 = (Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash
|
||||||
|
if ($actualSha256 -ne $zipSha256) {
|
||||||
|
Remove-Item -LiteralPath $zipPath -Force
|
||||||
|
throw "$zipName SHA256 is $actualSha256, expected $zipSha256."
|
||||||
|
}
|
||||||
|
|
||||||
$currentPath = Join-Path $installPath "current"
|
$currentPath = Join-Path $installPath "current"
|
||||||
Remove-Item -LiteralPath $currentPath -Recurse -Force -ErrorAction SilentlyContinue
|
Remove-Item -LiteralPath $currentPath -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
New-Item -ItemType Directory -Force -Path $currentPath | Out-Null
|
New-Item -ItemType Directory -Force -Path $currentPath | Out-Null
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
# CultMath for Unity
|
# CultMath for Unity
|
||||||
|
|
||||||
This is CultMath's Unity 2021.3-compatible Git-UPM surface. It contains the
|
This is CultMath's Unity 2021.3-compatible Git-UPM surface. It contains the
|
||||||
precompiled `CultMath.dll`, portable symbols, and the numeric
|
precompiled `CultMath.dll`, portable symbols, the numeric
|
||||||
`Shaders/CultMath.hlsl` mirror.
|
`Shaders/CultMath.hlsl` mirror, and the `CultMath.UnityBridge` assembly.
|
||||||
|
|
||||||
|
`CultMath.UnityBridge` is the only C# source Unity compiles here. It holds the
|
||||||
|
UnityEngine conversions the engine-free core cannot declare: `ToCultMath()` on
|
||||||
|
`Vector2/3/4`, `Vector2Int/3Int`, `Quaternion`, and `Color`; `ToUnity()` on
|
||||||
|
`float2/3/4`, `int2/3`, and `quaternion`; `ToColor()` on `float4`. C# only
|
||||||
|
admits conversion operators on the source or target type, so these are
|
||||||
|
extension methods rather than implicit casts. Import `CultMath.UnityBridge` to
|
||||||
|
use them.
|
||||||
|
|
||||||
Unity does not compile CultMath's repository source tree. That source uses the
|
Unity does not compile CultMath's repository source tree. That source uses the
|
||||||
current C# language and remains owned by the normal .NET project. The package
|
current C# language and remains owned by the normal .NET project. The package
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 01f9f472b0a645b1ab392567aa38911a
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "CultMath.UnityBridge",
|
||||||
|
"rootNamespace": "CultMath.UnityBridge",
|
||||||
|
"references": [],
|
||||||
|
"includePlatforms": [],
|
||||||
|
"excludePlatforms": [],
|
||||||
|
"allowUnsafeCode": false,
|
||||||
|
"overrideReferences": true,
|
||||||
|
"precompiledReferences": [
|
||||||
|
"CultMath.dll"
|
||||||
|
],
|
||||||
|
"autoReferenced": true,
|
||||||
|
"defineConstraints": [],
|
||||||
|
"versionDefines": [],
|
||||||
|
"noEngineReferences": false
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 77e50a05974842bc99d285ef6d5d04f7
|
||||||
|
AssemblyDefinitionImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace CultMath.UnityBridge
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Explicit conversions between UnityEngine and CultMath value types.
|
||||||
|
/// C# only admits user-defined conversion operators declared on the source or target
|
||||||
|
/// type, and CultMath's core types stay engine-free, so the bridge is extension methods.
|
||||||
|
/// </summary>
|
||||||
|
public static class UnityConversions
|
||||||
|
{
|
||||||
|
public static float2 ToCultMath(this Vector2 value) => new float2(value.x, value.y);
|
||||||
|
public static float3 ToCultMath(this Vector3 value) => new float3(value.x, value.y, value.z);
|
||||||
|
public static float4 ToCultMath(this Vector4 value) => new float4(value.x, value.y, value.z, value.w);
|
||||||
|
public static float4 ToCultMath(this Color value) => new float4(value.r, value.g, value.b, value.a);
|
||||||
|
public static quaternion ToCultMath(this Quaternion value) => new quaternion(value.x, value.y, value.z, value.w);
|
||||||
|
public static int2 ToCultMath(this Vector2Int value) => new int2(value.x, value.y);
|
||||||
|
public static int3 ToCultMath(this Vector3Int value) => new int3(value.x, value.y, value.z);
|
||||||
|
|
||||||
|
public static Vector2 ToUnity(this float2 value) => new Vector2(value.x, value.y);
|
||||||
|
public static Vector3 ToUnity(this float3 value) => new Vector3(value.x, value.y, value.z);
|
||||||
|
public static Vector4 ToUnity(this float4 value) => new Vector4(value.x, value.y, value.z, value.w);
|
||||||
|
public static Quaternion ToUnity(this quaternion value) => new Quaternion(value.x, value.y, value.z, value.w);
|
||||||
|
public static Vector2Int ToUnity(this int2 value) => new Vector2Int(value.x, value.y);
|
||||||
|
public static Vector3Int ToUnity(this int3 value) => new Vector3Int(value.x, value.y, value.z);
|
||||||
|
public static Color ToColor(this float4 value) => new Color(value.x, value.y, value.z, value.w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8fc742177b3f4715b204afe0e69a400c
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -26,10 +26,11 @@ float2 cultmath_clamp(float2 value, float2 minimum, float2 maximum) { return min
|
||||||
float3 cultmath_clamp(float3 value, float3 minimum, float3 maximum) { return min(max(value, minimum), maximum); }
|
float3 cultmath_clamp(float3 value, float3 minimum, float3 maximum) { return min(max(value, minimum), maximum); }
|
||||||
float4 cultmath_clamp(float4 value, float4 minimum, float4 maximum) { return min(max(value, minimum), maximum); }
|
float4 cultmath_clamp(float4 value, float4 minimum, float4 maximum) { return min(max(value, minimum), maximum); }
|
||||||
|
|
||||||
float cultmath_saturate(float value) { return cultmath_clamp(value, 0.0, 1.0); }
|
// DXIL Saturate is FMin(1, FMax(0, x)); dxc lowers min/max to the same FMin/FMax, NaN rules included.
|
||||||
float2 cultmath_saturate(float2 value) { return cultmath_clamp(value, float2(0.0, 0.0), float2(1.0, 1.0)); }
|
float cultmath_saturate(float value) { return min(1.0, max(0.0, value)); }
|
||||||
float3 cultmath_saturate(float3 value) { return cultmath_clamp(value, float3(0.0, 0.0, 0.0), float3(1.0, 1.0, 1.0)); }
|
float2 cultmath_saturate(float2 value) { return min(float2(1.0, 1.0), max(float2(0.0, 0.0), value)); }
|
||||||
float4 cultmath_saturate(float4 value) { return cultmath_clamp(value, float4(0.0, 0.0, 0.0, 0.0), float4(1.0, 1.0, 1.0, 1.0)); }
|
float3 cultmath_saturate(float3 value) { return min(float3(1.0, 1.0, 1.0), max(float3(0.0, 0.0, 0.0), value)); }
|
||||||
|
float4 cultmath_saturate(float4 value) { return min(float4(1.0, 1.0, 1.0, 1.0), max(float4(0.0, 0.0, 0.0, 0.0), value)); }
|
||||||
|
|
||||||
float cultmath_lerp(float start, float end, float amount) { return start + (end - start) * amount; }
|
float cultmath_lerp(float start, float end, float amount) { return start + (end - start) * amount; }
|
||||||
float2 cultmath_lerp(float2 start, float2 end, float2 amount) { return start + (end - start) * amount; }
|
float2 cultmath_lerp(float2 start, float2 end, float2 amount) { return start + (end - start) * amount; }
|
||||||
|
|
@ -73,13 +74,66 @@ float cultmath_smootherstep(float value)
|
||||||
return value * value * value * (value * (value * 6.0 - 15.0) + 10.0);
|
return value * value * value * (value * (value * 6.0 - 15.0) + 10.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
float2 cultmath_simplex_mod289(float2 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
|
float2 cultmath_snoise_mod289(float2 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
|
||||||
float3 cultmath_simplex_mod289(float3 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
|
float3 cultmath_snoise_mod289(float3 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
|
||||||
float3 cultmath_simplex_permute(float3 value) { return cultmath_simplex_mod289(((value * 34.0) + 1.0) * value); }
|
float4 cultmath_snoise_mod289(float4 value) { return value - floor(value * (1.0 / 289.0)) * 289.0; }
|
||||||
|
float3 cultmath_snoise_permute(float3 value) { return cultmath_snoise_mod289(((value * 34.0) + 1.0) * value); }
|
||||||
|
float4 cultmath_snoise_permute(float4 value) { return cultmath_snoise_mod289(((value * 34.0) + 1.0) * value); }
|
||||||
|
|
||||||
float cultmath_simplex_noise(float2 value)
|
// Ashima Arts / Ian McEwan 3D simplex noise (MIT), same float32 evaluation
|
||||||
|
// order as the C# math.snoise(float3) mirror.
|
||||||
|
float cultmath_snoise(float3 value)
|
||||||
{
|
{
|
||||||
const float4 c = float4(
|
float2 c = float2(1.0 / 6.0, 1.0 / 3.0);
|
||||||
|
float3 i = floor(value + dot(value, c.yyy));
|
||||||
|
float3 x0 = value - i + dot(i, c.xxx);
|
||||||
|
float3 g = step(x0.yzx, x0.xyz);
|
||||||
|
float3 l = 1.0 - g;
|
||||||
|
float3 i1 = min(g.xyz, l.zxy);
|
||||||
|
float3 i2 = max(g.xyz, l.zxy);
|
||||||
|
float3 x1 = x0 - i1 + c.xxx;
|
||||||
|
float3 x2 = x0 - i2 + c.yyy;
|
||||||
|
float3 x3 = x0 - 0.5;
|
||||||
|
|
||||||
|
i = cultmath_snoise_mod289(i);
|
||||||
|
float4 p = cultmath_snoise_permute(cultmath_snoise_permute(cultmath_snoise_permute(
|
||||||
|
i.z + float4(0.0, i1.z, i2.z, 1.0)) + i.y + float4(0.0, i1.y, i2.y, 1.0)) + i.x + float4(0.0, i1.x, i2.x, 1.0));
|
||||||
|
|
||||||
|
const float n_ = 0.142857142857;
|
||||||
|
float3 ns = float3(2.0 * n_, 0.5 * n_ - 1.0, n_);
|
||||||
|
float4 j = p - 49.0 * floor(p * ns.z * ns.z);
|
||||||
|
float4 x_ = floor(j * ns.z);
|
||||||
|
float4 y_ = floor(j - 7.0 * x_);
|
||||||
|
float4 x = x_ * ns.x + ns.yyyy;
|
||||||
|
float4 y = y_ * ns.x + ns.yyyy;
|
||||||
|
float4 h = 1.0 - abs(x) - abs(y);
|
||||||
|
|
||||||
|
float4 b0 = float4(x.xy, y.xy);
|
||||||
|
float4 b1 = float4(x.zw, y.zw);
|
||||||
|
float4 s0 = floor(b0) * 2.0 + 1.0;
|
||||||
|
float4 s1 = floor(b1) * 2.0 + 1.0;
|
||||||
|
float4 sh = -step(h, 0.0);
|
||||||
|
float4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
|
||||||
|
float4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
|
||||||
|
|
||||||
|
float3 p0 = float3(a0.xy, h.x);
|
||||||
|
float3 p1 = float3(a0.zw, h.y);
|
||||||
|
float3 p2 = float3(a1.xy, h.z);
|
||||||
|
float3 p3 = float3(a1.zw, h.w);
|
||||||
|
float4 norm = 1.79284291400159 - 0.85373472095314 * float4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3));
|
||||||
|
p0 *= norm.x;
|
||||||
|
p1 *= norm.y;
|
||||||
|
p2 *= norm.z;
|
||||||
|
p3 *= norm.w;
|
||||||
|
|
||||||
|
float4 m = max(0.6 - float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
|
||||||
|
m = m * m;
|
||||||
|
return 42.0 * dot(m * m, float4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
|
||||||
|
}
|
||||||
|
|
||||||
|
float cultmath_snoise(float2 value)
|
||||||
|
{
|
||||||
|
float4 c = float4(
|
||||||
0.211324865405187,
|
0.211324865405187,
|
||||||
0.366025403784439,
|
0.366025403784439,
|
||||||
-0.577350269189626,
|
-0.577350269189626,
|
||||||
|
|
@ -89,9 +143,9 @@ float cultmath_simplex_noise(float2 value)
|
||||||
float2 i1 = x0.x > x0.y ? float2(1.0, 0.0) : float2(0.0, 1.0);
|
float2 i1 = x0.x > x0.y ? float2(1.0, 0.0) : float2(0.0, 1.0);
|
||||||
float4 x12 = float4(x0.x + c.x - i1.x, x0.y + c.x - i1.y, x0.x + c.z, x0.y + c.z);
|
float4 x12 = float4(x0.x + c.x - i1.x, x0.y + c.x - i1.y, x0.x + c.z, x0.y + c.z);
|
||||||
|
|
||||||
i = cultmath_simplex_mod289(i);
|
i = cultmath_snoise_mod289(i);
|
||||||
float3 p = cultmath_simplex_permute(
|
float3 p = cultmath_snoise_permute(
|
||||||
cultmath_simplex_permute(i.y + float3(0.0, i1.y, 1.0)) + i.x + float3(0.0, i1.x, 1.0));
|
cultmath_snoise_permute(i.y + float3(0.0, i1.y, 1.0)) + i.x + float3(0.0, i1.x, 1.0));
|
||||||
float3 m = max(0.5 - float3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
|
float3 m = max(0.5 - float3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
|
||||||
m *= m;
|
m *= m;
|
||||||
m *= m;
|
m *= m;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
# Third-Party Notices
|
# Third-Party Notices
|
||||||
|
|
||||||
## 2D Simplex Noise
|
## 2D and 3D Simplex Noise
|
||||||
|
|
||||||
`math.simplex_noise` and `cultmath_simplex_noise` port the textureless 2D
|
`math.snoise` and `cultmath_snoise` port the textureless 2D and 3D
|
||||||
simplex-noise function by Ian McEwan and Ashima Arts, as distributed by Stefan
|
simplex-noise functions by Ian McEwan and Ashima Arts, as distributed by Stefan
|
||||||
Gustavson's `webgl-noise` project and Unity Mathematics.
|
Gustavson's `webgl-noise` project and Unity Mathematics.
|
||||||
|
|
||||||
- Copyright 2011 Ashima Arts.
|
- Copyright 2011 Ashima Arts.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "org.gamecult.cultmath",
|
"name": "org.gamecult.cultmath",
|
||||||
"displayName": "CultMath",
|
"displayName": "CultMath",
|
||||||
"version": "0.1.2",
|
"version": "0.2.0",
|
||||||
"unity": "2021.3",
|
"unity": "2021.3",
|
||||||
"description": "Precompiled CultMath numeric primitives and matching HLSL semantics for GameCult Unity runtimes.",
|
"description": "Precompiled CultMath numeric primitives and matching HLSL semantics for GameCult Unity runtimes.",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue