Complete portable CultMath runtime surface
This commit is contained in:
parent
26d9996d7b
commit
ab6a2c6456
68 changed files with 1130 additions and 23 deletions
7
CultMath.slnx.meta
Normal file
7
CultMath.slnx.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d310c959fd7a1d24a981ea6766ee3c59
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
6
Directory.Build.props
Normal file
6
Directory.Build.props
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<BaseOutputPath>$(MSBuildThisFileDirectory).tools\dotnet\bin\$(MSBuildProjectName)\</BaseOutputPath>
|
||||
<BaseIntermediateOutputPath>$(MSBuildThisFileDirectory).tools\dotnet\obj\$(MSBuildProjectName)\</BaseIntermediateOutputPath>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
7
Directory.Build.props.meta
Normal file
7
Directory.Build.props.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e041df73afea0094cae9b8d5dcc5e4eb
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
README.md.meta
Normal file
7
README.md.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 867daebafe0ce48479cf043461ee6bdf
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
docs.meta
Normal file
8
docs.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: de535b89253410b41b5989f712b80af4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
docs/design.md.meta
Normal file
7
docs/design.md.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 07021a8be816e3448a9490c10845b0aa
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
global.json.meta
Normal file
7
global.json.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 28e4ce693c46cbd4b990c5790745d62f
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
native.meta
Normal file
8
native.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9d1cb2ac88c530b4d9da456523115e12
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
native/cultmath-core.meta
Normal file
8
native/cultmath-core.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a42d65478346c354c8b4f353c5581b49
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
native/cultmath-core/Cargo.lock.meta
Normal file
7
native/cultmath-core/Cargo.lock.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f1c365f7eead2684ba621124e653bdab
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
native/cultmath-core/Cargo.toml.meta
Normal file
7
native/cultmath-core/Cargo.toml.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cdd2d4eab053b764b9a06d36b080e43e
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
native/cultmath-core/src.meta
Normal file
8
native/cultmath-core/src.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7de448b7e258d2c448bb7391051cc628
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
native/cultmath-core/src/lib.rs.meta
Normal file
7
native/cultmath-core/src/lib.rs.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b06c69c432a633f4584830d6b28d4473
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
14
package.json
Normal file
14
package.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "org.gamecult.cultmath",
|
||||
"displayName": "CultMath",
|
||||
"version": "0.1.0",
|
||||
"unity": "2021.3",
|
||||
"description": "Clean-room C# math primitives shaped for HLSL-style renderer and simulation code.",
|
||||
"keywords": [
|
||||
"GameCult",
|
||||
"Math",
|
||||
"Simulation"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"category": "Scripting"
|
||||
}
|
||||
7
package.json.meta
Normal file
7
package.json.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1abf066549a826c4ba7a30d6676735c0
|
||||
PackageManifestImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
shaders.meta
Normal file
8
shaders.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3bed74ebcba445543800218dbe185034
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -95,6 +95,18 @@ 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; }
|
||||
|
|
@ -251,4 +263,76 @@ float cultmath_value_noise_texture_bicubic(Texture2D<float> noise_texture, Sampl
|
|||
return cultmath_catmullrom(row0, row1, row2, row3, local.y);
|
||||
}
|
||||
|
||||
struct CultMathSphericalErosionParameters
|
||||
{
|
||||
float frequency; float amplitude; float lacunarity; float gain;
|
||||
float slope_strength; float detail; float seed; int octaves;
|
||||
};
|
||||
|
||||
struct CultMathSphericalErosionSample
|
||||
{
|
||||
float height_offset; float ridge; float gully; float3 gradient;
|
||||
};
|
||||
|
||||
float cultmath_spherical_cell_wave(float3 position, float3 flow, float3 cell, float seed)
|
||||
{
|
||||
float3 pivot = cell + float3(cultmath_hash(cell + seed + 11.7), cultmath_hash(cell.yzx + seed + 37.1), cultmath_hash(cell.zyx + seed + 73.9));
|
||||
return dot(position - pivot, flow) * 6.28318530718 + cultmath_hash(cell + seed + 101.3) * 6.28318530718;
|
||||
}
|
||||
|
||||
float cultmath_spherical_directional_wave(float3 position, float3 flow, float seed)
|
||||
{
|
||||
float3 cell = floor(position);
|
||||
float3 local = cultmath_frac(position);
|
||||
float3 weights = local * local * (3.0 - 2.0 * local);
|
||||
float z0 = cultmath_lerp(
|
||||
cultmath_lerp(cultmath_spherical_cell_wave(position, flow, cell + float3(0,0,0), seed), cultmath_spherical_cell_wave(position, flow, cell + float3(1,0,0), seed), weights.x),
|
||||
cultmath_lerp(cultmath_spherical_cell_wave(position, flow, cell + float3(0,1,0), seed), cultmath_spherical_cell_wave(position, flow, cell + float3(1,1,0), seed), weights.x), weights.y);
|
||||
float z1 = cultmath_lerp(
|
||||
cultmath_lerp(cultmath_spherical_cell_wave(position, flow, cell + float3(0,0,1), seed), cultmath_spherical_cell_wave(position, flow, cell + float3(1,0,1), seed), weights.x),
|
||||
cultmath_lerp(cultmath_spherical_cell_wave(position, flow, cell + float3(0,1,1), seed), cultmath_spherical_cell_wave(position, flow, cell + float3(1,1,1), seed), weights.x), weights.y);
|
||||
return cultmath_lerp(z0, z1, weights.z);
|
||||
}
|
||||
|
||||
float3 cultmath_spherical_stable_tangent(float3 position)
|
||||
{
|
||||
float3 axis = abs(position.z) < 0.8 ? float3(0,0,1) : float3(0,1,0);
|
||||
return cultmath_normalize(cross(axis, position));
|
||||
}
|
||||
|
||||
CultMathSphericalErosionSample cultmath_spherical_erosion(float3 unit_position, float base_height, float3 base_gradient, CultMathSphericalErosionParameters p)
|
||||
{
|
||||
float3 position = cultmath_normalize(unit_position);
|
||||
float3 gradient = base_gradient - position * dot(base_gradient, position);
|
||||
float slope = length(gradient);
|
||||
float3 flow = slope > 1.0e-6 ? -gradient / slope : cultmath_spherical_stable_tangent(position);
|
||||
float frequency = max(p.frequency, 0.001);
|
||||
float amplitude = p.amplitude;
|
||||
float combi_mask = 1.0 - pow(1.0 - cultmath_saturate(slope * p.slope_strength), 2.0);
|
||||
float fade_target = cultmath_saturate(base_height * 0.5 + 0.5) * 2.0 - 1.0;
|
||||
CultMathSphericalErosionSample result = (CultMathSphericalErosionSample)0;
|
||||
[loop] for (int octave = 0; octave < min(max(p.octaves, 0), 8); octave++)
|
||||
{
|
||||
float phase = cultmath_spherical_directional_wave(position * frequency, flow, p.seed + octave * 19.19);
|
||||
float wave = cos(phase);
|
||||
float wave_slope = sin(phase);
|
||||
float faded = cultmath_lerp(fade_target, wave, combi_mask);
|
||||
result.height_offset += faded * amplitude;
|
||||
result.ridge = max(result.ridge, cultmath_saturate(faded));
|
||||
result.gully = max(result.gully, cultmath_saturate(-faded));
|
||||
float3 across = cultmath_normalize(cross(position, flow));
|
||||
gradient += across * (sign(wave_slope) * amplitude * frequency * combi_mask);
|
||||
gradient -= position * dot(gradient, position);
|
||||
slope = length(gradient);
|
||||
flow = slope > 1.0e-6 ? -gradient / slope : cultmath_spherical_stable_tangent(position);
|
||||
float new_mask = 1.0 - pow(1.0 - cultmath_saturate(abs(wave_slope) * 1.35), 2.0);
|
||||
combi_mask = (1.0 - pow(1.0 - cultmath_saturate(combi_mask), max(p.detail, 0.001))) * new_mask;
|
||||
fade_target = faded;
|
||||
frequency *= max(p.lacunarity, 1.01);
|
||||
amplitude *= cultmath_saturate(p.gain);
|
||||
}
|
||||
result.gradient = gradient;
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
7
shaders/CultMath.hlsl.meta
Normal file
7
shaders/CultMath.hlsl.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 33174674795143c4a905408718f956f8
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
src.meta
Normal file
8
src.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 64f48d0cae0810e4ba9903e9094fe4c6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
src/CultMath.meta
Normal file
8
src/CultMath.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e857b457b93397d4fb4ad8481cb72e4c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace CultMath;
|
||||
|
|
@ -39,10 +40,19 @@ public static class BatchMath
|
|||
|
||||
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)
|
||||
{
|
||||
var px = new Vector<float>(positionX.Slice(i, lanes));
|
||||
var py = new Vector<float>(positionY.Slice(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);
|
||||
|
|
@ -53,8 +63,10 @@ public static class BatchMath
|
|||
var ax = Vector.ConditionalSelect(inRange, dx / distance * magnitude, zero);
|
||||
var ay = Vector.ConditionalSelect(inRange, dy / distance * magnitude, zero);
|
||||
|
||||
(new Vector<float>(accelerationX.Slice(i, lanes)) + ax).CopyTo(accelerationX.Slice(i, lanes));
|
||||
(new Vector<float>(accelerationY.Slice(i, lanes)) + ay).CopyTo(accelerationY.Slice(i, lanes));
|
||||
(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++)
|
||||
|
|
@ -75,6 +87,35 @@ public static class BatchMath
|
|||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
@ -102,25 +143,44 @@ public static class BatchMath
|
|||
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)
|
||||
{
|
||||
var mask = new Vector<float>(dynamicMask.Slice(i, lanes));
|
||||
var vx = new Vector<float>(velocityX.Slice(i, lanes));
|
||||
var vy = new Vector<float>(velocityY.Slice(i, lanes));
|
||||
var px = new Vector<float>(positionX.Slice(i, lanes));
|
||||
var py = new Vector<float>(positionY.Slice(i, lanes));
|
||||
var ax = new Vector<float>(accelerationX.Slice(i, lanes));
|
||||
var ay = new Vector<float>(accelerationY.Slice(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(velocityX.Slice(i, lanes));
|
||||
vy.CopyTo(velocityY.Slice(i, lanes));
|
||||
px.CopyTo(positionX.Slice(i, lanes));
|
||||
py.CopyTo(positionY.Slice(i, lanes));
|
||||
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++)
|
||||
|
|
@ -133,6 +193,28 @@ public static class BatchMath
|
|||
}
|
||||
}
|
||||
|
||||
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];
|
||||
|
|
@ -144,4 +226,20 @@ public static class BatchMath
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
src/CultMath/BatchMath.cs.meta
Normal file
2
src/CultMath/BatchMath.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d4771320e190bdd408dd296c293a9d0e
|
||||
2
src/CultMath/Color32.cs.meta
Normal file
2
src/CultMath/Color32.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bfa14a9aacb8ac4448761568643f2bc6
|
||||
14
src/CultMath/CultMath.asmdef
Normal file
14
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
src/CultMath/CultMath.asmdef.meta
Normal file
7
src/CultMath/CultMath.asmdef.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f0b66b909d13e18468f7aa56221531e9
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFrameworks>net8.0;netstandard2.1</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<RootNamespace>CultMath</RootNamespace>
|
||||
<AssemblyName>CultMath</AssemblyName>
|
||||
<PackageId>CultMath</PackageId>
|
||||
|
|
|
|||
7
src/CultMath/CultMath.csproj.meta
Normal file
7
src/CultMath/CultMath.csproj.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 77e1a0a2512910f41885650a7d0d6f60
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
2
src/CultMath/CultMathTone.cs.meta
Normal file
2
src/CultMath/CultMathTone.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d4bfc66f9f7e992448f884c2c338d50a
|
||||
5
src/CultMath/IsExternalInit.cs
Normal file
5
src/CultMath/IsExternalInit.cs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
namespace System.Runtime.CompilerServices;
|
||||
|
||||
internal static class IsExternalInit
|
||||
{
|
||||
}
|
||||
2
src/CultMath/IsExternalInit.cs.meta
Normal file
2
src/CultMath/IsExternalInit.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: de2b3c3e7f03c354aa2b84fbbc55b497
|
||||
62
src/CultMath/Random.cs
Normal file
62
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
src/CultMath/Random.cs.meta
Normal file
2
src/CultMath/Random.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7c1a557259026da4ca20b54264852c13
|
||||
108
src/CultMath/SphericalErosion.cs
Normal file
108
src/CultMath/SphericalErosion.cs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
namespace CultMath;
|
||||
|
||||
public readonly record struct SphericalErosionParameters(
|
||||
float Frequency,
|
||||
float Amplitude,
|
||||
float Lacunarity,
|
||||
float Gain,
|
||||
float SlopeStrength,
|
||||
float Detail,
|
||||
float Seed,
|
||||
int Octaves)
|
||||
{
|
||||
public static SphericalErosionParameters Default => new(18.0f, 0.035f, 2.03f, 0.52f, 6.0f, 1.35f, 0.0f, 5);
|
||||
}
|
||||
|
||||
public readonly record struct SphericalErosionSample(float HeightOffset, float Ridge, float Gully, float3 Gradient);
|
||||
|
||||
/// <summary>
|
||||
/// Stateless, point-evaluable erosion-like synthesis on a unit sphere.
|
||||
/// The matching shader body is cultmath_spherical_erosion in CultMath.hlsl.
|
||||
/// Position and gradient are planet-space; the gradient is projected tangent
|
||||
/// internally so cube faces and tile request order never enter the result.
|
||||
/// </summary>
|
||||
public static class SphericalErosion
|
||||
{
|
||||
public static SphericalErosionSample Sample(
|
||||
float3 unitPosition,
|
||||
float baseHeight,
|
||||
float3 baseGradient,
|
||||
SphericalErosionParameters parameters)
|
||||
{
|
||||
var position = math.normalize(unitPosition);
|
||||
var gradient = baseGradient - position * math.dot(baseGradient, position);
|
||||
var slope = math.length(gradient);
|
||||
var flow = slope > 1.0e-6f ? -gradient / slope : StableTangent(position);
|
||||
var frequency = math.max(parameters.Frequency, 0.001f);
|
||||
var amplitude = parameters.Amplitude;
|
||||
var combiMask = EaseOut(math.saturate(slope * parameters.SlopeStrength));
|
||||
var fadeTarget = math.saturate(baseHeight * 0.5f + 0.5f) * 2.0f - 1.0f;
|
||||
var heightOffset = 0.0f;
|
||||
var ridge = 0.0f;
|
||||
var gully = 0.0f;
|
||||
|
||||
var octaveCount = math.min(math.max(parameters.Octaves, 0), 8);
|
||||
for (var octave = 0; octave < octaveCount; octave++)
|
||||
{
|
||||
var phase = DirectionalCellWave(position * frequency, flow, parameters.Seed + octave * 19.19f);
|
||||
var wave = math.cos(phase);
|
||||
var waveSlope = math.sin(phase);
|
||||
var faded = math.lerp(fadeTarget, wave, combiMask);
|
||||
heightOffset += faded * amplitude;
|
||||
ridge = math.max(ridge, math.saturate(faded));
|
||||
gully = math.max(gully, math.saturate(-faded));
|
||||
|
||||
var across = math.normalize(math.cross(position, flow));
|
||||
gradient += across * (math.sign(waveSlope) * amplitude * frequency * combiMask);
|
||||
gradient -= position * math.dot(gradient, position);
|
||||
slope = math.length(gradient);
|
||||
flow = slope > 1.0e-6f ? -gradient / slope : StableTangent(position);
|
||||
|
||||
var newMask = EaseOut(math.saturate(math.abs(waveSlope) * 1.35f));
|
||||
combiMask = PowInverse(combiMask, parameters.Detail) * newMask;
|
||||
fadeTarget = faded;
|
||||
frequency *= math.max(parameters.Lacunarity, 1.01f);
|
||||
amplitude *= math.saturate(parameters.Gain);
|
||||
}
|
||||
|
||||
return new SphericalErosionSample(heightOffset, ridge, gully, gradient);
|
||||
}
|
||||
|
||||
private static float DirectionalCellWave(float3 position, float3 flow, float seed)
|
||||
{
|
||||
var cell = math.floor(position);
|
||||
var local = math.frac(position);
|
||||
var weights = local * local * (3.0f - 2.0f * local);
|
||||
var x00 = CellWave(position, flow, cell + new float3(0, 0, 0), seed);
|
||||
var x10 = CellWave(position, flow, cell + new float3(1, 0, 0), seed);
|
||||
var x01 = CellWave(position, flow, cell + new float3(0, 1, 0), seed);
|
||||
var x11 = CellWave(position, flow, cell + new float3(1, 1, 0), seed);
|
||||
var y00 = math.lerp(x00, x10, weights.x);
|
||||
var y10 = math.lerp(x01, x11, weights.x);
|
||||
var z0 = math.lerp(y00, y10, weights.y);
|
||||
var x00z = CellWave(position, flow, cell + new float3(0, 0, 1), seed);
|
||||
var x10z = CellWave(position, flow, cell + new float3(1, 0, 1), seed);
|
||||
var x01z = CellWave(position, flow, cell + new float3(0, 1, 1), seed);
|
||||
var x11z = CellWave(position, flow, cell + new float3(1, 1, 1), seed);
|
||||
var z1 = math.lerp(math.lerp(x00z, x10z, weights.x), math.lerp(x01z, x11z, weights.x), weights.y);
|
||||
return math.lerp(z0, z1, weights.z);
|
||||
}
|
||||
|
||||
private static float CellWave(float3 position, float3 flow, float3 cell, float seed)
|
||||
{
|
||||
var pivot = cell + new float3(
|
||||
math.hash(cell + seed + 11.7f),
|
||||
math.hash(cell.yzx + seed + 37.1f),
|
||||
math.hash(cell.zyx + seed + 73.9f));
|
||||
return math.dot(position - pivot, flow) * math.TAU + math.hash(cell + seed + 101.3f) * math.TAU;
|
||||
}
|
||||
|
||||
private static float3 StableTangent(float3 position)
|
||||
{
|
||||
var axis = math.abs(position.z) < 0.8f ? new float3(0, 0, 1) : new float3(0, 1, 0);
|
||||
return math.normalize(math.cross(axis, position));
|
||||
}
|
||||
|
||||
private static float EaseOut(float value) => 1.0f - (1.0f - value) * (1.0f - value);
|
||||
private static float PowInverse(float value, float power) => 1.0f - math.pow(1.0f - math.saturate(value), math.max(power, 0.001f));
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CultMath;
|
||||
|
|
|
|||
2
src/CultMath/Voronoi.cs.meta
Normal file
2
src/CultMath/Voronoi.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cb530f1428ac8ff43939b986986ae959
|
||||
18
src/CultMath/bool2.cs
Normal file
18
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
src/CultMath/bool2.cs.meta
Normal file
2
src/CultMath/bool2.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 725efb2725e987449a3782452f5a95e0
|
||||
25
src/CultMath/double2.cs
Normal file
25
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
src/CultMath/double2.cs.meta
Normal file
2
src/CultMath/double2.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bbe739a5f0216c3459762d2b5ee7401a
|
||||
36
src/CultMath/double3.cs
Normal file
36
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
src/CultMath/double3.cs.meta
Normal file
2
src/CultMath/double3.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1cbaafd219703ee43ae279aa5cbf407f
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
public readonly record struct float2(float x, float y)
|
||||
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);
|
||||
|
|
|
|||
2
src/CultMath/float2.cs.meta
Normal file
2
src/CultMath/float2.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fa066a0aed5494f499d462a2ebc51e50
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
public readonly record struct float3(float x, float y, float z)
|
||||
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);
|
||||
|
|
@ -10,9 +12,48 @@ public readonly record struct float3(float x, float y, float z)
|
|||
{
|
||||
}
|
||||
|
||||
public float2 xy => new(x, y);
|
||||
public float2 xz => new(x, z);
|
||||
public float2 yz => new(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
|
||||
{
|
||||
|
|
|
|||
2
src/CultMath/float3.cs.meta
Normal file
2
src/CultMath/float3.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 58a355dd1cf6b9f48a7595eea0c0e1f2
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
public readonly record struct float4(float x, float y, float z, float w)
|
||||
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);
|
||||
|
|
@ -15,7 +17,22 @@ public readonly record struct float4(float x, float y, float z, float w)
|
|||
{
|
||||
}
|
||||
|
||||
public float2 xy => new(x, y);
|
||||
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
|
||||
|
|
|
|||
2
src/CultMath/float4.cs.meta
Normal file
2
src/CultMath/float4.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fe24fbdab33538849bc6be0afbb0a692
|
||||
29
src/CultMath/int2.cs
Normal file
29
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
src/CultMath/int2.cs.meta
Normal file
2
src/CultMath/int2.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 577b174a998aaa4478ec26e7a6782f46
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using System;
|
||||
|
||||
namespace CultMath;
|
||||
|
||||
public static class math
|
||||
|
|
@ -8,13 +10,20 @@ public static class math
|
|||
|
||||
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 rect rect(float2 min, float2 max) => new(min, max);
|
||||
public static rect rect(float minX, float minY, float maxX, float maxY) => new(minX, minY, maxX, maxY);
|
||||
|
||||
public static float radians(float degrees) => degrees * (PI / 180.0f);
|
||||
public static float degrees(float radians) => radians * (180.0f / PI);
|
||||
|
|
@ -26,16 +35,26 @@ public static class math
|
|||
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));
|
||||
|
|
@ -52,11 +71,13 @@ public static class math
|
|||
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));
|
||||
|
|
@ -72,9 +93,20 @@ public static class math
|
|||
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));
|
||||
|
|
@ -130,10 +162,30 @@ public static class math
|
|||
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;
|
||||
|
|
|
|||
2
src/CultMath/math.cs.meta
Normal file
2
src/CultMath/math.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0d9b4cacc40d169429ee743d19577cb7
|
||||
68
src/CultMath/quaternion.cs
Normal file
68
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
src/CultMath/quaternion.cs.meta
Normal file
2
src/CultMath/quaternion.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 12f3627d9250dc348be9c1a1056ec7d0
|
||||
21
src/CultMath/rect.cs
Normal file
21
src/CultMath/rect.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
namespace CultMath;
|
||||
|
||||
public readonly record struct rect(float2 min, float2 max)
|
||||
{
|
||||
public rect(float minX, float minY, float maxX, float maxY)
|
||||
: this(math.float2(math.min(minX, maxX), math.min(minY, maxY)),
|
||||
math.float2(math.max(minX, maxX), math.max(minY, maxY)))
|
||||
{
|
||||
}
|
||||
|
||||
public float2 size => max - min;
|
||||
public float2 center => (min + max) * 0.5f;
|
||||
|
||||
public bool Contains(float2 point)
|
||||
{
|
||||
return point.x >= min.x &&
|
||||
point.x <= max.x &&
|
||||
point.y >= min.y &&
|
||||
point.y <= max.y;
|
||||
}
|
||||
}
|
||||
2
src/CultMath/rect.cs.meta
Normal file
2
src/CultMath/rect.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b00dc5f108967db47ac2f54a47033e33
|
||||
8
tests.meta
Normal file
8
tests.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4ca61dedbfb3c5d46b0acaf23202b5c2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
tests/CultMath.Tests.meta
Normal file
8
tests/CultMath.Tests.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bcee65f13f85daa45b0cf3fdaa901779
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
tests/CultMath.Tests/CultMath.Tests.csproj.meta
Normal file
7
tests/CultMath.Tests/CultMath.Tests.csproj.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 636051db121e19146a0baf92b790415c
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -4,6 +4,14 @@ namespace CultMath.Tests;
|
|||
|
||||
public sealed class HlslMirrorTests
|
||||
{
|
||||
[Fact]
|
||||
public void HlslMirrorPublishesSphericalErosionKernel()
|
||||
{
|
||||
var source = File.ReadAllText(FindHlslMirror());
|
||||
Assert.Contains("cultmath_spherical_erosion", source);
|
||||
Assert.Contains("CultMathSphericalErosionParameters", source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HlslMirrorPublishesCultMathPrimitives()
|
||||
{
|
||||
|
|
@ -23,6 +31,7 @@ public sealed class HlslMirrorTests
|
|||
"cultmath_distance",
|
||||
"cultmath_normalize",
|
||||
"cultmath_reflect",
|
||||
"cultmath_rotate",
|
||||
"cultmath_csum",
|
||||
"cultmath_decay",
|
||||
"cultmath_damp",
|
||||
|
|
|
|||
2
tests/CultMath.Tests/HlslMirrorTests.cs.meta
Normal file
2
tests/CultMath.Tests/HlslMirrorTests.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3d7eb9df849ea0148a71419bbfe2352c
|
||||
|
|
@ -23,6 +23,7 @@ public sealed class MathTests
|
|||
[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));
|
||||
|
|
@ -31,6 +32,112 @@ public sealed class MathTests
|
|||
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 RectNormalizesBoundsAndContainsPoints()
|
||||
{
|
||||
var viewport = math.rect(10.0f, 20.0f, -5.0f, -2.0f);
|
||||
|
||||
Assert.Equal(new float2(-5.0f, -2.0f), viewport.min);
|
||||
Assert.Equal(new float2(10.0f, 20.0f), viewport.max);
|
||||
Assert.Equal(new float2(15.0f, 22.0f), viewport.size);
|
||||
Assert.Equal(new float2(2.5f, 9.0f), viewport.center);
|
||||
Assert.True(viewport.Contains(new float2(0.0f, 0.0f)));
|
||||
Assert.False(viewport.Contains(new float2(11.0f, 0.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 RandomIsDeterministicAndUnityShaped()
|
||||
{
|
||||
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()
|
||||
{
|
||||
|
|
@ -55,12 +162,39 @@ public sealed class MathTests
|
|||
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);
|
||||
|
||||
|
|
|
|||
2
tests/CultMath.Tests/MathTests.cs.meta
Normal file
2
tests/CultMath.Tests/MathTests.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c7a709e5345da214aa650e8c796fcaff
|
||||
27
tests/CultMath.Tests/SphericalErosionTests.cs
Normal file
27
tests/CultMath.Tests/SphericalErosionTests.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
namespace CultMath.Tests;
|
||||
|
||||
public sealed class SphericalErosionTests
|
||||
{
|
||||
[Fact]
|
||||
public void SampleIsDeterministicAndScaleInvariant()
|
||||
{
|
||||
var parameters = SphericalErosionParameters.Default with { Seed = 42.0f };
|
||||
var a = SphericalErosion.Sample(new float3(0.31f, -0.74f, 0.59f), 0.2f, new float3(0.4f, -0.1f, 0.2f), parameters);
|
||||
var b = SphericalErosion.Sample(new float3(3.1f, -7.4f, 5.9f), 0.2f, new float3(0.4f, -0.1f, 0.2f), parameters);
|
||||
|
||||
Assert.Equal(a, b);
|
||||
Assert.InRange(a.HeightOffset, -0.08f, 0.08f);
|
||||
Assert.InRange(a.Ridge, 0.0f, 1.0f);
|
||||
Assert.InRange(a.Gully, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NearbySamplesRemainContinuousAcrossAChartBoundary()
|
||||
{
|
||||
var parameters = SphericalErosionParameters.Default;
|
||||
var left = SphericalErosion.Sample(math.normalize(new float3(1.0f, 0.99999f, 0.2f)), 0.1f, new float3(0.2f, -0.1f, 0.05f), parameters);
|
||||
var right = SphericalErosion.Sample(math.normalize(new float3(0.99999f, 1.0f, 0.2f)), 0.1f, new float3(0.2f, -0.1f, 0.05f), parameters);
|
||||
|
||||
Assert.InRange(math.abs(left.HeightOffset - right.HeightOffset), 0.0f, 0.0002f);
|
||||
}
|
||||
}
|
||||
8
tools.meta
Normal file
8
tools.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 697162910ffd9614ba9655d31af9afa4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
tools/compile-hlsl-spirv.ps1.meta
Normal file
7
tools/compile-hlsl-spirv.ps1.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b47e7016a99a5d148bc44c399c13c4d3
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
tools/compile-spirv-metal.ps1.meta
Normal file
7
tools/compile-spirv-metal.ps1.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6150c8f461e471b4c8641fd32d1984ef
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
tools/get-dxc.ps1.meta
Normal file
7
tools/get-dxc.ps1.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 86d0b9a1006042745b7fff523831049c
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
tools/get-spirv-cross.ps1.meta
Normal file
7
tools/get-spirv-cross.ps1.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: dcf41da07502f4b439f8fc5ad25d16c9
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in a new issue