Move CultMath into the CultLib monorepo
This commit is contained in:
commit
ea88cfcd05
98 changed files with 3574 additions and 4 deletions
35
CultLib.sln
35
CultLib.sln
|
|
@ -43,6 +43,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5D20
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EveBrowserNetworkSample", "samples\eve-browser-network\EveBrowserNetworkSample.csproj", "{5FA81CC7-87B2-4585-9D8E-9BBEC0AD85FB}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "packages", "packages", "{809F86A1-1C4C-B159-0CD4-DF9D33D876CE}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cultmath", "cultmath", "{B48EAE30-152F-01E3-6E31-82B350494D3C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CultMath", "packages\cultmath\src\CultMath\CultMath.csproj", "{54D543F7-8EB7-4A11-977E-22D3EE2D8C3F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CultMath.Tests", "packages\cultmath\tests\CultMath.Tests\CultMath.Tests.csproj", "{FEF9DA88-2177-4FD0-885D-7096ACFA8A9D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -257,6 +265,30 @@ Global
|
|||
{5FA81CC7-87B2-4585-9D8E-9BBEC0AD85FB}.Release|x64.Build.0 = Release|Any CPU
|
||||
{5FA81CC7-87B2-4585-9D8E-9BBEC0AD85FB}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{5FA81CC7-87B2-4585-9D8E-9BBEC0AD85FB}.Release|x86.Build.0 = Release|Any CPU
|
||||
{54D543F7-8EB7-4A11-977E-22D3EE2D8C3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{54D543F7-8EB7-4A11-977E-22D3EE2D8C3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{54D543F7-8EB7-4A11-977E-22D3EE2D8C3F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{54D543F7-8EB7-4A11-977E-22D3EE2D8C3F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{54D543F7-8EB7-4A11-977E-22D3EE2D8C3F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{54D543F7-8EB7-4A11-977E-22D3EE2D8C3F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{54D543F7-8EB7-4A11-977E-22D3EE2D8C3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{54D543F7-8EB7-4A11-977E-22D3EE2D8C3F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{54D543F7-8EB7-4A11-977E-22D3EE2D8C3F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{54D543F7-8EB7-4A11-977E-22D3EE2D8C3F}.Release|x64.Build.0 = Release|Any CPU
|
||||
{54D543F7-8EB7-4A11-977E-22D3EE2D8C3F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{54D543F7-8EB7-4A11-977E-22D3EE2D8C3F}.Release|x86.Build.0 = Release|Any CPU
|
||||
{FEF9DA88-2177-4FD0-885D-7096ACFA8A9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FEF9DA88-2177-4FD0-885D-7096ACFA8A9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FEF9DA88-2177-4FD0-885D-7096ACFA8A9D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{FEF9DA88-2177-4FD0-885D-7096ACFA8A9D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{FEF9DA88-2177-4FD0-885D-7096ACFA8A9D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{FEF9DA88-2177-4FD0-885D-7096ACFA8A9D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{FEF9DA88-2177-4FD0-885D-7096ACFA8A9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FEF9DA88-2177-4FD0-885D-7096ACFA8A9D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FEF9DA88-2177-4FD0-885D-7096ACFA8A9D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{FEF9DA88-2177-4FD0-885D-7096ACFA8A9D}.Release|x64.Build.0 = Release|Any CPU
|
||||
{FEF9DA88-2177-4FD0-885D-7096ACFA8A9D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{FEF9DA88-2177-4FD0-885D-7096ACFA8A9D}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -279,5 +311,8 @@ Global
|
|||
{7F32D96B-AB5D-4A72-9217-B5DCCFEFC7E5} = {E733CBFF-42AE-4B6A-B5B9-0071ECFB45F1}
|
||||
{B9A441EC-DADE-4500-B092-C635F3EB296A} = {39D316AB-ABB5-49A2-8E83-CAF4C75B48F4}
|
||||
{5FA81CC7-87B2-4585-9D8E-9BBEC0AD85FB} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
|
||||
{B48EAE30-152F-01E3-6E31-82B350494D3C} = {809F86A1-1C4C-B159-0CD4-DF9D33D876CE}
|
||||
{54D543F7-8EB7-4A11-977E-22D3EE2D8C3F} = {B48EAE30-152F-01E3-6E31-82B350494D3C}
|
||||
{FEF9DA88-2177-4FD0-885D-7096ACFA8A9D} = {B48EAE30-152F-01E3-6E31-82B350494D3C}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
13
README.md
13
README.md
|
|
@ -38,7 +38,8 @@ and pretending the label makes it furniture.
|
|||
| Job | Start With | Owns | Use When | Do Not Use It For |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Local typed state | `GameCult.Caching` / CultCache | Document identity, schema compatibility, record keys, indexes, globals, and local persistence | You need a typed cache, file-compatible save data, local reactive reads, or a stable domain document model | Peer discovery, transport security, shard routing, or mesh consensus |
|
||||
| Procedural geometry state | `GameCult.Geometry` | CultCache-native domain trees, LOD build requests, selected-cut diagnostics, and chunk artifact payloads | Rust, Unity, or remote workers need to share CSG/LOD geometry and graph metadata as typed state | Transport policy, peer discovery, or gameplay authority |
|
||||
| Portable math | `GameCult.Math` / CultMath | Shader-shaped numeric primitives, deterministic random, noise, and CPU/GPU parity helpers | Shared simulation and geometry code must not depend on an engine's math or RNG types | Domain-specific geometry, rendering, or world authority |
|
||||
| Procedural geometry | `GameCult.Geometry` / CultGeometry | Isosurface extraction plus CultCache-native domains, LOD requests, selected cuts, and mesh artifacts | C#, Rust, Godot, or remote workers need shared geometry algorithms and typed results | Transport policy, peer discovery, or gameplay authority |
|
||||
| Network transport and database plumbing | `GameCult.Networking` / CultNet | Native RUDP sessions, LiteNetLib/TCP adapters, an authenticated WebSocket host adapter, schema-v0 wire contracts, shard authority, raw document replication, snapshots, and subscriptions | You need a client/server pipe, login/session flow, schema discovery, reliable UDP, or a low-level distributed CultCache lane | Gameplay-facing mesh ergonomics, Verse policy, mod branches, or simulation consensus composition |
|
||||
| Distributed realtime gameplay state | `GameCult.Mesh` / CultMesh | Public mesh entrypoints, Verse discovery, peer exchange, shard replication defaults, authority leases, client prediction, and witness consensus | You want the game to treat clients and servers as one reactive database for persistent state, input state, and simulation facts | A tiny local-only tool, a bare transport client, or a storage format contract |
|
||||
| Realtime media/frame streams | `GameCult.Mesh` / CultMesh streaming mode | Stream identity, authority, clock metadata, body transport negotiation, frame cursors, and backpressure state | Audio/video/tensor frames need to move between runtimes through shared memory, GPU handles, platform buffers, or CultCache page refs | Durable document mutation, mesh consensus facts, or pretending inline bytes are zero-copy |
|
||||
|
|
@ -47,8 +48,10 @@ and pretending the label makes it furniture.
|
|||
Quick rule:
|
||||
|
||||
- Choose CultCache when the problem is "how do I model and persist typed state?"
|
||||
- Choose GameCult.Geometry when the problem is "how do geometry workers share
|
||||
domain trees, LOD build requests, and mesh chunks as typed state?"
|
||||
- Choose GameCult.Math when the problem is "which portable numeric and random
|
||||
semantics do these runtimes share?"
|
||||
- Choose GameCult.Geometry when the problem is "how do geometry workers build
|
||||
and share engine-neutral geometry?"
|
||||
- Choose CultNet when the problem is "how do peers exchange authenticated,
|
||||
schema-aware database messages?"
|
||||
- Choose CultMesh when the problem is "how does a game join a Verse and share
|
||||
|
|
@ -97,7 +100,8 @@ The solution includes:
|
|||
- `GameCult.Caching.NewtonsoftJson`: Newtonsoft.Json-backed persistence for the cache
|
||||
- `GameCult.Caching.MessagePack.Generator`: source generator for MessagePack formatters for cache models
|
||||
- `GameCult.Caching.MessagePack.Analyzers`: packaging project that delivers the generator to consuming projects
|
||||
- `GameCult.Geometry`: CultCache-native geometry domain, selected-cut, and chunk artifact documents for VibeGeometry/Fensalir-style pipelines
|
||||
- `GameCult.Math` / CultMath: portable numeric primitives, deterministic random, noise, and shader-parity helpers
|
||||
- `GameCult.Geometry` / CultGeometry: engine-neutral geometry algorithms plus CultCache-native domain, selected-cut, and mesh artifact documents
|
||||
- `GameCult.Networking`: encrypted login/register/verify flows, schema-v0 contracts, transport adapters, and native RUDP sessions
|
||||
- `GameCult.Networking.WebSockets`: bounded ASP.NET Core binary WebSocket host adapter for browser CultMesh clients
|
||||
- `GameCult.Mesh`: CultMesh package home for distributed realtime database, shard replication, client prediction, Verse discovery, and mesh witness consensus
|
||||
|
|
@ -134,6 +138,7 @@ tests/
|
|||
GameCult.Geometry.Tests/
|
||||
GameCult.Networking.Tests/
|
||||
packages/
|
||||
cultmath/
|
||||
cultcache-ts/
|
||||
cultnet-ts/
|
||||
cultmesh-ts/
|
||||
|
|
|
|||
9
packages/cultmath/.gitignore
vendored
Normal file
9
packages/cultmath/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
bin/
|
||||
obj/
|
||||
**/bin/
|
||||
**/obj/
|
||||
target/
|
||||
**/target/
|
||||
/native/cultmath-core/target.meta
|
||||
.tools/
|
||||
/artifacts/
|
||||
134
packages/cultmath/README.md
Normal file
134
packages/cultmath/README.md
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
# CultMath
|
||||
|
||||
CultMath is the project name. Its clean .NET package identity is
|
||||
`GameCult.Math`, owned and released from the CultLib monorepo.
|
||||
|
||||
Clean-room math shaped for people who think in HLSL.
|
||||
|
||||
CultMath provides lowercase vector types, shader-style intrinsics, and native
|
||||
hot-path kernels so math-heavy renderer code can move between C#, Rust, and HLSL
|
||||
without turning every line into a translation exercise. It is not a
|
||||
Unity.Mathematics clone. It is a small, Aquarium-born library built from public
|
||||
shader semantics.
|
||||
|
||||
## Current Surface
|
||||
|
||||
- `float2`, `float3`, `float4`
|
||||
- component-wise arithmetic
|
||||
- scalar-to-vector splats
|
||||
- swizzles for common lanes
|
||||
- conversions to and from `System.Numerics`
|
||||
- `Random`, a compact deterministic XORShift stream with scalar and vector draws
|
||||
- `BatchMath` SoA kernels over `Span<float>` / `ReadOnlySpan<float>` using
|
||||
`System.Numerics.Vector<float>` when available, including radial falloff
|
||||
acceleration and semi-implicit Euler integration
|
||||
- `math` intrinsics: `radians`, `degrees`, `abs`, `floor`, `ceil`, `frac`,
|
||||
`min`, `max`, `clamp`, `saturate`, `lerp`, `step`, `smoothstep`, `dot`,
|
||||
`cross`, `length`, `distance`, `normalize`, `reflect`, `csum`, exponential
|
||||
`decay`/`damp`, intercept/segment helpers, Catmull-Rom, Bezier curves, and
|
||||
deterministic value-noise primitives
|
||||
- `shaders/CultMath.hlsl`, a canonical HLSL mirror include for shader-side
|
||||
parity. HLSL already owns `float2`, `float3`, and `float4`; the include
|
||||
exposes `cultmath_*` functions for shared semantics, including CultMath's
|
||||
safe `normalize` contract.
|
||||
- `Voronoi.SampleTones`, a C# batch surface that calls the Rust
|
||||
`cultmath-core` native kernel when `cultmath_core` is available and falls back
|
||||
to the managed parity path otherwise.
|
||||
|
||||
## Native Core
|
||||
|
||||
The canonical hot-path body lives in `native/cultmath-core`. It exports a C ABI
|
||||
for batch kernels such as `cultmath_apollonian_voronoi_tones`.
|
||||
|
||||
Runtime wrappers own syntax and integration. Rust owns optimized primitive
|
||||
kernel behavior and parity fixtures.
|
||||
|
||||
## Example
|
||||
|
||||
```csharp
|
||||
using CultMath;
|
||||
|
||||
float3 normal = math.normalize(new float3(0.25f, 1.0f, -0.1f));
|
||||
float rim = math.saturate(1.0f - math.dot(normal, new float3(0.0f, 0.0f, -1.0f)));
|
||||
float glow = math.smoothstep(0.2f, 1.0f, rim);
|
||||
float grain = math.value_noise_bicubic(math.float2(12.0f, 3.5f));
|
||||
```
|
||||
|
||||
```hlsl
|
||||
#include "CultMath.hlsl"
|
||||
|
||||
float3 normal = cultmath_normalize(float3(0.25, 1.0, -0.1));
|
||||
float rim = cultmath_saturate(1.0 - dot(normal, float3(0.0, 0.0, -1.0)));
|
||||
float glow = cultmath_smoothstep(0.2, 1.0, rim);
|
||||
float grain = cultmath_value_noise_bicubic(float2(12.0, 3.5));
|
||||
```
|
||||
|
||||
## Provenance
|
||||
|
||||
The small spline, damping, and pursuit helpers were promoted from Aetheria's
|
||||
local `AetheriaMath` surface. The value-noise family follows public shader
|
||||
literature from Inigo Quilez's noise and mini-spline articles, expressed in
|
||||
CultMath names with tests instead of copied project-local helper drift.
|
||||
|
||||
## Build
|
||||
|
||||
```powershell
|
||||
dotnet build packages/cultmath/src/CultMath/CultMath.csproj
|
||||
dotnet test packages/cultmath/tests/CultMath.Tests/CultMath.Tests.csproj
|
||||
cargo test --manifest-path packages/cultmath/native/cultmath-core/Cargo.toml
|
||||
cargo build --release --manifest-path packages/cultmath/native/cultmath-core/Cargo.toml
|
||||
.\packages\cultmath\scripts\build-unity-package.ps1
|
||||
```
|
||||
|
||||
## Unity package
|
||||
|
||||
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.
|
||||
|
||||
```json
|
||||
"org.gamecult.cultmath": "https://github.com/GameCult/CultLib.git?path=/packages/cultmath/unity/org.gamecult.cultmath"
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
CultMath keeps portable DXC outside git under `.tools/`:
|
||||
|
||||
```powershell
|
||||
.\tools\get-dxc.ps1
|
||||
.\tools\get-spirv-cross.ps1
|
||||
.\tools\compile-hlsl-spirv.ps1 `
|
||||
-ShaderPath E:\Projects\Odin\crates\muninn-move-tracker\shaders\MoveSphereCandidate.comp.hlsl `
|
||||
-OutputPath E:\Projects\Odin\crates\muninn-move-tracker\artifacts\shader\MoveSphereCandidate.comp.spv `
|
||||
-IncludePath E:\Projects\CultMath\shaders
|
||||
.\tools\compile-spirv-metal.ps1 `
|
||||
-SpirvPath E:\Projects\Odin\crates\muninn-move-tracker\artifacts\shader\MoveSphereCandidate.comp.spv `
|
||||
-OutputPath E:\Projects\Odin\crates\muninn-move-tracker\artifacts\shader\MoveSphereCandidate.comp.metal
|
||||
```
|
||||
|
||||
DXC emits Vulkan SPIR-V with `-spirv`. `-TargetEnv vulkan1.2` is exposed by the
|
||||
script, but the current Windows DXC release compiled this shader successfully
|
||||
with DXC's default Vulkan target while rejecting the dotted target-env value.
|
||||
The SPIR-V script applies default Vulkan register shifts for cbuffers, textures,
|
||||
UAVs, and samplers so cross-compiled Metal buffers do not alias by resource
|
||||
class. SPIRV-Cross emits Metal Shading Language from SPIR-V on Windows; building
|
||||
an iPad-ready `.metallib` still belongs to Apple's Metal compiler on an Apple
|
||||
toolchain.
|
||||
|
||||
## Doctrine
|
||||
|
||||
The goal is mechanical sympathy between CPU-side authoring, native kernels, and
|
||||
GPU-side shader math. If an intrinsic exists here, it should behave like the
|
||||
shader concept unless a runtime's numeric rules make that impossible. Weird
|
||||
deviations get tests and documentation, not folklore.
|
||||
|
||||
CultMath is still not a shader generator. Projects should include
|
||||
`shaders/CultMath.hlsl` directly or add that directory to their shader include
|
||||
path. If a shared operation needs CPU/GPU parity, put the primitive here and
|
||||
test it here before copying local math helpers into a renderer or daemon.
|
||||
7
packages/cultmath/README.md.meta
Normal file
7
packages/cultmath/README.md.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 867daebafe0ce48479cf043461ee6bdf
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
11
packages/cultmath/THIRD-PARTY-NOTICES.md
Normal file
11
packages/cultmath/THIRD-PARTY-NOTICES.md
Normal file
|
|
@ -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: <https://github.com/ashima/webgl-noise>
|
||||
- License: MIT.
|
||||
7
packages/cultmath/THIRD-PARTY-NOTICES.md.meta
Normal file
7
packages/cultmath/THIRD-PARTY-NOTICES.md.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7c28c8631c7d4054eb751d7ce952df33
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
packages/cultmath/docs.meta
Normal file
8
packages/cultmath/docs.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: de535b89253410b41b5989f712b80af4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
29
packages/cultmath/docs/design.md
Normal file
29
packages/cultmath/docs/design.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Design
|
||||
|
||||
CultMath exists to make renderer math readable in every place it needs to live:
|
||||
Rust hot kernels, C# host code, and HLSL shader code.
|
||||
|
||||
## Rules
|
||||
|
||||
- Keep type names HLSL-shaped: `float2`, `float3`, `float4`, and `math`.
|
||||
- Keep Rust as the owner of native hot-path kernels and parity fixtures.
|
||||
- Keep C#, shader, and scripting surfaces as runtime wrappers, not competing
|
||||
math authorities.
|
||||
- Keep `shaders/CultMath.hlsl` as the canonical shader mirror for shared
|
||||
intrinsics. 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.
|
||||
- Prefer small immutable value types.
|
||||
- Prefer component-wise overloads over clever generic machinery until the API
|
||||
earns that complexity.
|
||||
- Keep the surface boring and exact. Noise primitives are allowed when they are
|
||||
source-grounded, shader-shaped, and shared by more than one project. Matrices,
|
||||
quaternions, and packing helpers still need an owner before they enter.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Compiling C# to shaders.
|
||||
- Generating HLSL from Rust or Rust from HLSL.
|
||||
- Replacing `System.Numerics` for general .NET work.
|
||||
- Recreating every Unity.Mathematics type because a spreadsheet somewhere says
|
||||
“coverage.” That way lies decorative obesity.
|
||||
7
packages/cultmath/docs/design.md.meta
Normal file
7
packages/cultmath/docs/design.md.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 07021a8be816e3448a9490c10845b0aa
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
packages/cultmath/native.meta
Normal file
8
packages/cultmath/native.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9d1cb2ac88c530b4d9da456523115e12
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
packages/cultmath/native/cultmath-core.meta
Normal file
8
packages/cultmath/native/cultmath-core.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a42d65478346c354c8b4f353c5581b49
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
packages/cultmath/native/cultmath-core/Cargo.lock
generated
Normal file
7
packages/cultmath/native/cultmath-core/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "cultmath-core"
|
||||
version = "0.1.0"
|
||||
7
packages/cultmath/native/cultmath-core/Cargo.lock.meta
Normal file
7
packages/cultmath/native/cultmath-core/Cargo.lock.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f1c365f7eead2684ba621124e653bdab
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
13
packages/cultmath/native/cultmath-core/Cargo.toml
Normal file
13
packages/cultmath/native/cultmath-core/Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "cultmath-core"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "cultmath_core"
|
||||
crate-type = ["cdylib", "staticlib"]
|
||||
|
||||
[profile.release]
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
7
packages/cultmath/native/cultmath-core/Cargo.toml.meta
Normal file
7
packages/cultmath/native/cultmath-core/Cargo.toml.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cdd2d4eab053b764b9a06d36b080e43e
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
packages/cultmath/native/cultmath-core/src.meta
Normal file
8
packages/cultmath/native/cultmath-core/src.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7de448b7e258d2c448bb7391051cc628
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
213
packages/cultmath/native/cultmath-core/src/lib.rs
Normal file
213
packages/cultmath/native/cultmath-core/src/lib.rs
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct CultMathColor32 {
|
||||
pub r: u8,
|
||||
pub g: u8,
|
||||
pub b: u8,
|
||||
pub a: u8,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cultmath_apollonian_voronoi_tones(
|
||||
xs: *const f32,
|
||||
ys: *const f32,
|
||||
tones: *const u8,
|
||||
spans: *const f32,
|
||||
count: usize,
|
||||
resolution_y: f32,
|
||||
frame_index: i32,
|
||||
out_colors: *mut CultMathColor32,
|
||||
) -> i32 {
|
||||
if xs.is_null() || ys.is_null() || tones.is_null() || spans.is_null() || out_colors.is_null() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
let xs = unsafe { std::slice::from_raw_parts(xs, count) };
|
||||
let ys = unsafe { std::slice::from_raw_parts(ys, count) };
|
||||
let tones = unsafe { std::slice::from_raw_parts(tones, count) };
|
||||
let spans = unsafe { std::slice::from_raw_parts(spans, count) };
|
||||
let out_colors = unsafe { std::slice::from_raw_parts_mut(out_colors, count) };
|
||||
for index in 0..count {
|
||||
out_colors[index] = tone_color(
|
||||
xs[index],
|
||||
ys[index],
|
||||
resolution_y,
|
||||
frame_index,
|
||||
tones[index],
|
||||
spans[index],
|
||||
);
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
fn tone_color(px: f32, py: f32, resolution_y: f32, frame_index: i32, tone: u8, span: f32) -> CultMathColor32 {
|
||||
let sample = stable_sample(px, py, resolution_y, frame_index, tone, span);
|
||||
let luminance = saturate(sample.0 * 0.299 + sample.1 * 0.587 + sample.2 * 0.114);
|
||||
match tone {
|
||||
3 => {
|
||||
let glow = 0.62 + luminance * 0.38;
|
||||
color(255.0 * glow, 96.0 * glow, 14.0 * glow)
|
||||
}
|
||||
4 => {
|
||||
let glow = 0.68 + luminance * 0.32;
|
||||
color(245.0 * glow, 248.0 * glow, 252.0 * glow)
|
||||
}
|
||||
_ => {
|
||||
let (gain, lift) = match tone {
|
||||
1 => (0.42, 14.0),
|
||||
2 => (1.05, 24.0),
|
||||
_ => (0.24, 4.0),
|
||||
};
|
||||
color(
|
||||
lift + sample.0 * 255.0 * gain,
|
||||
lift + sample.1 * 255.0 * gain,
|
||||
lift + sample.2 * 255.0 * gain,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn stable_sample(px: f32, py: f32, resolution_y: f32, frame_index: i32, tone: u8, span: f32) -> (f32, f32, f32) {
|
||||
let seed = px * 12.9898 + py * 78.233 + hash1((tone as f32 + 1.0) * 19.19);
|
||||
let jitter_x = (hash1(seed) - 0.5) * span;
|
||||
let jitter_y = (hash1(seed + 37.17) - 0.5) * span;
|
||||
voronoi(px + jitter_x, py + jitter_y, resolution_y, frame_index)
|
||||
}
|
||||
|
||||
fn voronoi(px: f32, py: f32, resolution_y: f32, frame_index: i32) -> (f32, f32, f32) {
|
||||
let time = frame_index as f32 / 120.0;
|
||||
let scale = 6.0;
|
||||
let x = scale * px / resolution_y.max(1.0);
|
||||
let y = scale * py / resolution_y.max(1.0);
|
||||
let nx = x.floor();
|
||||
let ny = y.floor();
|
||||
let fx = frac(x);
|
||||
let fy = frac(y);
|
||||
let mut distance = 8.0;
|
||||
let mut red = 0.0;
|
||||
let mut green = 0.0;
|
||||
let mut blue = 0.0;
|
||||
let smoothness = 0.005;
|
||||
|
||||
for j in -2..=2 {
|
||||
for i in -2..=2 {
|
||||
let gx = i as f32;
|
||||
let gy = j as f32;
|
||||
let (mut ox, mut oy) = hash2(nx + gx, ny + gy);
|
||||
let weight = ox * 0.5 + 0.5;
|
||||
ox = 0.5 + 0.5 * (time + 6.2831 * ox).sin();
|
||||
oy = 0.5 + 0.5 * (time + 6.2831 * oy).sin();
|
||||
let dx = (gx - fx + ox).abs();
|
||||
let dy = (gy - fy + oy).abs();
|
||||
let d = dx.max(dy) * weight;
|
||||
let seed = hash1((nx + gx) * 7.0 + (ny + gy) * 113.0);
|
||||
let hue = frac((nx + gx) * 0.173 + (ny + gy) * 0.379 + seed * 0.431);
|
||||
let (candidate_red, candidate_green, candidate_blue) = pastel_spectrum(hue);
|
||||
let h = smoothstep(0.0, 1.0, 0.5 + 0.5 * (distance - d) / smoothness);
|
||||
let correction = h * (1.0 - h) * smoothness / (1.0 + 3.0 * smoothness);
|
||||
distance = lerp(distance, d, h) - correction;
|
||||
red = lerp(red, candidate_red, h) - correction;
|
||||
green = lerp(green, candidate_green, h) - correction;
|
||||
blue = lerp(blue, candidate_blue, h) - correction;
|
||||
}
|
||||
}
|
||||
|
||||
let edge_dimming = 1.0 - 0.1 * smoothstep(0.04, 0.05, distance);
|
||||
(
|
||||
(red * edge_dimming).max(0.0),
|
||||
(green * edge_dimming).max(0.0),
|
||||
(blue * edge_dimming).max(0.0),
|
||||
)
|
||||
}
|
||||
|
||||
fn hash1(value: f32) -> f32 {
|
||||
frac(value.sin() * 43758.5453)
|
||||
}
|
||||
|
||||
fn hash2(x: f32, y: f32) -> (f32, f32) {
|
||||
(
|
||||
frac((x * 127.1 + y * 311.7).sin() * 43758.5453),
|
||||
frac((x * 269.5 + y * 183.3).sin() * 43758.5453),
|
||||
)
|
||||
}
|
||||
|
||||
fn frac(value: f32) -> f32 {
|
||||
value - value.floor()
|
||||
}
|
||||
|
||||
fn saturate(value: f32) -> f32 {
|
||||
value.clamp(0.0, 1.0)
|
||||
}
|
||||
|
||||
fn smoothstep(edge0: f32, edge1: f32, value: f32) -> f32 {
|
||||
let t = saturate((value - edge0) / (edge1 - edge0));
|
||||
t * t * (3.0 - 2.0 * t)
|
||||
}
|
||||
|
||||
fn lerp(start: f32, end: f32, amount: f32) -> f32 {
|
||||
start + (end - start) * amount
|
||||
}
|
||||
|
||||
fn pastel_spectrum(hue: f32) -> (f32, f32, f32) {
|
||||
hsv_to_rgb(hue, 0.52, 0.98)
|
||||
}
|
||||
|
||||
fn hsv_to_rgb(hue: f32, saturation: f32, value: f32) -> (f32, f32, f32) {
|
||||
let h = frac(hue) * 6.0;
|
||||
let c = value * saturation;
|
||||
let x = c * (1.0 - ((h % 2.0) - 1.0).abs());
|
||||
let m = value - c;
|
||||
let (r, g, b) = if h < 1.0 {
|
||||
(c, x, 0.0)
|
||||
} else if h < 2.0 {
|
||||
(x, c, 0.0)
|
||||
} else if h < 3.0 {
|
||||
(0.0, c, x)
|
||||
} else if h < 4.0 {
|
||||
(0.0, x, c)
|
||||
} else if h < 5.0 {
|
||||
(x, 0.0, c)
|
||||
} else {
|
||||
(c, 0.0, x)
|
||||
};
|
||||
|
||||
(r + m, g + m, b + m)
|
||||
}
|
||||
|
||||
fn color(r: f32, g: f32, b: f32) -> CultMathColor32 {
|
||||
CultMathColor32 {
|
||||
r: r.clamp(0.0, 255.0) as u8,
|
||||
g: g.clamp(0.0, 255.0) as u8,
|
||||
b: b.clamp(0.0, 255.0) as u8,
|
||||
a: 255,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn batch_sampler_writes_colors() {
|
||||
let xs = [0.0, 20.0, 100.0];
|
||||
let ys = [0.0, 30.0, 140.0];
|
||||
let tones = [0, 3, 4];
|
||||
let spans = [1920.0, 8.0, 8.0];
|
||||
let mut out = [CultMathColor32::default(); 3];
|
||||
let rc = unsafe {
|
||||
cultmath_apollonian_voronoi_tones(
|
||||
xs.as_ptr(),
|
||||
ys.as_ptr(),
|
||||
tones.as_ptr(),
|
||||
spans.as_ptr(),
|
||||
xs.len(),
|
||||
1080.0,
|
||||
12,
|
||||
out.as_mut_ptr(),
|
||||
)
|
||||
};
|
||||
assert_eq!(rc, 0);
|
||||
assert!(out.iter().any(|color| color.r != 0 || color.g != 0 || color.b != 0));
|
||||
}
|
||||
}
|
||||
7
packages/cultmath/native/cultmath-core/src/lib.rs.meta
Normal file
7
packages/cultmath/native/cultmath-core/src/lib.rs.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b06c69c432a633f4584830d6b28d4473
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
packages/cultmath/scripts.meta
Normal file
8
packages/cultmath/scripts.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0ddff5b782cf14e43ae5cda048e040d8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
119
packages/cultmath/scripts/build-unity-package.ps1
Normal file
119
packages/cultmath/scripts/build-unity-package.ps1
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
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",
|
||||
"LICENSE.md.meta",
|
||||
"README.md",
|
||||
"README.md.meta",
|
||||
"THIRD-PARTY-NOTICES.md",
|
||||
"THIRD-PARTY-NOTICES.md.meta",
|
||||
"package.json",
|
||||
"package.json.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
|
||||
$pluginMeta = Get-Content -LiteralPath ($trackedAssembly + ".meta") -Raw
|
||||
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"
|
||||
}
|
||||
$facadeDefinitions = @(Get-ChildItem -LiteralPath $templateRoot -Recurse -File -Filter "*.asmdef")
|
||||
if ($facadeDefinitions.Count -ne 0) {
|
||||
throw "CultMath's precompiled DLL is the Unity assembly authority; empty asmdef facades are forbidden"
|
||||
}
|
||||
$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")
|
||||
$stagedFacades = @(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) {
|
||||
throw "CultMath package must contain one owned auto-referenced DLL, no C# source files, and no asmdef facade"
|
||||
}
|
||||
|
||||
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)"
|
||||
Write-Host "Asmdef facades: $($stagedFacades.Count)"
|
||||
8
packages/cultmath/shaders.meta
Normal file
8
packages/cultmath/shaders.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3bed74ebcba445543800218dbe185034
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
302
packages/cultmath/shaders/CultMath.hlsl
Normal file
302
packages/cultmath/shaders/CultMath.hlsl
Normal file
|
|
@ -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<float> 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<float> 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
|
||||
7
packages/cultmath/shaders/CultMath.hlsl.meta
Normal file
7
packages/cultmath/shaders/CultMath.hlsl.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 33174674795143c4a905408718f956f8
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
packages/cultmath/src.meta
Normal file
8
packages/cultmath/src.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 64f48d0cae0810e4ba9903e9094fe4c6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
packages/cultmath/src/CultMath.meta
Normal file
8
packages/cultmath/src/CultMath.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e857b457b93397d4fb4ad8481cb72e4c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
245
packages/cultmath/src/CultMath/BatchMath.cs
Normal file
245
packages/cultmath/src/CultMath/BatchMath.cs
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
public static class BatchMath
|
||||
{
|
||||
private const float MinimumDistanceSquared = 1.0e-8f;
|
||||
|
||||
public static int LaneCount => Vector<float>.Count;
|
||||
|
||||
public static bool IsHardwareAccelerated => Vector.IsHardwareAccelerated;
|
||||
|
||||
public static void Clear(Span<float> values) => values.Clear();
|
||||
|
||||
public static void AddRadialFalloffAcceleration2D(
|
||||
ReadOnlySpan<float> positionX,
|
||||
ReadOnlySpan<float> positionY,
|
||||
float centerX,
|
||||
float centerY,
|
||||
float strength,
|
||||
float radius,
|
||||
Span<float> accelerationX,
|
||||
Span<float> accelerationY)
|
||||
{
|
||||
ValidateEqualLengths(positionX.Length, positionY.Length, accelerationX.Length, accelerationY.Length);
|
||||
|
||||
if (radius <= 0.0f || strength == 0.0f || positionX.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var radiusVector = new Vector<float>(radius);
|
||||
var centerXVector = new Vector<float>(centerX);
|
||||
var centerYVector = new Vector<float>(centerY);
|
||||
var strengthVector = new Vector<float>(strength);
|
||||
var one = Vector<float>.One;
|
||||
var zero = Vector<float>.Zero;
|
||||
var minimumDistanceSquared = new Vector<float>(MinimumDistanceSquared);
|
||||
|
||||
var i = 0;
|
||||
var lanes = Vector<float>.Count;
|
||||
var pxBuffer = new float[lanes];
|
||||
var pyBuffer = new float[lanes];
|
||||
var axBuffer = new float[lanes];
|
||||
var ayBuffer = new float[lanes];
|
||||
for (; i <= positionX.Length - lanes; i += lanes)
|
||||
{
|
||||
CopySpanToArray(positionX.Slice(i, lanes), pxBuffer);
|
||||
CopySpanToArray(positionY.Slice(i, lanes), pyBuffer);
|
||||
CopySpanToArray(accelerationX.Slice(i, lanes), axBuffer);
|
||||
CopySpanToArray(accelerationY.Slice(i, lanes), ayBuffer);
|
||||
|
||||
var px = new Vector<float>(pxBuffer);
|
||||
var py = new Vector<float>(pyBuffer);
|
||||
var dx = centerXVector - px;
|
||||
var dy = centerYVector - py;
|
||||
var distanceSquared = Vector.Max(dx * dx + dy * dy, minimumDistanceSquared);
|
||||
var distance = Vector.SquareRoot(distanceSquared);
|
||||
var inRange = Vector.LessThanOrEqual(distance, radiusVector);
|
||||
var falloff = Vector.Max(zero, one - distance / radiusVector);
|
||||
var magnitude = strengthVector * falloff;
|
||||
var ax = Vector.ConditionalSelect(inRange, dx / distance * magnitude, zero);
|
||||
var ay = Vector.ConditionalSelect(inRange, dy / distance * magnitude, zero);
|
||||
|
||||
(new Vector<float>(axBuffer) + ax).CopyTo(axBuffer);
|
||||
(new Vector<float>(ayBuffer) + ay).CopyTo(ayBuffer);
|
||||
CopyArrayToSpan(axBuffer, accelerationX.Slice(i, lanes));
|
||||
CopyArrayToSpan(ayBuffer, accelerationY.Slice(i, lanes));
|
||||
}
|
||||
|
||||
for (; i < positionX.Length; i++)
|
||||
{
|
||||
var dx = centerX - positionX[i];
|
||||
var dy = centerY - positionY[i];
|
||||
var distanceSquared = MathF.Max(dx * dx + dy * dy, MinimumDistanceSquared);
|
||||
var distance = MathF.Sqrt(distanceSquared);
|
||||
if (distance > radius)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var falloff = MathF.Max(0.0f, 1.0f - distance / radius);
|
||||
var magnitude = strength * falloff;
|
||||
accelerationX[i] += dx / distance * magnitude;
|
||||
accelerationY[i] += dy / distance * magnitude;
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddRadialFalloffAcceleration2D(
|
||||
ReadOnlySpan<float2> positions,
|
||||
float2 center,
|
||||
float strength,
|
||||
float radius,
|
||||
Span<float2> acceleration)
|
||||
{
|
||||
ValidateEqualLengths(positions.Length, acceleration.Length);
|
||||
|
||||
if (radius <= 0.0f || strength == 0.0f || positions.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < positions.Length; i++)
|
||||
{
|
||||
var delta = center - positions[i];
|
||||
var distanceSquared = MathF.Max(delta.x * delta.x + delta.y * delta.y, MinimumDistanceSquared);
|
||||
var distance = MathF.Sqrt(distanceSquared);
|
||||
if (distance > radius)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var falloff = MathF.Max(0.0f, 1.0f - distance / radius);
|
||||
acceleration[i] += delta / distance * (strength * falloff);
|
||||
}
|
||||
}
|
||||
|
||||
public static void IntegrateSemiImplicitEuler2D(
|
||||
float deltaTime,
|
||||
ReadOnlySpan<float> dynamicMask,
|
||||
Span<float> positionX,
|
||||
Span<float> positionY,
|
||||
Span<float> velocityX,
|
||||
Span<float> velocityY,
|
||||
ReadOnlySpan<float> accelerationX,
|
||||
ReadOnlySpan<float> accelerationY)
|
||||
{
|
||||
if (deltaTime <= 0.0f)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(deltaTime), "Delta time must be positive.");
|
||||
}
|
||||
|
||||
ValidateEqualLengths(
|
||||
dynamicMask.Length,
|
||||
positionX.Length,
|
||||
positionY.Length,
|
||||
velocityX.Length,
|
||||
velocityY.Length,
|
||||
accelerationX.Length,
|
||||
accelerationY.Length);
|
||||
|
||||
var delta = new Vector<float>(deltaTime);
|
||||
var i = 0;
|
||||
var lanes = Vector<float>.Count;
|
||||
var maskBuffer = new float[lanes];
|
||||
var pxBuffer = new float[lanes];
|
||||
var pyBuffer = new float[lanes];
|
||||
var vxBuffer = new float[lanes];
|
||||
var vyBuffer = new float[lanes];
|
||||
var axBuffer = new float[lanes];
|
||||
var ayBuffer = new float[lanes];
|
||||
for (; i <= positionX.Length - lanes; i += lanes)
|
||||
{
|
||||
CopySpanToArray(dynamicMask.Slice(i, lanes), maskBuffer);
|
||||
CopySpanToArray(velocityX.Slice(i, lanes), vxBuffer);
|
||||
CopySpanToArray(velocityY.Slice(i, lanes), vyBuffer);
|
||||
CopySpanToArray(positionX.Slice(i, lanes), pxBuffer);
|
||||
CopySpanToArray(positionY.Slice(i, lanes), pyBuffer);
|
||||
CopySpanToArray(accelerationX.Slice(i, lanes), axBuffer);
|
||||
CopySpanToArray(accelerationY.Slice(i, lanes), ayBuffer);
|
||||
|
||||
var mask = new Vector<float>(maskBuffer);
|
||||
var vx = new Vector<float>(vxBuffer);
|
||||
var vy = new Vector<float>(vyBuffer);
|
||||
var px = new Vector<float>(pxBuffer);
|
||||
var py = new Vector<float>(pyBuffer);
|
||||
var ax = new Vector<float>(axBuffer);
|
||||
var ay = new Vector<float>(ayBuffer);
|
||||
|
||||
vx += ax * delta * mask;
|
||||
vy += ay * delta * mask;
|
||||
px += vx * delta * mask;
|
||||
py += vy * delta * mask;
|
||||
|
||||
vx.CopyTo(vxBuffer);
|
||||
vy.CopyTo(vyBuffer);
|
||||
px.CopyTo(pxBuffer);
|
||||
py.CopyTo(pyBuffer);
|
||||
CopyArrayToSpan(vxBuffer, velocityX.Slice(i, lanes));
|
||||
CopyArrayToSpan(vyBuffer, velocityY.Slice(i, lanes));
|
||||
CopyArrayToSpan(pxBuffer, positionX.Slice(i, lanes));
|
||||
CopyArrayToSpan(pyBuffer, positionY.Slice(i, lanes));
|
||||
}
|
||||
|
||||
for (; i < positionX.Length; i++)
|
||||
{
|
||||
var mask = dynamicMask[i];
|
||||
velocityX[i] += accelerationX[i] * deltaTime * mask;
|
||||
velocityY[i] += accelerationY[i] * deltaTime * mask;
|
||||
positionX[i] += velocityX[i] * deltaTime * mask;
|
||||
positionY[i] += velocityY[i] * deltaTime * mask;
|
||||
}
|
||||
}
|
||||
|
||||
public static void IntegrateSemiImplicitEuler2D(
|
||||
float deltaTime,
|
||||
ReadOnlySpan<float> dynamicMask,
|
||||
Span<float2> position,
|
||||
Span<float2> velocity,
|
||||
ReadOnlySpan<float2> acceleration)
|
||||
{
|
||||
if (deltaTime <= 0.0f)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(deltaTime), "Delta time must be positive.");
|
||||
}
|
||||
|
||||
ValidateEqualLengths(dynamicMask.Length, position.Length, velocity.Length, acceleration.Length);
|
||||
|
||||
for (var i = 0; i < position.Length; i++)
|
||||
{
|
||||
var mask = dynamicMask[i];
|
||||
velocity[i] += acceleration[i] * deltaTime * mask;
|
||||
position[i] += velocity[i] * deltaTime * mask;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateEqualLengths(params int[] lengths)
|
||||
{
|
||||
var length = lengths[0];
|
||||
for (var i = 1; i < lengths.Length; i++)
|
||||
{
|
||||
if (lengths[i] != length)
|
||||
{
|
||||
throw new ArgumentException("CultMath batch spans must have equal length.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void CopySpanToArray(ReadOnlySpan<float> source, float[] destination)
|
||||
{
|
||||
for (var i = 0; i < source.Length; i++)
|
||||
{
|
||||
destination[i] = source[i];
|
||||
}
|
||||
}
|
||||
|
||||
private static void CopyArrayToSpan(float[] source, Span<float> destination)
|
||||
{
|
||||
for (var i = 0; i < destination.Length; i++)
|
||||
{
|
||||
destination[i] = source[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/BatchMath.cs.meta
Normal file
2
packages/cultmath/src/CultMath/BatchMath.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d4771320e190bdd408dd296c293a9d0e
|
||||
4
packages/cultmath/src/CultMath/Color32.cs
Normal file
4
packages/cultmath/src/CultMath/Color32.cs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
namespace CultMath;
|
||||
|
||||
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||
public readonly record struct Color32(byte r, byte g, byte b, byte a = 255);
|
||||
2
packages/cultmath/src/CultMath/Color32.cs.meta
Normal file
2
packages/cultmath/src/CultMath/Color32.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bfa14a9aacb8ac4448761568643f2bc6
|
||||
14
packages/cultmath/src/CultMath/CultMath.asmdef
Normal file
14
packages/cultmath/src/CultMath/CultMath.asmdef
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "CultMath",
|
||||
"rootNamespace": "CultMath",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": true
|
||||
}
|
||||
7
packages/cultmath/src/CultMath/CultMath.asmdef.meta
Normal file
7
packages/cultmath/src/CultMath/CultMath.asmdef.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f0b66b909d13e18468f7aa56221531e9
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
24
packages/cultmath/src/CultMath/CultMath.csproj
Normal file
24
packages/cultmath/src/CultMath/CultMath.csproj
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;netstandard2.1</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<RootNamespace>CultMath</RootNamespace>
|
||||
<AssemblyName>CultMath</AssemblyName>
|
||||
<PackageId>GameCult.Math</PackageId>
|
||||
<Version>0.1.2</Version>
|
||||
<Description>Clean-room C# math shaped for HLSL-style renderer code.</Description>
|
||||
<Authors>GameCult</Authors>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<NoWarn>$(NoWarn);CS8981</NoWarn>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
|
||||
<None Include="..\..\shaders\CultMath.hlsl" Pack="true" PackagePath="contentFiles\any\any\shaders\CultMath.hlsl" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
7
packages/cultmath/src/CultMath/CultMath.csproj.meta
Normal file
7
packages/cultmath/src/CultMath/CultMath.csproj.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 77e1a0a2512910f41885650a7d0d6f60
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
10
packages/cultmath/src/CultMath/CultMathTone.cs
Normal file
10
packages/cultmath/src/CultMath/CultMathTone.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
namespace CultMath;
|
||||
|
||||
public enum CultMathTone : byte
|
||||
{
|
||||
Background = 0,
|
||||
Panel = 1,
|
||||
Edge = 2,
|
||||
Header = 3,
|
||||
Body = 4,
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/CultMathTone.cs.meta
Normal file
2
packages/cultmath/src/CultMath/CultMathTone.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d4bfc66f9f7e992448f884c2c338d50a
|
||||
5
packages/cultmath/src/CultMath/IsExternalInit.cs
Normal file
5
packages/cultmath/src/CultMath/IsExternalInit.cs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
namespace System.Runtime.CompilerServices;
|
||||
|
||||
internal static class IsExternalInit
|
||||
{
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/IsExternalInit.cs.meta
Normal file
2
packages/cultmath/src/CultMath/IsExternalInit.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: de2b3c3e7f03c354aa2b84fbbc55b497
|
||||
62
packages/cultmath/src/CultMath/Random.cs
Normal file
62
packages/cultmath/src/CultMath/Random.cs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
namespace CultMath;
|
||||
|
||||
public struct Random
|
||||
{
|
||||
private const uint DefaultSeed = 0x6E624EB7u;
|
||||
|
||||
public uint state;
|
||||
|
||||
public Random(uint seed)
|
||||
{
|
||||
state = seed == 0 ? DefaultSeed : seed;
|
||||
}
|
||||
|
||||
public static Random CreateFromIndex(uint index) => new(index + DefaultSeed);
|
||||
|
||||
public void InitState(uint seed)
|
||||
{
|
||||
state = seed == 0 ? DefaultSeed : seed;
|
||||
}
|
||||
|
||||
public uint NextUInt()
|
||||
{
|
||||
var x = state == 0 ? DefaultSeed : state;
|
||||
x ^= x << 13;
|
||||
x ^= x >> 17;
|
||||
x ^= x << 5;
|
||||
state = x == 0 ? DefaultSeed : x;
|
||||
return state;
|
||||
}
|
||||
|
||||
public int NextInt(int max) => NextInt(0, max);
|
||||
|
||||
public int NextInt(int min, int max)
|
||||
{
|
||||
if (max <= min)
|
||||
{
|
||||
return min;
|
||||
}
|
||||
|
||||
return min + (int)(NextUInt() % (uint)(max - min));
|
||||
}
|
||||
|
||||
public float NextFloat() => (NextUInt() >> 8) * (1.0f / 16777216.0f);
|
||||
|
||||
public float NextFloat(float max) => NextFloat() * max;
|
||||
|
||||
public float NextFloat(float min, float max) => min + NextFloat() * (max - min);
|
||||
|
||||
public float2 NextFloat2() => new(NextFloat(), NextFloat());
|
||||
|
||||
public float2 NextFloat2(float min, float max) => new(NextFloat(min, max), NextFloat(min, max));
|
||||
|
||||
public float2 NextFloat2(float2 min, float2 max) => min + NextFloat2() * (max - min);
|
||||
|
||||
public float2 NextFloat2Direction()
|
||||
{
|
||||
var angle = NextFloat(0.0f, math.TAU);
|
||||
return new float2(math.cos(angle), math.sin(angle));
|
||||
}
|
||||
|
||||
public float3 NextFloat3(float3 min, float3 max) => min + new float3(NextFloat(), NextFloat(), NextFloat()) * (max - min);
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/Random.cs.meta
Normal file
2
packages/cultmath/src/CultMath/Random.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7c1a557259026da4ca20b54264852c13
|
||||
229
packages/cultmath/src/CultMath/Voronoi.cs
Normal file
229
packages/cultmath/src/CultMath/Voronoi.cs
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
public static class Voronoi
|
||||
{
|
||||
public static bool NativeAvailable { get; private set; } = true;
|
||||
|
||||
public static Color32 SampleTone(float x, float y, float resolutionY, int frameIndex, CultMathTone tone, float span)
|
||||
{
|
||||
Span<float> xs = stackalloc[] { x };
|
||||
Span<float> ys = stackalloc[] { y };
|
||||
Span<CultMathTone> tones = stackalloc[] { tone };
|
||||
Span<float> spans = stackalloc[] { span };
|
||||
Span<Color32> colors = stackalloc Color32[1];
|
||||
SampleTones(xs, ys, tones, spans, resolutionY, frameIndex, colors);
|
||||
return colors[0];
|
||||
}
|
||||
|
||||
public static void SampleTones(
|
||||
ReadOnlySpan<float> xs,
|
||||
ReadOnlySpan<float> ys,
|
||||
ReadOnlySpan<CultMathTone> tones,
|
||||
ReadOnlySpan<float> spans,
|
||||
float resolutionY,
|
||||
int frameIndex,
|
||||
Span<Color32> colors)
|
||||
{
|
||||
if (xs.Length != ys.Length || xs.Length != tones.Length || xs.Length != spans.Length || xs.Length != colors.Length)
|
||||
{
|
||||
throw new ArgumentException("CultMath Voronoi batch spans must have equal length.");
|
||||
}
|
||||
|
||||
if (xs.IsEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (NativeAvailable && TrySampleNative(xs, ys, tones, spans, resolutionY, frameIndex, colors))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var index = 0; index < xs.Length; index++)
|
||||
{
|
||||
colors[index] = SampleManaged(xs[index], ys[index], resolutionY, frameIndex, tones[index], spans[index]);
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe bool TrySampleNative(
|
||||
ReadOnlySpan<float> xs,
|
||||
ReadOnlySpan<float> ys,
|
||||
ReadOnlySpan<CultMathTone> tones,
|
||||
ReadOnlySpan<float> spans,
|
||||
float resolutionY,
|
||||
int frameIndex,
|
||||
Span<Color32> colors)
|
||||
{
|
||||
try
|
||||
{
|
||||
fixed (float* xPtr = xs)
|
||||
fixed (float* yPtr = ys)
|
||||
fixed (CultMathTone* tonePtr = tones)
|
||||
fixed (float* spanPtr = spans)
|
||||
fixed (Color32* colorPtr = colors)
|
||||
{
|
||||
var result = Native.cultmath_apollonian_voronoi_tones(
|
||||
xPtr,
|
||||
yPtr,
|
||||
(byte*)tonePtr,
|
||||
spanPtr,
|
||||
(nuint)xs.Length,
|
||||
resolutionY,
|
||||
frameIndex,
|
||||
colorPtr);
|
||||
if (result == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (DllNotFoundException)
|
||||
{
|
||||
NativeAvailable = false;
|
||||
}
|
||||
catch (EntryPointNotFoundException)
|
||||
{
|
||||
NativeAvailable = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Color32 SampleManaged(float x, float y, float resolutionY, int frameIndex, CultMathTone tone, float span)
|
||||
{
|
||||
var sample = StableSample(x, y, resolutionY, frameIndex, tone, span);
|
||||
var luminance = math.saturate(sample.x * 0.299f + sample.y * 0.587f + sample.z * 0.114f);
|
||||
if (tone == CultMathTone.Header)
|
||||
{
|
||||
var glow = 0.62f + luminance * 0.38f;
|
||||
return new Color32((byte)(255 * glow), (byte)(96 * glow), (byte)(14 * glow));
|
||||
}
|
||||
|
||||
if (tone == CultMathTone.Body)
|
||||
{
|
||||
var glow = 0.68f + luminance * 0.32f;
|
||||
return new Color32((byte)(245 * glow), (byte)(248 * glow), (byte)(252 * glow));
|
||||
}
|
||||
|
||||
var gain = tone switch
|
||||
{
|
||||
CultMathTone.Panel => 0.42f,
|
||||
CultMathTone.Edge => 1.05f,
|
||||
_ => 0.24f,
|
||||
};
|
||||
var lift = tone switch
|
||||
{
|
||||
CultMathTone.Panel => 14.0f,
|
||||
CultMathTone.Edge => 24.0f,
|
||||
_ => 4.0f,
|
||||
};
|
||||
return new Color32(
|
||||
(byte)Math.Clamp(lift + sample.x * 255.0f * gain, 0, 255),
|
||||
(byte)Math.Clamp(lift + sample.y * 255.0f * gain, 0, 255),
|
||||
(byte)Math.Clamp(lift + sample.z * 255.0f * gain, 0, 255));
|
||||
}
|
||||
|
||||
private static float3 StableSample(float x, float y, float resolutionY, int frameIndex, CultMathTone tone, float span)
|
||||
{
|
||||
var seed = x * 12.9898f + y * 78.233f + Hash1(((byte)tone + 1.0f) * 19.19f);
|
||||
var jitter = new float2((Hash1(seed) - 0.5f) * span, (Hash1(seed + 37.17f) - 0.5f) * span);
|
||||
return SampleField(x + jitter.x, y + jitter.y, resolutionY, frameIndex);
|
||||
}
|
||||
|
||||
private static float3 SampleField(float px, float py, float resolutionY, int frameIndex)
|
||||
{
|
||||
var time = frameIndex / 120.0f;
|
||||
var p = new float2(6.0f * px / Math.Max(1.0f, resolutionY), 6.0f * py / Math.Max(1.0f, resolutionY));
|
||||
var n = math.floor(p);
|
||||
var f = math.frac(p);
|
||||
var distance = 8.0f;
|
||||
var color = float3.zero;
|
||||
const float smoothness = 0.005f;
|
||||
for (var j = -2; j <= 2; j++)
|
||||
{
|
||||
for (var i = -2; i <= 2; i++)
|
||||
{
|
||||
var g = new float2(i, j);
|
||||
var o = Hash2(n + g);
|
||||
var weight = o.x * 0.5f + 0.5f;
|
||||
o = 0.5f + 0.5f * new float2(math.sin(time + 6.2831f * o.x), math.sin(time + 6.2831f * o.y));
|
||||
var d2 = math.abs(g - f + o);
|
||||
var d = Math.Max(d2.x, d2.y) * weight;
|
||||
var seed = Hash1(math.dot(n + g, new float2(7.0f, 113.0f)));
|
||||
var hue = math.frac((n.x + g.x) * 0.173f + (n.y + g.y) * 0.379f + seed * 0.431f);
|
||||
var candidate = PastelSpectrum(hue);
|
||||
var h = math.smoothstep(0.0f, 1.0f, 0.5f + 0.5f * (distance - d) / smoothness);
|
||||
var correction = h * (1.0f - h) * smoothness / (1.0f + 3.0f * smoothness);
|
||||
distance = math.lerp(distance, d, h) - correction;
|
||||
color = math.lerp(color, candidate, h) - correction;
|
||||
}
|
||||
}
|
||||
|
||||
return math.max(float3.zero, color * (1.0f - 0.1f * math.smoothstep(0.04f, 0.05f, distance)));
|
||||
}
|
||||
|
||||
private static float Hash1(float value) => math.frac(math.sin(value) * 43758.5453f);
|
||||
|
||||
private static float3 PastelSpectrum(float hue)
|
||||
{
|
||||
return HsvToRgb(hue, 0.52f, 0.98f);
|
||||
}
|
||||
|
||||
private static float3 HsvToRgb(float hue, float saturation, float value)
|
||||
{
|
||||
var h = math.frac(hue) * 6.0f;
|
||||
var c = value * saturation;
|
||||
var x = c * (1.0f - Math.Abs(h % 2.0f - 1.0f));
|
||||
var m = value - c;
|
||||
float3 rgb;
|
||||
if (h < 1.0f)
|
||||
{
|
||||
rgb = new float3(c, x, 0.0f);
|
||||
}
|
||||
else if (h < 2.0f)
|
||||
{
|
||||
rgb = new float3(x, c, 0.0f);
|
||||
}
|
||||
else if (h < 3.0f)
|
||||
{
|
||||
rgb = new float3(0.0f, c, x);
|
||||
}
|
||||
else if (h < 4.0f)
|
||||
{
|
||||
rgb = new float3(0.0f, x, c);
|
||||
}
|
||||
else if (h < 5.0f)
|
||||
{
|
||||
rgb = new float3(x, 0.0f, c);
|
||||
}
|
||||
else
|
||||
{
|
||||
rgb = new float3(c, 0.0f, x);
|
||||
}
|
||||
|
||||
return rgb + m;
|
||||
}
|
||||
|
||||
private static float2 Hash2(float2 value)
|
||||
{
|
||||
var p = new float2(math.dot(value, new float2(127.1f, 311.7f)), math.dot(value, new float2(269.5f, 183.3f)));
|
||||
return math.frac(new float2(math.sin(p.x) * 43758.5453f, math.sin(p.y) * 43758.5453f));
|
||||
}
|
||||
|
||||
private static unsafe partial class Native
|
||||
{
|
||||
[DllImport("cultmath_core", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int cultmath_apollonian_voronoi_tones(
|
||||
float* xs,
|
||||
float* ys,
|
||||
byte* tones,
|
||||
float* spans,
|
||||
nuint count,
|
||||
float resolutionY,
|
||||
int frameIndex,
|
||||
Color32* outColors);
|
||||
}
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/Voronoi.cs.meta
Normal file
2
packages/cultmath/src/CultMath/Voronoi.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cb530f1428ac8ff43939b986986ae959
|
||||
18
packages/cultmath/src/CultMath/bool2.cs
Normal file
18
packages/cultmath/src/CultMath/bool2.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
public record struct bool2(bool x, bool y)
|
||||
{
|
||||
public static readonly bool2 @false = new(false, false);
|
||||
public static readonly bool2 @true = new(true, true);
|
||||
|
||||
public bool this[int index] => index switch
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
|
||||
public static implicit operator bool2(bool value) => new(value, value);
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/bool2.cs.meta
Normal file
2
packages/cultmath/src/CultMath/bool2.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 725efb2725e987449a3782452f5a95e0
|
||||
25
packages/cultmath/src/CultMath/double2.cs
Normal file
25
packages/cultmath/src/CultMath/double2.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
public record struct double2(double x, double y)
|
||||
{
|
||||
public static readonly double2 zero = new(0.0, 0.0);
|
||||
public static readonly double2 one = new(1.0, 1.0);
|
||||
|
||||
public double this[int index] => index switch
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
|
||||
public static implicit operator double2(double value) => new(value, value);
|
||||
|
||||
public static double2 operator +(double2 value) => value;
|
||||
public static double2 operator -(double2 value) => new(-value.x, -value.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);
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/double2.cs.meta
Normal file
2
packages/cultmath/src/CultMath/double2.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bbe739a5f0216c3459762d2b5ee7401a
|
||||
36
packages/cultmath/src/CultMath/double3.cs
Normal file
36
packages/cultmath/src/CultMath/double3.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
public record struct double3(double x, double y, double z)
|
||||
{
|
||||
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 double2 xy
|
||||
{
|
||||
get => new(x, y);
|
||||
set
|
||||
{
|
||||
x = value.x;
|
||||
y = value.y;
|
||||
}
|
||||
}
|
||||
|
||||
public double this[int index] => index switch
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
2 => z,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
|
||||
public static implicit operator double3(double value) => new(value, value, value);
|
||||
|
||||
public static double3 operator +(double3 value) => value;
|
||||
public static double3 operator -(double3 value) => new(-value.x, -value.y, -value.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);
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/double3.cs.meta
Normal file
2
packages/cultmath/src/CultMath/double3.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1cbaafd219703ee43ae279aa5cbf407f
|
||||
28
packages/cultmath/src/CultMath/float2.cs
Normal file
28
packages/cultmath/src/CultMath/float2.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
public record struct float2(float x, float y)
|
||||
{
|
||||
public static readonly float2 zero = new(0.0f, 0.0f);
|
||||
public static readonly float2 one = new(1.0f, 1.0f);
|
||||
|
||||
public float this[int index] => index switch
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
|
||||
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 float2(System.Numerics.Vector2 value) => new(value.X, value.Y);
|
||||
|
||||
public static float2 operator +(float2 value) => value;
|
||||
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, 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, float2 right) => new(left.x % right.x, left.y % right.y);
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/float2.cs.meta
Normal file
2
packages/cultmath/src/CultMath/float2.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fa066a0aed5494f499d462a2ebc51e50
|
||||
79
packages/cultmath/src/CultMath/float3.cs
Normal file
79
packages/cultmath/src/CultMath/float3.cs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
public record struct float3(float x, float y, float z)
|
||||
{
|
||||
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 float3(float2 xy, float z)
|
||||
: this(xy.x, xy.y, z)
|
||||
{
|
||||
}
|
||||
|
||||
public float2 xy
|
||||
{
|
||||
get => new(x, y);
|
||||
set
|
||||
{
|
||||
x = value.x;
|
||||
y = value.y;
|
||||
}
|
||||
}
|
||||
|
||||
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 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 override readonly string ToString() => $"float3({x:R}, {y:R}, {z:R})";
|
||||
|
||||
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 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, 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, float3 right) => new(left.x % right.x, left.y % right.y, left.z % right.z);
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/float3.cs.meta
Normal file
2
packages/cultmath/src/CultMath/float3.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 58a355dd1cf6b9f48a7595eea0c0e1f2
|
||||
58
packages/cultmath/src/CultMath/float4.cs
Normal file
58
packages/cultmath/src/CultMath/float4.cs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
public record struct float4(float x, float y, float z, float w)
|
||||
{
|
||||
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 float4(float2 xy, float z, float w)
|
||||
: this(xy.x, xy.y, z, w)
|
||||
{
|
||||
}
|
||||
|
||||
public float4(float3 xyz, float w)
|
||||
: this(xyz.x, xyz.y, xyz.z, w)
|
||||
{
|
||||
}
|
||||
|
||||
public float4(float x, float3 yzw)
|
||||
: this(x, yzw.x, yzw.y, yzw.z)
|
||||
{
|
||||
}
|
||||
|
||||
public float2 xy
|
||||
{
|
||||
get => new(x, y);
|
||||
set
|
||||
{
|
||||
x = value.x;
|
||||
y = value.y;
|
||||
}
|
||||
}
|
||||
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 explicit operator System.Numerics.Vector4(float4 value) => new(value.x, value.y, value.z, value.w);
|
||||
public static explicit operator float4(System.Numerics.Vector4 value) => new(value.X, value.Y, value.Z, value.W);
|
||||
|
||||
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 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, 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, float4 right) => new(left.x % right.x, left.y % right.y, left.z % right.z, left.w % right.w);
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/float4.cs.meta
Normal file
2
packages/cultmath/src/CultMath/float4.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fe24fbdab33538849bc6be0afbb0a692
|
||||
29
packages/cultmath/src/CultMath/int2.cs
Normal file
29
packages/cultmath/src/CultMath/int2.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
public record struct int2(int x, int y)
|
||||
{
|
||||
public static readonly int2 zero = new(0, 0);
|
||||
public static readonly int2 one = new(1, 1);
|
||||
|
||||
public int this[int index] => index switch
|
||||
{
|
||||
0 => x,
|
||||
1 => y,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index)),
|
||||
};
|
||||
|
||||
public static implicit operator int2(int value) => new(value, value);
|
||||
public static implicit operator float2(int2 value) => new(value.x, value.y);
|
||||
public static explicit operator System.Numerics.Vector2(int2 value) => new(value.x, value.y);
|
||||
public static explicit operator int2(System.Numerics.Vector2 value) => new((int)value.X, (int)value.Y);
|
||||
|
||||
public static int2 operator +(int2 value) => value;
|
||||
public static int2 operator -(int2 value) => new(-value.x, -value.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);
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/int2.cs.meta
Normal file
2
packages/cultmath/src/CultMath/int2.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 577b174a998aaa4478ec26e7a6782f46
|
||||
417
packages/cultmath/src/CultMath/math.cs
Normal file
417
packages/cultmath/src/CultMath/math.cs
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
public static class math
|
||||
{
|
||||
public const float PI = MathF.PI;
|
||||
public const float TAU = MathF.PI * 2.0f;
|
||||
public const float HALF_PI = MathF.PI * 0.5f;
|
||||
|
||||
public static float2 float2(float x, float y) => new(x, y);
|
||||
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 value) => new(value, value);
|
||||
public static int2 int2(int x, int y) => new(x, y);
|
||||
public static int2 int2(int value) => new(value, value);
|
||||
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 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 float radians(float degrees) => degrees * (PI / 180.0f);
|
||||
public static float degrees(float radians) => radians * (180.0f / PI);
|
||||
public static float sin(float value) => MathF.Sin(value);
|
||||
public static float cos(float value) => MathF.Cos(value);
|
||||
public static float tan(float value) => MathF.Tan(value);
|
||||
public static float asin(float value) => MathF.Asin(value);
|
||||
public static float acos(float value) => MathF.Acos(value);
|
||||
public static float atan(float value) => MathF.Atan(value);
|
||||
public static float atan2(float y, float x) => MathF.Atan2(y, x);
|
||||
public static float sqrt(float value) => MathF.Sqrt(value);
|
||||
public static float pow(float value, float power) => MathF.Pow(value, power);
|
||||
public static bool isinf(float value) => float.IsInfinity(value);
|
||||
public static float exp(float value) => MathF.Exp(value);
|
||||
public static double exp(double value) => Math.Exp(value);
|
||||
public static float2 exp(float2 value) => new(exp(value.x), exp(value.y));
|
||||
public static float3 exp(float3 value) => new(exp(value.x), exp(value.y), exp(value.z));
|
||||
public static float4 exp(float4 value) => new(exp(value.x), exp(value.y), exp(value.z), exp(value.w));
|
||||
public static double2 exp(double2 value) => new(exp(value.x), exp(value.y));
|
||||
public static double3 exp(double3 value) => new(exp(value.x), exp(value.y), exp(value.z));
|
||||
|
||||
public static float abs(float value) => MathF.Abs(value);
|
||||
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 float4 abs(float4 value) => new(abs(value.x), abs(value.y), abs(value.z), abs(value.w));
|
||||
|
||||
public static float sign(float value) => MathF.Sign(value);
|
||||
public static float2 sign(float2 value) => new(sign(value.x), sign(value.y));
|
||||
public static float3 sign(float3 value) => new(sign(value.x), sign(value.y), sign(value.z));
|
||||
public static float4 sign(float4 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 float2 floor(float2 value) => new(floor(value.x), floor(value.y));
|
||||
public static float3 floor(float3 value) => new(floor(value.x), floor(value.y), floor(value.z));
|
||||
public static float4 floor(float4 value) => new(floor(value.x), floor(value.y), floor(value.z), floor(value.w));
|
||||
|
||||
public static float ceil(float value) => MathF.Ceiling(value);
|
||||
public static float2 ceil(float2 value) => new(ceil(value.x), ceil(value.y));
|
||||
public static float3 ceil(float3 value) => new(ceil(value.x), ceil(value.y), ceil(value.z));
|
||||
public static float4 ceil(float4 value) => new(ceil(value.x), ceil(value.y), ceil(value.z), ceil(value.w));
|
||||
|
||||
public static float frac(float value) => value - floor(value);
|
||||
public static float2 frac(float2 value) => value - floor(value);
|
||||
public static float3 frac(float3 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);
|
||||
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 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 float max(float left, float right) => MathF.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 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 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 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 float saturate(float value) => clamp(value, 0.0f, 1.0f);
|
||||
public static float2 saturate(float2 value) => clamp(value, 0.0f, 1.0f);
|
||||
public static float3 saturate(float3 value) => clamp(value, 0.0f, 1.0f);
|
||||
public static float4 saturate(float4 value) => clamp(value, 0.0f, 1.0f);
|
||||
|
||||
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 float2 lerp(float2 start, float2 end, float2 amount) => start + (end - start) * amount;
|
||||
public static float3 lerp(float3 start, float3 end, float3 amount) => start + (end - start) * amount;
|
||||
public static float4 lerp(float4 start, float4 end, float4 amount) => start + (end - start) * amount;
|
||||
public static double2 lerp(double2 start, double2 end, double2 amount) => start + (end - start) * amount;
|
||||
public static double3 lerp(double3 start, double3 end, double3 amount) => start + (end - start) * amount;
|
||||
|
||||
public static float unlerp(float start, float end, float value) => (value - start) / (end - start);
|
||||
public static double unlerp(double start, double end, double value) => (value - start) / (end - start);
|
||||
public static float2 unlerp(float2 start, float2 end, float2 value) => (value - start) / (end - start);
|
||||
public static float3 unlerp(float3 start, float3 end, float3 value) => (value - start) / (end - start);
|
||||
public static float4 unlerp(float4 start, float4 end, float4 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 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 float3 step(float3 edge, float3 value) => new(step(edge.x, value.x), step(edge.y, value.y), step(edge.z, value.z));
|
||||
public static float4 step(float4 edge, float4 value) => new(step(edge.x, value.x), step(edge.y, value.y), step(edge.z, value.z), step(edge.w, value.w));
|
||||
|
||||
public static float smoothstep(float minimum, float maximum, float value)
|
||||
{
|
||||
var t = saturate((value - minimum) / (maximum - minimum));
|
||||
return t * t * (3.0f - 2.0f * t);
|
||||
}
|
||||
|
||||
public static float2 smoothstep(float2 minimum, float2 maximum, float2 value)
|
||||
{
|
||||
var t = saturate((value - minimum) / (maximum - minimum));
|
||||
return t * t * (3.0f - 2.0f * t);
|
||||
}
|
||||
|
||||
public static float3 smoothstep(float3 minimum, float3 maximum, float3 value)
|
||||
{
|
||||
var t = saturate((value - minimum) / (maximum - minimum));
|
||||
return t * t * (3.0f - 2.0f * t);
|
||||
}
|
||||
|
||||
public static float4 smoothstep(float4 minimum, float4 maximum, float4 value)
|
||||
{
|
||||
var t = saturate((value - minimum) / (maximum - minimum));
|
||||
return t * t * (3.0f - 2.0f * t);
|
||||
}
|
||||
|
||||
public static float dot(float2 left, float2 right) => left.x * right.x + left.y * right.y;
|
||||
public static float dot(float3 left, float3 right) => left.x * right.x + left.y * right.y + left.z * right.z;
|
||||
public static float dot(float4 left, float4 right) => left.x * right.x + left.y * right.y + left.z * right.z + left.w * right.w;
|
||||
|
||||
public static float3 cross(float3 left, float3 right) =>
|
||||
new(
|
||||
left.y * right.z - left.z * right.y,
|
||||
left.z * right.x - left.x * right.z,
|
||||
left.x * right.y - left.y * right.x);
|
||||
|
||||
public static float lengthsq(float2 value) => dot(value, value);
|
||||
public static float lengthsq(float3 value) => dot(value, value);
|
||||
public static float lengthsq(float4 value) => dot(value, value);
|
||||
|
||||
public static float length(float2 value) => MathF.Sqrt(lengthsq(value));
|
||||
public static float length(float3 value) => MathF.Sqrt(lengthsq(value));
|
||||
public static float length(float4 value) => MathF.Sqrt(lengthsq(value));
|
||||
|
||||
public static float distance(float2 left, float2 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 float2 normalize(float2 value) => value / MathF.Max(length(value), 1.0e-20f);
|
||||
public static float3 normalize(float3 value) => value / MathF.Max(length(value), 1.0e-20f);
|
||||
public static float4 normalize(float4 value) => value / MathF.Max(length(value), 1.0e-20f);
|
||||
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);
|
||||
if (length <= 1.0e-20f)
|
||||
{
|
||||
return quaternion.identity;
|
||||
}
|
||||
|
||||
var invLength = 1.0f / length;
|
||||
return new quaternion(value.x * invLength, value.y * invLength, value.z * invLength, value.w * invLength);
|
||||
}
|
||||
|
||||
public static float2 reflect(float2 incident, float2 normal) => incident - 2.0f * dot(normal, incident) * normal;
|
||||
public static float3 reflect(float3 incident, float3 normal) => incident - 2.0f * dot(normal, incident) * normal;
|
||||
|
||||
public static float2 rotate(float2 value, float radians)
|
||||
{
|
||||
var s = sin(radians);
|
||||
var c = cos(radians);
|
||||
return new float2(value.x * c - value.y * s, value.x * s + value.y * c);
|
||||
}
|
||||
|
||||
public static float2 rotate_degrees(float2 value, float degrees) => rotate(value, radians(degrees));
|
||||
|
||||
public static float csum(float2 value) => value.x + value.y;
|
||||
public static float csum(float3 value) => value.x + value.y + value.z;
|
||||
public static float csum(float4 value) => value.x + value.y + value.z + value.w;
|
||||
|
||||
public static float decay(float source, float lambda, float dt) => source * exp(-lambda * dt);
|
||||
public static float2 decay(float2 source, float lambda, float dt) => source * exp(-lambda * dt);
|
||||
public static float3 decay(float3 source, float lambda, float dt) => source * exp(-lambda * dt);
|
||||
public static float4 decay(float4 source, float lambda, float dt) => source * exp(-lambda * dt);
|
||||
public static float2 decay(float2 source, float2 lambda, float dt) => source * exp(-lambda * dt);
|
||||
public static float3 decay(float3 source, float3 lambda, float dt) => source * exp(-lambda * dt);
|
||||
public static float4 decay(float4 source, float4 lambda, float dt) => source * exp(-lambda * dt);
|
||||
|
||||
public static float damp(float start, float end, float lambda, float dt) => lerp(start, end, 1.0f - exp(-lambda * dt));
|
||||
public static float2 damp(float2 start, float2 end, float lambda, float dt) => lerp(start, end, 1.0f - exp(-lambda * dt));
|
||||
public static float3 damp(float3 start, float3 end, float lambda, float dt) => lerp(start, end, 1.0f - exp(-lambda * dt));
|
||||
public static float4 damp(float4 start, float4 end, float lambda, float dt) => lerp(start, end, 1.0f - exp(-lambda * dt));
|
||||
|
||||
public static float first_order_intercept_time(float shotSpeed, float3 targetRelativePosition, float3 targetRelativeVelocity)
|
||||
{
|
||||
var velocitySquared = lengthsq(targetRelativeVelocity);
|
||||
if (velocitySquared < 0.001f)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
var a = velocitySquared - shotSpeed * shotSpeed;
|
||||
if (abs(a) < 0.001f)
|
||||
{
|
||||
var time = -lengthsq(targetRelativePosition) / (2.0f * dot(targetRelativeVelocity, targetRelativePosition));
|
||||
return max(time, 0.0f);
|
||||
}
|
||||
|
||||
var b = 2.0f * dot(targetRelativeVelocity, targetRelativePosition);
|
||||
var c = lengthsq(targetRelativePosition);
|
||||
var determinant = b * b - 4.0f * a * c;
|
||||
|
||||
if (determinant > 0.0f)
|
||||
{
|
||||
var root = sqrt(determinant);
|
||||
var t1 = (-b + root) / (2.0f * a);
|
||||
var t2 = (-b - root) / (2.0f * a);
|
||||
if (t1 > 0.0f)
|
||||
{
|
||||
return t2 > 0.0f ? min(t1, t2) : t1;
|
||||
}
|
||||
|
||||
return max(t2, 0.0f);
|
||||
}
|
||||
|
||||
if (determinant < 0.0f)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return max(-b / (2.0f * a), 0.0f);
|
||||
}
|
||||
|
||||
public static float3 first_order_intercept(
|
||||
float3 shooterPosition,
|
||||
float3 shooterVelocity,
|
||||
float shotSpeed,
|
||||
float3 targetPosition,
|
||||
float3 targetVelocity)
|
||||
{
|
||||
var targetRelativePosition = targetPosition - shooterPosition;
|
||||
var targetRelativeVelocity = targetVelocity - shooterVelocity;
|
||||
var time = first_order_intercept_time(shotSpeed, targetRelativePosition, targetRelativeVelocity);
|
||||
return targetPosition + time * targetRelativeVelocity;
|
||||
}
|
||||
|
||||
public static float distance_to_segment(float2 point, float2 start, float2 end, out float2 closest)
|
||||
{
|
||||
var segment = end - start;
|
||||
var segmentLengthSq = lengthsq(segment);
|
||||
if (segmentLengthSq <= 0.0f)
|
||||
{
|
||||
closest = start;
|
||||
return distance(point, start);
|
||||
}
|
||||
|
||||
var t = saturate(dot(point - start, segment) / segmentLengthSq);
|
||||
closest = start + segment * t;
|
||||
return distance(point, closest);
|
||||
}
|
||||
|
||||
public static float catmullrom(float p0, float p1, float p2, float p3, float t)
|
||||
{
|
||||
var t2 = t * t;
|
||||
var t3 = t2 * t;
|
||||
return 0.5f * ((2.0f * p1) + (p2 - p0) * t + (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t2 + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3);
|
||||
}
|
||||
|
||||
public static float2 catmullrom(float2 p0, float2 p1, float2 p2, float2 p3, float t) =>
|
||||
0.5f * ((2.0f * p1) + (p2 - p0) * t + (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * (t * t) + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * (t * t * t));
|
||||
|
||||
public static float3 catmullrom(float3 p0, float3 p1, float3 p2, float3 p3, float t) =>
|
||||
0.5f * ((2.0f * p1) + (p2 - p0) * t + (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * (t * t) + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * (t * t * t));
|
||||
|
||||
public static float4 catmullrom(float4 p0, float4 p1, float4 p2, float4 p3, float t) =>
|
||||
0.5f * ((2.0f * p1) + (p2 - p0) * t + (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * (t * t) + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * (t * t * t));
|
||||
|
||||
public static float quadratic_bezier(float p0, float p1, float p2, float t)
|
||||
{
|
||||
t = saturate(t);
|
||||
var inv = 1.0f - t;
|
||||
return inv * inv * p0 + 2.0f * inv * t * p1 + t * t * p2;
|
||||
}
|
||||
|
||||
public static float3 quadratic_bezier(float3 p0, float3 p1, float3 p2, float t)
|
||||
{
|
||||
t = saturate(t);
|
||||
var inv = 1.0f - t;
|
||||
return inv * inv * p0 + 2.0f * inv * t * p1 + t * t * p2;
|
||||
}
|
||||
|
||||
public static float cubic_bezier(float p0, float p1, float p2, float p3, float t)
|
||||
{
|
||||
t = saturate(t);
|
||||
var inv = 1.0f - t;
|
||||
return inv * inv * inv * p0 + 3.0f * inv * inv * t * p1 + 3.0f * inv * t * t * p2 + t * t * t * p3;
|
||||
}
|
||||
|
||||
public static float3 cubic_bezier(float3 p0, float3 p1, float3 p2, float3 p3, float t)
|
||||
{
|
||||
t = saturate(t);
|
||||
var inv = 1.0f - t;
|
||||
return inv * inv * inv * p0 + 3.0f * inv * inv * t * p1 + 3.0f * inv * t * t * p2 + t * t * t * p3;
|
||||
}
|
||||
|
||||
public static float smoothstep01(float value) => smoothstep(0.0f, 1.0f, value);
|
||||
|
||||
public static float smootherstep(float value)
|
||||
{
|
||||
value = saturate(value);
|
||||
return value * value * value * (value * (value * 6.0f - 15.0f) + 10.0f);
|
||||
}
|
||||
|
||||
public static float hash(float value) => frac(sin(value) * 43758.5453f);
|
||||
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)));
|
||||
|
||||
// Ashima Arts / Ian McEwan 2D simplex noise, kept component-explicit so
|
||||
// the C# and HLSL mirrors preserve the same float32 evaluation order.
|
||||
public static float simplex_noise(float2 value)
|
||||
{
|
||||
var c = new float4(
|
||||
0.211324865405187f,
|
||||
0.366025403784439f,
|
||||
-0.577350269189626f,
|
||||
0.024390243902439f);
|
||||
var i = floor(value + dot(value, new float2(c.y, c.y)));
|
||||
var x0 = value - i + dot(i, new float2(c.x, c.x));
|
||||
var i1 = x0.x > x0.y ? new float2(1.0f, 0.0f) : new float2(0.0f, 1.0f);
|
||||
var x12 = new float4(
|
||||
x0.x + c.x - i1.x,
|
||||
x0.y + c.x - i1.y,
|
||||
x0.x + c.z,
|
||||
x0.y + c.z);
|
||||
|
||||
i = simplex_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 m = max(0.5f - new float3(
|
||||
dot(x0, x0),
|
||||
x12.x * x12.x + x12.y * x12.y,
|
||||
x12.z * x12.z + x12.w * x12.w), 0.0f);
|
||||
m = m * m;
|
||||
m = m * m;
|
||||
|
||||
var x = 2.0f * frac(p * c.w) - 1.0f;
|
||||
var h = abs(x) - 0.5f;
|
||||
var ox = floor(x + 0.5f);
|
||||
var a0 = x - ox;
|
||||
m *= 1.79284291400159f - 0.85373472095314f * (a0 * a0 + h * h);
|
||||
|
||||
var g = new 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.0f * dot(m, g);
|
||||
}
|
||||
|
||||
private static float2 simplex_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 simplex_permute(float3 value) => simplex_mod289(((value * 34.0f) + 1.0f) * value);
|
||||
|
||||
public static float value_noise(float2 position)
|
||||
{
|
||||
var cell = floor(position);
|
||||
var local = frac(position);
|
||||
var u = local * local * (3.0f - 2.0f * local);
|
||||
|
||||
var a = hash(cell);
|
||||
var b = hash(cell + new float2(1.0f, 0.0f));
|
||||
var c = hash(cell + new float2(0.0f, 1.0f));
|
||||
var d = hash(cell + new float2(1.0f, 1.0f));
|
||||
|
||||
return lerp(lerp(a, b, u.x), lerp(c, d, u.x), u.y);
|
||||
}
|
||||
|
||||
public static float value_noise_bicubic(float2 position)
|
||||
{
|
||||
var cell = floor(position);
|
||||
var local = frac(position);
|
||||
|
||||
var y0 = catmullrom(
|
||||
hash(cell + new float2(-1.0f, -1.0f)),
|
||||
hash(cell + new float2(0.0f, -1.0f)),
|
||||
hash(cell + new float2(1.0f, -1.0f)),
|
||||
hash(cell + new float2(2.0f, -1.0f)),
|
||||
local.x);
|
||||
var y1 = catmullrom(
|
||||
hash(cell + new float2(-1.0f, 0.0f)),
|
||||
hash(cell + new float2(0.0f, 0.0f)),
|
||||
hash(cell + new float2(1.0f, 0.0f)),
|
||||
hash(cell + new float2(2.0f, 0.0f)),
|
||||
local.x);
|
||||
var y2 = catmullrom(
|
||||
hash(cell + new float2(-1.0f, 1.0f)),
|
||||
hash(cell + new float2(0.0f, 1.0f)),
|
||||
hash(cell + new float2(1.0f, 1.0f)),
|
||||
hash(cell + new float2(2.0f, 1.0f)),
|
||||
local.x);
|
||||
var y3 = catmullrom(
|
||||
hash(cell + new float2(-1.0f, 2.0f)),
|
||||
hash(cell + new float2(0.0f, 2.0f)),
|
||||
hash(cell + new float2(1.0f, 2.0f)),
|
||||
hash(cell + new float2(2.0f, 2.0f)),
|
||||
local.x);
|
||||
|
||||
return catmullrom(y0, y1, y2, y3, local.y);
|
||||
}
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/math.cs.meta
Normal file
2
packages/cultmath/src/CultMath/math.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0d9b4cacc40d169429ee743d19577cb7
|
||||
68
packages/cultmath/src/CultMath/quaternion.cs
Normal file
68
packages/cultmath/src/CultMath/quaternion.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
namespace CultMath;
|
||||
|
||||
public record struct quaternion(float x, float y, float z, float w)
|
||||
{
|
||||
public static readonly quaternion identity = new(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
||||
public static quaternion LookRotation(float3 forward, float3 up)
|
||||
{
|
||||
forward = math.normalize(forward);
|
||||
up = math.normalize(up);
|
||||
|
||||
var right = math.normalize(math.cross(up, forward));
|
||||
up = math.cross(forward, right);
|
||||
|
||||
var m00 = right.x;
|
||||
var m01 = up.x;
|
||||
var m02 = forward.x;
|
||||
var m10 = right.y;
|
||||
var m11 = up.y;
|
||||
var m12 = forward.y;
|
||||
var m20 = right.z;
|
||||
var m21 = up.z;
|
||||
var m22 = forward.z;
|
||||
|
||||
var trace = m00 + m11 + m22;
|
||||
if (trace > 0.0f)
|
||||
{
|
||||
var s = math.sqrt(trace + 1.0f) * 2.0f;
|
||||
return math.normalize(new quaternion(
|
||||
(m21 - m12) / s,
|
||||
(m02 - m20) / s,
|
||||
(m10 - m01) / s,
|
||||
0.25f * s));
|
||||
}
|
||||
|
||||
if (m00 > m11 && m00 > m22)
|
||||
{
|
||||
var s = math.sqrt(1.0f + m00 - m11 - m22) * 2.0f;
|
||||
return math.normalize(new quaternion(
|
||||
0.25f * s,
|
||||
(m01 + m10) / s,
|
||||
(m02 + m20) / s,
|
||||
(m21 - m12) / s));
|
||||
}
|
||||
|
||||
if (m11 > m22)
|
||||
{
|
||||
var s = math.sqrt(1.0f + m11 - m00 - m22) * 2.0f;
|
||||
return math.normalize(new quaternion(
|
||||
(m01 + m10) / s,
|
||||
0.25f * s,
|
||||
(m12 + m21) / s,
|
||||
(m02 - m20) / s));
|
||||
}
|
||||
|
||||
{
|
||||
var s = math.sqrt(1.0f + m22 - m00 - m11) * 2.0f;
|
||||
return math.normalize(new quaternion(
|
||||
(m02 + m20) / s,
|
||||
(m12 + m21) / s,
|
||||
0.25f * s,
|
||||
(m10 - m01) / s));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/quaternion.cs.meta
Normal file
2
packages/cultmath/src/CultMath/quaternion.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 12f3627d9250dc348be9c1a1056ec7d0
|
||||
59
packages/cultmath/src/CultMath/rect.cs
Normal file
59
packages/cultmath/src/CultMath/rect.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
namespace CultMath;
|
||||
|
||||
/// <summary>
|
||||
/// Axis-aligned 2D bounds stored canonically as normalized minimum and maximum
|
||||
/// corners. Point containment and intersection include the boundary.
|
||||
/// </summary>
|
||||
public readonly record struct rect
|
||||
{
|
||||
public rect(float minX, float minY, float maxX, float maxY)
|
||||
: this(new float2(minX, minY), new float2(maxX, maxY))
|
||||
{
|
||||
}
|
||||
|
||||
public rect(float2 min, float2 max)
|
||||
{
|
||||
this.min = new float2(MathF.Min(min.x, max.x), MathF.Min(min.y, max.y));
|
||||
this.max = new float2(MathF.Max(min.x, max.x), MathF.Max(min.y, max.y));
|
||||
}
|
||||
|
||||
public float2 min { get; }
|
||||
public float2 max { get; }
|
||||
|
||||
public float width => max.x - min.x;
|
||||
public float height => max.y - min.y;
|
||||
public float2 size => max - min;
|
||||
public float2 center => (min + max) * 0.5f;
|
||||
public float area => width * height;
|
||||
public bool isEmpty => width == 0.0f || height == 0.0f;
|
||||
|
||||
public bool Contains(float2 point) =>
|
||||
point.x >= min.x && point.x <= max.x &&
|
||||
point.y >= min.y && point.y <= max.y;
|
||||
|
||||
public bool Contains(rect other) =>
|
||||
other.min.x >= min.x && other.max.x <= max.x &&
|
||||
other.min.y >= min.y && other.max.y <= max.y;
|
||||
|
||||
public bool Intersects(rect other) =>
|
||||
min.x <= other.max.x && max.x >= other.min.x &&
|
||||
min.y <= other.max.y && max.y >= other.min.y;
|
||||
|
||||
public rect Intersection(rect other)
|
||||
{
|
||||
if (!Intersects(other))
|
||||
return new rect(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
return new rect(
|
||||
MathF.Max(min.x, other.min.x),
|
||||
MathF.Max(min.y, other.min.y),
|
||||
MathF.Min(max.x, other.max.x),
|
||||
MathF.Min(max.y, other.max.y));
|
||||
}
|
||||
|
||||
public rect Encapsulate(float2 point) => new(
|
||||
MathF.Min(min.x, point.x),
|
||||
MathF.Min(min.y, point.y),
|
||||
MathF.Max(max.x, point.x),
|
||||
MathF.Max(max.y, point.y));
|
||||
}
|
||||
2
packages/cultmath/src/CultMath/rect.cs.meta
Normal file
2
packages/cultmath/src/CultMath/rect.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5aab0d8e1ca94d42b85bba780a7ee449
|
||||
8
packages/cultmath/tests.meta
Normal file
8
packages/cultmath/tests.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4ca61dedbfb3c5d46b0acaf23202b5c2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
packages/cultmath/tests/CultMath.Tests.meta
Normal file
8
packages/cultmath/tests/CultMath.Tests.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bcee65f13f85daa45b0cf3fdaa901779
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
20
packages/cultmath/tests/CultMath.Tests/CultMath.Tests.csproj
Normal file
20
packages/cultmath/tests/CultMath.Tests/CultMath.Tests.csproj
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit.v3" Version="3.2.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\CultMath\CultMath.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 636051db121e19146a0baf92b790415c
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
76
packages/cultmath/tests/CultMath.Tests/HlslMirrorTests.cs
Normal file
76
packages/cultmath/tests/CultMath.Tests/HlslMirrorTests.cs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
using Xunit;
|
||||
|
||||
namespace CultMath.Tests;
|
||||
|
||||
public sealed class HlslMirrorTests
|
||||
{
|
||||
[Fact]
|
||||
public void HlslMirrorDoesNotPublishGeometryKernels()
|
||||
{
|
||||
var source = File.ReadAllText(Path.Combine(GetCultMathRoot(), "shaders", "CultMath.hlsl"));
|
||||
Assert.DoesNotContain("AdvancedErosionFilter.hlsl", source);
|
||||
Assert.DoesNotContain("Planetary.hlsl", source);
|
||||
Assert.DoesNotContain("spherical_erosion", source);
|
||||
Assert.DoesNotContain("planetary_radial_refinement", source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HlslMirrorPublishesCultMathPrimitives()
|
||||
{
|
||||
var include = File.ReadAllText(Path.Combine(GetCultMathRoot(), "shaders", "CultMath.hlsl"));
|
||||
var requiredSymbols = new[]
|
||||
{
|
||||
"cultmath_radians",
|
||||
"cultmath_degrees",
|
||||
"cultmath_frac",
|
||||
"cultmath_clamp",
|
||||
"cultmath_saturate",
|
||||
"cultmath_lerp",
|
||||
"cultmath_step",
|
||||
"cultmath_smoothstep",
|
||||
"cultmath_smootherstep",
|
||||
"cultmath_lengthsq",
|
||||
"cultmath_distance",
|
||||
"cultmath_normalize",
|
||||
"cultmath_reflect",
|
||||
"cultmath_rotate",
|
||||
"cultmath_csum",
|
||||
"cultmath_decay",
|
||||
"cultmath_damp",
|
||||
"cultmath_catmullrom",
|
||||
"cultmath_quadratic_bezier",
|
||||
"cultmath_cubic_bezier",
|
||||
"cultmath_hash",
|
||||
"cultmath_value_noise",
|
||||
"cultmath_simplex_noise",
|
||||
"cultmath_value_noise_bicubic",
|
||||
"cultmath_value_noise_texture",
|
||||
"cultmath_value_noise_texture_bicubic",
|
||||
};
|
||||
|
||||
foreach (var symbol in requiredSymbols)
|
||||
{
|
||||
Assert.Contains(symbol, include);
|
||||
}
|
||||
|
||||
Assert.Contains("CULTMATH_NORMALIZE_EPSILON", include);
|
||||
Assert.Contains("max(length(value), CULTMATH_NORMALIZE_EPSILON)", include);
|
||||
}
|
||||
|
||||
private static string GetCultMathRoot()
|
||||
{
|
||||
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (directory is not null)
|
||||
{
|
||||
var monorepoPackage = Path.Combine(directory.FullName, "packages", "cultmath");
|
||||
if (File.Exists(Path.Combine(monorepoPackage, "shaders", "CultMath.hlsl")))
|
||||
{
|
||||
return monorepoPackage;
|
||||
}
|
||||
|
||||
directory = directory.Parent;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Could not locate the CultMath package root.");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3d7eb9df849ea0148a71419bbfe2352c
|
||||
335
packages/cultmath/tests/CultMath.Tests/MathTests.cs
Normal file
335
packages/cultmath/tests/CultMath.Tests/MathTests.cs
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
using CultMath;
|
||||
using Xunit;
|
||||
|
||||
namespace CultMath.Tests;
|
||||
|
||||
public sealed class MathTests
|
||||
{
|
||||
[Fact]
|
||||
public void FracMatchesShaderStylePositiveAndNegativeValues()
|
||||
{
|
||||
Assert.Equal(0.25f, math.frac(1.25f));
|
||||
Assert.Equal(0.75f, math.frac(-1.25f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VectorOperationsAreComponentWise()
|
||||
{
|
||||
var value = new float3(1.0f, 2.0f, 3.0f) * new float3(4.0f, 5.0f, 6.0f);
|
||||
|
||||
Assert.Equal(new float3(4.0f, 10.0f, 18.0f), value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VectorConstructorsAcceptLowerDimensionVectors()
|
||||
{
|
||||
Assert.Equal(new int2(1, 2), math.int2(1, 2));
|
||||
Assert.Equal(new float2(1.0f, 2.0f), math.float2(1.0f, 2.0f));
|
||||
Assert.Equal(new float3(1.0f, 1.0f, 1.0f), math.float3(1.0f));
|
||||
Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), math.float4(new float3(1.0f, 2.0f, 3.0f), 4.0f));
|
||||
Assert.Equal(new float3(1.0f, 2.0f, 3.0f), new float3(new float2(1.0f, 2.0f), 3.0f));
|
||||
Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), new float4(new float2(1.0f, 2.0f), 3.0f, 4.0f));
|
||||
Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), new float4(new float3(1.0f, 2.0f, 3.0f), 4.0f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VectorsExposeCommonReadOnlySwizzles()
|
||||
{
|
||||
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 float2(3.0f, 2.0f), new float3(1.0f, 2.0f, 3.0f).zy);
|
||||
Assert.Equal(new float3(1.0f, 2.0f, 4.0f), new float4(1.0f, 2.0f, 3.0f, 4.0f).xyw);
|
||||
Assert.Equal(new float3(2.0f, 3.0f, 1.0f), new float4(1.0f, 2.0f, 3.0f, 4.0f).yzx);
|
||||
Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), new float4(1.0f, new float3(2.0f, 3.0f, 4.0f)));
|
||||
Assert.Equal(new float4(1.0f, 2.0f, 3.0f, 4.0f), math.float4(1.0f, new float3(2.0f, 3.0f, 4.0f)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VectorsSupportUnityStyleFieldAndSwizzleMutation()
|
||||
{
|
||||
var position = new float3(1.0f, 2.0f, 3.0f);
|
||||
position.y = 5.0f;
|
||||
position.xz = new float2(8.0f, 13.0f);
|
||||
|
||||
Assert.Equal(new float3(8.0f, 5.0f, 13.0f), position);
|
||||
|
||||
var bounds = new float4(1.0f, 2.0f, 3.0f, 4.0f);
|
||||
bounds.xy = new float2(-1.0f, -2.0f);
|
||||
|
||||
Assert.Equal(new float4(-1.0f, -2.0f, 3.0f, 4.0f), bounds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoubleVectorsSupportDampedMath()
|
||||
{
|
||||
var value = new double3(1.0, 2.0, 3.0);
|
||||
value.xy = new double2(5.0, 8.0);
|
||||
|
||||
Assert.Equal(new double3(5.0, 8.0, 3.0), value);
|
||||
Assert.Equal(new double2(Math.Exp(-2.0), Math.Exp(-4.0)), math.exp(new double2(-2.0, -4.0)));
|
||||
Assert.Equal(new double3(2.0, 4.0, 6.0), math.lerp(new double3(0.0, 0.0, 0.0), new double3(4.0, 8.0, 12.0), new double3(0.5, 0.5, 0.5)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Int2SupportsGridArithmeticAndFloatPromotion()
|
||||
{
|
||||
var cell = new int2(2, 3);
|
||||
|
||||
Assert.Equal(new int2(3, 5), cell + new int2(1, 2));
|
||||
Assert.Equal(new int2(4, 6), cell * new int2(2, 2));
|
||||
Assert.Equal(3, cell[1]);
|
||||
|
||||
float2 promoted = cell;
|
||||
Assert.Equal(new float2(2.0f, 3.0f), promoted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Bool2SupportsHullGridStyleStorage()
|
||||
{
|
||||
bool2 conductivity = true;
|
||||
|
||||
conductivity.y = false;
|
||||
|
||||
Assert.True(conductivity.x);
|
||||
Assert.False(conductivity[1]);
|
||||
Assert.Equal(new bool2(false, true), math.bool2(false, true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QuaternionLookRotationProducesUnitOrientation()
|
||||
{
|
||||
var rotation = quaternion.LookRotation(new float3(0.0f, 0.0f, 1.0f), new float3(0.0f, 1.0f, 0.0f));
|
||||
|
||||
Assert.Equal(quaternion.identity, rotation);
|
||||
Assert.Equal(1.0f, math.length((float4)quaternion.LookRotation(new float3(1.0f, 0.0f, 0.0f), new float3(0.0f, 1.0f, 0.0f))), precision: 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RandomUsesStableXorShift32Sequence()
|
||||
{
|
||||
var a = new Random(1234);
|
||||
var b = new Random(1234);
|
||||
|
||||
Assert.Equal(332584831u, a.NextUInt());
|
||||
Assert.Equal(1855942593u, a.NextUInt());
|
||||
Assert.Equal(4018585650u, a.NextUInt());
|
||||
Assert.Equal(3348358578u, a.NextUInt());
|
||||
Assert.Equal(332584831u, b.NextUInt());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RandomProvidesDeterministicPortableSampling()
|
||||
{
|
||||
var a = new Random(1234);
|
||||
var b = new Random(1234);
|
||||
|
||||
Assert.Equal(a.NextUInt(), b.NextUInt());
|
||||
Assert.Equal(a.NextFloat(), b.NextFloat());
|
||||
|
||||
var bounded = a.NextFloat(-2.0f, 4.0f);
|
||||
Assert.True(bounded >= -2.0f && bounded < 4.0f);
|
||||
|
||||
var direction = a.NextFloat2Direction();
|
||||
Assert.Equal(1.0f, math.length(direction), precision: 5);
|
||||
|
||||
var point = a.NextFloat3(new float3(-1.0f, -2.0f, -3.0f), new float3(1.0f, 2.0f, 3.0f));
|
||||
Assert.True(point.x >= -1.0f && point.x < 1.0f);
|
||||
Assert.True(point.y >= -2.0f && point.y < 2.0f);
|
||||
Assert.True(point.z >= -3.0f && point.z < 3.0f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NormalizeProducesUnitLength()
|
||||
{
|
||||
var value = math.normalize(new float3(3.0f, 4.0f, 0.0f));
|
||||
|
||||
Assert.Equal(1.0f, math.length(value), precision: 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SmoothstepUsesHermiteRamp()
|
||||
{
|
||||
Assert.Equal(0.0f, math.smoothstep(0.0f, 1.0f, -1.0f));
|
||||
Assert.Equal(0.5f, math.smoothstep(0.0f, 1.0f, 0.5f), precision: 5);
|
||||
Assert.Equal(1.0f, math.smoothstep(0.0f, 1.0f, 2.0f));
|
||||
Assert.Equal(0.5f, math.smootherstep(0.5f), precision: 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TrigIntrinsicsUseRadians()
|
||||
{
|
||||
Assert.Equal(1.0f, math.sin(math.HALF_PI), precision: 5);
|
||||
Assert.Equal(-1.0f, math.cos(math.PI), precision: 5);
|
||||
Assert.Equal(1.0f, math.tan(math.PI * 0.25f), precision: 5);
|
||||
Assert.Equal(math.HALF_PI, math.atan2(1.0f, 0.0f), precision: 5);
|
||||
Assert.Equal(2, math.min(2, 5));
|
||||
Assert.Equal(5, math.max(2, 5));
|
||||
Assert.True(math.isinf(float.PositiveInfinity));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SignIsComponentWiseForSimulationVectors()
|
||||
{
|
||||
Assert.Equal(-1.0f, math.sign(-12.0f));
|
||||
Assert.Equal(0.0f, math.sign(0.0f));
|
||||
Assert.Equal(1.0f, math.sign(12.0f));
|
||||
Assert.Equal(new float3(-1.0f, 0.0f, 1.0f), math.sign(new float3(-2.0f, 0.0f, 4.0f)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RotateTurnsFloat2CounterClockwiseInRadians()
|
||||
{
|
||||
var rotated = math.rotate(new float2(1.0f, 0.0f), math.HALF_PI);
|
||||
|
||||
Assert.Equal(0.0f, rotated.x, precision: 5);
|
||||
Assert.Equal(1.0f, rotated.y, precision: 5);
|
||||
|
||||
var rotatedDegrees = math.rotate_degrees(new float2(0.0f, 1.0f), 90.0f);
|
||||
Assert.Equal(-1.0f, rotatedDegrees.x, precision: 5);
|
||||
Assert.Equal(0.0f, rotatedDegrees.y, precision: 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AetheriaPrimitivesRemainSmallAndDeterministic()
|
||||
{
|
||||
Assert.Equal(6.0f, math.csum(new float3(1.0f, 2.0f, 3.0f)));
|
||||
Assert.Equal(0.25f, math.unlerp(10.0f, 30.0f, 15.0f));
|
||||
Assert.Equal(new float2(0.25f, 0.75f), math.unlerp(new float2(10.0f, 10.0f), new float2(30.0f, 30.0f), new float2(15.0f, 25.0f)));
|
||||
Assert.Equal(MathF.Exp(-2.0f), math.decay(1.0f, 2.0f, 1.0f), precision: 5);
|
||||
Assert.Equal(1.0f - MathF.Exp(-2.0f), math.damp(0.0f, 1.0f, 2.0f, 1.0f), precision: 5);
|
||||
|
||||
Assert.Equal(15.0f, math.catmullrom(0.0f, 10.0f, 20.0f, 30.0f, 0.5f), precision: 5);
|
||||
Assert.Equal(0.25f, math.quadratic_bezier(0.0f, 0.0f, 1.0f, 0.5f), precision: 5);
|
||||
Assert.Equal(0.5f, math.cubic_bezier(0.0f, 0.0f, 1.0f, 1.0f, 0.5f), precision: 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SegmentDistanceReportsClosestPoint()
|
||||
{
|
||||
var distance = math.distance_to_segment(new float2(2.0f, 3.0f), new float2(0.0f, 0.0f), new float2(4.0f, 0.0f), out var closest);
|
||||
|
||||
Assert.Equal(3.0f, distance, precision: 5);
|
||||
Assert.Equal(new float2(2.0f, 0.0f), closest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RectNormalizesAndDerivesFromMinMax()
|
||||
{
|
||||
var bounds = new rect(8.0f, 6.0f, 2.0f, -2.0f);
|
||||
|
||||
Assert.Equal(new float2(2.0f, -2.0f), bounds.min);
|
||||
Assert.Equal(new float2(8.0f, 6.0f), bounds.max);
|
||||
Assert.Equal(new float2(6.0f, 8.0f), bounds.size);
|
||||
Assert.Equal(new float2(5.0f, 2.0f), bounds.center);
|
||||
Assert.Equal(48.0f, bounds.area);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RectContainmentIncludesBoundary()
|
||||
{
|
||||
var bounds = new rect(-2.0f, -1.0f, 4.0f, 3.0f);
|
||||
|
||||
Assert.True(bounds.Contains(new float2(-2.0f, -1.0f)));
|
||||
Assert.True(bounds.Contains(new float2(4.0f, 3.0f)));
|
||||
Assert.True(bounds.Contains(new rect(-1.0f, 0.0f, 1.0f, 2.0f)));
|
||||
Assert.False(bounds.Contains(new float2(4.001f, 3.0f)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RectIntersectionTreatsTouchingEdgesAsContact()
|
||||
{
|
||||
var left = new rect(0.0f, 0.0f, 4.0f, 4.0f);
|
||||
var touching = new rect(4.0f, 1.0f, 8.0f, 3.0f);
|
||||
var separate = new rect(4.001f, 1.0f, 8.0f, 3.0f);
|
||||
|
||||
Assert.True(left.Intersects(touching));
|
||||
Assert.Equal(0.0f, left.Intersection(touching).area);
|
||||
Assert.False(left.Intersects(separate));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FirstOrderInterceptSolvesSimpleClosingTarget()
|
||||
{
|
||||
var time = math.first_order_intercept_time(
|
||||
2.0f,
|
||||
new float3(10.0f, 0.0f, 0.0f),
|
||||
new float3(-1.0f, 0.0f, 0.0f));
|
||||
|
||||
Assert.Equal(10.0f / 3.0f, time, precision: 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValueNoiseIsDeterministicAndSmoothAtCellCorners()
|
||||
{
|
||||
var position = new float2(12.25f, -4.5f);
|
||||
|
||||
Assert.Equal(math.value_noise(position), math.value_noise(position));
|
||||
Assert.Equal(math.hash(new float2(3.0f, 5.0f)), math.value_noise(new float2(3.0f, 5.0f)), precision: 5);
|
||||
|
||||
var bicubic = math.value_noise_bicubic(position);
|
||||
Assert.True(bicubic > -0.25f && bicubic < 1.25f);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0.0f, 0.0f, 0.0f)]
|
||||
[InlineData(0.25f, -0.5f, -0.425866783f)]
|
||||
[InlineData(12.25f, -4.5f, 0.258312f)]
|
||||
[InlineData(536.5106f, 536.5106f, -0.6901103f)]
|
||||
public void SimplexNoiseMatchesUnityMathematics(float x, float y, float expected)
|
||||
{
|
||||
Assert.Equal(expected, math.simplex_noise(new float2(x, y)), precision: 6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VoronoiBatchProducesToneMappedColors()
|
||||
{
|
||||
var xs = new[] { 0.0f, 24.0f, 96.0f };
|
||||
var ys = new[] { 0.0f, 36.0f, 144.0f };
|
||||
var tones = new[] { CultMathTone.Background, CultMathTone.Header, CultMathTone.Body };
|
||||
var spans = new[] { 1920.0f, 8.0f, 8.0f };
|
||||
var colors = new Color32[xs.Length];
|
||||
|
||||
Voronoi.SampleTones(xs, ys, tones, spans, 1080.0f, 12, colors);
|
||||
|
||||
Assert.NotEqual(new Color32(), colors[0]);
|
||||
Assert.True(colors[1].r > colors[1].g);
|
||||
Assert.True(colors[2].r > 120 && colors[2].g > 120 && colors[2].b > 120);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchRadialFalloffAccelerationMatchesScalarContract()
|
||||
{
|
||||
var xs = new[] { 0.0f, 5.0f, 10.0f };
|
||||
var ys = new[] { 0.0f, 0.0f, 0.0f };
|
||||
var ax = new float[xs.Length];
|
||||
var ay = new float[xs.Length];
|
||||
|
||||
BatchMath.AddRadialFalloffAcceleration2D(xs, ys, 10.0f, 0.0f, 10.0f, 20.0f, ax, ay);
|
||||
|
||||
Assert.True(BatchMath.LaneCount >= 4);
|
||||
Assert.Equal(5.0f, ax[0], precision: 5);
|
||||
Assert.Equal(7.5f, ax[1], precision: 5);
|
||||
Assert.Equal(0.0f, ax[2], precision: 5);
|
||||
Assert.Equal(0.0f, ay[0], precision: 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchEulerIntegrationHonorsDynamicMask()
|
||||
{
|
||||
var dynamicMask = new[] { 1.0f, 0.0f };
|
||||
var px = new[] { 0.0f, 0.0f };
|
||||
var py = new[] { 0.0f, 0.0f };
|
||||
var vx = new[] { 1.0f, 1.0f };
|
||||
var vy = new[] { 0.0f, 0.0f };
|
||||
var ax = new[] { 2.0f, 2.0f };
|
||||
var ay = new[] { 0.0f, 4.0f };
|
||||
|
||||
BatchMath.IntegrateSemiImplicitEuler2D(0.5f, dynamicMask, px, py, vx, vy, ax, ay);
|
||||
|
||||
Assert.Equal(2.0f, vx[0], precision: 5);
|
||||
Assert.Equal(1.0f, px[0], precision: 5);
|
||||
Assert.Equal(1.0f, vx[1], precision: 5);
|
||||
Assert.Equal(0.0f, px[1], precision: 5);
|
||||
Assert.Equal(0.0f, py[1], precision: 5);
|
||||
}
|
||||
}
|
||||
2
packages/cultmath/tests/CultMath.Tests/MathTests.cs.meta
Normal file
2
packages/cultmath/tests/CultMath.Tests/MathTests.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c7a709e5345da214aa650e8c796fcaff
|
||||
8
packages/cultmath/tools.meta
Normal file
8
packages/cultmath/tools.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 697162910ffd9614ba9655d31af9afa4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
69
packages/cultmath/tools/compile-hlsl-spirv.ps1
Normal file
69
packages/cultmath/tools/compile-hlsl-spirv.ps1
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ShaderPath,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$OutputPath,
|
||||
|
||||
[string]$EntryPoint = "main",
|
||||
[string]$Profile = "cs_6_0",
|
||||
[string[]]$IncludePath = @(),
|
||||
[string]$TargetEnv = "",
|
||||
[int]$VulkanCBufferShift = 0,
|
||||
[int]$VulkanTextureShift = 10,
|
||||
[int]$VulkanUavShift = 20,
|
||||
[int]$VulkanSamplerShift = 30,
|
||||
[string]$DxcPath = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||
if (-not $DxcPath) {
|
||||
$DxcPath = Join-Path $repoRoot ".tools\dxc\current\bin\x64\dxc.exe"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $DxcPath)) {
|
||||
throw "DXC not found at '$DxcPath'. Run tools\get-dxc.ps1 first or pass -DxcPath."
|
||||
}
|
||||
|
||||
$resolvedShader = Resolve-Path $ShaderPath
|
||||
$resolvedIncludes = @((Join-Path $repoRoot "shaders"))
|
||||
foreach ($path in $IncludePath) {
|
||||
$resolvedIncludes += (Resolve-Path $path)
|
||||
}
|
||||
|
||||
$outputDirectory = Split-Path -Parent $OutputPath
|
||||
if ($outputDirectory) {
|
||||
New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null
|
||||
}
|
||||
|
||||
$arguments = @(
|
||||
"-T", $Profile,
|
||||
"-E", $EntryPoint,
|
||||
"-spirv"
|
||||
)
|
||||
|
||||
if ($TargetEnv) {
|
||||
$arguments += "-fspv-target-env=$TargetEnv"
|
||||
}
|
||||
|
||||
$arguments += @(
|
||||
"-fvk-b-shift", $VulkanCBufferShift.ToString(), "0",
|
||||
"-fvk-t-shift", $VulkanTextureShift.ToString(), "0",
|
||||
"-fvk-u-shift", $VulkanUavShift.ToString(), "0",
|
||||
"-fvk-s-shift", $VulkanSamplerShift.ToString(), "0"
|
||||
)
|
||||
|
||||
foreach ($include in $resolvedIncludes) {
|
||||
$arguments += @("-I", $include)
|
||||
}
|
||||
|
||||
$arguments += @($resolvedShader, "-Fo", $OutputPath)
|
||||
|
||||
& $DxcPath @arguments
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "dxc.exe failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
|
||||
Get-Item -LiteralPath $OutputPath
|
||||
7
packages/cultmath/tools/compile-hlsl-spirv.ps1.meta
Normal file
7
packages/cultmath/tools/compile-hlsl-spirv.ps1.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b47e7016a99a5d148bc44c399c13c4d3
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
34
packages/cultmath/tools/compile-spirv-metal.ps1
Normal file
34
packages/cultmath/tools/compile-spirv-metal.ps1
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$SpirvPath,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$OutputPath,
|
||||
|
||||
[int]$MslVersion = 23000,
|
||||
[string]$SpirvCrossPath = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||
if (-not $SpirvCrossPath) {
|
||||
$SpirvCrossPath = Join-Path $repoRoot ".tools\spirv-cross\build\spirv-cross.exe"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $SpirvCrossPath)) {
|
||||
throw "SPIRV-Cross not found at '$SpirvCrossPath'. Run tools\get-spirv-cross.ps1 first or pass -SpirvCrossPath."
|
||||
}
|
||||
|
||||
$resolvedSpirv = Resolve-Path $SpirvPath
|
||||
$outputDirectory = Split-Path -Parent $OutputPath
|
||||
if ($outputDirectory) {
|
||||
New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null
|
||||
}
|
||||
|
||||
& $SpirvCrossPath $resolvedSpirv --msl --msl-version $MslVersion --output $OutputPath
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "spirv-cross failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
|
||||
Get-Item -LiteralPath $OutputPath
|
||||
7
packages/cultmath/tools/compile-spirv-metal.ps1.meta
Normal file
7
packages/cultmath/tools/compile-spirv-metal.ps1.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6150c8f461e471b4c8641fd32d1984ef
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
42
packages/cultmath/tools/get-dxc.ps1
Normal file
42
packages/cultmath/tools/get-dxc.ps1
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
param(
|
||||
[string]$InstallRoot = ".tools\dxc"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||
$installPath = Join-Path $repoRoot $InstallRoot
|
||||
New-Item -ItemType Directory -Force -Path $installPath | Out-Null
|
||||
|
||||
$release = Invoke-RestMethod `
|
||||
-Uri "https://api.github.com/repos/microsoft/DirectXShaderCompiler/releases/latest" `
|
||||
-Headers @{ "User-Agent" = "CultMath-DXC-Bootstrap" }
|
||||
|
||||
$asset = $release.assets |
|
||||
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
|
||||
|
||||
& curl.exe -L --retry 5 --retry-delay 2 --fail -o $zipPath $asset.browser_download_url
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "curl.exe failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
|
||||
$currentPath = Join-Path $installPath "current"
|
||||
Remove-Item -LiteralPath $currentPath -Recurse -Force -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Force -Path $currentPath | Out-Null
|
||||
Expand-Archive -LiteralPath $zipPath -DestinationPath $currentPath -Force
|
||||
|
||||
$dxcPath = Join-Path $currentPath "bin\x64\dxc.exe"
|
||||
if (-not (Test-Path $dxcPath)) {
|
||||
throw "DXC x64 executable was not found after extraction."
|
||||
}
|
||||
|
||||
& $dxcPath --version
|
||||
Write-Host "DXC installed at $dxcPath"
|
||||
7
packages/cultmath/tools/get-dxc.ps1.meta
Normal file
7
packages/cultmath/tools/get-dxc.ps1.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 86d0b9a1006042745b7fff523831049c
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
34
packages/cultmath/tools/get-spirv-cross.ps1
Normal file
34
packages/cultmath/tools/get-spirv-cross.ps1
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
param(
|
||||
[string]$InstallRoot = ".tools\spirv-cross"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||
$installPath = Join-Path $repoRoot $InstallRoot
|
||||
$sourcePath = Join-Path $installPath "src"
|
||||
$buildPath = Join-Path $installPath "build"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $installPath | Out-Null
|
||||
|
||||
if (-not (Test-Path $sourcePath)) {
|
||||
git clone --depth 1 https://github.com/KhronosGroup/SPIRV-Cross.git $sourcePath
|
||||
}
|
||||
|
||||
cmake `
|
||||
-S $sourcePath `
|
||||
-B $buildPath `
|
||||
-G Ninja `
|
||||
-DSPIRV_CROSS_CLI=ON `
|
||||
-DSPIRV_CROSS_ENABLE_TESTS=OFF `
|
||||
-DSPIRV_CROSS_ENABLE_C_API=OFF
|
||||
|
||||
cmake --build $buildPath --target spirv-cross --config Release
|
||||
|
||||
$spirvCrossPath = Join-Path $buildPath "spirv-cross.exe"
|
||||
if (-not (Test-Path $spirvCrossPath)) {
|
||||
throw "SPIRV-Cross executable was not found after build."
|
||||
}
|
||||
|
||||
& $spirvCrossPath --version
|
||||
Write-Host "SPIRV-Cross installed at $spirvCrossPath"
|
||||
7
packages/cultmath/tools/get-spirv-cross.ps1.meta
Normal file
7
packages/cultmath/tools/get-spirv-cross.ps1.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: dcf41da07502f4b439f8fc5ad25d16c9
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
packages/cultmath/unity.meta
Normal file
8
packages/cultmath/unity.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 366efeaef9f68224b80229332c7ecfc6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
10
packages/cultmath/unity/org.gamecult.cultmath/LICENSE.md
Normal file
10
packages/cultmath/unity/org.gamecult.cultmath/LICENSE.md
Normal file
|
|
@ -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
|
||||
<https://www.mozilla.org/MPL/2.0/>. 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`.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7172bce28532878409e57f5fbbb12719
|
||||
28
packages/cultmath/unity/org.gamecult.cultmath/README.md
Normal file
28
packages/cultmath/unity/org.gamecult.cultmath/README.md
Normal file
|
|
@ -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
|
||||
imports only the tracked auto-referenced precompiled assembly, so consumers do
|
||||
not need `csc.rsp` language overrides or an asmdef propagation shim.
|
||||
|
||||
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/CultLib.git?path=/packages/cultmath/unity/org.gamecult.cultmath"
|
||||
```
|
||||
|
||||
Production consumers should pin that URL to a commit or release tag.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b7429934026d747489a3876fd15576e5
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a31bf2bb35e54dd1ad1c765337682769
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f84156013e814c14b73f668b6610b9e0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
|
@ -0,0 +1,21 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 063b8a850f0f474aa7e89c4167f97a19
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a8cc555f643a4d7eb4dd340025b208f6
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ca5d773061ed49b3a173526048d40cb3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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<float> 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<float> 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
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a46136cb4acb4ff2bc47b6a4f786e4b3
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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: <https://github.com/ashima/webgl-noise>
|
||||
- License: MIT.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0c88669e4d1db604588be2a726b7d231
|
||||
17
packages/cultmath/unity/org.gamecult.cultmath/package.json
Normal file
17
packages/cultmath/unity/org.gamecult.cultmath/package.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "org.gamecult.cultmath",
|
||||
"displayName": "CultMath",
|
||||
"version": "0.1.2",
|
||||
"unity": "2021.3",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 33d8d4af49bf0f946ad00a375f26b1d1
|
||||
8
packages/cultmath/web.meta
Normal file
8
packages/cultmath/web.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: daf45471f8b5f4f44a2cb86e77e010db
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
Reference in a new issue