From 9a5597d233eb6c3ae25dccf17b77b7db99dfe17a Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 22 Jul 2026 04:52:09 +0100 Subject: [PATCH] Add precompiled CultMath Unity package --- .gitignore | 1 + README.md | 21 +- scripts/build-unity-package.ps1 | 107 +++++++ unity/Runtime/CultMath.Unity.asmdef | 16 - unity/org.gamecult.cultmath/LICENSE.md | 10 + unity/org.gamecult.cultmath/README.md | 28 ++ .../{ => org.gamecult.cultmath}/Runtime.meta | 2 +- .../Runtime/CultMath.asmdef | 16 + .../Runtime/CultMath.asmdef.meta} | 2 +- .../Runtime/Plugins.meta | 8 + .../Runtime/Plugins/CultMath.dll | Bin 0 -> 44544 bytes .../Runtime/Plugins/CultMath.dll.meta | 21 ++ .../Runtime/Plugins/CultMath.pdb | Bin 0 -> 23424 bytes .../Runtime/Plugins/CultMath.pdb.meta | 4 +- unity/org.gamecult.cultmath/Shaders.meta | 8 + .../Shaders/CultMath.hlsl | 302 ++++++++++++++++++ .../Shaders/CultMath.hlsl.meta | 7 + .../THIRD-PARTY-NOTICES.md | 11 + .../org.gamecult.cultmath/package.json | 11 +- 19 files changed, 542 insertions(+), 33 deletions(-) create mode 100644 scripts/build-unity-package.ps1 delete mode 100644 unity/Runtime/CultMath.Unity.asmdef create mode 100644 unity/org.gamecult.cultmath/LICENSE.md create mode 100644 unity/org.gamecult.cultmath/README.md rename unity/{ => org.gamecult.cultmath}/Runtime.meta (77%) create mode 100644 unity/org.gamecult.cultmath/Runtime/CultMath.asmdef rename unity/{Runtime/CultMath.Unity.asmdef.meta => org.gamecult.cultmath/Runtime/CultMath.asmdef.meta} (76%) create mode 100644 unity/org.gamecult.cultmath/Runtime/Plugins.meta create mode 100644 unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll create mode 100644 unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll.meta create mode 100644 unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.pdb rename package.json.meta => unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.pdb.meta (60%) create mode 100644 unity/org.gamecult.cultmath/Shaders.meta create mode 100644 unity/org.gamecult.cultmath/Shaders/CultMath.hlsl create mode 100644 unity/org.gamecult.cultmath/Shaders/CultMath.hlsl.meta create mode 100644 unity/org.gamecult.cultmath/THIRD-PARTY-NOTICES.md rename package.json => unity/org.gamecult.cultmath/package.json (52%) diff --git a/.gitignore b/.gitignore index 3779002b..c9b141d0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ target/ **/target/ /native/cultmath-core/target.meta .tools/ +/artifacts/ diff --git a/README.md b/README.md index b471c7fd..02d47752 100644 --- a/README.md +++ b/README.md @@ -73,21 +73,24 @@ dotnet build CultMath.slnx dotnet test CultMath.slnx cargo test --manifest-path native/cultmath-core/Cargo.toml cargo build --release --manifest-path native/cultmath-core/Cargo.toml +.\scripts\build-unity-package.ps1 ``` -## Unity compiler +## Unity package -CultMath uses C# 10 record structs and file-scoped namespaces. Unity projects -must select the compiler's latest language mode with an `Assets/csc.rsp` file: +CultMath's Unity 2021.3 surface is the tracked precompiled package at +`unity/org.gamecult.cultmath`. Unity consumes `CultMath.dll` and the numeric +`CultMath.hlsl`; it does not compile the repository's current-language C# source +tree and does not require a consumer `csc.rsp` workaround. -```text --langversion:latest +```json +"org.gamecult.cultmath": "https://github.com/GameCult/CultMath.git?path=/unity/org.gamecult.cultmath" ``` -The package's Unity smoke project verifies this contract against Unity 6. The -setting is already present in current GameCult Unity clients; it belongs to the -consumer project because Unity only reads compiler response files from its -`Assets` root. +The repository root is not a Unity package. Build the tracked package with +`scripts/build-unity-package.ps1`; the script fresh-builds the netstandard2.1 +assembly and rejects stale binaries, missing metadata, unexpected assemblies, +or leaked C# source. ## Shader Tooling diff --git a/scripts/build-unity-package.ps1 b/scripts/build-unity-package.ps1 new file mode 100644 index 00000000..329cedb7 --- /dev/null +++ b/scripts/build-unity-package.ps1 @@ -0,0 +1,107 @@ +param( + [string] $Configuration = "Release", + [string] $OutputDirectory = "artifacts\unity\org.gamecult.cultmath" +) + +$ErrorActionPreference = "Stop" + +$repoRoot = Split-Path -Parent $PSScriptRoot +$projectPath = Join-Path $repoRoot "src\CultMath\CultMath.csproj" +$templateRoot = Join-Path $repoRoot "unity\org.gamecult.cultmath" +$buildRoot = Join-Path $repoRoot "artifacts\cultmath-unity-build" +$outputRoot = if ([System.IO.Path]::IsPathRooted($OutputDirectory)) { + $OutputDirectory +} else { + Join-Path $repoRoot $OutputDirectory +} +$freshAssembly = Join-Path $buildRoot "CultMath.dll" +$freshSymbols = Join-Path $buildRoot "CultMath.pdb" +$trackedPluginRoot = Join-Path $templateRoot "Runtime\Plugins" +$trackedAssembly = Join-Path $trackedPluginRoot "CultMath.dll" +$trackedSymbols = Join-Path $trackedPluginRoot "CultMath.pdb" + +if (Test-Path -LiteralPath $buildRoot) { + Remove-Item -LiteralPath $buildRoot -Recurse -Force +} +if (Test-Path -LiteralPath $outputRoot) { + Remove-Item -LiteralPath $outputRoot -Recurse -Force +} + +dotnet build $projectPath -c $Configuration -f netstandard2.1 -o $buildRoot ` + --disable-build-servers -p:UseSharedCompilation=false -p:ContinuousIntegrationBuild=true -m:1 +if ($LASTEXITCODE -ne 0) { + throw "CultMath Unity assembly build failed with exit code $LASTEXITCODE" +} + +foreach ($path in @($freshAssembly, $freshSymbols)) { + if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { + throw "CultMath Unity build is missing $path" + } +} + +$requiredTemplateFiles = @( + "LICENSE.md", + "README.md", + "THIRD-PARTY-NOTICES.md", + "package.json", + "Runtime\CultMath.asmdef", + "Runtime\CultMath.asmdef.meta", + "Runtime\Plugins\CultMath.dll", + "Runtime\Plugins\CultMath.dll.meta", + "Runtime\Plugins\CultMath.pdb", + "Runtime\Plugins\CultMath.pdb.meta", + "Shaders\CultMath.hlsl", + "Shaders\CultMath.hlsl.meta" +) +foreach ($relativePath in $requiredTemplateFiles) { + if (-not (Test-Path -LiteralPath (Join-Path $templateRoot $relativePath) -PathType Leaf)) { + throw "Tracked CultMath Unity package is incomplete: missing $relativePath" + } +} + +$unityAssetFiles = @(Get-ChildItem -LiteralPath $templateRoot -Recurse -File | Where-Object { + $_.Extension -in @(".asmdef", ".dll", ".pdb", ".hlsl") +}) +$missingAssetMetas = @($unityAssetFiles | Where-Object { -not (Test-Path -LiteralPath ($_.FullName + ".meta")) }) +$missingDirectoryMetas = @(Get-ChildItem -LiteralPath $templateRoot -Recurse -Directory | Where-Object { + -not (Test-Path -LiteralPath ($_.FullName + ".meta")) +}) +if ($missingAssetMetas.Count -ne 0 -or $missingDirectoryMetas.Count -ne 0) { + $missing = @($missingAssetMetas.FullName) + @($missingDirectoryMetas.FullName) + throw "Tracked CultMath Unity package has assets without stable .meta files: $($missing -join ', ')" +} + +$manifest = Get-Content -LiteralPath (Join-Path $templateRoot "package.json") -Raw | ConvertFrom-Json +$expectedFileVersion = "$($manifest.version).0" +$trackedFileVersion = [Diagnostics.FileVersionInfo]::GetVersionInfo($trackedAssembly).FileVersion +$freshFileVersion = [Diagnostics.FileVersionInfo]::GetVersionInfo($freshAssembly).FileVersion +if ($trackedFileVersion -ne $expectedFileVersion -or $freshFileVersion -ne $expectedFileVersion) { + throw "CultMath Unity assemblies must match package version $expectedFileVersion; tracked=$trackedFileVersion fresh=$freshFileVersion" +} + +foreach ($pair in @(@($freshAssembly, $trackedAssembly), @($freshSymbols, $trackedSymbols))) { + $freshHash = (Get-FileHash -LiteralPath $pair[0] -Algorithm SHA256).Hash + $trackedHash = (Get-FileHash -LiteralPath $pair[1] -Algorithm SHA256).Hash + if ($freshHash -ne $trackedHash) { + throw "Tracked Unity artifact is stale: $($pair[1]) differs from a fresh deterministic build" + } +} + +$trackedSourceFiles = @(Get-ChildItem -LiteralPath $templateRoot -Recurse -File -Filter "*.cs") +if ($trackedSourceFiles.Count -ne 0) { + throw "CultMath Unity package must not compile repository C# sources: $($trackedSourceFiles.FullName -join ', ')" +} + +New-Item -ItemType Directory -Force -Path $outputRoot | Out-Null +Copy-Item -Path (Join-Path $templateRoot "*") -Destination $outputRoot -Recurse -Force + +$stagedAssemblies = @(Get-ChildItem -LiteralPath (Join-Path $outputRoot "Runtime\Plugins") -Filter "*.dll") +$stagedSources = @(Get-ChildItem -LiteralPath $outputRoot -Recurse -File -Filter "*.cs") +if ($stagedAssemblies.Count -ne 1 -or $stagedAssemblies[0].Name -ne "CultMath.dll" -or $stagedSources.Count -ne 0) { + throw "CultMath package must contain one owned DLL and no C# source files" +} + +Write-Host "CultMath Unity package: $outputRoot" +Write-Host "Package: $($manifest.name)@$($manifest.version)" +Write-Host "Managed assemblies: $($stagedAssemblies.Count)" +Write-Host "C# source files: $($stagedSources.Count)" diff --git a/unity/Runtime/CultMath.Unity.asmdef b/unity/Runtime/CultMath.Unity.asmdef deleted file mode 100644 index 632c4e4c..00000000 --- a/unity/Runtime/CultMath.Unity.asmdef +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "CultMath.Unity", - "rootNamespace": "CultMath.Unity", - "references": [ - "CultMath" - ], - "includePlatforms": [], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": true, - "defineConstraints": [], - "versionDefines": [], - "noEngineReferences": false -} diff --git a/unity/org.gamecult.cultmath/LICENSE.md b/unity/org.gamecult.cultmath/LICENSE.md new file mode 100644 index 00000000..5e50f2d6 --- /dev/null +++ b/unity/org.gamecult.cultmath/LICENSE.md @@ -0,0 +1,10 @@ +# CultMath License + +CultMath is licensed under the Mozilla Public License 2.0 (`MPL-2.0`). + +The complete license text is available from the Mozilla Foundation at +. Source and modification history for the +precompiled assembly in this package are available in the GameCult/CultMath +repository at the corresponding package tag. + +Third-party code retains its original license. See `THIRD-PARTY-NOTICES.md`. diff --git a/unity/org.gamecult.cultmath/README.md b/unity/org.gamecult.cultmath/README.md new file mode 100644 index 00000000..1802ae9a --- /dev/null +++ b/unity/org.gamecult.cultmath/README.md @@ -0,0 +1,28 @@ +# CultMath for Unity + +This is CultMath's Unity 2021.3-compatible Git-UPM surface. It contains the +precompiled `CultMath.dll`, portable symbols, and the numeric +`Shaders/CultMath.hlsl` mirror. + +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 +facade references only the tracked precompiled assembly, so consumers do not +need `csc.rsp` language overrides. + +Build and inspect the package from the repository root: + +```powershell +.\scripts\build-unity-package.ps1 +``` + +The builder fresh-builds CultMath for `netstandard2.1`, verifies the tracked +DLL/PDB and stable Unity metadata, and stages an inspectable package under +`artifacts/unity/org.gamecult.cultmath`. + +Consume the repository package with: + +```json +"org.gamecult.cultmath": "https://github.com/GameCult/CultMath.git?path=/unity/org.gamecult.cultmath" +``` + +Production consumers should pin that URL to a commit or release tag. diff --git a/unity/Runtime.meta b/unity/org.gamecult.cultmath/Runtime.meta similarity index 77% rename from unity/Runtime.meta rename to unity/org.gamecult.cultmath/Runtime.meta index 8e0678f8..b0e76599 100644 --- a/unity/Runtime.meta +++ b/unity/org.gamecult.cultmath/Runtime.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 8226365c03c5e1a438408b7e42b98740 +guid: a31bf2bb35e54dd1ad1c765337682769 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/unity/org.gamecult.cultmath/Runtime/CultMath.asmdef b/unity/org.gamecult.cultmath/Runtime/CultMath.asmdef new file mode 100644 index 00000000..72c968e8 --- /dev/null +++ b/unity/org.gamecult.cultmath/Runtime/CultMath.asmdef @@ -0,0 +1,16 @@ +{ + "name": "CultMath", + "rootNamespace": "CultMath", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": true, + "precompiledReferences": [ + "CultMath.dll" + ], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": true +} diff --git a/unity/Runtime/CultMath.Unity.asmdef.meta b/unity/org.gamecult.cultmath/Runtime/CultMath.asmdef.meta similarity index 76% rename from unity/Runtime/CultMath.Unity.asmdef.meta rename to unity/org.gamecult.cultmath/Runtime/CultMath.asmdef.meta index 2dd3a36a..0ec09c2f 100644 --- a/unity/Runtime/CultMath.Unity.asmdef.meta +++ b/unity/org.gamecult.cultmath/Runtime/CultMath.asmdef.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: d1906c397c420ae4eb222a3f09f36843 +guid: 4b5c49d478124399a5310d7a9d8ed166 AssemblyDefinitionImporter: externalObjects: {} userData: diff --git a/unity/org.gamecult.cultmath/Runtime/Plugins.meta b/unity/org.gamecult.cultmath/Runtime/Plugins.meta new file mode 100644 index 00000000..fe8acafa --- /dev/null +++ b/unity/org.gamecult.cultmath/Runtime/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f84156013e814c14b73f668b6610b9e0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll b/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.dll new file mode 100644 index 0000000000000000000000000000000000000000..8cdb6700145956da7fb303fc1d9b0a39f19c91a4 GIT binary patch literal 44544 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 literal 0 HcmV?d00001 diff --git a/package.json.meta b/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.pdb.meta similarity index 60% rename from package.json.meta rename to unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.pdb.meta index 23ed71cf..e18fedbe 100644 --- a/package.json.meta +++ b/unity/org.gamecult.cultmath/Runtime/Plugins/CultMath.pdb.meta @@ -1,6 +1,6 @@ fileFormatVersion: 2 -guid: 1abf066549a826c4ba7a30d6676735c0 -PackageManifestImporter: +guid: a8cc555f643a4d7eb4dd340025b208f6 +DefaultImporter: externalObjects: {} userData: assetBundleName: diff --git a/unity/org.gamecult.cultmath/Shaders.meta b/unity/org.gamecult.cultmath/Shaders.meta new file mode 100644 index 00000000..1e7e7110 --- /dev/null +++ b/unity/org.gamecult.cultmath/Shaders.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ca5d773061ed49b3a173526048d40cb3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl b/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl new file mode 100644 index 00000000..4c00fb69 --- /dev/null +++ b/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl @@ -0,0 +1,302 @@ +#ifndef CULTMATH_HLSL +#define CULTMATH_HLSL + +// CultMath HLSL mirror. +// +// HLSL already owns the float2/float3/float4 type names and most primitive +// intrinsics. This include names the CultMath semantic surface explicitly so +// shader code, C# wrappers, and Rust/native parity fixtures can share the same +// small vocabulary without local helper drift. + +static const float CULTMATH_PI = 3.14159265358979323846; +static const float CULTMATH_TAU = 6.28318530717958647692; +static const float CULTMATH_HALF_PI = 1.57079632679489661923; +static const float CULTMATH_NORMALIZE_EPSILON = 1.0e-20; + +float cultmath_radians(float degrees_value) { return degrees_value * (CULTMATH_PI / 180.0); } +float cultmath_degrees(float radians_value) { return radians_value * (180.0 / CULTMATH_PI); } + +float cultmath_frac(float value) { return value - floor(value); } +float2 cultmath_frac(float2 value) { return value - floor(value); } +float3 cultmath_frac(float3 value) { return value - floor(value); } +float4 cultmath_frac(float4 value) { return value - floor(value); } + +float cultmath_clamp(float value, float minimum, float maximum) { return min(max(value, minimum), maximum); } +float2 cultmath_clamp(float2 value, float2 minimum, float2 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); } + +float cultmath_saturate(float value) { return cultmath_clamp(value, 0.0, 1.0); } +float2 cultmath_saturate(float2 value) { return cultmath_clamp(value, float2(0.0, 0.0), float2(1.0, 1.0)); } +float3 cultmath_saturate(float3 value) { return cultmath_clamp(value, float3(0.0, 0.0, 0.0), float3(1.0, 1.0, 1.0)); } +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)); } + +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; } +float3 cultmath_lerp(float3 start, float3 end, float3 amount) { return start + (end - start) * amount; } +float4 cultmath_lerp(float4 start, float4 end, float4 amount) { return start + (end - start) * amount; } + +float cultmath_step(float edge, float value) { return value < edge ? 0.0 : 1.0; } +float2 cultmath_step(float2 edge, float2 value) { return float2(cultmath_step(edge.x, value.x), cultmath_step(edge.y, value.y)); } +float3 cultmath_step(float3 edge, float3 value) { return float3(cultmath_step(edge.x, value.x), cultmath_step(edge.y, value.y), cultmath_step(edge.z, value.z)); } +float4 cultmath_step(float4 edge, float4 value) { return float4(cultmath_step(edge.x, value.x), cultmath_step(edge.y, value.y), cultmath_step(edge.z, value.z), cultmath_step(edge.w, value.w)); } + +float cultmath_smoothstep(float minimum, float maximum, float value) +{ + float t = cultmath_saturate((value - minimum) / (maximum - minimum)); + return t * t * (3.0 - 2.0 * t); +} + +float2 cultmath_smoothstep(float2 minimum, float2 maximum, float2 value) +{ + float2 t = cultmath_saturate((value - minimum) / (maximum - minimum)); + return t * t * (float2(3.0, 3.0) - float2(2.0, 2.0) * t); +} + +float3 cultmath_smoothstep(float3 minimum, float3 maximum, float3 value) +{ + float3 t = cultmath_saturate((value - minimum) / (maximum - minimum)); + return t * t * (float3(3.0, 3.0, 3.0) - float3(2.0, 2.0, 2.0) * t); +} + +float4 cultmath_smoothstep(float4 minimum, float4 maximum, float4 value) +{ + float4 t = cultmath_saturate((value - minimum) / (maximum - minimum)); + return t * t * (float4(3.0, 3.0, 3.0, 3.0) - float4(2.0, 2.0, 2.0, 2.0) * t); +} + +float cultmath_smoothstep01(float value) { return cultmath_smoothstep(0.0, 1.0, value); } + +float cultmath_smootherstep(float value) +{ + value = cultmath_saturate(value); + 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; } +float3 cultmath_simplex_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); } + +float cultmath_simplex_noise(float2 value) +{ + const float4 c = float4( + 0.211324865405187, + 0.366025403784439, + -0.577350269189626, + 0.024390243902439); + float2 i = floor(value + dot(value, c.yy)); + float2 x0 = value - i + dot(i, c.xx); + 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); + + i = cultmath_simplex_mod289(i); + float3 p = cultmath_simplex_permute( + cultmath_simplex_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); + m *= m; + m *= m; + + float3 x = 2.0 * frac(p * c.w) - 1.0; + float3 h = abs(x) - 0.5; + float3 ox = floor(x + 0.5); + float3 a0 = x - ox; + m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h); + + float3 g = float3( + a0.x * x0.x + h.x * x0.y, + a0.y * x12.x + h.y * x12.y, + a0.z * x12.z + h.z * x12.w); + return 130.0 * dot(m, g); +} + +float cultmath_lengthsq(float2 value) { return dot(value, value); } +float cultmath_lengthsq(float3 value) { return dot(value, value); } +float cultmath_lengthsq(float4 value) { return dot(value, value); } + +float cultmath_distance(float2 left, float2 right) { return length(left - right); } +float cultmath_distance(float3 left, float3 right) { return length(left - right); } +float cultmath_distance(float4 left, float4 right) { return length(left - right); } + +float2 cultmath_normalize(float2 value) { return value / max(length(value), CULTMATH_NORMALIZE_EPSILON); } +float3 cultmath_normalize(float3 value) { return value / max(length(value), CULTMATH_NORMALIZE_EPSILON); } +float4 cultmath_normalize(float4 value) { return value / max(length(value), CULTMATH_NORMALIZE_EPSILON); } + +float2 cultmath_reflect(float2 incident, float2 normal_value) +{ + return incident - 2.0 * dot(normal_value, incident) * normal_value; +} + +float3 cultmath_reflect(float3 incident, float3 normal_value) +{ + return incident - 2.0 * dot(normal_value, incident) * normal_value; +} + +float2 cultmath_rotate(float2 value, float radians_value) +{ + float s = sin(radians_value); + float c = cos(radians_value); + return float2(value.x * c - value.y * s, value.x * s + value.y * c); +} + +float2 cultmath_rotate_degrees(float2 value, float degrees_value) +{ + return cultmath_rotate(value, cultmath_radians(degrees_value)); +} + +float cultmath_csum(float2 value) { return value.x + value.y; } +float cultmath_csum(float3 value) { return value.x + value.y + value.z; } +float cultmath_csum(float4 value) { return value.x + value.y + value.z + value.w; } + +float cultmath_decay(float source, float lambda_value, float dt) { return source * exp(-lambda_value * dt); } +float2 cultmath_decay(float2 source, float lambda_value, float dt) { return source * exp(-lambda_value * dt); } +float3 cultmath_decay(float3 source, float lambda_value, float dt) { return source * exp(-lambda_value * dt); } +float4 cultmath_decay(float4 source, float lambda_value, float dt) { return source * exp(-lambda_value * dt); } + +float cultmath_damp(float start, float end, float lambda_value, float dt) +{ + return cultmath_lerp(start, end, 1.0 - exp(-lambda_value * dt)); +} + +float2 cultmath_damp(float2 start, float2 end, float lambda_value, float dt) +{ + return cultmath_lerp(start, end, 1.0 - exp(-lambda_value * dt)); +} + +float3 cultmath_damp(float3 start, float3 end, float lambda_value, float dt) +{ + return cultmath_lerp(start, end, 1.0 - exp(-lambda_value * dt)); +} + +float4 cultmath_damp(float4 start, float4 end, float lambda_value, float dt) +{ + return cultmath_lerp(start, end, 1.0 - exp(-lambda_value * dt)); +} + +float cultmath_catmullrom(float p0, float p1, float p2, float p3, float t) +{ + float t2 = t * t; + float t3 = t2 * t; + return 0.5 * ((2.0 * p1) + (p2 - p0) * t + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 + (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3); +} + +float2 cultmath_catmullrom(float2 p0, float2 p1, float2 p2, float2 p3, float t) +{ + float t2 = t * t; + float t3 = t2 * t; + return 0.5 * ((2.0 * p1) + (p2 - p0) * t + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 + (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3); +} + +float3 cultmath_catmullrom(float3 p0, float3 p1, float3 p2, float3 p3, float t) +{ + float t2 = t * t; + float t3 = t2 * t; + return 0.5 * ((2.0 * p1) + (p2 - p0) * t + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 + (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3); +} + +float4 cultmath_catmullrom(float4 p0, float4 p1, float4 p2, float4 p3, float t) +{ + float t2 = t * t; + float t3 = t2 * t; + return 0.5 * ((2.0 * p1) + (p2 - p0) * t + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 + (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3); +} + +float cultmath_quadratic_bezier(float p0, float p1, float p2, float t) +{ + t = cultmath_saturate(t); + float inv = 1.0 - t; + return inv * inv * p0 + 2.0 * inv * t * p1 + t * t * p2; +} + +float3 cultmath_quadratic_bezier(float3 p0, float3 p1, float3 p2, float t) +{ + t = cultmath_saturate(t); + float inv = 1.0 - t; + return inv * inv * p0 + 2.0 * inv * t * p1 + t * t * p2; +} + +float cultmath_cubic_bezier(float p0, float p1, float p2, float p3, float t) +{ + t = cultmath_saturate(t); + float inv = 1.0 - t; + return inv * inv * inv * p0 + 3.0 * inv * inv * t * p1 + 3.0 * inv * t * t * p2 + t * t * t * p3; +} + +float3 cultmath_cubic_bezier(float3 p0, float3 p1, float3 p2, float3 p3, float t) +{ + t = cultmath_saturate(t); + float inv = 1.0 - t; + return inv * inv * inv * p0 + 3.0 * inv * inv * t * p1 + 3.0 * inv * t * t * p2 + t * t * t * p3; +} + +float cultmath_hash(float value) { return cultmath_frac(sin(value) * 43758.5453); } +float cultmath_hash(float2 value) { return cultmath_hash(dot(value, float2(127.1, 311.7))); } +float cultmath_hash(float3 value) { return cultmath_hash(dot(value, float3(127.1, 311.7, 74.7))); } + +float cultmath_value_noise(float2 position) +{ + float2 cell = floor(position); + float2 local = cultmath_frac(position); + float2 u = local * local * (float2(3.0, 3.0) - float2(2.0, 2.0) * local); + + float a = cultmath_hash(cell); + float b = cultmath_hash(cell + float2(1.0, 0.0)); + float c = cultmath_hash(cell + float2(0.0, 1.0)); + float d = cultmath_hash(cell + float2(1.0, 1.0)); + + return cultmath_lerp(cultmath_lerp(a, b, u.x), cultmath_lerp(c, d, u.x), u.y); +} + +float cultmath_value_noise_bicubic(float2 position) +{ + float2 cell = floor(position); + float2 local = cultmath_frac(position); + + float y0 = cultmath_catmullrom(cultmath_hash(cell + float2(-1.0, -1.0)), cultmath_hash(cell + float2(0.0, -1.0)), cultmath_hash(cell + float2(1.0, -1.0)), cultmath_hash(cell + float2(2.0, -1.0)), local.x); + float y1 = cultmath_catmullrom(cultmath_hash(cell + float2(-1.0, 0.0)), cultmath_hash(cell + float2(0.0, 0.0)), cultmath_hash(cell + float2(1.0, 0.0)), cultmath_hash(cell + float2(2.0, 0.0)), local.x); + float y2 = cultmath_catmullrom(cultmath_hash(cell + float2(-1.0, 1.0)), cultmath_hash(cell + float2(0.0, 1.0)), cultmath_hash(cell + float2(1.0, 1.0)), cultmath_hash(cell + float2(2.0, 1.0)), local.x); + float y3 = cultmath_catmullrom(cultmath_hash(cell + float2(-1.0, 2.0)), cultmath_hash(cell + float2(0.0, 2.0)), cultmath_hash(cell + float2(1.0, 2.0)), cultmath_hash(cell + float2(2.0, 2.0)), local.x); + + return cultmath_catmullrom(y0, y1, y2, y3, local.y); +} + +float cultmath_value_noise_texture(Texture2D noise_texture, SamplerState noise_sampler, float2 uv, float scale) +{ + return noise_texture.SampleLevel(noise_sampler, uv * scale, 0.0).r; +} + +float cultmath_value_noise_texture_bicubic(Texture2D noise_texture, SamplerState noise_sampler, float2 uv, float scale, float2 texel_size) +{ + float2 position = uv * scale; + float2 cell = floor(position); + float2 local = cultmath_frac(position); + float2 base_uv = cell / scale; + + float row0 = cultmath_catmullrom( + noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(-1.0, -1.0), 0.0).r, + noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(0.0, -1.0), 0.0).r, + noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(1.0, -1.0), 0.0).r, + noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(2.0, -1.0), 0.0).r, + local.x); + float row1 = cultmath_catmullrom( + noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(-1.0, 0.0), 0.0).r, + noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(0.0, 0.0), 0.0).r, + noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(1.0, 0.0), 0.0).r, + noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(2.0, 0.0), 0.0).r, + local.x); + float row2 = cultmath_catmullrom( + noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(-1.0, 1.0), 0.0).r, + noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(0.0, 1.0), 0.0).r, + noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(1.0, 1.0), 0.0).r, + noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(2.0, 1.0), 0.0).r, + local.x); + float row3 = cultmath_catmullrom( + noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(-1.0, 2.0), 0.0).r, + noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(0.0, 2.0), 0.0).r, + noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(1.0, 2.0), 0.0).r, + noise_texture.SampleLevel(noise_sampler, base_uv + texel_size * float2(2.0, 2.0), 0.0).r, + local.x); + + return cultmath_catmullrom(row0, row1, row2, row3, local.y); +} + +#endif diff --git a/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl.meta b/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl.meta new file mode 100644 index 00000000..d3b6d2b3 --- /dev/null +++ b/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a46136cb4acb4ff2bc47b6a4f786e4b3 +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/unity/org.gamecult.cultmath/THIRD-PARTY-NOTICES.md b/unity/org.gamecult.cultmath/THIRD-PARTY-NOTICES.md new file mode 100644 index 00000000..c283df2b --- /dev/null +++ b/unity/org.gamecult.cultmath/THIRD-PARTY-NOTICES.md @@ -0,0 +1,11 @@ +# Third-Party Notices + +## 2D Simplex Noise + +`math.simplex_noise` and `cultmath_simplex_noise` port the textureless 2D +simplex-noise function by Ian McEwan and Ashima Arts, as distributed by Stefan +Gustavson's `webgl-noise` project and Unity Mathematics. + +- Copyright 2011 Ashima Arts. +- Upstream: +- License: MIT. diff --git a/package.json b/unity/org.gamecult.cultmath/package.json similarity index 52% rename from package.json rename to unity/org.gamecult.cultmath/package.json index 6da2a17a..e45d0a35 100644 --- a/package.json +++ b/unity/org.gamecult.cultmath/package.json @@ -3,12 +3,15 @@ "displayName": "CultMath", "version": "0.1.0", "unity": "2021.3", - "description": "Clean-room C# math primitives shaped for HLSL-style renderer and simulation code.", + "description": "Precompiled CultMath numeric primitives and matching HLSL semantics for GameCult Unity runtimes.", + "license": "MPL-2.0", + "author": { + "name": "GameCult" + }, "keywords": [ "GameCult", "Math", + "HLSL", "Simulation" - ], - "license": "MPL-2.0", - "category": "Scripting" + ] }